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}