feat(web): remember search context (#16614)

* Retain search context in LocalStorage.

* Remove debug logging

* Prettier

* Added QueryType and VALID_QUERY_TYPES to $lib/constants

* Prettier

* Renamed VALID_QUERY_TYPES to fit the codestyle.

Ran prettier

* show current search type on search bar

* fix: linting

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
This commit is contained in:
Adam O'neill 2025-03-10 03:20:25 +00:00 committed by GitHub
parent 2f8e89c7ec
commit 70a08707d2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 83 additions and 4 deletions

View File

@ -32,6 +32,7 @@
let showFilter = $state(false);
let isSearchSuggestions = $state(false);
let selectedId: string | undefined = $state();
let isFocus = $state(false);
const listboxId = generateId();
@ -98,7 +99,25 @@
};
const onSubmit = () => {
handlePromiseError(handleSearch({ query: value }));
const searchType = getSearchType();
let payload: SmartSearchDto | MetadataSearchDto = {} as SmartSearchDto | MetadataSearchDto;
switch (searchType) {
case 'smart': {
payload = { query: value } as SmartSearchDto;
break;
}
case 'metadata': {
payload = { originalFileName: value } as MetadataSearchDto;
break;
}
case 'description': {
payload = { description: value } as MetadataSearchDto;
break;
}
}
handlePromiseError(handleSearch(payload));
saveSearchTerm(value);
};
@ -132,10 +151,12 @@
const openDropdown = () => {
showSuggestions = true;
isFocus = true;
};
const closeDropdown = () => {
showSuggestions = false;
isFocus = false;
searchHistoryBox?.clearSelection();
};
@ -143,6 +164,26 @@
event.preventDefault();
onSubmit();
};
function getSearchType(): 'smart' | 'metadata' | 'description' {
const t = localStorage.getItem('searchQueryType');
return t === 'smart' || t === 'description' ? t : 'metadata';
}
function getSearchTypeText(): string {
const searchType = getSearchType();
switch (searchType) {
case 'smart': {
return $t('context');
}
case 'metadata': {
return $t('filename');
}
case 'description': {
return $t('description');
}
}
}
</script>
<svelte:window
@ -214,6 +255,21 @@
<div class="absolute inset-y-0 {showClearIcon ? 'right-14' : 'right-2'} flex items-center pl-6 transition-all">
<CircleIconButton title={$t('show_search_options')} icon={mdiTune} onclick={onFilterClick} size="20" />
</div>
{#if isFocus}
<div
class="absolute inset-y-0 flex items-center"
class:right-16={isFocus}
class:right-28={isFocus && value.length > 0}
>
<p
class="bg-immich-primary text-white dark:bg-immich-dark-primary/90 dark:text-black/75 rounded-full px-3 py-1 text-xs z-10"
>
{getSearchTypeText()}
</p>
</div>
{/if}
{#if showClearIcon}
<div class="absolute inset-y-0 right-0 flex items-center pr-2">
<CircleIconButton onclick={onClear} icon={mdiClose} title={$t('clear')} size="20" />

View File

@ -2,7 +2,7 @@
import type { SearchLocationFilter } from './search-location-section.svelte';
import type { SearchDisplayFilters } from './search-display-section.svelte';
import type { SearchDateFilter } from './search-date-section.svelte';
import { MediaType } from '$lib/constants';
import { MediaType, QueryType, validQueryTypes } from '$lib/constants';
export type SearchFilter = {
query: string;
@ -55,9 +55,18 @@
return value === null ? undefined : value;
}
function storeQueryType(type: SearchFilter['queryType']) {
localStorage.setItem('searchQueryType', type);
}
function defaultQueryType(): QueryType {
const storedQueryType = localStorage.getItem('searchQueryType') as QueryType;
return validQueryTypes.has(storedQueryType) ? storedQueryType : QueryType.SMART;
}
let filter: SearchFilter = $state({
query: 'query' in searchQuery ? searchQuery.query : searchQuery.originalFileName || '',
queryType: 'smart',
queryType: defaultQueryType(),
personIds: new SvelteSet('personIds' in searchQuery ? searchQuery.personIds : []),
tagIds: new SvelteSet('tagIds' in searchQuery ? searchQuery.tagIds : []),
location: {
@ -90,7 +99,7 @@
const resetForm = () => {
filter = {
query: '',
queryType: 'smart',
queryType: defaultQueryType(), // retain from localStorage or default
personIds: new SvelteSet(),
tagIds: new SvelteSet(),
location: {},
@ -142,8 +151,14 @@
const onsubmit = (event: Event) => {
event.preventDefault();
storeQueryType(filter.queryType);
search();
};
// Will be called whenever queryType changes, not just onsubmit.
$effect(() => {
storeQueryType(filter.queryType);
});
</script>
<FullScreenModal icon={mdiTune} width="extra-wide" title={$t('search_options')} {onClose}>

View File

@ -119,6 +119,14 @@ export const fallbackLocale = {
name: 'English (US)',
};
export enum QueryType {
SMART = 'smart',
METADATA = 'metadata',
DESCRIPTION = 'description',
}
export const validQueryTypes = new Set([QueryType.SMART, QueryType.METADATA, QueryType.DESCRIPTION]);
export const locales = [
{ code: 'af-ZA', name: 'Afrikaans (South Africa)' },
{ code: 'sq-AL', name: 'Albanian (Albania)' },