file upload drop zones

This commit is contained in:
2025-07-29 21:17:39 +02:00
parent 45368dcc9a
commit b1409b44d1
5 changed files with 162 additions and 66 deletions

View File

@@ -1,8 +1,10 @@
<script setup lang="ts">
import { ScrollArea } from '@/components/ui/scroll-area';
import type { DirectoryEntry } from '#shared/types';
const { entries } = defineProps<{
const { entries, isOverDropZone } = defineProps<{
entries: DirectoryEntry[];
isOverDropZone?: boolean;
}>();
const { isLoading } = useLoadingIndicator();
@@ -14,6 +16,12 @@ const sortedEntries = computed(() =>
<template>
<ScrollArea class="h-full w-full">
<div
v-if="isOverDropZone"
class="bg-background/50 pointer-events-none absolute flex h-full w-full items-center justify-center"
>
<Icon class="size-16 animate-pulse" name="lucide:upload" />
</div>
<div class="flex flex-row flex-wrap gap-2">
<DirectoryEntry
v-for="entry in sortedEntries"

View File

@@ -15,6 +15,7 @@ import byteSize from 'byte-size';
import { uploadToWarren } from '~/lib/api/warrens';
import { toast } from 'vue-sonner';
import UploadListEntry from './UploadListEntry.vue';
import { useDropZone } from '@vueuse/core';
const warrenStore = useWarrenStore();
const uploadStore = useUploadStore();
@@ -32,7 +33,26 @@ const fileInputElement = ref<HTMLInputElement>(
);
const uploading = ref(false);
const presentPaths = new Set<string>();
const dropZoneRef = ref<HTMLElement>();
const dropZone = useDropZone(dropZoneRef, {
onDrop,
multiple: true,
});
function onDrop(files: File[] | null, event: DragEvent) {
event.preventDefault();
if (warrenStore.current == null || files == null || files.length < 1) {
return;
}
uploadStore.addFiles(
warrenStore.current.warrenId,
warrenStore.current.path,
files
);
}
function onFilesChanged(event: Event) {
if (warrenStore.current == null) {
@@ -52,58 +72,21 @@ function onFilesChanged(event: Event) {
return;
}
if (uploadStore.destination == null) {
uploadStore.destination = warrenStore.current;
} else if (!currentAndUploadRouteMatch.value) {
toast.warning('Upload', {
description:
'The unfinished items belong to a different directory. Remove them before attempting to upload to a different directory.',
});
return;
}
for (const file of target.files) {
if (presentPaths.has(file.name)) {
continue;
}
uploadStore.files.push({
status: 'not_uploaded',
data: file,
});
presentPaths.add(file.name);
}
uploadStore.addFiles(
warrenStore.current.warrenId,
warrenStore.current.path,
[...target.files]
);
fileInputElement.value.value = '';
}
function removeFile(index: number) {
if (uploadStore.files.length <= index) {
return;
}
const [file] = uploadStore.files.splice(index, 1);
presentPaths.delete(file.data.name);
if (uploadStore.files.length < 1) {
uploadStore.destination = null;
uploadStore.progress = null;
}
uploadStore.removeFile(index);
}
function clearCompletedFiles() {
uploadStore.files = uploadStore.files.filter((f) => {
if (f.status === 'completed') {
presentPaths.delete(f.data.name);
return false;
}
return true;
});
if (uploadStore.files.length < 1) {
uploadStore.destination = null;
uploadStore.progress = null;
}
uploadStore.clearCompletedFiles();
}
function updateFileListStatus(loadedBytes: number, ended: boolean = false) {
@@ -190,7 +173,7 @@ async function submit() {
</script>
<template>
<Dialog>
<Dialog v-model:open="uploadStore.dialogOpen">
<DialogTrigger as-child>
<slot />
</DialogTrigger>
@@ -220,6 +203,7 @@ async function submit() {
/>
<div
ref="dropZoneRef"
class="flex min-h-[280px] w-full items-center justify-center overflow-hidden rounded-xl border sm:aspect-video sm:min-h-[unset]"
>
<ScrollArea