1
0
mirror of https://github.com/immich-app/immich.git synced 2024-11-24 08:52:28 +02:00

feat (web/server) 360 degrees Web panoramas [attempt 2] (#3412)

* commit 1 (isPanorama: boolean)

* working solution for projectiontypeenum

* fix

* format fix

* fix

* fix

* fix

* fix

* enum projectiontype

* working solution with exif

* fix

* reverted >

* fix format

* reverted auto-magic api.ts prettification

* fix

* reverted api.ts autogenerated

* api ts regenerated

* Update web/src/lib/components/assets/thumbnail/thumbnail.svelte

Co-authored-by: Sergey Kondrikov <sergey.kondrikov@gmail.com>

* Update web/src/lib/components/asset-viewer/asset-viewer.svelte

Co-authored-by: Sergey Kondrikov <sergey.kondrikov@gmail.com>

* exifProjectionType

* Update server/src/microservices/processors/metadata-extraction.processor.ts

Co-authored-by: Sergey Kondrikov <sergey.kondrikov@gmail.com>

* projectionType?: string = ProjectionType.NONE;

* not null

* projectionType!: ProjectionType;

* opeapi generator fix

* fixes

* fix

* fix

* generate api

* asset.exifInifo?.projectionType

* Update server/src/domain/asset/response-dto/exif-response.dto.ts

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>

* Update server/src/microservices/processors/metadata-extraction.processor.ts

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>

* enum -> varchar;projectiontypeenum->projectiontype

* asset-viewer fixed prettiffier

* @Column({}) single line

* enum | string

* make api

* enum | string

* enum | str fix

* fix

* chore: use string instead of enum

* chore: open api

* fix: checks

---------

Co-authored-by: Sergey Kondrikov <sergey.kondrikov@gmail.com>
Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
This commit is contained in:
Dmitry Brazhenko 2023-07-28 06:29:09 +02:00 committed by GitHub
parent 13b2b2fc4e
commit e071b82e8a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 262 additions and 47 deletions

View File

@ -1304,6 +1304,12 @@ export interface ExifResponseDto {
* @memberof ExifResponseDto
*/
'description'?: string | null;
/**
*
* @type {string}
* @memberof ExifResponseDto
*/
'projectionType'?: string | null;
}
/**
*

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -5553,6 +5553,11 @@
"type": "string",
"nullable": true,
"default": null
},
"projectionType": {
"type": "string",
"nullable": true,
"default": null
}
}
},

View File

@ -28,7 +28,7 @@
"class-transformer": "^0.5.1",
"class-validator": "^0.14.0",
"cookie-parser": "^1.4.6",
"exiftool-vendored": "^19.0.0",
"exiftool-vendored": "^22.0.0",
"exiftool-vendored.pl": "^12.54.0",
"fluent-ffmpeg": "^2.1.2",
"handlebars": "^4.7.7",
@ -4064,9 +4064,9 @@
}
},
"node_modules/batch-cluster": {
"version": "11.0.0",
"resolved": "https://registry.npmjs.org/batch-cluster/-/batch-cluster-11.0.0.tgz",
"integrity": "sha512-8iwqa+rKTaakOHkqdcXDT5L5117pa+FoP8/yAKpNdL44ZnC4V2NEA/sIg0ZO0O9NkpdjLk0A3efRFM5nVizqHw==",
"version": "12.1.0",
"resolved": "https://registry.npmjs.org/batch-cluster/-/batch-cluster-12.1.0.tgz",
"integrity": "sha512-whGyJU4tr7kyg2USByu0/51mML5HsLAeNz5s03kMDYZNsQsGgDJgI47RdY3r7MciCjPkTaTD5O4eOVqOfEO7pg==",
"engines": {
"node": ">=14"
}
@ -5976,25 +5976,25 @@
}
},
"node_modules/exiftool-vendored": {
"version": "19.0.0",
"resolved": "https://registry.npmjs.org/exiftool-vendored/-/exiftool-vendored-19.0.0.tgz",
"integrity": "sha512-Zes7TZrYWxts92mbF2Gs3drtWZucm4qsaeYaE6A+OOqmeD9UGaGisqIbyh9MilJrLi+ZHzWEJZtDj37QFf6xsA==",
"version": "22.0.0",
"resolved": "https://registry.npmjs.org/exiftool-vendored/-/exiftool-vendored-22.0.0.tgz",
"integrity": "sha512-gBOQ4C2GLjxKPDPRuUbMOz91mG6IFA22L+Z/IQzFotFu20vc7YroqHALf/ophCbANA5sNSArbVDPijP7n/20Jg==",
"dependencies": {
"@photostructure/tz-lookup": "^7.0.0",
"@types/luxon": "^3.2.0",
"batch-cluster": "^11.0.0",
"@types/luxon": "^3.3.0",
"batch-cluster": "^12.1.0",
"he": "^1.2.0",
"luxon": "^3.2.1"
"luxon": "^3.3.0"
},
"optionalDependencies": {
"exiftool-vendored.exe": "12.54.0",
"exiftool-vendored.pl": "12.54.0"
"exiftool-vendored.exe": "12.62.0",
"exiftool-vendored.pl": "12.62.0"
}
},
"node_modules/exiftool-vendored.exe": {
"version": "12.54.0",
"resolved": "https://registry.npmjs.org/exiftool-vendored.exe/-/exiftool-vendored.exe-12.54.0.tgz",
"integrity": "sha512-Dc4W6e0NtQfYuJIYK4piHfDJnd2jvA04e0aaq9R3Q1oO34KC5e+L1D2C7lFuZXqPQLYC1x3GYc/GVv5e+SkkrQ==",
"version": "12.62.0",
"resolved": "https://registry.npmjs.org/exiftool-vendored.exe/-/exiftool-vendored.exe-12.62.0.tgz",
"integrity": "sha512-xNFkvmjwnMg6ivtmkc67w1qD23fIy06nRpMpGuBpTwTqAVatHV+vk7T75zyvLoXRRpd1rKID9XAVLGJCE/iiMQ==",
"optional": true,
"os": [
"win32"
@ -6008,15 +6008,6 @@
"!win32"
]
},
"node_modules/exiftool-vendored/node_modules/exiftool-vendored.pl": {
"version": "12.54.0",
"resolved": "https://registry.npmjs.org/exiftool-vendored.pl/-/exiftool-vendored.pl-12.54.0.tgz",
"integrity": "sha512-RBBowsYcM6EvbWoBkg2dOqHpH3WIzN7bIzHc+o+LquqCTo3doZwECClD/6PNHVSMQsl2Z0fEf75sNq2msooMSg==",
"optional": true,
"os": [
"!win32"
]
},
"node_modules/exit": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz",
@ -15584,9 +15575,9 @@
"integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog=="
},
"batch-cluster": {
"version": "11.0.0",
"resolved": "https://registry.npmjs.org/batch-cluster/-/batch-cluster-11.0.0.tgz",
"integrity": "sha512-8iwqa+rKTaakOHkqdcXDT5L5117pa+FoP8/yAKpNdL44ZnC4V2NEA/sIg0ZO0O9NkpdjLk0A3efRFM5nVizqHw=="
"version": "12.1.0",
"resolved": "https://registry.npmjs.org/batch-cluster/-/batch-cluster-12.1.0.tgz",
"integrity": "sha512-whGyJU4tr7kyg2USByu0/51mML5HsLAeNz5s03kMDYZNsQsGgDJgI47RdY3r7MciCjPkTaTD5O4eOVqOfEO7pg=="
},
"bcrypt": {
"version": "5.1.0",
@ -17011,31 +17002,23 @@
}
},
"exiftool-vendored": {
"version": "19.0.0",
"resolved": "https://registry.npmjs.org/exiftool-vendored/-/exiftool-vendored-19.0.0.tgz",
"integrity": "sha512-Zes7TZrYWxts92mbF2Gs3drtWZucm4qsaeYaE6A+OOqmeD9UGaGisqIbyh9MilJrLi+ZHzWEJZtDj37QFf6xsA==",
"version": "22.0.0",
"resolved": "https://registry.npmjs.org/exiftool-vendored/-/exiftool-vendored-22.0.0.tgz",
"integrity": "sha512-gBOQ4C2GLjxKPDPRuUbMOz91mG6IFA22L+Z/IQzFotFu20vc7YroqHALf/ophCbANA5sNSArbVDPijP7n/20Jg==",
"requires": {
"@photostructure/tz-lookup": "^7.0.0",
"@types/luxon": "^3.2.0",
"batch-cluster": "^11.0.0",
"exiftool-vendored.exe": "12.54.0",
"exiftool-vendored.pl": "12.54.0",
"@types/luxon": "^3.3.0",
"batch-cluster": "^12.1.0",
"exiftool-vendored.exe": "12.62.0",
"exiftool-vendored.pl": "12.62.0",
"he": "^1.2.0",
"luxon": "^3.2.1"
},
"dependencies": {
"exiftool-vendored.pl": {
"version": "12.54.0",
"resolved": "https://registry.npmjs.org/exiftool-vendored.pl/-/exiftool-vendored.pl-12.54.0.tgz",
"integrity": "sha512-RBBowsYcM6EvbWoBkg2dOqHpH3WIzN7bIzHc+o+LquqCTo3doZwECClD/6PNHVSMQsl2Z0fEf75sNq2msooMSg==",
"optional": true
}
"luxon": "^3.3.0"
}
},
"exiftool-vendored.exe": {
"version": "12.54.0",
"resolved": "https://registry.npmjs.org/exiftool-vendored.exe/-/exiftool-vendored.exe-12.54.0.tgz",
"integrity": "sha512-Dc4W6e0NtQfYuJIYK4piHfDJnd2jvA04e0aaq9R3Q1oO34KC5e+L1D2C7lFuZXqPQLYC1x3GYc/GVv5e+SkkrQ==",
"version": "12.62.0",
"resolved": "https://registry.npmjs.org/exiftool-vendored.exe/-/exiftool-vendored.exe-12.62.0.tgz",
"integrity": "sha512-xNFkvmjwnMg6ivtmkc67w1qD23fIy06nRpMpGuBpTwTqAVatHV+vk7T75zyvLoXRRpd1rKID9XAVLGJCE/iiMQ==",
"optional": true
},
"exiftool-vendored.pl": {

View File

@ -58,7 +58,7 @@
"class-transformer": "^0.5.1",
"class-validator": "^0.14.0",
"cookie-parser": "^1.4.6",
"exiftool-vendored": "^19.0.0",
"exiftool-vendored": "^22.0.0",
"exiftool-vendored.pl": "^12.54.0",
"fluent-ffmpeg": "^2.1.2",
"handlebars": "^4.7.7",

View File

@ -24,6 +24,7 @@ export class ExifResponseDto {
state?: string | null = null;
country?: string | null = null;
description?: string | null = null;
projectionType?: string | null = null;
}
export function mapExif(entity: ExifEntity): ExifResponseDto {
@ -48,5 +49,6 @@ export function mapExif(entity: ExifEntity): ExifResponseDto {
state: entity.state,
country: entity.country,
description: entity.description,
projectionType: entity.projectionType,
};
}

View File

@ -43,6 +43,9 @@ export class ExifEntity {
@Column({ type: 'float', nullable: true })
longitude!: number | null;
@Column({ type: 'varchar', nullable: true })
projectionType!: string | null;
@Column({ type: 'varchar', nullable: true })
city!: string | null;

View File

@ -0,0 +1,13 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
export class Panoramas1690217088596 implements MigrationInterface {
name = 'Panoramas1690217088596';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "exif" ADD "projectionType" character varying`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "exif" DROP COLUMN "projectionType"`);
}
}

View File

@ -336,6 +336,12 @@ export class MetadataExtractionProcessor {
await this.extractEmbeddedVideo(asset, offset, null, fileCreatedAt);
}
}
const projectionType = getExifProperty('ProjectionType');
if (projectionType) {
newExif.projectionType = String(projectionType).toUpperCase();
}
newExif.livePhotoCID = getExifProperty('MediaGroupUUID');
if (newExif.livePhotoCID && !asset.livePhotoVideoId) {
const motionAsset = await this.assetRepository.findLivePhotoMatch({

View File

@ -625,6 +625,7 @@ const assetInfo: ExifResponseDto = {
state: 'state',
country: 'country',
description: 'description',
projectionType: null,
};
const assetResponse: AssetResponseDto = {
@ -882,6 +883,7 @@ export const sharedLinkStub = {
livePhotoVideoId: null,
originalFileName: 'asset_1.jpeg',
exifInfo: {
projectionType: null,
livePhotoCID: null,
assetId: 'id_1',
description: 'description',

103
web/package-lock.json generated
View File

@ -8,6 +8,7 @@
"name": "immich-web",
"version": "1.0.0",
"dependencies": {
"@egjs/svelte-view360": "^4.0.0-beta.7",
"@zoom-image/svelte": "^0.1.8",
"axios": "^0.27.2",
"buffer": "^6.0.3",
@ -1840,6 +1841,47 @@
"integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==",
"dev": true
},
"node_modules/@cfcs/core": {
"version": "0.0.24",
"resolved": "https://registry.npmjs.org/@cfcs/core/-/core-0.0.24.tgz",
"integrity": "sha512-feB38qu+eDk0Pggh/yR7gjaNmvUYA2uCxHP3Pz2MLE4LZ/9jPdtu8bzCSI47yTEhWyZCF5Pk698hdz8IN2mTjA==",
"dependencies": {
"@egjs/component": "^3.0.4"
}
},
"node_modules/@egjs/component": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@egjs/component/-/component-3.0.4.tgz",
"integrity": "sha512-sXA7bGbIeLF2OAw/vpka66c6QBBUPcA4UUhR4WGJfnp2XWdiI8QrnJGJMr/UxpE/xnevX9tN3jvNPlW8WkHl3g=="
},
"node_modules/@egjs/imready": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/@egjs/imready/-/imready-1.4.1.tgz",
"integrity": "sha512-JIOBs4lB7FYdsKi5uvz2j3SObX8eShtZjtqlOH41tm185aJOQZwiKBK8+V4MxzG4X6DqVhpdN8UcuVwBbElfsg==",
"dependencies": {
"@cfcs/core": "^0.0.24",
"@egjs/component": "^3.0.1"
}
},
"node_modules/@egjs/svelte-view360": {
"version": "4.0.0-beta.7",
"resolved": "https://registry.npmjs.org/@egjs/svelte-view360/-/svelte-view360-4.0.0-beta.7.tgz",
"integrity": "sha512-qFNbLNME8H7QU2lg8SCKUTPoBXVdBcM5m8zmlDRE72esCTguDzUq2szXD7L1JWcb2lYPTFl3HVp/sZlcQ/1HpQ==",
"dependencies": {
"@egjs/view360": "4.0.0-beta.7"
}
},
"node_modules/@egjs/view360": {
"version": "4.0.0-beta.7",
"resolved": "https://registry.npmjs.org/@egjs/view360/-/view360-4.0.0-beta.7.tgz",
"integrity": "sha512-prVTTxuQ1/k59NM7G0tm58k2vPHGoaExoFr5E7MoJaSGF56Otj4okQHAxxosXH87aQLN0feZMtBlsKz0b/7zEw==",
"dependencies": {
"@egjs/component": "^3.0.2",
"@egjs/imready": "^1.3.0",
"@types/webxr": "^0.5.1",
"gl-matrix": "^3.4.3"
}
},
"node_modules/@esbuild/android-arm": {
"version": "0.17.19",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.19.tgz",
@ -3821,6 +3863,11 @@
"integrity": "sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw==",
"dev": true
},
"node_modules/@types/webxr": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.2.tgz",
"integrity": "sha512-szL74BnIcok9m7QwYtVmQ+EdIKwbjPANudfuvDrAF8Cljg9MKUlIoc1w5tjj9PMpeSH3U1Xnx//czQybJ0EfSw=="
},
"node_modules/@types/yargs": {
"version": "17.0.22",
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.22.tgz",
@ -6290,6 +6337,11 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/gl-matrix": {
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.4.3.tgz",
"integrity": "sha512-wcCp8vu8FT22BnvKVPjXa/ICBWRq/zjFfdofZy1WSpQZpphblv12/bOQLBC1rMM7SGOFS9ltVmKOHil5+Ml7gA=="
},
"node_modules/glob": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz",
@ -13334,6 +13386,47 @@
"integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==",
"dev": true
},
"@cfcs/core": {
"version": "0.0.24",
"resolved": "https://registry.npmjs.org/@cfcs/core/-/core-0.0.24.tgz",
"integrity": "sha512-feB38qu+eDk0Pggh/yR7gjaNmvUYA2uCxHP3Pz2MLE4LZ/9jPdtu8bzCSI47yTEhWyZCF5Pk698hdz8IN2mTjA==",
"requires": {
"@egjs/component": "^3.0.4"
}
},
"@egjs/component": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@egjs/component/-/component-3.0.4.tgz",
"integrity": "sha512-sXA7bGbIeLF2OAw/vpka66c6QBBUPcA4UUhR4WGJfnp2XWdiI8QrnJGJMr/UxpE/xnevX9tN3jvNPlW8WkHl3g=="
},
"@egjs/imready": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/@egjs/imready/-/imready-1.4.1.tgz",
"integrity": "sha512-JIOBs4lB7FYdsKi5uvz2j3SObX8eShtZjtqlOH41tm185aJOQZwiKBK8+V4MxzG4X6DqVhpdN8UcuVwBbElfsg==",
"requires": {
"@cfcs/core": "^0.0.24",
"@egjs/component": "^3.0.1"
}
},
"@egjs/svelte-view360": {
"version": "4.0.0-beta.7",
"resolved": "https://registry.npmjs.org/@egjs/svelte-view360/-/svelte-view360-4.0.0-beta.7.tgz",
"integrity": "sha512-qFNbLNME8H7QU2lg8SCKUTPoBXVdBcM5m8zmlDRE72esCTguDzUq2szXD7L1JWcb2lYPTFl3HVp/sZlcQ/1HpQ==",
"requires": {
"@egjs/view360": "4.0.0-beta.7"
}
},
"@egjs/view360": {
"version": "4.0.0-beta.7",
"resolved": "https://registry.npmjs.org/@egjs/view360/-/view360-4.0.0-beta.7.tgz",
"integrity": "sha512-prVTTxuQ1/k59NM7G0tm58k2vPHGoaExoFr5E7MoJaSGF56Otj4okQHAxxosXH87aQLN0feZMtBlsKz0b/7zEw==",
"requires": {
"@egjs/component": "^3.0.2",
"@egjs/imready": "^1.3.0",
"@types/webxr": "^0.5.1",
"gl-matrix": "^3.4.3"
}
},
"@esbuild/android-arm": {
"version": "0.17.19",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.19.tgz",
@ -14748,6 +14841,11 @@
"integrity": "sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw==",
"dev": true
},
"@types/webxr": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.2.tgz",
"integrity": "sha512-szL74BnIcok9m7QwYtVmQ+EdIKwbjPANudfuvDrAF8Cljg9MKUlIoc1w5tjj9PMpeSH3U1Xnx//czQybJ0EfSw=="
},
"@types/yargs": {
"version": "17.0.22",
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.22.tgz",
@ -16510,6 +16608,11 @@
"integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==",
"dev": true
},
"gl-matrix": {
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.4.3.tgz",
"integrity": "sha512-wcCp8vu8FT22BnvKVPjXa/ICBWRq/zjFfdofZy1WSpQZpphblv12/bOQLBC1rMM7SGOFS9ltVmKOHil5+Ml7gA=="
},
"glob": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz",

View File

@ -60,6 +60,7 @@
},
"type": "module",
"dependencies": {
"@egjs/svelte-view360": "^4.0.0-beta.7",
"@zoom-image/svelte": "^0.1.8",
"axios": "^0.27.2",
"buffer": "^6.0.3",

View File

@ -1304,6 +1304,12 @@ export interface ExifResponseDto {
* @memberof ExifResponseDto
*/
'description'?: string | null;
/**
*
* @type {string}
* @memberof ExifResponseDto
*/
'projectionType'?: string | null;
}
/**
*

View File

@ -12,6 +12,8 @@
import DetailPanel from './detail-panel.svelte';
import PhotoViewer from './photo-viewer.svelte';
import VideoViewer from './video-viewer.svelte';
import PanoramaViewer from './panorama-viewer.svelte';
import { ProjectionType } from '$lib/constants';
import ConfirmDialogue from '$lib/components/shared-components/confirm-dialogue.svelte';
import ProfileImageCropper from '../shared-components/profile-image-cropper.svelte';
@ -293,6 +295,8 @@
on:close={closeViewer}
on:onVideoEnded={() => (shouldPlayMotionPhoto = false)}
/>
{:else if asset.exifInfo?.projectionType === ProjectionType.EQUIRECTANGULAR}
<PanoramaViewer {publicSharedKey} {asset} />
{:else}
<PhotoViewer {publicSharedKey} {asset} on:close={closeViewer} />
{/if}

View File

@ -0,0 +1,20 @@
.view360-container {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
touch-action: pan-y;
overflow: hidden;
}
.view360-canvas {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
-ms-user-select: none;
user-select: none;
-webkit-user-drag: none;
}

View File

@ -0,0 +1,40 @@
<script lang="ts">
import { fade } from 'svelte/transition';
import LoadingSpinner from '../shared-components/loading-spinner.svelte';
import { api, AssetResponseDto } from '@api';
import View360, { EquirectProjection } from '@egjs/svelte-view360';
import './panorama-viewer.css';
export let asset: AssetResponseDto;
export let publicSharedKey = '';
let dataUrl = '';
let errorMessage = '';
const loadAssetData = async () => {
try {
const { data } = await api.assetApi.serveFile(
{ id: asset.id, isThumb: false, isWeb: false, key: publicSharedKey },
{ responseType: 'blob' },
);
if (data instanceof Blob) {
dataUrl = URL.createObjectURL(data);
return dataUrl;
} else {
throw new Error('Invalid data format');
}
} catch (error) {
errorMessage = 'Failed to load asset';
return '';
}
};
</script>
<div transition:fade={{ duration: 150 }} class="flex h-full select-none place-content-center place-items-center">
{#await loadAssetData()}
<LoadingSpinner />
{:then assetData}
{#if assetData}
<View360 autoResize={true} initialZoom={0.5} projection={new EquirectProjection({ src: assetData })} />
{:else}
<p>{errorMessage}</p>
{/if}
{/await}
</div>

View File

@ -1,4 +1,5 @@
<script lang="ts">
import { ProjectionType } from '$lib/constants';
import IntersectionObserver from '$lib/components/asset-viewer/intersection-observer.svelte';
import { timeToSeconds } from '$lib/utils/time-to-seconds';
import { api, AssetResponseDto, AssetTypeEnum, ThumbnailFormat } from '@api';
@ -12,6 +13,7 @@
import { fade } from 'svelte/transition';
import ImageThumbnail from './image-thumbnail.svelte';
import VideoThumbnail from './video-thumbnail.svelte';
import Rotate360Icon from 'svelte-material-icons/Rotate360.svelte';
const dispatch = createEventDispatcher();
@ -124,6 +126,14 @@
</div>
{/if}
{#if asset.type === AssetTypeEnum.Image && asset.exifInfo?.projectionType === ProjectionType.EQUIRECTANGULAR}
<div class="absolute right-0 top-0 z-20 flex place-items-center gap-1 text-xs font-medium text-white">
<span class="pr-2 pt-2">
<Rotate360Icon size="24" />
</span>
</div>
{/if}
{#if asset.resized}
<ImageThumbnail
url={api.getAssetThumbnailUrl(asset.id, format, publicSharedKey)}

View File

@ -25,3 +25,14 @@ export enum AppRoute {
AUTH_REGISTER = '/auth/register',
AUTH_CHANGE_PASSWORD = '/auth/change-password',
}
export enum ProjectionType {
EQUIRECTANGULAR = 'EQUIRECTANGULAR',
CUBEMAP = 'CUBEMAP',
CUBESTRIP = 'CUBESTRIP',
EQUIRECTANGULAR_STEREO = 'EQUIRECTANGULAR_STEREO',
CUBEMAP_STEREO = 'CUBEMAP_STEREO',
CUBESTRIP_STEREO = 'CUBESTRIP_STEREO',
CYLINDER = 'CYLINDER',
NONE = 'NONE',
}