3dviewer-sdk 1.0.10 → 1.0.11

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.js CHANGED
@@ -1 +1,1249 @@
1
- export { Viewer3D } from "./viewer";
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ Viewer3D: () => Viewer3D
24
+ });
25
+ module.exports = __toCommonJS(index_exports);
26
+
27
+ // src/core/emitter.ts
28
+ var Emitter = class {
29
+ constructor() {
30
+ this.listeners = {};
31
+ }
32
+ on(event, cb) {
33
+ if (!this.listeners[event]) {
34
+ this.listeners[event] = [];
35
+ }
36
+ const arr = this.listeners[event];
37
+ arr.push(cb);
38
+ return () => this.off(event, cb);
39
+ }
40
+ off(event, cb) {
41
+ const arr = this.listeners[event];
42
+ if (!arr) return;
43
+ const idx = arr.indexOf(cb);
44
+ if (idx >= 0) arr.splice(idx, 1);
45
+ if (arr.length === 0) delete this.listeners[event];
46
+ }
47
+ emit(event, payload) {
48
+ var _a;
49
+ (_a = this.listeners[event]) == null ? void 0 : _a.forEach((cb) => cb(payload));
50
+ }
51
+ clear() {
52
+ this.listeners = {};
53
+ }
54
+ };
55
+
56
+ // src/modules/camera.module.ts
57
+ var CameraModule = class {
58
+ constructor(viewer) {
59
+ this.viewer = viewer;
60
+ this.on = {
61
+ home: (cb) => this.viewer._on("camera:home", cb)
62
+ };
63
+ }
64
+ zoomIn(percent) {
65
+ this.viewer.postToViewer("viewer-zoom" /* ZOOM */, { action: "in", percent });
66
+ }
67
+ zoomOut(percent) {
68
+ this.viewer.postToViewer("viewer-zoom" /* ZOOM */, { action: "out", percent });
69
+ }
70
+ home() {
71
+ this.viewer.postToViewer("viewer-home" /* HOME */, {});
72
+ }
73
+ };
74
+
75
+ // src/modules/interaction.module.ts
76
+ var InteractionModule = class {
77
+ constructor(viewer) {
78
+ this.viewer = viewer;
79
+ this.on = {
80
+ panChange: (cb) => this.viewer._on("interaction:pan-change", cb)
81
+ };
82
+ }
83
+ enablePan() {
84
+ this.viewer.postToViewer("viewer-pan-toggle" /* PAN_TOGGLE */, { enabled: true });
85
+ }
86
+ disablePan() {
87
+ this.viewer.postToViewer("viewer-pan-toggle" /* PAN_TOGGLE */, { enabled: false });
88
+ }
89
+ select() {
90
+ this.viewer.postToViewer("viewer-select" /* SELECT */);
91
+ }
92
+ areaSelect() {
93
+ this.viewer.postToViewer("viewer-area-select" /* AREA_SELECT */);
94
+ }
95
+ orbit() {
96
+ this.viewer.postToViewer("viewer-orbit" /* ORBIT */);
97
+ }
98
+ rotateZ() {
99
+ this.viewer.postToViewer("viewer-rotate-z" /* ROTATE_Z */);
100
+ }
101
+ walkThrough() {
102
+ this.viewer.postToViewer("viewer-walk-through" /* WALK_THROUGH */);
103
+ }
104
+ zoomWindow() {
105
+ this.viewer.postToViewer("viewer-zoom-window" /* ZOOM_WINDOW */);
106
+ }
107
+ zoomFit() {
108
+ this.viewer.postToViewer("viewer-zoom-fit" /* ZOOM_FIT */);
109
+ }
110
+ drawModeShaded() {
111
+ this.setDrawMode("shaded");
112
+ }
113
+ drawModeWireframe() {
114
+ this.setDrawMode("wireframe");
115
+ }
116
+ drawModeHiddenLine() {
117
+ this.setDrawMode("hidden-line");
118
+ }
119
+ drawModeShadedWire() {
120
+ this.setDrawMode("shaded-wire");
121
+ }
122
+ drawModeXRay() {
123
+ this.setDrawMode("xray");
124
+ }
125
+ drawModeGhosting() {
126
+ this.setDrawMode("ghosting");
127
+ }
128
+ explode(magnitude) {
129
+ this.viewer.postToViewer("viewer-explode" /* EXPLODE */, { magnitude });
130
+ }
131
+ explodeOff() {
132
+ this.explode(0);
133
+ }
134
+ setDrawMode(mode) {
135
+ this.viewer.postToViewer("viewer-draw-mode" /* DRAW_MODE */, { mode });
136
+ }
137
+ };
138
+
139
+ // src/modules/node.module.ts
140
+ var NodeModule = class {
141
+ constructor(viewer) {
142
+ this.viewer = viewer;
143
+ this.on = { select: (cb) => this.viewer._on("node:select", cb) };
144
+ }
145
+ };
146
+
147
+ // src/modules/files.module.ts
148
+ var DEFAULT_API_BASE_URL = "https://dev.3dviewer.anybim.vn";
149
+ var DEFAULT_VIEWER_ORIGIN = "http://localhost:3000";
150
+ var FilesModule = class {
151
+ constructor(viewer) {
152
+ this.viewer = viewer;
153
+ this.config = {};
154
+ this.operationStartTime = 0;
155
+ this.state = {
156
+ isLoading: false,
157
+ stage: "idle"
158
+ };
159
+ this.lastUploadSession = null;
160
+ this.on = {
161
+ state: (cb) => this.viewer._on("files:state", cb),
162
+ uploadStart: (cb) => this.viewer._on("files:upload:start", cb),
163
+ uploadSuccess: (cb) => this.viewer._on("files:upload:success", cb),
164
+ uploadError: (cb) => this.viewer._on("files:upload:error", cb),
165
+ conversionStart: (cb) => this.viewer._on("files:conversion:start", cb),
166
+ conversionSuccess: (cb) => this.viewer._on("files:conversion:success", cb),
167
+ conversionError: (cb) => this.viewer._on("files:conversion:error", cb),
168
+ renderStart: (cb) => this.viewer._on("files:render:start", cb),
169
+ renderSuccess: (cb) => this.viewer._on("files:render:success", cb),
170
+ renderError: (cb) => this.viewer._on("files:render:error", cb),
171
+ loadSuccess: (cb) => this.viewer._on("files:load:success", cb),
172
+ loadError: (cb) => this.viewer._on("files:load:error", cb)
173
+ };
174
+ }
175
+ // Merge file-pipeline runtime config.
176
+ setConfig(next) {
177
+ this.config = { ...this.config, ...next };
178
+ }
179
+ // Return a snapshot of current loading state.
180
+ getState() {
181
+ return { ...this.state };
182
+ }
183
+ // ---------- public pipeline ----------
184
+ // Upload file to conversion server and keep generated baseFileId in session.
185
+ async upload(file) {
186
+ const target = this.resolveFile(file);
187
+ return this.withOperation({ stage: "uploading", message: "Uploading file..." }, async () => {
188
+ var _a;
189
+ this.viewer._emit("files:upload:start", { fileName: target.name });
190
+ await this.uploadInternal(target);
191
+ const baseFileId = ((_a = this.getUploadSessionForFile(target)) == null ? void 0 : _a.baseFileId) || "";
192
+ this.viewer._emit("files:upload:success", { fileName: target.name, baseFileId });
193
+ return { fileName: target.name, baseFileId };
194
+ });
195
+ }
196
+ // Trigger conversion flow and resolve final viewer metadata.
197
+ async convert(file, options = {}) {
198
+ const target = this.resolveFile(file);
199
+ return this.withOperation({ stage: "converting", message: "Converting file..." }, async () => {
200
+ this.viewer._emit("files:conversion:start", { fileName: target.name });
201
+ try {
202
+ const prepared = await this.convertInternal(target, options);
203
+ this.viewer._emit("files:conversion:success", prepared);
204
+ return prepared;
205
+ } catch (e) {
206
+ this.viewer._emit("files:conversion:error", { fileName: target.name, error: this.toErrorMessage(e) });
207
+ throw e;
208
+ }
209
+ });
210
+ }
211
+ // Convenience API: upload first, then convert in one call.
212
+ async prepare(file, options = {}) {
213
+ const target = this.resolveFile(file);
214
+ return this.withOperation({ stage: "uploading", message: "Preparing file..." }, async () => {
215
+ await this.uploadInternal(target);
216
+ const prepared = await this.convertInternal(target, options);
217
+ return prepared;
218
+ });
219
+ }
220
+ // Trigger the newer downloadUrl-based conversion flow.
221
+ async convertV2(options) {
222
+ return this.withOperation({ stage: "converting", message: "Converting file..." }, async () => {
223
+ this.viewer._emit("files:conversion:start", { fileName: options.filename });
224
+ try {
225
+ const prepared = await this.convertV2Internal(options);
226
+ this.viewer._emit("files:conversion:success", prepared);
227
+ return prepared;
228
+ } catch (e) {
229
+ this.viewer._emit("files:conversion:error", { fileName: options.filename, error: this.toErrorMessage(e) });
230
+ throw e;
231
+ }
232
+ });
233
+ }
234
+ // Check stream file info by one or more baseFileId values.
235
+ async checkFileInfo(baseFileIds) {
236
+ const payload = this.buildFileInfoPayload(baseFileIds);
237
+ const hostConversion = await this.resolveHostConversion();
238
+ const url = `${hostConversion}/api/StreamFile/info`;
239
+ const response = await fetch(url, {
240
+ method: "POST",
241
+ headers: {
242
+ "Content-Type": "application/json",
243
+ Accept: "application/json"
244
+ },
245
+ body: JSON.stringify(payload)
246
+ });
247
+ if (!response.ok) {
248
+ throw new Error(`Check file info failed (${response.status} ${response.statusText})`);
249
+ }
250
+ const text = await response.text();
251
+ if (!text) return null;
252
+ try {
253
+ return JSON.parse(text);
254
+ } catch {
255
+ return text;
256
+ }
257
+ }
258
+ // Open iframe with an already prepared viewer URL.
259
+ open(input) {
260
+ const url = input.url;
261
+ this.viewer._emit("files:render:start", { url });
262
+ try {
263
+ this.viewer.open(url);
264
+ this.viewer._emit("files:render:success", { url });
265
+ } catch (e) {
266
+ this.viewer._emit("files:render:error", { url, error: this.toErrorMessage(e) });
267
+ throw e;
268
+ }
269
+ }
270
+ // Full pipeline: upload + convert + open iframe.
271
+ async render(file, options = {}) {
272
+ const target = this.resolveFile(file);
273
+ return this.withOperation({ stage: "uploading", message: "Uploading + converting + opening..." }, async () => {
274
+ await this.upload(target);
275
+ const prepared = await this.convert(target, options);
276
+ this.updateState({ stage: "rendering", message: "Opening viewer..." });
277
+ this.open(prepared);
278
+ this.viewer._emit("files:load:success", prepared);
279
+ return prepared;
280
+ });
281
+ }
282
+ // Resolve file argument, fallback to options.file, and persist it back.
283
+ resolveFile(file) {
284
+ const optFile = this.viewer.getOptions().file;
285
+ const target = file || optFile;
286
+ if (!target) throw new Error("No file provided. Pass a File or set options.file");
287
+ this.viewer.patchOptions({ file: target });
288
+ return target;
289
+ }
290
+ // Trim input URL and remove trailing slash.
291
+ normalizeBaseUrl(input) {
292
+ return input.trim().replace(/\/+$/, "");
293
+ }
294
+ // Resolve API base URL with default fallback.
295
+ resolveBaseUrl() {
296
+ const raw = this.config.baseUrl || this.viewer.getOptions().baseUrl || DEFAULT_API_BASE_URL;
297
+ return this.normalizeBaseUrl(raw);
298
+ }
299
+ // Resolve viewer route path (e.g. /mainviewer).
300
+ resolveViewerPath() {
301
+ const configuredPath = this.config.viewerPath || this.viewer.getOptions().viewerPath;
302
+ if (!configuredPath) {
303
+ const viewerUrl = this.viewer.getOptions().url;
304
+ if (viewerUrl) {
305
+ try {
306
+ const pathname = new URL(viewerUrl, window.location.href).pathname;
307
+ if (pathname && pathname !== "/") return pathname;
308
+ } catch {
309
+ }
310
+ }
311
+ }
312
+ const p = (configuredPath || "/mainviewer").trim();
313
+ if (!p) return "/mainviewer";
314
+ return p.startsWith("/") ? p : `/${p}`;
315
+ }
316
+ // Viewer host used to open iframe after conversion completes.
317
+ resolveViewerOrigin() {
318
+ const configuredBaseUrl = this.config.baseUrl || this.viewer.getOptions().baseUrl;
319
+ if (configuredBaseUrl) {
320
+ try {
321
+ return this.normalizeBaseUrl(new URL(configuredBaseUrl, window.location.href).origin);
322
+ } catch {
323
+ }
324
+ }
325
+ const viewerUrl = this.viewer.getOptions().url;
326
+ if (viewerUrl) {
327
+ try {
328
+ return this.normalizeBaseUrl(new URL(viewerUrl, window.location.href).origin);
329
+ } catch {
330
+ }
331
+ }
332
+ return this.normalizeBaseUrl(DEFAULT_VIEWER_ORIGIN);
333
+ }
334
+ // Build conversion service root from API base URL.
335
+ resolveHostConversion() {
336
+ const baseUrl = this.resolveBaseUrl();
337
+ return baseUrl.endsWith("/service/conversion") ? baseUrl : `${baseUrl}/service/conversion`;
338
+ }
339
+ // Resolve upload path sent to conversion APIs.
340
+ getUploadPath() {
341
+ return this.config.uploadPath || this.viewer.getOptions().uploadPath || ".";
342
+ }
343
+ // Build a stable in-memory signature to identify same file uploads.
344
+ fileSignature(file) {
345
+ return `${file.name}::${file.size}::${file.lastModified}`;
346
+ }
347
+ // Create a UUID-like baseFileId when caller does not provide one.
348
+ createBaseFileId() {
349
+ if (typeof crypto !== "undefined" && "randomUUID" in crypto) return crypto.randomUUID();
350
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
351
+ const r = Math.floor(Math.random() * 16);
352
+ const v = c === "x" ? r : r & 3 | 8;
353
+ return v.toString(16);
354
+ });
355
+ }
356
+ // Create upload session metadata reused between upload and convert.
357
+ createUploadSession(file) {
358
+ return {
359
+ signature: this.fileSignature(file),
360
+ baseFileId: this.createBaseFileId(),
361
+ fileName: file.name,
362
+ uploadPath: this.getUploadPath()
363
+ };
364
+ }
365
+ // Return previous upload session for same file and upload path.
366
+ getUploadSessionForFile(file) {
367
+ if (!this.lastUploadSession) return null;
368
+ const sameFile = this.lastUploadSession.signature === this.fileSignature(file);
369
+ const samePath = this.lastUploadSession.uploadPath === this.getUploadPath();
370
+ return sameFile && samePath ? this.lastUploadSession : null;
371
+ }
372
+ // Call upload endpoint and persist upload session on success.
373
+ async uploadInternal(file) {
374
+ this.updateState({ stage: "uploading", message: "Uploading file..." });
375
+ try {
376
+ const existing = this.getUploadSessionForFile(file);
377
+ const session = existing || this.createUploadSession(file);
378
+ const hostConversion = this.resolveHostConversion();
379
+ const path = this.getUploadPath();
380
+ const url = `${hostConversion}/api/File/upload?path=${encodeURIComponent(path)}`;
381
+ const formData = new FormData();
382
+ formData.append("file", file, file.name);
383
+ const res = await fetch(url, { method: "POST", body: formData, headers: { Accept: "text/plain" } });
384
+ if (!res.ok) throw new Error(`Upload failed (${res.status} ${res.statusText})`);
385
+ this.lastUploadSession = session;
386
+ } catch (e) {
387
+ const msg = this.toErrorMessage(e);
388
+ this.viewer._emit("files:upload:error", { fileName: file.name, error: msg });
389
+ throw e;
390
+ }
391
+ }
392
+ // Build StreamFile payload compatible with conversion service.
393
+ buildCachePayload(file, baseFileId, options = {}) {
394
+ const createdDate = (/* @__PURE__ */ new Date()).toISOString();
395
+ return {
396
+ filename: file.name,
397
+ ...options.downloadUrl ? { downloadUrl: options.downloadUrl } : {},
398
+ baseFileId,
399
+ baseMajorRev: 0,
400
+ baseMinorRev: 0,
401
+ isChecked: false,
402
+ status: { size: file.size },
403
+ child: [],
404
+ isDirectory: false,
405
+ createdDate,
406
+ cacheStatus: 0,
407
+ modelFileId: "",
408
+ id: "",
409
+ originalFilePath: this.getUploadPath(),
410
+ streamLocation: null,
411
+ converter: "Hoops",
412
+ originalSize: 0,
413
+ cacheSize: 0,
414
+ importTime: 0,
415
+ importAssemblyTreeTime: 0,
416
+ creator: {
417
+ id: "00000000-0000-0000-0000-000000000000",
418
+ name: "Anonymous"
419
+ },
420
+ originalFile: file.name,
421
+ multiStream: false,
422
+ isRootModel: 0,
423
+ extraConvertOutput: "",
424
+ cacheFilename: null,
425
+ errorMassage: null,
426
+ convertOptions: {
427
+ convert3DModel: 1,
428
+ convert2DSheet: 1,
429
+ extractProperties: 1,
430
+ childModels: 0
431
+ },
432
+ drawingConvertStatus: {
433
+ convert3DModel: 5,
434
+ convert2DSheet: 5,
435
+ extractProperties: 5
436
+ },
437
+ attemptedConvertTimes: 0
438
+ };
439
+ }
440
+ // Build payload for POST /api/StreamFile/convert.
441
+ buildConvertV2Payload(options) {
442
+ var _a, _b;
443
+ const convertOptions = {
444
+ convert3DModel: 1,
445
+ convert2DSheet: 1,
446
+ extractProperties: 1,
447
+ childModels: 0,
448
+ ...options.convertOptions
449
+ };
450
+ return {
451
+ filename: options.filename,
452
+ originalFilePath: options.originalFilePath,
453
+ convertOptions,
454
+ downloadUrl: options.downloadUrl,
455
+ baseFileId: options.baseFileId,
456
+ baseMajorRev: (_a = options.baseMajorRev) != null ? _a : 0,
457
+ baseMinorRev: (_b = options.baseMinorRev) != null ? _b : 0
458
+ };
459
+ }
460
+ buildFileInfoPayload(baseFileIds) {
461
+ const ids = (Array.isArray(baseFileIds) ? baseFileIds : [baseFileIds]).map((baseFileId) => String(baseFileId).trim()).filter(Boolean);
462
+ if (ids.length === 0) {
463
+ throw new Error("No baseFileId provided");
464
+ }
465
+ return ids.map((baseFileId) => ({ baseFileId }));
466
+ }
467
+ // Submit conversion/caching request and return service response.
468
+ async cacheFile(file, baseFileId, options = {}) {
469
+ const hostConversion = await this.resolveHostConversion();
470
+ const url = `${hostConversion}/api/StreamFile?overwrite=true&ignore_line_weight=1`;
471
+ const payload = this.buildCachePayload(file, baseFileId, options);
472
+ const response = await fetch(url, {
473
+ method: "POST",
474
+ headers: {
475
+ "Content-Type": "application/json",
476
+ Accept: "application/json"
477
+ },
478
+ body: JSON.stringify(payload)
479
+ });
480
+ if (!response.ok) {
481
+ throw new Error(
482
+ `Cache/convert failed (${response.status} ${response.statusText})`
483
+ );
484
+ }
485
+ return await response.json();
486
+ }
487
+ // Submit conversion request to the newer downloadUrl-based endpoint.
488
+ async cacheFileV2(options) {
489
+ const hostConversion = await this.resolveHostConversion();
490
+ const params = new URLSearchParams();
491
+ if (typeof options.overwrite === "boolean") {
492
+ params.set("overwrite", String(options.overwrite));
493
+ }
494
+ if (options.project) {
495
+ params.set("project", options.project);
496
+ }
497
+ const query = params.toString();
498
+ const url = `${hostConversion}/api/StreamFile/convert${query ? `?${query}` : ""}`;
499
+ const payload = this.buildConvertV2Payload(options);
500
+ const response = await fetch(url, {
501
+ method: "POST",
502
+ headers: {
503
+ "Content-Type": "application/json",
504
+ Accept: "application/json"
505
+ },
506
+ body: JSON.stringify(payload)
507
+ });
508
+ if (!response.ok) {
509
+ throw new Error(
510
+ `Cache/convert v2 failed (${response.status} ${response.statusText})`
511
+ );
512
+ }
513
+ return await response.json();
514
+ }
515
+ // Convert file and generate final iframe URL with query string.
516
+ async convertInternal(file, options = {}) {
517
+ var _a, _b, _c, _d;
518
+ this.updateState({ stage: "converting", message: "Converting file..." });
519
+ const uploadSession = this.getUploadSessionForFile(file) || this.createUploadSession(file);
520
+ const seedBaseFileId = uploadSession.baseFileId;
521
+ const cacheResult = await this.cacheFile(file, seedBaseFileId, options);
522
+ const baseFileId = (_a = cacheResult.baseFileId) != null ? _a : seedBaseFileId;
523
+ const baseMajorRev = (_b = cacheResult.baseMajorRev) != null ? _b : 0;
524
+ const baseMinorRev = (_c = cacheResult.baseMinorRev) != null ? _c : 0;
525
+ const fileName = cacheResult.filename || cacheResult.fileName || file.name;
526
+ const cacheListItem = { baseFileId, baseMajorRev, baseMinorRev, fileName };
527
+ if (cacheResult.cacheStatus !== 2) {
528
+ throw new Error(`Conversion not ready after first request (cacheStatus=${(_d = cacheResult.cacheStatus) != null ? _d : "unknown"})`);
529
+ }
530
+ const query = new URLSearchParams({ fileList: JSON.stringify([cacheListItem]) }).toString();
531
+ const viewerBase = this.resolveViewerOrigin();
532
+ const viewerPath = this.resolveViewerPath();
533
+ const url = `${viewerBase}${viewerPath}?${query}`;
534
+ return { baseFileId, baseMajorRev, baseMinorRev, fileName, query, url };
535
+ }
536
+ async convertV2Internal(options) {
537
+ var _a, _b, _c, _d, _e;
538
+ this.updateState({ stage: "converting", message: "Converting file..." });
539
+ const cacheResult = await this.cacheFileV2(options);
540
+ const baseFileId = (_a = cacheResult.baseFileId) != null ? _a : options.baseFileId;
541
+ const baseMajorRev = (_c = (_b = cacheResult.baseMajorRev) != null ? _b : options.baseMajorRev) != null ? _c : 0;
542
+ const baseMinorRev = (_e = (_d = cacheResult.baseMinorRev) != null ? _d : options.baseMinorRev) != null ? _e : 0;
543
+ const fileName = cacheResult.filename || cacheResult.fileName || options.filename;
544
+ if (cacheResult.cacheStatus !== void 0 && cacheResult.cacheStatus !== 2) {
545
+ throw new Error(`Conversion not ready after v2 request (cacheStatus=${cacheResult.cacheStatus})`);
546
+ }
547
+ const cacheListItem = { baseFileId, baseMajorRev, baseMinorRev, fileName };
548
+ const query = new URLSearchParams({ fileList: JSON.stringify([cacheListItem]) }).toString();
549
+ const viewerBase = this.resolveViewerOrigin();
550
+ const viewerPath = this.resolveViewerPath();
551
+ const url = `${viewerBase}${viewerPath}?${query}`;
552
+ return { baseFileId, baseMajorRev, baseMinorRev, fileName, query, url };
553
+ }
554
+ // Update internal loading state and emit state event.
555
+ updateState(next) {
556
+ const elapsedMs = this.operationStartTime > 0 ? Date.now() - this.operationStartTime : 0;
557
+ this.state = { ...this.state, ...next, elapsedMs };
558
+ this.viewer._emit("files:state", this.state);
559
+ }
560
+ // Shared wrapper to handle loading state lifecycle and top-level errors.
561
+ async withOperation(initial, run) {
562
+ this.operationStartTime = Date.now();
563
+ this.updateState({
564
+ isLoading: true,
565
+ stage: initial.stage,
566
+ message: initial.message
567
+ });
568
+ try {
569
+ const result = await run();
570
+ this.updateState({ isLoading: false, stage: "completed", message: "Completed" });
571
+ return result;
572
+ } catch (e) {
573
+ const msg = this.toErrorMessage(e);
574
+ this.updateState({ isLoading: false, stage: "error", message: msg });
575
+ this.viewer._emit("files:load:error", { error: msg });
576
+ throw e;
577
+ }
578
+ }
579
+ // Normalize unknown error shape into displayable message.
580
+ toErrorMessage(e) {
581
+ return e instanceof Error ? e.message : String(e);
582
+ }
583
+ };
584
+
585
+ // src/modules/toolbar.module.ts
586
+ function createRequestId() {
587
+ return `sheets_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
588
+ }
589
+ var ALL_3D_TOOLBAR_OPERATORS = [
590
+ "home",
591
+ "select",
592
+ "areaSelect",
593
+ "pan",
594
+ "zoomIn",
595
+ "zoomOut",
596
+ "zoomWindow",
597
+ "zoomFit",
598
+ "orbit",
599
+ "rotateZ",
600
+ "walkThrough",
601
+ "drawMode-shaded",
602
+ "drawMode-wireframe",
603
+ "drawMode-shaded-wire",
604
+ "drawMode-hidden-line",
605
+ "drawMode-xray",
606
+ "drawMode-ghosting",
607
+ "cutting-plane",
608
+ "clipping-commands",
609
+ "explode",
610
+ "setting",
611
+ "propertyPanel",
612
+ "model-tree",
613
+ "linkedObjects",
614
+ "statesObjects",
615
+ "synchronized"
616
+ ];
617
+ var ALL_PDF_TOOLBAR_OPERATORS = [
618
+ "home",
619
+ "select",
620
+ "pan",
621
+ "zoomIn",
622
+ "zoomOut",
623
+ "zoomWindow",
624
+ "zoomFit",
625
+ "rotateZ",
626
+ "save",
627
+ "setting",
628
+ "plan-mode",
629
+ "document-mode",
630
+ "first-page",
631
+ "previous-page",
632
+ "next-page",
633
+ "last-page",
634
+ "current-page"
635
+ ];
636
+ var ToolbarModule = class {
637
+ constructor(viewer) {
638
+ this.viewer = viewer;
639
+ this.on = {
640
+ planMode: (cb) => this.viewer._on("toolbar:pdf-plan-mode", cb),
641
+ documentMode: (cb) => this.viewer._on("toolbar:pdf-document-mode", cb),
642
+ firstPage: (cb) => this.viewer._on("toolbar:pdf-first-page", cb),
643
+ previousPage: (cb) => this.viewer._on("toolbar:pdf-previous-page", cb),
644
+ nextPage: (cb) => this.viewer._on("toolbar:pdf-next-page", cb),
645
+ lastPage: (cb) => this.viewer._on("toolbar:pdf-last-page", cb),
646
+ currentPage: (cb) => this.viewer._on("toolbar:pdf-current-page", cb)
647
+ };
648
+ }
649
+ setDisabled3D(operators) {
650
+ this.postConfig({ format: "3d", mode: "set", operators });
651
+ }
652
+ setDisabledPdf(operators) {
653
+ this.postConfig({ format: "pdf", mode: "set", operators });
654
+ }
655
+ clearDisabled3D() {
656
+ this.postConfig({ format: "3d", mode: "clear" });
657
+ }
658
+ clearDisabledPdf() {
659
+ this.postConfig({ format: "pdf", mode: "clear" });
660
+ }
661
+ disableAll3D() {
662
+ this.setDisabled3D(ALL_3D_TOOLBAR_OPERATORS);
663
+ }
664
+ disableAllPdf() {
665
+ this.setDisabledPdf(ALL_PDF_TOOLBAR_OPERATORS);
666
+ }
667
+ enableAll3D() {
668
+ this.clearDisabled3D();
669
+ }
670
+ enableAllPdf() {
671
+ this.clearDisabledPdf();
672
+ }
673
+ hideToolbar() {
674
+ this.setToolbarVisible(false);
675
+ }
676
+ showToolbar() {
677
+ this.setToolbarVisible(true);
678
+ }
679
+ setToolbarVisible(visible, target = "all") {
680
+ this.postToolbarVisibility({ visible, target });
681
+ }
682
+ hideLeftToolbar() {
683
+ this.setToolbarVisible(false, "left");
684
+ }
685
+ showLeftToolbar() {
686
+ this.setToolbarVisible(true, "left");
687
+ }
688
+ hideCenterToolbar() {
689
+ this.setToolbarVisible(false, "center");
690
+ }
691
+ showCenterToolbar() {
692
+ this.setToolbarVisible(true, "center");
693
+ }
694
+ hideRightToolbar() {
695
+ this.setToolbarVisible(false, "right");
696
+ }
697
+ showRightToolbar() {
698
+ this.setToolbarVisible(true, "right");
699
+ }
700
+ openClippingPlanes() {
701
+ this.postPanelOpen({ panel: "clipping-commands", format: "3d" });
702
+ }
703
+ closeClippingPlanes() {
704
+ this.postPanelClose({ panel: "clipping-commands", format: "3d" });
705
+ }
706
+ openSetting() {
707
+ this.postPanelOpen({ panel: "setting" });
708
+ }
709
+ closeSetting() {
710
+ this.postPanelClose({ panel: "setting" });
711
+ }
712
+ openSetting3D() {
713
+ this.postPanelOpen({ panel: "setting", format: "3d" });
714
+ }
715
+ closeSetting3D() {
716
+ this.postPanelClose({ panel: "setting", format: "3d" });
717
+ }
718
+ openSettingPdf() {
719
+ this.postPanelOpen({ panel: "setting", format: "pdf" });
720
+ }
721
+ closeSettingPdf() {
722
+ this.postPanelClose({ panel: "setting", format: "pdf" });
723
+ }
724
+ openStatesObjects() {
725
+ this.postPanelOpen({ panel: "statesObjects", format: "3d" });
726
+ }
727
+ closeStatesObjects() {
728
+ this.postPanelClose({ panel: "statesObjects", format: "3d" });
729
+ }
730
+ openLinkedObjects() {
731
+ this.postPanelOpen({ panel: "linkedObjects", format: "3d" });
732
+ }
733
+ closeLinkedObjects() {
734
+ this.postPanelClose({ panel: "linkedObjects", format: "3d" });
735
+ }
736
+ openModelTree() {
737
+ this.postPanelOpen({ panel: "model-tree", format: "3d" });
738
+ }
739
+ closeModelTree() {
740
+ this.postPanelClose({ panel: "model-tree", format: "3d" });
741
+ }
742
+ openObjectProperties() {
743
+ this.postPanelOpen({ panel: "object-properties", format: "3d" });
744
+ }
745
+ closeObjectProperties() {
746
+ this.postPanelClose({ panel: "object-properties", format: "3d" });
747
+ }
748
+ openSheets() {
749
+ this.postPanelOpen({ panel: "sheets", format: "3d" });
750
+ }
751
+ closeSheets() {
752
+ this.postPanelClose({ panel: "sheets", format: "3d" });
753
+ }
754
+ getSheets(options) {
755
+ var _a;
756
+ const requestId = createRequestId();
757
+ const timeoutMs = Math.max(1e3, (_a = options == null ? void 0 : options.timeoutMs) != null ? _a : 1e4);
758
+ return new Promise((resolve, reject) => {
759
+ const timer = setTimeout(() => {
760
+ off();
761
+ reject(new Error("Timeout while getting sheets list from viewer"));
762
+ }, timeoutMs);
763
+ const off = this.viewer._on("sheets:list", (payload) => {
764
+ if (payload.requestId !== requestId) return;
765
+ clearTimeout(timer);
766
+ off();
767
+ resolve(payload.sheets);
768
+ });
769
+ this.postSheetsGetList({ requestId });
770
+ });
771
+ }
772
+ applySheet(sheetId) {
773
+ this.postSheetsApply({ sheetId });
774
+ }
775
+ cuttingCloseSections() {
776
+ this.postCuttingAction({ action: "close" });
777
+ }
778
+ cuttingMultipleSides() {
779
+ this.postCuttingAction({ action: "multi" });
780
+ }
781
+ cuttingToggleSelection() {
782
+ this.postCuttingAction({ action: "toggle-section" });
783
+ }
784
+ cuttingTogglePlanes() {
785
+ this.postCuttingAction({ action: "toggle-plane" });
786
+ }
787
+ cuttingPlaneX() {
788
+ this.postCuttingAction({ action: "plane-x" });
789
+ }
790
+ cuttingPlaneY() {
791
+ this.postCuttingAction({ action: "plane-y" });
792
+ }
793
+ cuttingPlaneZ() {
794
+ this.postCuttingAction({ action: "plane-z" });
795
+ }
796
+ cuttingPlaneBox() {
797
+ this.postCuttingAction({ action: "plane-box" });
798
+ }
799
+ cuttingRotateBox() {
800
+ this.postCuttingAction({ action: "rotate-box" });
801
+ }
802
+ cuttingReversePlaneX() {
803
+ this.postCuttingAction({ action: "reverse-plane-x" });
804
+ }
805
+ cuttingReversePlaneY() {
806
+ this.postCuttingAction({ action: "reverse-plane-y" });
807
+ }
808
+ cuttingReversePlaneZ() {
809
+ this.postCuttingAction({ action: "reverse-plane-z" });
810
+ }
811
+ postConfig(payload) {
812
+ this.viewer.postToViewer("viewer-toolbar-config" /* TOOLBAR_CONFIG */, payload);
813
+ }
814
+ postToolbarVisibility(payload) {
815
+ this.viewer.postToViewer("viewer-toolbar-visibility" /* TOOLBAR_VISIBILITY */, payload);
816
+ }
817
+ postPanelOpen(payload) {
818
+ this.viewer.postToViewer("viewer-panel-open" /* PANEL_OPEN */, payload);
819
+ }
820
+ postPanelClose(payload) {
821
+ this.viewer.postToViewer("viewer-panel-close" /* PANEL_CLOSE */, payload);
822
+ }
823
+ postCuttingAction(payload) {
824
+ this.viewer.postToViewer("viewer-cutting-plane-action" /* CUTTING_PLANE_ACTION */, payload);
825
+ }
826
+ postSheetsGetList(payload) {
827
+ this.viewer.postToViewer("viewer-sheets-get-list" /* SHEETS_GET_LIST */, payload);
828
+ }
829
+ postSheetsApply(payload) {
830
+ this.viewer.postToViewer("viewer-sheets-apply" /* SHEETS_APPLY */, payload);
831
+ }
832
+ };
833
+
834
+ // src/modules/model-tree.module.ts
835
+ function createRequestId2() {
836
+ return `tree_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
837
+ }
838
+ var ModelTreeModule = class {
839
+ constructor(viewer) {
840
+ this.viewer = viewer;
841
+ }
842
+ open() {
843
+ this.viewer.postToViewer("viewer-panel-open" /* PANEL_OPEN */, {
844
+ panel: "model-tree",
845
+ format: "3d"
846
+ });
847
+ }
848
+ selectNode(nodeId) {
849
+ this.viewer.postToViewer("viewer-tree-select-node" /* TREE_SELECT_NODE */, {
850
+ nodeId: String(nodeId)
851
+ });
852
+ }
853
+ getNodeIds(options) {
854
+ var _a;
855
+ const requestId = createRequestId2();
856
+ const timeoutMs = Math.max(1e3, (_a = options == null ? void 0 : options.timeoutMs) != null ? _a : 1e4);
857
+ return new Promise((resolve, reject) => {
858
+ const timer = setTimeout(() => {
859
+ off();
860
+ reject(new Error("Timeout while getting node ids from viewer"));
861
+ }, timeoutMs);
862
+ const off = this.viewer._on("modelTree:node-ids", (payload) => {
863
+ if (payload.requestId !== requestId) return;
864
+ clearTimeout(timer);
865
+ off();
866
+ resolve(payload.nodeIds);
867
+ });
868
+ this.viewer.postToViewer("viewer-tree-get-node-ids" /* TREE_GET_NODE_IDS */, {
869
+ requestId,
870
+ onlyRealNodes: (options == null ? void 0 : options.onlyRealNodes) !== false
871
+ });
872
+ });
873
+ }
874
+ };
875
+
876
+ // src/modules/markup.module.ts
877
+ function createRequestId3(prefix) {
878
+ return `${prefix}_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
879
+ }
880
+ var MarkupModule = class {
881
+ constructor(viewer) {
882
+ this.viewer = viewer;
883
+ }
884
+ action(action) {
885
+ this.viewer.postToViewer("viewer-markup-action" /* MARKUP_ACTION */, { action });
886
+ }
887
+ drawLine() {
888
+ this.action("line");
889
+ }
890
+ drawArrow() {
891
+ this.action("arrow");
892
+ }
893
+ drawCircle() {
894
+ this.action("circle");
895
+ }
896
+ drawEllipse() {
897
+ this.action("ellipse");
898
+ }
899
+ drawRectangle() {
900
+ this.action("rectangle");
901
+ }
902
+ drawPolygon() {
903
+ this.action("polygon");
904
+ }
905
+ drawPolyline() {
906
+ this.action("polyline");
907
+ }
908
+ drawTextBox() {
909
+ this.action("textbox");
910
+ }
911
+ drawNote() {
912
+ this.action("note");
913
+ }
914
+ drawCallout() {
915
+ this.action("callout");
916
+ }
917
+ drawCloud() {
918
+ this.action("cloud");
919
+ }
920
+ drawFreehand() {
921
+ this.action("freehand");
922
+ }
923
+ save(options) {
924
+ return this.runRequest("markup-save", "viewer-markup-save" /* MARKUP_SAVE */, "markup:save", options);
925
+ }
926
+ cancel(options) {
927
+ return this.runRequest("markup-cancel", "viewer-markup-cancel" /* MARKUP_CANCEL */, "markup:cancel", options);
928
+ }
929
+ getList(options) {
930
+ var _a;
931
+ const requestId = createRequestId3("markup-list");
932
+ const timeoutMs = Math.max(1e3, (_a = options == null ? void 0 : options.timeoutMs) != null ? _a : 1e4);
933
+ return new Promise((resolve, reject) => {
934
+ const timer = setTimeout(() => {
935
+ off();
936
+ reject(new Error("Timeout while getting markup list from viewer"));
937
+ }, timeoutMs);
938
+ const off = this.viewer._on("markup:list", (payload) => {
939
+ if (payload.requestId !== requestId) return;
940
+ clearTimeout(timer);
941
+ off();
942
+ resolve(payload.markups);
943
+ });
944
+ this.viewer.postToViewer("viewer-markup-get-list" /* MARKUP_GET_LIST */, { requestId });
945
+ });
946
+ }
947
+ runRequest(prefix, messageType, eventName, options) {
948
+ var _a;
949
+ const requestId = createRequestId3(prefix);
950
+ const timeoutMs = Math.max(1e3, (_a = options == null ? void 0 : options.timeoutMs) != null ? _a : 1e4);
951
+ return new Promise((resolve, reject) => {
952
+ const timer = setTimeout(() => {
953
+ off();
954
+ reject(new Error(`Timeout while waiting for ${prefix} result from viewer`));
955
+ }, timeoutMs);
956
+ const off = this.viewer._on(eventName, (payload) => {
957
+ if (payload.requestId !== requestId) return;
958
+ clearTimeout(timer);
959
+ off();
960
+ if (payload.success) {
961
+ resolve();
962
+ return;
963
+ }
964
+ reject(new Error(payload.error || `Viewer ${prefix} failed`));
965
+ });
966
+ this.viewer.postToViewer(messageType, { requestId });
967
+ });
968
+ }
969
+ };
970
+
971
+ // src/modules/language.module.ts
972
+ var LanguageModule = class {
973
+ constructor(viewer) {
974
+ this.viewer = viewer;
975
+ }
976
+ change(language) {
977
+ this.viewer.postToViewer("viewer-language-change" /* LANGUAGE_CHANGE */, {
978
+ language,
979
+ timestamp: Date.now()
980
+ });
981
+ }
982
+ set(language) {
983
+ this.change(language);
984
+ }
985
+ };
986
+
987
+ // src/viewer.ts
988
+ var Viewer3D = class {
989
+ constructor(options) {
990
+ this.options = options;
991
+ this.containerEl = null;
992
+ this.iframeEl = null;
993
+ this.initialized = false;
994
+ this.emitter = new Emitter();
995
+ this.handleMessage = (event) => {
996
+ var _a, _b, _c, _d;
997
+ const data = event.data;
998
+ if (!data || typeof data !== "object") return;
999
+ switch (data.type) {
1000
+ case "viewer-home-click" /* HOME_CLICK */:
1001
+ this._emit("camera:home", { timestamp: Date.now() });
1002
+ break;
1003
+ case "viewer-node-select" /* NODE_SELECT */:
1004
+ this._emit("node:select", { nodeId: String((_b = (_a = data.payload) == null ? void 0 : _a.nodeId) != null ? _b : ""), timestamp: Date.now() });
1005
+ break;
1006
+ case "viewer-pan-change" /* PAN_CHANGE */:
1007
+ this._emit("interaction:pan-change", { enabled: Boolean((_c = data.payload) == null ? void 0 : _c.enabled) });
1008
+ break;
1009
+ case "viewer-pdf-plan-mode" /* PDF_PLAN_MODE */: {
1010
+ const payload = data.payload;
1011
+ this._emit("toolbar:pdf-plan-mode", {
1012
+ mode: "plan",
1013
+ timestamp: Number(payload == null ? void 0 : payload.timestamp) || Date.now()
1014
+ });
1015
+ break;
1016
+ }
1017
+ case "viewer-pdf-document-mode" /* PDF_DOCUMENT_MODE */: {
1018
+ const payload = data.payload;
1019
+ this._emit("toolbar:pdf-document-mode", {
1020
+ mode: "document",
1021
+ timestamp: Number(payload == null ? void 0 : payload.timestamp) || Date.now()
1022
+ });
1023
+ break;
1024
+ }
1025
+ case "viewer-pdf-first-page" /* PDF_FIRST_PAGE */: {
1026
+ const payload = data.payload;
1027
+ this._emit("toolbar:pdf-first-page", {
1028
+ timestamp: Number(payload == null ? void 0 : payload.timestamp) || Date.now()
1029
+ });
1030
+ break;
1031
+ }
1032
+ case "viewer-pdf-previous-page" /* PDF_PREVIOUS_PAGE */: {
1033
+ const payload = data.payload;
1034
+ this._emit("toolbar:pdf-previous-page", {
1035
+ timestamp: Number(payload == null ? void 0 : payload.timestamp) || Date.now()
1036
+ });
1037
+ break;
1038
+ }
1039
+ case "viewer-pdf-next-page" /* PDF_NEXT_PAGE */: {
1040
+ const payload = data.payload;
1041
+ this._emit("toolbar:pdf-next-page", {
1042
+ timestamp: Number(payload == null ? void 0 : payload.timestamp) || Date.now()
1043
+ });
1044
+ break;
1045
+ }
1046
+ case "viewer-pdf-last-page" /* PDF_LAST_PAGE */: {
1047
+ const payload = data.payload;
1048
+ this._emit("toolbar:pdf-last-page", {
1049
+ timestamp: Number(payload == null ? void 0 : payload.timestamp) || Date.now()
1050
+ });
1051
+ break;
1052
+ }
1053
+ case "viewer-pdf-current-page" /* PDF_CURRENT_PAGE */: {
1054
+ const payload = data.payload;
1055
+ if (!payload) break;
1056
+ this._emit("toolbar:pdf-current-page", {
1057
+ pageIndex: Number(payload.pageIndex) || 0,
1058
+ pageNumber: Number(payload.pageNumber) || 1,
1059
+ timestamp: Number(payload.timestamp) || Date.now()
1060
+ });
1061
+ break;
1062
+ }
1063
+ case "viewer-tree-node-ids" /* TREE_NODE_IDS */: {
1064
+ const payload = data.payload;
1065
+ if (!payload || !payload.requestId || !Array.isArray(payload.nodeIds)) break;
1066
+ this._emit("modelTree:node-ids", {
1067
+ requestId: String(payload.requestId),
1068
+ nodeIds: payload.nodeIds.map(String),
1069
+ timestamp: Number(payload.timestamp) || Date.now()
1070
+ });
1071
+ break;
1072
+ }
1073
+ case "viewer-sheets-list" /* SHEETS_LIST */: {
1074
+ const payload = data.payload;
1075
+ if (!payload || !payload.requestId || !Array.isArray(payload.sheets)) break;
1076
+ this._emit("sheets:list", {
1077
+ requestId: String(payload.requestId),
1078
+ sheets: payload.sheets.map((sheet) => {
1079
+ var _a2;
1080
+ return {
1081
+ id: sheet.id,
1082
+ name: String((_a2 = sheet.name) != null ? _a2 : ""),
1083
+ is3D: Boolean(sheet.is3D),
1084
+ viewId: sheet.viewId ? String(sheet.viewId) : void 0
1085
+ };
1086
+ }),
1087
+ activeSheetId: (_d = payload.activeSheetId) != null ? _d : null,
1088
+ timestamp: Number(payload.timestamp) || Date.now()
1089
+ });
1090
+ break;
1091
+ }
1092
+ case "viewer-markup-list" /* MARKUP_LIST */: {
1093
+ const payload = data.payload;
1094
+ if (!payload || !payload.requestId || !Array.isArray(payload.markups)) break;
1095
+ this._emit("markup:list", {
1096
+ requestId: String(payload.requestId),
1097
+ markups: payload.markups.map((markup) => {
1098
+ var _a2, _b2;
1099
+ return {
1100
+ id: String(markup.id),
1101
+ viewId: String(markup.viewId),
1102
+ viewName: markup.viewName ? String(markup.viewName) : void 0,
1103
+ title: String((_a2 = markup.title) != null ? _a2 : ""),
1104
+ type: String((_b2 = markup.type) != null ? _b2 : ""),
1105
+ shapeName: markup.shapeName ? String(markup.shapeName) : void 0,
1106
+ createdDate: markup.createdDate ? String(markup.createdDate) : void 0,
1107
+ modifiedDate: markup.modifiedDate ? String(markup.modifiedDate) : void 0,
1108
+ createdBy: markup.createdBy ? String(markup.createdBy) : void 0,
1109
+ lastModifiedBy: markup.lastModifiedBy ? String(markup.lastModifiedBy) : void 0
1110
+ };
1111
+ }),
1112
+ timestamp: Number(payload.timestamp) || Date.now()
1113
+ });
1114
+ break;
1115
+ }
1116
+ case "viewer-markup-save-result" /* MARKUP_SAVE_RESULT */: {
1117
+ const payload = data.payload;
1118
+ if (!payload || !payload.requestId) break;
1119
+ this._emit("markup:save", {
1120
+ requestId: String(payload.requestId),
1121
+ success: Boolean(payload.success),
1122
+ error: payload.error ? String(payload.error) : void 0,
1123
+ timestamp: Number(payload.timestamp) || Date.now()
1124
+ });
1125
+ break;
1126
+ }
1127
+ case "viewer-markup-cancel-result" /* MARKUP_CANCEL_RESULT */: {
1128
+ const payload = data.payload;
1129
+ if (!payload || !payload.requestId) break;
1130
+ this._emit("markup:cancel", {
1131
+ requestId: String(payload.requestId),
1132
+ success: Boolean(payload.success),
1133
+ error: payload.error ? String(payload.error) : void 0,
1134
+ timestamp: Number(payload.timestamp) || Date.now()
1135
+ });
1136
+ break;
1137
+ }
1138
+ default:
1139
+ break;
1140
+ }
1141
+ };
1142
+ this.camera = new CameraModule(this);
1143
+ this.interaction = new InteractionModule(this);
1144
+ this.node = new NodeModule(this);
1145
+ this.files = new FilesModule(this);
1146
+ this.toolbar = new ToolbarModule(this);
1147
+ this.modelTree = new ModelTreeModule(this);
1148
+ this.markup = new MarkupModule(this);
1149
+ this.language = new LanguageModule(this);
1150
+ }
1151
+ // ===== options helpers =====
1152
+ getOptions() {
1153
+ return this.options;
1154
+ }
1155
+ patchOptions(next) {
1156
+ this.options = { ...this.options, ...next };
1157
+ }
1158
+ getUrl() {
1159
+ var _a;
1160
+ return (_a = this.options.url) != null ? _a : null;
1161
+ }
1162
+ // ===== lifecycle =====
1163
+ init() {
1164
+ if (this.initialized) return;
1165
+ this.containerEl = typeof this.options.container === "string" ? document.querySelector(this.options.container) : this.options.container;
1166
+ if (!this.containerEl) throw new Error("Container element not found");
1167
+ window.addEventListener("message", this.handleMessage);
1168
+ this.initialized = true;
1169
+ }
1170
+ async render(file) {
1171
+ this.ensureInit();
1172
+ if (this.iframeEl) return;
1173
+ if (!this.options.url) return this.files.render(file);
1174
+ const iframe = document.createElement("iframe");
1175
+ iframe.src = this.withInitialOptions(this.options.url);
1176
+ iframe.style.border = "none";
1177
+ iframe.style.width = this.options.width || "100%";
1178
+ iframe.style.height = this.options.height || "100%";
1179
+ iframe.width = this.options.width || "100%";
1180
+ iframe.height = this.options.height || "100%";
1181
+ if (this.options.sandbox) iframe.setAttribute("sandbox", this.options.sandbox);
1182
+ this.containerEl.appendChild(iframe);
1183
+ this.iframeEl = iframe;
1184
+ }
1185
+ open(url) {
1186
+ this.ensureInit();
1187
+ this.options.url = url;
1188
+ const finalUrl = this.withInitialOptions(url);
1189
+ if (!this.iframeEl) {
1190
+ this.render();
1191
+ return;
1192
+ }
1193
+ this.iframeEl.src = finalUrl;
1194
+ }
1195
+ destroy() {
1196
+ window.removeEventListener("message", this.handleMessage);
1197
+ if (this.iframeEl && this.containerEl) {
1198
+ try {
1199
+ this.containerEl.removeChild(this.iframeEl);
1200
+ } catch {
1201
+ }
1202
+ }
1203
+ this.iframeEl = null;
1204
+ this.containerEl = null;
1205
+ this.initialized = false;
1206
+ }
1207
+ ensureInit() {
1208
+ if (!this.initialized) throw new Error("Call viewer.init() before using viewer");
1209
+ }
1210
+ withInitialOptions(url) {
1211
+ if (!this.options.initialToolbarVisibility) return url;
1212
+ try {
1213
+ const parsedUrl = new URL(url, window.location.href);
1214
+ parsedUrl.searchParams.set(
1215
+ "toolbarVisibility",
1216
+ JSON.stringify(this.options.initialToolbarVisibility)
1217
+ );
1218
+ return parsedUrl.toString();
1219
+ } catch {
1220
+ return url;
1221
+ }
1222
+ }
1223
+ // ===== typed internal events used by modules =====
1224
+ _on(event, cb) {
1225
+ return this.emitter.on(event, cb);
1226
+ }
1227
+ _off(event, cb) {
1228
+ this.emitter.off(event, cb);
1229
+ }
1230
+ _emit(event, payload) {
1231
+ this.emitter.emit(event, payload);
1232
+ }
1233
+ // ===== postMessage bridge =====
1234
+ postToViewer(type, payload) {
1235
+ var _a;
1236
+ if (!((_a = this.iframeEl) == null ? void 0 : _a.contentWindow)) return;
1237
+ const message = {
1238
+ source: "HC_SDK" /* SDK */,
1239
+ type,
1240
+ payload
1241
+ };
1242
+ const targetOrigin = this.options.allowedOrigin || "*";
1243
+ this.iframeEl.contentWindow.postMessage(message, targetOrigin);
1244
+ }
1245
+ };
1246
+ // Annotate the CommonJS export names for ESM import in node:
1247
+ 0 && (module.exports = {
1248
+ Viewer3D
1249
+ });