diff --git a/web/src/harmonie/Harmonie.tsx b/web/src/harmonie/Harmonie.tsx
index f444a9b..1212b5d 100644
--- a/web/src/harmonie/Harmonie.tsx
+++ b/web/src/harmonie/Harmonie.tsx
@@ -90,6 +90,7 @@ export const Harmonie: Component<{}> = () => {
diff --git a/web/src/harmonie/SlideShow.tsx b/web/src/harmonie/SlideShow.tsx
index 0283b83..8e3be9d 100644
--- a/web/src/harmonie/SlideShow.tsx
+++ b/web/src/harmonie/SlideShow.tsx
@@ -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
,
getCanvas: Accessor,
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
- { getImgList().map(([forecastDate, img]) =>
- - draw(img)}>
- { format(forecastDate) }
-
)}
-
+ 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 <>
+
+
+ { getImgList().map(([forecastDate, img], i) =>
+ - draw(i)}
+ >
+ { format(forecastDate) }
+
)}
+
+ >
}
function format(date: string) {
- return date.slice(11, 13)
- // return date
+ return date.slice(11, 13) // 2025-02-23T1500Z -> 15
}
\ No newline at end of file
diff --git a/web/src/harmonie/harmonie.module.css b/web/src/harmonie/harmonie.module.css
index bb2c8e1..d2a6fb4 100644
--- a/web/src/harmonie/harmonie.module.css
+++ b/web/src/harmonie/harmonie.module.css
@@ -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;
}
\ No newline at end of file
diff --git a/web/src/helpers/download.ts b/web/src/helpers/download.ts
new file mode 100644
index 0000000..0daeca8
--- /dev/null
+++ b/web/src/helpers/download.ts
@@ -0,0 +1,46 @@
+export async function downloadImagesAsZip(images: [string, ImageBitmap][]) {
+ downloadCompressedImage(images[0][1], '00.gzip')
+}
+
+async function compressImage(bitmap: ImageBitmap): Promise {
+ // 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((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);
+ }
\ No newline at end of file