feat: virtualized all images page

This commit is contained in:
2025-07-21 16:52:57 +01:00
parent d5594c6e32
commit 018f0e96d4
3 changed files with 59 additions and 14 deletions

View File

@ -26,7 +26,9 @@ type CategoriesSpecificData = {
export type SearchImageStore = { export type SearchImageStore = {
images: Accessor<UserImage[]>; images: Accessor<UserImage[]>;
sortedImages: Accessor<Record<string, UserImage[]>>; imagesByDate: Accessor<
Array<{ date: Date; images: JustTheImageWhatAreTheseNames }>
>;
userImages: Accessor<JustTheImageWhatAreTheseNames>; userImages: Accessor<JustTheImageWhatAreTheseNames>;
@ -53,22 +55,22 @@ export const SearchImageContextProvider: Component<ParentProps> = (props) => {
return d.ImageProperties; return d.ImageProperties;
}); });
const sortedImages = createMemo<ReturnType<SearchImageStore["sortedImages"]>>( const sortedImages = createMemo<ReturnType<SearchImageStore["imagesByDate"]>>(
() => { () => {
const d = data(); const d = data();
if (d == null) { if (d == null) {
return {}; return [];
} }
// Sorted by day. But we could potentially add more in the future. // Sorted by day. But we could potentially add more in the future.
const buckets: ReturnType<SearchImageStore["sortedImages"]> = {}; const buckets: Record<string, JustTheImageWhatAreTheseNames> = {};
for (const image of d.ImageProperties) { for (const image of d.UserImages) {
if (image.data.CreatedAt == null) { if (image.CreatedAt == null) {
continue; continue;
} }
const date = new Date(image.data.CreatedAt).toDateString(); const date = new Date(image.CreatedAt).toDateString();
if (!(date in buckets)) { if (!(date in buckets)) {
buckets[date] = []; buckets[date] = [];
} }
@ -76,7 +78,9 @@ export const SearchImageContextProvider: Component<ParentProps> = (props) => {
buckets[date].push(image); 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<ParentProps> = (props) => {
<SearchImageContext.Provider <SearchImageContext.Provider
value={{ value={{
images: imageData, images: imageData,
sortedImages, imagesByDate: sortedImages,
imagesWithProperties: imagesWithProperties, imagesWithProperties: imagesWithProperties,
userImages: () => data()?.UserImages ?? [], userImages: () => data()?.UserImages ?? [],
processingImages, processingImages,

View File

@ -0,0 +1,9 @@
export const chunkRows = <T>(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;
};

View File

@ -1,16 +1,35 @@
import { useSearchImageContext } from "@contexts/SearchImageContext"; 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 { createVirtualizer } from "@tanstack/solid-virtual";
import { ImageComponent } from "@components/image"; import { ImageComponent } from "@components/image";
import { chunkRows } from "./chunk";
type ImageOrDate =
| { type: "image"; ID: string[] }
| { type: "date"; date: Date };
export const AllImages: Component = () => { export const AllImages: Component = () => {
let scrollRef: HTMLDivElement | undefined; let scrollRef: HTMLDivElement | undefined;
const { userImages } = useSearchImageContext(); const { imagesByDate } = useSearchImageContext();
const items = () => {
const items: Array<ImageOrDate> = [];
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({ const rowVirtualizer = createVirtualizer({
count: userImages().length, count: items().length,
estimateSize: () => 400 / 3, estimateSize: () => 400,
getScrollElement: () => scrollRef!, getScrollElement: () => scrollRef!,
overscan: 3, overscan: 3,
}); });
@ -21,7 +40,20 @@ export const AllImages: Component = () => {
class={`h-[${rowVirtualizer.getTotalSize()}px] grid grid-cols-3 gap-4`} class={`h-[${rowVirtualizer.getTotalSize()}px] grid grid-cols-3 gap-4`}
> >
<For each={rowVirtualizer.getVirtualItems()}> <For each={rowVirtualizer.getVirtualItems()}>
{(item) => <ImageComponent ID={userImages()[item.index].ImageID} />} {(i) => {
const item = items()[i.index];
if (item.type === "image") {
return (
<For each={item.ID}>{(id) => <ImageComponent ID={id} />}</For>
);
} else {
return (
<h3 class="col-span-3 font-bold text-2xl">
{item.date.toDateString()}
</h3>
);
}
}}
</For> </For>
</div> </div>
</div> </div>