Merge branch '367-send-multiple-files-at-once' into 'dev'
Resolve "Not able to send multiple files at once on IM" Closes #367 See merge request keanuapp/keanuapp-weblite!188
This commit is contained in:
commit
26f693ca85
2 changed files with 150 additions and 115 deletions
|
|
@ -90,7 +90,11 @@
|
||||||
"incoming_message_deleted_text": "This message was deleted.",
|
"incoming_message_deleted_text": "This message was deleted.",
|
||||||
"not_allowed_to_send": "Only admins and moderators are allowed to send to the room",
|
"not_allowed_to_send": "Only admins and moderators are allowed to send to the room",
|
||||||
"reaction_count_more": "{reactionCount} more",
|
"reaction_count_more": "{reactionCount} more",
|
||||||
"seen_by": "Seen by no members | Seen by 1 member | Seen by {count} members"
|
"seen_by": "Seen by no members | Seen by 1 member | Seen by {count} members",
|
||||||
|
"file": "File",
|
||||||
|
"files": "Files",
|
||||||
|
"images": "Images",
|
||||||
|
"send_attachements_dialog_title": "Do you want to send following attachments ?"
|
||||||
},
|
},
|
||||||
"room": {
|
"room": {
|
||||||
"invitations": "You have no invitations | You have 1 invitation | You have {count} invitations",
|
"invitations": "You have no invitations | You have 1 invitation | You have {count} invitations",
|
||||||
|
|
|
||||||
|
|
@ -188,35 +188,52 @@
|
||||||
</v-container>
|
</v-container>
|
||||||
|
|
||||||
<input ref="attachment" type="file" name="attachment" @change="handlePickedAttachment($event)"
|
<input ref="attachment" type="file" name="attachment" @change="handlePickedAttachment($event)"
|
||||||
accept="image/*, audio/*, video/*, .pdf" class="d-none" />
|
accept="image/*, audio/*, video/*, .pdf" class="d-none" multiple/>
|
||||||
|
|
||||||
<div v-if="currentImageInputPath">
|
<div v-if="currentFileInputsDialog">
|
||||||
<v-dialog v-model="currentImageInputPath" class="ma-0 pa-0" :width="$vuetify.breakpoint.smAndUp ? '50%' : '85%'">
|
<v-dialog v-model="currentFileInputsDialog" class="ma-0 pa-0" :width="$vuetify.breakpoint.smAndUp ? '50%' : '85%'" persistent scrollable>
|
||||||
<v-card class="ma-0 pa-0">
|
<v-card class="ma-0 pa-0">
|
||||||
<v-card-text class="ma-0 pa-2">
|
<v-card-title>{{ $t('message.send_attachements_dialog_title') }}</v-card-title>
|
||||||
<v-img v-if="currentImageInput && currentImageInput.image" :aspect-ratio="1" :src="currentImageInput.image"
|
<v-divider></v-divider>
|
||||||
contain class="current-image-input-path" />
|
<template v-if="Array.isArray(currentImageInputs) && currentImageInputs.length">
|
||||||
<div>
|
<v-card-title v-if="currentImageInputs.length > 1"> {{ $t('message.images') }} </v-card-title>
|
||||||
file: {{ currentImageInputPath.name }}
|
<v-card-text :class="{'ma-0 pa-2' : true, 'd-flex flex-wrap justify-center': currentImageInputs.length > 1}">
|
||||||
<span v-if="currentImageInput && currentImageInput.scaled && currentImageInput.useScaled">
|
<div :class="{'col-4': currentImageInputs.length > 1}" v-for="(currentImageInput, id) in currentImageInputs" :key="id">
|
||||||
{{ currentImageInput.scaledDimensions.width }} x {{ currentImageInput.scaledDimensions.height }}</span>
|
<v-img v-if="currentImageInput && currentImageInput.image" :aspect-ratio="1" :src="currentImageInput.image"
|
||||||
<span v-else-if="currentImageInput && currentImageInput.dimensions">
|
contain class="current-image-input-path" />
|
||||||
{{ currentImageInput.dimensions.width }} x {{ currentImageInput.dimensions.height }}</span>
|
<div>
|
||||||
<span v-if="currentImageInput && currentImageInput.scaled && currentImageInput.useScaled">
|
<span v-if="currentImageInput && currentImageInput.scaled && currentImageInput.useScaled">
|
||||||
({{ formatBytes(currentImageInput.scaledSize) }})</span>
|
{{ currentImageInput.scaledDimensions.width }} x {{ currentImageInput.scaledDimensions.height }}</span>
|
||||||
<span v-else> ({{ formatBytes(currentImageInputPath.size) }})</span>
|
<span v-else-if="currentImageInput && currentImageInput.dimensions">
|
||||||
<v-switch v-if="currentImageInput && currentImageInput.scaled" :label="$t('message.scale_image')"
|
{{ currentImageInput.dimensions.width }} x {{ currentImageInput.dimensions.height }}</span>
|
||||||
v-model="currentImageInput.useScaled" />
|
<span v-if="currentImageInput && currentImageInput.scaled && currentImageInput.useScaled">
|
||||||
</div>
|
({{ formatBytes(currentImageInput.scaledSize) }})</span>
|
||||||
<div v-if="currentSendError">{{ currentSendError }}</div>
|
<v-switch v-if="currentImageInput && currentImageInput.scaled" :label="$t('message.scale_image')"
|
||||||
<div v-else>{{ currentSendProgress }}</div>
|
v-model="currentImageInput.useScaled" />
|
||||||
</v-card-text>
|
</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>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</v-card-text>
|
||||||
|
</template>
|
||||||
<v-divider></v-divider>
|
<v-divider></v-divider>
|
||||||
<v-card-actions>
|
<v-card-actions>
|
||||||
<v-spacer></v-spacer>
|
<v-spacer>
|
||||||
<v-btn color="primary" text @click="cancelSendAttachment" id="btn-attachment-cancel">{{
|
<div v-if="currentSendError">{{ currentSendError }}</div>
|
||||||
$t("menu.cancel")
|
<div v-else>{{ currentSendProgress }}</div>
|
||||||
}}</v-btn>
|
</v-spacer>
|
||||||
|
<v-btn color="primary" text @click="cancelSendAttachment" id="btn-attachment-cancel">
|
||||||
|
{{ $t("menu.cancel") }}
|
||||||
|
</v-btn>
|
||||||
<v-btn id="btn-attachment-send" color="primary" text @click="sendAttachment"
|
<v-btn id="btn-attachment-send" color="primary" text @click="sendAttachment"
|
||||||
v-if="currentSendShowSendButton" :disabled="currentSendOperation != null">{{ $t("menu.send") }}</v-btn>
|
v-if="currentSendShowSendButton" :disabled="currentSendOperation != null">{{ $t("menu.send") }}</v-btn>
|
||||||
</v-card-actions>
|
</v-card-actions>
|
||||||
|
|
@ -342,8 +359,8 @@ export default {
|
||||||
timelineWindowPaginating: false,
|
timelineWindowPaginating: false,
|
||||||
|
|
||||||
scrollPosition: null,
|
scrollPosition: null,
|
||||||
currentImageInput: null,
|
currentImageInputs: null,
|
||||||
currentImageInputPath: null,
|
currentFileInputs: null,
|
||||||
currentSendOperation: null,
|
currentSendOperation: null,
|
||||||
currentSendProgress: null,
|
currentSendProgress: null,
|
||||||
currentSendShowSendButton: true,
|
currentSendShowSendButton: true,
|
||||||
|
|
@ -444,6 +461,20 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
|
nonImageFiles() {
|
||||||
|
return this.isCurrentFileInputsAnArray && this.currentFileInputs.filter(file => !file.type.includes("image/"))
|
||||||
|
},
|
||||||
|
isCurrentFileInputsAnArray() {
|
||||||
|
return Array.isArray(this.currentFileInputs)
|
||||||
|
},
|
||||||
|
currentFileInputsDialog: {
|
||||||
|
get() {
|
||||||
|
return this.isCurrentFileInputsAnArray
|
||||||
|
},
|
||||||
|
set() {
|
||||||
|
this.currentFileInputs = null
|
||||||
|
}
|
||||||
|
},
|
||||||
chatContainer() {
|
chatContainer() {
|
||||||
const container = this.$refs.chatContainer;
|
const container = this.$refs.chatContainer;
|
||||||
if (this.useVoiceMode) {
|
if (this.useVoiceMode) {
|
||||||
|
|
@ -950,64 +981,64 @@ export default {
|
||||||
this.$refs.attachment.click();
|
this.$refs.attachment.click();
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
optimizeImage(e,event,file) {
|
||||||
* Handle picked attachment
|
let currentImageInput = {
|
||||||
*/
|
image: e.target.result,
|
||||||
handlePickedAttachment(event) {
|
dimensions: null,
|
||||||
if (event.target.files && event.target.files[0]) {
|
};
|
||||||
|
try {
|
||||||
|
currentImageInput.dimensions = sizeOf(dataUriToBuffer(e.target.result));
|
||||||
|
|
||||||
|
// Need to resize?
|
||||||
|
const w = currentImageInput.dimensions.width;
|
||||||
|
const h = currentImageInput.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());
|
||||||
|
var imageResize = new ImageResize({
|
||||||
|
format: "png",
|
||||||
|
width: newWidth,
|
||||||
|
height: newHeight,
|
||||||
|
outputType: "blob",
|
||||||
|
});
|
||||||
|
imageResize
|
||||||
|
.play(event.target)
|
||||||
|
.then((img) => {
|
||||||
|
Vue.set(
|
||||||
|
currentImageInput,
|
||||||
|
"scaled",
|
||||||
|
new File([img], file.name, {
|
||||||
|
type: img.type,
|
||||||
|
lastModified: Date.now(),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
Vue.set(currentImageInput, "useScaled", true);
|
||||||
|
Vue.set(currentImageInput, "scaledSize", img.size);
|
||||||
|
Vue.set(currentImageInput, "scaledDimensions", {
|
||||||
|
width: newWidth,
|
||||||
|
height: newHeight,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error("Resize failed:", err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to get image dimensions: " + error);
|
||||||
|
}
|
||||||
|
return currentImageInput
|
||||||
|
},
|
||||||
|
handleFileReader(event, file) {
|
||||||
|
if (file) {
|
||||||
var reader = new FileReader();
|
var reader = new FileReader();
|
||||||
reader.onload = (e) => {
|
reader.onload = (e) => {
|
||||||
const file = event.target.files[0];
|
|
||||||
if (file.type.startsWith("image/")) {
|
if (file.type.startsWith("image/")) {
|
||||||
this.currentImageInput = {
|
const currentImageInput = this.optimizeImage(e, event, file)
|
||||||
image: e.target.result,
|
this.currentImageInputs = Array.isArray(this.currentImageInputs) ? [...this.currentImageInputs, currentImageInput] : [currentImageInput]
|
||||||
dimensions: null,
|
|
||||||
};
|
|
||||||
try {
|
|
||||||
this.currentImageInput.dimensions = sizeOf(dataUriToBuffer(e.target.result));
|
|
||||||
|
|
||||||
// Need to resize?
|
|
||||||
const w = this.currentImageInput.dimensions.width;
|
|
||||||
const h = this.currentImageInput.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());
|
|
||||||
var imageResize = new ImageResize({
|
|
||||||
format: "png",
|
|
||||||
width: newWidth,
|
|
||||||
height: newHeight,
|
|
||||||
outputType: "blob",
|
|
||||||
});
|
|
||||||
imageResize
|
|
||||||
.play(event.target)
|
|
||||||
.then((img) => {
|
|
||||||
Vue.set(
|
|
||||||
this.currentImageInput,
|
|
||||||
"scaled",
|
|
||||||
new File([img], file.name, {
|
|
||||||
type: img.type,
|
|
||||||
lastModified: Date.now(),
|
|
||||||
})
|
|
||||||
);
|
|
||||||
Vue.set(this.currentImageInput, "useScaled", true);
|
|
||||||
Vue.set(this.currentImageInput, "scaledSize", img.size);
|
|
||||||
Vue.set(this.currentImageInput, "scaledDimensions", {
|
|
||||||
width: newWidth,
|
|
||||||
height: newHeight,
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
console.error("Resize failed:", err);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Failed to get image dimensions: " + error);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
console.log(this.currentImageInput);
|
|
||||||
this.$matrix.matrixClient.getMediaConfig().then((config) => {
|
this.$matrix.matrixClient.getMediaConfig().then((config) => {
|
||||||
this.currentImageInputPath = file;
|
this.currentFileInputs = Array.isArray(this.currentFileInputs) ? [...this.currentFileInputs, file] : [file];
|
||||||
if (config["m.upload.size"] && file.size > config["m.upload.size"]) {
|
if (config["m.upload.size"] && file.size > config["m.upload.size"]) {
|
||||||
this.currentSendError = this.$t("message.upload_file_too_large");
|
this.currentSendError = this.$t("message.upload_file_too_large");
|
||||||
this.currentSendShowSendButton = false;
|
this.currentSendShowSendButton = false;
|
||||||
|
|
@ -1016,9 +1047,15 @@ export default {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
reader.readAsDataURL(event.target.files[0]);
|
reader.readAsDataURL(file);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* Handle picked attachment
|
||||||
|
*/
|
||||||
|
handlePickedAttachment(event) {
|
||||||
|
Object.values(event.target.files).forEach(file => this.handleFileReader(event, file));
|
||||||
|
},
|
||||||
|
|
||||||
showStickerPicker() {
|
showStickerPicker() {
|
||||||
this.$refs.stickerPickerSheet.open();
|
this.$refs.stickerPickerSheet.open();
|
||||||
|
|
@ -1036,41 +1073,35 @@ export default {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
sendAttachment(withText) {
|
sendAttachment(withText) {
|
||||||
this.$refs.attachment.value = null;
|
this.$refs.attachment.value = null;
|
||||||
if (this.currentImageInputPath) {
|
if (this.isCurrentFileInputsAnArray) {
|
||||||
var inputFile = this.currentImageInputPath;
|
let inputFiles = this.currentFileInputs;
|
||||||
if (this.currentImageInput && this.currentImageInput.scaled && this.currentImageInput.useScaled) {
|
if (Array.isArray(this.currentImageInputs) && this.currentImageInputs.scaled && this.currentImageInputs.useScaled) {
|
||||||
// Send scaled version of image instead!
|
// Send scaled version of image instead!
|
||||||
inputFile = this.currentImageInput.scaled;
|
inputFiles = this.currentImageInputs.map(({scaled}) => scaled)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const promises = inputFiles.map(inputFile => util.sendImage(this.$matrix.matrixClient, this.roomId, inputFile, this.onUploadProgress));
|
||||||
|
|
||||||
|
Promise.all(promises).then(() => {
|
||||||
|
this.currentSendOperation = null;
|
||||||
|
this.currentImageInputs = null;
|
||||||
|
this.currentFileInputs = null;
|
||||||
this.currentSendProgress = null;
|
this.currentSendProgress = null;
|
||||||
this.currentSendOperation = util.sendImage(
|
if (withText) {
|
||||||
this.$matrix.matrixClient,
|
this.sendMessage(withText);
|
||||||
this.roomId,
|
}
|
||||||
inputFile,
|
})
|
||||||
this.onUploadProgress
|
.catch((err) => {
|
||||||
);
|
if (err.name === "AbortError" || err === "Abort") {
|
||||||
this.currentSendOperation
|
this.currentSendError = null;
|
||||||
.then(() => {
|
} else {
|
||||||
this.currentSendOperation = null;
|
this.currentSendError = err.LocaleString();
|
||||||
this.currentImageInput = null;
|
}
|
||||||
this.currentImageInputPath = null;
|
this.currentSendOperation = null;
|
||||||
this.currentSendProgress = null;
|
this.currentSendProgress = null;
|
||||||
if (withText) {
|
});
|
||||||
this.sendMessage(withText);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
if (err.name === "AbortError" || err === "Abort") {
|
|
||||||
this.currentSendError = null;
|
|
||||||
} else {
|
|
||||||
this.currentSendError = err.LocaleString();
|
|
||||||
}
|
|
||||||
this.currentSendOperation = null;
|
|
||||||
this.currentSendProgress = null;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -1080,8 +1111,8 @@ export default {
|
||||||
this.currentSendOperation.abort();
|
this.currentSendOperation.abort();
|
||||||
}
|
}
|
||||||
this.currentSendOperation = null;
|
this.currentSendOperation = null;
|
||||||
this.currentImageInput = null;
|
this.currentImageInputs = null;
|
||||||
this.currentImageInputPath = null;
|
this.currentFileInputs = null;
|
||||||
this.currentSendProgress = null;
|
this.currentSendProgress = null;
|
||||||
this.currentSendError = null;
|
this.currentSendError = null;
|
||||||
},
|
},
|
||||||
|
|
@ -1461,7 +1492,7 @@ export default {
|
||||||
|
|
||||||
onVoiceRecording(event) {
|
onVoiceRecording(event) {
|
||||||
this.currentSendShowSendButton = false;
|
this.currentSendShowSendButton = false;
|
||||||
this.currentImageInputPath = event.file;
|
this.currentFileInputs = Array.isArray(this.currentFileInputs) ? [...this.currentFileInputs, event.file] : [event.file];
|
||||||
var text = undefined;
|
var text = undefined;
|
||||||
if (this.currentInput && this.currentInput.length > 0) {
|
if (this.currentInput && this.currentInput.length > 0) {
|
||||||
text = this.currentInput;
|
text = this.currentInput;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue