page editor zoom
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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 || '';
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user