Files
veridian/app/components/FileSelector.vue
T

114 lines
3.4 KiB
Vue

<script setup lang="ts">
import { nanoid } from 'nanoid';
import { assert } from '~~/utils/assert';
const inputRef = ref<HTMLInputElement | null>(null);
const files = defineModel<{
id: string;
name: string;
mimeType: string;
status: 'pending' | 'uploaded';
url: string;
}[]>({ required: false, default: [] });
const rawFiles = ref<{
name: string;
type: string;
file: File;
}[]>([]);
const triplit = useTriplitClient();
watch(rawFiles, async (newFiles, oldFiles) => {
const { user } = useAuth();
assert(user.value !== null);
const diff = newFiles.filter(f => !oldFiles.find(o => o.name === f.name));
if (diff.length === 0) return;
for (const file of diff) {
files.value.push({
id: nanoid(),
name: file.name,
mimeType: file.type,
status: 'pending',
url: URL.createObjectURL(file.file),
});
}
await Promise.all(diff.map(async file => {
const { url: uploadUrl, assetUrl } = await $fetch('/api/upload/presigned', {
method: 'POST',
body: JSON.stringify({
file: {
name: file.name,
mimeType: file.type,
}
}),
}) as { url: string; assetUrl: string };
const uploadResponse = await fetch(uploadUrl, {
method: 'PUT',
body: file.file,
});
assert(uploadResponse.ok, 'Failed to upload file');
await triplit.insert('files', {
id: files.value.find(f => f.name === file.name)!.id,
userId: user.value!.id,
name: file.name,
mimeType: file.type,
url: assetUrl,
})
files.value = files.value.map(f => {
if (f.name === file.name) {
return {
...f,
status: 'uploaded',
};
}
return f;
});
}));
})
const handleFileChange = (e: Event) => {
const inputFiles = (e.target! as HTMLInputElement).files;
if (inputFiles === null) return;
console.log(inputFiles);
rawFiles.value = [...inputFiles].map(file => ({
name: file.name,
type: file.type,
file: file,
}));
}
onMounted(() => {
inputRef.value?.addEventListener('change', handleFileChange);
});
onUnmounted(() => {
inputRef.value?.removeEventListener('change', handleFileChange);
});
</script>
<template>
<input ref="inputRef" type="file" multiple class="hidden" />
<Dropdown placement="top">
<template #default="{ toggle, setRef }">
<button :ref="setRef" @click="toggle"
class="flex items-center justify-center h-8.5 w-8.5 hover:bg-[var(--color-hover)] rounded-lg transition-colors duration-200 ease-[cubic-bezier(0.5,_1,_0.89,_1)]">
<span class="pointer-events-none i-mynaui-paperclip text-5 text-[var(--text-secondary)]"></span>
</button>
</template>
<template #dropdown="{ close }">
<button @click="inputRef?.click(); close()"
class="text-left px-3 py-1.5 items-center gap-1 hover:bg-[var(--color-hover)] rounded-lg transition-colors duration-150">
<span class="text-4.5 i-mynaui-file-plus"></span>
<span>Upload file</span>
</button>
</template>
</Dropdown>
</template>