|
|
|
|
@ -9,9 +9,9 @@
|
|
|
|
|
import { generateId } from '$lib/utils/generate-id';
|
|
|
|
|
import { getMetadataSearchQuery } from '$lib/utils/metadata-search';
|
|
|
|
|
import type { MetadataSearchDto, SmartSearchDto } from '@immich/sdk';
|
|
|
|
|
import { IconButton, modalManager } from '@immich/ui';
|
|
|
|
|
import { Button, IconButton, modalManager } from '@immich/ui';
|
|
|
|
|
import { mdiClose, mdiMagnify, mdiTune } from '@mdi/js';
|
|
|
|
|
import { onDestroy, tick } from 'svelte';
|
|
|
|
|
import { onDestroy, onMount, tick } from 'svelte';
|
|
|
|
|
import { t } from 'svelte-i18n';
|
|
|
|
|
import SearchHistoryBox from './search-history-box.svelte';
|
|
|
|
|
|
|
|
|
|
@ -31,6 +31,8 @@
|
|
|
|
|
let isSearchSuggestions = $state(false);
|
|
|
|
|
let selectedId: string | undefined = $state();
|
|
|
|
|
let close: (() => Promise<void>) | undefined;
|
|
|
|
|
let showSearchTypeDropdown = $state(false);
|
|
|
|
|
let currentSearchType = $state('smart');
|
|
|
|
|
|
|
|
|
|
const listboxId = generateId();
|
|
|
|
|
const searchTypeId = generateId();
|
|
|
|
|
@ -70,6 +72,7 @@
|
|
|
|
|
|
|
|
|
|
const onFocusIn = () => {
|
|
|
|
|
searchStore.isSearchEnabled = true;
|
|
|
|
|
getSearchType();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const onFocusOut = () => {
|
|
|
|
|
@ -98,6 +101,9 @@
|
|
|
|
|
const searchResult = await result.onClose;
|
|
|
|
|
close = undefined;
|
|
|
|
|
|
|
|
|
|
// Refresh search type after modal closes
|
|
|
|
|
getSearchType();
|
|
|
|
|
|
|
|
|
|
if (!searchResult) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
@ -139,6 +145,7 @@
|
|
|
|
|
|
|
|
|
|
const onEscape = () => {
|
|
|
|
|
closeDropdown();
|
|
|
|
|
closeSearchTypeDropdown();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const onArrow = async (direction: 1 | -1) => {
|
|
|
|
|
@ -168,6 +175,20 @@
|
|
|
|
|
searchHistoryBox?.clearSelection();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const toggleSearchTypeDropdown = () => {
|
|
|
|
|
showSearchTypeDropdown = !showSearchTypeDropdown;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const closeSearchTypeDropdown = () => {
|
|
|
|
|
showSearchTypeDropdown = false;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const selectSearchType = (type: string) => {
|
|
|
|
|
localStorage.setItem('searchQueryType', type);
|
|
|
|
|
currentSearchType = type;
|
|
|
|
|
showSearchTypeDropdown = false;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const onsubmit = (event: Event) => {
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
onSubmit();
|
|
|
|
|
@ -180,17 +201,18 @@
|
|
|
|
|
case 'metadata':
|
|
|
|
|
case 'description':
|
|
|
|
|
case 'ocr': {
|
|
|
|
|
currentSearchType = searchType;
|
|
|
|
|
return searchType;
|
|
|
|
|
}
|
|
|
|
|
default: {
|
|
|
|
|
currentSearchType = 'smart';
|
|
|
|
|
return 'smart';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getSearchTypeText(): string {
|
|
|
|
|
const searchType = getSearchType();
|
|
|
|
|
switch (searchType) {
|
|
|
|
|
switch (currentSearchType) {
|
|
|
|
|
case 'smart': {
|
|
|
|
|
return $t('context');
|
|
|
|
|
}
|
|
|
|
|
@ -203,8 +225,22 @@
|
|
|
|
|
case 'ocr': {
|
|
|
|
|
return $t('ocr');
|
|
|
|
|
}
|
|
|
|
|
default: {
|
|
|
|
|
return $t('context');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onMount(() => {
|
|
|
|
|
getSearchType();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const searchTypes = [
|
|
|
|
|
{ value: 'smart', label: () => $t('context') },
|
|
|
|
|
{ value: 'metadata', label: () => $t('filename') },
|
|
|
|
|
{ value: 'description', label: () => $t('description') },
|
|
|
|
|
{ value: 'ocr', label: () => $t('ocr') },
|
|
|
|
|
] as const;
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<svelte:document
|
|
|
|
|
@ -293,11 +329,34 @@
|
|
|
|
|
class:max-md:hidden={value}
|
|
|
|
|
class:end-28={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"
|
|
|
|
|
>
|
|
|
|
|
{getSearchTypeText()}
|
|
|
|
|
</p>
|
|
|
|
|
<div class="relative">
|
|
|
|
|
<Button
|
|
|
|
|
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"
|
|
|
|
|
onclick={toggleSearchTypeDropdown}
|
|
|
|
|
aria-expanded={showSearchTypeDropdown}
|
|
|
|
|
aria-haspopup="listbox"
|
|
|
|
|
>
|
|
|
|
|
{getSearchTypeText()}
|
|
|
|
|
</Button>
|
|
|
|
|
|
|
|
|
|
{#if showSearchTypeDropdown}
|
|
|
|
|
<div
|
|
|
|
|
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"
|
|
|
|
|
use:focusOutside={{ onFocusOut: closeSearchTypeDropdown }}
|
|
|
|
|
>
|
|
|
|
|
{#each searchTypes as searchType (searchType.value)}
|
|
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
class="w-full text-left px-3 py-2 text-xs hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors
|
|
|
|
|
{currentSearchType === searchType.value ? 'bg-gray-100 dark:bg-gray-700' : ''}"
|
|
|
|
|
onclick={() => selectSearchType(searchType.value)}
|
|
|
|
|
>
|
|
|
|
|
{searchType.label()}
|
|
|
|
|
</button>
|
|
|
|
|
{/each}
|
|
|
|
|
</div>
|
|
|
|
|
{/if}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
{/if}
|
|
|
|
|
|
|
|
|
|
|