@varianta/sdk 0.1.6 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs.js +5 -3
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.esm.js +755 -414
- 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 +204 -185
- package/dist/react.esm.js.map +1 -1
- package/dist/types/react/Customizer.d.ts +2 -3
- package/dist/types/react/Customizer.d.ts.map +1 -1
- package/dist/types/react/useCustomizer.d.ts +19 -3
- package/dist/types/react/useCustomizer.d.ts.map +1 -1
- package/dist/types/types/index.d.ts +43 -3
- 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
|
@@ -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();
|
|
82
|
-
|
|
83
|
-
this.bgImageElement =
|
|
84
|
-
},
|
|
85
|
-
this.bgImageElement = null, console.error("Failed to load background image:",
|
|
86
|
-
},
|
|
81
|
+
const t = e.url, i = new Image();
|
|
82
|
+
i.crossOrigin = "anonymous", i.onload = () => {
|
|
83
|
+
this.bgImageElement = i, this.requestRender();
|
|
84
|
+
}, i.onerror = () => {
|
|
85
|
+
this.bgImageElement = null, console.error("Failed to load background image:", t), this.requestRender();
|
|
86
|
+
}, i.src = t;
|
|
87
87
|
} else e?.url || (this.bgImageElement = null);
|
|
88
88
|
this.requestRender();
|
|
89
89
|
}
|
|
@@ -129,9 +129,9 @@ class N {
|
|
|
129
129
|
getCenteredPan(e) {
|
|
130
130
|
if (!this.artboard)
|
|
131
131
|
return null;
|
|
132
|
-
const { width: t, height: i } = this.artboard, s = window.devicePixelRatio || 1,
|
|
132
|
+
const { width: t, height: i } = this.artboard, s = window.devicePixelRatio || 1, n = this.canvas.width / s, o = this.canvas.height / s;
|
|
133
133
|
return {
|
|
134
|
-
x: (
|
|
134
|
+
x: (n / e - t) / 2,
|
|
135
135
|
y: (o / e - i) / 2
|
|
136
136
|
};
|
|
137
137
|
}
|
|
@@ -147,11 +147,11 @@ class N {
|
|
|
147
147
|
fitToView() {
|
|
148
148
|
if (!this.artboard)
|
|
149
149
|
return;
|
|
150
|
-
const e = 0, { width: t, height: i } = this.artboard, s = window.devicePixelRatio || 1,
|
|
151
|
-
if (
|
|
150
|
+
const e = 0, { width: t, height: i } = this.artboard, s = window.devicePixelRatio || 1, n = this.canvas.width / s - e * 2, o = this.canvas.height / s - e * 2;
|
|
151
|
+
if (n <= 0 || o <= 0)
|
|
152
152
|
return;
|
|
153
|
-
const
|
|
154
|
-
this.zoom = d, this.pan = { x:
|
|
153
|
+
const a = n / t, r = o / i, d = Math.min(a, r), l = this.canvas.width / s, c = this.canvas.height / s, p = (l / d - t) / 2, u = (c / d - i) / 2;
|
|
154
|
+
this.zoom = d, this.pan = { x: p, y: u }, this.requestRender();
|
|
155
155
|
}
|
|
156
156
|
/**
|
|
157
157
|
* Set the selected area
|
|
@@ -181,8 +181,8 @@ class N {
|
|
|
181
181
|
* Convert screen coordinates to canvas coordinates
|
|
182
182
|
*/
|
|
183
183
|
screenToCanvas(e, t) {
|
|
184
|
-
const i = window.devicePixelRatio || 1, s = e / i,
|
|
185
|
-
return { x: o, y:
|
|
184
|
+
const i = window.devicePixelRatio || 1, s = e / i, n = t / i, o = s / this.zoom - this.pan.x, a = n / this.zoom - this.pan.y;
|
|
185
|
+
return { x: o, y: a };
|
|
186
186
|
}
|
|
187
187
|
/**
|
|
188
188
|
* Get content bounds for an area (in artboard coordinates)
|
|
@@ -194,14 +194,14 @@ class N {
|
|
|
194
194
|
const i = this.contents.get(e);
|
|
195
195
|
if (!i)
|
|
196
196
|
return null;
|
|
197
|
-
const { location: s } = t, { x:
|
|
197
|
+
const { location: s } = t, { x: n, y: o, width: a, height: r } = s;
|
|
198
198
|
if (i.type === "text") {
|
|
199
|
-
const d = i.offset || { x: 0, y: 0 }, l = i.scale ?? 1, c = i.rotation ?? 0,
|
|
199
|
+
const d = i.offset || { x: 0, y: 0 }, l = i.scale ?? 1, c = i.rotation ?? 0, p = a * l, u = r * l, g = n + a / 2 + d.x, m = o + r / 2 + d.y;
|
|
200
200
|
return {
|
|
201
|
-
x: g -
|
|
202
|
-
y: m -
|
|
203
|
-
width:
|
|
204
|
-
height:
|
|
201
|
+
x: g - p / 2,
|
|
202
|
+
y: m - u / 2,
|
|
203
|
+
width: p,
|
|
204
|
+
height: u,
|
|
205
205
|
rotation: c,
|
|
206
206
|
centerX: g,
|
|
207
207
|
centerY: m
|
|
@@ -210,18 +210,18 @@ class N {
|
|
|
210
210
|
const d = this.loadedImages.get(e);
|
|
211
211
|
if (!d)
|
|
212
212
|
return null;
|
|
213
|
-
const l = i.offset || { x: 0, y: 0 }, c = i.scale ?? 1,
|
|
213
|
+
const l = i.offset || { x: 0, y: 0 }, c = i.scale ?? 1, p = i.rotation ?? 0, u = d.naturalWidth / d.naturalHeight, g = a / r;
|
|
214
214
|
let m, v;
|
|
215
|
-
|
|
216
|
-
const b = m * c,
|
|
215
|
+
u > g ? (m = a, v = a / u) : (v = r, m = r * u);
|
|
216
|
+
const b = m * c, w = v * c, y = n + a / 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:
|
|
222
|
-
rotation:
|
|
223
|
-
centerX:
|
|
224
|
-
centerY:
|
|
221
|
+
height: w,
|
|
222
|
+
rotation: p,
|
|
223
|
+
centerX: y,
|
|
224
|
+
centerY: x
|
|
225
225
|
};
|
|
226
226
|
}
|
|
227
227
|
return null;
|
|
@@ -234,8 +234,8 @@ class N {
|
|
|
234
234
|
if (!this.areaSelectionEnabled)
|
|
235
235
|
return null;
|
|
236
236
|
for (let i = this.areas.length - 1; i >= 0; i--) {
|
|
237
|
-
const s = this.areas[i],
|
|
238
|
-
if (
|
|
237
|
+
const s = this.areas[i], n = this.getContentBounds(s.id);
|
|
238
|
+
if (n && this.isPointInBounds(e, t, n))
|
|
239
239
|
return s.id;
|
|
240
240
|
}
|
|
241
241
|
return null;
|
|
@@ -248,8 +248,8 @@ class N {
|
|
|
248
248
|
if (!this.areaSelectionEnabled)
|
|
249
249
|
return null;
|
|
250
250
|
for (let i = this.areas.length - 1; i >= 0; i--) {
|
|
251
|
-
const s = this.areas[i], { x:
|
|
252
|
-
if (e >=
|
|
251
|
+
const s = this.areas[i], { x: n, y: o, width: a, height: r } = s.location;
|
|
252
|
+
if (e >= n && e <= n + a && t >= o && t <= o + r)
|
|
253
253
|
return s.id;
|
|
254
254
|
}
|
|
255
255
|
return null;
|
|
@@ -258,8 +258,8 @@ class N {
|
|
|
258
258
|
* Test if a point is inside rotated bounds
|
|
259
259
|
*/
|
|
260
260
|
isPointInBounds(e, t, i) {
|
|
261
|
-
const { centerX: s, centerY:
|
|
262
|
-
return g >= -o / 2 && g <= o / 2 && m >= -
|
|
261
|
+
const { centerX: s, centerY: n, width: o, height: a, rotation: r } = i, d = -r * Math.PI / 180, l = Math.cos(d), c = Math.sin(d), p = e - s, u = t - n, g = p * l - u * c, m = p * c + u * l;
|
|
262
|
+
return g >= -o / 2 && g <= o / 2 && m >= -a / 2 && m <= a / 2;
|
|
263
263
|
}
|
|
264
264
|
/**
|
|
265
265
|
* Get handle positions for the selected content
|
|
@@ -274,19 +274,19 @@ class N {
|
|
|
274
274
|
const s = this.getContentBounds(e);
|
|
275
275
|
if (!s)
|
|
276
276
|
return null;
|
|
277
|
-
const { centerX:
|
|
278
|
-
x:
|
|
279
|
-
y: o + b * g +
|
|
277
|
+
const { centerX: n, centerY: o, width: a, height: r, rotation: d } = s, l = 6 / this.zoom, c = 30 / this.zoom, p = d * Math.PI / 180, u = Math.cos(p), g = Math.sin(p), m = (b, w) => ({
|
|
278
|
+
x: n + b * u - w * g,
|
|
279
|
+
y: o + b * g + w * u
|
|
280
280
|
}), v = [];
|
|
281
281
|
if (i.type === "image" && t.imageOptions?.allowScaling || i.type === "text" && t.textOptions?.allowScaling) {
|
|
282
|
-
const b =
|
|
283
|
-
v.push({ type: "nw", ...
|
|
284
|
-
const
|
|
285
|
-
v.push({ type: "ne", ...
|
|
286
|
-
const C = m(b,
|
|
282
|
+
const b = a / 2, w = r / 2, y = m(-b, -w);
|
|
283
|
+
v.push({ type: "nw", ...y, radius: l });
|
|
284
|
+
const x = m(b, -w);
|
|
285
|
+
v.push({ type: "ne", ...x, radius: l });
|
|
286
|
+
const C = m(b, w);
|
|
287
287
|
v.push({ type: "se", ...C, radius: l });
|
|
288
|
-
const
|
|
289
|
-
v.push({ type: "sw", ...
|
|
288
|
+
const M = m(-b, w);
|
|
289
|
+
v.push({ type: "sw", ...M, radius: l });
|
|
290
290
|
}
|
|
291
291
|
if (i.type === "image" && t.imageOptions?.allowRotation || i.type === "text" && t.textOptions?.allowRotation) {
|
|
292
292
|
const b = m(0, -r / 2 - c);
|
|
@@ -301,10 +301,10 @@ class N {
|
|
|
301
301
|
const s = this.getContentHandlePositions(i);
|
|
302
302
|
if (!s)
|
|
303
303
|
return null;
|
|
304
|
-
for (const
|
|
305
|
-
const o = e -
|
|
306
|
-
if (Math.sqrt(o * o +
|
|
307
|
-
return
|
|
304
|
+
for (const n of s) {
|
|
305
|
+
const o = e - n.x, a = t - n.y;
|
|
306
|
+
if (Math.sqrt(o * o + a * a) <= n.radius * 2)
|
|
307
|
+
return n.type;
|
|
308
308
|
}
|
|
309
309
|
return null;
|
|
310
310
|
}
|
|
@@ -344,15 +344,15 @@ class N {
|
|
|
344
344
|
checkSafeAreaViolations() {
|
|
345
345
|
if (!this.safeArea || !this.artboard)
|
|
346
346
|
return [];
|
|
347
|
-
const e = [], { top: t, right: i, bottom: s, left:
|
|
347
|
+
const e = [], { top: t, right: i, bottom: s, left: n } = this.safeArea, o = n, a = t, r = this.artboard.width - i, 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
351
|
const c = this.getContentBounds(l.id);
|
|
352
352
|
if (!c)
|
|
353
353
|
continue;
|
|
354
|
-
const { centerX:
|
|
355
|
-
(
|
|
354
|
+
const { centerX: p, centerY: u, width: g, height: m, rotation: v } = c, b = v * Math.PI / 180, w = Math.abs(Math.cos(b)), y = Math.abs(Math.sin(b)), x = g * w + m * y, C = g * y + m * w, M = p - x / 2, R = u - C / 2, U = p + x / 2, D = u + C / 2;
|
|
355
|
+
(M < o || R < a || U > r || D > d) && e.push(l.id);
|
|
356
356
|
}
|
|
357
357
|
return this.safeAreaViolations = new Set(e), this.requestRender(), e;
|
|
358
358
|
}
|
|
@@ -381,16 +381,20 @@ class N {
|
|
|
381
381
|
throw new Error("No artboard configured");
|
|
382
382
|
const { width: e, height: t, backgroundColor: i } = this.artboard, s = document.createElement("canvas");
|
|
383
383
|
s.width = e, s.height = t;
|
|
384
|
-
const
|
|
385
|
-
if (!
|
|
384
|
+
const n = s.getContext("2d");
|
|
385
|
+
if (!n)
|
|
386
386
|
throw new Error("Failed to get 2D context for export");
|
|
387
|
-
|
|
387
|
+
n.fillStyle = i, n.fillRect(0, 0, e, t), this.backgroundImage?.position === "behind" && this.bgImageElement && this.drawBackgroundImageOnContext(n, e, t);
|
|
388
388
|
for (const o of this.areas)
|
|
389
|
-
this.drawAreaContentOnContext(
|
|
390
|
-
return this.backgroundImage?.position === "overlay" && this.bgImageElement && this.drawBackgroundImageOnContext(
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
389
|
+
this.drawAreaContentOnContext(n, o);
|
|
390
|
+
return this.backgroundImage?.position === "overlay" && this.bgImageElement && this.drawBackgroundImageOnContext(n, e, t), new Promise((o, a) => {
|
|
391
|
+
try {
|
|
392
|
+
s.toBlob((r) => {
|
|
393
|
+
r ? o(r) : a(new Error("Failed to create PNG blob"));
|
|
394
|
+
}, "image/png", 1);
|
|
395
|
+
} catch (r) {
|
|
396
|
+
r instanceof DOMException && r.name === "SecurityError" ? a(new Error("Cannot export: images failed to load with CORS. Check CDN configuration.")) : a(r);
|
|
397
|
+
}
|
|
394
398
|
});
|
|
395
399
|
}
|
|
396
400
|
/**
|
|
@@ -401,7 +405,7 @@ class N {
|
|
|
401
405
|
if (t.type === "image" && !this.loadedImages.has(e) && !this.loadingImages.has(e)) {
|
|
402
406
|
this.loadingImages.add(e);
|
|
403
407
|
const i = new Image();
|
|
404
|
-
i.onload = () => {
|
|
408
|
+
i.crossOrigin = "anonymous", i.onload = () => {
|
|
405
409
|
this.loadedImages.set(e, i), this.loadingImages.delete(e), this.requestRender();
|
|
406
410
|
}, i.onerror = () => {
|
|
407
411
|
this.loadingImages.delete(e), console.error("Failed to load image for area:", e);
|
|
@@ -422,17 +426,17 @@ class N {
|
|
|
422
426
|
e.save();
|
|
423
427
|
const i = window.devicePixelRatio || 1;
|
|
424
428
|
e.scale(i, i), e.scale(this.zoom, this.zoom), e.translate(this.pan.x, this.pan.y);
|
|
425
|
-
const { width: s, height:
|
|
426
|
-
e.fillStyle = o, e.fillRect(0, 0, s,
|
|
427
|
-
for (const
|
|
428
|
-
this.renderAreaWithContent(
|
|
429
|
+
const { width: s, height: n, backgroundColor: o } = this.artboard;
|
|
430
|
+
e.fillStyle = o, e.fillRect(0, 0, s, n), e.strokeStyle = "#d1d5db", e.lineWidth = 1 / this.zoom, e.strokeRect(0, 0, s, n), this.backgroundImage?.position === "behind" && this.renderBackgroundImage();
|
|
431
|
+
for (const a of this.areas)
|
|
432
|
+
this.renderAreaWithContent(a);
|
|
429
433
|
this.backgroundImage?.position === "overlay" && this.renderBackgroundImage(), this.renderSafeAreaGuide();
|
|
430
|
-
for (const
|
|
431
|
-
const r = this.areas.find((
|
|
434
|
+
for (const a of this.safeAreaViolations) {
|
|
435
|
+
const r = this.areas.find((u) => u.id === a);
|
|
432
436
|
if (!r)
|
|
433
437
|
continue;
|
|
434
|
-
const { x: d, y: l, width: c, height:
|
|
435
|
-
e.save(), e.fillStyle = "rgba(239, 68, 68, 0.15)", e.fillRect(d, l, c,
|
|
438
|
+
const { x: d, y: l, width: c, height: p } = r.location;
|
|
439
|
+
e.save(), e.fillStyle = "rgba(239, 68, 68, 0.15)", e.fillRect(d, l, c, p), e.restore();
|
|
436
440
|
}
|
|
437
441
|
this.selectedAreaId && this.renderSelectionOverlay(this.selectedAreaId), e.restore();
|
|
438
442
|
}
|
|
@@ -445,33 +449,33 @@ class N {
|
|
|
445
449
|
const l = this.areas.find((v) => v.id === e);
|
|
446
450
|
if (!l)
|
|
447
451
|
return;
|
|
448
|
-
const { ctx: c } = this, { x:
|
|
449
|
-
c.save(), c.strokeStyle = "#3b82f6", c.lineWidth = 2 / this.zoom, c.setLineDash([]), c.strokeRect(
|
|
452
|
+
const { ctx: c } = this, { x: p, y: u, width: g, height: m } = l.location;
|
|
453
|
+
c.save(), c.strokeStyle = "#3b82f6", c.lineWidth = 2 / this.zoom, c.setLineDash([]), c.strokeRect(p, u, g, m), c.restore();
|
|
450
454
|
return;
|
|
451
455
|
}
|
|
452
|
-
const { ctx: i } = this, { centerX: s, centerY:
|
|
453
|
-
i.save(), i.translate(s,
|
|
456
|
+
const { ctx: i } = this, { centerX: s, centerY: n, width: o, height: a, rotation: r } = t;
|
|
457
|
+
i.save(), i.translate(s, n), i.rotate(r * Math.PI / 180), i.strokeStyle = "#3b82f6", i.lineWidth = 2 / this.zoom, i.setLineDash([]), i.strokeRect(-o / 2, -a / 2, o, a), i.restore();
|
|
454
458
|
const d = this.getContentHandlePositions(e);
|
|
455
459
|
if (d)
|
|
456
460
|
for (const l of d) {
|
|
457
461
|
i.save();
|
|
458
|
-
const c = this.hoveredHandle === l.type,
|
|
462
|
+
const c = this.hoveredHandle === l.type, p = c ? "#dbeafe" : "#ffffff", u = c ? "#2563eb" : "#3b82f6";
|
|
459
463
|
if (l.type === "rotate") {
|
|
460
|
-
const g = this.getRotatedPoint(s,
|
|
461
|
-
i.beginPath(), i.strokeStyle =
|
|
464
|
+
const g = this.getRotatedPoint(s, n - a / 2, s, n, r);
|
|
465
|
+
i.beginPath(), i.strokeStyle = u, i.lineWidth = 1 / this.zoom, i.setLineDash([4 / this.zoom, 4 / this.zoom]), i.moveTo(g.x, g.y), i.lineTo(l.x, l.y), i.stroke(), i.setLineDash([]), i.beginPath(), i.fillStyle = p, i.strokeStyle = u, i.lineWidth = 2 / this.zoom, i.arc(l.x, l.y, l.radius, 0, Math.PI * 2), i.fill(), i.stroke(), i.beginPath(), i.strokeStyle = u, i.lineWidth = 1.5 / this.zoom, i.arc(l.x, l.y, l.radius * 0.5, -Math.PI * 0.8, Math.PI * 0.5), i.stroke();
|
|
462
466
|
} else
|
|
463
|
-
i.fillStyle =
|
|
467
|
+
i.fillStyle = p, i.strokeStyle = u, i.lineWidth = 2 / this.zoom, i.fillRect(l.x - l.radius, l.y - l.radius, l.radius * 2, l.radius * 2), i.strokeRect(l.x - l.radius, l.y - l.radius, l.radius * 2, l.radius * 2);
|
|
464
468
|
i.restore();
|
|
465
469
|
}
|
|
466
470
|
}
|
|
467
471
|
/**
|
|
468
472
|
* Rotate a point around a center
|
|
469
473
|
*/
|
|
470
|
-
getRotatedPoint(e, t, i, s,
|
|
471
|
-
const o =
|
|
474
|
+
getRotatedPoint(e, t, i, s, n) {
|
|
475
|
+
const o = n * Math.PI / 180, a = Math.cos(o), r = Math.sin(o), d = e - i, l = t - s;
|
|
472
476
|
return {
|
|
473
|
-
x: i + d *
|
|
474
|
-
y: s + d * r + l *
|
|
477
|
+
x: i + d * a - l * r,
|
|
478
|
+
y: s + d * r + l * a
|
|
475
479
|
};
|
|
476
480
|
}
|
|
477
481
|
/**
|
|
@@ -482,9 +486,9 @@ class N {
|
|
|
482
486
|
return;
|
|
483
487
|
const { ctx: e } = this, { opacity: t } = this.backgroundImage, { width: i, height: s } = this.artboard;
|
|
484
488
|
e.save(), e.globalAlpha = t;
|
|
485
|
-
const
|
|
486
|
-
let
|
|
487
|
-
|
|
489
|
+
const n = this.bgImageElement.naturalWidth / this.bgImageElement.naturalHeight, o = i / s;
|
|
490
|
+
let a, r, d, l;
|
|
491
|
+
n > o ? (r = s, a = s * n, d = (i - a) / 2, l = 0) : (a = i, r = i / n, d = 0, l = (s - r) / 2), e.drawImage(this.bgImageElement, d, l, a, r), e.restore();
|
|
488
492
|
}
|
|
489
493
|
/**
|
|
490
494
|
* Render safe area guide as dashed green rectangle
|
|
@@ -492,17 +496,17 @@ class N {
|
|
|
492
496
|
renderSafeAreaGuide() {
|
|
493
497
|
if (!this.safeArea || !this.artboard)
|
|
494
498
|
return;
|
|
495
|
-
const { ctx: e } = this, { top: t, right: i, bottom: s, left:
|
|
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: t, right: i, bottom: s, left: n } = this.safeArea, o = n, a = t, r = this.artboard.width - n - i, d = this.artboard.height - t - 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, a, 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: t } = this, { location: i, backgroundColor: s } = e, { x:
|
|
503
|
-
s && (t.fillStyle = s, t.fillRect(
|
|
506
|
+
const { ctx: t } = this, { location: i, backgroundColor: s } = e, { x: n, y: o, width: a, height: r } = i;
|
|
507
|
+
s && (t.fillStyle = s, t.fillRect(n, o, a, 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 && (t.save(), t.strokeStyle = e.borderColor || "#3b82f6", t.lineWidth = 1 / this.zoom, t.setLineDash([4 / this.zoom, 4 / this.zoom]), t.strokeRect(
|
|
509
|
+
d && (d.type === "text" ? this.renderTextContent(e, d) : d.type === "image" && this.renderImageContent(e, d)), this.showAreaBorders && e.showBorder && (t.save(), t.strokeStyle = e.borderColor || "#3b82f6", t.lineWidth = 1 / this.zoom, t.setLineDash([4 / this.zoom, 4 / this.zoom]), t.strokeRect(n, o, a, r), t.setLineDash([]), t.restore());
|
|
506
510
|
}
|
|
507
511
|
/**
|
|
508
512
|
* Render text content in an area
|
|
@@ -510,15 +514,15 @@ class N {
|
|
|
510
514
|
renderTextContent(e, t) {
|
|
511
515
|
if (!t.text.trim())
|
|
512
516
|
return;
|
|
513
|
-
const { ctx: i } = this, { location: s } = e, { x:
|
|
514
|
-
i.save(), i.beginPath(), i.rect(
|
|
517
|
+
const { ctx: i } = this, { location: s } = e, { x: n, y: o, width: a, height: r } = s, d = t.offset || { x: 0, y: 0 }, l = t.scale ?? 1, c = t.rotation ?? 0, p = n + a / 2 + d.x, u = o + r / 2 + d.y;
|
|
518
|
+
i.save(), i.beginPath(), i.rect(n, o, a, r), i.clip(), i.translate(p, u), i.rotate(c * Math.PI / 180), i.scale(l, l), i.font = `${t.size}px ${t.font}, sans-serif`, i.fillStyle = t.color, i.textBaseline = "middle";
|
|
515
519
|
let g;
|
|
516
520
|
switch (t.align) {
|
|
517
521
|
case "left":
|
|
518
|
-
i.textAlign = "left", g = -
|
|
522
|
+
i.textAlign = "left", g = -a / 2 + 10;
|
|
519
523
|
break;
|
|
520
524
|
case "right":
|
|
521
|
-
i.textAlign = "right", g =
|
|
525
|
+
i.textAlign = "right", g = a / 2 - 10;
|
|
522
526
|
break;
|
|
523
527
|
case "center":
|
|
524
528
|
default:
|
|
@@ -534,13 +538,13 @@ class N {
|
|
|
534
538
|
const i = this.loadedImages.get(e.id);
|
|
535
539
|
if (!i)
|
|
536
540
|
return;
|
|
537
|
-
const { ctx: s } = this, { location:
|
|
538
|
-
s.save(), s.beginPath(), s.rect(o,
|
|
539
|
-
const
|
|
541
|
+
const { ctx: s } = this, { location: n } = e, { x: o, y: a, width: r, height: d } = n, l = t.offset || { x: 0, y: 0 }, c = t.scale ?? 1, p = t.rotation ?? 0;
|
|
542
|
+
s.save(), s.beginPath(), s.rect(o, a, r, d), s.clip();
|
|
543
|
+
const u = i.naturalWidth / i.naturalHeight, g = r / d;
|
|
540
544
|
let m, v;
|
|
541
|
-
|
|
542
|
-
const b = m * c,
|
|
543
|
-
s.translate(
|
|
545
|
+
u > g ? (m = r, v = r / u) : (v = d, m = d * u);
|
|
546
|
+
const b = m * c, w = v * c, y = o + r / 2 + l.x, x = a + d / 2 + l.y;
|
|
547
|
+
s.translate(y, x), s.rotate(p * Math.PI / 180), s.drawImage(i, -b / 2, -w / 2, b, w), s.restore();
|
|
544
548
|
}
|
|
545
549
|
/**
|
|
546
550
|
* Draw background image on a specific context (for export)
|
|
@@ -550,30 +554,30 @@ class N {
|
|
|
550
554
|
return;
|
|
551
555
|
const { opacity: s } = this.backgroundImage;
|
|
552
556
|
e.save(), e.globalAlpha = s;
|
|
553
|
-
const
|
|
554
|
-
let
|
|
555
|
-
|
|
557
|
+
const n = this.bgImageElement.naturalWidth / this.bgImageElement.naturalHeight, o = t / i;
|
|
558
|
+
let a, r, d, l;
|
|
559
|
+
n > o ? (r = i, a = i * n, d = (t - a) / 2, l = 0) : (a = t, r = t / n, d = 0, l = (i - r) / 2), e.drawImage(this.bgImageElement, d, l, a, r), e.restore();
|
|
556
560
|
}
|
|
557
561
|
/**
|
|
558
562
|
* Draw area content on a specific context (for export)
|
|
559
563
|
*/
|
|
560
564
|
drawAreaContentOnContext(e, t) {
|
|
561
|
-
const { location: i, backgroundColor: s } = t, { x:
|
|
562
|
-
s && (e.fillStyle = s, e.fillRect(
|
|
565
|
+
const { location: i, backgroundColor: s } = t, { x: n, y: o, width: a, height: r } = i;
|
|
566
|
+
s && (e.fillStyle = s, e.fillRect(n, o, a, r));
|
|
563
567
|
const d = this.contents.get(t.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 }, c = d.scale ?? 1,
|
|
569
|
-
e.save(), e.beginPath(), e.rect(
|
|
572
|
+
const l = d.offset || { x: 0, y: 0 }, c = d.scale ?? 1, p = d.rotation ?? 0, u = n + a / 2 + l.x, g = o + r / 2 + l.y;
|
|
573
|
+
e.save(), e.beginPath(), e.rect(n, o, a, r), e.clip(), e.translate(u, g), e.rotate(p * Math.PI / 180), e.scale(c, c), e.font = `${d.size}px ${d.font}, sans-serif`, e.fillStyle = d.color, e.textBaseline = "middle";
|
|
570
574
|
let m;
|
|
571
575
|
switch (d.align) {
|
|
572
576
|
case "left":
|
|
573
|
-
e.textAlign = "left", m = -
|
|
577
|
+
e.textAlign = "left", m = -a / 2 + 10;
|
|
574
578
|
break;
|
|
575
579
|
case "right":
|
|
576
|
-
e.textAlign = "right", m =
|
|
580
|
+
e.textAlign = "right", m = a / 2 - 10;
|
|
577
581
|
break;
|
|
578
582
|
case "center":
|
|
579
583
|
default:
|
|
@@ -585,13 +589,13 @@ class N {
|
|
|
585
589
|
const l = this.loadedImages.get(t.id);
|
|
586
590
|
if (!l)
|
|
587
591
|
return;
|
|
588
|
-
const c = d.offset || { x: 0, y: 0 },
|
|
589
|
-
e.save(), e.beginPath(), e.rect(
|
|
590
|
-
const g = l.naturalWidth / l.naturalHeight, m =
|
|
592
|
+
const c = d.offset || { x: 0, y: 0 }, p = d.scale ?? 1, u = d.rotation ?? 0;
|
|
593
|
+
e.save(), e.beginPath(), e.rect(n, o, a, r), e.clip();
|
|
594
|
+
const g = l.naturalWidth / l.naturalHeight, m = a / r;
|
|
591
595
|
let v, b;
|
|
592
|
-
g > m ? (v =
|
|
593
|
-
const
|
|
594
|
-
e.translate(
|
|
596
|
+
g > m ? (v = a, b = a / g) : (b = r, v = r * g);
|
|
597
|
+
const w = v * p, y = b * p, x = n + a / 2 + c.x, C = o + r / 2 + c.y;
|
|
598
|
+
e.translate(x, C), e.rotate(u * Math.PI / 180), e.drawImage(l, -w / 2, -y / 2, w, y), e.restore();
|
|
595
599
|
}
|
|
596
600
|
}
|
|
597
601
|
}
|
|
@@ -602,7 +606,7 @@ class N {
|
|
|
602
606
|
this.stop(), this.bgImageElement = null, this.loadedImages.clear(), this.loadingImages.clear(), this.eventListeners.clear();
|
|
603
607
|
}
|
|
604
608
|
}
|
|
605
|
-
function
|
|
609
|
+
function S(f) {
|
|
606
610
|
const e = f.textOptions;
|
|
607
611
|
return {
|
|
608
612
|
type: "text",
|
|
@@ -613,14 +617,14 @@ function E(f) {
|
|
|
613
617
|
align: e?.defaultAlign || "center"
|
|
614
618
|
};
|
|
615
619
|
}
|
|
616
|
-
function
|
|
620
|
+
function E(f) {
|
|
617
621
|
return f.contentType === "text" || f.contentType === "both";
|
|
618
622
|
}
|
|
619
623
|
function T(f) {
|
|
620
624
|
return f.contentType === "image" || f.contentType === "both";
|
|
621
625
|
}
|
|
622
|
-
const
|
|
623
|
-
class
|
|
626
|
+
const V = 50;
|
|
627
|
+
class F {
|
|
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({
|
|
626
630
|
state: this.cloneState(e),
|
|
@@ -671,7 +675,7 @@ class V {
|
|
|
671
675
|
state: this.cloneState(e),
|
|
672
676
|
timestamp: Date.now(),
|
|
673
677
|
description: t
|
|
674
|
-
}), this.history.length >
|
|
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];
|
|
@@ -694,14 +698,14 @@ class V {
|
|
|
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
707
|
for (const t of this.areas)
|
|
704
|
-
|
|
708
|
+
E(t) && e.set(t.id, S(t));
|
|
705
709
|
return e;
|
|
706
710
|
}
|
|
707
711
|
getContents() {
|
|
@@ -711,7 +715,7 @@ class k {
|
|
|
711
715
|
return this.contents.get(e);
|
|
712
716
|
}
|
|
713
717
|
setTextContent(e, t) {
|
|
714
|
-
const i = this.contents.get(e), s = this.areas.find((o) => o.id === e),
|
|
718
|
+
const i = this.contents.get(e), s = this.areas.find((o) => o.id === e), n = s ? S(s) : {
|
|
715
719
|
type: "text",
|
|
716
720
|
text: "",
|
|
717
721
|
font: "Inter",
|
|
@@ -719,19 +723,27 @@ class k {
|
|
|
719
723
|
color: "#000000",
|
|
720
724
|
align: "center"
|
|
721
725
|
};
|
|
722
|
-
i?.type === "text" ? this.contents.set(e, { ...i, ...t }) : this.contents.set(e, { ...
|
|
726
|
+
i?.type === "text" ? this.contents.set(e, { ...i, ...t }) : this.contents.set(e, { ...n, ...t }), this.notify();
|
|
723
727
|
}
|
|
724
|
-
setImageContent(e, t, i) {
|
|
725
|
-
const
|
|
728
|
+
setImageContent(e, t, i, s) {
|
|
729
|
+
const n = this.contents.get(e), o = {
|
|
726
730
|
type: "image",
|
|
727
731
|
dataUrl: t,
|
|
728
|
-
filename: i
|
|
732
|
+
filename: i,
|
|
733
|
+
assetId: s?.assetId,
|
|
734
|
+
assetUrl: s?.assetUrl,
|
|
735
|
+
// Preserve transforms from existing image content
|
|
736
|
+
...n?.type === "image" && {
|
|
737
|
+
offset: n.offset,
|
|
738
|
+
scale: n.scale,
|
|
739
|
+
rotation: n.rotation
|
|
740
|
+
}
|
|
729
741
|
};
|
|
730
|
-
this.contents.set(e,
|
|
742
|
+
this.contents.set(e, o), this.notify();
|
|
731
743
|
}
|
|
732
744
|
clearContent(e) {
|
|
733
745
|
const t = this.areas.find((i) => i.id === e);
|
|
734
|
-
t &&
|
|
746
|
+
t && E(t) ? this.contents.set(e, S(t)) : this.contents.delete(e), this.notify();
|
|
735
747
|
}
|
|
736
748
|
setContentOffset(e, t) {
|
|
737
749
|
const i = this.contents.get(e);
|
|
@@ -776,7 +788,7 @@ class k {
|
|
|
776
788
|
}
|
|
777
789
|
}
|
|
778
790
|
}
|
|
779
|
-
class
|
|
791
|
+
class z {
|
|
780
792
|
constructor() {
|
|
781
793
|
this.listeners = /* @__PURE__ */ new Map();
|
|
782
794
|
}
|
|
@@ -823,9 +835,9 @@ class I {
|
|
|
823
835
|
const i = this.listeners.get(e);
|
|
824
836
|
if (i) {
|
|
825
837
|
const s = Array.from(i);
|
|
826
|
-
for (const
|
|
838
|
+
for (const n of s)
|
|
827
839
|
try {
|
|
828
|
-
|
|
840
|
+
n(t);
|
|
829
841
|
} catch (o) {
|
|
830
842
|
console.error(`Error in event handler for "${String(e)}":`, o);
|
|
831
843
|
}
|
|
@@ -849,7 +861,7 @@ class I {
|
|
|
849
861
|
return this.listeners.get(e)?.size ?? 0;
|
|
850
862
|
}
|
|
851
863
|
}
|
|
852
|
-
class O extends
|
|
864
|
+
class O extends z {
|
|
853
865
|
constructor() {
|
|
854
866
|
super(...arguments), this.activeTouches = /* @__PURE__ */ new Map(), this.initialPinchDistance = null, this.lastPinchCenter = null, this.isTwoFingerGesture = !1;
|
|
855
867
|
}
|
|
@@ -881,14 +893,14 @@ class O extends I {
|
|
|
881
893
|
}
|
|
882
894
|
if (this.activeTouches.size === 2 && this.initialPinchDistance !== null && this.lastPinchCenter) {
|
|
883
895
|
e.preventDefault();
|
|
884
|
-
const [t, i] = Array.from(this.activeTouches.values()), s = this.getDistance(t, i),
|
|
896
|
+
const [t, i] = Array.from(this.activeTouches.values()), s = this.getDistance(t, i), n = this.getCenter(t, i), o = s / this.initialPinchDistance;
|
|
885
897
|
this.emit("pinch", {
|
|
886
898
|
scale: o,
|
|
887
|
-
centerX:
|
|
888
|
-
centerY:
|
|
899
|
+
centerX: n.x,
|
|
900
|
+
centerY: n.y
|
|
889
901
|
});
|
|
890
|
-
const
|
|
891
|
-
(Math.abs(
|
|
902
|
+
const a = n.x - this.lastPinchCenter.x, r = n.y - this.lastPinchCenter.y;
|
|
903
|
+
(Math.abs(a) > 0.5 || Math.abs(r) > 0.5) && this.emit("pan", { deltaX: a, deltaY: r }), this.initialPinchDistance = s, this.lastPinchCenter = n;
|
|
892
904
|
}
|
|
893
905
|
}
|
|
894
906
|
handleTouchEnd(e) {
|
|
@@ -912,24 +924,24 @@ class O extends I {
|
|
|
912
924
|
};
|
|
913
925
|
}
|
|
914
926
|
}
|
|
915
|
-
class
|
|
927
|
+
class $ extends z {
|
|
916
928
|
constructor(e, t, i, s) {
|
|
917
|
-
super(), this.interactionState = { mode: "idle" }, this.initialPinchZoom = 1, this.canvas = e, this.engine = t, this.getContents = i, this.getSelectedAreaId = s, this.gestureHandler = new O(), 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:
|
|
918
|
-
const o = Math.max(0.1, Math.min(5, this.initialPinchZoom *
|
|
929
|
+
super(), this.interactionState = { mode: "idle" }, this.initialPinchZoom = 1, this.canvas = e, this.engine = t, this.getContents = i, this.getSelectedAreaId = s, this.gestureHandler = new O(), 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: n }) => {
|
|
930
|
+
const o = Math.max(0.1, Math.min(5, this.initialPinchZoom * n));
|
|
919
931
|
this.emit("zoom", { zoom: o });
|
|
920
|
-
}), this.gestureHandler.on("pan", ({ deltaX:
|
|
921
|
-
const
|
|
932
|
+
}), this.gestureHandler.on("pan", ({ deltaX: n, deltaY: o }) => {
|
|
933
|
+
const a = this.engine.getPan(), r = this.engine.getZoom();
|
|
922
934
|
this.emit("pan", {
|
|
923
935
|
pan: {
|
|
924
|
-
x:
|
|
925
|
-
y:
|
|
936
|
+
x: a.x + n / r,
|
|
937
|
+
y: a.y + o / r
|
|
926
938
|
}
|
|
927
939
|
});
|
|
928
940
|
});
|
|
929
941
|
}
|
|
930
942
|
getCanvasPosition(e, t) {
|
|
931
|
-
const i = this.canvas.getBoundingClientRect(), s = window.devicePixelRatio || 1,
|
|
932
|
-
return this.engine.screenToCanvas(
|
|
943
|
+
const i = this.canvas.getBoundingClientRect(), s = window.devicePixelRatio || 1, n = (e - i.left) * s, o = (t - i.top) * s;
|
|
944
|
+
return this.engine.screenToCanvas(n, o);
|
|
933
945
|
}
|
|
934
946
|
handleMouseDown(e) {
|
|
935
947
|
const t = this.getCanvasPosition(e.clientX, e.clientY);
|
|
@@ -978,22 +990,22 @@ class B extends I {
|
|
|
978
990
|
if (t) {
|
|
979
991
|
const o = this.engine.hitTestHandle(e.x, e.y, t);
|
|
980
992
|
if (o) {
|
|
981
|
-
const
|
|
982
|
-
if (o === "rotate" &&
|
|
993
|
+
const a = i.get(t), r = this.engine.getContentBounds(t);
|
|
994
|
+
if (o === "rotate" && a?.type === "image" && r) {
|
|
983
995
|
const d = Math.atan2(e.y - r.centerY, e.x - r.centerX) * (180 / Math.PI);
|
|
984
996
|
return this.interactionState = {
|
|
985
997
|
mode: "rotating",
|
|
986
998
|
areaId: t,
|
|
987
|
-
startRotation:
|
|
999
|
+
startRotation: a.rotation ?? 0,
|
|
988
1000
|
centerPoint: { x: r.centerX, y: r.centerY },
|
|
989
1001
|
startAngle: d
|
|
990
1002
|
}, !0;
|
|
991
|
-
} else if (o !== "rotate" &&
|
|
1003
|
+
} else if (o !== "rotate" && a?.type === "image" && r)
|
|
992
1004
|
return this.interactionState = {
|
|
993
1005
|
mode: "scaling",
|
|
994
1006
|
areaId: t,
|
|
995
1007
|
handle: o,
|
|
996
|
-
startScale:
|
|
1008
|
+
startScale: a.scale ?? 1,
|
|
997
1009
|
startMousePos: e,
|
|
998
1010
|
pivotPoint: { x: r.centerX, y: r.centerY }
|
|
999
1011
|
}, !0;
|
|
@@ -1012,47 +1024,45 @@ class B extends I {
|
|
|
1012
1024
|
}
|
|
1013
1025
|
return !0;
|
|
1014
1026
|
}
|
|
1015
|
-
const
|
|
1016
|
-
return
|
|
1027
|
+
const n = this.engine.hitTestArea(e.x, e.y);
|
|
1028
|
+
return n ? (this.emit("area:select", { areaId: n }), !1) : (this.emit("area:select", { areaId: null }), !1);
|
|
1017
1029
|
}
|
|
1018
1030
|
processPointerMove(e) {
|
|
1019
1031
|
if (this.interactionState.mode === "dragging") {
|
|
1020
|
-
const { areaId: t, startOffset: i, startMousePos: s } = this.interactionState,
|
|
1021
|
-
|
|
1022
|
-
x: i.x + a,
|
|
1032
|
+
const { areaId: t, startOffset: i, startMousePos: s } = this.interactionState, n = e.x - s.x, o = e.y - s.y, a = {
|
|
1033
|
+
x: i.x + n,
|
|
1023
1034
|
y: i.y + o
|
|
1024
|
-
};
|
|
1025
|
-
const r = this.engine.getAreaLocation(t);
|
|
1035
|
+
}, r = this.engine.getAreaLocation(t);
|
|
1026
1036
|
if (r) {
|
|
1027
1037
|
const d = r.width / 2 - 10, l = r.height / 2 - 10;
|
|
1028
|
-
|
|
1038
|
+
a.x = Math.max(-d, Math.min(d, a.x)), a.y = Math.max(-l, Math.min(l, a.y));
|
|
1029
1039
|
}
|
|
1030
|
-
this.emit("content:drag", { areaId: t, offset:
|
|
1040
|
+
this.emit("content:drag", { areaId: t, offset: a });
|
|
1031
1041
|
return;
|
|
1032
1042
|
}
|
|
1033
1043
|
if (this.interactionState.mode === "scaling") {
|
|
1034
|
-
const { areaId: t, startScale: i, startMousePos: s, pivotPoint:
|
|
1035
|
-
Math.pow(s.x -
|
|
1036
|
-
),
|
|
1037
|
-
Math.pow(e.x -
|
|
1044
|
+
const { areaId: t, startScale: i, startMousePos: s, pivotPoint: n } = this.interactionState, o = Math.sqrt(
|
|
1045
|
+
Math.pow(s.x - n.x, 2) + Math.pow(s.y - n.y, 2)
|
|
1046
|
+
), a = Math.sqrt(
|
|
1047
|
+
Math.pow(e.x - n.x, 2) + Math.pow(e.y - n.y, 2)
|
|
1038
1048
|
);
|
|
1039
1049
|
if (o > 0) {
|
|
1040
|
-
const r =
|
|
1050
|
+
const r = a / o, d = Math.max(0.1, Math.min(5, i * r));
|
|
1041
1051
|
this.emit("content:scale", { areaId: t, scale: d });
|
|
1042
1052
|
}
|
|
1043
1053
|
return;
|
|
1044
1054
|
}
|
|
1045
1055
|
if (this.interactionState.mode === "rotating") {
|
|
1046
|
-
const { areaId: t, startRotation: i, centerPoint: s, startAngle:
|
|
1047
|
-
let
|
|
1048
|
-
|
|
1056
|
+
const { areaId: t, startRotation: i, centerPoint: s, startAngle: n } = this.interactionState, o = Math.atan2(e.y - s.y, e.x - s.x) * (180 / Math.PI);
|
|
1057
|
+
let a = i + (o - n);
|
|
1058
|
+
a = a % 360, a < 0 && (a += 360);
|
|
1049
1059
|
const r = [0, 90, 180, 270, 360];
|
|
1050
1060
|
for (const d of r)
|
|
1051
|
-
if (Math.abs(
|
|
1052
|
-
|
|
1061
|
+
if (Math.abs(a - d) < 5) {
|
|
1062
|
+
a = d === 360 ? 0 : d;
|
|
1053
1063
|
break;
|
|
1054
1064
|
}
|
|
1055
|
-
this.emit("content:rotate", { areaId: t, rotation:
|
|
1065
|
+
this.emit("content:rotate", { areaId: t, rotation: a });
|
|
1056
1066
|
return;
|
|
1057
1067
|
}
|
|
1058
1068
|
this.updateCursor(e);
|
|
@@ -1077,7 +1087,7 @@ class B extends I {
|
|
|
1077
1087
|
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
1088
|
}
|
|
1079
1089
|
}
|
|
1080
|
-
class
|
|
1090
|
+
class B {
|
|
1081
1091
|
constructor(e = "/api") {
|
|
1082
1092
|
this.baseUrl = e;
|
|
1083
1093
|
}
|
|
@@ -1125,8 +1135,8 @@ class F {
|
|
|
1125
1135
|
|
|
1126
1136
|
`;
|
|
1127
1137
|
if (i.status === 400) {
|
|
1128
|
-
const
|
|
1129
|
-
s +=
|
|
1138
|
+
const n = await i.json().catch(() => ({}));
|
|
1139
|
+
s += n.error || "Invalid design data.";
|
|
1130
1140
|
} else i.status === 429 ? s += "Rate limit exceeded." : i.status >= 500 && (s += "Server error.");
|
|
1131
1141
|
throw new Error(s);
|
|
1132
1142
|
}
|
|
@@ -1135,6 +1145,79 @@ class F {
|
|
|
1135
1145
|
throw i instanceof Error && i.message.includes("Failed to fetch") ? new Error("Network error finalizing design") : i;
|
|
1136
1146
|
}
|
|
1137
1147
|
}
|
|
1148
|
+
async finalizeMultiView(e) {
|
|
1149
|
+
try {
|
|
1150
|
+
const t = e.designs.map((s) => ({
|
|
1151
|
+
templateId: s.templateId,
|
|
1152
|
+
viewName: s.viewName,
|
|
1153
|
+
areaContents: s.contents.map(([n, o]) => {
|
|
1154
|
+
const a = { areaId: n, isModified: !0 };
|
|
1155
|
+
return o.type === "text" ? a.text = {
|
|
1156
|
+
content: o.text,
|
|
1157
|
+
font: o.font,
|
|
1158
|
+
size: o.size,
|
|
1159
|
+
color: o.color,
|
|
1160
|
+
align: o.align,
|
|
1161
|
+
position: { x: o.offset?.x ?? 0, y: o.offset?.y ?? 0 }
|
|
1162
|
+
} : o.type === "image" && (a.image = {
|
|
1163
|
+
assetId: o.assetId || "inline",
|
|
1164
|
+
assetUrl: o.assetUrl || "",
|
|
1165
|
+
rotation: o.rotation ?? 0,
|
|
1166
|
+
scale: o.scale ?? 1,
|
|
1167
|
+
position: { x: o.offset?.x ?? 0, y: o.offset?.y ?? 0 },
|
|
1168
|
+
// Only send dataUrl if the image wasn't pre-uploaded
|
|
1169
|
+
...o.assetId ? {} : { dataUrl: o.dataUrl }
|
|
1170
|
+
}), a;
|
|
1171
|
+
})
|
|
1172
|
+
})), i = await fetch(`${this.baseUrl}/finalize`, {
|
|
1173
|
+
method: "POST",
|
|
1174
|
+
headers: { "Content-Type": "application/json" },
|
|
1175
|
+
body: JSON.stringify({
|
|
1176
|
+
productId: e.productId,
|
|
1177
|
+
designs: t,
|
|
1178
|
+
cartContext: e.cartContext,
|
|
1179
|
+
customerId: e.customerId,
|
|
1180
|
+
customerEmail: e.customerEmail,
|
|
1181
|
+
sessionId: e.sessionId
|
|
1182
|
+
})
|
|
1183
|
+
});
|
|
1184
|
+
if (!i.ok) {
|
|
1185
|
+
let s = `Failed to finalize multi-view design (${i.status} ${i.statusText})
|
|
1186
|
+
|
|
1187
|
+
`;
|
|
1188
|
+
if (i.status === 400) {
|
|
1189
|
+
const n = await i.json().catch(() => ({}));
|
|
1190
|
+
s += n.error || "Invalid design data.";
|
|
1191
|
+
} else i.status === 429 ? s += "Rate limit exceeded." : i.status >= 500 && (s += "Server error.");
|
|
1192
|
+
throw new Error(s);
|
|
1193
|
+
}
|
|
1194
|
+
return i.json();
|
|
1195
|
+
} catch (t) {
|
|
1196
|
+
throw t instanceof Error && t.message.includes("Failed to fetch") ? new Error("Network error finalizing multi-view design") : t;
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
async getDesignStatus(e) {
|
|
1200
|
+
try {
|
|
1201
|
+
const t = await fetch(`${this.baseUrl}/designs/${e}/status`);
|
|
1202
|
+
if (!t.ok)
|
|
1203
|
+
throw new Error(`Failed to check design status (${t.status})`);
|
|
1204
|
+
return t.json();
|
|
1205
|
+
} catch (t) {
|
|
1206
|
+
throw t instanceof Error && t.message.includes("Failed to fetch") ? new Error("Network error checking design status") : t;
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
async pollMultiViewStatus(e) {
|
|
1210
|
+
try {
|
|
1211
|
+
const t = await fetch(
|
|
1212
|
+
`${this.baseUrl}/finalize-status?designIds=${e.join(",")}`
|
|
1213
|
+
);
|
|
1214
|
+
if (!t.ok)
|
|
1215
|
+
throw new Error(`Failed to check multi-view design status (${t.status})`);
|
|
1216
|
+
return t.json();
|
|
1217
|
+
} catch (t) {
|
|
1218
|
+
throw t instanceof Error && t.message.includes("Failed to fetch") ? new Error("Network error checking multi-view design status") : t;
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1138
1221
|
async uploadAsset(e, t) {
|
|
1139
1222
|
try {
|
|
1140
1223
|
const i = new FormData();
|
|
@@ -1144,14 +1227,14 @@ class F {
|
|
|
1144
1227
|
body: i
|
|
1145
1228
|
});
|
|
1146
1229
|
if (!s.ok) {
|
|
1147
|
-
let
|
|
1230
|
+
let n = `Failed to upload asset (${s.status} ${s.statusText})
|
|
1148
1231
|
|
|
1149
1232
|
`;
|
|
1150
1233
|
if (s.status === 400) {
|
|
1151
1234
|
const o = await s.json().catch(() => ({}));
|
|
1152
|
-
|
|
1153
|
-
} else s.status === 413 ?
|
|
1154
|
-
throw new Error(
|
|
1235
|
+
n += o.error || "Invalid file.";
|
|
1236
|
+
} else s.status === 413 ? n += "File too large (max 15MB)." : s.status >= 500 && (n += "Server error.");
|
|
1237
|
+
throw new Error(n);
|
|
1155
1238
|
}
|
|
1156
1239
|
return s.json();
|
|
1157
1240
|
} catch (i) {
|
|
@@ -1171,16 +1254,16 @@ class F {
|
|
|
1171
1254
|
}
|
|
1172
1255
|
function h(f, e, ...t) {
|
|
1173
1256
|
const i = document.createElement(f);
|
|
1174
|
-
return e && Object.entries(e).forEach(([s,
|
|
1175
|
-
s === "class" ? i.className =
|
|
1257
|
+
return e && Object.entries(e).forEach(([s, n]) => {
|
|
1258
|
+
s === "class" ? i.className = n : s in i ? i[s] = n : i.setAttribute(s, String(n));
|
|
1176
1259
|
}), t.forEach((s) => {
|
|
1177
1260
|
typeof s == "string" ? i.appendChild(document.createTextNode(s)) : i.appendChild(s);
|
|
1178
1261
|
}), i;
|
|
1179
1262
|
}
|
|
1180
|
-
function
|
|
1263
|
+
function q(f, e) {
|
|
1181
1264
|
let t = null, i = null;
|
|
1182
|
-
const s = (...
|
|
1183
|
-
i =
|
|
1265
|
+
const s = (...n) => {
|
|
1266
|
+
i = n, t && clearTimeout(t), t = setTimeout(() => {
|
|
1184
1267
|
i && f(...i), t = null, i = null;
|
|
1185
1268
|
}, e);
|
|
1186
1269
|
};
|
|
@@ -1190,7 +1273,7 @@ function $(f, e) {
|
|
|
1190
1273
|
t && (clearTimeout(t), t = null), i = null;
|
|
1191
1274
|
}, s;
|
|
1192
1275
|
}
|
|
1193
|
-
class H extends
|
|
1276
|
+
class H extends z {
|
|
1194
1277
|
constructor() {
|
|
1195
1278
|
super(), this.saveEnabled = !0, this.percentLabel = h("span", { class: "zoom-percent" }, "100%");
|
|
1196
1279
|
const e = h("button", {
|
|
@@ -1211,13 +1294,13 @@ class H extends I {
|
|
|
1211
1294
|
this.emit("view-change", { viewName: this.viewSelect.value });
|
|
1212
1295
|
}), this.element = h("div", { class: "zoom-toolbar" }), this.element.appendChild(e), this.element.appendChild(t), this.element.appendChild(i), this.element.appendChild(this.percentLabel), this.element.appendChild(this.viewSelect);
|
|
1213
1296
|
const s = h("div", { class: "zoom-toolbar-spacer" });
|
|
1214
|
-
this.element.appendChild(s), this.
|
|
1215
|
-
class: "zoom-btn zoom-close-btn",
|
|
1216
|
-
title: "Close editor"
|
|
1217
|
-
}, "✕ Close"), this.closeBtn.addEventListener("click", () => this.emit("close", void 0)), this.element.appendChild(this.closeBtn), this.saveBtn = h("button", {
|
|
1297
|
+
this.element.appendChild(s), this.saveBtn = h("button", {
|
|
1218
1298
|
class: "zoom-btn zoom-save-btn",
|
|
1219
1299
|
title: "Save customization"
|
|
1220
|
-
}, "
|
|
1300
|
+
}, "✓"), this.saveBtn.style.display = "none", this.saveBtn.addEventListener("click", () => this.emit("save", void 0)), this.element.appendChild(this.saveBtn), this.closeBtn = h("button", {
|
|
1301
|
+
class: "zoom-btn zoom-close-btn",
|
|
1302
|
+
title: "Close editor"
|
|
1303
|
+
}, "✕"), this.closeBtn.addEventListener("click", () => this.emit("close", void 0)), this.element.appendChild(this.closeBtn);
|
|
1221
1304
|
}
|
|
1222
1305
|
setZoom(e) {
|
|
1223
1306
|
this.percentLabel.textContent = `${Math.round(e * 100)}%`;
|
|
@@ -1255,31 +1338,31 @@ const Z = [
|
|
|
1255
1338
|
{ label: "Georgia", value: "Georgia" },
|
|
1256
1339
|
{ label: "Times New Roman", value: "Times New Roman" },
|
|
1257
1340
|
{ label: "Courier New", value: "Courier New" }
|
|
1258
|
-
],
|
|
1341
|
+
], j = [
|
|
1259
1342
|
{ label: "L", value: "left" },
|
|
1260
1343
|
{ label: "C", value: "center" },
|
|
1261
1344
|
{ label: "R", value: "right" }
|
|
1262
|
-
],
|
|
1263
|
-
class L extends
|
|
1345
|
+
], A = ["image/png", "image/jpeg", "image/webp", "image/svg+xml"], Y = 15 * 1024 * 1024;
|
|
1346
|
+
class L extends z {
|
|
1264
1347
|
constructor(e) {
|
|
1265
|
-
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;
|
|
1266
|
-
const t =
|
|
1348
|
+
super(), this.mode = "text", this.isSelected = !1, this.isUploading = !1, this.textInputEl = null, this.fontSelectEl = null, this.sizeInputEl = null, this.colorInputEl = null, this.alignGroupEl = null, this.area = e;
|
|
1349
|
+
const t = E(e), i = T(e);
|
|
1267
1350
|
this.mode = i && !t ? "image" : "text", this.element = h("div", { class: "area-card" }), this.element.addEventListener("click", (r) => {
|
|
1268
1351
|
r.target.tagName === "INPUT" || r.target.tagName === "SELECT" || r.target.tagName === "BUTTON" || this.emit("select", { areaId: e.id });
|
|
1269
1352
|
});
|
|
1270
|
-
const s = h("div", { class: "area-card-header" }),
|
|
1271
|
-
if (
|
|
1353
|
+
const s = h("div", { class: "area-card-header" }), n = h("div", { class: "area-card-name-row" }), o = h("span", { class: "area-card-name" }, e.name);
|
|
1354
|
+
if (n.appendChild(o), e.required) {
|
|
1272
1355
|
const r = h("span", { class: "area-card-badge" }, "Required");
|
|
1273
|
-
|
|
1356
|
+
n.appendChild(r);
|
|
1274
1357
|
}
|
|
1275
|
-
s.appendChild(
|
|
1276
|
-
const
|
|
1358
|
+
s.appendChild(n);
|
|
1359
|
+
const a = h("button", {
|
|
1277
1360
|
class: "area-card-reset-btn",
|
|
1278
1361
|
title: "Reset content"
|
|
1279
1362
|
}, "Reset");
|
|
1280
|
-
if (
|
|
1363
|
+
if (a.addEventListener("click", (r) => {
|
|
1281
1364
|
r.stopPropagation(), this.emit("clear", { areaId: e.id });
|
|
1282
|
-
}), s.appendChild(
|
|
1365
|
+
}), s.appendChild(a), this.element.appendChild(s), t && i) {
|
|
1283
1366
|
const r = h("div", { class: "area-card-mode-switcher" }), d = h("button", {
|
|
1284
1367
|
class: "mode-btn mode-btn-active"
|
|
1285
1368
|
}, "Text");
|
|
@@ -1298,7 +1381,7 @@ class L extends I {
|
|
|
1298
1381
|
}
|
|
1299
1382
|
setContent(e) {
|
|
1300
1383
|
const t = this.currentContent?.type;
|
|
1301
|
-
this.currentContent = e, e?.type === "image" && (this.mode = "image"), this.element.querySelectorAll(".mode-btn").forEach((i) => {
|
|
1384
|
+
this.currentContent = e, e?.type === "image" ? this.mode = "image" : t === "image" && (this.mode = E(this.area) ? "text" : "image"), this.element.querySelectorAll(".mode-btn").forEach((i) => {
|
|
1302
1385
|
const s = i;
|
|
1303
1386
|
s.classList.toggle("mode-btn-active", s.dataset.mode === this.mode);
|
|
1304
1387
|
}), e?.type !== t || e?.type === "image" || e === void 0 ? this.renderContent() : e?.type === "text" && this.updateTextValues(e), this.renderTransforms();
|
|
@@ -1314,11 +1397,11 @@ class L extends I {
|
|
|
1314
1397
|
}
|
|
1315
1398
|
renderContent() {
|
|
1316
1399
|
this.contentContainer.innerHTML = "", this.textInputEl = null, this.fontSelectEl = null, this.sizeInputEl = null, this.colorInputEl = null, this.alignGroupEl = null;
|
|
1317
|
-
const e =
|
|
1318
|
-
e && i && this.renderTextControls(), t && s && this.renderImageControls();
|
|
1400
|
+
const e = E(this.area), t = T(this.area), i = !t || e && t && this.mode === "text", s = !e || e && t && this.mode === "image";
|
|
1401
|
+
e && i && this.renderTextControls(), t && s && this.renderImageControls(), this.applyUploadSpinner();
|
|
1319
1402
|
}
|
|
1320
1403
|
renderTextControls() {
|
|
1321
|
-
const e = this.currentContent, t = e?.type === "text" ? e :
|
|
1404
|
+
const e = this.currentContent, t = e?.type === "text" ? e : S(this.area), i = h("input", {
|
|
1322
1405
|
class: "area-input-text",
|
|
1323
1406
|
type: "text",
|
|
1324
1407
|
placeholder: this.area.placeholder || "Enter text..."
|
|
@@ -1326,14 +1409,14 @@ class L extends I {
|
|
|
1326
1409
|
i.value = t.text, this.area.textOptions?.maxLength && (i.maxLength = this.area.textOptions.maxLength), i.addEventListener("input", () => {
|
|
1327
1410
|
this.emit("text:change", { areaId: this.area.id, updates: { text: i.value } });
|
|
1328
1411
|
}), this.contentContainer.appendChild(i), this.textInputEl = i;
|
|
1329
|
-
const s = h("div", { class: "area-input-row" }),
|
|
1412
|
+
const s = h("div", { class: "area-input-row" }), n = h("select", { class: "area-input-font" });
|
|
1330
1413
|
for (const l of Z) {
|
|
1331
1414
|
const c = h("option");
|
|
1332
|
-
c.value = l.value, c.textContent = l.label, l.value === t.font && (c.selected = !0),
|
|
1415
|
+
c.value = l.value, c.textContent = l.label, l.value === t.font && (c.selected = !0), n.appendChild(c);
|
|
1333
1416
|
}
|
|
1334
|
-
|
|
1335
|
-
this.emit("text:change", { areaId: this.area.id, updates: { font:
|
|
1336
|
-
}), s.appendChild(
|
|
1417
|
+
n.addEventListener("change", () => {
|
|
1418
|
+
this.emit("text:change", { areaId: this.area.id, updates: { font: n.value } });
|
|
1419
|
+
}), s.appendChild(n), this.fontSelectEl = n;
|
|
1337
1420
|
const o = h("input", {
|
|
1338
1421
|
class: "area-input-size",
|
|
1339
1422
|
type: "number"
|
|
@@ -1344,24 +1427,24 @@ class L extends I {
|
|
|
1344
1427
|
updates: { size: parseInt(o.value, 10) || 24 }
|
|
1345
1428
|
});
|
|
1346
1429
|
}), s.appendChild(o), this.sizeInputEl = o, this.contentContainer.appendChild(s);
|
|
1347
|
-
const
|
|
1348
|
-
for (const l of
|
|
1430
|
+
const a = h("div", { class: "area-input-row" }), r = h("div", { class: "area-input-align" });
|
|
1431
|
+
for (const l of j) {
|
|
1349
1432
|
const c = h("button", {
|
|
1350
1433
|
class: `align-btn${t.align === l.value ? " align-btn-active" : ""}`,
|
|
1351
1434
|
title: l.label === "L" ? "Left" : l.label === "C" ? "Center" : "Right"
|
|
1352
1435
|
}, l.label);
|
|
1353
|
-
c.addEventListener("click", (
|
|
1354
|
-
|
|
1436
|
+
c.addEventListener("click", (p) => {
|
|
1437
|
+
p.stopPropagation(), this.emit("text:change", { areaId: this.area.id, updates: { align: l.value } }), r.querySelectorAll(".align-btn").forEach((u) => u.classList.remove("align-btn-active")), c.classList.add("align-btn-active");
|
|
1355
1438
|
}), r.appendChild(c);
|
|
1356
1439
|
}
|
|
1357
|
-
|
|
1440
|
+
a.appendChild(r), this.alignGroupEl = r;
|
|
1358
1441
|
const d = h("input", {
|
|
1359
1442
|
class: "area-input-color",
|
|
1360
1443
|
type: "color"
|
|
1361
1444
|
});
|
|
1362
1445
|
d.value = t.color, d.addEventListener("input", () => {
|
|
1363
1446
|
this.emit("text:change", { areaId: this.area.id, updates: { color: d.value } });
|
|
1364
|
-
}),
|
|
1447
|
+
}), a.appendChild(d), this.colorInputEl = d, this.contentContainer.appendChild(a);
|
|
1365
1448
|
}
|
|
1366
1449
|
renderImageControls() {
|
|
1367
1450
|
const e = this.currentContent;
|
|
@@ -1370,35 +1453,35 @@ class L extends I {
|
|
|
1370
1453
|
i.src = e.dataUrl, i.alt = e.filename || "Uploaded image", t.appendChild(i);
|
|
1371
1454
|
const s = h("div", { class: "area-image-info" });
|
|
1372
1455
|
s.appendChild(h("span", { class: "area-image-name" }, e.filename || "Image"));
|
|
1373
|
-
const
|
|
1374
|
-
|
|
1456
|
+
const n = h("button", { class: "area-image-remove-btn" }, "Remove");
|
|
1457
|
+
n.addEventListener("click", (o) => {
|
|
1375
1458
|
o.stopPropagation(), this.emit("clear", { areaId: this.area.id });
|
|
1376
|
-
}), s.appendChild(
|
|
1459
|
+
}), s.appendChild(n), t.appendChild(s), this.contentContainer.appendChild(t);
|
|
1377
1460
|
} else {
|
|
1378
1461
|
const t = h("button", { class: "area-upload-btn" }, "Upload Image"), i = h("input", { type: "file" });
|
|
1379
|
-
i.accept =
|
|
1462
|
+
i.accept = A.join(","), i.style.display = "none";
|
|
1380
1463
|
const s = h("div", { class: "area-validation-error" });
|
|
1381
1464
|
s.style.display = "none", i.addEventListener("change", () => {
|
|
1382
|
-
const
|
|
1383
|
-
if (!
|
|
1384
|
-
if (!
|
|
1385
|
-
const
|
|
1386
|
-
s.textContent =
|
|
1465
|
+
const n = i.files?.[0];
|
|
1466
|
+
if (!n) return;
|
|
1467
|
+
if (!A.includes(n.type)) {
|
|
1468
|
+
const a = "Only PNG, JPEG, WebP, and SVG images are accepted";
|
|
1469
|
+
s.textContent = a, s.style.display = "block", this.emit("validation:error", { areaId: this.area.id, message: a }), i.value = "";
|
|
1387
1470
|
return;
|
|
1388
1471
|
}
|
|
1389
|
-
if (
|
|
1390
|
-
const
|
|
1391
|
-
s.textContent =
|
|
1472
|
+
if (n.size > Y) {
|
|
1473
|
+
const a = "File must be smaller than 15MB";
|
|
1474
|
+
s.textContent = a, s.style.display = "block", this.emit("validation:error", { areaId: this.area.id, message: a }), i.value = "";
|
|
1392
1475
|
return;
|
|
1393
1476
|
}
|
|
1394
1477
|
s.style.display = "none";
|
|
1395
1478
|
const o = new FileReader();
|
|
1396
1479
|
o.onload = () => {
|
|
1397
|
-
const
|
|
1398
|
-
this.emit("image:change", { areaId: this.area.id, dataUrl:
|
|
1399
|
-
}, o.readAsDataURL(
|
|
1400
|
-
}), t.addEventListener("click", (
|
|
1401
|
-
|
|
1480
|
+
const a = o.result;
|
|
1481
|
+
this.emit("image:change", { areaId: this.area.id, dataUrl: a, filename: n.name });
|
|
1482
|
+
}, o.readAsDataURL(n), i.value = "";
|
|
1483
|
+
}), t.addEventListener("click", (n) => {
|
|
1484
|
+
n.stopPropagation(), i.click();
|
|
1402
1485
|
}), this.contentContainer.appendChild(t), this.contentContainer.appendChild(i), this.contentContainer.appendChild(s);
|
|
1403
1486
|
}
|
|
1404
1487
|
}
|
|
@@ -1408,14 +1491,14 @@ class L extends I {
|
|
|
1408
1491
|
if (!this.isSelected || !e) return;
|
|
1409
1492
|
const t = e.type === "text" ? this.area.textOptions?.allowPositioning : this.area.imageOptions?.allowPositioning, i = e.type === "image" && this.area.imageOptions?.allowScaling, s = e.type === "image" && this.area.imageOptions?.allowRotation;
|
|
1410
1493
|
if (!t && !i && !s) return;
|
|
1411
|
-
const
|
|
1412
|
-
this.transformContainer.appendChild(
|
|
1494
|
+
const n = h("div", { class: "area-card-divider" });
|
|
1495
|
+
this.transformContainer.appendChild(n);
|
|
1413
1496
|
const o = h("div", { class: "transform-header" });
|
|
1414
1497
|
o.appendChild(h("span", { class: "transform-title" }, "Transform"));
|
|
1415
|
-
const
|
|
1416
|
-
|
|
1498
|
+
const a = h("button", { class: "transform-reset-btn" }, "Reset");
|
|
1499
|
+
a.addEventListener("click", (d) => {
|
|
1417
1500
|
d.stopPropagation(), t && this.emit("offset:change", { areaId: this.area.id, offset: { x: 0, y: 0 } }), i && this.emit("scale:change", { areaId: this.area.id, scale: 1 }), s && this.emit("rotation:change", { areaId: this.area.id, rotation: 0 });
|
|
1418
|
-
}), o.appendChild(
|
|
1501
|
+
}), o.appendChild(a), this.transformContainer.appendChild(o);
|
|
1419
1502
|
const r = e.offset ?? { x: 0, y: 0 };
|
|
1420
1503
|
if (t) {
|
|
1421
1504
|
const d = h("div", { class: "area-input-row" }), l = h("label", { class: "transform-label" }, "X"), c = h("input", { class: "transform-input", type: "number" });
|
|
@@ -1425,34 +1508,46 @@ class L extends I {
|
|
|
1425
1508
|
offset: { ...r, x: parseInt(c.value, 10) || 0 }
|
|
1426
1509
|
});
|
|
1427
1510
|
});
|
|
1428
|
-
const
|
|
1429
|
-
|
|
1511
|
+
const p = h("label", { class: "transform-label" }, "Y"), u = h("input", { class: "transform-input", type: "number" });
|
|
1512
|
+
u.value = String(Math.round(r.y)), u.addEventListener("change", () => {
|
|
1430
1513
|
this.emit("offset:change", {
|
|
1431
1514
|
areaId: this.area.id,
|
|
1432
|
-
offset: { ...r, y: parseInt(
|
|
1515
|
+
offset: { ...r, y: parseInt(u.value, 10) || 0 }
|
|
1433
1516
|
});
|
|
1434
|
-
}), d.appendChild(l), d.appendChild(c), d.appendChild(
|
|
1517
|
+
}), d.appendChild(l), d.appendChild(c), d.appendChild(p), d.appendChild(u), this.transformContainer.appendChild(d);
|
|
1435
1518
|
}
|
|
1436
1519
|
if (i && e.type === "image") {
|
|
1437
|
-
const d = e.scale ?? 1, l = h("div", { class: "area-input-row transform-slider-row" }), c = h("label", { class: "transform-label" }, `Scale: ${Math.round(d * 100)}%`),
|
|
1438
|
-
|
|
1439
|
-
const
|
|
1440
|
-
c.textContent = `Scale: ${Math.round(
|
|
1441
|
-
}), l.appendChild(c), l.appendChild(
|
|
1520
|
+
const d = e.scale ?? 1, l = h("div", { class: "area-input-row transform-slider-row" }), c = h("label", { class: "transform-label" }, `Scale: ${Math.round(d * 100)}%`), p = h("input", { class: "transform-slider", type: "range" });
|
|
1521
|
+
p.min = "10", p.max = "500", p.step = "5", p.value = String(Math.round(d * 100)), p.addEventListener("input", () => {
|
|
1522
|
+
const u = parseInt(p.value, 10) / 100;
|
|
1523
|
+
c.textContent = `Scale: ${Math.round(u * 100)}%`, this.emit("scale:change", { areaId: this.area.id, scale: u });
|
|
1524
|
+
}), l.appendChild(c), l.appendChild(p), this.transformContainer.appendChild(l);
|
|
1442
1525
|
}
|
|
1443
1526
|
if (s && e.type === "image") {
|
|
1444
|
-
const d = e.rotation ?? 0, l = h("div", { class: "area-input-row transform-slider-row" }), c = h("label", { class: "transform-label" }, `Rotation: ${Math.round(d)}°`),
|
|
1445
|
-
|
|
1446
|
-
const
|
|
1447
|
-
c.textContent = `Rotation: ${
|
|
1448
|
-
}), l.appendChild(c), l.appendChild(
|
|
1527
|
+
const d = e.rotation ?? 0, l = h("div", { class: "area-input-row transform-slider-row" }), c = h("label", { class: "transform-label" }, `Rotation: ${Math.round(d)}°`), p = h("input", { class: "transform-slider", type: "range" });
|
|
1528
|
+
p.min = "0", p.max = "360", p.step = "1", p.value = String(Math.round(d)), p.addEventListener("input", () => {
|
|
1529
|
+
const u = parseInt(p.value, 10);
|
|
1530
|
+
c.textContent = `Rotation: ${u}°`, this.emit("rotation:change", { areaId: this.area.id, rotation: u });
|
|
1531
|
+
}), l.appendChild(c), l.appendChild(p), this.transformContainer.appendChild(l);
|
|
1449
1532
|
}
|
|
1450
1533
|
}
|
|
1534
|
+
setUploading(e) {
|
|
1535
|
+
this.isUploading = e, this.applyUploadSpinner();
|
|
1536
|
+
}
|
|
1537
|
+
applyUploadSpinner() {
|
|
1538
|
+
const e = this.contentContainer.querySelector(".area-image-preview");
|
|
1539
|
+
if (!e) return;
|
|
1540
|
+
const t = e.querySelector(".area-upload-spinner-overlay");
|
|
1541
|
+
if (this.isUploading && !t) {
|
|
1542
|
+
const i = h("div", { class: "area-upload-spinner-overlay" });
|
|
1543
|
+
i.appendChild(h("div", { class: "area-upload-spinner" })), e.appendChild(i);
|
|
1544
|
+
} else !this.isUploading && t && t.remove();
|
|
1545
|
+
}
|
|
1451
1546
|
getElement() {
|
|
1452
1547
|
return this.element;
|
|
1453
1548
|
}
|
|
1454
1549
|
}
|
|
1455
|
-
class
|
|
1550
|
+
class J extends z {
|
|
1456
1551
|
constructor(e) {
|
|
1457
1552
|
super(), this.cards = /* @__PURE__ */ new Map(), this.isMobile = !1, this.boundEscapeHandler = (s) => {
|
|
1458
1553
|
s.key === "Escape" && this.emit("dismiss", void 0);
|
|
@@ -1469,8 +1564,8 @@ class j extends I {
|
|
|
1469
1564
|
this.panelContent.appendChild(s);
|
|
1470
1565
|
} else
|
|
1471
1566
|
for (const s of e) {
|
|
1472
|
-
const
|
|
1473
|
-
this.cards.set(s.id,
|
|
1567
|
+
const n = new L(s);
|
|
1568
|
+
this.cards.set(s.id, n), n.on("text:change", (o) => this.emit("text:change", o)), n.on("image:change", (o) => this.emit("image:change", o)), n.on("clear", (o) => this.emit("clear", o)), n.on("select", (o) => this.emit("select", o)), n.on("offset:change", (o) => this.emit("offset:change", o)), n.on("scale:change", (o) => this.emit("scale:change", o)), n.on("rotation:change", (o) => this.emit("rotation:change", o)), n.on("validation:error", (o) => this.emit("validation:error", o)), this.panelContent.appendChild(n.getElement());
|
|
1474
1569
|
}
|
|
1475
1570
|
this.element.appendChild(this.panelContent);
|
|
1476
1571
|
}
|
|
@@ -1495,6 +1590,10 @@ class j extends I {
|
|
|
1495
1590
|
}
|
|
1496
1591
|
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));
|
|
1497
1592
|
}
|
|
1593
|
+
setUploading(e, t) {
|
|
1594
|
+
const i = this.cards.get(e);
|
|
1595
|
+
i && i.setUploading(t);
|
|
1596
|
+
}
|
|
1498
1597
|
setMobile(e) {
|
|
1499
1598
|
this.isMobile = e, this.element.classList.toggle("area-panel-mobile", e), e || (this.element.classList.remove("area-panel-visible"), this.backdrop.classList.remove("area-panel-backdrop-visible"));
|
|
1500
1599
|
}
|
|
@@ -1505,7 +1604,7 @@ class j extends I {
|
|
|
1505
1604
|
return this.backdrop;
|
|
1506
1605
|
}
|
|
1507
1606
|
}
|
|
1508
|
-
class
|
|
1607
|
+
class W {
|
|
1509
1608
|
constructor(e = window) {
|
|
1510
1609
|
this.target = e, this.commands = [], this.isEnabled = !0, this.handleKeyDown = (t) => {
|
|
1511
1610
|
if (!this.isEnabled) return;
|
|
@@ -1559,31 +1658,31 @@ class X {
|
|
|
1559
1658
|
this.target.removeEventListener("keydown", this.handleKeyDown), this.commands = [];
|
|
1560
1659
|
}
|
|
1561
1660
|
}
|
|
1562
|
-
function
|
|
1661
|
+
function X() {
|
|
1563
1662
|
return /Mac|iPod|iPhone|iPad/.test(navigator.platform);
|
|
1564
1663
|
}
|
|
1565
|
-
function
|
|
1566
|
-
return
|
|
1664
|
+
function G() {
|
|
1665
|
+
return X() ? "meta" : "ctrl";
|
|
1567
1666
|
}
|
|
1568
|
-
function
|
|
1569
|
-
const s = f / t,
|
|
1570
|
-
return Math.min(s,
|
|
1667
|
+
function K(f, e, t, i) {
|
|
1668
|
+
const s = f / t, n = e / i;
|
|
1669
|
+
return Math.min(s, n);
|
|
1571
1670
|
}
|
|
1572
|
-
function
|
|
1573
|
-
const
|
|
1671
|
+
function Q(f, e, t = 8, i = 10, s = 300) {
|
|
1672
|
+
const n = K(f, e, t, i), o = n >= s;
|
|
1574
1673
|
return {
|
|
1575
|
-
actualDPI: Math.round(
|
|
1674
|
+
actualDPI: Math.round(n),
|
|
1576
1675
|
targetDPI: s,
|
|
1577
1676
|
width: f,
|
|
1578
1677
|
height: e,
|
|
1579
1678
|
passed: o,
|
|
1580
|
-
warning: o ? void 0 : `Image resolution is ${Math.round(
|
|
1679
|
+
warning: o ? void 0 : `Image resolution is ${Math.round(n)} DPI at 100% scale. For best print quality, use images with at least ${s} DPI. This may result in pixelated or blurry prints.`
|
|
1581
1680
|
};
|
|
1582
1681
|
}
|
|
1583
|
-
const M = ':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;
|
|
1584
|
-
class
|
|
1682
|
+
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)}.area-image-preview{position:relative}.area-upload-spinner-overlay{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background:#fff9;border-radius:var(--editor-radius-sm);z-index:1}:host([theme="dark"]) .area-upload-spinner-overlay{background:#37415199}.area-upload-spinner{width:16px;height:16px;border:2px solid var(--editor-border);border-top-color:var(--editor-primary);border-radius:50%;animation:spin .8s linear infinite}.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-toolbar{padding:6px 8px;gap:2px}.zoom-btn{min-width:44px;min-height:44px}.zoom-percent{display:none}.view-select{max-width:110px;text-overflow:ellipsis;overflow:hidden;min-height:44px;font-size:16px}.zoom-close-btn,.zoom-save-btn{padding:0 8px}.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}}', _ = "2.0.0", ee = 768;
|
|
1683
|
+
class I extends HTMLElement {
|
|
1585
1684
|
constructor() {
|
|
1586
|
-
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 =
|
|
1685
|
+
super(), this.resizeObserver = null, this.pendingUploads = /* @__PURE__ */ new Map(), 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.renderAbortController = null, this.contentManagerUnsub = null, this.refitRafId = null, this.storageUsage = null, this.storageUsageLastRefresh = 0, this.emitChange = q(() => {
|
|
1587
1686
|
this.dispatchEvent(
|
|
1588
1687
|
new CustomEvent("change", {
|
|
1589
1688
|
detail: { design: this.getDesign() },
|
|
@@ -1614,7 +1713,7 @@ class ee extends HTMLElement {
|
|
|
1614
1713
|
if (!this.currentTemplate) throw new Error("No design loaded");
|
|
1615
1714
|
return {
|
|
1616
1715
|
templateId: this.currentTemplate.metadata.id,
|
|
1617
|
-
engineVersion:
|
|
1716
|
+
engineVersion: _,
|
|
1618
1717
|
contents: this.contentManager.toJSON(),
|
|
1619
1718
|
userData: {}
|
|
1620
1719
|
};
|
|
@@ -1673,37 +1772,176 @@ class ee extends HTMLElement {
|
|
|
1673
1772
|
getAreaContent(e) {
|
|
1674
1773
|
return this.contentManager.getContent(e);
|
|
1675
1774
|
}
|
|
1775
|
+
static hasEditorContent(e) {
|
|
1776
|
+
return e.type === "text" ? e.text.trim().length > 0 : e.type === "image" ? !!e.dataUrl : !1;
|
|
1777
|
+
}
|
|
1778
|
+
validateRequiredContent() {
|
|
1779
|
+
const e = [];
|
|
1780
|
+
if (this.productViews.length > 1) {
|
|
1781
|
+
this.activeViewName && this.contentManager && this.perViewContents.set(this.activeViewName, this.contentManager.toJSON());
|
|
1782
|
+
for (const t of this.productViews) {
|
|
1783
|
+
const i = this.perViewContents.get(t.viewName), s = i?.some(([, o]) => I.hasEditorContent(o)) ?? !1;
|
|
1784
|
+
if (t.isRequired && !s) {
|
|
1785
|
+
e.push(`View "${t.viewName}" is required`);
|
|
1786
|
+
continue;
|
|
1787
|
+
}
|
|
1788
|
+
const n = t.template.templateJson.areas || [];
|
|
1789
|
+
for (const o of n) {
|
|
1790
|
+
if (!o.required) continue;
|
|
1791
|
+
const a = i?.find(([r]) => r === o.id);
|
|
1792
|
+
a && I.hasEditorContent(a[1]) || e.push(`"${o.name}" in view "${t.viewName}" is required`);
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1795
|
+
} else {
|
|
1796
|
+
const t = this.currentTemplate?.areas || [], i = this.contentManager.getContents();
|
|
1797
|
+
for (const s of t) {
|
|
1798
|
+
if (!s.required) continue;
|
|
1799
|
+
const n = i.get(s.id);
|
|
1800
|
+
n && I.hasEditorContent(n) || e.push(`"${s.name}" is required`);
|
|
1801
|
+
}
|
|
1802
|
+
}
|
|
1803
|
+
return e;
|
|
1804
|
+
}
|
|
1805
|
+
showValidationErrors(e) {
|
|
1806
|
+
return new Promise((t) => {
|
|
1807
|
+
const i = h("div", { class: "editor-modal-overlay" }), s = h("div", { class: "editor-modal" });
|
|
1808
|
+
s.appendChild(h("div", { class: "editor-modal-title" }, "Required content missing"));
|
|
1809
|
+
const n = h("div", { class: "editor-modal-body" }), o = document.createElement("ul");
|
|
1810
|
+
o.style.margin = "8px 0", o.style.paddingLeft = "20px";
|
|
1811
|
+
for (const d of e) {
|
|
1812
|
+
const l = document.createElement("li");
|
|
1813
|
+
l.textContent = d, o.appendChild(l);
|
|
1814
|
+
}
|
|
1815
|
+
n.appendChild(o), s.appendChild(n);
|
|
1816
|
+
const a = h("div", { class: "editor-modal-actions" }), r = h("button", { class: "editor-modal-btn editor-modal-btn-primary" }, "OK");
|
|
1817
|
+
r.addEventListener("click", () => {
|
|
1818
|
+
i.remove(), t();
|
|
1819
|
+
}), a.appendChild(r), s.appendChild(a), i.appendChild(s), i.addEventListener("click", (d) => {
|
|
1820
|
+
d.target === i && (i.remove(), t());
|
|
1821
|
+
}), this.shadow.appendChild(i);
|
|
1822
|
+
});
|
|
1823
|
+
}
|
|
1676
1824
|
async finalize() {
|
|
1677
|
-
const e = this.
|
|
1678
|
-
if (e.length > 0)
|
|
1679
|
-
|
|
1825
|
+
const e = this.validateRequiredContent();
|
|
1826
|
+
if (e.length > 0)
|
|
1827
|
+
return await this.showValidationErrors(e), null;
|
|
1828
|
+
const t = this.engine.checkSafeAreaViolations();
|
|
1829
|
+
if (t.length > 0) {
|
|
1830
|
+
const o = this.currentTemplate?.areas || [], a = t.map((r) => o.find((d) => d.id === r)?.name || r).join(", ");
|
|
1680
1831
|
if (!await this.showConfirmation(
|
|
1681
1832
|
"Content outside safe area",
|
|
1682
|
-
`Content in ${
|
|
1833
|
+
`Content in ${a} extends beyond the safe area. It may be trimmed during printing. Continue?`,
|
|
1683
1834
|
"Cancel",
|
|
1684
1835
|
"Proceed Anyway"
|
|
1685
1836
|
))
|
|
1686
1837
|
throw new Error("Finalization cancelled by user");
|
|
1687
1838
|
}
|
|
1688
|
-
const
|
|
1839
|
+
const i = this.getAttribute("store-id") || "demo-shop";
|
|
1840
|
+
if (this.pendingUploads.size > 0 && await Promise.allSettled(this.pendingUploads.values()), this.productViews.length > 1) {
|
|
1841
|
+
this.activeViewName && this.contentManager && this.perViewContents.set(this.activeViewName, this.contentManager.toJSON());
|
|
1842
|
+
const o = [];
|
|
1843
|
+
for (const c of this.productViews) {
|
|
1844
|
+
const p = this.perViewContents.get(c.viewName);
|
|
1845
|
+
!p || p.length === 0 || !p.some(([, u]) => I.hasEditorContent(u)) || o.push({
|
|
1846
|
+
templateId: c.template.id,
|
|
1847
|
+
viewName: c.viewName,
|
|
1848
|
+
contents: p
|
|
1849
|
+
});
|
|
1850
|
+
}
|
|
1851
|
+
if (o.length === 0)
|
|
1852
|
+
throw new Error("No views have content to finalize");
|
|
1853
|
+
const a = this.getAttribute("product-id");
|
|
1854
|
+
if (!a)
|
|
1855
|
+
throw new Error("product-id attribute is required for multi-view finalize");
|
|
1856
|
+
const r = await this.apiClient.finalizeMultiView({
|
|
1857
|
+
productId: a,
|
|
1858
|
+
designs: o
|
|
1859
|
+
}), d = r.views.map((c) => ({
|
|
1860
|
+
viewName: c.viewName,
|
|
1861
|
+
designId: c.designId,
|
|
1862
|
+
proofUrl: c.proofUrl || "",
|
|
1863
|
+
status: c.status
|
|
1864
|
+
})), l = d[0];
|
|
1865
|
+
return this.dispatchEvent(
|
|
1866
|
+
new CustomEvent("customizer:finalize", {
|
|
1867
|
+
detail: {
|
|
1868
|
+
designId: l.designId,
|
|
1869
|
+
proofUrl: l.proofUrl,
|
|
1870
|
+
status: "processing",
|
|
1871
|
+
views: d,
|
|
1872
|
+
requestId: r.requestId
|
|
1873
|
+
},
|
|
1874
|
+
bubbles: !0,
|
|
1875
|
+
composed: !0
|
|
1876
|
+
})
|
|
1877
|
+
), { ...l, views: d };
|
|
1878
|
+
}
|
|
1879
|
+
const s = this.getDesign(), n = await this.apiClient.finalizeDesign(i, s);
|
|
1689
1880
|
return this.dispatchEvent(
|
|
1690
1881
|
new CustomEvent("customizer:finalize", {
|
|
1691
1882
|
detail: {
|
|
1692
|
-
designId:
|
|
1693
|
-
proofUrl:
|
|
1694
|
-
templateId:
|
|
1695
|
-
designJson:
|
|
1696
|
-
status:
|
|
1883
|
+
designId: n.designId,
|
|
1884
|
+
proofUrl: n.proofUrl,
|
|
1885
|
+
templateId: s.templateId,
|
|
1886
|
+
designJson: s,
|
|
1887
|
+
status: n.status
|
|
1697
1888
|
},
|
|
1698
1889
|
bubbles: !0,
|
|
1699
1890
|
composed: !0
|
|
1700
1891
|
})
|
|
1701
|
-
),
|
|
1892
|
+
), n;
|
|
1893
|
+
}
|
|
1894
|
+
async waitForResult(e, t) {
|
|
1895
|
+
const i = t?.pollInterval ?? 1500, s = t?.maxPolls ?? 40, n = t?.signal;
|
|
1896
|
+
let o = 0;
|
|
1897
|
+
for (; o < s; ) {
|
|
1898
|
+
if (n?.aborted)
|
|
1899
|
+
throw new DOMException("Polling aborted", "AbortError");
|
|
1900
|
+
await new Promise((d, l) => {
|
|
1901
|
+
const c = setTimeout(d, i);
|
|
1902
|
+
n?.addEventListener("abort", () => {
|
|
1903
|
+
clearTimeout(c), l(new DOMException("Polling aborted", "AbortError"));
|
|
1904
|
+
}, { once: !0 });
|
|
1905
|
+
}), o++;
|
|
1906
|
+
let r;
|
|
1907
|
+
if (e.length === 1) {
|
|
1908
|
+
const d = await this.apiClient.getDesignStatus(e[0]);
|
|
1909
|
+
r = {
|
|
1910
|
+
designs: [d],
|
|
1911
|
+
allCompleted: d.status !== "processing"
|
|
1912
|
+
};
|
|
1913
|
+
} else
|
|
1914
|
+
r = await this.apiClient.pollMultiViewStatus(e);
|
|
1915
|
+
if (r.allCompleted)
|
|
1916
|
+
return this.dispatchEvent(
|
|
1917
|
+
new CustomEvent("customizer:render-complete", {
|
|
1918
|
+
detail: r,
|
|
1919
|
+
bubbles: !0,
|
|
1920
|
+
composed: !0
|
|
1921
|
+
})
|
|
1922
|
+
), r;
|
|
1923
|
+
}
|
|
1924
|
+
const a = {
|
|
1925
|
+
designs: e.map((r) => ({
|
|
1926
|
+
designId: r,
|
|
1927
|
+
status: "failed",
|
|
1928
|
+
proofUrl: null,
|
|
1929
|
+
errorMessage: "Render timed out"
|
|
1930
|
+
})),
|
|
1931
|
+
allCompleted: !0
|
|
1932
|
+
};
|
|
1933
|
+
return this.dispatchEvent(
|
|
1934
|
+
new CustomEvent("customizer:render-complete", {
|
|
1935
|
+
detail: a,
|
|
1936
|
+
bubbles: !0,
|
|
1937
|
+
composed: !0
|
|
1938
|
+
})
|
|
1939
|
+
), a;
|
|
1702
1940
|
}
|
|
1703
1941
|
// ─── Initialization ───
|
|
1704
1942
|
renderLoading() {
|
|
1705
1943
|
const e = document.createElement("style");
|
|
1706
|
-
e.textContent =
|
|
1944
|
+
e.textContent = k, this.shadow.appendChild(e);
|
|
1707
1945
|
const t = h(
|
|
1708
1946
|
"div",
|
|
1709
1947
|
{ class: "loading-container" },
|
|
@@ -1713,7 +1951,7 @@ class ee extends HTMLElement {
|
|
|
1713
1951
|
}
|
|
1714
1952
|
async initialize() {
|
|
1715
1953
|
const e = this.getAttribute("api-url") || "http://localhost:4000";
|
|
1716
|
-
this.apiClient = new
|
|
1954
|
+
this.apiClient = new B(`${e.replace(/\/+$/, "")}/public`);
|
|
1717
1955
|
const t = this.getAttribute("product-id"), i = this.getAttribute("template-id");
|
|
1718
1956
|
if (!t && !i)
|
|
1719
1957
|
throw new Error("Either template-id or product-id attribute is required");
|
|
@@ -1734,7 +1972,7 @@ class ee extends HTMLElement {
|
|
|
1734
1972
|
async loadTemplateData(e) {
|
|
1735
1973
|
this.currentTemplate = e;
|
|
1736
1974
|
const t = e.areas || [];
|
|
1737
|
-
this.contentManager = new
|
|
1975
|
+
this.contentManager = new P(t);
|
|
1738
1976
|
const i = {
|
|
1739
1977
|
template: e,
|
|
1740
1978
|
contents: this.contentManager.toJSON(),
|
|
@@ -1744,27 +1982,22 @@ class ee extends HTMLElement {
|
|
|
1744
1982
|
isDirty: !1,
|
|
1745
1983
|
warnings: []
|
|
1746
1984
|
};
|
|
1747
|
-
this.stateManager = new
|
|
1985
|
+
this.stateManager = new F(i), this.buildUI(e, t), this.engine = new N(this.canvas), this.engine.setArtboard({
|
|
1748
1986
|
width: e.artboard.width,
|
|
1749
1987
|
height: e.artboard.height,
|
|
1750
1988
|
backgroundColor: e.artboard.backgroundColor ?? "#ffffff"
|
|
1751
1989
|
}), this.engine.setAreas(t), this.engine.setContents(this.contentManager.getContents()), e.backgroundImage && this.engine.setBackgroundImage(e.backgroundImage);
|
|
1752
1990
|
const s = e.artboard.safeArea ?? e.safeArea;
|
|
1753
|
-
s && this.engine.setSafeArea(s), this.engine.start(), this.
|
|
1754
|
-
zoom: this.engine.getZoom(),
|
|
1755
|
-
pan: this.engine.getPan()
|
|
1756
|
-
}), this.zoomToolbar.setZoom(this.engine.getZoom()), this.interaction = new B(
|
|
1991
|
+
s && this.engine.setSafeArea(s), this.engine.start(), this.scheduleCanvasRefit(), this.interaction = new $(
|
|
1757
1992
|
this.canvas,
|
|
1758
1993
|
this.engine,
|
|
1759
1994
|
() => this.contentManager.getContents(),
|
|
1760
1995
|
() => this.stateManager.getState().selectedAreaId
|
|
1761
|
-
), this.wireInteractionEvents(), this.
|
|
1762
|
-
this.contentManager.
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
const a = this.getAttribute("store-id");
|
|
1767
|
-
a && this.apiClient.getStorageUsage(a).then((o) => {
|
|
1996
|
+
), this.wireInteractionEvents(), this.contentManagerUnsub = this.contentManager.subscribe(() => {
|
|
1997
|
+
this.syncEngineContents(), this.inputPanel.setContents(this.contentManager.getContents());
|
|
1998
|
+
}), this.subscriptions.push(this.contentManagerUnsub), this.keyboard = new W(this), this.setupKeyboardShortcuts(), this.inputPanel.setContents(this.contentManager.getContents());
|
|
1999
|
+
const n = this.getAttribute("store-id");
|
|
2000
|
+
n && this.apiClient.getStorageUsage(n).then((o) => {
|
|
1768
2001
|
this.storageUsage = o, o.usagePercent >= 100 ? this.showToast("Storage quota exceeded. Upgrade your plan for more storage.", "error") : o.usagePercent >= 80 && this.showToast(`Storage ${o.usagePercent}% full. Consider upgrading your plan.`, "warning");
|
|
1769
2002
|
}).catch(() => {
|
|
1770
2003
|
});
|
|
@@ -1786,15 +2019,17 @@ class ee extends HTMLElement {
|
|
|
1786
2019
|
const t = e.template.templateJson;
|
|
1787
2020
|
this.currentTemplate = t;
|
|
1788
2021
|
const i = t.areas || [];
|
|
1789
|
-
this.contentManager = new
|
|
2022
|
+
this.contentManager = new P(i), this.contentManagerUnsub && this.contentManagerUnsub(), this.contentManagerUnsub = this.contentManager.subscribe(() => {
|
|
2023
|
+
this.syncEngineContents(), this.inputPanel.setContents(this.contentManager.getContents());
|
|
2024
|
+
});
|
|
1790
2025
|
const s = this.perViewContents.get(e.viewName);
|
|
1791
2026
|
s && this.contentManager.fromJSON(s), this.engine.setArtboard({
|
|
1792
2027
|
width: t.artboard.width,
|
|
1793
2028
|
height: t.artboard.height,
|
|
1794
2029
|
backgroundColor: t.artboard.backgroundColor ?? "#ffffff"
|
|
1795
2030
|
}), this.engine.setAreas(i), this.engine.setContents(this.contentManager.getContents()), this.engine.setBackgroundImage(t.backgroundImage ?? void 0);
|
|
1796
|
-
const
|
|
1797
|
-
this.engine.setSafeArea(
|
|
2031
|
+
const n = t.artboard.safeArea ?? t.safeArea;
|
|
2032
|
+
this.engine.setSafeArea(n ?? null), this.engine.fitToView(), this.stateManager.setState({
|
|
1798
2033
|
template: t,
|
|
1799
2034
|
contents: this.contentManager.toJSON(),
|
|
1800
2035
|
selectedAreaId: null,
|
|
@@ -1813,37 +2048,45 @@ class ee extends HTMLElement {
|
|
|
1813
2048
|
buildUI(e, t) {
|
|
1814
2049
|
this.shadow.innerHTML = "";
|
|
1815
2050
|
const i = document.createElement("style");
|
|
1816
|
-
i.textContent =
|
|
1817
|
-
const
|
|
1818
|
-
|
|
2051
|
+
i.textContent = k, this.shadow.appendChild(i), this.container = h("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 }) => {
|
|
2052
|
+
const a = this.productViews.find((r) => r.viewName === o);
|
|
2053
|
+
a && this.switchToView(a);
|
|
1819
2054
|
})), this.subscriptions.push(this.zoomToolbar.on("close", () => {
|
|
1820
2055
|
this.dispatchEvent(new CustomEvent("customizer:close", {
|
|
1821
2056
|
bubbles: !0,
|
|
1822
2057
|
composed: !0
|
|
1823
2058
|
}));
|
|
1824
2059
|
})), this.subscriptions.push(this.zoomToolbar.on("save", async () => {
|
|
1825
|
-
this.zoomToolbar.setSaveDisabled(!0, "Saving...");
|
|
2060
|
+
this.pendingUploads.size > 0 && (this.zoomToolbar.setSaveDisabled(!0, "Uploading..."), await Promise.allSettled(this.pendingUploads.values())), this.zoomToolbar.setSaveDisabled(!0, "Saving...");
|
|
1826
2061
|
try {
|
|
1827
2062
|
const o = await this.finalize();
|
|
2063
|
+
if (!o) {
|
|
2064
|
+
this.zoomToolbar.setSaveDisabled(!1, "✓");
|
|
2065
|
+
return;
|
|
2066
|
+
}
|
|
1828
2067
|
this.zoomToolbar.setSaveDisabled(!0, "Saved!"), this.showToast("Design saved successfully!", "info"), this.dispatchEvent(new CustomEvent("customizer:save", {
|
|
1829
2068
|
detail: o,
|
|
1830
2069
|
bubbles: !0,
|
|
1831
2070
|
composed: !0
|
|
1832
|
-
}))
|
|
1833
|
-
|
|
2071
|
+
}));
|
|
2072
|
+
const a = o.views ? o.views.map((r) => r.designId) : [o.designId];
|
|
2073
|
+
this.renderAbortController?.abort(), this.renderAbortController = new AbortController(), this.waitForResult(a, { signal: this.renderAbortController.signal }).catch((r) => {
|
|
2074
|
+
r instanceof DOMException && r.name === "AbortError" || console.error("Customizer: background render polling failed", r);
|
|
2075
|
+
}), setTimeout(() => {
|
|
2076
|
+
this.zoomToolbar.setSaveDisabled(!1, "✓"), this.zoomToolbar.showSaveButton(!1), this.stateManager.update({ isDirty: !1 });
|
|
1834
2077
|
}, 2e3);
|
|
1835
2078
|
} catch (o) {
|
|
1836
2079
|
console.error("Customizer: finalize failed", o);
|
|
1837
|
-
const
|
|
1838
|
-
this.showToast(
|
|
2080
|
+
const a = o instanceof Error ? o.message : "Save failed. Please try again.";
|
|
2081
|
+
this.showToast(a, "error"), this.zoomToolbar.setSaveDisabled(!1, "✓");
|
|
1839
2082
|
}
|
|
1840
2083
|
})), this.getAttribute("show-close-button") === "false" && this.zoomToolbar.setCloseButtonVisible(!1), this.getAttribute("show-save-button") === "false" && this.zoomToolbar.setSaveButtonEnabled(!1);
|
|
1841
2084
|
const s = h("div", { class: "canvas-area" });
|
|
1842
|
-
s.appendChild(this.zoomToolbar.getElement()), this.canvasWrapper = h("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
|
|
1843
|
-
const
|
|
1844
|
-
|
|
1845
|
-
for (const
|
|
1846
|
-
|
|
2085
|
+
s.appendChild(this.zoomToolbar.getElement()), this.canvasWrapper = h("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(t), this.wireInputPanelEvents();
|
|
2086
|
+
const n = h("div", { class: "editor-main" });
|
|
2087
|
+
n.appendChild(s), n.appendChild(this.inputPanel.getBackdrop()), n.appendChild(this.inputPanel.getElement()), this.container.appendChild(n), this.shadow.appendChild(this.container), this.resizeObserver = new ResizeObserver((o) => {
|
|
2088
|
+
for (const a of o)
|
|
2089
|
+
a.target === this.canvasWrapper && this.handleCanvasResize(a.contentRect), a.target === this.container && this.handleLayoutResize(a.contentRect);
|
|
1847
2090
|
}), this.resizeObserver.observe(this.canvasWrapper), this.resizeObserver.observe(this.container);
|
|
1848
2091
|
}
|
|
1849
2092
|
// ─── Event Wiring ───
|
|
@@ -1892,10 +2135,10 @@ class ee extends HTMLElement {
|
|
|
1892
2135
|
), this.subscriptions.push(
|
|
1893
2136
|
this.inputPanel.on("image:change", async ({ areaId: e, dataUrl: t, filename: i }) => {
|
|
1894
2137
|
const s = this.contentManager.getContents();
|
|
1895
|
-
let
|
|
1896
|
-
for (const [
|
|
1897
|
-
|
|
1898
|
-
if (
|
|
2138
|
+
let n = 0;
|
|
2139
|
+
for (const [r, d] of s)
|
|
2140
|
+
d.type === "image" && r !== e && n++;
|
|
2141
|
+
if (n >= 10) {
|
|
1899
2142
|
this.showToast("Maximum of 10 images per design reached", "error");
|
|
1900
2143
|
return;
|
|
1901
2144
|
}
|
|
@@ -1903,28 +2146,30 @@ class ee extends HTMLElement {
|
|
|
1903
2146
|
this.showToast("Storage quota exceeded. Upgrade your plan for more storage.", "error");
|
|
1904
2147
|
return;
|
|
1905
2148
|
}
|
|
1906
|
-
const o = this.currentTemplate?.areas.find((
|
|
2149
|
+
const o = this.currentTemplate?.areas.find((r) => r.id === e);
|
|
1907
2150
|
if (o && this.currentTemplate) {
|
|
1908
|
-
const
|
|
2151
|
+
const r = this.currentTemplate.print.targetDpi;
|
|
1909
2152
|
try {
|
|
1910
|
-
const
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
d.width,
|
|
1914
|
-
|
|
1915
|
-
l,
|
|
2153
|
+
const d = new Image();
|
|
2154
|
+
d.crossOrigin = "anonymous";
|
|
2155
|
+
const l = await new Promise((g, m) => {
|
|
2156
|
+
d.onload = () => g({ width: d.naturalWidth, height: d.naturalHeight }), d.onerror = () => m(new Error("Failed to load image")), d.src = t;
|
|
2157
|
+
}), c = o.location.width / r, p = o.location.height / r, u = Q(
|
|
2158
|
+
l.width,
|
|
2159
|
+
l.height,
|
|
1916
2160
|
c,
|
|
1917
|
-
|
|
2161
|
+
p,
|
|
2162
|
+
r
|
|
1918
2163
|
);
|
|
1919
2164
|
if (u.actualDPI < 150) {
|
|
1920
2165
|
if (!await this.showConfirmation(
|
|
1921
2166
|
"Low resolution detected",
|
|
1922
|
-
`This image is ${u.actualDPI} DPI at print size. Print may appear blurry. Recommended: ${
|
|
2167
|
+
`This image is ${u.actualDPI} DPI at print size. Print may appear blurry. Recommended: ${r} DPI.`,
|
|
1923
2168
|
"Cancel",
|
|
1924
2169
|
"Use Anyway"
|
|
1925
2170
|
)) return;
|
|
1926
|
-
} else u.actualDPI <
|
|
1927
|
-
`Image resolution is ${u.actualDPI} DPI (${
|
|
2171
|
+
} else u.actualDPI < r && (this.showToast(
|
|
2172
|
+
`Image resolution is ${u.actualDPI} DPI (${r} recommended)`,
|
|
1928
2173
|
"warning"
|
|
1929
2174
|
), this.stateManager.update({
|
|
1930
2175
|
warnings: [
|
|
@@ -1935,9 +2180,11 @@ class ee extends HTMLElement {
|
|
|
1935
2180
|
} catch {
|
|
1936
2181
|
}
|
|
1937
2182
|
}
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
2183
|
+
this.contentManager.setImageContent(e, t, i), this.saveContentState();
|
|
2184
|
+
const a = this.getAttribute("store-id");
|
|
2185
|
+
if (a && this.preUploadImage(e, t, a), this.storageUsage) {
|
|
2186
|
+
const r = Math.round(t.length * 0.75);
|
|
2187
|
+
this.storageUsage.storageUsed += r, this.storageUsage.usagePercent = this.storageUsage.storageQuota > 0 ? Math.round(this.storageUsage.storageUsed / this.storageUsage.storageQuota * 100) : 0;
|
|
1941
2188
|
}
|
|
1942
2189
|
})
|
|
1943
2190
|
), this.subscriptions.push(
|
|
@@ -1946,7 +2193,7 @@ class ee extends HTMLElement {
|
|
|
1946
2193
|
})
|
|
1947
2194
|
), this.subscriptions.push(
|
|
1948
2195
|
this.inputPanel.on("clear", ({ areaId: e }) => {
|
|
1949
|
-
this.contentManager.clearContent(e), this.saveContentState();
|
|
2196
|
+
this.contentManager.clearContent(e), this.syncEngineContents(), this.inputPanel.setContents(this.contentManager.getContents()), this.saveContentState();
|
|
1950
2197
|
})
|
|
1951
2198
|
), this.subscriptions.push(
|
|
1952
2199
|
this.inputPanel.on("select", ({ areaId: e }) => {
|
|
@@ -1994,6 +2241,19 @@ class ee extends HTMLElement {
|
|
|
1994
2241
|
handleZoomFit() {
|
|
1995
2242
|
this.engine.fitToView(), this.stateManager.update({ zoom: this.engine.getZoom(), pan: this.engine.getPan() }), this.zoomToolbar.setZoom(this.engine.getZoom());
|
|
1996
2243
|
}
|
|
2244
|
+
/**
|
|
2245
|
+
* Schedule a canvas refit after layout settles (double-rAF ensures
|
|
2246
|
+
* ResizeObserver callbacks and layout-class changes have been applied).
|
|
2247
|
+
*/
|
|
2248
|
+
scheduleCanvasRefit() {
|
|
2249
|
+
this.refitRafId != null && cancelAnimationFrame(this.refitRafId), this.refitRafId = requestAnimationFrame(() => {
|
|
2250
|
+
this.refitRafId = requestAnimationFrame(() => {
|
|
2251
|
+
if (this.refitRafId = null, !this.canvasWrapper || !this.engine) return;
|
|
2252
|
+
const e = this.canvasWrapper.getBoundingClientRect();
|
|
2253
|
+
e.width > 0 && e.height > 0 && this.handleCanvasResize(e);
|
|
2254
|
+
});
|
|
2255
|
+
});
|
|
2256
|
+
}
|
|
1997
2257
|
handleCanvasResize(e) {
|
|
1998
2258
|
const { width: t, height: i } = e;
|
|
1999
2259
|
if (t === 0 || i === 0) return;
|
|
@@ -2001,8 +2261,8 @@ class ee extends HTMLElement {
|
|
|
2001
2261
|
this.canvas.width = t * s, this.canvas.height = i * s, this.canvas.style.width = `${t}px`, this.canvas.style.height = `${i}px`, this.engine && (this.engine.fitToView(), this.stateManager.update({ zoom: this.engine.getZoom(), pan: this.engine.getPan() }), this.zoomToolbar.setZoom(this.engine.getZoom()));
|
|
2002
2262
|
}
|
|
2003
2263
|
handleLayoutResize(e) {
|
|
2004
|
-
const t = e.width <
|
|
2005
|
-
this.container.classList.toggle("layout-desktop", !t), this.container.classList.toggle("layout-mobile", t), t !== this.isMobileLayout && (this.isMobileLayout = t, this.inputPanel.setMobile(t));
|
|
2264
|
+
const t = e.width < ee;
|
|
2265
|
+
this.container.classList.toggle("layout-desktop", !t), this.container.classList.toggle("layout-mobile", t), t !== this.isMobileLayout && (this.isMobileLayout = t, this.inputPanel.setMobile(t), this.scheduleCanvasRefit());
|
|
2006
2266
|
}
|
|
2007
2267
|
// ─── State Management ───
|
|
2008
2268
|
async refreshStorageUsage() {
|
|
@@ -2014,6 +2274,42 @@ class ee extends HTMLElement {
|
|
|
2014
2274
|
} catch {
|
|
2015
2275
|
}
|
|
2016
2276
|
}
|
|
2277
|
+
/**
|
|
2278
|
+
* Pre-upload an image to the server so finalize doesn't need to send base64.
|
|
2279
|
+
* On success, updates the content manager with assetId/assetUrl.
|
|
2280
|
+
* Tracks the promise so finalize() can await it.
|
|
2281
|
+
*/
|
|
2282
|
+
preUploadImage(e, t, i) {
|
|
2283
|
+
const s = this.activeViewName, n = this._doPreUpload(e, t, i, s);
|
|
2284
|
+
return this.pendingUploads.set(e, n), this.inputPanel?.setUploading(e, !0), this.updateSaveButtonForUploads(), n.finally(() => {
|
|
2285
|
+
this.pendingUploads.get(e) === n && this.pendingUploads.delete(e), this.inputPanel?.setUploading(e, !1), this.updateSaveButtonForUploads();
|
|
2286
|
+
}), n;
|
|
2287
|
+
}
|
|
2288
|
+
async _doPreUpload(e, t, i, s) {
|
|
2289
|
+
const [n, o] = t.split(","), a = n.match(/:(.*?);/)?.[1] || "image/png", r = atob(o), d = new Uint8Array(r.length);
|
|
2290
|
+
for (let g = 0; g < r.length; g++) d[g] = r.charCodeAt(g);
|
|
2291
|
+
const l = new Blob([d], { type: a }), c = a.split("/")[1] || "png", p = new File([l], `upload.${c}`, { type: a });
|
|
2292
|
+
let u;
|
|
2293
|
+
try {
|
|
2294
|
+
u = await this.apiClient.uploadAsset(p, i);
|
|
2295
|
+
} catch (g) {
|
|
2296
|
+
console.error("Customizer: image pre-upload failed", g), this.showToast("Image upload failed. Will retry on save.", "warning");
|
|
2297
|
+
return;
|
|
2298
|
+
}
|
|
2299
|
+
if (this.activeViewName === s) {
|
|
2300
|
+
const g = this.contentManager.getContent(e);
|
|
2301
|
+
g?.type === "image" && g.dataUrl === t && this.contentManager.setImageContent(e, u.publicUrl, g.filename, {
|
|
2302
|
+
assetId: u.assetId,
|
|
2303
|
+
assetUrl: u.publicUrl
|
|
2304
|
+
});
|
|
2305
|
+
} else if (s) {
|
|
2306
|
+
const g = this.perViewContents.get(s);
|
|
2307
|
+
if (g) {
|
|
2308
|
+
const m = g.map(([v, b]) => v === e && b.type === "image" && b.dataUrl === t ? [v, { ...b, dataUrl: u.publicUrl, assetId: u.assetId, assetUrl: u.publicUrl }] : [v, b]);
|
|
2309
|
+
this.perViewContents.set(s, m);
|
|
2310
|
+
}
|
|
2311
|
+
}
|
|
2312
|
+
}
|
|
2017
2313
|
saveContentState() {
|
|
2018
2314
|
this.stateManager.setState({
|
|
2019
2315
|
contents: this.contentManager.toJSON(),
|
|
@@ -2029,7 +2325,7 @@ class ee extends HTMLElement {
|
|
|
2029
2325
|
}
|
|
2030
2326
|
// ─── Keyboard ───
|
|
2031
2327
|
setupKeyboardShortcuts() {
|
|
2032
|
-
const e =
|
|
2328
|
+
const e = G();
|
|
2033
2329
|
this.keyboard.register({
|
|
2034
2330
|
key: "z",
|
|
2035
2331
|
[e]: !0,
|
|
@@ -2044,24 +2340,27 @@ class ee extends HTMLElement {
|
|
|
2044
2340
|
handler: () => this.handleAreaSelect(null)
|
|
2045
2341
|
});
|
|
2046
2342
|
}
|
|
2343
|
+
updateSaveButtonForUploads() {
|
|
2344
|
+
this.zoomToolbar && this.stateManager?.getState().isDirty && (this.pendingUploads.size > 0 ? this.zoomToolbar.setSaveDisabled(!0, "Uploading...") : this.zoomToolbar.setSaveDisabled(!1, "✓"));
|
|
2345
|
+
}
|
|
2047
2346
|
// ─── Toast & Modal ───
|
|
2048
2347
|
showToast(e, t = "info") {
|
|
2049
2348
|
const i = h("div", { class: `editor-toast editor-toast-${t}` }, e);
|
|
2050
2349
|
this.shadow.appendChild(i), setTimeout(() => i.remove(), 4e3);
|
|
2051
2350
|
}
|
|
2052
2351
|
showConfirmation(e, t, i = "Cancel", s = "Continue") {
|
|
2053
|
-
return new Promise((
|
|
2054
|
-
const o = h("div", { class: "editor-modal-overlay" }),
|
|
2055
|
-
|
|
2352
|
+
return new Promise((n) => {
|
|
2353
|
+
const o = h("div", { class: "editor-modal-overlay" }), a = h("div", { class: "editor-modal" });
|
|
2354
|
+
a.appendChild(h("div", { class: "editor-modal-title" }, e)), a.appendChild(h("div", { class: "editor-modal-body" }, t));
|
|
2056
2355
|
const r = h("div", { class: "editor-modal-actions" }), d = h("button", { class: "editor-modal-btn" }, i);
|
|
2057
2356
|
d.addEventListener("click", () => {
|
|
2058
|
-
o.remove(),
|
|
2357
|
+
o.remove(), n(!1);
|
|
2059
2358
|
});
|
|
2060
2359
|
const l = h("button", { class: "editor-modal-btn editor-modal-btn-primary" }, s);
|
|
2061
2360
|
l.addEventListener("click", () => {
|
|
2062
|
-
o.remove(),
|
|
2063
|
-
}), r.appendChild(d), r.appendChild(l),
|
|
2064
|
-
c.target === o && (o.remove(),
|
|
2361
|
+
o.remove(), n(!0);
|
|
2362
|
+
}), r.appendChild(d), r.appendChild(l), a.appendChild(r), o.appendChild(a), o.addEventListener("click", (c) => {
|
|
2363
|
+
c.target === o && (o.remove(), n(!1));
|
|
2065
2364
|
}), this.shadow.appendChild(o);
|
|
2066
2365
|
});
|
|
2067
2366
|
}
|
|
@@ -2069,7 +2368,7 @@ class ee extends HTMLElement {
|
|
|
2069
2368
|
showError(e, t) {
|
|
2070
2369
|
this.shadow.innerHTML = "";
|
|
2071
2370
|
const i = document.createElement("style");
|
|
2072
|
-
i.textContent =
|
|
2371
|
+
i.textContent = k, this.shadow.appendChild(i);
|
|
2073
2372
|
const s = h("div", { class: "error-container" });
|
|
2074
2373
|
s.appendChild(h("div", { class: "error-title" }, e)), s.appendChild(h("div", { class: "error-message" }, t?.message ?? "")), this.shadow.appendChild(s), this.dispatchEvent(
|
|
2075
2374
|
new CustomEvent("error", {
|
|
@@ -2080,12 +2379,13 @@ class ee extends HTMLElement {
|
|
|
2080
2379
|
);
|
|
2081
2380
|
}
|
|
2082
2381
|
cleanup() {
|
|
2382
|
+
this.renderAbortController?.abort(), this.renderAbortController = null;
|
|
2083
2383
|
for (const e of this.subscriptions)
|
|
2084
2384
|
e();
|
|
2085
|
-
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();
|
|
2385
|
+
this.subscriptions = [], this.refitRafId != null && (cancelAnimationFrame(this.refitRafId), this.refitRafId = null), 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();
|
|
2086
2386
|
}
|
|
2087
2387
|
}
|
|
2088
|
-
customElements.get("customizer-editor") || customElements.define("customizer-editor",
|
|
2388
|
+
customElements.get("customizer-editor") || customElements.define("customizer-editor", I);
|
|
2089
2389
|
function te(f, e) {
|
|
2090
2390
|
const t = typeof f == "string" ? document.querySelector(f) : f;
|
|
2091
2391
|
if (!t)
|
|
@@ -2098,8 +2398,8 @@ function te(f, e) {
|
|
|
2098
2398
|
throw new Error("Only one of templateId or productId should be provided, not both");
|
|
2099
2399
|
const i = document.createElement("customizer-editor");
|
|
2100
2400
|
e.productId ? i.setAttribute("product-id", e.productId) : i.setAttribute("template-id", e.templateId), e.theme && i.setAttribute("theme", e.theme), e.mode && i.setAttribute("mode", e.mode), e.className && i.classList.add(e.className), e.showCloseButton === !1 && i.setAttribute("show-close-button", "false"), e.showSaveButton === !1 && i.setAttribute("show-save-button", "false"), i.style.width = "100%", i.style.height = "100%";
|
|
2101
|
-
const s = [],
|
|
2102
|
-
i.addEventListener(
|
|
2401
|
+
const s = [], n = (a, r) => {
|
|
2402
|
+
i.addEventListener(a, r), s.push({ event: a, handler: r });
|
|
2103
2403
|
};
|
|
2104
2404
|
t.innerHTML = "", t.appendChild(i);
|
|
2105
2405
|
const o = {
|
|
@@ -2108,10 +2408,10 @@ function te(f, e) {
|
|
|
2108
2408
|
throw new Error("Editor not ready: getDesign method not available");
|
|
2109
2409
|
return i.getDesign();
|
|
2110
2410
|
},
|
|
2111
|
-
setDesign(
|
|
2411
|
+
setDesign(a) {
|
|
2112
2412
|
if (typeof i.setDesign != "function")
|
|
2113
2413
|
throw new Error("Editor not ready: setDesign method not available");
|
|
2114
|
-
i.setDesign(
|
|
2414
|
+
i.setDesign(a);
|
|
2115
2415
|
},
|
|
2116
2416
|
undo() {
|
|
2117
2417
|
if (typeof i.undo != "function")
|
|
@@ -2130,77 +2430,116 @@ function te(f, e) {
|
|
|
2130
2430
|
return typeof i.canRedo != "function" ? !1 : i.canRedo();
|
|
2131
2431
|
},
|
|
2132
2432
|
async finalize() {
|
|
2133
|
-
const
|
|
2433
|
+
const a = e.apiUrl || "https://api.varianta.io";
|
|
2134
2434
|
try {
|
|
2135
|
-
|
|
2435
|
+
if (e.productId && typeof i.finalize == "function") {
|
|
2436
|
+
const p = await i.finalize(), u = {
|
|
2437
|
+
designId: p.designId,
|
|
2438
|
+
status: "processing",
|
|
2439
|
+
proofUrl: p.proofUrl ?? null,
|
|
2440
|
+
errorMessage: null,
|
|
2441
|
+
requestId: p.requestId,
|
|
2442
|
+
views: p.views
|
|
2443
|
+
};
|
|
2444
|
+
return e.onFinalize && e.onFinalize(u), u;
|
|
2445
|
+
}
|
|
2446
|
+
const r = this.getDesign(), d = await fetch(`${a}/public/finalize`, {
|
|
2136
2447
|
method: "POST",
|
|
2137
2448
|
headers: {
|
|
2138
2449
|
"Content-Type": "application/json"
|
|
2139
2450
|
},
|
|
2140
2451
|
body: JSON.stringify({
|
|
2141
|
-
templateId:
|
|
2142
|
-
designJson:
|
|
2452
|
+
templateId: r.templateId || e.templateId,
|
|
2453
|
+
designJson: r
|
|
2143
2454
|
})
|
|
2144
2455
|
});
|
|
2145
2456
|
if (!d.ok) {
|
|
2146
|
-
const
|
|
2457
|
+
const p = await d.json().catch(() => ({
|
|
2147
2458
|
message: "Finalization failed"
|
|
2148
2459
|
}));
|
|
2149
|
-
throw new Error(
|
|
2460
|
+
throw new Error(p.message || "Finalization failed");
|
|
2150
2461
|
}
|
|
2151
|
-
const l = await d.json()
|
|
2152
|
-
let c = {
|
|
2462
|
+
const l = await d.json(), c = {
|
|
2153
2463
|
designId: l.designId,
|
|
2154
2464
|
status: l.status,
|
|
2155
2465
|
proofUrl: l.proofUrl ?? null,
|
|
2156
2466
|
errorMessage: l.errorMessage ?? null
|
|
2157
2467
|
};
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
await new Promise((b) => setTimeout(b, u)), g++;
|
|
2162
|
-
const m = await fetch(
|
|
2163
|
-
`${r}/public/designs/${c.designId}/status`
|
|
2164
|
-
);
|
|
2165
|
-
if (!m.ok)
|
|
2166
|
-
throw new Error("Failed to check design status");
|
|
2167
|
-
const v = await m.json();
|
|
2168
|
-
c = {
|
|
2169
|
-
designId: v.designId,
|
|
2170
|
-
status: v.status,
|
|
2171
|
-
proofUrl: v.proofUrl ?? null,
|
|
2172
|
-
errorMessage: v.errorMessage ?? null
|
|
2173
|
-
};
|
|
2174
|
-
}
|
|
2175
|
-
return c.status === "processing" && (c = { ...c, status: "failed", errorMessage: "Render timed out" }), e.onFinalize && e.onFinalize(c), c;
|
|
2176
|
-
} catch (d) {
|
|
2177
|
-
const l = {
|
|
2468
|
+
return e.onFinalize && e.onFinalize(c), c;
|
|
2469
|
+
} catch (r) {
|
|
2470
|
+
const d = {
|
|
2178
2471
|
code: "FINALIZE_ERROR",
|
|
2179
|
-
message:
|
|
2180
|
-
details:
|
|
2472
|
+
message: r instanceof Error ? r.message : "Unknown error",
|
|
2473
|
+
details: r
|
|
2181
2474
|
};
|
|
2182
|
-
throw e.onError && e.onError(
|
|
2475
|
+
throw e.onError && e.onError(d), r;
|
|
2476
|
+
}
|
|
2477
|
+
},
|
|
2478
|
+
async waitForResult(a, r) {
|
|
2479
|
+
if (typeof i.waitForResult == "function") {
|
|
2480
|
+
const m = await i.waitForResult(a, r);
|
|
2481
|
+
return e.onRenderComplete && e.onRenderComplete(m), m;
|
|
2183
2482
|
}
|
|
2483
|
+
const d = e.apiUrl || "https://api.varianta.io", l = r?.pollInterval ?? 1500, c = r?.maxPolls ?? 40, p = r?.signal;
|
|
2484
|
+
let u = 0;
|
|
2485
|
+
for (; u < c; ) {
|
|
2486
|
+
if (p?.aborted)
|
|
2487
|
+
throw new DOMException("Polling aborted", "AbortError");
|
|
2488
|
+
await new Promise((v, b) => {
|
|
2489
|
+
const w = setTimeout(v, l);
|
|
2490
|
+
p?.addEventListener("abort", () => {
|
|
2491
|
+
clearTimeout(w), b(new DOMException("Polling aborted", "AbortError"));
|
|
2492
|
+
}, { once: !0 });
|
|
2493
|
+
}), u++;
|
|
2494
|
+
let m;
|
|
2495
|
+
if (a.length === 1) {
|
|
2496
|
+
const v = await fetch(`${d}/public/designs/${a[0]}/status`);
|
|
2497
|
+
if (!v.ok) throw new Error("Failed to check design status");
|
|
2498
|
+
const b = await v.json();
|
|
2499
|
+
m = {
|
|
2500
|
+
designs: [b],
|
|
2501
|
+
allCompleted: b.status !== "processing"
|
|
2502
|
+
};
|
|
2503
|
+
} else {
|
|
2504
|
+
const v = await fetch(
|
|
2505
|
+
`${d}/public/finalize-status?designIds=${a.join(",")}`
|
|
2506
|
+
);
|
|
2507
|
+
if (!v.ok) throw new Error("Failed to check design status");
|
|
2508
|
+
m = await v.json();
|
|
2509
|
+
}
|
|
2510
|
+
if (m.allCompleted)
|
|
2511
|
+
return e.onRenderComplete && e.onRenderComplete(m), m;
|
|
2512
|
+
}
|
|
2513
|
+
const g = {
|
|
2514
|
+
designs: a.map((m) => ({
|
|
2515
|
+
designId: m,
|
|
2516
|
+
status: "failed",
|
|
2517
|
+
proofUrl: null,
|
|
2518
|
+
errorMessage: "Render timed out"
|
|
2519
|
+
})),
|
|
2520
|
+
allCompleted: !0
|
|
2521
|
+
};
|
|
2522
|
+
return e.onRenderComplete && e.onRenderComplete(g), g;
|
|
2184
2523
|
},
|
|
2185
|
-
addTextLayer(
|
|
2524
|
+
addTextLayer(a) {
|
|
2186
2525
|
if (typeof i.addTextLayer != "function")
|
|
2187
2526
|
throw new Error("Editor not ready: addTextLayer method not available");
|
|
2188
|
-
i.addTextLayer(
|
|
2527
|
+
i.addTextLayer(a);
|
|
2189
2528
|
},
|
|
2190
|
-
async addImageLayer(
|
|
2529
|
+
async addImageLayer(a) {
|
|
2191
2530
|
if (typeof i.addImageLayer != "function")
|
|
2192
2531
|
throw new Error("Editor not ready: addImageLayer method not available");
|
|
2193
|
-
return i.addImageLayer(
|
|
2532
|
+
return i.addImageLayer(a);
|
|
2194
2533
|
},
|
|
2195
|
-
removeLayer(
|
|
2534
|
+
removeLayer(a) {
|
|
2196
2535
|
if (typeof i.removeLayer != "function")
|
|
2197
2536
|
throw new Error("Editor not ready: removeLayer method not available");
|
|
2198
|
-
i.removeLayer(
|
|
2537
|
+
i.removeLayer(a);
|
|
2199
2538
|
},
|
|
2200
|
-
selectLayer(
|
|
2539
|
+
selectLayer(a) {
|
|
2201
2540
|
if (typeof i.selectLayer != "function")
|
|
2202
2541
|
throw new Error("Editor not ready: selectLayer method not available");
|
|
2203
|
-
i.selectLayer(
|
|
2542
|
+
i.selectLayer(a);
|
|
2204
2543
|
},
|
|
2205
2544
|
getSelectedLayerId() {
|
|
2206
2545
|
return typeof i.getSelectedLayerId != "function" ? null : i.getSelectedLayerId();
|
|
@@ -2211,48 +2550,50 @@ function te(f, e) {
|
|
|
2211
2550
|
getViews() {
|
|
2212
2551
|
return typeof i.getViews != "function" ? [] : i.getViews();
|
|
2213
2552
|
},
|
|
2214
|
-
setActiveView(
|
|
2553
|
+
setActiveView(a) {
|
|
2215
2554
|
if (typeof i.setActiveView != "function")
|
|
2216
2555
|
throw new Error("Editor not ready: setActiveView method not available");
|
|
2217
|
-
i.setActiveView(
|
|
2556
|
+
i.setActiveView(a);
|
|
2218
2557
|
},
|
|
2219
|
-
setTheme(
|
|
2220
|
-
i.setAttribute("theme",
|
|
2558
|
+
setTheme(a) {
|
|
2559
|
+
i.setAttribute("theme", a);
|
|
2221
2560
|
},
|
|
2222
|
-
setMode(
|
|
2223
|
-
i.setAttribute("mode",
|
|
2561
|
+
setMode(a) {
|
|
2562
|
+
i.setAttribute("mode", a);
|
|
2224
2563
|
},
|
|
2225
2564
|
destroy() {
|
|
2226
|
-
s.forEach(({ event:
|
|
2227
|
-
i.removeEventListener(
|
|
2565
|
+
s.forEach(({ event: a, handler: r }) => {
|
|
2566
|
+
i.removeEventListener(a, r);
|
|
2228
2567
|
}), i.parentNode && i.parentNode.removeChild(i);
|
|
2229
2568
|
},
|
|
2230
2569
|
getElement() {
|
|
2231
2570
|
return i;
|
|
2232
2571
|
}
|
|
2233
2572
|
};
|
|
2234
|
-
return e.onReady &&
|
|
2573
|
+
return e.onReady && n("ready", (() => {
|
|
2235
2574
|
e.onReady?.(o);
|
|
2236
|
-
})), e.onChange &&
|
|
2237
|
-
const r =
|
|
2575
|
+
})), e.onChange && n("change", ((a) => {
|
|
2576
|
+
const r = a.detail.design;
|
|
2238
2577
|
e.onChange?.(r);
|
|
2239
|
-
})), e.onLayerSelect &&
|
|
2240
|
-
e.onLayerSelect?.(
|
|
2241
|
-
})), e.onLayerAdd &&
|
|
2242
|
-
e.onLayerAdd?.(
|
|
2243
|
-
})), e.onLayerRemove &&
|
|
2244
|
-
e.onLayerRemove?.(
|
|
2245
|
-
})), e.onLayerUpdate &&
|
|
2246
|
-
e.onLayerUpdate?.(
|
|
2247
|
-
})), e.onError &&
|
|
2248
|
-
const r =
|
|
2578
|
+
})), e.onLayerSelect && n("layer:select", ((a) => {
|
|
2579
|
+
e.onLayerSelect?.(a.detail.layerId);
|
|
2580
|
+
})), e.onLayerAdd && n("layer:add", ((a) => {
|
|
2581
|
+
e.onLayerAdd?.(a.detail.layerId);
|
|
2582
|
+
})), e.onLayerRemove && n("layer:remove", ((a) => {
|
|
2583
|
+
e.onLayerRemove?.(a.detail.layerId);
|
|
2584
|
+
})), e.onLayerUpdate && n("layer:update", ((a) => {
|
|
2585
|
+
e.onLayerUpdate?.(a.detail.layerId);
|
|
2586
|
+
})), e.onError && n("error", ((a) => {
|
|
2587
|
+
const r = a.detail.error;
|
|
2249
2588
|
e.onError?.(r);
|
|
2250
|
-
})), e.onViewChange &&
|
|
2251
|
-
e.onViewChange?.(
|
|
2252
|
-
})), e.
|
|
2589
|
+
})), e.onViewChange && n("view-change", ((a) => {
|
|
2590
|
+
e.onViewChange?.(a.detail.viewName);
|
|
2591
|
+
})), e.onRenderComplete && n("customizer:render-complete", ((a) => {
|
|
2592
|
+
e.onRenderComplete?.(a.detail);
|
|
2593
|
+
})), e.onClose && n("customizer:close", (() => {
|
|
2253
2594
|
e.onClose?.();
|
|
2254
|
-
})), e.onSave &&
|
|
2255
|
-
e.onSave?.(
|
|
2595
|
+
})), e.onSave && n("customizer:save", ((a) => {
|
|
2596
|
+
e.onSave?.(a.detail);
|
|
2256
2597
|
})), e.initialDesign && i.addEventListener(
|
|
2257
2598
|
"ready",
|
|
2258
2599
|
() => {
|