From 9fdd019ed399834f5b7585e17abafe63cc873262 Mon Sep 17 00:00:00 2001 From: Ferdinand Thiessen Date: Tue, 2 Jun 2026 20:54:34 +0200 Subject: [PATCH] fix(files): make sure nested changes are propagated to sidebar tabs If an object is passed from one app to another app through a web component, then only reference changes (new objects) are marked as reactive changes in the receiving app. In this case e.g. `mtime` changes were not properly propagated, so to fix this we explicitly clone the objects before passing to the sidebar tab. Signed-off-by: Ferdinand Thiessen --- .../FilesSidebar/FilesSidebarTab.vue | 23 +++++++++---- apps/files/src/store/files.ts | 10 +++++- .../src/views/FilesVersionsSidebarTab.vue | 34 +++++++++++-------- 3 files changed, 45 insertions(+), 22 deletions(-) diff --git a/apps/files/src/components/FilesSidebar/FilesSidebarTab.vue b/apps/files/src/components/FilesSidebar/FilesSidebarTab.vue index 57865449283..23114276b09 100644 --- a/apps/files/src/components/FilesSidebar/FilesSidebarTab.vue +++ b/apps/files/src/components/FilesSidebar/FilesSidebarTab.vue @@ -7,10 +7,9 @@ import type { ISidebarTab } from '@nextcloud/files' import { NcIconSvgWrapper, NcLoadingIcon } from '@nextcloud/vue' -import { ref, toRef, watch } from 'vue' +import { computed, ref, toRef, watch } from 'vue' import NcAppSidebarTab from '@nextcloud/vue/components/NcAppSidebarTab' import NcEmptyContent from '@nextcloud/vue/components/NcEmptyContent' -import { useActiveStore } from '../../store/active.ts' import { useSidebarStore } from '../../store/sidebar.ts' import { logger } from '../../utils/logger.ts' @@ -27,7 +26,17 @@ const props = defineProps<{ }>() const sidebar = useSidebarStore() -const activeStore = useActiveStore() + +const context = computed(() => { + if (!sidebar.currentContext) { + return undefined + } + return { + folder: sidebar.currentContext.folder.clone(), + node: sidebar.currentContext.node.clone(), + view: sidebar.currentContext.view, + } +}) const loading = ref(true) watch(toRef(props, 'active'), async (active) => { @@ -65,7 +74,7 @@ const initializedTabs = new Set() - + @@ -75,8 +84,8 @@ const initializedTabs = new Set() :is="tab.tagName" v-else :active.prop="active" - :node.prop="sidebar.currentNode" - :folder.prop="activeStore.activeFolder" - :view.prop="activeStore.activeView" /> + :node.prop="context.node" + :folder.prop="context.folder" + :view.prop="context.view" /> diff --git a/apps/files/src/store/files.ts b/apps/files/src/store/files.ts index 90dffe19a21..6ea456fb6bd 100644 --- a/apps/files/src/store/files.ts +++ b/apps/files/src/store/files.ts @@ -11,6 +11,7 @@ import { defineStore } from 'pinia' import Vue, { ref } from 'vue' import { fetchNode } from '../services/WebdavClient.ts' import { logger } from '../utils/logger.ts' +import { useActiveStore } from './active.ts' import { usePathsStore } from './paths.ts' /** @@ -124,6 +125,12 @@ export const useFilesStore = defineStore('files', () => { }, {} as FilesStore) files.value = { ...files.value, ...newNodes } + + // handle updating the active node + const activeStore = useActiveStore() + if (activeStore.activeNode && activeStore.activeNode.source in newNodes) { + activeStore.activeNode = files.value[activeStore.activeNode.source] + } } /** @@ -232,7 +239,8 @@ export const useFilesStore = defineStore('files', () => { } // Otherwise, it means we receive an event for a node that is not in the store - fetchNode(node.path).then((n) => updateNodes([n])) + const newNode = await fetchNode(node.path) + updateNodes([newNode]) } /** diff --git a/apps/files_versions/src/views/FilesVersionsSidebarTab.vue b/apps/files_versions/src/views/FilesVersionsSidebarTab.vue index 1337dbc1b93..b35fe611877 100644 --- a/apps/files_versions/src/views/FilesVersionsSidebarTab.vue +++ b/apps/files_versions/src/views/FilesVersionsSidebarTab.vue @@ -48,7 +48,8 @@ import { showError, showSuccess } from '@nextcloud/dialogs' import { emit } from '@nextcloud/event-bus' import { t } from '@nextcloud/l10n' import { useIsMobile } from '@nextcloud/vue/composables/useIsMobile' -import { computed, ref, toRef, watch } from 'vue' +import { watchDebounced } from '@vueuse/core' +import { computed, ref, watch } from 'vue' import NcLoadingIcon from '@nextcloud/vue/components/NcLoadingIcon' import VersionEntry from '../components/VersionEntry.vue' import VersionLabelDialog from '../components/VersionLabelDialog.vue' @@ -72,19 +73,6 @@ const loading = ref(false) const showVersionLabelForm = ref(false) const editedVersion = ref(null) -watch(toRef(() => props.node), async () => { - if (!props.node) { - return - } - - try { - loading.value = true - versions.value = await fetchVersions(props.node) - } finally { - loading.value = false - } -}, { immediate: true }) - const currentVersionMtime = computed(() => props.node?.mtime?.getTime() ?? 0) /** @@ -139,6 +127,24 @@ const canCompare = computed(() => { && window.OCA.Viewer?.mimetypesCompare?.includes(props.node?.mime) }) +// When either the current node to show or its mtime changes we need to refetch the versions +// When the id changed we immediately show changes +watch(() => props.node.id, loadVersions, { immediate: true }) +// On mtime changes we debounce to prevent too many requests. +watchDebounced(currentVersionMtime, loadVersions, { debounce: 600 }) + +/** + * Load versions for the current node + */ +async function loadVersions() { + try { + loading.value = true + versions.value = await fetchVersions(props.node) + } finally { + loading.value = false + } +} + /** * Handle restored event from Version.vue *