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 { colorScheme } = useMantineColorScheme();
const viewerRef = React.useRef<HTMLDivElement>(null); const viewerRef = React.useRef<HTMLDivElement>(null);
const [isViewerHovered, setIsViewerHovered] = React.useState(false); 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 // Get current file from FileContext
@ -71,28 +75,13 @@ const EmbedPdfViewerContent = ({
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
<<<<<<< HEAD
if (event.deltaY < 0) { if (event.deltaY < 0) {
// Scroll up - zoom in // Scroll up - zoom in
zoomActions.zoomIn(); zoomActions.zoomIn();
} else { } else {
// Scroll down - zoom out // Scroll down - zoom out
zoomActions.zoomOut(); 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]); }, [isViewerHovered]);
<<<<<<< HEAD
=======
// Expose toggle functions globally for right rail buttons
React.useEffect(() => {
window.toggleThumbnailSidebar = toggleThumbnailSidebar;
return () => {
delete window.toggleThumbnailSidebar;
};
}, [toggleThumbnailSidebar]);
>>>>>>> 81c5d8ff46dcc5fc983109fb2348b6d6dfb129d2
return ( return (
<Box <Box
@ -220,17 +198,17 @@ const EmbedPdfViewerContent = ({
> >
<div style={{ pointerEvents: "auto" }}> <div style={{ pointerEvents: "auto" }}>
<PdfViewerToolbar <PdfViewerToolbar
currentPage={1} currentPage={scrollState.currentPage}
totalPages={1} totalPages={scrollState.totalPages}
onPageChange={(page) => { onPageChange={(page) => {
// Placeholder - will implement page navigation later // Placeholder - will implement page navigation later
console.log('Navigate to page:', page); console.log('Navigate to page:', page);
}} }}
dualPage={false} dualPage={spreadState.isDualPage}
onDualPageToggle={() => { onDualPageToggle={() => {
spreadActions.toggleSpreadMode(); spreadActions.toggleSpreadMode();
}} }}
currentZoom={100} currentZoom={zoomState.zoomPercent}
/> />
</div> </div>
</div> </div>

View File

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

View File

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

View File

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

View File

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

View File

@ -21,14 +21,21 @@ export function ScrollAPIBridge() {
currentPage: scrollState.currentPage, currentPage: scrollState.currentPage,
totalPages: scrollState.totalPages, 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', { registerBridge('scroll', {
state: newState, state: newState,
api: scroll api: scroll
}); });
} }
}, [scroll, scrollState, registerBridge]); }, [scroll, scrollState]);
return null; return null;
} }

View File

@ -20,9 +20,17 @@ export function SearchAPIBridge() {
if (!search) return; if (!search) return;
const unsubscribe = search.onSearchResultStateChange?.((state: any) => { const unsubscribe = search.onSearchResultStateChange?.((state: any) => {
setLocalState({ const newState = {
results: state?.results || null, results: state?.results || null,
activeIndex: (state?.activeResultIndex || 0) + 1 // Convert to 1-based index 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; return null;
} }

View File

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

View File

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

View File

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

View File

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

View File

@ -59,6 +59,9 @@ interface ViewerContextType {
getSearchActiveIndex: () => number; getSearchActiveIndex: () => number;
getThumbnailAPI: () => any; getThumbnailAPI: () => any;
// Immediate update callbacks
registerImmediateZoomUpdate: (callback: (percent: number) => void) => void;
// Action handlers - call EmbedPDF APIs directly // Action handlers - call EmbedPDF APIs directly
scrollActions: { scrollActions: {
scrollToPage: (page: number) => void; scrollToPage: (page: number) => void;
@ -133,6 +136,9 @@ export const ViewerProvider: React.FC<ViewerProviderProps> = ({ children }) => {
thumbnail: null as BridgeRef | null, 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) => { const registerBridge = (type: string, ref: BridgeRef) => {
bridgeRefs.current[type as keyof typeof bridgeRefs.current] = ref; bridgeRefs.current[type as keyof typeof bridgeRefs.current] = ref;
}; };
@ -217,12 +223,24 @@ export const ViewerProvider: React.FC<ViewerProviderProps> = ({ children }) => {
zoomIn: () => { zoomIn: () => {
const api = bridgeRefs.current.zoom?.api; const api = bridgeRefs.current.zoom?.api;
if (api?.zoomIn) { 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(); api.zoomIn();
} }
}, },
zoomOut: () => { zoomOut: () => {
const api = bridgeRefs.current.zoom?.api; const api = bridgeRefs.current.zoom?.api;
if (api?.zoomOut) { 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(); 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 = { const value: ViewerContextType = {
// UI state // UI state
isThumbnailSidebarVisible, isThumbnailSidebarVisible,
@ -377,6 +399,9 @@ export const ViewerProvider: React.FC<ViewerProviderProps> = ({ children }) => {
getSearchActiveIndex, getSearchActiveIndex,
getThumbnailAPI, getThumbnailAPI,
// Immediate updates
registerImmediateZoomUpdate,
// Actions // Actions
scrollActions, scrollActions,
zoomActions, zoomActions,