Compare commits

..

8 Commits

Author SHA1 Message Date
github-actions
089085fcdb chore: version v1.136.0 2025-07-24 14:24:38 +00:00
Jason Rasmussen
fc68cf4f32 chore: remove migration (#20129) 2025-07-24 14:11:53 +00:00
Alex
0051a9bba5 fix(web): Revert prevent flashing white background in dark mode on page load/reload (#19934) (#20122)
Revert "fix(web): prevent flashing white background in dark mode on page load/reload (#19934)"

This reverts commit 32f23b8d38.
2025-07-24 09:45:38 +02:00
Daniel Dietzler
f27bdf7523 chore: migrate to UI modal manager (#20116) 2025-07-23 17:27:09 -04:00
Daniel Dietzler
c1c9f30ea4 chore: migrate to immich/ui confirm modal (#20114) 2025-07-23 22:56:56 +02:00
Jason Rasmussen
bc8cb9b671 fix: default route permission (#20113) 2025-07-23 16:56:38 -04:00
Jason Rasmussen
a675922172 fix: unset prewarn param (#20109) 2025-07-23 16:52:59 -04:00
Jason Rasmussen
2bead445bd docs: remove outdated note (#20110) 2025-07-23 16:00:19 -04:00
99 changed files with 225 additions and 572 deletions

6
cli/package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "@immich/cli",
"version": "2.2.72",
"version": "2.2.73",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@immich/cli",
"version": "2.2.72",
"version": "2.2.73",
"license": "GNU Affero General Public License version 3",
"dependencies": {
"chokidar": "^4.0.3",
@@ -54,7 +54,7 @@
},
"../open-api/typescript-sdk": {
"name": "@immich/sdk",
"version": "1.135.3",
"version": "1.136.0",
"dev": true,
"license": "GNU Affero General Public License version 3",
"dependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "@immich/cli",
"version": "2.2.72",
"version": "2.2.73",
"description": "Command Line Interface (CLI) for Immich",
"type": "module",
"exports": "./dist/index.js",

View File

@@ -29,29 +29,26 @@ These environment variables are used by the `docker-compose.yml` file and do **N
## General
| Variable | Description | Default | Containers | Workers |
| :---------------------------------- | :---------------------------------------------------------------------------------------- | :---------------------------------: | :----------------------- | :----------------- |
| `TZ` | Timezone | <sup>\*1</sup> | server | microservices |
| `IMMICH_ENV` | Environment (production, development) | `production` | server, machine learning | api, microservices |
| `IMMICH_LOG_LEVEL` | Log level (verbose, debug, log, warn, error) | `log` | server, machine learning | api, microservices |
| `IMMICH_MEDIA_LOCATION` | Media location inside the container ⚠️**You probably shouldn't set this**<sup>\*2</sup>⚠️ | `/usr/src/app/upload`<sup>\*3</sup> | server | api, microservices |
| `IMMICH_CONFIG_FILE` | Path to config file | | server | api, microservices |
| `NO_COLOR` | Set to `true` to disable color-coded log output | `false` | server, machine learning | |
| `CPU_CORES` | Number of cores available to the Immich server | auto-detected CPU core count | server | |
| `IMMICH_API_METRICS_PORT` | Port for the OTEL metrics | `8081` | server | api |
| `IMMICH_MICROSERVICES_METRICS_PORT` | Port for the OTEL metrics | `8082` | server | microservices |
| `IMMICH_PROCESS_INVALID_IMAGES` | When `true`, generate thumbnails for invalid images | | server | microservices |
| `IMMICH_TRUSTED_PROXIES` | List of comma-separated IPs set as trusted proxies | | server | api |
| `IMMICH_IGNORE_MOUNT_CHECK_ERRORS` | See [System Integrity](/docs/administration/system-integrity) | | server | api, microservices |
| Variable | Description | Default | Containers | Workers |
| :---------------------------------- | :---------------------------------------------------------------------------------------- | :--------------------------: | :----------------------- | :----------------- |
| `TZ` | Timezone | <sup>\*1</sup> | server | microservices |
| `IMMICH_ENV` | Environment (production, development) | `production` | server, machine learning | api, microservices |
| `IMMICH_LOG_LEVEL` | Log level (verbose, debug, log, warn, error) | `log` | server, machine learning | api, microservices |
| `IMMICH_MEDIA_LOCATION` | Media location inside the container ⚠️**You probably shouldn't set this**<sup>\*2</sup>⚠️ | `/usr/src/app/upload` | server | api, microservices |
| `IMMICH_CONFIG_FILE` | Path to config file | | server | api, microservices |
| `NO_COLOR` | Set to `true` to disable color-coded log output | `false` | server, machine learning | |
| `CPU_CORES` | Number of cores available to the Immich server | auto-detected CPU core count | server | |
| `IMMICH_API_METRICS_PORT` | Port for the OTEL metrics | `8081` | server | api |
| `IMMICH_MICROSERVICES_METRICS_PORT` | Port for the OTEL metrics | `8082` | server | microservices |
| `IMMICH_PROCESS_INVALID_IMAGES` | When `true`, generate thumbnails for invalid images | | server | microservices |
| `IMMICH_TRUSTED_PROXIES` | List of comma-separated IPs set as trusted proxies | | server | api |
| `IMMICH_IGNORE_MOUNT_CHECK_ERRORS` | See [System Integrity](/docs/administration/system-integrity) | | server | api, microservices |
\*1: `TZ` should be set to a `TZ identifier` from [this list][tz-list]. For example, `TZ="Etc/UTC"`.
`TZ` is used by `exiftool` as a fallback in case the timezone cannot be determined from the image metadata. It is also used for logfile timestamps and cron job execution.
\*2: This path is where the Immich code looks for the files, which is internal to the docker container. Setting it to a path on your host will certainly break things, you should use the `UPLOAD_LOCATION` variable instead.
\*3: With the default `WORKDIR` of `/usr/src/app`, this path will resolve to `/usr/src/app/upload`.
It only needs to be set if the Immich deployment method is changing.
## Workers
| Variable | Description | Default | Containers |

View File

@@ -1,4 +1,8 @@
[
{
"label": "v1.136.0",
"url": "https://v1.136.0.archive.immich.app"
},
{
"label": "v1.135.3",
"url": "https://v1.135.3.archive.immich.app"

8
e2e/package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "immich-e2e",
"version": "1.135.3",
"version": "1.136.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "immich-e2e",
"version": "1.135.3",
"version": "1.136.0",
"license": "GNU Affero General Public License version 3",
"devDependencies": {
"@eslint/eslintrc": "^3.1.0",
@@ -46,7 +46,7 @@
},
"../cli": {
"name": "@immich/cli",
"version": "2.2.72",
"version": "2.2.73",
"dev": true,
"license": "GNU Affero General Public License version 3",
"dependencies": {
@@ -95,7 +95,7 @@
},
"../open-api/typescript-sdk": {
"name": "@immich/sdk",
"version": "1.135.3",
"version": "1.136.0",
"dev": true,
"license": "GNU Affero General Public License version 3",
"dependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "immich-e2e",
"version": "1.135.3",
"version": "1.136.0",
"description": "",
"main": "index.js",
"type": "module",

View File

@@ -817,7 +817,6 @@
"edit_avatar": "Edit avatar",
"edit_date": "Edit date",
"edit_date_and_time": "Edit date and time",
"edit_date_time_action_prompt": "{count} date and time edited",
"edit_description": "Edit description",
"edit_description_prompt": "Please select a new description:",
"edit_exclusion_pattern": "Edit exclusion pattern",

View File

@@ -35,8 +35,8 @@ platform :android do
task: 'bundle',
build_type: 'Release',
properties: {
"android.injected.version.code" => 204,
"android.injected.version.name" => "1.135.3",
"android.injected.version.code" => 205,
"android.injected.version.name" => "1.136.0",
}
)
upload_to_play_store(skip_upload_apk: true, skip_upload_images: true, skip_upload_screenshots: true, aab: '../build/app/outputs/bundle/release/app-release.aab')

View File

@@ -22,7 +22,7 @@ platform :ios do
path: "./Runner.xcodeproj",
)
increment_version_number(
version_number: "1.135.3"
version_number: "1.136.0"
)
increment_build_number(
build_number: latest_testflight_build_number + 1,

View File

@@ -13,7 +13,6 @@ class RemoteAsset extends BaseAsset {
final String? localId;
final String? thumbHash;
final AssetVisibility visibility;
final DateTime? localDateTime;
final String ownerId;
final String? stackId;
@@ -32,7 +31,6 @@ class RemoteAsset extends BaseAsset {
super.isFavorite = false,
this.thumbHash,
this.visibility = AssetVisibility.timeline,
this.localDateTime,
super.livePhotoVideoId,
this.stackId,
});
@@ -60,7 +58,6 @@ class RemoteAsset extends BaseAsset {
isFavorite: $isFavorite,
thumbHash: ${thumbHash ?? "<NA>"},
visibility: $visibility,
localDateTime: ${localDateTime ?? "<NA>"},
stackId: ${stackId ?? "<NA>"},
checksum: $checksum,
livePhotoVideoId: ${livePhotoVideoId ?? "<NA>"},
@@ -77,7 +74,6 @@ class RemoteAsset extends BaseAsset {
ownerId == other.ownerId &&
thumbHash == other.thumbHash &&
visibility == other.visibility &&
localDateTime == other.localDateTime &&
stackId == other.stackId;
}
@@ -89,7 +85,6 @@ class RemoteAsset extends BaseAsset {
localId.hashCode ^
thumbHash.hashCode ^
visibility.hashCode ^
localDateTime.hashCode ^
stackId.hashCode;
RemoteAsset copyWith({
@@ -107,7 +102,6 @@ class RemoteAsset extends BaseAsset {
bool? isFavorite,
String? thumbHash,
AssetVisibility? visibility,
DateTime? localDateTime,
String? livePhotoVideoId,
String? stackId,
}) {
@@ -126,7 +120,6 @@ class RemoteAsset extends BaseAsset {
isFavorite: isFavorite ?? this.isFavorite,
thumbHash: thumbHash ?? this.thumbHash,
visibility: visibility ?? this.visibility,
localDateTime: localDateTime ?? this.localDateTime,
livePhotoVideoId: livePhotoVideoId ?? this.livePhotoVideoId,
stackId: stackId ?? this.stackId,
);

View File

@@ -53,7 +53,6 @@ extension RemoteAssetEntityDataDomainEx on RemoteAssetEntityData {
isFavorite: isFavorite,
height: height,
width: width,
localDateTime: localDateTime,
thumbHash: thumbHash,
visibility: visibility,
livePhotoVideoId: livePhotoVideoId,

View File

@@ -72,13 +72,6 @@ class RemoteAssetRepository extends DriftDatabaseRepository {
.getSingleOrNull();
}
Future<RemoteAsset?> getAsset(String id) {
return _db.managers.remoteAssetEntity
.filter((row) => row.id.equals(id))
.map((row) => row.toDto())
.getSingleOrNull();
}
Future<List<(String, String)>> getPlaces() {
final asset = Subquery(
_db.remoteAssetEntity.select()
@@ -182,29 +175,6 @@ class RemoteAssetRepository extends DriftDatabaseRepository {
});
}
Future<void> updateDateTime(List<String> ids, String dateTime) {
final localDateTime =
dateTime.replaceAll(RegExp(r'[\+|-][0-9]{2}:[0-9]{2}'), '');
return _db.batch((batch) async {
for (final id in ids) {
batch.update(
_db.remoteAssetEntity,
RemoteAssetEntityCompanion(
localDateTime: Value(DateTime.parse(localDateTime).toUtc()),
),
where: (e) => e.id.equals(id),
);
batch.update(
_db.remoteExifEntity,
RemoteExifEntityCompanion(
dateTimeOriginal: Value(DateTime.parse(dateTime)),
),
where: (e) => e.assetId.equals(id),
);
}
});
}
Future<void> stack(String userId, StackResponse stack) {
return _db.transaction(() async {
final stackIds = await _db.managers.stackEntity

View File

@@ -1,47 +1,10 @@
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/enums.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart';
import 'package:immich_mobile/providers/infrastructure/action.provider.dart';
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
import 'package:immich_mobile/widgets/common/immich_toast.dart';
class EditDateTimeActionButton extends ConsumerWidget {
final ActionSource source;
const EditDateTimeActionButton({super.key, required this.source});
_onTap(BuildContext context, WidgetRef ref) async {
if (!context.mounted) {
return;
}
final result =
await ref.read(actionProvider.notifier).editDateTime(source, context);
if (result == null) {
return;
}
ref.read(multiSelectProvider.notifier).reset();
final successMessage = 'edit_date_time_action_prompt'.t(
context: context,
args: {'count': result.count.toString()},
);
if (context.mounted) {
ImmichToast.show(
context: context,
msg: result.success
? successMessage
: 'scaffold_body_error_occurred'.t(context: context),
gravity: ToastGravity.BOTTOM,
toastType: result.success ? ToastType.success : ToastType.error,
);
}
}
const EditDateTimeActionButton({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
@@ -49,7 +12,6 @@ class EditDateTimeActionButton extends ConsumerWidget {
maxWidth: 95.0,
iconData: Icons.edit_calendar_outlined,
label: "control_bottom_app_bar_edit_time".t(context: context),
onPressed: () => _onTap(context, ref),
);
}
}

View File

@@ -44,7 +44,7 @@ class ArchiveBottomSheet extends ConsumerWidget {
: const DeletePermanentActionButton(
source: ActionSource.timeline,
),
const EditDateTimeActionButton(source: ActionSource.timeline),
const EditDateTimeActionButton(),
const EditLocationActionButton(source: ActionSource.timeline),
const MoveToLockFolderActionButton(
source: ActionSource.timeline,

View File

@@ -44,7 +44,7 @@ class FavoriteBottomSheet extends ConsumerWidget {
: const DeletePermanentActionButton(
source: ActionSource.timeline,
),
const EditDateTimeActionButton(source: ActionSource.timeline),
const EditDateTimeActionButton(),
const EditLocationActionButton(source: ActionSource.timeline),
const MoveToLockFolderActionButton(
source: ActionSource.timeline,

View File

@@ -47,7 +47,7 @@ class GeneralBottomSheet extends ConsumerWidget {
if (multiselect.hasLocal || multiselect.hasMerged) ...[
const DeleteLocalActionButton(source: ActionSource.timeline),
],
const EditDateTimeActionButton(source: ActionSource.timeline),
const EditDateTimeActionButton(),
const EditLocationActionButton(source: ActionSource.timeline),
const MoveToLockFolderActionButton(
source: ActionSource.timeline,

View File

@@ -47,7 +47,7 @@ class RemoteAlbumBottomSheet extends ConsumerWidget {
: const DeletePermanentActionButton(
source: ActionSource.timeline,
),
const EditDateTimeActionButton(source: ActionSource.timeline),
const EditDateTimeActionButton(),
const EditLocationActionButton(source: ActionSource.timeline),
const MoveToLockFolderActionButton(
source: ActionSource.timeline,

View File

@@ -291,28 +291,6 @@ class ActionNotifier extends Notifier<void> {
}
}
Future<ActionResult?> editDateTime(
ActionSource source,
BuildContext context,
) async {
final ids = _getOwnedRemoteIdsForSource(source);
try {
final isEdited = await _service.editDateTime(ids, context);
if (!isEdited) {
return null;
}
return ActionResult(count: ids.length, success: true);
} catch (error, stack) {
_logger.severe('Failed to edit date and time for assets', error, stack);
return ActionResult(
count: ids.length,
success: false,
error: error.toString(),
);
}
}
Future<ActionResult> removeFromAlbum(
ActionSource source,
String albumId,

View File

@@ -98,18 +98,6 @@ class AssetApiRepository extends ApiRepository {
);
}
Future<void> updateDateTime(
List<String> ids,
String dateTime,
) async {
return _api.updateAssets(
AssetBulkUpdateDto(
ids: ids,
dateTimeOriginal: dateTime,
),
);
}
Future<StackResponse> stack(List<String> ids) async {
final responseDto =
await checkNull(_stacksApi.createStack(StackCreateDto(assetIds: ids)));

View File

@@ -13,11 +13,9 @@ import 'package:immich_mobile/repositories/asset_api.repository.dart';
import 'package:immich_mobile/repositories/asset_media.repository.dart';
import 'package:immich_mobile/repositories/drift_album_api_repository.dart';
import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/widgets/common/date_time_picker.dart';
import 'package:immich_mobile/widgets/common/location_picker.dart';
import 'package:maplibre_gl/maplibre_gl.dart' as maplibre;
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:timezone/timezone.dart';
final actionServiceProvider = Provider<ActionService>(
(ref) => ActionService(
@@ -161,7 +159,7 @@ class ActionService {
) async {
maplibre.LatLng? initialLatLng;
if (remoteIds.length == 1) {
final exif = await _remoteAssetRepository.getExif(remoteIds.first);
final exif = await _remoteAssetRepository.getExif(remoteIds[0]);
if (exif?.latitude != null && exif?.longitude != null) {
initialLatLng = maplibre.LatLng(exif!.latitude!, exif.longitude!);
@@ -189,47 +187,6 @@ class ActionService {
return true;
}
Future<bool> editDateTime(
List<String> remoteIds,
BuildContext context,
) async {
DateTime? initialDateTime;
Duration? initialOffset;
String? initialTimeZone;
if (remoteIds.length == 1) {
final asset = await _remoteAssetRepository.getAsset(remoteIds.first);
final exif = await _remoteAssetRepository.getExif(remoteIds.first);
initialDateTime = asset?.localDateTime;
initialTimeZone = exif?.timeZone;
if (initialDateTime != null && initialTimeZone != null) {
initialOffset = getTimeZoneOffset(initialDateTime, initialTimeZone);
}
}
final dateTime = await showDateTimePicker(
context: context,
initialDateTime: initialDateTime,
initialTZ: initialTimeZone,
initialTZOffset: initialOffset,
);
if (dateTime == null) {
return false;
}
await _assetApiRepository.updateDateTime(
remoteIds,
dateTime,
);
await _remoteAssetRepository.updateDateTime(
remoteIds,
dateTime,
);
return true;
}
Future<int> removeFromAlbum(List<String> remoteIds, String albumId) async {
int removedCount = 0;
final result = await _albumApiRepository.removeAssets(albumId, remoteIds);
@@ -259,25 +216,4 @@ class ActionService {
Future<List<bool>> downloadAll(List<RemoteAsset> assets) {
return _downloadRepository.downloadAllAssets(assets);
}
static Duration? getTimeZoneOffset(DateTime dateTime, String timeZone) {
try {
final location = getLocation(timeZone);
return TZDateTime.from(dateTime, location).timeZoneOffset;
} on LocationNotFoundException {
final re = RegExp(
r'^utc(?:([+-]\d{1,2})(?::(\d{2}))?)?$',
caseSensitive: false,
);
final m = re.firstMatch(timeZone);
if (m != null) {
final offset = Duration(
hours: int.parse(m.group(1) ?? '0'),
minutes: int.parse(m.group(2) ?? '0'),
);
return offset;
}
return null;
}
}
}

View File

@@ -3,7 +3,7 @@ Immich API
This Dart package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
- API version: 1.135.3
- API version: 1.136.0
- Generator version: 7.8.0
- Build package: org.openapitools.codegen.languages.DartClientCodegen

View File

@@ -2,7 +2,7 @@ name: immich_mobile
description: Immich - selfhosted backup media file on mobile phone
publish_to: 'none'
version: 1.135.3+204
version: 1.136.0+205
environment:
sdk: '>=3.3.0 <4.0.0'

View File

@@ -1,34 +0,0 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:timezone/data/latest.dart';
import 'package:immich_mobile/services/action.service.dart';
void main() {
setUpAll(() {
initializeTimeZones();
});
group("Returns timezone offset", () {
final dateTime = DateTime.parse("2025-01-01T00:00:00+0800");
test('Returns null with invalid timezone', () {
const timeZone = "#_#";
final timeZoneOffset = ActionService.getTimeZoneOffset(dateTime, timeZone);
expect(timeZoneOffset, null);
});
test('With timezone as location', () {
const timeZone = "Asia/Hong_Kong";
final timeZoneOffset = ActionService.getTimeZoneOffset(dateTime, timeZone);
expect(timeZoneOffset, const Duration(hours: 8));
});
test('With timezone as offset', () {
const timeZone = "utc+08:00";
final timeZoneOffset = ActionService.getTimeZoneOffset(dateTime, timeZone);
expect(timeZoneOffset, const Duration(hours: 8));
});
});
}

View File

@@ -8663,7 +8663,7 @@
"info": {
"title": "Immich",
"description": "Immich API",
"version": "1.135.3",
"version": "1.136.0",
"contact": {}
},
"tags": [],

View File

@@ -1,12 +1,12 @@
{
"name": "@immich/sdk",
"version": "1.135.3",
"version": "1.136.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@immich/sdk",
"version": "1.135.3",
"version": "1.136.0",
"license": "GNU Affero General Public License version 3",
"dependencies": {
"@oazapfts/runtime": "^1.0.2"

View File

@@ -1,6 +1,6 @@
{
"name": "@immich/sdk",
"version": "1.135.3",
"version": "1.136.0",
"description": "Auto-generated TypeScript SDK for the Immich API",
"type": "module",
"main": "./build/index.js",

View File

@@ -1,6 +1,6 @@
/**
* Immich
* 1.135.3
* 1.136.0
* DO NOT MODIFY - This file has been generated using oazapfts.
* See https://www.npmjs.com/package/oazapfts
*/

View File

@@ -1,12 +1,12 @@
{
"name": "immich",
"version": "1.135.3",
"version": "1.136.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "immich",
"version": "1.135.3",
"version": "1.136.0",
"license": "GNU Affero General Public License version 3",
"dependencies": {
"@nestjs/bullmq": "^11.0.1",

View File

@@ -1,6 +1,6 @@
{
"name": "immich",
"version": "1.135.3",
"version": "1.136.0",
"description": "",
"author": "",
"private": true,

View File

@@ -85,8 +85,13 @@ export class LoggingRepository {
this.logger = new MyConsoleLogger(cls, { context: LoggingRepository.name, color: !noColor });
}
static create() {
return new LoggingRepository(undefined, undefined);
static create(context?: string) {
const logger = new LoggingRepository(undefined, undefined);
if (context) {
logger.setContext(context);
}
return logger;
}
setAppName(name: string): void {

View File

@@ -1,25 +1,10 @@
import { Kysely, sql } from 'kysely';
// this file used to try to reset the `vchordrq.prewarm_dim;` parameter
// that ends up being a problem on pg 15 + since the extension is not installed.
export async function up(qb: Kysely<any>): Promise<void> {
type Conf = { db: string; guc: string[] };
const res = await sql<Conf>`
select current_database() db, to_json(setconfig) guc
from pg_db_role_setting
where setdatabase = (select oid from pg_database where datname = current_database())
and setrole = 0;`.execute(qb);
if (res.rows.length === 0) {
return;
}
const { db, guc } = res.rows[0];
await sql.raw(`alter database "${db}" reset all;`).execute(qb);
for (const parameter of guc) {
const [key, value] = parameter.split('=');
if (key === 'vchordrq.prewarm_dim') {
continue;
}
await sql.raw(`alter database "${db}" set ${key} to ${value};`).execute(qb);
}
export async function up(): Promise<void> {
// noop
}
export async function down(): Promise<void> {}
export async function down(): Promise<void> {
// noop
}

View File

@@ -1,8 +1,7 @@
import { Kysely, sql } from 'kysely';
import { LoggingRepository } from 'src/repositories/logging.repository';
const logger = LoggingRepository.create();
logger.setContext('Migrations');
const logger = LoggingRepository.create('Migrations');
export async function up(db: Kysely<any>): Promise<void> {
if (process.env.IMMICH_MEDIA_LOCATION) {

View File

@@ -459,18 +459,34 @@ describe(AuthService.name, () => {
mocks.apiKey.getKey.mockResolvedValue({ ...authApiKey, user: authUser });
await expect(
sut.authenticate({
headers: { 'x-api-key': 'auth_token' },
queryParams: {},
metadata: { adminRoute: false, sharedLinkRoute: false, uri: 'test', permission: Permission.AssetRead },
}),
).rejects.toBeInstanceOf(ForbiddenException);
const result = sut.authenticate({
headers: { 'x-api-key': 'auth_token' },
queryParams: {},
metadata: { adminRoute: false, sharedLinkRoute: false, uri: 'test', permission: Permission.AssetRead },
});
await expect(result).rejects.toBeInstanceOf(ForbiddenException);
await expect(result).rejects.toThrow('Missing required permission: asset.read');
});
it('should default to requiring the all permission when omitted', async () => {
const authUser = factory.authUser();
const authApiKey = factory.authApiKey({ permissions: [Permission.AssetRead] });
mocks.apiKey.getKey.mockResolvedValue({ ...authApiKey, user: authUser });
const result = sut.authenticate({
headers: { 'x-api-key': 'auth_token' },
queryParams: {},
metadata: { adminRoute: false, sharedLinkRoute: false, uri: 'test' },
});
await expect(result).rejects.toBeInstanceOf(ForbiddenException);
await expect(result).rejects.toThrow('Missing required permission: all');
});
it('should return an auth dto', async () => {
const authUser = factory.authUser();
const authApiKey = factory.authApiKey({ permissions: [] });
const authApiKey = factory.authApiKey({ permissions: [Permission.All] });
mocks.apiKey.getKey.mockResolvedValue({ ...authApiKey, user: authUser });

View File

@@ -174,7 +174,8 @@ export class AuthService extends BaseService {
async authenticate({ headers, queryParams, metadata }: ValidateRequest): Promise<AuthDto> {
const authDto = await this.validate({ headers, queryParams });
const { adminRoute, sharedLinkRoute, permission, uri } = metadata;
const { adminRoute, sharedLinkRoute, uri } = metadata;
const requestedPermission = metadata.permission ?? Permission.All;
if (!authDto.user.isAdmin && adminRoute) {
this.logger.warn(`Denied access to admin only route: ${uri}`);
@@ -186,8 +187,8 @@ export class AuthService extends BaseService {
throw new ForbiddenException('Forbidden');
}
if (authDto.apiKey && permission && !isGranted({ requested: [permission], current: authDto.apiKey.permissions })) {
throw new ForbiddenException(`Missing required permission: ${permission}`);
if (authDto.apiKey && !isGranted({ requested: [requestedPermission], current: authDto.apiKey.permissions })) {
throw new ForbiddenException(`Missing required permission: ${requestedPermission}`);
}
return authDto;

74
web/package-lock.json generated
View File

@@ -1,17 +1,17 @@
{
"name": "immich-web",
"version": "1.135.3",
"version": "1.136.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "immich-web",
"version": "1.135.3",
"version": "1.136.0",
"license": "GNU Affero General Public License version 3",
"dependencies": {
"@formatjs/icu-messageformat-parser": "^2.9.8",
"@immich/sdk": "file:../open-api/typescript-sdk",
"@immich/ui": "^0.23.2",
"@immich/ui": "^0.23.5",
"@mapbox/mapbox-gl-rtl-text": "0.2.3",
"@mdi/js": "^7.4.47",
"@photo-sphere-viewer/core": "^5.11.5",
@@ -94,7 +94,7 @@
},
"../open-api/typescript-sdk": {
"name": "@immich/sdk",
"version": "1.135.3",
"version": "1.136.0",
"license": "GNU Affero General Public License version 3",
"dependencies": {
"@oazapfts/runtime": "^1.0.2"
@@ -1357,9 +1357,9 @@
"link": true
},
"node_modules/@immich/ui": {
"version": "0.23.3",
"resolved": "https://registry.npmjs.org/@immich/ui/-/ui-0.23.3.tgz",
"integrity": "sha512-YbYJSv3HqDu2+6MmiHhLThSessZ6HkoVOWun/ZoGb8mKj5x/ZZ4AyXGPIqbyKTamsjzbcD9FInij70G+m4egkg==",
"version": "0.23.5",
"resolved": "https://registry.npmjs.org/@immich/ui/-/ui-0.23.5.tgz",
"integrity": "sha512-1wlFMmfDmtGC+Kcc8cYTT00mQaSumR41KEOOOmVn5Rw/8z9pUhpNY8mGl1AxY4qhtnaz+G3dH6vowYzL23D+YQ==",
"license": "GNU Affero General Public License version 3",
"dependencies": {
"@mdi/js": "^7.4.47",
@@ -2512,6 +2512,66 @@
"node": ">=14.0.0"
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core": {
"version": "1.4.3",
"dev": true,
"inBundle": true,
"license": "MIT",
"optional": true,
"dependencies": {
"@emnapi/wasi-threads": "1.0.2",
"tslib": "^2.4.0"
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime": {
"version": "1.4.3",
"dev": true,
"inBundle": true,
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads": {
"version": "1.0.2",
"dev": true,
"inBundle": true,
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": {
"version": "0.2.11",
"dev": true,
"inBundle": true,
"license": "MIT",
"optional": true,
"dependencies": {
"@emnapi/core": "^1.4.3",
"@emnapi/runtime": "^1.4.3",
"@tybys/wasm-util": "^0.9.0"
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util": {
"version": "0.9.0",
"dev": true,
"inBundle": true,
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/tslib": {
"version": "2.8.0",
"dev": true,
"inBundle": true,
"license": "0BSD",
"optional": true
},
"node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
"version": "4.1.11",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.11.tgz",

View File

@@ -1,6 +1,6 @@
{
"name": "immich-web",
"version": "1.135.3",
"version": "1.136.0",
"license": "GNU Affero General Public License version 3",
"type": "module",
"scripts": {
@@ -28,7 +28,7 @@
"dependencies": {
"@formatjs/icu-messageformat-parser": "^2.9.8",
"@immich/sdk": "file:../open-api/typescript-sdk",
"@immich/ui": "^0.23.2",
"@immich/ui": "^0.23.5",
"@mapbox/mapbox-gl-rtl-text": "0.2.3",
"@mdi/js": "^7.4.47",
"@photo-sphere-viewer/core": "^5.11.5",

View File

@@ -21,11 +21,6 @@
html {
height: 100%;
width: 100%;
background-color: rgb(255, 255, 255);
}
html.dark {
background-color: rgb(10, 10, 10);
}
body,
@@ -34,10 +29,6 @@
padding: 0;
}
body {
transition: background-color 0.15s ease;
}
@keyframes delayedVisibility {
to {
visibility: visible;

View File

@@ -3,11 +3,11 @@
notificationController,
NotificationType,
} from '$lib/components/shared-components/notification/notification';
import { modalManager } from '$lib/managers/modal-manager.svelte';
import { featureFlags } from '$lib/stores/server-config.store';
import { getJobName } from '$lib/utils';
import { handleError } from '$lib/utils/handle-error';
import { JobCommand, JobName, sendJobCommand, type AllJobStatusResponseDto, type JobCommandDto } from '@immich/sdk';
import { modalManager } from '@immich/ui';
import {
mdiContentDuplicate,
mdiFaceRecognition,

View File

@@ -6,9 +6,9 @@
import SettingSelect from '$lib/components/shared-components/settings/setting-select.svelte';
import SettingSwitch from '$lib/components/shared-components/settings/setting-switch.svelte';
import { SettingInputFieldType } from '$lib/constants';
import { modalManager } from '$lib/managers/modal-manager.svelte';
import AuthDisableLoginConfirmModal from '$lib/modals/AuthDisableLoginConfirmModal.svelte';
import { OAuthTokenEndpointAuthMethod, type SystemConfigDto } from '@immich/sdk';
import { modalManager } from '@immich/ui';
import { isEqual } from 'lodash-es';
import { t } from 'svelte-i18n';
import { fade } from 'svelte/transition';

View File

@@ -4,11 +4,10 @@
import LoadingSpinner from '$lib/components/shared-components/loading-spinner.svelte';
import SettingAccordion from '$lib/components/shared-components/settings/setting-accordion.svelte';
import SettingTextarea from '$lib/components/shared-components/settings/setting-textarea.svelte';
import { modalManager } from '$lib/managers/modal-manager.svelte';
import EmailTemplatePreviewModal from '$lib/modals/EmailTemplatePreviewModal.svelte';
import { handleError } from '$lib/utils/handle-error';
import { type SystemConfigDto, type SystemConfigTemplateEmailsDto, getNotificationTemplateAdmin } from '@immich/sdk';
import { Button } from '@immich/ui';
import { Button, modalManager } from '@immich/ui';
import { mdiEyeOutline } from '@mdi/js';
import { t } from 'svelte-i18n';
import { fade } from 'svelte/transition';

View File

@@ -1,9 +1,8 @@
<script lang="ts">
import { modalManager } from '$lib/managers/modal-manager.svelte';
import MapModal from '$lib/modals/MapModal.svelte';
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
import { getAlbumInfo, type AlbumResponseDto, type MapMarkerResponseDto } from '@immich/sdk';
import { IconButton } from '@immich/ui';
import { IconButton, modalManager } from '@immich/ui';
import { mdiMapOutline } from '@mdi/js';
import { onDestroy, onMount } from 'svelte';
import { t } from 'svelte-i18n';

View File

@@ -9,7 +9,6 @@
notificationController,
} from '$lib/components/shared-components/notification/notification';
import { AppRoute } from '$lib/constants';
import { modalManager } from '$lib/managers/modal-manager.svelte';
import AlbumEditModal from '$lib/modals/AlbumEditModal.svelte';
import AlbumShareModal from '$lib/modals/AlbumShareModal.svelte';
import QrCodeModal from '$lib/modals/QrCodeModal.svelte';
@@ -38,6 +37,7 @@
import { handleError } from '$lib/utils/handle-error';
import { normalizeSearchString } from '$lib/utils/string-utils';
import { addUsersToAlbum, deleteAlbum, isHttpError, type AlbumResponseDto, type AlbumUserAddDto } from '@immich/sdk';
import { modalManager } from '@immich/ui';
import { mdiDeleteOutline, mdiFolderDownloadOutline, mdiRenameOutline, mdiShareVariantOutline } from '@mdi/js';
import { groupBy } from 'lodash-es';
import { onMount, type Snippet } from 'svelte';

View File

@@ -3,11 +3,11 @@
import type { OnAction } from '$lib/components/asset-viewer/actions/action';
import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
import { AssetAction } from '$lib/constants';
import { modalManager } from '$lib/managers/modal-manager.svelte';
import AlbumPickerModal from '$lib/modals/AlbumPickerModal.svelte';
import { addAssetsToAlbum } from '$lib/utils/asset-utils';
import { toTimelineAsset } from '$lib/utils/timeline-util';
import type { AssetResponseDto } from '@immich/sdk';
import { modalManager } from '@immich/ui';
import { mdiImageAlbum, mdiShareVariantOutline } from '@mdi/js';
import { t } from 'svelte-i18n';

View File

@@ -1,11 +1,10 @@
<script lang="ts">
import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
import { AssetAction } from '$lib/constants';
import { modalManager } from '$lib/managers/modal-manager.svelte';
import { keepThisDeleteOthers } from '$lib/utils/asset-utils';
import { toTimelineAsset } from '$lib/utils/timeline-util';
import type { AssetResponseDto, StackResponseDto } from '@immich/sdk';
import { modalManager } from '@immich/ui';
import { mdiPinOutline } from '@mdi/js';
import { t } from 'svelte-i18n';
import type { OnAction } from './action';

View File

@@ -1,8 +1,8 @@
<script lang="ts">
import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
import { modalManager } from '$lib/managers/modal-manager.svelte';
import ProfileImageCropperModal from '$lib/modals/ProfileImageCropperModal.svelte';
import type { AssetResponseDto } from '@immich/sdk';
import { modalManager } from '@immich/ui';
import { mdiAccountCircleOutline } from '@mdi/js';
import { t } from 'svelte-i18n';

View File

@@ -1,11 +1,10 @@
<script lang="ts">
import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
import { AssetAction } from '$lib/constants';
import { modalManager } from '$lib/managers/modal-manager.svelte';
import type { TimelineAsset } from '$lib/managers/timeline-manager/types';
import { handleError } from '$lib/utils/handle-error';
import { AssetVisibility, updateAssets } from '@immich/sdk';
import { modalManager } from '@immich/ui';
import { mdiLockOpenVariantOutline, mdiLockOutline } from '@mdi/js';
import { t } from 'svelte-i18n';
import type { OnAction, PreAction } from './action';

View File

@@ -1,10 +1,9 @@
<script lang="ts">
import { modalManager } from '$lib/managers/modal-manager.svelte';
import QrCodeModal from '$lib/modals/QrCodeModal.svelte';
import SharedLinkCreateModal from '$lib/modals/SharedLinkCreateModal.svelte';
import { makeSharedLinkUrl } from '$lib/utils';
import type { AssetResponseDto } from '@immich/sdk';
import { IconButton } from '@immich/ui';
import { IconButton, modalManager } from '@immich/ui';
import { mdiShareVariantOutline } from '@mdi/js';
import { t } from 'svelte-i18n';

View File

@@ -3,10 +3,10 @@
import Icon from '$lib/components/elements/icon.svelte';
import { AppRoute } from '$lib/constants';
import { authManager } from '$lib/managers/auth-manager.svelte';
import { modalManager } from '$lib/managers/modal-manager.svelte';
import AssetTagModal from '$lib/modals/AssetTagModal.svelte';
import { removeTag } from '$lib/utils/asset-utils';
import { getAssetInfo, type AssetResponseDto } from '@immich/sdk';
import { modalManager } from '@immich/ui';
import { mdiClose, mdiPlus } from '@mdi/js';
import { t } from 'svelte-i18n';

View File

@@ -1,10 +1,9 @@
<script lang="ts">
import { shortcut } from '$lib/actions/shortcut';
import ConfirmModal from '$lib/modals/ConfirmModal.svelte';
import { editTypes, showCancelConfirmDialog } from '$lib/stores/asset-editor.store';
import { websocketEvents } from '$lib/stores/websocket';
import { type AssetResponseDto } from '@immich/sdk';
import { IconButton } from '@immich/ui';
import { ConfirmModal, IconButton } from '@immich/ui';
import { mdiClose } from '@mdi/js';
import { onMount } from 'svelte';
import { t } from 'svelte-i18n';

View File

@@ -1,13 +1,12 @@
<script lang="ts">
import ImageThumbnail from '$lib/components/assets/thumbnail/image-thumbnail.svelte';
import { notificationController } from '$lib/components/shared-components/notification/notification';
import { modalManager } from '$lib/managers/modal-manager.svelte';
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
import { isFaceEditMode } from '$lib/stores/face-edit.svelte';
import { getPeopleThumbnailUrl } from '$lib/utils';
import { handleError } from '$lib/utils/handle-error';
import { createFace, getAllPeople, type PersonResponseDto } from '@immich/sdk';
import { Button, Input } from '@immich/ui';
import { Button, Input, modalManager } from '@immich/ui';
import { Canvas, InteractiveFabricObject, Rect } from 'fabric';
import { onMount } from 'svelte';
import { t } from 'svelte-i18n';

View File

@@ -2,10 +2,9 @@
import { shortcuts } from '$lib/actions/shortcut';
import ProgressBar from '$lib/components/shared-components/progress-bar/progress-bar.svelte';
import { ProgressBarStatus } from '$lib/constants';
import { modalManager } from '$lib/managers/modal-manager.svelte';
import SlideshowSettingsModal from '$lib/modals/SlideshowSettingsModal.svelte';
import { SlideshowNavigation, slideshowStore } from '$lib/stores/slideshow.store';
import { IconButton } from '@immich/ui';
import { IconButton, modalManager } from '@immich/ui';
import { mdiChevronLeft, mdiChevronRight, mdiClose, mdiCog, mdiFullscreen, mdiPause, mdiPlay } from '@mdi/js';
import { onDestroy, onMount } from 'svelte';
import { swipe } from 'svelte-gestures';

View File

@@ -3,10 +3,9 @@
import { page } from '$app/state';
import Icon from '$lib/components/elements/icon.svelte';
import { ActionQueryParameterValue, AppRoute, QueryParameter } from '$lib/constants';
import { modalManager } from '$lib/managers/modal-manager.svelte';
import { handleError } from '$lib/utils/handle-error';
import { getAllPeople, getPerson, mergePerson, type PersonResponseDto } from '@immich/sdk';
import { Button, IconButton } from '@immich/ui';
import { Button, IconButton, modalManager } from '@immich/ui';
import { mdiCallMerge, mdiMerge, mdiSwapHorizontal } from '@mdi/js';
import { onMount } from 'svelte';
import { t } from 'svelte-i18n';

View File

@@ -2,7 +2,6 @@
import Icon from '$lib/components/elements/icon.svelte';
import LoadingSpinner from '$lib/components/shared-components/loading-spinner.svelte';
import { timeBeforeShowLoadingSpinner } from '$lib/constants';
import { modalManager } from '$lib/managers/modal-manager.svelte';
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
import { photoViewerImgElement } from '$lib/stores/assets-store.svelte';
import { boundingBoxesArray } from '$lib/stores/people.store';
@@ -20,7 +19,7 @@
type AssetFaceResponseDto,
type PersonResponseDto,
} from '@immich/sdk';
import { IconButton } from '@immich/ui';
import { IconButton, modalManager } from '@immich/ui';
import { mdiAccountOff, mdiArrowLeftThin, mdiPencil, mdiRestart, mdiTrashCan } from '@mdi/js';
import { onMount } from 'svelte';
import { t } from 'svelte-i18n';

View File

@@ -1,10 +1,9 @@
<script lang="ts">
import Icon from '$lib/components/elements/icon.svelte';
import { modalManager } from '$lib/managers/modal-manager.svelte';
import LibraryImportPathModal from '$lib/modals/LibraryImportPathModal.svelte';
import type { ValidateLibraryImportPathResponseDto } from '@immich/sdk';
import { validate, type LibraryResponseDto } from '@immich/sdk';
import { Button, IconButton } from '@immich/ui';
import { Button, IconButton, modalManager } from '@immich/ui';
import { mdiAlertOutline, mdiCheckCircleOutline, mdiPencilOutline, mdiRefresh } from '@mdi/js';
import { onMount } from 'svelte';
import { t } from 'svelte-i18n';

View File

@@ -1,8 +1,7 @@
<script lang="ts">
import { modalManager } from '$lib/managers/modal-manager.svelte';
import LibraryExclusionPatternModal from '$lib/modals/LibraryExclusionPatternModal.svelte';
import { type LibraryResponseDto } from '@immich/sdk';
import { Button, IconButton } from '@immich/ui';
import { Button, IconButton, modalManager } from '@immich/ui';
import { mdiPencilOutline } from '@mdi/js';
import { onMount } from 'svelte';
import { t } from 'svelte-i18n';

View File

@@ -1,9 +1,9 @@
<script lang="ts">
import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
import { modalManager } from '$lib/managers/modal-manager.svelte';
import AlbumPickerModal from '$lib/modals/AlbumPickerModal.svelte';
import type { OnAddToAlbum } from '$lib/utils/actions';
import { addAssetsToAlbum } from '$lib/utils/asset-utils';
import { modalManager } from '@immich/ui';
import { mdiImageAlbum, mdiShareVariantOutline } from '@mdi/js';
import { t } from 'svelte-i18n';
import { getAssetControlContext } from '../asset-select-control-bar.svelte';

View File

@@ -1,10 +1,10 @@
<script lang="ts">
import { modalManager } from '$lib/managers/modal-manager.svelte';
import AssetUpdateDecriptionConfirmModal from '$lib/modals/AssetUpdateDecriptionConfirmModal.svelte';
import AssetUpdateDescriptionConfirmModal from '$lib/modals/AssetUpdateDescriptionConfirmModal.svelte';
import { user } from '$lib/stores/user.store';
import { getSelectedAssets } from '$lib/utils/asset-utils';
import { handleError } from '$lib/utils/handle-error';
import { updateAssets } from '@immich/sdk';
import { modalManager } from '@immich/ui';
import { mdiText } from '@mdi/js';
import { t } from 'svelte-i18n';
import MenuOption from '../../shared-components/context-menu/menu-option.svelte';
@@ -18,7 +18,7 @@
const { clearSelect, getOwnedAssets } = getAssetControlContext();
const handleUpdateDescription = async () => {
const description = await modalManager.show(AssetUpdateDecriptionConfirmModal);
const description = await modalManager.show(AssetUpdateDescriptionConfirmModal);
if (description) {
const ids = getSelectedAssets(getOwnedAssets(), $user);

View File

@@ -1,12 +1,11 @@
<script lang="ts">
import { getAssetControlContext } from '$lib/components/photos-page/asset-select-control-bar.svelte';
import { modalManager } from '$lib/managers/modal-manager.svelte';
import QrCodeModal from '$lib/modals/QrCodeModal.svelte';
import SharedLinkCreateModal from '$lib/modals/SharedLinkCreateModal.svelte';
import { makeSharedLinkUrl } from '$lib/utils';
import { IconButton, modalManager } from '@immich/ui';
import { mdiShareVariantOutline } from '@mdi/js';
import { t } from 'svelte-i18n';
import { IconButton } from '@immich/ui';
const { getAssets } = getAssetControlContext();

View File

@@ -3,9 +3,8 @@
NotificationType,
notificationController,
} from '$lib/components/shared-components/notification/notification';
import { modalManager } from '$lib/managers/modal-manager.svelte';
import { getAlbumInfo, removeAssetFromAlbum, type AlbumResponseDto } from '@immich/sdk';
import { IconButton } from '@immich/ui';
import { IconButton, modalManager } from '@immich/ui';
import { mdiDeleteOutline, mdiImageRemoveOutline } from '@mdi/js';
import { t } from 'svelte-i18n';
import MenuOption from '../../shared-components/context-menu/menu-option.svelte';

View File

@@ -1,13 +1,12 @@
<script lang="ts">
import { authManager } from '$lib/managers/auth-manager.svelte';
import { modalManager } from '$lib/managers/modal-manager.svelte';
import { handleError } from '$lib/utils/handle-error';
import { removeSharedLinkAssets, type SharedLinkResponseDto } from '@immich/sdk';
import { IconButton, modalManager } from '@immich/ui';
import { mdiDeleteOutline } from '@mdi/js';
import { t } from 'svelte-i18n';
import { NotificationType, notificationController } from '../../shared-components/notification/notification';
import { getAssetControlContext } from '../asset-select-control-bar.svelte';
import { IconButton } from '@immich/ui';
interface Props {
sharedLink: SharedLinkResponseDto;

View File

@@ -1,12 +1,10 @@
<script lang="ts">
import { getAssetControlContext } from '$lib/components/photos-page/asset-select-control-bar.svelte';
import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
import { modalManager } from '$lib/managers/modal-manager.svelte';
import type { OnSetVisibility } from '$lib/utils/actions';
import { handleError } from '$lib/utils/handle-error';
import { AssetVisibility, updateAssets } from '@immich/sdk';
import { Button } from '@immich/ui';
import { Button, modalManager } from '@immich/ui';
import { mdiLockOpenVariantOutline, mdiLockOutline } from '@mdi/js';
import { t } from 'svelte-i18n';

View File

@@ -1,8 +1,7 @@
<script lang="ts">
import { shortcut } from '$lib/actions/shortcut';
import { modalManager } from '$lib/managers/modal-manager.svelte';
import AssetTagModal from '$lib/modals/AssetTagModal.svelte';
import { IconButton } from '@immich/ui';
import { IconButton, modalManager } from '@immich/ui';
import { mdiTagMultipleOutline } from '@mdi/js';
import { t } from 'svelte-i18n';
import MenuOption from '../../shared-components/context-menu/menu-option.svelte';

View File

@@ -13,7 +13,6 @@
import Scrubber from '$lib/components/shared-components/scrubber/scrubber.svelte';
import { AppRoute, AssetAction } from '$lib/constants';
import { authManager } from '$lib/managers/auth-manager.svelte';
import { modalManager } from '$lib/managers/modal-manager.svelte';
import type { MonthGroup } from '$lib/managers/timeline-manager/month-group.svelte';
import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
import type { TimelineAsset } from '$lib/managers/timeline-manager/types';
@@ -37,6 +36,7 @@
type TimelinePlainYearMonth,
} from '$lib/utils/timeline-util';
import { AssetVisibility, getAssetInfo, type AlbumResponseDto, type PersonResponseDto } from '@immich/sdk';
import { modalManager } from '@immich/ui';
import { DateTime } from 'luxon';
import { onMount, type Snippet } from 'svelte';
import type { UpdatePayload } from 'vite';
@@ -729,7 +729,7 @@
}
isShortcutModalOpen = true;
await modalManager.show(ShortcutsModal);
await modalManager.show(ShortcutsModal, {});
isShortcutModalOpen = false;
};

View File

@@ -1,8 +1,7 @@
<script lang="ts">
import FormatMessage from '$lib/components/i18n/format-message.svelte';
import ConfirmModal from '$lib/modals/ConfirmModal.svelte';
import { showDeleteModal } from '$lib/stores/preferences.store';
import { Checkbox, Label } from '@immich/ui';
import { Checkbox, ConfirmModal, Label } from '@immich/ui';
import { mdiDeleteForeverOutline } from '@mdi/js';
import { t } from 'svelte-i18n';

View File

@@ -12,8 +12,8 @@ describe('ChangeDate component', () => {
const getDateInput = () => screen.getByLabelText('date_and_time') as HTMLInputElement;
const getTimeZoneInput = () => screen.getByLabelText('timezone') as HTMLInputElement;
const getCancelButton = () => screen.getByText('cancel');
const getConfirmButton = () => screen.getByText('confirm');
const getCancelButton = () => screen.getByText('Cancel');
const getConfirmButton = () => screen.getByText('Confirm');
beforeEach(() => {
vi.stubGlobal('IntersectionObserver', getIntersectionObserverMock());

View File

@@ -1,5 +1,5 @@
<script lang="ts">
import ConfirmModal from '$lib/modals/ConfirmModal.svelte';
import { ConfirmModal } from '@immich/ui';
import { mdiCalendarEditOutline } from '@mdi/js';
import { DateTime, Duration } from 'luxon';
import { t } from 'svelte-i18n';
@@ -184,8 +184,6 @@
disabled={!date.isValid}
onClose={(confirmed) => (confirmed ? handleConfirm() : onCancel())}
>
<!-- @migration-task: migrate this slot by hand, `prompt` would shadow a prop on the parent component -->
<!-- @migration-task: migrate this slot by hand, `prompt` would shadow a prop on the parent component -->
{#snippet promptSnippet()}
<div class="flex flex-col text-start gap-2">
<div class="flex flex-col">

View File

@@ -6,11 +6,11 @@
import LoadingSpinner from '$lib/components/shared-components/loading-spinner.svelte';
import type Map from '$lib/components/shared-components/map/map.svelte';
import { timeDebounceOnSearch, timeToLoadTheMap } from '$lib/constants';
import ConfirmModal from '$lib/modals/ConfirmModal.svelte';
import { lastChosenLocation } from '$lib/stores/asset-editor.store';
import { delay } from '$lib/utils/asset-utils';
import { handleError } from '$lib/utils/handle-error';
import { searchPlaces, type AssetResponseDto, type PlacesResponseDto } from '@immich/sdk';
import { ConfirmModal } from '@immich/ui';
import { mdiMapMarkerMultipleOutline } from '@mdi/js';
import { t } from 'svelte-i18n';
import { get } from 'svelte/store';

View File

@@ -4,7 +4,6 @@
import type { Action } from '$lib/components/asset-viewer/actions/action';
import Thumbnail from '$lib/components/assets/thumbnail/thumbnail.svelte';
import { AppRoute, AssetAction } from '$lib/constants';
import { modalManager } from '$lib/managers/modal-manager.svelte';
import type { TimelineAsset, Viewport } from '$lib/managers/timeline-manager/types';
import ShortcutsModal from '$lib/modals/ShortcutsModal.svelte';
import type { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
@@ -20,6 +19,7 @@
import { navigate } from '$lib/utils/navigation';
import { isTimelineAsset, toTimelineAsset } from '$lib/utils/timeline-util';
import { AssetVisibility, type AssetResponseDto } from '@immich/sdk';
import { modalManager } from '@immich/ui';
import { debounce } from 'lodash-es';
import { t } from 'svelte-i18n';
import AssetViewer from '../../asset-viewer/asset-viewer.svelte';

View File

@@ -10,13 +10,13 @@
import { afterNavigate } from '$app/navigation';
import Icon from '$lib/components/elements/icon.svelte';
import { Theme } from '$lib/constants';
import { modalManager } from '$lib/managers/modal-manager.svelte';
import { themeManager } from '$lib/managers/theme-manager.svelte';
import MapSettingsModal from '$lib/modals/MapSettingsModal.svelte';
import { mapSettings } from '$lib/stores/preferences.store';
import { serverConfig } from '$lib/stores/server-config.store';
import { getAssetThumbnailUrl, handlePromiseError } from '$lib/utils';
import { getMapMarkers, type MapMarkerResponseDto } from '@immich/sdk';
import { modalManager } from '@immich/ui';
import mapboxRtlUrl from '@mapbox/mapbox-gl-rtl-text/mapbox-gl-rtl-text.min.js?url';
import { mdiCog, mdiMap, mdiMapMarker } from '@mdi/js';
import type { Feature, GeoJsonProperties, Geometry, Point } from 'geojson';

View File

@@ -1,49 +0,0 @@
<script lang="ts">
import Icon from '$lib/components/elements/icon.svelte';
import ImmichLogo from '$lib/components/shared-components/immich-logo.svelte';
import { IconButton } from '@immich/ui';
import { mdiClose } from '@mdi/js';
import { t } from 'svelte-i18n';
interface Props {
/**
* Unique identifier for the header text.
*/
id: string;
title: string;
onClose: () => void;
/**
* If true, the logo will be displayed next to the modal title.
*/
showLogo?: boolean;
/**
* Optional icon to display next to the modal title, if `showLogo` is false.
*/
icon?: string;
}
let { id, title, onClose, showLogo = false, icon = undefined }: Props = $props();
</script>
<div class="flex place-items-center justify-between px-5 pb-3">
<div class="flex gap-2 place-items-center">
{#if showLogo}
<ImmichLogo noText={true} class="h-[40px]" />
{:else if icon}
<Icon path={icon} size={24} ariaHidden={true} class="text-immich-primary dark:text-immich-dark-primary" />
{/if}
<h1 {id}>
{title}
</h1>
</div>
<IconButton
shape="round"
color="secondary"
variant="ghost"
onclick={onClose}
icon={mdiClose}
size="medium"
aria-label={$t('close')}
/>
</div>

View File

@@ -3,13 +3,12 @@
import { focusTrap } from '$lib/actions/focus-trap';
import Icon from '$lib/components/elements/icon.svelte';
import { AppRoute } from '$lib/constants';
import { modalManager } from '$lib/managers/modal-manager.svelte';
import AvatarEditModal from '$lib/modals/AvatarEditModal.svelte';
import HelpAndFeedbackModal from '$lib/modals/HelpAndFeedbackModal.svelte';
import { user } from '$lib/stores/user.store';
import { userInteraction } from '$lib/stores/user.svelte';
import { getAboutInfo, type ServerAboutResponseDto } from '@immich/sdk';
import { Button, IconButton } from '@immich/ui';
import { Button, IconButton, modalManager } from '@immich/ui';
import { mdiCog, mdiLogout, mdiPencil, mdiWrench } from '@mdi/js';
import { onMount } from 'svelte';
import { t } from 'svelte-i18n';

View File

@@ -3,14 +3,13 @@
import { focusOutside } from '$lib/actions/focus-outside';
import { shortcuts } from '$lib/actions/shortcut';
import { AppRoute } from '$lib/constants';
import { modalManager } from '$lib/managers/modal-manager.svelte';
import SearchFilterModal from '$lib/modals/SearchFilterModal.svelte';
import { searchStore } from '$lib/stores/search.svelte';
import { handlePromiseError } from '$lib/utils';
import { generateId } from '$lib/utils/generate-id';
import { getMetadataSearchQuery } from '$lib/utils/metadata-search';
import type { MetadataSearchDto, SmartSearchDto } from '@immich/sdk';
import { IconButton } from '@immich/ui';
import { IconButton, modalManager } from '@immich/ui';
import { mdiClose, mdiMagnify, mdiTune } from '@mdi/js';
import { onDestroy, tick } from 'svelte';
import { t } from 'svelte-i18n';

View File

@@ -5,7 +5,6 @@
import Portal from '$lib/components/shared-components/portal/portal.svelte';
import SupporterBadge from '$lib/components/shared-components/side-bar/supporter-badge.svelte';
import { AppRoute } from '$lib/constants';
import { modalManager } from '$lib/managers/modal-manager.svelte';
import PurchaseModal from '$lib/modals/PurchaseModal.svelte';
import { purchaseStore } from '$lib/stores/purchase.store';
import { preferences } from '$lib/stores/user.store';
@@ -13,11 +12,11 @@
import { handleError } from '$lib/utils/handle-error';
import { getButtonVisibility } from '$lib/utils/purchase-utils';
import { updateMyPreferences } from '@immich/sdk';
import { Button, IconButton } from '@immich/ui';
import { Button, IconButton, modalManager } from '@immich/ui';
import { mdiClose, mdiInformationOutline } from '@mdi/js';
import { t } from 'svelte-i18n';
import { fade } from 'svelte/transition';
import { SvelteDate } from 'svelte/reactivity';
import { fade } from 'svelte/transition';
let showMessage = $state(false);
let hoverMessage = $state(false);

View File

@@ -1,6 +1,5 @@
<script lang="ts">
import Icon from '$lib/components/elements/icon.svelte';
import { modalManager } from '$lib/managers/modal-manager.svelte';
import ServerAboutModal from '$lib/modals/ServerAboutModal.svelte';
import { userInteraction } from '$lib/stores/user.svelte';
import { websocketStore } from '$lib/stores/websocket';
@@ -11,6 +10,7 @@
type ServerAboutResponseDto,
type ServerVersionHistoryResponseDto,
} from '@immich/sdk';
import { modalManager } from '@immich/ui';
import { mdiAlert } from '@mdi/js';
import { onMount } from 'svelte';
import { t } from 'svelte-i18n';

View File

@@ -1,7 +1,6 @@
<script lang="ts">
import { modalManager } from '$lib/managers/modal-manager.svelte';
import { deleteAllSessions, deleteSession, getSessions, type SessionResponseDto } from '@immich/sdk';
import { Button } from '@immich/ui';
import { Button, modalManager } from '@immich/ui';
import { t } from 'svelte-i18n';
import { handleError } from '../../utils/handle-error';
import { notificationController, NotificationType } from '../shared-components/notification/notification';

View File

@@ -1,7 +1,6 @@
<script lang="ts">
import SettingSwitch from '$lib/components/shared-components/settings/setting-switch.svelte';
import UserAvatar from '$lib/components/shared-components/user-avatar.svelte';
import { modalManager } from '$lib/managers/modal-manager.svelte';
import PartnerSelectionModal from '$lib/modals/PartnerSelectionModal.svelte';
import {
createPartner,
@@ -12,7 +11,7 @@
type PartnerResponseDto,
type UserResponseDto,
} from '@immich/sdk';
import { Button, IconButton } from '@immich/ui';
import { Button, IconButton, modalManager } from '@immich/ui';
import { mdiCheck, mdiClose } from '@mdi/js';
import { onMount } from 'svelte';
import { t } from 'svelte-i18n';

View File

@@ -1,11 +1,10 @@
<script lang="ts">
import { dateFormats } from '$lib/constants';
import { modalManager } from '$lib/managers/modal-manager.svelte';
import ApiKeyModal from '$lib/modals/ApiKeyModal.svelte';
import ApiKeySecretModal from '$lib/modals/ApiKeySecretModal.svelte';
import { locale } from '$lib/stores/preferences.store';
import { createApiKey, deleteApiKey, getApiKeys, updateApiKey, type ApiKeyResponseDto } from '@immich/sdk';
import { Button, IconButton } from '@immich/ui';
import { Button, IconButton, modalManager } from '@immich/ui';
import { mdiPencilOutline, mdiTrashCanOutline } from '@mdi/js';
import { t } from 'svelte-i18n';
import { fade } from 'svelte/transition';

View File

@@ -5,7 +5,6 @@
import PurchaseContent from '$lib/components/shared-components/purchasing/purchase-content.svelte';
import SettingSwitch from '$lib/components/shared-components/settings/setting-switch.svelte';
import { dateFormats } from '$lib/constants';
import { modalManager } from '$lib/managers/modal-manager.svelte';
import { locale } from '$lib/stores/preferences.store';
import { purchaseStore } from '$lib/stores/purchase.store';
import { preferences, user } from '$lib/stores/user.store';
@@ -20,7 +19,7 @@
isHttpError,
type LicenseResponseDto,
} from '@immich/sdk';
import { Button } from '@immich/ui';
import { Button, modalManager } from '@immich/ui';
import { mdiKey } from '@mdi/js';
import { onMount } from 'svelte';
import { t } from 'svelte-i18n';

View File

@@ -1,53 +0,0 @@
import ConfirmModal from '$lib/modals/ConfirmModal.svelte';
import { mount, unmount, type Component, type ComponentProps } from 'svelte';
type OnCloseData<T> = T extends { onClose: (data?: infer R) => void }
? R | undefined
: T extends { onClose: (data: infer R) => void }
? R
: never;
type ExtendsEmptyObject<T> = keyof T extends never ? never : T;
type StripValueIfOptional<T> = T extends undefined ? undefined : T;
// if the modal does not expect any props, makes the props param optional but also allows passing `{}` and `undefined`
type OptionalParamIfEmpty<T> = ExtendsEmptyObject<T> extends never ? [] | [Record<string, never> | undefined] : [T];
class ModalManager {
show<T extends object>(Component: Component<T>, ...props: OptionalParamIfEmpty<Omit<T, 'onClose'>>) {
return this.open(Component, ...props).onClose;
}
open<T extends object, K = OnCloseData<T>>(
Component: Component<T>,
...props: OptionalParamIfEmpty<Omit<T, 'onClose'>>
) {
let modal: object = {};
let onClose: (...args: [StripValueIfOptional<K>]) => Promise<void>;
const deferred = new Promise<StripValueIfOptional<K>>((resolve) => {
onClose = async (...args: [StripValueIfOptional<K>]) => {
await unmount(modal);
setTimeout(() => resolve(args?.[0]), 0);
};
modal = mount(Component, {
target: document.body,
props: {
...((props?.[0] ?? {}) as T),
onClose,
},
});
});
return {
onClose: deferred,
close: (...args: [StripValueIfOptional<K>]) => onClose(args[0]),
};
}
showDialog(options: Omit<ComponentProps<typeof ConfirmModal>, 'onClose'>) {
return this.show(ConfirmModal, options);
}
}
export const modalManager = new ModalManager();

View File

@@ -4,7 +4,6 @@
import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
import SettingSwitch from '$lib/components/shared-components/settings/setting-switch.svelte';
import UserAvatar from '$lib/components/shared-components/user-avatar.svelte';
import { modalManager } from '$lib/managers/modal-manager.svelte';
import { handleError } from '$lib/utils/handle-error';
import {
AlbumUserRole,
@@ -15,7 +14,7 @@
type AlbumResponseDto,
type UserResponseDto,
} from '@immich/sdk';
import { Modal, ModalBody } from '@immich/ui';
import { Modal, ModalBody, modalManager } from '@immich/ui';
import { mdiArrowDownThin, mdiArrowUpThin, mdiDotsVertical, mdiPlus } from '@mdi/js';
import { findKey } from 'lodash-es';
import { t } from 'svelte-i18n';

View File

@@ -6,7 +6,6 @@
notificationController,
} from '$lib/components/shared-components/notification/notification';
import UserAvatar from '$lib/components/shared-components/user-avatar.svelte';
import { modalManager } from '$lib/managers/modal-manager.svelte';
import { handleError } from '$lib/utils/handle-error';
import {
AlbumUserRole,
@@ -16,7 +15,7 @@
type AlbumResponseDto,
type UserResponseDto,
} from '@immich/sdk';
import { Modal, ModalBody } from '@immich/ui';
import { Modal, ModalBody, modalManager } from '@immich/ui';
import { mdiDotsVertical } from '@mdi/js';
import { onMount } from 'svelte';
import { t } from 'svelte-i18n';

View File

@@ -1,6 +1,5 @@
<script lang="ts">
import ConfirmModal from '$lib/modals/ConfirmModal.svelte';
import { Input } from '@immich/ui';
import { ConfirmModal, Input } from '@immich/ui';
import { mdiText } from '@mdi/js';
import { t } from 'svelte-i18n';

View File

@@ -1,52 +0,0 @@
<script lang="ts">
import { Button, HStack, Modal, ModalBody, ModalFooter, type Color } from '@immich/ui';
import type { Snippet } from 'svelte';
import { t } from 'svelte-i18n';
interface Props {
title?: string;
prompt?: string;
confirmText?: string;
confirmColor?: Color;
disabled?: boolean;
size?: 'small' | 'medium';
icon?: string;
onClose: (confirmed: boolean) => void;
promptSnippet?: Snippet;
}
let {
title = $t('confirm'),
prompt = $t('are_you_sure_to_do_this'),
confirmText = $t('confirm'),
confirmColor = 'danger',
disabled = false,
size = 'small',
icon = undefined,
onClose,
promptSnippet,
}: Props = $props();
const handleConfirm = () => {
onClose(true);
};
</script>
<Modal {title} {icon} onClose={() => onClose(false)} {size}>
<ModalBody>
{#if promptSnippet}{@render promptSnippet()}{:else}
<p>{prompt}</p>
{/if}
</ModalBody>
<ModalFooter>
<HStack fullWidth>
<Button shape="round" color="secondary" fullWidth onclick={() => onClose(false)}>
{$t('cancel')}
</Button>
<Button shape="round" color={confirmColor} fullWidth onclick={handleConfirm} {disabled}>
{confirmText}
</Button>
</HStack>
</ModalFooter>
</Modal>

View File

@@ -4,9 +4,9 @@
notificationController,
NotificationType,
} from '$lib/components/shared-components/notification/notification';
import ConfirmModal from '$lib/modals/ConfirmModal.svelte';
import { handleError } from '$lib/utils/handle-error';
import { createJob, ManualJobName } from '@immich/sdk';
import { ConfirmModal } from '@immich/ui';
import { t } from 'svelte-i18n';
type Props = { onClose: (confirmed: boolean) => void };

View File

@@ -1,10 +1,9 @@
<script lang="ts">
import FormatMessage from '$lib/components/i18n/format-message.svelte';
import ConfirmModal from '$lib/modals/ConfirmModal.svelte';
import { serverConfig } from '$lib/stores/server-config.store';
import { handleError } from '$lib/utils/handle-error';
import { deleteUserAdmin, type UserAdminResponseDto, type UserResponseDto } from '@immich/sdk';
import { Checkbox, Label } from '@immich/ui';
import { Checkbox, ConfirmModal, Label } from '@immich/ui';
import { t } from 'svelte-i18n';
interface Props {

View File

@@ -1,6 +1,5 @@
import { goto } from '$app/navigation';
import { AppRoute } from '$lib/constants';
import { modalManager } from '$lib/managers/modal-manager.svelte';
import {
AlbumFilter,
AlbumGroupBy,
@@ -13,6 +12,7 @@ import {
import { handleError } from '$lib/utils/handle-error';
import type { AlbumResponseDto } from '@immich/sdk';
import * as sdk from '@immich/sdk';
import { modalManager } from '@immich/ui';
import { orderBy } from 'lodash-es';
import { t } from 'svelte-i18n';
import { get } from 'svelte/store';

View File

@@ -34,7 +34,6 @@
import UserAvatar from '$lib/components/shared-components/user-avatar.svelte';
import { AlbumPageViewMode, AppRoute } from '$lib/constants';
import { activityManager } from '$lib/managers/activity-manager.svelte';
import { modalManager } from '$lib/managers/modal-manager.svelte';
import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
import type { TimelineAsset } from '$lib/managers/timeline-manager/types';
import AlbumOptionsModal from '$lib/modals/AlbumOptionsModal.svelte';
@@ -70,7 +69,7 @@
updateAlbumInfo,
type AlbumUserAddDto,
} from '@immich/sdk';
import { Button, IconButton } from '@immich/ui';
import { Button, IconButton, modalManager } from '@immich/ui';
import {
mdiArrowLeft,
mdiCogOutline,

View File

@@ -15,7 +15,6 @@
NotificationType,
} from '$lib/components/shared-components/notification/notification';
import { ActionQueryParameterValue, AppRoute, QueryParameter, SessionStorageKey } from '$lib/constants';
import { modalManager } from '$lib/managers/modal-manager.svelte';
import PersonEditBirthDateModal from '$lib/modals/PersonEditBirthDateModal.svelte';
import PersonMergeSuggestionModal from '$lib/modals/PersonMergeSuggestionModal.svelte';
import { locale } from '$lib/stores/preferences.store';
@@ -24,7 +23,7 @@
import { handleError } from '$lib/utils/handle-error';
import { clearQueryParam } from '$lib/utils/navigation';
import { getAllPeople, getPerson, searchPerson, updatePerson, type PersonResponseDto } from '@immich/sdk';
import { Button } from '@immich/ui';
import { Button, modalManager } from '@immich/ui';
import { mdiAccountOff, mdiEyeOutline } from '@mdi/js';
import { onMount } from 'svelte';
import { t } from 'svelte-i18n';

View File

@@ -31,7 +31,6 @@
notificationController,
} from '$lib/components/shared-components/notification/notification';
import { AppRoute, PersonPageViewMode, QueryParameter, SessionStorageKey } from '$lib/constants';
import { modalManager } from '$lib/managers/modal-manager.svelte';
import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
import type { TimelineAsset } from '$lib/managers/timeline-manager/types';
import PersonEditBirthDateModal from '$lib/modals/PersonEditBirthDateModal.svelte';
@@ -51,6 +50,7 @@
updatePerson,
type PersonResponseDto,
} from '@immich/sdk';
import { modalManager } from '@immich/ui';
import {
mdiAccountBoxOutline,
mdiAccountMultipleCheckOutline,

View File

@@ -9,10 +9,10 @@
} from '$lib/components/shared-components/notification/notification';
import SharedLinkCard from '$lib/components/sharedlinks-page/shared-link-card.svelte';
import { AppRoute } from '$lib/constants';
import { modalManager } from '$lib/managers/modal-manager.svelte';
import SharedLinkCreateModal from '$lib/modals/SharedLinkCreateModal.svelte';
import { handleError } from '$lib/utils/handle-error';
import { getAllSharedLinks, removeSharedLink, SharedLinkType, type SharedLinkResponseDto } from '@immich/sdk';
import { modalManager } from '@immich/ui';
import { onMount } from 'svelte';
import { t } from 'svelte-i18n';
import type { PageData } from './$types';

View File

@@ -8,14 +8,13 @@
import TreeItems from '$lib/components/shared-components/tree/tree-items.svelte';
import Sidebar from '$lib/components/sidebar/sidebar.svelte';
import { AppRoute, AssetAction, QueryParameter } from '$lib/constants';
import { modalManager } from '$lib/managers/modal-manager.svelte';
import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
import TagCreateModal from '$lib/modals/TagCreateModal.svelte';
import TagEditModal from '$lib/modals/TagEditModal.svelte';
import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
import { joinPaths, TreeNode } from '$lib/utils/tree-utils';
import { deleteTag, getAllTags, type TagResponseDto } from '@immich/sdk';
import { Button, HStack, Text } from '@immich/ui';
import { Button, HStack, modalManager, Text } from '@immich/ui';
import { mdiPencil, mdiPlus, mdiTag, mdiTagMultiple, mdiTrashCanOutline } from '@mdi/js';
import { onDestroy } from 'svelte';
import { t } from 'svelte-i18n';

View File

@@ -9,18 +9,17 @@
import AssetSelectControlBar from '$lib/components/photos-page/asset-select-control-bar.svelte';
import EmptyPlaceholder from '$lib/components/shared-components/empty-placeholder.svelte';
import {
NotificationType,
notificationController,
NotificationType,
} from '$lib/components/shared-components/notification/notification';
import { AppRoute } from '$lib/constants';
import { modalManager } from '$lib/managers/modal-manager.svelte';
import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
import { featureFlags, serverConfig } from '$lib/stores/server-config.store';
import { handlePromiseError } from '$lib/utils';
import { handleError } from '$lib/utils/handle-error';
import { emptyTrash, restoreTrash } from '@immich/sdk';
import { Button, HStack, Text } from '@immich/ui';
import { Button, HStack, modalManager, Text } from '@immich/ui';
import { mdiDeleteForeverOutline, mdiHistory } from '@mdi/js';
import { onDestroy } from 'svelte';
import { t } from 'svelte-i18n';

View File

@@ -1,9 +1,8 @@
<script lang="ts">
import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
import UserSettingsList from '$lib/components/user-settings-page/user-settings-list.svelte';
import { modalManager } from '$lib/managers/modal-manager.svelte';
import ShortcutsModal from '$lib/modals/ShortcutsModal.svelte';
import { Container, IconButton } from '@immich/ui';
import { Container, IconButton, modalManager } from '@immich/ui';
import { mdiKeyboard } from '@mdi/js';
import { t } from 'svelte-i18n';
import type { PageData } from './$types';

View File

@@ -1,11 +1,10 @@
<script lang="ts">
import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
import {
NotificationType,
notificationController,
NotificationType,
} from '$lib/components/shared-components/notification/notification';
import DuplicatesCompareControl from '$lib/components/utilities-page/duplicates/duplicates-compare-control.svelte';
import { modalManager } from '$lib/managers/modal-manager.svelte';
import DuplicatesInformationModal from '$lib/modals/DuplicatesInformationModal.svelte';
import ShortcutsModal from '$lib/modals/ShortcutsModal.svelte';
import { locale } from '$lib/stores/preferences.store';
@@ -15,7 +14,7 @@
import { handleError } from '$lib/utils/handle-error';
import type { AssetResponseDto } from '@immich/sdk';
import { deleteAssets, deleteDuplicates, updateAssets } from '@immich/sdk';
import { Button, HStack, IconButton, Text } from '@immich/ui';
import { Button, HStack, IconButton, modalManager, Text } from '@immich/ui';
import { mdiCheckOutline, mdiInformationOutline, mdiKeyboard, mdiTrashCanOutline } from '@mdi/js';
import { t } from 'svelte-i18n';
import type { PageData } from './$types';

View File

@@ -9,7 +9,6 @@
import NotificationList from '$lib/components/shared-components/notification/notification-list.svelte';
import UploadPanel from '$lib/components/shared-components/upload-panel.svelte';
import { eventManager } from '$lib/managers/event-manager.svelte';
import { modalManager } from '$lib/managers/modal-manager.svelte';
import VersionAnnouncementModal from '$lib/modals/VersionAnnouncementModal.svelte';
import { serverConfig } from '$lib/stores/server-config.store';
import { user } from '$lib/stores/user.store';
@@ -22,7 +21,7 @@
import { copyToClipboard } from '$lib/utils';
import { isAssetViewerRoute } from '$lib/utils/navigation';
import type { ServerVersionResponseDto } from '@immich/sdk';
import { setTranslations } from '@immich/ui';
import { modalManager, setTranslations } from '@immich/ui';
import { onMount, type Snippet } from 'svelte';
import { t } from 'svelte-i18n';
import { run } from 'svelte/legacy';

View File

@@ -2,11 +2,10 @@
import JobsPanel from '$lib/components/admin-page/jobs/jobs-panel.svelte';
import AdminPageLayout from '$lib/components/layouts/AdminPageLayout.svelte';
import { AppRoute } from '$lib/constants';
import { modalManager } from '$lib/managers/modal-manager.svelte';
import JobCreateModal from '$lib/modals/JobCreateModal.svelte';
import { asyncTimeout } from '$lib/utils';
import { getAllJobsStatus, type AllJobStatusResponseDto } from '@immich/sdk';
import { Button, HStack, Text } from '@immich/ui';
import { Button, HStack, modalManager, Text } from '@immich/ui';
import { mdiCog, mdiPlus } from '@mdi/js';
import { onDestroy, onMount } from 'svelte';
import { t } from 'svelte-i18n';

View File

@@ -10,7 +10,6 @@
notificationController,
NotificationType,
} from '$lib/components/shared-components/notification/notification';
import { modalManager } from '$lib/managers/modal-manager.svelte';
import LibraryImportPathModal from '$lib/modals/LibraryImportPathModal.svelte';
import LibraryRenameModal from '$lib/modals/LibraryRenameModal.svelte';
import LibraryUserPickerModal from '$lib/modals/LibraryUserPickerModal.svelte';
@@ -32,7 +31,7 @@
type LibraryStatsResponseDto,
type UserResponseDto,
} from '@immich/sdk';
import { Button, Text } from '@immich/ui';
import { Button, modalManager, Text } from '@immich/ui';
import { mdiDotsVertical, mdiPlusBoxOutline, mdiSync } from '@mdi/js';
import { onMount } from 'svelte';
import { t } from 'svelte-i18n';

View File

@@ -7,7 +7,6 @@
notificationController,
} from '$lib/components/shared-components/notification/notification';
import { AppRoute } from '$lib/constants';
import { modalManager } from '$lib/managers/modal-manager.svelte';
import UserCreateModal from '$lib/modals/UserCreateModal.svelte';
import UserDeleteConfirmModal from '$lib/modals/UserDeleteConfirmModal.svelte';
import UserRestoreConfirmModal from '$lib/modals/UserRestoreConfirmModal.svelte';
@@ -17,7 +16,7 @@
import { websocketEvents } from '$lib/stores/websocket';
import { getByteUnitString } from '$lib/utils/byte-units';
import { UserStatus, searchUsersAdmin, type UserAdminResponseDto } from '@immich/sdk';
import { Button, HStack, IconButton, Text } from '@immich/ui';
import { Button, HStack, IconButton, Text, modalManager } from '@immich/ui';
import { mdiDeleteRestore, mdiEyeOutline, mdiInfinity, mdiPlusBoxOutline, mdiTrashCanOutline } from '@mdi/js';
import { DateTime } from 'luxon';
import { onMount } from 'svelte';

View File

@@ -1,12 +1,12 @@
<script lang="ts">
import StatsCard from '$lib/components/admin-page/server-stats/stats-card.svelte';
import FeatureSetting from '$lib/components/admin-page/user/feature-setting.svelte';
import AdminPageLayout from '$lib/components/layouts/AdminPageLayout.svelte';
import {
notificationController,
NotificationType,
} from '$lib/components/shared-components/notification/notification';
import UserAvatar from '$lib/components/shared-components/user-avatar.svelte';
import { modalManager } from '$lib/managers/modal-manager.svelte';
import PasswordResetSuccessModal from '$lib/modals/PasswordResetSuccessModal.svelte';
import UserDeleteConfirmModal from '$lib/modals/UserDeleteConfirmModal.svelte';
import UserEditModal from '$lib/modals/UserEditModal.svelte';
@@ -29,6 +29,7 @@
Heading,
HStack,
Icon,
modalManager,
Stack,
Text,
} from '@immich/ui';
@@ -48,7 +49,6 @@
} from '@mdi/js';
import { t } from 'svelte-i18n';
import type { PageData } from './$types';
import FeatureSetting from '$lib/components/admin-page/user/feature-setting.svelte';
interface Props {
data: PageData;