diff --git a/e2e/src/web/specs/auth.e2e-spec.ts b/e2e/src/web/specs/auth.e2e-spec.ts index b616a365cf..e89f17a4e9 100644 --- a/e2e/src/web/specs/auth.e2e-spec.ts +++ b/e2e/src/web/specs/auth.e2e-spec.ts @@ -33,6 +33,7 @@ test.describe('Registration', () => { // onboarding await expect(page).toHaveURL('/auth/onboarding'); await page.getByRole('button', { name: 'Theme' }).click(); + await page.getByRole('button', { name: 'Privacy' }).click(); await page.getByRole('button', { name: 'Storage Template' }).click(); await page.getByRole('button', { name: 'Done' }).click(); diff --git a/web/src/lib/components/admin-page/settings/admin-settings.svelte b/web/src/lib/components/admin-page/settings/admin-settings.svelte index 55750a9737..21e70df950 100644 --- a/web/src/lib/components/admin-page/settings/admin-settings.svelte +++ b/web/src/lib/components/admin-page/settings/admin-settings.svelte @@ -8,7 +8,7 @@ import { handleError } from '$lib/utils/handle-error'; import { getConfig, getConfigDefaults, updateConfig, type SystemConfigDto } from '@immich/sdk'; import { loadConfig } from '$lib/stores/server-config.store'; - import { cloneDeep } from 'lodash-es'; + import { cloneDeep, isEqual } from 'lodash-es'; import { onMount } from 'svelte'; import type { SettingsResetOptions } from './admin-settings'; import { t } from 'svelte-i18n'; @@ -23,12 +23,16 @@ }; export const handleSave = async (update: Partial<SystemConfigDto>) => { + let systemConfigDto = { + ...savedConfig, + ...update, + }; + if (isEqual(systemConfigDto, savedConfig)) { + return; + } try { const newConfig = await updateConfig({ - systemConfigDto: { - ...savedConfig, - ...update, - }, + systemConfigDto, }); config = cloneDeep(newConfig); diff --git a/web/src/lib/components/admin-page/settings/map-settings/map-settings.svelte b/web/src/lib/components/admin-page/settings/map-settings/map-settings.svelte index 74cbe2d9a1..7c2c5c856a 100644 --- a/web/src/lib/components/admin-page/settings/map-settings/map-settings.svelte +++ b/web/src/lib/components/admin-page/settings/map-settings/map-settings.svelte @@ -26,7 +26,12 @@ <div class="flex flex-col gap-4"> <SettingAccordion key="map" title={$t('admin.map_settings')} subtitle={$t('admin.map_settings_description')}> <div class="ml-4 mt-4 flex flex-col gap-4"> - <SettingSwitch title={$t('admin.map_enable_description')} {disabled} bind:checked={config.map.enabled} /> + <SettingSwitch + title={$t('admin.map_enable_description')} + subtitle={$t('admin.map_implications')} + {disabled} + bind:checked={config.map.enabled} + /> <hr /> diff --git a/web/src/lib/components/admin-page/settings/new-version-check-settings/new-version-check-settings.svelte b/web/src/lib/components/admin-page/settings/new-version-check-settings/new-version-check-settings.svelte index 4ef4804c3f..76c238df82 100644 --- a/web/src/lib/components/admin-page/settings/new-version-check-settings/new-version-check-settings.svelte +++ b/web/src/lib/components/admin-page/settings/new-version-check-settings/new-version-check-settings.svelte @@ -21,6 +21,7 @@ <div class="ml-4 mt-4"> <SettingSwitch title={$t('admin.version_check_enabled_description')} + subtitle={$t('admin.version_check_implications')} bind:checked={config.newVersionCheck.enabled} {disabled} /> diff --git a/web/src/lib/components/admin-page/settings/storage-template/storage-template-settings.svelte b/web/src/lib/components/admin-page/settings/storage-template/storage-template-settings.svelte index 1d0cec3296..4ebf4ed118 100644 --- a/web/src/lib/components/admin-page/settings/storage-template/storage-template-settings.svelte +++ b/web/src/lib/components/admin-page/settings/storage-template/storage-template-settings.svelte @@ -29,6 +29,7 @@ export let minified = false; export let onReset: SettingsResetEvent; export let onSave: SettingsSaveEvent; + export let duration: number = 500; let templateOptions: SystemConfigTemplateStorageOptionDto; let selectedPreset = ''; @@ -87,7 +88,7 @@ </script> <section class="dark:text-immich-dark-fg mt-2"> - <div in:fade={{ duration: 500 }} class="mx-4 flex flex-col gap-4 py-4"> + <div in:fade={{ duration }} class="mx-4 flex flex-col gap-4 py-4"> <p class="text-sm dark:text-immich-dark-fg"> <FormatMessage key="admin.storage_template_more_details" let:tag let:message> {#if tag === 'template-link'} diff --git a/web/src/lib/components/onboarding-page/onboarding-card.svelte b/web/src/lib/components/onboarding-page/onboarding-card.svelte index 8b2da48bb9..9b2378ccd8 100644 --- a/web/src/lib/components/onboarding-page/onboarding-card.svelte +++ b/web/src/lib/components/onboarding-page/onboarding-card.svelte @@ -1,11 +1,27 @@ <script lang="ts"> + import Icon from '$lib/components/elements/icon.svelte'; import { fade } from 'svelte/transition'; + + export let title: string | undefined = undefined; + export let icon: string | undefined = undefined; </script> <div id="onboarding-card" - class="flex w-full max-w-4xl flex-col gap-4 rounded-3xl border-2 border-gray-500 px-8 py-14 dark:border-immich-dark-gray dark:bg-immich-dark-gray text-black dark:text-immich-dark-fg bg-gray-50" + class="flex w-full max-w-4xl flex-col gap-4 rounded-3xl border-2 border-gray-500 px-8 py-8 dark:border-immich-dark-gray dark:bg-immich-dark-gray text-black dark:text-immich-dark-fg bg-gray-50" in:fade={{ duration: 250 }} > + {#if title || icon} + <div class="flex gap-2 items-center justify-center w-fit"> + {#if icon} + <Icon path={icon} size="30" class="text-immich-primary dark:text-immich-dark-primary" /> + {/if} + {#if title} + <p class="text-xl text-immich-primary dark:text-immich-dark-primary"> + {title.toUpperCase()} + </p> + {/if} + </div> + {/if} <slot /> </div> diff --git a/web/src/lib/components/onboarding-page/onboarding-hello.svelte b/web/src/lib/components/onboarding-page/onboarding-hello.svelte index c2d318ccda..466e1d29f7 100644 --- a/web/src/lib/components/onboarding-page/onboarding-hello.svelte +++ b/web/src/lib/components/onboarding-page/onboarding-hello.svelte @@ -3,14 +3,11 @@ import Button from '$lib/components/elements/buttons/button.svelte'; import { mdiArrowRight } from '@mdi/js'; import Icon from '$lib/components/elements/icon.svelte'; - import { createEventDispatcher } from 'svelte'; - import ImmichLogo from '../shared-components/immich-logo.svelte'; + import ImmichLogo from '$lib/components/shared-components/immich-logo.svelte'; import { user } from '$lib/stores/user.store'; import { t } from 'svelte-i18n'; - const dispatch = createEventDispatcher<{ - done: void; - }>(); + export let onDone: () => void; </script> <OnboardingCard> @@ -21,7 +18,7 @@ <p class="text-3xl pb-6 font-light">{$t('onboarding_welcome_description')}</p> <div class="w-full flex place-content-end"> - <Button class="flex gap-2 place-content-center" on:click={() => dispatch('done')}> + <Button class="flex gap-2 place-content-center" on:click={() => onDone()}> <p>{$t('theme')}</p> <Icon path={mdiArrowRight} size="18" /> </Button> diff --git a/web/src/lib/components/onboarding-page/onboarding-privacy.svelte b/web/src/lib/components/onboarding-page/onboarding-privacy.svelte new file mode 100644 index 0000000000..da36f741f1 --- /dev/null +++ b/web/src/lib/components/onboarding-page/onboarding-privacy.svelte @@ -0,0 +1,63 @@ +<script lang="ts"> + import { user } from '$lib/stores/user.store'; + import { getConfig, type SystemConfigDto } from '@immich/sdk'; + import { mdiArrowLeft, mdiArrowRight, mdiIncognito } from '@mdi/js'; + import { onMount } from 'svelte'; + import AdminSettings from '$lib/components/admin-page/settings/admin-settings.svelte'; + import Button from '$lib/components/elements/buttons/button.svelte'; + import Icon from '$lib/components/elements/icon.svelte'; + import OnboardingCard from './onboarding-card.svelte'; + import { t } from 'svelte-i18n'; + import SettingSwitch from '$lib/components/shared-components/settings/setting-switch.svelte'; + + export let onDone: () => void; + export let onPrevious: () => void; + + let config: SystemConfigDto | null = null; + + onMount(async () => { + config = await getConfig(); + }); +</script> + +<OnboardingCard title={$t('privacy')} icon={mdiIncognito}> + <p> + {$t('onboarding_privacy_description')} + </p> + + {#if config && $user} + <AdminSettings bind:config let:handleSave> + <SettingSwitch + title={$t('admin.map_settings')} + subtitle={$t('admin.map_implications')} + bind:checked={config.map.enabled} + /> + <SettingSwitch + title={$t('admin.version_check_settings')} + subtitle={$t('admin.version_check_implications')} + bind:checked={config.newVersionCheck.enabled} + /> + <div class="flex pt-4"> + <div class="w-full flex place-content-start"> + <Button class="flex gap-2 place-content-center" on:click={() => onPrevious()}> + <Icon path={mdiArrowLeft} size="18" /> + <p>{$t('theme')}</p> + </Button> + </div> + <div class="flex w-full place-content-end"> + <Button + on:click={() => { + handleSave({ map: config?.map, newVersionCheck: config?.newVersionCheck }); + onDone(); + }} + > + <span class="flex place-content-center place-items-center gap-2"> + {$t('admin.storage_template_settings')} + <Icon path={mdiArrowRight} size="18" /> + </span> + </Button> + </div> + </div> + </AdminSettings> + {/if} +</OnboardingCard> diff --git a/web/src/lib/components/onboarding-page/onboarding-storage-template.svelte b/web/src/lib/components/onboarding-page/onboarding-storage-template.svelte index 096417d72a..69809dd39d 100644 --- a/web/src/lib/components/onboarding-page/onboarding-storage-template.svelte +++ b/web/src/lib/components/onboarding-page/onboarding-storage-template.svelte @@ -2,20 +2,18 @@ import { featureFlags } from '$lib/stores/server-config.store'; import { user } from '$lib/stores/user.store'; import { getConfig, type SystemConfigDto } from '@immich/sdk'; - import { mdiArrowLeft, mdiCheck } from '@mdi/js'; - import { createEventDispatcher, onMount } from 'svelte'; - import AdminSettings from '../admin-page/settings/admin-settings.svelte'; - import StorageTemplateSettings from '../admin-page/settings/storage-template/storage-template-settings.svelte'; - import Button from '../elements/buttons/button.svelte'; - import Icon from '../elements/icon.svelte'; + import { mdiArrowLeft, mdiCheck, mdiHarddisk } from '@mdi/js'; + import { onMount } from 'svelte'; + import AdminSettings from '$lib/components/admin-page/settings/admin-settings.svelte'; + import StorageTemplateSettings from '$lib/components/admin-page/settings/storage-template/storage-template-settings.svelte'; + import Button from '$lib/components/elements/buttons/button.svelte'; + import Icon from '$lib/components/elements/icon.svelte'; import OnboardingCard from './onboarding-card.svelte'; import { t } from 'svelte-i18n'; import FormatMessage from '$lib/components/i18n/format-message.svelte'; - const dispatch = createEventDispatcher<{ - done: void; - previous: void; - }>(); + export let onDone: () => void; + export let onPrevious: () => void; let config: SystemConfigDto | null = null; @@ -24,11 +22,7 @@ }); </script> -<OnboardingCard> - <p class="text-xl text-immich-primary dark:text-immich-dark-primary"> - {$t('admin.storage_template_settings').toUpperCase()} - </p> - +<OnboardingCard title={$t('admin.storage_template_settings')} icon={mdiHarddisk}> <p> <FormatMessage key="admin.storage_template_onboarding_description" let:message> <a class="underline" href="https://immich.app/docs/administration/storage-template">{message}</a> @@ -45,10 +39,11 @@ {savedConfig} onSave={(config) => handleSave(config)} onReset={(options) => handleReset(options)} + duration={0} > <div class="flex pt-4"> <div class="w-full flex place-content-start"> - <Button class="flex gap-2 place-content-center" on:click={() => dispatch('previous')}> + <Button class="flex gap-2 place-content-center" on:click={() => onPrevious()}> <Icon path={mdiArrowLeft} size="18" /> <p>{$t('theme')}</p> </Button> @@ -57,7 +52,7 @@ <Button on:click={() => { handleSave({ storageTemplate: config?.storageTemplate }); - dispatch('done'); + onDone(); }} > <span class="flex place-content-center place-items-center gap-2"> diff --git a/web/src/lib/components/onboarding-page/onboarding-theme.svelte b/web/src/lib/components/onboarding-page/onboarding-theme.svelte index ff15b8b64a..975dbd1ec3 100644 --- a/web/src/lib/components/onboarding-page/onboarding-theme.svelte +++ b/web/src/lib/components/onboarding-page/onboarding-theme.svelte @@ -1,23 +1,17 @@ <script lang="ts"> - import { mdiArrowRight } from '@mdi/js'; - import Button from '../elements/buttons/button.svelte'; - import Icon from '../elements/icon.svelte'; + import { mdiArrowRight, mdiThemeLightDark } from '@mdi/js'; + import Button from '$lib/components/elements/buttons/button.svelte'; + import Icon from '$lib/components/elements/icon.svelte'; import OnboardingCard from './onboarding-card.svelte'; - import { createEventDispatcher } from 'svelte'; import { colorTheme } from '$lib/stores/preferences.store'; import { moonPath, moonViewBox, sunPath, sunViewBox } from '$lib/assets/svg-paths'; import { Theme } from '$lib/constants'; import { t } from 'svelte-i18n'; - const dispatch = createEventDispatcher<{ - done: void; - previous: void; - }>(); + export let onDone: () => void; </script> -<OnboardingCard> - <p class="text-xl text-immich-primary dark:text-immich-dark-primary">{$t('color_theme').toUpperCase()}</p> - +<OnboardingCard icon={mdiThemeLightDark} title={$t('color_theme')}> <div> <p class="pb-6 font-light">{$t('onboarding_theme_description')}</p> </div> @@ -51,8 +45,8 @@ <div class="flex"> <div class="w-full flex place-content-end"> - <Button class="flex gap-2 place-content-center" on:click={() => dispatch('done')}> - <p>{$t('admin.storage_template_settings')}</p> + <Button class="flex gap-2 place-content-center" on:click={() => onDone()}> + <p>{$t('privacy')}</p> <Icon path={mdiArrowRight} size="18" /> </Button> </div> diff --git a/web/src/lib/i18n/en.json b/web/src/lib/i18n/en.json index 6796ae3a71..eaf5ffc1a4 100644 --- a/web/src/lib/i18n/en.json +++ b/web/src/lib/i18n/en.json @@ -127,12 +127,13 @@ "map_enable_description": "Enable map features", "map_gps_settings": "Map & GPS Settings", "map_gps_settings_description": "Manage Map & GPS (Reverse Geocoding) Settings", + "map_implications": "The map feature relies on an external tile service (tiles.immich.cloud)", "map_light_style": "Light style", "map_manage_reverse_geocoding_settings": "Manage <link>Reverse Geocoding</link> settings", "map_reverse_geocoding": "Reverse Geocoding", "map_reverse_geocoding_enable_description": "Enable reverse geocoding", "map_reverse_geocoding_settings": "Reverse Geocoding Settings", - "map_settings": "Map Settings", + "map_settings": "Map", "map_settings_description": "Manage map settings", "map_style_description": "URL to a style.json map theme", "metadata_extraction_job": "Extract metadata", @@ -317,7 +318,8 @@ "user_settings": "User Settings", "user_settings_description": "Manage user settings", "user_successfully_removed": "User {email} has been successfully removed.", - "version_check_enabled_description": "Enable periodic requests to GitHub to check for new releases", + "version_check_enabled_description": "Enable version check", + "version_check_implications": "The version check feature relies on periodic communication with github.com", "version_check_settings": "Version Check", "version_check_settings_description": "Enable/disable the new version notification", "video_conversion_job": "Transcode videos", @@ -850,6 +852,7 @@ "ok": "Ok", "oldest_first": "Oldest first", "onboarding": "Onboarding", + "onboarding_privacy_description": "The following (optional) features rely on external services, and can by disabled at any time in the administration settings.", "onboarding_theme_description": "Choose a color theme for your instance. You can change this later in your settings.", "onboarding_welcome_description": "Let's get your instance set up with some common settings.", "onboarding_welcome_user": "Welcome, {user}", @@ -920,6 +923,7 @@ "previous_memory": "Previous memory", "previous_or_next_photo": "Previous or next photo", "primary": "Primary", + "privacy": "Privacy", "profile_image_of_user": "Profile image of {user}", "profile_picture_set": "Profile picture set.", "public_album": "Public album", diff --git a/web/src/routes/auth/onboarding/+page.svelte b/web/src/routes/auth/onboarding/+page.svelte index 4647ad8bde..0fe2c68c84 100644 --- a/web/src/routes/auth/onboarding/+page.svelte +++ b/web/src/routes/auth/onboarding/+page.svelte @@ -2,6 +2,7 @@ import { goto } from '$app/navigation'; import { page } from '$app/stores'; import OnboardingHello from '$lib/components/onboarding-page/onboarding-hello.svelte'; + import OnboardingPrivacy from '$lib/components/onboarding-page/onboarding-privacy.svelte'; import OnboadingStorageTemplate from '$lib/components/onboarding-page/onboarding-storage-template.svelte'; import OnboardingTheme from '$lib/components/onboarding-page/onboarding-theme.svelte'; import { AppRoute, QueryParameter } from '$lib/constants'; @@ -11,12 +12,17 @@ interface OnboardingStep { name: string; - component: typeof OnboardingHello | typeof OnboardingTheme | typeof OnboadingStorageTemplate; + component: + | typeof OnboardingHello + | typeof OnboardingTheme + | typeof OnboadingStorageTemplate + | typeof OnboardingPrivacy; } const onboardingSteps: OnboardingStep[] = [ { name: 'hello', component: OnboardingHello }, { name: 'theme', component: OnboardingTheme }, + { name: 'privacy', component: OnboardingPrivacy }, { name: 'storage', component: OnboadingStorageTemplate }, ]; @@ -55,8 +61,8 @@ <div class="w-full min-w-screen py-8 flex h-full place-content-center place-items-center"> <svelte:component this={onboardingSteps[index].component} - on:done={handleDoneClicked} - on:previous={handlePrevious} + onDone={handleDoneClicked} + onPrevious={handlePrevious} /> </div> </div>