Send progress in main view

This commit is contained in:
N-Pex 2025-08-20 15:12:04 +02:00
parent 41232fbd90
commit e9accdebf1
11 changed files with 465 additions and 86 deletions

View file

@ -52,6 +52,7 @@ import ReadMarker from "./messages/ReadMarker.vue";
import RoomTombstone from "./messages/composition/RoomTombstone.vue";
import roomDisplayOptionsMixin from "./roomDisplayOptionsMixin";
import roomTypeMixin from "./roomTypeMixin";
import MessageThreadSending from "./messages/composition/MessageThreadSending.vue";
export const ROOM_READ_MARKER_EVENT_PLACEHOLDER = { getId: () => "ROOM_READ_MARKER", getTs: () => Date.now() };
@ -225,6 +226,9 @@ export default {
}
return null;
}
if (!isForExport && event.uploadBatch) {
return MessageThreadSending;
}
if (event.isMxThread) {
// Outgoing thread
return isForExport ? MessageThreadExport : MessageThread;

View file

@ -245,8 +245,8 @@ export default defineComponent({
batch: {
type: Object,
default: function () {
return reactive(createUploadBatch(null, null, 0));
default: () => {
return createUploadBatch(null, null);
},
},
},
@ -294,7 +294,7 @@ export default defineComponent({
this.currentItemIndex = newValue.length - 1;
}
},
deep: true,
deep: 1,
},
},
methods: {
@ -323,6 +323,7 @@ export default defineComponent({
},
sendAll() {
this.status = this.mainStatuses.SENDING;
this.$emit("close");
this.batch
.send(this.messageInput && this.messageInput.length > 0 ? this.messageInput : this.defaultRootMessageText)
.then(() => {

View file

@ -23,7 +23,7 @@
</template>
<script setup lang="ts">
import utils from "../../plugins/utils";
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";
@ -119,7 +119,7 @@ const loadingProgress = computed(() => {
onMounted(() => {
if (thumbnailRef.value && (item as EventAttachment)) {
const hammerInstance = utils.singleOrDoubleTabRecognizer(thumbnailRef.value);
const hammerInstance = singleOrDoubleTapRecognizer(thumbnailRef.value);
hammerInstance.on("singletap doubletap", (ev: any) => {
if (ev.type === "singletap") {
emits("itemclick", { item: item as EventAttachment });

View file

@ -21,6 +21,7 @@
</template>
<script setup lang="ts">
import { singleOrDoubleTapRecognizer } from "@/plugins/touch";
import { computed, inject, onMounted, ref, useTemplateRef, watch } from "vue";
import MessageIncoming from "./MessageIncoming.vue";
import MessageOutgoing from "./MessageOutgoing.vue";
@ -30,7 +31,6 @@ import { useI18n } from "vue-i18n";
import { MessageProps, useMessage } from "./useMessage";
import { EventAttachment } from "../../../models/eventAttachment";
import { useDisplay } from "vuetify";
import utils from "@/plugins/utils";
import Hammer from "hammerjs";
const { t } = useI18n()
@ -80,7 +80,7 @@ onMounted(() => {
contain.value = true;
}
if (imageRef.value) {
const hammerInstance = utils.singleOrDoubleTabRecognizer(imageRef.value);
const hammerInstance = singleOrDoubleTapRecognizer(imageRef.value);
hammerInstance.on("singletap doubletap", (ev: Hammer.HammerInput) => {
if (ev.type === "singletap") {
attachment.value?.loadSrc();

View file

@ -0,0 +1,194 @@
<template>
<MessageOutgoing :is="rootComponent" ref="root" v-bind="{ ...$props, ...$attrs }">
<div class="bubble">
<div class="original-message" v-if="inReplyToText">
<div class="original-message-sender">{{ inReplyToSender }}</div>
<div class="original-message-text" v-html="linkify($$sanitize(inReplyToText))" />
</div>
<div class="message">
<div class="message-upload-progress" v-if="unref(uploadBatch?.sendingStatus) === 'sending'">
<v-progress-circular
class="message-upload-progress__progress clickable"
:model-value="unref(uploadBatch?.progressPercent ?? 0)"
color="white"
bg-color="rgba(255,255,255,0.5)"
width="2"
size="16"
@click.stop="cancelUpload"
>
<v-icon size="8">close</v-icon> </v-progress-circular
>{{ uploadBatch?.progressPercent }}%
</div>
<SwipeableThumbnailsView :items="items" v-if="room.displayType == ROOM_TYPE_CHANNEL" v-bind="$attrs" />
<v-container v-else fluid class="imageCollection">
<v-row wrap>
<v-col v-for="{ size, item } in layoutedItems" :key="item.file.name + item.file.size" :cols="size">
<ThumbnailView :file="item" v-on:itemclick="onItemClick($event)" />
</v-col>
</v-row>
</v-container>
<span v-html="linkify($$sanitize(messageText))" v-if="messageText" />
<span class="edit-marker" v-if="event && event.replacingEventId() && !event.isRedacted()">
{{ t("message.edited") }}
</span>
</div>
</div>
<GalleryItemsView
:originalEvent="originalEvent"
:items="items"
:initialItem="showItem"
v-if="!!showItem"
v-on:close="showItem = undefined"
/>
</MessageOutgoing>
</template>
<script setup lang="ts">
import MessageOutgoing from "./MessageOutgoing.vue";
import { MessageProps, useMessage } from "./useMessage";
import { ROOM_TYPE_CHANNEL } from "@/plugins/utils";
import GalleryItemsView from "../../file_mode/GalleryItemsView.vue";
import ThumbnailView from "../../file_mode/ThumbnailView.vue";
import SwipeableThumbnailsView from "../channel/SwipeableThumbnailsView.vue";
import { inject, ref, Ref, unref, watch } from "vue";
import { useI18n } from "vue-i18n";
import { Attachment, AttachmentBatch } from "../../../models/attachment";
const { t } = useI18n();
const $matrix: any = inject("globalMatrix");
const $$sanitize: any = inject("globalSanitize");
let items: Ref<Attachment[]> = ref([]);
const layoutedItems: Ref<{ size: number; item: Attachment }[]> = ref([]);
const showItem: Ref<Attachment | undefined> = ref(undefined);
const uploadBatch: Ref<AttachmentBatch | undefined> = ref(undefined);
const props = defineProps<MessageProps>();
const { room } = props;
const {
event,
inReplyToSender,
inReplyToText,
messageText,
linkify,
} = useMessage($matrix, t, props, undefined, undefined);
const cancelUpload = () => {
if (uploadBatch.value) {
uploadBatch.value.cancel();
}
}
const onItemClick = (event: any) => {
showItem.value = event.item;
};
const layout = () => {
if (!items.value || items.value.length == 0) {
layoutedItems.value = [];
return;
}
let array = items.value.slice(0);
let rows = [];
while (array.length > 0) {
if (array.length >= 7) {
rows.push({ size: 6, item: array[0] });
rows.push({ size: 6, item: array[1] });
rows.push({ size: 12, item: array[2] });
rows.push({ size: 3, item: array[3] });
rows.push({ size: 3, item: array[4] });
rows.push({ size: 3, item: array[5] });
rows.push({ size: 3, item: array[6] });
array = array.slice(7);
} else if (array.length >= 3) {
rows.push({ size: 6, item: array[0] });
rows.push({ size: 6, item: array[1] });
rows.push({ size: 12, item: array[2] });
array = array.slice(3);
} else if (array.length >= 2) {
rows.push({ size: 6, item: array[0] });
rows.push({ size: 6, item: array[1] });
array = array.slice(2);
} else {
rows.push({ size: 12, item: array[0] });
array = array.slice(1);
}
}
layoutedItems.value = rows;
};
watch(
event,
(updated) => {
const batch = updated?.uploadBatch;
if (batch) {
uploadBatch.value = updated?.uploadBatch;
items = ref(batch.attachments);
layout();
}
},
{ immediate: true }
);
</script>
<style lang="scss">
@use "@/assets/css/chat.scss" as *;
</style>
<style lang="scss" scoped>
.bubble {
width: 100%;
}
.imageCollection {
border-radius: 15px;
padding: 0;
overflow: hidden;
.row {
margin: -4px; // Compensate for column padding, so the border-radius above looks round!
padding: 0;
}
.col {
padding: 2px;
}
.file-item {
display: flex;
align-items: center;
justify-content: center;
font-size: 0.6rem;
flex-direction: column;
padding: 20px;
}
}
.message-upload-progress {
z-index: 1;
position: absolute;
top: 4px;
left: 4px;
height: 20px;
border-radius: 10px;
background-color: black;
color: white;
font-family: "Inter";
font-weight: 400;
font-style: normal;
font-size: 10px;
line-height: 125%;
letter-spacing: 0.4px;
display: flex;
align-items: center;
padding-left: 2px;
padding-right: 4px;
.message-upload-progress__progress {
margin-right: 3px;
}
}
</style>