customforge 0.1.0-alpha.0 → 0.1.0-alpha.2

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.
Files changed (39) hide show
  1. package/CHANGELOG.md +61 -27
  2. package/LICENSE +201 -201
  3. package/LICENSES/NunitoSans-OFL.txt +93 -0
  4. package/README.md +339 -152
  5. package/README.zh-CN.md +337 -152
  6. package/dist/NunitoSans-Variable.ttf +0 -0
  7. package/dist/ProductCustomizer-rMRyWe7L.js +1624 -0
  8. package/dist/ProductCustomizer-rMRyWe7L.js.map +1 -0
  9. package/dist/core/design.d.ts.map +1 -1
  10. package/dist/core/types.d.ts +35 -4
  11. package/dist/core/types.d.ts.map +1 -1
  12. package/dist/customizer/ProductCustomizer.d.ts +78 -0
  13. package/dist/customizer/ProductCustomizer.d.ts.map +1 -1
  14. package/dist/editor/DesignEditor.d.ts +108 -6
  15. package/dist/editor/DesignEditor.d.ts.map +1 -1
  16. package/dist/editor/DesignHistory.d.ts +27 -0
  17. package/dist/editor/DesignHistory.d.ts.map +1 -0
  18. package/dist/index.d.ts +1 -1
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +1 -1108
  21. package/dist/index.js.map +1 -1
  22. package/dist/style.css +1330 -11
  23. package/dist/workbench/CustomForgeWorkbench.d.ts +128 -0
  24. package/dist/workbench/CustomForgeWorkbench.d.ts.map +1 -0
  25. package/dist/workbench/assets/catalog.d.ts +8 -0
  26. package/dist/workbench/assets/catalog.d.ts.map +1 -0
  27. package/dist/workbench/config.d.ts +55 -0
  28. package/dist/workbench/config.d.ts.map +1 -0
  29. package/dist/workbench/icons.d.ts +4 -0
  30. package/dist/workbench/icons.d.ts.map +1 -0
  31. package/dist/workbench/index.d.ts +25 -0
  32. package/dist/workbench/index.d.ts.map +1 -0
  33. package/dist/workbench/template.d.ts +3 -0
  34. package/dist/workbench/template.d.ts.map +1 -0
  35. package/dist/workbench/types.d.ts +294 -0
  36. package/dist/workbench/types.d.ts.map +1 -0
  37. package/dist/workbench.js +1920 -0
  38. package/dist/workbench.js.map +1 -0
  39. package/package.json +8 -5
