mirror of
https://github.com/immich-app/immich.git
synced 2025-07-13 20:38:46 +02:00
fix: responsive: timeline glitch and keyboard-accessible scrubber (#17556)
* fix: responsive: timeline glitch * lint * fix margin-right on mobile --------- Co-authored-by: Alex <alex.tran1502@gmail.com>
This commit is contained in:
parent
664c99278a
commit
5a51ad3622
8 changed files with 427 additions and 142 deletions
web/src/lib/components/photos-page
|
@ -78,13 +78,19 @@
|
|||
let scrubBucketPercent = $state(0);
|
||||
let scrubBucket: { bucketDate: string | undefined } | undefined = $state();
|
||||
let scrubOverallPercent: number = $state(0);
|
||||
let scrubberWidth = $state(0);
|
||||
|
||||
// 60 is the bottom spacer element at 60px
|
||||
let bottomSectionHeight = 60;
|
||||
let leadout = $state(false);
|
||||
|
||||
const maxMd = $derived(mobileDevice.maxMd);
|
||||
const usingMobileDevice = $derived(mobileDevice.pointerCoarse);
|
||||
|
||||
$effect(() => {
|
||||
assetStore.rowHeight = maxMd ? 100 : 235;
|
||||
});
|
||||
|
||||
const scrollTo = (top: number) => {
|
||||
element?.scrollTo({ top });
|
||||
showSkeleton = false;
|
||||
|
@ -162,7 +168,13 @@
|
|||
const updateIsScrolling = () => (assetStore.scrolling = true);
|
||||
// note: don't throttle, debounch, or otherwise do this function async - it causes flicker
|
||||
const updateSlidingWindow = () => assetStore.updateSlidingWindow(element?.scrollTop || 0);
|
||||
const compensateScrollCallback = (delta: number) => element?.scrollBy(0, delta);
|
||||
const compensateScrollCallback = ({ delta, top }: { delta?: number; top?: number }) => {
|
||||
if (delta) {
|
||||
element?.scrollBy(0, delta);
|
||||
} else if (top) {
|
||||
element?.scrollTo({ top });
|
||||
}
|
||||
};
|
||||
const topSectionResizeObserver: OnResizeCallback = ({ height }) => (assetStore.topSectionHeight = height);
|
||||
|
||||
onMount(() => {
|
||||
|
@ -267,10 +279,21 @@
|
|||
bucket = assetStore.buckets[i];
|
||||
bucketHeight = assetStore.buckets[i].bucketHeight;
|
||||
}
|
||||
|
||||
let next = top - bucketHeight * maxScrollPercent;
|
||||
if (next < 0) {
|
||||
// instead of checking for < 0, add a little wiggle room for subpixel resolution
|
||||
if (next < -1 && bucket) {
|
||||
scrubBucket = bucket;
|
||||
scrubBucketPercent = top / (bucketHeight * maxScrollPercent);
|
||||
|
||||
// allowing next to be at least 1 may cause percent to go negative, so ensure positive percentage
|
||||
scrubBucketPercent = Math.max(0, top / (bucketHeight * maxScrollPercent));
|
||||
|
||||
// compensate for lost precision/rouding errors advance to the next bucket, if present
|
||||
if (scrubBucketPercent > 0.9999 && i + 1 < bucketsLength - 1) {
|
||||
scrubBucket = assetStore.buckets[i + 1];
|
||||
scrubBucketPercent = 0;
|
||||
}
|
||||
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
|
@ -689,7 +712,6 @@
|
|||
|
||||
{#if assetStore.buckets.length > 0}
|
||||
<Scrubber
|
||||
invisible={showSkeleton}
|
||||
{assetStore}
|
||||
height={assetStore.viewportHeight}
|
||||
timelineTopOffset={assetStore.topSectionHeight}
|
||||
|
@ -699,6 +721,7 @@
|
|||
{scrubBucketPercent}
|
||||
{scrubBucket}
|
||||
{onScrub}
|
||||
bind:scrubberWidth
|
||||
onScrubKeyDown={(evt) => {
|
||||
evt.preventDefault();
|
||||
let amount = 50;
|
||||
|
@ -720,12 +743,8 @@
|
|||
<!-- Right margin MUST be equal to the width of immich-scrubbable-scrollbar -->
|
||||
<section
|
||||
id="asset-grid"
|
||||
class={[
|
||||
'scrollbar-hidden h-full overflow-y-auto outline-none',
|
||||
{ 'm-0': isEmpty },
|
||||
{ 'ml-0': !isEmpty },
|
||||
{ 'mr-[60px]': !isEmpty && !usingMobileDevice },
|
||||
]}
|
||||
class={['scrollbar-hidden h-full overflow-y-auto outline-none', { 'm-0': isEmpty }, { 'ml-0': !isEmpty }]}
|
||||
style:margin-right={(usingMobileDevice ? 0 : scrubberWidth) + 'px'}
|
||||
tabindex="-1"
|
||||
bind:clientHeight={assetStore.viewportHeight}
|
||||
bind:clientWidth={null, (v) => ((assetStore.viewportWidth = v), updateSlidingWindow())}
|
||||
|
@ -763,7 +782,7 @@
|
|||
style:transform={`translate3d(0,${absoluteHeight}px,0)`}
|
||||
style:width="100%"
|
||||
>
|
||||
<Skeleton height={bucket.bucketHeight} title={bucket.bucketDateFormatted} />
|
||||
<Skeleton height={bucket.bucketHeight - bucket.store.headerHeight} title={bucket.bucketDateFormatted} />
|
||||
</div>
|
||||
{:else if display}
|
||||
<div
|
||||
|
@ -788,7 +807,14 @@
|
|||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
<!-- <div class="h-[60px]" style:position="absolute" style:left="0" style:right="0" style:bottom="0"></div> -->
|
||||
<!-- spacer for leadout -->
|
||||
<div
|
||||
class="h-[60px]"
|
||||
style:position="absolute"
|
||||
style:left="0"
|
||||
style:right="0"
|
||||
style:transform={`translate3d(0,${assetStore.timelineHeight}px,0)`}
|
||||
></div>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue