UNPKG

wcz-layout

Version:

151 lines (122 loc) • 6.06 kB
# file ## Client `import { ... } from "wcz-layout/data/client";` - Query: `fileMetasQueryOptions(GetFileMetasSchema)` → `FileMeta[]` // metadata of all files under `subId` `fileThumbnailQueryOptions(FileSchema)` → `string` // thumbnail as object URL, disabled while `id` is empty `fileQueryOptions(FileSchema)` → `string` // file content as object URL for preview, disabled while `id` is empty - Mutation: `downloadFileMutationOptions()` → `mutate(FileSchema & { fileName, fileExtension })` // downloads and saves as `fileName.fileExtension` `openFileMutationOptions({ target })` → `mutate(FileSchema)` // opens file in new tab; `target` is a `useRef<Window | null>`, open `about:blank` into it synchronously in the click handler to avoid popup blockers `updateFileMetaMutationOptions({ queryClient })` → `mutate(UpdateFileMetaSchema)` // update metadata (e.g. rename `fileName`), invalidates metas `deleteFileMutationOptions({ queryClient })` → `mutate(FileSchema)` // delete single file, invalidates metas `batchDeleteFilesMutationOptions({ queryClient })` → `mutate(GetFileMetasSchema)` // delete all files under `subId`, invalidates metas `uploadFileMutationOptions({ queryClient })` → `mutate(UploadFileMetaSchema & { setProgress? })` → `{ id, appName, subId, fileName }` // uploads directly from browser via TUS (1 MB chunks), `setProgress(percent)` for a progress bar, invalidates metas of `subId` ## Server `import { ... } from "wcz-layout/data/server";` - Fetch: `getFileMetas({ data: GetFileMetasSchema })` → `FileMeta[]` // metadata of all files under `subId` `getFileThumbnail({ data: FileSchema })` → `Response` // thumbnail binary stream `getFile({ data: FileSchema })` → `Response` // file binary stream, `Content-Disposition: inline` `downloadFile({ data: FileSchema })` → `Response` // file binary stream for download - Mutate: `updateFileMeta({ data: UpdateFileMetaSchema })` → `void` // update metadata `deleteFile({ data: FileSchema })` → `void` // delete single file `batchDeleteFiles({ data: GetFileMetasSchema })` → `void` // delete all files under `subId` `uploadFile({ data: UploadFileMetaSchema })` → `{ id, appName, subId, fileName }` // server-originated uploads only, client uploads go direct via `uploadFileMutationOptions` ## Components `import { Dropzone, FileViewer } from "wcz-layout/components";` - `<Dropzone onDrop progress? sx? {...DropzoneOptions} />` // react-dropzone area (`accept`, `maxFiles`, `disabled`, `onDropAccepted`, …), `progress` (0-100) shows upload progress - `<FileViewer appName subId actions? onDelete?>{(component) => <component.Grid /> | <component.List />}</FileViewer>` // fetches metas itself, renders nothing when there are no files - `actions?: { download?: boolean, delete?: boolean }` // both enabled by default - `onDelete?: ({ remainingFileMetas: FileMeta[], deletedFileMeta: FileMeta }) => void` - `component.Grid` props: `size?: number` (default 150), `itemBar?: "hidden" | "always" | "onMouseEnter"` (default on hover), `sx?` - `component.List` props: `sx?` ## Schemas/Models ```ts const FileMetaSchema = z.object({ id: z.uuid(), subId: z.uuid(), appName: z.string().trim().min(1).max(255), fileName: z.string().trim().min(1).max(255), fileExtension: z.string().trim().min(1).max(255), fileSize: z.number().min(0), mediaSubType: z.string().trim().min(1).max(255), mediaType: z.string().trim().min(1).max(255), mimeType: z.string().trim().min(1).max(255), createdBy: z.string().trim().min(1).max(255), createdDate: z.date(), }); const GetFileMetasSchema = z.object({ appName: z.string().min(1).max(255), subId: z.uuid(), }); const FileSchema = z.object({ appName: z.string().min(1).max(255), id: z.uuid(), }); const UpdateFileMetaSchema = FileMetaSchema.extend({ fileName: z.string().min(1).max(255), }); const UploadFileMetaSchema = z.object({ id: z.uuid().default(uuidv7), appName: z.string().min(1).max(255), subId: z.uuid(), file: z.instanceof(File), }); ``` ## Examples ```tsx import { useRef, useState } from "react"; import { useMutation, useQuery } from "@tanstack/react-query"; import { deleteFileMutationOptions, downloadFileMutationOptions, fileMetasQueryOptions, fileQueryOptions, openFileMutationOptions, uploadFileMutationOptions, } from "wcz-layout/data/client"; import { Dropzone, FileViewer } from "wcz-layout/components"; import { useInView } from "wcz-layout/hooks"; const { queryClient } = Route.useRouteContext(); // fetch metas (lazily when in view) + fetch first file const { ref, inView } = useInView({ triggerOnce: true }); const { data: fileMetas = [] } = useQuery({ ...fileMetasQueryOptions({ appName: packageJson.name, subId }), enabled: inView, }); const { data: file } = useQuery({ ...fileQueryOptions({ appName: packageJson.name, id: fileMetas[0]?.id ?? "" }), enabled: !!fileMetas[0], }); // upload via Dropzone const [progress, setProgress] = useState(0); const { mutateAsync: uploadFile } = useMutation(uploadFileMutationOptions({ queryClient })); const onDrop = async (acceptedFiles: File[]) => { await Promise.allSettled( acceptedFiles.map((file) => uploadFile({ appName: packageJson.name, subId, file, setProgress }), ), ); setProgress(0); }; <Dropzone onDrop={onDrop} progress={progress} />; // download / delete const { mutate: download } = useMutation(downloadFileMutationOptions()); const { mutate: remove } = useMutation(deleteFileMutationOptions({ queryClient })); download({ appName: packageJson.name, id: meta.id, fileName: meta.fileName, fileExtension: meta.fileExtension, }); remove({ appName: packageJson.name, id: meta.id }); // open file const target = useRef<Window | null>(null); const { mutate: openFile } = useMutation(openFileMutationOptions({ target })); // File Viewer - no manual fetch of metas needed <FileViewer appName={packageJson.name} subId={subId}> {(component) => <component.Grid />} // or component.List </FileViewer>; ```