فهرست منبع

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>
martin 1 سال پیش
والد
کامیت
a0163d8df0
2فایلهای تغییر یافته به همراه83 افزوده شده و 49 حذف شده
  1. 19 4
      web/src/lib/components/faces-page/merge-face-selector.svelte
  2. 64 45
      web/src/routes/(user)/people/[personId]/+page.svelte

+ 19 - 4
web/src/lib/components/faces-page/merge-face-selector.svelte

@@ -12,7 +12,9 @@
   import { NotificationType, notificationController } from '../shared-components/notification/notification';
   import ConfirmDialogue from '../shared-components/confirm-dialogue.svelte';
   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;
   let people: PersonResponseDto[] = [];
@@ -22,8 +24,9 @@
   let dispatch = createEventDispatcher();
 
   $: 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 () => {
     const { data } = await api.personApi.getAllPeople({ withHidden: false });
     people = data.people;
@@ -33,6 +36,11 @@
     dispatch('go-back');
   };
 
+  const handleSwapPeople = () => {
+    [person, selectedPeople[0]] = [selectedPeople[0], person];
+    goto(`${AppRoute.PEOPLE}/${person.id}?action=merge`);
+  };
+
   const onSelect = (selected: PersonResponseDto) => {
     if (selectedPeople.includes(selected)) {
       selectedPeople = selectedPeople.filter((person) => person.id !== selected.id);
@@ -112,7 +120,14 @@
           {/each}
 
           {#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}
           <FaceThumbnail {person} border circle selectable={false} thumbnailSize={180} />
         </div>

+ 64 - 45
web/src/routes/(user)/people/[personId]/+page.svelte

@@ -43,7 +43,7 @@
     BIRTH_DATE = 'birth-date',
   }
 
-  const assetStore = new AssetStore({
+  let assetStore = new AssetStore({
     size: TimeBucketSize.Month,
     isArchived: false,
     personId: data.person.id,
@@ -54,6 +54,7 @@
   let viewMode: ViewMode = ViewMode.VIEW_ASSETS;
   let isEditingName = false;
   let previousRoute: string = AppRoute.EXPLORE;
+  let previousPersonId: string = data.person.id;
   let people = data.people.people;
   let personMerge1: PersonResponseDto;
   let personMerge2: PersonResponseDto;
@@ -74,6 +75,14 @@
     if (from && from.route.id !== $page.route.id) {
       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 () => {
@@ -215,6 +224,14 @@
       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>
 
 {#if viewMode === ViewMode.SUGGEST_MERGE}
@@ -237,7 +254,7 @@
 {/if}
 
 {#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}
 
 <header>
@@ -279,48 +296,50 @@
 </header>
 
 <main class="relative h-screen overflow-hidden bg-immich-bg pt-[var(--navbar-height)] dark:bg-immich-dark-bg">
-  <AssetGrid
-    {assetStore}
-    {assetInteractionStore}
-    isSelectionMode={viewMode === ViewMode.SELECT_FACE}
-    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 -->
-      <section class="flex place-items-center p-4 sm:px-6">
-        {#if isEditingName}
-          <EditNameInput
-            person={data.person}
-            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"
+  {#key previousPersonId}
+    <AssetGrid
+      {assetStore}
+      {assetInteractionStore}
+      isSelectionMode={viewMode === ViewMode.SELECT_FACE}
+      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 -->
+        <section class="flex place-items-center p-4 sm:px-6">
+          {#if isEditingName}
+            <EditNameInput
+              person={data.person}
+              on:change={(event) => handleNameChange(event.detail)}
+              on:cancel={() => handleCancelEditName()}
             />
-          </button>
-
-          <button
-            title="Edit name"
-            class="px-4 text-immich-primary dark:text-immich-dark-primary"
-            on:click={() => (isEditingName = true)}
-          >
-            {#if data.person.name}
-              <p class="py-2 font-medium">{data.person.name}</p>
-            {:else}
-              <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>
-            {/if}
-          </button>
-        {/if}
-      </section>
-    {/if}
-  </AssetGrid>
+          {: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
+              title="Edit name"
+              class="px-4 text-immich-primary dark:text-immich-dark-primary"
+              on:click={() => (isEditingName = true)}
+            >
+              {#if data.person.name}
+                <p class="py-2 font-medium">{data.person.name}</p>
+              {:else}
+                <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>
+              {/if}
+            </button>
+          {/if}
+        </section>
+      {/if}
+    </AssetGrid>
+  {/key}
 </main>