feat: recently added assets page (#28272)
* feat(server): add ordering date option to time buckets * feat(web): add recently added page * feat(server): recently created assets in explore data * feat(web): recently added in explore tab * fix: recently added assets ordering * fix(server): failing bucket test * feat(web): improve recently added preview * chore: update e2e explore/timeline tests * chore: rename and refactor timeline ordering dates * fix(web): invalid timeline option * feat(mobile): recently added page * fix(server): sync tests * fix(mobile): resync assets to get uploadedAt column * chore: rename assetorderby enum * chore(mobile): formatting * minor fixes * stylings --------- Co-authored-by: Alex <alex.tran1502@gmail.com>pull/28240/merge
parent
38438c8d9a
commit
e142e3aca7
|
|
@ -441,7 +441,18 @@ describe('/search', () => {
|
|||
.get('/search/explore')
|
||||
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||
expect(status).toBe(200);
|
||||
expect(body).toEqual([{ fieldName: 'exifInfo.city', items: [] }]);
|
||||
expect(Array.isArray(body)).toBe(true);
|
||||
expect(body).toEqual(expect.arrayContaining([{ fieldName: 'exifInfo.city', items: [] }]));
|
||||
expect(body).toEqual(
|
||||
expect.arrayContaining([
|
||||
{
|
||||
fieldName: 'createdAt',
|
||||
items: expect.arrayContaining([
|
||||
expect.objectContaining({ data: expect.objectContaining({ id: assetLast.id }) }),
|
||||
]),
|
||||
},
|
||||
]),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ export function toColumnarFormat(assets: MockTimelineAsset[]): TimeBucketAssetRe
|
|||
ownerId: [],
|
||||
ratio: [],
|
||||
thumbhash: [],
|
||||
createdAt: [],
|
||||
fileCreatedAt: [],
|
||||
localOffsetHours: [],
|
||||
isFavorite: [],
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -10,6 +10,7 @@ class RemoteAsset extends BaseAsset {
|
|||
final AssetVisibility visibility;
|
||||
final String ownerId;
|
||||
final String? stackId;
|
||||
final DateTime? uploadedAt;
|
||||
|
||||
const RemoteAsset({
|
||||
required this.id,
|
||||
|
|
@ -20,6 +21,7 @@ class RemoteAsset extends BaseAsset {
|
|||
required super.type,
|
||||
required super.createdAt,
|
||||
required super.updatedAt,
|
||||
this.uploadedAt,
|
||||
super.width,
|
||||
super.height,
|
||||
super.durationMs,
|
||||
|
|
@ -55,6 +57,7 @@ class RemoteAsset extends BaseAsset {
|
|||
type: $type,
|
||||
createdAt: $createdAt,
|
||||
updatedAt: $updatedAt,
|
||||
uploadedAt: ${uploadedAt ?? "<NA>"},
|
||||
width: ${width ?? "<NA>"},
|
||||
height: ${height ?? "<NA>"},
|
||||
durationMs: ${durationMs ?? "<NA>"},
|
||||
|
|
@ -82,7 +85,8 @@ class RemoteAsset extends BaseAsset {
|
|||
ownerId == other.ownerId &&
|
||||
thumbHash == other.thumbHash &&
|
||||
visibility == other.visibility &&
|
||||
stackId == other.stackId;
|
||||
stackId == other.stackId &&
|
||||
uploadedAt == other.uploadedAt;
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
@ -93,7 +97,8 @@ class RemoteAsset extends BaseAsset {
|
|||
localId.hashCode ^
|
||||
thumbHash.hashCode ^
|
||||
visibility.hashCode ^
|
||||
stackId.hashCode;
|
||||
stackId.hashCode ^
|
||||
uploadedAt.hashCode;
|
||||
|
||||
RemoteAsset copyWith({
|
||||
String? id,
|
||||
|
|
@ -104,6 +109,7 @@ class RemoteAsset extends BaseAsset {
|
|||
AssetType? type,
|
||||
DateTime? createdAt,
|
||||
DateTime? updatedAt,
|
||||
DateTime? uploadedAt,
|
||||
int? width,
|
||||
int? height,
|
||||
int? durationMs,
|
||||
|
|
@ -123,6 +129,7 @@ class RemoteAsset extends BaseAsset {
|
|||
type: type ?? this.type,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
updatedAt: updatedAt ?? this.updatedAt,
|
||||
uploadedAt: uploadedAt ?? this.uploadedAt,
|
||||
width: width ?? this.width,
|
||||
height: height ?? this.height,
|
||||
durationMs: durationMs ?? this.durationMs,
|
||||
|
|
@ -148,6 +155,7 @@ class RemoteAssetExif extends RemoteAsset {
|
|||
required super.type,
|
||||
required super.createdAt,
|
||||
required super.updatedAt,
|
||||
super.uploadedAt,
|
||||
super.width,
|
||||
super.height,
|
||||
super.durationMs,
|
||||
|
|
@ -184,6 +192,7 @@ class RemoteAssetExif extends RemoteAsset {
|
|||
AssetType? type,
|
||||
DateTime? createdAt,
|
||||
DateTime? updatedAt,
|
||||
DateTime? uploadedAt,
|
||||
int? width,
|
||||
int? height,
|
||||
int? durationMs,
|
||||
|
|
@ -204,6 +213,7 @@ class RemoteAssetExif extends RemoteAsset {
|
|||
type: type ?? this.type,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
updatedAt: updatedAt ?? this.updatedAt,
|
||||
uploadedAt: uploadedAt ?? this.uploadedAt,
|
||||
width: width ?? this.width,
|
||||
height: height ?? this.height,
|
||||
durationMs: durationMs ?? this.durationMs,
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ enum GroupAssetsBy { day, month, auto, none }
|
|||
|
||||
enum HeaderType { none, month, day, monthAndDay }
|
||||
|
||||
enum SortAssetsBy { taken, uploaded }
|
||||
|
||||
class Bucket {
|
||||
final int assetCount;
|
||||
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ enum SyncMigrationTask {
|
|||
v20260128_ResetExifV1, // EXIF table has incorrect width and height information.
|
||||
v20260128_CopyExifWidthHeightToAsset, // Asset table has incorrect width and height for video ratio calculations.
|
||||
v20260128_ResetAssetV1, // Asset v2.5.0 has width and height information that were edited assets.
|
||||
v20260597_ResetAssetV1AssetV2, // Assets didn't include the uploadedAt column.
|
||||
}
|
||||
|
||||
class SyncStreamService {
|
||||
|
|
@ -132,6 +133,13 @@ class SyncStreamService {
|
|||
migrations.add(SyncMigrationTask.v20260128_CopyExifWidthHeightToAsset.name);
|
||||
}
|
||||
}
|
||||
|
||||
if (!migrations.contains(SyncMigrationTask.v20260597_ResetAssetV1AssetV2.name) &&
|
||||
semVer > const SemVer(major: 2, minor: 7, patch: 5)) {
|
||||
_logger.info("Running pre-sync task: v20260597_ResetAssetV1AssetV2");
|
||||
await _syncApiRepository.deleteSyncAck([SyncEntityType.assetV1, SyncEntityType.assetV2]);
|
||||
migrations.add(SyncMigrationTask.v20260597_ResetAssetV1AssetV2.name);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _runPostSyncTasks(List<String> migrations) async {
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ enum TimelineOrigin {
|
|||
deepLink,
|
||||
albumActivities,
|
||||
folder,
|
||||
recentlyAdded,
|
||||
}
|
||||
|
||||
class TimelineFactory {
|
||||
|
|
@ -61,6 +62,8 @@ class TimelineFactory {
|
|||
|
||||
TimelineService remoteAssets(String userId) => TimelineService(_timelineRepository.remote(userId, groupBy));
|
||||
|
||||
TimelineService recentlyAdded(String userId) => TimelineService(_timelineRepository.recentlyAdded(userId, groupBy));
|
||||
|
||||
TimelineService favorite(String userId) => TimelineService(_timelineRepository.favorite(userId, groupBy));
|
||||
|
||||
TimelineService trash(String userId) => TimelineService(_timelineRepository.trash(userId, groupBy));
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ extension DTOToAsset on api.AssetResponseDto {
|
|||
checksum: checksum,
|
||||
createdAt: fileCreatedAt,
|
||||
updatedAt: updatedAt,
|
||||
uploadedAt: createdAt,
|
||||
ownerId: ownerId,
|
||||
visibility: visibility.toAssetVisibility(),
|
||||
durationMs: duration,
|
||||
|
|
@ -33,6 +34,7 @@ extension DTOToAsset on api.AssetResponseDto {
|
|||
checksum: checksum,
|
||||
createdAt: fileCreatedAt,
|
||||
updatedAt: updatedAt,
|
||||
uploadedAt: createdAt,
|
||||
ownerId: ownerId,
|
||||
visibility: visibility.toAssetVisibility(),
|
||||
durationMs: duration,
|
||||
|
|
|
|||
|
|
@ -27,7 +27,8 @@ SELECT
|
|||
NULL as longitude,
|
||||
NULL as adjustmentTime,
|
||||
rae.is_edited,
|
||||
0 as playback_style
|
||||
0 as playback_style,
|
||||
rae.uploaded_at
|
||||
FROM
|
||||
remote_asset_entity rae
|
||||
LEFT JOIN
|
||||
|
|
@ -65,7 +66,8 @@ SELECT
|
|||
lae.longitude,
|
||||
lae.adjustment_time,
|
||||
0 as is_edited,
|
||||
lae.playback_style
|
||||
lae.playback_style,
|
||||
NULL as uploaded_at
|
||||
FROM
|
||||
local_asset_entity lae
|
||||
WHERE NOT EXISTS (
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ class MergedAssetDrift extends i1.ModularAccessor {
|
|||
);
|
||||
$arrayStartIndex += generatedlimit.amountOfVariables;
|
||||
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_ms, 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, 0 AS playback_style 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_ms, 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, lae.playback_style 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_ms, 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, 0 AS playback_style, rae.uploaded_at 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_ms, 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, lae.playback_style, NULL AS uploaded_at 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: [
|
||||
for (var $ in userIds) i0.Variable<String>($),
|
||||
...generatedlimit.introducedVariables,
|
||||
|
|
@ -68,6 +68,7 @@ class MergedAssetDrift extends i1.ModularAccessor {
|
|||
adjustmentTime: row.readNullable<DateTime>('adjustmentTime'),
|
||||
isEdited: row.read<bool>('is_edited'),
|
||||
playbackStyle: row.read<int>('playback_style'),
|
||||
uploadedAt: row.readNullable<DateTime>('uploaded_at'),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
@ -141,6 +142,7 @@ class MergedAssetResult {
|
|||
final DateTime? adjustmentTime;
|
||||
final bool isEdited;
|
||||
final int playbackStyle;
|
||||
final DateTime? uploadedAt;
|
||||
MergedAssetResult({
|
||||
this.remoteId,
|
||||
this.localId,
|
||||
|
|
@ -164,6 +166,7 @@ class MergedAssetResult {
|
|||
this.adjustmentTime,
|
||||
required this.isEdited,
|
||||
required this.playbackStyle,
|
||||
this.uploadedAt,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -43,6 +43,8 @@ class RemoteAssetEntity extends Table with DriftDefaultsMixin, AssetEntityMixin
|
|||
|
||||
DateTimeColumn get deletedAt => dateTime().nullable()();
|
||||
|
||||
DateTimeColumn get uploadedAt => dateTime().nullable()();
|
||||
|
||||
TextColumn get livePhotoVideoId => text().nullable()();
|
||||
|
||||
IntColumn get visibility => intEnum<AssetVisibility>()();
|
||||
|
|
@ -66,6 +68,7 @@ extension RemoteAssetEntityDataDomainEx on RemoteAssetEntityData {
|
|||
type: type,
|
||||
createdAt: createdAt,
|
||||
updatedAt: updatedAt,
|
||||
uploadedAt: uploadedAt,
|
||||
durationMs: durationMs,
|
||||
isFavorite: isFavorite,
|
||||
height: height,
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ typedef $$RemoteAssetEntityTableCreateCompanionBuilder =
|
|||
i0.Value<DateTime?> localDateTime,
|
||||
i0.Value<String?> thumbHash,
|
||||
i0.Value<DateTime?> deletedAt,
|
||||
i0.Value<DateTime?> uploadedAt,
|
||||
i0.Value<String?> livePhotoVideoId,
|
||||
required i2.AssetVisibility visibility,
|
||||
i0.Value<String?> stackId,
|
||||
|
|
@ -49,6 +50,7 @@ typedef $$RemoteAssetEntityTableUpdateCompanionBuilder =
|
|||
i0.Value<DateTime?> localDateTime,
|
||||
i0.Value<String?> thumbHash,
|
||||
i0.Value<DateTime?> deletedAt,
|
||||
i0.Value<DateTime?> uploadedAt,
|
||||
i0.Value<String?> livePhotoVideoId,
|
||||
i0.Value<i2.AssetVisibility> visibility,
|
||||
i0.Value<String?> stackId,
|
||||
|
|
@ -177,6 +179,11 @@ class $$RemoteAssetEntityTableFilterComposer
|
|||
builder: (column) => i0.ColumnFilters(column),
|
||||
);
|
||||
|
||||
i0.ColumnFilters<DateTime> get uploadedAt => $composableBuilder(
|
||||
column: $table.uploadedAt,
|
||||
builder: (column) => i0.ColumnFilters(column),
|
||||
);
|
||||
|
||||
i0.ColumnFilters<String> get livePhotoVideoId => $composableBuilder(
|
||||
column: $table.livePhotoVideoId,
|
||||
builder: (column) => i0.ColumnFilters(column),
|
||||
|
|
@ -305,6 +312,11 @@ class $$RemoteAssetEntityTableOrderingComposer
|
|||
builder: (column) => i0.ColumnOrderings(column),
|
||||
);
|
||||
|
||||
i0.ColumnOrderings<DateTime> get uploadedAt => $composableBuilder(
|
||||
column: $table.uploadedAt,
|
||||
builder: (column) => i0.ColumnOrderings(column),
|
||||
);
|
||||
|
||||
i0.ColumnOrderings<String> get livePhotoVideoId => $composableBuilder(
|
||||
column: $table.livePhotoVideoId,
|
||||
builder: (column) => i0.ColumnOrderings(column),
|
||||
|
|
@ -412,6 +424,11 @@ class $$RemoteAssetEntityTableAnnotationComposer
|
|||
i0.GeneratedColumn<DateTime> get deletedAt =>
|
||||
$composableBuilder(column: $table.deletedAt, builder: (column) => column);
|
||||
|
||||
i0.GeneratedColumn<DateTime> get uploadedAt => $composableBuilder(
|
||||
column: $table.uploadedAt,
|
||||
builder: (column) => column,
|
||||
);
|
||||
|
||||
i0.GeneratedColumn<String> get livePhotoVideoId => $composableBuilder(
|
||||
column: $table.livePhotoVideoId,
|
||||
builder: (column) => column,
|
||||
|
|
@ -507,6 +524,7 @@ class $$RemoteAssetEntityTableTableManager
|
|||
i0.Value<DateTime?> localDateTime = const i0.Value.absent(),
|
||||
i0.Value<String?> thumbHash = const i0.Value.absent(),
|
||||
i0.Value<DateTime?> deletedAt = const i0.Value.absent(),
|
||||
i0.Value<DateTime?> uploadedAt = const i0.Value.absent(),
|
||||
i0.Value<String?> livePhotoVideoId = const i0.Value.absent(),
|
||||
i0.Value<i2.AssetVisibility> visibility =
|
||||
const i0.Value.absent(),
|
||||
|
|
@ -528,6 +546,7 @@ class $$RemoteAssetEntityTableTableManager
|
|||
localDateTime: localDateTime,
|
||||
thumbHash: thumbHash,
|
||||
deletedAt: deletedAt,
|
||||
uploadedAt: uploadedAt,
|
||||
livePhotoVideoId: livePhotoVideoId,
|
||||
visibility: visibility,
|
||||
stackId: stackId,
|
||||
|
|
@ -550,6 +569,7 @@ class $$RemoteAssetEntityTableTableManager
|
|||
i0.Value<DateTime?> localDateTime = const i0.Value.absent(),
|
||||
i0.Value<String?> thumbHash = const i0.Value.absent(),
|
||||
i0.Value<DateTime?> deletedAt = const i0.Value.absent(),
|
||||
i0.Value<DateTime?> uploadedAt = const i0.Value.absent(),
|
||||
i0.Value<String?> livePhotoVideoId = const i0.Value.absent(),
|
||||
required i2.AssetVisibility visibility,
|
||||
i0.Value<String?> stackId = const i0.Value.absent(),
|
||||
|
|
@ -570,6 +590,7 @@ class $$RemoteAssetEntityTableTableManager
|
|||
localDateTime: localDateTime,
|
||||
thumbHash: thumbHash,
|
||||
deletedAt: deletedAt,
|
||||
uploadedAt: uploadedAt,
|
||||
livePhotoVideoId: livePhotoVideoId,
|
||||
visibility: visibility,
|
||||
stackId: stackId,
|
||||
|
|
@ -818,6 +839,18 @@ class $RemoteAssetEntityTable extends i3.RemoteAssetEntity
|
|||
type: i0.DriftSqlType.dateTime,
|
||||
requiredDuringInsert: false,
|
||||
);
|
||||
static const i0.VerificationMeta _uploadedAtMeta = const i0.VerificationMeta(
|
||||
'uploadedAt',
|
||||
);
|
||||
@override
|
||||
late final i0.GeneratedColumn<DateTime> uploadedAt =
|
||||
i0.GeneratedColumn<DateTime>(
|
||||
'uploaded_at',
|
||||
aliasedName,
|
||||
true,
|
||||
type: i0.DriftSqlType.dateTime,
|
||||
requiredDuringInsert: false,
|
||||
);
|
||||
static const i0.VerificationMeta _livePhotoVideoIdMeta =
|
||||
const i0.VerificationMeta('livePhotoVideoId');
|
||||
@override
|
||||
|
|
@ -894,6 +927,7 @@ class $RemoteAssetEntityTable extends i3.RemoteAssetEntity
|
|||
localDateTime,
|
||||
thumbHash,
|
||||
deletedAt,
|
||||
uploadedAt,
|
||||
livePhotoVideoId,
|
||||
visibility,
|
||||
stackId,
|
||||
|
|
@ -998,6 +1032,12 @@ class $RemoteAssetEntityTable extends i3.RemoteAssetEntity
|
|||
deletedAt.isAcceptableOrUnknown(data['deleted_at']!, _deletedAtMeta),
|
||||
);
|
||||
}
|
||||
if (data.containsKey('uploaded_at')) {
|
||||
context.handle(
|
||||
_uploadedAtMeta,
|
||||
uploadedAt.isAcceptableOrUnknown(data['uploaded_at']!, _uploadedAtMeta),
|
||||
);
|
||||
}
|
||||
if (data.containsKey('live_photo_video_id')) {
|
||||
context.handle(
|
||||
_livePhotoVideoIdMeta,
|
||||
|
|
@ -1095,6 +1135,10 @@ class $RemoteAssetEntityTable extends i3.RemoteAssetEntity
|
|||
i0.DriftSqlType.dateTime,
|
||||
data['${effectivePrefix}deleted_at'],
|
||||
),
|
||||
uploadedAt: attachedDatabase.typeMapping.read(
|
||||
i0.DriftSqlType.dateTime,
|
||||
data['${effectivePrefix}uploaded_at'],
|
||||
),
|
||||
livePhotoVideoId: attachedDatabase.typeMapping.read(
|
||||
i0.DriftSqlType.string,
|
||||
data['${effectivePrefix}live_photo_video_id'],
|
||||
|
|
@ -1153,6 +1197,7 @@ class RemoteAssetEntityData extends i0.DataClass
|
|||
final DateTime? localDateTime;
|
||||
final String? thumbHash;
|
||||
final DateTime? deletedAt;
|
||||
final DateTime? uploadedAt;
|
||||
final String? livePhotoVideoId;
|
||||
final i2.AssetVisibility visibility;
|
||||
final String? stackId;
|
||||
|
|
@ -1173,6 +1218,7 @@ class RemoteAssetEntityData extends i0.DataClass
|
|||
this.localDateTime,
|
||||
this.thumbHash,
|
||||
this.deletedAt,
|
||||
this.uploadedAt,
|
||||
this.livePhotoVideoId,
|
||||
required this.visibility,
|
||||
this.stackId,
|
||||
|
|
@ -1212,6 +1258,9 @@ class RemoteAssetEntityData extends i0.DataClass
|
|||
if (!nullToAbsent || deletedAt != null) {
|
||||
map['deleted_at'] = i0.Variable<DateTime>(deletedAt);
|
||||
}
|
||||
if (!nullToAbsent || uploadedAt != null) {
|
||||
map['uploaded_at'] = i0.Variable<DateTime>(uploadedAt);
|
||||
}
|
||||
if (!nullToAbsent || livePhotoVideoId != null) {
|
||||
map['live_photo_video_id'] = i0.Variable<String>(livePhotoVideoId);
|
||||
}
|
||||
|
|
@ -1252,6 +1301,7 @@ class RemoteAssetEntityData extends i0.DataClass
|
|||
localDateTime: serializer.fromJson<DateTime?>(json['localDateTime']),
|
||||
thumbHash: serializer.fromJson<String?>(json['thumbHash']),
|
||||
deletedAt: serializer.fromJson<DateTime?>(json['deletedAt']),
|
||||
uploadedAt: serializer.fromJson<DateTime?>(json['uploadedAt']),
|
||||
livePhotoVideoId: serializer.fromJson<String?>(json['livePhotoVideoId']),
|
||||
visibility: i1.$RemoteAssetEntityTable.$convertervisibility.fromJson(
|
||||
serializer.fromJson<int>(json['visibility']),
|
||||
|
|
@ -1281,6 +1331,7 @@ class RemoteAssetEntityData extends i0.DataClass
|
|||
'localDateTime': serializer.toJson<DateTime?>(localDateTime),
|
||||
'thumbHash': serializer.toJson<String?>(thumbHash),
|
||||
'deletedAt': serializer.toJson<DateTime?>(deletedAt),
|
||||
'uploadedAt': serializer.toJson<DateTime?>(uploadedAt),
|
||||
'livePhotoVideoId': serializer.toJson<String?>(livePhotoVideoId),
|
||||
'visibility': serializer.toJson<int>(
|
||||
i1.$RemoteAssetEntityTable.$convertervisibility.toJson(visibility),
|
||||
|
|
@ -1306,6 +1357,7 @@ class RemoteAssetEntityData extends i0.DataClass
|
|||
i0.Value<DateTime?> localDateTime = const i0.Value.absent(),
|
||||
i0.Value<String?> thumbHash = const i0.Value.absent(),
|
||||
i0.Value<DateTime?> deletedAt = const i0.Value.absent(),
|
||||
i0.Value<DateTime?> uploadedAt = const i0.Value.absent(),
|
||||
i0.Value<String?> livePhotoVideoId = const i0.Value.absent(),
|
||||
i2.AssetVisibility? visibility,
|
||||
i0.Value<String?> stackId = const i0.Value.absent(),
|
||||
|
|
@ -1328,6 +1380,7 @@ class RemoteAssetEntityData extends i0.DataClass
|
|||
: this.localDateTime,
|
||||
thumbHash: thumbHash.present ? thumbHash.value : this.thumbHash,
|
||||
deletedAt: deletedAt.present ? deletedAt.value : this.deletedAt,
|
||||
uploadedAt: uploadedAt.present ? uploadedAt.value : this.uploadedAt,
|
||||
livePhotoVideoId: livePhotoVideoId.present
|
||||
? livePhotoVideoId.value
|
||||
: this.livePhotoVideoId,
|
||||
|
|
@ -1358,6 +1411,9 @@ class RemoteAssetEntityData extends i0.DataClass
|
|||
: this.localDateTime,
|
||||
thumbHash: data.thumbHash.present ? data.thumbHash.value : this.thumbHash,
|
||||
deletedAt: data.deletedAt.present ? data.deletedAt.value : this.deletedAt,
|
||||
uploadedAt: data.uploadedAt.present
|
||||
? data.uploadedAt.value
|
||||
: this.uploadedAt,
|
||||
livePhotoVideoId: data.livePhotoVideoId.present
|
||||
? data.livePhotoVideoId.value
|
||||
: this.livePhotoVideoId,
|
||||
|
|
@ -1387,6 +1443,7 @@ class RemoteAssetEntityData extends i0.DataClass
|
|||
..write('localDateTime: $localDateTime, ')
|
||||
..write('thumbHash: $thumbHash, ')
|
||||
..write('deletedAt: $deletedAt, ')
|
||||
..write('uploadedAt: $uploadedAt, ')
|
||||
..write('livePhotoVideoId: $livePhotoVideoId, ')
|
||||
..write('visibility: $visibility, ')
|
||||
..write('stackId: $stackId, ')
|
||||
|
|
@ -1412,6 +1469,7 @@ class RemoteAssetEntityData extends i0.DataClass
|
|||
localDateTime,
|
||||
thumbHash,
|
||||
deletedAt,
|
||||
uploadedAt,
|
||||
livePhotoVideoId,
|
||||
visibility,
|
||||
stackId,
|
||||
|
|
@ -1436,6 +1494,7 @@ class RemoteAssetEntityData extends i0.DataClass
|
|||
other.localDateTime == this.localDateTime &&
|
||||
other.thumbHash == this.thumbHash &&
|
||||
other.deletedAt == this.deletedAt &&
|
||||
other.uploadedAt == this.uploadedAt &&
|
||||
other.livePhotoVideoId == this.livePhotoVideoId &&
|
||||
other.visibility == this.visibility &&
|
||||
other.stackId == this.stackId &&
|
||||
|
|
@ -1459,6 +1518,7 @@ class RemoteAssetEntityCompanion
|
|||
final i0.Value<DateTime?> localDateTime;
|
||||
final i0.Value<String?> thumbHash;
|
||||
final i0.Value<DateTime?> deletedAt;
|
||||
final i0.Value<DateTime?> uploadedAt;
|
||||
final i0.Value<String?> livePhotoVideoId;
|
||||
final i0.Value<i2.AssetVisibility> visibility;
|
||||
final i0.Value<String?> stackId;
|
||||
|
|
@ -1479,6 +1539,7 @@ class RemoteAssetEntityCompanion
|
|||
this.localDateTime = const i0.Value.absent(),
|
||||
this.thumbHash = const i0.Value.absent(),
|
||||
this.deletedAt = const i0.Value.absent(),
|
||||
this.uploadedAt = const i0.Value.absent(),
|
||||
this.livePhotoVideoId = const i0.Value.absent(),
|
||||
this.visibility = const i0.Value.absent(),
|
||||
this.stackId = const i0.Value.absent(),
|
||||
|
|
@ -1500,6 +1561,7 @@ class RemoteAssetEntityCompanion
|
|||
this.localDateTime = const i0.Value.absent(),
|
||||
this.thumbHash = const i0.Value.absent(),
|
||||
this.deletedAt = const i0.Value.absent(),
|
||||
this.uploadedAt = const i0.Value.absent(),
|
||||
this.livePhotoVideoId = const i0.Value.absent(),
|
||||
required i2.AssetVisibility visibility,
|
||||
this.stackId = const i0.Value.absent(),
|
||||
|
|
@ -1526,6 +1588,7 @@ class RemoteAssetEntityCompanion
|
|||
i0.Expression<DateTime>? localDateTime,
|
||||
i0.Expression<String>? thumbHash,
|
||||
i0.Expression<DateTime>? deletedAt,
|
||||
i0.Expression<DateTime>? uploadedAt,
|
||||
i0.Expression<String>? livePhotoVideoId,
|
||||
i0.Expression<int>? visibility,
|
||||
i0.Expression<String>? stackId,
|
||||
|
|
@ -1547,6 +1610,7 @@ class RemoteAssetEntityCompanion
|
|||
if (localDateTime != null) 'local_date_time': localDateTime,
|
||||
if (thumbHash != null) 'thumb_hash': thumbHash,
|
||||
if (deletedAt != null) 'deleted_at': deletedAt,
|
||||
if (uploadedAt != null) 'uploaded_at': uploadedAt,
|
||||
if (livePhotoVideoId != null) 'live_photo_video_id': livePhotoVideoId,
|
||||
if (visibility != null) 'visibility': visibility,
|
||||
if (stackId != null) 'stack_id': stackId,
|
||||
|
|
@ -1570,6 +1634,7 @@ class RemoteAssetEntityCompanion
|
|||
i0.Value<DateTime?>? localDateTime,
|
||||
i0.Value<String?>? thumbHash,
|
||||
i0.Value<DateTime?>? deletedAt,
|
||||
i0.Value<DateTime?>? uploadedAt,
|
||||
i0.Value<String?>? livePhotoVideoId,
|
||||
i0.Value<i2.AssetVisibility>? visibility,
|
||||
i0.Value<String?>? stackId,
|
||||
|
|
@ -1591,6 +1656,7 @@ class RemoteAssetEntityCompanion
|
|||
localDateTime: localDateTime ?? this.localDateTime,
|
||||
thumbHash: thumbHash ?? this.thumbHash,
|
||||
deletedAt: deletedAt ?? this.deletedAt,
|
||||
uploadedAt: uploadedAt ?? this.uploadedAt,
|
||||
livePhotoVideoId: livePhotoVideoId ?? this.livePhotoVideoId,
|
||||
visibility: visibility ?? this.visibility,
|
||||
stackId: stackId ?? this.stackId,
|
||||
|
|
@ -1646,6 +1712,9 @@ class RemoteAssetEntityCompanion
|
|||
if (deletedAt.present) {
|
||||
map['deleted_at'] = i0.Variable<DateTime>(deletedAt.value);
|
||||
}
|
||||
if (uploadedAt.present) {
|
||||
map['uploaded_at'] = i0.Variable<DateTime>(uploadedAt.value);
|
||||
}
|
||||
if (livePhotoVideoId.present) {
|
||||
map['live_photo_video_id'] = i0.Variable<String>(livePhotoVideoId.value);
|
||||
}
|
||||
|
|
@ -1683,6 +1752,7 @@ class RemoteAssetEntityCompanion
|
|||
..write('localDateTime: $localDateTime, ')
|
||||
..write('thumbHash: $thumbHash, ')
|
||||
..write('deletedAt: $deletedAt, ')
|
||||
..write('uploadedAt: $uploadedAt, ')
|
||||
..write('livePhotoVideoId: $livePhotoVideoId, ')
|
||||
..write('visibility: $visibility, ')
|
||||
..write('stackId: $stackId, ')
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@ class Drift extends $Drift {
|
|||
}
|
||||
|
||||
@override
|
||||
int get schemaVersion => 25;
|
||||
int get schemaVersion => 26;
|
||||
|
||||
@override
|
||||
MigrationStrategy get migration => MigrationStrategy(
|
||||
|
|
@ -267,6 +267,9 @@ class Drift extends $Drift {
|
|||
from24To25: (m, v25) async {
|
||||
await m.createTable(v25.metadata);
|
||||
},
|
||||
from25To26: (m, v26) async {
|
||||
await m.addColumn(v26.remoteAssetEntity, v26.remoteAssetEntity.uploadedAt);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -12943,6 +12943,602 @@ i1.GeneratedColumn<String> _column_211(String aliasedName) =>
|
|||
type: i1.DriftSqlType.string,
|
||||
$customConstraints: 'NOT NULL',
|
||||
);
|
||||
|
||||
final class Schema26 extends i0.VersionedSchema {
|
||||
Schema26({required super.database}) : super(version: 26);
|
||||
@override
|
||||
late final List<i1.DatabaseSchemaEntity> entities = [
|
||||
userEntity,
|
||||
remoteAssetEntity,
|
||||
stackEntity,
|
||||
localAssetEntity,
|
||||
remoteAlbumEntity,
|
||||
localAlbumEntity,
|
||||
localAlbumAssetEntity,
|
||||
idxLocalAlbumAssetAlbumAsset,
|
||||
idxLocalAssetChecksum,
|
||||
idxLocalAssetCloudId,
|
||||
idxStackPrimaryAssetId,
|
||||
idxRemoteAssetOwnerChecksum,
|
||||
uQRemoteAssetsOwnerChecksum,
|
||||
uQRemoteAssetsOwnerLibraryChecksum,
|
||||
idxRemoteAssetChecksum,
|
||||
idxRemoteAssetStackId,
|
||||
idxRemoteAssetLocalDateTimeDay,
|
||||
idxRemoteAssetLocalDateTimeMonth,
|
||||
authUserEntity,
|
||||
userMetadataEntity,
|
||||
partnerEntity,
|
||||
remoteExifEntity,
|
||||
remoteAlbumAssetEntity,
|
||||
remoteAlbumUserEntity,
|
||||
remoteAssetCloudIdEntity,
|
||||
memoryEntity,
|
||||
memoryAssetEntity,
|
||||
personEntity,
|
||||
assetFaceEntity,
|
||||
storeEntity,
|
||||
trashedLocalAssetEntity,
|
||||
assetEditEntity,
|
||||
metadata,
|
||||
idxPartnerSharedWithId,
|
||||
idxLatLng,
|
||||
idxRemoteAlbumAssetAlbumAsset,
|
||||
idxRemoteAssetCloudId,
|
||||
idxPersonOwnerId,
|
||||
idxAssetFacePersonId,
|
||||
idxAssetFaceAssetId,
|
||||
idxTrashedLocalAssetChecksum,
|
||||
idxTrashedLocalAssetAlbum,
|
||||
idxAssetEditAssetId,
|
||||
];
|
||||
late final Shape33 userEntity = Shape33(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'user_entity',
|
||||
withoutRowId: true,
|
||||
isStrict: true,
|
||||
tableConstraints: ['PRIMARY KEY(id)'],
|
||||
columns: [
|
||||
_column_107,
|
||||
_column_108,
|
||||
_column_109,
|
||||
_column_110,
|
||||
_column_111,
|
||||
_column_112,
|
||||
],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape50 remoteAssetEntity = Shape50(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'remote_asset_entity',
|
||||
withoutRowId: true,
|
||||
isStrict: true,
|
||||
tableConstraints: ['PRIMARY KEY(id)'],
|
||||
columns: [
|
||||
_column_108,
|
||||
_column_113,
|
||||
_column_114,
|
||||
_column_115,
|
||||
_column_116,
|
||||
_column_117,
|
||||
_column_118,
|
||||
_column_107,
|
||||
_column_119,
|
||||
_column_120,
|
||||
_column_121,
|
||||
_column_122,
|
||||
_column_123,
|
||||
_column_124,
|
||||
_column_212,
|
||||
_column_125,
|
||||
_column_126,
|
||||
_column_127,
|
||||
_column_128,
|
||||
_column_129,
|
||||
],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape35 stackEntity = Shape35(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'stack_entity',
|
||||
withoutRowId: true,
|
||||
isStrict: true,
|
||||
tableConstraints: ['PRIMARY KEY(id)'],
|
||||
columns: [
|
||||
_column_107,
|
||||
_column_114,
|
||||
_column_115,
|
||||
_column_121,
|
||||
_column_130,
|
||||
],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape36 localAssetEntity = Shape36(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'local_asset_entity',
|
||||
withoutRowId: true,
|
||||
isStrict: true,
|
||||
tableConstraints: ['PRIMARY KEY(id)'],
|
||||
columns: [
|
||||
_column_108,
|
||||
_column_113,
|
||||
_column_114,
|
||||
_column_115,
|
||||
_column_116,
|
||||
_column_117,
|
||||
_column_118,
|
||||
_column_107,
|
||||
_column_131,
|
||||
_column_120,
|
||||
_column_132,
|
||||
_column_133,
|
||||
_column_134,
|
||||
_column_135,
|
||||
_column_136,
|
||||
_column_137,
|
||||
],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape48 remoteAlbumEntity = Shape48(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'remote_album_entity',
|
||||
withoutRowId: true,
|
||||
isStrict: true,
|
||||
tableConstraints: ['PRIMARY KEY(id)'],
|
||||
columns: [
|
||||
_column_107,
|
||||
_column_108,
|
||||
_column_138,
|
||||
_column_114,
|
||||
_column_115,
|
||||
_column_139,
|
||||
_column_140,
|
||||
_column_141,
|
||||
],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape38 localAlbumEntity = Shape38(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'local_album_entity',
|
||||
withoutRowId: true,
|
||||
isStrict: true,
|
||||
tableConstraints: ['PRIMARY KEY(id)'],
|
||||
columns: [
|
||||
_column_107,
|
||||
_column_108,
|
||||
_column_115,
|
||||
_column_142,
|
||||
_column_143,
|
||||
_column_144,
|
||||
_column_145,
|
||||
],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape39 localAlbumAssetEntity = Shape39(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'local_album_asset_entity',
|
||||
withoutRowId: true,
|
||||
isStrict: true,
|
||||
tableConstraints: ['PRIMARY KEY(asset_id, album_id)'],
|
||||
columns: [_column_146, _column_147, _column_145],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
final i1.Index idxLocalAlbumAssetAlbumAsset = i1.Index(
|
||||
'idx_local_album_asset_album_asset',
|
||||
'CREATE INDEX IF NOT EXISTS idx_local_album_asset_album_asset ON local_album_asset_entity (album_id, asset_id)',
|
||||
);
|
||||
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 idxStackPrimaryAssetId = i1.Index(
|
||||
'idx_stack_primary_asset_id',
|
||||
'CREATE INDEX IF NOT EXISTS idx_stack_primary_asset_id ON stack_entity (primary_asset_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)',
|
||||
);
|
||||
final i1.Index idxRemoteAssetStackId = i1.Index(
|
||||
'idx_remote_asset_stack_id',
|
||||
'CREATE INDEX IF NOT EXISTS idx_remote_asset_stack_id ON remote_asset_entity (stack_id)',
|
||||
);
|
||||
final i1.Index idxRemoteAssetLocalDateTimeDay = i1.Index(
|
||||
'idx_remote_asset_local_date_time_day',
|
||||
'CREATE INDEX IF NOT EXISTS idx_remote_asset_local_date_time_day ON remote_asset_entity (STRFTIME(\'%Y-%m-%d\', local_date_time))',
|
||||
);
|
||||
final i1.Index idxRemoteAssetLocalDateTimeMonth = i1.Index(
|
||||
'idx_remote_asset_local_date_time_month',
|
||||
'CREATE INDEX IF NOT EXISTS idx_remote_asset_local_date_time_month ON remote_asset_entity (STRFTIME(\'%Y-%m\', local_date_time))',
|
||||
);
|
||||
late final Shape40 authUserEntity = Shape40(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'auth_user_entity',
|
||||
withoutRowId: true,
|
||||
isStrict: true,
|
||||
tableConstraints: ['PRIMARY KEY(id)'],
|
||||
columns: [
|
||||
_column_107,
|
||||
_column_108,
|
||||
_column_109,
|
||||
_column_148,
|
||||
_column_110,
|
||||
_column_111,
|
||||
_column_149,
|
||||
_column_150,
|
||||
_column_151,
|
||||
_column_152,
|
||||
],
|
||||
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_153, _column_154, _column_155],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape41 partnerEntity = Shape41(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'partner_entity',
|
||||
withoutRowId: true,
|
||||
isStrict: true,
|
||||
tableConstraints: ['PRIMARY KEY(shared_by_id, shared_with_id)'],
|
||||
columns: [_column_156, _column_157, _column_158],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape42 remoteExifEntity = Shape42(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'remote_exif_entity',
|
||||
withoutRowId: true,
|
||||
isStrict: true,
|
||||
tableConstraints: ['PRIMARY KEY(asset_id)'],
|
||||
columns: [
|
||||
_column_159,
|
||||
_column_160,
|
||||
_column_161,
|
||||
_column_162,
|
||||
_column_163,
|
||||
_column_164,
|
||||
_column_117,
|
||||
_column_116,
|
||||
_column_165,
|
||||
_column_166,
|
||||
_column_167,
|
||||
_column_168,
|
||||
_column_135,
|
||||
_column_136,
|
||||
_column_169,
|
||||
_column_170,
|
||||
_column_171,
|
||||
_column_172,
|
||||
_column_173,
|
||||
_column_174,
|
||||
_column_175,
|
||||
_column_176,
|
||||
],
|
||||
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_159, _column_177],
|
||||
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_177, _column_153, _column_178],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape43 remoteAssetCloudIdEntity = Shape43(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'remote_asset_cloud_id_entity',
|
||||
withoutRowId: true,
|
||||
isStrict: true,
|
||||
tableConstraints: ['PRIMARY KEY(asset_id)'],
|
||||
columns: [
|
||||
_column_159,
|
||||
_column_179,
|
||||
_column_180,
|
||||
_column_134,
|
||||
_column_135,
|
||||
_column_136,
|
||||
],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape44 memoryEntity = Shape44(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'memory_entity',
|
||||
withoutRowId: true,
|
||||
isStrict: true,
|
||||
tableConstraints: ['PRIMARY KEY(id)'],
|
||||
columns: [
|
||||
_column_107,
|
||||
_column_114,
|
||||
_column_115,
|
||||
_column_124,
|
||||
_column_121,
|
||||
_column_113,
|
||||
_column_181,
|
||||
_column_182,
|
||||
_column_183,
|
||||
_column_184,
|
||||
_column_185,
|
||||
_column_186,
|
||||
],
|
||||
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_159, _column_187],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape45 personEntity = Shape45(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'person_entity',
|
||||
withoutRowId: true,
|
||||
isStrict: true,
|
||||
tableConstraints: ['PRIMARY KEY(id)'],
|
||||
columns: [
|
||||
_column_107,
|
||||
_column_114,
|
||||
_column_115,
|
||||
_column_121,
|
||||
_column_108,
|
||||
_column_188,
|
||||
_column_189,
|
||||
_column_190,
|
||||
_column_191,
|
||||
_column_192,
|
||||
],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape46 assetFaceEntity = Shape46(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'asset_face_entity',
|
||||
withoutRowId: true,
|
||||
isStrict: true,
|
||||
tableConstraints: ['PRIMARY KEY(id)'],
|
||||
columns: [
|
||||
_column_107,
|
||||
_column_159,
|
||||
_column_193,
|
||||
_column_194,
|
||||
_column_195,
|
||||
_column_196,
|
||||
_column_197,
|
||||
_column_198,
|
||||
_column_199,
|
||||
_column_200,
|
||||
_column_201,
|
||||
_column_124,
|
||||
],
|
||||
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_202, _column_203, _column_204],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape47 trashedLocalAssetEntity = Shape47(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'trashed_local_asset_entity',
|
||||
withoutRowId: true,
|
||||
isStrict: true,
|
||||
tableConstraints: ['PRIMARY KEY(id, album_id)'],
|
||||
columns: [
|
||||
_column_108,
|
||||
_column_113,
|
||||
_column_114,
|
||||
_column_115,
|
||||
_column_116,
|
||||
_column_117,
|
||||
_column_118,
|
||||
_column_107,
|
||||
_column_205,
|
||||
_column_131,
|
||||
_column_120,
|
||||
_column_132,
|
||||
_column_206,
|
||||
_column_137,
|
||||
],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape32 assetEditEntity = Shape32(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'asset_edit_entity',
|
||||
withoutRowId: true,
|
||||
isStrict: true,
|
||||
tableConstraints: ['PRIMARY KEY(id)'],
|
||||
columns: [
|
||||
_column_107,
|
||||
_column_159,
|
||||
_column_207,
|
||||
_column_208,
|
||||
_column_209,
|
||||
],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape49 metadata = Shape49(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'metadata',
|
||||
withoutRowId: true,
|
||||
isStrict: true,
|
||||
tableConstraints: ['PRIMARY KEY("key")'],
|
||||
columns: [_column_210, _column_211, _column_115],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
final i1.Index idxPartnerSharedWithId = i1.Index(
|
||||
'idx_partner_shared_with_id',
|
||||
'CREATE INDEX IF NOT EXISTS idx_partner_shared_with_id ON partner_entity (shared_with_id)',
|
||||
);
|
||||
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 idxRemoteAlbumAssetAlbumAsset = i1.Index(
|
||||
'idx_remote_album_asset_album_asset',
|
||||
'CREATE INDEX IF NOT EXISTS idx_remote_album_asset_album_asset ON remote_album_asset_entity (album_id, asset_id)',
|
||||
);
|
||||
final i1.Index idxRemoteAssetCloudId = i1.Index(
|
||||
'idx_remote_asset_cloud_id',
|
||||
'CREATE INDEX IF NOT EXISTS idx_remote_asset_cloud_id ON remote_asset_cloud_id_entity (cloud_id)',
|
||||
);
|
||||
final i1.Index idxPersonOwnerId = i1.Index(
|
||||
'idx_person_owner_id',
|
||||
'CREATE INDEX IF NOT EXISTS idx_person_owner_id ON person_entity (owner_id)',
|
||||
);
|
||||
final i1.Index idxAssetFacePersonId = i1.Index(
|
||||
'idx_asset_face_person_id',
|
||||
'CREATE INDEX IF NOT EXISTS idx_asset_face_person_id ON asset_face_entity (person_id)',
|
||||
);
|
||||
final i1.Index idxAssetFaceAssetId = i1.Index(
|
||||
'idx_asset_face_asset_id',
|
||||
'CREATE INDEX IF NOT EXISTS idx_asset_face_asset_id ON asset_face_entity (asset_id)',
|
||||
);
|
||||
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)',
|
||||
);
|
||||
final i1.Index idxAssetEditAssetId = i1.Index(
|
||||
'idx_asset_edit_asset_id',
|
||||
'CREATE INDEX IF NOT EXISTS idx_asset_edit_asset_id ON asset_edit_entity (asset_id)',
|
||||
);
|
||||
}
|
||||
|
||||
class Shape50 extends i0.VersionedTable {
|
||||
Shape50({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<String> get createdAt =>
|
||||
columnsByName['created_at']! as i1.GeneratedColumn<String>;
|
||||
i1.GeneratedColumn<String> get updatedAt =>
|
||||
columnsByName['updated_at']! as i1.GeneratedColumn<String>;
|
||||
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 durationMs =>
|
||||
columnsByName['duration_ms']! 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<int> get isFavorite =>
|
||||
columnsByName['is_favorite']! as i1.GeneratedColumn<int>;
|
||||
i1.GeneratedColumn<String> get ownerId =>
|
||||
columnsByName['owner_id']! as i1.GeneratedColumn<String>;
|
||||
i1.GeneratedColumn<String> get localDateTime =>
|
||||
columnsByName['local_date_time']! as i1.GeneratedColumn<String>;
|
||||
i1.GeneratedColumn<String> get thumbHash =>
|
||||
columnsByName['thumb_hash']! as i1.GeneratedColumn<String>;
|
||||
i1.GeneratedColumn<String> get deletedAt =>
|
||||
columnsByName['deleted_at']! as i1.GeneratedColumn<String>;
|
||||
i1.GeneratedColumn<String> get uploadedAt =>
|
||||
columnsByName['uploaded_at']! as i1.GeneratedColumn<String>;
|
||||
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<int> get isEdited =>
|
||||
columnsByName['is_edited']! as i1.GeneratedColumn<int>;
|
||||
}
|
||||
|
||||
i1.GeneratedColumn<String> _column_212(String aliasedName) =>
|
||||
i1.GeneratedColumn<String>(
|
||||
'uploaded_at',
|
||||
aliasedName,
|
||||
true,
|
||||
type: i1.DriftSqlType.string,
|
||||
$customConstraints: 'NULL',
|
||||
);
|
||||
i0.MigrationStepWithVersion migrationSteps({
|
||||
required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2,
|
||||
required Future<void> Function(i1.Migrator m, Schema3 schema) from2To3,
|
||||
|
|
@ -12968,6 +13564,7 @@ i0.MigrationStepWithVersion migrationSteps({
|
|||
required Future<void> Function(i1.Migrator m, Schema23 schema) from22To23,
|
||||
required Future<void> Function(i1.Migrator m, Schema24 schema) from23To24,
|
||||
required Future<void> Function(i1.Migrator m, Schema25 schema) from24To25,
|
||||
required Future<void> Function(i1.Migrator m, Schema26 schema) from25To26,
|
||||
}) {
|
||||
return (currentVersion, database) async {
|
||||
switch (currentVersion) {
|
||||
|
|
@ -13091,6 +13688,11 @@ i0.MigrationStepWithVersion migrationSteps({
|
|||
final migrator = i1.Migrator(database, schema);
|
||||
await from24To25(migrator, schema);
|
||||
return 25;
|
||||
case 25:
|
||||
final schema = Schema26(database: database);
|
||||
final migrator = i1.Migrator(database, schema);
|
||||
await from25To26(migrator, schema);
|
||||
return 26;
|
||||
default:
|
||||
throw ArgumentError.value('Unknown migration from $currentVersion');
|
||||
}
|
||||
|
|
@ -13122,6 +13724,7 @@ i1.OnUpgrade stepByStep({
|
|||
required Future<void> Function(i1.Migrator m, Schema23 schema) from22To23,
|
||||
required Future<void> Function(i1.Migrator m, Schema24 schema) from23To24,
|
||||
required Future<void> Function(i1.Migrator m, Schema25 schema) from24To25,
|
||||
required Future<void> Function(i1.Migrator m, Schema26 schema) from25To26,
|
||||
}) => i0.VersionedSchema.stepByStepHelper(
|
||||
step: migrationSteps(
|
||||
from1To2: from1To2,
|
||||
|
|
@ -13148,5 +13751,6 @@ i1.OnUpgrade stepByStep({
|
|||
from22To23: from22To23,
|
||||
from23To24: from23To24,
|
||||
from24To25: from24To25,
|
||||
from25To26: from25To26,
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -191,6 +191,7 @@ class SyncStreamRepository extends DriftDatabaseRepository {
|
|||
type: Value(asset.type.toAssetType()),
|
||||
createdAt: Value.absentIfNull(asset.fileCreatedAt),
|
||||
updatedAt: Value.absentIfNull(asset.fileModifiedAt),
|
||||
uploadedAt: Value(asset.createdAt),
|
||||
durationMs: Value(asset.duration?.toDuration()?.inMilliseconds ?? 0),
|
||||
checksum: Value(asset.checksum),
|
||||
isFavorite: Value(asset.isFavorite),
|
||||
|
|
@ -229,6 +230,7 @@ class SyncStreamRepository extends DriftDatabaseRepository {
|
|||
type: Value(asset.type.toAssetType()),
|
||||
createdAt: Value.absentIfNull(asset.fileCreatedAt),
|
||||
updatedAt: Value.absentIfNull(asset.fileModifiedAt),
|
||||
uploadedAt: Value(asset.createdAt),
|
||||
durationMs: Value(asset.duration),
|
||||
checksum: Value(asset.checksum),
|
||||
isFavorite: Value(asset.isFavorite),
|
||||
|
|
|
|||
|
|
@ -79,6 +79,7 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
|
|||
type: row.type,
|
||||
createdAt: row.createdAt,
|
||||
updatedAt: row.updatedAt,
|
||||
uploadedAt: row.uploadedAt,
|
||||
thumbHash: row.thumbHash,
|
||||
width: row.width,
|
||||
height: row.height,
|
||||
|
|
@ -317,6 +318,17 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
|
|||
origin: TimelineOrigin.remoteAssets,
|
||||
);
|
||||
|
||||
TimelineQuery recentlyAdded(String userId, GroupAssetsBy groupBy) => _remoteQueryBuilder(
|
||||
filter: (row) =>
|
||||
row.uploadedAt.isNotNull() &
|
||||
row.deletedAt.isNull() &
|
||||
row.ownerId.equals(userId) &
|
||||
(row.visibility.equalsValue(AssetVisibility.timeline) | row.visibility.equalsValue(AssetVisibility.archive)),
|
||||
origin: TimelineOrigin.recentlyAdded,
|
||||
groupBy: groupBy,
|
||||
sortBy: SortAssetsBy.uploaded,
|
||||
);
|
||||
|
||||
TimelineQuery favorite(String userId, GroupAssetsBy groupBy) => _remoteQueryBuilder(
|
||||
filter: (row) =>
|
||||
row.deletedAt.isNull() &
|
||||
|
|
@ -597,9 +609,10 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
|
|||
required TimelineOrigin origin,
|
||||
GroupAssetsBy groupBy = GroupAssetsBy.day,
|
||||
bool joinLocal = false,
|
||||
SortAssetsBy sortBy = SortAssetsBy.taken,
|
||||
}) {
|
||||
return (
|
||||
bucketSource: () => _watchRemoteBucket(filter: filter, groupBy: groupBy),
|
||||
bucketSource: () => _watchRemoteBucket(filter: filter, groupBy: groupBy, sortBy: sortBy),
|
||||
assetSource: (offset, count) =>
|
||||
_getRemoteAssets(filter: filter, offset: offset, count: count, joinLocal: joinLocal),
|
||||
origin: origin,
|
||||
|
|
@ -609,6 +622,7 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
|
|||
Stream<List<Bucket>> _watchRemoteBucket({
|
||||
required Expression<bool> Function($RemoteAssetEntityTable row) filter,
|
||||
GroupAssetsBy groupBy = GroupAssetsBy.day,
|
||||
SortAssetsBy sortBy = SortAssetsBy.taken,
|
||||
}) {
|
||||
if (groupBy == GroupAssetsBy.none) {
|
||||
final query = _db.remoteAssetEntity.count(where: filter);
|
||||
|
|
@ -616,7 +630,7 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
|
|||
}
|
||||
|
||||
final assetCountExp = _db.remoteAssetEntity.id.count();
|
||||
final dateExp = _db.remoteAssetEntity.effectiveCreatedAt(groupBy);
|
||||
final dateExp = _db.remoteAssetEntity.effectiveCreatedAt(groupBy, sortBy: sortBy);
|
||||
|
||||
final query = _db.remoteAssetEntity.selectOnly()
|
||||
..addColumns([assetCountExp, dateExp])
|
||||
|
|
@ -692,8 +706,13 @@ extension on Expression<DateTime> {
|
|||
}
|
||||
|
||||
extension on $RemoteAssetEntityTable {
|
||||
Expression<String> effectiveCreatedAt(GroupAssetsBy groupBy) =>
|
||||
coalesce([localDateTime.dateFmt(groupBy), createdAt.dateFmt(groupBy, toLocal: true)]);
|
||||
Expression<String> effectiveCreatedAt(GroupAssetsBy groupBy, {SortAssetsBy sortBy = SortAssetsBy.taken}) {
|
||||
if (sortBy == SortAssetsBy.uploaded) {
|
||||
return uploadedAt.dateFmt(groupBy, toLocal: true);
|
||||
}
|
||||
|
||||
return coalesce([localDateTime.dateFmt(groupBy), createdAt.dateFmt(groupBy, toLocal: true)]);
|
||||
}
|
||||
}
|
||||
|
||||
extension on String {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,32 @@
|
|||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/timeline/timeline.widget.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
|
||||
import 'package:immich_mobile/providers/user.provider.dart';
|
||||
import 'package:immich_mobile/widgets/common/mesmerizing_sliver_app_bar.dart';
|
||||
|
||||
@RoutePage()
|
||||
class DriftRecentlyAddedPage extends StatelessWidget {
|
||||
const DriftRecentlyAddedPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ProviderScope(
|
||||
overrides: [
|
||||
timelineServiceProvider.overrideWith((ref) {
|
||||
final user = ref.watch(currentUserProvider);
|
||||
if (user == null) {
|
||||
throw Exception('User must be logged in to access recently taken');
|
||||
}
|
||||
|
||||
final timelineService = ref.watch(timelineFactoryProvider).recentlyAdded(user.id);
|
||||
ref.onDispose(timelineService.dispose);
|
||||
return timelineService;
|
||||
}),
|
||||
],
|
||||
child: Timeline(appBar: MesmerizingSliverAppBar(title: 'recently_added'.t())),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -13,6 +13,7 @@ import 'package:immich_mobile/domain/models/timeline.model.dart';
|
|||
import 'package:immich_mobile/domain/services/timeline.service.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||
import 'package:immich_mobile/generated/translations.g.dart';
|
||||
import 'package:immich_mobile/models/search/search_filter.model.dart';
|
||||
import 'package:immich_mobile/presentation/pages/search/paginated_search.provider.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/bottom_sheet/general_bottom_sheet.widget.dart';
|
||||
|
|
@ -879,6 +880,12 @@ class _QuickLinkList extends StatelessWidget {
|
|||
isTop: true,
|
||||
onTap: () => context.pushRoute(const DriftRecentlyTakenRoute()),
|
||||
),
|
||||
_QuickLink(
|
||||
title: context.t.recently_added,
|
||||
icon: Icons.upload_outlined,
|
||||
isTop: true,
|
||||
onTap: () => context.pushRoute(const DriftRecentlyAddedRoute()),
|
||||
),
|
||||
_QuickLink(
|
||||
title: 'videos'.t(context: context),
|
||||
icon: Icons.play_circle_outline_rounded,
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@ import 'package:immich_mobile/presentation/pages/drift_person.page.dart';
|
|||
import 'package:immich_mobile/presentation/pages/drift_place.page.dart';
|
||||
import 'package:immich_mobile/presentation/pages/drift_place_detail.page.dart';
|
||||
import 'package:immich_mobile/presentation/pages/drift_recently_taken.page.dart';
|
||||
import 'package:immich_mobile/presentation/pages/drift_recently_added.page.dart';
|
||||
import 'package:immich_mobile/presentation/pages/drift_remote_album.page.dart';
|
||||
import 'package:immich_mobile/presentation/pages/drift_trash.page.dart';
|
||||
import 'package:immich_mobile/presentation/pages/drift_user_selection.page.dart';
|
||||
|
|
@ -168,6 +169,7 @@ class AppRouter extends RootStackRouter {
|
|||
AutoRoute(page: DriftAssetSelectionTimelineRoute.page, guards: [_authGuard, _duplicateGuard]),
|
||||
AutoRoute(page: DriftPartnerDetailRoute.page, guards: [_authGuard, _duplicateGuard]),
|
||||
AutoRoute(page: DriftRecentlyTakenRoute.page, guards: [_authGuard, _duplicateGuard]),
|
||||
AutoRoute(page: DriftRecentlyAddedRoute.page, guards: [_authGuard, _duplicateGuard]),
|
||||
AutoRoute(page: DriftLocalAlbumsRoute.page, guards: [_authGuard, _duplicateGuard]),
|
||||
AutoRoute(page: DriftCreateAlbumRoute.page, guards: [_authGuard, _duplicateGuard]),
|
||||
AutoRoute(page: DriftPlaceRoute.page, guards: [_authGuard, _duplicateGuard]),
|
||||
|
|
|
|||
|
|
@ -1047,6 +1047,22 @@ class DriftPlaceRouteArgs {
|
|||
int get hashCode => key.hashCode ^ currentLocation.hashCode;
|
||||
}
|
||||
|
||||
/// generated route for
|
||||
/// [DriftRecentlyAddedPage]
|
||||
class DriftRecentlyAddedRoute extends PageRouteInfo<void> {
|
||||
const DriftRecentlyAddedRoute({List<PageRouteInfo>? children})
|
||||
: super(DriftRecentlyAddedRoute.name, initialChildren: children);
|
||||
|
||||
static const String name = 'DriftRecentlyAddedRoute';
|
||||
|
||||
static PageInfo page = PageInfo(
|
||||
name,
|
||||
builder: (data) {
|
||||
return const DriftRecentlyAddedPage();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// generated route for
|
||||
/// [DriftRecentlyTakenPage]
|
||||
class DriftRecentlyTakenRoute extends PageRouteInfo<void> {
|
||||
|
|
|
|||
|
|
@ -329,7 +329,7 @@ class ForegroundUploadService {
|
|||
'fileCreatedAt': asset.createdAt.toUtc().toIso8601String(),
|
||||
'fileModifiedAt': asset.updatedAt.toUtc().toIso8601String(),
|
||||
'isFavorite': asset.isFavorite.toString(),
|
||||
'duration': asset.duration.toString(),
|
||||
'duration': (asset.durationMs ?? 0).toString(),
|
||||
};
|
||||
|
||||
// Upload live photo video first if available
|
||||
|
|
|
|||
|
|
@ -375,6 +375,7 @@ Class | Method | HTTP request | Description
|
|||
- [AssetMetadataUpsertItemDto](doc//AssetMetadataUpsertItemDto.md)
|
||||
- [AssetOcrResponseDto](doc//AssetOcrResponseDto.md)
|
||||
- [AssetOrder](doc//AssetOrder.md)
|
||||
- [AssetOrderBy](doc//AssetOrderBy.md)
|
||||
- [AssetRejectReason](doc//AssetRejectReason.md)
|
||||
- [AssetResponseDto](doc//AssetResponseDto.md)
|
||||
- [AssetStackResponseDto](doc//AssetStackResponseDto.md)
|
||||
|
|
|
|||
|
|
@ -123,6 +123,7 @@ part 'model/asset_metadata_upsert_dto.dart';
|
|||
part 'model/asset_metadata_upsert_item_dto.dart';
|
||||
part 'model/asset_ocr_response_dto.dart';
|
||||
part 'model/asset_order.dart';
|
||||
part 'model/asset_order_by.dart';
|
||||
part 'model/asset_reject_reason.dart';
|
||||
part 'model/asset_response_dto.dart';
|
||||
part 'model/asset_stack_response_dto.dart';
|
||||
|
|
|
|||
|
|
@ -44,6 +44,9 @@ class TimelineApi {
|
|||
/// * [AssetOrder] order:
|
||||
/// Sort order for assets within time buckets (ASC for oldest first, DESC for newest first)
|
||||
///
|
||||
/// * [AssetOrderBy] orderBy:
|
||||
/// Date to group and order assets by (takenAt for date taken, createdAt for date added to Immich)
|
||||
///
|
||||
/// * [String] personId:
|
||||
/// Filter assets containing a specific person (face recognition)
|
||||
///
|
||||
|
|
@ -66,7 +69,7 @@ class TimelineApi {
|
|||
///
|
||||
/// * [bool] withStacked:
|
||||
/// Include stacked assets in the response. When true, only primary assets from stacks are returned.
|
||||
Future<Response> getTimeBucketWithHttpInfo(String timeBucket, { String? albumId, String? bbox, bool? isFavorite, bool? isTrashed, String? key, AssetOrder? order, String? personId, String? slug, String? tagId, String? userId, AssetVisibility? visibility, bool? withCoordinates, bool? withPartners, bool? withStacked, }) async {
|
||||
Future<Response> getTimeBucketWithHttpInfo(String timeBucket, { String? albumId, String? bbox, bool? isFavorite, bool? isTrashed, String? key, AssetOrder? order, AssetOrderBy? orderBy, String? personId, String? slug, String? tagId, String? userId, AssetVisibility? visibility, bool? withCoordinates, bool? withPartners, bool? withStacked, }) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/timeline/bucket';
|
||||
|
||||
|
|
@ -95,6 +98,9 @@ class TimelineApi {
|
|||
if (order != null) {
|
||||
queryParams.addAll(_queryParams('', 'order', order));
|
||||
}
|
||||
if (orderBy != null) {
|
||||
queryParams.addAll(_queryParams('', 'orderBy', orderBy));
|
||||
}
|
||||
if (personId != null) {
|
||||
queryParams.addAll(_queryParams('', 'personId', personId));
|
||||
}
|
||||
|
|
@ -161,6 +167,9 @@ class TimelineApi {
|
|||
/// * [AssetOrder] order:
|
||||
/// Sort order for assets within time buckets (ASC for oldest first, DESC for newest first)
|
||||
///
|
||||
/// * [AssetOrderBy] orderBy:
|
||||
/// Date to group and order assets by (takenAt for date taken, createdAt for date added to Immich)
|
||||
///
|
||||
/// * [String] personId:
|
||||
/// Filter assets containing a specific person (face recognition)
|
||||
///
|
||||
|
|
@ -183,8 +192,8 @@ class TimelineApi {
|
|||
///
|
||||
/// * [bool] withStacked:
|
||||
/// Include stacked assets in the response. When true, only primary assets from stacks are returned.
|
||||
Future<TimeBucketAssetResponseDto?> getTimeBucket(String timeBucket, { String? albumId, String? bbox, bool? isFavorite, bool? isTrashed, String? key, AssetOrder? order, String? personId, String? slug, String? tagId, String? userId, AssetVisibility? visibility, bool? withCoordinates, bool? withPartners, bool? withStacked, }) async {
|
||||
final response = await getTimeBucketWithHttpInfo(timeBucket, albumId: albumId, bbox: bbox, isFavorite: isFavorite, isTrashed: isTrashed, key: key, order: order, personId: personId, slug: slug, tagId: tagId, userId: userId, visibility: visibility, withCoordinates: withCoordinates, withPartners: withPartners, withStacked: withStacked, );
|
||||
Future<TimeBucketAssetResponseDto?> getTimeBucket(String timeBucket, { String? albumId, String? bbox, bool? isFavorite, bool? isTrashed, String? key, AssetOrder? order, AssetOrderBy? orderBy, String? personId, String? slug, String? tagId, String? userId, AssetVisibility? visibility, bool? withCoordinates, bool? withPartners, bool? withStacked, }) async {
|
||||
final response = await getTimeBucketWithHttpInfo(timeBucket, albumId: albumId, bbox: bbox, isFavorite: isFavorite, isTrashed: isTrashed, key: key, order: order, orderBy: orderBy, personId: personId, slug: slug, tagId: tagId, userId: userId, visibility: visibility, withCoordinates: withCoordinates, withPartners: withPartners, withStacked: withStacked, );
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||
}
|
||||
|
|
@ -223,6 +232,9 @@ class TimelineApi {
|
|||
/// * [AssetOrder] order:
|
||||
/// Sort order for assets within time buckets (ASC for oldest first, DESC for newest first)
|
||||
///
|
||||
/// * [AssetOrderBy] orderBy:
|
||||
/// Date to group and order assets by (takenAt for date taken, createdAt for date added to Immich)
|
||||
///
|
||||
/// * [String] personId:
|
||||
/// Filter assets containing a specific person (face recognition)
|
||||
///
|
||||
|
|
@ -245,7 +257,7 @@ class TimelineApi {
|
|||
///
|
||||
/// * [bool] withStacked:
|
||||
/// Include stacked assets in the response. When true, only primary assets from stacks are returned.
|
||||
Future<Response> getTimeBucketsWithHttpInfo({ String? albumId, String? bbox, bool? isFavorite, bool? isTrashed, String? key, AssetOrder? order, String? personId, String? slug, String? tagId, String? userId, AssetVisibility? visibility, bool? withCoordinates, bool? withPartners, bool? withStacked, }) async {
|
||||
Future<Response> getTimeBucketsWithHttpInfo({ String? albumId, String? bbox, bool? isFavorite, bool? isTrashed, String? key, AssetOrder? order, AssetOrderBy? orderBy, String? personId, String? slug, String? tagId, String? userId, AssetVisibility? visibility, bool? withCoordinates, bool? withPartners, bool? withStacked, }) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/timeline/buckets';
|
||||
|
||||
|
|
@ -274,6 +286,9 @@ class TimelineApi {
|
|||
if (order != null) {
|
||||
queryParams.addAll(_queryParams('', 'order', order));
|
||||
}
|
||||
if (orderBy != null) {
|
||||
queryParams.addAll(_queryParams('', 'orderBy', orderBy));
|
||||
}
|
||||
if (personId != null) {
|
||||
queryParams.addAll(_queryParams('', 'personId', personId));
|
||||
}
|
||||
|
|
@ -336,6 +351,9 @@ class TimelineApi {
|
|||
/// * [AssetOrder] order:
|
||||
/// Sort order for assets within time buckets (ASC for oldest first, DESC for newest first)
|
||||
///
|
||||
/// * [AssetOrderBy] orderBy:
|
||||
/// Date to group and order assets by (takenAt for date taken, createdAt for date added to Immich)
|
||||
///
|
||||
/// * [String] personId:
|
||||
/// Filter assets containing a specific person (face recognition)
|
||||
///
|
||||
|
|
@ -358,8 +376,8 @@ class TimelineApi {
|
|||
///
|
||||
/// * [bool] withStacked:
|
||||
/// Include stacked assets in the response. When true, only primary assets from stacks are returned.
|
||||
Future<List<TimeBucketsResponseDto>?> getTimeBuckets({ String? albumId, String? bbox, bool? isFavorite, bool? isTrashed, String? key, AssetOrder? order, String? personId, String? slug, String? tagId, String? userId, AssetVisibility? visibility, bool? withCoordinates, bool? withPartners, bool? withStacked, }) async {
|
||||
final response = await getTimeBucketsWithHttpInfo( albumId: albumId, bbox: bbox, isFavorite: isFavorite, isTrashed: isTrashed, key: key, order: order, personId: personId, slug: slug, tagId: tagId, userId: userId, visibility: visibility, withCoordinates: withCoordinates, withPartners: withPartners, withStacked: withStacked, );
|
||||
Future<List<TimeBucketsResponseDto>?> getTimeBuckets({ String? albumId, String? bbox, bool? isFavorite, bool? isTrashed, String? key, AssetOrder? order, AssetOrderBy? orderBy, String? personId, String? slug, String? tagId, String? userId, AssetVisibility? visibility, bool? withCoordinates, bool? withPartners, bool? withStacked, }) async {
|
||||
final response = await getTimeBucketsWithHttpInfo( albumId: albumId, bbox: bbox, isFavorite: isFavorite, isTrashed: isTrashed, key: key, order: order, orderBy: orderBy, personId: personId, slug: slug, tagId: tagId, userId: userId, visibility: visibility, withCoordinates: withCoordinates, withPartners: withPartners, withStacked: withStacked, );
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -292,6 +292,8 @@ class ApiClient {
|
|||
return AssetOcrResponseDto.fromJson(value);
|
||||
case 'AssetOrder':
|
||||
return AssetOrderTypeTransformer().decode(value);
|
||||
case 'AssetOrderBy':
|
||||
return AssetOrderByTypeTransformer().decode(value);
|
||||
case 'AssetRejectReason':
|
||||
return AssetRejectReasonTypeTransformer().decode(value);
|
||||
case 'AssetResponseDto':
|
||||
|
|
|
|||
|
|
@ -76,6 +76,9 @@ String parameterToString(dynamic value) {
|
|||
if (value is AssetOrder) {
|
||||
return AssetOrderTypeTransformer().encode(value).toString();
|
||||
}
|
||||
if (value is AssetOrderBy) {
|
||||
return AssetOrderByTypeTransformer().encode(value).toString();
|
||||
}
|
||||
if (value is AssetRejectReason) {
|
||||
return AssetRejectReasonTypeTransformer().encode(value).toString();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,85 @@
|
|||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.18
|
||||
|
||||
// ignore_for_file: unused_element, unused_import
|
||||
// ignore_for_file: always_put_required_named_parameters_first
|
||||
// ignore_for_file: constant_identifier_names
|
||||
// ignore_for_file: lines_longer_than_80_chars
|
||||
|
||||
part of openapi.api;
|
||||
|
||||
/// Asset sorting property
|
||||
class AssetOrderBy {
|
||||
/// Instantiate a new enum with the provided [value].
|
||||
const AssetOrderBy._(this.value);
|
||||
|
||||
/// The underlying value of this enum member.
|
||||
final String value;
|
||||
|
||||
@override
|
||||
String toString() => value;
|
||||
|
||||
String toJson() => value;
|
||||
|
||||
static const takenAt = AssetOrderBy._(r'takenAt');
|
||||
static const createdAt = AssetOrderBy._(r'createdAt');
|
||||
|
||||
/// List of all possible values in this [enum][AssetOrderBy].
|
||||
static const values = <AssetOrderBy>[
|
||||
takenAt,
|
||||
createdAt,
|
||||
];
|
||||
|
||||
static AssetOrderBy? fromJson(dynamic value) => AssetOrderByTypeTransformer().decode(value);
|
||||
|
||||
static List<AssetOrderBy> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <AssetOrderBy>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = AssetOrderBy.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
}
|
||||
|
||||
/// Transformation class that can [encode] an instance of [AssetOrderBy] to String,
|
||||
/// and [decode] dynamic data back to [AssetOrderBy].
|
||||
class AssetOrderByTypeTransformer {
|
||||
factory AssetOrderByTypeTransformer() => _instance ??= const AssetOrderByTypeTransformer._();
|
||||
|
||||
const AssetOrderByTypeTransformer._();
|
||||
|
||||
String encode(AssetOrderBy data) => data.value;
|
||||
|
||||
/// Decodes a [dynamic value][data] to a AssetOrderBy.
|
||||
///
|
||||
/// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully,
|
||||
/// then null is returned. However, if [allowNull] is false and the [dynamic value][data]
|
||||
/// cannot be decoded successfully, then an [UnimplementedError] is thrown.
|
||||
///
|
||||
/// The [allowNull] is very handy when an API changes and a new enum value is added or removed,
|
||||
/// and users are still using an old app with the old code.
|
||||
AssetOrderBy? decode(dynamic data, {bool allowNull = true}) {
|
||||
if (data != null) {
|
||||
switch (data) {
|
||||
case r'takenAt': return AssetOrderBy.takenAt;
|
||||
case r'createdAt': return AssetOrderBy.createdAt;
|
||||
default:
|
||||
if (!allowNull) {
|
||||
throw ArgumentError('Unknown enum value to decode: $data');
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Singleton [AssetOrderByTypeTransformer] instance.
|
||||
static AssetOrderByTypeTransformer? _instance;
|
||||
}
|
||||
|
||||
|
|
@ -14,6 +14,7 @@ class SyncAssetV1 {
|
|||
/// Returns a new [SyncAssetV1] instance.
|
||||
SyncAssetV1({
|
||||
required this.checksum,
|
||||
required this.createdAt,
|
||||
required this.deletedAt,
|
||||
required this.duration,
|
||||
required this.fileCreatedAt,
|
||||
|
|
@ -37,6 +38,9 @@ class SyncAssetV1 {
|
|||
/// Checksum
|
||||
String checksum;
|
||||
|
||||
/// Uploaded to Immich at
|
||||
DateTime? createdAt;
|
||||
|
||||
/// Deleted at
|
||||
DateTime? deletedAt;
|
||||
|
||||
|
|
@ -98,6 +102,7 @@ class SyncAssetV1 {
|
|||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is SyncAssetV1 &&
|
||||
other.checksum == checksum &&
|
||||
other.createdAt == createdAt &&
|
||||
other.deletedAt == deletedAt &&
|
||||
other.duration == duration &&
|
||||
other.fileCreatedAt == fileCreatedAt &&
|
||||
|
|
@ -121,6 +126,7 @@ class SyncAssetV1 {
|
|||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(checksum.hashCode) +
|
||||
(createdAt == null ? 0 : createdAt!.hashCode) +
|
||||
(deletedAt == null ? 0 : deletedAt!.hashCode) +
|
||||
(duration == null ? 0 : duration!.hashCode) +
|
||||
(fileCreatedAt == null ? 0 : fileCreatedAt!.hashCode) +
|
||||
|
|
@ -141,11 +147,18 @@ class SyncAssetV1 {
|
|||
(width == null ? 0 : width!.hashCode);
|
||||
|
||||
@override
|
||||
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]';
|
||||
String toString() => 'SyncAssetV1[checksum=$checksum, createdAt=$createdAt, 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() {
|
||||
final json = <String, dynamic>{};
|
||||
json[r'checksum'] = this.checksum;
|
||||
if (this.createdAt != null) {
|
||||
json[r'createdAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')
|
||||
? this.createdAt!.millisecondsSinceEpoch
|
||||
: this.createdAt!.toUtc().toIso8601String();
|
||||
} else {
|
||||
// json[r'createdAt'] = null;
|
||||
}
|
||||
if (this.deletedAt != null) {
|
||||
json[r'deletedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')
|
||||
? this.deletedAt!.millisecondsSinceEpoch
|
||||
|
|
@ -229,6 +242,7 @@ class SyncAssetV1 {
|
|||
|
||||
return SyncAssetV1(
|
||||
checksum: mapValueOfType<String>(json, r'checksum')!,
|
||||
createdAt: mapDateTime(json, r'createdAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'),
|
||||
deletedAt: mapDateTime(json, r'deletedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'),
|
||||
duration: mapValueOfType<String>(json, r'duration'),
|
||||
fileCreatedAt: mapDateTime(json, r'fileCreatedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'),
|
||||
|
|
@ -295,6 +309,7 @@ class SyncAssetV1 {
|
|||
/// The list of required keys that must be present in a JSON.
|
||||
static const requiredKeys = <String>{
|
||||
'checksum',
|
||||
'createdAt',
|
||||
'deletedAt',
|
||||
'duration',
|
||||
'fileCreatedAt',
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ class SyncAssetV2 {
|
|||
/// Returns a new [SyncAssetV2] instance.
|
||||
SyncAssetV2({
|
||||
required this.checksum,
|
||||
required this.createdAt,
|
||||
required this.deletedAt,
|
||||
required this.duration,
|
||||
required this.fileCreatedAt,
|
||||
|
|
@ -37,6 +38,9 @@ class SyncAssetV2 {
|
|||
/// Checksum
|
||||
String checksum;
|
||||
|
||||
/// Uploaded to Immich at
|
||||
DateTime? createdAt;
|
||||
|
||||
/// Deleted at
|
||||
DateTime? deletedAt;
|
||||
|
||||
|
|
@ -101,6 +105,7 @@ class SyncAssetV2 {
|
|||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is SyncAssetV2 &&
|
||||
other.checksum == checksum &&
|
||||
other.createdAt == createdAt &&
|
||||
other.deletedAt == deletedAt &&
|
||||
other.duration == duration &&
|
||||
other.fileCreatedAt == fileCreatedAt &&
|
||||
|
|
@ -124,6 +129,7 @@ class SyncAssetV2 {
|
|||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(checksum.hashCode) +
|
||||
(createdAt == null ? 0 : createdAt!.hashCode) +
|
||||
(deletedAt == null ? 0 : deletedAt!.hashCode) +
|
||||
(duration == null ? 0 : duration!.hashCode) +
|
||||
(fileCreatedAt == null ? 0 : fileCreatedAt!.hashCode) +
|
||||
|
|
@ -144,11 +150,18 @@ class SyncAssetV2 {
|
|||
(width == null ? 0 : width!.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'SyncAssetV2[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]';
|
||||
String toString() => 'SyncAssetV2[checksum=$checksum, createdAt=$createdAt, 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() {
|
||||
final json = <String, dynamic>{};
|
||||
json[r'checksum'] = this.checksum;
|
||||
if (this.createdAt != null) {
|
||||
json[r'createdAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')
|
||||
? this.createdAt!.millisecondsSinceEpoch
|
||||
: this.createdAt!.toUtc().toIso8601String();
|
||||
} else {
|
||||
// json[r'createdAt'] = null;
|
||||
}
|
||||
if (this.deletedAt != null) {
|
||||
json[r'deletedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')
|
||||
? this.deletedAt!.millisecondsSinceEpoch
|
||||
|
|
@ -232,6 +245,7 @@ class SyncAssetV2 {
|
|||
|
||||
return SyncAssetV2(
|
||||
checksum: mapValueOfType<String>(json, r'checksum')!,
|
||||
createdAt: mapDateTime(json, r'createdAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'),
|
||||
deletedAt: mapDateTime(json, r'deletedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'),
|
||||
duration: mapValueOfType<int>(json, r'duration'),
|
||||
fileCreatedAt: mapDateTime(json, r'fileCreatedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'),
|
||||
|
|
@ -298,6 +312,7 @@ class SyncAssetV2 {
|
|||
/// The list of required keys that must be present in a JSON.
|
||||
static const requiredKeys = <String>{
|
||||
'checksum',
|
||||
'createdAt',
|
||||
'deletedAt',
|
||||
'duration',
|
||||
'fileCreatedAt',
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ class TimeBucketAssetResponseDto {
|
|||
TimeBucketAssetResponseDto({
|
||||
this.city = const [],
|
||||
this.country = const [],
|
||||
this.createdAt = const [],
|
||||
this.duration = const [],
|
||||
this.fileCreatedAt = const [],
|
||||
this.id = const [],
|
||||
|
|
@ -39,6 +40,9 @@ class TimeBucketAssetResponseDto {
|
|||
/// Array of country names extracted from EXIF GPS data
|
||||
List<String?> country;
|
||||
|
||||
/// Array of UTC timestamps when each asset was originally uploaded to Immich
|
||||
List<String> createdAt;
|
||||
|
||||
/// Array of video/gif durations in milliseconds (null for static images)
|
||||
List<int?> duration;
|
||||
|
||||
|
|
@ -91,6 +95,7 @@ class TimeBucketAssetResponseDto {
|
|||
bool operator ==(Object other) => identical(this, other) || other is TimeBucketAssetResponseDto &&
|
||||
_deepEquality.equals(other.city, city) &&
|
||||
_deepEquality.equals(other.country, country) &&
|
||||
_deepEquality.equals(other.createdAt, createdAt) &&
|
||||
_deepEquality.equals(other.duration, duration) &&
|
||||
_deepEquality.equals(other.fileCreatedAt, fileCreatedAt) &&
|
||||
_deepEquality.equals(other.id, id) &&
|
||||
|
|
@ -113,6 +118,7 @@ class TimeBucketAssetResponseDto {
|
|||
// ignore: unnecessary_parenthesis
|
||||
(city.hashCode) +
|
||||
(country.hashCode) +
|
||||
(createdAt.hashCode) +
|
||||
(duration.hashCode) +
|
||||
(fileCreatedAt.hashCode) +
|
||||
(id.hashCode) +
|
||||
|
|
@ -131,12 +137,13 @@ class TimeBucketAssetResponseDto {
|
|||
(visibility.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'TimeBucketAssetResponseDto[city=$city, country=$country, duration=$duration, fileCreatedAt=$fileCreatedAt, id=$id, isFavorite=$isFavorite, isImage=$isImage, isTrashed=$isTrashed, latitude=$latitude, livePhotoVideoId=$livePhotoVideoId, localOffsetHours=$localOffsetHours, longitude=$longitude, ownerId=$ownerId, projectionType=$projectionType, ratio=$ratio, stack=$stack, thumbhash=$thumbhash, visibility=$visibility]';
|
||||
String toString() => 'TimeBucketAssetResponseDto[city=$city, country=$country, createdAt=$createdAt, duration=$duration, fileCreatedAt=$fileCreatedAt, id=$id, isFavorite=$isFavorite, isImage=$isImage, isTrashed=$isTrashed, latitude=$latitude, livePhotoVideoId=$livePhotoVideoId, localOffsetHours=$localOffsetHours, longitude=$longitude, ownerId=$ownerId, projectionType=$projectionType, ratio=$ratio, stack=$stack, thumbhash=$thumbhash, visibility=$visibility]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
json[r'city'] = this.city;
|
||||
json[r'country'] = this.country;
|
||||
json[r'createdAt'] = this.createdAt;
|
||||
json[r'duration'] = this.duration;
|
||||
json[r'fileCreatedAt'] = this.fileCreatedAt;
|
||||
json[r'id'] = this.id;
|
||||
|
|
@ -171,6 +178,9 @@ class TimeBucketAssetResponseDto {
|
|||
country: json[r'country'] is Iterable
|
||||
? (json[r'country'] as Iterable).cast<String>().toList(growable: false)
|
||||
: const [],
|
||||
createdAt: json[r'createdAt'] is Iterable
|
||||
? (json[r'createdAt'] as Iterable).cast<String>().toList(growable: false)
|
||||
: const [],
|
||||
duration: json[r'duration'] is Iterable
|
||||
? (json[r'duration'] as Iterable).cast<int>().toList(growable: false)
|
||||
: const [],
|
||||
|
|
@ -268,6 +278,7 @@ class TimeBucketAssetResponseDto {
|
|||
static const requiredKeys = <String>{
|
||||
'city',
|
||||
'country',
|
||||
'createdAt',
|
||||
'duration',
|
||||
'fileCreatedAt',
|
||||
'id',
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ SyncAssetV1 _createAsset({
|
|||
isFavorite: false,
|
||||
fileCreatedAt: DateTime(2024, 1, 1),
|
||||
fileModifiedAt: DateTime(2024, 1, 1),
|
||||
createdAt: DateTime(2024, 1, 1),
|
||||
localDateTime: DateTime(2024, 1, 1),
|
||||
visibility: AssetVisibility.timeline,
|
||||
width: width,
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import 'schema_v22.dart' as v22;
|
|||
import 'schema_v23.dart' as v23;
|
||||
import 'schema_v24.dart' as v24;
|
||||
import 'schema_v25.dart' as v25;
|
||||
import 'schema_v26.dart' as v26;
|
||||
|
||||
class GeneratedHelper implements SchemaInstantiationHelper {
|
||||
@override
|
||||
|
|
@ -84,6 +85,8 @@ class GeneratedHelper implements SchemaInstantiationHelper {
|
|||
return v24.DatabaseAtV24(db);
|
||||
case 25:
|
||||
return v25.DatabaseAtV25(db);
|
||||
case 26:
|
||||
return v26.DatabaseAtV26(db);
|
||||
default:
|
||||
throw MissingSchemaException(version, versions);
|
||||
}
|
||||
|
|
@ -115,5 +118,6 @@ class GeneratedHelper implements SchemaInstantiationHelper {
|
|||
23,
|
||||
24,
|
||||
25,
|
||||
26,
|
||||
];
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -115,6 +115,7 @@ abstract final class SyncStreamStub {
|
|||
duration: '0',
|
||||
fileCreatedAt: DateTime(2025),
|
||||
fileModifiedAt: DateTime(2025, 1, 2),
|
||||
createdAt: DateTime(2025, 1, 2),
|
||||
id: id,
|
||||
isFavorite: false,
|
||||
libraryId: null,
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ void main() {
|
|||
visibility: AssetVisibility.timeline,
|
||||
createdAt: Value(createdAt),
|
||||
updatedAt: Value(createdAt),
|
||||
uploadedAt: Value(createdAt),
|
||||
localDateTime: const Value(null),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@ abstract final class TestUtils {
|
|||
type: domain.AssetType.image,
|
||||
createdAt: DateTime(2024, 1, 1),
|
||||
updatedAt: DateTime(2024, 1, 1),
|
||||
uploadedAt: DateTime(2024, 1, 1),
|
||||
durationMs: 0,
|
||||
isFavorite: false,
|
||||
width: width,
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ RemoteAsset createRemoteAsset({
|
|||
AssetType type = AssetType.image,
|
||||
DateTime? createdAt,
|
||||
DateTime? updatedAt,
|
||||
DateTime? uploadedAt,
|
||||
bool isFavorite = false,
|
||||
}) {
|
||||
return RemoteAsset(
|
||||
|
|
@ -46,6 +47,7 @@ RemoteAsset createRemoteAsset({
|
|||
ownerId: 'owner-id',
|
||||
createdAt: createdAt ?? DateTime.now(),
|
||||
updatedAt: updatedAt ?? DateTime.now(),
|
||||
uploadedAt: uploadedAt ?? DateTime.now(),
|
||||
isFavorite: isFavorite,
|
||||
isEdited: false,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -13322,6 +13322,15 @@
|
|||
"$ref": "#/components/schemas/AssetOrder"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "orderBy",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "Date to group and order assets by (takenAt for date taken, createdAt for date added to Immich)",
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/AssetOrderBy"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "personId",
|
||||
"required": false,
|
||||
|
|
@ -13512,6 +13521,15 @@
|
|||
"$ref": "#/components/schemas/AssetOrder"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "orderBy",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "Date to group and order assets by (takenAt for date taken, createdAt for date added to Immich)",
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/AssetOrderBy"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "personId",
|
||||
"required": false,
|
||||
|
|
@ -16557,6 +16575,14 @@
|
|||
],
|
||||
"type": "string"
|
||||
},
|
||||
"AssetOrderBy": {
|
||||
"description": "Asset sorting property",
|
||||
"enum": [
|
||||
"takenAt",
|
||||
"createdAt"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"AssetRejectReason": {
|
||||
"description": "Rejection reason if rejected",
|
||||
"enum": [
|
||||
|
|
@ -22914,6 +22940,14 @@
|
|||
"description": "Checksum",
|
||||
"type": "string"
|
||||
},
|
||||
"createdAt": {
|
||||
"description": "Uploaded to Immich at",
|
||||
"example": "2024-01-01T00:00:00.000Z",
|
||||
"format": "date-time",
|
||||
"nullable": true,
|
||||
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
|
||||
"type": "string"
|
||||
},
|
||||
"deletedAt": {
|
||||
"description": "Deleted at",
|
||||
"example": "2024-01-01T00:00:00.000Z",
|
||||
|
|
@ -23014,6 +23048,7 @@
|
|||
},
|
||||
"required": [
|
||||
"checksum",
|
||||
"createdAt",
|
||||
"deletedAt",
|
||||
"duration",
|
||||
"fileCreatedAt",
|
||||
|
|
@ -23041,6 +23076,14 @@
|
|||
"description": "Checksum",
|
||||
"type": "string"
|
||||
},
|
||||
"createdAt": {
|
||||
"description": "Uploaded to Immich at",
|
||||
"example": "2024-01-01T00:00:00.000Z",
|
||||
"format": "date-time",
|
||||
"nullable": true,
|
||||
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
|
||||
"type": "string"
|
||||
},
|
||||
"deletedAt": {
|
||||
"description": "Deleted at",
|
||||
"example": "2024-01-01T00:00:00.000Z",
|
||||
|
|
@ -23143,6 +23186,7 @@
|
|||
},
|
||||
"required": [
|
||||
"checksum",
|
||||
"createdAt",
|
||||
"deletedAt",
|
||||
"duration",
|
||||
"fileCreatedAt",
|
||||
|
|
@ -24991,6 +25035,13 @@
|
|||
},
|
||||
"type": "array"
|
||||
},
|
||||
"createdAt": {
|
||||
"description": "Array of UTC timestamps when each asset was originally uploaded to Immich",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"duration": {
|
||||
"description": "Array of video/gif durations in milliseconds (null for static images)",
|
||||
"items": {
|
||||
|
|
@ -25121,6 +25172,7 @@
|
|||
"required": [
|
||||
"city",
|
||||
"country",
|
||||
"createdAt",
|
||||
"duration",
|
||||
"fileCreatedAt",
|
||||
"id",
|
||||
|
|
|
|||
|
|
@ -2636,6 +2636,8 @@ export type TimeBucketAssetResponseDto = {
|
|||
city: (string | null)[];
|
||||
/** Array of country names extracted from EXIF GPS data */
|
||||
country: (string | null)[];
|
||||
/** Array of UTC timestamps when each asset was originally uploaded to Immich */
|
||||
createdAt: string[];
|
||||
/** Array of video/gif durations in milliseconds (null for static images) */
|
||||
duration: (number | null)[];
|
||||
/** Array of file creation timestamps in UTC */
|
||||
|
|
@ -3003,6 +3005,8 @@ export type SyncAssetMetadataV1 = {
|
|||
export type SyncAssetV1 = {
|
||||
/** Checksum */
|
||||
checksum: string;
|
||||
/** Uploaded to Immich at */
|
||||
createdAt: string | null;
|
||||
/** Deleted at */
|
||||
deletedAt: string | null;
|
||||
/** Duration */
|
||||
|
|
@ -3041,6 +3045,8 @@ export type SyncAssetV1 = {
|
|||
export type SyncAssetV2 = {
|
||||
/** Checksum */
|
||||
checksum: string;
|
||||
/** Uploaded to Immich at */
|
||||
createdAt: string | null;
|
||||
/** Deleted at */
|
||||
deletedAt: string | null;
|
||||
/** Duration */
|
||||
|
|
@ -6289,13 +6295,14 @@ export function tagAssets({ id, bulkIdsDto }: {
|
|||
/**
|
||||
* Get time bucket
|
||||
*/
|
||||
export function getTimeBucket({ albumId, bbox, isFavorite, isTrashed, key, order, personId, slug, tagId, timeBucket, userId, visibility, withCoordinates, withPartners, withStacked }: {
|
||||
export function getTimeBucket({ albumId, bbox, isFavorite, isTrashed, key, order, orderBy, personId, slug, tagId, timeBucket, userId, visibility, withCoordinates, withPartners, withStacked }: {
|
||||
albumId?: string;
|
||||
bbox?: string;
|
||||
isFavorite?: boolean;
|
||||
isTrashed?: boolean;
|
||||
key?: string;
|
||||
order?: AssetOrder;
|
||||
orderBy?: AssetOrderBy;
|
||||
personId?: string;
|
||||
slug?: string;
|
||||
tagId?: string;
|
||||
|
|
@ -6316,6 +6323,7 @@ export function getTimeBucket({ albumId, bbox, isFavorite, isTrashed, key, order
|
|||
isTrashed,
|
||||
key,
|
||||
order,
|
||||
orderBy,
|
||||
personId,
|
||||
slug,
|
||||
tagId,
|
||||
|
|
@ -6332,13 +6340,14 @@ export function getTimeBucket({ albumId, bbox, isFavorite, isTrashed, key, order
|
|||
/**
|
||||
* Get time buckets
|
||||
*/
|
||||
export function getTimeBuckets({ albumId, bbox, isFavorite, isTrashed, key, order, personId, slug, tagId, userId, visibility, withCoordinates, withPartners, withStacked }: {
|
||||
export function getTimeBuckets({ albumId, bbox, isFavorite, isTrashed, key, order, orderBy, personId, slug, tagId, userId, visibility, withCoordinates, withPartners, withStacked }: {
|
||||
albumId?: string;
|
||||
bbox?: string;
|
||||
isFavorite?: boolean;
|
||||
isTrashed?: boolean;
|
||||
key?: string;
|
||||
order?: AssetOrder;
|
||||
orderBy?: AssetOrderBy;
|
||||
personId?: string;
|
||||
slug?: string;
|
||||
tagId?: string;
|
||||
|
|
@ -6358,6 +6367,7 @@ export function getTimeBuckets({ albumId, bbox, isFavorite, isTrashed, key, orde
|
|||
isTrashed,
|
||||
key,
|
||||
order,
|
||||
orderBy,
|
||||
personId,
|
||||
slug,
|
||||
tagId,
|
||||
|
|
@ -7257,6 +7267,10 @@ export enum OAuthTokenEndpointAuthMethod {
|
|||
ClientSecretPost = "client_secret_post",
|
||||
ClientSecretBasic = "client_secret_basic"
|
||||
}
|
||||
export enum AssetOrderBy {
|
||||
TakenAt = "takenAt",
|
||||
CreatedAt = "createdAt"
|
||||
}
|
||||
export enum UserMetadataKey {
|
||||
Preferences = "preferences",
|
||||
License = "license",
|
||||
|
|
|
|||
|
|
@ -382,6 +382,7 @@ export const columns = {
|
|||
'asset.checksum',
|
||||
'asset.fileCreatedAt',
|
||||
'asset.fileModifiedAt',
|
||||
'asset.createdAt',
|
||||
'asset.localDateTime',
|
||||
'asset.type',
|
||||
'asset.deletedAt',
|
||||
|
|
@ -404,6 +405,7 @@ export const columns = {
|
|||
'asset.fileCreatedAt',
|
||||
'asset.fileModifiedAt',
|
||||
'asset.localDateTime',
|
||||
'asset.createdAt',
|
||||
'asset.type',
|
||||
'asset.deletedAt',
|
||||
'asset.visibility',
|
||||
|
|
|
|||
|
|
@ -75,6 +75,7 @@ const SyncAssetV1Schema = z
|
|||
checksum: z.string().describe('Checksum'),
|
||||
fileCreatedAt: isoDatetimeToDate.nullable().describe('File created at'),
|
||||
fileModifiedAt: isoDatetimeToDate.nullable().describe('File modified at'),
|
||||
createdAt: isoDatetimeToDate.nullable().describe('Uploaded to Immich at'),
|
||||
localDateTime: isoDatetimeToDate.nullable().describe('Local date time'),
|
||||
duration: z.string().nullable().describe('Duration'),
|
||||
type: AssetTypeSchema,
|
||||
|
|
@ -99,6 +100,7 @@ const SyncAssetV2Schema = z
|
|||
checksum: z.string().describe('Checksum'),
|
||||
fileCreatedAt: isoDatetimeToDate.nullable().describe('File created at'),
|
||||
fileModifiedAt: isoDatetimeToDate.nullable().describe('File modified at'),
|
||||
createdAt: isoDatetimeToDate.nullable().describe('Uploaded to Immich at'),
|
||||
localDateTime: isoDatetimeToDate.nullable().describe('Local date time'),
|
||||
duration: z.int32().min(0).nullable().describe('Duration'),
|
||||
type: AssetTypeSchema,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { createZodDto } from 'nestjs-zod';
|
||||
import { BBoxSchema } from 'src/dtos/bbox.dto';
|
||||
import { AssetOrderSchema, AssetVisibilitySchema } from 'src/enum';
|
||||
import { AssetOrderBySchema, AssetOrderSchema, AssetVisibilitySchema } from 'src/enum';
|
||||
import { stringToBool } from 'src/validation';
|
||||
import z from 'zod';
|
||||
|
||||
|
|
@ -23,6 +23,9 @@ const TimeBucketQueryBaseSchema = z
|
|||
order: AssetOrderSchema.optional().describe(
|
||||
'Sort order for assets within time buckets (ASC for oldest first, DESC for newest first)',
|
||||
),
|
||||
orderBy: AssetOrderBySchema.optional().describe(
|
||||
'Date to group and order assets by (takenAt for date taken, createdAt for date added to Immich)',
|
||||
),
|
||||
visibility: AssetVisibilitySchema.optional().describe(
|
||||
'Filter by asset visibility status (ARCHIVE, TIMELINE, HIDDEN, LOCKED)',
|
||||
),
|
||||
|
|
@ -82,6 +85,9 @@ const TimeBucketAssetResponseSchema = z
|
|||
thumbhash: z
|
||||
.array(z.string().nullable())
|
||||
.describe('Array of BlurHash strings for generating asset previews (base64 encoded)'),
|
||||
createdAt: z
|
||||
.array(z.string())
|
||||
.describe('Array of UTC timestamps when each asset was originally uploaded to Immich'),
|
||||
fileCreatedAt: z.array(z.string()).describe('Array of file creation timestamps in UTC'),
|
||||
localOffsetHours: z
|
||||
.array(z.number())
|
||||
|
|
|
|||
|
|
@ -74,6 +74,13 @@ export enum AssetOrder {
|
|||
|
||||
export const AssetOrderSchema = z.enum(AssetOrder).describe('Asset sort order').meta({ id: 'AssetOrder' });
|
||||
|
||||
export enum AssetOrderBy {
|
||||
TakenAt = 'takenAt',
|
||||
CreatedAt = 'createdAt',
|
||||
}
|
||||
|
||||
export const AssetOrderBySchema = z.enum(AssetOrderBy).describe('Asset sorting property').meta({ id: 'AssetOrderBy' });
|
||||
|
||||
export enum MemoryType {
|
||||
/** pictures taken on this day X years ago */
|
||||
OnThisDay = 'on_this_day',
|
||||
|
|
|
|||
|
|
@ -382,6 +382,7 @@ with
|
|||
"asset"."ownerId",
|
||||
"asset"."status",
|
||||
asset."fileCreatedAt" at time zone 'utc' as "fileCreatedAt",
|
||||
asset."createdAt" at time zone 'utc' as "createdAt",
|
||||
encode("asset"."thumbhash", 'base64') as "thumbhash",
|
||||
"asset_exif"."city",
|
||||
"asset_exif"."country",
|
||||
|
|
@ -442,6 +443,7 @@ with
|
|||
coalesce(array_agg("livePhotoVideoId"), '{}') as "livePhotoVideoId",
|
||||
coalesce(array_agg("fileCreatedAt"), '{}') as "fileCreatedAt",
|
||||
coalesce(array_agg("localOffsetHours"), '{}') as "localOffsetHours",
|
||||
coalesce(array_agg("createdAt"), '{}') as "createdAt",
|
||||
coalesce(array_agg("ownerId"), '{}') as "ownerId",
|
||||
coalesce(array_agg("projectionType"), '{}') as "projectionType",
|
||||
coalesce(array_agg("ratio"), '{}') as "ratio",
|
||||
|
|
@ -485,6 +487,22 @@ where
|
|||
limit
|
||||
$5
|
||||
|
||||
-- AssetRepository.getRecentlyCreatedAssetIds
|
||||
select
|
||||
"id" as "data",
|
||||
"createdAt" as "value"
|
||||
from
|
||||
"asset"
|
||||
where
|
||||
"ownerId" = $1::uuid
|
||||
and "asset"."visibility" = $2
|
||||
and "type" = $3
|
||||
and "deletedAt" is null
|
||||
order by
|
||||
"value" desc
|
||||
limit
|
||||
$4
|
||||
|
||||
-- AssetRepository.detectOfflineExternalAssets
|
||||
update "asset"
|
||||
set
|
||||
|
|
|
|||
|
|
@ -65,6 +65,7 @@ select
|
|||
"asset"."checksum",
|
||||
"asset"."fileCreatedAt",
|
||||
"asset"."fileModifiedAt",
|
||||
"asset"."createdAt",
|
||||
"asset"."localDateTime",
|
||||
"asset"."type",
|
||||
"asset"."deletedAt",
|
||||
|
|
@ -98,6 +99,7 @@ select
|
|||
"asset"."checksum",
|
||||
"asset"."fileCreatedAt",
|
||||
"asset"."fileModifiedAt",
|
||||
"asset"."createdAt",
|
||||
"asset"."localDateTime",
|
||||
"asset"."type",
|
||||
"asset"."deletedAt",
|
||||
|
|
@ -133,6 +135,7 @@ select
|
|||
"asset"."checksum",
|
||||
"asset"."fileCreatedAt",
|
||||
"asset"."fileModifiedAt",
|
||||
"asset"."createdAt",
|
||||
"asset"."localDateTime",
|
||||
"asset"."type",
|
||||
"asset"."deletedAt",
|
||||
|
|
@ -407,6 +410,7 @@ select
|
|||
"asset"."checksum",
|
||||
"asset"."fileCreatedAt",
|
||||
"asset"."fileModifiedAt",
|
||||
"asset"."createdAt",
|
||||
"asset"."localDateTime",
|
||||
"asset"."type",
|
||||
"asset"."deletedAt",
|
||||
|
|
@ -737,6 +741,7 @@ select
|
|||
"asset"."fileCreatedAt",
|
||||
"asset"."fileModifiedAt",
|
||||
"asset"."localDateTime",
|
||||
"asset"."createdAt",
|
||||
"asset"."type",
|
||||
"asset"."deletedAt",
|
||||
"asset"."visibility",
|
||||
|
|
@ -789,6 +794,7 @@ select
|
|||
"asset"."fileCreatedAt",
|
||||
"asset"."fileModifiedAt",
|
||||
"asset"."localDateTime",
|
||||
"asset"."createdAt",
|
||||
"asset"."type",
|
||||
"asset"."deletedAt",
|
||||
"asset"."visibility",
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ import { InjectKysely } from 'nestjs-kysely';
|
|||
import { LockableProperty, Stack } from 'src/database';
|
||||
import { Chunked, ChunkedArray, DummyValue, GenerateSql } from 'src/decorators';
|
||||
import { AuthDto } from 'src/dtos/auth.dto';
|
||||
import { AssetFileType, AssetOrder, AssetStatus, AssetType, AssetVisibility } from 'src/enum';
|
||||
import { AssetFileType, AssetOrder, AssetOrderBy, AssetStatus, AssetType, AssetVisibility } from 'src/enum';
|
||||
import { DB } from 'src/schema';
|
||||
import { AssetAudioTable, AssetKeyframeTable, AssetVideoTable } from 'src/schema/tables/asset-av.table';
|
||||
import { AssetExifTable } from 'src/schema/tables/asset-exif.table';
|
||||
|
|
@ -89,6 +89,7 @@ interface AssetBuilderOptions {
|
|||
|
||||
export interface TimeBucketOptions extends AssetBuilderOptions {
|
||||
order?: AssetOrder;
|
||||
orderBy?: AssetOrderBy;
|
||||
}
|
||||
|
||||
export interface TimeBucketItem {
|
||||
|
|
@ -711,7 +712,7 @@ export class AssetRepository {
|
|||
.with('asset', (qb) =>
|
||||
qb
|
||||
.selectFrom('asset')
|
||||
.select(truncatedDate<Date>().as('timeBucket'))
|
||||
.select(truncatedDate<Date>(options.orderBy).as('timeBucket'))
|
||||
.$if(!!options.isTrashed, (qb) => qb.where('asset.status', '!=', AssetStatus.Deleted))
|
||||
.where('asset.deletedAt', options.isTrashed ? 'is not' : 'is', null)
|
||||
.$if(!!options.bbox, (qb) => {
|
||||
|
|
@ -783,6 +784,7 @@ export class AssetRepository {
|
|||
'asset.ownerId',
|
||||
'asset.status',
|
||||
sql`asset."fileCreatedAt" at time zone 'utc'`.as('fileCreatedAt'),
|
||||
sql`asset."createdAt" at time zone 'utc'`.as('createdAt'),
|
||||
eb.fn('encode', ['asset.thumbhash', sql.lit('base64')]).as('thumbhash'),
|
||||
'asset_exif.city',
|
||||
'asset_exif.country',
|
||||
|
|
@ -815,7 +817,7 @@ export class AssetRepository {
|
|||
|
||||
return withBoundingBox(withBoundingCircle, bbox);
|
||||
})
|
||||
.where(truncatedDate(), '=', timeBucket.replace(/^[+-]/, ''))
|
||||
.where(truncatedDate(options.orderBy), '=', timeBucket.replace(/^[+-]/, ''))
|
||||
.$if(!!options.albumId, (qb) =>
|
||||
qb.where((eb) =>
|
||||
eb.exists(
|
||||
|
|
@ -861,7 +863,12 @@ export class AssetRepository {
|
|||
)
|
||||
.$if(!!options.isTrashed, (qb) => qb.where('asset.status', '!=', AssetStatus.Deleted))
|
||||
.$if(!!options.tagId, (qb) => withTagId(qb, options.tagId!))
|
||||
.orderBy(sql`(asset."localDateTime" AT TIME ZONE 'UTC')::date`, order)
|
||||
.orderBy(
|
||||
options.orderBy == AssetOrderBy.CreatedAt
|
||||
? sql`"createdAt"`
|
||||
: sql`(asset."localDateTime" AT TIME ZONE 'UTC')::date`,
|
||||
order,
|
||||
)
|
||||
.orderBy('asset.fileCreatedAt', order),
|
||||
)
|
||||
.with('agg', (qb) =>
|
||||
|
|
@ -880,6 +887,7 @@ export class AssetRepository {
|
|||
eb.fn.coalesce(eb.fn('array_agg', ['livePhotoVideoId']), sql.lit('{}')).as('livePhotoVideoId'),
|
||||
eb.fn.coalesce(eb.fn('array_agg', ['fileCreatedAt']), sql.lit('{}')).as('fileCreatedAt'),
|
||||
eb.fn.coalesce(eb.fn('array_agg', ['localOffsetHours']), sql.lit('{}')).as('localOffsetHours'),
|
||||
eb.fn.coalesce(eb.fn('array_agg', ['createdAt']), sql.lit('{}')).as('createdAt'),
|
||||
eb.fn.coalesce(eb.fn('array_agg', ['ownerId']), sql.lit('{}')).as('ownerId'),
|
||||
eb.fn.coalesce(eb.fn('array_agg', ['projectionType']), sql.lit('{}')).as('projectionType'),
|
||||
eb.fn.coalesce(eb.fn('array_agg', ['ratio']), sql.lit('{}')).as('ratio'),
|
||||
|
|
@ -929,6 +937,22 @@ export class AssetRepository {
|
|||
return { fieldName: 'exifInfo.city', items };
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID, 12] })
|
||||
async getRecentlyCreatedAssetIds(ownerId: string, maxAssets: number) {
|
||||
const items = await this.db
|
||||
.selectFrom('asset')
|
||||
.select(['id as data', 'createdAt as value'])
|
||||
.where('ownerId', '=', asUuid(ownerId))
|
||||
.where('asset.visibility', '=', AssetVisibility.Timeline)
|
||||
.where('type', '=', AssetType.Image)
|
||||
.where('deletedAt', 'is', null)
|
||||
.orderBy('value', 'desc')
|
||||
.limit(maxAssets)
|
||||
.execute();
|
||||
|
||||
return { fieldName: 'createdAt', items };
|
||||
}
|
||||
|
||||
async upsertFile(
|
||||
file: Pick<
|
||||
Insertable<AssetFileTable>,
|
||||
|
|
|
|||
|
|
@ -110,6 +110,7 @@ export class JobService extends BaseService {
|
|||
checksum: hexOrBufferToBase64(asset.checksum),
|
||||
fileCreatedAt: asset.fileCreatedAt,
|
||||
fileModifiedAt: asset.fileModifiedAt,
|
||||
createdAt: asset.createdAt,
|
||||
localDateTime: asset.localDateTime,
|
||||
duration: asset.duration,
|
||||
type: asset.type,
|
||||
|
|
@ -166,6 +167,7 @@ export class JobService extends BaseService {
|
|||
checksum: hexOrBufferToBase64(asset.checksum),
|
||||
fileCreatedAt: asset.fileCreatedAt,
|
||||
fileModifiedAt: asset.fileModifiedAt,
|
||||
createdAt: asset.createdAt,
|
||||
localDateTime: asset.localDateTime,
|
||||
duration: asset.duration,
|
||||
type: asset.type,
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ describe(SearchService.name, () => {
|
|||
});
|
||||
|
||||
describe('getExploreData', () => {
|
||||
it('should get assets by city and tag', async () => {
|
||||
it('should get recent assets and assets by city and tag', async () => {
|
||||
const auth = AuthFactory.create();
|
||||
const asset = AssetFactory.from()
|
||||
.exif({ latitude: 42, longitude: 69, city: 'city', state: 'state', country: 'country' })
|
||||
|
|
@ -74,9 +74,17 @@ describe(SearchService.name, () => {
|
|||
fieldName: 'exifInfo.city',
|
||||
items: [{ value: 'city', data: asset.id }],
|
||||
});
|
||||
mocks.asset.getRecentlyCreatedAssetIds.mockResolvedValue({
|
||||
fieldName: 'createdAt',
|
||||
items: [{ value: asset.createdAt, data: asset.id }],
|
||||
});
|
||||
mocks.asset.getByIdsWithAllRelationsButStacks.mockResolvedValue([asset as never]);
|
||||
const expectedResponse = [
|
||||
{ fieldName: 'exifInfo.city', items: [{ value: 'city', data: mapAsset(getForAsset(asset)) }] },
|
||||
{
|
||||
fieldName: 'createdAt',
|
||||
items: [{ value: asset.createdAt.toISOString(), data: mapAsset(getForAsset(asset)) }],
|
||||
},
|
||||
];
|
||||
|
||||
const result = await sut.getExploreData(auth);
|
||||
|
|
|
|||
|
|
@ -40,10 +40,26 @@ export class SearchService extends BaseService {
|
|||
|
||||
async getExploreData(auth: AuthDto) {
|
||||
const options = { maxFields: 12, minAssetsPerField: 5 };
|
||||
|
||||
const cities = await this.assetRepository.getAssetIdByCity(auth.user.id, options);
|
||||
const assets = await this.assetRepository.getByIdsWithAllRelationsButStacks(cities.items.map(({ data }) => data));
|
||||
const items = assets.map((asset) => ({ value: asset.exifInfo!.city!, data: mapAsset(asset, { auth }) }));
|
||||
return [{ fieldName: cities.fieldName, items }];
|
||||
const cityAssets = await this.assetRepository.getByIdsWithAllRelationsButStacks(
|
||||
cities.items.map(({ data }) => data),
|
||||
);
|
||||
const cityItems = cityAssets.map((asset) => ({ value: asset.exifInfo!.city!, data: mapAsset(asset, { auth }) }));
|
||||
|
||||
const recents = await this.assetRepository.getRecentlyCreatedAssetIds(auth.user.id, options.maxFields);
|
||||
const recentAssets = await this.assetRepository.getByIdsWithAllRelationsButStacks(
|
||||
recents.items.map((item) => item.data),
|
||||
);
|
||||
const recentItems = recentAssets.map((asset) => ({
|
||||
value: asset.createdAt.toISOString(),
|
||||
data: mapAsset(asset, { auth }),
|
||||
}));
|
||||
|
||||
return [
|
||||
{ fieldName: cities.fieldName, items: cityItems },
|
||||
{ fieldName: recents.fieldName, items: recentItems },
|
||||
];
|
||||
}
|
||||
|
||||
async searchMetadata(auth: AuthDto, dto: MetadataSearchDto): Promise<SearchResponseDto> {
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ import { jsonArrayFrom, jsonObjectFrom } from 'kysely/helpers/postgres';
|
|||
import { Notice, PostgresError } from 'postgres';
|
||||
import { columns, lockableProperties, LockableProperty, Person } from 'src/database';
|
||||
import { AssetEditActionItem } from 'src/dtos/editing.dto';
|
||||
import { AssetFileType, AssetVisibility, DatabaseExtension, ExifOrientation } from 'src/enum';
|
||||
import { AssetFileType, AssetOrderBy, AssetVisibility, DatabaseExtension, ExifOrientation } from 'src/enum';
|
||||
import { AssetSearchBuilderOptions } from 'src/repositories/search.repository';
|
||||
import { DB } from 'src/schema';
|
||||
import { AssetExifTable } from 'src/schema/tables/asset-exif.table';
|
||||
|
|
@ -298,8 +298,8 @@ export function withTags(eb: ExpressionBuilder<DB, 'asset'>) {
|
|||
).as('tags');
|
||||
}
|
||||
|
||||
export function truncatedDate<O>() {
|
||||
return sql<O>`date_trunc(${sql.lit('MONTH')}, "localDateTime" AT TIME ZONE 'UTC') AT TIME ZONE 'UTC'`;
|
||||
export function truncatedDate<O>(order: AssetOrderBy = AssetOrderBy.TakenAt) {
|
||||
return sql<O>`date_trunc(${sql.lit('MONTH')}, ${sql.ref(order === AssetOrderBy.CreatedAt ? 'asset.createdAt' : 'localDateTime')} AT TIME ZONE 'UTC') AT TIME ZONE 'UTC'`;
|
||||
}
|
||||
|
||||
export function withTagId<O>(qb: SelectQueryBuilder<DB, 'asset', O>, tagId: string) {
|
||||
|
|
|
|||
|
|
@ -118,6 +118,7 @@ describe(TimelineService.name, () => {
|
|||
expect(response).toEqual({
|
||||
city: [],
|
||||
country: [],
|
||||
createdAt: [],
|
||||
duration: [],
|
||||
id: [],
|
||||
visibility: [],
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ describe(SyncRequestType.AlbumAssetsV2, () => {
|
|||
fileCreatedAt: date,
|
||||
fileModifiedAt: date,
|
||||
localDateTime: date,
|
||||
createdAt: date,
|
||||
deletedAt: null,
|
||||
duration: 600_000,
|
||||
livePhotoVideoId: null,
|
||||
|
|
@ -73,6 +74,7 @@ describe(SyncRequestType.AlbumAssetsV2, () => {
|
|||
deletedAt: asset.deletedAt,
|
||||
fileCreatedAt: asset.fileCreatedAt,
|
||||
fileModifiedAt: asset.fileModifiedAt,
|
||||
createdAt: asset.createdAt,
|
||||
isFavorite: asset.isFavorite,
|
||||
localDateTime: asset.localDateTime,
|
||||
type: asset.type,
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ describe(SyncEntityType.AssetV2, () => {
|
|||
fileCreatedAt: date,
|
||||
fileModifiedAt: date,
|
||||
localDateTime: date,
|
||||
createdAt: date,
|
||||
deletedAt: null,
|
||||
duration: 600_000,
|
||||
libraryId: null,
|
||||
|
|
@ -54,6 +55,7 @@ describe(SyncEntityType.AssetV2, () => {
|
|||
deletedAt: asset.deletedAt,
|
||||
fileCreatedAt: asset.fileCreatedAt,
|
||||
fileModifiedAt: asset.fileModifiedAt,
|
||||
createdAt: asset.createdAt,
|
||||
isFavorite: asset.isFavorite,
|
||||
localDateTime: asset.localDateTime,
|
||||
type: asset.type,
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ describe(SyncRequestType.PartnerAssetsV2, () => {
|
|||
fileCreatedAt: date,
|
||||
fileModifiedAt: date,
|
||||
localDateTime: date,
|
||||
createdAt: date,
|
||||
deletedAt: null,
|
||||
duration: 600_000,
|
||||
libraryId: null,
|
||||
|
|
@ -58,6 +59,7 @@ describe(SyncRequestType.PartnerAssetsV2, () => {
|
|||
deletedAt: null,
|
||||
fileCreatedAt: date,
|
||||
fileModifiedAt: date,
|
||||
createdAt: date,
|
||||
isFavorite: false,
|
||||
localDateTime: date,
|
||||
type: asset.type,
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ export const newAssetRepositoryMock = (): Mocked<RepositoryInterface<AssetReposi
|
|||
getTimeBucket: vitest.fn(),
|
||||
getTimeBuckets: vitest.fn(),
|
||||
getAssetIdByCity: vitest.fn(),
|
||||
getRecentlyCreatedAssetIds: vitest.fn(),
|
||||
upsertFile: vitest.fn(),
|
||||
upsertFiles: vitest.fn(),
|
||||
deleteFile: vitest.fn(),
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { AssetOrder } from '@immich/sdk';
|
||||
import { AssetOrder, AssetOrderBy } from '@immich/sdk';
|
||||
import { SvelteSet } from 'svelte/reactivity';
|
||||
import type { CommonLayoutOptions } from '$lib/utils/layout-utils';
|
||||
import { getJustifiedLayoutFromAssets } from '$lib/utils/layout-utils';
|
||||
import { plainDateTimeCompare } from '$lib/utils/timeline-util';
|
||||
import { getOrderingDate, plainDateTimeCompare } from '$lib/utils/timeline-util';
|
||||
import type { TimelineMonth } from './timeline-month.svelte';
|
||||
import type { Direction, MoveAsset, TimelineAsset } from './types';
|
||||
import { ViewerAsset } from './viewer-asset.svelte';
|
||||
|
|
@ -12,6 +12,7 @@ export class TimelineDay {
|
|||
readonly index: number;
|
||||
readonly groupTitle: string;
|
||||
readonly day: number;
|
||||
readonly orderBy: AssetOrderBy;
|
||||
viewerAssets: ViewerAsset[] = $state([]);
|
||||
|
||||
height = $state(0);
|
||||
|
|
@ -24,11 +25,12 @@ export class TimelineDay {
|
|||
#col = $state(0);
|
||||
#deferredLayout = false;
|
||||
|
||||
constructor(timelineMonth: TimelineMonth, index: number, day: number, groupTitle: string) {
|
||||
constructor(timelineMonth: TimelineMonth, index: number, day: number, groupTitle: string, orderBy: AssetOrderBy) {
|
||||
this.index = index;
|
||||
this.timelineMonth = timelineMonth;
|
||||
this.day = day;
|
||||
this.groupTitle = groupTitle;
|
||||
this.orderBy = orderBy;
|
||||
}
|
||||
|
||||
get top() {
|
||||
|
|
@ -115,10 +117,10 @@ export class TimelineDay {
|
|||
continue;
|
||||
}
|
||||
|
||||
const oldTime = { ...asset.localDateTime };
|
||||
const oldTime = { ...getOrderingDate(asset, this.orderBy) };
|
||||
const callbackResult = callback(asset);
|
||||
let remove = (callbackResult as { remove?: boolean } | undefined)?.remove ?? false;
|
||||
const newTime = asset.localDateTime;
|
||||
const newTime = getOrderingDate(asset, this.orderBy);
|
||||
if (oldTime.year !== newTime.year || oldTime.month !== newTime.month || oldTime.day !== newTime.day) {
|
||||
const { year, month, day } = newTime;
|
||||
remove = true;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { AssetOrder, getAssetInfo, getTimeBuckets, type AssetResponseDto } from '@immich/sdk';
|
||||
import { AssetOrder, getAssetInfo, getTimeBuckets, AssetOrderBy, type AssetResponseDto } from '@immich/sdk';
|
||||
import { clamp, isEqual } from 'lodash-es';
|
||||
import { SvelteDate, SvelteSet } from 'svelte/reactivity';
|
||||
import { VirtualScrollManager } from '$lib/managers/VirtualScrollManager/VirtualScrollManager.svelte';
|
||||
|
|
@ -20,6 +20,7 @@ import { WebsocketSupport } from '$lib/managers/timeline-manager/internal/websoc
|
|||
import { CancellableTask } from '$lib/utils/cancellable-task';
|
||||
import { PersistedLocalStorage } from '$lib/utils/persisted';
|
||||
import {
|
||||
getOrderingDate,
|
||||
isAssetResponseDto,
|
||||
setDifference,
|
||||
toTimelineAsset,
|
||||
|
|
@ -252,6 +253,7 @@ export class TimelineManager extends VirtualScrollManager {
|
|||
timeBucket.count,
|
||||
false,
|
||||
this.#options.order,
|
||||
this.#options.orderBy,
|
||||
);
|
||||
});
|
||||
this.albumAssets.clear();
|
||||
|
|
@ -393,7 +395,10 @@ export class TimelineManager extends VirtualScrollManager {
|
|||
return;
|
||||
}
|
||||
|
||||
timelineMonth = await this.#loadTimelineMonthAtTime(timelineAsset.localDateTime, { cancelable: false });
|
||||
timelineMonth = await this.#loadTimelineMonthAtTime(
|
||||
getOrderingDate(timelineAsset, this.#options.orderBy || AssetOrderBy.TakenAt),
|
||||
{ cancelable: false },
|
||||
);
|
||||
if (timelineMonth?.findAssetById({ id })) {
|
||||
return timelineMonth;
|
||||
}
|
||||
|
|
@ -462,10 +467,11 @@ export class TimelineManager extends VirtualScrollManager {
|
|||
}
|
||||
|
||||
protected upsertSegmentForAsset(asset: TimelineAsset) {
|
||||
let month = getTimelineMonthByDate(this, asset.localDateTime);
|
||||
const dateTime = getOrderingDate(asset, this.#options.orderBy || AssetOrderBy.TakenAt);
|
||||
let month = getTimelineMonthByDate(this, dateTime);
|
||||
|
||||
if (!month) {
|
||||
month = new TimelineMonth(this, asset.localDateTime, 1, true, this.#options.order);
|
||||
month = new TimelineMonth(this, dateTime, 1, true, this.#options.order, this.#options.orderBy);
|
||||
this.months.push(month);
|
||||
}
|
||||
return month;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { AssetOrder, type TimeBucketAssetResponseDto } from '@immich/sdk';
|
||||
import { AssetOrder, AssetOrderBy, type TimeBucketAssetResponseDto } from '@immich/sdk';
|
||||
import { t } from 'svelte-i18n';
|
||||
import { SvelteSet } from 'svelte/reactivity';
|
||||
import { get } from 'svelte/store';
|
||||
|
|
@ -15,10 +15,12 @@ import {
|
|||
fromTimelinePlainDate,
|
||||
fromTimelinePlainDateTime,
|
||||
fromTimelinePlainYearMonth,
|
||||
fromISODateTimeUTC,
|
||||
getTimes,
|
||||
setDifference,
|
||||
type TimelineDateTime,
|
||||
type TimelineYearMonth,
|
||||
getOrderingDate,
|
||||
} from '$lib/utils/timeline-util';
|
||||
import { GroupInsertionCache } from './group-insertion-cache.svelte';
|
||||
import { TimelineDay } from './timeline-day.svelte';
|
||||
|
|
@ -37,6 +39,7 @@ export class TimelineMonth {
|
|||
|
||||
#initialCount: number = 0;
|
||||
#sortOrder: AssetOrder = AssetOrder.Desc;
|
||||
#orderBy: AssetOrderBy = AssetOrderBy.TakenAt;
|
||||
percent: number = $state(0);
|
||||
|
||||
assetsCount: number = $derived(
|
||||
|
|
@ -56,10 +59,12 @@ export class TimelineMonth {
|
|||
initialCount: number,
|
||||
loaded: boolean,
|
||||
order: AssetOrder = AssetOrder.Desc,
|
||||
orderBy: AssetOrderBy = AssetOrderBy.TakenAt,
|
||||
) {
|
||||
this.timelineManager = timelineManager;
|
||||
this.#initialCount = initialCount;
|
||||
this.#sortOrder = order;
|
||||
this.#orderBy = orderBy;
|
||||
|
||||
this.yearMonth = { year: yearMonth.year, month: yearMonth.month };
|
||||
this.title = formatTimelineMonthTitle(fromTimelinePlainYearMonth(yearMonth));
|
||||
|
|
@ -185,6 +190,7 @@ export class TimelineMonth {
|
|||
isVideo: !bucketAssets.isImage[i],
|
||||
livePhotoVideoId: bucketAssets.livePhotoVideoId[i],
|
||||
localDateTime,
|
||||
createdAt: fromISODateTimeUTC(bucketAssets.createdAt[i]).setZone('local'),
|
||||
fileCreatedAt,
|
||||
ownerId: bucketAssets.ownerId[i],
|
||||
projectionType: bucketAssets.projectionType[i],
|
||||
|
|
@ -229,22 +235,22 @@ export class TimelineMonth {
|
|||
}
|
||||
|
||||
addTimelineAsset(timelineAsset: TimelineAsset, addContext: GroupInsertionCache) {
|
||||
const { localDateTime } = timelineAsset;
|
||||
const dateTime = getOrderingDate(timelineAsset, this.#orderBy);
|
||||
|
||||
const { year, month } = this.yearMonth;
|
||||
if (month !== localDateTime.month || year !== localDateTime.year) {
|
||||
if (month !== dateTime.month || year !== dateTime.year) {
|
||||
addContext.unprocessedAssets.push(timelineAsset);
|
||||
return;
|
||||
}
|
||||
|
||||
let timelineDay = addContext.getTimelineDay(localDateTime) || this.findTimelineDayByDay(localDateTime.day);
|
||||
let timelineDay = addContext.getTimelineDay(dateTime) || this.findTimelineDayByDay(dateTime.day);
|
||||
if (timelineDay) {
|
||||
addContext.setTimelineDay(timelineDay, localDateTime);
|
||||
addContext.setTimelineDay(timelineDay, dateTime);
|
||||
} else {
|
||||
const groupTitle = formatGroupTitle(fromTimelinePlainDate(localDateTime));
|
||||
timelineDay = new TimelineDay(this, this.timelineDays.length, localDateTime.day, groupTitle);
|
||||
const groupTitle = formatGroupTitle(fromTimelinePlainDate(dateTime));
|
||||
timelineDay = new TimelineDay(this, this.timelineDays.length, dateTime.day, groupTitle, this.#orderBy);
|
||||
this.timelineDays.push(timelineDay);
|
||||
addContext.setTimelineDay(timelineDay, localDateTime);
|
||||
addContext.setTimelineDay(timelineDay, dateTime);
|
||||
addContext.newTimelineDays.add(timelineDay);
|
||||
}
|
||||
|
||||
|
|
@ -372,7 +378,7 @@ export class TimelineMonth {
|
|||
let closest = undefined;
|
||||
let smallestDiff = Infinity;
|
||||
for (const current of this.assetsIterator()) {
|
||||
const currentAssetDate = fromTimelinePlainDateTime(current.localDateTime);
|
||||
const currentAssetDate = fromTimelinePlainDateTime(getOrderingDate(current, this.#orderBy));
|
||||
const diff = Math.abs(targetDate.diff(currentAssetDate).as('milliseconds'));
|
||||
if (diff < smallestDiff) {
|
||||
smallestDiff = diff;
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ export type TimelineAsset = {
|
|||
ratio: number;
|
||||
thumbhash: string | null;
|
||||
localDateTime: TimelineDateTime;
|
||||
createdAt: TimelineDateTime;
|
||||
fileCreatedAt: TimelineDateTime;
|
||||
visibility: AssetVisibility;
|
||||
isFavorite: boolean;
|
||||
|
|
|
|||
|
|
@ -105,6 +105,7 @@ export const Route = {
|
|||
locked: () => '/locked',
|
||||
trash: () => '/trash',
|
||||
viewTrashedAsset: ({ id }: { id: string }) => `/trash/photos/${id}`,
|
||||
recentlyAdded: () => '/recently-added',
|
||||
|
||||
// search
|
||||
search: (dto?: MetadataSearchDto | SmartSearchDto) => {
|
||||
|
|
|
|||
|
|
@ -71,6 +71,15 @@ describe('getAltText', () => {
|
|||
second: testDate.getUTCSeconds(),
|
||||
millisecond: testDate.getUTCMilliseconds(),
|
||||
},
|
||||
createdAt: {
|
||||
year: testDate.getUTCFullYear(),
|
||||
month: testDate.getUTCMonth() + 1, // Note: getMonth() is 0-based
|
||||
day: testDate.getUTCDate(),
|
||||
hour: testDate.getUTCHours(),
|
||||
minute: testDate.getUTCMinutes(),
|
||||
second: testDate.getUTCSeconds(),
|
||||
millisecond: testDate.getUTCMilliseconds(),
|
||||
},
|
||||
localDateTime: {
|
||||
year: testDate.getUTCFullYear(),
|
||||
month: testDate.getUTCMonth() + 1, // Note: getMonth() is 0-based
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { AssetTypeEnum, type AssetResponseDto } from '@immich/sdk';
|
||||
import { AssetTypeEnum, AssetOrderBy, type AssetResponseDto } from '@immich/sdk';
|
||||
import { DateTime, type LocaleOptions } from 'luxon';
|
||||
import { SvelteSet } from 'svelte/reactivity';
|
||||
import { get } from 'svelte/store';
|
||||
|
|
@ -166,6 +166,7 @@ export const toTimelineAsset = (unknownAsset: AssetResponseDto | TimelineAsset):
|
|||
|
||||
const localDateTime = fromISODateTimeUTCToObject(assetResponse.localDateTime);
|
||||
const fileCreatedAt = fromISODateTimeToObject(assetResponse.fileCreatedAt, assetResponse.exifInfo?.timeZone ?? 'UTC');
|
||||
const createdAt = fromISODateTimeUTCToObject(assetResponse.createdAt);
|
||||
|
||||
return {
|
||||
id: assetResponse.id,
|
||||
|
|
@ -174,6 +175,7 @@ export const toTimelineAsset = (unknownAsset: AssetResponseDto | TimelineAsset):
|
|||
ratio,
|
||||
thumbhash: assetResponse.thumbhash,
|
||||
localDateTime,
|
||||
createdAt,
|
||||
fileCreatedAt,
|
||||
isFavorite: assetResponse.isFavorite,
|
||||
visibility: assetResponse.visibility,
|
||||
|
|
@ -236,3 +238,6 @@ export function setDifference<T>(setA: Set<T>, setB: Set<T>): SvelteSet<T> {
|
|||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export const getOrderingDate = (asset: TimelineAsset, order: AssetOrderBy) =>
|
||||
order === AssetOrderBy.CreatedAt ? asset.createdAt : asset.localDateTime;
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@
|
|||
import { mdiHeart } from '@mdi/js';
|
||||
import { t } from 'svelte-i18n';
|
||||
import type { PageData } from './$types';
|
||||
import { toTimelineAsset } from '$lib/utils/timeline-util';
|
||||
import { getAltText } from '$lib/utils/thumbnail-util';
|
||||
|
||||
interface Props {
|
||||
data: PageData;
|
||||
|
|
@ -24,6 +26,9 @@
|
|||
};
|
||||
|
||||
let places = $derived(getFieldItems(data.items, 'exifInfo.city'));
|
||||
let recents = $derived(
|
||||
getFieldItems(data.items, 'createdAt').sort((a, b) => new Date(b.value).getTime() - new Date(a.value).getTime()),
|
||||
);
|
||||
let people = $state(data.response.people);
|
||||
|
||||
let hasPeople = $derived(data.response.total > 0);
|
||||
|
|
@ -107,7 +112,31 @@
|
|||
</div>
|
||||
{/if}
|
||||
|
||||
{#if !hasPeople && places.length === 0}
|
||||
{#if recents.length > 0}
|
||||
<div class="mt-2 mb-6">
|
||||
<div class="flex justify-between">
|
||||
<p class="mb-4 font-medium dark:text-immich-dark-fg">{$t('recently_added')}</p>
|
||||
<a
|
||||
href={Route.recentlyAdded()}
|
||||
class="pe-4 text-sm font-medium hover:text-immich-primary dark:text-immich-dark-fg dark:hover:text-immich-dark-primary"
|
||||
draggable="false">{$t('view_all')}</a
|
||||
>
|
||||
</div>
|
||||
<div class="flex h-24 flex-wrap gap-x-1 overflow-hidden md:h-42">
|
||||
{#each recents as item (item.data.id)}
|
||||
<a class="relative h-full flex-auto" href={Route.viewAsset({ id: item.data.id })} draggable="false">
|
||||
<img
|
||||
src={getAssetMediaUrl({ id: item.data.id, size: AssetMediaSize.Thumbnail })}
|
||||
alt={$getAltText(toTimelineAsset(item.data))}
|
||||
class="size-full min-w-max rounded-xl object-cover"
|
||||
/>
|
||||
</a>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if !hasPeople && places.length === 0 && recents.length === 0}
|
||||
<EmptyPlaceholder text={$t('no_explore_results_message')} class="mx-auto mt-10" />
|
||||
{/if}
|
||||
</UserPageLayout>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,165 @@
|
|||
<script lang="ts">
|
||||
import ActionMenuItem from '$lib/components/ActionMenuItem.svelte';
|
||||
import UserPageLayout from '$lib/components/layouts/UserPageLayout.svelte';
|
||||
import ButtonContextMenu from '$lib/components/shared-components/context-menu/ButtonContextMenu.svelte';
|
||||
import EmptyPlaceholder from '$lib/components/shared-components/EmptyPlaceholder.svelte';
|
||||
import ArchiveAction from '$lib/components/timeline/actions/ArchiveAction.svelte';
|
||||
import ChangeDate from '$lib/components/timeline/actions/ChangeDateAction.svelte';
|
||||
import ChangeDescription from '$lib/components/timeline/actions/ChangeDescriptionAction.svelte';
|
||||
import ChangeLocation from '$lib/components/timeline/actions/ChangeLocationAction.svelte';
|
||||
import CreateSharedLink from '$lib/components/timeline/actions/CreateSharedLinkAction.svelte';
|
||||
import DeleteAssets from '$lib/components/timeline/actions/DeleteAssetsAction.svelte';
|
||||
import DownloadAction from '$lib/components/timeline/actions/DownloadAction.svelte';
|
||||
import FavoriteAction from '$lib/components/timeline/actions/FavoriteAction.svelte';
|
||||
import LinkLivePhotoAction from '$lib/components/timeline/actions/LinkLivePhotoAction.svelte';
|
||||
import SelectAllAssets from '$lib/components/timeline/actions/SelectAllAction.svelte';
|
||||
import SetVisibilityAction from '$lib/components/timeline/actions/SetVisibilityAction.svelte';
|
||||
import StackAction from '$lib/components/timeline/actions/StackAction.svelte';
|
||||
import TagAction from '$lib/components/timeline/actions/TagAction.svelte';
|
||||
import AssetSelectControlBar from '$lib/components/timeline/AssetSelectControlBar.svelte';
|
||||
import Timeline from '$lib/components/timeline/Timeline.svelte';
|
||||
import { AssetAction } from '$lib/constants';
|
||||
import { assetMultiSelectManager } from '$lib/managers/asset-multi-select-manager.svelte';
|
||||
import { assetViewerManager } from '$lib/managers/asset-viewer-manager.svelte';
|
||||
import { authManager } from '$lib/managers/auth-manager.svelte';
|
||||
import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
|
||||
import { getAssetBulkActions } from '$lib/services/asset.service';
|
||||
import {
|
||||
updateStackedAssetInTimeline,
|
||||
updateUnstackedAssetInTimeline,
|
||||
type OnLink,
|
||||
type OnUnlink,
|
||||
} from '$lib/utils/actions';
|
||||
import { openFileUploadDialog } from '$lib/utils/file-uploader';
|
||||
import { AssetVisibility, AssetOrderBy } from '@immich/sdk';
|
||||
import { ActionButton, CommandPaletteDefaultProvider } from '@immich/ui';
|
||||
import { mdiDotsVertical } from '@mdi/js';
|
||||
import { t } from 'svelte-i18n';
|
||||
import type { PageData } from './$types';
|
||||
|
||||
type Props = {
|
||||
data: PageData;
|
||||
};
|
||||
|
||||
let { data }: Props = $props();
|
||||
|
||||
let timelineManager = $state<TimelineManager>() as TimelineManager;
|
||||
const options = {
|
||||
visibility: AssetVisibility.Timeline,
|
||||
withStacked: true,
|
||||
withPartners: true,
|
||||
orderBy: AssetOrderBy.CreatedAt,
|
||||
};
|
||||
|
||||
let selectedAssets = $derived(assetMultiSelectManager.assets);
|
||||
let isAssetStackSelected = $derived(selectedAssets.length === 1 && !!selectedAssets[0].stack);
|
||||
let isLinkActionAvailable = $derived.by(() => {
|
||||
const isLivePhoto = selectedAssets.length === 1 && !!selectedAssets[0].livePhotoVideoId;
|
||||
const isLivePhotoCandidate =
|
||||
selectedAssets.length === 2 &&
|
||||
selectedAssets.some((asset) => asset.isImage) &&
|
||||
selectedAssets.some((asset) => asset.isVideo);
|
||||
|
||||
return assetMultiSelectManager.isAllUserOwned && (isLivePhoto || isLivePhotoCandidate);
|
||||
});
|
||||
|
||||
const handleEscape = () => {
|
||||
if (assetViewerManager.isViewing) {
|
||||
return;
|
||||
}
|
||||
if (assetMultiSelectManager.selectionActive) {
|
||||
assetMultiSelectManager.clear();
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
const handleLink: OnLink = ({ still, motion }) => {
|
||||
timelineManager.removeAssets([motion.id]);
|
||||
timelineManager.upsertAssets([still]);
|
||||
};
|
||||
|
||||
const handleUnlink: OnUnlink = ({ still, motion }) => {
|
||||
timelineManager.upsertAssets([motion]);
|
||||
timelineManager.upsertAssets([still]);
|
||||
};
|
||||
|
||||
const handleSetVisibility = (assetIds: string[]) => {
|
||||
timelineManager.removeAssets(assetIds);
|
||||
assetMultiSelectManager.clear();
|
||||
};
|
||||
</script>
|
||||
|
||||
<UserPageLayout hideNavbar={assetMultiSelectManager.selectionActive} title={data.meta.title} scrollbar={false}>
|
||||
<Timeline
|
||||
enableRouting={true}
|
||||
bind:timelineManager
|
||||
{options}
|
||||
assetInteraction={assetMultiSelectManager}
|
||||
removeAction={AssetAction.ARCHIVE}
|
||||
onEscape={handleEscape}
|
||||
withStacked
|
||||
>
|
||||
{#snippet empty()}
|
||||
<EmptyPlaceholder text={$t('no_assets_message')} onClick={() => openFileUploadDialog()} class="mx-auto mt-10" />
|
||||
{/snippet}
|
||||
</Timeline>
|
||||
</UserPageLayout>
|
||||
|
||||
{#if assetMultiSelectManager.selectionActive}
|
||||
<AssetSelectControlBar>
|
||||
{@const Actions = getAssetBulkActions($t)}
|
||||
<CommandPaletteDefaultProvider name={$t('assets')} actions={Object.values(Actions)} />
|
||||
|
||||
<CreateSharedLink />
|
||||
<SelectAllAssets {timelineManager} assetInteraction={assetMultiSelectManager} />
|
||||
<ActionButton action={Actions.AddToAlbum} />
|
||||
|
||||
{#if assetMultiSelectManager.isAllUserOwned}
|
||||
<FavoriteAction
|
||||
removeFavorite={assetMultiSelectManager.isAllFavorite}
|
||||
onFavorite={(ids, isFavorite) => timelineManager.update(ids, (asset) => (asset.isFavorite = isFavorite))}
|
||||
/>
|
||||
|
||||
<ButtonContextMenu icon={mdiDotsVertical} title={$t('menu')}>
|
||||
<DownloadAction menuItem />
|
||||
{#if assetMultiSelectManager.assets.length > 1 || isAssetStackSelected}
|
||||
<StackAction
|
||||
unstack={isAssetStackSelected}
|
||||
onStack={(result) => updateStackedAssetInTimeline(timelineManager, result)}
|
||||
onUnstack={(assets) => updateUnstackedAssetInTimeline(timelineManager, assets)}
|
||||
/>
|
||||
{/if}
|
||||
{#if isLinkActionAvailable}
|
||||
<LinkLivePhotoAction
|
||||
menuItem
|
||||
unlink={assetMultiSelectManager.assets.length === 1}
|
||||
onLink={handleLink}
|
||||
onUnlink={handleUnlink}
|
||||
/>
|
||||
{/if}
|
||||
<ChangeDate menuItem />
|
||||
<ChangeDescription menuItem />
|
||||
<ChangeLocation menuItem />
|
||||
<ArchiveAction
|
||||
menuItem
|
||||
onArchive={(ids, visibility) => timelineManager.update(ids, (asset) => (asset.visibility = visibility))}
|
||||
/>
|
||||
{#if authManager.preferences.tags.enabled}
|
||||
<TagAction menuItem />
|
||||
{/if}
|
||||
<DeleteAssets
|
||||
menuItem
|
||||
onAssetDelete={(assetIds) => timelineManager.removeAssets(assetIds)}
|
||||
onUndoDelete={(assets) => timelineManager.upsertAssets(assets)}
|
||||
/>
|
||||
<SetVisibilityAction menuItem onVisibilitySet={handleSetVisibility} />
|
||||
<hr />
|
||||
<ActionMenuItem action={Actions.RegenerateThumbnailJob} />
|
||||
<ActionMenuItem action={Actions.RefreshMetadataJob} />
|
||||
<ActionMenuItem action={Actions.TranscodeVideoJob} />
|
||||
</ButtonContextMenu>
|
||||
{:else}
|
||||
<DownloadAction />
|
||||
{/if}
|
||||
</AssetSelectControlBar>
|
||||
{/if}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
import { authenticate } from '$lib/utils/auth';
|
||||
import { getFormatter } from '$lib/utils/i18n';
|
||||
import type { PageLoad } from './$types';
|
||||
|
||||
export const load = (async ({ url }) => {
|
||||
await authenticate(url);
|
||||
const $t = await getFormatter();
|
||||
|
||||
return {
|
||||
meta: {
|
||||
title: $t('recently_added_page_title'),
|
||||
},
|
||||
};
|
||||
}) satisfies PageLoad;
|
||||
|
|
@ -38,6 +38,7 @@ export const timelineAssetFactory = Sync.makeFactory<TimelineAsset>({
|
|||
tags: [],
|
||||
thumbhash: Sync.each(() => faker.string.alphanumeric(28)),
|
||||
localDateTime: Sync.each(() => fromISODateTimeUTCToObject(faker.date.past().toISOString())),
|
||||
createdAt: Sync.each(() => fromISODateTimeUTCToObject(faker.date.past().toISOString())),
|
||||
fileCreatedAt: Sync.each(() => fromISODateTimeUTCToObject(faker.date.past().toISOString())),
|
||||
isFavorite: Sync.each(() => faker.datatype.boolean()),
|
||||
visibility: AssetVisibility.Timeline,
|
||||
|
|
@ -66,6 +67,7 @@ export const toResponseDto = (...timelineAsset: TimelineAsset[]) => {
|
|||
livePhotoVideoId: [],
|
||||
fileCreatedAt: [],
|
||||
localOffsetHours: [],
|
||||
createdAt: [],
|
||||
ownerId: [],
|
||||
projectionType: [],
|
||||
ratio: [],
|
||||
|
|
|
|||
Loading…
Reference in New Issue