From c028c7db4e52ee7889e14118f7bbdc08b88d1e6c Mon Sep 17 00:00:00 2001
From: Alex <alex.tran1502@gmail.com>
Date: Mon, 18 Jul 2022 00:22:39 -0500
Subject: [PATCH] dev/add detail viewer to album (#358)

* Rename asset viewer folder

* Refactor AssetViewer to be able to user with different component

* Refactor AssetViewer to be able to user with different component

* Added viewer for album and sharing
---
 .../src/api-v1/album/album-repository.ts      |  1 +
 .../components/album-page/album-viewer.svelte | 73 ++++++++++++++++-
 .../asser-viewer-nav-bar.svelte               |  0
 .../asset-viewer.svelte                       | 80 ++++++-------------
 .../detail-panel.svelte                       |  0
 .../download-panel.svelte                     |  0
 .../intersection-observer.svelte              |  0
 .../photo-viewer.svelte                       |  0
 .../video-viewer.svelte                       |  0
 .../shared-components/immich-thumbnail.svelte |  2 +-
 .../shared-album-list-tile.svelte             |  2 +-
 web/src/routes/__layout.svelte                |  2 +-
 .../index.svelte}                             |  2 -
 .../albums/[albumId]/photos/[assetId].svelte  | 27 +++++++
 web/src/routes/photos/index.svelte            | 56 ++++++++++---
 15 files changed, 170 insertions(+), 75 deletions(-)
 rename web/src/lib/components/{asset-viewer-page => asset-viewer}/asser-viewer-nav-bar.svelte (100%)
 rename web/src/lib/components/{asset-viewer-page => asset-viewer}/asset-viewer.svelte (69%)
 rename web/src/lib/components/{asset-viewer-page => asset-viewer}/detail-panel.svelte (100%)
 rename web/src/lib/components/{asset-viewer-page => asset-viewer}/download-panel.svelte (100%)
 rename web/src/lib/components/{asset-viewer-page => asset-viewer}/intersection-observer.svelte (100%)
 rename web/src/lib/components/{asset-viewer-page => asset-viewer}/photo-viewer.svelte (100%)
 rename web/src/lib/components/{asset-viewer-page => asset-viewer}/video-viewer.svelte (100%)
 rename web/src/routes/albums/{[albumId].svelte => [albumId]/index.svelte} (95%)
 create mode 100644 web/src/routes/albums/[albumId]/photos/[assetId].svelte

diff --git a/server/apps/immich/src/api-v1/album/album-repository.ts b/server/apps/immich/src/api-v1/album/album-repository.ts
index 6336a2574b..4c48907264 100644
--- a/server/apps/immich/src/api-v1/album/album-repository.ts
+++ b/server/apps/immich/src/api-v1/album/album-repository.ts
@@ -164,6 +164,7 @@ export class AlbumRepository implements IAlbumRepository {
       .leftJoinAndSelect('sharedUser.userInfo', 'userInfo')
       .leftJoinAndSelect('album.assets', 'assets')
       .leftJoinAndSelect('assets.assetInfo', 'assetInfo')
+      .leftJoinAndSelect('assetInfo.exifInfo', 'exifInfo')
       .orderBy('"assetInfo"."createdAt"::timestamptz', 'ASC')
       .getOne();
 
diff --git a/web/src/lib/components/album-page/album-viewer.svelte b/web/src/lib/components/album-page/album-viewer.svelte
index 908f9cb0c6..c78dd6490b 100644
--- a/web/src/lib/components/album-page/album-viewer.svelte
+++ b/web/src/lib/components/album-page/album-viewer.svelte
@@ -1,15 +1,21 @@
 <script lang="ts">
 	import { afterNavigate } from '$app/navigation';
-
-	import { AlbumResponseDto, ThumbnailFormat } from '@api';
+	import { page } from '$app/stores';
+	import { AlbumResponseDto, AssetResponseDto, ThumbnailFormat } from '@api';
 	import { createEventDispatcher, onMount } from 'svelte';
 	import ArrowLeft from 'svelte-material-icons/ArrowLeft.svelte';
 	import FileImagePlusOutline from 'svelte-material-icons/FileImagePlusOutline.svelte';
+	import AssetViewer from '../asset-viewer/asset-viewer.svelte';
 	import CircleAvatar from '../shared-components/circle-avatar.svelte';
 	import ImmichThumbnail from '../shared-components/immich-thumbnail.svelte';
 
 	const dispatch = createEventDispatcher();
 	export let album: AlbumResponseDto;
+
+	let isShowAssetViewer = false;
+	let selectedAsset: AssetResponseDto;
+	let currentViewAssetIndex = 0;
+
 	let viewWidth: number;
 	let thumbnailSize: number = 300;
 	let border = '';
@@ -52,6 +58,50 @@
 			}
 		};
 	});
