diff options
| author | Terry Truong <terry06890@gmail.com> | 2022-07-05 15:57:46 +1000 |
|---|---|---|
| committer | Terry Truong <terry06890@gmail.com> | 2022-07-05 15:57:46 +1000 |
| commit | 195e1111f286631fc871b49487755eaeafaf03a8 (patch) | |
| tree | 1c319e8b7e291232c5e097a2f45e762ae54b22c1 /src | |
| parent | 4bfd6889b2d6184f5500f47231b8288b4c04df04 (diff) | |
Add loading-from-server indicator
Diffstat (limited to 'src')
| -rw-r--r-- | src/App.vue | 43 | ||||
| -rw-r--r-- | src/components/LoadingModal.vue | 34 | ||||
| -rw-r--r-- | src/components/SearchModal.vue | 18 | ||||
| -rw-r--r-- | src/components/icon/LoaderIcon.vue | 18 | ||||
| -rw-r--r-- | src/lib.ts | 2 |
5 files changed, 101 insertions, 14 deletions
diff --git a/src/App.vue b/src/App.vue index 03a254b..df2b9b7 100644 --- a/src/App.vue +++ b/src/App.vue @@ -48,9 +48,9 @@ </div> <!-- Modals --> <transition name="fade"> - <search-modal v-if="searchOpen" :tolMap="tolMap" :lytOpts="lytOpts" :uiOpts="uiOpts" class="z-10" + <search-modal v-if="searchOpen" :tolMap="tolMap" :lytMap="layoutMap" :lytOpts="lytOpts" :uiOpts="uiOpts" @close="onSearchClose" @search="onSearch" @info-click="onInfoClick" @setting-chg="onSettingChg" - ref="searchModal"/> + @net-wait="primeLoadInd" @net-get="endLoadInd" class="z-10" ref="searchModal"/> </transition> <transition name="fade"> <tile-info-modal v-if="infoModalNodeName != null && infoModalData != null" @@ -63,6 +63,9 @@ </transition> <settings-modal v-if="settingsOpen" :lytOpts="lytOpts" :uiOpts="uiOpts" class="z-10" @close="settingsOpen = false" @reset="onResetSettings" @setting-chg="onSettingChg"/> + <transition name="fade"> + <loading-modal v-if="loadingOpen" :uiOpts="uiOpts" class="z-10"/> + </transition> <!-- Overlay used to capture clicks during auto mode, etc --> <div :style="{visibility: modeRunning != null ? 'visible' : 'hidden'}" class="absolute left-0 top-0 w-full h-full" @click="modeRunning = null"></div> @@ -79,6 +82,7 @@ import SettingsModal from './components/SettingsModal.vue'; import HelpModal from './components/HelpModal.vue'; import AncestryBar from './components/AncestryBar.vue'; import TutorialPane from './components/TutorialPane.vue'; +import LoadingModal from './components/LoadingModal.vue'; import IconButton from './components/IconButton.vue'; // Icons import SearchIcon from './components/icon/SearchIcon.vue'; @@ -91,7 +95,7 @@ import HelpIcon from './components/icon/HelpIcon.vue'; import {TolNode, TolMap} from './tol'; import {LayoutNode, LayoutOptions, LayoutTreeChg, initLayoutTree, initLayoutMap, tryLayout} from './layout'; -import {getServerResponse, InfoResponse, Action, +import {queryServer, InfoResponse, Action, UiOptions, getDefaultLytOpts, getDefaultUiOpts, OptionType} from './lib'; import {arraySum, randWeightedChoice, timeout} from './util'; @@ -136,6 +140,7 @@ export default defineComponent({ searchOpen: false, settingsOpen: false, helpOpen: false, + loadingOpen: false, // For search and auto-mode modeRunning: null as null | 'search' | 'autoMode', lastFocused: null as LayoutNode | null, // Used to un-focus @@ -160,6 +165,7 @@ export default defineComponent({ tutPaneInTransition: false, // Other justInitialised: false, // Used to skip transition for the tile initially loaded from server + pendingLoadingRevealHdlr: 0, // Used to delay showing the loading modal changedSweepToParent: false, // Set during search animation for efficiency excessTolNodeThreshold: 1000, // Threshold where excess tolMap entries get removed }; @@ -319,7 +325,7 @@ export default defineComponent({ if (!this.tolMap.has(tolNode.children[0])){ let urlParams = 'type=node&name=' + encodeURIComponent(layoutNode.name); urlParams += '&tree=' + this.uiOpts.tree; - let responseObj: {[x: string]: TolNode} = await getServerResponse(urlParams); + let responseObj: {[x: string]: TolNode} = await this.loadFromServer(urlParams); if (responseObj == null){ return false; } @@ -414,7 +420,7 @@ export default defineComponent({ if (!this.tolMap.has(tolNode.children[0])){ let urlParams = 'type=node&name=' + encodeURIComponent(layoutNode.name); urlParams += '&tree=' + this.uiOpts.tree; - let responseObj: {[x: string]: TolNode} = await getServerResponse(urlParams); + let responseObj: {[x: string]: TolNode} = await this.loadFromServer(urlParams); if (responseObj == null){ return false; } @@ -483,7 +489,7 @@ export default defineComponent({ // Query server for tol-node info let urlParams = 'type=info&name=' + encodeURIComponent(nodeName); urlParams += '&tree=' + this.uiOpts.tree; - let responseObj: InfoResponse = await getServerResponse(urlParams); + let responseObj: InfoResponse = await this.loadFromServer(urlParams); if (responseObj == null){ return; } @@ -791,6 +797,27 @@ export default defineComponent({ this.tutTriggerFlag = !this.tutTriggerFlag; } }, + // For the loading-indicator + async loadFromServer(urlParams: string){ // Like queryServer(), but enables the loading indicator + this.primeLoadInd(); + let responseObj = await queryServer(urlParams); + this.endLoadInd(); + return responseObj; + }, + primeLoadInd(){ + if (this.pendingLoadingRevealHdlr == 0){ + this.pendingLoadingRevealHdlr = setTimeout(() => { + this.loadingOpen = true; + }, 300); + } + }, + endLoadInd(){ + clearTimeout(this.pendingLoadingRevealHdlr); + this.pendingLoadingRevealHdlr = 0; + if (this.loadingOpen){ + this.loadingOpen = false; + } + }, // For other events async onResize(){ // Handle event if not recently done @@ -865,7 +892,7 @@ export default defineComponent({ // Query server let urlParams = 'type=node'; urlParams += '&tree=' + this.uiOpts.tree; - let responseObj: {[x: string]: TolNode} = await getServerResponse(urlParams); + let responseObj: {[x: string]: TolNode} = await this.loadFromServer(urlParams); if (responseObj == null){ return; } @@ -1027,7 +1054,7 @@ export default defineComponent({ components: { Tile, TutorialPane, AncestryBar, IconButton, SearchIcon, PlayIcon, PauseIcon, SettingsIcon, HelpIcon, - TileInfoModal, SearchModal, SettingsModal, HelpModal, + TileInfoModal, SearchModal, SettingsModal, HelpModal, LoadingModal, }, }); </script> diff --git a/src/components/LoadingModal.vue b/src/components/LoadingModal.vue new file mode 100644 index 0000000..6ef7a97 --- /dev/null +++ b/src/components/LoadingModal.vue @@ -0,0 +1,34 @@ +<template> +<div class="fixed left-0 top-0 w-full h-full bg-black/40"> + <div class="absolute left-1/2 -translate-x-1/2 top-1/2 -translate-y-1/2 + flex items-center py-3 px-3 gap-2" :style="styles"> + <loader-icon class="block w-12 h-12 animate-[spin_6s_linear_infinite]"/> + <div class="whitespace-nowrap">Querying server ...</div> + </div> +</div> +</template> + +<script lang="ts"> +import {defineComponent, PropType} from 'vue'; +import LoaderIcon from './icon/LoaderIcon.vue'; +import {UiOptions} from '../lib'; + +export default defineComponent({ + props: { + uiOpts: {type: Object as PropType<UiOptions>, required: true}, + }, + computed: { + styles(): Record<string,string> { + return { + color: this.uiOpts.textColor, + backgroundColor: this.uiOpts.bgColorDark2, + borderRadius: this.uiOpts.borderRadius + 'px', + boxShadow: this.uiOpts.shadowNormal, + }; + }, + }, + components: { + LoaderIcon, + }, +}); +</script> diff --git a/src/components/SearchModal.vue b/src/components/SearchModal.vue index 55e50a1..e9abef5 100644 --- a/src/components/SearchModal.vue +++ b/src/components/SearchModal.vue @@ -37,11 +37,12 @@ import SearchIcon from './icon/SearchIcon.vue'; import LogInIcon from './icon/LogInIcon.vue'; import InfoIcon from './icon/InfoIcon.vue'; import {TolNode, TolMap} from '../tol'; -import {LayoutNode, LayoutOptions} from '../layout'; -import {getServerResponse, SearchSugg, SearchSuggResponse, UiOptions} from '../lib'; +import {LayoutNode, LayoutMap, LayoutOptions} from '../layout'; +import {queryServer, SearchSugg, SearchSuggResponse, UiOptions} from '../lib'; export default defineComponent({ props: { + lytMap: {type: Object as PropType<LayoutMap>, required: true}, // Used to check if a searched-for node exists tolMap: {type: Object as PropType<TolMap>, required: true}, // Upon a search response, gets new nodes added lytOpts: {type: Object as PropType<LayoutOptions>, required: true}, uiOpts: {type: Object as PropType<UiOptions>, required: true}, @@ -131,7 +132,7 @@ export default defineComponent({ let doReq = async () => { let suggInput = this.pendingSuggInput; let responseObj: SearchSuggResponse = - await getServerResponse(this.pendingSuggReqParams); + await queryServer(this.pendingSuggReqParams); if (responseObj == null){ return; } @@ -191,10 +192,17 @@ export default defineComponent({ if (tolNodeName == ''){ return; } + // Check if the node has already been retrieved + if (this.lytMap.has(tolNodeName)){ + this.$emit('search', tolNodeName); + return; + } // Ask server for nodes in parent-chain, updates tolMap, then emits search event let urlParams = 'type=node&toroot=true&name=' + encodeURIComponent(tolNodeName); urlParams += '&tree=' + this.uiOpts.tree; - let responseObj: {[x: string]: TolNode} = await getServerResponse(urlParams); + this.$emit('net-wait'); // Allows the parent component to show a loading-indicator + let responseObj: {[x: string]: TolNode} = await queryServer(urlParams); + this.$emit('net-get'); if (responseObj == null){ return; } @@ -232,6 +240,6 @@ export default defineComponent({ (this.$refs.searchInput as HTMLInputElement).focus(); }, components: {SearchIcon, InfoIcon, LogInIcon, }, - emits: ['search', 'close', 'info-click', 'setting-chg', ], + emits: ['search', 'close', 'info-click', 'setting-chg', 'net-wait', 'net-get', ], }); </script> diff --git a/src/components/icon/LoaderIcon.vue b/src/components/icon/LoaderIcon.vue new file mode 100644 index 0000000..cd5093b --- /dev/null +++ b/src/components/icon/LoaderIcon.vue @@ -0,0 +1,18 @@ +<template> +<svg viewBox="0 0 24 24" fill="none" + stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> + <line x1="12" y1="2" x2="12" y2="6"></line> + <line x1="12" y1="18" x2="12" y2="22"></line> + <line x1="4.93" y1="4.93" x2="7.76" y2="7.76"></line> + <line x1="16.24" y1="16.24" x2="19.07" y2="19.07"></line> + <line x1="2" y1="12" x2="6" y2="12"></line> + <line x1="18" y1="12" x2="22" y2="12"></line> + <line x1="4.93" y1="19.07" x2="7.76" y2="16.24"></line> + <line x1="16.24" y1="7.76" x2="19.07" y2="4.93"></line> +</svg> +</template> + +<script lang="ts"> +import {defineComponent, PropType} from 'vue'; +export default defineComponent({}); +</script> @@ -8,7 +8,7 @@ import {getBreakpoint, Breakpoint, getScrollBarWidth, onTouchDevice} from './uti // For server requests const SERVER_URL = 'http://localhost:8000/cgi-bin/data.py' -export async function getServerResponse(params: string){ +export async function queryServer(params: string){ // Construct URL let url = new URL(SERVER_URL); url.search = params; |
