2024-12-01 17:20:21 +01:00
|
|
|
<template>
|
|
|
|
|
<DropdownMenu>
|
2025-03-28 01:16:24 +01:00
|
|
|
<DropdownMenuTrigger
|
|
|
|
|
as="button"
|
2024-12-01 17:20:21 +01:00
|
|
|
:disabled="file.uploading || file.updating"
|
2025-03-28 01:16:24 +01:00
|
|
|
class="block bg-card text-card-foreground shadow-sm h-28 overflow-hidden rounded relative min-w-28 *:disabled:opacity-50"
|
|
|
|
|
>
|
2025-12-09 22:32:22 +01:00
|
|
|
<img
|
|
|
|
|
v-if="file.file?.type.startsWith('image/')"
|
|
|
|
|
:src="createObjectURL(file.file)"
|
|
|
|
|
class="object-contain h-28 w-full"
|
|
|
|
|
:alt="file.alt"
|
|
|
|
|
>
|
2026-01-09 21:47:12 +01:00
|
|
|
<FileIcon v-else class="size-6 m-auto text-muted-foreground" />
|
2025-03-28 01:16:24 +01:00
|
|
|
<Badge
|
2025-08-28 07:41:51 +02:00
|
|
|
v-if="file.file && !(file.uploading || file.updating)"
|
2025-03-28 01:16:24 +01:00
|
|
|
class="absolute bottom-1 right-1"
|
|
|
|
|
variant="default"
|
|
|
|
|
>
|
2025-12-09 22:32:22 +01:00
|
|
|
{{ formatBytes(file.file.size) }}
|
|
|
|
|
</Badge>
|
|
|
|
|
<Spinner
|
|
|
|
|
v-else-if="file.file"
|
|
|
|
|
class="absolute bottom-1 right-1 size-8 p-1.5"
|
|
|
|
|
/>
|
2024-12-01 17:20:21 +01:00
|
|
|
</DropdownMenuTrigger>
|
|
|
|
|
<DropdownMenuContent class="min-w-48">
|
2025-12-09 22:32:22 +01:00
|
|
|
<DropdownMenuLabel v-if="file.file">
|
|
|
|
|
{{ file.file.name }}
|
|
|
|
|
</DropdownMenuLabel>
|
2026-01-09 21:47:12 +01:00
|
|
|
<DropdownMenuSeparator />
|
2025-02-09 19:39:05 +01:00
|
|
|
<DropdownMenuItem @click="editCaption">
|
2026-01-09 21:47:12 +01:00
|
|
|
<Captions />
|
2025-06-27 00:28:14 +02:00
|
|
|
Add caption
|
2024-12-01 17:20:21 +01:00
|
|
|
</DropdownMenuItem>
|
2026-01-09 21:47:12 +01:00
|
|
|
<DropdownMenuSeparator />
|
2024-12-01 17:20:21 +01:00
|
|
|
<DropdownMenuItem @click="emit('remove')">
|
2026-01-09 21:47:12 +01:00
|
|
|
<Delete />
|
2025-06-27 00:28:14 +02:00
|
|
|
Remove
|
2024-12-01 17:20:21 +01:00
|
|
|
</DropdownMenuItem>
|
|
|
|
|
</DropdownMenuContent>
|
|
|
|
|
</DropdownMenu>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script lang="ts" setup>
|
2025-08-28 07:41:51 +02:00
|
|
|
import { Captions, Delete, FileIcon } from "lucide-vue-next";
|
2025-03-28 01:16:24 +01:00
|
|
|
import Spinner from "~/components/graphics/spinner.vue";
|
2024-12-01 17:20:21 +01:00
|
|
|
import { confirmModalService } from "~/components/modals/composable.ts";
|
|
|
|
|
import { Badge } from "~/components/ui/badge";
|
|
|
|
|
import {
|
|
|
|
|
DropdownMenu,
|
|
|
|
|
DropdownMenuContent,
|
|
|
|
|
DropdownMenuItem,
|
|
|
|
|
DropdownMenuLabel,
|
|
|
|
|
DropdownMenuSeparator,
|
|
|
|
|
DropdownMenuTrigger,
|
|
|
|
|
} from "~/components/ui/dropdown-menu";
|
2025-08-28 07:41:51 +02:00
|
|
|
import type { ComposerStateKey } from "~/stores/composer";
|
|
|
|
|
|
|
|
|
|
const { composerKey } = defineProps<{
|
|
|
|
|
composerKey: ComposerStateKey;
|
|
|
|
|
}>();
|
2024-12-01 17:20:21 +01:00
|
|
|
|
2025-08-28 07:41:51 +02:00
|
|
|
const file = defineModel<ComposerFile>("file", {
|
2024-12-01 17:20:21 +01:00
|
|
|
required: true,
|
|
|
|
|
});
|
|
|
|
|
|
2025-08-28 07:41:51 +02:00
|
|
|
const composerStore = useComposerStore(composerKey)();
|
|
|
|
|
|
2024-12-01 17:20:21 +01:00
|
|
|
const emit = defineEmits<{
|
|
|
|
|
remove: [];
|
|
|
|
|
}>();
|
|
|
|
|
|
|
|
|
|
const editCaption = async () => {
|
|
|
|
|
const result = await confirmModalService.confirm({
|
|
|
|
|
title: "Enter a caption",
|
|
|
|
|
message:
|
|
|
|
|
"Captions are useful for people with visual impairments, or when the image can't be displayed.",
|
|
|
|
|
defaultValue: file.value.alt,
|
|
|
|
|
confirmText: "Add",
|
|
|
|
|
inputType: "textarea",
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (result.confirmed) {
|
2025-08-28 07:41:51 +02:00
|
|
|
await composerStore.updateFileDescription(
|
|
|
|
|
file.value.id,
|
|
|
|
|
result.value ?? "",
|
|
|
|
|
);
|
2024-12-01 17:20:21 +01:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const createObjectURL = URL.createObjectURL;
|
|
|
|
|
|
|
|
|
|
const formatBytes = (bytes: number) => {
|
|
|
|
|
if (bytes === 0) {
|
|
|
|
|
return "0 Bytes";
|
|
|
|
|
}
|
|
|
|
|
const k = 1000;
|
|
|
|
|
const digitsAfterPoint = 2;
|
|
|
|
|
const sizes = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
|
|
|
|
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
2025-03-28 01:16:24 +01:00
|
|
|
return `${Number.parseFloat((bytes / k ** i).toFixed(digitsAfterPoint))} ${
|
|
|
|
|
sizes[i]
|
|
|
|
|
}`;
|
2024-12-01 17:20:21 +01:00
|
|
|
};
|
2025-02-09 19:39:05 +01:00
|
|
|
</script>
|