diff --git a/app/src/data/api/DataController.ts b/app/src/data/api/DataController.ts index 1a44912..a315177 100644 --- a/app/src/data/api/DataController.ts +++ b/app/src/data/api/DataController.ts @@ -220,7 +220,7 @@ export class DataController extends Controller { return c.notFound(); } - const where = c.req.json() as any; + const where = (await c.req.json()) as any; const result = await this.em.repository(entity).count(where); return c.json({ entity, count: result.count }); } diff --git a/app/src/media/api/MediaApi.ts b/app/src/media/api/MediaApi.ts index bf3277b..c139435 100644 --- a/app/src/media/api/MediaApi.ts +++ b/app/src/media/api/MediaApi.ts @@ -44,7 +44,8 @@ export class MediaApi extends ModuleApi { return (await res.blob()) as File; } - getFileUploadUrl(file: FileWithPath): string { + getFileUploadUrl(file?: FileWithPath): string { + if (!file) return this.getUrl("/upload"); return this.getUrl(`/upload/${file.path}`); } diff --git a/app/src/ui/client/index.ts b/app/src/ui/client/index.ts index 0d9711c..30b1e21 100644 --- a/app/src/ui/client/index.ts +++ b/app/src/ui/client/index.ts @@ -10,4 +10,5 @@ export * from "./api/use-api"; export * from "./api/use-entity"; export { useAuth } from "./schema/auth/use-auth"; export { Api, type TApiUser, type AuthState, type ApiOptions } from "../../Api"; +export { FetchPromise } from "modules/ModuleApi"; export type { RepoQueryIn } from "data"; diff --git a/app/src/ui/elements/media/Dropzone.tsx b/app/src/ui/elements/media/Dropzone.tsx index 537cfe0..2c2b39d 100644 --- a/app/src/ui/elements/media/Dropzone.tsx +++ b/app/src/ui/elements/media/Dropzone.tsx @@ -1,3 +1,4 @@ +import type { DB } from "core"; import { type ComponentPropsWithRef, type ComponentPropsWithoutRef, @@ -23,6 +24,8 @@ export type FileState = { progress: number; }; +export type FileStateWithData = FileState & { data: DB["media"] }; + export type DropzoneRenderProps = { wrapperRef: RefObject; inputProps: ComponentPropsWithRef<"input">; @@ -50,7 +53,7 @@ export type DropzoneProps = { autoUpload?: boolean; onRejected?: (files: FileWithPath[]) => void; onDeleted?: (file: FileState) => void; - onUploaded?: (files: FileState[]) => void; + onUploaded?: (files: FileStateWithData[]) => void; placeholder?: { show?: boolean; text?: string; @@ -172,15 +175,16 @@ export function Dropzone({ setUploading(false); return; } else { + const uploaded: FileStateWithData[] = []; for (const file of pendingFiles) { try { - await uploadFileProgress(file); + uploaded.push(await uploadFileProgress(file)); } catch (e) { handleUploadError(e); } } setUploading(false); - onUploaded?.(files); + onUploaded?.(uploaded); } })(); } @@ -220,8 +224,8 @@ export function Dropzone({ setFiles((prev) => prev.filter((f) => f.path !== path)); } - function uploadFileProgress(file: FileState) { - return new Promise((resolve, reject) => { + function uploadFileProgress(file: FileState): Promise { + return new Promise((resolve, reject) => { if (!file.body) { console.error("File has no body"); reject(); @@ -279,17 +283,19 @@ export function Dropzone({ const response = JSON.parse(xhr.responseText); console.log("Response:", file, response); - console.log("New state", response.state); - replaceFileState(file.path, { + const newState = { ...response.state, progress: 1, state: "uploaded" - }); + }; + + replaceFileState(file.path, newState); + resolve({ ...response, ...file, ...newState }); } catch (e) { setFileState(file.path, "uploaded", 1); console.error("Error parsing response", e); + reject(e); } - resolve(); } else { setFileState(file.path, "failed", 1); console.error("Upload failed with status: ", xhr.status, xhr.statusText); @@ -327,8 +333,8 @@ export function Dropzone({ } async function uploadFile(file: FileState) { - await uploadFileProgress(file); - onUploaded?.([file]); + const result = await uploadFileProgress(file); + onUploaded?.([result]); } const openFileInput = () => inputRef.current?.click(); @@ -496,9 +502,9 @@ const Preview: React.FC = ({ file, handleUpload, handleDelete }) = />
-

{file.name}

+

{file.name}

- {file.type} + {file.type} {(file.size / 1024).toFixed(1)} KB
diff --git a/app/src/ui/elements/media/DropzoneContainer.tsx b/app/src/ui/elements/media/DropzoneContainer.tsx index ba140ed..ebb4a83 100644 --- a/app/src/ui/elements/media/DropzoneContainer.tsx +++ b/app/src/ui/elements/media/DropzoneContainer.tsx @@ -10,7 +10,7 @@ import { mediaItemsToFileStates } from "./helper"; export type DropzoneContainerProps = { children?: ReactNode; - initialItems?: MediaFieldSchema[]; + initialItems?: MediaFieldSchema[] | false; entity?: { name: string; id: number; @@ -18,6 +18,7 @@ export type DropzoneContainerProps = { }; media?: Pick; query?: RepoQueryIn; + randomFilename?: boolean; } & Omit, "children" | "initialItems">; const DropzoneContainerContext = createContext(undefined!); @@ -28,6 +29,7 @@ export function DropzoneContainer({ entity, query, children, + randomFilename, ...props }: DropzoneContainerProps) { const id = useId(); @@ -57,12 +59,12 @@ export function DropzoneContainer({ ...query }); - const $q = useApiQuery(selectApi, { enabled: !initialItems }); + const $q = useApiQuery(selectApi, { enabled: initialItems !== false && !initialItems }); const getUploadInfo = useEvent((file) => { const url = entity ? api.media.getEntityUploadUrl(entity.name, entity.id, entity.field) - : api.media.getFileUploadUrl(file); + : api.media.getFileUploadUrl(randomFilename ? undefined : file); return { url, @@ -79,7 +81,7 @@ export function DropzoneContainer({ return api.media.deleteFile(file.path); }); - const actualItems = initialItems ?? (($q.data || []) as MediaFieldSchema[]); + const actualItems = (initialItems || $q.data || []) as MediaFieldSchema[]; const _initialItems = mediaItemsToFileStates(actualItems, { baseUrl }); const key = id + JSON.stringify(_initialItems); diff --git a/app/src/ui/elements/media/index.ts b/app/src/ui/elements/media/index.ts index ff5c8f8..b0f64e6 100644 --- a/app/src/ui/elements/media/index.ts +++ b/app/src/ui/elements/media/index.ts @@ -12,6 +12,7 @@ export { useDropzone as useMediaDropzone }; export type { PreviewComponentProps, FileState, + FileStateWithData, DropzoneProps, DropzoneRenderProps } from "./Dropzone";