mirror of
https://github.com/immich-app/immich.git
synced 2026-03-10 03:43:43 -04:00
Replace ImageManager with a new AdaptiveImageLoader that progressively loads images through quality tiers (thumbnail → preview → original). New components and utilities: - AdaptiveImage: layered image renderer with thumbhash, thumbnail, preview, and original layers with visibility managed by load state - AdaptiveImageLoader: state machine driving the quality progression with per-quality callbacks and error handling - ImageLayer/Image: low-level image elements with load/error lifecycle - PreloadManager: preloads adjacent assets for instant navigation - AlphaBackground/DelayedLoadingSpinner: loading state UI Zoom is handled via a derived CSS transform applied to the content wrapper in AdaptiveImage, with the zoom library (zoomTarget: null) only tracking state without manipulating the DOM directly. Also adds scaleToCover to container-utils and getAssetUrls to utils.
59 lines
1.9 KiB
TypeScript
59 lines
1.9 KiB
TypeScript
export interface ContentMetrics {
|
|
contentWidth: number;
|
|
contentHeight: number;
|
|
offsetX: number;
|
|
offsetY: number;
|
|
}
|
|
|
|
export const scaleToCover = (
|
|
dimensions: { width: number; height: number },
|
|
container: { width: number; height: number },
|
|
): { width: number; height: number } => {
|
|
const scaleX = container.width / dimensions.width;
|
|
const scaleY = container.height / dimensions.height;
|
|
const scale = Math.max(scaleX, scaleY);
|
|
return {
|
|
width: dimensions.width * scale,
|
|
height: dimensions.height * scale,
|
|
};
|
|
};
|
|
|
|
export const scaleToFit = (
|
|
dimensions: { width: number; height: number },
|
|
container: { width: number; height: number },
|
|
): { width: number; height: number } => {
|
|
const scaleX = container.width / dimensions.width;
|
|
const scaleY = container.height / dimensions.height;
|
|
const scale = Math.min(scaleX, scaleY);
|
|
return {
|
|
width: dimensions.width * scale,
|
|
height: dimensions.height * scale,
|
|
};
|
|
};
|
|
|
|
const getElementSize = (element: HTMLImageElement | HTMLVideoElement): { width: number; height: number } => {
|
|
if (element instanceof HTMLVideoElement) {
|
|
return { width: element.clientWidth, height: element.clientHeight };
|
|
}
|
|
return { width: element.width, height: element.height };
|
|
};
|
|
|
|
export const getNaturalSize = (element: HTMLImageElement | HTMLVideoElement): { width: number; height: number } => {
|
|
if (element instanceof HTMLVideoElement) {
|
|
return { width: element.videoWidth, height: element.videoHeight };
|
|
}
|
|
return { width: element.naturalWidth, height: element.naturalHeight };
|
|
};
|
|
|
|
export const getContentMetrics = (element: HTMLImageElement | HTMLVideoElement): ContentMetrics => {
|
|
const natural = getNaturalSize(element);
|
|
const client = getElementSize(element);
|
|
const { width: contentWidth, height: contentHeight } = scaleToFit(natural, client);
|
|
return {
|
|
contentWidth,
|
|
contentHeight,
|
|
offsetX: (client.width - contentWidth) / 2,
|
|
offsetY: (client.height - contentHeight) / 2,
|
|
};
|
|
};
|