mirror of
https://github.com/immich-app/immich.git
synced 2025-12-16 01:10:57 +03:00
Compare commits
2 Commits
fix/drift-
...
renovate/c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b4b51df0a3 | ||
|
|
6bb1a9e083 |
@@ -20,7 +20,6 @@ targets:
|
|||||||
- lib/infrastructure/entities/*.drift
|
- lib/infrastructure/entities/*.drift
|
||||||
- lib/infrastructure/repositories/db.repository.dart
|
- lib/infrastructure/repositories/db.repository.dart
|
||||||
- lib/infrastructure/repositories/logger_db.repository.dart
|
- lib/infrastructure/repositories/logger_db.repository.dart
|
||||||
- lib/infrastructure/repositories/background_downloader.repository.dart
|
|
||||||
drift_dev:modular:
|
drift_dev:modular:
|
||||||
enabled: true
|
enabled: true
|
||||||
options: *drift_options
|
options: *drift_options
|
||||||
|
|||||||
@@ -1,66 +0,0 @@
|
|||||||
import 'package:drift/drift.dart';
|
|
||||||
import 'package:immich_mobile/infrastructure/utils/drift_default.mixin.dart';
|
|
||||||
|
|
||||||
class TaskRecordEntity extends Table with DriftDefaultsMixin {
|
|
||||||
const TaskRecordEntity();
|
|
||||||
|
|
||||||
TextColumn get taskId => text()();
|
|
||||||
|
|
||||||
TextColumn get url => text()();
|
|
||||||
|
|
||||||
TextColumn get filename => text()();
|
|
||||||
|
|
||||||
TextColumn get group => text()();
|
|
||||||
|
|
||||||
TextColumn get metaData => text()();
|
|
||||||
|
|
||||||
IntColumn get creationTime => integer()();
|
|
||||||
|
|
||||||
IntColumn get status => integer()();
|
|
||||||
|
|
||||||
RealColumn get progress => real()();
|
|
||||||
|
|
||||||
TextColumn get objectJsonMap => text()();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Set<Column> get primaryKey => {taskId};
|
|
||||||
}
|
|
||||||
|
|
||||||
class PausedTasksEntity extends Table with DriftDefaultsMixin {
|
|
||||||
const PausedTasksEntity();
|
|
||||||
|
|
||||||
TextColumn get taskId => text()();
|
|
||||||
|
|
||||||
IntColumn get modified => integer()();
|
|
||||||
|
|
||||||
TextColumn get objectJsonMap => text()();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Set<Column> get primaryKey => {taskId};
|
|
||||||
}
|
|
||||||
|
|
||||||
class ModifiedTasksEntity extends Table with DriftDefaultsMixin {
|
|
||||||
const ModifiedTasksEntity();
|
|
||||||
|
|
||||||
TextColumn get taskId => text()();
|
|
||||||
|
|
||||||
IntColumn get modified => integer()();
|
|
||||||
|
|
||||||
TextColumn get objectJsonMap => text()();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Set<Column> get primaryKey => {taskId};
|
|
||||||
}
|
|
||||||
|
|
||||||
class ResumeTasksEntity extends Table with DriftDefaultsMixin {
|
|
||||||
const ResumeTasksEntity();
|
|
||||||
|
|
||||||
TextColumn get taskId => text()();
|
|
||||||
|
|
||||||
IntColumn get modified => integer()();
|
|
||||||
|
|
||||||
TextColumn get objectJsonMap => text()();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Set<Column> get primaryKey => {taskId};
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,195 +0,0 @@
|
|||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
import 'package:background_downloader/background_downloader.dart';
|
|
||||||
import 'package:drift/drift.dart';
|
|
||||||
import 'package:drift_flutter/drift_flutter.dart';
|
|
||||||
import 'package:immich_mobile/domain/interfaces/db.interface.dart';
|
|
||||||
import 'package:immich_mobile/infrastructure/entities/background_downloader.entity.dart';
|
|
||||||
import 'package:logging/logging.dart';
|
|
||||||
|
|
||||||
import 'background_downloader.repository.drift.dart';
|
|
||||||
|
|
||||||
final driftPersistentStorage = DriftPersistentStorage(DriftBackgroundDownloader());
|
|
||||||
|
|
||||||
class _SqlPersistentStorageMigrator extends BasePersistentStorageMigrator {
|
|
||||||
_SqlPersistentStorageMigrator();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<bool> migrateFrom(String persistentStorageName, PersistentStorage toStorage) =>
|
|
||||||
switch (persistentStorageName.toLowerCase().replaceAll('_', '')) {
|
|
||||||
'localstore' => migrateFromLocalStore(toStorage),
|
|
||||||
_ => Future.value(false),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@DriftDatabase(tables: [TaskRecordEntity, PausedTasksEntity, ModifiedTasksEntity, ResumeTasksEntity])
|
|
||||||
class DriftBackgroundDownloader extends $DriftBackgroundDownloader implements IDatabaseRepository {
|
|
||||||
DriftBackgroundDownloader([QueryExecutor? executor])
|
|
||||||
: super(
|
|
||||||
executor ??
|
|
||||||
driftDatabase(
|
|
||||||
name: 'immich_background_downloader',
|
|
||||||
native: const DriftNativeOptions(shareAcrossIsolates: true),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get schemaVersion => 1;
|
|
||||||
|
|
||||||
@override
|
|
||||||
MigrationStrategy get migration => MigrationStrategy(
|
|
||||||
onCreate: (migrator) async {
|
|
||||||
await migrator.createAll();
|
|
||||||
await _SqlPersistentStorageMigrator().migrateFromLocalStore(driftPersistentStorage);
|
|
||||||
},
|
|
||||||
beforeOpen: (details) async {
|
|
||||||
await customStatement('PRAGMA synchronous = NORMAL');
|
|
||||||
await customStatement('PRAGMA journal_mode = WAL');
|
|
||||||
await customStatement('PRAGMA busy_timeout = 500');
|
|
||||||
await driftPersistentStorage.purgeOldRecords();
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
class DriftPersistentStorage implements PersistentStorage {
|
|
||||||
final log = Logger('DriftPersistentStorage');
|
|
||||||
|
|
||||||
late final DriftBackgroundDownloader db;
|
|
||||||
|
|
||||||
DriftPersistentStorage(this.db);
|
|
||||||
|
|
||||||
@override
|
|
||||||
(String, int) get currentDatabaseVersion => ('Sqlite', 1);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> initialize() async {}
|
|
||||||
|
|
||||||
Future<void> purgeOldRecords({Duration age = const Duration(days: 30)}) async {
|
|
||||||
final cutOff = (DateTime.now().subtract(age).millisecondsSinceEpoch / 1000).floor();
|
|
||||||
for (final TableInfo table in [db.modifiedTasksEntity, db.pausedTasksEntity, db.resumeTasksEntity]) {
|
|
||||||
await db.customStatement('DELETE FROM ${table.actualTableName} WHERE modified < ?', [cutOff]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _remove(TableInfo table, String? taskId) async {
|
|
||||||
if (taskId == null) {
|
|
||||||
await db.delete(table).go();
|
|
||||||
} else {
|
|
||||||
await db.customStatement('DELETE FROM ${table.actualTableName} WHERE task_id = ?', [taskId]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> removePausedTask(String? taskId) => _remove(db.pausedTasksEntity, taskId);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> removeResumeData(String? taskId) => _remove(db.resumeTasksEntity, taskId);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> removeTaskRecord(String? taskId) => _remove(db.taskRecordEntity, taskId);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<List<Task>> retrieveAllPausedTasks() async {
|
|
||||||
final query = db.selectOnly(db.pausedTasksEntity)..addColumns([db.pausedTasksEntity.objectJsonMap]);
|
|
||||||
final result = await query.map((row) => row.read(db.pausedTasksEntity.objectJsonMap)).get();
|
|
||||||
return result.nonNulls.map((e) => Task.createFromJson(jsonDecode(e))).toList(growable: false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<List<ResumeData>> retrieveAllResumeData() async {
|
|
||||||
final query = db.selectOnly(db.resumeTasksEntity)..addColumns([db.resumeTasksEntity.objectJsonMap]);
|
|
||||||
final result = await query.map((row) => row.read(db.resumeTasksEntity.objectJsonMap)).get();
|
|
||||||
return result.nonNulls.map((e) => ResumeData.fromJson(jsonDecode(e))).toList(growable: false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<List<TaskRecord>> retrieveAllTaskRecords() async {
|
|
||||||
final query = db.selectOnly(db.taskRecordEntity)..addColumns([db.taskRecordEntity.objectJsonMap]);
|
|
||||||
final result = await query.map((row) => row.read(db.taskRecordEntity.objectJsonMap)).get();
|
|
||||||
return result.nonNulls.map((e) => TaskRecord.fromJson(jsonDecode(e))).toList(growable: false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<Task?> retrievePausedTask(String taskId) async {
|
|
||||||
final query = db.selectOnly(db.pausedTasksEntity)
|
|
||||||
..addColumns([db.pausedTasksEntity.objectJsonMap])
|
|
||||||
..where(db.pausedTasksEntity.taskId.equals(taskId));
|
|
||||||
final result = await query.map((row) => row.read(db.pausedTasksEntity.objectJsonMap)).getSingleOrNull();
|
|
||||||
if (result == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return Task.createFromJson(jsonDecode(result));
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<ResumeData?> retrieveResumeData(String taskId) async {
|
|
||||||
final query = db.selectOnly(db.resumeTasksEntity)
|
|
||||||
..addColumns([db.resumeTasksEntity.objectJsonMap])
|
|
||||||
..where(db.resumeTasksEntity.taskId.equals(taskId));
|
|
||||||
final result = await query.map((row) => row.read(db.resumeTasksEntity.objectJsonMap)).getSingleOrNull();
|
|
||||||
if (result == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return ResumeData.fromJson(jsonDecode(result));
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<TaskRecord?> retrieveTaskRecord(String taskId) async {
|
|
||||||
final query = db.selectOnly(db.taskRecordEntity)
|
|
||||||
..addColumns([db.taskRecordEntity.objectJsonMap])
|
|
||||||
..where(db.taskRecordEntity.taskId.equals(taskId));
|
|
||||||
final result = await query.map((row) => row.read(db.taskRecordEntity.objectJsonMap)).getSingleOrNull();
|
|
||||||
if (result == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return TaskRecord.fromJson(jsonDecode(result));
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> storePausedTask(Task task) async {
|
|
||||||
await db.managers.pausedTasksEntity.create(
|
|
||||||
(o) => o(
|
|
||||||
modified: (DateTime.now().millisecondsSinceEpoch / 1000).floor(),
|
|
||||||
objectJsonMap: jsonEncode(task.toJson()),
|
|
||||||
taskId: task.taskId,
|
|
||||||
),
|
|
||||||
mode: InsertMode.insertOrReplace,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> storeResumeData(ResumeData resumeData) async {
|
|
||||||
await db.managers.resumeTasksEntity.create(
|
|
||||||
(o) => o(
|
|
||||||
modified: (DateTime.now().millisecondsSinceEpoch / 1000).floor(),
|
|
||||||
objectJsonMap: jsonEncode(resumeData.toJson()),
|
|
||||||
taskId: resumeData.taskId,
|
|
||||||
),
|
|
||||||
mode: InsertMode.insertOrReplace,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> storeTaskRecord(TaskRecord record) async {
|
|
||||||
final task = record.task;
|
|
||||||
await db.managers.taskRecordEntity.create(
|
|
||||||
(o) => o(
|
|
||||||
taskId: task.taskId,
|
|
||||||
url: task.url,
|
|
||||||
filename: task.filename,
|
|
||||||
group: task.group,
|
|
||||||
metaData: task.metaData,
|
|
||||||
creationTime: (task.creationTime.millisecondsSinceEpoch / 1000).floor(),
|
|
||||||
status: record.status.index,
|
|
||||||
progress: record.progress,
|
|
||||||
objectJsonMap: jsonEncode(record.toJson()),
|
|
||||||
),
|
|
||||||
mode: InsertMode.insertOrReplace,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<(String, int)> get storedDatabaseVersion async {
|
|
||||||
return ('Sqlite', db.schemaVersion);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
// dart format width=80
|
|
||||||
// ignore_for_file: type=lint
|
|
||||||
import 'package:drift/drift.dart' as i0;
|
|
||||||
import 'package:immich_mobile/infrastructure/entities/background_downloader.entity.drift.dart'
|
|
||||||
as i1;
|
|
||||||
|
|
||||||
abstract class $DriftBackgroundDownloader extends i0.GeneratedDatabase {
|
|
||||||
$DriftBackgroundDownloader(i0.QueryExecutor e) : super(e);
|
|
||||||
$DriftBackgroundDownloaderManager get managers =>
|
|
||||||
$DriftBackgroundDownloaderManager(this);
|
|
||||||
late final i1.$TaskRecordEntityTable taskRecordEntity = i1
|
|
||||||
.$TaskRecordEntityTable(this);
|
|
||||||
late final i1.$PausedTasksEntityTable pausedTasksEntity = i1
|
|
||||||
.$PausedTasksEntityTable(this);
|
|
||||||
late final i1.$ModifiedTasksEntityTable modifiedTasksEntity = i1
|
|
||||||
.$ModifiedTasksEntityTable(this);
|
|
||||||
late final i1.$ResumeTasksEntityTable resumeTasksEntity = i1
|
|
||||||
.$ResumeTasksEntityTable(this);
|
|
||||||
@override
|
|
||||||
Iterable<i0.TableInfo<i0.Table, Object?>> get allTables =>
|
|
||||||
allSchemaEntities.whereType<i0.TableInfo<i0.Table, Object?>>();
|
|
||||||
@override
|
|
||||||
List<i0.DatabaseSchemaEntity> get allSchemaEntities => [
|
|
||||||
taskRecordEntity,
|
|
||||||
pausedTasksEntity,
|
|
||||||
modifiedTasksEntity,
|
|
||||||
resumeTasksEntity,
|
|
||||||
];
|
|
||||||
@override
|
|
||||||
i0.DriftDatabaseOptions get options =>
|
|
||||||
const i0.DriftDatabaseOptions(storeDateTimeAsText: true);
|
|
||||||
}
|
|
||||||
|
|
||||||
class $DriftBackgroundDownloaderManager {
|
|
||||||
final $DriftBackgroundDownloader _db;
|
|
||||||
$DriftBackgroundDownloaderManager(this._db);
|
|
||||||
i1.$$TaskRecordEntityTableTableManager get taskRecordEntity =>
|
|
||||||
i1.$$TaskRecordEntityTableTableManager(_db, _db.taskRecordEntity);
|
|
||||||
i1.$$PausedTasksEntityTableTableManager get pausedTasksEntity =>
|
|
||||||
i1.$$PausedTasksEntityTableTableManager(_db, _db.pausedTasksEntity);
|
|
||||||
i1.$$ModifiedTasksEntityTableTableManager get modifiedTasksEntity =>
|
|
||||||
i1.$$ModifiedTasksEntityTableTableManager(_db, _db.modifiedTasksEntity);
|
|
||||||
i1.$$ResumeTasksEntityTableTableManager get resumeTasksEntity =>
|
|
||||||
i1.$$ResumeTasksEntityTableTableManager(_db, _db.resumeTasksEntity);
|
|
||||||
}
|
|
||||||
@@ -19,7 +19,6 @@ import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
|||||||
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||||
import 'package:immich_mobile/generated/codegen_loader.g.dart';
|
import 'package:immich_mobile/generated/codegen_loader.g.dart';
|
||||||
import 'package:immich_mobile/generated/intl_keys.g.dart';
|
import 'package:immich_mobile/generated/intl_keys.g.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/background_downloader.repository.dart';
|
|
||||||
import 'package:immich_mobile/platform/background_worker_lock_api.g.dart';
|
import 'package:immich_mobile/platform/background_worker_lock_api.g.dart';
|
||||||
import 'package:immich_mobile/providers/app_life_cycle.provider.dart';
|
import 'package:immich_mobile/providers/app_life_cycle.provider.dart';
|
||||||
import 'package:immich_mobile/providers/asset_viewer/share_intent_upload.provider.dart';
|
import 'package:immich_mobile/providers/asset_viewer/share_intent_upload.provider.dart';
|
||||||
@@ -104,7 +103,7 @@ Future<void> initApp() async {
|
|||||||
initializeTimeZones();
|
initializeTimeZones();
|
||||||
|
|
||||||
// Initialize the file downloader
|
// Initialize the file downloader
|
||||||
await FileDownloader(persistentStorage: driftPersistentStorage).configure(
|
await FileDownloader().configure(
|
||||||
// maxConcurrent: 6, maxConcurrentByHost(server):6, maxConcurrentByGroup: 3
|
// maxConcurrent: 6, maxConcurrentByHost(server):6, maxConcurrentByGroup: 3
|
||||||
|
|
||||||
// On Android, if files are larger than 256MB, run in foreground service
|
// On Android, if files are larger than 256MB, run in foreground service
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|||||||
import 'package:immich_mobile/constants/constants.dart';
|
import 'package:immich_mobile/constants/constants.dart';
|
||||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||||
import 'package:immich_mobile/entities/store.entity.dart';
|
import 'package:immich_mobile/entities/store.entity.dart';
|
||||||
import 'package:immich_mobile/utils/debug_print.dart';
|
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
|
import 'package:immich_mobile/utils/debug_print.dart';
|
||||||
|
|
||||||
class UploadTaskWithFile {
|
class UploadTaskWithFile {
|
||||||
final File file;
|
final File file;
|
||||||
|
|||||||
@@ -317,10 +317,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: connectivity_plus
|
name: connectivity_plus
|
||||||
sha256: b5e72753cf63becce2c61fd04dfe0f1c430cc5278b53a1342dc5ad839eab29ec
|
sha256: "33bae12a398f841c6cda09d1064212957265869104c478e5ad51e2fb26c3973c"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.1.5"
|
version: "7.0.0"
|
||||||
connectivity_plus_platform_interface:
|
connectivity_plus_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ dependencies:
|
|||||||
cancellation_token_http: ^2.1.0
|
cancellation_token_http: ^2.1.0
|
||||||
cast: ^2.1.0
|
cast: ^2.1.0
|
||||||
collection: ^1.19.1
|
collection: ^1.19.1
|
||||||
connectivity_plus: ^6.1.3
|
connectivity_plus: 7.0.0
|
||||||
crop_image: ^1.0.16
|
crop_image: ^1.0.16
|
||||||
crypto: ^3.0.6
|
crypto: ^3.0.6
|
||||||
device_info_plus: ^12.2.0
|
device_info_plus: ^12.2.0
|
||||||
|
|||||||
@@ -440,15 +440,15 @@
|
|||||||
|
|
||||||
{#if latlng && $featureFlags.loaded && $featureFlags.map}
|
{#if latlng && $featureFlags.loaded && $featureFlags.map}
|
||||||
<div class="h-[360px]">
|
<div class="h-[360px]">
|
||||||
{#await import('../shared-components/map/map.svelte')}
|
{#await import('$lib/components/shared-components/map/map.svelte')}
|
||||||
{#await delay(timeToLoadTheMap) then}
|
{#await delay(timeToLoadTheMap) then}
|
||||||
<!-- show the loading spinner only if loading the map takes too much time -->
|
<!-- show the loading spinner only if loading the map takes too much time -->
|
||||||
<div class="flex items-center justify-center h-full w-full">
|
<div class="flex items-center justify-center h-full w-full">
|
||||||
<LoadingSpinner />
|
<LoadingSpinner />
|
||||||
</div>
|
</div>
|
||||||
{/await}
|
{/await}
|
||||||
{:then component}
|
{:then { default: Map }}
|
||||||
<component.default
|
<Map
|
||||||
mapMarkers={[
|
mapMarkers={[
|
||||||
{
|
{
|
||||||
lat: latlng.lat,
|
lat: latlng.lat,
|
||||||
@@ -480,7 +480,7 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
</component.default>
|
</Map>
|
||||||
{/await}
|
{/await}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -19,7 +19,6 @@
|
|||||||
import { IconButton } from '@immich/ui';
|
import { IconButton } from '@immich/ui';
|
||||||
import { mdiArrowLeft, mdiDownload, mdiFileImagePlusOutline, mdiSelectAll } from '@mdi/js';
|
import { mdiArrowLeft, mdiDownload, mdiFileImagePlusOutline, mdiSelectAll } from '@mdi/js';
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
import AssetViewer from '../asset-viewer/asset-viewer.svelte';
|
|
||||||
import ControlAppBar from '../shared-components/control-app-bar.svelte';
|
import ControlAppBar from '../shared-components/control-app-bar.svelte';
|
||||||
import GalleryViewer from '../shared-components/gallery-viewer/gallery-viewer.svelte';
|
import GalleryViewer from '../shared-components/gallery-viewer/gallery-viewer.svelte';
|
||||||
import { NotificationType, notificationController } from '../shared-components/notification/notification';
|
import { NotificationType, notificationController } from '../shared-components/notification/notification';
|
||||||
@@ -146,15 +145,17 @@
|
|||||||
</section>
|
</section>
|
||||||
{:else if assets.length === 1}
|
{:else if assets.length === 1}
|
||||||
{#await getAssetInfo({ ...authManager.params, id: assets[0].id }) then asset}
|
{#await getAssetInfo({ ...authManager.params, id: assets[0].id }) then asset}
|
||||||
<AssetViewer
|
{#await import('$lib/components/asset-viewer/asset-viewer.svelte') then { default: AssetViewer }}
|
||||||
{asset}
|
<AssetViewer
|
||||||
showCloseButton={false}
|
{asset}
|
||||||
onAction={handleAction}
|
showCloseButton={false}
|
||||||
onPrevious={() => Promise.resolve(false)}
|
onAction={handleAction}
|
||||||
onNext={() => Promise.resolve(false)}
|
onPrevious={() => Promise.resolve(false)}
|
||||||
onRandom={() => Promise.resolve(undefined)}
|
onNext={() => Promise.resolve(false)}
|
||||||
onClose={() => {}}
|
onRandom={() => Promise.resolve(undefined)}
|
||||||
/>
|
onClose={() => {}}
|
||||||
|
/>
|
||||||
|
{/await}
|
||||||
{/await}
|
{/await}
|
||||||
{/if}
|
{/if}
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -173,7 +173,7 @@
|
|||||||
|
|
||||||
<span>{$t('pick_a_location')}</span>
|
<span>{$t('pick_a_location')}</span>
|
||||||
<div class="h-[500px] min-h-[300px] w-full z-0">
|
<div class="h-[500px] min-h-[300px] w-full z-0">
|
||||||
{#await import('../shared-components/map/map.svelte')}
|
{#await import('$lib/components/shared-components/map/map.svelte')}
|
||||||
{#await delay(timeToLoadTheMap) then}
|
{#await delay(timeToLoadTheMap) then}
|
||||||
<!-- show the loading spinner only if loading the map takes too much time -->
|
<!-- show the loading spinner only if loading the map takes too much time -->
|
||||||
<div class="flex items-center justify-center h-full w-full">
|
<div class="flex items-center justify-center h-full w-full">
|
||||||
|
|||||||
@@ -23,7 +23,6 @@
|
|||||||
import { modalManager } from '@immich/ui';
|
import { modalManager } from '@immich/ui';
|
||||||
import { debounce } from 'lodash-es';
|
import { debounce } from 'lodash-es';
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
import AssetViewer from '../../asset-viewer/asset-viewer.svelte';
|
|
||||||
import DeleteAssetDialog from '../../photos-page/delete-asset-dialog.svelte';
|
import DeleteAssetDialog from '../../photos-page/delete-asset-dialog.svelte';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -487,16 +486,18 @@
|
|||||||
<!-- Overlay Asset Viewer -->
|
<!-- Overlay Asset Viewer -->
|
||||||
{#if $isViewerOpen}
|
{#if $isViewerOpen}
|
||||||
<Portal target="body">
|
<Portal target="body">
|
||||||
<AssetViewer
|
{#await import('$lib/components/asset-viewer/asset-viewer.svelte') then { default: AssetViewer }}
|
||||||
asset={$viewingAsset}
|
<AssetViewer
|
||||||
onAction={handleAction}
|
asset={$viewingAsset}
|
||||||
onPrevious={handlePrevious}
|
onAction={handleAction}
|
||||||
onNext={handleNext}
|
onPrevious={handlePrevious}
|
||||||
onRandom={handleRandom}
|
onNext={handleNext}
|
||||||
onClose={() => {
|
onRandom={handleRandom}
|
||||||
assetViewingStore.showAssetViewer(false);
|
onClose={() => {
|
||||||
handlePromiseError(navigate({ targetRoute: 'current', assetId: null }));
|
assetViewingStore.showAssetViewer(false);
|
||||||
}}
|
handlePromiseError(navigate({ targetRoute: 'current', assetId: null }));
|
||||||
/>
|
}}
|
||||||
|
/>
|
||||||
|
{/await}
|
||||||
</Portal>
|
</Portal>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
<script lang="ts" module>
|
<script lang="ts" module>
|
||||||
|
import mapboxRtlUrl from '@mapbox/mapbox-gl-rtl-text/mapbox-gl-rtl-text.min.js?url';
|
||||||
|
import { addProtocol, setRTLTextPlugin } from 'maplibre-gl';
|
||||||
import { Protocol } from 'pmtiles';
|
import { Protocol } from 'pmtiles';
|
||||||
|
|
||||||
let protocol = new Protocol();
|
let protocol = new Protocol();
|
||||||
void maplibregl.addProtocol('pmtiles', protocol.tile);
|
void addProtocol('pmtiles', protocol.tile);
|
||||||
void maplibregl.setRTLTextPlugin(mapboxRtlUrl, true);
|
void setRTLTextPlugin(mapboxRtlUrl, true);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
@@ -16,12 +18,20 @@
|
|||||||
import { getAssetThumbnailUrl, handlePromiseError } from '$lib/utils';
|
import { getAssetThumbnailUrl, handlePromiseError } from '$lib/utils';
|
||||||
import { getMapMarkers, type MapMarkerResponseDto } from '@immich/sdk';
|
import { getMapMarkers, type MapMarkerResponseDto } from '@immich/sdk';
|
||||||
import { Icon, modalManager } from '@immich/ui';
|
import { Icon, 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 { mdiCog, mdiMap, mdiMapMarker } from '@mdi/js';
|
||||||
import type { Feature, GeoJsonProperties, Geometry, Point } from 'geojson';
|
import type { Feature, GeoJsonProperties, Geometry, Point } from 'geojson';
|
||||||
import { isEqual, omit } from 'lodash-es';
|
import { isEqual, omit } from 'lodash-es';
|
||||||
import { DateTime, Duration } from 'luxon';
|
import { DateTime, Duration } from 'luxon';
|
||||||
import maplibregl, { GlobeControl, type GeoJSONSource, type LngLatLike } from 'maplibre-gl';
|
import {
|
||||||
|
GlobeControl,
|
||||||
|
LngLat,
|
||||||
|
LngLatBounds,
|
||||||
|
Marker,
|
||||||
|
type GeoJSONSource,
|
||||||
|
type LngLatLike,
|
||||||
|
type Map,
|
||||||
|
type MapMouseEvent,
|
||||||
|
} from 'maplibre-gl';
|
||||||
import { onDestroy, onMount, untrack } from 'svelte';
|
import { onDestroy, onMount, untrack } from 'svelte';
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
import {
|
import {
|
||||||
@@ -37,7 +47,6 @@
|
|||||||
NavigationControl,
|
NavigationControl,
|
||||||
Popup,
|
Popup,
|
||||||
ScaleControl,
|
ScaleControl,
|
||||||
type Map,
|
|
||||||
} from 'svelte-maplibre';
|
} from 'svelte-maplibre';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -82,15 +91,15 @@
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const bounds = new maplibregl.LngLatBounds();
|
const bounds = new LngLatBounds();
|
||||||
for (const marker of mapMarkers) {
|
for (const marker of mapMarkers) {
|
||||||
bounds.extend([marker.lon, marker.lat]);
|
bounds.extend([marker.lon, marker.lat]);
|
||||||
}
|
}
|
||||||
return bounds;
|
return bounds;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
let map: maplibregl.Map | undefined = $state();
|
let map: Map | undefined = $state();
|
||||||
let marker: maplibregl.Marker | null = null;
|
let marker: Marker | null = null;
|
||||||
let abortController: AbortController;
|
let abortController: AbortController;
|
||||||
|
|
||||||
const theme = $derived($mapSettings.allowDarkMode ? themeManager.value : Theme.LIGHT);
|
const theme = $derived($mapSettings.allowDarkMode ? themeManager.value : Theme.LIGHT);
|
||||||
@@ -103,7 +112,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
center = { lng, lat };
|
center = { lng, lat };
|
||||||
marker = new maplibregl.Marker().setLngLat([lng, lat]).addTo(map);
|
marker = new Marker().setLngLat([lng, lat]).addTo(map);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,7 +134,7 @@
|
|||||||
onSelect(ids);
|
onSelect(ids);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleMapClick(event: maplibregl.MapMouseEvent) {
|
function handleMapClick(event: MapMouseEvent) {
|
||||||
if (clickable) {
|
if (clickable) {
|
||||||
const { lng, lat } = event.lngLat;
|
const { lng, lat } = event.lngLat;
|
||||||
onClickPoint({ lng, lat });
|
onClickPoint({ lng, lat });
|
||||||
@@ -135,7 +144,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (map) {
|
if (map) {
|
||||||
marker = new maplibregl.Marker().setLngLat([lng, lat]).addTo(map);
|
marker = new Marker().setLngLat([lng, lat]).addTo(map);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -157,7 +166,7 @@
|
|||||||
|
|
||||||
const asMarker = (feature: Feature<Geometry, GeoJsonProperties>): MapMarkerResponseDto => {
|
const asMarker = (feature: Feature<Geometry, GeoJsonProperties>): MapMarkerResponseDto => {
|
||||||
const featurePoint = feature as FeaturePoint;
|
const featurePoint = feature as FeaturePoint;
|
||||||
const coords = maplibregl.LngLat.convert(featurePoint.geometry.coordinates as [number, number]);
|
const coords = LngLat.convert(featurePoint.geometry.coordinates as [number, number]);
|
||||||
return {
|
return {
|
||||||
lat: coords.lat,
|
lat: coords.lat,
|
||||||
lon: coords.lng,
|
lon: coords.lng,
|
||||||
@@ -294,7 +303,7 @@
|
|||||||
fitBoundsOptions={{ padding: 50, maxZoom: 15 }}
|
fitBoundsOptions={{ padding: 50, maxZoom: 15 }}
|
||||||
attributionControl={false}
|
attributionControl={false}
|
||||||
diffStyleUpdates={true}
|
diffStyleUpdates={true}
|
||||||
onload={(event) => {
|
onload={(event: Map) => {
|
||||||
event.setMaxZoom(18);
|
event.setMaxZoom(18);
|
||||||
event.on('click', handleMapClick);
|
event.on('click', handleMapClick);
|
||||||
if (!simplified) {
|
if (!simplified) {
|
||||||
@@ -303,7 +312,7 @@
|
|||||||
}}
|
}}
|
||||||
bind:map
|
bind:map
|
||||||
>
|
>
|
||||||
{#snippet children({ map }: { map: maplibregl.Map })}
|
{#snippet children({ map }: { map: Map })}
|
||||||
{#if showSimpleControls}
|
{#if showSimpleControls}
|
||||||
<NavigationControl position="top-left" showCompass={!simplified} />
|
<NavigationControl position="top-left" showCompass={!simplified} />
|
||||||
|
|
||||||
|
|||||||
@@ -169,7 +169,7 @@
|
|||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#await import('../asset-viewer/asset-viewer.svelte') then { default: AssetViewer }}
|
{#await import('$lib/components/asset-viewer/asset-viewer.svelte') then { default: AssetViewer }}
|
||||||
<AssetViewer
|
<AssetViewer
|
||||||
{withStacked}
|
{withStacked}
|
||||||
asset={$viewingAsset}
|
asset={$viewingAsset}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
<ModalBody>
|
<ModalBody>
|
||||||
<div class="flex flex-col w-full h-full gap-2 border border-gray-300 dark:border-light rounded-2xl">
|
<div class="flex flex-col w-full h-full gap-2 border border-gray-300 dark:border-light rounded-2xl">
|
||||||
<div class="h-[75vh] min-h-[300px] w-full">
|
<div class="h-[75vh] min-h-[300px] w-full">
|
||||||
{#await import('../components/shared-components/map/map.svelte')}
|
{#await import('$lib/components/shared-components/map/map.svelte')}
|
||||||
{#await delay(timeToLoadTheMap) then}
|
{#await delay(timeToLoadTheMap) then}
|
||||||
<!-- show the loading spinner only if loading the map takes too much time -->
|
<!-- show the loading spinner only if loading the map takes too much time -->
|
||||||
<div class="flex items-center justify-center h-full w-full">
|
<div class="flex items-center justify-center h-full w-full">
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { run } from 'svelte/legacy';
|
|
||||||
|
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
|
import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
|
||||||
import Map from '$lib/components/shared-components/map/map.svelte';
|
import { AppRoute, timeToLoadTheMap } from '$lib/constants';
|
||||||
import { AppRoute } from '$lib/constants';
|
|
||||||
import Portal from '$lib/elements/Portal.svelte';
|
import Portal from '$lib/elements/Portal.svelte';
|
||||||
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
|
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
|
||||||
import { featureFlags } from '$lib/stores/server-config.store';
|
import { featureFlags } from '$lib/stores/server-config.store';
|
||||||
import { handlePromiseError } from '$lib/utils';
|
import { handlePromiseError } from '$lib/utils';
|
||||||
|
import { delay } from '$lib/utils/asset-utils';
|
||||||
import { navigate } from '$lib/utils/navigation';
|
import { navigate } from '$lib/utils/navigation';
|
||||||
|
import { LoadingSpinner } from '@immich/ui';
|
||||||
import { onDestroy } from 'svelte';
|
import { onDestroy } from 'svelte';
|
||||||
|
import { run } from 'svelte/legacy';
|
||||||
import type { PageData } from './$types';
|
import type { PageData } from './$types';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -72,12 +72,21 @@
|
|||||||
{#if $featureFlags.loaded && $featureFlags.map}
|
{#if $featureFlags.loaded && $featureFlags.map}
|
||||||
<UserPageLayout title={data.meta.title}>
|
<UserPageLayout title={data.meta.title}>
|
||||||
<div class="isolate h-full w-full">
|
<div class="isolate h-full w-full">
|
||||||
<Map hash onSelect={onViewAssets} />
|
{#await import('$lib/components/shared-components/map/map.svelte')}
|
||||||
|
{#await delay(timeToLoadTheMap) then}
|
||||||
|
<!-- show the loading spinner only if loading the map takes too much time -->
|
||||||
|
<div class="flex items-center justify-center h-full w-full">
|
||||||
|
<LoadingSpinner />
|
||||||
|
</div>
|
||||||
|
{/await}
|
||||||
|
{:then { default: Map }}
|
||||||
|
<Map hash onSelect={onViewAssets} />
|
||||||
|
{/await}
|
||||||
</div>
|
</div>
|
||||||
</UserPageLayout>
|
</UserPageLayout>
|
||||||
<Portal target="body">
|
<Portal target="body">
|
||||||
{#if $showAssetViewer}
|
{#if $showAssetViewer}
|
||||||
{#await import('../../../../../lib/components/asset-viewer/asset-viewer.svelte') then { default: AssetViewer }}
|
{#await import('$lib/components/asset-viewer/asset-viewer.svelte') then { default: AssetViewer }}
|
||||||
<AssetViewer
|
<AssetViewer
|
||||||
asset={$viewingAsset}
|
asset={$viewingAsset}
|
||||||
showNavigation={viewingAssets.length > 1}
|
showNavigation={viewingAssets.length > 1}
|
||||||
|
|||||||
Reference in New Issue
Block a user