|
9 | 9 | import { generateId } from '$lib/utils/generate-id'; |
10 | 10 | import { getMetadataSearchQuery } from '$lib/utils/metadata-search'; |
11 | 11 | import type { MetadataSearchDto, SmartSearchDto } from '@immich/sdk'; |
12 | | - import { IconButton, modalManager } from '@immich/ui'; |
| 12 | + import { Button, IconButton, modalManager } from '@immich/ui'; |
13 | 13 | import { mdiClose, mdiMagnify, mdiTune } from '@mdi/js'; |
14 | | - import { onDestroy, tick } from 'svelte'; |
| 14 | + import { onDestroy, onMount, tick } from 'svelte'; |
15 | 15 | import { t } from 'svelte-i18n'; |
16 | 16 | import SearchHistoryBox from './search-history-box.svelte'; |
17 | 17 |
|
|
31 | 31 | let isSearchSuggestions = $state(false); |
32 | 32 | let selectedId: string | undefined = $state(); |
33 | 33 | let close: (() => Promise<void>) | undefined; |
| 34 | + let showSearchTypeDropdown = $state(false); |
| 35 | + let currentSearchType = $state('smart'); |
34 | 36 |
|
35 | 37 | const listboxId = generateId(); |
36 | 38 | const searchTypeId = generateId(); |
|
70 | 72 |
|
71 | 73 | const onFocusIn = () => { |
72 | 74 | searchStore.isSearchEnabled = true; |
| 75 | + getSearchType(); |
73 | 76 | }; |
74 | 77 |
|
75 | 78 | const onFocusOut = () => { |
|
98 | 101 | const searchResult = await result.onClose; |
99 | 102 | close = undefined; |
100 | 103 |
|
| 104 | + // Refresh search type after modal closes |
| 105 | + getSearchType(); |
| 106 | +
|
101 | 107 | if (!searchResult) { |
102 | 108 | return; |
103 | 109 | } |
|
139 | 145 |
|
140 | 146 | const onEscape = () => { |
141 | 147 | closeDropdown(); |
| 148 | + closeSearchTypeDropdown(); |
142 | 149 | }; |
143 | 150 |
|
144 | 151 | const onArrow = async (direction: 1 | -1) => { |
|
168 | 175 | searchHistoryBox?.clearSelection(); |
169 | 176 | }; |
170 | 177 |
|
| 178 | + const toggleSearchTypeDropdown = () => { |
| 179 | + showSearchTypeDropdown = !showSearchTypeDropdown; |
| 180 | + }; |
| 181 | +
|
| 182 | + const closeSearchTypeDropdown = () => { |
| 183 | + showSearchTypeDropdown = false; |
| 184 | + }; |
| 185 | +
|
| 186 | + const selectSearchType = (type: string) => { |
| 187 | + localStorage.setItem('searchQueryType', type); |
| 188 | + currentSearchType = type; |
| 189 | + showSearchTypeDropdown = false; |
| 190 | + }; |
| 191 | +
|
171 | 192 | const onsubmit = (event: Event) => { |
172 | 193 | event.preventDefault(); |
173 | 194 | onSubmit(); |
|
180 | 201 | case 'metadata': |
181 | 202 | case 'description': |
182 | 203 | case 'ocr': { |
| 204 | + currentSearchType = searchType; |
183 | 205 | return searchType; |
184 | 206 | } |
185 | 207 | default: { |
| 208 | + currentSearchType = 'smart'; |
186 | 209 | return 'smart'; |
187 | 210 | } |
188 | 211 | } |
189 | 212 | } |
190 | 213 |
|
191 | 214 | function getSearchTypeText(): string { |
192 | | - const searchType = getSearchType(); |
193 | | - switch (searchType) { |
| 215 | + switch (currentSearchType) { |
194 | 216 | case 'smart': { |
195 | 217 | return $t('context'); |
196 | 218 | } |
|
203 | 225 | case 'ocr': { |
204 | 226 | return $t('ocr'); |
205 | 227 | } |
| 228 | + default: { |
| 229 | + return $t('context'); |
| 230 | + } |
206 | 231 | } |
207 | 232 | } |
| 233 | +
|
| 234 | + onMount(() => { |
| 235 | + getSearchType(); |
| 236 | + }); |
| 237 | +
|
| 238 | + const searchTypes = [ |
| 239 | + { value: 'smart', label: () => $t('context') }, |
| 240 | + { value: 'metadata', label: () => $t('filename') }, |
| 241 | + { value: 'description', label: () => $t('description') }, |
| 242 | + { value: 'ocr', label: () => $t('ocr') }, |
| 243 | + ] as const; |
208 | 244 | </script> |
209 | 245 |
|
210 | 246 | <svelte:document |
|
293 | 329 | class:max-md:hidden={value} |
294 | 330 | class:end-28={value.length > 0} |
295 | 331 | > |
296 | | - <p |
297 | | - class="bg-immich-primary text-white dark:bg-immich-dark-primary/90 dark:text-black/75 rounded-full px-3 py-1 text-xs" |
298 | | - > |
299 | | - {getSearchTypeText()} |
300 | | - </p> |
| 332 | + <div class="relative"> |
| 333 | + <Button |
| 334 | + class="bg-immich-primary text-white dark:bg-immich-dark-primary/90 dark:text-black/75 rounded-full px-3 py-1 text-xs hover:opacity-80 transition-opacity cursor-pointer" |
| 335 | + onclick={toggleSearchTypeDropdown} |
| 336 | + aria-expanded={showSearchTypeDropdown} |
| 337 | + aria-haspopup="listbox" |
| 338 | + > |
| 339 | + {getSearchTypeText()} |
| 340 | + </Button> |
| 341 | + |
| 342 | + {#if showSearchTypeDropdown} |
| 343 | + <div |
| 344 | + class="absolute top-full right-0 mt-1 bg-white dark:bg-immich-dark-gray border border-gray-200 dark:border-gray-600 rounded-lg shadow-lg py-1 min-w-32 z-9999" |
| 345 | + use:focusOutside={{ onFocusOut: closeSearchTypeDropdown }} |
| 346 | + > |
| 347 | + {#each searchTypes as searchType (searchType.value)} |
| 348 | + <button |
| 349 | + type="button" |
| 350 | + class="w-full text-left px-3 py-2 text-xs hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors |
| 351 | + {currentSearchType === searchType.value ? 'bg-gray-100 dark:bg-gray-700' : ''}" |
| 352 | + onclick={() => selectSearchType(searchType.value)} |
| 353 | + > |
| 354 | + {searchType.label()} |
| 355 | + </button> |
| 356 | + {/each} |
| 357 | + </div> |
| 358 | + {/if} |
| 359 | + </div> |
301 | 360 | </div> |
302 | 361 | {/if} |
303 | 362 |
|
|
0 commit comments