mirror of
https://github.com/immich-app/immich.git
synced 2024-11-16 02:18:50 -07:00
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
This commit is contained in:
parent
c129023821
commit
c028c7db4e
@ -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();
|
||||
|
||||
|
@ -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}
|
||||
|
@ -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} />
|
||||
{#key asset.id}
|
||||
{#if asset.type == AssetTypeEnum.Image}
|
||||
<PhotoViewer assetId={asset.id} deviceId={asset.deviceId} on:close={closeViewer} />
|
||||
{:else}
|
||||
<VideoViewer assetId={viewAssetId} on:close={closeViewer} />
|
||||
{/if}
|
||||
<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>
|
@ -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';
|
||||
|
@ -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>
|
||||
|
@ -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';
|
||||
|
@ -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;
|
27
web/src/routes/albums/[albumId]/photos/[assetId].svelte
Normal file
27
web/src/routes/albums/[albumId]/photos/[assetId].svelte
Normal file
@ -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>
|
@ -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}
|
||||
|
Loading…
Reference in New Issue
Block a user