mirror of
https://github.com/immich-app/immich.git
synced 2025-03-11 02:23:09 -05:00
feat(web) add scrollbar with timeline information (#658)
- Implement a scrollbar with a timeline similar to Google Photos - The scrollbar can also be dragged
This commit is contained in:
parent
b6d025da09
commit
d856b35afc
9 changed files with 194 additions and 69 deletions
|
@ -88,4 +88,11 @@ input:focus-visible {
|
||||||
background: #4250afad;
|
background: #4250afad;
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Hidden scrollbar */
|
||||||
|
/* width */
|
||||||
|
.scrollbar-hidden::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
scrollbar-width: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,12 +26,16 @@
|
||||||
NotificationType
|
NotificationType
|
||||||
} from '../shared-components/notification/notification';
|
} from '../shared-components/notification/notification';
|
||||||
import { browser } from '$app/env';
|
import { browser } from '$app/env';
|
||||||
|
import { albumAssetSelectionStore } from '$lib/stores/album-asset-selection.store';
|
||||||
|
|
||||||
export let album: AlbumResponseDto;
|
export let album: AlbumResponseDto;
|
||||||
|
const { isAlbumAssetSelectionOpen } = albumAssetSelectionStore;
|
||||||
|
|
||||||
let isShowAssetViewer = false;
|
let isShowAssetViewer = false;
|
||||||
|
|
||||||
let isShowAssetSelection = false;
|
let isShowAssetSelection = false;
|
||||||
|
|
||||||
|
$: $isAlbumAssetSelectionOpen = isShowAssetSelection;
|
||||||
$: {
|
$: {
|
||||||
if (browser) {
|
if (browser) {
|
||||||
if (isShowAssetSelection) {
|
if (isShowAssetSelection) {
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
import IntersectionObserver from '../asset-viewer/intersection-observer.svelte';
|
import IntersectionObserver from '../asset-viewer/intersection-observer.svelte';
|
||||||
import { assetGridState, assetStore, loadingBucketState } from '$lib/stores/assets.store';
|
import { assetGridState, assetStore, loadingBucketState } from '$lib/stores/assets.store';
|
||||||
import { api, TimeGroupEnum } from '@api';
|
import { api, AssetCountByTimeBucketResponseDto, TimeGroupEnum } from '@api';
|
||||||
import AssetDateGroup from './asset-date-group.svelte';
|
import AssetDateGroup from './asset-date-group.svelte';
|
||||||
import Portal from '../shared-components/portal/portal.svelte';
|
import Portal from '../shared-components/portal/portal.svelte';
|
||||||
import AssetViewer from '../asset-viewer/asset-viewer.svelte';
|
import AssetViewer from '../asset-viewer/asset-viewer.svelte';
|
||||||
|
@ -12,16 +12,23 @@
|
||||||
isViewingAssetStoreState,
|
isViewingAssetStoreState,
|
||||||
viewingAssetStoreState
|
viewingAssetStoreState
|
||||||
} from '$lib/stores/asset-interaction.store';
|
} from '$lib/stores/asset-interaction.store';
|
||||||
|
import Scrollbar, {
|
||||||
|
OnScrollbarClickDetail,
|
||||||
|
OnScrollbarDragDetail
|
||||||
|
} from '../shared-components/scrollbar/scrollbar.svelte';
|
||||||
|
|
||||||
|
export let isAlbumSelectionMode = false;
|
||||||
|
|
||||||
let viewportHeight = 0;
|
let viewportHeight = 0;
|
||||||
let viewportWidth = 0;
|
let viewportWidth = 0;
|
||||||
let assetGridElement: HTMLElement;
|
let assetGridElement: HTMLElement;
|
||||||
export let isAlbumSelectionMode = false;
|
let bucketInfo: AssetCountByTimeBucketResponseDto;
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
const { data: assetCountByTimebucket } = await api.assetApi.getAssetCountByTimeBucket({
|
const { data: assetCountByTimebucket } = await api.assetApi.getAssetCountByTimeBucket({
|
||||||
timeGroup: TimeGroupEnum.Month
|
timeGroup: TimeGroupEnum.Month
|
||||||
});
|
});
|
||||||
|
bucketInfo = assetCountByTimebucket;
|
||||||
|
|
||||||
assetStore.setInitialState(viewportHeight, viewportWidth, assetCountByTimebucket);
|
assetStore.setInitialState(viewportHeight, viewportWidth, assetCountByTimebucket);
|
||||||
|
|
||||||
|
@ -60,14 +67,46 @@
|
||||||
const navigateToNextAsset = () => {
|
const navigateToNextAsset = () => {
|
||||||
assetInteractionStore.navigateAsset('next');
|
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;
|
||||||
|
};
|
||||||
</script>
|
</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
|
<section
|
||||||
id="asset-grid"
|
id="asset-grid"
|
||||||
class="overflow-y-auto pl-4"
|
class="overflow-y-auto pl-4 scrollbar-hidden"
|
||||||
bind:clientHeight={viewportHeight}
|
bind:clientHeight={viewportHeight}
|
||||||
bind:clientWidth={viewportWidth}
|
bind:clientWidth={viewportWidth}
|
||||||
bind:this={assetGridElement}
|
bind:this={assetGridElement}
|
||||||
|
on:scroll={handleTimelineScroll}
|
||||||
>
|
>
|
||||||
{#if assetGridElement}
|
{#if assetGridElement}
|
||||||
<section id="virtual-timeline" style:height={$assetGridState.timelineHeight + 'px'}>
|
<section id="virtual-timeline" style:height={$assetGridState.timelineHeight + 'px'}>
|
||||||
|
@ -117,5 +156,6 @@
|
||||||
<style>
|
<style>
|
||||||
#asset-grid {
|
#asset-grid {
|
||||||
contain: layout;
|
contain: layout;
|
||||||
|
scrollbar-width: none;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -136,7 +136,7 @@
|
||||||
<div
|
<div
|
||||||
style:width={`${thumbnailSize}px`}
|
style:width={`${thumbnailSize}px`}
|
||||||
style:height={`${thumbnailSize}px`}
|
style:height={`${thumbnailSize}px`}
|
||||||
class={`bg-gray-100 relative ${getSize()} ${
|
class={`bg-gray-100 relative select-none ${getSize()} ${
|
||||||
disabled ? 'cursor-not-allowed' : 'hover:cursor-pointer'
|
disabled ? 'cursor-not-allowed' : 'hover:cursor-pointer'
|
||||||
}`}
|
}`}
|
||||||
on:mouseenter={handleMouseOverThumbnail}
|
on:mouseenter={handleMouseOverThumbnail}
|
||||||
|
|
|
@ -1,74 +1,111 @@
|
||||||
|
<script lang="ts" context="module">
|
||||||
|
type OnScrollbarClick = {
|
||||||
|
onscrollbarclick: OnScrollbarClickDetail;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type OnScrollbarClickDetail = {
|
||||||
|
scrollTo: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
type OnScrollbarDrag = {
|
||||||
|
onscrollbardrag: OnScrollbarDragDetail;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type OnScrollbarDragDetail = {
|
||||||
|
scrollTo: number;
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from 'svelte';
|
import { albumAssetSelectionStore } from '$lib/stores/album-asset-selection.store';
|
||||||
|
|
||||||
|
import { assetGridState } from '$lib/stores/assets.store';
|
||||||
|
|
||||||
|
import { createEventDispatcher } from 'svelte';
|
||||||
import { SegmentScrollbarLayout } from './segment-scrollbar-layout';
|
import { SegmentScrollbarLayout } from './segment-scrollbar-layout';
|
||||||
|
|
||||||
export let scrollTop = 0;
|
export let scrollTop = 0;
|
||||||
// export let viewportWidth = 0;
|
|
||||||
export let scrollbarHeight = 0;
|
export let scrollbarHeight = 0;
|
||||||
|
|
||||||
let timelineHeight = 0;
|
$: timelineHeight = $assetGridState.timelineHeight;
|
||||||
|
$: viewportWidth = $assetGridState.viewportWidth;
|
||||||
|
$: timelineScrolltop = (scrollbarPosition / scrollbarHeight) * timelineHeight;
|
||||||
|
|
||||||
let segmentScrollbarLayout: SegmentScrollbarLayout[] = [];
|
let segmentScrollbarLayout: SegmentScrollbarLayout[] = [];
|
||||||
let isHover = false;
|
let isHover = false;
|
||||||
|
let isDragging = false;
|
||||||
let hoveredDate: Date;
|
let hoveredDate: Date;
|
||||||
let currentMouseYLocation = 0;
|
let currentMouseYLocation = 0;
|
||||||
let scrollbarPosition = 0;
|
let scrollbarPosition = 0;
|
||||||
|
let animationTick = false;
|
||||||
|
|
||||||
|
const { isAlbumAssetSelectionOpen } = albumAssetSelectionStore;
|
||||||
|
$: offset = $isAlbumAssetSelectionOpen ? 100 : 71;
|
||||||
|
const dispatchClick = createEventDispatcher<OnScrollbarClick>();
|
||||||
|
const dispatchDrag = createEventDispatcher<OnScrollbarDrag>();
|
||||||
$: {
|
$: {
|
||||||
scrollbarPosition = (scrollTop / timelineHeight) * scrollbarHeight;
|
scrollbarPosition = (scrollTop / timelineHeight) * scrollbarHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
// let result: SegmentScrollbarLayout[] = [];
|
let result: SegmentScrollbarLayout[] = [];
|
||||||
// for (const [i, segment] of assetStoreState.entries()) {
|
for (const bucket of $assetGridState.buckets) {
|
||||||
// let segmentLayout = new SegmentScrollbarLayout();
|
let segmentLayout = new SegmentScrollbarLayout();
|
||||||
// segmentLayout.count = segmentData.groups[i].count;
|
segmentLayout.count = bucket.assets.length;
|
||||||
// segmentLayout.height =
|
segmentLayout.height = (bucket.bucketHeight / timelineHeight) * scrollbarHeight;
|
||||||
// segment.assets.length == 0
|
segmentLayout.timeGroup = bucket.bucketDate;
|
||||||
// ? getSegmentHeight(segmentData.groups[i].count)
|
result.push(segmentLayout);
|
||||||
// : Math.round((segment.segmentHeight / timelineHeight) * scrollbarHeight);
|
}
|
||||||
// segmentLayout.timeGroup = segment.segmentDate;
|
segmentScrollbarLayout = result;
|
||||||
// result.push(segmentLayout);
|
|
||||||
// }
|
|
||||||
// segmentScrollbarLayout = result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
// segmentScrollbarLayout = getLayoutDistance();
|
|
||||||
});
|
|
||||||
|
|
||||||
// const getSegmentHeight = (groupCount: number) => {
|
|
||||||
// if (segmentData.groups.length > 0) {
|
|
||||||
// const percentage = (groupCount * 100) / segmentData.totalAssets;
|
|
||||||
// return Math.round((percentage * scrollbarHeight) / 100);
|
|
||||||
// } else {
|
|
||||||
// return 0;
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
|
|
||||||
// const getLayoutDistance = () => {
|
|
||||||
// let result: SegmentScrollbarLayout[] = [];
|
|
||||||
// for (const segment of segmentData.groups) {
|
|
||||||
// let segmentLayout = new SegmentScrollbarLayout();
|
|
||||||
// segmentLayout.count = segment.count;
|
|
||||||
// segmentLayout.height = getSegmentHeight(segment.count);
|
|
||||||
// segmentLayout.timeGroup = segment.timeGroup;
|
|
||||||
// result.push(segmentLayout);
|
|
||||||
// }
|
|
||||||
// return result;
|
|
||||||
// };
|
|
||||||
|
|
||||||
const handleMouseMove = (e: MouseEvent, currentDate: Date) => {
|
const handleMouseMove = (e: MouseEvent, currentDate: Date) => {
|
||||||
currentMouseYLocation = e.clientY - 71 - 30;
|
currentMouseYLocation = e.clientY - offset - 30;
|
||||||
|
|
||||||
hoveredDate = new Date(currentDate.toISOString().slice(0, -1));
|
hoveredDate = new Date(currentDate.toISOString().slice(0, -1));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleMouseDown = (e: MouseEvent) => {
|
||||||
|
isDragging = true;
|
||||||
|
scrollbarPosition = e.clientY - offset;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMouseUp = (e: MouseEvent) => {
|
||||||
|
isDragging = false;
|
||||||
|
scrollbarPosition = e.clientY - offset;
|
||||||
|
dispatchClick('onscrollbarclick', { scrollTo: timelineScrolltop });
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMouseDrag = (e: MouseEvent) => {
|
||||||
|
if (isDragging) {
|
||||||
|
if (!animationTick) {
|
||||||
|
window.requestAnimationFrame(() => {
|
||||||
|
const dy = e.clientY - scrollbarPosition - offset;
|
||||||
|
scrollbarPosition += dy;
|
||||||
|
dispatchDrag('onscrollbardrag', { scrollTo: timelineScrolltop });
|
||||||
|
animationTick = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
animationTick = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
id="immich-scubbable-scrollbar"
|
id="immich-scrubbable-scrollbar"
|
||||||
class="fixed right-0 w-[60px] h-full bg-immich-bg z-[9999] hover:cursor-row-resize"
|
class="fixed right-0 bg-immich-bg z-10 hover:cursor-row-resize select-none"
|
||||||
|
style:width={isDragging ? '100vw' : '60px'}
|
||||||
|
style:background-color={isDragging ? 'transparent' : 'transparent'}
|
||||||
on:mouseenter={() => (isHover = true)}
|
on:mouseenter={() => (isHover = true)}
|
||||||
on:mouseleave={() => (isHover = false)}
|
on:mouseleave={() => {
|
||||||
|
isHover = false;
|
||||||
|
isDragging = false;
|
||||||
|
}}
|
||||||
|
on:mouseup={handleMouseUp}
|
||||||
|
on:mousemove={handleMouseDrag}
|
||||||
|
on:mousedown={handleMouseDown}
|
||||||
|
style:height={scrollbarHeight + 'px'}
|
||||||
>
|
>
|
||||||
{#if isHover}
|
{#if isHover}
|
||||||
<div
|
<div
|
||||||
|
@ -81,29 +118,33 @@
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<!-- Scroll Position Indicator Line -->
|
<!-- Scroll Position Indicator Line -->
|
||||||
<div
|
{#if !isDragging}
|
||||||
class="absolute right-0 w-10 h-[2px] bg-immich-primary"
|
<div
|
||||||
style:top={scrollbarPosition + 'px'}
|
class="absolute right-0 w-10 h-[2px] bg-immich-primary"
|
||||||
/>
|
style:top={scrollbarPosition + 'px'}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
<!-- Time Segment -->
|
<!-- Time Segment -->
|
||||||
{#each segmentScrollbarLayout as segment, index (segment.timeGroup)}
|
{#each segmentScrollbarLayout as segment, index (segment.timeGroup)}
|
||||||
{@const groupDate = new Date(segment.timeGroup)}
|
{@const groupDate = new Date(segment.timeGroup)}
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="relative "
|
id="time-segment"
|
||||||
|
class="relative"
|
||||||
style:height={segment.height + 'px'}
|
style:height={segment.height + 'px'}
|
||||||
aria-label={segment.timeGroup + ' ' + segment.count}
|
aria-label={segment.timeGroup + ' ' + segment.count}
|
||||||
on:mousemove={(e) => handleMouseMove(e, groupDate)}
|
on:mousemove={(e) => handleMouseMove(e, groupDate)}
|
||||||
>
|
>
|
||||||
{#if new Date(segmentScrollbarLayout[index - 1]?.timeGroup).getFullYear() !== groupDate.getFullYear()}
|
{#if new Date(segmentScrollbarLayout[index - 1]?.timeGroup).getFullYear() !== groupDate.getFullYear()}
|
||||||
<div
|
{#if segment.height > 8}
|
||||||
aria-label={segment.timeGroup + ' ' + segment.count}
|
<div
|
||||||
class="absolute right-0 pr-3 z-10 text-xs font-medium"
|
aria-label={segment.timeGroup + ' ' + segment.count}
|
||||||
>
|
class="absolute right-0 pr-5 z-10 text-xs font-medium"
|
||||||
{groupDate.getFullYear()}
|
>
|
||||||
</div>
|
{groupDate.getFullYear()}
|
||||||
{:else if segment.count > 5}
|
</div>
|
||||||
|
{/if}
|
||||||
|
{:else if segment.height > 5}
|
||||||
<div
|
<div
|
||||||
aria-label={segment.timeGroup + ' ' + segment.count}
|
aria-label={segment.timeGroup + ' ' + segment.count}
|
||||||
class="absolute right-0 rounded-full h-[4px] w-[4px] mr-3 bg-gray-300 block"
|
class="absolute right-0 rounded-full h-[4px] w-[4px] mr-3 bg-gray-300 block"
|
||||||
|
@ -114,7 +155,8 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
#immich-scubbable-scrollbar {
|
#immich-scrubbable-scrollbar,
|
||||||
|
#time-segment {
|
||||||
contain: layout;
|
contain: layout;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
let showSharingCount = false;
|
let showSharingCount = false;
|
||||||
let showAlbumsCount = false;
|
let showAlbumsCount = false;
|
||||||
|
|
||||||
|
// let domCount = 0;
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
if ($page.routeId == 'albums') {
|
if ($page.routeId == 'albums') {
|
||||||
selectedAction = AppSideBarSelection.ALBUMS;
|
selectedAction = AppSideBarSelection.ALBUMS;
|
||||||
|
@ -26,6 +27,10 @@
|
||||||
} else if ($page.routeId == 'sharing') {
|
} else if ($page.routeId == 'sharing') {
|
||||||
selectedAction = AppSideBarSelection.SHARING;
|
selectedAction = AppSideBarSelection.SHARING;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// setInterval(() => {
|
||||||
|
// domCount = document.getElementsByTagName('*').length;
|
||||||
|
// }, 500);
|
||||||
});
|
});
|
||||||
|
|
||||||
const getAssetCount = async () => {
|
const getAssetCount = async () => {
|
||||||
|
@ -48,6 +53,7 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<section id="sidebar" class="flex flex-col gap-1 pt-8 pr-6">
|
<section id="sidebar" class="flex flex-col gap-1 pt-8 pr-6">
|
||||||
|
<!-- {domCount} -->
|
||||||
<a
|
<a
|
||||||
sveltekit:prefetch
|
sveltekit:prefetch
|
||||||
sveltekit:noscroll
|
sveltekit:noscroll
|
||||||
|
@ -110,7 +116,7 @@
|
||||||
<LoadingSpinner />
|
<LoadingSpinner />
|
||||||
{:then data}
|
{:then data}
|
||||||
<div>
|
<div>
|
||||||
<p>{data.shared + data.sharing} albums</p>
|
<p>{data.shared + data.sharing} Albums</p>
|
||||||
</div>
|
</div>
|
||||||
{/await}
|
{/await}
|
||||||
</div>
|
</div>
|
||||||
|
@ -145,7 +151,7 @@
|
||||||
<LoadingSpinner />
|
<LoadingSpinner />
|
||||||
{:then data}
|
{:then data}
|
||||||
<div>
|
<div>
|
||||||
<p>{data.owned} albums</p>
|
<p>{data.owned} Albums</p>
|
||||||
</div>
|
</div>
|
||||||
{/await}
|
{/await}
|
||||||
</div>
|
</div>
|
||||||
|
|
10
web/src/lib/stores/album-asset-selection.store.ts
Normal file
10
web/src/lib/stores/album-asset-selection.store.ts
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
import { writable } from 'svelte/store';
|
||||||
|
|
||||||
|
function createAlbumAssetSelectionStore() {
|
||||||
|
const isAlbumAssetSelectionOpen = writable<boolean>(false);
|
||||||
|
return {
|
||||||
|
isAlbumAssetSelectionOpen
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const albumAssetSelectionStore = createAlbumAssetSelectionStore();
|
|
@ -34,7 +34,7 @@ function createAssetStore() {
|
||||||
assetGridState.set({
|
assetGridState.set({
|
||||||
viewportHeight,
|
viewportHeight,
|
||||||
viewportWidth,
|
viewportWidth,
|
||||||
timelineHeight: calculateViewportHeightByNumberOfAsset(data.totalCount, viewportWidth),
|
timelineHeight: 0,
|
||||||
buckets: data.buckets.map((d) => ({
|
buckets: data.buckets.map((d) => ({
|
||||||
bucketDate: d.timeBucket,
|
bucketDate: d.timeBucket,
|
||||||
bucketHeight: calculateViewportHeightByNumberOfAsset(d.count, viewportWidth),
|
bucketHeight: calculateViewportHeightByNumberOfAsset(d.count, viewportWidth),
|
||||||
|
@ -43,6 +43,12 @@ function createAssetStore() {
|
||||||
})),
|
})),
|
||||||
assets: []
|
assets: []
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Update timeline height based on calculated bucket height
|
||||||
|
assetGridState.update((state) => {
|
||||||
|
state.timelineHeight = lodash.sumBy(state.buckets, (d) => d.bucketHeight);
|
||||||
|
return state;
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const getAssetsByBucket = async (bucket: string) => {
|
const getAssetsByBucket = async (bucket: string) => {
|
||||||
|
@ -108,10 +114,19 @@ function createAssetStore() {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateBucketHeight = (bucket: string, height: number) => {
|
const updateBucketHeight = (bucket: string, actualBucketHeight: number) => {
|
||||||
assetGridState.update((state) => {
|
assetGridState.update((state) => {
|
||||||
const bucketIndex = state.buckets.findIndex((b) => b.bucketDate === bucket);
|
const bucketIndex = state.buckets.findIndex((b) => b.bucketDate === bucket);
|
||||||
state.buckets[bucketIndex].bucketHeight = height;
|
// Update timeline height based on the new bucket height
|
||||||
|
const estimateBucketHeight = state.buckets[bucketIndex].bucketHeight;
|
||||||
|
|
||||||
|
if (actualBucketHeight >= estimateBucketHeight) {
|
||||||
|
state.timelineHeight += actualBucketHeight - estimateBucketHeight;
|
||||||
|
} else {
|
||||||
|
state.timelineHeight -= estimateBucketHeight - actualBucketHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.buckets[bucketIndex].bucketHeight = actualBucketHeight;
|
||||||
return state;
|
return state;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -4,9 +4,10 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export function calculateViewportHeightByNumberOfAsset(assetCount: number, viewportWidth: number) {
|
export function calculateViewportHeightByNumberOfAsset(assetCount: number, viewportWidth: number) {
|
||||||
const thumbnailHeight = 235;
|
const thumbnailHeight = 237;
|
||||||
|
|
||||||
const unwrappedWidth = (3 / 2) * assetCount * thumbnailHeight * (7 / 10);
|
// const unwrappedWidth = (3 / 2) * assetCount * thumbnailHeight * (7 / 10);
|
||||||
|
const unwrappedWidth = assetCount * thumbnailHeight;
|
||||||
const rows = Math.ceil(unwrappedWidth / viewportWidth);
|
const rows = Math.ceil(unwrappedWidth / viewportWidth);
|
||||||
const height = rows * thumbnailHeight;
|
const height = rows * thumbnailHeight;
|
||||||
return height;
|
return height;
|
||||||
|
|
Loading…
Add table
Reference in a new issue