You've already forked immich
mirror of
https://github.com/immich-app/immich.git
synced 2025-08-07 23:03:36 +02:00
feat: adds option to search only for untagged assets (#19730)
Co-authored-by: SkwalExe <skwal@skwal.net>
This commit is contained in:
committed by
GitHub
parent
818bdde317
commit
f778adea92
@ -33,6 +33,7 @@
|
||||
|
||||
interface Props {
|
||||
label: string;
|
||||
disabled?: boolean;
|
||||
hideLabel?: boolean;
|
||||
options?: ComboBoxOption[];
|
||||
selectedOption?: ComboBoxOption | undefined;
|
||||
@ -52,6 +53,7 @@
|
||||
let {
|
||||
label,
|
||||
hideLabel = false,
|
||||
disabled = false,
|
||||
options = [],
|
||||
selectedOption = $bindable(),
|
||||
placeholder = '',
|
||||
@ -275,6 +277,7 @@
|
||||
|
||||
<input
|
||||
{placeholder}
|
||||
{disabled}
|
||||
aria-activedescendant={selectedIndex || selectedIndex === 0 ? `${listboxId}-${selectedIndex}` : ''}
|
||||
aria-autocomplete="list"
|
||||
aria-controls={listboxId}
|
||||
|
@ -3,13 +3,14 @@
|
||||
import Combobox, { type ComboBoxOption } from '$lib/components/shared-components/combobox.svelte';
|
||||
import { preferences } from '$lib/stores/user.store';
|
||||
import { getAllTags, type TagResponseDto } from '@immich/sdk';
|
||||
import { Checkbox, Label } from '@immich/ui';
|
||||
import { mdiClose } from '@mdi/js';
|
||||
import { onMount } from 'svelte';
|
||||
import { t } from 'svelte-i18n';
|
||||
import { SvelteSet } from 'svelte/reactivity';
|
||||
|
||||
interface Props {
|
||||
selectedTags: SvelteSet<string>;
|
||||
selectedTags: SvelteSet<string> | null;
|
||||
}
|
||||
|
||||
let { selectedTags = $bindable() }: Props = $props();
|
||||
@ -23,7 +24,7 @@
|
||||
});
|
||||
|
||||
const handleSelect = (option?: ComboBoxOption) => {
|
||||
if (!option || !option.id) {
|
||||
if (!option || !option.id || selectedTags === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -32,6 +33,10 @@
|
||||
};
|
||||
|
||||
const handleRemove = (tag: string) => {
|
||||
if (selectedTags === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
selectedTags.delete(tag);
|
||||
};
|
||||
</script>
|
||||
@ -41,6 +46,7 @@
|
||||
<form autocomplete="off" id="create-tag-form">
|
||||
<div class="my-4 flex flex-col gap-2">
|
||||
<Combobox
|
||||
disabled={selectedTags === null}
|
||||
onSelect={handleSelect}
|
||||
label={$t('tags').toUpperCase()}
|
||||
defaultFirstOption
|
||||
@ -49,10 +55,21 @@
|
||||
placeholder={$t('search_tags')}
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<Checkbox
|
||||
id="untagged-checkbox"
|
||||
size="tiny"
|
||||
checked={selectedTags === null}
|
||||
onCheckedChange={(checked) => {
|
||||
selectedTags = checked ? null : new SvelteSet();
|
||||
}}
|
||||
/>
|
||||
<Label label={$t('untagged')} for="untagged-checkbox" />
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<section class="flex flex-wrap pt-2 gap-1">
|
||||
{#each selectedTags as tagId (tagId)}
|
||||
{#each selectedTags ?? [] as tagId (tagId)}
|
||||
{@const tag = tagMap[tagId]}
|
||||
{#if tag}
|
||||
<div class="flex group transition-all">
|
||||
|
@ -8,7 +8,7 @@
|
||||
query: string;
|
||||
queryType: 'smart' | 'metadata' | 'description';
|
||||
personIds: SvelteSet<string>;
|
||||
tagIds: SvelteSet<string>;
|
||||
tagIds: SvelteSet<string> | null;
|
||||
location: SearchLocationFilter;
|
||||
camera: SearchCameraFilter;
|
||||
date: SearchDateFilter;
|
||||
@ -68,7 +68,12 @@
|
||||
query: 'query' in searchQuery ? searchQuery.query : searchQuery.originalFileName || '',
|
||||
queryType: defaultQueryType(),
|
||||
personIds: new SvelteSet('personIds' in searchQuery ? searchQuery.personIds : []),
|
||||
tagIds: new SvelteSet('tagIds' in searchQuery ? searchQuery.tagIds : []),
|
||||
tagIds:
|
||||
'tagIds' in searchQuery
|
||||
? searchQuery.tagIds === null
|
||||
? null
|
||||
: new SvelteSet(searchQuery.tagIds)
|
||||
: new SvelteSet(),
|
||||
location: {
|
||||
country: withNullAsUndefined(searchQuery.country),
|
||||
state: withNullAsUndefined(searchQuery.state),
|
||||
@ -140,7 +145,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,
|
||||
tagIds: filter.tagIds === null ? null : filter.tagIds.size > 0 ? [...filter.tagIds] : undefined,
|
||||
type,
|
||||
rating: filter.rating,
|
||||
};
|
||||
|
@ -233,7 +233,10 @@
|
||||
return personNames.join(', ');
|
||||
}
|
||||
|
||||
async function getTagNames(tagIds: string[]) {
|
||||
async function getTagNames(tagIds: string[] | null) {
|
||||
if (tagIds === null) {
|
||||
return $t('untagged');
|
||||
}
|
||||
const tagNames = await Promise.all(
|
||||
tagIds.map(async (tagId) => {
|
||||
const tag = await getTagById({ id: tagId });
|
||||
@ -343,7 +346,7 @@
|
||||
{#await getPersonName(value) then personName}
|
||||
{personName}
|
||||
{/await}
|
||||
{:else if searchKey === 'tagIds' && Array.isArray(value)}
|
||||
{:else if searchKey === 'tagIds' && (Array.isArray(value) || value === null)}
|
||||
{#await getTagNames(value) then tagNames}
|
||||
{tagNames}
|
||||
{/await}
|
||||
|
Reference in New Issue
Block a user