diff options
| author | Terry Truong <terry06890@gmail.com> | 2022-03-20 20:38:03 +1100 |
|---|---|---|
| committer | Terry Truong <terry06890@gmail.com> | 2022-03-20 20:38:03 +1100 |
| commit | 9f3492dad0a37361ca796e8c4de2bfc32328b554 (patch) | |
| tree | 8a29153e5dc90d534e8cace63c52677d7f71ecb2 | |
| parent | 3063a9a83cc9c8a5aa026e0bfc0393170188833a (diff) | |
Add animation on expand/collapse failure
| -rw-r--r-- | src/components/Tile.vue | 69 | ||||
| -rw-r--r-- | src/components/TileTree.vue | 56 |
2 files changed, 83 insertions, 42 deletions
diff --git a/src/components/Tile.vue b/src/components/Tile.vue index 0c596ed..444ee9a 100644 --- a/src/components/Tile.vue +++ b/src/components/Tile.vue @@ -2,27 +2,26 @@ import {defineComponent, PropType} from 'vue'; import {LayoutNode} from '../lib'; -// Configurable settings (integer values specify pixels) +// Configurable settings let options = { - borderRadius: 5, + borderRadius: 5, //px shadowNormal: '0 0 2px black', shadowWithHover: '0 0 1px 2px greenyellow', // For leaf tiles - leafHeaderX: 4, - leafHeaderY: 4, - leafHeaderFontSz: 15, + leafHeaderX: 4, //px + leafHeaderY: 4, //px + leafHeaderFontSz: 15, //px leafHeaderColor: '#fafaf9', expandableLeafHeaderColor: 'greenyellow', //yellow, greenyellow, turquoise, // For non-leaf tile-groups nonLeafBgColors: ['#44403c', '#57534e'], //tiles at depth N use the Nth color, repeating from the start as needed - nonLeafHeaderFontSz: 15, + nonLeafHeaderFontSz: 15, //px nonLeafHeaderColor: '#fafaf9', nonLeafHeaderBgColor: '#1c1917', }; // Component holds a tree-node structure representing a tile or tile-group to be rendered export default defineComponent({ - name: 'tile', // Need this to use self in template props: { layoutNode: {type: Object as PropType<LayoutNode>, required: true}, isRoot: {type: Boolean, default: false}, @@ -44,7 +43,7 @@ export default defineComponent({ return this.layoutNode.children.length == 0; }, isExpandable(){ - return this.layoutNode.tolNode.children.length > this.layoutNode.children; + return this.layoutNode.tolNode.children.length > this.layoutNode.children.length; }, showHeader(){ return (this.layoutNode.showHeader && !this.layoutNode.sepSweptArea) || @@ -167,53 +166,65 @@ export default defineComponent({ methods: { // For tile expansion and collapse onLeafClick(){ - this.$emit('leaf-clicked', this.layoutNode); - // Increase z-index and hide overflow during transition + this.$emit('leaf-clicked', {layoutNode: this.layoutNode, domNode: this.$el}); + (this.$refs.leaf as Element).classList.replace('shadow-highlight', 'shadow-normal'); + // Temporary changes during transition this.zIdx = 1; this.overflow = 'hidden'; - setTimeout(() => {this.zIdx = 0; this.overflow = 'visible'}, this.transitionDuration); + setTimeout(() => { + this.zIdx = 0; + this.overflow = 'visible'; + }, this.transitionDuration); }, - onInnerLeafClicked(node: LayoutNode){ - this.$emit('leaf-clicked', node); + onInnerLeafClicked(data: {layoutNode: LayoutNode, domNode: HTMLElement}){ + this.$emit('leaf-clicked', data); }, onHeaderClick(){ - this.$emit('header-clicked', this.layoutNode); - // Increase z-index and hide overflow during transition + this.$emit('header-clicked', {layoutNode: this.layoutNode, domNode: this.$el}); + (this.$refs.nonLeaf as Element).classList.replace('shadow-highlight', 'shadow-normal'); + // Temporary changes during transition this.zIdx = 1; this.overflow = 'hidden'; - setTimeout(() => {this.zIdx = 0; this.overflow = 'visible'}, this.transitionDuration); + setTimeout(() => { + this.zIdx = 0; + this.overflow = 'visible'; + }, this.transitionDuration); }, - onInnerHeaderClicked(node: LayoutNode){ - this.$emit('header-clicked', node); + onInnerHeaderClicked(data: {layoutNode: LayoutNode, domNode: HTMLElement}){ + this.$emit('header-clicked', data); }, // For coloured-outlines on hovered-over leaf-tiles or non-leaf-headers - onMouseEnter(evt){ + onMouseEnter(evt: Event){ if (!this.isLeaf){ - this.$refs.nonLeaf.classList.replace('shadow-normal', 'shadow-highlight'); - if (this.$refs.sepSweptArea != null){ - this.$refs.sepSweptArea.classList.replace('shadow-normal', 'shadow-highlight'); + (this.$refs.nonLeaf as Element).classList.replace('shadow-normal', 'shadow-highlight'); + let sepSweptArea = (this.$refs.sepSweptArea as Element | null); + if (sepSweptArea != null){ + sepSweptArea.classList.replace('shadow-normal', 'shadow-highlight'); } } else if (this.isExpandable){ - evt.target.classList.replace('shadow-normal', 'shadow-highlight'); + (evt.target as Element).classList.replace('shadow-normal', 'shadow-highlight'); } }, - onMouseLeave(evt){ + onMouseLeave(evt: Event){ if (!this.isLeaf){ - this.$refs.nonLeaf.classList.replace('shadow-highlight', 'shadow-normal'); - if (this.$refs.sepSweptArea != null){ - this.$refs.sepSweptArea.classList.replace('shadow-highlight', 'shadow-normal'); + (this.$refs.nonLeaf as Element).classList.replace('shadow-highlight', 'shadow-normal'); + let sepSweptArea = this.$refs.sepSweptArea as Element | null; + if (sepSweptArea != null){ + sepSweptArea.classList.replace('shadow-highlight', 'shadow-normal'); } } else if (this.isExpandable){ - evt.target.classList.replace('shadow-highlight', 'shadow-normal'); + (evt.target as Element).classList.replace('shadow-highlight', 'shadow-normal'); } }, }, + name: 'tile', // Need this to use self in template + emits: ['leaf-clicked', 'header-clicked'], }); </script> <template> <div :style="tileStyles"> - <div v-if="isLeaf" :style="leafStyles" + <div v-if="isLeaf" :style="leafStyles" ref="leaf" :class="['shadow-normal'].concat(isExpandable ? ['hover:cursor-pointer'] : [])" @click="onLeafClick" @mouseenter="onMouseEnter" @mouseleave="onMouseLeave"> <div :style="{borderRadius: options.borderRadius + 'px'}" class="scrim-upper-half"/> diff --git a/src/components/TileTree.vue b/src/components/TileTree.vue index f0d8f07..85516c9 100644 --- a/src/components/TileTree.vue +++ b/src/components/TileTree.vue @@ -18,12 +18,12 @@ function preprocessTol(node: any): any { } const tol: TolNode = preprocessTol(tolRaw); -// Configurable settings (integer values specify pixels) +// Configurable settings let layoutOptions: LayoutOptions = { - tileSpacing: 8, - headerSz: 20, - minTileSz: 50, - maxTileSz: 200, + tileSpacing: 8, //px + headerSz: 20, //px + minTileSz: 50, //px + maxTileSz: 200, //px layoutType: 'sweep', //'sqr' | 'rect' | 'sweep' rectMode: 'auto', //'horz' | 'vert' | 'linear' | 'auto' sweepMode: 'left', //'left' | 'top' | 'shorter' | 'auto' @@ -63,18 +63,28 @@ export default defineComponent({ setTimeout(() => {this.resizeThrottled = false;}, otherOptions.resizeDelay); } }, - onInnerLeafClicked(clickedNode: LayoutNode){ - if (clickedNode.tolNode.children.length == 0){ - console.log('Tile-to-expand has no children'); + onInnerLeafClicked({layoutNode, domNode}: {layoutNode: LayoutNode, domNode: HTMLElement}){ + if (layoutNode.tolNode.children.length == 0){ + //console.log('Tile to expand has no children'); return; } - if (!this.layoutTree.tryLayoutOnExpand([0,0], [this.width,this.height], clickedNode)){ - console.log('Unable to layout tree'); + let success = this.layoutTree.tryLayoutOnExpand([0,0], [this.width,this.height], layoutNode); + if (!success){ + // Trigger failure animation + domNode.classList.remove('animate-expand-shrink'); + domNode.offsetWidth; // Triggers reflow + domNode.classList.add('animate-expand-shrink'); + //console.log('Unable to layout tree'); } }, - onInnerHeaderClicked(clickedNode: LayoutNode){ - if (!this.layoutTree.tryLayoutOnCollapse([0,0], [this.width,this.height], clickedNode)){ - console.log('Unable to layout tree'); + onInnerHeaderClicked({layoutNode, domNode}: {layoutNode: LayoutNode, domNode: HTMLElement}){ + let success = this.layoutTree.tryLayoutOnCollapse([0,0], [this.width,this.height], layoutNode); + if (!success){ + // Trigger failure animation + domNode.classList.remove('animate-expand-shrink'); + domNode.offsetWidth; // Triggers reflow + domNode.classList.add('animate-expand-shrink'); + //console.log('Unable to layout tree'); } }, }, @@ -101,3 +111,23 @@ export default defineComponent({ @leaf-clicked="onInnerLeafClicked" @header-clicked="onInnerHeaderClicked"/> </div> </template> + +<style> +.animate-expand-shrink { + animation-name: expand-shrink; + animation-duration: 300ms; + animation-iteration-count: 1; + animation-timing-function: ease-in-out; +} +@keyframes expand-shrink { + from { + transform: scale(1, 1); + } + 50% { + transform: scale(1.1, 1.1); + } + to { + transform: scale(1, 1); + } +} +</style> |
