+
(mouseOver = true)}
+ on:mouseleave={() => (mouseOver = false)}
+ on:click={() => dispatch('viewAsset', { assetId: asset.id, deviceId: asset.deviceId })}
+ >
+ {#if mouseOver}
+
+
(mouseOverIcon = true)}
+ on:mouseleave={() => (mouseOverIcon = false)}
+ class="inline-block"
+ >
+
+
+
+ {/if}
+
+ {#if asset.type === AssetType.VIDEO}
+
+ {parseVideoDuration(asset.duration)}
+
+
+ {/if}
+
{#if intersecting}
{#await loadImageData()}
...
@@ -37,10 +110,18 @@
in:fade={{ duration: 200 }}
src={imageData}
alt={asset.id}
- class="object-cover h-[200px] w-[200px] transition-all duration-100"
+ class="object-cover h-[200px] w-[200px] transition-all duration-100 z-0"
loading="lazy"
/>
{/await}
{/if}
+
+
diff --git a/web/src/lib/components/photos/photo_viewer.svelte b/web/src/lib/components/photos/photo_viewer.svelte
new file mode 100644
index 0000000000..b4a7d03985
--- /dev/null
+++ b/web/src/lib/components/photos/photo_viewer.svelte
@@ -0,0 +1,62 @@
+
+
+
dispatch('close')} class="h-screen">
+ {#if assetInfo}
+ {#await loadAssetData()}
+
+
+
+ {:then assetData}
+
+
+
+ {/await}
+ {/if}
+
diff --git a/web/src/lib/components/shared/loading-spinner.svelte b/web/src/lib/components/shared/loading-spinner.svelte
new file mode 100644
index 0000000000..d5be9eb872
--- /dev/null
+++ b/web/src/lib/components/shared/loading-spinner.svelte
@@ -0,0 +1,18 @@
+
diff --git a/web/src/lib/stores/assets.ts b/web/src/lib/stores/assets.ts
index a9d922d8b9..f43295f60d 100644
--- a/web/src/lib/stores/assets.ts
+++ b/web/src/lib/stores/assets.ts
@@ -4,9 +4,9 @@ import type { ImmichAsset } from '$lib/models/immich-asset'
import lodash from 'lodash-es';
import moment from 'moment';
-const assets = writable
([]);
+export const assets = writable([]);
-const assetsGroupByDate = derived(assets, ($assets) => {
+export const assetsGroupByDate = derived(assets, ($assets) => {
try {
return lodash.chain($assets)
@@ -20,14 +20,14 @@ const assetsGroupByDate = derived(assets, ($assets) => {
})
-const getAssetsInfo = async (accessToken: string) => {
+export const flattenAssetGroupByDate = derived(assetsGroupByDate, ($assetsGroupByDate) => {
+ return $assetsGroupByDate.flat();
+})
+
+export const getAssetsInfo = async (accessToken: string) => {
const res = await getRequest('asset', accessToken);
assets.set(res);
+
}
-export default {
- assets,
- assetsGroupByDate,
- getAssetsInfo,
-}
\ No newline at end of file
diff --git a/web/src/routes/__layout.svelte b/web/src/routes/__layout.svelte
index c66405cb88..13c5700990 100644
--- a/web/src/routes/__layout.svelte
+++ b/web/src/routes/__layout.svelte
@@ -11,7 +11,7 @@
const response = await getRequest('server-info/ping', '');
if (response.res === 'pong') isServerOk = true;
- if (response.statusCode === 404) isServerOk = false;
+ else isServerOk = false;
}, 10000);
onDestroy(() => clearInterval(pingServerInterval));
diff --git a/web/src/routes/photos/index.svelte b/web/src/routes/photos/index.svelte
index 4cc585cb37..327a29c6c7 100644
--- a/web/src/routes/photos/index.svelte
+++ b/web/src/routes/photos/index.svelte
@@ -2,8 +2,9 @@
export const prerender = false;
import type { Load } from '@sveltejs/kit';
+ import { getAssetsInfo } from '$lib/stores/assets';
- export const load: Load = ({ session }) => {
+ export const load: Load = async ({ session }) => {
if (!session.user) {
return {
status: 302,
@@ -25,24 +26,36 @@
import NavigationBar from '../../lib/components/shared/navigation-bar.svelte';
import SideBarButton from '$lib/components/shared/side-bar-button.svelte';
- import Magnify from 'svelte-material-icons/Magnify.svelte';
+ import CheckCircle from 'svelte-material-icons/CheckCircle.svelte';
+ import ChevronRight from 'svelte-material-icons/ChevronRight.svelte';
+ import ChevronLeft from 'svelte-material-icons/ChevronLeft.svelte';
import ImageOutline from 'svelte-material-icons/ImageOutline.svelte';
import { AppSideBarSelection } from '$lib/models/admin-sidebar-selection';
- import { onDestroy, onMount } from 'svelte';
+ import { onMount } from 'svelte';
+ import { fade, fly } from 'svelte/transition';
import { session } from '$app/stores';
- import assetStore from '$lib/stores/assets';
- import type { ImmichAsset } from '../../lib/models/immich-asset';
+ import { assetsGroupByDate, flattenAssetGroupByDate } from '$lib/stores/assets';
import ImmichThumbnail from '../../lib/components/photos/immich-thumbnail.svelte';
import moment from 'moment';
+ import PhotoViewer from '../../lib/components/photos/photo_viewer.svelte';
+ import type { ImmichAsset } from '../../lib/models/immich-asset';
+ import { AssetType } from '../../lib/models/immich-asset';
+ import LoadingSpinner from '../../lib/components/shared/loading-spinner.svelte';
export let user: ImmichUser;
let selectedAction: AppSideBarSelection;
- let assets: ImmichAsset[] = [];
- let assetsGroupByDate: ImmichAsset[][];
- // Subscribe to store values
- const assetsSub = assetStore.assets.subscribe((newAssets) => (assets = newAssets));
- const assetsGroupByDateSub = assetStore.assetsGroupByDate.subscribe((value) => (assetsGroupByDate = value));
+ let selectedGroupThumbnail: number | null;
+ let isMouseOverGroup: boolean;
+ $: if (isMouseOverGroup == false) {
+ selectedGroupThumbnail = null;
+ }
+
+ let isShowAsset = false;
+ let viewDeviceId: string = '';
+ let viewAssetId: string = '';
+ let currentViewAssetIndex = 0;
+ let currentSelectedAsset: ImmichAsset;
const onButtonClicked = (buttonType: CustomEvent) => {
selectedAction = buttonType.detail['actionType'] as AppSideBarSelection;
@@ -50,15 +63,46 @@
onMount(async () => {
selectedAction = AppSideBarSelection.PHOTOS;
+
if ($session.user) {
- await assetStore.getAssetsInfo($session.user.accessToken);
+ await getAssetsInfo($session.user.accessToken);
}
});
- onDestroy(() => {
- assetsSub();
- assetsGroupByDateSub();
- });
+ const thumbnailMouseEventHandler = (event: CustomEvent) => {
+ const { selectedGroupIndex }: { selectedGroupIndex: number } = event.detail;
+
+ selectedGroupThumbnail = selectedGroupIndex;
+ };
+
+ const viewAssetHandler = (event: CustomEvent) => {
+ const { assetId, deviceId }: { assetId: string; deviceId: string } = event.detail;
+
+ viewDeviceId = deviceId;
+ viewAssetId = assetId;
+
+ currentViewAssetIndex = $flattenAssetGroupByDate.findIndex((a) => a.id == assetId);
+ currentSelectedAsset = $flattenAssetGroupByDate[currentViewAssetIndex];
+ isShowAsset = true;
+ };
+
+ const navigateAssetForward = () => {
+ const nextAsset = $flattenAssetGroupByDate[currentViewAssetIndex + 1];
+ viewDeviceId = nextAsset.deviceId;
+ viewAssetId = nextAsset.id;
+
+ currentViewAssetIndex = currentViewAssetIndex + 1;
+ currentSelectedAsset = $flattenAssetGroupByDate[currentViewAssetIndex];
+ };
+
+ const navigateAssetBackward = () => {
+ const lastAsset = $flattenAssetGroupByDate[currentViewAssetIndex - 1];
+ viewDeviceId = lastAsset.deviceId;
+ viewAssetId = lastAsset.id;
+
+ currentViewAssetIndex = currentViewAssetIndex - 1;
+ currentSelectedAsset = $flattenAssetGroupByDate[currentViewAssetIndex];
+ };
@@ -70,6 +114,7 @@
+
+
-
-
- {#each assetsGroupByDate as assetsInDateGroup}
-
-
+
+
+ {#each $assetsGroupByDate as assetsInDateGroup, groupIndex}
+
+ (isMouseOverGroup = true)}
+ on:mouseleave={() => (isMouseOverGroup = false)}
+ >
+
+
+ {#if selectedGroupThumbnail === groupIndex && isMouseOverGroup}
+
+
+
+ {/if}
+
{moment(assetsInDateGroup[0].createdAt).format('ddd, MMM DD YYYY')}
-
+
+
+
{#each assetsInDateGroup as asset}
-
+
{/each}
@@ -107,3 +168,37 @@
+
+
+{#if isShowAsset}
+
+
+
+ {#key currentViewAssetIndex}
+ {#if currentSelectedAsset.type == AssetType.IMAGE}
+ (isShowAsset = false)} />
+ {:else}
+ (isShowAsset = false)}
+ >
+
Video viewer is under construction
+
+ {/if}
+ {/key}
+
+
+
+{/if}