Files
immich/web/src/lib/components/timeline/Month.svelte
midzelis 3563d2c51c refactor(web): rename intersecting/actuallyIntersecting to nearby/intersecting
Change-Id: Id6f63247441fe290e7732e489f73e8c16a6a6964
2026-03-18 04:41:03 +00:00

120 lines
4.3 KiB
Svelte

<script lang="ts">
import AssetLayout from '$lib/components/timeline/AssetLayout.svelte';
import { DayGroup } from '$lib/managers/timeline-manager/day-group.svelte';
import type { MonthGroup } from '$lib/managers/timeline-manager/month-group.svelte';
import type { TimelineAsset } from '$lib/managers/timeline-manager/types';
import { assetsSnapshot, filterRenderable } from '$lib/managers/timeline-manager/utils.svelte';
import type { VirtualScrollManager } from '$lib/managers/VirtualScrollManager/VirtualScrollManager.svelte';
import type { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
import { uploadAssetsStore } from '$lib/stores/upload';
import type { CommonPosition } from '$lib/utils/layout-utils';
import { fromTimelinePlainDate, getDateLocaleString } from '$lib/utils/timeline-util';
import { Icon } from '@immich/ui';
import { mdiCheckCircle, mdiCircleOutline } from '@mdi/js';
import type { Snippet } from 'svelte';
type Props = {
thumbnail: Snippet<
[
{
asset: TimelineAsset;
position: CommonPosition;
dayGroup: DayGroup;
groupIndex: number;
intersecting: boolean;
},
]
>;
customThumbnailLayout?: Snippet<[TimelineAsset]>;
singleSelect: boolean;
assetInteraction: AssetInteraction;
monthGroup: MonthGroup;
manager: VirtualScrollManager;
onDayGroupSelect: (dayGroup: DayGroup, assets: TimelineAsset[]) => void;
};
let {
thumbnail: thumbnailWithGroup,
customThumbnailLayout,
singleSelect,
assetInteraction,
monthGroup,
manager,
onDayGroupSelect,
}: Props = $props();
let { isUploading } = uploadAssetsStore;
let hoveredDayGroup = $state<string | null>(null);
const transitionDuration = $derived(monthGroup.timelineManager.suspendTransitions && !$isUploading ? 0 : 150);
const getDayGroupFullDate = (dayGroup: DayGroup): string => {
const { month, year } = dayGroup.monthGroup.yearMonth;
const date = fromTimelinePlainDate({
year,
month,
day: dayGroup.day,
});
return getDateLocaleString(date);
};
</script>
{#each filterRenderable(monthGroup.dayGroups) as dayGroup, groupIndex (dayGroup.day)}
{@const isDayGroupSelected = assetInteraction.selectedGroup.has(dayGroup.groupTitle)}
<!-- svelte-ignore a11y_no_static_element_interactions -->
<section
class={[
{ 'transition-all': !monthGroup.timelineManager.suspendTransitions },
!monthGroup.timelineManager.suspendTransitions && `delay-${transitionDuration}`,
]}
data-group
style:position="absolute"
style:inset-inline-start={dayGroup.start + 'px'}
style:top={dayGroup.top + 'px'}
onmouseenter={() => (hoveredDayGroup = dayGroup.groupTitle)}
onmouseleave={() => (hoveredDayGroup = null)}
>
<!-- Day title -->
<div
class="flex pt-7 pb-5 max-md:pt-5 max-md:pb-3 h-6 place-items-center text-xs font-medium text-immich-fg dark:text-immich-dark-fg md:text-sm"
style:width={dayGroup.width + 'px'}
>
{#if !singleSelect}
<div
class="hover:cursor-pointer transition-all duration-200 ease-out overflow-hidden w-0"
class:w-8={hoveredDayGroup === dayGroup.groupTitle || assetInteraction.selectedGroup.has(dayGroup.groupTitle)}
onclick={() => onDayGroupSelect(dayGroup, assetsSnapshot(dayGroup.getAssets()))}
onkeydown={() => onDayGroupSelect(dayGroup, assetsSnapshot(dayGroup.getAssets()))}
>
{#if isDayGroupSelected}
<Icon icon={mdiCheckCircle} size="24" class="text-primary" />
{:else}
<Icon icon={mdiCircleOutline} size="24" class="text-light-500" />
{/if}
</div>
{/if}
<span class="w-full truncate first-letter:capitalize" title={getDayGroupFullDate(dayGroup)}>
{dayGroup.groupTitle}
</span>
</div>
<AssetLayout
{manager}
viewerAssets={dayGroup.viewerAssets}
height={dayGroup.height}
width={dayGroup.width}
{customThumbnailLayout}
>
{#snippet thumbnail({ asset, position, intersecting })}
{@render thumbnailWithGroup({ asset, position, dayGroup, groupIndex, intersecting })}
{/snippet}
</AssetLayout>
</section>
{/each}
<style>
section {
contain: layout paint style;
}
</style>