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 <opensource@fthiessen.de>
This commit is contained in:
Ferdinand Thiessen 2026-06-02 20:54:34 +02:00
parent e01a54c53a
commit 9fdd019ed3
No known key found for this signature in database
GPG key ID: 7E849AE05218500F
3 changed files with 45 additions and 22 deletions

View file

@ -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<string>()
<template #icon>
<NcIconSvgWrapper :svg="tab.iconSvgInline" />
</template>
<NcEmptyContent v-if="loading">
<NcEmptyContent v-if="loading || !context">
<template #icon>
<NcLoadingIcon />
</template>
@ -75,8 +84,8 @@ const initializedTabs = new Set<string>()
: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" />
</NcAppSidebarTab>
</template>

View file

@ -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])
}
/**

View file

@ -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<Version | null>(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
*