Merge branch 'catch-component-errors' into 'dev'

Handle message rendering errors via the errorCaptured hook

See merge request keanuapp/keanuapp-weblite!298
This commit is contained in:
N Pex 2024-06-07 13:06:05 +00:00
commit 134b1f8cff
5 changed files with 68 additions and 34 deletions

View file

@ -109,7 +109,8 @@
"files": "Files",
"images": "Images",
"send_attachements_dialog_title": "Do you want to send following attachments ?",
"download_all": "Download all"
"download_all": "Download all",
"failed_to_render": "Failed to render event"
},
"room": {
"invitations": "You have no invitations | You have 1 invitation | You have {count} invitations",

View file

@ -62,35 +62,37 @@
<div v-if="showDayMarkerBeforeEvent(event) && !!componentForEvent(event, isForExport = false)" class="day-marker"><div class="line"></div><div class="text">{{ dayForEvent(event) }}</div><div class="line"></div></div>
<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">
<!-- Note: For threaded media messages, IF there is only one item we show that media item as a single component.
We might therefore get calls to v-on:context-menu that has the event set to that single media item, not the top level thread event
that is really displayed in the flow. Therefore, we rewrite these events with "{event: event, anchor: $event.anchor}",
see below. Otherwise things like context menus won't work as designed.
-->
<component :is="componentForEvent(event)" :room="room" :originalEvent="event" :nextEvent="filteredEvents[index + 1]"
:timelineSet="timelineSet" v-on:send-quick-reaction.stop="sendQuickReaction"
:componentFn="componentForEvent"
v-on:context-menu="showContextMenuForEvent({event: event, anchor: $event.anchor})"
v-on:own-avatar-clicked="viewProfile"
v-on:other-avatar-clicked="showAvatarMenuForEvent({event: event, anchor: $event.anchor})"
v-on:download="download(event)"
v-on:poll-closed="pollWasClosed(event)"
v-on:more="
isEmojiQuickReaction = true
showMoreMessageOperations({event: event, anchor: $event.anchor})
"
v-on:layout-change="onLayoutChange"
v-on:addQuickHeartReaction="addQuickHeartReaction(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 < filteredEvents.length - 1" class="read-marker"><div class="line"></div><div class="text">{{ $t('message.unread_messages') }}</div><div class="line"></div></div>
</div>
<MessageErrorHandler>
<div class="message-wrapper" v-on:touchstart="
(e) => {
touchStart(e, event);
}
" v-on:touchend="touchEnd" v-on:touchcancel="touchCancel" v-on:touchmove="touchMove">
<!-- Note: For threaded media messages, IF there is only one item we show that media item as a single component.
We might therefore get calls to v-on:context-menu that has the event set to that single media item, not the top level thread event
that is really displayed in the flow. Therefore, we rewrite these events with "{event: event, anchor: $event.anchor}",
see below. Otherwise things like context menus won't work as designed.
-->
<component :is="componentForEvent(event)" :room="room" :originalEvent="event" :nextEvent="filteredEvents[index + 1]"
:timelineSet="timelineSet" v-on:send-quick-reaction.stop="sendQuickReaction"
:componentFn="componentForEvent"
v-on:context-menu="showContextMenuForEvent({event: event, anchor: $event.anchor})"
v-on:own-avatar-clicked="viewProfile"
v-on:other-avatar-clicked="showAvatarMenuForEvent({event: event, anchor: $event.anchor})"
v-on:download="download(event)"
v-on:poll-closed="pollWasClosed(event)"
v-on:more="
isEmojiQuickReaction = true
showMoreMessageOperations({event: event, anchor: $event.anchor})
"
v-on:layout-change="onLayoutChange"
v-on:addQuickHeartReaction="addQuickHeartReaction(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 < filteredEvents.length - 1" class="read-marker"><div class="line"></div><div class="text">{{ $t('message.unread_messages') }}</div><div class="line"></div></div>
</div>
</MessageErrorHandler>
</div>
</div>
@ -213,7 +215,7 @@
</v-container>
<input ref="attachment" type="file" name="attachment" @change="handlePickedAttachment($event)"
accept="image/*,audio/*,video/*,.pdf,application/pdf,.apk,application/vnd.android.package-archive,.ipa,.zip,application/zip,application/x-zip-compressed,multipart/x-zip" class="d-none" multiple/>
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">
<v-dialog v-model="currentFileInputsDialog" class="ma-0 pa-0" :width="$vuetify.breakpoint.smAndUp ? '50%' : '85%'" persistent scrollable>
@ -364,6 +366,7 @@ import FileDropLayout from "./file_mode/FileDropLayout";
import roomTypeMixin from "./roomTypeMixin";
import roomMembersMixin from "./roomMembersMixin";
import PurgeRoomDialog from "../components/PurgeRoomDialog";
import MessageErrorHandler from "./MessageErrorHandler";
const sizeOf = require("image-size");
const dataUriToBuffer = require("data-uri-to-buffer");
@ -417,7 +420,8 @@ export default {
FileDropLayout,
UserProfileDialog,
PurgeRoomDialog,
WelcomeHeaderChannelUser
WelcomeHeaderChannelUser,
MessageErrorHandler
},
data() {

View file

@ -0,0 +1,29 @@
<template>
<div>
<slot
v-if="err"
name="error"
v-bind:err="err"
><div class="text-center">{{ $t('message.failed_to_render') }}</div></slot>
<slot v-else></slot>
</div>
</template>
<script>
export default {
name: "MessageErrorHandler",
data() {
return {
err: false,
};
},
errorCaptured(err, ignoredvm, ignoredinfo) {
this.err = err;
return false;
}
};
</script>
<style lang="scss" scoped>
</style>

View file

@ -27,7 +27,7 @@ export default {
components: { MessageIncoming },
data() {
return {
src: null,
src: undefined,
cover: true,
contain: false,
dialog: false

View file

@ -27,7 +27,7 @@ export default {
components: { MessageOutgoing },
data() {
return {
src: null,
src: undefined,
cover: true,
contain: false,
dialog: false