diff --git a/web/src/app.css b/web/src/app.css index 06e9acfd3e..79baeff7d1 100644 --- a/web/src/app.css +++ b/web/src/app.css @@ -88,4 +88,11 @@ input:focus-visible { background: #4250afad; border-radius: 16px; } + + /* Hidden scrollbar */ + /* width */ + .scrollbar-hidden::-webkit-scrollbar { + display: none; + scrollbar-width: none; + } } diff --git a/web/src/lib/components/album-page/album-viewer.svelte b/web/src/lib/components/album-page/album-viewer.svelte index caef56f017..88928c804a 100644 --- a/web/src/lib/components/album-page/album-viewer.svelte +++ b/web/src/lib/components/album-page/album-viewer.svelte @@ -26,12 +26,16 @@ NotificationType } from '../shared-components/notification/notification'; import { browser } from '$app/env'; + import { albumAssetSelectionStore } from '$lib/stores/album-asset-selection.store'; export let album: AlbumResponseDto; + const { isAlbumAssetSelectionOpen } = albumAssetSelectionStore; let isShowAssetViewer = false; let isShowAssetSelection = false; + + $: $isAlbumAssetSelectionOpen = isShowAssetSelection; $: { if (browser) { if (isShowAssetSelection) { diff --git a/web/src/lib/components/photos-page/asset-grid.svelte b/web/src/lib/components/photos-page/asset-grid.svelte index aab3b32865..6327bd86a7 100644 --- a/web/src/lib/components/photos-page/asset-grid.svelte +++ b/web/src/lib/components/photos-page/asset-grid.svelte @@ -3,7 +3,7 @@ import IntersectionObserver from '../asset-viewer/intersection-observer.svelte'; 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 Portal from '../shared-components/portal/portal.svelte'; import AssetViewer from '../asset-viewer/asset-viewer.svelte'; @@ -12,16 +12,23 @@ isViewingAssetStoreState, viewingAssetStoreState } from '$lib/stores/asset-interaction.store'; + import Scrollbar, { + OnScrollbarClickDetail, + OnScrollbarDragDetail + } from '../shared-components/scrollbar/scrollbar.svelte'; + + export let isAlbumSelectionMode = false; let viewportHeight = 0; let viewportWidth = 0; let assetGridElement: HTMLElement; - export let isAlbumSelectionMode = false; + let bucketInfo: AssetCountByTimeBucketResponseDto; onMount(async () => { const { data: assetCountByTimebucket } = await api.assetApi.getAssetCountByTimeBucket({ timeGroup: TimeGroupEnum.Month }); + bucketInfo = assetCountByTimebucket; assetStore.setInitialState(viewportHeight, viewportWidth, assetCountByTimebucket); @@ -60,14 +67,46 @@ 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; + }; +{#if bucketInfo && viewportHeight && $assetGridState.timelineHeight > viewportHeight} + handleScrollbarClick(e.detail)} + on:onscrollbardrag={(e) => handleScrollbarDrag(e.detail)} + /> +{/if} +
{#if assetGridElement}
@@ -117,5 +156,6 @@ diff --git a/web/src/lib/components/shared-components/immich-thumbnail.svelte b/web/src/lib/components/shared-components/immich-thumbnail.svelte index 1140caa37a..d02b411a69 100644 --- a/web/src/lib/components/shared-components/immich-thumbnail.svelte +++ b/web/src/lib/components/shared-components/immich-thumbnail.svelte @@ -136,7 +136,7 @@
+ type OnScrollbarClick = { + onscrollbarclick: OnScrollbarClickDetail; + }; + + export type OnScrollbarClickDetail = { + scrollTo: number; + }; + + type OnScrollbarDrag = { + onscrollbardrag: OnScrollbarDragDetail; + }; + + export type OnScrollbarDragDetail = { + scrollTo: number; + }; + +
(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 !isDragging} +
+ {/if} {#each segmentScrollbarLayout as segment, index (segment.timeGroup)} {@const groupDate = new Date(segment.timeGroup)}
handleMouseMove(e, groupDate)} > {#if new Date(segmentScrollbarLayout[index - 1]?.timeGroup).getFullYear() !== groupDate.getFullYear()} -
- {groupDate.getFullYear()} -
- {:else if segment.count > 5} + {#if segment.height > 8} +
+ {groupDate.getFullYear()} +
+ {/if} + {:else if segment.height > 5}
diff --git a/web/src/lib/components/shared-components/side-bar/side-bar.svelte b/web/src/lib/components/shared-components/side-bar/side-bar.svelte index a496fa7c48..1610fdd7e2 100644 --- a/web/src/lib/components/shared-components/side-bar/side-bar.svelte +++ b/web/src/lib/components/shared-components/side-bar/side-bar.svelte @@ -18,6 +18,7 @@ let showSharingCount = false; let showAlbumsCount = false; + // let domCount = 0; onMount(async () => { if ($page.routeId == 'albums') { selectedAction = AppSideBarSelection.ALBUMS; @@ -26,6 +27,10 @@ } else if ($page.routeId == 'sharing') { selectedAction = AppSideBarSelection.SHARING; } + + // setInterval(() => { + // domCount = document.getElementsByTagName('*').length; + // }, 500); }); const getAssetCount = async () => { @@ -48,6 +53,7 @@
@@ -145,7 +151,7 @@ {:then data}
-

{data.owned} albums

+

{data.owned} Albums

{/await}
diff --git a/web/src/lib/stores/album-asset-selection.store.ts b/web/src/lib/stores/album-asset-selection.store.ts new file mode 100644 index 0000000000..18cf42eb2d --- /dev/null +++ b/web/src/lib/stores/album-asset-selection.store.ts @@ -0,0 +1,10 @@ +import { writable } from 'svelte/store'; + +function createAlbumAssetSelectionStore() { + const isAlbumAssetSelectionOpen = writable(false); + return { + isAlbumAssetSelectionOpen + }; +} + +export const albumAssetSelectionStore = createAlbumAssetSelectionStore(); diff --git a/web/src/lib/stores/assets.store.ts b/web/src/lib/stores/assets.store.ts index 9e755f38cc..f6e56bb480 100644 --- a/web/src/lib/stores/assets.store.ts +++ b/web/src/lib/stores/assets.store.ts @@ -34,7 +34,7 @@ function createAssetStore() { assetGridState.set({ viewportHeight, viewportWidth, - timelineHeight: calculateViewportHeightByNumberOfAsset(data.totalCount, viewportWidth), + timelineHeight: 0, buckets: data.buckets.map((d) => ({ bucketDate: d.timeBucket, bucketHeight: calculateViewportHeightByNumberOfAsset(d.count, viewportWidth), @@ -43,6 +43,12 @@ function createAssetStore() { })), 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) => { @@ -108,10 +114,19 @@ function createAssetStore() { }); }; - const updateBucketHeight = (bucket: string, height: number) => { + const updateBucketHeight = (bucket: string, actualBucketHeight: number) => { assetGridState.update((state) => { 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; }); }; diff --git a/web/src/lib/utils/viewport-utils.ts b/web/src/lib/utils/viewport-utils.ts index b084f6d72f..42293cc4a7 100644 --- a/web/src/lib/utils/viewport-utils.ts +++ b/web/src/lib/utils/viewport-utils.ts @@ -4,9 +4,10 @@ */ 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 height = rows * thumbnailHeight; return height;