Experimental "file drop" mode
This commit is contained in:
parent
791fa5936a
commit
ebadd509e9
19 changed files with 1038 additions and 85 deletions
|
|
@ -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"));
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue