fix(web): address review feedback on hero view transitions

Change-Id: I9f12e1616ddcf124a9926d54868b5e166a6a6964
This commit is contained in:
midzelis
2026-05-14 02:30:18 +00:00
parent c7cf2714ef
commit fb061d9830
12 changed files with 29 additions and 8458 deletions
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
-16
View File
@@ -1,16 +0,0 @@
import { HttpError } from '@oazapfts/runtime';
export interface ApiValidationError {
code: string;
path: (string | number)[];
message: string;
}
export interface ApiExceptionResponse {
message: string;
error?: string;
statusCode: number;
errors?: ApiValidationError[];
}
export interface ApiHttpError extends HttpError {
data: ApiExceptionResponse;
}
export declare function isHttpError(error: unknown): error is ApiHttpError;
@@ -1,4 +0,0 @@
import { HttpError } from '@oazapfts/runtime';
export function isHttpError(error) {
return error instanceof HttpError;
}
-18
View File
@@ -1,18 +0,0 @@
export * from './fetch-client.js';
export * from './fetch-errors.js';
export interface InitOptions {
baseUrl: string;
apiKey: string;
headers?: Record<string, string>;
}
export declare const init: ({ baseUrl, apiKey, headers }: InitOptions) => void;
export declare const getBaseUrl: () => string;
export declare const setBaseUrl: (baseUrl: string) => void;
export declare const setApiKey: (apiKey: string) => void;
export declare const setHeader: (key: string, value: string) => void;
export declare const setHeaders: (headers: Record<string, string>) => void;
export declare const getAssetOriginalPath: (id: string) => string;
export declare const getAssetThumbnailPath: (id: string) => string;
export declare const getAssetPlaybackPath: (id: string) => string;
export declare const getUserProfileImagePath: (userId: string) => string;
export declare const getPeopleThumbnailPath: (personId: string) => string;
-40
View File
@@ -1,40 +0,0 @@
import { defaults } from './fetch-client.js';
export * from './fetch-client.js';
export * from './fetch-errors.js';
export const init = ({ baseUrl, apiKey, headers }) => {
setBaseUrl(baseUrl);
setApiKey(apiKey);
if (headers) {
setHeaders(headers);
}
};
export const getBaseUrl = () => defaults.baseUrl;
export const setBaseUrl = (baseUrl) => {
defaults.baseUrl = baseUrl;
};
export const setApiKey = (apiKey) => {
defaults.headers = defaults.headers || {};
defaults.headers['x-api-key'] = apiKey;
};
export const setHeader = (key, value) => {
assertNoApiKey(key);
defaults.headers = defaults.headers || {};
defaults.headers[key] = value;
};
export const setHeaders = (headers) => {
defaults.headers = defaults.headers || {};
for (const [key, value] of Object.entries(headers)) {
assertNoApiKey(key);
defaults.headers[key] = value;
}
};
const assertNoApiKey = (headerKey) => {
if (headerKey.toLowerCase() === 'x-api-key') {
throw new Error('The API key header can only be set using setApiKey().');
}
};
export const getAssetOriginalPath = (id) => `/assets/${id}/original`;
export const getAssetThumbnailPath = (id) => `/assets/${id}/thumbnail`;
export const getAssetPlaybackPath = (id) => `/assets/${id}/video/playback`;
export const getUserProfileImagePath = (userId) => `/users/${userId}/profile-image`;
export const getPeopleThumbnailPath = (personId) => `/people/${personId}/thumbnail`;
-15
View File
@@ -1,15 +0,0 @@
{
"name": "immich-monorepo",
"version": "2.7.5",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "immich-monorepo",
"version": "2.7.5",
"engines": {
"pnpm": ">=10.0.0"
}
}
}
}
File diff suppressed because one or more lines are too long
+16 -13
View File
@@ -10,6 +10,7 @@
import { assetsSnapshot, filterIsInOrNearViewport } from '$lib/managers/timeline-manager/utils.svelte';
import { viewTransitionManager } from '$lib/managers/ViewTransitionManager.svelte';
import { uploadAssetsStore } from '$lib/stores/upload';
import { handlePromiseError } from '$lib/utils';
import type { CommonPosition } from '$lib/utils/layout-utils';
import { fromTimelinePlainDate, getDateLocaleString } from '$lib/utils/timeline-util';
import { Icon } from '@immich/ui';
@@ -68,19 +69,21 @@
if (!asset) {
return;
}
void viewTransitionManager.startTransition({
types: ['timeline'],
performUpdate: async () => {
assetViewerManager.emit('ViewerCloseTransitionReady');
const event = await eventManager.untilNext('TimelineLoaded');
toTimelineHeroAssetId = event.id;
await tick();
},
onFinished: () => {
toTimelineHeroAssetId = null;
focusAsset(asset.id);
},
});
handlePromiseError(
viewTransitionManager.startTransition({
types: ['timeline'],
performUpdate: async () => {
assetViewerManager.emit('ViewerCloseTransitionReady');
const event = await eventManager.untilNext('TimelineLoaded');
toTimelineHeroAssetId = event.id;
await tick();
},
onFinished: () => {
toTimelineHeroAssetId = null;
focusAsset(asset.id);
},
}),
);
};
if (viewTransitionManager.isSupported()) {
onMount(() => assetViewerManager.on({ ViewerCloseTransition: handleViewerCloseTransition }));
@@ -22,6 +22,7 @@
import type { TimelineAsset, TimelineManagerOptions, ViewportTopMonth } from '$lib/managers/timeline-manager/types';
import { assetsSnapshot } from '$lib/managers/timeline-manager/utils.svelte';
import { mediaQueryManager } from '$lib/stores/media-query-manager.svelte';
import { handlePromiseError } from '$lib/utils';
import { isAssetViewerRoute, navigate } from '$lib/utils/navigation';
import { getTimes, type ScrubberListener } from '$lib/utils/timeline-util';
import { type AlbumResponseDto, type PersonResponseDto, type UserResponseDto } from '@immich/sdk';
@@ -581,11 +582,13 @@
}
const openViewer = () => void navigate({ targetRoute: 'current', assetId: asset.id });
startViewerTransition(
asset.id,
openViewer,
(id) => (toViewerHeroAssetId = id),
() => (toViewerHeroAssetId = null),
handlePromiseError(
startViewerTransition(
asset.id,
openViewer,
(id) => (toViewerHeroAssetId = id),
() => (toViewerHeroAssetId = null),
),
);
};
</script>
@@ -99,7 +99,9 @@
const handleClose = async (assetId: string) => {
if (viewTransitionManager.isSupported()) {
const transitionReady = assetViewerManager.untilNext('ViewerCloseTransitionReady');
const transitionReady = assetViewerManager.untilNext('ViewerCloseTransitionReady', {
signal: AbortSignal.timeout(200),
});
assetViewerManager.emit('ViewerCloseTransition', { id: assetId });
await transitionReady;
}
+2 -2
View File
@@ -2,13 +2,13 @@ import { tick } from 'svelte';
import { viewTransitionManager } from '$lib/managers/ViewTransitionManager.svelte';
import { assetViewerManager } from '$lib/managers/asset-viewer-manager.svelte';
export function startViewerTransition(
export async function startViewerTransition(
heroAssetId: string,
openViewer: () => void,
activateHeroAsset: (assetId: string) => void,
deactivateHeroAsset: () => void,
) {
void viewTransitionManager.startTransition({
await viewTransitionManager.startTransition({
types: ['viewer'],
prepareOldSnapshot: () => {
activateHeroAsset(heroAssetId);