page editor zoom

This commit is contained in:
Reece
2026-01-12 12:43:45 +00:00
parent beac51c1b7
commit a4480248e1
3 changed files with 73 additions and 9 deletions
@@ -115,7 +115,7 @@ export default function Workbench() {
// Handle exit transition (viewer → fileEditor/pageEditor)
if ((view === 'fileEditor' || view === 'pageEditor') && currentView === 'viewer') {
handleExitTransition();
handleExitTransition(view);
}
navActions.setWorkbench(view);
+30 -8
View File
@@ -1,7 +1,7 @@
import { useCallback } from 'react';
import { useNavigationActions } from '@app/contexts/NavigationContext';
import { useFileState } from '@app/contexts/FileContext';
import { getThumbnailRect, getCenteredFallbackRect } from '@app/utils/dom';
import { getThumbnailRect, getCenteredFallbackRect, getContainedImageRect } from '@app/utils/dom';
import type { WorkbenchType } from '@app/types/workbench';
import type { FileId } from '@app/types/fileContext';
@@ -39,11 +39,15 @@ export function useViewerTransition({
return;
}
// Only capture screenshot for fileEditor transitions
// pageEditor doesn't need it since we're animating the thumbnail directly
let screenshot: string | null = null;
try {
screenshot = await captureScreenshot();
} catch {
screenshot = null;
if (currentView === 'fileEditor') {
try {
screenshot = await captureScreenshot();
} catch {
screenshot = null;
}
}
// Use passed fileId to find the file directly
@@ -69,8 +73,21 @@ export function useViewerTransition({
// Use passed sourceRect if available, otherwise search DOM
let rect = sourceRect;
if (!rect) {
const fileCard = document.querySelector(`[data-file-id="${targetFileId}"]`) as HTMLElement | null;
rect = fileCard ? getThumbnailRect(fileCard) : getCenteredFallbackRect();
if (currentView === 'pageEditor') {
// In page editor, find the first page thumbnail image
const firstPageThumbnail = document.querySelector('[data-page-number="1"]') as HTMLElement | null;
if (firstPageThumbnail) {
const img = firstPageThumbnail.querySelector('img') as HTMLImageElement | null;
// Calculate the actual rendered image size (accounting for objectFit: contain)
rect = img ? getContainedImageRect(img) : firstPageThumbnail.getBoundingClientRect();
} else {
rect = getCenteredFallbackRect();
}
} else {
// In file editor, find the file card
const fileCard = document.querySelector(`[data-file-id="${targetFileId}"]`) as HTMLElement | null;
rect = fileCard ? getThumbnailRect(fileCard) : getCenteredFallbackRect();
}
}
navActions.startViewerTransition(
@@ -88,11 +105,16 @@ export function useViewerTransition({
* Finds PDF page position, finds target thumbnail, starts reverse zoom animation
*/
const handleExitTransition = useCallback(
() => {
(targetView: 'fileEditor' | 'pageEditor') => {
if (currentView !== 'viewer') {
return;
}
// Don't animate when returning to pageEditor
if (targetView === 'pageEditor') {
return;
}
const activeFile = getActiveFile();
const activeStub = activeFile ? selectors.getStirlingFileStub(activeFile.fileId) : null;
const thumbnailUrl = activeStub?.thumbnailUrl || '';
+42
View File
@@ -26,3 +26,45 @@ export function getCenteredFallbackRect(): DOMRect {
const centerY = window.innerHeight / 2;
return new DOMRect(centerX - width / 2, centerY - height / 2, width, height);
}
/**
* Calculate the actual rendered image rect for an img element with objectFit: contain
* Accounts for letterboxing/pillarboxing when aspect ratios don't match
*
* @param img - The image element with objectFit: contain
* @returns The bounding rect of the actual rendered image (excluding empty space)
*/
export function getContainedImageRect(img: HTMLImageElement): DOMRect {
const imgRect = img.getBoundingClientRect();
// If image hasn't loaded yet, return the full rect
if (!img.naturalWidth || !img.naturalHeight) {
return imgRect;
}
const naturalRatio = img.naturalWidth / img.naturalHeight;
const displayRatio = imgRect.width / imgRect.height;
let actualWidth, actualHeight, offsetX, offsetY;
if (naturalRatio > displayRatio) {
// Image is wider - constrained by width (letterboxed top/bottom)
actualWidth = imgRect.width;
actualHeight = imgRect.width / naturalRatio;
offsetX = 0;
offsetY = (imgRect.height - actualHeight) / 2;
} else {
// Image is taller - constrained by height (pillarboxed left/right)
actualHeight = imgRect.height;
actualWidth = imgRect.height * naturalRatio;
offsetX = (imgRect.width - actualWidth) / 2;
offsetY = 0;
}
return new DOMRect(
imgRect.left + offsetX,
imgRect.top + offsetY,
actualWidth,
actualHeight
);
}