Initial implementation of "audio mode"
This commit is contained in:
parent
d5942fdb8e
commit
09173a65f1
14 changed files with 944 additions and 410 deletions
|
|
@ -4,49 +4,39 @@
|
|||
{{ $tc("room.invitations", invitationCount) }}
|
||||
</div>
|
||||
<ChatHeader class="chat-header flex-grow-0 flex-shrink-0" v-on:header-click="onHeaderClick" />
|
||||
<div
|
||||
class="chat-content flex-grow-1 flex-shrink-1"
|
||||
ref="chatContainer"
|
||||
v-on:scroll="onScroll"
|
||||
@click="closeContextMenusIfOpen"
|
||||
>
|
||||
<AudioLayout ref="chatContainer" class="auto-audio-player-root" v-if="useAudioLayout" :room="room"
|
||||
:events="events" :autoplay="!showRecorder"
|
||||
:timelineSet="timelineSet"
|
||||
:readMarker="readMarker"
|
||||
v-on:start-recording="showRecorder = true"
|
||||
v-on:loadnext="handleScrolledToBottom(false)"
|
||||
v-on:loadprevious="handleScrolledToTop()"
|
||||
v-on:mark-read="sendRR"
|
||||
/>
|
||||
<VoiceRecorder class="audio-layout" v-if="useAudioLayout" :micButtonRef="$refs.mic_button" :ptt="showRecorderPTT" :show="showRecorder"
|
||||
v-on:close="showRecorder = false" v-on:file="onVoiceRecording" />
|
||||
|
||||
|
||||
<div v-if="!useAudioLayout" 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="
|
||||
showContextMenu = false;
|
||||
showContextMenuAnchor = null;
|
||||
"
|
||||
v-if="showMessageOperations"
|
||||
v-on:addreaction="addReaction"
|
||||
v-on:addquickreaction="addQuickReaction"
|
||||
v-on:addreply="addReply(selectedEvent)"
|
||||
v-on:edit="edit(selectedEvent)"
|
||||
v-on:redact="redact(selectedEvent)"
|
||||
v-on:download="download(selectedEvent)"
|
||||
v-on:more="
|
||||
<message-operations ref="messageOperations" :style="opStyle" :emojis="recentEmojis" v-on:close="
|
||||
showContextMenu = false;
|
||||
showContextMenuAnchor = null;
|
||||
" v-if="showMessageOperations" v-on:addreaction="addReaction" v-on:addquickreaction="addQuickReaction"
|
||||
v-on:addreply="addReply(selectedEvent)" v-on:edit="edit(selectedEvent)" v-on:redact="redact(selectedEvent)"
|
||||
v-on:download="download(selectedEvent)" v-on:more="
|
||||
isEmojiQuickReaction= true
|
||||
showMoreMessageOperations($event)
|
||||
"
|
||||
:event="selectedEvent"
|
||||
/>
|
||||
" :originalEvent="selectedEvent" />
|
||||
</div>
|
||||
|
||||
<div ref="avatarOperationsStrut" class="avatar-operations-strut">
|
||||
<avatar-operations
|
||||
ref="avatarOperations"
|
||||
:style="avatarOpStyle"
|
||||
v-on:close="
|
||||
showAvatarMenu = false;
|
||||
showAvatarMenuAnchor = null;
|
||||
"
|
||||
v-on:start-private-chat="startPrivateChat($event)"
|
||||
v-if="selectedEvent && showAvatarMenu"
|
||||
:room="room"
|
||||
:event="selectedEvent"
|
||||
/>
|
||||
<avatar-operations ref="avatarOperations" :style="avatarOpStyle" v-on:close="
|
||||
showAvatarMenu = false;
|
||||
showAvatarMenuAnchor = null;
|
||||
" v-on:start-private-chat="startPrivateChat($event)" v-if="selectedEvent && showAvatarMenu" :room="room"
|
||||
:originalEvent="selectedEvent" />
|
||||
</div>
|
||||
|
||||
<!-- Handle resizes, e.g. when soft keyboard is shown/hidden -->
|
||||
|
|
@ -59,55 +49,31 @@
|
|||
<div v-if="showDayMarkerBeforeEvent(event)" class="day-marker" :title="dayForEvent(event)" />
|
||||
|
||||
<div v-if="!event.isRelation() && !event.isRedaction()" :ref="event.getId()">
|
||||
<div
|
||||
class="message-wrapper"
|
||||
v-on:touchstart="
|
||||
(e) => {
|
||||
touchStart(e, event);
|
||||
}
|
||||
"
|
||||
v-on:touchend="touchEnd"
|
||||
v-on:touchcancel="touchCancel"
|
||||
v-on:touchmove="touchMove"
|
||||
>
|
||||
<component
|
||||
:is="componentForEvent(event)"
|
||||
:room="room"
|
||||
:event="event"
|
||||
:nextEvent="events[index + 1]"
|
||||
:timelineSet="timelineSet"
|
||||
v-on:send-quick-reaction="sendQuickReaction"
|
||||
v-on:context-menu="showContextMenuForEvent($event)"
|
||||
v-on:own-avatar-clicked="viewProfile"
|
||||
v-on:other-avatar-clicked="showAvatarMenuForEvent($event)"
|
||||
v-on:download="download(event)"
|
||||
v-on:poll-closed="pollWasClosed(event)"
|
||||
/>
|
||||
<div class="message-wrapper" v-on:touchstart="
|
||||
(e) => {
|
||||
touchStart(e, event);
|
||||
}
|
||||
" v-on:touchend="touchEnd" v-on:touchcancel="touchCancel" v-on:touchmove="touchMove">
|
||||
<component :is="componentForEvent(event)" :room="room" :originalEvent="event" :nextEvent="events[index + 1]"
|
||||
:timelineSet="timelineSet" v-on:send-quick-reaction="sendQuickReaction"
|
||||
v-on:context-menu="showContextMenuForEvent($event)" v-on:own-avatar-clicked="viewProfile"
|
||||
v-on:other-avatar-clicked="showAvatarMenuForEvent($event)" v-on:download="download(event)"
|
||||
v-on:poll-closed="pollWasClosed(event)" />
|
||||
<!-- <div v-if="debugging" style="user-select:text">EventID: {{ event.getId() }}</div> -->
|
||||
<!-- <div v-if="debugging" style="user-select:text">Event: {{ JSON.stringify(event) }}</div> -->
|
||||
<div
|
||||
v-if="event.getId() == readMarker && index < events.length - 1"
|
||||
class="read-marker"
|
||||
:title="$t('message.unread_messages')"
|
||||
/>
|
||||
<div v-if="event.getId() == readMarker && index < events.length - 1" class="read-marker"
|
||||
:title="$t('message.unread_messages')" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Input area -->
|
||||
<v-container v-if="room" fluid :class="['input-area-outer', replyToEvent ? 'reply-to' : '']">
|
||||
<v-container v-if="!useAudioLayout && room" fluid :class="['input-area-outer', replyToEvent ? 'reply-to' : '']">
|
||||
<div :class="[replyToEvent ? 'iput-area-inner-box' : '']">
|
||||
<!-- "Scroll to end"-button -->
|
||||
<v-btn
|
||||
class="scroll-to-end"
|
||||
v-show="showScrollToEnd"
|
||||
fab
|
||||
x-small
|
||||
elevation="0"
|
||||
color="black"
|
||||
@click.stop="scrollToEndOfTimeline"
|
||||
>
|
||||
<v-btn v-if="!useAudioLayout" class="scroll-to-end" v-show="showScrollToEnd" fab x-small elevation="0" color="black"
|
||||
@click.stop="scrollToEndOfTimeline">
|
||||
<v-icon color="white">arrow_downward</v-icon>
|
||||
</v-btn>
|
||||
|
||||
|
|
@ -121,10 +87,11 @@
|
|||
<div v-if="replyToContentType === 'm.image'">{{ $t("message.reply_image") }}</div>
|
||||
<div v-if="replyToContentType === 'm.audio'">{{ $t("message.reply_audio_message") }}</div>
|
||||
<div v-if="replyToContentType === 'm.video'">{{ $t("message.reply_video") }}</div>
|
||||
<div v-if="replyToContentType === 'm.poll'">{{ $t("message.reply_poll") }}</div>
|
||||
<div v-if="replyToContentType === 'm.poll'">{{ $t("message.reply_poll") }}</div>
|
||||
</div>
|
||||
<div class="col col-auto" v-if="replyToContentType !== 'm.text'">
|
||||
<img v-if="replyToContentType === 'm.image'" width="50px" height="50px" :src="replyToImg" class="rounded" />
|
||||
<img v-if="replyToContentType === 'm.image'" width="50px" height="50px" :src="replyToImg"
|
||||
class="rounded" />
|
||||
<v-img v-if="replyToContentType === 'm.audio'" src="@/assets/icons/audio_message.svg" />
|
||||
<v-img v-if="replyToContentType === 'm.video'" src="@/assets/icons/video_message.svg" />
|
||||
<v-icon v-if="replyToContentType === 'm.poll'" light>$vuetify.icons.poll</v-icon>
|
||||
|
|
@ -143,24 +110,13 @@
|
|||
</v-row>
|
||||
<v-row class="input-area-inner align-center">
|
||||
<v-col class="flex-grow-1 flex-shrink-1 ma-0 pa-0">
|
||||
<v-textarea
|
||||
height="undefined"
|
||||
ref="messageInput"
|
||||
full-width
|
||||
auto-grow
|
||||
rows="1"
|
||||
v-model="currentInput"
|
||||
no-resize
|
||||
class="input-area-text"
|
||||
:placeholder="$t('message.your_message')"
|
||||
hide-details
|
||||
background-color="white"
|
||||
v-on:keydown.enter.prevent="
|
||||
<v-textarea height="undefined" ref="messageInput" full-width auto-grow rows="1" v-model="currentInput"
|
||||
no-resize class="input-area-text" :placeholder="$t('message.your_message')" hide-details
|
||||
background-color="white" v-on:keydown.enter.prevent="
|
||||
() => {
|
||||
sendCurrentTextMessage();
|
||||
}
|
||||
"
|
||||
/>
|
||||
" />
|
||||
</v-col>
|
||||
|
||||
<v-col class="input-area-button text-center flex-grow-0 flex-shrink-1" v-if="editedEvent">
|
||||
|
|
@ -169,150 +125,82 @@
|
|||
</v-btn>
|
||||
</v-col>
|
||||
|
||||
<v-col
|
||||
v-if="(!currentInput || currentInput.length == 0) && !showRecorder && canCreatePoll"
|
||||
class="input-area-button text-center flex-grow-0 flex-shrink-1"
|
||||
>
|
||||
<v-col v-if="(!currentInput || currentInput.length == 0) && !showRecorder && canCreatePoll"
|
||||
class="input-area-button text-center flex-grow-0 flex-shrink-1">
|
||||
<v-btn icon large color="black" @click="showCreatePollDialog = true">
|
||||
<v-icon dark>$vuetify.icons.poll</v-icon>
|
||||
</v-btn>
|
||||
</v-col>
|
||||
|
||||
<v-col
|
||||
class="input-area-button text-center flex-grow-0 flex-shrink-1"
|
||||
v-if="!currentInput || currentInput.length == 0 || showRecorder"
|
||||
>
|
||||
<v-btn
|
||||
v-if="canRecordAudio"
|
||||
class="mic-button"
|
||||
ref="mic_button"
|
||||
fab
|
||||
small
|
||||
elevation="0"
|
||||
v-blur
|
||||
v-longTap:250="[showRecordingUI, startRecording]"
|
||||
>
|
||||
<v-col class="input-area-button text-center flex-grow-0 flex-shrink-1"
|
||||
v-if="!currentInput || currentInput.length == 0 || showRecorder">
|
||||
<v-btn v-if="canRecordAudio" class="mic-button" ref="mic_button" fab small elevation="0" v-blur
|
||||
v-longTap:250="[showRecordingUI, startRecording]">
|
||||
<v-icon :color="showRecorder ? 'white' : 'black'">mic</v-icon>
|
||||
</v-btn>
|
||||
<v-btn
|
||||
v-else
|
||||
class="mic-button"
|
||||
ref="mic_button"
|
||||
fab
|
||||
small
|
||||
elevation="0"
|
||||
v-blur
|
||||
@click.stop="showNoRecordingAvailableDialog = true"
|
||||
>
|
||||
<v-btn v-else class="mic-button" ref="mic_button" fab small elevation="0" v-blur
|
||||
@click.stop="showNoRecordingAvailableDialog = true">
|
||||
<v-icon :color="showRecorder ? 'white' : 'black'">mic</v-icon>
|
||||
</v-btn>
|
||||
</v-col>
|
||||
|
||||
<v-col class="input-area-button text-center flex-grow-0 flex-shrink-1" v-else>
|
||||
<v-btn
|
||||
fab
|
||||
small
|
||||
elevation="0"
|
||||
color="black"
|
||||
@click.stop="sendCurrentTextMessage"
|
||||
:disabled="sendButtonDisabled"
|
||||
>
|
||||
<v-btn fab small elevation="0" color="black" @click.stop="sendCurrentTextMessage"
|
||||
:disabled="sendButtonDisabled">
|
||||
<v-icon color="white">{{ editedEvent ? "save" : "arrow_upward" }}</v-icon>
|
||||
</v-btn>
|
||||
</v-col>
|
||||
|
||||
<v-col
|
||||
class="input-area-button text-center flex-grow-0 flex-shrink-1 input-more-icon"
|
||||
>
|
||||
<v-btn
|
||||
fab
|
||||
small
|
||||
elevation="0"
|
||||
v-blur
|
||||
@click.stop="
|
||||
isEmojiQuickReaction = false
|
||||
showMoreMessageOperations($event)
|
||||
"
|
||||
>
|
||||
<v-col class="input-area-button text-center flex-grow-0 flex-shrink-1 input-more-icon">
|
||||
<v-btn fab small elevation="0" v-blur @click.stop="
|
||||
isEmojiQuickReaction = false
|
||||
showMoreMessageOperations($event)
|
||||
">
|
||||
<v-icon>$vuetify.icons.addReaction</v-icon>
|
||||
</v-btn>
|
||||
</v-col>
|
||||
|
||||
<v-col v-if="$config.shortCodeStickers" class="input-area-button text-center flex-grow-0 flex-shrink-1">
|
||||
<v-btn
|
||||
v-if="!showRecorder"
|
||||
id="btn-attach"
|
||||
icon
|
||||
large
|
||||
color="black"
|
||||
@click="showStickerPicker"
|
||||
:disabled="attachButtonDisabled"
|
||||
>
|
||||
<v-btn v-if="!showRecorder" id="btn-attach" icon large color="black" @click="showStickerPicker"
|
||||
:disabled="attachButtonDisabled">
|
||||
<v-icon large>face</v-icon>
|
||||
</v-btn>
|
||||
</v-col>
|
||||
|
||||
<v-col class="input-area-button text-center flex-grow-0 flex-shrink-1">
|
||||
<label icon flat ref="attachmentLabel">
|
||||
<v-btn
|
||||
v-if="!showRecorder"
|
||||
icon
|
||||
large
|
||||
color="black"
|
||||
@click="showAttachmentPicker"
|
||||
:disabled="attachButtonDisabled"
|
||||
>
|
||||
<v-btn v-if="!showRecorder" icon large color="black" @click="showAttachmentPicker"
|
||||
:disabled="attachButtonDisabled">
|
||||
<v-icon x-large>add_circle_outline</v-icon>
|
||||
</v-btn>
|
||||
<input
|
||||
ref="attachment"
|
||||
type="file"
|
||||
name="attachment"
|
||||
@change="handlePickedAttachment($event)"
|
||||
accept="image/*|audio/*|video/*|application/pdf"
|
||||
class="d-none"
|
||||
/>
|
||||
</label>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<VoiceRecorder
|
||||
:micButtonRef="$refs.mic_button"
|
||||
:ptt="showRecorderPTT"
|
||||
:show="showRecorder"
|
||||
v-on:close="showRecorder = false"
|
||||
v-on:file="onVoiceRecording"
|
||||
/>
|
||||
<VoiceRecorder :micButtonRef="$refs.mic_button" :ptt="showRecorderPTT" :show="showRecorder"
|
||||
v-on:close="showRecorder = false" v-on:file="onVoiceRecording" />
|
||||
</div>
|
||||
</v-container>
|
||||
|
||||
<input ref="attachment" type="file" name="attachment" @change="handlePickedAttachment($event)"
|
||||
accept="image/*|audio/*|video/*|application/pdf" class="d-none" />
|
||||
|
||||
<div v-if="currentImageInputPath">
|
||||
<v-dialog v-model="currentImageInputPath" class="ma-0 pa-0" :width="$vuetify.breakpoint.smAndUp ? '50%' : '85%'">
|
||||
<v-card class="ma-0 pa-0">
|
||||
<v-card-text class="ma-0 pa-2">
|
||||
<v-img
|
||||
v-if="currentImageInput && currentImageInput.image"
|
||||
:aspect-ratio="1"
|
||||
:src="currentImageInput.image"
|
||||
contain
|
||||
class="current-image-input-path"
|
||||
/>
|
||||
<v-img v-if="currentImageInput && currentImageInput.image" :aspect-ratio="1" :src="currentImageInput.image"
|
||||
contain class="current-image-input-path" />
|
||||
<div>
|
||||
file: {{ currentImageInputPath.name }}
|
||||
<span v-if="currentImageInput && currentImageInput.scaled && currentImageInput.useScaled">
|
||||
{{ currentImageInput.scaledDimensions.width }} x {{ currentImageInput.scaledDimensions.height }}</span
|
||||
>
|
||||
{{ currentImageInput.scaledDimensions.width }} x {{ currentImageInput.scaledDimensions.height }}</span>
|
||||
<span v-else-if="currentImageInput && currentImageInput.dimensions">
|
||||
{{ currentImageInput.dimensions.width }} x {{ currentImageInput.dimensions.height }}</span
|
||||
>
|
||||
{{ currentImageInput.dimensions.width }} x {{ currentImageInput.dimensions.height }}</span>
|
||||
<span v-if="currentImageInput && currentImageInput.scaled && currentImageInput.useScaled">
|
||||
({{ formatBytes(currentImageInput.scaledSize) }})</span
|
||||
>
|
||||
({{ formatBytes(currentImageInput.scaledSize) }})</span>
|
||||
<span v-else> ({{ formatBytes(currentImageInputPath.size) }})</span>
|
||||
<v-switch
|
||||
v-if="currentImageInput && currentImageInput.scaled"
|
||||
:label="$t('message.scale_image')"
|
||||
v-model="currentImageInput.useScaled"
|
||||
/>
|
||||
<v-switch v-if="currentImageInput && currentImageInput.scaled" :label="$t('message.scale_image')"
|
||||
v-model="currentImageInput.useScaled" />
|
||||
</div>
|
||||
<div v-if="currentSendError">{{ currentSendError }}</div>
|
||||
<div v-else>{{ currentSendProgress }}</div>
|
||||
|
|
@ -321,17 +209,10 @@
|
|||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn color="primary" text @click="cancelSendAttachment" id="btn-attachment-cancel">{{
|
||||
$t("menu.cancel")
|
||||
$t("menu.cancel")
|
||||
}}</v-btn>
|
||||
<v-btn
|
||||
id="btn-attachment-send"
|
||||
color="primary"
|
||||
text
|
||||
@click="sendAttachment"
|
||||
v-if="currentSendShowSendButton"
|
||||
:disabled="currentSendOperation != null"
|
||||
>{{ $t("menu.send") }}</v-btn
|
||||
>
|
||||
<v-btn id="btn-attachment-send" color="primary" text @click="sendAttachment"
|
||||
v-if="currentSendShowSendButton" :disabled="currentSendOperation != null">{{ $t("menu.send") }}</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
|
@ -363,7 +244,7 @@
|
|||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn id="btn-ok" color="primary" text @click="showNoRecordingAvailableDialog = false">{{
|
||||
$t("menu.ok")
|
||||
$t("menu.ok")
|
||||
}}</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
|
|
@ -389,6 +270,7 @@ import BottomSheet from "./BottomSheet.vue";
|
|||
import ImageResize from "image-resize";
|
||||
import CreatePollDialog from "./CreatePollDialog.vue";
|
||||
import chatMixin from "./chatMixin";
|
||||
import AudioLayout from "./AudioLayout.vue";
|
||||
|
||||
const sizeOf = require("image-size");
|
||||
const dataUriToBuffer = require("data-uri-to-buffer");
|
||||
|
|
@ -405,7 +287,7 @@ function ScrollPosition(node) {
|
|||
this.readyFor = "up";
|
||||
}
|
||||
|
||||
ScrollPosition.prototype.restore = function() {
|
||||
ScrollPosition.prototype.restore = function () {
|
||||
if (this.readyFor === "up") {
|
||||
this.node.scrollTop = this.node.scrollHeight - this.previousScrollHeightMinusTop;
|
||||
} else {
|
||||
|
|
@ -413,7 +295,7 @@ ScrollPosition.prototype.restore = function() {
|
|||
}
|
||||
};
|
||||
|
||||
ScrollPosition.prototype.prepareFor = function(direction) {
|
||||
ScrollPosition.prototype.prepareFor = function (direction) {
|
||||
this.readyFor = direction || "up";
|
||||
if (this.readyFor === "up") {
|
||||
this.previousScrollHeightMinusTop = this.node.scrollHeight - this.node.scrollTop;
|
||||
|
|
@ -436,6 +318,7 @@ export default {
|
|||
BottomSheet,
|
||||
AvatarOperations,
|
||||
CreatePollDialog,
|
||||
AudioLayout
|
||||
},
|
||||
|
||||
data() {
|
||||
|
|
@ -516,9 +399,13 @@ export default {
|
|||
},
|
||||
|
||||
mounted() {
|
||||
const container = this.$refs.chatContainer;
|
||||
this.scrollPosition = new ScrollPosition(container);
|
||||
this.chatContainerSize = this.$refs.chatContainerResizer.$el.clientHeight;
|
||||
const container = this.chatContainer;
|
||||
if (container) {
|
||||
this.scrollPosition = new ScrollPosition(container);
|
||||
if (this.$refs.chatContainerResizer) {
|
||||
this.chatContainerSize = this.$refs.chatContainerResizer.$el.clientHeight;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
|
|
@ -531,6 +418,14 @@ export default {
|
|||
},
|
||||
|
||||
computed: {
|
||||
chatContainer() {
|
||||
const container = this.$refs.chatContainer;
|
||||
console.log("GOT CONTAINER", container);
|
||||
if (this.useAudioLayout) {
|
||||
return container.$el;
|
||||
}
|
||||
return container;
|
||||
},
|
||||
senderDisplayName() {
|
||||
return this.room.getMember(this.replyToEvent.sender.userId).name;
|
||||
},
|
||||
|
|
@ -627,9 +522,28 @@ export default {
|
|||
me && this.room.currentState && this.room.currentState.hasSufficientPowerLevelFor("redact", me.powerLevel);
|
||||
return isAdmin;
|
||||
},
|
||||
useAudioLayout: {
|
||||
get: function () {
|
||||
if (this.room) {
|
||||
const tags = this.room.tags;
|
||||
if (tags && tags["ui_options"]) {
|
||||
return tags["ui_options"]["audio_layout"] === 1;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
initialLoadDone: {
|
||||
immediate: true,
|
||||
handler(value, oldValue) {
|
||||
if (value && !oldValue) {
|
||||
console.log("Loading finished!");
|
||||
}
|
||||
}
|
||||
},
|
||||
roomId: {
|
||||
immediate: true,
|
||||
handler(value, oldValue) {
|
||||
|
|
@ -729,6 +643,7 @@ export default {
|
|||
const getMoreIfNeeded = function _getMoreIfNeeded() {
|
||||
const container = self.$refs.chatContainer;
|
||||
if (
|
||||
container &&
|
||||
container.scrollHeight <= (1 + 2 * WINDOW_BUFFER_SIZE) * container.clientHeight &&
|
||||
self.timelineWindow &&
|
||||
self.timelineWindow.canPaginate(EventTimeline.BACKWARDS)
|
||||
|
|
@ -867,22 +782,22 @@ export default {
|
|||
handleChatContainerResize({ ignoredWidth, height }) {
|
||||
const delta = height - this.chatContainerSize;
|
||||
this.chatContainerSize = height;
|
||||
const container = this.$refs.chatContainer;
|
||||
if (delta < 0) {
|
||||
const container = this.chatContainer;
|
||||
if (container && delta < 0) {
|
||||
container.scrollTop -= delta;
|
||||
}
|
||||
},
|
||||
|
||||
paginateBackIfNeeded() {
|
||||
this.$nextTick(() => {
|
||||
const container = this.$refs.chatContainer;
|
||||
if (container.scrollHeight <= container.clientHeight) {
|
||||
const container = this.chatContainer;
|
||||
if (container && container.scrollHeight <= container.clientHeight) {
|
||||
this.handleScrolledToTop();
|
||||
}
|
||||
});
|
||||
},
|
||||
onScroll(ignoredevent) {
|
||||
const container = this.$refs.chatContainer;
|
||||
const container = this.chatContainer;
|
||||
if (!container) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -898,7 +813,7 @@ export default {
|
|||
container.scrollHeight === container.clientHeight
|
||||
? false
|
||||
: container.scrollHeight - container.scrollTop.toFixed(0) > container.clientHeight ||
|
||||
(this.timelineWindow && this.timelineWindow.canPaginate(EventTimeline.FORWARDS));
|
||||
(this.timelineWindow && this.timelineWindow.canPaginate(EventTimeline.FORWARDS));
|
||||
|
||||
this.restartRRTimer();
|
||||
},
|
||||
|
|
@ -908,19 +823,20 @@ export default {
|
|||
return; // Not for this room
|
||||
}
|
||||
|
||||
const loadingDone = this.initialLoadDone;
|
||||
this.$matrix.matrixClient.decryptEventIfNeeded(event, {});
|
||||
|
||||
if (this.initialLoadDone) {
|
||||
if (this.initialLoadDone && !this.useAudioLayout) {
|
||||
this.paginateBackIfNeeded();
|
||||
}
|
||||
|
||||
// If we are at bottom, scroll to see new events...
|
||||
const container = this.$refs.chatContainer;
|
||||
var scrollToSeeNew = event.getSender() == this.$matrix.currentUserId; // When we sent, scroll
|
||||
if (container.scrollHeight - container.scrollTop.toFixed(0) == container.clientHeight) {
|
||||
scrollToSeeNew = true;
|
||||
}
|
||||
if (this.initialLoadDone && event.forwardLooking && !event.isRelation()) {
|
||||
if (loadingDone && event.forwardLooking && !event.isRelation()) {
|
||||
// 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;
|
||||
}
|
||||
this.handleScrolledToBottom(scrollToSeeNew);
|
||||
}
|
||||
},
|
||||
|
|
@ -1140,16 +1056,18 @@ export default {
|
|||
.paginate(EventTimeline.FORWARDS, 10, true)
|
||||
.then((success) => {
|
||||
if (success) {
|
||||
this.scrollPosition.prepareFor("down");
|
||||
this.events = this.timelineWindow.getEvents();
|
||||
this.$nextTick(() => {
|
||||
// restore scroll position!
|
||||
console.log("Restore scroll!");
|
||||
this.scrollPosition.restore();
|
||||
if (scrollToEnd) {
|
||||
this.smoothScrollToEnd();
|
||||
}
|
||||
});
|
||||
if (!this.useAudioLayout) {
|
||||
this.scrollPosition.prepareFor("down");
|
||||
this.$nextTick(() => {
|
||||
// restore scroll position!
|
||||
console.log("Restore scroll!");
|
||||
this.scrollPosition.restore();
|
||||
if (scrollToEnd) {
|
||||
this.smoothScrollToEnd();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
|
|
@ -1162,7 +1080,7 @@ export default {
|
|||
* Scroll so that the given event is at the middle of the chat view (if more events) or else at the bottom.
|
||||
*/
|
||||
scrollToEvent(eventId) {
|
||||
const container = this.$refs.chatContainer;
|
||||
const container = this.chatContainer;
|
||||
const ref = this.$refs[eventId];
|
||||
if (container && ref) {
|
||||
const targetY = container.clientHeight / 2;
|
||||
|
|
@ -1172,9 +1090,9 @@ export default {
|
|||
},
|
||||
|
||||
smoothScrollToEnd() {
|
||||
this.$nextTick(function() {
|
||||
const container = this.$refs.chatContainer;
|
||||
if (container.children.length > 0) {
|
||||
this.$nextTick(function () {
|
||||
const container = this.chatContainer;
|
||||
if (container && container.children.length > 0) {
|
||||
const lastChild = container.children[container.children.length - 1];
|
||||
console.log("Scroll into view", lastChild);
|
||||
window.requestAnimationFrame(() => {
|
||||
|
|
@ -1251,7 +1169,7 @@ export default {
|
|||
document.body.appendChild(link);
|
||||
link.click();
|
||||
|
||||
setTimeout(function() {
|
||||
setTimeout(function () {
|
||||
document.body.removeChild(link);
|
||||
URL.revokeObjectURL(url);
|
||||
}, 200);
|
||||
|
|
@ -1268,8 +1186,8 @@ export default {
|
|||
},
|
||||
|
||||
emojiSelected(e) {
|
||||
if(this.isEmojiQuickReaction) {
|
||||
// When quick emoji picker is clicked
|
||||
if (this.isEmojiQuickReaction) {
|
||||
// When quick emoji picker is clicked
|
||||
if (this.selectedEvent) {
|
||||
const event = this.selectedEvent;
|
||||
this.selectedEvent = null;
|
||||
|
|
@ -1369,65 +1287,78 @@ export default {
|
|||
* Start/restart the timer to Read Receipts.
|
||||
*/
|
||||
restartRRTimer() {
|
||||
console.log("Restart RR timer");
|
||||
this.stopRRTimer();
|
||||
this.rrTimer = setTimeout(this.rrTimerElapsed, READ_RECEIPT_TIMEOUT);
|
||||
|
||||
let eventIdFirst = null;
|
||||
let eventIdLast = null;
|
||||
if (!this.useAudioLayout) {
|
||||
const container = this.chatContainer;
|
||||
const elFirst = util.getFirstVisibleElement(container);
|
||||
const elLast = util.getLastVisibleElement(container);
|
||||
if (elFirst && elLast) {
|
||||
eventIdFirst = elFirst.getAttribute("eventId");
|
||||
eventIdLast = elLast.getAttribute("eventId");
|
||||
}
|
||||
}
|
||||
if (eventIdFirst && eventIdLast) {
|
||||
this.rrTimer = setTimeout(() => { this.rrTimerElapsed(eventIdFirst, eventIdLast) }, READ_RECEIPT_TIMEOUT);
|
||||
}
|
||||
},
|
||||
|
||||
rrTimerElapsed() {
|
||||
rrTimerElapsed(eventIdFirst, eventIdLast) {
|
||||
console.log("RR timer elapsed", eventIdFirst, eventIdLast);
|
||||
this.rrTimer = null;
|
||||
this.sendRR(eventIdFirst, eventIdLast);
|
||||
this.restartRRTimer();
|
||||
},
|
||||
|
||||
const container = this.$refs.chatContainer;
|
||||
const elFirst = util.getFirstVisibleElement(container);
|
||||
const elLast = util.getLastVisibleElement(container);
|
||||
if (elFirst && elLast) {
|
||||
const eventIdFirst = elFirst.getAttribute("eventId");
|
||||
const eventIdLast = elLast.getAttribute("eventId");
|
||||
if (eventIdLast && this.room) {
|
||||
var event = this.room.findEventById(eventIdLast);
|
||||
const index = this.events.indexOf(event);
|
||||
sendRR(eventIdFirst, eventIdLast) {
|
||||
console.log("SEND RR", eventIdFirst, eventIdLast);
|
||||
if (eventIdLast && this.room) {
|
||||
var event = this.room.findEventById(eventIdLast);
|
||||
const index = this.events.indexOf(event);
|
||||
|
||||
// Walk backwards through visible events to the first one that is incoming
|
||||
//
|
||||
var lastTimestamp = 0;
|
||||
if (this.lastRR) {
|
||||
lastTimestamp = this.lastRR.getTs();
|
||||
// Walk backwards through visible events to the first one that is incoming
|
||||
//
|
||||
var lastTimestamp = 0;
|
||||
if (this.lastRR) {
|
||||
lastTimestamp = this.lastRR.getTs();
|
||||
}
|
||||
|
||||
for (var i = index; i >= 0; i--) {
|
||||
event = this.events[i];
|
||||
if (event == this.lastRR || event.getTs() <= lastTimestamp) {
|
||||
// Already sent this or too old...
|
||||
break;
|
||||
}
|
||||
// Make sure it's not a local echo event...
|
||||
if (!event.getId().startsWith("~")) {
|
||||
// Send read receipt
|
||||
this.$matrix.matrixClient
|
||||
.sendReadReceipt(event)
|
||||
.then(() => {
|
||||
this.$matrix.matrixClient.setRoomReadMarkers(this.room.roomId, event.getId());
|
||||
})
|
||||
.then(() => {
|
||||
console.log("RR sent for event: " + event.getId());
|
||||
this.lastRR = event;
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log("Failed to update read marker: ", err);
|
||||
})
|
||||
.finally(() => {
|
||||
this.restartRRTimer();
|
||||
});
|
||||
return; // Bail out here
|
||||
}
|
||||
|
||||
for (var i = index; i >= 0; i--) {
|
||||
event = this.events[i];
|
||||
if (event == this.lastRR || event.getTs() <= lastTimestamp) {
|
||||
// Already sent this or too old...
|
||||
break;
|
||||
}
|
||||
// Make sure it's not a local echo event...
|
||||
if (!event.getId().startsWith("~")) {
|
||||
// Send read receipt
|
||||
this.$matrix.matrixClient
|
||||
.sendReadReceipt(event)
|
||||
.then(() => {
|
||||
this.$matrix.matrixClient.setRoomReadMarkers(this.room.roomId, event.getId());
|
||||
})
|
||||
.then(() => {
|
||||
console.log("RR sent for event: " + event.getId());
|
||||
this.lastRR = event;
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log("Failed to update read marker: ", err);
|
||||
})
|
||||
.finally(() => {
|
||||
this.restartRRTimer();
|
||||
});
|
||||
return; // Bail out here
|
||||
}
|
||||
|
||||
// Stop iterating at first visible
|
||||
if (event.getId() == eventIdFirst) {
|
||||
break;
|
||||
}
|
||||
// Stop iterating at first visible
|
||||
if (event.getId() == eventIdFirst) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.restartRRTimer();
|
||||
},
|
||||
|
||||
showRecordingUI() {
|
||||
|
|
@ -1499,9 +1430,9 @@ export default {
|
|||
let div = document.createElement("div");
|
||||
div.classList.add("toast");
|
||||
div.innerText = this.$t("poll_create.results_shared");
|
||||
this.$refs.chatContainer.parentElement.appendChild(div);
|
||||
this.chatContainer.parentElement.appendChild(div);
|
||||
setTimeout(() => {
|
||||
this.$refs.chatContainer.parentElement.removeChild(div);
|
||||
this.chatContainer.parentElement.removeChild(div);
|
||||
}, 3000);
|
||||
}
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue