From d75ecf61b6cf5cdd99e52d7b57efdf6f5d632060 Mon Sep 17 00:00:00 2001 From: Szymon Nowakowski Date: Sun, 1 Dec 2024 22:16:53 +0100 Subject: [PATCH] Drag and drop attachments, implement attachment delete, update attachment list --- packages/frontend/src/routes/Piece.tsx | 150 ++++++++++++++----------- 1 file changed, 82 insertions(+), 68 deletions(-) diff --git a/packages/frontend/src/routes/Piece.tsx b/packages/frontend/src/routes/Piece.tsx index 1774102..97ae063 100644 --- a/packages/frontend/src/routes/Piece.tsx +++ b/packages/frontend/src/routes/Piece.tsx @@ -2,16 +2,17 @@ import { client } from "@/client"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; -import { FileReducer } from "@/FileReducer"; import { useLoading } from "@/hooks/useLoading"; +import { Updater } from "@/hooks/useStore"; import { timeout } from "@/lib/utils"; import { Label } from "@radix-ui/react-label"; import type { Attachment, Piece } from "backend/database"; +import clsx from "clsx"; import { PieceId } from "common"; -import { ACCEPTED_EXTENSIONS } from "common/MediaType"; +import { getMediaTypeForFilename } from "common/MediaType"; import { ELYSIA_FORM_DATA } from "elysia"; -import { Download, Loader2, Trash } from "lucide-react"; -import { FormEventHandler, MouseEvent, useCallback, useId, useReducer, useRef, useState } from "react"; +import { Download, Loader2, Trash, UploadCloud } from "lucide-react"; +import { DragEventHandler, FormEventHandler, MouseEvent, useCallback, useId, useState } from "react"; import { Link, useParams } from "react-router-dom"; export function Piece() { @@ -40,9 +41,9 @@ export function Piece() {

Utwór

Załączniki

- + - + )} ); @@ -143,6 +144,7 @@ namespace Attachments { export interface Props { readonly pieceId: PieceId; readonly attachments: readonly Attachment[]; + readonly setAttachments: Updater; } } @@ -160,7 +162,13 @@ function Attachments(props: Attachments.Props) { - {props.attachments.map((attachment) => )} + {props.attachments.map((attachment) => ( + + ))} @@ -170,6 +178,7 @@ function Attachments(props: Attachments.Props) { namespace AttachmentRow { export interface Props { readonly attachment: Attachment; + readonly setAttachments: Updater; } } @@ -218,6 +227,22 @@ function AttachmentRow(props: AttachmentRow.Props) { URL.revokeObjectURL(url); }, [props.attachment.mediaType, props.attachment.attachmentId, props.attachment.pieceId]); + const doDelete = useCallback(async () => { + + const { error } = await client + .piece({ pieceId: props.attachment.pieceId }) + .attachment({ attachmentId: props.attachment.attachmentId }) + .delete(); + + if (error !== null) { + console.error(error.value); + return; + } + + props.setAttachments((prev) => prev.filter((a) => a.attachmentId !== props.attachment.attachmentId)); + + }, [props.attachment.attachmentId, props.attachment.pieceId]); + return ( @@ -246,7 +271,7 @@ function AttachmentRow(props: AttachmentRow.Props) { - @@ -257,86 +282,75 @@ function AttachmentRow(props: AttachmentRow.Props) { namespace AttachmentForm { export interface Props { readonly pieceId: PieceId; + readonly setAttachments: Updater; } } function AttachmentForm(props: AttachmentForm.Props) { - const [{ filename, mediaType, file }, reduce] = useReducer(FileReducer, FileReducer.initial); - - const filenameId = useId(); - const mediaTypeId = useId(); - const fileId = useId(); - - const fileInputRef = useRef(null); - const [isLoading, setIsLoading] = useState(false); - const onSubmit: FormEventHandler = async (e) => { + const onDragOver: DragEventHandler = async (e) => { + e.preventDefault(); + e.dataTransfer.dropEffect = "copy"; + }; + + const onDrop: DragEventHandler = async (e) => { e.preventDefault(); + if (isLoading) { + return; + } + + const delay = timeout(250); try { setIsLoading(true); - const { error } = await client.piece({ pieceId: props.pieceId }).attachment.post({ - filename, - mediaType, - data: file!, - }); + for (const file of e.dataTransfer.files) { + const mediaType = getMediaTypeForFilename(file.name); + if (mediaType === undefined) { + continue; + } - if (error) { - console.error(error.value); - return; - } + const { data, error } = await client.piece({ pieceId: props.pieceId }).attachment.post({ + filename: file.name, + mediaType, + data: file, + }); - reduce(FileReducer.reset); - if (fileInputRef.current !== null) { - fileInputRef.current.files = null; + if (error !== null) { + console.error(error.value); + continue; + } + + props.setAttachments((prev) => { + const next = [...prev, data]; + next.sort((a, b) => a.filename.localeCompare(b.filename)); + return next; + }); } } finally { + await delay; setIsLoading(false); } } return ( -
-
- - reduce(FileReducer.setFilename(e.target.value))} - /> - - reduce(FileReducer.setMediaType(e.target.value))} - /> - - { - const file = e.target.files?.item(0) ?? null; - reduce(FileReducer.setFile(file)); - }} - accept={ACCEPTED_EXTENSIONS} - /> -
-
- -
-
+
+ {isLoading ? (<> + +
Wysyłanie załączników…
+ ) : (<> + +
Przeciągnij i upuść tutaj załączniki
+ )} +
); }