From f64a3003af320d5dd85206289491756631488ecf Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 4 Jun 2025 21:09:53 -0500 Subject: [PATCH 01/14] chore: album's header styling (#18930) --- mobile/lib/pages/album/album_viewer.dart | 4 ++-- .../widgets/album/album_action_filled_button.dart | 12 ++++++++---- mobile/lib/widgets/album/album_viewer_appbar.dart | 1 + .../album/album_viewer_editable_description.dart | 2 +- 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/mobile/lib/pages/album/album_viewer.dart b/mobile/lib/pages/album/album_viewer.dart index f22fc30716..86b23fba30 100644 --- a/mobile/lib/pages/album/album_viewer.dart +++ b/mobile/lib/pages/album/album_viewer.dart @@ -114,9 +114,9 @@ class AlbumViewer extends HookConsumerWidget { begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ + context.primaryColor.withValues(alpha: 0.06), context.primaryColor.withValues(alpha: 0.04), - context.primaryColor.withValues(alpha: 0.02), - Colors.orange.withValues(alpha: 0.02), + Colors.indigo.withValues(alpha: 0.02), Colors.transparent, ], stops: const [0.0, 0.3, 0.7, 1.0], diff --git a/mobile/lib/widgets/album/album_action_filled_button.dart b/mobile/lib/widgets/album/album_action_filled_button.dart index f5064f499c..48a8a27f59 100644 --- a/mobile/lib/widgets/album/album_action_filled_button.dart +++ b/mobile/lib/widgets/album/album_action_filled_button.dart @@ -17,11 +17,15 @@ class AlbumActionFilledButton extends StatelessWidget { Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.only(right: 8.0), - child: FilledButton.icon( - style: FilledButton.styleFrom( + child: OutlinedButton.icon( + style: OutlinedButton.styleFrom( padding: const EdgeInsets.symmetric(vertical: 0, horizontal: 16), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(25), + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(20)), + ), + side: BorderSide( + color: context.colorScheme.surfaceContainerHighest, + width: 1, ), backgroundColor: context.colorScheme.surfaceContainerHigh, ), diff --git a/mobile/lib/widgets/album/album_viewer_appbar.dart b/mobile/lib/widgets/album/album_viewer_appbar.dart index 9d48045459..14715e40a9 100644 --- a/mobile/lib/widgets/album/album_viewer_appbar.dart +++ b/mobile/lib/widgets/album/album_viewer_appbar.dart @@ -326,6 +326,7 @@ class AlbumViewerAppbar extends HookConsumerWidget return AppBar( elevation: 0, + backgroundColor: context.scaffoldBackgroundColor, leading: buildLeadingButton(), centerTitle: false, actions: [ diff --git a/mobile/lib/widgets/album/album_viewer_editable_description.dart b/mobile/lib/widgets/album/album_viewer_editable_description.dart index 06bfbc0186..b82e7f3d83 100644 --- a/mobile/lib/widgets/album/album_viewer_editable_description.dart +++ b/mobile/lib/widgets/album/album_viewer_editable_description.dart @@ -55,7 +55,7 @@ class AlbumViewerEditableDescription extends HookConsumerWidget { } }, focusNode: descriptionFocusNode, - style: context.textTheme.bodyMedium, + style: context.textTheme.bodyLarge, maxLines: 3, minLines: 1, controller: descriptionTextEditController, From e2ffc9d5a10cb7b7c085ff88051f7c8b1f9d3e20 Mon Sep 17 00:00:00 2001 From: Min Idzelis Date: Wed, 4 Jun 2025 22:27:54 -0400 Subject: [PATCH 02/14] refactor: asset-store (#18938) * refactor: asset-store * Potential fix for code scanning alert no. 152: Prototype-polluting function Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> --------- Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> --- .../components/album-page/album-viewer.svelte | 2 +- .../components/asset-viewer/actions/action.ts | 2 +- .../actions/download-action.svelte | 2 +- .../actions/set-visibility-action.svelte | 2 +- .../asset-viewer/asset-viewer.svelte | 2 +- .../asset-viewer/photo-viewer.svelte | 3 +- .../assets/thumbnail/thumbnail.svelte | 2 +- .../memory-page/memory-viewer.svelte | 3 +- .../photos-page/actions/focus-actions.ts | 3 +- .../actions/link-live-photo-action.svelte | 2 +- .../actions/select-all-assets.svelte | 3 +- .../photos-page/asset-date-group.svelte | 13 +- .../components/photos-page/asset-grid.svelte | 12 +- .../asset-select-control-bar.svelte | 2 +- .../individual-shared-viewer.svelte | 2 +- .../gallery-viewer/gallery-viewer.svelte | 3 +- .../scrubber/scrubber.svelte | 3 +- .../timeline-manager/add-context.svelte.ts | 60 + .../timeline-manager/asset-bucket.svelte.ts | 361 ++++ .../asset-date-group.svelte.ts | 162 ++ .../timeline-manager/asset-store.svelte.ts | 934 +++++++++ .../intersecting-asset.svelte.ts | 45 + .../lib/managers/timeline-manager/types.ts | 93 + .../managers/timeline-manager/utils.svelte.ts | 34 + .../lib/stores/asset-interaction.svelte.ts | 2 +- web/src/lib/stores/asset-viewing.store.ts | 2 +- web/src/lib/stores/assets-store.spec.ts | 3 +- web/src/lib/stores/assets-store.svelte.ts | 1674 +---------------- web/src/lib/stores/memory.store.svelte.ts | 2 +- web/src/lib/utils/actions.ts | 3 +- web/src/lib/utils/asset-utils.ts | 10 +- web/src/lib/utils/layout-utils.ts | 2 +- web/src/lib/utils/thumbnail-util.spec.ts | 2 +- web/src/lib/utils/thumbnail-util.ts | 2 +- web/src/lib/utils/timeline-util.ts | 2 +- .../[[assetId=id]]/+page.svelte | 4 +- .../[[assetId=id]]/+page.svelte | 2 +- .../[[assetId=id]]/+page.svelte | 2 +- .../[[assetId=id]]/+page.svelte | 2 +- .../[[assetId=id]]/+page.svelte | 2 +- .../[[assetId=id]]/+page.svelte | 2 +- .../[[assetId=id]]/+page.svelte | 3 +- .../(user)/photos/[[assetId=id]]/+page.svelte | 2 +- .../[[assetId=id]]/+page.svelte | 3 +- .../[[assetId=id]]/+page.svelte | 2 +- .../[[assetId=id]]/+page.svelte | 2 +- web/src/test-data/factories/asset-factory.ts | 2 +- 47 files changed, 1751 insertions(+), 1731 deletions(-) create mode 100644 web/src/lib/managers/timeline-manager/add-context.svelte.ts create mode 100644 web/src/lib/managers/timeline-manager/asset-bucket.svelte.ts create mode 100644 web/src/lib/managers/timeline-manager/asset-date-group.svelte.ts create mode 100644 web/src/lib/managers/timeline-manager/asset-store.svelte.ts create mode 100644 web/src/lib/managers/timeline-manager/intersecting-asset.svelte.ts create mode 100644 web/src/lib/managers/timeline-manager/types.ts create mode 100644 web/src/lib/managers/timeline-manager/utils.svelte.ts diff --git a/web/src/lib/components/album-page/album-viewer.svelte b/web/src/lib/components/album-page/album-viewer.svelte index 59d4b5e6ea..71cd9de932 100644 --- a/web/src/lib/components/album-page/album-viewer.svelte +++ b/web/src/lib/components/album-page/album-viewer.svelte @@ -6,7 +6,7 @@ import AssetSelectControlBar from '$lib/components/photos-page/asset-select-control-bar.svelte'; import { AssetInteraction } from '$lib/stores/asset-interaction.svelte'; import { assetViewingStore } from '$lib/stores/asset-viewing.store'; - import { AssetStore } from '$lib/stores/assets-store.svelte'; + import { AssetStore } from '$lib/managers/timeline-manager/asset-store.svelte'; import { dragAndDropFilesStore } from '$lib/stores/drag-and-drop-files.store'; import { featureFlags } from '$lib/stores/server-config.store'; import { handlePromiseError } from '$lib/utils'; diff --git a/web/src/lib/components/asset-viewer/actions/action.ts b/web/src/lib/components/asset-viewer/actions/action.ts index 0918c86bfe..fd37d253aa 100644 --- a/web/src/lib/components/asset-viewer/actions/action.ts +++ b/web/src/lib/components/asset-viewer/actions/action.ts @@ -1,5 +1,5 @@ import type { AssetAction } from '$lib/constants'; -import type { TimelineAsset } from '$lib/stores/assets-store.svelte'; +import type { TimelineAsset } from '$lib/managers/timeline-manager/types'; import type { AlbumResponseDto } from '@immich/sdk'; type ActionMap = { diff --git a/web/src/lib/components/asset-viewer/actions/download-action.svelte b/web/src/lib/components/asset-viewer/actions/download-action.svelte index 89bf5b72cb..9a2e1a9553 100644 --- a/web/src/lib/components/asset-viewer/actions/download-action.svelte +++ b/web/src/lib/components/asset-viewer/actions/download-action.svelte @@ -2,7 +2,7 @@ import { shortcut } from '$lib/actions/shortcut'; import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte'; import { authManager } from '$lib/managers/auth-manager.svelte'; - import type { TimelineAsset } from '$lib/stores/assets-store.svelte'; + import type { TimelineAsset } from '$lib/managers/timeline-manager/types'; import { downloadFile } from '$lib/utils/asset-utils'; import { getAssetInfo } from '@immich/sdk'; import { IconButton } from '@immich/ui'; diff --git a/web/src/lib/components/asset-viewer/actions/set-visibility-action.svelte b/web/src/lib/components/asset-viewer/actions/set-visibility-action.svelte index ab97427524..1b06bf8e21 100644 --- a/web/src/lib/components/asset-viewer/actions/set-visibility-action.svelte +++ b/web/src/lib/components/asset-viewer/actions/set-visibility-action.svelte @@ -3,7 +3,7 @@ import { AssetAction } from '$lib/constants'; import { modalManager } from '$lib/managers/modal-manager.svelte'; - import type { TimelineAsset } from '$lib/stores/assets-store.svelte'; + import type { TimelineAsset } from '$lib/managers/timeline-manager/types'; import { handleError } from '$lib/utils/handle-error'; import { AssetVisibility, updateAssets } from '@immich/sdk'; import { mdiLockOpenVariantOutline, mdiLockOutline } from '@mdi/js'; diff --git a/web/src/lib/components/asset-viewer/asset-viewer.svelte b/web/src/lib/components/asset-viewer/asset-viewer.svelte index eadecfff95..e5d2554e2c 100644 --- a/web/src/lib/components/asset-viewer/asset-viewer.svelte +++ b/web/src/lib/components/asset-viewer/asset-viewer.svelte @@ -10,7 +10,7 @@ import { authManager } from '$lib/managers/auth-manager.svelte'; import { closeEditorCofirm } from '$lib/stores/asset-editor.store'; import { assetViewingStore } from '$lib/stores/asset-viewing.store'; - import type { TimelineAsset } from '$lib/stores/assets-store.svelte'; + import type { TimelineAsset } from '$lib/managers/timeline-manager/types'; import { isShowDetail } from '$lib/stores/preferences.store'; import { SlideshowNavigation, SlideshowState, slideshowStore } from '$lib/stores/slideshow.store'; import { user } from '$lib/stores/user.store'; diff --git a/web/src/lib/components/asset-viewer/photo-viewer.svelte b/web/src/lib/components/asset-viewer/photo-viewer.svelte index 3dffc0e84c..9cb80fb2dd 100644 --- a/web/src/lib/components/asset-viewer/photo-viewer.svelte +++ b/web/src/lib/components/asset-viewer/photo-viewer.svelte @@ -4,7 +4,8 @@ import FaceEditor from '$lib/components/asset-viewer/face-editor/face-editor.svelte'; import BrokenAsset from '$lib/components/assets/broken-asset.svelte'; import { castManager } from '$lib/managers/cast-manager.svelte'; - import { photoViewerImgElement, type TimelineAsset } from '$lib/stores/assets-store.svelte'; + import { photoViewerImgElement } from '$lib/stores/assets-store.svelte'; + import type { TimelineAsset } from '$lib/managers/timeline-manager/types'; import { isFaceEditMode } from '$lib/stores/face-edit.svelte'; import { boundingBoxesArray } from '$lib/stores/people.store'; import { alwaysLoadOriginalFile } from '$lib/stores/preferences.store'; diff --git a/web/src/lib/components/assets/thumbnail/thumbnail.svelte b/web/src/lib/components/assets/thumbnail/thumbnail.svelte index 1f9a29268f..2121531cf5 100644 --- a/web/src/lib/components/assets/thumbnail/thumbnail.svelte +++ b/web/src/lib/components/assets/thumbnail/thumbnail.svelte @@ -18,7 +18,7 @@ import { thumbhash } from '$lib/actions/thumbhash'; import { authManager } from '$lib/managers/auth-manager.svelte'; - import type { TimelineAsset } from '$lib/stores/assets-store.svelte'; + import type { TimelineAsset } from '$lib/managers/timeline-manager/types'; import { mobileDevice } from '$lib/stores/mobile-device.svelte'; import { moveFocus } from '$lib/utils/focus-util'; import { currentUrlReplaceAssetId } from '$lib/utils/navigation'; diff --git a/web/src/lib/components/memory-page/memory-viewer.svelte b/web/src/lib/components/memory-page/memory-viewer.svelte index 92118b4d8c..62e74f5685 100644 --- a/web/src/lib/components/memory-page/memory-viewer.svelte +++ b/web/src/lib/components/memory-page/memory-viewer.svelte @@ -27,7 +27,8 @@ import { authManager } from '$lib/managers/auth-manager.svelte'; import { AssetInteraction } from '$lib/stores/asset-interaction.svelte'; import { assetViewingStore } from '$lib/stores/asset-viewing.store'; - import { type TimelineAsset, type Viewport } from '$lib/stores/assets-store.svelte'; + import type { TimelineAsset } from '$lib/managers/timeline-manager/types'; + import type { Viewport } from '$lib/managers/timeline-manager/types'; import { type MemoryAsset, memoryStore } from '$lib/stores/memory.store.svelte'; import { locale, videoViewerMuted, videoViewerVolume } from '$lib/stores/preferences.store'; import { preferences } from '$lib/stores/user.store'; diff --git a/web/src/lib/components/photos-page/actions/focus-actions.ts b/web/src/lib/components/photos-page/actions/focus-actions.ts index 5085faa0a3..3a75820cd8 100644 --- a/web/src/lib/components/photos-page/actions/focus-actions.ts +++ b/web/src/lib/components/photos-page/actions/focus-actions.ts @@ -1,4 +1,5 @@ -import type { AssetStore, TimelineAsset } from '$lib/stores/assets-store.svelte'; +import { AssetStore } from '$lib/managers/timeline-manager/asset-store.svelte'; +import type { TimelineAsset } from '$lib/managers/timeline-manager/types'; import { moveFocus } from '$lib/utils/focus-util'; import { InvocationTracker } from '$lib/utils/invocationTracker'; import { tick } from 'svelte'; diff --git a/web/src/lib/components/photos-page/actions/link-live-photo-action.svelte b/web/src/lib/components/photos-page/actions/link-live-photo-action.svelte index 1d36c79730..09ca94cb25 100644 --- a/web/src/lib/components/photos-page/actions/link-live-photo-action.svelte +++ b/web/src/lib/components/photos-page/actions/link-live-photo-action.svelte @@ -1,6 +1,6 @@ onClick(!isPlaying)} From 737fedd527d513d6b5c61780ed40af14174e5b98 Mon Sep 17 00:00:00 2001 From: xCJPECKOVERx Date: Fri, 6 Jun 2025 09:36:28 -0400 Subject: [PATCH 14/14] fix(web): Update add to album notification to better announce errors (#18955) * Update add to album notification to better announce errors * fix i18n --------- Co-authored-by: wuzihao051119 --- i18n/en.json | 1 + web/src/lib/utils/asset-utils.ts | 56 +++++++++++++++++++++----------- 2 files changed, 38 insertions(+), 19 deletions(-) diff --git a/i18n/en.json b/i18n/en.json index 849f96e4a5..2fbce6fbb7 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -463,6 +463,7 @@ "assets_added_count": "Added {count, plural, one {# asset} other {# assets}}", "assets_added_to_album_count": "Added {count, plural, one {# asset} other {# assets}} to the album", "assets_added_to_name_count": "Added {count, plural, one {# asset} other {# assets}} to {hasName, select, true {{name}} other {new album}}", + "assets_cannot_be_added_to_album_count": "{count, plural, one {Asset} other {Assets}} cannot be added to the album", "assets_count": "{count, plural, one {# asset} other {# assets}}", "assets_deleted_permanently": "{count} asset(s) deleted permanently", "assets_deleted_permanently_from_server": "{count} asset(s) deleted permanently from the Immich server", diff --git a/web/src/lib/utils/asset-utils.ts b/web/src/lib/utils/asset-utils.ts index cb4c9eeff0..b64ab51124 100644 --- a/web/src/lib/utils/asset-utils.ts +++ b/web/src/lib/utils/asset-utils.ts @@ -52,16 +52,20 @@ export const addAssetsToAlbum = async (albumId: string, assetIds: string[], show key: authManager.key, }); const count = result.filter(({ success }) => success).length; + const duplicateErrorCount = result.filter(({ error }) => error === 'duplicate').length; const $t = get(t); if (showNotification) { + let message = $t('assets_cannot_be_added_to_album_count', { values: { count: assetIds.length } }); + if (count > 0) { + message = $t('assets_added_to_album_count', { values: { count } }); + } else if (duplicateErrorCount > 0) { + message = $t('assets_were_part_of_album_count', { values: { count: duplicateErrorCount } }); + } notificationController.show({ type: NotificationType.Info, timeout: 5000, - message: - count > 0 - ? $t('assets_added_to_album_count', { values: { count } }) - : $t('assets_were_part_of_album_count', { values: { count: assetIds.length } }), + message, button: { text: $t('view_album'), onClick() { @@ -125,23 +129,37 @@ export const addAssetsToNewAlbum = async (albumName: string, assetIds: string[]) } const $t = get(t); // for reasons beyond me > doesn't work, even though it's (afaik) exactly this object - notificationController.show<{ key: Translations; values: InterpolationValues }>({ - type: NotificationType.Info, - timeout: 5000, - component: { - type: FormatBoldMessage, - props: { - key: 'assets_added_to_name_count', - values: { count: assetIds.length, name: albumName, hasName: !!albumName }, + if (album.assets.length === 0) { + notificationController.show({ + type: NotificationType.Info, + timeout: 5000, + message: $t('assets_cannot_be_added_to_album_count', { values: { count: assetIds.length } }), + button: { + text: $t('view_album'), + onClick() { + return goto(`${AppRoute.ALBUMS}/${album.id}`); + }, }, - }, - button: { - text: $t('view_album'), - onClick() { - return goto(`${AppRoute.ALBUMS}/${album.id}`); + }); + } else { + notificationController.show<{ key: Translations; values: InterpolationValues }>({ + type: NotificationType.Info, + timeout: 5000, + component: { + type: FormatBoldMessage, + props: { + key: 'assets_added_to_name_count', + values: { count: album.assets.length, name: albumName, hasName: !!albumName }, + }, }, - }, - }); + button: { + text: $t('view_album'), + onClick() { + return goto(`${AppRoute.ALBUMS}/${album.id}`); + }, + }, + }); + } return album; };