From f70ee3f3502899e68a797ab2104b7a56911ed81a Mon Sep 17 00:00:00 2001 From: Jason Rasmussen Date: Tue, 14 Jan 2025 09:14:28 -0500 Subject: [PATCH 01/30] refactor: auth pages (#15328) --- web/eslint.config.mjs | 1 + web/src/app.html | 6 + .../forms/admin-registration-form.svelte | 78 -------- .../forms/change-password-form.svelte | 64 ------- .../lib/components/forms/login-form.svelte | 177 ----------------- .../AuthPageLayout.svelte} | 2 +- web/src/routes/+layout.svelte | 26 +-- .../routes/auth/change-password/+page.svelte | 47 ++++- web/src/routes/auth/login/+page.svelte | 179 +++++++++++++++++- web/src/routes/auth/register/+page.svelte | 76 +++++++- 10 files changed, 294 insertions(+), 362 deletions(-) delete mode 100644 web/src/lib/components/forms/admin-registration-form.svelte delete mode 100644 web/src/lib/components/forms/change-password-form.svelte delete mode 100644 web/src/lib/components/forms/login-form.svelte rename web/src/lib/components/{shared-components/fullscreen-container.svelte => layouts/AuthPageLayout.svelte} (93%) diff --git a/web/eslint.config.mjs b/web/eslint.config.mjs index f3cf9d7f10..fc5e35ce6d 100644 --- a/web/eslint.config.mjs +++ b/web/eslint.config.mjs @@ -81,6 +81,7 @@ export default [ 'unicorn/prevent-abbreviations': 'off', 'unicorn/no-nested-ternary': 'off', 'unicorn/consistent-function-scoping': 'off', + 'unicorn/filename-case': 'off', 'unicorn/prefer-top-level-await': 'off', 'unicorn/import-style': 'off', 'svelte/button-has-type': 'error', diff --git a/web/src/app.html b/web/src/app.html index 6fd02dc9f8..c0ac3cfe6c 100644 --- a/web/src/app.html +++ b/web/src/app.html @@ -101,6 +101,12 @@ + +
diff --git a/web/src/lib/components/forms/admin-registration-form.svelte b/web/src/lib/components/forms/admin-registration-form.svelte deleted file mode 100644 index b4ecd56283..0000000000 --- a/web/src/lib/components/forms/admin-registration-form.svelte +++ /dev/null @@ -1,78 +0,0 @@ - - -
-
- - -
- -
- - -
- -
- - -
- -
- - -
- - {#if errorMessage} -

{errorMessage}

- {/if} - -
- -
- diff --git a/web/src/lib/components/forms/change-password-form.svelte b/web/src/lib/components/forms/change-password-form.svelte deleted file mode 100644 index 6f16781d9a..0000000000 --- a/web/src/lib/components/forms/change-password-form.svelte +++ /dev/null @@ -1,64 +0,0 @@ - - -
-
- - -
- -
- - -
- - {#if errorMessage} -

{errorMessage}

- {/if} - -
- -
-
diff --git a/web/src/lib/components/forms/login-form.svelte b/web/src/lib/components/forms/login-form.svelte deleted file mode 100644 index 6c1dcecba3..0000000000 --- a/web/src/lib/components/forms/login-form.svelte +++ /dev/null @@ -1,177 +0,0 @@ - - -{#if !oauthLoading && $featureFlags.passwordLogin} -
- {#if errorMessage} -

- {errorMessage} -

- {/if} - -
- - -
- -
- - -
- -
- -
-
-{/if} - -{#if $featureFlags.oauth} - {#if $featureFlags.passwordLogin} -
-
- - {$t('or')} - -
- {/if} -
- {#if oauthError} -

{oauthError}

- {/if} - -
-{/if} - -{#if !$featureFlags.passwordLogin && !$featureFlags.oauth} -

{$t('login_has_been_disabled')}

-{/if} diff --git a/web/src/lib/components/shared-components/fullscreen-container.svelte b/web/src/lib/components/layouts/AuthPageLayout.svelte similarity index 93% rename from web/src/lib/components/shared-components/fullscreen-container.svelte rename to web/src/lib/components/layouts/AuthPageLayout.svelte index 64ee41a225..c470f809a6 100644 --- a/web/src/lib/components/shared-components/fullscreen-container.svelte +++ b/web/src/lib/components/layouts/AuthPageLayout.svelte @@ -1,6 +1,6 @@ - + {#snippet message()}

{$t('hi_user', { values: { name: $user.name, email: $user.email } })} @@ -31,5 +44,23 @@

{/snippet} - -
+
+
+ + +
+ +
+ + +
+ + {#if errorMessage} +

{errorMessage}

+ {/if} + +
+ +
+
+ diff --git a/web/src/routes/auth/login/+page.svelte b/web/src/routes/auth/login/+page.svelte index 0ab506f5e3..63346a6abf 100644 --- a/web/src/routes/auth/login/+page.svelte +++ b/web/src/routes/auth/login/+page.svelte @@ -1,9 +1,17 @@ {#if $featureFlags.loaded} - + {#snippet message()}

@@ -22,10 +111,82 @@

{/snippet} - await goto(AppRoute.PHOTOS, { invalidateAll: true })} - onFirstLogin={async () => await goto(AppRoute.AUTH_CHANGE_PASSWORD)} - onOnboarding={async () => await goto(AppRoute.AUTH_ONBOARDING)} - /> -
+ {#if !oauthLoading && $featureFlags.passwordLogin} +
+ {#if errorMessage} +

+ {errorMessage} +

+ {/if} + +
+ + +
+ +
+ + +
+ +
+ +
+
+ {/if} + + {#if $featureFlags.oauth} + {#if $featureFlags.passwordLogin} +
+
+ + {$t('or')} + +
+ {/if} +
+ {#if oauthError} +

{oauthError}

+ {/if} + +
+ {/if} + + {#if !$featureFlags.passwordLogin && !$featureFlags.oauth} +

{$t('login_has_been_disabled')}

+ {/if} + {/if} diff --git a/web/src/routes/auth/register/+page.svelte b/web/src/routes/auth/register/+page.svelte index 2e55ba7435..43e28d5964 100644 --- a/web/src/routes/auth/register/+page.svelte +++ b/web/src/routes/auth/register/+page.svelte @@ -1,22 +1,86 @@ - + {#snippet message()}

{$t('admin.registration_description')}

{/snippet} - -
+
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ + {#if errorMessage} +

{errorMessage}

+ {/if} + +
+ +
+
+ From 4279cd6e1e98685aa55e49758e8adc3c97c0ecf2 Mon Sep 17 00:00:00 2001 From: Mattia Natali Date: Tue, 14 Jan 2025 15:24:58 +0100 Subject: [PATCH 02/30] feat(web): Slideshow is enabled everywhere. It no longer needs assetStore. (#15077) Slideshow no longer needs assetStore. It is enabled everywhere Co-authored-by: Alex --- .../asset-viewer/asset-viewer.svelte | 52 ++++++++----------- .../components/photos-page/asset-grid.svelte | 14 ++++- .../gallery-viewer/gallery-viewer.svelte | 51 +++++++++++++++--- .../duplicates-compare-control.svelte | 39 +++++++++++--- web/src/lib/stores/asset-viewing.store.ts | 3 +- .../[[assetId=id]]/+page.svelte | 15 ++++++ 6 files changed, 128 insertions(+), 46 deletions(-) diff --git a/web/src/lib/components/asset-viewer/asset-viewer.svelte b/web/src/lib/components/asset-viewer/asset-viewer.svelte index 7a2f97bb65..ea5d6e9275 100644 --- a/web/src/lib/components/asset-viewer/asset-viewer.svelte +++ b/web/src/lib/components/asset-viewer/asset-viewer.svelte @@ -8,7 +8,6 @@ import { updateNumberOfComments } from '$lib/stores/activity.store'; import { closeEditorCofirm } from '$lib/stores/asset-editor.store'; import { assetViewingStore } from '$lib/stores/asset-viewing.store'; - import type { AssetStore } from '$lib/stores/assets.store'; import { isShowDetail } from '$lib/stores/preferences.store'; import { SlideshowNavigation, SlideshowState, slideshowStore } from '$lib/stores/slideshow.store'; import { user } from '$lib/stores/user.store'; @@ -49,8 +48,9 @@ import VideoViewer from './video-wrapper-viewer.svelte'; import ImagePanoramaViewer from './image-panorama-viewer.svelte'; + type HasAsset = boolean; + interface Props { - assetStore?: AssetStore | null; asset: AssetResponseDto; preloadAssets?: AssetResponseDto[]; showNavigation?: boolean; @@ -61,13 +61,13 @@ onAction?: OnAction | undefined; reactions?: ActivityResponseDto[]; onClose: (dto: { asset: AssetResponseDto }) => void; - onNext: () => void; - onPrevious: () => void; + onNext: () => Promise; + onPrevious: () => Promise; + onRandom: () => Promise; copyImage?: () => Promise; } let { - assetStore = null, asset = $bindable(), preloadAssets = $bindable([]), showNavigation = true, @@ -80,6 +80,7 @@ onClose, onNext, onPrevious, + onRandom, copyImage = $bindable(), }: Props = $props(); @@ -271,22 +272,6 @@ }); }; - const navigateAssetRandom = async () => { - if (!assetStore) { - return; - } - - const asset = await assetStore.getRandomAsset(); - if (!asset) { - return; - } - - slideshowHistory.queue(asset); - - setAsset(asset); - $restartSlideshowProgress = true; - }; - const navigateAsset = async (order?: 'previous' | 'next', e?: Event) => { if (!order) { if ($slideshowState === SlideshowState.PlaySlideshow) { @@ -296,23 +281,30 @@ } } + e?.stopPropagation(); + + let hasNext = false; + if ($slideshowState === SlideshowState.PlaySlideshow && $slideshowNavigation === SlideshowNavigation.Shuffle) { - return (order === 'previous' ? slideshowHistory.previous() : slideshowHistory.next()) || navigateAssetRandom(); + hasNext = order === 'previous' ? slideshowHistory.previous() : slideshowHistory.next(); + if (!hasNext) { + const asset = await onRandom(); + if (asset) { + slideshowHistory.queue(asset); + hasNext = true; + } + } + } else { + hasNext = order === 'previous' ? await onPrevious() : await onNext(); } - if ($slideshowState === SlideshowState.PlaySlideshow && assetStore) { - const hasNext = - order === 'previous' ? await assetStore.getPreviousAsset(asset) : await assetStore.getNextAsset(asset); + if ($slideshowState === SlideshowState.PlaySlideshow) { if (hasNext) { $restartSlideshowProgress = true; } else { await handleStopSlideshow(); } } - - e?.stopPropagation(); - // eslint-disable-next-line @typescript-eslint/no-unused-expressions - order === 'previous' ? onPrevious() : onNext(); }; // const showEditorHandler = () => { @@ -435,7 +427,7 @@ {person} {stack} showDetailButton={enableDetailPanel} - showSlideshow={!!assetStore} + showSlideshow={true} onZoomImage={zoomToggle} onCopyImage={copyImage} onAction={handleAction} diff --git a/web/src/lib/components/photos-page/asset-grid.svelte b/web/src/lib/components/photos-page/asset-grid.svelte index 55f935c8dd..fd98f7e6a3 100644 --- a/web/src/lib/components/photos-page/asset-grid.svelte +++ b/web/src/lib/components/photos-page/asset-grid.svelte @@ -527,6 +527,18 @@ return !!nextAsset; }; + const handleRandom = async () => { + const randomAsset = await $assetStore.getRandomAsset(); + + if (randomAsset) { + const preloadAsset = await $assetStore.getNextAsset(randomAsset); + assetViewingStore.setAsset(randomAsset, preloadAsset ? [preloadAsset] : []); + await navigate({ targetRoute: 'current', assetId: randomAsset.id }); + } + + return randomAsset; + }; + const handleClose = async ({ asset }: { asset: AssetResponseDto }) => { assetViewingStore.showAssetViewer(false); showSkeleton = true; @@ -911,7 +923,6 @@ {#await import('../asset-viewer/asset-viewer.svelte') then { default: AssetViewer }} {/await} diff --git a/web/src/lib/components/shared-components/gallery-viewer/gallery-viewer.svelte b/web/src/lib/components/shared-components/gallery-viewer/gallery-viewer.svelte index 65c6c20e7b..4c3c35aeca 100644 --- a/web/src/lib/components/shared-components/gallery-viewer/gallery-viewer.svelte +++ b/web/src/lib/components/shared-components/gallery-viewer/gallery-viewer.svelte @@ -34,6 +34,7 @@ isShowDeleteConfirmation?: boolean; onPrevious?: (() => Promise) | undefined; onNext?: (() => Promise) | undefined; + onRandom?: (() => Promise) | undefined; } let { @@ -47,6 +48,7 @@ isShowDeleteConfirmation = $bindable(false), onPrevious = undefined, onNext = undefined, + onRandom = undefined, }: Props = $props(); let { isViewing: isViewerOpen, asset: viewingAsset, setAsset } = assetViewingStore; @@ -202,35 +204,71 @@ })(), ); - const handleNext = async () => { + const handleNext = async (): Promise => { try { let asset: AssetResponseDto | undefined; if (onNext) { asset = await onNext(); } else { - currentViewAssetIndex = Math.min(currentViewAssetIndex + 1, assets.length - 1); - asset = assets[currentViewAssetIndex]; + currentViewAssetIndex = currentViewAssetIndex + 1; + asset = currentViewAssetIndex < assets.length ? assets[currentViewAssetIndex] : undefined; + } + + if (!asset) { + return false; } await navigateToAsset(asset); + return true; } catch (error) { handleError(error, $t('errors.cannot_navigate_next_asset')); + return false; } }; - const handlePrevious = async () => { + const handleRandom = async (): Promise => { + try { + let asset: AssetResponseDto | undefined; + if (onRandom) { + asset = await onRandom(); + } else { + if (assets.length > 0) { + const randomIndex = Math.floor(Math.random() * assets.length); + asset = assets[randomIndex]; + } + } + + if (!asset) { + return null; + } + + await navigateToAsset(asset); + return asset; + } catch (error) { + handleError(error, $t('errors.cannot_navigate_next_asset')); + return null; + } + }; + + const handlePrevious = async (): Promise => { try { let asset: AssetResponseDto | undefined; if (onPrevious) { asset = await onPrevious(); } else { - currentViewAssetIndex = Math.max(currentViewAssetIndex - 1, 0); - asset = assets[currentViewAssetIndex]; + currentViewAssetIndex = currentViewAssetIndex - 1; + asset = currentViewAssetIndex >= 0 ? assets[currentViewAssetIndex] : undefined; + } + + if (!asset) { + return false; } await navigateToAsset(asset); + return true; } catch (error) { handleError(error, $t('errors.cannot_navigate_previous_asset')); + return false; } }; @@ -372,6 +410,7 @@ onAction={handleAction} onPrevious={handlePrevious} onNext={handleNext} + onRandom={handleRandom} onClose={() => { assetViewingStore.showAssetViewer(false); handlePromiseError(navigate({ targetRoute: 'current', assetId: null })); diff --git a/web/src/lib/components/utilities-page/duplicates/duplicates-compare-control.svelte b/web/src/lib/components/utilities-page/duplicates/duplicates-compare-control.svelte index 11a5c67fcf..e6b9954349 100644 --- a/web/src/lib/components/utilities-page/duplicates/duplicates-compare-control.svelte +++ b/web/src/lib/components/utilities-page/duplicates/duplicates-compare-control.svelte @@ -42,6 +42,34 @@ assetViewingStore.showAssetViewer(false); }); + const onNext = () => { + const index = getAssetIndex($viewingAsset.id) + 1; + if (index >= assets.length) { + return Promise.resolve(false); + } + setAsset(assets[index]); + return Promise.resolve(true); + }; + + const onPrevious = () => { + const index = getAssetIndex($viewingAsset.id) - 1; + if (index < 0) { + return Promise.resolve(false); + } + setAsset(assets[index]); + return Promise.resolve(true); + }; + + const onRandom = () => { + if (assets.length <= 0) { + return Promise.resolve(null); + } + const index = Math.floor(Math.random() * assets.length); + const asset = assets[index]; + setAsset(asset); + return Promise.resolve(asset); + }; + const onSelectAsset = (asset: AssetResponseDto) => { if (selectedAssetIds.has(asset.id)) { selectedAssetIds.delete(asset.id); @@ -153,14 +181,9 @@ 1} - onNext={() => { - const index = getAssetIndex($viewingAsset.id) + 1; - setAsset(assets[index % assets.length]); - }} - onPrevious={() => { - const index = getAssetIndex($viewingAsset.id) - 1 + assets.length; - setAsset(assets[index % assets.length]); - }} + {onNext} + {onPrevious} + {onRandom} onClose={() => { assetViewingStore.showAssetViewer(false); handlePromiseError(navigate({ targetRoute: 'current', assetId: null })); diff --git a/web/src/lib/stores/asset-viewing.store.ts b/web/src/lib/stores/asset-viewing.store.ts index 2e6e44511d..689556b522 100644 --- a/web/src/lib/stores/asset-viewing.store.ts +++ b/web/src/lib/stores/asset-viewing.store.ts @@ -15,9 +15,10 @@ function createAssetViewingStore() { viewState.set(true); }; - const setAssetId = async (id: string) => { + const setAssetId = async (id: string): Promise => { const asset = await getAssetInfo({ id, key: getKey() }); setAsset(asset); + return asset; }; const showAssetViewer = (show: boolean) => { diff --git a/web/src/routes/(user)/map/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/map/[[photos=photos]]/[[assetId=id]]/+page.svelte index 613ae4d66b..2239a21cd5 100644 --- a/web/src/routes/(user)/map/[[photos=photos]]/[[assetId=id]]/+page.svelte +++ b/web/src/routes/(user)/map/[[photos=photos]]/[[assetId=id]]/+page.svelte @@ -107,14 +107,28 @@ if (viewingAssetCursor < viewingAssets.length - 1) { await setAssetId(viewingAssets[++viewingAssetCursor]); await navigate({ targetRoute: 'current', assetId: $viewingAsset.id }); + return true; } + return false; } async function navigatePrevious() { if (viewingAssetCursor > 0) { await setAssetId(viewingAssets[--viewingAssetCursor]); await navigate({ targetRoute: 'current', assetId: $viewingAsset.id }); + return true; } + return false; + } + + async function navigateRandom() { + if (viewingAssets.length <= 0) { + return null; + } + const index = Math.floor(Math.random() * viewingAssets.length); + const asset = await setAssetId(viewingAssets[index]); + await navigate({ targetRoute: 'current', assetId: $viewingAsset.id }); + return asset; } @@ -132,6 +146,7 @@ showNavigation={viewingAssets.length > 1} onNext={navigateNext} onPrevious={navigatePrevious} + onRandom={navigateRandom} onClose={() => { assetViewingStore.showAssetViewer(false); handlePromiseError(navigate({ targetRoute: 'current', assetId: null })); From 19e2504583595d4c5e6ce847a89e4dacdf762ad2 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 14 Jan 2025 10:19:01 -0500 Subject: [PATCH 03/30] fix(deps): update machine-learning (#15336) --- machine-learning/Dockerfile | 4 ++-- machine-learning/poetry.lock | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/machine-learning/Dockerfile b/machine-learning/Dockerfile index 705e4827ff..925e027ff6 100644 --- a/machine-learning/Dockerfile +++ b/machine-learning/Dockerfile @@ -1,6 +1,6 @@ ARG DEVICE=cpu -FROM python:3.11-bookworm@sha256:b337e1fd27dbacda505219f713789bf82766694095876769ea10c2d34b4f470b AS builder-cpu +FROM python:3.11-bookworm@sha256:f997d3f71b7dcff3f937703c02861437f2b41a94e1ddbd1b5fa357ee99f5cce4 AS builder-cpu FROM builder-cpu AS builder-openvino @@ -34,7 +34,7 @@ RUN python3 -m venv /opt/venv COPY poetry.lock pyproject.toml ./ RUN poetry install --sync --no-interaction --no-ansi --no-root --with ${DEVICE} --without dev -FROM python:3.11-slim-bookworm@sha256:873952659a04188d2a62d5f7e30fd673d2559432a847a8ad5fcaf9cbd085e9ed AS prod-cpu +FROM python:3.11-slim-bookworm@sha256:de909939264a469f834cf89e4dd2ed6aca2ef0844f47ba0bf7f2b4661f5111a5 AS prod-cpu FROM prod-cpu AS prod-openvino diff --git a/machine-learning/poetry.lock b/machine-learning/poetry.lock index 33a4354c30..ebfd075c7c 100644 --- a/machine-learning/poetry.lock +++ b/machine-learning/poetry.lock @@ -2498,13 +2498,13 @@ files = [ [[package]] name = "pydantic" -version = "2.10.4" +version = "2.10.5" description = "Data validation using Python type hints" optional = false python-versions = ">=3.8" files = [ - {file = "pydantic-2.10.4-py3-none-any.whl", hash = "sha256:597e135ea68be3a37552fb524bc7d0d66dcf93d395acd93a00682f1efcb8ee3d"}, - {file = "pydantic-2.10.4.tar.gz", hash = "sha256:82f12e9723da6de4fe2ba888b5971157b3be7ad914267dea8f05f82b28254f06"}, + {file = "pydantic-2.10.5-py3-none-any.whl", hash = "sha256:4dd4e322dbe55472cb7ca7e73f4b63574eecccf2835ffa2af9021ce113c83c53"}, + {file = "pydantic-2.10.5.tar.gz", hash = "sha256:278b38dbbaec562011d659ee05f63346951b3a248a6f3642e1bc68894ea2b4ff"}, ] [package.dependencies] From 3e11b90851819374342d493d6c575f0c14381463 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 14 Jan 2025 13:20:12 -0500 Subject: [PATCH 04/30] chore(deps): update node.js to v22.13.0 (#15337) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- cli/Dockerfile | 2 +- server/Dockerfile | 2 +- web/Dockerfile | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cli/Dockerfile b/cli/Dockerfile index 31dd8576e2..da2f17cc39 100644 --- a/cli/Dockerfile +++ b/cli/Dockerfile @@ -1,4 +1,4 @@ -FROM node:22.12.0-alpine3.20@sha256:96cc8323e25c8cc6ddcb8b965e135cfd57846e8003ec0d7bcec16c5fd5f6d39f AS core +FROM node:22.13.0-alpine3.20@sha256:db8dcb90326a0116375414e9a7c068a6b87a4422b7da37b5c6cd026f7c7835d3 AS core WORKDIR /usr/src/open-api/typescript-sdk COPY open-api/typescript-sdk/package*.json open-api/typescript-sdk/tsconfig*.json ./ diff --git a/server/Dockerfile b/server/Dockerfile index 85c3ffae1f..7a54578770 100644 --- a/server/Dockerfile +++ b/server/Dockerfile @@ -25,7 +25,7 @@ COPY --from=dev /usr/src/app/node_modules/@img ./node_modules/@img COPY --from=dev /usr/src/app/node_modules/exiftool-vendored.pl ./node_modules/exiftool-vendored.pl # web build -FROM node:22.12.0-alpine3.20@sha256:96cc8323e25c8cc6ddcb8b965e135cfd57846e8003ec0d7bcec16c5fd5f6d39f AS web +FROM node:22.13.0-alpine3.20@sha256:db8dcb90326a0116375414e9a7c068a6b87a4422b7da37b5c6cd026f7c7835d3 AS web WORKDIR /usr/src/open-api/typescript-sdk COPY open-api/typescript-sdk/package*.json open-api/typescript-sdk/tsconfig*.json ./ diff --git a/web/Dockerfile b/web/Dockerfile index dfef1d8348..0b51000426 100644 --- a/web/Dockerfile +++ b/web/Dockerfile @@ -1,4 +1,4 @@ -FROM node:22.12.0-alpine3.20@sha256:96cc8323e25c8cc6ddcb8b965e135cfd57846e8003ec0d7bcec16c5fd5f6d39f +FROM node:22.13.0-alpine3.20@sha256:db8dcb90326a0116375414e9a7c068a6b87a4422b7da37b5c6cd026f7c7835d3 RUN apk add --no-cache tini USER node From 073fccb517afb13335e9428e2f9082d2c9137b48 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 14 Jan 2025 18:33:27 +0000 Subject: [PATCH 05/30] chore(deps): update python:3.11-slim-bookworm docker digest to 6ed5bff (#15346) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- machine-learning/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/machine-learning/Dockerfile b/machine-learning/Dockerfile index 925e027ff6..7b0f97c1cf 100644 --- a/machine-learning/Dockerfile +++ b/machine-learning/Dockerfile @@ -34,7 +34,7 @@ RUN python3 -m venv /opt/venv COPY poetry.lock pyproject.toml ./ RUN poetry install --sync --no-interaction --no-ansi --no-root --with ${DEVICE} --without dev -FROM python:3.11-slim-bookworm@sha256:de909939264a469f834cf89e4dd2ed6aca2ef0844f47ba0bf7f2b4661f5111a5 AS prod-cpu +FROM python:3.11-slim-bookworm@sha256:6ed5bff4d7d377e2a27d9285553b8c21cfccc4f00881de1b24c9bc8d90016e82 AS prod-cpu FROM prod-cpu AS prod-openvino From b9000d8770109bbd145040e13de07c3f5e16927f Mon Sep 17 00:00:00 2001 From: Jason Rasmussen Date: Tue, 14 Jan 2025 14:53:33 -0500 Subject: [PATCH 06/30] feat(web): immich-ui components (#14263) * feat: add immich-ui to auth pages * fix: welcome icon * styling * fix: mobile padding --------- Co-authored-by: Alex Tran --- docker/docker-compose.dev.yml | 1 + docs/docs/developer/setup.md | 11 + web/package-lock.json | 317 +++++++++++------- web/package.json | 1 + web/src/app.css | 24 ++ .../components/layouts/AuthPageLayout.svelte | 39 +-- web/src/routes/+layout.svelte | 10 + web/src/routes/+page.svelte | 11 +- .../routes/auth/change-password/+page.svelte | 50 ++- web/src/routes/auth/login/+page.svelte | 80 ++--- web/src/routes/auth/register/+page.svelte | 47 ++- web/tailwind.config.js | 15 +- web/vite.config.js | 1 + 13 files changed, 345 insertions(+), 262 deletions(-) diff --git a/docker/docker-compose.dev.yml b/docker/docker-compose.dev.yml index fc1e2602da..4dc41e143e 100644 --- a/docker/docker-compose.dev.yml +++ b/docker/docker-compose.dev.yml @@ -71,6 +71,7 @@ services: - ../web:/usr/src/app - ../i18n:/usr/src/i18n - ../open-api/:/usr/src/open-api/ + # - ../../ui:/usr/ui - /usr/src/app/node_modules ulimits: nofile: diff --git a/docs/docs/developer/setup.md b/docs/docs/developer/setup.md index 9dbaf157b5..f341c3e9cb 100644 --- a/docs/docs/developer/setup.md +++ b/docs/docs/developer/setup.md @@ -63,6 +63,17 @@ If you only want to do web development connected to an existing, remote backend, IMMICH_SERVER_URL=https://demo.immich.app/ npm run dev ``` +#### `@immich/ui` + +To see local changes to `@immich/ui` in Immich, do the following: + +1. Install `@immich/ui` as a sibling to `immich/`, for example `/home/user/immich` and `/home/user/ui` +1. Build the `@immich/ui` project via `npm run build` +1. Uncomment the corresponding volume in web service of the `docker/docker-compose.dev.yaml` file (`../../ui:/usr/ui`) +1. Uncomment the corresponding alias in the `web/vite.config.js` file (`'@immich/ui': path.resolve(\_\_dirname, '../../ui')`) +1. Start up the stack via `make dev` +1. After making changes in `@immich/ui`, rebuild it (`npm run build`) + ### Mobile app The mobile app `(/mobile)` will required Flutter toolchain 3.13.x to be installed on your system. diff --git a/web/package-lock.json b/web/package-lock.json index 426df0acd7..14d0928731 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -11,6 +11,7 @@ "dependencies": { "@formatjs/icu-messageformat-parser": "^2.9.8", "@immich/sdk": "file:../open-api/typescript-sdk", + "@immich/ui": "^0.11.0", "@mapbox/mapbox-gl-rtl-text": "0.2.3", "@mdi/js": "^7.4.47", "@photo-sphere-viewer/core": "^5.11.5", @@ -104,7 +105,6 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", - "dev": true, "engines": { "node": ">=10" }, @@ -793,6 +793,31 @@ "npm": ">=9.0.0" } }, + "node_modules/@floating-ui/core": { + "version": "1.6.9", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.9.tgz", + "integrity": "sha512-uMXCuQ3BItDUbAMhIXw7UPXRfAlOAvZzdK9BWpE60MCn+Svt3aLn9jsPTi/WNGlRUu2uI0v5S7JiIUsbsvh3fw==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.9" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.6.13", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.13.tgz", + "integrity": "sha512-umqzocjDgNRGTuO7Q8CU32dkHkECqI8ZdMZ5Swb6QAM0t5rnlrN3lGo1hdpscRd3WS8T6DKYK4ephgIH9iRh3w==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.6.0", + "@floating-ui/utils": "^0.2.9" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.9.tgz", + "integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==", + "license": "MIT" + }, "node_modules/@formatjs/ecma402-abstract": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.3.2.tgz", @@ -1279,11 +1304,34 @@ "resolved": "../open-api/typescript-sdk", "link": true }, + "node_modules/@immich/ui": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@immich/ui/-/ui-0.11.0.tgz", + "integrity": "sha512-zRQFHCVt6BstNkGuVt27rLUAurOpZ0djfaZYDeqHuc8H97XXXk+hsbXzvADlVa9xAPHetUM3JuusPseJ+Hr23g==", + "license": "GNU Affero General Public License version 3", + "dependencies": { + "@mdi/js": "^7.4.47", + "bits-ui": "^1.0.0-next.46", + "tailwind-merge": "^2.5.4", + "tailwind-variants": "^0.3.0" + }, + "peerDependencies": { + "svelte": "^5.0.0" + } + }, + "node_modules/@internationalized/date": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.6.0.tgz", + "integrity": "sha512-+z6ti+CcJnRlLHok/emGEsWQhe7kfSmEW+/6qCzvKY67YPh7YOBfvc7+/+NXq+zJlbArg30tYpqLjNgcAYv2YQ==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dev": true, "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", @@ -1300,7 +1348,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "dev": true, "engines": { "node": ">=12" }, @@ -1312,7 +1359,6 @@ "version": "6.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true, "engines": { "node": ">=12" }, @@ -1323,14 +1369,12 @@ "node_modules/@isaacs/cliui/node_modules/emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" }, "node_modules/@isaacs/cliui/node_modules/string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", @@ -1347,7 +1391,6 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, "dependencies": { "ansi-regex": "^6.0.1" }, @@ -1362,7 +1405,6 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", @@ -1542,7 +1584,6 @@ "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" @@ -1555,7 +1596,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, "engines": { "node": ">= 8" } @@ -1564,7 +1604,6 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" @@ -1605,7 +1644,6 @@ "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "dev": true, "optional": true, "engines": { "node": ">=14" @@ -2017,6 +2055,15 @@ "vite": "^5.0.0" } }, + "node_modules/@swc/helpers": { + "version": "0.5.15", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", + "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, "node_modules/@testing-library/dom": { "version": "10.2.0", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.2.0.tgz", @@ -2826,7 +2873,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "engines": { "node": ">=8" } @@ -2846,14 +2892,12 @@ "node_modules/any-promise": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", - "dev": true + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==" }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" @@ -2865,8 +2909,7 @@ "node_modules/arg": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", - "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", - "dev": true + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==" }, "node_modules/argparse": { "version": "2.0.1", @@ -2967,18 +3010,40 @@ "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "node_modules/binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true, "engines": { "node": ">=8" } }, + "node_modules/bits-ui": { + "version": "1.0.0-next.77", + "resolved": "https://registry.npmjs.org/bits-ui/-/bits-ui-1.0.0-next.77.tgz", + "integrity": "sha512-IV0AyVEvsRkXv4s/fl4iea5E9W2b9EBf98s9mRMKMc1xHxM9MmtM2r6MZMqftHQ/c+gHTIt3A9EKuTlh7uay8w==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.6.4", + "@floating-ui/dom": "^1.6.7", + "@internationalized/date": "^3.5.6", + "esm-env": "^1.1.2", + "runed": "^0.22.0", + "svelte-toolbelt": "^0.7.0" + }, + "engines": { + "node": ">=18", + "pnpm": ">=8.7.0" + }, + "funding": { + "url": "https://github.com/sponsors/huntabyte" + }, + "peerDependencies": { + "svelte": "^5.11.0" + } + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -2993,7 +3058,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, "license": "MIT", "dependencies": { "fill-range": "^7.1.1" @@ -3093,7 +3157,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", - "dev": true, "engines": { "node": ">= 6" } @@ -3164,7 +3227,6 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, "license": "MIT", "dependencies": { "anymatch": "~3.1.2", @@ -3189,7 +3251,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, "dependencies": { "is-glob": "^4.0.1" }, @@ -3350,7 +3411,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", - "dev": true, "engines": { "node": ">= 6" } @@ -3388,7 +3448,6 @@ "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -3416,7 +3475,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "dev": true, "bin": { "cssesc": "bin/cssesc" }, @@ -3580,14 +3638,12 @@ "node_modules/didyoumean": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", - "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", - "dev": true + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==" }, "node_modules/dlv": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", - "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", - "dev": true + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==" }, "node_modules/dom-accessibility-api": { "version": "0.5.16", @@ -3621,8 +3677,7 @@ "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" }, "node_modules/electron-to-chromium": { "version": "1.5.74", @@ -3634,8 +3689,7 @@ "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "node_modules/engine.io-client": { "version": "6.5.4", @@ -4146,9 +4200,9 @@ } }, "node_modules/esm-env": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.1.tgz", - "integrity": "sha512-U9JedYYjCnadUlXk7e1Kr+aENQhtUaoaV9+gZm1T8LC/YBAPJx3NSPIAurFOC0U5vrdSevnUJS2/wUVxGwPhng==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz", + "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==", "license": "MIT" }, "node_modules/esniff": { @@ -4306,7 +4360,6 @@ "version": "3.3.2", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", - "dev": true, "license": "MIT", "dependencies": { "@nodelib/fs.stat": "^2.0.2", @@ -4323,7 +4376,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, "dependencies": { "is-glob": "^4.0.1" }, @@ -4347,7 +4399,6 @@ "version": "1.15.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", - "dev": true, "dependencies": { "reusify": "^1.0.4" } @@ -4374,7 +4425,6 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" @@ -4424,7 +4474,6 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.2.1.tgz", "integrity": "sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==", - "dev": true, "dependencies": { "cross-spawn": "^7.0.0", "signal-exit": "^4.0.1" @@ -4469,7 +4518,6 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, "hasInstallScript": true, "optional": true, "os": [ @@ -4482,8 +4530,7 @@ "node_modules/function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, "node_modules/geojson-vt": { "version": "3.2.1", @@ -4527,7 +4574,6 @@ "version": "10.4.5", "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", - "dev": true, "license": "ISC", "dependencies": { "foreground-child": "^3.1.0", @@ -4548,7 +4594,6 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, "dependencies": { "is-glob": "^4.0.3" }, @@ -4560,7 +4605,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" @@ -4570,7 +4614,6 @@ "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" @@ -4666,7 +4709,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, "dependencies": { "function-bind": "^1.1.1" }, @@ -4852,6 +4894,12 @@ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" }, + "node_modules/inline-style-parser": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.4.tgz", + "integrity": "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==", + "license": "MIT" + }, "node_modules/internmap": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", @@ -4882,7 +4930,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, "dependencies": { "binary-extensions": "^2.0.0" }, @@ -4909,7 +4956,6 @@ "version": "2.13.0", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz", "integrity": "sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==", - "dev": true, "dependencies": { "has": "^1.0.3" }, @@ -4944,7 +4990,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -4953,7 +4998,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, "engines": { "node": ">=8" } @@ -4962,7 +5006,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, "dependencies": { "is-extglob": "^2.1.1" }, @@ -4974,7 +5017,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.12.0" @@ -5113,7 +5155,6 @@ "version": "3.4.3", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "dev": true, "dependencies": { "@isaacs/cliui": "^8.0.2" }, @@ -5128,7 +5169,6 @@ "version": "1.21.6", "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz", "integrity": "sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==", - "dev": true, "license": "MIT", "bin": { "jiti": "bin/jiti.js" @@ -5303,8 +5343,7 @@ "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" }, "node_modules/locate-character": { "version": "3.0.0", @@ -5546,7 +5585,6 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, "engines": { "node": ">= 8" } @@ -5555,7 +5593,6 @@ "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, "license": "MIT", "dependencies": { "braces": "^3.0.3", @@ -5623,7 +5660,6 @@ "version": "7.1.2", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "dev": true, "engines": { "node": ">=16 || 14 >=14.17" } @@ -5660,7 +5696,6 @@ "version": "2.7.0", "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", - "dev": true, "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", @@ -5671,7 +5706,6 @@ "version": "3.3.8", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", - "dev": true, "funding": [ { "type": "github", @@ -5734,7 +5768,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -5760,7 +5793,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -5769,7 +5801,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", - "dev": true, "engines": { "node": ">= 6" } @@ -5850,8 +5881,7 @@ "node_modules/package-json-from-dist": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz", - "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==", - "dev": true + "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==" }, "node_modules/parent-module": { "version": "1.0.1", @@ -5910,7 +5940,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, "engines": { "node": ">=8" } @@ -5918,14 +5947,12 @@ "node_modules/path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, "node_modules/path-scurry": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "dev": true, "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" @@ -5940,8 +5967,7 @@ "node_modules/path-scurry/node_modules/lru-cache": { "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==" }, "node_modules/pathe": { "version": "1.1.2", @@ -5976,14 +6002,12 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, "license": "ISC" }, "node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, "engines": { "node": ">=8.6" }, @@ -5995,7 +6019,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -6004,7 +6027,6 @@ "version": "4.0.6", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", - "dev": true, "engines": { "node": ">= 6" } @@ -6031,7 +6053,6 @@ "version": "8.5.0", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.0.tgz", "integrity": "sha512-27VKOqrYfPncKA2NrFOVhP5MGAfHKLYn/Q0mz9cNQyRAKYi3VNHwYU2qKKqPCqgBmeeJ0uAFB56NumXZ5ZReXg==", - "dev": true, "funding": [ { "type": "opencollective", @@ -6060,7 +6081,6 @@ "version": "15.1.0", "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", - "dev": true, "dependencies": { "postcss-value-parser": "^4.0.0", "read-cache": "^1.0.0", @@ -6077,7 +6097,6 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", - "dev": true, "dependencies": { "camelcase-css": "^2.0.1" }, @@ -6125,7 +6144,6 @@ "version": "6.2.0", "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", - "dev": true, "funding": [ { "type": "opencollective", @@ -6194,7 +6212,6 @@ "version": "6.1.2", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", - "dev": true, "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -6207,8 +6224,7 @@ "node_modules/postcss-value-parser": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" }, "node_modules/potpack": { "version": "2.0.0", @@ -6341,7 +6357,6 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, "funding": [ { "type": "github", @@ -6372,7 +6387,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", - "dev": true, "dependencies": { "pify": "^2.3.0" } @@ -6483,7 +6497,6 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, "dependencies": { "picomatch": "^2.2.1" }, @@ -6561,7 +6574,6 @@ "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", - "dev": true, "license": "MIT", "dependencies": { "is-core-module": "^2.13.0", @@ -6587,7 +6599,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, "engines": { "iojs": ">=1.0.0", "node": ">=0.10.0" @@ -6697,7 +6708,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, "funding": [ { "type": "github", @@ -6716,6 +6726,21 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/runed": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/runed/-/runed-0.22.0.tgz", + "integrity": "sha512-ZWVXWhOr0P5xdNgtviz6D1ivLUDWKLCbeC5SUEJ3zBkqLReVqWHenFxMNFeFaiC5bfxhFxyxzyzB+98uYFtwdA==", + "funding": [ + "https://github.com/sponsors/huntabyte", + "https://github.com/sponsors/tglide" + ], + "dependencies": { + "esm-env": "^1.0.0" + }, + "peerDependencies": { + "svelte": "^5.7.0" + } + }, "node_modules/rw": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", @@ -6843,7 +6868,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, "dependencies": { "shebang-regex": "^3.0.0" }, @@ -6855,7 +6879,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, "engines": { "node": ">=8" } @@ -6870,7 +6893,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, "engines": { "node": ">=14" }, @@ -6979,7 +7001,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -7078,7 +7099,6 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -7093,7 +7113,6 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -7107,7 +7126,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -7120,7 +7138,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -7152,11 +7169,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/style-to-object": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.8.tgz", + "integrity": "sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g==", + "license": "MIT", + "dependencies": { + "inline-style-parser": "0.2.4" + } + }, "node_modules/sucrase": { "version": "3.35.0", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", @@ -7199,7 +7224,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -7841,6 +7865,41 @@ "svelte": "^3.0.0 || ^4.0.0 || ^5.0.0-next.1" } }, + "node_modules/svelte-toolbelt": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/svelte-toolbelt/-/svelte-toolbelt-0.7.0.tgz", + "integrity": "sha512-i/Tv4NwAWWqJnK5H0F8y/ubDnogDYlwwyzKhrspTUFzrFuGnYshqd2g4/R43ds841wmaFiSW/HsdsdWhPOlrAA==", + "funding": [ + "https://github.com/sponsors/huntabyte" + ], + "dependencies": { + "clsx": "^2.1.1", + "runed": "^0.20.0", + "style-to-object": "^1.0.8" + }, + "engines": { + "node": ">=18", + "pnpm": ">=8.7.0" + }, + "peerDependencies": { + "svelte": "^5.0.0" + } + }, + "node_modules/svelte-toolbelt/node_modules/runed": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/runed/-/runed-0.20.0.tgz", + "integrity": "sha512-YqPxaUdWL5nUXuSF+/v8a+NkVN8TGyEGbQwTA25fLY35MR/2bvZ1c6sCbudoo1kT4CAJPh4kUkcgGVxW127WKw==", + "funding": [ + "https://github.com/sponsors/huntabyte", + "https://github.com/sponsors/tglide" + ], + "dependencies": { + "esm-env": "^1.0.0" + }, + "peerDependencies": { + "svelte": "^5.7.0" + } + }, "node_modules/svelte/node_modules/aria-query": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", @@ -7858,11 +7917,36 @@ "optional": true, "peer": true }, + "node_modules/tailwind-merge": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.6.0.tgz", + "integrity": "sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, + "node_modules/tailwind-variants": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/tailwind-variants/-/tailwind-variants-0.3.0.tgz", + "integrity": "sha512-ho2k5kn+LB1fT5XdNS3Clb96zieWxbStE9wNLK7D0AV64kdZMaYzAKo0fWl6fXLPY99ffF9oBJnIj5escEl/8A==", + "license": "MIT", + "dependencies": { + "tailwind-merge": "^2.5.4" + }, + "engines": { + "node": ">=16.x", + "pnpm": ">=7.x" + }, + "peerDependencies": { + "tailwindcss": "*" + } + }, "node_modules/tailwindcss": { "version": "3.4.17", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz", "integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==", - "dev": true, "license": "MIT", "dependencies": { "@alloc/quick-lru": "^5.2.0", @@ -7900,7 +7984,6 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", - "dev": true, "license": "MIT", "engines": { "node": ">=14" @@ -7913,7 +7996,6 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==", - "dev": true, "funding": [ { "type": "opencollective", @@ -7949,7 +8031,6 @@ "version": "2.6.1", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.1.tgz", "integrity": "sha512-7r0XPzioN/Q9kXBro/XPnA6kznR73DHq+GXh5ON7ZozRO6aMjbmiBuKste2wslTFkC5d1dw0GooOCepZXJ2SAg==", - "dev": true, "license": "ISC", "bin": { "yaml": "bin.mjs" @@ -8000,7 +8081,6 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", - "dev": true, "dependencies": { "any-promise": "^1.0.0" } @@ -8009,7 +8089,6 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", - "dev": true, "dependencies": { "thenify": ">= 3.1.0 < 4" }, @@ -8098,7 +8177,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, "license": "MIT", "dependencies": { "is-number": "^7.0.0" @@ -8163,8 +8241,7 @@ "node_modules/ts-interface-checker": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", - "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", - "dev": true + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==" }, "node_modules/tslib": { "version": "2.8.1", @@ -8308,8 +8385,7 @@ "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, "node_modules/validate-npm-package-license": { "version": "3.0.4", @@ -8581,7 +8657,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, "dependencies": { "isexe": "^2.0.0" }, @@ -8635,7 +8710,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -8652,7 +8726,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -8667,7 +8740,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "dependencies": { "color-name": "~1.1.4" }, @@ -8678,8 +8750,7 @@ "node_modules/wrap-ansi-cjs/node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "node_modules/wrap-ansi/node_modules/ansi-styles": { "version": "4.3.0", diff --git a/web/package.json b/web/package.json index b843f6c13a..fc936efdc4 100644 --- a/web/package.json +++ b/web/package.json @@ -67,6 +67,7 @@ "dependencies": { "@formatjs/icu-messageformat-parser": "^2.9.8", "@immich/sdk": "file:../open-api/typescript-sdk", + "@immich/ui": "^0.11.0", "@mapbox/mapbox-gl-rtl-text": "0.2.3", "@mdi/js": "^7.4.47", "@photo-sphere-viewer/core": "^5.11.5", diff --git a/web/src/app.css b/web/src/app.css index d1af865bca..00cefc5ce6 100644 --- a/web/src/app.css +++ b/web/src/app.css @@ -22,6 +22,30 @@ --immich-dark-success: 56 142 60; --immich-dark-warning: 245 124 0; } + + :root { + /* light */ + --immich-ui-primary: 66 80 175; + --immich-ui-dark: 0 0 0; + --immich-ui-light: 255 255 255; + --immich-ui-success: 34 197 94; + --immich-ui-danger: 180 0 0; + --immich-ui-warning: 255 170 0; + --immich-ui-info: 14 165 233; + --immich-ui-default-border: 209 213 219; + } + + .dark { + /* dark */ + --immich-ui-primary: 172 203 250; + --immich-ui-light: 0 0 0; + --immich-ui-dark: 229 231 235; + /* --immich-success: 56 142 60; */ + --immich-ui-danger: 239 68 68; + --immich-ui-warning: 255 170 0; + --immich-ui-info: 14 165 233; + --immich-ui-default-border: 55 65 81; + } } @font-face { diff --git a/web/src/lib/components/layouts/AuthPageLayout.svelte b/web/src/lib/components/layouts/AuthPageLayout.svelte index c470f809a6..3a61a1671c 100644 --- a/web/src/lib/components/layouts/AuthPageLayout.svelte +++ b/web/src/lib/components/layouts/AuthPageLayout.svelte @@ -1,36 +1,25 @@ -
-
-
- -

- {title} -

-
- - {#if showMessage} -
- {@render message?.()} -
- {/if} - - {@render children?.()} -
+
+ + + + + {title} + + + + {@render children?.()} + +
diff --git a/web/src/routes/+layout.svelte b/web/src/routes/+layout.svelte index fa1351ab20..2706ead46e 100644 --- a/web/src/routes/+layout.svelte +++ b/web/src/routes/+layout.svelte @@ -19,12 +19,22 @@ import { isAssetViewerRoute, isSharedLinkRoute } from '$lib/utils/navigation'; import { onDestroy, onMount, type Snippet } from 'svelte'; import { run } from 'svelte/legacy'; + import { setTranslations } from '@immich/ui'; import '../app.css'; + import { t } from 'svelte-i18n'; interface Props { children?: Snippet; } + $effect(() => { + setTranslations({ + close: $t('close'), + showPassword: $t('show_password'), + hidePassword: $t('hide_password'), + }); + }); + let { children }: Props = $props(); let showNavigationLoadingBar = $state(false); diff --git a/web/src/routes/+page.svelte b/web/src/routes/+page.svelte index 68a5deb0f9..b3ac52bd7c 100644 --- a/web/src/routes/+page.svelte +++ b/web/src/routes/+page.svelte @@ -1,17 +1,16 @@
-
+
- +
-

{$t('welcome_to_immich')}

-
diff --git a/web/src/routes/auth/change-password/+page.svelte b/web/src/routes/auth/change-password/+page.svelte index ea340ff600..6b91118475 100644 --- a/web/src/routes/auth/change-password/+page.svelte +++ b/web/src/routes/auth/change-password/+page.svelte @@ -1,11 +1,10 @@ - {#snippet message()} -

- {$t('hi_user', { values: { name: $user.name, email: $user.email } })} -
-
- {$t('change_password_description')} -

- {/snippet} +
+ + + {$t('hi_user', { values: { name: $user.name, email: $user.email } })} + {$t('change_password_description')} + + +
-
-
- - -
+ + + + + -
- - -
+ + + {#if errorMessage} + {errorMessage} + {/if} + - {#if errorMessage} -

{errorMessage}

- {/if} - -
- -
+
+ +
+
diff --git a/web/src/routes/auth/login/+page.svelte b/web/src/routes/auth/login/+page.svelte index 63346a6abf..f52face78e 100644 --- a/web/src/routes/auth/login/+page.svelte +++ b/web/src/routes/auth/login/+page.svelte @@ -1,17 +1,14 @@ {#if $featureFlags.loaded} - - {#snippet message()} -

+ + {#if $serverConfig.loginPageMessage} + {@html $serverConfig.loginPageMessage} -

- {/snippet} + + {/if} {#if !oauthLoading && $featureFlags.passwordLogin} -
+ {#if errorMessage} -

- {errorMessage} -

+ {/if} -
- - -
+ + + -
- - -
+ + + -
- -
+ {/if} {#if $featureFlags.oauth} {#if $featureFlags.passwordLogin} -
+

- {$t('or')} + {$t('or').toUpperCase()}
{/if} -
+
{#if oauthError} -

{oauthError}

+ {/if}
{/if} {#if !$featureFlags.passwordLogin && !$featureFlags.oauth} -

{$t('login_has_been_disabled')}

+ {/if} {/if} diff --git a/web/src/routes/auth/register/+page.svelte b/web/src/routes/auth/register/+page.svelte index 43e28d5964..50551358ea 100644 --- a/web/src/routes/auth/register/+page.svelte +++ b/web/src/routes/auth/register/+page.svelte @@ -1,12 +1,11 @@ - {#snippet message()} -

- {$t('admin.registration_description')} -

- {/snippet} +
+ + {$t('admin.registration_description')} + +
-
-
- - -
+ + + + -
- - -
+ + + -
- - -
+ + + -
- - -
+ + + {#if errorMessage} -

{errorMessage}

+ {/if}
- +
diff --git a/web/tailwind.config.js b/web/tailwind.config.js index eb1ea78fae..12bfd7c604 100644 --- a/web/tailwind.config.js +++ b/web/tailwind.config.js @@ -2,7 +2,7 @@ import plugin from 'tailwindcss/plugin'; /** @type {import('tailwindcss').Config} */ export default { - content: ['./src/**/*.{html,js,svelte,ts}'], + content: ['./src/**/*.{html,js,svelte,ts}', './node_modules/@immich/ui/dist/**/*.{svelte,js}'], darkMode: 'class', theme: { extend: { @@ -24,7 +24,20 @@ export default { 'immich-dark-error': 'rgb(var(--immich-dark-error) / )', 'immich-dark-success': 'rgb(var(--immich-dark-success) / )', 'immich-dark-warning': 'rgb(var(--immich-dark-warning) / )', + + primary: 'rgb(var(--immich-ui-primary) / )', + light: 'rgb(var(--immich-ui-light) / )', + dark: 'rgb(var(--immich-ui-dark) / )', + success: 'rgb(var(--immich-ui-success) / )', + danger: 'rgb(var(--immich-ui-danger) / )', + warning: 'rgb(var(--immich-ui-warning) / )', + info: 'rgb(var(--immich-ui-info) / )', + subtle: 'rgb(var(--immich-gray) / )', }, + borderColor: ({ theme }) => ({ + ...theme('colors'), + DEFAULT: 'rgb(var(--immich-ui-default-border) / )', + }), fontFamily: { 'immich-mono': ['Overpass Mono', 'monospace'], }, diff --git a/web/vite.config.js b/web/vite.config.js index 266312e137..5d134beab0 100644 --- a/web/vite.config.js +++ b/web/vite.config.js @@ -19,6 +19,7 @@ export default defineConfig({ 'xmlhttprequest-ssl': './node_modules/engine.io-client/lib/xmlhttprequest.js', // eslint-disable-next-line unicorn/prefer-module '@test-data': path.resolve(__dirname, './src/test-data'), + // '@immich/ui': path.resolve(__dirname, '../../ui'), }, }, server: { From 5d2e421800c47d02d97736ad44abe89c89f430f9 Mon Sep 17 00:00:00 2001 From: Jason Rasmussen Date: Tue, 14 Jan 2025 15:01:21 -0500 Subject: [PATCH 07/30] chore: add renovate config for immich-ui (#15349) --- renovate.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/renovate.json b/renovate.json index dd3ca1ad59..2634eaef4d 100644 --- a/renovate.json +++ b/renovate.json @@ -6,6 +6,10 @@ ], "minimumReleaseAge": "5 days", "packageRules": [ + { + "groupName": "@immich/ui", + "matchPackageNames": ["@immich/ui"] + }, { "matchFileNames": [ "cli/**", From c5476a99b1276c3d5395d744f47fbaf6953a892b Mon Sep 17 00:00:00 2001 From: Tempest <110401501+1-tempest@users.noreply.github.com> Date: Tue, 14 Jan 2025 16:06:01 -0600 Subject: [PATCH 08/30] feat: Add additional env variables for Machine Learning (#15326) * Add additional variables to preload part ML models * Add additional variables to preload part ML models * Add additional variables to preload part ML models * Add additional variables to preload part ML models * Add additional variables to preload part ML models * Add additional variables to preload part ML models * Add additional variables to preload part ML models * Add additional variables to preload part ML models * Add additional variables to preload part ML models * Update config.py * Add additional variables to preload part ML models * Add additional variables to preload part ML models * Apply formatting * minor update * formatting * root validator * minor update * minor update * minor update * change to support explicit models * minor update * minor change * minor change * minor change * minor update * add logs, resolve errors * minor change * add new enviornment variables * minor revisons * remove comments --- docs/docs/install/environment-variables.md | 38 ++++++++++++---------- machine-learning/app/config.py | 36 ++++++++++++++++++-- machine-learning/app/main.py | 23 +++++++++---- machine-learning/app/test_main.py | 24 +++++++++----- 4 files changed, 87 insertions(+), 34 deletions(-) diff --git a/docs/docs/install/environment-variables.md b/docs/docs/install/environment-variables.md index 1f34b5c6d0..b4cb905a0c 100644 --- a/docs/docs/install/environment-variables.md +++ b/docs/docs/install/environment-variables.md @@ -148,24 +148,26 @@ Redis (Sentinel) URL example JSON before encoding: ## Machine Learning -| Variable | Description | Default | Containers | -| :-------------------------------------------------------- | :-------------------------------------------------------------------------------------------------- | :-----------------------------: | :--------------- | -| `MACHINE_LEARNING_MODEL_TTL` | Inactivity time (s) before a model is unloaded (disabled if \<= 0) | `300` | machine learning | -| `MACHINE_LEARNING_MODEL_TTL_POLL_S` | Interval (s) between checks for the model TTL (disabled if \<= 0) | `10` | machine learning | -| `MACHINE_LEARNING_CACHE_FOLDER` | Directory where models are downloaded | `/cache` | machine learning | -| `MACHINE_LEARNING_REQUEST_THREADS`\*1 | Thread count of the request thread pool (disabled if \<= 0) | number of CPU cores | machine learning | -| `MACHINE_LEARNING_MODEL_INTER_OP_THREADS` | Number of parallel model operations | `1` | machine learning | -| `MACHINE_LEARNING_MODEL_INTRA_OP_THREADS` | Number of threads for each model operation | `2` | machine learning | -| `MACHINE_LEARNING_WORKERS`\*2 | Number of worker processes to spawn | `1` | machine learning | -| `MACHINE_LEARNING_HTTP_KEEPALIVE_TIMEOUT_S`\*3 | HTTP Keep-alive time in seconds | `2` | machine learning | -| `MACHINE_LEARNING_WORKER_TIMEOUT` | Maximum time (s) of unresponsiveness before a worker is killed | `120` (`300` if using OpenVINO) | machine learning | -| `MACHINE_LEARNING_PRELOAD__CLIP` | Name of a CLIP model to be preloaded and kept in cache | | machine learning | -| `MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION` | Name of a facial recognition model to be preloaded and kept in cache | | machine learning | -| `MACHINE_LEARNING_ANN` | Enable ARM-NN hardware acceleration if supported | `True` | machine learning | -| `MACHINE_LEARNING_ANN_FP16_TURBO` | Execute operations in FP16 precision: increasing speed, reducing precision (applies only to ARM-NN) | `False` | machine learning | -| `MACHINE_LEARNING_ANN_TUNING_LEVEL` | ARM-NN GPU tuning level (1: rapid, 2: normal, 3: exhaustive) | `2` | machine learning | -| `MACHINE_LEARNING_DEVICE_IDS`\*4 | Device IDs to use in multi-GPU environments | `0` | machine learning | -| `MACHINE_LEARNING_MAX_BATCH_SIZE__FACIAL_RECOGNITION` | Set the maximum number of faces that will be processed at once by the facial recognition model | None (`1` if using OpenVINO) | machine learning | +| Variable | Description | Default | Containers | +| :---------------------------------------------------------- | :-------------------------------------------------------------------------------------------------- | :-----------------------------: | :--------------- | +| `MACHINE_LEARNING_MODEL_TTL` | Inactivity time (s) before a model is unloaded (disabled if \<= 0) | `300` | machine learning | +| `MACHINE_LEARNING_MODEL_TTL_POLL_S` | Interval (s) between checks for the model TTL (disabled if \<= 0) | `10` | machine learning | +| `MACHINE_LEARNING_CACHE_FOLDER` | Directory where models are downloaded | `/cache` | machine learning | +| `MACHINE_LEARNING_REQUEST_THREADS`\*1 | Thread count of the request thread pool (disabled if \<= 0) | number of CPU cores | machine learning | +| `MACHINE_LEARNING_MODEL_INTER_OP_THREADS` | Number of parallel model operations | `1` | machine learning | +| `MACHINE_LEARNING_MODEL_INTRA_OP_THREADS` | Number of threads for each model operation | `2` | machine learning | +| `MACHINE_LEARNING_WORKERS`\*2 | Number of worker processes to spawn | `1` | machine learning | +| `MACHINE_LEARNING_HTTP_KEEPALIVE_TIMEOUT_S`\*3 | HTTP Keep-alive time in seconds | `2` | machine learning | +| `MACHINE_LEARNING_WORKER_TIMEOUT` | Maximum time (s) of unresponsiveness before a worker is killed | `120` (`300` if using OpenVINO) | machine learning | +| `MACHINE_LEARNING_PRELOAD__CLIP__TEXTUAL` | Name of the textual CLIP model to be preloaded and kept in cache | | machine learning | +| `MACHINE_LEARNING_PRELOAD__CLIP__VISUAL` | Name of the visual CLIP model to be preloaded and kept in cache | | machine learning | +| `MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION__RECOGNITION` | Name of the recognition portion of the facial recognition model to be preloaded and kept in cache | | machine learning | +| `MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION__DETECTION` | Name of the detection portion of the facial recognition model to be preloaded and kept in cache | | machine learning | +| `MACHINE_LEARNING_ANN` | Enable ARM-NN hardware acceleration if supported | `True` | machine learning | +| `MACHINE_LEARNING_ANN_FP16_TURBO` | Execute operations in FP16 precision: increasing speed, reducing precision (applies only to ARM-NN) | `False` | machine learning | +| `MACHINE_LEARNING_ANN_TUNING_LEVEL` | ARM-NN GPU tuning level (1: rapid, 2: normal, 3: exhaustive) | `2` | machine learning | +| `MACHINE_LEARNING_DEVICE_IDS`\*4 | Device IDs to use in multi-GPU environments | `0` | machine learning | +| `MACHINE_LEARNING_MAX_BATCH_SIZE__FACIAL_RECOGNITION` | Set the maximum number of faces that will be processed at once by the facial recognition model | None (`1` if using OpenVINO) | machine learning | \*1: It is recommended to begin with this parameter when changing the concurrency levels of the machine learning service and then tune the other ones. diff --git a/machine-learning/app/config.py b/machine-learning/app/config.py index 92799ac692..fa7dc61d47 100644 --- a/machine-learning/app/config.py +++ b/machine-learning/app/config.py @@ -14,9 +14,41 @@ from uvicorn import Server from uvicorn.workers import UvicornWorker +class ClipSettings(BaseModel): + textual: str | None = None + visual: str | None = None + + +class FacialRecognitionSettings(BaseModel): + recognition: str | None = None + detection: str | None = None + + class PreloadModelData(BaseModel): - clip: str | None = None - facial_recognition: str | None = None + clip: ClipSettings = ClipSettings() + facial_recognition: FacialRecognitionSettings = FacialRecognitionSettings() + + clip_model_fallback: str | None = os.getenv("MACHINE_LEARNING_PRELOAD__CLIP", None) + facial_recognition_model_fallback: str | None = os.getenv("MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION", None) + + def update_from_fallbacks(self) -> None: + if self.clip_model_fallback: + self.clip.textual = self.clip_model_fallback + self.clip.visual = self.clip_model_fallback + log.warning( + "Deprecated env variable: MACHINE_LEARNING_PRELOAD__CLIP. " + "Use MACHINE_LEARNING_PRELOAD__CLIP__TEXTUAL and " + "MACHINE_LEARNING_PRELOAD__CLIP__VISUAL instead." + ) + + if self.facial_recognition_model_fallback: + self.facial_recognition.recognition = self.facial_recognition_model_fallback + self.facial_recognition.detection = self.facial_recognition_model_fallback + log.warning( + "Deprecated environment variable: MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION. " + "Use MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION__RECOGNITION and " + "MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION__DETECTION instead." + ) class MaxBatchSize(BaseModel): diff --git a/machine-learning/app/main.py b/machine-learning/app/main.py index 684001b875..ee8881b6c6 100644 --- a/machine-learning/app/main.py +++ b/machine-learning/app/main.py @@ -76,18 +76,29 @@ async def lifespan(_: FastAPI) -> AsyncGenerator[None, None]: async def preload_models(preload: PreloadModelData) -> None: log.info(f"Preloading models: {preload}") - if preload.clip is not None: - model = await model_cache.get(preload.clip, ModelType.TEXTUAL, ModelTask.SEARCH) + + if preload.clip.textual is not None: + model = await model_cache.get(preload.clip.textual, ModelType.TEXTUAL, ModelTask.SEARCH) await load(model) - model = await model_cache.get(preload.clip, ModelType.VISUAL, ModelTask.SEARCH) + if preload.clip.visual is not None: + model = await model_cache.get(preload.clip.visual, ModelType.VISUAL, ModelTask.SEARCH) await load(model) - if preload.facial_recognition is not None: - model = await model_cache.get(preload.facial_recognition, ModelType.DETECTION, ModelTask.FACIAL_RECOGNITION) + if preload.facial_recognition.detection is not None: + model = await model_cache.get( + preload.facial_recognition.detection, + ModelType.DETECTION, + ModelTask.FACIAL_RECOGNITION, + ) await load(model) - model = await model_cache.get(preload.facial_recognition, ModelType.RECOGNITION, ModelTask.FACIAL_RECOGNITION) + if preload.facial_recognition.recognition is not None: + model = await model_cache.get( + preload.facial_recognition.recognition, + ModelType.RECOGNITION, + ModelTask.FACIAL_RECOGNITION, + ) await load(model) diff --git a/machine-learning/app/test_main.py b/machine-learning/app/test_main.py index e5cb63997c..5da3baded7 100644 --- a/machine-learning/app/test_main.py +++ b/machine-learning/app/test_main.py @@ -700,11 +700,13 @@ class TestCache: await model_cache.get("test_model_name", ModelType.TEXTUAL, ModelTask.SEARCH) async def test_preloads_clip_models(self, monkeypatch: MonkeyPatch, mock_get_model: mock.Mock) -> None: - os.environ["MACHINE_LEARNING_PRELOAD__CLIP"] = "ViT-B-32__openai" + os.environ["MACHINE_LEARNING_PRELOAD__CLIP__TEXTUAL"] = "ViT-B-32__openai" + os.environ["MACHINE_LEARNING_PRELOAD__CLIP__VISUAL"] = "ViT-B-32__openai" settings = Settings() assert settings.preload is not None - assert settings.preload.clip == "ViT-B-32__openai" + assert settings.preload.clip.textual == "ViT-B-32__openai" + assert settings.preload.clip.visual == "ViT-B-32__openai" model_cache = ModelCache() monkeypatch.setattr("app.main.model_cache", model_cache) @@ -721,11 +723,13 @@ class TestCache: async def test_preloads_facial_recognition_models( self, monkeypatch: MonkeyPatch, mock_get_model: mock.Mock ) -> None: - os.environ["MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION"] = "buffalo_s" + os.environ["MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION__DETECTION"] = "buffalo_s" + os.environ["MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION__RECOGNITION"] = "buffalo_s" settings = Settings() assert settings.preload is not None - assert settings.preload.facial_recognition == "buffalo_s" + assert settings.preload.facial_recognition.detection == "buffalo_s" + assert settings.preload.facial_recognition.recognition == "buffalo_s" model_cache = ModelCache() monkeypatch.setattr("app.main.model_cache", model_cache) @@ -740,13 +744,17 @@ class TestCache: ) async def test_preloads_all_models(self, monkeypatch: MonkeyPatch, mock_get_model: mock.Mock) -> None: - os.environ["MACHINE_LEARNING_PRELOAD__CLIP"] = "ViT-B-32__openai" - os.environ["MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION"] = "buffalo_s" + os.environ["MACHINE_LEARNING_PRELOAD__CLIP__TEXTUAL"] = "ViT-B-32__openai" + os.environ["MACHINE_LEARNING_PRELOAD__CLIP__VISUAL"] = "ViT-B-32__openai" + os.environ["MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION__RECOGNITION"] = "buffalo_s" + os.environ["MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION__DETECTION"] = "buffalo_s" settings = Settings() assert settings.preload is not None - assert settings.preload.clip == "ViT-B-32__openai" - assert settings.preload.facial_recognition == "buffalo_s" + assert settings.preload.clip.visual == "ViT-B-32__openai" + assert settings.preload.clip.textual == "ViT-B-32__openai" + assert settings.preload.facial_recognition.recognition == "buffalo_s" + assert settings.preload.facial_recognition.detection == "buffalo_s" model_cache = ModelCache() monkeypatch.setattr("app.main.model_cache", model_cache) From 2903ad815608f82d81107619f9cef67f65d2d885 Mon Sep 17 00:00:00 2001 From: Mert <101130780+mertalev@users.noreply.github.com> Date: Tue, 14 Jan 2025 19:27:16 -0500 Subject: [PATCH 09/30] refactor(server): migrate album-user repo to kysely (#15351) --- server/src/bin/sync-sql.ts | 1 + server/src/interfaces/album-user.interface.ts | 16 ++++--- server/src/queries/album.user.repository.sql | 25 +++++++++++ .../src/repositories/album-user.repository.ts | 42 ++++++++++++------- server/src/services/album.service.spec.ts | 24 +++++------ server/src/services/album.service.ts | 6 +-- 6 files changed, 78 insertions(+), 36 deletions(-) create mode 100644 server/src/queries/album.user.repository.sql diff --git a/server/src/bin/sync-sql.ts b/server/src/bin/sync-sql.ts index 2de4fb4127..22dae63750 100644 --- a/server/src/bin/sync-sql.ts +++ b/server/src/bin/sync-sql.ts @@ -86,6 +86,7 @@ class SqlGenerator { this.sqlLogger.logQuery(event.query.sql); } else if (event.level === 'error') { this.sqlLogger.logQueryError(event.error as Error, event.query.sql); + this.sqlLogger.logQuery(event.query.sql); } }, }), diff --git a/server/src/interfaces/album-user.interface.ts b/server/src/interfaces/album-user.interface.ts index d5742ad788..835e835589 100644 --- a/server/src/interfaces/album-user.interface.ts +++ b/server/src/interfaces/album-user.interface.ts @@ -1,14 +1,18 @@ -import { AlbumUserEntity } from 'src/entities/album-user.entity'; +import { Insertable, Selectable, Updateable } from 'kysely'; +import { AlbumsSharedUsersUsers } from 'src/db'; export const IAlbumUserRepository = 'IAlbumUserRepository'; export type AlbumPermissionId = { - albumId: string; - userId: string; + albumsId: string; + usersId: string; }; export interface IAlbumUserRepository { - create(albumUser: Partial): Promise; - update({ userId, albumId }: AlbumPermissionId, albumPermission: Partial): Promise; - delete({ userId, albumId }: AlbumPermissionId): Promise; + create(albumUser: Insertable): Promise>; + update( + id: AlbumPermissionId, + albumPermission: Updateable, + ): Promise>; + delete(id: AlbumPermissionId): Promise; } diff --git a/server/src/queries/album.user.repository.sql b/server/src/queries/album.user.repository.sql new file mode 100644 index 0000000000..d628e4980a --- /dev/null +++ b/server/src/queries/album.user.repository.sql @@ -0,0 +1,25 @@ +-- NOTE: This file is auto generated by ./sql-generator + +-- AlbumUserRepository.create +insert into + "albums_shared_users_users" ("usersId", "albumsId") +values + ($1, $2) +returning + * + +-- AlbumUserRepository.update +update "albums_shared_users_users" +set + "role" = $1 +where + "usersId" = $2 + and "albumsId" = $3 +returning + * + +-- AlbumUserRepository.delete +delete from "albums_shared_users_users" +where + "usersId" = $1 + and "albumsId" = $2 diff --git a/server/src/repositories/album-user.repository.ts b/server/src/repositories/album-user.repository.ts index 9328ea8cfc..5895721b63 100644 --- a/server/src/repositories/album-user.repository.ts +++ b/server/src/repositories/album-user.repository.ts @@ -1,26 +1,40 @@ import { Injectable } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; -import { AlbumUserEntity } from 'src/entities/album-user.entity'; +import { Insertable, Kysely, Selectable, Updateable } from 'kysely'; +import { InjectKysely } from 'nestjs-kysely'; +import { AlbumsSharedUsersUsers, DB } from 'src/db'; +import { DummyValue, GenerateSql } from 'src/decorators'; +import { AlbumUserRole } from 'src/enum'; import { AlbumPermissionId, IAlbumUserRepository } from 'src/interfaces/album-user.interface'; -import { Repository } from 'typeorm'; @Injectable() export class AlbumUserRepository implements IAlbumUserRepository { - constructor(@InjectRepository(AlbumUserEntity) private repository: Repository) {} + constructor(@InjectKysely() private db: Kysely) {} - async create(albumUser: Partial): Promise { - const { userId, albumId } = await this.repository.save(albumUser); - return this.repository.findOneOrFail({ where: { userId, albumId } }); + @GenerateSql({ params: [{ usersId: DummyValue.UUID, albumsId: DummyValue.UUID }] }) + create(albumUser: Insertable): Promise> { + return this.db.insertInto('albums_shared_users_users').values(albumUser).returningAll().executeTakeFirstOrThrow(); } - async update({ userId, albumId }: AlbumPermissionId, dto: Partial): Promise { - await this.repository.update({ userId, albumId }, dto); - return this.repository.findOneOrFail({ - where: { userId, albumId }, - }); + @GenerateSql({ params: [{ usersId: DummyValue.UUID, albumsId: DummyValue.UUID }, { role: AlbumUserRole.VIEWER }] }) + update( + { usersId, albumsId }: AlbumPermissionId, + dto: Updateable, + ): Promise> { + return this.db + .updateTable('albums_shared_users_users') + .set(dto) + .where('usersId', '=', usersId) + .where('albumsId', '=', albumsId) + .returningAll() + .executeTakeFirstOrThrow(); } - async delete({ userId, albumId }: AlbumPermissionId): Promise { - await this.repository.delete({ userId, albumId }); + @GenerateSql({ params: [{ usersId: DummyValue.UUID, albumsId: DummyValue.UUID }] }) + async delete({ usersId, albumsId }: AlbumPermissionId): Promise { + await this.db + .deleteFrom('albums_shared_users_users') + .where('usersId', '=', usersId) + .where('albumsId', '=', albumsId) + .execute(); } } diff --git a/server/src/services/album.service.spec.ts b/server/src/services/album.service.spec.ts index bb1aac8e6e..ca6b56e085 100644 --- a/server/src/services/album.service.spec.ts +++ b/server/src/services/album.service.spec.ts @@ -323,18 +323,16 @@ describe(AlbumService.name, () => { albumMock.update.mockResolvedValue(albumStub.sharedWithAdmin); userMock.get.mockResolvedValue(userStub.user2); albumUserMock.create.mockResolvedValue({ - userId: userStub.user2.id, - user: userStub.user2, - albumId: albumStub.sharedWithAdmin.id, - album: albumStub.sharedWithAdmin, + usersId: userStub.user2.id, + albumsId: albumStub.sharedWithAdmin.id, role: AlbumUserRole.EDITOR, }); await sut.addUsers(authStub.user1, albumStub.sharedWithAdmin.id, { albumUsers: [{ userId: authStub.user2.user.id }], }); expect(albumUserMock.create).toHaveBeenCalledWith({ - userId: authStub.user2.user.id, - albumId: albumStub.sharedWithAdmin.id, + usersId: authStub.user2.user.id, + albumsId: albumStub.sharedWithAdmin.id, }); expect(eventMock.emit).toHaveBeenCalledWith('album.invite', { id: albumStub.sharedWithAdmin.id, @@ -361,8 +359,8 @@ describe(AlbumService.name, () => { expect(albumUserMock.delete).toHaveBeenCalledTimes(1); expect(albumUserMock.delete).toHaveBeenCalledWith({ - albumId: albumStub.sharedWithUser.id, - userId: userStub.user1.id, + albumsId: albumStub.sharedWithUser.id, + usersId: userStub.user1.id, }); expect(albumMock.getById).toHaveBeenCalledWith(albumStub.sharedWithUser.id, { withAssets: false }); }); @@ -388,8 +386,8 @@ describe(AlbumService.name, () => { expect(albumUserMock.delete).toHaveBeenCalledTimes(1); expect(albumUserMock.delete).toHaveBeenCalledWith({ - albumId: albumStub.sharedWithUser.id, - userId: authStub.user1.user.id, + albumsId: albumStub.sharedWithUser.id, + usersId: authStub.user1.user.id, }); }); @@ -400,8 +398,8 @@ describe(AlbumService.name, () => { expect(albumUserMock.delete).toHaveBeenCalledTimes(1); expect(albumUserMock.delete).toHaveBeenCalledWith({ - albumId: albumStub.sharedWithUser.id, - userId: authStub.user1.user.id, + albumsId: albumStub.sharedWithUser.id, + usersId: authStub.user1.user.id, }); }); @@ -433,7 +431,7 @@ describe(AlbumService.name, () => { role: AlbumUserRole.EDITOR, }); expect(albumUserMock.update).toHaveBeenCalledWith( - { albumId: albumStub.sharedWithAdmin.id, userId: userStub.admin.id }, + { albumsId: albumStub.sharedWithAdmin.id, usersId: userStub.admin.id }, { role: AlbumUserRole.EDITOR }, ); }); diff --git a/server/src/services/album.service.ts b/server/src/services/album.service.ts index e57e6b168c..f5685f84eb 100644 --- a/server/src/services/album.service.ts +++ b/server/src/services/album.service.ts @@ -229,7 +229,7 @@ export class AlbumService extends BaseService { throw new BadRequestException('User not found'); } - await this.albumUserRepository.create({ userId, albumId: id, role }); + await this.albumUserRepository.create({ usersId: userId, albumsId: id, role }); await this.eventRepository.emit('album.invite', { id, userId }); } @@ -257,12 +257,12 @@ export class AlbumService extends BaseService { await this.requireAccess({ auth, permission: Permission.ALBUM_SHARE, ids: [id] }); } - await this.albumUserRepository.delete({ albumId: id, userId }); + await this.albumUserRepository.delete({ albumsId: id, usersId: userId }); } async updateUser(auth: AuthDto, id: string, userId: string, dto: Partial): Promise { await this.requireAccess({ auth, permission: Permission.ALBUM_SHARE, ids: [id] }); - await this.albumUserRepository.update({ albumId: id, userId }, { role: dto.role }); + await this.albumUserRepository.update({ albumsId: id, usersId: userId }, { role: dto.role }); } private async findOrFail(id: string, options: AlbumInfoOptions) { From 43b3181f45b3b3366400c546743674a0d59e04b5 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 14 Jan 2025 19:58:02 -0500 Subject: [PATCH 10/30] chore(deps): update base-image to v20250114 (major) (#15347) chore(deps): update base-image to v20250114 Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- server/Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/Dockerfile b/server/Dockerfile index 7a54578770..84902df3ca 100644 --- a/server/Dockerfile +++ b/server/Dockerfile @@ -1,5 +1,5 @@ # dev build -FROM ghcr.io/immich-app/base-server-dev:20250107@sha256:d00ab37e1c1ed87b799d6509fbc825a721ca0723c59c67955217826882017d38 AS dev +FROM ghcr.io/immich-app/base-server-dev:20250114@sha256:fce0404484bde5afc38a4399c6b25895eb079a666d269f199c93dfbfdd5b26b6 AS dev RUN apt-get install --no-install-recommends -yqq tini WORKDIR /usr/src/app @@ -42,7 +42,7 @@ RUN npm run build # prod build -FROM ghcr.io/immich-app/base-server-prod:20250107@sha256:78e92f113103271d43a3b050370b21b31c3c14792d3d23b18b542581a440c72b +FROM ghcr.io/immich-app/base-server-prod:20250114@sha256:94ec8a36cdf11691810c4aeccee1b49b00348e17f6b6781d87dd48a74e6c6787 WORKDIR /usr/src/app ENV NODE_ENV=production \ From 93e25452758c2480d0aa3e053ae205b00af4e0e5 Mon Sep 17 00:00:00 2001 From: Jason Rasmussen Date: Wed, 15 Jan 2025 11:34:11 -0500 Subject: [PATCH 11/30] refactor: migrate memory to kysely (#15314) --- server/src/interfaces/memory.interface.ts | 11 +- server/src/queries/memory.repository.sql | 83 +++++++++++-- server/src/repositories/memory.repository.ts | 121 ++++++++++--------- server/src/services/memory.service.spec.ts | 23 ++-- server/src/services/memory.service.ts | 28 ++--- 5 files changed, 177 insertions(+), 89 deletions(-) diff --git a/server/src/interfaces/memory.interface.ts b/server/src/interfaces/memory.interface.ts index 308943d97e..b1dbcbef85 100644 --- a/server/src/interfaces/memory.interface.ts +++ b/server/src/interfaces/memory.interface.ts @@ -1,4 +1,6 @@ -import { MemoryEntity } from 'src/entities/memory.entity'; +import { Insertable, Updateable } from 'kysely'; +import { Memories } from 'src/db'; +import { MemoryEntity, OnThisDayData } from 'src/entities/memory.entity'; import { IBulkAsset } from 'src/utils/asset.util'; export const IMemoryRepository = 'IMemoryRepository'; @@ -6,7 +8,10 @@ export const IMemoryRepository = 'IMemoryRepository'; export interface IMemoryRepository extends IBulkAsset { search(ownerId: string): Promise; get(id: string): Promise; - create(memory: Partial): Promise; - update(memory: Partial): Promise; + create( + memory: Omit, 'data'> & { data: OnThisDayData }, + assetIds: Set, + ): Promise; + update(id: string, memory: Updateable): Promise; delete(id: string): Promise; } diff --git a/server/src/queries/memory.repository.sql b/server/src/queries/memory.repository.sql index e3945ca028..396da3f56e 100644 --- a/server/src/queries/memory.repository.sql +++ b/server/src/queries/memory.repository.sql @@ -1,10 +1,79 @@ -- NOTE: This file is auto generated by ./sql-generator +-- MemoryRepository.search +select + * +from + "memories" +where + "ownerId" = $1 +order by + "memoryAt" desc + +-- MemoryRepository.get +select + "memories".*, + ( + select + coalesce(json_agg(agg), '[]') + from + ( + select + "assets".* + from + "assets" + inner join "memories_assets_assets" on "assets"."id" = "memories_assets_assets"."assetsId" + where + "memories_assets_assets"."memoriesId" = "memories"."id" + and "assets"."deletedAt" is null + ) as agg + ) as "assets" +from + "memories" +where + "id" = $1 + and "deletedAt" is null + +-- MemoryRepository.update +update "memories" +set + "ownerId" = $1, + "isSaved" = $2 +where + "id" = $3 +select + "memories".*, + ( + select + coalesce(json_agg(agg), '[]') + from + ( + select + "assets".* + from + "assets" + inner join "memories_assets_assets" on "assets"."id" = "memories_assets_assets"."assetsId" + where + "memories_assets_assets"."memoriesId" = "memories"."id" + and "assets"."deletedAt" is null + ) as agg + ) as "assets" +from + "memories" +where + "id" = $1 + and "deletedAt" is null + +-- MemoryRepository.delete +delete from "memories" +where + "id" = $1 + -- MemoryRepository.getAssetIds -SELECT - "memories_assets"."assetsId" AS "assetId" -FROM - "memories_assets_assets" "memories_assets" -WHERE - "memories_assets"."memoriesId" = $1 - AND "memories_assets"."assetsId" IN ($2) +select + "assetsId" +from + "memories_assets_assets" +where + "memoriesId" = $1 + and "assetsId" in ($2) diff --git a/server/src/repositories/memory.repository.ts b/server/src/repositories/memory.repository.ts index 47dc705093..7e59b92e68 100644 --- a/server/src/repositories/memory.repository.ts +++ b/server/src/repositories/memory.repository.ts @@ -1,49 +1,55 @@ import { Injectable } from '@nestjs/common'; -import { InjectDataSource, InjectRepository } from '@nestjs/typeorm'; +import { Insertable, Kysely, Updateable } from 'kysely'; +import { jsonArrayFrom } from 'kysely/helpers/postgres'; +import { InjectKysely } from 'nestjs-kysely'; +import { DB, Memories } from 'src/db'; import { Chunked, ChunkedSet, DummyValue, GenerateSql } from 'src/decorators'; import { MemoryEntity } from 'src/entities/memory.entity'; import { IMemoryRepository } from 'src/interfaces/memory.interface'; -import { DataSource, In, Repository } from 'typeorm'; @Injectable() export class MemoryRepository implements IMemoryRepository { - constructor( - @InjectRepository(MemoryEntity) private repository: Repository, - @InjectDataSource() private dataSource: DataSource, - ) {} + constructor(@InjectKysely() private db: Kysely) {} + @GenerateSql({ params: [DummyValue.UUID] }) search(ownerId: string): Promise { - return this.repository.find({ - where: { - ownerId, - }, - order: { - memoryAt: 'DESC', - }, - }); + return this.db + .selectFrom('memories') + .selectAll() + .where('ownerId', '=', ownerId) + .orderBy('memoryAt', 'desc') + .execute() as Promise; } + @GenerateSql({ params: [DummyValue.UUID] }) get(id: string): Promise { - return this.repository.findOne({ - where: { - id, - }, - relations: { - assets: true, - }, + return this.getByIdBuilder(id).executeTakeFirst() as unknown as Promise; + } + + async create(memory: Insertable, assetIds: Set): Promise { + const id = await this.db.transaction().execute(async (tx) => { + const { id } = await tx.insertInto('memories').values(memory).returning('id').executeTakeFirstOrThrow(); + + if (assetIds.size > 0) { + const values = [...assetIds].map((assetId) => ({ memoriesId: id, assetsId: assetId })); + await tx.insertInto('memories_assets_assets').values(values).execute(); + } + + return id; }); + + return this.getByIdBuilder(id).executeTakeFirstOrThrow() as unknown as Promise; } - create(memory: Partial): Promise { - return this.save(memory); - } - - update(memory: Partial): Promise { - return this.save(memory); + @GenerateSql({ params: [DummyValue.UUID, { ownerId: DummyValue.UUID, isSaved: true }] }) + async update(id: string, memory: Updateable): Promise { + await this.db.updateTable('memories').set(memory).where('id', '=', id).execute(); + return this.getByIdBuilder(id).executeTakeFirstOrThrow() as unknown as Promise; } + @GenerateSql({ params: [DummyValue.UUID] }) async delete(id: string): Promise { - await this.repository.delete({ id }); + await this.db.deleteFrom('memories').where('id', '=', id).execute(); } @GenerateSql({ params: [DummyValue.UUID, [DummyValue.UUID]] }) @@ -53,46 +59,49 @@ export class MemoryRepository implements IMemoryRepository { return new Set(); } - const results = await this.dataSource - .createQueryBuilder() - .select('memories_assets.assetsId', 'assetId') - .from('memories_assets_assets', 'memories_assets') - .where('"memories_assets"."memoriesId" = :memoryId', { memoryId: id }) - .andWhere('memories_assets.assetsId IN (:...assetIds)', { assetIds }) - .getRawMany<{ assetId: string }>(); + const results = await this.db + .selectFrom('memories_assets_assets') + .select(['assetsId']) + .where('memoriesId', '=', id) + .where('assetsId', 'in', assetIds) + .execute(); - return new Set(results.map(({ assetId }) => assetId)); + return new Set(results.map(({ assetsId }) => assetsId)); } + @GenerateSql({ params: [DummyValue.UUID, [DummyValue.UUID]] }) async addAssetIds(id: string, assetIds: string[]): Promise { - await this.dataSource - .createQueryBuilder() - .insert() - .into('memories_assets_assets', ['memoriesId', 'assetsId']) + await this.db + .insertInto('memories_assets_assets') .values(assetIds.map((assetId) => ({ memoriesId: id, assetsId: assetId }))) .execute(); } @Chunked({ paramIndex: 1 }) + @GenerateSql({ params: [DummyValue.UUID, [DummyValue.UUID]] }) async removeAssetIds(id: string, assetIds: string[]): Promise { - await this.dataSource - .createQueryBuilder() - .delete() - .from('memories_assets_assets') - .where({ - memoriesId: id, - assetsId: In(assetIds), - }) + await this.db + .deleteFrom('memories_assets_assets') + .where('memoriesId', '=', id) + .where('assetsId', 'in', assetIds) .execute(); } - private async save(memory: Partial): Promise { - const { id } = await this.repository.save(memory); - return this.repository.findOneOrFail({ - where: { id }, - relations: { - assets: true, - }, - }); + private getByIdBuilder(id: string) { + return this.db + .selectFrom('memories') + .selectAll('memories') + .select((eb) => + jsonArrayFrom( + eb + .selectFrom('assets') + .selectAll('assets') + .innerJoin('memories_assets_assets', 'assets.id', 'memories_assets_assets.assetsId') + .whereRef('memories_assets_assets.memoriesId', '=', 'memories.id') + .where('assets.deletedAt', 'is', null), + ).as('assets'), + ) + .where('id', '=', id) + .where('deletedAt', 'is', null); } } diff --git a/server/src/services/memory.service.spec.ts b/server/src/services/memory.service.spec.ts index b5dd4c2553..9c5336eb6e 100644 --- a/server/src/services/memory.service.spec.ts +++ b/server/src/services/memory.service.spec.ts @@ -69,7 +69,17 @@ describe(MemoryService.name, () => { memoryAt: new Date(2024), }), ).resolves.toMatchObject({ assets: [] }); - expect(memoryMock.create).toHaveBeenCalledWith(expect.objectContaining({ assets: [] })); + expect(memoryMock.create).toHaveBeenCalledWith( + { + ownerId: 'admin_id', + memoryAt: expect.any(Date), + type: MemoryType.ON_THIS_DAY, + isSaved: undefined, + sendAt: undefined, + data: { year: 2024 }, + }, + new Set(), + ); }); it('should create a memory', async () => { @@ -80,14 +90,14 @@ describe(MemoryService.name, () => { type: MemoryType.ON_THIS_DAY, data: { year: 2024 }, assetIds: ['asset1'], - memoryAt: new Date(2024), + memoryAt: new Date(2024, 0, 1), }), ).resolves.toBeDefined(); expect(memoryMock.create).toHaveBeenCalledWith( expect.objectContaining({ ownerId: userStub.admin.id, - assets: [{ id: 'asset1' }], }), + new Set(['asset1']), ); }); @@ -115,12 +125,7 @@ describe(MemoryService.name, () => { accessMock.memory.checkOwnerAccess.mockResolvedValue(new Set(['memory1'])); memoryMock.update.mockResolvedValue(memoryStub.memory1); await expect(sut.update(authStub.admin, 'memory1', { isSaved: true })).resolves.toBeDefined(); - expect(memoryMock.update).toHaveBeenCalledWith( - expect.objectContaining({ - id: 'memory1', - isSaved: true, - }), - ); + expect(memoryMock.update).toHaveBeenCalledWith('memory1', expect.objectContaining({ isSaved: true })); }); }); diff --git a/server/src/services/memory.service.ts b/server/src/services/memory.service.ts index 816b0fddeb..926571e43c 100644 --- a/server/src/services/memory.service.ts +++ b/server/src/services/memory.service.ts @@ -2,7 +2,6 @@ import { BadRequestException, Injectable } from '@nestjs/common'; import { BulkIdResponseDto, BulkIdsDto } from 'src/dtos/asset-ids.response.dto'; import { AuthDto } from 'src/dtos/auth.dto'; import { MemoryCreateDto, MemoryResponseDto, MemoryUpdateDto, mapMemory } from 'src/dtos/memory.dto'; -import { AssetEntity } from 'src/entities/asset.entity'; import { Permission } from 'src/enum'; import { BaseService } from 'src/services/base.service'; import { addAssets, removeAssets } from 'src/utils/asset.util'; @@ -29,15 +28,17 @@ export class MemoryService extends BaseService { permission: Permission.ASSET_SHARE, ids: assetIds, }); - const memory = await this.memoryRepository.create({ - ownerId: auth.user.id, - type: dto.type, - data: dto.data, - isSaved: dto.isSaved, - memoryAt: dto.memoryAt, - seenAt: dto.seenAt, - assets: [...allowedAssetIds].map((id) => ({ id }) as AssetEntity), - }); + const memory = await this.memoryRepository.create( + { + ownerId: auth.user.id, + type: dto.type, + data: dto.data, + isSaved: dto.isSaved, + memoryAt: dto.memoryAt, + seenAt: dto.seenAt, + }, + allowedAssetIds, + ); return mapMemory(memory); } @@ -45,8 +46,7 @@ export class MemoryService extends BaseService { async update(auth: AuthDto, id: string, dto: MemoryUpdateDto): Promise { await this.requireAccess({ auth, permission: Permission.MEMORY_UPDATE, ids: [id] }); - const memory = await this.memoryRepository.update({ - id, + const memory = await this.memoryRepository.update(id, { isSaved: dto.isSaved, memoryAt: dto.memoryAt, seenAt: dto.seenAt, @@ -68,7 +68,7 @@ export class MemoryService extends BaseService { const hasSuccess = results.find(({ success }) => success); if (hasSuccess) { - await this.memoryRepository.update({ id, updatedAt: new Date() }); + await this.memoryRepository.update(id, { updatedAt: new Date() }); } return results; @@ -86,7 +86,7 @@ export class MemoryService extends BaseService { const hasSuccess = results.find(({ success }) => success); if (hasSuccess) { - await this.memoryRepository.update({ id, updatedAt: new Date() }); + await this.memoryRepository.update(id, { id, updatedAt: new Date() }); } return results; From 7d087371b5a15e6052e694385b3c68884f439088 Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 15 Jan 2025 13:55:29 -0600 Subject: [PATCH 12/30] chore: sql sync (#15370) * chore: sql sync * chore: sql sync --- server/src/queries/memory.repository.sql | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/server/src/queries/memory.repository.sql b/server/src/queries/memory.repository.sql index 396da3f56e..3144f314dd 100644 --- a/server/src/queries/memory.repository.sql +++ b/server/src/queries/memory.repository.sql @@ -77,3 +77,9 @@ from where "memoriesId" = $1 and "assetsId" in ($2) + +-- MemoryRepository.addAssetIds +insert into + "memories_assets_assets" ("memoriesId", "assetsId") +values + ($1, $2) From 2d2966caa02342c67069378cbf27a88d5dbb28d5 Mon Sep 17 00:00:00 2001 From: Jason Rasmussen Date: Wed, 15 Jan 2025 15:03:20 -0500 Subject: [PATCH 13/30] chore: use port 2286 for the auth server (#15369) --- e2e/src/api/specs/oauth.e2e-spec.ts | 4 ++-- e2e/src/setup/auth-server.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/e2e/src/api/specs/oauth.e2e-spec.ts b/e2e/src/api/specs/oauth.e2e-spec.ts index 42989a118f..9cd5f0252a 100644 --- a/e2e/src/api/specs/oauth.e2e-spec.ts +++ b/e2e/src/api/specs/oauth.e2e-spec.ts @@ -13,8 +13,8 @@ import request from 'supertest'; import { beforeAll, describe, expect, it } from 'vitest'; const authServer = { - internal: 'http://auth-server:3000', - external: 'http://127.0.0.1:3000', + internal: 'http://auth-server:2286', + external: 'http://127.0.0.1:2286', }; const mobileOverrideRedirectUri = 'https://photos.immich.app/oauth/mobile-redirect'; diff --git a/e2e/src/setup/auth-server.ts b/e2e/src/setup/auth-server.ts index cde50813dd..575e97d291 100644 --- a/e2e/src/setup/auth-server.ts +++ b/e2e/src/setup/auth-server.ts @@ -51,7 +51,7 @@ const setup = async () => { const { privateKey, publicKey } = await generateKeyPair('RS256'); const redirectUris = ['http://127.0.0.1:2285/auth/login', 'https://photos.immich.app/oauth/mobile-redirect']; - const port = 3000; + const port = 2286; const host = '0.0.0.0'; const oidc = new Provider(`http://${host}:${port}`, { renderError: async (ctx, out, error) => { From a60da1ccab9d40c874124b9547df6f1dfc53df50 Mon Sep 17 00:00:00 2001 From: Jason Rasmussen Date: Wed, 15 Jan 2025 15:09:19 -0500 Subject: [PATCH 14/30] refactor: migrate create user form to immich ui (#15350) * refactor: migrate create user form to immich ui * minor styling tweak * remove unintentional commit * revert formating diff --------- Co-authored-by: Alex Tran --- .../components/forms/create-user-form.svelte | 197 ++++++++---------- .../components/layouts/AuthPageLayout.svelte | 2 +- .../search-bar/search-filter-modal.svelte | 6 +- .../routes/admin/user-management/+page.svelte | 52 +++-- .../routes/auth/change-password/+page.svelte | 4 +- web/src/routes/auth/login/+page.svelte | 2 +- web/tailwind.config.js | 6 +- 7 files changed, 121 insertions(+), 148 deletions(-) diff --git a/web/src/lib/components/forms/create-user-form.svelte b/web/src/lib/components/forms/create-user-form.svelte index 7aa1c76ed3..8bd955734a 100644 --- a/web/src/lib/components/forms/create-user-form.svelte +++ b/web/src/lib/components/forms/create-user-form.svelte @@ -5,10 +5,8 @@ import { ByteUnit, convertToBytes } from '$lib/utils/byte-units'; import { handleError } from '$lib/utils/handle-error'; import { createUserAdmin } from '@immich/sdk'; + import { Alert, Button, Field, HelperText, Input, PasswordInput, Stack, Switch } from '@immich/ui'; import { t } from 'svelte-i18n'; - import Button from '../elements/buttons/button.svelte'; - import Slider from '../elements/slider.svelte'; - import PasswordField from '../shared-components/password-field.svelte'; interface Props { onClose: () => void; @@ -17,137 +15,114 @@ oauthEnabled?: boolean; } - let { onClose, onSubmit, onCancel, oauthEnabled = false }: Props = $props(); + let { onClose, onSubmit: onDone, onCancel, oauthEnabled = false }: Props = $props(); let error = $state(''); - let success = $state(''); + let success = $state(false); let email = $state(''); let password = $state(''); - let confirmPassword = $state(''); + let passwordConfirm = $state(''); let name = $state(''); let shouldChangePassword = $state(true); let notify = $state(true); - let canCreateUser = $state(false); - let quotaSize: number | undefined = $state(); + let quotaSize: string | undefined = $state(); let isCreatingUser = $state(false); - let quotaSizeInBytes = $derived(quotaSize ? convertToBytes(quotaSize, ByteUnit.GiB) : null); + let quotaSizeInBytes = $derived(quotaSize ? convertToBytes(Number(quotaSize), ByteUnit.GiB) : null); let quotaSizeWarning = $derived( quotaSizeInBytes && userInteraction.serverInfo && quotaSizeInBytes > userInteraction.serverInfo.diskSizeRaw, ); - $effect(() => { - if (password !== confirmPassword && confirmPassword.length > 0) { - error = $t('password_does_not_match'); - canCreateUser = false; - } else { - error = ''; - canCreateUser = true; - } - }); + const passwordMismatch = $derived(password !== passwordConfirm && passwordConfirm.length > 0); + const passwordMismatchMessage = $derived(passwordMismatch ? $t('password_does_not_match') : ''); + const valid = $derived(!passwordMismatch && !isCreatingUser); - async function registerUser() { - if (canCreateUser && !isCreatingUser) { - isCreatingUser = true; - error = ''; - - try { - await createUserAdmin({ - userAdminCreateDto: { - email, - password, - shouldChangePassword, - name, - quotaSizeInBytes, - notify, - }, - }); - - success = $t('new_user_created'); - - onSubmit(); - - return; - } catch (error) { - handleError(error, $t('errors.unable_to_create_user')); - } finally { - isCreatingUser = false; - } - } - } - - const onsubmit = async (event: Event) => { + const onSubmit = async (event: Event) => { event.preventDefault(); - await registerUser(); + + if (!valid) { + return; + } + + isCreatingUser = true; + error = ''; + + try { + await createUserAdmin({ + userAdminCreateDto: { + email, + password, + shouldChangePassword, + name, + quotaSizeInBytes, + notify, + }, + }); + + success = true; + + onDone(); + + return; + } catch (error) { + handleError(error, $t('errors.unable_to_create_user')); + } finally { + isCreatingUser = false; + } }; - -
-
- - -
- - {#if $featureFlags.email} -
- - -
- {/if} - -
- - -
- -
- - -
- -
- - -
- -
- - -
- -
- - -
- + + {#if error} -

{error}

+ {/if} {#if success} -

{success}

+

{$t('new_user_created')}

{/if} - - {#snippet stickyBottom()} - - - {/snippet} -
+ + + + + + {#if $featureFlags.email} + + + + {/if} + + + + + + + + {passwordMismatchMessage} + + + + + + + + + + + + + {#if quotaSizeWarning} + {$t('errors.quota_higher_than_disk_size')} + {/if} + + + + {#snippet stickyBottom()} + + + {/snippet} +
+ diff --git a/web/src/lib/components/layouts/AuthPageLayout.svelte b/web/src/lib/components/layouts/AuthPageLayout.svelte index 3a61a1671c..78ff67cfcb 100644 --- a/web/src/lib/components/layouts/AuthPageLayout.svelte +++ b/web/src/lib/components/layouts/AuthPageLayout.svelte @@ -11,7 +11,7 @@
- + diff --git a/web/src/lib/components/shared-components/search-bar/search-filter-modal.svelte b/web/src/lib/components/shared-components/search-bar/search-filter-modal.svelte index de34092658..c367d001f2 100644 --- a/web/src/lib/components/shared-components/search-bar/search-filter-modal.svelte +++ b/web/src/lib/components/shared-components/search-bar/search-filter-modal.svelte @@ -17,7 +17,7 @@ - - - - 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 @@ - -
- - - - - - {#if customDateRange} -
-
- - -
-
- - -
-
- { - customDateRange = false; - settings.dateAfter = ''; - settings.dateBefore = ''; - }} - > - {$t('remove_custom_date_range')} - -
-
- {:else} -
- -
- { - customDateRange = true; - settings.relativeDate = ''; - }} - > - {$t('use_custom_date_range')} - -
-
- {/if} - +
+ + + + + + + + + + + + + + + + + - {#snippet stickyBottom()} - - - {/snippet} - + {#if customDateRange} +
+
+ + +
+
+ + +
+
+ +
+
+ {:else} +
+ +
+ +
+
+ {/if} + + + {#snippet stickyBottom()} + + + {/snippet} + +
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 @@