|
|
|
|
@@ -21,6 +21,8 @@ let tagNames = $state<string[]>(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(() => {
|
|
|
|
|
<div
|
|
|
|
|
role="button"
|
|
|
|
|
tabindex="0"
|
|
|
|
|
class="mb-4 flex cursor-pointer items-center justify-center gap-3 border border-dashed px-6 py-3 text-sm transition-colors duration-150 {isDragging
|
|
|
|
|
? 'border-primary bg-accent-bg text-primary'
|
|
|
|
|
: 'border-ink/20 text-ink-3 hover:border-primary hover:text-primary'}"
|
|
|
|
|
class="mb-4 flex cursor-pointer items-center justify-center gap-3 border border-dashed px-6 text-sm transition-all duration-200 {isDragging
|
|
|
|
|
? 'border-primary bg-accent-bg py-10 text-primary'
|
|
|
|
|
: windowDragging
|
|
|
|
|
? 'border-primary/60 bg-accent-bg/50 py-10 text-primary/80'
|
|
|
|
|
: 'border-ink/20 py-3 text-ink-3 hover:border-primary hover:text-primary'}"
|
|
|
|
|
ondragover={handleDragOver}
|
|
|
|
|
ondragleave={handleDragLeave}
|
|
|
|
|
ondrop={handleDrop}
|
|
|
|
|
|