@dtducas/wh-forge-viewer 2.0.0-beta.2 → 3.0.0-beta.1

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 CHANGED
@@ -1,38 +1,16 @@
1
1
  # wh-forge-viewer
2
2
 
3
- A professional Autodesk Forge Viewer component for React with advanced PDF support, comprehensive markup tools, and real-time collaboration capabilities.
3
+ React wrapper for Autodesk Forge Viewer v7 with an Edit2D-first markup system, custom viewer toolbar, multi-page document navigation, and utility APIs for export, scene building, and property database queries.
4
4
 
5
- ## Features
5
+ ## Highlights
6
6
 
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
7
+ - Edit2D-first markup architecture (`MarkupManager` + `Edit2DMarkupEngine`)
8
+ - Custom Forge toolbar (Pan, Document Browser, Download/Export combo, Pagination)
9
+ - Direct local/document loading for `pdf`, `dwf`, `dwfx`, `gltf`, `glb`
10
+ - Excalidraw-inspired property panel and right-side markup toolbar
11
+ - Event-driven communication via `EventBus`
12
+ - Optional custom geometry APIs (`OverlayManager`, `SceneBuilderService`)
13
+ - Optional property worker query API (`PropertyDatabaseService`)
36
14
 
37
15
  ## Installation
38
16
 
@@ -40,394 +18,225 @@ A professional Autodesk Forge Viewer component for React with advanced PDF suppo
40
18
  npm install @dtducas/wh-forge-viewer
41
19
  ```
42
20
 
21
+ Peer dependencies:
22
+
23
+ - `react` `^17 || ^18`
24
+ - `antd` `^5`
25
+
43
26
  ## Quick Start
44
27
 
45
28
  ### Basic Viewer
46
29
 
47
30
  ```jsx
31
+ import { useState } from 'react';
48
32
  import { ViewerForgePDF } from '@dtducas/wh-forge-viewer';
49
33
 
