feat(fe): preserveSelectionHighlightFilter

feat(fe): preserveSelectionHighlightFilter
This commit is contained in:
andrewwallacespeckle
2025-09-19 15:07:17 +01:00
committed by GitHub
5 changed files with 84 additions and 47 deletions
@@ -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)
}
@@ -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)
}
}
@@ -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 = <T>(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 }
)
@@ -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<HighlightExtension | null>(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)
@@ -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)
}