diff --git a/web/src/lib/components/album-page/album-viewer.svelte b/web/src/lib/components/album-page/album-viewer.svelte index b8f2f5a8a7..3f8587a232 100644 --- a/web/src/lib/components/album-page/album-viewer.svelte +++ b/web/src/lib/components/album-page/album-viewer.svelte @@ -4,6 +4,7 @@ import AlbumMap from '$lib/components/album-page/album-map.svelte'; import SelectAllAssets from '$lib/components/photos-page/actions/select-all-assets.svelte'; import AssetSelectControlBar from '$lib/components/photos-page/asset-select-control-bar.svelte'; + import AssetGrid from '$lib/components/timeline/base-components/base-timeline.svelte'; import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte'; import { AssetInteraction } from '$lib/stores/asset-interaction.svelte'; import { assetViewingStore } from '$lib/stores/asset-viewing.store'; @@ -21,7 +22,6 @@ import ControlAppBar from '../shared-components/control-app-bar.svelte'; import ImmichLogoSmallLink from '../shared-components/immich-logo-small-link.svelte'; import ThemeButton from '../shared-components/theme-button.svelte'; - import AssetGrid from '../timeline-viewer/base-timeline.svelte'; import AlbumSummary from './album-summary.svelte'; interface Props { diff --git a/web/src/lib/components/asset-viewer/actions/delete-action.svelte b/web/src/lib/components/asset-viewer/actions/delete-action.svelte index 25f439246e..0badc97c87 100644 --- a/web/src/lib/components/asset-viewer/actions/delete-action.svelte +++ b/web/src/lib/components/asset-viewer/actions/delete-action.svelte @@ -5,7 +5,8 @@ notificationController, } from '$lib/components/shared-components/notification/notification'; import Portal from '$lib/components/shared-components/portal/portal.svelte'; - import DeleteAssetDialog from '$lib/components/timeline-viewer/actions/delete-asset-dialog.svelte'; + + import DeleteAssetDialog from '$lib/components/timeline/actions/delete-asset-dialog.svelte'; import { AssetAction } from '$lib/constants'; import { showDeleteModal } from '$lib/stores/preferences.store'; import { featureFlags } from '$lib/stores/server-config.store'; diff --git a/web/src/lib/components/photos-page/actions/delete-assets.svelte b/web/src/lib/components/photos-page/actions/delete-assets.svelte index 2583844522..a74d24b5a0 100644 --- a/web/src/lib/components/photos-page/actions/delete-assets.svelte +++ b/web/src/lib/components/photos-page/actions/delete-assets.svelte @@ -5,7 +5,7 @@ import { mdiDeleteForeverOutline, mdiDeleteOutline, mdiTimerSand } from '@mdi/js'; import { t } from 'svelte-i18n'; import MenuOption from '../../shared-components/context-menu/menu-option.svelte'; - import DeleteAssetDialog from '../../timeline-viewer/actions/delete-asset-dialog.svelte'; + import DeleteAssetDialog from '../../timeline/actions/delete-asset-dialog.svelte'; import { getAssetControlContext } from '../asset-select-control-bar.svelte'; interface Props { diff --git a/web/src/lib/components/photos-page/asset-date-group.svelte b/web/src/lib/components/photos-page/asset-date-group.svelte deleted file mode 100644 index 93eaa9e65f..0000000000 --- a/web/src/lib/components/photos-page/asset-date-group.svelte +++ /dev/null @@ -1,264 +0,0 @@ - - - - -{#each filterIntersecting(monthGroup.dayGroups) as dayGroup, groupIndex (dayGroup.day)} - {@const absoluteWidth = dayGroup.left} - - -
{ - isMouseOverGroup = true; - assetMouseEventHandler(dayGroup.groupTitle, null); - }} - onmouseleave={() => { - isMouseOverGroup = false; - assetMouseEventHandler(dayGroup.groupTitle, null); - }} - > - -
- {#if !singleSelect && ((hoveredDayGroup === dayGroup.groupTitle && isMouseOverGroup) || assetInteraction.selectedGroup.has(dayGroup.groupTitle))} -
actionLib.onDayGroupSelect(dayGroup, assetsSnapshot(dayGroup.getAssets()))} - onkeydown={() => actionLib.onDayGroupSelect(dayGroup, assetsSnapshot(dayGroup.getAssets()))} - > - {#if assetInteraction.selectedGroup.has(dayGroup.groupTitle)} - - {:else} - - {/if} -
- {/if} - - - {dayGroup.groupTitle} - -
- - -
- {#each filterIntersecting(dayGroup.viewerAssets) as viewerAsset (viewerAsset.id)} - {@const position = viewerAsset.position!} - {@const asset = viewerAsset.asset!} - - - -
- { - if (typeof onThumbnailClick === 'function') { - onThumbnailClick(asset, timelineManager, dayGroup, _onClick); - } else { - _onClick(timelineManager, dayGroup.getAssets(), dayGroup.groupTitle, asset); - } - }} - onSelect={(asset) => assetSelectHandler(timelineManager, asset, dayGroup.getAssets(), dayGroup.groupTitle)} - onMouseEvent={() => assetMouseEventHandler(dayGroup.groupTitle, assetSnapshot(asset))} - selected={assetInteraction.hasSelectedAsset(asset.id) || - dayGroup.monthGroup.timelineManager.albumAssets.has(asset.id)} - selectionCandidate={assetInteraction.hasSelectionCandidate(asset.id)} - disabled={dayGroup.monthGroup.timelineManager.albumAssets.has(asset.id)} - thumbnailWidth={position.width} - thumbnailHeight={position.height} - /> - {#if customLayout} - {@render customLayout(asset)} - {/if} -
- - {/each} -
-
-{/each} - - diff --git a/web/src/lib/components/photos-page/asset-grid.svelte b/web/src/lib/components/photos-page/asset-grid.svelte deleted file mode 100644 index 9598b12479..0000000000 --- a/web/src/lib/components/photos-page/asset-grid.svelte +++ /dev/null @@ -1,226 +0,0 @@ - - - - {#snippet header(handleScrollTop)} - {#if timelineManager.months.length > 0} - onScrub({ ...args, handleScrollTop })} - bind:scrubberWidth - /> - {/if} - {/snippet} - diff --git a/web/src/lib/components/shared-components/gallery-viewer/gallery-viewer.svelte b/web/src/lib/components/shared-components/gallery-viewer/gallery-viewer.svelte index a3c741cee9..df9ce18224 100644 --- a/web/src/lib/components/shared-components/gallery-viewer/gallery-viewer.svelte +++ b/web/src/lib/components/shared-components/gallery-viewer/gallery-viewer.svelte @@ -23,7 +23,8 @@ import { debounce } from 'lodash-es'; import { t } from 'svelte-i18n'; import AssetViewer from '../../asset-viewer/asset-viewer.svelte'; - import DeleteAssetDialog from '../../timeline-viewer/actions/delete-asset-dialog.svelte'; + + import DeleteAssetDialog from '$lib/components/timeline/actions/delete-asset-dialog.svelte'; import Portal from '../portal/portal.svelte'; interface Props { diff --git a/web/src/lib/components/timeline-viewer/timeline-viewer.svelte b/web/src/lib/components/timeline-viewer/timeline-viewer.svelte index 51b8c437c1..ccf601a2fe 100644 --- a/web/src/lib/components/timeline-viewer/timeline-viewer.svelte +++ b/web/src/lib/components/timeline-viewer/timeline-viewer.svelte @@ -2,10 +2,10 @@ import { afterNavigate, beforeNavigate } from '$app/navigation'; import { page } from '$app/stores'; import { resizeObserver, type OnResizeCallback } from '$lib/actions/resize-observer'; - import AssetGridActions from '$lib/components/timeline-viewer/actions/timeline-keyboard-actions.svelte'; - import Skeleton from '$lib/components/timeline-viewer/skeleton.svelte'; - import TimelineAssetViewer from '$lib/components/timeline-viewer/timeline-asset-viewer.svelte'; - import SelectableTimelineDay from '$lib/components/timeline-viewer/timeline-day/selectable-timeline-day.svelte'; + import AssetGridActions from '$lib/components/timeline/actions/timeline-keyboard-actions.svelte'; + import Skeleton from '$lib/components/timeline/base-components/skeleton.svelte'; + import SelectableTimelineDay from '$lib/components/timeline/internal-components/selectable-timeline-day.svelte'; + import TimelineAssetViewer from '$lib/components/timeline/internal-components/timeline-asset-viewer.svelte'; import { AssetAction } from '$lib/constants'; import type { DayGroup } from '$lib/managers/timeline-manager/day-group.svelte'; import type { MonthGroup } from '$lib/managers/timeline-manager/month-group.svelte'; @@ -21,7 +21,7 @@ import Portal from '../shared-components/portal/portal.svelte'; interface Props { - customLayout?: Snippet<[TimelineAsset]>; + customThumbnailLayout?: Snippet<[TimelineAsset]>; isSelectionMode?: boolean; singleSelect?: boolean; /** `true` if this asset grid is responds to navigation events; if `true`, then look at the @@ -52,7 +52,7 @@ } let { - customLayout, + customThumbnailLayout, isSelectionMode = false, singleSelect = false, enableRouting, @@ -322,7 +322,7 @@ style:width="100%" > void; + onEscape?: () => void; scrollToAsset: (asset: TimelineAsset) => boolean; } @@ -82,7 +81,7 @@ updateStackedAssetInTimeline(timelineManager, result); - onEscape(); + onEscape?.(); }; const toggleArchive = async () => { @@ -160,7 +159,6 @@ } const shortcuts: ShortcutOptions[] = [ - { shortcut: { key: 'Escape' }, onShortcut: onEscape }, { shortcut: { key: '?', shift: true }, onShortcut: handleOpenShortcutModal }, { shortcut: { key: '/' }, onShortcut: () => goto(AppRoute.EXPLORE) }, { shortcut: { key: 'A', ctrl: true }, onShortcut: () => selectAllAssets(timelineManager, assetInteraction) }, @@ -174,6 +172,9 @@ { shortcut: { key: 'Y', shift: true }, onShortcut: () => setFocusTo('later', 'year') }, { shortcut: { key: 'G' }, onShortcut: () => (isShowSelectDate = true) }, ]; + if (onEscape) { + shortcuts.push({ shortcut: { key: 'Escape' }, onShortcut: onEscape }); + } if (assetInteraction.selectionActive) { shortcuts.push( diff --git a/web/src/lib/components/timeline/base-components/base-timeline-viewer.svelte b/web/src/lib/components/timeline/base-components/base-timeline-viewer.svelte new file mode 100644 index 0000000000..ee609a1858 --- /dev/null +++ b/web/src/lib/components/timeline/base-components/base-timeline-viewer.svelte @@ -0,0 +1,373 @@ + + + + +{@render header?.(scrollTop)} + + +
((timelineManager.viewportWidth = v), updateSlidingWindow())} + bind:this={element} + onscroll={() => (handleTimelineScroll(), updateSlidingWindow(), updateIsScrolling())} +> +
+
+ {@render children?.()} + {#if isEmpty} + + {@render empty?.()} + {/if} +
+ + {#each timelineManager.months as monthGroup (monthGroup.viewId)} + {@const display = monthGroup.intersecting} + {@const absoluteHeight = monthGroup.top} + + {#if !monthGroup.isLoaded} +
+ +
+ {:else if display} +
+ +
+ {/if} + {/each} + +
+
+
+ + + {#if $showAssetViewer} + + {/if} + + + diff --git a/web/src/lib/components/timeline-viewer/base-timeline.svelte b/web/src/lib/components/timeline/base-components/base-timeline.svelte similarity index 92% rename from web/src/lib/components/timeline-viewer/base-timeline.svelte rename to web/src/lib/components/timeline/base-components/base-timeline.svelte index 4dc932dd80..c7d348174f 100644 --- a/web/src/lib/components/timeline-viewer/base-timeline.svelte +++ b/web/src/lib/components/timeline/base-components/base-timeline.svelte @@ -1,16 +1,19 @@ ; isSelectionMode: boolean; singleSelect: boolean; withStacked: boolean; @@ -25,8 +26,6 @@ monthGroup: MonthGroup; timelineManager: TimelineManager; - customLayout?: Snippet<[TimelineAsset]>; - onScrollCompensation: (compensation: { heightDelta?: number; scrollTop?: number }) => void; onHover: (dayGroup: DayGroup, asset: TimelineAsset) => void; @@ -42,14 +41,13 @@ } let { + customThumbnailLayout, + singleSelect, withStacked, showArchiveIcon, monthGroup, timelineManager, - - customLayout, - onScrollCompensation, onHover, @@ -165,8 +163,8 @@ thumbnailWidth={position.width} thumbnailHeight={position.height} /> - {#if customLayout} - {@render customLayout(asset)} + {#if customThumbnailLayout} + {@render customThumbnailLayout(asset)} {/if} {/each} diff --git a/web/src/lib/components/timeline-viewer/timeline-day/selectable-timeline-day.svelte b/web/src/lib/components/timeline/internal-components/selectable-timeline-day.svelte similarity index 97% rename from web/src/lib/components/timeline-viewer/timeline-day/selectable-timeline-day.svelte rename to web/src/lib/components/timeline/internal-components/selectable-timeline-day.svelte index 29b6e2076a..2b13224695 100644 --- a/web/src/lib/components/timeline-viewer/timeline-day/selectable-timeline-day.svelte +++ b/web/src/lib/components/timeline/internal-components/selectable-timeline-day.svelte @@ -7,7 +7,7 @@ import { uploadAssetsStore } from '$lib/stores/upload'; import { navigate } from '$lib/utils/navigation'; - import TimelineDay from '$lib/components/timeline-viewer/timeline-day/timeline-day.svelte'; + import TimelineDay from '$lib/components/timeline/base-components/timeline-day.svelte'; import { DayGroup } from '$lib/managers/timeline-manager/day-group.svelte'; import { assetsSnapshot } from '$lib/managers/timeline-manager/utils.svelte'; import { searchStore } from '$lib/stores/search.svelte'; @@ -24,10 +24,10 @@ timelineManager: TimelineManager; assetInteraction: AssetInteraction; - customLayout?: Snippet<[TimelineAsset]>; + customThumbnailLayout?: Snippet<[TimelineAsset]>; onAssetOpen?: (dayGroup: DayGroup, asset: TimelineAsset, defaultAssetOpen: () => void) => void; - onSelect: (asset: TimelineAsset) => void; + onSelect?: (asset: TimelineAsset) => void; onScrollCompensation: (compensation: { heightDelta?: number; scrollTop?: number }) => void; onScrollToTop: () => void; } @@ -39,7 +39,7 @@ showArchiveIcon, monthGroup = $bindable(), assetInteraction, - customLayout, + customThumbnailLayout, timelineManager, onAssetOpen, onSelect, @@ -164,7 +164,7 @@ if (!asset) { return; } - onSelect(asset); + onSelect?.(asset); if (singleSelect) { onScrollToTop(); @@ -263,7 +263,7 @@ -{#await import('../asset-viewer/asset-viewer.svelte') then { default: AssetViewer }} +{#await import('../../asset-viewer/asset-viewer.svelte') then { default: AssetViewer }} import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte'; - import AssetGrid from '$lib/components/photos-page/asset-grid.svelte'; import ChangeLocation from '$lib/components/shared-components/change-location.svelte'; import EmptyPlaceholder from '$lib/components/shared-components/empty-placeholder.svelte'; + import AssetGrid from '$lib/components/timeline/base-components/base-timeline.svelte'; import { AssetAction } from '$lib/constants'; import { authManager } from '$lib/managers/auth-manager.svelte'; import type { DayGroup } from '$lib/managers/timeline-manager/day-group.svelte'; @@ -185,7 +185,7 @@ withStacked onAssetOpen={handleOnAssetOpen} > - {#snippet customLayout(asset: TimelineAsset)} + {#snippet customThumbnailLayout(asset: TimelineAsset)} {#if hasGps(asset)}
{asset.city || $t('gps')}