(cherry picked from commit 2576ee28cfb790e99ddf401a129e1d9a18713121)
140 lines
5.4 KiB
Svelte
140 lines
5.4 KiB
Svelte
<script lang="ts">
|
|
import ImageThumbnail from '#lib/components/assets/thumbnail/ImageThumbnail.svelte';
|
|
import { assetViewerManager } from '#lib/managers/asset-viewer-manager.svelte.js';
|
|
import { authManager } from '#lib/managers/auth-manager.svelte.js';
|
|
import { Route } from '#lib/route.js';
|
|
import { faceManager } from '#lib/stores/face.svelte.js';
|
|
import { locale } from '#lib/stores/preferences.store.js';
|
|
import { getPeopleThumbnailUrl } from '#lib/utils.js';
|
|
import { type AssetResponseDto } from '@immich/sdk';
|
|
import { IconButton, Text } from '@immich/ui';
|
|
import { mdiEye, mdiEyeOff, mdiPencil, mdiPlus } from '@mdi/js';
|
|
import { DateTime } from 'luxon';
|
|
import { t } from 'svelte-i18n';
|
|
|
|
type Props = {
|
|
asset: AssetResponseDto;
|
|
isOwner: boolean;
|
|
previousRoute: string;
|
|
};
|
|
|
|
const { asset, isOwner, previousRoute }: Props = $props();
|
|
|
|
const people = $derived(Array.from(faceManager.people));
|
|
const visiblePeople = $derived(
|
|
people
|
|
.filter((p) => assetViewerManager.isShowingHiddenPeople || !p.isHidden)
|
|
.map((person) => {
|
|
if (!person.birthDate) {
|
|
return { formattedBirthDate: undefined, formattedAge: undefined, ...person };
|
|
}
|
|
const personBirthDate = DateTime.fromISO(person.birthDate);
|
|
const ageInYears = Math.floor(DateTime.fromISO(asset.localDateTime).diff(personBirthDate, 'years').years);
|
|
const ageInMonths = Math.floor(DateTime.fromISO(asset.localDateTime).diff(personBirthDate, 'months').months);
|
|
|
|
let formattedAge;
|
|
if (ageInYears < 0) {
|
|
return { formattedBirthDate: undefined, formattedAge: undefined, ...person };
|
|
}
|
|
if (ageInMonths < 12) {
|
|
formattedAge = $t('age_months', { values: { months: ageInMonths } });
|
|
} else if (ageInMonths > 12 && ageInMonths < 24) {
|
|
formattedAge = $t('age_year_months', { values: { months: ageInMonths - 12 } });
|
|
} else {
|
|
formattedAge = $t('age_years', { values: { years: ageInYears } });
|
|
}
|
|
|
|
const formattedBirthDate = personBirthDate.toLocaleString(
|
|
{
|
|
month: 'long',
|
|
day: 'numeric',
|
|
year: 'numeric',
|
|
},
|
|
{ locale: $locale },
|
|
);
|
|
return { formattedBirthDate, formattedAge, ...person };
|
|
}),
|
|
);
|
|
</script>
|
|
|
|
{#if !authManager.isSharedLink}
|
|
<section class="px-4 pt-4 text-sm">
|
|
{#if isOwner || visiblePeople.length > 0}
|
|
<div class="flex h-10 w-full items-center justify-between">
|
|
<Text size="small" color="muted">{$t('people')}</Text>
|
|
<div class="flex items-center gap-2">
|
|
{#if isOwner}
|
|
{#if people.some((person) => person.isHidden)}
|
|
<IconButton
|
|
aria-label={$t('show_hidden_people')}
|
|
icon={assetViewerManager.isShowingHiddenPeople ? mdiEyeOff : mdiEye}
|
|
size="medium"
|
|
shape="round"
|
|
color="secondary"
|
|
variant="ghost"
|
|
onclick={() => assetViewerManager.toggleHiddenPeople()}
|
|
/>
|
|
{/if}
|
|
<IconButton
|
|
aria-label={$t('tag_people')}
|
|
icon={mdiPlus}
|
|
size="medium"
|
|
shape="round"
|
|
color="secondary"
|
|
variant="ghost"
|
|
onclick={() => assetViewerManager.toggleFaceEditMode()}
|
|
/>
|
|
|
|
{#if faceManager.data.length > 0}
|
|
<IconButton
|
|
aria-label={$t('edit_people')}
|
|
icon={mdiPencil}
|
|
size="medium"
|
|
shape="round"
|
|
color="secondary"
|
|
variant="ghost"
|
|
onclick={() => assetViewerManager.openEditFacesPanel()}
|
|
/>
|
|
{/if}
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
{/if}
|
|
|
|
<div class="mt-2 grid {visiblePeople.length <= 6 ? 'grid-cols-3 gap-3' : 'grid-cols-4 gap-2'}">
|
|
{#each visiblePeople as person (person.id)}
|
|
{const personFaces = $derived(faceManager.facesByPersonId.get(person.id) ?? [])}
|
|
{const isHighlighted = $derived(
|
|
personFaces.some((f) => assetViewerManager.highlightedFaces.some((b) => b.id === f.id)),
|
|
)}
|
|
<a
|
|
class="group outline-none"
|
|
href={Route.viewPerson(person, { previousRoute })}
|
|
onfocus={() => assetViewerManager.setHighlightedFaces(personFaces)}
|
|
onblur={() => assetViewerManager.clearHighlightedFaces()}
|
|
onpointerenter={() => assetViewerManager.setHighlightedFaces(personFaces)}
|
|
onpointerleave={() => assetViewerManager.clearHighlightedFaces()}
|
|
>
|
|
<ImageThumbnail
|
|
curve
|
|
shadow
|
|
url={getPeopleThumbnailUrl(person)}
|
|
altText={person.name}
|
|
title={person.name}
|
|
widthStyle="100%"
|
|
hidden={person.isHidden}
|
|
highlighted={isHighlighted}
|
|
class="outline-offset-2 outline-immich-primary group-focus-visible:outline-2 dark:outline-immich-dark-primary"
|
|
/>
|
|
<p class="mt-1 truncate font-medium" title={person.name}>{person.name}</p>
|
|
{#if person.birthDate && person.formattedAge}
|
|
<p class="font-light {visiblePeople.length > 6 ? 'text-xs' : ''}" title={person.formattedBirthDate!}>
|
|
{person.formattedAge}
|
|
</p>
|
|
{/if}
|
|
</a>
|
|
{/each}
|
|
</div>
|
|
</section>
|
|
{/if}
|