aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/App.vue21
-rw-r--r--src/components/SearchModal.vue254
-rw-r--r--src/components/TileInfoModal.vue2
-rw-r--r--src/lib.ts4
4 files changed, 145 insertions, 136 deletions
diff --git a/src/App.vue b/src/App.vue
index a3d0559..1c80acf 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -58,10 +58,11 @@ function getDefaultLytOpts(): LayoutOptions {
function getDefaultUiOpts(lytOpts: LayoutOptions): UiOptions {
let screenSz = getBreakpoint();
// Reused option values
- let textColor = '#fafaf9';
- let bgColor = '#292524', bgColorAlt = '#fafaf9',
+ let textColor = '#fafaf9', textColorAlt = '#1c1917';
+ let bgColor = '#292524',
bgColorLight = '#44403c', bgColorDark = '#1c1917',
- bgColorLight2 = '#57534e', bgColorDark2 = '#0e0c0b';
+ bgColorLight2 = '#57534e', bgColorDark2 = '#0e0c0b',
+ bgColorAlt = '#fafaf9', bgColorAltDark = '#a8a29e';
let altColor = '#a3e623', altColorDark = '#65a30d';
let accentColor = '#f59e0b';
let scrollGap = getScrollBarWidth();
@@ -69,12 +70,14 @@ function getDefaultUiOpts(lytOpts: LayoutOptions): UiOptions {
return {
// Shared coloring/sizing
textColor,
+ textColorAlt,
bgColor,
- bgColorAlt,
bgColorLight,
bgColorDark,
bgColorLight2,
bgColorDark2,
+ bgColorAlt,
+ bgColorAltDark,
altColor,
altColorDark,
borderRadius: 5, // px
@@ -97,7 +100,7 @@ function getDefaultUiOpts(lytOpts: LayoutOptions): UiOptions {
autoActionDelay: 500, // ms
// Other
useReducedTree: false,
- searchSuggLimit: 5,
+ searchSuggLimit: 10,
searchJumpMode: false,
tutorialSkip: false,
disabledActions: new Set() as Set<Action>,
@@ -258,7 +261,7 @@ export default defineComponent({
let response = await fetch(urlPath);
responseObj = await response.json();
} catch (error){
- console.log('ERROR: Unable to retreive tol-node data', error);
+ console.log('Error with retreiving tol-node data: ' + error);
return false;
}
Object.getOwnPropertyNames(responseObj).forEach(n => {this.tolMap.set(n, responseObj[n])});
@@ -356,7 +359,7 @@ export default defineComponent({
let response = await fetch(urlPath);
responseObj = await response.json();
} catch (error){
- console.log('ERROR: Unable to retreive tol-node data', error);
+ console.log('Error with retreiving tol-node data: ' + error);
return false;
}
Object.getOwnPropertyNames(responseObj).forEach(n => {this.tolMap.set(n, responseObj[n])});
@@ -815,7 +818,7 @@ export default defineComponent({
let response = await fetch(urlPath);
responseObj = await response.json();
} catch (error) {
- console.log('ERROR: Unable to retrieve tree data', error);
+ console.log('Error with retrieving tree data: ' + error);
return;
}
// Get root node name
@@ -1016,7 +1019,7 @@ export default defineComponent({
</div>
<!-- Modals -->
<transition name="fade">
- <search-modal v-if="searchOpen" :tolMap="tolMap" :uiOpts="uiOpts" ref="searchModal"
+ <search-modal v-if="searchOpen" :tolMap="tolMap" :lytOpts="lytOpts" :uiOpts="uiOpts" ref="searchModal"
@close="searchOpen = false" @search="onSearch" @info-click="onInfoClick" @setting-chg="onSettingChg" />
</transition>
<transition name="fade">
diff --git a/src/components/SearchModal.vue b/src/components/SearchModal.vue
index 166c9f8..d14173f 100644
--- a/src/components/SearchModal.vue
+++ b/src/components/SearchModal.vue
@@ -1,36 +1,37 @@
<script lang="ts">
+
import {defineComponent, PropType} from 'vue';
import SearchIcon from './icon/SearchIcon.vue';
import LogInIcon from './icon/LogInIcon.vue';
import InfoIcon from './icon/InfoIcon.vue';
-import {TolMap, SearchSugg, SearchSuggResponse, UiOptions} from '../lib';
-import {LayoutNode} from '../layout';
+import {TolNode, TolMap, UiOptions, SearchSugg, SearchSuggResponse} from '../lib';
+import {LayoutNode, LayoutOptions} from '../layout';
-// Displays a search box, and sends search requests
export default defineComponent({
+ props: {
+ tolMap: {type: Object as PropType<TolMap>, required: true}, // Added to from a search response
+ lytOpts: {type: Object as PropType<LayoutOptions>, required: true},
+ uiOpts: {type: Object as PropType<UiOptions>, required: true},
+ },
data(){
return {
- searchSuggs: [] as SearchSugg[],
- searchHasMoreSuggs: false,
- focusedSuggIdx: null as null | number, // Denotes a search-suggestion selected using the arrow keys
+ // For search-suggestion requests
lastSuggReqTime: 0, // Set when a search-suggestions request is initiated
- pendingSuggReqUrl: '', // Used by a pendingSuggReq callback to use the latest user input
+ pendingSuggReqUrl: '', // Used by a search-suggestion requester to use the latest user input
pendingDelayedSuggReq: 0, // Set via setTimeout() for a non-initial search-suggestions request
+ // Search-suggestion data
+ searchSuggs: [] as SearchSugg[],
+ searchHadMoreSuggs: false,
+ // Other
+ focusedSuggIdx: null as null | number, // Denotes a search-suggestion selected using the arrow keys
};
},
- props: {
- tolMap: {type: Object as PropType<TolMap>, required: true},
- uiOpts: {type: Object as PropType<UiOptions>, required: true},
- },
computed: {
- infoIconStyles(): Record<string,string> {
- let size = '18px';
+ styles(): Record<string,string> {
return {
- width: size,
- height: size,
- minWidth: size,
- minHeight: size,
- margin: '2px',
+ backgroundColor: this.uiOpts.bgColorAlt,
+ borderRadius: this.uiOpts.borderRadius + 'px',
+ boxShadow: this.uiOpts.shadowNormal,
};
},
suggDisplayStrings(): [string, string, string][] {
@@ -46,7 +47,7 @@ export default defineComponent({
} else {
strings = [input, '', ''];
}
- // Indicate a distinct canonical-name
+ // Indicate any distinct canonical-name
if (sugg.canonicalName != null){
strings[2] += ` (aka ${sugg.canonicalName})`;
}
@@ -57,64 +58,13 @@ export default defineComponent({
},
},
methods: {
- onCloseClick(evt: Event){
- if (evt.target == this.$el){
- this.$emit('close');
- }
- },
- onSearch(){
- if (this.focusedSuggIdx == null){
- this.resolveSearch((this.$refs.searchInput as HTMLInputElement).value.toLowerCase())
- } else {
- let sugg = this.searchSuggs[this.focusedSuggIdx]
- this.resolveSearch(sugg.canonicalName || sugg.name);
- }
- },
- onSearchModeChg(){
- this.uiOpts.searchJumpMode = !this.uiOpts.searchJumpMode;
- this.$emit('setting-chg', 'searchJumpMode');
- },
- resolveSearch(tolNodeName: string){
- if (tolNodeName == ''){
- return;
- }
- // Asks server for nodes in parent-chain, updates tolMap, then emits search event
- let url = new URL(window.location.href);
- url.pathname = '/data/chain';
- url.search = '?name=' + encodeURIComponent(tolNodeName);
- url.search += (this.uiOpts.useReducedTree ? '&tree=reduced' : '');
- fetch(url.toString())
- .then(response => response.json())
- .then(obj => {
- let keys = Object.getOwnPropertyNames(obj);
- if (keys.length > 0){
- keys.forEach(key => {
- if (!this.tolMap.has(key)){
- this.tolMap.set(key, obj[key])
- }
- });
- this.$emit('search', tolNodeName);
- } else {
- // Trigger failure animation
- let input = this.$refs.searchInput as HTMLInputElement;
- input.classList.remove('animate-red-then-fade');
- input.offsetWidth; // Triggers reflow
- input.classList.add('animate-red-then-fade');
- }
- })
- .catch(error => {
- console.log('ERROR loading tolnode chain', error);
- });
- },
- focusInput(){
- (this.$refs.searchInput as HTMLInputElement).focus();
- },
- onInput(){
+ // Search-suggestion events
+ async onInput(){
let input = this.$refs.searchInput as HTMLInputElement;
// Check for empty input
if (input.value.length == 0){
this.searchSuggs = [];
- this.searchHasMoreSuggs = false;
+ this.searchHadMoreSuggs = false;
this.focusedSuggIdx = null;
return;
}
@@ -124,67 +74,120 @@ export default defineComponent({
url.search = '?name=' + encodeURIComponent(input.value);
url.search += this.uiOpts.useReducedTree ? '&tree=reduced' : '';
url.search += '&limit=' + this.uiOpts.searchSuggLimit;
- // Query server, delaying/ignoring if a request was recently sent
+ // Query server, delaying/skipping if a request was recently sent
this.pendingSuggReqUrl = url.toString();
- let doReq = () => {
- return fetch(this.pendingSuggReqUrl)
- .then(response => {
- if (!response.ok){
- throw new Error('Server response not OK')
- }
- return response.json()
- })
- .then((results: SearchSuggResponse) => {
- this.searchSuggs = results.suggs;
- this.searchHasMoreSuggs = results.hasMore;
- this.focusedSuggIdx = null;
- })
- .catch(error => {
- console.error('Error encountered during fetch operation', error);
- });
+ let doReq = async () => {
+ let responseObj: SearchSuggResponse;
+ try {
+ let response = await fetch(this.pendingSuggReqUrl);
+ responseObj = await response.json();
+ } catch (error){
+ console.log('Error with getting search suggestions from server: ' + error)
+ return;
+ }
+ this.searchSuggs = responseObj.suggs;
+ this.searchHadMoreSuggs = responseObj.hasMore;
+ this.focusedSuggIdx = null;
};
let currentTime = new Date().getTime();
if (this.lastSuggReqTime == 0){
this.lastSuggReqTime = currentTime;
- doReq().finally(() => {
- if (this.lastSuggReqTime == currentTime){
- this.lastSuggReqTime = 0;
- }
- });
+ await doReq();
+ if (this.lastSuggReqTime == currentTime){
+ this.lastSuggReqTime = 0;
+ }
} else if (this.pendingDelayedSuggReq == 0){
this.lastSuggReqTime = currentTime;
- this.pendingDelayedSuggReq = setTimeout(() => {
+ this.pendingDelayedSuggReq = setTimeout(async () => {
this.pendingDelayedSuggReq = 0;
- doReq().finally(() => {
- if (this.lastSuggReqTime == currentTime){
- this.lastSuggReqTime = 0;
- }
- });
+ await doReq();
+ if (this.lastSuggReqTime == currentTime){
+ this.lastSuggReqTime = 0;
+ }
}, 300);
}
},
+ onInfoIconClick(nodeName: string){
+ this.$emit('info-click', nodeName);
+ },
onDownKey(){
- // Select next search-suggestion, if any
+ // Select next search-suggestion, possibly cycling back to top
if (this.searchSuggs.length > 0){
if (this.focusedSuggIdx == null){
this.focusedSuggIdx = 0;
+ } else if (this.focusedSuggIdx == this.searchSuggs.length - 1){
+ this.focusedSuggIdx = null;
} else {
- this.focusedSuggIdx = Math.min(this.focusedSuggIdx + 1, this.searchSuggs.length - 1);
+ this.focusedSuggIdx += 1;
}
}
},
onUpKey(){
- // Select previous search-suggestion, or cancel selection
- if (this.focusedSuggIdx != null){
- if (this.focusedSuggIdx == 0){
+ // Select previous search-suggestion, possibly cycling to bottom
+ if (this.searchSuggs.length > 0){
+ if (this.focusedSuggIdx == null){
+ this.focusedSuggIdx = this.searchSuggs.length - 1;
+ } else if (this.focusedSuggIdx == 0){
this.focusedSuggIdx = null;
} else {
this.focusedSuggIdx -= 1;
}
}
},
- onInfoIconClick(nodeName: string){
- this.$emit('info-click', nodeName);
+ // Search events
+ onSearch(){
+ if (this.focusedSuggIdx == null){
+ this.resolveSearch((this.$refs.searchInput as HTMLInputElement).value.toLowerCase())
+ } else {
+ let sugg = this.searchSuggs[this.focusedSuggIdx]
+ this.resolveSearch(sugg.canonicalName || sugg.name);
+ }
+ },
+ async resolveSearch(tolNodeName: string){
+ if (tolNodeName == ''){
+ return;
+ }
+ // Ask server for nodes in parent-chain, updates tolMap, then emits search event
+ let url = new URL(window.location.href);
+ url.pathname = '/data/chain';
+ url.search = '?name=' + encodeURIComponent(tolNodeName);
+ url.search += (this.uiOpts.useReducedTree ? '&tree=reduced' : '');
+ let responseObj: {[x: string]: TolNode};
+ try {
+ let response = await fetch(url.toString());
+ responseObj = await response.json();
+ } catch (error){
+ console.log('Error with getting tolnode chain: ' + error);
+ return;
+ }
+ let keys = Object.getOwnPropertyNames(responseObj);
+ if (keys.length > 0){
+ keys.forEach(key => {
+ if (!this.tolMap.has(key)){
+ this.tolMap.set(key, responseObj[key])
+ }
+ });
+ this.$emit('search', tolNodeName);
+ } else {
+ // Trigger failure animation
+ let input = this.$refs.searchInput as HTMLInputElement;
+ input.classList.remove('animate-red-then-fade');
+ input.offsetWidth; // Triggers reflow
+ input.classList.add('animate-red-then-fade');
+ }
+ },
+ // Other
+ onSearchModeChg(){
+ this.uiOpts.searchJumpMode = !this.uiOpts.searchJumpMode;
+ this.$emit('setting-chg', 'searchJumpMode');
+ },
+ onClose(evt: Event){
+ if (evt.target == this.$el){
+ this.$emit('close');
+ }
+ },
+ focusInput(){ // Used from external component
+ (this.$refs.searchInput as HTMLInputElement).focus();
},
},
mounted(){
@@ -196,34 +199,35 @@ export default defineComponent({
</script>
<template>
-<div class="fixed left-0 top-0 w-full h-full bg-black/40" @click="onCloseClick">
- <div class="absolute left-1/2 -translate-x-1/2 top-1/2 -translate-y-1/2
- bg-stone-50 rounded-md shadow shadow-black flex">
- <div class="relative">
- <input type="text" class="block border p-1 m-2" ref="searchInput"
- @keyup.enter="onSearch" @keyup.esc="onCloseClick"
+<div class="fixed left-0 top-0 w-full h-full bg-black/40" @click="onClose">
+ <div class="absolute left-1/2 -translate-x-1/2 top-1/4 w-3/4 -translate-y-1/2 flex" :style="styles">
+ <div class="relative grow m-2">
+ <input type="text" class="block border p-1 w-full" ref="searchInput"
+ @keyup.enter="onSearch" @keyup.esc="onClose"
@input="onInput" @keydown.down.prevent="onDownKey" @keydown.up.prevent="onUpKey"/>
- <div class="absolute top-[100%] w-full">
+ <div class="absolute top-[100%] w-full"
+ :style="{backgroundColor: uiOpts.bgColorAlt, color: uiOpts.textColorAlt}">
<div v-for="(sugg, idx) of searchSuggs"
- :style="{backgroundColor: idx == focusedSuggIdx ? '#a3a3a3' : 'white'}"
- class="border p-1 hover:underline hover:cursor-pointer"
+ :style="{backgroundColor: idx == focusedSuggIdx ? uiOpts.bgColorAltDark : uiOpts.bgColorAlt}"
+ class="border p-1 hover:underline hover:cursor-pointer flex"
@click="resolveSearch(sugg.canonicalName || sugg.name)">
- <span>{{suggDisplayStrings[idx][0]}}</span>
- <span class="font-bold">{{suggDisplayStrings[idx][1]}}</span>
- <span>{{suggDisplayStrings[idx][2]}}</span>
- <info-icon :style="infoIconStyles"
- class="float-right text-stone-500 hover:text-stone-900 hover:cursor-pointer"
+ <div class="grow overflow-hidden whitespace-nowrap text-ellipsis">
+ <span>{{suggDisplayStrings[idx][0]}}</span>
+ <span class="font-bold">{{suggDisplayStrings[idx][1]}}</span>
+ <span>{{suggDisplayStrings[idx][2]}}</span>
+ </div>
+ <info-icon class="hover:cursor-pointer my-auto w-5 h-5"
@click.stop="onInfoIconClick(sugg.canonicalName || sugg.name)"/>
</div>
- <div v-if="searchHasMoreSuggs" class="bg-white px-1 text-center border">...</div>
+ <div v-if="searchHadMoreSuggs" class="text-center border">...</div>
</div>
</div>
- <div class="my-auto hover:cursor-pointer hover:brightness-75 rounded border shadow">
+ <div class="my-auto hover:cursor-pointer hover:brightness-75 rounded border shadow">
<search-icon @click.stop="onSearch" class="block w-8 h-8"/>
</div>
<div class="my-auto mx-2 hover:cursor-pointer hover:brightness-75 rounded">
<log-in-icon @click.stop="onSearchModeChg" class="block w-8 h-8"
- :class="uiOpts.searchJumpMode ? 'text-stone-500' : 'text-stone-300'"/>
+ :class="uiOpts.searchJumpMode ? 'opacity-100' : 'opacity-30'"/>
</div>
</div>
</div>
diff --git a/src/components/TileInfoModal.vue b/src/components/TileInfoModal.vue
index c8f8047..88c6065 100644
--- a/src/components/TileInfoModal.vue
+++ b/src/components/TileInfoModal.vue
@@ -112,7 +112,7 @@ export default defineComponent({
let response = await fetch(url.toString());
responseObj = await response.json();
} catch (error){
- console.log("ERROR: Unable to retrieve data from server")
+ console.log("Error with retrieving data from server: " + error);
return;
}
// Set fields from response
diff --git a/src/lib.ts b/src/lib.ts
index b3e6acc..0183519 100644
--- a/src/lib.ts
+++ b/src/lib.ts
@@ -68,12 +68,14 @@ export type Action =
export type UiOptions = {
// Shared coloring/sizing
textColor: string, // CSS color
+ textColorAlt: string,
bgColor: string,
- bgColorAlt: string,
bgColorLight: string,
bgColorDark: string,
bgColorLight2: string,
bgColorDark2: string,
+ bgColorAlt: string,
+ bgColorAltDark: string,
altColor: string,
altColorDark: string,
borderRadius: number, // CSS border-radius value, in px