Work on attachments

This commit is contained in:
N-Pex 2025-06-09 09:44:37 +02:00
parent ec79a33eab
commit 842c87dc96
28 changed files with 2714 additions and 798 deletions

View file

@ -27,10 +27,10 @@
v-on:close="showRecorder = false" v-on:file="onVoiceRecording" :sendTypingIndicators="useVoiceMode" />
<FileDropLayout class="file-drop-root" v-if="useFileModeNonAdmin" :room="room"
v-on:pick-file="showAttachmentPicker()"
v-on:add-file="addAttachment($event)"
v-on:pick-file="showAttachmentPicker(false)"
v-on:add-files="(files) => addAttachments(files)"
v-on:remove-file="currentFileInputs.splice($event, 1)"
v-on:reset="resetAttachments"
v-on:reset="v"
:attachments="currentFileInputs"
v-on:close="closeFileMode"
/>
@ -213,7 +213,7 @@
<v-col v-if="!$config.disableMediaSharing" class="input-area-button text-center flex-grow-0 flex-shrink-1">
<label icon flat ref="attachmentLabel">
<v-btn icon @click="showAttachmentPicker"
<v-btn icon @click="() => showAttachmentPicker(true)"
:disabled="attachButtonDisabled">
<v-icon size="36">add_circle_outline</v-icon>
</v-btn>
@ -229,7 +229,7 @@
<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">
<!-- <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">
@ -253,6 +253,7 @@
<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">
@ -302,7 +303,17 @@
</template>
</v-card>
</v-dialog>
</div>
</div> -->
<SendAttachmentsLayout
v-if="currentFileInputs && currentFileInputs.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"
/>
<MessageOperationsBottomSheet ref="messageOperationsSheet">
<EmojiPicker ref="emojiPicker"
@ -385,9 +396,10 @@ import BottomSheet from "./BottomSheet.vue";
import imageResize from "image-resize";
import CreatePollDialog from "./CreatePollDialog.vue";
import chatMixin, { ROOM_READ_MARKER_EVENT_PLACEHOLDER } from "./chatMixin";
import sendAttachmentsMixin from "./sendAttachmentsMixin";
import sendAttachmentsMixin from "./sendAttachmentsMixin.ts";
import AudioLayout from "./AudioLayout.vue";
import FileDropLayout from "./file_mode/FileDropLayout";
import SendAttachmentsLayout from "./file_mode/SendAttachmentsLayout.vue";
import roomTypeMixin from "./roomTypeMixin";
import roomMembersMixin from "./roomMembersMixin";
import PurgeRoomDialog from "../components/PurgeRoomDialog";
@ -401,6 +413,8 @@ import 'vue3-emoji-picker/css';
import emitter from 'tiny-emitter/instance';
import { markRaw } from "vue";
import timerIcon from '@/assets/icons/ic_timer.svg';
import proofmode from "../plugins/proofmode.js";
import C2PABadge from "./c2pa/C2PABadge.vue";
const READ_RECEIPT_TIMEOUT = 5000; /* How long a message must have been visible before the read marker is updated */
const WINDOW_BUFFER_SIZE = 0.3; /** Relative window height of when we start paginating. Always keep this much loaded before and after our scroll position! */
@ -448,13 +462,15 @@ export default {
CreatePollDialog,
AudioLayout,
FileDropLayout,
SendAttachmentsLayout,
UserProfileDialog,
PurgeRoomDialog,
WelcomeHeaderChannelUser,
MessageErrorHandler,
MessageOperationsChannel,
RoomExport,
EmojiPicker
EmojiPicker,
C2PABadge
},
data() {
@ -473,7 +489,7 @@ export default {
timelineWindowPaginating: false,
scrollPosition: null,
currentFileInputs: null,
currentFileInputs: [],
currentSendShowSendButton: true,
currentSendError: null,
currentSendErrorExceededFile: null,
@ -619,7 +635,7 @@ export default {
return this.isCurrentFileInputsAnArray
},
set() {
this.currentFileInputs = null
this.currentFileInputs = [];
}
},
chatContainer() {
@ -1470,97 +1486,65 @@ export default {
/**
* Show attachment picker to select file
*/
showAttachmentPicker() {
showAttachmentPicker(reset) {
if (reset) {
this.resetAttachments();
}
this.$refs.attachment.click();
},
optimizeImage(evt,file) {
let fileObj = {}
fileObj.image = evt.target.result;
fileObj.dimensions = null;
fileObj.type = file.type;
fileObj.actualSize = file.size;
fileObj.actualFile = file
try {
const buffer = Uint8Array.from(window.atob(evt.target.result.replace(/^data[^,]+,/,'')), v => v.charCodeAt(0));
fileObj.dimensions = imageSize(buffer);
// Need to resize?
const w = fileObj.dimensions.width;
const h = fileObj.dimensions.height;
if (w > 640 || h > 640) {
var aspect = w / h;
var newWidth = parseInt((w > h ? 640 : 640 * aspect).toFixed());
var newHeight = parseInt((w > h ? 640 / aspect : 640).toFixed());
imageResize(evt.target.result, {
format: "png",
width: newWidth,
height: newHeight,
outputType: "blob",
})
.then((img) => {
fileObj["scaled"] =
new File([img], file.name, {
type: img.type,
lastModified: Date.now(),
});
fileObj["useScaled"] = true;
fileObj["scaledSize"] = img.size;
fileObj["scaledDimensions"] = {
width: newWidth,
height: newHeight,
};
})
.catch((err) => {
console.error("Resize failed:", err);
});
}
} catch (error) {
console.error("Failed to get image dimensions: " + error);
}
return fileObj
},
handleFileReader(file) {
async addAttachment(file) {
if (file) {
let optimizedFileObj;
var reader = new FileReader();
reader.onload = (evt) => {
if (file.type.startsWith("image/")) {
optimizedFileObj = this.optimizeImage(evt, file)
} else {
optimizedFileObj = file
}
this.currentFileInputs = Array.isArray(this.currentFileInputs) ? [...this.currentFileInputs, optimizedFileObj] : [optimizedFileObj];
};
reader.readAsDataURL(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];
}
},
removeAttachment(index) {
this.currentFileInputs = this.currentFileInputs.toSpliced(index, 1);
},
/**
* Handle picked attachment
*/
handlePickedAttachment(event) {
this.currentFileInputs = []
const uploadedFiles = Object.values(event.target.files);
this.addAttachments(Object.values(event.target.files));
},
this.$matrix.matrixClient.getMediaConfig().then((config) => {
const configUploadSize = config["m.upload.size"];
const configFormattedUploadSize = this.formatBytes(configUploadSize);
uploadedFiles.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;
});
uploadedFiles.forEach(file => this.handleFileReader(file));
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));
});
},
showStickerPicker() {
@ -1574,9 +1558,9 @@ export default {
const promise = this.sendAttachments(text, this.currentFileInputs);
promise.then(() => {
this.sendingAttachments = [];
this.currentFileInputs = null;
this.currentFileInputs = [];
this.attachmentCaption = undefined;
this.sendingStatus = this.sendStatuses.INITIAL;
this.sendingStatus = "initial"
})
.catch((err) => {
if (err.name === "AbortError" || err === "Abort") {
@ -1592,18 +1576,14 @@ export default {
cancelSendAttachment() {
this.$refs.attachment.value = null;
if (this.sendingStatus != this.sendStatuses.INITIAL) {
if (this.sendingStatus != "initial") {
this.cancelSendAttachments();
}
this.currentFileInputs = null;
this.currentFileInputs = [];
this.attachmentCaption = undefined;
this.currentSendError = null;
this.currentSendErrorExceededFile = null;
this.sendingStatus = this.sendStatuses.INITIAL;
},
addAttachment(file) {
this.handleFileReader(null, file);
this.sendingStatus = "initial";
},
resetAttachments() {
@ -2044,6 +2024,7 @@ export default {
onVoiceRecording(event) {
this.currentSendShowSendButton = false;
// TODO - refactor
this.currentFileInputs = Array.isArray(this.currentFileInputs) ? [...this.currentFileInputs, event.file] : [event.file];
var text = undefined;
if (this.currentInput && this.currentInput.length > 0) {