mirror of
https://github.com/immich-app/immich.git
synced 2024-12-28 22:51:59 +00:00
parent
f69ce6ad8a
commit
7fbf50a75e
18 changed files with 77 additions and 127 deletions
|
@ -843,7 +843,6 @@ describe('/asset', () => {
|
|||
expected: {
|
||||
type: AssetTypeEnum.Image,
|
||||
originalFileName: '8bit-sRGB.avif',
|
||||
resized: true,
|
||||
exifInfo: {
|
||||
description: '',
|
||||
exifImageHeight: 1080,
|
||||
|
@ -859,7 +858,6 @@ describe('/asset', () => {
|
|||
expected: {
|
||||
type: AssetTypeEnum.Image,
|
||||
originalFileName: 'el_torcal_rocks.jpg',
|
||||
resized: true,
|
||||
exifInfo: {
|
||||
dateTimeOriginal: '2012-08-05T11:39:59.000Z',
|
||||
exifImageWidth: 512,
|
||||
|
@ -883,7 +881,6 @@ describe('/asset', () => {
|
|||
expected: {
|
||||
type: AssetTypeEnum.Image,
|
||||
originalFileName: '8bit-sRGB.jxl',
|
||||
resized: true,
|
||||
exifInfo: {
|
||||
description: '',
|
||||
exifImageHeight: 1080,
|
||||
|
@ -899,7 +896,6 @@ describe('/asset', () => {
|
|||
expected: {
|
||||
type: AssetTypeEnum.Image,
|
||||
originalFileName: 'IMG_2682.heic',
|
||||
resized: true,
|
||||
fileCreatedAt: '2019-03-21T16:04:22.348Z',
|
||||
exifInfo: {
|
||||
dateTimeOriginal: '2019-03-21T16:04:22.348Z',
|
||||
|
@ -924,7 +920,6 @@ describe('/asset', () => {
|
|||
expected: {
|
||||
type: AssetTypeEnum.Image,
|
||||
originalFileName: 'density_plot.png',
|
||||
resized: true,
|
||||
exifInfo: {
|
||||
exifImageWidth: 800,
|
||||
exifImageHeight: 800,
|
||||
|
@ -939,7 +934,6 @@ describe('/asset', () => {
|
|||
expected: {
|
||||
type: AssetTypeEnum.Image,
|
||||
originalFileName: 'glarus.nef',
|
||||
resized: true,
|
||||
fileCreatedAt: '2010-07-20T17:27:12.000Z',
|
||||
exifInfo: {
|
||||
make: 'NIKON CORPORATION',
|
||||
|
@ -961,7 +955,6 @@ describe('/asset', () => {
|
|||
expected: {
|
||||
type: AssetTypeEnum.Image,
|
||||
originalFileName: 'philadelphia.nef',
|
||||
resized: true,
|
||||
fileCreatedAt: '2016-09-22T22:10:29.060Z',
|
||||
exifInfo: {
|
||||
make: 'NIKON CORPORATION',
|
||||
|
@ -984,7 +977,6 @@ describe('/asset', () => {
|
|||
expected: {
|
||||
type: AssetTypeEnum.Image,
|
||||
originalFileName: '4_3.rw2',
|
||||
resized: true,
|
||||
fileCreatedAt: '2018-05-10T08:42:37.842Z',
|
||||
exifInfo: {
|
||||
make: 'Panasonic',
|
||||
|
@ -1008,7 +1000,6 @@ describe('/asset', () => {
|
|||
expected: {
|
||||
type: AssetTypeEnum.Image,
|
||||
originalFileName: '12bit-compressed-(3_2).arw',
|
||||
resized: true,
|
||||
fileCreatedAt: '2016-09-27T10:51:44.000Z',
|
||||
exifInfo: {
|
||||
make: 'SONY',
|
||||
|
@ -1033,7 +1024,6 @@ describe('/asset', () => {
|
|||
expected: {
|
||||
type: AssetTypeEnum.Image,
|
||||
originalFileName: '14bit-uncompressed-(3_2).arw',
|
||||
resized: true,
|
||||
fileCreatedAt: '2016-01-08T14:08:01.000Z',
|
||||
exifInfo: {
|
||||
make: 'SONY',
|
||||
|
|
BIN
mobile/openapi/lib/model/asset_response_dto.dart
generated
BIN
mobile/openapi/lib/model/asset_response_dto.dart
generated
Binary file not shown.
|
@ -8335,9 +8335,6 @@
|
|||
},
|
||||
"type": "array"
|
||||
},
|
||||
"resized": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"smartInfo": {
|
||||
"$ref": "#/components/schemas/SmartInfoResponseDto"
|
||||
},
|
||||
|
@ -8390,7 +8387,6 @@
|
|||
"originalFileName",
|
||||
"originalPath",
|
||||
"ownerId",
|
||||
"resized",
|
||||
"thumbhash",
|
||||
"type",
|
||||
"updatedAt"
|
||||
|
|
|
@ -229,7 +229,6 @@ export type AssetResponseDto = {
|
|||
owner?: UserResponseDto;
|
||||
ownerId: string;
|
||||
people?: PersonWithFacesResponseDto[];
|
||||
resized: boolean;
|
||||
smartInfo?: SmartInfoResponseDto;
|
||||
stack?: (AssetStackResponseDto) | null;
|
||||
tags?: TagResponseDto[];
|
||||
|
|
|
@ -14,7 +14,6 @@ import { AssetFaceEntity } from 'src/entities/asset-face.entity';
|
|||
import { AssetEntity } from 'src/entities/asset.entity';
|
||||
import { SmartInfoEntity } from 'src/entities/smart-info.entity';
|
||||
import { AssetType } from 'src/enum';
|
||||
import { getAssetFiles } from 'src/utils/asset.util';
|
||||
import { mimeTypes } from 'src/utils/mime-types';
|
||||
|
||||
export class SanitizedAssetResponseDto {
|
||||
|
@ -23,7 +22,6 @@ export class SanitizedAssetResponseDto {
|
|||
type!: AssetType;
|
||||
thumbhash!: string | null;
|
||||
originalMimeType?: string;
|
||||
resized!: boolean;
|
||||
localDateTime!: Date;
|
||||
duration!: string;
|
||||
livePhotoVideoId?: string | null;
|
||||
|
@ -112,7 +110,6 @@ export function mapAsset(entity: AssetEntity, options: AssetMapOptions = {}): As
|
|||
originalMimeType: mimeTypes.lookup(entity.originalFileName),
|
||||
thumbhash: entity.thumbhash?.toString('base64') ?? null,
|
||||
localDateTime: entity.localDateTime,
|
||||
resized: !!getAssetFiles(entity.files).previewFile,
|
||||
duration: entity.duration ?? '0:00:00.00000',
|
||||
livePhotoVideoId: entity.livePhotoVideoId,
|
||||
hasMetadata: false,
|
||||
|
@ -131,7 +128,6 @@ export function mapAsset(entity: AssetEntity, options: AssetMapOptions = {}): As
|
|||
originalPath: entity.originalPath,
|
||||
originalFileName: entity.originalFileName,
|
||||
originalMimeType: mimeTypes.lookup(entity.originalFileName),
|
||||
resized: !!getAssetFiles(entity.files).previewFile,
|
||||
thumbhash: entity.thumbhash?.toString('base64') ?? null,
|
||||
fileCreatedAt: entity.fileCreatedAt,
|
||||
fileModifiedAt: entity.fileModifiedAt,
|
||||
|
|
|
@ -598,12 +598,6 @@ SELECT
|
|||
"asset"."sidecarPath" AS "asset_sidecarPath",
|
||||
"asset"."stackId" AS "asset_stackId",
|
||||
"asset"."duplicateId" AS "asset_duplicateId",
|
||||
"files"."id" AS "files_id",
|
||||
"files"."assetId" AS "files_assetId",
|
||||
"files"."createdAt" AS "files_createdAt",
|
||||
"files"."updatedAt" AS "files_updatedAt",
|
||||
"files"."type" AS "files_type",
|
||||
"files"."path" AS "files_path",
|
||||
"exifInfo"."assetId" AS "exifInfo_assetId",
|
||||
"exifInfo"."description" AS "exifInfo_description",
|
||||
"exifInfo"."exifImageWidth" AS "exifInfo_exifImageWidth",
|
||||
|
@ -665,7 +659,6 @@ SELECT
|
|||
"stackedAssets"."duplicateId" AS "stackedAssets_duplicateId"
|
||||
FROM
|
||||
"assets" "asset"
|
||||
LEFT JOIN "asset_files" "files" ON "files"."assetId" = "asset"."id"
|
||||
LEFT JOIN "exif" "exifInfo" ON "exifInfo"."assetId" = "asset"."id"
|
||||
LEFT JOIN "asset_stack" "stack" ON "stack"."id" = "asset"."stackId"
|
||||
LEFT JOIN "assets" "stackedAssets" ON "stackedAssets"."stackId" = "stack"."id"
|
||||
|
@ -692,7 +685,6 @@ SELECT
|
|||
)::timestamptz AS "timeBucket"
|
||||
FROM
|
||||
"assets" "asset"
|
||||
LEFT JOIN "asset_files" "files" ON "files"."assetId" = "asset"."id"
|
||||
LEFT JOIN "exif" "exifInfo" ON "exifInfo"."assetId" = "asset"."id"
|
||||
LEFT JOIN "asset_stack" "stack" ON "stack"."id" = "asset"."stackId"
|
||||
LEFT JOIN "assets" "stackedAssets" ON "stackedAssets"."stackId" = "stack"."id"
|
||||
|
@ -744,12 +736,6 @@ SELECT
|
|||
"asset"."sidecarPath" AS "asset_sidecarPath",
|
||||
"asset"."stackId" AS "asset_stackId",
|
||||
"asset"."duplicateId" AS "asset_duplicateId",
|
||||
"files"."id" AS "files_id",
|
||||
"files"."assetId" AS "files_assetId",
|
||||
"files"."createdAt" AS "files_createdAt",
|
||||
"files"."updatedAt" AS "files_updatedAt",
|
||||
"files"."type" AS "files_type",
|
||||
"files"."path" AS "files_path",
|
||||
"exifInfo"."assetId" AS "exifInfo_assetId",
|
||||
"exifInfo"."description" AS "exifInfo_description",
|
||||
"exifInfo"."exifImageWidth" AS "exifInfo_exifImageWidth",
|
||||
|
@ -811,7 +797,6 @@ SELECT
|
|||
"stackedAssets"."duplicateId" AS "stackedAssets_duplicateId"
|
||||
FROM
|
||||
"assets" "asset"
|
||||
LEFT JOIN "asset_files" "files" ON "files"."assetId" = "asset"."id"
|
||||
LEFT JOIN "exif" "exifInfo" ON "exifInfo"."assetId" = "asset"."id"
|
||||
LEFT JOIN "asset_stack" "stack" ON "stack"."id" = "asset"."stackId"
|
||||
LEFT JOIN "assets" "stackedAssets" ON "stackedAssets"."stackId" = "stack"."id"
|
||||
|
@ -865,12 +850,6 @@ SELECT
|
|||
"asset"."sidecarPath" AS "asset_sidecarPath",
|
||||
"asset"."stackId" AS "asset_stackId",
|
||||
"asset"."duplicateId" AS "asset_duplicateId",
|
||||
"files"."id" AS "files_id",
|
||||
"files"."assetId" AS "files_assetId",
|
||||
"files"."createdAt" AS "files_createdAt",
|
||||
"files"."updatedAt" AS "files_updatedAt",
|
||||
"files"."type" AS "files_type",
|
||||
"files"."path" AS "files_path",
|
||||
"exifInfo"."assetId" AS "exifInfo_assetId",
|
||||
"exifInfo"."description" AS "exifInfo_description",
|
||||
"exifInfo"."exifImageWidth" AS "exifInfo_exifImageWidth",
|
||||
|
@ -932,7 +911,6 @@ SELECT
|
|||
"stackedAssets"."duplicateId" AS "stackedAssets_duplicateId"
|
||||
FROM
|
||||
"assets" "asset"
|
||||
LEFT JOIN "asset_files" "files" ON "files"."assetId" = "asset"."id"
|
||||
LEFT JOIN "exif" "exifInfo" ON "exifInfo"."assetId" = "asset"."id"
|
||||
LEFT JOIN "asset_stack" "stack" ON "stack"."id" = "asset"."stackId"
|
||||
LEFT JOIN "assets" "stackedAssets" ON "stackedAssets"."stackId" = "stack"."id"
|
||||
|
@ -964,7 +942,6 @@ SELECT DISTINCT
|
|||
c.city AS "value"
|
||||
FROM
|
||||
"assets" "asset"
|
||||
LEFT JOIN "asset_files" "files" ON "files"."assetId" = "asset"."id"
|
||||
INNER JOIN "exif" "e" ON "asset"."id" = e."assetId"
|
||||
INNER JOIN "cities" "c" ON c.city = "e"."city"
|
||||
WHERE
|
||||
|
@ -995,7 +972,6 @@ SELECT DISTINCT
|
|||
unnest("si"."tags") AS "value"
|
||||
FROM
|
||||
"assets" "asset"
|
||||
LEFT JOIN "asset_files" "files" ON "files"."assetId" = "asset"."id"
|
||||
INNER JOIN "smart_info" "si" ON "asset"."id" = si."assetId"
|
||||
INNER JOIN "random_tags" "t" ON "si"."tags" @> ARRAY[t.tag]
|
||||
WHERE
|
||||
|
@ -1038,12 +1014,6 @@ SELECT
|
|||
"asset"."sidecarPath" AS "asset_sidecarPath",
|
||||
"asset"."stackId" AS "asset_stackId",
|
||||
"asset"."duplicateId" AS "asset_duplicateId",
|
||||
"files"."id" AS "files_id",
|
||||
"files"."assetId" AS "files_assetId",
|
||||
"files"."createdAt" AS "files_createdAt",
|
||||
"files"."updatedAt" AS "files_updatedAt",
|
||||
"files"."type" AS "files_type",
|
||||
"files"."path" AS "files_path",
|
||||
"exifInfo"."assetId" AS "exifInfo_assetId",
|
||||
"exifInfo"."description" AS "exifInfo_description",
|
||||
"exifInfo"."exifImageWidth" AS "exifInfo_exifImageWidth",
|
||||
|
@ -1078,7 +1048,6 @@ SELECT
|
|||
"stack"."primaryAssetId" AS "stack_primaryAssetId"
|
||||
FROM
|
||||
"assets" "asset"
|
||||
LEFT JOIN "asset_files" "files" ON "files"."assetId" = "asset"."id"
|
||||
LEFT JOIN "exif" "exifInfo" ON "exifInfo"."assetId" = "asset"."id"
|
||||
LEFT JOIN "asset_stack" "stack" ON "stack"."id" = "asset"."stackId"
|
||||
WHERE
|
||||
|
@ -1120,12 +1089,6 @@ SELECT
|
|||
"asset"."sidecarPath" AS "asset_sidecarPath",
|
||||
"asset"."stackId" AS "asset_stackId",
|
||||
"asset"."duplicateId" AS "asset_duplicateId",
|
||||
"files"."id" AS "files_id",
|
||||
"files"."assetId" AS "files_assetId",
|
||||
"files"."createdAt" AS "files_createdAt",
|
||||
"files"."updatedAt" AS "files_updatedAt",
|
||||
"files"."type" AS "files_type",
|
||||
"files"."path" AS "files_path",
|
||||
"exifInfo"."assetId" AS "exifInfo_assetId",
|
||||
"exifInfo"."description" AS "exifInfo_description",
|
||||
"exifInfo"."exifImageWidth" AS "exifInfo_exifImageWidth",
|
||||
|
@ -1160,7 +1123,6 @@ SELECT
|
|||
"stack"."primaryAssetId" AS "stack_primaryAssetId"
|
||||
FROM
|
||||
"assets" "asset"
|
||||
LEFT JOIN "asset_files" "files" ON "files"."assetId" = "asset"."id"
|
||||
LEFT JOIN "exif" "exifInfo" ON "exifInfo"."assetId" = "asset"."id"
|
||||
LEFT JOIN "asset_stack" "stack" ON "stack"."id" = "asset"."stackId"
|
||||
WHERE
|
||||
|
@ -1197,12 +1159,6 @@ SELECT
|
|||
"asset"."sidecarPath" AS "asset_sidecarPath",
|
||||
"asset"."stackId" AS "asset_stackId",
|
||||
"asset"."duplicateId" AS "asset_duplicateId",
|
||||
"files"."id" AS "files_id",
|
||||
"files"."assetId" AS "files_assetId",
|
||||
"files"."createdAt" AS "files_createdAt",
|
||||
"files"."updatedAt" AS "files_updatedAt",
|
||||
"files"."type" AS "files_type",
|
||||
"files"."path" AS "files_path",
|
||||
"exifInfo"."assetId" AS "exifInfo_assetId",
|
||||
"exifInfo"."description" AS "exifInfo_description",
|
||||
"exifInfo"."exifImageWidth" AS "exifInfo_exifImageWidth",
|
||||
|
@ -1264,7 +1220,6 @@ SELECT
|
|||
"stackedAssets"."duplicateId" AS "stackedAssets_duplicateId"
|
||||
FROM
|
||||
"assets" "asset"
|
||||
LEFT JOIN "asset_files" "files" ON "files"."assetId" = "asset"."id"
|
||||
LEFT JOIN "exif" "exifInfo" ON "exifInfo"."assetId" = "asset"."id"
|
||||
LEFT JOIN "asset_stack" "stack" ON "stack"."id" = "asset"."stackId"
|
||||
LEFT JOIN "assets" "stackedAssets" ON "stackedAssets"."stackId" = "stack"."id"
|
||||
|
|
|
@ -710,10 +710,7 @@ export class AssetRepository implements IAssetRepository {
|
|||
}
|
||||
|
||||
private getBuilder(options: AssetBuilderOptions) {
|
||||
const builder = this.repository
|
||||
.createQueryBuilder('asset')
|
||||
.where('asset.isVisible = true')
|
||||
.leftJoinAndSelect('asset.files', 'files');
|
||||
const builder = this.repository.createQueryBuilder('asset').where('asset.isVisible = true');
|
||||
|
||||
if (options.assetType !== undefined) {
|
||||
builder.andWhere('asset.type = :assetType', { assetType: options.assetType });
|
||||
|
|
2
server/test/fixtures/shared-link.stub.ts
vendored
2
server/test/fixtures/shared-link.stub.ts
vendored
|
@ -54,7 +54,6 @@ const assetResponse: AssetResponseDto = {
|
|||
originalMimeType: 'image/jpeg',
|
||||
originalPath: 'fake_path/jpeg',
|
||||
originalFileName: 'asset_1.jpeg',
|
||||
resized: false,
|
||||
thumbhash: null,
|
||||
fileModifiedAt: today,
|
||||
isOffline: false,
|
||||
|
@ -82,7 +81,6 @@ const assetResponseWithoutMetadata = {
|
|||
id: 'id_1',
|
||||
type: AssetType.VIDEO,
|
||||
originalMimeType: 'image/jpeg',
|
||||
resized: false,
|
||||
thumbhash: null,
|
||||
localDateTime: today,
|
||||
duration: '0:00:00.00000',
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -12,7 +12,6 @@ export const assetFactory = Sync.makeFactory<AssetResponseDto>({
|
|||
originalPath: Sync.each(() => faker.system.filePath()),
|
||||
originalFileName: Sync.each(() => faker.system.fileName()),
|
||||
originalMimeType: Sync.each(() => faker.system.mimeType()),
|
||||
resized: true,
|
||||
thumbhash: Sync.each(() => faker.string.alphanumeric(28)),
|
||||
fileCreatedAt: Sync.each(() => faker.date.past().toISOString()),
|
||||
fileModifiedAt: Sync.each(() => faker.date.past().toISOString()),
|
||||
|
|
Loading…
Reference in a new issue