@preference-sl/pref-viewer 2.10.0 → 2.11.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +6 -3
- package/src/babylonjs-controller.js +929 -0
- package/src/file-storage.js +288 -0
- package/src/gltf-resolver.js +288 -0
- package/src/index.js +492 -962
- package/src/panzoom-controller.js +494 -0
- package/src/pref-viewer-2d.js +441 -0
- package/src/pref-viewer-3d-data.js +178 -0
- package/src/pref-viewer-3d.js +457 -0
- package/src/pref-viewer-task.js +54 -0
- package/src/svg-resolver.js +281 -0
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
import { ContainerData, MaterialData, CameraData } from "./pref-viewer-3d-data.js";
|
|
2
|
+
import BabylonJSController from "./babylonjs-controller.js";
|
|
3
|
+
|
|
4
|
+
export class PrefViewer3D extends HTMLElement {
|
|
5
|
+
// State flags
|
|
6
|
+
#isInitialized = false;
|
|
7
|
+
#isLoaded = false;
|
|
8
|
+
#isVisible = false;
|
|
9
|
+
|
|
10
|
+
#wrapper = null;
|
|
11
|
+
#canvas = null;
|
|
12
|
+
|
|
13
|
+
#data = null; // Component data
|
|
14
|
+
|
|
15
|
+
#babylonJSController = null; // BabylonJSController instance
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Create a new instance of the 2D viewer component.
|
|
19
|
+
* The constructor only calls super(); heavy initialization happens in connectedCallback.
|
|
20
|
+
*/
|
|
21
|
+
constructor() {
|
|
22
|
+
super();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Attributes observed by the custom element.
|
|
27
|
+
* @returns {string[]} Array of attribute names that trigger attributeChangedCallback.
|
|
28
|
+
*/
|
|
29
|
+
static get observedAttributes() {
|
|
30
|
+
return ["config", "options", "show-model", "show-scene", "visible"];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Handle attribute changes.
|
|
35
|
+
* @param {string} name - Attribute name.
|
|
36
|
+
* @param {string|null} _old - Previous attribute value (unused).
|
|
37
|
+
* @param {string|null} value -New attribute value.
|
|
38
|
+
* @returns {void}
|
|
39
|
+
* @description
|
|
40
|
+
* - "svg": triggers load of new SVG content.
|
|
41
|
+
* - "visible": shows or hides the component according to the attribute value ("true"/"false").
|
|
42
|
+
*/
|
|
43
|
+
attributeChangedCallback(name, _old, value) {
|
|
44
|
+
switch (name) {
|
|
45
|
+
case "visible":
|
|
46
|
+
if (_old === value) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
if (value === "true" && this.#isVisible === false) {
|
|
50
|
+
this.show();
|
|
51
|
+
} else if (value === "false" && this.#isVisible === true) {
|
|
52
|
+
this.hide();
|
|
53
|
+
}
|
|
54
|
+
break;
|
|
55
|
+
case "show-model":
|
|
56
|
+
if (_old === value) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const showModel = value.toLowerCase?.() === "true";
|
|
60
|
+
showModel ? this.showModel() : this.hideModel();
|
|
61
|
+
break;
|
|
62
|
+
case "show-scene":
|
|
63
|
+
if (_old === value) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const showScene = value.toLowerCase?.() === "true";
|
|
67
|
+
showScene ? this.showEnvironment() : this.hideEnvironment();
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Called when the element is inserted into the DOM.
|
|
74
|
+
* Sets up DOM nodes, initial visibility and starts loading the SVG if provided.
|
|
75
|
+
* @returns {void}
|
|
76
|
+
*/
|
|
77
|
+
connectedCallback() {
|
|
78
|
+
this.#createDOMElements();
|
|
79
|
+
this.#setInitialVisibility();
|
|
80
|
+
this.#initializeData();
|
|
81
|
+
this.#initializeBabylonJS();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
disconnectedCallback() {
|
|
85
|
+
if (this.#babylonJSController) {
|
|
86
|
+
this.#babylonJSController.disable();
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Create DOM structure and basic styles used by the component.
|
|
92
|
+
* @private
|
|
93
|
+
* @returns {void}
|
|
94
|
+
*/
|
|
95
|
+
#createDOMElements() {
|
|
96
|
+
this.#wrapper = document.createElement("div");
|
|
97
|
+
this.append(this.#wrapper);
|
|
98
|
+
this.#canvas = document.createElement("canvas");
|
|
99
|
+
this.#wrapper.appendChild(this.#canvas);
|
|
100
|
+
const style = document.createElement("style");
|
|
101
|
+
style.textContent = `pref-viewer-3d[visible="true"] { display: block; } pref-viewer-3d[visible="false"] { display: none; } pref-viewer-3d, pref-viewer-3d > div, pref-viewer-3d > div > canvas { width: 100%; height: 100%; display: block; position: relative; outline: none; box-sizing: border-box; }`;
|
|
102
|
+
this.append(style);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Set initial visibility based on inline style and the visible attribute.
|
|
107
|
+
* @private
|
|
108
|
+
* @returns {void}
|
|
109
|
+
* @description If the "visible" attribute is not present, defaults to visible.
|
|
110
|
+
*/
|
|
111
|
+
#setInitialVisibility() {
|
|
112
|
+
const visible = !this.hasAttribute("visible") || this.getAttribute("visible") === "true";
|
|
113
|
+
visible ? this.show() : this.hide();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
#initializeData() {
|
|
117
|
+
this.#data = {
|
|
118
|
+
containers: {
|
|
119
|
+
model: new ContainerData("model"),
|
|
120
|
+
environment: new ContainerData("environment"),
|
|
121
|
+
materials: new ContainerData("materials"),
|
|
122
|
+
},
|
|
123
|
+
options: {
|
|
124
|
+
camera: new CameraData("activeCamera", null, true),
|
|
125
|
+
materials: {
|
|
126
|
+
innerWall: new MaterialData("innerWall", undefined, undefined, ["innerWall"]),
|
|
127
|
+
outerWall: new MaterialData("outerWall", undefined, undefined, ["outerWall"]),
|
|
128
|
+
innerFloor: new MaterialData("innerFloor", undefined, undefined, ["innerFloor"]),
|
|
129
|
+
outerFloor: new MaterialData("outerFloor", undefined, undefined, ["outerFloor"]),
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
#initializeBabylonJS() {
|
|
136
|
+
this.#babylonJSController = new BabylonJSController(this.#canvas, this.#data.containers, this.#data.options);
|
|
137
|
+
this.#babylonJSController.enable();
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
#resetUpdateFlags() {
|
|
141
|
+
Object.values(this.#data.containers).forEach(container => container.reset());
|
|
142
|
+
Object.values(this.#data.options.materials).forEach(material => material.reset());
|
|
143
|
+
this.#data.options.camera.reset();
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
#checkNeedToUpdateContainers(config) {
|
|
147
|
+
const modelState = this.#data.containers.model;
|
|
148
|
+
const modelHasStorage = !!config.model?.storage;
|
|
149
|
+
const modelNeedToChangeVisibility = config.model?.visible !== undefined && config.model?.visible !== modelState.show;
|
|
150
|
+
if (modelHasStorage) {
|
|
151
|
+
modelState.setPending(modelHasStorage ? config.model.storage : null, modelNeedToChangeVisibility ? config.model.visible : undefined);
|
|
152
|
+
} else if (modelNeedToChangeVisibility) {
|
|
153
|
+
modelState.show = config.model.visible;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const environmentState = this.#data.containers.environment;
|
|
157
|
+
const environmentHasStorage = !!config.scene?.storage;
|
|
158
|
+
const environmentNeedToChangeVisibility = config.scene?.visible !== undefined && config.scene?.visible !== environmentState.show;
|
|
159
|
+
if (environmentHasStorage) {
|
|
160
|
+
environmentState.setPending(environmentHasStorage ? config.scene.storage : null, environmentNeedToChangeVisibility ? config.scene.visible : undefined);
|
|
161
|
+
} else if (environmentNeedToChangeVisibility) {
|
|
162
|
+
environmentState.show = config.scene.visible;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Materials container. Visibility is not established for this container because it does not contain geometry.
|
|
166
|
+
const materialsState = this.#data.containers.materials;
|
|
167
|
+
const materialsHasStorage = !!config.materials?.storage;
|
|
168
|
+
if (materialsHasStorage) {
|
|
169
|
+
materialsState.setPending(materialsHasStorage ? config.materials.storage : null, null);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
#checkNeedToUpdateCamera(options) {
|
|
174
|
+
if (!options || options.camera === undefined || options.camera === "") {
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
const cameraState = this.#data.options.camera;
|
|
178
|
+
const needUpdate = options.camera !== cameraState.value;
|
|
179
|
+
if (needUpdate) {
|
|
180
|
+
cameraState.setPending(options.camera);
|
|
181
|
+
}
|
|
182
|
+
return needUpdate;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
#checkNeedToUpdateMaterials(options) {
|
|
186
|
+
if (!options) {
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
189
|
+
let someNeedUpdate = false;
|
|
190
|
+
Object.keys(this.#data.options.materials).forEach((material) => {
|
|
191
|
+
const key = `${material}Material`;
|
|
192
|
+
const materialState = this.#data.options.materials[material];
|
|
193
|
+
const previousValue = materialState.value;
|
|
194
|
+
const incomingValue = options[key];
|
|
195
|
+
const needUpdate = !!incomingValue && incomingValue !== previousValue;
|
|
196
|
+
if (needUpdate) {
|
|
197
|
+
materialState.setPending(incomingValue);
|
|
198
|
+
}
|
|
199
|
+
someNeedUpdate = someNeedUpdate || needUpdate;
|
|
200
|
+
});
|
|
201
|
+
return someNeedUpdate;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
#onLoading() {
|
|
206
|
+
this.dispatchEvent(
|
|
207
|
+
new CustomEvent("prefviewer3d-loading", {
|
|
208
|
+
bubbles: true,
|
|
209
|
+
cancelable: false,
|
|
210
|
+
composed: true,
|
|
211
|
+
})
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
this.removeAttribute("loaded");
|
|
215
|
+
this.setAttribute("loading", "");
|
|
216
|
+
|
|
217
|
+
if (this.#isLoaded === true) {
|
|
218
|
+
this.#isLoaded = false;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
#onLoaded() {
|
|
223
|
+
const toLoadDetail = {
|
|
224
|
+
container_model: this.#data.containers.model.isPending,
|
|
225
|
+
container_scene: this.#data.containers.environment.isPending,
|
|
226
|
+
container_materials: this.#data.containers.materials.isPending,
|
|
227
|
+
options_camera: this.#data.options.camera.isPending,
|
|
228
|
+
options_material_innerWall: this.#data.options.materials.innerWall.isPending,
|
|
229
|
+
options_material_outerWall: this.#data.options.materials.outerWall.isPending,
|
|
230
|
+
options_material_innerFloor: this.#data.options.materials.innerFloor.isPending,
|
|
231
|
+
options_material_outerFloor: this.#data.options.materials.outerFloor.isPending,
|
|
232
|
+
};
|
|
233
|
+
const loadedDetail = {
|
|
234
|
+
container_model: this.#data.containers.model.isSuccess,
|
|
235
|
+
container_scene: this.#data.containers.environment.isSuccess,
|
|
236
|
+
container_materials: this.#data.containers.materials.isSuccess,
|
|
237
|
+
options_camera: this.#data.options.camera.isSuccess,
|
|
238
|
+
options_material_innerWall: this.#data.options.materials.innerWall.isSuccess,
|
|
239
|
+
options_material_outerWall: this.#data.options.materials.outerWall.isSuccess,
|
|
240
|
+
options_material_innerFloor: this.#data.options.materials.innerFloor.isSuccess,
|
|
241
|
+
options_material_outerFloor: this.#data.options.materials.outerFloor.isSuccess,
|
|
242
|
+
};
|
|
243
|
+
const detail = {
|
|
244
|
+
tried: toLoadDetail,
|
|
245
|
+
success: loadedDetail,
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
this.dispatchEvent(
|
|
249
|
+
new CustomEvent("prefviewer3d-loaded", {
|
|
250
|
+
bubbles: true,
|
|
251
|
+
cancelable: false,
|
|
252
|
+
composed: true,
|
|
253
|
+
detail: detail,
|
|
254
|
+
})
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
this.removeAttribute("loading");
|
|
258
|
+
this.setAttribute("loaded", "");
|
|
259
|
+
|
|
260
|
+
this.#resetUpdateFlags();
|
|
261
|
+
this.#isLoaded = true;
|
|
262
|
+
|
|
263
|
+
return detail;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
#onSettingOptions() {
|
|
267
|
+
this.dispatchEvent(
|
|
268
|
+
new CustomEvent("prefviewer3d-setting-options", {
|
|
269
|
+
bubbles: true,
|
|
270
|
+
cancelable: false,
|
|
271
|
+
composed: true,
|
|
272
|
+
})
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
this.removeAttribute("loaded");
|
|
276
|
+
this.setAttribute("loading", "");
|
|
277
|
+
|
|
278
|
+
if (this.#isLoaded === true) {
|
|
279
|
+
this.#isLoaded = false;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
#onSettedOptions() {
|
|
284
|
+
const toSetDetail = {
|
|
285
|
+
camera: this.#data.options.camera.isPending,
|
|
286
|
+
innerWallMaterial: this.#data.options.materials.innerWall.isPending,
|
|
287
|
+
outerWallMaterial: this.#data.options.materials.outerWall.isPending,
|
|
288
|
+
innerFloorMaterial: this.#data.options.materials.innerFloor.isPending,
|
|
289
|
+
outerFloorMaterial: this.#data.options.materials.outerFloor.isPending,
|
|
290
|
+
};
|
|
291
|
+
const settedDetail = {
|
|
292
|
+
camera: this.#data.options.camera.isSuccess,
|
|
293
|
+
innerWallMaterial: this.#data.options.materials.innerWall.isSuccess,
|
|
294
|
+
outerWallMaterial: this.#data.options.materials.outerWall.isSuccess,
|
|
295
|
+
innerFloorMaterial: this.#data.options.materials.innerFloor.isSuccess,
|
|
296
|
+
outerFloorMaterial: this.#data.options.materials.outerFloor.isSuccess,
|
|
297
|
+
};
|
|
298
|
+
const detail = {
|
|
299
|
+
tried: toSetDetail,
|
|
300
|
+
success: settedDetail,
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
this.dispatchEvent(
|
|
304
|
+
new CustomEvent("prefviewer3d-loaded", {
|
|
305
|
+
bubbles: true,
|
|
306
|
+
cancelable: false,
|
|
307
|
+
composed: true,
|
|
308
|
+
detail: detail,
|
|
309
|
+
})
|
|
310
|
+
);
|
|
311
|
+
|
|
312
|
+
this.removeAttribute("loading");
|
|
313
|
+
this.setAttribute("loaded", "");
|
|
314
|
+
|
|
315
|
+
this.#resetUpdateFlags();
|
|
316
|
+
this.#isLoaded = true;
|
|
317
|
+
|
|
318
|
+
return detail;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* ---------------------------
|
|
323
|
+
* Public methods
|
|
324
|
+
* ---------------------------
|
|
325
|
+
*/
|
|
326
|
+
hide() {
|
|
327
|
+
this.#isVisible = false;
|
|
328
|
+
this.setAttribute("visible", "false");
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
show() {
|
|
332
|
+
this.#isVisible = true;
|
|
333
|
+
this.setAttribute("visible", "true");
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
async load(config) {
|
|
337
|
+
if (!this.#babylonJSController) {
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
this.#onLoading();
|
|
342
|
+
|
|
343
|
+
// Containers
|
|
344
|
+
this.#checkNeedToUpdateContainers(config);
|
|
345
|
+
|
|
346
|
+
// Options
|
|
347
|
+
if (config.options) {
|
|
348
|
+
this.#checkNeedToUpdateCamera(config.options);
|
|
349
|
+
this.#checkNeedToUpdateMaterials(config.options);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const success = await this.#babylonJSController.load();
|
|
353
|
+
|
|
354
|
+
return { success: success, detail: this.#onLoaded() };
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
setOptions(options) {
|
|
358
|
+
if (!this.#babylonJSController) {
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
this.#onSettingOptions();
|
|
363
|
+
|
|
364
|
+
let someSetted = false;
|
|
365
|
+
if (this.#checkNeedToUpdateCamera(options)) {
|
|
366
|
+
someSetted = someSetted || this.#babylonJSController.setCameraOptions();
|
|
367
|
+
}
|
|
368
|
+
if (this.#checkNeedToUpdateMaterials(options)) {
|
|
369
|
+
someSetted = someSetted || this.#babylonJSController.setMaterialOptions();
|
|
370
|
+
}
|
|
371
|
+
const detail = this.#onSettedOptions();
|
|
372
|
+
return {success: someSetted, detail: detail};
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
showModel() {
|
|
376
|
+
if (!this.#babylonJSController) {
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
this.#babylonJSController.setContainerVisibility("model", true);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
hideModel() {
|
|
383
|
+
if (!this.#babylonJSController) {
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
this.#babylonJSController.setContainerVisibility("model", false);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
showEnvironment() {
|
|
390
|
+
if (!this.#babylonJSController) {
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
this.#babylonJSController.setContainerVisibility("environment", true);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
hideEnvironment() {
|
|
397
|
+
if (!this.#babylonJSController) {
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
this.#babylonJSController.setContainerVisibility("environment", false);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
downloadModelGLB() {
|
|
404
|
+
if (!this.#babylonJSController) {
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
this.#babylonJSController.downloadModelGLB();
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
downloadModelUSDZ() {
|
|
411
|
+
if (!this.#babylonJSController) {
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
this.#babylonJSController.downloadModelUSDZ();
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
downloadModelAndSceneUSDZ() {
|
|
418
|
+
if (!this.#babylonJSController) {
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
this.#babylonJSController.downloadModelAndSceneUSDZ();
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
downloadModelAndSceneGLB() {
|
|
425
|
+
if (!this.#babylonJSController) {
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
this.#babylonJSController.downloadModelAndSceneGLB();
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* Indicates whether the component has completed its initialization.
|
|
433
|
+
* @public
|
|
434
|
+
* @returns {boolean} True if the component is initialized; otherwise, false.
|
|
435
|
+
*/
|
|
436
|
+
get initialized() {
|
|
437
|
+
return this.#isInitialized;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Indicates whether the GLTF/GLB content is loaded and ready.
|
|
442
|
+
* @public
|
|
443
|
+
* @returns {boolean} True if the GLTF/GLB is loaded; otherwise, false.
|
|
444
|
+
*/
|
|
445
|
+
get loaded() {
|
|
446
|
+
return this.#isLoaded;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* Indicates whether the component is currently visible.
|
|
451
|
+
* @public
|
|
452
|
+
* @returns {boolean} True if the component is visible; otherwise, false.
|
|
453
|
+
*/
|
|
454
|
+
get visible() {
|
|
455
|
+
return this.#isVisible;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PrefViewerTask - Represents a single task or operation to be processed by the PrefViewer.
|
|
3
|
+
*
|
|
4
|
+
* Responsibilities:
|
|
5
|
+
* - Encapsulates the payload and type of a viewer task (e.g., loading a model, updating materials).
|
|
6
|
+
* - Validates the task type against a predefined set of allowed types.
|
|
7
|
+
* - Provides an immutable instance to ensure task integrity.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* - Instantiate with a value and a type:
|
|
11
|
+
* new PrefViewerTask(data, PrefViewerTask.Types.Model)
|
|
12
|
+
* - Access the payload via the `value` property and the normalized type via the `type` property.
|
|
13
|
+
*
|
|
14
|
+
* Static Properties:
|
|
15
|
+
* - Types: An object containing all allowed task types as string constants.
|
|
16
|
+
*
|
|
17
|
+
* Constructor:
|
|
18
|
+
* - Validates the type (case-insensitive) and throws a TypeError if invalid.
|
|
19
|
+
* - Freezes the instance to prevent further modification.
|
|
20
|
+
*/
|
|
21
|
+
export class PrefViewerTask {
|
|
22
|
+
static Types = Object.freeze({
|
|
23
|
+
Config: "config",
|
|
24
|
+
Drawing: "drawing",
|
|
25
|
+
Environment: "environment",
|
|
26
|
+
Materials: "materials",
|
|
27
|
+
Model: "model",
|
|
28
|
+
Options: "options",
|
|
29
|
+
Visibility: "visibility",
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Creates a new PrefViewerTask instance.
|
|
34
|
+
* Validates that the provided type matches one of the allowed task types (case-insensitive).
|
|
35
|
+
* If the type is invalid, throws a TypeError.
|
|
36
|
+
* The instance is frozen to prevent further modification.
|
|
37
|
+
*
|
|
38
|
+
* @param {*} value - The payload or data associated with the task.
|
|
39
|
+
* @param {string} type - The type of task; must match one of PrefViewerTask.Types values.
|
|
40
|
+
* @throws {TypeError} If the type is not valid.
|
|
41
|
+
*/
|
|
42
|
+
constructor(value, type) {
|
|
43
|
+
this.value = value;
|
|
44
|
+
|
|
45
|
+
const t = typeof type === "string" ? type.toLowerCase() : String(type).toLowerCase();
|
|
46
|
+
const allowed = Object.values(PrefViewerTask.Types);
|
|
47
|
+
if (!allowed.includes(t)) {
|
|
48
|
+
throw new TypeError(`PrefViewerTask: invalid type "${type}". Allowed types: ${allowed.join(", ")}`);
|
|
49
|
+
}
|
|
50
|
+
this.type = t;
|
|
51
|
+
|
|
52
|
+
Object.freeze(this);
|
|
53
|
+
}
|
|
54
|
+
}
|