diff options
| -rw-r--r-- | src/components/TileTree.vue | 80 | ||||
| -rw-r--r-- | src/lib.ts | 33 |
2 files changed, 59 insertions, 54 deletions
diff --git a/src/components/TileTree.vue b/src/components/TileTree.vue index b915601..0285505 100644 --- a/src/components/TileTree.vue +++ b/src/components/TileTree.vue @@ -5,7 +5,7 @@ import ParentBar from './ParentBar.vue'; import TileInfoModal from './TileInfoModal.vue'; import SearchModal from './SearchModal.vue'; import Settings from './Settings.vue'; -import {TolNode, LayoutNode, initLayoutTree, tryLayout} from '../lib'; +import {TolNode, LayoutNode, initLayoutTree, initLayoutMap, tryLayout} from '../lib'; import type {LayoutOptions} from '../lib'; // Import paths lack a .ts or .js extension because .ts makes vue-tsc complain, and .js makes vite complain @@ -86,7 +86,7 @@ export default defineComponent({ return { layoutTree: layoutTree, activeRoot: layoutTree, - layoutMap: this.initLayoutMap(layoutTree), // Maps names to LayoutNode objects + layoutMap: initLayoutMap(layoutTree), // Maps names to LayoutNode objects tolMap: tolMap, // Maps names to TolNode objects // infoModalNode: null as TolNode | null, // Hides/unhides info modal, and provides the node to display @@ -167,7 +167,8 @@ export default defineComponent({ if (!this.resizeThrottled){ this.width = document.documentElement.clientWidth; this.height = document.documentElement.clientHeight; - tryLayout(this.activeRoot, this.tileAreaPos, this.tileAreaDims, this.layoutOptions, true); + tryLayout(this.activeRoot, this.layoutMap, + this.tileAreaPos, this.tileAreaDims, this.layoutOptions, true); // Prevent re-triggering until after a delay this.resizeThrottled = true; setTimeout(() => {this.resizeThrottled = false;}, this.resizeDelay); @@ -175,33 +176,25 @@ export default defineComponent({ }, // For tile expand/collapse events onInnerLeafClicked({layoutNode, domNode}: {layoutNode: LayoutNode, domNode?: HTMLElement}){ - let success = tryLayout(this.activeRoot, this.tileAreaPos, this.tileAreaDims, this.layoutOptions, false, - {type: 'expand', node: layoutNode}); - if (success){ - layoutNode.children.forEach(n => this.layoutMap.set(n.tolNode.name, n)); - } else { - if (domNode != null){ - // Trigger failure animation - domNode.classList.remove('animate-expand-shrink'); - domNode.offsetWidth; // Triggers reflow - domNode.classList.add('animate-expand-shrink'); - } + let success = tryLayout(this.activeRoot, this.layoutMap, + this.tileAreaPos, this.tileAreaDims, this.layoutOptions, false, {type: 'expand', node: layoutNode}); + if (!success && domNode != null){ + // Trigger failure animation + domNode.classList.remove('animate-expand-shrink'); + domNode.offsetWidth; // Triggers reflow + domNode.classList.add('animate-expand-shrink'); } return success; }, onInnerHeaderClicked({layoutNode, domNode}: {layoutNode: LayoutNode, domNode?: HTMLElement}){ let oldChildren = layoutNode.children; - let success = tryLayout(this.activeRoot, this.tileAreaPos, this.tileAreaDims, this.layoutOptions, false, - {type: 'collapse', node: layoutNode}); - if (success){ - oldChildren.forEach(n => this.removeFromLayoutMap(n, this.layoutMap)); - } else { - if (domNode != null){ - // Trigger failure animation - domNode.classList.remove('animate-shrink-expand'); - domNode.offsetWidth; // Triggers reflow - domNode.classList.add('animate-shrink-expand'); - } + let success = tryLayout(this.activeRoot, this.layoutMap, + this.tileAreaPos, this.tileAreaDims, this.layoutOptions, false, {type: 'collapse', node: layoutNode}); + if (!success && domNode != null){ + // Trigger failure animation + domNode.classList.remove('animate-shrink-expand'); + domNode.offsetWidth; // Triggers reflow + domNode.classList.add('animate-shrink-expand'); } return success; }, @@ -213,8 +206,8 @@ export default defineComponent({ } LayoutNode.hideUpward(layoutNode); this.activeRoot = layoutNode; - tryLayout(layoutNode, this.tileAreaPos, this.tileAreaDims, this.layoutOptions, true, - {type: 'expand', node: layoutNode}); + tryLayout(this.activeRoot, this.layoutMap, + this.tileAreaPos, this.tileAreaDims, this.layoutOptions, true, {type: 'expand', node: layoutNode}); }, onInnerHeaderClickHeld(layoutNode: LayoutNode){ if (layoutNode == this.activeRoot){ @@ -223,12 +216,12 @@ export default defineComponent({ } LayoutNode.hideUpward(layoutNode); this.activeRoot = layoutNode; - tryLayout(layoutNode, this.tileAreaPos, this.tileAreaDims, this.layoutOptions, true); + tryLayout(this.activeRoot, this.layoutMap, this.tileAreaPos, this.tileAreaDims, this.layoutOptions, true); }, onSepdParentClicked(layoutNode: LayoutNode){ LayoutNode.showDownward(layoutNode); this.activeRoot = layoutNode; - tryLayout(layoutNode, this.tileAreaPos, this.tileAreaDims, this.layoutOptions, true); + tryLayout(this.activeRoot, this.layoutMap, this.tileAreaPos, this.tileAreaDims, this.layoutOptions, true); }, // For info modal events onInnerInfoIconClicked(node: LayoutNode){ @@ -247,7 +240,7 @@ export default defineComponent({ this.settingsOpen = false; }, onLayoutOptionChange(){ - tryLayout(this.activeRoot, this.tileAreaPos, this.tileAreaDims, this.layoutOptions, true); + tryLayout(this.activeRoot, this.layoutMap, this.tileAreaPos, this.tileAreaDims, this.layoutOptions, true); }, // onSearchIconClick(){ @@ -276,31 +269,18 @@ export default defineComponent({ } } }, - initLayoutMap(node: LayoutNode): Map<string,LayoutNode> { - function helper(node: LayoutNode, map: Map<string,LayoutNode>){ - map.set(node.tolNode.name, node); - node.children.forEach(n => helper(n, map)); - } - let map = new Map(); - helper(node, map); - return map; - }, - removeFromLayoutMap(node: LayoutNode, map: Map<string,LayoutNode>){ - map.delete(node.tolNode.name); - node.children.forEach(n => this.removeFromLayoutMap(n, map)); - }, expandToTolNode(tolNode: TolNode){ // Check if searched node is shown if (this.layoutMap.get(tolNode.name) != null){ return; } // Get ancestor to expand - let ancestor = tolNode.parent; + let ancestor = tolNode.parent!; while (this.layoutMap.get(ancestor.name) == null){ - ancestor = ancestor.parent; + ancestor = ancestor.parent!; } // Attempt expansion - let layoutNode = this.layoutMap.get(ancestor.name); + let layoutNode = this.layoutMap.get(ancestor.name)!; let success = this.onInnerLeafClicked({layoutNode}); if (success){ console.log('Expanded ' + ancestor.name); @@ -314,12 +294,12 @@ export default defineComponent({ return; } while (true){ - if (ancestor.parent.name == this.activeRoot.tolNode.name){ + if (ancestor.parent!.name == this.activeRoot.tolNode.name){ break; } - ancestor = ancestor.parent; + ancestor = ancestor.parent!; } - layoutNode = this.layoutMap.get(ancestor.name); + layoutNode = this.layoutMap.get(ancestor.name)!; this.onInnerHeaderClickHeld(layoutNode); console.log('Expanded-to-view ' + ancestor.name); setTimeout(() => this.expandToTolNode(tolNode), 300); @@ -328,7 +308,7 @@ export default defineComponent({ created(){ window.addEventListener('resize', this.onResize); window.addEventListener('keyup', this.onKeyUp); - tryLayout(this.activeRoot, this.tileAreaPos, this.tileAreaDims, this.layoutOptions, true); + tryLayout(this.activeRoot, this.layoutMap, this.tileAreaPos, this.tileAreaDims, this.layoutOptions, true); }, unmounted(){ window.removeEventListener('resize', this.onResize); @@ -75,7 +75,7 @@ export class LayoutNode { } // Copies render-relevant data to a given LayoutNode tree // If a target node has more/less children, removes/gives own children - copyTreeForRender(target: LayoutNode): void { + copyTreeForRender(target: LayoutNode, map?: LayoutMap): void { target.pos = this.pos; target.dims = this.dims; target.showHeader = this.showHeader; @@ -84,12 +84,18 @@ export class LayoutNode { target.empSpc = this.empSpc; // Currently redundant, but maintains data-consistency // Handle children if (this.children.length == target.children.length){ - this.children.forEach((n,i) => n.copyTreeForRender(target.children[i])); + this.children.forEach((n,i) => n.copyTreeForRender(target.children[i], map)); } else if (this.children.length < target.children.length){ + if (map != null){ + target.children.forEach(child => removeFromLayoutMap(child, map)); + } target.children = []; } else { target.children = this.children; target.children.forEach(n => {n.parent = target}); + if (map != null){ + target.children.forEach(child => {addToLayoutMap(child, map)}); + } } } // Assigns render-relevant data to this single node @@ -157,6 +163,8 @@ export class SepSweptArea { return new SepSweptArea([...this.pos], [...this.dims], this.sweptLeft); } } +// +export type LayoutMap = Map<string, LayoutNode>; // Creates a LayoutNode representing a TolNode tree, up to a given depth (0 means just the root) export function initLayoutTree(tol: TolNode, depth: number): LayoutNode { @@ -174,10 +182,27 @@ export function initLayoutTree(tol: TolNode, depth: number): LayoutNode { } return initHelper(tol, depth); } +export function initLayoutMap(node: LayoutNode): LayoutMap { + function helper(node: LayoutNode, map: LayoutMap){ + map.set(node.tolNode.name, node); + node.children.forEach(n => helper(n, map)); + } + let map = new Map(); + helper(node, map); + return map; +} +function removeFromLayoutMap(node: LayoutNode, map: LayoutMap){ + map.delete(node.tolNode.name); + node.children.forEach(n => removeFromLayoutMap(n, map)); +} +function addToLayoutMap(node: LayoutNode, map: LayoutMap){ + map.set(node.tolNode.name, node); + node.children.forEach(n => addToLayoutMap(n, map)); +} // Attempts layout on a LayoutNode's corresponding TolNode tree, for an area with given xy-position and width+height // 'allowCollapse' allows the layout algorithm to collapse nodes to avoid layout failure // 'chg' allows for performing layout after expanding/collapsing a node -export function tryLayout(layoutTree: LayoutNode, pos: [number,number], dims: [number,number], +export function tryLayout(layoutTree: LayoutNode, layoutMap: LayoutMap, pos: [number,number], dims: [number,number], options: LayoutOptions, allowCollapse: boolean = false, chg?: LayoutTreeChg){ // Create a new LayoutNode tree, in case of layout failure let tempTree = layoutTree.cloneNodeTree(chg); @@ -192,7 +217,7 @@ export function tryLayout(layoutTree: LayoutNode, pos: [number,number], dims: [n tempTree.pos[0] += (dims[0] - tempTree.dims[0]) / 2; tempTree.pos[1] += (dims[1] - tempTree.dims[1]) / 2; // Apply to active LayoutNode tree - tempTree.copyTreeForRender(layoutTree); + tempTree.copyTreeForRender(layoutTree, layoutMap); } return success; } |
