From 53c3c916a663c07a7e98352dd2817b75e0eea649 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 3 Jun 2022 11:04:30 -0500 Subject: [PATCH] View assets detail and download operation (#198) * Fixed not displaying default user profile picture * Added buttons to close viewer and micro-interaction for navigating assets left, right * Add additional buttons to the control bar * Display EXIF info * Added map to detail info * Handle user input keyboard * Fixed incorrect file name when downloading multiple files * Implemented download panel --- server/src/api-v1/asset/asset.controller.ts | 2 +- server/src/api-v1/asset/asset.service.ts | 26 +- server/src/api-v1/user/user.service.ts | 7 +- web/package-lock.json | 202 +++++++++++++++ web/package.json | 4 + .../asset-viewer/asser-viewer-nav-bar.svelte | 22 ++ .../asset-viewer/asset-viewer.svelte | 234 ++++++++++++++++++ .../asset-viewer/detail-panel.svelte | 164 ++++++++++++ .../asset-viewer/download-panel.svelte | 26 ++ .../immich-thumbnail.svelte | 21 +- .../intersection-observer.svelte | 0 .../photo-viewer.svelte} | 25 +- .../shared/circle_icon_button.svelte | 18 ++ .../components/shared/navigation-bar.svelte | 13 +- .../components/shared/side-bar-button.svelte | 4 +- web/src/lib/stores/download.ts | 13 + web/src/routes/__layout.svelte | 22 +- web/src/routes/photos/[assetId].svelte | 20 ++ web/src/routes/photos/index.svelte | 75 +----- 19 files changed, 798 insertions(+), 100 deletions(-) create mode 100644 web/src/lib/components/asset-viewer/asser-viewer-nav-bar.svelte create mode 100644 web/src/lib/components/asset-viewer/asset-viewer.svelte create mode 100644 web/src/lib/components/asset-viewer/detail-panel.svelte create mode 100644 web/src/lib/components/asset-viewer/download-panel.svelte rename web/src/lib/components/{photos => asset-viewer}/immich-thumbnail.svelte (84%) rename web/src/lib/components/{photos => asset-viewer}/intersection-observer.svelte (100%) rename web/src/lib/components/{photos/photo_viewer.svelte => asset-viewer/photo-viewer.svelte} (71%) create mode 100644 web/src/lib/components/shared/circle_icon_button.svelte create mode 100644 web/src/lib/stores/download.ts create mode 100644 web/src/routes/photos/[assetId].svelte diff --git a/server/src/api-v1/asset/asset.controller.ts b/server/src/api-v1/asset/asset.controller.ts index 2e9820b76d..83316abbfe 100644 --- a/server/src/api-v1/asset/asset.controller.ts +++ b/server/src/api-v1/asset/asset.controller.ts @@ -123,7 +123,7 @@ export class AssetController { @Get('/') async getAllAssets(@GetAuthUser() authUser: AuthUserDto) { - return await this.assetService.getAllAssetsNoPagination(authUser); + return await this.assetService.getAllAssets(authUser); } @Get('/:deviceId') diff --git a/server/src/api-v1/asset/asset.service.ts b/server/src/api-v1/asset/asset.service.ts index 6237e4aa28..59a0d11ed3 100644 --- a/server/src/api-v1/asset/asset.service.ts +++ b/server/src/api-v1/asset/asset.service.ts @@ -61,13 +61,17 @@ export class AssetService { return res; } - public async getAllAssetsNoPagination(authUser: AuthUserDto) { + public async getAllAssets(authUser: AuthUserDto) { try { - return await this.assetRepository - .createQueryBuilder('a') - .where('a."userId" = :userId', { userId: authUser.id }) - .orderBy('a."createdAt"::date', 'DESC') - .getMany(); + return await this.assetRepository.find({ + where: { + userId: authUser.id + }, + relations: ['exifInfo'], + order: { + createdAt: 'DESC' + } + }) } catch (e) { Logger.error(e, 'getAllAssets'); } @@ -100,8 +104,18 @@ export class AssetService { const asset = await this.findOne(query.did, query.aid); if (query.isThumb === 'false' || !query.isThumb) { + const { size } = await fileInfo(asset.originalPath); + res.set({ + 'Content-Type': asset.mimeType, + 'Content-Length': size, + }); file = createReadStream(asset.originalPath); } else { + const { size } = await fileInfo(asset.resizePath); + res.set({ + 'Content-Type': 'image/jpeg', + 'Content-Length': size, + }); file = createReadStream(asset.resizePath); } diff --git a/server/src/api-v1/user/user.service.ts b/server/src/api-v1/user/user.service.ts index e747b521f0..cc5954b8ba 100644 --- a/server/src/api-v1/user/user.service.ts +++ b/server/src/api-v1/user/user.service.ts @@ -147,15 +147,16 @@ export class UserService { async getUserProfileImage(userId: string, res: Res) { try { const user = await this.userRepository.findOne({ id: userId }) + if (!user.profileImagePath) { - console.log("empty return") - throw new BadRequestException('User does not have a profile image'); + // throw new BadRequestException('User does not have a profile image'); + res.status(404).send('User does not have a profile image'); + return; } res.set({ 'Content-Type': 'image/jpeg', }); - const fileStream = createReadStream(user.profileImagePath) return new StreamableFile(fileStream); } catch (e) { diff --git a/web/package-lock.json b/web/package-lock.json index abb58313e6..3d2be5a20d 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -8,7 +8,9 @@ "name": "web", "version": "0.0.1", "dependencies": { + "axios": "^0.27.2", "cookie": "^0.4.2", + "leaflet": "^1.8.0", "lodash": "^4.17.21", "lodash-es": "^4.17.21", "moment": "^2.29.3", @@ -18,8 +20,10 @@ "@sveltejs/adapter-auto": "next", "@sveltejs/adapter-node": "^1.0.0-next.73", "@sveltejs/kit": "next", + "@types/axios": "^0.14.0", "@types/bcrypt": "^5.0.0", "@types/cookie": "^0.4.1", + "@types/leaflet": "^1.7.10", "@types/lodash": "^4.14.182", "@types/lodash-es": "^4.17.6", "@typescript-eslint/eslint-plugin": "^5.10.1", @@ -231,6 +235,16 @@ } } }, + "node_modules/@types/axios": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@types/axios/-/axios-0.14.0.tgz", + "integrity": "sha512-KqQnQbdYE54D7oa/UmYVMZKq7CO4l8DEENzOKc4aBRwxCXSlJXGz83flFx5L7AWrOQnmuN3kVsRdt+GZPPjiVQ==", + "deprecated": "This is a stub types definition for axios (https://github.com/mzabriskie/axios). axios provides its own type definitions, so you don't need @types/axios installed!", + "dev": true, + "dependencies": { + "axios": "*" + } + }, "node_modules/@types/bcrypt": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-5.0.0.tgz", @@ -246,12 +260,27 @@ "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==", "dev": true }, + "node_modules/@types/geojson": { + "version": "7946.0.8", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.8.tgz", + "integrity": "sha512-1rkryxURpr6aWP7R786/UQOkJ3PcpQiWkAXBmdWc7ryFWqN6a4xfK7BtjXvFBKO9LjQ+MWQSWxYeZX1OApnArA==", + "dev": true + }, "node_modules/@types/json-schema": { "version": "7.0.11", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", "dev": true }, + "node_modules/@types/leaflet": { + "version": "1.7.10", + "resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.7.10.tgz", + "integrity": "sha512-RzK5BYwYboOXXxyF01tp8g1J8UbdRvoaf+F/jCnVaWC42+QITB6wKvUklcX7jCMRWkzTnGO9NLg7A6SzrlGALA==", + "dev": true, + "dependencies": { + "@types/geojson": "*" + } + }, "node_modules/@types/lodash": { "version": "4.14.182", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.182.tgz", @@ -599,6 +628,11 @@ "node": ">=8" } }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, "node_modules/autoprefixer": { "version": "10.4.7", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.7.tgz", @@ -632,6 +666,15 @@ "postcss": "^8.1.0" } }, + "node_modules/axios": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz", + "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==", + "dependencies": { + "follow-redirects": "^1.14.9", + "form-data": "^4.0.0" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -802,6 +845,17 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -880,6 +934,14 @@ "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=", "dev": true }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/detect-indent": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz", @@ -1653,6 +1715,38 @@ "integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==", "dev": true }, + "node_modules/follow-redirects": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz", + "integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fraction.js": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz", @@ -1947,6 +2041,11 @@ "node": ">=6" } }, + "node_modules/leaflet": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.8.0.tgz", + "integrity": "sha512-gwhMjFCQiYs3x/Sf+d49f10ERXaEFCPr+nVTryhAW8DWbMGqJqt9G4XuIaHmFW08zYvhgdzqXGr8AlW8v8dQkA==" + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -2031,6 +2130,25 @@ "node": ">=8.6" } }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/min-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", @@ -3276,6 +3394,15 @@ "svelte-hmr": "^0.14.11" } }, + "@types/axios": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@types/axios/-/axios-0.14.0.tgz", + "integrity": "sha512-KqQnQbdYE54D7oa/UmYVMZKq7CO4l8DEENzOKc4aBRwxCXSlJXGz83flFx5L7AWrOQnmuN3kVsRdt+GZPPjiVQ==", + "dev": true, + "requires": { + "axios": "*" + } + }, "@types/bcrypt": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-5.0.0.tgz", @@ -3291,12 +3418,27 @@ "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==", "dev": true }, + "@types/geojson": { + "version": "7946.0.8", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.8.tgz", + "integrity": "sha512-1rkryxURpr6aWP7R786/UQOkJ3PcpQiWkAXBmdWc7ryFWqN6a4xfK7BtjXvFBKO9LjQ+MWQSWxYeZX1OApnArA==", + "dev": true + }, "@types/json-schema": { "version": "7.0.11", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", "dev": true }, + "@types/leaflet": { + "version": "1.7.10", + "resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.7.10.tgz", + "integrity": "sha512-RzK5BYwYboOXXxyF01tp8g1J8UbdRvoaf+F/jCnVaWC42+QITB6wKvUklcX7jCMRWkzTnGO9NLg7A6SzrlGALA==", + "dev": true, + "requires": { + "@types/geojson": "*" + } + }, "@types/lodash": { "version": "4.14.182", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.182.tgz", @@ -3521,6 +3663,11 @@ "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", "dev": true }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, "autoprefixer": { "version": "10.4.7", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.7.tgz", @@ -3535,6 +3682,15 @@ "postcss-value-parser": "^4.2.0" } }, + "axios": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz", + "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==", + "requires": { + "follow-redirects": "^1.14.9", + "form-data": "^4.0.0" + } + }, "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -3644,6 +3800,14 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -3699,6 +3863,11 @@ "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=", "dev": true }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" + }, "detect-indent": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz", @@ -4192,6 +4361,21 @@ "integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==", "dev": true }, + "follow-redirects": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz", + "integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==" + }, + "form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + }, "fraction.js": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz", @@ -4412,6 +4596,11 @@ "integrity": "sha512-8QADVssbrFjivHWQU7KkMgptGTl6WAcSdlbBPY4uNF+mWr6DGcKrvY2w4FQJoXch7+fKMjj0dRrL75vk3k23OA==", "dev": true }, + "leaflet": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.8.0.tgz", + "integrity": "sha512-gwhMjFCQiYs3x/Sf+d49f10ERXaEFCPr+nVTryhAW8DWbMGqJqt9G4XuIaHmFW08zYvhgdzqXGr8AlW8v8dQkA==" + }, "levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -4478,6 +4667,19 @@ "picomatch": "^2.3.1" } }, + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" + }, + "mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "requires": { + "mime-db": "1.52.0" + } + }, "min-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", diff --git a/web/package.json b/web/package.json index 014e2de157..6189345244 100644 --- a/web/package.json +++ b/web/package.json @@ -16,8 +16,10 @@ "@sveltejs/adapter-auto": "next", "@sveltejs/adapter-node": "^1.0.0-next.73", "@sveltejs/kit": "next", + "@types/axios": "^0.14.0", "@types/bcrypt": "^5.0.0", "@types/cookie": "^0.4.1", + "@types/leaflet": "^1.7.10", "@types/lodash": "^4.14.182", "@types/lodash-es": "^4.17.6", "@typescript-eslint/eslint-plugin": "^5.10.1", @@ -38,7 +40,9 @@ }, "type": "module", "dependencies": { + "axios": "^0.27.2", "cookie": "^0.4.2", + "leaflet": "^1.8.0", "lodash": "^4.17.21", "lodash-es": "^4.17.21", "moment": "^2.29.3", diff --git a/web/src/lib/components/asset-viewer/asser-viewer-nav-bar.svelte b/web/src/lib/components/asset-viewer/asser-viewer-nav-bar.svelte new file mode 100644 index 0000000000..ac1ce747be --- /dev/null +++ b/web/src/lib/components/asset-viewer/asser-viewer-nav-bar.svelte @@ -0,0 +1,22 @@ + + +
+
+ dispatch('goBack')} /> +
+
+ dispatch('download')} /> + + dispatch('showDetail')} /> +
+
diff --git a/web/src/lib/components/asset-viewer/asset-viewer.svelte b/web/src/lib/components/asset-viewer/asset-viewer.svelte new file mode 100644 index 0000000000..c19df36132 --- /dev/null +++ b/web/src/lib/components/asset-viewer/asset-viewer.svelte @@ -0,0 +1,234 @@ + + +
+
+ +
+ +
{ + halfLeftHover = true; + halfRightHover = false; + }} + on:mouseleave={() => { + halfLeftHover = false; + }} + on:click={navigateAssetBackward} + > + +
+ +
+ {#key selectedIndex} + {#if viewAssetId && viewDeviceId} + {#if selectedAsset.type == AssetType.IMAGE} + + {:else} +
+

Video viewer is under construction

+
+ {/if} + {/if} + {/key} +
+ +
{ + halfLeftHover = false; + halfRightHover = true; + }} + on:mouseleave={() => { + halfRightHover = false; + }} + > + +
+ + {#if isShowDetail} +
+ (isShowDetail = false)} /> +
+ {/if} +
+ + diff --git a/web/src/lib/components/asset-viewer/detail-panel.svelte b/web/src/lib/components/asset-viewer/detail-panel.svelte new file mode 100644 index 0000000000..f607f9f98d --- /dev/null +++ b/web/src/lib/components/asset-viewer/detail-panel.svelte @@ -0,0 +1,164 @@ + + +
+
+ + +

