2022-06-03 11:04:30 -05:00
< script lang = "ts" >
2022-08-24 22:18:28 -07:00
import { createEventDispatcher , onMount , onDestroy } from 'svelte' ;
import { fly } from 'svelte/transition' ;
2022-10-30 17:08:22 +01:00
import AsserViewerNavBar from './asset-viewer-nav-bar.svelte' ;
2022-08-24 22:18:28 -07:00
import ChevronRight from 'svelte-material-icons/ChevronRight.svelte' ;
import ChevronLeft from 'svelte-material-icons/ChevronLeft.svelte' ;
import PhotoViewer from './photo-viewer.svelte' ;
import DetailPanel from './detail-panel.svelte' ;
2022-11-04 10:32:09 -04:00
import { goto } from '$app/navigation' ;
2022-08-24 22:18:28 -07:00
import { downloadAssets } from '$lib/stores/download' ;
import VideoViewer from './video-viewer.svelte' ;
2022-11-04 23:29:48 -04:00
import AlbumSelectionModal from '../shared-components/album-selection-modal.svelte' ;
2022-11-04 10:32:09 -04:00
import {
api ,
AddAssetsResponseDto ,
AssetResponseDto ,
AssetTypeEnum ,
AlbumResponseDto
} from '@api' ;
2022-08-26 10:36:41 -07:00
import {
notificationController ,
NotificationType
} from '../shared-components/notification/notification' ;
2022-10-30 17:08:22 +01:00
import { assetStore } from '$lib/stores/assets.store' ;
2022-06-03 11:04:30 -05:00
2022-08-24 22:18:28 -07:00
export let asset : AssetResponseDto ;
2022-09-05 15:50:20 +02:00
$ : {
appearsInAlbums = [];
2022-09-08 12:53:09 +02:00
api . albumApi . getAllAlbums ( undefined , asset . id ). then (( result ) => {
2022-09-05 15:50:20 +02:00
appearsInAlbums = result . data ;
});
}
2022-06-03 11:04:30 -05:00
2022-08-24 22:18:28 -07:00
const dispatch = createEventDispatcher ();
let halfLeftHover = false ;
let halfRightHover = false ;
let isShowDetail = 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-06-03 11:04:30 -05:00
2022-09-08 17:30:49 +02:00
const onKeyboardPress = ( keyInfo : KeyboardEvent ) => handleKeyboardPress ( keyInfo . key );
2022-08-24 22:18:28 -07:00
onMount (() => {
2022-09-08 17:30:49 +02:00
document . addEventListener ( 'keydown' , onKeyboardPress );
2022-08-24 22:18:28 -07:00
});
2022-06-03 11:04:30 -05:00
2022-08-24 22:18:28 -07:00
onDestroy (() => {
2022-09-08 17:30:49 +02:00
document . removeEventListener ( 'keydown' , onKeyboardPress );
2022-08-24 22:18:28 -07:00
});
2022-06-03 11:04:30 -05: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' :
isShowDetail = ! isShowDetail ;
return ;
case 'ArrowLeft' :
navigateAssetBackward ();
return ;
case 'ArrowRight' :
navigateAssetForward ();
return ;
}
};
2022-06-03 11:04:30 -05:00
2022-08-24 22:18:28 -07:00
const closeViewer = () => {
dispatch ( 'close' );
};
2022-06-03 11:04:30 -05:00
2022-08-24 22:18:28 -07:00
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
};
2022-06-03 11:04:30 -05:00
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
};
2022-06-03 11:04:30 -05:00
2022-08-24 22:18:28 -07:00
const showDetailInfoHandler = () => {
isShowDetail = ! isShowDetail ;
};
2022-06-03 11:04:30 -05:00
2022-08-24 22:18:28 -07:00
const downloadFile = async () => {
try {
const imageName = asset . exifInfo ? . imageName ? asset.exifInfo?.imageName : asset.id ;
const imageExtension = asset . originalPath . split ( '.' )[ 1 ];
const imageFileName = imageName + '.' + imageExtension ;
2022-06-03 11:04:30 -05:00
2022-08-24 22:18:28 -07:00
// If assets is already download -> return;
if ( $downloadAssets [ imageFileName ]) {
return ;
}
2022-07-18 00:22:39 -05:00
2022-08-24 22:18:28 -07:00
$downloadAssets [ imageFileName ] = 0 ;
2022-06-03 11:04:30 -05:00
2022-08-24 22:18:28 -07:00
const { data , status } = await api . assetApi . downloadFile (
asset . deviceAssetId ,
asset . deviceId ,
false ,
false ,
{
responseType : 'blob' ,
onDownloadProgress : ( progressEvent ) => {
if ( progressEvent . lengthComputable ) {
const total = progressEvent . total ;
const current = progressEvent . loaded ;
$downloadAssets [ imageFileName ] = Math . floor (( current / total ) * 100 );
}
}
}
);
2022-06-03 11:04:30 -05:00
2022-08-24 22:18:28 -07:00
if ( ! ( data instanceof Blob )) {
return ;
}
2022-07-10 21:41:45 -05:00
2022-08-24 22:18:28 -07:00
if ( status === 200 ) {
const fileUrl = URL . createObjectURL ( data );
const anchor = document . createElement ( 'a' );
anchor . href = fileUrl ;
anchor . download = imageFileName ;
2022-07-26 12:28:07 -05:00
2022-08-24 22:18:28 -07:00
document . body . appendChild ( anchor );
anchor . click ();
document . body . removeChild ( anchor );
2022-07-26 12:28:07 -05:00
2022-08-24 22:18:28 -07:00
URL . revokeObjectURL ( fileUrl );
2022-07-26 12:28:07 -05:00
2022-08-24 22:18:28 -07:00
// Remove item from download list
setTimeout (() => {
const copy = $downloadAssets ;
delete copy [ imageFileName ];
$downloadAssets = copy ;
}, 2000 );
}
} catch ( e ) {
2022-08-26 10:36:41 -07:00
console . error ( 'Error downloading file ' , e );
notificationController . show ({
type : NotificationType . Error ,
message : 'Error downloading file, check console for more details.'
});
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 ({
ids : [ asset . id ]
});
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 () => {
const { data } = await api . assetApi . updateAssetById ( asset . id , {
isFavorite : ! asset . isFavorite
});
asset . isFavorite = data . isFavorite ;
};
2022-11-04 10:32:09 -04:00
const openAlbumPicker = ( shared : boolean ) => {
isShowAlbumPicker = true ;
addToSharedAlbum = shared ;
};
const showAddNotification = ( dto : AddAssetsResponseDto ) => {
notificationController . show ({
message : `Added ${ dto . successfullyAdded } to ${ dto . album ? . albumName } ` ,
type : NotificationType . Info
});
if ( dto . successfullyAdded === 1 && dto . album ) {
appearsInAlbums = [... appearsInAlbums , dto . album ];
}
};
const handleAddToNewAlbum = () => {
isShowAlbumPicker = false ;
api . albumApi . createAlbum ({ albumName : 'Untitled' , assetIds : [ asset . id ] }). then (( response ) => {
const album = response . data ;
goto ( '/albums/' + album . id );
});
};
const handleAddToAlbum = async ( event : CustomEvent < { album : AlbumResponseDto } > ) => {
isShowAlbumPicker = false ;
const album = event . detail . album ;
api . albumApi
. addAssetsToAlbum ( album . id , { assetIds : [ asset . id ] })
. then (( response ) => showAddNotification ( response . data ));
};
2022-06-03 11:04:30 -05:00
</ script >
< section
2022-08-24 22:18:28 -07:00
id = "immich-asset-viewer"
2022-09-04 08:34:39 -05:00
class = "fixed h-screen w-screen top-0 overflow-y-hidden bg-black z-[999] 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" >
< AsserViewerNavBar
2022-11-08 11:20:36 -05:00
{ asset }
2022-08-24 22:18:28 -07:00
on:goBack = { closeViewer }
on:showDetail= { showDetailInfoHandler }
on:download = { downloadFile }
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-08-24 22:18:28 -07:00
/ >
</ div >
2022-06-03 11:04:30 -05:00
2022-08-24 22:18:28 -07:00
< div
2022-09-23 18:22:06 -05:00
class = { `row-start-2 row-span-end col-start-1 col-span-2 flex place-items-center hover:cursor-pointer w-3/4 mb-[60px] ${
2022-08-12 21:19:54 -05:00
asset . type === AssetTypeEnum . Video ? '' : 'z-[999]'
2022-06-04 18:34:11 -05:00
} ` }
2022-08-24 22:18:28 -07:00
on:mouseenter= {() => {
2022-06-03 11:04:30 -05:00
halfLeftHover = true ;
halfRightHover = false ;
}}
2022-08-24 22:18:28 -07:00
on:mouseleave = {() => {
2022-06-03 11:04:30 -05:00
halfLeftHover = false ;
}}
2022-08-24 22:18:28 -07:00
on:click= { navigateAssetBackward }
>
< button
class = "rounded-full p-3 hover:bg-gray-500 hover:text-gray-700 z-[1000] text-gray-500 mx-4"
class:navigation-button-hover = { halfLeftHover }
on:click= { navigateAssetBackward }
>
< ChevronLeft size = "36" />
</ button >
</ div >
2022-06-03 11:04:30 -05:00
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 }
{ #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 >
2022-06-03 11:04:30 -05:00
2022-08-24 22:18:28 -07:00
< div
2022-09-23 18:22:06 -05:00
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 mb-[60px] ${
2022-08-12 21:19:54 -05:00
asset . type === AssetTypeEnum . Video ? '' : 'z-[500]'
2022-06-04 18:34:11 -05:00
} ` }
2022-08-24 22:18:28 -07:00
on:click= { navigateAssetForward }
on:mouseenter = {() => {
2022-06-03 11:04:30 -05:00
halfLeftHover = false ;
halfRightHover = true ;
}}
2022-08-24 22:18:28 -07:00
on:mouseleave= {() => {
2022-06-03 11:04:30 -05:00
halfRightHover = false ;
}}
2022-08-24 22:18:28 -07:00
>
< button
2022-09-23 18:22:06 -05:00
class = "rounded-full p-3 hover:bg-gray-500 hover:text-gray-700 text-gray-500 mx-4"
2022-08-24 22:18:28 -07:00
class:navigation-button-hover = { halfRightHover }
on:click= { navigateAssetForward }
>
< ChevronRight size = "36" />
</ button >
</ div >
2022-06-03 11:04:30 -05:00
2022-08-24 22:18:28 -07:00
{ #if isShowDetail }
< div
transition:fly = {{ duration : 150 }}
id="detail-panel"
2022-10-26 11:10:48 -05:00
class = "bg-immich-bg w-[360px] 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"
>
2022-09-05 15:50:20 +02:00
< DetailPanel { asset } albums = { appearsInAlbums } on:close= {() => ( isShowDetail = false )} />
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 ));
color : rgb ( 55 65 81 / var ( -- tw - text - opacity ));
transition : all 150 ms ;
}
2022-06-03 11:04:30 -05:00
</ style >