From 59a7006ccbdc10c1d55dc7f6895fbca1fbe7cec5 Mon Sep 17 00:00:00 2001 From: MaxNumerique Date: Thu, 23 Apr 2026 14:22:28 +0200 Subject: [PATCH 01/10] v0 --- .../Viewer/ObjectTree/Views/GlobalObjects.vue | 7 ++- .../ObjectTree/Views/ModelComponents.vue | 31 ++++++++++--- app/composables/use_tree_filter.js | 43 ++++++++++++++++--- 3 files changed, 67 insertions(+), 14 deletions(-) diff --git a/app/components/Viewer/ObjectTree/Views/GlobalObjects.vue b/app/components/Viewer/ObjectTree/Views/GlobalObjects.vue index 7d96057a..8e51f0ca 100644 --- a/app/components/Viewer/ObjectTree/Views/GlobalObjects.vue +++ b/app/components/Viewer/ObjectTree/Views/GlobalObjects.vue @@ -24,13 +24,18 @@ const { sortType, filterOptions, processedItems, + filteredIds, availableFilterOptions, toggleSort, customFilter, } = useTreeFilter(toRef(() => treeviewStore.items)); +const actuallyVisibleIds = computed(() => { + return treeviewStore.selection.filter((id) => filteredIds.value.has(id)); +}); + watch( - () => treeviewStore.selection, + actuallyVisibleIds, async (current, previous) => { const oldSelection = previous || []; if (current === oldSelection) { diff --git a/app/components/Viewer/ObjectTree/Views/ModelComponents.vue b/app/components/Viewer/ObjectTree/Views/ModelComponents.vue index 26b15711..761c897a 100644 --- a/app/components/Viewer/ObjectTree/Views/ModelComponents.vue +++ b/app/components/Viewer/ObjectTree/Views/ModelComponents.vue @@ -24,26 +24,41 @@ const opened = computed({ }); const items = dataStore.refFormatedMeshComponents(viewId); -const mesh_components_selection = dataStyleStore.visibleMeshComponents(viewId); +const localSelection = ref([]); + +watch( + mesh_components_selection, + (val) => { + if (val) { + localSelection.value = [...val]; + } + }, + { immediate: true }, +); const { search, sortType, filterOptions, processedItems, + filteredIds, availableFilterOptions, toggleSort, customFilter, } = useTreeFilter(items); -async function onSelectionChange(current) { - const previous = mesh_components_selection.value; - const { added, removed } = compareSelections(current, previous); +const actuallyVisibleIds = computed(() => { + return localSelection.value.filter((id) => filteredIds.value.has(id)); +}); - if (added.length === 0 && removed.length === 0) { +watch(actuallyVisibleIds, async (current, previous) => { + const oldSelection = previous || []; + if (current === oldSelection) { return; } + const { added, removed } = compareSelections(current, previous); + if (added.length > 0) { await dataStyleStore.setModelComponentsVisibility(viewId, added, true); } @@ -51,6 +66,10 @@ async function onSelectionChange(current) { await dataStyleStore.setModelComponentsVisibility(viewId, removed, false); } hybridViewerStore.remoteRender(); +}); + +function onSelectionChange(current) { + localSelection.value = current; } function showContextMenu(event, item) { @@ -79,7 +98,7 @@ function showContextMenu(event, item) { { - const key = category.title || category.id; - return filterOptions.value[key] !== false; - }), - sortType.value, - ); + const filteredByType = rawItems.value.filter((category) => { + const key = category.title || category.id; + return filterOptions.value[key] !== false; + }); + + const query = search.value.toLowerCase(); + const filteredBySearch = filteredByType + .map((category) => { + const children = (category.children || []).filter((item) => { + if (!query) { + return true; + } + const title = (item.title || "").toLowerCase(); + const idValue = String(item.id || "").toLowerCase(); + return title.includes(query) || idValue.includes(query); + }); + return { ...category, children }; + }) + .filter((category) => category.children.length > 0); + + return sortAndFormatItems(filteredBySearch, sortType.value); + }); + + const filteredIds = computed(() => { + const ids = new Set(); + const traverse = (items) => { + for (const item of items) { + ids.add(item.id); + if (item.children) { + traverse(item.children); + } + } + }; + traverse(processedItems.value); + return ids; }); function toggleSort() { @@ -74,6 +102,7 @@ function useTreeFilter(rawItems, options = {}) { sortType, filterOptions, processedItems, + filteredIds, availableFilterOptions, toggleSort, customFilter, From faca10224393e8378fc5abf5f7f20c0c1ab43bbf Mon Sep 17 00:00:00 2001 From: MaxNumerique Date: Thu, 23 Apr 2026 16:45:15 +0200 Subject: [PATCH 02/10] revert --- .../Viewer/ObjectTree/Views/GlobalObjects.vue | 34 +++++++++--- .../ObjectTree/Views/ModelComponents.vue | 47 ++++++++-------- app/composables/use_tree_filter.js | 53 +++++++++---------- 3 files changed, 74 insertions(+), 60 deletions(-) diff --git a/app/components/Viewer/ObjectTree/Views/GlobalObjects.vue b/app/components/Viewer/ObjectTree/Views/GlobalObjects.vue index 8e51f0ca..e281ade9 100644 --- a/app/components/Viewer/ObjectTree/Views/GlobalObjects.vue +++ b/app/components/Viewer/ObjectTree/Views/GlobalObjects.vue @@ -24,18 +24,23 @@ const { sortType, filterOptions, processedItems, - filteredIds, + processedItemIds, + filteredItemIds, availableFilterOptions, toggleSort, customFilter, } = useTreeFilter(toRef(() => treeviewStore.items)); -const actuallyVisibleIds = computed(() => { - return treeviewStore.selection.filter((id) => filteredIds.value.has(id)); +const treeviewSelection = computed({ + get: () => treeviewStore.selection.filter((id) => processedItemIds.value.has(id)), + set: (newVal) => { + const hiddenSelected = treeviewStore.selection.filter((id) => !processedItemIds.value.has(id)); + treeviewStore.selection = [...newVal, ...hiddenSelected]; + }, }); watch( - actuallyVisibleIds, + () => treeviewStore.selection, async (current, previous) => { const oldSelection = previous || []; if (current === oldSelection) { @@ -50,7 +55,7 @@ watch( const updates = [ ...added - .filter((id) => allObjectIds.has(id)) + .filter((id) => allObjectIds.has(id) && filteredItemIds.value.has(id)) .map((id) => dataStyleStore.setVisibility(id, true)), ...removed .filter((id) => allObjectIds.has(id)) @@ -61,6 +66,23 @@ watch( }, ); +watch(filteredItemIds, async (newFiltered, oldFiltered) => { + const prev = oldFiltered ?? new Set(); + const selectionSet = new Set(treeviewStore.selection); + + const toHide = [...prev].filter((id) => !newFiltered.has(id) && selectionSet.has(id)); + const toShow = [...newFiltered].filter((id) => !prev.has(id) && selectionSet.has(id)); + + if (toHide.length === 0 && toShow.length === 0) { + return; + } + await Promise.all([ + ...toHide.map((id) => dataStyleStore.setVisibility(id, false)), + ...toShow.map((id) => dataStyleStore.setVisibility(id, true)), + ]); + hybridViewerStore.remoteRender(); +}); + function isModel(item) { const actualItem = item.raw || item; return ( @@ -80,7 +102,7 @@ function isModel(item) { /> { - if (val) { - localSelection.value = [...val]; - } - }, - { immediate: true }, -); +const mesh_components_selection = dataStyleStore.visibleMeshComponents(viewId); const { search, sortType, filterOptions, processedItems, - filteredIds, availableFilterOptions, toggleSort, customFilter, } = useTreeFilter(items); -const actuallyVisibleIds = computed(() => { - return localSelection.value.filter((id) => filteredIds.value.has(id)); -}); +async function onSelectionChange(current) { + const previous = mesh_components_selection.value; + const { added, removed } = compareSelections(current, previous); -watch(actuallyVisibleIds, async (current, previous) => { - const oldSelection = previous || []; - if (current === oldSelection) { + if (added.length === 0 && removed.length === 0) { return; } - const { added, removed } = compareSelections(current, previous); - if (added.length > 0) { await dataStyleStore.setModelComponentsVisibility(viewId, added, true); } @@ -66,10 +53,6 @@ watch(actuallyVisibleIds, async (current, previous) => { await dataStyleStore.setModelComponentsVisibility(viewId, removed, false); } hybridViewerStore.remoteRender(); -}); - -function onSelectionChange(current) { - localSelection.value = current; } function showContextMenu(event, item) { @@ -82,6 +65,18 @@ function showContextMenu(event, item) { modelComponentType: actualItem.category ? undefined : actualItem.id, }); } + +function handleHoverEnter(item) { + const actualItem = item.raw || item; + const block_ids = actualItem.category + ? [actualItem.viewer_id] + : actualItem.children?.map((child) => child.viewer_id) || []; + onHoverEnter(viewId, block_ids); +} + +function handleHoverLeave() { + onHoverLeave(viewId); +} diff --git a/app/composables/use_tree_filter.js b/app/composables/use_tree_filter.js index d9132a06..ec96899c 100644 --- a/app/composables/use_tree_filter.js +++ b/app/composables/use_tree_filter.js @@ -56,40 +56,34 @@ function useTreeFilter(rawItems, options = {}) { if (!rawItems.value) { return []; } - const filteredByType = rawItems.value.filter((category) => { - const key = category.title || category.id; - return filterOptions.value[key] !== false; - }); - - const query = search.value.toLowerCase(); - const filteredBySearch = filteredByType - .map((category) => { - const children = (category.children || []).filter((item) => { - if (!query) { - return true; - } - const title = (item.title || "").toLowerCase(); - const idValue = String(item.id || "").toLowerCase(); - return title.includes(query) || idValue.includes(query); - }); - return { ...category, children }; - }) - .filter((category) => category.children.length > 0); + return sortAndFormatItems( + rawItems.value.filter((category) => { + const key = category.title || category.id; + return filterOptions.value[key] !== false; + }), + sortType.value, + ); + }); - return sortAndFormatItems(filteredBySearch, sortType.value); + const processedItemIds = computed(() => { + const ids = new Set(); + for (const category of processedItems.value) { + for (const child of category.children || []) { + ids.add(child.id); + } + } + return ids; }); - const filteredIds = computed(() => { + const filteredItemIds = computed(() => { const ids = new Set(); - const traverse = (items) => { - for (const item of items) { - ids.add(item.id); - if (item.children) { - traverse(item.children); + for (const category of processedItems.value) { + for (const child of category.children || []) { + if (!search.value || customFilter(child.id, search.value, { raw: child })) { + ids.add(child.id); } } - }; - traverse(processedItems.value); + } return ids; }); @@ -102,7 +96,8 @@ function useTreeFilter(rawItems, options = {}) { sortType, filterOptions, processedItems, - filteredIds, + processedItemIds, + filteredItemIds, availableFilterOptions, toggleSort, customFilter, From c70a7669b149c58a10025c8af144389a92f4e3f1 Mon Sep 17 00:00:00 2001 From: MaxNumerique Date: Thu, 23 Apr 2026 16:54:10 +0200 Subject: [PATCH 03/10] revert --- .../Viewer/ObjectTree/Views/GlobalObjects.vue | 31 +--------- .../ObjectTree/Views/ModelComponents.vue | 60 +++++++++++++++++-- app/composables/use_tree_filter.js | 24 -------- 3 files changed, 58 insertions(+), 57 deletions(-) diff --git a/app/components/Viewer/ObjectTree/Views/GlobalObjects.vue b/app/components/Viewer/ObjectTree/Views/GlobalObjects.vue index 48f16982..47748688 100644 --- a/app/components/Viewer/ObjectTree/Views/GlobalObjects.vue +++ b/app/components/Viewer/ObjectTree/Views/GlobalObjects.vue @@ -28,21 +28,11 @@ const { sortType, filterOptions, processedItems, - processedItemIds, - filteredItemIds, availableFilterOptions, toggleSort, customFilter, } = useTreeFilter(toRef(() => treeviewStore.items)); -const treeviewSelection = computed({ - get: () => treeviewStore.selection.filter((id) => processedItemIds.value.has(id)), - set: (newVal) => { - const hiddenSelected = treeviewStore.selection.filter((id) => !processedItemIds.value.has(id)); - treeviewStore.selection = [...newVal, ...hiddenSelected]; - }, -}); - watch( () => treeviewStore.selection, async (current, previous) => { @@ -59,7 +49,7 @@ watch( const updates = [ ...added - .filter((id) => allObjectIds.has(id) && filteredItemIds.value.has(id)) + .filter((id) => allObjectIds.has(id)) .map((id) => dataStyleStore.setVisibility(id, true)), ...removed .filter((id) => allObjectIds.has(id)) @@ -70,23 +60,6 @@ watch( }, ); -watch(filteredItemIds, async (newFiltered, oldFiltered) => { - const prev = oldFiltered ?? new Set(); - const selectionSet = new Set(treeviewStore.selection); - - const toHide = [...prev].filter((id) => !newFiltered.has(id) && selectionSet.has(id)); - const toShow = [...newFiltered].filter((id) => !prev.has(id) && selectionSet.has(id)); - - if (toHide.length === 0 && toShow.length === 0) { - return; - } - await Promise.all([ - ...toHide.map((id) => dataStyleStore.setVisibility(id, false)), - ...toShow.map((id) => dataStyleStore.setVisibility(id, true)), - ]); - hybridViewerStore.remoteRender(); -}); - function isModel(item) { const actualItem = item.raw || item; return ( @@ -121,7 +94,7 @@ function handleHoverLeave(item) { /> filteredItemIds.value.has(id))); + const newValSet = new Set(newVal.filter((id) => filteredItemIds.value.has(id))); + + const added = [...newValSet].filter((id) => !prevVisibleSet.has(id)); + const removed = [...prevVisibleSet].filter((id) => !newValSet.has(id)); if (added.length === 0 && removed.length === 0) { return; @@ -55,6 +61,53 @@ async function onSelectionChange(current) { hybridViewerStore.remoteRender(); } +const filterHiddenCache = new Map(); + +watch(filteredItemIds, async (newFiltered, oldFiltered) => { + const prev = oldFiltered ?? new Set(); + const selectionSet = new Set(mesh_components_selection.value); + + const nowHidden = [...prev].filter((id) => !newFiltered.has(id)); + const nowShown = [...newFiltered].filter((id) => !prev.has(id)); + + if (nowHidden.length === 0 && nowShown.length === 0) { + return; + } + + for (const id of nowHidden) { + filterHiddenCache.set(id, selectionSet.has(id)); + } + + const toShow = []; + const toHide = []; + for (const id of nowShown) { + const wasVisible = filterHiddenCache.has(id) ? filterHiddenCache.get(id) : selectionSet.has(id); + filterHiddenCache.delete(id); + if (wasVisible) { + toShow.push(id); + } else { + toHide.push(id); + } + } + + const actualToHide = nowHidden.filter((id) => selectionSet.has(id)); + + const promises = []; + if (actualToHide.length > 0) { + promises.push(dataStyleStore.setModelComponentsVisibility(viewId, actualToHide, false)); + } + if (toShow.length > 0) { + promises.push(dataStyleStore.setModelComponentsVisibility(viewId, toShow, true)); + } + if (toHide.length > 0) { + promises.push(dataStyleStore.setModelComponentsVisibility(viewId, toHide, false)); + } + if (promises.length > 0) { + await Promise.all(promises); + hybridViewerStore.remoteRender(); + } +}); + function showContextMenu(event, item) { const actualItem = item.raw || item; emit("show-menu", { @@ -100,9 +153,8 @@ function handleHoverLeave() { :custom-filter="customFilter" class="transparent-treeview" item-value="id" - select-strategy="independent" + select-strategy="classic" selectable - items-registration="props" @update:selected="onSelectionChange" >