Experimental "file drop" mode

This commit is contained in:
N Pex 2023-06-28 12:14:44 +00:00
parent 791fa5936a
commit ebadd509e9
19 changed files with 1038 additions and 85 deletions

View file

@ -1,6 +1,6 @@
<template>
<div class="chat-root fill-height d-flex flex-column">
<ChatHeader class="chat-header flex-grow-0 flex-shrink-0" v-on:header-click="onHeaderClick" v-on:view-room-details="viewRoomDetails" />
<ChatHeader class="chat-header flex-grow-0 flex-shrink-0" v-on:header-click="onHeaderClick" v-on:view-room-details="viewRoomDetails" v-if="!useFileModeNonAdmin" />
<AudioLayout ref="chatContainer" class="auto-audio-player-root" v-if="useVoiceMode" :room="room"
:events="events" :autoplay="!showRecorder"
:timelineSet="timelineSet"
@ -15,8 +15,14 @@
<VoiceRecorder class="audio-layout" v-if="useVoiceMode" :micButtonRef="$refs.mic_button" :ptt="showRecorderPTT" :show="showRecorder"
v-on:close="showRecorder = false" v-on:file="onVoiceRecording" :sendTypingIndicators="useVoiceMode" />
<FileDropLayout class="file-drop-root" v-if="useFileModeNonAdmin" :room="room"
v-on:add-file="showAttachmentPicker()"
v-on:remove-file="currentFileInputs.splice($event, 1)"
v-on:reset="resetAttachments"
:attachments="currentFileInputs"
/>
<div v-if="!useVoiceMode" class="chat-content flex-grow-1 flex-shrink-1" ref="chatContainer"
<div v-if="!useVoiceMode && !useFileModeNonAdmin" class="chat-content flex-grow-1 flex-shrink-1" ref="chatContainer"
v-on:scroll="onScroll" @click="closeContextMenusIfOpen">
<div ref="messageOperationsStrut" class="message-operations-strut">
<message-operations ref="messageOperations" :style="opStyle" :emojis="recentEmojis" v-on:close="
@ -75,7 +81,7 @@
</div>
<!-- Input area -->
<v-container v-if="!useVoiceMode && room" fluid :class="['input-area-outer', replyToEvent ? 'reply-to' : '']">
<v-container v-if="!useVoiceMode && !useFileModeNonAdmin && room" fluid :class="['input-area-outer', replyToEvent ? 'reply-to' : '']">
<div :class="[replyToEvent ? 'iput-area-inner-box' : '']">
<!-- "Scroll to end"-button -->
<v-btn v-if="!useVoiceMode" class="scroll-to-end" v-show="showScrollToEnd" fab x-small elevation="0" color="black"
@ -191,15 +197,15 @@
<input ref="attachment" type="file" name="attachment" @change="handlePickedAttachment($event)"
accept="image/*, audio/*, video/*, .pdf" class="d-none" multiple/>
<div v-if="currentFileInputsDialog">
<div v-if="currentFileInputsDialog && !useFileModeNonAdmin">
<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-title>{{ $t('message.send_attachements_dialog_title') }}</v-card-title>
<v-divider></v-divider>
<template v-if="Array.isArray(currentImageInputs) && currentImageInputs.length">
<v-card-title v-if="currentImageInputs.length > 1"> {{ $t('message.images') }} </v-card-title>
<v-card-text :class="{'ma-0 pa-2' : true, 'd-flex flex-wrap justify-center': currentImageInputs.length > 1}">
<div :class="{'col-4': currentImageInputs.length > 1}" v-for="(currentImageInput, id) in currentImageInputs" :key="id">
<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">
<v-img v-if="currentImageInput && currentImageInput.image" :aspect-ratio="1" :src="currentImageInput.image"
contain class="current-image-input-path" />
<div>
@ -281,7 +287,7 @@
<script>
import Vue from "vue";
import { TimelineWindow, EventTimeline } from "matrix-js-sdk";
import util from "../plugins/utils";
import util, { ROOM_TYPE_VOICE_MODE, ROOM_TYPE_FILE_MODE } from "../plugins/utils";
import MessageOperations from "./messages/MessageOperations.vue";
import AvatarOperations from "./messages/AvatarOperations.vue";
import ChatHeader from "./ChatHeader";
@ -296,6 +302,7 @@ import ImageResize from "image-resize";
import CreatePollDialog from "./CreatePollDialog.vue";
import chatMixin from "./chatMixin";
import AudioLayout from "./AudioLayout.vue";
import FileDropLayout from "./file_mode/FileDropLayout";
const sizeOf = require("image-size");
const dataUriToBuffer = require("data-uri-to-buffer");
@ -344,7 +351,8 @@ export default {
BottomSheet,
AvatarOperations,
CreatePollDialog,
AudioLayout
AudioLayout,
FileDropLayout
},
data() {
@ -360,7 +368,6 @@ export default {
timelineWindowPaginating: false,
scrollPosition: null,
currentImageInputs: null,
currentFileInputs: null,
currentSendOperation: null,
currentSendProgress: null,
@ -465,6 +472,9 @@ export default {
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)
},
@ -584,9 +594,16 @@ export default {
useVoiceMode: {
get: function () {
if (!this.$config.experimental_voice_mode) return false;
return util.useVoiceMode(this.room);
return util.roomDisplayType(this.room) === ROOM_TYPE_VOICE_MODE;
},
},
useFileModeNonAdmin: {
get: function() {
if (!this.$config.experimental_file_mode) return false;
return util.roomDisplayType(this.room) === ROOM_TYPE_FILE_MODE && !this.canCreatePoll; // TODO - Check user or admin
}
},
/**
* If we have no events and the room is encrypted, show info about this
* to the user.
@ -928,8 +945,10 @@ export default {
// If we are at bottom, scroll to see new events...
var scrollToSeeNew = event.getSender() == this.$matrix.currentUserId; // When we sent, scroll
const container = this.chatContainer;
if (container && container.scrollHeight - container.scrollTop.toFixed(0) == container.clientHeight) {
scrollToSeeNew = true;
if (container) {
if (container.scrollHeight - container.scrollTop.toFixed(0) == container.clientHeight) {
scrollToSeeNew = true;
}
}
this.handleScrolledToBottom(scrollToSeeNew);
@ -996,16 +1015,14 @@ export default {
},
optimizeImage(e,event,file) {
let currentImageInput = {
image: e.target.result,
dimensions: null,
};
file.image = e.target.result;
file.dimensions = null;
try {
currentImageInput.dimensions = sizeOf(dataUriToBuffer(e.target.result));
file.dimensions = sizeOf(dataUriToBuffer(e.target.result));
// Need to resize?
const w = currentImageInput.dimensions.width;
const h = currentImageInput.dimensions.height;
const w = file.dimensions.width;
const h = file.dimensions.height;
if (w > 640 || h > 640) {
var aspect = w / h;
var newWidth = parseInt((w > h ? 640 : 640 * aspect).toFixed());
@ -1020,16 +1037,16 @@ export default {
.play(event.target)
.then((img) => {
Vue.set(
currentImageInput,
file,
"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", {
Vue.set(file, "useScaled", true);
Vue.set(file, "scaledSize", img.size);
Vue.set(file, "scaledDimensions", {
width: newWidth,
height: newHeight,
});
@ -1041,15 +1058,14 @@ export default {
} catch (error) {
console.error("Failed to get image dimensions: " + error);
}
return currentImageInput
return file
},
handleFileReader(event, file) {
if (file) {
var reader = new FileReader();
reader.onload = (e) => {
if (file.type.startsWith("image/")) {
const currentImageInput = this.optimizeImage(e, event, file)
this.currentImageInputs = Array.isArray(this.currentImageInputs) ? [...this.currentImageInputs, currentImageInput] : [currentImageInput]
this.optimizeImage(e, event, file)
}
this.$matrix.matrixClient.getMediaConfig().then((config) => {
this.currentFileInputs = Array.isArray(this.currentFileInputs) ? [...this.currentFileInputs, file] : [file];
@ -1090,17 +1106,17 @@ export default {
sendAttachment(withText) {
this.$refs.attachment.value = null;
if (this.isCurrentFileInputsAnArray) {
let inputFiles = this.currentFileInputs;
if (Array.isArray(this.currentImageInputs) && this.currentImageInputs.scaled && this.currentImageInputs.useScaled) {
// Send scaled version of image instead!
inputFiles = this.currentImageInputs.map(({scaled}) => scaled)
}
let inputFiles = this.currentFileInputs.map(entry => {
if (entry.scaled && entry.useScaled) {
// Send scaled version of image instead!
return entry.scaled;
}
return entry;
})
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;
if (withText) {
@ -1125,12 +1141,15 @@ export default {
this.currentSendOperation.abort();
}
this.currentSendOperation = null;
this.currentImageInputs = null;
this.currentFileInputs = null;
this.currentSendProgress = null;
this.currentSendError = null;
},
resetAttachments() {
this.cancelSendAttachment();
},
handleScrolledToTop() {
if (
this.timelineWindow &&
@ -1437,7 +1456,7 @@ export default {
let eventIdFirst = null;
let eventIdLast = null;
if (!this.useVoiceMode) {
if (!this.useVoiceMode && !this.useFileModeNonAdmin) {
const container = this.chatContainer;
const elFirst = util.getFirstVisibleElement(container, (item) => item.hasAttribute("eventId"));
const elLast = util.getLastVisibleElement(container, (item) => item.hasAttribute("eventId"));