chore(web): refactor search ui

This commit is contained in:
Ben Beckford 2026-08-12 14:10:07 -07:00
parent b5adec7290
commit 993c757f33
8 changed files with 51 additions and 60 deletions

View file

@ -9,9 +9,7 @@
let filters = $derived(searchManager.filter.date);
let invalid = $derived(filters.takenAfter && filters.takenBefore && filters.takenAfter > filters.takenBefore);
let currentPreset: SearchDatePreset | undefined = $derived(
getSearchDatePreset(filters.takenAfter, filters.takenBefore),
);
let currentPreset = $derived(getSearchDatePreset(filters.takenAfter, filters.takenBefore));
const setPreset = (preset: SearchDatePreset) => {
if (currentPreset === preset) {

View file

@ -28,12 +28,12 @@
import {
getPeople,
getSearchDatePreset,
searchDateTitle,
searchMediaTitle,
searchPeopleTitle,
searchPlacesTitle,
searchTagsTitle,
searchTypeTitle,
getSearchDateTitle,
getSearchMediaTitle,
getSearchPeopleTitle,
getSearchPlacesTitle,
getSearchTagsTitle,
getSearchTypeTitle,
} from './search-bar-utils';
import { onMount } from 'svelte';
import { searchManager } from '$lib/managers/search-manager.svelte';
@ -61,30 +61,30 @@
let searchHistory = $state<SearchHistorySection>();
let activeFilter: string = $state('type');
let peoplePromise: Promise<PersonResponseDto[]> | undefined = $state(undefined);
let people: PersonResponseDto[] | undefined = $state(undefined);
let tagsPromise: Promise<TagResponseDto[]> | undefined = $state(undefined);
let tags: TagResponseDto[] | undefined = $state(undefined);
let activeFilter = $state('type');
let peoplePromise = $state<Promise<PersonResponseDto[]>>();
let people = $state<PersonResponseDto[]>();
let tagsPromise = $state<Promise<TagResponseDto[]>>();
let tags = $state<TagResponseDto[]>();
let typeTitle: string | undefined = $derived(searchTypeTitle(searchManager.filter.queryType));
let peopleTitle: string | undefined = $state(undefined);
let dateTitle: string | undefined = $derived(
searchDateTitle(
let typeTitle = $derived(getSearchTypeTitle(searchManager.filter.queryType));
let peopleTitle = $state<string>();
let dateTitle = $derived(
getSearchDateTitle(
getSearchDatePreset(searchManager.filter.date.takenAfter, searchManager.filter.date.takenBefore),
searchManager.filter.date.takenAfter,
searchManager.filter.date.takenBefore,
),
);
let placesTitle: string | undefined = $derived(
searchPlacesTitle(
let placesTitle = $derived(
getSearchPlacesTitle(
searchManager.filter.location.city,
searchManager.filter.location.state,
searchManager.filter.location.country,
),
);
let tagsTitle: string | undefined = $state(undefined);
let mediaTitle: string | undefined = $derived(searchMediaTitle(searchManager.filter.mediaType));
let tagsTitle = $state<string>();
let mediaTitle = $derived(getSearchMediaTitle(searchManager.filter.mediaType));
let filters = [
{
@ -155,13 +155,13 @@
$effect(() => {
if (people) {
peopleTitle = searchPeopleTitle(people, searchManager.filter.personIds);
peopleTitle = getSearchPeopleTitle(people, searchManager.filter.personIds);
}
});
$effect(() => {
if (tags) {
tagsTitle = searchTagsTitle(tags, searchManager.filter.tagIds!);
tagsTitle = getSearchTagsTitle(tags, searchManager.filter.tagIds!);
}
});

View file

@ -27,6 +27,8 @@
let selectedIndex: number | undefined = $state(undefined);
let element = $state<HTMLDivElement>();
const rootId = $props.id();
export function moveSelection(increment: 1 | -1) {
if (!isSearchSuggestions) {
return;
@ -73,7 +75,7 @@
if (index === undefined) {
return undefined;
}
return `history-section-${index}`;
return `${rootId}-entry-${index}`;
};
</script>

View file

@ -59,9 +59,7 @@
$effect(() => handlePromiseError(updateStates(countryFilter)));
$effect(() => handlePromiseError(updateCities(countryFilter, stateFilter)));
onMount(() => {
void updateCountries();
});
onMount(() => updateCountries());
</script>
<div id="location-selection">

View file

@ -1,6 +1,6 @@
<script lang="ts">
import ImageThumbnail from '$lib/components/assets/thumbnail/ImageThumbnail.svelte';
import { getPeople, searchPeopleTitle } from './search-bar-utils';
import { getPeople, getSearchPeopleTitle } from './search-bar-utils';
import SingleGridRow from '$lib/components/shared-components/SingleGridRow.svelte';
import SearchBar from '$lib/elements/SearchBar.svelte';
import { getPeopleThumbnailUrl } from '$lib/utils';
@ -31,7 +31,7 @@
selectedPeople.add(id);
}
title = searchPeopleTitle(people, selectedPeople);
title = getSearchPeopleTitle(people, selectedPeople);
}
const filterPeople = (list: PersonResponseDto[], name: string) => {

View file

@ -6,7 +6,7 @@
import { onMount } from 'svelte';
import { t } from 'svelte-i18n';
import { mdiClose } from '@mdi/js';
import { searchTagsTitle } from './search-bar-utils';
import { getSearchTagsTitle } from './search-bar-utils';
import { searchManager } from '$lib/managers/search-manager.svelte';
interface Props {
@ -37,7 +37,7 @@
selectedTags.add(option.value);
selectedOption = undefined;
title = searchTagsTitle(allTags, selectedTags);
title = getSearchTagsTitle(allTags, selectedTags);
};
const handleRemove = (tag: string) => {
@ -46,7 +46,7 @@
}
selectedTags.delete(tag);
title = searchTagsTitle(allTags, selectedTags);
title = getSearchTagsTitle(allTags, selectedTags);
};
</script>

View file

@ -1,5 +1,5 @@
<script lang="ts">
import { searchTypeTitle } from './search-bar-utils';
import { getSearchTypeTitle } from './search-bar-utils';
import SearchButton from './SearchButton.svelte';
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
import { searchManager } from '$lib/managers/search-manager.svelte';
@ -23,17 +23,17 @@
</SearchButton>
{/if}
<SearchButton checked active={queryType === 'metadata'} onclick={() => setType('metadata')}>
{searchTypeTitle('metadata')}
{getSearchTypeTitle('metadata')}
</SearchButton>
<SearchButton checked active={queryType === 'description'} onclick={() => setType('description')}>
{searchTypeTitle('description')}
{getSearchTypeTitle('description')}
</SearchButton>
<SearchButton checked active={queryType === 'fullPath'} onclick={() => setType('fullPath')}>
{searchTypeTitle('fullPath')}
{getSearchTypeTitle('fullPath')}
</SearchButton>
{#if featureFlagsManager.value.ocr}
<SearchButton checked active={queryType === 'ocr'} onclick={() => setType('ocr')}>
{searchTypeTitle('ocr')}
{getSearchTypeTitle('ocr')}
</SearchButton>
{/if}
</div>

View file

@ -15,7 +15,7 @@ export enum SearchDatePreset {
export const getSearchDatePreset = (after: DateTime | undefined, before: DateTime | undefined) => {
if (!after && !before) {
return undefined;
return;
}
const start = after?.toMillis();
@ -46,7 +46,7 @@ export const getSearchDateRange = (after: DateTime | undefined, before: DateTime
return start && end ? $t('search_filter_date_interval', { values: { start, end } }) : (start ?? end);
};
export const searchDateTitle = (
export const getSearchDateTitle = (
preset: SearchDatePreset | undefined,
before: DateTime | undefined,
after: DateTime | undefined,
@ -66,12 +66,12 @@ export const searchDateTitle = (
return getSearchDateRange(before, after);
}
default: {
return undefined;
return;
}
}
};
export const searchTypeTitle = (type: string) => {
export const getSearchTypeTitle = (type: string) => {
const $t = get(t);
switch (type) {
case 'metadata': {
@ -87,23 +87,15 @@ export const searchTypeTitle = (type: string) => {
return $t('ocr');
}
default: {
return undefined;
return;
}
}
};
export const searchPlacesTitle = (city?: string, state?: string, country?: string) => {
let title = city;
if (state) {
title = title ? `${title}, ${state}` : state;
}
if (country) {
title = title ? `${title}, ${country}` : country;
}
return title;
};
export const getSearchPlacesTitle = (city?: string, state?: string, country?: string) =>
[city, state, country].filter(Boolean).join(', ') || undefined;
export const searchMediaTitle = (mediaType: MediaType) => {
export const getSearchMediaTitle = (mediaType: MediaType) => {
const $t = get(t);
switch (mediaType) {
case MediaType.Image: {
@ -113,7 +105,7 @@ export const searchMediaTitle = (mediaType: MediaType) => {
return $t('video');
}
default: {
return undefined;
return;
}
}
};
@ -122,21 +114,22 @@ export const getPeople = async (selected: SvelteSet<string>): Promise<PersonResp
const $t = get(t);
try {
const res = await getAllPeople({ withHidden: false });
return [...res.people.filter((p) => selected.has(p.id)), ...res.people.filter((p) => !selected.has(p.id))];
res.people.sort((a, b) => (selected.has(a.id) ? -1 : selected.has(b.id) ? 1 : 0));
return res.people;
} catch (error) {
handleError(error, $t('errors.failed_to_get_people'));
}
return [];
};
export const searchPeopleTitle = (people: PersonResponseDto[], selected: SvelteSet<string>) => {
export const getSearchPeopleTitle = (people: PersonResponseDto[], selected: SvelteSet<string>) => {
if (selected.size === 0) {
return undefined;
return;
}
const $t = get(t);
const name = people.filter((p) => p.name).find((p) => selected.has(p.id))?.name;
const name = people.find(({ id, name }) => name && selected.has(id))?.name;
if (name) {
return selected.size === 1 ? name : $t('name_plus_more_people', { values: { name, count: selected.size - 1 } });
}
@ -144,7 +137,7 @@ export const searchPeopleTitle = (people: PersonResponseDto[], selected: SvelteS
return $t('people_count', { values: { count: selected.size } });
};
export const searchTagsTitle = (tags: TagResponseDto[], selected: SvelteSet<string>) => {
export const getSearchTagsTitle = (tags: TagResponseDto[], selected: SvelteSet<string>) => {
const $t = get(t);
const id = selected.values().next().value;