refactor: zoom support
This commit is contained in:
@@ -1,25 +0,0 @@
|
|||||||
import { photoZoomState } from '$lib/stores/zoom-image.store';
|
|
||||||
import { useZoomImageWheel } from '@zoom-image/svelte';
|
|
||||||
import { get } from 'svelte/store';
|
|
||||||
|
|
||||||
export const zoomImageAction = (node: HTMLElement) => {
|
|
||||||
const { createZoomImage, zoomImageState, setZoomImageState } = useZoomImageWheel();
|
|
||||||
|
|
||||||
createZoomImage(node, {
|
|
||||||
maxZoom: 10,
|
|
||||||
});
|
|
||||||
|
|
||||||
const state = get(photoZoomState);
|
|
||||||
if (state) {
|
|
||||||
setZoomImageState(state);
|
|
||||||
}
|
|
||||||
|
|
||||||
const unsubscribes = [photoZoomState.subscribe(setZoomImageState), zoomImageState.subscribe(photoZoomState.set)];
|
|
||||||
return {
|
|
||||||
destroy() {
|
|
||||||
for (const unsubscribe of unsubscribes) {
|
|
||||||
unsubscribe();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { AssetManager } from '$lib/managers/asset-manager.svelte';
|
import type { AssetManager } from '$lib/managers/asset-manager/asset-manager.svelte';
|
||||||
import { modalManager } from '$lib/managers/modal-manager.svelte';
|
import { modalManager } from '$lib/managers/modal-manager.svelte';
|
||||||
import MapModal from '$lib/modals/MapModal.svelte';
|
import MapModal from '$lib/modals/MapModal.svelte';
|
||||||
import { navigate } from '$lib/utils/navigation';
|
import { navigate } from '$lib/utils/navigation';
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
import AlbumMap from '$lib/components/album-page/album-map.svelte';
|
import AlbumMap from '$lib/components/album-page/album-map.svelte';
|
||||||
import SelectAllAssets from '$lib/components/photos-page/actions/select-all-assets.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 AssetSelectControlBar from '$lib/components/photos-page/asset-select-control-bar.svelte';
|
||||||
import type { AssetManager } from '$lib/managers/asset-manager.svelte';
|
import type { AssetManager } from '$lib/managers/asset-manager/asset-manager.svelte';
|
||||||
import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
|
import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
|
||||||
import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
|
import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
|
||||||
import { dragAndDropFilesStore } from '$lib/stores/drag-and-drop-files.store';
|
import { dragAndDropFilesStore } from '$lib/stores/drag-and-drop-files.store';
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { shortcut } from '$lib/actions/shortcut';
|
import { shortcut } from '$lib/actions/shortcut';
|
||||||
import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
|
import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
|
||||||
import type { AssetManager } from '$lib/managers/asset-manager.svelte';
|
import type { AssetManager } from '$lib/managers/asset-manager/asset-manager.svelte';
|
||||||
import { downloadFile } from '$lib/utils/asset-utils';
|
import { downloadFile } from '$lib/utils/asset-utils';
|
||||||
import { IconButton } from '@immich/ui';
|
import { IconButton } from '@immich/ui';
|
||||||
import { mdiFolderDownloadOutline } from '@mdi/js';
|
import { mdiFolderDownloadOutline } from '@mdi/js';
|
||||||
|
|||||||
@@ -21,9 +21,8 @@
|
|||||||
import ButtonContextMenu from '$lib/components/shared-components/context-menu/button-context-menu.svelte';
|
import ButtonContextMenu from '$lib/components/shared-components/context-menu/button-context-menu.svelte';
|
||||||
import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
|
import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
|
||||||
import { AppRoute } from '$lib/constants';
|
import { AppRoute } from '$lib/constants';
|
||||||
import type { AssetManager } from '$lib/managers/asset-manager.svelte';
|
import type { AssetManager } from '$lib/managers/asset-manager/asset-manager.svelte';
|
||||||
import { user } from '$lib/stores/user.store';
|
import { user } from '$lib/stores/user.store';
|
||||||
import { photoZoomState } from '$lib/stores/zoom-image.store';
|
|
||||||
import { getAssetJobName, getSharedLink } from '$lib/utils';
|
import { getAssetJobName, getSharedLink } from '$lib/utils';
|
||||||
import { canCopyImageToClipboard } from '$lib/utils/asset-utils';
|
import { canCopyImageToClipboard } from '$lib/utils/asset-utils';
|
||||||
import { openFileUploadDialog } from '$lib/utils/file-uploader';
|
import { openFileUploadDialog } from '$lib/utils/file-uploader';
|
||||||
@@ -93,7 +92,8 @@
|
|||||||
motionPhoto,
|
motionPhoto,
|
||||||
}: Props = $props();
|
}: Props = $props();
|
||||||
|
|
||||||
let asset = $derived(assetManager.asset);
|
let asset = $derived(assetManager.asset!);
|
||||||
|
let zoomImageState = $derived(assetManager.zoomImageState);
|
||||||
|
|
||||||
const sharedLink = getSharedLink();
|
const sharedLink = getSharedLink();
|
||||||
let isOwner = $derived($user && asset.ownerId === $user?.id);
|
let isOwner = $derived($user && asset.ownerId === $user?.id);
|
||||||
@@ -143,7 +143,7 @@
|
|||||||
color="secondary"
|
color="secondary"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
shape="round"
|
shape="round"
|
||||||
icon={$photoZoomState && $photoZoomState.currentZoom > 1 ? mdiMagnifyMinusOutline : mdiMagnifyPlusOutline}
|
icon={zoomImageState && zoomImageState.currentZoom > 1 ? mdiMagnifyMinusOutline : mdiMagnifyPlusOutline}
|
||||||
aria-label={$t('zoom_image')}
|
aria-label={$t('zoom_image')}
|
||||||
onclick={onZoomImage}
|
onclick={onZoomImage}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
import AssetViewerNavBar from '$lib/components/asset-viewer/asset-viewer-nav-bar.svelte';
|
import AssetViewerNavBar from '$lib/components/asset-viewer/asset-viewer-nav-bar.svelte';
|
||||||
import { AssetAction, ProjectionType } from '$lib/constants';
|
import { AssetAction, ProjectionType } from '$lib/constants';
|
||||||
import { activityManager } from '$lib/managers/activity-manager.svelte';
|
import { activityManager } from '$lib/managers/activity-manager.svelte';
|
||||||
import { AssetManager } from '$lib/managers/asset-manager.svelte';
|
import { AssetManager } from '$lib/managers/asset-manager/asset-manager.svelte';
|
||||||
import { authManager } from '$lib/managers/auth-manager.svelte';
|
import { authManager } from '$lib/managers/auth-manager.svelte';
|
||||||
import { closeEditorCofirm } from '$lib/stores/asset-editor.store';
|
import { closeEditorCofirm } from '$lib/stores/asset-editor.store';
|
||||||
import { isShowDetail } from '$lib/stores/preferences.store';
|
import { isShowDetail } from '$lib/stores/preferences.store';
|
||||||
@@ -96,7 +96,7 @@
|
|||||||
|
|
||||||
let shouldPlayMotionPhoto = $state(false);
|
let shouldPlayMotionPhoto = $state(false);
|
||||||
let sharedLink = getSharedLink();
|
let sharedLink = getSharedLink();
|
||||||
let enableDetailPanel = $derived(asset.hasMetadata);
|
let enableDetailPanel = $derived(asset?.hasMetadata ?? false);
|
||||||
let slideshowStateUnsubscribe: () => void;
|
let slideshowStateUnsubscribe: () => void;
|
||||||
let shuffleSlideshowUnsubscribe: () => void;
|
let shuffleSlideshowUnsubscribe: () => void;
|
||||||
let previewStackedAsset: AssetResponseDto | undefined = $state();
|
let previewStackedAsset: AssetResponseDto | undefined = $state();
|
||||||
@@ -442,8 +442,7 @@
|
|||||||
{#if asset.type === AssetTypeEnum.Image}
|
{#if asset.type === AssetTypeEnum.Image}
|
||||||
{#if shouldPlayMotionPhoto && asset.livePhotoVideoId}
|
{#if shouldPlayMotionPhoto && asset.livePhotoVideoId}
|
||||||
<VideoViewer
|
<VideoViewer
|
||||||
assetId={asset.livePhotoVideoId}
|
{assetManager}
|
||||||
cacheKey={asset.thumbhash}
|
|
||||||
projectionType={asset.exifInfo?.projectionType}
|
projectionType={asset.exifInfo?.projectionType}
|
||||||
loopVideo={$slideshowState !== SlideshowState.PlaySlideshow}
|
loopVideo={$slideshowState !== SlideshowState.PlaySlideshow}
|
||||||
onPreviousAsset={() => navigateAsset('previous')}
|
onPreviousAsset={() => navigateAsset('previous')}
|
||||||
@@ -453,15 +452,14 @@
|
|||||||
{:else if asset.exifInfo?.projectionType === ProjectionType.EQUIRECTANGULAR || (asset.originalPath && asset.originalPath
|
{:else if asset.exifInfo?.projectionType === ProjectionType.EQUIRECTANGULAR || (asset.originalPath && asset.originalPath
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.endsWith('.insp'))}
|
.endsWith('.insp'))}
|
||||||
<ImagePanoramaViewer {asset} />
|
<ImagePanoramaViewer {assetManager} />
|
||||||
{:else if isShowEditor && selectedEditType === 'crop'}
|
{:else if isShowEditor && selectedEditType === 'crop'}
|
||||||
<CropArea {asset} />
|
<CropArea {asset} />
|
||||||
{:else}
|
{:else}
|
||||||
<PhotoViewer
|
<PhotoViewer
|
||||||
bind:zoomToggle
|
bind:zoomToggle
|
||||||
bind:copyImage
|
bind:copyImage
|
||||||
{asset}
|
{assetManager}
|
||||||
{preloadAssets}
|
|
||||||
onPreviousAsset={() => navigateAsset('previous')}
|
onPreviousAsset={() => navigateAsset('previous')}
|
||||||
onNextAsset={() => navigateAsset('next')}
|
onNextAsset={() => navigateAsset('next')}
|
||||||
{sharedLink}
|
{sharedLink}
|
||||||
@@ -470,8 +468,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
{:else}
|
{:else}
|
||||||
<VideoViewer
|
<VideoViewer
|
||||||
assetId={asset.id}
|
{assetManager}
|
||||||
cacheKey={asset.thumbhash}
|
|
||||||
projectionType={asset.exifInfo?.projectionType}
|
projectionType={asset.exifInfo?.projectionType}
|
||||||
loopVideo={$slideshowState !== SlideshowState.PlaySlideshow}
|
loopVideo={$slideshowState !== SlideshowState.PlaySlideshow}
|
||||||
onPreviousAsset={() => navigateAsset('previous')}
|
onPreviousAsset={() => navigateAsset('previous')}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
import { onDestroy, onMount, tick } from 'svelte';
|
import { onDestroy, onMount, tick } from 'svelte';
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
|
|
||||||
|
import type { AssetManager } from '$lib/managers/asset-manager/asset-manager.svelte';
|
||||||
import {
|
import {
|
||||||
changedOriention,
|
changedOriention,
|
||||||
cropAspectRatio,
|
cropAspectRatio,
|
||||||
@@ -13,7 +14,6 @@
|
|||||||
rotateDegrees,
|
rotateDegrees,
|
||||||
} from '$lib/stores/asset-editor.store';
|
} from '$lib/stores/asset-editor.store';
|
||||||
import { toTimelineAsset } from '$lib/utils/timeline-util';
|
import { toTimelineAsset } from '$lib/utils/timeline-util';
|
||||||
import type { AssetResponseDto } from '@immich/sdk';
|
|
||||||
import { animateCropChange, recalculateCrop } from './crop-settings';
|
import { animateCropChange, recalculateCrop } from './crop-settings';
|
||||||
import { cropAreaEl, cropFrame, imgElement, isResizingOrDragging, overlayEl, resetCropStore } from './crop-store';
|
import { cropAreaEl, cropFrame, imgElement, isResizingOrDragging, overlayEl, resetCropStore } from './crop-store';
|
||||||
import { draw } from './drawing';
|
import { draw } from './drawing';
|
||||||
@@ -21,10 +21,10 @@
|
|||||||
import { handleMouseDown, handleMouseMove, handleMouseUp } from './mouse-handlers';
|
import { handleMouseDown, handleMouseMove, handleMouseUp } from './mouse-handlers';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
asset: AssetResponseDto;
|
assetManager: AssetManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
let { asset }: Props = $props();
|
let { assetManager = $bindable() }: Props = $props();
|
||||||
|
|
||||||
let img = $state<HTMLImageElement>();
|
let img = $state<HTMLImageElement>();
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,19 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import type { AssetManager } from '$lib/managers/asset-manager/asset-manager.svelte';
|
||||||
import { authManager } from '$lib/managers/auth-manager.svelte';
|
import { authManager } from '$lib/managers/auth-manager.svelte';
|
||||||
import { getAssetOriginalUrl } from '$lib/utils';
|
import { getAssetOriginalUrl } from '$lib/utils';
|
||||||
import { isWebCompatibleImage } from '$lib/utils/asset-utils';
|
import { isWebCompatibleImage } from '$lib/utils/asset-utils';
|
||||||
import { AssetMediaSize, viewAsset, type AssetResponseDto } from '@immich/sdk';
|
import { AssetMediaSize, viewAsset } from '@immich/sdk';
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
import { fade } from 'svelte/transition';
|
import { fade } from 'svelte/transition';
|
||||||
import LoadingSpinner from '../shared-components/loading-spinner.svelte';
|
import LoadingSpinner from '../shared-components/loading-spinner.svelte';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
asset: AssetResponseDto;
|
assetManager: AssetManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { asset }: Props = $props();
|
// TODO: do not preload assets.
|
||||||
|
const { assetManager = $bindable() }: Props = $props();
|
||||||
|
|
||||||
const loadAssetData = async (id: string) => {
|
const loadAssetData = async (id: string) => {
|
||||||
const data = await viewAsset({ id, size: AssetMediaSize.Preview, key: authManager.key });
|
const data = await viewAsset({ id, size: AssetMediaSize.Preview, key: authManager.key });
|
||||||
|
|||||||
@@ -1,26 +1,28 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { shortcuts } from '$lib/actions/shortcut';
|
import { shortcuts } from '$lib/actions/shortcut';
|
||||||
import { zoomImageAction } from '$lib/actions/zoom-image';
|
|
||||||
import FaceEditor from '$lib/components/asset-viewer/face-editor/face-editor.svelte';
|
import FaceEditor from '$lib/components/asset-viewer/face-editor/face-editor.svelte';
|
||||||
import BrokenAsset from '$lib/components/assets/broken-asset.svelte';
|
import BrokenAsset from '$lib/components/assets/broken-asset.svelte';
|
||||||
import { assetViewerFadeDuration } from '$lib/constants';
|
import { assetViewerFadeDuration } from '$lib/constants';
|
||||||
|
import { type AssetManager } from '$lib/managers/asset-manager/asset-manager.svelte';
|
||||||
|
import {
|
||||||
|
cancelImageLoad,
|
||||||
|
mediaLoadError,
|
||||||
|
mediaLoaded,
|
||||||
|
} from '$lib/managers/asset-manager/internal/load-support.svelte';
|
||||||
|
import { zoomImageAttachment } from '$lib/managers/asset-manager/internal/zoom-support.svelte';
|
||||||
import { castManager } from '$lib/managers/cast-manager.svelte';
|
import { castManager } from '$lib/managers/cast-manager.svelte';
|
||||||
import type { TimelineAsset } from '$lib/managers/timeline-manager/types';
|
|
||||||
import { photoViewerImgElement } from '$lib/stores/assets-store.svelte';
|
import { photoViewerImgElement } from '$lib/stores/assets-store.svelte';
|
||||||
import { isFaceEditMode } from '$lib/stores/face-edit.svelte';
|
import { isFaceEditMode } from '$lib/stores/face-edit.svelte';
|
||||||
import { boundingBoxesArray } from '$lib/stores/people.store';
|
import { boundingBoxesArray } from '$lib/stores/people.store';
|
||||||
import { alwaysLoadOriginalFile } from '$lib/stores/preferences.store';
|
|
||||||
import { SlideshowLook, SlideshowState, slideshowLookCssMapping, slideshowStore } from '$lib/stores/slideshow.store';
|
import { SlideshowLook, SlideshowState, slideshowLookCssMapping, slideshowStore } from '$lib/stores/slideshow.store';
|
||||||
import { photoZoomState } from '$lib/stores/zoom-image.store';
|
import { handlePromiseError } from '$lib/utils';
|
||||||
import { getAssetOriginalUrl, getAssetThumbnailUrl, handlePromiseError } from '$lib/utils';
|
import { canCopyImageToClipboard, copyImageToClipboard } from '$lib/utils/asset-utils';
|
||||||
import { canCopyImageToClipboard, copyImageToClipboard, isWebCompatibleImage } from '$lib/utils/asset-utils';
|
|
||||||
import { handleError } from '$lib/utils/handle-error';
|
import { handleError } from '$lib/utils/handle-error';
|
||||||
import { getBoundingBox } from '$lib/utils/people-utils';
|
import { getBoundingBox } from '$lib/utils/people-utils';
|
||||||
import { cancelImageUrl } from '$lib/utils/sw-messaging';
|
|
||||||
import { getAltText } from '$lib/utils/thumbnail-util';
|
import { getAltText } from '$lib/utils/thumbnail-util';
|
||||||
import { toTimelineAsset } from '$lib/utils/timeline-util';
|
import { toTimelineAsset } from '$lib/utils/timeline-util';
|
||||||
import { AssetMediaSize, type AssetResponseDto, type SharedLinkResponseDto } from '@immich/sdk';
|
import { type SharedLinkResponseDto } from '@immich/sdk';
|
||||||
import { onDestroy, onMount } from 'svelte';
|
import { onDestroy } from 'svelte';
|
||||||
import { swipe, type SwipeCustomEvent } from 'svelte-gestures';
|
import { swipe, type SwipeCustomEvent } from 'svelte-gestures';
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
import { fade } from 'svelte/transition';
|
import { fade } from 'svelte/transition';
|
||||||
@@ -28,8 +30,7 @@
|
|||||||
import { NotificationType, notificationController } from '../shared-components/notification/notification';
|
import { NotificationType, notificationController } from '../shared-components/notification/notification';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
asset: AssetResponseDto;
|
assetManager: AssetManager;
|
||||||
preloadAssets?: TimelineAsset[] | undefined;
|
|
||||||
element?: HTMLDivElement | undefined;
|
element?: HTMLDivElement | undefined;
|
||||||
haveFadeTransition?: boolean;
|
haveFadeTransition?: boolean;
|
||||||
sharedLink?: SharedLinkResponseDto | undefined;
|
sharedLink?: SharedLinkResponseDto | undefined;
|
||||||
@@ -40,8 +41,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
let {
|
let {
|
||||||
asset,
|
assetManager = $bindable(),
|
||||||
preloadAssets = undefined,
|
|
||||||
element = $bindable(),
|
element = $bindable(),
|
||||||
haveFadeTransition = true,
|
haveFadeTransition = true,
|
||||||
sharedLink = undefined,
|
sharedLink = undefined,
|
||||||
@@ -53,51 +53,25 @@
|
|||||||
|
|
||||||
const { slideshowState, slideshowLook } = slideshowStore;
|
const { slideshowState, slideshowLook } = slideshowStore;
|
||||||
|
|
||||||
let assetFileUrl: string = $state('');
|
let zoomImageState = $derived(assetManager.zoomImageState);
|
||||||
let imageLoaded: boolean = $state(false);
|
|
||||||
let originalImageLoaded: boolean = $state(false);
|
|
||||||
let imageError: boolean = $state(false);
|
|
||||||
|
|
||||||
let loader = $state<HTMLImageElement>();
|
zoomToggle = () => {
|
||||||
|
if (zoomImageState) {
|
||||||
photoZoomState.set({
|
zoomImageState.currentZoom = zoomImageState.currentZoom > 1 ? 1 : 2;
|
||||||
currentRotation: 0,
|
}
|
||||||
currentZoom: 1,
|
};
|
||||||
enable: true,
|
|
||||||
currentPositionX: 0,
|
|
||||||
currentPositionY: 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
onDestroy(() => {
|
onDestroy(() => {
|
||||||
$boundingBoxesArray = [];
|
$boundingBoxesArray = [];
|
||||||
});
|
});
|
||||||
|
|
||||||
const preload = (targetSize: AssetMediaSize | 'original', preloadAssets?: TimelineAsset[]) => {
|
|
||||||
for (const preloadAsset of preloadAssets || []) {
|
|
||||||
if (preloadAsset.isImage) {
|
|
||||||
let img = new Image();
|
|
||||||
img.src = getAssetUrl(preloadAsset.id, targetSize, preloadAsset.thumbhash);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const getAssetUrl = (id: string, targetSize: AssetMediaSize | 'original', cacheKey: string | null) => {
|
|
||||||
if (sharedLink && (!sharedLink.allowDownload || !sharedLink.showMetadata)) {
|
|
||||||
return getAssetThumbnailUrl({ id, size: AssetMediaSize.Preview, cacheKey });
|
|
||||||
}
|
|
||||||
|
|
||||||
return targetSize === 'original'
|
|
||||||
? getAssetOriginalUrl({ id, cacheKey })
|
|
||||||
: getAssetThumbnailUrl({ id, size: targetSize, cacheKey });
|
|
||||||
};
|
|
||||||
|
|
||||||
copyImage = async () => {
|
copyImage = async () => {
|
||||||
if (!canCopyImageToClipboard()) {
|
if (!canCopyImageToClipboard()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await copyImageToClipboard($photoViewerImgElement ?? assetFileUrl);
|
await copyImageToClipboard($photoViewerImgElement ?? assetManager.url!);
|
||||||
notificationController.show({
|
notificationController.show({
|
||||||
type: NotificationType.Info,
|
type: NotificationType.Info,
|
||||||
message: $t('copied_image_to_clipboard'),
|
message: $t('copied_image_to_clipboard'),
|
||||||
@@ -108,17 +82,10 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
zoomToggle = () => {
|
|
||||||
photoZoomState.set({
|
|
||||||
...$photoZoomState,
|
|
||||||
currentZoom: $photoZoomState.currentZoom > 1 ? 1 : 2,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const onPlaySlideshow = () => ($slideshowState = SlideshowState.PlaySlideshow);
|
const onPlaySlideshow = () => ($slideshowState = SlideshowState.PlaySlideshow);
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (isFaceEditMode.value && $photoZoomState.currentZoom > 1) {
|
if (isFaceEditMode.value && zoomImageState && zoomImageState.currentZoom > 1) {
|
||||||
zoomToggle();
|
zoomToggle();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -132,7 +99,7 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onSwipe = (event: SwipeCustomEvent) => {
|
const onSwipe = (event: SwipeCustomEvent) => {
|
||||||
if ($photoZoomState.currentZoom > 1) {
|
if (!zoomImageState || zoomImageState.currentZoom > 1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (onNextAsset && event.detail.direction === 'left') {
|
if (onNextAsset && event.detail.direction === 'left') {
|
||||||
@@ -143,21 +110,10 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// when true, will force loading of the original image
|
|
||||||
let forceUseOriginal: boolean = $derived(asset.originalMimeType === 'image/gif' || $photoZoomState.currentZoom > 1);
|
|
||||||
|
|
||||||
const targetImageSize = $derived.by(() => {
|
|
||||||
if ($alwaysLoadOriginalFile || forceUseOriginal || originalImageLoaded) {
|
|
||||||
return isWebCompatibleImage(asset) ? 'original' : AssetMediaSize.Fullsize;
|
|
||||||
}
|
|
||||||
|
|
||||||
return AssetMediaSize.Preview;
|
|
||||||
});
|
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (assetFileUrl) {
|
if (assetManager.url) {
|
||||||
// this can't be in an async context with $effect
|
// this can't be in an async context with $effect
|
||||||
void cast(assetFileUrl);
|
void cast(assetManager.url);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -175,35 +131,10 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onload = () => {
|
onDestroy(() => {
|
||||||
imageLoaded = true;
|
cancelImageLoad(assetManager);
|
||||||
assetFileUrl = imageLoaderUrl;
|
|
||||||
originalImageLoaded = targetImageSize === AssetMediaSize.Fullsize || targetImageSize === 'original';
|
|
||||||
};
|
|
||||||
|
|
||||||
const onerror = () => {
|
|
||||||
imageError = imageLoaded = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
$effect(() => {
|
|
||||||
preload(targetImageSize, preloadAssets);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
if (loader?.complete) {
|
|
||||||
onload();
|
|
||||||
}
|
|
||||||
loader?.addEventListener('load', onload, { passive: true });
|
|
||||||
loader?.addEventListener('error', onerror, { passive: true });
|
|
||||||
return () => {
|
|
||||||
loader?.removeEventListener('load', onload);
|
|
||||||
loader?.removeEventListener('error', onerror);
|
|
||||||
cancelImageUrl(imageLoaderUrl);
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
let imageLoaderUrl = $derived(getAssetUrl(asset.id, targetImageSize, asset.thumbhash));
|
|
||||||
|
|
||||||
let containerWidth = $state(0);
|
let containerWidth = $state(0);
|
||||||
let containerHeight = $state(0);
|
let containerHeight = $state(0);
|
||||||
</script>
|
</script>
|
||||||
@@ -217,27 +148,32 @@
|
|||||||
{ shortcut: { key: 'z' }, onShortcut: zoomToggle, preventDefault: false },
|
{ shortcut: { key: 'z' }, onShortcut: zoomToggle, preventDefault: false },
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
{#if imageError}
|
{#if assetManager.loadError}
|
||||||
<div class="h-full w-full">
|
<div class="h-full w-full">
|
||||||
<BrokenAsset class="text-xl h-full w-full" />
|
<BrokenAsset class="text-xl h-full w-full" />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<!-- svelte-ignore a11y_missing_attribute -->
|
|
||||||
<img bind:this={loader} style="display:none" src={imageLoaderUrl} aria-hidden="true" />
|
|
||||||
<div
|
<div
|
||||||
bind:this={element}
|
bind:this={element}
|
||||||
class="relative h-full select-none"
|
class="relative h-full select-none"
|
||||||
bind:clientWidth={containerWidth}
|
bind:clientWidth={containerWidth}
|
||||||
bind:clientHeight={containerHeight}
|
bind:clientHeight={containerHeight}
|
||||||
>
|
>
|
||||||
<img style="display:none" src={imageLoaderUrl} alt="" {onload} {onerror} />
|
<img
|
||||||
{#if !imageLoaded}
|
style="display:none"
|
||||||
|
src={assetManager.url}
|
||||||
|
alt=""
|
||||||
|
aria-hidden="true"
|
||||||
|
onload={() => mediaLoaded(assetManager)}
|
||||||
|
onerror={() => mediaLoadError(assetManager)}
|
||||||
|
/>
|
||||||
|
{#if !assetManager.isLoaded}
|
||||||
<div id="spinner" class="flex h-full items-center justify-center">
|
<div id="spinner" class="flex h-full items-center justify-center">
|
||||||
<LoadingSpinner />
|
<LoadingSpinner />
|
||||||
</div>
|
</div>
|
||||||
{:else if !imageError}
|
{:else if !assetManager.loadError}
|
||||||
<div
|
<div
|
||||||
use:zoomImageAction
|
{@attach zoomImageAttachment(assetManager)}
|
||||||
use:swipe={() => ({})}
|
use:swipe={() => ({})}
|
||||||
onswipe={onSwipe}
|
onswipe={onSwipe}
|
||||||
class="h-full w-full"
|
class="h-full w-full"
|
||||||
@@ -245,7 +181,7 @@
|
|||||||
>
|
>
|
||||||
{#if $slideshowState !== SlideshowState.None && $slideshowLook === SlideshowLook.BlurredBackground}
|
{#if $slideshowState !== SlideshowState.None && $slideshowLook === SlideshowLook.BlurredBackground}
|
||||||
<img
|
<img
|
||||||
src={assetFileUrl}
|
src={assetManager.url}
|
||||||
alt=""
|
alt=""
|
||||||
class="-z-1 absolute top-0 start-0 object-cover h-full w-full blur-lg"
|
class="-z-1 absolute top-0 start-0 object-cover h-full w-full blur-lg"
|
||||||
draggable="false"
|
draggable="false"
|
||||||
@@ -253,15 +189,15 @@
|
|||||||
{/if}
|
{/if}
|
||||||
<img
|
<img
|
||||||
bind:this={$photoViewerImgElement}
|
bind:this={$photoViewerImgElement}
|
||||||
src={assetFileUrl}
|
src={assetManager.url}
|
||||||
alt={$getAltText(toTimelineAsset(asset))}
|
alt={$getAltText(toTimelineAsset(assetManager.asset!))}
|
||||||
class="h-full w-full {$slideshowState === SlideshowState.None
|
class="h-full w-full {$slideshowState === SlideshowState.None
|
||||||
? 'object-contain'
|
? 'object-contain'
|
||||||
: slideshowLookCssMapping[$slideshowLook]}"
|
: slideshowLookCssMapping[$slideshowLook]}"
|
||||||
draggable="false"
|
draggable="false"
|
||||||
/>
|
/>
|
||||||
<!-- eslint-disable-next-line svelte/require-each-key -->
|
<!-- eslint-disable-next-line svelte/require-each-key -->
|
||||||
{#each getBoundingBox($boundingBoxesArray, $photoZoomState, $photoViewerImgElement) as boundingbox}
|
{#each getBoundingBox($boundingBoxesArray, zoomImageState!, $photoViewerImgElement) as boundingbox}
|
||||||
<div
|
<div
|
||||||
class="absolute border-solid border-white border-[3px] rounded-lg"
|
class="absolute border-solid border-white border-[3px] rounded-lg"
|
||||||
style="top: {boundingbox.top}px; left: {boundingbox.left}px; height: {boundingbox.height}px; width: {boundingbox.width}px;"
|
style="top: {boundingbox.top}px; left: {boundingbox.left}px; height: {boundingbox.height}px; width: {boundingbox.width}px;"
|
||||||
@@ -270,7 +206,12 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if isFaceEditMode.value}
|
{#if isFaceEditMode.value}
|
||||||
<FaceEditor htmlElement={$photoViewerImgElement} {containerWidth} {containerHeight} assetId={asset.id} />
|
<FaceEditor
|
||||||
|
htmlElement={$photoViewerImgElement}
|
||||||
|
{containerWidth}
|
||||||
|
{containerHeight}
|
||||||
|
assetId={assetManager.asset!.id}
|
||||||
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import VideoRemoteViewer from '$lib/components/asset-viewer/video-remote-viewer.svelte';
|
import VideoRemoteViewer from '$lib/components/asset-viewer/video-remote-viewer.svelte';
|
||||||
import LoadingSpinner from '$lib/components/shared-components/loading-spinner.svelte';
|
import LoadingSpinner from '$lib/components/shared-components/loading-spinner.svelte';
|
||||||
import { assetViewerFadeDuration } from '$lib/constants';
|
import { assetViewerFadeDuration } from '$lib/constants';
|
||||||
|
import { AssetManager } from '$lib/managers/asset-manager/asset-manager.svelte';
|
||||||
import { castManager } from '$lib/managers/cast-manager.svelte';
|
import { castManager } from '$lib/managers/cast-manager.svelte';
|
||||||
import { isFaceEditMode } from '$lib/stores/face-edit.svelte';
|
import { isFaceEditMode } from '$lib/stores/face-edit.svelte';
|
||||||
import { loopVideo as loopVideoPreference, videoViewerMuted, videoViewerVolume } from '$lib/stores/preferences.store';
|
import { loopVideo as loopVideoPreference, videoViewerMuted, videoViewerVolume } from '$lib/stores/preferences.store';
|
||||||
@@ -16,9 +17,8 @@
|
|||||||
import { fade } from 'svelte/transition';
|
import { fade } from 'svelte/transition';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
assetId: string;
|
assetManager: AssetManager;
|
||||||
loopVideo: boolean;
|
loopVideo: boolean;
|
||||||
cacheKey: string | null;
|
|
||||||
onPreviousAsset?: () => void;
|
onPreviousAsset?: () => void;
|
||||||
onNextAsset?: () => void;
|
onNextAsset?: () => void;
|
||||||
onVideoEnded?: () => void;
|
onVideoEnded?: () => void;
|
||||||
@@ -27,9 +27,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
let {
|
let {
|
||||||
assetId,
|
assetManager = $bindable(),
|
||||||
loopVideo,
|
loopVideo,
|
||||||
cacheKey,
|
|
||||||
onPreviousAsset = () => {},
|
onPreviousAsset = () => {},
|
||||||
onNextAsset = () => {},
|
onNextAsset = () => {},
|
||||||
onVideoEnded = () => {},
|
onVideoEnded = () => {},
|
||||||
|
|||||||
@@ -3,12 +3,13 @@
|
|||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
import { fade } from 'svelte/transition';
|
import { fade } from 'svelte/transition';
|
||||||
import LoadingSpinner from '../shared-components/loading-spinner.svelte';
|
import LoadingSpinner from '../shared-components/loading-spinner.svelte';
|
||||||
|
import type { AssetManager } from '$lib/managers/asset-manager/asset-manager.svelte';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
assetId: string;
|
assetManager: AssetManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { assetId }: Props = $props();
|
const { assetManager = $bindable() }: Props = $props();
|
||||||
|
|
||||||
const modules = Promise.all([
|
const modules = Promise.all([
|
||||||
import('./photo-sphere-viewer-adapter.svelte').then((module) => module.default),
|
import('./photo-sphere-viewer-adapter.svelte').then((module) => module.default),
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { ProjectionType } from '$lib/constants';
|
|
||||||
import VideoNativeViewer from '$lib/components/asset-viewer/video-native-viewer.svelte';
|
import VideoNativeViewer from '$lib/components/asset-viewer/video-native-viewer.svelte';
|
||||||
import VideoPanoramaViewer from '$lib/components/asset-viewer/video-panorama-viewer.svelte';
|
import VideoPanoramaViewer from '$lib/components/asset-viewer/video-panorama-viewer.svelte';
|
||||||
|
import { ProjectionType } from '$lib/constants';
|
||||||
|
import type { AssetManager } from '$lib/managers/asset-manager/asset-manager.svelte';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
assetId: string;
|
assetManager: AssetManager;
|
||||||
projectionType: string | null | undefined;
|
projectionType: string | null | undefined;
|
||||||
cacheKey: string | null;
|
|
||||||
loopVideo: boolean;
|
loopVideo: boolean;
|
||||||
onClose?: () => void;
|
onClose?: () => void;
|
||||||
onPreviousAsset?: () => void;
|
onPreviousAsset?: () => void;
|
||||||
@@ -15,10 +15,10 @@
|
|||||||
onVideoStarted?: () => void;
|
onVideoStarted?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: do not preload assets.
|
||||||
let {
|
let {
|
||||||
assetId,
|
assetManager = $bindable(),
|
||||||
projectionType,
|
projectionType,
|
||||||
cacheKey,
|
|
||||||
loopVideo,
|
loopVideo,
|
||||||
onPreviousAsset,
|
onPreviousAsset,
|
||||||
onClose,
|
onClose,
|
||||||
@@ -29,12 +29,11 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if projectionType === ProjectionType.EQUIRECTANGULAR}
|
{#if projectionType === ProjectionType.EQUIRECTANGULAR}
|
||||||
<VideoPanoramaViewer {assetId} />
|
<VideoPanoramaViewer {assetManager} />
|
||||||
{:else}
|
{:else}
|
||||||
<VideoNativeViewer
|
<VideoNativeViewer
|
||||||
{loopVideo}
|
{loopVideo}
|
||||||
{cacheKey}
|
{assetManager}
|
||||||
{assetId}
|
|
||||||
{onPreviousAsset}
|
{onPreviousAsset}
|
||||||
{onNextAsset}
|
{onNextAsset}
|
||||||
{onVideoEnded}
|
{onVideoEnded}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
NotificationType,
|
NotificationType,
|
||||||
} from '$lib/components/shared-components/notification/notification';
|
} from '$lib/components/shared-components/notification/notification';
|
||||||
import { AppRoute, QueryParameter } from '$lib/constants';
|
import { AppRoute, QueryParameter } from '$lib/constants';
|
||||||
import { AssetManager } from '$lib/managers/asset-manager.svelte';
|
import { AssetManager } from '$lib/managers/asset-manager/asset-manager.svelte';
|
||||||
import { authManager } from '$lib/managers/auth-manager.svelte';
|
import { authManager } from '$lib/managers/auth-manager.svelte';
|
||||||
import type { TimelineAsset, Viewport } from '$lib/managers/timeline-manager/types';
|
import type { TimelineAsset, Viewport } from '$lib/managers/timeline-manager/types';
|
||||||
import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
|
import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
import ChangeDate from '$lib/components/shared-components/change-date.svelte';
|
import ChangeDate from '$lib/components/shared-components/change-date.svelte';
|
||||||
import Scrubber from '$lib/components/shared-components/scrubber/scrubber.svelte';
|
import Scrubber from '$lib/components/shared-components/scrubber/scrubber.svelte';
|
||||||
import { AppRoute, AssetAction } from '$lib/constants';
|
import { AppRoute, AssetAction } from '$lib/constants';
|
||||||
import type { AssetManager } from '$lib/managers/asset-manager.svelte';
|
import type { AssetManager } from '$lib/managers/asset-manager/asset-manager.svelte';
|
||||||
import { modalManager } from '$lib/managers/modal-manager.svelte';
|
import { modalManager } from '$lib/managers/modal-manager.svelte';
|
||||||
import type { MonthGroup } from '$lib/managers/timeline-manager/month-group.svelte';
|
import type { MonthGroup } from '$lib/managers/timeline-manager/month-group.svelte';
|
||||||
import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
|
import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
|
||||||
@@ -439,35 +439,38 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handlePrevious = async () => {
|
const handlePrevious = async () => {
|
||||||
const laterAsset = await timelineManager.getLaterAsset(asset);
|
let laterAsset = undefined;
|
||||||
|
if (asset) {
|
||||||
if (laterAsset) {
|
laterAsset = await timelineManager.getLaterAsset(asset);
|
||||||
// TODO: If preloadAsset is undefined, throw an exception.
|
if (laterAsset) {
|
||||||
// assetManager.preloadAssets = [await timelineManager.getLaterAsset(laterAsset)];
|
// TODO: If preloadAsset is undefined, throw an exception.
|
||||||
await navigate({ targetRoute: 'current', assetId: laterAsset.id });
|
// assetManager.preloadAssets = [await timelineManager.getLaterAsset(laterAsset)];
|
||||||
|
await navigate({ targetRoute: 'current', assetId: laterAsset.id });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return !!laterAsset;
|
return !!laterAsset;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleNext = async () => {
|
const handleNext = async () => {
|
||||||
const earlierAsset = await timelineManager.getEarlierAsset(asset);
|
let earlierAsset = undefined;
|
||||||
|
if (asset) {
|
||||||
if (earlierAsset) {
|
earlierAsset = await timelineManager.getEarlierAsset(asset);
|
||||||
// assetManager.preloadAssets = [await timelineManager.getEarlierAsset(earlierAsset)];
|
if (earlierAsset) {
|
||||||
await navigate({ targetRoute: 'current', assetId: earlierAsset.id });
|
// assetManager.preloadAssets = [await timelineManager.getEarlierAsset(earlierAsset)];
|
||||||
|
await navigate({ targetRoute: 'current', assetId: earlierAsset.id });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return !!earlierAsset;
|
return !!earlierAsset;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRandom = async () => {
|
const handleRandom = async () => {
|
||||||
const randomAsset = await timelineManager.getRandomAsset();
|
let randomAsset = undefined;
|
||||||
|
if (asset) {
|
||||||
if (randomAsset) {
|
randomAsset = await timelineManager.getRandomAsset();
|
||||||
await navigate({ targetRoute: 'current', assetId: randomAsset.id });
|
if (randomAsset) {
|
||||||
|
await navigate({ targetRoute: 'current', assetId: randomAsset.id });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return !!randomAsset;
|
return !!randomAsset;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -777,7 +780,7 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (assetManager.showAssetViewer) {
|
if (assetManager.showAssetViewer && asset) {
|
||||||
const { localDateTime } = getTimes(asset.fileCreatedAt, DateTime.local().offset / 60);
|
const { localDateTime } = getTimes(asset.fileCreatedAt, DateTime.local().offset / 60);
|
||||||
void timelineManager.loadMonthGroup({ year: localDateTime.year, month: localDateTime.month });
|
void timelineManager.loadMonthGroup({ year: localDateTime.year, month: localDateTime.month });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import type { Action } from '$lib/components/asset-viewer/actions/action';
|
import type { Action } from '$lib/components/asset-viewer/actions/action';
|
||||||
import ImmichLogoSmallLink from '$lib/components/shared-components/immich-logo-small-link.svelte';
|
import ImmichLogoSmallLink from '$lib/components/shared-components/immich-logo-small-link.svelte';
|
||||||
import { AppRoute, AssetAction } from '$lib/constants';
|
import { AppRoute, AssetAction } from '$lib/constants';
|
||||||
import type { AssetManager } from '$lib/managers/asset-manager.svelte';
|
import type { AssetManager } from '$lib/managers/asset-manager/asset-manager.svelte';
|
||||||
import { authManager } from '$lib/managers/auth-manager.svelte';
|
import { authManager } from '$lib/managers/auth-manager.svelte';
|
||||||
import type { Viewport } from '$lib/managers/timeline-manager/types';
|
import type { Viewport } from '$lib/managers/timeline-manager/types';
|
||||||
import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
|
import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
import type { Action } from '$lib/components/asset-viewer/actions/action';
|
import type { Action } from '$lib/components/asset-viewer/actions/action';
|
||||||
import Thumbnail from '$lib/components/assets/thumbnail/thumbnail.svelte';
|
import Thumbnail from '$lib/components/assets/thumbnail/thumbnail.svelte';
|
||||||
import { AppRoute, AssetAction } from '$lib/constants';
|
import { AppRoute, AssetAction } from '$lib/constants';
|
||||||
import type { AssetManager } from '$lib/managers/asset-manager.svelte';
|
import type { AssetManager } from '$lib/managers/asset-manager/asset-manager.svelte';
|
||||||
import { modalManager } from '$lib/managers/modal-manager.svelte';
|
import { modalManager } from '$lib/managers/modal-manager.svelte';
|
||||||
import type { TimelineAsset, Viewport } from '$lib/managers/timeline-manager/types';
|
import type { TimelineAsset, Viewport } from '$lib/managers/timeline-manager/types';
|
||||||
import ShortcutsModal from '$lib/modals/ShortcutsModal.svelte';
|
import ShortcutsModal from '$lib/modals/ShortcutsModal.svelte';
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import { shortcuts } from '$lib/actions/shortcut';
|
import { shortcuts } from '$lib/actions/shortcut';
|
||||||
import Portal from '$lib/components/shared-components/portal/portal.svelte';
|
import Portal from '$lib/components/shared-components/portal/portal.svelte';
|
||||||
import DuplicateAsset from '$lib/components/utilities-page/duplicates/duplicate-asset.svelte';
|
import DuplicateAsset from '$lib/components/utilities-page/duplicates/duplicate-asset.svelte';
|
||||||
import type { AssetManager } from '$lib/managers/asset-manager.svelte';
|
import type { AssetManager } from '$lib/managers/asset-manager/asset-manager.svelte';
|
||||||
import { handlePromiseError } from '$lib/utils';
|
import { handlePromiseError } from '$lib/utils';
|
||||||
import { suggestDuplicate } from '$lib/utils/duplicate-utils';
|
import { suggestDuplicate } from '$lib/utils/duplicate-utils';
|
||||||
import { navigate } from '$lib/utils/navigation';
|
import { navigate } from '$lib/utils/navigation';
|
||||||
|
|||||||
@@ -1,143 +0,0 @@
|
|||||||
import { authManager } from '$lib/managers/auth-manager.svelte';
|
|
||||||
import type { TimelineAsset } from '$lib/managers/timeline-manager/types';
|
|
||||||
import { CancellableTask } from '$lib/utils/cancellable-task';
|
|
||||||
import type { AssetGridRouteSearchParams } from '$lib/utils/navigation';
|
|
||||||
import {
|
|
||||||
AssetMediaSize,
|
|
||||||
AssetTypeEnum,
|
|
||||||
AssetVisibility,
|
|
||||||
getAllAlbums,
|
|
||||||
getAssetInfo,
|
|
||||||
getAssetOriginalPath,
|
|
||||||
getAssetPlaybackPath,
|
|
||||||
getAssetThumbnailPath,
|
|
||||||
getBaseUrl,
|
|
||||||
type AlbumResponseDto,
|
|
||||||
type AssetResponseDto,
|
|
||||||
} from '@immich/sdk';
|
|
||||||
import { isEqual } from 'lodash-es';
|
|
||||||
|
|
||||||
export type AssetManagerOptions = {
|
|
||||||
assetId?: string;
|
|
||||||
preloadAssetIds?: string[];
|
|
||||||
size?: AssetMediaSize;
|
|
||||||
};
|
|
||||||
|
|
||||||
export class AssetManager {
|
|
||||||
isInitialized = $state(false);
|
|
||||||
#asset: AssetResponseDto | undefined = $state();
|
|
||||||
preloadAssets: TimelineAsset[] = $state([]);
|
|
||||||
cacheKey: string | null = $state(null);
|
|
||||||
albums: AlbumResponseDto[] = $state([]);
|
|
||||||
|
|
||||||
showAssetViewer: boolean = $state(false);
|
|
||||||
gridScrollTarget: AssetGridRouteSearchParams | undefined = $state();
|
|
||||||
|
|
||||||
initTask = new CancellableTask(
|
|
||||||
() => (this.isInitialized = true),
|
|
||||||
() => (this.isInitialized = false),
|
|
||||||
() => void 0,
|
|
||||||
);
|
|
||||||
|
|
||||||
// TODO: Delete this after development
|
|
||||||
#emptyAsset: AssetResponseDto = {
|
|
||||||
checksum: '',
|
|
||||||
deviceAssetId: '',
|
|
||||||
deviceId: '',
|
|
||||||
duration: '',
|
|
||||||
fileCreatedAt: '',
|
|
||||||
fileModifiedAt: '',
|
|
||||||
hasMetadata: true,
|
|
||||||
id: '',
|
|
||||||
isArchived: false,
|
|
||||||
isFavorite: false,
|
|
||||||
isOffline: false,
|
|
||||||
isTrashed: false,
|
|
||||||
localDateTime: '',
|
|
||||||
originalFileName: '',
|
|
||||||
originalPath: '',
|
|
||||||
ownerId: '',
|
|
||||||
thumbhash: null,
|
|
||||||
type: AssetTypeEnum.Image,
|
|
||||||
updatedAt: '',
|
|
||||||
visibility: AssetVisibility.Timeline,
|
|
||||||
};
|
|
||||||
|
|
||||||
static #INIT_OPTIONS = {};
|
|
||||||
#options: AssetManagerOptions = AssetManager.#INIT_OPTIONS;
|
|
||||||
|
|
||||||
constructor() {}
|
|
||||||
|
|
||||||
async #initializeAsset(id: string) {
|
|
||||||
const assetResponse = await getAssetInfo({ id, key: authManager.key });
|
|
||||||
|
|
||||||
if (!assetResponse) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.#asset = assetResponse;
|
|
||||||
|
|
||||||
const albumsResponse = await getAllAlbums({ assetId: this.asset.id });
|
|
||||||
|
|
||||||
if (!albumsResponse) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.albums = albumsResponse;
|
|
||||||
}
|
|
||||||
|
|
||||||
async refreshAlbums() {}
|
|
||||||
|
|
||||||
async refreshAsset() {}
|
|
||||||
|
|
||||||
async updateOptions(options: AssetManagerOptions) {
|
|
||||||
if (this.#options !== AssetManager.#INIT_OPTIONS && isEqual(this.#options, options)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await this.initTask.reset();
|
|
||||||
await this.#init(options);
|
|
||||||
}
|
|
||||||
|
|
||||||
async #init(options: AssetManagerOptions) {
|
|
||||||
this.isInitialized = false;
|
|
||||||
await this.initTask.execute(async () => {
|
|
||||||
this.#options = options;
|
|
||||||
// TODO: If assetId is undefined, throw an exception.
|
|
||||||
await this.#initializeAsset(this.#options.assetId!);
|
|
||||||
// TODO: Preload assets.
|
|
||||||
}, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public destroy() {
|
|
||||||
this.isInitialized = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
#createUrl(path: string, parameters?: Record<string, unknown>) {
|
|
||||||
const searchParameters = new URLSearchParams();
|
|
||||||
for (const key in parameters) {
|
|
||||||
const value = parameters[key];
|
|
||||||
if (value !== undefined && value !== null) {
|
|
||||||
searchParameters.set(key, value.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return getBaseUrl() + path + searchParameters.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
get asset() {
|
|
||||||
return this.#asset ?? this.#emptyAsset;
|
|
||||||
}
|
|
||||||
|
|
||||||
get originalUrl() {
|
|
||||||
return this.#createUrl(getAssetOriginalPath(this.asset.id), { key: authManager.key, c: this.cacheKey });
|
|
||||||
}
|
|
||||||
|
|
||||||
get thumbnailUrl() {
|
|
||||||
return this.#createUrl(getAssetThumbnailPath(this.asset.id), {
|
|
||||||
key: authManager.key,
|
|
||||||
c: this.cacheKey,
|
|
||||||
size: this.#options.size,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
get playbackUrl() {
|
|
||||||
return this.#createUrl(getAssetPlaybackPath(this.asset.id), { key: authManager.key, c: this.cacheKey });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,204 @@
|
|||||||
|
import { authManager } from '$lib/managers/auth-manager.svelte';
|
||||||
|
import type { TimelineAsset } from '$lib/managers/timeline-manager/types';
|
||||||
|
import { CancellableTask } from '$lib/utils/cancellable-task';
|
||||||
|
import type { AssetGridRouteSearchParams } from '$lib/utils/navigation';
|
||||||
|
import { toTimelineAsset } from '$lib/utils/timeline-util';
|
||||||
|
import {
|
||||||
|
getAllAlbums,
|
||||||
|
getAssetInfo,
|
||||||
|
getAssetOriginalPath,
|
||||||
|
getAssetPlaybackPath,
|
||||||
|
getAssetThumbnailPath,
|
||||||
|
getBaseUrl,
|
||||||
|
type AlbumResponseDto,
|
||||||
|
type AssetResponseDto,
|
||||||
|
} from '@immich/sdk';
|
||||||
|
import { type ZoomImageWheelState } from '@zoom-image/core';
|
||||||
|
import { isEqual } from 'lodash-es';
|
||||||
|
|
||||||
|
export enum AssetMediaSize {
|
||||||
|
Original = 'original',
|
||||||
|
Fullsize = 'fullsize',
|
||||||
|
Preview = 'preview',
|
||||||
|
Thumbnail = 'thumbnail',
|
||||||
|
Playback = 'playback',
|
||||||
|
}
|
||||||
|
|
||||||
|
export type AssetManagerOptions = {
|
||||||
|
assetId?: string;
|
||||||
|
preloadAssetIds?: string[];
|
||||||
|
loadAlbums?: boolean;
|
||||||
|
size?: AssetMediaSize;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class AssetManager {
|
||||||
|
isInitialized = $state(false);
|
||||||
|
isLoaded = $state(false);
|
||||||
|
loadError = $state(false);
|
||||||
|
asset: AssetResponseDto | undefined = $state();
|
||||||
|
preloadAssets: TimelineAsset[] = $state([]);
|
||||||
|
albums: AlbumResponseDto[] = $state([]);
|
||||||
|
|
||||||
|
cacheKey: string | null = $derived(this.asset?.thumbhash ?? null);
|
||||||
|
|
||||||
|
url: string | undefined = $derived.by(() => {
|
||||||
|
if (this.asset) {
|
||||||
|
return this.#getAssetUrl(toTimelineAsset(this.asset!));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
showAssetViewer: boolean = $state(false);
|
||||||
|
gridScrollTarget: AssetGridRouteSearchParams | undefined = $state();
|
||||||
|
zoomImageState: ZoomImageWheelState | undefined = $state();
|
||||||
|
|
||||||
|
initTask = new CancellableTask(
|
||||||
|
() => (this.isInitialized = true),
|
||||||
|
() => {
|
||||||
|
this.asset = undefined;
|
||||||
|
this.preloadAssets = [];
|
||||||
|
this.albums = [];
|
||||||
|
this.isInitialized = false;
|
||||||
|
},
|
||||||
|
() => void 0,
|
||||||
|
);
|
||||||
|
|
||||||
|
static #INIT_OPTIONS = {};
|
||||||
|
#options: AssetManagerOptions = AssetManager.#INIT_OPTIONS;
|
||||||
|
|
||||||
|
constructor() {}
|
||||||
|
|
||||||
|
async #initializeAsset() {
|
||||||
|
if (this.#options.assetId) {
|
||||||
|
const assetResponse = await getAssetInfo({ id: this.#options.assetId, key: authManager.key });
|
||||||
|
if (!assetResponse) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.asset = assetResponse;
|
||||||
|
} else {
|
||||||
|
throw new Error('The assetId in required in options.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Preload assets.
|
||||||
|
|
||||||
|
if (this.#options.loadAlbums ?? true) {
|
||||||
|
const albumsResponse = await getAllAlbums({ assetId: this.#options.assetId });
|
||||||
|
if (!albumsResponse) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.albums = albumsResponse;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateOptions(options: AssetManagerOptions) {
|
||||||
|
if (this.#options !== AssetManager.#INIT_OPTIONS && isEqual(this.#options, options)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await this.initTask.reset();
|
||||||
|
await this.#init(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
#checkOptions() {
|
||||||
|
this.#options.size = AssetMediaSize.Original;
|
||||||
|
|
||||||
|
if (!this.asset || !this.zoomImageState) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.asset.originalMimeType === 'image/gif' || this.zoomImageState.currentZoom > 1) {
|
||||||
|
// TODO: use original image forcely and according to the setting.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async #init(options: AssetManagerOptions) {
|
||||||
|
this.isInitialized = false;
|
||||||
|
this.asset = undefined;
|
||||||
|
this.preloadAssets = [];
|
||||||
|
this.albums = [];
|
||||||
|
await this.initTask.execute(async () => {
|
||||||
|
this.#options = options;
|
||||||
|
await this.#initializeAsset();
|
||||||
|
this.#checkOptions();
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public destroy() {
|
||||||
|
this.isInitialized = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async refreshAlbums() {}
|
||||||
|
|
||||||
|
async refreshAsset() {}
|
||||||
|
|
||||||
|
#preload() {
|
||||||
|
for (const preloadAsset of this.preloadAssets) {
|
||||||
|
if (preloadAsset.isImage) {
|
||||||
|
let img = new Image();
|
||||||
|
const preloadUrl = this.#getAssetUrl(preloadAsset);
|
||||||
|
if (preloadUrl) {
|
||||||
|
img.src = preloadUrl;
|
||||||
|
} else {
|
||||||
|
throw new Error('AssetManager is not initialized.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#getAssetUrl(asset: TimelineAsset) {
|
||||||
|
if (!this.asset) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let path = undefined;
|
||||||
|
const searchParameters = new URLSearchParams();
|
||||||
|
if (authManager.key) {
|
||||||
|
searchParameters.set('key', authManager.key);
|
||||||
|
}
|
||||||
|
if (this.cacheKey) {
|
||||||
|
searchParameters.set('c', this.cacheKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (this.#options.size) {
|
||||||
|
case AssetMediaSize.Original: {
|
||||||
|
path = getAssetOriginalPath(this.asset.id);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case AssetMediaSize.Fullsize:
|
||||||
|
case AssetMediaSize.Thumbnail:
|
||||||
|
case AssetMediaSize.Preview: {
|
||||||
|
path = getAssetThumbnailPath(this.asset.id);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case AssetMediaSize.Playback: {
|
||||||
|
path = getAssetPlaybackPath(this.asset.id);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// TODO: default AssetMediaSize
|
||||||
|
}
|
||||||
|
|
||||||
|
return getBaseUrl() + path + '?' + searchParameters.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
get isOriginalImage() {
|
||||||
|
return this.#options.size === AssetMediaSize.Original || this.#options.size === AssetMediaSize.Fullsize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// const getAssetUrl = (id: string, targetSize: AssetMediaSize | 'original', cacheKey: string | null) => {
|
||||||
|
// if (sharedLink && (!sharedLink.allowDownload || !sharedLink.showMetadata)) {
|
||||||
|
// return getAssetThumbnailUrl({ id, size: AssetMediaSize.Preview, cacheKey });
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return targetSize === 'original'
|
||||||
|
// ? getAssetOriginalUrl({ id, cacheKey })
|
||||||
|
// : getAssetThumbnailUrl({ id, size: targetSize, cacheKey });
|
||||||
|
// };
|
||||||
|
|
||||||
|
// $effect(() => {
|
||||||
|
// if ($alwaysLoadOriginalFile || forceUseOriginal || originalImageLoaded) {
|
||||||
|
// assetManager.updateOptions({
|
||||||
|
// size: isWebCompatibleImage(asset) ? AssetMediaSize.Original : AssetMediaSize.Fullsize,
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
// assetManager.updateOptions({ size: AssetMediaSize.Preview });
|
||||||
|
// });
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import type { AssetManager } from '$lib/managers/asset-manager/asset-manager.svelte';
|
||||||
|
import { cancelImageUrl } from '$lib/utils/sw-messaging';
|
||||||
|
|
||||||
|
export function mediaLoaded(assetManager: AssetManager) {
|
||||||
|
assetManager.isLoaded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function mediaLoadError(assetManager: AssetManager) {
|
||||||
|
assetManager.isLoaded = assetManager.loadError = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function cancelImageLoad(assetManager: AssetManager) {
|
||||||
|
if (assetManager.url) {
|
||||||
|
cancelImageUrl(assetManager.url);
|
||||||
|
}
|
||||||
|
assetManager.isLoaded = assetManager.loadError = false;
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import type { AssetManager } from '$lib/managers/asset-manager/asset-manager.svelte';
|
||||||
|
import { useZoomImageWheel } from '@zoom-image/svelte';
|
||||||
|
import type { Attachment } from 'svelte/attachments';
|
||||||
|
|
||||||
|
export function zoomImageAttachment(assetManager: AssetManager): Attachment<HTMLElement> {
|
||||||
|
return (element) => {
|
||||||
|
let zoomImage = $derived(assetManager.zoomImageState);
|
||||||
|
const { createZoomImage, zoomImageState, setZoomImageState } = useZoomImageWheel();
|
||||||
|
|
||||||
|
createZoomImage(element, { maxZoom: 10 });
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
if (zoomImage) {
|
||||||
|
setZoomImageState(zoomImage);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const unsubscribe = zoomImageState.subscribe((value) => (zoomImage = value));
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
unsubscribe();
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
import type { ZoomImageWheelState } from '@zoom-image/core';
|
|
||||||
import { writable } from 'svelte/store';
|
|
||||||
|
|
||||||
export const photoZoomState = writable<ZoomImageWheelState>();
|
|
||||||
+1
-1
@@ -34,7 +34,7 @@
|
|||||||
import UserAvatar from '$lib/components/shared-components/user-avatar.svelte';
|
import UserAvatar from '$lib/components/shared-components/user-avatar.svelte';
|
||||||
import { AlbumPageViewMode, AppRoute } from '$lib/constants';
|
import { AlbumPageViewMode, AppRoute } from '$lib/constants';
|
||||||
import { activityManager } from '$lib/managers/activity-manager.svelte';
|
import { activityManager } from '$lib/managers/activity-manager.svelte';
|
||||||
import { AssetManager } from '$lib/managers/asset-manager.svelte';
|
import { AssetManager } from '$lib/managers/asset-manager/asset-manager.svelte';
|
||||||
import { modalManager } from '$lib/managers/modal-manager.svelte';
|
import { modalManager } from '$lib/managers/modal-manager.svelte';
|
||||||
import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
|
import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
|
||||||
import type { TimelineAsset } from '$lib/managers/timeline-manager/types';
|
import type { TimelineAsset } from '$lib/managers/timeline-manager/types';
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
import { AssetAction } from '$lib/constants';
|
import { AssetAction } from '$lib/constants';
|
||||||
|
|
||||||
import SetVisibilityAction from '$lib/components/photos-page/actions/set-visibility-action.svelte';
|
import SetVisibilityAction from '$lib/components/photos-page/actions/set-visibility-action.svelte';
|
||||||
import { AssetManager } from '$lib/managers/asset-manager.svelte';
|
import { AssetManager } from '$lib/managers/asset-manager/asset-manager.svelte';
|
||||||
import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
|
import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
|
||||||
import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
|
import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
|
||||||
import { AssetVisibility } from '@immich/sdk';
|
import { AssetVisibility } from '@immich/sdk';
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
import ButtonContextMenu from '$lib/components/shared-components/context-menu/button-context-menu.svelte';
|
import ButtonContextMenu from '$lib/components/shared-components/context-menu/button-context-menu.svelte';
|
||||||
import EmptyPlaceholder from '$lib/components/shared-components/empty-placeholder.svelte';
|
import EmptyPlaceholder from '$lib/components/shared-components/empty-placeholder.svelte';
|
||||||
import { AssetAction } from '$lib/constants';
|
import { AssetAction } from '$lib/constants';
|
||||||
import { AssetManager } from '$lib/managers/asset-manager.svelte';
|
import { AssetManager } from '$lib/managers/asset-manager/asset-manager.svelte';
|
||||||
import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
|
import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
|
||||||
import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
|
import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
|
||||||
import { preferences } from '$lib/stores/user.store';
|
import { preferences } from '$lib/stores/user.store';
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
import { mdiDotsVertical, mdiFolder, mdiFolderHome, mdiFolderOutline, mdiPlus, mdiSelectAll } from '@mdi/js';
|
import { mdiDotsVertical, mdiFolder, mdiFolderHome, mdiFolderOutline, mdiPlus, mdiSelectAll } from '@mdi/js';
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
import type { PageData } from './$types';
|
import type { PageData } from './$types';
|
||||||
import { AssetManager } from '$lib/managers/asset-manager.svelte';
|
import { AssetManager } from '$lib/managers/asset-manager/asset-manager.svelte';
|
||||||
import { onDestroy } from 'svelte';
|
import { onDestroy } from 'svelte';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
import ButtonContextMenu from '$lib/components/shared-components/context-menu/button-context-menu.svelte';
|
import ButtonContextMenu from '$lib/components/shared-components/context-menu/button-context-menu.svelte';
|
||||||
import EmptyPlaceholder from '$lib/components/shared-components/empty-placeholder.svelte';
|
import EmptyPlaceholder from '$lib/components/shared-components/empty-placeholder.svelte';
|
||||||
import { AppRoute, AssetAction } from '$lib/constants';
|
import { AppRoute, AssetAction } from '$lib/constants';
|
||||||
import { AssetManager } from '$lib/managers/asset-manager.svelte';
|
import { AssetManager } from '$lib/managers/asset-manager/asset-manager.svelte';
|
||||||
import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
|
import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
|
||||||
import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
|
import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
|
||||||
import { AssetVisibility, lockAuthSession } from '@immich/sdk';
|
import { AssetVisibility, lockAuthSession } from '@immich/sdk';
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
import Map from '$lib/components/shared-components/map/map.svelte';
|
import Map from '$lib/components/shared-components/map/map.svelte';
|
||||||
import Portal from '$lib/components/shared-components/portal/portal.svelte';
|
import Portal from '$lib/components/shared-components/portal/portal.svelte';
|
||||||
import { AppRoute } from '$lib/constants';
|
import { AppRoute } from '$lib/constants';
|
||||||
import { AssetManager } from '$lib/managers/asset-manager.svelte';
|
import { AssetManager } from '$lib/managers/asset-manager/asset-manager.svelte';
|
||||||
import { featureFlags } from '$lib/stores/server-config.store';
|
import { featureFlags } from '$lib/stores/server-config.store';
|
||||||
import { handlePromiseError } from '$lib/utils';
|
import { handlePromiseError } from '$lib/utils';
|
||||||
import { navigate } from '$lib/utils/navigation';
|
import { navigate } from '$lib/utils/navigation';
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import MemoryViewer from '$lib/components/memory-page/memory-viewer.svelte';
|
import MemoryViewer from '$lib/components/memory-page/memory-viewer.svelte';
|
||||||
import { AssetManager } from '$lib/managers/asset-manager.svelte';
|
import { AssetManager } from '$lib/managers/asset-manager/asset-manager.svelte';
|
||||||
import { onDestroy } from 'svelte';
|
import { onDestroy } from 'svelte';
|
||||||
import type { PageData } from './$types';
|
import type { PageData } from './$types';
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -8,7 +8,7 @@
|
|||||||
import ButtonContextMenu from '$lib/components/shared-components/context-menu/button-context-menu.svelte';
|
import ButtonContextMenu from '$lib/components/shared-components/context-menu/button-context-menu.svelte';
|
||||||
import ControlAppBar from '$lib/components/shared-components/control-app-bar.svelte';
|
import ControlAppBar from '$lib/components/shared-components/control-app-bar.svelte';
|
||||||
import { AppRoute } from '$lib/constants';
|
import { AppRoute } from '$lib/constants';
|
||||||
import { AssetManager } from '$lib/managers/asset-manager.svelte';
|
import { AssetManager } from '$lib/managers/asset-manager/asset-manager.svelte';
|
||||||
import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
|
import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
|
||||||
import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
|
import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
|
||||||
import { AssetVisibility } from '@immich/sdk';
|
import { AssetVisibility } from '@immich/sdk';
|
||||||
|
|||||||
+1
-1
@@ -31,7 +31,7 @@
|
|||||||
notificationController,
|
notificationController,
|
||||||
} from '$lib/components/shared-components/notification/notification';
|
} from '$lib/components/shared-components/notification/notification';
|
||||||
import { AppRoute, PersonPageViewMode, QueryParameter, SessionStorageKey } from '$lib/constants';
|
import { AppRoute, PersonPageViewMode, QueryParameter, SessionStorageKey } from '$lib/constants';
|
||||||
import { AssetManager } from '$lib/managers/asset-manager.svelte';
|
import { AssetManager } from '$lib/managers/asset-manager/asset-manager.svelte';
|
||||||
import { modalManager } from '$lib/managers/modal-manager.svelte';
|
import { modalManager } from '$lib/managers/modal-manager.svelte';
|
||||||
import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
|
import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
|
||||||
import type { TimelineAsset } from '$lib/managers/timeline-manager/types';
|
import type { TimelineAsset } from '$lib/managers/timeline-manager/types';
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
import ButtonContextMenu from '$lib/components/shared-components/context-menu/button-context-menu.svelte';
|
import ButtonContextMenu from '$lib/components/shared-components/context-menu/button-context-menu.svelte';
|
||||||
import EmptyPlaceholder from '$lib/components/shared-components/empty-placeholder.svelte';
|
import EmptyPlaceholder from '$lib/components/shared-components/empty-placeholder.svelte';
|
||||||
import { AssetAction } from '$lib/constants';
|
import { AssetAction } from '$lib/constants';
|
||||||
import { AssetManager } from '$lib/managers/asset-manager.svelte';
|
import { AssetManager } from '$lib/managers/asset-manager/asset-manager.svelte';
|
||||||
import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
|
import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
|
||||||
import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
|
import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
|
||||||
import { isFaceEditMode } from '$lib/stores/face-edit.svelte';
|
import { isFaceEditMode } from '$lib/stores/face-edit.svelte';
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
import LoadingSpinner from '$lib/components/shared-components/loading-spinner.svelte';
|
import LoadingSpinner from '$lib/components/shared-components/loading-spinner.svelte';
|
||||||
import SearchBar from '$lib/components/shared-components/search-bar/search-bar.svelte';
|
import SearchBar from '$lib/components/shared-components/search-bar/search-bar.svelte';
|
||||||
import { AppRoute, QueryParameter } from '$lib/constants';
|
import { AppRoute, QueryParameter } from '$lib/constants';
|
||||||
import { AssetManager } from '$lib/managers/asset-manager.svelte';
|
import { AssetManager } from '$lib/managers/asset-manager/asset-manager.svelte';
|
||||||
import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
|
import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
|
||||||
import type { TimelineAsset, Viewport } from '$lib/managers/timeline-manager/types';
|
import type { TimelineAsset, Viewport } from '$lib/managers/timeline-manager/types';
|
||||||
import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
|
import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
import ImmichLogoSmallLink from '$lib/components/shared-components/immich-logo-small-link.svelte';
|
import ImmichLogoSmallLink from '$lib/components/shared-components/immich-logo-small-link.svelte';
|
||||||
import PasswordField from '$lib/components/shared-components/password-field.svelte';
|
import PasswordField from '$lib/components/shared-components/password-field.svelte';
|
||||||
import ThemeButton from '$lib/components/shared-components/theme-button.svelte';
|
import ThemeButton from '$lib/components/shared-components/theme-button.svelte';
|
||||||
import { AssetManager } from '$lib/managers/asset-manager.svelte';
|
import { AssetManager } from '$lib/managers/asset-manager/asset-manager.svelte';
|
||||||
import { user } from '$lib/stores/user.store';
|
import { user } from '$lib/stores/user.store';
|
||||||
import { setSharedLink } from '$lib/utils';
|
import { setSharedLink } from '$lib/utils';
|
||||||
import { handleError } from '$lib/utils/handle-error';
|
import { handleError } from '$lib/utils/handle-error';
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
import { onDestroy } from 'svelte';
|
import { onDestroy } from 'svelte';
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
import type { PageData } from './$types';
|
import type { PageData } from './$types';
|
||||||
import { AssetManager } from '$lib/managers/asset-manager.svelte';
|
import { AssetManager } from '$lib/managers/asset-manager/asset-manager.svelte';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
data: PageData;
|
data: PageData;
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
notificationController,
|
notificationController,
|
||||||
} from '$lib/components/shared-components/notification/notification';
|
} from '$lib/components/shared-components/notification/notification';
|
||||||
import { AppRoute } from '$lib/constants';
|
import { AppRoute } from '$lib/constants';
|
||||||
import { AssetManager } from '$lib/managers/asset-manager.svelte';
|
import { AssetManager } from '$lib/managers/asset-manager/asset-manager.svelte';
|
||||||
import { modalManager } from '$lib/managers/modal-manager.svelte';
|
import { modalManager } from '$lib/managers/modal-manager.svelte';
|
||||||
import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
|
import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
|
||||||
import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
|
import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
|
||||||
|
|||||||
+1
-1
@@ -20,7 +20,7 @@
|
|||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
import type { PageData } from './$types';
|
import type { PageData } from './$types';
|
||||||
import { onDestroy } from 'svelte';
|
import { onDestroy } from 'svelte';
|
||||||
import { AssetManager } from '$lib/managers/asset-manager.svelte';
|
import { AssetManager } from '$lib/managers/asset-manager/asset-manager.svelte';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
data: PageData;
|
data: PageData;
|
||||||
|
|||||||
Reference in New Issue
Block a user