123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176 |
- <script lang="ts">
- import {
- assetInteractionStore,
- isViewingAssetStoreState,
- viewingAssetStoreState
- } from '$lib/stores/asset-interaction.store';
- import { assetGridState, assetStore, loadingBucketState } from '$lib/stores/assets.store';
- import type { UserResponseDto } from '@api';
- import { AssetCountByTimeBucketResponseDto, AssetResponseDto, TimeGroupEnum, api } from '@api';
- import { onDestroy, onMount } from 'svelte';
- import AssetViewer from '../asset-viewer/asset-viewer.svelte';
- import IntersectionObserver from '../asset-viewer/intersection-observer.svelte';
- import Portal from '../shared-components/portal/portal.svelte';
- import Scrollbar, {
- OnScrollbarClickDetail,
- OnScrollbarDragDetail
- } from '../shared-components/scrollbar/scrollbar.svelte';
- import AssetDateGroup from './asset-date-group.svelte';
- export let user: UserResponseDto | undefined = undefined;
- export let isAlbumSelectionMode = false;
- let viewportHeight = 0;
- let viewportWidth = 0;
- let assetGridElement: HTMLElement;
- let bucketInfo: AssetCountByTimeBucketResponseDto;
- onMount(async () => {
- const { data: assetCountByTimebucket } = await api.assetApi.getAssetCountByTimeBucket({
- getAssetCountByTimeBucketDto: {
- timeGroup: TimeGroupEnum.Month,
- userId: user?.id
- }
- });
- bucketInfo = assetCountByTimebucket;
- assetStore.setInitialState(viewportHeight, viewportWidth, assetCountByTimebucket, user?.id);
- // Get asset bucket if bucket height is smaller than viewport height
- let bucketsToFetchInitially: string[] = [];
- let initialBucketsHeight = 0;
- $assetGridState.buckets.every((bucket) => {
- if (initialBucketsHeight < viewportHeight) {
- initialBucketsHeight += bucket.bucketHeight;
- bucketsToFetchInitially.push(bucket.bucketDate);
- return true;
- } else {
- return false;
- }
- });
- bucketsToFetchInitially.forEach((bucketDate) => {
- assetStore.getAssetsByBucket(bucketDate);
- });
- });
- onDestroy(() => {
- assetStore.setInitialState(0, 0, { totalCount: 0, buckets: [] }, undefined);
- });
- function intersectedHandler(event: CustomEvent) {
- const el = event.detail as HTMLElement;
- const target = el.firstChild as HTMLElement;
- if (target) {
- const bucketDate = target.id.split('_')[1];
- assetStore.getAssetsByBucket(bucketDate);
- }
- }
- const navigateToPreviousAsset = () => {
- assetInteractionStore.navigateAsset('previous');
- };
- const navigateToNextAsset = () => {
- assetInteractionStore.navigateAsset('next');
- };
- let lastScrollPosition = 0;
- let animationTick = false;
- const handleTimelineScroll = () => {
- if (!animationTick) {
- window.requestAnimationFrame(() => {
- lastScrollPosition = assetGridElement?.scrollTop;
- animationTick = false;
- });
- animationTick = true;
- }
- };
- const handleScrollbarClick = (e: OnScrollbarClickDetail) => {
- assetGridElement.scrollTop = e.scrollTo;
- };
- const handleScrollbarDrag = (e: OnScrollbarDragDetail) => {
- assetGridElement.scrollTop = e.scrollTo;
- };
- const handleArchiveSuccess = (e: CustomEvent) => {
- const asset = e.detail as AssetResponseDto;
- navigateToNextAsset();
- assetStore.removeAsset(asset.id);
- };
- </script>
- {#if bucketInfo && viewportHeight && $assetGridState.timelineHeight > viewportHeight}
- <Scrollbar
- scrollbarHeight={viewportHeight}
- scrollTop={lastScrollPosition}
- on:onscrollbarclick={(e) => handleScrollbarClick(e.detail)}
- on:onscrollbardrag={(e) => handleScrollbarDrag(e.detail)}
- />
- {/if}
- <section
- id="asset-grid"
- class="overflow-y-auto pl-4 scrollbar-hidden"
- bind:clientHeight={viewportHeight}
- bind:clientWidth={viewportWidth}
- bind:this={assetGridElement}
- on:scroll={handleTimelineScroll}
- >
- {#if assetGridElement}
- <section id="virtual-timeline" style:height={$assetGridState.timelineHeight + 'px'}>
- {#each $assetGridState.buckets as bucket, bucketIndex (bucketIndex)}
- <IntersectionObserver
- on:intersected={intersectedHandler}
- on:hidden={async () => {
- // If bucket is hidden and in loading state, cancel the request
- if ($loadingBucketState[bucket.bucketDate]) {
- await assetStore.cancelBucketRequest(bucket.cancelToken, bucket.bucketDate);
- }
- }}
- let:intersecting
- top={750}
- bottom={750}
- root={assetGridElement}
- >
- <div id={'bucket_' + bucket.bucketDate} style:height={bucket.bucketHeight + 'px'}>
- {#if intersecting}
- <AssetDateGroup
- {isAlbumSelectionMode}
- assets={bucket.assets}
- bucketDate={bucket.bucketDate}
- bucketHeight={bucket.bucketHeight}
- />
- {/if}
- </div>
- </IntersectionObserver>
- {/each}
- </section>
- {/if}
- </section>
- <Portal target="body">
- {#if $isViewingAssetStoreState}
- <AssetViewer
- asset={$viewingAssetStoreState}
- on:navigate-previous={navigateToPreviousAsset}
- on:navigate-next={navigateToNextAsset}
- on:close={() => {
- assetInteractionStore.setIsViewingAsset(false);
- }}
- on:archived={handleArchiveSuccess}
- />
- {/if}
- </Portal>
- <style>
- #asset-grid {
- contain: layout;
- scrollbar-width: none;
- }
- </style>
|