diff options
Diffstat (limited to 'src/App.vue')
| -rw-r--r-- | src/App.vue | 436 |
1 files changed, 194 insertions, 242 deletions
diff --git a/src/App.vue b/src/App.vue index b28531a..cf25b18 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,114 +1,114 @@ <script lang="ts"> import {defineComponent, PropType} from 'vue'; -// +// Components import Tile from './components/Tile.vue'; import AncestryBar from './components/AncestryBar.vue'; import TileInfoModal from './components/TileInfoModal.vue'; -import SearchModal from './components/SearchModal.vue'; import HelpModal from './components/HelpModal.vue'; +import SearchModal from './components/SearchModal.vue'; import SettingsPane from './components/SettingsPane.vue'; -// +// Icons +import HelpIcon from './components/icon/HelpIcon.vue'; import SearchIcon from './components/icon/SearchIcon.vue'; import PlayIcon from './components/icon/PlayIcon.vue'; -import HelpIcon from './components/icon/HelpIcon.vue'; import SettingsIcon from './components/icon/SettingsIcon.vue'; -// +// Other import {TolNode, TolNodeRaw, tolFromRaw, getTolMap} from './tol'; import {LayoutNode, initLayoutTree, initLayoutMap, tryLayout} from './layout'; import type {LayoutOptions} from './layout'; import {arraySum, randWeightedChoice} from './util'; -// Import paths lack a .ts or .js extension because .ts makes vue-tsc complain, and .js makes vite complain +// Note: Import paths lack a .ts or .js extension because .ts makes vue-tsc complain, and .js makes vite complain + +// Type representing auto-mode actions +type Action = 'move across' | 'move down' | 'move up' | + 'expand' | 'collapse' | 'expand to view' | 'expand ancestry bar'; +// Used in auto-mode to help avoid action cycles +function getReverseAction(action: Action): Action | null { + let reversePairs: Action[][] = [ + ['move down', 'move up'], + ['expand', 'collapse'], + ['expand to view', 'expand ancestry bar'], + ]; + let pair = reversePairs.find(pair => pair.includes(action)); + if (pair != null){ + return pair[0] == action ? pair[1] : pair[0]; + } else { + return null; + } +} -// Obtain tree-of-life data +// Get tree-of-life data import tolRaw from './tolData.json'; -const tol: TolNode = tolFromRaw(tolRaw); +const tol = tolFromRaw(tolRaw); const tolMap = getTolMap(tol); -// Configurable settings +// Configurable options const defaultLytOpts: LayoutOptions = { tileSpacing: 8, //px - headerSz: 20, //px + headerSz: 22, //px minTileSz: 50, //px maxTileSz: 200, //px + // Layout-algorithm related layoutType: 'sweep', //'sqr' | 'rect' | 'sweep' rectMode: 'auto first-row', //'horz' | 'vert' | 'linear' | 'auto' | 'auto first-row' sweepMode: 'left', //'left' | 'top' | 'shorter' | 'auto' sweptNodesPrio: 'pow-2/3', //'linear' | 'sqrt' | 'pow-2/3' - sweepingToParent: true, + sweepToParent: true, }; const defaultUiOpts = { - // For leaf/non_leaf tile and detached-ancestor components + // For tiles borderRadius: 5, //px shadowNormal: '0 0 2px black', shadowHighlight: '0 0 1px 2px greenyellow', shadowFocused: '0 0 1px 2px orange', - // For leaf and detached-ancestor components - imgTilePadding: 4, //px - imgTileFontSz: 15, //px - imgTileColor: '#fafaf9', - expandableImgTileColor: 'greenyellow', //yellow, greenyellow, turquoise, - // For non-leaf tile-group components - nonLeafBgColors: ['#44403c', '#57534e'], //tiles at depth N use the Nth color, repeating from the start as needed - nonLeafHeaderFontSz: 15, //px - nonLeafHeaderColor: '#fafaf9', - nonLeafHeaderBgColor: '#1c1917', - // For tile-info modal + infoIconSz: 18, //px + infoIconMargin: 2, //px + // For leaf tiles + leafTilePadding: 4, //px + leafHeaderFontSz: 15, //px + leafHeaderColor: '#fafaf9', + leafHeaderExColor: 'greenyellow', //yellow, greenyellow, turquoise, + // For non-leaf tiles + nonleafBgColors: ['#44403c', '#57534e'], //tiles at depth N use the Nth color, repeating from the start as needed + nonleafHeaderFontSz: 15, //px + nonleafHeaderColor: '#fafaf9', + nonleafHeaderBgColor: '#1c1917', + // For other components + appBgColor: '#292524', + tileAreaOffset: 5, //px (space between root tile and display boundary) + ancestryBarSz: defaultLytOpts.minTileSz * 2, //px (breadth of ancestry-bar area) + ancestryBarBgColor: '#44403c', + ancestryTileMargin: 5, //px (gap between detached-ancestor tiles) + ancestryBarScrollGap: 10, //px (gap for ancestry-bar scrollbar, used to prevent overlap with tiles) infoModalImgSz: 200, + autoWaitTime: 500, //ms (time to wait between actions (with their transitions)) // Timing related - transitionDuration: 300, //ms + tileChgDuration: 300, //ms (for tile move/expand/collapse) clickHoldDuration: 400, //ms (duration after mousedown when a click-and-hold is recognised) }; -const defaultOwnOptions = { - tileAreaOffset: 5, //px (space between root tile and display boundary) - ancestryBarSz: defaultLytOpts.minTileSz * 2, //px (breadth of ancestry-bar area) -}; - -// Type representing auto-mode actions -type Action = 'move across' | 'move down' | 'move up' | 'expand' | 'collapse' | 'expand to view' | 'expand ancestry bar'; -// Used in auto-mode to help avoid action cycles -function getReverseAction(action: Action): Action | null { - switch (action){ - case 'move across': - return null; - case 'move down': - return 'move up'; - case 'move up': - return 'move down'; - case 'expand': - return 'collapse'; - case 'collapse': - return 'expand'; - case 'expand to view': - return 'expand ancestry bar'; - case 'expand ancestry bar': - return 'expand to view'; - } -} -// Holds a tree structure representing a subtree of 'tol' to be rendered -// Collects events about tile expansion/collapse and window-resize, and initiates relayout of tiles export default defineComponent({ data(){ let layoutTree = initLayoutTree(tol, 0); return { layoutTree: layoutTree, - activeRoot: layoutTree, + activeRoot: layoutTree, // Differs from layoutTree root when expand-to-view is used 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 + // Modals and settings related + infoModalNode: null as TolNode | null, // Node to display info for, or null + helpOpen: false, searchOpen: false, settingsOpen: false, + // For search and auto-mode + modeRunning: false, lastFocused: null as LayoutNode | null, - animationActive: false, - autoWaitTime: 500, //ms (in auto mode, time to wait after an action ends) - autoPrevAction: null as Action | null, // Used in auto-mode for reducing action cycles - autoPrevActionFail: false, // Used in auto-mode to avoid re-trying a failed expand/collapse - helpOpen: false, + // For auto-mode + autoPrevAction: null as Action | null, // Used to help prevent action cycles + autoPrevActionFail: false, // Used to avoid re-trying a failed expand/collapse // Options lytOpts: {...defaultLytOpts}, uiOpts: {...defaultUiOpts}, - ...defaultOwnOptions, // For window-resize handling width: document.documentElement.clientWidth, height: document.documentElement.clientHeight, @@ -117,9 +117,10 @@ export default defineComponent({ }; }, computed: { - wideArea(): boolean{ + wideArea(): boolean { return this.width >= this.height; }, + // Nodes to show in ancestry-bar, with tol root first detachedAncestors(): LayoutNode[] | null { if (this.activeRoot == this.layoutTree){ return null; @@ -132,166 +133,114 @@ export default defineComponent({ } return ancestors.reverse(); }, + // Placement info for Tile and AncestryBar tileAreaPos(){ - let pos = [this.tileAreaOffset, this.tileAreaOffset] as [number, number]; + let pos = [this.uiOpts.tileAreaOffset, this.uiOpts.tileAreaOffset] as [number, number]; if (this.detachedAncestors != null){ if (this.wideArea){ - pos[0] += this.ancestryBarSz; + pos[0] += this.uiOpts.ancestryBarSz; } else { - pos[1] += this.ancestryBarSz; + pos[1] += this.uiOpts.ancestryBarSz; } } return pos; }, tileAreaDims(){ let dims = [ - this.width - this.tileAreaOffset*2, - this.height - this.tileAreaOffset*2 + this.width - this.uiOpts.tileAreaOffset*2, + this.height - this.uiOpts.tileAreaOffset*2 ] as [number, number]; if (this.detachedAncestors != null){ if (this.wideArea){ - dims[0] -= this.ancestryBarSz; + dims[0] -= this.uiOpts.ancestryBarSz; } else { - dims[1] -= this.ancestryBarSz; + dims[1] -= this.uiOpts.ancestryBarSz; } } return dims; }, ancestryBarDims(): [number, number] { if (this.wideArea){ - return [this.ancestryBarSz, this.height]; + return [this.uiOpts.ancestryBarSz, this.height]; } else { - return [this.width, this.ancestryBarSz]; + return [this.width, this.uiOpts.ancestryBarSz]; } }, - styles(): Record<string,string> { - return { - position: 'absolute', - left: '0', - top: '0', - width: '100vw', // Making this dynamic causes white flashes when resizing - height: '100vh', - backgroundColor: '#292524', - overflow: 'hidden', - }; - }, }, methods: { - onResize(){ - if (!this.resizeThrottled){ - this.width = document.documentElement.clientWidth; - this.height = document.documentElement.clientHeight; - tryLayout(this.activeRoot, this.layoutMap, - this.tileAreaPos, this.tileAreaDims, this.lytOpts, true); - // Prevent re-triggering until after a delay - this.resizeThrottled = true; - setTimeout(() => {this.resizeThrottled = false;}, this.resizeDelay); - } - }, // For tile expand/collapse events - onInnerLeafClicked(layoutNode: LayoutNode){ - let success = tryLayout(this.activeRoot, this.layoutMap, - this.tileAreaPos, this.tileAreaDims, this.lytOpts, false, {type: 'expand', node: layoutNode}); + onLeafClick(layoutNode: LayoutNode){ + let success = tryLayout(this.activeRoot, this.tileAreaPos, this.tileAreaDims, this.lytOpts, + {allowCollapse: false, chg: {type: 'expand', node: layoutNode}, layoutMap: this.layoutMap}); if (!success){ - layoutNode.expandFailFlag = !layoutNode.expandFailFlag; // Triggers failure animation + layoutNode.failFlag = !layoutNode.failFlag; // Triggers failure animation } return success; }, - onInnerHeaderClicked(layoutNode: LayoutNode){ - let oldChildren = layoutNode.children; - let success = tryLayout(this.activeRoot, this.layoutMap, - this.tileAreaPos, this.tileAreaDims, this.lytOpts, false, {type: 'collapse', node: layoutNode}); + onNonleafClick(layoutNode: LayoutNode){ + let success = tryLayout(this.activeRoot, this.tileAreaPos, this.tileAreaDims, this.lytOpts, + {allowCollapse: false, chg: {type: 'collapse', node: layoutNode}, layoutMap: this.layoutMap}); if (!success){ - layoutNode.collapseFailFlag = !layoutNode.collapseFailFlag; // Triggers failure animation + layoutNode.failFlag = !layoutNode.failFlag; // Triggers failure animation } return success; }, - // For expand-to-view events - onInnerLeafClickHeld(layoutNode: LayoutNode){ + // For expand-to-view and ancestry-bar events + onLeafClickHeld(layoutNode: LayoutNode){ if (layoutNode == this.activeRoot){ - console.log('Ignored expand-to-view on root node'); + console.log('Ignored expand-to-view on active-root node'); return; } LayoutNode.hideUpward(layoutNode); this.activeRoot = layoutNode; - tryLayout(this.activeRoot, this.layoutMap, - this.tileAreaPos, this.tileAreaDims, this.lytOpts, true, {type: 'expand', node: layoutNode}); + tryLayout(this.activeRoot, this.tileAreaPos, this.tileAreaDims, this.lytOpts, + {allowCollapse: true, chg: {type: 'expand', node: layoutNode}, layoutMap: this.layoutMap}); }, - onInnerHeaderClickHeld(layoutNode: LayoutNode){ + onNonleafClickHeld(layoutNode: LayoutNode){ if (layoutNode == this.activeRoot){ console.log('Ignored expand-to-view on active-root node'); return; } LayoutNode.hideUpward(layoutNode); this.activeRoot = layoutNode; - tryLayout(this.activeRoot, this.layoutMap, this.tileAreaPos, this.tileAreaDims, this.lytOpts, true); + tryLayout(this.activeRoot, this.tileAreaPos, this.tileAreaDims, this.lytOpts, + {allowCollapse: true, layoutMap: this.layoutMap, }); }, - onDetachedAncestorClicked(layoutNode: LayoutNode){ + onDetachedAncestorClick(layoutNode: LayoutNode){ LayoutNode.showDownward(layoutNode); this.activeRoot = layoutNode; - tryLayout(this.activeRoot, this.layoutMap, this.tileAreaPos, this.tileAreaDims, this.lytOpts, true); + tryLayout(this.activeRoot, this.tileAreaPos, this.tileAreaDims, this.lytOpts, + {allowCollapse: true, layoutMap: this.layoutMap}); }, - // For info modal events - onInnerInfoIconClicked(node: LayoutNode){ - this.closeModesAndSettings(); + // For tile-info events + onInfoIconClick(node: LayoutNode){ + this.resetMode(); this.infoModalNode = node.tolNode; }, - onInfoModalClose(){ - this.infoModalNode = null; - }, - // - onSettingsIconClick(){ - this.closeModesAndSettings(); - this.settingsOpen = true; - }, - onSettingsClose(){ - this.settingsOpen = false; - }, - onLayoutOptionChange(){ - tryLayout(this.activeRoot, this.layoutMap, this.tileAreaPos, this.tileAreaDims, this.lytOpts, true); + // For help events + onHelpIconClick(){ + this.resetMode(); + this.helpOpen = true; }, - // + // For search events onSearchIconClick(){ - this.closeModesAndSettings(); + this.resetMode(); this.searchOpen = true; }, - onSearchClose(){ - this.searchOpen = false; - }, onSearchNode(tolNode: TolNode){ this.searchOpen = false; - this.animationActive = true; + this.modeRunning = true; this.expandToTolNode(tolNode); }, - // - closeModesAndSettings(){ - this.infoModalNode = null; - this.searchOpen = false; - this.helpOpen = false; - this.settingsOpen = false; - this.animationActive = false; - this.setLastFocused(null); - }, - onKeyUp(evt: KeyboardEvent){ - if (evt.key == 'Escape'){ - this.closeModesAndSettings(); - } else if (evt.key == 'F' && evt.ctrlKey){ - if (!this.searchOpen){ - this.onSearchIconClick(); - } else { - (this.$refs.searchModal as InstanceType<typeof SearchModal>).focusInput(); - } - } - }, expandToTolNode(tolNode: TolNode){ - if (!this.animationActive){ + if (!this.modeRunning){ return; } - // Check if searched node is shown + // Check if searched node is displayed let layoutNode = this.layoutMap.get(tolNode.name); if (layoutNode != null && !layoutNode.hidden){ this.setLastFocused(layoutNode); - this.animationActive = false; + this.modeRunning = false; return; } // Get nearest in-layout-tree ancestor @@ -300,28 +249,27 @@ export default defineComponent({ ancestor = ancestor.parent!; } layoutNode = this.layoutMap.get(ancestor.name)!; - // If hidden, expand ancestor in ancestry-bar + // If hidden, expand self/ancestor in ancestry-bar if (layoutNode.hidden){ - // Get self/ancestor in ancestry-bar while (!this.detachedAncestors!.includes(layoutNode)){ ancestor = ancestor.parent!; layoutNode = this.layoutMap.get(ancestor.name)!; } - this.onDetachedAncestorClicked(layoutNode!); - setTimeout(() => this.expandToTolNode(tolNode), this.uiOpts.transitionDuration); + this.onDetachedAncestorClick(layoutNode!); + setTimeout(() => this.expandToTolNode(tolNode), this.uiOpts.tileChgDuration); return; } // Attempt tile-expand - let success = this.onInnerLeafClicked(layoutNode); + let success = this.onLeafClick(layoutNode); if (success){ - setTimeout(() => this.expandToTolNode(tolNode), this.uiOpts.transitionDuration); + setTimeout(() => this.expandToTolNode(tolNode), this.uiOpts.tileChgDuration); return; } // Attempt expand-to-view on ancestor just below activeRoot if (ancestor.name == this.activeRoot.tolNode.name){ console.log('Unable to complete search (not enough room to expand active root)'); - // Happens if screen is very small or node has very many children - this.animationActive = false; + // Note: Only happens if screen is significantly small or node has significantly many children + this.modeRunning = false; return; } while (true){ @@ -331,19 +279,17 @@ export default defineComponent({ ancestor = ancestor.parent!; } layoutNode = this.layoutMap.get(ancestor.name)!; - this.onInnerHeaderClickHeld(layoutNode); - setTimeout(() => this.expandToTolNode(tolNode), this.uiOpts.transitionDuration); - }, - onOverlayClick(){ - this.animationActive = false; + this.onNonleafClickHeld(layoutNode); + setTimeout(() => this.expandToTolNode(tolNode), this.uiOpts.tileChgDuration); }, + // For auto-mode events onPlayIconClick(){ - this.closeModesAndSettings(); - this.animationActive = true; + this.resetMode(); + this.modeRunning = true; this.autoAction(); }, autoAction(){ - if (!this.animationActive){ + if (!this.modeRunning){ this.setLastFocused(null); return; } @@ -356,14 +302,14 @@ export default defineComponent({ layoutNode = layoutNode.children[idx!]; } this.setLastFocused(layoutNode); - setTimeout(this.autoAction, this.autoWaitTime); + setTimeout(this.autoAction, this.uiOpts.autoWaitTime); } else { // Determine available actions let action: Action | null; let actionWeights: {[key: string]: number}; // Maps actions to choice weights let node = this.lastFocused; if (node.children.length == 0){ - actionWeights = {'move across': 1, 'move up': 2, 'expand': 4}; + actionWeights = {'move across': 1, 'move up': 2, 'expand': 3}; // Zero weights for disallowed actions if (node == this.activeRoot || node.parent!.children.length == 1){ actionWeights['move across'] = 0; @@ -377,7 +323,7 @@ export default defineComponent({ } else { actionWeights = { 'move across': 1, 'move down': 2, 'move up': 1, - 'collapse': 1, 'expand to view': 0.5, 'expand ancestry bar': 0.5 + 'collapse': 1, 'expand to view': 1, 'expand ancestry bar': 1 }; // Zero weights for disallowed actions if (node == this.activeRoot || node.parent!.children.length == 1){ @@ -416,12 +362,12 @@ export default defineComponent({ // Perform action this.autoPrevActionFail = false; switch (action){ - case 'move across': + case 'move across': // Bias towards siblings with higher dCount let siblings = node.parent!.children.filter(n => n != node); let siblingWeights = siblings.map(n => n.dCount + 1); this.setLastFocused(siblings[randWeightedChoice(siblingWeights)!]); break; - case 'move down': + case 'move down': // Bias towards children with higher dCount let childWeights = node.children.map(n => n.dCount + 1); this.setLastFocused(node.children[randWeightedChoice(childWeights)!]); break; @@ -429,22 +375,63 @@ export default defineComponent({ this.setLastFocused(node.parent!); break; case 'expand': - this.autoPrevActionFail = !this.onInnerLeafClicked(node); + this.autoPrevActionFail = !this.onLeafClick(node); break; case 'collapse': - this.autoPrevActionFail = !this.onInnerHeaderClicked(node); + this.autoPrevActionFail = !this.onNonleafClick(node); break; case 'expand to view': - this.onInnerHeaderClickHeld(node); + this.onNonleafClickHeld(node); break; case 'expand ancestry bar': - this.onDetachedAncestorClicked(node.parent!); + this.onDetachedAncestorClick(node.parent!); break; } - setTimeout(this.autoAction, this.uiOpts.transitionDuration + this.autoWaitTime); + setTimeout(this.autoAction, this.uiOpts.tileChgDuration + this.uiOpts.autoWaitTime); this.autoPrevAction = action; } }, + // For settings events + onSettingsIconClick(){ + this.resetMode(); + this.settingsOpen = true; + }, + onLayoutOptionChange(){ + tryLayout(this.activeRoot, this.tileAreaPos, this.tileAreaDims, this.lytOpts, + {allowCollapse: true, layoutMap: this.layoutMap}); + }, + // For other events + onResize(){ + if (!this.resizeThrottled){ + this.width = document.documentElement.clientWidth; + this.height = document.documentElement.clientHeight; + tryLayout(this.activeRoot, this.tileAreaPos, this.tileAreaDims, this.lytOpts, + {allowCollapse: true, layoutMap: this.layoutMap}); + // Prevent re-triggering until after a delay + this.resizeThrottled = true; + setTimeout(() => {this.resizeThrottled = false;}, this.resizeDelay); + } + }, + onKeyUp(evt: KeyboardEvent){ + if (evt.key == 'Escape'){ + this.resetMode(); + } else if (evt.key == 'F' && evt.ctrlKey){ // On ctrl-shift-f + if (!this.searchOpen){ + this.onSearchIconClick(); + } else { + (this.$refs.searchModal as InstanceType<typeof SearchModal>).focusInput(); + } + } + }, + // Helper methods + resetMode(){ + this.infoModalNode = null; + this.searchOpen = false; + this.helpOpen = false; + this.settingsOpen = false; + this.modeRunning = false; + this.setLastFocused(null); + }, setLastFocused(node: LayoutNode | null){ if (this.lastFocused != null){ this.lastFocused.hasFocus = false; @@ -454,65 +441,64 @@ export default defineComponent({ node.hasFocus = true; } }, - onHelpIconClick(){ - this.closeModesAndSettings(); - this.helpOpen = true; - }, - onHelpModalClose(){ - this.helpOpen = false; - }, }, created(){ window.addEventListener('resize', this.onResize); window.addEventListener('keyup', this.onKeyUp); - tryLayout(this.activeRoot, this.layoutMap, this.tileAreaPos, this.tileAreaDims, this.lytOpts, true); + tryLayout(this.activeRoot, this.tileAreaPos, this.tileAreaDims, this.lytOpts, + {allowCollapse: true, layoutMap: this.layoutMap}); }, unmounted(){ window.removeEventListener('resize', this.onResize); window.removeEventListener('keyup', this.onKeyUp); }, components: { - Tile, AncestryBar, TileInfoModal, SettingsPane, SearchModal, HelpModal, - SearchIcon, PlayIcon, HelpIcon, SettingsIcon, + Tile, AncestryBar, + HelpIcon, SearchIcon, PlayIcon, SettingsIcon, + TileInfoModal, HelpModal, SearchModal, SettingsPane, }, }); </script> <template> -<div :style="styles"> +<div class="absolute left-0 top-0 w-screen h-screen overflow-hidden" :style="{backgroundColor: uiOpts.appBgColor}"> + <!-- Note: Making the above enclosing div's width/height dynamic seems to cause white flashes when resizing --> <tile :layoutNode="layoutTree" :lytOpts="lytOpts" :uiOpts="uiOpts" - @leaf-clicked="onInnerLeafClicked" @header-clicked="onInnerHeaderClicked" - @leaf-click-held="onInnerLeafClickHeld" @header-click-held="onInnerHeaderClickHeld" - @info-icon-clicked="onInnerInfoIconClicked"/> + @leaf-click="onLeafClick" @nonleaf-click="onNonleafClick" + @leaf-click-held="onLeafClickHeld" @nonleaf-click-held="onNonleafClickHeld" + @info-icon-click="onInfoIconClick"/> <ancestry-bar v-if="detachedAncestors != null" :pos="[0,0]" :dims="ancestryBarDims" :nodes="detachedAncestors" :lytOpts="lytOpts" :uiOpts="uiOpts" - @detached-ancestor-clicked="onDetachedAncestorClicked" @info-icon-clicked="onInnerInfoIconClicked"/> + @detached-ancestor-click="onDetachedAncestorClick" @info-icon-click="onInfoIconClick"/> <!-- Icons --> <help-icon @click="onHelpIconClick" - class="absolute bottom-[6px] left-[6px] w-[18px] h-[18px] text-white/40 hover:text-white hover:cursor-pointer"/> + class="absolute bottom-[6px] left-[6px] w-[18px] h-[18px] + text-white/40 hover:text-white hover:cursor-pointer"/> <search-icon @click="onSearchIconClick" - class="absolute bottom-[6px] left-[30px] w-[18px] h-[18px] text-white/40 hover:text-white hover:cursor-pointer"/> + class="absolute bottom-[6px] left-[30px] w-[18px] h-[18px] + text-white/40 hover:text-white hover:cursor-pointer"/> <play-icon @click="onPlayIconClick" - class="absolute bottom-[6px] left-[54px] w-[18px] h-[18px] text-white/40 hover:text-white hover:cursor-pointer"/> + class="absolute bottom-[6px] left-[54px] w-[18px] h-[18px] + text-white/40 hover:text-white hover:cursor-pointer"/> <!-- Modals --> <transition name="fade"> <tile-info-modal v-if="infoModalNode != null" :tolNode="infoModalNode" :uiOpts="uiOpts" - @info-modal-close="onInfoModalClose"/> + @info-modal-close="infoModalNode = null"/> </transition> <transition name="fade"> - <search-modal v-if="searchOpen" :layoutTree="layoutTree" :tolMap="tolMap" :uiOpts="uiOpts" - @search-close="onSearchClose" @search-node="onSearchNode" ref="searchModal"/> + <search-modal v-if="searchOpen" :tolMap="tolMap" :uiOpts="uiOpts" + @search-close="searchOpen = false" @search-node="onSearchNode" ref="searchModal"/> </transition> <transition name="fade"> - <help-modal v-if="helpOpen" :uiOpts="uiOpts" @help-modal-close="onHelpModalClose"/> + <help-modal v-if="helpOpen" :uiOpts="uiOpts" @help-modal-close="helpOpen = false"/> </transition> <!-- Settings --> <transition name="slide-bottom-right"> <settings-pane v-if="settingsOpen" :lytOpts="lytOpts" :uiOpts="uiOpts" - @settings-close="onSettingsClose" @layout-option-change="onLayoutOptionChange"/> - <!-- outer div prevents transition interference with inner rotate --> + @settings-close="settingsOpen = false" @layout-option-change="onLayoutOptionChange"/> <div v-else class="absolute bottom-0 right-0 w-[100px] h-[100px] invisible"> + <!-- Note: Above enclosing div prevents transition interference with inner rotate --> <div class="absolute bottom-[-50px] right-[-50px] w-[100px] h-[100px] visible -rotate-45 bg-black text-white hover:cursor-pointer" @click="onSettingsIconClick"> <settings-icon class="w-6 h-6 mx-auto mt-2"/> @@ -520,46 +506,12 @@ export default defineComponent({ </div> </transition> <!-- Overlay used to prevent interaction and capture clicks --> - <div :style="{visibility: animationActive ? 'visible' : 'hidden'}" - class="absolute left-0 top-0 w-full h-full" @click="onOverlayClick"></div> + <div :style="{visibility: modeRunning ? 'visible' : 'hidden'}" + class="absolute left-0 top-0 w-full h-full" @click="modeRunning = false"></div> </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); - } -} -.animate-shrink-expand { - animation-name: shrink-expand; - animation-duration: 300ms; - animation-iteration-count: 1; - animation-timing-function: ease-in-out; -} -@keyframes shrink-expand { - from { - transform: translate3d(0,0,0) scale(1, 1); - } - 50% { - transform: translate3d(0,0,0) scale(0.9, 0.9); - } - to { - transform: translate3d(0,0,0) scale(1, 1); - } -} .fade-enter-from, .fade-leave-to { opacity: 0; } |