50
- function App() {
34
+ export default function App() {
35
+ const [viewer, setViewer] = useState(null);
36
+
51
37
  return (
52
- <ViewerForgePDF
53
- filePath='https://example.com/document.pdf'
54
- fileExt='pdf'
55
- onViewerReady={(viewer) => console.log('Ready:', viewer)}
56
- />
38
+ <div style={{ width: '100%', height: '100vh' }}>
39
+ <ViewerForgePDF
40
+ filePath='https://example.com/document.pdf'
41
+ fileExt='pdf'
42
+ setViewer={setViewer}
43
+ />
44
+ </div>
57
45
  );
58
46
  }
59
47
  ```
60
48
 
61
- ### With Markup Tools
49
+ ### Enable Edit2D Markup
62
50
 
63
51
  ```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({});
78
-
79
- const eventBus = EventBus.getInstance();
80
-
81
- useEffect(() => {
82
- if (!viewer) return;
83
-
84
- const manager = new DrawingManager(viewer, eventBus, {
85
- autoEnterEditMode: true,
86
- defaultTool: 'arrow',
87
- });
88
-
89
- setDrawingManager(manager);
90
-
91
- return () => manager.destroy();
92
- }, [viewer]);
52
+ import { useState } from 'react';
53
+ import { ViewerForgePDF } from '@dtducas/wh-forge-viewer';
93
54
 
94
- const handleToolChange = (tool) => {
95
- drawingManager?.setActiveTool(tool);
96
- setActiveTool(tool);
97
- setPropertyConfig(drawingManager?.getCurrentToolPropertyConfig());
98
- setPropertyValues(drawingManager?.getCurrentToolPropertyValues() || {});
99
- };
55
+ export default function MarkupApp() {
56
+ const [viewer, setViewer] = useState(null);
100
57
 
101
58
  return (
102
- <div style={{ position: 'relative', width: '100%', height: '100vh' }}>
59
+ <div style={{ width: '100%', height: '100vh' }}>
103
60
  <ViewerForgePDF
104
- filePath='/document.pdf'
61
+ filePath='/sample.pdf'
105
62
  fileExt='pdf'
106
- onViewerReady={setViewer}
63
+ enableEdit2D
64
+ edit2dConfig={{ enableArcs: true }}
65
+ setViewer={setViewer}
107
66
  />
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
67
  </div>
129
68
  );
130
69
  }
131
70
  ```
132
71
 
133
- ### Real-Time Collaboration
72
+ When markup is enabled, `ViewerForgePDF` initializes `MarkupManager`, enters edit mode, and shows the built-in `MarkupToolbar` + `PropertyPanel`.
134
73
 
135
- ```jsx
136
- import {
137
- DrawingManager,
138
- EventBus,
139
- CursorOverlay,
140
- } from '@dtducas/wh-forge-viewer';
74
+ ### Save/Load Markup Per Page
141
75
 
142
- function CollaborativeViewer({ socket }) {
143
- const [drawingManager, setDrawingManager] = useState(null);
144
- const [remoteCursors, setRemoteCursors] = useState(new Map());
76
+ ```jsx
77
+ import { useEffect } from 'react';
78
+ import { EventBus, EVENT_NAMES } from '@dtducas/wh-forge-viewer';
145
79
 
80
+ function useMarkupPersistence(viewer) {
146
81
  useEffect(() => {
147
- if (!drawingManager) return;
82
+ if (!viewer?.getMarkupData || !viewer?.loadMarkupData) return;
148
83
 
149
- // Subscribe to local markup changes
150
- const unsubscribe = drawingManager.onMarkupChange((delta) => {
151
- socket.emit('markup:delta', delta);
152
- });
84
+ const eventBus = EventBus.getInstance();
153
85
 
154
- // Subscribe to local cursor movement
155
- const unsubCursor = drawingManager.onLocalCursorMove((position) => {
156
- socket.emit('cursor:move', { userId: currentUser.id, position });
157
- });
86
+ const onPageChanging = async ({ fromViewable }) => {
87
+ const guid = fromViewable?.data?.guid ?? fromViewable?.guid;
88
+ if (!guid) return;
89
+ const data = viewer.getMarkupData(guid);
90
+ await saveToServer(guid, data);
91
+ };
158
92
 
159
- // Handle remote changes
160
- socket.on('markup:delta', (delta) => {
161
- drawingManager.applyDelta(delta);
162
- });
93
+ const onPageChanged = async ({ viewable }) => {
94
+ const guid = viewable?.data?.guid ?? viewable?.guid;
95
+ if (!guid) return;
96
+ const data = await loadFromServer(guid);
97
+ if (data) {
98
+ await viewer.loadMarkupData(data, { mode: 'replace' });
99
+ }
100
+ };
163
101
 
164
- // Handle remote cursors
165
- socket.on('cursor:move', (cursor) => {
166
- drawingManager.updateUserCursor(cursor);
167
- setRemoteCursors(new Map(drawingManager.getRemoteCursors()));
168
- });
102
+ eventBus.on(EVENT_NAMES.PAGE_CHANGING, onPageChanging);
103
+ eventBus.on(EVENT_NAMES.PAGE_CHANGED, onPageChanged);
169
104
 
170
105
  return () => {
171
- unsubscribe();
172
- unsubCursor();
106
+ eventBus.off(EVENT_NAMES.PAGE_CHANGING, onPageChanging);
107
+ eventBus.off(EVENT_NAMES.PAGE_CHANGED, onPageChanged);
173
108
  };
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
109
+ }, [viewer]);
213
110
  }
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
111
  ```
224
112
 
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
251
-
252
- Side panel for editing properties.
113
+ ## `ViewerForgePDF` Props
114
+
115
+ | Prop | Type | Default | Description |
116
+ | --- | --- | --- | --- |
117
+ | `filePath` | `string` | required | URL/path to input document |
118
+ | `fileExt` | `'pdf' \| 'dwf' \| 'dwfx' \| 'gltf' \| 'glb'` | required | File extension |
119
+ | `setViewer` | `(viewer) => void` | `undefined` | Receives enhanced viewer instance after load |
120
+ | `enableCustomMarkups` | `boolean` | `false` | Enables markup mode (compat flag) |
121
+ | `enableEdit2D` | `boolean` | `false` | Enables Edit2D markup mode |
122
+ | `markupEngine` | `unknown` | ignored | Kept only for API compatibility |
123
+ | `markupConfig` | `unknown` | ignored | Kept only for API compatibility |
124
+ | `edit2dConfig` | `object` | `{ enableArcs: true }` | Forwarded to `MarkupManager` |
125
+ | `toolbarOptions` | `IToolbarOptions` | all visible | Show/hide custom toolbar controls |
126
+ | `enableSceneBuilder` | `boolean` | `false` | Initializes `SceneBuilderService` |
127
+ | `loadFilter` | `ILoadFilter` | `undefined` | For SVF2 selective loading workflows; Local env currently warns/ignores |
128
+ | `viewerPreset` | `IViewerPresetSettings` | `undefined` | Applies Autodesk profile settings |
129
+
130
+ `toolbarOptions` keys:
131
+
132
+ - `showToolbar`
133
+ - `showPan`
134
+ - `showDocBrowser`
135
+ - `showDownload`
136
+ - `showPagination`
137
+
138
+ For `gltf/glb`, Document Browser and Pagination are auto-disabled.
139
+
140
+ ## Enhanced Viewer API (`setViewer`)
141
+
142
+ `setViewer` receives the Forge viewer instance with extra fields:
143
+
144
+ - `capabilities`
145
+ - `markupEngine` (when markup enabled)
146
+ - `getMarkupData(viewableGUID)`
147
+ - `loadMarkupData(data, options)`
148
+ - `onMarkupChange(callback)`
149
+ - `applyDelta(delta)`
150
+ - `overlay` (`OverlayManager`)
151
+ - `sceneBuilder` (`SceneBuilderService`, when enabled)
152
+ - `propertyDatabase` (`PropertyDatabaseService`)
153
+ - `queryPropertyDatabase(fn)`
154
+ - `presetManager` (`ViewerPresetManager`)
253
155
 
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 |
260
-
261
- ### CursorOverlay
262
-
263
- Overlay for displaying remote cursors.
264
-
265
- | Prop | Type | Description |
266
- | -------------- | ------- | ----------------------------- |
267
- | `cursors` | Map | Remote user cursors |
268
- | `viewerBounds` | DOMRect | Viewer bounds for positioning |
269
-
270
- ## DrawingManager API
271
-
272
- ### Edit Mode
273
-
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);
156
+ ## EventBus Events
338
157
 
339
- // Get all remote cursors
340
- const cursors = drawingManager.getRemoteCursors();
341
- ```
158
+ Core custom events exposed in `EVENT_NAMES`:
342
159
 
343
- ## EventBus Events
160
+ - `PAGE_CHANGING`
161
+ - `PAGE_CHANGED`
162
+ - `VIEWABLES_SET`
163
+ - `DOC_BROWSER_OPENED`
164
+ - `DOC_BROWSER_CLOSED`
165
+ - `DRAWING_TOOL_CHANGED`
166
+ - `DRAWING_PROPERTIES_SYNCED`
167
+ - `MARKUP_CHANGED`
168
+ - `MARKUP_DOCUMENT_LOADED`
344
169
 
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 |
170
+ Example:
353
171
 
354
- ```javascript
172
+ ```ts
355
173
  import { EventBus, EVENT_NAMES } from '@dtducas/wh-forge-viewer';
356
174
 
357
- const eventBus = EventBus.getInstance();
358
-
359
- eventBus.on(EVENT_NAMES.PAGE_CHANGED, ({ index }) => {
360
- console.log('Now on page:', index);
175
+ const bus = EventBus.getInstance();
176
+ bus.on(EVENT_NAMES.PAGE_CHANGED, ({ index }) => {
177
+ console.log('Current page index:', index);
361
178
  });
362
179
  ```
363
180
 
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
181
+ ## Public Exports (Runtime)
182
+
183
+ Main entry exports from `src/index.js` include:
184
+
185
+ - Components: `ViewerForgePDF`, `MarkupToolbar`, `PropertyPanel`, `CursorOverlay`
186
+ - Markup engine/core: `Edit2DMarkupEngine`, `MarkupManager`, `HistoryManager`, `LayerManager`, `SelectionManager`, `SerializationManager`, `PropertyBridge`, `ContextMenuManager`
187
+ - Edit2D adapters: `Edit2DBootstrap`, `ShapeFactory`
188
+ - Services: `EventBus`, `MarkupExportService`, `SvgToPdfConverter`, `ImageStorageService`, `Edit2DSerializationService`, `Edit2DImportExportService`, `Edit2DStyleService`, `Edit2DLabelService`, `SceneBuilderService`, `PropertyDatabaseService`
189
+ - Managers: `OverlayManager`, `ViewerPresetManager`
190
+ - Constants and types: drawing presets, markup model types, toolbar visibility types, selective loading types, edit2d types
191
+
192
+ ## Current Source Structure
193
+
194
+ ```text
195
+ src/
196
+ ├── components/
197
+ ├── constants/
198
+ ├── engines/
199
+ ├── extensions/
200
+ ├── hooks/
201
+ ├── managers/
202
+ ├── markup/
203
+ │ ├── core/
204
+ │ ├── edit2d/
205
+ │ ├── model/
206
+ │ └── tools/
207
+ ├── services/
208
+ ├── styles/
209
+ ├── types/
210
+ ├── utils/
211
+ ├── index.js
212
+ └── index.d.ts
213
+ ```
386
214
 
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+)
215
+ Note: runtime architecture is Edit2D-first. Older docs or examples referencing `DrawingManager`/LMV MarkupsCore are legacy and no longer reflect the current `src` implementation.
390
216
 
391
217
  ## Development
392
218
 
393
219
  ```bash
394
- # Install dependencies
395
220
  npm install
396
-
397
- # Build package
398
221
  npm run build
399
-
400
- # Watch mode
401
222
  npm run dev
223
+ ```
402
224
 
403
- # Test app
404
- cd test/app && npm install && npm run dev
225
+ Run local test app:
226
+
227
+ ```bash
228
+ cd test/app
229
+ npm install
230
+ npm run dev
405
231
  ```
406
232
 
407
233
  ## Documentation
408
234
 
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
235
+ - [API Reference](API.md)
236
+ - [Architecture](ARCHITECTURE.md)
237
+ - [Collaborative API](docs/COLLABORATIVE-API.md)
238
+ - [Versioning Guide](docs/VERSIONING.md)
413
239
 
414
240
  ## License
415
241
 
416
242
  MIT
417
-
418
- ## Author
419
-
420
- **Duong Tran Quang**
421
-
422
- - Email: baymax.contact@gmail.com
423
- - GitHub: [@DTDucas](https://github.com/DTDucas)
424
- - LinkedIn: [dtducas](https://www.linkedin.com/in/dtducas)
425
-
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
- ---
432
-
433
- Made with care by [DTDucas](https://github.com/DTDucas)