From 018f0e96d4dc743f48c71ce3fda85c46c93deeea Mon Sep 17 00:00:00 2001 From: John Costa Date: Mon, 21 Jul 2025 16:52:57 +0100 Subject: [PATCH] feat: virtualized all images page --- frontend/src/contexts/SearchImageContext.tsx | 22 +++++----- frontend/src/pages/all-images/chunk.ts | 9 +++++ frontend/src/pages/all-images/index.tsx | 42 +++++++++++++++++--- 3 files changed, 59 insertions(+), 14 deletions(-) create mode 100644 frontend/src/pages/all-images/chunk.ts diff --git a/frontend/src/contexts/SearchImageContext.tsx b/frontend/src/contexts/SearchImageContext.tsx index 380cfce..3315466 100644 --- a/frontend/src/contexts/SearchImageContext.tsx +++ b/frontend/src/contexts/SearchImageContext.tsx @@ -26,7 +26,9 @@ type CategoriesSpecificData = { export type SearchImageStore = { images: Accessor; - sortedImages: Accessor>; + imagesByDate: Accessor< + Array<{ date: Date; images: JustTheImageWhatAreTheseNames }> + >; userImages: Accessor; @@ -53,22 +55,22 @@ export const SearchImageContextProvider: Component = (props) => { return d.ImageProperties; }); - const sortedImages = createMemo>( + const sortedImages = createMemo>( () => { const d = data(); if (d == null) { - return {}; + return []; } // Sorted by day. But we could potentially add more in the future. - const buckets: ReturnType = {}; + const buckets: Record = {}; - for (const image of d.ImageProperties) { - if (image.data.CreatedAt == null) { + for (const image of d.UserImages) { + if (image.CreatedAt == null) { continue; } - const date = new Date(image.data.CreatedAt).toDateString(); + const date = new Date(image.CreatedAt).toDateString(); if (!(date in buckets)) { buckets[date] = []; } @@ -76,7 +78,9 @@ export const SearchImageContextProvider: Component = (props) => { buckets[date].push(image); } - return buckets; + return Object.entries(buckets) + .map(([date, images]) => ({ date: new Date(date), images })) + .sort((a, b) => b.date.getTime() - a.date.getTime()); }, ); @@ -116,7 +120,7 @@ export const SearchImageContextProvider: Component = (props) => { data()?.UserImages ?? [], processingImages, diff --git a/frontend/src/pages/all-images/chunk.ts b/frontend/src/pages/all-images/chunk.ts new file mode 100644 index 0000000..c086390 --- /dev/null +++ b/frontend/src/pages/all-images/chunk.ts @@ -0,0 +1,9 @@ +export const chunkRows = (chunkSize: number, data: T[]): T[][] => { + const chunks: T[][] = []; + + for (let i = 0; i < data.length / chunkSize; i++) { + chunks.push(data.slice(i * chunkSize, (i + 1) * chunkSize)); + } + + return chunks; +}; diff --git a/frontend/src/pages/all-images/index.tsx b/frontend/src/pages/all-images/index.tsx index 628553f..dc32969 100644 --- a/frontend/src/pages/all-images/index.tsx +++ b/frontend/src/pages/all-images/index.tsx @@ -1,16 +1,35 @@ import { useSearchImageContext } from "@contexts/SearchImageContext"; -import { Component, For } from "solid-js"; +import { Component, createEffect, For } from "solid-js"; import { createVirtualizer } from "@tanstack/solid-virtual"; import { ImageComponent } from "@components/image"; +import { chunkRows } from "./chunk"; + +type ImageOrDate = + | { type: "image"; ID: string[] } + | { type: "date"; date: Date }; export const AllImages: Component = () => { let scrollRef: HTMLDivElement | undefined; - const { userImages } = useSearchImageContext(); + const { imagesByDate } = useSearchImageContext(); + + const items = () => { + const items: Array = []; + + for (const { date, images } of imagesByDate()) { + items.push({ type: "date", date }); + const chunkedRows = chunkRows(3, images); + for (const chunk of chunkedRows) { + items.push({ type: "image", ID: chunk.map((c) => c.ImageID) }); + } + } + + return items; + }; const rowVirtualizer = createVirtualizer({ - count: userImages().length, - estimateSize: () => 400 / 3, + count: items().length, + estimateSize: () => 400, getScrollElement: () => scrollRef!, overscan: 3, }); @@ -21,7 +40,20 @@ export const AllImages: Component = () => { class={`h-[${rowVirtualizer.getTotalSize()}px] grid grid-cols-3 gap-4`} > - {(item) => } + {(i) => { + const item = items()[i.index]; + if (item.type === "image") { + return ( + {(id) => } + ); + } else { + return ( +

+ {item.date.toDateString()} +

+ ); + } + }}