diff --git a/packages/frontend-2/components/viewer/filters/filter/Header.vue b/packages/frontend-2/components/viewer/filters/filter/Header.vue index 5e2bca8a5..429206e46 100644 --- a/packages/frontend-2/components/viewer/filters/filter/Header.vue +++ b/packages/frontend-2/components/viewer/filters/filter/Header.vue @@ -95,10 +95,6 @@ import { useFilterUtilities } from '~/lib/viewer/composables/filtering/filtering import { useFilterColoringHelpers } from '~/lib/viewer/composables/filtering/coloringHelpers' import type { FilterData } from '~/lib/viewer/helpers/filters/types' import { FilterType } from '~/lib/viewer/helpers/filters/types' -import { - useHighlightedObjectsUtilities, - useSelectionUtilities -} from '~/lib/viewer/composables/ui' const props = defineProps<{ filter: FilterData @@ -112,8 +108,6 @@ const { removeActiveFilter, toggleFilterApplied, getPropertyName, filters } = useFilterUtilities() const { toggleColorFilter } = useFilterColoringHelpers() -const { clearHighlightedObjects } = useHighlightedObjectsUtilities() -const { clearSelection } = useSelectionUtilities() const emit = defineEmits<{ swapProperty: [filterId: string] @@ -124,8 +118,6 @@ const isColoringActive = computed(() => { }) const removeFilter = () => { - clearHighlightedObjects() - clearSelection() removeActiveFilter(props.filter.id) } @@ -134,8 +126,6 @@ const toggleVisibility = () => { } const toggleColors = () => { - clearHighlightedObjects() - clearSelection() toggleColorFilter(props.filter.id) } diff --git a/packages/frontend-2/components/viewer/models/Card.vue b/packages/frontend-2/components/viewer/models/Card.vue index a901f7917..186739e63 100644 --- a/packages/frontend-2/components/viewer/models/Card.vue +++ b/packages/frontend-2/components/viewer/models/Card.vue @@ -140,7 +140,7 @@ const { hideObjects, showObjects, isolateObjects, unIsolateObjects } = const { zoom } = useCameraUtilities() const { items } = useInjectedViewerRequestedResources() const { resourceItems } = useInjectedViewerLoadedResources() -const { addToSelectionFromObjectIds, clearSelection } = useSelectionUtilities() +const { addToSelectionFromObjectIds } = useSelectionUtilities() const { viewer: { @@ -313,7 +313,6 @@ const handleClick = () => { if (!props.isExpanded) { emit('toggle-expansion') } else { - clearSelection() addToSelectionFromObjectIds(modelObjectIds.value) } } diff --git a/packages/frontend-2/lib/viewer/composables/setup/filters.ts b/packages/frontend-2/lib/viewer/composables/setup/filters.ts index 66bfdab7a..cb6b8e5f7 100644 --- a/packages/frontend-2/lib/viewer/composables/setup/filters.ts +++ b/packages/frontend-2/lib/viewer/composables/setup/filters.ts @@ -1,11 +1,12 @@ import type { FilterData } from '~/lib/viewer/helpers/filters/types' import type { SpeckleObject } from '@speckle/viewer' import type { Raw } from 'vue' -import { FilteringExtension } from '@speckle/viewer' +import { FilteringExtension, SelectionExtension } from '@speckle/viewer' import { watchTriggerable } from '@vueuse/core' import { useInjectedViewerState } from '~/lib/viewer/composables/setup' import { useOnViewerLoadComplete } from '~/lib/viewer/composables/viewer' import { useFilteringDataStore } from '~/lib/viewer/composables/filtering/dataStore' +import { HighlightExtension } from '~/lib/viewer/composables/setup/highlighting' /** * Setup composable for filter-related state @@ -61,6 +62,39 @@ export const useManualFilteringPostSetup = () => { const filteringExtension = () => instance.getExtension(FilteringExtension) + /** + * Preserve selection and highlighting state during filtering operations + * This replicates LegacyViewer's preserveSelectionHighlightFilter function + */ + const preserveSelectionHighlightFilter = (filterFn: () => T): T => { + const selectionExtension = instance.getExtension(SelectionExtension) + const highlightExtension = instance.getExtension(HighlightExtension) + + // 1. SAVE current state from viewer extensions + const selectedObjects = selectionExtension + .getSelectedObjects() + .map((obj) => obj.id as string) + const highlightedObjects = + highlightExtension?.getSelectedObjects().map((obj) => obj.id as string) || [] + + // 2. CLEAR viewer extensions directly + if (selectedObjects.length) selectionExtension.clearSelection() + if (highlightedObjects.length && highlightExtension) { + highlightExtension.clearSelection() + } + + // 3. EXECUTE the filtering operation + const result = filterFn() + + // 4. RESTORE to viewer extensions directly + if (selectedObjects.length) selectionExtension.selectObjects(selectedObjects) + if (highlightedObjects.length && highlightExtension) { + highlightExtension.selectObjects(highlightedObjects) + } + + return result + } + /** * Watch for changes to manually isolated object IDs */ @@ -69,17 +103,19 @@ export const useManualFilteringPostSetup = () => { (newIds, oldIds) => { if (!newIds || !oldIds) return - const extension = filteringExtension() + preserveSelectionHighlightFilter(() => { + const extension = filteringExtension() - const toIsolate = newIds.filter((id) => !oldIds.includes(id)) - if (toIsolate.length > 0) { - extension.isolateObjects(toIsolate, 'manual-isolation', true, true) - } + const toIsolate = newIds.filter((id) => !oldIds.includes(id)) + if (toIsolate.length > 0) { + extension.isolateObjects(toIsolate, 'manual-isolation', true, true) + } - const toUnIsolate = oldIds.filter((id) => !newIds.includes(id)) - if (toUnIsolate.length > 0) { - extension.unIsolateObjects(toUnIsolate, 'manual-isolation', true, true) - } + const toUnIsolate = oldIds.filter((id) => !newIds.includes(id)) + if (toUnIsolate.length > 0) { + extension.unIsolateObjects(toUnIsolate, 'manual-isolation', true, true) + } + }) }, { deep: true } ) @@ -92,17 +128,19 @@ export const useManualFilteringPostSetup = () => { (newIds, oldIds) => { if (!newIds || !oldIds) return - const extension = filteringExtension() + preserveSelectionHighlightFilter(() => { + const extension = filteringExtension() - const toHide = newIds.filter((id) => !oldIds.includes(id)) - if (toHide.length > 0) { - extension.hideObjects(toHide, 'manual-hiding', false, false) - } + const toHide = newIds.filter((id) => !oldIds.includes(id)) + if (toHide.length > 0) { + extension.hideObjects(toHide, 'manual-hiding', false, false) + } - const toShow = oldIds.filter((id) => !newIds.includes(id)) - if (toShow.length > 0) { - extension.showObjects(toShow, 'manual-hiding', false) - } + const toShow = oldIds.filter((id) => !newIds.includes(id)) + if (toShow.length > 0) { + extension.showObjects(toShow, 'manual-hiding', false) + } + }) }, { deep: true } ) diff --git a/packages/frontend-2/lib/viewer/composables/setup/highlighting.ts b/packages/frontend-2/lib/viewer/composables/setup/highlighting.ts index 4edb828f1..ad467bc8b 100644 --- a/packages/frontend-2/lib/viewer/composables/setup/highlighting.ts +++ b/packages/frontend-2/lib/viewer/composables/setup/highlighting.ts @@ -15,7 +15,7 @@ import { ViewerRenderPageType } from '~/lib/viewer/helpers/state' * Highlighting extension that replicates LegacyViewer's HighlightExtension * Uses SelectionExtension but disables default events for UI-only highlighting */ -class HighlightExtension extends SelectionExtension { +export class HighlightExtension extends SelectionExtension { public constructor(viewer: IViewer, cameraProvider: CameraController) { super(viewer, cameraProvider) @@ -51,20 +51,16 @@ export const useHighlightingPostSetup = () => { if (pageType.value === ViewerRenderPageType.Presentation) return - const highlightExtension = ref(null) + // Create the highlighting extension once during setup + instance.createExtension(HighlightExtension) // Get the highlighting extension instance - const getHighlightExtension = () => { - if (!highlightExtension.value) { - highlightExtension.value = instance.createExtension(HighlightExtension) - } - return highlightExtension.value - } + const getHighlightExtensionInstance = () => instance.getExtension(HighlightExtension) useOnViewerLoadComplete( ({ isInitial }) => { if (!isInitial) return - getHighlightExtension() + getHighlightExtensionInstance() }, { initialOnly: true } ) @@ -73,7 +69,7 @@ export const useHighlightingPostSetup = () => { watch( highlightedObjectIds, (newIds, oldIds) => { - const extension = getHighlightExtension() + const extension = getHighlightExtensionInstance() if (!extension) return // Clear all current highlights if new list is empty @@ -83,8 +79,6 @@ export const useHighlightingPostSetup = () => { } if (oldIds && isEqual(newIds, oldIds)) return - - // Clear and re-select to avoid accumulation extension.clearSelection() if (newIds.length > 0) { extension.selectObjects(newIds) diff --git a/packages/frontend-2/lib/viewer/composables/setup/postSetup.ts b/packages/frontend-2/lib/viewer/composables/setup/postSetup.ts index 7754c742b..d0d762717 100644 --- a/packages/frontend-2/lib/viewer/composables/setup/postSetup.ts +++ b/packages/frontend-2/lib/viewer/composables/setup/postSetup.ts @@ -67,7 +67,10 @@ import { } from '~/lib/viewer/composables/setup/filters' import { useFilterUtilities } from '~/lib/viewer/composables/filtering/filtering' import { useFilteringSetup } from '~/lib/viewer/composables/filtering/setup' -import { useHighlightingPostSetup } from '~/lib/viewer/composables/setup/highlighting' +import { + useHighlightingPostSetup, + HighlightExtension +} from '~/lib/viewer/composables/setup/highlighting' function useViewerLoadCompleteEventHandler() { const state = useInjectedViewerState() @@ -566,12 +569,25 @@ function useViewerFiltersIntegration() { ).filter(isNonNullable) if (arraysEqual(newIds, oldIds)) return - state.ui.highlightedObjectIds.value = [] - const selectionExtension = instance.getExtension(SelectionExtension) + const currentViewerSelection = selectionExtension + .getSelectedObjects() + .map((obj) => obj.id as string) + + if ( + currentViewerSelection.length === newIds.length && + difference(currentViewerSelection, newIds).length === 0 + ) { + return + } + + state.ui.highlightedObjectIds.value = [] + const highlightExtension = instance.getExtension(HighlightExtension) + if (highlightExtension) { + highlightExtension.clearSelection() + } selectionExtension.clearSelection() - if (newVal.length > 0) { selectionExtension.selectObjects(newIds) }