Info

+
+ +
+

DETAILS

+ + {#if asset.exifInfo?.dateTimeOriginal} +
+
+ +
+ +
+

{moment(asset.exifInfo.dateTimeOriginal).format('MMM DD')}

+
+

+ {moment( + asset.exifInfo.dateTimeOriginal + .toString() + .slice(0, asset.exifInfo.dateTimeOriginal.toString().length - 1), + ).format('ddd, hh:mm A')} +

+

GMT{moment(asset.exifInfo.dateTimeOriginal).format('Z')}

+
+
+
{/if} + + {#if asset.exifInfo?.fileSizeInByte} +
+
+ +
+

{`${asset.exifInfo.imageName}.${asset.originalPath.split('.')[1]}` || ''}

+
+

{((asset.exifInfo.exifImageHeight * asset.exifInfo.exifImageWidth) / 1_000_000).toFixed(0)}MP

+

{asset.exifInfo.exifImageHeight} x {asset.exifInfo.exifImageWidth}

+

{getHumanReadableString(asset.exifInfo.fileSizeInByte)}

+
+
+
+ {/if} + + {#if asset.exifInfo?.fNumber} +
+
+ +
+

{asset.exifInfo.make || ''} {asset.exifInfo.model || ''}

+
+

{`f/${asset.exifInfo.fNumber}` || ''}

+

{`1/${1 / asset.exifInfo.exposureTime}` || ''}

+

{`${asset.exifInfo.focalLength}mm` || ''}

+

{`ISO${asset.exifInfo.iso}` || ''}

+
+
+
+ {/if} + + {#if asset.exifInfo?.city} +
+
+ +
+

{asset.exifInfo.city}

+
+

{asset.exifInfo.state},

+

{asset.exifInfo.country}

+
+
+
+ {/if} +
+
+ +
+
+
+ + diff --git a/web/src/lib/components/asset-viewer/download-panel.svelte b/web/src/lib/components/asset-viewer/download-panel.svelte new file mode 100644 index 0000000000..b18a704e33 --- /dev/null +++ b/web/src/lib/components/asset-viewer/download-panel.svelte @@ -0,0 +1,26 @@ + + +{#if $isDownloading} +
+

DOWNLOADING

+
+ {#each Object.keys($downloadAssets) as fileName} +
+

■ {fileName}

+
+

{$downloadAssets[fileName]}/100

+
+
+
+
+
+ {/each} +
+
+{/if} diff --git a/web/src/lib/components/photos/immich-thumbnail.svelte b/web/src/lib/components/asset-viewer/immich-thumbnail.svelte similarity index 84% rename from web/src/lib/components/photos/immich-thumbnail.svelte rename to web/src/lib/components/asset-viewer/immich-thumbnail.svelte index 9cdfd0a633..6b7e357cc4 100644 --- a/web/src/lib/components/photos/immich-thumbnail.svelte +++ b/web/src/lib/components/asset-viewer/immich-thumbnail.svelte @@ -4,8 +4,7 @@ import { createEventDispatcher, onDestroy } from 'svelte'; import { fade } from 'svelte/transition'; import { serverEndpoint } from '../../constants'; - - import IntersectionObserver from '$lib/components/photos/intersection-observer.svelte'; + import IntersectionObserver from '$lib/components/asset-viewer/intersection-observer.svelte'; import CheckCircle from 'svelte-material-icons/CheckCircle.svelte'; import PlayCircleOutline from 'svelte-material-icons/PlayCircleOutline.svelte'; @@ -57,6 +56,7 @@ return videoData; } }; + const parseVideoDuration = (duration: string) => { const timePart = duration.split(':'); const hours = timePart[0]; @@ -71,11 +71,21 @@ }; onDestroy(() => URL.revokeObjectURL(imageContent)); + + const getSize = () => { + if (asset.exifInfo?.orientation === 'Rotate 90 CW') { + return 'w-[176px] h-[235px]'; + } else if (asset.exifInfo?.orientation === 'Horizontal (normal)') { + return 'w-[313px] h-[235px]'; + } else { + return 'w-[235px] h-[235px]'; + } + };
(mouseOver = true)} on:mouseleave={() => (mouseOver = false)} on:click={() => dispatch('viewAsset', { assetId: asset.id, deviceId: asset.deviceId })} @@ -104,13 +114,12 @@ {#if intersecting} {#await loadImageData()} -
...
+
...
{:then imageData} {asset.id} {/await} diff --git a/web/src/lib/components/photos/intersection-observer.svelte b/web/src/lib/components/asset-viewer/intersection-observer.svelte similarity index 100% rename from web/src/lib/components/photos/intersection-observer.svelte rename to web/src/lib/components/asset-viewer/intersection-observer.svelte diff --git a/web/src/lib/components/photos/photo_viewer.svelte b/web/src/lib/components/asset-viewer/photo-viewer.svelte similarity index 71% rename from web/src/lib/components/photos/photo_viewer.svelte rename to web/src/lib/components/asset-viewer/photo-viewer.svelte index b4a7d03985..3a148947b3 100644 --- a/web/src/lib/components/photos/photo_viewer.svelte +++ b/web/src/lib/components/asset-viewer/photo-viewer.svelte @@ -3,12 +3,13 @@ import { serverEndpoint } from '$lib/constants'; import { fade } from 'svelte/transition'; - import type { ImmichAsset } from '$lib/models/immich-asset'; + import type { ImmichAsset, ImmichExif } from '$lib/models/immich-asset'; import { createEventDispatcher, onMount } from 'svelte'; import LoadingSpinner from '../shared/loading-spinner.svelte'; export let assetId: string; export let deviceId: string; + let assetInfo: ImmichAsset; const dispatch = createEventDispatcher(); @@ -41,22 +42,18 @@ }; -
dispatch('close')} class="h-screen"> +
{#if assetInfo} {#await loadAssetData()} -
- -
+ {:then assetData} -
- {assetId} -
+ {assetId} {/await} {/if}
diff --git a/web/src/lib/components/shared/circle_icon_button.svelte b/web/src/lib/components/shared/circle_icon_button.svelte new file mode 100644 index 0000000000..0753211703 --- /dev/null +++ b/web/src/lib/components/shared/circle_icon_button.svelte @@ -0,0 +1,18 @@ + + + diff --git a/web/src/lib/components/shared/navigation-bar.svelte b/web/src/lib/components/shared/navigation-bar.svelte index 8e3cf47798..af253c7c05 100644 --- a/web/src/lib/components/shared/navigation-bar.svelte +++ b/web/src/lib/components/shared/navigation-bar.svelte @@ -1,4 +1,5 @@
@@ -33,11 +40,11 @@ {#if user.isAdmin} - AdministrationAdministration {/if} diff --git a/web/src/lib/components/shared/side-bar-button.svelte b/web/src/lib/components/shared/side-bar-button.svelte index 7d36a25880..eeb02a2232 100644 --- a/web/src/lib/components/shared/side-bar-button.svelte +++ b/web/src/lib/components/shared/side-bar-button.svelte @@ -18,8 +18,8 @@
diff --git a/web/src/lib/stores/download.ts b/web/src/lib/stores/download.ts new file mode 100644 index 0000000000..e87d2a8703 --- /dev/null +++ b/web/src/lib/stores/download.ts @@ -0,0 +1,13 @@ +import { writable, derived } from 'svelte/store'; + +export const downloadAssets = writable>({}); + + +export const isDownloading = derived(downloadAssets, ($downloadAssets) => { + if (Object.keys($downloadAssets).length == 0) { + return false; + } + + return true; +}) + diff --git a/web/src/routes/__layout.svelte b/web/src/routes/__layout.svelte index 13c5700990..28c9692ed0 100644 --- a/web/src/routes/__layout.svelte +++ b/web/src/routes/__layout.svelte @@ -1,9 +1,20 @@ + +
- + {#key url} +
+ + +
+ {/key}