mirror of
https://github.com/immich-app/immich
synced 2026-08-15 13:03:57 +00:00
chore(web): search ui tweaks and refactoring
This commit is contained in:
parent
c54722d666
commit
b3a0283fcf
11 changed files with 110 additions and 166 deletions
|
|
@ -2111,7 +2111,6 @@
|
|||
"tags": "Tags",
|
||||
"tap_to_run_job": "Tap to run job",
|
||||
"template": "Template",
|
||||
"text_in_images": "Text in images",
|
||||
"text_recognition": "Text recognition",
|
||||
"theme": "Theme",
|
||||
"theme_selection": "Theme selection",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
<script lang="ts">
|
||||
import { Button } from '@immich/ui';
|
||||
import { mdiCheck } from '@mdi/js';
|
||||
let {
|
||||
active = $bindable(),
|
||||
children,
|
||||
class: className = '',
|
||||
leadingIcon = undefined,
|
||||
checked = false,
|
||||
...rest
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<Button
|
||||
size="small"
|
||||
shape="round"
|
||||
color={active ? 'primary' : 'secondary'}
|
||||
variant="outline"
|
||||
class="{active ? '' : 'bg-transparent'} {className}"
|
||||
leadingIcon={checked && active ? mdiCheck : leadingIcon}
|
||||
{...rest}>{@render children()}</Button
|
||||
>
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
<script lang="ts">
|
||||
import { getSearchDatePreset, SearchDatePreset } from './search-bar-utils';
|
||||
import { Button, DatePicker, Text } from '@immich/ui';
|
||||
import { mdiCheck } from '@mdi/js';
|
||||
import { DatePicker, Text } from '@immich/ui';
|
||||
import { DateTime } from 'luxon';
|
||||
import { t } from 'svelte-i18n';
|
||||
import { searchManager } from '$lib/managers/search-manager.svelte';
|
||||
import SearchButton from './SearchButton.svelte';
|
||||
|
||||
let filters = $derived(searchManager.filter.date);
|
||||
let invalid = $derived(filters.takenAfter && filters.takenBefore && filters.takenAfter > filters.takenBefore);
|
||||
|
|
@ -48,42 +48,26 @@
|
|||
<div>
|
||||
<Text class="pb-5">{$t('search_filter_date_description')}</Text>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<Button
|
||||
shape="round"
|
||||
color={currentPreset === SearchDatePreset.ThisYear ? 'primary' : 'secondary'}
|
||||
variant="outline"
|
||||
onclick={() => setPreset(SearchDatePreset.ThisYear)}
|
||||
class={currentPreset === SearchDatePreset.ThisYear ? undefined : 'bg-transparent'}
|
||||
leadingIcon={currentPreset === SearchDatePreset.ThisYear ? mdiCheck : undefined}
|
||||
>{$t('search_filter_date_this_year')}
|
||||
</Button>
|
||||
<Button
|
||||
shape="round"
|
||||
color={currentPreset === SearchDatePreset.LastYear ? 'primary' : 'secondary'}
|
||||
variant="outline"
|
||||
onclick={() => setPreset(SearchDatePreset.LastYear)}
|
||||
class={currentPreset === SearchDatePreset.LastYear ? undefined : 'bg-transparent'}
|
||||
leadingIcon={currentPreset === SearchDatePreset.LastYear ? mdiCheck : undefined}
|
||||
>{$t('search_filter_date_last_year')}
|
||||
</Button>
|
||||
<Button
|
||||
shape="round"
|
||||
color={currentPreset === SearchDatePreset.Last30Days ? 'primary' : 'secondary'}
|
||||
variant="outline"
|
||||
onclick={() => setPreset(SearchDatePreset.Last30Days)}
|
||||
class={currentPreset === SearchDatePreset.Last30Days ? undefined : 'bg-transparent'}
|
||||
leadingIcon={currentPreset === SearchDatePreset.Last30Days ? mdiCheck : undefined}
|
||||
>{$t('search_filter_date_last_30_days')}
|
||||
</Button>
|
||||
<Button
|
||||
shape="round"
|
||||
color={currentPreset === SearchDatePreset.Custom ? 'primary' : 'secondary'}
|
||||
variant="outline"
|
||||
onclick={() => setPreset(SearchDatePreset.Custom)}
|
||||
class={currentPreset === SearchDatePreset.Custom ? undefined : 'bg-transparent'}
|
||||
leadingIcon={currentPreset === SearchDatePreset.Custom ? mdiCheck : undefined}
|
||||
>{$t('search_filter_date_custom')}
|
||||
</Button>
|
||||
<SearchButton
|
||||
checked
|
||||
active={currentPreset === SearchDatePreset.ThisYear}
|
||||
onclick={() => setPreset(SearchDatePreset.ThisYear)}>{$t('search_filter_date_this_year')}</SearchButton
|
||||
>
|
||||
<SearchButton
|
||||
checked
|
||||
active={currentPreset === SearchDatePreset.LastYear}
|
||||
onclick={() => setPreset(SearchDatePreset.LastYear)}>{$t('search_filter_date_last_year')}</SearchButton
|
||||
>
|
||||
<SearchButton
|
||||
checked
|
||||
active={currentPreset === SearchDatePreset.Last30Days}
|
||||
onclick={() => setPreset(SearchDatePreset.Last30Days)}>{$t('search_filter_date_last_30_days')}</SearchButton
|
||||
>
|
||||
<SearchButton
|
||||
checked
|
||||
active={currentPreset === SearchDatePreset.Custom}
|
||||
onclick={() => setPreset(SearchDatePreset.Custom)}>{$t('search_filter_date_custom')}</SearchButton
|
||||
>
|
||||
</div>
|
||||
{#if currentPreset === SearchDatePreset.Custom}
|
||||
<div id="date-range-selection" class="grid grid-auto-fit-40 gap-5 py-5">
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script lang="ts">
|
||||
import SearchButton from './SearchButton.svelte';
|
||||
import { searchManager } from '$lib/managers/search-manager.svelte';
|
||||
import { Button, Text } from '@immich/ui';
|
||||
import { mdiCheck } from '@mdi/js';
|
||||
import { Text } from '@immich/ui';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
let filters = $derived(searchManager.filter.display);
|
||||
|
|
@ -10,35 +10,16 @@
|
|||
<div id="display-options-selection">
|
||||
<fieldset>
|
||||
<Text class="py-5" fontWeight="medium">{$t('library')}</Text>
|
||||
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<Button
|
||||
shape="round"
|
||||
color={filters.isFavorite ? 'primary' : 'secondary'}
|
||||
variant="outline"
|
||||
onclick={() => (filters.isFavorite = !filters.isFavorite)}
|
||||
class={filters.isFavorite ? undefined : 'bg-transparent'}
|
||||
leadingIcon={filters.isFavorite ? mdiCheck : undefined}
|
||||
>{$t('favorites')}
|
||||
</Button>
|
||||
<Button
|
||||
shape="round"
|
||||
color={filters.isArchive ? 'primary' : 'secondary'}
|
||||
variant="outline"
|
||||
onclick={() => (filters.isArchive = !filters.isArchive)}
|
||||
class={filters.isArchive ? undefined : 'bg-transparent'}
|
||||
leadingIcon={filters.isArchive ? mdiCheck : undefined}
|
||||
>{$t('archive')}
|
||||
</Button>
|
||||
<Button
|
||||
shape="round"
|
||||
color={filters.isNotInAlbum ? 'primary' : 'secondary'}
|
||||
variant="outline"
|
||||
onclick={() => (filters.isNotInAlbum = !filters.isNotInAlbum)}
|
||||
class={filters.isNotInAlbum ? undefined : 'bg-transparent'}
|
||||
leadingIcon={filters.isNotInAlbum ? mdiCheck : undefined}
|
||||
>{$t('search_filter_display_option_not_in_album')}
|
||||
</Button>
|
||||
<SearchButton checked active={filters.isFavorite} onclick={() => (filters.isFavorite = !filters.isFavorite)}
|
||||
>{$t('favorites')}</SearchButton
|
||||
>
|
||||
<SearchButton checked active={filters.isArchive} onclick={() => (filters.isArchive = !filters.isArchive)}
|
||||
>{$t('archive')}</SearchButton
|
||||
>
|
||||
<SearchButton checked active={filters.isNotInAlbum} onclick={() => (filters.isNotInAlbum = !filters.isNotInAlbum)}
|
||||
>{$t('search_filter_display_option_not_in_album')}</SearchButton
|
||||
>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@
|
|||
} from './search-bar-utils';
|
||||
import { onMount } from 'svelte';
|
||||
import { searchManager } from '$lib/managers/search-manager.svelte';
|
||||
import SearchButton from './SearchButton.svelte';
|
||||
|
||||
interface Props {
|
||||
id: string;
|
||||
|
|
@ -196,16 +197,14 @@
|
|||
<Text class="py-5" fontWeight="medium" aria-hidden={true}>{$t('filter_by')}</Text>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
{#each filters as item (item.name)}
|
||||
<Button
|
||||
shape="round"
|
||||
color={activeFilter === item.name || item.activeTitle() ? 'primary' : 'secondary'}
|
||||
variant="outline"
|
||||
<SearchButton
|
||||
active={activeFilter === item.name || item.activeTitle()}
|
||||
leadingIcon={item.icon}
|
||||
class={activeFilter === item.name ? 'border-2' : undefined}
|
||||
onclick={() => (activeFilter = item.name)}
|
||||
class="{activeFilter === item.name || item.activeTitle() ? undefined : 'bg-transparent'}
|
||||
{activeFilter === item.name ? 'border-2' : undefined}"
|
||||
>{item.activeTitle() ?? item.title}
|
||||
</Button>
|
||||
>
|
||||
{item.activeTitle() ?? item.title}
|
||||
</SearchButton>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -238,6 +237,7 @@
|
|||
<div class="my-5 h-px w-full bg-light-200 dark:bg-dark-600"></div>
|
||||
<div class="flex gap-2 px-5 pb-5">
|
||||
<Button
|
||||
size="small"
|
||||
variant={advancedFiltersSet ? 'outline' : 'ghost'}
|
||||
leadingIcon={mdiTune}
|
||||
trailingIcon={activeFilter === 'advanced' ? mdiChevronUp : mdiChevronDown}
|
||||
|
|
@ -245,10 +245,15 @@
|
|||
>{$t('advanced_filters')}</Button
|
||||
>
|
||||
<div class="flex-1"></div>
|
||||
<Button shape="round" variant="outline" color="secondary" class="bg-transparent" onclick={() => clear()}
|
||||
>{$t('clear_all')}</Button
|
||||
<Button
|
||||
size="small"
|
||||
shape="round"
|
||||
variant="outline"
|
||||
color="secondary"
|
||||
class="bg-transparent"
|
||||
onclick={() => clear()}>{$t('clear_all')}</Button
|
||||
>
|
||||
<Button shape="round" onclick={() => onSearch()}>{$t('search')}</Button>
|
||||
<Button size="small" shape="round" onclick={() => onSearch()}>{$t('search')}</Button>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
<script lang="ts">
|
||||
import { searchMediaTitle } from './search-bar-utils';
|
||||
import { MediaType } from '$lib/constants';
|
||||
import { Button, Text } from '@immich/ui';
|
||||
import { mdiCheck } from '@mdi/js';
|
||||
import { Text } from '@immich/ui';
|
||||
import { t } from 'svelte-i18n';
|
||||
import { searchManager } from '$lib/managers/search-manager.svelte';
|
||||
import SearchButton from './SearchButton.svelte';
|
||||
|
||||
interface Props {
|
||||
title: string | undefined;
|
||||
|
|
@ -25,33 +25,15 @@
|
|||
<Text class="pb-5">{$t('media_type_description')}</Text>
|
||||
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<Button
|
||||
shape="round"
|
||||
color={filteredMedia === MediaType.All ? 'primary' : 'secondary'}
|
||||
variant="outline"
|
||||
onclick={() => setMedia(MediaType.All)}
|
||||
class={filteredMedia === MediaType.All ? undefined : 'bg-transparent'}
|
||||
leadingIcon={filteredMedia === MediaType.All ? mdiCheck : undefined}
|
||||
>{$t('all')}
|
||||
</Button>
|
||||
<Button
|
||||
shape="round"
|
||||
color={filteredMedia === MediaType.Image ? 'primary' : 'secondary'}
|
||||
variant="outline"
|
||||
onclick={() => setMedia(MediaType.Image)}
|
||||
class={filteredMedia === MediaType.Image ? undefined : 'bg-transparent'}
|
||||
leadingIcon={filteredMedia === MediaType.Image ? mdiCheck : undefined}
|
||||
>{$t('image')}
|
||||
</Button>
|
||||
<Button
|
||||
shape="round"
|
||||
color={filteredMedia === MediaType.Video ? 'primary' : 'secondary'}
|
||||
variant="outline"
|
||||
onclick={() => setMedia(MediaType.Video)}
|
||||
class={filteredMedia === MediaType.Video ? undefined : 'bg-transparent'}
|
||||
leadingIcon={filteredMedia === MediaType.Video ? mdiCheck : undefined}
|
||||
>{$t('video')}
|
||||
</Button>
|
||||
<SearchButton checked active={filteredMedia === MediaType.All} onclick={() => setMedia(MediaType.All)}>
|
||||
{$t('all')}
|
||||
</SearchButton>
|
||||
<SearchButton checked active={filteredMedia === MediaType.Image} onclick={() => setMedia(MediaType.Image)}>
|
||||
{$t('image')}
|
||||
</SearchButton>
|
||||
<SearchButton checked active={filteredMedia === MediaType.Video} onclick={() => setMedia(MediaType.Video)}>
|
||||
{$t('video')}
|
||||
</SearchButton>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -88,6 +88,7 @@
|
|||
{#if showAllPeople || people.length > peopleList.length}
|
||||
<div class="mt-2 flex justify-center">
|
||||
<Button
|
||||
size="small"
|
||||
color="primary"
|
||||
variant="ghost"
|
||||
shape="round"
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
<script lang="ts">
|
||||
import { searchManager } from '$lib/managers/search-manager.svelte';
|
||||
import { Button, Text } from '@immich/ui';
|
||||
import { Text } from '@immich/ui';
|
||||
import { t } from 'svelte-i18n';
|
||||
import SearchButton from './SearchButton.svelte';
|
||||
|
||||
let rating = $derived(searchManager.filter.rating);
|
||||
|
||||
|
|
@ -18,14 +19,12 @@
|
|||
<Text class="my-5" fontWeight="medium">{$t('rating')}</Text>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
{#each options as option (option.value)}
|
||||
<Button
|
||||
shape="round"
|
||||
color={rating === option.value ? 'primary' : 'secondary'}
|
||||
variant="outline"
|
||||
<SearchButton
|
||||
active={rating === option.value}
|
||||
onclick={() => (searchManager.filter.rating = rating === option.value ? undefined : option.value)}
|
||||
class={rating === option.value ? undefined : 'bg-transparent'}
|
||||
>{option.label}
|
||||
</Button>
|
||||
>
|
||||
{option.label}
|
||||
</SearchButton>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -72,6 +72,7 @@
|
|||
{@const tag = tagMap[tagId]}
|
||||
{#if tag}
|
||||
<Button
|
||||
size="small"
|
||||
shape="round"
|
||||
color="primary"
|
||||
variant="outline"
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
<script lang="ts">
|
||||
import { searchTypeTitle } from '$lib/components/shared-components/search-bar/search-bar-utils';
|
||||
import { searchTypeTitle } 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';
|
||||
import { Button, Text } from '@immich/ui';
|
||||
import { mdiCheck } from '@mdi/js';
|
||||
import { Text } from '@immich/ui';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
let queryType = $derived(searchManager.filter.queryType);
|
||||
|
|
@ -18,53 +18,23 @@
|
|||
<Text class="mb-5">{$t('search_type_description')}</Text>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
{#if featureFlagsManager.value.smartSearch}
|
||||
<Button
|
||||
shape="round"
|
||||
color={queryType === 'smart' ? 'primary' : 'secondary'}
|
||||
variant="outline"
|
||||
onclick={() => setType('smart')}
|
||||
class={queryType === 'smart' ? undefined : 'bg-transparent'}
|
||||
leadingIcon={queryType === 'smart' ? mdiCheck : undefined}
|
||||
>{$t('context')}
|
||||
</Button>
|
||||
<SearchButton checked active={queryType === 'smart'} onclick={() => setType('smart')}>
|
||||
{$t('context')}
|
||||
</SearchButton>
|
||||
{/if}
|
||||
<Button
|
||||
shape="round"
|
||||
color={queryType === 'metadata' ? 'primary' : 'secondary'}
|
||||
variant="outline"
|
||||
onclick={() => setType('metadata')}
|
||||
class={queryType === 'metadata' ? undefined : 'bg-transparent'}
|
||||
leadingIcon={queryType === 'metadata' ? mdiCheck : undefined}
|
||||
>{searchTypeTitle('metadata')}
|
||||
</Button>
|
||||
<Button
|
||||
shape="round"
|
||||
color={queryType === 'description' ? 'primary' : 'secondary'}
|
||||
variant="outline"
|
||||
onclick={() => setType('description')}
|
||||
class={queryType === 'description' ? undefined : 'bg-transparent'}
|
||||
leadingIcon={queryType === 'description' ? mdiCheck : undefined}
|
||||
>{searchTypeTitle('description')}
|
||||
</Button>
|
||||
<Button
|
||||
shape="round"
|
||||
color={queryType === 'fullPath' ? 'primary' : 'secondary'}
|
||||
variant="outline"
|
||||
onclick={() => setType('fullPath')}
|
||||
class={queryType === 'fullPath' ? undefined : 'bg-transparent'}
|
||||
leadingIcon={queryType === 'fullPath' ? mdiCheck : undefined}
|
||||
>{searchTypeTitle('fullPath')}
|
||||
</Button>
|
||||
<SearchButton checked active={queryType === 'metadata'} onclick={() => setType('metadata')}>
|
||||
{searchTypeTitle('metadata')}
|
||||
</SearchButton>
|
||||
<SearchButton checked active={queryType === 'description'} onclick={() => setType('description')}>
|
||||
{searchTypeTitle('description')}
|
||||
</SearchButton>
|
||||
<SearchButton checked active={queryType === 'fullPath'} onclick={() => setType('fullPath')}>
|
||||
{searchTypeTitle('fullPath')}
|
||||
</SearchButton>
|
||||
{#if featureFlagsManager.value.ocr}
|
||||
<Button
|
||||
shape="round"
|
||||
color={queryType === 'ocr' ? 'primary' : 'secondary'}
|
||||
variant="outline"
|
||||
onclick={() => setType('ocr')}
|
||||
class={queryType === 'ocr' ? undefined : 'bg-transparent'}
|
||||
leadingIcon={queryType === 'ocr' ? mdiCheck : undefined}
|
||||
>{searchTypeTitle('ocr')}
|
||||
</Button>
|
||||
<SearchButton checked active={queryType === 'ocr'} onclick={() => setType('ocr')}>
|
||||
{searchTypeTitle('ocr')}
|
||||
</SearchButton>
|
||||
{/if}
|
||||
</div>
|
||||
</fieldset>
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ export const searchTypeTitle = (type: string) => {
|
|||
return $t('full_path_or_folder');
|
||||
}
|
||||
case 'ocr': {
|
||||
return $t('text_in_images');
|
||||
return $t('ocr');
|
||||
}
|
||||
default: {
|
||||
return undefined;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue