Restructure to avoid global variables

fix zoom
This commit is contained in:
Reece Browne 2025-09-17 12:00:20 +01:00
parent b81ed9ec2e
commit 41e5a7fbd6
12 changed files with 78 additions and 52 deletions

View File

@ -28,7 +28,11 @@ const EmbedPdfViewerContent = ({
const { colorScheme } = useMantineColorScheme();
const viewerRef = React.useRef<HTMLDivElement>(null);
const [isViewerHovered, setIsViewerHovered] = React.useState(false);
const { isThumbnailSidebarVisible, toggleThumbnailSidebar, zoomActions, spreadActions, panActions: _panActions, rotationActions: _rotationActions } = useViewer();
const { isThumbnailSidebarVisible, toggleThumbnailSidebar, zoomActions, spreadActions, panActions: _panActions, rotationActions: _rotationActions, getScrollState, getZoomState, getSpreadState } = useViewer();
const scrollState = getScrollState();
const zoomState = getZoomState();
const spreadState = getSpreadState();
// Get current file from FileContext
@ -71,28 +75,13 @@ const EmbedPdfViewerContent = ({
event.preventDefault();
event.stopPropagation();
<<<<<<< HEAD
if (event.deltaY < 0) {
// Scroll up - zoom in
zoomActions.zoomIn();
} else {
// Scroll down - zoom out
zoomActions.zoomOut();
=======
// Convert smooth scrolling gestures into discrete notches
accumulator += event.deltaY;
const threshold = 10;
const zoomAPI = window.embedPdfZoom;
if (zoomAPI) {
if (accumulator <= -threshold) {
zoomAPI.zoomIn();
accumulator = 0;
} else if (accumulator >= threshold) {
zoomAPI.zoomOut();
accumulator = 0;
}
>>>>>>> 81c5d8ff46dcc5fc983109fb2348b6d6dfb129d2
}
}
};
@ -131,17 +120,6 @@ const EmbedPdfViewerContent = ({
};
}, [isViewerHovered]);
<<<<<<< HEAD
=======
// Expose toggle functions globally for right rail buttons
React.useEffect(() => {
window.toggleThumbnailSidebar = toggleThumbnailSidebar;
return () => {
delete window.toggleThumbnailSidebar;
};
}, [toggleThumbnailSidebar]);
>>>>>>> 81c5d8ff46dcc5fc983109fb2348b6d6dfb129d2
return (
<Box
@ -220,17 +198,17 @@ const EmbedPdfViewerContent = ({
>
<div style={{ pointerEvents: "auto" }}>
<PdfViewerToolbar
currentPage={1}
totalPages={1}
currentPage={scrollState.currentPage}
totalPages={scrollState.totalPages}
onPageChange={(page) => {
// Placeholder - will implement page navigation later
console.log('Navigate to page:', page);
}}
dualPage={false}
dualPage={spreadState.isDualPage}
onDualPageToggle={() => {
spreadActions.toggleSpreadMode();
}}
currentZoom={100}
currentZoom={zoomState.zoomPercent}
/>
</div>
</div>

View File

@ -231,16 +231,13 @@ export function LocalEmbedPDF({ file, url, colorScheme }: LocalEmbedPDFProps) {
onDrop={(e) => e.preventDefault()}
onDragOver={(e) => e.preventDefault()}
>
{/* 1. Low-resolution base layer for immediate feedback */}
<RenderLayer pageIndex={pageIndex} scale={0.5} />
{/* 2. High-resolution tile layer on top */}
{/* High-resolution tile layer */}
<TilingLayer pageIndex={pageIndex} scale={scale} />
{/* 3. Search highlight layer */}
{/* Search highlight layer */}
<CustomSearchLayer pageIndex={pageIndex} scale={scale} />
{/* 4. Selection layer for text interaction */}
{/* Selection layer for text interaction */}
<SelectionLayer pageIndex={pageIndex} scale={scale} />
</div>
</PagePointerProvider>

View File

@ -39,7 +39,7 @@ export function PanAPIBridge() {
}
});
}
}, [pan, isPanning, registerBridge]);
}, [pan, isPanning]);
return null;
}

View File

@ -33,17 +33,24 @@ export function PdfViewerToolbar({
currentZoom: _currentZoom = 100,
}: PdfViewerToolbarProps) {
const { t } = useTranslation();
const { getScrollState, getZoomState, scrollActions, zoomActions } = useViewer();
const { getScrollState, getZoomState, scrollActions, zoomActions, registerImmediateZoomUpdate } = useViewer();
const scrollState = getScrollState();
const zoomState = getZoomState();
const [pageInput, setPageInput] = useState(scrollState.currentPage || currentPage);
const [displayZoomPercent, setDisplayZoomPercent] = useState(zoomState.zoomPercent || 140);
// Update page input when scroll state changes
useEffect(() => {
setPageInput(scrollState.currentPage);
}, [scrollState.currentPage]);
// Register for immediate zoom updates and sync with actual zoom state
useEffect(() => {
registerImmediateZoomUpdate(setDisplayZoomPercent);
setDisplayZoomPercent(zoomState.zoomPercent || 140);
}, [zoomState.zoomPercent, registerImmediateZoomUpdate]);
const handleZoomOut = () => {
zoomActions.zoomOut();
};
@ -204,7 +211,7 @@ export function PdfViewerToolbar({
</Button>
<span style={{ minWidth: '2.5rem', textAlign: "center" }}>
{zoomState.zoomPercent}%
{displayZoomPercent}%
</span>
<Button
variant="subtle"

View File

@ -33,7 +33,7 @@ export function RotateAPIBridge() {
}
});
}
}, [rotate, rotation, registerBridge]);
}, [rotate, rotation]);
return null;
}

