From de07d577925b415125df46df89e0a2be4161062a Mon Sep 17 00:00:00 2001 From: Oliver Wright Date: Sun, 29 Dec 2024 23:15:13 +0000 Subject: [PATCH] FIX: Prevent iframe reloads where possible By using randomly generated ids, and maintaining them when changing layouts we can reduce DOM movement and prevent iframes being force-reloaded as much as possible. --- .../multiview/MultiviewLayoutMixin.ts | 16 ++-- src/store/multiview.module.ts | 6 +- src/utils/mv-utils.ts | 7 +- src/views/MultiView.vue | 73 ++++++++++--------- 4 files changed, 58 insertions(+), 44 deletions(-) diff --git a/src/components/multiview/MultiviewLayoutMixin.ts b/src/components/multiview/MultiviewLayoutMixin.ts index dc8a3c1b4..16c67e6f2 100644 --- a/src/components/multiview/MultiviewLayoutMixin.ts +++ b/src/components/multiview/MultiviewLayoutMixin.ts @@ -1,5 +1,5 @@ import type { Content } from "@/utils/mv-utils"; -import { decodeLayout, sortLayout } from "@/utils/mv-utils"; +import { decodeLayout, generateContentId, sortLayout } from "@/utils/mv-utils"; import { mapGetters } from "vuex"; export default { @@ -178,18 +178,24 @@ export default { if (mergeContent) { const contentsToMerge = {}; let videoIndex = 0; - const currentVideos = Object.values(this.layoutContent as Content[]).filter((o) => o.type === "video"); + // Maintain the original layout ordering when chosing new videos. + const currentVideoContents = this.layout.filter(({ i }) => this.layoutContent[i]?.type === "video"); const newVideoIdToIndex = {}; // Loop through the incoming layout, and fill with current content layout.filter((item) => !content[item.i]).forEach((item) => { // For empty cells fill until there's no more current videos if (videoIndex < this.activeVideos.length) { // get next video to fill this item's cell - const key = item.i; - contentsToMerge[key] = currentVideos[videoIndex]; + const key = currentVideoContents[videoIndex].i; + // Re-use the original video's index so that the component is not re-mounted. + item.i = key; + contentsToMerge[key] = this.layoutContent[key]; // Infer next activeVideos to be used in auto chat tabbing below - newVideoIdToIndex[currentVideos[videoIndex].video.id] = videoIndex; + newVideoIdToIndex[this.layoutContent[key].video.id] = videoIndex; videoIndex += 1; + } else { + // Create a new id because the templates are cached with re-used ids that would otherwise cause issues. + item.i = generateContentId(); } }); diff --git a/src/store/multiview.module.ts b/src/store/multiview.module.ts index 7781f3346..648831130 100644 --- a/src/store/multiview.module.ts +++ b/src/store/multiview.module.ts @@ -3,6 +3,7 @@ import type { LayoutItem } from "@/external/vue-grid-layout/src/helpers/utils"; import { getFirstCollision } from "@/external/vue-grid-layout/src/helpers/utils"; import { getDesktopDefaults, desktopPresets, mobilePresets, decodeLayout, + generateContentId, } from "@/utils/mv-utils"; import type { Content } from "@/utils/mv-utils"; import api from "@/utils/backend-api"; @@ -128,7 +129,6 @@ const mutations = { }, addLayoutItem(state) { // Increment the counter to ensure key is always unique. - state.index = new Date().getTime(); let newLayoutItem: LayoutItem; // try to find a good location for it: @@ -140,7 +140,7 @@ const mutations = { y, w: 4, h: 6, - i: state.index, + i: generateContentId(), isResizable: true, isDraggable: true, }; @@ -158,7 +158,7 @@ const mutations = { y: 24, // puts it at the bottom w: 4, h: 6, - i: state.index, + i: generateContentId(), isResizable: true, isDraggable: true, }; diff --git a/src/utils/mv-utils.ts b/src/utils/mv-utils.ts index 22e2a7b27..034453940 100644 --- a/src/utils/mv-utils.ts +++ b/src/utils/mv-utils.ts @@ -13,6 +13,10 @@ const b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_."; export const sortLayout = (a, b) => a.x - b.x || a.y - b.y; // export const sortLayout = (a, b) => a.y - b.y || a.x - b.x; +export const generateContentId = () => Array.from({ length: 8 }) + .map(() => b64[Math.floor(Math.random() * b64.length)]) + .join(""); + /** * Encodes a layout array and contents to a compact URI * @param {{layout, contents, includeVideo?}} layout and layout contents @@ -68,7 +72,8 @@ export function decodeLayout(encodedStr) { let videoCellCount = 0; const parts = encodedStr.split(","); parts.sort(); // DO NOT TOUCH THIS LINE - parts.forEach((str, index) => { + parts.forEach((str) => { + const index = generateContentId(); const xywh = str.substring(0, 4); const idOrChat = str.substring(4, 15); const isChat = idOrChat.substring(0, 4) === "chat"; diff --git a/src/views/MultiView.vue b/src/views/MultiView.vue index 56736e556..7473c4278 100644 --- a/src/views/MultiView.vue +++ b/src/views/MultiView.vue @@ -81,41 +81,44 @@ :margin="[1, 1]" @layout-updated="onLayoutUpdated" > - - - - - - - + + + + + + + + + +