mirror of
				https://github.com/immich-app/immich.git
				synced 2025-10-31 02:27:08 -04:00 
			
		
		
		
	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:
		
							parent
							
								
									2f8e89c7ec
								
							
						
					
					
						commit
						70a08707d2
					
				| @ -32,6 +32,7 @@ | |||||||
|   let showFilter = $state(false); |   let showFilter = $state(false); | ||||||
|   let isSearchSuggestions = $state(false); |   let isSearchSuggestions = $state(false); | ||||||
|   let selectedId: string | undefined = $state(); |   let selectedId: string | undefined = $state(); | ||||||
|  |   let isFocus = $state(false); | ||||||
| 
 | 
 | ||||||
|   const listboxId = generateId(); |   const listboxId = generateId(); | ||||||
| 
 | 
 | ||||||
| @ -98,7 +99,25 @@ | |||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   const onSubmit = () => { |   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); |     saveSearchTerm(value); | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
| @ -132,10 +151,12 @@ | |||||||
| 
 | 
 | ||||||
|   const openDropdown = () => { |   const openDropdown = () => { | ||||||
|     showSuggestions = true; |     showSuggestions = true; | ||||||
|  |     isFocus = true; | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   const closeDropdown = () => { |   const closeDropdown = () => { | ||||||
|     showSuggestions = false; |     showSuggestions = false; | ||||||
|  |     isFocus = false; | ||||||
|     searchHistoryBox?.clearSelection(); |     searchHistoryBox?.clearSelection(); | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
| @ -143,6 +164,26 @@ | |||||||
|     event.preventDefault(); |     event.preventDefault(); | ||||||
|     onSubmit(); |     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> | </script> | ||||||
| 
 | 
 | ||||||
| <svelte:window | <svelte:window | ||||||
| @ -214,6 +255,21 @@ | |||||||
|     <div class="absolute inset-y-0 {showClearIcon ? 'right-14' : 'right-2'} flex items-center pl-6 transition-all"> |     <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" /> |       <CircleIconButton title={$t('show_search_options')} icon={mdiTune} onclick={onFilterClick} size="20" /> | ||||||
|     </div> |     </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} |     {#if showClearIcon} | ||||||
|       <div class="absolute inset-y-0 right-0 flex items-center pr-2"> |       <div class="absolute inset-y-0 right-0 flex items-center pr-2"> | ||||||
|         <CircleIconButton onclick={onClear} icon={mdiClose} title={$t('clear')} size="20" /> |         <CircleIconButton onclick={onClear} icon={mdiClose} title={$t('clear')} size="20" /> | ||||||
|  | |||||||
| @ -2,7 +2,7 @@ | |||||||
|   import type { SearchLocationFilter } from './search-location-section.svelte'; |   import type { SearchLocationFilter } from './search-location-section.svelte'; | ||||||
|   import type { SearchDisplayFilters } from './search-display-section.svelte'; |   import type { SearchDisplayFilters } from './search-display-section.svelte'; | ||||||
|   import type { SearchDateFilter } from './search-date-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 = { |   export type SearchFilter = { | ||||||
|     query: string; |     query: string; | ||||||
| @ -55,9 +55,18 @@ | |||||||
|     return value === null ? undefined : value; |     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({ |   let filter: SearchFilter = $state({ | ||||||
|     query: 'query' in searchQuery ? searchQuery.query : searchQuery.originalFileName || '', |     query: 'query' in searchQuery ? searchQuery.query : searchQuery.originalFileName || '', | ||||||
|     queryType: 'smart', |     queryType: defaultQueryType(), | ||||||
|     personIds: new SvelteSet('personIds' in searchQuery ? searchQuery.personIds : []), |     personIds: new SvelteSet('personIds' in searchQuery ? searchQuery.personIds : []), | ||||||
|     tagIds: new SvelteSet('tagIds' in searchQuery ? searchQuery.tagIds : []), |     tagIds: new SvelteSet('tagIds' in searchQuery ? searchQuery.tagIds : []), | ||||||
|     location: { |     location: { | ||||||
| @ -90,7 +99,7 @@ | |||||||
|   const resetForm = () => { |   const resetForm = () => { | ||||||
|     filter = { |     filter = { | ||||||
|       query: '', |       query: '', | ||||||
|       queryType: 'smart', |       queryType: defaultQueryType(), // retain from localStorage or default | ||||||
|       personIds: new SvelteSet(), |       personIds: new SvelteSet(), | ||||||
|       tagIds: new SvelteSet(), |       tagIds: new SvelteSet(), | ||||||
|       location: {}, |       location: {}, | ||||||
| @ -142,8 +151,14 @@ | |||||||
| 
 | 
 | ||||||
|   const onsubmit = (event: Event) => { |   const onsubmit = (event: Event) => { | ||||||
|     event.preventDefault(); |     event.preventDefault(); | ||||||
|  |     storeQueryType(filter.queryType); | ||||||
|     search(); |     search(); | ||||||
|   }; |   }; | ||||||
|  | 
 | ||||||
|  |   // Will be called whenever queryType changes, not just onsubmit. | ||||||
|  |   $effect(() => { | ||||||
|  |     storeQueryType(filter.queryType); | ||||||
|  |   }); | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <FullScreenModal icon={mdiTune} width="extra-wide" title={$t('search_options')} {onClose}> | <FullScreenModal icon={mdiTune} width="extra-wide" title={$t('search_options')} {onClose}> | ||||||
|  | |||||||
| @ -119,6 +119,14 @@ export const fallbackLocale = { | |||||||
|   name: 'English (US)', |   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 = [ | export const locales = [ | ||||||
|   { code: 'af-ZA', name: 'Afrikaans (South Africa)' }, |   { code: 'af-ZA', name: 'Afrikaans (South Africa)' }, | ||||||
|   { code: 'sq-AL', name: 'Albanian (Albania)' }, |   { code: 'sq-AL', name: 'Albanian (Albania)' }, | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user