Refactor NavigationMenuGlobalSearch: add input focus handling, enhance entity type selectors with checklists, and improve responsive layout.
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { computed, nextTick, ref, watch } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { BButton, BCheckbox, BField, BInput, BModal, BSelect, BSidebar, BTag } from 'buefy';
|
||||
import SessionUser from '@/components/session/token/SessionUser.vue';
|
||||
@@ -70,6 +70,7 @@ const router = useRouter();
|
||||
const isModalOpen = ref(false);
|
||||
const method = ref<SearchMethod>('POST');
|
||||
const query = ref('');
|
||||
const queryInputRef = ref<unknown>(null);
|
||||
const includeTypes = ref<EntityType[]>([]);
|
||||
const excludeTypes = ref<EntityType[]>([]);
|
||||
const includeAssociations = ref(true);
|
||||
@@ -140,6 +141,30 @@ watch(grouped, (next) => {
|
||||
}
|
||||
});
|
||||
|
||||
const tryFocusAndSelectQuery = () => {
|
||||
const buefyInput = queryInputRef.value as { $el?: HTMLElement; $refs?: { input?: HTMLInputElement } } | null;
|
||||
const input = buefyInput?.$refs?.input
|
||||
?? buefyInput?.$el?.querySelector('input')
|
||||
?? null;
|
||||
if (!input) return;
|
||||
input.focus();
|
||||
input.select();
|
||||
};
|
||||
|
||||
const focusQueryInput = () => {
|
||||
nextTick(() => {
|
||||
tryFocusAndSelectQuery();
|
||||
window.setTimeout(() => tryFocusAndSelectQuery(), 90);
|
||||
});
|
||||
};
|
||||
|
||||
watch(isModalOpen, (open) => {
|
||||
if (!open) return;
|
||||
activeSetting.value = null;
|
||||
settingsOpen.value = false;
|
||||
focusQueryInput();
|
||||
});
|
||||
|
||||
const parseErr = (e: unknown) => {
|
||||
const parsed = SessionUser.functions.parseErrorMessage(e);
|
||||
if (typeof parsed === 'string') return parsed;
|
||||
@@ -421,7 +446,7 @@ const asJson = (v: unknown) => {
|
||||
|
||||
<template v-if="!activeSetting">
|
||||
<b-field grouped group-multiline>
|
||||
<b-input v-model="query" placeholder="Search everything..." icon="search" icon-pack="fas" expanded @keyup.enter="runSearch(0)" />
|
||||
<b-input ref="queryInputRef" v-model="query" placeholder="Search everything..." icon="search" icon-pack="fas" expanded @keyup.enter="runSearch(0)" />
|
||||
<p class="control"><b-button type="is-link" icon-left="search" icon-pack="fas" :loading="loading" @click="runSearch(0)">Search</b-button></p>
|
||||
</b-field>
|
||||
|
||||
@@ -516,7 +541,7 @@ const asJson = (v: unknown) => {
|
||||
</div>
|
||||
|
||||
<div v-if="activeSetting === 'pagination'" class="sidebar-settings-section no-divider">
|
||||
<b-field label="Search method" label-position="on-border" class="mb-2">
|
||||
<b-field v-if="!canCacheManage" label="Search method" label-position="on-border" class="mb-2">
|
||||
<b-select v-model="method" expanded>
|
||||
<option value="POST">POST (JSON body)</option>
|
||||
<option value="GET">GET (query params)</option>
|
||||
@@ -530,17 +555,25 @@ const asJson = (v: unknown) => {
|
||||
</div>
|
||||
|
||||
<div v-if="activeSetting === 'object-types'" class="sidebar-settings-section no-divider">
|
||||
<b-field label="Include entity types" label-position="on-border">
|
||||
<b-select v-model="includeTypes" expanded multiple>
|
||||
<option v-for="t in includeTypeOptions" :key="`inc-${t}`" :value="t">{{ humanize(t) }}</option>
|
||||
</b-select>
|
||||
</b-field>
|
||||
<div class="object-type-grid">
|
||||
<div class="object-type-column">
|
||||
<p class="menu-label mb-2">Include entity types</p>
|
||||
<div class="object-type-checklist">
|
||||
<b-field v-for="t in includeTypeOptions" :key="`inc-${t}`" class="mb-1">
|
||||
<b-checkbox v-model="includeTypes" :native-value="t" size="is-medium">{{ humanize(t) }}</b-checkbox>
|
||||
</b-field>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<b-field label="Exclude entity types" label-position="on-border">
|
||||
<b-select v-model="excludeTypes" expanded multiple>
|
||||
<option v-for="t in excludeTypeOptions" :key="`exc-${t}`" :value="t">{{ humanize(t) }}</option>
|
||||
</b-select>
|
||||
</b-field>
|
||||
<div class="object-type-column">
|
||||
<p class="menu-label mb-2">Exclude entity types</p>
|
||||
<div class="object-type-checklist">
|
||||
<b-field v-for="t in excludeTypeOptions" :key="`exc-${t}`" class="mb-1">
|
||||
<b-checkbox v-model="excludeTypes" :native-value="t" size="is-medium">{{ humanize(t) }}</b-checkbox>
|
||||
</b-field>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="is-flex is-flex-wrap-wrap mt-2">
|
||||
<b-button size="is-small" type="is-light" class="mr-2 mb-2" @click="includeTypes = [...ENTITY_TYPES]; excludeTypes = []">Include all</b-button>
|
||||
@@ -574,11 +607,14 @@ const asJson = (v: unknown) => {
|
||||
<p class="control"><b-button type="is-info" icon-left="redo" icon-pack="fas" :loading="cacheLoading" @click="rebuildCache">Queue rebuild</b-button></p>
|
||||
</b-field>
|
||||
|
||||
<b-field v-if="cacheScope === 'types'" label="Rebuild types" label-position="on-border" class="mt-2">
|
||||
<b-select v-model="cacheTypes" expanded multiple :disabled="cacheLoading">
|
||||
<option v-for="t in ENTITY_TYPES" :key="`cache-${t}`" :value="t">{{ humanize(t) }}</option>
|
||||
</b-select>
|
||||
</b-field>
|
||||
<div v-if="cacheScope === 'types'" class="mt-2">
|
||||
<p class="menu-label mb-2">Rebuild types</p>
|
||||
<div class="object-type-checklist">
|
||||
<b-field v-for="t in ENTITY_TYPES" :key="`cache-${t}`" class="mb-1">
|
||||
<b-checkbox v-model="cacheTypes" :native-value="t" size="is-medium" :disabled="cacheLoading">{{ humanize(t) }}</b-checkbox>
|
||||
</b-field>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p v-if="cacheMsg" class="help is-success mt-1">{{ cacheMsg }}</p>
|
||||
<p v-if="cacheErr" class="help is-danger mt-1">{{ cacheErr }}</p>
|
||||
@@ -639,6 +675,16 @@ const asJson = (v: unknown) => {
|
||||
.sidebar-settings-section { border-top: 1px solid rgba(0, 0, 0, 0.08); margin-top: 8px; padding-top: 10px; }
|
||||
.settings-right-panel { max-height: 68vh; overflow: auto; }
|
||||
.no-divider { border-top: none; margin-top: 0; padding-top: 0; }
|
||||
.object-type-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
|
||||
.object-type-column { min-width: 0; }
|
||||
.object-type-checklist {
|
||||
max-height: 320px;
|
||||
overflow: auto;
|
||||
border: 1px solid rgba(0, 0, 0, 0.08);
|
||||
border-radius: 6px;
|
||||
padding: 8px;
|
||||
background: #fff;
|
||||
}
|
||||
.search-modal-main-pane {
|
||||
flex: 1;
|
||||
padding: 16px;
|
||||
@@ -661,4 +707,7 @@ const asJson = (v: unknown) => {
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
@media (max-width: 980px) {
|
||||
.object-type-grid { grid-template-columns: 1fr; }
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user