merge: remote-tracking branch 'origin/main' into feat/integrity-checks-izzy

This commit is contained in:
izzy
2026-01-14 12:25:28 +00:00
89 changed files with 13815 additions and 4655 deletions
@@ -31,7 +31,7 @@
{#if isOwned}
<Textarea
bind:value={description}
class="outline-none border-b border-gray-500 bg-transparent ring-0 focus:ring-0 resize-none focus:border-b-2 focus:border-immich-primary dark:focus:border-immich-dark-primary dark:bg-transparent"
class="outline-none border-b max-h-32 border-transparent pl-0 bg-transparent ring-0 focus:ring-0 resize-none focus:border-b-2 focus:border-immich-primary dark:focus:border-immich-dark-primary dark:bg-transparent"
rows={1}
grow
shape="rectangle"
@@ -44,7 +44,7 @@
}))}
/>
{:else if description}
<p class="break-words whitespace-pre-line w-full text-black dark:text-white text-base">
<p class="wrap-break-words whitespace-pre-line w-full text-black dark:text-white text-base">
{description}
</p>
{/if}
@@ -138,7 +138,11 @@
const normalizedSearchQuery = $derived(normalizeSearchString(searchQuery));
let filteredAlbums = $derived(
normalizedSearchQuery
? albums.filter(({ albumName }) => normalizeSearchString(albumName).includes(normalizedSearchQuery))
? albums.filter(
({ albumName, description }) =>
normalizeSearchString(albumName).includes(normalizedSearchQuery) ||
normalizeSearchString(description).includes(normalizedSearchQuery),
)
: albums,
);
@@ -566,7 +566,7 @@
<div
transition:fly={{ duration: 150 }}
id="detail-panel"
class="row-start-1 row-span-4 w-[360px] overflow-y-auto transition-all dark:border-l dark:border-s-immich-dark-gray bg-light"
class="row-start-1 row-span-4 w-90 overflow-y-auto transition-all dark:border-l dark:border-s-immich-dark-gray bg-light"
translate="yes"
>
<DetailPanel {asset} currentAlbum={album} albums={appearsInAlbums} />
@@ -577,7 +577,7 @@
<div
transition:fly={{ duration: 150 }}
id="editor-panel"
class="row-start-1 row-span-4 w-[400px] overflow-y-auto transition-all dark:border-l dark:border-s-immich-dark-gray"
class="row-start-1 row-span-4 w-100 overflow-y-auto transition-all dark:border-l dark:border-s-immich-dark-gray"
translate="yes"
>
<EditorPanel {asset} onClose={closeEditor} />
@@ -624,7 +624,7 @@
<div
transition:fly={{ duration: 150 }}
id="activity-panel"
class="row-start-1 row-span-5 w-[360px] md:w-[460px] overflow-y-auto transition-all dark:border-l dark:border-s-immich-dark-gray"
class="row-start-1 row-span-5 w-90 md:w-115 overflow-y-auto transition-all dark:border-l dark:border-s-immich-dark-gray"
translate="yes"
>
<ActivityViewer
@@ -33,7 +33,7 @@
<section class="px-4 mt-10">
<Textarea
bind:value={description}
class="max-h-40 outline-none border-b border-gray-500 bg-transparent ring-0 focus:ring-0 resize-none focus:border-b-2 focus:border-immich-primary dark:focus:border-immich-dark-primary dark:bg-transparent"
class="max-h-40 pl-0 outline-none border-b border-gray-500 bg-transparent ring-0 focus:ring-0 resize-none focus:border-b-2 focus:border-immich-primary dark:focus:border-immich-dark-primary dark:bg-transparent"
rows={1}
grow
shape="rectangle"
@@ -79,7 +79,7 @@
<div class="absolute flex h-16 w-full place-items-center justify-between border-b p-2 text-dark">
<div class="flex gap-2 items-center">
{#if title}
<div class="font-medium outline-none pe-8" tabindex="-1" id={headerId}>{title}</div>
<div class="outline-none pe-8" tabindex="-1" id={headerId}>{title}</div>
{/if}
{#if description}
<p class="text-sm text-gray-400 dark:text-gray-600">{description}</p>
@@ -27,13 +27,13 @@
{#if icon}
<Icon {icon} size="40" />
{/if}
<Text size="large" fontWeight="bold" class="uppercase">{title}</Text>
<Text size="giant" class="font-medium">{title}</Text>
</div>
<div class="relative mx-auto font-mono text-2xl font-semibold">
<span class="text-gray-400 dark:text-gray-600">{zeros()}</span><span>{value}</span>
<div class="relative mx-auto font-immich-mono text-2xl font-medium">
<span class="text-gray-300 dark:text-gray-600">{zeros()}</span><span>{value}</span>
{#if unit}
<Code color="muted" class="absolute -top-5 end-1 font-light p-0">{unit}</Code>
<Code color="muted" class="font-immich-mono absolute -top-5 end-1 font-light p-0">{unit}</Code>
{/if}
</div>
@@ -3,7 +3,7 @@
import { locale } from '$lib/stores/preferences.store';
import { getByteUnitString, getBytesWithUnit } from '$lib/utils/byte-units';
import type { ServerStatsResponseDto } from '@immich/sdk';
import { Code, Heading, Icon, Text } from '@immich/ui';
import { Code, Icon, Text } from '@immich/ui';
import { mdiCameraIris, mdiChartPie, mdiPlayCircle } from '@mdi/js';
import { t } from 'svelte-i18n';
@@ -13,8 +13,7 @@
const { stats }: Props = $props();
const zeros = (value: number) => {
const maxLength = 13;
const zeros = (value: number, maxLength = 13) => {
const valueLength = value.toString().length;
const zeroLength = maxLength - valueLength;
@@ -27,7 +26,7 @@
<div class="flex flex-col gap-5 my-4">
<div>
<Heading size="tiny" class="mb-2">{$t('total_usage')}</Heading>
<Text class="mb-2 font-medium">{$t('total_usage')}</Text>
<div class="hidden justify-between lg:flex gap-4">
<StatsCard icon={mdiCameraIris} title={$t('photos')} value={stats.photos} />
@@ -40,38 +39,35 @@
<div class="flex flex-wrap gap-x-12">
<div class="flex flex-1 place-items-center gap-4 text-primary">
<Icon icon={mdiCameraIris} size="25" />
<Text fontWeight="bold" class="uppercase">{$t('photos')}</Text>
<Text class="font-medium" size="medium">{$t('photos')}</Text>
</div>
<div class="relative text-center font-mono text-2xl font-semibold">
<span class="text-gray-400 dark:text-gray-600">{zeros(stats.photos)}</span><span class="text-primary"
>{stats.photos}</span
>
<div class="relative text-center font-immich-mono text-2xl font-medium">
<span class="text-light-300">{zeros(stats.photos)}</span><span class="text-primary">{stats.photos}</span>
</div>
</div>
<div class="flex flex-wrap gap-x-12">
<div class="flex flex-1 place-items-center gap-4 text-primary">
<Icon icon={mdiPlayCircle} size="25" />
<Text fontWeight="bold" class="uppercase">{$t('videos')}</Text>
<Text class="font-medium" size="medium">{$t('videos')}</Text>
</div>
<div class="relative text-center font-mono text-2xl font-semibold">
<span class="text-gray-400 dark:text-gray-600">{zeros(stats.videos)}</span><span class="text-primary"
>{stats.videos}</span
>
<div class="relative text-center font-immich-mono text-2xl font-medium">
<span class="text-light-300">{zeros(stats.videos)}</span><span class="text-primary">{stats.videos}</span>
</div>
</div>
<div class="flex flex-wrap gap-x-5">
<div class="flex flex-1 flex-nowrap place-items-center gap-4 text-primary">
<Icon icon={mdiChartPie} size="25" />
<Text fontWeight="bold" class="uppercase">{$t('storage')}</Text>
<Text class="font-medium" size="medium">{$t('storage')}</Text>
</div>
<div class="relative flex text-center font-mono text-2xl font-semibold">
<span class="text-gray-400 dark:text-gray-600">{zeros(statsUsage)}</span><span class="text-primary"
>{statsUsage}</span
>
<Code color="muted" class="font-light">{statsUsageUnit}</Code>
<div class="relative flex text-center font-immich-mono text-2xl font-medium">
<span class="text-light-300">{zeros(statsUsage)}</span><span class="text-primary">{statsUsage}</span>
<div class="absolute -right-1.5 -bottom-4">
<Code color="muted" class="text-xs font-light font-immich-mono">{statsUsageUnit}</Code>
</div>
</div>
</div>
</div>
@@ -79,7 +75,7 @@
</div>
<div>
<Heading size="tiny" class="mb-2">{$t('user_usage_detail')}</Heading>
<Text class="mt-6 mb-2 font-medium">{$t('user_usage_detail')}</Text>
<table class="mt-5 w-full text-start">
<thead
class="mb-4 flex h-12 w-full rounded-md border bg-gray-50 text-primary dark:border-immich-dark-gray dark:bg-immich-dark-gray"
@@ -467,7 +467,7 @@
/>
{#if showAssetName && !isTimelineAsset(asset)}
<div
class="absolute text-center p-1 text-xs font-mono font-semibold w-full bottom-0 bg-linear-to-t bg-slate-50/75 dark:bg-slate-800/75 overflow-clip text-ellipsis whitespace-pre-wrap"
class="absolute text-center p-1 text-xs font-immich-mono font-semibold w-full bottom-0 bg-linear-to-t bg-slate-50/75 dark:bg-slate-800/75 overflow-clip text-ellipsis whitespace-pre-wrap"
>
{asset.originalFileName}
</div>
@@ -361,7 +361,7 @@
>
{#snippet children({ feature })}
<div
class="rounded-full w-10 h-10 bg-immich-primary text-white flex justify-center items-center font-mono font-bold shadow-lg hover:bg-immich-dark-primary transition-all duration-200 hover:text-immich-dark-bg opacity-90"
class="rounded-full w-10 h-10 bg-immich-primary text-white flex justify-center items-center font-immich-mono font-bold shadow-lg hover:bg-immich-dark-primary transition-all duration-200 hover:text-immich-dark-bg opacity-90"
>
{feature.properties?.point_count?.toLocaleString()}
</div>
@@ -64,7 +64,7 @@
</script>
<div
class="border-2 rounded-2xl border-primary/20 my-4 px-6 py-4 transition-all {isOpen
class="border-2 rounded-2xl border-primary/20 mt-4 px-6 py-4 transition-all {isOpen
? 'border-primary/60 shadow-md'
: ''}"
bind:this={accordionElement}
@@ -35,11 +35,11 @@
let closestLanguage = $derived(getClosestAvailableLocale([$lang], langCodes));
</script>
<div class="max-w-[300px]">
<div class="max-w-75">
{#if showSettingDescription}
<div>
<div class="flex h-6.5 place-items-center gap-1">
<Label>{$t('language')}</Label>
<Label size="small">{$t('language')}</Label>
</div>
<Text size="small" color="muted">{$t('language_setting_description')}</Text>
@@ -62,12 +62,12 @@
>
{#if $connected}
<div class="flex gap-2 place-items-center place-content-center">
<div class="w-[7px] h-[7px] bg-green-500 rounded-full"></div>
<div class="w-1.75 h-1.75 bg-green-500 rounded-full"></div>
<p class="dark:text-immich-gray">{$t('server_online')}</p>
</div>
{:else}
<div class="flex gap-2 place-items-center place-content-center">
<div class="w-[7px] h-[7px] bg-red-500 rounded-full"></div>
<div class="w-1.75 h-1.75 bg-red-500 rounded-full"></div>
<p class="text-red-500">{$t('server_offline')}</p>
</div>
{/if}
@@ -66,8 +66,8 @@
})}
</p>
<div class="mt-4 h-[7px] w-full rounded-full bg-gray-200 dark:bg-gray-700">
<div class="h-[7px] rounded-full {usageClasses}" style="width: {usedPercentage}%"></div>
<div class="mt-4 h-1.75 w-full rounded-full bg-gray-200 dark:bg-gray-700">
<div class="h-1.75 rounded-full {usageClasses}" style="width: {usedPercentage}%"></div>
</div>
{:else}
<div class="mt-2">
@@ -51,7 +51,7 @@
/>
</li>
{#each parents as parent (parent)}
<li class="flex gap-2 items-center font-mono text-sm text-nowrap text-primary">
<li class="flex gap-2 items-center font-immich-mono text-sm text-nowrap text-primary">
<Icon icon={mdiChevronRight} class="text-gray-500 dark:text-gray-300" size="16" aria-hidden />
<a class="underline hover:font-semibold whitespace-pre-wrap" href={getLink(parent.path)}>
{parent.value}
@@ -59,7 +59,7 @@
</li>
{/each}
<li class="flex gap-2 items-center font-mono text-sm text-nowrap text-primary">
<li class="flex gap-2 items-center font-immich-mono text-sm text-nowrap text-primary">
<Icon icon={mdiChevronRight} class="text-gray-500 dark:text-gray-300" size="16" aria-hidden />
<p class="cursor-default whitespace-pre-wrap">{node.value}</p>
</li>
@@ -42,7 +42,9 @@
size="20"
/>
</div>
<span class="text-nowrap overflow-hidden text-ellipsis font-mono ps-1 pt-1 whitespace-pre-wrap">{node.value}</span>
<span class="text-nowrap overflow-hidden text-ellipsis font-immich-mono ps-1 pt-1 whitespace-pre-wrap"
>{node.value}</span
>
</a>
{#if isOpen}
@@ -543,7 +543,7 @@
in:fade={{ duration: 200 }}
out:fade={{ duration: 200 }}
>
<Icon icon={mdiPlay} size="20" class="-rotate-90 relative top-[9px] -end-0.5" />
<Icon icon={mdiPlay} size="20" class="-rotate-90 relative top-2.25 -end-0.5" />
<Icon icon={mdiPlay} size="20" class="rotate-90 relative top-px -end-0.5" />
{#if (timelineManager.scrolling && scrollHoverLabel) || isHover || isDragging}
<p
@@ -588,7 +588,7 @@
>
{#if !usingMobileDevice}
{#if segment.hasLabel}
<div class="absolute end-5 text-[12px] dark:text-immich-dark-fg font-immich-mono bottom-0">
<div class="absolute end-5 text-[13px] dark:text-immich-dark-fg font-immich-mono bottom-0">
{segment.year}
</div>
{/if}
@@ -128,7 +128,7 @@
maxlength="1"
bind:this={pinCodeInputElements[index]}
id="pin-code-{index}"
class="h-12 w-10 rounded-xl border-2 border-suble dark:border-gray-700 text-center text-lg font-medium focus:border-immich-primary focus:ring-primary dark:focus:border-primary font-mono bg-white dark:bg-light"
class="h-12 w-10 rounded-xl border-2 border-suble dark:border-gray-700 text-center text-lg font-medium focus:border-immich-primary focus:ring-primary dark:focus:border-primary font-immich-mono bg-white dark:bg-light"
bind:value={pinValues[index]}
onkeydown={handleKeydown}
oninput={(event) => handleInput(event, index)}
@@ -59,7 +59,7 @@
<section class="my-4">
<div in:fade={{ duration: 500 }}>
<div class="ms-8 mt-4 flex flex-col gap-4">
<div class="ms-8 mt-4 flex flex-col gap-6">
<Field label={$t('theme_selection')} description={$t('theme_selection_description')}>
<Switch checked={themeManager.theme.system} onCheckedChange={(checked) => themeManager.setSystem(checked)} />
</Field>
@@ -68,7 +68,7 @@
<Field label={$t('default_locale')} description={$t('default_locale_description')}>
<Switch checked={$locale == 'default'} onCheckedChange={handleToggleLocaleBrowser} />
<Text size="small" class="mt-2">{selectedDate}</Text>
<Text size="small" class="mt-2 font-immich-mono text-sm">{selectedDate}</Text>
</Field>
{#if $locale !== 'default'}
@@ -42,7 +42,7 @@
<section class="my-4">
<div in:fade={{ duration: 500 }}>
<form autocomplete="off" {onsubmit}>
<div class="ms-4 mt-4 flex flex-col gap-4">
<div class="ms-4 mt-4 flex flex-col gap-6">
<Field label={$t('enable')} description={$t('notification_toggle_setting_description')}>
<Switch bind:checked={emailNotificationsEnabled} />
</Field>
@@ -39,7 +39,7 @@
checked={selectAllSubItems}
onCheckedChange={handleSelectAllSubItems}
/>
<Label label={title} for="permission-{title}" class="font-mono text-primary text-lg" />
<Label label={title} for="permission-{title}" class="font-immich-mono text-primary text-lg" />
</div>
<div class="mx-6 mt-3 grid grid-cols-3 gap-2">
{#each subItems as item (item)}
@@ -50,7 +50,7 @@
checked={selectedItems.includes(item)}
onCheckedChange={() => handleToggleItem(item)}
/>
<Label label={item} for="permission-{item}" class="text-sm font-mono" />
<Label label={item} for="permission-{item}" class="text-sm font-immich-mono" />
</div>
{/each}
</div>
@@ -64,7 +64,7 @@
>
<td class="w-1/4 text-ellipsis px-4 text-sm overflow-hidden">{key.name}</td>
<td
class="w-1/4 text-ellipsis px-4 text-xs overflow-hidden line-clamp-3 break-all font-mono"
class="w-1/4 text-ellipsis px-4 text-xs overflow-hidden line-clamp-3 break-all font-immich-mono"
title={JSON.stringify(key.permissions, undefined, 2)}>{key.permissions}</td
>
<td class="w-1/4 text-ellipsis px-4 text-sm overflow-hidden"