From 7bc4522be1ef1e49591dbe0a924754e61ed6abe2 Mon Sep 17 00:00:00 2001 From: Terry Truong Date: Tue, 22 Mar 2022 22:56:28 +1100 Subject: Hide ancestors on double-click --- src/components/Tile.vue | 19 +++++++++++-------- src/components/TileTree.vue | 35 +++++++++++++++++++++++------------ src/lib.ts | 27 ++++++++++++++++++++++++--- 3 files changed, 58 insertions(+), 23 deletions(-) diff --git a/src/components/Tile.vue b/src/components/Tile.vue index b1454ac..4009068 100644 --- a/src/components/Tile.vue +++ b/src/components/Tile.vue @@ -25,7 +25,6 @@ const defaultOptions = { export default defineComponent({ props: { layoutNode: {type: Object as PropType, required: true}, - isRoot: {type: Boolean, default: false}, // Settings from parent component headerSz: {type: Number, required: true}, tileSpacing: {type: Number, required: true}, @@ -58,17 +57,17 @@ export default defineComponent({ return { // Places div using layoutNode, with centering if root position: 'absolute', - left: this.isRoot ? '50%' : this.layoutNode.pos[0] + 'px', - top: this.isRoot ? '50%' : this.layoutNode.pos[1] + 'px', - transform: this.isRoot ? 'translate(-50%, -50%)' : 'none', - width: this.layoutNode.dims[0] + 'px', - height: this.layoutNode.dims[1] + 'px', + left: (this.layoutNode.hidden ? 0 : this.layoutNode.pos[0]) + 'px', + top: (this.layoutNode.hidden ? 0 : this.layoutNode.pos[1]) + 'px', + width: (this.layoutNode.hidden ? 0 : this.layoutNode.dims[0]) + 'px', + height: (this.layoutNode.hidden ? 0 : this.layoutNode.dims[1]) + 'px', + visibility: this.layoutNode.hidden ? 'hidden' : 'visible', // Other bindings transitionDuration: this.transitionDuration + 'ms', zIndex: String(this.zIdx), overflow: String(this.overflow), // Static styles - transitionProperty: 'left, top, width, height', + transitionProperty: 'left, top, width, height, visibility', transitionTimingFunction: 'ease-out', // CSS variables '--nonLeafBgColor': this.nonLeafBgColor, @@ -91,7 +90,7 @@ export default defineComponent({ position: 'absolute', left: this.options.leafHeaderX + 'px', top: this.options.leafHeaderY + 'px', - maxWidth: (this.layoutNode.dims[0] - this.options.leafHeaderX * 2) + 'px', + maxWidth: this.layoutNode.hidden ? 0 : this.layoutNode.dims[0] - this.options.leafHeaderX * 2 + 'px', height: this.options.leafHeaderFontSz + 'px', lineHeight: this.options.leafHeaderFontSz + 'px', fontSize: this.options.leafHeaderFontSz + 'px', @@ -167,6 +166,10 @@ export default defineComponent({ methods: { // For click handling onLeafClick(evt: UIEvent){ + if (!this.isExpandable){ + console.log('Ignored click on non-expandable leaf node'); + return; + } let prepForTransition = () => { (this.$refs.leaf as Element).classList.replace('shadow-highlight', 'shadow-normal'); // Temporary changes to prevent content overlap and overflow diff --git a/src/components/TileTree.vue b/src/components/TileTree.vue index 7f2f554..2cc2d53 100644 --- a/src/components/TileTree.vue +++ b/src/components/TileTree.vue @@ -40,8 +40,10 @@ const defaultOtherOptions = { // Collects events about tile expansion/collapse and window-resize, and initiates relayout of tiles export default defineComponent({ data(){ + let layoutTree = new LayoutTree(tol, defaultLayoutOptions, 0); return { - layoutTree: new LayoutTree(tol, defaultLayoutOptions, 0), + layoutTree: layoutTree, + activeRoot: layoutTree.root, layoutOptions: {...defaultLayoutOptions}, otherOptions: {...defaultOtherOptions}, width: document.documentElement.clientWidth - (defaultOtherOptions.rootOffset * 2), @@ -55,7 +57,7 @@ export default defineComponent({ // Update data and relayout tiles this.width = document.documentElement.clientWidth - (this.otherOptions.rootOffset * 2); this.height = document.documentElement.clientHeight - (this.otherOptions.rootOffset * 2); - if (!this.layoutTree.tryLayout([0,0], [this.width,this.height], true)){ + if (!this.layoutTree.tryLayout(this.activeRoot, [0,0], [this.width,this.height], true)){ console.log('Unable to layout tree'); } // Prevent re-triggering until after a delay @@ -64,11 +66,7 @@ export default defineComponent({ } }, onInnerLeafClicked({layoutNode, domNode}: {layoutNode: LayoutNode, domNode: HTMLElement}){ - if (layoutNode.tolNode.children.length == 0){ - //console.log('Tile to expand has no children'); - return; - } - let success = this.layoutTree.tryLayout([0,0], [this.width,this.height], false, + let success = this.layoutTree.tryLayout(this.activeRoot, [0,0], [this.width,this.height], false, {type: 'expand', node: layoutNode}); if (!success){ // Trigger failure animation @@ -79,7 +77,7 @@ export default defineComponent({ } }, onInnerHeaderClicked({layoutNode, domNode}: {layoutNode: LayoutNode, domNode: HTMLElement}){ - let success = this.layoutTree.tryLayout([0,0], [this.width,this.height], false, + let success = this.layoutTree.tryLayout(this.activeRoot, [0,0], [this.width,this.height], false, {type: 'collapse', node: layoutNode}); if (!success){ // Trigger failure animation @@ -90,15 +88,28 @@ export default defineComponent({ } }, onInnerLeafDblClicked(layoutNode: LayoutNode){ - console.log('double clicked leaf: ' + layoutNode.tolNode.name); + if (layoutNode == this.activeRoot){ + console.log('Ignored expand-to-view on root node'); + return; + } + LayoutNode.hideUpward(layoutNode); + this.activeRoot = layoutNode; + this.layoutTree.tryLayout(layoutNode, [0,0], [this.width,this.height], true, + {type: 'expand', node: layoutNode}); }, onInnerHeaderDblClicked(layoutNode: LayoutNode){ - console.log('double clicked header: ' + layoutNode.tolNode.name); + if (layoutNode.parent == null){ + console.log('Ignored expand-to-view on root node'); + return; + } + LayoutNode.hideUpward(layoutNode); + this.activeRoot = layoutNode; + this.layoutTree.tryLayout(layoutNode, [0,0], [this.width,this.height], true); }, }, created(){ window.addEventListener('resize', this.onResize); - if (!this.layoutTree.tryLayout([0,0], [this.width,this.height], true)){ + if (!this.layoutTree.tryLayout(this.activeRoot, [0,0], [this.width,this.height], true)){ console.log('Unable to layout tree'); } }, @@ -115,7 +126,7 @@ export default defineComponent({
diff --git a/src/lib.ts b/src/lib.ts index 5566fba..c4bea0b 100644 --- a/src/lib.ts +++ b/src/lib.ts @@ -40,9 +40,10 @@ export class LayoutTree { // Attempts layout of TolNode tree, for an area with given xy-coordinate and width+height (in pixels) // 'allowCollapse' allows the layout algorithm to collapse nodes to avoid layout failure // 'chg' allows for performing layout after expanding/collapsing a node - tryLayout(pos: [number,number], dims: [number,number], allowCollapse: boolean = false, chg?: LayoutTreeChg){ + tryLayout(root: LayoutNode, pos: [number,number], dims: [number,number], allowCollapse: boolean = false, + chg?: LayoutTreeChg){ // Create a new LayoutNode tree, keeping the old one in case of layout failure - let tempTree = this.root.cloneNodeTree(chg); + let tempTree = root.cloneNodeTree(chg); let success: boolean; switch (this.options.layoutType){ case 'sqr': success = sqrLayout(tempTree, pos, dims, true, allowCollapse, this.options); break; @@ -50,7 +51,11 @@ export class LayoutTree { case 'sweep': success = sweepLayout(tempTree, pos, dims, true, allowCollapse, this.options); break; } if (success){ - tempTree.copyTreeForRender(this.root); + // Center root in layout area + 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(root); } return success; } @@ -77,6 +82,7 @@ export class LayoutNode { dims: [number, number]; showHeader: boolean; sepSweptArea: SepSweptArea | null; + hidden: boolean; // Used for layout heuristics and info display dCount: number; // Number of descendant leaf nodes depth: number; // Number of ancestor nodes @@ -90,6 +96,7 @@ export class LayoutNode { this.dims = [0,0]; this.showHeader = false; this.sepSweptArea = null; + this.hidden = false; this.dCount = children.length == 0 ? 1 : arraySum(children.map(n => n.dCount)); this.depth = 0; this.empSpc = 0; @@ -155,6 +162,20 @@ export class LayoutNode { node = node.parent; } } + // + static hideUpward(node: LayoutNode){ + if (node.parent != null){ + node.parent.hidden = true; + node.parent.children.filter(n => n != node).forEach(n => LayoutNode.hideDownward(n)); + LayoutNode.hideUpward(node.parent); + } + } + static hideDownward(node: LayoutNode){ + node.hidden = true; + node.children.forEach(n => { + LayoutNode.hideDownward(n) + }); + } } export type LayoutTreeChg = { type: 'expand' | 'collapse'; -- cgit v1.2.3