@varianta/sdk 0.1.5 → 0.1.7
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/README.md +28 -18
- package/dist/index.cjs.js +7 -5
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.esm.js +1008 -805
- package/dist/index.esm.js.map +1 -1
- package/dist/index.umd.js +12 -0
- package/dist/index.umd.js.map +1 -0
- package/dist/react.cjs.js +1 -1
- package/dist/react.cjs.js.map +1 -1
- package/dist/react.esm.js +175 -163
- package/dist/react.esm.js.map +1 -1
- package/dist/types/react/Customizer.d.ts +21 -3
- package/dist/types/react/Customizer.d.ts.map +1 -1
- package/dist/types/types/index.d.ts +29 -2
- package/dist/types/types/index.d.ts.map +1 -1
- package/dist/types/vanilla/index.d.ts.map +1 -1
- package/dist/types/vue/types.d.ts +1 -2
- package/dist/types/vue/types.d.ts.map +1 -1
- package/package.json +3 -2
package/dist/index.esm.js
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
class
|
|
1
|
+
class U {
|
|
2
2
|
constructor(e) {
|
|
3
3
|
this.animationFrameId = null, this.needsRender = !0, this.areas = [], this.contents = /* @__PURE__ */ new Map(), this.artboard = null, this.bgImageElement = null, this.loadedImages = /* @__PURE__ */ new Map(), this.loadingImages = /* @__PURE__ */ new Set(), this.zoom = 1, this.pan = { x: 0, y: 0 }, this.selectedAreaId = null, this.hoveredHandle = null, this.eventListeners = /* @__PURE__ */ new Map(), this.safeArea = null, this.safeAreaViolations = /* @__PURE__ */ new Set(), this.showAreaBorders = !0, this.areaSelectionEnabled = !0, this.renderLoop = () => {
|
|
4
4
|
try {
|
|
5
5
|
this.needsRender && (this.render(), this.needsRender = !1, this.emit("render", void 0));
|
|
6
|
-
} catch (
|
|
7
|
-
console.error("Error in render loop:",
|
|
6
|
+
} catch (t) {
|
|
7
|
+
console.error("Error in render loop:", t);
|
|
8
8
|
}
|
|
9
9
|
this.animationFrameId = requestAnimationFrame(this.renderLoop);
|
|
10
10
|
}, this.canvas = e;
|
|
11
|
-
const
|
|
12
|
-
if (!
|
|
11
|
+
const i = e.getContext("2d");
|
|
12
|
+
if (!i)
|
|
13
13
|
throw new Error("Failed to get 2D context from canvas");
|
|
14
|
-
this.ctx =
|
|
14
|
+
this.ctx = i;
|
|
15
15
|
}
|
|
16
16
|
/**
|
|
17
17
|
* Start the render loop
|
|
@@ -41,7 +41,7 @@ class N {
|
|
|
41
41
|
* Set areas to render
|
|
42
42
|
*/
|
|
43
43
|
setAreas(e) {
|
|
44
|
-
this.areas = [...e].sort((
|
|
44
|
+
this.areas = [...e].sort((i, t) => i.zIndex - t.zIndex), this.requestRender();
|
|
45
45
|
}
|
|
46
46
|
/**
|
|
47
47
|
* Get areas
|
|
@@ -64,8 +64,8 @@ class N {
|
|
|
64
64
|
/**
|
|
65
65
|
* Update content for a single area
|
|
66
66
|
*/
|
|
67
|
-
updateContent(e,
|
|
68
|
-
this.contents.set(e,
|
|
67
|
+
updateContent(e, i) {
|
|
68
|
+
this.contents.set(e, i), i.type === "image" && this.loadContentImages(), this.requestRender(), this.emit("content:change", { areaId: e, content: i });
|
|
69
69
|
}
|
|
70
70
|
/**
|
|
71
71
|
* Remove content for an area
|
|
@@ -78,12 +78,12 @@ class N {
|
|
|
78
78
|
*/
|
|
79
79
|
setBackgroundImage(e) {
|
|
80
80
|
if (this.backgroundImage = e, e?.url && e.url !== this.bgImageElement?.src) {
|
|
81
|
-
const t = new Image();
|
|
81
|
+
const i = e.url, t = new Image();
|
|
82
82
|
t.crossOrigin = "anonymous", t.onload = () => {
|
|
83
83
|
this.bgImageElement = t, this.requestRender();
|
|
84
84
|
}, t.onerror = () => {
|
|
85
|
-
this.bgImageElement = null, console.error("Failed to load background image:",
|
|
86
|
-
}, t.src =
|
|
85
|
+
this.bgImageElement = null, console.error("Failed to load background image:", i), this.requestRender();
|
|
86
|
+
}, t.src = i;
|
|
87
87
|
} else e?.url || (this.bgImageElement = null);
|
|
88
88
|
this.requestRender();
|
|
89
89
|
}
|
|
@@ -129,10 +129,10 @@ class N {
|
|
|
129
129
|
getCenteredPan(e) {
|
|
130
130
|
if (!this.artboard)
|
|
131
131
|
return null;
|
|
132
|
-
const { width:
|
|
132
|
+
const { width: i, height: t } = this.artboard, s = window.devicePixelRatio || 1, a = this.canvas.width / s, o = this.canvas.height / s;
|
|
133
133
|
return {
|
|
134
|
-
x: (a / e -
|
|
135
|
-
y: (o / e -
|
|
134
|
+
x: (a / e - i) / 2,
|
|
135
|
+
y: (o / e - t) / 2
|
|
136
136
|
};
|
|
137
137
|
}
|
|
138
138
|
/**
|
|
@@ -147,10 +147,10 @@ class N {
|
|
|
147
147
|
fitToView() {
|
|
148
148
|
if (!this.artboard)
|
|
149
149
|
return;
|
|
150
|
-
const e = 0, { width:
|
|
150
|
+
const e = 0, { width: i, height: t } = this.artboard, s = window.devicePixelRatio || 1, a = this.canvas.width / s - e * 2, o = this.canvas.height / s - e * 2;
|
|
151
151
|
if (a <= 0 || o <= 0)
|
|
152
152
|
return;
|
|
153
|
-
const
|
|
153
|
+
const n = a / i, r = o / t, d = Math.min(n, r), l = this.canvas.width / s, h = this.canvas.height / s, u = (l / d - i) / 2, p = (h / d - t) / 2;
|
|
154
154
|
this.zoom = d, this.pan = { x: u, y: p }, this.requestRender();
|
|
155
155
|
}
|
|
156
156
|
/**
|
|
@@ -180,48 +180,48 @@ class N {
|
|
|
180
180
|
/**
|
|
181
181
|
* Convert screen coordinates to canvas coordinates
|
|
182
182
|
*/
|
|
183
|
-
screenToCanvas(e,
|
|
184
|
-
const
|
|
185
|
-
return { x: o, y:
|
|
183
|
+
screenToCanvas(e, i) {
|
|
184
|
+
const t = window.devicePixelRatio || 1, s = e / t, a = i / t, o = s / this.zoom - this.pan.x, n = a / this.zoom - this.pan.y;
|
|
185
|
+
return { x: o, y: n };
|
|
186
186
|
}
|
|
187
187
|
/**
|
|
188
188
|
* Get content bounds for an area (in artboard coordinates)
|
|
189
189
|
*/
|
|
190
190
|
getContentBounds(e) {
|
|
191
|
-
const
|
|
192
|
-
if (!t)
|
|
193
|
-
return null;
|
|
194
|
-
const i = this.contents.get(e);
|
|
191
|
+
const i = this.areas.find((d) => d.id === e);
|
|
195
192
|
if (!i)
|
|
196
193
|
return null;
|
|
197
|
-
const
|
|
198
|
-
if (
|
|
199
|
-
|
|
194
|
+
const t = this.contents.get(e);
|
|
195
|
+
if (!t)
|
|
196
|
+
return null;
|
|
197
|
+
const { location: s } = i, { x: a, y: o, width: n, height: r } = s;
|
|
198
|
+
if (t.type === "text") {
|
|
199
|
+
const d = t.offset || { x: 0, y: 0 }, l = t.scale ?? 1, h = t.rotation ?? 0, u = n * l, p = r * l, m = a + n / 2 + d.x, g = o + r / 2 + d.y;
|
|
200
200
|
return {
|
|
201
|
-
x:
|
|
202
|
-
y:
|
|
201
|
+
x: m - u / 2,
|
|
202
|
+
y: g - p / 2,
|
|
203
203
|
width: u,
|
|
204
204
|
height: p,
|
|
205
|
-
rotation:
|
|
206
|
-
centerX:
|
|
207
|
-
centerY:
|
|
205
|
+
rotation: h,
|
|
206
|
+
centerX: m,
|
|
207
|
+
centerY: g
|
|
208
208
|
};
|
|
209
|
-
} else if (
|
|
209
|
+
} else if (t.type === "image") {
|
|
210
210
|
const d = this.loadedImages.get(e);
|
|
211
211
|
if (!d)
|
|
212
212
|
return null;
|
|
213
|
-
const l =
|
|
214
|
-
let
|
|
215
|
-
p >
|
|
216
|
-
const b =
|
|
213
|
+
const l = t.offset || { x: 0, y: 0 }, h = t.scale ?? 1, u = t.rotation ?? 0, p = d.naturalWidth / d.naturalHeight, m = n / r;
|
|
214
|
+
let g, f;
|
|
215
|
+
p > m ? (g = n, f = n / p) : (f = r, g = r * p);
|
|
216
|
+
const b = g * h, w = f * h, y = a + n / 2 + l.x, x = o + r / 2 + l.y;
|
|
217
217
|
return {
|
|
218
|
-
x:
|
|
219
|
-
y:
|
|
218
|
+
x: y - b / 2,
|
|
219
|
+
y: x - w / 2,
|
|
220
220
|
width: b,
|
|
221
|
-
height:
|
|
221
|
+
height: w,
|
|
222
222
|
rotation: u,
|
|
223
|
-
centerX:
|
|
224
|
-
centerY:
|
|
223
|
+
centerX: y,
|
|
224
|
+
centerY: x
|
|
225
225
|
};
|
|
226
226
|
}
|
|
227
227
|
return null;
|
|
@@ -230,13 +230,13 @@ class N {
|
|
|
230
230
|
* Hit test content at given canvas coordinates
|
|
231
231
|
* Returns area ID if content was hit, null otherwise
|
|
232
232
|
*/
|
|
233
|
-
hitTestContent(e,
|
|
233
|
+
hitTestContent(e, i) {
|
|
234
234
|
if (!this.areaSelectionEnabled)
|
|
235
235
|
return null;
|
|
236
|
-
for (let
|
|
237
|
-
const
|
|
238
|
-
if (a && this.isPointInBounds(e,
|
|
239
|
-
return
|
|
236
|
+
for (let t = this.areas.length - 1; t >= 0; t--) {
|
|
237
|
+
const s = this.areas[t], a = this.getContentBounds(s.id);
|
|
238
|
+
if (a && this.isPointInBounds(e, i, a))
|
|
239
|
+
return s.id;
|
|
240
240
|
}
|
|
241
241
|
return null;
|
|
242
242
|
}
|
|
@@ -244,66 +244,66 @@ class N {
|
|
|
244
244
|
* Hit test area at given canvas coordinates (ignoring content)
|
|
245
245
|
* Returns area ID if point is inside area bounds
|
|
246
246
|
*/
|
|
247
|
-
hitTestArea(e,
|
|
247
|
+
hitTestArea(e, i) {
|
|
248
248
|
if (!this.areaSelectionEnabled)
|
|
249
249
|
return null;
|
|
250
|
-
for (let
|
|
251
|
-
const
|
|
252
|
-
if (e >= a && e <= a +
|
|
253
|
-
return
|
|
250
|
+
for (let t = this.areas.length - 1; t >= 0; t--) {
|
|
251
|
+
const s = this.areas[t], { x: a, y: o, width: n, height: r } = s.location;
|
|
252
|
+
if (e >= a && e <= a + n && i >= o && i <= o + r)
|
|
253
|
+
return s.id;
|
|
254
254
|
}
|
|
255
255
|
return null;
|
|
256
256
|
}
|
|
257
257
|
/**
|
|
258
258
|
* Test if a point is inside rotated bounds
|
|
259
259
|
*/
|
|
260
|
-
isPointInBounds(e,
|
|
261
|
-
const { centerX:
|
|
262
|
-
return
|
|
260
|
+
isPointInBounds(e, i, t) {
|
|
261
|
+
const { centerX: s, centerY: a, width: o, height: n, rotation: r } = t, d = -r * Math.PI / 180, l = Math.cos(d), h = Math.sin(d), u = e - s, p = i - a, m = u * l - p * h, g = u * h + p * l;
|
|
262
|
+
return m >= -o / 2 && m <= o / 2 && g >= -n / 2 && g <= n / 2;
|
|
263
263
|
}
|
|
264
264
|
/**
|
|
265
265
|
* Get handle positions for the selected content
|
|
266
266
|
*/
|
|
267
267
|
getContentHandlePositions(e) {
|
|
268
|
-
const
|
|
269
|
-
if (!t)
|
|
270
|
-
return null;
|
|
271
|
-
const i = this.contents.get(e);
|
|
268
|
+
const i = this.areas.find((b) => b.id === e);
|
|
272
269
|
if (!i)
|
|
273
270
|
return null;
|
|
274
|
-
const
|
|
275
|
-
if (!
|
|
271
|
+
const t = this.contents.get(e);
|
|
272
|
+
if (!t)
|
|
276
273
|
return null;
|
|
277
|
-
const
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
}),
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
274
|
+
const s = this.getContentBounds(e);
|
|
275
|
+
if (!s)
|
|
276
|
+
return null;
|
|
277
|
+
const { centerX: a, centerY: o, width: n, height: r, rotation: d } = s, l = 6 / this.zoom, h = 30 / this.zoom, u = d * Math.PI / 180, p = Math.cos(u), m = Math.sin(u), g = (b, w) => ({
|
|
278
|
+
x: a + b * p - w * m,
|
|
279
|
+
y: o + b * m + w * p
|
|
280
|
+
}), f = [];
|
|
281
|
+
if (t.type === "image" && i.imageOptions?.allowScaling || t.type === "text" && i.textOptions?.allowScaling) {
|
|
282
|
+
const b = n / 2, w = r / 2, y = g(-b, -w);
|
|
283
|
+
f.push({ type: "nw", ...y, radius: l });
|
|
284
|
+
const x = g(b, -w);
|
|
285
|
+
f.push({ type: "ne", ...x, radius: l });
|
|
286
|
+
const C = g(b, w);
|
|
287
|
+
f.push({ type: "se", ...C, radius: l });
|
|
288
|
+
const M = g(-b, w);
|
|
289
|
+
f.push({ type: "sw", ...M, radius: l });
|
|
290
290
|
}
|
|
291
|
-
if (
|
|
292
|
-
const b =
|
|
293
|
-
|
|
291
|
+
if (t.type === "image" && i.imageOptions?.allowRotation || t.type === "text" && i.textOptions?.allowRotation) {
|
|
292
|
+
const b = g(0, -r / 2 - h);
|
|
293
|
+
f.push({ type: "rotate", ...b, radius: l });
|
|
294
294
|
}
|
|
295
|
-
return
|
|
295
|
+
return f;
|
|
296
296
|
}
|
|
297
297
|
/**
|
|
298
298
|
* Hit test handles at given canvas coordinates
|
|
299
299
|
*/
|
|
300
|
-
hitTestHandle(e,
|
|
301
|
-
const
|
|
302
|
-
if (!
|
|
300
|
+
hitTestHandle(e, i, t) {
|
|
301
|
+
const s = this.getContentHandlePositions(t);
|
|
302
|
+
if (!s)
|
|
303
303
|
return null;
|
|
304
|
-
for (const a of
|
|
305
|
-
const o = e - a.x,
|
|
306
|
-
if (Math.sqrt(o * o +
|
|
304
|
+
for (const a of s) {
|
|
305
|
+
const o = e - a.x, n = i - a.y;
|
|
306
|
+
if (Math.sqrt(o * o + n * n) <= a.radius * 2)
|
|
307
307
|
return a.type;
|
|
308
308
|
}
|
|
309
309
|
return null;
|
|
@@ -312,24 +312,24 @@ class N {
|
|
|
312
312
|
* Check if positioning is allowed for an area
|
|
313
313
|
*/
|
|
314
314
|
isPositioningAllowed(e) {
|
|
315
|
-
const
|
|
316
|
-
if (!
|
|
315
|
+
const i = this.areas.find((s) => s.id === e);
|
|
316
|
+
if (!i)
|
|
317
317
|
return !1;
|
|
318
|
-
const
|
|
319
|
-
return
|
|
318
|
+
const t = this.contents.get(e);
|
|
319
|
+
return t ? t.type === "text" ? i.textOptions?.allowPositioning ?? !1 : i.imageOptions?.allowPositioning ?? !1 : !1;
|
|
320
320
|
}
|
|
321
321
|
/**
|
|
322
322
|
* Get area location (for constraint calculations)
|
|
323
323
|
*/
|
|
324
324
|
getAreaLocation(e) {
|
|
325
|
-
const
|
|
326
|
-
return
|
|
325
|
+
const i = this.areas.find((t) => t.id === e);
|
|
326
|
+
return i ? i.location : null;
|
|
327
327
|
}
|
|
328
328
|
/**
|
|
329
329
|
* Get area by ID
|
|
330
330
|
*/
|
|
331
331
|
getArea(e) {
|
|
332
|
-
return this.areas.find((
|
|
332
|
+
return this.areas.find((i) => i.id === e);
|
|
333
333
|
}
|
|
334
334
|
/**
|
|
335
335
|
* Set safe area margins for content safety guides
|
|
@@ -344,34 +344,34 @@ class N {
|
|
|
344
344
|
checkSafeAreaViolations() {
|
|
345
345
|
if (!this.safeArea || !this.artboard)
|
|
346
346
|
return [];
|
|
347
|
-
const e = [], { top:
|
|
347
|
+
const e = [], { top: i, right: t, bottom: s, left: a } = this.safeArea, o = a, n = i, r = this.artboard.width - t, d = this.artboard.height - s;
|
|
348
348
|
for (const l of this.areas) {
|
|
349
349
|
if (!this.contents.get(l.id))
|
|
350
350
|
continue;
|
|
351
|
-
const
|
|
352
|
-
if (!
|
|
351
|
+
const h = this.getContentBounds(l.id);
|
|
352
|
+
if (!h)
|
|
353
353
|
continue;
|
|
354
|
-
const { centerX: u, centerY: p, width:
|
|
355
|
-
(M < o ||
|
|
354
|
+
const { centerX: u, centerY: p, width: m, height: g, rotation: f } = h, b = f * Math.PI / 180, w = Math.abs(Math.cos(b)), y = Math.abs(Math.sin(b)), x = m * w + g * y, C = m * y + g * w, M = u - x / 2, R = p - C / 2, D = u + x / 2, N = p + C / 2;
|
|
355
|
+
(M < o || R < n || D > r || N > d) && e.push(l.id);
|
|
356
356
|
}
|
|
357
357
|
return this.safeAreaViolations = new Set(e), this.requestRender(), e;
|
|
358
358
|
}
|
|
359
359
|
/**
|
|
360
360
|
* Add event listener
|
|
361
361
|
*/
|
|
362
|
-
on(e,
|
|
363
|
-
return this.eventListeners.has(e) || this.eventListeners.set(e, /* @__PURE__ */ new Set()), this.eventListeners.get(e).add(
|
|
364
|
-
this.eventListeners.get(e)?.delete(
|
|
362
|
+
on(e, i) {
|
|
363
|
+
return this.eventListeners.has(e) || this.eventListeners.set(e, /* @__PURE__ */ new Set()), this.eventListeners.get(e).add(i), () => {
|
|
364
|
+
this.eventListeners.get(e)?.delete(i);
|
|
365
365
|
};
|
|
366
366
|
}
|
|
367
367
|
/**
|
|
368
368
|
* Emit event
|
|
369
369
|
*/
|
|
370
|
-
emit(e,
|
|
371
|
-
const
|
|
372
|
-
if (
|
|
373
|
-
for (const
|
|
374
|
-
|
|
370
|
+
emit(e, i) {
|
|
371
|
+
const t = this.eventListeners.get(e);
|
|
372
|
+
if (t)
|
|
373
|
+
for (const s of t)
|
|
374
|
+
s(i);
|
|
375
375
|
}
|
|
376
376
|
/**
|
|
377
377
|
* Export to PNG at full resolution
|
|
@@ -379,60 +379,64 @@ class N {
|
|
|
379
379
|
async exportToPNG() {
|
|
380
380
|
if (!this.artboard)
|
|
381
381
|
throw new Error("No artboard configured");
|
|
382
|
-
const { width: e, height:
|
|
383
|
-
|
|
384
|
-
const a =
|
|
382
|
+
const { width: e, height: i, backgroundColor: t } = this.artboard, s = document.createElement("canvas");
|
|
383
|
+
s.width = e, s.height = i;
|
|
384
|
+
const a = s.getContext("2d");
|
|
385
385
|
if (!a)
|
|
386
386
|
throw new Error("Failed to get 2D context for export");
|
|
387
|
-
a.fillStyle =
|
|
387
|
+
a.fillStyle = t, a.fillRect(0, 0, e, i), this.backgroundImage?.position === "behind" && this.bgImageElement && this.drawBackgroundImageOnContext(a, e, i);
|
|
388
388
|
for (const o of this.areas)
|
|
389
389
|
this.drawAreaContentOnContext(a, o);
|
|
390
|
-
return this.backgroundImage?.position === "overlay" && this.bgImageElement && this.drawBackgroundImageOnContext(a, e,
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
390
|
+
return this.backgroundImage?.position === "overlay" && this.bgImageElement && this.drawBackgroundImageOnContext(a, e, i), new Promise((o, n) => {
|
|
391
|
+
try {
|
|
392
|
+
s.toBlob((r) => {
|
|
393
|
+
r ? o(r) : n(new Error("Failed to create PNG blob"));
|
|
394
|
+
}, "image/png", 1);
|
|
395
|
+
} catch (r) {
|
|
396
|
+
r instanceof DOMException && r.name === "SecurityError" ? n(new Error("Cannot export: images failed to load with CORS. Check CDN configuration.")) : n(r);
|
|
397
|
+
}
|
|
394
398
|
});
|
|
395
399
|
}
|
|
396
400
|
/**
|
|
397
401
|
* Load images from content data URLs
|
|
398
402
|
*/
|
|
399
403
|
loadContentImages() {
|
|
400
|
-
for (const [e,
|
|
401
|
-
if (
|
|
404
|
+
for (const [e, i] of this.contents)
|
|
405
|
+
if (i.type === "image" && !this.loadedImages.has(e) && !this.loadingImages.has(e)) {
|
|
402
406
|
this.loadingImages.add(e);
|
|
403
|
-
const
|
|
404
|
-
|
|
405
|
-
this.loadedImages.set(e,
|
|
406
|
-
},
|
|
407
|
+
const t = new Image();
|
|
408
|
+
t.crossOrigin = "anonymous", t.onload = () => {
|
|
409
|
+
this.loadedImages.set(e, t), this.loadingImages.delete(e), this.requestRender();
|
|
410
|
+
}, t.onerror = () => {
|
|
407
411
|
this.loadingImages.delete(e), console.error("Failed to load image for area:", e);
|
|
408
|
-
},
|
|
412
|
+
}, t.src = i.dataUrl;
|
|
409
413
|
}
|
|
410
414
|
for (const e of this.loadedImages.keys()) {
|
|
411
|
-
const
|
|
412
|
-
(!
|
|
415
|
+
const i = this.contents.get(e);
|
|
416
|
+
(!i || i.type !== "image") && this.loadedImages.delete(e);
|
|
413
417
|
}
|
|
414
418
|
}
|
|
415
419
|
/**
|
|
416
420
|
* Render the canvas
|
|
417
421
|
*/
|
|
418
422
|
render() {
|
|
419
|
-
const { ctx: e, canvas:
|
|
420
|
-
if (e.fillStyle = "#f3f4f6", e.fillRect(0, 0,
|
|
423
|
+
const { ctx: e, canvas: i } = this;
|
|
424
|
+
if (e.fillStyle = "#f3f4f6", e.fillRect(0, 0, i.width, i.height), !this.artboard)
|
|
421
425
|
return;
|
|
422
426
|
e.save();
|
|
423
|
-
const
|
|
424
|
-
e.scale(
|
|
425
|
-
const { width:
|
|
426
|
-
e.fillStyle = o, e.fillRect(0, 0,
|
|
427
|
-
for (const
|
|
428
|
-
this.renderAreaWithContent(
|
|
427
|
+
const t = window.devicePixelRatio || 1;
|
|
428
|
+
e.scale(t, t), e.scale(this.zoom, this.zoom), e.translate(this.pan.x, this.pan.y);
|
|
429
|
+
const { width: s, height: a, backgroundColor: o } = this.artboard;
|
|
430
|
+
e.fillStyle = o, e.fillRect(0, 0, s, a), e.strokeStyle = "#d1d5db", e.lineWidth = 1 / this.zoom, e.strokeRect(0, 0, s, a), this.backgroundImage?.position === "behind" && this.renderBackgroundImage();
|
|
431
|
+
for (const n of this.areas)
|
|
432
|
+
this.renderAreaWithContent(n);
|
|
429
433
|
this.backgroundImage?.position === "overlay" && this.renderBackgroundImage(), this.renderSafeAreaGuide();
|
|
430
|
-
for (const
|
|
431
|
-
const r = this.areas.find((p) => p.id ===
|
|
434
|
+
for (const n of this.safeAreaViolations) {
|
|
435
|
+
const r = this.areas.find((p) => p.id === n);
|
|
432
436
|
if (!r)
|
|
433
437
|
continue;
|
|
434
|
-
const { x: d, y: l, width:
|
|
435
|
-
e.save(), e.fillStyle = "rgba(239, 68, 68, 0.15)", e.fillRect(d, l,
|
|
438
|
+
const { x: d, y: l, width: h, height: u } = r.location;
|
|
439
|
+
e.save(), e.fillStyle = "rgba(239, 68, 68, 0.15)", e.fillRect(d, l, h, u), e.restore();
|
|
436
440
|
}
|
|
437
441
|
this.selectedAreaId && this.renderSelectionOverlay(this.selectedAreaId), e.restore();
|
|
438
442
|
}
|
|
@@ -440,38 +444,38 @@ class N {
|
|
|
440
444
|
* Render selection overlay with handles
|
|
441
445
|
*/
|
|
442
446
|
renderSelectionOverlay(e) {
|
|
443
|
-
const
|
|
444
|
-
if (!
|
|
445
|
-
const l = this.areas.find((
|
|
447
|
+
const i = this.getContentBounds(e);
|
|
448
|
+
if (!i) {
|
|
449
|
+
const l = this.areas.find((f) => f.id === e);
|
|
446
450
|
if (!l)
|
|
447
451
|
return;
|
|
448
|
-
const { ctx:
|
|
449
|
-
|
|
452
|
+
const { ctx: h } = this, { x: u, y: p, width: m, height: g } = l.location;
|
|
453
|
+
h.save(), h.strokeStyle = "#3b82f6", h.lineWidth = 2 / this.zoom, h.setLineDash([]), h.strokeRect(u, p, m, g), h.restore();
|
|
450
454
|
return;
|
|
451
455
|
}
|
|
452
|
-
const { ctx:
|
|
453
|
-
|
|
456
|
+
const { ctx: t } = this, { centerX: s, centerY: a, width: o, height: n, rotation: r } = i;
|
|
457
|
+
t.save(), t.translate(s, a), t.rotate(r * Math.PI / 180), t.strokeStyle = "#3b82f6", t.lineWidth = 2 / this.zoom, t.setLineDash([]), t.strokeRect(-o / 2, -n / 2, o, n), t.restore();
|
|
454
458
|
const d = this.getContentHandlePositions(e);
|
|
455
459
|
if (d)
|
|
456
460
|
for (const l of d) {
|
|
457
|
-
|
|
458
|
-
const
|
|
461
|
+
t.save();
|
|
462
|
+
const h = this.hoveredHandle === l.type, u = h ? "#dbeafe" : "#ffffff", p = h ? "#2563eb" : "#3b82f6";
|
|
459
463
|
if (l.type === "rotate") {
|
|
460
|
-
const
|
|
461
|
-
|
|
464
|
+
const m = this.getRotatedPoint(s, a - n / 2, s, a, r);
|
|
465
|
+
t.beginPath(), t.strokeStyle = p, t.lineWidth = 1 / this.zoom, t.setLineDash([4 / this.zoom, 4 / this.zoom]), t.moveTo(m.x, m.y), t.lineTo(l.x, l.y), t.stroke(), t.setLineDash([]), t.beginPath(), t.fillStyle = u, t.strokeStyle = p, t.lineWidth = 2 / this.zoom, t.arc(l.x, l.y, l.radius, 0, Math.PI * 2), t.fill(), t.stroke(), t.beginPath(), t.strokeStyle = p, t.lineWidth = 1.5 / this.zoom, t.arc(l.x, l.y, l.radius * 0.5, -Math.PI * 0.8, Math.PI * 0.5), t.stroke();
|
|
462
466
|
} else
|
|
463
|
-
|
|
464
|
-
|
|
467
|
+
t.fillStyle = u, t.strokeStyle = p, t.lineWidth = 2 / this.zoom, t.fillRect(l.x - l.radius, l.y - l.radius, l.radius * 2, l.radius * 2), t.strokeRect(l.x - l.radius, l.y - l.radius, l.radius * 2, l.radius * 2);
|
|
468
|
+
t.restore();
|
|
465
469
|
}
|
|
466
470
|
}
|
|
467
471
|
/**
|
|
468
472
|
* Rotate a point around a center
|
|
469
473
|
*/
|
|
470
|
-
getRotatedPoint(e,
|
|
471
|
-
const o = a * Math.PI / 180,
|
|
474
|
+
getRotatedPoint(e, i, t, s, a) {
|
|
475
|
+
const o = a * Math.PI / 180, n = Math.cos(o), r = Math.sin(o), d = e - t, l = i - s;
|
|
472
476
|
return {
|
|
473
|
-
x:
|
|
474
|
-
y:
|
|
477
|
+
x: t + d * n - l * r,
|
|
478
|
+
y: s + d * r + l * n
|
|
475
479
|
};
|
|
476
480
|
}
|
|
477
481
|
/**
|
|
@@ -480,11 +484,11 @@ class N {
|
|
|
480
484
|
renderBackgroundImage() {
|
|
481
485
|
if (!this.bgImageElement || !this.artboard || !this.backgroundImage)
|
|
482
486
|
return;
|
|
483
|
-
const { ctx: e } = this, { opacity:
|
|
484
|
-
e.save(), e.globalAlpha =
|
|
485
|
-
const a = this.bgImageElement.naturalWidth / this.bgImageElement.naturalHeight, o =
|
|
486
|
-
let
|
|
487
|
-
a > o ? (r =
|
|
487
|
+
const { ctx: e } = this, { opacity: i } = this.backgroundImage, { width: t, height: s } = this.artboard;
|
|
488
|
+
e.save(), e.globalAlpha = i;
|
|
489
|
+
const a = this.bgImageElement.naturalWidth / this.bgImageElement.naturalHeight, o = t / s;
|
|
490
|
+
let n, r, d, l;
|
|
491
|
+
a > o ? (r = s, n = s * a, d = (t - n) / 2, l = 0) : (n = t, r = t / a, d = 0, l = (s - r) / 2), e.drawImage(this.bgImageElement, d, l, n, r), e.restore();
|
|
488
492
|
}
|
|
489
493
|
/**
|
|
490
494
|
* Render safe area guide as dashed green rectangle
|
|
@@ -492,106 +496,106 @@ class N {
|
|
|
492
496
|
renderSafeAreaGuide() {
|
|
493
497
|
if (!this.safeArea || !this.artboard)
|
|
494
498
|
return;
|
|
495
|
-
const { ctx: e } = this, { top:
|
|
496
|
-
r <= 0 || d <= 0 || (e.save(), e.strokeStyle = "#22c55e", e.lineWidth = 1 / this.zoom, e.setLineDash([6 / this.zoom, 4 / this.zoom]), e.strokeRect(o,
|
|
499
|
+
const { ctx: e } = this, { top: i, right: t, bottom: s, left: a } = this.safeArea, o = a, n = i, r = this.artboard.width - a - t, d = this.artboard.height - i - s;
|
|
500
|
+
r <= 0 || d <= 0 || (e.save(), e.strokeStyle = "#22c55e", e.lineWidth = 1 / this.zoom, e.setLineDash([6 / this.zoom, 4 / this.zoom]), e.strokeRect(o, n, r, d), e.setLineDash([]), e.restore());
|
|
497
501
|
}
|
|
498
502
|
/**
|
|
499
503
|
* Render an area with its content
|
|
500
504
|
*/
|
|
501
505
|
renderAreaWithContent(e) {
|
|
502
|
-
const { ctx:
|
|
503
|
-
|
|
506
|
+
const { ctx: i } = this, { location: t, backgroundColor: s } = e, { x: a, y: o, width: n, height: r } = t;
|
|
507
|
+
s && (i.fillStyle = s, i.fillRect(a, o, n, r));
|
|
504
508
|
const d = this.contents.get(e.id);
|
|
505
|
-
d && (d.type === "text" ? this.renderTextContent(e, d) : d.type === "image" && this.renderImageContent(e, d)), this.showAreaBorders && e.showBorder && (
|
|
509
|
+
d && (d.type === "text" ? this.renderTextContent(e, d) : d.type === "image" && this.renderImageContent(e, d)), this.showAreaBorders && e.showBorder && (i.save(), i.strokeStyle = e.borderColor || "#3b82f6", i.lineWidth = 1 / this.zoom, i.setLineDash([4 / this.zoom, 4 / this.zoom]), i.strokeRect(a, o, n, r), i.setLineDash([]), i.restore());
|
|
506
510
|
}
|
|
507
511
|
/**
|
|
508
512
|
* Render text content in an area
|
|
509
513
|
*/
|
|
510
|
-
renderTextContent(e,
|
|
511
|
-
if (!
|
|
514
|
+
renderTextContent(e, i) {
|
|
515
|
+
if (!i.text.trim())
|
|
512
516
|
return;
|
|
513
|
-
const { ctx:
|
|
514
|
-
|
|
515
|
-
let
|
|
516
|
-
switch (
|
|
517
|
+
const { ctx: t } = this, { location: s } = e, { x: a, y: o, width: n, height: r } = s, d = i.offset || { x: 0, y: 0 }, l = i.scale ?? 1, h = i.rotation ?? 0, u = a + n / 2 + d.x, p = o + r / 2 + d.y;
|
|
518
|
+
t.save(), t.beginPath(), t.rect(a, o, n, r), t.clip(), t.translate(u, p), t.rotate(h * Math.PI / 180), t.scale(l, l), t.font = `${i.size}px ${i.font}, sans-serif`, t.fillStyle = i.color, t.textBaseline = "middle";
|
|
519
|
+
let m;
|
|
520
|
+
switch (i.align) {
|
|
517
521
|
case "left":
|
|
518
|
-
|
|
522
|
+
t.textAlign = "left", m = -n / 2 + 10;
|
|
519
523
|
break;
|
|
520
524
|
case "right":
|
|
521
|
-
|
|
525
|
+
t.textAlign = "right", m = n / 2 - 10;
|
|
522
526
|
break;
|
|
523
527
|
case "center":
|
|
524
528
|
default:
|
|
525
|
-
|
|
529
|
+
t.textAlign = "center", m = 0;
|
|
526
530
|
break;
|
|
527
531
|
}
|
|
528
|
-
|
|
532
|
+
t.fillText(i.text, m, 0), t.restore();
|
|
529
533
|
}
|
|
530
534
|
/**
|
|
531
535
|
* Render image content in an area
|
|
532
536
|
*/
|
|
533
|
-
renderImageContent(e,
|
|
534
|
-
const
|
|
535
|
-
if (!
|
|
537
|
+
renderImageContent(e, i) {
|
|
538
|
+
const t = this.loadedImages.get(e.id);
|
|
539
|
+
if (!t)
|
|
536
540
|
return;
|
|
537
|
-
const { ctx:
|
|
538
|
-
|
|
539
|
-
const p =
|
|
540
|
-
let
|
|
541
|
-
p >
|
|
542
|
-
const b =
|
|
543
|
-
|
|
541
|
+
const { ctx: s } = this, { location: a } = e, { x: o, y: n, width: r, height: d } = a, l = i.offset || { x: 0, y: 0 }, h = i.scale ?? 1, u = i.rotation ?? 0;
|
|
542
|
+
s.save(), s.beginPath(), s.rect(o, n, r, d), s.clip();
|
|
543
|
+
const p = t.naturalWidth / t.naturalHeight, m = r / d;
|
|
544
|
+
let g, f;
|
|
545
|
+
p > m ? (g = r, f = r / p) : (f = d, g = d * p);
|
|
546
|
+
const b = g * h, w = f * h, y = o + r / 2 + l.x, x = n + d / 2 + l.y;
|
|
547
|
+
s.translate(y, x), s.rotate(u * Math.PI / 180), s.drawImage(t, -b / 2, -w / 2, b, w), s.restore();
|
|
544
548
|
}
|
|
545
549
|
/**
|
|
546
550
|
* Draw background image on a specific context (for export)
|
|
547
551
|
*/
|
|
548
|
-
drawBackgroundImageOnContext(e,
|
|
552
|
+
drawBackgroundImageOnContext(e, i, t) {
|
|
549
553
|
if (!this.bgImageElement || !this.backgroundImage)
|
|
550
554
|
return;
|
|
551
|
-
const { opacity:
|
|
552
|
-
e.save(), e.globalAlpha =
|
|
553
|
-
const a = this.bgImageElement.naturalWidth / this.bgImageElement.naturalHeight, o =
|
|
554
|
-
let
|
|
555
|
-
a > o ? (r =
|
|
555
|
+
const { opacity: s } = this.backgroundImage;
|
|
556
|
+
e.save(), e.globalAlpha = s;
|
|
557
|
+
const a = this.bgImageElement.naturalWidth / this.bgImageElement.naturalHeight, o = i / t;
|
|
558
|
+
let n, r, d, l;
|
|
559
|
+
a > o ? (r = t, n = t * a, d = (i - n) / 2, l = 0) : (n = i, r = i / a, d = 0, l = (t - r) / 2), e.drawImage(this.bgImageElement, d, l, n, r), e.restore();
|
|
556
560
|
}
|
|
557
561
|
/**
|
|
558
562
|
* Draw area content on a specific context (for export)
|
|
559
563
|
*/
|
|
560
|
-
drawAreaContentOnContext(e,
|
|
561
|
-
const { location:
|
|
562
|
-
|
|
563
|
-
const d = this.contents.get(
|
|
564
|
+
drawAreaContentOnContext(e, i) {
|
|
565
|
+
const { location: t, backgroundColor: s } = i, { x: a, y: o, width: n, height: r } = t;
|
|
566
|
+
s && (e.fillStyle = s, e.fillRect(a, o, n, r));
|
|
567
|
+
const d = this.contents.get(i.id);
|
|
564
568
|
if (d) {
|
|
565
569
|
if (d.type === "text") {
|
|
566
570
|
if (!d.text.trim())
|
|
567
571
|
return;
|
|
568
|
-
const l = d.offset || { x: 0, y: 0 },
|
|
569
|
-
e.save(), e.beginPath(), e.rect(a, o,
|
|
570
|
-
let
|
|
572
|
+
const l = d.offset || { x: 0, y: 0 }, h = d.scale ?? 1, u = d.rotation ?? 0, p = a + n / 2 + l.x, m = o + r / 2 + l.y;
|
|
573
|
+
e.save(), e.beginPath(), e.rect(a, o, n, r), e.clip(), e.translate(p, m), e.rotate(u * Math.PI / 180), e.scale(h, h), e.font = `${d.size}px ${d.font}, sans-serif`, e.fillStyle = d.color, e.textBaseline = "middle";
|
|
574
|
+
let g;
|
|
571
575
|
switch (d.align) {
|
|
572
576
|
case "left":
|
|
573
|
-
e.textAlign = "left",
|
|
577
|
+
e.textAlign = "left", g = -n / 2 + 10;
|
|
574
578
|
break;
|
|
575
579
|
case "right":
|
|
576
|
-
e.textAlign = "right",
|
|
580
|
+
e.textAlign = "right", g = n / 2 - 10;
|
|
577
581
|
break;
|
|
578
582
|
case "center":
|
|
579
583
|
default:
|
|
580
|
-
e.textAlign = "center",
|
|
584
|
+
e.textAlign = "center", g = 0;
|
|
581
585
|
break;
|
|
582
586
|
}
|
|
583
|
-
e.fillText(d.text,
|
|
587
|
+
e.fillText(d.text, g, 0), e.restore();
|
|
584
588
|
} else if (d.type === "image") {
|
|
585
|
-
const l = this.loadedImages.get(
|
|
589
|
+
const l = this.loadedImages.get(i.id);
|
|
586
590
|
if (!l)
|
|
587
591
|
return;
|
|
588
|
-
const
|
|
589
|
-
e.save(), e.beginPath(), e.rect(a, o,
|
|
590
|
-
const
|
|
591
|
-
let
|
|
592
|
-
|
|
593
|
-
const
|
|
594
|
-
e.translate(
|
|
592
|
+
const h = d.offset || { x: 0, y: 0 }, u = d.scale ?? 1, p = d.rotation ?? 0;
|
|
593
|
+
e.save(), e.beginPath(), e.rect(a, o, n, r), e.clip();
|
|
594
|
+
const m = l.naturalWidth / l.naturalHeight, g = n / r;
|
|
595
|
+
let f, b;
|
|
596
|
+
m > g ? (f = n, b = n / m) : (b = r, f = r * m);
|
|
597
|
+
const w = f * u, y = b * u, x = a + n / 2 + h.x, C = o + r / 2 + h.y;
|
|
598
|
+
e.translate(x, C), e.rotate(p * Math.PI / 180), e.drawImage(l, -w / 2, -y / 2, w, y), e.restore();
|
|
595
599
|
}
|
|
596
600
|
}
|
|
597
601
|
}
|
|
@@ -602,8 +606,8 @@ class N {
|
|
|
602
606
|
this.stop(), this.bgImageElement = null, this.loadedImages.clear(), this.loadingImages.clear(), this.eventListeners.clear();
|
|
603
607
|
}
|
|
604
608
|
}
|
|
605
|
-
function
|
|
606
|
-
const e =
|
|
609
|
+
function S(v) {
|
|
610
|
+
const e = v.textOptions;
|
|
607
611
|
return {
|
|
608
612
|
type: "text",
|
|
609
613
|
text: e?.defaultText || "",
|
|
@@ -613,13 +617,13 @@ function E(f) {
|
|
|
613
617
|
align: e?.defaultAlign || "center"
|
|
614
618
|
};
|
|
615
619
|
}
|
|
616
|
-
function
|
|
617
|
-
return
|
|
620
|
+
function z(v) {
|
|
621
|
+
return v.contentType === "text" || v.contentType === "both";
|
|
618
622
|
}
|
|
619
|
-
function
|
|
620
|
-
return
|
|
623
|
+
function T(v) {
|
|
624
|
+
return v.contentType === "image" || v.contentType === "both";
|
|
621
625
|
}
|
|
622
|
-
const
|
|
626
|
+
const V = 50;
|
|
623
627
|
class O {
|
|
624
628
|
constructor(e) {
|
|
625
629
|
this.history = [], this.historyIndex = -1, this.listeners = /* @__PURE__ */ new Set(), this.isRestoring = !1, this.state = this.cloneState(e), this.history.push({
|
|
@@ -634,9 +638,9 @@ class O {
|
|
|
634
638
|
/**
|
|
635
639
|
* Update state and save to history
|
|
636
640
|
*/
|
|
637
|
-
setState(e,
|
|
638
|
-
const
|
|
639
|
-
this.state = { ...this.state, ...e }, !
|
|
641
|
+
setState(e, i, t = !1) {
|
|
642
|
+
const s = this.state;
|
|
643
|
+
this.state = { ...this.state, ...e }, !t && !this.isRestoring && this.saveToHistory(s, i), this.notifyListeners();
|
|
640
644
|
}
|
|
641
645
|
/**
|
|
642
646
|
* Update state without saving to history
|
|
@@ -666,12 +670,12 @@ class O {
|
|
|
666
670
|
getHistory() {
|
|
667
671
|
return this.history;
|
|
668
672
|
}
|
|
669
|
-
saveToHistory(e,
|
|
673
|
+
saveToHistory(e, i) {
|
|
670
674
|
this.historyIndex < this.history.length - 1 && (this.history = this.history.slice(0, this.historyIndex + 1)), this.history.push({
|
|
671
675
|
state: this.cloneState(e),
|
|
672
676
|
timestamp: Date.now(),
|
|
673
|
-
description:
|
|
674
|
-
}), this.history.length >
|
|
677
|
+
description: i
|
|
678
|
+
}), this.history.length > V && this.history.shift(), this.historyIndex = this.history.length - 1;
|
|
675
679
|
}
|
|
676
680
|
restoreFromHistory() {
|
|
677
681
|
const e = this.history[this.historyIndex];
|
|
@@ -679,11 +683,11 @@ class O {
|
|
|
679
683
|
}
|
|
680
684
|
notifyListeners() {
|
|
681
685
|
const e = this.state;
|
|
682
|
-
for (const
|
|
686
|
+
for (const i of this.listeners)
|
|
683
687
|
try {
|
|
684
|
-
|
|
685
|
-
} catch (
|
|
686
|
-
console.error("Error in state change listener:",
|
|
688
|
+
i(e);
|
|
689
|
+
} catch (t) {
|
|
690
|
+
console.error("Error in state change listener:", t);
|
|
687
691
|
}
|
|
688
692
|
}
|
|
689
693
|
/**
|
|
@@ -694,14 +698,14 @@ class O {
|
|
|
694
698
|
return JSON.parse(JSON.stringify(e));
|
|
695
699
|
}
|
|
696
700
|
}
|
|
697
|
-
class
|
|
701
|
+
class P {
|
|
698
702
|
constructor(e) {
|
|
699
703
|
this.listeners = /* @__PURE__ */ new Set(), this.areas = e, this.contents = this.buildInitialContents();
|
|
700
704
|
}
|
|
701
705
|
buildInitialContents() {
|
|
702
706
|
const e = /* @__PURE__ */ new Map();
|
|
703
|
-
for (const
|
|
704
|
-
|
|
707
|
+
for (const i of this.areas)
|
|
708
|
+
z(i) && e.set(i.id, S(i));
|
|
705
709
|
return e;
|
|
706
710
|
}
|
|
707
711
|
getContents() {
|
|
@@ -710,8 +714,8 @@ class T {
|
|
|
710
714
|
getContent(e) {
|
|
711
715
|
return this.contents.get(e);
|
|
712
716
|
}
|
|
713
|
-
setTextContent(e,
|
|
714
|
-
const
|
|
717
|
+
setTextContent(e, i) {
|
|
718
|
+
const t = this.contents.get(e), s = this.areas.find((o) => o.id === e), a = s ? S(s) : {
|
|
715
719
|
type: "text",
|
|
716
720
|
text: "",
|
|
717
721
|
font: "Inter",
|
|
@@ -719,35 +723,35 @@ class T {
|
|
|
719
723
|
color: "#000000",
|
|
720
724
|
align: "center"
|
|
721
725
|
};
|
|
722
|
-
|
|
726
|
+
t?.type === "text" ? this.contents.set(e, { ...t, ...i }) : this.contents.set(e, { ...a, ...i }), this.notify();
|
|
723
727
|
}
|
|
724
|
-
setImageContent(e,
|
|
725
|
-
const
|
|
728
|
+
setImageContent(e, i, t) {
|
|
729
|
+
const s = {
|
|
726
730
|
type: "image",
|
|
727
|
-
dataUrl:
|
|
728
|
-
filename:
|
|
731
|
+
dataUrl: i,
|
|
732
|
+
filename: t
|
|
729
733
|
};
|
|
730
|
-
this.contents.set(e,
|
|
734
|
+
this.contents.set(e, s), this.notify();
|
|
731
735
|
}
|
|
732
736
|
clearContent(e) {
|
|
733
|
-
const
|
|
734
|
-
|
|
737
|
+
const i = this.areas.find((t) => t.id === e);
|
|
738
|
+
i && z(i) ? this.contents.set(e, S(i)) : this.contents.delete(e), this.notify();
|
|
735
739
|
}
|
|
736
|
-
setContentOffset(e,
|
|
737
|
-
const
|
|
738
|
-
|
|
740
|
+
setContentOffset(e, i) {
|
|
741
|
+
const t = this.contents.get(e);
|
|
742
|
+
t && (this.contents.set(e, { ...t, offset: i }), this.notify());
|
|
739
743
|
}
|
|
740
|
-
setImageScale(e,
|
|
741
|
-
const
|
|
742
|
-
if (!
|
|
743
|
-
const
|
|
744
|
-
this.contents.set(e, { ...
|
|
744
|
+
setImageScale(e, i) {
|
|
745
|
+
const t = this.contents.get(e);
|
|
746
|
+
if (!t || t.type !== "image") return;
|
|
747
|
+
const s = Math.max(0.1, Math.min(5, i));
|
|
748
|
+
this.contents.set(e, { ...t, scale: s }), this.notify();
|
|
745
749
|
}
|
|
746
|
-
setImageRotation(e,
|
|
747
|
-
const
|
|
748
|
-
if (!
|
|
749
|
-
let
|
|
750
|
-
|
|
750
|
+
setImageRotation(e, i) {
|
|
751
|
+
const t = this.contents.get(e);
|
|
752
|
+
if (!t || t.type !== "image") return;
|
|
753
|
+
let s = i % 360;
|
|
754
|
+
s < 0 && (s += 360), this.contents.set(e, { ...t, rotation: s }), this.notify();
|
|
751
755
|
}
|
|
752
756
|
reset() {
|
|
753
757
|
this.contents = this.buildInitialContents(), this.notify();
|
|
@@ -771,12 +775,12 @@ class T {
|
|
|
771
775
|
for (const e of this.listeners)
|
|
772
776
|
try {
|
|
773
777
|
e();
|
|
774
|
-
} catch (
|
|
775
|
-
console.error("Error in AreaContentManager listener:",
|
|
778
|
+
} catch (i) {
|
|
779
|
+
console.error("Error in AreaContentManager listener:", i);
|
|
776
780
|
}
|
|
777
781
|
}
|
|
778
782
|
}
|
|
779
|
-
class
|
|
783
|
+
class E {
|
|
780
784
|
constructor() {
|
|
781
785
|
this.listeners = /* @__PURE__ */ new Map();
|
|
782
786
|
}
|
|
@@ -787,8 +791,8 @@ class I {
|
|
|
787
791
|
* @param handler - Event handler function
|
|
788
792
|
* @returns Unsubscribe function
|
|
789
793
|
*/
|
|
790
|
-
on(e,
|
|
791
|
-
return this.listeners.has(e) || this.listeners.set(e, /* @__PURE__ */ new Set()), this.listeners.get(e).add(
|
|
794
|
+
on(e, i) {
|
|
795
|
+
return this.listeners.has(e) || this.listeners.set(e, /* @__PURE__ */ new Set()), this.listeners.get(e).add(i), () => this.off(e, i);
|
|
792
796
|
}
|
|
793
797
|
/**
|
|
794
798
|
* Register a one-time event listener
|
|
@@ -797,11 +801,11 @@ class I {
|
|
|
797
801
|
* @param handler - Event handler function
|
|
798
802
|
* @returns Unsubscribe function
|
|
799
803
|
*/
|
|
800
|
-
once(e,
|
|
801
|
-
const
|
|
802
|
-
|
|
804
|
+
once(e, i) {
|
|
805
|
+
const t = (s) => {
|
|
806
|
+
i(s), this.off(e, t);
|
|
803
807
|
};
|
|
804
|
-
return this.on(e,
|
|
808
|
+
return this.on(e, t);
|
|
805
809
|
}
|
|
806
810
|
/**
|
|
807
811
|
* Remove an event listener
|
|
@@ -809,9 +813,9 @@ class I {
|
|
|
809
813
|
* @param event - Event name
|
|
810
814
|
* @param handler - Event handler function to remove
|
|
811
815
|
*/
|
|
812
|
-
off(e,
|
|
813
|
-
const
|
|
814
|
-
|
|
816
|
+
off(e, i) {
|
|
817
|
+
const t = this.listeners.get(e);
|
|
818
|
+
t && (t.delete(i), t.size === 0 && this.listeners.delete(e));
|
|
815
819
|
}
|
|
816
820
|
/**
|
|
817
821
|
* Emit an event to all registered listeners
|
|
@@ -819,13 +823,13 @@ class I {
|
|
|
819
823
|
* @param event - Event name
|
|
820
824
|
* @param data - Event data
|
|
821
825
|
*/
|
|
822
|
-
emit(e,
|
|
823
|
-
const
|
|
824
|
-
if (
|
|
825
|
-
const
|
|
826
|
-
for (const a of
|
|
826
|
+
emit(e, i) {
|
|
827
|
+
const t = this.listeners.get(e);
|
|
828
|
+
if (t) {
|
|
829
|
+
const s = Array.from(t);
|
|
830
|
+
for (const a of s)
|
|
827
831
|
try {
|
|
828
|
-
a(
|
|
832
|
+
a(i);
|
|
829
833
|
} catch (o) {
|
|
830
834
|
console.error(`Error in event handler for "${String(e)}":`, o);
|
|
831
835
|
}
|
|
@@ -849,7 +853,7 @@ class I {
|
|
|
849
853
|
return this.listeners.get(e)?.size ?? 0;
|
|
850
854
|
}
|
|
851
855
|
}
|
|
852
|
-
class
|
|
856
|
+
class $ extends E {
|
|
853
857
|
constructor() {
|
|
854
858
|
super(...arguments), this.activeTouches = /* @__PURE__ */ new Map(), this.initialPinchDistance = null, this.lastPinchCenter = null, this.isTwoFingerGesture = !1;
|
|
855
859
|
}
|
|
@@ -858,86 +862,86 @@ class V extends I {
|
|
|
858
862
|
return this.isTwoFingerGesture;
|
|
859
863
|
}
|
|
860
864
|
handleTouchStart(e) {
|
|
861
|
-
for (let
|
|
862
|
-
const
|
|
863
|
-
this.activeTouches.set(
|
|
864
|
-
x:
|
|
865
|
-
y:
|
|
865
|
+
for (let i = 0; i < e.changedTouches.length; i++) {
|
|
866
|
+
const t = e.changedTouches[i];
|
|
867
|
+
this.activeTouches.set(t.identifier, {
|
|
868
|
+
x: t.clientX,
|
|
869
|
+
y: t.clientY
|
|
866
870
|
});
|
|
867
871
|
}
|
|
868
872
|
if (this.activeTouches.size === 2) {
|
|
869
873
|
this.isTwoFingerGesture = !0, e.preventDefault();
|
|
870
|
-
const [
|
|
871
|
-
this.initialPinchDistance = this.getDistance(
|
|
874
|
+
const [i, t] = Array.from(this.activeTouches.values());
|
|
875
|
+
this.initialPinchDistance = this.getDistance(i, t), this.lastPinchCenter = this.getCenter(i, t);
|
|
872
876
|
}
|
|
873
877
|
}
|
|
874
878
|
handleTouchMove(e) {
|
|
875
|
-
for (let
|
|
876
|
-
const
|
|
877
|
-
this.activeTouches.set(
|
|
878
|
-
x:
|
|
879
|
-
y:
|
|
879
|
+
for (let i = 0; i < e.changedTouches.length; i++) {
|
|
880
|
+
const t = e.changedTouches[i];
|
|
881
|
+
this.activeTouches.set(t.identifier, {
|
|
882
|
+
x: t.clientX,
|
|
883
|
+
y: t.clientY
|
|
880
884
|
});
|
|
881
885
|
}
|
|
882
886
|
if (this.activeTouches.size === 2 && this.initialPinchDistance !== null && this.lastPinchCenter) {
|
|
883
887
|
e.preventDefault();
|
|
884
|
-
const [
|
|
888
|
+
const [i, t] = Array.from(this.activeTouches.values()), s = this.getDistance(i, t), a = this.getCenter(i, t), o = s / this.initialPinchDistance;
|
|
885
889
|
this.emit("pinch", {
|
|
886
890
|
scale: o,
|
|
887
891
|
centerX: a.x,
|
|
888
892
|
centerY: a.y
|
|
889
893
|
});
|
|
890
|
-
const
|
|
891
|
-
(Math.abs(
|
|
894
|
+
const n = a.x - this.lastPinchCenter.x, r = a.y - this.lastPinchCenter.y;
|
|
895
|
+
(Math.abs(n) > 0.5 || Math.abs(r) > 0.5) && this.emit("pan", { deltaX: n, deltaY: r }), this.initialPinchDistance = s, this.lastPinchCenter = a;
|
|
892
896
|
}
|
|
893
897
|
}
|
|
894
898
|
handleTouchEnd(e) {
|
|
895
|
-
for (let
|
|
896
|
-
const
|
|
897
|
-
this.activeTouches.delete(
|
|
899
|
+
for (let i = 0; i < e.changedTouches.length; i++) {
|
|
900
|
+
const t = e.changedTouches[i];
|
|
901
|
+
this.activeTouches.delete(t.identifier);
|
|
898
902
|
}
|
|
899
903
|
this.activeTouches.size < 2 && (this.initialPinchDistance = null, this.lastPinchCenter = null, this.isTwoFingerGesture = !1);
|
|
900
904
|
}
|
|
901
905
|
reset() {
|
|
902
906
|
this.activeTouches.clear(), this.initialPinchDistance = null, this.lastPinchCenter = null, this.isTwoFingerGesture = !1;
|
|
903
907
|
}
|
|
904
|
-
getDistance(e,
|
|
905
|
-
const
|
|
906
|
-
return Math.sqrt(
|
|
908
|
+
getDistance(e, i) {
|
|
909
|
+
const t = i.x - e.x, s = i.y - e.y;
|
|
910
|
+
return Math.sqrt(t * t + s * s);
|
|
907
911
|
}
|
|
908
|
-
getCenter(e,
|
|
912
|
+
getCenter(e, i) {
|
|
909
913
|
return {
|
|
910
|
-
x: (e.x +
|
|
911
|
-
y: (e.y +
|
|
914
|
+
x: (e.x + i.x) / 2,
|
|
915
|
+
y: (e.y + i.y) / 2
|
|
912
916
|
};
|
|
913
917
|
}
|
|
914
918
|
}
|
|
915
|
-
class F extends
|
|
916
|
-
constructor(e,
|
|
917
|
-
super(), this.interactionState = { mode: "idle" }, this.initialPinchZoom = 1, this.canvas = e, this.engine =
|
|
919
|
+
class F extends E {
|
|
920
|
+
constructor(e, i, t, s) {
|
|
921
|
+
super(), this.interactionState = { mode: "idle" }, this.initialPinchZoom = 1, this.canvas = e, this.engine = i, this.getContents = t, this.getSelectedAreaId = s, this.gestureHandler = new $(), this.boundMouseDown = this.handleMouseDown.bind(this), this.boundMouseMove = this.handleMouseMove.bind(this), this.boundMouseUp = this.handleMouseUp.bind(this), this.boundMouseLeave = this.handleMouseLeave.bind(this), this.boundContextMenu = this.handleContextMenu.bind(this), this.boundDoubleClick = this.handleDoubleClick.bind(this), this.boundTouchStart = this.handleTouchStart.bind(this), this.boundTouchMove = this.handleTouchMove.bind(this), this.boundTouchEnd = this.handleTouchEnd.bind(this), e.addEventListener("mousedown", this.boundMouseDown), e.addEventListener("mousemove", this.boundMouseMove), e.addEventListener("mouseup", this.boundMouseUp), e.addEventListener("mouseleave", this.boundMouseLeave), e.addEventListener("contextmenu", this.boundContextMenu), e.addEventListener("dblclick", this.boundDoubleClick), e.addEventListener("touchstart", this.boundTouchStart, { passive: !1 }), e.addEventListener("touchmove", this.boundTouchMove, { passive: !1 }), e.addEventListener("touchend", this.boundTouchEnd), this.gestureHandler.on("pinch", ({ scale: a }) => {
|
|
918
922
|
const o = Math.max(0.1, Math.min(5, this.initialPinchZoom * a));
|
|
919
923
|
this.emit("zoom", { zoom: o });
|
|
920
924
|
}), this.gestureHandler.on("pan", ({ deltaX: a, deltaY: o }) => {
|
|
921
|
-
const
|
|
925
|
+
const n = this.engine.getPan(), r = this.engine.getZoom();
|
|
922
926
|
this.emit("pan", {
|
|
923
927
|
pan: {
|
|
924
|
-
x:
|
|
925
|
-
y:
|
|
928
|
+
x: n.x + a / r,
|
|
929
|
+
y: n.y + o / r
|
|
926
930
|
}
|
|
927
931
|
});
|
|
928
932
|
});
|
|
929
933
|
}
|
|
930
|
-
getCanvasPosition(e,
|
|
931
|
-
const
|
|
934
|
+
getCanvasPosition(e, i) {
|
|
935
|
+
const t = this.canvas.getBoundingClientRect(), s = window.devicePixelRatio || 1, a = (e - t.left) * s, o = (i - t.top) * s;
|
|
932
936
|
return this.engine.screenToCanvas(a, o);
|
|
933
937
|
}
|
|
934
938
|
handleMouseDown(e) {
|
|
935
|
-
const
|
|
936
|
-
this.processPointerDown(
|
|
939
|
+
const i = this.getCanvasPosition(e.clientX, e.clientY);
|
|
940
|
+
this.processPointerDown(i);
|
|
937
941
|
}
|
|
938
942
|
handleMouseMove(e) {
|
|
939
|
-
const
|
|
940
|
-
this.processPointerMove(
|
|
943
|
+
const i = this.getCanvasPosition(e.clientX, e.clientY);
|
|
944
|
+
this.processPointerMove(i);
|
|
941
945
|
}
|
|
942
946
|
handleMouseUp() {
|
|
943
947
|
this.processPointerUp();
|
|
@@ -949,9 +953,9 @@ class F extends I {
|
|
|
949
953
|
e.preventDefault();
|
|
950
954
|
}
|
|
951
955
|
handleDoubleClick(e) {
|
|
952
|
-
const
|
|
953
|
-
let
|
|
954
|
-
|
|
956
|
+
const i = this.getCanvasPosition(e.clientX, e.clientY);
|
|
957
|
+
let t = this.engine.hitTestContent(i.x, i.y);
|
|
958
|
+
t || (t = this.engine.hitTestArea(i.x, i.y) ?? null), this.emit("context-menu", { areaId: t, clientX: e.clientX, clientY: e.clientY });
|
|
955
959
|
}
|
|
956
960
|
handleTouchStart(e) {
|
|
957
961
|
if (this.gestureHandler.handleTouchStart(e), this.gestureHandler.isMultiTouch) {
|
|
@@ -959,13 +963,13 @@ class F extends I {
|
|
|
959
963
|
return;
|
|
960
964
|
}
|
|
961
965
|
if (e.touches.length !== 1) return;
|
|
962
|
-
const
|
|
963
|
-
this.processPointerDown(
|
|
966
|
+
const i = e.touches[0], t = this.getCanvasPosition(i.clientX, i.clientY);
|
|
967
|
+
this.processPointerDown(t) && e.preventDefault();
|
|
964
968
|
}
|
|
965
969
|
handleTouchMove(e) {
|
|
966
970
|
if (this.gestureHandler.handleTouchMove(e), this.gestureHandler.isMultiTouch || e.touches.length !== 1 || this.interactionState.mode === "idle") return;
|
|
967
|
-
const
|
|
968
|
-
this.processPointerMove(
|
|
971
|
+
const i = e.touches[0], t = this.getCanvasPosition(i.clientX, i.clientY);
|
|
972
|
+
this.processPointerMove(t), e.preventDefault();
|
|
969
973
|
}
|
|
970
974
|
handleTouchEnd(e) {
|
|
971
975
|
this.gestureHandler.handleTouchEnd(e), this.gestureHandler.isMultiTouch || this.processPointerUp();
|
|
@@ -974,38 +978,38 @@ class F extends I {
|
|
|
974
978
|
* Process pointer down. Returns true if an interaction started.
|
|
975
979
|
*/
|
|
976
980
|
processPointerDown(e) {
|
|
977
|
-
const
|
|
978
|
-
if (
|
|
979
|
-
const o = this.engine.hitTestHandle(e.x, e.y,
|
|
981
|
+
const i = this.getSelectedAreaId(), t = this.getContents();
|
|
982
|
+
if (i) {
|
|
983
|
+
const o = this.engine.hitTestHandle(e.x, e.y, i);
|
|
980
984
|
if (o) {
|
|
981
|
-
const
|
|
982
|
-
if (o === "rotate" &&
|
|
985
|
+
const n = t.get(i), r = this.engine.getContentBounds(i);
|
|
986
|
+
if (o === "rotate" && n?.type === "image" && r) {
|
|
983
987
|
const d = Math.atan2(e.y - r.centerY, e.x - r.centerX) * (180 / Math.PI);
|
|
984
988
|
return this.interactionState = {
|
|
985
989
|
mode: "rotating",
|
|
986
|
-
areaId:
|
|
987
|
-
startRotation:
|
|
990
|
+
areaId: i,
|
|
991
|
+
startRotation: n.rotation ?? 0,
|
|
988
992
|
centerPoint: { x: r.centerX, y: r.centerY },
|
|
989
993
|
startAngle: d
|
|
990
994
|
}, !0;
|
|
991
|
-
} else if (o !== "rotate" &&
|
|
995
|
+
} else if (o !== "rotate" && n?.type === "image" && r)
|
|
992
996
|
return this.interactionState = {
|
|
993
997
|
mode: "scaling",
|
|
994
|
-
areaId:
|
|
998
|
+
areaId: i,
|
|
995
999
|
handle: o,
|
|
996
|
-
startScale:
|
|
1000
|
+
startScale: n.scale ?? 1,
|
|
997
1001
|
startMousePos: e,
|
|
998
1002
|
pivotPoint: { x: r.centerX, y: r.centerY }
|
|
999
1003
|
}, !0;
|
|
1000
1004
|
}
|
|
1001
1005
|
}
|
|
1002
|
-
const
|
|
1003
|
-
if (
|
|
1004
|
-
if (this.emit("area:select", { areaId:
|
|
1005
|
-
const o =
|
|
1006
|
+
const s = this.engine.hitTestContent(e.x, e.y);
|
|
1007
|
+
if (s) {
|
|
1008
|
+
if (this.emit("area:select", { areaId: s }), this.engine.isPositioningAllowed(s)) {
|
|
1009
|
+
const o = t.get(s)?.offset ?? { x: 0, y: 0 };
|
|
1006
1010
|
return this.interactionState = {
|
|
1007
1011
|
mode: "dragging",
|
|
1008
|
-
areaId:
|
|
1012
|
+
areaId: s,
|
|
1009
1013
|
startOffset: o,
|
|
1010
1014
|
startMousePos: e
|
|
1011
1015
|
}, !0;
|
|
@@ -1017,42 +1021,40 @@ class F extends I {
|
|
|
1017
1021
|
}
|
|
1018
1022
|
processPointerMove(e) {
|
|
1019
1023
|
if (this.interactionState.mode === "dragging") {
|
|
1020
|
-
const { areaId:
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
};
|
|
1025
|
-
const r = this.engine.getAreaLocation(t);
|
|
1024
|
+
const { areaId: i, startOffset: t, startMousePos: s } = this.interactionState, a = e.x - s.x, o = e.y - s.y, n = {
|
|
1025
|
+
x: t.x + a,
|
|
1026
|
+
y: t.y + o
|
|
1027
|
+
}, r = this.engine.getAreaLocation(i);
|
|
1026
1028
|
if (r) {
|
|
1027
1029
|
const d = r.width / 2 - 10, l = r.height / 2 - 10;
|
|
1028
|
-
|
|
1030
|
+
n.x = Math.max(-d, Math.min(d, n.x)), n.y = Math.max(-l, Math.min(l, n.y));
|
|
1029
1031
|
}
|
|
1030
|
-
this.emit("content:drag", { areaId:
|
|
1032
|
+
this.emit("content:drag", { areaId: i, offset: n });
|
|
1031
1033
|
return;
|
|
1032
1034
|
}
|
|
1033
1035
|
if (this.interactionState.mode === "scaling") {
|
|
1034
|
-
const { areaId:
|
|
1035
|
-
Math.pow(
|
|
1036
|
-
),
|
|
1036
|
+
const { areaId: i, startScale: t, startMousePos: s, pivotPoint: a } = this.interactionState, o = Math.sqrt(
|
|
1037
|
+
Math.pow(s.x - a.x, 2) + Math.pow(s.y - a.y, 2)
|
|
1038
|
+
), n = Math.sqrt(
|
|
1037
1039
|
Math.pow(e.x - a.x, 2) + Math.pow(e.y - a.y, 2)
|
|
1038
1040
|
);
|
|
1039
1041
|
if (o > 0) {
|
|
1040
|
-
const r =
|
|
1041
|
-
this.emit("content:scale", { areaId:
|
|
1042
|
+
const r = n / o, d = Math.max(0.1, Math.min(5, t * r));
|
|
1043
|
+
this.emit("content:scale", { areaId: i, scale: d });
|
|
1042
1044
|
}
|
|
1043
1045
|
return;
|
|
1044
1046
|
}
|
|
1045
1047
|
if (this.interactionState.mode === "rotating") {
|
|
1046
|
-
const { areaId:
|
|
1047
|
-
let
|
|
1048
|
-
|
|
1048
|
+
const { areaId: i, startRotation: t, centerPoint: s, startAngle: a } = this.interactionState, o = Math.atan2(e.y - s.y, e.x - s.x) * (180 / Math.PI);
|
|
1049
|
+
let n = t + (o - a);
|
|
1050
|
+
n = n % 360, n < 0 && (n += 360);
|
|
1049
1051
|
const r = [0, 90, 180, 270, 360];
|
|
1050
1052
|
for (const d of r)
|
|
1051
|
-
if (Math.abs(
|
|
1052
|
-
|
|
1053
|
+
if (Math.abs(n - d) < 5) {
|
|
1054
|
+
n = d === 360 ? 0 : d;
|
|
1053
1055
|
break;
|
|
1054
1056
|
}
|
|
1055
|
-
this.emit("content:rotate", { areaId:
|
|
1057
|
+
this.emit("content:rotate", { areaId: i, rotation: n });
|
|
1056
1058
|
return;
|
|
1057
1059
|
}
|
|
1058
1060
|
this.updateCursor(e);
|
|
@@ -1061,14 +1063,14 @@ class F extends I {
|
|
|
1061
1063
|
this.interactionState.mode !== "idle" && (this.interactionState = { mode: "idle" });
|
|
1062
1064
|
}
|
|
1063
1065
|
updateCursor(e) {
|
|
1064
|
-
const
|
|
1065
|
-
let
|
|
1066
|
-
if (
|
|
1067
|
-
const
|
|
1068
|
-
|
|
1066
|
+
const i = this.getSelectedAreaId();
|
|
1067
|
+
let t = "default";
|
|
1068
|
+
if (i) {
|
|
1069
|
+
const s = this.engine.hitTestHandle(e.x, e.y, i);
|
|
1070
|
+
s ? t = s === "rotate" ? "crosshair" : s === "nw" || s === "se" ? "nwse-resize" : "nesw-resize" : this.engine.hitTestContent(e.x, e.y) === i && (t = this.engine.isPositioningAllowed(i) ? "move" : "pointer");
|
|
1069
1071
|
} else
|
|
1070
|
-
this.engine.hitTestContent(e.x, e.y) && (
|
|
1071
|
-
this.emit("cursor", { cursor:
|
|
1072
|
+
this.engine.hitTestContent(e.x, e.y) && (t = "pointer");
|
|
1073
|
+
this.emit("cursor", { cursor: t });
|
|
1072
1074
|
}
|
|
1073
1075
|
setSelectedArea(e) {
|
|
1074
1076
|
this.engine.setSelectedArea(e);
|
|
@@ -1077,164 +1079,230 @@ class F extends I {
|
|
|
1077
1079
|
this.canvas.removeEventListener("mousedown", this.boundMouseDown), this.canvas.removeEventListener("mousemove", this.boundMouseMove), this.canvas.removeEventListener("mouseup", this.boundMouseUp), this.canvas.removeEventListener("mouseleave", this.boundMouseLeave), this.canvas.removeEventListener("contextmenu", this.boundContextMenu), this.canvas.removeEventListener("dblclick", this.boundDoubleClick), this.canvas.removeEventListener("touchstart", this.boundTouchStart), this.canvas.removeEventListener("touchmove", this.boundTouchMove), this.canvas.removeEventListener("touchend", this.boundTouchEnd), this.gestureHandler.reset(), this.clear();
|
|
1078
1080
|
}
|
|
1079
1081
|
}
|
|
1080
|
-
class
|
|
1082
|
+
class B {
|
|
1081
1083
|
constructor(e = "/api") {
|
|
1082
1084
|
this.baseUrl = e;
|
|
1083
1085
|
}
|
|
1084
1086
|
async getTemplate(e) {
|
|
1085
1087
|
try {
|
|
1086
|
-
const
|
|
1087
|
-
if (!
|
|
1088
|
-
let
|
|
1088
|
+
const i = await fetch(`${this.baseUrl}/templates/${e}`);
|
|
1089
|
+
if (!i.ok) {
|
|
1090
|
+
let t = `Failed to load template (${i.status} ${i.statusText}): ${e}
|
|
1089
1091
|
|
|
1090
1092
|
`;
|
|
1091
|
-
throw
|
|
1093
|
+
throw i.status === 404 ? t += "Template not found." : i.status === 403 ? t += "Access forbidden." : i.status >= 500 && (t += "Server error."), new Error(t);
|
|
1092
1094
|
}
|
|
1093
|
-
return (await
|
|
1094
|
-
} catch (
|
|
1095
|
-
throw
|
|
1095
|
+
return (await i.json()).templateJson;
|
|
1096
|
+
} catch (i) {
|
|
1097
|
+
throw i instanceof Error && i.message.includes("Failed to fetch") ? new Error(`Network error loading template: ${e}`) : i;
|
|
1096
1098
|
}
|
|
1097
1099
|
}
|
|
1098
1100
|
async getProduct(e) {
|
|
1099
1101
|
try {
|
|
1100
|
-
const
|
|
1102
|
+
const i = await fetch(`${this.baseUrl}/products/${e}`);
|
|
1103
|
+
if (!i.ok) {
|
|
1104
|
+
let t = `Failed to load product (${i.status} ${i.statusText}): ${e}
|
|
1105
|
+
|
|
1106
|
+
`;
|
|
1107
|
+
throw i.status === 404 ? t += "Product not found." : i.status === 403 ? t += "Access forbidden." : i.status >= 500 && (t += "Server error."), new Error(t);
|
|
1108
|
+
}
|
|
1109
|
+
return i.json();
|
|
1110
|
+
} catch (i) {
|
|
1111
|
+
throw i instanceof Error && i.message.includes("Failed to fetch") ? new Error(`Network error loading product: ${e}`) : i;
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
async finalizeDesign(e, i) {
|
|
1115
|
+
try {
|
|
1116
|
+
const t = await fetch(`${this.baseUrl}/finalize`, {
|
|
1117
|
+
method: "POST",
|
|
1118
|
+
headers: { "Content-Type": "application/json" },
|
|
1119
|
+
body: JSON.stringify({
|
|
1120
|
+
shopId: e,
|
|
1121
|
+
templateId: i.templateId,
|
|
1122
|
+
designJson: i
|
|
1123
|
+
})
|
|
1124
|
+
});
|
|
1101
1125
|
if (!t.ok) {
|
|
1102
|
-
let
|
|
1126
|
+
let s = `Failed to finalize design (${t.status} ${t.statusText})
|
|
1103
1127
|
|
|
1104
1128
|
`;
|
|
1105
|
-
|
|
1129
|
+
if (t.status === 400) {
|
|
1130
|
+
const a = await t.json().catch(() => ({}));
|
|
1131
|
+
s += a.error || "Invalid design data.";
|
|
1132
|
+
} else t.status === 429 ? s += "Rate limit exceeded." : t.status >= 500 && (s += "Server error.");
|
|
1133
|
+
throw new Error(s);
|
|
1106
1134
|
}
|
|
1107
1135
|
return t.json();
|
|
1108
1136
|
} catch (t) {
|
|
1109
|
-
throw t instanceof Error && t.message.includes("Failed to fetch") ? new Error(
|
|
1137
|
+
throw t instanceof Error && t.message.includes("Failed to fetch") ? new Error("Network error finalizing design") : t;
|
|
1110
1138
|
}
|
|
1111
1139
|
}
|
|
1112
|
-
async
|
|
1140
|
+
async finalizeMultiView(e) {
|
|
1113
1141
|
try {
|
|
1114
|
-
const i =
|
|
1142
|
+
const i = e.designs.map((s) => ({
|
|
1143
|
+
templateId: s.templateId,
|
|
1144
|
+
viewName: s.viewName,
|
|
1145
|
+
areaContents: s.contents.map(([a, o]) => {
|
|
1146
|
+
const n = { areaId: a, isModified: !0 };
|
|
1147
|
+
return o.type === "text" ? n.text = {
|
|
1148
|
+
content: o.text,
|
|
1149
|
+
font: o.font,
|
|
1150
|
+
size: o.size,
|
|
1151
|
+
color: o.color,
|
|
1152
|
+
align: o.align,
|
|
1153
|
+
position: { x: o.offset?.x ?? 0, y: o.offset?.y ?? 0 }
|
|
1154
|
+
} : o.type === "image" && (n.image = {
|
|
1155
|
+
assetId: "inline",
|
|
1156
|
+
assetUrl: "",
|
|
1157
|
+
rotation: o.rotation ?? 0,
|
|
1158
|
+
scale: o.scale ?? 1,
|
|
1159
|
+
position: { x: o.offset?.x ?? 0, y: o.offset?.y ?? 0 },
|
|
1160
|
+
dataUrl: o.dataUrl
|
|
1161
|
+
}), n;
|
|
1162
|
+
})
|
|
1163
|
+
})), t = await fetch(`${this.baseUrl}/finalize/multi`, {
|
|
1115
1164
|
method: "POST",
|
|
1116
1165
|
headers: { "Content-Type": "application/json" },
|
|
1117
1166
|
body: JSON.stringify({
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1167
|
+
productId: e.productId,
|
|
1168
|
+
designs: i,
|
|
1169
|
+
cartContext: e.cartContext,
|
|
1170
|
+
customerId: e.customerId,
|
|
1171
|
+
customerEmail: e.customerEmail,
|
|
1172
|
+
sessionId: e.sessionId
|
|
1121
1173
|
})
|
|
1122
1174
|
});
|
|
1123
|
-
if (!
|
|
1124
|
-
let
|
|
1175
|
+
if (!t.ok) {
|
|
1176
|
+
let s = `Failed to finalize multi-view design (${t.status} ${t.statusText})
|
|
1125
1177
|
|
|
1126
1178
|
`;
|
|
1127
|
-
if (
|
|
1128
|
-
const a = await
|
|
1129
|
-
|
|
1130
|
-
} else
|
|
1131
|
-
throw new Error(
|
|
1179
|
+
if (t.status === 400) {
|
|
1180
|
+
const a = await t.json().catch(() => ({}));
|
|
1181
|
+
s += a.error || "Invalid design data.";
|
|
1182
|
+
} else t.status === 429 ? s += "Rate limit exceeded." : t.status >= 500 && (s += "Server error.");
|
|
1183
|
+
throw new Error(s);
|
|
1132
1184
|
}
|
|
1185
|
+
return t.json();
|
|
1186
|
+
} catch (i) {
|
|
1187
|
+
throw i instanceof Error && i.message.includes("Failed to fetch") ? new Error("Network error finalizing multi-view design") : i;
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
async pollMultiViewStatus(e) {
|
|
1191
|
+
try {
|
|
1192
|
+
const i = await fetch(
|
|
1193
|
+
`${this.baseUrl}/designs/multi-status?designIds=${e.join(",")}`
|
|
1194
|
+
);
|
|
1195
|
+
if (!i.ok)
|
|
1196
|
+
throw new Error(`Failed to check multi-view design status (${i.status})`);
|
|
1133
1197
|
return i.json();
|
|
1134
1198
|
} catch (i) {
|
|
1135
|
-
throw i instanceof Error && i.message.includes("Failed to fetch") ? new Error("Network error
|
|
1199
|
+
throw i instanceof Error && i.message.includes("Failed to fetch") ? new Error("Network error checking multi-view design status") : i;
|
|
1136
1200
|
}
|
|
1137
1201
|
}
|
|
1138
|
-
async uploadAsset(e,
|
|
1202
|
+
async uploadAsset(e, i) {
|
|
1139
1203
|
try {
|
|
1140
|
-
const
|
|
1141
|
-
|
|
1142
|
-
const
|
|
1204
|
+
const t = new FormData();
|
|
1205
|
+
t.append("shopId", i), t.append("file", e);
|
|
1206
|
+
const s = await fetch(`${this.baseUrl}/assets/upload`, {
|
|
1143
1207
|
method: "POST",
|
|
1144
|
-
body:
|
|
1208
|
+
body: t
|
|
1145
1209
|
});
|
|
1146
|
-
if (!
|
|
1147
|
-
let a = `Failed to upload asset (${
|
|
1210
|
+
if (!s.ok) {
|
|
1211
|
+
let a = `Failed to upload asset (${s.status} ${s.statusText})
|
|
1148
1212
|
|
|
1149
1213
|
`;
|
|
1150
|
-
if (
|
|
1151
|
-
const o = await
|
|
1214
|
+
if (s.status === 400) {
|
|
1215
|
+
const o = await s.json().catch(() => ({}));
|
|
1152
1216
|
a += o.error || "Invalid file.";
|
|
1153
|
-
} else
|
|
1217
|
+
} else s.status === 413 ? a += "File too large (max 15MB)." : s.status >= 500 && (a += "Server error.");
|
|
1154
1218
|
throw new Error(a);
|
|
1155
1219
|
}
|
|
1156
|
-
return
|
|
1157
|
-
} catch (
|
|
1158
|
-
throw
|
|
1220
|
+
return s.json();
|
|
1221
|
+
} catch (t) {
|
|
1222
|
+
throw t instanceof Error && t.message.includes("Failed to fetch") ? new Error("Network error uploading asset") : t;
|
|
1159
1223
|
}
|
|
1160
1224
|
}
|
|
1161
1225
|
async getStorageUsage(e) {
|
|
1162
1226
|
try {
|
|
1163
|
-
const
|
|
1164
|
-
if (!
|
|
1165
|
-
throw new Error(`Failed to fetch storage usage (${
|
|
1166
|
-
return
|
|
1167
|
-
} catch (
|
|
1168
|
-
throw
|
|
1227
|
+
const i = await fetch(`${this.baseUrl}/assets/usage?shopId=${encodeURIComponent(e)}`);
|
|
1228
|
+
if (!i.ok)
|
|
1229
|
+
throw new Error(`Failed to fetch storage usage (${i.status})`);
|
|
1230
|
+
return i.json();
|
|
1231
|
+
} catch (i) {
|
|
1232
|
+
throw i instanceof Error && i.message.includes("Failed to fetch") ? new Error("Network error fetching storage usage") : i;
|
|
1169
1233
|
}
|
|
1170
1234
|
}
|
|
1171
1235
|
}
|
|
1172
|
-
function
|
|
1173
|
-
const
|
|
1174
|
-
return e && Object.entries(e).forEach(([
|
|
1175
|
-
|
|
1176
|
-
}),
|
|
1177
|
-
typeof
|
|
1178
|
-
}),
|
|
1236
|
+
function c(v, e, ...i) {
|
|
1237
|
+
const t = document.createElement(v);
|
|
1238
|
+
return e && Object.entries(e).forEach(([s, a]) => {
|
|
1239
|
+
s === "class" ? t.className = a : s in t ? t[s] = a : t.setAttribute(s, String(a));
|
|
1240
|
+
}), i.forEach((s) => {
|
|
1241
|
+
typeof s == "string" ? t.appendChild(document.createTextNode(s)) : t.appendChild(s);
|
|
1242
|
+
}), t;
|
|
1179
1243
|
}
|
|
1180
|
-
function
|
|
1181
|
-
let
|
|
1182
|
-
const
|
|
1183
|
-
|
|
1184
|
-
|
|
1244
|
+
function q(v, e) {
|
|
1245
|
+
let i = null, t = null;
|
|
1246
|
+
const s = (...a) => {
|
|
1247
|
+
t = a, i && clearTimeout(i), i = setTimeout(() => {
|
|
1248
|
+
t && v(...t), i = null, t = null;
|
|
1185
1249
|
}, e);
|
|
1186
1250
|
};
|
|
1187
|
-
return
|
|
1188
|
-
|
|
1189
|
-
},
|
|
1190
|
-
|
|
1191
|
-
},
|
|
1251
|
+
return s.flush = () => {
|
|
1252
|
+
i && (clearTimeout(i), i = null), t && (v(...t), t = null);
|
|
1253
|
+
}, s.cancel = () => {
|
|
1254
|
+
i && (clearTimeout(i), i = null), t = null;
|
|
1255
|
+
}, s;
|
|
1192
1256
|
}
|
|
1193
|
-
class
|
|
1257
|
+
class H extends E {
|
|
1194
1258
|
constructor() {
|
|
1195
|
-
super(), this.percentLabel =
|
|
1196
|
-
const e =
|
|
1259
|
+
super(), this.saveEnabled = !0, this.percentLabel = c("span", { class: "zoom-percent" }, "100%");
|
|
1260
|
+
const e = c("button", {
|
|
1197
1261
|
class: "zoom-btn",
|
|
1198
1262
|
title: "Zoom out"
|
|
1199
1263
|
}, "−");
|
|
1200
1264
|
e.addEventListener("click", () => this.emit("zoom-out", void 0));
|
|
1201
|
-
const
|
|
1265
|
+
const i = c("button", {
|
|
1202
1266
|
class: "zoom-btn",
|
|
1203
1267
|
title: "Fit to view"
|
|
1204
1268
|
}, "Fit");
|
|
1205
|
-
|
|
1206
|
-
const
|
|
1269
|
+
i.addEventListener("click", () => this.emit("zoom-fit", void 0));
|
|
1270
|
+
const t = c("button", {
|
|
1207
1271
|
class: "zoom-btn",
|
|
1208
1272
|
title: "Zoom in"
|
|
1209
1273
|
}, "+");
|
|
1210
|
-
|
|
1274
|
+
t.addEventListener("click", () => this.emit("zoom-in", void 0)), this.viewSelect = document.createElement("select"), this.viewSelect.className = "view-select", this.viewSelect.title = "Switch view", this.viewSelect.style.display = "none", this.viewSelect.addEventListener("change", () => {
|
|
1211
1275
|
this.emit("view-change", { viewName: this.viewSelect.value });
|
|
1212
|
-
}), this.element =
|
|
1213
|
-
const
|
|
1214
|
-
this.element.appendChild(
|
|
1215
|
-
const a = h("button", {
|
|
1216
|
-
class: "zoom-btn zoom-close-btn",
|
|
1217
|
-
title: "Close editor"
|
|
1218
|
-
}, "✕ Close");
|
|
1219
|
-
a.addEventListener("click", () => this.emit("close", void 0)), this.element.appendChild(a), this.saveBtn = h("button", {
|
|
1276
|
+
}), this.element = c("div", { class: "zoom-toolbar" }), this.element.appendChild(e), this.element.appendChild(i), this.element.appendChild(t), this.element.appendChild(this.percentLabel), this.element.appendChild(this.viewSelect);
|
|
1277
|
+
const s = c("div", { class: "zoom-toolbar-spacer" });
|
|
1278
|
+
this.element.appendChild(s), this.saveBtn = c("button", {
|
|
1220
1279
|
class: "zoom-btn zoom-save-btn",
|
|
1221
1280
|
title: "Save customization"
|
|
1222
|
-
}, "Save
|
|
1281
|
+
}, "Save"), this.saveBtn.style.display = "none", this.saveBtn.addEventListener("click", () => this.emit("save", void 0)), this.element.appendChild(this.saveBtn), this.closeBtn = c("button", {
|
|
1282
|
+
class: "zoom-btn zoom-close-btn",
|
|
1283
|
+
title: "Close editor"
|
|
1284
|
+
}, "✕ Close"), this.closeBtn.addEventListener("click", () => this.emit("close", void 0)), this.element.appendChild(this.closeBtn);
|
|
1223
1285
|
}
|
|
1224
1286
|
setZoom(e) {
|
|
1225
1287
|
this.percentLabel.textContent = `${Math.round(e * 100)}%`;
|
|
1226
1288
|
}
|
|
1227
1289
|
showSaveButton(e) {
|
|
1228
|
-
this.saveBtn.style.display = e ? "" : "none";
|
|
1290
|
+
this.saveEnabled && (this.saveBtn.style.display = e ? "" : "none");
|
|
1291
|
+
}
|
|
1292
|
+
setCloseButtonVisible(e) {
|
|
1293
|
+
this.closeBtn.style.display = e ? "" : "none";
|
|
1229
1294
|
}
|
|
1230
|
-
|
|
1231
|
-
this.
|
|
1295
|
+
setSaveButtonEnabled(e) {
|
|
1296
|
+
this.saveEnabled = e, e || (this.saveBtn.style.display = "none");
|
|
1297
|
+
}
|
|
1298
|
+
setSaveDisabled(e, i) {
|
|
1299
|
+
this.saveBtn.disabled = e, i && (this.saveBtn.textContent = i);
|
|
1232
1300
|
}
|
|
1233
1301
|
setViews(e) {
|
|
1234
1302
|
this.viewSelect.innerHTML = "";
|
|
1235
|
-
for (const
|
|
1236
|
-
const
|
|
1237
|
-
|
|
1303
|
+
for (const i of e) {
|
|
1304
|
+
const t = document.createElement("option");
|
|
1305
|
+
t.value = i.viewName, t.textContent = i.viewName, this.viewSelect.appendChild(t);
|
|
1238
1306
|
}
|
|
1239
1307
|
this.viewSelect.style.display = e.length > 1 ? "" : "none";
|
|
1240
1308
|
}
|
|
@@ -1251,243 +1319,243 @@ const Z = [
|
|
|
1251
1319
|
{ label: "Georgia", value: "Georgia" },
|
|
1252
1320
|
{ label: "Times New Roman", value: "Times New Roman" },
|
|
1253
1321
|
{ label: "Courier New", value: "Courier New" }
|
|
1254
|
-
],
|
|
1322
|
+
], j = [
|
|
1255
1323
|
{ label: "L", value: "left" },
|
|
1256
1324
|
{ label: "C", value: "center" },
|
|
1257
1325
|
{ label: "R", value: "right" }
|
|
1258
|
-
],
|
|
1259
|
-
class
|
|
1326
|
+
], L = ["image/png", "image/jpeg", "image/webp", "image/svg+xml"], Y = 15 * 1024 * 1024;
|
|
1327
|
+
class A extends E {
|
|
1260
1328
|
constructor(e) {
|
|
1261
1329
|
super(), this.mode = "text", this.isSelected = !1, this.textInputEl = null, this.fontSelectEl = null, this.sizeInputEl = null, this.colorInputEl = null, this.alignGroupEl = null, this.area = e;
|
|
1262
|
-
const
|
|
1263
|
-
this.mode =
|
|
1330
|
+
const i = z(e), t = T(e);
|
|
1331
|
+
this.mode = t && !i ? "image" : "text", this.element = c("div", { class: "area-card" }), this.element.addEventListener("click", (r) => {
|
|
1264
1332
|
r.target.tagName === "INPUT" || r.target.tagName === "SELECT" || r.target.tagName === "BUTTON" || this.emit("select", { areaId: e.id });
|
|
1265
1333
|
});
|
|
1266
|
-
const
|
|
1334
|
+
const s = c("div", { class: "area-card-header" }), a = c("div", { class: "area-card-name-row" }), o = c("span", { class: "area-card-name" }, e.name);
|
|
1267
1335
|
if (a.appendChild(o), e.required) {
|
|
1268
|
-
const r =
|
|
1336
|
+
const r = c("span", { class: "area-card-badge" }, "Required");
|
|
1269
1337
|
a.appendChild(r);
|
|
1270
1338
|
}
|
|
1271
|
-
|
|
1272
|
-
const
|
|
1339
|
+
s.appendChild(a);
|
|
1340
|
+
const n = c("button", {
|
|
1273
1341
|
class: "area-card-reset-btn",
|
|
1274
1342
|
title: "Reset content"
|
|
1275
1343
|
}, "Reset");
|
|
1276
|
-
if (
|
|
1344
|
+
if (n.addEventListener("click", (r) => {
|
|
1277
1345
|
r.stopPropagation(), this.emit("clear", { areaId: e.id });
|
|
1278
|
-
}),
|
|
1279
|
-
const r =
|
|
1346
|
+
}), s.appendChild(n), this.element.appendChild(s), i && t) {
|
|
1347
|
+
const r = c("div", { class: "area-card-mode-switcher" }), d = c("button", {
|
|
1280
1348
|
class: "mode-btn mode-btn-active"
|
|
1281
1349
|
}, "Text");
|
|
1282
1350
|
d.dataset.mode = "text";
|
|
1283
|
-
const l =
|
|
1284
|
-
l.dataset.mode = "image", d.addEventListener("click", (
|
|
1285
|
-
|
|
1286
|
-
}), l.addEventListener("click", (
|
|
1287
|
-
|
|
1351
|
+
const l = c("button", { class: "mode-btn" }, "Image");
|
|
1352
|
+
l.dataset.mode = "image", d.addEventListener("click", (h) => {
|
|
1353
|
+
h.stopPropagation(), this.setMode("text"), d.classList.add("mode-btn-active"), l.classList.remove("mode-btn-active"), this.currentContent?.type === "image" && this.emit("clear", { areaId: e.id });
|
|
1354
|
+
}), l.addEventListener("click", (h) => {
|
|
1355
|
+
h.stopPropagation(), this.setMode("image"), l.classList.add("mode-btn-active"), d.classList.remove("mode-btn-active"), this.currentContent?.type === "text" && this.emit("clear", { areaId: e.id });
|
|
1288
1356
|
}), r.appendChild(d), r.appendChild(l), this.element.appendChild(r);
|
|
1289
1357
|
}
|
|
1290
|
-
this.contentContainer =
|
|
1358
|
+
this.contentContainer = c("div", { class: "area-card-content" }), this.element.appendChild(this.contentContainer), this.transformContainer = c("div", { class: "area-card-transforms" }), this.element.appendChild(this.transformContainer), this.renderContent();
|
|
1291
1359
|
}
|
|
1292
1360
|
setMode(e) {
|
|
1293
1361
|
this.mode = e, this.renderContent();
|
|
1294
1362
|
}
|
|
1295
1363
|
setContent(e) {
|
|
1296
|
-
const
|
|
1297
|
-
this.currentContent = e, e?.type === "image" && (this.mode = "image"), this.element.querySelectorAll(".mode-btn").forEach((
|
|
1298
|
-
const
|
|
1299
|
-
|
|
1300
|
-
}), e?.type !==
|
|
1364
|
+
const i = this.currentContent?.type;
|
|
1365
|
+
this.currentContent = e, e?.type === "image" && (this.mode = "image"), this.element.querySelectorAll(".mode-btn").forEach((t) => {
|
|
1366
|
+
const s = t;
|
|
1367
|
+
s.classList.toggle("mode-btn-active", s.dataset.mode === this.mode);
|
|
1368
|
+
}), e?.type !== i || e?.type === "image" || e === void 0 ? this.renderContent() : e?.type === "text" && this.updateTextValues(e), this.renderTransforms();
|
|
1301
1369
|
}
|
|
1302
1370
|
setSelected(e) {
|
|
1303
1371
|
this.isSelected = e, this.element.classList.toggle("area-card-selected", e), this.renderTransforms();
|
|
1304
1372
|
}
|
|
1305
1373
|
updateTextValues(e) {
|
|
1306
|
-
this.textInputEl && this.textInputEl.value !== e.text && (this.textInputEl.value = e.text), this.fontSelectEl && this.fontSelectEl.value !== e.font && (this.fontSelectEl.value = e.font), this.sizeInputEl && this.sizeInputEl.value !== String(e.size) && (this.sizeInputEl.value = String(e.size)), this.colorInputEl && this.colorInputEl.value !== e.color && (this.colorInputEl.value = e.color), this.alignGroupEl && this.alignGroupEl.querySelectorAll(".align-btn").forEach((
|
|
1307
|
-
const
|
|
1308
|
-
|
|
1374
|
+
this.textInputEl && this.textInputEl.value !== e.text && (this.textInputEl.value = e.text), this.fontSelectEl && this.fontSelectEl.value !== e.font && (this.fontSelectEl.value = e.font), this.sizeInputEl && this.sizeInputEl.value !== String(e.size) && (this.sizeInputEl.value = String(e.size)), this.colorInputEl && this.colorInputEl.value !== e.color && (this.colorInputEl.value = e.color), this.alignGroupEl && this.alignGroupEl.querySelectorAll(".align-btn").forEach((i) => {
|
|
1375
|
+
const t = i, s = t.title === "Left" ? "left" : t.title === "Center" ? "center" : "right";
|
|
1376
|
+
t.classList.toggle("align-btn-active", s === e.align);
|
|
1309
1377
|
});
|
|
1310
1378
|
}
|
|
1311
1379
|
renderContent() {
|
|
1312
1380
|
this.contentContainer.innerHTML = "", this.textInputEl = null, this.fontSelectEl = null, this.sizeInputEl = null, this.colorInputEl = null, this.alignGroupEl = null;
|
|
1313
|
-
const e =
|
|
1314
|
-
e &&
|
|
1381
|
+
const e = z(this.area), i = T(this.area), t = !i || e && i && this.mode === "text", s = !e || e && i && this.mode === "image";
|
|
1382
|
+
e && t && this.renderTextControls(), i && s && this.renderImageControls();
|
|
1315
1383
|
}
|
|
1316
1384
|
renderTextControls() {
|
|
1317
|
-
const e = this.currentContent,
|
|
1385
|
+
const e = this.currentContent, i = e?.type === "text" ? e : S(this.area), t = c("input", {
|
|
1318
1386
|
class: "area-input-text",
|
|
1319
1387
|
type: "text",
|
|
1320
1388
|
placeholder: this.area.placeholder || "Enter text..."
|
|
1321
1389
|
});
|
|
1322
|
-
|
|
1323
|
-
this.emit("text:change", { areaId: this.area.id, updates: { text:
|
|
1324
|
-
}), this.contentContainer.appendChild(
|
|
1325
|
-
const
|
|
1390
|
+
t.value = i.text, this.area.textOptions?.maxLength && (t.maxLength = this.area.textOptions.maxLength), t.addEventListener("input", () => {
|
|
1391
|
+
this.emit("text:change", { areaId: this.area.id, updates: { text: t.value } });
|
|
1392
|
+
}), this.contentContainer.appendChild(t), this.textInputEl = t;
|
|
1393
|
+
const s = c("div", { class: "area-input-row" }), a = c("select", { class: "area-input-font" });
|
|
1326
1394
|
for (const l of Z) {
|
|
1327
|
-
const
|
|
1328
|
-
|
|
1395
|
+
const h = c("option");
|
|
1396
|
+
h.value = l.value, h.textContent = l.label, l.value === i.font && (h.selected = !0), a.appendChild(h);
|
|
1329
1397
|
}
|
|
1330
1398
|
a.addEventListener("change", () => {
|
|
1331
1399
|
this.emit("text:change", { areaId: this.area.id, updates: { font: a.value } });
|
|
1332
|
-
}),
|
|
1333
|
-
const o =
|
|
1400
|
+
}), s.appendChild(a), this.fontSelectEl = a;
|
|
1401
|
+
const o = c("input", {
|
|
1334
1402
|
class: "area-input-size",
|
|
1335
1403
|
type: "number"
|
|
1336
1404
|
});
|
|
1337
|
-
o.value = String(
|
|
1405
|
+
o.value = String(i.size), o.min = String(this.area.textOptions?.minSize || 8), o.max = String(this.area.textOptions?.maxSize || 200), o.addEventListener("change", () => {
|
|
1338
1406
|
this.emit("text:change", {
|
|
1339
1407
|
areaId: this.area.id,
|
|
1340
1408
|
updates: { size: parseInt(o.value, 10) || 24 }
|
|
1341
1409
|
});
|
|
1342
|
-
}),
|
|
1343
|
-
const
|
|
1344
|
-
for (const l of
|
|
1345
|
-
const
|
|
1346
|
-
class: `align-btn${
|
|
1410
|
+
}), s.appendChild(o), this.sizeInputEl = o, this.contentContainer.appendChild(s);
|
|
1411
|
+
const n = c("div", { class: "area-input-row" }), r = c("div", { class: "area-input-align" });
|
|
1412
|
+
for (const l of j) {
|
|
1413
|
+
const h = c("button", {
|
|
1414
|
+
class: `align-btn${i.align === l.value ? " align-btn-active" : ""}`,
|
|
1347
1415
|
title: l.label === "L" ? "Left" : l.label === "C" ? "Center" : "Right"
|
|
1348
1416
|
}, l.label);
|
|
1349
|
-
|
|
1350
|
-
u.stopPropagation(), this.emit("text:change", { areaId: this.area.id, updates: { align: l.value } }), r.querySelectorAll(".align-btn").forEach((p) => p.classList.remove("align-btn-active")),
|
|
1351
|
-
}), r.appendChild(
|
|
1417
|
+
h.addEventListener("click", (u) => {
|
|
1418
|
+
u.stopPropagation(), this.emit("text:change", { areaId: this.area.id, updates: { align: l.value } }), r.querySelectorAll(".align-btn").forEach((p) => p.classList.remove("align-btn-active")), h.classList.add("align-btn-active");
|
|
1419
|
+
}), r.appendChild(h);
|
|
1352
1420
|
}
|
|
1353
|
-
|
|
1354
|
-
const d =
|
|
1421
|
+
n.appendChild(r), this.alignGroupEl = r;
|
|
1422
|
+
const d = c("input", {
|
|
1355
1423
|
class: "area-input-color",
|
|
1356
1424
|
type: "color"
|
|
1357
1425
|
});
|
|
1358
|
-
d.value =
|
|
1426
|
+
d.value = i.color, d.addEventListener("input", () => {
|
|
1359
1427
|
this.emit("text:change", { areaId: this.area.id, updates: { color: d.value } });
|
|
1360
|
-
}),
|
|
1428
|
+
}), n.appendChild(d), this.colorInputEl = d, this.contentContainer.appendChild(n);
|
|
1361
1429
|
}
|
|
1362
1430
|
renderImageControls() {
|
|
1363
1431
|
const e = this.currentContent;
|
|
1364
1432
|
if (e?.type === "image") {
|
|
1365
|
-
const
|
|
1366
|
-
|
|
1367
|
-
const
|
|
1368
|
-
|
|
1369
|
-
const a =
|
|
1433
|
+
const i = c("div", { class: "area-image-preview" }), t = c("img", { class: "area-image-thumb" });
|
|
1434
|
+
t.src = e.dataUrl, t.alt = e.filename || "Uploaded image", i.appendChild(t);
|
|
1435
|
+
const s = c("div", { class: "area-image-info" });
|
|
1436
|
+
s.appendChild(c("span", { class: "area-image-name" }, e.filename || "Image"));
|
|
1437
|
+
const a = c("button", { class: "area-image-remove-btn" }, "Remove");
|
|
1370
1438
|
a.addEventListener("click", (o) => {
|
|
1371
1439
|
o.stopPropagation(), this.emit("clear", { areaId: this.area.id });
|
|
1372
|
-
}),
|
|
1440
|
+
}), s.appendChild(a), i.appendChild(s), this.contentContainer.appendChild(i);
|
|
1373
1441
|
} else {
|
|
1374
|
-
const
|
|
1375
|
-
|
|
1376
|
-
const
|
|
1377
|
-
|
|
1378
|
-
const a =
|
|
1442
|
+
const i = c("button", { class: "area-upload-btn" }, "Upload Image"), t = c("input", { type: "file" });
|
|
1443
|
+
t.accept = L.join(","), t.style.display = "none";
|
|
1444
|
+
const s = c("div", { class: "area-validation-error" });
|
|
1445
|
+
s.style.display = "none", t.addEventListener("change", () => {
|
|
1446
|
+
const a = t.files?.[0];
|
|
1379
1447
|
if (!a) return;
|
|
1380
|
-
if (!
|
|
1381
|
-
const
|
|
1382
|
-
|
|
1448
|
+
if (!L.includes(a.type)) {
|
|
1449
|
+
const n = "Only PNG, JPEG, WebP, and SVG images are accepted";
|
|
1450
|
+
s.textContent = n, s.style.display = "block", this.emit("validation:error", { areaId: this.area.id, message: n }), t.value = "";
|
|
1383
1451
|
return;
|
|
1384
1452
|
}
|
|
1385
1453
|
if (a.size > Y) {
|
|
1386
|
-
const
|
|
1387
|
-
|
|
1454
|
+
const n = "File must be smaller than 15MB";
|
|
1455
|
+
s.textContent = n, s.style.display = "block", this.emit("validation:error", { areaId: this.area.id, message: n }), t.value = "";
|
|
1388
1456
|
return;
|
|
1389
1457
|
}
|
|
1390
|
-
|
|
1458
|
+
s.style.display = "none";
|
|
1391
1459
|
const o = new FileReader();
|
|
1392
1460
|
o.onload = () => {
|
|
1393
|
-
const
|
|
1394
|
-
this.emit("image:change", { areaId: this.area.id, dataUrl:
|
|
1395
|
-
}, o.readAsDataURL(a),
|
|
1396
|
-
}),
|
|
1397
|
-
a.stopPropagation(),
|
|
1398
|
-
}), this.contentContainer.appendChild(
|
|
1461
|
+
const n = o.result;
|
|
1462
|
+
this.emit("image:change", { areaId: this.area.id, dataUrl: n, filename: a.name });
|
|
1463
|
+
}, o.readAsDataURL(a), t.value = "";
|
|
1464
|
+
}), i.addEventListener("click", (a) => {
|
|
1465
|
+
a.stopPropagation(), t.click();
|
|
1466
|
+
}), this.contentContainer.appendChild(i), this.contentContainer.appendChild(t), this.contentContainer.appendChild(s);
|
|
1399
1467
|
}
|
|
1400
1468
|
}
|
|
1401
1469
|
renderTransforms() {
|
|
1402
1470
|
this.transformContainer.innerHTML = "";
|
|
1403
1471
|
const e = this.currentContent;
|
|
1404
1472
|
if (!this.isSelected || !e) return;
|
|
1405
|
-
const
|
|
1406
|
-
if (!
|
|
1407
|
-
const a =
|
|
1473
|
+
const i = e.type === "text" ? this.area.textOptions?.allowPositioning : this.area.imageOptions?.allowPositioning, t = e.type === "image" && this.area.imageOptions?.allowScaling, s = e.type === "image" && this.area.imageOptions?.allowRotation;
|
|
1474
|
+
if (!i && !t && !s) return;
|
|
1475
|
+
const a = c("div", { class: "area-card-divider" });
|
|
1408
1476
|
this.transformContainer.appendChild(a);
|
|
1409
|
-
const o =
|
|
1410
|
-
o.appendChild(
|
|
1411
|
-
const
|
|
1412
|
-
|
|
1413
|
-
d.stopPropagation(),
|
|
1414
|
-
}), o.appendChild(
|
|
1477
|
+
const o = c("div", { class: "transform-header" });
|
|
1478
|
+
o.appendChild(c("span", { class: "transform-title" }, "Transform"));
|
|
1479
|
+
const n = c("button", { class: "transform-reset-btn" }, "Reset");
|
|
1480
|
+
n.addEventListener("click", (d) => {
|
|
1481
|
+
d.stopPropagation(), i && this.emit("offset:change", { areaId: this.area.id, offset: { x: 0, y: 0 } }), t && this.emit("scale:change", { areaId: this.area.id, scale: 1 }), s && this.emit("rotation:change", { areaId: this.area.id, rotation: 0 });
|
|
1482
|
+
}), o.appendChild(n), this.transformContainer.appendChild(o);
|
|
1415
1483
|
const r = e.offset ?? { x: 0, y: 0 };
|
|
1416
|
-
if (
|
|
1417
|
-
const d =
|
|
1418
|
-
|
|
1484
|
+
if (i) {
|
|
1485
|
+
const d = c("div", { class: "area-input-row" }), l = c("label", { class: "transform-label" }, "X"), h = c("input", { class: "transform-input", type: "number" });
|
|
1486
|
+
h.value = String(Math.round(r.x)), h.addEventListener("change", () => {
|
|
1419
1487
|
this.emit("offset:change", {
|
|
1420
1488
|
areaId: this.area.id,
|
|
1421
|
-
offset: { ...r, x: parseInt(
|
|
1489
|
+
offset: { ...r, x: parseInt(h.value, 10) || 0 }
|
|
1422
1490
|
});
|
|
1423
1491
|
});
|
|
1424
|
-
const u =
|
|
1492
|
+
const u = c("label", { class: "transform-label" }, "Y"), p = c("input", { class: "transform-input", type: "number" });
|
|
1425
1493
|
p.value = String(Math.round(r.y)), p.addEventListener("change", () => {
|
|
1426
1494
|
this.emit("offset:change", {
|
|
1427
1495
|
areaId: this.area.id,
|
|
1428
1496
|
offset: { ...r, y: parseInt(p.value, 10) || 0 }
|
|
1429
1497
|
});
|
|
1430
|
-
}), d.appendChild(l), d.appendChild(
|
|
1498
|
+
}), d.appendChild(l), d.appendChild(h), d.appendChild(u), d.appendChild(p), this.transformContainer.appendChild(d);
|
|
1431
1499
|
}
|
|
1432
|
-
if (
|
|
1433
|
-
const d = e.scale ?? 1, l =
|
|
1500
|
+
if (t && e.type === "image") {
|
|
1501
|
+
const d = e.scale ?? 1, l = c("div", { class: "area-input-row transform-slider-row" }), h = c("label", { class: "transform-label" }, `Scale: ${Math.round(d * 100)}%`), u = c("input", { class: "transform-slider", type: "range" });
|
|
1434
1502
|
u.min = "10", u.max = "500", u.step = "5", u.value = String(Math.round(d * 100)), u.addEventListener("input", () => {
|
|
1435
1503
|
const p = parseInt(u.value, 10) / 100;
|
|
1436
|
-
|
|
1437
|
-
}), l.appendChild(
|
|
1504
|
+
h.textContent = `Scale: ${Math.round(p * 100)}%`, this.emit("scale:change", { areaId: this.area.id, scale: p });
|
|
1505
|
+
}), l.appendChild(h), l.appendChild(u), this.transformContainer.appendChild(l);
|
|
1438
1506
|
}
|
|
1439
|
-
if (
|
|
1440
|
-
const d = e.rotation ?? 0, l =
|
|
1507
|
+
if (s && e.type === "image") {
|
|
1508
|
+
const d = e.rotation ?? 0, l = c("div", { class: "area-input-row transform-slider-row" }), h = c("label", { class: "transform-label" }, `Rotation: ${Math.round(d)}°`), u = c("input", { class: "transform-slider", type: "range" });
|
|
1441
1509
|
u.min = "0", u.max = "360", u.step = "1", u.value = String(Math.round(d)), u.addEventListener("input", () => {
|
|
1442
1510
|
const p = parseInt(u.value, 10);
|
|
1443
|
-
|
|
1444
|
-
}), l.appendChild(
|
|
1511
|
+
h.textContent = `Rotation: ${p}°`, this.emit("rotation:change", { areaId: this.area.id, rotation: p });
|
|
1512
|
+
}), l.appendChild(h), l.appendChild(u), this.transformContainer.appendChild(l);
|
|
1445
1513
|
}
|
|
1446
1514
|
}
|
|
1447
1515
|
getElement() {
|
|
1448
1516
|
return this.element;
|
|
1449
1517
|
}
|
|
1450
1518
|
}
|
|
1451
|
-
class
|
|
1519
|
+
class J extends E {
|
|
1452
1520
|
constructor(e) {
|
|
1453
|
-
super(), this.cards = /* @__PURE__ */ new Map(), this.isMobile = !1, this.boundEscapeHandler = (
|
|
1454
|
-
|
|
1455
|
-
}, this.backdrop =
|
|
1521
|
+
super(), this.cards = /* @__PURE__ */ new Map(), this.isMobile = !1, this.boundEscapeHandler = (s) => {
|
|
1522
|
+
s.key === "Escape" && this.emit("dismiss", void 0);
|
|
1523
|
+
}, this.backdrop = c("div", { class: "area-panel-backdrop" }), this.backdrop.addEventListener("click", () => {
|
|
1456
1524
|
this.emit("dismiss", void 0);
|
|
1457
|
-
}), this.element =
|
|
1458
|
-
const
|
|
1459
|
-
|
|
1460
|
-
const
|
|
1461
|
-
if (
|
|
1525
|
+
}), this.element = c("div", { class: "area-panel" });
|
|
1526
|
+
const i = c("div", { class: "area-panel-header" });
|
|
1527
|
+
i.appendChild(c("span", { class: "area-panel-title" }, "Customize"));
|
|
1528
|
+
const t = c("button", { class: "area-panel-close-btn", title: "Close" }, "×");
|
|
1529
|
+
if (t.addEventListener("click", () => {
|
|
1462
1530
|
this.emit("dismiss", void 0);
|
|
1463
|
-
}),
|
|
1464
|
-
const
|
|
1465
|
-
this.panelContent.appendChild(
|
|
1531
|
+
}), i.appendChild(t), this.element.appendChild(i), this.panelContent = c("div", { class: "area-panel-content" }), e.length === 0) {
|
|
1532
|
+
const s = c("p", { class: "area-panel-empty" }, "No customization areas defined.");
|
|
1533
|
+
this.panelContent.appendChild(s);
|
|
1466
1534
|
} else
|
|
1467
|
-
for (const
|
|
1468
|
-
const a = new
|
|
1469
|
-
this.cards.set(
|
|
1535
|
+
for (const s of e) {
|
|
1536
|
+
const a = new A(s);
|
|
1537
|
+
this.cards.set(s.id, a), a.on("text:change", (o) => this.emit("text:change", o)), a.on("image:change", (o) => this.emit("image:change", o)), a.on("clear", (o) => this.emit("clear", o)), a.on("select", (o) => this.emit("select", o)), a.on("offset:change", (o) => this.emit("offset:change", o)), a.on("scale:change", (o) => this.emit("scale:change", o)), a.on("rotation:change", (o) => this.emit("rotation:change", o)), a.on("validation:error", (o) => this.emit("validation:error", o)), this.panelContent.appendChild(a.getElement());
|
|
1470
1538
|
}
|
|
1471
1539
|
this.element.appendChild(this.panelContent);
|
|
1472
1540
|
}
|
|
1473
1541
|
setAreas(e) {
|
|
1474
1542
|
if (this.cards.clear(), this.panelContent.innerHTML = "", e.length === 0) {
|
|
1475
|
-
const
|
|
1476
|
-
this.panelContent.appendChild(
|
|
1543
|
+
const i = c("p", { class: "area-panel-empty" }, "No customization areas defined.");
|
|
1544
|
+
this.panelContent.appendChild(i);
|
|
1477
1545
|
} else
|
|
1478
|
-
for (const
|
|
1479
|
-
const
|
|
1480
|
-
this.cards.set(
|
|
1546
|
+
for (const i of e) {
|
|
1547
|
+
const t = new A(i);
|
|
1548
|
+
this.cards.set(i.id, t), t.on("text:change", (s) => this.emit("text:change", s)), t.on("image:change", (s) => this.emit("image:change", s)), t.on("clear", (s) => this.emit("clear", s)), t.on("select", (s) => this.emit("select", s)), t.on("offset:change", (s) => this.emit("offset:change", s)), t.on("scale:change", (s) => this.emit("scale:change", s)), t.on("rotation:change", (s) => this.emit("rotation:change", s)), t.on("validation:error", (s) => this.emit("validation:error", s)), this.panelContent.appendChild(t.getElement());
|
|
1481
1549
|
}
|
|
1482
1550
|
}
|
|
1483
1551
|
setContents(e) {
|
|
1484
|
-
for (const [
|
|
1485
|
-
|
|
1552
|
+
for (const [i, t] of this.cards)
|
|
1553
|
+
t.setContent(e.get(i));
|
|
1486
1554
|
}
|
|
1487
1555
|
setSelectedArea(e) {
|
|
1488
|
-
for (const [
|
|
1489
|
-
const
|
|
1490
|
-
|
|
1556
|
+
for (const [i, t] of this.cards) {
|
|
1557
|
+
const s = i === e;
|
|
1558
|
+
t.setSelected(s), t.getElement().style.display = s ? "" : "none";
|
|
1491
1559
|
}
|
|
1492
1560
|
e ? (this.element.classList.add("area-panel-visible"), this.isMobile && this.backdrop.classList.add("area-panel-backdrop-visible"), document.addEventListener("keydown", this.boundEscapeHandler)) : (this.element.classList.remove("area-panel-visible"), this.isMobile && this.backdrop.classList.remove("area-panel-backdrop-visible"), document.removeEventListener("keydown", this.boundEscapeHandler));
|
|
1493
1561
|
}
|
|
@@ -1503,13 +1571,13 @@ class j extends I {
|
|
|
1503
1571
|
}
|
|
1504
1572
|
class X {
|
|
1505
1573
|
constructor(e = window) {
|
|
1506
|
-
this.target = e, this.commands = [], this.isEnabled = !0, this.handleKeyDown = (
|
|
1574
|
+
this.target = e, this.commands = [], this.isEnabled = !0, this.handleKeyDown = (i) => {
|
|
1507
1575
|
if (!this.isEnabled) return;
|
|
1508
|
-
const
|
|
1509
|
-
if (!(
|
|
1510
|
-
for (const
|
|
1511
|
-
if (this.matchesCommand(
|
|
1512
|
-
|
|
1576
|
+
const t = i.target;
|
|
1577
|
+
if (!(t.tagName === "INPUT" || t.tagName === "TEXTAREA" || t.isContentEditable)) {
|
|
1578
|
+
for (const s of this.commands)
|
|
1579
|
+
if (this.matchesCommand(i, s)) {
|
|
1580
|
+
i.preventDefault(), s.handler(i);
|
|
1513
1581
|
break;
|
|
1514
1582
|
}
|
|
1515
1583
|
}
|
|
@@ -1520,8 +1588,8 @@ class X {
|
|
|
1520
1588
|
*/
|
|
1521
1589
|
register(e) {
|
|
1522
1590
|
return this.commands.push(e), () => {
|
|
1523
|
-
const
|
|
1524
|
-
|
|
1591
|
+
const i = this.commands.indexOf(e);
|
|
1592
|
+
i !== -1 && this.commands.splice(i, 1);
|
|
1525
1593
|
};
|
|
1526
1594
|
}
|
|
1527
1595
|
/**
|
|
@@ -1545,8 +1613,8 @@ class X {
|
|
|
1545
1613
|
/**
|
|
1546
1614
|
* Check if event matches a command
|
|
1547
1615
|
*/
|
|
1548
|
-
matchesCommand(e,
|
|
1549
|
-
return !(e.key.toLowerCase() !==
|
|
1616
|
+
matchesCommand(e, i) {
|
|
1617
|
+
return !(e.key.toLowerCase() !== i.key.toLowerCase() || i.ctrl !== void 0 && i.ctrl !== e.ctrlKey || i.shift !== void 0 && i.shift !== e.shiftKey || i.alt !== void 0 && i.alt !== e.altKey || i.meta !== void 0 && i.meta !== e.metaKey);
|
|
1550
1618
|
}
|
|
1551
1619
|
/**
|
|
1552
1620
|
* Cleanup
|
|
@@ -1558,28 +1626,28 @@ class X {
|
|
|
1558
1626
|
function W() {
|
|
1559
1627
|
return /Mac|iPod|iPhone|iPad/.test(navigator.platform);
|
|
1560
1628
|
}
|
|
1561
|
-
function
|
|
1629
|
+
function G() {
|
|
1562
1630
|
return W() ? "meta" : "ctrl";
|
|
1563
1631
|
}
|
|
1564
|
-
function
|
|
1565
|
-
const
|
|
1566
|
-
return Math.min(
|
|
1632
|
+
function K(v, e, i, t) {
|
|
1633
|
+
const s = v / i, a = e / t;
|
|
1634
|
+
return Math.min(s, a);
|
|
1567
1635
|
}
|
|
1568
|
-
function
|
|
1569
|
-
const a =
|
|
1636
|
+
function Q(v, e, i = 8, t = 10, s = 300) {
|
|
1637
|
+
const a = K(v, e, i, t), o = a >= s;
|
|
1570
1638
|
return {
|
|
1571
1639
|
actualDPI: Math.round(a),
|
|
1572
|
-
targetDPI:
|
|
1573
|
-
width:
|
|
1640
|
+
targetDPI: s,
|
|
1641
|
+
width: v,
|
|
1574
1642
|
height: e,
|
|
1575
1643
|
passed: o,
|
|
1576
|
-
warning: o ? void 0 : `Image resolution is ${Math.round(a)} DPI at 100% scale. For best print quality, use images with at least ${
|
|
1644
|
+
warning: o ? void 0 : `Image resolution is ${Math.round(a)} DPI at 100% scale. For best print quality, use images with at least ${s} DPI. This may result in pixelated or blurry prints.`
|
|
1577
1645
|
};
|
|
1578
1646
|
}
|
|
1579
|
-
const z = ':host{--editor-bg: #f3f4f6;--editor-surface: #ffffff;--editor-border: #e5e7eb;--editor-text: #111827;--editor-text-muted: #6b7280;--editor-primary: #3b82f6;--editor-primary-hover: #2563eb;--editor-danger: #ef4444;--editor-radius: 8px;--editor-radius-sm: 4px;--editor-font: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;--editor-panel-width: 280px;display:block;width:100%;height:100%;font-family:var(--editor-font);font-size:13px;color:var(--editor-text);line-height:1.4}:host([theme="dark"]){--editor-bg: #1f2937;--editor-surface: #374151;--editor-border: #4b5563;--editor-text: #f9fafb;--editor-text-muted: #9ca3af}.editor-container{display:flex;flex-direction:column;width:100%;height:100%;position:relative;overflow:hidden;background:var(--editor-bg)}.layout-desktop .editor-main{display:flex;flex:1;min-height:0}.layout-desktop .canvas-area{flex:1;min-width:0;position:relative;display:flex;flex-direction:column}.layout-desktop .area-panel{width:var(--editor-panel-width);border-left:1px solid var(--editor-border);background:var(--editor-surface);display:none;flex-direction:column;overflow:hidden}.layout-desktop .area-panel.area-panel-visible{display:flex}.layout-mobile .editor-main{display:flex;flex-direction:column;flex:1;min-height:0}.layout-mobile .canvas-area{flex:1;position:relative;display:flex;flex-direction:column;min-height:0}.layout-mobile .area-panel{position:absolute;bottom:0;left:0;right:0;max-height:50%;background:var(--editor-surface);border-top:1px solid var(--editor-border);border-radius:var(--editor-radius) var(--editor-radius) 0 0;box-shadow:0 -4px 20px #00000026;transform:translateY(100%);transition:transform .25s ease-out;z-index:20;display:flex;flex-direction:column;overflow:hidden}.layout-mobile .area-panel.area-panel-visible{transform:translateY(0)}.area-panel-backdrop{display:none}.layout-mobile .area-panel-backdrop{display:block;position:absolute;inset:0;background:#0000004d;z-index:15;opacity:0;pointer-events:none;transition:opacity .25s ease-out}.layout-mobile .area-panel-backdrop.area-panel-backdrop-visible{opacity:1;pointer-events:auto}.zoom-toolbar{display:flex;align-items:center;gap:4px;padding:8px 12px;background:var(--editor-surface);border-bottom:1px solid var(--editor-border)}.zoom-btn{display:inline-flex;align-items:center;justify-content:center;width:32px;height:32px;border:1px solid var(--editor-border);border-radius:var(--editor-radius-sm);background:var(--editor-surface);color:var(--editor-text);cursor:pointer;font-size:14px;font-family:var(--editor-font);transition:background .15s;-webkit-tap-highlight-color:transparent;touch-action:manipulation}.zoom-btn:hover{background:var(--editor-bg)}.zoom-btn:active{background:var(--editor-border)}.zoom-percent{margin-left:8px;font-size:12px;color:var(--editor-text-muted);min-width:40px}.view-select{margin-left:8px;padding:4px 8px;height:32px;border:1px solid var(--editor-border);border-radius:var(--editor-radius-sm);background:var(--editor-surface);color:var(--editor-text);font-family:var(--editor-font);font-size:12px;cursor:pointer;-webkit-tap-highlight-color:transparent}.view-select:hover{background:var(--editor-bg)}.view-select:focus{outline:2px solid var(--editor-primary);outline-offset:-1px}.zoom-toolbar-spacer{flex:1}.zoom-close-btn{width:auto;padding:0 12px;font-size:13px;gap:4px}.zoom-save-btn{width:auto;padding:0 16px;margin-left:8px;font-size:13px;font-weight:600;background:var(--editor-primary);color:#fff;border-color:var(--editor-primary)}.zoom-save-btn:hover{background:var(--editor-primary-hover)}.zoom-save-btn:disabled{opacity:.5;cursor:not-allowed}.canvas-wrapper{flex:1;position:relative;overflow:hidden;min-height:200px}.editor-canvas{position:absolute;top:0;left:0}.area-panel-header{display:flex;justify-content:space-between;align-items:center;padding:12px 16px;border-bottom:1px solid var(--editor-border);flex-shrink:0}.area-panel-title{font-weight:600;font-size:14px}.area-panel-close-btn{width:28px;height:28px;border:none;background:none;color:var(--editor-text-muted);cursor:pointer;font-size:18px;line-height:1;border-radius:var(--editor-radius-sm);display:flex;align-items:center;justify-content:center}.area-panel-close-btn:hover{background:var(--editor-bg);color:var(--editor-text)}.area-panel-content{flex:1;overflow-y:auto;padding:12px;display:flex;flex-direction:column;gap:12px}.area-panel-empty{color:var(--editor-text-muted);font-size:12px;text-align:center;padding:20px 0}.area-card{padding:12px;border:1px solid var(--editor-border);border-radius:var(--editor-radius);background:var(--editor-surface);cursor:pointer;transition:border-color .15s}.area-card:hover{border-color:var(--editor-primary)}.area-card-selected{border-color:var(--editor-primary);box-shadow:0 0 0 1px var(--editor-primary)}.area-card-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:8px}.area-card-name-row{display:flex;align-items:center;gap:6px}.area-card-name{font-weight:600;font-size:13px}.area-card-badge{display:inline-block;padding:1px 6px;font-size:10px;font-weight:500;background:#fef3c7;color:#92400e;border-radius:10px}.area-card-reset-btn{padding:2px 8px;border:1px solid var(--editor-border);border-radius:var(--editor-radius-sm);background:none;color:var(--editor-text-muted);cursor:pointer;font-size:11px;font-family:var(--editor-font)}.area-card-reset-btn:hover{background:var(--editor-bg);color:var(--editor-danger);border-color:var(--editor-danger)}.area-card-mode-switcher{display:flex;gap:4px;margin-bottom:8px}.mode-btn{flex:1;padding:4px 8px;border:1px solid var(--editor-border);border-radius:var(--editor-radius-sm);background:var(--editor-surface);color:var(--editor-text);cursor:pointer;font-size:12px;font-family:var(--editor-font);transition:all .15s}.mode-btn-active{background:var(--editor-primary);color:#fff;border-color:var(--editor-primary)}.area-input-text{width:100%;padding:6px 8px;border:1px solid var(--editor-border);border-radius:var(--editor-radius-sm);font-family:var(--editor-font);font-size:13px;color:var(--editor-text);background:var(--editor-surface);box-sizing:border-box;margin-bottom:6px}.area-input-text:focus{outline:none;border-color:var(--editor-primary);box-shadow:0 0 0 1px var(--editor-primary)}.area-input-row{display:flex;gap:6px;align-items:center;margin-bottom:6px}.area-input-font{flex:1;padding:4px 6px;border:1px solid var(--editor-border);border-radius:var(--editor-radius-sm);font-family:var(--editor-font);font-size:12px;color:var(--editor-text);background:var(--editor-surface);min-width:0}.area-input-size{width:56px;padding:4px 6px;border:1px solid var(--editor-border);border-radius:var(--editor-radius-sm);font-family:var(--editor-font);font-size:12px;color:var(--editor-text);background:var(--editor-surface)}.area-input-align{display:flex;gap:2px}.align-btn{width:28px;height:28px;border:1px solid var(--editor-border);border-radius:var(--editor-radius-sm);background:var(--editor-surface);color:var(--editor-text);cursor:pointer;font-size:12px;font-weight:600;font-family:var(--editor-font)}.align-btn-active{background:var(--editor-primary);color:#fff;border-color:var(--editor-primary)}.area-input-color{width:32px;height:28px;padding:0;border:1px solid var(--editor-border);border-radius:var(--editor-radius-sm);cursor:pointer;background:none}.area-upload-btn{width:100%;padding:10px;border:2px dashed var(--editor-border);border-radius:var(--editor-radius);background:var(--editor-bg);color:var(--editor-text-muted);cursor:pointer;font-family:var(--editor-font);font-size:12px;text-align:center;transition:all .15s;min-height:44px}.area-upload-btn:hover{border-color:var(--editor-primary);color:var(--editor-primary)}.area-image-preview{display:flex;gap:8px;align-items:center}.area-image-thumb{width:48px;height:48px;object-fit:cover;border-radius:var(--editor-radius-sm);border:1px solid var(--editor-border)}.area-image-info{flex:1;min-width:0;display:flex;flex-direction:column;gap:4px}.area-image-name{font-size:12px;color:var(--editor-text-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.area-image-remove-btn{padding:2px 8px;border:1px solid var(--editor-danger);border-radius:var(--editor-radius-sm);background:none;color:var(--editor-danger);cursor:pointer;font-size:11px;font-family:var(--editor-font);width:fit-content}.area-image-remove-btn:hover{background:#fef2f2}.area-card-divider{height:1px;background:var(--editor-border);margin:8px 0}.transform-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:6px}.transform-title{font-weight:600;font-size:12px}.transform-reset-btn{padding:2px 8px;border:1px solid var(--editor-border);border-radius:var(--editor-radius-sm);background:none;color:var(--editor-text-muted);cursor:pointer;font-size:11px;font-family:var(--editor-font)}.transform-reset-btn:hover{background:var(--editor-bg)}.transform-label{font-size:11px;color:var(--editor-text-muted);white-space:nowrap}.transform-input{width:60px;padding:3px 6px;border:1px solid var(--editor-border);border-radius:var(--editor-radius-sm);font-family:var(--editor-font);font-size:12px;color:var(--editor-text);background:var(--editor-surface)}.transform-slider-row{flex-direction:column;align-items:stretch}.transform-slider{width:100%;accent-color:var(--editor-primary)}.loading-container{display:flex;justify-content:center;align-items:center;width:100%;height:100%;min-height:200px}.loading-spinner{width:32px;height:32px;border:3px solid var(--editor-border);border-top-color:var(--editor-primary);border-radius:50%;animation:spin .8s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}.error-container{display:flex;flex-direction:column;justify-content:center;align-items:center;width:100%;height:100%;min-height:200px;padding:20px;text-align:center}.error-title{font-weight:600;font-size:16px;color:var(--editor-danger);margin-bottom:8px}.error-message{font-size:13px;color:var(--editor-text-muted);max-width:400px;white-space:pre-wrap}:host(:focus){outline:2px solid var(--editor-primary);outline-offset:-2px}input:focus,select:focus,button:focus-visible{outline:2px solid var(--editor-primary);outline-offset:-1px}.context-menu{position:absolute;z-index:100;min-width:160px;background:var(--editor-surface);border:1px solid var(--editor-border);border-radius:var(--editor-radius);box-shadow:0 4px 12px #00000026;padding:4px 0;font-size:13px}.context-menu-header{padding:6px 12px;font-weight:600;font-size:12px;color:var(--editor-text-muted);user-select:none}.context-menu-divider{height:1px;background:var(--editor-border);margin:4px 0}.context-menu-item{padding:6px 12px;cursor:pointer;color:var(--editor-text);user-select:none;transition:background .1s}.context-menu-item:hover{background:var(--editor-bg)}.context-menu-item:active{background:var(--editor-border)}.context-menu-item-disabled{color:var(--editor-text-muted);cursor:default;pointer-events:none}.editor-toast{position:absolute;top:16px;left:50%;transform:translate(-50%);padding:10px 20px;border-radius:var(--editor-radius);font-size:13px;font-family:var(--editor-font);line-height:1.4;z-index:200;max-width:400px;text-align:center;animation:toast-in .25s ease-out,toast-out .25s ease-in forwards;animation-delay:0s,var(--toast-duration, 3.75s);pointer-events:none;box-shadow:0 4px 12px #00000026}.editor-toast-warning{background:#fef3c7;color:#92400e;border:1px solid #f59e0b}.editor-toast-error{background:#fef2f2;color:#991b1b;border:1px solid var(--editor-danger)}.editor-toast-info{background:#eff6ff;color:#1e40af;border:1px solid var(--editor-primary)}@keyframes toast-in{0%{opacity:0;transform:translate(-50%) translateY(-10px)}to{opacity:1;transform:translate(-50%) translateY(0)}}@keyframes toast-out{0%{opacity:1;transform:translate(-50%) translateY(0)}to{opacity:0;transform:translate(-50%) translateY(-10px)}}.editor-modal-overlay{position:absolute;inset:0;background:#0006;z-index:300;display:flex;align-items:center;justify-content:center;animation:modal-overlay-in .2s ease-out}@keyframes modal-overlay-in{0%{opacity:0}to{opacity:1}}.editor-modal{background:var(--editor-surface);border-radius:var(--editor-radius);box-shadow:0 8px 32px #0003;max-width:380px;width:calc(100% - 32px);padding:20px;animation:modal-in .2s ease-out}@keyframes modal-in{0%{opacity:0;transform:scale(.95)}to{opacity:1;transform:scale(1)}}.editor-modal-title{font-weight:600;font-size:15px;margin-bottom:8px;color:var(--editor-text)}.editor-modal-body{font-size:13px;color:var(--editor-text-muted);margin-bottom:16px;line-height:1.5}.editor-modal-actions{display:flex;justify-content:flex-end;gap:8px}.editor-modal-btn{padding:8px 16px;border-radius:var(--editor-radius-sm);font-size:13px;font-family:var(--editor-font);cursor:pointer;border:1px solid var(--editor-border);background:var(--editor-surface);color:var(--editor-text);transition:background .15s}.editor-modal-btn:hover{background:var(--editor-bg)}.editor-modal-btn-primary{background:var(--editor-primary);color:#fff;border-color:var(--editor-primary)}.editor-modal-btn-primary:hover{background:var(--editor-primary-hover)}.area-validation-error{font-size:11px;color:var(--editor-danger);margin-top:4px}@media(pointer:coarse){.zoom-btn{min-width:44px;min-height:44px}.area-input-text,.area-input-font,.area-input-size{min-height:44px;font-size:16px}.align-btn{min-width:44px;min-height:44px}.area-upload-btn{min-height:48px}.view-select{min-height:44px;font-size:16px}}', Q = "2.0.0", _ = 768;
|
|
1580
|
-
class
|
|
1647
|
+
const k = ':host{--editor-bg: #f3f4f6;--editor-surface: #ffffff;--editor-border: #e5e7eb;--editor-text: #111827;--editor-text-muted: #6b7280;--editor-primary: #3b82f6;--editor-primary-hover: #2563eb;--editor-danger: #ef4444;--editor-radius: 8px;--editor-radius-sm: 4px;--editor-font: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;--editor-panel-width: 280px;display:block;width:100%;height:100%;font-family:var(--editor-font);font-size:13px;color:var(--editor-text);line-height:1.4}:host([theme="dark"]){--editor-bg: #1f2937;--editor-surface: #374151;--editor-border: #4b5563;--editor-text: #f9fafb;--editor-text-muted: #9ca3af}.editor-container{display:flex;flex-direction:column;width:100%;height:100%;position:relative;overflow:hidden;background:var(--editor-bg)}.layout-desktop .editor-main{display:flex;flex:1;min-height:0}.layout-desktop .canvas-area{flex:1;min-width:0;position:relative;display:flex;flex-direction:column}.layout-desktop .area-panel{width:var(--editor-panel-width);border-left:1px solid var(--editor-border);background:var(--editor-surface);display:none;flex-direction:column;overflow:hidden}.layout-desktop .area-panel.area-panel-visible{display:flex}.layout-mobile .editor-main{display:flex;flex-direction:column;flex:1;min-height:0}.layout-mobile .canvas-area{flex:1;position:relative;display:flex;flex-direction:column;min-height:0}.layout-mobile .area-panel{position:absolute;bottom:0;left:0;right:0;max-height:50%;background:var(--editor-surface);border-top:1px solid var(--editor-border);border-radius:var(--editor-radius) var(--editor-radius) 0 0;box-shadow:0 -4px 20px #00000026;transform:translateY(100%);transition:transform .25s ease-out;z-index:20;display:flex;flex-direction:column;overflow:hidden}.layout-mobile .area-panel.area-panel-visible{transform:translateY(0)}.area-panel-backdrop{display:none}.layout-mobile .area-panel-backdrop{display:block;position:absolute;inset:0;background:#0000004d;z-index:15;opacity:0;pointer-events:none;transition:opacity .25s ease-out}.layout-mobile .area-panel-backdrop.area-panel-backdrop-visible{opacity:1;pointer-events:auto}.zoom-toolbar{display:flex;align-items:center;gap:4px;padding:8px 12px;background:var(--editor-surface);border-bottom:1px solid var(--editor-border)}.zoom-btn{display:inline-flex;align-items:center;justify-content:center;width:32px;height:32px;border:1px solid var(--editor-border);border-radius:var(--editor-radius-sm);background:var(--editor-surface);color:var(--editor-text);cursor:pointer;font-size:14px;font-family:var(--editor-font);transition:background .15s;-webkit-tap-highlight-color:transparent;touch-action:manipulation}.zoom-btn:hover{background:var(--editor-bg)}.zoom-btn:active{background:var(--editor-border)}.zoom-percent{margin-left:8px;font-size:12px;color:var(--editor-text-muted);min-width:40px}.view-select{margin-left:8px;padding:4px 8px;height:32px;border:1px solid var(--editor-border);border-radius:var(--editor-radius-sm);background:var(--editor-surface);color:var(--editor-text);font-family:var(--editor-font);font-size:12px;cursor:pointer;-webkit-tap-highlight-color:transparent}.view-select:hover{background:var(--editor-bg)}.view-select:focus{outline:2px solid var(--editor-primary);outline-offset:-1px}.zoom-toolbar-spacer{flex:1}.zoom-close-btn{width:auto;padding:0 12px;font-size:13px;gap:4px}.zoom-save-btn{width:auto;padding:0 16px;margin-left:8px;font-size:13px;font-weight:600;background:var(--editor-primary);color:#fff;border-color:var(--editor-primary)}.zoom-save-btn:hover{background:var(--editor-primary-hover)}.zoom-save-btn:disabled{opacity:.5;cursor:not-allowed}.canvas-wrapper{flex:1;position:relative;overflow:hidden;min-height:200px}.editor-canvas{position:absolute;top:0;left:0}.area-panel-header{display:flex;justify-content:space-between;align-items:center;padding:12px 16px;border-bottom:1px solid var(--editor-border);flex-shrink:0}.area-panel-title{font-weight:600;font-size:14px}.area-panel-close-btn{width:28px;height:28px;border:none;background:none;color:var(--editor-text-muted);cursor:pointer;font-size:18px;line-height:1;border-radius:var(--editor-radius-sm);display:flex;align-items:center;justify-content:center}.area-panel-close-btn:hover{background:var(--editor-bg);color:var(--editor-text)}.area-panel-content{flex:1;overflow-y:auto;padding:12px;display:flex;flex-direction:column;gap:12px}.area-panel-empty{color:var(--editor-text-muted);font-size:12px;text-align:center;padding:20px 0}.area-card{padding:12px;border:1px solid var(--editor-border);border-radius:var(--editor-radius);background:var(--editor-surface);cursor:pointer;transition:border-color .15s}.area-card:hover{border-color:var(--editor-primary)}.area-card-selected{border-color:var(--editor-primary);box-shadow:0 0 0 1px var(--editor-primary)}.area-card-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:8px}.area-card-name-row{display:flex;align-items:center;gap:6px}.area-card-name{font-weight:600;font-size:13px}.area-card-badge{display:inline-block;padding:1px 6px;font-size:10px;font-weight:500;background:#fef3c7;color:#92400e;border-radius:10px}.area-card-reset-btn{padding:2px 8px;border:1px solid var(--editor-border);border-radius:var(--editor-radius-sm);background:none;color:var(--editor-text-muted);cursor:pointer;font-size:11px;font-family:var(--editor-font)}.area-card-reset-btn:hover{background:var(--editor-bg);color:var(--editor-danger);border-color:var(--editor-danger)}.area-card-mode-switcher{display:flex;gap:4px;margin-bottom:8px}.mode-btn{flex:1;padding:4px 8px;border:1px solid var(--editor-border);border-radius:var(--editor-radius-sm);background:var(--editor-surface);color:var(--editor-text);cursor:pointer;font-size:12px;font-family:var(--editor-font);transition:all .15s}.mode-btn-active{background:var(--editor-primary);color:#fff;border-color:var(--editor-primary)}.area-input-text{width:100%;padding:6px 8px;border:1px solid var(--editor-border);border-radius:var(--editor-radius-sm);font-family:var(--editor-font);font-size:13px;color:var(--editor-text);background:var(--editor-surface);box-sizing:border-box;margin-bottom:6px}.area-input-text:focus{outline:none;border-color:var(--editor-primary);box-shadow:0 0 0 1px var(--editor-primary)}.area-input-row{display:flex;gap:6px;align-items:center;margin-bottom:6px}.area-input-font{flex:1;padding:4px 6px;border:1px solid var(--editor-border);border-radius:var(--editor-radius-sm);font-family:var(--editor-font);font-size:12px;color:var(--editor-text);background:var(--editor-surface);min-width:0}.area-input-size{width:56px;padding:4px 6px;border:1px solid var(--editor-border);border-radius:var(--editor-radius-sm);font-family:var(--editor-font);font-size:12px;color:var(--editor-text);background:var(--editor-surface)}.area-input-align{display:flex;gap:2px}.align-btn{width:28px;height:28px;border:1px solid var(--editor-border);border-radius:var(--editor-radius-sm);background:var(--editor-surface);color:var(--editor-text);cursor:pointer;font-size:12px;font-weight:600;font-family:var(--editor-font)}.align-btn-active{background:var(--editor-primary);color:#fff;border-color:var(--editor-primary)}.area-input-color{width:32px;height:28px;padding:0;border:1px solid var(--editor-border);border-radius:var(--editor-radius-sm);cursor:pointer;background:none}.area-upload-btn{width:100%;padding:10px;border:2px dashed var(--editor-border);border-radius:var(--editor-radius);background:var(--editor-bg);color:var(--editor-text-muted);cursor:pointer;font-family:var(--editor-font);font-size:12px;text-align:center;transition:all .15s;min-height:44px}.area-upload-btn:hover{border-color:var(--editor-primary);color:var(--editor-primary)}.area-image-preview{display:flex;gap:8px;align-items:center}.area-image-thumb{width:48px;height:48px;object-fit:cover;border-radius:var(--editor-radius-sm);border:1px solid var(--editor-border)}.area-image-info{flex:1;min-width:0;display:flex;flex-direction:column;gap:4px}.area-image-name{font-size:12px;color:var(--editor-text-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.area-image-remove-btn{padding:2px 8px;border:1px solid var(--editor-danger);border-radius:var(--editor-radius-sm);background:none;color:var(--editor-danger);cursor:pointer;font-size:11px;font-family:var(--editor-font);width:fit-content}.area-image-remove-btn:hover{background:#fef2f2}.area-card-divider{height:1px;background:var(--editor-border);margin:8px 0}.transform-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:6px}.transform-title{font-weight:600;font-size:12px}.transform-reset-btn{padding:2px 8px;border:1px solid var(--editor-border);border-radius:var(--editor-radius-sm);background:none;color:var(--editor-text-muted);cursor:pointer;font-size:11px;font-family:var(--editor-font)}.transform-reset-btn:hover{background:var(--editor-bg)}.transform-label{font-size:11px;color:var(--editor-text-muted);white-space:nowrap}.transform-input{width:60px;padding:3px 6px;border:1px solid var(--editor-border);border-radius:var(--editor-radius-sm);font-family:var(--editor-font);font-size:12px;color:var(--editor-text);background:var(--editor-surface)}.transform-slider-row{flex-direction:column;align-items:stretch}.transform-slider{width:100%;accent-color:var(--editor-primary)}.loading-container{display:flex;justify-content:center;align-items:center;width:100%;height:100%;min-height:200px}.loading-spinner{width:32px;height:32px;border:3px solid var(--editor-border);border-top-color:var(--editor-primary);border-radius:50%;animation:spin .8s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}.error-container{display:flex;flex-direction:column;justify-content:center;align-items:center;width:100%;height:100%;min-height:200px;padding:20px;text-align:center}.error-title{font-weight:600;font-size:16px;color:var(--editor-danger);margin-bottom:8px}.error-message{font-size:13px;color:var(--editor-text-muted);max-width:400px;white-space:pre-wrap}:host(:focus){outline:2px solid var(--editor-primary);outline-offset:-2px}input:focus,select:focus,button:focus-visible{outline:2px solid var(--editor-primary);outline-offset:-1px}.context-menu{position:absolute;z-index:100;min-width:160px;background:var(--editor-surface);border:1px solid var(--editor-border);border-radius:var(--editor-radius);box-shadow:0 4px 12px #00000026;padding:4px 0;font-size:13px}.context-menu-header{padding:6px 12px;font-weight:600;font-size:12px;color:var(--editor-text-muted);user-select:none}.context-menu-divider{height:1px;background:var(--editor-border);margin:4px 0}.context-menu-item{padding:6px 12px;cursor:pointer;color:var(--editor-text);user-select:none;transition:background .1s}.context-menu-item:hover{background:var(--editor-bg)}.context-menu-item:active{background:var(--editor-border)}.context-menu-item-disabled{color:var(--editor-text-muted);cursor:default;pointer-events:none}.editor-toast{position:absolute;top:16px;left:50%;transform:translate(-50%);padding:10px 20px;border-radius:var(--editor-radius);font-size:13px;font-family:var(--editor-font);line-height:1.4;z-index:200;max-width:400px;text-align:center;animation:toast-in .25s ease-out,toast-out .25s ease-in forwards;animation-delay:0s,var(--toast-duration, 3.75s);pointer-events:none;box-shadow:0 4px 12px #00000026}.editor-toast-warning{background:#fef3c7;color:#92400e;border:1px solid #f59e0b}.editor-toast-error{background:#fef2f2;color:#991b1b;border:1px solid var(--editor-danger)}.editor-toast-info{background:#eff6ff;color:#1e40af;border:1px solid var(--editor-primary)}@keyframes toast-in{0%{opacity:0;transform:translate(-50%) translateY(-10px)}to{opacity:1;transform:translate(-50%) translateY(0)}}@keyframes toast-out{0%{opacity:1;transform:translate(-50%) translateY(0)}to{opacity:0;transform:translate(-50%) translateY(-10px)}}.editor-modal-overlay{position:absolute;inset:0;background:#0006;z-index:300;display:flex;align-items:center;justify-content:center;animation:modal-overlay-in .2s ease-out}@keyframes modal-overlay-in{0%{opacity:0}to{opacity:1}}.editor-modal{background:var(--editor-surface);border-radius:var(--editor-radius);box-shadow:0 8px 32px #0003;max-width:380px;width:calc(100% - 32px);padding:20px;animation:modal-in .2s ease-out}@keyframes modal-in{0%{opacity:0;transform:scale(.95)}to{opacity:1;transform:scale(1)}}.editor-modal-title{font-weight:600;font-size:15px;margin-bottom:8px;color:var(--editor-text)}.editor-modal-body{font-size:13px;color:var(--editor-text-muted);margin-bottom:16px;line-height:1.5}.editor-modal-actions{display:flex;justify-content:flex-end;gap:8px}.editor-modal-btn{padding:8px 16px;border-radius:var(--editor-radius-sm);font-size:13px;font-family:var(--editor-font);cursor:pointer;border:1px solid var(--editor-border);background:var(--editor-surface);color:var(--editor-text);transition:background .15s}.editor-modal-btn:hover{background:var(--editor-bg)}.editor-modal-btn-primary{background:var(--editor-primary);color:#fff;border-color:var(--editor-primary)}.editor-modal-btn-primary:hover{background:var(--editor-primary-hover)}.area-validation-error{font-size:11px;color:var(--editor-danger);margin-top:4px}@media(pointer:coarse){.zoom-btn{min-width:44px;min-height:44px}.area-input-text,.area-input-font,.area-input-size{min-height:44px;font-size:16px}.align-btn{min-width:44px;min-height:44px}.area-upload-btn{min-height:48px}.view-select{min-height:44px;font-size:16px}}', _ = "2.0.0", ee = 768;
|
|
1648
|
+
class I extends HTMLElement {
|
|
1581
1649
|
constructor() {
|
|
1582
|
-
super(), this.resizeObserver = null, this.isReady = !1, this.currentTemplate = null, this.loadingTemplateId = null, this.isMobileLayout = !1, this.productViews = [], this.activeViewName = null, this.perViewContents = /* @__PURE__ */ new Map(), this.subscriptions = [], this.storageUsage = null, this.storageUsageLastRefresh = 0, this.emitChange =
|
|
1650
|
+
super(), this.resizeObserver = null, this.isReady = !1, this.currentTemplate = null, this.loadingTemplateId = null, this.isMobileLayout = !1, this.productViews = [], this.activeViewName = null, this.perViewContents = /* @__PURE__ */ new Map(), this.subscriptions = [], this.storageUsage = null, this.storageUsageLastRefresh = 0, this.emitChange = q(() => {
|
|
1583
1651
|
this.dispatchEvent(
|
|
1584
1652
|
new CustomEvent("change", {
|
|
1585
1653
|
detail: { design: this.getDesign() },
|
|
@@ -1590,7 +1658,7 @@ class ee extends HTMLElement {
|
|
|
1590
1658
|
}, 300), this.shadow = this.attachShadow({ mode: "open" });
|
|
1591
1659
|
}
|
|
1592
1660
|
static get observedAttributes() {
|
|
1593
|
-
return ["template-id", "product-id", "theme", "mode", "store-id", "api-url"];
|
|
1661
|
+
return ["template-id", "product-id", "theme", "mode", "store-id", "api-url", "show-close-button", "show-save-button"];
|
|
1594
1662
|
}
|
|
1595
1663
|
async connectedCallback() {
|
|
1596
1664
|
try {
|
|
@@ -1602,15 +1670,15 @@ class ee extends HTMLElement {
|
|
|
1602
1670
|
disconnectedCallback() {
|
|
1603
1671
|
this.cleanup();
|
|
1604
1672
|
}
|
|
1605
|
-
attributeChangedCallback(e,
|
|
1606
|
-
|
|
1673
|
+
attributeChangedCallback(e, i, t) {
|
|
1674
|
+
i !== t && (e === "template-id" && this.isReady && t && this.loadTemplate(t), e === "product-id" && this.isReady && t && this.loadProduct(t), e === "show-close-button" && this.zoomToolbar && this.zoomToolbar.setCloseButtonVisible(t !== "false"), e === "show-save-button" && this.zoomToolbar && this.zoomToolbar.setSaveButtonEnabled(t !== "false"));
|
|
1607
1675
|
}
|
|
1608
1676
|
// ─── Public API ───
|
|
1609
1677
|
getDesign() {
|
|
1610
1678
|
if (!this.currentTemplate) throw new Error("No design loaded");
|
|
1611
1679
|
return {
|
|
1612
1680
|
templateId: this.currentTemplate.metadata.id,
|
|
1613
|
-
engineVersion:
|
|
1681
|
+
engineVersion: _,
|
|
1614
1682
|
contents: this.contentManager.toJSON(),
|
|
1615
1683
|
userData: {}
|
|
1616
1684
|
};
|
|
@@ -1659,79 +1727,188 @@ class ee extends HTMLElement {
|
|
|
1659
1727
|
}
|
|
1660
1728
|
setActiveView(e) {
|
|
1661
1729
|
if (this.activeViewName === e) return;
|
|
1662
|
-
const
|
|
1663
|
-
if (!
|
|
1664
|
-
this.switchToView(
|
|
1730
|
+
const i = this.productViews.find((t) => t.viewName === e);
|
|
1731
|
+
if (!i) throw new Error(`View not found: ${e}`);
|
|
1732
|
+
this.switchToView(i);
|
|
1665
1733
|
}
|
|
1666
|
-
setAreaContent(e,
|
|
1667
|
-
|
|
1734
|
+
setAreaContent(e, i) {
|
|
1735
|
+
i.type === "text" ? this.contentManager.setTextContent(e, i) : this.contentManager.setImageContent(e, i.dataUrl, i.filename), this.syncEngineContents(), this.saveContentState();
|
|
1668
1736
|
}
|
|
1669
1737
|
getAreaContent(e) {
|
|
1670
1738
|
return this.contentManager.getContent(e);
|
|
1671
1739
|
}
|
|
1740
|
+
static hasEditorContent(e) {
|
|
1741
|
+
return e.type === "text" ? e.text.trim().length > 0 : e.type === "image" ? !!e.dataUrl : !1;
|
|
1742
|
+
}
|
|
1743
|
+
validateRequiredContent() {
|
|
1744
|
+
const e = [];
|
|
1745
|
+
if (this.productViews.length > 1) {
|
|
1746
|
+
this.activeViewName && this.contentManager && this.perViewContents.set(this.activeViewName, this.contentManager.toJSON());
|
|
1747
|
+
for (const i of this.productViews) {
|
|
1748
|
+
const t = this.perViewContents.get(i.viewName), s = t?.some(([, o]) => I.hasEditorContent(o)) ?? !1;
|
|
1749
|
+
if (i.isRequired && !s) {
|
|
1750
|
+
e.push(`View "${i.viewName}" is required`);
|
|
1751
|
+
continue;
|
|
1752
|
+
}
|
|
1753
|
+
const a = i.template.templateJson.areas || [];
|
|
1754
|
+
for (const o of a) {
|
|
1755
|
+
if (!o.required) continue;
|
|
1756
|
+
const n = t?.find(([r]) => r === o.id);
|
|
1757
|
+
n && I.hasEditorContent(n[1]) || e.push(`"${o.name}" in view "${i.viewName}" is required`);
|
|
1758
|
+
}
|
|
1759
|
+
}
|
|
1760
|
+
} else {
|
|
1761
|
+
const i = this.currentTemplate?.areas || [], t = this.contentManager.getContents();
|
|
1762
|
+
for (const s of i) {
|
|
1763
|
+
if (!s.required) continue;
|
|
1764
|
+
const a = t.get(s.id);
|
|
1765
|
+
a && I.hasEditorContent(a) || e.push(`"${s.name}" is required`);
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
return e;
|
|
1769
|
+
}
|
|
1770
|
+
showValidationErrors(e) {
|
|
1771
|
+
return new Promise((i) => {
|
|
1772
|
+
const t = c("div", { class: "editor-modal-overlay" }), s = c("div", { class: "editor-modal" });
|
|
1773
|
+
s.appendChild(c("div", { class: "editor-modal-title" }, "Required content missing"));
|
|
1774
|
+
const a = c("div", { class: "editor-modal-body" }), o = document.createElement("ul");
|
|
1775
|
+
o.style.margin = "8px 0", o.style.paddingLeft = "20px";
|
|
1776
|
+
for (const d of e) {
|
|
1777
|
+
const l = document.createElement("li");
|
|
1778
|
+
l.textContent = d, o.appendChild(l);
|
|
1779
|
+
}
|
|
1780
|
+
a.appendChild(o), s.appendChild(a);
|
|
1781
|
+
const n = c("div", { class: "editor-modal-actions" }), r = c("button", { class: "editor-modal-btn editor-modal-btn-primary" }, "OK");
|
|
1782
|
+
r.addEventListener("click", () => {
|
|
1783
|
+
t.remove(), i();
|
|
1784
|
+
}), n.appendChild(r), s.appendChild(n), t.appendChild(s), t.addEventListener("click", (d) => {
|
|
1785
|
+
d.target === t && (t.remove(), i());
|
|
1786
|
+
}), this.shadow.appendChild(t);
|
|
1787
|
+
});
|
|
1788
|
+
}
|
|
1672
1789
|
async finalize() {
|
|
1673
|
-
const e = this.
|
|
1674
|
-
if (e.length > 0)
|
|
1675
|
-
|
|
1790
|
+
const e = this.validateRequiredContent();
|
|
1791
|
+
if (e.length > 0)
|
|
1792
|
+
return await this.showValidationErrors(e), null;
|
|
1793
|
+
const i = this.engine.checkSafeAreaViolations();
|
|
1794
|
+
if (i.length > 0) {
|
|
1795
|
+
const o = this.currentTemplate?.areas || [], n = i.map((r) => o.find((d) => d.id === r)?.name || r).join(", ");
|
|
1676
1796
|
if (!await this.showConfirmation(
|
|
1677
1797
|
"Content outside safe area",
|
|
1678
|
-
`Content in ${
|
|
1798
|
+
`Content in ${n} extends beyond the safe area. It may be trimmed during printing. Continue?`,
|
|
1679
1799
|
"Cancel",
|
|
1680
1800
|
"Proceed Anyway"
|
|
1681
1801
|
))
|
|
1682
1802
|
throw new Error("Finalization cancelled by user");
|
|
1683
1803
|
}
|
|
1684
|
-
const t = this.
|
|
1804
|
+
const t = this.getAttribute("store-id") || "demo-shop";
|
|
1805
|
+
if (this.productViews.length > 1) {
|
|
1806
|
+
this.activeViewName && this.contentManager && this.perViewContents.set(this.activeViewName, this.contentManager.toJSON());
|
|
1807
|
+
const o = [];
|
|
1808
|
+
for (const h of this.productViews) {
|
|
1809
|
+
const u = this.perViewContents.get(h.viewName);
|
|
1810
|
+
!u || u.length === 0 || !u.some(([, p]) => I.hasEditorContent(p)) || o.push({
|
|
1811
|
+
templateId: h.template.id,
|
|
1812
|
+
viewName: h.viewName,
|
|
1813
|
+
contents: u
|
|
1814
|
+
});
|
|
1815
|
+
}
|
|
1816
|
+
if (o.length === 0)
|
|
1817
|
+
throw new Error("No views have content to finalize");
|
|
1818
|
+
const n = this.getAttribute("product-id");
|
|
1819
|
+
if (!n)
|
|
1820
|
+
throw new Error("product-id attribute is required for multi-view finalize");
|
|
1821
|
+
const r = await this.apiClient.finalizeMultiView({
|
|
1822
|
+
productId: n,
|
|
1823
|
+
designs: o
|
|
1824
|
+
});
|
|
1825
|
+
if (!r.allCompleted) {
|
|
1826
|
+
const h = r.views.map((g) => g.designId), u = 1500, p = 40;
|
|
1827
|
+
let m = 0;
|
|
1828
|
+
for (; m < p; ) {
|
|
1829
|
+
await new Promise((f) => setTimeout(f, u)), m++;
|
|
1830
|
+
const g = await this.apiClient.pollMultiViewStatus(h);
|
|
1831
|
+
if (g.allCompleted) {
|
|
1832
|
+
for (const f of g.designs) {
|
|
1833
|
+
const b = r.views.find((w) => w.designId === f.designId);
|
|
1834
|
+
b && (b.status = f.status, b.proofUrl = f.proofUrl, b.errorMessage = f.errorMessage);
|
|
1835
|
+
}
|
|
1836
|
+
r.allCompleted = !0;
|
|
1837
|
+
break;
|
|
1838
|
+
}
|
|
1839
|
+
}
|
|
1840
|
+
}
|
|
1841
|
+
const d = r.views.map((h) => ({
|
|
1842
|
+
viewName: h.viewName,
|
|
1843
|
+
designId: h.designId,
|
|
1844
|
+
proofUrl: h.proofUrl || "",
|
|
1845
|
+
status: h.status
|
|
1846
|
+
})), l = d[0];
|
|
1847
|
+
return this.dispatchEvent(
|
|
1848
|
+
new CustomEvent("customizer:finalize", {
|
|
1849
|
+
detail: {
|
|
1850
|
+
designId: l.designId,
|
|
1851
|
+
proofUrl: l.proofUrl,
|
|
1852
|
+
status: l.status,
|
|
1853
|
+
views: d,
|
|
1854
|
+
requestId: r.requestId
|
|
1855
|
+
},
|
|
1856
|
+
bubbles: !0,
|
|
1857
|
+
composed: !0
|
|
1858
|
+
})
|
|
1859
|
+
), { ...l, views: d };
|
|
1860
|
+
}
|
|
1861
|
+
const s = this.getDesign(), a = await this.apiClient.finalizeDesign(t, s);
|
|
1685
1862
|
return this.dispatchEvent(
|
|
1686
1863
|
new CustomEvent("customizer:finalize", {
|
|
1687
1864
|
detail: {
|
|
1688
|
-
designId:
|
|
1689
|
-
proofUrl:
|
|
1690
|
-
templateId:
|
|
1691
|
-
designJson:
|
|
1692
|
-
status:
|
|
1865
|
+
designId: a.designId,
|
|
1866
|
+
proofUrl: a.proofUrl,
|
|
1867
|
+
templateId: s.templateId,
|
|
1868
|
+
designJson: s,
|
|
1869
|
+
status: a.status
|
|
1693
1870
|
},
|
|
1694
1871
|
bubbles: !0,
|
|
1695
1872
|
composed: !0
|
|
1696
1873
|
})
|
|
1697
|
-
),
|
|
1874
|
+
), a;
|
|
1698
1875
|
}
|
|
1699
1876
|
// ─── Initialization ───
|
|
1700
1877
|
renderLoading() {
|
|
1701
1878
|
const e = document.createElement("style");
|
|
1702
|
-
e.textContent =
|
|
1703
|
-
const
|
|
1879
|
+
e.textContent = k, this.shadow.appendChild(e);
|
|
1880
|
+
const i = c(
|
|
1704
1881
|
"div",
|
|
1705
1882
|
{ class: "loading-container" },
|
|
1706
|
-
|
|
1883
|
+
c("div", { class: "loading-spinner" })
|
|
1707
1884
|
);
|
|
1708
|
-
this.shadow.appendChild(
|
|
1885
|
+
this.shadow.appendChild(i);
|
|
1709
1886
|
}
|
|
1710
1887
|
async initialize() {
|
|
1711
1888
|
const e = this.getAttribute("api-url") || "http://localhost:4000";
|
|
1712
|
-
this.apiClient = new
|
|
1713
|
-
const
|
|
1714
|
-
if (!
|
|
1889
|
+
this.apiClient = new B(`${e.replace(/\/+$/, "")}/public`);
|
|
1890
|
+
const i = this.getAttribute("product-id"), t = this.getAttribute("template-id");
|
|
1891
|
+
if (!i && !t)
|
|
1715
1892
|
throw new Error("Either template-id or product-id attribute is required");
|
|
1716
|
-
|
|
1893
|
+
i ? await this.loadProduct(i) : await this.loadTemplate(t), this.isReady = !0, this.dispatchEvent(new CustomEvent("ready", { bubbles: !0, composed: !0 }));
|
|
1717
1894
|
}
|
|
1718
1895
|
async loadTemplate(e) {
|
|
1719
1896
|
if (this.loadingTemplateId !== e) {
|
|
1720
1897
|
this.loadingTemplateId = e;
|
|
1721
1898
|
try {
|
|
1722
|
-
const
|
|
1899
|
+
const i = await this.apiClient.getTemplate(e);
|
|
1723
1900
|
if (this.loadingTemplateId !== e) return;
|
|
1724
|
-
await this.loadTemplateData(
|
|
1725
|
-
} catch (
|
|
1726
|
-
throw this.loadingTemplateId = null, new Error(`Failed to load template: ${
|
|
1901
|
+
await this.loadTemplateData(i), this.loadingTemplateId = null;
|
|
1902
|
+
} catch (i) {
|
|
1903
|
+
throw this.loadingTemplateId = null, new Error(`Failed to load template: ${i.message}`);
|
|
1727
1904
|
}
|
|
1728
1905
|
}
|
|
1729
1906
|
}
|
|
1730
1907
|
async loadTemplateData(e) {
|
|
1731
1908
|
this.currentTemplate = e;
|
|
1732
|
-
const
|
|
1733
|
-
this.contentManager = new
|
|
1734
|
-
const
|
|
1909
|
+
const i = e.areas || [];
|
|
1910
|
+
this.contentManager = new P(i);
|
|
1911
|
+
const t = {
|
|
1735
1912
|
template: e,
|
|
1736
1913
|
contents: this.contentManager.toJSON(),
|
|
1737
1914
|
selectedAreaId: null,
|
|
@@ -1740,13 +1917,13 @@ class ee extends HTMLElement {
|
|
|
1740
1917
|
isDirty: !1,
|
|
1741
1918
|
warnings: []
|
|
1742
1919
|
};
|
|
1743
|
-
this.stateManager = new O(
|
|
1920
|
+
this.stateManager = new O(t), this.buildUI(e, i), this.engine = new U(this.canvas), this.engine.setArtboard({
|
|
1744
1921
|
width: e.artboard.width,
|
|
1745
1922
|
height: e.artboard.height,
|
|
1746
1923
|
backgroundColor: e.artboard.backgroundColor ?? "#ffffff"
|
|
1747
|
-
}), this.engine.setAreas(
|
|
1748
|
-
const
|
|
1749
|
-
|
|
1924
|
+
}), this.engine.setAreas(i), this.engine.setContents(this.contentManager.getContents()), e.backgroundImage && this.engine.setBackgroundImage(e.backgroundImage);
|
|
1925
|
+
const s = e.artboard.safeArea ?? e.safeArea;
|
|
1926
|
+
s && this.engine.setSafeArea(s), this.engine.start(), this.engine.fitToView(), this.stateManager.update({
|
|
1750
1927
|
zoom: this.engine.getZoom(),
|
|
1751
1928
|
pan: this.engine.getPan()
|
|
1752
1929
|
}), this.zoomToolbar.setZoom(this.engine.getZoom()), this.interaction = new F(
|
|
@@ -1767,38 +1944,38 @@ class ee extends HTMLElement {
|
|
|
1767
1944
|
}
|
|
1768
1945
|
async loadProduct(e) {
|
|
1769
1946
|
try {
|
|
1770
|
-
const
|
|
1771
|
-
if (
|
|
1947
|
+
const i = await this.apiClient.getProduct(e);
|
|
1948
|
+
if (i.views.length === 0)
|
|
1772
1949
|
throw new Error("Product has no active templates");
|
|
1773
|
-
this.productViews =
|
|
1774
|
-
const
|
|
1775
|
-
await this.loadTemplateData(
|
|
1776
|
-
} catch (
|
|
1777
|
-
throw new Error(`Failed to load product: ${
|
|
1950
|
+
this.productViews = i.views, this.perViewContents.clear();
|
|
1951
|
+
const t = i.views.find((s) => s.isDefault) || i.views[0];
|
|
1952
|
+
await this.loadTemplateData(t.template.templateJson), this.activeViewName = t.viewName, this.zoomToolbar && i.views.length > 1 && (this.zoomToolbar.setViews(this.getViews()), this.zoomToolbar.setActiveView(t.viewName));
|
|
1953
|
+
} catch (i) {
|
|
1954
|
+
throw new Error(`Failed to load product: ${i.message}`);
|
|
1778
1955
|
}
|
|
1779
1956
|
}
|
|
1780
1957
|
switchToView(e) {
|
|
1781
1958
|
this.activeViewName && this.contentManager && this.perViewContents.set(this.activeViewName, this.contentManager.toJSON()), this.activeViewName = e.viewName, this.zoomToolbar?.setActiveView(e.viewName);
|
|
1782
|
-
const
|
|
1783
|
-
this.currentTemplate =
|
|
1784
|
-
const
|
|
1785
|
-
this.contentManager = new
|
|
1786
|
-
const
|
|
1787
|
-
|
|
1788
|
-
width:
|
|
1789
|
-
height:
|
|
1790
|
-
backgroundColor:
|
|
1791
|
-
}), this.engine.setAreas(
|
|
1792
|
-
const a =
|
|
1959
|
+
const i = e.template.templateJson;
|
|
1960
|
+
this.currentTemplate = i;
|
|
1961
|
+
const t = i.areas || [];
|
|
1962
|
+
this.contentManager = new P(t);
|
|
1963
|
+
const s = this.perViewContents.get(e.viewName);
|
|
1964
|
+
s && this.contentManager.fromJSON(s), this.engine.setArtboard({
|
|
1965
|
+
width: i.artboard.width,
|
|
1966
|
+
height: i.artboard.height,
|
|
1967
|
+
backgroundColor: i.artboard.backgroundColor ?? "#ffffff"
|
|
1968
|
+
}), this.engine.setAreas(t), this.engine.setContents(this.contentManager.getContents()), this.engine.setBackgroundImage(i.backgroundImage ?? void 0);
|
|
1969
|
+
const a = i.artboard.safeArea ?? i.safeArea;
|
|
1793
1970
|
this.engine.setSafeArea(a ?? null), this.engine.fitToView(), this.stateManager.setState({
|
|
1794
|
-
template:
|
|
1971
|
+
template: i,
|
|
1795
1972
|
contents: this.contentManager.toJSON(),
|
|
1796
1973
|
selectedAreaId: null,
|
|
1797
1974
|
isDirty: this.stateManager.getState().isDirty,
|
|
1798
1975
|
zoom: this.engine.getZoom(),
|
|
1799
1976
|
pan: this.engine.getPan(),
|
|
1800
1977
|
warnings: []
|
|
1801
|
-
}), this.zoomToolbar.setZoom(this.engine.getZoom()), this.inputPanel.setAreas(
|
|
1978
|
+
}), this.zoomToolbar.setZoom(this.engine.getZoom()), this.inputPanel.setAreas(t), this.inputPanel.setContents(this.contentManager.getContents()), this.inputPanel.setSelectedArea(null), this.engine.setSelectedArea(null), this.dispatchEvent(
|
|
1802
1979
|
new CustomEvent("view-change", {
|
|
1803
1980
|
detail: { viewName: e.viewName },
|
|
1804
1981
|
bubbles: !0,
|
|
@@ -1806,12 +1983,12 @@ class ee extends HTMLElement {
|
|
|
1806
1983
|
})
|
|
1807
1984
|
);
|
|
1808
1985
|
}
|
|
1809
|
-
buildUI(e,
|
|
1986
|
+
buildUI(e, i) {
|
|
1810
1987
|
this.shadow.innerHTML = "";
|
|
1811
|
-
const
|
|
1812
|
-
|
|
1813
|
-
const
|
|
1814
|
-
|
|
1988
|
+
const t = document.createElement("style");
|
|
1989
|
+
t.textContent = k, this.shadow.appendChild(t), this.container = c("div", { class: "editor-container" }), this.zoomToolbar = new H(), this.subscriptions.push(this.zoomToolbar.on("zoom-in", () => this.handleZoomIn())), this.subscriptions.push(this.zoomToolbar.on("zoom-out", () => this.handleZoomOut())), this.subscriptions.push(this.zoomToolbar.on("zoom-fit", () => this.handleZoomFit())), this.subscriptions.push(this.zoomToolbar.on("view-change", ({ viewName: o }) => {
|
|
1990
|
+
const n = this.productViews.find((r) => r.viewName === o);
|
|
1991
|
+
n && this.switchToView(n);
|
|
1815
1992
|
})), this.subscriptions.push(this.zoomToolbar.on("close", () => {
|
|
1816
1993
|
this.dispatchEvent(new CustomEvent("customizer:close", {
|
|
1817
1994
|
bubbles: !0,
|
|
@@ -1820,21 +1997,30 @@ class ee extends HTMLElement {
|
|
|
1820
1997
|
})), this.subscriptions.push(this.zoomToolbar.on("save", async () => {
|
|
1821
1998
|
this.zoomToolbar.setSaveDisabled(!0, "Saving...");
|
|
1822
1999
|
try {
|
|
1823
|
-
|
|
1824
|
-
|
|
2000
|
+
const o = await this.finalize();
|
|
2001
|
+
if (!o) {
|
|
2002
|
+
this.zoomToolbar.setSaveDisabled(!1, "Save");
|
|
2003
|
+
return;
|
|
2004
|
+
}
|
|
2005
|
+
this.zoomToolbar.setSaveDisabled(!0, "Saved!"), this.showToast("Design saved successfully!", "info"), this.dispatchEvent(new CustomEvent("customizer:save", {
|
|
2006
|
+
detail: o,
|
|
2007
|
+
bubbles: !0,
|
|
2008
|
+
composed: !0
|
|
2009
|
+
})), setTimeout(() => {
|
|
2010
|
+
this.zoomToolbar.setSaveDisabled(!1, "Save"), this.zoomToolbar.showSaveButton(!1), this.stateManager.update({ isDirty: !1 });
|
|
1825
2011
|
}, 2e3);
|
|
1826
2012
|
} catch (o) {
|
|
1827
2013
|
console.error("Customizer: finalize failed", o);
|
|
1828
|
-
const
|
|
1829
|
-
this.showToast(
|
|
2014
|
+
const n = o instanceof Error ? o.message : "Save failed. Please try again.";
|
|
2015
|
+
this.showToast(n, "error"), this.zoomToolbar.setSaveDisabled(!1, "Save");
|
|
1830
2016
|
}
|
|
1831
|
-
}));
|
|
1832
|
-
const
|
|
1833
|
-
|
|
1834
|
-
const a =
|
|
1835
|
-
a.appendChild(
|
|
1836
|
-
for (const
|
|
1837
|
-
|
|
2017
|
+
})), this.getAttribute("show-close-button") === "false" && this.zoomToolbar.setCloseButtonVisible(!1), this.getAttribute("show-save-button") === "false" && this.zoomToolbar.setSaveButtonEnabled(!1);
|
|
2018
|
+
const s = c("div", { class: "canvas-area" });
|
|
2019
|
+
s.appendChild(this.zoomToolbar.getElement()), this.canvasWrapper = c("div", { class: "canvas-wrapper" }), this.canvas = document.createElement("canvas"), this.canvas.className = "editor-canvas", this.canvasWrapper.appendChild(this.canvas), s.appendChild(this.canvasWrapper), this.inputPanel = new J(i), this.wireInputPanelEvents();
|
|
2020
|
+
const a = c("div", { class: "editor-main" });
|
|
2021
|
+
a.appendChild(s), a.appendChild(this.inputPanel.getBackdrop()), a.appendChild(this.inputPanel.getElement()), this.container.appendChild(a), this.shadow.appendChild(this.container), this.resizeObserver = new ResizeObserver((o) => {
|
|
2022
|
+
for (const n of o)
|
|
2023
|
+
n.target === this.canvasWrapper && this.handleCanvasResize(n.contentRect), n.target === this.container && this.handleLayoutResize(n.contentRect);
|
|
1838
2024
|
}), this.resizeObserver.observe(this.canvasWrapper), this.resizeObserver.observe(this.container);
|
|
1839
2025
|
}
|
|
1840
2026
|
// ─── Event Wiring ───
|
|
@@ -1844,22 +2030,22 @@ class ee extends HTMLElement {
|
|
|
1844
2030
|
this.handleAreaSelect(e);
|
|
1845
2031
|
})
|
|
1846
2032
|
), this.subscriptions.push(
|
|
1847
|
-
this.interaction.on("content:drag", ({ areaId: e, offset:
|
|
1848
|
-
this.contentManager.setContentOffset(e,
|
|
2033
|
+
this.interaction.on("content:drag", ({ areaId: e, offset: i }) => {
|
|
2034
|
+
this.contentManager.setContentOffset(e, i), this.saveContentState();
|
|
1849
2035
|
})
|
|
1850
2036
|
), this.subscriptions.push(
|
|
1851
|
-
this.interaction.on("content:scale", ({ areaId: e, scale:
|
|
1852
|
-
this.contentManager.setImageScale(e,
|
|
2037
|
+
this.interaction.on("content:scale", ({ areaId: e, scale: i }) => {
|
|
2038
|
+
this.contentManager.setImageScale(e, i), this.saveContentState();
|
|
1853
2039
|
})
|
|
1854
2040
|
), this.subscriptions.push(
|
|
1855
|
-
this.interaction.on("content:rotate", ({ areaId: e, rotation:
|
|
1856
|
-
this.contentManager.setImageRotation(e,
|
|
2041
|
+
this.interaction.on("content:rotate", ({ areaId: e, rotation: i }) => {
|
|
2042
|
+
this.contentManager.setImageRotation(e, i), this.saveContentState();
|
|
1857
2043
|
})
|
|
1858
2044
|
), this.subscriptions.push(
|
|
1859
2045
|
this.interaction.on("zoom", ({ zoom: e }) => {
|
|
1860
2046
|
this.engine.setZoom(e);
|
|
1861
|
-
const
|
|
1862
|
-
|
|
2047
|
+
const i = this.engine.getCenteredPan(e);
|
|
2048
|
+
i && this.engine.setPan(i), this.stateManager.update({ zoom: this.engine.getZoom(), pan: this.engine.getPan() }), this.zoomToolbar.setZoom(this.engine.getZoom());
|
|
1863
2049
|
})
|
|
1864
2050
|
), this.subscriptions.push(
|
|
1865
2051
|
this.interaction.on("pan", ({ pan: e }) => {
|
|
@@ -1870,22 +2056,22 @@ class ee extends HTMLElement {
|
|
|
1870
2056
|
this.canvas.style.cursor = e;
|
|
1871
2057
|
})
|
|
1872
2058
|
), this.subscriptions.push(
|
|
1873
|
-
this.interaction.on("context-menu", ({ areaId: e, clientX:
|
|
1874
|
-
this.handleContextMenu(e,
|
|
2059
|
+
this.interaction.on("context-menu", ({ areaId: e, clientX: i, clientY: t }) => {
|
|
2060
|
+
this.handleContextMenu(e, i, t);
|
|
1875
2061
|
})
|
|
1876
2062
|
);
|
|
1877
2063
|
}
|
|
1878
2064
|
wireInputPanelEvents() {
|
|
1879
2065
|
this.subscriptions.push(
|
|
1880
|
-
this.inputPanel.on("text:change", ({ areaId: e, updates:
|
|
1881
|
-
this.contentManager.setTextContent(e,
|
|
2066
|
+
this.inputPanel.on("text:change", ({ areaId: e, updates: i }) => {
|
|
2067
|
+
this.contentManager.setTextContent(e, i), this.saveContentState();
|
|
1882
2068
|
})
|
|
1883
2069
|
), this.subscriptions.push(
|
|
1884
|
-
this.inputPanel.on("image:change", async ({ areaId: e, dataUrl:
|
|
1885
|
-
const
|
|
2070
|
+
this.inputPanel.on("image:change", async ({ areaId: e, dataUrl: i, filename: t }) => {
|
|
2071
|
+
const s = this.contentManager.getContents();
|
|
1886
2072
|
let a = 0;
|
|
1887
|
-
for (const [
|
|
1888
|
-
r.type === "image" &&
|
|
2073
|
+
for (const [n, r] of s)
|
|
2074
|
+
r.type === "image" && n !== e && a++;
|
|
1889
2075
|
if (a >= 10) {
|
|
1890
2076
|
this.showToast("Maximum of 10 images per design reached", "error");
|
|
1891
2077
|
return;
|
|
@@ -1894,28 +2080,30 @@ class ee extends HTMLElement {
|
|
|
1894
2080
|
this.showToast("Storage quota exceeded. Upgrade your plan for more storage.", "error");
|
|
1895
2081
|
return;
|
|
1896
2082
|
}
|
|
1897
|
-
const o = this.currentTemplate?.areas.find((
|
|
2083
|
+
const o = this.currentTemplate?.areas.find((n) => n.id === e);
|
|
1898
2084
|
if (o && this.currentTemplate) {
|
|
1899
|
-
const
|
|
2085
|
+
const n = this.currentTemplate.print.targetDpi;
|
|
1900
2086
|
try {
|
|
1901
|
-
const r = new Image()
|
|
1902
|
-
|
|
1903
|
-
|
|
2087
|
+
const r = new Image();
|
|
2088
|
+
r.crossOrigin = "anonymous";
|
|
2089
|
+
const d = await new Promise((p, m) => {
|
|
2090
|
+
r.onload = () => p({ width: r.naturalWidth, height: r.naturalHeight }), r.onerror = () => m(new Error("Failed to load image")), r.src = i;
|
|
2091
|
+
}), l = o.location.width / n, h = o.location.height / n, u = Q(
|
|
1904
2092
|
d.width,
|
|
1905
2093
|
d.height,
|
|
1906
2094
|
l,
|
|
1907
|
-
|
|
1908
|
-
|
|
2095
|
+
h,
|
|
2096
|
+
n
|
|
1909
2097
|
);
|
|
1910
2098
|
if (u.actualDPI < 150) {
|
|
1911
2099
|
if (!await this.showConfirmation(
|
|
1912
2100
|
"Low resolution detected",
|
|
1913
|
-
`This image is ${u.actualDPI} DPI at print size. Print may appear blurry. Recommended: ${
|
|
2101
|
+
`This image is ${u.actualDPI} DPI at print size. Print may appear blurry. Recommended: ${n} DPI.`,
|
|
1914
2102
|
"Cancel",
|
|
1915
2103
|
"Use Anyway"
|
|
1916
2104
|
)) return;
|
|
1917
|
-
} else u.actualDPI <
|
|
1918
|
-
`Image resolution is ${u.actualDPI} DPI (${
|
|
2105
|
+
} else u.actualDPI < n && (this.showToast(
|
|
2106
|
+
`Image resolution is ${u.actualDPI} DPI (${n} recommended)`,
|
|
1919
2107
|
"warning"
|
|
1920
2108
|
), this.stateManager.update({
|
|
1921
2109
|
warnings: [
|
|
@@ -1926,9 +2114,9 @@ class ee extends HTMLElement {
|
|
|
1926
2114
|
} catch {
|
|
1927
2115
|
}
|
|
1928
2116
|
}
|
|
1929
|
-
if (this.contentManager.setImageContent(e,
|
|
1930
|
-
const
|
|
1931
|
-
this.storageUsage.storageUsed +=
|
|
2117
|
+
if (this.contentManager.setImageContent(e, i, t), this.saveContentState(), this.storageUsage) {
|
|
2118
|
+
const n = Math.round(i.length * 0.75);
|
|
2119
|
+
this.storageUsage.storageUsed += n, this.storageUsage.usagePercent = this.storageUsage.storageQuota > 0 ? Math.round(this.storageUsage.storageUsed / this.storageUsage.storageQuota * 100) : 0;
|
|
1932
2120
|
}
|
|
1933
2121
|
})
|
|
1934
2122
|
), this.subscriptions.push(
|
|
@@ -1944,16 +2132,16 @@ class ee extends HTMLElement {
|
|
|
1944
2132
|
this.handleAreaSelect(e);
|
|
1945
2133
|
})
|
|
1946
2134
|
), this.subscriptions.push(
|
|
1947
|
-
this.inputPanel.on("offset:change", ({ areaId: e, offset:
|
|
1948
|
-
this.contentManager.setContentOffset(e,
|
|
2135
|
+
this.inputPanel.on("offset:change", ({ areaId: e, offset: i }) => {
|
|
2136
|
+
this.contentManager.setContentOffset(e, i), this.saveContentState();
|
|
1949
2137
|
})
|
|
1950
2138
|
), this.subscriptions.push(
|
|
1951
|
-
this.inputPanel.on("scale:change", ({ areaId: e, scale:
|
|
1952
|
-
this.contentManager.setImageScale(e,
|
|
2139
|
+
this.inputPanel.on("scale:change", ({ areaId: e, scale: i }) => {
|
|
2140
|
+
this.contentManager.setImageScale(e, i), this.saveContentState();
|
|
1953
2141
|
})
|
|
1954
2142
|
), this.subscriptions.push(
|
|
1955
|
-
this.inputPanel.on("rotation:change", ({ areaId: e, rotation:
|
|
1956
|
-
this.contentManager.setImageRotation(e,
|
|
2143
|
+
this.inputPanel.on("rotation:change", ({ areaId: e, rotation: i }) => {
|
|
2144
|
+
this.contentManager.setImageRotation(e, i), this.saveContentState();
|
|
1957
2145
|
})
|
|
1958
2146
|
), this.subscriptions.push(
|
|
1959
2147
|
this.inputPanel.on("dismiss", () => {
|
|
@@ -1962,8 +2150,8 @@ class ee extends HTMLElement {
|
|
|
1962
2150
|
);
|
|
1963
2151
|
}
|
|
1964
2152
|
// ─── Handlers ───
|
|
1965
|
-
handleAreaSelect(e,
|
|
1966
|
-
this.stateManager.update({ selectedAreaId: e }), this.engine.setSelectedArea(e), this.inputPanel.setSelectedArea(
|
|
2153
|
+
handleAreaSelect(e, i = !1) {
|
|
2154
|
+
this.stateManager.update({ selectedAreaId: e }), this.engine.setSelectedArea(e), this.inputPanel.setSelectedArea(i ? e : null), e && document.activeElement !== this && this.focus(), this.dispatchEvent(
|
|
1967
2155
|
new CustomEvent("area:select", {
|
|
1968
2156
|
detail: { areaId: e },
|
|
1969
2157
|
bubbles: !0,
|
|
@@ -1971,29 +2159,29 @@ class ee extends HTMLElement {
|
|
|
1971
2159
|
})
|
|
1972
2160
|
);
|
|
1973
2161
|
}
|
|
1974
|
-
handleContextMenu(e,
|
|
2162
|
+
handleContextMenu(e, i, t) {
|
|
1975
2163
|
this.handleAreaSelect(e, !!e);
|
|
1976
2164
|
}
|
|
1977
2165
|
handleZoomIn() {
|
|
1978
|
-
const e = this.engine.getZoom(),
|
|
1979
|
-
this.engine.setZoom(
|
|
2166
|
+
const e = this.engine.getZoom(), i = Math.min(e * 1.25, 5), t = this.engine.getCenteredPan(i);
|
|
2167
|
+
this.engine.setZoom(i), t && this.engine.setPan(t), this.stateManager.update({ zoom: this.engine.getZoom(), pan: this.engine.getPan() }), this.zoomToolbar.setZoom(this.engine.getZoom());
|
|
1980
2168
|
}
|
|
1981
2169
|
handleZoomOut() {
|
|
1982
|
-
const e = this.engine.getZoom(),
|
|
1983
|
-
this.engine.setZoom(
|
|
2170
|
+
const e = this.engine.getZoom(), i = Math.max(e * 0.8, 0.1), t = this.engine.getCenteredPan(i);
|
|
2171
|
+
this.engine.setZoom(i), t && this.engine.setPan(t), this.stateManager.update({ zoom: this.engine.getZoom(), pan: this.engine.getPan() }), this.zoomToolbar.setZoom(this.engine.getZoom());
|
|
1984
2172
|
}
|
|
1985
2173
|
handleZoomFit() {
|
|
1986
2174
|
this.engine.fitToView(), this.stateManager.update({ zoom: this.engine.getZoom(), pan: this.engine.getPan() }), this.zoomToolbar.setZoom(this.engine.getZoom());
|
|
1987
2175
|
}
|
|
1988
2176
|
handleCanvasResize(e) {
|
|
1989
|
-
const { width:
|
|
1990
|
-
if (
|
|
1991
|
-
const
|
|
1992
|
-
this.canvas.width =
|
|
2177
|
+
const { width: i, height: t } = e;
|
|
2178
|
+
if (i === 0 || t === 0) return;
|
|
2179
|
+
const s = window.devicePixelRatio || 1;
|
|
2180
|
+
this.canvas.width = i * s, this.canvas.height = t * s, this.canvas.style.width = `${i}px`, this.canvas.style.height = `${t}px`, this.engine && (this.engine.fitToView(), this.stateManager.update({ zoom: this.engine.getZoom(), pan: this.engine.getPan() }), this.zoomToolbar.setZoom(this.engine.getZoom()));
|
|
1993
2181
|
}
|
|
1994
2182
|
handleLayoutResize(e) {
|
|
1995
|
-
const
|
|
1996
|
-
this.container.classList.toggle("layout-desktop", !
|
|
2183
|
+
const i = e.width < ee;
|
|
2184
|
+
this.container.classList.toggle("layout-desktop", !i), this.container.classList.toggle("layout-mobile", i), i !== this.isMobileLayout && (this.isMobileLayout = i, this.inputPanel.setMobile(i));
|
|
1997
2185
|
}
|
|
1998
2186
|
// ─── State Management ───
|
|
1999
2187
|
async refreshStorageUsage() {
|
|
@@ -2020,7 +2208,7 @@ class ee extends HTMLElement {
|
|
|
2020
2208
|
}
|
|
2021
2209
|
// ─── Keyboard ───
|
|
2022
2210
|
setupKeyboardShortcuts() {
|
|
2023
|
-
const e =
|
|
2211
|
+
const e = G();
|
|
2024
2212
|
this.keyboard.register({
|
|
2025
2213
|
key: "z",
|
|
2026
2214
|
[e]: !0,
|
|
@@ -2036,35 +2224,35 @@ class ee extends HTMLElement {
|
|
|
2036
2224
|
});
|
|
2037
2225
|
}
|
|
2038
2226
|
// ─── Toast & Modal ───
|
|
2039
|
-
showToast(e,
|
|
2040
|
-
const
|
|
2041
|
-
this.shadow.appendChild(
|
|
2227
|
+
showToast(e, i = "info") {
|
|
2228
|
+
const t = c("div", { class: `editor-toast editor-toast-${i}` }, e);
|
|
2229
|
+
this.shadow.appendChild(t), setTimeout(() => t.remove(), 4e3);
|
|
2042
2230
|
}
|
|
2043
|
-
showConfirmation(e,
|
|
2231
|
+
showConfirmation(e, i, t = "Cancel", s = "Continue") {
|
|
2044
2232
|
return new Promise((a) => {
|
|
2045
|
-
const o =
|
|
2046
|
-
|
|
2047
|
-
const r =
|
|
2233
|
+
const o = c("div", { class: "editor-modal-overlay" }), n = c("div", { class: "editor-modal" });
|
|
2234
|
+
n.appendChild(c("div", { class: "editor-modal-title" }, e)), n.appendChild(c("div", { class: "editor-modal-body" }, i));
|
|
2235
|
+
const r = c("div", { class: "editor-modal-actions" }), d = c("button", { class: "editor-modal-btn" }, t);
|
|
2048
2236
|
d.addEventListener("click", () => {
|
|
2049
2237
|
o.remove(), a(!1);
|
|
2050
2238
|
});
|
|
2051
|
-
const l =
|
|
2239
|
+
const l = c("button", { class: "editor-modal-btn editor-modal-btn-primary" }, s);
|
|
2052
2240
|
l.addEventListener("click", () => {
|
|
2053
2241
|
o.remove(), a(!0);
|
|
2054
|
-
}), r.appendChild(d), r.appendChild(l),
|
|
2055
|
-
|
|
2242
|
+
}), r.appendChild(d), r.appendChild(l), n.appendChild(r), o.appendChild(n), o.addEventListener("click", (h) => {
|
|
2243
|
+
h.target === o && (o.remove(), a(!1));
|
|
2056
2244
|
}), this.shadow.appendChild(o);
|
|
2057
2245
|
});
|
|
2058
2246
|
}
|
|
2059
2247
|
// ─── Error / Cleanup ───
|
|
2060
|
-
showError(e,
|
|
2248
|
+
showError(e, i) {
|
|
2061
2249
|
this.shadow.innerHTML = "";
|
|
2062
|
-
const
|
|
2063
|
-
|
|
2064
|
-
const
|
|
2065
|
-
|
|
2250
|
+
const t = document.createElement("style");
|
|
2251
|
+
t.textContent = k, this.shadow.appendChild(t);
|
|
2252
|
+
const s = c("div", { class: "error-container" });
|
|
2253
|
+
s.appendChild(c("div", { class: "error-title" }, e)), s.appendChild(c("div", { class: "error-message" }, i?.message ?? "")), this.shadow.appendChild(s), this.dispatchEvent(
|
|
2066
2254
|
new CustomEvent("error", {
|
|
2067
|
-
detail: { message: e, error:
|
|
2255
|
+
detail: { message: e, error: i },
|
|
2068
2256
|
bubbles: !0,
|
|
2069
2257
|
composed: !0
|
|
2070
2258
|
})
|
|
@@ -2076,174 +2264,189 @@ class ee extends HTMLElement {
|
|
|
2076
2264
|
this.subscriptions = [], this.resizeObserver && (this.resizeObserver.disconnect(), this.resizeObserver = null), this.engine?.destroy(), this.interaction?.destroy(), this.keyboard?.destroy(), this.isReady = !1, this.currentTemplate = null, this.productViews = [], this.activeViewName = null, this.perViewContents.clear();
|
|
2077
2265
|
}
|
|
2078
2266
|
}
|
|
2079
|
-
customElements.get("customizer-editor") || customElements.define("customizer-editor",
|
|
2080
|
-
function te(
|
|
2081
|
-
const
|
|
2082
|
-
if (!
|
|
2267
|
+
customElements.get("customizer-editor") || customElements.define("customizer-editor", I);
|
|
2268
|
+
function te(v, e) {
|
|
2269
|
+
const i = typeof v == "string" ? document.querySelector(v) : v;
|
|
2270
|
+
if (!i)
|
|
2083
2271
|
throw new Error(
|
|
2084
|
-
`Container not found: ${typeof
|
|
2272
|
+
`Container not found: ${typeof v == "string" ? v : "provided element is null"}`
|
|
2085
2273
|
);
|
|
2086
2274
|
if (!e.templateId && !e.productId)
|
|
2087
2275
|
throw new Error("Either templateId or productId must be provided");
|
|
2088
2276
|
if (e.templateId && e.productId)
|
|
2089
2277
|
throw new Error("Only one of templateId or productId should be provided, not both");
|
|
2090
|
-
const
|
|
2091
|
-
e.productId ?
|
|
2092
|
-
const
|
|
2093
|
-
|
|
2278
|
+
const t = document.createElement("customizer-editor");
|
|
2279
|
+
e.productId ? t.setAttribute("product-id", e.productId) : t.setAttribute("template-id", e.templateId), e.theme && t.setAttribute("theme", e.theme), e.mode && t.setAttribute("mode", e.mode), e.className && t.classList.add(e.className), e.showCloseButton === !1 && t.setAttribute("show-close-button", "false"), e.showSaveButton === !1 && t.setAttribute("show-save-button", "false"), t.style.width = "100%", t.style.height = "100%";
|
|
2280
|
+
const s = [], a = (n, r) => {
|
|
2281
|
+
t.addEventListener(n, r), s.push({ event: n, handler: r });
|
|
2094
2282
|
};
|
|
2095
|
-
|
|
2283
|
+
i.innerHTML = "", i.appendChild(t);
|
|
2096
2284
|
const o = {
|
|
2097
2285
|
getDesign() {
|
|
2098
|
-
if (typeof
|
|
2286
|
+
if (typeof t.getDesign != "function")
|
|
2099
2287
|
throw new Error("Editor not ready: getDesign method not available");
|
|
2100
|
-
return
|
|
2288
|
+
return t.getDesign();
|
|
2101
2289
|
},
|
|
2102
|
-
setDesign(
|
|
2103
|
-
if (typeof
|
|
2290
|
+
setDesign(n) {
|
|
2291
|
+
if (typeof t.setDesign != "function")
|
|
2104
2292
|
throw new Error("Editor not ready: setDesign method not available");
|
|
2105
|
-
|
|
2293
|
+
t.setDesign(n);
|
|
2106
2294
|
},
|
|
2107
2295
|
undo() {
|
|
2108
|
-
if (typeof
|
|
2296
|
+
if (typeof t.undo != "function")
|
|
2109
2297
|
throw new Error("Editor not ready: undo method not available");
|
|
2110
|
-
|
|
2298
|
+
t.undo();
|
|
2111
2299
|
},
|
|
2112
2300
|
redo() {
|
|
2113
|
-
if (typeof
|
|
2301
|
+
if (typeof t.redo != "function")
|
|
2114
2302
|
throw new Error("Editor not ready: redo method not available");
|
|
2115
|
-
|
|
2303
|
+
t.redo();
|
|
2116
2304
|
},
|
|
2117
2305
|
canUndo() {
|
|
2118
|
-
return typeof
|
|
2306
|
+
return typeof t.canUndo != "function" ? !1 : t.canUndo();
|
|
2119
2307
|
},
|
|
2120
2308
|
canRedo() {
|
|
2121
|
-
return typeof
|
|
2309
|
+
return typeof t.canRedo != "function" ? !1 : t.canRedo();
|
|
2122
2310
|
},
|
|
2123
2311
|
async finalize() {
|
|
2124
|
-
const
|
|
2312
|
+
const n = e.apiUrl || "https://api.varianta.io";
|
|
2125
2313
|
try {
|
|
2126
|
-
|
|
2314
|
+
if (e.productId && typeof t.finalize == "function") {
|
|
2315
|
+
const g = await t.finalize(), f = {
|
|
2316
|
+
designId: g.designId,
|
|
2317
|
+
status: g.status,
|
|
2318
|
+
proofUrl: g.proofUrl ?? null,
|
|
2319
|
+
errorMessage: null,
|
|
2320
|
+
requestId: g.requestId,
|
|
2321
|
+
views: g.views
|
|
2322
|
+
};
|
|
2323
|
+
return e.onFinalize && e.onFinalize(f), f;
|
|
2324
|
+
}
|
|
2325
|
+
const r = this.getDesign(), d = await fetch(`${n}/public/finalize`, {
|
|
2127
2326
|
method: "POST",
|
|
2128
2327
|
headers: {
|
|
2129
2328
|
"Content-Type": "application/json"
|
|
2130
2329
|
},
|
|
2131
2330
|
body: JSON.stringify({
|
|
2132
|
-
templateId:
|
|
2133
|
-
designJson:
|
|
2331
|
+
templateId: r.templateId || e.templateId,
|
|
2332
|
+
designJson: r
|
|
2134
2333
|
})
|
|
2135
2334
|
});
|
|
2136
2335
|
if (!d.ok) {
|
|
2137
|
-
const
|
|
2336
|
+
const g = await d.json().catch(() => ({
|
|
2138
2337
|
message: "Finalization failed"
|
|
2139
2338
|
}));
|
|
2140
|
-
throw new Error(
|
|
2339
|
+
throw new Error(g.message || "Finalization failed");
|
|
2141
2340
|
}
|
|
2142
2341
|
const l = await d.json();
|
|
2143
|
-
let
|
|
2342
|
+
let h = {
|
|
2144
2343
|
designId: l.designId,
|
|
2145
2344
|
status: l.status,
|
|
2146
2345
|
proofUrl: l.proofUrl ?? null,
|
|
2147
2346
|
errorMessage: l.errorMessage ?? null
|
|
2148
2347
|
};
|
|
2149
2348
|
const u = 1500, p = 40;
|
|
2150
|
-
let
|
|
2151
|
-
for (;
|
|
2152
|
-
await new Promise((b) => setTimeout(b, u)),
|
|
2153
|
-
const
|
|
2154
|
-
`${
|
|
2349
|
+
let m = 0;
|
|
2350
|
+
for (; h.status === "processing" && m < p; ) {
|
|
2351
|
+
await new Promise((b) => setTimeout(b, u)), m++;
|
|
2352
|
+
const g = await fetch(
|
|
2353
|
+
`${n}/public/designs/${h.designId}/status`
|
|
2155
2354
|
);
|
|
2156
|
-
if (!
|
|
2355
|
+
if (!g.ok)
|
|
2157
2356
|
throw new Error("Failed to check design status");
|
|
2158
|
-
const
|
|
2159
|
-
|
|
2160
|
-
designId:
|
|
2161
|
-
status:
|
|
2162
|
-
proofUrl:
|
|
2163
|
-
errorMessage:
|
|
2357
|
+
const f = await g.json();
|
|
2358
|
+
h = {
|
|
2359
|
+
designId: f.designId,
|
|
2360
|
+
status: f.status,
|
|
2361
|
+
proofUrl: f.proofUrl ?? null,
|
|
2362
|
+
errorMessage: f.errorMessage ?? null
|
|
2164
2363
|
};
|
|
2165
2364
|
}
|
|
2166
|
-
return
|
|
2167
|
-
} catch (
|
|
2168
|
-
const
|
|
2365
|
+
return h.status === "processing" && (h = { ...h, status: "failed", errorMessage: "Render timed out" }), e.onFinalize && e.onFinalize(h), h;
|
|
2366
|
+
} catch (r) {
|
|
2367
|
+
const d = {
|
|
2169
2368
|
code: "FINALIZE_ERROR",
|
|
2170
|
-
message:
|
|
2171
|
-
details:
|
|
2369
|
+
message: r instanceof Error ? r.message : "Unknown error",
|
|
2370
|
+
details: r
|
|
2172
2371
|
};
|
|
2173
|
-
throw e.onError && e.onError(
|
|
2372
|
+
throw e.onError && e.onError(d), r;
|
|
2174
2373
|
}
|
|
2175
2374
|
},
|
|
2176
|
-
addTextLayer(
|
|
2177
|
-
if (typeof
|
|
2375
|
+
addTextLayer(n) {
|
|
2376
|
+
if (typeof t.addTextLayer != "function")
|
|
2178
2377
|
throw new Error("Editor not ready: addTextLayer method not available");
|
|
2179
|
-
|
|
2378
|
+
t.addTextLayer(n);
|
|
2180
2379
|
},
|
|
2181
|
-
async addImageLayer(
|
|
2182
|
-
if (typeof
|
|
2380
|
+
async addImageLayer(n) {
|
|
2381
|
+
if (typeof t.addImageLayer != "function")
|
|
2183
2382
|
throw new Error("Editor not ready: addImageLayer method not available");
|
|
2184
|
-
return
|
|
2383
|
+
return t.addImageLayer(n);
|
|
2185
2384
|
},
|
|
2186
|
-
removeLayer(
|
|
2187
|
-
if (typeof
|
|
2385
|
+
removeLayer(n) {
|
|
2386
|
+
if (typeof t.removeLayer != "function")
|
|
2188
2387
|
throw new Error("Editor not ready: removeLayer method not available");
|
|
2189
|
-
|
|
2388
|
+
t.removeLayer(n);
|
|
2190
2389
|
},
|
|
2191
|
-
selectLayer(
|
|
2192
|
-
if (typeof
|
|
2390
|
+
selectLayer(n) {
|
|
2391
|
+
if (typeof t.selectLayer != "function")
|
|
2193
2392
|
throw new Error("Editor not ready: selectLayer method not available");
|
|
2194
|
-
|
|
2393
|
+
t.selectLayer(n);
|
|
2195
2394
|
},
|
|
2196
2395
|
getSelectedLayerId() {
|
|
2197
|
-
return typeof
|
|
2396
|
+
return typeof t.getSelectedLayerId != "function" ? null : t.getSelectedLayerId();
|
|
2198
2397
|
},
|
|
2199
2398
|
getActiveView() {
|
|
2200
|
-
return typeof
|
|
2399
|
+
return typeof t.getActiveView != "function" ? null : t.getActiveView();
|
|
2201
2400
|
},
|
|
2202
2401
|
getViews() {
|
|
2203
|
-
return typeof
|
|
2402
|
+
return typeof t.getViews != "function" ? [] : t.getViews();
|
|
2204
2403
|
},
|
|
2205
|
-
setActiveView(
|
|
2206
|
-
if (typeof
|
|
2404
|
+
setActiveView(n) {
|
|
2405
|
+
if (typeof t.setActiveView != "function")
|
|
2207
2406
|
throw new Error("Editor not ready: setActiveView method not available");
|
|
2208
|
-
|
|
2407
|
+
t.setActiveView(n);
|
|
2209
2408
|
},
|
|
2210
|
-
setTheme(
|
|
2211
|
-
|
|
2409
|
+
setTheme(n) {
|
|
2410
|
+
t.setAttribute("theme", n);
|
|
2212
2411
|
},
|
|
2213
|
-
setMode(
|
|
2214
|
-
|
|
2412
|
+
setMode(n) {
|
|
2413
|
+
t.setAttribute("mode", n);
|
|
2215
2414
|
},
|
|
2216
2415
|
destroy() {
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
}),
|
|
2416
|
+
s.forEach(({ event: n, handler: r }) => {
|
|
2417
|
+
t.removeEventListener(n, r);
|
|
2418
|
+
}), t.parentNode && t.parentNode.removeChild(t);
|
|
2220
2419
|
},
|
|
2221
2420
|
getElement() {
|
|
2222
|
-
return
|
|
2421
|
+
return t;
|
|
2223
2422
|
}
|
|
2224
2423
|
};
|
|
2225
2424
|
return e.onReady && a("ready", (() => {
|
|
2226
2425
|
e.onReady?.(o);
|
|
2227
|
-
})), e.onChange && a("change", ((
|
|
2228
|
-
const r =
|
|
2426
|
+
})), e.onChange && a("change", ((n) => {
|
|
2427
|
+
const r = n.detail.design;
|
|
2229
2428
|
e.onChange?.(r);
|
|
2230
|
-
})), e.onLayerSelect && a("layer:select", ((
|
|
2231
|
-
e.onLayerSelect?.(
|
|
2232
|
-
})), e.onLayerAdd && a("layer:add", ((
|
|
2233
|
-
e.onLayerAdd?.(
|
|
2234
|
-
})), e.onLayerRemove && a("layer:remove", ((
|
|
2235
|
-
e.onLayerRemove?.(
|
|
2236
|
-
})), e.onLayerUpdate && a("layer:update", ((
|
|
2237
|
-
e.onLayerUpdate?.(
|
|
2238
|
-
})), e.onError && a("error", ((
|
|
2239
|
-
const r =
|
|
2429
|
+
})), e.onLayerSelect && a("layer:select", ((n) => {
|
|
2430
|
+
e.onLayerSelect?.(n.detail.layerId);
|
|
2431
|
+
})), e.onLayerAdd && a("layer:add", ((n) => {
|
|
2432
|
+
e.onLayerAdd?.(n.detail.layerId);
|
|
2433
|
+
})), e.onLayerRemove && a("layer:remove", ((n) => {
|
|
2434
|
+
e.onLayerRemove?.(n.detail.layerId);
|
|
2435
|
+
})), e.onLayerUpdate && a("layer:update", ((n) => {
|
|
2436
|
+
e.onLayerUpdate?.(n.detail.layerId);
|
|
2437
|
+
})), e.onError && a("error", ((n) => {
|
|
2438
|
+
const r = n.detail.error;
|
|
2240
2439
|
e.onError?.(r);
|
|
2241
|
-
})), e.onViewChange && a("view-change", ((
|
|
2242
|
-
e.onViewChange?.(
|
|
2243
|
-
})), e.
|
|
2440
|
+
})), e.onViewChange && a("view-change", ((n) => {
|
|
2441
|
+
e.onViewChange?.(n.detail.viewName);
|
|
2442
|
+
})), e.onClose && a("customizer:close", (() => {
|
|
2443
|
+
e.onClose?.();
|
|
2444
|
+
})), e.onSave && a("customizer:save", ((n) => {
|
|
2445
|
+
e.onSave?.(n.detail);
|
|
2446
|
+
})), e.initialDesign && t.addEventListener(
|
|
2244
2447
|
"ready",
|
|
2245
2448
|
() => {
|
|
2246
|
-
e.initialDesign &&
|
|
2449
|
+
e.initialDesign && t.setDesign(e.initialDesign);
|
|
2247
2450
|
},
|
|
2248
2451
|
{ once: !0 }
|
|
2249
2452
|
), e.debug && (console.log("[Customizer SDK] Initialized with options:", e), console.log("[Customizer SDK] Instance:", o)), o;
|