file uploads
This commit is contained in:
66
frontend/components/actions/CreateDirectoryDialog.vue
Normal file
66
frontend/components/actions/CreateDirectoryDialog.vue
Normal file
@@ -0,0 +1,66 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
Dialog,
|
||||
DialogTrigger,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
} from '@/components/ui/dialog';
|
||||
import { createDirectory } from '~/lib/api/warrens';
|
||||
|
||||
const warrenRoute = useWarrenRoute();
|
||||
|
||||
const creating = ref(false);
|
||||
const open = ref(false);
|
||||
const directoryName = ref('');
|
||||
|
||||
async function submit() {
|
||||
creating.value = true;
|
||||
|
||||
const { success } = await createDirectory(
|
||||
warrenRoute.value,
|
||||
directoryName.value
|
||||
);
|
||||
|
||||
creating.value = false;
|
||||
|
||||
if (success) {
|
||||
directoryName.value = '';
|
||||
open.value = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Dialog v-model:open="open">
|
||||
<DialogTrigger as-child>
|
||||
<slot />
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Create a directory</DialogTitle>
|
||||
<DialogDescription
|
||||
>Give your directory a memorable name</DialogDescription
|
||||
>
|
||||
</DialogHeader>
|
||||
|
||||
<Input
|
||||
v-model="directoryName"
|
||||
type="text"
|
||||
name="directory-name"
|
||||
placeholder="my-awesome-directory"
|
||||
minlength="20"
|
||||
maxlength="30"
|
||||
aria-required="true"
|
||||
autocomplete="off"
|
||||
required
|
||||
/>
|
||||
|
||||
<DialogFooter>
|
||||
<Button :disabled="creating" @click="submit">Create</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
196
frontend/components/actions/UploadDialog.vue
Normal file
196
frontend/components/actions/UploadDialog.vue
Normal file
@@ -0,0 +1,196 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
Dialog,
|
||||
DialogTrigger,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
} from '@/components/ui/dialog';
|
||||
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import byteSize from 'byte-size';
|
||||
import { uploadToWarren } from '~/lib/api/warrens';
|
||||
const warrenRoute = useWarrenRoute();
|
||||
|
||||
const fileInputElement = ref<HTMLInputElement>(
|
||||
null as unknown as HTMLInputElement
|
||||
);
|
||||
|
||||
const uploading = ref(false);
|
||||
const open = ref(false);
|
||||
const files = ref<Array<{ uploaded: boolean; data: File }>>([]);
|
||||
const presentPaths = new Set<string>();
|
||||
|
||||
function onFilesChanged(event: Event) {
|
||||
if (event.target == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const target = event.target as HTMLInputElement;
|
||||
|
||||
if (target.files == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const file of target.files) {
|
||||
if (presentPaths.has(file.name)) {
|
||||
continue;
|
||||
}
|
||||
files.value.push({ uploaded: false, data: file });
|
||||
presentPaths.add(file.name);
|
||||
}
|
||||
|
||||
fileInputElement.value.value = '';
|
||||
}
|
||||
|
||||
async function submit() {
|
||||
if (files.value.length < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
uploading.value = true;
|
||||
|
||||
const dt = new DataTransfer();
|
||||
for (const file of files.value) {
|
||||
if (!file.uploaded) {
|
||||
dt.items.add(file.data);
|
||||
}
|
||||
}
|
||||
|
||||
const { success } = await uploadToWarren(warrenRoute.value, dt.files);
|
||||
|
||||
uploading.value = false;
|
||||
if (success) {
|
||||
for (const file of files.value) {
|
||||
file.uploaded = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function removeFile(index: number) {
|
||||
if (files.value.length <= index) {
|
||||
return;
|
||||
}
|
||||
|
||||
const [file] = files.value.splice(index, 1);
|
||||
presentPaths.delete(file.data.name);
|
||||
}
|
||||
|
||||
function clearCompletedFiles() {
|
||||
files.value = files.value.filter((f) => {
|
||||
if (f.uploaded) {
|
||||
presentPaths.delete(f.data.name);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Dialog v-model:open="open">
|
||||
<DialogTrigger as-child>
|
||||
<slot />
|
||||
</DialogTrigger>
|
||||
<DialogContent class="!min-w-[min(50%,98vw)] sm:!min-h-[unset]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Upload files</DialogTitle>
|
||||
<DialogDescription
|
||||
>Upload files to the current directory</DialogDescription
|
||||
>
|
||||
</DialogHeader>
|
||||
|
||||
<input
|
||||
ref="fileInputElement"
|
||||
class="hidden"
|
||||
type="file"
|
||||
multiple="true"
|
||||
@change="onFilesChanged"
|
||||
/>
|
||||
|
||||
<div
|
||||
class="flex min-h-[280px] sm:min-h-[unset] sm:aspect-video w-full items-center justify-center rounded-xl border overflow-hidden"
|
||||
>
|
||||
<ScrollArea v-if="files.length > 0" class="h-full w-full">
|
||||
<div
|
||||
class="flex h-full w-full flex-col items-stretch gap-1 text-left p-2"
|
||||
>
|
||||
<div
|
||||
v-for="(file, i) in files"
|
||||
:key="file.data.name"
|
||||
class="flex flex-row items-center justify-between gap-4 rounded-lg border px-4 py-2"
|
||||
>
|
||||
<div class="rounded-sm border p-3">
|
||||
<Icon
|
||||
v-if="!file.uploaded"
|
||||
class="h-5 w-5"
|
||||
:name="getFileIcon(file.data.type)"
|
||||
/>
|
||||
<Icon
|
||||
v-else
|
||||
class="h-5 w-5 text-green-300"
|
||||
name="lucide:circle-check"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-col grow overflow-hidden">
|
||||
<span class="font-medium truncate">{{
|
||||
file.data.name
|
||||
}}</span>
|
||||
<span class="text-muted-foreground">{{
|
||||
byteSize(file.data.size)
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="flex flex-row gap-2 items-center">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
:disabled="uploading"
|
||||
@click="() => removeFile(i)"
|
||||
>
|
||||
<Icon name="lucide:x" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ScrollArea>
|
||||
<div
|
||||
v-else
|
||||
class="flex h-full w-full items-center justify-center"
|
||||
:disabled="fileInputElement == null"
|
||||
@click="() => files.length < 1 && fileInputElement?.click()"
|
||||
>
|
||||
<Icon
|
||||
class="h-[25%] w-[25%] opacity-25"
|
||||
name="lucide:upload"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
<Button
|
||||
class="sm:mr-auto"
|
||||
variant="outline"
|
||||
:disabled="files.every((f) => !f.uploaded)"
|
||||
@click="clearCompletedFiles"
|
||||
>
|
||||
Clear completed
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
:disabled="fileInputElement == null || uploading"
|
||||
@click="() => fileInputElement?.click()"
|
||||
>
|
||||
Select files
|
||||
</Button>
|
||||
<Button
|
||||
:disabled="uploading || !files.some((f) => !f.uploaded)"
|
||||
@click="submit"
|
||||
>Upload</Button
|
||||
>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
Reference in New Issue
Block a user