Add AttachmentBatch for future removal of sendAttachmentsMixin

This commit is contained in:
N-Pex 2025-06-11 14:59:34 +02:00
parent 1d30d0633d
commit fd82fd8840
5 changed files with 377 additions and 231 deletions

View file

@ -228,90 +228,13 @@
<input ref="attachment" type="file" name="attachment" @change="handlePickedAttachment($event)"
accept="image/*,audio/*,video/*,.mp3,.mp4,.wav,.m4a,.pdf,application/pdf,.apk,application/vnd.android.package-archive,.ipa,.zip,application/zip,application/x-zip-compressed,multipart/x-zip" class="d-none" multiple/>
<!-- <div v-if="currentFileInputsDialog && !useFileModeNonAdmin">
<v-dialog v-model="currentFileInputsDialog" class="ma-0 pa-0" :width="$vuetify.display.smAndUp ? '50%' : '85%'" persistent scrollable>
<v-card class="ma-0 pa-0">
<v-card-text v-if="!currentFileInputs.length">
{{ this.$t("message.preparing_to_upload")}}
<v-progress-linear
indeterminate
class="mb-0"
></v-progress-linear>
</v-card-text>
<template v-else>
<v-card-title>
<div v-if="currentSendErrorExceededFile" class="text-red">{{ currentSendErrorExceededFile }}</div>
<span v-else> {{ $t('message.send_attachements_dialog_title') }} </span>
</v-card-title>
<v-divider></v-divider>
<template v-if="imageFiles && imageFiles.length">
<v-card-title v-if="imageFiles.length > 1"> {{ $t('message.images') }} </v-card-title>
<v-card-text :class="{'ma-0 pa-2' : true, 'd-flex flex-wrap justify-center': imageFiles.length > 1}">
<div :class="{'col-4': imageFiles.length > 1}" v-for="(currentImageInput, id) in imageFiles" :key="id">
<div style="position: relative">
<v-img v-if="currentImageInput && currentImageInput.image" :aspect-ratio="1" :src="currentImageInput.image"
contain class="current-image-input-path" />
<v-progress-linear :style="{ position: 'absolute', left: '0', right: '0', bottom: '0', opacity: currentImageInput.sendInfo ? '1' : '0' }" :value="currentImageInput.sendInfo ? currentImageInput.sendInfo.progress : 0"></v-progress-linear>
<C2PABadge :file="currentImageInput.actualFile" />
</div>
<div>
<span v-if="currentImageInput && currentImageInput.scaled && currentImageInput.useScaled">
{{ currentImageInput.scaledDimensions.width }} x {{ currentImageInput.scaledDimensions.height }}</span>
<span v-else-if="currentImageInput && currentImageInput.dimensions">
{{ currentImageInput.dimensions.width }} x {{ currentImageInput.dimensions.height }}
</span>
<span v-if="currentImageInput && currentImageInput.scaled && currentImageInput.useScaled">
({{ formatBytes(currentImageInput.scaledSize) }})
</span>
<span v-else>
({{ formatBytes(currentImageInput.actualSize) }})
</span>
<v-switch v-if="currentImageInput && currentImageInput.scaled" :label="$t('message.scale_image')"
v-model="currentImageInput.useScaled" :disabled="currentImageInput && currentImageInput.sendInfo !== undefined" />
</div>
</div>
</v-card-text>
</template>
<template v-if="Array.isArray(currentFileInputs) && currentFileInputs.length">
<v-card-title v-if="nonImageFiles.length > 1">{{ $t('message.files') }}</v-card-title>
<v-card-text>
<div v-for="(currentImageInputPath, id) in currentFileInputs" :key="id">
<div v-if="!currentImageInputPath.type.includes('image/')">
<span> {{ $t('message.file') }}: {{ currentImageInputPath.name }}</span>
<span> ({{ formatBytes(currentImageInputPath.size) }})</span>
<v-progress-linear :style="{ opacity: currentImageInputPath.sendInfo ? '1' : '0' }" :value="currentImageInputPath.sendInfo ? currentImageInputPath.sendInfo.progress : 0"></v-progress-linear>
</div>
</div>
</v-card-text>
</template>
<v-divider></v-divider>
<v-textarea v-if="showAttachmentCaptionInput" v-model="attachmentCaption" ref="attachmentCaption" color="black" background-color="transparent"
variant="solo" full-width auto-grow rows="1" no-resize hide-details :placeholder="$t('file_mode.add_a_message')"></v-textarea>
<v-card-actions>
<v-spacer>
<div v-if="currentSendError">{{ currentSendError }}</div>
</v-spacer>
<v-btn color="primary" variant="text" @click="cancelSendAttachment" id="btn-attachment-cancel" :disabled="sendingStatus != sendStatuses.SENDING && sendingStatus != sendStatuses.INITIAL">
{{ $t("menu.cancel") }}
</v-btn>
<v-btn id="btn-attachment-send" color="primary" variant="text" @click="sendAttachment(attachmentCaption)"
v-if="currentSendShowSendButton" :disabled="currentSendShowSendButton && sendingStatus != sendStatuses.INITIAL">{{ $t("menu.send") }}</v-btn>
</v-card-actions>
</template>
</v-card>
</v-dialog>
</div> -->
<SendAttachmentsLayout
v-if="currentFileInputs && currentFileInputs.length > 0 && !useFileModeNonAdmin"
v-if="uploadBatch && uploadBatch.attachments.length > 0 && !useFileModeNonAdmin"
:room="room"
v-on:pick-file="showAttachmentPicker(false)"
v-on:add-files="(files) => addAttachments(files)"
v-on:remove-file="(index) => removeAttachment(index)"
:attachments="currentFileInputs"
v-on:close="resetAttachments"
:batch="uploadBatch"
v-on:close="() => { uploadBatch = undefined }"
/>
<MessageOperationsBottomSheet ref="messageOperationsSheet">
@ -490,10 +413,7 @@ export default {
scrollPosition: null,
currentFileInputs: [],
currentSendShowSendButton: true,
currentSendError: null,
currentSendErrorExceededFile: null,
attachmentCaption: undefined,
uploadBatch: undefined,
showEmojiPicker: false,
selectedEvent: null,
editedEvent: null,
@ -615,29 +535,6 @@ export default {
const currentUserId= this.selectedEvent?.sender.userId || this.$matrix.currentUserId
return this.joinedAndInvitedMembers.find(({userId}) => userId === currentUserId)
},
nonImageFiles() {
return this.isCurrentFileInputsAnArray && this.currentFileInputs.filter(file => !file?.type.includes("image/"))
},
imageFiles() {
return this.isCurrentFileInputsAnArray && this.currentFileInputs.filter(file => file?.type.includes("image/"))
},
isCurrentFileInputsAnArray() {
return Array.isArray(this.currentFileInputs)
},
showAttachmentCaptionInput() {
// IFF we are sending one PDF, add option to set caption.
const imageFiles = this.imageFiles || [];
const otherFiles = this.nonImageFiles || [];
return imageFiles.length == 0 && otherFiles.length == 1 && (otherFiles[0].type === "application/pdf" || (otherFiles[0].name || "").endsWith(".pdf"));
},
currentFileInputsDialog: {
get() {
return this.isCurrentFileInputsAnArray
},
set() {
this.currentFileInputs = [];
}
},
chatContainer() {
const container = this.$refs.chatContainer;
if (this.useVoiceMode) {
@ -1493,32 +1390,15 @@ export default {
this.$refs.attachment.click();
},
async addAttachment(file) {
addAttachment(file) {
if (file) {
this.currentFileInputs = [... this.currentFileInputs, this.$matrix.attachmentManager.createAttachment(file)];
// let optimizedFileObj;
// if (file.type.startsWith("image/")) {
// const f = await proofmode.proofCheckFile(file);
// var reader = new FileReader();
// optimizedFileObj = await new Promise(resolve => {
// reader.onload = evt => {
// resolve(this.optimizeImage(evt, file));
// }
// reader.readAsDataURL(f);
// })
// } else {
// optimizedFileObj = file;
// }
// console.error("OPTIMIZED", optimizedFileObj);
// this.currentFileInputs = Array.isArray(this.currentFileInputs) ? [...this.currentFileInputs, optimizedFileObj] : [optimizedFileObj];
if (!this.uploadBatch) {
this.uploadBatch = this.$matrix.attachmentManager.createUpload(this.room);
}
this.uploadBatch?.addAttachment(this.$matrix.attachmentManager.createAttachment(file));
}
},
removeAttachment(index) {
this.currentFileInputs = this.currentFileInputs.toSpliced(index, 1);
},
/**
* Handle picked attachment
*/
@ -1527,24 +1407,7 @@ export default {
},
addAttachments(files) {
// TODO - refactor
this.$matrix.matrixClient.getMediaConfig(this.$matrix.useAuthedMedia).then((config) => {
const configUploadSize = config["m.upload.size"];
const configFormattedUploadSize = this.formatBytes(configUploadSize);
files.every(file => {
if (configUploadSize && file.size > configUploadSize) {
this.currentSendError = this.$t("message.upload_file_too_large");
this.currentSendErrorExceededFile = this.$t("message.upload_exceeded_file_limit", { configFormattedUploadSize });
this.currentSendShowSendButton = false;
return false;
} else {
this.currentSendShowSendButton = true;
}
return true;
});
files.forEach(file => this.addAttachment(file));
});
files.forEach(file => this.addAttachment(file));
},
showStickerPicker() {
@ -1559,7 +1422,6 @@ export default {
promise.then(() => {
this.sendingAttachments = [];
this.currentFileInputs = [];
this.attachmentCaption = undefined;
this.sendingStatus = "initial"
})
.catch((err) => {
@ -1580,7 +1442,6 @@ export default {
this.cancelSendAttachments();
}
this.currentFileInputs = [];
this.attachmentCaption = undefined;
this.currentSendError = null;
this.currentSendErrorExceededFile = null;
this.sendingStatus = "initial";

View file

@ -3,7 +3,7 @@
<div class="send-attachments__title">{{ $t("message.send_attachements_dialog_title") }}</div>
<!-- ATTACHMENT SELECTION MODE -->
<template v-if="attachments && attachments.length > 0 && status == mainStatuses.SELECTING">
<template v-if="batch.attachments.length > 0 && status == mainStatuses.SELECTING">
<div class="attachment-wrapper" ref="attachmentWrapper" v-if="currentAttachment">
<div
:class="{ 'file-drop-current-item': true, 'drop-target': dropTarget }"
@ -22,7 +22,7 @@
</div>
</div>
<div class="send-attachments__current-item__info">
<!-- <div class="send-attachments__current-item__info">
<div class="send-attachments__current-item__info__size">
<span
v-if="currentAttachment.scaledFile && currentAttachment.useScaled && currentAttachment.scaledDimensions"
@ -38,10 +38,13 @@
</span>
<span v-else> ({{ formatBytes(currentAttachment.file.size) }}) </span>
<span class="send-attachments__current-item__info__size__filename" v-if="currentAttachment.src && currentAttachment.file.name">
<span
class="send-attachments__current-item__info__size__filename"
v-if="currentAttachment.src && currentAttachment.file.name"
>
- {{ currentAttachment.file.name }}
</span>
</div>
</div>
<v-switch
v-if="currentAttachment.scaledFile"
@ -51,17 +54,21 @@
/>
<C2PABadge :proof="currentAttachment.proof" />
</div>
</div> -->
<div class="file-drop-thumbnail-container">
<div
:class="{ 'file-drop-thumbnail': true, clickable: true, current: id == currentItemIndex }"
@click="currentItemIndex = id"
v-for="(currentImageInput, id) in attachments"
v-for="(currentImageInput, id) in batch.attachments"
:key="id"
>
<v-img v-if="currentImageInput && currentImageInput.src" :src="currentImageInput.src" />
<div v-if="currentItemIndex == id" class="remove clickable" @click.stop="$emit('remove-file', id)">
<div
v-if="currentItemIndex == id"
class="remove clickable"
@click.stop="batch.removeAttachment(currentImageInput)"
>
<v-icon>$vuetify.icons.ic_trash</v-icon>
</div>
</div>
@ -91,25 +98,23 @@
/>
<div class="input-container__buttons">
<v-btn @click="close">{{ $t("menu.cancel") }}</v-btn>
<v-btn @click="sendAll" :disabled="!attachments || attachments.length == 0">{{ $t("menu.send") }}</v-btn>
<v-btn @click="sendAll" :disabled="batch.attachments.length == 0">{{ $t("menu.send") }}</v-btn>
</div>
</div>
</div>
</template>
<!-- ATTACHMENT SENDING/SENT MODE -->
<template
v-if="attachments && attachments.length > 0 && (status == mainStatuses.SENDING || status == mainStatuses.SENT)"
>
<template v-if="batch.attachments.length > 0 && (status == mainStatuses.SENDING || status == mainStatuses.SENT)">
<div class="attachment-wrapper">
<div class="file-drop-sent-stack" ref="stackContainer">
<div v-if="status == mainStatuses.SENDING && attachmentsSentCount == 0" class="no-items">
<div v-if="status == mainStatuses.SENDING && batch.attachmentsSentCount == 0" class="no-items">
<div class="file-drop-stack-item direct" :style="stackItemTransform(null, -1)"></div>
<div>{{ $t("file_mode.sending_progress") }}</div>
</div>
<div
v-else
v-for="(info, index) in attachmentsSent"
v-for="(info, index) in batch.attachmentsSent"
:key="info.file.name"
class="file-drop-stack-item animated"
:style="stackItemTransform(info, index)"
@ -123,11 +128,11 @@
<!-- Middle section -->
<div v-if="status == mainStatuses.SENDING" class="file-drop-sending-container">
<div class="file-drop-sending-item" v-for="(attachment) in attachmentsSending" :key="attachment.file.name">
<div class="file-drop-sending-item" v-for="attachment in batch.attachmentsSending" :key="attachment.file.name">
<v-img v-if="attachment.src" :src="attachment.src" />
<div v-else class="filename">{{ attachment.file.name }}</div>
<v-progress-linear :model-value="attachment.sendInfo?.progress ?? 0"></v-progress-linear>
<div class="file-drop-cancel clickable" @click.stop="cancelSendAttachmentItem(attachment)">
<div class="file-drop-cancel clickable" @click.stop="batch.cancelSendAttachment(attachment)">
<v-icon size="14" color="white">close</v-icon>
</div>
</div>
@ -136,10 +141,8 @@
<div class="file-drop-files-sent">
{{
$t(
messageInput && messageInput.length > 0
? "file_mode.files_sent_with_note"
: "file_mode.files_sent",
attachmentsSent.length
messageInput && messageInput.length > 0 ? "file_mode.files_sent_with_note" : "file_mode.files_sent",
batch.attachmentsSent.length
)
}}
</div>
@ -192,21 +195,19 @@
<script lang="ts">
import { defineComponent } from "vue";
import messageMixin from "../messages/messageMixin";
import sendAttachmentsMixin from "../sendAttachmentsMixin";
import prettyBytes from "pretty-bytes";
import type { PropType } from "vue";
import { Attachment } from "../../models/attachment";
import C2PABadge from "../c2pa/C2PABadge.vue";
export default defineComponent({
mixins: [messageMixin, sendAttachmentsMixin],
mixins: [messageMixin],
components: { C2PABadge },
emits: ["add-files", "remove-file", "pick-file", "close"],
emits: ["add-files", "pick-file", "close"],
props: {
attachments: {
type: Array as PropType<Attachment[]>,
batch: {
type: Object,
default: function () {
return [] as Attachment[];
return {}
},
},
},
@ -227,25 +228,31 @@ export default defineComponent({
this.$audioPlayer.setAutoplay(false);
},
computed: {
currentAttachment(): Attachment {
return this.attachments[this.currentItemIndex];
currentAttachment(): Attachment | undefined {
if (this.currentItemIndex >= 0 && this.currentItemIndex < this.batch.attachments.length) {
return this.batch.attachments[this.currentItemIndex];
}
return undefined;
},
currentItemHasImagePreview() {
return (
this.currentItemIndex >= 0 &&
this.currentItemIndex < this.attachments.length &&
this.attachments[this.currentItemIndex].src
this.currentItemIndex < this.batch.attachments.length &&
this.batch.attachments[this.currentItemIndex].src
);
},
},
watch: {
attachments(newValue, oldValue) {
// Added or removed?
if (newValue && oldValue && newValue.length > oldValue.length) {
this.currentItemIndex = oldValue.length;
} else if (newValue) {
this.currentItemIndex = newValue.length - 1;
}
"batch.attachments": {
handler(newValue, oldValue) {
// Added or removed?
if (newValue && oldValue && newValue.length > oldValue.length) {
this.currentItemIndex = oldValue.length;
} else if (newValue) {
this.currentItemIndex = newValue.length - 1;
}
},
deep: true,
},
messageInput() {
this.scrollToBottom();
@ -256,7 +263,7 @@ export default defineComponent({
this.dropTarget = false;
let droppedFiles: FileList | undefined = e.dataTransfer?.files;
if (!droppedFiles) return;
this.$emit("add-files", [... droppedFiles]);
this.$emit("add-files", [...droppedFiles]);
},
scrollToBottom() {
const el = this.$refs.attachmentWrapper;
@ -276,7 +283,7 @@ export default defineComponent({
return prettyBytes(bytes);
},
close() {
this.sendingAttachments = [];
this.batch.cancel();
this.status = this.mainStatuses.SELECTING;
this.messageInput = "";
this.currentItemIndex = 0;
@ -284,28 +291,27 @@ export default defineComponent({
},
sendAll() {
this.status = this.mainStatuses.SENDING;
this.sendAttachments(
this.messageInput && this.messageInput.length > 0 ? this.messageInput : this.$t("file_mode.files"),
this.attachments
this.batch.send(
this.messageInput && this.messageInput.length > 0 ? this.messageInput : this.$t("file_mode.files")
).then(() => {
this.status = this.mainStatuses.SENT;
});
},
stackItemTransform(item, index) {
stackItemTransform(attachment: Attachment, index: number) {
const size =
0.6 *
(this.$refs.stackContainer
? Math.min(this.$refs.stackContainer.clientWidth, this.$refs.stackContainer.clientHeight)
: 176);
let transform = "";
if (item != null && index != -1) {
if (attachment != null && attachment.sendInfo && index != -1) {
transform =
"transform: rotate(" +
item.randomRotation +
attachment.sendInfo.randomRotation +
"deg) translate(" +
item.randomTranslationX +
attachment.sendInfo.randomTranslationX +
"px," +
item.randomTranslationY +
attachment.sendInfo.randomTranslationY +
"px); z-index:" +
(index + 2) +
";";