diff options
| author | Terry Truong <terry06890@gmail.com> | 2022-04-26 13:53:46 +1000 |
|---|---|---|
| committer | Terry Truong <terry06890@gmail.com> | 2022-04-26 13:53:46 +1000 |
| commit | 04e9444746d3ba8ddcc96d0fd16f1c02adce1389 (patch) | |
| tree | 0f48275709dc676bf8b9ba3c2dbc9f1beeff7b75 /src | |
| parent | e8fab23fe92230c2cb42412bb9ea6040ff14f072 (diff) | |
Have tol data in sqlite db, and add server script that accesses it
Adapt otol-data-converting script to generate otol.db, add server.py
script that provides access to that db, and adapt the app to query the
server for tol data when needed.
Diffstat (limited to 'src')
| -rw-r--r-- | src/App.vue | 110 | ||||
| -rw-r--r-- | src/components/SearchModal.vue | 37 |
2 files changed, 105 insertions, 42 deletions
diff --git a/src/App.vue b/src/App.vue index cb2b1f4..f1e6e2a 100644 --- a/src/App.vue +++ b/src/App.vue @@ -39,9 +39,8 @@ function getReverseAction(action: Action): Action | null { } // Get tree-of-life data -import data from './tolData.json'; -let tolMap: TolMap = data; -const rootName = "[Elaeocarpus williamsianus + Brunellia mexicana]"; +const rootName = "cellular organisms"; +const tolMap: TolMap = {[rootName]: new TolNode()}; // Configurable options const defaultLytOpts: LayoutOptions = { @@ -117,6 +116,8 @@ export default defineComponent({ height: document.documentElement.clientHeight, resizeThrottled: false, resizeDelay: 50, //ms (increasing to 100 seems to cause resize-skipping when opening browser mobile-view) + // Other + excessTolNodeThreshold: 1000, // Threshold where excess tolMap entries are removed (done on tile collapse) }; }, computed: { @@ -173,15 +174,32 @@ export default defineComponent({ methods: { // For tile expand/collapse events onLeafClick(layoutNode: LayoutNode){ - let success = tryLayout(this.activeRoot, this.tileAreaPos, this.tileAreaDims, this.lytOpts, { - allowCollapse: false, - chg: {type: 'expand', node: layoutNode, tolMap: this.tolMap}, - layoutMap: this.layoutMap - }); - if (!success){ - layoutNode.failFlag = !layoutNode.failFlag; // Triggers failure animation + let doExpansion = () => { + let success = tryLayout(this.activeRoot, this.tileAreaPos, this.tileAreaDims, this.lytOpts, { + allowCollapse: false, + chg: {type: 'expand', node: layoutNode, tolMap: this.tolMap}, + layoutMap: this.layoutMap + }); + if (!success){ + layoutNode.failFlag = !layoutNode.failFlag; // Triggers failure animation + } + return success; + }; + // Check if data for node-to-expand exists, getting from server if needed + let tolNode = this.tolMap[layoutNode.name]; + if (tolNode.children[0] in this.tolMap == false){ + return fetch('/tolnode/' + layoutNode.name + '?type=children') + .then(response => response.json()) + .then(obj => { + Object.getOwnPropertyNames(obj).forEach(key => {this.tolMap[key] = obj[key]}); + doExpansion(); + }) + .catch(error => { + console.log('ERROR loading tolnode data', error); + }); + } else { + return new Promise((resolve, reject) => resolve(doExpansion())); } - return success; }, onNonleafClick(layoutNode: LayoutNode){ let success = tryLayout(this.activeRoot, this.tileAreaPos, this.tileAreaDims, this.lytOpts, { @@ -191,6 +209,19 @@ export default defineComponent({ }); if (!success){ layoutNode.failFlag = !layoutNode.failFlag; // Triggers failure animation + } else { + // Clear out excess nodes when a threshold is reached + let tolNodeNames = Object.getOwnPropertyNames(this.tolMap) + let extraNodes = tolNodeNames.length - this.layoutMap.size; + if (extraNodes > this.excessTolNodeThreshold){ + for (let n of tolNodeNames){ + if (!this.layoutMap.has(n)){ + delete this.tolMap[n]; + } + } + let numRemovedNodes = tolNodeNames.length - Object.getOwnPropertyNames(this.tolMap).length; + console.log(`Cleaned up tolMap (removed ${numRemovedNodes} out of ${tolNodeNames.length})`); + } } return success; }, @@ -271,26 +302,27 @@ export default defineComponent({ return; } // Attempt tile-expand - let success = this.onLeafClick(layoutNode); - if (success){ - setTimeout(() => this.expandToNode(name), this.uiOpts.tileChgDuration); - return; - } - // Attempt expand-to-view on ancestor just below activeRoot - if (layoutNode == this.activeRoot){ - console.log('Unable to complete search (not enough room to expand active root)'); - // Note: Only happens if screen is significantly small or node has significantly many children - this.modeRunning = false; - return; - } - while (true){ - if (layoutNode.parent! == this.activeRoot){ - break; + this.onLeafClick(layoutNode).then(success => { + if (success){ + setTimeout(() => this.expandToNode(name), this.uiOpts.tileChgDuration); + return; } - layoutNode = layoutNode.parent!; - } - this.onNonleafClickHeld(layoutNode); - setTimeout(() => this.expandToNode(name), this.uiOpts.tileChgDuration); + // Attempt expand-to-view on ancestor just below activeRoot + if (layoutNode == this.activeRoot){ + console.log('Unable to complete search (not enough room to expand active root)'); + // Note: Only happens if screen is significantly small or node has significantly many children + this.modeRunning = false; + return; + } + while (true){ + if (layoutNode.parent! == this.activeRoot){ + break; + } + layoutNode = layoutNode.parent!; + } + this.onNonleafClickHeld(layoutNode); + setTimeout(() => this.expandToNode(name), this.uiOpts.tileChgDuration); + }); }, // For auto-mode events onPlayIconClick(){ @@ -385,7 +417,9 @@ export default defineComponent({ this.setLastFocused(node.parent!); break; case 'expand': - this.autoPrevActionFail = !this.onLeafClick(node); + this.onLeafClick(node) + .then(success => this.autoPrevActionFail = !success) + .catch(error => this.autoPrevActionFail = true); break; case 'collapse': this.autoPrevActionFail = !this.onNonleafClick(node); @@ -457,6 +491,20 @@ export default defineComponent({ window.addEventListener('keyup', this.onKeyUp); tryLayout(this.activeRoot, this.tileAreaPos, this.tileAreaDims, this.lytOpts, {allowCollapse: true, layoutMap: this.layoutMap}); + // Get initial tol node data + fetch('/tolnode/' + rootName) + .then(response => response.json()) + .then(obj => { + Object.getOwnPropertyNames(obj).forEach(key => {this.tolMap[key] = obj[key]}); + this.layoutTree = initLayoutTree(this.tolMap, this.layoutTree.name, 0); + this.activeRoot = this.layoutTree; + this.layoutMap = initLayoutMap(this.layoutTree); + tryLayout(this.activeRoot, this.tileAreaPos, this.tileAreaDims, this.lytOpts, + {allowCollapse: true, layoutMap: this.layoutMap}); + }) + .catch(error => { + console.log('ERROR loading initial tolnode data', error); + }); }, unmounted(){ window.removeEventListener('resize', this.onResize); diff --git a/src/components/SearchModal.vue b/src/components/SearchModal.vue index 22b6896..5accd62 100644 --- a/src/components/SearchModal.vue +++ b/src/components/SearchModal.vue @@ -12,21 +12,36 @@ export default defineComponent({ }, methods: { onCloseClick(evt: Event){ - if (evt.target == this.$el || (this.$refs.searchInput as typeof SearchIcon).$el.contains(evt.target)){ + if (evt.target == this.$el || (this.$refs.searchIcon as typeof SearchIcon).$el.contains(evt.target)){ this.$emit('search-close'); } }, onSearchEnter(){ let input = this.$refs.searchInput as HTMLInputElement; - if (this.tolMap.hasOwnProperty(input.value)){ - this.$emit('search-node', input.value); - } else { - input.value = ''; - // Trigger failure animation - input.classList.remove('animate-red-then-fade'); - input.offsetWidth; // Triggers reflow - input.classList.add('animate-red-then-fade'); - } + // Query server + let url = new URL(window.location.href); + url.pathname = '/tolnode/' + input.value; + fetch(url) + .then(response => { + // Search successful. Get nodes in parent-chain, add to tolMap, then emit event. + url.search = '?type=chain'; + fetch(url) + .then(response => response.json()) + .then(obj => { + Object.getOwnPropertyNames(obj).forEach(key => {this.tolMap[key] = obj[key]}); + this.$emit('search-node', input.value); + }) + .catch(error => { + console.log('ERROR loading tolnode chain', error); + }); + }) + .catch(error => { + input.value = ''; + // Trigger failure animation + input.classList.remove('animate-red-then-fade'); + input.offsetWidth; // Triggers reflow + input.classList.add('animate-red-then-fade'); + }); }, focusInput(){ (this.$refs.searchInput as HTMLInputElement).focus(); @@ -46,7 +61,7 @@ export default defineComponent({ bg-stone-50 rounded-md shadow shadow-black flex gap-1"> <input type="text" class="block border" @keyup.enter="onSearchEnter" @keyup.esc="onCloseClick" ref="searchInput"/> - <search-icon @click.stop="onSearchEnter" + <search-icon @click.stop="onSearchEnter" ref="searchIcon" class="block w-6 h-6 ml-1 hover:cursor-pointer hover:bg-stone-200" /> </div> </div> |
