@dtducas/wh-forge-viewer 1.0.4 → 1.0.6

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/dist/index.esm.js CHANGED
@@ -184,333 +184,1625 @@ function _unsupportedIterableToArray(r, a) {
184
184
  }
185
185
  }
186
186
 
187
+ /**
188
+ * Document Browser Configuration Constants
189
+ *
190
+ * @file document-browser.constants.ts
191
+ * @description Constants for Document Browser panel configuration and styling
192
+ */
193
+ const DOCUMENT_BROWSER_STYLES = {
194
+ top: '0',
195
+ left: 'unset',
196
+ right: '0px',
197
+ };
198
+
199
+ /**
200
+ * Event Names Constants
201
+ *
202
+ * @file events.constants.ts
203
+ * @description Constants for Autodesk Forge events and custom application events
204
+ */
205
+ const EVENT_NAMES = {
206
+ // Autodesk Forge Events
207
+ GEOMETRY_LOADED: 'Autodesk.Viewing.GEOMETRY_LOADED_EVENT',
208
+ MODEL_ADDED: 'Autodesk.Viewing.MODEL_ADDED_EVENT',
209
+ TOOLBAR_CREATED: 'Autodesk.Viewing.TOOLBAR_CREATED_EVENT',
210
+ // Custom Application Events
211
+ PAGE_CHANGED: 'page:changed',
212
+ VIEWABLES_SET: 'viewables:set',
213
+ DOC_BROWSER_OPENED: 'docBrowser:opened',
214
+ DOC_BROWSER_CLOSED: 'docBrowser:closed',
215
+ TOOLBAR_BUTTON_CLICKED: 'toolbar:button:clicked',
216
+ PAN_ACTIVATED: 'pan:activated',
217
+ DOWNLOAD_REQUESTED: 'download:requested',
218
+ };
219
+
220
+ /**
221
+ * Toolbar Configuration Constants
222
+ *
223
+ * @file toolbar.constants.ts
224
+ * @description Constants for custom toolbar buttons, groups, and refresh intervals
225
+ */
226
+ const DEFAULT_HIDDEN_TOOLBAR_GROUPS = [
227
+ 'settingsTools',
228
+ 'modelTools',
229
+ 'navTools',
230
+ ];
231
+ const CUSTOM_TOOLBAR_BUTTONS = {
232
+ PAN: {
233
+ id: 'custom-pan-btn',
234
+ icon: 'adsk-icon-pan',
235
+ tooltip: 'Pan',
236
+ },
237
+ DOC_BROWSER: {
238
+ id: 'custom-doc-browser-btn',
239
+ icon: 'adsk-icon-documentModels',
240
+ tooltip: 'Document Browser',
241
+ },
242
+ DOWNLOAD: {
243
+ id: 'custom-download-btn',
244
+ icon: 'adsk-icon-custom-download',
245
+ tooltip: 'Download File',
246
+ },
247
+ };
248
+ const PAGINATION_BUTTONS = {
249
+ PREV: {
250
+ id: 'prev-page-btn',
251
+ icon: 'adsk-icon-custom-prev',
252
+ tooltip: 'Previous Page',
253
+ },
254
+ NEXT: {
255
+ id: 'next-page-btn',
256
+ icon: 'adsk-icon-custom-next',
257
+ tooltip: 'Next Page',
258
+ },
259
+ LABEL: {
260
+ id: 'total-page-label',
261
+ tooltip: 'Page info',
262
+ },
263
+ };
264
+ const TOOLBAR_REFRESH_INTERVALS = {
265
+ HEALING_POLL: 8,
266
+ BUTTON_STATE_CHECK: 10,
267
+ DOM_SETTLE: 50,
268
+ GEOMETRY_SETTLE: 80,
269
+ EXTENSION_INIT: 150,
270
+ };
271
+ const TOOLBAR_CONTROL_GROUP_IDS = {
272
+ TOOLS: 'custom-tool-group',
273
+ PAGINATION: 'custom-pagination-group',
274
+ };
275
+
276
+ /**
277
+ * Autodesk Forge Viewer Configuration Constants
278
+ *
279
+ * @file viewer.constants.ts
280
+ * @description Constants for Forge Viewer initialization, CDN URLs, and file type mappings
281
+ */
282
+ const FORGE_VIEWER_VERSION = '7.*';
283
+ const FORGE_VIEWER_CDN = 'https://developer.api.autodesk.com/modelderivative/v2/viewers';
284
+ const FORGE_STYLE_URL = `${FORGE_VIEWER_CDN}/${FORGE_VIEWER_VERSION}/style.min.css`;
285
+ const FORGE_SCRIPT_URL = `${FORGE_VIEWER_CDN}/${FORGE_VIEWER_VERSION}/viewer3D.min.js`;
286
+ const SUPPORTED_FILE_EXTENSIONS = ['pdf', 'dwf', 'dwfx'];
287
+ const FILE_EXTENSION_TO_VIEWER_EXTENSION = {
288
+ pdf: 'Autodesk.PDF',
289
+ dwf: 'Autodesk.DWF',
290
+ dwfx: 'Autodesk.DWF',
291
+ };
292
+ const GEOMETRY_SEARCH_CRITERIA = {
293
+ VIEW_3D: { type: 'geometry', role: '3d', progress: 'complete' },
294
+ VIEW_2D: { type: 'geometry', role: '2d', progress: 'complete' },
295
+ };
296
+
297
+ /******************************************************************************
298
+ Copyright (c) Microsoft Corporation.
299
+
300
+ Permission to use, copy, modify, and/or distribute this software for any
301
+ purpose with or without fee is hereby granted.
302
+
303
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
304
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
305
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
306
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
307
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
308
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
309
+ PERFORMANCE OF THIS SOFTWARE.
310
+ ***************************************************************************** */
311
+ /* global Reflect, Promise, SuppressedError, Symbol, Iterator */
312
+
313
+
314
+ function __awaiter(thisArg, _arguments, P, generator) {
315
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
316
+ return new (P || (P = Promise))(function (resolve, reject) {
317
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
318
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
319
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
320
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
321
+ });
322
+ }
323
+
324
+ typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
325
+ var e = new Error(message);
326
+ return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
327
+ };
328
+
329
+ /**
330
+ * DOM Utility Functions
331
+ *
332
+ * @file dom.utils.ts
333
+ * @description Utilities for safe DOM manipulation and querying
334
+ */
335
+ /**
336
+ * Find element by text content
337
+ */
338
+ function findElementByText(selector, text) {
339
+ var _a;
340
+ const elements = Array.from(document.querySelectorAll(selector));
341
+ return (_a = elements.find((el) => { var _a; return (_a = el.textContent) === null || _a === void 0 ? void 0 : _a.includes(text); })) !== null && _a !== void 0 ? _a : null;
342
+ }
343
+ /**
344
+ * Find thumbnail tab element using multiple selectors
345
+ */
346
+ function findThumbnailTab() {
347
+ return (document.querySelector('.docking-panel-thumbnail-view') ||
348
+ document.querySelector('[data-i18n="Thumbnails"]') ||
349
+ findElementByText('.adsk-control-group .adsk-button', 'Thumbnails'));
350
+ }
351
+ /**
352
+ * Safely click an element
353
+ */
354
+ function clickElement(element) {
355
+ if (element && 'click' in element) {
356
+ element.click();
357
+ }
358
+ }
359
+ /**
360
+ * Apply multiple styles to an element
361
+ */
362
+ function applyStyles(element, styles) {
363
+ if (element) {
364
+ Object.assign(element.style, styles);
365
+ }
366
+ }
367
+
368
+ /**
369
+ * File Utility Functions
370
+ *
371
+ * @file file.utils.ts
372
+ * @description Utilities for file handling and path operations
373
+ */
374
+ /**
375
+ * Extract filename from URL or path
376
+ */
377
+ function extractFilenameFromPath(path) {
378
+ try {
379
+ const urlPath = new URL(path).pathname;
380
+ const filename = urlPath.split('/').pop();
381
+ return decodeURIComponent(filename || 'document.pdf');
382
+ }
383
+ catch (_a) {
384
+ const parts = path.split('/');
385
+ return parts[parts.length - 1] || 'document.pdf';
386
+ }
387
+ }
388
+
389
+ class DownloadService {
390
+ /**
391
+ * Download a file from URL
392
+ *
393
+ * @param fileUrl - URL of file to download
394
+ *
395
+ * @example
396
+ * const downloadService = new DownloadService();
397
+ * await downloadService.downloadFile('https://example.com/document.pdf');
398
+ */
399
+ downloadFile(fileUrl) {
400
+ return __awaiter(this, void 0, void 0, function* () {
401
+ try {
402
+ const response = yield fetch(fileUrl);
403
+ if (!response.ok) {
404
+ throw new Error(`HTTP error! status: ${response.status}`);
405
+ }
406
+ const blob = yield response.blob();
407
+ const filename = extractFilenameFromPath(fileUrl);
408
+ // Create download link
409
+ const blobUrl = URL.createObjectURL(blob);
410
+ const anchor = document.createElement('a');
411
+ anchor.href = blobUrl;
412
+ anchor.download = filename;
413
+ anchor.style.display = 'none';
414
+ // Trigger download
415
+ document.body.appendChild(anchor);
416
+ anchor.click();
417
+ document.body.removeChild(anchor);
418
+ // Cleanup
419
+ URL.revokeObjectURL(blobUrl);
420
+ }
421
+ catch (error) {
422
+ throw error;
423
+ }
424
+ });
425
+ }
426
+ }
427
+
428
+ class EventBus {
429
+ constructor() {
430
+ this.listeners = new Map();
431
+ // Private constructor for singleton pattern
432
+ }
433
+ /**
434
+ * Get singleton instance of EventBus
435
+ *
436
+ * @example
437
+ * const eventBus = EventBus.getInstance();
438
+ * eventBus.on('myEvent', (data) => console.log(data));
439
+ */
440
+ static getInstance() {
441
+ if (!EventBus.instance) {
442
+ EventBus.instance = new EventBus();
443
+ }
444
+ return EventBus.instance;
445
+ }
446
+ /**
447
+ * Subscribe to an event
448
+ *
449
+ * @param eventName - Event name to listen for
450
+ * @param listener - Callback function to execute when event is emitted
451
+ *
452
+ * @example
453
+ * eventBus.on('page:changed', (data) => {
454
+ * console.log('Page changed to:', data.index);
455
+ * });
456
+ */
457
+ on(eventName, listener) {
458
+ if (!this.listeners.has(eventName)) {
459
+ this.listeners.set(eventName, new Set());
460
+ }
461
+ this.listeners.get(eventName).add(listener);
462
+ }
463
+ /**
464
+ * Unsubscribe from an event
465
+ *
466
+ * @param eventName - Event name to stop listening for
467
+ * @param listener - Callback function to remove
468
+ *
469
+ * @example
470
+ * const handler = (data) => console.log(data);
471
+ * eventBus.on('myEvent', handler);
472
+ * eventBus.off('myEvent', handler);
473
+ */
474
+ off(eventName, listener) {
475
+ const listeners = this.listeners.get(eventName);
476
+ if (listeners) {
477
+ listeners.delete(listener);
478
+ if (listeners.size === 0) {
479
+ this.listeners.delete(eventName);
480
+ }
481
+ }
482
+ }
483
+ /**
484
+ * Emit an event to all subscribers
485
+ *
486
+ * @param eventName - Event name to emit
487
+ * @param data - Data to pass to subscribers
488
+ *
489
+ * @example
490
+ * eventBus.emit('page:changed', { index: 5, viewable: {...} });
491
+ */
492
+ emit(eventName, data) {
493
+ const listeners = this.listeners.get(eventName);
494
+ if (listeners) {
495
+ listeners.forEach((listener) => {
496
+ try {
497
+ listener(data);
498
+ }
499
+ catch (error) {
500
+ // Silent error handling
501
+ }
502
+ });
503
+ }
504
+ }
505
+ /**
506
+ * Clear all listeners for an event (or all events if no eventName provided)
507
+ *
508
+ * @param eventName - Optional event name to clear. If not provided, clears all events
509
+ */
510
+ clear(eventName) {
511
+ if (eventName) {
512
+ this.listeners.delete(eventName);
513
+ }
514
+ else {
515
+ this.listeners.clear();
516
+ }
517
+ }
518
+ /**
519
+ * Get number of listeners for an event
520
+ *
521
+ * @param eventName - Event name to count listeners for
522
+ * @returns Number of listeners registered for the event
523
+ */
524
+ listenerCount(eventName) {
525
+ var _a, _b;
526
+ return (_b = (_a = this.listeners.get(eventName)) === null || _a === void 0 ? void 0 : _a.size) !== null && _b !== void 0 ? _b : 0;
527
+ }
528
+ }
529
+
530
+ class FileLoader {
531
+ constructor(viewer) {
532
+ this.viewer = viewer;
533
+ this.eventBus = EventBus.getInstance();
534
+ }
535
+ /**
536
+ * Load a file into the viewer
537
+ *
538
+ * @param filePath - Path or URL to the file
539
+ * @param fileExt - File extension (pdf, dwf, dwfx)
540
+ * @param callbacks - Optional success/error callbacks
541
+ *
542
+ * @example
543
+ * const fileLoader = new FileLoader(viewer);
544
+ * await fileLoader.loadFile('document.pdf', 'pdf', {
545
+ * onSuccess: (e) => console.log('Loaded successfully'),
546
+ * onError: (err) => console.error('Load failed', err)
547
+ * });
548
+ */
549
+ loadFile(filePath, fileExt, callbacks) {
550
+ return __awaiter(this, void 0, void 0, function* () {
551
+ const viewerExtension = FILE_EXTENSION_TO_VIEWER_EXTENSION[fileExt];
552
+ // Load appropriate viewer extension
553
+ yield this.viewer.loadExtension(viewerExtension);
554
+ // Determine loading method based on file type
555
+ if (fileExt === 'pdf') {
556
+ yield this.loadPDFFile(filePath, callbacks);
557
+ }
558
+ else {
559
+ yield this.loadDWFFile(filePath, callbacks);
560
+ }
561
+ });
562
+ }
563
+ /**
564
+ * Load PDF file directly
565
+ */
566
+ loadPDFFile(filePath, callbacks) {
567
+ return __awaiter(this, void 0, void 0, function* () {
568
+ const handleLoadSuccess = this.createLoadSuccessHandler(callbacks === null || callbacks === void 0 ? void 0 : callbacks.onSuccess);
569
+ this.viewer.loadModel(filePath, {}, handleLoadSuccess);
570
+ });
571
+ }
572
+ /**
573
+ * Load DWF/DWFX file via blob
574
+ * Extracted from ViewerForgePDF.jsx lines 125-136
575
+ */
576
+ loadDWFFile(filePath, callbacks) {
577
+ return __awaiter(this, void 0, void 0, function* () {
578
+ return new Promise((resolve, reject) => {
579
+ const xhr = new XMLHttpRequest();
580
+ xhr.open('GET', filePath, true);
581
+ xhr.responseType = 'blob';
582
+ xhr.onload = () => {
583
+ var _a;
584
+ if (xhr.status === 200) {
585
+ const blob = xhr.response;
586
+ const blobUrl = window.URL.createObjectURL(blob);
587
+ const handleLoadSuccess = this.createLoadSuccessHandler(callbacks === null || callbacks === void 0 ? void 0 : callbacks.onSuccess);
588
+ this.viewer.loadModel(blobUrl + '#.dwf', {}, handleLoadSuccess);
589
+ resolve();
590
+ }
591
+ else {
592
+ const error = new Error(`Failed to load DWF file: HTTP ${xhr.status}`);
593
+ (_a = callbacks === null || callbacks === void 0 ? void 0 : callbacks.onError) === null || _a === void 0 ? void 0 : _a.call(callbacks, error);
594
+ reject(error);
595
+ }
596
+ };
597
+ xhr.onerror = () => {
598
+ var _a;
599
+ const error = new Error('Network error loading DWF file');
600
+ (_a = callbacks === null || callbacks === void 0 ? void 0 : callbacks.onError) === null || _a === void 0 ? void 0 : _a.call(callbacks, error);
601
+ reject(error);
602
+ };
603
+ xhr.send();
604
+ });
605
+ });
606
+ }
607
+ /**
608
+ * Create load success handler that extracts viewables
609
+ * Extracted from ViewerForgePDF.jsx lines 44-56
610
+ */
611
+ createLoadSuccessHandler(userCallback) {
612
+ return (event) => {
613
+ try {
614
+ // Enable reverse zoom
615
+ this.viewer.setReverseZoomDirection(true);
616
+ // Extract viewables
617
+ const viewables = this.extractViewables(event);
618
+ // Emit event with viewables
619
+ this.eventBus.emit(EVENT_NAMES.VIEWABLES_SET, { viewables });
620
+ // Show toolbar
621
+ if (this.viewer.toolbar) {
622
+ this.viewer.toolbar.setVisible(true);
623
+ }
624
+ // Call user callback if provided
625
+ userCallback === null || userCallback === void 0 ? void 0 : userCallback(event);
626
+ }
627
+ catch (error) {
628
+ // Silent error handling
629
+ }
630
+ };
631
+ }
632
+ /**
633
+ * Extract viewables from document
634
+ * Extracted from ViewerForgePDF.jsx lines 47-56
635
+ */
636
+ extractViewables(event) {
637
+ const root = event.getDocumentNode().getRootNode();
638
+ const view3d = root.search(GEOMETRY_SEARCH_CRITERIA.VIEW_3D, true);
639
+ const view2d = root.search(GEOMETRY_SEARCH_CRITERIA.VIEW_2D, true);
640
+ return view3d.concat(view2d);
641
+ }
642
+ }
643
+
644
+ class DocumentBrowserManager {
645
+ constructor(viewer, config, eventBus) {
646
+ this.shouldRemainOpen = false;
647
+ this.viewer = viewer;
648
+ this.config = config || {
649
+ autoOpen: true,
650
+ persistState: true,
651
+ defaultTab: 'thumbnails',
652
+ };
653
+ // Use provided EventBus instance or fallback to singleton
654
+ this.eventBus = eventBus || EventBus.getInstance();
655
+ }
656
+ autoOpenIfMultiPage(isMultiPage) {
657
+ return __awaiter(this, void 0, void 0, function* () {
658
+ if (!isMultiPage || !this.config.autoOpen) {
659
+ return;
660
+ }
661
+ setTimeout(() => {
662
+ const originalDocBtn = document.getElementById('toolbar-documentModels');
663
+ if (originalDocBtn && !originalDocBtn.classList.contains('active')) {
664
+ originalDocBtn.click();
665
+ this.shouldRemainOpen = true;
666
+ setTimeout(() => {
667
+ this.applyCustomStyling();
668
+ this.switchToThumbnailTab();
669
+ }, TOOLBAR_REFRESH_INTERVALS.EXTENSION_INIT);
670
+ this.eventBus.emit(EVENT_NAMES.DOC_BROWSER_OPENED);
671
+ }
672
+ else if (originalDocBtn &&
673
+ originalDocBtn.classList.contains('active')) {
674
+ this.shouldRemainOpen = true;
675
+ }
676
+ }, TOOLBAR_REFRESH_INTERVALS.GEOMETRY_SETTLE);
677
+ });
678
+ }
679
+ openPanel() {
680
+ return __awaiter(this, void 0, void 0, function* () {
681
+ var _a, _b, _c;
682
+ let ext = this.viewer.getExtension('Autodesk.DocumentBrowser');
683
+ if (!ext) {
684
+ try {
685
+ yield this.viewer.loadExtension('Autodesk.DocumentBrowser');
686
+ ext = this.viewer.getExtension('Autodesk.DocumentBrowser');
687
+ }
688
+ catch (e) {
689
+ return;
690
+ }
691
+ }
692
+ // Check if already open
693
+ if ((_b = (_a = ext === null || ext === void 0 ? void 0 : ext.ui) === null || _a === void 0 ? void 0 : _a.panel) === null || _b === void 0 ? void 0 : _b.isVisible()) {
694
+ this.shouldRemainOpen = true;
695
+ this.applyCustomStyling();
696
+ this.switchToThumbnailTab();
697
+ return;
698
+ }
699
+ // Try to use original button first for proper initialization
700
+ const originalDocBtn = document.getElementById('toolbar-documentModels');
701
+ if (originalDocBtn) {
702
+ originalDocBtn.click();
703
+ this.shouldRemainOpen = true;
704
+ setTimeout(() => {
705
+ this.applyCustomStyling();
706
+ this.switchToThumbnailTab();
707
+ this.eventBus.emit(EVENT_NAMES.DOC_BROWSER_OPENED);
708
+ }, TOOLBAR_REFRESH_INTERVALS.EXTENSION_INIT);
709
+ }
710
+ else {
711
+ if ((_c = ext === null || ext === void 0 ? void 0 : ext.ui) === null || _c === void 0 ? void 0 : _c.panel) {
712
+ ext.ui.panel.setVisible(true);
713
+ this.shouldRemainOpen = true;
714
+ setTimeout(() => {
715
+ this.applyCustomStyling();
716
+ this.switchToThumbnailTab();
717
+ this.eventBus.emit(EVENT_NAMES.DOC_BROWSER_OPENED);
718
+ }, TOOLBAR_REFRESH_INTERVALS.EXTENSION_INIT);
719
+ }
720
+ }
721
+ });
722
+ }
723
+ closePanel() {
724
+ return __awaiter(this, void 0, void 0, function* () {
725
+ var _a, _b, _c;
726
+ const ext = this.viewer.getExtension('Autodesk.DocumentBrowser');
727
+ if (!((_b = (_a = ext === null || ext === void 0 ? void 0 : ext.ui) === null || _a === void 0 ? void 0 : _a.panel) === null || _b === void 0 ? void 0 : _b.isVisible())) {
728
+ this.shouldRemainOpen = false;
729
+ return;
730
+ }
731
+ const originalDocBtn = document.getElementById('toolbar-documentModels');
732
+ if (originalDocBtn && originalDocBtn.classList.contains('active')) {
733
+ originalDocBtn.click();
734
+ this.shouldRemainOpen = false;
735
+ this.eventBus.emit(EVENT_NAMES.DOC_BROWSER_CLOSED);
736
+ }
737
+ else {
738
+ if ((_c = ext === null || ext === void 0 ? void 0 : ext.ui) === null || _c === void 0 ? void 0 : _c.panel) {
739
+ ext.ui.panel.setVisible(false);
740
+ this.shouldRemainOpen = false;
741
+ this.eventBus.emit(EVENT_NAMES.DOC_BROWSER_CLOSED);
742
+ }
743
+ }
744
+ });
745
+ }
746
+ togglePanel() {
747
+ return __awaiter(this, void 0, void 0, function* () {
748
+ const isCurrentlyOpen = this.isOpen();
749
+ if (isCurrentlyOpen) {
750
+ yield this.closePanel();
751
+ }
752
+ else {
753
+ yield this.openPanel();
754
+ }
755
+ });
756
+ }
757
+ isOpen() {
758
+ var _a, _b, _c;
759
+ const ext = this.viewer.getExtension('Autodesk.DocumentBrowser');
760
+ return (_c = (_b = (_a = ext === null || ext === void 0 ? void 0 : ext.ui) === null || _a === void 0 ? void 0 : _a.panel) === null || _b === void 0 ? void 0 : _b.isVisible()) !== null && _c !== void 0 ? _c : false;
761
+ }
762
+ applyCustomStyling() {
763
+ setTimeout(() => {
764
+ var _a, _b;
765
+ const ext = this.viewer.getExtension('Autodesk.DocumentBrowser');
766
+ if ((_b = (_a = ext === null || ext === void 0 ? void 0 : ext.ui) === null || _a === void 0 ? void 0 : _a.panel) === null || _b === void 0 ? void 0 : _b.container) {
767
+ applyStyles(ext.ui.panel.container, DOCUMENT_BROWSER_STYLES);
768
+ }
769
+ }, TOOLBAR_REFRESH_INTERVALS.DOM_SETTLE);
770
+ }
771
+ switchToThumbnailTab() {
772
+ return __awaiter(this, void 0, void 0, function* () {
773
+ if (this.config.defaultTab !== 'thumbnails')
774
+ return;
775
+ setTimeout(() => {
776
+ const thumbnailTab = findThumbnailTab();
777
+ if (thumbnailTab && !thumbnailTab.classList.contains('active')) {
778
+ clickElement(thumbnailTab);
779
+ }
780
+ }, TOOLBAR_REFRESH_INTERVALS.DOM_SETTLE);
781
+ });
782
+ }
783
+ restoreState() {
784
+ return __awaiter(this, void 0, void 0, function* () {
785
+ var _a, _b, _c;
786
+ if (!this.config.persistState || !this.shouldRemainOpen) {
787
+ return;
788
+ }
789
+ const ext = this.viewer.getExtension('Autodesk.DocumentBrowser');
790
+ const isCurrentlyOpen = (_c = (_b = (_a = ext === null || ext === void 0 ? void 0 : ext.ui) === null || _a === void 0 ? void 0 : _a.panel) === null || _b === void 0 ? void 0 : _b.isVisible()) !== null && _c !== void 0 ? _c : false;
791
+ if (isCurrentlyOpen) {
792
+ this.applyCustomStyling();
793
+ this.switchToThumbnailTab();
794
+ // Emit event MULTIPLE times to ensure button state syncs
795
+ this.eventBus.emit(EVENT_NAMES.DOC_BROWSER_OPENED);
796
+ setTimeout(() => this.eventBus.emit(EVENT_NAMES.DOC_BROWSER_OPENED), 50);
797
+ setTimeout(() => this.eventBus.emit(EVENT_NAMES.DOC_BROWSER_OPENED), 100);
798
+ setTimeout(() => this.eventBus.emit(EVENT_NAMES.DOC_BROWSER_OPENED), 150);
799
+ }
800
+ else {
801
+ yield this.openPanel();
802
+ }
803
+ });
804
+ }
805
+ setShouldRemainOpen(value) {
806
+ this.shouldRemainOpen = value;
807
+ }
808
+ getShouldRemainOpen() {
809
+ return this.shouldRemainOpen;
810
+ }
811
+ }
812
+
813
+ /**
814
+ * PaginationManager
815
+ *
816
+ * @file PaginationManager.ts
817
+ * @description Manages page navigation for multi-page documents
818
+ * Extracted from ToolbarExtension.js lines 34-148
819
+ */
820
+ class PaginationManager {
821
+ constructor(viewer, Autodesk, config, eventBus) {
822
+ this.currentIndex = 0;
823
+ this.viewables = [];
824
+ this.paginationGroup = null;
825
+ this.docBrowserShouldBeOpen = false;
826
+ this.viewer = viewer;
827
+ this.Autodesk = Autodesk;
828
+ this.config = config || {};
829
+ // Use provided EventBus instance or fallback to singleton
830
+ this.eventBus = eventBus || EventBus.getInstance();
831
+ this.modelAddedHandler = this.updateCurrentIndexFromModel.bind(this);
832
+ // Add MODEL_ADDED_EVENT listener immediately in constructor
833
+ // This ensures it's always listening for page changes
834
+ this.viewer.addEventListener(this.Autodesk.Viewing.MODEL_ADDED_EVENT, this.modelAddedHandler);
835
+ }
836
+ /**
837
+ * Set callback for page changes (alternative to EventBus)
838
+ */
839
+ setPageChangeCallback(callback) {
840
+ this.onPageChangeCallback = callback;
841
+ }
842
+ setViewables(viewables) {
843
+ this.viewables = viewables;
844
+ this.currentIndex = 0;
845
+ if (viewables.length > 1) {
846
+ this.docBrowserShouldBeOpen = true;
847
+ }
848
+ this.updatePaginationState();
849
+ // Remove existing listener to avoid duplicates
850
+ if (this.modelAddedHandler) {
851
+ this.viewer.removeEventListener(this.Autodesk.Viewing.MODEL_ADDED_EVENT, this.modelAddedHandler);
852
+ }
853
+ // Add MODEL_ADDED_EVENT listener
854
+ this.viewer.addEventListener(this.Autodesk.Viewing.MODEL_ADDED_EVENT, this.modelAddedHandler);
855
+ }
856
+ nextPage() {
857
+ if (this.viewables.length > 0) {
858
+ this.currentIndex = (this.currentIndex + 1) % this.viewables.length;
859
+ this.loadCurrentViewable();
860
+ }
861
+ }
862
+ previousPage() {
863
+ if (this.viewables.length > 0) {
864
+ this.currentIndex =
865
+ (this.currentIndex - 1 + this.viewables.length) % this.viewables.length;
866
+ this.loadCurrentViewable();
867
+ }
868
+ }
869
+ goToPage(index) {
870
+ if (index >= 0 && index < this.viewables.length) {
871
+ this.currentIndex = index;
872
+ this.loadCurrentViewable();
873
+ }
874
+ }
875
+ getCurrentPage() {
876
+ return this.currentIndex;
877
+ }
878
+ getTotalPages() {
879
+ return this.viewables.length;
880
+ }
881
+ isMultiPage() {
882
+ return this.viewables.length > 1;
883
+ }
884
+ getViewables() {
885
+ return this.viewables;
886
+ }
887
+ updateCurrentIndexFromModel() {
888
+ var _a, _b, _c, _d, _e, _f, _g;
889
+ if (!this.viewer.model || this.viewables.length === 0) {
890
+ return;
891
+ }
892
+ const currentNode = this.viewer.model.getDocumentNode();
893
+ if (currentNode) {
894
+ const currentGuid = ((_a = currentNode.data) === null || _a === void 0 ? void 0 : _a.guid) || currentNode.guid;
895
+ const index = this.viewables.findIndex((v) => {
896
+ var _a;
897
+ const vGuid = ((_a = v.data) === null || _a === void 0 ? void 0 : _a.guid) || v.guid;
898
+ return vGuid === currentGuid;
899
+ });
900
+ if (index !== -1 && index !== this.currentIndex) {
901
+ // Page changed via Document Browser panel
902
+ this.currentIndex = index;
903
+ // Check if Document Browser is currently open
904
+ const docBrowserExt = this.viewer.getExtension('Autodesk.DocumentBrowser');
905
+ const isCurrentlyOpen = (_d = (_c = (_b = docBrowserExt === null || docBrowserExt === void 0 ? void 0 : docBrowserExt.ui) === null || _b === void 0 ? void 0 : _b.panel) === null || _c === void 0 ? void 0 : _c.isVisible()) !== null && _d !== void 0 ? _d : false;
906
+ if (isCurrentlyOpen) {
907
+ this.docBrowserShouldBeOpen = true;
908
+ }
909
+ // Emit page changed event to trigger button state sync and toolbar refresh
910
+ const pageData = {
911
+ index: this.currentIndex,
912
+ viewable: this.viewables[this.currentIndex],
913
+ shouldRestoreDocBrowser: this.docBrowserShouldBeOpen,
914
+ fromDocBrowser: true,
915
+ };
916
+ // Use callback first (direct communication)
917
+ if (this.onPageChangeCallback) {
918
+ this.onPageChangeCallback(pageData);
919
+ }
920
+ // Also emit via EventBus as fallback
921
+ this.eventBus.emit(EVENT_NAMES.PAGE_CHANGED, pageData);
922
+ // Update pagination state multiple times to ensure it sticks
923
+ const updateTimes = [10, 50, 100, 150, 200, 250, 300, 350, 400, 500];
924
+ updateTimes.forEach((delay) => {
925
+ setTimeout(() => {
926
+ this.updatePaginationState();
927
+ }, delay);
928
+ });
929
+ }
930
+ else if (index !== -1) {
931
+ // Same page - but user might have clicked thumbnail while panel is open
932
+ const docBrowserExt = this.viewer.getExtension('Autodesk.DocumentBrowser');
933
+ const isCurrentlyOpen = (_g = (_f = (_e = docBrowserExt === null || docBrowserExt === void 0 ? void 0 : docBrowserExt.ui) === null || _e === void 0 ? void 0 : _e.panel) === null || _f === void 0 ? void 0 : _f.isVisible()) !== null && _g !== void 0 ? _g : false;
934
+ if (isCurrentlyOpen && !this.docBrowserShouldBeOpen) {
935
+ this.docBrowserShouldBeOpen = true;
936
+ // Emit a "refresh" event to ensure button state is synced
937
+ const pageData = {
938
+ index: this.currentIndex,
939
+ viewable: this.viewables[this.currentIndex],
940
+ shouldRestoreDocBrowser: true,
941
+ fromDocBrowser: true,
942
+ refresh: true,
943
+ };
944
+ if (this.onPageChangeCallback) {
945
+ this.onPageChangeCallback(pageData);
946
+ }
947
+ this.eventBus.emit(EVENT_NAMES.PAGE_CHANGED, pageData);
948
+ }
949
+ // Always update pagination state
950
+ this.updatePaginationState();
951
+ }
952
+ }
953
+ }
954
+ updatePaginationState() {
955
+ // IMPORTANT: First sync currentIndex with actual loaded model
956
+ this.syncCurrentIndexFromModel();
957
+ if (!this.paginationGroup) {
958
+ return;
959
+ }
960
+ if (this.viewables.length <= 1) {
961
+ this.paginationGroup.setVisible(false);
962
+ if (this.paginationGroup.container) {
963
+ this.paginationGroup.container.style.display = 'none';
964
+ }
965
+ return;
966
+ }
967
+ this.paginationGroup.setVisible(true);
968
+ if (this.paginationGroup.container) {
969
+ this.paginationGroup.container.style.display = '';
970
+ }
971
+ const totalBtn = this.paginationGroup.getControl(PAGINATION_BUTTONS.LABEL.id);
972
+ if (totalBtn) {
973
+ const current = this.viewables.length > 0 ? this.currentIndex + 1 : 0;
974
+ const total = this.viewables.length;
975
+ totalBtn.setToolTip(`Page ${current} of ${total}`);
976
+ const domElem = totalBtn.container;
977
+ if (domElem) {
978
+ const newContent = `<div style="display: flex; align-items: center; justify-content: center; height: 100%; padding: 0 10px; color: white; font-size: 14px; white-space: nowrap;">${current} / ${total}</div>`;
979
+ domElem.innerHTML = newContent;
980
+ // Use requestAnimationFrame to ensure DOM update
981
+ requestAnimationFrame(() => {
982
+ domElem.innerHTML = newContent;
983
+ });
984
+ // Also try direct text update as fallback
985
+ setTimeout(() => {
986
+ if (domElem.innerHTML !== newContent) {
987
+ domElem.innerHTML = newContent;
988
+ }
989
+ }, 20);
990
+ }
991
+ }
992
+ else {
993
+ // Try to get the control again in case it was just created
994
+ setTimeout(() => {
995
+ const totalBtnRetry = this.paginationGroup.getControl(PAGINATION_BUTTONS.LABEL.id);
996
+ if (totalBtnRetry) {
997
+ const current = this.viewables.length > 0 ? this.currentIndex + 1 : 0;
998
+ const total = this.viewables.length;
999
+ totalBtnRetry.setToolTip(`Page ${current} of ${total}`);
1000
+ const domElem = totalBtnRetry.container;
1001
+ if (domElem) {
1002
+ domElem.innerHTML = `<div style="display: flex; align-items: center; justify-content: center; height: 100%; padding: 0 10px; color: white; font-size: 14px; white-space: nowrap;">${current} / ${total}</div>`;
1003
+ }
1004
+ }
1005
+ }, 50);
1006
+ }
1007
+ }
1008
+ /**
1009
+ * Sync currentIndex with the actual loaded model (without emitting events)
1010
+ * This is used to ensure pagination display shows correct page number
1011
+ */
1012
+ syncCurrentIndexFromModel() {
1013
+ var _a;
1014
+ if (!this.viewer.model || this.viewables.length === 0) {
1015
+ return;
1016
+ }
1017
+ const currentNode = this.viewer.model.getDocumentNode();
1018
+ if (currentNode) {
1019
+ const currentGuid = ((_a = currentNode.data) === null || _a === void 0 ? void 0 : _a.guid) || currentNode.guid;
1020
+ const index = this.viewables.findIndex((v) => {
1021
+ var _a;
1022
+ const vGuid = ((_a = v.data) === null || _a === void 0 ? void 0 : _a.guid) || v.guid;
1023
+ return vGuid === currentGuid;
1024
+ });
1025
+ if (index !== -1 && index !== this.currentIndex) {
1026
+ this.currentIndex = index;
1027
+ }
1028
+ }
1029
+ }
1030
+ /**
1031
+ * Update thumbnail selection in Document Browser panel
1032
+ * This ensures the correct thumbnail is highlighted after navigation
1033
+ */
1034
+ updateThumbnailSelection(targetViewable) {
1035
+ var _a;
1036
+ try {
1037
+ const targetGuid = ((_a = targetViewable.data) === null || _a === void 0 ? void 0 : _a.guid) || targetViewable.guid;
1038
+ // Find all thumbnails
1039
+ const thumbnails = document.querySelectorAll('.viewer-ext-docbrowser-thumbnail');
1040
+ let selectedThumbnail = null;
1041
+ thumbnails.forEach((thumbnail) => {
1042
+ const bubbleGuid = thumbnail.getAttribute('bubble-guid');
1043
+ if (bubbleGuid === targetGuid) {
1044
+ thumbnail.classList.add('viewer-ext-docbrowser-thumbnail-selected');
1045
+ selectedThumbnail = thumbnail;
1046
+ }
1047
+ else {
1048
+ thumbnail.classList.remove('viewer-ext-docbrowser-thumbnail-selected');
1049
+ }
1050
+ });
1051
+ // CRITICAL: Scroll to selected thumbnail
1052
+ if (selectedThumbnail) {
1053
+ this.scrollToThumbnail(selectedThumbnail);
1054
+ }
1055
+ }
1056
+ catch (error) {
1057
+ // Silent error handling
1058
+ }
1059
+ }
1060
+ /**
1061
+ * Scroll the Document Browser panel to show the selected thumbnail
1062
+ */
1063
+ scrollToThumbnail(thumbnail) {
1064
+ try {
1065
+ // Find the scroll container
1066
+ const scrollContainer = document.querySelector('.docking-panel-scroll');
1067
+ if (!scrollContainer) {
1068
+ return;
1069
+ }
1070
+ // Get thumbnail position relative to container
1071
+ const thumbnailRect = thumbnail.getBoundingClientRect();
1072
+ const containerRect = scrollContainer.getBoundingClientRect();
1073
+ // Calculate scroll position to center the thumbnail in the viewport
1074
+ const thumbnailTop = thumbnailRect.top - containerRect.top + scrollContainer.scrollTop;
1075
+ const containerHeight = scrollContainer.clientHeight;
1076
+ const thumbnailHeight = thumbnailRect.height;
1077
+ const targetScrollTop = thumbnailTop - containerHeight / 2 + thumbnailHeight / 2;
1078
+ // Smooth scroll to target position
1079
+ scrollContainer.scrollTo({
1080
+ top: targetScrollTop,
1081
+ behavior: 'smooth',
1082
+ });
1083
+ }
1084
+ catch (error) {
1085
+ // Silent error handling
1086
+ }
1087
+ }
1088
+ /**
1089
+ * Try to use DocumentBrowser API to change page without refreshing panel
1090
+ * Returns true if successful, false otherwise
1091
+ */
1092
+ tryUseDocBrowserAPI(targetIndex) {
1093
+ var _a, _b, _c;
1094
+ const docBrowserExt = this.viewer.getExtension('Autodesk.DocumentBrowser');
1095
+ const isOpen = (_c = (_b = (_a = docBrowserExt === null || docBrowserExt === void 0 ? void 0 : docBrowserExt.ui) === null || _a === void 0 ? void 0 : _a.panel) === null || _b === void 0 ? void 0 : _b.isVisible()) !== null && _c !== void 0 ? _c : false;
1096
+ if (!isOpen || targetIndex < 0 || targetIndex >= this.viewables.length) {
1097
+ return false;
1098
+ }
1099
+ try {
1100
+ const targetViewable = this.viewables[targetIndex];
1101
+ if (!targetViewable) {
1102
+ return false;
1103
+ }
1104
+ // Try _changeModelFn (internal method used by DocumentBrowser)
1105
+ if (docBrowserExt.ui &&
1106
+ typeof docBrowserExt.ui._changeModelFn === 'function') {
1107
+ docBrowserExt.ui._changeModelFn(targetViewable);
1108
+ // Update thumbnail selection after model change
1109
+ setTimeout(() => {
1110
+ this.updateThumbnailSelection(targetViewable);
1111
+ }, 100);
1112
+ return true;
1113
+ }
1114
+ // Fallback: Try _changeModel
1115
+ if (docBrowserExt.ui &&
1116
+ typeof docBrowserExt.ui._changeModel === 'function') {
1117
+ docBrowserExt.ui._changeModel(targetViewable);
1118
+ return true;
1119
+ }
1120
+ // Fallback: Try setCurrentViewable
1121
+ if (docBrowserExt &&
1122
+ typeof docBrowserExt.setCurrentViewable === 'function') {
1123
+ docBrowserExt.setCurrentViewable(targetViewable);
1124
+ return true;
1125
+ }
1126
+ return false;
1127
+ }
1128
+ catch (error) {
1129
+ return false;
1130
+ }
1131
+ }
1132
+ loadCurrentViewable() {
1133
+ var _a, _b, _c, _d, _e;
1134
+ if (this.viewables.length === 0 || !this.viewables[this.currentIndex]) {
1135
+ return;
1136
+ }
1137
+ const viewer = this.viewer;
1138
+ // Check current Document Browser state before loading new page
1139
+ const docBrowserExt = viewer.getExtension('Autodesk.DocumentBrowser');
1140
+ const isCurrentlyOpen = (_c = (_b = (_a = docBrowserExt === null || docBrowserExt === void 0 ? void 0 : docBrowserExt.ui) === null || _a === void 0 ? void 0 : _a.panel) === null || _b === void 0 ? void 0 : _b.isVisible()) !== null && _c !== void 0 ? _c : false;
1141
+ if (isCurrentlyOpen) {
1142
+ this.docBrowserShouldBeOpen = true;
1143
+ }
1144
+ // OPTIMIZATION: If Document Browser is open, try to use its API to navigate
1145
+ // This prevents the panel from being refreshed (same behavior as manual thumbnail click)
1146
+ if (isCurrentlyOpen) {
1147
+ const apiSuccess = this.tryUseDocBrowserAPI(this.currentIndex);
1148
+ if (apiSuccess) {
1149
+ // Update pagination state after a short delay
1150
+ setTimeout(() => {
1151
+ this.updatePaginationState();
1152
+ }, 100);
1153
+ return;
1154
+ }
1155
+ }
1156
+ // FALLBACK: Use traditional loadDocumentNode if Document Browser is closed
1157
+ const onGeometryLoaded = () => {
1158
+ viewer.removeEventListener(this.Autodesk.Viewing.GEOMETRY_LOADED_EVENT, onGeometryLoaded);
1159
+ setTimeout(() => {
1160
+ const pageData = {
1161
+ index: this.currentIndex,
1162
+ viewable: this.viewables[this.currentIndex],
1163
+ shouldRestoreDocBrowser: this.docBrowserShouldBeOpen,
1164
+ fromPagination: true,
1165
+ };
1166
+ if (this.onPageChangeCallback) {
1167
+ this.onPageChangeCallback(pageData);
1168
+ }
1169
+ this.eventBus.emit(EVENT_NAMES.PAGE_CHANGED, pageData);
1170
+ this.updatePaginationState();
1171
+ }, TOOLBAR_REFRESH_INTERVALS.GEOMETRY_SETTLE);
1172
+ };
1173
+ viewer.addEventListener(this.Autodesk.Viewing.GEOMETRY_LOADED_EVENT, onGeometryLoaded);
1174
+ viewer
1175
+ .loadDocumentNode(viewer.model.getDocumentNode().getDocument(), this.viewables[this.currentIndex])
1176
+ .then(() => {
1177
+ const pageData = {
1178
+ index: this.currentIndex,
1179
+ viewable: this.viewables[this.currentIndex],
1180
+ shouldRestoreDocBrowser: this.docBrowserShouldBeOpen,
1181
+ immediate: true,
1182
+ fromPagination: true,
1183
+ };
1184
+ if (this.onPageChangeCallback) {
1185
+ this.onPageChangeCallback(pageData);
1186
+ }
1187
+ this.eventBus.emit(EVENT_NAMES.PAGE_CHANGED, pageData);
1188
+ this.updatePaginationState();
1189
+ })
1190
+ .catch((err) => {
1191
+ // Silent error handling
1192
+ });
1193
+ (_e = (_d = this.config).onPageChange) === null || _e === void 0 ? void 0 : _e.call(_d, this.currentIndex);
1194
+ }
1195
+ createPaginationGroup(toolbar) {
1196
+ this.paginationGroup = new this.Autodesk.Viewing.UI.ControlGroup(TOOLBAR_CONTROL_GROUP_IDS.PAGINATION);
1197
+ toolbar.addControl(this.paginationGroup);
1198
+ const prevBtn = new this.Autodesk.Viewing.UI.Button(PAGINATION_BUTTONS.PREV.id);
1199
+ prevBtn.setIcon(PAGINATION_BUTTONS.PREV.icon);
1200
+ prevBtn.addClass('custom-prev-btn');
1201
+ prevBtn.setToolTip(PAGINATION_BUTTONS.PREV.tooltip);
1202
+ prevBtn.onClick = () => this.previousPage();
1203
+ this.paginationGroup.addControl(prevBtn);
1204
+ const labelBtn = new this.Autodesk.Viewing.UI.Button(PAGINATION_BUTTONS.LABEL.id);
1205
+ labelBtn.setToolTip(PAGINATION_BUTTONS.LABEL.tooltip);
1206
+ this.paginationGroup.addControl(labelBtn);
1207
+ const nextBtn = new this.Autodesk.Viewing.UI.Button(PAGINATION_BUTTONS.NEXT.id);
1208
+ nextBtn.setIcon(PAGINATION_BUTTONS.NEXT.icon);
1209
+ nextBtn.addClass('custom-next-btn');
1210
+ nextBtn.setToolTip(PAGINATION_BUTTONS.NEXT.tooltip);
1211
+ nextBtn.onClick = () => this.nextPage();
1212
+ this.paginationGroup.addControl(nextBtn);
1213
+ // Immediately update pagination state after creating group
1214
+ setTimeout(() => {
1215
+ this.updatePaginationState();
1216
+ }, 10);
1217
+ }
1218
+ getPaginationGroup() {
1219
+ return this.paginationGroup;
1220
+ }
1221
+ setPaginationGroup(group) {
1222
+ this.paginationGroup = group;
1223
+ }
1224
+ cleanup() {
1225
+ if (this.modelAddedHandler) {
1226
+ this.viewer.removeEventListener(this.Autodesk.Viewing.MODEL_ADDED_EVENT, this.modelAddedHandler);
1227
+ }
1228
+ if (this.viewer.toolbar && this.paginationGroup) {
1229
+ this.viewer.toolbar.removeControl(this.paginationGroup);
1230
+ this.paginationGroup = null;
1231
+ }
1232
+ }
1233
+ }
1234
+
1235
+ /**
1236
+ * ToolbarManager
1237
+ *
1238
+ * @file ToolbarManager.ts
1239
+ * @description Manages custom toolbar creation and button state
1240
+ * Extracted from ToolbarExtension.js lines 256-479
1241
+ */
1242
+ class ToolbarManager {
1243
+ constructor(viewer, Autodesk, config) {
1244
+ this.subToolbar = null;
1245
+ this.viewer = viewer;
1246
+ this.Autodesk = Autodesk;
1247
+ this.config = config;
1248
+ this.eventBus = EventBus.getInstance();
1249
+ }
1250
+ createToolbar() {
1251
+ const toolbar = this.viewer.toolbar;
1252
+ if (!toolbar)
1253
+ return;
1254
+ this.subToolbar = new this.Autodesk.Viewing.UI.ControlGroup(TOOLBAR_CONTROL_GROUP_IDS.TOOLS);
1255
+ const panBtn = this.createPanButton();
1256
+ const docBtn = this.createDocBrowserButton();
1257
+ const dlBtn = this.createDownloadButton();
1258
+ this.subToolbar.addControl(panBtn);
1259
+ this.subToolbar.addControl(docBtn);
1260
+ this.subToolbar.addControl(dlBtn);
1261
+ toolbar.addControl(this.subToolbar);
1262
+ }
1263
+ createPanButton() {
1264
+ const btn = new this.Autodesk.Viewing.UI.Button(CUSTOM_TOOLBAR_BUTTONS.PAN.id);
1265
+ btn.setIcon(CUSTOM_TOOLBAR_BUTTONS.PAN.icon);
1266
+ btn.addClass('custom-pan-btn');
1267
+ btn.setToolTip(CUSTOM_TOOLBAR_BUTTONS.PAN.tooltip);
1268
+ btn.onClick = () => {
1269
+ var _a, _b;
1270
+ (_b = (_a = this.config).onPanClick) === null || _b === void 0 ? void 0 : _b.call(_a);
1271
+ this.activatePanButton();
1272
+ };
1273
+ return btn;
1274
+ }
1275
+ createDocBrowserButton() {
1276
+ const btn = new this.Autodesk.Viewing.UI.Button(CUSTOM_TOOLBAR_BUTTONS.DOC_BROWSER.id);
1277
+ btn.setIcon(CUSTOM_TOOLBAR_BUTTONS.DOC_BROWSER.icon);
1278
+ btn.addClass('custom-doc-browser-btn');
1279
+ btn.setToolTip(CUSTOM_TOOLBAR_BUTTONS.DOC_BROWSER.tooltip);
1280
+ btn.onClick = () => {
1281
+ var _a, _b;
1282
+ (_b = (_a = this.config).onDocBrowserClick) === null || _b === void 0 ? void 0 : _b.call(_a);
1283
+ this.eventBus.emit(EVENT_NAMES.TOOLBAR_BUTTON_CLICKED, {
1284
+ buttonId: CUSTOM_TOOLBAR_BUTTONS.DOC_BROWSER.id,
1285
+ });
1286
+ };
1287
+ return btn;
1288
+ }
1289
+ createDownloadButton() {
1290
+ const btn = new this.Autodesk.Viewing.UI.Button(CUSTOM_TOOLBAR_BUTTONS.DOWNLOAD.id);
1291
+ btn.setIcon(CUSTOM_TOOLBAR_BUTTONS.DOWNLOAD.icon);
1292
+ btn.addClass('custom-download-btn');
1293
+ btn.setToolTip(CUSTOM_TOOLBAR_BUTTONS.DOWNLOAD.tooltip);
1294
+ btn.onClick = () => {
1295
+ var _a, _b;
1296
+ (_b = (_a = this.config).onDownloadClick) === null || _b === void 0 ? void 0 : _b.call(_a);
1297
+ this.eventBus.emit(EVENT_NAMES.DOWNLOAD_REQUESTED, {
1298
+ filePath: this.config.filePath,
1299
+ });
1300
+ };
1301
+ return btn;
1302
+ }
1303
+ updateButtonState(buttonId, isActive) {
1304
+ if (!this.subToolbar)
1305
+ return;
1306
+ const btn = this.subToolbar.getControl(buttonId);
1307
+ if (btn) {
1308
+ const newState = isActive
1309
+ ? this.Autodesk.Viewing.UI.Button.State.ACTIVE
1310
+ : this.Autodesk.Viewing.UI.Button.State.INACTIVE;
1311
+ btn.setState(newState);
1312
+ }
1313
+ }
1314
+ activatePanButton() {
1315
+ try {
1316
+ this.viewer.setActiveNavigationTool('pan');
1317
+ // Note: Removed EVENT_NAMES.PAN_ACTIVATED emit as no listeners were registered
1318
+ // If you need this event, add a listener first
1319
+ }
1320
+ catch (e) {
1321
+ const originalPanBtn = document.getElementById('toolbar-panTool');
1322
+ if (originalPanBtn) {
1323
+ originalPanBtn.click();
1324
+ }
1325
+ }
1326
+ this.updateButtonState(CUSTOM_TOOLBAR_BUTTONS.PAN.id, true);
1327
+ const customPanBtn = document.querySelector('.custom-pan-btn');
1328
+ if (customPanBtn) {
1329
+ customPanBtn.classList.add('active');
1330
+ customPanBtn.classList.remove('inactive');
1331
+ }
1332
+ }
1333
+ hideDefaultGroups() {
1334
+ const toolbar = this.viewer.toolbar;
1335
+ if (!toolbar)
1336
+ return;
1337
+ DEFAULT_HIDDEN_TOOLBAR_GROUPS.forEach((id) => {
1338
+ const group = toolbar.getControl(id);
1339
+ if (group) {
1340
+ group.setVisible(false);
1341
+ }
1342
+ });
1343
+ }
1344
+ getToolbar() {
1345
+ return this.subToolbar;
1346
+ }
1347
+ cleanup() {
1348
+ if (this.viewer.toolbar && this.subToolbar) {
1349
+ this.viewer.toolbar.removeControl(this.subToolbar);
1350
+ this.subToolbar = null;
1351
+ }
1352
+ }
1353
+ }
1354
+
1355
+ // CRITICAL: Global state to persist across extension unload/reload cycles
1356
+ // This is necessary because Forge Viewer unloads extensions when navigating via thumbnails
1357
+ var GLOBAL_STATE = {
1358
+ viewables: null,
1359
+ docBrowserShouldBeOpen: false,
1360
+ currentIndex: 0
1361
+ };
187
1362
  function registerToolbarExtension(Autodesk) {
188
1363
  function ToolbarExtension(viewer, options) {
1364
+ var _this = this;
189
1365
  Autodesk.Viewing.Extension.call(this, viewer, options);
190
1366
  this.viewer = viewer;
191
- this.subToolbar = null;
192
- this.paginationGroup = null;
193
- this.viewables = [];
194
- this.currentIndex = 0;
1367
+ this.Autodesk = Autodesk;
1368
+ this.options = options;
1369
+ // Initialize EventBus
1370
+ this.eventBus = EventBus.getInstance();
1371
+ // Initialize Managers (Dependency Injection)
1372
+ // CRITICAL: Pass EventBus instance to ensure all managers use the same instance
1373
+ this.paginationManager = new PaginationManager(viewer, Autodesk, {}, this.eventBus);
1374
+ // Register page change callback (direct communication instead of EventBus)
1375
+ this.paginationManager.setPageChangeCallback(function (data) {
1376
+ _this.onPageChanged(data);
1377
+ });
1378
+ this.toolbarManager = new ToolbarManager(viewer, Autodesk, {
1379
+ Autodesk: Autodesk,
1380
+ filePath: options.filePath,
1381
+ onPanClick: function onPanClick() {
1382
+ return _this.handlePanClick();
1383
+ },
1384
+ onDocBrowserClick: function onDocBrowserClick() {
1385
+ return _this.handleDocBrowserClick();
1386
+ },
1387
+ onDownloadClick: function onDownloadClick() {
1388
+ return _this.handleDownloadClick();
1389
+ }
1390
+ });
1391
+ this.docBrowserManager = new DocumentBrowserManager(viewer, {
1392
+ autoOpen: true,
1393
+ persistState: true,
1394
+ defaultTab: 'thumbnails'
1395
+ }, this.eventBus);
1396
+ // Initialize Services
1397
+ this.downloadService = new DownloadService();
1398
+ // Toolbar healing interval
1399
+ this._toolbarInterval = null;
1400
+ this._buttonStateInterval = null;
1401
+ // Store viewables temporarily if toolbar not ready yet
1402
+ this._pendingViewables = null;
1403
+ this._toolbarReady = false;
1404
+ // Track if page change is in progress
1405
+ this._isPageChanging = false;
1406
+ // Bind event handlers
1407
+ this.onToolbarCreatedHandler = this.onToolbarCreated.bind(this);
1408
+ this.onGeometryLoadedHandler = this.onGeometryLoaded.bind(this);
1409
+ this.onPageChangedHandler = this.onPageChanged.bind(this);
1410
+ this.onViewablesSetHandler = this.onViewablesSet.bind(this);
195
1411
  }
196
1412
  ToolbarExtension.prototype = Object.create(Autodesk.Viewing.Extension.prototype);
197
1413
  ToolbarExtension.prototype.constructor = ToolbarExtension;
198
1414
  ToolbarExtension.prototype.load = function () {
199
- return true;
200
- };
201
- ToolbarExtension.prototype.unload = function () {
202
- this.cleanupToolbar();
203
- return true;
204
- };
205
- ToolbarExtension.prototype.cleanupToolbar = function () {
206
- if (this.viewer.toolbar) {
207
- if (this.subToolbar) {
208
- this.viewer.toolbar.removeControl(this.subToolbar);
209
- this.subToolbar = null;
210
- }
211
- if (this.paginationGroup) {
212
- this.viewer.toolbar.removeControl(this.paginationGroup);
213
- this.paginationGroup = null;
214
- }
215
- }
216
- };
217
- ToolbarExtension.prototype.setViewables = function (viewables) {
218
- var _this = this;
219
- this.viewables = viewables;
220
- this.currentIndex = 0;
221
- this.updatePaginationState();
222
- this.viewer.addEventListener(Autodesk.Viewing.MODEL_ADDED_EVENT, function (e) {
223
- _this.updateCurrentIndexFromModel();
1415
+ var _this2 = this;
1416
+ this.viewer.addEventListener(Autodesk.Viewing.TOOLBAR_CREATED_EVENT, this.onToolbarCreatedHandler);
1417
+ this.viewer.addEventListener(Autodesk.Viewing.GEOMETRY_LOADED_EVENT, this.onGeometryLoadedHandler);
1418
+ // Subscribe to EventBus events
1419
+ this.eventBus.on(EVENT_NAMES.PAGE_CHANGED, this.onPageChangedHandler);
1420
+ this.eventBus.on(EVENT_NAMES.VIEWABLES_SET, this.onViewablesSetHandler);
1421
+ // Subscribe to Document Browser open/close events
1422
+ this.eventBus.on(EVENT_NAMES.DOC_BROWSER_OPENED, function () {
1423
+ GLOBAL_STATE.docBrowserShouldBeOpen = true;
1424
+ setTimeout(function () {
1425
+ return _this2.syncDocBrowserButtonState();
1426
+ }, TOOLBAR_REFRESH_INTERVALS.DOM_SETTLE);
224
1427
  });
225
- };
226
- ToolbarExtension.prototype.updateCurrentIndexFromModel = function () {
227
- if (!this.viewer.model || this.viewables.length === 0) return;
228
- var currentNode = this.viewer.model.getDocumentNode();
229
- if (currentNode) {
230
- var currentGuid = currentNode.data.guid || currentNode.guid;
231
- var index = this.viewables.findIndex(function (v) {
232
- var vGuid = v.data && v.data.guid ? v.data.guid : v.guid;
233
- return vGuid === currentGuid;
234
- });
235
- if (index !== -1) {
236
- this.currentIndex = index;
1428
+ this.eventBus.on(EVENT_NAMES.DOC_BROWSER_CLOSED, function () {
1429
+ GLOBAL_STATE.docBrowserShouldBeOpen = false;
1430
+ setTimeout(function () {
1431
+ return _this2.syncDocBrowserButtonState();
1432
+ }, TOOLBAR_REFRESH_INTERVALS.DOM_SETTLE);
1433
+ });
1434
+ // CRITICAL: Restore state from GLOBAL_STATE if available
1435
+ // This handles the case where extension was unloaded and reloaded (e.g., thumbnail navigation)
1436
+ if (GLOBAL_STATE.viewables && GLOBAL_STATE.viewables.length > 0) {
1437
+ // Restore viewables to pagination manager
1438
+ this._pendingViewables = GLOBAL_STATE.viewables;
1439
+ // Restore Document Browser state
1440
+ if (GLOBAL_STATE.docBrowserShouldBeOpen) {
1441
+ this.docBrowserManager.setShouldRemainOpen(true);
1442
+ // CRITICAL: Sync button state immediately and repeatedly after load
1443
+ // This ensures button is active when extension reloads after using _changeModelFn
1444
+ var syncTimes = [50, 100, 150, 200, 300, 400, 500];
1445
+ syncTimes.forEach(function (delay) {
1446
+ setTimeout(function () {
1447
+ _this2.syncDocBrowserButtonState();
1448
+ }, delay);
1449
+ });
237
1450
  }
238
- this.updatePaginationState();
239
1451
  }
1452
+ return true;
240
1453
  };
241
- ToolbarExtension.prototype.updatePaginationState = function () {
242
- if (!this.paginationGroup) return;
243
-
244
- // Hide pagination group if there's only 1 page or no pages
245
- if (this.viewables.length <= 1) {
246
- this.paginationGroup.setVisible(false);
247
- // Also hide the container via CSS to ensure it's completely hidden
248
- if (this.paginationGroup.container) {
249
- this.paginationGroup.container.style.display = 'none';
250
- }
251
- return;
252
- }
253
-
254
- // Show pagination group for multi-page documents
255
- this.paginationGroup.setVisible(true);
256
- // Ensure container is visible
257
- if (this.paginationGroup.container) {
258
- this.paginationGroup.container.style.display = '';
259
- }
260
- var totalBtn = this.paginationGroup.getControl('total-page-label');
261
- if (totalBtn) {
262
- var current = this.viewables.length > 0 ? this.currentIndex + 1 : 0;
263
- var total = this.viewables.length;
264
- totalBtn.setToolTip("Page ".concat(current, " of ").concat(total));
265
- var domElem = totalBtn.container;
266
- if (domElem) {
267
- domElem.innerHTML = "<div style=\"display: flex; align-items: center; justify-content: center; height: 100%; padding: 0 10px; color: white; font-size: 14px; white-space: nowrap;\">".concat(current, " / ").concat(total, "</div>");
268
- }
1454
+ ToolbarExtension.prototype.unload = function () {
1455
+ // CRITICAL: Save state to GLOBAL_STATE before unloading
1456
+ // This allows state to persist across unload/reload cycles (e.g., thumbnail navigation)
1457
+ var totalPages = this.paginationManager.getTotalPages();
1458
+ if (totalPages > 0) {
1459
+ GLOBAL_STATE.viewables = this.paginationManager.getViewables();
1460
+ GLOBAL_STATE.currentIndex = this.paginationManager.getCurrentPage();
1461
+ GLOBAL_STATE.docBrowserShouldBeOpen = this.docBrowserManager.getShouldRemainOpen();
269
1462
  }
270
- };
271
- ToolbarExtension.prototype.loadCurrentViewable = function () {
272
- var _this2 = this;
273
- if (this.viewables.length > 0 && this.viewables[this.currentIndex]) {
274
- var viewer = this.viewer;
275
- var toolbar = viewer.toolbar;
276
- viewer.loadDocumentNode(viewer.model.getDocumentNode().getDocument(), this.viewables[this.currentIndex]).then(function () {
277
- var defaultGroups = ['settingsTools', 'modelTools', 'navTools'];
278
- defaultGroups.forEach(function (id) {
279
- var group = toolbar.getControl(id);
280
- if (group) group.setVisible(false);
281
- });
282
- var toolGroupExists = toolbar.getControl('custom-tool-group');
283
- var paginationGroupExists = toolbar.getControl('custom-pagination-group');
284
- if (!toolGroupExists && _this2.subToolbar) {
285
- toolbar.addControl(_this2.subToolbar);
286
- }
287
- if (!paginationGroupExists && _this2.paginationGroup) {
288
- toolbar.addControl(_this2.paginationGroup);
289
- }
290
- if (_this2.subToolbar) _this2.subToolbar.setVisible(true);
291
- if (_this2.paginationGroup) _this2.paginationGroup.setVisible(true);
292
- toolbar.setVisible(true);
293
- _this2.updatePaginationState();
294
- })["catch"](function (err) {
295
- return console.error('Error loading viewable:', err);
296
- });
1463
+ // Cleanup event listeners
1464
+ this.viewer.removeEventListener(Autodesk.Viewing.TOOLBAR_CREATED_EVENT, this.onToolbarCreatedHandler);
1465
+ this.viewer.removeEventListener(Autodesk.Viewing.GEOMETRY_LOADED_EVENT, this.onGeometryLoadedHandler);
1466
+ // Unsubscribe from EventBus
1467
+ this.eventBus.off(EVENT_NAMES.PAGE_CHANGED, this.onPageChangedHandler);
1468
+ this.eventBus.off(EVENT_NAMES.VIEWABLES_SET, this.onViewablesSetHandler);
1469
+ // Cleanup intervals
1470
+ if (this._toolbarInterval) {
1471
+ clearInterval(this._toolbarInterval);
1472
+ this._toolbarInterval = null;
297
1473
  }
1474
+ return true;
298
1475
  };
299
- ToolbarExtension.prototype.onToolbarCreated = function (toolbar) {
1476
+ /**
1477
+ * Called when toolbar is created
1478
+ * Delegates to managers for setup
1479
+ */
1480
+ ToolbarExtension.prototype.onToolbarCreated = function () {
300
1481
  var _this3 = this;
1482
+ // Initial toolbar setup
301
1483
  this.refreshToolbar();
1484
+ // Add listener for geometry loaded to refresh toolbar
302
1485
  this.viewer.addEventListener(Autodesk.Viewing.GEOMETRY_LOADED_EVENT, function () {
303
1486
  setTimeout(function () {
304
1487
  _this3.refreshToolbar();
305
- _this3.updateCurrentIndexFromModel();
306
- }, 100);
1488
+ }, TOOLBAR_REFRESH_INTERVALS.GEOMETRY_SETTLE);
307
1489
  });
1490
+ // Add listener for toolbar created event
308
1491
  this.viewer.addEventListener(Autodesk.Viewing.TOOLBAR_CREATED_EVENT, function () {
309
1492
  setTimeout(function () {
310
1493
  _this3.refreshToolbar();
311
- }, 10);
1494
+ }, TOOLBAR_REFRESH_INTERVALS.BUTTON_STATE_CHECK);
312
1495
  });
313
- if (this._toolbarInterval) clearInterval(this._toolbarInterval);
314
- this._toolbarInterval = setInterval(function () {
315
- if (_this3.viewer && _this3.viewer.toolbar) {
316
- var toolGroup = _this3.viewer.toolbar.getControl('custom-tool-group');
317
- var pagGroup = _this3.viewer.toolbar.getControl('custom-pagination-group');
318
- if (!toolGroup || !pagGroup) {
319
- _this3.refreshToolbar();
320
- }
321
- _this3.checkButtonState();
322
- }
323
- }, 200);
324
- };
325
- ToolbarExtension.prototype.checkButtonState = function () {
326
- var viewer = this.viewer;
327
- if (!viewer.toolbar) return;
328
- var toolGroup = viewer.toolbar.getControl('custom-tool-group');
329
- if (!toolGroup) return;
330
- var docBtn = toolGroup.getControl('custom-doc-browser-btn');
331
- if (!docBtn) return;
332
- var ext = viewer.getExtension('Autodesk.DocumentBrowser');
333
- var isVisible = ext && ext.ui && ext.ui.panel && ext.ui.panel.isVisible();
334
- var newState = isVisible ? Autodesk.Viewing.UI.Button.State.ACTIVE : Autodesk.Viewing.UI.Button.State.INACTIVE;
335
- if (docBtn.getState() !== newState) {
336
- docBtn.setState(newState);
337
- }
338
-
339
- // Force CSS class update to ensure visual state is correct
340
- if (docBtn.container) {
341
- if (isVisible) {
342
- docBtn.container.classList.add('active');
343
- docBtn.container.classList.remove('inactive');
344
- } else {
345
- docBtn.container.classList.remove('active');
346
- docBtn.container.classList.add('inactive');
347
- }
348
- }
349
- };
350
- var originalUnload = ToolbarExtension.prototype.unload;
351
- ToolbarExtension.prototype.unload = function () {
352
- if (this._toolbarInterval) {
353
- clearInterval(this._toolbarInterval);
354
- this._toolbarInterval = null;
355
- }
356
- return originalUnload.call(this);
1496
+ // Start toolbar healing mechanism (also handles button state sync)
1497
+ this.startToolbarHealing();
357
1498
  };
1499
+ /**
1500
+ * Refresh toolbar - recreate if needed
1501
+ * This is the core method that handles toolbar persistence
1502
+ */
358
1503
  ToolbarExtension.prototype.refreshToolbar = function () {
359
- var viewer = this.viewer;
360
- var toolbar = viewer.toolbar;
1504
+ var _this4 = this;
1505
+ var toolbar = this.viewer.toolbar;
361
1506
  if (!toolbar) return;
362
- var defaultGroups = ['settingsTools', 'modelTools', 'navTools'];
363
- defaultGroups.forEach(function (id) {
364
- var group = toolbar.getControl(id);
365
- if (group) {
366
- group.setVisible(false);
367
- if (group.container) {
368
- group.container.style.display = 'none';
369
- }
370
- }
371
- });
1507
+ // Hide default groups
1508
+ this.toolbarManager.hideDefaultGroups();
1509
+ // Check and recreate tool group if needed
372
1510
  var toolGroup = toolbar.getControl('custom-tool-group');
373
- var pagGroup = toolbar.getControl('custom-pagination-group');
374
1511
  if (!toolGroup) {
375
- if (this.subToolbar) {
376
- this.subToolbar = null;
377
- }
378
- this.createToolGroup(toolbar);
1512
+ this.toolbarManager.createToolbar();
379
1513
  } else {
380
1514
  toolGroup.setVisible(true);
381
- this.subToolbar = toolGroup;
382
1515
  }
1516
+ // Check and recreate pagination group if needed
1517
+ var pagGroup = toolbar.getControl('custom-pagination-group');
383
1518
  if (!pagGroup) {
384
- if (this.paginationGroup) {
385
- this.paginationGroup = null;
386
- }
387
- this.createPaginationGroup(toolbar);
1519
+ this.paginationManager.createPaginationGroup(toolbar);
388
1520
  } else {
389
- // Don't force visibility here - let updatePaginationState handle it
390
- this.paginationGroup = pagGroup;
1521
+ // Ensure pagination manager has correct reference to existing group
1522
+ this.paginationManager.setPaginationGroup(pagGroup);
1523
+ pagGroup.setVisible(true);
1524
+ }
1525
+ // Mark toolbar as ready
1526
+ this._toolbarReady = true;
1527
+ // Process pending viewables if any
1528
+ if (this._pendingViewables) {
1529
+ this.paginationManager.setViewables(this._pendingViewables);
1530
+ if (this._pendingViewables.length > 1) {
1531
+ this.docBrowserManager.autoOpenIfMultiPage(true);
1532
+ }
1533
+ this._pendingViewables = null;
391
1534
  }
1535
+ // Show toolbar
392
1536
  toolbar.setVisible(true);
393
- this.updatePaginationState();
1537
+ // ALWAYS update pagination state after toolbar refresh
1538
+ // Use multiple timeouts to ensure it sticks
1539
+ var updateTimes = [10, 30, 50, 100];
1540
+ updateTimes.forEach(function (delay) {
1541
+ setTimeout(function () {
1542
+ _this4.paginationManager.updatePaginationState();
1543
+ }, delay);
1544
+ });
1545
+ // CRITICAL: Sync Document Browser button state after toolbar refresh
1546
+ // This ensures button is active if panel is open (especially after reload)
1547
+ var syncTimes = [50, 100, 150, 200];
1548
+ syncTimes.forEach(function (delay) {
1549
+ setTimeout(function () {
1550
+ var isOpen = _this4.docBrowserManager.isOpen();
1551
+ if (isOpen) {
1552
+ _this4.syncDocBrowserButtonState();
1553
+ }
1554
+ }, delay);
1555
+ });
1556
+ // Activate pan tool
1557
+ this.toolbarManager.activatePanButton();
394
1558
  };
395
- ToolbarExtension.prototype.createToolGroup = function (toolbar) {
396
- var _this4 = this;
397
- var viewer = this.viewer;
398
- this.subToolbar = new Autodesk.Viewing.UI.ControlGroup('custom-tool-group');
399
- toolbar.addControl(this.subToolbar);
400
- var panBtn = new Autodesk.Viewing.UI.Button('custom-pan-btn');
401
- panBtn.setIcon('adsk-icon-pan');
402
- panBtn.setToolTip('Pan');
403
- panBtn.onClick = function () {
404
- var originalPanBtn = document.getElementById('toolbar-panTool');
405
- if (originalPanBtn) {
406
- originalPanBtn.click();
1559
+ /**
1560
+ * Called when geometry is loaded
1561
+ * Restores button states
1562
+ */
1563
+ ToolbarExtension.prototype.onGeometryLoaded = function () {
1564
+ var _this5 = this;
1565
+ setTimeout(function () {
1566
+ _this5.toolbarManager.activatePanButton();
1567
+ _this5.paginationManager.updatePaginationState();
1568
+ }, TOOLBAR_REFRESH_INTERVALS.GEOMETRY_SETTLE);
1569
+ };
1570
+ /**
1571
+ * Called when page changes
1572
+ * Restores Document Browser and toolbar states
1573
+ */
1574
+ ToolbarExtension.prototype.onPageChanged = function (data) {
1575
+ var _this6 = this;
1576
+ if (!data || data.test) {
1577
+ return;
1578
+ }
1579
+ // Mark that page change is in progress to prevent premature pagination updates
1580
+ this._isPageChanging = true;
1581
+ // CRITICAL FIX: If page changed from Document Browser (thumbnails), sync button state IMMEDIATELY
1582
+ // This ensures the button is active when user navigates via thumbnails
1583
+ if (data.fromDocBrowser) {
1584
+ // Sync button state right away (before any other operations)
1585
+ this.syncDocBrowserButtonState();
1586
+ // Force recreate pagination group to ensure display updates
1587
+ var toolbar = this.viewer.toolbar;
1588
+ if (toolbar) {
1589
+ var pagGroup = toolbar.getControl('custom-pagination-group');
1590
+ if (pagGroup) {
1591
+ toolbar.removeControl(pagGroup);
1592
+ this.paginationManager.setPaginationGroup(null);
1593
+ }
407
1594
  }
408
-
409
- // Set this button to active state
410
- if (panBtn.container) {
411
- panBtn.container.classList.add('active');
412
- panBtn.container.classList.remove('inactive');
1595
+ }
1596
+ // Refresh toolbar to ensure both tool and pagination groups are restored
1597
+ this.refreshToolbar();
1598
+ // Restore Document Browser panel if needed
1599
+ if (data.shouldRestoreDocBrowser) {
1600
+ this.docBrowserManager.restoreState();
1601
+ // Force sync button state multiple times after restore
1602
+ var syncDelays = data.fromDocBrowser ? [10, 30, 50, 80, 100, 150, 200, 250, 300] : [50, 100, 150, 200, 250, 300];
1603
+ syncDelays.forEach(function (delay) {
1604
+ setTimeout(function () {
1605
+ _this6.syncDocBrowserButtonState();
1606
+ }, delay);
1607
+ });
1608
+ }
1609
+ // Sync Document Browser button state and update pagination display at multiple intervals
1610
+ var syncIntervals = data.fromDocBrowser ? [10, 30, 50, 80, 100, 150, 200, 250, 300] : [50, 100, 150, 200, 250, 300];
1611
+ syncIntervals.forEach(function (delay, index) {
1612
+ setTimeout(function () {
1613
+ _this6.syncDocBrowserButtonState();
1614
+ _this6.paginationManager.updatePaginationState();
1615
+ if (index === 0) {
1616
+ _this6._isPageChanging = false;
1617
+ }
1618
+ }, delay);
1619
+ });
1620
+ // Additional pagination updates at various intervals to handle race conditions
1621
+ var updateIntervals = data.fromDocBrowser ? [60, 100, 120, 150, 180, 200, 250, 300, 350, 400, 500] : [100, 150, 200, 300];
1622
+ updateIntervals.forEach(function (delay) {
1623
+ setTimeout(function () {
1624
+ _this6.paginationManager.updatePaginationState();
1625
+ }, delay);
1626
+ });
1627
+ };
1628
+ /**
1629
+ * Called when viewables are set (from FileLoader)
1630
+ * Sets viewables to pagination manager and auto-opens Document Browser
1631
+ */
1632
+ ToolbarExtension.prototype.onViewablesSet = function (data) {
1633
+ if (data && data.viewables) {
1634
+ // CRITICAL: Save viewables to GLOBAL_STATE for persistence across unload/reload
1635
+ GLOBAL_STATE.viewables = data.viewables;
1636
+ // Check if toolbar is ready
1637
+ if (this._toolbarReady) {
1638
+ // Toolbar ready - set viewables immediately
1639
+ this.paginationManager.setViewables(data.viewables);
1640
+ // Auto-open Document Browser if multi-page
1641
+ if (data.viewables.length > 1) {
1642
+ GLOBAL_STATE.docBrowserShouldBeOpen = true;
1643
+ this.docBrowserManager.autoOpenIfMultiPage(true);
1644
+ }
1645
+ } else {
1646
+ // Toolbar not ready yet - store viewables for later
1647
+ this._pendingViewables = data.viewables;
413
1648
  }
414
- panBtn.setState(Autodesk.Viewing.UI.Button.State.ACTIVE);
415
- };
416
- this.subToolbar.addControl(panBtn);
417
- var docBrowserBtn = new Autodesk.Viewing.UI.Button('custom-doc-browser-btn');
418
- docBrowserBtn.setIcon('adsk-icon-documentModels');
419
- docBrowserBtn.setToolTip('Document Browser');
420
- docBrowserBtn.onClick = function () {
421
- var ext = viewer.getExtension('Autodesk.DocumentBrowser');
422
- if (ext && ext.ui) ext.ui.togglePanel();
423
- };
424
- this.subToolbar.addControl(docBrowserBtn);
425
- var downloadBtn = new Autodesk.Viewing.UI.Button('custom-download-btn');
426
- downloadBtn.setIcon('adsk-icon-custom-download');
427
- downloadBtn.setToolTip('Download File');
428
- downloadBtn.onClick = /*#__PURE__*/_asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee() {
429
- var fileUrl, response, blob, urlPath, filename, blobUrl, anchor, _t;
430
- return _regenerator().w(function (_context) {
431
- while (1) switch (_context.p = _context.n) {
432
- case 0:
433
- if (!(_this4.options && _this4.options.filePath)) {
434
- _context.n = 7;
435
- break;
436
- }
437
- _context.p = 1;
438
- fileUrl = _this4.options.filePath;
439
- _context.n = 2;
440
- return fetch(fileUrl);
441
- case 2:
442
- response = _context.v;
443
- if (response.ok) {
444
- _context.n = 3;
445
- break;
446
- }
447
- throw new Error("HTTP error! status: ".concat(response.status));
448
- case 3:
449
- _context.n = 4;
450
- return response.blob();
451
- case 4:
452
- blob = _context.v;
453
- urlPath = new URL(fileUrl).pathname;
454
- filename = decodeURIComponent(urlPath.split('/').pop()) || 'document.pdf';
455
- blobUrl = URL.createObjectURL(blob);
456
- anchor = document.createElement('a');
457
- anchor.href = blobUrl;
458
- anchor.download = filename;
459
- anchor.style.display = 'none';
460
- document.body.appendChild(anchor);
461
- anchor.click();
462
- document.body.removeChild(anchor);
463
- URL.revokeObjectURL(blobUrl);
464
- _context.n = 6;
465
- break;
466
- case 5:
467
- _context.p = 5;
468
- _t = _context.v;
469
- console.error('Download error:', _t);
470
- alert('Unable to download file: ' + _t.message);
471
- case 6:
472
- _context.n = 8;
473
- break;
474
- case 7:
475
- console.warn('FilePath not available in extension options');
476
- alert('File path not available for download');
477
- case 8:
478
- return _context.a(2);
479
- }
480
- }, _callee, null, [[1, 5]]);
481
- }));
482
- this.subToolbar.addControl(downloadBtn);
1649
+ }
483
1650
  };
484
- ToolbarExtension.prototype.createPaginationGroup = function (toolbar) {
485
- var _this5 = this;
486
- this.paginationGroup = new Autodesk.Viewing.UI.ControlGroup('custom-pagination-group');
487
- toolbar.addControl(this.paginationGroup);
488
- var prevBtn = new Autodesk.Viewing.UI.Button('prev-page-btn');
489
- prevBtn.setIcon('adsk-icon-custom-prev');
490
- prevBtn.addClass('custom-prev-btn');
491
- prevBtn.setToolTip('Previous Page');
492
- prevBtn.onClick = function () {
493
- if (_this5.viewables.length > 0) {
494
- _this5.currentIndex = (_this5.currentIndex - 1 + _this5.viewables.length) % _this5.viewables.length;
495
- _this5.loadCurrentViewable();
1651
+ /**
1652
+ * Start toolbar healing mechanism
1653
+ * Polls every 8ms to check if toolbar needs recreation
1654
+ */
1655
+ ToolbarExtension.prototype.startToolbarHealing = function () {
1656
+ var _this7 = this;
1657
+ if (this._toolbarInterval) {
1658
+ clearInterval(this._toolbarInterval);
1659
+ }
1660
+ // Store last known Document Browser state to detect changes
1661
+ this._lastDocBrowserState = null;
1662
+ this._healingCounter = 0;
1663
+ this._toolbarInterval = setInterval(function () {
1664
+ if (_this7.viewer && _this7.viewer.toolbar) {
1665
+ _this7._healingCounter++;
1666
+ var toolbar = _this7.viewer.toolbar;
1667
+ var toolGroup = toolbar.getControl('custom-tool-group');
1668
+ var pagGroup = toolbar.getControl('custom-pagination-group');
1669
+ // Refresh toolbar if either group is missing
1670
+ if (!toolGroup || !pagGroup) {
1671
+ _this7.refreshToolbar();
1672
+ }
1673
+ // Check if Document Browser panel state changed
1674
+ var currentDocBrowserState = _this7.docBrowserManager.isOpen();
1675
+ if (_this7._lastDocBrowserState !== null && _this7._lastDocBrowserState !== currentDocBrowserState) {
1676
+ // Update shouldRemainOpen flag based on current state
1677
+ if (currentDocBrowserState) {
1678
+ _this7.docBrowserManager.setShouldRemainOpen(true);
1679
+ }
1680
+ // Sync button state immediately when state changes
1681
+ _this7.syncDocBrowserButtonState();
1682
+ }
1683
+ _this7._lastDocBrowserState = currentDocBrowserState;
1684
+ // CRITICAL: Sync button state periodically (every ~50ms = every 6 healing cycles)
1685
+ // This catches thumbnail navigation where panel stays open but button might lose active state
1686
+ if (_this7._healingCounter % 6 === 0) {
1687
+ _this7.syncDocBrowserButtonState();
1688
+ }
1689
+ // ADDITIONAL: If panel is currently open, ensure button is always active
1690
+ // This is the ultimate fallback to handle thumbnail navigation
1691
+ if (currentDocBrowserState && _this7._healingCounter % 3 === 0) {
1692
+ // Check button state and force sync if needed (every ~24ms = every 3 healing cycles)
1693
+ var _toolGroup = toolbar.getControl('custom-tool-group');
1694
+ if (_toolGroup) {
1695
+ var docBtn = _toolGroup.getControl('custom-doc-browser-btn');
1696
+ if (docBtn) {
1697
+ var currentBtnState = docBtn.getState();
1698
+ var ACTIVE_STATE = _this7.Autodesk.Viewing.UI.Button.State.ACTIVE;
1699
+ // If panel is open but button is not active, force sync
1700
+ if (currentBtnState !== ACTIVE_STATE) {
1701
+ _this7.syncDocBrowserButtonState();
1702
+ }
1703
+ }
1704
+ }
1705
+ }
496
1706
  }
497
- };
498
- this.paginationGroup.addControl(prevBtn);
499
- var labelBtn = new Autodesk.Viewing.UI.Button('total-page-label');
500
- labelBtn.setToolTip('Page info');
501
- this.paginationGroup.addControl(labelBtn);
502
- var nextBtn = new Autodesk.Viewing.UI.Button('next-page-btn');
503
- nextBtn.setIcon('adsk-icon-custom-next');
504
- nextBtn.addClass('custom-next-btn');
505
- nextBtn.setToolTip('Next Page');
506
- nextBtn.onClick = function () {
507
- if (_this5.viewables.length > 0) {
508
- _this5.currentIndex = (_this5.currentIndex + 1) % _this5.viewables.length;
509
- _this5.loadCurrentViewable();
1707
+ }, TOOLBAR_REFRESH_INTERVALS.HEALING_POLL);
1708
+ };
1709
+ /**
1710
+ * Start button state synchronization
1711
+ * Polls to keep custom button states in sync with actual states
1712
+ */
1713
+ ToolbarExtension.prototype.startButtonStateSync = function () {
1714
+ var _this8 = this;
1715
+ if (this._buttonStateInterval) {
1716
+ clearInterval(this._buttonStateInterval);
1717
+ }
1718
+ this._buttonStateInterval = setInterval(function () {
1719
+ _this8.syncDocBrowserButtonState();
1720
+ }, TOOLBAR_REFRESH_INTERVALS.BUTTON_STATE_CHECK);
1721
+ };
1722
+ /**
1723
+ * Set Document Browser button active state
1724
+ */
1725
+ ToolbarExtension.prototype.setDocBrowserButtonActive = function (isActive) {
1726
+ var toolbar = this.viewer.toolbar;
1727
+ if (!toolbar) return;
1728
+ var toolGroup = toolbar.getControl('custom-tool-group');
1729
+ if (!toolGroup) return;
1730
+ var docBtn = toolGroup.getControl(CUSTOM_TOOLBAR_BUTTONS.DOC_BROWSER.id);
1731
+ if (!docBtn) return;
1732
+ var newState = isActive ? Autodesk.Viewing.UI.Button.State.ACTIVE : Autodesk.Viewing.UI.Button.State.INACTIVE;
1733
+ docBtn.setState(newState);
1734
+ };
1735
+ /**
1736
+ * Sync Document Browser button state with panel visibility
1737
+ */
1738
+ ToolbarExtension.prototype.syncDocBrowserButtonState = function () {
1739
+ var toolbar = this.viewer.toolbar;
1740
+ if (!toolbar) return;
1741
+ var toolGroup = toolbar.getControl('custom-tool-group');
1742
+ if (!toolGroup) return;
1743
+ var docBtn = toolGroup.getControl(CUSTOM_TOOLBAR_BUTTONS.DOC_BROWSER.id);
1744
+ if (!docBtn) return;
1745
+ var isOpen = this.docBrowserManager.isOpen();
1746
+ var ACTIVE_STATE = Autodesk.Viewing.UI.Button.State.ACTIVE;
1747
+ var INACTIVE_STATE = Autodesk.Viewing.UI.Button.State.INACTIVE;
1748
+ var expectedState = isOpen ? ACTIVE_STATE : INACTIVE_STATE;
1749
+ // Set button state via API
1750
+ docBtn.setState(expectedState);
1751
+ // Also update DOM class directly to ensure visual state
1752
+ var btnElement = docBtn.container;
1753
+ if (btnElement) {
1754
+ if (isOpen) {
1755
+ btnElement.classList.add('active');
1756
+ btnElement.classList.remove('inactive');
1757
+ } else {
1758
+ btnElement.classList.remove('active');
1759
+ btnElement.classList.add('inactive');
510
1760
  }
511
- };
512
- this.paginationGroup.addControl(nextBtn);
1761
+ }
1762
+ };
1763
+ /**
1764
+ * Handle Pan button click
1765
+ */
1766
+ ToolbarExtension.prototype.handlePanClick = function () {
1767
+ this.toolbarManager.activatePanButton();
1768
+ };
1769
+ /**
1770
+ * Handle Document Browser button click
1771
+ */
1772
+ ToolbarExtension.prototype.handleDocBrowserClick = function () {
1773
+ var _this9 = this;
1774
+ this.docBrowserManager.togglePanel();
1775
+ // Sync button state with new panel state at multiple intervals
1776
+ [50, 100, 150, 200].forEach(function (delay) {
1777
+ setTimeout(function () {
1778
+ _this9.syncDocBrowserButtonState();
1779
+ }, delay);
1780
+ });
513
1781
  };
1782
+ /**
1783
+ * Handle Download button click
1784
+ */
1785
+ ToolbarExtension.prototype.handleDownloadClick = /*#__PURE__*/_asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee() {
1786
+ var _t;
1787
+ return _regenerator().w(function (_context) {
1788
+ while (1) switch (_context.p = _context.n) {
1789
+ case 0:
1790
+ _context.p = 0;
1791
+ _context.n = 1;
1792
+ return this.downloadService.downloadFile(this.options.filePath);
1793
+ case 1:
1794
+ _context.n = 3;
1795
+ break;
1796
+ case 2:
1797
+ _context.p = 2;
1798
+ _t = _context.v;
1799
+ console.error('Download failed:', _t);
1800
+ case 3:
1801
+ return _context.a(2);
1802
+ }
1803
+ }, _callee, this, [[0, 2]]);
1804
+ }));
1805
+ // Register extension
514
1806
  Autodesk.Viewing.theExtensionManager.registerExtension('ToolbarExtension', ToolbarExtension);
515
1807
  }
516
1808
 
@@ -606,152 +1898,109 @@ function styleInject(css, ref) {
606
1898
  var css_248z$1 = "#navTools,\n#modelTools,\n#settingsTools,\n#measureTools {\n display: none !important;\n visibility: hidden !important;\n}\n#guiviewer3d-toolbar {\n display: flex !important;\n align-items: center !important;\n justify-content: center !important;\n gap: 20px;\n position: fixed !important;\n bottom: 10px !important;\n left: 50% !important;\n transform: translateX(-50%) !important;\n width: auto !important;\n border-radius: 4px !important;\n padding: 8px 12px !important;\n}\n#custom-tool-group {\n display: flex !important;\n margin: 0 !important;\n padding: 0 !important;\n}\n#custom-pagination-group {\n display: flex;\n position: relative !important;\n margin: 0 !important;\n padding: 0 !important;\n transform: none !important;\n left: auto !important;\n}\n";
607
1899
  styleInject(css_248z$1);
608
1900
 
609
- var css_248z = "/* Custom icon styles for toolbar using SVG */\n\n/* Download icon - custom SVG */\n.adsk-icon-custom-download::before {\n content: '';\n display: inline-block;\n width: 24px;\n height: 24px;\n background-image: url('data:image/svg+xml;utf8,\\\n<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 16 16\">\\\n<path fill=\"%23FFFFFF\" d=\"M9.293 0H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V4.707A1 1 0 0 0 13.707 4L10 .293A1 1 0 0 0 9.293 0M9.5 3.5v-2l3 3h-2a1 1 0 0 1-1-1m-1 4v3.793l1.146-1.147a.5.5 0 0 1 .708.708l-2 2a.5.5 0 0 1-.708 0l-2-2a.5.5 0 0 1 .708-.708L7.5 11.293V7.5a.5.5 0 0 1 1 0\"/>\\\n</svg>');\n background-repeat: no-repeat;\n background-position: center;\n background-size: 16px 16px;\n}\n\n/* Previous page icon - Font Awesome chevron left */\n.adsk-icon-custom-prev::before {\n content: '';\n display: inline-block;\n width: 24px;\n height: 24px;\n background-image: url('data:image/svg+xml;utf8,\\\n<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 16 16\">\\\n<path fill=\"%23FFFFFF\" d=\"M16 14a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2zm-4.5-6.5H5.707l2.147-2.146a.5.5 0 1 0-.708-.708l-3 3a.5.5 0 0 0 0 .708l3 3a.5.5 0 0 0 .708-.708L5.707 8.5H11.5a.5.5 0 0 0 0-1\"/>\\\n</svg>');\n background-repeat: no-repeat;\n background-position: center;\n background-size: 16px 16px;\n}\n\n/* Next page icon - Font Awesome chevron right */\n.adsk-icon-custom-next::before {\n content: '';\n display: inline-block;\n width: 24px;\n height: 24px;\n background-image: url('data:image/svg+xml;utf8,\\\n<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 16 16\">\\\n<path fill=\"%23FFFFFF\" d=\"M0 14a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2a2 2 0 0 0-2 2zm4.5-6.5h5.793L8.146 5.354a.5.5 0 1 1 .708-.708l3 3a.5.5 0 0 1 0 .708l-3 3a.5.5 0 0 1-.708-.708L10.293 8.5H4.5a.5.5 0 0 1 0-1\"/>\\\n</svg>');\n background-repeat: no-repeat;\n background-position: center;\n background-size: 16px 16px;\n}\n\n/* Fallback for adsk-icon-caret-left and adsk-icon-caret-right if not defined */\n.adsk-icon-caret-left::before {\n content: '[';\n font-family: 'adsk-viewing';\n}\n\n.adsk-icon-caret-right::before {\n content: ']';\n font-family: 'adsk-viewing';\n}\n\n/* Comment icon - custom SVG (Smiley) */\n.adsk-icon-custom-comment::before {\n content: '';\n display: inline-block;\n width: 24px;\n height: 24px;\n background-image: url('data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"%23FFFFFF\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"10\"></circle><path d=\"M8 14s1.5 2 4 2 4-2 4-2\"></path><line x1=\"9\" y1=\"9\" x2=\"9.01\" y2=\"9\"></line><line x1=\"15\" y1=\"9\" x2=\"15.01\" y2=\"9\"></line></svg>');\n background-repeat: no-repeat;\n background-position: center;\n background-size: contain;\n}\n\n/* Save icon - custom SVG */\n.adsk-icon-custom-save::before {\n content: '';\n display: inline-block;\n width: 24px;\n height: 24px;\n background-image: url('data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"%23FFFFFF\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z\"></path><polyline points=\"17 21 17 13 7 13 7 21\"></polyline><polyline points=\"7 3 7 8 15 8\"></polyline></svg>');\n background-repeat: no-repeat;\n background-position: center;\n background-size: contain;\n}\n";
1901
+ var css_248z = "/* Custom icon styles for toolbar using SVG */\n/* Download icon - custom SVG (white, thicker stroke) */\n.adsk-icon-custom-download::before {\n content: '';\n display: inline-block;\n width: 24px;\n height: 24px;\n background-image: url('data:image/svg+xml;utf8,\\\n<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"%23ffffff\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\\\n <path d=\"M4.75 17.25a.75.75 0 0 1 .75.75v2.25c0 .138.112.25.25.25h12.5a.25.25 0 0 0 .25-.25V18a.75.75 0 0 1 1.5 0v2.25A1.75 1.75 0 0 1 18.25 22H5.75A1.75 1.75 0 0 1 4 20.25V18a.75.75 0 0 1 .75-.75Z\"/>\\\n <path d=\"M5.22 9.97a.749.749 0 0 1 1.06 0l4.97 4.969V2.75a.75.75 0 0 1 1.5 0v12.189l4.97-4.969a.749.749 0 1 1 1.06 1.06l-6.25 6.25a.749.749 0 0 1-1.06 0l-6.25-6.25a.749.749 0 0 1 0-1.06Z\"/>\\\n</svg>');\n background-repeat: no-repeat;\n background-position: center;\n background-size: 16px 16px;\n}\n/* Previous page icon - custom SVG (white, filled) */\n.adsk-icon-custom-prev::before {\n content: '';\n display: inline-block;\n width: 24px;\n height: 24px;\n background-image: url('data:image/svg+xml;utf8,\\\n<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 1024 1024\">\\\n<g transform=\"translate(512 512) scale(1.2) translate(-512 -512)\">\\\n<path fill=\"%23FFFFFF\" d=\"M685.248 104.704a64 64 0 010 90.496L368.448 512l316.8 316.8a64 64 0 01-90.496 90.496L232.704 557.248a64 64 0 010-90.496l362.048-362.048a64 64 0 0190.496 0z\"/>\\\n</g>\\\n</svg>');\n background-repeat: no-repeat;\n background-position: center;\n background-size: 16px 16px;\n}\n/* Next page icon - custom SVG (white, filled) */\n.adsk-icon-custom-next::before {\n content: '';\n display: inline-block;\n width: 24px;\n height: 24px;\n background-image: url('data:image/svg+xml;utf8,\\\n<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 1024 1024\">\\\n<g transform=\"translate(512 512) scale(1.2) translate(-512 -512)\">\\\n<path fill=\"%23FFFFFF\" d=\"M338.752 104.704a64 64 0 000 90.496l316.8 316.8-316.8 316.8a64 64 0 0090.496 90.496l362.048-362.048a64 64 0 000-90.496L429.248 104.704a64 64 0 00-90.496 0z\"/>\\\n</g>\\\n</svg>');\n background-repeat: no-repeat;\n background-position: center;\n background-size: 16px 16px;\n}\n/* Fallback for adsk-icon-caret-left and adsk-icon-caret-right if not defined */\n.adsk-icon-caret-left::before {\n content: '[';\n font-family: 'adsk-viewing';\n}\n.adsk-icon-caret-right::before {\n content: ']';\n font-family: 'adsk-viewing';\n}\n/* Comment icon - custom SVG (Smiley) */\n.adsk-icon-custom-comment::before {\n content: '';\n display: inline-block;\n width: 24px;\n height: 24px;\n background-image: url('data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"%23FFFFFF\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"10\"></circle><path d=\"M8 14s1.5 2 4 2 4-2 4-2\"></path><line x1=\"9\" y1=\"9\" x2=\"9.01\" y2=\"9\"></line><line x1=\"15\" y1=\"9\" x2=\"15.01\" y2=\"9\"></line></svg>');\n background-repeat: no-repeat;\n background-position: center;\n background-size: contain;\n}\n/* Save icon - custom SVG */\n.adsk-icon-custom-save::before {\n content: '';\n display: inline-block;\n width: 24px;\n height: 24px;\n background-image: url('data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"%23FFFFFF\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z\"></path><polyline points=\"17 21 17 13 7 13 7 21\"></polyline><polyline points=\"7 3 7 8 15 8\"></polyline></svg>');\n background-repeat: no-repeat;\n background-position: center;\n background-size: contain;\n}\n";
610
1902
  styleInject(css_248z);
611
1903
 
612
1904
  var ViewerForgePDF = function ViewerForgePDF(_ref) {
613
1905
  var filePath = _ref.filePath,
614
1906
  fileExt = _ref.fileExt,
615
1907
  setViewer = _ref.setViewer;
616
- useCss('https://developer.api.autodesk.com/modelderivative/v2/viewers/7.*/style.min.css');
617
- var status = useScript('https://developer.api.autodesk.com/modelderivative/v2/viewers/7.*/viewer3D.min.js');
1908
+ // Load Forge Viewer CSS and JS from CDN
1909
+ useCss(FORGE_STYLE_URL);
1910
+ var status = useScript(FORGE_SCRIPT_URL);
618
1911
  useEffect(function () {
619
- if (status === 'ready' && window.Autodesk) {
620
- var Autodesk = window.Autodesk;
621
- if (!fileExt) {
622
- message.warning('You need to provide file extension');
623
- return;
624
- }
625
- var validExts = ['pdf', 'dwf', 'dwfx'];
626
- if (!validExts.includes(fileExt.toLowerCase())) {
627
- message.warning('Only support pdf, dwf, dwfx format');
628
- return;
629
- }
630
- Autodesk.Viewing.Initializer({
631
- env: 'Local'
632
- }, /*#__PURE__*/_asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee() {
633
- var viewerDiv, viewer, isDWF, handleLoadSuccess, xhr;
634
- return _regenerator().w(function (_context) {
635
- while (1) switch (_context.n) {
1912
+ if (status !== 'ready' || !window.Autodesk || !filePath || !fileExt) return;
1913
+ var initializeViewer = /*#__PURE__*/function () {
1914
+ var _ref2 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee2() {
1915
+ var Autodesk;
1916
+ return _regenerator().w(function (_context2) {
1917
+ while (1) switch (_context2.p = _context2.n) {
636
1918
  case 0:
637
- registerToolbarExtension(Autodesk);
638
- viewerDiv = document.getElementById('forgeViewerPDF');
639
- viewerDiv.innerHTML = '';
640
- viewer = new Autodesk.Viewing.GuiViewer3D(viewerDiv);
641
- viewer.start();
642
- viewer.loadExtension('Autodesk.DocumentBrowser');
643
- viewer.loadExtension('Autodesk.Viewing.MarkupsCore');
644
- viewer.loadExtension('Autodesk.Viewing.MarkupsGui');
645
- viewer.loadExtension('ToolbarExtension', {
646
- filePath: filePath
647
- });
648
- viewer.addEventListener(Autodesk.Viewing.GEOMETRY_LOADED_EVENT, function (e) {
649
- e.target.loadExtension('Autodesk.Viewing.MarkupsCore');
650
- e.target.loadExtension('Autodesk.Viewing.MarkupsGui');
651
- });
652
- isDWF = fileExt.toLowerCase() === 'pdf' ? 'Autodesk.PDF' : 'Autodesk.DWF';
653
- _context.n = 1;
654
- return viewer.loadExtension(isDWF);
1919
+ _context2.p = 0;
1920
+ Autodesk = window.Autodesk; // Validate file extension
1921
+ if (SUPPORTED_FILE_EXTENSIONS.includes(fileExt.toLowerCase())) {
1922
+ _context2.n = 1;
1923
+ break;
1924
+ }
1925
+ message.warning('Only support pdf, dwf, dwfx format');
1926
+ return _context2.a(2);
655
1927
  case 1:
656
- handleLoadSuccess = function handleLoadSuccess(e) {
657
- try {
658
- viewer.setReverseZoomDirection(true);
659
- var root = e.getDocumentNode().getRootNode();
660
- var view3d = root.search({
661
- type: 'geometry',
662
- role: '3d',
663
- progress: 'complete'
664
- }, true);
665
- var view2d = root.search({
666
- type: 'geometry',
667
- role: '2d',
668
- progress: 'complete'
669
- }, true);
670
- var viewables = view3d.concat(view2d);
671
- var toolbarExt = viewer.getExtension('ToolbarExtension');
672
- if (toolbarExt && typeof toolbarExt.setViewables === 'function') {
673
- toolbarExt.setViewables(viewables);
674
- }
675
- if (viewer.toolbar) {
676
- viewer.toolbar.setVisible(true);
677
- }
678
- if (viewables.length > 1) {
679
- // Auto-open Document Browser by clicking the original button
680
- setTimeout(function () {
681
- var originalDocBtn = document.getElementById('toolbar-documentModels');
682
- if (originalDocBtn && !originalDocBtn.classList.contains('active')) {
683
- originalDocBtn.click();
684
- }
685
-
686
- // Apply custom styling to the panel
687
- var documentBrowser = viewer.getExtension('Autodesk.DocumentBrowser');
688
- if (documentBrowser && documentBrowser.ui && documentBrowser.ui.panel) {
689
- documentBrowser.ui.panel.container.style.top = 0;
690
- documentBrowser.ui.panel.container.style.left = 'unset';
691
- documentBrowser.ui.panel.container.style.right = '0px';
692
- documentBrowser.ui.panel.container.style.width = '200px';
1928
+ // Initialize Forge Viewer
1929
+ Autodesk.Viewing.Initializer({
1930
+ env: 'Local'
1931
+ }, /*#__PURE__*/_asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee() {
1932
+ var viewerDiv, viewer, fileLoader;
1933
+ return _regenerator().w(function (_context) {
1934
+ while (1) switch (_context.n) {
1935
+ case 0:
1936
+ // Register custom toolbar extension
1937
+ registerToolbarExtension(Autodesk);
1938
+ // Create viewer instance
1939
+ viewerDiv = document.getElementById('forgeViewerPDF');
1940
+ if (viewerDiv) {
1941
+ _context.n = 1;
1942
+ break;
693
1943
  }
694
-
695
- // Switch to Thumbnail tab after a short delay
696
- setTimeout(function () {
697
- // Try multiple selectors to find the thumbnail tab button
698
- var thumbnailTab = document.querySelector('.docking-panel-thumbnail-view') || document.querySelector('[data-i18n="Thumbnails"]') || Array.from(document.querySelectorAll('.adsk-control-group .adsk-button')).find(function (btn) {
699
- return btn.textContent.includes('Thumbnails') || btn.title.includes('Thumbnails');
700
- });
701
- if (thumbnailTab && !thumbnailTab.classList.contains('active')) {
702
- thumbnailTab.click();
1944
+ return _context.a(2);
1945
+ case 1:
1946
+ viewerDiv.innerHTML = '';
1947
+ viewer = new Autodesk.Viewing.GuiViewer3D(viewerDiv);
1948
+ viewer.start();
1949
+ // Load required extensions
1950
+ _context.n = 2;
1951
+ return viewer.loadExtension('Autodesk.DocumentBrowser');
1952
+ case 2:
1953
+ _context.n = 3;
1954
+ return viewer.loadExtension('Autodesk.Viewing.MarkupsCore');
1955
+ case 3:
1956
+ _context.n = 4;
1957
+ return viewer.loadExtension('Autodesk.Viewing.MarkupsGui');
1958
+ case 4:
1959
+ _context.n = 5;
1960
+ return viewer.loadExtension('ToolbarExtension', {
1961
+ filePath: filePath
1962
+ });
1963
+ case 5:
1964
+ // Initialize FileLoader service
1965
+ fileLoader = new FileLoader(viewer); // Note: ToolbarExtension now subscribes to VIEWABLES_SET directly
1966
+ // No need to forward events here - prevents infinite recursion
1967
+ // Load the document using FileLoader service
1968
+ _context.n = 6;
1969
+ return fileLoader.loadFile(filePath, fileExt.toLowerCase(), {
1970
+ onSuccess: function onSuccess() {
1971
+ // Pass viewer instance to parent component
1972
+ if (setViewer) {
1973
+ setViewer(viewer);
1974
+ }
1975
+ },
1976
+ onError: function onError(error) {
1977
+ message.error('Failed to load document');
703
1978
  }
704
- }, 200);
705
- }, 500);
706
- }
707
-
708
- // Ensure Custom Pan tool is active by default
709
- setTimeout(function () {
710
- var customPanBtn = document.getElementById('custom-pan-btn');
711
- if (customPanBtn) {
712
- customPanBtn.click();
713
- // Double check visual state
714
- if (!customPanBtn.classList.contains('active')) {
715
- customPanBtn.classList.add('active');
716
- customPanBtn.classList.remove('inactive');
717
- }
718
- } else {
719
- var _viewer$toolbar;
720
- // Fallback if custom button not found immediately, try accessing via viewer toolbar
721
- var toolGroup = (_viewer$toolbar = viewer.toolbar) === null || _viewer$toolbar === void 0 ? void 0 : _viewer$toolbar.getControl('custom-tool-group');
722
- var panBtnConf = toolGroup === null || toolGroup === void 0 ? void 0 : toolGroup.getControl('custom-pan-btn');
723
- if (panBtnConf) {
724
- panBtnConf.setState(1); // ACTIVE
725
- }
726
- }
727
- }, 600);
728
- if (setViewer) setViewer(viewer);
729
- } catch (err) {
730
- console.error('Error in handleLoadSuccess:', err);
731
- }
732
- };
733
- if (isDWF === 'Autodesk.DWF') {
734
- xhr = new XMLHttpRequest();
735
- xhr.open('GET', filePath, true);
736
- xhr.responseType = 'blob';
737
- xhr.onload = function () {
738
- if (this.status === 200) {
739
- var myBlob = this.response;
740
- var url1 = window.URL.createObjectURL(myBlob);
741
- viewer.loadModel(url1 + '#.dwf', {}, handleLoadSuccess);
1979
+ });
1980
+ case 6:
1981
+ return _context.a(2);
742
1982
  }
743
- };
744
- xhr.send();
745
- } else {
746
- viewer.loadModel(filePath, {}, handleLoadSuccess);
747
- }
1983
+ }, _callee);
1984
+ })));
1985
+ _context2.n = 3;
1986
+ break;
748
1987
  case 2:
749
- return _context.a(2);
1988
+ _context2.p = 2;
1989
+ _context2.v;
1990
+ message.error('Failed to initialize viewer');
1991
+ case 3:
1992
+ return _context2.a(2);
750
1993
  }
751
- }, _callee);
752
- })));
753
- }
754
- }, [status, filePath, fileExt, setViewer]);
1994
+ }, _callee2, null, [[0, 2]]);
1995
+ }));
1996
+ return function initializeViewer() {
1997
+ return _ref2.apply(this, arguments);
1998
+ };
1999
+ }();
2000
+ initializeViewer();
2001
+ // No cleanup needed - ToolbarExtension handles its own subscriptions
2002
+ // eslint-disable-next-line react-hooks/exhaustive-deps
2003
+ }, [status, filePath, fileExt]);
755
2004
  return /*#__PURE__*/jsx("div", {
756
2005
  style: {
757
2006
  position: 'relative',