+
+	const viewAsset = (event: CustomEvent) => {
+		const { assetId, deviceId }: { assetId: string; deviceId: string } = event.detail;
+
+		currentViewAssetIndex = album.assets.findIndex((a) => a.id == assetId);
+		selectedAsset = album.assets[currentViewAssetIndex];
+		isShowAssetViewer = true;
+		pushState(selectedAsset.id);
+	};
+
+	const navigateAssetForward = () => {
+		try {
+			if (currentViewAssetIndex < album.assets.length - 1) {
+				currentViewAssetIndex++;
+				selectedAsset = album.assets[currentViewAssetIndex];
+				pushState(selectedAsset.id);
+			}
+		} catch (e) {
+			console.error(e);
+		}
+	};
+
+	const navigateAssetBackward = () => {
+		try {
+			if (currentViewAssetIndex > 0) {
+				currentViewAssetIndex--;
+				selectedAsset = album.assets[currentViewAssetIndex];
+				pushState(selectedAsset.id);
+			}
+		} catch (e) {
+			console.error(e);
+		}
+	};
+
+	const pushState = (assetId: string) => {
+		// add a URL to the browser's history
+		// changes the current URL in the address bar but doesn't perform any SvelteKit navigation
+		history.pushState(null, '', `${$page.url.pathname}/photos/${assetId}`);
+	};
+
+	const closeViewer = () => {
+		isShowAssetViewer = false;
+		history.pushState(null, '', `${$page.url.pathname}`);
+	};
 </script>
 
 <section class="w-screen h-screen bg-immich-bg">
