fix: mobile edit handling (#25315)

Co-authored-by: mertalev <101130780+mertalev@users.noreply.github.com>
This commit is contained in:
Brandon Wees
2026-01-19 12:22:53 -06:00
committed by GitHub
parent b3f5b8ede8
commit 1b56bb84f9
64 changed files with 9350 additions and 168 deletions
File diff suppressed because one or more lines are too long
@@ -22,6 +22,7 @@ sealed class BaseAsset {
final int? durationInSeconds; final int? durationInSeconds;
final bool isFavorite; final bool isFavorite;
final String? livePhotoVideoId; final String? livePhotoVideoId;
final bool isEdited;
const BaseAsset({ const BaseAsset({
required this.name, required this.name,
@@ -34,6 +35,7 @@ sealed class BaseAsset {
this.durationInSeconds, this.durationInSeconds,
this.isFavorite = false, this.isFavorite = false,
this.livePhotoVideoId, this.livePhotoVideoId,
required this.isEdited,
}); });
bool get isImage => type == AssetType.image; bool get isImage => type == AssetType.image;
@@ -71,6 +73,7 @@ sealed class BaseAsset {
height: ${height ?? "<NA>"}, height: ${height ?? "<NA>"},
durationInSeconds: ${durationInSeconds ?? "<NA>"}, durationInSeconds: ${durationInSeconds ?? "<NA>"},
isFavorite: $isFavorite, isFavorite: $isFavorite,
isEdited: $isEdited,
}'''; }''';
} }
@@ -85,7 +88,8 @@ sealed class BaseAsset {
width == other.width && width == other.width &&
height == other.height && height == other.height &&
durationInSeconds == other.durationInSeconds && durationInSeconds == other.durationInSeconds &&
isFavorite == other.isFavorite; isFavorite == other.isFavorite &&
isEdited == other.isEdited;
} }
return false; return false;
} }
@@ -99,6 +103,7 @@ sealed class BaseAsset {
width.hashCode ^ width.hashCode ^
height.hashCode ^ height.hashCode ^
durationInSeconds.hashCode ^ durationInSeconds.hashCode ^
isFavorite.hashCode; isFavorite.hashCode ^
isEdited.hashCode;
} }
} }
@@ -28,6 +28,7 @@ class LocalAsset extends BaseAsset {
this.adjustmentTime, this.adjustmentTime,
this.latitude, this.latitude,
this.longitude, this.longitude,
required super.isEdited,
}) : remoteAssetId = remoteId; }) : remoteAssetId = remoteId;
@override @override
@@ -107,6 +108,7 @@ class LocalAsset extends BaseAsset {
DateTime? adjustmentTime, DateTime? adjustmentTime,
double? latitude, double? latitude,
double? longitude, double? longitude,
bool? isEdited,
}) { }) {
return LocalAsset( return LocalAsset(
id: id ?? this.id, id: id ?? this.id,
@@ -125,6 +127,7 @@ class LocalAsset extends BaseAsset {
adjustmentTime: adjustmentTime ?? this.adjustmentTime, adjustmentTime: adjustmentTime ?? this.adjustmentTime,
latitude: latitude ?? this.latitude, latitude: latitude ?? this.latitude,
longitude: longitude ?? this.longitude, longitude: longitude ?? this.longitude,
isEdited: isEdited ?? this.isEdited,
); );
} }
} }
@@ -28,6 +28,7 @@ class RemoteAsset extends BaseAsset {
this.visibility = AssetVisibility.timeline, this.visibility = AssetVisibility.timeline,
super.livePhotoVideoId, super.livePhotoVideoId,
this.stackId, this.stackId,
required super.isEdited,
}) : localAssetId = localId; }) : localAssetId = localId;
@override @override
@@ -104,6 +105,7 @@ class RemoteAsset extends BaseAsset {
AssetVisibility? visibility, AssetVisibility? visibility,
String? livePhotoVideoId, String? livePhotoVideoId,
String? stackId, String? stackId,
bool? isEdited,
}) { }) {
return RemoteAsset( return RemoteAsset(
id: id ?? this.id, id: id ?? this.id,
@@ -122,6 +124,7 @@ class RemoteAsset extends BaseAsset {
visibility: visibility ?? this.visibility, visibility: visibility ?? this.visibility,
livePhotoVideoId: livePhotoVideoId ?? this.livePhotoVideoId, livePhotoVideoId: livePhotoVideoId ?? this.livePhotoVideoId,
stackId: stackId ?? this.stackId, stackId: stackId ?? this.stackId,
isEdited: isEdited ?? this.isEdited,
); );
} }
} }
@@ -436,5 +436,6 @@ extension PlatformToLocalAsset on PlatformAsset {
adjustmentTime: tryFromSecondsSinceEpoch(adjustmentTime, isUtc: true), adjustmentTime: tryFromSecondsSinceEpoch(adjustmentTime, isUtc: true),
latitude: latitude, latitude: latitude,
longitude: longitude, longitude: longitude,
isEdited: false,
); );
} }
@@ -77,6 +77,7 @@ extension on AssetResponseDto {
thumbHash: thumbhash, thumbHash: thumbhash,
localId: null, localId: null,
type: type.toAssetType(), type: type.toAssetType(),
isEdited: isEdited,
); );
} }
} }
@@ -247,6 +247,42 @@ class SyncStreamService {
} }
} }
Future<void> handleWsAssetEditReadyV1Batch(List<dynamic> batchData) async {
if (batchData.isEmpty) return;
_logger.info('Processing batch of ${batchData.length} AssetEditReadyV1 events');
final List<SyncAssetV1> assets = [];
try {
for (final data in batchData) {
if (data is! Map<String, dynamic>) {
continue;
}
final payload = data;
final assetData = payload['asset'];
if (assetData == null) {
continue;
}
final asset = SyncAssetV1.fromJson(assetData);
if (asset != null) {
assets.add(asset);
}
}
if (assets.isNotEmpty) {
await _syncStreamRepository.updateAssetsV1(assets, debugLabel: 'websocket-edit');
_logger.info('Successfully processed ${assets.length} edited assets');
}
} catch (error, stackTrace) {
_logger.severe("Error processing AssetEditReadyV1 websocket batch events", error, stackTrace);
}
}
Future<void> _handleRemoteTrashed(Iterable<String> checksums) async { Future<void> _handleRemoteTrashed(Iterable<String> checksums) async {
if (checksums.isEmpty) { if (checksums.isEmpty) {
return Future.value(); return Future.value();
@@ -196,6 +196,16 @@ class BackgroundSyncManager {
}); });
} }
Future<void> syncWebsocketEditBatch(List<dynamic> batchData) {
if (_syncWebsocketTask != null) {
return _syncWebsocketTask!.future;
}
_syncWebsocketTask = _handleWsAssetEditReadyV1Batch(batchData);
return _syncWebsocketTask!.whenComplete(() {
_syncWebsocketTask = null;
});
}
Future<void> syncLinkedAlbum() { Future<void> syncLinkedAlbum() {
if (_linkedAlbumSyncTask != null) { if (_linkedAlbumSyncTask != null) {
return _linkedAlbumSyncTask!.future; return _linkedAlbumSyncTask!.future;
@@ -231,3 +241,8 @@ Cancelable<void> _handleWsAssetUploadReadyV1Batch(List<dynamic> batchData) => ru
computation: (ref) => ref.read(syncStreamServiceProvider).handleWsAssetUploadReadyV1Batch(batchData), computation: (ref) => ref.read(syncStreamServiceProvider).handleWsAssetUploadReadyV1Batch(batchData),
debugLabel: 'websocket-batch', debugLabel: 'websocket-batch',
); );
Cancelable<void> _handleWsAssetEditReadyV1Batch(List<dynamic> batchData) => runInIsolateGentle(
computation: (ref) => ref.read(syncStreamServiceProvider).handleWsAssetEditReadyV1Batch(batchData),
debugLabel: 'websocket-edit',
);
@@ -47,5 +47,6 @@ extension LocalAssetEntityDataDomainExtension on LocalAssetEntityData {
latitude: latitude, latitude: latitude,
longitude: longitude, longitude: longitude,
cloudId: iCloudId, cloudId: iCloudId,
isEdited: false,
); );
} }
@@ -25,7 +25,8 @@ SELECT
NULL as i_cloud_id, NULL as i_cloud_id,
NULL as latitude, NULL as latitude,
NULL as longitude, NULL as longitude,
NULL as adjustmentTime NULL as adjustmentTime,
rae.is_edited
FROM FROM
remote_asset_entity rae remote_asset_entity rae
LEFT JOIN LEFT JOIN
@@ -61,7 +62,8 @@ SELECT
lae.i_cloud_id, lae.i_cloud_id,
lae.latitude, lae.latitude,
lae.longitude, lae.longitude,
lae.adjustment_time lae.adjustment_time,
0 as is_edited
FROM FROM
local_asset_entity lae local_asset_entity lae
WHERE NOT EXISTS ( WHERE NOT EXISTS (
+4 -1
View File
@@ -29,7 +29,7 @@ class MergedAssetDrift extends i1.ModularAccessor {
); );
$arrayStartIndex += generatedlimit.amountOfVariables; $arrayStartIndex += generatedlimit.amountOfVariables;
return customSelect( return customSelect(
'SELECT rae.id AS remote_id, (SELECT lae.id FROM local_asset_entity AS lae WHERE lae.checksum = rae.checksum LIMIT 1) AS local_id, rae.name, rae.type, rae.created_at AS created_at, rae.updated_at, rae.width, rae.height, rae.duration_in_seconds, rae.is_favorite, rae.thumb_hash, rae.checksum, rae.owner_id, rae.live_photo_video_id, 0 AS orientation, rae.stack_id, NULL AS i_cloud_id, NULL AS latitude, NULL AS longitude, NULL AS adjustmentTime FROM remote_asset_entity AS rae LEFT JOIN stack_entity AS se ON rae.stack_id = se.id WHERE rae.deleted_at IS NULL AND rae.visibility = 0 AND rae.owner_id IN ($expandeduserIds) AND(rae.stack_id IS NULL OR rae.id = se.primary_asset_id)UNION ALL SELECT NULL AS remote_id, lae.id AS local_id, lae.name, lae.type, lae.created_at AS created_at, lae.updated_at, lae.width, lae.height, lae.duration_in_seconds, lae.is_favorite, NULL AS thumb_hash, lae.checksum, NULL AS owner_id, NULL AS live_photo_video_id, lae.orientation, NULL AS stack_id, lae.i_cloud_id, lae.latitude, lae.longitude, lae.adjustment_time FROM local_asset_entity AS lae WHERE NOT EXISTS (SELECT 1 FROM remote_asset_entity AS rae WHERE rae.checksum = lae.checksum AND rae.owner_id IN ($expandeduserIds)) AND EXISTS (SELECT 1 FROM local_album_asset_entity AS laa INNER JOIN local_album_entity AS la ON laa.album_id = la.id WHERE laa.asset_id = lae.id AND la.backup_selection = 0) AND NOT EXISTS (SELECT 1 FROM local_album_asset_entity AS laa INNER JOIN local_album_entity AS la ON laa.album_id = la.id WHERE laa.asset_id = lae.id AND la.backup_selection = 2) ORDER BY created_at DESC ${generatedlimit.sql}', 'SELECT rae.id AS remote_id, (SELECT lae.id FROM local_asset_entity AS lae WHERE lae.checksum = rae.checksum LIMIT 1) AS local_id, rae.name, rae.type, rae.created_at AS created_at, rae.updated_at, rae.width, rae.height, rae.duration_in_seconds, rae.is_favorite, rae.thumb_hash, rae.checksum, rae.owner_id, rae.live_photo_video_id, 0 AS orientation, rae.stack_id, NULL AS i_cloud_id, NULL AS latitude, NULL AS longitude, NULL AS adjustmentTime, rae.is_edited FROM remote_asset_entity AS rae LEFT JOIN stack_entity AS se ON rae.stack_id = se.id WHERE rae.deleted_at IS NULL AND rae.visibility = 0 AND rae.owner_id IN ($expandeduserIds) AND(rae.stack_id IS NULL OR rae.id = se.primary_asset_id)UNION ALL SELECT NULL AS remote_id, lae.id AS local_id, lae.name, lae.type, lae.created_at AS created_at, lae.updated_at, lae.width, lae.height, lae.duration_in_seconds, lae.is_favorite, NULL AS thumb_hash, lae.checksum, NULL AS owner_id, NULL AS live_photo_video_id, lae.orientation, NULL AS stack_id, lae.i_cloud_id, lae.latitude, lae.longitude, lae.adjustment_time, 0 AS is_edited FROM local_asset_entity AS lae WHERE NOT EXISTS (SELECT 1 FROM remote_asset_entity AS rae WHERE rae.checksum = lae.checksum AND rae.owner_id IN ($expandeduserIds)) AND EXISTS (SELECT 1 FROM local_album_asset_entity AS laa INNER JOIN local_album_entity AS la ON laa.album_id = la.id WHERE laa.asset_id = lae.id AND la.backup_selection = 0) AND NOT EXISTS (SELECT 1 FROM local_album_asset_entity AS laa INNER JOIN local_album_entity AS la ON laa.album_id = la.id WHERE laa.asset_id = lae.id AND la.backup_selection = 2) ORDER BY created_at DESC ${generatedlimit.sql}',
variables: [ variables: [
for (var $ in userIds) i0.Variable<String>($), for (var $ in userIds) i0.Variable<String>($),
...generatedlimit.introducedVariables, ...generatedlimit.introducedVariables,
@@ -66,6 +66,7 @@ class MergedAssetDrift extends i1.ModularAccessor {
latitude: row.readNullable<double>('latitude'), latitude: row.readNullable<double>('latitude'),
longitude: row.readNullable<double>('longitude'), longitude: row.readNullable<double>('longitude'),
adjustmentTime: row.readNullable<DateTime>('adjustmentTime'), adjustmentTime: row.readNullable<DateTime>('adjustmentTime'),
isEdited: row.read<bool>('is_edited'),
), ),
); );
} }
@@ -137,6 +138,7 @@ class MergedAssetResult {
final double? latitude; final double? latitude;
final double? longitude; final double? longitude;
final DateTime? adjustmentTime; final DateTime? adjustmentTime;
final bool isEdited;
MergedAssetResult({ MergedAssetResult({
this.remoteId, this.remoteId,
this.localId, this.localId,
@@ -158,6 +160,7 @@ class MergedAssetResult {
this.latitude, this.latitude,
this.longitude, this.longitude,
this.adjustmentTime, this.adjustmentTime,
required this.isEdited,
}); });
} }
@@ -44,6 +44,8 @@ class RemoteAssetEntity extends Table with DriftDefaultsMixin, AssetEntityMixin
TextColumn get libraryId => text().nullable()(); TextColumn get libraryId => text().nullable()();
BoolColumn get isEdited => boolean().withDefault(const Constant(false))();
@override @override
Set<Column> get primaryKey => {id}; Set<Column> get primaryKey => {id};
} }
@@ -66,5 +68,6 @@ extension RemoteAssetEntityDataDomainEx on RemoteAssetEntityData {
livePhotoVideoId: livePhotoVideoId, livePhotoVideoId: livePhotoVideoId,
localId: localId, localId: localId,
stackId: stackId, stackId: stackId,
isEdited: isEdited,
); );
} }
@@ -31,6 +31,7 @@ typedef $$RemoteAssetEntityTableCreateCompanionBuilder =
required i2.AssetVisibility visibility, required i2.AssetVisibility visibility,
i0.Value<String?> stackId, i0.Value<String?> stackId,
i0.Value<String?> libraryId, i0.Value<String?> libraryId,
i0.Value<bool> isEdited,
}); });
typedef $$RemoteAssetEntityTableUpdateCompanionBuilder = typedef $$RemoteAssetEntityTableUpdateCompanionBuilder =
i1.RemoteAssetEntityCompanion Function({ i1.RemoteAssetEntityCompanion Function({
@@ -52,6 +53,7 @@ typedef $$RemoteAssetEntityTableUpdateCompanionBuilder =
i0.Value<i2.AssetVisibility> visibility, i0.Value<i2.AssetVisibility> visibility,
i0.Value<String?> stackId, i0.Value<String?> stackId,
i0.Value<String?> libraryId, i0.Value<String?> libraryId,
i0.Value<bool> isEdited,
}); });
final class $$RemoteAssetEntityTableReferences final class $$RemoteAssetEntityTableReferences
@@ -196,6 +198,11 @@ class $$RemoteAssetEntityTableFilterComposer
builder: (column) => i0.ColumnFilters(column), builder: (column) => i0.ColumnFilters(column),
); );
i0.ColumnFilters<bool> get isEdited => $composableBuilder(
column: $table.isEdited,
builder: (column) => i0.ColumnFilters(column),
);
i5.$$UserEntityTableFilterComposer get ownerId { i5.$$UserEntityTableFilterComposer get ownerId {
final i5.$$UserEntityTableFilterComposer composer = $composerBuilder( final i5.$$UserEntityTableFilterComposer composer = $composerBuilder(
composer: this, composer: this,
@@ -318,6 +325,11 @@ class $$RemoteAssetEntityTableOrderingComposer
builder: (column) => i0.ColumnOrderings(column), builder: (column) => i0.ColumnOrderings(column),
); );
i0.ColumnOrderings<bool> get isEdited => $composableBuilder(
column: $table.isEdited,
builder: (column) => i0.ColumnOrderings(column),
);
i5.$$UserEntityTableOrderingComposer get ownerId { i5.$$UserEntityTableOrderingComposer get ownerId {
final i5.$$UserEntityTableOrderingComposer composer = $composerBuilder( final i5.$$UserEntityTableOrderingComposer composer = $composerBuilder(
composer: this, composer: this,
@@ -417,6 +429,9 @@ class $$RemoteAssetEntityTableAnnotationComposer
i0.GeneratedColumn<String> get libraryId => i0.GeneratedColumn<String> get libraryId =>
$composableBuilder(column: $table.libraryId, builder: (column) => column); $composableBuilder(column: $table.libraryId, builder: (column) => column);
i0.GeneratedColumn<bool> get isEdited =>
$composableBuilder(column: $table.isEdited, builder: (column) => column);
i5.$$UserEntityTableAnnotationComposer get ownerId { i5.$$UserEntityTableAnnotationComposer get ownerId {
final i5.$$UserEntityTableAnnotationComposer composer = $composerBuilder( final i5.$$UserEntityTableAnnotationComposer composer = $composerBuilder(
composer: this, composer: this,
@@ -497,6 +512,7 @@ class $$RemoteAssetEntityTableTableManager
const i0.Value.absent(), const i0.Value.absent(),
i0.Value<String?> stackId = const i0.Value.absent(), i0.Value<String?> stackId = const i0.Value.absent(),
i0.Value<String?> libraryId = const i0.Value.absent(), i0.Value<String?> libraryId = const i0.Value.absent(),
i0.Value<bool> isEdited = const i0.Value.absent(),
}) => i1.RemoteAssetEntityCompanion( }) => i1.RemoteAssetEntityCompanion(
name: name, name: name,
type: type, type: type,
@@ -516,6 +532,7 @@ class $$RemoteAssetEntityTableTableManager
visibility: visibility, visibility: visibility,
stackId: stackId, stackId: stackId,
libraryId: libraryId, libraryId: libraryId,
isEdited: isEdited,
), ),
createCompanionCallback: createCompanionCallback:
({ ({
@@ -537,6 +554,7 @@ class $$RemoteAssetEntityTableTableManager
required i2.AssetVisibility visibility, required i2.AssetVisibility visibility,
i0.Value<String?> stackId = const i0.Value.absent(), i0.Value<String?> stackId = const i0.Value.absent(),
i0.Value<String?> libraryId = const i0.Value.absent(), i0.Value<String?> libraryId = const i0.Value.absent(),
i0.Value<bool> isEdited = const i0.Value.absent(),
}) => i1.RemoteAssetEntityCompanion.insert( }) => i1.RemoteAssetEntityCompanion.insert(
name: name, name: name,
type: type, type: type,
@@ -556,6 +574,7 @@ class $$RemoteAssetEntityTableTableManager
visibility: visibility, visibility: visibility,
stackId: stackId, stackId: stackId,
libraryId: libraryId, libraryId: libraryId,
isEdited: isEdited,
), ),
withReferenceMapper: (p0) => p0 withReferenceMapper: (p0) => p0
.map( .map(
@@ -844,6 +863,21 @@ class $RemoteAssetEntityTable extends i3.RemoteAssetEntity
type: i0.DriftSqlType.string, type: i0.DriftSqlType.string,
requiredDuringInsert: false, requiredDuringInsert: false,
); );
static const i0.VerificationMeta _isEditedMeta = const i0.VerificationMeta(
'isEdited',
);
@override
late final i0.GeneratedColumn<bool> isEdited = i0.GeneratedColumn<bool>(
'is_edited',
aliasedName,
false,
type: i0.DriftSqlType.bool,
requiredDuringInsert: false,
defaultConstraints: i0.GeneratedColumn.constraintIsAlways(
'CHECK ("is_edited" IN (0, 1))',
),
defaultValue: const i4.Constant(false),
);
@override @override
List<i0.GeneratedColumn> get $columns => [ List<i0.GeneratedColumn> get $columns => [
name, name,
@@ -864,6 +898,7 @@ class $RemoteAssetEntityTable extends i3.RemoteAssetEntity
visibility, visibility,
stackId, stackId,
libraryId, libraryId,
isEdited,
]; ];
@override @override
String get aliasedName => _alias ?? actualTableName; String get aliasedName => _alias ?? actualTableName;
@@ -987,6 +1022,12 @@ class $RemoteAssetEntityTable extends i3.RemoteAssetEntity
libraryId.isAcceptableOrUnknown(data['library_id']!, _libraryIdMeta), libraryId.isAcceptableOrUnknown(data['library_id']!, _libraryIdMeta),
); );
} }
if (data.containsKey('is_edited')) {
context.handle(
_isEditedMeta,
isEdited.isAcceptableOrUnknown(data['is_edited']!, _isEditedMeta),
);
}
return context; return context;
} }
@@ -1075,6 +1116,10 @@ class $RemoteAssetEntityTable extends i3.RemoteAssetEntity
i0.DriftSqlType.string, i0.DriftSqlType.string,
data['${effectivePrefix}library_id'], data['${effectivePrefix}library_id'],
), ),
isEdited: attachedDatabase.typeMapping.read(
i0.DriftSqlType.bool,
data['${effectivePrefix}is_edited'],
)!,
); );
} }
@@ -1115,6 +1160,7 @@ class RemoteAssetEntityData extends i0.DataClass
final i2.AssetVisibility visibility; final i2.AssetVisibility visibility;
final String? stackId; final String? stackId;
final String? libraryId; final String? libraryId;
final bool isEdited;
const RemoteAssetEntityData({ const RemoteAssetEntityData({
required this.name, required this.name,
required this.type, required this.type,
@@ -1134,6 +1180,7 @@ class RemoteAssetEntityData extends i0.DataClass
required this.visibility, required this.visibility,
this.stackId, this.stackId,
this.libraryId, this.libraryId,
required this.isEdited,
}); });
@override @override
Map<String, i0.Expression> toColumns(bool nullToAbsent) { Map<String, i0.Expression> toColumns(bool nullToAbsent) {
@@ -1182,6 +1229,7 @@ class RemoteAssetEntityData extends i0.DataClass
if (!nullToAbsent || libraryId != null) { if (!nullToAbsent || libraryId != null) {
map['library_id'] = i0.Variable<String>(libraryId); map['library_id'] = i0.Variable<String>(libraryId);
} }
map['is_edited'] = i0.Variable<bool>(isEdited);
return map; return map;
} }
@@ -1213,6 +1261,7 @@ class RemoteAssetEntityData extends i0.DataClass
), ),
stackId: serializer.fromJson<String?>(json['stackId']), stackId: serializer.fromJson<String?>(json['stackId']),
libraryId: serializer.fromJson<String?>(json['libraryId']), libraryId: serializer.fromJson<String?>(json['libraryId']),
isEdited: serializer.fromJson<bool>(json['isEdited']),
); );
} }
@override @override
@@ -1241,6 +1290,7 @@ class RemoteAssetEntityData extends i0.DataClass
), ),
'stackId': serializer.toJson<String?>(stackId), 'stackId': serializer.toJson<String?>(stackId),
'libraryId': serializer.toJson<String?>(libraryId), 'libraryId': serializer.toJson<String?>(libraryId),
'isEdited': serializer.toJson<bool>(isEdited),
}; };
} }
@@ -1263,6 +1313,7 @@ class RemoteAssetEntityData extends i0.DataClass
i2.AssetVisibility? visibility, i2.AssetVisibility? visibility,
i0.Value<String?> stackId = const i0.Value.absent(), i0.Value<String?> stackId = const i0.Value.absent(),
i0.Value<String?> libraryId = const i0.Value.absent(), i0.Value<String?> libraryId = const i0.Value.absent(),
bool? isEdited,
}) => i1.RemoteAssetEntityData( }) => i1.RemoteAssetEntityData(
name: name ?? this.name, name: name ?? this.name,
type: type ?? this.type, type: type ?? this.type,
@@ -1288,6 +1339,7 @@ class RemoteAssetEntityData extends i0.DataClass
visibility: visibility ?? this.visibility, visibility: visibility ?? this.visibility,
stackId: stackId.present ? stackId.value : this.stackId, stackId: stackId.present ? stackId.value : this.stackId,
libraryId: libraryId.present ? libraryId.value : this.libraryId, libraryId: libraryId.present ? libraryId.value : this.libraryId,
isEdited: isEdited ?? this.isEdited,
); );
RemoteAssetEntityData copyWithCompanion(i1.RemoteAssetEntityCompanion data) { RemoteAssetEntityData copyWithCompanion(i1.RemoteAssetEntityCompanion data) {
return RemoteAssetEntityData( return RemoteAssetEntityData(
@@ -1319,6 +1371,7 @@ class RemoteAssetEntityData extends i0.DataClass
: this.visibility, : this.visibility,
stackId: data.stackId.present ? data.stackId.value : this.stackId, stackId: data.stackId.present ? data.stackId.value : this.stackId,
libraryId: data.libraryId.present ? data.libraryId.value : this.libraryId, libraryId: data.libraryId.present ? data.libraryId.value : this.libraryId,
isEdited: data.isEdited.present ? data.isEdited.value : this.isEdited,
); );
} }
@@ -1342,7 +1395,8 @@ class RemoteAssetEntityData extends i0.DataClass
..write('livePhotoVideoId: $livePhotoVideoId, ') ..write('livePhotoVideoId: $livePhotoVideoId, ')
..write('visibility: $visibility, ') ..write('visibility: $visibility, ')
..write('stackId: $stackId, ') ..write('stackId: $stackId, ')
..write('libraryId: $libraryId') ..write('libraryId: $libraryId, ')
..write('isEdited: $isEdited')
..write(')')) ..write(')'))
.toString(); .toString();
} }
@@ -1367,6 +1421,7 @@ class RemoteAssetEntityData extends i0.DataClass
visibility, visibility,
stackId, stackId,
libraryId, libraryId,
isEdited,
); );
@override @override
bool operator ==(Object other) => bool operator ==(Object other) =>
@@ -1389,7 +1444,8 @@ class RemoteAssetEntityData extends i0.DataClass
other.livePhotoVideoId == this.livePhotoVideoId && other.livePhotoVideoId == this.livePhotoVideoId &&
other.visibility == this.visibility && other.visibility == this.visibility &&
other.stackId == this.stackId && other.stackId == this.stackId &&
other.libraryId == this.libraryId); other.libraryId == this.libraryId &&
other.isEdited == this.isEdited);
} }
class RemoteAssetEntityCompanion class RemoteAssetEntityCompanion
@@ -1412,6 +1468,7 @@ class RemoteAssetEntityCompanion
final i0.Value<i2.AssetVisibility> visibility; final i0.Value<i2.AssetVisibility> visibility;
final i0.Value<String?> stackId; final i0.Value<String?> stackId;
final i0.Value<String?> libraryId; final i0.Value<String?> libraryId;
final i0.Value<bool> isEdited;
const RemoteAssetEntityCompanion({ const RemoteAssetEntityCompanion({
this.name = const i0.Value.absent(), this.name = const i0.Value.absent(),
this.type = const i0.Value.absent(), this.type = const i0.Value.absent(),
@@ -1431,6 +1488,7 @@ class RemoteAssetEntityCompanion
this.visibility = const i0.Value.absent(), this.visibility = const i0.Value.absent(),
this.stackId = const i0.Value.absent(), this.stackId = const i0.Value.absent(),
this.libraryId = const i0.Value.absent(), this.libraryId = const i0.Value.absent(),
this.isEdited = const i0.Value.absent(),
}); });
RemoteAssetEntityCompanion.insert({ RemoteAssetEntityCompanion.insert({
required String name, required String name,
@@ -1451,6 +1509,7 @@ class RemoteAssetEntityCompanion
required i2.AssetVisibility visibility, required i2.AssetVisibility visibility,
this.stackId = const i0.Value.absent(), this.stackId = const i0.Value.absent(),
this.libraryId = const i0.Value.absent(), this.libraryId = const i0.Value.absent(),
this.isEdited = const i0.Value.absent(),
}) : name = i0.Value(name), }) : name = i0.Value(name),
type = i0.Value(type), type = i0.Value(type),
id = i0.Value(id), id = i0.Value(id),
@@ -1476,6 +1535,7 @@ class RemoteAssetEntityCompanion
i0.Expression<int>? visibility, i0.Expression<int>? visibility,
i0.Expression<String>? stackId, i0.Expression<String>? stackId,
i0.Expression<String>? libraryId, i0.Expression<String>? libraryId,
i0.Expression<bool>? isEdited,
}) { }) {
return i0.RawValuesInsertable({ return i0.RawValuesInsertable({
if (name != null) 'name': name, if (name != null) 'name': name,
@@ -1496,6 +1556,7 @@ class RemoteAssetEntityCompanion
if (visibility != null) 'visibility': visibility, if (visibility != null) 'visibility': visibility,
if (stackId != null) 'stack_id': stackId, if (stackId != null) 'stack_id': stackId,
if (libraryId != null) 'library_id': libraryId, if (libraryId != null) 'library_id': libraryId,
if (isEdited != null) 'is_edited': isEdited,
}); });
} }
@@ -1518,6 +1579,7 @@ class RemoteAssetEntityCompanion
i0.Value<i2.AssetVisibility>? visibility, i0.Value<i2.AssetVisibility>? visibility,
i0.Value<String?>? stackId, i0.Value<String?>? stackId,
i0.Value<String?>? libraryId, i0.Value<String?>? libraryId,
i0.Value<bool>? isEdited,
}) { }) {
return i1.RemoteAssetEntityCompanion( return i1.RemoteAssetEntityCompanion(
name: name ?? this.name, name: name ?? this.name,
@@ -1538,6 +1600,7 @@ class RemoteAssetEntityCompanion
visibility: visibility ?? this.visibility, visibility: visibility ?? this.visibility,
stackId: stackId ?? this.stackId, stackId: stackId ?? this.stackId,
libraryId: libraryId ?? this.libraryId, libraryId: libraryId ?? this.libraryId,
isEdited: isEdited ?? this.isEdited,
); );
} }
@@ -1602,6 +1665,9 @@ class RemoteAssetEntityCompanion
if (libraryId.present) { if (libraryId.present) {
map['library_id'] = i0.Variable<String>(libraryId.value); map['library_id'] = i0.Variable<String>(libraryId.value);
} }
if (isEdited.present) {
map['is_edited'] = i0.Variable<bool>(isEdited.value);
}
return map; return map;
} }
@@ -1625,7 +1691,8 @@ class RemoteAssetEntityCompanion
..write('livePhotoVideoId: $livePhotoVideoId, ') ..write('livePhotoVideoId: $livePhotoVideoId, ')
..write('visibility: $visibility, ') ..write('visibility: $visibility, ')
..write('stackId: $stackId, ') ..write('stackId: $stackId, ')
..write('libraryId: $libraryId') ..write('libraryId: $libraryId, ')
..write('isEdited: $isEdited')
..write(')')) ..write(')'))
.toString(); .toString();
} }
@@ -45,5 +45,6 @@ extension TrashedLocalAssetEntityDataDomainExtension on TrashedLocalAssetEntityD
height: height, height: height,
width: width, width: width,
orientation: orientation, orientation: orientation,
isEdited: false,
); );
} }
@@ -97,7 +97,7 @@ class Drift extends $Drift implements IDatabaseRepository {
} }
@override @override
int get schemaVersion => 16; int get schemaVersion => 17;
@override @override
MigrationStrategy get migration => MigrationStrategy( MigrationStrategy get migration => MigrationStrategy(
@@ -201,6 +201,9 @@ class Drift extends $Drift implements IDatabaseRepository {
await m.createIndex(v16.idxLocalAssetCloudId); await m.createIndex(v16.idxLocalAssetCloudId);
await m.createTable(v16.remoteAssetCloudIdEntity); await m.createTable(v16.remoteAssetCloudIdEntity);
}, },
from16To17: (m, v17) async {
await m.addColumn(v17.remoteAssetEntity, v17.remoteAssetEntity.isEdited);
},
), ),
); );
@@ -6911,6 +6911,503 @@ i1.GeneratedColumn<DateTime> _column_100(String aliasedName) =>
true, true,
type: i1.DriftSqlType.dateTime, type: i1.DriftSqlType.dateTime,
); );
final class Schema17 extends i0.VersionedSchema {
Schema17({required super.database}) : super(version: 17);
@override
late final List<i1.DatabaseSchemaEntity> entities = [
userEntity,
remoteAssetEntity,
stackEntity,
localAssetEntity,
remoteAlbumEntity,
localAlbumEntity,
localAlbumAssetEntity,
idxLocalAssetChecksum,
idxLocalAssetCloudId,
idxRemoteAssetOwnerChecksum,
uQRemoteAssetsOwnerChecksum,
uQRemoteAssetsOwnerLibraryChecksum,
idxRemoteAssetChecksum,
authUserEntity,
userMetadataEntity,
partnerEntity,
remoteExifEntity,
remoteAlbumAssetEntity,
remoteAlbumUserEntity,
remoteAssetCloudIdEntity,
memoryEntity,
memoryAssetEntity,
personEntity,
assetFaceEntity,
storeEntity,
trashedLocalAssetEntity,
idxLatLng,
idxTrashedLocalAssetChecksum,
idxTrashedLocalAssetAlbum,
];
late final Shape20 userEntity = Shape20(
source: i0.VersionedTable(
entityName: 'user_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(id)'],
columns: [
_column_0,
_column_1,
_column_3,
_column_84,
_column_85,
_column_91,
],
attachedDatabase: database,
),
alias: null,
);
late final Shape28 remoteAssetEntity = Shape28(
source: i0.VersionedTable(
entityName: 'remote_asset_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(id)'],
columns: [
_column_1,
_column_8,
_column_9,
_column_5,
_column_10,
_column_11,
_column_12,
_column_0,
_column_13,
_column_14,
_column_15,
_column_16,
_column_17,
_column_18,
_column_19,
_column_20,
_column_21,
_column_86,
_column_101,
],
attachedDatabase: database,
),
alias: null,
);
late final Shape3 stackEntity = Shape3(
source: i0.VersionedTable(
entityName: 'stack_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(id)'],
columns: [_column_0, _column_9, _column_5, _column_15, _column_75],
attachedDatabase: database,
),
alias: null,
);
late final Shape26 localAssetEntity = Shape26(
source: i0.VersionedTable(
entityName: 'local_asset_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(id)'],
columns: [
_column_1,
_column_8,
_column_9,
_column_5,
_column_10,
_column_11,
_column_12,
_column_0,
_column_22,
_column_14,
_column_23,
_column_98,
_column_96,
_column_46,
_column_47,
],
attachedDatabase: database,
),
alias: null,
);
late final Shape9 remoteAlbumEntity = Shape9(
source: i0.VersionedTable(
entityName: 'remote_album_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(id)'],
columns: [
_column_0,
_column_1,
_column_56,
_column_9,
_column_5,
_column_15,
_column_57,
_column_58,
_column_59,
],
attachedDatabase: database,
),
alias: null,
);
late final Shape19 localAlbumEntity = Shape19(
source: i0.VersionedTable(
entityName: 'local_album_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(id)'],
columns: [
_column_0,
_column_1,
_column_5,
_column_31,
_column_32,
_column_90,
_column_33,
],
attachedDatabase: database,
),
alias: null,
);
late final Shape22 localAlbumAssetEntity = Shape22(
source: i0.VersionedTable(
entityName: 'local_album_asset_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(asset_id, album_id)'],
columns: [_column_34, _column_35, _column_33],
attachedDatabase: database,
),
alias: null,
);
final i1.Index idxLocalAssetChecksum = i1.Index(
'idx_local_asset_checksum',
'CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)',
);
final i1.Index idxLocalAssetCloudId = i1.Index(
'idx_local_asset_cloud_id',
'CREATE INDEX IF NOT EXISTS idx_local_asset_cloud_id ON local_asset_entity (i_cloud_id)',
);
final i1.Index idxRemoteAssetOwnerChecksum = i1.Index(
'idx_remote_asset_owner_checksum',
'CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_checksum ON remote_asset_entity (owner_id, checksum)',
);
final i1.Index uQRemoteAssetsOwnerChecksum = i1.Index(
'UQ_remote_assets_owner_checksum',
'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum ON remote_asset_entity (owner_id, checksum) WHERE(library_id IS NULL)',
);
final i1.Index uQRemoteAssetsOwnerLibraryChecksum = i1.Index(
'UQ_remote_assets_owner_library_checksum',
'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum ON remote_asset_entity (owner_id, library_id, checksum) WHERE(library_id IS NOT NULL)',
);
final i1.Index idxRemoteAssetChecksum = i1.Index(
'idx_remote_asset_checksum',
'CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)',
);
late final Shape21 authUserEntity = Shape21(
source: i0.VersionedTable(
entityName: 'auth_user_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(id)'],
columns: [
_column_0,
_column_1,
_column_3,
_column_2,
_column_84,
_column_85,
_column_92,
_column_93,
_column_7,
_column_94,
],
attachedDatabase: database,
),
alias: null,
);
late final Shape4 userMetadataEntity = Shape4(
source: i0.VersionedTable(
entityName: 'user_metadata_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(user_id, "key")'],
columns: [_column_25, _column_26, _column_27],
attachedDatabase: database,
),
alias: null,
);
late final Shape5 partnerEntity = Shape5(
source: i0.VersionedTable(
entityName: 'partner_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(shared_by_id, shared_with_id)'],
columns: [_column_28, _column_29, _column_30],
attachedDatabase: database,
),
alias: null,
);
late final Shape8 remoteExifEntity = Shape8(
source: i0.VersionedTable(
entityName: 'remote_exif_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(asset_id)'],
columns: [
_column_36,
_column_37,
_column_38,
_column_39,
_column_40,
_column_41,
_column_11,
_column_10,
_column_42,
_column_43,
_column_44,
_column_45,
_column_46,
_column_47,
_column_48,
_column_49,
_column_50,
_column_51,
_column_52,
_column_53,
_column_54,
_column_55,
],
attachedDatabase: database,
),
alias: null,
);
late final Shape7 remoteAlbumAssetEntity = Shape7(
source: i0.VersionedTable(
entityName: 'remote_album_asset_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(asset_id, album_id)'],
columns: [_column_36, _column_60],
attachedDatabase: database,
),
alias: null,
);
late final Shape10 remoteAlbumUserEntity = Shape10(
source: i0.VersionedTable(
entityName: 'remote_album_user_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(album_id, user_id)'],
columns: [_column_60, _column_25, _column_61],
attachedDatabase: database,
),
alias: null,
);
late final Shape27 remoteAssetCloudIdEntity = Shape27(
source: i0.VersionedTable(
entityName: 'remote_asset_cloud_id_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(asset_id)'],
columns: [
_column_36,
_column_99,
_column_100,
_column_96,
_column_46,
_column_47,
],
attachedDatabase: database,
),
alias: null,
);
late final Shape11 memoryEntity = Shape11(
source: i0.VersionedTable(
entityName: 'memory_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(id)'],
columns: [
_column_0,
_column_9,
_column_5,
_column_18,
_column_15,
_column_8,
_column_62,
_column_63,
_column_64,
_column_65,
_column_66,
_column_67,
],
attachedDatabase: database,
),
alias: null,
);
late final Shape12 memoryAssetEntity = Shape12(
source: i0.VersionedTable(
entityName: 'memory_asset_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(asset_id, memory_id)'],
columns: [_column_36, _column_68],
attachedDatabase: database,
),
alias: null,
);
late final Shape14 personEntity = Shape14(
source: i0.VersionedTable(
entityName: 'person_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(id)'],
columns: [
_column_0,
_column_9,
_column_5,
_column_15,
_column_1,
_column_69,
_column_71,
_column_72,
_column_73,
_column_74,
],
attachedDatabase: database,
),
alias: null,
);
late final Shape15 assetFaceEntity = Shape15(
source: i0.VersionedTable(
entityName: 'asset_face_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(id)'],
columns: [
_column_0,
_column_36,
_column_76,
_column_77,
_column_78,
_column_79,
_column_80,
_column_81,
_column_82,
_column_83,
],
attachedDatabase: database,
),
alias: null,
);
late final Shape18 storeEntity = Shape18(
source: i0.VersionedTable(
entityName: 'store_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(id)'],
columns: [_column_87, _column_88, _column_89],
attachedDatabase: database,
),
alias: null,
);
late final Shape25 trashedLocalAssetEntity = Shape25(
source: i0.VersionedTable(
entityName: 'trashed_local_asset_entity',
withoutRowId: true,
isStrict: true,
tableConstraints: ['PRIMARY KEY(id, album_id)'],
columns: [
_column_1,
_column_8,
_column_9,
_column_5,
_column_10,
_column_11,
_column_12,
_column_0,
_column_95,
_column_22,
_column_14,
_column_23,
_column_97,
],
attachedDatabase: database,
),
alias: null,
);
final i1.Index idxLatLng = i1.Index(
'idx_lat_lng',
'CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)',
);
final i1.Index idxTrashedLocalAssetChecksum = i1.Index(
'idx_trashed_local_asset_checksum',
'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)',
);
final i1.Index idxTrashedLocalAssetAlbum = i1.Index(
'idx_trashed_local_asset_album',
'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_album ON trashed_local_asset_entity (album_id)',
);
}
class Shape28 extends i0.VersionedTable {
Shape28({required super.source, required super.alias}) : super.aliased();
i1.GeneratedColumn<String> get name =>
columnsByName['name']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<int> get type =>
columnsByName['type']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<DateTime> get createdAt =>
columnsByName['created_at']! as i1.GeneratedColumn<DateTime>;
i1.GeneratedColumn<DateTime> get updatedAt =>
columnsByName['updated_at']! as i1.GeneratedColumn<DateTime>;
i1.GeneratedColumn<int> get width =>
columnsByName['width']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<int> get height =>
columnsByName['height']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<int> get durationInSeconds =>
columnsByName['duration_in_seconds']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<String> get id =>
columnsByName['id']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<String> get checksum =>
columnsByName['checksum']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<bool> get isFavorite =>
columnsByName['is_favorite']! as i1.GeneratedColumn<bool>;
i1.GeneratedColumn<String> get ownerId =>
columnsByName['owner_id']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<DateTime> get localDateTime =>
columnsByName['local_date_time']! as i1.GeneratedColumn<DateTime>;
i1.GeneratedColumn<String> get thumbHash =>
columnsByName['thumb_hash']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<DateTime> get deletedAt =>
columnsByName['deleted_at']! as i1.GeneratedColumn<DateTime>;
i1.GeneratedColumn<String> get livePhotoVideoId =>
columnsByName['live_photo_video_id']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<int> get visibility =>
columnsByName['visibility']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<String> get stackId =>
columnsByName['stack_id']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<String> get libraryId =>
columnsByName['library_id']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<bool> get isEdited =>
columnsByName['is_edited']! as i1.GeneratedColumn<bool>;
}
i1.GeneratedColumn<bool> _column_101(String aliasedName) =>
i1.GeneratedColumn<bool>(
'is_edited',
aliasedName,
false,
type: i1.DriftSqlType.bool,
defaultConstraints: i1.GeneratedColumn.constraintIsAlways(
'CHECK ("is_edited" IN (0, 1))',
),
defaultValue: const CustomExpression('0'),
);
i0.MigrationStepWithVersion migrationSteps({ i0.MigrationStepWithVersion migrationSteps({
required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2, required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2,
required Future<void> Function(i1.Migrator m, Schema3 schema) from2To3, required Future<void> Function(i1.Migrator m, Schema3 schema) from2To3,
@@ -6927,6 +7424,7 @@ i0.MigrationStepWithVersion migrationSteps({
required Future<void> Function(i1.Migrator m, Schema14 schema) from13To14, required Future<void> Function(i1.Migrator m, Schema14 schema) from13To14,
required Future<void> Function(i1.Migrator m, Schema15 schema) from14To15, required Future<void> Function(i1.Migrator m, Schema15 schema) from14To15,
required Future<void> Function(i1.Migrator m, Schema16 schema) from15To16, required Future<void> Function(i1.Migrator m, Schema16 schema) from15To16,
required Future<void> Function(i1.Migrator m, Schema17 schema) from16To17,
}) { }) {
return (currentVersion, database) async { return (currentVersion, database) async {
switch (currentVersion) { switch (currentVersion) {
@@ -7005,6 +7503,11 @@ i0.MigrationStepWithVersion migrationSteps({
final migrator = i1.Migrator(database, schema); final migrator = i1.Migrator(database, schema);
await from15To16(migrator, schema); await from15To16(migrator, schema);
return 16; return 16;
case 16:
final schema = Schema17(database: database);
final migrator = i1.Migrator(database, schema);
await from16To17(migrator, schema);
return 17;
default: default:
throw ArgumentError.value('Unknown migration from $currentVersion'); throw ArgumentError.value('Unknown migration from $currentVersion');
} }
@@ -7027,6 +7530,7 @@ i1.OnUpgrade stepByStep({
required Future<void> Function(i1.Migrator m, Schema14 schema) from13To14, required Future<void> Function(i1.Migrator m, Schema14 schema) from13To14,
required Future<void> Function(i1.Migrator m, Schema15 schema) from14To15, required Future<void> Function(i1.Migrator m, Schema15 schema) from14To15,
required Future<void> Function(i1.Migrator m, Schema16 schema) from15To16, required Future<void> Function(i1.Migrator m, Schema16 schema) from15To16,
required Future<void> Function(i1.Migrator m, Schema17 schema) from16To17,
}) => i0.VersionedSchema.stepByStepHelper( }) => i0.VersionedSchema.stepByStepHelper(
step: migrationSteps( step: migrationSteps(
from1To2: from1To2, from1To2: from1To2,
@@ -7044,5 +7548,6 @@ i1.OnUpgrade stepByStep({
from13To14: from13To14, from13To14: from13To14,
from14To15: from14To15, from14To15: from14To15,
from15To16: from15To16, from15To16: from15To16,
from16To17: from16To17,
), ),
); );
@@ -200,6 +200,7 @@ class SyncStreamRepository extends DriftDatabaseRepository {
libraryId: Value(asset.libraryId), libraryId: Value(asset.libraryId),
width: Value(asset.width), width: Value(asset.width),
height: Value(asset.height), height: Value(asset.height),
isEdited: Value(asset.isEdited),
); );
batch.insert( batch.insert(
@@ -70,6 +70,7 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
durationInSeconds: row.durationInSeconds, durationInSeconds: row.durationInSeconds,
livePhotoVideoId: row.livePhotoVideoId, livePhotoVideoId: row.livePhotoVideoId,
stackId: row.stackId, stackId: row.stackId,
isEdited: row.isEdited,
) )
: LocalAsset( : LocalAsset(
id: row.localId!, id: row.localId!,
@@ -88,6 +89,7 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
latitude: row.latitude, latitude: row.latitude,
longitude: row.longitude, longitude: row.longitude,
adjustmentTime: row.adjustmentTime, adjustmentTime: row.adjustmentTime,
isEdited: row.isEdited,
), ),
) )
.get(); .get();
@@ -118,6 +118,7 @@ class _AssetPropertiesSectionState extends ConsumerState<_AssetPropertiesSection
), ),
_PropertyItem(label: 'Is Favorite', value: asset.isFavorite.toString()), _PropertyItem(label: 'Is Favorite', value: asset.isFavorite.toString()),
_PropertyItem(label: 'Live Photo Video ID', value: asset.livePhotoVideoId), _PropertyItem(label: 'Live Photo Video ID', value: asset.livePhotoVideoId),
_PropertyItem(label: 'Is Edited', value: asset.isEdited.toString()),
]); ]);
} }
@@ -167,7 +167,7 @@ class _PlaceTile extends StatelessWidget {
child: SizedBox( child: SizedBox(
width: 80, width: 80,
height: 80, height: 80,
child: Thumbnail.remote(remoteId: place.$2, fit: BoxFit.cover), child: Thumbnail.remote(remoteId: place.$2, fit: BoxFit.cover, thumbhash: ""),
), ),
), ),
); );
@@ -14,14 +14,15 @@ import 'package:immich_mobile/models/albums/album_search.model.dart';
import 'package:immich_mobile/presentation/widgets/album/album_tile.dart'; import 'package:immich_mobile/presentation/widgets/album/album_tile.dart';
import 'package:immich_mobile/presentation/widgets/album/new_album_name_modal.widget.dart'; import 'package:immich_mobile/presentation/widgets/album/new_album_name_modal.widget.dart';
import 'package:immich_mobile/presentation/widgets/images/thumbnail.widget.dart'; import 'package:immich_mobile/presentation/widgets/images/thumbnail.widget.dart';
import 'package:immich_mobile/providers/album/album_sort_by_options.provider.dart';
import 'package:immich_mobile/providers/app_settings.provider.dart';
import 'package:immich_mobile/providers/infrastructure/album.provider.dart'; import 'package:immich_mobile/providers/infrastructure/album.provider.dart';
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
import 'package:immich_mobile/providers/infrastructure/asset_viewer/current_asset.provider.dart'; import 'package:immich_mobile/providers/infrastructure/asset_viewer/current_asset.provider.dart';
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
import 'package:immich_mobile/providers/user.provider.dart'; import 'package:immich_mobile/providers/user.provider.dart';
import 'package:immich_mobile/providers/album/album_sort_by_options.provider.dart';
import 'package:immich_mobile/providers/app_settings.provider.dart';
import 'package:immich_mobile/services/app_settings.service.dart';
import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/services/app_settings.service.dart';
import 'package:immich_mobile/utils/album_filter.utils.dart'; import 'package:immich_mobile/utils/album_filter.utils.dart';
import 'package:immich_mobile/widgets/common/confirm_dialog.dart'; import 'package:immich_mobile/widgets/common/confirm_dialog.dart';
import 'package:immich_mobile/widgets/common/immich_toast.dart'; import 'package:immich_mobile/widgets/common/immich_toast.dart';
@@ -666,6 +667,8 @@ class _GridAlbumCard extends ConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final albumThumbnailAsset = ref.read(assetServiceProvider).getRemoteAsset(album.thumbnailAssetId ?? "");
return GestureDetector( return GestureDetector(
onTap: () => onAlbumSelected(album), onTap: () => onAlbumSelected(album),
child: Card( child: Card(
@@ -684,12 +687,22 @@ class _GridAlbumCard extends ConsumerWidget {
borderRadius: const BorderRadius.vertical(top: Radius.circular(15)), borderRadius: const BorderRadius.vertical(top: Radius.circular(15)),
child: SizedBox( child: SizedBox(
width: double.infinity, width: double.infinity,
child: album.thumbnailAssetId != null child: FutureBuilder(
? Thumbnail.remote(remoteId: album.thumbnailAssetId!) future: albumThumbnailAsset,
: Container( builder: (context, snapshot) {
color: context.colorScheme.surfaceContainerHighest, if (snapshot.hasData && snapshot.data != null) {
child: const Icon(Icons.photo_album_rounded, size: 40, color: Colors.grey), return Thumbnail.remote(
), remoteId: album.thumbnailAssetId!,
thumbhash: snapshot.data!.thumbHash ?? "",
);
}
return Container(
color: context.colorScheme.surfaceContainerHighest,
child: const Icon(Icons.photo_album_rounded, size: 40, color: Colors.grey),
);
},
),
), ),
), ),
), ),
@@ -1,12 +1,14 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/album/album.model.dart'; import 'package:immich_mobile/domain/models/album/album.model.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/theme_extensions.dart'; import 'package:immich_mobile/extensions/theme_extensions.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/pages/common/large_leading_tile.dart'; import 'package:immich_mobile/pages/common/large_leading_tile.dart';
import 'package:immich_mobile/presentation/widgets/images/thumbnail.widget.dart'; import 'package:immich_mobile/presentation/widgets/images/thumbnail.widget.dart';
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
class AlbumTile extends StatelessWidget { class AlbumTile extends ConsumerWidget {
const AlbumTile({super.key, required this.album, required this.isOwner, this.onAlbumSelected}); const AlbumTile({super.key, required this.album, required this.isOwner, this.onAlbumSelected});
final RemoteAlbum album; final RemoteAlbum album;
@@ -14,7 +16,9 @@ class AlbumTile extends StatelessWidget {
final Function(RemoteAlbum)? onAlbumSelected; final Function(RemoteAlbum)? onAlbumSelected;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context, WidgetRef ref) {
final albumThumbnailAsset = ref.read(assetServiceProvider).getRemoteAsset(album.thumbnailAssetId ?? "");
return LargeLeadingTile( return LargeLeadingTile(
title: Text( title: Text(
album.name, album.name,
@@ -29,23 +33,35 @@ class AlbumTile extends StatelessWidget {
), ),
onTap: () => onAlbumSelected?.call(album), onTap: () => onAlbumSelected?.call(album),
leadingPadding: const EdgeInsets.only(right: 16), leadingPadding: const EdgeInsets.only(right: 16),
leading: album.thumbnailAssetId != null leading: FutureBuilder(
? ClipRRect( future: albumThumbnailAsset,
borderRadius: const BorderRadius.all(Radius.circular(15)), builder: (context, snapshot) {
child: SizedBox(width: 80, height: 80, child: Thumbnail.remote(remoteId: album.thumbnailAssetId!)), return snapshot.hasData && snapshot.data != null
) ? ClipRRect(
: SizedBox( borderRadius: const BorderRadius.all(Radius.circular(15)),
width: 80, child: SizedBox(
height: 80, width: 80,
child: Container( height: 80,
decoration: BoxDecoration( child: Thumbnail.remote(
color: context.colorScheme.surfaceContainer, remoteId: album.thumbnailAssetId!,
borderRadius: const BorderRadius.all(Radius.circular(16)), thumbhash: snapshot.data!.thumbHash ?? "",
border: Border.all(color: context.colorScheme.outline.withAlpha(50), width: 1), ),
), ),
child: const Icon(Icons.photo_album_rounded, size: 24, color: Colors.grey), )
), : SizedBox(
), width: 80,
height: 80,
child: Container(
decoration: BoxDecoration(
color: context.colorScheme.surfaceContainer,
borderRadius: const BorderRadius.all(Radius.circular(16)),
border: Border.all(color: context.colorScheme.outline.withAlpha(50), width: 1),
),
child: const Icon(Icons.photo_album_rounded, size: 24, color: Colors.grey),
),
);
},
),
); );
} }
} }
@@ -112,14 +112,17 @@ ImageProvider getFullImageProvider(BaseAsset asset, {Size size = const Size(1080
provider = LocalFullImageProvider(id: id, size: size, assetType: asset.type); provider = LocalFullImageProvider(id: id, size: size, assetType: asset.type);
} else { } else {
final String assetId; final String assetId;
final String thumbhash;
if (asset is LocalAsset && asset.hasRemote) { if (asset is LocalAsset && asset.hasRemote) {
assetId = asset.remoteId!; assetId = asset.remoteId!;
thumbhash = "";
} else if (asset is RemoteAsset) { } else if (asset is RemoteAsset) {
assetId = asset.id; assetId = asset.id;
thumbhash = asset.thumbHash ?? "";
} else { } else {
throw ArgumentError("Unsupported asset type: ${asset.runtimeType}"); throw ArgumentError("Unsupported asset type: ${asset.runtimeType}");
} }
provider = RemoteFullImageProvider(assetId: assetId); provider = RemoteFullImageProvider(assetId: assetId, thumbhash: thumbhash);
} }
return provider; return provider;
@@ -132,8 +135,9 @@ ImageProvider? getThumbnailImageProvider(BaseAsset asset, {Size size = kThumbnai
} }
final assetId = asset is RemoteAsset ? asset.id : (asset as LocalAsset).remoteId; final assetId = asset is RemoteAsset ? asset.id : (asset as LocalAsset).remoteId;
return assetId != null ? RemoteThumbProvider(assetId: assetId) : null; final thumbhash = asset is RemoteAsset ? asset.thumbHash ?? "" : "";
return assetId != null ? RemoteThumbProvider(assetId: assetId, thumbhash: thumbhash) : null;
} }
bool _shouldUseLocalAsset(BaseAsset asset) => bool _shouldUseLocalAsset(BaseAsset asset) =>
asset.hasLocal && (!asset.hasRemote || !AppSetting.get(Setting.preferRemoteImage)); asset.hasLocal && (!asset.hasRemote || !AppSetting.get(Setting.preferRemoteImage)) && !asset.isEdited;
@@ -16,8 +16,9 @@ class RemoteThumbProvider extends CancellableImageProvider<RemoteThumbProvider>
with CancellableImageProviderMixin<RemoteThumbProvider> { with CancellableImageProviderMixin<RemoteThumbProvider> {
static final cacheManager = RemoteThumbnailCacheManager(); static final cacheManager = RemoteThumbnailCacheManager();
final String assetId; final String assetId;
final String thumbhash;
RemoteThumbProvider({required this.assetId}); RemoteThumbProvider({required this.assetId, required this.thumbhash});
@override @override
Future<RemoteThumbProvider> obtainKey(ImageConfiguration configuration) { Future<RemoteThumbProvider> obtainKey(ImageConfiguration configuration) {
@@ -38,7 +39,7 @@ class RemoteThumbProvider extends CancellableImageProvider<RemoteThumbProvider>
Stream<ImageInfo> _codec(RemoteThumbProvider key, ImageDecoderCallback decode) { Stream<ImageInfo> _codec(RemoteThumbProvider key, ImageDecoderCallback decode) {
final request = this.request = RemoteImageRequest( final request = this.request = RemoteImageRequest(
uri: getThumbnailUrlForRemoteId(key.assetId), uri: getThumbnailUrlForRemoteId(key.assetId, thumbhash: key.thumbhash),
headers: ApiService.getRequestHeaders(), headers: ApiService.getRequestHeaders(),
cacheManager: cacheManager, cacheManager: cacheManager,
); );
@@ -49,22 +50,23 @@ class RemoteThumbProvider extends CancellableImageProvider<RemoteThumbProvider>
bool operator ==(Object other) { bool operator ==(Object other) {
if (identical(this, other)) return true; if (identical(this, other)) return true;
if (other is RemoteThumbProvider) { if (other is RemoteThumbProvider) {
return assetId == other.assetId; return assetId == other.assetId && thumbhash == other.thumbhash;
} }
return false; return false;
} }
@override @override
int get hashCode => assetId.hashCode; int get hashCode => assetId.hashCode ^ thumbhash.hashCode;
} }
class RemoteFullImageProvider extends CancellableImageProvider<RemoteFullImageProvider> class RemoteFullImageProvider extends CancellableImageProvider<RemoteFullImageProvider>
with CancellableImageProviderMixin<RemoteFullImageProvider> { with CancellableImageProviderMixin<RemoteFullImageProvider> {
static final cacheManager = RemoteThumbnailCacheManager(); static final cacheManager = RemoteThumbnailCacheManager();
final String assetId; final String assetId;
final String thumbhash;
RemoteFullImageProvider({required this.assetId}); RemoteFullImageProvider({required this.assetId, required this.thumbhash});
@override @override
Future<RemoteFullImageProvider> obtainKey(ImageConfiguration configuration) { Future<RemoteFullImageProvider> obtainKey(ImageConfiguration configuration) {
@@ -75,7 +77,7 @@ class RemoteFullImageProvider extends CancellableImageProvider<RemoteFullImagePr
ImageStreamCompleter loadImage(RemoteFullImageProvider key, ImageDecoderCallback decode) { ImageStreamCompleter loadImage(RemoteFullImageProvider key, ImageDecoderCallback decode) {
return OneFramePlaceholderImageStreamCompleter( return OneFramePlaceholderImageStreamCompleter(
_codec(key, decode), _codec(key, decode),
initialImage: getInitialImage(RemoteThumbProvider(assetId: key.assetId)), initialImage: getInitialImage(RemoteThumbProvider(assetId: key.assetId, thumbhash: key.thumbhash)),
informationCollector: () => <DiagnosticsNode>[ informationCollector: () => <DiagnosticsNode>[
DiagnosticsProperty<ImageProvider>('Image provider', this), DiagnosticsProperty<ImageProvider>('Image provider', this),
DiagnosticsProperty<String>('Asset Id', key.assetId), DiagnosticsProperty<String>('Asset Id', key.assetId),
@@ -94,7 +96,7 @@ class RemoteFullImageProvider extends CancellableImageProvider<RemoteFullImagePr
final headers = ApiService.getRequestHeaders(); final headers = ApiService.getRequestHeaders();
final request = this.request = RemoteImageRequest( final request = this.request = RemoteImageRequest(
uri: getThumbnailUrlForRemoteId(key.assetId, type: AssetMediaSize.preview), uri: getThumbnailUrlForRemoteId(key.assetId, type: AssetMediaSize.preview, thumbhash: key.thumbhash),
headers: headers, headers: headers,
cacheManager: cacheManager, cacheManager: cacheManager,
); );
@@ -115,12 +117,12 @@ class RemoteFullImageProvider extends CancellableImageProvider<RemoteFullImagePr
bool operator ==(Object other) { bool operator ==(Object other) {
if (identical(this, other)) return true; if (identical(this, other)) return true;
if (other is RemoteFullImageProvider) { if (other is RemoteFullImageProvider) {
return assetId == other.assetId; return assetId == other.assetId && thumbhash == other.thumbhash;
} }
return false; return false;
} }
@override @override
int get hashCode => assetId.hashCode; int get hashCode => assetId.hashCode ^ thumbhash.hashCode;
} }
@@ -21,9 +21,14 @@ class Thumbnail extends StatefulWidget {
const Thumbnail({this.imageProvider, this.fit = BoxFit.cover, this.thumbhashProvider, super.key}); const Thumbnail({this.imageProvider, this.fit = BoxFit.cover, this.thumbhashProvider, super.key});
Thumbnail.remote({required String remoteId, this.fit = BoxFit.cover, Size size = kThumbnailResolution, super.key}) Thumbnail.remote({
: imageProvider = RemoteThumbProvider(assetId: remoteId), required String remoteId,
thumbhashProvider = null; required String thumbhash,
this.fit = BoxFit.cover,
Size size = kThumbnailResolution,
super.key,
}) : imageProvider = RemoteThumbProvider(assetId: remoteId, thumbhash: thumbhash),
thumbhashProvider = null;
Thumbnail.fromAsset({ Thumbnail.fromAsset({
required BaseAsset? asset, required BaseAsset? asset,
@@ -60,7 +60,11 @@ class DriftMemoryCard extends ConsumerWidget {
child: SizedBox( child: SizedBox(
width: 205, width: 205,
height: 200, height: 200,
child: Thumbnail.remote(remoteId: memory.assets[0].id, fit: BoxFit.cover), child: Thumbnail.remote(
remoteId: memory.assets[0].id,
thumbhash: memory.assets[0].thumbHash ?? "",
fit: BoxFit.cover,
),
), ),
), ),
Positioned( Positioned(
+1
View File
@@ -69,6 +69,7 @@ class CastNotifier extends StateNotifier<CastManagerState> {
: AssetType.other, : AssetType.other,
createdAt: asset.fileCreatedAt, createdAt: asset.fileCreatedAt,
updatedAt: asset.updatedAt, updatedAt: asset.updatedAt,
isEdited: false,
); );
_gCastService.loadMedia(remoteAsset, reload); _gCastService.loadMedia(remoteAsset, reload);
@@ -144,6 +144,7 @@ class WebsocketNotifier extends StateNotifier<WebsocketState> {
socket.on('on_asset_hidden', _handleOnAssetHidden); socket.on('on_asset_hidden', _handleOnAssetHidden);
} else { } else {
socket.on('AssetUploadReadyV1', _handleSyncAssetUploadReady); socket.on('AssetUploadReadyV1', _handleSyncAssetUploadReady);
socket.on('AssetEditReadyV1', _handleSyncAssetEditReady);
} }
socket.on('on_config_update', _handleOnConfigUpdate); socket.on('on_config_update', _handleOnConfigUpdate);
@@ -192,10 +193,12 @@ class WebsocketNotifier extends StateNotifier<WebsocketState> {
void stopListeningToBetaEvents() { void stopListeningToBetaEvents() {
state.socket?.off('AssetUploadReadyV1'); state.socket?.off('AssetUploadReadyV1');
state.socket?.off('AssetEditReadyV1');
} }
void startListeningToBetaEvents() { void startListeningToBetaEvents() {
state.socket?.on('AssetUploadReadyV1', _handleSyncAssetUploadReady); state.socket?.on('AssetUploadReadyV1', _handleSyncAssetUploadReady);
state.socket?.on('AssetEditReadyV1', _handleSyncAssetEditReady);
} }
void listenUploadEvent() { void listenUploadEvent() {
@@ -315,6 +318,10 @@ class WebsocketNotifier extends StateNotifier<WebsocketState> {
_batchDebouncer.run(_processBatchedAssetUploadReady); _batchDebouncer.run(_processBatchedAssetUploadReady);
} }
void _handleSyncAssetEditReady(dynamic data) {
unawaited(_ref.read(backgroundSyncProvider).syncWebsocketEditBatch([data]));
}
void _processBatchedAssetUploadReady() { void _processBatchedAssetUploadReady() {
if (_batchedAssetUploadReady.isEmpty) { if (_batchedAssetUploadReady.isEmpty) {
return; return;
@@ -25,6 +25,7 @@ class FileMediaRepository {
type: AssetType.image, type: AssetType.image,
createdAt: entity.createDateTime, createdAt: entity.createDateTime,
updatedAt: entity.modifiedDateTime, updatedAt: entity.modifiedDateTime,
isEdited: false,
); );
} }
+3 -1
View File
@@ -50,8 +50,10 @@ String getThumbnailUrlForRemoteId(
final String id, { final String id, {
AssetMediaSize type = AssetMediaSize.thumbnail, AssetMediaSize type = AssetMediaSize.thumbnail,
bool edited = true, bool edited = true,
String? thumbhash,
}) { }) {
return '${Store.get(StoreKey.serverEndpoint)}/assets/$id/thumbnail?size=${type.value}&edited=$edited'; final url = '${Store.get(StoreKey.serverEndpoint)}/assets/$id/thumbnail?size=${type.value}&edited=$edited';
return thumbhash != null ? '$url&c=${Uri.encodeComponent(thumbhash)}' : url;
} }
String getPlaybackUrlForRemoteId(final String id) { String getPlaybackUrlForRemoteId(final String id) {
+1 -1
View File
@@ -49,7 +49,7 @@ dynamic upgradeDto(dynamic value, String targetType) {
} }
case 'SyncAssetV1': case 'SyncAssetV1':
if (value is Map) { if (value is Map) {
addDefault(value, 'editCount', 0); addDefault(value, 'isEdited', false);
} }
case 'ServerFeaturesDto': case 'ServerFeaturesDto':
if (value is Map) { if (value is Map) {
+9 -9
View File
@@ -16,11 +16,11 @@ class SyncAssetV1 {
required this.checksum, required this.checksum,
required this.deletedAt, required this.deletedAt,
required this.duration, required this.duration,
required this.editCount,
required this.fileCreatedAt, required this.fileCreatedAt,
required this.fileModifiedAt, required this.fileModifiedAt,
required this.height, required this.height,
required this.id, required this.id,
required this.isEdited,
required this.isFavorite, required this.isFavorite,
required this.libraryId, required this.libraryId,
required this.livePhotoVideoId, required this.livePhotoVideoId,
@@ -40,8 +40,6 @@ class SyncAssetV1 {
String? duration; String? duration;
int editCount;
DateTime? fileCreatedAt; DateTime? fileCreatedAt;
DateTime? fileModifiedAt; DateTime? fileModifiedAt;
@@ -50,6 +48,8 @@ class SyncAssetV1 {
String id; String id;
bool isEdited;
bool isFavorite; bool isFavorite;
String? libraryId; String? libraryId;
@@ -77,11 +77,11 @@ class SyncAssetV1 {
other.checksum == checksum && other.checksum == checksum &&
other.deletedAt == deletedAt && other.deletedAt == deletedAt &&
other.duration == duration && other.duration == duration &&
other.editCount == editCount &&
other.fileCreatedAt == fileCreatedAt && other.fileCreatedAt == fileCreatedAt &&
other.fileModifiedAt == fileModifiedAt && other.fileModifiedAt == fileModifiedAt &&
other.height == height && other.height == height &&
other.id == id && other.id == id &&
other.isEdited == isEdited &&
other.isFavorite == isFavorite && other.isFavorite == isFavorite &&
other.libraryId == libraryId && other.libraryId == libraryId &&
other.livePhotoVideoId == livePhotoVideoId && other.livePhotoVideoId == livePhotoVideoId &&
@@ -100,11 +100,11 @@ class SyncAssetV1 {
(checksum.hashCode) + (checksum.hashCode) +
(deletedAt == null ? 0 : deletedAt!.hashCode) + (deletedAt == null ? 0 : deletedAt!.hashCode) +
(duration == null ? 0 : duration!.hashCode) + (duration == null ? 0 : duration!.hashCode) +
(editCount.hashCode) +
(fileCreatedAt == null ? 0 : fileCreatedAt!.hashCode) + (fileCreatedAt == null ? 0 : fileCreatedAt!.hashCode) +
(fileModifiedAt == null ? 0 : fileModifiedAt!.hashCode) + (fileModifiedAt == null ? 0 : fileModifiedAt!.hashCode) +
(height == null ? 0 : height!.hashCode) + (height == null ? 0 : height!.hashCode) +
(id.hashCode) + (id.hashCode) +
(isEdited.hashCode) +
(isFavorite.hashCode) + (isFavorite.hashCode) +
(libraryId == null ? 0 : libraryId!.hashCode) + (libraryId == null ? 0 : libraryId!.hashCode) +
(livePhotoVideoId == null ? 0 : livePhotoVideoId!.hashCode) + (livePhotoVideoId == null ? 0 : livePhotoVideoId!.hashCode) +
@@ -118,7 +118,7 @@ class SyncAssetV1 {
(width == null ? 0 : width!.hashCode); (width == null ? 0 : width!.hashCode);
@override @override
String toString() => 'SyncAssetV1[checksum=$checksum, deletedAt=$deletedAt, duration=$duration, editCount=$editCount, fileCreatedAt=$fileCreatedAt, fileModifiedAt=$fileModifiedAt, height=$height, id=$id, isFavorite=$isFavorite, libraryId=$libraryId, livePhotoVideoId=$livePhotoVideoId, localDateTime=$localDateTime, originalFileName=$originalFileName, ownerId=$ownerId, stackId=$stackId, thumbhash=$thumbhash, type=$type, visibility=$visibility, width=$width]'; String toString() => 'SyncAssetV1[checksum=$checksum, deletedAt=$deletedAt, duration=$duration, fileCreatedAt=$fileCreatedAt, fileModifiedAt=$fileModifiedAt, height=$height, id=$id, isEdited=$isEdited, isFavorite=$isFavorite, libraryId=$libraryId, livePhotoVideoId=$livePhotoVideoId, localDateTime=$localDateTime, originalFileName=$originalFileName, ownerId=$ownerId, stackId=$stackId, thumbhash=$thumbhash, type=$type, visibility=$visibility, width=$width]';
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
final json = <String, dynamic>{}; final json = <String, dynamic>{};
@@ -133,7 +133,6 @@ class SyncAssetV1 {
} else { } else {
// json[r'duration'] = null; // json[r'duration'] = null;
} }
json[r'editCount'] = this.editCount;
if (this.fileCreatedAt != null) { if (this.fileCreatedAt != null) {
json[r'fileCreatedAt'] = this.fileCreatedAt!.toUtc().toIso8601String(); json[r'fileCreatedAt'] = this.fileCreatedAt!.toUtc().toIso8601String();
} else { } else {
@@ -150,6 +149,7 @@ class SyncAssetV1 {
// json[r'height'] = null; // json[r'height'] = null;
} }
json[r'id'] = this.id; json[r'id'] = this.id;
json[r'isEdited'] = this.isEdited;
json[r'isFavorite'] = this.isFavorite; json[r'isFavorite'] = this.isFavorite;
if (this.libraryId != null) { if (this.libraryId != null) {
json[r'libraryId'] = this.libraryId; json[r'libraryId'] = this.libraryId;
@@ -200,11 +200,11 @@ class SyncAssetV1 {
checksum: mapValueOfType<String>(json, r'checksum')!, checksum: mapValueOfType<String>(json, r'checksum')!,
deletedAt: mapDateTime(json, r'deletedAt', r''), deletedAt: mapDateTime(json, r'deletedAt', r''),
duration: mapValueOfType<String>(json, r'duration'), duration: mapValueOfType<String>(json, r'duration'),
editCount: mapValueOfType<int>(json, r'editCount')!,
fileCreatedAt: mapDateTime(json, r'fileCreatedAt', r''), fileCreatedAt: mapDateTime(json, r'fileCreatedAt', r''),
fileModifiedAt: mapDateTime(json, r'fileModifiedAt', r''), fileModifiedAt: mapDateTime(json, r'fileModifiedAt', r''),
height: mapValueOfType<int>(json, r'height'), height: mapValueOfType<int>(json, r'height'),
id: mapValueOfType<String>(json, r'id')!, id: mapValueOfType<String>(json, r'id')!,
isEdited: mapValueOfType<bool>(json, r'isEdited')!,
isFavorite: mapValueOfType<bool>(json, r'isFavorite')!, isFavorite: mapValueOfType<bool>(json, r'isFavorite')!,
libraryId: mapValueOfType<String>(json, r'libraryId'), libraryId: mapValueOfType<String>(json, r'libraryId'),
livePhotoVideoId: mapValueOfType<String>(json, r'livePhotoVideoId'), livePhotoVideoId: mapValueOfType<String>(json, r'livePhotoVideoId'),
@@ -266,11 +266,11 @@ class SyncAssetV1 {
'checksum', 'checksum',
'deletedAt', 'deletedAt',
'duration', 'duration',
'editCount',
'fileCreatedAt', 'fileCreatedAt',
'fileModifiedAt', 'fileModifiedAt',
'height', 'height',
'id', 'id',
'isEdited',
'isFavorite', 'isFavorite',
'libraryId', 'libraryId',
'livePhotoVideoId', 'livePhotoVideoId',
@@ -44,7 +44,7 @@ SyncAssetV1 _createAsset({
livePhotoVideoId: null, livePhotoVideoId: null,
stackId: null, stackId: null,
thumbhash: null, thumbhash: null,
editCount: 0, isEdited: false,
); );
} }
+4
View File
@@ -19,6 +19,7 @@ import 'schema_v13.dart' as v13;
import 'schema_v14.dart' as v14; import 'schema_v14.dart' as v14;
import 'schema_v15.dart' as v15; import 'schema_v15.dart' as v15;
import 'schema_v16.dart' as v16; import 'schema_v16.dart' as v16;
import 'schema_v17.dart' as v17;
class GeneratedHelper implements SchemaInstantiationHelper { class GeneratedHelper implements SchemaInstantiationHelper {
@override @override
@@ -56,6 +57,8 @@ class GeneratedHelper implements SchemaInstantiationHelper {
return v15.DatabaseAtV15(db); return v15.DatabaseAtV15(db);
case 16: case 16:
return v16.DatabaseAtV16(db); return v16.DatabaseAtV16(db);
case 17:
return v17.DatabaseAtV17(db);
default: default:
throw MissingSchemaException(version, versions); throw MissingSchemaException(version, versions);
} }
@@ -78,5 +81,6 @@ class GeneratedHelper implements SchemaInstantiationHelper {
14, 14,
15, 15,
16, 16,
17,
]; ];
} }
File diff suppressed because it is too large Load Diff
+2
View File
@@ -64,6 +64,7 @@ abstract final class LocalAssetStub {
type: AssetType.image, type: AssetType.image,
createdAt: DateTime(2025), createdAt: DateTime(2025),
updatedAt: DateTime(2025, 2), updatedAt: DateTime(2025, 2),
isEdited: false,
); );
static final image2 = LocalAsset( static final image2 = LocalAsset(
@@ -72,5 +73,6 @@ abstract final class LocalAssetStub {
type: AssetType.image, type: AssetType.image,
createdAt: DateTime(2000), createdAt: DateTime(2000),
updatedAt: DateTime(20021), updatedAt: DateTime(20021),
isEdited: false,
); );
} }
+1 -1
View File
@@ -128,7 +128,7 @@ abstract final class SyncStreamStub {
visibility: AssetVisibility.timeline, visibility: AssetVisibility.timeline,
width: null, width: null,
height: null, height: null,
editCount: 0, isEdited: false,
), ),
ack: ack, ack: ack,
); );
@@ -194,6 +194,7 @@ void main() {
latitude: 37.7749, latitude: 37.7749,
longitude: -122.4194, longitude: -122.4194,
adjustmentTime: DateTime(2026, 1, 2), adjustmentTime: DateTime(2026, 1, 2),
isEdited: false,
); );
final mockEntity = MockAssetEntity(); final mockEntity = MockAssetEntity();
@@ -242,6 +243,7 @@ void main() {
cloudId: 'cloud-id-123', cloudId: 'cloud-id-123',
latitude: 37.7749, latitude: 37.7749,
longitude: -122.4194, longitude: -122.4194,
isEdited: false,
); );
final mockEntity = MockAssetEntity(); final mockEntity = MockAssetEntity();
@@ -279,6 +281,7 @@ void main() {
createdAt: DateTime(2025, 1, 1), createdAt: DateTime(2025, 1, 1),
updatedAt: DateTime(2025, 1, 2), updatedAt: DateTime(2025, 1, 2),
cloudId: null, // No cloudId cloudId: null, // No cloudId
isEdited: false,
); );
final mockEntity = MockAssetEntity(); final mockEntity = MockAssetEntity();
@@ -320,6 +323,7 @@ void main() {
cloudId: 'cloud-id-livephoto', cloudId: 'cloud-id-livephoto',
latitude: 37.7749, latitude: 37.7749,
longitude: -122.4194, longitude: -122.4194,
isEdited: false,
); );
final mockEntity = MockAssetEntity(); final mockEntity = MockAssetEntity();
+2
View File
@@ -131,6 +131,7 @@ abstract final class TestUtils {
isFavorite: false, isFavorite: false,
width: width, width: width,
height: height, height: height,
isEdited: false,
); );
} }
@@ -154,6 +155,7 @@ abstract final class TestUtils {
width: width, width: width,
height: height, height: height,
orientation: orientation, orientation: orientation,
isEdited: false,
); );
} }
} }
@@ -27,6 +27,7 @@ class MediumFactory {
type: type ?? AssetType.image, type: type ?? AssetType.image,
createdAt: createdAt ?? DateTime.fromMillisecondsSinceEpoch(random.nextInt(1000000000)), createdAt: createdAt ?? DateTime.fromMillisecondsSinceEpoch(random.nextInt(1000000000)),
updatedAt: updatedAt ?? DateTime.fromMillisecondsSinceEpoch(random.nextInt(1000000000)), updatedAt: updatedAt ?? DateTime.fromMillisecondsSinceEpoch(random.nextInt(1000000000)),
isEdited: false,
); );
} }
@@ -23,6 +23,7 @@ LocalAsset createLocalAsset({
createdAt: createdAt ?? DateTime.now(), createdAt: createdAt ?? DateTime.now(),
updatedAt: updatedAt ?? DateTime.now(), updatedAt: updatedAt ?? DateTime.now(),
isFavorite: isFavorite, isFavorite: isFavorite,
isEdited: false,
); );
} }
@@ -45,6 +46,7 @@ RemoteAsset createRemoteAsset({
createdAt: createdAt ?? DateTime.now(), createdAt: createdAt ?? DateTime.now(),
updatedAt: updatedAt ?? DateTime.now(), updatedAt: updatedAt ?? DateTime.now(),
isFavorite: isFavorite, isFavorite: isFavorite,
isEdited: false,
); );
} }
+4 -4
View File
@@ -21291,9 +21291,6 @@
"nullable": true, "nullable": true,
"type": "string" "type": "string"
}, },
"editCount": {
"type": "integer"
},
"fileCreatedAt": { "fileCreatedAt": {
"format": "date-time", "format": "date-time",
"nullable": true, "nullable": true,
@@ -21311,6 +21308,9 @@
"id": { "id": {
"type": "string" "type": "string"
}, },
"isEdited": {
"type": "boolean"
},
"isFavorite": { "isFavorite": {
"type": "boolean" "type": "boolean"
}, },
@@ -21364,11 +21364,11 @@
"checksum", "checksum",
"deletedAt", "deletedAt",
"duration", "duration",
"editCount",
"fileCreatedAt", "fileCreatedAt",
"fileModifiedAt", "fileModifiedAt",
"height", "height",
"id", "id",
"isEdited",
"isFavorite", "isFavorite",
"libraryId", "libraryId",
"livePhotoVideoId", "livePhotoVideoId",
+1 -1
View File
@@ -395,7 +395,7 @@ export const columns = {
'asset.libraryId', 'asset.libraryId',
'asset.width', 'asset.width',
'asset.height', 'asset.height',
'asset.editCount', 'asset.isEdited',
], ],
syncAlbumUser: ['album_user.albumId as albumId', 'album_user.userId as userId', 'album_user.role'], syncAlbumUser: ['album_user.albumId as albumId', 'album_user.userId as userId', 'album_user.role'],
syncStack: ['stack.id', 'stack.createdAt', 'stack.updatedAt', 'stack.primaryAssetId', 'stack.ownerId'], syncStack: ['stack.id', 'stack.createdAt', 'stack.updatedAt', 'stack.primaryAssetId', 'stack.ownerId'],
+2 -2
View File
@@ -139,7 +139,7 @@ export type MapAsset = {
type: AssetType; type: AssetType;
width: number | null; width: number | null;
height: number | null; height: number | null;
editCount: number; isEdited: boolean;
}; };
export class AssetStackResponseDto { export class AssetStackResponseDto {
@@ -248,6 +248,6 @@ export function mapAsset(entity: MapAsset, options: AssetMapOptions = {}): Asset
resized: true, resized: true,
width: entity.width, width: entity.width,
height: entity.height, height: entity.height,
isEdited: entity.editCount > 0, isEdited: entity.isEdited,
}; };
} }
+2 -2
View File
@@ -121,8 +121,8 @@ export class SyncAssetV1 {
width!: number | null; width!: number | null;
@ApiProperty({ type: 'integer' }) @ApiProperty({ type: 'integer' })
height!: number | null; height!: number | null;
@ApiProperty({ type: 'integer' }) @ApiProperty({ type: 'boolean' })
editCount!: number; isEdited!: boolean;
} }
@ExtraModel() @ExtraModel()
+6 -6
View File
@@ -71,7 +71,7 @@ select
"asset"."libraryId", "asset"."libraryId",
"asset"."width", "asset"."width",
"asset"."height", "asset"."height",
"asset"."editCount", "asset"."isEdited",
"album_asset"."updateId" "album_asset"."updateId"
from from
"album_asset" as "album_asset" "album_asset" as "album_asset"
@@ -104,7 +104,7 @@ select
"asset"."libraryId", "asset"."libraryId",
"asset"."width", "asset"."width",
"asset"."height", "asset"."height",
"asset"."editCount", "asset"."isEdited",
"asset"."updateId" "asset"."updateId"
from from
"asset" as "asset" "asset" as "asset"
@@ -143,7 +143,7 @@ select
"asset"."libraryId", "asset"."libraryId",
"asset"."width", "asset"."width",
"asset"."height", "asset"."height",
"asset"."editCount" "asset"."isEdited"
from from
"album_asset" as "album_asset" "album_asset" as "album_asset"
inner join "asset" on "asset"."id" = "album_asset"."assetId" inner join "asset" on "asset"."id" = "album_asset"."assetId"
@@ -459,7 +459,7 @@ select
"asset"."libraryId", "asset"."libraryId",
"asset"."width", "asset"."width",
"asset"."height", "asset"."height",
"asset"."editCount", "asset"."isEdited",
"asset"."updateId" "asset"."updateId"
from from
"asset" as "asset" "asset" as "asset"
@@ -755,7 +755,7 @@ select
"asset"."libraryId", "asset"."libraryId",
"asset"."width", "asset"."width",
"asset"."height", "asset"."height",
"asset"."editCount", "asset"."isEdited",
"asset"."updateId" "asset"."updateId"
from from
"asset" as "asset" "asset" as "asset"
@@ -807,7 +807,7 @@ select
"asset"."libraryId", "asset"."libraryId",
"asset"."width", "asset"."width",
"asset"."height", "asset"."height",
"asset"."editCount", "asset"."isEdited",
"asset"."updateId" "asset"."updateId"
from from
"asset" as "asset" "asset" as "asset"
@@ -37,7 +37,7 @@ export interface ClientEventMap {
AssetUploadReadyV1: [{ asset: SyncAssetV1; exif: SyncAssetExifV1 }]; AssetUploadReadyV1: [{ asset: SyncAssetV1; exif: SyncAssetExifV1 }];
AppRestartV1: [AppRestartEvent]; AppRestartV1: [AppRestartEvent];
AssetEditReadyV1: [{ assetId: string }]; AssetEditReadyV1: [{ asset: SyncAssetV1 }];
} }
export type AuthFn = (client: Socket) => Promise<AuthDto>; export type AuthFn = (client: Socket) => Promise<AuthDto>;
+7 -4
View File
@@ -263,8 +263,9 @@ export const asset_edit_insert = registerFunction({
body: ` body: `
BEGIN BEGIN
UPDATE asset UPDATE asset
SET "editCount" = "editCount" + 1 SET "isEdited" = true
WHERE "id" = NEW."assetId"; FROM inserted_edit
WHERE asset.id = inserted_edit."assetId" AND NOT asset."isEdited";
RETURN NULL; RETURN NULL;
END END
`, `,
@@ -277,8 +278,10 @@ export const asset_edit_delete = registerFunction({
body: ` body: `
BEGIN BEGIN
UPDATE asset UPDATE asset
SET "editCount" = "editCount" - 1 SET "isEdited" = false
WHERE "id" = OLD."assetId"; FROM deleted_edit
WHERE asset.id = deleted_edit."assetId" AND asset."isEdited"
AND NOT EXISTS (SELECT FROM asset_edit edit WHERE edit."assetId" = asset.id);
RETURN NULL; RETURN NULL;
END END
`, `,
@@ -0,0 +1,89 @@
import { Kysely, sql } from 'kysely';
export async function up(db: Kysely<any>): Promise<void> {
await sql`CREATE OR REPLACE FUNCTION asset_edit_insert()
RETURNS TRIGGER
LANGUAGE PLPGSQL
AS $$
BEGIN
UPDATE asset
SET "isEdited" = true
FROM inserted_edit
WHERE asset.id = inserted_edit."assetId" AND NOT asset."isEdited";
RETURN NULL;
END
$$;`.execute(db);
await sql`CREATE OR REPLACE FUNCTION asset_edit_delete()
RETURNS TRIGGER
LANGUAGE PLPGSQL
AS $$
BEGIN
UPDATE asset
SET "isEdited" = false
FROM deleted_edit
WHERE asset.id = deleted_edit."assetId" AND asset."isEdited"
AND NOT EXISTS (SELECT FROM asset_edit edit WHERE edit."assetId" = asset.id);
RETURN NULL;
END
$$;`.execute(db);
await sql`ALTER TABLE "asset" ADD "isEdited" boolean NOT NULL DEFAULT false;`.execute(db);
await sql`CREATE OR REPLACE TRIGGER "asset_edit_delete"
AFTER DELETE ON "asset_edit"
REFERENCING OLD TABLE AS "deleted_edit"
FOR EACH STATEMENT
WHEN (pg_trigger_depth() = 0)
EXECUTE FUNCTION asset_edit_delete();`.execute(db);
await sql`CREATE OR REPLACE TRIGGER "asset_edit_insert"
AFTER INSERT ON "asset_edit"
REFERENCING NEW TABLE AS "inserted_edit"
FOR EACH STATEMENT
EXECUTE FUNCTION asset_edit_insert();`.execute(db);
await sql`ALTER TABLE "asset" DROP COLUMN "editCount";`.execute(db);
await sql`UPDATE "migration_overrides" SET "value" = '{"type":"function","name":"asset_edit_insert","sql":"CREATE OR REPLACE FUNCTION asset_edit_insert()\\n RETURNS TRIGGER\\n LANGUAGE PLPGSQL\\n AS $$\\n BEGIN\\n UPDATE asset\\n SET \\"isEdited\\" = true\\n FROM inserted_edit\\n WHERE asset.id = inserted_edit.\\"assetId\\" AND NOT asset.\\"isEdited\\";\\n RETURN NULL;\\n END\\n $$;"}'::jsonb WHERE "name" = 'function_asset_edit_insert';`.execute(db);
await sql`UPDATE "migration_overrides" SET "value" = '{"type":"function","name":"asset_edit_delete","sql":"CREATE OR REPLACE FUNCTION asset_edit_delete()\\n RETURNS TRIGGER\\n LANGUAGE PLPGSQL\\n AS $$\\n BEGIN\\n UPDATE asset\\n SET \\"isEdited\\" = false\\n FROM deleted_edit\\n WHERE asset.id = deleted_edit.\\"assetId\\" AND asset.\\"isEdited\\" \\n AND NOT EXISTS (SELECT FROM asset_edit edit WHERE edit.\\"assetId\\" = asset.id);\\n RETURN NULL;\\n END\\n $$;"}'::jsonb WHERE "name" = 'function_asset_edit_delete';`.execute(db);
await sql`UPDATE "migration_overrides" SET "value" = '{"type":"trigger","name":"asset_edit_delete","sql":"CREATE OR REPLACE TRIGGER \\"asset_edit_delete\\"\\n AFTER DELETE ON \\"asset_edit\\"\\n REFERENCING OLD TABLE AS \\"deleted_edit\\"\\n FOR EACH STATEMENT\\n WHEN (pg_trigger_depth() = 0)\\n EXECUTE FUNCTION asset_edit_delete();"}'::jsonb WHERE "name" = 'trigger_asset_edit_delete';`.execute(db);
await sql`UPDATE "migration_overrides" SET "value" = '{"type":"trigger","name":"asset_edit_insert","sql":"CREATE OR REPLACE TRIGGER \\"asset_edit_insert\\"\\n AFTER INSERT ON \\"asset_edit\\"\\n REFERENCING NEW TABLE AS \\"inserted_edit\\"\\n FOR EACH STATEMENT\\n EXECUTE FUNCTION asset_edit_insert();"}'::jsonb WHERE "name" = 'trigger_asset_edit_insert';`.execute(db);
}
export async function down(db: Kysely<any>): Promise<void> {
await sql`CREATE OR REPLACE FUNCTION public.asset_edit_insert()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
BEGIN
UPDATE asset
SET "editCount" = "editCount" + 1
WHERE "id" = NEW."assetId";
RETURN NULL;
END
$function$
`.execute(db);
await sql`CREATE OR REPLACE FUNCTION public.asset_edit_delete()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
BEGIN
UPDATE asset
SET "editCount" = "editCount" - 1
WHERE "id" = OLD."assetId";
RETURN NULL;
END
$function$
`.execute(db);
await sql`ALTER TABLE "asset" ADD "editCount" integer NOT NULL DEFAULT 0;`.execute(db);
await sql`CREATE OR REPLACE TRIGGER "asset_edit_delete"
AFTER DELETE ON "asset_edit"
REFERENCING OLD TABLE AS "old"
FOR EACH ROW
WHEN ((pg_trigger_depth() = 0))
EXECUTE FUNCTION asset_edit_delete();`.execute(db);
await sql`CREATE OR REPLACE TRIGGER "asset_edit_insert"
AFTER INSERT ON "asset_edit"
FOR EACH ROW
EXECUTE FUNCTION asset_edit_insert();`.execute(db);
await sql`ALTER TABLE "asset" DROP COLUMN "isEdited";`.execute(db);
await sql`UPDATE "migration_overrides" SET "value" = '{"sql":"CREATE OR REPLACE FUNCTION asset_edit_insert()\\n RETURNS TRIGGER\\n LANGUAGE PLPGSQL\\n AS $$\\n BEGIN\\n UPDATE asset\\n SET \\"editCount\\" = \\"editCount\\" + 1\\n WHERE \\"id\\" = NEW.\\"assetId\\";\\n RETURN NULL;\\n END\\n $$;","name":"asset_edit_insert","type":"function"}'::jsonb WHERE "name" = 'function_asset_edit_insert';`.execute(db);
await sql`UPDATE "migration_overrides" SET "value" = '{"sql":"CREATE OR REPLACE FUNCTION asset_edit_delete()\\n RETURNS TRIGGER\\n LANGUAGE PLPGSQL\\n AS $$\\n BEGIN\\n UPDATE asset\\n SET \\"editCount\\" = \\"editCount\\" - 1\\n WHERE \\"id\\" = OLD.\\"assetId\\";\\n RETURN NULL;\\n END\\n $$;","name":"asset_edit_delete","type":"function"}'::jsonb WHERE "name" = 'function_asset_edit_delete';`.execute(db);
await sql`UPDATE "migration_overrides" SET "value" = '{"sql":"CREATE OR REPLACE TRIGGER \\"asset_edit_delete\\"\\n AFTER DELETE ON \\"asset_edit\\"\\n REFERENCING OLD TABLE AS \\"old\\"\\n FOR EACH ROW\\n WHEN (pg_trigger_depth() = 0)\\n EXECUTE FUNCTION asset_edit_delete();","name":"asset_edit_delete","type":"trigger"}'::jsonb WHERE "name" = 'trigger_asset_edit_delete';`.execute(db);
await sql`UPDATE "migration_overrides" SET "value" = '{"sql":"CREATE OR REPLACE TRIGGER \\"asset_edit_insert\\"\\n AFTER INSERT ON \\"asset_edit\\"\\n FOR EACH ROW\\n EXECUTE FUNCTION asset_edit_insert();","name":"asset_edit_insert","type":"trigger"}'::jsonb WHERE "name" = 'trigger_asset_edit_insert';`.execute(db);
}
+3 -3
View File
@@ -12,11 +12,11 @@ import {
} from 'src/sql-tools'; } from 'src/sql-tools';
@Table('asset_edit') @Table('asset_edit')
@AfterInsertTrigger({ scope: 'row', function: asset_edit_insert }) @AfterInsertTrigger({ scope: 'statement', function: asset_edit_insert, referencingNewTableAs: 'inserted_edit' })
@AfterDeleteTrigger({ @AfterDeleteTrigger({
scope: 'row', scope: 'statement',
function: asset_edit_delete, function: asset_edit_delete,
referencingOldTableAs: 'old', referencingOldTableAs: 'deleted_edit',
when: 'pg_trigger_depth() = 0', when: 'pg_trigger_depth() = 0',
}) })
export class AssetEditTable<T extends AssetEditAction = AssetEditAction> { export class AssetEditTable<T extends AssetEditAction = AssetEditAction> {
+2 -2
View File
@@ -144,6 +144,6 @@ export class AssetTable {
@Column({ type: 'integer', nullable: true }) @Column({ type: 'integer', nullable: true })
height!: number | null; height!: number | null;
@Column({ type: 'integer', default: 0 }) @Column({ type: 'boolean', default: false })
editCount!: Generated<number>; isEdited!: Generated<boolean>;
} }
+24 -2
View File
@@ -100,7 +100,29 @@ export class JobService extends BaseService {
const asset = await this.assetRepository.getById(item.data.id); const asset = await this.assetRepository.getById(item.data.id);
if (asset) { if (asset) {
this.websocketRepository.clientSend('AssetEditReadyV1', asset.ownerId, { assetId: item.data.id }); this.websocketRepository.clientSend('AssetEditReadyV1', asset.ownerId, {
asset: {
id: asset.id,
ownerId: asset.ownerId,
originalFileName: asset.originalFileName,
thumbhash: asset.thumbhash ? hexOrBufferToBase64(asset.thumbhash) : null,
checksum: hexOrBufferToBase64(asset.checksum),
fileCreatedAt: asset.fileCreatedAt,
fileModifiedAt: asset.fileModifiedAt,
localDateTime: asset.localDateTime,
duration: asset.duration,
type: asset.type,
deletedAt: asset.deletedAt,
isFavorite: asset.isFavorite,
visibility: asset.visibility,
livePhotoVideoId: asset.livePhotoVideoId,
stackId: asset.stackId,
libraryId: asset.libraryId,
width: asset.width,
height: asset.height,
isEdited: asset.isEdited,
},
});
} }
break; break;
@@ -153,7 +175,7 @@ export class JobService extends BaseService {
libraryId: asset.libraryId, libraryId: asset.libraryId,
width: asset.width, width: asset.width,
height: asset.height, height: asset.height,
editCount: asset.editCount, isEdited: asset.isEdited,
}, },
exif: { exif: {
assetId: exif.assetId, assetId: exif.assetId,
+25 -25
View File
@@ -86,7 +86,7 @@ export const assetStub = {
make: 'FUJIFILM', make: 'FUJIFILM',
model: 'X-T50', model: 'X-T50',
lensModel: 'XF27mm F2.8 R WR', lensModel: 'XF27mm F2.8 R WR',
editCount: 0, isEdited: false,
...asset, ...asset,
}), }),
noResizePath: Object.freeze({ noResizePath: Object.freeze({
@@ -126,7 +126,7 @@ export const assetStub = {
width: null, width: null,
height: null, height: null,
edits: [], edits: [],
editCount: 0, isEdited: false,
}), }),
noWebpPath: Object.freeze({ noWebpPath: Object.freeze({
@@ -168,7 +168,7 @@ export const assetStub = {
width: null, width: null,
height: null, height: null,
edits: [], edits: [],
editCount: 0, isEdited: false,
}), }),
noThumbhash: Object.freeze({ noThumbhash: Object.freeze({
@@ -207,7 +207,7 @@ export const assetStub = {
width: null, width: null,
height: null, height: null,
edits: [], edits: [],
editCount: 0, isEdited: false,
}), }),
primaryImage: Object.freeze({ primaryImage: Object.freeze({
@@ -256,7 +256,7 @@ export const assetStub = {
width: null, width: null,
height: null, height: null,
edits: [], edits: [],
editCount: 0, isEdited: false,
}), }),
image: Object.freeze({ image: Object.freeze({
@@ -303,7 +303,7 @@ export const assetStub = {
width: null, width: null,
visibility: AssetVisibility.Timeline, visibility: AssetVisibility.Timeline,
edits: [], edits: [],
editCount: 0, isEdited: false,
}), }),
trashed: Object.freeze({ trashed: Object.freeze({
@@ -347,7 +347,7 @@ export const assetStub = {
width: null, width: null,
height: null, height: null,
edits: [], edits: [],
editCount: 0, isEdited: false,
}), }),
trashedOffline: Object.freeze({ trashedOffline: Object.freeze({
@@ -391,7 +391,7 @@ export const assetStub = {
width: null, width: null,
height: null, height: null,
edits: [], edits: [],
editCount: 0, isEdited: false,
}), }),
archived: Object.freeze({ archived: Object.freeze({
id: 'asset-id', id: 'asset-id',
@@ -434,7 +434,7 @@ export const assetStub = {
width: null, width: null,
height: null, height: null,
edits: [], edits: [],
editCount: 0, isEdited: false,
}), }),
external: Object.freeze({ external: Object.freeze({
@@ -477,7 +477,7 @@ export const assetStub = {
width: null, width: null,
height: null, height: null,
edits: [], edits: [],
editCount: 0, isEdited: false,
}), }),
image1: Object.freeze({ image1: Object.freeze({
@@ -520,7 +520,7 @@ export const assetStub = {
width: null, width: null,
height: null, height: null,
edits: [], edits: [],
editCount: 0, isEdited: false,
}), }),
imageFrom2015: Object.freeze({ imageFrom2015: Object.freeze({
@@ -562,7 +562,7 @@ export const assetStub = {
width: null, width: null,
height: null, height: null,
edits: [], edits: [],
editCount: 0, isEdited: false,
}), }),
video: Object.freeze({ video: Object.freeze({
@@ -606,7 +606,7 @@ export const assetStub = {
width: null, width: null,
height: null, height: null,
edits: [], edits: [],
editCount: 0, isEdited: false,
}), }),
livePhotoMotionAsset: Object.freeze({ livePhotoMotionAsset: Object.freeze({
@@ -627,7 +627,7 @@ export const assetStub = {
width: null, width: null,
height: null, height: null,
edits: [] as AssetEditActionItem[], edits: [] as AssetEditActionItem[],
editCount: 0, isEdited: false,
} as MapAsset & { faces: AssetFace[]; files: AssetFile[]; exifInfo: Exif; edits: AssetEditActionItem[] }), } as MapAsset & { faces: AssetFace[]; files: AssetFile[]; exifInfo: Exif; edits: AssetEditActionItem[] }),
livePhotoStillAsset: Object.freeze({ livePhotoStillAsset: Object.freeze({
@@ -649,7 +649,7 @@ export const assetStub = {
width: null, width: null,
height: null, height: null,
edits: [] as AssetEditActionItem[], edits: [] as AssetEditActionItem[],
editCount: 0, isEdited: false,
} as MapAsset & { faces: AssetFace[]; files: AssetFile[]; edits: AssetEditActionItem[] }), } as MapAsset & { faces: AssetFace[]; files: AssetFile[]; edits: AssetEditActionItem[] }),
livePhotoWithOriginalFileName: Object.freeze({ livePhotoWithOriginalFileName: Object.freeze({
@@ -673,7 +673,7 @@ export const assetStub = {
width: null, width: null,
height: null, height: null,
edits: [] as AssetEditActionItem[], edits: [] as AssetEditActionItem[],
editCount: 0, isEdited: false,
} as MapAsset & { faces: AssetFace[]; files: AssetFile[]; edits: AssetEditActionItem[] }), } as MapAsset & { faces: AssetFace[]; files: AssetFile[]; edits: AssetEditActionItem[] }),
withLocation: Object.freeze({ withLocation: Object.freeze({
@@ -721,7 +721,7 @@ export const assetStub = {
width: null, width: null,
height: null, height: null,
edits: [], edits: [],
editCount: 0, isEdited: false,
}), }),
sidecar: Object.freeze({ sidecar: Object.freeze({
@@ -760,7 +760,7 @@ export const assetStub = {
width: null, width: null,
height: null, height: null,
edits: [], edits: [],
editCount: 0, isEdited: false,
}), }),
sidecarWithoutExt: Object.freeze({ sidecarWithoutExt: Object.freeze({
@@ -796,7 +796,7 @@ export const assetStub = {
width: null, width: null,
height: null, height: null,
edits: [], edits: [],
editCount: 0, isEdited: false,
}), }),
hasEncodedVideo: Object.freeze({ hasEncodedVideo: Object.freeze({
@@ -839,7 +839,7 @@ export const assetStub = {
width: null, width: null,
height: null, height: null,
edits: [], edits: [],
editCount: 0, isEdited: false,
}), }),
hasFileExtension: Object.freeze({ hasFileExtension: Object.freeze({
@@ -879,7 +879,7 @@ export const assetStub = {
width: null, width: null,
height: null, height: null,
edits: [], edits: [],
editCount: 0, isEdited: false,
}), }),
imageDng: Object.freeze({ imageDng: Object.freeze({
@@ -923,7 +923,7 @@ export const assetStub = {
width: null, width: null,
height: null, height: null,
edits: [], edits: [],
editCount: 0, isEdited: false,
}), }),
imageHif: Object.freeze({ imageHif: Object.freeze({
@@ -967,7 +967,7 @@ export const assetStub = {
width: null, width: null,
height: null, height: null,
edits: [], edits: [],
editCount: 0, isEdited: false,
}), }),
panoramaTif: Object.freeze({ panoramaTif: Object.freeze({
@@ -1068,7 +1068,7 @@ export const assetStub = {
}, },
}, },
] as AssetEditActionItem[], ] as AssetEditActionItem[],
editCount: 1, isEdited: true,
}), }),
withoutEdits: Object.freeze({ withoutEdits: Object.freeze({
@@ -1116,6 +1116,6 @@ export const assetStub = {
width: 2160, width: 2160,
visibility: AssetVisibility.Timeline, visibility: AssetVisibility.Timeline,
edits: [], edits: [],
editCount: 0, isEdited: false,
}), }),
}; };
+1 -1
View File
@@ -159,7 +159,7 @@ export const sharedLinkStub = {
visibility: AssetVisibility.Timeline, visibility: AssetVisibility.Timeline,
width: 500, width: 500,
height: 500, height: 500,
editCount: 0, isEdited: false,
}, },
], ],
albumId: null, albumId: null,
+1 -1
View File
@@ -537,7 +537,7 @@ const assetInsert = (asset: Partial<Insertable<AssetTable>> = {}) => {
fileModifiedAt: now, fileModifiedAt: now,
localDateTime: now, localDateTime: now,
visibility: AssetVisibility.Timeline, visibility: AssetVisibility.Timeline,
editCount: 0, isEdited: false,
}; };
return { return {
@@ -24,32 +24,32 @@ beforeAll(async () => {
describe(AssetEditRepository.name, () => { describe(AssetEditRepository.name, () => {
describe('replaceAll', () => { describe('replaceAll', () => {
it('should increment editCount on insert', async () => { it('should set isEdited on insert', async () => {
const { ctx, sut } = setup(); const { ctx, sut } = setup();
const { user } = await ctx.newUser(); const { user } = await ctx.newUser();
const { asset } = await ctx.newAsset({ ownerId: user.id }); const { asset } = await ctx.newAsset({ ownerId: user.id });
await expect( await expect(
ctx.database.selectFrom('asset').select('editCount').where('id', '=', asset.id).executeTakeFirstOrThrow(), ctx.database.selectFrom('asset').select('isEdited').where('id', '=', asset.id).executeTakeFirstOrThrow(),
).resolves.toEqual({ editCount: 0 }); ).resolves.toEqual({ isEdited: false });
await sut.replaceAll(asset.id, [ await sut.replaceAll(asset.id, [
{ action: AssetEditAction.Crop, parameters: { height: 1, width: 1, x: 1, y: 1 } }, { action: AssetEditAction.Crop, parameters: { height: 1, width: 1, x: 1, y: 1 } },
]); ]);
await expect( await expect(
ctx.database.selectFrom('asset').select('editCount').where('id', '=', asset.id).executeTakeFirstOrThrow(), ctx.database.selectFrom('asset').select('isEdited').where('id', '=', asset.id).executeTakeFirstOrThrow(),
).resolves.toEqual({ editCount: 1 }); ).resolves.toEqual({ isEdited: true });
}); });
it('should increment editCount when inserting multiple edits', async () => { it('should set isEdited when inserting multiple edits', async () => {
const { ctx, sut } = setup(); const { ctx, sut } = setup();
const { user } = await ctx.newUser(); const { user } = await ctx.newUser();
const { asset } = await ctx.newAsset({ ownerId: user.id }); const { asset } = await ctx.newAsset({ ownerId: user.id });
await expect( await expect(
ctx.database.selectFrom('asset').select('editCount').where('id', '=', asset.id).executeTakeFirstOrThrow(), ctx.database.selectFrom('asset').select('isEdited').where('id', '=', asset.id).executeTakeFirstOrThrow(),
).resolves.toEqual({ editCount: 0 }); ).resolves.toEqual({ isEdited: false });
await sut.replaceAll(asset.id, [ await sut.replaceAll(asset.id, [
{ action: AssetEditAction.Crop, parameters: { height: 1, width: 1, x: 1, y: 1 } }, { action: AssetEditAction.Crop, parameters: { height: 1, width: 1, x: 1, y: 1 } },
@@ -58,18 +58,18 @@ describe(AssetEditRepository.name, () => {
]); ]);
await expect( await expect(
ctx.database.selectFrom('asset').select('editCount').where('id', '=', asset.id).executeTakeFirstOrThrow(), ctx.database.selectFrom('asset').select('isEdited').where('id', '=', asset.id).executeTakeFirstOrThrow(),
).resolves.toEqual({ editCount: 3 }); ).resolves.toEqual({ isEdited: true });
}); });
it('should decrement editCount', async () => { it('should keep isEdited when removing some edits', async () => {
const { ctx, sut } = setup(); const { ctx, sut } = setup();
const { user } = await ctx.newUser(); const { user } = await ctx.newUser();
const { asset } = await ctx.newAsset({ ownerId: user.id }); const { asset } = await ctx.newAsset({ ownerId: user.id });
await expect( await expect(
ctx.database.selectFrom('asset').select('editCount').where('id', '=', asset.id).executeTakeFirstOrThrow(), ctx.database.selectFrom('asset').select('isEdited').where('id', '=', asset.id).executeTakeFirstOrThrow(),
).resolves.toEqual({ editCount: 0 }); ).resolves.toEqual({ isEdited: false });
await sut.replaceAll(asset.id, [ await sut.replaceAll(asset.id, [
{ action: AssetEditAction.Crop, parameters: { height: 1, width: 1, x: 1, y: 1 } }, { action: AssetEditAction.Crop, parameters: { height: 1, width: 1, x: 1, y: 1 } },
@@ -77,23 +77,27 @@ describe(AssetEditRepository.name, () => {
{ action: AssetEditAction.Rotate, parameters: { angle: 90 } }, { action: AssetEditAction.Rotate, parameters: { angle: 90 } },
]); ]);
await expect(
ctx.database.selectFrom('asset').select('isEdited').where('id', '=', asset.id).executeTakeFirstOrThrow(),
).resolves.toEqual({ isEdited: true });
await sut.replaceAll(asset.id, [ await sut.replaceAll(asset.id, [
{ action: AssetEditAction.Crop, parameters: { height: 1, width: 1, x: 1, y: 1 } }, { action: AssetEditAction.Crop, parameters: { height: 1, width: 1, x: 1, y: 1 } },
]); ]);
await expect( await expect(
ctx.database.selectFrom('asset').select('editCount').where('id', '=', asset.id).executeTakeFirstOrThrow(), ctx.database.selectFrom('asset').select('isEdited').where('id', '=', asset.id).executeTakeFirstOrThrow(),
).resolves.toEqual({ editCount: 1 }); ).resolves.toEqual({ isEdited: true });
}); });
it('should set editCount to 0 if all edits are deleted', async () => { it('should set isEdited to false if all edits are deleted', async () => {
const { ctx, sut } = setup(); const { ctx, sut } = setup();
const { user } = await ctx.newUser(); const { user } = await ctx.newUser();
const { asset } = await ctx.newAsset({ ownerId: user.id }); const { asset } = await ctx.newAsset({ ownerId: user.id });
await expect( await expect(
ctx.database.selectFrom('asset').select('editCount').where('id', '=', asset.id).executeTakeFirstOrThrow(), ctx.database.selectFrom('asset').select('isEdited').where('id', '=', asset.id).executeTakeFirstOrThrow(),
).resolves.toEqual({ editCount: 0 }); ).resolves.toEqual({ isEdited: false });
await sut.replaceAll(asset.id, [ await sut.replaceAll(asset.id, [
{ action: AssetEditAction.Crop, parameters: { height: 1, width: 1, x: 1, y: 1 } }, { action: AssetEditAction.Crop, parameters: { height: 1, width: 1, x: 1, y: 1 } },
@@ -104,8 +108,8 @@ describe(AssetEditRepository.name, () => {
await sut.replaceAll(asset.id, []); await sut.replaceAll(asset.id, []);
await expect( await expect(
ctx.database.selectFrom('asset').select('editCount').where('id', '=', asset.id).executeTakeFirstOrThrow(), ctx.database.selectFrom('asset').select('isEdited').where('id', '=', asset.id).executeTakeFirstOrThrow(),
).resolves.toEqual({ editCount: 0 }); ).resolves.toEqual({ isEdited: false });
}); });
}); });
}); });
@@ -83,7 +83,7 @@ describe(SyncRequestType.AlbumAssetsV1, () => {
libraryId: asset.libraryId, libraryId: asset.libraryId,
width: asset.width, width: asset.width,
height: asset.height, height: asset.height,
editCount: asset.editCount, isEdited: asset.isEdited,
}, },
type: SyncEntityType.AlbumAssetCreateV1, type: SyncEntityType.AlbumAssetCreateV1,
}, },
@@ -64,7 +64,7 @@ describe(SyncEntityType.AssetV1, () => {
libraryId: asset.libraryId, libraryId: asset.libraryId,
width: asset.width, width: asset.width,
height: asset.height, height: asset.height,
editCount: asset.editCount, isEdited: asset.isEdited,
}, },
type: 'AssetV1', type: 'AssetV1',
}, },
@@ -63,7 +63,7 @@ describe(SyncRequestType.PartnerAssetsV1, () => {
type: asset.type, type: asset.type,
visibility: asset.visibility, visibility: asset.visibility,
duration: asset.duration, duration: asset.duration,
editCount: asset.editCount, isEdited: asset.isEdited,
stackId: null, stackId: null,
livePhotoVideoId: null, livePhotoVideoId: null,
libraryId: asset.libraryId, libraryId: asset.libraryId,
+1 -1
View File
@@ -253,7 +253,7 @@ const assetFactory = (asset: Partial<MapAsset> = {}) => ({
visibility: AssetVisibility.Timeline, visibility: AssetVisibility.Timeline,
width: null, width: null,
height: null, height: null,
editCount: 0, isEdited: false,
...asset, ...asset,
}); });
@@ -7,6 +7,7 @@
import AddToStackAction from '$lib/components/asset-viewer/actions/add-to-stack-action.svelte'; import AddToStackAction from '$lib/components/asset-viewer/actions/add-to-stack-action.svelte';
import ArchiveAction from '$lib/components/asset-viewer/actions/archive-action.svelte'; import ArchiveAction from '$lib/components/asset-viewer/actions/archive-action.svelte';
import DeleteAction from '$lib/components/asset-viewer/actions/delete-action.svelte'; import DeleteAction from '$lib/components/asset-viewer/actions/delete-action.svelte';
import EditAction from '$lib/components/asset-viewer/actions/edit-action.svelte';
import KeepThisDeleteOthersAction from '$lib/components/asset-viewer/actions/keep-this-delete-others.svelte'; import KeepThisDeleteOthersAction from '$lib/components/asset-viewer/actions/keep-this-delete-others.svelte';
import RatingAction from '$lib/components/asset-viewer/actions/rating-action.svelte'; import RatingAction from '$lib/components/asset-viewer/actions/rating-action.svelte';
import RemoveAssetFromStack from '$lib/components/asset-viewer/actions/remove-asset-from-stack.svelte'; import RemoveAssetFromStack from '$lib/components/asset-viewer/actions/remove-asset-from-stack.svelte';
@@ -19,6 +20,7 @@
import UnstackAction from '$lib/components/asset-viewer/actions/unstack-action.svelte'; import UnstackAction from '$lib/components/asset-viewer/actions/unstack-action.svelte';
import ButtonContextMenu from '$lib/components/shared-components/context-menu/button-context-menu.svelte'; import ButtonContextMenu from '$lib/components/shared-components/context-menu/button-context-menu.svelte';
import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte'; import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
import { ProjectionType } from '$lib/constants';
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte'; import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
import { Route } from '$lib/route'; import { Route } from '$lib/route';
import { getGlobalActions } from '$lib/services/app.service'; import { getGlobalActions } from '$lib/services/app.service';
@@ -71,7 +73,7 @@
onUndoDelete?: OnUndoDelete; onUndoDelete?: OnUndoDelete;
onRunJob: (name: AssetJobName) => void; onRunJob: (name: AssetJobName) => void;
onPlaySlideshow: () => void; onPlaySlideshow: () => void;
// onEdit: () => void; onEdit: () => void;
onClose?: () => void; onClose?: () => void;
playOriginalVideo: boolean; playOriginalVideo: boolean;
setPlayOriginalVideo: (value: boolean) => void; setPlayOriginalVideo: (value: boolean) => void;
@@ -91,7 +93,7 @@
onRunJob, onRunJob,
onPlaySlideshow, onPlaySlideshow,
onClose, onClose,
// onEdit, onEdit,
playOriginalVideo = false, playOriginalVideo = false,
setPlayOriginalVideo, setPlayOriginalVideo,
}: Props = $props(); }: Props = $props();
@@ -126,17 +128,17 @@
const sharedLink = getSharedLink(); const sharedLink = getSharedLink();
// TODO: Enable when edits are ready for release // TODO: Enable when edits are ready for release
// let showEditorButton = $derived( let showEditorButton = $derived(
// isOwner && isOwner &&
// asset.type === AssetTypeEnum.Image && asset.type === AssetTypeEnum.Image &&
// !( !(
// asset.exifInfo?.projectionType === ProjectionType.EQUIRECTANGULAR || asset.exifInfo?.projectionType === ProjectionType.EQUIRECTANGULAR ||
// (asset.originalPath && asset.originalPath.toLowerCase().endsWith('.insp')) (asset.originalPath && asset.originalPath.toLowerCase().endsWith('.insp'))
// ) && ) &&
// !(asset.originalPath && asset.originalPath.toLowerCase().endsWith('.gif')) && !(asset.originalPath && asset.originalPath.toLowerCase().endsWith('.gif')) &&
// !(asset.originalPath && asset.originalPath.toLowerCase().endsWith('.svg')) && !(asset.originalPath && asset.originalPath.toLowerCase().endsWith('.svg')) &&
// !asset.livePhotoVideoId, !asset.livePhotoVideoId,
// ); );
</script> </script>
<CommandPaletteDefaultProvider <CommandPaletteDefaultProvider
@@ -189,9 +191,9 @@
<RatingAction {asset} {onAction} /> <RatingAction {asset} {onAction} />
{/if} {/if}
<!-- {#if showEditorButton} {#if showEditorButton}
<EditAction onAction={onEdit} /> <EditAction onAction={onEdit} />
{/if} --> {/if}
{#if isOwner} {#if isOwner}
<DeleteAction {asset} {onAction} {preAction} {onUndoDelete} /> <DeleteAction {asset} {onAction} {preAction} {onUndoDelete} />
@@ -254,12 +254,12 @@
}); });
}; };
// const showEditor = () => { const showEditor = () => {
// if (assetViewerManager.isShowActivityPanel) { if (assetViewerManager.isShowActivityPanel) {
// assetViewerManager.isShowActivityPanel = false; assetViewerManager.isShowActivityPanel = false;
// } }
// isShowEditor = !isShowEditor; isShowEditor = !isShowEditor;
// }; };
const handleRunJob = async (name: AssetJobName) => { const handleRunJob = async (name: AssetJobName) => {
try { try {
@@ -466,6 +466,7 @@
preAction={handlePreAction} preAction={handlePreAction}
onAction={handleAction} onAction={handleAction}
{onUndoDelete} {onUndoDelete}
onEdit={showEditor}
onRunJob={handleRunJob} onRunJob={handleRunJob}
onPlaySlideshow={() => ($slideshowState = SlideshowState.PlaySlideshow)} onPlaySlideshow={() => ($slideshowState = SlideshowState.PlaySlideshow)}
onClose={onClose ? () => onClose(asset) : undefined} onClose={onClose ? () => onClose(asset) : undefined}
@@ -115,7 +115,7 @@ export class EditManager {
// Setup the websocket listener before sending the edit request // Setup the websocket listener before sending the edit request
const editCompleted = waitForWebsocketEvent( const editCompleted = waitForWebsocketEvent(
'AssetEditReadyV1', 'AssetEditReadyV1',
(event) => event.assetId === this.currentAsset!.id, (event) => event.asset.id === this.currentAsset!.id,
10_000, 10_000,
); );
+1 -1
View File
@@ -31,7 +31,7 @@ export interface Events {
on_notification: (notification: NotificationDto) => void; on_notification: (notification: NotificationDto) => void;
AppRestartV1: (event: AppRestartEvent) => void; AppRestartV1: (event: AppRestartEvent) => void;
AssetEditReadyV1: (data: { assetId: string }) => void; AssetEditReadyV1: (data: { asset: { id: string } }) => void;
} }
const websocket: Socket<Events> = io({ const websocket: Socket<Events> = io({