diff --git a/app/package.json b/app/package.json index b19a44e..71ca15f 100644 --- a/app/package.json +++ b/app/package.json @@ -3,7 +3,7 @@ "type": "module", "sideEffects": false, "bin": "./dist/cli/index.js", - "version": "0.7.0-rc.11", + "version": "0.7.1", "description": "Lightweight Firebase/Supabase alternative built to run anywhere — incl. Next.js, Remix, Astro, Cloudflare, Bun, Node, AWS Lambda & more.", "homepage": "https://bknd.io", "repository": { diff --git a/app/src/data/index.ts b/app/src/data/index.ts index db70e28..a5de079 100644 --- a/app/src/data/index.ts +++ b/app/src/data/index.ts @@ -28,3 +28,5 @@ export const DatabaseEvents = { export { MutatorEvents, RepositoryEvents }; export * as DataPermissions from "./permissions"; + +export { MediaField, type MediaFieldConfig, type MediaItem } from "media/MediaField"; diff --git a/app/src/ui/components/overlay/Popover.tsx b/app/src/ui/components/overlay/Popover.tsx index 09e71bd..563841b 100644 --- a/app/src/ui/components/overlay/Popover.tsx +++ b/app/src/ui/components/overlay/Popover.tsx @@ -47,7 +47,7 @@ export function Popover({
Promise; openFileInput: () => void; }; - dropzoneProps: Pick; + dropzoneProps: Pick; }; export type DropzoneProps = { getUploadInfo: (file: FileWithPath) => { url: string; headers?: Headers; method?: string }; handleDelete: (file: FileState) => Promise; initialItems?: FileState[]; + flow?: "start" | "end"; maxItems?: number; overwrite?: boolean; autoUpload?: boolean; onRejected?: (files: FileWithPath[]) => void; onDeleted?: (file: FileState) => void; - onUploaded?: (file: FileState) => void; + onUploaded?: (files: FileState[]) => void; placeholder?: { show?: boolean; text?: string; @@ -61,6 +62,7 @@ export function Dropzone({ getUploadInfo, handleDelete, initialItems = [], + flow = "start", maxItems, overwrite, autoUpload, @@ -133,7 +135,7 @@ export function Dropzone({ progress: 0 })); - return [..._prev, ...filteredFiles]; + return flow === "start" ? [...filteredFiles, ..._prev] : [..._prev, ...filteredFiles]; }); if (autoUpload) { @@ -164,6 +166,8 @@ export function Dropzone({ for (const file of pendingFiles) { await uploadFileProgress(file); } + setUploading(false); + onUploaded?.(files); } })(); } @@ -259,7 +263,6 @@ export function Dropzone({ if (xhr.status === 200) { //setFileState(file.path, "uploaded", 1); console.log("Upload complete"); - onUploaded?.(file); try { const response = JSON.parse(xhr.responseText); @@ -312,6 +315,11 @@ export function Dropzone({ } } + async function uploadFile(file: FileState) { + await uploadFileProgress(file); + onUploaded?.([file]); + } + const openFileInput = () => inputRef.current?.click(); const showPlaceholder = Boolean( placeholder?.show === true || !maxItems || (maxItems && files.length < maxItems) @@ -332,14 +340,15 @@ export function Dropzone({ showPlaceholder }, actions: { - uploadFile: uploadFileProgress, + uploadFile, deleteFile, openFileInput }, dropzoneProps: { maxItems, placeholder, - autoUpload + autoUpload, + flow } }; @@ -351,8 +360,12 @@ const DropzoneInner = ({ inputProps, state: { files, isOver, isOverAccepted, showPlaceholder }, actions: { uploadFile, deleteFile, openFileInput }, - dropzoneProps: { placeholder } + dropzoneProps: { placeholder, flow } }: DropzoneRenderProps) => { + const Placeholder = showPlaceholder && ( + + ); + return (
+ {flow === "start" && Placeholder} {files.map((file) => ( ))} - {showPlaceholder && ( - - )} + {flow === "end" && Placeholder}
diff --git a/app/src/ui/elements/media/DropzoneContainer.tsx b/app/src/ui/elements/media/DropzoneContainer.tsx index fa41f90..ba140ed 100644 --- a/app/src/ui/elements/media/DropzoneContainer.tsx +++ b/app/src/ui/elements/media/DropzoneContainer.tsx @@ -1,8 +1,9 @@ +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, useEntityQuery, useInvalidate } from "ui/client"; +import { useApi, useApiQuery, 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"; @@ -31,28 +32,32 @@ export function DropzoneContainer({ }: DropzoneContainerProps) { const id = useId(); const api = useApi(); - const baseUrl = api.baseUrl; const invalidate = useInvalidate(); - const limit = query?.limit ? query?.limit : props.maxItems ? props.maxItems : 50; + const baseUrl = api.baseUrl; + const defaultQuery = { + limit: query?.limit ? query?.limit : props.maxItems ? props.maxItems : 50, + sort: "-id" + }; const entity_name = (media?.entity_name ?? "media") as "media"; //console.log("dropzone:baseUrl", baseUrl); - const $q = useEntityQuery( - entity_name, - undefined, - { - ...query, - limit, - where: entity - ? { + const selectApi = (api: Api) => + entity + ? api.data.readManyByReference(entity.name, entity.id, entity.field, { + ...defaultQuery, + ...query, + where: { reference: `${entity.name}.${entity.field}`, entity_id: entity.id, ...query?.where } - : query?.where - }, - { enabled: !initialItems } - ); + }) + : api.data.readMany(entity_name, { + ...defaultQuery, + ...query + }); + + const $q = useApiQuery(selectApi, { enabled: !initialItems }); const getUploadInfo = useEvent((file) => { const url = entity @@ -67,10 +72,7 @@ export function DropzoneContainer({ }); const refresh = useEvent(async () => { - if (entity) { - invalidate((api) => api.data.readOne(entity.name, entity.id)); - } - await $q.mutate(); + await invalidate($q.promise.key({ search: false })); }); const handleDelete = useEvent(async (file: FileState) => {