diff --git a/packages/frontend-2/components/viewer/explorer/Explorer.vue b/packages/frontend-2/components/viewer/explorer/Explorer.vue index 0cc816be58..87f04d7064 100644 --- a/packages/frontend-2/components/viewer/explorer/Explorer.vue +++ b/packages/frontend-2/components/viewer/explorer/Explorer.vue @@ -53,8 +53,7 @@ class="bg-foundation rounded-lg" > { if (!worldTree.value) return [] // eslint-disable-next-line vue/no-side-effects-in-computed-properties expandLevel.value = -1 - const nodes = [] const rootNodes = worldTree.value._root.children as ExplorerNode[] + + const results: Record = {} + const unmatchedNodes: ExplorerNode[] = [] + for (const node of rootNodes) { const objectId = ((node.model as Record).id as string) .split('/') .reverse()[0] as string - const resourceItem = resourceItems.value.find((res) => res.objectId === objectId) + const resourceItemIdx = resourceItems.value.findIndex( + (res) => res.objectId === objectId + ) + const resourceItem = + resourceItemIdx !== -1 ? resourceItems.value[resourceItemIdx] : null + const raw = node.model?.raw as Record if (resourceItem?.modelId) { // Model resource @@ -140,9 +150,24 @@ const rootNodes = computed(() => { raw.name = 'Object' raw.type = 'Single Object' } - nodes.push(node.model as ExplorerNode) + + const res = node.model as ExplorerNode + if (resourceItem) { + ;(results[resourceItemIdx] = results[resourceItemIdx] || []).push(res) + } else { + unmatchedNodes.push(res) + } } - return nodes + const nodes = [ + ...flatten(sortBy(Object.entries(results), (i) => i[0]).map((i) => i[1])), + ...unmatchedNodes + ] + + return nodes.map( + (n): TreeItemComponentModel => ({ + rawNode: markRaw(n) + }) + ) }) diff --git a/packages/frontend-2/components/viewer/explorer/TreeItem.vue b/packages/frontend-2/components/viewer/explorer/TreeItem.vue index df2ae82c3f..d26d589422 100644 --- a/packages/frontend-2/components/viewer/explorer/TreeItem.vue +++ b/packages/frontend-2/components/viewer/explorer/TreeItem.vue @@ -95,10 +95,13 @@
-
+
-
+
props.treeItem.atomic === true) -const speckleData = props.treeItem?.raw as SpeckleObject -const rawSpeckleData = props.treeItem?.raw as SpeckleObject +const isAtomic = computed(() => props.treeItem.rawNode.atomic === true) +const rawSpeckleData = computed(() => props.treeItem?.rawNode.raw as SpeckleObject) +const speckleData = rawSpeckleData function getNestedModelHeader(name: string): string { const parts = name.split('/') @@ -196,7 +203,9 @@ function getNestedModelHeader(name: string): string { } const headerAndSubheader = computed(() => { - const { header, subheader } = getHeaderAndSubheaderForSpeckleObject(rawSpeckleData) + const { header, subheader } = getHeaderAndSubheaderForSpeckleObject( + rawSpeckleData.value + ) return { header: getNestedModelHeader(header), subheader @@ -204,35 +213,45 @@ const headerAndSubheader = computed(() => { }) const childrenLength = computed(() => { - if (rawSpeckleData.elements && Array.isArray(rawSpeckleData.elements)) - return rawSpeckleData.elements.length - if (rawSpeckleData.children && Array.isArray(rawSpeckleData.children)) - return rawSpeckleData.children.length + if (rawSpeckleData.value.elements && Array.isArray(rawSpeckleData.value.elements)) + return rawSpeckleData.value.elements.length + if (rawSpeckleData.value.children && Array.isArray(rawSpeckleData.value.children)) + return rawSpeckleData.value.children.length return 0 }) const isSingleCollection = computed(() => { return ( - isNonEmptyObjectArray(speckleData.children) || - isNonEmptyObjectArray(speckleData.elements) + isNonEmptyObjectArray(speckleData.value.children) || + isNonEmptyObjectArray(speckleData.value.elements) ) }) const singleCollectionItems = computed(() => { - const treeItems = props.treeItem.children.filter( + const treeItems = props.treeItem.rawNode.children.filter( (child) => !!child.raw?.id && isAllowedType(child) // filter out random tree children (no id means they're not actual objects) ) // Handle the case of a wall, roof or other atomic objects that have nested children - if (isNonEmptyObjectArray(speckleData.elements) && isAtomic.value) { + if (isNonEmptyObjectArray(speckleData.value.elements) && isAtomic.value) { // We need to filter out children that are not direct descendants of `elements` // Note: this is a current assumption convention. - const ids = (speckleData.elements as SpeckleReference[]).map( + const ids = (speckleData.value.elements as SpeckleReference[]).map( (obj) => obj.referencedId ) - return treeItems.filter((item) => ids.includes(item.raw?.id as string)) + return treeItems + .filter((item) => ids.includes(item.raw?.id as string)) + .map( + (i): TreeItemComponentModel => ({ + rawNode: i + }) + ) } - return treeItems + return treeItems.map( + (i): TreeItemComponentModel => ({ + rawNode: i + }) + ) }) const itemCount = ref(10) @@ -245,16 +264,16 @@ const singleCollectionItemsPaginated = computed(() => { // object { @boat: [obj, obj, obj], @harbour: [obj, obj, obj], etc. } // @boat and @harbour would ideally be model collections, but, alas, connectors don't have that yet. const arrayCollections = computed(() => { - const arr = [] as ExplorerNode[] + const arr = [] as TreeItemComponentModel[] for (const k of Object.keys(rawSpeckleData)) { if (k === 'children' || k === 'elements' || k.includes('displayValue')) continue - const val = rawSpeckleData[k] as SpeckleReference[] + const val = rawSpeckleData.value[k] as SpeckleReference[] if (!isNonEmptyObjectArray(val)) continue const ids = val.map((ref) => ref.referencedId) // NOTE: we're assuming all collections have refs inside; might revisit/to think re edge cases - const actualRawRefs = props.treeItem.children.filter( + const actualRawRefs = props.treeItem.rawNode.children.filter( (node) => ids.includes(node.raw?.id as string) && isAllowedType(node) ) @@ -270,7 +289,9 @@ const arrayCollections = computed(() => { children: actualRawRefs, expanded: false } - arr.push(modelCollectionItem) + arr.push({ + rawNode: modelCollectionItem + }) } return arr @@ -326,7 +347,7 @@ const manualUnfoldToggle = () => { } const isSelected = computed(() => { - return !!objects.value.find((o) => o.id === speckleData.id) + return !!objects.value.find((o) => o.id === speckleData.value.id) }) const setSelection = (e: MouseEvent) => { @@ -335,19 +356,19 @@ const setSelection = (e: MouseEvent) => { return } if (isSelected.value && e.shiftKey) { - removeFromSelection(rawSpeckleData) + removeFromSelection(rawSpeckleData.value) return } if (!e.shiftKey) clearSelection() - addToSelection(rawSpeckleData) + addToSelection(rawSpeckleData.value) } const highlightObject = () => { - highlightObjects(getTargetObjectIds(rawSpeckleData)) + highlightObjects(getTargetObjectIds(rawSpeckleData.value)) } const unhighlightObject = () => { - unhighlightObjects(getTargetObjectIds(rawSpeckleData)) + unhighlightObjects(getTargetObjectIds(rawSpeckleData.value)) } const hiddenObjects = computed(() => filteringState.value?.hiddenObjects) @@ -355,7 +376,7 @@ const isolatedObjects = computed(() => filteringState.value?.isolatedObjects) const isHidden = computed(() => { if (!hiddenObjects.value) return false - const ids = getTargetObjectIds(rawSpeckleData) + const ids = getTargetObjectIds(rawSpeckleData.value) return containsAll(ids, hiddenObjects.value) }) @@ -366,12 +387,12 @@ const stateHasIsolatedObjectsInGeneral = computed(() => { const isIsolated = computed(() => { if (!isolatedObjects.value) return false - const ids = getTargetObjectIds(rawSpeckleData) + const ids = getTargetObjectIds(rawSpeckleData.value) return containsAll(ids, isolatedObjects.value) }) const hideOrShowObject = () => { - const ids = getTargetObjectIds(rawSpeckleData) + const ids = getTargetObjectIds(rawSpeckleData.value) if (!isHidden.value) { hideObjects(ids) return @@ -381,7 +402,7 @@ const hideOrShowObject = () => { } const isolateOrUnisolateObject = () => { - const ids = getTargetObjectIds(rawSpeckleData) + const ids = getTargetObjectIds(rawSpeckleData.value) if (!isIsolated.value) { isolateObjects(ids) return diff --git a/packages/frontend-2/lib/viewer/composables/setup.ts b/packages/frontend-2/lib/viewer/composables/setup.ts index a4abd0e5b0..654f595334 100644 --- a/packages/frontend-2/lib/viewer/composables/setup.ts +++ b/packages/frontend-2/lib/viewer/composables/setup.ts @@ -111,6 +111,9 @@ export type InjectableViewerState = Readonly<{ * Various values that represent the current Viewer instance state */ metadata: { + /** + * Based on a shallow ref + */ worldTree: ComputedRef> availableFilters: ComputedRef> views: ComputedRef diff --git a/packages/frontend-2/lib/viewer/helpers/sceneExplorer.ts b/packages/frontend-2/lib/viewer/helpers/sceneExplorer.ts index 50db63f29f..5102f6d8e0 100644 --- a/packages/frontend-2/lib/viewer/helpers/sceneExplorer.ts +++ b/packages/frontend-2/lib/viewer/helpers/sceneExplorer.ts @@ -6,6 +6,7 @@ import { type SpeckleReference, type StringPropertyInfo } from '@speckle/viewer' +import type { Raw } from 'vue' export const isStringPropertyInfo = ( info: MaybeNullOrUndefined @@ -26,4 +27,8 @@ export type ExplorerNode = { children: ExplorerNode[] } +export type TreeItemComponentModel = { + rawNode: Raw +} + export type { SpeckleObject, SpeckleReference }