View File

@ -21,14 +21,21 @@ export function ScrollAPIBridge() {
currentPage: scrollState.currentPage,
totalPages: scrollState.totalPages,
};
setLocalState(newState);
setLocalState(prevState => {
// Only update if state actually changed
if (prevState.currentPage !== newState.currentPage || prevState.totalPages !== newState.totalPages) {
return newState;
}
return prevState;
});
registerBridge('scroll', {
state: newState,
api: scroll
});
}
}, [scroll, scrollState, registerBridge]);
}, [scroll, scrollState]);
return null;
}

View File

@ -20,9 +20,17 @@ export function SearchAPIBridge() {
if (!search) return;
const unsubscribe = search.onSearchResultStateChange?.((state: any) => {
setLocalState({
const newState = {
results: state?.results || null,
activeIndex: (state?.activeResultIndex || 0) + 1 // Convert to 1-based index
};
setLocalState(prevState => {
// Only update if state actually changed
if (prevState.results !== newState.results || prevState.activeIndex !== newState.activeIndex) {
return newState;
}
return prevState;
});
});
@ -49,7 +57,7 @@ export function SearchAPIBridge() {
}
});
}
}, [search, localState, registerBridge]);
}, [search, localState]);
return null;
}

View File

@ -67,7 +67,7 @@ export function SelectionAPIBridge() {
document.removeEventListener('keydown', handleKeyDown);
};
}
}, [selection, hasSelection, registerBridge]);
}, [selection, hasSelection]);
return null;
}

View File

@ -41,7 +41,7 @@ export function SpreadAPIBridge() {
}
});
}
}, [spread, spreadMode, registerBridge]);
}, [spread, spreadMode]);
return null;
}

View File

@ -17,7 +17,7 @@ export function ThumbnailAPIBridge() {
api: thumbnail
});
}
}, [thumbnail, registerBridge]);
}, [thumbnail]);
return null;
}

View File

@ -9,7 +9,7 @@ export function ZoomAPIBridge() {
const { provides: zoom, state: zoomState } = useZoom();
const { registerBridge } = useViewer();
const hasSetInitialZoom = useRef(false);
// Store state locally
const [_localState, setLocalState] = useState({
currentZoom: 1.4,
@ -30,10 +30,14 @@ export function ZoomAPIBridge() {
useEffect(() => {
if (zoom && zoomState) {
// Update local state
const currentZoomLevel = zoomState.currentZoomLevel || 1.4;
const newState = {
currentZoom: zoomState.currentZoomLevel || 1.4,
zoomPercent: Math.round((zoomState.currentZoomLevel || 1.4) * 100),
currentZoom: currentZoomLevel,
zoomPercent: Math.round(currentZoomLevel * 100),
};
console.log('ZoomAPIBridge - Raw zoom level:', currentZoomLevel, 'Rounded percent:', newState.zoomPercent);
setLocalState(newState);
// Register this bridge with ViewerContext
@ -42,7 +46,7 @@ export function ZoomAPIBridge() {
api: zoom
});
}
}, [zoom, zoomState, registerBridge]);
}, [zoom, zoomState]);
return null;
}

View File

@ -59,6 +59,9 @@ interface ViewerContextType {
getSearchActiveIndex: () => number;
getThumbnailAPI: () => any;
// Immediate update callbacks
registerImmediateZoomUpdate: (callback: (percent: number) => void) => void;
// Action handlers - call EmbedPDF APIs directly
scrollActions: {
scrollToPage: (page: number) => void;
@ -133,6 +136,9 @@ export const ViewerProvider: React.FC<ViewerProviderProps> = ({ children }) => {
thumbnail: null as BridgeRef | null,
});
// Immediate zoom callback for responsive display updates
const immediateZoomUpdateCallback = useRef<((percent: number) => void) | null>(null);
const registerBridge = (type: string, ref: BridgeRef) => {
bridgeRefs.current[type as keyof typeof bridgeRefs.current] = ref;
};
@ -217,12 +223,24 @@ export const ViewerProvider: React.FC<ViewerProviderProps> = ({ children }) => {
zoomIn: () => {
const api = bridgeRefs.current.zoom?.api;
if (api?.zoomIn) {
// Update display immediately if callback is registered
if (immediateZoomUpdateCallback.current) {
const currentState = getZoomState();
const newPercent = Math.min(Math.round(currentState.zoomPercent * 1.2), 300);
immediateZoomUpdateCallback.current(newPercent);
}
api.zoomIn();
}
},
zoomOut: () => {
const api = bridgeRefs.current.zoom?.api;
if (api?.zoomOut) {
// Update display immediately if callback is registered
if (immediateZoomUpdateCallback.current) {
const currentState = getZoomState();
const newPercent = Math.max(Math.round(currentState.zoomPercent / 1.2), 20);
immediateZoomUpdateCallback.current(newPercent);
}
api.zoomOut();
}
},
@ -361,6 +379,10 @@ export const ViewerProvider: React.FC<ViewerProviderProps> = ({ children }) => {
}
};
const registerImmediateZoomUpdate = (callback: (percent: number) => void) => {
immediateZoomUpdateCallback.current = callback;
};
const value: ViewerContextType = {
// UI state
isThumbnailSidebarVisible,
@ -377,6 +399,9 @@ export const ViewerProvider: React.FC<ViewerProviderProps> = ({ children }) => {
getSearchActiveIndex,
getThumbnailAPI,
// Immediate updates
registerImmediateZoomUpdate,
// Actions
scrollActions,
zoomActions,