diff --git a/frontend/src/routes/+page.svelte b/frontend/src/routes/+page.svelte index 3e51d4f2..6099189d 100644 --- a/frontend/src/routes/+page.svelte +++ b/frontend/src/routes/+page.svelte @@ -21,6 +21,8 @@ let tagNames = $state(untrack(() => data.filters?.tags || [])); const ACCEPTED_TYPES = ['application/pdf', 'image/jpeg', 'image/png', 'image/tiff']; let isDragging = $state(false); +let windowDragging = $state(false); +let dragCounter = 0; let isUploading = $state(false); let uploadMessages = $state<{ text: string; isError: boolean }[]>([]); let fileInput: HTMLInputElement; @@ -48,6 +50,8 @@ function handleDragLeave() { async function handleDrop(e: DragEvent) { e.preventDefault(); isDragging = false; + windowDragging = false; + dragCounter = 0; const files = Array.from(e.dataTransfer?.files ?? []); await uploadFiles(files); } @@ -144,6 +148,40 @@ $effect(() => { } }); +// Expand drop zone whenever a file is dragged anywhere over the browser window +$effect(() => { + if (!data.canWrite) return; + + function onWindowDragEnter(e: DragEvent) { + if (!e.dataTransfer?.types.includes('Files')) return; + dragCounter++; + windowDragging = true; + } + + function onWindowDragLeave() { + dragCounter--; + if (dragCounter <= 0) { + dragCounter = 0; + windowDragging = false; + } + } + + function onWindowDrop() { + dragCounter = 0; + windowDragging = false; + } + + window.addEventListener('dragenter', onWindowDragEnter); + window.addEventListener('dragleave', onWindowDragLeave); + window.addEventListener('drop', onWindowDrop); + + return () => { + window.removeEventListener('dragenter', onWindowDragEnter); + window.removeEventListener('dragleave', onWindowDragLeave); + window.removeEventListener('drop', onWindowDrop); + }; +}); + // Sync local state with server data after navigation. // Guard q: skip overwrite while the user is actively typing in the search field. $effect(() => { @@ -297,9 +335,11 @@ $effect(() => {