3dviewer-sdk 1.0.1 → 1.0.3

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
@@ -194,12 +194,12 @@ var FilesModule = class {
194
194
  });
195
195
  }
196
196
  // Trigger conversion flow and resolve final viewer metadata.
197
- async convert(file) {
197
+ async convert(file, options = {}) {
198
198
  const target = this.resolveFile(file);
199
199
  return this.withOperation({ stage: "converting", message: "Converting file..." }, async () => {
200
200
  this.viewer._emit("files:conversion:start", { fileName: target.name });
201
201
  try {
202
- const prepared = await this.convertInternal(target);
202
+ const prepared = await this.convertInternal(target, options);
203
203
  this.viewer._emit("files:conversion:success", prepared);
204
204
  return prepared;
205
205
  } catch (e) {
@@ -209,11 +209,11 @@ var FilesModule = class {
209
209
  });
210
210
  }
211
211
  // Convenience API: upload first, then convert in one call.
212
- async prepare(file) {
212
+ async prepare(file, options = {}) {
213
213
  const target = this.resolveFile(file);
214
214
  return this.withOperation({ stage: "uploading", message: "Preparing file..." }, async () => {
215
215
  await this.uploadInternal(target);
216
- const prepared = await this.convertInternal(target);
216
+ const prepared = await this.convertInternal(target, options);
217
217
  return prepared;
218
218
  });
219
219
  }
@@ -230,11 +230,11 @@ var FilesModule = class {
230
230
  }
231
231
  }
232
232
  // Full pipeline: upload + convert + open iframe.
233
- async render(file) {
233
+ async render(file, options = {}) {
234
234
  const target = this.resolveFile(file);
235
235
  return this.withOperation({ stage: "uploading", message: "Uploading + converting + opening..." }, async () => {
236
236
  await this.upload(target);
237
- const prepared = await this.convert(target);
237
+ const prepared = await this.convert(target, options);
238
238
  this.updateState({ stage: "rendering", message: "Opening viewer..." });
239
239
  this.open(prepared);
240
240
  this.viewer._emit("files:load:success", prepared);
@@ -327,10 +327,11 @@ var FilesModule = class {
327
327
  }
328
328
  }
329
329
  // Build StreamFile payload compatible with conversion service.
330
- buildCachePayload(file, baseFileId) {
330
+ buildCachePayload(file, baseFileId, options = {}) {
331
331
  const createdDate = (/* @__PURE__ */ new Date()).toISOString();
332
332
  return {
333
333
  filename: file.name,
334
+ ...options.downloadUrl ? { downloadUrl: options.downloadUrl } : {},
334
335
  baseFileId,
335
336
  baseMajorRev: 0,
336
337
  baseMinorRev: 0,
@@ -374,10 +375,10 @@ var FilesModule = class {
374
375
  };
375
376
  }
376
377
  // Submit conversion/caching request and return service response.
377
- async cacheFile(file, baseFileId) {
378
+ async cacheFile(file, baseFileId, options = {}) {
378
379
  const hostConversion = await this.resolveHostConversion();
379
380
  const url = `${hostConversion}/api/StreamFile?overwrite=true&ignore_line_weight=1`;
380
- const payload = this.buildCachePayload(file, baseFileId);
381
+ const payload = this.buildCachePayload(file, baseFileId, options);
381
382
  const response = await fetch(url, {
382
383
  method: "POST",
383
384
  headers: {
@@ -394,12 +395,12 @@ var FilesModule = class {
394
395
  return await response.json();
395
396
  }
396
397
  // Convert file and generate final iframe URL with query string.
397
- async convertInternal(file) {
398
+ async convertInternal(file, options = {}) {
398
399
  var _a, _b, _c, _d;
399
400
  this.updateState({ stage: "converting", message: "Converting file..." });
400
401
  const uploadSession = this.getUploadSessionForFile(file) || this.createUploadSession(file);
401
402
  const seedBaseFileId = uploadSession.baseFileId;
402
- const cacheResult = await this.cacheFile(file, seedBaseFileId);
403
+ const cacheResult = await this.cacheFile(file, seedBaseFileId, options);
403
404
  const baseFileId = (_a = cacheResult.baseFileId) != null ? _a : seedBaseFileId;
404
405
  const baseMajorRev = (_b = cacheResult.baseMajorRev) != null ? _b : 0;
405
406
  const baseMinorRev = (_c = cacheResult.baseMinorRev) != null ? _c : 0;
@@ -801,6 +802,22 @@ var MarkupModule = class {
801
802
  }
802
803
  };
803
804
 
805
+ // src/modules/language.module.ts
806
+ var LanguageModule = class {
807
+ constructor(viewer) {
808
+ this.viewer = viewer;
809
+ }
810
+ change(language) {
811
+ this.viewer.postToViewer("viewer-language-change" /* LANGUAGE_CHANGE */, {
812
+ language,
813
+ timestamp: Date.now()
814
+ });
815
+ }
816
+ set(language) {
817
+ this.change(language);
818
+ }
819
+ };
820
+
804
821
  // src/viewer.ts
805
822
  var Viewer3D = class {
806
823
  constructor(options) {
@@ -963,6 +980,7 @@ var Viewer3D = class {
963
980
  this.toolbar = new ToolbarModule(this);
964
981
  this.modelTree = new ModelTreeModule(this);
965
982
  this.markup = new MarkupModule(this);
983
+ this.language = new LanguageModule(this);
966
984
  }
967
985
  // ===== options helpers =====
968
986
  getOptions() {
package/dist/index.mjs CHANGED
@@ -168,12 +168,12 @@ var FilesModule = class {
168
168
  });
169
169
  }
170
170
  // Trigger conversion flow and resolve final viewer metadata.
171
- async convert(file) {
171
+ async convert(file, options = {}) {
172
172
  const target = this.resolveFile(file);
173
173
  return this.withOperation({ stage: "converting", message: "Converting file..." }, async () => {
174
174
  this.viewer._emit("files:conversion:start", { fileName: target.name });
175
175
  try {
176
- const prepared = await this.convertInternal(target);
176
+ const prepared = await this.convertInternal(target, options);
177
177
  this.viewer._emit("files:conversion:success", prepared);
178
178
  return prepared;
179
179
  } catch (e) {
@@ -183,11 +183,11 @@ var FilesModule = class {
183
183
  });
184
184
  }
185
185
  // Convenience API: upload first, then convert in one call.
186
- async prepare(file) {
186
+ async prepare(file, options = {}) {
187
187
  const target = this.resolveFile(file);
188
188
  return this.withOperation({ stage: "uploading", message: "Preparing file..." }, async () => {
189
189
  await this.uploadInternal(target);
190
- const prepared = await this.convertInternal(target);
190
+ const prepared = await this.convertInternal(target, options);
191
191
  return prepared;
192
192
  });
193
193
  }
@@ -204,11 +204,11 @@ var FilesModule = class {
204
204
  }
205
205
  }
206
206
  // Full pipeline: upload + convert + open iframe.
207
- async render(file) {
207
+ async render(file, options = {}) {
208
208
  const target = this.resolveFile(file);
209
209
  return this.withOperation({ stage: "uploading", message: "Uploading + converting + opening..." }, async () => {
210
210
  await this.upload(target);
211
- const prepared = await this.convert(target);
211
+ const prepared = await this.convert(target, options);
212
212
  this.updateState({ stage: "rendering", message: "Opening viewer..." });
213
213
  this.open(prepared);
214
214
  this.viewer._emit("files:load:success", prepared);
@@ -301,10 +301,11 @@ var FilesModule = class {
301
301
  }
302
302
  }
303
303
  // Build StreamFile payload compatible with conversion service.
304
- buildCachePayload(file, baseFileId) {
304
+ buildCachePayload(file, baseFileId, options = {}) {
305
305
  const createdDate = (/* @__PURE__ */ new Date()).toISOString();
306
306
  return {
307
307
  filename: file.name,
308
+ ...options.downloadUrl ? { downloadUrl: options.downloadUrl } : {},
308
309
  baseFileId,
309
310
  baseMajorRev: 0,
310
311
  baseMinorRev: 0,
@@ -348,10 +349,10 @@ var FilesModule = class {
348
349
  };
349
350
  }
350
351
  // Submit conversion/caching request and return service response.
351
- async cacheFile(file, baseFileId) {
352
+ async cacheFile(file, baseFileId, options = {}) {
352
353
  const hostConversion = await this.resolveHostConversion();
353
354
  const url = `${hostConversion}/api/StreamFile?overwrite=true&ignore_line_weight=1`;
354
- const payload = this.buildCachePayload(file, baseFileId);
355
+ const payload = this.buildCachePayload(file, baseFileId, options);
355
356
  const response = await fetch(url, {
356
357
  method: "POST",
357
358
  headers: {
@@ -368,12 +369,12 @@ var FilesModule = class {
368
369
  return await response.json();
369
370
  }
370
371
  // Convert file and generate final iframe URL with query string.
371
- async convertInternal(file) {
372
+ async convertInternal(file, options = {}) {
372
373
  var _a, _b, _c, _d;
373
374
  this.updateState({ stage: "converting", message: "Converting file..." });
374
375
  const uploadSession = this.getUploadSessionForFile(file) || this.createUploadSession(file);
375
376
  const seedBaseFileId = uploadSession.baseFileId;
376
- const cacheResult = await this.cacheFile(file, seedBaseFileId);
377
+ const cacheResult = await this.cacheFile(file, seedBaseFileId, options);
377
378
  const baseFileId = (_a = cacheResult.baseFileId) != null ? _a : seedBaseFileId;
378
379
  const baseMajorRev = (_b = cacheResult.baseMajorRev) != null ? _b : 0;
379
380
  const baseMinorRev = (_c = cacheResult.baseMinorRev) != null ? _c : 0;
@@ -775,6 +776,22 @@ var MarkupModule = class {
775
776
  }
776
777
  };
777
778
 
779
+ // src/modules/language.module.ts
780
+ var LanguageModule = class {
781
+ constructor(viewer) {
782
+ this.viewer = viewer;
783
+ }
784
+ change(language) {
785
+ this.viewer.postToViewer("viewer-language-change" /* LANGUAGE_CHANGE */, {
786
+ language,
787
+ timestamp: Date.now()
788
+ });
789
+ }
790
+ set(language) {
791
+ this.change(language);
792
+ }
793
+ };
794
+
778
795
  // src/viewer.ts
779
796
  var Viewer3D = class {
780
797
  constructor(options) {
@@ -937,6 +954,7 @@ var Viewer3D = class {
937
954
  this.toolbar = new ToolbarModule(this);
938
955
  this.modelTree = new ModelTreeModule(this);
939
956
  this.markup = new MarkupModule(this);
957
+ this.language = new LanguageModule(this);
940
958
  }
941
959
  // ===== options helpers =====
942
960
  getOptions() {
@@ -0,0 +1,13 @@
1
+ import { Viewer3D } from "../viewer";
2
+ export declare class CameraModule {
3
+ private viewer;
4
+ on: {
5
+ home: (cb: (payload: {
6
+ timestamp: number;
7
+ }) => void) => () => void;
8
+ };
9
+ constructor(viewer: Viewer3D);
10
+ zoomIn(percent: number): void;
11
+ zoomOut(percent: number): void;
12
+ home(): void;
13
+ }
@@ -0,0 +1,18 @@
1
+ import { ViewerMessageType } from "../contracts/messages";
2
+ export class CameraModule {
3
+ constructor(viewer) {
4
+ this.viewer = viewer;
5
+ this.on = {
6
+ home: (cb) => this.viewer._on("camera:home", cb),
7
+ };
8
+ }
9
+ zoomIn(percent) {
10
+ this.viewer.postToViewer(ViewerMessageType.ZOOM, { action: "in", percent });
11
+ }
12
+ zoomOut(percent) {
13
+ this.viewer.postToViewer(ViewerMessageType.ZOOM, { action: "out", percent });
14
+ }
15
+ home() {
16
+ this.viewer.postToViewer(ViewerMessageType.HOME, {});
17
+ }
18
+ }
@@ -0,0 +1,84 @@
1
+ import type { LoadStatePayload, PreparedViewerData } from "../contracts/events";
2
+ import { Viewer3D } from "../viewer";
3
+ export declare type FilesConfig = {
4
+ baseUrl?: string;
5
+ viewerPath?: string;
6
+ uploadPath?: string;
7
+ };
8
+ export declare type ConvertOptions = {
9
+ downloadUrl?: string;
10
+ };
11
+ export declare class FilesModule {
12
+ private viewer;
13
+ on: {
14
+ state: (cb: (payload: LoadStatePayload) => void) => () => void;
15
+ uploadStart: (cb: (payload: {
16
+ fileName: string;
17
+ }) => void) => () => void;
18
+ uploadSuccess: (cb: (payload: {
19
+ fileName: string;
20
+ baseFileId: string;
21
+ }) => void) => () => void;
22
+ uploadError: (cb: (payload: {
23
+ fileName: string;
24
+ error: string;
25
+ }) => void) => () => void;
26
+ conversionStart: (cb: (payload: {
27
+ fileName: string;
28
+ }) => void) => () => void;
29
+ conversionSuccess: (cb: (payload: PreparedViewerData) => void) => () => void;
30
+ conversionError: (cb: (payload: {
31
+ fileName: string;
32
+ error: string;
33
+ }) => void) => () => void;
34
+ renderStart: (cb: (payload: {
35
+ url: string;
36
+ }) => void) => () => void;
37
+ renderSuccess: (cb: (payload: {
38
+ url: string;
39
+ }) => void) => () => void;
40
+ renderError: (cb: (payload: {
41
+ url?: string;
42
+ error: string;
43
+ }) => void) => () => void;
44
+ loadSuccess: (cb: (payload: PreparedViewerData) => void) => () => void;
45
+ loadError: (cb: (payload: {
46
+ error: string;
47
+ }) => void) => () => void;
48
+ };
49
+ private config;
50
+ private operationStartTime;
51
+ private state;
52
+ private lastUploadSession;
53
+ constructor(viewer: Viewer3D);
54
+ setConfig(next: FilesConfig): void;
55
+ getState(): LoadStatePayload;
56
+ upload(file?: File): Promise<{
57
+ fileName: string;
58
+ baseFileId: string;
59
+ }>;
60
+ convert(file?: File, options?: ConvertOptions): Promise<PreparedViewerData>;
61
+ prepare(file?: File, options?: ConvertOptions): Promise<PreparedViewerData>;
62
+ open(input: PreparedViewerData | {
63
+ url: string;
64
+ }): void;
65
+ render(file?: File, options?: ConvertOptions): Promise<PreparedViewerData>;
66
+ private resolveFile;
67
+ private normalizeBaseUrl;
68
+ private resolveBaseUrl;
69
+ private resolveViewerPath;
70
+ private resolveViewerOrigin;
71
+ private resolveHostConversion;
72
+ private getUploadPath;
73
+ private fileSignature;
74
+ private createBaseFileId;
75
+ private createUploadSession;
76
+ private getUploadSessionForFile;
77
+ private uploadInternal;
78
+ private buildCachePayload;
79
+ private cacheFile;
80
+ private convertInternal;
81
+ private updateState;
82
+ private withOperation;
83
+ private toErrorMessage;
84
+ }
@@ -0,0 +1,315 @@
1
+ const DEFAULT_API_BASE_URL = "https://dev.3dviewer.anybim.vn";
2
+ const DEFAULT_VIEWER_ORIGIN = "http://localhost:3000";
3
+ export class FilesModule {
4
+ constructor(viewer) {
5
+ this.viewer = viewer;
6
+ this.config = {};
7
+ this.operationStartTime = 0;
8
+ this.state = {
9
+ isLoading: false,
10
+ stage: "idle",
11
+ };
12
+ this.lastUploadSession = null;
13
+ // Bind external event helpers to internal typed emitter events.
14
+ this.on = {
15
+ state: (cb) => this.viewer._on("files:state", cb),
16
+ uploadStart: (cb) => this.viewer._on("files:upload:start", cb),
17
+ uploadSuccess: (cb) => this.viewer._on("files:upload:success", cb),
18
+ uploadError: (cb) => this.viewer._on("files:upload:error", cb),
19
+ conversionStart: (cb) => this.viewer._on("files:conversion:start", cb),
20
+ conversionSuccess: (cb) => this.viewer._on("files:conversion:success", cb),
21
+ conversionError: (cb) => this.viewer._on("files:conversion:error", cb),
22
+ renderStart: (cb) => this.viewer._on("files:render:start", cb),
23
+ renderSuccess: (cb) => this.viewer._on("files:render:success", cb),
24
+ renderError: (cb) => this.viewer._on("files:render:error", cb),
25
+ loadSuccess: (cb) => this.viewer._on("files:load:success", cb),
26
+ loadError: (cb) => this.viewer._on("files:load:error", cb),
27
+ };
28
+ }
29
+ // Merge file-pipeline runtime config.
30
+ setConfig(next) {
31
+ this.config = { ...this.config, ...next };
32
+ }
33
+ // Return a snapshot of current loading state.
34
+ getState() {
35
+ return { ...this.state };
36
+ }
37
+ // ---------- public pipeline ----------
38
+ // Upload file to conversion server and keep generated baseFileId in session.
39
+ async upload(file) {
40
+ const target = this.resolveFile(file);
41
+ return this.withOperation({ stage: "uploading", message: "Uploading file..." }, async () => {
42
+ var _a;
43
+ this.viewer._emit("files:upload:start", { fileName: target.name });
44
+ await this.uploadInternal(target);
45
+ const baseFileId = ((_a = this.getUploadSessionForFile(target)) === null || _a === void 0 ? void 0 : _a.baseFileId) || "";
46
+ this.viewer._emit("files:upload:success", { fileName: target.name, baseFileId });
47
+ return { fileName: target.name, baseFileId };
48
+ });
49
+ }
50
+ // Trigger conversion flow and resolve final viewer metadata.
51
+ async convert(file, options = {}) {
52
+ const target = this.resolveFile(file);
53
+ return this.withOperation({ stage: "converting", message: "Converting file..." }, async () => {
54
+ this.viewer._emit("files:conversion:start", { fileName: target.name });
55
+ try {
56
+ const prepared = await this.convertInternal(target, options);
57
+ this.viewer._emit("files:conversion:success", prepared);
58
+ return prepared;
59
+ }
60
+ catch (e) {
61
+ this.viewer._emit("files:conversion:error", { fileName: target.name, error: this.toErrorMessage(e) });
62
+ throw e;
63
+ }
64
+ });
65
+ }
66
+ // Convenience API: upload first, then convert in one call.
67
+ async prepare(file, options = {}) {
68
+ const target = this.resolveFile(file);
69
+ return this.withOperation({ stage: "uploading", message: "Preparing file..." }, async () => {
70
+ await this.uploadInternal(target);
71
+ const prepared = await this.convertInternal(target, options);
72
+ return prepared;
73
+ });
74
+ }
75
+ // Open iframe with an already prepared viewer URL.
76
+ open(input) {
77
+ const url = input.url;
78
+ this.viewer._emit("files:render:start", { url });
79
+ try {
80
+ this.viewer.open(url);
81
+ this.viewer._emit("files:render:success", { url });
82
+ }
83
+ catch (e) {
84
+ this.viewer._emit("files:render:error", { url, error: this.toErrorMessage(e) });
85
+ throw e;
86
+ }
87
+ }
88
+ // Full pipeline: upload + convert + open iframe.
89
+ async render(file, options = {}) {
90
+ const target = this.resolveFile(file);
91
+ return this.withOperation({ stage: "uploading", message: "Uploading + converting + opening..." }, async () => {
92
+ await this.upload(target);
93
+ const prepared = await this.convert(target, options);
94
+ // open viewer
95
+ this.updateState({ stage: "rendering", message: "Opening viewer..." });
96
+ this.open(prepared);
97
+ this.viewer._emit("files:load:success", prepared);
98
+ return prepared;
99
+ });
100
+ }
101
+ // Resolve file argument, fallback to options.file, and persist it back.
102
+ resolveFile(file) {
103
+ const optFile = this.viewer.getOptions().file;
104
+ const target = file || optFile;
105
+ if (!target)
106
+ throw new Error("No file provided. Pass a File or set options.file");
107
+ // store back to viewer options (optional)
108
+ this.viewer.patchOptions({ file: target });
109
+ return target;
110
+ }
111
+ // Trim input URL and remove trailing slash.
112
+ normalizeBaseUrl(input) {
113
+ return input.trim().replace(/\/+$/, "");
114
+ }
115
+ // Resolve API base URL with default fallback.
116
+ resolveBaseUrl() {
117
+ const raw = this.config.baseUrl || this.viewer.getOptions().baseUrl || DEFAULT_API_BASE_URL;
118
+ return this.normalizeBaseUrl(raw);
119
+ }
120
+ // Resolve viewer route path (e.g. /mainviewer).
121
+ resolveViewerPath() {
122
+ const p = (this.config.viewerPath || this.viewer.getOptions().viewerPath || "/mainviewer").trim();
123
+ if (!p)
124
+ return "/mainviewer";
125
+ return p.startsWith("/") ? p : `/${p}`;
126
+ }
127
+ // Viewer host used to open iframe after conversion completes.
128
+ resolveViewerOrigin() {
129
+ return this.normalizeBaseUrl(DEFAULT_VIEWER_ORIGIN);
130
+ }
131
+ // Build conversion service root from API base URL.
132
+ resolveHostConversion() {
133
+ const baseUrl = this.resolveBaseUrl();
134
+ return baseUrl.endsWith("/service/conversion") ? baseUrl : `${baseUrl}/service/conversion`;
135
+ }
136
+ // Resolve upload path sent to conversion APIs.
137
+ getUploadPath() {
138
+ return this.config.uploadPath || this.viewer.getOptions().uploadPath || ".";
139
+ }
140
+ // Build a stable in-memory signature to identify same file uploads.
141
+ fileSignature(file) {
142
+ return `${file.name}::${file.size}::${file.lastModified}`;
143
+ }
144
+ // Create a UUID-like baseFileId when caller does not provide one.
145
+ createBaseFileId() {
146
+ if (typeof crypto !== "undefined" && "randomUUID" in crypto)
147
+ return crypto.randomUUID();
148
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
149
+ const r = Math.floor(Math.random() * 16);
150
+ const v = c === "x" ? r : (r & 0x3) | 0x8;
151
+ return v.toString(16);
152
+ });
153
+ }
154
+ // Create upload session metadata reused between upload and convert.
155
+ createUploadSession(file) {
156
+ return {
157
+ signature: this.fileSignature(file),
158
+ baseFileId: this.createBaseFileId(),
159
+ fileName: file.name,
160
+ uploadPath: this.getUploadPath(),
161
+ };
162
+ }
163
+ // Return previous upload session for same file and upload path.
164
+ getUploadSessionForFile(file) {
165
+ if (!this.lastUploadSession)
166
+ return null;
167
+ const sameFile = this.lastUploadSession.signature === this.fileSignature(file);
168
+ const samePath = this.lastUploadSession.uploadPath === this.getUploadPath();
169
+ return sameFile && samePath ? this.lastUploadSession : null;
170
+ }
171
+ // Call upload endpoint and persist upload session on success.
172
+ async uploadInternal(file) {
173
+ this.updateState({ stage: "uploading", message: "Uploading file..." });
174
+ try {
175
+ const existing = this.getUploadSessionForFile(file);
176
+ const session = existing || this.createUploadSession(file);
177
+ const hostConversion = this.resolveHostConversion();
178
+ const path = this.getUploadPath();
179
+ // upload endpoint
180
+ const url = `${hostConversion}/api/File/upload?path=${encodeURIComponent(path)}`;
181
+ const formData = new FormData();
182
+ formData.append("file", file, file.name);
183
+ const res = await fetch(url, { method: "POST", body: formData, headers: { Accept: "text/plain" } });
184
+ if (!res.ok)
185
+ throw new Error(`Upload failed (${res.status} ${res.statusText})`);
186
+ this.lastUploadSession = session;
187
+ }
188
+ catch (e) {
189
+ const msg = this.toErrorMessage(e);
190
+ this.viewer._emit("files:upload:error", { fileName: file.name, error: msg });
191
+ throw e;
192
+ }
193
+ }
194
+ // Build StreamFile payload compatible with conversion service.
195
+ buildCachePayload(file, baseFileId, options = {}) {
196
+ const createdDate = new Date().toISOString();
197
+ return {
198
+ filename: file.name,
199
+ ...(options.downloadUrl ? { downloadUrl: options.downloadUrl } : {}),
200
+ baseFileId,
201
+ baseMajorRev: 0,
202
+ baseMinorRev: 0,
203
+ isChecked: false,
204
+ status: { size: file.size },
205
+ child: [],
206
+ isDirectory: false,
207
+ createdDate,
208
+ cacheStatus: 0,
209
+ modelFileId: "",
210
+ id: "",
211
+ originalFilePath: this.getUploadPath(),
212
+ streamLocation: null,
213
+ converter: "Hoops",
214
+ originalSize: 0,
215
+ cacheSize: 0,
216
+ importTime: 0,
217
+ importAssemblyTreeTime: 0,
218
+ creator: {
219
+ id: "00000000-0000-0000-0000-000000000000",
220
+ name: "Anonymous",
221
+ },
222
+ originalFile: file.name,
223
+ multiStream: false,
224
+ isRootModel: 0,
225
+ extraConvertOutput: "",
226
+ cacheFilename: null,
227
+ errorMassage: null,
228
+ convertOptions: {
229
+ convert3DModel: 1,
230
+ convert2DSheet: 1,
231
+ extractProperties: 1,
232
+ childModels: 0,
233
+ },
234
+ drawingConvertStatus: {
235
+ convert3DModel: 5,
236
+ convert2DSheet: 5,
237
+ extractProperties: 5,
238
+ },
239
+ attemptedConvertTimes: 0,
240
+ };
241
+ }
242
+ // Submit conversion/caching request and return service response.
243
+ async cacheFile(file, baseFileId, options = {}) {
244
+ const hostConversion = await this.resolveHostConversion();
245
+ const url = `${hostConversion}/api/StreamFile?overwrite=true&ignore_line_weight=1`;
246
+ const payload = this.buildCachePayload(file, baseFileId, options);
247
+ const response = await fetch(url, {
248
+ method: "POST",
249
+ headers: {
250
+ "Content-Type": "application/json",
251
+ Accept: "application/json",
252
+ },
253
+ body: JSON.stringify(payload),
254
+ });
255
+ if (!response.ok) {
256
+ throw new Error(`Cache/convert failed (${response.status} ${response.statusText})`);
257
+ }
258
+ return (await response.json());
259
+ }
260
+ // Convert file and generate final iframe URL with query string.
261
+ async convertInternal(file, options = {}) {
262
+ var _a, _b, _c, _d;
263
+ this.updateState({ stage: "converting", message: "Converting file..." });
264
+ const uploadSession = this.getUploadSessionForFile(file) || this.createUploadSession(file);
265
+ const seedBaseFileId = uploadSession.baseFileId;
266
+ // 1) request cache/convert
267
+ const cacheResult = await this.cacheFile(file, seedBaseFileId, options);
268
+ const baseFileId = (_a = cacheResult.baseFileId) !== null && _a !== void 0 ? _a : seedBaseFileId;
269
+ const baseMajorRev = (_b = cacheResult.baseMajorRev) !== null && _b !== void 0 ? _b : 0;
270
+ const baseMinorRev = (_c = cacheResult.baseMinorRev) !== null && _c !== void 0 ? _c : 0;
271
+ const fileName = cacheResult.filename || file.name;
272
+ const cacheListItem = { baseFileId, baseMajorRev, baseMinorRev, fileName };
273
+ // Single-shot mode: one conversion request only, no polling retry.
274
+ if (cacheResult.cacheStatus !== 2) {
275
+ throw new Error(`Conversion not ready after first request (cacheStatus=${(_d = cacheResult.cacheStatus) !== null && _d !== void 0 ? _d : "unknown"})`);
276
+ }
277
+ // 3) build viewer url
278
+ const query = new URLSearchParams({ fileList: JSON.stringify([cacheListItem]) }).toString();
279
+ const viewerBase = this.resolveViewerOrigin();
280
+ const viewerPath = this.resolveViewerPath();
281
+ const url = `${viewerBase}${viewerPath}?${query}`;
282
+ return { baseFileId, baseMajorRev, baseMinorRev, fileName, query, url };
283
+ }
284
+ // Update internal loading state and emit state event.
285
+ updateState(next) {
286
+ const elapsedMs = this.operationStartTime > 0 ? Date.now() - this.operationStartTime : 0;
287
+ this.state = { ...this.state, ...next, elapsedMs };
288
+ this.viewer._emit("files:state", this.state);
289
+ }
290
+ // Shared wrapper to handle loading state lifecycle and top-level errors.
291
+ async withOperation(initial, run) {
292
+ // shared operation wrapper for loading state and errors
293
+ this.operationStartTime = Date.now();
294
+ this.updateState({
295
+ isLoading: true,
296
+ stage: initial.stage,
297
+ message: initial.message,
298
+ });
299
+ try {
300
+ const result = await run();
301
+ this.updateState({ isLoading: false, stage: "completed", message: "Completed" });
302
+ return result;
303
+ }
304
+ catch (e) {
305
+ const msg = this.toErrorMessage(e);
306
+ this.updateState({ isLoading: false, stage: "error", message: msg });
307
+ this.viewer._emit("files:load:error", { error: msg });
308
+ throw e;
309
+ }
310
+ }
311
+ // Normalize unknown error shape into displayable message.
312
+ toErrorMessage(e) {
313
+ return e instanceof Error ? e.message : String(e);
314
+ }
315
+ }
@@ -0,0 +1,28 @@
1
+ import { Viewer3D } from "../viewer";
2
+ export declare class InteractionModule {
3
+ private viewer;
4
+ on: {
5
+ panChange: (cb: (payload: {
6
+ enabled: boolean;
7
+ }) => void) => () => void;
8
+ };
9
+ constructor(viewer: Viewer3D);
10
+ enablePan(): void;
11
+ disablePan(): void;
12
+ select(): void;
13
+ areaSelect(): void;
14
+ orbit(): void;
15
+ rotateZ(): void;
16
+ walkThrough(): void;
17
+ zoomWindow(): void;
18
+ zoomFit(): void;
19
+ drawModeShaded(): void;
20
+ drawModeWireframe(): void;
21
+ drawModeHiddenLine(): void;
22
+ drawModeShadedWire(): void;
23
+ drawModeXRay(): void;
24
+ drawModeGhosting(): void;
25
+ explode(magnitude: number): void;
26
+ explodeOff(): void;
27
+ private setDrawMode;
28
+ }