mirror of
https://github.com/immich-app/immich.git
synced 2026-03-26 20:00:44 +03:00
feat(web): face overlay hover UX and face editor zoom preservation
Change-Id: I7164305d7764bec54fa06b8738cd97fd6a6a6964 refactor(web): use asset metadata for face editor image dimensions instead of DOM The face editor previously read naturalWidth/naturalHeight from the DOM element via a $effect + load event listener. This was fragile on slow hardware (ARM CI) because imgRef changes as AdaptiveImage progresses through quality levels, and the DOM element's natural dimensions could be 0 during transitions. Now the face editor receives imageSize as a prop from the parent, derived from the asset's metadata dimensions which are always available immediately. Change-Id: Id4c3a59110feff4c50f429bbd744eac46a6a6964 Change-Id: I7164305d7764bec54fa06b8738cd97fd6a6a6964
This commit is contained in:
@@ -63,16 +63,24 @@ export const getNaturalSize = (element: HTMLImageElement | HTMLVideoElement): Si
|
||||
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);
|
||||
export function computeContentMetrics(
|
||||
imageSize: Size,
|
||||
containerSize: Size,
|
||||
scaleFn: (dimensions: Size, container: Size) => Size = scaleToFit,
|
||||
) {
|
||||
const { width: contentWidth, height: contentHeight } = scaleFn(imageSize, containerSize);
|
||||
return {
|
||||
contentWidth,
|
||||
contentHeight,
|
||||
offsetX: (client.width - contentWidth) / 2,
|
||||
offsetY: (client.height - contentHeight) / 2,
|
||||
offsetX: (containerSize.width - contentWidth) / 2,
|
||||
offsetY: (containerSize.height - contentHeight) / 2,
|
||||
};
|
||||
}
|
||||
|
||||
export const getContentMetrics = (element: HTMLImageElement | HTMLVideoElement): ContentMetrics => {
|
||||
const natural = getNaturalSize(element);
|
||||
const client = getElementSize(element);
|
||||
return computeContentMetrics(natural, client);
|
||||
};
|
||||
|
||||
export function mapNormalizedToContent(point: Point, sizeOrMetrics: Size | ContentMetrics): Point {
|
||||
@@ -88,6 +96,13 @@ export function mapNormalizedToContent(point: Point, sizeOrMetrics: Size | Conte
|
||||
};
|
||||
}
|
||||
|
||||
export function mapContentToNatural(point: Point, metrics: ContentMetrics, naturalSize: Size): Point {
|
||||
return {
|
||||
x: ((point.x - metrics.offsetX) / metrics.contentWidth) * naturalSize.width,
|
||||
y: ((point.y - metrics.offsetY) / metrics.contentHeight) * naturalSize.height,
|
||||
};
|
||||
}
|
||||
|
||||
export type Rect = {
|
||||
top: number;
|
||||
left: number;
|
||||
@@ -109,3 +124,18 @@ export function mapNormalizedRectToContent(
|
||||
height: br.y - tl.y,
|
||||
};
|
||||
}
|
||||
|
||||
export function mapContentRectToNatural(rect: Rect, metrics: ContentMetrics, naturalSize: Size): Rect {
|
||||
const topLeft = mapContentToNatural({ x: rect.left, y: rect.top }, metrics, naturalSize);
|
||||
const bottomRight = mapContentToNatural(
|
||||
{ x: rect.left + rect.width, y: rect.top + rect.height },
|
||||
metrics,
|
||||
naturalSize,
|
||||
);
|
||||
return {
|
||||
top: topLeft.y,
|
||||
left: topLeft.x,
|
||||
width: bottomRight.x - topLeft.x,
|
||||
height: bottomRight.y - topLeft.y,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user