176 lines
5.2 KiB
Vue
176 lines
5.2 KiB
Vue
<template>
|
|
<div ref="thumbnailRef" style="width: 100%; height: 100%">
|
|
<v-responsive v-if="isVideo && (source || poster)" :class="{ 'thumbnail-item': true, preview: previewOnly }">
|
|
<video :src="source" :poster="poster" :controls="!previewOnly" class="w-100 h-100">
|
|
{{ $t("fallbacks.video_file") }}
|
|
</video>
|
|
</v-responsive>
|
|
<ImageWithProgress
|
|
v-else-if="isImage"
|
|
:aspect-ratio="previewOnly ? 16 / 9 : undefined"
|
|
:class="{ 'thumbnail-item': true, preview: previewOnly }"
|
|
:src="source"
|
|
:contain="!previewOnly"
|
|
:cover="previewOnly"
|
|
:loadingProgress="loadingProgress"
|
|
/>
|
|
<div v-else :class="{ 'thumbnail-item': true, preview: previewOnly, 'file-item': true }">
|
|
<v-icon :class="fileTypeIconClass">{{ fileTypeIcon }}</v-icon>
|
|
<div class="file-name">{{ $$sanitize(fileName) }}</div>
|
|
<div class="file-size">{{ fileSize }}</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { singleOrDoubleTapRecognizer } from "../../plugins/touch";
|
|
import { computed, inject, onBeforeUnmount, onMounted, Ref, ref, useTemplateRef, watch } from "vue";
|
|
import { EventAttachment } from "../../models/eventAttachment";
|
|
import { useThumbnail } from "../messages/composition/useThumbnail";
|
|
import { Attachment } from "../../models/attachment";
|
|
import ImageWithProgress from "../ImageWithProgress.vue";
|
|
|
|
function isEventAttachment(source: EventAttachment | Attachment | undefined): source is EventAttachment {
|
|
return (source as EventAttachment)?.event !== undefined;
|
|
}
|
|
|
|
function isAttachment(source: EventAttachment | Attachment | undefined): source is Attachment {
|
|
return (source as Attachment)?.file !== undefined;
|
|
}
|
|
|
|
const $$sanitize: any = inject("globalSanitize");
|
|
|
|
const thumbnailRef = useTemplateRef("thumbnailRef");
|
|
|
|
interface ThumbnailProps {
|
|
item?: EventAttachment | Attachment;
|
|
previewOnly?: boolean;
|
|
}
|
|
|
|
type ThumbnailEmits = {
|
|
(event: "itemclick", value: { item: EventAttachment | Attachment }): void;
|
|
};
|
|
|
|
const props = defineProps<ThumbnailProps>();
|
|
const { item, previewOnly = false } = props;
|
|
const emits = defineEmits<ThumbnailEmits>();
|
|
|
|
let { isVideo, isImage, fileTypeIcon, fileTypeIconClass, fileName, fileSize } = useThumbnail(isEventAttachment(props.item) ? props.item.event : isAttachment(props.item) ? props.item.file : undefined);
|
|
|
|
const fileURL: Ref<string | undefined> = ref(undefined);
|
|
const source: Ref<string | undefined> = ref(undefined);
|
|
const poster: Ref<string | undefined> = ref(undefined);
|
|
|
|
const updateSource = () => {
|
|
if (isEventAttachment(props.item)) {
|
|
const eventAttachment = props.item;
|
|
if (isVideo.value || eventAttachment.src) {
|
|
source.value = eventAttachment.src;
|
|
} else if (previewOnly) {
|
|
eventAttachment.loadThumbnail().then((url) => {
|
|
source.value = url.data;
|
|
})
|
|
} else if (isImage.value) {
|
|
eventAttachment.loadSrc().then((url) => {
|
|
source.value = url.data;
|
|
})
|
|
}
|
|
} else if (isAttachment(props.item)) {
|
|
const attachment = props.item;
|
|
if (fileURL.value) {
|
|
URL.revokeObjectURL(fileURL.value);
|
|
}
|
|
fileURL.value = URL.createObjectURL(attachment.file);
|
|
source.value = fileURL.value;
|
|
} else {
|
|
source.value = undefined;
|
|
}
|
|
};
|
|
|
|
const updatePoster = () => {
|
|
if (isEventAttachment(props.item) && isVideo.value) {
|
|
const eventAttachment = props.item;
|
|
if (eventAttachment.thumbnail) {
|
|
poster.value = eventAttachment.thumbnail;
|
|
} else {
|
|
eventAttachment
|
|
.loadThumbnail()
|
|
.then((url) => {
|
|
poster.value = url.data;
|
|
})
|
|
.catch(() => {
|
|
poster.value = undefined;
|
|
});
|
|
}
|
|
}
|
|
};
|
|
|
|
updateSource();
|
|
updatePoster();
|
|
|
|
|
|
watch(props, (props: ThumbnailProps) => {
|
|
const updates = useThumbnail(isEventAttachment(props.item) ? props.item.event : isAttachment(props.item) ? props.item.file : undefined);
|
|
isVideo.value = updates.isVideo.value;
|
|
isImage.value = updates.isImage.value;
|
|
fileTypeIcon = updates.fileTypeIcon;
|
|
fileTypeIconClass = updates.fileTypeIconClass;
|
|
fileName = updates.fileName;
|
|
fileSize = updates.fileSize;
|
|
updateSource();
|
|
updatePoster();
|
|
});
|
|
|
|
const loadingProgress = computed(() => {
|
|
if (isEventAttachment(item)) {
|
|
const eventAttachment = item;
|
|
return previewOnly ? eventAttachment.thumbnailProgress : eventAttachment.srcProgress;
|
|
}
|
|
return -1;
|
|
});
|
|
|
|
onMounted(() => {
|
|
if (thumbnailRef.value && item) {
|
|
const hammerInstance = singleOrDoubleTapRecognizer(thumbnailRef.value);
|
|
hammerInstance.on("singletap doubletap", (ev: any) => {
|
|
if (ev.type === "singletap") {
|
|
emits("itemclick", { item: item });
|
|
}
|
|
});
|
|
}
|
|
if (!previewOnly && item && isEventAttachment(item)) {
|
|
item.loadSrc();
|
|
}
|
|
});
|
|
|
|
onBeforeUnmount(() => {
|
|
if (fileURL.value) {
|
|
URL.revokeObjectURL(fileURL.value);
|
|
fileURL.value = undefined;
|
|
}
|
|
});
|
|
</script>
|
|
|
|
<style lang="scss">
|
|
@use "@/assets/css/chat.scss" as *;
|
|
|
|
.thumbnail-item {
|
|
width: 100%;
|
|
height: 100%;
|
|
margin: 0;
|
|
padding: 0;
|
|
}
|
|
|
|
.file-item {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 0.6rem;
|
|
flex-direction: column;
|
|
padding: 20px;
|
|
.v-icon {
|
|
margin-bottom: 10px;
|
|
color: currentColor;
|
|
}
|
|
}
|
|
</style>
|