@@ -97,11 +147,26 @@
 		<div class="flex flex-wrap gap-1 w-full" bind:clientWidth={viewWidth}>
 			{#each album.assets as asset}
 				{#if album.assets.length < 7}
-					<ImmichThumbnail {asset} {thumbnailSize} format={ThumbnailFormat.Jpeg} />
+					<ImmichThumbnail
+						{asset}
+						{thumbnailSize}
+						format={ThumbnailFormat.Jpeg}
+						on:viewAsset={viewAsset}
+					/>
 				{:else}
-					<ImmichThumbnail {asset} {thumbnailSize} />
+					<ImmichThumbnail {asset} {thumbnailSize} on:viewAsset={viewAsset} />
 				{/if}
 			{/each}
 		</div>
 	</section>
 </section>
+
+<!-- Overlay Asset Viewer -->
+{#if isShowAssetViewer}
+	<AssetViewer
+		asset={selectedAsset}
+		on:navigate-backward={navigateAssetBackward}
+		on:navigate-forward={navigateAssetForward}
+		on:close={closeViewer}
+	/>
+{/if}
diff --git a/web/src/lib/components/asset-viewer-page/asser-viewer-nav-bar.svelte b/web/src/lib/components/asset-viewer/asser-viewer-nav-bar.svelte
similarity index 100%
rename from web/src/lib/components/asset-viewer-page/asser-viewer-nav-bar.svelte
rename to web/src/lib/components/asset-viewer/asser-viewer-nav-bar.svelte
diff --git a/web/src/lib/components/asset-viewer-page/asset-viewer.svelte b/web/src/lib/components/asset-viewer/asset-viewer.svelte
similarity index 69%
rename from web/src/lib/components/asset-viewer-page/asset-viewer.svelte
rename to web/src/lib/components/asset-viewer/asset-viewer.svelte
index 1539b86751..e3cdbd3a7b 100644
--- a/web/src/lib/components/asset-viewer-page/asset-viewer.svelte
+++ b/web/src/lib/components/asset-viewer/asset-viewer.svelte
@@ -2,7 +2,6 @@
 	import { createEventDispatcher, onDestroy, onMount } from 'svelte';
 	import { fly } from 'svelte/transition';
 	import AsserViewerNavBar from './asser-viewer-nav-bar.svelte';
-	import { flattenAssetGroupByDate } from '$lib/stores/assets';
 	import ChevronRight from 'svelte-material-icons/ChevronRight.svelte';
 	import ChevronLeft from 'svelte-material-icons/ChevronLeft.svelte';
 	import PhotoViewer from './photo-viewer.svelte';
@@ -14,31 +13,16 @@
 
 	const dispatch = createEventDispatcher();
 
-	export let selectedAsset: AssetResponseDto;
-
-	export let selectedIndex: number;
-
-	let viewDeviceId: string;
-	let viewAssetId: string;
+	export let asset: AssetResponseDto;
 
 	let halfLeftHover = false;
 	let halfRightHover = false;
 	let isShowDetail = false;
 
 	onMount(() => {
-		viewAssetId = selectedAsset.id;
-		viewDeviceId = selectedAsset.deviceId;
-		pushState(viewAssetId);
-
 		document.addEventListener('keydown', (keyInfo) => handleKeyboardPress(keyInfo.key));
 	});
 
-	onDestroy(() => {
-		document.removeEventListener('keydown', (b) => {
-			console.log('destroyed', b);
-		});
-	});
-
 	const handleKeyboardPress = (key: string) => {
 		switch (key) {
 			case 'Escape':
@@ -57,38 +41,17 @@
 	};
 
 	const closeViewer = () => {
-		history.pushState(null, '', `/photos`);
 		dispatch('close');
 	};
 
 	const navigateAssetForward = (e?: Event) => {
 		e?.stopPropagation();
-
-		const nextAsset = $flattenAssetGroupByDate[selectedIndex + 1];
-		viewDeviceId = nextAsset.deviceId;
-		viewAssetId = nextAsset.id;
-
-		selectedIndex = selectedIndex + 1;
-		selectedAsset = $flattenAssetGroupByDate[selectedIndex];
-		pushState(viewAssetId);
+		dispatch('navigate-forward');
 	};
 
 	const navigateAssetBackward = (e?: Event) => {
 		e?.stopPropagation();
-
-		const lastAsset = $flattenAssetGroupByDate[selectedIndex - 1];
-		viewDeviceId = lastAsset.deviceId;
-		viewAssetId = lastAsset.id;
-
-		selectedIndex = selectedIndex - 1;
-		selectedAsset = $flattenAssetGroupByDate[selectedIndex];
-		pushState(viewAssetId);
-	};
-
-	const pushState = (assetId: string) => {
-		// add a URL to the browser's history
-		// changes the current URL in the address bar but doesn't perform any SvelteKit navigation
-		history.pushState(null, '', `/photos/${assetId}`);
+		dispatch('navigate-backward');
 	};
 
 	const showDetailInfoHandler = () => {
@@ -98,19 +61,20 @@
 	const downloadFile = async () => {
 		if ($session.user) {
 			try {
-				const imageName = selectedAsset.exifInfo?.imageName ? selectedAsset.exifInfo?.imageName : selectedAsset.id;
-				const imageExtension = selectedAsset.originalPath.split('.')[1];
+				const imageName = asset.exifInfo?.imageName ? asset.exifInfo?.imageName : asset.id;
+				const imageExtension = asset.originalPath.split('.')[1];
 				const imageFileName = imageName + '.' + imageExtension;
 
 				// If assets is already download -> return;
 				if ($downloadAssets[imageFileName]) {
 					return;
 				}
+
 				$downloadAssets[imageFileName] = 0;
 
 				const { data, status } = await api.assetApi.downloadFile(
-					selectedAsset.deviceAssetId,
-					selectedAsset.deviceId,
+					asset.deviceAssetId,
+					asset.deviceId,
 					false,
 					false,
 					{
@@ -123,8 +87,8 @@
 
 								$downloadAssets[imageFileName] = percentCompleted;
 							}
-						},
-					},
+						}
+					}
 				);
 
 				if (!(data instanceof Blob)) {
@@ -162,12 +126,16 @@
 	class="absolute h-screen w-screen top-0 overflow-y-hidden bg-black z-[999] grid grid-rows-[64px_1fr] grid-cols-4  "
 >
 	<div class="col-start-1 col-span-4 row-start-1 row-span-1 z-[1000] transition-transform">
-		<AsserViewerNavBar on:goBack={closeViewer} on:showDetail={showDetailInfoHandler} on:download={downloadFile} />
+		<AsserViewerNavBar
+			on:goBack={closeViewer}
+			on:showDetail={showDetailInfoHandler}
+			on:download={downloadFile}
+		/>
 	</div>
 
 	<div
 		class={`row-start-2 row-span-end col-start-1 col-span-2 flex place-items-center hover:cursor-pointer w-3/4 ${
-			selectedAsset.type == 'VIDEO' ? '' : 'z-[999]'
+			asset.type == AssetTypeEnum.Video ? '' : 'z-[999]'
 		}`}
 		on:mouseenter={() => {
 			halfLeftHover = true;
@@ -188,20 +156,18 @@
 	</div>
 
 	<div class="row-start-1 row-span-full col-start-1 col-span-4">
-		{#key selectedIndex}
-			{#if viewAssetId && viewDeviceId}
-				{#if selectedAsset.type == AssetTypeEnum.Image}
-					<PhotoViewer assetId={viewAssetId} deviceId={viewDeviceId} on:close={closeViewer} />
-				{:else}
-					<VideoViewer assetId={viewAssetId} on:close={closeViewer} />
-				{/if}
+		{#key asset.id}
+			{#if asset.type == AssetTypeEnum.Image}
+				<PhotoViewer assetId={asset.id} deviceId={asset.deviceId} on:close={closeViewer} />
+			{:else}
+				<VideoViewer assetId={asset.id} on:close={closeViewer} />
 			{/if}
 		{/key}
 	</div>
 
 	<div
 		class={`row-start-2 row-span-full col-start-3 col-span-2 flex justify-end place-items-center hover:cursor-pointer w-3/4 justify-self-end ${
-			selectedAsset.type == 'VIDEO' ? '' : 'z-[500]'
+			asset.type == AssetTypeEnum.Video ? '' : 'z-[500]'
 		}`}
 		on:click={navigateAssetForward}
 		on:mouseenter={() => {
@@ -228,7 +194,7 @@
 			class="bg-immich-bg w-[360px] row-span-full transition-all "
 			translate="yes"
 		>
-			<DetailPanel asset={selectedAsset} on:close={() => (isShowDetail = false)} />
+			<DetailPanel {asset} on:close={() => (isShowDetail = false)} />
 		</div>
 	{/if}
 </section>
diff --git a/web/src/lib/components/asset-viewer-page/detail-panel.svelte b/web/src/lib/components/asset-viewer/detail-panel.svelte
similarity index 100%
rename from web/src/lib/components/asset-viewer-page/detail-panel.svelte
rename to web/src/lib/components/asset-viewer/detail-panel.svelte
diff --git a/web/src/lib/components/asset-viewer-page/download-panel.svelte b/web/src/lib/components/asset-viewer/download-panel.svelte
similarity index 100%
rename from web/src/lib/components/asset-viewer-page/download-panel.svelte
rename to web/src/lib/components/asset-viewer/download-panel.svelte
diff --git a/web/src/lib/components/asset-viewer-page/intersection-observer.svelte b/web/src/lib/components/asset-viewer/intersection-observer.svelte
similarity index 100%
rename from web/src/lib/components/asset-viewer-page/intersection-observer.svelte
rename to web/src/lib/components/asset-viewer/intersection-observer.svelte
diff --git a/web/src/lib/components/asset-viewer-page/photo-viewer.svelte b/web/src/lib/components/asset-viewer/photo-viewer.svelte
similarity index 100%
rename from web/src/lib/components/asset-viewer-page/photo-viewer.svelte
rename to web/src/lib/components/asset-viewer/photo-viewer.svelte
diff --git a/web/src/lib/components/asset-viewer-page/video-viewer.svelte b/web/src/lib/components/asset-viewer/video-viewer.svelte
similarity index 100%
rename from web/src/lib/components/asset-viewer-page/video-viewer.svelte
rename to web/src/lib/components/asset-viewer/video-viewer.svelte
diff --git a/web/src/lib/components/shared-components/immich-thumbnail.svelte b/web/src/lib/components/shared-components/immich-thumbnail.svelte
index 0ed4d964ef..9f13c3da70 100644
--- a/web/src/lib/components/shared-components/immich-thumbnail.svelte
+++ b/web/src/lib/components/shared-components/immich-thumbnail.svelte
@@ -2,7 +2,7 @@
 	import { session } from '$app/stores';
 	import { createEventDispatcher, onDestroy } from 'svelte';
 	import { fade, fly } from 'svelte/transition';
-	import IntersectionObserver from '$lib/components/asset-viewer-page/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';
 	import PauseCircleOutline from 'svelte-material-icons/PauseCircleOutline.svelte';
diff --git a/web/src/lib/components/sharing-page/shared-album-list-tile.svelte b/web/src/lib/components/sharing-page/shared-album-list-tile.svelte
index ae00957ed4..ab7d3a87aa 100644
--- a/web/src/lib/components/sharing-page/shared-album-list-tile.svelte
+++ b/web/src/lib/components/sharing-page/shared-album-list-tile.svelte
@@ -53,7 +53,7 @@
 			{#if user.email == albumOwner.email}
 				<p class="text-xs text-gray-600">Owned</p>
 			{:else}
-				<p class="text-xs text-gray-600">Shared by {albumOwner.email}</p>
+				<p class="text-xs text-gray-600">Shared by {albumOwner.firstName} {albumOwner.lastName}</p>
 			{/if}
 		{/await}
 	</div>
diff --git a/web/src/routes/__layout.svelte b/web/src/routes/__layout.svelte
index 44435f8bd1..52b94e1bd3 100644
--- a/web/src/routes/__layout.svelte
+++ b/web/src/routes/__layout.svelte
@@ -18,7 +18,7 @@
 
 	import { blur, fade, slide } from 'svelte/transition';
 
-	import DownloadPanel from '$lib/components/asset-viewer-page/download-panel.svelte';
+	import DownloadPanel from '$lib/components/asset-viewer/download-panel.svelte';
 	import AnnouncementBox from '$lib/components/shared-components/announcement-box.svelte';
 	import UploadPanel from '$lib/components/shared-components/upload-panel.svelte';
 	import { onMount } from 'svelte';
diff --git a/web/src/routes/albums/[albumId].svelte b/web/src/routes/albums/[albumId]/index.svelte
similarity index 95%
rename from web/src/routes/albums/[albumId].svelte
rename to web/src/routes/albums/[albumId]/index.svelte
index 42e1ded410..2ac136ae17 100644
--- a/web/src/routes/albums/[albumId].svelte
+++ b/web/src/routes/albums/[albumId]/index.svelte
@@ -35,8 +35,6 @@
 </script>
 
 <script lang="ts">
-	import { goto } from '$app/navigation';
-
 	import AlbumViewer from '$lib/components/album-page/album-viewer.svelte';
 
 	export let album: AlbumResponseDto;
diff --git a/web/src/routes/albums/[albumId]/photos/[assetId].svelte b/web/src/routes/albums/[albumId]/photos/[assetId].svelte
new file mode 100644
index 0000000000..2edd20a56b
--- /dev/null
+++ b/web/src/routes/albums/[albumId]/photos/[assetId].svelte
@@ -0,0 +1,27 @@
+<script context="module" lang="ts">
+	export const prerender = false;
+
+	import type { Load } from '@sveltejs/kit';
+
+	export const load: Load = async ({ session, params }) => {
+		if (!session.user) {
+			return {
+				status: 302,
+				redirect: '/auth/login'
+			};
+		}
+		const albumId = params['albumId'];
+
+		if (albumId) {
+			return {
+				status: 302,
+				redirect: `/albums/${albumId}`
+			};
+		} else {
+			return {
+				status: 302,
+				redirect: `/photos`
+			};
+		}
+	};
+</script>
diff --git a/web/src/routes/photos/index.svelte b/web/src/routes/photos/index.svelte
index e6d0100928..4ca3797823 100644
--- a/web/src/routes/photos/index.svelte
+++ b/web/src/routes/photos/index.svelte
@@ -33,7 +33,7 @@
 	import { assetsGroupByDate, flattenAssetGroupByDate } from '$lib/stores/assets';
 	import ImmichThumbnail from '$lib/components/shared-components/immich-thumbnail.svelte';
 	import moment from 'moment';
-	import AssetViewer from '$lib/components/asset-viewer-page/asset-viewer.svelte';
+	import AssetViewer from '$lib/components/asset-viewer/asset-viewer.svelte';
 	import { fileUploader } from '$lib/utils/file-uploader';
 	import { AssetResponseDto } from '@api';
 	import SideBar from '$lib/components/shared-components/side-bar/side-bar.svelte';
@@ -42,13 +42,14 @@
 
 	let selectedGroupThumbnail: number | null;
 	let isMouseOverGroup: boolean;
+
 	$: if (isMouseOverGroup == false) {
 		selectedGroupThumbnail = null;
 	}
 
-	let isShowAsset = false;
+	let isShowAssetViewer = false;
 	let currentViewAssetIndex = 0;
-	let currentSelectedAsset: AssetResponseDto;
+	let selectedAsset: AssetResponseDto;
 
 	const thumbnailMouseEventHandler = (event: CustomEvent) => {
 		const { selectedGroupIndex }: { selectedGroupIndex: number } = event.detail;
@@ -60,8 +61,9 @@
 		const { assetId, deviceId }: { assetId: string; deviceId: string } = event.detail;
 
 		currentViewAssetIndex = $flattenAssetGroupByDate.findIndex((a) => a.id == assetId);
-		currentSelectedAsset = $flattenAssetGroupByDate[currentViewAssetIndex];
-		isShowAsset = true;
+		selectedAsset = $flattenAssetGroupByDate[currentViewAssetIndex];
+		isShowAssetViewer = true;
+		pushState(selectedAsset.id);
 	};
 
 	const uploadClickedHandler = async () => {
@@ -91,6 +93,41 @@
 			}
 		}
 	};
+
+	const navigateAssetForward = () => {
+		try {
+			if (currentViewAssetIndex < $flattenAssetGroupByDate.length - 1) {
+				currentViewAssetIndex++;
+				selectedAsset = $flattenAssetGroupByDate[currentViewAssetIndex];
+				pushState(selectedAsset.id);
+			}
+		} catch (e) {
+			console.log('Error navigating asset forward', e);
+		}
+	};
+
+	const navigateAssetBackward = () => {
+		try {
+			if (currentViewAssetIndex > 0) {
+				currentViewAssetIndex--;
+				selectedAsset = $flattenAssetGroupByDate[currentViewAssetIndex];
+				pushState(selectedAsset.id);
+			}
+		} catch (e) {
+			console.log('Error navigating asset backward', e);
+		}
+	};
+
+	const pushState = (assetId: string) => {
+		// add a URL to the browser's history
+		// changes the current URL in the address bar but doesn't perform any SvelteKit navigation
+		history.pushState(null, '', `/photos/${assetId}`);
+	};
+
+	const closeViewer = () => {
+		isShowAssetViewer = false;
+		history.pushState(null, '', `/photos`);
+	};
 </script>
 
 <svelte:head>
@@ -149,10 +186,11 @@
 </section>
 
 <!-- Overlay Asset Viewer -->
-{#if isShowAsset}
+{#if isShowAssetViewer}
 	<AssetViewer
-		selectedAsset={currentSelectedAsset}
-		selectedIndex={currentViewAssetIndex}
-		on:close={() => (isShowAsset = false)}
+		asset={selectedAsset}
+		on:navigate-backward={navigateAssetBackward}
+		on:navigate-forward={navigateAssetForward}
+		on:close={closeViewer}
 	/>
 {/if}