mirror of
https://github.com/immich-app/immich.git
synced 2026-05-18 03:10:24 +03:00
refactor!: remove asset faces from AssetResponseDto (#27779)
* refactor!: remove faces from `people` in AssetResposnseDto * chore: tests * chore: e2e generator * chore: code review readonly * chore: code review changes * chore: cleanup * fix: openapi * chore: format --------- Co-authored-by: Jason Rasmussen <jason@rasm.me>
This commit is contained in:
@@ -15,6 +15,7 @@
|
||||
import { editManager, EditToolType } from '$lib/managers/edit/edit-manager.svelte';
|
||||
import { eventManager } from '$lib/managers/event-manager.svelte';
|
||||
import { getAssetActions } from '$lib/services/asset.service';
|
||||
import { faceManager } from '$lib/stores/face.svelte';
|
||||
import { ocrManager } from '$lib/stores/ocr.svelte';
|
||||
import { alwaysLoadOriginalVideo } from '$lib/stores/preferences.store';
|
||||
import { SlideshowNavigation, SlideshowState, slideshowStore } from '$lib/stores/slideshow.store';
|
||||
@@ -333,6 +334,7 @@
|
||||
case AssetAction.SET_PERSON_FEATURED_PHOTO: {
|
||||
const assetInfo = await getAssetInfo({ id: asset.id });
|
||||
cursor.current = { ...asset, people: assetInfo.people };
|
||||
eventManager.emit('AssetUpdate', cursor.current);
|
||||
break;
|
||||
}
|
||||
case AssetAction.RATING: {
|
||||
@@ -376,11 +378,14 @@
|
||||
const refresh = async () => {
|
||||
await refreshStack();
|
||||
ocrManager.clear();
|
||||
faceManager.clear();
|
||||
if (!sharedLink) {
|
||||
if (previewStackedAsset) {
|
||||
await ocrManager.getAssetOcr(previewStackedAsset.id);
|
||||
await faceManager.getAssetFaces(previewStackedAsset.id);
|
||||
}
|
||||
await ocrManager.getAssetOcr(asset.id);
|
||||
await faceManager.getAssetFaces(asset.id);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { assetViewerManager } from '$lib/managers/asset-viewer-manager.svelte';
|
||||
import { authManager } from '$lib/managers/auth-manager.svelte';
|
||||
import { Route } from '$lib/route';
|
||||
import { faceManager } from '$lib/stores/face.svelte';
|
||||
import { locale } from '$lib/stores/preferences.store';
|
||||
import { getPeopleThumbnailUrl } from '$lib/utils';
|
||||
import { type AssetResponseDto } from '@immich/sdk';
|
||||
@@ -19,8 +20,7 @@
|
||||
|
||||
const { asset, isOwner, previousRoute }: Props = $props();
|
||||
|
||||
const unassignedFaces = $derived(asset.unassignedFaces || []);
|
||||
const people = $derived(asset.people || []);
|
||||
const people = $derived(Array.from(faceManager.people));
|
||||
const visiblePeople = $derived(
|
||||
people
|
||||
.filter((p) => assetViewerManager.isShowingHiddenPeople || !p.isHidden)
|
||||
@@ -82,7 +82,7 @@
|
||||
onclick={() => assetViewerManager.toggleFaceEditMode()}
|
||||
/>
|
||||
|
||||
{#if people.length > 0 || unassignedFaces.length > 0}
|
||||
{#if faceManager.data.length > 0}
|
||||
<IconButton
|
||||
aria-label={$t('edit_people')}
|
||||
icon={mdiPencil}
|
||||
@@ -98,15 +98,14 @@
|
||||
|
||||
<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 isHighlighted = person.faces.some((f) =>
|
||||
assetViewerManager.highlightedFaces.some((b) => b.id === f.id),
|
||||
)}
|
||||
{@const personFaces = faceManager.facesByPersonId.get(person.id) ?? []}
|
||||
{@const isHighlighted = personFaces.some((f) => assetViewerManager.highlightedFaces.some((b) => b.id === f.id))}
|
||||
<a
|
||||
class="group outline-none"
|
||||
href={Route.viewPerson(person, { previousRoute })}
|
||||
onfocus={() => assetViewerManager.setHighlightedFaces(person.faces)}
|
||||
onfocus={() => assetViewerManager.setHighlightedFaces(personFaces)}
|
||||
onblur={() => assetViewerManager.clearHighlightedFaces()}
|
||||
onpointerenter={() => assetViewerManager.setHighlightedFaces(person.faces)}
|
||||
onpointerenter={() => assetViewerManager.setHighlightedFaces(personFaces)}
|
||||
onpointerleave={() => assetViewerManager.clearHighlightedFaces()}
|
||||
>
|
||||
<ImageThumbnail
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
import AssetViewerEvents from '$lib/components/AssetViewerEvents.svelte';
|
||||
import { assetViewerManager, type Faces } from '$lib/managers/asset-viewer-manager.svelte';
|
||||
import { castManager } from '$lib/managers/cast-manager.svelte';
|
||||
import { faceManager } from '$lib/stores/face.svelte';
|
||||
import { ocrManager } from '$lib/stores/ocr.svelte';
|
||||
import { SlideshowLook, SlideshowState, slideshowStore } from '$lib/stores/slideshow.store';
|
||||
import { handlePromiseError } from '$lib/utils';
|
||||
@@ -157,13 +158,14 @@
|
||||
const faceToNameMap = $derived.by(() => {
|
||||
// eslint-disable-next-line svelte/prefer-svelte-reactivity
|
||||
const map = new Map<Faces, string>();
|
||||
for (const person of asset.people ?? []) {
|
||||
if (person.isHidden && !assetViewerManager.isShowingHiddenPeople) {
|
||||
for (const face of faceManager.data) {
|
||||
if (!face.person) {
|
||||
continue;
|
||||
}
|
||||
for (const face of person.faces ?? []) {
|
||||
map.set(face, person.name);
|
||||
if (face.person.isHidden && !assetViewerManager.isShowingHiddenPeople) {
|
||||
continue;
|
||||
}
|
||||
map.set(face, face.person.name);
|
||||
}
|
||||
return map;
|
||||
});
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
import type { OnAction } from '$lib/components/asset-viewer/actions/action';
|
||||
import MenuOption from '$lib/components/shared-components/context-menu/MenuOption.svelte';
|
||||
import { AssetAction } from '$lib/constants';
|
||||
import { handleError } from '$lib/utils/handle-error';
|
||||
@@ -6,7 +7,6 @@
|
||||
import { toastManager } from '@immich/ui';
|
||||
import { mdiFaceManProfile } from '@mdi/js';
|
||||
import { t } from 'svelte-i18n';
|
||||
import type { OnAction } from './action';
|
||||
|
||||
interface Props {
|
||||
asset: AssetResponseDto;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getAssetInfo, getAssetOcr } from '@immich/sdk';
|
||||
import { getAssetInfo, getAssetOcr, getFaces } from '@immich/sdk';
|
||||
import { authManager } from '$lib/managers/auth-manager.svelte';
|
||||
import { eventManager } from '$lib/managers/event-manager.svelte';
|
||||
|
||||
@@ -38,6 +38,7 @@ class AsyncCache<K, V> {
|
||||
class AssetCacheManager {
|
||||
#assetCache = new AsyncCache(getAssetInfo);
|
||||
#ocrCache = new AsyncCache(getAssetOcr);
|
||||
#faceCache = new AsyncCache(getFaces);
|
||||
|
||||
constructor() {
|
||||
eventManager.on({
|
||||
@@ -58,10 +59,15 @@ class AssetCacheManager {
|
||||
return this.#ocrCache.getOrFetch({ id }, true);
|
||||
}
|
||||
|
||||
async getAssetFaces(id: string) {
|
||||
return this.#faceCache.getOrFetch({ id }, true);
|
||||
}
|
||||
|
||||
invalidateAsset(id: string) {
|
||||
const { key, slug } = authManager.params;
|
||||
this.#assetCache.clearKey({ id, key, slug });
|
||||
this.#ocrCache.clearKey({ id });
|
||||
this.#faceCache.clearKey({ id });
|
||||
}
|
||||
|
||||
clearAssetCache() {
|
||||
@@ -72,9 +78,14 @@ class AssetCacheManager {
|
||||
this.#ocrCache.clear();
|
||||
}
|
||||
|
||||
clearFaceCache() {
|
||||
this.#faceCache.clear();
|
||||
}
|
||||
|
||||
invalidate() {
|
||||
this.clearAssetCache();
|
||||
this.clearOcrCache();
|
||||
this.clearFaceCache();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
import type { AssetFaceResponseDto, PersonResponseDto } from '@immich/sdk';
|
||||
import { SvelteMap, SvelteSet } from 'svelte/reactivity';
|
||||
import { assetCacheManager } from '$lib/managers/AssetCacheManager.svelte';
|
||||
import type { Faces } from '$lib/managers/asset-viewer-manager.svelte';
|
||||
import { CancellableTask } from '$lib/utils/cancellable-task';
|
||||
|
||||
class FaceManager {
|
||||
#data = $state<AssetFaceResponseDto[]>([]);
|
||||
#faceLoader = new CancellableTask();
|
||||
#cleared = false;
|
||||
|
||||
readonly faceNames = $derived.by(() => {
|
||||
// eslint-disable-next-line svelte/prefer-svelte-reactivity
|
||||
const map = new Map<Faces, string>();
|
||||
|
||||
for (const face of this.data) {
|
||||
if (!face.person) {
|
||||
continue;
|
||||
}
|
||||
map.set(face, face.person.name);
|
||||
}
|
||||
|
||||
return map;
|
||||
});
|
||||
|
||||
readonly people = $derived.by(() => {
|
||||
const people = new SvelteSet<PersonResponseDto>();
|
||||
|
||||
for (const face of this.data) {
|
||||
if (face.person) {
|
||||
people.add(face.person);
|
||||
}
|
||||
}
|
||||
|
||||
return people;
|
||||
});
|
||||
|
||||
readonly facesByPersonId = $derived.by(() => {
|
||||
const map = new SvelteMap<string, AssetFaceResponseDto[]>();
|
||||
for (const face of faceManager.data) {
|
||||
if (!face.person) {
|
||||
continue;
|
||||
}
|
||||
const existing = map.get(face.person.id);
|
||||
if (existing) {
|
||||
existing.push(face);
|
||||
} else {
|
||||
map.set(face.person.id, [face]);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
});
|
||||
|
||||
get data() {
|
||||
return this.#data;
|
||||
}
|
||||
|
||||
async getAssetFaces(id: string) {
|
||||
if (this.#cleared) {
|
||||
await this.#faceLoader.reset();
|
||||
this.#cleared = false;
|
||||
}
|
||||
await this.#faceLoader.execute(async () => {
|
||||
this.#data = await assetCacheManager.getAssetFaces(id);
|
||||
}, false);
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.#cleared = true;
|
||||
this.#data = [];
|
||||
}
|
||||
}
|
||||
|
||||
export const faceManager = new FaceManager();
|
||||
@@ -7,6 +7,7 @@ import { ocrManager, type OcrBoundingBox } from '$lib/stores/ocr.svelte';
|
||||
vi.mock('@immich/sdk', () => ({
|
||||
getAssetInfo: vi.fn(),
|
||||
getAssetOcr: vi.fn(),
|
||||
getFaces: vi.fn(),
|
||||
}));
|
||||
|
||||
const createMockOcrData = (overrides?: Partial<OcrBoundingBox>): OcrBoundingBox[] => [
|
||||
|
||||
Reference in New Issue
Block a user