@dtducas/wh-forge-viewer
v2.0.0-beta.2
Published
A customized Autodesk Forge Viewer component for React with custom toolbar and PDF support
Maintainers
Readme
wh-forge-viewer
A professional Autodesk Forge Viewer component for React with advanced PDF support, comprehensive markup tools, and real-time collaboration capabilities.
Features
Core Viewer
- Custom Toolbar - Pan, Document Browser, Download, and Pagination controls
- Enhanced PDF Support - Optimized for viewing PDF documents with intelligent page navigation
- Smart Document Browser - Auto-opens with adaptive panel sizing
- 3D/2D Models - Full support for DWF, DWFX formats
- React Integration - Easy-to-use React component with hooks support
Markup Tools
- 12 Markup Tools - Arrow, Rectangle, Circle, Cloud, Text, Freehand, Line, Polyline, Polycloud, Callout, Image/Stamp
- SelectTool with Multi-Select - Drag rectangle to select multiple markups
- Property Panel - Excalidraw-inspired UI for editing markup properties
- Undo/Redo - Full undo/redo support for all operations
- Custom Stamps - Insert images as stamps with drag-drop support
Collaboration
- Real-Time Sync - Subscribe to markup changes for WebSocket broadcasting
- User Presence - Track and display remote user cursors
- Persistence API - Save/load markups with JSON or SVG formats
- Page Change Events - Handle markup data during page navigation
Architecture
- SOLID Principles - Manager pattern with clear separation of concerns
- Type Safety - Full TypeScript definitions included
- Event-Driven - Decoupled communication via EventBus
- Extensible - Factory pattern for adding custom tools
Installation
npm install @dtducas/wh-forge-viewerQuick Start
Basic Viewer
import { ViewerForgePDF } from '@dtducas/wh-forge-viewer';
function App() {
return (
<ViewerForgePDF
filePath='https://example.com/document.pdf'
fileExt='pdf'
onViewerReady={(viewer) => console.log('Ready:', viewer)}
/>
);
}With Markup Tools
import {
ViewerForgePDF,
DrawingManager,
MarkupToolbar,
PropertyPanel,
EventBus,
} from '@dtducas/wh-forge-viewer';
function MarkupViewer() {
const [viewer, setViewer] = useState(null);
const [drawingManager, setDrawingManager] = useState(null);
const [activeTool, setActiveTool] = useState(null);
const [propertyConfig, setPropertyConfig] = useState(null);
const [propertyValues, setPropertyValues] = useState({});
const eventBus = EventBus.getInstance();
useEffect(() => {
if (!viewer) return;
const manager = new DrawingManager(viewer, eventBus, {
autoEnterEditMode: true,
defaultTool: 'arrow',
});
setDrawingManager(manager);
return () => manager.destroy();
}, [viewer]);
const handleToolChange = (tool) => {
drawingManager?.setActiveTool(tool);
setActiveTool(tool);
setPropertyConfig(drawingManager?.getCurrentToolPropertyConfig());
setPropertyValues(drawingManager?.getCurrentToolPropertyValues() || {});
};
return (
<div style={{ position: 'relative', width: '100%', height: '100vh' }}>
<ViewerForgePDF
filePath='/document.pdf'
fileExt='pdf'
onViewerReady={setViewer}
/>
{drawingManager && (
<>
<MarkupToolbar
activeTool={activeTool}
onToolChange={handleToolChange}
onUndo={() => drawingManager.undo()}
onRedo={() => drawingManager.redo()}
/>
<PropertyPanel
config={propertyConfig}
values={propertyValues}
onChange={(key, value) => {
drawingManager.handlePropertyChange(key, value);
setPropertyValues(drawingManager.getCurrentToolPropertyValues());
}}
/>
</>
)}
</div>
);
}Real-Time Collaboration
import {
DrawingManager,
EventBus,
CursorOverlay,
} from '@dtducas/wh-forge-viewer';
function CollaborativeViewer({ socket }) {
const [drawingManager, setDrawingManager] = useState(null);
const [remoteCursors, setRemoteCursors] = useState(new Map());
useEffect(() => {
if (!drawingManager) return;
// Subscribe to local markup changes
const unsubscribe = drawingManager.onMarkupChange((delta) => {
socket.emit('markup:delta', delta);
});
// Subscribe to local cursor movement
const unsubCursor = drawingManager.onLocalCursorMove((position) => {
socket.emit('cursor:move', { userId: currentUser.id, position });
});
// Handle remote changes
socket.on('markup:delta', (delta) => {
drawingManager.applyDelta(delta);
});
// Handle remote cursors
socket.on('cursor:move', (cursor) => {
drawingManager.updateUserCursor(cursor);
setRemoteCursors(new Map(drawingManager.getRemoteCursors()));
});
return () => {
unsubscribe();
unsubCursor();
};
}, [drawingManager, socket]);
return (
<>
{/* ... viewer components ... */}
<CursorOverlay cursors={remoteCursors} />
</>
);
}Persistence
// Save to localStorage
const data = drawingManager.getMarkupData();
localStorage.setItem('markups', JSON.stringify(data));
// Load from localStorage
const saved = JSON.parse(localStorage.getItem('markups'));
await drawingManager.loadMarkupData(saved, { mode: 'replace' });
// Save to database (JSON format)
const jsonData = drawingManager.getMarkupDataAsJson('page-guid-123');
await db.collection('markups').doc('page-guid-123').set(jsonData);
// Load from database
const doc = await db.collection('markups').doc('page-guid-123').get();
await drawingManager.loadMarkupDataFromJson(doc.data());PDF Export with Markup
// Export current page with markup as vector overlay
const result = await drawingManager.exportCurrentPagePDF(filePath, 0);
if (result.success && result.blob) {
const url = URL.createObjectURL(result.blob);
window.open(url); // Preview in new tab
}
// Multi-page export
const markupsByPage = [
{ pageIndex: 0, svgString: savedPage0Svg },
{ pageIndex: 1, svgString: drawingManager.getMarkupSVG() }, // current page
];
await drawingManager.exportAndDownloadPDF(filePath, markupsByPage, {
filename: 'annotated-document',
});Components
ViewerForgePDF
Main viewer component.
| Prop | Type | Required | Description |
| ------------------ | -------- | -------- | ------------------------------- |
| filePath | string | Yes | URL to the document |
| fileExt | string | Yes | File extension (pdf, dwf, dwfx) |
| onViewerReady | function | No | Callback with viewer instance |
| onDocumentLoaded | function | No | Callback with viewables array |
| onError | function | No | Callback for errors |
MarkupToolbar
Floating toolbar for tool selection.
| Prop | Type | Description |
| -------------- | -------- | --------------------- |
| activeTool | string | Currently active tool |
| onToolChange | function | Tool change callback |
| onUndo | function | Undo callback |
| onRedo | function | Redo callback |
PropertyPanel
Side panel for editing properties.
| Prop | Type | Description |
| ---------- | ------------------- | ------------------------ |
| config | IToolPropertyConfig | Property configuration |
| values | object | Current property values |
| onChange | function | Property change callback |
| onAction | function | Action button callback |
CursorOverlay
Overlay for displaying remote cursors.
| Prop | Type | Description |
| -------------- | ------- | ----------------------------- |
| cursors | Map | Remote user cursors |
| viewerBounds | DOMRect | Viewer bounds for positioning |
DrawingManager API
Edit Mode
await drawingManager.enterEditMode(); // Enter markup mode
drawingManager.leaveEditMode(); // Exit markup mode
drawingManager.isEditMode(); // Check if in edit modeTools
drawingManager.setActiveTool('arrow'); // Set active tool
drawingManager.getActiveTool(); // Get current tool
drawingManager.setStrokeStyle('#e03131', 2, 1.0); // Set stroke
drawingManager.setFillStyle('#228be6', 0.3); // Set fillAvailable Tools:
select, arrow, rectangle, ellipse, cloud, label, freehand, line, polyline, polycloud, callout, image
Markup Operations
drawingManager.saveMarkups(); // Save current markups
drawingManager.loadMarkups(data); // Load markups
drawingManager.clearMarkups(); // Clear all markups
drawingManager.exportToSVG(); // Export to SVG string
drawingManager.undo(); // Undo last action
drawingManager.redo(); // Redo last undone actionCollaborative API
// Get markup data for persistence
const data = drawingManager.getMarkupData(viewableGUID);
// Load markup data
await drawingManager.loadMarkupData(data, { mode: 'replace' });
// Subscribe to changes
const unsubscribe = drawingManager.onMarkupChange((delta) => {
// delta: { type: 'create'|'update'|'delete', markupId, data, timestamp }
socket.emit('markup:delta', delta);
});
// Apply remote changes
drawingManager.applyDelta(delta);
// Batch apply (for initial load)
drawingManager.applyBatch(elements, true);User Presence
// Broadcast local cursor
const unsub = drawingManager.onLocalCursorMove((position) => {
socket.emit('cursor:move', { userId, position });
});
// Update remote cursor
drawingManager.updateUserCursor({ userId, userName, color, position });
// Remove cursor when user leaves
drawingManager.removeUserCursor(userId);
// Get all remote cursors
const cursors = drawingManager.getRemoteCursors();EventBus Events
| Event | Payload | Description |
| -------------------------- | ------------------------ | ---------------------- |
| page:changing | { fromIndex, toIndex } | Before page navigation |
| page:changed | { index, viewable } | After page navigation |
| drawing:editModeEntered | {} | Entered markup mode |
| drawing:editModeExited | {} | Exited markup mode |
| drawing:toolChanged | { tool } | Tool changed |
| drawing:propertiesSynced | { values } | Properties updated |
import { EventBus, EVENT_NAMES } from '@dtducas/wh-forge-viewer';
const eventBus = EventBus.getInstance();
eventBus.on(EVENT_NAMES.PAGE_CHANGED, ({ index }) => {
console.log('Now on page:', index);
});Custom Toolbar
Built-in toolbar includes:
Tools Group:
- Pan Tool - Navigate the document
- Document Browser - Toggle thumbnail/tree view
- Download - Download current document
Pagination Group:
- Previous/Next Page navigation
- Current page indicator
Smart Features:
- No-refresh navigation when Document Browser is open
- Self-healing mechanism for toolbar persistence
- Synchronized button states
Requirements
- React ^17.0.0 || ^18.0.0
- Ant Design ^5.0.0 (peer dependency)
- Modern browsers (Chrome 90+, Firefox 88+, Safari 14+, Edge 90+)
Development
# Install dependencies
npm install
# Build package
npm run build
# Watch mode
npm run dev
# Test app
cd test/app && npm install && npm run devDocumentation
- API Reference - Complete API documentation
- Architecture - Detailed architecture documentation
- Versioning Guide - Forge Viewer version management
- Collaborative API - Real-time collaboration guide
License
MIT
Author
Duong Tran Quang
- Email: [email protected]
- GitHub: @DTDucas
- LinkedIn: dtducas
Support
Made with care by DTDucas
