diff options
| author | Terry Truong <terry06890@gmail.com> | 2023-01-29 12:21:55 +1100 |
|---|---|---|
| committer | Terry Truong <terry06890@gmail.com> | 2023-01-29 12:23:13 +1100 |
| commit | 629b9208503369c3f20ceb59685ef49766344093 (patch) | |
| tree | 87071d862358c56ee38756ab94eb04f9c55fd0dc /src/components/SearchModal.vue | |
| parent | 8781fdb2b8c530a6c1531ae9e82221eb062e34fb (diff) | |
Adjust frontend coding style
Add line spacing and section comments
Fix 'Last updated' line in help modal being shown despite overflow
Diffstat (limited to 'src/components/SearchModal.vue')
| -rw-r--r-- | src/components/SearchModal.vue | 55 |
1 files changed, 42 insertions, 13 deletions
diff --git a/src/components/SearchModal.vue b/src/components/SearchModal.vue index 1818529..607587f 100644 --- a/src/components/SearchModal.vue +++ b/src/components/SearchModal.vue @@ -2,12 +2,17 @@ <div class="fixed left-0 top-0 w-full h-full bg-black/40" @click="onClose" ref="rootRef"> <div class="absolute left-1/2 -translate-x-1/2 top-1/4 -translate-y-1/2 min-w-3/4 md:min-w-[12cm] flex" :style="styles"> + <!-- Input field --> <input type="text" class="block border p-1 px-2 rounded-l-[inherit] grow" ref="inputRef" @keyup.enter="onSearch" @keyup.esc="onClose" @input="onInput" @keydown.down.prevent="onDownKey" @keydown.up.prevent="onUpKey"/> + + <!-- Search button --> <div class="p-1 hover:cursor-pointer"> <search-icon @click.stop="onSearch" class="w-8 h-8"/> </div> + + <!-- Search suggestions --> <div class="absolute top-[100%] w-full overflow-hidden" :style="suggContainerStyles"> <div v-for="(sugg, idx) of searchSuggs" :key="sugg.name + '|' + sugg.canonicalName" :style="{backgroundColor: idx == focusedSuggIdx ? store.color.bgAltDark : store.color.bgAlt}" @@ -24,6 +29,8 @@ </div> <div v-if="searchHadMoreSuggs" class="text-center">• • •</div> </div> + + <!-- Options --> <label :style="animateLabelStyles" class="flex gap-1"> <input type="checkbox" v-model="store.searchJumpMode" @change="emit('setting-chg', 'searchJumpMode')"/> <div class="text-sm">Jump to result</div> @@ -34,6 +41,7 @@ <script setup lang="ts"> import {ref, computed, onMounted, onUnmounted, PropType} from 'vue'; + import SearchIcon from './icon/SearchIcon.vue'; import InfoIcon from './icon/InfoIcon.vue'; import {TolNode, TolMap} from '../tol'; @@ -41,28 +49,28 @@ import {LayoutNode, LayoutMap} from '../layout'; import {queryServer, SearchSugg, SearchSuggResponse} from '../lib'; import {useStore} from '../store'; -// Refs const rootRef = ref(null as HTMLDivElement | null); const inputRef = ref(null as HTMLInputElement | null); -// Global store const store = useStore(); -// Props + events const props = defineProps({ lytMap: {type: Object as PropType<LayoutMap>, required: true}, // Used to check if a searched-for node exists activeRoot: {type: Object as PropType<LayoutNode>, required: true}, // Sent to server to reduce response size tolMap: {type: Object as PropType<TolMap>, required: true}, // Upon a search response, gets new nodes added }); + const emit = defineEmits(['search', 'close', 'info-click', 'setting-chg', 'net-wait', 'net-get']); -// Search-suggestion data +// ========== Search-suggestion data ========== + const searchSuggs = ref([] as SearchSugg[]); const searchHadMoreSuggs = ref(false); +const suggsInput = ref(''); // The input that resulted in the current suggestions (used to highlight matching text) + const suggDisplayStrings = computed((): [string, string, string, string][] => { let result: [string, string, string, string][] = []; let input = suggsInput.value.toLowerCase(); - // For each SearchSugg for (let sugg of searchSuggs.value){ let idx = sugg.name.indexOf(input); // Split suggestion text into parts before/within/after an input match @@ -72,26 +80,30 @@ const suggDisplayStrings = computed((): [string, string, string, string][] => { } else { strings = [input, '', '', '']; } + // Indicate any distinct canonical-name if (sugg.canonicalName != null){ strings[3] = ` (aka ${sugg.canonicalName})`; } - // + result.push(strings); } return result; }); -const suggsInput = ref(''); // The input that resulted in the current suggestions (used to highlight matching text) + const focusedSuggIdx = ref(null as null | number); // Index of a search-suggestion selected using the arrow keys -// For search-suggestion requests +// ========== For search-suggestion requests ========== + const lastSuggReqTime = ref(0); // Set when a search-suggestions request is initiated const pendingSuggReqParams = ref(null as null | URLSearchParams); // Used by a search-suggestion requester to request with the latest user input const pendingDelayedSuggReq = ref(0); // Set via setTimeout() for a non-initial search-suggestions request const pendingSuggInput = ref(''); // Used to remember what input triggered a suggestions request + async function onInput(){ let input = inputRef.value!; + // Check for empty input if (input.value.length == 0){ searchSuggs.value = []; @@ -99,6 +111,7 @@ async function onInput(){ focusedSuggIdx.value = null; return; } + // Get URL params to use for querying search-suggestions let urlParams = new URLSearchParams({ type: 'sugg', @@ -106,6 +119,7 @@ async function onInput(){ limit: String(store.searchSuggLimit), tree: store.tree, }); + // Query server, delaying/skipping if a request was recently sent pendingSuggReqParams.value = urlParams; pendingSuggInput.value = input.value; @@ -119,6 +133,7 @@ async function onInput(){ searchSuggs.value = responseObj.suggs; searchHadMoreSuggs.value = responseObj.hasMore; suggsInput.value = suggInput; + // Auto-select first result if present if (searchSuggs.value.length > 0){ focusedSuggIdx.value = 0; @@ -145,7 +160,8 @@ async function onInput(){ } } -// For search events +// ========== For search events ========== + function onSearch(){ if (focusedSuggIdx.value == null){ let input = inputRef.value!.value.toLowerCase(); @@ -155,15 +171,18 @@ function onSearch(){ resolveSearch(sugg.canonicalName || sugg.name); } } + async function resolveSearch(tolNodeName: string){ if (tolNodeName == ''){ return; } + // Check if the node data is already here if (props.lytMap.has(tolNodeName)){ emit('search', tolNodeName); return; } + // Ask server for nodes in parent-chain, updates tolMap, then emits search event let urlParams = new URLSearchParams({ type: 'node', @@ -195,28 +214,33 @@ async function resolveSearch(tolNodeName: string){ } } -// More event handling +// ========== More event handling ========== + function onClose(evt: Event){ if (evt.target == rootRef.value){ emit('close'); } } + function onDownKey(){ if (focusedSuggIdx.value != null){ focusedSuggIdx.value = (focusedSuggIdx.value + 1) % searchSuggs.value.length; } } + function onUpKey(){ if (focusedSuggIdx.value != null){ focusedSuggIdx.value = (focusedSuggIdx.value - 1 + searchSuggs.value.length) % searchSuggs.value.length; // The addition after '-1' is to avoid becoming negative } } + function onInfoIconClick(nodeName: string){ emit('info-click', nodeName); } -// For keyboard shortcuts +// ========== For keyboard shortcuts ========== + function onKeyDown(evt: KeyboardEvent){ if (store.disableShortcuts){ return; @@ -226,13 +250,16 @@ function onKeyDown(evt: KeyboardEvent){ inputRef.value!.focus(); } } + onMounted(() => window.addEventListener('keydown', onKeyDown)) onUnmounted(() => window.removeEventListener('keydown', onKeyDown)) -// Focus input on mount +// ========== Focus input on mount ========== + onMounted(() => inputRef.value!.focus()) -// Styles +// ========== For styling ========== + const styles = computed((): Record<string,string> => { let br = store.borderRadius; return { @@ -241,6 +268,7 @@ const styles = computed((): Record<string,string> => { boxShadow: store.shadowNormal, }; }); + const suggContainerStyles = computed((): Record<string,string> => { let br = store.borderRadius; return { @@ -249,6 +277,7 @@ const suggContainerStyles = computed((): Record<string,string> => { borderRadius: `0 0 ${br}px ${br}px`, }; }); + const animateLabelStyles = computed(() => ({ position: 'absolute', top: -store.lytOpts.headerSz - 2 + 'px', |
