mirror of
https://github.com/immich-app/immich.git
synced 2025-06-16 21:38:28 +02:00
refactor: search filter modal (#18159)
This commit is contained in:
parent
eace0f716d
commit
8db666bc38
2 changed files with 49 additions and 53 deletions
web/src/lib
|
@ -1,19 +1,20 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { AppRoute } from '$lib/constants';
|
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { searchStore } from '$lib/stores/search.svelte';
|
|
||||||
import { mdiClose, mdiMagnify, mdiTune } from '@mdi/js';
|
|
||||||
import SearchHistoryBox from './search-history-box.svelte';
|
|
||||||
import SearchFilterModal from './search-filter-modal.svelte';
|
|
||||||
import type { MetadataSearchDto, SmartSearchDto } from '@immich/sdk';
|
|
||||||
import { getMetadataSearchQuery } from '$lib/utils/metadata-search';
|
|
||||||
import { handlePromiseError } from '$lib/utils';
|
|
||||||
import { shortcuts } from '$lib/actions/shortcut';
|
|
||||||
import { focusOutside } from '$lib/actions/focus-outside';
|
import { focusOutside } from '$lib/actions/focus-outside';
|
||||||
|
import { shortcuts } from '$lib/actions/shortcut';
|
||||||
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
|
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
|
||||||
import { t } from 'svelte-i18n';
|
import { AppRoute } from '$lib/constants';
|
||||||
|
import { modalManager } from '$lib/managers/modal-manager.svelte';
|
||||||
|
import SearchFilterModal from '$lib/modals/SearchFilterModal.svelte';
|
||||||
|
import { searchStore } from '$lib/stores/search.svelte';
|
||||||
|
import { handlePromiseError } from '$lib/utils';
|
||||||
import { generateId } from '$lib/utils/generate-id';
|
import { generateId } from '$lib/utils/generate-id';
|
||||||
|
import { getMetadataSearchQuery } from '$lib/utils/metadata-search';
|
||||||
|
import type { MetadataSearchDto, SmartSearchDto } from '@immich/sdk';
|
||||||
|
import { mdiClose, mdiMagnify, mdiTune } from '@mdi/js';
|
||||||
import { onDestroy, tick } from 'svelte';
|
import { onDestroy, tick } from 'svelte';
|
||||||
|
import { t } from 'svelte-i18n';
|
||||||
|
import SearchHistoryBox from './search-history-box.svelte';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
value?: string;
|
value?: string;
|
||||||
|
@ -28,10 +29,10 @@
|
||||||
let input = $state<HTMLInputElement>();
|
let input = $state<HTMLInputElement>();
|
||||||
let searchHistoryBox = $state<ReturnType<typeof SearchHistoryBox>>();
|
let searchHistoryBox = $state<ReturnType<typeof SearchHistoryBox>>();
|
||||||
let showSuggestions = $state(false);
|
let showSuggestions = $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);
|
let isFocus = $state(false);
|
||||||
|
let close: (() => Promise<void>) | undefined;
|
||||||
|
|
||||||
const listboxId = generateId();
|
const listboxId = generateId();
|
||||||
|
|
||||||
|
@ -43,7 +44,6 @@
|
||||||
const params = getMetadataSearchQuery(payload);
|
const params = getMetadataSearchQuery(payload);
|
||||||
|
|
||||||
closeDropdown();
|
closeDropdown();
|
||||||
showFilter = false;
|
|
||||||
searchStore.isSearchEnabled = false;
|
searchStore.isSearchEnabled = false;
|
||||||
await goto(`${AppRoute.SEARCH}?${params}`);
|
await goto(`${AppRoute.SEARCH}?${params}`);
|
||||||
};
|
};
|
||||||
|
@ -83,13 +83,27 @@
|
||||||
await handleSearch(searchPayload);
|
await handleSearch(searchPayload);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onFilterClick = () => {
|
const onFilterClick = async () => {
|
||||||
showFilter = !showFilter;
|
|
||||||
value = '';
|
value = '';
|
||||||
|
|
||||||
if (showFilter) {
|
if (close) {
|
||||||
closeDropdown();
|
await close();
|
||||||
|
close = undefined;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const result = modalManager.open(SearchFilterModal, { searchQuery });
|
||||||
|
close = result.close;
|
||||||
|
closeDropdown();
|
||||||
|
|
||||||
|
const searchResult = await result.onClose;
|
||||||
|
close = undefined;
|
||||||
|
|
||||||
|
if (!searchResult) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await handleSearch(searchResult);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onSubmit = () => {
|
const onSubmit = () => {
|
||||||
|
@ -122,7 +136,6 @@
|
||||||
|
|
||||||
const onEscape = () => {
|
const onEscape = () => {
|
||||||
closeDropdown();
|
closeDropdown();
|
||||||
showFilter = false;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const onArrow = async (direction: 1 | -1) => {
|
const onArrow = async (direction: 1 | -1) => {
|
||||||
|
@ -221,9 +234,7 @@
|
||||||
class="w-full transition-all border-2 px-14 py-4 max-md:py-2 text-immich-fg/75 dark:text-immich-dark-fg
|
class="w-full transition-all border-2 px-14 py-4 max-md:py-2 text-immich-fg/75 dark:text-immich-dark-fg
|
||||||
{grayTheme ? 'dark:bg-immich-dark-gray' : 'dark:bg-immich-dark-bg'}
|
{grayTheme ? 'dark:bg-immich-dark-gray' : 'dark:bg-immich-dark-bg'}
|
||||||
{showSuggestions && isSearchSuggestions ? 'rounded-t-3xl' : 'rounded-3xl bg-gray-200'}
|
{showSuggestions && isSearchSuggestions ? 'rounded-t-3xl' : 'rounded-3xl bg-gray-200'}
|
||||||
{searchStore.isSearchEnabled && !showFilter
|
{searchStore.isSearchEnabled ? 'border-gray-200 dark:border-gray-700 bg-white' : 'border-transparent'}"
|
||||||
? 'border-gray-200 dark:border-gray-700 bg-white'
|
|
||||||
: 'border-transparent'}"
|
|
||||||
placeholder={$t('search_your_photos')}
|
placeholder={$t('search_your_photos')}
|
||||||
required
|
required
|
||||||
pattern="^(?!m:$).*$"
|
pattern="^(?!m:$).*$"
|
||||||
|
@ -231,7 +242,6 @@
|
||||||
bind:this={input}
|
bind:this={input}
|
||||||
onfocus={openDropdown}
|
onfocus={openDropdown}
|
||||||
oninput={onInput}
|
oninput={onInput}
|
||||||
disabled={showFilter}
|
|
||||||
role="combobox"
|
role="combobox"
|
||||||
aria-controls={listboxId}
|
aria-controls={listboxId}
|
||||||
aria-activedescendant={selectedId ?? ''}
|
aria-activedescendant={selectedId ?? ''}
|
||||||
|
@ -285,22 +295,7 @@
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<div class="absolute inset-y-0 start-0 flex items-center ps-2">
|
<div class="absolute inset-y-0 start-0 flex items-center ps-2">
|
||||||
<CircleIconButton
|
<CircleIconButton type="submit" title={$t('search')} icon={mdiMagnify} size="20" onclick={() => {}} />
|
||||||
type="submit"
|
|
||||||
disabled={showFilter}
|
|
||||||
title={$t('search')}
|
|
||||||
icon={mdiMagnify}
|
|
||||||
size="20"
|
|
||||||
onclick={() => {}}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
{#if showFilter}
|
|
||||||
<SearchFilterModal
|
|
||||||
{searchQuery}
|
|
||||||
onSearch={(payload) => handleSearch(payload)}
|
|
||||||
onClose={() => (showFilter = false)}
|
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
<script lang="ts" module>
|
<script lang="ts" module>
|
||||||
import { MediaType, QueryType, validQueryTypes } from '$lib/constants';
|
import { MediaType, QueryType, validQueryTypes } from '$lib/constants';
|
||||||
import type { SearchDateFilter } from './search-date-section.svelte';
|
import type { SearchDateFilter } from '../components/shared-components/search-bar/search-date-section.svelte';
|
||||||
import type { SearchDisplayFilters } from './search-display-section.svelte';
|
import type { SearchDisplayFilters } from '../components/shared-components/search-bar/search-display-section.svelte';
|
||||||
import type { SearchLocationFilter } from './search-location-section.svelte';
|
import type { SearchLocationFilter } from '../components/shared-components/search-bar/search-location-section.svelte';
|
||||||
|
|
||||||
export type SearchFilter = {
|
export type SearchFilter = {
|
||||||
query: string;
|
query: string;
|
||||||
|
@ -20,6 +20,17 @@
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import FullScreenModal from '$lib/components/shared-components/full-screen-modal.svelte';
|
import FullScreenModal from '$lib/components/shared-components/full-screen-modal.svelte';
|
||||||
|
import SearchCameraSection, {
|
||||||
|
type SearchCameraFilter,
|
||||||
|
} from '$lib/components/shared-components/search-bar/search-camera-section.svelte';
|
||||||
|
import SearchDateSection from '$lib/components/shared-components/search-bar/search-date-section.svelte';
|
||||||
|
import SearchDisplaySection from '$lib/components/shared-components/search-bar/search-display-section.svelte';
|
||||||
|
import SearchLocationSection from '$lib/components/shared-components/search-bar/search-location-section.svelte';
|
||||||
|
import SearchMediaSection from '$lib/components/shared-components/search-bar/search-media-section.svelte';
|
||||||
|
import SearchPeopleSection from '$lib/components/shared-components/search-bar/search-people-section.svelte';
|
||||||
|
import SearchRatingsSection from '$lib/components/shared-components/search-bar/search-ratings-section.svelte';
|
||||||
|
import SearchTagsSection from '$lib/components/shared-components/search-bar/search-tags-section.svelte';
|
||||||
|
import SearchTextSection from '$lib/components/shared-components/search-bar/search-text-section.svelte';
|
||||||
import { preferences } from '$lib/stores/user.store';
|
import { preferences } from '$lib/stores/user.store';
|
||||||
import { parseUtcDate } from '$lib/utils/date-time';
|
import { parseUtcDate } from '$lib/utils/date-time';
|
||||||
import { generateId } from '$lib/utils/generate-id';
|
import { generateId } from '$lib/utils/generate-id';
|
||||||
|
@ -28,23 +39,13 @@
|
||||||
import { mdiTune } from '@mdi/js';
|
import { mdiTune } from '@mdi/js';
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
import { SvelteSet } from 'svelte/reactivity';
|
import { SvelteSet } from 'svelte/reactivity';
|
||||||
import SearchCameraSection, { type SearchCameraFilter } from './search-camera-section.svelte';
|
|
||||||
import SearchDateSection from './search-date-section.svelte';
|
|
||||||
import SearchDisplaySection from './search-display-section.svelte';
|
|
||||||
import SearchLocationSection from './search-location-section.svelte';
|
|
||||||
import SearchMediaSection from './search-media-section.svelte';
|
|
||||||
import SearchPeopleSection from './search-people-section.svelte';
|
|
||||||
import SearchRatingsSection from './search-ratings-section.svelte';
|
|
||||||
import SearchTagsSection from './search-tags-section.svelte';
|
|
||||||
import SearchTextSection from './search-text-section.svelte';
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
searchQuery: MetadataSearchDto | SmartSearchDto;
|
searchQuery: MetadataSearchDto | SmartSearchDto;
|
||||||
onClose: () => void;
|
onClose: (search?: SmartSearchDto | MetadataSearchDto) => void;
|
||||||
onSearch: (search: SmartSearchDto | MetadataSearchDto) => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let { searchQuery, onClose, onSearch }: Props = $props();
|
let { searchQuery, onClose }: Props = $props();
|
||||||
|
|
||||||
const parseOptionalDate = (dateString?: string) => (dateString ? parseUtcDate(dateString) : undefined);
|
const parseOptionalDate = (dateString?: string) => (dateString ? parseUtcDate(dateString) : undefined);
|
||||||
const toStartOfDayDate = (dateString: string) => parseUtcDate(dateString)?.startOf('day').toISODate() || undefined;
|
const toStartOfDayDate = (dateString: string) => parseUtcDate(dateString)?.startOf('day').toISODate() || undefined;
|
||||||
|
@ -141,7 +142,7 @@
|
||||||
rating: filter.rating,
|
rating: filter.rating,
|
||||||
};
|
};
|
||||||
|
|
||||||
onSearch(payload);
|
onClose(payload);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onreset = (event: Event) => {
|
const onreset = (event: Event) => {
|
Loading…
Add table
Add a link
Reference in a new issue