https://medium.com/@jsdbroughton/not-as-recursive-as-id-hoped-2f0fc2a30cc7
Last active
February 16, 2020 23:27
-
-
Save jsdbroughton/524c4fac4804b42db2ebe24dafe1e072 to your computer and use it in GitHub Desktop.
Not as recursive as I'd hoped. Or how Javascript's essence saved me from recurring dreams.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import {AbstractMesh} from "webgl-library"; | |
export interface ModelNode { | |
// values from WebGL | |
id: string; | |
uniqueId: number; | |
mesh: AbstractMesh; | |
// name for cross-referencing | |
name?: string; | |
// data fields used to structure the model | |
building: string; | |
floor: string; | |
zone: string; | |
//final leaf node id | |
mark: string; | |
} | |
export interface TreeItem { | |
id: number; | |
name: string; | |
parentKey?: string; | |
key?: string; | |
children?: TreeItem[]; | |
// data object connects tree to model element | |
data?: ModelNode; | |
} | |
export interface FieldToken { | |
name: string; | |
// attributes used in UI | |
id?: number; | |
fix?: boolean; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { FieldToken, TreeItem } from "../types.ts"; | |
// basic string value sort by name values | |
const sortByName = ( { name:a }: TreeItem, { name:b }: TreeItem ):number => | |
a == b ? 0 : a < b ? -1 : 1; | |
/** | |
* Nest a flat array of objects based on the precedence order of attributes. | |
* @param {TreeItem[]} treeItems - The flat object array. | |
* @param {FieldToken[]} fieldTokens - The ordered array of attributes. | |
* @return {TreeItem[]} The nested array. | |
*/ | |
export const nestTreeByFieldNames = ( treeItems: TreeItem[], fieldTokens: FieldToken[] ): TreeItem[] => { | |
const uniqueIds: Set<number> = new Set(); | |
let lastId = 1; | |
// All treeviewItems | |
treeItems.forEach( ( item ): void => { | |
if ( item && item.data ) { | |
uniqueIds.add( item.data.uniqueId ); | |
lastId += 1; | |
} | |
} ); | |
const fieldNames = fieldTokens.map( ( field ): string => field.name ); | |
const parentKeys: Set<string> = new Set(); | |
const keyedItems = treeItems.map( ( item ): TreeItem => { | |
const fieldValues = fieldNames.map( ( field ): string => item.data && item.data[field] ); | |
let parentKey = fieldValues.slice( 0, -1 ).reduce( ( id, field ):string=>{ | |
if ( id && field ) { | |
id += `-` + field; | |
} | |
if ( !id && field ) { | |
id = field; | |
} | |
parentKeys.add( id ); | |
return id; | |
}, `` ); | |
let key = fieldValues.join( `-` ); | |
parentKeys.add( parentKey ); | |
// return the TreeItem with correct key and parentKey values | |
// ditch the mesh component of the TreeItem as unneeded baggage | |
// eslint-disable-next-line @typescript-eslint/no-unused-vars | |
const { mesh, ...keyedItem } = Object.assign( item.data, { id:item.id, name: item.name, parentKey, key } ); | |
return keyedItem; | |
} ); | |
const parentItems:TreeItem[] = []; | |
parentKeys.forEach( ( v:string ):void=>{ | |
let parts = v.split( `-` ); | |
while ( uniqueIds.has( lastId ) ) { | |
lastId += 1; | |
} | |
uniqueIds.add( lastId ); | |
let name = parts.pop() || ``; | |
parentItems.push( { | |
id: lastId, | |
key: v, | |
name, | |
parentKey: parts.join( `-` ) | |
} ); | |
} ); | |
let tree:TreeItem[] = []; | |
const keyMap:Map<string, number> = new Map(); | |
const treeItemsWithBranches = [...keyedItems, ...parentItems]; | |
treeItemsWithBranches.forEach( ( node ):void => { | |
if ( !node.parentKey ) { | |
tree.push( node ); | |
tree = tree.sort( sortByName ); | |
return; | |
} | |
let parentIndex = keyMap.get( node.parentKey ); | |
if ( !keyMap.get( node.parentKey ) ) { | |
parentIndex = treeItemsWithBranches.findIndex( ( el ):boolean => el.key === node.parentKey ); | |
keyMap.set( node.parentKey, parentIndex ); | |
} | |
if ( parentIndex !== undefined && parentIndex >= 0 ) { | |
if ( !treeItemsWithBranches[parentIndex] || !treeItemsWithBranches[parentIndex].children ) { | |
treeItemsWithBranches[parentIndex].children = [node]; | |
return; | |
} | |
treeItemsWithBranches[parentIndex]?.children?.push( node ); | |
treeItemsWithBranches[parentIndex]?.children?.sort( sortByName ); | |
} | |
} ); | |
return tree; | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<template> | |
<app> | |
<ul style="list-style: none;"> | |
<draggable v-model="tokens" draggable=".drg"> | |
<li v-for="token in tokens" :key="token.id" :class="{drg:!token.fix}">{{ token.name }}</li> | |
</draggable> | |
</ul> | |
<treeview | |
ref="selectionTree" | |
:items="itemTree" | |
selectable | |
@input="updateVisibleItems" | |
/> | |
<Model @loaded="modelLoaded"> | |
</app> | |
</template> | |
<script lang="ts"> | |
import Vue from "vue"; | |
import { nestTreeByFieldNames } from "../utils/pivotMap"; | |
import { ModelNode, TreeItem } from "../types/maps"; | |
Vue.use( vb ); | |
export default Vue.extend( { | |
name: `ModelView`, | |
components: { draggable: () => import( `vuedraggable` ) }, | |
data () { | |
return { | |
tokens: [{ name:`building` }, { name: `floor` }, { name: `zone` }, { name: `mark`, fix:true }], | |
mapOfMeshes: new Map() as Map<number, AbstractMesh> | |
}; | |
}, | |
computed: { | |
treeItems (): TreeItem[] { | |
if ( !this.modelNodes ) return []; | |
const modelNodes: ModelNode[] = this.modelNodes.slice(); | |
let lastId = 0; | |
const allItems: TreeItem[] = modelNodes.map( model => { | |
if ( model.uniqueId > lastId ) lastId = model.uniqueId; | |
return { | |
data: model, | |
name: model.mark, | |
id: model.uniqueId | |
}; | |
} ); | |
return allItems; | |
}, | |
uniqueIds (): number[] { | |
if ( !this.treeItems ) return []; | |
const allItems: TreeItem[] = this.treeItems; | |
const uniqueIds: Set<number> = new Set(); | |
allItems.forEach( item => { | |
if ( item && item.data ) uniqueIds.add( item.data.uniqueId ); | |
} ); | |
return Array.from( uniqueIds.values() ); | |
}, | |
itemTree (): TreeItem[] { | |
if ( this.treeItems.length > 0 ) { | |
return nestTreeByFieldNames( this.treeItems, this.tokens ); | |
} | |
return []; | |
}, | |
modelNodes (): ModelNode[] { | |
if ( Object.entries( this.meshes ).length == 0 ) return []; | |
const modelNodes = this.meshes.map( mesh => { | |
const [building, floor, zone, mark] = mesh.id.split( /[-#]/ ); | |
return { | |
mesh, | |
building, | |
floor, | |
zone, | |
mark, | |
id: mesh.id, | |
uniqueId: mesh.uniqueId | |
}; | |
} ); | |
return modelNodes; | |
} | |
}, | |
methods: { | |
updateVisibleItems ( items: number[] ): void { | |
const selected: Set<number> = new Set( items || [] ); | |
const uniqueIds: Set<number> = new Set( this.uniqueIds || [] ); | |
const complement = new Set( | |
Array.from( uniqueIds ).filter( ( uid: number ) => !selected.has( uid ) ) | |
); | |
const intersection = new Set( | |
Array.from( uniqueIds ).filter( ( uid: number ) => selected.has( uid ) ) | |
); | |
this.mapOfMeshes.forEach( ( model: AbstractMesh, k: number ) => { | |
if ( complement.has( k ) ) model.isVisible = false; | |
if ( intersection.has( k ) ) model.isVisible = true; | |
} ); | |
}, | |
async modelLoaded ( scene: Scene ): Promise<void> { | |
const assets = await loadPromise( `../models/`, `model.gltf`, scene ); | |
assets.addAllToScene(); | |
this.assets = assets; | |
this.meshes = assets.meshes.slice( 1 ); | |
const mapOfMeshes: Map<number, AbstractMesh> = new Map(); | |
this.meshes.forEach( ( model: AbstractMesh ) => { | |
model.isVisible = false; | |
mapOfMeshes.set( model.uniqueId, model ); | |
} ); | |
this.mapOfMeshes = mapOfMeshes; | |
this.scene = scene; | |
}, | |
} | |
} ); | |
</script> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment