From 5fc1d430122131b6916155ca7a1e5c07c1a8754b Mon Sep 17 00:00:00 2001 From: ItsJustRuby <3r4o02hucyw7o9fekpx8gla2@pollination.email> Date: Wed, 14 Feb 2024 09:48:59 +0100 Subject: [PATCH 001/194] chore(web,mobile): Fix reoccurring typo (#7111) --- install.sh | 2 +- .../home/providers/upload_profile_image.provider.dart | 2 +- web/src/routes/(user)/people/+page.svelte | 10 +++++----- web/src/routes/(user)/people/[personId]/+page.svelte | 6 +++--- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/install.sh b/install.sh index 6b266ef925..8f8d3b0c18 100755 --- a/install.sh +++ b/install.sh @@ -59,7 +59,7 @@ start_docker_compose() { } show_friendly_message() { - echo "Succesfully deployed Immich!" + echo "Successfully deployed Immich!" echo "You can access the website at http://$ip_address:2283 and the server URL for the mobile app is http://$ip_address:2283/api" echo "The library location is $upload_location" echo "---------------------------------------------------" diff --git a/mobile/lib/modules/home/providers/upload_profile_image.provider.dart b/mobile/lib/modules/home/providers/upload_profile_image.provider.dart index 66060da383..a0dc925902 100644 --- a/mobile/lib/modules/home/providers/upload_profile_image.provider.dart +++ b/mobile/lib/modules/home/providers/upload_profile_image.provider.dart @@ -88,7 +88,7 @@ class UploadProfileImageNotifier var res = await _userSErvice.uploadProfileImage(file); if (res != null) { - debugPrint("Succesfully upload profile image"); + debugPrint("Successfully upload profile image"); state = state.copyWith( status: UploadProfileStatus.success, profileImagePath: res.profileImagePath, diff --git a/web/src/routes/(user)/people/+page.svelte b/web/src/routes/(user)/people/+page.svelte index 004742946c..53785ed4f8 100644 --- a/web/src/routes/(user)/people/+page.svelte +++ b/web/src/routes/(user)/people/+page.svelte @@ -199,7 +199,7 @@ people = people.map((person: PersonResponseDto) => (person.id === personToBeMergedIn.id ? mergedPerson : person)); notificationController.show({ - message: 'Merge people succesfully', + message: 'Merge people successfully', type: NotificationType.Info, }); } catch (error) { @@ -222,7 +222,7 @@ } } notificationController.show({ - message: 'Change name succesfully', + message: 'Change name successfully', type: NotificationType.Info, }); @@ -267,7 +267,7 @@ showChangeNameModal = false; notificationController.show({ - message: 'Changed visibility succesfully', + message: 'Changed visibility successfully', type: NotificationType.Info, }); } catch (error) { @@ -365,7 +365,7 @@ return person; }); notificationController.show({ - message: 'Date of birth saved succesfully', + message: 'Date of birth saved successfully', type: NotificationType.Info, }); } catch (error) { @@ -392,7 +392,7 @@ return person; }); notificationController.show({ - message: 'Change name succesfully', + message: 'Change name successfully', type: NotificationType.Info, }); } catch (error) { diff --git a/web/src/routes/(user)/people/[personId]/+page.svelte b/web/src/routes/(user)/people/[personId]/+page.svelte index c6cac370c7..8821daaf4c 100644 --- a/web/src/routes/(user)/people/[personId]/+page.svelte +++ b/web/src/routes/(user)/people/[personId]/+page.svelte @@ -221,7 +221,7 @@ }); notificationController.show({ - message: 'Changed visibility succesfully', + message: 'Changed visibility successfully', type: NotificationType.Info, }); @@ -275,7 +275,7 @@ mergePersonDto: { ids: [personToMerge.id] }, }); notificationController.show({ - message: 'Merge people succesfully', + message: 'Merge people successfully', type: NotificationType.Info, }); people = people.filter((person: PersonResponseDto) => person.id !== personToMerge.id); @@ -311,7 +311,7 @@ }); notificationController.show({ - message: 'Change name succesfully', + message: 'Change name successfully', type: NotificationType.Info, }); } catch (error) { From d8631a00bb3fd99eab3ba78943bb0514d38f731d Mon Sep 17 00:00:00 2001 From: Jason Rasmussen Date: Wed, 14 Feb 2024 08:09:49 -0500 Subject: [PATCH 002/194] refactor(web) open api client (#7103) * refactor: person api * refactor: shared link and others --- web/src/api/api.ts | 122 +----------------- web/src/api/utils.ts | 2 +- .../admin-page/jobs/job-tile.svelte | 11 +- .../admin-page/jobs/jobs-panel.svelte | 27 ++-- .../server-stats/server-stats-panel.svelte | 10 +- .../admin-page/settings/admin-settings.ts | 2 +- .../settings/ffmpeg/ffmpeg-settings.svelte | 27 ++-- .../settings/job-settings/job-settings.svelte | 8 +- .../library-settings/library-settings.svelte | 2 +- .../logging-settings/logging-settings.svelte | 3 +- .../machine-learning-settings.svelte | 2 +- .../settings/map-settings/map-settings.svelte | 2 +- .../new-version-check-settings.svelte | 2 +- .../settings/oauth/oauth-settings.svelte | 2 +- .../password-login-settings.svelte | 2 +- .../settings/server/server-settings.svelte | 6 +- .../supported-datetime-panel.svelte | 2 +- .../settings/theme/theme-settings.svelte | 2 +- .../thumbnail/thumbnail-settings.svelte | 3 +- .../trash-settings/trash-settings.svelte | 2 +- .../components/album-page/album-card.svelte | 3 +- .../lib/components/album-page/album-card.ts | 2 +- .../album-page/share-info-modal.svelte | 2 +- .../album-page/user-selection-modal.svelte | 12 +- .../asset-viewer/activity-viewer.svelte | 17 +-- .../asset-viewer/album-list-item.svelte | 5 +- .../asset-viewer/asset-viewer-nav-bar.svelte | 11 +- .../asset-viewer/asset-viewer.svelte | 22 ++-- .../asset-viewer/detail-panel.svelte | 75 +++++------ .../asset-viewer/panorama-viewer.svelte | 3 +- .../asset-viewer/photo-viewer.svelte | 23 ++-- .../asset-viewer/video-viewer.svelte | 13 +- .../assets/thumbnail/thumbnail.svelte | 25 ++-- .../faces-page/assign-face-side-panel.svelte | 26 ++-- .../faces-page/face-thumbnail.svelte | 13 +- .../faces-page/merge-face-selector.svelte | 31 ++--- .../faces-page/merge-suggestion-modal.svelte | 13 +- .../components/faces-page/people-card.svelte | 13 +- .../components/faces-page/people-list.svelte | 12 +- .../faces-page/person-side-panel.svelte | 33 ++--- .../faces-page/unmerge-face-selector.svelte | 44 +++---- .../memory-page/memory-viewer.svelte | 32 ++--- .../photos-page/actions/archive-action.svelte | 8 +- .../actions/asset-job-actions.svelte | 10 +- .../actions/change-date-action.svelte | 10 +- .../actions/change-location-action.svelte | 16 +-- .../actions/favorite-action.svelte | 10 +- .../actions/remove-from-shared-link.svelte | 16 ++- .../photos-page/actions/stack-action.svelte | 8 +- .../components/photos-page/memory-lane.svelte | 21 ++- .../individual-shared-viewer.svelte | 20 +-- .../create-shared-link-modal.svelte | 17 +-- .../shared-components/map/map.svelte | 6 +- .../navigation-bar/account-info-panel.svelte | 5 +- .../search-bar/search-filter-box.svelte | 24 ++-- .../side-bar/side-bar.svelte | 9 +- .../shared-components/user-avatar.svelte | 5 +- .../sharedlinks-page/shared-link-card.svelte | 22 ++-- web/src/lib/stores/asset-viewing.store.ts | 6 +- web/src/lib/stores/assets.store.ts | 18 +-- web/src/lib/utils.ts | 90 +++++++++++++ web/src/lib/utils/actions.ts | 4 +- web/src/lib/utils/asset-utils.ts | 11 +- web/src/lib/utils/file-uploader.ts | 3 +- web/src/routes/(user)/explore/+page.svelte | 5 +- web/src/routes/(user)/explore/+page.ts | 6 +- web/src/routes/(user)/map/+page.svelte | 10 +- web/src/routes/(user)/people/+page.svelte | 76 +++++------ web/src/routes/(user)/people/+page.ts | 4 +- .../(user)/people/[personId]/+page.svelte | 62 ++++----- .../routes/(user)/people/[personId]/+page.ts | 8 +- web/src/routes/(user)/places/+page.ts | 4 +- web/src/routes/(user)/search/+page.svelte | 13 +- web/src/routes/(user)/search/+page.ts | 12 +- .../routes/(user)/share/[key]/+page.svelte | 12 +- web/src/routes/(user)/share/[key]/+page.ts | 13 +- .../share/[key]/photos/[assetId]/+page.ts | 4 +- .../(user)/sharing/sharedlinks/+page.svelte | 22 ++-- web/src/routes/+layout.svelte | 30 ++--- web/src/routes/admin/jobs-status/+page.svelte | 3 +- web/src/test-data/factories/album-factory.ts | 2 +- 81 files changed, 638 insertions(+), 656 deletions(-) diff --git a/web/src/api/api.ts b/web/src/api/api.ts index c11ac66534..53340cb455 100644 --- a/web/src/api/api.ts +++ b/web/src/api/api.ts @@ -1,135 +1,15 @@ -import { - AssetApi, - AssetApiFp, - AssetJobName, - DownloadApi, - JobName, - PersonApi, - SearchApi, - SharedLinkApi, - UserApiFp, - base, - common, - configuration, -} from '@immich/sdk/axios'; -import type { ApiParams as ApiParameters } from './types'; +import { AssetApi, DownloadApi, configuration } from '@immich/sdk/axios'; class ImmichApi { public downloadApi: DownloadApi; public assetApi: AssetApi; - public searchApi: SearchApi; - public sharedLinkApi: SharedLinkApi; - public personApi: PersonApi; private config: configuration.Configuration; - private key?: string; - - get isSharedLink() { - return !!this.key; - } constructor(parameters: configuration.ConfigurationParameters) { this.config = new configuration.Configuration(parameters); - this.downloadApi = new DownloadApi(this.config); this.assetApi = new AssetApi(this.config); - this.searchApi = new SearchApi(this.config); - this.sharedLinkApi = new SharedLinkApi(this.config); - this.personApi = new PersonApi(this.config); - } - - private createUrl(path: string, parameters?: Record) { - const searchParameters = new URLSearchParams(); - for (const key in parameters) { - const value = parameters[key]; - if (value !== undefined && value !== null) { - searchParameters.set(key, value.toString()); - } - } - - const url = new URL(path, common.DUMMY_BASE_URL); - url.search = searchParameters.toString(); - - return (this.config.basePath || base.BASE_PATH) + common.toPathString(url); - } - - public setKey(key: string) { - this.key = key; - } - - public getKey(): string | undefined { - return this.key; - } - - public setAccessToken(accessToken: string) { - this.config.accessToken = accessToken; - } - - public removeAccessToken() { - this.config.accessToken = undefined; - } - - public setBaseUrl(baseUrl: string) { - this.config.basePath = baseUrl; - } - - public getAssetFileUrl(...[assetId, isThumb, isWeb]: ApiParameters) { - const path = `/asset/file/${assetId}`; - return this.createUrl(path, { isThumb, isWeb, key: this.getKey() }); - } - - public getAssetThumbnailUrl(...[assetId, format]: ApiParameters) { - const path = `/asset/thumbnail/${assetId}`; - return this.createUrl(path, { format, key: this.getKey() }); - } - - public getProfileImageUrl(...[userId]: ApiParameters) { - const path = `/user/profile-image/${userId}`; - return this.createUrl(path); - } - - public getPeopleThumbnailUrl(personId: string) { - const path = `/person/${personId}/thumbnail`; - return this.createUrl(path); - } - - public getJobName(jobName: JobName) { - const names: Record = { - [JobName.ThumbnailGeneration]: 'Generate Thumbnails', - [JobName.MetadataExtraction]: 'Extract Metadata', - [JobName.Sidecar]: 'Sidecar Metadata', - [JobName.SmartSearch]: 'Smart Search', - [JobName.FaceDetection]: 'Face Detection', - [JobName.FacialRecognition]: 'Facial Recognition', - [JobName.VideoConversion]: 'Transcode Videos', - [JobName.StorageTemplateMigration]: 'Storage Template Migration', - [JobName.Migration]: 'Migration', - [JobName.BackgroundTask]: 'Background Tasks', - [JobName.Search]: 'Search', - [JobName.Library]: 'Library', - }; - - return names[jobName]; - } - - public getAssetJobName(job: AssetJobName) { - const names: Record = { - [AssetJobName.RefreshMetadata]: 'Refresh metadata', - [AssetJobName.RegenerateThumbnail]: 'Refresh thumbnails', - [AssetJobName.TranscodeVideo]: 'Refresh encoded videos', - }; - - return names[job]; - } - - public getAssetJobMessage(job: AssetJobName) { - const messages: Record = { - [AssetJobName.RefreshMetadata]: 'Refreshing metadata', - [AssetJobName.RegenerateThumbnail]: `Regenerating thumbnails`, - [AssetJobName.TranscodeVideo]: `Refreshing encoded video`, - }; - - return messages[job]; } } diff --git a/web/src/api/utils.ts b/web/src/api/utils.ts index 0367e7905a..decfefc560 100644 --- a/web/src/api/utils.ts +++ b/web/src/api/utils.ts @@ -1,5 +1,5 @@ import { finishOAuth, linkOAuthAccount, startOAuth, unlinkOAuthAccount } from '@immich/sdk'; -import type { UserResponseDto } from '@immich/sdk/axios'; +import { type UserResponseDto } from '@immich/sdk/axios'; import type { AxiosError } from 'axios'; import { NotificationType, diff --git a/web/src/lib/components/admin-page/jobs/job-tile.svelte b/web/src/lib/components/admin-page/jobs/job-tile.svelte index 75d8ab6b81..42f489d41d 100644 --- a/web/src/lib/components/admin-page/jobs/job-tile.svelte +++ b/web/src/lib/components/admin-page/jobs/job-tile.svelte @@ -1,12 +1,10 @@ diff --git a/web/src/lib/components/admin-page/settings/logging-settings/logging-settings.svelte b/web/src/lib/components/admin-page/settings/logging-settings/logging-settings.svelte index 85ea60545c..f29e6f9eaf 100644 --- a/web/src/lib/components/admin-page/settings/logging-settings/logging-settings.svelte +++ b/web/src/lib/components/admin-page/settings/logging-settings/logging-settings.svelte @@ -1,6 +1,5 @@ diff --git a/web/src/lib/components/shared-components/version-announcement-box.svelte b/web/src/lib/components/shared-components/version-announcement-box.svelte index 93ba1d7a4b..1c58a37286 100644 --- a/web/src/lib/components/shared-components/version-announcement-box.svelte +++ b/web/src/lib/components/shared-components/version-announcement-box.svelte @@ -6,13 +6,13 @@ let showModal = false; - const { onRelease } = websocketStore; + const { release } = websocketStore; const semverToName = ({ major, minor, patch }: ServerVersionResponseDto) => `v${major}.${minor}.${patch}`; - $: releaseVersion = $onRelease && semverToName($onRelease.releaseVersion); - $: serverVersion = $onRelease && semverToName($onRelease.serverVersion); - $: $onRelease?.isAvailable && handleRelease(); + $: releaseVersion = $release && semverToName($release.releaseVersion); + $: serverVersion = $release && semverToName($release.serverVersion); + $: $release?.isAvailable && handleRelease(); const onAcknowledge = () => { localStorage.setItem('appVersion', releaseVersion); diff --git a/web/src/lib/stores/assets.store.ts b/web/src/lib/stores/assets.store.ts index 3000a64b06..f6f2219703 100644 --- a/web/src/lib/stores/assets.store.ts +++ b/web/src/lib/stores/assets.store.ts @@ -4,7 +4,7 @@ import { throttle } from 'lodash-es'; import { DateTime } from 'luxon'; import { writable, type Unsubscriber } from 'svelte/store'; import { handleError } from '../utils/handle-error'; -import { websocketStore } from './websocket'; +import { websocketEvents } from './websocket'; export enum BucketPosition { Above = 'above', @@ -96,22 +96,14 @@ export class AssetStore { connect() { this.unsubscribers.push( - websocketStore.onUploadSuccess.subscribe((value) => { - if (value) { - this.addPendingChanges({ type: 'add', value }); - } + websocketEvents.on('on_upload_success', (asset) => { + this.addPendingChanges({ type: 'add', value: asset }); }), - - websocketStore.onAssetTrash.subscribe((ids) => { - if (ids) { - this.addPendingChanges(...ids.map((id) => ({ type: 'trash', value: id }) as PendingChange)); - } + websocketEvents.on('on_asset_trash', (ids) => { + this.addPendingChanges(...ids.map((id): TrashAsset => ({ type: 'trash', value: id }))); }), - - websocketStore.onAssetDelete.subscribe((value) => { - if (value) { - this.addPendingChanges({ type: 'delete', value }); - } + websocketEvents.on('on_asset_delete', (id: string) => { + this.addPendingChanges({ type: 'delete', value: id }); }), ); } diff --git a/web/src/lib/stores/websocket.ts b/web/src/lib/stores/websocket.ts index 71ba4354f0..33748f35fe 100644 --- a/web/src/lib/stores/websocket.ts +++ b/web/src/lib/stores/websocket.ts @@ -1,7 +1,7 @@ +import { createEventEmitter } from '$lib/utils/eventemitter'; import type { AssetResponseDto, ServerVersionResponseDto } from '@immich/sdk'; import { io, type Socket } from 'socket.io-client'; import { get, writable } from 'svelte/store'; -import { loadConfig } from './server-config.store'; import { user } from './user.store'; export interface ReleaseEvent { @@ -10,58 +10,54 @@ export interface ReleaseEvent { serverVersion: ServerVersionResponseDto; releaseVersion: ServerVersionResponseDto; } +export interface Events { + on_upload_success: (asset: AssetResponseDto) => void; + on_asset_delete: (assetId: string) => void; + on_asset_trash: (assetIds: string[]) => void; + on_asset_update: (asset: AssetResponseDto) => void; + on_asset_hidden: (assetId: string) => void; + on_asset_restore: (assetIds: string[]) => void; + on_person_thumbnail: (personId: string) => void; + on_server_version: (serverVersion: ServerVersionResponseDto) => void; + on_config_update: () => void; + on_new_release: (newRelase: ReleaseEvent) => void; +} + +const websocket: Socket = io('', { + path: '/api/socket.io', + transports: ['websocket'], + reconnection: true, + forceNew: true, + autoConnect: false, +}); export const websocketStore = { - onUploadSuccess: writable(), - onAssetDelete: writable(), - onAssetTrash: writable(), - onAssetUpdate: writable(), - onPersonThumbnail: writable(), - serverVersion: writable(), connected: writable(false), - onRelease: writable(), + serverVersion: writable(), + release: writable(), }; -let websocket: Socket | null = null; +export const websocketEvents = createEventEmitter(websocket); + +websocket + .on('connect', () => websocketStore.connected.set(true)) + .on('disconnect', () => websocketStore.connected.set(false)) + .on('on_server_version', (serverVersion) => websocketStore.serverVersion.set(serverVersion)) + .on('on_new_release', (releaseVersion) => websocketStore.release.set(releaseVersion)) + .on('connect_error', (e) => console.log('Websocket Connect Error', e)); export const openWebsocketConnection = async () => { try { - if (websocket) { - return; - } - if (!get(user)) { return; } - websocket = io('', { - path: '/api/socket.io', - transports: ['websocket'], - reconnection: true, - forceNew: true, - autoConnect: true, - }); - - websocket - .on('connect', () => websocketStore.connected.set(true)) - .on('disconnect', () => websocketStore.connected.set(false)) - // .on('on_upload_success', (data) => websocketStore.onUploadSuccess.set(data)) - .on('on_asset_delete', (data) => websocketStore.onAssetDelete.set(data)) - .on('on_asset_trash', (data) => websocketStore.onAssetTrash.set(data)) - .on('on_asset_update', (data) => websocketStore.onAssetUpdate.set(data)) - .on('on_person_thumbnail', (data) => websocketStore.onPersonThumbnail.set(data)) - .on('on_server_version', (data) => websocketStore.serverVersion.set(data)) - .on('on_config_update', () => loadConfig()) - .on('on_new_release', (data) => websocketStore.onRelease.set(data)) - .on('error', (e) => console.log('Websocket Error', e)); + websocket.connect(); } catch (error) { console.log('Cannot connect to websocket', error); } }; export const closeWebsocketConnection = () => { - if (websocket) { - websocket.close(); - } - websocket = null; + websocket.disconnect(); }; diff --git a/web/src/lib/utils/eventemitter.ts b/web/src/lib/utils/eventemitter.ts new file mode 100644 index 0000000000..35d8eecf87 --- /dev/null +++ b/web/src/lib/utils/eventemitter.ts @@ -0,0 +1,42 @@ +import type { + DefaultEventsMap, + EventsMap, + ReservedOrUserEventNames, + ReservedOrUserListener, +} from '@socket.io/component-emitter'; +import type { Socket } from 'socket.io-client'; + +export function createEventEmitter< + ListenEvents extends EventsMap = DefaultEventsMap, + EmitEvents extends EventsMap = ListenEvents, + ReservedEvents extends EventsMap = NonNullable, +>(socket: Socket) { + function on>( + ev: Ev, + listener: ReservedOrUserListener, + ) { + socket.on(ev, listener); + return () => { + socket.off(ev, listener); + }; + } + + function once>( + ev: Ev, + listener: ReservedOrUserListener, + ) { + socket.once(ev, listener); + return () => { + socket.off(ev, listener); + }; + } + + function off>( + ev: Ev, + listener: ReservedOrUserListener, + ) { + socket.off(ev, listener); + } + + return { on, once, off }; +} diff --git a/web/src/routes/(user)/people/[personId]/+page.svelte b/web/src/routes/(user)/people/[personId]/+page.svelte index babaca1069..64381677f0 100644 --- a/web/src/routes/(user)/people/[personId]/+page.svelte +++ b/web/src/routes/(user)/people/[personId]/+page.svelte @@ -30,7 +30,7 @@ import { createAssetInteractionStore } from '$lib/stores/asset-interaction.store'; import { assetViewingStore } from '$lib/stores/asset-viewing.store'; import { AssetStore } from '$lib/stores/assets.store'; - import { websocketStore } from '$lib/stores/websocket'; + import { websocketEvents } from '$lib/stores/websocket'; import { getPeopleThumbnailUrl } from '$lib/utils'; import { clickOutside } from '$lib/utils/click-outside'; import { handleError } from '$lib/utils/handle-error'; @@ -68,7 +68,6 @@ }); const assetInteractionStore = createAssetInteractionStore(); const { selectedAssets, isMultiSelectState } = assetInteractionStore; - const { onPersonThumbnail } = websocketStore; let viewMode: ViewMode = ViewMode.VIEW_ASSETS; let isEditingName = false; @@ -119,8 +118,6 @@ $: isAllArchive = [...$selectedAssets].every((asset) => asset.isArchived); $: isAllFavorite = [...$selectedAssets].every((asset) => asset.isFavorite); - $: $onPersonThumbnail === data.person.id && - (thumbnailData = getPeopleThumbnailUrl(data.person.id) + `?now=${Date.now()}`); $: { if (people) { @@ -138,6 +135,12 @@ if (action == 'merge') { viewMode = ViewMode.MERGE_PEOPLE; } + + return websocketEvents.on('on_person_thumbnail', (personId: string) => { + if (data.person.id === personId) { + thumbnailData = getPeopleThumbnailUrl(data.person.id) + `?now=${Date.now()}`; + } + }); }); const handleKeyboardPress = (event: KeyboardEvent) => { From 67b1675850cd1a2bc5134eb052978c6e47070532 Mon Sep 17 00:00:00 2001 From: Jan <17313367+JW-CH@users.noreply.github.com> Date: Fri, 16 Feb 2024 22:01:44 +0100 Subject: [PATCH 019/194] fix(web) display wrong apikey-name on edit (#7131) * fix display wrong apikey-name on edit * use apiKey property with fallback value * remove null fallback * chore: cleanup --------- Co-authored-by: Jason Rasmussen --- web/src/lib/components/forms/api-key-form.svelte | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/web/src/lib/components/forms/api-key-form.svelte b/web/src/lib/components/forms/api-key-form.svelte index 53283d9b91..62034d651c 100644 --- a/web/src/lib/components/forms/api-key-form.svelte +++ b/web/src/lib/components/forms/api-key-form.svelte @@ -11,7 +11,6 @@ export let title = 'API Key'; export let cancelText = 'Cancel'; export let submitText = 'Save'; - export let apiKeyName = 'API Key'; const dispatch = createEventDispatcher<{ cancel: void; @@ -19,8 +18,8 @@ }>(); const handleCancel = () => dispatch('cancel'); const handleSubmit = () => { - if (apiKeyName) { - dispatch('submit', { ...apiKey, name: apiKeyName }); + if (apiKey.name) { + dispatch('submit', apiKey); } else { notificationController.show({ message: "Your API Key name shouldn't be empty", @@ -46,7 +45,7 @@
- +
From a24f3805c97f1ed992c24d1efa04ad3d1cf16f06 Mon Sep 17 00:00:00 2001 From: Jason Rasmussen Date: Fri, 16 Feb 2024 16:31:22 -0500 Subject: [PATCH 020/194] chore: web e2e improvements (#7155) --- e2e/specs/auth.e2e-spec.ts | 2 +- e2e/test-utils.ts | 23 +++++++++++++------ web/src/lib/constants.ts | 1 - web/src/lib/utils/handle-error.ts | 2 +- .../routes/auth/change-password/+page.svelte | 8 +------ 5 files changed, 19 insertions(+), 17 deletions(-) diff --git a/e2e/specs/auth.e2e-spec.ts b/e2e/specs/auth.e2e-spec.ts index 4c55d67ac1..134e0241d8 100644 --- a/e2e/specs/auth.e2e-spec.ts +++ b/e2e/specs/auth.e2e-spec.ts @@ -2,7 +2,7 @@ import { test, expect } from '@playwright/test'; import { app } from '../test-utils'; test.describe('Registration', () => { - test.beforeAll(async () => { + test.beforeEach(async () => { await app.reset(); }); diff --git a/e2e/test-utils.ts b/e2e/test-utils.ts index f0d13be816..41ef01a3e7 100644 --- a/e2e/test-utils.ts +++ b/e2e/test-utils.ts @@ -63,17 +63,26 @@ export const app = { return response; }, reset: async () => { - if (!connected) { - await client.connect(); - } + try { + if (!connected) { + await client.connect(); + connected = true; + } - for (const table of ['users', 'system_metadata']) { - await client.query(`DELETE FROM ${table} CASCADE;`); + for (const table of ['user_token', 'users', 'system_metadata']) { + await client.query(`DELETE FROM ${table} CASCADE;`); + } + } catch (error) { + console.error('Failed to reset database', error); } }, teardown: async () => { - if (connected) { - await client.end(); + try { + if (connected) { + await client.end(); + } + } catch (error) { + console.error('Failed to teardown database', error); } }, }; diff --git a/web/src/lib/constants.ts b/web/src/lib/constants.ts index 295bd99433..83f6476ba7 100644 --- a/web/src/lib/constants.ts +++ b/web/src/lib/constants.ts @@ -35,7 +35,6 @@ export enum AppRoute { PARTNERS = '/partners', AUTH_LOGIN = '/auth/login', - AUTH_LOGOUT = '/auth/logout', AUTH_REGISTER = '/auth/register', AUTH_CHANGE_PASSWORD = '/auth/change-password', AUTH_ONBOARDING = '/auth/onboarding', diff --git a/web/src/lib/utils/handle-error.ts b/web/src/lib/utils/handle-error.ts index 8164f716a1..3337dbb475 100644 --- a/web/src/lib/utils/handle-error.ts +++ b/web/src/lib/utils/handle-error.ts @@ -20,7 +20,7 @@ export async function handleError(error: unknown, message: string) { return; } - console.error(`[handleError]: ${message}`, error); + console.error(`[handleError]: ${message}`, error, (error as Error)?.stack); let serverMessage = await getServerErrorMessage(error); if (serverMessage) { diff --git a/web/src/routes/auth/change-password/+page.svelte b/web/src/routes/auth/change-password/+page.svelte index 08446c8923..f56169ddb7 100644 --- a/web/src/routes/auth/change-password/+page.svelte +++ b/web/src/routes/auth/change-password/+page.svelte @@ -7,12 +7,6 @@ import type { PageData } from './$types'; export let data: PageData; - - const onSuccessHandler = async () => { - await fetch(AppRoute.AUTH_LOGOUT, { method: 'POST' }); - - goto(AppRoute.AUTH_LOGIN); - }; @@ -24,5 +18,5 @@ enter the new password below.

- + goto(AppRoute.AUTH_LOGIN)} />
From fab19a8583db295847bfda6643e3894d8bf848a1 Mon Sep 17 00:00:00 2001 From: Michel Heusschen <59014050+michelheusschen@users.noreply.github.com> Date: Sat, 17 Feb 2024 04:32:11 +0100 Subject: [PATCH 021/194] fix(server): recognize faces when min. faces is set to 1 (#7144) * fix(server): recognize face when min. faces is set to 1 * update logic * clarified log --------- Co-authored-by: mertalev <101130780+mertalev@users.noreply.github.com> --- server/src/domain/person/person.service.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/domain/person/person.service.ts b/server/src/domain/person/person.service.ts index 359084bf21..6fbc409bf8 100644 --- a/server/src/domain/person/person.service.ts +++ b/server/src/domain/person/person.service.ts @@ -410,8 +410,8 @@ export class PersonService { }); // `matches` also includes the face itself - if (matches.length <= 1) { - this.logger.debug(`Face ${id} has no matches`); + if (machineLearning.facialRecognition.minFaces > 1 && matches.length <= 1) { + this.logger.debug(`Face ${id} only matched the face itself, skipping`); return true; } From 3915867b1be2dad86fd1c713c84ac71bc479f935 Mon Sep 17 00:00:00 2001 From: Ben McCann <322311+benmccann@users.noreply.github.com> Date: Sat, 17 Feb 2024 05:35:51 -0800 Subject: [PATCH 022/194] chore: remove svelte-preprocess (#7159) --- web/package-lock.json | 15 +++++++-------- web/package.json | 9 ++++----- web/svelte.config.js | 4 ++-- 3 files changed, 13 insertions(+), 15 deletions(-) diff --git a/web/package-lock.json b/web/package-lock.json index 0177175c30..2c5c7f1d24 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -31,8 +31,8 @@ "@floating-ui/dom": "^1.5.1", "@socket.io/component-emitter": "^3.1.0", "@sveltejs/adapter-static": "^3.0.1", - "@sveltejs/kit": "^2.0.6", - "@sveltejs/vite-plugin-svelte": "^3.0.0", + "@sveltejs/kit": "^2.5.0", + "@sveltejs/vite-plugin-svelte": "^3.0.2", "@testing-library/jest-dom": "^6.1.5", "@testing-library/svelte": "^4.0.3", "@types/dom-to-image": "^2.6.4", @@ -54,9 +54,8 @@ "prettier-plugin-organize-imports": "^3.2.4", "prettier-plugin-svelte": "^3.1.2", "rollup-plugin-visualizer": "^5.12.0", - "svelte": "^4.0.5", - "svelte-check": "^3.4.3", - "svelte-preprocess": "^5.0.3", + "svelte": "^4.2.11", + "svelte-check": "^3.6.4", "tailwindcss": "^3.2.7", "tslib": "^2.5.0", "typescript": "^5.3.3", @@ -7428,9 +7427,9 @@ } }, "node_modules/svelte": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/svelte/-/svelte-4.2.10.tgz", - "integrity": "sha512-Ep06yCaCdgG1Mafb/Rx8sJ1QS3RW2I2BxGp2Ui9LBHSZ2/tO/aGLc5WqPjgiAP6KAnLJGaIr/zzwQlOo1b8MxA==", + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-4.2.11.tgz", + "integrity": "sha512-YIQk3J4X89wOLhjsqIW8tqY3JHPuBdtdOIkASP2PZeAMcSW9RsIjQzMesCrxOF3gdWYC0mKknlKF7OqmLM+Zqg==", "dependencies": { "@ampproject/remapping": "^2.2.1", "@jridgewell/sourcemap-codec": "^1.4.15", diff --git a/web/package.json b/web/package.json index 35eac08dda..50cd310e27 100644 --- a/web/package.json +++ b/web/package.json @@ -26,8 +26,8 @@ "@floating-ui/dom": "^1.5.1", "@socket.io/component-emitter": "^3.1.0", "@sveltejs/adapter-static": "^3.0.1", - "@sveltejs/kit": "^2.0.6", - "@sveltejs/vite-plugin-svelte": "^3.0.0", + "@sveltejs/kit": "^2.5.0", + "@sveltejs/vite-plugin-svelte": "^3.0.2", "@testing-library/jest-dom": "^6.1.5", "@testing-library/svelte": "^4.0.3", "@types/dom-to-image": "^2.6.4", @@ -49,9 +49,8 @@ "prettier-plugin-organize-imports": "^3.2.4", "prettier-plugin-svelte": "^3.1.2", "rollup-plugin-visualizer": "^5.12.0", - "svelte": "^4.0.5", - "svelte-check": "^3.4.3", - "svelte-preprocess": "^5.0.3", + "svelte": "^4.2.11", + "svelte-check": "^3.6.4", "tailwindcss": "^3.2.7", "tslib": "^2.5.0", "typescript": "^5.3.3", diff --git a/web/svelte.config.js b/web/svelte.config.js index c6ff761400..0081e8e76b 100644 --- a/web/svelte.config.js +++ b/web/svelte.config.js @@ -1,9 +1,9 @@ import adapter from '@sveltejs/adapter-static'; -import preprocess from 'svelte-preprocess'; +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; /** @type {import('@sveltejs/kit').Config} */ const config = { - preprocess: preprocess(), + preprocess: vitePreprocess(), onwarn: (warning, handler) => { if (warning.code.includes('a11y')) { return; From 60ba37b3a7db06d75c47efc4fbe162acc6c0ecdf Mon Sep 17 00:00:00 2001 From: Michel Heusschen <59014050+michelheusschen@users.noreply.github.com> Date: Sat, 17 Feb 2024 15:28:34 +0100 Subject: [PATCH 023/194] fix(web): validation when editing asset date & time (#7160) --- .../shared-components/change-date.svelte | 19 ++++--------------- web/tsconfig.json | 1 - 2 files changed, 4 insertions(+), 16 deletions(-) diff --git a/web/src/lib/components/shared-components/change-date.svelte b/web/src/lib/components/shared-components/change-date.svelte index bf80fee5a1..a6818ee1fd 100644 --- a/web/src/lib/components/shared-components/change-date.svelte +++ b/web/src/lib/components/shared-components/change-date.svelte @@ -1,11 +1,3 @@ - - {#if assets.length > 0} -
- {#each assets as asset, i (asset.id)} -
+
+ {#each assets as asset, i (i)} +
(isMultiSelectionMode ? selectAssetHandler(e) : viewAssetHandler(e))} on:select={selectAssetHandler} on:intersected={(event) => i === Math.max(1, assets.length - 7) ? dispatch('intersected', event.detail) : undefined} selected={selectedAssets.has(asset)} {showArchiveIcon} + thumbnailWidth={geometry.boxes[i].width} + thumbnailHeight={geometry.boxes[i].height} />
{/each} diff --git a/web/src/lib/components/shared-components/search-bar/search-bar.svelte b/web/src/lib/components/shared-components/search-bar/search-bar.svelte index 8dfba184ad..62e950566d 100644 --- a/web/src/lib/components/shared-components/search-bar/search-bar.svelte +++ b/web/src/lib/components/shared-components/search-bar/search-bar.svelte @@ -2,12 +2,18 @@ import { AppRoute } from '$lib/constants'; import Icon from '$lib/components/elements/icon.svelte'; import { goto } from '$app/navigation'; - import { isSearchEnabled, preventRaceConditionSearchBar, savedSearchTerms } from '$lib/stores/search.store'; + import { + isSearchEnabled, + preventRaceConditionSearchBar, + savedSearchTerms, + searchQuery, + } from '$lib/stores/search.store'; import { clickOutside } from '$lib/utils/click-outside'; import { mdiClose, mdiMagnify, mdiTune } from '@mdi/js'; import IconButton from '$lib/components/elements/buttons/icon-button.svelte'; import SearchHistoryBox from './search-history-box.svelte'; import SearchFilterBox from './search-filter-box.svelte'; + import type { MetadataSearchDto, SmartSearchDto } from '@immich/sdk'; export let value = ''; export let grayTheme: boolean; @@ -17,28 +23,17 @@ let showFilter = false; $: showClearIcon = value.length > 0; - function onSearch() { - let smartSearch = 'true'; - let searchValue = value; - - if (value.slice(0, 2) == 'm:') { - smartSearch = 'false'; - searchValue = value.slice(2); - } - - $savedSearchTerms = $savedSearchTerms.filter((item) => item !== value); - saveSearchTerm(value); - + const onSearch = (payload: SmartSearchDto | MetadataSearchDto) => { const parameters = new URLSearchParams({ - q: searchValue, - smart: smartSearch, - take: '100', + query: JSON.stringify(payload), }); showHistory = false; + showFilter = false; $isSearchEnabled = false; + $searchQuery = payload; goto(`${AppRoute.SEARCH}?${parameters}`, { invalidateAll: true }); - } + }; const clearSearchTerm = (searchTerm: string) => { input.focus(); @@ -70,6 +65,26 @@ showHistory = false; $isSearchEnabled = false; + showFilter = false; + }; + + const onHistoryTermClick = (searchTerm: string) => { + const searchPayload = { query: searchTerm }; + onSearch(searchPayload); + }; + + const onFilterClick = () => { + showFilter = !showFilter; + value = ''; + + if (showFilter) { + showHistory = false; + } + }; + + const onSubmit = () => { + onSearch({ query: value }); + saveSearchTerm(value); }; @@ -80,7 +95,7 @@ class="relative select-text text-sm" action={AppRoute.SEARCH} on:reset={() => (value = '')} - on:submit|preventDefault={() => onSearch()} + on:submit|preventDefault={onSubmit} >