From 916603d2d43c5b9c75a8df8976ceeccfcc83c279 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 27 Mar 2024 12:07:49 -0400 Subject: [PATCH 01/18] fix(deps): update typescript-projects (#8287) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- cli/package-lock.json | 10 ++--- server/package-lock.json | 84 ++++++++++++++++++++-------------------- web/package-lock.json | 10 ++--- 3 files changed, 52 insertions(+), 52 deletions(-) diff --git a/cli/package-lock.json b/cli/package-lock.json index cd4ba9682..8ddf47446 100644 --- a/cli/package-lock.json +++ b/cli/package-lock.json @@ -54,8 +54,8 @@ "@oazapfts/runtime": "^1.0.2" }, "devDependencies": { - "@types/node": "^20.11.30", - "typescript": "^5.4.3" + "@types/node": "^20.11.0", + "typescript": "^5.3.3" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -4456,9 +4456,9 @@ } }, "node_modules/vite": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.2.tgz", - "integrity": "sha512-FWZbz0oSdLq5snUI0b6sULbz58iXFXdvkZfZWR/F0ZJuKTSPO7v72QPXt6KqYeMFb0yytNp6kZosxJ96Nr/wDQ==", + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.3.tgz", + "integrity": "sha512-+i1oagbvkVIhEy9TnEV+fgXsng13nZM90JQbrcPrf6DvW2mXARlz+DK7DLiDP+qeKoD1FCVx/1SpFL1CLq9Mhw==", "dev": true, "dependencies": { "esbuild": "^0.20.1", diff --git a/server/package-lock.json b/server/package-lock.json index b9c14346f..12ae0358c 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -2538,9 +2538,9 @@ } }, "node_modules/@nestjs/common": { - "version": "10.3.4", - "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-10.3.4.tgz", - "integrity": "sha512-HmehujZhUZjf9TN2o0TyzWYNwEgyRYqZZ5qIcF/mCgIUZ4olIKlazna0kGK56FGlCvviHWNKQM5eTuVeTstIgA==", + "version": "10.3.5", + "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-10.3.5.tgz", + "integrity": "sha512-XWxbDf2ey/jAyEa3/XpckgfzJZ9j3I05ZkEFx7cAlebFuVKeq5UDDb5Sq9O7hMmbH9xdQj3pYT19SSj01hKeug==", "dependencies": { "iterare": "1.2.1", "tslib": "2.6.2", @@ -2592,9 +2592,9 @@ } }, "node_modules/@nestjs/core": { - "version": "10.3.4", - "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-10.3.4.tgz", - "integrity": "sha512-rF0yebuHmMj+9/CkbjPWWMvlF5x8j5Biw2DRvbl8R8n2X3OdFBN+06x/9xm3/ZssR5tLoB9tsYspFUb+SvnnwA==", + "version": "10.3.5", + "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-10.3.5.tgz", + "integrity": "sha512-U7SrGD9/Mu4eUtxfZYiGdY38FcksEyJegs4dQZ8B19nnusw0aTocPEy4HVsmx0LLO4sG+fBLLYzCDDr9kFwXAQ==", "hasInstallScript": true, "dependencies": { "@nuxtjs/opencollective": "0.3.2", @@ -2660,9 +2660,9 @@ } }, "node_modules/@nestjs/platform-express": { - "version": "10.3.4", - "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-10.3.4.tgz", - "integrity": "sha512-rzUUUZCGYNs/viT9I6W5izJ1+oYCG0ym/dAn31NmYJW9UchxJdX5PCJqWF8iIbys6JgfbdcapMR5t+L7OZsasQ==", + "version": "10.3.5", + "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-10.3.5.tgz", + "integrity": "sha512-IhVomwLvdLlv4zCdQK2ROT/nInk1i8m4K48lAUHJV5UVktgVmg0WbQga2/9KywaTjNbx+eWhZXXFii+vtFRAOw==", "dependencies": { "body-parser": "1.20.2", "cors": "2.8.5", @@ -2680,9 +2680,9 @@ } }, "node_modules/@nestjs/platform-socket.io": { - "version": "10.3.4", - "resolved": "https://registry.npmjs.org/@nestjs/platform-socket.io/-/platform-socket.io-10.3.4.tgz", - "integrity": "sha512-HiL7FbLQBanf8ORxQDpub8wdkRJmXHj8vmExDJ+lD1/E2ChrJbBgRDaKWI7QcSzPKF1uS8VVwz3w0zn3F/EDtA==", + "version": "10.3.5", + "resolved": "https://registry.npmjs.org/@nestjs/platform-socket.io/-/platform-socket.io-10.3.5.tgz", + "integrity": "sha512-G2N3sTd9tZ7XQQ7RlrpaQdt1/IBztVHuKg686QmBTLVlRHZ1AMOmXouBk+q5SINT1XURiABa8tQh1Ydx0OEh9w==", "dependencies": { "socket.io": "4.7.5", "tslib": "2.6.2" @@ -2765,9 +2765,9 @@ } }, "node_modules/@nestjs/testing": { - "version": "10.3.4", - "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-10.3.4.tgz", - "integrity": "sha512-g3NQnRUFBcYF+ySkB7INg5RiV7CNfkP5zwaf3NFo0WjhBrfih9f1jMZ/19blLZ4djN/ngulYks2E3lzROAW8RQ==", + "version": "10.3.5", + "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-10.3.5.tgz", + "integrity": "sha512-j30/lxH0BayeDTigapYtQn/XhMRR7CzlFsm3dHoWViWQv0qT1r2ffe3927BbBLX3N/ZzglE10OAqW06ADZV8dw==", "dev": true, "dependencies": { "tslib": "2.6.2" @@ -2807,9 +2807,9 @@ } }, "node_modules/@nestjs/websockets": { - "version": "10.3.4", - "resolved": "https://registry.npmjs.org/@nestjs/websockets/-/websockets-10.3.4.tgz", - "integrity": "sha512-ZGDY8t1bBYzY2xbOe2QOxYG+D6W1mALSS3VD/rcVW34oaysF4iQQEr4t2ktYLbPAuZlEvwM5EhutqCkBUsDw7Q==", + "version": "10.3.5", + "resolved": "https://registry.npmjs.org/@nestjs/websockets/-/websockets-10.3.5.tgz", + "integrity": "sha512-6w383LUBFHoZ0eFODqEHN2NoIRUwbTd37Hc1KqtZZihhFUzscC/0LMAV20o9LdfS/Xjog5ShNTxvOHuzNBnE4A==", "dependencies": { "iterare": "1.2.1", "object-hash": "3.0.0", @@ -8749,9 +8749,9 @@ } }, "node_modules/i18n-iso-countries": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/i18n-iso-countries/-/i18n-iso-countries-7.10.1.tgz", - "integrity": "sha512-9DXmAMkfGcGNE+E/2fE85UUjjkPeT0LHMA8d+kcxXiO+s50W28lxiICel8f8qWZmCNic1cuhN1+nw7ZazMQJFA==", + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/i18n-iso-countries/-/i18n-iso-countries-7.11.0.tgz", + "integrity": "sha512-MP2+aAJwvBTuruaMEj+mPEhu4D9rn03GkkbuP8/xkvNzhVwNe2cAg1ivkL5Oj+vwqEwvIcf5C7Q+5Y/UZNLBHw==", "dependencies": { "diacritics": "1.3.0" }, @@ -16155,9 +16155,9 @@ } }, "@nestjs/common": { - "version": "10.3.4", - "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-10.3.4.tgz", - "integrity": "sha512-HmehujZhUZjf9TN2o0TyzWYNwEgyRYqZZ5qIcF/mCgIUZ4olIKlazna0kGK56FGlCvviHWNKQM5eTuVeTstIgA==", + "version": "10.3.5", + "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-10.3.5.tgz", + "integrity": "sha512-XWxbDf2ey/jAyEa3/XpckgfzJZ9j3I05ZkEFx7cAlebFuVKeq5UDDb5Sq9O7hMmbH9xdQj3pYT19SSj01hKeug==", "requires": { "iterare": "1.2.1", "tslib": "2.6.2", @@ -16183,9 +16183,9 @@ } }, "@nestjs/core": { - "version": "10.3.4", - "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-10.3.4.tgz", - "integrity": "sha512-rF0yebuHmMj+9/CkbjPWWMvlF5x8j5Biw2DRvbl8R8n2X3OdFBN+06x/9xm3/ZssR5tLoB9tsYspFUb+SvnnwA==", + "version": "10.3.5", + "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-10.3.5.tgz", + "integrity": "sha512-U7SrGD9/Mu4eUtxfZYiGdY38FcksEyJegs4dQZ8B19nnusw0aTocPEy4HVsmx0LLO4sG+fBLLYzCDDr9kFwXAQ==", "requires": { "@nuxtjs/opencollective": "0.3.2", "fast-safe-stringify": "2.1.1", @@ -16210,9 +16210,9 @@ "requires": {} }, "@nestjs/platform-express": { - "version": "10.3.4", - "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-10.3.4.tgz", - "integrity": "sha512-rzUUUZCGYNs/viT9I6W5izJ1+oYCG0ym/dAn31NmYJW9UchxJdX5PCJqWF8iIbys6JgfbdcapMR5t+L7OZsasQ==", + "version": "10.3.5", + "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-10.3.5.tgz", + "integrity": "sha512-IhVomwLvdLlv4zCdQK2ROT/nInk1i8m4K48lAUHJV5UVktgVmg0WbQga2/9KywaTjNbx+eWhZXXFii+vtFRAOw==", "requires": { "body-parser": "1.20.2", "cors": "2.8.5", @@ -16222,9 +16222,9 @@ } }, "@nestjs/platform-socket.io": { - "version": "10.3.4", - "resolved": "https://registry.npmjs.org/@nestjs/platform-socket.io/-/platform-socket.io-10.3.4.tgz", - "integrity": "sha512-HiL7FbLQBanf8ORxQDpub8wdkRJmXHj8vmExDJ+lD1/E2ChrJbBgRDaKWI7QcSzPKF1uS8VVwz3w0zn3F/EDtA==", + "version": "10.3.5", + "resolved": "https://registry.npmjs.org/@nestjs/platform-socket.io/-/platform-socket.io-10.3.5.tgz", + "integrity": "sha512-G2N3sTd9tZ7XQQ7RlrpaQdt1/IBztVHuKg686QmBTLVlRHZ1AMOmXouBk+q5SINT1XURiABa8tQh1Ydx0OEh9w==", "requires": { "socket.io": "4.7.5", "tslib": "2.6.2" @@ -16274,9 +16274,9 @@ } }, "@nestjs/testing": { - "version": "10.3.4", - "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-10.3.4.tgz", - "integrity": "sha512-g3NQnRUFBcYF+ySkB7INg5RiV7CNfkP5zwaf3NFo0WjhBrfih9f1jMZ/19blLZ4djN/ngulYks2E3lzROAW8RQ==", + "version": "10.3.5", + "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-10.3.5.tgz", + "integrity": "sha512-j30/lxH0BayeDTigapYtQn/XhMRR7CzlFsm3dHoWViWQv0qT1r2ffe3927BbBLX3N/ZzglE10OAqW06ADZV8dw==", "dev": true, "requires": { "tslib": "2.6.2" @@ -16291,9 +16291,9 @@ } }, "@nestjs/websockets": { - "version": "10.3.4", - "resolved": "https://registry.npmjs.org/@nestjs/websockets/-/websockets-10.3.4.tgz", - "integrity": "sha512-ZGDY8t1bBYzY2xbOe2QOxYG+D6W1mALSS3VD/rcVW34oaysF4iQQEr4t2ktYLbPAuZlEvwM5EhutqCkBUsDw7Q==", + "version": "10.3.5", + "resolved": "https://registry.npmjs.org/@nestjs/websockets/-/websockets-10.3.5.tgz", + "integrity": "sha512-6w383LUBFHoZ0eFODqEHN2NoIRUwbTd37Hc1KqtZZihhFUzscC/0LMAV20o9LdfS/Xjog5ShNTxvOHuzNBnE4A==", "requires": { "iterare": "1.2.1", "object-hash": "3.0.0", @@ -20760,9 +20760,9 @@ "dev": true }, "i18n-iso-countries": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/i18n-iso-countries/-/i18n-iso-countries-7.10.1.tgz", - "integrity": "sha512-9DXmAMkfGcGNE+E/2fE85UUjjkPeT0LHMA8d+kcxXiO+s50W28lxiICel8f8qWZmCNic1cuhN1+nw7ZazMQJFA==", + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/i18n-iso-countries/-/i18n-iso-countries-7.11.0.tgz", + "integrity": "sha512-MP2+aAJwvBTuruaMEj+mPEhu4D9rn03GkkbuP8/xkvNzhVwNe2cAg1ivkL5Oj+vwqEwvIcf5C7Q+5Y/UZNLBHw==", "requires": { "diacritics": "1.3.0" } diff --git a/web/package-lock.json b/web/package-lock.json index c6c74d2ff..60a6651bd 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -69,8 +69,8 @@ "@oazapfts/runtime": "^1.0.2" }, "devDependencies": { - "@types/node": "^20.11.30", - "typescript": "^5.4.3" + "@types/node": "^20.11.0", + "typescript": "^5.3.3" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -8671,9 +8671,9 @@ } }, "node_modules/vite": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.2.tgz", - "integrity": "sha512-FWZbz0oSdLq5snUI0b6sULbz58iXFXdvkZfZWR/F0ZJuKTSPO7v72QPXt6KqYeMFb0yytNp6kZosxJ96Nr/wDQ==", + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.3.tgz", + "integrity": "sha512-+i1oagbvkVIhEy9TnEV+fgXsng13nZM90JQbrcPrf6DvW2mXARlz+DK7DLiDP+qeKoD1FCVx/1SpFL1CLq9Mhw==", "dev": true, "dependencies": { "esbuild": "^0.20.1", From 613b544bf0ebddfc410e11bd8c47ccc9d75c9b73 Mon Sep 17 00:00:00 2001 From: Jason Rasmussen Date: Wed, 27 Mar 2024 15:01:36 -0400 Subject: [PATCH 02/18] feat(cli): better server info output (#8307) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(cli): server-info command prints url and user email * chore: clean up --------- Co-authored-by: 澪 --- cli/src/commands/server-info.ts | 27 +++++++++++++++-------- cli/src/utils.ts | 16 +++++++++----- e2e/src/cli/specs/server-info.e2e-spec.ts | 17 ++++++++------ 3 files changed, 39 insertions(+), 21 deletions(-) diff --git a/cli/src/commands/server-info.ts b/cli/src/commands/server-info.ts index a7de804df..074513bd6 100644 --- a/cli/src/commands/server-info.ts +++ b/cli/src/commands/server-info.ts @@ -1,15 +1,24 @@ -import { getAssetStatistics, getServerVersion, getSupportedMediaTypes } from '@immich/sdk'; +import { getAssetStatistics, getMyUserInfo, getServerVersion, getSupportedMediaTypes } from '@immich/sdk'; import { BaseOptions, authenticate } from 'src/utils'; export const serverInfo = async (options: BaseOptions) => { - await authenticate(options); + const { url } = await authenticate(options); - const versionInfo = await getServerVersion(); - const mediaTypes = await getSupportedMediaTypes(); - const stats = await getAssetStatistics({}); + const [versionInfo, mediaTypes, stats, userInfo] = await Promise.all([ + getServerVersion(), + getSupportedMediaTypes(), + getAssetStatistics({}), + getMyUserInfo(), + ]); - console.log(`Server Version: ${versionInfo.major}.${versionInfo.minor}.${versionInfo.patch}`); - console.log(`Image Types: ${mediaTypes.image.map((extension) => extension.replace('.', ''))}`); - console.log(`Video Types: ${mediaTypes.video.map((extension) => extension.replace('.', ''))}`); - console.log(`Statistics:\n Images: ${stats.images}\n Videos: ${stats.videos}\n Total: ${stats.total}`); + console.log(`Server Info (via ${userInfo.email})`); + console.log(` Url: ${url}`); + console.log(` Version: ${versionInfo.major}.${versionInfo.minor}.${versionInfo.patch}`); + console.log(` Formats:`); + console.log(` Images: ${mediaTypes.image.map((extension) => extension.replace('.', ''))}`); + console.log(` Videos: ${mediaTypes.video.map((extension) => extension.replace('.', ''))}`); + console.log(` Statistics:`); + console.log(` Images: ${stats.images}`); + console.log(` Videos: ${stats.videos}`); + console.log(` Total: ${stats.total}`); }; diff --git a/cli/src/utils.ts b/cli/src/utils.ts index c17ad6903..b2d34bbb4 100644 --- a/cli/src/utils.ts +++ b/cli/src/utils.ts @@ -15,21 +15,25 @@ export interface BaseOptions { export type AuthDto = { url: string; key: string }; type OldAuthDto = { instanceUrl: string; apiKey: string }; -export const authenticate = async (options: BaseOptions): Promise => { +export const authenticate = async (options: BaseOptions): Promise => { const { configDirectory: configDir, url, key } = options; // provided in command if (url && key) { - await connect(url, key); - return; + return connect(url, key); } // fallback to auth file const config = await readAuthFile(configDir); - await connect(config.url, config.key); + const auth = await connect(config.url, config.key); + if (auth.url !== config.url) { + await writeAuthFile(configDir, auth); + } + + return auth; }; -export const connect = async (url: string, key: string): Promise => { +export const connect = async (url: string, key: string) => { const wellKnownUrl = new URL('.well-known/immich', url); try { const wellKnown = await fetch(wellKnownUrl).then((response) => response.json()); @@ -50,6 +54,8 @@ export const connect = async (url: string, key: string): Promise => { logError(error, 'Failed to connect to server'); process.exit(1); } + + return { url, key }; }; export const logError = (error: unknown, message: string) => { diff --git a/e2e/src/cli/specs/server-info.e2e-spec.ts b/e2e/src/cli/specs/server-info.e2e-spec.ts index f207f1fa2..13eefd3df 100644 --- a/e2e/src/cli/specs/server-info.e2e-spec.ts +++ b/e2e/src/cli/specs/server-info.e2e-spec.ts @@ -11,13 +11,16 @@ describe(`immich server-info`, () => { it('should return the server info', async () => { const { stderr, stdout, exitCode } = await immichCli(['server-info']); expect(stdout.split('\n')).toEqual([ - expect.stringContaining('Server Version:'), - expect.stringContaining('Image Types:'), - expect.stringContaining('Video Types:'), - 'Statistics:', - ' Images: 0', - ' Videos: 0', - ' Total: 0', + expect.stringContaining('Server Info (via admin@immich.cloud'), + ' Url: http://127.0.0.1:2283/api', + expect.stringContaining('Version:'), + ' Formats:', + expect.stringContaining('Images:'), + expect.stringContaining('Videos:'), + ' Statistics:', + ' Images: 0', + ' Videos: 0', + ' Total: 0', ]); expect(stderr).toBe(''); expect(exitCode).toBe(0); From 8bf571bf4823a8464b9197c3fde670dc93e154d7 Mon Sep 17 00:00:00 2001 From: Ethan Margaillan Date: Wed, 27 Mar 2024 20:47:42 +0100 Subject: [PATCH 03/18] feat(web): better UX when creating a new album (#8270) * feat(web): ask user before going to newly created album * feat(web): add button option to notification cards * feat(web): allow html messages in notification cards * show album -> view album * remove 'link' action from notifications * remove unused type --- .../asset-viewer/album-list-item.svelte | 3 +- .../asset-viewer/asset-viewer.svelte | 9 +-- .../photos-page/actions/add-to-album.svelte | 31 ++------- .../album-selection-modal.svelte | 5 +- .../notification/notification-card.svelte | 40 ++++++++++-- .../notification/notification.ts | 19 +++++- web/src/lib/utils/asset-utils.ts | 65 +++++++++++++++---- web/src/lib/utils/string-utils.ts | 16 +++++ 8 files changed, 134 insertions(+), 54 deletions(-) create mode 100644 web/src/lib/utils/string-utils.ts diff --git a/web/src/lib/components/asset-viewer/album-list-item.svelte b/web/src/lib/components/asset-viewer/album-list-item.svelte index f2cb181e4..d97a40ae4 100644 --- a/web/src/lib/components/asset-viewer/album-list-item.svelte +++ b/web/src/lib/components/asset-viewer/album-list-item.svelte @@ -2,6 +2,7 @@ import { getAssetThumbnailUrl } from '$lib/utils'; import { ThumbnailFormat, type AlbumResponseDto } from '@immich/sdk'; import { createEventDispatcher } from 'svelte'; + import { normalizeSearchString } from '$lib/utils/string-utils.js'; const dispatch = createEventDispatcher<{ album: void; @@ -16,7 +17,7 @@ // It is used to highlight the search query in the album name $: { let { albumName } = album; - let findIndex = albumName.toLowerCase().indexOf(searchQuery.toLowerCase()); + let findIndex = normalizeSearchString(albumName).indexOf(normalizeSearchString(searchQuery)); let findLength = searchQuery.length; albumNameArray = [ albumName.slice(0, findIndex), diff --git a/web/src/lib/components/asset-viewer/asset-viewer.svelte b/web/src/lib/components/asset-viewer/asset-viewer.svelte index 51da30411..be5d13e5f 100644 --- a/web/src/lib/components/asset-viewer/asset-viewer.svelte +++ b/web/src/lib/components/asset-viewer/asset-viewer.svelte @@ -1,7 +1,6 @@ @@ -55,7 +67,7 @@ transition:fade={{ duration: 250 }} style:background-color={backgroundColor[notification.type]} style:border-color={borderColor[notification.type]} - class="border z-[999999] mb-4 min-h-[80px] w-[300px] rounded-2xl p-4 shadow-md hover:cursor-pointer" + class="border z-[999999] mb-4 min-h-[80px] w-[300px] rounded-2xl p-4 shadow-md {hoverStyle}" on:click={handleClick} on:keydown={handleClick} > @@ -72,6 +84,22 @@

- {notification.message} + {#if notification.html} + + {@html notification.message} + {:else} + {notification.message} + {/if}

+ + {#if notification.button} +

+ +

+ {/if} diff --git a/web/src/lib/components/shared-components/notification/notification.ts b/web/src/lib/components/shared-components/notification/notification.ts index 52e75bf41..dfe4e1f92 100644 --- a/web/src/lib/components/shared-components/notification/notification.ts +++ b/web/src/lib/components/shared-components/notification/notification.ts @@ -6,20 +6,31 @@ export enum NotificationType { Warning = 'Warning', } +export type NotificationButton = { + text: string; + onClick: () => unknown; +}; + export type Notification = { id: number; type: NotificationType; message: string; + /** + * Allow HTML to be inserted within the message. Make sure to verify/encode + * variables that may be interpoalted into 'message' + */ + html?: boolean; /** The action to take when the notification is clicked */ action: NotificationAction; + button?: NotificationButton; /** Timeout in miliseconds */ timeout: number; }; type DiscardAction = { type: 'discard' }; type NoopAction = { type: 'noop' }; -type LinkAction = { type: 'link'; target: string }; -export type NotificationAction = DiscardAction | NoopAction | LinkAction; + +export type NotificationAction = DiscardAction | NoopAction; export type NotificationOptions = Partial> & { message: string }; @@ -32,7 +43,9 @@ function createNotificationList() { currentList.push({ id: count++, type: NotificationType.Info, - action: { type: 'discard' }, + action: { + type: options.button ? 'noop' : 'discard', + }, timeout: 3000, ...options, }); diff --git a/web/src/lib/utils/asset-utils.ts b/web/src/lib/utils/asset-utils.ts index 27c99a473..532479912 100644 --- a/web/src/lib/utils/asset-utils.ts +++ b/web/src/lib/utils/asset-utils.ts @@ -1,15 +1,18 @@ -import { notificationController, NotificationType } from '$lib/components/shared-components/notification/notification'; +import { goto } from '$app/navigation'; +import { NotificationType, notificationController } from '$lib/components/shared-components/notification/notification'; +import { AppRoute } from '$lib/constants'; import type { AssetInteractionStore } from '$lib/stores/asset-interaction.store'; import { BucketPosition, isSelectingAllAssets, type AssetStore } from '$lib/stores/assets.store'; import { downloadManager } from '$lib/stores/download'; import { downloadRequest, getKey } from '$lib/utils'; +import { encodeHTMLSpecialChars } from '$lib/utils/string-utils'; import { addAssetsToAlbum as addAssets, + createAlbum, defaults, getDownloadInfo, type AssetResponseDto, type AssetTypeEnum, - type BulkIdResponseDto, type DownloadInfoDto, type DownloadResponseDto, type UserResponseDto, @@ -18,20 +21,60 @@ import { DateTime } from 'luxon'; import { get } from 'svelte/store'; import { handleError } from './handle-error'; -export const addAssetsToAlbum = async (albumId: string, assetIds: Array): Promise => - addAssets({ +export const addAssetsToAlbum = async (albumId: string, assetIds: string[]) => { + const result = await addAssets({ id: albumId, - bulkIdsDto: { ids: assetIds }, + bulkIdsDto: { + ids: assetIds, + }, key: getKey(), - }).then((results) => { - const count = results.filter(({ success }) => success).length; + }); + const count = result.filter(({ success }) => success).length; + notificationController.show({ + type: NotificationType.Info, + timeout: 5000, + message: + count > 0 + ? `Added ${count} asset${count === 1 ? '' : 's'} to the album` + : `Asset${assetIds.length === 1 ? ' was' : 's were'} already part of the album`, + button: { + text: 'View Album', + onClick() { + return goto(`${AppRoute.ALBUMS}/${albumId}`); + }, + }, + }); +}; + +export const addAssetsToNewAlbum = async (albumName: string, assetIds: string[]) => { + try { + const album = await createAlbum({ + createAlbumDto: { + albumName, + assetIds, + }, + }); + const displayName = albumName ? `${encodeHTMLSpecialChars(albumName)}` : 'new album'; notificationController.show({ type: NotificationType.Info, - message: `Added ${count} asset${count === 1 ? '' : 's'}`, + timeout: 5000, + message: `Added ${assetIds.length} asset${assetIds.length === 1 ? '' : 's'} to ${displayName}`, + html: true, + button: { + text: 'View Album', + onClick() { + return goto(`${AppRoute.ALBUMS}/${album.id}`); + }, + }, }); - - return results; - }); + return album; + } catch { + notificationController.show({ + type: NotificationType.Error, + message: 'Failed to create album', + }); + } +}; export const downloadBlob = (data: Blob, filename: string) => { const url = URL.createObjectURL(data); diff --git a/web/src/lib/utils/string-utils.ts b/web/src/lib/utils/string-utils.ts new file mode 100644 index 000000000..b58f859f6 --- /dev/null +++ b/web/src/lib/utils/string-utils.ts @@ -0,0 +1,16 @@ +export const removeAccents = (str: string) => { + return str.normalize('NFD').replaceAll(/[\u0300-\u036F]/g, ''); +}; + +export const normalizeSearchString = (str: string) => { + return removeAccents(str.toLocaleLowerCase()); +}; + +export const encodeHTMLSpecialChars = (str: string) => { + return str + .replaceAll('&', '&') + .replaceAll('<', '<') + .replaceAll('>', '>') + .replaceAll('"', '"') + .replaceAll("'", '''); +}; From 13b11a39a92db748d7c2e1028c2b9d7c7bdfbbe0 Mon Sep 17 00:00:00 2001 From: Sam Holton Date: Wed, 27 Mar 2024 15:58:38 -0400 Subject: [PATCH 04/18] feat(web) add filter when viewing all people in search box (#7997) * feat(web) add filter when viewing all people in search box * chore: svelte check * pr feedback: fix vertical spacing to eliminate jump when filter appears * pr feedback * simplify filter logic --------- Co-authored-by: Alex Tran --- .../lib/components/elements/search-bar.svelte | 2 +- .../search-bar/search-people-section.svelte | 17 ++++++++++++++--- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/web/src/lib/components/elements/search-bar.svelte b/web/src/lib/components/elements/search-bar.svelte index 21d36c4a2..fb301b65f 100644 --- a/web/src/lib/components/elements/search-bar.svelte +++ b/web/src/lib/components/elements/search-bar.svelte @@ -23,7 +23,7 @@ ? 'rounded-2xl' : 'rounded-t-lg'} bg-gray-200 p-2 dark:bg-immich-dark-gray gap-2 place-items-center h-full" > - @@ -254,7 +254,7 @@ {#if nextMemory}

UP NEXT

-

{nextMemory.title}

+

{memoryLaneTitle(nextMemory.yearsAgo)}

{/if} diff --git a/web/src/lib/components/photos-page/memory-lane.svelte b/web/src/lib/components/photos-page/memory-lane.svelte index 6faa41362..e481d8fd3 100644 --- a/web/src/lib/components/photos-page/memory-lane.svelte +++ b/web/src/lib/components/photos-page/memory-lane.svelte @@ -3,7 +3,7 @@ import Icon from '$lib/components/elements/icon.svelte'; import { AppRoute, QueryParameter } from '$lib/constants'; import { memoryStore } from '$lib/stores/memory.store'; - import { getAssetThumbnailUrl } from '$lib/utils'; + import { getAssetThumbnailUrl, memoryLaneTitle } from '$lib/utils'; import { getAltText } from '$lib/utils/thumbnail-util'; import { ThumbnailFormat, getMemoryLane } from '@immich/sdk'; import { mdiChevronLeft, mdiChevronRight } from '@mdi/js'; @@ -66,7 +66,7 @@ {/if}
- {#each $memoryStore as memory, index (memory.title)} + {#each $memoryStore as memory, index (memory.yearsAgo)}