1
0
mirror of https://github.com/immich-app/immich.git synced 2025-04-24 13:16:41 +02:00

feat(web): increase usage of CircleIconButton (#9256)

This commit is contained in:
Ben 2024-05-04 18:29:50 +00:00 committed by GitHub
parent 5b87abb021
commit 48b490f5e9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 166 additions and 177 deletions

View File

@ -1,6 +1,5 @@
<script lang="ts"> <script lang="ts">
import Badge from '$lib/components/elements/badge.svelte'; 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 Icon from '$lib/components/elements/icon.svelte';
import { locale } from '$lib/stores/preferences.store'; import { locale } from '$lib/stores/preferences.store';
import { JobCommand, type JobCommandDto, type JobCountsDto, type QueueStatusDto } from '@immich/sdk'; import { JobCommand, type JobCommandDto, type JobCountsDto, type QueueStatusDto } from '@immich/sdk';
@ -16,6 +15,7 @@
import { createEventDispatcher } from 'svelte'; import { createEventDispatcher } from 'svelte';
import JobTileButton from './job-tile-button.svelte'; import JobTileButton from './job-tile-button.svelte';
import JobTileStatus from './job-tile-status.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 title: string;
export let subtitle: string | undefined; export let subtitle: string | undefined;
@ -56,16 +56,19 @@
<div class="flex gap-2"> <div class="flex gap-2">
{#if jobCounts.failed > 0} {#if jobCounts.failed > 0}
<Badge color="primary"> <Badge color="primary">
<div class="flex flex-row gap-1">
<span class="text-sm"> <span class="text-sm">
{jobCounts.failed.toLocaleString($locale)} failed {jobCounts.failed.toLocaleString($locale)} failed
</span> </span>
<Button <CircleIconButton
size="tiny" color="primary"
shadow={false} icon={mdiClose}
title="Clear message"
size="12"
padding="1"
on:click={() => dispatch('command', { command: JobCommand.ClearFailed, force: false })} on:click={() => dispatch('command', { command: JobCommand.ClearFailed, force: false })}
> />
<Icon path={mdiClose} size="18" /> </div>
</Button>
</Badge> </Badge>
{/if} {/if}
{#if jobCounts.delayed > 0} {#if jobCounts.delayed > 0}

View File

@ -33,7 +33,7 @@
data-testid="context-button-parent" data-testid="context-button-parent"
> >
<CircleIconButton <CircleIconButton
color="light" color="opaque"
title="Show album options" title="Show album options"
icon={mdiDotsVertical} icon={mdiDotsVertical}
size="20" size="20"

View File

@ -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" 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}> <button class={disabled ? 'cursor-not-allowed' : ''} on:click={() => dispatch('favorite')} {disabled}>
<!-- svelte-ignore missing-declaration -->
<div class="items-center justify-center"> <div class="items-center justify-center">
<Icon path={isLiked ? mdiHeart : mdiHeartOutline} size={24} /> <Icon path={isLiked ? mdiHeart : mdiHeartOutline} size={24} />
</div> </div>

View File

@ -160,12 +160,7 @@
bind:clientHeight={activityHeight} bind:clientHeight={activityHeight}
> >
<div class="flex place-items-center gap-2"> <div class="flex place-items-center gap-2">
<button <CircleIconButton on:click={() => dispatch('close')} icon={mdiClose} title="Close" />
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>
<p class="text-lg text-immich-fg dark:text-immich-dark-fg">Activity</p> <p class="text-lg text-immich-fg dark:text-immich-dark-fg">Activity</p>
</div> </div>
@ -193,10 +188,13 @@
</a> </a>
{/if} {/if}
{#if reaction.user.id === user.id || albumOwnerId === user.id} {#if reaction.user.id === user.id || albumOwnerId === user.id}
<div class="flex items-start w-fit pt-[5px]" title="Delete comment"> <div class="flex items-start w-fit pt-[5px]">
<button on:click={() => (showDeleteReaction[index] ? '' : showOptionsMenu(index))}> <CircleIconButton
<Icon path={mdiDotsVertical} /> icon={mdiDotsVertical}
</button> title="Comment options"
size="16"
on:click={() => (showDeleteReaction[index] ? '' : showOptionsMenu(index))}
/>
</div> </div>
{/if} {/if}
<div> <div>
@ -242,10 +240,13 @@
</a> </a>
{/if} {/if}
{#if reaction.user.id === user.id || albumOwnerId === user.id} {#if reaction.user.id === user.id || albumOwnerId === user.id}
<div class="flex items-start w-fit" title="Delete like"> <div class="flex items-start w-fit">
<button on:click={() => (showDeleteReaction[index] ? '' : showOptionsMenu(index))}> <CircleIconButton
<Icon path={mdiDotsVertical} /> icon={mdiDotsVertical}
</button> title="Reaction options"
size="16"
on:click={() => (showDeleteReaction[index] ? '' : showOptionsMenu(index))}
/>
</div> </div>
{/if} {/if}
<div> <div>

View File

@ -169,13 +169,7 @@
<section class="relative p-2 dark:bg-immich-dark-bg dark:text-immich-dark-fg"> <section class="relative p-2 dark:bg-immich-dark-bg dark:text-immich-dark-fg">
<div class="flex place-items-center gap-2"> <div class="flex place-items-center gap-2">
<button <CircleIconButton icon={mdiClose} title="Close" on:click={() => dispatch('close')} />
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>
<p class="text-lg text-immich-fg dark:text-immich-dark-fg">Info</p> <p class="text-lg text-immich-fg dark:text-immich-dark-fg">Info</p>
</div> </div>
@ -401,9 +395,13 @@
<p class="break-all flex place-items-center gap-2"> <p class="break-all flex place-items-center gap-2">
{asset.originalFileName} {asset.originalFileName}
{#if isOwner} {#if isOwner}
<button title="Show File Location" on:click={toggleAssetPath} class="-translate-y-[2px]"> <CircleIconButton
<Icon path={mdiInformationOutline} /> icon={mdiInformationOutline}
</button> title="Show file location"
size="16"
padding="2"
on:click={toggleAssetPath}
/>
{/if} {/if}
</p> </p>
<div class="flex gap-2 text-sm"> <div class="flex gap-2 text-sm">

View File

@ -1,20 +1,23 @@
<script lang="ts"> <script lang="ts">
import { mdiClose, mdiMagnify } from '@mdi/js'; import { mdiClose, mdiMagnify } from '@mdi/js';
import Icon from './icon.svelte';
import { createEventDispatcher } from 'svelte'; import { createEventDispatcher } from 'svelte';
import type { SearchOptions } from '$lib/utils/dipatch'; import type { SearchOptions } from '$lib/utils/dipatch';
import LoadingSpinner from '../shared-components/loading-spinner.svelte'; 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 name: string;
export let roundedBottom = true; export let roundedBottom = true;
export let showLoadingSpinner: boolean; export let showLoadingSpinner: boolean;
export let placeholder: string; export let placeholder: string;
let inputRef: HTMLElement;
const dispatch = createEventDispatcher<{ search: SearchOptions; reset: void }>(); const dispatch = createEventDispatcher<{ search: SearchOptions; reset: void }>();
const resetSearch = () => { const resetSearch = () => {
name = ''; name = '';
dispatch('reset'); dispatch('reset');
inputRef?.focus();
}; };
const handleSearch = (event: KeyboardEvent) => { const handleSearch = (event: KeyboardEvent) => {
@ -29,16 +32,19 @@
? 'rounded-2xl' ? 'rounded-2xl'
: 'rounded-t-lg'} bg-gray-200 p-2 dark:bg-immich-dark-gray gap-2 place-items-center h-full" : '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 })}> <CircleIconButton
<div class="w-fit"> icon={mdiMagnify}
<Icon path={mdiMagnify} size="24" /> title="Search"
</div> size="16"
</button> padding="2"
on:click={() => dispatch('search', { force: true })}
/>
<input <input
class="w-full gap-2 bg-gray-200 dark:bg-immich-dark-gray dark:text-white" class="w-full gap-2 bg-gray-200 dark:bg-immich-dark-gray dark:text-white"
type="text" type="text"
{placeholder} {placeholder}
bind:value={name} bind:value={name}
bind:this={inputRef}
on:keydown={handleSearch} on:keydown={handleSearch}
on:input={() => dispatch('search', { force: false })} on:input={() => dispatch('search', { force: false })}
/> />
@ -48,8 +54,6 @@
</div> </div>
{/if} {/if}
{#if name} {#if name}
<button on:click={resetSearch}> <CircleIconButton icon={mdiClose} title="Clear value" size="16" padding="2" on:click={resetSearch} />
<Icon path={mdiClose} />
</button>
{/if} {/if}
</div> </div>

View File

@ -9,9 +9,9 @@
import { linear } from 'svelte/easing'; import { linear } from 'svelte/easing';
import { fly } from 'svelte/transition'; import { fly } from 'svelte/transition';
import ImageThumbnail from '../assets/thumbnail/image-thumbnail.svelte'; import ImageThumbnail from '../assets/thumbnail/image-thumbnail.svelte';
import Icon from '../elements/icon.svelte';
import LoadingSpinner from '../shared-components/loading-spinner.svelte'; import LoadingSpinner from '../shared-components/loading-spinner.svelte';
import SearchPeople from '$lib/components/faces-page/people-search.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 peopleWithFaces: AssetFaceResponseDto[];
export let allPeople: PersonResponseDto[]; export let allPeople: PersonResponseDto[];
@ -119,38 +119,19 @@
<div class="flex place-items-center justify-between gap-2"> <div class="flex place-items-center justify-between gap-2">
{#if !searchFaces} {#if !searchFaces}
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<button <CircleIconButton icon={mdiArrowLeftThin} title="Back" on:click={handleBackButton} />
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>
<p class="flex text-lg text-immich-fg dark:text-immich-dark-fg">Select face</p> <p class="flex text-lg text-immich-fg dark:text-immich-dark-fg">Select face</p>
</div> </div>
<div class="flex justify-end gap-2"> <div class="flex justify-end gap-2">
<button <CircleIconButton
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" icon={mdiMagnify}
title="Search existing person" title="Search for existing person"
on:click={() => { on:click={() => {
searchFaces = true; searchFaces = true;
}} }}
> />
<div>
<Icon path={mdiMagnify} size="24" />
</div>
</button>
{#if !isShowLoadingNewPerson} {#if !isShowLoadingNewPerson}
<button <CircleIconButton icon={mdiPlus} title="Create new person" on:click={handleCreatePerson} />
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>
{:else} {:else}
<div class="flex place-content-center place-items-center"> <div class="flex place-content-center place-items-center">
<LoadingSpinner /> <LoadingSpinner />
@ -158,14 +139,7 @@
{/if} {/if}
</div> </div>
{:else} {:else}
<button <CircleIconButton icon={mdiArrowLeftThin} title="Back" on:click={handleBackButton} />
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>
<div class="w-full flex"> <div class="w-full flex">
<SearchPeople <SearchPeople
type="input" type="input"
@ -179,14 +153,7 @@
</div> </div>
{/if} {/if}
</div> </div>
<button <CircleIconButton icon={mdiClose} title="Cancel search" on:click={() => (searchFaces = false)} />
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>
{/if} {/if}
</div> </div>
<div class="px-4 py-4 text-sm"> <div class="px-4 py-4 text-sm">

View File

@ -74,7 +74,7 @@
<div class="absolute right-2 top-2" class:hidden={!showVerticalDots}> <div class="absolute right-2 top-2" class:hidden={!showVerticalDots}>
<CircleIconButton <CircleIconButton
color="light" color="opaque"
icon={mdiDotsVertical} icon={mdiDotsVertical}
title="Show person options" title="Show person options"
size="20" size="20"

View File

@ -15,14 +15,14 @@
type AssetFaceResponseDto, type AssetFaceResponseDto,
type PersonResponseDto, type PersonResponseDto,
} from '@immich/sdk'; } from '@immich/sdk';
import { mdiArrowLeftThin, mdiRestart } from '@mdi/js'; import { mdiArrowLeftThin, mdiMinus, mdiRestart } from '@mdi/js';
import { createEventDispatcher, onMount } from 'svelte'; import { createEventDispatcher, onMount } from 'svelte';
import { linear } from 'svelte/easing'; import { linear } from 'svelte/easing';
import { fly } from 'svelte/transition'; import { fly } from 'svelte/transition';
import ImageThumbnail from '../assets/thumbnail/image-thumbnail.svelte'; import ImageThumbnail from '../assets/thumbnail/image-thumbnail.svelte';
import Icon from '../elements/icon.svelte';
import { NotificationType, notificationController } from '../shared-components/notification/notification'; import { NotificationType, notificationController } from '../shared-components/notification/notification';
import AssignFaceSidePanel from './assign-face-side-panel.svelte'; 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 assetId: string;
export let assetType: AssetTypeEnum; export let assetType: AssetTypeEnum;
@ -42,7 +42,7 @@
let isShowLoadingPeople = false; let isShowLoadingPeople = false;
// search people // search people
let showSeletecFaces = false; let showSelectedFaces = false;
let allPeople: PersonResponseDto[] = []; let allPeople: PersonResponseDto[] = [];
// timers // timers
@ -159,21 +159,21 @@
if (newFeaturePhoto && personToUpdate) { if (newFeaturePhoto && personToUpdate) {
selectedPersonToCreate[personToUpdate.id] = newFeaturePhoto; selectedPersonToCreate[personToUpdate.id] = newFeaturePhoto;
} }
showSeletecFaces = false; showSelectedFaces = false;
}; };
const handleReassignFace = (person: PersonResponseDto | null) => { const handleReassignFace = (person: PersonResponseDto | null) => {
const personToUpdate = peopleWithFaces.find((face) => face.person?.id === editedPerson.id); const personToUpdate = peopleWithFaces.find((face) => face.person?.id === editedPerson.id);
if (person && personToUpdate) { if (person && personToUpdate) {
selectedPersonToReassign[personToUpdate.id] = person; selectedPersonToReassign[personToUpdate.id] = person;
showSeletecFaces = false; showSelectedFaces = false;
} }
}; };
const handlePersonPicker = (person: PersonResponseDto | null) => { const handlePersonPicker = (person: PersonResponseDto | null) => {
if (person) { if (person) {
editedPerson = person; editedPerson = person;
showSeletecFaces = true; showSelectedFaces = true;
} }
}; };
</script> </script>
@ -184,14 +184,7 @@
> >
<div class="flex place-items-center justify-between gap-2"> <div class="flex place-items-center justify-between gap-2">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<button <CircleIconButton icon={mdiArrowLeftThin} title="Back" on:click={handleBackButton} />
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>
<p class="flex text-lg text-immich-fg dark:text-immich-dark-fg">Edit faces</p> <p class="flex text-lg text-immich-fg dark:text-immich-dark-fg">Edit faces</p>
</div> </div>
{#if !isShowLoadingDone} {#if !isShowLoadingDone}
@ -273,21 +266,27 @@
</p> </p>
{/if} {/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]} {#if selectedPersonToCreate[face.id] || selectedPersonToReassign[face.id]}
<button on:click={() => handleReset(face.id)} class="flex h-full w-full"> <CircleIconButton
<div class="absolute left-1/2 top-1/2 translate-x-[-50%] translate-y-[-50%] transform"> color="primary"
<div> icon={mdiRestart}
<Icon path={mdiRestart} size={18} /> title="Reset"
</div> size="18"
</div> padding="1"
</button> class="absolute left-1/2 top-1/2 translate-x-[-50%] translate-y-[-50%] transform"
{:else} on:click={() => handleReset(face.id)}
<button on:click={() => handlePersonPicker(face.person)} class="flex h-full w-full"> />
<div {:else}
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={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} {/if}
</div> </div>
</div> </div>
@ -299,14 +298,14 @@
</div> </div>
</section> </section>
{#if showSeletecFaces} {#if showSelectedFaces}
<AssignFaceSidePanel <AssignFaceSidePanel
{peopleWithFaces} {peopleWithFaces}
{allPeople} {allPeople}
{editedPerson} {editedPerson}
{assetType} {assetType}
{assetId} {assetId}
on:close={() => (showSeletecFaces = false)} on:close={() => (showSelectedFaces = false)}
on:createPerson={(event) => handleCreatePerson(event.detail)} on:createPerson={(event) => handleCreatePerson(event.detail)}
on:reassign={(event) => handleReassignFace(event.detail)} on:reassign={(event) => handleReassignFace(event.detail)}
/> />

View File

@ -8,6 +8,7 @@
import { validate, type LibraryResponseDto } from '@immich/sdk'; import { validate, type LibraryResponseDto } from '@immich/sdk';
import type { ValidateLibraryImportPathResponseDto } from '@immich/sdk'; import type { ValidateLibraryImportPathResponseDto } from '@immich/sdk';
import { NotificationType, notificationController } from '../shared-components/notification/notification'; import { NotificationType, notificationController } from '../shared-components/notification/notification';
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
export let library: LibraryResponseDto; export let library: LibraryResponseDto;
@ -209,17 +210,17 @@
</td> </td>
<td class="w-4/5 text-ellipsis px-4 text-sm">{validatedPath.importPath}</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"> <td class="w-1/5 text-ellipsis flex justify-center">
<button <CircleIconButton
type="button" color="primary"
icon={mdiPencilOutline}
title="Edit import path"
size="16"
on:click={() => { on:click={() => {
editImportPath = listIndex; editImportPath = listIndex;
editedImportPath = validatedPath.importPath; 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> </td>
</tr> </tr>
{/each} {/each}

View File

@ -1,11 +1,11 @@
<script lang="ts"> <script lang="ts">
import Icon from '$lib/components/elements/icon.svelte';
import { LibraryType, type LibraryResponseDto } from '@immich/sdk'; import { LibraryType, type LibraryResponseDto } from '@immich/sdk';
import { mdiPencilOutline } from '@mdi/js'; import { mdiPencilOutline } from '@mdi/js';
import { createEventDispatcher, onMount } from 'svelte'; import { createEventDispatcher, onMount } from 'svelte';
import { handleError } from '../../utils/handle-error'; import { handleError } from '../../utils/handle-error';
import Button from '../elements/buttons/button.svelte'; import Button from '../elements/buttons/button.svelte';
import LibraryExclusionPatternForm from './library-exclusion-pattern-form.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>; 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-3/4 text-ellipsis px-4 text-sm">{exclusionPattern}</td>
<td class="w-1/4 text-ellipsis px-4 text-sm"> <td class="w-1/4 text-ellipsis flex justify-center">
<button <CircleIconButton
type="button" color="primary"
icon={mdiPencilOutline}
title="Edit exclusion pattern"
size="16"
on:click={() => { on:click={() => {
editExclusionPattern = listIndex; editExclusionPattern = listIndex;
editedExclusionPattern = exclusionPattern; 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> </td>
</tr> </tr>
{/each} {/each}

View File

@ -340,9 +340,12 @@
class:opacity-0={galleryInView} class:opacity-0={galleryInView}
class:opacity-100={!galleryInView} class:opacity-100={!galleryInView}
> >
<button on:click={() => memoryGallery.scrollIntoView({ behavior: 'smooth' })}> <CircleIconButton
<CircleIconButton title="Show gallery" icon={mdiChevronDown} color="light" /> title="Show gallery"
</button> icon={mdiChevronDown}
color="light"
on:click={() => memoryGallery.scrollIntoView({ behavior: 'smooth' })}
/>
</div> </div>
<IntersectionObserver <IntersectionObserver

View File

@ -12,6 +12,7 @@
import UserAvatar from '../user-avatar.svelte'; import UserAvatar from '../user-avatar.svelte';
import AvatarSelector from './avatar-selector.svelte'; import AvatarSelector from './avatar-selector.svelte';
import FocusTrap from '$lib/components/shared-components/focus-trap.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; let isShowSelectAvatar = false;
@ -61,15 +62,16 @@
{#key $user} {#key $user}
<UserAvatar user={$user} size="xl" /> <UserAvatar user={$user} size="xl" />
{/key} {/key}
<div <div class="absolute z-10 bottom-0 right-0 rounded-full w-6 h-6">
class="absolute z-10 bottom-0 right-0 rounded-full w-6 h-6 border dark:border-immich-dark-primary bg-immich-primary" <CircleIconButton
> color="primary"
<button icon={mdiPencil}
class="flex items-center justify-center w-full h-full text-white" title="Edit avatar"
class="border"
size="12"
padding="2"
on:click={() => (isShowSelectAvatar = true)} on:click={() => (isShowSelectAvatar = true)}
> />
<Icon path={mdiPencil} />
</button>
</div> </div>
</div> </div>
<div> <div>

View File

@ -8,6 +8,7 @@
} from '$lib/components/shared-components/notification/notification'; } from '$lib/components/shared-components/notification/notification';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { mdiCloseCircleOutline, mdiInformationOutline, mdiWindowClose } from '@mdi/js'; import { mdiCloseCircleOutline, mdiInformationOutline, mdiWindowClose } from '@mdi/js';
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
export let notification: Notification; export let notification: Notification;
@ -78,9 +79,14 @@
{notification.type.toString()} {notification.type.toString()}
</h2> </h2>
</div> </div>
<button on:click|stopPropagation={discard}> <CircleIconButton
<Icon path={mdiWindowClose} size="20" /> icon={mdiWindowClose}
</button> title="Close"
class="dark:text-immich-dark-gray"
size="20"
padding="2"
on:click={discard}
/>
</div> </div>
<p class="whitespace-pre-wrap pl-[28px] pr-[16px] text-sm" data-testid="message"> <p class="whitespace-pre-wrap pl-[28px] pr-[16px] text-sm" data-testid="message">

View File

@ -1,4 +1,5 @@
<script lang="ts"> <script lang="ts">
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
import Icon from '$lib/components/elements/icon.svelte'; import Icon from '$lib/components/elements/icon.svelte';
import { locale } from '$lib/stores/preferences.store'; import { locale } from '$lib/stores/preferences.store';
import type { SessionResponseDto } from '@immich/sdk'; import type { SessionResponseDto } from '@immich/sdk';
@ -64,14 +65,14 @@
</div> </div>
</div> </div>
{#if !device.current} {#if !device.current}
<div class="flex flex-col justify-center text-sm"> <div>
<button <CircleIconButton
on:click={() => dispatcher('delete')} color="primary"
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={mdiTrashCanOutline}
title="Log out" title="Log out"
> size="16"
<Icon path={mdiTrashCanOutline} size="16" /> on:click={() => dispatcher('delete')}
</button> />
</div> </div>
{/if} {/if}
</div> </div>

View File

@ -1,5 +1,4 @@
<script lang="ts"> <script lang="ts">
import Icon from '$lib/components/elements/icon.svelte';
import { locale } from '$lib/stores/preferences.store'; import { locale } from '$lib/stores/preferences.store';
import { createApiKey, deleteApiKey, getApiKeys, updateApiKey, type ApiKeyResponseDto } from '@immich/sdk'; import { createApiKey, deleteApiKey, getApiKeys, updateApiKey, type ApiKeyResponseDto } from '@immich/sdk';
import { mdiPencilOutline, mdiTrashCanOutline } from '@mdi/js'; import { mdiPencilOutline, mdiTrashCanOutline } from '@mdi/js';
@ -10,6 +9,7 @@
import APIKeySecret from '../forms/api-key-secret.svelte'; import APIKeySecret from '../forms/api-key-secret.svelte';
import ConfirmDialogue from '../shared-components/confirm-dialogue.svelte'; import ConfirmDialogue from '../shared-components/confirm-dialogue.svelte';
import { NotificationType, notificationController } from '../shared-components/notification/notification'; import { NotificationType, notificationController } from '../shared-components/notification/notification';
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
export let keys: ApiKeyResponseDto[]; export let keys: ApiKeyResponseDto[];
@ -143,19 +143,21 @@
<td class="w-1/3 text-ellipsis px-4 text-sm" <td class="w-1/3 text-ellipsis px-4 text-sm"
>{new Date(key.createdAt).toLocaleDateString($locale, format)} >{new Date(key.createdAt).toLocaleDateString($locale, format)}
</td> </td>
<td class="w-1/3 text-ellipsis px-4 text-sm"> <td class="flex flex-row flex-wrap justify-center gap-x-2 gap-y-1 w-1/3">
<button <CircleIconButton
color="primary"
icon={mdiPencilOutline}
title="Edit key"
size="16"
on:click={() => (editKey = key)} 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" />
> <CircleIconButton
<Icon path={mdiPencilOutline} size="16" /> color="primary"
</button> icon={mdiTrashCanOutline}
<button title="Delete key"
size="16"
on:click={() => (deleteKey = key)} 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> </td>
</tr> </tr>
{/key} {/key}

View File

@ -2,6 +2,7 @@
import { page } from '$app/stores'; import { page } from '$app/stores';
import Icon from '$lib/components/elements/icon.svelte'; import Icon from '$lib/components/elements/icon.svelte';
import ImmichLogo from '$lib/components/shared-components/immich-logo.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 { copyToClipboard } from '$lib/utils';
import { mdiCodeTags, mdiContentCopy, mdiMessage, mdiPartyPopper } from '@mdi/js'; import { mdiCodeTags, mdiContentCopy, mdiMessage, mdiPartyPopper } from '@mdi/js';
@ -36,12 +37,12 @@
🚨 Error - Something went wrong 🚨 Error - Something went wrong
</h1> </h1>
<div class="flex justify-end"> <div class="flex justify-end">
<button <CircleIconButton
color="primary"
icon={mdiContentCopy}
title="Copy error"
on:click={() => handleCopy()} 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>
</div> </div>

View File

@ -37,6 +37,7 @@
import { fade, slide } from 'svelte/transition'; import { fade, slide } from 'svelte/transition';
import LinkButton from '../../../lib/components/elements/buttons/link-button.svelte'; import LinkButton from '../../../lib/components/elements/buttons/link-button.svelte';
import type { PageData } from './$types'; import type { PageData } from './$types';
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
export let data: PageData; export let data: PageData;
@ -386,12 +387,13 @@
{/if} {/if}
<td class=" text-ellipsis px-4 text-sm"> <td class=" text-ellipsis px-4 text-sm">
<button <CircleIconButton
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" color="primary"
on:click|stopPropagation|preventDefault={(e) => showMenu(e, library, index)} icon={mdiDotsVertical}
> title="Library options"
<Icon path={mdiDotsVertical} size="16" /> size="16"
</button> on:click={(e) => showMenu(e, library, index)}
/>
{#if showContextMenu} {#if showContextMenu}
<Portal target="body"> <Portal target="body">