Skip to content

Commit

Permalink
feat: ensure consistent ordering of nodes to match ViewerLayoutPanel (#…
Browse files Browse the repository at this point in the history
…2279)

* feat: ensure consistent ordering of nodes to match ViewerLayoutPanel

* feat: Order rootNodes based on resourceItems

* Revert "feat: Order rootNodes based on resourceItems"

This reverts commit 5029e36.

* Revert "feat: ensure consistent ordering of nodes to match ViewerLayoutPanel"

This reverts commit e2f557b.

* fix: change rawSpeckleData to computed. order scene explorer items.

* Fix to unnamed models after call with Fabians
  • Loading branch information
andrewwallacespeckle authored Jun 21, 2024
1 parent 25c92ff commit 67b778b
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 44 deletions.
41 changes: 33 additions & 8 deletions packages/frontend-2/components/viewer/explorer/Explorer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,7 @@
class="bg-foundation rounded-lg"
>
<ViewerExplorerTreeItem
:item-id="(rootNode.data?.id as string)"
:tree-item="markRaw(rootNode)"
:tree-item="rootNode"
:sub-header="'Model Version'"
:debug="false"
:expand-level="expandLevel"
Expand All @@ -75,14 +74,17 @@ import {
CodeBracketIcon
} from '@heroicons/vue/24/solid'
import { ViewerEvent } from '@speckle/viewer'
import type { ExplorerNode } from '~~/lib/viewer/helpers/sceneExplorer'
import type {
ExplorerNode,
TreeItemComponentModel
} from '~~/lib/viewer/helpers/sceneExplorer'
import {
useInjectedViewer,
useInjectedViewerLoadedResources,
useInjectedViewerState
} from '~~/lib/viewer/composables/setup'
import { markRaw } from 'vue'
import { useViewerEventListener } from '~~/lib/viewer/composables/viewer'
import { sortBy, flatten } from 'lodash-es'
defineEmits(['close'])
Expand Down Expand Up @@ -121,13 +123,21 @@ const rootNodes = computed(() => {
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<number, ExplorerNode[]> = {}
const unmatchedNodes: ExplorerNode[] = []
for (const node of rootNodes) {
const objectId = ((node.model as Record<string, unknown>).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<string, unknown>
if (resourceItem?.modelId) {
// Model resource
Expand All @@ -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)
})
)
})
</script>
93 changes: 57 additions & 36 deletions packages/frontend-2/components/viewer/explorer/TreeItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -95,10 +95,13 @@
<!-- If we have array collections -->
<div v-if="isMultipleCollection">
<!-- mul col items -->
<div v-for="collection in arrayCollections" :key="collection?.raw?.name">
<div
v-for="(item, idx) in arrayCollections"
:key="item?.rawNode.raw?.name || idx"
>
<TreeItem
:item-id="(collection.raw?.id as string)"
:tree-item="collection"
:item-id="(item.rawNode.raw?.id as string)"
:tree-item="item"
:depth="depth + 1"
:expand-level="props.expandLevel"
:manual-expand-level="manualExpandLevel"
Expand All @@ -111,9 +114,12 @@
<!-- If we have a single model collection -->
<div v-if="isSingleCollection">
<!-- single col items -->
<div v-for="item in singleCollectionItemsPaginated" :key="item.raw?.id">
<div
v-for="(item, idx) in singleCollectionItemsPaginated"
:key="item.rawNode.raw?.id || idx"
>
<TreeItem
:item-id="(item.raw?.id as string)"
:item-id="(item.rawNode.raw?.id as string)"
:tree-item="item"
:depth="depth + 1"
:expand-level="props.expandLevel"
Expand Down Expand Up @@ -143,7 +149,8 @@ import { FunnelIcon as FunnelIconOutline } from '@heroicons/vue/24/outline'
import type {
ExplorerNode,
SpeckleObject,
SpeckleReference
SpeckleReference,
TreeItemComponentModel
} from '~~/lib/viewer/helpers/sceneExplorer'
import { useInjectedViewerState } from '~~/lib/viewer/composables/setup'
import {
Expand All @@ -159,8 +166,8 @@ import {

const props = withDefaults(
defineProps<{
treeItem: ExplorerNode
parent?: ExplorerNode
treeItem: TreeItemComponentModel
parent?: TreeItemComponentModel
depth?: number
debug?: boolean
expandLevel: number
Expand All @@ -186,53 +193,65 @@ const { hideObjects, showObjects, isolateObjects, unIsolateObjects } =
useFilterUtilities()
const { highlightObjects, unhighlightObjects } = useHighlightedObjectsUtilities()

const isAtomic = computed(() => 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('/')
return parts.length > 1 ? (parts.pop() as string) : name
}

const headerAndSubheader = computed(() => {
const { header, subheader } = getHeaderAndSubheaderForSpeckleObject(rawSpeckleData)
const { header, subheader } = getHeaderAndSubheaderForSpeckleObject(
rawSpeckleData.value
)
return {
header: getNestedModelHeader(header),
subheader
}
})

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)
Expand All @@ -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)
)

Expand All @@ -270,7 +289,9 @@ const arrayCollections = computed(() => {
children: actualRawRefs,
expanded: false
}
arr.push(modelCollectionItem)
arr.push({
rawNode: modelCollectionItem
})
}

return arr
Expand Down Expand Up @@ -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) => {
Expand All @@ -335,27 +356,27 @@ 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)
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)
})

Expand All @@ -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
Expand All @@ -381,7 +402,7 @@ const hideOrShowObject = () => {
}

const isolateOrUnisolateObject = () => {
const ids = getTargetObjectIds(rawSpeckleData)
const ids = getTargetObjectIds(rawSpeckleData.value)
if (!isIsolated.value) {
isolateObjects(ids)
return
Expand Down
3 changes: 3 additions & 0 deletions packages/frontend-2/lib/viewer/composables/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Optional<WorldTree>>
availableFilters: ComputedRef<Optional<PropertyInfo[]>>
views: ComputedRef<SpeckleView[]>
Expand Down
5 changes: 5 additions & 0 deletions packages/frontend-2/lib/viewer/helpers/sceneExplorer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
type SpeckleReference,
type StringPropertyInfo
} from '@speckle/viewer'
import type { Raw } from 'vue'

export const isStringPropertyInfo = (
info: MaybeNullOrUndefined<PropertyInfo>
Expand All @@ -26,4 +27,8 @@ export type ExplorerNode = {
children: ExplorerNode[]
}

export type TreeItemComponentModel = {
rawNode: Raw<ExplorerNode>
}

export type { SpeckleObject, SpeckleReference }

0 comments on commit 67b778b

Please sign in to comment.