feat(communication): add chat reactions emojis and images
This commit is contained in:
38
frontend/components/ChatImage.vue
Normal file
38
frontend/components/ChatImage.vue
Normal file
@@ -0,0 +1,38 @@
|
||||
<script setup>
|
||||
const props = defineProps({ api: { type: Function, required: true }, endpoint: { type: String, required: true }, message: { type: Object, required: true } })
|
||||
const dataUrl = ref(''), error = ref(''), loading = ref(false), expanded = ref(false)
|
||||
// Reserve dimensions while fetching so a new photo does not move below the composer.
|
||||
const imageStyle = computed(() => {
|
||||
const { width, height } = props.message.image
|
||||
const scale = expanded.value ? 1 : Math.min(1, 320 / width, 256 / height)
|
||||
return { width: `${Math.round(width * scale)}px`, aspectRatio: `${width} / ${height}`, maxHeight: expanded.value ? '70vh' : undefined }
|
||||
})
|
||||
let generation = 0
|
||||
async function load() {
|
||||
const version = ++generation
|
||||
dataUrl.value = ''; error.value = ''; expanded.value = false; loading.value = false
|
||||
if (props.message.pending || props.message.failed) return
|
||||
loading.value = true
|
||||
try {
|
||||
const result = await props.api(`${props.endpoint}/image?messageId=${encodeURIComponent(props.message.id)}`)
|
||||
if (version !== generation) return
|
||||
// Even a partner instance can only supply bounded raster data, never an external tracking URL.
|
||||
if (typeof result.dataUrl !== 'string' || result.dataUrl.length > 700000 || !/^data:image\/(webp|png|jpeg);base64,[A-Za-z0-9+/]+=*$/.test(result.dataUrl)) throw new Error('Ungültiges Bild')
|
||||
dataUrl.value = result.dataUrl
|
||||
} catch { if (version === generation) error.value = 'Bild derzeit nicht verfügbar.' }
|
||||
finally { if (version === generation) loading.value = false }
|
||||
}
|
||||
watch(() => [props.endpoint, props.message.id, props.message.pending, props.message.failed], load, { immediate: true })
|
||||
onBeforeUnmount(() => { ++generation; dataUrl.value = '' })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="my-2 max-w-full" :style="imageStyle">
|
||||
<button v-if="dataUrl" type="button" class="block max-w-full rounded-lg focus-visible:ring-2" :aria-label="expanded ? 'Bild verkleinern' : 'Bild vergrößern'" :aria-expanded="expanded" @click="expanded = !expanded">
|
||||
<img :src="dataUrl" :alt="message.image.name" :width="message.image.width" :height="message.image.height" class="h-auto max-w-full rounded-lg object-contain" :class="expanded ? 'max-h-[70vh]' : 'max-h-64'" />
|
||||
</button>
|
||||
<p v-else-if="loading" class="text-xs opacity-70" role="status">Bild wird geladen …</p>
|
||||
<p v-else-if="message.pending || message.failed" class="text-xs opacity-70">📷 {{ message.image.name }} · {{ message.failed ? 'Nicht zugestellt' : 'Wartet auf Zustellung' }}</p>
|
||||
<button v-else-if="error" type="button" class="text-xs underline" @click="load">{{ error }} Erneut laden</button>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user