2022-06-03 11:04:30 -05:00
< script lang = "ts" >
2022-11-04 10:32:09 -04:00
import { goto } from '$app/navigation';
2023-01-21 22:15:16 -06:00
import {
2023-01-27 09:32:26 -05:00
AlbumResponseDto,
2023-01-21 22:15:16 -06:00
api,
AssetResponseDto,
AssetTypeEnum,
SharedLinkResponseDto
} from '@api';
2023-01-27 09:32:26 -05:00
import { createEventDispatcher , onDestroy , onMount } from 'svelte';
import ChevronLeft from 'svelte-material-icons/ChevronLeft.svelte';
import ChevronRight from 'svelte-material-icons/ChevronRight.svelte';
2023-05-24 22:13:02 -04:00
import ImageBrokenVariant from 'svelte-material-icons/ImageBrokenVariant.svelte';
2023-01-27 09:32:26 -05:00
import { fly } from 'svelte/transition';
import AlbumSelectionModal from '../shared-components/album-selection-modal.svelte';
2022-08-26 10:36:41 -07:00
import {
notificationController,
NotificationType
} from '../shared-components/notification/notification';
2023-01-27 09:32:26 -05:00
import AssetViewerNavBar from './asset-viewer-nav-bar.svelte';
import DetailPanel from './detail-panel.svelte';
import PhotoViewer from './photo-viewer.svelte';
import VideoViewer from './video-viewer.svelte';
2022-12-22 13:29:51 -06:00
2022-10-30 17:08:22 +01:00
import { assetStore } from '$lib/stores/assets.store';
2023-06-29 04:14:16 +02:00
import { isShowDetail } from '$lib/stores/preferences.store';
2023-06-30 12:24:28 -04:00
import { addAssetsToAlbum , downloadFile } from '$lib/utils/asset-utils';
2023-02-22 18:53:08 +01:00
import { browser } from '$app/environment';
2022-08-24 22:18:28 -07:00
export let asset: AssetResponseDto;
2023-01-09 14:16:08 -06:00
export let publicSharedKey = '';
export let showNavigation = true;
2023-01-21 22:15:16 -06:00
export let sharedLink: SharedLinkResponseDto | undefined = undefined;
2023-01-09 14:16:08 -06:00
2022-08-24 22:18:28 -07:00
const dispatch = createEventDispatcher();
let halfLeftHover = false;
let halfRightHover = false;
2022-09-05 15:50:20 +02:00
let appearsInAlbums: AlbumResponseDto[] = [];
2022-11-04 10:32:09 -04:00
let isShowAlbumPicker = false;
let addToSharedAlbum = true;
2022-11-18 23:12:54 -06:00
let shouldPlayMotionPhoto = false;
2023-01-21 22:15:16 -06:00
let shouldShowDownloadButton = sharedLink ? sharedLink.allowDownload : true;
2023-02-17 17:41:52 +01:00
let canCopyImagesToClipboard: boolean;
2022-09-08 17:30:49 +02:00
const onKeyboardPress = (keyInfo: KeyboardEvent) => handleKeyboardPress(keyInfo.key);
2023-01-10 10:03:15 -06:00
onMount(async () => {
2022-09-08 17:30:49 +02:00
document.addEventListener('keydown', onKeyboardPress);
2023-01-10 10:03:15 -06:00
2023-01-22 04:26:58 +01:00
getAllAlbums();
2023-02-17 17:41:52 +01:00
// Import hack :( see https://github.com/vadimkorr/svelte-carousel/issues/27#issuecomment-851022295
// TODO: Move to regular import once the package correctly supports ESM.
const module = await import('copy-image-clipboard');
canCopyImagesToClipboard = module.canCopyImagesToClipboard();
2023-01-22 04:26:58 +01:00
});
onDestroy(() => {
2023-02-22 18:53:08 +01:00
if (browser) {
document.removeEventListener('keydown', onKeyboardPress);
}
2023-01-22 04:26:58 +01:00
});
$: asset.id & & getAllAlbums(); // Update the album information when the asset ID changes
const getAllAlbums = async () => {
2023-01-10 10:03:15 -06:00
try {
2023-05-28 04:52:22 +03:00
const { data } = await api.albumApi.getAllAlbums({ assetId : asset.id } );
2023-01-10 10:03:15 -06:00
appearsInAlbums = data;
} catch (e) {
console.error('Error getting album that asset belong to', e);
}
2023-01-21 22:53:45 -06:00
};
2022-08-24 22:18:28 -07:00
const handleKeyboardPress = (key: string) => {
switch (key) {
case 'Escape':
closeViewer();
return;
2022-10-30 17:08:22 +01:00
case 'Delete':
deleteAsset();
return;
2022-08-24 22:18:28 -07:00
case 'i':
2023-06-29 04:14:16 +02:00
$isShowDetail = !$isShowDetail;
2022-08-24 22:18:28 -07:00
return;
case 'ArrowLeft':
navigateAssetBackward();
return;
case 'ArrowRight':
navigateAssetForward();
return;
}
};
2023-05-17 13:07:17 -04:00
const handleCloseViewer = () => {
2023-06-29 04:14:16 +02:00
$isShowDetail = false;
2023-05-17 13:07:17 -04:00
closeViewer();
};
2022-08-24 22:18:28 -07:00
const closeViewer = () => {
dispatch('close');
};
const navigateAssetForward = (e?: Event) => {
e?.stopPropagation();
2022-09-04 08:34:39 -05:00
dispatch('navigate-next');
2022-08-24 22:18:28 -07:00
};
const navigateAssetBackward = (e?: Event) => {
e?.stopPropagation();
2022-09-04 08:34:39 -05:00
dispatch('navigate-previous');
2022-08-24 22:18:28 -07:00
};
const showDetailInfoHandler = () => {
2023-06-29 04:14:16 +02:00
$isShowDetail = !$isShowDetail;
2022-08-24 22:18:28 -07:00
};
2022-10-30 17:08:22 +01:00
const deleteAsset = async () => {
try {
if (
window.confirm(
`Caution! Are you sure you want to delete this asset? This step also deletes this asset in the album(s) to which it belongs. You can not undo this action!`
)
) {
const { data : deletedAssets } = await api.assetApi.deleteAsset({
2023-05-28 04:52:22 +03:00
deleteAssetDto: {
ids: [asset.id]
}
2022-10-30 17:08:22 +01:00
});
navigateAssetForward();
for (const asset of deletedAssets) {
if (asset.status == 'SUCCESS') {
assetStore.removeAsset(asset.id);
}
}
}
} catch (e) {
notificationController.show({
type: NotificationType.Error,
message: 'Error deleting this asset, check console for more details'
});
console.error('Error deleteSelectedAssetHandler', e);
}
};
2022-11-04 10:32:09 -04:00
2022-11-08 11:20:36 -05:00
const toggleFavorite = async () => {
2023-05-28 04:52:22 +03:00
const { data } = await api.assetApi.updateAsset({
2023-06-01 22:19:25 -04:00
id: asset.id,
2023-05-28 04:52:22 +03:00
updateAssetDto: {
isFavorite: !asset.isFavorite
}
2022-11-08 11:20:36 -05:00
});
asset.isFavorite = data.isFavorite;
2023-01-27 09:32:26 -05:00
assetStore.updateAsset(asset.id, data.isFavorite);
2022-11-08 11:20:36 -05:00
};
2022-11-04 10:32:09 -04:00
const openAlbumPicker = (shared: boolean) => {
isShowAlbumPicker = true;
addToSharedAlbum = shared;
};
2023-01-09 22:24:07 +02:00
const handleAddToNewAlbum = (event: CustomEvent) => {
2022-11-04 10:32:09 -04:00
isShowAlbumPicker = false;
2023-01-09 22:24:07 +02:00
const { albumName } : { albumName : string } = event.detail;
2023-05-28 04:52:22 +03:00
api.albumApi
.createAlbum({ createAlbumDto : { albumName , assetIds : [ asset . id ] } } )
.then((response) => {
const album = response.data;
goto('/albums/' + album.id);
});
2022-11-04 10:32:09 -04:00
};
const handleAddToAlbum = async (event: CustomEvent< { album : AlbumResponseDto } >) => {
isShowAlbumPicker = false;
const album = event.detail.album;
2022-12-30 04:07:18 +02:00
addAssetsToAlbum(album.id, [asset.id]).then((dto) => {
if (dto.successfullyAdded === 1 && dto.album) {
appearsInAlbums = [...appearsInAlbums, dto.album];
}
});
2022-11-04 10:32:09 -04:00
};
2023-04-13 10:22:06 -05:00
const disableKeyDownEvent = () => {
if (browser) {
document.removeEventListener('keydown', onKeyboardPress);
}
};
const enableKeyDownEvent = () => {
if (browser) {
document.addEventListener('keydown', onKeyboardPress);
}
};
2023-04-20 09:09:27 -05:00
const toggleArchive = async () => {
try {
2023-05-28 04:52:22 +03:00
const { data } = await api.assetApi.updateAsset({
2023-06-01 22:19:25 -04:00
id: asset.id,
2023-05-28 04:52:22 +03:00
updateAssetDto: {
isArchived: !asset.isArchived
}
2023-04-20 09:09:27 -05:00
});
asset.isArchived = data.isArchived;
if (data.isArchived) {
dispatch('archived', data);
} else {
dispatch('unarchived', data);
}
notificationController.show({
type: NotificationType.Info,
message: asset.isArchived ? `Added to archive` : `Removed from archive`
});
} catch (error) {
console.error(error);
notificationController.show({
type: NotificationType.Error,
message: `Error ${
asset.isArchived ? 'archiving' : 'unarchiving'
} asset, check console for more details`
});
}
};
2022-06-03 11:04:30 -05:00
< / script >
< section
2022-08-24 22:18:28 -07:00
id="immich-asset-viewer"
2023-05-06 03:33:30 +02:00
class="fixed h-screen w-screen left-0 top-0 overflow-y-hidden bg-black z-[1001] grid grid-rows-[64px_1fr] grid-cols-4"
2022-06-03 11:04:30 -05:00
>
2022-08-24 22:18:28 -07:00
< div class = "col-start-1 col-span-4 row-start-1 row-span-1 z-[1000] transition-transform" >
2022-11-16 22:04:37 +01:00
< AssetViewerNavBar
2022-11-08 11:20:36 -05:00
{ asset }
2022-11-18 23:12:54 -06:00
isMotionPhotoPlaying={ shouldPlayMotionPhoto }
2023-02-17 17:41:52 +01:00
showCopyButton={ canCopyImagesToClipboard && asset . type === AssetTypeEnum . Image }
2023-06-20 16:36:38 +02:00
showZoomButton={ asset . type === AssetTypeEnum . Image }
2022-11-18 23:12:54 -06:00
showMotionPlayButton={ !! asset . livePhotoVideoId }
2023-01-21 22:15:16 -06:00
showDownloadButton={ shouldShowDownloadButton }
2022-08-24 22:18:28 -07:00
on:goBack={ closeViewer }
on:showDetail={ showDetailInfoHandler }
2023-06-30 12:24:28 -04:00
on:download={() => downloadFile ( asset , publicSharedKey )}
2022-10-30 17:08:22 +01:00
on:delete={ deleteAsset }
2022-11-08 11:20:36 -05:00
on:favorite={ toggleFavorite }
2022-11-04 10:32:09 -04:00
on:addToAlbum={() => openAlbumPicker ( false )}
on:addToSharedAlbum={() => openAlbumPicker ( true )}
2022-11-18 23:12:54 -06:00
on:playMotionPhoto={() => ( shouldPlayMotionPhoto = true )}
on:stopMotionPhoto={() => ( shouldPlayMotionPhoto = false )}
2023-04-20 09:09:27 -05:00
on:toggleArchive={ toggleArchive }
2022-08-24 22:18:28 -07:00
/>
< / div >
2023-01-09 14:16:08 -06:00
{ #if showNavigation }
< div
2023-05-29 16:12:58 +02:00
class={ `row-start-2 row-span-end col-start-1 flex place-items-center hover:cursor-pointer w-1/4 mb-[60px] $ {
2023-01-09 14:16:08 -06:00
asset.type === AssetTypeEnum.Video ? '' : 'z-[999]'
}`}
on:mouseenter={() => {
halfLeftHover = true;
halfRightHover = false;
}}
on:mouseleave={() => {
halfLeftHover = false;
}}
2022-08-24 22:18:28 -07:00
on:click={ navigateAssetBackward }
2023-01-09 14:16:08 -06:00
on:keydown={ navigateAssetBackward }
2022-08-24 22:18:28 -07:00
>
2023-01-09 14:16:08 -06:00
< button
2023-05-15 19:58:35 +02:00
class="rounded-full p-3 hover:bg-gray-500 hover:text-gray-700 z-[1000] text-gray-500 mx-4"
2023-01-09 14:16:08 -06:00
class:navigation-button-hover={ halfLeftHover }
on:click={ navigateAssetBackward }
>
< ChevronLeft size = "36" / >
< / button >
< / div >
{ /if }
2022-08-24 22:18:28 -07:00
< div class = "row-start-1 row-span-full col-start-1 col-span-4" >
{ #key asset . id }
2023-05-27 21:56:17 -04:00
{ #if ! asset . resized }
2023-05-24 22:13:02 -04:00
< div class = "h-full w-full flex justify-center" >
< div
class="h-full bg-gray-100 dark:bg-immich-dark-gray flex items-center justify-center aspect-square px-auto"
>
< ImageBrokenVariant size = "25%" / >
< / div >
< / div >
{ :else if asset . type === AssetTypeEnum . Image }
2022-11-18 23:12:54 -06:00
{ #if shouldPlayMotionPhoto && asset . livePhotoVideoId }
< VideoViewer
2023-01-09 14:16:08 -06:00
{ publicSharedKey }
2022-11-18 23:12:54 -06:00
assetId={ asset . livePhotoVideoId }
on:close={ closeViewer }
on:onVideoEnded={() => ( shouldPlayMotionPhoto = false )}
/>
{ : else }
2023-02-15 18:56:19 +01:00
< PhotoViewer { publicSharedKey } { asset } on:close = { closeViewer } / >
2022-11-18 23:12:54 -06:00
{ /if }
2022-08-24 22:18:28 -07:00
{ : else }
2023-01-09 14:16:08 -06:00
< VideoViewer { publicSharedKey } assetId = { asset . id } on:close= { closeViewer } />
2022-08-24 22:18:28 -07:00
{ /if }
{ /key }
< / div >
2023-01-09 14:16:08 -06:00
{ #if showNavigation }
< div
2023-05-29 16:12:58 +02:00
class={ `row-start-2 row-span-full col-start-4 flex justify-end place-items-center hover:cursor-pointer w-1/4 justify-self-end mb-[60px] $ {
2023-01-09 14:16:08 -06:00
asset.type === AssetTypeEnum.Video ? '' : 'z-[500]'
}`}
2022-08-24 22:18:28 -07:00
on:click={ navigateAssetForward }
2023-01-09 14:16:08 -06:00
on:keydown={ navigateAssetForward }
on:mouseenter={() => {
halfLeftHover = false;
halfRightHover = true;
}}
on:mouseleave={() => {
halfRightHover = false;
}}
2022-08-24 22:18:28 -07:00
>
2023-01-09 14:16:08 -06:00
< button
2023-05-09 17:10:13 +02:00
class="rounded-full p-3 hover:bg-gray-500 hover:text-white text-gray-500 mx-4"
2023-01-09 14:16:08 -06:00
class:navigation-button-hover={ halfRightHover }
on:click={ navigateAssetForward }
>
< ChevronRight size = "36" / >
< / button >
< / div >
{ /if }
2022-08-24 22:18:28 -07:00
2023-06-29 04:14:16 +02:00
{ #if $isShowDetail }
2022-08-24 22:18:28 -07:00
< div
transition:fly={{ duration : 150 }}
id="detail-panel"
2023-04-26 04:30:19 +02:00
class="bg-immich-bg w-[360px] z-[1002] row-span-full transition-all overflow-y-auto dark:bg-immich-dark-bg dark:border-l dark:border-l-immich-dark-gray"
2022-08-24 22:18:28 -07:00
translate="yes"
>
2023-04-13 10:22:06 -05:00
< DetailPanel
{ asset }
albums={ appearsInAlbums }
2023-06-29 04:14:16 +02:00
on:close={() => ( $isShowDetail = false )}
2023-05-17 13:07:17 -04:00
on:close-viewer={ handleCloseViewer }
2023-04-13 10:22:06 -05:00
on:description-focus-in={ disableKeyDownEvent }
on:description-focus-out={ enableKeyDownEvent }
/>
2022-08-24 22:18:28 -07:00
< / div >
{ /if }
2022-11-04 10:32:09 -04:00
{ #if isShowAlbumPicker }
< AlbumSelectionModal
shared={ addToSharedAlbum }
on:newAlbum={ handleAddToNewAlbum }
on:newSharedAlbum={ handleAddToNewAlbum }
on:album={ handleAddToAlbum }
on:close={() => ( isShowAlbumPicker = false )}
/>
{ /if }
2022-06-03 11:04:30 -05:00
< / section >
< style >
2022-09-04 08:34:39 -05:00
#immich-asset-viewer {
contain: layout;
}
2022-08-24 22:18:28 -07:00
.navigation-button-hover {
background-color: rgb(107 114 128 / var(--tw-bg-opacity));
2023-05-09 17:10:13 +02:00
color: rgb(255 255 255 / var(--tw-text-opacity));
2022-08-24 22:18:28 -07:00
transition: all 150ms;
}
2022-06-03 11:04:30 -05:00
< / style >