feat(web): swap between people when merging faces (#4089)

* feat: swap between people when merging faces

* rename

* fix: remove url parameter when closing

* chore: handler naming

---------

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
This commit is contained in:
martin 2023-09-18 15:24:31 +02:00 committed by GitHub
parent 49ef86173f
commit a0163d8df0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 82 additions and 48 deletions

View File

@ -12,7 +12,9 @@
import { NotificationType, notificationController } from '../shared-components/notification/notification'; import { NotificationType, notificationController } from '../shared-components/notification/notification';
import ConfirmDialogue from '../shared-components/confirm-dialogue.svelte'; import ConfirmDialogue from '../shared-components/confirm-dialogue.svelte';
import { handleError } from '$lib/utils/handle-error'; import { handleError } from '$lib/utils/handle-error';
import { invalidateAll } from '$app/navigation'; import { goto, invalidateAll } from '$app/navigation';
import { AppRoute } from '$lib/constants';
import SwapHorizontal from 'svelte-material-icons/SwapHorizontal.svelte';
export let person: PersonResponseDto; export let person: PersonResponseDto;
let people: PersonResponseDto[] = []; let people: PersonResponseDto[] = [];
@ -22,8 +24,9 @@
let dispatch = createEventDispatcher(); let dispatch = createEventDispatcher();
$: hasSelection = selectedPeople.length > 0; $: hasSelection = selectedPeople.length > 0;
$: unselectedPeople = people.filter((source) => !selectedPeople.includes(source) && source.id !== person.id); $: unselectedPeople = people.filter(
(source) => !selectedPeople.some((selected) => selected.id === source.id) && source.id !== person.id,
);
onMount(async () => { onMount(async () => {
const { data } = await api.personApi.getAllPeople({ withHidden: false }); const { data } = await api.personApi.getAllPeople({ withHidden: false });
people = data.people; people = data.people;
@ -33,6 +36,11 @@
dispatch('go-back'); dispatch('go-back');
}; };
const handleSwapPeople = () => {
[person, selectedPeople[0]] = [selectedPeople[0], person];
goto(`${AppRoute.PEOPLE}/${person.id}?action=merge`);
};
const onSelect = (selected: PersonResponseDto) => { const onSelect = (selected: PersonResponseDto) => {
if (selectedPeople.includes(selected)) { if (selectedPeople.includes(selected)) {
selectedPeople = selectedPeople.filter((person) => person.id !== selected.id); selectedPeople = selectedPeople.filter((person) => person.id !== selected.id);
@ -112,7 +120,14 @@
{/each} {/each}
{#if hasSelection} {#if hasSelection}
<span><CallMerge size={48} class="rotate-90 dark:text-white" /> </span> <span class="grid grid-cols-1"
><CallMerge size={48} class="rotate-90 dark:text-white" />
{#if selectedPeople.length === 1}
<button class="flex justify-center" on:click={handleSwapPeople}
><SwapHorizontal size={24} class="dark:text-white" />
</button>
{/if}
</span>
{/if} {/if}
<FaceThumbnail {person} border circle selectable={false} thumbnailSize={180} /> <FaceThumbnail {person} border circle selectable={false} thumbnailSize={180} />
</div> </div>

View File

@ -43,7 +43,7 @@
BIRTH_DATE = 'birth-date', BIRTH_DATE = 'birth-date',
} }
const assetStore = new AssetStore({ let assetStore = new AssetStore({
size: TimeBucketSize.Month, size: TimeBucketSize.Month,
isArchived: false, isArchived: false,
personId: data.person.id, personId: data.person.id,
@ -54,6 +54,7 @@
let viewMode: ViewMode = ViewMode.VIEW_ASSETS; let viewMode: ViewMode = ViewMode.VIEW_ASSETS;
let isEditingName = false; let isEditingName = false;
let previousRoute: string = AppRoute.EXPLORE; let previousRoute: string = AppRoute.EXPLORE;
let previousPersonId: string = data.person.id;
let people = data.people.people; let people = data.people.people;
let personMerge1: PersonResponseDto; let personMerge1: PersonResponseDto;
let personMerge2: PersonResponseDto; let personMerge2: PersonResponseDto;
@ -74,6 +75,14 @@
if (from && from.route.id !== $page.route.id) { if (from && from.route.id !== $page.route.id) {
previousRoute = from.url.href; previousRoute = from.url.href;
} }
if (previousPersonId !== data.person.id) {
assetStore = new AssetStore({
size: TimeBucketSize.Month,
isArchived: false,
personId: data.person.id,
});
previousPersonId = data.person.id;
}
}); });
const hideFace = async () => { const hideFace = async () => {
@ -215,6 +224,14 @@
handleError(error, 'Unable to save date of birth'); handleError(error, 'Unable to save date of birth');
} }
}; };
const handleGoBack = () => {
viewMode = ViewMode.VIEW_ASSETS;
if ($page.url.searchParams.has('action')) {
$page.url.searchParams.delete('action');
goto($page.url);
}
};
</script> </script>
{#if viewMode === ViewMode.SUGGEST_MERGE} {#if viewMode === ViewMode.SUGGEST_MERGE}
@ -237,7 +254,7 @@
{/if} {/if}
{#if viewMode === ViewMode.MERGE_FACES} {#if viewMode === ViewMode.MERGE_FACES}
<MergeFaceSelector person={data.person} on:go-back={() => (viewMode = ViewMode.VIEW_ASSETS)} /> <MergeFaceSelector person={data.person} on:go-back={handleGoBack} />
{/if} {/if}
<header> <header>
@ -279,48 +296,50 @@
</header> </header>
<main class="relative h-screen overflow-hidden bg-immich-bg pt-[var(--navbar-height)] dark:bg-immich-dark-bg"> <main class="relative h-screen overflow-hidden bg-immich-bg pt-[var(--navbar-height)] dark:bg-immich-dark-bg">
<AssetGrid {#key previousPersonId}
{assetStore} <AssetGrid
{assetInteractionStore} {assetStore}
isSelectionMode={viewMode === ViewMode.SELECT_FACE} {assetInteractionStore}
singleSelect={viewMode === ViewMode.SELECT_FACE} isSelectionMode={viewMode === ViewMode.SELECT_FACE}
on:select={({ detail: asset }) => handleSelectFeaturePhoto(asset)} singleSelect={viewMode === ViewMode.SELECT_FACE}
> on:select={({ detail: asset }) => handleSelectFeaturePhoto(asset)}
{#if viewMode === ViewMode.VIEW_ASSETS || viewMode === ViewMode.SUGGEST_MERGE || viewMode === ViewMode.BIRTH_DATE} >
<!-- Face information block --> {#if viewMode === ViewMode.VIEW_ASSETS || viewMode === ViewMode.SUGGEST_MERGE || viewMode === ViewMode.BIRTH_DATE}
<section class="flex place-items-center p-4 sm:px-6"> <!-- Face information block -->
{#if isEditingName} <section class="flex place-items-center p-4 sm:px-6">
<EditNameInput {#if isEditingName}
person={data.person} <EditNameInput
on:change={(event) => handleNameChange(event.detail)} person={data.person}
on:cancel={() => handleCancelEditName()} on:change={(event) => handleNameChange(event.detail)}
/> on:cancel={() => handleCancelEditName()}
{:else}
<button on:click={() => (viewMode = ViewMode.VIEW_ASSETS)}>
<ImageThumbnail
circle
shadow
url={api.getPeopleThumbnailUrl(data.person.id)}
altText={data.person.name}
widthStyle="3.375rem"
heightStyle="3.375rem"
/> />
</button> {:else}
<button on:click={() => (viewMode = ViewMode.VIEW_ASSETS)}>
<ImageThumbnail
circle
shadow
url={api.getPeopleThumbnailUrl(data.person.id)}
altText={data.person.name}
widthStyle="3.375rem"
heightStyle="3.375rem"
/>
</button>
<button <button
title="Edit name" title="Edit name"
class="px-4 text-immich-primary dark:text-immich-dark-primary" class="px-4 text-immich-primary dark:text-immich-dark-primary"
on:click={() => (isEditingName = true)} on:click={() => (isEditingName = true)}
> >
{#if data.person.name} {#if data.person.name}
<p class="py-2 font-medium">{data.person.name}</p> <p class="py-2 font-medium">{data.person.name}</p>
{:else} {:else}
<p class="w-fit font-medium">Add a name</p> <p class="w-fit font-medium">Add a name</p>
<p class="text-sm text-gray-500 dark:text-immich-gray">Find them fast by name with search</p> <p class="text-sm text-gray-500 dark:text-immich-gray">Find them fast by name with search</p>
{/if} {/if}
</button> </button>
{/if} {/if}
</section> </section>
{/if} {/if}
</AssetGrid> </AssetGrid>
{/key}
</main> </main>