refactor: asset tag modal (#18867)

This commit is contained in:
Daniel Dietzler 2025-06-02 18:41:28 +02:00 committed by GitHub
parent 72401aa6b1
commit 97e86e409a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 33 additions and 63 deletions

View File

@ -1,11 +1,11 @@
<script lang="ts"> <script lang="ts">
import { shortcut } from '$lib/actions/shortcut'; import { shortcut } from '$lib/actions/shortcut';
import Icon from '$lib/components/elements/icon.svelte'; import Icon from '$lib/components/elements/icon.svelte';
import TagAssetForm from '$lib/components/forms/tag-asset-form.svelte';
import Portal from '$lib/components/shared-components/portal/portal.svelte';
import { AppRoute } from '$lib/constants'; import { AppRoute } from '$lib/constants';
import { authManager } from '$lib/managers/auth-manager.svelte'; import { authManager } from '$lib/managers/auth-manager.svelte';
import { removeTag, tagAssets } from '$lib/utils/asset-utils'; import { modalManager } from '$lib/managers/modal-manager.svelte';
import AssetTagModal from '$lib/modals/AssetTagModal.svelte';
import { removeTag } from '$lib/utils/asset-utils';
import { getAssetInfo, type AssetResponseDto } from '@immich/sdk'; import { getAssetInfo, type AssetResponseDto } from '@immich/sdk';
import { mdiClose, mdiPlus } from '@mdi/js'; import { mdiClose, mdiPlus } from '@mdi/js';
import { t } from 'svelte-i18n'; import { t } from 'svelte-i18n';
@ -19,19 +19,12 @@
let tags = $derived(asset.tags || []); let tags = $derived(asset.tags || []);
let isOpen = $state(false); const handleAddTag = async () => {
const success = await modalManager.show(AssetTagModal, { assetIds: [asset.id] });
const handleAdd = () => (isOpen = true); if (success) {
asset = await getAssetInfo({ id: asset.id });
const handleCancel = () => (isOpen = false);
const handleTag = async (tagIds: string[]) => {
const ids = await tagAssets({ tagIds, assetIds: [asset.id], showNotification: false });
if (ids) {
isOpen = false;
} }
asset = await getAssetInfo({ id: asset.id });
}; };
const handleRemove = async (tagId: string) => { const handleRemove = async (tagId: string) => {
@ -42,7 +35,7 @@
}; };
</script> </script>
<svelte:document use:shortcut={{ shortcut: { key: 't' }, onShortcut: () => (isOpen = true) }} /> <svelte:document use:shortcut={{ shortcut: { key: 't' }, onShortcut: handleAddTag }} />
{#if isOwner && !authManager.key} {#if isOwner && !authManager.key}
<section class="px-4 mt-4"> <section class="px-4 mt-4">
@ -75,16 +68,10 @@
type="button" type="button"
class="rounded-full bg-gray-100 dark:bg-gray-800 text-gray-600 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-700 hover:text-gray-700 dark:hover:text-gray-200 flex place-items-center place-content-center gap-1 px-2 py-1" class="rounded-full bg-gray-100 dark:bg-gray-800 text-gray-600 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-700 hover:text-gray-700 dark:hover:text-gray-200 flex place-items-center place-content-center gap-1 px-2 py-1"
title="Add tag" title="Add tag"
onclick={handleAdd} onclick={handleAddTag}
> >
<span class="text-sm px-1 flex place-items-center place-content-center gap-1"><Icon path={mdiPlus} />Add</span> <span class="text-sm px-1 flex place-items-center place-content-center gap-1"><Icon path={mdiPlus} />Add</span>
</button> </button>
</section> </section>
</section> </section>
{/if} {/if}
{#if isOpen}
<Portal>
<TagAssetForm onTag={(tagsIds) => handleTag(tagsIds)} onCancel={handleCancel} />
</Portal>
{/if}

View File

@ -1,10 +1,10 @@
<script lang="ts"> <script lang="ts">
import { shortcut } from '$lib/actions/shortcut'; import { shortcut } from '$lib/actions/shortcut';
import TagAssetForm from '$lib/components/forms/tag-asset-form.svelte'; import { modalManager } from '$lib/managers/modal-manager.svelte';
import { tagAssets } from '$lib/utils/asset-utils'; import AssetTagModal from '$lib/modals/AssetTagModal.svelte';
import { mdiTagMultipleOutline, mdiTimerSand } from '@mdi/js';
import { t } from 'svelte-i18n';
import { IconButton } from '@immich/ui'; import { IconButton } from '@immich/ui';
import { mdiTagMultipleOutline } from '@mdi/js';
import { t } from 'svelte-i18n';
import MenuOption from '../../shared-components/context-menu/menu-option.svelte'; import MenuOption from '../../shared-components/context-menu/menu-option.svelte';
import { getAssetControlContext } from '../asset-select-control-bar.svelte'; import { getAssetControlContext } from '../asset-select-control-bar.svelte';
@ -17,45 +17,24 @@
const text = $t('tag'); const text = $t('tag');
const icon = mdiTagMultipleOutline; const icon = mdiTagMultipleOutline;
let loading = $state(false);
let isOpen = $state(false);
const { clearSelect, getOwnedAssets } = getAssetControlContext(); const { clearSelect, getOwnedAssets } = getAssetControlContext();
const handleOpen = () => (isOpen = true); const handleTagAssets = async () => {
const handleCancel = () => (isOpen = false);
const handleTag = async (tagIds: string[]) => {
const assets = [...getOwnedAssets()]; const assets = [...getOwnedAssets()];
loading = true; const success = await modalManager.show(AssetTagModal, { assetIds: assets.map(({ id }) => id) });
const ids = await tagAssets({ tagIds, assetIds: assets.map((asset) => asset.id) });
if (ids) { if (success) {
clearSelect(); clearSelect();
} }
loading = false;
}; };
</script> </script>
<svelte:document use:shortcut={{ shortcut: { key: 't' }, onShortcut: () => (isOpen = true) }} /> <svelte:document use:shortcut={{ shortcut: { key: 't' }, onShortcut: handleTagAssets }} />
{#if menuItem} {#if menuItem}
<MenuOption {text} {icon} onClick={handleOpen} /> <MenuOption {text} {icon} onClick={handleTagAssets} />
{/if} {/if}
{#if !menuItem} {#if !menuItem}
{#if loading} <IconButton shape="round" color="secondary" variant="ghost" aria-label={text} {icon} onclick={handleTagAssets} />
<IconButton
shape="round"
color="secondary"
variant="ghost"
aria-label={$t('loading')}
icon={mdiTimerSand}
onclick={() => {}}
/>
{:else}
<IconButton shape="round" color="secondary" variant="ghost" aria-label={text} {icon} onclick={handleOpen} />
{/if}
{/if}
{#if isOpen}
<TagAssetForm onTag={(tagIds) => handleTag(tagIds)} onCancel={handleCancel} />
{/if} {/if}

View File

@ -1,19 +1,20 @@
<script lang="ts"> <script lang="ts">
import Icon from '$lib/components/elements/icon.svelte'; import Icon from '$lib/components/elements/icon.svelte';
import { tagAssets } from '$lib/utils/asset-utils';
import { getAllTags, upsertTags, type TagResponseDto } from '@immich/sdk'; import { getAllTags, upsertTags, type TagResponseDto } from '@immich/sdk';
import { Button, Modal, ModalBody, ModalFooter } from '@immich/ui'; import { Button, Modal, ModalBody, ModalFooter } from '@immich/ui';
import { mdiClose, mdiTag } from '@mdi/js'; import { mdiClose, mdiTag } from '@mdi/js';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { t } from 'svelte-i18n'; import { t } from 'svelte-i18n';
import { SvelteSet } from 'svelte/reactivity'; import { SvelteSet } from 'svelte/reactivity';
import Combobox, { type ComboBoxOption } from '../shared-components/combobox.svelte'; import Combobox, { type ComboBoxOption } from '../components/shared-components/combobox.svelte';
interface Props { interface Props {
onTag: (tagIds: string[]) => void; onClose: (success?: true) => void;
onCancel: () => void; assetIds: string[];
} }
let { onTag, onCancel }: Props = $props(); let { onClose, assetIds }: Props = $props();
let allTags: TagResponseDto[] = $state([]); let allTags: TagResponseDto[] = $state([]);
let tagMap = $derived(Object.fromEntries(allTags.map((tag) => [tag.id, tag]))); let tagMap = $derived(Object.fromEntries(allTags.map((tag) => [tag.id, tag])));
@ -25,7 +26,10 @@
allTags = await getAllTags(); allTags = await getAllTags();
}); });
const handleSubmit = () => onTag([...selectedIds]); const handleSubmit = async () => {
await tagAssets({ tagIds: [...selectedIds], assetIds, showNotification: false });
onClose(true);
};
const handleSelect = async (option?: ComboBoxOption) => { const handleSelect = async (option?: ComboBoxOption) => {
if (!option) { if (!option) {
@ -45,13 +49,13 @@
selectedIds.delete(tag); selectedIds.delete(tag);
}; };
const onsubmit = (event: Event) => { const onsubmit = async (event: Event) => {
event.preventDefault(); event.preventDefault();
handleSubmit(); await handleSubmit();
}; };
</script> </script>
<Modal size="small" title={$t('tag_assets')} icon={mdiTag} onClose={onCancel}> <Modal size="small" title={$t('tag_assets')} icon={mdiTag} {onClose}>
<ModalBody> <ModalBody>
<form {onsubmit} autocomplete="off" id="create-tag-form"> <form {onsubmit} autocomplete="off" id="create-tag-form">
<div class="my-4 flex flex-col gap-2"> <div class="my-4 flex flex-col gap-2">
@ -95,7 +99,7 @@
<ModalFooter> <ModalFooter>
<div class="flex w-full gap-2"> <div class="flex w-full gap-2">
<Button shape="round" fullWidth color="secondary" onclick={onCancel}>{$t('cancel')}</Button> <Button shape="round" fullWidth color="secondary" onclick={() => onClose()}>{$t('cancel')}</Button>
<Button type="submit" shape="round" fullWidth form="create-tag-form" {disabled}>{$t('tag_assets')}</Button> <Button type="submit" shape="round" fullWidth form="create-tag-form" {disabled}>{$t('tag_assets')}</Button>
</div> </div>
</ModalFooter> </ModalFooter>