{$t('create_tag_description')}
diff --git a/web/src/lib/modals/UserEditModal.svelte b/web/src/lib/modals/UserEditModal.svelte
index 8238c6c5d8..9d05ef353c 100644
--- a/web/src/lib/modals/UserEditModal.svelte
+++ b/web/src/lib/modals/UserEditModal.svelte
@@ -101,7 +101,7 @@
{$t('admin.note_apply_storage_label_previous_assets')}
-
+
{$t('admin.storage_template_migration_job')}
diff --git a/web/src/routes/(user)/albums/[albumId=id]/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/albums/[albumId=id]/[[photos=photos]]/[[assetId=id]]/+page.svelte
index 5a895bf372..8c8b84ce16 100644
--- a/web/src/routes/(user)/albums/[albumId=id]/[[photos=photos]]/[[assetId=id]]/+page.svelte
+++ b/web/src/routes/(user)/albums/[albumId=id]/[[photos=photos]]/[[assetId=id]]/+page.svelte
@@ -81,6 +81,7 @@
mdiPlus,
mdiPresentationPlay,
mdiShareVariantOutline,
+ mdiUpload,
} from '@mdi/js';
import { onDestroy } from 'svelte';
import { t } from 'svelte-i18n';
@@ -534,7 +535,7 @@
onclick={() => (viewMode = AlbumPageViewMode.SELECT_ASSETS)}
class="mt-5 bg-subtle flex w-full place-items-center gap-6 rounded-2xl border px-8 py-8 text-immich-fg transition-all hover:bg-gray-100 dark:hover:bg-gray-500/20 hover:text-immich-primary dark:border-none dark:text-immich-dark-fg dark:hover:text-immich-dark-primary"
>
-
+
{$t('select_photos')}
@@ -710,16 +711,10 @@
{/snippet}
{#snippet trailing()}
-
- {$t('select_from_computer')}
+
{/snippet}
{/if}
diff --git a/web/src/routes/(user)/people/+page.svelte b/web/src/routes/(user)/people/+page.svelte
index dda3c516ad..5a782c5a11 100644
--- a/web/src/routes/(user)/people/+page.svelte
+++ b/web/src/routes/(user)/people/+page.svelte
@@ -374,7 +374,7 @@
e.currentTarget.blur() }}
diff --git a/web/src/routes/(user)/people/[personId]/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/people/[personId]/[[photos=photos]]/[[assetId=id]]/+page.svelte
index 4574ed6d95..5de52fc689 100644
--- a/web/src/routes/(user)/people/[personId]/[[photos=photos]]/[[assetId=id]]/+page.svelte
+++ b/web/src/routes/(user)/people/[personId]/[[photos=photos]]/[[assetId=id]]/+page.svelte
@@ -431,9 +431,7 @@
widthStyle="3.375rem"
heightStyle="3.375rem"
/>
-
+
{person.name || $t('add_a_name')}
{$t('assets_count', { values: { count: numberOfAssets } })}
diff --git a/web/src/routes/admin/library-management/+page.svelte b/web/src/routes/admin/library-management/+page.svelte
index 53f8311091..2af89a34fb 100644
--- a/web/src/routes/admin/library-management/+page.svelte
+++ b/web/src/routes/admin/library-management/+page.svelte
@@ -277,7 +277,7 @@
{#if libraries.length > 0}
| {$t('name')} |
diff --git a/web/src/routes/admin/users/+page.svelte b/web/src/routes/admin/users/+page.svelte
index f34b393291..9416d92db8 100644
--- a/web/src/routes/admin/users/+page.svelte
+++ b/web/src/routes/admin/users/+page.svelte
@@ -87,7 +87,7 @@
|
Date: Wed, 17 Sep 2025 12:14:16 -0400
Subject: [PATCH 16/48] fix(mobile): load original image (#22142)
load original image
---
.../alextran/immich/images/ThumbnailsImpl.kt | 8 ++++----
mobile/ios/Runner/Images/ThumbnailsImpl.swift | 2 +-
.../widgets/images/local_image_provider.dart | 17 ++++++++++++++++-
3 files changed, 21 insertions(+), 6 deletions(-)
diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/images/ThumbnailsImpl.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/images/ThumbnailsImpl.kt
index 1b1716f55c..1ccd742d67 100644
--- a/mobile/android/app/src/main/kotlin/app/alextran/immich/images/ThumbnailsImpl.kt
+++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/images/ThumbnailsImpl.kt
@@ -8,7 +8,6 @@ import android.net.Uri
import android.os.Build
import android.os.CancellationSignal
import android.os.OperationCanceledException
-import android.provider.MediaStore
import android.provider.MediaStore.Images
import android.provider.MediaStore.Video
import android.util.Size
@@ -19,7 +18,6 @@ import com.bumptech.glide.Glide
import com.bumptech.glide.Priority
import com.bumptech.glide.load.DecodeFormat
import java.util.Base64
-import java.util.HashMap
import java.util.concurrent.CancellationException
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.Future
@@ -202,8 +200,10 @@ class ThumbnailsImpl(context: Context) : ThumbnailApi {
val source = ImageDecoder.createSource(resolver, uri)
signal.throwIfCanceled()
ImageDecoder.decodeBitmap(source) { decoder, info, _ ->
- val sampleSize = max(1, min(info.size.width / targetWidth, info.size.height / targetHeight))
- decoder.setTargetSampleSize(sampleSize)
+ if (targetWidth > 0 && targetHeight > 0) {
+ val sample = max(1, min(info.size.width / targetWidth, info.size.height / targetHeight))
+ decoder.setTargetSampleSize(sample)
+ }
decoder.allocator = ImageDecoder.ALLOCATOR_SOFTWARE
decoder.setTargetColorSpace(ColorSpace.get(ColorSpace.Named.SRGB))
}
diff --git a/mobile/ios/Runner/Images/ThumbnailsImpl.swift b/mobile/ios/Runner/Images/ThumbnailsImpl.swift
index d1ea2cc0e0..452ca62377 100644
--- a/mobile/ios/Runner/Images/ThumbnailsImpl.swift
+++ b/mobile/ios/Runner/Images/ThumbnailsImpl.swift
@@ -105,7 +105,7 @@ class ThumbnailApiImpl: ThumbnailApi {
var image: UIImage?
Self.imageManager.requestImage(
for: asset,
- targetSize: CGSize(width: Double(width), height: Double(height)),
+ targetSize: width > 0 && height > 0 ? CGSize(width: Double(width), height: Double(height)) : PHImageManagerMaximumSize,
contentMode: .aspectFill,
options: Self.requestOptions,
resultHandler: { (_image, info) -> Void in
diff --git a/mobile/lib/presentation/widgets/images/local_image_provider.dart b/mobile/lib/presentation/widgets/images/local_image_provider.dart
index 223d095432..f90961ea5a 100644
--- a/mobile/lib/presentation/widgets/images/local_image_provider.dart
+++ b/mobile/lib/presentation/widgets/images/local_image_provider.dart
@@ -4,6 +4,8 @@ import 'dart:ui';
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
+import 'package:immich_mobile/domain/models/store.model.dart';
+import 'package:immich_mobile/entities/store.entity.dart';
import 'package:immich_mobile/infrastructure/loaders/image_request.dart';
import 'package:immich_mobile/presentation/widgets/images/image_provider.dart';
import 'package:immich_mobile/presentation/widgets/images/one_frame_multi_image_stream_completer.dart';
@@ -88,13 +90,26 @@ class LocalFullImageProvider extends CancellableImageProvider
Date: Wed, 17 Sep 2025 21:48:54 +0530
Subject: [PATCH 17/48] fix: show delete on device when asset has a local match
(#22143)
* fix: show delete on device when asset has a local match
* change test description
---------
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
---
mobile/lib/utils/action_button.utils.dart | 2 +-
mobile/test/utils/action_button_utils_test.dart | 15 +++++++++++++++
2 files changed, 16 insertions(+), 1 deletion(-)
diff --git a/mobile/lib/utils/action_button.utils.dart b/mobile/lib/utils/action_button.utils.dart
index 4dfc0398bd..090aeeeaa7 100644
--- a/mobile/lib/utils/action_button.utils.dart
+++ b/mobile/lib/utils/action_button.utils.dart
@@ -102,7 +102,7 @@ enum ActionButtonType {
context.asset.hasRemote,
ActionButtonType.deleteLocal =>
!context.isInLockedView && //
- context.asset.storage == AssetState.local,
+ context.asset.hasLocal,
ActionButtonType.upload =>
!context.isInLockedView && //
context.asset.storage == AssetState.local,
diff --git a/mobile/test/utils/action_button_utils_test.dart b/mobile/test/utils/action_button_utils_test.dart
index 497246e2a1..f8c51173d7 100644
--- a/mobile/test/utils/action_button_utils_test.dart
+++ b/mobile/test/utils/action_button_utils_test.dart
@@ -502,6 +502,21 @@ void main() {
expect(ActionButtonType.deleteLocal.shouldShow(context), isFalse);
});
+
+ test('should show when asset is merged', () {
+ final context = ActionButtonContext(
+ asset: mergedAsset,
+ isOwner: true,
+ isArchived: false,
+ isTrashEnabled: true,
+ isInLockedView: false,
+ currentAlbum: null,
+ advancedTroubleshooting: false,
+ source: ActionSource.timeline,
+ );
+
+ expect(ActionButtonType.deleteLocal.shouldShow(context), isTrue);
+ });
});
group('upload button', () {
From 98ea3847e52bc50bd4004394bcce2a99a07c71b4 Mon Sep 17 00:00:00 2001
From: Jason Rasmussen
Date: Wed, 17 Sep 2025 12:23:23 -0400
Subject: [PATCH 18/48] refactor: server-about-modal (#22138)
* refactor: server-about-modal
* fix: bits-ui scroll lock cleanup
---
web/src/lib/components/ServerAboutItem.svelte | 24 +++
.../shared-components/change-date.spec.ts | 9 +-
web/src/lib/modals/ServerAboutModal.svelte | 159 +++++-------------
3 files changed, 74 insertions(+), 118 deletions(-)
create mode 100644 web/src/lib/components/ServerAboutItem.svelte
diff --git a/web/src/lib/components/ServerAboutItem.svelte b/web/src/lib/components/ServerAboutItem.svelte
new file mode 100644
index 0000000000..9e169a9839
--- /dev/null
+++ b/web/src/lib/components/ServerAboutItem.svelte
@@ -0,0 +1,24 @@
+
+
+
+
+
+ {#if versionHref}
+ {version}
+ {:else}
+ {version}
+ {/if}
+
+
diff --git a/web/src/lib/components/shared-components/change-date.spec.ts b/web/src/lib/components/shared-components/change-date.spec.ts
index 43035051f3..63926a44a6 100644
--- a/web/src/lib/components/shared-components/change-date.spec.ts
+++ b/web/src/lib/components/shared-components/change-date.spec.ts
@@ -1,6 +1,6 @@
import { getIntersectionObserverMock } from '$lib/__mocks__/intersection-observer.mock';
import { getVisualViewportMock } from '$lib/__mocks__/visual-viewport.mock';
-import { fireEvent, render, screen } from '@testing-library/svelte';
+import { fireEvent, render, screen, waitFor } from '@testing-library/svelte';
import userEvent from '@testing-library/user-event';
import { DateTime } from 'luxon';
import ChangeDate from './change-date.svelte';
@@ -30,6 +30,13 @@ describe('ChangeDate component', () => {
vi.resetAllMocks();
});
+ afterAll(async () => {
+ await waitFor(() => {
+ // check that bits-ui body scroll-lock class is gone
+ expect(document.body.style.pointerEvents).not.toBe('none');
+ });
+ });
+
test('should render correct values', () => {
render(ChangeDate, { initialDate, initialTimeZone, onCancel, onConfirm });
expect(getDateInput().value).toBe('2024-01-01T00:00');
diff --git a/web/src/lib/modals/ServerAboutModal.svelte b/web/src/lib/modals/ServerAboutModal.svelte
index 99967e7588..92bbac3d67 100644
--- a/web/src/lib/modals/ServerAboutModal.svelte
+++ b/web/src/lib/modals/ServerAboutModal.svelte
@@ -1,8 +1,8 @@
- |