From 3a2bf91889ddfeb0262f48228e0d845e506bce05 Mon Sep 17 00:00:00 2001 From: Jason Rasmussen <jason@rasm.me> Date: Thu, 16 Jan 2025 11:03:04 -0500 Subject: [PATCH] refactor: replace link-button component with immich-ui buttons (#15374) * refactor: replace link-button component with immich-ui buttons * minor styling tweak --------- Co-authored-by: Alex Tran <alex.tran1502@gmail.com> --- web/src/app.css | 2 +- .../album-page/albums-controls.svelte | 90 ++++---- .../elements/buttons/link-button.svelte | 25 --- .../lib/components/elements/dropdown.svelte | 26 +-- .../map-page/map-settings-modal.svelte | 198 ++++++++++-------- .../create-shared-link-modal.svelte | 35 ++-- .../navigation-bar/navigation-bar.svelte | 24 ++- web/src/routes/(user)/people/+page.svelte | 19 +- web/src/routes/(user)/sharing/+page.svelte | 34 ++- .../[[assetId=id]]/+page.svelte | 53 ++--- .../[[assetId=id]]/+page.svelte | 48 +++-- .../[[assetId=id]]/+page.svelte | 57 ++--- web/src/routes/admin/jobs-status/+page.svelte | 26 +-- .../admin/library-management/+page.svelte | 22 +- web/src/routes/admin/repair/+page.svelte | 68 +++--- .../routes/admin/system-settings/+page.svelte | 41 ++-- 16 files changed, 385 insertions(+), 383 deletions(-) delete mode 100644 web/src/lib/components/elements/buttons/link-button.svelte diff --git a/web/src/app.css b/web/src/app.css index 00cefc5ce6..7a547d3504 100644 --- a/web/src/app.css +++ b/web/src/app.css @@ -26,7 +26,7 @@ :root { /* light */ --immich-ui-primary: 66 80 175; - --immich-ui-dark: 0 0 0; + --immich-ui-dark: 58 58 58; --immich-ui-light: 255 255 255; --immich-ui-success: 34 197 94; --immich-ui-danger: 180 0 0; diff --git a/web/src/lib/components/album-page/albums-controls.svelte b/web/src/lib/components/album-page/albums-controls.svelte index 85a7260f40..12b83a2b4c 100644 --- a/web/src/lib/components/album-page/albums-controls.svelte +++ b/web/src/lib/components/album-page/albums-controls.svelte @@ -1,15 +1,30 @@ <script lang="ts"> - import LinkButton from '$lib/components/elements/buttons/link-button.svelte'; import Dropdown from '$lib/components/elements/dropdown.svelte'; + import GroupTab from '$lib/components/elements/group-tab.svelte'; import Icon from '$lib/components/elements/icon.svelte'; + import SearchBar from '$lib/components/elements/search-bar.svelte'; import { AlbumFilter, - AlbumSortBy, AlbumGroupBy, + AlbumSortBy, AlbumViewMode, albumViewSettings, SortOrder, } from '$lib/stores/preferences.store'; + import { + type AlbumGroupOptionMetadata, + type AlbumSortOptionMetadata, + collapseAllAlbumGroups, + createAlbumAndRedirect, + expandAllAlbumGroups, + findFilterOption, + findGroupOptionMetadata, + findSortOptionMetadata, + getSelectedAlbumGroupOption, + groupOptionsMetadata, + sortOptionsMetadata, + } from '$lib/utils/album-utils'; + import { Button, IconButton, Text } from '@immich/ui'; import { mdiArrowDownThin, mdiArrowUpThin, @@ -22,21 +37,8 @@ mdiUnfoldMoreHorizontal, mdiViewGridOutline, } from '@mdi/js'; - import { - type AlbumGroupOptionMetadata, - type AlbumSortOptionMetadata, - findGroupOptionMetadata, - findFilterOption, - findSortOptionMetadata, - getSelectedAlbumGroupOption, - groupOptionsMetadata, - sortOptionsMetadata, - } from '$lib/utils/album-utils'; - import SearchBar from '$lib/components/elements/search-bar.svelte'; - import GroupTab from '$lib/components/elements/group-tab.svelte'; - import { createAlbumAndRedirect, collapseAllAlbumGroups, expandAllAlbumGroups } from '$lib/utils/album-utils'; - import { fly } from 'svelte/transition'; import { t } from 'svelte-i18n'; + import { fly } from 'svelte/transition'; interface Props { albumGroups: string[]; @@ -127,12 +129,10 @@ </div> <!-- Create Album --> -<LinkButton onclick={() => createAlbumAndRedirect()}> - <div class="flex place-items-center gap-2 text-sm"> - <Icon path={mdiPlusBoxOutline} size="18" /> - <p class="hidden md:block">{$t('create_album')}</p> - </div> -</LinkButton> +<Button onclick={() => createAlbumAndRedirect()} size="small" variant="ghost" color="secondary"> + <Icon path={mdiPlusBoxOutline} /> + <p class="hidden md:block">{$t('create_album')}</p> +</Button> <!-- Sort Albums --> <Dropdown @@ -164,34 +164,38 @@ <!-- Expand Album Groups --> <div class="hidden xl:flex gap-0"> <div class="block"> - <LinkButton title={$t('expand_all')} onclick={() => expandAllAlbumGroups()}> - <div class="flex place-items-center gap-2 text-sm"> - <Icon path={mdiUnfoldMoreHorizontal} size="18" /> - </div> - </LinkButton> + <IconButton + title={$t('expand_all')} + onclick={() => expandAllAlbumGroups()} + variant="ghost" + color="secondary" + shape="round" + icon={mdiUnfoldMoreHorizontal} + /> </div> <!-- Collapse Album Groups --> <div class="block"> - <LinkButton title={$t('collapse_all')} onclick={() => collapseAllAlbumGroups(albumGroups)}> - <div class="flex place-items-center gap-2 text-sm"> - <Icon path={mdiUnfoldLessHorizontal} size="18" /> - </div> - </LinkButton> + <IconButton + title={$t('collapse_all')} + onclick={() => collapseAllAlbumGroups(albumGroups)} + variant="ghost" + color="secondary" + shape="round" + icon={mdiUnfoldLessHorizontal} + /> </div> </div> </span> {/if} <!-- Cover/List Display Toggle --> -<LinkButton onclick={() => handleChangeListMode()}> - <div class="flex place-items-center gap-2 text-sm"> - {#if $albumViewSettings.view === AlbumViewMode.List} - <Icon path={mdiViewGridOutline} size="18" /> - <p class="hidden md:block">{$t('covers')}</p> - {:else} - <Icon path={mdiFormatListBulletedSquare} size="18" /> - <p class="hidden md:block">{$t('list')}</p> - {/if} - </div> -</LinkButton> +<Button onclick={() => handleChangeListMode()} size="small" variant="ghost" color="secondary"> + {#if $albumViewSettings.view === AlbumViewMode.List} + <Icon path={mdiViewGridOutline} /> + <Text class="hidden md:block">{$t('covers')}</Text> + {:else} + <Icon path={mdiFormatListBulletedSquare} size="18" /> + <Text class="hidden md:block">{$t('list')}</Text> + {/if} +</Button> diff --git a/web/src/lib/components/elements/buttons/link-button.svelte b/web/src/lib/components/elements/buttons/link-button.svelte deleted file mode 100644 index a39e2608cf..0000000000 --- a/web/src/lib/components/elements/buttons/link-button.svelte +++ /dev/null @@ -1,25 +0,0 @@ -<script lang="ts" module> - export type Color = 'transparent-primary' | 'transparent-gray'; -</script> - -<script lang="ts"> - import Button from '$lib/components/elements/buttons/button.svelte'; - import type { Snippet } from 'svelte'; - - interface Props { - href?: string; - color?: Color; - children?: Snippet; - onclick?: (e: MouseEvent) => void; - title?: string; - disabled?: boolean; - fullwidth?: boolean; - class?: string; - } - - let { color = 'transparent-gray', children, ...rest }: Props = $props(); -</script> - -<Button size="link" {color} shadow={false} rounded="lg" {...rest}> - {@render children?.()} -</Button> diff --git a/web/src/lib/components/elements/dropdown.svelte b/web/src/lib/components/elements/dropdown.svelte index b146f347dc..25d279b0f4 100644 --- a/web/src/lib/components/elements/dropdown.svelte +++ b/web/src/lib/components/elements/dropdown.svelte @@ -11,14 +11,12 @@ </script> <script lang="ts" generics="T"> - import Icon from './icon.svelte'; - - import { mdiCheck } from '@mdi/js'; - - import { isEqual } from 'lodash-es'; - import LinkButton from './buttons/link-button.svelte'; import { clickOutside } from '$lib/actions/click-outside'; + import { Button, Text } from '@immich/ui'; + import { mdiCheck } from '@mdi/js'; + import { isEqual } from 'lodash-es'; import { fly } from 'svelte/transition'; + import Icon from './icon.svelte'; interface Props { class?: string; @@ -82,14 +80,12 @@ <div use:clickOutside={{ onOutclick: handleClickOutside, onEscape: handleClickOutside }}> <!-- BUTTON TITLE --> - <LinkButton onclick={() => (showMenu = true)} fullwidth {title}> - <div class="flex place-items-center gap-2 text-sm"> - {#if renderedSelectedOption?.icon} - <Icon path={renderedSelectedOption.icon} size="18" /> - {/if} - <p class={hideTextOnSmallScreen ? 'hidden sm:block' : ''}>{renderedSelectedOption.title}</p> - </div> - </LinkButton> + <Button onclick={() => (showMenu = true)} fullWidth {title} variant="ghost" color="secondary" size="small"> + {#if renderedSelectedOption?.icon} + <Icon path={renderedSelectedOption.icon} /> + {/if} + <Text class={hideTextOnSmallScreen ? 'hidden sm:block' : ''}>{renderedSelectedOption.title}</Text> + </Button> <!-- DROP DOWN MENU --> {#if showMenu} @@ -108,7 +104,7 @@ > {#if isEqual(selectedOption, option)} <div class="text-immich-primary dark:text-immich-dark-primary"> - <Icon path={mdiCheck} size="18" /> + <Icon path={mdiCheck} /> </div> <p class="justify-self-start text-immich-primary dark:text-immich-dark-primary"> {renderedOption.title} diff --git a/web/src/lib/components/map-page/map-settings-modal.svelte b/web/src/lib/components/map-page/map-settings-modal.svelte index 270978e120..387da7f997 100644 --- a/web/src/lib/components/map-page/map-settings-modal.svelte +++ b/web/src/lib/components/map-page/map-settings-modal.svelte @@ -1,13 +1,11 @@ <script lang="ts"> import FullScreenModal from '$lib/components/shared-components/full-screen-modal.svelte'; import SettingSelect from '$lib/components/shared-components/settings/setting-select.svelte'; - import SettingSwitch from '$lib/components/shared-components/settings/setting-switch.svelte'; import type { MapSettings } from '$lib/stores/preferences.store'; + import { Button, Field, Stack, Switch } from '@immich/ui'; import { Duration } from 'luxon'; import { t } from 'svelte-i18n'; import { fly } from 'svelte/transition'; - import Button from '../elements/buttons/button.svelte'; - import LinkButton from '../elements/buttons/link-button.svelte'; import DateInput from '../elements/date-input.svelte'; interface Props { @@ -16,7 +14,8 @@ onSave: (settings: MapSettings) => void; } - let { settings = $bindable(), onClose, onSave }: Props = $props(); + let { settings: initialValues, onClose, onSave }: Props = $props(); + let settings = $state(initialValues); let customDateRange = $state(!!settings.dateAfter || !!settings.dateBefore); @@ -26,90 +25,109 @@ }; </script> -<FullScreenModal title={$t('map_settings')} {onClose}> - <form {onsubmit} class="flex flex-col gap-4 text-immich-primary dark:text-immich-dark-primary" id="map-settings-form"> - <SettingSwitch title={$t('allow_dark_mode')} bind:checked={settings.allowDarkMode} /> - <SettingSwitch title={$t('only_favorites')} bind:checked={settings.onlyFavorites} /> - <SettingSwitch title={$t('include_archived')} bind:checked={settings.includeArchived} /> - <SettingSwitch title={$t('include_shared_partner_assets')} bind:checked={settings.withPartners} /> - <SettingSwitch title={$t('include_shared_albums')} bind:checked={settings.withSharedAlbums} /> - {#if customDateRange} - <div in:fly={{ y: 10, duration: 200 }} class="flex flex-col gap-4"> - <div class="flex items-center justify-between gap-8"> - <label class="immich-form-label shrink-0 text-sm" for="date-after">{$t('date_after')}</label> - <DateInput - class="immich-form-input w-40" - type="date" - id="date-after" - max={settings.dateBefore} - bind:value={settings.dateAfter} - /> - </div> - <div class="flex items-center justify-between gap-8"> - <label class="immich-form-label shrink-0 text-sm" for="date-before">{$t('date_before')}</label> - <DateInput class="immich-form-input w-40" type="date" id="date-before" bind:value={settings.dateBefore} /> - </div> - <div class="flex justify-center text-xs"> - <LinkButton - onclick={() => { - customDateRange = false; - settings.dateAfter = ''; - settings.dateBefore = ''; - }} - > - {$t('remove_custom_date_range')} - </LinkButton> - </div> - </div> - {:else} - <div in:fly={{ y: -10, duration: 200 }} class="flex flex-col gap-1"> - <SettingSelect - label={$t('date_range')} - name="date-range" - bind:value={settings.relativeDate} - options={[ - { - value: '', - text: $t('all'), - }, - { - value: Duration.fromObject({ hours: 24 }).toISO() || '', - text: $t('past_durations.hours', { values: { hours: 24 } }), - }, - { - value: Duration.fromObject({ days: 7 }).toISO() || '', - text: $t('past_durations.days', { values: { days: 7 } }), - }, - { - value: Duration.fromObject({ days: 30 }).toISO() || '', - text: $t('past_durations.days', { values: { days: 30 } }), - }, - { - value: Duration.fromObject({ years: 1 }).toISO() || '', - text: $t('past_durations.years', { values: { years: 1 } }), - }, - { - value: Duration.fromObject({ years: 3 }).toISO() || '', - text: $t('past_durations.years', { values: { years: 3 } }), - }, - ]} - /> - <div class="text-xs"> - <LinkButton - onclick={() => { - customDateRange = true; - settings.relativeDate = ''; - }} - > - {$t('use_custom_date_range')} - </LinkButton> - </div> - </div> - {/if} - </form> +<form {onsubmit}> + <FullScreenModal title={$t('map_settings')} {onClose}> + <Stack gap={4}> + <Field label={$t('allow_dark_mode')}> + <Switch bind:checked={settings.allowDarkMode} class="flex justify-between items-center text-sm" /> + </Field> + <Field label={$t('only_favorites')}> + <Switch bind:checked={settings.onlyFavorites} class="flex justify-between items-center text-sm" /> + </Field> + <Field label={$t('include_archived')}> + <Switch bind:checked={settings.includeArchived} class="flex justify-between items-center text-sm" /> + </Field> + <Field label={$t('include_shared_partner_assets')}> + <Switch bind:checked={settings.withPartners} class="flex justify-between items-center text-sm" /> + </Field> + <Field label={$t('include_shared_albums')}> + <Switch bind:checked={settings.withSharedAlbums} class="flex justify-between items-center text-sm" /> + </Field> - {#snippet stickyBottom()} - <Button color="gray" size="sm" fullwidth onclick={onClose}>{$t('cancel')}</Button> - <Button type="submit" size="sm" fullwidth form="map-settings-form">{$t('save')}</Button> - {/snippet} -</FullScreenModal> + {#if customDateRange} + <div in:fly={{ y: 10, duration: 200 }} class="flex flex-col gap-4"> + <div class="flex items-center justify-between gap-8"> + <label class="immich-form-label shrink-0 text-sm" for="date-after">{$t('date_after')}</label> + <DateInput + class="immich-form-input w-40" + type="date" + id="date-after" + max={settings.dateBefore} + bind:value={settings.dateAfter} + /> + </div> + <div class="flex items-center justify-between gap-8"> + <label class="immich-form-label shrink-0 text-sm" for="date-before">{$t('date_before')}</label> + <DateInput class="immich-form-input w-40" type="date" id="date-before" bind:value={settings.dateBefore} /> + </div> + <div class="flex justify-center text-xs"> + <Button + color="primary" + size="small" + variant="ghost" + onclick={() => { + customDateRange = false; + settings.dateAfter = ''; + settings.dateBefore = ''; + }} + > + {$t('remove_custom_date_range')} + </Button> + </div> + </div> + {:else} + <div in:fly={{ y: -10, duration: 200 }} class="flex flex-col gap-1"> + <SettingSelect + label={$t('date_range')} + name="date-range" + bind:value={settings.relativeDate} + options={[ + { + value: '', + text: $t('all'), + }, + { + value: Duration.fromObject({ hours: 24 }).toISO() || '', + text: $t('past_durations.hours', { values: { hours: 24 } }), + }, + { + value: Duration.fromObject({ days: 7 }).toISO() || '', + text: $t('past_durations.days', { values: { days: 7 } }), + }, + { + value: Duration.fromObject({ days: 30 }).toISO() || '', + text: $t('past_durations.days', { values: { days: 30 } }), + }, + { + value: Duration.fromObject({ years: 1 }).toISO() || '', + text: $t('past_durations.years', { values: { years: 1 } }), + }, + { + value: Duration.fromObject({ years: 3 }).toISO() || '', + text: $t('past_durations.years', { values: { years: 3 } }), + }, + ]} + /> + <div class="text-xs"> + <Button + color="primary" + size="small" + variant="ghost" + onclick={() => { + customDateRange = true; + settings.relativeDate = ''; + }} + > + {$t('use_custom_date_range')} + </Button> + </div> + </div> + {/if} + </Stack> + + {#snippet stickyBottom()} + <Button color="secondary" shape="round" fullWidth onclick={onClose}>{$t('cancel')}</Button> + <Button type="submit" shape="round" fullWidth>{$t('save')}</Button> + {/snippet} + </FullScreenModal> +</form> diff --git a/web/src/lib/components/shared-components/create-share-link-modal/create-shared-link-modal.svelte b/web/src/lib/components/shared-components/create-share-link-modal/create-shared-link-modal.svelte index 443e8f06b1..77890529b7 100644 --- a/web/src/lib/components/shared-components/create-share-link-modal/create-shared-link-modal.svelte +++ b/web/src/lib/components/shared-components/create-share-link-modal/create-shared-link-modal.svelte @@ -1,21 +1,20 @@ <script lang="ts"> import Button from '$lib/components/elements/buttons/button.svelte'; - import LinkButton from '$lib/components/elements/buttons/link-button.svelte'; - import Icon from '$lib/components/elements/icon.svelte'; + import FullScreenModal from '$lib/components/shared-components/full-screen-modal.svelte'; + import SettingSelect from '$lib/components/shared-components/settings/setting-select.svelte'; + import { SettingInputFieldType } from '$lib/constants'; + import { locale } from '$lib/stores/preferences.store'; import { serverConfig } from '$lib/stores/server-config.store'; import { copyToClipboard, makeSharedLinkUrl } from '$lib/utils'; import { handleError } from '$lib/utils/handle-error'; import { SharedLinkType, createSharedLink, updateSharedLink, type SharedLinkResponseDto } from '@immich/sdk'; + import { HStack, IconButton, Input } from '@immich/ui'; import { mdiContentCopy, mdiLink } from '@mdi/js'; + import { DateTime, Duration } from 'luxon'; + import { t } from 'svelte-i18n'; import { NotificationType, notificationController } from '../notification/notification'; import SettingInputField from '../settings/setting-input-field.svelte'; import SettingSwitch from '../settings/setting-switch.svelte'; - import FullScreenModal from '$lib/components/shared-components/full-screen-modal.svelte'; - import { t } from 'svelte-i18n'; - import { locale } from '$lib/stores/preferences.store'; - import { DateTime, Duration } from 'luxon'; - import SettingSelect from '$lib/components/shared-components/settings/setting-select.svelte'; - import { SettingInputFieldType } from '$lib/constants'; interface Props { onClose: () => void; @@ -243,14 +242,18 @@ <Button size="sm" fullwidth onclick={handleCreateSharedLink}>{$t('create_link')}</Button> {/if} {:else} - <div class="flex w-full gap-2"> - <input class="immich-form-input w-full" bind:value={sharedLink} disabled /> - <LinkButton onclick={() => (sharedLink ? copyToClipboard(sharedLink) : '')}> - <div class="flex place-items-center gap-2 text-sm"> - <Icon path={mdiContentCopy} ariaLabel={$t('copy_link_to_clipboard')} size="18" /> - </div> - </LinkButton> - </div> + <HStack class="w-full"> + <Input bind:value={sharedLink} disabled class="flex flex-row" /> + <IconButton + variant="ghost" + shape="round" + color="secondary" + size="giant" + icon={mdiContentCopy} + onclick={() => (sharedLink ? copyToClipboard(sharedLink) : '')} + aria-label={$t('copy_link_to_clipboard')} + /> + </HStack> {/if} {/snippet} </FullScreenModal> diff --git a/web/src/lib/components/shared-components/navigation-bar/navigation-bar.svelte b/web/src/lib/components/shared-components/navigation-bar/navigation-bar.svelte index ae63a249b5..193cc2ed43 100644 --- a/web/src/lib/components/shared-components/navigation-bar/navigation-bar.svelte +++ b/web/src/lib/components/shared-components/navigation-bar/navigation-bar.svelte @@ -2,25 +2,25 @@ import { page } from '$app/state'; import { clickOutside } from '$lib/actions/click-outside'; import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte'; - import LinkButton from '$lib/components/elements/buttons/link-button.svelte'; import SkipLink from '$lib/components/elements/buttons/skip-link.svelte'; import Icon from '$lib/components/elements/icon.svelte'; + import HelpAndFeedbackModal from '$lib/components/shared-components/help-and-feedback-modal.svelte'; + import ImmichLogo from '$lib/components/shared-components/immich-logo.svelte'; + import SearchBar from '$lib/components/shared-components/search-bar/search-bar.svelte'; + import { AppRoute } from '$lib/constants'; import { featureFlags } from '$lib/stores/server-config.store'; import { user } from '$lib/stores/user.store'; import { userInteraction } from '$lib/stores/user.svelte'; import { handleLogout } from '$lib/utils/auth'; import { getAboutInfo, logout, type ServerAboutResponseDto } from '@immich/sdk'; + import { Button, IconButton } from '@immich/ui'; import { mdiHelpCircleOutline, mdiMagnify, mdiTrayArrowUp } from '@mdi/js'; + import { onMount } from 'svelte'; import { t } from 'svelte-i18n'; import { fade } from 'svelte/transition'; - import { AppRoute } from '$lib/constants'; - import ImmichLogo from '$lib/components/shared-components/immich-logo.svelte'; - import SearchBar from '$lib/components/shared-components/search-bar/search-bar.svelte'; import ThemeButton from '../theme-button.svelte'; import UserAvatar from '../user-avatar.svelte'; import AccountInfoPanel from './account-info-panel.svelte'; - import HelpAndFeedbackModal from '$lib/components/shared-components/help-and-feedback-modal.svelte'; - import { onMount } from 'svelte'; interface Props { showUploadButton?: boolean; @@ -87,22 +87,24 @@ onEscape: () => (shouldShowHelpPanel = false), }} > - <CircleIconButton - id="support-feedback-button" + <IconButton + shape="round" + color="secondary" + variant="ghost" + size="large" title={$t('support_and_feedback')} icon={mdiHelpCircleOutline} onclick={() => (shouldShowHelpPanel = !shouldShowHelpPanel)} - padding="1" /> </div> {#if !page.url.pathname.includes('/admin') && showUploadButton} - <LinkButton onclick={onUploadClick} class="hidden lg:block"> + <Button onclick={onUploadClick} class="hidden lg:block" variant="ghost" color="secondary"> <div class="flex gap-2"> <Icon path={mdiTrayArrowUp} size="1.5em" /> <span>{$t('upload')}</span> </div> - </LinkButton> + </Button> <CircleIconButton onclick={onUploadClick} title={$t('upload')} diff --git a/web/src/routes/(user)/people/+page.svelte b/web/src/routes/(user)/people/+page.svelte index 0b51a7e240..c367010353 100644 --- a/web/src/routes/(user)/people/+page.svelte +++ b/web/src/routes/(user)/people/+page.svelte @@ -3,8 +3,6 @@ import { page } from '$app/stores'; import { focusTrap } from '$lib/actions/focus-trap'; import { scrollMemory } from '$lib/actions/scroll-memory'; - import Button from '$lib/components/elements/buttons/button.svelte'; - import LinkButton from '$lib/components/elements/buttons/link-button.svelte'; import Icon from '$lib/components/elements/icon.svelte'; import ManagePeopleVisibility from '$lib/components/faces-page/manage-people-visibility.svelte'; import MergeSuggestionModal from '$lib/components/faces-page/merge-suggestion-modal.svelte'; @@ -32,6 +30,7 @@ updatePerson, type PersonResponseDto, } from '@immich/sdk'; + import { Button, Text } from '@immich/ui'; import { mdiAccountOff, mdiEyeOutline } from '@mdi/js'; import { onMount } from 'svelte'; import { t } from 'svelte-i18n'; @@ -393,12 +392,10 @@ /> </div> </div> - <LinkButton onclick={() => (selectHidden = !selectHidden)}> - <div class="flex flex-wrap place-items-center justify-center gap-x-1 text-sm"> - <Icon path={mdiEyeOutline} size="18" /> - <p class="ml-2">{$t('show_and_hide_people')}</p> - </div> - </LinkButton> + <Button onclick={() => (selectHidden = !selectHidden)} size="small" variant="ghost" color="secondary"> + <Icon path={mdiEyeOutline} /> + <Text>{$t('show_and_hide_people')}</Text> + </Button> </div> {/if} {/snippet} @@ -445,13 +442,13 @@ {#snippet stickyBottom()} <Button - color="gray" - fullwidth + color="secondary" + fullWidth onclick={() => { showChangeNameModal = false; }}>{$t('cancel')}</Button > - <Button type="submit" fullwidth form="change-name-form">{$t('ok')}</Button> + <Button type="submit" fullWidth form="change-name-form">{$t('ok')}</Button> {/snippet} </FullScreenModal> {/if} diff --git a/web/src/routes/(user)/sharing/+page.svelte b/web/src/routes/(user)/sharing/+page.svelte index 1e59a2720d..33282761aa 100644 --- a/web/src/routes/(user)/sharing/+page.svelte +++ b/web/src/routes/(user)/sharing/+page.svelte @@ -1,14 +1,11 @@ <script lang="ts"> import empty2Url from '$lib/assets/empty-2.svg'; - import LinkButton from '$lib/components/elements/buttons/link-button.svelte'; + import Albums from '$lib/components/album-page/albums-list.svelte'; import Icon from '$lib/components/elements/icon.svelte'; import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte'; import EmptyPlaceholder from '$lib/components/shared-components/empty-placeholder.svelte'; import UserAvatar from '$lib/components/shared-components/user-avatar.svelte'; import { AppRoute } from '$lib/constants'; - import { mdiLink, mdiPlusBoxOutline } from '@mdi/js'; - import type { PageData } from './$types'; - import { createAlbumAndRedirect } from '$lib/utils/album-utils'; import { AlbumFilter, AlbumGroupBy, @@ -17,8 +14,11 @@ SortOrder, type AlbumViewSettings, } from '$lib/stores/preferences.store'; - import Albums from '$lib/components/album-page/albums-list.svelte'; + import { createAlbumAndRedirect } from '$lib/utils/album-utils'; + import { Button, HStack } from '@immich/ui'; + import { mdiLink, mdiPlusBoxOutline } from '@mdi/js'; import { t } from 'svelte-i18n'; + import type { PageData } from './$types'; interface Props { data: PageData; @@ -39,21 +39,17 @@ <UserPageLayout title={data.meta.title}> {#snippet buttons()} - <div class="flex"> - <LinkButton onclick={() => createAlbumAndRedirect()}> - <div class="flex flex-wrap place-items-center justify-center gap-x-1 text-sm"> - <Icon path={mdiPlusBoxOutline} size="18" class="shrink-0" /> - <span class="leading-none max-sm:text-xs">{$t('create_album')}</span> - </div> - </LinkButton> + <HStack gap={0}> + <Button onclick={() => createAlbumAndRedirect()} size="small" variant="ghost" color="secondary"> + <Icon path={mdiPlusBoxOutline} class="shrink-0" /> + <span class="leading-none max-sm:text-xs">{$t('create_album')}</span> + </Button> - <LinkButton href={AppRoute.SHARED_LINKS}> - <div class="flex flex-wrap place-items-center justify-center gap-x-1 text-sm"> - <Icon path={mdiLink} size="18" class="shrink-0" /> - <span class="leading-none max-sm:text-xs">{$t('shared_links')}</span> - </div> - </LinkButton> - </div> + <Button href={AppRoute.SHARED_LINKS} size="small" variant="ghost" color="secondary"> + <Icon path={mdiLink} class="shrink-0" /> + <span class="leading-none max-sm:text-xs">{$t('shared_links')}</span> + </Button> + </HStack> {/snippet} <div class="flex flex-col"> diff --git a/web/src/routes/(user)/tags/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/tags/[[photos=photos]]/[[assetId=id]]/+page.svelte index b9955644a8..742e386722 100644 --- a/web/src/routes/(user)/tags/[[photos=photos]]/[[assetId=id]]/+page.svelte +++ b/web/src/routes/(user)/tags/[[photos=photos]]/[[assetId=id]]/+page.svelte @@ -1,11 +1,11 @@ <script lang="ts"> import { goto } from '$app/navigation'; import { page } from '$app/stores'; - import Button from '$lib/components/elements/buttons/button.svelte'; - import LinkButton from '$lib/components/elements/buttons/link-button.svelte'; + import SkipLink from '$lib/components/elements/buttons/skip-link.svelte'; import Icon from '$lib/components/elements/icon.svelte'; import UserPageLayout, { headerId } from '$lib/components/layouts/user-page-layout.svelte'; import AssetGrid from '$lib/components/photos-page/asset-grid.svelte'; + import { dialogController } from '$lib/components/shared-components/dialog/dialog'; import FullScreenModal from '$lib/components/shared-components/full-screen-modal.svelte'; import { notificationController, @@ -13,19 +13,18 @@ } from '$lib/components/shared-components/notification/notification'; import SettingInputField from '$lib/components/shared-components/settings/setting-input-field.svelte'; import SideBarSection from '$lib/components/shared-components/side-bar/side-bar-section.svelte'; + import Breadcrumbs from '$lib/components/shared-components/tree/breadcrumbs.svelte'; import TreeItemThumbnails from '$lib/components/shared-components/tree/tree-item-thumbnails.svelte'; import TreeItems from '$lib/components/shared-components/tree/tree-items.svelte'; import { AppRoute, AssetAction, QueryParameter, SettingInputFieldType } from '$lib/constants'; + import { AssetInteraction } from '$lib/stores/asset-interaction.svelte'; import { AssetStore } from '$lib/stores/assets.store'; import { buildTree, normalizeTreePath } from '$lib/utils/tree-utils'; import { deleteTag, getAllTags, updateTag, upsertTags, type TagResponseDto } from '@immich/sdk'; + import { Button, HStack, Text } from '@immich/ui'; import { mdiPencil, mdiPlus, mdiTag, mdiTagMultiple, mdiTrashCanOutline } from '@mdi/js'; import { t } from 'svelte-i18n'; import type { PageData } from './$types'; - import { dialogController } from '$lib/components/shared-components/dialog/dialog'; - import Breadcrumbs from '$lib/components/shared-components/tree/breadcrumbs.svelte'; - import SkipLink from '$lib/components/elements/buttons/skip-link.svelte'; - import { AssetInteraction } from '$lib/stores/asset-interaction.svelte'; interface Props { data: PageData; @@ -169,29 +168,23 @@ {/snippet} {#snippet buttons()} - <section> - <LinkButton onclick={handleCreate}> - <div class="flex place-items-center gap-2 text-sm"> - <Icon path={mdiPlus} size="18" /> - <p class="hidden md:block">{$t('create_tag')}</p> - </div> - </LinkButton> + <HStack> + <Button onclick={handleCreate} size="small" variant="ghost" color="secondary"> + <Icon path={mdiPlus} /> + <p class="hidden md:block">{$t('create_tag')}</p> + </Button> {#if pathSegments.length > 0 && tag} - <LinkButton onclick={handleEdit}> - <div class="flex place-items-center gap-2 text-sm"> - <Icon path={mdiPencil} size="18" /> - <p class="hidden md:block">{$t('edit_tag')}</p> - </div> - </LinkButton> - <LinkButton onclick={handleDelete}> - <div class="flex place-items-center gap-2 text-sm"> - <Icon path={mdiTrashCanOutline} size="18" /> - <p class="hidden md:block">{$t('delete_tag')}</p> - </div> - </LinkButton> + <Button onclick={handleEdit} size="small" variant="ghost" color="secondary"> + <Icon path={mdiPencil} size="18" /> + <Text class="hidden md:block">{$t('edit_tag')}</Text> + </Button> + <Button onclick={handleDelete} size="small" variant="ghost" color="secondary"> + <Icon path={mdiTrashCanOutline} /> + <Text class="hidden md:block">{$t('delete_tag')}</Text> + </Button> {/if} - </section> + </HStack> {/snippet} <Breadcrumbs {pathSegments} icon={mdiTagMultiple} title={$t('tags')} {getLink} /> @@ -230,8 +223,8 @@ </form> {#snippet stickyBottom()} - <Button color="gray" fullwidth onclick={() => handleCancel()}>{$t('cancel')}</Button> - <Button type="submit" fullwidth form="create-tag-form">{$t('create')}</Button> + <Button color="secondary" fullWidth shape="round" onclick={() => handleCancel()}>{$t('cancel')}</Button> + <Button type="submit" fullWidth shape="round" form="create-tag-form">{$t('create')}</Button> {/snippet} </FullScreenModal> {/if} @@ -249,8 +242,8 @@ </form> {#snippet stickyBottom()} - <Button color="gray" fullwidth onclick={() => handleCancel()}>{$t('cancel')}</Button> - <Button type="submit" fullwidth form="edit-tag-form">{$t('save')}</Button> + <Button color="secondary" fullWidth shape="round" onclick={() => handleCancel()}>{$t('cancel')}</Button> + <Button type="submit" fullWidth shape="round" form="edit-tag-form">{$t('save')}</Button> {/snippet} </FullScreenModal> {/if} diff --git a/web/src/routes/(user)/trash/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/trash/[[photos=photos]]/[[assetId=id]]/+page.svelte index 686d82b125..031d2a6268 100644 --- a/web/src/routes/(user)/trash/[[photos=photos]]/[[assetId=id]]/+page.svelte +++ b/web/src/routes/(user)/trash/[[photos=photos]]/[[assetId=id]]/+page.svelte @@ -1,7 +1,6 @@ <script lang="ts"> import { goto } from '$app/navigation'; import empty3Url from '$lib/assets/empty-3.svg'; - import LinkButton from '$lib/components/elements/buttons/link-button.svelte'; import Icon from '$lib/components/elements/icon.svelte'; import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte'; import DeleteAssets from '$lib/components/photos-page/actions/delete-assets.svelte'; @@ -9,23 +8,24 @@ import SelectAllAssets from '$lib/components/photos-page/actions/select-all-assets.svelte'; import AssetGrid from '$lib/components/photos-page/asset-grid.svelte'; import AssetSelectControlBar from '$lib/components/photos-page/asset-select-control-bar.svelte'; + import { dialogController } from '$lib/components/shared-components/dialog/dialog'; import EmptyPlaceholder from '$lib/components/shared-components/empty-placeholder.svelte'; import { NotificationType, notificationController, } from '$lib/components/shared-components/notification/notification'; import { AppRoute } from '$lib/constants'; + import { AssetInteraction } from '$lib/stores/asset-interaction.svelte'; import { AssetStore } from '$lib/stores/assets.store'; import { featureFlags, serverConfig } from '$lib/stores/server-config.store'; + import { handlePromiseError } from '$lib/utils'; import { handleError } from '$lib/utils/handle-error'; import { emptyTrash, restoreTrash } from '@immich/sdk'; + import { Button, HStack } from '@immich/ui'; import { mdiDeleteForeverOutline, mdiHistory } from '@mdi/js'; - import type { PageData } from './$types'; - import { handlePromiseError } from '$lib/utils'; - import { dialogController } from '$lib/components/shared-components/dialog/dialog'; - import { t } from 'svelte-i18n'; import { onDestroy } from 'svelte'; - import { AssetInteraction } from '$lib/stores/asset-interaction.svelte'; + import { t } from 'svelte-i18n'; + import type { PageData } from './$types'; interface Props { data: PageData; @@ -113,20 +113,28 @@ {#if $featureFlags.loaded && $featureFlags.trash} <UserPageLayout hideNavbar={assetInteraction.selectionActive} title={data.meta.title} scrollbar={false}> {#snippet buttons()} - <div class="flex place-items-center gap-2"> - <LinkButton onclick={handleRestoreTrash} disabled={assetInteraction.selectionActive}> - <div class="flex place-items-center gap-2 text-sm"> - <Icon path={mdiHistory} size="18" /> - {$t('restore_all')} - </div> - </LinkButton> - <LinkButton onclick={() => handleEmptyTrash()} disabled={assetInteraction.selectionActive}> - <div class="flex place-items-center gap-2 text-sm"> - <Icon path={mdiDeleteForeverOutline} size="18" /> - {$t('empty_trash')} - </div> - </LinkButton> - </div> + <HStack gap={0}> + <Button + onclick={handleRestoreTrash} + disabled={assetInteraction.selectionActive} + variant="ghost" + color="secondary" + size="small" + > + <Icon path={mdiHistory} /> + {$t('restore_all')} + </Button> + <Button + onclick={() => handleEmptyTrash()} + disabled={assetInteraction.selectionActive} + variant="ghost" + color="secondary" + size="small" + > + <Icon path={mdiDeleteForeverOutline} /> + {$t('empty_trash')} + </Button> + </HStack> {/snippet} <AssetGrid enableRouting={true} {assetStore} {assetInteraction} onEscape={handleEscape}> diff --git a/web/src/routes/(user)/utilities/duplicates/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/utilities/duplicates/[[photos=photos]]/[[assetId=id]]/+page.svelte index 22e4f86c74..8515d163ab 100644 --- a/web/src/routes/(user)/utilities/duplicates/[[photos=photos]]/[[assetId=id]]/+page.svelte +++ b/web/src/routes/(user)/utilities/duplicates/[[photos=photos]]/[[assetId=id]]/+page.svelte @@ -1,27 +1,26 @@ <script lang="ts"> + import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte'; + import Icon from '$lib/components/elements/icon.svelte'; import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte'; import { dialogController } from '$lib/components/shared-components/dialog/dialog'; + import DuplicatesModal from '$lib/components/shared-components/duplicates-modal.svelte'; import { NotificationType, notificationController, } from '$lib/components/shared-components/notification/notification'; + import ShowShortcuts from '$lib/components/shared-components/show-shortcuts.svelte'; import DuplicatesCompareControl from '$lib/components/utilities-page/duplicates/duplicates-compare-control.svelte'; - import type { AssetResponseDto } from '@immich/sdk'; + import { locale } from '$lib/stores/preferences.store'; import { featureFlags } from '$lib/stores/server-config.store'; + import { stackAssets } from '$lib/utils/asset-utils'; + import { suggestDuplicate } from '$lib/utils/duplicate-utils'; import { handleError } from '$lib/utils/handle-error'; + import type { AssetResponseDto } from '@immich/sdk'; import { deleteAssets, updateAssets } from '@immich/sdk'; + import { Button, HStack, IconButton } from '@immich/ui'; + import { mdiCheckOutline, mdiInformationOutline, mdiKeyboard, mdiTrashCanOutline } from '@mdi/js'; import { t } from 'svelte-i18n'; import type { PageData } from './$types'; - import { suggestDuplicate } from '$lib/utils/duplicate-utils'; - import LinkButton from '$lib/components/elements/buttons/link-button.svelte'; - import { mdiCheckOutline, mdiInformationOutline, mdiTrashCanOutline } from '@mdi/js'; - import { stackAssets } from '$lib/utils/asset-utils'; - import ShowShortcuts from '$lib/components/shared-components/show-shortcuts.svelte'; - import DuplicatesModal from '$lib/components/shared-components/duplicates-modal.svelte'; - import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte'; - import { mdiKeyboard } from '@mdi/js'; - import Icon from '$lib/components/elements/icon.svelte'; - import { locale } from '$lib/stores/preferences.store'; interface Props { data: PageData; @@ -163,25 +162,31 @@ <UserPageLayout title={data.meta.title + ` (${duplicates.length.toLocaleString($locale)})`} scrollbar={true}> {#snippet buttons()} - <div class="flex place-items-center gap-2"> - <LinkButton onclick={() => handleDeduplicateAll()} disabled={!hasDuplicates}> - <div class="flex place-items-center gap-2 text-sm"> - <Icon path={mdiTrashCanOutline} size="18" /> - {$t('deduplicate_all')} - </div> - </LinkButton> - <LinkButton onclick={() => handleKeepAll()} disabled={!hasDuplicates}> - <div class="flex place-items-center gap-2 text-sm"> - <Icon path={mdiCheckOutline} size="18" /> - {$t('keep_all')} - </div> - </LinkButton> - <CircleIconButton + <HStack gap={0}> + <Button + onclick={() => handleDeduplicateAll()} + disabled={!hasDuplicates} + size="small" + variant="ghost" + color="secondary" + > + <Icon path={mdiTrashCanOutline} /> + {$t('deduplicate_all')} + </Button> + <Button onclick={() => handleKeepAll()} disabled={!hasDuplicates} size="small" variant="ghost" color="secondary"> + <Icon path={mdiCheckOutline} /> + {$t('keep_all')} + </Button> + <IconButton + shape="round" + variant="ghost" + color="secondary" + size="large" icon={mdiKeyboard} title={$t('show_keyboard_shortcuts')} onclick={() => (isShowKeyboardShortcut = !isShowKeyboardShortcut)} /> - </div> + </HStack> {/snippet} <div class=""> diff --git a/web/src/routes/admin/jobs-status/+page.svelte b/web/src/routes/admin/jobs-status/+page.svelte index b323a136aa..e3657f805a 100644 --- a/web/src/routes/admin/jobs-status/+page.svelte +++ b/web/src/routes/admin/jobs-status/+page.svelte @@ -1,6 +1,5 @@ <script lang="ts"> import JobsPanel from '$lib/components/admin-page/jobs/jobs-panel.svelte'; - import LinkButton from '$lib/components/elements/buttons/link-button.svelte'; import Icon from '$lib/components/elements/icon.svelte'; import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte'; import Combobox, { type ComboBoxOption } from '$lib/components/shared-components/combobox.svelte'; @@ -13,6 +12,7 @@ import { asyncTimeout } from '$lib/utils'; import { handleError } from '$lib/utils/handle-error'; import { createJob, getAllJobsStatus, ManualJobName, type AllJobStatusResponseDto } from '@immich/sdk'; + import { Button, HStack } from '@immich/ui'; import { mdiCog, mdiPlus } from '@mdi/js'; import { onDestroy, onMount } from 'svelte'; import { t } from 'svelte-i18n'; @@ -71,20 +71,16 @@ <UserPageLayout title={data.meta.title} admin> {#snippet buttons()} - <div class="flex justify-end"> - <LinkButton onclick={() => (isOpen = true)}> - <div class="flex place-items-center gap-2 text-sm"> - <Icon path={mdiPlus} size="18" /> - {$t('admin.create_job')} - </div> - </LinkButton> - <LinkButton href="{AppRoute.ADMIN_SETTINGS}?isOpen=job"> - <div class="flex place-items-center gap-2 text-sm"> - <Icon path={mdiCog} size="18" /> - {$t('admin.manage_concurrency')} - </div> - </LinkButton> - </div> + <HStack gap={0}> + <Button onclick={() => (isOpen = true)} size="small" variant="ghost" color="secondary"> + <Icon path={mdiPlus} size="18" /> + {$t('admin.create_job')} + </Button> + <Button href="{AppRoute.ADMIN_SETTINGS}?isOpen=job" size="small" variant="ghost" color="secondary"> + <Icon path={mdiCog} size="18" /> + {$t('admin.manage_concurrency')} + </Button> + </HStack> {/snippet} <section id="setting-content" class="flex place-content-center sm:mx-4"> <section class="w-full pb-28 sm:w-5/6 md:w-[850px]"> diff --git a/web/src/routes/admin/library-management/+page.svelte b/web/src/routes/admin/library-management/+page.svelte index b89e81ebf6..eb3f22878e 100644 --- a/web/src/routes/admin/library-management/+page.svelte +++ b/web/src/routes/admin/library-management/+page.svelte @@ -1,17 +1,20 @@ <script lang="ts"> import Icon from '$lib/components/elements/icon.svelte'; - import EmptyPlaceholder from '$lib/components/shared-components/empty-placeholder.svelte'; import LibraryImportPathsForm from '$lib/components/forms/library-import-paths-form.svelte'; import LibraryRenameForm from '$lib/components/forms/library-rename-form.svelte'; import LibraryScanSettingsForm from '$lib/components/forms/library-scan-settings-form.svelte'; import LibraryUserPickerForm from '$lib/components/forms/library-user-picker-form.svelte'; import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte'; + import ButtonContextMenu from '$lib/components/shared-components/context-menu/button-context-menu.svelte'; import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte'; + import { dialogController } from '$lib/components/shared-components/dialog/dialog'; + import EmptyPlaceholder from '$lib/components/shared-components/empty-placeholder.svelte'; import LoadingSpinner from '$lib/components/shared-components/loading-spinner.svelte'; import { notificationController, NotificationType, } from '$lib/components/shared-components/notification/notification'; + import { locale } from '$lib/stores/preferences.store'; import { ByteUnit, getBytesWithUnit } from '$lib/utils/byte-units'; import { handleError } from '$lib/utils/handle-error'; import { @@ -26,15 +29,12 @@ type LibraryStatsResponseDto, type UserResponseDto, } from '@immich/sdk'; + import { Button } from '@immich/ui'; import { mdiDatabase, mdiDotsVertical, mdiPlusBoxOutline, mdiSync } from '@mdi/js'; import { onMount } from 'svelte'; - import { fade, slide } from 'svelte/transition'; - import LinkButton from '../../../lib/components/elements/buttons/link-button.svelte'; - import type { PageData } from './$types'; - import { dialogController } from '$lib/components/shared-components/dialog/dialog'; import { t } from 'svelte-i18n'; - import ButtonContextMenu from '$lib/components/shared-components/context-menu/button-context-menu.svelte'; - import { locale } from '$lib/stores/preferences.store'; + import { fade, slide } from 'svelte/transition'; + import type { PageData } from './$types'; interface Props { data: PageData; @@ -220,19 +220,19 @@ {#snippet buttons()} <div class="flex justify-end gap-2"> {#if libraries.length > 0} - <LinkButton onclick={() => handleScanAll()}> + <Button onclick={handleScanAll} size="small" variant="ghost" color="secondary"> <div class="flex gap-1 text-sm"> <Icon path={mdiSync} size="18" /> <span>{$t('scan_all_libraries')}</span> </div> - </LinkButton> + </Button> {/if} - <LinkButton onclick={() => (toCreateLibrary = true)}> + <Button onclick={() => (toCreateLibrary = true)} size="small" variant="ghost" color="secondary"> <div class="flex gap-1 text-sm"> <Icon path={mdiPlusBoxOutline} size="18" /> <span>{$t('create_library')}</span> </div> - </LinkButton> + </Button> </div> {/snippet} <section class="my-4"> diff --git a/web/src/routes/admin/repair/+page.svelte b/web/src/routes/admin/repair/+page.svelte index 9f19fddd03..a6ffd52322 100644 --- a/web/src/routes/admin/repair/+page.svelte +++ b/web/src/routes/admin/repair/+page.svelte @@ -1,7 +1,6 @@ <script lang="ts"> import empty4Url from '$lib/assets/empty-4.svg'; import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte'; - import LinkButton from '$lib/components/elements/buttons/link-button.svelte'; import Icon from '$lib/components/elements/icon.svelte'; import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte'; import EmptyPlaceholder from '$lib/components/shared-components/empty-placeholder.svelte'; @@ -10,14 +9,15 @@ notificationController, } from '$lib/components/shared-components/notification/notification'; import { downloadManager } from '$lib/stores/download'; + import { locale } from '$lib/stores/preferences.store'; import { copyToClipboard } from '$lib/utils'; import { downloadBlob } from '$lib/utils/asset-utils'; import { handleError } from '$lib/utils/handle-error'; import { fixAuditFiles, getAuditFiles, getFileChecksums, type FileReportItemDto } from '@immich/sdk'; + import { Button, HStack } from '@immich/ui'; import { mdiCheckAll, mdiContentCopy, mdiDownload, mdiRefresh, mdiWrench } from '@mdi/js'; - import type { PageData } from './$types'; import { t } from 'svelte-i18n'; - import { locale } from '$lib/stores/preferences.store'; + import type { PageData } from './$types'; interface Props { data: PageData; @@ -185,32 +185,42 @@ <UserPageLayout title={data.meta.title} admin> {#snippet buttons()} - <div class="flex justify-end gap-2"> - <LinkButton onclick={() => handleRepair()} disabled={matches.length === 0 || repairing}> - <div class="flex place-items-center gap-2 text-sm"> - <Icon path={mdiWrench} size="18" /> - {$t('admin.repair_all')} - </div> - </LinkButton> - <LinkButton onclick={() => handleCheckAll()} disabled={extras.length === 0 || checking}> - <div class="flex place-items-center gap-2 text-sm"> - <Icon path={mdiCheckAll} size="18" /> - {$t('admin.check_all')} - </div> - </LinkButton> - <LinkButton onclick={() => handleDownload()} disabled={extras.length + orphans.length === 0}> - <div class="flex place-items-center gap-2 text-sm"> - <Icon path={mdiDownload} size="18" /> - {$t('export')} - </div> - </LinkButton> - <LinkButton onclick={() => handleRefresh()}> - <div class="flex place-items-center gap-2 text-sm"> - <Icon path={mdiRefresh} size="18" /> - {$t('refresh')} - </div> - </LinkButton> - </div> + <HStack gap={0}> + <Button + onclick={() => handleRepair()} + disabled={matches.length === 0 || repairing} + size="small" + variant="ghost" + color="secondary" + > + <Icon path={mdiWrench} /> + {$t('admin.repair_all')} + </Button> + <Button + onclick={() => handleCheckAll()} + disabled={extras.length === 0 || checking} + size="small" + variant="ghost" + color="secondary" + > + <Icon path={mdiCheckAll} /> + {$t('admin.check_all')} + </Button> + <Button + onclick={() => handleDownload()} + disabled={extras.length + orphans.length === 0} + size="small" + variant="ghost" + color="secondary" + > + <Icon path={mdiDownload} /> + {$t('export')} + </Button> + <Button onclick={() => handleRefresh()} size="small" variant="ghost" color="secondary"> + <Icon path={mdiRefresh} /> + {$t('refresh')} + </Button> + </HStack> {/snippet} <section id="setting-content" class="flex place-content-center sm:mx-4"> <section class="w-full pb-28 sm:w-5/6 md:w-[850px]"> diff --git a/web/src/routes/admin/system-settings/+page.svelte b/web/src/routes/admin/system-settings/+page.svelte index 6a71245051..c54aeba631 100644 --- a/web/src/routes/admin/system-settings/+page.svelte +++ b/web/src/routes/admin/system-settings/+page.svelte @@ -17,7 +17,7 @@ import ThemeSettings from '$lib/components/admin-page/settings/theme/theme-settings.svelte'; import TrashSettings from '$lib/components/admin-page/settings/trash-settings/trash-settings.svelte'; import UserSettings from '$lib/components/admin-page/settings/user-settings/user-settings.svelte'; - import LinkButton from '$lib/components/elements/buttons/link-button.svelte'; + import { Button, HStack } from '@immich/ui'; import Icon from '$lib/components/elements/icon.svelte'; import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte'; import SettingAccordionState from '$lib/components/shared-components/settings/setting-accordion-state.svelte'; @@ -255,31 +255,30 @@ <UserPageLayout title={data.meta.title} admin> {#snippet buttons()} - <div class="flex justify-end gap-2"> + <HStack gap={1}> <div class="hidden lg:block"> <SearchBar placeholder={$t('search_settings')} bind:name={searchQuery} showLoadingSpinner={false} /> </div> - <LinkButton onclick={() => copyToClipboard(JSON.stringify(config, jsonReplacer, 2))}> - <div class="flex place-items-center gap-2 text-sm"> - <Icon path={mdiContentCopy} size="18" /> - {$t('copy_to_clipboard')} - </div> - </LinkButton> - <LinkButton onclick={() => downloadConfig()}> - <div class="flex place-items-center gap-2 text-sm"> - <Icon path={mdiDownload} size="18" /> - {$t('export_as_json')} - </div> - </LinkButton> + <Button + onclick={() => copyToClipboard(JSON.stringify(config, jsonReplacer, 2))} + size="small" + variant="ghost" + color="secondary" + > + <Icon path={mdiContentCopy} /> + {$t('copy_to_clipboard')} + </Button> + <Button onclick={() => downloadConfig()} size="small" variant="ghost" color="secondary"> + <Icon path={mdiDownload} /> + {$t('export_as_json')} + </Button> {#if !$featureFlags.configFile} - <LinkButton onclick={() => inputElement?.click()}> - <div class="flex place-items-center gap-2 text-sm"> - <Icon path={mdiUpload} size="18" /> - {$t('import_from_json')} - </div> - </LinkButton> + <Button onclick={() => inputElement?.click()} size="small" variant="ghost" color="secondary"> + <Icon path={mdiUpload} /> + {$t('import_from_json')} + </Button> {/if} - </div> + </HStack> {/snippet} <AdminSettings bind:config bind:this={adminSettingElement}>