Use thumbnail view when sending attachments

So we can preview videos etc.
This commit is contained in:
N-Pex 2025-07-03 10:54:31 +02:00
parent 2b2c736311
commit e5bb2d7202
4 changed files with 164 additions and 93 deletions

View file

@ -1,108 +1,107 @@
<template>
<div ref="thumbnailRef" style="width: 100%;height: 100%;">
<v-responsive
v-if="item.event.getContent().msgtype == 'm.video' && item.src"
:class="{ 'thumbnail-item': true, preview: previewOnly }"
>
<video :src="item.src" :controls="!previewOnly" class="w-100 h-100">
<div ref="thumbnailRef" style="width: 100%; height: 100%">
<v-responsive v-if="isVideo && source" :class="{ 'thumbnail-item': true, preview: previewOnly }">
<video :src="source" :controls="!previewOnly" class="w-100 h-100">
{{ $t("fallbacks.video_file") }}
</video>
</v-responsive>
<ImageWithProgress
v-else-if="item.event.getContent().msgtype == 'm.image'"
v-else-if="isImage"
:aspect-ratio="previewOnly ? 16 / 9 : undefined"
:class="{ 'thumbnail-item': true, preview: previewOnly }"
:src="item.src ? item.src : item.thumbnail"
:src="source"
:contain="!previewOnly"
:cover="previewOnly"
:loadingProgress="previewOnly ? item.thumbnailProgress : item.srcProgress"
: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-name">{{ $$sanitize(fileName) }}</div>
<div class="file-size">{{ fileSize }}</div>
</div>
</div>
</template>
<script lang="ts">
import util from "../../plugins/utils";
import { defineComponent } from "vue";
import type { PropType } from 'vue'
<script setup lang="ts">
import utils from "../../plugins/utils";
import { computed, inject, onBeforeUnmount, onMounted, Ref, ref, useTemplateRef, watch } from "vue";
import { EventAttachment } from "../../models/eventAttachment";
import ImageWithProgress from "../ImageWithProgress";
import { useThumbnail } from "../messages/composition/useThumbnail";
import { Attachment } from "../../models/attachment";
export default defineComponent({
components: { ImageWithProgress },
props: {
/**
* Item is an object of { event: MXEvent, src: URL }
*/
item: {
type: Object as PropType<EventAttachment>,
default: function () {
return {};
},
},
previewOnly: {
type: Boolean,
default: function () {
return false;
},
},
},
computed: {
fileTypeIcon() {
if (util.isFileTypeAPK(this.item.event)) {
if (this.item.event.isChannelMessage) {
return "$vuetify.icons.ic_channel_apk";
}
return "$vuetify.icons.ic_apk";
} else if (util.isFileTypeIPA(this.item.event)) {
return "$vuetify.icons.ic_ipa";
} else if (util.isFileTypePDF(this.item.event)) {
if (this.item.event.isChannelMessage) {
return "$vuetify.icons.ic_channel_pdf";
}
return "$vuetify.icons.ic_pdf";
} else if (util.isFileTypeZip(this.item.event)) {
return "$vuetify.icons.ic_zip";
}
return "description";
},
fileTypeIconClass() {
if (util.isFileTypeZip(this.item.event)) {
return "zip";
}
return undefined;
},
fileName() {
return util.getFileName(this.item.event);
},
fileSize() {
return util.getFileSizeFormatted(this.item.event);
},
},
methods: {
// listen for custom hammerJs singletab click to differentiate it from double click(heart animation).
initThumbnailHammerJs(element: any) {
const hammerInstance = util.singleOrDoubleTabRecognizer(element);
const $$sanitize: any = inject("globalSanitize");
hammerInstance.on("singletap doubletap", (ev: any) => {
if (ev.type === "singletap") {
this.$emit("itemclick", { item: this.item });
}
});
},
},
mounted() {
if (this.$refs.thumbnailRef) {
this.initThumbnailHammerJs(this.$refs.thumbnailRef);
const thumbnailRef = useTemplateRef("thumbnailRef");
interface ThumbnailProps {
item?: EventAttachment;
file?: Attachment;
previewOnly?: boolean;
}
type ThumbnailEmits = {
(event: "itemclick", value: { item: EventAttachment }): void;
};
const props = defineProps<ThumbnailProps>();
const { item, file, previewOnly = false } = props;
const emits = defineEmits<ThumbnailEmits>();
let { isVideo, isImage, fileTypeIcon, fileTypeIconClass, fileName, fileSize } = useThumbnail(file?.file ?? item?.event);
const fileURL: Ref<string | undefined> = ref(undefined);
watch(props, (props: ThumbnailProps) => {
const updates = useThumbnail(props.file?.file ?? props.item?.event);
isVideo.value = updates.isVideo.value;
isImage.value = updates.isImage.value;
fileTypeIcon = updates.fileTypeIcon;
fileTypeIconClass = updates.fileTypeIconClass;
fileName = updates.fileName;
fileSize = updates.fileSize;
});
const source = computed(() => {
if (item) {
if (isVideo.value) {
return item.src;
}
if (!this.previewOnly && this.item) {
this.item.loadSrc();
return item.src ? item.src : item.thumbnail;
} else if (file) {
if (!fileURL.value) {
fileURL.value = URL.createObjectURL(file.file);
}
},
return fileURL.value;
}
return undefined;
});
const loadingProgress = computed(() => {
if (item) {
return previewOnly ? item.thumbnailProgress : item.srcProgress;
}
return -1;
});
onMounted(() => {
if (thumbnailRef.value && (item as EventAttachment)) {
const hammerInstance = utils.singleOrDoubleTabRecognizer(thumbnailRef.value);
hammerInstance.on("singletap doubletap", (ev: any) => {
if (ev.type === "singletap") {
emits("itemclick", { item: item as EventAttachment });
}
});
}
if (!previewOnly && item && (item as EventAttachment)) {
(item as EventAttachment).loadSrc();
}
});
onBeforeUnmount(() => {
if (fileURL.value) {
URL.revokeObjectURL(fileURL.value);
fileURL.value = undefined;
}
});
</script>