@dtducas/wh-forge-viewer 1.0.7 → 1.0.9-beta0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +364 -58
- package/dist/index.d.ts +1062 -4
- package/dist/index.esm.js +49638 -1473
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +49695 -1474
- package/dist/index.js.map +1 -1
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -1,92 +1,392 @@
|
|
|
1
1
|
# wh-forge-viewer
|
|
2
2
|
|
|
3
|
-
A
|
|
3
|
+
A professional Autodesk Forge Viewer component for React with advanced PDF support, comprehensive markup tools, and real-time collaboration capabilities.
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
|
|
7
|
+
### Core Viewer
|
|
8
|
+
|
|
9
|
+
- **Custom Toolbar** - Pan, Document Browser, Download, and Pagination controls
|
|
10
|
+
- **Enhanced PDF Support** - Optimized for viewing PDF documents with intelligent page navigation
|
|
11
|
+
- **Smart Document Browser** - Auto-opens with adaptive panel sizing
|
|
12
|
+
- **3D/2D Models** - Full support for DWF, DWFX formats
|
|
13
|
+
- **React Integration** - Easy-to-use React component with hooks support
|
|
14
|
+
|
|
15
|
+
### Markup Tools
|
|
16
|
+
|
|
17
|
+
- **12 Markup Tools** - Arrow, Rectangle, Circle, Cloud, Text, Freehand, Line, Polyline, Polycloud, Callout, Image/Stamp
|
|
18
|
+
- **SelectTool with Multi-Select** - Drag rectangle to select multiple markups
|
|
19
|
+
- **Property Panel** - Excalidraw-inspired UI for editing markup properties
|
|
20
|
+
- **Undo/Redo** - Full undo/redo support for all operations
|
|
21
|
+
- **Custom Stamps** - Insert images as stamps with drag-drop support
|
|
22
|
+
|
|
23
|
+
### Collaboration
|
|
24
|
+
|
|
25
|
+
- **Real-Time Sync** - Subscribe to markup changes for WebSocket broadcasting
|
|
26
|
+
- **User Presence** - Track and display remote user cursors
|
|
27
|
+
- **Persistence API** - Save/load markups with JSON or SVG formats
|
|
28
|
+
- **Page Change Events** - Handle markup data during page navigation
|
|
29
|
+
|
|
30
|
+
### Architecture
|
|
31
|
+
|
|
32
|
+
- **SOLID Principles** - Manager pattern with clear separation of concerns
|
|
33
|
+
- **Type Safety** - Full TypeScript definitions included
|
|
34
|
+
- **Event-Driven** - Decoupled communication via EventBus
|
|
35
|
+
- **Extensible** - Factory pattern for adding custom tools
|
|
15
36
|
|
|
16
37
|
## Installation
|
|
17
38
|
|
|
18
39
|
```bash
|
|
19
|
-
npm install wh-forge-viewer
|
|
40
|
+
npm install @dtducas/wh-forge-viewer
|
|
20
41
|
```
|
|
21
42
|
|
|
22
|
-
##
|
|
43
|
+
## Quick Start
|
|
23
44
|
|
|
24
|
-
|
|
25
|
-
import { ViewerForgePDF } from 'wh-forge-viewer';
|
|
45
|
+
### Basic Viewer
|
|
26
46
|
|
|
27
|
-
|
|
28
|
-
|
|
47
|
+
```jsx
|
|
48
|
+
import { ViewerForgePDF } from '@dtducas/wh-forge-viewer';
|
|
29
49
|
|
|
50
|
+
function App() {
|
|
30
51
|
return (
|
|
31
52
|
<ViewerForgePDF
|
|
32
53
|
filePath='https://example.com/document.pdf'
|
|
33
54
|
fileExt='pdf'
|
|
34
|
-
|
|
55
|
+
onViewerReady={(viewer) => console.log('Ready:', viewer)}
|
|
35
56
|
/>
|
|
36
57
|
);
|
|
37
58
|
}
|
|
38
59
|
```
|
|
39
60
|
|
|
40
|
-
|
|
61
|
+
### With Markup Tools
|
|
41
62
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
63
|
+
```jsx
|
|
64
|
+
import {
|
|
65
|
+
ViewerForgePDF,
|
|
66
|
+
DrawingManager,
|
|
67
|
+
MarkupToolbar,
|
|
68
|
+
PropertyPanel,
|
|
69
|
+
EventBus,
|
|
70
|
+
} from '@dtducas/wh-forge-viewer';
|
|
71
|
+
|
|
72
|
+
function MarkupViewer() {
|
|
73
|
+
const [viewer, setViewer] = useState(null);
|
|
74
|
+
const [drawingManager, setDrawingManager] = useState(null);
|
|
75
|
+
const [activeTool, setActiveTool] = useState(null);
|
|
76
|
+
const [propertyConfig, setPropertyConfig] = useState(null);
|
|
77
|
+
const [propertyValues, setPropertyValues] = useState({});
|
|
47
78
|
|
|
48
|
-
|
|
79
|
+
const eventBus = EventBus.getInstance();
|
|
49
80
|
|
|
50
|
-
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
if (!viewer) return;
|
|
51
83
|
|
|
52
|
-
|
|
84
|
+
const manager = new DrawingManager(viewer, eventBus, {
|
|
85
|
+
autoEnterEditMode: true,
|
|
86
|
+
defaultTool: 'arrow',
|
|
87
|
+
});
|
|
53
88
|
|
|
54
|
-
|
|
55
|
-
- **Document Browser** - Toggle document tree view
|
|
56
|
-
- **Download** - Download the current document
|
|
89
|
+
setDrawingManager(manager);
|
|
57
90
|
|
|
58
|
-
|
|
91
|
+
return () => manager.destroy();
|
|
92
|
+
}, [viewer]);
|
|
59
93
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
94
|
+
const handleToolChange = (tool) => {
|
|
95
|
+
drawingManager?.setActiveTool(tool);
|
|
96
|
+
setActiveTool(tool);
|
|
97
|
+
setPropertyConfig(drawingManager?.getCurrentToolPropertyConfig());
|
|
98
|
+
setPropertyValues(drawingManager?.getCurrentToolPropertyValues() || {});
|
|
99
|
+
};
|
|
63
100
|
|
|
64
|
-
|
|
101
|
+
return (
|
|
102
|
+
<div style={{ position: 'relative', width: '100%', height: '100vh' }}>
|
|
103
|
+
<ViewerForgePDF
|
|
104
|
+
filePath='/document.pdf'
|
|
105
|
+
fileExt='pdf'
|
|
106
|
+
onViewerReady={setViewer}
|
|
107
|
+
/>
|
|
108
|
+
|
|
109
|
+
{drawingManager && (
|
|
110
|
+
<>
|
|
111
|
+
<MarkupToolbar
|
|
112
|
+
activeTool={activeTool}
|
|
113
|
+
onToolChange={handleToolChange}
|
|
114
|
+
onUndo={() => drawingManager.undo()}
|
|
115
|
+
onRedo={() => drawingManager.redo()}
|
|
116
|
+
/>
|
|
117
|
+
|
|
118
|
+
<PropertyPanel
|
|
119
|
+
config={propertyConfig}
|
|
120
|
+
values={propertyValues}
|
|
121
|
+
onChange={(key, value) => {
|
|
122
|
+
drawingManager.handlePropertyChange(key, value);
|
|
123
|
+
setPropertyValues(drawingManager.getCurrentToolPropertyValues());
|
|
124
|
+
}}
|
|
125
|
+
/>
|
|
126
|
+
</>
|
|
127
|
+
)}
|
|
128
|
+
</div>
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
```
|
|
65
132
|
|
|
66
|
-
-
|
|
67
|
-
|
|
68
|
-
|
|
133
|
+
### Real-Time Collaboration
|
|
134
|
+
|
|
135
|
+
```jsx
|
|
136
|
+
import {
|
|
137
|
+
DrawingManager,
|
|
138
|
+
EventBus,
|
|
139
|
+
CursorOverlay,
|
|
140
|
+
} from '@dtducas/wh-forge-viewer';
|
|
141
|
+
|
|
142
|
+
function CollaborativeViewer({ socket }) {
|
|
143
|
+
const [drawingManager, setDrawingManager] = useState(null);
|
|
144
|
+
const [remoteCursors, setRemoteCursors] = useState(new Map());
|
|
145
|
+
|
|
146
|
+
useEffect(() => {
|
|
147
|
+
if (!drawingManager) return;
|
|
148
|
+
|
|
149
|
+
// Subscribe to local markup changes
|
|
150
|
+
const unsubscribe = drawingManager.onMarkupChange((delta) => {
|
|
151
|
+
socket.emit('markup:delta', delta);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
// Subscribe to local cursor movement
|
|
155
|
+
const unsubCursor = drawingManager.onLocalCursorMove((position) => {
|
|
156
|
+
socket.emit('cursor:move', { userId: currentUser.id, position });
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
// Handle remote changes
|
|
160
|
+
socket.on('markup:delta', (delta) => {
|
|
161
|
+
drawingManager.applyDelta(delta);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
// Handle remote cursors
|
|
165
|
+
socket.on('cursor:move', (cursor) => {
|
|
166
|
+
drawingManager.updateUserCursor(cursor);
|
|
167
|
+
setRemoteCursors(new Map(drawingManager.getRemoteCursors()));
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
return () => {
|
|
171
|
+
unsubscribe();
|
|
172
|
+
unsubCursor();
|
|
173
|
+
};
|
|
174
|
+
}, [drawingManager, socket]);
|
|
175
|
+
|
|
176
|
+
return (
|
|
177
|
+
<>
|
|
178
|
+
{/* ... viewer components ... */}
|
|
179
|
+
<CursorOverlay cursors={remoteCursors} />
|
|
180
|
+
</>
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Persistence
|
|
186
|
+
|
|
187
|
+
```javascript
|
|
188
|
+
// Save to localStorage
|
|
189
|
+
const data = drawingManager.getMarkupData();
|
|
190
|
+
localStorage.setItem('markups', JSON.stringify(data));
|
|
191
|
+
|
|
192
|
+
// Load from localStorage
|
|
193
|
+
const saved = JSON.parse(localStorage.getItem('markups'));
|
|
194
|
+
await drawingManager.loadMarkupData(saved, { mode: 'replace' });
|
|
195
|
+
|
|
196
|
+
// Save to database (JSON format)
|
|
197
|
+
const jsonData = drawingManager.getMarkupDataAsJson('page-guid-123');
|
|
198
|
+
await db.collection('markups').doc('page-guid-123').set(jsonData);
|
|
199
|
+
|
|
200
|
+
// Load from database
|
|
201
|
+
const doc = await db.collection('markups').doc('page-guid-123').get();
|
|
202
|
+
await drawingManager.loadMarkupDataFromJson(doc.data());
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### PDF Export with Markup
|
|
206
|
+
|
|
207
|
+
```javascript
|
|
208
|
+
// Export current page with markup as vector overlay
|
|
209
|
+
const result = await drawingManager.exportCurrentPagePDF(filePath, 0);
|
|
210
|
+
if (result.success && result.blob) {
|
|
211
|
+
const url = URL.createObjectURL(result.blob);
|
|
212
|
+
window.open(url); // Preview in new tab
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Multi-page export
|
|
216
|
+
const markupsByPage = [
|
|
217
|
+
{ pageIndex: 0, svgString: savedPage0Svg },
|
|
218
|
+
{ pageIndex: 1, svgString: drawingManager.getMarkupSVG() }, // current page
|
|
219
|
+
];
|
|
220
|
+
await drawingManager.exportAndDownloadPDF(filePath, markupsByPage, {
|
|
221
|
+
filename: 'annotated-document',
|
|
222
|
+
});
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
## Components
|
|
226
|
+
|
|
227
|
+
### ViewerForgePDF
|
|
228
|
+
|
|
229
|
+
Main viewer component.
|
|
230
|
+
|
|
231
|
+
| Prop | Type | Required | Description |
|
|
232
|
+
| ------------------ | -------- | -------- | ------------------------------- |
|
|
233
|
+
| `filePath` | string | Yes | URL to the document |
|
|
234
|
+
| `fileExt` | string | Yes | File extension (pdf, dwf, dwfx) |
|
|
235
|
+
| `onViewerReady` | function | No | Callback with viewer instance |
|
|
236
|
+
| `onDocumentLoaded` | function | No | Callback with viewables array |
|
|
237
|
+
| `onError` | function | No | Callback for errors |
|
|
238
|
+
|
|
239
|
+
### MarkupToolbar
|
|
240
|
+
|
|
241
|
+
Floating toolbar for tool selection.
|
|
242
|
+
|
|
243
|
+
| Prop | Type | Description |
|
|
244
|
+
| -------------- | -------- | --------------------- |
|
|
245
|
+
| `activeTool` | string | Currently active tool |
|
|
246
|
+
| `onToolChange` | function | Tool change callback |
|
|
247
|
+
| `onUndo` | function | Undo callback |
|
|
248
|
+
| `onRedo` | function | Redo callback |
|
|
249
|
+
|
|
250
|
+
### PropertyPanel
|
|
69
251
|
|
|
70
|
-
|
|
252
|
+
Side panel for editing properties.
|
|
71
253
|
|
|
72
|
-
|
|
254
|
+
| Prop | Type | Description |
|
|
255
|
+
| ---------- | ------------------- | ------------------------ |
|
|
256
|
+
| `config` | IToolPropertyConfig | Property configuration |
|
|
257
|
+
| `values` | object | Current property values |
|
|
258
|
+
| `onChange` | function | Property change callback |
|
|
259
|
+
| `onAction` | function | Action button callback |
|
|
73
260
|
|
|
74
|
-
|
|
261
|
+
### CursorOverlay
|
|
75
262
|
|
|
76
|
-
|
|
263
|
+
Overlay for displaying remote cursors.
|
|
77
264
|
|
|
78
|
-
|
|
265
|
+
| Prop | Type | Description |
|
|
266
|
+
| -------------- | ------- | ----------------------------- |
|
|
267
|
+
| `cursors` | Map | Remote user cursors |
|
|
268
|
+
| `viewerBounds` | DOMRect | Viewer bounds for positioning |
|
|
79
269
|
|
|
80
|
-
##
|
|
270
|
+
## DrawingManager API
|
|
81
271
|
|
|
82
|
-
|
|
272
|
+
### Edit Mode
|
|
83
273
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
274
|
+
```typescript
|
|
275
|
+
await drawingManager.enterEditMode(); // Enter markup mode
|
|
276
|
+
drawingManager.leaveEditMode(); // Exit markup mode
|
|
277
|
+
drawingManager.isEditMode(); // Check if in edit mode
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
### Tools
|
|
281
|
+
|
|
282
|
+
```typescript
|
|
283
|
+
drawingManager.setActiveTool('arrow'); // Set active tool
|
|
284
|
+
drawingManager.getActiveTool(); // Get current tool
|
|
285
|
+
drawingManager.setStrokeStyle('#e03131', 2, 1.0); // Set stroke
|
|
286
|
+
drawingManager.setFillStyle('#228be6', 0.3); // Set fill
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
**Available Tools:**
|
|
290
|
+
`select`, `arrow`, `rectangle`, `ellipse`, `cloud`, `label`, `freehand`, `line`, `polyline`, `polycloud`, `callout`, `image`
|
|
291
|
+
|
|
292
|
+
### Markup Operations
|
|
293
|
+
|
|
294
|
+
```typescript
|
|
295
|
+
drawingManager.saveMarkups(); // Save current markups
|
|
296
|
+
drawingManager.loadMarkups(data); // Load markups
|
|
297
|
+
drawingManager.clearMarkups(); // Clear all markups
|
|
298
|
+
drawingManager.exportToSVG(); // Export to SVG string
|
|
299
|
+
drawingManager.undo(); // Undo last action
|
|
300
|
+
drawingManager.redo(); // Redo last undone action
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
### Collaborative API
|
|
304
|
+
|
|
305
|
+
```typescript
|
|
306
|
+
// Get markup data for persistence
|
|
307
|
+
const data = drawingManager.getMarkupData(viewableGUID);
|
|
308
|
+
|
|
309
|
+
// Load markup data
|
|
310
|
+
await drawingManager.loadMarkupData(data, { mode: 'replace' });
|
|
311
|
+
|
|
312
|
+
// Subscribe to changes
|
|
313
|
+
const unsubscribe = drawingManager.onMarkupChange((delta) => {
|
|
314
|
+
// delta: { type: 'create'|'update'|'delete', markupId, data, timestamp }
|
|
315
|
+
socket.emit('markup:delta', delta);
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
// Apply remote changes
|
|
319
|
+
drawingManager.applyDelta(delta);
|
|
320
|
+
|
|
321
|
+
// Batch apply (for initial load)
|
|
322
|
+
drawingManager.applyBatch(elements, true);
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
### User Presence
|
|
326
|
+
|
|
327
|
+
```typescript
|
|
328
|
+
// Broadcast local cursor
|
|
329
|
+
const unsub = drawingManager.onLocalCursorMove((position) => {
|
|
330
|
+
socket.emit('cursor:move', { userId, position });
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
// Update remote cursor
|
|
334
|
+
drawingManager.updateUserCursor({ userId, userName, color, position });
|
|
335
|
+
|
|
336
|
+
// Remove cursor when user leaves
|
|
337
|
+
drawingManager.removeUserCursor(userId);
|
|
338
|
+
|
|
339
|
+
// Get all remote cursors
|
|
340
|
+
const cursors = drawingManager.getRemoteCursors();
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
## EventBus Events
|
|
344
|
+
|
|
345
|
+
| Event | Payload | Description |
|
|
346
|
+
| -------------------------- | ------------------------ | ---------------------- |
|
|
347
|
+
| `page:changing` | `{ fromIndex, toIndex }` | Before page navigation |
|
|
348
|
+
| `page:changed` | `{ index, viewable }` | After page navigation |
|
|
349
|
+
| `drawing:editModeEntered` | `{}` | Entered markup mode |
|
|
350
|
+
| `drawing:editModeExited` | `{}` | Exited markup mode |
|
|
351
|
+
| `drawing:toolChanged` | `{ tool }` | Tool changed |
|
|
352
|
+
| `drawing:propertiesSynced` | `{ values }` | Properties updated |
|
|
88
353
|
|
|
89
|
-
|
|
354
|
+
```javascript
|
|
355
|
+
import { EventBus, EVENT_NAMES } from '@dtducas/wh-forge-viewer';
|
|
356
|
+
|
|
357
|
+
const eventBus = EventBus.getInstance();
|
|
358
|
+
|
|
359
|
+
eventBus.on(EVENT_NAMES.PAGE_CHANGED, ({ index }) => {
|
|
360
|
+
console.log('Now on page:', index);
|
|
361
|
+
});
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
## Custom Toolbar
|
|
365
|
+
|
|
366
|
+
Built-in toolbar includes:
|
|
367
|
+
|
|
368
|
+
**Tools Group:**
|
|
369
|
+
|
|
370
|
+
- Pan Tool - Navigate the document
|
|
371
|
+
- Document Browser - Toggle thumbnail/tree view
|
|
372
|
+
- Download - Download current document
|
|
373
|
+
|
|
374
|
+
**Pagination Group:**
|
|
375
|
+
|
|
376
|
+
- Previous/Next Page navigation
|
|
377
|
+
- Current page indicator
|
|
378
|
+
|
|
379
|
+
**Smart Features:**
|
|
380
|
+
|
|
381
|
+
- No-refresh navigation when Document Browser is open
|
|
382
|
+
- Self-healing mechanism for toolbar persistence
|
|
383
|
+
- Synchronized button states
|
|
384
|
+
|
|
385
|
+
## Requirements
|
|
386
|
+
|
|
387
|
+
- React ^17.0.0 || ^18.0.0
|
|
388
|
+
- Ant Design ^5.0.0 (peer dependency)
|
|
389
|
+
- Modern browsers (Chrome 90+, Firefox 88+, Safari 14+, Edge 90+)
|
|
90
390
|
|
|
91
391
|
## Development
|
|
92
392
|
|
|
@@ -99,16 +399,17 @@ npm run build
|
|
|
99
399
|
|
|
100
400
|
# Watch mode
|
|
101
401
|
npm run dev
|
|
102
|
-
```
|
|
103
402
|
|
|
104
|
-
|
|
403
|
+
# Test app
|
|
404
|
+
cd test/app && npm install && npm run dev
|
|
405
|
+
```
|
|
105
406
|
|
|
106
|
-
|
|
407
|
+
## Documentation
|
|
107
408
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
409
|
+
- [API Reference](docs/API.md) - Complete API documentation
|
|
410
|
+
- [Architecture](ARCHITECTURE.md) - Detailed architecture documentation
|
|
411
|
+
- [Versioning Guide](docs/VERSIONING.md) - Forge Viewer version management
|
|
412
|
+
- [Collaborative API](docs/COLLABORATIVE-API.md) - Real-time collaboration guide
|
|
112
413
|
|
|
113
414
|
## License
|
|
114
415
|
|
|
@@ -118,10 +419,15 @@ MIT
|
|
|
118
419
|
|
|
119
420
|
**Duong Tran Quang**
|
|
120
421
|
|
|
121
|
-
- Email:
|
|
422
|
+
- Email: baymax.contact@gmail.com
|
|
122
423
|
- GitHub: [@DTDucas](https://github.com/DTDucas)
|
|
123
424
|
- LinkedIn: [dtducas](https://www.linkedin.com/in/dtducas)
|
|
124
425
|
|
|
125
|
-
##
|
|
426
|
+
## Support
|
|
427
|
+
|
|
428
|
+
- [Report bugs](https://github.com/DTDucas/wh-forge-viewer/issues)
|
|
429
|
+
- [Request features](https://github.com/DTDucas/wh-forge-viewer/issues)
|
|
430
|
+
|
|
431
|
+
---
|
|
126
432
|
|
|
127
|
-
|
|
433
|
+
Made with care by [DTDucas](https://github.com/DTDucas)
|