mirror of
https://github.com/immich-app/immich.git
synced 2025-07-09 03:04:16 -04:00
Fix disappearing bucket, longpress, dynamic viewport sizes
This commit is contained in:
parent
d45a4a7386
commit
e33995d356
@ -99,7 +99,7 @@
|
|||||||
</header>
|
</header>
|
||||||
|
|
||||||
<main
|
<main
|
||||||
class="relative h-screen overflow-hidden bg-immich-bg px-6 max-md:pt-[var(--navbar-height-md)] pt-[var(--navbar-height)] dark:bg-immich-dark-bg"
|
class="relative h-dvh overflow-hidden bg-immich-bg px-6 max-md:pt-[var(--navbar-height-md)] pt-[var(--navbar-height)] dark:bg-immich-dark-bg"
|
||||||
>
|
>
|
||||||
<AssetGrid enableRouting={true} {album} {assetStore} {assetInteraction}>
|
<AssetGrid enableRouting={true} {album} {assetStore} {assetInteraction}>
|
||||||
<section class="pt-8 md:pt-24">
|
<section class="pt-8 md:pt-24">
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
import { fade } from 'svelte/transition';
|
import { fade } from 'svelte/transition';
|
||||||
import ImageThumbnail from './image-thumbnail.svelte';
|
import ImageThumbnail from './image-thumbnail.svelte';
|
||||||
import VideoThumbnail from './video-thumbnail.svelte';
|
import VideoThumbnail from './video-thumbnail.svelte';
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
asset: AssetResponseDto;
|
asset: AssetResponseDto;
|
||||||
@ -124,24 +125,59 @@
|
|||||||
mouseOver = false;
|
mouseOver = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let timer: ReturnType<typeof setTimeout>;
|
||||||
|
const clearLongPressTimer = () => clearTimeout(timer);
|
||||||
|
|
||||||
|
let startX: number = 0;
|
||||||
|
let startY: number = 0;
|
||||||
function longPress(element: HTMLElement, { onLongPress }: { onLongPress: () => void }) {
|
function longPress(element: HTMLElement, { onLongPress }: { onLongPress: () => void }) {
|
||||||
let timer: ReturnType<typeof setTimeout>;
|
let didPress = false;
|
||||||
const start = (event: TouchEvent) => {
|
const start = (evt: TouchEvent) => {
|
||||||
|
startX = evt.changedTouches[0].clientX;
|
||||||
|
startY = evt.changedTouches[0].clientY;
|
||||||
|
didPress = false;
|
||||||
timer = setTimeout(() => {
|
timer = setTimeout(() => {
|
||||||
onLongPress();
|
onLongPress();
|
||||||
event.preventDefault();
|
didPress = true;
|
||||||
}, 350);
|
}, 350);
|
||||||
};
|
};
|
||||||
const end = () => clearTimeout(timer);
|
const click = (e: MouseEvent) => {
|
||||||
element.addEventListener('touchstart', start);
|
if (!didPress) {
|
||||||
element.addEventListener('touchend', end);
|
return;
|
||||||
|
}
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
};
|
||||||
|
element.addEventListener('click', click);
|
||||||
|
element.addEventListener('touchstart', start, true);
|
||||||
|
element.addEventListener('touchend', clearLongPressTimer, true);
|
||||||
return {
|
return {
|
||||||
destroy: () => {
|
destroy: () => {
|
||||||
element.removeEventListener('touchstart', start);
|
element.removeEventListener('click', click);
|
||||||
element.removeEventListener('touchend', end);
|
element.removeEventListener('touchstart', start, true);
|
||||||
|
element.removeEventListener('touchend', clearLongPressTimer, true);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
function moveHandler(e: PointerEvent) {
|
||||||
|
var diffX = Math.abs(startX - e.clientX);
|
||||||
|
var diffY = Math.abs(startY - e.clientY);
|
||||||
|
if (diffX >= 10 || diffY >= 10) {
|
||||||
|
clearLongPressTimer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onMount(() => {
|
||||||
|
document.addEventListener('scroll', clearLongPressTimer, true);
|
||||||
|
document.addEventListener('wheel', clearLongPressTimer, true);
|
||||||
|
document.addEventListener('contextmenu', clearLongPressTimer, true);
|
||||||
|
document.addEventListener('pointermove', moveHandler, true);
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('scroll', clearLongPressTimer, true);
|
||||||
|
document.removeEventListener('wheel', clearLongPressTimer, true);
|
||||||
|
document.removeEventListener('contextmenu', clearLongPressTimer, true);
|
||||||
|
document.removeEventListener('pointermove', moveHandler, true);
|
||||||
|
};
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
let { title, children }: Props = $props();
|
let { title, children }: Props = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<section class="min-w-screen flex min-h-dvh items-center justify-center relative">
|
<section class="min-w-dvw flex min-h-dvh items-center justify-center relative">
|
||||||
<div class="absolute -z-10 w-full h-full flex place-items-center place-content-center">
|
<div class="absolute -z-10 w-full h-full flex place-items-center place-content-center">
|
||||||
<img
|
<img
|
||||||
src={immichLogo}
|
src={immichLogo}
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="h-screen w-screen">
|
<div class="h-dvh w-dvw">
|
||||||
<section class="bg-immich-bg dark:bg-immich-dark-bg">
|
<section class="bg-immich-bg dark:bg-immich-dark-bg">
|
||||||
<div class="flex place-items-center border-b px-6 py-4 dark:border-b-immich-dark-gray">
|
<div class="flex place-items-center border-b px-6 py-4 dark:border-b-immich-dark-gray">
|
||||||
<a class="flex place-items-center gap-2 hover:cursor-pointer" href="/photos">
|
<a class="flex place-items-center gap-2 hover:cursor-pointer" href="/photos">
|
||||||
|
@ -51,7 +51,7 @@
|
|||||||
</header>
|
</header>
|
||||||
<main
|
<main
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
class="relative grid h-screen grid-cols-[theme(spacing.0)_auto] overflow-hidden bg-immich-bg max-md:pt-[var(--navbar-height-md)] pt-[var(--navbar-height)] dark:bg-immich-dark-bg md:grid-cols-[theme(spacing.64)_auto]"
|
class="relative grid h-dvh grid-cols-[theme(spacing.0)_auto] overflow-hidden bg-immich-bg max-md:pt-[var(--navbar-height-md)] pt-[var(--navbar-height)] dark:bg-immich-dark-bg md:grid-cols-[theme(spacing.64)_auto]"
|
||||||
>
|
>
|
||||||
{#if sidebar}{@render sidebar()}{:else if admin}
|
{#if sidebar}{@render sidebar()}{:else if admin}
|
||||||
<AdminSideBar />
|
<AdminSideBar />
|
||||||
|
@ -277,7 +277,7 @@
|
|||||||
bucketHeight = assetStore.buckets[i].bucketHeight;
|
bucketHeight = assetStore.buckets[i].bucketHeight;
|
||||||
}
|
}
|
||||||
let next = top - bucketHeight * maxScrollPercent;
|
let next = top - bucketHeight * maxScrollPercent;
|
||||||
if (next < 0) {
|
if (next < 0 && bucket) {
|
||||||
scrubBucket = bucket;
|
scrubBucket = bucket;
|
||||||
scrubBucketPercent = top / (bucketHeight * maxScrollPercent);
|
scrubBucketPercent = top / (bucketHeight * maxScrollPercent);
|
||||||
found = true;
|
found = true;
|
||||||
@ -698,7 +698,6 @@
|
|||||||
|
|
||||||
{#if assetStore.buckets.length > 0}
|
{#if assetStore.buckets.length > 0}
|
||||||
<Scrubber
|
<Scrubber
|
||||||
invisible={showSkeleton}
|
|
||||||
{assetStore}
|
{assetStore}
|
||||||
height={assetStore.viewportHeight}
|
height={assetStore.viewportHeight}
|
||||||
timelineTopOffset={assetStore.topSectionHeight}
|
timelineTopOffset={assetStore.topSectionHeight}
|
||||||
@ -771,8 +770,9 @@
|
|||||||
style:position="absolute"
|
style:position="absolute"
|
||||||
style:transform={`translate3d(0,${absoluteHeight}px,0)`}
|
style:transform={`translate3d(0,${absoluteHeight}px,0)`}
|
||||||
style:width="100%"
|
style:width="100%"
|
||||||
|
style:padding-left="10px"
|
||||||
>
|
>
|
||||||
<Skeleton height={bucket.bucketHeight} title={bucket.bucketDateFormatted} />
|
<Skeleton height={bucket.bucketHeight - bucket.store.headerHeight} title={bucket.bucketDateFormatted} />
|
||||||
</div>
|
</div>
|
||||||
{:else if display}
|
{:else if display}
|
||||||
<div
|
<div
|
||||||
@ -797,7 +797,13 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
<!-- <div class="h-[60px]" style:position="absolute" style:left="0" style:right="0" style:bottom="0"></div> -->
|
<div
|
||||||
|
class="h-[60px]"
|
||||||
|
style:position="absolute"
|
||||||
|
style:left="0"
|
||||||
|
style:right="0"
|
||||||
|
style:transform={`translate3d(0,${assetStore.timelineHeight}px,0)`}
|
||||||
|
></div>
|
||||||
</section>
|
</section>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
@ -22,6 +22,11 @@
|
|||||||
background-repeat: repeat;
|
background-repeat: repeat;
|
||||||
background-size: 235px, 235px;
|
background-size: 235px, 235px;
|
||||||
}
|
}
|
||||||
|
@media (max-width: 850px) {
|
||||||
|
[data-skeleton] {
|
||||||
|
background-size: 100px, 100px;
|
||||||
|
}
|
||||||
|
}
|
||||||
:global(.dark) [data-skeleton] {
|
:global(.dark) [data-skeleton] {
|
||||||
background-image: url('/dark_skeleton.png');
|
background-image: url('/dark_skeleton.png');
|
||||||
}
|
}
|
||||||
|
@ -66,7 +66,7 @@
|
|||||||
aria-labelledby={ariaLabelledBy}
|
aria-labelledby={ariaLabelledBy}
|
||||||
bind:this={menuElement}
|
bind:this={menuElement}
|
||||||
class="{isVisible
|
class="{isVisible
|
||||||
? 'max-h-screen max-h-svh'
|
? 'max-h-dvh max-h-svh'
|
||||||
: 'max-h-0'} flex flex-col transition-all duration-[250ms] ease-in-out outline-none overflow-auto"
|
: 'max-h-0'} flex flex-col transition-all duration-[250ms] ease-in-out outline-none overflow-auto"
|
||||||
role="menu"
|
role="menu"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
|
@ -91,7 +91,7 @@
|
|||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<section class="fixed left-0 top-0 z-10 flex h-screen w-screen" {oncontextmenu} role="presentation">
|
<section class="fixed left-0 top-0 z-10 flex h-dvh w-dvw" {oncontextmenu} role="presentation">
|
||||||
<ContextMenu
|
<ContextMenu
|
||||||
{direction}
|
{direction}
|
||||||
{x}
|
{x}
|
||||||
|
@ -77,7 +77,7 @@
|
|||||||
role="presentation"
|
role="presentation"
|
||||||
in:fade={{ duration: 100 }}
|
in:fade={{ duration: 100 }}
|
||||||
out:fade={{ duration: 100 }}
|
out:fade={{ duration: 100 }}
|
||||||
class="fixed left-0 top-0 z-[9999] flex h-dvh w-screen place-content-center place-items-center bg-black/40"
|
class="fixed left-0 top-0 z-[9999] flex h-dvh w-dvw place-content-center place-items-center bg-black/40"
|
||||||
onkeydown={(event) => {
|
onkeydown={(event) => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
}}
|
}}
|
||||||
|
@ -58,7 +58,7 @@
|
|||||||
|
|
||||||
<section
|
<section
|
||||||
id="dashboard-navbar"
|
id="dashboard-navbar"
|
||||||
class="fixed z-[900] max-md:h-[var(--navbar-height-md)] h-[var(--navbar-height)] w-screen text-sm"
|
class="fixed z-[900] max-md:h-[var(--navbar-height-md)] h-[var(--navbar-height)] w-dvw text-sm"
|
||||||
>
|
>
|
||||||
<SkipLink text={$t('skip_to_content')} />
|
<SkipLink text={$t('skip_to_content')} />
|
||||||
<div
|
<div
|
||||||
|
@ -26,7 +26,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if showing}
|
{#if showing}
|
||||||
<div class="absolute left-0 top-0 z-[999999999] h-[3px] w-screen bg-white">
|
<div class="absolute left-0 top-0 z-[999999999] h-[3px] w-dvw bg-white">
|
||||||
<span class="absolute h-[3px] bg-immich-primary" style:width={`${$progress}%`}></span>
|
<span class="absolute h-[3px] bg-immich-primary" style:width={`${$progress}%`}></span>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -41,15 +41,33 @@
|
|||||||
|
|
||||||
let isHover = $state(false);
|
let isHover = $state(false);
|
||||||
let isDragging = $state(false);
|
let isDragging = $state(false);
|
||||||
|
let isHoverOnPaddingTop = $state(false);
|
||||||
|
let isHoverOnPaddingBottom = $state(false);
|
||||||
let hoverY = $state(0);
|
let hoverY = $state(0);
|
||||||
let clientY = 0;
|
let clientY = 0;
|
||||||
let windowHeight = $state(0);
|
let windowHeight = $state(0);
|
||||||
let scrollBar: HTMLElement | undefined = $state();
|
let scrollBar: HTMLElement | undefined = $state();
|
||||||
|
|
||||||
const toScrollY = (percent: number) => percent * (height - HOVER_DATE_HEIGHT * 2);
|
const toScrollY = (percent: number) => percent * (height - (PADDING_TOP + PADDING_BOTTOM));
|
||||||
const toTimelineY = (scrollY: number) => scrollY / (height - HOVER_DATE_HEIGHT * 2);
|
const toTimelineY = (scrollY: number) => scrollY / (height - (PADDING_TOP + PADDING_BOTTOM));
|
||||||
|
|
||||||
|
const usingMobileDevice = $derived(mobileDevice.pointerCoarse);
|
||||||
|
const width = $derived.by(() => {
|
||||||
|
if (isDragging) {
|
||||||
|
return '100vw';
|
||||||
|
}
|
||||||
|
if (usingMobileDevice) {
|
||||||
|
if (assetStore.scrolling) {
|
||||||
|
return '20px';
|
||||||
|
}
|
||||||
|
return '0px';
|
||||||
|
}
|
||||||
|
return '60px';
|
||||||
|
});
|
||||||
|
|
||||||
const HOVER_DATE_HEIGHT = 31.75;
|
const HOVER_DATE_HEIGHT = 31.75;
|
||||||
|
const PADDING_TOP = $derived(usingMobileDevice ? 25 : HOVER_DATE_HEIGHT);
|
||||||
|
const PADDING_BOTTOM = $derived(usingMobileDevice ? 25 : 10);
|
||||||
const MIN_YEAR_LABEL_DISTANCE = 16;
|
const MIN_YEAR_LABEL_DISTANCE = 16;
|
||||||
const MIN_DOT_DISTANCE = 8;
|
const MIN_DOT_DISTANCE = 8;
|
||||||
|
|
||||||
@ -83,7 +101,7 @@
|
|||||||
return offset - 2;
|
return offset - 2;
|
||||||
} else {
|
} else {
|
||||||
// 2px is the height of the indicator
|
// 2px is the height of the indicator
|
||||||
return scrubOverallPercent * (height - HOVER_DATE_HEIGHT * 2) - 2;
|
return scrubOverallPercent * (height - (PADDING_TOP + PADDING_BOTTOM)) - 2;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let scrollY = $derived(toScrollFromBucketPercentage(scrubBucket, scrubBucketPercent, scrubOverallPercent));
|
let scrollY = $derived(toScrollFromBucketPercentage(scrubBucket, scrubBucketPercent, scrubOverallPercent));
|
||||||
@ -146,7 +164,15 @@
|
|||||||
};
|
};
|
||||||
let activeSegment: HTMLElement | undefined = $state();
|
let activeSegment: HTMLElement | undefined = $state();
|
||||||
const segments = $derived(calculateSegments(assetStore.scrubberBuckets));
|
const segments = $derived(calculateSegments(assetStore.scrubberBuckets));
|
||||||
const hoverLabel = $derived(activeSegment?.dataset.label);
|
const hoverLabel = $derived.by(() => {
|
||||||
|
if (isHoverOnPaddingTop) {
|
||||||
|
return segments.at(0)?.dateFormatted;
|
||||||
|
}
|
||||||
|
if (isHoverOnPaddingBottom) {
|
||||||
|
return segments.at(-1)?.dateFormatted;
|
||||||
|
}
|
||||||
|
return activeSegment?.dataset.label;
|
||||||
|
});
|
||||||
const bucketDate = $derived(activeSegment?.dataset.timeSegmentBucketDate);
|
const bucketDate = $derived(activeSegment?.dataset.timeSegmentBucketDate);
|
||||||
const scrollHoverLabel = $derived.by(() => {
|
const scrollHoverLabel = $derived.by(() => {
|
||||||
const y = scrollY;
|
const y = scrollY;
|
||||||
@ -160,6 +186,62 @@
|
|||||||
return '';
|
return '';
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const findElement = (elements: Element[], ...ids: string[]) => {
|
||||||
|
if (ids.length === 0) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const result = elements.find((element) => {
|
||||||
|
if (element instanceof HTMLElement && element.dataset.id) {
|
||||||
|
return ids.includes(element.dataset.id);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
return result as HTMLElement | undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getActive = (x: number, y: number) => {
|
||||||
|
const elements = document.elementsFromPoint(x, y);
|
||||||
|
const element = findElement(elements, 'time-segment', 'lead-in', 'lead-out');
|
||||||
|
|
||||||
|
if (element) {
|
||||||
|
const segment = element as HTMLElement;
|
||||||
|
const sr = segment.getBoundingClientRect();
|
||||||
|
const sy = sr.y;
|
||||||
|
const relativeY = y - sy;
|
||||||
|
const bucketPercentY = relativeY / sr.height;
|
||||||
|
return {
|
||||||
|
isOnPaddingTop: false,
|
||||||
|
isOnPaddingBottom: false,
|
||||||
|
segment,
|
||||||
|
bucketPercentY,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if padding
|
||||||
|
const bar = findElement(elements, 'immich-scrubbable-scrollbar');
|
||||||
|
let isOnPaddingTop = false;
|
||||||
|
let isOnPaddingBottom = false;
|
||||||
|
|
||||||
|
if (bar) {
|
||||||
|
const segment = bar as HTMLElement;
|
||||||
|
const sr = segment.getBoundingClientRect();
|
||||||
|
|
||||||
|
if (y < sr.top + PADDING_TOP) {
|
||||||
|
isOnPaddingTop = true;
|
||||||
|
}
|
||||||
|
if (y > sr.bottom - PADDING_BOTTOM - 1) {
|
||||||
|
isOnPaddingBottom = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
isOnPaddingTop,
|
||||||
|
isOnPaddingBottom,
|
||||||
|
segment: undefined,
|
||||||
|
bucketPercentY: 0,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
const handleMouseEvent = (event: { clientY: number; isDragging?: boolean }) => {
|
const handleMouseEvent = (event: { clientY: number; isDragging?: boolean }) => {
|
||||||
const wasDragging = isDragging;
|
const wasDragging = isDragging;
|
||||||
|
|
||||||
@ -172,28 +254,14 @@
|
|||||||
|
|
||||||
const rect = scrollBar.getBoundingClientRect()!;
|
const rect = scrollBar.getBoundingClientRect()!;
|
||||||
const lower = 0;
|
const lower = 0;
|
||||||
const upper = rect?.height - HOVER_DATE_HEIGHT * 2;
|
const upper = rect?.height - (PADDING_TOP + PADDING_BOTTOM);
|
||||||
hoverY = clamp(clientY - rect?.top - HOVER_DATE_HEIGHT, lower, upper);
|
hoverY = clamp(clientY - rect?.top - PADDING_TOP, lower, upper);
|
||||||
const x = rect!.left + rect!.width / 2;
|
const x = rect!.left + rect!.width / 2;
|
||||||
const elems = document.elementsFromPoint(x, clientY);
|
// console.log('hoverY is', hoverY, clientY);
|
||||||
const segment = elems.find(({ id }) => id === 'time-segment');
|
const { segment, bucketPercentY, isOnPaddingTop, isOnPaddingBottom } = getActive(x, clientY);
|
||||||
let bucketPercentY = 0;
|
activeSegment = segment;
|
||||||
if (segment) {
|
isHoverOnPaddingTop = isOnPaddingTop;
|
||||||
activeSegment = segment as HTMLElement;
|
isHoverOnPaddingBottom = isOnPaddingBottom;
|
||||||
|
|
||||||
const sr = segment.getBoundingClientRect();
|
|
||||||
const sy = sr.y;
|
|
||||||
const relativeY = clientY - sy;
|
|
||||||
bucketPercentY = relativeY / sr.height;
|
|
||||||
} else {
|
|
||||||
const leadin = elems.find(({ id }) => id === 'lead-in');
|
|
||||||
if (leadin) {
|
|
||||||
activeSegment = leadin as HTMLElement;
|
|
||||||
} else {
|
|
||||||
activeSegment = undefined;
|
|
||||||
bucketPercentY = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const scrollPercent = toTimelineY(hoverY);
|
const scrollPercent = toTimelineY(hoverY);
|
||||||
if (wasDragging === false && isDragging) {
|
if (wasDragging === false && isDragging) {
|
||||||
@ -225,9 +293,8 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const elements = document.elementsFromPoint(touch.clientX, touch.clientY);
|
const elements = document.elementsFromPoint(touch.clientX, touch.clientY);
|
||||||
const isHoverScrollbar = elements.some(({ id }) => {
|
const isHoverScrollbar =
|
||||||
return id === 'immich-scrubbable-scrollbar' || id === 'time-label';
|
findElement(elements, 'immich-scrubbable-scrollbar', 'time-label', 'lead-in', 'lead-out') !== undefined;
|
||||||
});
|
|
||||||
|
|
||||||
isHover = isHoverScrollbar;
|
isHover = isHoverScrollbar;
|
||||||
|
|
||||||
@ -253,22 +320,25 @@
|
|||||||
handleMouseEvent({
|
handleMouseEvent({
|
||||||
clientY: touch.clientY,
|
clientY: touch.clientY,
|
||||||
});
|
});
|
||||||
event.preventDefault();
|
|
||||||
} else {
|
} else {
|
||||||
isHover = false;
|
isHover = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
const opts = {
|
document.addEventListener('touchmove', onTouchMove, true);
|
||||||
passive: false,
|
|
||||||
};
|
|
||||||
globalThis.addEventListener('touchmove', onTouchMove, opts);
|
|
||||||
return () => {
|
return () => {
|
||||||
globalThis.removeEventListener('touchmove', onTouchMove);
|
document.removeEventListener('touchmove', onTouchMove);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
document.addEventListener('touchstart', onTouchStart, true);
|
||||||
|
document.addEventListener('touchend', onTouchEnd, true);
|
||||||
|
return () => {
|
||||||
|
document.addEventListener('touchstart', onTouchStart, true);
|
||||||
|
document.addEventListener('touchend', onTouchEnd, true);
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
const usingMobileDevice = $derived(mobileDevice.pointerCoarse);
|
|
||||||
const width = $derived(isDragging ? '100vw' : usingMobileDevice ? '20px' : '60px');
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:window
|
<svelte:window
|
||||||
@ -276,9 +346,6 @@
|
|||||||
onmousemove={({ clientY }) => (isDragging || isHover) && handleMouseEvent({ clientY })}
|
onmousemove={({ clientY }) => (isDragging || isHover) && handleMouseEvent({ clientY })}
|
||||||
onmousedown={({ clientY }) => isHover && handleMouseEvent({ clientY, isDragging: true })}
|
onmousedown={({ clientY }) => isHover && handleMouseEvent({ clientY, isDragging: true })}
|
||||||
onmouseup={({ clientY }) => handleMouseEvent({ clientY, isDragging: false })}
|
onmouseup={({ clientY }) => handleMouseEvent({ clientY, isDragging: false })}
|
||||||
ontouchstart={onTouchStart}
|
|
||||||
ontouchend={onTouchEnd}
|
|
||||||
ontouchcancel={onTouchEnd}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@ -286,13 +353,13 @@
|
|||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
role="scrollbar"
|
role="scrollbar"
|
||||||
aria-controls="time-label"
|
aria-controls="time-label"
|
||||||
aria-valuenow={scrollY + HOVER_DATE_HEIGHT}
|
aria-valuenow={scrollY + PADDING_TOP}
|
||||||
aria-valuemax={toScrollY(100)}
|
aria-valuemax={toScrollY(100)}
|
||||||
aria-valuemin={toScrollY(0)}
|
aria-valuemin={toScrollY(0)}
|
||||||
id="immich-scrubbable-scrollbar"
|
data-id="immich-scrubbable-scrollbar"
|
||||||
class="absolute right-0 z-[1] select-none bg-immich-bg hover:cursor-row-resize"
|
class="absolute right-0 z-[1] select-none bg-immich-bg hover:cursor-row-resize"
|
||||||
style:padding-top={HOVER_DATE_HEIGHT + 'px'}
|
style:padding-top={PADDING_TOP + 'px'}
|
||||||
style:padding-bottom={HOVER_DATE_HEIGHT + 'px'}
|
style:padding-bottom={PADDING_BOTTOM + 'px'}
|
||||||
style:width
|
style:width
|
||||||
style:height={height + 'px'}
|
style:height={height + 'px'}
|
||||||
style:background-color={isDragging ? 'transparent' : 'transparent'}
|
style:background-color={isDragging ? 'transparent' : 'transparent'}
|
||||||
@ -319,7 +386,7 @@
|
|||||||
<div
|
<div
|
||||||
id="time-label"
|
id="time-label"
|
||||||
class="rounded-l-full w-[32px] pl-2 text-white bg-immich-primary dark:bg-gray-600 hover:cursor-pointer select-none"
|
class="rounded-l-full w-[32px] pl-2 text-white bg-immich-primary dark:bg-gray-600 hover:cursor-pointer select-none"
|
||||||
style:top="{scrollY + HOVER_DATE_HEIGHT - 25}px"
|
style:top="{PADDING_TOP + (scrollY - 50 / 2)}px"
|
||||||
style:height="50px"
|
style:height="50px"
|
||||||
style:right="0"
|
style:right="0"
|
||||||
style:position="absolute"
|
style:position="absolute"
|
||||||
@ -345,9 +412,9 @@
|
|||||||
{#if !usingMobileDevice && !isDragging}
|
{#if !usingMobileDevice && !isDragging}
|
||||||
<div
|
<div
|
||||||
class="absolute right-0 h-[2px] w-10 bg-immich-primary dark:bg-immich-dark-primary"
|
class="absolute right-0 h-[2px] w-10 bg-immich-primary dark:bg-immich-dark-primary"
|
||||||
style:top="{scrollY + HOVER_DATE_HEIGHT}px"
|
style:top="{scrollY + PADDING_TOP}px"
|
||||||
>
|
>
|
||||||
{#if assetStore.scrolling && scrollHoverLabel}
|
{#if assetStore.scrolling && scrollHoverLabel && !isHover}
|
||||||
<p
|
<p
|
||||||
transition:fade={{ duration: 200 }}
|
transition:fade={{ duration: 200 }}
|
||||||
class="truncate pointer-events-none absolute right-0 bottom-0 z-[100] min-w-20 max-w-64 w-fit rounded-tl-md border-b-2 border-immich-primary bg-immich-bg/80 py-1 px-1 text-sm font-medium shadow-[0_0_8px_rgba(0,0,0,0.25)] dark:border-immich-dark-primary dark:bg-immich-dark-gray/80 dark:text-immich-dark-fg"
|
class="truncate pointer-events-none absolute right-0 bottom-0 z-[100] min-w-20 max-w-64 w-fit rounded-tl-md border-b-2 border-immich-primary bg-immich-bg/80 py-1 px-1 text-sm font-medium shadow-[0_0_8px_rgba(0,0,0,0.25)] dark:border-immich-dark-primary dark:bg-immich-dark-gray/80 dark:text-immich-dark-fg"
|
||||||
@ -357,7 +424,13 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<div id="lead-in" class="relative" style:height={relativeTopOffset + 'px'} data-label={segments.at(0)?.dateFormatted}>
|
<div
|
||||||
|
class="relative z-10"
|
||||||
|
style:height={relativeTopOffset + 'px'}
|
||||||
|
data-id="lead-in"
|
||||||
|
data-time-segment-bucket-date={segments.at(0)?.date}
|
||||||
|
data-label={segments.at(0)?.dateFormatted}
|
||||||
|
>
|
||||||
{#if relativeTopOffset > 6}
|
{#if relativeTopOffset > 6}
|
||||||
<div class="absolute right-[0.75rem] h-[4px] w-[4px] rounded-full bg-gray-300"></div>
|
<div class="absolute right-[0.75rem] h-[4px] w-[4px] rounded-full bg-gray-300"></div>
|
||||||
{/if}
|
{/if}
|
||||||
@ -365,28 +438,26 @@
|
|||||||
<!-- Time Segment -->
|
<!-- Time Segment -->
|
||||||
{#each segments as segment (segment.date)}
|
{#each segments as segment (segment.date)}
|
||||||
<div
|
<div
|
||||||
id="time-segment"
|
|
||||||
class="relative"
|
class="relative"
|
||||||
|
data-id="time-segment"
|
||||||
data-time-segment-bucket-date={segment.date}
|
data-time-segment-bucket-date={segment.date}
|
||||||
data-label={segment.dateFormatted}
|
data-label={segment.dateFormatted}
|
||||||
style:height={segment.height + 'px'}
|
style:height={segment.height + 'px'}
|
||||||
>
|
>
|
||||||
{#if !usingMobileDevice && segment.hasLabel}
|
{#if !usingMobileDevice}
|
||||||
<div class="absolute right-[1.25rem] top-[-16px] z-10 text-[12px] dark:text-immich-dark-fg font-immich-mono">
|
{#if segment.hasLabel}
|
||||||
{segment.date.year}
|
<div class="absolute right-[1.25rem] top-[-16px] z-10 text-[12px] dark:text-immich-dark-fg font-immich-mono">
|
||||||
</div>
|
{segment.date.year}
|
||||||
{/if}
|
</div>
|
||||||
{#if !usingMobileDevice && segment.hasDot}
|
{/if}
|
||||||
<div class="absolute right-[0.75rem] bottom-0 h-[4px] w-[4px] rounded-full bg-gray-300"></div>
|
{#if segment.hasDot}
|
||||||
|
<div class="absolute right-[0.75rem] bottom-0 h-[4px] w-[4px] rounded-full bg-gray-300"></div>
|
||||||
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
<div id="lead-out" class="relative" style:height={relativeBottomOffset + 'px'}></div>
|
<div data-id="lead-out" class="relative" style:height={relativeBottomOffset + 'px'}></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
#immich-scrubbable-scrollbar,
|
|
||||||
#time-segment {
|
|
||||||
contain: layout size style;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
@ -30,10 +30,6 @@ const {
|
|||||||
TIMELINE: { INTERSECTION_EXPAND_TOP, INTERSECTION_EXPAND_BOTTOM },
|
TIMELINE: { INTERSECTION_EXPAND_TOP, INTERSECTION_EXPAND_BOTTOM },
|
||||||
} = TUNABLES;
|
} = TUNABLES;
|
||||||
|
|
||||||
const THUMBNAIL_HEIGHT = 235;
|
|
||||||
const GAP = 12;
|
|
||||||
const HEADER = 49; //(1.5rem)
|
|
||||||
|
|
||||||
type AssetApiGetTimeBucketsRequest = Parameters<typeof getTimeBuckets>[0];
|
type AssetApiGetTimeBucketsRequest = Parameters<typeof getTimeBuckets>[0];
|
||||||
export type AssetStoreOptions = Omit<AssetApiGetTimeBucketsRequest, 'size'> & {
|
export type AssetStoreOptions = Omit<AssetApiGetTimeBucketsRequest, 'size'> & {
|
||||||
timelineAlbumId?: string;
|
timelineAlbumId?: string;
|
||||||
@ -83,8 +79,8 @@ class IntersectingAsset {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const store = this.#group.bucket.store;
|
const store = this.#group.bucket.store;
|
||||||
const topWindow = store.visibleWindow.top - HEADER - INTERSECTION_EXPAND_TOP;
|
const topWindow = store.visibleWindow.top - store.headerHeight - INTERSECTION_EXPAND_TOP;
|
||||||
const bottomWindow = store.visibleWindow.bottom + HEADER + INTERSECTION_EXPAND_BOTTOM;
|
const bottomWindow = store.visibleWindow.bottom + store.headerHeight + INTERSECTION_EXPAND_BOTTOM;
|
||||||
const positionTop = this.#group.absoluteDateGroupTop + this.position.top;
|
const positionTop = this.#group.absoluteDateGroupTop + this.position.top;
|
||||||
const positionBottom = positionTop + this.position.height;
|
const positionBottom = positionTop + this.position.height;
|
||||||
|
|
||||||
@ -97,7 +93,7 @@ class IntersectingAsset {
|
|||||||
|
|
||||||
position: CommonPosition | undefined = $state();
|
position: CommonPosition | undefined = $state();
|
||||||
asset: AssetResponseDto | undefined = $state();
|
asset: AssetResponseDto | undefined = $state();
|
||||||
id: string = $derived.by(() => this.asset!.id);
|
id: string | undefined = $derived(this.asset?.id);
|
||||||
|
|
||||||
constructor(group: AssetDateGroup, asset: AssetResponseDto) {
|
constructor(group: AssetDateGroup, asset: AssetResponseDto) {
|
||||||
this.#group = group;
|
this.#group = group;
|
||||||
@ -244,6 +240,7 @@ export class AssetBucket {
|
|||||||
*/
|
*/
|
||||||
#bucketHeight: number = $state(0);
|
#bucketHeight: number = $state(0);
|
||||||
#top: number = $state(0);
|
#top: number = $state(0);
|
||||||
|
|
||||||
#initialCount: number = 0;
|
#initialCount: number = 0;
|
||||||
#sortOrder: AssetOrder = AssetOrder.Desc;
|
#sortOrder: AssetOrder = AssetOrder.Desc;
|
||||||
percent: number = $state(0);
|
percent: number = $state(0);
|
||||||
@ -284,6 +281,7 @@ export class AssetBucket {
|
|||||||
this.isLoaded = true;
|
this.isLoaded = true;
|
||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
|
this.dateGroups = [];
|
||||||
this.isLoaded = false;
|
this.isLoaded = false;
|
||||||
},
|
},
|
||||||
this.handleLoadError,
|
this.handleLoadError,
|
||||||
@ -449,19 +447,21 @@ export class AssetBucket {
|
|||||||
const { store, percent } = this;
|
const { store, percent } = this;
|
||||||
const index = store.buckets.indexOf(this);
|
const index = store.buckets.indexOf(this);
|
||||||
const bucketHeightDelta = height - this.#bucketHeight;
|
const bucketHeightDelta = height - this.#bucketHeight;
|
||||||
|
this.#bucketHeight = height;
|
||||||
const prevBucket = store.buckets[index - 1];
|
const prevBucket = store.buckets[index - 1];
|
||||||
if (prevBucket) {
|
if (prevBucket) {
|
||||||
this.#top = prevBucket.#top + prevBucket.#bucketHeight;
|
const newTop = prevBucket.#top + prevBucket.#bucketHeight;
|
||||||
}
|
if (this.#top !== newTop) {
|
||||||
if (bucketHeightDelta) {
|
this.#top = newTop;
|
||||||
let cursor = index + 1;
|
}
|
||||||
while (cursor < store.buckets.length) {
|
}
|
||||||
const nextBucket = this.store.buckets[cursor];
|
for (let cursor = index + 1; cursor < store.buckets.length; cursor++) {
|
||||||
nextBucket.#top += bucketHeightDelta;
|
const bucket = this.store.buckets[cursor];
|
||||||
cursor++;
|
const newTop = bucket.#top + bucketHeightDelta;
|
||||||
|
if (bucket.#top !== newTop) {
|
||||||
|
bucket.#top = newTop;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.#bucketHeight = height;
|
|
||||||
if (store.topIntersectingBucket) {
|
if (store.topIntersectingBucket) {
|
||||||
const currentIndex = store.buckets.indexOf(store.topIntersectingBucket);
|
const currentIndex = store.buckets.indexOf(store.topIntersectingBucket);
|
||||||
// if the bucket is 'before' the last intersecting bucket in the sliding window
|
// if the bucket is 'before' the last intersecting bucket in the sliding window
|
||||||
@ -470,9 +470,8 @@ export class AssetBucket {
|
|||||||
if (currentIndex > 0) {
|
if (currentIndex > 0) {
|
||||||
if (index < currentIndex) {
|
if (index < currentIndex) {
|
||||||
store.compensateScrollCallback?.({ delta: bucketHeightDelta });
|
store.compensateScrollCallback?.({ delta: bucketHeightDelta });
|
||||||
} else if (currentIndex == currentIndex) {
|
} else if (currentIndex == currentIndex && percent > 0) {
|
||||||
this.store.updateIntersections();
|
const top = this.top + height * percent;
|
||||||
const top = this.#top + height * percent;
|
|
||||||
store.compensateScrollCallback?.({ top });
|
store.compensateScrollCallback?.({ top });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -482,10 +481,7 @@ export class AssetBucket {
|
|||||||
return this.#bucketHeight;
|
return this.#bucketHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
set top(top: number) {
|
get top(): number {
|
||||||
this.#top = top;
|
|
||||||
}
|
|
||||||
get top() {
|
|
||||||
return this.#top + this.store.topSectionHeight;
|
return this.#top + this.store.topSectionHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -502,7 +498,7 @@ export class AssetBucket {
|
|||||||
for (const group of this.dateGroups) {
|
for (const group of this.dateGroups) {
|
||||||
const intersectingAsset = group.intersetingAssets.find((asset) => asset.id === assetId);
|
const intersectingAsset = group.intersetingAssets.find((asset) => asset.id === assetId);
|
||||||
if (intersectingAsset) {
|
if (intersectingAsset) {
|
||||||
return this.top + group.top + intersectingAsset.position!.top + HEADER;
|
return this.top + group.top + intersectingAsset.position!.top + this.store.headerHeight;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return -1;
|
return -1;
|
||||||
@ -593,13 +589,16 @@ export class AssetStore {
|
|||||||
|
|
||||||
// --- private
|
// --- private
|
||||||
static #INIT_OPTIONS = {};
|
static #INIT_OPTIONS = {};
|
||||||
#rowHeight = 235;
|
|
||||||
#viewportHeight = $state(0);
|
#viewportHeight = $state(0);
|
||||||
#viewportWidth = $state(0);
|
#viewportWidth = $state(0);
|
||||||
#scrollTop = $state(0);
|
#scrollTop = $state(0);
|
||||||
#pendingChanges: PendingChange[] = [];
|
#pendingChanges: PendingChange[] = [];
|
||||||
#unsubscribers: Unsubscriber[] = [];
|
#unsubscribers: Unsubscriber[] = [];
|
||||||
|
|
||||||
|
#rowHeight = 235;
|
||||||
|
#headerHeight = $state(49);
|
||||||
|
#gap = $state(12);
|
||||||
|
|
||||||
#options: AssetStoreOptions = AssetStore.#INIT_OPTIONS;
|
#options: AssetStoreOptions = AssetStore.#INIT_OPTIONS;
|
||||||
|
|
||||||
#scrolling = $state(false);
|
#scrolling = $state(false);
|
||||||
@ -609,6 +608,22 @@ export class AssetStore {
|
|||||||
|
|
||||||
constructor() {}
|
constructor() {}
|
||||||
|
|
||||||
|
set headerHeight(value) {
|
||||||
|
this.#headerHeight = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
get headerHeight() {
|
||||||
|
return this.#headerHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
set gap(value) {
|
||||||
|
this.#gap = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
get gap() {
|
||||||
|
return this.#gap;
|
||||||
|
}
|
||||||
|
|
||||||
set scrolling(value: boolean) {
|
set scrolling(value: boolean) {
|
||||||
this.#scrolling = value;
|
this.#scrolling = value;
|
||||||
if (value) {
|
if (value) {
|
||||||
@ -834,11 +849,6 @@ export class AssetStore {
|
|||||||
this.#updateViewportGeometry(false);
|
this.#updateViewportGeometry(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateLayoutOptions(options: AssetStoreLayoutOptions) {
|
|
||||||
this.#rowHeight = options.rowHeight;
|
|
||||||
this.refreshLayout();
|
|
||||||
}
|
|
||||||
|
|
||||||
async #init(options: AssetStoreOptions) {
|
async #init(options: AssetStoreOptions) {
|
||||||
// doing the following outside of the task reduces flickr
|
// doing the following outside of the task reduces flickr
|
||||||
this.isInitialized = false;
|
this.isInitialized = false;
|
||||||
@ -924,9 +934,9 @@ export class AssetStore {
|
|||||||
// optimize - if bucket already has data, no need to create estimates
|
// optimize - if bucket already has data, no need to create estimates
|
||||||
const viewportWidth = this.viewportWidth;
|
const viewportWidth = this.viewportWidth;
|
||||||
if (!bucket.isBucketHeightActual) {
|
if (!bucket.isBucketHeightActual) {
|
||||||
const unwrappedWidth = (3 / 2) * bucket.bucketCount * THUMBNAIL_HEIGHT * (7 / 10);
|
const unwrappedWidth = (3 / 2) * bucket.bucketCount * this.#rowHeight * (7 / 10);
|
||||||
const rows = Math.ceil(unwrappedWidth / viewportWidth);
|
const rows = Math.ceil(unwrappedWidth / viewportWidth);
|
||||||
const height = 51 + Math.max(1, rows) * THUMBNAIL_HEIGHT;
|
const height = 51 + Math.max(1, rows) * this.#rowHeight;
|
||||||
bucket.bucketHeight = height;
|
bucket.bucketHeight = height;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@ -952,7 +962,7 @@ export class AssetStore {
|
|||||||
assetGroup.layout(options);
|
assetGroup.layout(options);
|
||||||
rowSpaceRemaining[dateGroupRow] -= assetGroup.width - 1;
|
rowSpaceRemaining[dateGroupRow] -= assetGroup.width - 1;
|
||||||
if (dateGroupCol > 0) {
|
if (dateGroupCol > 0) {
|
||||||
rowSpaceRemaining[dateGroupRow] -= GAP;
|
rowSpaceRemaining[dateGroupRow] -= this.gap;
|
||||||
}
|
}
|
||||||
if (rowSpaceRemaining[dateGroupRow] >= 0) {
|
if (rowSpaceRemaining[dateGroupRow] >= 0) {
|
||||||
assetGroup.row = dateGroupRow;
|
assetGroup.row = dateGroupRow;
|
||||||
@ -962,7 +972,7 @@ export class AssetStore {
|
|||||||
|
|
||||||
dateGroupCol++;
|
dateGroupCol++;
|
||||||
|
|
||||||
cummulativeWidth += assetGroup.width + GAP;
|
cummulativeWidth += assetGroup.width + this.gap;
|
||||||
} else {
|
} else {
|
||||||
// starting a new row, we need to update the last col of the previous row
|
// starting a new row, we need to update the last col of the previous row
|
||||||
cummulativeWidth = 0;
|
cummulativeWidth = 0;
|
||||||
@ -976,10 +986,10 @@ export class AssetStore {
|
|||||||
dateGroupCol++;
|
dateGroupCol++;
|
||||||
cummulativeHeight += lastRowHeight;
|
cummulativeHeight += lastRowHeight;
|
||||||
assetGroup.top = cummulativeHeight;
|
assetGroup.top = cummulativeHeight;
|
||||||
cummulativeWidth += assetGroup.width + GAP;
|
cummulativeWidth += assetGroup.width + this.gap;
|
||||||
lastRow = assetGroup.row - 1;
|
lastRow = assetGroup.row - 1;
|
||||||
}
|
}
|
||||||
lastRowHeight = assetGroup.height + HEADER;
|
lastRowHeight = assetGroup.height + this.headerHeight;
|
||||||
}
|
}
|
||||||
if (lastRow === 0 || lastRow !== bucket.lastDateGroup?.row) {
|
if (lastRow === 0 || lastRow !== bucket.lastDateGroup?.row) {
|
||||||
cummulativeHeight += lastRowHeight;
|
cummulativeHeight += lastRowHeight;
|
||||||
|
@ -61,6 +61,9 @@ export class CancellableTask {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await f(cancelToken.signal);
|
await f(cancelToken.signal);
|
||||||
|
if (cancelToken.signal.aborted) {
|
||||||
|
return 'CANCELED';
|
||||||
|
}
|
||||||
this.#transitionToExecuted();
|
this.#transitionToExecuted();
|
||||||
return 'LOADED';
|
return 'LOADED';
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -605,7 +605,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<main
|
<main
|
||||||
class="relative h-screen overflow-hidden bg-immich-bg px-6 max-md:pt-[var(--navbar-height-md)] pt-[var(--navbar-height)] dark:bg-immich-dark-bg"
|
class="relative h-dvh overflow-hidden bg-immich-bg px-6 max-md:pt-[var(--navbar-height-md)] pt-[var(--navbar-height)] dark:bg-immich-dark-bg"
|
||||||
>
|
>
|
||||||
<AssetGrid
|
<AssetGrid
|
||||||
enableRouting={viewMode === AlbumPageViewMode.SELECT_ASSETS ? false : true}
|
enableRouting={viewMode === AlbumPageViewMode.SELECT_ASSETS ? false : true}
|
||||||
|
@ -34,7 +34,7 @@
|
|||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<main class="grid h-screen bg-immich-bg pt-18 dark:bg-immich-dark-bg">
|
<main class="grid h-dvh bg-immich-bg pt-18 dark:bg-immich-dark-bg">
|
||||||
{#if assetInteraction.selectionActive}
|
{#if assetInteraction.selectionActive}
|
||||||
<AssetSelectControlBar
|
<AssetSelectControlBar
|
||||||
assets={assetInteraction.selectedAssets}
|
assets={assetInteraction.selectedAssets}
|
||||||
|
@ -486,7 +486,7 @@
|
|||||||
</header>
|
</header>
|
||||||
|
|
||||||
<main
|
<main
|
||||||
class="relative h-screen overflow-hidden bg-immich-bg tall:ml-4 md:pt-[var(--navbar-height-md)] pt-[var(--navbar-height)] dark:bg-immich-dark-bg"
|
class="relative h-dvh overflow-hidden bg-immich-bg tall:ml-4 md:pt-[var(--navbar-height-md)] pt-[var(--navbar-height)] dark:bg-immich-dark-bg"
|
||||||
use:scrollMemoryClearer={{
|
use:scrollMemoryClearer={{
|
||||||
routeStartsWith: AppRoute.PEOPLE,
|
routeStartsWith: AppRoute.PEOPLE,
|
||||||
beforeClear: () => {
|
beforeClear: () => {
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
<title>Oops! Error - Immich</title>
|
<title>Oops! Error - Immich</title>
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
<section class="flex flex-col px-4 h-screen w-screen place-content-center place-items-center">
|
<section class="flex flex-col px-4 h-dvh w-dvw place-content-center place-items-center">
|
||||||
<h1 class="py-10 text-4xl text-immich-primary dark:text-immich-dark-primary">Page not found :/</h1>
|
<h1 class="py-10 text-4xl text-immich-primary dark:text-immich-dark-primary">Page not found :/</h1>
|
||||||
{#if page.error?.message}
|
{#if page.error?.message}
|
||||||
<h2 class="text-xl text-immich-fg dark:text-immich-dark-fg">{page.error.message}</h2>
|
<h2 class="text-xl text-immich-fg dark:text-immich-dark-fg">{page.error.message}</h2>
|
||||||
|
@ -70,7 +70,7 @@
|
|||||||
</ControlAppBar>
|
</ControlAppBar>
|
||||||
</header>
|
</header>
|
||||||
<main
|
<main
|
||||||
class="relative h-screen overflow-hidden bg-immich-bg px-6 max-md:pt-[var(--navbar-height-md)] pt-[var(--navbar-height)] dark:bg-immich-dark-bg sm:px-12 md:px-24 lg:px-40"
|
class="relative h-dvh overflow-hidden bg-immich-bg px-6 max-md:pt-[var(--navbar-height-md)] pt-[var(--navbar-height)] dark:bg-immich-dark-bg sm:px-12 md:px-24 lg:px-40"
|
||||||
>
|
>
|
||||||
<div class="flex flex-col items-center justify-center mt-20">
|
<div class="flex flex-col items-center justify-center mt-20">
|
||||||
<div class="text-2xl font-bold text-immich-primary dark:text-immich-dark-primary">{$t('password_required')}</div>
|
<div class="text-2xl font-bold text-immich-primary dark:text-immich-dark-primary">{$t('password_required')}</div>
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<section class="flex h-screen w-screen place-content-center place-items-center">
|
<section class="flex h-dvh w-dvw place-content-center place-items-center">
|
||||||
<div class="flex max-w-[350px] flex-col place-items-center gap-10 text-center">
|
<div class="flex max-w-[350px] flex-col place-items-center gap-10 text-center">
|
||||||
<div class="flex place-content-center place-items-center">
|
<div class="flex place-content-center place-items-center">
|
||||||
<Logo variant="icon" class="text-center" size="landing" />
|
<Logo variant="icon" class="text-center" size="landing" />
|
||||||
|
@ -56,7 +56,7 @@
|
|||||||
const SvelteComponent = $derived(onboardingSteps[index].component);
|
const SvelteComponent = $derived(onboardingSteps[index].component);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<section id="onboarding-page" class="min-w-screen flex min-h-screen p-4">
|
<section id="onboarding-page" class="min-w-dvw flex min-h-dvh p-4">
|
||||||
<div class="flex flex-col w-full">
|
<div class="flex flex-col w-full">
|
||||||
<div class="w-full bg-gray-300 dark:bg-gray-600 rounded-md h-2">
|
<div class="w-full bg-gray-300 dark:bg-gray-600 rounded-md h-2">
|
||||||
<div
|
<div
|
||||||
@ -64,7 +64,7 @@
|
|||||||
style="width: {(index / (onboardingSteps.length - 1)) * 100}%"
|
style="width: {(index / (onboardingSteps.length - 1)) * 100}%"
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full min-w-screen py-8 flex h-full place-content-center place-items-center">
|
<div class="w-full min-w-dvw py-8 flex h-full place-content-center place-items-center">
|
||||||
<SvelteComponent onDone={handleDoneClicked} onPrevious={handlePrevious} />
|
<SvelteComponent onDone={handleDoneClicked} onPrevious={handlePrevious} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user