immich/web/src/lib/components/QueueCard.svelte

186 lines
7 KiB
Svelte
Raw Normal View History

<script lang="ts">
2025-12-03 13:39:32 -05:00
import QueueCardBadge from '$lib/components/QueueCardBadge.svelte';
import QueueCardButton from '$lib/components/QueueCardButton.svelte';
import Badge from '$lib/elements/Badge.svelte';
2025-12-03 13:39:32 -05:00
import { asQueueItem, getQueueDetailUrl } from '$lib/services/queue.service';
import { locale } from '$lib/stores/preferences.store';
2025-12-03 13:39:32 -05:00
import { QueueCommand, type QueueCommandDto, type QueueResponseDto } from '@immich/sdk';
import { Icon, IconButton, Link } from '@immich/ui';
2023-10-25 09:48:25 -04:00
import {
mdiAlertCircle,
mdiAllInclusive,
2025-12-03 13:39:32 -05:00
mdiChartLine,
2023-10-25 09:48:25 -04:00
mdiClose,
mdiFastForward,
mdiImageRefreshOutline,
2023-10-25 09:48:25 -04:00
mdiPause,
mdiPlay,
mdiSelectionSearch,
} from '@mdi/js';
import { type Component } from 'svelte';
import { t } from 'svelte-i18n';
interface Props {
2025-12-03 13:39:32 -05:00
queue: QueueResponseDto;
description?: Component;
disabled?: boolean;
2025-12-03 13:39:32 -05:00
allText?: string;
refreshText?: string;
missingText: string;
2025-11-14 14:42:00 -05:00
onCommand: (command: QueueCommandDto) => void;
}
2025-12-03 13:39:32 -05:00
let { queue, description, disabled = false, allText, refreshText, missingText, onCommand }: Props = $props();
feat(server): xmp sidecar metadata (#2466) * initial commit for XMP sidecar support * Added support for 'missing' metadata files to include those without sidecar files, now detects sidecar files in the filesystem for media already ingested but the sidecar was created afterwards * didn't mean to commit default log level during testing * new sidecar logic for video metadata as well * Added xml mimetype for sidecars only * don't need capture group for this regex * wrong default value reverted * simplified the move here - keep it in the same try catch since the outcome is to move the media back anyway * simplified setter logic Co-authored-by: Jason Rasmussen <jrasm91@gmail.com> * simplified logic per suggestions * sidecar is now its own queue with a discover and sync, updated UI for the new job queueing * queue a sidecar job for every asset based on discovery or sync, though the logic is almost identical aside from linking the sidecar * now queue sidecar jobs for each assset, though logic is mostly the same between discovery and sync * simplified logic of filename extraction and asset instantiation * not sure how that got deleted.. * updated code per suggestions and comments in the PR * stat was not being used, removed the variable set * better type checking, using in-scope variables for exif getter instead of passing in every time * removed commented out test * ran and resolved all lints, formats, checks, and tests * resolved suggested change in PR * made getExifProperty more dynamic with multiple possible args for fallbacks, fixed typo, used generic in function for better type checking * better error handling and moving files back to positions on move or save failure * regenerated api * format fixes * Added XMP documentation * documentation typo * Merged in main * missed merge conflict * more changes due to a merge * Resolving conflicts * added icon for sidecar jobs --------- Co-authored-by: Jason Rasmussen <jrasm91@gmail.com> Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-05-24 21:59:30 -04:00
2025-12-03 13:39:32 -05:00
const { icon, title, subtitle } = $derived(asQueueItem($t, queue));
const { statistics } = $derived(queue);
2025-11-14 14:42:00 -05:00
let waitingCount = $derived(statistics.waiting + statistics.paused + statistics.delayed);
2025-12-03 13:39:32 -05:00
let isIdle = $derived(statistics.active + statistics.waiting === 0 && !queue.isPaused);
let multipleButtons = $derived(allText || refreshText);
2025-04-28 09:53:53 -04:00
const commonClasses = 'flex place-items-center justify-between w-full py-2 sm:py-4 pe-4 ps-6';
</script>
<div class="flex flex-col overflow-hidden rounded-2xl bg-gray-100 dark:bg-immich-dark-gray sm:flex-row sm:rounded-9">
<div class="flex w-full flex-col">
2025-12-03 13:39:32 -05:00
{#if queue.isPaused}
<QueueCardBadge color="warning">{$t('paused')}</QueueCardBadge>
{:else if statistics.active > 0}
<QueueCardBadge color="success">{$t('active')}</QueueCardBadge>
{/if}
<div class="flex flex-col gap-2 p-5 sm:p-7 md:p-9">
2025-12-03 13:39:32 -05:00
<div class="flex items-center gap-2 text-xl font-semibold text-primary">
<Link class="flex items-center gap-2 hover:underline" href={getQueueDetailUrl(queue)} underline={false}>
2025-09-16 21:40:43 +02:00
<Icon {icon} size="1.25em" class="hidden shrink-0 sm:block" />
<span class="uppercase">{title}</span>
2025-12-03 13:39:32 -05:00
</Link>
<IconButton
color="primary"
icon={mdiChartLine}
aria-label={$t('view_details')}
size="small"
variant="ghost"
href={getQueueDetailUrl(queue)}
/>
<div class="flex gap-2">
2025-11-14 14:42:00 -05:00
{#if statistics.failed > 0}
2025-06-12 19:06:38 -04:00
<Badge>
<div class="flex flex-row gap-1">
<span class="text-sm">
2025-11-14 14:42:00 -05:00
{$t('admin.jobs_failed', { values: { jobCount: statistics.failed.toLocaleString($locale) } })}
</span>
<IconButton
color="primary"
icon={mdiClose}
aria-label={$t('clear_message')}
size="tiny"
shape="round"
2025-11-14 14:42:00 -05:00
onclick={() => onCommand({ command: QueueCommand.ClearFailed, force: false })}
/>
</div>
</Badge>
{/if}
2025-11-14 14:42:00 -05:00
{#if statistics.delayed > 0}
2025-06-12 19:06:38 -04:00
<Badge>
<span class="text-sm">
2025-11-14 14:42:00 -05:00
{$t('admin.jobs_delayed', { values: { jobCount: statistics.delayed.toLocaleString($locale) } })}
</span>
</Badge>
{/if}
</div>
</div>
{#if subtitle}
<div class="whitespace-pre-line text-sm dark:text-white">{subtitle}</div>
{/if}
{#if description}
{@const SvelteComponent = description}
<div class="text-sm dark:text-white">
<SvelteComponent />
</div>
{/if}
2023-06-07 12:10:31 -04:00
<div class="mt-2 flex w-full max-w-md flex-col sm:flex-row">
<div
2025-04-28 09:53:53 -04:00
class="{commonClasses} rounded-t-lg bg-immich-primary text-white dark:bg-immich-dark-primary dark:text-immich-dark-gray sm:rounded-s-lg sm:rounded-e-none"
>
feat(web): translations (#9854) * First test * Added translation using Weblate (French) * Translated using Weblate (German) Currently translated at 100.0% (4 of 4 strings) Translation: immich/web Translate-URL: http://familie-mach.net/projects/immich/web/de/ * Translated using Weblate (French) Currently translated at 100.0% (4 of 4 strings) Translation: immich/web Translate-URL: http://familie-mach.net/projects/immich/web/fr/ * Further testing * Further testing * Translated using Weblate (German) Currently translated at 100.0% (18 of 18 strings) Translation: immich/web Translate-URL: http://familie-mach.net/projects/immich/web/de/ * Further work * Update string file. * More strings * Automatically changed strings * Add automatically translated german file for testing purposes * Fix merge-face-selector component * Make server stats strings uppercase * Fix uppercase string * Fix some strings in jobs-panel * Fix lower and uppercase strings. Add a few additional string. Fix a few unnecessary replacements * Update german test translations * Fix typo in locales file * Change string keys * Extract more strings * Extract and replace some more strings * Update testtranslationfile * Change translation keys * Fix rebase errors * Fix one more rebase error * Remove german translation file * Co-authored-by: Daniel Dietzler <danieldietzler@users.noreply.github.com> * chore: clean up translations * chore: add new line * fix formatting * chore: fixes * fix: loading and tests --------- Co-authored-by: root <root@Blacki> Co-authored-by: admin <admin@example.com> Co-authored-by: Jason Rasmussen <jrasm91@gmail.com> Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>
2024-06-04 21:53:00 +02:00
<p>{$t('active')}</p>
<p class="text-2xl">
2025-11-14 14:42:00 -05:00
{statistics.active.toLocaleString($locale)}
</p>
</div>
<div
2025-04-28 09:53:53 -04:00
class="{commonClasses} flex-row-reverse rounded-b-lg bg-gray-200 text-immich-dark-bg dark:bg-gray-700 dark:text-immich-gray sm:rounded-s-none sm:rounded-e-lg"
>
<p class="text-2xl">
{waitingCount.toLocaleString($locale)}
</p>
feat(web): translations (#9854) * First test * Added translation using Weblate (French) * Translated using Weblate (German) Currently translated at 100.0% (4 of 4 strings) Translation: immich/web Translate-URL: http://familie-mach.net/projects/immich/web/de/ * Translated using Weblate (French) Currently translated at 100.0% (4 of 4 strings) Translation: immich/web Translate-URL: http://familie-mach.net/projects/immich/web/fr/ * Further testing * Further testing * Translated using Weblate (German) Currently translated at 100.0% (18 of 18 strings) Translation: immich/web Translate-URL: http://familie-mach.net/projects/immich/web/de/ * Further work * Update string file. * More strings * Automatically changed strings * Add automatically translated german file for testing purposes * Fix merge-face-selector component * Make server stats strings uppercase * Fix uppercase string * Fix some strings in jobs-panel * Fix lower and uppercase strings. Add a few additional string. Fix a few unnecessary replacements * Update german test translations * Fix typo in locales file * Change string keys * Extract more strings * Extract and replace some more strings * Update testtranslationfile * Change translation keys * Fix rebase errors * Fix one more rebase error * Remove german translation file * Co-authored-by: Daniel Dietzler <danieldietzler@users.noreply.github.com> * chore: clean up translations * chore: add new line * fix formatting * chore: fixes * fix: loading and tests --------- Co-authored-by: root <root@Blacki> Co-authored-by: admin <admin@example.com> Co-authored-by: Jason Rasmussen <jrasm91@gmail.com> Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>
2024-06-04 21:53:00 +02:00
<p>{$t('waiting')}</p>
</div>
</div>
</div>
</div>
<div class="flex w-full flex-row overflow-hidden sm:w-32 sm:flex-col">
{#if disabled}
2025-12-03 13:39:32 -05:00
<QueueCardButton
disabled={true}
color="light-gray"
2025-11-14 14:42:00 -05:00
onClick={() => onCommand({ command: QueueCommand.Start, force: false })}
>
2025-09-16 21:40:43 +02:00
<Icon icon={mdiAlertCircle} size="36" />
<span class="uppercase">{$t('disabled')}</span>
2025-12-03 13:39:32 -05:00
</QueueCardButton>
{/if}
{#if !disabled && !isIdle}
{#if waitingCount > 0}
2025-12-03 13:39:32 -05:00
<QueueCardButton color="gray" onClick={() => onCommand({ command: QueueCommand.Empty, force: false })}>
2025-09-16 21:40:43 +02:00
<Icon icon={mdiClose} size="24" />
<span class="uppercase">{$t('clear')}</span>
2025-12-03 13:39:32 -05:00
</QueueCardButton>
{/if}
2025-12-03 13:39:32 -05:00
{#if queue.isPaused}
{@const size = waitingCount > 0 ? '24' : '48'}
2025-12-03 13:39:32 -05:00
<QueueCardButton color="light-gray" onClick={() => onCommand({ command: QueueCommand.Resume, force: false })}>
<!-- size property is not reactive, so have to use width and height -->
2025-09-16 21:40:43 +02:00
<Icon icon={mdiFastForward} {size} />
<span class="uppercase">{$t('resume')}</span>
2025-12-03 13:39:32 -05:00
</QueueCardButton>
{:else}
2025-12-03 13:39:32 -05:00
<QueueCardButton color="light-gray" onClick={() => onCommand({ command: QueueCommand.Pause, force: false })}>
2025-09-16 21:40:43 +02:00
<Icon icon={mdiPause} size="24" />
<span class="uppercase">{$t('pause')}</span>
2025-12-03 13:39:32 -05:00
</QueueCardButton>
{/if}
{/if}
{#if !disabled && multipleButtons && isIdle}
{#if allText}
2025-12-03 13:39:32 -05:00
<QueueCardButton color="dark-gray" onClick={() => onCommand({ command: QueueCommand.Start, force: true })}>
2025-09-16 21:40:43 +02:00
<Icon icon={mdiAllInclusive} size="24" />
<span class="uppercase">{allText}</span>
2025-12-03 13:39:32 -05:00
</QueueCardButton>
{/if}
{#if refreshText}
2025-12-03 13:39:32 -05:00
<QueueCardButton color="gray" onClick={() => onCommand({ command: QueueCommand.Start, force: undefined })}>
2025-09-16 21:40:43 +02:00
<Icon icon={mdiImageRefreshOutline} size="24" />
<span class="uppercase">{refreshText}</span>
2025-12-03 13:39:32 -05:00
</QueueCardButton>
{/if}
2025-12-03 13:39:32 -05:00
<QueueCardButton color="light-gray" onClick={() => onCommand({ command: QueueCommand.Start, force: false })}>
2025-09-16 21:40:43 +02:00
<Icon icon={mdiSelectionSearch} size="24" />
<span class="uppercase">{missingText}</span>
2025-12-03 13:39:32 -05:00
</QueueCardButton>
{/if}
{#if !disabled && !multipleButtons && isIdle}
2025-12-03 13:39:32 -05:00
<QueueCardButton color="light-gray" onClick={() => onCommand({ command: QueueCommand.Start, force: false })}>
2025-09-16 21:40:43 +02:00
<Icon icon={mdiPlay} size="48" />
<span class="uppercase">{missingText}</span>
2025-12-03 13:39:32 -05:00
</QueueCardButton>
{/if}
</div>
</div>