mirror of
https://github.com/immich-app/immich
synced 2025-11-07 17:27:20 +00:00
feat: add searching by tags (#15395)
* feat: add searching by tags * fix: fix merge --------- Co-authored-by: Alex <alex.tran1502@gmail.com>
This commit is contained in:
parent
221e197633
commit
9ac95d6845
11 changed files with 187 additions and 5 deletions
|
|
@ -8,6 +8,7 @@
|
|||
query: string;
|
||||
queryType: 'smart' | 'metadata';
|
||||
personIds: SvelteSet<string>;
|
||||
tagIds: SvelteSet<string>;
|
||||
location: SearchLocationFilter;
|
||||
camera: SearchCameraFilter;
|
||||
date: SearchDateFilter;
|
||||
|
|
@ -20,6 +21,7 @@
|
|||
import { Button } from '@immich/ui';
|
||||
import { AssetTypeEnum, type SmartSearchDto, type MetadataSearchDto } from '@immich/sdk';
|
||||
import SearchPeopleSection from './search-people-section.svelte';
|
||||
import SearchTagsSection from './search-tags-section.svelte';
|
||||
import SearchLocationSection from './search-location-section.svelte';
|
||||
import SearchCameraSection, { type SearchCameraFilter } from './search-camera-section.svelte';
|
||||
import SearchDateSection from './search-date-section.svelte';
|
||||
|
|
@ -54,6 +56,7 @@
|
|||
query: 'query' in searchQuery ? searchQuery.query : searchQuery.originalFileName || '',
|
||||
queryType: 'query' in searchQuery ? 'smart' : 'metadata',
|
||||
personIds: new SvelteSet('personIds' in searchQuery ? searchQuery.personIds : []),
|
||||
tagIds: new SvelteSet('tagIds' in searchQuery ? searchQuery.tagIds : []),
|
||||
location: {
|
||||
country: withNullAsUndefined(searchQuery.country),
|
||||
state: withNullAsUndefined(searchQuery.state),
|
||||
|
|
@ -85,6 +88,7 @@
|
|||
query: '',
|
||||
queryType: 'smart',
|
||||
personIds: new SvelteSet(),
|
||||
tagIds: new SvelteSet(),
|
||||
location: {},
|
||||
camera: {},
|
||||
date: {},
|
||||
|
|
@ -117,6 +121,7 @@
|
|||
isFavorite: filter.display.isFavorite || undefined,
|
||||
isNotInAlbum: filter.display.isNotInAlbum || undefined,
|
||||
personIds: filter.personIds.size > 0 ? [...filter.personIds] : undefined,
|
||||
tagIds: filter.tagIds.size > 0 ? [...filter.tagIds] : undefined,
|
||||
type,
|
||||
};
|
||||
|
||||
|
|
@ -143,6 +148,9 @@
|
|||
<!-- TEXT -->
|
||||
<SearchTextSection bind:query={filter.query} bind:queryType={filter.queryType} />
|
||||
|
||||
<!-- TAGS -->
|
||||
<SearchTagsSection bind:selectedTags={filter.tagIds} />
|
||||
|
||||
<!-- LOCATION -->
|
||||
<SearchLocationSection bind:filters={filter.location} />
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,80 @@
|
|||
<script lang="ts">
|
||||
import Combobox, { type ComboBoxOption } from '$lib/components/shared-components/combobox.svelte';
|
||||
import { getAllTags, type TagResponseDto } from '@immich/sdk';
|
||||
import { t } from 'svelte-i18n';
|
||||
import { onMount } from 'svelte';
|
||||
import { SvelteSet } from 'svelte/reactivity';
|
||||
import Icon from '$lib/components/elements/icon.svelte';
|
||||
import { mdiClose } from '@mdi/js';
|
||||
import { preferences } from '$lib/stores/user.store';
|
||||
|
||||
interface Props {
|
||||
selectedTags: SvelteSet<string>;
|
||||
}
|
||||
|
||||
let { selectedTags = $bindable() }: Props = $props();
|
||||
|
||||
let allTags: TagResponseDto[] = $state([]);
|
||||
let tagMap = $derived(Object.fromEntries(allTags.map((tag) => [tag.id, tag])));
|
||||
let selectedOption = $state(undefined);
|
||||
|
||||
onMount(async () => {
|
||||
allTags = await getAllTags();
|
||||
});
|
||||
|
||||
const handleSelect = (option?: ComboBoxOption) => {
|
||||
if (!option || !option.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
selectedTags.add(option.value);
|
||||
selectedOption = undefined;
|
||||
};
|
||||
|
||||
const handleRemove = (tag: string) => {
|
||||
selectedTags.delete(tag);
|
||||
};
|
||||
</script>
|
||||
|
||||
{#if $preferences?.tags?.enabled}
|
||||
<div id="location-selection">
|
||||
<form autocomplete="off" id="create-tag-form">
|
||||
<div class="my-4 flex flex-col gap-2">
|
||||
<Combobox
|
||||
onSelect={handleSelect}
|
||||
label={$t('tags').toUpperCase()}
|
||||
defaultFirstOption
|
||||
options={allTags.map((tag) => ({ id: tag.id, label: tag.value, value: tag.id }))}
|
||||
bind:selectedOption
|
||||
placeholder={$t('search_tags')}
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<section class="flex flex-wrap pt-2 gap-1">
|
||||
{#each selectedTags as tagId (tagId)}
|
||||
{@const tag = tagMap[tagId]}
|
||||
{#if tag}
|
||||
<div class="flex group transition-all">
|
||||
<span
|
||||
class="inline-block h-min whitespace-nowrap pl-3 pr-1 group-hover:pl-3 py-1 text-center align-baseline leading-none text-gray-100 dark:text-immich-dark-gray bg-immich-primary dark:bg-immich-dark-primary rounded-tl-full rounded-bl-full hover:bg-immich-primary/80 dark:hover:bg-immich-dark-primary/80 transition-all"
|
||||
>
|
||||
<p class="text-sm">
|
||||
{tag.value}
|
||||
</p>
|
||||
</span>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="text-gray-100 dark:text-immich-dark-gray bg-immich-primary/95 dark:bg-immich-dark-primary/95 rounded-tr-full rounded-br-full place-items-center place-content-center pr-2 pl-1 py-1 hover:bg-immich-primary/80 dark:hover:bg-immich-dark-primary/80 transition-all"
|
||||
title="Remove tag"
|
||||
onclick={() => handleRemove(tagId)}
|
||||
>
|
||||
<Icon path={mdiClose} />
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
</section>
|
||||
</div>
|
||||
{/if}
|
||||
Loading…
Add table
Add a link
Reference in a new issue