mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2025-11-16 01:21:16 +01:00
Viewer bridge handler. Reduce overengineering as flagged by copilot
This commit is contained in:
parent
1bc8e7613f
commit
fe23952828
@ -209,7 +209,7 @@ export function ZoomAPIBridge() {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!zoom || typeof zoom.onZoomChange !== 'function') {
|
if (!zoom) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -222,9 +222,7 @@ export function ZoomAPIBridge() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
if (typeof unsubscribe === 'function') {
|
unsubscribe();
|
||||||
unsubscribe();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}, [zoom, triggerImmediateZoomUpdate]);
|
}, [zoom, triggerImmediateZoomUpdate]);
|
||||||
|
|
||||||
|
|||||||
@ -6,115 +6,35 @@ import React, {
|
|||||||
useRef,
|
useRef,
|
||||||
useCallback,
|
useCallback,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import { SpreadMode } from '@embedpdf/plugin-spread/react';
|
|
||||||
import { useNavigation } from '@app/contexts/NavigationContext';
|
import { useNavigation } from '@app/contexts/NavigationContext';
|
||||||
|
import {
|
||||||
// Bridge API interfaces - these match what the bridges provide
|
BridgeRef,
|
||||||
interface ScrollAPIWrapper {
|
BridgeApiMap,
|
||||||
scrollToPage: (params: { pageNumber: number }) => void;
|
BridgeStateMap,
|
||||||
scrollToPreviousPage: () => void;
|
BridgeKey,
|
||||||
scrollToNextPage: () => void;
|
ViewerBridgeRegistry,
|
||||||
}
|
createBridgeRegistry,
|
||||||
|
registerBridge as setBridgeRef,
|
||||||
interface ZoomAPIWrapper {
|
ScrollAPIWrapper,
|
||||||
zoomIn: () => void;
|
ScrollState,
|
||||||
zoomOut: () => void;
|
ZoomAPIWrapper,
|
||||||
toggleMarqueeZoom: () => void;
|
ZoomState,
|
||||||
requestZoom: (level: number) => void;
|
PanAPIWrapper,
|
||||||
}
|
PanState,
|
||||||
|
SelectionAPIWrapper,
|
||||||
interface PanAPIWrapper {
|
SelectionState,
|
||||||
enable: () => void;
|
SpreadAPIWrapper,
|
||||||
disable: () => void;
|
SpreadState,
|
||||||
toggle: () => void;
|
RotationAPIWrapper,
|
||||||
}
|
RotationState,
|
||||||
|
SearchAPIWrapper,
|
||||||
interface SelectionAPIWrapper {
|
SearchState,
|
||||||
copyToClipboard: () => void;
|
SearchResult,
|
||||||
getSelectedText: () => string | any;
|
ThumbnailAPIWrapper,
|
||||||
getFormattedSelection: () => any;
|
ExportAPIWrapper,
|
||||||
}
|
ExportState,
|
||||||
|
} from '@core/contexts/viewer/viewerBridges';
|
||||||
interface SpreadAPIWrapper {
|
import { SpreadMode } from '@embedpdf/plugin-spread/react';
|
||||||
setSpreadMode: (mode: SpreadMode) => void;
|
|
||||||
getSpreadMode: () => SpreadMode | null;
|
|
||||||
toggleSpreadMode: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface RotationAPIWrapper {
|
|
||||||
rotateForward: () => void;
|
|
||||||
rotateBackward: () => void;
|
|
||||||
setRotation: (rotation: number) => void;
|
|
||||||
getRotation: () => number;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface SearchAPIWrapper {
|
|
||||||
search: (query: string) => Promise<any>;
|
|
||||||
clear: () => void;
|
|
||||||
next: () => void;
|
|
||||||
previous: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ThumbnailAPIWrapper {
|
|
||||||
renderThumb: (pageIndex: number, scale: number) => { toPromise: () => Promise<Blob> };
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ExportAPIWrapper {
|
|
||||||
download: () => void;
|
|
||||||
saveAsCopy: () => { toPromise: () => Promise<ArrayBuffer> };
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// State interfaces - represent the shape of data from each bridge
|
|
||||||
interface ScrollState {
|
|
||||||
currentPage: number;
|
|
||||||
totalPages: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ZoomState {
|
|
||||||
currentZoom: number;
|
|
||||||
zoomPercent: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface PanState {
|
|
||||||
isPanning: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface SelectionState {
|
|
||||||
hasSelection: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface SpreadState {
|
|
||||||
spreadMode: SpreadMode;
|
|
||||||
isDualPage: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface RotationState {
|
|
||||||
rotation: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface SearchResult {
|
|
||||||
pageIndex: number;
|
|
||||||
rects: Array<{
|
|
||||||
origin: { x: number; y: number };
|
|
||||||
size: { width: number; height: number };
|
|
||||||
}>;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface SearchState {
|
|
||||||
results: SearchResult[] | null;
|
|
||||||
activeIndex: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ExportState {
|
|
||||||
canExport: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bridge registration interface - bridges register with state and API
|
|
||||||
interface BridgeRef<TState = unknown, TApi = unknown> {
|
|
||||||
state: TState;
|
|
||||||
api: TApi;
|
|
||||||
}
|
|
||||||
|
|
||||||
function useImmediateNotifier<Args extends unknown[]>() {
|
function useImmediateNotifier<Args extends unknown[]>() {
|
||||||
const callbackRef = useRef<((...args: Args) => void) | null>(null);
|
const callbackRef = useRef<((...args: Args) => void) | null>(null);
|
||||||
@ -232,7 +152,7 @@ interface ViewerContextType {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Bridge registration - internal use by bridges
|
// Bridge registration - internal use by bridges
|
||||||
registerBridge: (type: string, ref: BridgeRef) => void;
|
registerBridge: (type: BridgeKey, ref: BridgeRef) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ViewerContext = createContext<ViewerContextType | null>(null);
|
export const ViewerContext = createContext<ViewerContextType | null>(null);
|
||||||
@ -252,17 +172,7 @@ export const ViewerProvider: React.FC<ViewerProviderProps> = ({ children }) => {
|
|||||||
useNavigation();
|
useNavigation();
|
||||||
|
|
||||||
// Bridge registry - bridges register their state and APIs here
|
// Bridge registry - bridges register their state and APIs here
|
||||||
const bridgeRefs = useRef({
|
const bridgeRefs = useRef<ViewerBridgeRegistry>(createBridgeRegistry());
|
||||||
scroll: null as BridgeRef<ScrollState, ScrollAPIWrapper> | null,
|
|
||||||
zoom: null as BridgeRef<ZoomState, ZoomAPIWrapper> | null,
|
|
||||||
pan: null as BridgeRef<PanState, PanAPIWrapper> | null,
|
|
||||||
selection: null as BridgeRef<SelectionState, SelectionAPIWrapper> | null,
|
|
||||||
search: null as BridgeRef<SearchState, SearchAPIWrapper> | null,
|
|
||||||
spread: null as BridgeRef<SpreadState, SpreadAPIWrapper> | null,
|
|
||||||
rotation: null as BridgeRef<RotationState, RotationAPIWrapper> | null,
|
|
||||||
thumbnail: null as BridgeRef<unknown, ThumbnailAPIWrapper> | null,
|
|
||||||
export: null as BridgeRef<ExportState, ExportAPIWrapper> | null,
|
|
||||||
});
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
register: registerImmediateZoomUpdate,
|
register: registerImmediateZoomUpdate,
|
||||||
@ -298,38 +208,15 @@ export const ViewerProvider: React.FC<ViewerProviderProps> = ({ children }) => {
|
|||||||
[triggerImmediateSpreadInternal]
|
[triggerImmediateSpreadInternal]
|
||||||
);
|
);
|
||||||
|
|
||||||
const registerBridge = (type: string, ref: BridgeRef) => {
|
const registerBridge = useCallback(
|
||||||
// Type-safe assignment - we know the bridges will provide correct types
|
<K extends BridgeKey>(
|
||||||
switch (type) {
|
type: K,
|
||||||
case 'scroll':
|
ref: BridgeRef<BridgeStateMap[K], BridgeApiMap[K]>
|
||||||
bridgeRefs.current.scroll = ref as BridgeRef<ScrollState, ScrollAPIWrapper>;
|
) => {
|
||||||
break;
|
setBridgeRef(bridgeRefs.current, type, ref);
|
||||||
case 'zoom':
|
},
|
||||||
bridgeRefs.current.zoom = ref as BridgeRef<ZoomState, ZoomAPIWrapper>;
|
[]
|
||||||
break;
|
);
|
||||||
case 'pan':
|
|
||||||
bridgeRefs.current.pan = ref as BridgeRef<PanState, PanAPIWrapper>;
|
|
||||||
break;
|
|
||||||
case 'selection':
|
|
||||||
bridgeRefs.current.selection = ref as BridgeRef<SelectionState, SelectionAPIWrapper>;
|
|
||||||
break;
|
|
||||||
case 'search':
|
|
||||||
bridgeRefs.current.search = ref as BridgeRef<SearchState, SearchAPIWrapper>;
|
|
||||||
break;
|
|
||||||
case 'spread':
|
|
||||||
bridgeRefs.current.spread = ref as BridgeRef<SpreadState, SpreadAPIWrapper>;
|
|
||||||
break;
|
|
||||||
case 'rotation':
|
|
||||||
bridgeRefs.current.rotation = ref as BridgeRef<RotationState, RotationAPIWrapper>;
|
|
||||||
break;
|
|
||||||
case 'thumbnail':
|
|
||||||
bridgeRefs.current.thumbnail = ref as BridgeRef<unknown, ThumbnailAPIWrapper>;
|
|
||||||
break;
|
|
||||||
case 'export':
|
|
||||||
bridgeRefs.current.export = ref as BridgeRef<ExportState, ExportAPIWrapper>;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const toggleThumbnailSidebar = () => {
|
const toggleThumbnailSidebar = () => {
|
||||||
setIsThumbnailSidebarVisible(prev => !prev);
|
setIsThumbnailSidebarVisible(prev => !prev);
|
||||||
|
|||||||
171
frontend/src/core/contexts/viewer/viewerBridges.ts
Normal file
171
frontend/src/core/contexts/viewer/viewerBridges.ts
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
import { SpreadMode } from '@embedpdf/plugin-spread/react';
|
||||||
|
|
||||||
|
export interface ScrollAPIWrapper {
|
||||||
|
scrollToPage: (params: { pageNumber: number }) => void;
|
||||||
|
scrollToPreviousPage: () => void;
|
||||||
|
scrollToNextPage: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ZoomAPIWrapper {
|
||||||
|
zoomIn: () => void;
|
||||||
|
zoomOut: () => void;
|
||||||
|
toggleMarqueeZoom: () => void;
|
||||||
|
requestZoom: (level: number) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PanAPIWrapper {
|
||||||
|
enable: () => void;
|
||||||
|
disable: () => void;
|
||||||
|
toggle: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SelectionAPIWrapper {
|
||||||
|
copyToClipboard: () => void;
|
||||||
|
getSelectedText: () => string | any;
|
||||||
|
getFormattedSelection: () => any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SpreadAPIWrapper {
|
||||||
|
setSpreadMode: (mode: SpreadMode) => void;
|
||||||
|
getSpreadMode: () => SpreadMode | null;
|
||||||
|
toggleSpreadMode: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RotationAPIWrapper {
|
||||||
|
rotateForward: () => void;
|
||||||
|
rotateBackward: () => void;
|
||||||
|
setRotation: (rotation: number) => void;
|
||||||
|
getRotation: () => number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SearchAPIWrapper {
|
||||||
|
search: (query: string) => Promise<any>;
|
||||||
|
clear: () => void;
|
||||||
|
next: () => void;
|
||||||
|
previous: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ThumbnailAPIWrapper {
|
||||||
|
renderThumb: (pageIndex: number, scale: number) => {
|
||||||
|
toPromise: () => Promise<Blob>;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExportAPIWrapper {
|
||||||
|
download: () => void;
|
||||||
|
saveAsCopy: () => { toPromise: () => Promise<ArrayBuffer> };
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ScrollState {
|
||||||
|
currentPage: number;
|
||||||
|
totalPages: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ZoomState {
|
||||||
|
currentZoom: number;
|
||||||
|
zoomPercent: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PanState {
|
||||||
|
isPanning: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SelectionState {
|
||||||
|
hasSelection: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SpreadState {
|
||||||
|
spreadMode: SpreadMode;
|
||||||
|
isDualPage: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RotationState {
|
||||||
|
rotation: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SearchResult {
|
||||||
|
pageIndex: number;
|
||||||
|
rects: Array<{
|
||||||
|
origin: { x: number; y: number };
|
||||||
|
size: { width: number; height: number };
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SearchState {
|
||||||
|
results: SearchResult[] | null;
|
||||||
|
activeIndex: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExportState {
|
||||||
|
canExport: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BridgeRef<TState = unknown, TApi = unknown> {
|
||||||
|
state: TState;
|
||||||
|
api: TApi;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BridgeStateMap {
|
||||||
|
scroll: ScrollState;
|
||||||
|
zoom: ZoomState;
|
||||||
|
pan: PanState;
|
||||||
|
selection: SelectionState;
|
||||||
|
spread: SpreadState;
|
||||||
|
rotation: RotationState;
|
||||||
|
search: SearchState;
|
||||||
|
thumbnail: unknown;
|
||||||
|
export: ExportState;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BridgeApiMap {
|
||||||
|
scroll: ScrollAPIWrapper;
|
||||||
|
zoom: ZoomAPIWrapper;
|
||||||
|
pan: PanAPIWrapper;
|
||||||
|
selection: SelectionAPIWrapper;
|
||||||
|
spread: SpreadAPIWrapper;
|
||||||
|
rotation: RotationAPIWrapper;
|
||||||
|
search: SearchAPIWrapper;
|
||||||
|
thumbnail: ThumbnailAPIWrapper;
|
||||||
|
export: ExportAPIWrapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type BridgeKey = keyof BridgeStateMap;
|
||||||
|
|
||||||
|
export type ViewerBridgeRegistry = {
|
||||||
|
[K in BridgeKey]: BridgeRef<BridgeStateMap[K], BridgeApiMap[K]> | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createBridgeRegistry = (): ViewerBridgeRegistry => ({
|
||||||
|
scroll: null,
|
||||||
|
zoom: null,
|
||||||
|
pan: null,
|
||||||
|
selection: null,
|
||||||
|
spread: null,
|
||||||
|
rotation: null,
|
||||||
|
search: null,
|
||||||
|
thumbnail: null,
|
||||||
|
export: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
export function registerBridge<K extends BridgeKey>(
|
||||||
|
registry: ViewerBridgeRegistry,
|
||||||
|
type: K,
|
||||||
|
ref: BridgeRef<BridgeStateMap[K], BridgeApiMap[K]>
|
||||||
|
): void {
|
||||||
|
registry[type] = ref;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getBridgeState<K extends BridgeKey>(
|
||||||
|
registry: ViewerBridgeRegistry,
|
||||||
|
type: K,
|
||||||
|
fallback: BridgeStateMap[K]
|
||||||
|
): BridgeStateMap[K] {
|
||||||
|
return registry[type]?.state ?? fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getBridgeApi<K extends BridgeKey>(
|
||||||
|
registry: ViewerBridgeRegistry,
|
||||||
|
type: K
|
||||||
|
): BridgeApiMap[K] | null {
|
||||||
|
return registry[type]?.api ?? null;
|
||||||
|
}
|
||||||
@ -167,7 +167,7 @@ export function useFitWidthResize({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof timeoutId === 'number') {
|
if (timeoutId !== undefined) {
|
||||||
window.clearTimeout(timeoutId);
|
window.clearTimeout(timeoutId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -179,7 +179,7 @@ export function useFitWidthResize({
|
|||||||
|
|
||||||
window.addEventListener('resize', handleResize);
|
window.addEventListener('resize', handleResize);
|
||||||
return () => {
|
return () => {
|
||||||
if (typeof timeoutId === 'number') {
|
if (timeoutId !== undefined) {
|
||||||
window.clearTimeout(timeoutId);
|
window.clearTimeout(timeoutId);
|
||||||
}
|
}
|
||||||
window.removeEventListener('resize', handleResize);
|
window.removeEventListener('resize', handleResize);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user