feat: virtualized all images page
This commit is contained in:
@ -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,
|
||||||
|
9
frontend/src/pages/all-images/chunk.ts
Normal file
9
frontend/src/pages/all-images/chunk.ts
Normal 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;
|
||||||
|
};
|
@ -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>
|
||||||
|
Reference in New Issue
Block a user