Files
BRETT/app/components/CommentAttachments.vue
T
2026-10-03 20:07:49 +02:00

127 lines
4.9 KiB
Vue

<script setup lang="ts">
import AudioPlayerDialog from '@/components/AudioPlayerDialog.vue'
import ImageLightbox from '@/components/ImageLightbox.vue'
import VoiceMessagePlayer from '@/components/VoiceMessagePlayer.vue'
import { Separator } from '@/components/ui/separator'
import { formatFileSize, isAudioAttachment, isImageAttachment, isVoiceAttachment } from '~~/shared/utils/attachments'
import type { CommentAttachment } from '~~/shared/utils/attachments'
const props = defineProps<{
attachments: CommentAttachment[]
compact?: boolean
}>()
const voices = computed(() => props.attachments.filter(isVoiceAttachment))
const images = computed(() => props.attachments.filter(a => isImageAttachment(a) && !isVoiceAttachment(a)))
const audios = computed(() => props.attachments.filter(a => isAudioAttachment(a) && !isImageAttachment(a) && !isVoiceAttachment(a)))
const files = computed(() => props.attachments.filter(a => !isImageAttachment(a) && !isAudioAttachment(a) && !isVoiceAttachment(a)))
const hasFileAttachments = computed(() => images.value.length + audios.value.length + files.value.length > 0)
const lightboxSrc = ref<string | null>(null)
const lightboxAlt = ref('')
const audioAtt = ref<CommentAttachment | null>(null)
function fileIcon(a: CommentAttachment) {
const mime = a.mimeType ?? ''
const name = a.name.toLowerCase()
if (mime.startsWith('video/')) return 'ph:file-video-fill'
if (mime === 'application/pdf' || /\.(txt|md|pdf|docx?|odt)$/.test(name)) return 'ph:file-text-fill'
if (/\.(zip|rar|7z|tar|gz|bz2|xz)$/.test(name) || /zip|compressed|x-tar|gzip/.test(mime)) return 'ph:file-archive-fill'
return 'ph:file-fill'
}
</script>
<template>
<!-- Kompakt-Modus (Sidebar): nur ein Hinweis-Chip -->
<span
v-if="compact && attachments.length"
class="mx-0.5 inline-flex items-center gap-1 rounded-sm bg-muted px-1.5 py-0.5 align-middle text-xs text-muted-foreground"
>
<Icon name="ph:folder-plus-fill" class="size-3.5" />
{{ attachments.length }}
</span>
<div v-else-if="attachments.length" class="mt-2.5 space-y-2.5">
<!-- Sprachnachrichten gehören direkt zum Kommentar, nicht in den Anhang-Bereich -->
<div v-if="voices.length" class="flex flex-col gap-1.5">
<VoiceMessagePlayer
v-for="a in voices"
:key="a.id"
:src="a.url"
:name="a.name"
/>
</div>
<template v-if="hasFileAttachments">
<div class="mb-1.5 flex items-center gap-2">
<span class="text-[11px] font-medium uppercase tracking-wider text-muted-foreground">Anhänge</span>
<Separator class="flex-1" />
</div>
<div class="flex flex-wrap items-center gap-1.5">
<button
v-for="a in images"
:key="a.id"
type="button"
class="group relative size-16 cursor-pointer overflow-hidden rounded-md border bg-muted transition-colors hover:border-primary"
:title="a.name"
@click.stop="lightboxSrc = a.url; lightboxAlt = a.name"
>
<NuxtImg
:src="a.url"
:alt="a.name"
width="64"
height="64"
fit="cover"
loading="lazy"
class="size-full object-cover"
/>
</button>
<button
v-for="a in audios"
:key="a.id"
type="button"
class="flex max-w-48 cursor-pointer items-center gap-1.5 rounded-md border bg-muted/50 px-2 py-1 text-xs transition-colors hover:border-primary hover:bg-accent"
:title="`${a.name} abspielen`"
@click.stop="audioAtt = a"
>
<img v-if="a.cover" :src="a.cover" :alt="a.name" class="size-5 shrink-0 rounded-sm object-cover">
<Icon name="ph:file-audio-fill" v-else class="size-3.5 shrink-0 text-muted-foreground" />
<span class="truncate">{{ a.name }}</span>
<span v-if="formatFileSize(a.size)" class="shrink-0 text-muted-foreground">{{ formatFileSize(a.size) }}</span>
<Icon name="ph:play-fill" class="size-3.5 shrink-0 text-muted-foreground" />
</button>
<a
v-for="a in files"
:key="a.id"
:href="a.url"
:download="a.name"
class="flex max-w-48 items-center gap-1.5 rounded-md border bg-muted/50 px-2 py-1 text-xs transition-colors hover:border-primary hover:bg-accent"
:title="a.name"
@click.stop
>
<Icon :name="fileIcon(a)" class="size-3.5 shrink-0 text-muted-foreground" />
<span class="truncate">{{ a.name }}</span>
<span v-if="formatFileSize(a.size)" class="shrink-0 text-muted-foreground">{{ formatFileSize(a.size) }}</span>
<Icon name="ph:download-fill" class="size-3.5 shrink-0 text-muted-foreground" />
</a>
<ImageLightbox
v-if="lightboxSrc"
:src="lightboxSrc"
:alt="lightboxAlt"
@close="lightboxSrc = null"
/>
<AudioPlayerDialog
v-if="audioAtt"
:open="true"
:src="audioAtt.url"
:name="audioAtt.name"
:size="audioAtt.size"
:cover="audioAtt.cover"
:meta="audioAtt.meta"
@update:open="audioAtt = null"
/>
</div>
</template>
</div>
</template>