mirror of
				https://github.com/immich-app/immich.git
				synced 2025-11-04 03:27:09 -05:00 
			
		
		
		
	feat(web): increase usage of CircleIconButton (#9256)
This commit is contained in:
		
							parent
							
								
									5b87abb021
								
							
						
					
					
						commit
						48b490f5e9
					
				@ -1,6 +1,5 @@
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
  import Badge from '$lib/components/elements/badge.svelte';
 | 
			
		||||
  import Button from '$lib/components/elements/buttons/button.svelte';
 | 
			
		||||
  import Icon from '$lib/components/elements/icon.svelte';
 | 
			
		||||
  import { locale } from '$lib/stores/preferences.store';
 | 
			
		||||
  import { JobCommand, type JobCommandDto, type JobCountsDto, type QueueStatusDto } from '@immich/sdk';
 | 
			
		||||
@ -16,6 +15,7 @@
 | 
			
		||||
  import { createEventDispatcher } from 'svelte';
 | 
			
		||||
  import JobTileButton from './job-tile-button.svelte';
 | 
			
		||||
  import JobTileStatus from './job-tile-status.svelte';
 | 
			
		||||
  import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
 | 
			
		||||
 | 
			
		||||
  export let title: string;
 | 
			
		||||
  export let subtitle: string | undefined;
 | 
			
		||||
@ -56,16 +56,19 @@
 | 
			
		||||
        <div class="flex gap-2">
 | 
			
		||||
          {#if jobCounts.failed > 0}
 | 
			
		||||
            <Badge color="primary">
 | 
			
		||||
              <div class="flex flex-row gap-1">
 | 
			
		||||
                <span class="text-sm">
 | 
			
		||||
                  {jobCounts.failed.toLocaleString($locale)} failed
 | 
			
		||||
                </span>
 | 
			
		||||
              <Button
 | 
			
		||||
                size="tiny"
 | 
			
		||||
                shadow={false}
 | 
			
		||||
                <CircleIconButton
 | 
			
		||||
                  color="primary"
 | 
			
		||||
                  icon={mdiClose}
 | 
			
		||||
                  title="Clear message"
 | 
			
		||||
                  size="12"
 | 
			
		||||
                  padding="1"
 | 
			
		||||
                  on:click={() => dispatch('command', { command: JobCommand.ClearFailed, force: false })}
 | 
			
		||||
              >
 | 
			
		||||
                <Icon path={mdiClose} size="18" />
 | 
			
		||||
              </Button>
 | 
			
		||||
                />
 | 
			
		||||
              </div>
 | 
			
		||||
            </Badge>
 | 
			
		||||
          {/if}
 | 
			
		||||
          {#if jobCounts.delayed > 0}
 | 
			
		||||
 | 
			
		||||
@ -33,7 +33,7 @@
 | 
			
		||||
      data-testid="context-button-parent"
 | 
			
		||||
    >
 | 
			
		||||
      <CircleIconButton
 | 
			
		||||
        color="light"
 | 
			
		||||
        color="opaque"
 | 
			
		||||
        title="Show album options"
 | 
			
		||||
        icon={mdiDotsVertical}
 | 
			
		||||
        size="20"
 | 
			
		||||
 | 
			
		||||
@ -19,7 +19,6 @@
 | 
			
		||||
  class="w-full h-14 flex p-4 text-white items-center justify-center rounded-full gap-4 bg-immich-dark-bg bg-opacity-60"
 | 
			
		||||
>
 | 
			
		||||
  <button class={disabled ? 'cursor-not-allowed' : ''} on:click={() => dispatch('favorite')} {disabled}>
 | 
			
		||||
    <!-- svelte-ignore missing-declaration -->
 | 
			
		||||
    <div class="items-center justify-center">
 | 
			
		||||
      <Icon path={isLiked ? mdiHeart : mdiHeartOutline} size={24} />
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
@ -160,12 +160,7 @@
 | 
			
		||||
      bind:clientHeight={activityHeight}
 | 
			
		||||
    >
 | 
			
		||||
      <div class="flex place-items-center gap-2">
 | 
			
		||||
        <button
 | 
			
		||||
          class="flex place-content-center place-items-center rounded-full p-3 transition-colors hover:bg-gray-200 dark:text-immich-dark-fg dark:hover:bg-gray-900"
 | 
			
		||||
          on:click={() => dispatch('close')}
 | 
			
		||||
        >
 | 
			
		||||
          <Icon path={mdiClose} size="24" />
 | 
			
		||||
        </button>
 | 
			
		||||
        <CircleIconButton on:click={() => dispatch('close')} icon={mdiClose} title="Close" />
 | 
			
		||||
 | 
			
		||||
        <p class="text-lg text-immich-fg dark:text-immich-dark-fg">Activity</p>
 | 
			
		||||
      </div>
 | 
			
		||||
@ -193,10 +188,13 @@
 | 
			
		||||
                </a>
 | 
			
		||||
              {/if}
 | 
			
		||||
              {#if reaction.user.id === user.id || albumOwnerId === user.id}
 | 
			
		||||
                <div class="flex items-start w-fit pt-[5px]" title="Delete comment">
 | 
			
		||||
                  <button on:click={() => (showDeleteReaction[index] ? '' : showOptionsMenu(index))}>
 | 
			
		||||
                    <Icon path={mdiDotsVertical} />
 | 
			
		||||
                  </button>
 | 
			
		||||
                <div class="flex items-start w-fit pt-[5px]">
 | 
			
		||||
                  <CircleIconButton
 | 
			
		||||
                    icon={mdiDotsVertical}
 | 
			
		||||
                    title="Comment options"
 | 
			
		||||
                    size="16"
 | 
			
		||||
                    on:click={() => (showDeleteReaction[index] ? '' : showOptionsMenu(index))}
 | 
			
		||||
                  />
 | 
			
		||||
                </div>
 | 
			
		||||
              {/if}
 | 
			
		||||
              <div>
 | 
			
		||||
@ -242,10 +240,13 @@
 | 
			
		||||
                  </a>
 | 
			
		||||
                {/if}
 | 
			
		||||
                {#if reaction.user.id === user.id || albumOwnerId === user.id}
 | 
			
		||||
                  <div class="flex items-start w-fit" title="Delete like">
 | 
			
		||||
                    <button on:click={() => (showDeleteReaction[index] ? '' : showOptionsMenu(index))}>
 | 
			
		||||
                      <Icon path={mdiDotsVertical} />
 | 
			
		||||
                    </button>
 | 
			
		||||
                  <div class="flex items-start w-fit">
 | 
			
		||||
                    <CircleIconButton
 | 
			
		||||
                      icon={mdiDotsVertical}
 | 
			
		||||
                      title="Reaction options"
 | 
			
		||||
                      size="16"
 | 
			
		||||
                      on:click={() => (showDeleteReaction[index] ? '' : showOptionsMenu(index))}
 | 
			
		||||
                    />
 | 
			
		||||
                  </div>
 | 
			
		||||
                {/if}
 | 
			
		||||
                <div>
 | 
			
		||||
 | 
			
		||||
@ -169,13 +169,7 @@
 | 
			
		||||
 | 
			
		||||
<section class="relative p-2 dark:bg-immich-dark-bg dark:text-immich-dark-fg">
 | 
			
		||||
  <div class="flex place-items-center gap-2">
 | 
			
		||||
    <button
 | 
			
		||||
      class="flex place-content-center place-items-center rounded-full p-3 transition-colors hover:bg-gray-200 dark:text-immich-dark-fg dark:hover:bg-gray-900"
 | 
			
		||||
      on:click={() => dispatch('close')}
 | 
			
		||||
    >
 | 
			
		||||
      <Icon path={mdiClose} size="24" />
 | 
			
		||||
    </button>
 | 
			
		||||
 | 
			
		||||
    <CircleIconButton icon={mdiClose} title="Close" on:click={() => dispatch('close')} />
 | 
			
		||||
    <p class="text-lg text-immich-fg dark:text-immich-dark-fg">Info</p>
 | 
			
		||||
  </div>
 | 
			
		||||
 | 
			
		||||
@ -401,9 +395,13 @@
 | 
			
		||||
          <p class="break-all flex place-items-center gap-2">
 | 
			
		||||
            {asset.originalFileName}
 | 
			
		||||
            {#if isOwner}
 | 
			
		||||
              <button title="Show File Location" on:click={toggleAssetPath} class="-translate-y-[2px]">
 | 
			
		||||
                <Icon path={mdiInformationOutline} />
 | 
			
		||||
              </button>
 | 
			
		||||
              <CircleIconButton
 | 
			
		||||
                icon={mdiInformationOutline}
 | 
			
		||||
                title="Show file location"
 | 
			
		||||
                size="16"
 | 
			
		||||
                padding="2"
 | 
			
		||||
                on:click={toggleAssetPath}
 | 
			
		||||
              />
 | 
			
		||||
            {/if}
 | 
			
		||||
          </p>
 | 
			
		||||
          <div class="flex gap-2 text-sm">
 | 
			
		||||
 | 
			
		||||
@ -1,20 +1,23 @@
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
  import { mdiClose, mdiMagnify } from '@mdi/js';
 | 
			
		||||
  import Icon from './icon.svelte';
 | 
			
		||||
  import { createEventDispatcher } from 'svelte';
 | 
			
		||||
  import type { SearchOptions } from '$lib/utils/dipatch';
 | 
			
		||||
  import LoadingSpinner from '../shared-components/loading-spinner.svelte';
 | 
			
		||||
  import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
 | 
			
		||||
 | 
			
		||||
  export let name: string;
 | 
			
		||||
  export let roundedBottom = true;
 | 
			
		||||
  export let showLoadingSpinner: boolean;
 | 
			
		||||
  export let placeholder: string;
 | 
			
		||||
 | 
			
		||||
  let inputRef: HTMLElement;
 | 
			
		||||
 | 
			
		||||
  const dispatch = createEventDispatcher<{ search: SearchOptions; reset: void }>();
 | 
			
		||||
 | 
			
		||||
  const resetSearch = () => {
 | 
			
		||||
    name = '';
 | 
			
		||||
    dispatch('reset');
 | 
			
		||||
    inputRef?.focus();
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const handleSearch = (event: KeyboardEvent) => {
 | 
			
		||||
@ -29,16 +32,19 @@
 | 
			
		||||
    ? 'rounded-2xl'
 | 
			
		||||
    : 'rounded-t-lg'} bg-gray-200 p-2 dark:bg-immich-dark-gray gap-2 place-items-center h-full"
 | 
			
		||||
>
 | 
			
		||||
  <button type="button" on:click={() => dispatch('search', { force: true })}>
 | 
			
		||||
    <div class="w-fit">
 | 
			
		||||
      <Icon path={mdiMagnify} size="24" />
 | 
			
		||||
    </div>
 | 
			
		||||
  </button>
 | 
			
		||||
  <CircleIconButton
 | 
			
		||||
    icon={mdiMagnify}
 | 
			
		||||
    title="Search"
 | 
			
		||||
    size="16"
 | 
			
		||||
    padding="2"
 | 
			
		||||
    on:click={() => dispatch('search', { force: true })}
 | 
			
		||||
  />
 | 
			
		||||
  <input
 | 
			
		||||
    class="w-full gap-2 bg-gray-200 dark:bg-immich-dark-gray dark:text-white"
 | 
			
		||||
    type="text"
 | 
			
		||||
    {placeholder}
 | 
			
		||||
    bind:value={name}
 | 
			
		||||
    bind:this={inputRef}
 | 
			
		||||
    on:keydown={handleSearch}
 | 
			
		||||
    on:input={() => dispatch('search', { force: false })}
 | 
			
		||||
  />
 | 
			
		||||
@ -48,8 +54,6 @@
 | 
			
		||||
    </div>
 | 
			
		||||
  {/if}
 | 
			
		||||
  {#if name}
 | 
			
		||||
    <button on:click={resetSearch}>
 | 
			
		||||
      <Icon path={mdiClose} />
 | 
			
		||||
    </button>
 | 
			
		||||
    <CircleIconButton icon={mdiClose} title="Clear value" size="16" padding="2" on:click={resetSearch} />
 | 
			
		||||
  {/if}
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
@ -9,9 +9,9 @@
 | 
			
		||||
  import { linear } from 'svelte/easing';
 | 
			
		||||
  import { fly } from 'svelte/transition';
 | 
			
		||||
  import ImageThumbnail from '../assets/thumbnail/image-thumbnail.svelte';
 | 
			
		||||
  import Icon from '../elements/icon.svelte';
 | 
			
		||||
  import LoadingSpinner from '../shared-components/loading-spinner.svelte';
 | 
			
		||||
  import SearchPeople from '$lib/components/faces-page/people-search.svelte';
 | 
			
		||||
  import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
 | 
			
		||||
 | 
			
		||||
  export let peopleWithFaces: AssetFaceResponseDto[];
 | 
			
		||||
  export let allPeople: PersonResponseDto[];
 | 
			
		||||
@ -119,38 +119,19 @@
 | 
			
		||||
  <div class="flex place-items-center justify-between gap-2">
 | 
			
		||||
    {#if !searchFaces}
 | 
			
		||||
      <div class="flex items-center gap-2">
 | 
			
		||||
        <button
 | 
			
		||||
          class="flex place-content-center rounded-full p-3 transition-colors hover:bg-gray-200 dark:text-immich-dark-fg dark:hover:bg-gray-900"
 | 
			
		||||
          on:click={handleBackButton}
 | 
			
		||||
        >
 | 
			
		||||
          <div>
 | 
			
		||||
            <Icon path={mdiArrowLeftThin} size="24" />
 | 
			
		||||
          </div>
 | 
			
		||||
        </button>
 | 
			
		||||
        <CircleIconButton icon={mdiArrowLeftThin} title="Back" on:click={handleBackButton} />
 | 
			
		||||
        <p class="flex text-lg text-immich-fg dark:text-immich-dark-fg">Select face</p>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class="flex justify-end gap-2">
 | 
			
		||||
        <button
 | 
			
		||||
          class="flex place-content-center place-items-center rounded-full p-3 transition-colors hover:bg-gray-200 dark:text-immich-dark-fg dark:hover:bg-gray-900"
 | 
			
		||||
          title="Search existing person"
 | 
			
		||||
        <CircleIconButton
 | 
			
		||||
          icon={mdiMagnify}
 | 
			
		||||
          title="Search for existing person"
 | 
			
		||||
          on:click={() => {
 | 
			
		||||
            searchFaces = true;
 | 
			
		||||
          }}
 | 
			
		||||
        >
 | 
			
		||||
          <div>
 | 
			
		||||
            <Icon path={mdiMagnify} size="24" />
 | 
			
		||||
          </div>
 | 
			
		||||
        </button>
 | 
			
		||||
        />
 | 
			
		||||
        {#if !isShowLoadingNewPerson}
 | 
			
		||||
          <button
 | 
			
		||||
            class="flex place-content-center place-items-center rounded-full p-3 transition-colors hover:bg-gray-200 dark:text-immich-dark-fg dark:hover:bg-gray-900"
 | 
			
		||||
            on:click={handleCreatePerson}
 | 
			
		||||
            title="Create new person"
 | 
			
		||||
          >
 | 
			
		||||
            <div>
 | 
			
		||||
              <Icon path={mdiPlus} size="24" />
 | 
			
		||||
            </div>
 | 
			
		||||
          </button>
 | 
			
		||||
          <CircleIconButton icon={mdiPlus} title="Create new person" on:click={handleCreatePerson} />
 | 
			
		||||
        {:else}
 | 
			
		||||
          <div class="flex place-content-center place-items-center">
 | 
			
		||||
            <LoadingSpinner />
 | 
			
		||||
@ -158,14 +139,7 @@
 | 
			
		||||
        {/if}
 | 
			
		||||
      </div>
 | 
			
		||||
    {:else}
 | 
			
		||||
      <button
 | 
			
		||||
        class="flex place-content-center rounded-full p-3 transition-colors hover:bg-gray-200 dark:text-immich-dark-fg dark:hover:bg-gray-900"
 | 
			
		||||
        on:click={handleBackButton}
 | 
			
		||||
      >
 | 
			
		||||
        <div>
 | 
			
		||||
          <Icon path={mdiArrowLeftThin} size="24" />
 | 
			
		||||
        </div>
 | 
			
		||||
      </button>
 | 
			
		||||
      <CircleIconButton icon={mdiArrowLeftThin} title="Back" on:click={handleBackButton} />
 | 
			
		||||
      <div class="w-full flex">
 | 
			
		||||
        <SearchPeople
 | 
			
		||||
          type="input"
 | 
			
		||||
@ -179,14 +153,7 @@
 | 
			
		||||
          </div>
 | 
			
		||||
        {/if}
 | 
			
		||||
      </div>
 | 
			
		||||
      <button
 | 
			
		||||
        class="flex place-content-center place-items-center rounded-full p-3 transition-colors hover:bg-gray-200 dark:text-immich-dark-fg dark:hover:bg-gray-900"
 | 
			
		||||
        on:click={() => (searchFaces = false)}
 | 
			
		||||
      >
 | 
			
		||||
        <div>
 | 
			
		||||
          <Icon path={mdiClose} size="24" />
 | 
			
		||||
        </div>
 | 
			
		||||
      </button>
 | 
			
		||||
      <CircleIconButton icon={mdiClose} title="Cancel search" on:click={() => (searchFaces = false)} />
 | 
			
		||||
    {/if}
 | 
			
		||||
  </div>
 | 
			
		||||
  <div class="px-4 py-4 text-sm">
 | 
			
		||||
 | 
			
		||||
@ -74,7 +74,7 @@
 | 
			
		||||
 | 
			
		||||
  <div class="absolute right-2 top-2" class:hidden={!showVerticalDots}>
 | 
			
		||||
    <CircleIconButton
 | 
			
		||||
      color="light"
 | 
			
		||||
      color="opaque"
 | 
			
		||||
      icon={mdiDotsVertical}
 | 
			
		||||
      title="Show person options"
 | 
			
		||||
      size="20"
 | 
			
		||||
 | 
			
		||||
@ -15,14 +15,14 @@
 | 
			
		||||
    type AssetFaceResponseDto,
 | 
			
		||||
    type PersonResponseDto,
 | 
			
		||||
  } from '@immich/sdk';
 | 
			
		||||
  import { mdiArrowLeftThin, mdiRestart } from '@mdi/js';
 | 
			
		||||
  import { mdiArrowLeftThin, mdiMinus, mdiRestart } from '@mdi/js';
 | 
			
		||||
  import { createEventDispatcher, onMount } from 'svelte';
 | 
			
		||||
  import { linear } from 'svelte/easing';
 | 
			
		||||
  import { fly } from 'svelte/transition';
 | 
			
		||||
  import ImageThumbnail from '../assets/thumbnail/image-thumbnail.svelte';
 | 
			
		||||
  import Icon from '../elements/icon.svelte';
 | 
			
		||||
  import { NotificationType, notificationController } from '../shared-components/notification/notification';
 | 
			
		||||
  import AssignFaceSidePanel from './assign-face-side-panel.svelte';
 | 
			
		||||
  import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
 | 
			
		||||
 | 
			
		||||
  export let assetId: string;
 | 
			
		||||
  export let assetType: AssetTypeEnum;
 | 
			
		||||
@ -42,7 +42,7 @@
 | 
			
		||||
  let isShowLoadingPeople = false;
 | 
			
		||||
 | 
			
		||||
  // search people
 | 
			
		||||
  let showSeletecFaces = false;
 | 
			
		||||
  let showSelectedFaces = false;
 | 
			
		||||
  let allPeople: PersonResponseDto[] = [];
 | 
			
		||||
 | 
			
		||||
  // timers
 | 
			
		||||
@ -159,21 +159,21 @@
 | 
			
		||||
    if (newFeaturePhoto && personToUpdate) {
 | 
			
		||||
      selectedPersonToCreate[personToUpdate.id] = newFeaturePhoto;
 | 
			
		||||
    }
 | 
			
		||||
    showSeletecFaces = false;
 | 
			
		||||
    showSelectedFaces = false;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const handleReassignFace = (person: PersonResponseDto | null) => {
 | 
			
		||||
    const personToUpdate = peopleWithFaces.find((face) => face.person?.id === editedPerson.id);
 | 
			
		||||
    if (person && personToUpdate) {
 | 
			
		||||
      selectedPersonToReassign[personToUpdate.id] = person;
 | 
			
		||||
      showSeletecFaces = false;
 | 
			
		||||
      showSelectedFaces = false;
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const handlePersonPicker = (person: PersonResponseDto | null) => {
 | 
			
		||||
    if (person) {
 | 
			
		||||
      editedPerson = person;
 | 
			
		||||
      showSeletecFaces = true;
 | 
			
		||||
      showSelectedFaces = true;
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
</script>
 | 
			
		||||
@ -184,14 +184,7 @@
 | 
			
		||||
>
 | 
			
		||||
  <div class="flex place-items-center justify-between gap-2">
 | 
			
		||||
    <div class="flex items-center gap-2">
 | 
			
		||||
      <button
 | 
			
		||||
        class="flex place-content-center rounded-full p-3 transition-colors hover:bg-gray-200 dark:text-immich-dark-fg dark:hover:bg-gray-900"
 | 
			
		||||
        on:click={handleBackButton}
 | 
			
		||||
      >
 | 
			
		||||
        <div>
 | 
			
		||||
          <Icon path={mdiArrowLeftThin} size="24" />
 | 
			
		||||
        </div>
 | 
			
		||||
      </button>
 | 
			
		||||
      <CircleIconButton icon={mdiArrowLeftThin} title="Back" on:click={handleBackButton} />
 | 
			
		||||
      <p class="flex text-lg text-immich-fg dark:text-immich-dark-fg">Edit faces</p>
 | 
			
		||||
    </div>
 | 
			
		||||
    {#if !isShowLoadingDone}
 | 
			
		||||
@ -273,21 +266,27 @@
 | 
			
		||||
                  </p>
 | 
			
		||||
                {/if}
 | 
			
		||||
 | 
			
		||||
                <div class="absolute -right-[5px] -top-[5px] h-[20px] w-[20px] rounded-full bg-blue-700">
 | 
			
		||||
                <div class="absolute -right-[5px] -top-[5px] h-[20px] w-[20px] rounded-full">
 | 
			
		||||
                  {#if selectedPersonToCreate[face.id] || selectedPersonToReassign[face.id]}
 | 
			
		||||
                    <button on:click={() => handleReset(face.id)} class="flex h-full w-full">
 | 
			
		||||
                      <div class="absolute left-1/2 top-1/2 translate-x-[-50%] translate-y-[-50%] transform">
 | 
			
		||||
                        <div>
 | 
			
		||||
                          <Icon path={mdiRestart} size={18} />
 | 
			
		||||
                        </div>
 | 
			
		||||
                      </div>
 | 
			
		||||
                    </button>
 | 
			
		||||
                  {:else}
 | 
			
		||||
                    <button on:click={() => handlePersonPicker(face.person)} class="flex h-full w-full">
 | 
			
		||||
                      <div
 | 
			
		||||
                        class="absolute left-1/2 top-1/2 h-[2px] w-[14px] translate-x-[-50%] translate-y-[-50%] transform bg-white"
 | 
			
		||||
                    <CircleIconButton
 | 
			
		||||
                      color="primary"
 | 
			
		||||
                      icon={mdiRestart}
 | 
			
		||||
                      title="Reset"
 | 
			
		||||
                      size="18"
 | 
			
		||||
                      padding="1"
 | 
			
		||||
                      class="absolute left-1/2 top-1/2 translate-x-[-50%] translate-y-[-50%] transform"
 | 
			
		||||
                      on:click={() => handleReset(face.id)}
 | 
			
		||||
                    />
 | 
			
		||||
                  {:else}
 | 
			
		||||
                    <CircleIconButton
 | 
			
		||||
                      color="primary"
 | 
			
		||||
                      icon={mdiMinus}
 | 
			
		||||
                      title="Select new face"
 | 
			
		||||
                      size="18"
 | 
			
		||||
                      padding="1"
 | 
			
		||||
                      class="absolute left-1/2 top-1/2 translate-x-[-50%] translate-y-[-50%] transform"
 | 
			
		||||
                      on:click={() => handlePersonPicker(face.person)}
 | 
			
		||||
                    />
 | 
			
		||||
                    </button>
 | 
			
		||||
                  {/if}
 | 
			
		||||
                </div>
 | 
			
		||||
              </div>
 | 
			
		||||
@ -299,14 +298,14 @@
 | 
			
		||||
  </div>
 | 
			
		||||
</section>
 | 
			
		||||
 | 
			
		||||
{#if showSeletecFaces}
 | 
			
		||||
{#if showSelectedFaces}
 | 
			
		||||
  <AssignFaceSidePanel
 | 
			
		||||
    {peopleWithFaces}
 | 
			
		||||
    {allPeople}
 | 
			
		||||
    {editedPerson}
 | 
			
		||||
    {assetType}
 | 
			
		||||
    {assetId}
 | 
			
		||||
    on:close={() => (showSeletecFaces = false)}
 | 
			
		||||
    on:close={() => (showSelectedFaces = false)}
 | 
			
		||||
    on:createPerson={(event) => handleCreatePerson(event.detail)}
 | 
			
		||||
    on:reassign={(event) => handleReassignFace(event.detail)}
 | 
			
		||||
  />
 | 
			
		||||
 | 
			
		||||
@ -8,6 +8,7 @@
 | 
			
		||||
  import { validate, type LibraryResponseDto } from '@immich/sdk';
 | 
			
		||||
  import type { ValidateLibraryImportPathResponseDto } from '@immich/sdk';
 | 
			
		||||
  import { NotificationType, notificationController } from '../shared-components/notification/notification';
 | 
			
		||||
  import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
 | 
			
		||||
 | 
			
		||||
  export let library: LibraryResponseDto;
 | 
			
		||||
 | 
			
		||||
@ -209,17 +210,17 @@
 | 
			
		||||
          </td>
 | 
			
		||||
 | 
			
		||||
          <td class="w-4/5 text-ellipsis px-4 text-sm">{validatedPath.importPath}</td>
 | 
			
		||||
          <td class="w-1/5 text-ellipsis px-4 text-sm flex flex-row">
 | 
			
		||||
            <button
 | 
			
		||||
              type="button"
 | 
			
		||||
          <td class="w-1/5 text-ellipsis flex justify-center">
 | 
			
		||||
            <CircleIconButton
 | 
			
		||||
              color="primary"
 | 
			
		||||
              icon={mdiPencilOutline}
 | 
			
		||||
              title="Edit import path"
 | 
			
		||||
              size="16"
 | 
			
		||||
              on:click={() => {
 | 
			
		||||
                editImportPath = listIndex;
 | 
			
		||||
                editedImportPath = validatedPath.importPath;
 | 
			
		||||
              }}
 | 
			
		||||
              class="rounded-full bg-immich-primary p-3 text-gray-100 transition-all duration-150 hover:bg-immich-primary/75 dark:bg-immich-dark-primary dark:text-gray-700"
 | 
			
		||||
            >
 | 
			
		||||
              <Icon path={mdiPencilOutline} size="16" />
 | 
			
		||||
            </button>
 | 
			
		||||
            />
 | 
			
		||||
          </td>
 | 
			
		||||
        </tr>
 | 
			
		||||
      {/each}
 | 
			
		||||
 | 
			
		||||
@ -1,11 +1,11 @@
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
  import Icon from '$lib/components/elements/icon.svelte';
 | 
			
		||||
  import { LibraryType, type LibraryResponseDto } from '@immich/sdk';
 | 
			
		||||
  import { mdiPencilOutline } from '@mdi/js';
 | 
			
		||||
  import { createEventDispatcher, onMount } from 'svelte';
 | 
			
		||||
  import { handleError } from '../../utils/handle-error';
 | 
			
		||||
  import Button from '../elements/buttons/button.svelte';
 | 
			
		||||
  import LibraryExclusionPatternForm from './library-exclusion-pattern-form.svelte';
 | 
			
		||||
  import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
 | 
			
		||||
 | 
			
		||||
  export let library: Partial<LibraryResponseDto>;
 | 
			
		||||
 | 
			
		||||
@ -138,17 +138,17 @@
 | 
			
		||||
          }`}
 | 
			
		||||
        >
 | 
			
		||||
          <td class="w-3/4 text-ellipsis px-4 text-sm">{exclusionPattern}</td>
 | 
			
		||||
          <td class="w-1/4 text-ellipsis px-4 text-sm">
 | 
			
		||||
            <button
 | 
			
		||||
              type="button"
 | 
			
		||||
          <td class="w-1/4 text-ellipsis flex justify-center">
 | 
			
		||||
            <CircleIconButton
 | 
			
		||||
              color="primary"
 | 
			
		||||
              icon={mdiPencilOutline}
 | 
			
		||||
              title="Edit exclusion pattern"
 | 
			
		||||
              size="16"
 | 
			
		||||
              on:click={() => {
 | 
			
		||||
                editExclusionPattern = listIndex;
 | 
			
		||||
                editedExclusionPattern = exclusionPattern;
 | 
			
		||||
              }}
 | 
			
		||||
              class="rounded-full bg-immich-primary p-3 text-gray-100 transition-all duration-150 hover:bg-immich-primary/75 dark:bg-immich-dark-primary dark:text-gray-700"
 | 
			
		||||
            >
 | 
			
		||||
              <Icon path={mdiPencilOutline} size="16" />
 | 
			
		||||
            </button>
 | 
			
		||||
            />
 | 
			
		||||
          </td>
 | 
			
		||||
        </tr>
 | 
			
		||||
      {/each}
 | 
			
		||||
 | 
			
		||||
@ -340,9 +340,12 @@
 | 
			
		||||
        class:opacity-0={galleryInView}
 | 
			
		||||
        class:opacity-100={!galleryInView}
 | 
			
		||||
      >
 | 
			
		||||
        <button on:click={() => memoryGallery.scrollIntoView({ behavior: 'smooth' })}>
 | 
			
		||||
          <CircleIconButton title="Show gallery" icon={mdiChevronDown} color="light" />
 | 
			
		||||
        </button>
 | 
			
		||||
        <CircleIconButton
 | 
			
		||||
          title="Show gallery"
 | 
			
		||||
          icon={mdiChevronDown}
 | 
			
		||||
          color="light"
 | 
			
		||||
          on:click={() => memoryGallery.scrollIntoView({ behavior: 'smooth' })}
 | 
			
		||||
        />
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <IntersectionObserver
 | 
			
		||||
 | 
			
		||||
@ -12,6 +12,7 @@
 | 
			
		||||
  import UserAvatar from '../user-avatar.svelte';
 | 
			
		||||
  import AvatarSelector from './avatar-selector.svelte';
 | 
			
		||||
  import FocusTrap from '$lib/components/shared-components/focus-trap.svelte';
 | 
			
		||||
  import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
 | 
			
		||||
 | 
			
		||||
  let isShowSelectAvatar = false;
 | 
			
		||||
 | 
			
		||||
@ -61,15 +62,16 @@
 | 
			
		||||
        {#key $user}
 | 
			
		||||
          <UserAvatar user={$user} size="xl" />
 | 
			
		||||
        {/key}
 | 
			
		||||
        <div
 | 
			
		||||
          class="absolute z-10 bottom-0 right-0 rounded-full w-6 h-6 border dark:border-immich-dark-primary bg-immich-primary"
 | 
			
		||||
        >
 | 
			
		||||
          <button
 | 
			
		||||
            class="flex items-center justify-center w-full h-full text-white"
 | 
			
		||||
        <div class="absolute z-10 bottom-0 right-0 rounded-full w-6 h-6">
 | 
			
		||||
          <CircleIconButton
 | 
			
		||||
            color="primary"
 | 
			
		||||
            icon={mdiPencil}
 | 
			
		||||
            title="Edit avatar"
 | 
			
		||||
            class="border"
 | 
			
		||||
            size="12"
 | 
			
		||||
            padding="2"
 | 
			
		||||
            on:click={() => (isShowSelectAvatar = true)}
 | 
			
		||||
          >
 | 
			
		||||
            <Icon path={mdiPencil} />
 | 
			
		||||
          </button>
 | 
			
		||||
          />
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div>
 | 
			
		||||
 | 
			
		||||
@ -8,6 +8,7 @@
 | 
			
		||||
  } from '$lib/components/shared-components/notification/notification';
 | 
			
		||||
  import { onMount } from 'svelte';
 | 
			
		||||
  import { mdiCloseCircleOutline, mdiInformationOutline, mdiWindowClose } from '@mdi/js';
 | 
			
		||||
  import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
 | 
			
		||||
 | 
			
		||||
  export let notification: Notification;
 | 
			
		||||
 | 
			
		||||
@ -78,9 +79,14 @@
 | 
			
		||||
        {notification.type.toString()}
 | 
			
		||||
      </h2>
 | 
			
		||||
    </div>
 | 
			
		||||
    <button on:click|stopPropagation={discard}>
 | 
			
		||||
      <Icon path={mdiWindowClose} size="20" />
 | 
			
		||||
    </button>
 | 
			
		||||
    <CircleIconButton
 | 
			
		||||
      icon={mdiWindowClose}
 | 
			
		||||
      title="Close"
 | 
			
		||||
      class="dark:text-immich-dark-gray"
 | 
			
		||||
      size="20"
 | 
			
		||||
      padding="2"
 | 
			
		||||
      on:click={discard}
 | 
			
		||||
    />
 | 
			
		||||
  </div>
 | 
			
		||||
 | 
			
		||||
  <p class="whitespace-pre-wrap pl-[28px] pr-[16px] text-sm" data-testid="message">
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,5 @@
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
  import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
 | 
			
		||||
  import Icon from '$lib/components/elements/icon.svelte';
 | 
			
		||||
  import { locale } from '$lib/stores/preferences.store';
 | 
			
		||||
  import type { SessionResponseDto } from '@immich/sdk';
 | 
			
		||||
@ -64,14 +65,14 @@
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    {#if !device.current}
 | 
			
		||||
      <div class="flex flex-col justify-center text-sm">
 | 
			
		||||
        <button
 | 
			
		||||
          on:click={() => dispatcher('delete')}
 | 
			
		||||
          class="rounded-full bg-immich-primary p-3 text-gray-100 transition-all duration-150 hover:bg-immich-primary/75 dark:bg-immich-dark-primary dark:text-gray-700"
 | 
			
		||||
      <div>
 | 
			
		||||
        <CircleIconButton
 | 
			
		||||
          color="primary"
 | 
			
		||||
          icon={mdiTrashCanOutline}
 | 
			
		||||
          title="Log out"
 | 
			
		||||
        >
 | 
			
		||||
          <Icon path={mdiTrashCanOutline} size="16" />
 | 
			
		||||
        </button>
 | 
			
		||||
          size="16"
 | 
			
		||||
          on:click={() => dispatcher('delete')}
 | 
			
		||||
        />
 | 
			
		||||
      </div>
 | 
			
		||||
    {/if}
 | 
			
		||||
  </div>
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,4 @@
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
  import Icon from '$lib/components/elements/icon.svelte';
 | 
			
		||||
  import { locale } from '$lib/stores/preferences.store';
 | 
			
		||||
  import { createApiKey, deleteApiKey, getApiKeys, updateApiKey, type ApiKeyResponseDto } from '@immich/sdk';
 | 
			
		||||
  import { mdiPencilOutline, mdiTrashCanOutline } from '@mdi/js';
 | 
			
		||||
@ -10,6 +9,7 @@
 | 
			
		||||
  import APIKeySecret from '../forms/api-key-secret.svelte';
 | 
			
		||||
  import ConfirmDialogue from '../shared-components/confirm-dialogue.svelte';
 | 
			
		||||
  import { NotificationType, notificationController } from '../shared-components/notification/notification';
 | 
			
		||||
  import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
 | 
			
		||||
 | 
			
		||||
  export let keys: ApiKeyResponseDto[];
 | 
			
		||||
 | 
			
		||||
@ -143,19 +143,21 @@
 | 
			
		||||
                <td class="w-1/3 text-ellipsis px-4 text-sm"
 | 
			
		||||
                  >{new Date(key.createdAt).toLocaleDateString($locale, format)}
 | 
			
		||||
                </td>
 | 
			
		||||
                <td class="w-1/3 text-ellipsis px-4 text-sm">
 | 
			
		||||
                  <button
 | 
			
		||||
                <td class="flex flex-row flex-wrap justify-center gap-x-2 gap-y-1 w-1/3">
 | 
			
		||||
                  <CircleIconButton
 | 
			
		||||
                    color="primary"
 | 
			
		||||
                    icon={mdiPencilOutline}
 | 
			
		||||
                    title="Edit key"
 | 
			
		||||
                    size="16"
 | 
			
		||||
                    on:click={() => (editKey = key)}
 | 
			
		||||
                    class="rounded-full bg-immich-primary p-3 text-gray-100 transition-all duration-150 hover:bg-immich-primary/75 dark:bg-immich-dark-primary dark:text-gray-700"
 | 
			
		||||
                  >
 | 
			
		||||
                    <Icon path={mdiPencilOutline} size="16" />
 | 
			
		||||
                  </button>
 | 
			
		||||
                  <button
 | 
			
		||||
                  />
 | 
			
		||||
                  <CircleIconButton
 | 
			
		||||
                    color="primary"
 | 
			
		||||
                    icon={mdiTrashCanOutline}
 | 
			
		||||
                    title="Delete key"
 | 
			
		||||
                    size="16"
 | 
			
		||||
                    on:click={() => (deleteKey = key)}
 | 
			
		||||
                    class="rounded-full bg-immich-primary p-3 text-gray-100 transition-all duration-150 hover:bg-immich-primary/75 dark:bg-immich-dark-primary dark:text-gray-700"
 | 
			
		||||
                  >
 | 
			
		||||
                    <Icon path={mdiTrashCanOutline} size="16" />
 | 
			
		||||
                  </button>
 | 
			
		||||
                  />
 | 
			
		||||
                </td>
 | 
			
		||||
              </tr>
 | 
			
		||||
            {/key}
 | 
			
		||||
 | 
			
		||||
@ -2,6 +2,7 @@
 | 
			
		||||
  import { page } from '$app/stores';
 | 
			
		||||
  import Icon from '$lib/components/elements/icon.svelte';
 | 
			
		||||
  import ImmichLogo from '$lib/components/shared-components/immich-logo.svelte';
 | 
			
		||||
  import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
 | 
			
		||||
  import { copyToClipboard } from '$lib/utils';
 | 
			
		||||
  import { mdiCodeTags, mdiContentCopy, mdiMessage, mdiPartyPopper } from '@mdi/js';
 | 
			
		||||
 | 
			
		||||
@ -36,12 +37,12 @@
 | 
			
		||||
              🚨 Error - Something went wrong
 | 
			
		||||
            </h1>
 | 
			
		||||
            <div class="flex justify-end">
 | 
			
		||||
              <button
 | 
			
		||||
              <CircleIconButton
 | 
			
		||||
                color="primary"
 | 
			
		||||
                icon={mdiContentCopy}
 | 
			
		||||
                title="Copy error"
 | 
			
		||||
                on:click={() => handleCopy()}
 | 
			
		||||
                class="rounded-full bg-immich-primary px-3 py-2 text-sm text-white shadow-md transition-colors hover:bg-immich-primary/75 dark:bg-immich-dark-primary dark:text-immich-dark-gray dark:hover:bg-immich-dark-primary/80"
 | 
			
		||||
              >
 | 
			
		||||
                <Icon path={mdiContentCopy} size={24} />
 | 
			
		||||
              </button>
 | 
			
		||||
              />
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -37,6 +37,7 @@
 | 
			
		||||
  import { fade, slide } from 'svelte/transition';
 | 
			
		||||
  import LinkButton from '../../../lib/components/elements/buttons/link-button.svelte';
 | 
			
		||||
  import type { PageData } from './$types';
 | 
			
		||||
  import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
 | 
			
		||||
 | 
			
		||||
  export let data: PageData;
 | 
			
		||||
 | 
			
		||||
@ -386,12 +387,13 @@
 | 
			
		||||
                {/if}
 | 
			
		||||
 | 
			
		||||
                <td class=" text-ellipsis px-4 text-sm">
 | 
			
		||||
                  <button
 | 
			
		||||
                    class="rounded-full bg-immich-primary p-3 text-gray-100 transition-all duration-150 hover:bg-immich-primary/75 dark:bg-immich-dark-primary dark:text-gray-700"
 | 
			
		||||
                    on:click|stopPropagation|preventDefault={(e) => showMenu(e, library, index)}
 | 
			
		||||
                  >
 | 
			
		||||
                    <Icon path={mdiDotsVertical} size="16" />
 | 
			
		||||
                  </button>
 | 
			
		||||
                  <CircleIconButton
 | 
			
		||||
                    color="primary"
 | 
			
		||||
                    icon={mdiDotsVertical}
 | 
			
		||||
                    title="Library options"
 | 
			
		||||
                    size="16"
 | 
			
		||||
                    on:click={(e) => showMenu(e, library, index)}
 | 
			
		||||
                  />
 | 
			
		||||
 | 
			
		||||
                  {#if showContextMenu}
 | 
			
		||||
                    <Portal target="body">
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user