Compare commits

...

14 Commits

Author SHA1 Message Date
LeLunZ
afc470e8f4 Merge remote-tracking branch 'upstream/main' into feature/load-previews 2026-04-22 00:56:53 +02:00
Luis Nachtigall
793a7054fb fix(mobile): thumbnail transition to asset viewer (#27850) 2026-04-21 15:54:40 -05:00
Luis Nachtigall
3a874dd441 fix(mobile): enable autoplay for motion photos in video viewer (#27961) 2026-04-21 15:53:21 -05:00
Luis Nachtigall
3dc7dc93d8 fix(mobile): clear local data on forced logout (#27957) 2026-04-21 15:52:00 -05:00
Yaros
70397dc5a6 fix(mobile): zero exposure (#28017) 2026-04-21 15:47:27 -05:00
Jason Rasmussen
a16d233a0c chore(web): sort imports (#27922)
* feat: sort imports

* fix: something?
2026-04-21 14:51:38 -04:00
Daniel Dietzler
bb0872afef chore: upgrade sql-tools (#27885) 2026-04-21 17:42:27 +00:00
Freddie Floydd
b9ca68f6e4 chore(web): rename components to PascalCase (#28013)
chore: rename components to PascalCase
2026-04-21 12:29:42 -04:00
Daniel Dietzler
837305da7e chore: un-skip tests (#28012) 2026-04-21 12:08:23 -04:00
Daniel Dietzler
e20fb44142 fix: web navigation/animation regression (#28011) 2026-04-21 14:51:37 +00:00
renovate[bot]
c2786978cd fix(deps): update typescript-projects (#28008)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>
2026-04-21 15:29:34 +02:00
LeLunZ
b156ae46b6 fix: don't clear cachedOperation in whenComplete listener 2026-04-12 14:54:05 +02:00
LeLunZ
67b1a9d99e fix: simplify request assignment in local image provider 2026-04-12 14:50:44 +02:00
LeLunZ
d4f7c2ae0a feat(mobile): implement load preview setting in asset viewer 2026-04-12 12:52:40 +02:00
470 changed files with 2022 additions and 1503 deletions

12
.vscode/settings.json vendored
View File

@@ -13,10 +13,6 @@
"editor.wordBasedSuggestions": "off"
},
"[javascript]": {
"editor.codeActionsOnSave": {
"source.organizeImports": "explicit",
"source.removeUnusedImports": "explicit"
},
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true
},
@@ -29,18 +25,10 @@
"editor.formatOnSave": true
},
"[svelte]": {
"editor.codeActionsOnSave": {
"source.organizeImports": "explicit",
"source.removeUnusedImports": "explicit"
},
"editor.defaultFormatter": "svelte.svelte-vscode",
"editor.formatOnSave": true
},
"[typescript]": {
"editor.codeActionsOnSave": {
"source.organizeImports": "explicit",
"source.removeUnusedImports": "explicit"
},
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true
},

View File

@@ -17,10 +17,10 @@
"write-heading-ids": "docusaurus write-heading-ids"
},
"dependencies": {
"@docusaurus/core": "~3.9.0",
"@docusaurus/preset-classic": "~3.9.0",
"@docusaurus/theme-common": "~3.9.0",
"@docusaurus/theme-mermaid": "~3.9.0",
"@docusaurus/core": "~3.10.0",
"@docusaurus/preset-classic": "~3.10.0",
"@docusaurus/theme-common": "~3.10.0",
"@docusaurus/theme-mermaid": "~3.10.0",
"@mdi/js": "^7.3.67",
"@mdi/react": "^1.6.1",
"@mdx-js/react": "^3.0.0",
@@ -36,9 +36,9 @@
"url": "^0.11.0"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "~3.9.0",
"@docusaurus/module-type-aliases": "~3.10.0",
"@docusaurus/tsconfig": "^3.10.0",
"@docusaurus/types": "^3.7.0",
"@docusaurus/types": "^3.10.0",
"prettier": "^3.7.4",
"typescript": "^6.0.0"
},

View File

@@ -349,7 +349,7 @@ test.describe('Timeline', () => {
expect(visibleMockAssetsYearMonths).toContain(month);
}
});
test('Deep link to last photo, scroll up', async ({ page }) => {
test.skip('Deep link to last photo, scroll up', async ({ page }) => {
const lastAsset = assets.at(-1)!;
await pageUtils.deepLinkPhotosPage(page, lastAsset.id);
@@ -361,7 +361,7 @@ test.describe('Timeline', () => {
await thumbnailUtils.expectInViewport(page, '14e5901f-fd7f-40c0-b186-4d7e7fc67968');
});
test('Deep link to first bucket, scroll down', async ({ page }) => {
test.skip('Deep link to first bucket, scroll down', async ({ page }) => {
const lastAsset = assets.at(0)!;
await pageUtils.deepLinkPhotosPage(page, lastAsset.id);
await timelineUtils.locator(page).hover();
@@ -440,7 +440,7 @@ test.describe('Timeline', () => {
await thumbnailUtils.expectInViewport(page, asset.id);
await thumbnailUtils.expectSelectedDisabled(page, asset.id);
});
test('Add photos to album', async ({ page }) => {
test.skip('Add photos to album', async ({ page }) => {
const album = timelineRestData.album;
await pageUtils.openAlbumPage(page, album.id);
await page.locator('nav button[aria-label="Add photos"]').click();
@@ -752,7 +752,7 @@ test.describe('Timeline', () => {
await page.getByText('Photos', { exact: true }).click();
await thumbnailUtils.expectInViewport(page, assetToFavorite.id);
});
test('open /favorites, archive photo, unarchive photo', async ({ page }) => {
test.skip('open /favorites, archive photo, unarchive photo', async ({ page }) => {
await pageUtils.openFavorites(page);
const assetToArchive = getAsset(timelineRestData, 'ad31e29f-2069-4574-b9a9-ad86523c92cb')!;
await thumbnailUtils.withAssetId(page, assetToArchive.id).hover();

View File

@@ -29,7 +29,7 @@ class ExifInfo {
bool get hasCoordinates => latitude != null && longitude != null && latitude != 0 && longitude != 0;
String get exposureTime {
if (exposureSeconds == null) {
if (exposureSeconds == null || exposureSeconds! <= 0 || exposureSeconds!.isNaN) {
return "";
}
if (exposureSeconds! < 1) {

View File

@@ -4,6 +4,7 @@ enum Setting<T> {
tilesPerRow<int>(StoreKey.tilesPerRow, 4),
groupAssetsBy<int>(StoreKey.groupAssetsBy, 0),
showStorageIndicator<bool>(StoreKey.storageIndicator, true),
loadPreview<bool>(StoreKey.loadPreview, true),
loadOriginal<bool>(StoreKey.loadOriginal, false),
loadOriginalVideo<bool>(StoreKey.loadOriginalVideo, false),
autoPlayVideo<bool>(StoreKey.autoPlayVideo, true),

View File

@@ -148,7 +148,7 @@ class _NativeVideoViewerState extends ConsumerState<NativeVideoViewer> with Widg
if (ref.read(assetViewerProvider).showingDetails) return;
final autoPlayVideo = AppSetting.get(Setting.autoPlayVideo);
if (autoPlayVideo) await _notifier.play();
if (autoPlayVideo || widget.asset.isMotionPhoto) await _notifier.play();
}
void _onPlaybackEnded() {

View File

@@ -45,7 +45,6 @@ mixin CancellableImageProviderMixin<T extends Object> on CancellableImageProvide
completer.operation.valueOrCancellation().whenComplete(() {
cachedStream.removeListener(listener);
cachedOperation = null;
});
cachedOperation = completer.operation;
return null;
@@ -106,18 +105,33 @@ mixin CancellableImageProviderMixin<T extends Object> on CancellableImageProvide
}
}
Stream<ImageInfo> initialImageStream() async* {
Stream<ImageInfo> initialImageStream({required bool isFinal}) async* {
final cachedOperation = this.cachedOperation;
if (isCancelled) {
return;
}
if (cachedOperation == null) {
// image resolved synchronously
isFinished = isFinal;
return;
}
try {
final cachedImage = await cachedOperation.valueOrCancellation();
if (cachedImage != null && !isCancelled) {
yield cachedImage;
if (isCancelled || cachedImage == null) {
return;
}
isFinished = isFinal;
yield cachedImage;
} catch (e, stack) {
if (isCancelled) {
return;
}
if (isFinal) {
isFinished = true;
PaintingBinding.instance.imageCache.evict(this);
rethrow;
}
_log.severe('Error loading initial image', e, stack);
} finally {
this.cachedOperation = null;

View File

@@ -1,8 +1,8 @@
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/domain/models/setting.model.dart';
import 'package:immich_mobile/domain/services/setting.service.dart';
import 'package:immich_mobile/infrastructure/loaders/image_request.dart';
import 'package:immich_mobile/presentation/widgets/images/animated_image_stream_completer.dart';
import 'package:immich_mobile/presentation/widgets/images/image_provider.dart';
@@ -97,51 +97,55 @@ class LocalFullImageProvider extends CancellableImageProvider<LocalFullImageProv
}
Stream<ImageInfo> _codec(LocalFullImageProvider key, ImageDecoderCallback decode) async* {
yield* initialImageStream();
final loadOriginal = AppSetting.get(Setting.loadOriginal);
final loadPreview = AppSetting.get(Setting.loadPreview);
yield* initialImageStream(isFinal: !loadOriginal && !loadPreview);
if (isCancelled) {
return;
}
final loadOriginal = Store.get(StoreKey.loadOriginal, false);
final devicePixelRatio = PlatformDispatcher.instance.views.first.devicePixelRatio;
var request = this.request = LocalImageRequest(
localId: key.id,
size: Size(size.width * devicePixelRatio, size.height * devicePixelRatio),
assetType: key.assetType,
);
yield* loadRequest(request, decode, isFinal: !loadOriginal);
if (loadPreview) {
final devicePixelRatio = PlatformDispatcher.instance.views.first.devicePixelRatio;
final previewRequest = request = LocalImageRequest(
localId: key.id,
size: Size(size.width * devicePixelRatio, size.height * devicePixelRatio),
assetType: key.assetType,
);
yield* loadRequest(previewRequest, decode, isFinal: !loadOriginal);
if (isCancelled) {
return;
}
}
if (!loadOriginal) {
return;
}
if (isCancelled) {
return;
}
request = this.request = LocalImageRequest(localId: key.id, assetType: key.assetType, size: Size.zero);
yield* loadRequest(request, decode, isFinal: true);
final originalRequest = request = LocalImageRequest(localId: key.id, assetType: key.assetType, size: Size.zero);
yield* loadRequest(originalRequest, decode, isFinal: true);
}
Stream<Object> _animatedCodec(LocalFullImageProvider key, ImageDecoderCallback decode) async* {
yield* initialImageStream();
yield* initialImageStream(isFinal: false);
if (isCancelled) {
return;
}
final devicePixelRatio = PlatformDispatcher.instance.views.first.devicePixelRatio;
final previewRequest = request = LocalImageRequest(
localId: key.id,
size: Size(size.width * devicePixelRatio, size.height * devicePixelRatio),
assetType: key.assetType,
);
yield* loadRequest(previewRequest, decode, isFinal: false);
if (AppSetting.get(Setting.loadPreview)) {
final devicePixelRatio = PlatformDispatcher.instance.views.first.devicePixelRatio;
final previewRequest = request = LocalImageRequest(
localId: key.id,
size: Size(size.width * devicePixelRatio, size.height * devicePixelRatio),
assetType: key.assetType,
);
yield* loadRequest(previewRequest, decode, isFinal: false);
if (isCancelled) {
return;
if (isCancelled) {
return;
}
}
// always try original for animated, since previews don't support animation

View File

@@ -107,31 +107,35 @@ class RemoteFullImageProvider extends CancellableImageProvider<RemoteFullImagePr
}
Stream<ImageInfo> _codec(RemoteFullImageProvider key, ImageDecoderCallback decode) async* {
yield* initialImageStream();
final isImage = assetType == AssetType.image;
final loadOriginal = isImage && AppSetting.get(Setting.loadOriginal);
final loadPreview = isImage && AppSetting.get(Setting.loadPreview);
yield* initialImageStream(isFinal: !loadOriginal && !loadPreview);
if (isCancelled) {
return;
}
final previewRequest = request = RemoteImageRequest(
uri: getThumbnailUrlForRemoteId(
key.assetId,
type: AssetMediaSize.preview,
thumbhash: key.thumbhash,
edited: key.edited,
),
);
final loadOriginal = assetType == AssetType.image && AppSetting.get(Setting.loadOriginal);
yield* loadRequest(previewRequest, decode, isFinal: !loadOriginal);
if (loadPreview) {
final previewRequest = request = RemoteImageRequest(
uri: getThumbnailUrlForRemoteId(
key.assetId,
type: AssetMediaSize.preview,
thumbhash: key.thumbhash,
edited: key.edited,
),
);
yield* loadRequest(previewRequest, decode, isFinal: !loadOriginal);
if (isCancelled) {
return;
}
}
if (!loadOriginal) {
return;
}
if (isCancelled) {
return;
}
final originalRequest = request = RemoteImageRequest(
uri: getOriginalUrlForRemoteId(key.assetId, edited: key.edited),
);
@@ -139,24 +143,26 @@ class RemoteFullImageProvider extends CancellableImageProvider<RemoteFullImagePr
}
Stream<Object> _animatedCodec(RemoteFullImageProvider key, ImageDecoderCallback decode) async* {
yield* initialImageStream();
yield* initialImageStream(isFinal: false);
if (isCancelled) {
return;
}
final previewRequest = request = RemoteImageRequest(
uri: getThumbnailUrlForRemoteId(
key.assetId,
type: AssetMediaSize.preview,
thumbhash: key.thumbhash,
edited: key.edited,
),
);
yield* loadRequest(previewRequest, decode, isFinal: false);
if (AppSetting.get(Setting.loadPreview)) {
final previewRequest = request = RemoteImageRequest(
uri: getThumbnailUrlForRemoteId(
key.assetId,
type: AssetMediaSize.preview,
thumbhash: key.thumbhash,
edited: key.edited,
),
);
yield* loadRequest(previewRequest, decode, isFinal: false);
if (isCancelled) {
return;
if (isCancelled) {
return;
}
}
// always try original for animated, since previews don't support animation

View File

@@ -129,7 +129,7 @@ class _ThumbnailTileState extends ConsumerState<ThumbnailTile> {
}
animation.addStatusListener(animationStatusListener);
return to.widget;
return direction == HeroFlightDirection.push ? from.widget : to.widget;
},
),
),

View File

@@ -7,13 +7,15 @@ import 'package:immich_mobile/domain/services/store.service.dart';
import 'package:immich_mobile/entities/store.entity.dart';
import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/services/api.service.dart';
import 'package:immich_mobile/services/auth.service.dart';
import 'package:logging/logging.dart';
import 'package:openapi/api.dart';
class AuthGuard extends AutoRouteGuard {
final ApiService _apiService;
final AuthService _authService;
final _log = Logger("AuthGuard");
AuthGuard(this._apiService);
AuthGuard(this._apiService, this._authService);
@override
void onNavigation(NavigationResolver resolver, StackRouter router) async {
resolver.next(true);
@@ -27,7 +29,7 @@ class AuthGuard extends AutoRouteGuard {
if (res == null || res.authStatus != true) {
// If the access token is invalid, take user back to login
_log.fine('User token is invalid. Redirecting to login');
unawaited(router.replaceAll([const LoginRoute()]));
unawaited(router.replaceAll([const LoginRoute()]).then((_) => _authService.clearLocalData()));
}
} on StoreKeyNotFoundException catch (_) {
// If there is no access token, take us to the login page
@@ -38,7 +40,7 @@ class AuthGuard extends AutoRouteGuard {
// On an unauthorized request, take us to the login page
if (e.code == HttpStatus.unauthorized) {
_log.warning("Unauthorized access token.");
unawaited(router.replaceAll([const LoginRoute()]));
unawaited(router.replaceAll([const LoginRoute()]).then((_) => _authService.clearLocalData()));
return;
}
} catch (e) {

View File

@@ -73,6 +73,7 @@ import 'package:immich_mobile/routing/auth_guard.dart';
import 'package:immich_mobile/routing/duplicate_guard.dart';
import 'package:immich_mobile/routing/locked_guard.dart';
import 'package:immich_mobile/services/api.service.dart';
import 'package:immich_mobile/services/auth.service.dart';
import 'package:immich_mobile/services/local_auth.service.dart';
import 'package:immich_mobile/services/secure_storage.service.dart';
import 'package:maplibre_gl/maplibre_gl.dart';
@@ -82,6 +83,7 @@ part 'router.gr.dart';
final appRouterProvider = Provider(
(ref) => AppRouter(
ref.watch(apiServiceProvider),
ref.watch(authServiceProvider),
ref.watch(galleryPermissionNotifier.notifier),
ref.watch(secureStorageServiceProvider),
ref.watch(localAuthServiceProvider),
@@ -96,11 +98,12 @@ class AppRouter extends RootStackRouter {
AppRouter(
ApiService apiService,
AuthService authService,
GalleryPermissionNotifier galleryPermissionNotifier,
SecureStorageService secureStorageService,
LocalAuthService localAuthService,
) {
_authGuard = AuthGuard(apiService);
_authGuard = AuthGuard(apiService, authService);
_duplicateGuard = const DuplicateGuard();
_lockedGuard = LockedGuard(apiService, secureStorageService, localAuthService);
}

View File

@@ -10,14 +10,14 @@
"license": "AGPL-3.0",
"devDependencies": {
"@extism/js-pdk": "^1.0.1",
"esbuild": "^0.27.0",
"esbuild": "^0.28.0",
"typescript": "^6.0.0"
}
},
"node_modules/@esbuild/aix-ppc64": {
"version": "0.27.5",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.5.tgz",
"integrity": "sha512-nGsF/4C7uzUj+Nj/4J+Zt0bYQ6bz33Phz8Lb2N80Mti1HjGclTJdXZ+9APC4kLvONbjxN1zfvYNd8FEcbBK/MQ==",
"version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.0.tgz",
"integrity": "sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==",
"cpu": [
"ppc64"
],
@@ -32,9 +32,9 @@
}
},
"node_modules/@esbuild/android-arm": {
"version": "0.27.5",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.5.tgz",
"integrity": "sha512-Cv781jd0Rfj/paoNrul1/r4G0HLvuFKYh7C9uHZ2Pl8YXstzvCyyeWENTFR9qFnRzNMCjXmsulZuvosDg10Mog==",
"version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.0.tgz",
"integrity": "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==",
"cpu": [
"arm"
],
@@ -49,9 +49,9 @@
}
},
"node_modules/@esbuild/android-arm64": {
"version": "0.27.5",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.5.tgz",
"integrity": "sha512-Oeghq+XFgh1pUGd1YKs4DDoxzxkoUkvko+T/IVKwlghKLvvjbGFB3ek8VEDBmNvqhwuL0CQS3cExdzpmUyIrgA==",
"version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.0.tgz",
"integrity": "sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==",
"cpu": [
"arm64"
],
@@ -66,9 +66,9 @@
}
},
"node_modules/@esbuild/android-x64": {
"version": "0.27.5",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.5.tgz",
"integrity": "sha512-nQD7lspbzerlmtNOxYMFAGmhxgzn8Z7m9jgFkh6kpkjsAhZee1w8tJW3ZlW+N9iRePz0oPUDrYrXidCPSImD0Q==",
"version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.0.tgz",
"integrity": "sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==",
"cpu": [
"x64"
],
@@ -83,9 +83,9 @@
}
},
"node_modules/@esbuild/darwin-arm64": {
"version": "0.27.5",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.5.tgz",
"integrity": "sha512-I+Ya/MgC6rr8oRWGRDF3BXDfP8K1BVUggHqN6VI2lUZLdDi1IM1v2cy0e3lCPbP+pVcK3Tv8cgUhHse1kaNZZw==",
"version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.0.tgz",
"integrity": "sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==",
"cpu": [
"arm64"
],
@@ -100,9 +100,9 @@
}
},
"node_modules/@esbuild/darwin-x64": {
"version": "0.27.5",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.5.tgz",
"integrity": "sha512-MCjQUtC8wWJn/pIPM7vQaO69BFgwPD1jriEdqwTCKzWjGgkMbcg+M5HzrOhPhuYe1AJjXlHmD142KQf+jnYj8A==",
"version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.0.tgz",
"integrity": "sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==",
"cpu": [
"x64"
],
@@ -117,9 +117,9 @@
}
},
"node_modules/@esbuild/freebsd-arm64": {
"version": "0.27.5",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.5.tgz",
"integrity": "sha512-X6xVS+goSH0UelYXnuf4GHLwpOdc8rgK/zai+dKzBMnncw7BTQIwquOodE7EKvY2UVUetSqyAfyZC1D+oqLQtg==",
"version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.0.tgz",
"integrity": "sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==",
"cpu": [
"arm64"
],
@@ -134,9 +134,9 @@
}
},
"node_modules/@esbuild/freebsd-x64": {
"version": "0.27.5",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.5.tgz",
"integrity": "sha512-233X1FGo3a8x1ekLB6XT69LfZ83vqz+9z3TSEQCTYfMNY880A97nr81KbPcAMl9rmOFp11wO0dP+eB18KU/Ucg==",
"version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.0.tgz",
"integrity": "sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==",
"cpu": [
"x64"
],
@@ -151,9 +151,9 @@
}
},
"node_modules/@esbuild/linux-arm": {
"version": "0.27.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.5.tgz",
"integrity": "sha512-0wkVrYHG4sdCCN/bcwQ7yYMXACkaHc3UFeaEOwSVW6e5RycMageYAFv+JS2bKLwHyeKVUvtoVH+5/RHq0fgeFw==",
"version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.0.tgz",
"integrity": "sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==",
"cpu": [
"arm"
],
@@ -168,9 +168,9 @@
}
},
"node_modules/@esbuild/linux-arm64": {
"version": "0.27.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.5.tgz",
"integrity": "sha512-euKkilsNOv7x/M1NKsx5znyprbpsRFIzTV6lWziqJch7yWYayfLtZzDxDTl+LSQDJYAjd9TVb/Kt5UKIrj2e4A==",
"version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.0.tgz",
"integrity": "sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==",
"cpu": [
"arm64"
],
@@ -185,9 +185,9 @@
}
},
"node_modules/@esbuild/linux-ia32": {
"version": "0.27.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.5.tgz",
"integrity": "sha512-hVRQX4+P3MS36NxOy24v/Cdsimy/5HYePw+tmPqnNN1fxV0bPrFWR6TMqwXPwoTM2VzbkA+4lbHWUKDd5ZDA/w==",
"version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.0.tgz",
"integrity": "sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==",
"cpu": [
"ia32"
],
@@ -202,9 +202,9 @@
}
},
"node_modules/@esbuild/linux-loong64": {
"version": "0.27.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.5.tgz",
"integrity": "sha512-mKqqRuOPALI8nDzhOBmIS0INvZOOFGGg5n1osGIXAx8oersceEbKd4t1ACNTHM3sJBXGFAlEgqM+svzjPot+ZQ==",
"version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.0.tgz",
"integrity": "sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==",
"cpu": [
"loong64"
],
@@ -219,9 +219,9 @@
}
},
"node_modules/@esbuild/linux-mips64el": {
"version": "0.27.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.5.tgz",
"integrity": "sha512-EE/QXH9IyaAj1qeuIV5+/GZkBTipgGO782Ff7Um3vPS9cvLhJJeATy4Ggxikz2inZ46KByamMn6GqtqyVjhenA==",
"version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.0.tgz",
"integrity": "sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==",
"cpu": [
"mips64el"
],
@@ -236,9 +236,9 @@
}
},
"node_modules/@esbuild/linux-ppc64": {
"version": "0.27.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.5.tgz",
"integrity": "sha512-0V2iF1RGxBf1b7/BjurA5jfkl7PtySjom1r6xOK2q9KWw/XCpAdtB6KNMO+9xx69yYfSCRR9FE0TyKfHA2eQMw==",
"version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.0.tgz",
"integrity": "sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==",
"cpu": [
"ppc64"
],
@@ -253,9 +253,9 @@
}
},
"node_modules/@esbuild/linux-riscv64": {
"version": "0.27.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.5.tgz",
"integrity": "sha512-rYxThBx6G9HN6tFNuvB/vykeLi4VDsm5hE5pVwzqbAjZEARQrWu3noZSfbEnPZ/CRXP3271GyFk/49up2W190g==",
"version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.0.tgz",
"integrity": "sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==",
"cpu": [
"riscv64"
],
@@ -270,9 +270,9 @@
}
},
"node_modules/@esbuild/linux-s390x": {
"version": "0.27.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.5.tgz",
"integrity": "sha512-uEP2q/4qgd8goEUc4QIdU/1P2NmEtZ/zX5u3OpLlCGhJIuBIv0s0wr7TB2nBrd3/A5XIdEkkS5ZLF0ULuvaaYQ==",
"version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.0.tgz",
"integrity": "sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==",
"cpu": [
"s390x"
],
@@ -287,9 +287,9 @@
}
},
"node_modules/@esbuild/linux-x64": {
"version": "0.27.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.5.tgz",
"integrity": "sha512-+Gq47Wqq6PLOOZuBzVSII2//9yyHNKZLuwfzCemqexqOQCSz0zy0O26kIzyp9EMNMK+nZ0tFHBZrCeVUuMs/ew==",
"version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.0.tgz",
"integrity": "sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==",
"cpu": [
"x64"
],
@@ -304,9 +304,9 @@
}
},
"node_modules/@esbuild/netbsd-arm64": {
"version": "0.27.5",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.5.tgz",
"integrity": "sha512-3F/5EG8VHfN/I+W5cO1/SV2H9Q/5r7vcHabMnBqhHK2lTWOh3F8vixNzo8lqxrlmBtZVFpW8pmITHnq54+Tq4g==",
"version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.0.tgz",
"integrity": "sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==",
"cpu": [
"arm64"
],
@@ -321,9 +321,9 @@
}
},
"node_modules/@esbuild/netbsd-x64": {
"version": "0.27.5",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.5.tgz",
"integrity": "sha512-28t+Sj3CPN8vkMOlZotOmDgilQwVvxWZl7b8rxpn73Tt/gCnvrHxQUMng4uu3itdFvrtba/1nHejvxqz8xgEMA==",
"version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.0.tgz",
"integrity": "sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==",
"cpu": [
"x64"
],
@@ -338,9 +338,9 @@
}
},
"node_modules/@esbuild/openbsd-arm64": {
"version": "0.27.5",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.5.tgz",
"integrity": "sha512-Doz/hKtiuVAi9hMsBMpwBANhIZc8l238U2Onko3t2xUp8xtM0ZKdDYHMnm/qPFVthY8KtxkXaocwmMh6VolzMA==",
"version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.0.tgz",
"integrity": "sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==",
"cpu": [
"arm64"
],
@@ -355,9 +355,9 @@
}
},
"node_modules/@esbuild/openbsd-x64": {
"version": "0.27.5",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.5.tgz",
"integrity": "sha512-WfGVaa1oz5A7+ZFPkERIbIhKT4olvGl1tyzTRaB5yoZRLqC0KwaO95FeZtOdQj/oKkjW57KcVF944m62/0GYtA==",
"version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.0.tgz",
"integrity": "sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==",
"cpu": [
"x64"
],
@@ -372,9 +372,9 @@
}
},
"node_modules/@esbuild/openharmony-arm64": {
"version": "0.27.5",
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.5.tgz",
"integrity": "sha512-Xh+VRuh6OMh3uJ0JkCjI57l+DVe7VRGBYymen8rFPnTVgATBwA6nmToxM2OwTlSvrnWpPKkrQUj93+K9huYC6A==",
"version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.0.tgz",
"integrity": "sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==",
"cpu": [
"arm64"
],
@@ -389,9 +389,9 @@
}
},
"node_modules/@esbuild/sunos-x64": {
"version": "0.27.5",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.5.tgz",
"integrity": "sha512-aC1gpJkkaUADHuAdQfuVTnqVUTLqqUNhAvEwHwVWcnVVZvNlDPGA0UveZsfXJJ9T6k9Po4eHi3c02gbdwO3g6w==",
"version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.0.tgz",
"integrity": "sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==",
"cpu": [
"x64"
],
@@ -406,9 +406,9 @@
}
},
"node_modules/@esbuild/win32-arm64": {
"version": "0.27.5",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.5.tgz",
"integrity": "sha512-0UNx2aavV0fk6UpZcwXFLztA2r/k9jTUa7OW7SAea1VYUhkug99MW1uZeXEnPn5+cHOd0n8myQay6TlFnBR07w==",
"version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.0.tgz",
"integrity": "sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==",
"cpu": [
"arm64"
],
@@ -423,9 +423,9 @@
}
},
"node_modules/@esbuild/win32-ia32": {
"version": "0.27.5",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.5.tgz",
"integrity": "sha512-5nlJ3AeJWCTSzR7AEqVjT/faWyqKU86kCi1lLmxVqmNR+j4HrYdns+eTGjS/vmrzCIe8inGQckUadvS0+JkKdQ==",
"version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.0.tgz",
"integrity": "sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==",
"cpu": [
"ia32"
],
@@ -440,9 +440,9 @@
}
},
"node_modules/@esbuild/win32-x64": {
"version": "0.27.5",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.5.tgz",
"integrity": "sha512-PWypQR+d4FLfkhBIV+/kHsUELAnMpx1bRvvsn3p+/sAERbnCzFrtDRG2Xw5n+2zPxBK2+iaP+vetsRl4Ti7WgA==",
"version": "0.28.0",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.0.tgz",
"integrity": "sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==",
"cpu": [
"x64"
],
@@ -467,9 +467,9 @@
}
},
"node_modules/esbuild": {
"version": "0.27.5",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.5.tgz",
"integrity": "sha512-zdQoHBjuDqKsvV5OPaWansOwfSQ0Js+Uj9J85TBvj3bFW1JjWTSULMRwdQAc8qMeIScbClxeMK0jlrtB9linhA==",
"version": "0.28.0",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.0.tgz",
"integrity": "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
@@ -480,32 +480,32 @@
"node": ">=18"
},
"optionalDependencies": {
"@esbuild/aix-ppc64": "0.27.5",
"@esbuild/android-arm": "0.27.5",
"@esbuild/android-arm64": "0.27.5",
"@esbuild/android-x64": "0.27.5",
"@esbuild/darwin-arm64": "0.27.5",
"@esbuild/darwin-x64": "0.27.5",
"@esbuild/freebsd-arm64": "0.27.5",
"@esbuild/freebsd-x64": "0.27.5",
"@esbuild/linux-arm": "0.27.5",
"@esbuild/linux-arm64": "0.27.5",
"@esbuild/linux-ia32": "0.27.5",
"@esbuild/linux-loong64": "0.27.5",
"@esbuild/linux-mips64el": "0.27.5",
"@esbuild/linux-ppc64": "0.27.5",
"@esbuild/linux-riscv64": "0.27.5",
"@esbuild/linux-s390x": "0.27.5",
"@esbuild/linux-x64": "0.27.5",
"@esbuild/netbsd-arm64": "0.27.5",
"@esbuild/netbsd-x64": "0.27.5",
"@esbuild/openbsd-arm64": "0.27.5",
"@esbuild/openbsd-x64": "0.27.5",
"@esbuild/openharmony-arm64": "0.27.5",
"@esbuild/sunos-x64": "0.27.5",
"@esbuild/win32-arm64": "0.27.5",
"@esbuild/win32-ia32": "0.27.5",
"@esbuild/win32-x64": "0.27.5"
"@esbuild/aix-ppc64": "0.28.0",
"@esbuild/android-arm": "0.28.0",
"@esbuild/android-arm64": "0.28.0",
"@esbuild/android-x64": "0.28.0",
"@esbuild/darwin-arm64": "0.28.0",
"@esbuild/darwin-x64": "0.28.0",
"@esbuild/freebsd-arm64": "0.28.0",
"@esbuild/freebsd-x64": "0.28.0",
"@esbuild/linux-arm": "0.28.0",
"@esbuild/linux-arm64": "0.28.0",
"@esbuild/linux-ia32": "0.28.0",
"@esbuild/linux-loong64": "0.28.0",
"@esbuild/linux-mips64el": "0.28.0",
"@esbuild/linux-ppc64": "0.28.0",
"@esbuild/linux-riscv64": "0.28.0",
"@esbuild/linux-s390x": "0.28.0",
"@esbuild/linux-x64": "0.28.0",
"@esbuild/netbsd-arm64": "0.28.0",
"@esbuild/netbsd-x64": "0.28.0",
"@esbuild/openbsd-arm64": "0.28.0",
"@esbuild/openbsd-x64": "0.28.0",
"@esbuild/openharmony-arm64": "0.28.0",
"@esbuild/sunos-x64": "0.28.0",
"@esbuild/win32-arm64": "0.28.0",
"@esbuild/win32-ia32": "0.28.0",
"@esbuild/win32-x64": "0.28.0"
}
},
"node_modules/typescript": {

View File

@@ -13,7 +13,7 @@
"license": "AGPL-3.0",
"devDependencies": {
"@extism/js-pdk": "^1.0.1",
"esbuild": "^0.27.0",
"esbuild": "^0.28.0",
"typescript": "^6.0.0"
}
}

1440
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -32,6 +32,8 @@ onlyBuiltDependencies:
overrides:
canvas: 2.11.2
sharp: ^0.34.5
# pending docusaurus 3.10.1
webpackbar: ^7.0.0
packageExtensions:
nestjs-kysely:
dependencies:

View File

@@ -39,7 +39,7 @@
},
"dependencies": {
"@extism/extism": "2.0.0-rc13",
"@immich/sql-tools": "^0.3.2",
"@immich/sql-tools": "^0.5.1",
"@nestjs/bullmq": "^11.0.1",
"@nestjs/common": "^11.0.4",
"@nestjs/core": "^11.0.4",
@@ -84,7 +84,7 @@
"jose": "^6.0.0",
"js-yaml": "^4.1.0",
"jsonwebtoken": "^9.0.2",
"kysely": "0.28.15",
"kysely": "0.28.16",
"kysely-postgres-js": "^3.0.0",
"lodash": "^4.17.21",
"luxon": "^3.4.2",
@@ -99,7 +99,7 @@
"openid-client": "^6.3.3",
"pg": "^8.11.3",
"picomatch": "^4.0.2",
"postgres": "3.4.8",
"postgres": "3.4.9",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-email": "^5.0.0",

View File

@@ -0,0 +1,17 @@
import { Kysely, sql } from 'kysely';
export async function up(db: Kysely<any>): Promise<void> {
await sql`UPDATE "migration_overrides" SET "value" = '{"type":"index","name":"asset_id_timeline_notDeleted_idx","sql":"CREATE INDEX \\"asset_id_timeline_notDeleted_idx\\" ON \\"asset\\" (\\"id\\") WHERE (visibility = ''timeline'' AND \\"deletedAt\\" IS NULL);"}'::jsonb WHERE "name" = 'index_asset_id_timeline_notDeleted_idx';`.execute(db);
await sql`UPDATE "migration_overrides" SET "value" = '{"type":"index","name":"asset_localDateTime_month_idx","sql":"CREATE INDEX \\"asset_localDateTime_month_idx\\" ON \\"asset\\" (date_trunc(''MONTH''::text, (\\"localDateTime\\" AT TIME ZONE ''UTC''::text)) AT TIME ZONE ''UTC''::text);"}'::jsonb WHERE "name" = 'index_asset_localDateTime_month_idx';`.execute(db);
await sql`UPDATE "migration_overrides" SET "value" = '{"type":"index","name":"asset_localDateTime_idx","sql":"CREATE INDEX \\"asset_localDateTime_idx\\" ON \\"asset\\" ((\\"localDateTime\\" at time zone ''UTC'')::date);"}'::jsonb WHERE "name" = 'index_asset_localDateTime_idx';`.execute(db);
await sql`UPDATE "migration_overrides" SET "value" = '{"type":"index","name":"activity_like_idx","sql":"CREATE UNIQUE INDEX \\"activity_like_idx\\" ON \\"activity\\" (\\"assetId\\", \\"userId\\", \\"albumId\\") WHERE ((\\"isLiked\\" = true));"}'::jsonb WHERE "name" = 'index_activity_like_idx';`.execute(db);
await sql`UPDATE "migration_overrides" SET "value" = '{"type":"index","name":"asset_face_personId_assetId_notDeleted_isVisible_idx","sql":"CREATE INDEX \\"asset_face_personId_assetId_notDeleted_isVisible_idx\\" ON \\"asset_face\\" (\\"personId\\", \\"assetId\\") WHERE (\\"deletedAt\\" IS NULL AND \\"isVisible\\" IS TRUE);"}'::jsonb WHERE "name" = 'index_asset_face_personId_assetId_notDeleted_isVisible_idx';`.execute(db);
}
export async function down(db: Kysely<any>): Promise<void> {
await sql`UPDATE "migration_overrides" SET "value" = '{"sql":"CREATE INDEX \\"asset_localDateTime_month_idx\\" ON \\"asset\\" ((date_trunc(''MONTH''::text, (\\"localDateTime\\" AT TIME ZONE ''UTC''::text)) AT TIME ZONE ''UTC''::text));","name":"asset_localDateTime_month_idx","type":"index"}'::jsonb WHERE "name" = 'index_asset_localDateTime_month_idx';`.execute(db);
await sql`UPDATE "migration_overrides" SET "value" = '{"sql":"CREATE INDEX \\"asset_localDateTime_idx\\" ON \\"asset\\" (((\\"localDateTime\\" at time zone ''UTC'')::date));","name":"asset_localDateTime_idx","type":"index"}'::jsonb WHERE "name" = 'index_asset_localDateTime_idx';`.execute(db);
await sql`UPDATE "migration_overrides" SET "value" = '{"sql":"CREATE UNIQUE INDEX \\"activity_like_idx\\" ON \\"activity\\" (\\"assetId\\", \\"userId\\", \\"albumId\\") WHERE (\\"isLiked\\" = true);","name":"activity_like_idx","type":"index"}'::jsonb WHERE "name" = 'index_activity_like_idx';`.execute(db);
await sql`UPDATE "migration_overrides" SET "value" = '{"sql":"CREATE INDEX \\"asset_id_timeline_notDeleted_idx\\" ON \\"asset\\" (\\"id\\") WHERE visibility = ''timeline'' AND \\"deletedAt\\" IS NULL;","name":"asset_id_timeline_notDeleted_idx","type":"index"}'::jsonb WHERE "name" = 'index_asset_id_timeline_notDeleted_idx';`.execute(db);
await sql`UPDATE "migration_overrides" SET "value" = '{"sql":"CREATE INDEX \\"asset_face_personId_assetId_notDeleted_isVisible_idx\\" ON \\"asset_face\\" (\\"personId\\", \\"assetId\\") WHERE \\"deletedAt\\" IS NULL AND \\"isVisible\\" IS TRUE;","name":"asset_face_personId_assetId_notDeleted_isVisible_idx","type":"index"}'::jsonb WHERE "name" = 'index_asset_face_personId_assetId_notDeleted_isVisible_idx';`.execute(db);
}

View File

@@ -33,20 +33,20 @@ import { ASSET_CHECKSUM_CONSTRAINT } from 'src/utils/database';
name: ASSET_CHECKSUM_CONSTRAINT,
columns: ['ownerId', 'checksum'],
unique: true,
where: '("libraryId" IS NULL)',
where: '"libraryId" IS NULL',
})
@Index({
columns: ['ownerId', 'libraryId', 'checksum'],
unique: true,
where: '("libraryId" IS NOT NULL)',
where: '"libraryId" IS NOT NULL',
})
@Index({
name: 'asset_localDateTime_idx',
expression: `(("localDateTime" at time zone 'UTC')::date)`,
expression: `("localDateTime" at time zone 'UTC')::date`,
})
@Index({
name: 'asset_localDateTime_month_idx',
expression: `(date_trunc('MONTH'::text, ("localDateTime" AT TIME ZONE 'UTC'::text)) AT TIME ZONE 'UTC'::text)`,
expression: `date_trunc('MONTH'::text, ("localDateTime" AT TIME ZONE 'UTC'::text)) AT TIME ZONE 'UTC'::text`,
})
@Index({ columns: ['originalPath', 'libraryId'] })
@Index({ columns: ['id', 'stackId'] })

View File

@@ -27,5 +27,6 @@
"tsBuildInfoFile": "./dist/tsconfig.tsbuildinfo",
"noErrorTruncation": true
},
"include": ["src", "test"],
"exclude": ["dist", "node_modules", "upload"]
}

View File

@@ -1,8 +1,9 @@
{
"importOrder": ["<THIRD_PARTY_MODULES>", "<BUILTIN_MODULES>", "^\\$(.*)$", "^@test-data(.*)$", "^[./]"],
"importOrderSeparation": false,
"jsonRecursiveSort": true,
"organizeImportsSkipDestructiveCodeActions": true,
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }],
"plugins": ["prettier-plugin-organize-imports", "prettier-plugin-svelte", "prettier-plugin-sort-json"],
"plugins": ["@trivago/prettier-plugin-sort-imports", "prettier-plugin-svelte", "prettier-plugin-sort-json"],
"printWidth": 120,
"semi": true,
"singleQuote": true,

View File

@@ -5,11 +5,11 @@ import eslintPluginCompat from 'eslint-plugin-compat';
import eslintPluginSvelte from 'eslint-plugin-svelte';
import eslintPluginUnicorn from 'eslint-plugin-unicorn';
import globals from 'globals';
import parser from 'svelte-eslint-parser';
import typescriptEslint from 'typescript-eslint';
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import parser from 'svelte-eslint-parser';
import typescriptEslint from 'typescript-eslint';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

View File

@@ -80,6 +80,7 @@
"@testing-library/jest-dom": "^6.4.2",
"@testing-library/svelte": "^5.2.8",
"@testing-library/user-event": "^14.5.2",
"@trivago/prettier-plugin-sort-imports": "^6.0.2",
"@types/chromecast-caf-sender": "^1.0.11",
"@types/dom-to-image": "^2.6.7",
"@types/justified-layout": "^4.1.4",
@@ -88,7 +89,7 @@
"@types/qrcode": "^1.5.5",
"@vitest/coverage-v8": "^4.0.0",
"dotenv": "^17.0.0",
"eslint": "^10.0.0",
"eslint": "^10.2.1",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-compat": "^7.0.0",
"eslint-plugin-svelte": "^3.12.4",
@@ -97,11 +98,10 @@
"globals": "^17.0.0",
"happy-dom": "^20.0.0",
"prettier": "^3.7.4",
"prettier-plugin-organize-imports": "^4.0.0",
"prettier-plugin-sort-json": "^4.1.1",
"prettier-plugin-svelte": "^3.3.3",
"rollup-plugin-visualizer": "^7.0.0",
"svelte": "5.55.1",
"svelte": "5.55.2",
"svelte-check": "^4.4.6",
"svelte-eslint-parser": "^1.3.3",
"tailwindcss": "^4.2.2",

8
web/src/app.d.ts vendored
View File

@@ -1,3 +1,6 @@
import 'svelte-i18n';
import type en from '$i18n/en.json';
/// <reference types="@sveltejs/kit" />
// See https://kit.svelte.dev/docs/types#app
@@ -28,15 +31,12 @@ interface Element {
requestFullscreen?(options?: FullscreenOptions): Promise<void>;
}
import type en from '$i18n/en.json';
import 'svelte-i18n';
type NestedKeys<T, K = keyof T> = K extends keyof T & string
? `${K}` | (T[K] extends object ? `${K}.${NestedKeys<T[K]>}` : never)
: never;
declare module 'svelte-i18n' {
import type { InterpolationValues } from '$lib/elements/format-message.svelte';
import type { InterpolationValues } from '$lib/elements/format-message';
import type { Readable } from 'svelte/store';
type Translations = NestedKeys<typeof en>;

View File

@@ -1,6 +1,6 @@
import type { Handle } from '@sveltejs/kit';
import GoogleSans from '$lib/assets/fonts/GoogleSans/GoogleSans.ttf?url';
import GoogleSansCode from '$lib/assets/fonts/GoogleSansCode/GoogleSansCode.ttf?url';
import type { Handle } from '@sveltejs/kit';
// only used during the build to replace the variables from app.html
export const handle = (async ({ event, resolve }) => {

View File

@@ -1,8 +1,8 @@
import FocusTrapTest from '$lib/actions/__test__/focus-trap-test.svelte';
import { setDefaultTabbleOptions } from '$lib/utils/focus-util';
import { render, screen } from '@testing-library/svelte';
import userEvent from '@testing-library/user-event';
import { tick } from 'svelte';
import FocusTrapTest from '$lib/actions/__test__/FocusTrapTest.svelte';
import { setDefaultTabbleOptions } from '$lib/utils/focus-util';
setDefaultTabbleOptions({ displayCheck: 'none' });

View File

@@ -1,5 +1,5 @@
import { matchesShortcut } from '$lib/actions/shortcut';
import type { ActionReturn } from 'svelte/action';
import { matchesShortcut } from '$lib/actions/shortcut';
interface Options {
onOutclick?: () => void;

View File

@@ -1,6 +1,6 @@
import { shortcuts } from '$lib/actions/shortcut';
import { tick } from 'svelte';
import type { Action } from 'svelte/action';
import { shortcuts } from '$lib/actions/shortcut';
interface Options {
/**

View File

@@ -1,5 +1,5 @@
import { getTabbable } from '$lib/utils/focus-util';
import { tick } from 'svelte';
import { getTabbable } from '$lib/utils/focus-util';
interface Options {
/**

View File

@@ -1,5 +1,5 @@
import { shortcuts } from '$lib/actions/shortcut';
import type { Action } from 'svelte/action';
import { shortcuts } from '$lib/actions/shortcut';
/**
* Enables keyboard navigation (up and down arrows) for a list of elements.

View File

@@ -1,5 +1,5 @@
import { assetViewerManager } from '$lib/managers/asset-viewer-manager.svelte';
import { createZoomImageWheel } from '@zoom-image/core';
import { assetViewerManager } from '$lib/managers/asset-viewer-manager.svelte';
// Minimal touch shape — avoids importing DOM TouchEvent which isn't available in all TS targets.
type TouchEventLike = {

View File

@@ -1,8 +1,3 @@
import { goto } from '$app/navigation';
import { page } from '$app/state';
import { authManager } from '$lib/managers/auth-manager.svelte';
import { Route } from '$lib/route';
import { copyToClipboard } from '$lib/utils';
import { defaultProvider, screencastManager, themeManager, ThemePreference, type ActionItem } from '@immich/ui';
import {
mdiAccountMultipleOutline,
@@ -14,6 +9,11 @@ import {
mdiThemeLightDark,
} from '@mdi/js';
import type { MessageFormatter } from 'svelte-i18n';
import { goto } from '$app/navigation';
import { page } from '$app/state';
import { authManager } from '$lib/managers/auth-manager.svelte';
import { Route } from '$lib/route';
import { copyToClipboard } from '$lib/utils';
export const getPagesProvider = ($t: MessageFormatter) => {
const adminPages: ActionItem[] = [

View File

@@ -1,5 +1,5 @@
<script lang="ts">
import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
import MenuOption from '$lib/components/shared-components/context-menu/MenuOption.svelte';
import { isEnabled } from '$lib/utils';
import { type ActionItem } from '@immich/ui';

View File

@@ -1,6 +1,6 @@
<script lang="ts">
import AlphaBackground from '$lib/components/AlphaBackground.svelte';
import BrokenAsset from '$lib/components/assets/broken-asset.svelte';
import BrokenAsset from '$lib/components/assets/BrokenAsset.svelte';
import DelayedLoadingSpinner from '$lib/components/DelayedLoadingSpinner.svelte';
import ImageLayer from '$lib/components/ImageLayer.svelte';
import Thumbhash from '$lib/components/Thumbhash.svelte';

View File

@@ -1,5 +1,5 @@
<script lang="ts">
import ApiKeyGrid from '$lib/components/user-settings-page/user-api-key-grid.svelte';
import ApiKeyGrid from '$lib/components/user-settings-page/UserApiKeyGrid.svelte';
import { Permission } from '@immich/sdk';
import { Checkbox, IconButton, Input, Label } from '@immich/ui';
import { mdiClose } from '@mdi/js';

View File

@@ -1,6 +1,6 @@
import { fireEvent, render } from '@testing-library/svelte';
import Image from '$lib/components/Image.svelte';
import { cancelImageUrl } from '$lib/utils/sw-messaging';
import { fireEvent, render } from '@testing-library/svelte';
vi.mock('$lib/utils/sw-messaging', () => ({
cancelImageUrl: vi.fn(),

View File

@@ -1,5 +1,5 @@
import { renderWithTooltips } from '$tests/helpers';
import userEvent from '@testing-library/user-event';
import { renderWithTooltips } from '$tests/helpers';
import SharedLinkFormFields from './SharedLinkFormFields.svelte';
describe('SharedLinkFormFields component', () => {

View File

@@ -2,8 +2,8 @@
import SupportedDatetimePanel from '$lib/components/admin-settings/SupportedDatetimePanel.svelte';
import SupportedVariablesPanel from '$lib/components/admin-settings/SupportedVariablesPanel.svelte';
import SettingButtonsRow from '$lib/components/shared-components/settings/SystemConfigButtonRow.svelte';
import SettingInputField from '$lib/components/shared-components/settings/setting-input-field.svelte';
import SettingSwitch from '$lib/components/shared-components/settings/setting-switch.svelte';
import SettingInputField from '$lib/components/shared-components/settings/SettingInputField.svelte';
import SettingSwitch from '$lib/components/shared-components/settings/SettingSwitch.svelte';
import { SettingInputFieldType } from '$lib/constants';
import FormatMessage from '$lib/elements/FormatMessage.svelte';
import { authManager } from '$lib/managers/auth-manager.svelte';

View File

@@ -1,5 +1,5 @@
<script lang="ts">
import AlbumCover from '$lib/components/album-page/album-cover.svelte';
import AlbumCover from '$lib/components/album-page/AlbumCover.svelte';
import { authManager } from '$lib/managers/auth-manager.svelte';
import { getContextMenuPositionFromEvent, type ContextMenuPosition } from '$lib/utils/context-menu';
import { getShortDateRange } from '$lib/utils/date-time';

View File

@@ -1,5 +1,5 @@
<script lang="ts">
import AlbumCard from '$lib/components/album-page/album-card.svelte';
import AlbumCard from '$lib/components/album-page/AlbumCard.svelte';
import { Route } from '$lib/route';
import { albumViewSettings } from '$lib/stores/preferences.store';
import { type AlbumGroup, isAlbumGroupCollapsed, toggleAlbumGroupCollapsing } from '$lib/utils/album-utils';

View File

@@ -1,6 +1,6 @@
<script lang="ts">
import AssetCover from '$lib/components/sharedlinks-page/covers/asset-cover.svelte';
import NoCover from '$lib/components/sharedlinks-page/covers/no-cover.svelte';
import AssetCover from '$lib/components/sharedlinks-page/covers/AssetCover.svelte';
import NoCover from '$lib/components/sharedlinks-page/covers/NoCover.svelte';
import { getAssetMediaUrl } from '$lib/utils';
import { type AlbumResponseDto } from '@immich/sdk';
import { t } from 'svelte-i18n';

View File

@@ -1,6 +1,6 @@
<script lang="ts">
import { shortcut } from '$lib/actions/shortcut';
import AlbumMap from '$lib/components/album-page/album-map.svelte';
import AlbumMap from '$lib/components/album-page/AlbumMap.svelte';
import DownloadAction from '$lib/components/timeline/actions/DownloadAction.svelte';
import SelectAllAssets from '$lib/components/timeline/actions/SelectAllAction.svelte';
import AssetSelectControlBar from '$lib/components/timeline/AssetSelectControlBar.svelte';
@@ -20,9 +20,9 @@
import { ActionButton, IconButton, Logo } from '@immich/ui';
import { mdiDownload, mdiFileImagePlusOutline, mdiPresentationPlay } from '@mdi/js';
import { t } from 'svelte-i18n';
import ControlAppBar from '../shared-components/control-app-bar.svelte';
import ThemeButton from '../shared-components/theme-button.svelte';
import AlbumSummary from './album-summary.svelte';
import ControlAppBar from '../shared-components/ControlAppBar.svelte';
import ThemeButton from '../shared-components/ThemeButton.svelte';
import AlbumSummary from './AlbumSummary.svelte';
interface Props {
sharedLink: SharedLinkResponseDto;

View File

@@ -1,9 +1,9 @@
<script lang="ts">
import AlbumCardGroup from '$lib/components/album-page/album-card-group.svelte';
import AlbumsTable from '$lib/components/album-page/albums-table.svelte';
import AlbumCardGroup from '$lib/components/album-page/AlbumCardGroup.svelte';
import AlbumsTable from '$lib/components/album-page/AlbumsTable.svelte';
import OnEvents from '$lib/components/OnEvents.svelte';
import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
import RightClickContextMenu from '$lib/components/shared-components/context-menu/right-click-context-menu.svelte';
import MenuOption from '$lib/components/shared-components/context-menu/MenuOption.svelte';
import RightClickContextMenu from '$lib/components/shared-components/context-menu/RightClickContextMenu.svelte';
import { authManager } from '$lib/managers/auth-manager.svelte';
import AlbumEditModal from '$lib/modals/AlbumEditModal.svelte';
import AlbumOptionsModal from '$lib/modals/AlbumOptionsModal.svelte';

View File

@@ -1,6 +1,6 @@
<script lang="ts">
import AlbumTableHeader from '$lib/components/album-page/albums-table-header.svelte';
import AlbumTableRow from '$lib/components/album-page/albums-table-row.svelte';
import AlbumTableHeader from '$lib/components/album-page/AlbumsTableHeader.svelte';
import AlbumTableRow from '$lib/components/album-page/AlbumsTableRow.svelte';
import { AlbumGroupBy, albumViewSettings } from '$lib/stores/preferences.store';
import {
isAlbumGroupCollapsed,

View File

@@ -1,11 +1,11 @@
import { sdkMock } from '$lib/__mocks__/sdk.mock';
import { renderWithTooltips } from '$tests/helpers';
import { albumFactory } from '@test-data/factories/album-factory';
import '@testing-library/jest-dom';
import { render, waitFor, type RenderResult } from '@testing-library/svelte';
import userEvent from '@testing-library/user-event';
import { init, register, waitLocale } from 'svelte-i18n';
import AlbumCard from '../album-card.svelte';
import { sdkMock } from '$lib/__mocks__/sdk.mock';
import { renderWithTooltips } from '$tests/helpers';
import { albumFactory } from '@test-data/factories/album-factory';
import AlbumCard from '../AlbumCard.svelte';
const onShowContextMenu = vi.fn();

View File

@@ -1,7 +1,7 @@
import AlbumCover from '$lib/components/album-page/album-cover.svelte';
import { render } from '@testing-library/svelte';
import AlbumCover from '$lib/components/album-page/AlbumCover.svelte';
import { getAssetMediaUrl } from '$lib/utils';
import { albumFactory } from '@test-data/factories/album-factory';
import { render } from '@testing-library/svelte';
vi.mock('$lib/utils');

View File

@@ -1,7 +1,7 @@
<script lang="ts">
import { shortcut } from '$lib/actions/shortcut';
import ButtonContextMenu from '$lib/components/shared-components/context-menu/button-context-menu.svelte';
import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
import ButtonContextMenu from '$lib/components/shared-components/context-menu/ButtonContextMenu.svelte';
import MenuOption from '$lib/components/shared-components/context-menu/MenuOption.svelte';
import { timeBeforeShowLoadingSpinner } from '$lib/constants';
import { activityManager } from '$lib/managers/activity-manager.svelte';
import { assetViewerManager } from '$lib/managers/asset-viewer-manager.svelte';
@@ -18,7 +18,7 @@
import * as luxon from 'luxon';
import { t } from 'svelte-i18n';
import { fromAction } from 'svelte/attachments';
import UserAvatar from '../shared-components/user-avatar.svelte';
import UserAvatar from '../shared-components/UserAvatar.svelte';
const units: Intl.RelativeTimeFormatUnit[] = ['year', 'month', 'week', 'day', 'hour', 'minute', 'second'];

View File

@@ -7,7 +7,7 @@
import { Icon } from '@immich/ui';
import { mdiCheckCircle } from '@mdi/js';
import type { Action } from 'svelte/action';
import AlbumListItemDetails from './album-list-item-details.svelte';
import AlbumListItemDetails from './AlbumListItemDetails.svelte';
interface Props {
album: AlbumResponseDto;

View File

@@ -1,14 +1,14 @@
import { updateAsset } from '@immich/sdk';
import { fireEvent, waitFor } from '@testing-library/svelte';
import { getAnimateMock } from '$lib/__mocks__/animate.mock';
import { getResizeObserverMock } from '$lib/__mocks__/resize-observer.mock';
import { authManager } from '$lib/managers/auth-manager.svelte';
import { SlideshowState, slideshowStore } from '$lib/stores/slideshow.store';
import { renderWithTooltips } from '$tests/helpers';
import { updateAsset } from '@immich/sdk';
import { assetFactory } from '@test-data/factories/asset-factory';
import { preferencesFactory } from '@test-data/factories/preferences-factory';
import { userAdminFactory } from '@test-data/factories/user-factory';
import { fireEvent, waitFor } from '@testing-library/svelte';
import AssetViewer from './asset-viewer.svelte';
import AssetViewer from './AssetViewer.svelte';
vi.mock('$lib/managers/feature-flags-manager.svelte', () => ({
featureFlagsManager: {

View File

@@ -2,9 +2,9 @@
import { browser } from '$app/environment';
import { focusTrap } from '$lib/actions/focus-trap';
import type { Action, OnAction, PreAction } from '$lib/components/asset-viewer/actions/action';
import NextAssetAction from '$lib/components/asset-viewer/actions/next-asset-action.svelte';
import PreviousAssetAction from '$lib/components/asset-viewer/actions/previous-asset-action.svelte';
import AssetViewerNavBar from '$lib/components/asset-viewer/asset-viewer-nav-bar.svelte';
import NextAssetAction from '$lib/components/asset-viewer/actions/NextAssetAction.svelte';
import PreviousAssetAction from '$lib/components/asset-viewer/actions/PreviousAssetAction.svelte';
import AssetViewerNavBar from '$lib/components/asset-viewer/AssetViewerNavBar.svelte';
import { preloadManager } from '$lib/components/asset-viewer/PreloadManager.svelte';
import OnEvents from '$lib/components/OnEvents.svelte';
import { AssetAction, ProjectionType } from '$lib/constants';
@@ -38,17 +38,17 @@
import type { SwipeCustomEvent } from 'svelte-gestures';
import { t } from 'svelte-i18n';
import { fly } from 'svelte/transition';
import Thumbnail from '../assets/thumbnail/thumbnail.svelte';
import ActivityStatus from './activity-status.svelte';
import ActivityViewer from './activity-viewer.svelte';
import DetailPanel from './detail-panel.svelte';
import EditorPanel from './editor/editor-panel.svelte';
import CropArea from './editor/transform-tool/crop-area.svelte';
import ImagePanoramaViewer from './image-panorama-viewer.svelte';
import OcrButton from './ocr-button.svelte';
import PhotoViewer from './photo-viewer.svelte';
import SlideshowBar from './slideshow-bar.svelte';
import VideoViewer from './video-wrapper-viewer.svelte';
import Thumbnail from '../assets/thumbnail/Thumbnail.svelte';
import ActivityStatus from './ActivityStatus.svelte';
import ActivityViewer from './ActivityViewer.svelte';
import DetailPanel from './DetailPanel.svelte';
import EditorPanel from './editor/EditorPanel.svelte';
import CropArea from './editor/transform-tool/CropArea.svelte';
import ImagePanoramaViewer from './ImagePanoramaViewer.svelte';
import OcrButton from './OcrButton.svelte';
import PhotoViewer from './PhotoViewer.svelte';
import SlideshowBar from './SlideshowBar.svelte';
import VideoViewer from './VideoWrapperViewer.svelte';
export type AssetCursor = {
current: AssetResponseDto;

View File

@@ -1,11 +1,11 @@
import '@testing-library/jest-dom';
import { getResizeObserverMock } from '$lib/__mocks__/resize-observer.mock';
import { authManager } from '$lib/managers/auth-manager.svelte';
import { renderWithTooltips } from '$tests/helpers';
import { assetFactory } from '@test-data/factories/asset-factory';
import { preferencesFactory } from '@test-data/factories/preferences-factory';
import { userAdminFactory } from '@test-data/factories/user-factory';
import '@testing-library/jest-dom';
import AssetViewerNavBar from './asset-viewer-nav-bar.svelte';
import AssetViewerNavBar from './AssetViewerNavBar.svelte';
describe('AssetViewerNavBar component', () => {
const additionalProps = {

View File

@@ -2,22 +2,22 @@
import { goto } from '$app/navigation';
import ActionMenuItem from '$lib/components/ActionMenuItem.svelte';
import type { OnAction, PreAction } from '$lib/components/asset-viewer/actions/action';
import AddToStackAction from '$lib/components/asset-viewer/actions/add-to-stack-action.svelte';
import ArchiveAction from '$lib/components/asset-viewer/actions/archive-action.svelte';
import DeleteAction from '$lib/components/asset-viewer/actions/delete-action.svelte';
import KeepThisDeleteOthersAction from '$lib/components/asset-viewer/actions/keep-this-delete-others.svelte';
import RatingAction from '$lib/components/asset-viewer/actions/rating-action.svelte';
import RemoveAssetFromStack from '$lib/components/asset-viewer/actions/remove-asset-from-stack.svelte';
import RestoreAction from '$lib/components/asset-viewer/actions/restore-action.svelte';
import SetAlbumCoverAction from '$lib/components/asset-viewer/actions/set-album-cover-action.svelte';
import SetFeaturedPhotoAction from '$lib/components/asset-viewer/actions/set-person-featured-action.svelte';
import SetProfilePictureAction from '$lib/components/asset-viewer/actions/set-profile-picture-action.svelte';
import SetStackPrimaryAsset from '$lib/components/asset-viewer/actions/set-stack-primary-asset.svelte';
import SetVisibilityAction from '$lib/components/asset-viewer/actions/set-visibility-action.svelte';
import UnstackAction from '$lib/components/asset-viewer/actions/unstack-action.svelte';
import AddToStackAction from '$lib/components/asset-viewer/actions/AddToStackAction.svelte';
import ArchiveAction from '$lib/components/asset-viewer/actions/ArchiveAction.svelte';
import DeleteAction from '$lib/components/asset-viewer/actions/DeleteAction.svelte';
import KeepThisDeleteOthersAction from '$lib/components/asset-viewer/actions/KeepThisDeleteOthers.svelte';
import RatingAction from '$lib/components/asset-viewer/actions/RatingAction.svelte';
import RemoveAssetFromStack from '$lib/components/asset-viewer/actions/RemoveAssetFromStack.svelte';
import RestoreAction from '$lib/components/asset-viewer/actions/RestoreAction.svelte';
import SetAlbumCoverAction from '$lib/components/asset-viewer/actions/SetAlbumCoverAction.svelte';
import SetFeaturedPhotoAction from '$lib/components/asset-viewer/actions/SetPersonFeaturedAction.svelte';
import SetProfilePictureAction from '$lib/components/asset-viewer/actions/SetProfilePictureAction.svelte';
import SetStackPrimaryAsset from '$lib/components/asset-viewer/actions/SetStackPrimaryAsset.svelte';
import SetVisibilityAction from '$lib/components/asset-viewer/actions/SetVisibilityAction.svelte';
import UnstackAction from '$lib/components/asset-viewer/actions/UnstackAction.svelte';
import LoadingDots from '$lib/components/LoadingDots.svelte';
import ButtonContextMenu from '$lib/components/shared-components/context-menu/button-context-menu.svelte';
import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
import ButtonContextMenu from '$lib/components/shared-components/context-menu/ButtonContextMenu.svelte';
import MenuOption from '$lib/components/shared-components/context-menu/MenuOption.svelte';
import RemoveFromAlbumAction from '$lib/components/timeline/actions/RemoveFromAlbumAction.svelte';
import { assetViewerManager } from '$lib/managers/asset-viewer-manager.svelte';
import { authManager } from '$lib/managers/auth-manager.svelte';

View File

@@ -1,10 +1,10 @@
<script lang="ts">
import { goto } from '$app/navigation';
import DetailPanelDate from '$lib/components/asset-viewer/detail-panel-date.svelte';
import DetailPanelDescription from '$lib/components/asset-viewer/detail-panel-description.svelte';
import DetailPanelLocation from '$lib/components/asset-viewer/detail-panel-location.svelte';
import DetailPanelRating from '$lib/components/asset-viewer/detail-panel-star-rating.svelte';
import DetailPanelTags from '$lib/components/asset-viewer/detail-panel-tags.svelte';
import DetailPanelDate from '$lib/components/asset-viewer/DetailPanelDate.svelte';
import DetailPanelDescription from '$lib/components/asset-viewer/DetailPanelDescription.svelte';
import DetailPanelLocation from '$lib/components/asset-viewer/DetailPanelLocation.svelte';
import DetailPanelRating from '$lib/components/asset-viewer/DetailPanelStarRating.svelte';
import DetailPanelTags from '$lib/components/asset-viewer/DetailPanelTags.svelte';
import { timeToLoadTheMap } from '$lib/constants';
import { assetViewerManager } from '$lib/managers/asset-viewer-manager.svelte';
import { authManager } from '$lib/managers/auth-manager.svelte';
@@ -40,11 +40,11 @@
import { onDestroy } from 'svelte';
import { t } from 'svelte-i18n';
import { slide } from 'svelte/transition';
import ImageThumbnail from '../assets/thumbnail/image-thumbnail.svelte';
import PersonSidePanel from '../faces-page/person-side-panel.svelte';
import ImageThumbnail from '../assets/thumbnail/ImageThumbnail.svelte';
import PersonSidePanel from '../faces-page/PersonSidePanel.svelte';
import OnEvents from '../OnEvents.svelte';
import UserAvatar from '../shared-components/user-avatar.svelte';
import AlbumListItemDetails from './album-list-item-details.svelte';
import UserAvatar from '../shared-components/UserAvatar.svelte';
import AlbumListItemDetails from './AlbumListItemDetails.svelte';
interface Props {
asset: AssetResponseDto;
@@ -393,7 +393,7 @@
{#if latlng && featureFlagsManager.value.map}
<div class="h-90">
{#await import('$lib/components/shared-components/map/map.svelte')}
{#await import('$lib/components/shared-components/map/Map.svelte')}
{#await delay(timeToLoadTheMap) then}
<!-- show the loading spinner only if loading the map takes too much time -->
<div class="flex items-center justify-center h-full w-full">

View File

@@ -1,8 +1,8 @@
import { assetFactory } from '@test-data/factories/asset-factory';
import '@testing-library/jest-dom';
import { render, screen } from '@testing-library/svelte';
import userEvent from '@testing-library/user-event';
import DetailPanelDescription from './detail-panel-description.svelte';
import { assetFactory } from '@test-data/factories/asset-factory';
import DetailPanelDescription from './DetailPanelDescription.svelte';
describe('DetailPanelDescription', () => {
it('clears unsaved draft on asset change', async () => {

View File

@@ -21,7 +21,7 @@
</script>
<div transition:fade={{ duration: 150 }} class="flex h-full select-none place-content-center place-items-center">
{#await Promise.all([loadAssetData(assetId), import('./photo-sphere-viewer-adapter.svelte')])}
{#await Promise.all([loadAssetData(assetId), import('./PhotoSphereViewerAdapter.svelte')])}
<LoadingSpinner />
{:then [data, { default: PhotoSphereViewer }]}
<PhotoSphereViewer panorama={data} originalPanorama={getAssetUrl({ asset, forceOriginal: true })} />

View File

@@ -2,9 +2,9 @@
import { shortcuts } from '$lib/actions/shortcut';
import { zoomImageAction } from '$lib/actions/zoom-image';
import AdaptiveImage from '$lib/components/AdaptiveImage.svelte';
import FaceEditor from '$lib/components/asset-viewer/face-editor/face-editor.svelte';
import FaceEditor from '$lib/components/asset-viewer/face-editor/FaceEditor.svelte';
import Thumbhash from '$lib/components/Thumbhash.svelte';
import OcrBoundingBox from '$lib/components/asset-viewer/ocr-bounding-box.svelte';
import OcrBoundingBox from '$lib/components/asset-viewer/OcrBoundingBox.svelte';
import AssetViewerEvents from '$lib/components/AssetViewerEvents.svelte';
import { assetViewerManager } from '$lib/managers/asset-viewer-manager.svelte';
import { castManager } from '$lib/managers/cast-manager.svelte';
@@ -22,7 +22,7 @@
import { onDestroy, untrack } from 'svelte';
import { useSwipe, type SwipeCustomEvent } from 'svelte-gestures';
import { t } from 'svelte-i18n';
import type { AssetCursor } from './asset-viewer.svelte';
import type { AssetCursor } from './AssetViewer.svelte';
type Props = {
cursor: AssetCursor;

View File

@@ -1,7 +1,7 @@
import type { AssetResponseDto, SharedLinkResponseDto } from '@immich/sdk';
import { loadImage } from '$lib/actions/image-loader.svelte';
import { getAssetUrls } from '$lib/utils';
import { AdaptiveImageLoader, type QualityList } from '$lib/utils/adaptive-image-loader.svelte';
import type { AssetResponseDto, SharedLinkResponseDto } from '@immich/sdk';
type AssetCursor = {
current: AssetResponseDto;

View File

@@ -1,6 +1,6 @@
<script lang="ts">
import { shortcuts, type ShortcutOptions } from '$lib/actions/shortcut';
import ProgressBar from '$lib/components/shared-components/progress-bar/progress-bar.svelte';
import ProgressBar from '$lib/components/shared-components/progress-bar/ProgressBar.svelte';
import { ProgressBarStatus } from '$lib/constants';
import { languageManager } from '$lib/managers/language-manager.svelte';
import SlideshowSettingsModal from '$lib/modals/SlideshowSettingsModal.svelte';

View File

@@ -1,6 +1,6 @@
<script lang="ts">
import FaceEditor from '$lib/components/asset-viewer/face-editor/face-editor.svelte';
import VideoRemoteViewer from '$lib/components/asset-viewer/video-remote-viewer.svelte';
import FaceEditor from '$lib/components/asset-viewer/face-editor/FaceEditor.svelte';
import VideoRemoteViewer from '$lib/components/asset-viewer/VideoRemoteViewer.svelte';
import { assetViewerFadeDuration } from '$lib/constants';
import { castManager } from '$lib/managers/cast-manager.svelte';
import { assetViewerManager } from '$lib/managers/asset-viewer-manager.svelte';

View File

@@ -12,7 +12,7 @@
const { asset }: Props = $props();
const modules = Promise.all([
import('./photo-sphere-viewer-adapter.svelte').then((module) => module.default),
import('./PhotoSphereViewerAdapter.svelte').then((module) => module.default),
import('@photo-sphere-viewer/equirectangular-video-adapter').then((module) => module.EquirectangularVideoAdapter),
import('@photo-sphere-viewer/video-plugin').then((module) => module.VideoPlugin),
import('@photo-sphere-viewer/video-plugin/index.css'),

View File

@@ -1,6 +1,6 @@
<script lang="ts">
import VideoNativeViewer from '$lib/components/asset-viewer/video-native-viewer.svelte';
import VideoPanoramaViewer from '$lib/components/asset-viewer/video-panorama-viewer.svelte';
import VideoNativeViewer from '$lib/components/asset-viewer/VideoNativeViewer.svelte';
import VideoPanoramaViewer from '$lib/components/asset-viewer/VideoPanoramaViewer.svelte';
import { ProjectionType } from '$lib/constants';
import type { AssetResponseDto } from '@immich/sdk';

View File

@@ -1,5 +1,5 @@
<script lang="ts">
import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
import MenuOption from '$lib/components/shared-components/context-menu/MenuOption.svelte';
import { AssetAction } from '$lib/constants';
import { openFileUploadDialog } from '$lib/utils/file-uploader';
import { createStack, type AssetResponseDto, type StackResponseDto } from '@immich/sdk';

View File

@@ -1,7 +1,7 @@
<script lang="ts">
import { shortcut } from '$lib/actions/shortcut';
import type { OnAction, PreAction } from '$lib/components/asset-viewer/actions/action';
import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
import MenuOption from '$lib/components/shared-components/context-menu/MenuOption.svelte';
import { AssetAction } from '$lib/constants';
import { toggleArchive } from '$lib/utils/asset-utils';
import { toTimelineAsset } from '$lib/utils/timeline-util';

View File

@@ -1,8 +1,8 @@
import { renderWithTooltips } from '$tests/helpers';
import type { AssetResponseDto } from '@immich/sdk';
import { assetFactory } from '@test-data/factories/asset-factory';
import '@testing-library/jest-dom';
import DeleteAction from './delete-action.svelte';
import { renderWithTooltips } from '$tests/helpers';
import { assetFactory } from '@test-data/factories/asset-factory';
import DeleteAction from './DeleteAction.svelte';
let asset: AssetResponseDto;

View File

@@ -1,5 +1,5 @@
<script lang="ts">
import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
import MenuOption from '$lib/components/shared-components/context-menu/MenuOption.svelte';
import { AssetAction } from '$lib/constants';
import { keepThisDeleteOthers } from '$lib/utils/asset-utils';
import { toTimelineAsset } from '$lib/utils/timeline-util';

View File

@@ -3,7 +3,7 @@
import { Icon } from '@immich/ui';
import { mdiChevronRight } from '@mdi/js';
import { t } from 'svelte-i18n';
import NavigationArea from '../navigation-area.svelte';
import NavigationArea from '../NavigationArea.svelte';
interface Props {
onNextAsset: () => void;

View File

@@ -3,7 +3,7 @@
import { Icon } from '@immich/ui';
import { mdiChevronLeft } from '@mdi/js';
import { t } from 'svelte-i18n';
import NavigationArea from '../navigation-area.svelte';
import NavigationArea from '../NavigationArea.svelte';
interface Props {
onPreviousAsset: () => void;

View File

@@ -1,5 +1,5 @@
<script lang="ts">
import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
import MenuOption from '$lib/components/shared-components/context-menu/MenuOption.svelte';
import { AssetAction } from '$lib/constants';
import { removeAssetFromStack, type AssetResponseDto, type StackResponseDto } from '@immich/sdk';

View File

@@ -1,5 +1,5 @@
<script lang="ts">
import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
import MenuOption from '$lib/components/shared-components/context-menu/MenuOption.svelte';
import { AssetAction } from '$lib/constants';
import { handleError } from '$lib/utils/handle-error';
import { toTimelineAsset } from '$lib/utils/timeline-util';

View File

@@ -1,5 +1,5 @@
<script lang="ts">
import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
import MenuOption from '$lib/components/shared-components/context-menu/MenuOption.svelte';
import { eventManager } from '$lib/managers/event-manager.svelte';
import { handleError } from '$lib/utils/handle-error';
import { updateAlbumInfo, type AlbumResponseDto, type AssetResponseDto } from '@immich/sdk';

View File

@@ -1,5 +1,5 @@
<script lang="ts">
import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
import MenuOption from '$lib/components/shared-components/context-menu/MenuOption.svelte';
import { AssetAction } from '$lib/constants';
import { handleError } from '$lib/utils/handle-error';
import { updatePerson, type AssetResponseDto, type PersonResponseDto } from '@immich/sdk';

View File

@@ -1,5 +1,5 @@
<script lang="ts">
import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
import MenuOption from '$lib/components/shared-components/context-menu/MenuOption.svelte';
import ProfileImageCropperModal from '$lib/modals/ProfileImageCropperModal.svelte';
import type { AssetResponseDto } from '@immich/sdk';
import { modalManager } from '@immich/ui';

View File

@@ -1,5 +1,5 @@
<script lang="ts">
import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
import MenuOption from '$lib/components/shared-components/context-menu/MenuOption.svelte';
import { AssetAction } from '$lib/constants';
import { updateStack, type AssetResponseDto, type StackResponseDto } from '@immich/sdk';

View File

@@ -1,5 +1,5 @@
<script lang="ts">
import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
import MenuOption from '$lib/components/shared-components/context-menu/MenuOption.svelte';
import { AssetAction } from '$lib/constants';
import type { TimelineAsset } from '$lib/managers/timeline-manager/types';
import { handleError } from '$lib/utils/handle-error';

View File

@@ -1,5 +1,5 @@
<script lang="ts">
import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
import MenuOption from '$lib/components/shared-components/context-menu/MenuOption.svelte';
import { AssetAction } from '$lib/constants';
import { deleteStack } from '$lib/utils/asset-utils';
import { toTimelineAsset } from '$lib/utils/timeline-util';

View File

@@ -1,6 +1,6 @@
import type { AssetResponseDto, PersonResponseDto, StackResponseDto } from '@immich/sdk';
import type { AssetAction } from '$lib/constants';
import type { TimelineAsset } from '$lib/managers/timeline-manager/types';
import type { AssetResponseDto, PersonResponseDto, StackResponseDto } from '@immich/sdk';
type ActionMap = {
[AssetAction.ARCHIVE]: { asset: TimelineAsset };

Some files were not shown because too many files have changed in this diff Show More