2022-09-04 08:34:39 -05:00
|
|
|
<script lang="ts">
|
2023-08-11 12:00:51 -04:00
|
|
|
import { browser } from '$app/environment';
|
|
|
|
|
import { goto } from '$app/navigation';
|
|
|
|
|
import { AppRoute, AssetAction } from '$lib/constants';
|
|
|
|
|
import type { AssetInteractionStore } from '$lib/stores/asset-interaction.store';
|
2023-08-01 04:27:56 +03:00
|
|
|
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
|
2023-08-11 12:00:51 -04:00
|
|
|
import { BucketPosition, type AssetStore, type Viewport } from '$lib/stores/assets.store';
|
2023-07-12 05:12:19 +03:00
|
|
|
import { locale } from '$lib/stores/preferences.store';
|
2023-08-11 12:00:51 -04:00
|
|
|
import { isSearchEnabled } from '$lib/stores/search.store';
|
2023-07-12 05:12:19 +03:00
|
|
|
import { formatGroupTitle, splitBucketIntoDateGroups } from '$lib/utils/timeline-util';
|
2023-08-03 11:44:12 -04:00
|
|
|
import type { AssetResponseDto } from '@api';
|
2023-07-12 05:12:19 +03:00
|
|
|
import { DateTime } from 'luxon';
|
2023-08-05 09:58:52 -04:00
|
|
|
import { createEventDispatcher, onDestroy, onMount } from 'svelte';
|
2023-07-01 00:50:47 -04:00
|
|
|
import AssetViewer from '../asset-viewer/asset-viewer.svelte';
|
|
|
|
|
import IntersectionObserver from '../asset-viewer/intersection-observer.svelte';
|
|
|
|
|
import Portal from '../shared-components/portal/portal.svelte';
|
2023-08-03 14:20:41 -04:00
|
|
|
import Scrollbar from '../shared-components/scrollbar/scrollbar.svelte';
|
2023-07-24 04:09:06 +02:00
|
|
|
import ShowShortcuts from '../shared-components/show-shortcuts.svelte';
|
2023-08-11 12:00:51 -04:00
|
|
|
import AssetDateGroup from './asset-date-group.svelte';
|
2023-07-17 05:16:14 +02:00
|
|
|
|
2023-08-05 09:58:52 -04:00
|
|
|
export let isSelectionMode = false;
|
|
|
|
|
export let singleSelect = false;
|
2023-08-01 04:27:56 +03:00
|
|
|
export let assetStore: AssetStore;
|
|
|
|
|
export let assetInteractionStore: AssetInteractionStore;
|
2023-08-04 23:26:28 -04:00
|
|
|
export let removeAction: AssetAction | null = null;
|
2023-08-25 00:03:28 -04:00
|
|
|
|
2023-08-11 12:00:51 -04:00
|
|
|
const { assetSelectionCandidates, assetSelectionStart, selectedGroup, selectedAssets, isMultiSelectState } =
|
|
|
|
|
assetInteractionStore;
|
2023-08-03 11:44:12 -04:00
|
|
|
const viewport: Viewport = { width: 0, height: 0 };
|
2023-08-03 14:20:41 -04:00
|
|
|
let { isViewing: showAssetViewer, asset: viewingAsset } = assetViewingStore;
|
|
|
|
|
let element: HTMLElement;
|
2023-07-24 04:09:06 +02:00
|
|
|
let showShortcuts = false;
|
2023-07-01 00:50:47 -04:00
|
|
|
|
2023-08-03 14:20:41 -04:00
|
|
|
$: timelineY = element?.scrollTop || 0;
|
|
|
|
|
|
2023-07-17 05:16:14 +02:00
|
|
|
const onKeyboardPress = (event: KeyboardEvent) => handleKeyboardPress(event);
|
2023-08-05 09:58:52 -04:00
|
|
|
const dispatch = createEventDispatcher<{ select: AssetResponseDto }>();
|
2023-07-17 05:16:14 +02:00
|
|
|
|
2023-07-01 00:50:47 -04:00
|
|
|
onMount(async () => {
|
2023-07-17 05:16:14 +02:00
|
|
|
document.addEventListener('keydown', onKeyboardPress);
|
2023-08-03 11:44:12 -04:00
|
|
|
await assetStore.init(viewport);
|
2023-07-01 00:50:47 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
onDestroy(() => {
|
2023-07-19 11:03:23 -05:00
|
|
|
if (browser) {
|
|
|
|
|
document.removeEventListener('keydown', onKeyboardPress);
|
|
|
|
|
}
|
2023-08-11 12:00:51 -04:00
|
|
|
|
|
|
|
|
if ($showAssetViewer) {
|
|
|
|
|
$showAssetViewer = false;
|
|
|
|
|
}
|
2023-07-01 00:50:47 -04:00
|
|
|
});
|
|
|
|
|
|
2023-07-17 05:16:14 +02:00
|
|
|
const handleKeyboardPress = (event: KeyboardEvent) => {
|
2023-07-19 11:03:23 -05:00
|
|
|
if ($isSearchEnabled) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-01 04:27:56 +03:00
|
|
|
if (!$showAssetViewer) {
|
2023-07-17 05:16:14 +02:00
|
|
|
switch (event.key) {
|
2023-07-30 18:03:08 +02:00
|
|
|
case 'Escape':
|
|
|
|
|
assetInteractionStore.clearMultiselect();
|
|
|
|
|
return;
|
2023-07-24 04:09:06 +02:00
|
|
|
case '?':
|
2023-07-30 18:03:08 +02:00
|
|
|
if (event.shiftKey) {
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
showShortcuts = !showShortcuts;
|
|
|
|
|
}
|
2023-07-24 04:09:06 +02:00
|
|
|
return;
|
2023-07-17 05:16:14 +02:00
|
|
|
case '/':
|
2023-07-30 18:03:08 +02:00
|
|
|
event.preventDefault();
|
2023-07-17 05:16:14 +02:00
|
|
|
goto(AppRoute.EXPLORE);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2023-08-11 12:00:51 -04:00
|
|
|
const handleSelectAsset = (asset: AssetResponseDto) => {
|
|
|
|
|
if (!assetStore.albumAssets.has(asset.id)) {
|
|
|
|
|
assetInteractionStore.selectAsset(asset);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2023-07-01 00:50:47 -04:00
|
|
|
function intersectedHandler(event: CustomEvent) {
|
|
|
|
|
const el = event.detail.container as HTMLElement;
|
|
|
|
|
const target = el.firstChild as HTMLElement;
|
|
|
|
|
if (target) {
|
|
|
|
|
const bucketDate = target.id.split('_')[1];
|
2023-08-02 21:57:11 -04:00
|
|
|
assetStore.loadBucket(bucketDate, event.detail.position);
|
2023-07-01 00:50:47 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function handleScrollTimeline(event: CustomEvent) {
|
2023-08-03 14:20:41 -04:00
|
|
|
element.scrollBy(0, event.detail.heightDelta);
|
2023-07-01 00:50:47 -04:00
|
|
|
}
|
|
|
|
|
|
2023-08-04 23:26:28 -04:00
|
|
|
const handlePrevious = async () => {
|
|
|
|
|
const previousAsset = await assetStore.getPreviousAssetId($viewingAsset.id);
|
|
|
|
|
if (previousAsset) {
|
2023-08-25 00:03:28 -04:00
|
|
|
assetViewingStore.setAssetId(previousAsset);
|
2023-08-01 04:27:56 +03:00
|
|
|
}
|
2023-08-04 23:26:28 -04:00
|
|
|
|
|
|
|
|
return !!previousAsset;
|
2023-07-01 00:50:47 -04:00
|
|
|
};
|
|
|
|
|
|
2023-08-04 23:26:28 -04:00
|
|
|
const handleNext = async () => {
|
2023-08-02 21:57:11 -04:00
|
|
|
const nextAsset = await assetStore.getNextAssetId($viewingAsset.id);
|
2023-08-01 04:27:56 +03:00
|
|
|
if (nextAsset) {
|
2023-08-25 00:03:28 -04:00
|
|
|
assetViewingStore.setAssetId(nextAsset);
|
2023-08-01 04:27:56 +03:00
|
|
|
}
|
2023-08-04 23:26:28 -04:00
|
|
|
|
|
|
|
|
return !!nextAsset;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleClose = () => assetViewingStore.showAssetViewer(false);
|
|
|
|
|
|
|
|
|
|
const handleAction = async (asset: AssetResponseDto, action: AssetAction) => {
|
|
|
|
|
if (removeAction === action) {
|
|
|
|
|
// find the next asset to show or close the viewer
|
|
|
|
|
(await handleNext()) || (await handlePrevious()) || handleClose();
|
|
|
|
|
|
|
|
|
|
// delete after find the next one
|
|
|
|
|
assetStore.removeAsset(asset.id);
|
|
|
|
|
}
|
2023-07-01 00:50:47 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let animationTick = false;
|
|
|
|
|
|
|
|
|
|
const handleTimelineScroll = () => {
|
2023-08-03 14:20:41 -04:00
|
|
|
if (animationTick) {
|
|
|
|
|
return;
|
2023-07-01 00:50:47 -04:00
|
|
|
}
|
|
|
|
|
|
2023-08-03 14:20:41 -04:00
|
|
|
animationTick = true;
|
|
|
|
|
window.requestAnimationFrame(() => {
|
|
|
|
|
timelineY = element?.scrollTop || 0;
|
|
|
|
|
animationTick = false;
|
|
|
|
|
});
|
2023-07-01 00:50:47 -04:00
|
|
|
};
|
|
|
|
|
|
2023-07-03 10:56:58 +01:00
|
|
|
let lastAssetMouseEvent: AssetResponseDto | null = null;
|
|
|
|
|
|
|
|
|
|
$: if (!lastAssetMouseEvent) {
|
|
|
|
|
assetInteractionStore.clearAssetSelectionCandidates();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let shiftKeyIsDown = false;
|
|
|
|
|
|
|
|
|
|
const onKeyDown = (e: KeyboardEvent) => {
|
2023-07-19 11:03:23 -05:00
|
|
|
if ($isSearchEnabled) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-19 21:15:22 -05:00
|
|
|
if (e.key == 'Shift') {
|
2023-07-03 10:56:58 +01:00
|
|
|
e.preventDefault();
|
|
|
|
|
shiftKeyIsDown = true;
|
|
|
|
|
}
|
|
|
|
|
};
|
2023-07-19 21:15:22 -05:00
|
|
|
|
2023-07-03 10:56:58 +01:00
|
|
|
const onKeyUp = (e: KeyboardEvent) => {
|
2023-07-19 11:03:23 -05:00
|
|
|
if ($isSearchEnabled) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-19 21:15:22 -05:00
|
|
|
if (e.key == 'Shift') {
|
2023-07-03 10:56:58 +01:00
|
|
|
e.preventDefault();
|
|
|
|
|
shiftKeyIsDown = false;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
$: if (!shiftKeyIsDown) {
|
|
|
|
|
assetInteractionStore.clearAssetSelectionCandidates();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$: if (shiftKeyIsDown && lastAssetMouseEvent) {
|
|
|
|
|
selectAssetCandidates(lastAssetMouseEvent);
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-11 12:00:51 -04:00
|
|
|
const handleSelectAssetCandidates = (asset: AssetResponseDto | null) => {
|
2023-07-03 10:56:58 +01:00
|
|
|
if (asset) {
|
|
|
|
|
selectAssetCandidates(asset);
|
|
|
|
|
}
|
|
|
|
|
lastAssetMouseEvent = asset;
|
|
|
|
|
};
|
|
|
|
|
|
2023-08-11 12:00:51 -04:00
|
|
|
const handleGroupSelect = (group: string, assets: AssetResponseDto[]) => {
|
|
|
|
|
if ($selectedGroup.has(group)) {
|
|
|
|
|
assetInteractionStore.removeGroupFromMultiselectGroup(group);
|
|
|
|
|
for (const asset of assets) {
|
|
|
|
|
assetInteractionStore.removeAssetFromMultiselectGroup(asset);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
assetInteractionStore.addGroupToMultiselectGroup(group);
|
|
|
|
|
for (const asset of assets) {
|
|
|
|
|
handleSelectAsset(asset);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleSelectAssets = async (asset: AssetResponseDto) => {
|
2023-07-12 05:12:19 +03:00
|
|
|
if (!asset) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-05 09:58:52 -04:00
|
|
|
dispatch('select', asset);
|
|
|
|
|
|
|
|
|
|
if (singleSelect) {
|
|
|
|
|
element.scrollTop = 0;
|
2023-08-11 12:00:51 -04:00
|
|
|
return;
|
2023-08-05 09:58:52 -04:00
|
|
|
}
|
|
|
|
|
|
2023-07-12 05:12:19 +03:00
|
|
|
const rangeSelection = $assetSelectionCandidates.size > 0;
|
|
|
|
|
const deselect = $selectedAssets.has(asset);
|
|
|
|
|
|
|
|
|
|
// Select/deselect already loaded assets
|
|
|
|
|
if (deselect) {
|
|
|
|
|
for (const candidate of $assetSelectionCandidates || []) {
|
|
|
|
|
assetInteractionStore.removeAssetFromMultiselectGroup(candidate);
|
|
|
|
|
}
|
|
|
|
|
assetInteractionStore.removeAssetFromMultiselectGroup(asset);
|
|
|
|
|
} else {
|
|
|
|
|
for (const candidate of $assetSelectionCandidates || []) {
|
2023-08-11 12:00:51 -04:00
|
|
|
handleSelectAsset(candidate);
|
2023-07-12 05:12:19 +03:00
|
|
|
}
|
2023-08-11 12:00:51 -04:00
|
|
|
handleSelectAsset(asset);
|
2023-07-12 05:12:19 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
assetInteractionStore.clearAssetSelectionCandidates();
|
|
|
|
|
|
|
|
|
|
if ($assetSelectionStart && rangeSelection) {
|
2023-08-02 21:57:11 -04:00
|
|
|
let startBucketIndex = $assetStore.getBucketIndexByAssetId($assetSelectionStart.id);
|
|
|
|
|
let endBucketIndex = $assetStore.getBucketIndexByAssetId(asset.id);
|
|
|
|
|
|
|
|
|
|
if (startBucketIndex === null || endBucketIndex === null) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2023-07-12 05:12:19 +03:00
|
|
|
|
|
|
|
|
if (endBucketIndex < startBucketIndex) {
|
|
|
|
|
[startBucketIndex, endBucketIndex] = [endBucketIndex, startBucketIndex];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Select/deselect assets in all intermediate buckets
|
|
|
|
|
for (let bucketIndex = startBucketIndex + 1; bucketIndex < endBucketIndex; bucketIndex++) {
|
2023-08-01 04:27:56 +03:00
|
|
|
const bucket = $assetStore.buckets[bucketIndex];
|
2023-08-02 21:57:11 -04:00
|
|
|
await assetStore.loadBucket(bucket.bucketDate, BucketPosition.Unknown);
|
2023-07-12 05:12:19 +03:00
|
|
|
for (const asset of bucket.assets) {
|
|
|
|
|
if (deselect) {
|
|
|
|
|
assetInteractionStore.removeAssetFromMultiselectGroup(asset);
|
|
|
|
|
} else {
|
2023-08-11 12:00:51 -04:00
|
|
|
handleSelectAsset(asset);
|
2023-07-12 05:12:19 +03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Update date group selection
|
|
|
|
|
for (let bucketIndex = startBucketIndex; bucketIndex <= endBucketIndex; bucketIndex++) {
|
2023-08-01 04:27:56 +03:00
|
|
|
const bucket = $assetStore.buckets[bucketIndex];
|
2023-07-12 05:12:19 +03:00
|
|
|
|
|
|
|
|
// Split bucket into date groups and check each group
|
|
|
|
|
const assetsGroupByDate = splitBucketIntoDateGroups(bucket.assets, $locale);
|
|
|
|
|
|
|
|
|
|
for (const dateGroup of assetsGroupByDate) {
|
|
|
|
|
const dateGroupTitle = formatGroupTitle(DateTime.fromISO(dateGroup[0].fileCreatedAt).startOf('day'));
|
|
|
|
|
if (dateGroup.every((a) => $selectedAssets.has(a))) {
|
|
|
|
|
assetInteractionStore.addGroupToMultiselectGroup(dateGroupTitle);
|
|
|
|
|
} else {
|
|
|
|
|
assetInteractionStore.removeGroupFromMultiselectGroup(dateGroupTitle);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
assetInteractionStore.setAssetSelectionStart(deselect ? null : asset);
|
|
|
|
|
};
|
|
|
|
|
|
2023-07-03 10:56:58 +01:00
|
|
|
const selectAssetCandidates = (asset: AssetResponseDto) => {
|
|
|
|
|
if (!shiftKeyIsDown) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-12 05:12:19 +03:00
|
|
|
const rangeStart = $assetSelectionStart;
|
|
|
|
|
if (!rangeStart) {
|
2023-07-03 10:56:58 +01:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-01 04:27:56 +03:00
|
|
|
let start = $assetStore.assets.indexOf(rangeStart);
|
|
|
|
|
let end = $assetStore.assets.indexOf(asset);
|
2023-07-03 10:56:58 +01:00
|
|
|
|
|
|
|
|
if (start > end) {
|
|
|
|
|
[start, end] = [end, start];
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-01 04:27:56 +03:00
|
|
|
assetInteractionStore.setAssetSelectionCandidates($assetStore.assets.slice(start, end + 1));
|
2023-07-03 10:56:58 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const onSelectStart = (e: Event) => {
|
2023-08-01 04:27:56 +03:00
|
|
|
if ($isMultiSelectState && shiftKeyIsDown) {
|
2023-07-03 10:56:58 +01:00
|
|
|
e.preventDefault();
|
|
|
|
|
}
|
|
|
|
|
};
|
2022-09-04 08:34:39 -05:00
|
|
|
</script>
|
|
|
|
|
|
2023-07-03 10:56:58 +01:00
|
|
|
<svelte:window on:keydown={onKeyDown} on:keyup={onKeyUp} on:selectstart={onSelectStart} />
|
|
|
|
|
|
2023-07-24 04:09:06 +02:00
|
|
|
{#if showShortcuts}
|
|
|
|
|
<ShowShortcuts on:close={() => (showShortcuts = !showShortcuts)} />
|
|
|
|
|
{/if}
|
|
|
|
|
|
2023-08-03 14:20:41 -04:00
|
|
|
<Scrollbar
|
|
|
|
|
{assetStore}
|
|
|
|
|
height={viewport.height}
|
|
|
|
|
{timelineY}
|
|
|
|
|
on:scrollTimeline={({ detail }) => (element.scrollTop = detail)}
|
|
|
|
|
/>
|
2022-09-09 15:55:20 -05:00
|
|
|
|
2023-06-08 18:22:45 +03:00
|
|
|
<!-- Right margin MUST be equal to the width of immich-scrubbable-scrollbar -->
|
2022-09-04 08:34:39 -05:00
|
|
|
<section
|
2023-07-01 00:50:47 -04:00
|
|
|
id="asset-grid"
|
2023-08-11 12:00:51 -04:00
|
|
|
class="scrollbar-hidden ml-4 mr-[60px] h-full overflow-y-auto pb-[60px]"
|
2023-08-03 11:44:12 -04:00
|
|
|
bind:clientHeight={viewport.height}
|
|
|
|
|
bind:clientWidth={viewport.width}
|
2023-08-03 14:20:41 -04:00
|
|
|
bind:this={element}
|
2023-07-01 00:50:47 -04:00
|
|
|
on:scroll={handleTimelineScroll}
|
2022-09-04 08:34:39 -05:00
|
|
|
>
|
2023-08-03 14:20:41 -04:00
|
|
|
{#if element}
|
2023-08-04 00:52:15 -04:00
|
|
|
<slot />
|
2023-09-01 19:12:09 +02:00
|
|
|
|
|
|
|
|
<!-- skeleton -->
|
|
|
|
|
{#if !$assetStore.initialized}
|
|
|
|
|
<div class="ml-[14px] mt-5">
|
|
|
|
|
<div class="flex w-[120%] flex-wrap">
|
|
|
|
|
{#each Array(100) as _}
|
|
|
|
|
<div class="m-[1px] h-[10em] w-[16em] animate-pulse bg-immich-primary/20 dark:bg-immich-dark-primary/20" />
|
|
|
|
|
{/each}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
{/if}
|
|
|
|
|
|
|
|
|
|
<!-- (optional) empty placeholder -->
|
|
|
|
|
{#if $assetStore.initialized && $assetStore.buckets.length === 0}
|
|
|
|
|
<slot name="empty" />
|
|
|
|
|
{/if}
|
2023-08-01 04:27:56 +03:00
|
|
|
<section id="virtual-timeline" style:height={$assetStore.timelineHeight + 'px'}>
|
|
|
|
|
{#each $assetStore.buckets as bucket, bucketIndex (bucketIndex)}
|
2023-07-01 00:50:47 -04:00
|
|
|
<IntersectionObserver
|
|
|
|
|
on:intersected={intersectedHandler}
|
2023-08-02 21:57:11 -04:00
|
|
|
on:hidden={() => assetStore.cancelBucket(bucket)}
|
2023-07-01 00:50:47 -04:00
|
|
|
let:intersecting
|
|
|
|
|
top={750}
|
|
|
|
|
bottom={750}
|
2023-08-03 14:20:41 -04:00
|
|
|
root={element}
|
2023-07-01 00:50:47 -04:00
|
|
|
>
|
|
|
|
|
<div id={'bucket_' + bucket.bucketDate} style:height={bucket.bucketHeight + 'px'}>
|
|
|
|
|
{#if intersecting}
|
|
|
|
|
<AssetDateGroup
|
2023-08-01 04:27:56 +03:00
|
|
|
{assetStore}
|
|
|
|
|
{assetInteractionStore}
|
2023-08-05 09:58:52 -04:00
|
|
|
{isSelectionMode}
|
|
|
|
|
{singleSelect}
|
2023-08-11 12:00:51 -04:00
|
|
|
on:select={({ detail: group }) => handleGroupSelect(group.title, group.assets)}
|
2023-07-01 00:50:47 -04:00
|
|
|
on:shift={handleScrollTimeline}
|
2023-08-11 12:00:51 -04:00
|
|
|
on:selectAssetCandidates={({ detail: asset }) => handleSelectAssetCandidates(asset)}
|
|
|
|
|
on:selectAssets={({ detail: asset }) => handleSelectAssets(asset)}
|
2023-07-01 00:50:47 -04:00
|
|
|
assets={bucket.assets}
|
|
|
|
|
bucketDate={bucket.bucketDate}
|
|
|
|
|
bucketHeight={bucket.bucketHeight}
|
2023-08-03 11:44:12 -04:00
|
|
|
{viewport}
|
2023-07-01 00:50:47 -04:00
|
|
|
/>
|
|
|
|
|
{/if}
|
|
|
|
|
</div>
|
|
|
|
|
</IntersectionObserver>
|
|
|
|
|
{/each}
|
|
|
|
|
</section>
|
|
|
|
|
{/if}
|
2022-09-04 08:34:39 -05:00
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
<Portal target="body">
|
2023-08-01 04:27:56 +03:00
|
|
|
{#if $showAssetViewer}
|
2023-07-01 00:50:47 -04:00
|
|
|
<AssetViewer
|
2023-08-01 04:27:56 +03:00
|
|
|
{assetStore}
|
|
|
|
|
asset={$viewingAsset}
|
2023-08-04 23:26:28 -04:00
|
|
|
on:previous={() => handlePrevious()}
|
|
|
|
|
on:next={() => handleNext()}
|
|
|
|
|
on:close={() => handleClose()}
|
|
|
|
|
on:archived={({ detail: asset }) => handleAction(asset, AssetAction.ARCHIVE)}
|
|
|
|
|
on:unarchived={({ detail: asset }) => handleAction(asset, AssetAction.UNARCHIVE)}
|
|
|
|
|
on:favorite={({ detail: asset }) => handleAction(asset, AssetAction.FAVORITE)}
|
|
|
|
|
on:unfavorite={({ detail: asset }) => handleAction(asset, AssetAction.UNFAVORITE)}
|
2023-07-01 00:50:47 -04:00
|
|
|
/>
|
|
|
|
|
{/if}
|
2022-09-04 08:34:39 -05:00
|
|
|
</Portal>
|
|
|
|
|
|
|
|
|
|
<style>
|
2023-07-01 00:50:47 -04:00
|
|
|
#asset-grid {
|
|
|
|
|
contain: layout;
|
|
|
|
|
scrollbar-width: none;
|
|
|
|
|
}
|
2022-09-04 08:34:39 -05:00
|
|
|
</style>
|