@@ -0,0 +1,1920 @@
1
+ import { n as resolveElement, t as ProductCustomizer } from "./ProductCustomizer-rMRyWe7L.js";
2
+ //#endregion
3
+ //#region src/workbench/assets/catalog.ts
4
+ /**
5
+ * Workbench 随包提供的图片素材目录
6
+ *
7
+ * 新素材文件应分别放入 backgrounds 或 elements 目录,并在此处使用 import 后注册
8
+ */
9
+ var builtInAssetLibrary = {
10
+ backgrounds: [],
11
+ elements: [{
12
+ id: "customforge-logo",
13
+ name: "CustomForge logo",
14
+ url: "data:image/png;base64,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",
15
+ alt: "CustomForge logo"
16
+ }]
17
+ };
18
+ //#endregion
19
+ //#region src/workbench/config.ts
20
+ var defaultFeatures = {
21
+ addText: true,
22
+ addImage: true,
23
+ undoRedo: true,
24
+ deleteSelection: true,
25
+ saveDesign: true,
26
+ loadDesign: true,
27
+ loadRemoteProduct: true,
28
+ exportTexture: true,
29
+ resetView: true,
30
+ reorderObjects: true,
31
+ toggleObjectVisibility: true,
32
+ lockObjects: true,
33
+ renameObjects: true,
34
+ presetBackgrounds: true,
35
+ presetElements: true
36
+ };
37
+ var defaultLayout = {
38
+ header: true,
39
+ editorHeader: true,
40
+ viewerHeader: true,
41
+ toolbar: true,
42
+ layers: true,
43
+ status: true
44
+ };
45
+ var defaultLabels = {
46
+ workbench: "Product customization studio",
47
+ editorTitle: "Design surface",
48
+ viewerTitle: "Product preview",
49
+ layers: "Layers",
50
+ noObjects: "No design objects",
51
+ addText: "Text",
52
+ addImage: "Image",
53
+ undo: "Undo",
54
+ redo: "Redo",
55
+ saveDesign: "Save design JSON",
56
+ loadDesign: "Open design JSON",
57
+ deleteSelection: "Delete selected object",
58
+ loadProduct: "Load product",
59
+ exportTexture: "Export PNG",
60
+ resetView: "Reset 3D view",
61
+ textObject: "Text",
62
+ imageObject: "Image",
63
+ backgroundObject: "Background",
64
+ showLayer: "Show layer",
65
+ hideLayer: "Hide layer",
66
+ lockLayer: "Lock layer",
67
+ unlockLayer: "Unlock layer",
68
+ moveLayerForward: "Move layer forward",
69
+ moveLayerBackward: "Move layer backward",
70
+ deleteLayer: "Delete layer",
71
+ textDialogEyebrow: "Typography",
72
+ textDialogTitle: "Add text",
73
+ textInputLabel: "Text",
74
+ textInputPlaceholder: "Make it yours",
75
+ textPresets: "Style",
76
+ textColor: "Color",
77
+ addTextConfirm: "Add text",
78
+ imageDialogEyebrow: "Artwork",
79
+ imageDialogTitle: "Add image",
80
+ uploadImageTab: "Upload",
81
+ backgroundsTab: "Backgrounds",
82
+ elementsTab: "Elements",
83
+ chooseImage: "Choose image",
84
+ noImageSelected: "No image selected",
85
+ noPresetAssets: "No preset assets",
86
+ addImageConfirm: "Add image",
87
+ close: "Close",
88
+ cancel: "Cancel",
89
+ productDialogEyebrow: "Product source",
90
+ productDialogTitle: "Load remote product",
91
+ modelUrl: "GLB or GLTF URL",
92
+ textureUrl: "Base texture URL",
93
+ surfaceMesh: "Customizable mesh",
94
+ flipTexture: "Flip texture vertically",
95
+ useDemo: "Use built-in demo",
96
+ starting: "Starting",
97
+ productReady: "Product ready",
98
+ designSaved: "Design saved",
99
+ designLoaded: "Design loaded",
100
+ undoComplete: "Undo complete",
101
+ redoComplete: "Redo complete",
102
+ objectCountOne: "{count} object",
103
+ objectCountMany: "{count} objects"
104
+ };
105
+ var defaultTheme = {
106
+ ink: "#24272b",
107
+ muted: "#747b84",
108
+ border: "#e1e4e8",
109
+ surface: "#ffffff",
110
+ surfaceMuted: "#f5f6f8",
111
+ stage: "#f1f3f5",
112
+ accent: "#0875c9",
113
+ accentHover: "#075fa3",
114
+ accentContrast: "#ffffff",
115
+ danger: "#b42318",
116
+ fontFamily: "\"Nunito Sans\", \"Avenir Next\", \"Segoe UI Variable\", \"Segoe UI\", ui-sans-serif, system-ui, sans-serif",
117
+ controlRadius: "7px"
118
+ };
119
+ var defaultTextPresets = [
120
+ {
121
+ id: "modern-bold",
122
+ name: "Modern bold",
123
+ previewText: "MAKE IT YOURS",
124
+ fontFamily: "Arial",
125
+ fontSize: 68,
126
+ width: 460,
127
+ color: "#17191c"
128
+ },
129
+ {
130
+ id: "editorial-serif",
131
+ name: "Editorial serif",
132
+ previewText: "Made for you",
133
+ fontFamily: "Georgia",
134
+ fontSize: 64,
135
+ width: 480,
136
+ color: "#1f2933"
137
+ },
138
+ {
139
+ id: "clean-label",
140
+ name: "Clean label",
141
+ previewText: "CUSTOM EDITION",
142
+ fontFamily: "Trebuchet MS",
143
+ fontSize: 52,
144
+ width: 420,
145
+ color: "#0b66c3"
146
+ },
147
+ {
148
+ id: "signature",
149
+ name: "Signature",
150
+ previewText: "Yours truly",
151
+ fontFamily: "Brush Script MT",
152
+ fontSize: 76,
153
+ width: 440,
154
+ color: "#7a3e2f"
155
+ }
156
+ ];
157
+ function positiveInteger(value, fallback, name) {
158
+ const resolved = value ?? fallback;
159
+ if (!Number.isInteger(resolved) || resolved < 1) throw new RangeError(`${name} must be a positive integer`);
160
+ return resolved;
161
+ }
162
+ function uniqueAssets(assets, name) {
163
+ const ids = /* @__PURE__ */ new Set();
164
+ return assets.map((asset) => {
165
+ const id = asset.id.trim();
166
+ const label = asset.name.trim();
167
+ const url = asset.url.trim();
168
+ if (!id || !label || !url) throw new TypeError(`${name} assets require id, name, and url`);
169
+ if (ids.has(id)) throw new TypeError(`${name} asset id is duplicated: ${id}`);
170
+ ids.add(id);
171
+ return {
172
+ ...asset,
173
+ id,
174
+ name: label,
175
+ url
176
+ };
177
+ });
178
+ }
179
+ function uniqueTextPresets(presets) {
180
+ const ids = /* @__PURE__ */ new Set();
181
+ return presets.map((preset) => {
182
+ const id = preset.id.trim();
183
+ const name = preset.name.trim();
184
+ if (!id || !name || !preset.fontFamily.trim()) throw new TypeError("Text presets require id, name, and fontFamily");
185
+ if (ids.has(id)) throw new TypeError(`Text preset id is duplicated: ${id}`);
186
+ if (preset.fontSize <= 0 || preset.width <= 0) throw new RangeError(`Text preset ${id} requires positive fontSize and width`);
187
+ ids.add(id);
188
+ return {
189
+ ...preset,
190
+ id,
191
+ name,
192
+ fontFamily: preset.fontFamily.trim()
193
+ };
194
+ });
195
+ }
196
+ function normalizeBranding(branding) {
197
+ return {
198
+ logoUrl: branding?.logoUrl?.trim() || "data:image/png;base64,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",
199
+ logoAlt: branding?.logoAlt?.trim() || "CustomForge",
200
+ showLogo: branding?.showLogo ?? true,
201
+ title: branding?.title ?? "CustomForge",
202
+ showTitle: branding?.showTitle ?? true,
203
+ subtitle: branding?.subtitle ?? "Product customization studio",
204
+ showSubtitle: branding?.showSubtitle ?? true
205
+ };
206
+ }
207
+ /**
208
+ * 补全并校验 Workbench 默认配置
209
+ *
210
+ * @param options 外部 Workbench 配置
211
+ * @returns 不包含挂载容器的完整内部配置
212
+ */
213
+ function normalizeWorkbenchOptions(options) {
214
+ return {
215
+ editorWidth: positiveInteger(options.editorWidth, 1024, "editorWidth"),
216
+ editorHeight: positiveInteger(options.editorHeight, 512, "editorHeight"),
217
+ historyLimit: positiveInteger(options.historyLimit, 50, "historyLimit"),
218
+ product: options.product,
219
+ features: {
220
+ ...defaultFeatures,
221
+ ...options.features
222
+ },
223
+ layout: {
224
+ ...defaultLayout,
225
+ ...options.layout
226
+ },
227
+ branding: normalizeBranding(options.branding),
228
+ labels: {
229
+ ...defaultLabels,
230
+ ...options.labels
231
+ },
232
+ theme: {
233
+ ...defaultTheme,
234
+ ...options.theme
235
+ },
236
+ icons: {
237
+ enabled: options.icons?.enabled ?? true,
238
+ sources: { ...options.icons?.sources }
239
+ },
240
+ textPresets: uniqueTextPresets(options.textPresets ?? defaultTextPresets),
241
+ backgrounds: uniqueAssets(options.assets?.backgrounds ?? builtInAssetLibrary.backgrounds, "Background"),
242
+ elements: uniqueAssets(options.assets?.elements ?? builtInAssetLibrary.elements, "Element")
243
+ };
244
+ }
245
+ //#endregion
246
+ //#region node_modules/.pnpm/lucide@1.34.0/node_modules/lucide/dist/esm/defaultAttributes.mjs
247
+ /**
248
+ * @license lucide v1.34.0 - ISC
249
+ *
250
+ * This source code is licensed under the ISC license.
251
+ * See the LICENSE file in the root directory of this source tree.
252
+ */
253
+ var defaultAttributes = {
254
+ xmlns: "http://www.w3.org/2000/svg",
255
+ width: 24,
256
+ height: 24,
257
+ viewBox: "0 0 24 24",
258
+ fill: "none",
259
+ stroke: "currentColor",
260
+ "stroke-width": 2,
261
+ "stroke-linecap": "round",
262
+ "stroke-linejoin": "round"
263
+ };
264
+ //#endregion
265
+ //#region node_modules/.pnpm/lucide@1.34.0/node_modules/lucide/dist/esm/createElement.mjs
266
+ /**
267
+ * @license lucide v1.34.0 - ISC
268
+ *
269
+ * This source code is licensed under the ISC license.
270
+ * See the LICENSE file in the root directory of this source tree.
271
+ */
272
+ var createSVGElement = ([tag, attrs, children]) => {
273
+ const element = document.createElementNS("http://www.w3.org/2000/svg", tag);
274
+ Object.keys(attrs).forEach((name) => {
275
+ element.setAttribute(name, String(attrs[name]));
276
+ });
277
+ if (children?.length) children.forEach((child) => {
278
+ const childElement = createSVGElement(child);
279
+ element.appendChild(childElement);
280
+ });
281
+ return element;
282
+ };
283
+ var createElement = (iconNode, customAttrs = {}) => {
284
+ return createSVGElement([
285
+ "svg",
286
+ {
287
+ ...defaultAttributes,
288
+ ...customAttrs
289
+ },
290
+ iconNode
291
+ ]);
292
+ };
293
+ //#endregion
294
+ //#region node_modules/.pnpm/lucide@1.34.0/node_modules/lucide/dist/esm/shared/src/utils/hasA11yProp.mjs
295
+ /**
296
+ * @license lucide v1.34.0 - ISC
297
+ *
298
+ * This source code is licensed under the ISC license.
299
+ * See the LICENSE file in the root directory of this source tree.
300
+ */
301
+ var hasA11yProp = (props) => {
302
+ for (const prop in props) if (prop.startsWith("aria-") || prop === "role" || prop === "title") return true;
303
+ return false;
304
+ };
305
+ //#endregion
306
+ //#region node_modules/.pnpm/lucide@1.34.0/node_modules/lucide/dist/esm/shared/src/utils/mergeClasses.mjs
307
+ /**
308
+ * @license lucide v1.34.0 - ISC
309
+ *
310
+ * This source code is licensed under the ISC license.
311
+ * See the LICENSE file in the root directory of this source tree.
312
+ */
313
+ var mergeClasses = (...classes) => classes.filter((className, index, array) => {
314
+ return Boolean(className) && className.trim() !== "" && array.indexOf(className) === index;
315
+ }).join(" ").trim();
316
+ //#endregion
317
+ //#region node_modules/.pnpm/lucide@1.34.0/node_modules/lucide/dist/esm/shared/src/utils/toCamelCase.mjs
318
+ /**
319
+ * @license lucide v1.34.0 - ISC
320
+ *
321
+ * This source code is licensed under the ISC license.
322
+ * See the LICENSE file in the root directory of this source tree.
323
+ */
324
+ var toCamelCase = (string) => string.replace(/^([A-Z])|[\s-_]+(\w)/g, (match, p1, p2) => p2 ? p2.toUpperCase() : p1.toLowerCase());
325
+ //#endregion
326
+ //#region node_modules/.pnpm/lucide@1.34.0/node_modules/lucide/dist/esm/shared/src/utils/toPascalCase.mjs
327
+ /**
328
+ * @license lucide v1.34.0 - ISC
329
+ *
330
+ * This source code is licensed under the ISC license.
331
+ * See the LICENSE file in the root directory of this source tree.
332
+ */
333
+ var toPascalCase = (string) => {
334
+ const camelCase = toCamelCase(string);
335
+ return camelCase.charAt(0).toUpperCase() + camelCase.slice(1);
336
+ };
337
+ //#endregion
338
+ //#region node_modules/.pnpm/lucide@1.34.0/node_modules/lucide/dist/esm/replaceElement.mjs
339
+ /**
340
+ * @license lucide v1.34.0 - ISC
341
+ *
342
+ * This source code is licensed under the ISC license.
343
+ * See the LICENSE file in the root directory of this source tree.
344
+ */
345
+ var getAttrs = (element) => Array.from(element.attributes).reduce((attrs, attr) => {
346
+ attrs[attr.name] = attr.value;
347
+ return attrs;
348
+ }, {});
349
+ var getClassNames = (attrs) => {
350
+ if (typeof attrs === "string") return attrs;
351
+ if (!attrs || !attrs.class) return "";
352
+ if (attrs.class && typeof attrs.class === "string") return attrs.class.split(" ");
353
+ if (attrs.class && Array.isArray(attrs.class)) return attrs.class;
354
+ return "";
355
+ };
356
+ var replaceElement = (element, { nameAttr, icons, attrs }) => {
357
+ const iconName = element.getAttribute(nameAttr);
358
+ if (iconName == null) return;
359
+ const iconNode = icons[toPascalCase(iconName)];
360
+ if (!iconNode) return console.warn(`${element.outerHTML} icon name was not found in the provided icons object.`);
361
+ const elementAttrs = getAttrs(element);
362
+ const ariaProps = hasA11yProp(elementAttrs) ? {} : { "aria-hidden": "true" };
363
+ const iconAttrs = {
364
+ ...defaultAttributes,
365
+ "data-lucide": iconName,
366
+ ...ariaProps,
367
+ ...attrs,
368
+ ...elementAttrs
369
+ };
370
+ const elementClassNames = getClassNames(elementAttrs);
371
+ const className = getClassNames(attrs);
372
+ const classNames = mergeClasses("lucide", `lucide-${iconName}`, ...elementClassNames, ...className);
373
+ if (classNames) Object.assign(iconAttrs, { class: classNames });
374
+ const svgElement = createElement(iconNode, iconAttrs);
375
+ return element.parentNode?.replaceChild(svgElement, element);
376
+ };
377
+ //#endregion
378
+ //#region node_modules/.pnpm/lucide@1.34.0/node_modules/lucide/dist/esm/icons/arrow-down.mjs
379
+ /**
380
+ * @license lucide v1.34.0 - ISC
381
+ *
382
+ * This source code is licensed under the ISC license.
383
+ * See the LICENSE file in the root directory of this source tree.
384
+ */
385
+ var ArrowDown = [["path", { d: "M12 5v14" }], ["path", { d: "m19 12-7 7-7-7" }]];
386
+ //#endregion
387
+ //#region node_modules/.pnpm/lucide@1.34.0/node_modules/lucide/dist/esm/icons/arrow-up.mjs
388
+ /**
389
+ * @license lucide v1.34.0 - ISC
390
+ *
391
+ * This source code is licensed under the ISC license.
392
+ * See the LICENSE file in the root directory of this source tree.
393
+ */
394
+ var ArrowUp = [["path", { d: "m5 12 7-7 7 7" }], ["path", { d: "M12 19V5" }]];
395
+ //#endregion
396
+ //#region node_modules/.pnpm/lucide@1.34.0/node_modules/lucide/dist/esm/icons/download.mjs
397
+ /**
398
+ * @license lucide v1.34.0 - ISC
399
+ *
400
+ * This source code is licensed under the ISC license.
401
+ * See the LICENSE file in the root directory of this source tree.
402
+ */
403
+ var Download = [
404
+ ["path", { d: "M12 15V3" }],
405
+ ["path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }],
406
+ ["path", { d: "m7 10 5 5 5-5" }]
407
+ ];
408
+ //#endregion
409
+ //#region node_modules/.pnpm/lucide@1.34.0/node_modules/lucide/dist/esm/icons/eye-off.mjs
410
+ /**
411
+ * @license lucide v1.34.0 - ISC
412
+ *
413
+ * This source code is licensed under the ISC license.
414
+ * See the LICENSE file in the root directory of this source tree.
415
+ */
416
+ var EyeOff = [
417
+ ["path", { d: "M10.733 5.076a10.744 10.744 0 0 1 11.205 6.575 1 1 0 0 1 0 .696 10.747 10.747 0 0 1-1.444 2.49" }],
418
+ ["path", { d: "M14.084 14.158a3 3 0 0 1-4.242-4.242" }],
419
+ ["path", { d: "M17.479 17.499a10.75 10.75 0 0 1-15.417-5.151 1 1 0 0 1 0-.696 10.75 10.75 0 0 1 4.446-5.143" }],
420
+ ["path", { d: "m2 2 20 20" }]
421
+ ];
422
+ //#endregion
423
+ //#region node_modules/.pnpm/lucide@1.34.0/node_modules/lucide/dist/esm/icons/eye.mjs
424
+ /**
425
+ * @license lucide v1.34.0 - ISC
426
+ *
427
+ * This source code is licensed under the ISC license.
428
+ * See the LICENSE file in the root directory of this source tree.
429
+ */
430
+ var Eye = [["path", { d: "M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0" }], ["circle", {
431
+ cx: "12",
432
+ cy: "12",
433
+ r: "3"
434
+ }]];
435
+ //#endregion
436
+ //#region node_modules/.pnpm/lucide@1.34.0/node_modules/lucide/dist/esm/icons/folder-open.mjs
437
+ /**
438
+ * @license lucide v1.34.0 - ISC
439
+ *
440
+ * This source code is licensed under the ISC license.
441
+ * See the LICENSE file in the root directory of this source tree.
442
+ */
443
+ var FolderOpen = [["path", { d: "m6 14 1.5-2.9A2 2 0 0 1 9.24 10H20a2 2 0 0 1 1.94 2.5l-1.54 6a2 2 0 0 1-1.95 1.5H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H18a2 2 0 0 1 2 2v2" }]];
444
+ //#endregion
445
+ //#region node_modules/.pnpm/lucide@1.34.0/node_modules/lucide/dist/esm/icons/image-plus.mjs
446
+ /**
447
+ * @license lucide v1.34.0 - ISC
448
+ *
449
+ * This source code is licensed under the ISC license.
450
+ * See the LICENSE file in the root directory of this source tree.
451
+ */
452
+ var ImagePlus = [
453
+ ["path", { d: "M16 5h6" }],
454
+ ["path", { d: "M19 2v6" }],
455
+ ["path", { d: "M21 11.5V19a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h7.5" }],
456
+ ["path", { d: "m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21" }],
457
+ ["circle", {
458
+ cx: "9",
459
+ cy: "9",
460
+ r: "2"
461
+ }]
462
+ ];
463
+ //#endregion
464
+ //#region node_modules/.pnpm/lucide@1.34.0/node_modules/lucide/dist/esm/icons/image.mjs
465
+ /**
466
+ * @license lucide v1.34.0 - ISC
467
+ *
468
+ * This source code is licensed under the ISC license.
469
+ * See the LICENSE file in the root directory of this source tree.
470
+ */
471
+ var Image = [
472
+ ["rect", {
473
+ width: "18",
474
+ height: "18",
475
+ x: "3",
476
+ y: "3",
477
+ rx: "2",
478
+ ry: "2"
479
+ }],
480
+ ["circle", {
481
+ cx: "9",
482
+ cy: "9",
483
+ r: "2"
484
+ }],
485
+ ["path", { d: "m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21" }]
486
+ ];
487
+ //#endregion
488
+ //#region node_modules/.pnpm/lucide@1.34.0/node_modules/lucide/dist/esm/icons/layers.mjs
489
+ /**
490
+ * @license lucide v1.34.0 - ISC
491
+ *
492
+ * This source code is licensed under the ISC license.
493
+ * See the LICENSE file in the root directory of this source tree.
494
+ */
495
+ var Layers = [
496
+ ["path", { d: "M12.83 2.18a2 2 0 0 0-1.66 0L2.6 6.08a1 1 0 0 0 0 1.83l8.58 3.91a2 2 0 0 0 1.66 0l8.58-3.9a1 1 0 0 0 0-1.83z" }],
497
+ ["path", { d: "M2 12a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9A1 1 0 0 0 22 12" }],
498
+ ["path", { d: "M2 17a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9A1 1 0 0 0 22 17" }]
499
+ ];
500
+ //#endregion
501
+ //#region node_modules/.pnpm/lucide@1.34.0/node_modules/lucide/dist/esm/icons/link-2.mjs
502
+ /**
503
+ * @license lucide v1.34.0 - ISC
504
+ *
505
+ * This source code is licensed under the ISC license.
506
+ * See the LICENSE file in the root directory of this source tree.
507
+ */
508
+ var Link2 = [
509
+ ["path", { d: "M9 17H7A5 5 0 0 1 7 7h2" }],
510
+ ["path", { d: "M15 7h2a5 5 0 1 1 0 10h-2" }],
511
+ ["line", {
512
+ x1: "8",
513
+ x2: "16",
514
+ y1: "12",
515
+ y2: "12"
516
+ }]
517
+ ];
518
+ //#endregion
519
+ //#region node_modules/.pnpm/lucide@1.34.0/node_modules/lucide/dist/esm/icons/lock-open.mjs
520
+ /**
521
+ * @license lucide v1.34.0 - ISC
522
+ *
523
+ * This source code is licensed under the ISC license.
524
+ * See the LICENSE file in the root directory of this source tree.
525
+ */
526
+ var LockOpen = [["rect", {
527
+ width: "18",
528
+ height: "11",
529
+ x: "3",
530
+ y: "11",
531
+ rx: "2",
532
+ ry: "2"
533
+ }], ["path", { d: "M7 11V7a5 5 0 0 1 9.9-1" }]];
534
+ //#endregion
535
+ //#region node_modules/.pnpm/lucide@1.34.0/node_modules/lucide/dist/esm/icons/lock.mjs
536
+ /**
537
+ * @license lucide v1.34.0 - ISC
538
+ *
539
+ * This source code is licensed under the ISC license.
540
+ * See the LICENSE file in the root directory of this source tree.
541
+ */
542
+ var Lock = [["rect", {
543
+ width: "18",
544
+ height: "11",
545
+ x: "3",
546
+ y: "11",
547
+ rx: "2",
548
+ ry: "2"
549
+ }], ["path", { d: "M7 11V7a5 5 0 0 1 10 0v4" }]];
550
+ //#endregion
551
+ //#region node_modules/.pnpm/lucide@1.34.0/node_modules/lucide/dist/esm/icons/redo-2.mjs
552
+ /**
553
+ * @license lucide v1.34.0 - ISC
554
+ *
555
+ * This source code is licensed under the ISC license.
556
+ * See the LICENSE file in the root directory of this source tree.
557
+ */
558
+ var Redo2 = [["path", { d: "m15 14 5-5-5-5" }], ["path", { d: "M20 9H9.5A5.5 5.5 0 0 0 4 14.5A5.5 5.5 0 0 0 9.5 20H13" }]];
559
+ //#endregion
560
+ //#region node_modules/.pnpm/lucide@1.34.0/node_modules/lucide/dist/esm/icons/rotate-ccw.mjs
561
+ /**
562
+ * @license lucide v1.34.0 - ISC
563
+ *
564
+ * This source code is licensed under the ISC license.
565
+ * See the LICENSE file in the root directory of this source tree.
566
+ */
567
+ var RotateCcw = [["path", { d: "M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" }], ["path", { d: "M3 3v5h5" }]];
568
+ //#endregion
569
+ //#region node_modules/.pnpm/lucide@1.34.0/node_modules/lucide/dist/esm/icons/save.mjs
570
+ /**
571
+ * @license lucide v1.34.0 - ISC
572
+ *
573
+ * This source code is licensed under the ISC license.
574
+ * See the LICENSE file in the root directory of this source tree.
575
+ */
576
+ var Save = [
577
+ ["path", { d: "M15.2 3a2 2 0 0 1 1.4.6l3.8 3.8a2 2 0 0 1 .6 1.4V19a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2z" }],
578
+ ["path", { d: "M17 21v-7a1 1 0 0 0-1-1H8a1 1 0 0 0-1 1v7" }],
579
+ ["path", { d: "M7 3v4a1 1 0 0 0 1 1h7" }]
580
+ ];
581
+ //#endregion
582
+ //#region node_modules/.pnpm/lucide@1.34.0/node_modules/lucide/dist/esm/icons/trash-2.mjs
583
+ /**
584
+ * @license lucide v1.34.0 - ISC
585
+ *
586
+ * This source code is licensed under the ISC license.
587
+ * See the LICENSE file in the root directory of this source tree.
588
+ */
589
+ var Trash2 = [
590
+ ["path", { d: "M10 11v6" }],
591
+ ["path", { d: "M14 11v6" }],
592
+ ["path", { d: "M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6" }],
593
+ ["path", { d: "M3 6h18" }],
594
+ ["path", { d: "M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" }]
595
+ ];
596
+ //#endregion
597
+ //#region node_modules/.pnpm/lucide@1.34.0/node_modules/lucide/dist/esm/icons/type.mjs
598
+ /**
599
+ * @license lucide v1.34.0 - ISC
600
+ *
601
+ * This source code is licensed under the ISC license.
602
+ * See the LICENSE file in the root directory of this source tree.
603
+ */
604
+ var Type = [
605
+ ["path", { d: "M12 4v16" }],
606
+ ["path", { d: "M4 7V5a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v2" }],
607
+ ["path", { d: "M9 20h6" }]
608
+ ];
609
+ //#endregion
610
+ //#region node_modules/.pnpm/lucide@1.34.0/node_modules/lucide/dist/esm/icons/undo-2.mjs
611
+ /**
612
+ * @license lucide v1.34.0 - ISC
613
+ *
614
+ * This source code is licensed under the ISC license.
615
+ * See the LICENSE file in the root directory of this source tree.
616
+ */
617
+ var Undo2 = [["path", { d: "M9 14 4 9l5-5" }], ["path", { d: "M4 9h10.5a5.5 5.5 0 0 1 5.5 5.5a5.5 5.5 0 0 1-5.5 5.5H11" }]];
618
+ //#endregion
619
+ //#region node_modules/.pnpm/lucide@1.34.0/node_modules/lucide/dist/esm/icons/upload.mjs
620
+ /**
621
+ * @license lucide v1.34.0 - ISC
622
+ *
623
+ * This source code is licensed under the ISC license.
624
+ * See the LICENSE file in the root directory of this source tree.
625
+ */
626
+ var Upload = [
627
+ ["path", { d: "M12 3v12" }],
628
+ ["path", { d: "m17 8-5-5-5 5" }],
629
+ ["path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }]
630
+ ];
631
+ //#endregion
632
+ //#region node_modules/.pnpm/lucide@1.34.0/node_modules/lucide/dist/esm/icons/x.mjs
633
+ /**
634
+ * @license lucide v1.34.0 - ISC
635
+ *
636
+ * This source code is licensed under the ISC license.
637
+ * See the LICENSE file in the root directory of this source tree.
638
+ */
639
+ var X = [["path", { d: "M18 6 6 18" }], ["path", { d: "m6 6 12 12" }]];
640
+ //#endregion
641
+ //#region node_modules/.pnpm/lucide@1.34.0/node_modules/lucide/dist/esm/lucide.mjs
642
+ /**
643
+ * @license lucide v1.34.0 - ISC
644
+ *
645
+ * This source code is licensed under the ISC license.
646
+ * See the LICENSE file in the root directory of this source tree.
647
+ */
648
+ var createIcons = ({ icons = {}, nameAttr = "data-lucide", attrs = {}, root = document, inTemplates } = {}) => {
649
+ if (!Object.values(icons).length) throw new Error("Please provide an icons object.\nIf you want to use all the icons you can import it like:\n `import { createIcons, icons } from 'lucide';\nlucide.createIcons({icons});`");
650
+ if (typeof root === "undefined") throw new Error("`createIcons()` only works in a browser environment.");
651
+ Array.from(root.querySelectorAll(`[${nameAttr}]`)).forEach((element) => replaceElement(element, {
652
+ nameAttr,
653
+ icons,
654
+ attrs
655
+ }));
656
+ if (inTemplates) Array.from(root.querySelectorAll("template")).forEach((template) => createIcons({
657
+ icons,
658
+ nameAttr,
659
+ attrs,
660
+ root: template.content,
661
+ inTemplates
662
+ }));
663
+ if (nameAttr === "data-lucide") {
664
+ const deprecatedElements = root.querySelectorAll("[icon-name]");
665
+ if (deprecatedElements.length > 0) {
666
+ console.warn("[Lucide] Some icons were found with the now deprecated icon-name attribute. These will still be replaced for backwards compatibility, but will no longer be supported in v1.0 and you should switch to data-lucide");
667
+ Array.from(deprecatedElements).forEach((element) => replaceElement(element, {
668
+ nameAttr: "icon-name",
669
+ icons,
670
+ attrs
671
+ }));
672
+ }
673
+ }
674
+ };
675
+ //#endregion
676
+ //#region src/workbench/icons.ts
677
+ /** 渲染 Workbench 内置图标并应用调用方覆盖 */
678
+ function renderWorkbenchIcons(root, configuration) {
679
+ root.dataset.icons = configuration.enabled ? "visible" : "hidden";
680
+ createIcons({
681
+ nameAttr: "data-customforge-icon",
682
+ icons: {
683
+ ArrowDown,
684
+ ArrowUp,
685
+ Download,
686
+ Eye,
687
+ EyeOff,
688
+ FolderOpen,
689
+ Image,
690
+ ImagePlus,
691
+ Layers3: Layers,
692
+ Link2,
693
+ Lock,
694
+ LockOpen,
695
+ Redo2,
696
+ RotateCcw,
697
+ Save,
698
+ Trash2,
699
+ Type,
700
+ Undo2,
701
+ Upload,
702
+ X
703
+ },
704
+ attrs: {
705
+ width: 18,
706
+ height: 18,
707
+ "stroke-width": 1.8,
708
+ "aria-hidden": "true"
709
+ }
710
+ });
711
+ root.querySelectorAll("[data-icon-slot]").forEach((element) => {
712
+ const name = element.dataset.iconSlot;
713
+ const source = configuration.sources[name];
714
+ const control = element.closest("button");
715
+ if (!configuration.enabled || source === null) {
716
+ element.hidden = true;
717
+ if (source === null) control?.classList.add("customforge-workbench__icon-missing");
718
+ return;
719
+ }
720
+ control?.classList.remove("customforge-workbench__icon-missing");
721
+ if (!source) {
722
+ element.hidden = false;
723
+ return;
724
+ }
725
+ if (element instanceof HTMLImageElement && element.getAttribute("src") === source) {
726
+ element.hidden = false;
727
+ return;
728
+ }
729
+ const image = document.createElement("img");
730
+ image.className = "customforge-workbench__custom-icon";
731
+ image.dataset.iconSlot = name;
732
+ image.src = source;
733
+ image.alt = "";
734
+ image.setAttribute("aria-hidden", "true");
735
+ element.replaceWith(image);
736
+ });
737
+ }
738
+ //#endregion
739
+ //#region src/workbench/template.ts
740
+ /** 创建 Workbench 使用的静态 DOM 结构 */
741
+ function createWorkbenchElement() {
742
+ const root = document.createElement("main");
743
+ root.className = "customforge-workbench";
744
+ root.innerHTML = `
745
+ <header class="customforge-workbench__topbar" data-layout="header">
746
+ <div class="customforge-workbench__brand" data-role="brand">
747
+ <img class="customforge-workbench__brand-logo" data-role="brand-logo" alt="">
748
+ <span class="customforge-workbench__brand-copy" data-role="brand-copy">
749
+ <strong data-role="brand-title"></strong>
750
+ <span data-role="brand-subtitle"></span>
751
+ </span>
752
+ </div>
753
+ <div class="customforge-workbench__global-actions">
754
+ <button class="customforge-workbench__button customforge-workbench__button--secondary" data-action="load-remote" data-feature="loadRemoteProduct" data-control-label="loadProduct" type="button">
755
+ <i data-customforge-icon="link-2" data-icon-slot="loadProduct"></i>
756
+ <span data-label="loadProduct"></span>
757
+ </button>
758
+ <button class="customforge-workbench__button customforge-workbench__button--primary" data-action="export-texture" data-feature="exportTexture" data-control-label="exportTexture" type="button">
759
+ <i data-customforge-icon="download" data-icon-slot="exportTexture"></i>
760
+ <span data-label="exportTexture"></span>
761
+ </button>
762
+ </div>
763
+ </header>
764
+
765
+ <section class="customforge-workbench__workspace">
766
+ <section class="customforge-workbench__panel customforge-workbench__panel--editor" data-region-label="editorTitle">
767
+ <header class="customforge-workbench__panelbar" data-layout="editorHeader">
768
+ <div class="customforge-workbench__panel-title">
769
+ <span class="customforge-workbench__panel-kicker">2D</span>
770
+ <h1 data-label="editorTitle"></h1>
771
+ </div>
772
+ <span class="customforge-workbench__resolution" data-role="resolution">1024 x 512</span>
773
+ </header>
774
+
775
+ <div class="customforge-workbench__toolbar" data-layout="toolbar" data-region-label="workbench" role="toolbar">
776
+ <div class="customforge-workbench__tool-group" data-tool-group="history">
777
+ <button class="customforge-workbench__icon-button" data-action="undo" data-feature="undoRedo" data-control-label="undo" type="button" disabled>
778
+ <i data-customforge-icon="undo-2" data-icon-slot="undo"></i>
779
+ <span class="customforge-workbench__icon-label" data-label="undo"></span>
780
+ </button>
781
+ <button class="customforge-workbench__icon-button" data-action="redo" data-feature="undoRedo" data-control-label="redo" type="button" disabled>
782
+ <i data-customforge-icon="redo-2" data-icon-slot="redo"></i>
783
+ <span class="customforge-workbench__icon-label" data-label="redo"></span>
784
+ </button>
785
+ </div>
786
+ <span class="customforge-workbench__separator" data-tool-group="history-separator" aria-hidden="true"></span>
787
+ <div class="customforge-workbench__tool-group" data-tool-group="insert">
788
+ <button class="customforge-workbench__tool-button" data-action="open-text-dialog" data-feature="addText" data-control-label="addText" type="button">
789
+ <i data-customforge-icon="type" data-icon-slot="addText"></i>
790
+ <span data-label="addText"></span>
791
+ </button>
792
+ <button class="customforge-workbench__tool-button" data-action="open-image-dialog" data-feature="addImage" data-control-label="addImage" type="button">
793
+ <i data-customforge-icon="image-plus" data-icon-slot="addImage"></i>
794
+ <span data-label="addImage"></span>
795
+ </button>
796
+ </div>
797
+ <span class="customforge-workbench__separator" data-tool-group="document-separator" aria-hidden="true"></span>
798
+ <div class="customforge-workbench__tool-group" data-tool-group="document">
799
+ <button class="customforge-workbench__icon-button" data-action="save-design" data-feature="saveDesign" data-control-label="saveDesign" type="button">
800
+ <i data-customforge-icon="save" data-icon-slot="saveDesign"></i>
801
+ <span class="customforge-workbench__icon-label" data-label="saveDesign"></span>
802
+ </button>
803
+ <button class="customforge-workbench__icon-button" data-action="load-design" data-feature="loadDesign" data-control-label="loadDesign" type="button">
804
+ <i data-customforge-icon="folder-open" data-icon-slot="loadDesign"></i>
805
+ <span class="customforge-workbench__icon-label" data-label="loadDesign"></span>
806
+ </button>
807
+ </div>
808
+ <span class="customforge-workbench__separator" data-tool-group="selection-separator" aria-hidden="true"></span>
809
+ <button class="customforge-workbench__icon-button customforge-workbench__icon-button--danger" data-action="delete-selection" data-feature="deleteSelection" data-control-label="deleteSelection" type="button" disabled>
810
+ <i data-customforge-icon="trash-2" data-icon-slot="deleteSelection"></i>
811
+ <span class="customforge-workbench__icon-label" data-label="deleteSelection"></span>
812
+ </button>
813
+ </div>
814
+
815
+ <div class="customforge-workbench__editor-body">
816
+ <div class="customforge-workbench__editor-stage" data-role="editor-host"></div>
817
+ <aside class="customforge-workbench__layers" data-layout="layers" data-region-label="layers">
818
+ <header class="customforge-workbench__layers-header">
819
+ <span>
820
+ <i data-customforge-icon="layers-3" data-icon-slot="layers"></i>
821
+ <span data-label="layers"></span>
822
+ </span>
823
+ <span data-role="layer-count">0</span>
824
+ </header>
825
+ <ol class="customforge-workbench__layer-list" data-role="layer-list"></ol>
826
+ </aside>
827
+ </div>
828
+ </section>
829
+
830
+ <section class="customforge-workbench__panel customforge-workbench__panel--viewer" data-region-label="viewerTitle">
831
+ <header class="customforge-workbench__panelbar" data-layout="viewerHeader">
832
+ <div class="customforge-workbench__panel-title">
833
+ <span class="customforge-workbench__panel-kicker">3D</span>
834
+ <h2 data-label="viewerTitle"></h2>
835
+ </div>
836
+ <button class="customforge-workbench__icon-button" data-action="reset-view" data-feature="resetView" data-control-label="resetView" type="button">
837
+ <i data-customforge-icon="rotate-ccw" data-icon-slot="resetView"></i>
838
+ <span class="customforge-workbench__icon-label" data-label="resetView"></span>
839
+ </button>
840
+ </header>
841
+ <div class="customforge-workbench__viewer-stage" data-role="viewer-host"></div>
842
+ </section>
843
+ </section>
844
+
845
+ <footer class="customforge-workbench__statusbar" data-layout="status">
846
+ <span class="customforge-workbench__status-indicator" aria-hidden="true"></span>
847
+ <span data-role="status-label" role="status" data-label="starting"></span>
848
+ <span class="customforge-workbench__object-count" data-role="object-count"></span>
849
+ </footer>
850
+
851
+ <input data-role="image-input" type="file" accept="image/png,image/jpeg,image/webp" hidden>
852
+ <input data-role="design-input" type="file" accept="application/json,.json" hidden>
853
+
854
+ <dialog class="customforge-workbench__dialog customforge-workbench__dialog--text" data-role="text-dialog">
855
+ <form data-role="text-form">
856
+ <header class="customforge-workbench__dialog-header">
857
+ <div>
858
+ <span class="customforge-workbench__eyebrow" data-label="textDialogEyebrow"></span>
859
+ <h2 data-label="textDialogTitle"></h2>
860
+ </div>
861
+ <button class="customforge-workbench__icon-button" data-action="close-text-dialog" data-control-label="close" type="button">
862
+ <i data-customforge-icon="x" data-icon-slot="close"></i>
863
+ <span class="customforge-workbench__icon-label" data-label="close"></span>
864
+ </button>
865
+ </header>
866
+ <div class="customforge-workbench__dialog-body">
867
+ <label class="customforge-workbench__field customforge-workbench__field--prominent">
868
+ <span data-label="textInputLabel"></span>
869
+ <input data-role="text-value" type="text" maxlength="160" data-placeholder-label="textInputPlaceholder" required>
870
+ </label>
871
+ <fieldset class="customforge-workbench__fieldset">
872
+ <legend data-label="textPresets"></legend>
873
+ <div class="customforge-workbench__preset-grid customforge-workbench__preset-grid--text" data-role="text-presets"></div>
874
+ </fieldset>
875
+ <label class="customforge-workbench__field customforge-workbench__color-field">
876
+ <span data-label="textColor"></span>
877
+ <input data-role="text-color" type="color" value="#17191c">
878
+ </label>
879
+ </div>
880
+ <footer class="customforge-workbench__dialog-actions">
881
+ <button class="customforge-workbench__button customforge-workbench__button--secondary" data-action="close-text-dialog" data-label="cancel" type="button"></button>
882
+ <button class="customforge-workbench__button customforge-workbench__button--primary" data-action="submit-text" data-label="addTextConfirm" type="submit"></button>
883
+ </footer>
884
+ </form>
885
+ </dialog>
886
+
887
+ <dialog class="customforge-workbench__dialog customforge-workbench__dialog--image" data-role="image-dialog">
888
+ <form data-role="image-form">
889
+ <header class="customforge-workbench__dialog-header">
890
+ <div>
891
+ <span class="customforge-workbench__eyebrow" data-label="imageDialogEyebrow"></span>
892
+ <h2 data-label="imageDialogTitle"></h2>
893
+ </div>
894
+ <button class="customforge-workbench__icon-button" data-action="close-image-dialog" data-control-label="close" type="button">
895
+ <i data-customforge-icon="x" data-icon-slot="close"></i>
896
+ <span class="customforge-workbench__icon-label" data-label="close"></span>
897
+ </button>
898
+ </header>
899
+ <div class="customforge-workbench__dialog-body">
900
+ <div class="customforge-workbench__tabs" role="tablist">
901
+ <button data-image-tab="upload" data-label="uploadImageTab" type="button" role="tab"></button>
902
+ <button data-image-tab="backgrounds" data-feature="presetBackgrounds" data-label="backgroundsTab" type="button" role="tab"></button>
903
+ <button data-image-tab="elements" data-feature="presetElements" data-label="elementsTab" type="button" role="tab"></button>
904
+ </div>
905
+ <section class="customforge-workbench__tab-panel" data-image-panel="upload" role="tabpanel">
906
+ <button class="customforge-workbench__upload-field" data-action="choose-image" type="button">
907
+ <span class="customforge-workbench__upload-icon"><i data-customforge-icon="upload" data-icon-slot="upload"></i></span>
908
+ <strong data-label="chooseImage"></strong>
909
+ <span data-role="image-file-name" data-label="noImageSelected"></span>
910
+ <img data-role="image-preview" alt="" hidden>
911
+ </button>
912
+ </section>
913
+ <section class="customforge-workbench__tab-panel" data-image-panel="backgrounds" role="tabpanel" hidden>
914
+ <div class="customforge-workbench__preset-grid customforge-workbench__preset-grid--assets" data-role="background-presets"></div>
915
+ </section>
916
+ <section class="customforge-workbench__tab-panel" data-image-panel="elements" role="tabpanel" hidden>
917
+ <div class="customforge-workbench__preset-grid customforge-workbench__preset-grid--assets" data-role="element-presets"></div>
918
+ </section>
919
+ </div>
920
+ <footer class="customforge-workbench__dialog-actions">
921
+ <button class="customforge-workbench__button customforge-workbench__button--secondary" data-action="close-image-dialog" data-label="cancel" type="button"></button>
922
+ <button class="customforge-workbench__button customforge-workbench__button--primary" data-action="submit-image" data-label="addImageConfirm" type="submit" disabled></button>
923
+ </footer>
924
+ </form>
925
+ </dialog>
926
+
927
+ <dialog class="customforge-workbench__dialog" data-role="product-dialog">
928
+ <form data-role="remote-form">
929
+ <header class="customforge-workbench__dialog-header">
930
+ <div>
931
+ <span class="customforge-workbench__eyebrow" data-label="productDialogEyebrow"></span>
932
+ <h2 data-label="productDialogTitle"></h2>
933
+ </div>
934
+ <button class="customforge-workbench__icon-button" data-action="close-product-dialog" data-control-label="close" type="button">
935
+ <i data-customforge-icon="x" data-icon-slot="close"></i>
936
+ <span class="customforge-workbench__icon-label" data-label="close"></span>
937
+ </button>
938
+ </header>
939
+ <div class="customforge-workbench__dialog-body customforge-workbench__form-fields">
940
+ <label class="customforge-workbench__field">
941
+ <span data-label="modelUrl"></span>
942
+ <div class="customforge-workbench__input-shell">
943
+ <i data-customforge-icon="link-2" data-icon-slot="loadProduct"></i>
944
+ <input data-role="model-url" name="modelUrl" type="url" placeholder="https://example.com/product.glb" required>
945
+ </div>
946
+ </label>
947
+ <label class="customforge-workbench__field">
948
+ <span data-label="textureUrl"></span>
949
+ <div class="customforge-workbench__input-shell">
950
+ <i data-customforge-icon="image-plus" data-icon-slot="addImage"></i>
951
+ <input data-role="texture-url" name="textureUrl" type="url" placeholder="https://example.com/texture.png">
952
+ </div>
953
+ </label>
954
+ <label class="customforge-workbench__field">
955
+ <span data-label="surfaceMesh"></span>
956
+ <input data-role="mesh-name" name="meshName" type="text" value="PrintArea" required>
957
+ </label>
958
+ <label class="customforge-workbench__check-field">
959
+ <input data-role="flip-texture" name="flipTexture" type="checkbox">
960
+ <span data-label="flipTexture"></span>
961
+ </label>
962
+ </div>
963
+ <footer class="customforge-workbench__dialog-actions">
964
+ <button class="customforge-workbench__button customforge-workbench__button--secondary" data-action="use-demo" data-label="useDemo" type="button"></button>
965
+ <button class="customforge-workbench__button customforge-workbench__button--primary" data-action="submit-product" data-control-label="loadProduct" type="submit">
966
+ <i data-customforge-icon="upload" data-icon-slot="upload"></i>
967
+ <span data-label="loadProduct"></span>
968
+ </button>
969
+ </footer>
970
+ </form>
971
+ </dialog>
972
+ `;
973
+ return root;
974
+ }
975
+ //#endregion
976
+ //#region src/workbench/CustomForgeWorkbench.ts
977
+ var DIALOG_CLOSE_DELAY_MS = 180;
978
+ function errorMessage(error) {
979
+ return error instanceof Error ? error.message : String(error);
980
+ }
981
+ function requiredElement(root, selector) {
982
+ const element = root.querySelector(selector);
983
+ if (!element) throw new Error(`Workbench element was not found: ${selector}`);
984
+ return element;
985
+ }
986
+ function downloadJson(filename, value) {
987
+ const blob = new Blob([JSON.stringify(value, null, 2)], { type: "application/json" });
988
+ const url = URL.createObjectURL(blob);
989
+ const anchor = document.createElement("a");
990
+ anchor.download = filename;
991
+ anchor.href = url;
992
+ anchor.click();
993
+ window.setTimeout(() => URL.revokeObjectURL(url), 0);
994
+ }
995
+ function applyLabels(element, labels) {
996
+ element.setAttribute("aria-label", labels.workbench);
997
+ element.querySelectorAll("[data-label]").forEach((target) => {
998
+ target.textContent = labels[target.dataset.label];
999
+ });
1000
+ element.querySelectorAll("[data-placeholder-label]").forEach((target) => {
1001
+ target.placeholder = labels[target.dataset.placeholderLabel];
1002
+ });
1003
+ element.querySelectorAll("[data-control-label]").forEach((target) => {
1004
+ const key = target.dataset.controlLabel;
1005
+ const label = labels[key] || String(key);
1006
+ target.title = label;
1007
+ target.setAttribute("aria-label", label);
1008
+ });
1009
+ element.querySelectorAll("[data-region-label]").forEach((target) => {
1010
+ const key = target.dataset.regionLabel;
1011
+ target.setAttribute("aria-label", labels[key] || String(key));
1012
+ });
1013
+ }
1014
+ function applyBranding(element, options) {
1015
+ const logo = requiredElement(element, "[data-role=\"brand-logo\"]");
1016
+ const title = requiredElement(element, "[data-role=\"brand-title\"]");
1017
+ const subtitle = requiredElement(element, "[data-role=\"brand-subtitle\"]");
1018
+ const copy = requiredElement(element, "[data-role=\"brand-copy\"]");
1019
+ const brand = requiredElement(element, "[data-role=\"brand\"]");
1020
+ logo.src = options.logoUrl;
1021
+ logo.alt = options.logoAlt;
1022
+ logo.hidden = !options.showLogo;
1023
+ title.textContent = options.title;
1024
+ title.hidden = !options.showTitle || options.title.length === 0;
1025
+ subtitle.textContent = options.subtitle;
1026
+ subtitle.hidden = !options.showSubtitle || options.subtitle.length === 0;
1027
+ copy.hidden = title.hidden && subtitle.hidden;
1028
+ brand.hidden = logo.hidden && copy.hidden;
1029
+ brand.setAttribute("aria-label", options.title || options.logoAlt);
1030
+ }
1031
+ function applyTheme(element, theme) {
1032
+ element.style.setProperty("--cfw-ink", theme.ink);
1033
+ element.style.setProperty("--cfw-muted", theme.muted);
1034
+ element.style.setProperty("--cfw-line", theme.border);
1035
+ element.style.setProperty("--cfw-surface", theme.surface);
1036
+ element.style.setProperty("--cfw-surface-muted", theme.surfaceMuted);
1037
+ element.style.setProperty("--cfw-stage", theme.stage);
1038
+ element.style.setProperty("--cfw-accent", theme.accent);
1039
+ element.style.setProperty("--cfw-accent-hover", theme.accentHover);
1040
+ element.style.setProperty("--cfw-accent-contrast", theme.accentContrast);
1041
+ element.style.setProperty("--cfw-danger", theme.danger);
1042
+ element.style.setProperty("--cfw-font-family", theme.fontFamily);
1043
+ element.style.setProperty("--cfw-control-radius", theme.controlRadius);
1044
+ }
1045
+ function formatCount(labels, count) {
1046
+ return (count === 1 ? labels.objectCountOne : labels.objectCountMany).replace("{count}", String(count));
1047
+ }
1048
+ /**
1049
+ * 提供可配置默认界面的产品定制工作台
1050
+ *
1051
+ * 功能和布局开关只控制 Workbench 自带界面
1052
+ * 底层 ProductCustomizer 仍通过 customizer 属性提供完整公共 API
1053
+ * 不再使用实例时必须调用 destroy
1054
+ */
1055
+ var CustomForgeWorkbench = class CustomForgeWorkbench {
1056
+ /** Workbench 使用的底层无界面定制器 */
1057
+ customizer;
1058
+ /** Workbench 根元素 */
1059
+ element;
1060
+ host;
1061
+ abortController = new AbortController();
1062
+ dialogCloseTimers = /* @__PURE__ */ new Map();
1063
+ unsubscribe = [];
1064
+ features;
1065
+ layout;
1066
+ labels;
1067
+ icons;
1068
+ textPresets;
1069
+ backgrounds;
1070
+ elements;
1071
+ layerList;
1072
+ deleteButton;
1073
+ undoButton;
1074
+ redoButton;
1075
+ statusLabel;
1076
+ objectCountLabel;
1077
+ imageInput;
1078
+ designInput;
1079
+ loadDesignButton;
1080
+ textDialog;
1081
+ textForm;
1082
+ imageDialog;
1083
+ imageForm;
1084
+ imageSubmitButton;
1085
+ productDialog;
1086
+ remoteForm;
1087
+ submitProductButton;
1088
+ selectedObjectIds = /* @__PURE__ */ new Set();
1089
+ selectedTextPresetId;
1090
+ selectedImage;
1091
+ activeImageTab = "upload";
1092
+ layerSignature = "";
1093
+ draggedObjectId;
1094
+ destroyed = false;
1095
+ constructor(host, element, customizer, options) {
1096
+ this.host = host;
1097
+ this.element = element;
1098
+ this.customizer = customizer;
1099
+ this.features = options.features;
1100
+ this.layout = options.layout;
1101
+ this.labels = options.labels;
1102
+ this.icons = options.icons;
1103
+ this.textPresets = options.textPresets;
1104
+ this.backgrounds = options.backgrounds;
1105
+ this.elements = options.elements;
1106
+ this.selectedTextPresetId = this.textPresets[0]?.id;
1107
+ this.layerList = requiredElement(element, "[data-role=\"layer-list\"]");
1108
+ this.deleteButton = this.action("delete-selection");
1109
+ this.undoButton = this.action("undo");
1110
+ this.redoButton = this.action("redo");
1111
+ this.statusLabel = requiredElement(element, "[data-role=\"status-label\"]");
1112
+ this.objectCountLabel = requiredElement(element, "[data-role=\"object-count\"]");
1113
+ this.imageInput = requiredElement(element, "[data-role=\"image-input\"]");
1114
+ this.designInput = requiredElement(element, "[data-role=\"design-input\"]");
1115
+ this.loadDesignButton = this.action("load-design");
1116
+ this.textDialog = requiredElement(element, "[data-role=\"text-dialog\"]");
1117
+ this.textForm = requiredElement(element, "[data-role=\"text-form\"]");
1118
+ this.imageDialog = requiredElement(element, "[data-role=\"image-dialog\"]");
1119
+ this.imageForm = requiredElement(element, "[data-role=\"image-form\"]");
1120
+ this.imageSubmitButton = this.action("submit-image");
1121
+ this.productDialog = requiredElement(element, "[data-role=\"product-dialog\"]");
1122
+ this.remoteForm = requiredElement(element, "[data-role=\"remote-form\"]");
1123
+ this.submitProductButton = this.action("submit-product");
1124
+ this.renderTextPresets();
1125
+ this.renderAssetPresets();
1126
+ this.bindEvents();
1127
+ this.applyVisibility();
1128
+ this.setImageTab("upload");
1129
+ this.updateHistoryButtons();
1130
+ this.renderLayers(true);
1131
+ this.setStatus(this.labels.productReady);
1132
+ }
1133
+ /**
1134
+ * 创建 Workbench 并完成底层定制器初始化
1135
+ *
1136
+ * @param options 挂载容器、产品资源、功能、外观和素材配置
1137
+ * @returns 初始化完成的 Workbench
1138
+ * @throws 挂载容器无效或产品初始化失败时清理已创建界面并继续抛出错误
1139
+ */
1140
+ static async create(options) {
1141
+ const host = resolveElement(options.container, "Workbench");
1142
+ const normalized = normalizeWorkbenchOptions(options);
1143
+ const element = createWorkbenchElement();
1144
+ applyLabels(element, normalized.labels);
1145
+ applyBranding(element, normalized.branding);
1146
+ applyTheme(element, normalized.theme);
1147
+ requiredElement(element, "[data-role=\"resolution\"]").textContent = `${normalized.editorWidth} x ${normalized.editorHeight}`;
1148
+ host.replaceChildren(element);
1149
+ renderWorkbenchIcons(element, normalized.icons);
1150
+ try {
1151
+ const customizer = await ProductCustomizer.create({
1152
+ editor: requiredElement(element, "[data-role=\"editor-host\"]"),
1153
+ viewer: requiredElement(element, "[data-role=\"viewer-host\"]"),
1154
+ editorWidth: normalized.editorWidth,
1155
+ editorHeight: normalized.editorHeight,
1156
+ historyLimit: normalized.historyLimit,
1157
+ product: normalized.product
1158
+ });
1159
+ return new CustomForgeWorkbench(host, element, customizer, normalized);
1160
+ } catch (error) {
1161
+ host.replaceChildren();
1162
+ throw error;
1163
+ }
1164
+ }
1165
+ /**
1166
+ * 在运行时显示或隐藏某项内置功能控件
1167
+ *
1168
+ * 该方法不限制通过 customizer 属性直接调用对应核心 API
1169
+ *
1170
+ * @param feature 功能名称
1171
+ * @param enabled 是否显示并启用对应控件
1172
+ */
1173
+ setFeature(feature, enabled) {
1174
+ this.features[feature] = enabled;
1175
+ if (feature === "loadRemoteProduct" && !enabled && this.productDialog.open) this.closeDialog(this.productDialog);
1176
+ if (feature === "addText" && !enabled && this.textDialog.open) this.closeDialog(this.textDialog);
1177
+ if (feature === "addImage" && !enabled && this.imageDialog.open) this.closeDialog(this.imageDialog);
1178
+ if (feature === "presetBackgrounds" && this.activeImageTab === "backgrounds" || feature === "presetElements" && this.activeImageTab === "elements") this.setImageTab("upload");
1179
+ this.applyVisibility();
1180
+ this.renderLayers(true);
1181
+ }
1182
+ /**
1183
+ * 在运行时显示或隐藏 Workbench 布局区域
1184
+ *
1185
+ * @param section 布局区域名称
1186
+ * @param visible 是否显示该区域
1187
+ */
1188
+ setLayout(section, visible) {
1189
+ this.layout[section] = visible;
1190
+ this.applyVisibility();
1191
+ }
1192
+ /**
1193
+ * 更新底部状态栏内容
1194
+ *
1195
+ * @param message 状态文字
1196
+ * @param mode 状态样式,默认为 ready
1197
+ */
1198
+ setStatus(message, mode = "ready") {
1199
+ this.statusLabel.textContent = message;
1200
+ this.element.dataset.status = mode;
1201
+ }
1202
+ /** 释放 DOM 事件、核心实例和 Workbench 挂载内容 */
1203
+ destroy() {
1204
+ if (this.destroyed) return;
1205
+ this.destroyed = true;
1206
+ this.releaseImageSelection();
1207
+ this.dialogCloseTimers.forEach((timer) => window.clearTimeout(timer));
1208
+ this.dialogCloseTimers.clear();
1209
+ this.abortController.abort();
1210
+ this.unsubscribe.splice(0).forEach((stop) => stop());
1211
+ this.customizer.destroy();
1212
+ this.host.replaceChildren();
1213
+ }
1214
+ bindEvents() {
1215
+ const signal = this.abortController.signal;
1216
+ this.unsubscribe.push(this.customizer.on("change", ({ objectCount }) => {
1217
+ this.updateObjectCount(objectCount);
1218
+ this.renderLayers();
1219
+ }), this.customizer.on("selectionchange", ({ objectIds }) => {
1220
+ this.selectedObjectIds = new Set(objectIds);
1221
+ this.deleteButton.disabled = objectIds.length === 0;
1222
+ this.renderLayers();
1223
+ }), this.customizer.on("historychange", () => this.updateHistoryButtons()), this.customizer.on("status", ({ message }) => this.setStatus(this.localizeStatus(message))), this.customizer.on("error", ({ error }) => this.setStatus(error.message, "error")));
1224
+ this.element.addEventListener("click", (event) => this.handleRootClick(event), { signal });
1225
+ this.element.addEventListener("keydown", (event) => {
1226
+ this.handleImageTabShortcut(event);
1227
+ this.handleHistoryShortcut(event);
1228
+ }, { signal });
1229
+ this.textForm.addEventListener("submit", (event) => this.addTextFromDialog(event), { signal });
1230
+ this.imageForm.addEventListener("submit", (event) => void this.addImageFromDialog(event), { signal });
1231
+ this.remoteForm.addEventListener("submit", (event) => void this.loadRemoteProduct(event), { signal });
1232
+ requiredElement(this.element, "[data-role=\"text-value\"]").addEventListener("input", (event) => {
1233
+ event.currentTarget.setCustomValidity("");
1234
+ this.updateTextPreviews();
1235
+ }, { signal });
1236
+ requiredElement(this.element, "[data-role=\"text-color\"]").addEventListener("input", () => this.updateTextPreviews(), { signal });
1237
+ this.imageInput.addEventListener("change", () => this.selectUploadedImage(), { signal });
1238
+ this.designInput.addEventListener("change", () => void this.loadSelectedDesign(), { signal });
1239
+ this.imageDialog.addEventListener("close", () => this.resetImageSelection(), { signal });
1240
+ for (const dialog of [
1241
+ this.textDialog,
1242
+ this.imageDialog,
1243
+ this.productDialog
1244
+ ]) {
1245
+ dialog.addEventListener("cancel", (event) => {
1246
+ event.preventDefault();
1247
+ this.closeDialog(dialog);
1248
+ }, { signal });
1249
+ dialog.addEventListener("close", () => this.clearDialogClose(dialog), { signal });
1250
+ }
1251
+ this.layerList.addEventListener("dblclick", (event) => this.handleLayerRename(event), { signal });
1252
+ this.layerList.addEventListener("dragstart", (event) => this.handleLayerDragStart(event), { signal });
1253
+ this.layerList.addEventListener("dragover", (event) => this.handleLayerDragOver(event), { signal });
1254
+ this.layerList.addEventListener("drop", (event) => this.handleLayerDrop(event), { signal });
1255
+ this.layerList.addEventListener("dragend", () => this.clearLayerDragState(), { signal });
1256
+ window.addEventListener("beforeunload", () => this.destroy(), {
1257
+ once: true,
1258
+ signal
1259
+ });
1260
+ }
1261
+ handleRootClick(event) {
1262
+ const target = event.target;
1263
+ const layerAction = target.closest("[data-layer-action]");
1264
+ if (layerAction) {
1265
+ this.handleLayerAction(layerAction);
1266
+ return;
1267
+ }
1268
+ const textPreset = target.closest("[data-text-preset-id]");
1269
+ if (textPreset?.dataset.textPresetId) {
1270
+ this.selectTextPreset(textPreset.dataset.textPresetId);
1271
+ return;
1272
+ }
1273
+ const asset = target.closest("[data-asset-id]");
1274
+ if (asset?.dataset.assetId && asset.dataset.assetRole) {
1275
+ this.selectPresetAsset(asset.dataset.assetId, asset.dataset.assetRole);
1276
+ return;
1277
+ }
1278
+ const tab = target.closest("[data-image-tab]");
1279
+ if (tab?.dataset.imageTab) {
1280
+ this.setImageTab(tab.dataset.imageTab);
1281
+ return;
1282
+ }
1283
+ switch (target.closest("[data-action]")?.dataset.action) {
1284
+ case "open-text-dialog":
1285
+ this.openTextDialog();
1286
+ break;
1287
+ case "open-image-dialog":
1288
+ this.openImageDialog();
1289
+ break;
1290
+ case "undo":
1291
+ this.runHistory("undo");
1292
+ break;
1293
+ case "redo":
1294
+ this.runHistory("redo");
1295
+ break;
1296
+ case "save-design":
1297
+ this.saveDesign();
1298
+ break;
1299
+ case "load-design":
1300
+ this.designInput.click();
1301
+ break;
1302
+ case "delete-selection":
1303
+ this.customizer.deleteSelected();
1304
+ break;
1305
+ case "reset-view":
1306
+ this.customizer.resetView();
1307
+ break;
1308
+ case "export-texture":
1309
+ this.exportTexture();
1310
+ break;
1311
+ case "load-remote":
1312
+ this.showDialog(this.productDialog);
1313
+ break;
1314
+ case "close-text-dialog":
1315
+ this.closeDialog(this.textDialog);
1316
+ break;
1317
+ case "close-image-dialog":
1318
+ this.closeDialog(this.imageDialog);
1319
+ break;
1320
+ case "close-product-dialog":
1321
+ this.closeDialog(this.productDialog);
1322
+ break;
1323
+ case "choose-image":
1324
+ this.imageInput.click();
1325
+ break;
1326
+ case "use-demo": this.loadDemoProduct();
1327
+ }
1328
+ }
1329
+ handleHistoryShortcut(event) {
1330
+ if (!this.features.undoRedo || !event.ctrlKey && !event.metaKey || event.altKey) return;
1331
+ const target = event.target;
1332
+ if (target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement || target instanceof HTMLSelectElement || target.isContentEditable) return;
1333
+ const key = event.key.toLowerCase();
1334
+ if (key === "z" && !event.shiftKey) {
1335
+ event.preventDefault();
1336
+ this.runHistory("undo");
1337
+ } else if (key === "z" && event.shiftKey || key === "y") {
1338
+ event.preventDefault();
1339
+ this.runHistory("redo");
1340
+ }
1341
+ }
1342
+ handleImageTabShortcut(event) {
1343
+ const current = event.target.closest("[data-image-tab]");
1344
+ if (!current || ![
1345
+ "ArrowLeft",
1346
+ "ArrowRight",
1347
+ "Home",
1348
+ "End"
1349
+ ].includes(event.key)) return;
1350
+ const tabs = Array.from(this.element.querySelectorAll("[data-image-tab]")).filter((tab) => !tab.hidden);
1351
+ const currentIndex = tabs.indexOf(current);
1352
+ if (currentIndex < 0 || tabs.length === 0) return;
1353
+ event.preventDefault();
1354
+ const next = tabs[event.key === "Home" ? 0 : event.key === "End" ? tabs.length - 1 : (currentIndex + (event.key === "ArrowRight" ? 1 : -1) + tabs.length) % tabs.length];
1355
+ if (next?.dataset.imageTab) {
1356
+ this.setImageTab(next.dataset.imageTab);
1357
+ next.focus();
1358
+ }
1359
+ }
1360
+ action(name) {
1361
+ return requiredElement(this.element, `[data-action="${name}"]`);
1362
+ }
1363
+ showDialog(dialog) {
1364
+ this.clearDialogClose(dialog);
1365
+ if (!dialog.open) dialog.showModal();
1366
+ }
1367
+ closeDialog(dialog) {
1368
+ if (!dialog.open || dialog.dataset.closing === "true") return;
1369
+ if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
1370
+ dialog.close();
1371
+ return;
1372
+ }
1373
+ dialog.dataset.closing = "true";
1374
+ const timer = window.setTimeout(() => {
1375
+ this.dialogCloseTimers.delete(dialog);
1376
+ delete dialog.dataset.closing;
1377
+ if (dialog.open) dialog.close();
1378
+ }, DIALOG_CLOSE_DELAY_MS);
1379
+ this.dialogCloseTimers.set(dialog, timer);
1380
+ }
1381
+ clearDialogClose(dialog) {
1382
+ const timer = this.dialogCloseTimers.get(dialog);
1383
+ if (timer !== void 0) {
1384
+ window.clearTimeout(timer);
1385
+ this.dialogCloseTimers.delete(dialog);
1386
+ }
1387
+ delete dialog.dataset.closing;
1388
+ }
1389
+ applyVisibility() {
1390
+ this.element.querySelectorAll("[data-feature]").forEach((element) => {
1391
+ const feature = element.dataset.feature;
1392
+ element.hidden = !this.features[feature];
1393
+ });
1394
+ this.element.querySelectorAll("[data-layout]").forEach((element) => {
1395
+ const section = element.dataset.layout;
1396
+ element.hidden = !this.layout[section];
1397
+ });
1398
+ const historyVisible = this.features.undoRedo;
1399
+ const insertVisible = this.features.addText || this.features.addImage;
1400
+ const documentVisible = this.features.saveDesign || this.features.loadDesign;
1401
+ const selectionVisible = this.features.deleteSelection;
1402
+ this.toolGroup("history").hidden = !historyVisible;
1403
+ this.toolGroup("insert").hidden = !insertVisible;
1404
+ this.toolGroup("document").hidden = !documentVisible;
1405
+ this.toolGroup("history-separator").hidden = !historyVisible || !insertVisible && !documentVisible && !selectionVisible;
1406
+ this.toolGroup("document-separator").hidden = !insertVisible || !documentVisible && !selectionVisible;
1407
+ this.toolGroup("selection-separator").hidden = !selectionVisible || !historyVisible && !insertVisible && !documentVisible;
1408
+ const toolbar = requiredElement(this.element, "[data-layout=\"toolbar\"]");
1409
+ toolbar.hidden = !this.layout.toolbar || !historyVisible && !insertVisible && !documentVisible && !selectionVisible;
1410
+ this.element.dataset.layers = this.layout.layers ? "visible" : "hidden";
1411
+ const brandHidden = requiredElement(this.element, "[data-role=\"brand\"]").hidden;
1412
+ const hasGlobalActions = this.features.loadRemoteProduct || this.features.exportTexture;
1413
+ const topbar = requiredElement(this.element, "[data-layout=\"header\"]");
1414
+ topbar.hidden = !this.layout.header || brandHidden && !hasGlobalActions;
1415
+ renderWorkbenchIcons(this.element, this.icons);
1416
+ }
1417
+ toolGroup(name) {
1418
+ return requiredElement(this.element, `[data-tool-group="${name}"]`);
1419
+ }
1420
+ updateObjectCount(objectCount) {
1421
+ this.objectCountLabel.textContent = formatCount(this.labels, objectCount);
1422
+ requiredElement(this.element, "[data-role=\"layer-count\"]").textContent = String(objectCount);
1423
+ }
1424
+ updateHistoryButtons() {
1425
+ this.undoButton.disabled = !this.customizer.canUndo();
1426
+ this.redoButton.disabled = !this.customizer.canRedo();
1427
+ }
1428
+ localizeStatus(message) {
1429
+ switch (message) {
1430
+ case "Loading design": return this.labels.loadDesign;
1431
+ case "Design loaded": return this.labels.designLoaded;
1432
+ case "Loading product": return this.labels.loadProduct;
1433
+ case "Remote product ready":
1434
+ case "Demo product ready": return this.labels.productReady;
1435
+ case "Undo complete": return this.labels.undoComplete;
1436
+ case "Redo complete": return this.labels.redoComplete;
1437
+ default: return message;
1438
+ }
1439
+ }
1440
+ async runHistory(direction) {
1441
+ if (direction === "undo" && !this.customizer.canUndo() || direction === "redo" && !this.customizer.canRedo()) return;
1442
+ this.undoButton.disabled = true;
1443
+ this.redoButton.disabled = true;
1444
+ this.setStatus(direction === "undo" ? this.labels.undo : this.labels.redo, "busy");
1445
+ try {
1446
+ if (direction === "undo" ? await this.customizer.undo() : await this.customizer.redo()) this.setStatus(direction === "undo" ? this.labels.undoComplete : this.labels.redoComplete);
1447
+ } catch (error) {
1448
+ this.setStatus(errorMessage(error), "error");
1449
+ } finally {
1450
+ this.updateHistoryButtons();
1451
+ }
1452
+ }
1453
+ saveDesign() {
1454
+ try {
1455
+ downloadJson("customforge-design.json", this.customizer.saveDesign());
1456
+ this.setStatus(this.labels.designSaved);
1457
+ } catch (error) {
1458
+ this.setStatus(errorMessage(error), "error");
1459
+ }
1460
+ }
1461
+ exportTexture() {
1462
+ try {
1463
+ this.customizer.exportTexture();
1464
+ this.setStatus(this.labels.exportTexture);
1465
+ } catch (error) {
1466
+ this.setStatus(errorMessage(error), "error");
1467
+ }
1468
+ }
1469
+ renderLayers(force = false) {
1470
+ const objects = this.customizer.getObjects();
1471
+ const signature = JSON.stringify({
1472
+ layers: objects.map((object) => [
1473
+ object.id,
1474
+ object.name,
1475
+ object.visible ?? true,
1476
+ object.locked ?? false,
1477
+ object.type === "image" ? object.role : void 0
1478
+ ]),
1479
+ selected: [...this.selectedObjectIds]
1480
+ });
1481
+ if (!force && signature === this.layerSignature) {
1482
+ this.updateObjectCount(objects.length);
1483
+ return;
1484
+ }
1485
+ this.layerSignature = signature;
1486
+ const backgroundCount = objects.filter((object) => object.type === "image" && object.role === "background").length;
1487
+ const items = objects.map((object, index) => this.createLayerItem(object, index, objects.length, backgroundCount)).reverse();
1488
+ if (items.length === 0) {
1489
+ const empty = document.createElement("li");
1490
+ empty.className = "customforge-workbench__layer-empty";
1491
+ empty.textContent = this.labels.noObjects;
1492
+ items.push(empty);
1493
+ }
1494
+ this.layerList.replaceChildren(...items);
1495
+ this.updateObjectCount(objects.length);
1496
+ this.applyVisibility();
1497
+ }
1498
+ createLayerItem(object, index, objectCount, backgroundCount) {
1499
+ const isBackground = object.type === "image" && object.role === "background";
1500
+ const typeLabel = object.type === "text" ? this.labels.textObject : isBackground ? this.labels.backgroundObject : this.labels.imageObject;
1501
+ const fallbackName = object.type === "text" ? object.text || this.labels.textObject : typeLabel;
1502
+ const item = document.createElement("li");
1503
+ item.className = "customforge-workbench__layer-item";
1504
+ item.dataset.objectId = object.id;
1505
+ item.dataset.visible = String(object.visible ?? true);
1506
+ item.dataset.locked = String(object.locked ?? false);
1507
+ item.dataset.selected = String(this.selectedObjectIds.has(object.id));
1508
+ item.dataset.role = isBackground ? "background" : "element";
1509
+ item.draggable = this.features.reorderObjects && !isBackground;
1510
+ const selectButton = document.createElement("button");
1511
+ selectButton.className = "customforge-workbench__layer-main";
1512
+ selectButton.type = "button";
1513
+ selectButton.dataset.layerAction = "select";
1514
+ selectButton.title = object.name || fallbackName;
1515
+ selectButton.setAttribute("aria-label", object.name || fallbackName);
1516
+ const icon = document.createElement("i");
1517
+ icon.dataset.customforgeIcon = object.type === "text" ? "type" : "image";
1518
+ icon.dataset.iconSlot = object.type === "text" ? "textObject" : isBackground ? "backgroundObject" : "imageObject";
1519
+ const copy = document.createElement("span");
1520
+ copy.className = "customforge-workbench__layer-copy";
1521
+ const name = document.createElement("strong");
1522
+ name.textContent = object.name || fallbackName;
1523
+ const type = document.createElement("span");
1524
+ type.textContent = typeLabel;
1525
+ copy.append(name, type);
1526
+ selectButton.append(icon, copy);
1527
+ const actions = document.createElement("span");
1528
+ actions.className = "customforge-workbench__layer-actions";
1529
+ actions.append(this.createLayerButton("visibility", object.visible === false ? "eye-off" : "eye", object.visible === false ? "showLayer" : "hideLayer", object.visible === false ? this.labels.showLayer : this.labels.hideLayer, "toggleObjectVisibility"), this.createLayerButton("lock", object.locked ? "lock" : "lock-open", object.locked ? "unlock" : "lock", object.locked ? this.labels.unlockLayer : this.labels.lockLayer, "lockObjects"), this.createLayerButton("forward", "arrow-up", "moveForward", this.labels.moveLayerForward, "reorderObjects", isBackground || index === objectCount - 1), this.createLayerButton("backward", "arrow-down", "moveBackward", this.labels.moveLayerBackward, "reorderObjects", isBackground || index <= backgroundCount), this.createLayerButton("remove", "trash-2", "deleteSelection", this.labels.deleteLayer, "deleteSelection"));
1530
+ item.append(selectButton, actions);
1531
+ return item;
1532
+ }
1533
+ createLayerButton(action, iconName, iconSlot, title, feature, disabled = false) {
1534
+ const button = document.createElement("button");
1535
+ button.className = "customforge-workbench__layer-action";
1536
+ button.type = "button";
1537
+ button.dataset.layerAction = action;
1538
+ button.dataset.feature = feature;
1539
+ button.title = title;
1540
+ button.setAttribute("aria-label", title);
1541
+ button.disabled = disabled;
1542
+ const icon = document.createElement("i");
1543
+ icon.dataset.customforgeIcon = iconName;
1544
+ icon.dataset.iconSlot = iconSlot;
1545
+ const label = document.createElement("span");
1546
+ label.className = "customforge-workbench__icon-label";
1547
+ label.textContent = title;
1548
+ button.append(icon, label);
1549
+ return button;
1550
+ }
1551
+ handleLayerAction(button) {
1552
+ const id = button.closest("[data-object-id]")?.dataset.objectId;
1553
+ if (!id) return;
1554
+ const object = this.customizer.getObjects().find((entry) => entry.id === id);
1555
+ if (!object) return;
1556
+ switch (button.dataset.layerAction) {
1557
+ case "select":
1558
+ this.customizer.selectObject(id);
1559
+ break;
1560
+ case "visibility":
1561
+ this.customizer.setObjectVisibility(id, object.visible === false);
1562
+ break;
1563
+ case "lock":
1564
+ this.customizer.setObjectLocked(id, !object.locked);
1565
+ break;
1566
+ case "forward":
1567
+ this.moveLayerBy(id, 1);
1568
+ break;
1569
+ case "backward":
1570
+ this.moveLayerBy(id, -1);
1571
+ break;
1572
+ case "remove": this.customizer.removeObject(id);
1573
+ }
1574
+ }
1575
+ handleLayerRename(event) {
1576
+ if (!this.features.renameObjects) return;
1577
+ const button = event.target.closest("[data-layer-action=\"select\"]");
1578
+ const id = (button?.closest("[data-object-id]"))?.dataset.objectId;
1579
+ const object = id ? this.customizer.getObjects().find((entry) => entry.id === id) : void 0;
1580
+ if (!button || !id || !object) return;
1581
+ const input = document.createElement("input");
1582
+ input.className = "customforge-workbench__layer-name-input";
1583
+ input.value = object.name || (object.type === "text" ? object.text : object.role === "background" ? this.labels.backgroundObject : this.labels.imageObject);
1584
+ input.maxLength = 80;
1585
+ button.replaceWith(input);
1586
+ input.focus();
1587
+ input.select();
1588
+ let completed = false;
1589
+ const finish = (save) => {
1590
+ if (completed) return;
1591
+ completed = true;
1592
+ if (save) this.customizer.renameObject(id, input.value);
1593
+ this.renderLayers(true);
1594
+ };
1595
+ input.addEventListener("blur", () => finish(true), { once: true });
1596
+ input.addEventListener("keydown", (keyboardEvent) => {
1597
+ if (keyboardEvent.key === "Enter") {
1598
+ keyboardEvent.preventDefault();
1599
+ finish(true);
1600
+ } else if (keyboardEvent.key === "Escape") {
1601
+ keyboardEvent.preventDefault();
1602
+ finish(false);
1603
+ }
1604
+ });
1605
+ }
1606
+ moveLayerBy(id, offset) {
1607
+ const currentIndex = this.customizer.getObjects().findIndex((object) => object.id === id);
1608
+ if (currentIndex >= 0 && this.customizer.moveObject(id, currentIndex + offset)) this.renderLayers(true);
1609
+ }
1610
+ handleLayerDragStart(event) {
1611
+ const item = event.target.closest("[data-object-id]");
1612
+ if (!this.features.reorderObjects || item?.dataset.role === "background") {
1613
+ event.preventDefault();
1614
+ return;
1615
+ }
1616
+ this.draggedObjectId = item?.dataset.objectId;
1617
+ if (!this.draggedObjectId) {
1618
+ event.preventDefault();
1619
+ return;
1620
+ }
1621
+ item?.classList.add("is-dragging");
1622
+ event.dataTransfer?.setData("text/plain", this.draggedObjectId);
1623
+ if (event.dataTransfer) event.dataTransfer.effectAllowed = "move";
1624
+ }
1625
+ handleLayerDragOver(event) {
1626
+ if (!this.draggedObjectId) return;
1627
+ event.preventDefault();
1628
+ this.layerList.querySelectorAll(".is-drag-target").forEach((item) => item.classList.remove("is-drag-target"));
1629
+ event.target.closest("[data-object-id]")?.classList.add("is-drag-target");
1630
+ }
1631
+ handleLayerDrop(event) {
1632
+ event.preventDefault();
1633
+ const targetId = event.target.closest("[data-object-id]")?.dataset.objectId;
1634
+ const sourceId = this.draggedObjectId;
1635
+ if (!sourceId || !targetId || sourceId === targetId) {
1636
+ this.clearLayerDragState();
1637
+ return;
1638
+ }
1639
+ const targetIndex = this.customizer.getObjects().findIndex((object) => object.id === targetId);
1640
+ if (targetIndex >= 0) this.customizer.moveObject(sourceId, targetIndex);
1641
+ this.clearLayerDragState();
1642
+ this.renderLayers(true);
1643
+ }
1644
+ clearLayerDragState() {
1645
+ this.draggedObjectId = void 0;
1646
+ this.layerList.querySelectorAll(".is-dragging, .is-drag-target").forEach((item) => item.classList.remove("is-dragging", "is-drag-target"));
1647
+ }
1648
+ renderTextPresets() {
1649
+ const container = requiredElement(this.element, "[data-role=\"text-presets\"]");
1650
+ const items = this.textPresets.map((preset) => {
1651
+ const button = document.createElement("button");
1652
+ button.className = "customforge-workbench__text-preset";
1653
+ button.type = "button";
1654
+ button.dataset.textPresetId = preset.id;
1655
+ button.dataset.selected = String(preset.id === this.selectedTextPresetId);
1656
+ const preview = document.createElement("span");
1657
+ preview.className = "customforge-workbench__text-preview";
1658
+ preview.dataset.previewFallback = preset.previewText ?? preset.name;
1659
+ preview.textContent = preset.previewText ?? preset.name;
1660
+ preview.style.fontFamily = preset.fontFamily;
1661
+ preview.style.color = preset.color;
1662
+ const name = document.createElement("strong");
1663
+ name.textContent = preset.name;
1664
+ button.append(preview, name);
1665
+ return button;
1666
+ });
1667
+ container.replaceChildren(...items);
1668
+ const fieldset = container.closest("fieldset");
1669
+ if (fieldset) fieldset.hidden = items.length === 0;
1670
+ }
1671
+ openTextDialog() {
1672
+ const input = requiredElement(this.element, "[data-role=\"text-value\"]");
1673
+ input.value = this.labels.textInputPlaceholder;
1674
+ this.selectedTextPresetId = this.textPresets[0]?.id;
1675
+ if (this.selectedTextPresetId) this.selectTextPreset(this.selectedTextPresetId);
1676
+ this.updateTextPreviews();
1677
+ this.showDialog(this.textDialog);
1678
+ input.focus();
1679
+ input.select();
1680
+ }
1681
+ selectTextPreset(id) {
1682
+ const preset = this.textPresets.find((entry) => entry.id === id);
1683
+ if (!preset) return;
1684
+ this.selectedTextPresetId = id;
1685
+ requiredElement(this.element, "[data-role=\"text-color\"]").value = preset.color;
1686
+ this.element.querySelectorAll("[data-text-preset-id]").forEach((button) => {
1687
+ button.dataset.selected = String(button.dataset.textPresetId === id);
1688
+ });
1689
+ this.updateTextPreviews();
1690
+ }
1691
+ updateTextPreviews() {
1692
+ const text = requiredElement(this.element, "[data-role=\"text-value\"]").value;
1693
+ const color = requiredElement(this.element, "[data-role=\"text-color\"]").value;
1694
+ this.element.querySelectorAll(".customforge-workbench__text-preview").forEach((preview) => {
1695
+ const presetId = preview.closest("[data-text-preset-id]")?.dataset.textPresetId;
1696
+ const preset = this.textPresets.find((entry) => entry.id === presetId);
1697
+ preview.textContent = text || preview.dataset.previewFallback || "";
1698
+ preview.style.color = presetId === this.selectedTextPresetId ? color : preset?.color ?? "";
1699
+ });
1700
+ }
1701
+ addTextFromDialog(event) {
1702
+ event.preventDefault();
1703
+ const input = requiredElement(this.element, "[data-role=\"text-value\"]");
1704
+ input.setCustomValidity("");
1705
+ if (!this.textForm.reportValidity()) return;
1706
+ const text = input.value.trim();
1707
+ if (!text) {
1708
+ input.setCustomValidity(this.labels.textInputLabel);
1709
+ input.reportValidity();
1710
+ return;
1711
+ }
1712
+ const color = requiredElement(this.element, "[data-role=\"text-color\"]").value;
1713
+ const preset = this.textPresets.find((entry) => entry.id === this.selectedTextPresetId);
1714
+ this.customizer.addText({
1715
+ text,
1716
+ name: text.slice(0, 48),
1717
+ color,
1718
+ ...preset ? {
1719
+ fontFamily: preset.fontFamily,
1720
+ fontSize: preset.fontSize,
1721
+ width: preset.width
1722
+ } : {}
1723
+ });
1724
+ this.closeDialog(this.textDialog);
1725
+ this.setStatus(this.labels.addTextConfirm);
1726
+ }
1727
+ renderAssetPresets() {
1728
+ this.renderAssetGroup("background-presets", this.backgrounds, "background");
1729
+ this.renderAssetGroup("element-presets", this.elements, "element");
1730
+ }
1731
+ renderAssetGroup(roleName, assets, role) {
1732
+ const container = requiredElement(this.element, `[data-role="${roleName}"]`);
1733
+ if (assets.length === 0) {
1734
+ const empty = document.createElement("p");
1735
+ empty.className = "customforge-workbench__preset-empty";
1736
+ empty.textContent = this.labels.noPresetAssets;
1737
+ container.replaceChildren(empty);
1738
+ return;
1739
+ }
1740
+ container.replaceChildren(...assets.map((asset) => {
1741
+ const button = document.createElement("button");
1742
+ button.className = "customforge-workbench__asset-preset";
1743
+ button.type = "button";
1744
+ button.dataset.assetId = asset.id;
1745
+ button.dataset.assetRole = role;
1746
+ button.dataset.selected = "false";
1747
+ const image = document.createElement("img");
1748
+ image.src = asset.thumbnailUrl ?? asset.url;
1749
+ image.alt = asset.alt ?? asset.name;
1750
+ image.loading = "lazy";
1751
+ const name = document.createElement("strong");
1752
+ name.textContent = asset.name;
1753
+ button.append(image, name);
1754
+ return button;
1755
+ }));
1756
+ }
1757
+ openImageDialog() {
1758
+ this.resetImageSelection();
1759
+ this.setImageTab("upload");
1760
+ this.showDialog(this.imageDialog);
1761
+ }
1762
+ setImageTab(tab) {
1763
+ const next = tab === "backgrounds" && !this.features.presetBackgrounds ? "upload" : tab === "elements" && !this.features.presetElements ? "upload" : tab;
1764
+ if (this.activeImageTab !== next && this.selectedImage) this.resetImageSelection();
1765
+ this.activeImageTab = next;
1766
+ this.element.querySelectorAll("[data-image-tab]").forEach((button) => {
1767
+ const selected = button.dataset.imageTab === next;
1768
+ button.dataset.selected = String(selected);
1769
+ button.setAttribute("aria-selected", String(selected));
1770
+ button.tabIndex = selected ? 0 : -1;
1771
+ });
1772
+ this.element.querySelectorAll("[data-image-panel]").forEach((panel) => {
1773
+ panel.hidden = panel.dataset.imagePanel !== next;
1774
+ });
1775
+ }
1776
+ selectUploadedImage() {
1777
+ const file = this.imageInput.files?.[0];
1778
+ if (!file) return;
1779
+ this.releaseImageSelection();
1780
+ const objectUrl = URL.createObjectURL(file);
1781
+ this.selectedImage = {
1782
+ src: objectUrl,
1783
+ name: file.name.replace(/\.[^.]+$/, "") || this.labels.imageObject,
1784
+ role: "element",
1785
+ objectUrl
1786
+ };
1787
+ requiredElement(this.element, "[data-role=\"image-file-name\"]").textContent = file.name;
1788
+ const preview = requiredElement(this.element, "[data-role=\"image-preview\"]");
1789
+ preview.src = objectUrl;
1790
+ preview.alt = file.name;
1791
+ preview.hidden = false;
1792
+ this.imageSubmitButton.disabled = false;
1793
+ }
1794
+ selectPresetAsset(id, role) {
1795
+ const asset = (role === "background" ? this.backgrounds : this.elements).find((entry) => entry.id === id);
1796
+ if (!asset) return;
1797
+ this.releaseImageSelection();
1798
+ this.selectedImage = {
1799
+ src: asset.url,
1800
+ name: asset.name,
1801
+ role
1802
+ };
1803
+ this.element.querySelectorAll("[data-asset-id]").forEach((button) => {
1804
+ button.dataset.selected = String(button.dataset.assetId === id && button.dataset.assetRole === role);
1805
+ });
1806
+ this.imageSubmitButton.disabled = false;
1807
+ }
1808
+ async addImageFromDialog(event) {
1809
+ event.preventDefault();
1810
+ const selection = this.selectedImage;
1811
+ if (!selection) return;
1812
+ this.imageSubmitButton.disabled = true;
1813
+ this.setStatus(this.labels.addImage, "busy");
1814
+ try {
1815
+ await this.customizer.addImage({
1816
+ src: selection.src,
1817
+ name: selection.name,
1818
+ role: selection.role
1819
+ });
1820
+ this.closeDialog(this.imageDialog);
1821
+ this.setStatus(this.labels.addImageConfirm);
1822
+ } catch (error) {
1823
+ this.setStatus(errorMessage(error), "error");
1824
+ this.imageSubmitButton.disabled = false;
1825
+ }
1826
+ }
1827
+ resetImageSelection() {
1828
+ this.releaseImageSelection();
1829
+ this.selectedImage = void 0;
1830
+ this.imageInput.value = "";
1831
+ requiredElement(this.element, "[data-role=\"image-file-name\"]").textContent = this.labels.noImageSelected;
1832
+ const preview = requiredElement(this.element, "[data-role=\"image-preview\"]");
1833
+ preview.removeAttribute("src");
1834
+ preview.alt = "";
1835
+ preview.hidden = true;
1836
+ this.element.querySelectorAll("[data-asset-id]").forEach((button) => {
1837
+ button.dataset.selected = "false";
1838
+ });
1839
+ this.imageSubmitButton.disabled = true;
1840
+ }
1841
+ releaseImageSelection() {
1842
+ if (this.selectedImage?.objectUrl) URL.revokeObjectURL(this.selectedImage.objectUrl);
1843
+ }
1844
+ async loadSelectedDesign() {
1845
+ const file = this.designInput.files?.[0];
1846
+ if (!file) return;
1847
+ this.loadDesignButton.disabled = true;
1848
+ this.setStatus(this.labels.loadDesign, "busy");
1849
+ try {
1850
+ await this.customizer.loadDesign(JSON.parse(await file.text()));
1851
+ this.setStatus(this.labels.designLoaded);
1852
+ } catch (error) {
1853
+ this.setStatus(errorMessage(error), "error");
1854
+ } finally {
1855
+ this.loadDesignButton.disabled = false;
1856
+ this.designInput.value = "";
1857
+ }
1858
+ }
1859
+ async loadDemoProduct() {
1860
+ this.submitProductButton.disabled = true;
1861
+ this.setStatus(this.labels.loadProduct, "busy");
1862
+ try {
1863
+ await this.customizer.loadProduct({});
1864
+ this.closeDialog(this.productDialog);
1865
+ this.setStatus(this.labels.productReady);
1866
+ } catch (error) {
1867
+ this.setStatus(errorMessage(error), "error");
1868
+ } finally {
1869
+ this.submitProductButton.disabled = false;
1870
+ }
1871
+ }
1872
+ async loadRemoteProduct(event) {
1873
+ event.preventDefault();
1874
+ if (!this.remoteForm.reportValidity()) return;
1875
+ this.submitProductButton.disabled = true;
1876
+ this.setStatus(this.labels.loadProduct, "busy");
1877
+ try {
1878
+ await this.customizer.loadProduct({
1879
+ modelUrl: requiredElement(this.element, "[data-role=\"model-url\"]").value,
1880
+ textureUrl: requiredElement(this.element, "[data-role=\"texture-url\"]").value,
1881
+ surfaceMesh: requiredElement(this.element, "[data-role=\"mesh-name\"]").value,
1882
+ textureFlipY: requiredElement(this.element, "[data-role=\"flip-texture\"]").checked
1883
+ });
1884
+ this.closeDialog(this.productDialog);
1885
+ this.setStatus(this.labels.productReady);
1886
+ } catch (error) {
1887
+ this.setStatus(errorMessage(error), "error");
1888
+ } finally {
1889
+ this.submitProductButton.disabled = false;
1890
+ }
1891
+ }
1892
+ };
1893
+ //#endregion
1894
+ //#region src/workbench/index.ts
1895
+ /**
1896
+ * 创建带默认界面的产品定制工作台
1897
+ *
1898
+ * 使用 `features` 控制具体功能控件,使用 `layout` 控制顶部栏、工具栏、图层面板等区域
1899
+ * 不再使用返回的实例时必须调用 `destroy()`
1900
+ *
1901
+ * @param options 挂载容器、产品资源、功能和布局开关
1902
+ * @returns 初始化完成的 Workbench
1903
+ * @throws 挂载容器无效、模型加载失败或目标 Mesh 不存在时抛出错误
1904
+ *
1905
+ * @example
1906
+ * ```ts
1907
+ * const workbench = await createWorkbench({
1908
+ * container: '#app',
1909
+ * features: { addText: false },
1910
+ * layout: { header: false },
1911
+ * })
1912
+ * ```
1913
+ */
1914
+ async function createWorkbench(options) {
1915
+ return CustomForgeWorkbench.create(options);
1916
+ }
1917
+ //#endregion
1918
+ export { CustomForgeWorkbench, createWorkbench };
1919
+
1920
+ //# sourceMappingURL=workbench.js.map