From e22ec7b4eecd2d77a90010b7ff660434e42f3a81 Mon Sep 17 00:00:00 2001 From: andrewwallacespeckle Date: Tue, 16 Sep 2025 10:57:16 +0100 Subject: [PATCH 1/3] fix(fe): resolve infinite reactivity loops in models panel --- .../components/viewer/models/Panel.vue | 22 ++++++++++++++----- .../frontend-2/lib/viewer/composables/tree.ts | 20 +++++++++++------ 2 files changed, 29 insertions(+), 13 deletions(-) diff --git a/packages/frontend-2/components/viewer/models/Panel.vue b/packages/frontend-2/components/viewer/models/Panel.vue index d247047db..34898b1c0 100644 --- a/packages/frontend-2/components/viewer/models/Panel.vue +++ b/packages/frontend-2/components/viewer/models/Panel.vue @@ -250,17 +250,21 @@ const handleDiffClose = async () => { const toggleModelExpansion = (modelId: string) => { if (expandedModels.value.has(modelId)) { - expandedModels.value.delete(modelId) + expandedModels.value = new Set( + [...expandedModels.value].filter((id) => id !== modelId) + ) } else { - expandedModels.value.add(modelId) + expandedModels.value = new Set([...expandedModels.value, modelId]) } } const toggleTreeItemExpansion = (itemId: string) => { if (expandedNodes.value.has(itemId)) { - expandedNodes.value.delete(itemId) + expandedNodes.value = new Set( + [...expandedNodes.value].filter((id) => id !== itemId) + ) } else { - expandedNodes.value.add(itemId) + expandedNodes.value = new Set([...expandedNodes.value, itemId]) } } @@ -351,13 +355,19 @@ const handleSelectionChange = useDebounceFn( const containsObject = findObjectInNodes(modelRootNodes, selectedObj.id) if (containsObject) { - expandedModels.value.add(model.id) - expandNodesToShowObject( + expandedModels.value = new Set([...expandedModels.value, model.id]) + const result = expandNodesToShowObject( modelRootNodes, selectedObj.id, model.id, expandedNodes.value ) + if (result.found && result.nodesToExpand.length > 0) { + expandedNodes.value = new Set([ + ...expandedNodes.value, + ...result.nodesToExpand + ]) + } scrollToSelectedItem(selectedObj.id) break diff --git a/packages/frontend-2/lib/viewer/composables/tree.ts b/packages/frontend-2/lib/viewer/composables/tree.ts index aa02f8d0a..dbc1cff19 100644 --- a/packages/frontend-2/lib/viewer/composables/tree.ts +++ b/packages/frontend-2/lib/viewer/composables/tree.ts @@ -419,22 +419,26 @@ export function useTreeManagement() { modelId: string, expandedNodes: Set, depth = 0 - ): boolean => { - if (!nodes?.length || depth > MAX_EXPANSION_DEPTH) return false + ): { found: boolean; nodesToExpand: string[] } => { + if (!nodes?.length || depth > MAX_EXPANSION_DEPTH) + return { found: false, nodesToExpand: [] } - return nodes.some((node) => { + const nodesToExpand: string[] = [] + + const found = nodes.some((node) => { if (node.raw?.id === objectId) return true if (node.children?.length) { - const found = expandNodesToShowObject( + const result = expandNodesToShowObject( node.children, objectId, modelId, expandedNodes, depth + 1 ) - if (found) { - if (node.raw?.id) expandedNodes.add(node.raw.id) + if (result.found) { + if (node.raw?.id) nodesToExpand.push(node.raw.id) + nodesToExpand.push(...result.nodesToExpand) // Handle array collections const speckleData = node.raw @@ -449,7 +453,7 @@ export function useTreeManagement() { if (isReferencedIdArray(val)) { const ids = new Set(val.map((ref) => ref.referencedId)) if (node.children?.some((child) => ids.has(child.raw?.id as string))) { - expandedNodes.add(k) + nodesToExpand.push(k) } } }) @@ -459,6 +463,8 @@ export function useTreeManagement() { } return false }) + + return { found, nodesToExpand } } return { From 6731817f8e3b764776bbe8abeb0591e3f510900d Mon Sep 17 00:00:00 2001 From: andrewwallacespeckle Date: Tue, 16 Sep 2025 11:09:18 +0100 Subject: [PATCH 2/3] shallowRef and triggerRef --- .../components/viewer/models/Panel.vue | 28 +++++++++---------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/packages/frontend-2/components/viewer/models/Panel.vue b/packages/frontend-2/components/viewer/models/Panel.vue index 34898b1c0..2506e396a 100644 --- a/packages/frontend-2/components/viewer/models/Panel.vue +++ b/packages/frontend-2/components/viewer/models/Panel.vue @@ -135,8 +135,8 @@ const expandedModelId = ref(null) const showAddModel = ref(false) -const expandedNodes = ref>(new Set()) -const expandedModels = ref>(new Set()) +const expandedNodes = shallowRef>(new Set()) +const expandedModels = shallowRef>(new Set()) const disableScrollOnNextSelection = ref(false) const stickyHeader = ref<{ model: ModelItem; versionId: string } | null>(null) @@ -250,22 +250,20 @@ const handleDiffClose = async () => { const toggleModelExpansion = (modelId: string) => { if (expandedModels.value.has(modelId)) { - expandedModels.value = new Set( - [...expandedModels.value].filter((id) => id !== modelId) - ) + expandedModels.value.delete(modelId) } else { - expandedModels.value = new Set([...expandedModels.value, modelId]) + expandedModels.value.add(modelId) } + triggerRef(expandedModels) } const toggleTreeItemExpansion = (itemId: string) => { if (expandedNodes.value.has(itemId)) { - expandedNodes.value = new Set( - [...expandedNodes.value].filter((id) => id !== itemId) - ) + expandedNodes.value.delete(itemId) } else { - expandedNodes.value = new Set([...expandedNodes.value, itemId]) + expandedNodes.value.add(itemId) } + triggerRef(expandedNodes) } const handleItemClick = ( @@ -355,7 +353,9 @@ const handleSelectionChange = useDebounceFn( const containsObject = findObjectInNodes(modelRootNodes, selectedObj.id) if (containsObject) { - expandedModels.value = new Set([...expandedModels.value, model.id]) + expandedModels.value.add(model.id) + triggerRef(expandedModels) + const result = expandNodesToShowObject( modelRootNodes, selectedObj.id, @@ -363,10 +363,8 @@ const handleSelectionChange = useDebounceFn( expandedNodes.value ) if (result.found && result.nodesToExpand.length > 0) { - expandedNodes.value = new Set([ - ...expandedNodes.value, - ...result.nodesToExpand - ]) + result.nodesToExpand.forEach((nodeId) => expandedNodes.value.add(nodeId)) + triggerRef(expandedNodes) } scrollToSelectedItem(selectedObj.id) From 0cd36b6923cc9bc17fb2e327432e3f5d6068e907 Mon Sep 17 00:00:00 2001 From: andrewwallacespeckle Date: Wed, 17 Sep 2025 13:01:19 +0100 Subject: [PATCH 3/3] Undo ref > shallowref --- packages/frontend-2/components/viewer/models/Panel.vue | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/frontend-2/components/viewer/models/Panel.vue b/packages/frontend-2/components/viewer/models/Panel.vue index 2506e396a..1c31856a3 100644 --- a/packages/frontend-2/components/viewer/models/Panel.vue +++ b/packages/frontend-2/components/viewer/models/Panel.vue @@ -135,8 +135,8 @@ const expandedModelId = ref(null) const showAddModel = ref(false) -const expandedNodes = shallowRef>(new Set()) -const expandedModels = shallowRef>(new Set()) +const expandedNodes = ref>(new Set()) +const expandedModels = ref>(new Set()) const disableScrollOnNextSelection = ref(false) const stickyHeader = ref<{ model: ModelItem; versionId: string } | null>(null) @@ -254,7 +254,6 @@ const toggleModelExpansion = (modelId: string) => { } else { expandedModels.value.add(modelId) } - triggerRef(expandedModels) } const toggleTreeItemExpansion = (itemId: string) => { @@ -263,7 +262,6 @@ const toggleTreeItemExpansion = (itemId: string) => { } else { expandedNodes.value.add(itemId) } - triggerRef(expandedNodes) } const handleItemClick = ( @@ -354,7 +352,6 @@ const handleSelectionChange = useDebounceFn( if (containsObject) { expandedModels.value.add(model.id) - triggerRef(expandedModels) const result = expandNodesToShowObject( modelRootNodes, @@ -364,7 +361,6 @@ const handleSelectionChange = useDebounceFn( ) if (result.found && result.nodesToExpand.length > 0) { result.nodesToExpand.forEach((nodeId) => expandedNodes.value.add(nodeId)) - triggerRef(expandedNodes) } scrollToSelectedItem(selectedObj.id)