From b9c0a3c64174109c8069e25e5638d20326d1e8a9 Mon Sep 17 00:00:00 2001 From: martabal <74269598+martabal@users.noreply.github.com> Date: Tue, 17 Oct 2023 00:23:24 +0200 Subject: [PATCH] feat: remove asset from memory --- cli/src/api/open-api/api.ts | 12 +++ mobile/openapi/doc/AssetBulkUpdateDto.md | 1 + mobile/openapi/doc/AssetResponseDto.md | 1 + .../lib/model/asset_bulk_update_dto.dart | 23 +++++- .../openapi/lib/model/asset_response_dto.dart | 10 ++- .../test/asset_bulk_update_dto_test.dart | 5 ++ .../openapi/test/asset_response_dto_test.dart | 5 ++ server/immich-openapi-specs.json | 7 ++ server/src/domain/asset/asset.service.ts | 2 +- server/src/domain/asset/dto/asset.dto.ts | 4 + .../asset/response-dto/asset-response.dto.ts | 2 + server/src/infra/entities/asset.entity.ts | 3 + .../1697484859613-RemoveFromMemory.ts | 28 +++++++ server/test/fixtures/asset.stub.ts | 12 +++ server/test/fixtures/shared-link.stub.ts | 2 + web/src/api/open-api/api.ts | 12 +++ .../asset-viewer/asset-viewer-nav-bar.svelte | 4 + .../asset-viewer/asset-viewer.svelte | 15 ++++ .../memory-page/memory-viewer.svelte | 79 +++++++++++++++++++ 19 files changed, 222 insertions(+), 5 deletions(-) create mode 100644 server/src/infra/migrations/1697484859613-RemoveFromMemory.ts diff --git a/cli/src/api/open-api/api.ts b/cli/src/api/open-api/api.ts index 549cc59d0..d1defea5b 100644 --- a/cli/src/api/open-api/api.ts +++ b/cli/src/api/open-api/api.ts @@ -399,6 +399,12 @@ export interface AssetBulkUpdateDto { * @memberof AssetBulkUpdateDto */ 'isFavorite'?: boolean; + /** + * + * @type {boolean} + * @memberof AssetBulkUpdateDto + */ + 'isShownInMemory'?: boolean; } /** * @@ -682,6 +688,12 @@ export interface AssetResponseDto { * @memberof AssetResponseDto */ 'isReadOnly': boolean; + /** + * + * @type {boolean} + * @memberof AssetResponseDto + */ + 'isShownInMemory': boolean; /** * * @type {boolean} diff --git a/mobile/openapi/doc/AssetBulkUpdateDto.md b/mobile/openapi/doc/AssetBulkUpdateDto.md index b48268464..3ade86c10 100644 --- a/mobile/openapi/doc/AssetBulkUpdateDto.md +++ b/mobile/openapi/doc/AssetBulkUpdateDto.md @@ -11,6 +11,7 @@ Name | Type | Description | Notes **ids** | **List** | | [default to const []] **isArchived** | **bool** | | [optional] **isFavorite** | **bool** | | [optional] +**isShownInMemory** | **bool** | | [optional] [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/mobile/openapi/doc/AssetResponseDto.md b/mobile/openapi/doc/AssetResponseDto.md index a08be71ac..48e85fbe8 100644 --- a/mobile/openapi/doc/AssetResponseDto.md +++ b/mobile/openapi/doc/AssetResponseDto.md @@ -22,6 +22,7 @@ Name | Type | Description | Notes **isFavorite** | **bool** | | **isOffline** | **bool** | | **isReadOnly** | **bool** | | +**isShownInMemory** | **bool** | | **isTrashed** | **bool** | | **libraryId** | **String** | | **livePhotoVideoId** | **String** | | [optional] diff --git a/mobile/openapi/lib/model/asset_bulk_update_dto.dart b/mobile/openapi/lib/model/asset_bulk_update_dto.dart index 7eb0e31af..baae3e3cb 100644 --- a/mobile/openapi/lib/model/asset_bulk_update_dto.dart +++ b/mobile/openapi/lib/model/asset_bulk_update_dto.dart @@ -16,6 +16,7 @@ class AssetBulkUpdateDto { this.ids = const [], this.isArchived, this.isFavorite, + this.isShownInMemory, }); List ids; @@ -36,21 +37,31 @@ class AssetBulkUpdateDto { /// bool? isFavorite; + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? isShownInMemory; + @override bool operator ==(Object other) => identical(this, other) || other is AssetBulkUpdateDto && other.ids == ids && other.isArchived == isArchived && - other.isFavorite == isFavorite; + other.isFavorite == isFavorite && + other.isShownInMemory == isShownInMemory; @override int get hashCode => // ignore: unnecessary_parenthesis (ids.hashCode) + (isArchived == null ? 0 : isArchived!.hashCode) + - (isFavorite == null ? 0 : isFavorite!.hashCode); + (isFavorite == null ? 0 : isFavorite!.hashCode) + + (isShownInMemory == null ? 0 : isShownInMemory!.hashCode); @override - String toString() => 'AssetBulkUpdateDto[ids=$ids, isArchived=$isArchived, isFavorite=$isFavorite]'; + String toString() => 'AssetBulkUpdateDto[ids=$ids, isArchived=$isArchived, isFavorite=$isFavorite, isShownInMemory=$isShownInMemory]'; Map toJson() { final json = {}; @@ -65,6 +76,11 @@ class AssetBulkUpdateDto { } else { // json[r'isFavorite'] = null; } + if (this.isShownInMemory != null) { + json[r'isShownInMemory'] = this.isShownInMemory; + } else { + // json[r'isShownInMemory'] = null; + } return json; } @@ -81,6 +97,7 @@ class AssetBulkUpdateDto { : const [], isArchived: mapValueOfType(json, r'isArchived'), isFavorite: mapValueOfType(json, r'isFavorite'), + isShownInMemory: mapValueOfType(json, r'isShownInMemory'), ); } return null; diff --git a/mobile/openapi/lib/model/asset_response_dto.dart b/mobile/openapi/lib/model/asset_response_dto.dart index b2feb0ee8..cfdea1fbe 100644 --- a/mobile/openapi/lib/model/asset_response_dto.dart +++ b/mobile/openapi/lib/model/asset_response_dto.dart @@ -27,6 +27,7 @@ class AssetResponseDto { required this.isFavorite, required this.isOffline, required this.isReadOnly, + required this.isShownInMemory, required this.isTrashed, required this.libraryId, this.livePhotoVideoId, @@ -79,6 +80,8 @@ class AssetResponseDto { bool isReadOnly; + bool isShownInMemory; + bool isTrashed; String libraryId; @@ -137,6 +140,7 @@ class AssetResponseDto { other.isFavorite == isFavorite && other.isOffline == isOffline && other.isReadOnly == isReadOnly && + other.isShownInMemory == isShownInMemory && other.isTrashed == isTrashed && other.libraryId == libraryId && other.livePhotoVideoId == livePhotoVideoId && @@ -170,6 +174,7 @@ class AssetResponseDto { (isFavorite.hashCode) + (isOffline.hashCode) + (isReadOnly.hashCode) + + (isShownInMemory.hashCode) + (isTrashed.hashCode) + (libraryId.hashCode) + (livePhotoVideoId == null ? 0 : livePhotoVideoId!.hashCode) + @@ -187,7 +192,7 @@ class AssetResponseDto { (updatedAt.hashCode); @override - String toString() => 'AssetResponseDto[checksum=$checksum, deviceAssetId=$deviceAssetId, deviceId=$deviceId, duration=$duration, exifInfo=$exifInfo, fileCreatedAt=$fileCreatedAt, fileModifiedAt=$fileModifiedAt, hasMetadata=$hasMetadata, id=$id, isArchived=$isArchived, isExternal=$isExternal, isFavorite=$isFavorite, isOffline=$isOffline, isReadOnly=$isReadOnly, isTrashed=$isTrashed, libraryId=$libraryId, livePhotoVideoId=$livePhotoVideoId, localDateTime=$localDateTime, originalFileName=$originalFileName, originalPath=$originalPath, owner=$owner, ownerId=$ownerId, people=$people, resized=$resized, smartInfo=$smartInfo, tags=$tags, thumbhash=$thumbhash, type=$type, updatedAt=$updatedAt]'; + String toString() => 'AssetResponseDto[checksum=$checksum, deviceAssetId=$deviceAssetId, deviceId=$deviceId, duration=$duration, exifInfo=$exifInfo, fileCreatedAt=$fileCreatedAt, fileModifiedAt=$fileModifiedAt, hasMetadata=$hasMetadata, id=$id, isArchived=$isArchived, isExternal=$isExternal, isFavorite=$isFavorite, isOffline=$isOffline, isReadOnly=$isReadOnly, isShownInMemory=$isShownInMemory, isTrashed=$isTrashed, libraryId=$libraryId, livePhotoVideoId=$livePhotoVideoId, localDateTime=$localDateTime, originalFileName=$originalFileName, originalPath=$originalPath, owner=$owner, ownerId=$ownerId, people=$people, resized=$resized, smartInfo=$smartInfo, tags=$tags, thumbhash=$thumbhash, type=$type, updatedAt=$updatedAt]'; Map toJson() { final json = {}; @@ -209,6 +214,7 @@ class AssetResponseDto { json[r'isFavorite'] = this.isFavorite; json[r'isOffline'] = this.isOffline; json[r'isReadOnly'] = this.isReadOnly; + json[r'isShownInMemory'] = this.isShownInMemory; json[r'isTrashed'] = this.isTrashed; json[r'libraryId'] = this.libraryId; if (this.livePhotoVideoId != null) { @@ -265,6 +271,7 @@ class AssetResponseDto { isFavorite: mapValueOfType(json, r'isFavorite')!, isOffline: mapValueOfType(json, r'isOffline')!, isReadOnly: mapValueOfType(json, r'isReadOnly')!, + isShownInMemory: mapValueOfType(json, r'isShownInMemory')!, isTrashed: mapValueOfType(json, r'isTrashed')!, libraryId: mapValueOfType(json, r'libraryId')!, livePhotoVideoId: mapValueOfType(json, r'livePhotoVideoId'), @@ -340,6 +347,7 @@ class AssetResponseDto { 'isFavorite', 'isOffline', 'isReadOnly', + 'isShownInMemory', 'isTrashed', 'libraryId', 'localDateTime', diff --git a/mobile/openapi/test/asset_bulk_update_dto_test.dart b/mobile/openapi/test/asset_bulk_update_dto_test.dart index cb23751e0..429b0c42d 100644 --- a/mobile/openapi/test/asset_bulk_update_dto_test.dart +++ b/mobile/openapi/test/asset_bulk_update_dto_test.dart @@ -31,6 +31,11 @@ void main() { // TODO }); + // bool isShownInMemory + test('to test the property `isShownInMemory`', () async { + // TODO + }); + }); diff --git a/mobile/openapi/test/asset_response_dto_test.dart b/mobile/openapi/test/asset_response_dto_test.dart index f450aae27..dbafb4f0a 100644 --- a/mobile/openapi/test/asset_response_dto_test.dart +++ b/mobile/openapi/test/asset_response_dto_test.dart @@ -87,6 +87,11 @@ void main() { // TODO }); + // bool isShownInMemory + test('to test the property `isShownInMemory`', () async { + // TODO + }); + // bool isTrashed test('to test the property `isTrashed`', () async { // TODO diff --git a/server/immich-openapi-specs.json b/server/immich-openapi-specs.json index cc649d784..49f167dcb 100644 --- a/server/immich-openapi-specs.json +++ b/server/immich-openapi-specs.json @@ -5696,6 +5696,9 @@ }, "isFavorite": { "type": "boolean" + }, + "isShownInMemory": { + "type": "boolean" } }, "required": [ @@ -5903,6 +5906,9 @@ "isReadOnly": { "type": "boolean" }, + "isShownInMemory": { + "type": "boolean" + }, "isTrashed": { "type": "boolean" }, @@ -5971,6 +5977,7 @@ "fileCreatedAt", "fileModifiedAt", "updatedAt", + "isShownInMemory", "isFavorite", "isArchived", "isTrashed", diff --git a/server/src/domain/asset/asset.service.ts b/server/src/domain/asset/asset.service.ts index abd0dbe0d..1839e40bd 100644 --- a/server/src/domain/asset/asset.service.ts +++ b/server/src/domain/asset/asset.service.ts @@ -165,7 +165,7 @@ export class AssetService { const assets = await this.assetRepository.getByDayOfYear(authUser.id, dto); return _.chain(assets) - .filter((asset) => asset.localDateTime.getFullYear() < currentYear) + .filter((asset) => asset.localDateTime.getFullYear() < currentYear && asset.isShownInMemory) .map((asset) => { const years = currentYear - asset.localDateTime.getFullYear(); diff --git a/server/src/domain/asset/dto/asset.dto.ts b/server/src/domain/asset/dto/asset.dto.ts index f5ada315c..18c36c52d 100644 --- a/server/src/domain/asset/dto/asset.dto.ts +++ b/server/src/domain/asset/dto/asset.dto.ts @@ -11,6 +11,10 @@ export class AssetBulkUpdateDto extends BulkIdsDto { @Optional() @IsBoolean() isArchived?: boolean; + + @Optional() + @IsBoolean() + isShownInMemory?: boolean; } export class UpdateAssetDto { diff --git a/server/src/domain/asset/response-dto/asset-response.dto.ts b/server/src/domain/asset/response-dto/asset-response.dto.ts index e7d5061be..e76f994e1 100644 --- a/server/src/domain/asset/response-dto/asset-response.dto.ts +++ b/server/src/domain/asset/response-dto/asset-response.dto.ts @@ -30,6 +30,7 @@ export class AssetResponseDto extends SanitizedAssetResponseDto { fileCreatedAt!: Date; fileModifiedAt!: Date; updatedAt!: Date; + isShownInMemory!: boolean; isFavorite!: boolean; isArchived!: boolean; isTrashed!: boolean; @@ -77,6 +78,7 @@ export function mapAsset(entity: AssetEntity, stripMetadata = false): AssetRespo fileModifiedAt: entity.fileModifiedAt, localDateTime: entity.localDateTime, updatedAt: entity.updatedAt, + isShownInMemory: entity.isShownInMemory, isFavorite: entity.isFavorite, isArchived: entity.isArchived, isTrashed: !!entity.deletedAt, diff --git a/server/src/infra/entities/asset.entity.ts b/server/src/infra/entities/asset.entity.ts index 31935ae5f..7314e6990 100644 --- a/server/src/infra/entities/asset.entity.ts +++ b/server/src/infra/entities/asset.entity.ts @@ -106,6 +106,9 @@ export class AssetEntity { @Column({ type: 'boolean', default: false }) isOffline!: boolean; + @Column({ type: 'boolean', default: true }) + isShownInMemory!: boolean; + @Column({ type: 'bytea' }) @Index() checksum!: Buffer; // sha1 checksum diff --git a/server/src/infra/migrations/1697484859613-RemoveFromMemory.ts b/server/src/infra/migrations/1697484859613-RemoveFromMemory.ts new file mode 100644 index 000000000..bd721235a --- /dev/null +++ b/server/src/infra/migrations/1697484859613-RemoveFromMemory.ts @@ -0,0 +1,28 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class RemoveFromMemory1697484859613 implements MigrationInterface { + name = 'RemoveFromMemory1697484859613' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "assets" RENAME COLUMN "isSkipMotion" TO "isShownInMemory"`); + await queryRunner.query(`ALTER TABLE "asset_faces" DROP CONSTRAINT "PK_6df76ab2eb6f5b57b7c2f1fc684"`); + await queryRunner.query(`ALTER TABLE "asset_faces" DROP COLUMN "id"`); + await queryRunner.query(`ALTER TABLE "asset_faces" ADD CONSTRAINT "PK_bf339a24070dac7e71304ec530a" PRIMARY KEY ("assetId", "personId")`); + await queryRunner.query(`ALTER TABLE "asset_faces" DROP CONSTRAINT "FK_95ad7106dd7b484275443f580f9"`); + await queryRunner.query(`ALTER TABLE "asset_faces" ALTER COLUMN "personId" SET NOT NULL`); + await queryRunner.query(`ALTER TABLE "assets" ALTER COLUMN "isShownInMemory" SET DEFAULT true`); + await queryRunner.query(`ALTER TABLE "asset_faces" ADD CONSTRAINT "FK_95ad7106dd7b484275443f580f9" FOREIGN KEY ("personId") REFERENCES "person"("id") ON DELETE CASCADE ON UPDATE CASCADE`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "asset_faces" DROP CONSTRAINT "FK_95ad7106dd7b484275443f580f9"`); + await queryRunner.query(`ALTER TABLE "assets" ALTER COLUMN "isShownInMemory" SET DEFAULT false`); + await queryRunner.query(`ALTER TABLE "asset_faces" ALTER COLUMN "personId" DROP NOT NULL`); + await queryRunner.query(`ALTER TABLE "asset_faces" ADD CONSTRAINT "FK_95ad7106dd7b484275443f580f9" FOREIGN KEY ("personId") REFERENCES "person"("id") ON DELETE CASCADE ON UPDATE CASCADE`); + await queryRunner.query(`ALTER TABLE "asset_faces" DROP CONSTRAINT "PK_bf339a24070dac7e71304ec530a"`); + await queryRunner.query(`ALTER TABLE "asset_faces" ADD "id" uuid NOT NULL DEFAULT uuid_generate_v4()`); + await queryRunner.query(`ALTER TABLE "asset_faces" ADD CONSTRAINT "PK_6df76ab2eb6f5b57b7c2f1fc684" PRIMARY KEY ("id")`); + await queryRunner.query(`ALTER TABLE "assets" RENAME COLUMN "isShownInMemory" TO "isSkipMotion"`); + } + +} diff --git a/server/test/fixtures/asset.stub.ts b/server/test/fixtures/asset.stub.ts index 5fef9f6d1..0caab1a77 100644 --- a/server/test/fixtures/asset.stub.ts +++ b/server/test/fixtures/asset.stub.ts @@ -24,6 +24,7 @@ export const assetStub = { createdAt: new Date('2023-02-23T05:06:29.716Z'), updatedAt: new Date('2023-02-23T05:06:29.716Z'), localDateTime: new Date('2023-02-23T05:06:29.716Z'), + isShownInMemory: true, isFavorite: true, isArchived: false, duration: null, @@ -59,6 +60,7 @@ export const assetStub = { createdAt: new Date('2023-02-23T05:06:29.716Z'), updatedAt: new Date('2023-02-23T05:06:29.716Z'), localDateTime: new Date('2023-02-23T05:06:29.716Z'), + isShownInMemory: true, isFavorite: true, isArchived: false, duration: null, @@ -98,6 +100,7 @@ export const assetStub = { createdAt: new Date('2023-02-23T05:06:29.716Z'), updatedAt: new Date('2023-02-23T05:06:29.716Z'), localDateTime: new Date('2023-02-23T05:06:29.716Z'), + isShownInMemory: true, isFavorite: true, isArchived: false, isReadOnly: false, @@ -134,6 +137,7 @@ export const assetStub = { createdAt: new Date('2023-02-23T05:06:29.716Z'), updatedAt: new Date('2023-02-23T05:06:29.716Z'), localDateTime: new Date('2023-02-23T05:06:29.716Z'), + isShownInMemory: true, isFavorite: true, isArchived: false, isReadOnly: false, @@ -173,6 +177,7 @@ export const assetStub = { createdAt: new Date('2023-02-23T05:06:29.716Z'), updatedAt: new Date('2023-02-23T05:06:29.716Z'), localDateTime: new Date('2023-02-23T05:06:29.716Z'), + isShownInMemory: true, isFavorite: true, isArchived: false, isReadOnly: false, @@ -212,6 +217,7 @@ export const assetStub = { createdAt: new Date('2023-02-23T05:06:29.716Z'), updatedAt: new Date('2023-02-23T05:06:29.716Z'), localDateTime: new Date('2023-02-23T05:06:29.716Z'), + isShownInMemory: true, isFavorite: true, isArchived: false, isReadOnly: false, @@ -251,6 +257,7 @@ export const assetStub = { createdAt: new Date('2023-02-23T05:06:29.716Z'), updatedAt: new Date('2023-02-23T05:06:29.716Z'), localDateTime: new Date('2023-02-23T05:06:29.716Z'), + isShownInMemory: true, isFavorite: true, isArchived: false, isReadOnly: false, @@ -291,6 +298,7 @@ export const assetStub = { updatedAt: new Date('2023-02-23T05:06:29.716Z'), deletedAt: null, localDateTime: new Date('2023-02-23T05:06:29.716Z'), + isShownInMemory: true, isFavorite: true, isArchived: false, isReadOnly: false, @@ -329,6 +337,7 @@ export const assetStub = { createdAt: new Date('2015-02-23T05:06:29.716Z'), updatedAt: new Date('2015-02-23T05:06:29.716Z'), localDateTime: new Date('2015-02-23T05:06:29.716Z'), + isShownInMemory: true, isFavorite: true, isArchived: false, isExternal: false, @@ -369,6 +378,7 @@ export const assetStub = { createdAt: new Date('2023-02-23T05:06:29.716Z'), updatedAt: new Date('2023-02-23T05:06:29.716Z'), localDateTime: new Date('2023-02-23T05:06:29.716Z'), + isShownInMemory: true, isFavorite: true, isArchived: false, isReadOnly: false, @@ -439,6 +449,7 @@ export const assetStub = { createdAt: new Date('2023-02-23T05:06:29.716Z'), updatedAt: new Date('2023-02-23T05:06:29.716Z'), localDateTime: new Date('2023-02-23T05:06:29.716Z'), + isShownInMemory: true, isFavorite: false, isArchived: false, isReadOnly: false, @@ -479,6 +490,7 @@ export const assetStub = { createdAt: new Date('2023-02-23T05:06:29.716Z'), updatedAt: new Date('2023-02-23T05:06:29.716Z'), localDateTime: new Date('2023-02-23T05:06:29.716Z'), + isShownInMemory: true, isFavorite: true, isArchived: false, isReadOnly: false, diff --git a/server/test/fixtures/shared-link.stub.ts b/server/test/fixtures/shared-link.stub.ts index acb14c6b2..915dc1788 100644 --- a/server/test/fixtures/shared-link.stub.ts +++ b/server/test/fixtures/shared-link.stub.ts @@ -51,6 +51,7 @@ const assetResponse: AssetResponseDto = { resized: false, thumbhash: null, fileModifiedAt: today, + isShownInMemory: true, isExternal: false, isReadOnly: false, isOffline: false, @@ -191,6 +192,7 @@ export const sharedLinkStub = { localDateTime: today, createdAt: today, updatedAt: today, + isShownInMemory: true, isFavorite: false, isArchived: false, isExternal: false, diff --git a/web/src/api/open-api/api.ts b/web/src/api/open-api/api.ts index 549cc59d0..d1defea5b 100644 --- a/web/src/api/open-api/api.ts +++ b/web/src/api/open-api/api.ts @@ -399,6 +399,12 @@ export interface AssetBulkUpdateDto { * @memberof AssetBulkUpdateDto */ 'isFavorite'?: boolean; + /** + * + * @type {boolean} + * @memberof AssetBulkUpdateDto + */ + 'isShownInMemory'?: boolean; } /** * @@ -682,6 +688,12 @@ export interface AssetResponseDto { * @memberof AssetResponseDto */ 'isReadOnly': boolean; + /** + * + * @type {boolean} + * @memberof AssetResponseDto + */ + 'isShownInMemory': boolean; /** * * @type {boolean} diff --git a/web/src/lib/components/asset-viewer/asset-viewer-nav-bar.svelte b/web/src/lib/components/asset-viewer/asset-viewer-nav-bar.svelte index fe83d087b..a9767c264 100644 --- a/web/src/lib/components/asset-viewer/asset-viewer-nav-bar.svelte +++ b/web/src/lib/components/asset-viewer/asset-viewer-nav-bar.svelte @@ -49,6 +49,7 @@ asProfileImage: void; runJob: AssetJobName; playSlideShow: void; + enableMemories: void; }>(); let contextMenuPosition = { x: 0, y: 0 }; @@ -185,6 +186,9 @@ text={api.getAssetJobName(AssetJobName.TranscodeVideo)} /> {/if} + {#if !asset.isShownInMemory} + dispatch('enableMemories')} text="Show in memories" /> + {/if} {/if} {/if} diff --git a/web/src/lib/components/asset-viewer/asset-viewer.svelte b/web/src/lib/components/asset-viewer/asset-viewer.svelte index b1837b860..e06c9dbc5 100644 --- a/web/src/lib/components/asset-viewer/asset-viewer.svelte +++ b/web/src/lib/components/asset-viewer/asset-viewer.svelte @@ -346,6 +346,20 @@ } }; + const handleEnableMemories = async () => { + try { + await api.assetApi.updateAssets({ assetBulkUpdateDto: { ids: [asset.id], isShownInMemory: true } }); + + notificationController.show({ + message: `Asset can be shown in memories`, + type: NotificationType.Info, + }); + asset.isShownInMemory = true; + } catch (error) { + handleError(error, "Can't remove asset from memory"); + } + }; + const handleStopSlideshow = async () => { try { await document.exitFullscreen(); @@ -408,6 +422,7 @@ on:asProfileImage={() => (isShowProfileImageCrop = true)} on:runJob={({ detail: job }) => handleRunJob(job)} on:playSlideShow={handlePlaySlideshow} + on:enableMemories={handleEnableMemories} /> {/if} diff --git a/web/src/lib/components/memory-page/memory-viewer.svelte b/web/src/lib/components/memory-page/memory-viewer.svelte index 4cd0b2cec..9322b4427 100644 --- a/web/src/lib/components/memory-page/memory-viewer.svelte +++ b/web/src/lib/components/memory-page/memory-viewer.svelte @@ -20,6 +20,12 @@ import IntersectionObserver from '$lib/components/asset-viewer/intersection-observer.svelte'; import { fade } from 'svelte/transition'; import { tweened } from 'svelte/motion'; + import ContextMenu from '../shared-components/context-menu/context-menu.svelte'; + import MenuOption from '../shared-components/context-menu/menu-option.svelte'; + import DotsVertical from 'svelte-material-icons/DotsVertical.svelte'; + import { clickOutside } from '$lib/utils/click-outside'; + import { handleError } from '$lib/utils/handle-error'; + import { NotificationType, notificationController } from '../shared-components/notification/notification'; const parseIndex = (s: string | null, max: number | null) => Math.max(Math.min(parseInt(s ?? '') || 0, max ?? 0), 0); @@ -58,6 +64,69 @@ let paused = false; + let isShowAssetOptions = false; + let contextMenuPosition = { x: 0, y: 0 }; + + const updateMemoryStoreAsset = () => { + if ($memoryStore && $memoryStore[memoryIndex].assets[assetIndex]) { + memoryStore.update((memoryStore) => { + const updatedMemoryStore = [...memoryStore]; + + if (memoryIndex >= 0 && memoryIndex < updatedMemoryStore.length) { + const memoryItem = updatedMemoryStore[memoryIndex]; + + if (assetIndex >= 0 && memoryItem.assets.length > 1) { + memoryItem.assets.splice(assetIndex, 1); + } + } + return updatedMemoryStore; // Return the updated memoryStore + }); + } + }; + + const updateMemoryStoreMemory = () => { + if ($memoryStore && $memoryStore[memoryIndex].assets[assetIndex]) { + memoryStore.update((memoryStore) => { + const updatedMemoryStore = [...memoryStore]; + updatedMemoryStore.splice(memoryIndex, 1); + + return updatedMemoryStore; // Return the updated memoryStore + }); + } + }; + + const removeFromMemory = async () => { + try { + await api.assetApi.updateAssets({ assetBulkUpdateDto: { ids: [currentAsset.id], isShownInMemory: false } }); + + notificationController.show({ + message: `Removed asset from memory`, + type: NotificationType.Info, + }); + if (currentMemory?.assets.length === 1) { + if ($memoryStore?.length === 1) { + goto(AppRoute.PHOTOS); + } else { + if ($memoryStore?.length === memoryIndex + 1) { + toPreviousMemory(); + } else { + toNextMemory(); + } + updateMemoryStoreAsset(); + updateMemoryStoreMemory(); + } + } else { + updateMemoryStoreAsset(); + } + isShowAssetOptions = false; + } catch (error) { + handleError(error, "Can't remove asset from memory"); + } + }; + const handleRemoveFromMemory = () => { + isShowAssetOptions = !isShowAssetOptions; + }; + // Play or pause progress when the paused state changes. $: paused ? pause() : play(); @@ -222,6 +291,16 @@ {currentAsset.exifInfo?.country || ''}

+
+
(isShowAssetOptions = false)}> + + {#if isShowAssetOptions} + + + + {/if} +
+