Feature/v2/bookmarks (#4949)

bookmark panel on viewer
This commit is contained in:
Reece Browne
2025-11-21 11:33:49 +00:00
committed by GitHub
parent fca8470637
commit 4fd336c26c
12 changed files with 990 additions and 3 deletions
@@ -17,6 +17,7 @@ import {
RotationActions,
SearchActions,
ExportActions,
BookmarkActions,
} from '@app/contexts/viewer/viewerActions';
import {
BridgeRef,
@@ -35,6 +36,7 @@ import {
SearchState,
ExportState,
ThumbnailAPIWrapper,
BookmarkState,
} from '@app/contexts/viewer/viewerBridges';
import { SpreadMode } from '@embedpdf/plugin-spread/react';
@@ -74,6 +76,8 @@ interface ViewerContextType {
// UI state managed by this context
isThumbnailSidebarVisible: boolean;
toggleThumbnailSidebar: () => void;
isBookmarkSidebarVisible: boolean;
toggleBookmarkSidebar: () => void;
// Annotation visibility toggle
isAnnotationsVisible: boolean;
@@ -98,6 +102,8 @@ interface ViewerContextType {
getSearchState: () => SearchState;
getThumbnailAPI: () => ThumbnailAPIWrapper | null;
getExportState: () => ExportState;
getBookmarkState: () => BookmarkState;
hasBookmarkSupport: () => boolean;
// Immediate update callbacks
registerImmediateZoomUpdate: (callback: (percent: number) => void) => () => void;
@@ -118,6 +124,7 @@ interface ViewerContextType {
rotationActions: RotationActions;
searchActions: SearchActions;
exportActions: ExportActions;
bookmarkActions: BookmarkActions;
// Bridge registration - internal use by bridges
registerBridge: <K extends BridgeKey>(
@@ -135,6 +142,7 @@ interface ViewerProviderProps {
export const ViewerProvider: React.FC<ViewerProviderProps> = ({ children }) => {
// UI state - only state directly managed by this context
const [isThumbnailSidebarVisible, setIsThumbnailSidebarVisible] = useState(false);
const [isBookmarkSidebarVisible, setIsBookmarkSidebarVisible] = useState(false);
const [isAnnotationsVisible, setIsAnnotationsVisible] = useState(true);
const [isAnnotationMode, setIsAnnotationModeState] = useState(false);
const [activeFileIndex, setActiveFileIndex] = useState(0);
@@ -193,6 +201,10 @@ export const ViewerProvider: React.FC<ViewerProviderProps> = ({ children }) => {
setIsThumbnailSidebarVisible(prev => !prev);
};
const toggleBookmarkSidebar = () => {
setIsBookmarkSidebarVisible(prev => !prev);
};
const toggleAnnotationsVisibility = () => {
setIsAnnotationsVisible(prev => !prev);
};
@@ -242,6 +254,18 @@ export const ViewerProvider: React.FC<ViewerProviderProps> = ({ children }) => {
return bridgeRefs.current.export?.state || { canExport: false };
};
const getBookmarkState = (): BookmarkState => {
return (
bridgeRefs.current.bookmark?.state || {
bookmarks: null,
isLoading: false,
error: null,
}
);
};
const hasBookmarkSupport = () => Boolean(bridgeRefs.current.bookmark);
// Action handlers - call APIs directly
const {
scrollActions,
@@ -252,6 +276,7 @@ export const ViewerProvider: React.FC<ViewerProviderProps> = ({ children }) => {
rotationActions,
searchActions,
exportActions,
bookmarkActions,
} = createViewerActions({
registry: bridgeRefs,
getScrollState,
@@ -263,6 +288,8 @@ export const ViewerProvider: React.FC<ViewerProviderProps> = ({ children }) => {
// UI state
isThumbnailSidebarVisible,
toggleThumbnailSidebar,
isBookmarkSidebarVisible,
toggleBookmarkSidebar,
// Annotation controls
isAnnotationsVisible,
@@ -285,6 +312,8 @@ export const ViewerProvider: React.FC<ViewerProviderProps> = ({ children }) => {
getSearchState,
getThumbnailAPI,
getExportState,
getBookmarkState,
hasBookmarkSupport,
// Immediate updates
registerImmediateZoomUpdate,
@@ -303,6 +332,7 @@ export const ViewerProvider: React.FC<ViewerProviderProps> = ({ children }) => {
rotationActions,
searchActions,
exportActions,
bookmarkActions,
// Bridge registration
registerBridge,
@@ -5,6 +5,7 @@ import {
ScrollState,
ZoomState,
} from '@app/contexts/viewer/viewerBridges';
import { PdfBookmarkObject } from '@embedpdf/models';
export interface ScrollActions {
scrollToPage: (page: number) => void;
@@ -58,6 +59,12 @@ export interface ExportActions {
saveAsCopy: () => Promise<ArrayBuffer | null>;
}
export interface BookmarkActions {
fetchBookmarks: () => Promise<PdfBookmarkObject[] | null>;
clearBookmarks: () => void;
setLocalBookmarks: (bookmarks: PdfBookmarkObject[] | null, error?: string | null) => void;
}
export interface ViewerActionsBundle {
scrollActions: ScrollActions;
zoomActions: ZoomActions;
@@ -67,6 +74,7 @@ export interface ViewerActionsBundle {
rotationActions: RotationActions;
searchActions: SearchActions;
exportActions: ExportActions;
bookmarkActions: BookmarkActions;
}
interface ViewerActionDependencies {
@@ -307,5 +315,22 @@ export function createViewerActions({
rotationActions,
searchActions,
exportActions,
bookmarkActions: {
fetchBookmarks: async () => {
const api = registry.current.bookmark?.api;
if (!api?.fetchBookmarks) {
return null;
}
return api.fetchBookmarks();
},
clearBookmarks: () => {
const api = registry.current.bookmark?.api;
api?.clearBookmarks?.();
},
setLocalBookmarks: (bookmarks, error = null) => {
const api = registry.current.bookmark?.api;
api?.setLocalBookmarks?.(bookmarks ?? null, error);
},
},
};
}
@@ -1,4 +1,5 @@
import { SpreadMode } from '@embedpdf/plugin-spread/react';
import { PdfBookmarkObject } from '@embedpdf/models';
export interface ScrollAPIWrapper {
scrollToPage: (params: { pageNumber: number }) => void;
@@ -59,6 +60,12 @@ export interface ExportAPIWrapper {
saveAsCopy: () => { toPromise: () => Promise<ArrayBuffer> };
}
export interface BookmarkAPIWrapper {
fetchBookmarks: () => Promise<PdfBookmarkObject[]>;
clearBookmarks: () => void;
setLocalBookmarks: (bookmarks: PdfBookmarkObject[] | null, error?: string | null) => void;
}
export interface ScrollState {
currentPage: number;
totalPages: number;
@@ -103,6 +110,12 @@ export interface ExportState {
canExport: boolean;
}
export interface BookmarkState {
bookmarks: PdfBookmarkObject[] | null;
isLoading: boolean;
error: string | null;
}
export interface BridgeRef<TState = unknown, TApi = unknown> {
state: TState;
api: TApi;
@@ -118,6 +131,7 @@ export interface BridgeStateMap {
search: SearchState;
thumbnail: unknown;
export: ExportState;
bookmark: BookmarkState;
}
export interface BridgeApiMap {
@@ -130,6 +144,7 @@ export interface BridgeApiMap {
search: SearchAPIWrapper;
thumbnail: ThumbnailAPIWrapper;
export: ExportAPIWrapper;
bookmark: BookmarkAPIWrapper;
}
export type BridgeKey = keyof BridgeStateMap;
@@ -148,6 +163,7 @@ export const createBridgeRegistry = (): ViewerBridgeRegistry => ({
search: null,
thumbnail: null,
export: null,
bookmark: null,
});
export function registerBridge<K extends BridgeKey>(