mirror of
https://github.com/immich-app/immich
synced 2025-11-14 17:36:12 +00:00
parent
f69ce6ad8a
commit
7fbf50a75e
18 changed files with 78 additions and 136 deletions
|
|
@ -4,9 +4,9 @@
|
|||
import MotionPhotoAction from '$lib/components/asset-viewer/actions/motion-photo-action.svelte';
|
||||
import NextAssetAction from '$lib/components/asset-viewer/actions/next-asset-action.svelte';
|
||||
import PreviousAssetAction from '$lib/components/asset-viewer/actions/previous-asset-action.svelte';
|
||||
import Icon from '$lib/components/elements/icon.svelte';
|
||||
import { AssetAction, ProjectionType } from '$lib/constants';
|
||||
import { updateNumberOfComments } from '$lib/stores/activity.store';
|
||||
import { closeEditorCofirm } from '$lib/stores/asset-editor.store';
|
||||
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
|
||||
import type { AssetStore } from '$lib/stores/assets.store';
|
||||
import { isShowDetail } from '$lib/stores/preferences.store';
|
||||
|
|
@ -25,14 +25,13 @@
|
|||
getActivities,
|
||||
getActivityStatistics,
|
||||
getAllAlbums,
|
||||
getStack,
|
||||
runAssetJobs,
|
||||
type ActivityResponseDto,
|
||||
type AlbumResponseDto,
|
||||
type AssetResponseDto,
|
||||
getStack,
|
||||
type StackResponseDto,
|
||||
} from '@immich/sdk';
|
||||
import { mdiImageBrokenVariant } from '@mdi/js';
|
||||
import { createEventDispatcher, onDestroy, onMount } from 'svelte';
|
||||
import { t } from 'svelte-i18n';
|
||||
import { fly } from 'svelte/transition';
|
||||
|
|
@ -42,13 +41,13 @@
|
|||
import ActivityViewer from './activity-viewer.svelte';
|
||||
import AssetViewerNavBar from './asset-viewer-nav-bar.svelte';
|
||||
import DetailPanel from './detail-panel.svelte';
|
||||
import CropArea from './editor/crop-tool/crop-area.svelte';
|
||||
import EditorPanel from './editor/editor-panel.svelte';
|
||||
import PanoramaViewer from './panorama-viewer.svelte';
|
||||
import PhotoViewer from './photo-viewer.svelte';
|
||||
import SlideshowBar from './slideshow-bar.svelte';
|
||||
import VideoViewer from './video-wrapper-viewer.svelte';
|
||||
import EditorPanel from './editor/editor-panel.svelte';
|
||||
import CropArea from './editor/crop-tool/crop-area.svelte';
|
||||
import { closeEditorCofirm } from '$lib/stores/asset-editor.store';
|
||||
|
||||
export let assetStore: AssetStore | null = null;
|
||||
export let asset: AssetResponseDto;
|
||||
export let preloadAssets: AssetResponseDto[] = [];
|
||||
|
|
@ -481,15 +480,7 @@
|
|||
{/key}
|
||||
{:else}
|
||||
{#key asset.id}
|
||||
{#if !asset.resized}
|
||||
<div class="flex h-full w-full justify-center">
|
||||
<div
|
||||
class="px-auto flex aspect-square h-full items-center justify-center bg-gray-100 dark:bg-immich-dark-gray"
|
||||
>
|
||||
<Icon path={mdiImageBrokenVariant} size="25%" />
|
||||
</div>
|
||||
</div>
|
||||
{:else if asset.type === AssetTypeEnum.Image}
|
||||
{#if asset.type === AssetTypeEnum.Image}
|
||||
{#if shouldPlayMotionPhoto && asset.livePhotoVideoId}
|
||||
<VideoViewer
|
||||
assetId={asset.livePhotoVideoId}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
<script lang="ts">
|
||||
import { shortcuts } from '$lib/actions/shortcut';
|
||||
import { zoomImageAction, zoomed } from '$lib/actions/zoom-image';
|
||||
import BrokenAsset from '$lib/components/assets/broken-asset.svelte';
|
||||
import { photoViewer } from '$lib/stores/assets.store';
|
||||
import { boundingBoxesArray } from '$lib/stores/people.store';
|
||||
import { alwaysLoadOriginalFile } from '$lib/stores/preferences.store';
|
||||
|
|
@ -9,15 +11,13 @@
|
|||
import { isWebCompatibleImage } from '$lib/utils/asset-utils';
|
||||
import { getBoundingBox } from '$lib/utils/people-utils';
|
||||
import { getAltText } from '$lib/utils/thumbnail-util';
|
||||
import { AssetTypeEnum, type AssetResponseDto, AssetMediaSize, type SharedLinkResponseDto } from '@immich/sdk';
|
||||
import { zoomImageAction, zoomed } from '$lib/actions/zoom-image';
|
||||
import { AssetMediaSize, AssetTypeEnum, type AssetResponseDto, type SharedLinkResponseDto } from '@immich/sdk';
|
||||
import { canCopyImagesToClipboard, copyImageToClipboard } from 'copy-image-clipboard';
|
||||
import { onDestroy, onMount } from 'svelte';
|
||||
|
||||
import { t } from 'svelte-i18n';
|
||||
import { fade } from 'svelte/transition';
|
||||
import LoadingSpinner from '../shared-components/loading-spinner.svelte';
|
||||
import { NotificationType, notificationController } from '../shared-components/notification/notification';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
export let asset: AssetResponseDto;
|
||||
export let preloadAssets: AssetResponseDto[] | undefined = undefined;
|
||||
|
|
@ -137,7 +137,7 @@
|
|||
]}
|
||||
/>
|
||||
{#if imageError}
|
||||
<div class="h-full flex items-center justify-center">{$t('error_loading_image')}</div>
|
||||
<BrokenAsset square />
|
||||
{/if}
|
||||
<!-- svelte-ignore a11y-missing-attribute -->
|
||||
<img bind:this={loader} style="display:none" src={imageLoaderUrl} aria-hidden="true" />
|
||||
|
|
|
|||
25
web/src/lib/components/assets/broken-asset.svelte
Normal file
25
web/src/lib/components/assets/broken-asset.svelte
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
<script lang="ts">
|
||||
import Icon from '$lib/components/elements/icon.svelte';
|
||||
import { mdiImageBrokenVariant } from '@mdi/js';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
export let square = false;
|
||||
export let noMessage = false;
|
||||
</script>
|
||||
|
||||
<section class="flex h-full w-full justify-center">
|
||||
<div
|
||||
class="px-auto flex flex-col {square
|
||||
? 'aspect-square'
|
||||
: 'w-full'} h-full items-center justify-center bg-gray-100 dark:text-gray-100 dark:bg-immich-dark-gray"
|
||||
>
|
||||
<slot name="message">
|
||||
{#if !noMessage}
|
||||
<div class="text-lg absolute top-2/3">{$t('error_loading_image')}</div>
|
||||
{/if}
|
||||
</slot>
|
||||
<div class="flex h-full w-full items-center justify-center p-4 bg-gray-100 dark:text-slate-200 dark:bg-gray-700">
|
||||
<Icon path={mdiImageBrokenVariant} size="33%" />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
import { fade } from 'svelte/transition';
|
||||
|
||||
import { thumbhash } from '$lib/actions/thumbhash';
|
||||
import BrokenAsset from '$lib/components/assets/broken-asset.svelte';
|
||||
import Icon from '$lib/components/elements/icon.svelte';
|
||||
import { TUNABLES } from '$lib/utils/tunables';
|
||||
import { mdiEyeOffOutline, mdiImageBrokenVariant } from '@mdi/js';
|
||||
import { mdiEyeOffOutline } from '@mdi/js';
|
||||
import { onMount } from 'svelte';
|
||||
import { t } from 'svelte-i18n';
|
||||
import { fade } from 'svelte/transition';
|
||||
|
||||
export let url: string;
|
||||
export let altText: string | undefined;
|
||||
|
|
@ -48,9 +48,9 @@
|
|||
</script>
|
||||
|
||||
{#if errored}
|
||||
<div class="absolute flex h-full w-full items-center justify-center p-4 z-10">
|
||||
<Icon path={mdiImageBrokenVariant} size="48" />
|
||||
</div>
|
||||
<BrokenAsset>
|
||||
<div slot="message" class="absolute top-2/3">{$t('error_loading_image')}</div>
|
||||
</BrokenAsset>
|
||||
{:else}
|
||||
<img
|
||||
bind:this={img}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@
|
|||
mdiCameraBurst,
|
||||
mdiCheckCircle,
|
||||
mdiHeart,
|
||||
mdiImageBrokenVariant,
|
||||
mdiMotionPauseOutline,
|
||||
mdiMotionPlayOutline,
|
||||
mdiRotate360,
|
||||
|
|
@ -297,20 +296,14 @@
|
|||
</div>
|
||||
{/if}
|
||||
|
||||
{#if asset.resized}
|
||||
<ImageThumbnail
|
||||
url={getAssetThumbnailUrl({ id: asset.id, size: AssetMediaSize.Thumbnail, checksum: asset.checksum })}
|
||||
altText={$getAltText(asset)}
|
||||
widthStyle="{width}px"
|
||||
heightStyle="{height}px"
|
||||
curve={selected}
|
||||
onComplete={() => (loaded = true)}
|
||||
/>
|
||||
{:else}
|
||||
<div class="absolute flex h-full w-full items-center justify-center p-4 z-10">
|
||||
<Icon path={mdiImageBrokenVariant} size="48" />
|
||||
</div>
|
||||
{/if}
|
||||
<ImageThumbnail
|
||||
url={getAssetThumbnailUrl({ id: asset.id, size: AssetMediaSize.Thumbnail, checksum: asset.checksum })}
|
||||
altText={$getAltText(asset)}
|
||||
widthStyle="{width}px"
|
||||
heightStyle="{height}px"
|
||||
curve={selected}
|
||||
onComplete={() => (loaded = true)}
|
||||
/>
|
||||
|
||||
{#if asset.type === AssetTypeEnum.Video}
|
||||
<div class="absolute top-0 h-full w-full">
|
||||
|
|
|
|||
|
|
@ -47,13 +47,15 @@ describe('ShareCover component', () => {
|
|||
expect(img.className).toBe('z-0 rounded-xl object-cover aspect-square text');
|
||||
});
|
||||
|
||||
it('renders fallback image when asset is not resized', () => {
|
||||
const link = sharedLinkFactory.build({ assets: [assetFactory.build({ resized: false })] });
|
||||
it.skip('renders fallback image when asset is not resized', () => {
|
||||
const link = sharedLinkFactory.build({ assets: [assetFactory.build()] });
|
||||
render(ShareCover, {
|
||||
link: link,
|
||||
preload: false,
|
||||
});
|
||||
|
||||
// TODO emit image error event and check if fallback image is rendered
|
||||
|
||||
const img = screen.getByTestId<HTMLImageElement>('album-image');
|
||||
expect(img.alt).toBe('unnamed_share');
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,16 +1,25 @@
|
|||
<script lang="ts">
|
||||
import BrokenAsset from '$lib/components/assets/broken-asset.svelte';
|
||||
|
||||
export let alt;
|
||||
export let preload = false;
|
||||
export let src: string;
|
||||
let className = '';
|
||||
export { className as class };
|
||||
|
||||
let isBroken = false;
|
||||
</script>
|
||||
|
||||
<img
|
||||
{alt}
|
||||
class="z-0 rounded-xl object-cover aspect-square {className}"
|
||||
data-testid="album-image"
|
||||
draggable="false"
|
||||
loading={preload ? 'eager' : 'lazy'}
|
||||
{src}
|
||||
/>
|
||||
{#if isBroken}
|
||||
<BrokenAsset noMessage />
|
||||
{:else}
|
||||
<img
|
||||
{alt}
|
||||
on:error={() => (isBroken = true)}
|
||||
class="z-0 rounded-xl object-cover aspect-square {className}"
|
||||
data-testid="album-image"
|
||||
draggable="false"
|
||||
loading={preload ? 'eager' : 'lazy'}
|
||||
{src}
|
||||
/>
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -12,10 +12,10 @@
|
|||
export { className as class };
|
||||
</script>
|
||||
|
||||
<div class="relative shrink-0">
|
||||
<div class="relative shrink-0 size-24">
|
||||
{#if link?.album}
|
||||
<AlbumCover album={link.album} class={className} {preload} />
|
||||
{:else if link.assets[0]?.resized}
|
||||
{:else if link.assets[0]}
|
||||
<AssetCover
|
||||
alt={$t('individual_share')}
|
||||
class={className}
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@
|
|||
href={isExpired ? undefined : `${AppRoute.SHARE}/${link.key}`}
|
||||
class="flex gap-4 w-full py-4"
|
||||
>
|
||||
<ShareCover class="size-24 transition-all duration-300 hover:shadow-lg" {link} />
|
||||
<ShareCover class="transition-all duration-300 hover:shadow-lg" {link} />
|
||||
|
||||
<div class="flex flex-col justify-between">
|
||||
<div class="info-top">
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue