Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@

const handleAddTag = async () => {
const success = await modalManager.show(AssetTagModal, { assetIds: [asset.id] });

if (success) {
asset = await getAssetInfo({ id: asset.id });
}
Expand Down
81 changes: 23 additions & 58 deletions web/src/lib/components/asset-viewer/detail-panel.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,14 @@
import { locale } from '$lib/stores/preferences.store';
import { featureFlags } from '$lib/stores/server-config.store';
import { preferences, user } from '$lib/stores/user.store';
import { getAssetThumbnailUrl, getPeopleThumbnailUrl, handlePromiseError } from '$lib/utils';
import { delay, isFlipped } from '$lib/utils/asset-utils';
import { getAssetThumbnailUrl, getPeopleThumbnailUrl } from '$lib/utils';
import { delay, getDimensions } from '$lib/utils/asset-utils';
import { getByteUnitString } from '$lib/utils/byte-units';
import { handleError } from '$lib/utils/handle-error';
import { getMetadataSearchQuery } from '$lib/utils/metadata-search';
import { fromISODateTime, fromISODateTimeUTC } from '$lib/utils/timeline-util';
import { getParentPath } from '$lib/utils/tree-utils';
import {
AssetMediaSize,
getAssetInfo,
updateAsset,
type AlbumResponseDto,
type AssetResponseDto,
type ExifResponseDto,
} from '@immich/sdk';
import { AssetMediaSize, getAssetInfo, updateAsset, type AlbumResponseDto, type AssetResponseDto } from '@immich/sdk';
import { IconButton } from '@immich/ui';
import {
mdiCalendar,
Expand Down Expand Up @@ -61,44 +54,18 @@

let { asset, albums = [], currentAlbum = null, onClose }: Props = $props();

const getDimensions = (exifInfo: ExifResponseDto) => {
const { exifImageWidth: width, exifImageHeight: height } = exifInfo;
if (isFlipped(exifInfo.orientation)) {
return { width: height, height: width };
}

return { width, height };
};

let showAssetPath = $state(false);
let showEditFaces = $state(false);
let previousId: string | undefined = $state();

$effect(() => {
if (!previousId) {
previousId = asset.id;
}
if (asset.id !== previousId) {
showEditFaces = false;
previousId = asset.id;
}
});

let isOwner = $derived($user?.id === asset.ownerId);

const handleNewAsset = async (newAsset: AssetResponseDto) => {
// TODO: check if reloading asset data is necessary
if (newAsset.id && !authManager.isSharedLink) {
const data = await getAssetInfo({ id: asset.id });
people = data?.people || [];
unassignedFaces = data?.unassignedFaces || [];
}
};

$effect(() => {
handlePromiseError(handleNewAsset(asset));
});

let people = $derived(asset.people || []);
let unassignedFaces = $derived(asset.unassignedFaces || []);
let showingHiddenPeople = $state(false);
let timeZone = $derived(asset.exifInfo?.timeZone);
let dateTime = $derived(
timeZone && asset.exifInfo?.dateTimeOriginal
? fromISODateTime(asset.exifInfo.dateTimeOriginal, timeZone)
: fromISODateTimeUTC(asset.localDateTime),
);
let latlng = $derived(
(() => {
const lat = asset.exifInfo?.latitude;
Expand All @@ -109,16 +76,17 @@
}
})(),
);
let previousId: string | undefined = $state();

let people = $state(asset.people || []);
let unassignedFaces = $state(asset.unassignedFaces || []);
let showingHiddenPeople = $state(false);
let timeZone = $derived(asset.exifInfo?.timeZone);
let dateTime = $derived(
timeZone && asset.exifInfo?.dateTimeOriginal
? fromISODateTime(asset.exifInfo.dateTimeOriginal, timeZone)
: fromISODateTimeUTC(asset.localDateTime),
);
$effect(() => {
if (!previousId) {
previousId = asset.id;
}
if (asset.id !== previousId) {
showEditFaces = false;
previousId = asset.id;
}
});

const getMegapixel = (width: number, height: number): number | undefined => {
const megapixel = Math.round((height * width) / 1_000_000);
Expand All @@ -131,10 +99,7 @@
};

const handleRefreshPeople = async () => {
await getAssetInfo({ id: asset.id }).then((data) => {
people = data?.people || [];
unassignedFaces = data?.unassignedFaces || [];
});
asset = await getAssetInfo({ id: asset.id });
showEditFaces = false;
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@
import { shortcuts } from '$lib/actions/shortcut';
import Portal from '$lib/components/shared-components/portal/portal.svelte';
import DuplicateAsset from '$lib/components/utilities-page/duplicates/duplicate-asset.svelte';
import { authManager } from '$lib/managers/auth-manager.svelte';
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
import { handlePromiseError } from '$lib/utils';
import { suggestDuplicate } from '$lib/utils/duplicate-utils';
import { navigate } from '$lib/utils/navigation';
import { type AssetResponseDto } from '@immich/sdk';
import { getAssetInfo, type AssetResponseDto } from '@immich/sdk';
import { Button } from '@immich/ui';
import { mdiCheck, mdiImageMultipleOutline, mdiTrashCanOutline } from '@mdi/js';
import { onDestroy, onMount } from 'svelte';
Expand Down Expand Up @@ -42,32 +43,32 @@
assetViewingStore.showAssetViewer(false);
});

const onNext = () => {
const onNext = async () => {
const index = getAssetIndex($viewingAsset.id) + 1;
if (index >= assets.length) {
return Promise.resolve(false);
return false;
}
setAsset(assets[index]);
return Promise.resolve(true);
await onViewAsset(assets[index]);
return true;
};

const onPrevious = () => {
const onPrevious = async () => {
const index = getAssetIndex($viewingAsset.id) - 1;
if (index < 0) {
return Promise.resolve(false);
return false;
}
setAsset(assets[index]);
return Promise.resolve(true);
await onViewAsset(assets[index]);
return true;
};

const onRandom = () => {
const onRandom = async () => {
if (assets.length <= 0) {
return Promise.resolve(undefined);
return;
}
const index = Math.floor(Math.random() * assets.length);
const asset = assets[index];
setAsset(asset);
return Promise.resolve(asset);
await onViewAsset(asset);
return { id: asset.id };
};

const onSelectAsset = (asset: AssetResponseDto) => {
Expand All @@ -86,6 +87,12 @@
selectedAssetIds = new SvelteSet(assets.map((asset) => asset.id));
};

const onViewAsset = async ({ id }: AssetResponseDto) => {
const asset = await getAssetInfo({ ...authManager.params, id });
setAsset(asset);
await navigate({ targetRoute: 'current', assetId: asset.id });
};

const handleResolve = () => {
const trashIds = assets.map((asset) => asset.id).filter((id) => !selectedAssetIds.has(id));
const duplicateAssetIds = assets.map((asset) => asset.id);
Expand All @@ -102,9 +109,7 @@
{ shortcut: { key: 'a' }, onShortcut: onSelectAll },
{
shortcut: { key: 's' },
onShortcut: () => {
setAsset(assets[0]);
},
onShortcut: () => onViewAsset(assets[0]),
},
{ shortcut: { key: 'd' }, onShortcut: onSelectNone },
{ shortcut: { key: 'c', shift: true }, onShortcut: handleResolve },
Expand Down Expand Up @@ -166,12 +171,7 @@

<div class="flex flex-wrap gap-1 mb-4 place-items-center place-content-center px-4 pt-4">
{#each assets as asset (asset.id)}
<DuplicateAsset
{asset}
{onSelectAsset}
isSelected={selectedAssetIds.has(asset.id)}
onViewAsset={(asset) => setAsset(asset)}
/>
<DuplicateAsset {asset} {onSelectAsset} isSelected={selectedAssetIds.has(asset.id)} {onViewAsset} />
{/each}
</div>
</div>
Expand Down
10 changes: 10 additions & 0 deletions web/src/lib/utils/asset-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import {
type AssetResponseDto,
type AssetTypeEnum,
type DownloadInfoDto,
type ExifResponseDto,
type StackResponseDto,
type UserPreferencesResponseDto,
type UserResponseDto,
Expand Down Expand Up @@ -328,6 +329,15 @@ export function isFlipped(orientation?: string | null) {
return value && (isRotated270CW(value) || isRotated90CW(value));
}

export const getDimensions = (exifInfo: ExifResponseDto) => {
const { exifImageWidth: width, exifImageHeight: height } = exifInfo;
if (isFlipped(exifInfo.orientation)) {
return { width: height, height: width };
}

return { width, height };
};

export function getFileSize(asset: AssetResponseDto, maxPrecision = 4): string {
const size = asset.exifInfo?.fileSizeInByte || 0;
return size > 0 ? getByteUnitString(size, undefined, maxPrecision) : 'Invalid Data';
Expand Down