mirror of
https://github.com/immich-app/immich.git
synced 2025-12-10 14:51:07 -08:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6563fa608a | ||
|
|
1a90fc8e58 | ||
|
|
c707f9cef4 | ||
|
|
6fda863c08 | ||
|
|
373b654156 | ||
|
|
a5d84ba552 | ||
|
|
1dc8fa2979 | ||
|
|
0426699f13 | ||
|
|
8154ec29df | ||
|
|
3024cd343b | ||
|
|
0b44d4b6f2 |
6
cli/package-lock.json
generated
6
cli/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@immich/cli",
|
||||
"version": "2.2.71",
|
||||
"version": "2.2.72",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@immich/cli",
|
||||
"version": "2.2.71",
|
||||
"version": "2.2.72",
|
||||
"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.2",
|
||||
"version": "1.135.3",
|
||||
"dev": true,
|
||||
"license": "GNU Affero General Public License version 3",
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@immich/cli",
|
||||
"version": "2.2.71",
|
||||
"version": "2.2.72",
|
||||
"description": "Command Line Interface (CLI) for Immich",
|
||||
"type": "module",
|
||||
"exports": "./dist/index.js",
|
||||
|
||||
4
docs/static/archived-versions.json
vendored
4
docs/static/archived-versions.json
vendored
@@ -1,4 +1,8 @@
|
||||
[
|
||||
{
|
||||
"label": "v1.135.3",
|
||||
"url": "https://v1.135.3.archive.immich.app"
|
||||
},
|
||||
{
|
||||
"label": "v1.135.2",
|
||||
"url": "https://v1.135.2.archive.immich.app"
|
||||
|
||||
8
e2e/package-lock.json
generated
8
e2e/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "immich-e2e",
|
||||
"version": "1.135.2",
|
||||
"version": "1.135.3",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "immich-e2e",
|
||||
"version": "1.135.2",
|
||||
"version": "1.135.3",
|
||||
"license": "GNU Affero General Public License version 3",
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "^3.1.0",
|
||||
@@ -44,7 +44,7 @@
|
||||
},
|
||||
"../cli": {
|
||||
"name": "@immich/cli",
|
||||
"version": "2.2.71",
|
||||
"version": "2.2.72",
|
||||
"dev": true,
|
||||
"license": "GNU Affero General Public License version 3",
|
||||
"dependencies": {
|
||||
@@ -93,7 +93,7 @@
|
||||
},
|
||||
"../open-api/typescript-sdk": {
|
||||
"name": "@immich/sdk",
|
||||
"version": "1.135.2",
|
||||
"version": "1.135.3",
|
||||
"dev": true,
|
||||
"license": "GNU Affero General Public License version 3",
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "immich-e2e",
|
||||
"version": "1.135.2",
|
||||
"version": "1.135.3",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
|
||||
@@ -35,8 +35,8 @@ platform :android do
|
||||
task: 'bundle',
|
||||
build_type: 'Release',
|
||||
properties: {
|
||||
"android.injected.version.code" => 203,
|
||||
"android.injected.version.name" => "1.135.2",
|
||||
"android.injected.version.code" => 204,
|
||||
"android.injected.version.name" => "1.135.3",
|
||||
}
|
||||
)
|
||||
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')
|
||||
|
||||
@@ -22,7 +22,7 @@ platform :ios do
|
||||
path: "./Runner.xcodeproj",
|
||||
)
|
||||
increment_version_number(
|
||||
version_number: "1.135.2"
|
||||
version_number: "1.135.3"
|
||||
)
|
||||
increment_build_number(
|
||||
build_number: latest_testflight_build_number + 1,
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
// ignore_for_file: public_member_api_docs, sort_constructors_first
|
||||
import 'dart:convert';
|
||||
|
||||
abstract interface class IPersonApiRepository {
|
||||
Future<List<Person>> getAll();
|
||||
Future<Person> update(String id, {String? name});
|
||||
}
|
||||
|
||||
class Person {
|
||||
Person({
|
||||
required this.id,
|
||||
@@ -1,8 +0,0 @@
|
||||
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||
|
||||
abstract class IPartnerRepository {
|
||||
Future<List<UserDto>> getSharedWith();
|
||||
Future<List<UserDto>> getSharedBy();
|
||||
Stream<List<UserDto>> watchSharedWith();
|
||||
Stream<List<UserDto>> watchSharedBy();
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||
|
||||
abstract interface class IPartnerApiRepository {
|
||||
Future<List<UserDto>> getAll(Direction direction);
|
||||
Future<UserDto> create(String id);
|
||||
Future<UserDto> update(String id, {required bool inTimeline});
|
||||
Future<void> delete(String id);
|
||||
}
|
||||
|
||||
enum Direction {
|
||||
sharedWithMe,
|
||||
sharedByMe,
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
import 'package:immich_mobile/entities/album.entity.dart';
|
||||
import 'package:immich_mobile/entities/asset.entity.dart';
|
||||
import 'package:immich_mobile/widgets/asset_grid/asset_grid_data_structure.dart';
|
||||
|
||||
abstract class ITimelineRepository {
|
||||
Future<List<String>> getTimelineUserIds(String id);
|
||||
|
||||
Stream<List<String>> watchTimelineUsers(String id);
|
||||
|
||||
Stream<RenderList> watchArchiveTimeline(String userId);
|
||||
Stream<RenderList> watchFavoriteTimeline(String userId);
|
||||
Stream<RenderList> watchTrashTimeline(String userId);
|
||||
Stream<RenderList> watchAlbumTimeline(
|
||||
Album album,
|
||||
GroupAssetsBy groupAssetsBy,
|
||||
);
|
||||
Stream<RenderList> watchAllVideosTimeline(String userId);
|
||||
|
||||
Stream<RenderList> watchHomeTimeline(
|
||||
String userId,
|
||||
GroupAssetsBy groupAssetsBy,
|
||||
);
|
||||
Stream<RenderList> watchMultiUsersTimeline(
|
||||
List<String> userIds,
|
||||
GroupAssetsBy groupAssetsBy,
|
||||
);
|
||||
|
||||
Future<RenderList> getTimelineFromAssets(
|
||||
List<Asset> assets,
|
||||
GroupAssetsBy getGroupByOption,
|
||||
);
|
||||
|
||||
Stream<RenderList> watchAssetSelectionTimeline(String userId);
|
||||
|
||||
Stream<RenderList> watchLockedTimeline(
|
||||
String userId,
|
||||
GroupAssetsBy groupAssetsBy,
|
||||
);
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
// ignore_for_file: public_member_api_docs, sort_constructors_first
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:immich_mobile/domain/models/person.model.dart';
|
||||
import 'package:immich_mobile/entities/asset.entity.dart';
|
||||
import 'package:immich_mobile/interfaces/person_api.interface.dart';
|
||||
|
||||
class SearchLocationFilter {
|
||||
String? country;
|
||||
|
||||
@@ -6,9 +6,9 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/constants/enums.dart';
|
||||
import 'package:immich_mobile/domain/models/person.model.dart';
|
||||
import 'package:immich_mobile/entities/asset.entity.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/interfaces/person_api.interface.dart';
|
||||
import 'package:immich_mobile/models/search/search_filter.model.dart';
|
||||
import 'package:immich_mobile/providers/search/paginated_search.provider.dart';
|
||||
import 'package:immich_mobile/providers/search/search_input_focus.provider.dart';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/interfaces/person_api.interface.dart';
|
||||
import 'package:immich_mobile/domain/models/person.model.dart';
|
||||
import 'package:immich_mobile/widgets/asset_grid/asset_grid_data_structure.dart';
|
||||
import 'package:immich_mobile/services/person.service.dart';
|
||||
import 'package:immich_mobile/providers/app_settings.provider.dart';
|
||||
|
||||
@@ -2,7 +2,6 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/user.entity.dart'
|
||||
as entity;
|
||||
import 'package:immich_mobile/interfaces/partner.interface.dart';
|
||||
import 'package:immich_mobile/providers/db.provider.dart';
|
||||
import 'package:immich_mobile/repositories/database.repository.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
@@ -11,11 +10,9 @@ final partnerRepositoryProvider = Provider(
|
||||
(ref) => PartnerRepository(ref.watch(dbProvider)),
|
||||
);
|
||||
|
||||
class PartnerRepository extends DatabaseRepository
|
||||
implements IPartnerRepository {
|
||||
class PartnerRepository extends DatabaseRepository {
|
||||
PartnerRepository(super.db);
|
||||
|
||||
@override
|
||||
Future<List<UserDto>> getSharedBy() async {
|
||||
return (await db.users
|
||||
.filter()
|
||||
@@ -26,7 +23,6 @@ class PartnerRepository extends DatabaseRepository
|
||||
.toList();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<UserDto>> getSharedWith() async {
|
||||
return (await db.users
|
||||
.filter()
|
||||
@@ -37,13 +33,11 @@ class PartnerRepository extends DatabaseRepository
|
||||
.toList();
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<List<UserDto>> watchSharedBy() {
|
||||
return (db.users.filter().isPartnerSharedByEqualTo(true).sortById().watch())
|
||||
.map((users) => users.map((u) => u.toDto()).toList());
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<List<UserDto>> watchSharedWith() {
|
||||
return (db.users
|
||||
.filter()
|
||||
|
||||
@@ -1,24 +1,26 @@
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||
import 'package:immich_mobile/infrastructure/utils/user.converter.dart';
|
||||
import 'package:immich_mobile/interfaces/partner_api.interface.dart';
|
||||
import 'package:immich_mobile/providers/api.provider.dart';
|
||||
import 'package:immich_mobile/repositories/api.repository.dart';
|
||||
import 'package:openapi/api.dart';
|
||||
|
||||
enum Direction {
|
||||
sharedWithMe,
|
||||
sharedByMe,
|
||||
}
|
||||
|
||||
final partnerApiRepositoryProvider = Provider(
|
||||
(ref) => PartnerApiRepository(
|
||||
ref.watch(apiServiceProvider).partnersApi,
|
||||
),
|
||||
);
|
||||
|
||||
class PartnerApiRepository extends ApiRepository
|
||||
implements IPartnerApiRepository {
|
||||
class PartnerApiRepository extends ApiRepository {
|
||||
final PartnersApi _api;
|
||||
|
||||
PartnerApiRepository(this._api);
|
||||
|
||||
@override
|
||||
Future<List<UserDto>> getAll(Direction direction) async {
|
||||
final response = await checkNull(
|
||||
_api.getPartners(
|
||||
@@ -30,16 +32,13 @@ class PartnerApiRepository extends ApiRepository
|
||||
return response.map(UserConverter.fromPartnerDto).toList();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<UserDto> create(String id) async {
|
||||
final dto = await checkNull(_api.createPartner(id));
|
||||
return UserConverter.fromPartnerDto(dto);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> delete(String id) => _api.removePartner(id);
|
||||
|
||||
@override
|
||||
Future<UserDto> update(String id, {required bool inTimeline}) async {
|
||||
final dto = await checkNull(
|
||||
_api.updatePartner(
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/interfaces/person_api.interface.dart';
|
||||
import 'package:immich_mobile/domain/models/person.model.dart';
|
||||
import 'package:immich_mobile/providers/api.provider.dart';
|
||||
import 'package:immich_mobile/repositories/api.repository.dart';
|
||||
import 'package:openapi/api.dart';
|
||||
@@ -8,19 +8,16 @@ final personApiRepositoryProvider = Provider(
|
||||
(ref) => PersonApiRepository(ref.watch(apiServiceProvider).peopleApi),
|
||||
);
|
||||
|
||||
class PersonApiRepository extends ApiRepository
|
||||
implements IPersonApiRepository {
|
||||
class PersonApiRepository extends ApiRepository {
|
||||
final PeopleApi _api;
|
||||
|
||||
PersonApiRepository(this._api);
|
||||
|
||||
@override
|
||||
Future<List<Person>> getAll() async {
|
||||
final dto = await checkNull(_api.getAllPeople());
|
||||
return dto.people.map(_toPerson).toList();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Person> update(String id, {String? name}) async {
|
||||
final dto = await checkNull(
|
||||
_api.updatePerson(id, PersonUpdateDto(name: name)),
|
||||
|
||||
@@ -3,7 +3,6 @@ import 'package:immich_mobile/constants/enums.dart';
|
||||
import 'package:immich_mobile/entities/album.entity.dart';
|
||||
import 'package:immich_mobile/entities/asset.entity.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/user.entity.dart';
|
||||
import 'package:immich_mobile/interfaces/timeline.interface.dart';
|
||||
import 'package:immich_mobile/providers/db.provider.dart';
|
||||
import 'package:immich_mobile/repositories/database.repository.dart';
|
||||
import 'package:immich_mobile/utils/hash.dart';
|
||||
@@ -13,11 +12,9 @@ import 'package:isar/isar.dart';
|
||||
final timelineRepositoryProvider =
|
||||
Provider((ref) => TimelineRepository(ref.watch(dbProvider)));
|
||||
|
||||
class TimelineRepository extends DatabaseRepository
|
||||
implements ITimelineRepository {
|
||||
class TimelineRepository extends DatabaseRepository {
|
||||
TimelineRepository(super.db);
|
||||
|
||||
@override
|
||||
Future<List<String>> getTimelineUserIds(String id) {
|
||||
return db.users
|
||||
.filter()
|
||||
@@ -28,7 +25,6 @@ class TimelineRepository extends DatabaseRepository
|
||||
.findAll();
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<List<String>> watchTimelineUsers(String id) {
|
||||
return db.users
|
||||
.filter()
|
||||
@@ -39,7 +35,6 @@ class TimelineRepository extends DatabaseRepository
|
||||
.watch();
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<RenderList> watchArchiveTimeline(String userId) {
|
||||
final query = db.assets
|
||||
.where()
|
||||
@@ -52,7 +47,6 @@ class TimelineRepository extends DatabaseRepository
|
||||
return _watchRenderList(query, GroupAssetsBy.none);
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<RenderList> watchFavoriteTimeline(String userId) {
|
||||
final query = db.assets
|
||||
.where()
|
||||
@@ -67,7 +61,6 @@ class TimelineRepository extends DatabaseRepository
|
||||
return _watchRenderList(query, GroupAssetsBy.none);
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<RenderList> watchAlbumTimeline(
|
||||
Album album,
|
||||
GroupAssetsBy groupAssetByOption,
|
||||
@@ -86,7 +79,6 @@ class TimelineRepository extends DatabaseRepository
|
||||
return _watchRenderList(withSortedOption, groupAssetByOption);
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<RenderList> watchTrashTimeline(String userId) {
|
||||
final query = db.assets
|
||||
.filter()
|
||||
@@ -97,7 +89,6 @@ class TimelineRepository extends DatabaseRepository
|
||||
return _watchRenderList(query, GroupAssetsBy.none);
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<RenderList> watchAllVideosTimeline(String userId) {
|
||||
final query = db.assets
|
||||
.where()
|
||||
@@ -111,7 +102,6 @@ class TimelineRepository extends DatabaseRepository
|
||||
return _watchRenderList(query, GroupAssetsBy.none);
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<RenderList> watchHomeTimeline(
|
||||
String userId,
|
||||
GroupAssetsBy groupAssetByOption,
|
||||
@@ -128,7 +118,6 @@ class TimelineRepository extends DatabaseRepository
|
||||
return _watchRenderList(query, groupAssetByOption);
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<RenderList> watchMultiUsersTimeline(
|
||||
List<String> userIds,
|
||||
GroupAssetsBy groupAssetByOption,
|
||||
@@ -145,7 +134,6 @@ class TimelineRepository extends DatabaseRepository
|
||||
return _watchRenderList(query, groupAssetByOption);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<RenderList> getTimelineFromAssets(
|
||||
List<Asset> assets,
|
||||
GroupAssetsBy getGroupByOption,
|
||||
@@ -153,7 +141,6 @@ class TimelineRepository extends DatabaseRepository
|
||||
return RenderList.fromAssets(assets, getGroupByOption);
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<RenderList> watchAssetSelectionTimeline(String userId) {
|
||||
final query = db.assets
|
||||
.where()
|
||||
@@ -168,7 +155,6 @@ class TimelineRepository extends DatabaseRepository
|
||||
return _watchRenderList(query, GroupAssetsBy.none);
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<RenderList> watchLockedTimeline(
|
||||
String userId,
|
||||
GroupAssetsBy getGroupByOption,
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/user.repository.dart';
|
||||
import 'package:immich_mobile/interfaces/partner.interface.dart';
|
||||
import 'package:immich_mobile/interfaces/partner_api.interface.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/user.provider.dart';
|
||||
import 'package:immich_mobile/repositories/partner.repository.dart';
|
||||
import 'package:immich_mobile/repositories/partner_api.repository.dart';
|
||||
@@ -17,8 +15,8 @@ final partnerServiceProvider = Provider(
|
||||
);
|
||||
|
||||
class PartnerService {
|
||||
final IPartnerApiRepository _partnerApiRepository;
|
||||
final IPartnerRepository _partnerRepository;
|
||||
final PartnerApiRepository _partnerApiRepository;
|
||||
final PartnerRepository _partnerRepository;
|
||||
final IsarUserRepository _isarUserRepository;
|
||||
final Logger _log = Logger("PartnerService");
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/domain/models/person.model.dart';
|
||||
import 'package:immich_mobile/entities/asset.entity.dart';
|
||||
import 'package:immich_mobile/interfaces/asset.interface.dart';
|
||||
import 'package:immich_mobile/interfaces/asset_api.interface.dart';
|
||||
import 'package:immich_mobile/interfaces/person_api.interface.dart';
|
||||
import 'package:immich_mobile/repositories/asset.repository.dart';
|
||||
import 'package:immich_mobile/repositories/asset_api.repository.dart';
|
||||
import 'package:immich_mobile/repositories/person_api.repository.dart';
|
||||
@@ -20,7 +20,7 @@ PersonService personService(Ref ref) => PersonService(
|
||||
|
||||
class PersonService {
|
||||
final Logger _log = Logger("PersonService");
|
||||
final IPersonApiRepository _personApiRepository;
|
||||
final PersonApiRepository _personApiRepository;
|
||||
final IAssetApiRepository _assetApiRepository;
|
||||
final IAssetRepository _assetRepository;
|
||||
|
||||
|
||||
@@ -17,8 +17,6 @@ import 'package:immich_mobile/interfaces/album.interface.dart';
|
||||
import 'package:immich_mobile/interfaces/asset.interface.dart';
|
||||
import 'package:immich_mobile/interfaces/etag.interface.dart';
|
||||
import 'package:immich_mobile/interfaces/local_files_manager.interface.dart';
|
||||
import 'package:immich_mobile/interfaces/partner.interface.dart';
|
||||
import 'package:immich_mobile/interfaces/partner_api.interface.dart';
|
||||
import 'package:immich_mobile/providers/app_settings.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/exif.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/user.provider.dart';
|
||||
@@ -69,9 +67,9 @@ class SyncService {
|
||||
final IExifInfoRepository _exifInfoRepository;
|
||||
final IsarUserRepository _isarUserRepository;
|
||||
final UserService _userService;
|
||||
final IPartnerRepository _partnerRepository;
|
||||
final PartnerRepository _partnerRepository;
|
||||
final IETagRepository _eTagRepository;
|
||||
final IPartnerApiRepository _partnerApiRepository;
|
||||
final PartnerApiRepository _partnerApiRepository;
|
||||
final UserApiRepository _userApiRepository;
|
||||
final AsyncMutex _lock = AsyncMutex();
|
||||
final Logger _log = Logger('SyncService');
|
||||
|
||||
@@ -2,7 +2,6 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/domain/services/user.service.dart';
|
||||
import 'package:immich_mobile/entities/album.entity.dart';
|
||||
import 'package:immich_mobile/entities/asset.entity.dart';
|
||||
import 'package:immich_mobile/interfaces/timeline.interface.dart';
|
||||
import 'package:immich_mobile/providers/app_settings.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/user.provider.dart';
|
||||
import 'package:immich_mobile/repositories/timeline.repository.dart';
|
||||
@@ -18,7 +17,7 @@ final timelineServiceProvider = Provider<TimelineService>((ref) {
|
||||
});
|
||||
|
||||
class TimelineService {
|
||||
final ITimelineRepository _timelineRepository;
|
||||
final TimelineRepository _timelineRepository;
|
||||
final AppSettingsService _appSettingsService;
|
||||
final UserService _userService;
|
||||
|
||||
|
||||
@@ -2,9 +2,9 @@ import 'package:flutter/material.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/domain/models/person.model.dart';
|
||||
import 'package:immich_mobile/extensions/asyncvalue_extensions.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/interfaces/person_api.interface.dart';
|
||||
import 'package:immich_mobile/pages/common/large_leading_tile.dart';
|
||||
import 'package:immich_mobile/providers/search/people.provider.dart';
|
||||
import 'package:immich_mobile/services/api.service.dart';
|
||||
|
||||
2
mobile/openapi/README.md
generated
2
mobile/openapi/README.md
generated
@@ -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.2
|
||||
- API version: 1.135.3
|
||||
- Generator version: 7.8.0
|
||||
- Build package: org.openapitools.codegen.languages.DartClientCodegen
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ name: immich_mobile
|
||||
description: Immich - selfhosted backup media file on mobile phone
|
||||
|
||||
publish_to: 'none'
|
||||
version: 1.135.2+203
|
||||
version: 1.135.3+204
|
||||
|
||||
environment:
|
||||
sdk: '>=3.3.0 <4.0.0'
|
||||
|
||||
@@ -11,7 +11,7 @@ import 'package:immich_mobile/entities/store.entity.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/log.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/store.repository.dart';
|
||||
import 'package:immich_mobile/interfaces/asset.interface.dart';
|
||||
import 'package:immich_mobile/interfaces/partner_api.interface.dart';
|
||||
import 'package:immich_mobile/repositories/partner_api.repository.dart';
|
||||
import 'package:immich_mobile/services/sync.service.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
|
||||
|
||||
@@ -9,10 +9,10 @@ import 'package:immich_mobile/interfaces/backup_album.interface.dart';
|
||||
import 'package:immich_mobile/interfaces/etag.interface.dart';
|
||||
import 'package:immich_mobile/interfaces/file_media.interface.dart';
|
||||
import 'package:immich_mobile/interfaces/local_files_manager.interface.dart';
|
||||
import 'package:immich_mobile/interfaces/partner.interface.dart';
|
||||
import 'package:immich_mobile/interfaces/partner_api.interface.dart';
|
||||
import 'package:immich_mobile/repositories/partner_api.repository.dart';
|
||||
import 'package:immich_mobile/repositories/album_media.repository.dart';
|
||||
import 'package:immich_mobile/repositories/album_api.repository.dart';
|
||||
import 'package:immich_mobile/repositories/partner.repository.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
|
||||
class MockAlbumRepository extends Mock implements IAlbumRepository {}
|
||||
@@ -42,9 +42,9 @@ class MockAuthApiRepository extends Mock implements IAuthApiRepository {}
|
||||
|
||||
class MockAuthRepository extends Mock implements IAuthRepository {}
|
||||
|
||||
class MockPartnerRepository extends Mock implements IPartnerRepository {}
|
||||
class MockPartnerRepository extends Mock implements PartnerRepository {}
|
||||
|
||||
class MockPartnerApiRepository extends Mock implements IPartnerApiRepository {}
|
||||
class MockPartnerApiRepository extends Mock implements PartnerApiRepository {}
|
||||
|
||||
class MockLocalFilesManagerRepository extends Mock
|
||||
implements ILocalFilesManager {}
|
||||
|
||||
@@ -8503,7 +8503,7 @@
|
||||
"info": {
|
||||
"title": "Immich",
|
||||
"description": "Immich API",
|
||||
"version": "1.135.2",
|
||||
"version": "1.135.3",
|
||||
"contact": {}
|
||||
},
|
||||
"tags": [],
|
||||
|
||||
4
open-api/typescript-sdk/package-lock.json
generated
4
open-api/typescript-sdk/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@immich/sdk",
|
||||
"version": "1.135.2",
|
||||
"version": "1.135.3",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@immich/sdk",
|
||||
"version": "1.135.2",
|
||||
"version": "1.135.3",
|
||||
"license": "GNU Affero General Public License version 3",
|
||||
"dependencies": {
|
||||
"@oazapfts/runtime": "^1.0.2"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@immich/sdk",
|
||||
"version": "1.135.2",
|
||||
"version": "1.135.3",
|
||||
"description": "Auto-generated TypeScript SDK for the Immich API",
|
||||
"type": "module",
|
||||
"main": "./build/index.js",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Immich
|
||||
* 1.135.2
|
||||
* 1.135.3
|
||||
* DO NOT MODIFY - This file has been generated using oazapfts.
|
||||
* See https://www.npmjs.com/package/oazapfts
|
||||
*/
|
||||
|
||||
4
server/package-lock.json
generated
4
server/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "immich",
|
||||
"version": "1.135.2",
|
||||
"version": "1.135.3",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "immich",
|
||||
"version": "1.135.2",
|
||||
"version": "1.135.3",
|
||||
"hasInstallScript": true,
|
||||
"license": "GNU Affero General Public License version 3",
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "immich",
|
||||
"version": "1.135.2",
|
||||
"version": "1.135.3",
|
||||
"description": "",
|
||||
"author": "",
|
||||
"private": true,
|
||||
|
||||
@@ -130,7 +130,7 @@ from
|
||||
where
|
||||
"ownerId" = $1
|
||||
and "updatedAt" < now() - interval '1 millisecond'
|
||||
and "updateId" < $2
|
||||
and "updateId" <= $2
|
||||
and "updateId" >= $3
|
||||
order by
|
||||
"updateId" asc
|
||||
@@ -274,7 +274,7 @@ from
|
||||
where
|
||||
"assets"."ownerId" = $1
|
||||
and "exif"."updatedAt" < now() - interval '1 millisecond'
|
||||
and "exif"."updateId" < $2
|
||||
and "exif"."updateId" <= $2
|
||||
and "exif"."updateId" >= $3
|
||||
order by
|
||||
"exif"."updateId" asc
|
||||
@@ -418,7 +418,7 @@ from
|
||||
where
|
||||
"albumsId" = $1
|
||||
and "updatedAt" < now() - interval '1 millisecond'
|
||||
and "updateId" < $2
|
||||
and "updateId" <= $2
|
||||
and "updateId" >= $3
|
||||
order by
|
||||
"updateId" asc
|
||||
|
||||
@@ -111,7 +111,7 @@ export class SyncRepository {
|
||||
.select(columns.syncAsset)
|
||||
.where('ownerId', '=', partnerId)
|
||||
.where('updatedAt', '<', sql.raw<Date>("now() - interval '1 millisecond'"))
|
||||
.where('updateId', '<', beforeUpdateId)
|
||||
.where('updateId', '<=', beforeUpdateId)
|
||||
.$if(!!afterUpdateId, (eb) => eb.where('updateId', '>=', afterUpdateId!))
|
||||
.orderBy('updateId', 'asc')
|
||||
.stream();
|
||||
@@ -169,7 +169,7 @@ export class SyncRepository {
|
||||
.innerJoin('assets', 'assets.id', 'exif.assetId')
|
||||
.where('assets.ownerId', '=', partnerId)
|
||||
.where('exif.updatedAt', '<', sql.raw<Date>("now() - interval '1 millisecond'"))
|
||||
.where('exif.updateId', '<', beforeUpdateId)
|
||||
.where('exif.updateId', '<=', beforeUpdateId)
|
||||
.$if(!!afterUpdateId, (eb) => eb.where('exif.updateId', '>=', afterUpdateId!))
|
||||
.orderBy('exif.updateId', 'asc')
|
||||
.stream();
|
||||
@@ -273,7 +273,7 @@ export class SyncRepository {
|
||||
.select(columns.syncAlbumUser)
|
||||
.where('albumsId', '=', albumId)
|
||||
.where('updatedAt', '<', sql.raw<Date>("now() - interval '1 millisecond'"))
|
||||
.where('updateId', '<', beforeUpdateId)
|
||||
.where('updateId', '<=', beforeUpdateId)
|
||||
.$if(!!afterUpdateId, (eb) => eb.where('updateId', '>=', afterUpdateId!))
|
||||
.orderBy('updateId', 'asc')
|
||||
.stream();
|
||||
|
||||
@@ -7,8 +7,8 @@ export async function up(qb: Kysely<any>): Promise<void> {
|
||||
return;
|
||||
}
|
||||
|
||||
await sql`alter database immich reset all;`.execute(qb);
|
||||
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') {
|
||||
|
||||
@@ -38,11 +38,11 @@ const mapSyncAssetV1 = ({ checksum, thumbhash, ...data }: AssetLike): SyncAssetV
|
||||
thumbhash: thumbhash ? hexOrBufferToBase64(thumbhash) : null,
|
||||
});
|
||||
|
||||
const isEntityBackfillComplete = (entity: { createId: string }, checkpoint: SyncAck | undefined): boolean =>
|
||||
entity.createId === checkpoint?.updateId && checkpoint.extraId === COMPLETE_ID;
|
||||
const isEntityBackfillComplete = (createId: string, checkpoint: SyncAck | undefined): boolean =>
|
||||
createId === checkpoint?.updateId && checkpoint.extraId === COMPLETE_ID;
|
||||
|
||||
const getStartId = (entity: { createId: string }, checkpoint: SyncAck | undefined): string | undefined =>
|
||||
checkpoint?.updateId === entity.createId ? checkpoint?.extraId : undefined;
|
||||
const getStartId = (createId: string, checkpoint: SyncAck | undefined): string | undefined =>
|
||||
createId === checkpoint?.updateId ? checkpoint?.extraId : undefined;
|
||||
|
||||
const send = <T extends keyof SyncItem, D extends SyncItem[T]>(response: Writable, item: SerializeOptions<T, D>) => {
|
||||
response.write(serialize(item));
|
||||
@@ -235,22 +235,23 @@ export class SyncService extends BaseService {
|
||||
const endId = upsertCheckpoint.updateId;
|
||||
|
||||
for (const partner of partners) {
|
||||
if (isEntityBackfillComplete(partner, backfillCheckpoint)) {
|
||||
const createId = partner.createId;
|
||||
if (isEntityBackfillComplete(createId, backfillCheckpoint)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const startId = getStartId(partner, backfillCheckpoint);
|
||||
const startId = getStartId(createId, backfillCheckpoint);
|
||||
const backfill = this.syncRepository.getPartnerAssetsBackfill(partner.sharedById, startId, endId);
|
||||
|
||||
for await (const { updateId, ...data } of backfill) {
|
||||
send(response, {
|
||||
type: backfillType,
|
||||
ids: [updateId],
|
||||
ids: [createId, updateId],
|
||||
data: mapSyncAssetV1(data),
|
||||
});
|
||||
}
|
||||
|
||||
sendEntityBackfillCompleteAck(response, backfillType, partner.sharedById);
|
||||
sendEntityBackfillCompleteAck(response, backfillType, createId);
|
||||
}
|
||||
} else if (partners.length > 0) {
|
||||
await this.upsertBackfillCheckpoint({
|
||||
@@ -291,18 +292,19 @@ export class SyncService extends BaseService {
|
||||
const endId = upsertCheckpoint.updateId;
|
||||
|
||||
for (const partner of partners) {
|
||||
if (isEntityBackfillComplete(partner, backfillCheckpoint)) {
|
||||
const createId = partner.createId;
|
||||
if (isEntityBackfillComplete(createId, backfillCheckpoint)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const startId = getStartId(partner, backfillCheckpoint);
|
||||
const startId = getStartId(createId, backfillCheckpoint);
|
||||
const backfill = this.syncRepository.getPartnerAssetExifsBackfill(partner.sharedById, startId, endId);
|
||||
|
||||
for await (const { updateId, ...data } of backfill) {
|
||||
send(response, { type: backfillType, ids: [updateId], data });
|
||||
send(response, { type: backfillType, ids: [partner.createId, updateId], data });
|
||||
}
|
||||
|
||||
sendEntityBackfillCompleteAck(response, backfillType, partner.sharedById);
|
||||
sendEntityBackfillCompleteAck(response, backfillType, partner.createId);
|
||||
}
|
||||
} else if (partners.length > 0) {
|
||||
await this.upsertBackfillCheckpoint({
|
||||
@@ -350,18 +352,19 @@ export class SyncService extends BaseService {
|
||||
const endId = upsertCheckpoint.updateId;
|
||||
|
||||
for (const album of albums) {
|
||||
if (isEntityBackfillComplete(album, backfillCheckpoint)) {
|
||||
const createId = album.createId;
|
||||
if (isEntityBackfillComplete(createId, backfillCheckpoint)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const startId = getStartId(album, backfillCheckpoint);
|
||||
const startId = getStartId(createId, backfillCheckpoint);
|
||||
const backfill = this.syncRepository.getAlbumUsersBackfill(album.id, startId, endId);
|
||||
|
||||
for await (const { updateId, ...data } of backfill) {
|
||||
send(response, { type: backfillType, ids: [updateId], data });
|
||||
send(response, { type: backfillType, ids: [createId, updateId], data });
|
||||
}
|
||||
|
||||
sendEntityBackfillCompleteAck(response, backfillType, album.id);
|
||||
sendEntityBackfillCompleteAck(response, backfillType, createId);
|
||||
}
|
||||
} else if (albums.length > 0) {
|
||||
await this.upsertBackfillCheckpoint({
|
||||
|
||||
@@ -7,12 +7,13 @@ import { getKyselyConfig } from 'src/utils/database';
|
||||
import { GenericContainer, Wait } from 'testcontainers';
|
||||
|
||||
const globalSetup = async () => {
|
||||
const templateName = 'mich';
|
||||
const postgresContainer = await new GenericContainer('ghcr.io/immich-app/postgres:14-vectorchord0.4.3')
|
||||
.withExposedPorts(5432)
|
||||
.withEnvironment({
|
||||
POSTGRES_PASSWORD: 'postgres',
|
||||
POSTGRES_USER: 'postgres',
|
||||
POSTGRES_DB: 'immich',
|
||||
POSTGRES_DB: templateName,
|
||||
})
|
||||
.withCommand([
|
||||
'postgres',
|
||||
@@ -35,7 +36,7 @@ const globalSetup = async () => {
|
||||
.start();
|
||||
|
||||
const postgresPort = postgresContainer.getMappedPort(5432);
|
||||
const postgresUrl = `postgres://postgres:postgres@localhost:${postgresPort}/immich`;
|
||||
const postgresUrl = `postgres://postgres:postgres@localhost:${postgresPort}/${templateName}`;
|
||||
|
||||
process.env.IMMICH_TEST_POSTGRES_URL = postgresUrl;
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ beforeAll(async () => {
|
||||
defaultDatabase = await getKyselyDB();
|
||||
});
|
||||
|
||||
describe.concurrent(SyncRequestType.PartnerAssetExifsV1, () => {
|
||||
describe(SyncRequestType.PartnerAssetExifsV1, () => {
|
||||
it('should detect and sync the first partner asset exif', async () => {
|
||||
const { auth, sut, getRepository, testSync } = await setup();
|
||||
|
||||
@@ -78,7 +78,6 @@ describe.concurrent(SyncRequestType.PartnerAssetExifsV1, () => {
|
||||
await sut.setAcks(auth, { acks });
|
||||
|
||||
const ackSyncResponse = await testSync(auth, [SyncRequestType.PartnerAssetExifsV1]);
|
||||
|
||||
expect(ackSyncResponse).toHaveLength(0);
|
||||
});
|
||||
|
||||
@@ -196,6 +195,79 @@ describe.concurrent(SyncRequestType.PartnerAssetExifsV1, () => {
|
||||
expect(finalAcks).toEqual([]);
|
||||
});
|
||||
|
||||
it('should handle partners with users ids lower than a uuidv7', async () => {
|
||||
const { auth, sut, getRepository, testSync } = await setup();
|
||||
|
||||
const userRepo = getRepository('user');
|
||||
const user2 = mediumFactory.userInsert({ id: '00d4c0af-7695-4cf2-85e6-415eeaf449cb' });
|
||||
const user3 = mediumFactory.userInsert({ id: '00e4c0af-7695-4cf2-85e6-415eeaf449cb' });
|
||||
await userRepo.create(user2);
|
||||
await userRepo.create(user3);
|
||||
|
||||
const assetRepo = getRepository('asset');
|
||||
const assetUser3 = mediumFactory.assetInsert({ ownerId: user3.id });
|
||||
await assetRepo.create(assetUser3);
|
||||
await assetRepo.upsertExif({ assetId: assetUser3.id, make: 'assetUser3' });
|
||||
|
||||
await wait(2);
|
||||
|
||||
const assetUser2 = mediumFactory.assetInsert({ ownerId: user2.id });
|
||||
await assetRepo.create(assetUser2);
|
||||
await assetRepo.upsertExif({ assetId: assetUser2.id, make: 'assetUser2' });
|
||||
|
||||
const partnerRepo = getRepository('partner');
|
||||
await partnerRepo.create({ sharedById: user2.id, sharedWithId: auth.user.id });
|
||||
|
||||
const response = await testSync(auth, [SyncRequestType.PartnerAssetExifsV1]);
|
||||
|
||||
expect(response).toHaveLength(1);
|
||||
expect(response).toEqual(
|
||||
expect.arrayContaining([
|
||||
{
|
||||
ack: expect.any(String),
|
||||
data: expect.objectContaining({
|
||||
assetId: assetUser2.id,
|
||||
}),
|
||||
type: SyncEntityType.PartnerAssetExifV1,
|
||||
},
|
||||
]),
|
||||
);
|
||||
|
||||
const acks = response.map(({ ack }) => ack);
|
||||
await sut.setAcks(auth, { acks });
|
||||
|
||||
// This checks that our ack upsert is correct
|
||||
const ackUpsertResponse = await testSync(auth, [SyncRequestType.PartnerAssetExifsV1]);
|
||||
expect(ackUpsertResponse).toEqual([]);
|
||||
|
||||
await partnerRepo.create({ sharedById: user3.id, sharedWithId: auth.user.id });
|
||||
|
||||
const syncAckResponse = await testSync(auth, [SyncRequestType.PartnerAssetExifsV1]);
|
||||
expect(syncAckResponse).toHaveLength(2);
|
||||
expect(syncAckResponse).toEqual(
|
||||
expect.arrayContaining([
|
||||
{
|
||||
ack: expect.stringMatching(new RegExp(`${SyncEntityType.PartnerAssetExifBackfillV1}\\|.+?\\|.+`)),
|
||||
data: expect.objectContaining({
|
||||
assetId: assetUser3.id,
|
||||
}),
|
||||
type: SyncEntityType.PartnerAssetExifBackfillV1,
|
||||
},
|
||||
{
|
||||
ack: expect.stringContaining(SyncEntityType.PartnerAssetExifBackfillV1),
|
||||
data: {},
|
||||
type: SyncEntityType.SyncAckV1,
|
||||
},
|
||||
]),
|
||||
);
|
||||
|
||||
const syncAckResponseAcks = syncAckResponse.map(({ ack }) => ack);
|
||||
await sut.setAcks(auth, { acks: [syncAckResponseAcks[1]] });
|
||||
|
||||
const finalResponse = await testSync(auth, [SyncRequestType.PartnerAssetExifsV1]);
|
||||
expect(finalResponse).toEqual([]);
|
||||
});
|
||||
|
||||
it('should only backfill partner assets created prior to the current partner asset checkpoint', async () => {
|
||||
const { auth, sut, getRepository, testSync } = await setup();
|
||||
|
||||
@@ -210,13 +282,13 @@ describe.concurrent(SyncRequestType.PartnerAssetExifsV1, () => {
|
||||
const assetUser2 = mediumFactory.assetInsert({ ownerId: user2.id });
|
||||
const asset2User3 = mediumFactory.assetInsert({ ownerId: user3.id });
|
||||
await assetRepo.create(assetUser3);
|
||||
await assetRepo.upsertExif({ assetId: assetUser3.id, make: 'Canon' });
|
||||
await assetRepo.upsertExif({ assetId: assetUser3.id, make: 'assetUser3' });
|
||||
await wait(2);
|
||||
await assetRepo.create(assetUser2);
|
||||
await assetRepo.upsertExif({ assetId: assetUser2.id, make: 'Canon' });
|
||||
await assetRepo.upsertExif({ assetId: assetUser2.id, make: 'assetUser2' });
|
||||
await wait(2);
|
||||
await assetRepo.create(asset2User3);
|
||||
await assetRepo.upsertExif({ assetId: asset2User3.id, make: 'Canon' });
|
||||
await assetRepo.upsertExif({ assetId: asset2User3.id, make: 'asset2User3' });
|
||||
|
||||
const partnerRepo = getRepository('partner');
|
||||
await partnerRepo.create({ sharedById: user2.id, sharedWithId: auth.user.id });
|
||||
@@ -246,7 +318,7 @@ describe.concurrent(SyncRequestType.PartnerAssetExifsV1, () => {
|
||||
expect(backfillResponse).toEqual(
|
||||
expect.arrayContaining([
|
||||
{
|
||||
ack: expect.any(String),
|
||||
ack: expect.stringMatching(new RegExp(`${SyncEntityType.PartnerAssetExifBackfillV1}\\|.+?\\|.+`)),
|
||||
data: expect.objectContaining({
|
||||
assetId: assetUser3.id,
|
||||
}),
|
||||
@@ -270,7 +342,6 @@ describe.concurrent(SyncRequestType.PartnerAssetExifsV1, () => {
|
||||
const backfillAck = backfillResponse[1].ack;
|
||||
const partnerAssetAck = backfillResponse[2].ack;
|
||||
await sut.setAcks(auth, { acks: [backfillAck, partnerAssetAck] });
|
||||
|
||||
const finalResponse = await testSync(auth, [SyncRequestType.PartnerAssetExifsV1]);
|
||||
|
||||
const finalAcks = finalResponse.map(({ ack }) => ack);
|
||||
|
||||
@@ -373,18 +373,23 @@ function* newPngFactory() {
|
||||
|
||||
const pngFactory = newPngFactory();
|
||||
|
||||
const withDatabase = (url: string, name: string) => url.replace('/immich', `/${name}`);
|
||||
const templateName = 'mich';
|
||||
|
||||
const withDatabase = (url: string, name: string) => url.replace(`/${templateName}`, `/${name}`);
|
||||
|
||||
export const getKyselyDB = async (suffix?: string): Promise<Kysely<DB>> => {
|
||||
const testUrl = process.env.IMMICH_TEST_POSTGRES_URL!;
|
||||
const sql = postgres({
|
||||
...asPostgresConnectionConfig({ connectionType: 'url', url: withDatabase(testUrl, 'postgres') }),
|
||||
...asPostgresConnectionConfig({
|
||||
connectionType: 'url',
|
||||
url: withDatabase(testUrl, 'postgres'),
|
||||
}),
|
||||
max: 1,
|
||||
});
|
||||
|
||||
const randomSuffix = Math.random().toString(36).slice(2, 7);
|
||||
const dbName = `immich_${suffix ?? randomSuffix}`;
|
||||
await sql.unsafe(`CREATE DATABASE ${dbName} WITH TEMPLATE immich OWNER postgres;`);
|
||||
await sql.unsafe(`CREATE DATABASE ${dbName} WITH TEMPLATE ${templateName} OWNER postgres;`);
|
||||
|
||||
return new Kysely<DB>(getKyselyConfig({ connectionType: 'url', url: withDatabase(testUrl, dbName) }));
|
||||
};
|
||||
|
||||
14
web/package-lock.json
generated
14
web/package-lock.json
generated
@@ -1,17 +1,17 @@
|
||||
{
|
||||
"name": "immich-web",
|
||||
"version": "1.135.2",
|
||||
"version": "1.135.3",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "immich-web",
|
||||
"version": "1.135.2",
|
||||
"version": "1.135.3",
|
||||
"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.22.7",
|
||||
"@immich/ui": "^0.22.8",
|
||||
"@mapbox/mapbox-gl-rtl-text": "0.2.3",
|
||||
"@mdi/js": "^7.4.47",
|
||||
"@photo-sphere-viewer/core": "^5.11.5",
|
||||
@@ -87,7 +87,7 @@
|
||||
},
|
||||
"../open-api/typescript-sdk": {
|
||||
"name": "@immich/sdk",
|
||||
"version": "1.135.2",
|
||||
"version": "1.135.3",
|
||||
"license": "GNU Affero General Public License version 3",
|
||||
"dependencies": {
|
||||
"@oazapfts/runtime": "^1.0.2"
|
||||
@@ -1333,9 +1333,9 @@
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@immich/ui": {
|
||||
"version": "0.22.7",
|
||||
"resolved": "https://registry.npmjs.org/@immich/ui/-/ui-0.22.7.tgz",
|
||||
"integrity": "sha512-FdA0RDSOO1IDSTQmCbW9u5yXFl59EHu++tYonDR/FEZUKrMwfmQEanePSW5g5KofdumKEuxBN1fWFym3NbB0jQ==",
|
||||
"version": "0.22.8",
|
||||
"resolved": "https://registry.npmjs.org/@immich/ui/-/ui-0.22.8.tgz",
|
||||
"integrity": "sha512-DVhDgz6drx7vfNhAssX4yYgOC3JpLm8uovLvz3n36skCNU6pm8GoSgH6gMGTM36sx5go3fvhHw5N3KR+A/7bjg==",
|
||||
"license": "GNU Affero General Public License version 3",
|
||||
"dependencies": {
|
||||
"@mdi/js": "^7.4.47",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "immich-web",
|
||||
"version": "1.135.2",
|
||||
"version": "1.135.3",
|
||||
"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.22.7",
|
||||
"@immich/ui": "^0.22.8",
|
||||
"@mapbox/mapbox-gl-rtl-text": "0.2.3",
|
||||
"@mdi/js": "^7.4.47",
|
||||
"@photo-sphere-viewer/core": "^5.11.5",
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import { goto } from '$app/navigation';
|
||||
import AlbumCardGroup from '$lib/components/album-page/album-card-group.svelte';
|
||||
import AlbumsTable from '$lib/components/album-page/albums-table.svelte';
|
||||
import EditAlbumForm from '$lib/components/forms/edit-album-form.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 {
|
||||
@@ -11,6 +10,7 @@
|
||||
} 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';
|
||||
import SharedLinkCreateModal from '$lib/modals/SharedLinkCreateModal.svelte';
|
||||
@@ -258,7 +258,7 @@
|
||||
|
||||
const handleEdit = async (album: AlbumResponseDto) => {
|
||||
closeAlbumContextMenu();
|
||||
const editedAlbum = await modalManager.show(EditAlbumForm, {
|
||||
const editedAlbum = await modalManager.show(AlbumEditModal, {
|
||||
album,
|
||||
});
|
||||
if (editedAlbum) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
|
||||
import Portal from '$lib/components/shared-components/portal/portal.svelte';
|
||||
import ProfileImageCropper from '$lib/components/shared-components/profile-image-cropper.svelte';
|
||||
import { modalManager } from '$lib/managers/modal-manager.svelte';
|
||||
import ProfileImageCropperModal from '$lib/modals/ProfileImageCropperModal.svelte';
|
||||
import type { AssetResponseDto } from '@immich/sdk';
|
||||
import { mdiAccountCircleOutline } from '@mdi/js';
|
||||
import { t } from 'svelte-i18n';
|
||||
@@ -11,18 +11,10 @@
|
||||
}
|
||||
|
||||
let { asset }: Props = $props();
|
||||
|
||||
let showProfileImageCrop = $state(false);
|
||||
</script>
|
||||
|
||||
<MenuOption
|
||||
icon={mdiAccountCircleOutline}
|
||||
onClick={() => (showProfileImageCrop = true)}
|
||||
onClick={() => modalManager.show(ProfileImageCropperModal, { asset })}
|
||||
text={$t('set_as_profile_picture')}
|
||||
/>
|
||||
|
||||
{#if showProfileImageCrop}
|
||||
<Portal target="body">
|
||||
<ProfileImageCropper {asset} onClose={() => (showProfileImageCrop = false)} />
|
||||
</Portal>
|
||||
{/if}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import AlbumCover from '$lib/components/album-page/album-cover.svelte';
|
||||
import { handleError } from '$lib/utils/handle-error';
|
||||
import { updateAlbumInfo, type AlbumResponseDto } from '@immich/sdk';
|
||||
import { Button, Modal, ModalBody, ModalFooter } from '@immich/ui';
|
||||
import { Button, HStack, Modal, ModalBody, ModalFooter } from '@immich/ui';
|
||||
import { mdiRenameOutline } from '@mdi/js';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
@@ -68,9 +68,9 @@
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
<div class="flex gap-2 w-full">
|
||||
<HStack fullWidth>
|
||||
<Button shape="round" color="secondary" fullWidth onclick={() => onClose()}>{$t('cancel')}</Button>
|
||||
<Button shape="round" type="submit" fullWidth disabled={isSubmitting} form="edit-album-form">{$t('save')}</Button>
|
||||
</div>
|
||||
</HStack>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
@@ -5,7 +5,7 @@
|
||||
} from '$lib/components/shared-components/notification/notification';
|
||||
import ApiKeyGrid from '$lib/components/user-settings-page/user-api-key-grid.svelte';
|
||||
import { Permission } from '@immich/sdk';
|
||||
import { Button, Checkbox, Label, Modal, ModalBody, ModalFooter } from '@immich/ui';
|
||||
import { Button, Checkbox, HStack, Label, Modal, ModalBody, ModalFooter } from '@immich/ui';
|
||||
import { mdiKeyVariant } from '@mdi/js';
|
||||
import { onMount } from 'svelte';
|
||||
import { t } from 'svelte-i18n';
|
||||
@@ -219,9 +219,9 @@
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
<div class="flex gap-3 w-full">
|
||||
<HStack fullWidth>
|
||||
<Button shape="round" color="secondary" fullWidth onclick={() => onClose()}>{cancelText}</Button>
|
||||
<Button shape="round" type="submit" fullWidth form="api-key-form">{submitText}</Button>
|
||||
</div>
|
||||
</HStack>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { copyToClipboard } from '$lib/utils';
|
||||
import { Button, Modal, ModalBody, ModalFooter } from '@immich/ui';
|
||||
import { Button, HStack, Modal, ModalBody, ModalFooter } from '@immich/ui';
|
||||
import { mdiKeyVariant } from '@mdi/js';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
@@ -27,9 +27,9 @@
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
<div class="flex gap-3 w-full">
|
||||
<HStack fullWidth>
|
||||
<Button shape="round" onclick={() => copyToClipboard(secret)} fullWidth>{$t('copy_to_clipboard')}</Button>
|
||||
<Button shape="round" onclick={onClose} fullWidth>{$t('done')}</Button>
|
||||
</div>
|
||||
</HStack>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import Icon from '$lib/components/elements/icon.svelte';
|
||||
import { tagAssets } from '$lib/utils/asset-utils';
|
||||
import { getAllTags, upsertTags, type TagResponseDto } from '@immich/sdk';
|
||||
import { Button, Modal, ModalBody, ModalFooter } from '@immich/ui';
|
||||
import { Button, HStack, Modal, ModalBody, ModalFooter } from '@immich/ui';
|
||||
import { mdiClose, mdiTag } from '@mdi/js';
|
||||
import { onMount } from 'svelte';
|
||||
import { t } from 'svelte-i18n';
|
||||
@@ -99,9 +99,9 @@
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
<div class="flex w-full gap-2">
|
||||
<HStack fullWidth>
|
||||
<Button shape="round" fullWidth color="secondary" onclick={() => onClose()}>{$t('cancel')}</Button>
|
||||
<Button type="submit" shape="round" fullWidth form="create-tag-form" {disabled}>{$t('tag_assets')}</Button>
|
||||
</div>
|
||||
</HStack>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import FormatMessage from '$lib/components/i18n/format-message.svelte';
|
||||
import { Button, Modal, ModalBody, ModalFooter } from '@immich/ui';
|
||||
import { Button, HStack, Modal, ModalBody, ModalFooter } from '@immich/ui';
|
||||
import { mdiCancel } from '@mdi/js';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
@@ -32,13 +32,13 @@
|
||||
</div>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<div class="flex gap-3 w-full">
|
||||
<HStack fullWidth>
|
||||
<Button shape="round" color="secondary" fullWidth onclick={() => onClose(false)}>
|
||||
{$t('cancel')}
|
||||
</Button>
|
||||
<Button shape="round" color="danger" fullWidth onclick={() => onClose(true)}>
|
||||
{$t('confirm')}
|
||||
</Button>
|
||||
</div>
|
||||
</HStack>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { Button, Modal, ModalBody, ModalFooter, type Color } from '@immich/ui';
|
||||
import { Button, HStack, Modal, ModalBody, ModalFooter, type Color } from '@immich/ui';
|
||||
import type { Snippet } from 'svelte';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
@@ -38,13 +38,13 @@
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
<div class="flex gap-3 w-full">
|
||||
<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>
|
||||
</div>
|
||||
</HStack>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { Button, Modal, ModalBody, ModalFooter } from '@immich/ui';
|
||||
import { Button, HStack, Modal, ModalBody, ModalFooter } from '@immich/ui';
|
||||
import { mdiFolderRemove } from '@mdi/js';
|
||||
import { onMount } from 'svelte';
|
||||
import { t } from 'svelte-i18n';
|
||||
@@ -63,7 +63,7 @@
|
||||
</form>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<div class="flex gap-2 w-full">
|
||||
<HStack fullWidth>
|
||||
<Button shape="round" color="secondary" fullWidth onclick={() => onClose()}>{$t('cancel')}</Button>
|
||||
{#if isEditing}
|
||||
<Button shape="round" color="danger" fullWidth onclick={() => onClose({ action: 'delete' })}
|
||||
@@ -73,6 +73,6 @@
|
||||
<Button shape="round" type="submit" disabled={!canSubmit} fullWidth form="add-exclusion-pattern-form">
|
||||
{submitText}
|
||||
</Button>
|
||||
</div>
|
||||
</HStack>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { Button, Modal, ModalBody, ModalFooter } from '@immich/ui';
|
||||
import { Button, HStack, Modal, ModalBody, ModalFooter } from '@immich/ui';
|
||||
import { mdiFolderSync } from '@mdi/js';
|
||||
import { onMount } from 'svelte';
|
||||
import { t } from 'svelte-i18n';
|
||||
@@ -60,7 +60,7 @@
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
<div class="flex gap-2 w-full">
|
||||
<HStack fullWidth>
|
||||
<Button shape="round" color="secondary" fullWidth onclick={() => onClose()}>{cancelText}</Button>
|
||||
{#if isEditing}
|
||||
<Button shape="round" color="danger" fullWidth onclick={() => onClose({ action: 'delete' })}>
|
||||
@@ -70,6 +70,6 @@
|
||||
<Button shape="round" type="submit" disabled={!canSubmit} fullWidth form="library-import-path-form">
|
||||
{submitText}
|
||||
</Button>
|
||||
</div>
|
||||
</HStack>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import type { LibraryResponseDto } from '@immich/sdk';
|
||||
import { Button, Field, Input, Modal, ModalBody, ModalFooter } from '@immich/ui';
|
||||
import { Button, Field, HStack, Input, Modal, ModalBody, ModalFooter } from '@immich/ui';
|
||||
import { mdiRenameOutline } from '@mdi/js';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
@@ -29,9 +29,9 @@
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
<div class="flex gap-2 w-full">
|
||||
<HStack fullWidth>
|
||||
<Button shape="round" fullWidth color="secondary" onclick={() => onClose()}>{$t('cancel')}</Button>
|
||||
<Button shape="round" fullWidth type="submit" form="rename-library-form">{$t('save')}</Button>
|
||||
</div>
|
||||
</HStack>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import SettingSelect from '$lib/components/shared-components/settings/setting-select.svelte';
|
||||
import { user } from '$lib/stores/user.store';
|
||||
import { searchUsersAdmin } from '@immich/sdk';
|
||||
import { Button, Modal, ModalBody, ModalFooter } from '@immich/ui';
|
||||
import { Button, HStack, Modal, ModalBody, ModalFooter } from '@immich/ui';
|
||||
import { mdiFolderSync } from '@mdi/js';
|
||||
import { onMount } from 'svelte';
|
||||
import { t } from 'svelte-i18n';
|
||||
@@ -38,9 +38,9 @@
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
<div class="flex gap-2 w-full">
|
||||
<HStack fullWidth>
|
||||
<Button shape="round" color="secondary" fullWidth onclick={() => onClose()}>{$t('cancel')}</Button>
|
||||
<Button shape="round" type="submit" fullWidth form="select-library-owner-form">{$t('create')}</Button>
|
||||
</div>
|
||||
</HStack>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import SettingSelect from '$lib/components/shared-components/settings/setting-select.svelte';
|
||||
import type { MapSettings } from '$lib/stores/preferences.store';
|
||||
import { Button, Field, Modal, ModalBody, ModalFooter, Stack, Switch } from '@immich/ui';
|
||||
import { Button, Field, HStack, Modal, ModalBody, ModalFooter, Stack, Switch } from '@immich/ui';
|
||||
import { Duration } from 'luxon';
|
||||
import { t } from 'svelte-i18n';
|
||||
import { fly } from 'svelte/transition';
|
||||
@@ -127,9 +127,9 @@
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
<div class="flex gap-3 w-full">
|
||||
<HStack fullWidth>
|
||||
<Button color="secondary" shape="round" fullWidth onclick={() => onClose()}>{$t('cancel')}</Button>
|
||||
<Button type="submit" shape="round" fullWidth form="map-settings-form">{$t('save')}</Button>
|
||||
</div>
|
||||
</HStack>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { copyToClipboard } from '$lib/utils';
|
||||
import { Button, Code, IconButton, Modal, ModalBody, ModalFooter, Text } from '@immich/ui';
|
||||
import { Button, Code, HStack, IconButton, Modal, ModalBody, ModalFooter, Text } from '@immich/ui';
|
||||
import { mdiCheck, mdiContentCopy } from '@mdi/js';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
@@ -35,10 +35,10 @@
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
<div class="flex gap-3 w-full">
|
||||
<HStack fullWidth>
|
||||
<Button shape="round" color="primary" fullWidth onclick={() => onClose()}>
|
||||
{$t('done')}
|
||||
</Button>
|
||||
</div>
|
||||
</HStack>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
} from '$lib/components/shared-components/notification/notification';
|
||||
import { handleError } from '$lib/utils/handle-error';
|
||||
import { updatePerson, type PersonResponseDto } from '@immich/sdk';
|
||||
import { Button, Modal, ModalBody, ModalFooter } from '@immich/ui';
|
||||
import { Button, HStack, Modal, ModalBody, ModalFooter } from '@immich/ui';
|
||||
import { mdiCake } from '@mdi/js';
|
||||
import { t } from 'svelte-i18n';
|
||||
import DateInput from '../components/elements/date-input.svelte';
|
||||
@@ -65,13 +65,13 @@
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
<div class="flex gap-3 w-full">
|
||||
<HStack fullWidth>
|
||||
<Button shape="round" color="secondary" fullWidth onclick={() => onClose()}>
|
||||
{$t('cancel')}
|
||||
</Button>
|
||||
<Button type="submit" shape="round" color="primary" fullWidth form="set-birth-date-form">
|
||||
{$t('save')}
|
||||
</Button>
|
||||
</div>
|
||||
</HStack>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
import { getPeopleThumbnailUrl } from '$lib/utils';
|
||||
import { handleError } from '$lib/utils/handle-error';
|
||||
import { mergePerson, type PersonResponseDto } from '@immich/sdk';
|
||||
import { Button, IconButton, Modal, ModalBody, ModalFooter } from '@immich/ui';
|
||||
import { Button, HStack, IconButton, Modal, ModalBody, ModalFooter } from '@immich/ui';
|
||||
import { mdiArrowLeft, mdiMerge } from '@mdi/js';
|
||||
import { onMount, tick } from 'svelte';
|
||||
import { t } from 'svelte-i18n';
|
||||
@@ -139,11 +139,11 @@
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
<div class="flex gap-3 w-full">
|
||||
<HStack fullWidth>
|
||||
<Button fullWidth shape="round" color="secondary" onclick={() => onClose()}>{$t('no')}</Button>
|
||||
<Button id="merge-confirm-button" fullWidth shape="round" onclick={handleMergePerson}>
|
||||
{$t('yes')}
|
||||
</Button>
|
||||
</div>
|
||||
</HStack>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
import domtoimage from 'dom-to-image';
|
||||
import { onMount } from 'svelte';
|
||||
import { t } from 'svelte-i18n';
|
||||
import PhotoViewer from '../asset-viewer/photo-viewer.svelte';
|
||||
import { NotificationType, notificationController } from './notification/notification';
|
||||
import PhotoViewer from '../components/asset-viewer/photo-viewer.svelte';
|
||||
import { NotificationType, notificationController } from '../components/shared-components/notification/notification';
|
||||
|
||||
interface Props {
|
||||
asset: AssetResponseDto;
|
||||
@@ -34,7 +34,7 @@
|
||||
import { parseUtcDate } from '$lib/utils/date-time';
|
||||
import { generateId } from '$lib/utils/generate-id';
|
||||
import { AssetTypeEnum, AssetVisibility, type MetadataSearchDto, type SmartSearchDto } from '@immich/sdk';
|
||||
import { Button, Modal, ModalBody, ModalFooter } from '@immich/ui';
|
||||
import { Button, HStack, Modal, ModalBody, ModalFooter } from '@immich/ui';
|
||||
import { mdiTune } from '@mdi/js';
|
||||
import { t } from 'svelte-i18n';
|
||||
import { SvelteSet } from 'svelte/reactivity';
|
||||
@@ -204,11 +204,11 @@
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
<div class="flex gap-3 w-full">
|
||||
<HStack fullWidth>
|
||||
<Button shape="round" size="large" type="reset" color="secondary" fullWidth form={formId}
|
||||
>{$t('clear_all')}</Button
|
||||
>
|
||||
<Button shape="round" size="large" type="submit" fullWidth form={formId}>{$t('search')}</Button>
|
||||
</div>
|
||||
</HStack>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import SettingInputField from '$lib/components/shared-components/settings/setting-input-field.svelte';
|
||||
import SettingSwitch from '$lib/components/shared-components/settings/setting-switch.svelte';
|
||||
import { SettingInputFieldType } from '$lib/constants';
|
||||
import { Button, Modal, ModalBody, ModalFooter } from '@immich/ui';
|
||||
import { Button, HStack, Modal, ModalBody, ModalFooter } from '@immich/ui';
|
||||
import {
|
||||
mdiArrowDownThin,
|
||||
mdiArrowUpThin,
|
||||
@@ -105,9 +105,9 @@
|
||||
</div>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<div class="flex gap-2 w-full">
|
||||
<HStack fullWidth>
|
||||
<Button color="secondary" shape="round" fullWidth onclick={() => onClose()}>{$t('cancel')}</Button>
|
||||
<Button fullWidth color="primary" shape="round" onclick={applyChanges}>{$t('confirm')}</Button>
|
||||
</div>
|
||||
</HStack>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
Button,
|
||||
Field,
|
||||
HelperText,
|
||||
HStack,
|
||||
Input,
|
||||
Modal,
|
||||
ModalBody,
|
||||
@@ -131,10 +132,11 @@
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
<div class="flex gap-3 w-full">
|
||||
<HStack fullWidth>
|
||||
<Button color="secondary" fullWidth onclick={() => onClose()} shape="round">{$t('cancel')}</Button>
|
||||
<Button type="submit" disabled={!valid} fullWidth shape="round" form="create-new-user-form">{$t('create')}</Button
|
||||
>
|
||||
</div>
|
||||
<Button type="submit" disabled={!valid} fullWidth shape="round" form="create-new-user-form"
|
||||
>{$t('create')}
|
||||
</Button>
|
||||
</HStack>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
import { ByteUnit, convertFromBytes, convertToBytes } from '$lib/utils/byte-units';
|
||||
import { handleError } from '$lib/utils/handle-error';
|
||||
import { updateUserAdmin, type UserAdminResponseDto } from '@immich/sdk';
|
||||
import { Button, Field, Modal, ModalBody, ModalFooter, Switch } from '@immich/ui';
|
||||
import { Button, Field, HStack, Modal, ModalBody, ModalFooter, Switch } from '@immich/ui';
|
||||
import { mdiAccountEditOutline } from '@mdi/js';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
@@ -115,11 +115,11 @@
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
<div class="flex gap-3 w-full">
|
||||
<HStack fullWidth>
|
||||
<Button shape="round" color="secondary" fullWidth form="edit-user-form" onclick={() => onClose()}
|
||||
>{$t('cancel')}</Button
|
||||
>
|
||||
<Button type="submit" shape="round" fullWidth form="edit-user-form">{$t('confirm')}</Button>
|
||||
</div>
|
||||
</HStack>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import FormatMessage from '$lib/components/i18n/format-message.svelte';
|
||||
import { handleError } from '$lib/utils/handle-error';
|
||||
import { restoreUserAdmin, type UserAdminResponseDto, type UserResponseDto } from '@immich/sdk';
|
||||
import { Button, Modal, ModalBody, ModalFooter } from '@immich/ui';
|
||||
import { Button, HStack, Modal, ModalBody, ModalFooter } from '@immich/ui';
|
||||
import { mdiDeleteRestore } from '@mdi/js';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
@@ -35,13 +35,13 @@
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
<div class="flex gap-3 w-full">
|
||||
<HStack fullWidth>
|
||||
<Button shape="round" color="secondary" fullWidth onclick={() => onClose()}>
|
||||
{$t('cancel')}
|
||||
</Button>
|
||||
<Button shape="round" color="primary" fullWidth onclick={() => handleRestoreUser()}>
|
||||
{$t('restore')}
|
||||
</Button>
|
||||
</div>
|
||||
</HStack>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
|
||||
@@ -191,10 +191,10 @@
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
<div class="flex w-full gap-2">
|
||||
<HStack fullWidth>
|
||||
<Button color="secondary" fullWidth shape="round" onclick={() => handleCancel()}>{$t('cancel')}</Button>
|
||||
<Button type="submit" fullWidth shape="round" form="create-tag-form">{$t('create')}</Button>
|
||||
</div>
|
||||
</HStack>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
{/if}
|
||||
@@ -214,10 +214,10 @@
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
<div class="flex w-full gap-2">
|
||||
<HStack fullWidth>
|
||||
<Button color="secondary" fullWidth shape="round" onclick={() => handleCancel()}>{$t('cancel')}</Button>
|
||||
<Button type="submit" fullWidth shape="round" form="edit-tag-form">{$t('save')}</Button>
|
||||
</div>
|
||||
</HStack>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
{/if}
|
||||
|
||||
Reference in New Issue
Block a user