add media detail dialog and infinite loading

This commit is contained in:
dswbx
2025-03-27 09:23:14 +01:00
parent 0424c08a9e
commit f6a511d998
17 changed files with 419 additions and 50 deletions

View File

@@ -2,11 +2,20 @@ import type { Api } from "bknd/client";
import type { RepoQueryIn } from "data";
import type { MediaFieldSchema } from "media/AppMedia";
import type { TAppMediaConfig } from "media/media-schema";
import { type ReactNode, createContext, useContext, useId } from "react";
import { useApi, useApiQuery, useInvalidate } from "ui/client";
import {
type ReactNode,
createContext,
useContext,
useId,
useEffect,
useRef,
useState,
} from "react";
import { useApi, useApiInfiniteQuery, useInvalidate } from "ui/client";
import { useEvent } from "ui/hooks/use-event";
import { Dropzone, type DropzoneProps, type DropzoneRenderProps, type FileState } from "./Dropzone";
import { mediaItemsToFileStates } from "./helper";
import { useInViewport } from "@mantine/hooks";
export type DropzoneContainerProps = {
children?: ReactNode;
@@ -36,30 +45,32 @@ export function DropzoneContainer({
const api = useApi();
const invalidate = useInvalidate();
const baseUrl = api.baseUrl;
const defaultQuery = {
limit: query?.limit ? query?.limit : props.maxItems ? props.maxItems : 50,
const pageSize = query?.limit ?? props.maxItems ?? 50;
const defaultQuery = (page: number) => ({
limit: pageSize,
offset: page * pageSize,
sort: "-id",
};
});
const entity_name = (media?.entity_name ?? "media") as "media";
//console.log("dropzone:baseUrl", baseUrl);
const selectApi = (api: Api) =>
const selectApi = (api: Api, page: number) =>
entity
? api.data.readManyByReference(entity.name, entity.id, entity.field, {
...defaultQuery,
...query,
where: {
reference: `${entity.name}.${entity.field}`,
entity_id: entity.id,
...query?.where,
},
...defaultQuery(page),
})
: api.data.readMany(entity_name, {
...defaultQuery,
...query,
...defaultQuery(page),
});
const $q = useApiQuery(selectApi, { enabled: initialItems !== false && !initialItems });
const $q = useApiInfiniteQuery(selectApi, {});
const getUploadInfo = useEvent((file) => {
const url = entity
@@ -88,27 +99,62 @@ export function DropzoneContainer({
const key = id + JSON.stringify(_initialItems);
return (
<Dropzone
key={id + key}
getUploadInfo={getUploadInfo}
handleDelete={handleDelete}
onUploaded={refresh}
onDeleted={refresh}
autoUpload
initialItems={_initialItems}
{...props}
>
{children
? (props) => (
<DropzoneContainerContext.Provider value={props}>
{children}
</DropzoneContainerContext.Provider>
)
: undefined}
</Dropzone>
<div className="flex flex-col gap-4">
<Dropzone
key={id + key}
getUploadInfo={getUploadInfo}
handleDelete={handleDelete}
onUploaded={refresh}
onDeleted={refresh}
autoUpload
initialItems={_initialItems}
footer={
<Footer
items={_initialItems.length}
length={$q._data?.[0]?.body.meta.count ?? 0}
onFirstVisible={() => $q.setSize($q.size + 1)}
/>
}
{...props}
>
{children
? (props) => (
<DropzoneContainerContext.Provider value={props}>
{children}
</DropzoneContainerContext.Provider>
)
: undefined}
</Dropzone>
</div>
);
}
const Footer = ({ items = 0, length = 0, onFirstVisible }) => {
const { ref, inViewport } = useInViewport();
const [visible, setVisible] = useState(0);
const lastItemsCount = useRef(-1);
useEffect(() => {
if (inViewport && items > lastItemsCount.current) {
lastItemsCount.current = items;
setVisible((v) => v + 1);
onFirstVisible();
}
}, [inViewport]);
const _len = length - items;
if (_len <= 0) return null;
return new Array(Math.max(length - items, 0)).fill(0).map((_, i) => (
<div
key={i}
ref={i === 0 ? ref : undefined}
className="w-[49%] md:w-60 bg-muted aspect-square"
>
{i === 0 ? (inViewport ? `load ${visible}` : "first") : "other"}
</div>
));
};
export function useDropzone() {
return useContext(DropzoneContainerContext);
}