SlideShow controls - prev, next, play and download (not finished)

This commit is contained in:
Guntis Smaukstelis
2025-02-23 22:08:26 +02:00
parent b6cc4a4b24
commit e099addd66
4 changed files with 135 additions and 14 deletions
+1
View File
@@ -90,6 +90,7 @@ export const Harmonie: Component<{}> = () => {
</div>
<div class={styles.column}>
<SlideShow
getIsLoading={getIsLoading}
getCanvas={getCanvas}
getImgList={getImgList}
/>
+67 -10
View File
@@ -1,32 +1,89 @@
import { Accessor, Component } from 'solid-js'
import { Accessor, Component, createSignal } from 'solid-js'
import styles from './harmonie.module.css'
import { downloadImagesAsZip } from '../helpers/download'
export const SlideShow: Component<{
getIsLoading: Accessor<boolean>,
getCanvas: Accessor<HTMLCanvasElement | undefined>,
getImgList: Accessor<[string, ImageBitmap | undefined][]>,
}> = ({
getIsLoading,
getCanvas,
getImgList
}) => {
function draw(img: ImageBitmap | undefined) {
const [getActive, setActive] = createSignal(-1)
const [getIsPlaying, setIsPlaying] = createSignal(false)
function draw(i: number) {
const canvas = getCanvas()!
const ctx = canvas.getContext('2d')!
ctx.clearRect(0, 0, canvas.width, canvas.height)
setActive(i)
const img = getImgList()[i][1]
if (!img) return;
ctx.drawImage(img, 0, 0)
}
return <ul class={styles.slideShow}>
{ getImgList().map(([forecastDate, img]) =>
<li class={img ? styles.withImg : ''} onClick={() => draw(img)}>
{ format(forecastDate) }
</li>)}
</ul>
function next(delta = 1) {
const count = getImgList().length
if (!count) return;
const result = (getActive() + delta) % count
const nextValue = result >= 0 ? result : count - 1
setActive(nextValue)
draw(nextValue)
}
function prev() { next(-1) }
function areControlsVisible(): boolean {
return getImgList().length > 0 && !getIsLoading()
}
let playingTimeout = 0
function play() {
if (getIsPlaying()) {
clearTimeout(playingTimeout)
setIsPlaying(false)
return;
}
function loop() {
next()
playingTimeout = setTimeout(loop, 300)
}
setIsPlaying(true)
loop()
}
function download() {
const imgs = getImgList().filter(([,img]) => !!img) as [string, ImageBitmap][]
downloadImagesAsZip(imgs)
}
return <>
<div class={styles.slideShowControls} style={{visibility: areControlsVisible() ? 'visible' : 'hidden'}}>
<div class={styles.leftButtons}>
<input type='button' value='prev' onClick={prev} />
<input type='button' value={getIsPlaying()?'pause':'play'} onClick={play} />
<input type='button' value='next' onClick={() => next()} />
</div>
<input type='button' value='download all' onClick={download} />
</div>
<ul class={styles.slideShowList}>
{ getImgList().map(([forecastDate, img], i) =>
<li
class={`${img?styles.withImg:''} ${i===getActive()?styles.active:''}`}
onClick={() => draw(i)}
>
{ format(forecastDate) }
</li>)}
</ul>
</>
}
function format(date: string) {
return date.slice(11, 13)
// return date
return date.slice(11, 13) // 2025-02-23T1500Z -> 15
}
+21 -4
View File
@@ -70,17 +70,34 @@
padding: 10px 3px;
}
.slideShow {
.slideShowControls {
display: flex;
justify-content: space-between;
}
.slideShowControls .leftButtons {
display: flex;
gap: 5px;
}
.slideShowList {
padding: 0;
}
.slideShow li {
.slideShowList li {
display: inline;
font-size: 14px;
padding: 3px 3px;
padding: 3px 1px;
border-radius: 5px;
border: 2px solid transparent;
}
.withImg {
background-color: rgb(187, 247, 187);
background-color: rgb(213, 248, 213);
}
.slideShowList li.active {
border: 2px solid green;
}
+46
View File
@@ -0,0 +1,46 @@
export async function downloadImagesAsZip(images: [string, ImageBitmap][]) {
downloadCompressedImage(images[0][1], '00.gzip')
}
async function compressImage(bitmap: ImageBitmap): Promise<Blob> {
// First convert ImageBitmap to WebP using canvas
const canvas = document.createElement('canvas');
canvas.width = bitmap.width;
canvas.height = bitmap.height;
const ctx = canvas.getContext('2d');
if (!ctx) throw new Error('Could not get canvas context');
ctx.drawImage(bitmap, 0, 0);
// Get WebP blob
const webpBlob = await new Promise<Blob>((resolve) => {
canvas.toBlob((blob) => {
if (blob) resolve(blob);
}, 'image/webp', 0.8); // Quality 0.8, adjust as needed
});
// Convert Blob to ReadableStream
const webpStream = webpBlob.stream();
// Apply GZIP compression
const compressedStream = webpStream.pipeThrough(
new CompressionStream('gzip')
);
// Return as Blob
return new Response(compressedStream).blob();
}
// Usage example:
async function downloadCompressedImage(bitmap: ImageBitmap, filename: string) {
const compressedBlob = await compressImage(bitmap);
const url = URL.createObjectURL(compressedBlob);
const link = document.createElement('a');
link.href = url;
link.download = filename;
link.click();
URL.revokeObjectURL(url);
}