@varianta/sdk 0.1.3 → 0.1.4
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 +655 -778
- package/dist/index.esm.js.map +1 -1
- package/dist/index.umd.js +6 -4
- package/dist/index.umd.js.map +1 -1
- package/dist/types/index.d.ts +1 -11
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/react/Customizer.d.ts +11 -2
- package/dist/types/react/Customizer.d.ts.map +1 -1
- package/dist/types/types/index.d.ts +36 -2
- package/dist/types/types/index.d.ts.map +1 -1
- package/dist/types/vanilla/index.d.ts.map +1 -1
- package/package.json +2 -30
package/dist/index.esm.js
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
import { forwardRef as Z, useRef as k, useEffect as L, useImperativeHandle as q, useState as S, useCallback as M } from "react";
|
|
3
|
-
class B {
|
|
1
|
+
class N {
|
|
4
2
|
constructor(e) {
|
|
5
3
|
this.animationFrameId = null, this.needsRender = !0, this.areas = [], this.contents = /* @__PURE__ */ new Map(), this.artboard = null, this.bgImageElement = null, this.loadedImages = /* @__PURE__ */ new Map(), this.loadingImages = /* @__PURE__ */ new Set(), this.zoom = 1, this.pan = { x: 0, y: 0 }, this.selectedAreaId = null, this.hoveredHandle = null, this.eventListeners = /* @__PURE__ */ new Map(), this.safeArea = null, this.safeAreaViolations = /* @__PURE__ */ new Set(), this.showAreaBorders = !0, this.areaSelectionEnabled = !0, this.renderLoop = () => {
|
|
6
4
|
try {
|
|
@@ -131,10 +129,10 @@ class B {
|
|
|
131
129
|
getCenteredPan(e) {
|
|
132
130
|
if (!this.artboard)
|
|
133
131
|
return null;
|
|
134
|
-
const { width: t, height: i } = this.artboard, n = window.devicePixelRatio || 1,
|
|
132
|
+
const { width: t, height: i } = this.artboard, n = window.devicePixelRatio || 1, a = this.canvas.width / n, o = this.canvas.height / n;
|
|
135
133
|
return {
|
|
136
|
-
x: (
|
|
137
|
-
y: (
|
|
134
|
+
x: (a / e - t) / 2,
|
|
135
|
+
y: (o / e - i) / 2
|
|
138
136
|
};
|
|
139
137
|
}
|
|
140
138
|
/**
|
|
@@ -149,11 +147,11 @@ class B {
|
|
|
149
147
|
fitToView() {
|
|
150
148
|
if (!this.artboard)
|
|
151
149
|
return;
|
|
152
|
-
const e = 0, { width: t, height: i } = this.artboard, n = window.devicePixelRatio || 1,
|
|
153
|
-
if (
|
|
150
|
+
const e = 0, { width: t, height: i } = this.artboard, n = window.devicePixelRatio || 1, a = this.canvas.width / n - e * 2, o = this.canvas.height / n - e * 2;
|
|
151
|
+
if (a <= 0 || o <= 0)
|
|
154
152
|
return;
|
|
155
|
-
const
|
|
156
|
-
this.zoom = d, this.pan = { x: u, y:
|
|
153
|
+
const s = a / t, r = o / i, d = Math.min(s, r), l = this.canvas.width / n, c = this.canvas.height / n, u = (l / d - t) / 2, p = (c / d - i) / 2;
|
|
154
|
+
this.zoom = d, this.pan = { x: u, y: p }, this.requestRender();
|
|
157
155
|
}
|
|
158
156
|
/**
|
|
159
157
|
* Set the selected area
|
|
@@ -183,8 +181,8 @@ class B {
|
|
|
183
181
|
* Convert screen coordinates to canvas coordinates
|
|
184
182
|
*/
|
|
185
183
|
screenToCanvas(e, t) {
|
|
186
|
-
const i = window.devicePixelRatio || 1, n = e / i,
|
|
187
|
-
return { x:
|
|
184
|
+
const i = window.devicePixelRatio || 1, n = e / i, a = t / i, o = n / this.zoom - this.pan.x, s = a / this.zoom - this.pan.y;
|
|
185
|
+
return { x: o, y: s };
|
|
188
186
|
}
|
|
189
187
|
/**
|
|
190
188
|
* Get content bounds for an area (in artboard coordinates)
|
|
@@ -196,34 +194,34 @@ class B {
|
|
|
196
194
|
const i = this.contents.get(e);
|
|
197
195
|
if (!i)
|
|
198
196
|
return null;
|
|
199
|
-
const { location: n } = t, { x:
|
|
197
|
+
const { location: n } = t, { x: a, y: o, width: s, height: r } = n;
|
|
200
198
|
if (i.type === "text") {
|
|
201
|
-
const d = i.offset || { x: 0, y: 0 }, l = i.scale ?? 1,
|
|
199
|
+
const d = i.offset || { x: 0, y: 0 }, l = i.scale ?? 1, c = i.rotation ?? 0, u = s * l, p = r * l, g = a + s / 2 + d.x, m = o + r / 2 + d.y;
|
|
202
200
|
return {
|
|
203
|
-
x:
|
|
204
|
-
y:
|
|
201
|
+
x: g - u / 2,
|
|
202
|
+
y: m - p / 2,
|
|
205
203
|
width: u,
|
|
206
|
-
height:
|
|
207
|
-
rotation:
|
|
208
|
-
centerX:
|
|
209
|
-
centerY:
|
|
204
|
+
height: p,
|
|
205
|
+
rotation: c,
|
|
206
|
+
centerX: g,
|
|
207
|
+
centerY: m
|
|
210
208
|
};
|
|
211
209
|
} else if (i.type === "image") {
|
|
212
210
|
const d = this.loadedImages.get(e);
|
|
213
211
|
if (!d)
|
|
214
212
|
return null;
|
|
215
|
-
const l = i.offset || { x: 0, y: 0 },
|
|
216
|
-
let
|
|
217
|
-
|
|
218
|
-
const
|
|
213
|
+
const l = i.offset || { x: 0, y: 0 }, c = i.scale ?? 1, u = i.rotation ?? 0, p = d.naturalWidth / d.naturalHeight, g = s / r;
|
|
214
|
+
let m, v;
|
|
215
|
+
p > g ? (m = s, v = s / p) : (v = r, m = r * p);
|
|
216
|
+
const b = m * c, y = v * c, x = a + s / 2 + l.x, w = o + r / 2 + l.y;
|
|
219
217
|
return {
|
|
220
|
-
x:
|
|
221
|
-
y:
|
|
222
|
-
width:
|
|
223
|
-
height:
|
|
218
|
+
x: x - b / 2,
|
|
219
|
+
y: w - y / 2,
|
|
220
|
+
width: b,
|
|
221
|
+
height: y,
|
|
224
222
|
rotation: u,
|
|
225
|
-
centerX:
|
|
226
|
-
centerY:
|
|
223
|
+
centerX: x,
|
|
224
|
+
centerY: w
|
|
227
225
|
};
|
|
228
226
|
}
|
|
229
227
|
return null;
|
|
@@ -236,8 +234,8 @@ class B {
|
|
|
236
234
|
if (!this.areaSelectionEnabled)
|
|
237
235
|
return null;
|
|
238
236
|
for (let i = this.areas.length - 1; i >= 0; i--) {
|
|
239
|
-
const n = this.areas[i],
|
|
240
|
-
if (
|
|
237
|
+
const n = this.areas[i], a = this.getContentBounds(n.id);
|
|
238
|
+
if (a && this.isPointInBounds(e, t, a))
|
|
241
239
|
return n.id;
|
|
242
240
|
}
|
|
243
241
|
return null;
|
|
@@ -250,8 +248,8 @@ class B {
|
|
|
250
248
|
if (!this.areaSelectionEnabled)
|
|
251
249
|
return null;
|
|
252
250
|
for (let i = this.areas.length - 1; i >= 0; i--) {
|
|
253
|
-
const n = this.areas[i], { x:
|
|
254
|
-
if (e >=
|
|
251
|
+
const n = this.areas[i], { x: a, y: o, width: s, height: r } = n.location;
|
|
252
|
+
if (e >= a && e <= a + s && t >= o && t <= o + r)
|
|
255
253
|
return n.id;
|
|
256
254
|
}
|
|
257
255
|
return null;
|
|
@@ -260,14 +258,14 @@ class B {
|
|
|
260
258
|
* Test if a point is inside rotated bounds
|
|
261
259
|
*/
|
|
262
260
|
isPointInBounds(e, t, i) {
|
|
263
|
-
const { centerX: n, centerY:
|
|
264
|
-
return
|
|
261
|
+
const { centerX: n, centerY: a, width: o, height: s, rotation: r } = i, d = -r * Math.PI / 180, l = Math.cos(d), c = Math.sin(d), u = e - n, p = t - a, g = u * l - p * c, m = u * c + p * l;
|
|
262
|
+
return g >= -o / 2 && g <= o / 2 && m >= -s / 2 && m <= s / 2;
|
|
265
263
|
}
|
|
266
264
|
/**
|
|
267
265
|
* Get handle positions for the selected content
|
|
268
266
|
*/
|
|
269
267
|
getContentHandlePositions(e) {
|
|
270
|
-
const t = this.areas.find((
|
|
268
|
+
const t = this.areas.find((b) => b.id === e);
|
|
271
269
|
if (!t)
|
|
272
270
|
return null;
|
|
273
271
|
const i = this.contents.get(e);
|
|
@@ -276,25 +274,25 @@ class B {
|
|
|
276
274
|
const n = this.getContentBounds(e);
|
|
277
275
|
if (!n)
|
|
278
276
|
return null;
|
|
279
|
-
const { centerX:
|
|
280
|
-
x:
|
|
281
|
-
y:
|
|
282
|
-
}),
|
|
277
|
+
const { centerX: a, centerY: o, width: s, height: r, rotation: d } = n, l = 6 / this.zoom, c = 30 / this.zoom, u = d * Math.PI / 180, p = Math.cos(u), g = Math.sin(u), m = (b, y) => ({
|
|
278
|
+
x: a + b * p - y * g,
|
|
279
|
+
y: o + b * g + y * p
|
|
280
|
+
}), v = [];
|
|
283
281
|
if (i.type === "image" && t.imageOptions?.allowScaling || i.type === "text" && t.textOptions?.allowScaling) {
|
|
284
|
-
const
|
|
285
|
-
|
|
286
|
-
const
|
|
287
|
-
|
|
288
|
-
const
|
|
289
|
-
|
|
290
|
-
const
|
|
291
|
-
|
|
282
|
+
const b = s / 2, y = r / 2, x = m(-b, -y);
|
|
283
|
+
v.push({ type: "nw", ...x, radius: l });
|
|
284
|
+
const w = m(b, -y);
|
|
285
|
+
v.push({ type: "ne", ...w, radius: l });
|
|
286
|
+
const C = m(b, y);
|
|
287
|
+
v.push({ type: "se", ...C, radius: l });
|
|
288
|
+
const M = m(-b, y);
|
|
289
|
+
v.push({ type: "sw", ...M, radius: l });
|
|
292
290
|
}
|
|
293
291
|
if (i.type === "image" && t.imageOptions?.allowRotation || i.type === "text" && t.textOptions?.allowRotation) {
|
|
294
|
-
const
|
|
295
|
-
|
|
292
|
+
const b = m(0, -r / 2 - c);
|
|
293
|
+
v.push({ type: "rotate", ...b, radius: l });
|
|
296
294
|
}
|
|
297
|
-
return
|
|
295
|
+
return v;
|
|
298
296
|
}
|
|
299
297
|
/**
|
|
300
298
|
* Hit test handles at given canvas coordinates
|
|
@@ -303,10 +301,10 @@ class B {
|
|
|
303
301
|
const n = this.getContentHandlePositions(i);
|
|
304
302
|
if (!n)
|
|
305
303
|
return null;
|
|
306
|
-
for (const
|
|
307
|
-
const
|
|
308
|
-
if (Math.sqrt(
|
|
309
|
-
return
|
|
304
|
+
for (const a of n) {
|
|
305
|
+
const o = e - a.x, s = t - a.y;
|
|
306
|
+
if (Math.sqrt(o * o + s * s) <= a.radius * 2)
|
|
307
|
+
return a.type;
|
|
310
308
|
}
|
|
311
309
|
return null;
|
|
312
310
|
}
|
|
@@ -346,15 +344,15 @@ class B {
|
|
|
346
344
|
checkSafeAreaViolations() {
|
|
347
345
|
if (!this.safeArea || !this.artboard)
|
|
348
346
|
return [];
|
|
349
|
-
const e = [], { top: t, right: i, bottom: n, left:
|
|
347
|
+
const e = [], { top: t, right: i, bottom: n, left: a } = this.safeArea, o = a, s = t, r = this.artboard.width - i, d = this.artboard.height - n;
|
|
350
348
|
for (const l of this.areas) {
|
|
351
349
|
if (!this.contents.get(l.id))
|
|
352
350
|
continue;
|
|
353
|
-
const
|
|
354
|
-
if (!
|
|
351
|
+
const c = this.getContentBounds(l.id);
|
|
352
|
+
if (!c)
|
|
355
353
|
continue;
|
|
356
|
-
const { centerX: u, centerY:
|
|
357
|
-
(
|
|
354
|
+
const { centerX: u, centerY: p, width: g, height: m, rotation: v } = c, b = v * Math.PI / 180, y = Math.abs(Math.cos(b)), x = Math.abs(Math.sin(b)), w = g * y + m * x, C = g * x + m * y, M = u - w / 2, A = p - C / 2, D = u + w / 2, R = p + C / 2;
|
|
355
|
+
(M < o || A < s || D > r || R > d) && e.push(l.id);
|
|
358
356
|
}
|
|
359
357
|
return this.safeAreaViolations = new Set(e), this.requestRender(), e;
|
|
360
358
|
}
|
|
@@ -383,15 +381,15 @@ class B {
|
|
|
383
381
|
throw new Error("No artboard configured");
|
|
384
382
|
const { width: e, height: t, backgroundColor: i } = this.artboard, n = document.createElement("canvas");
|
|
385
383
|
n.width = e, n.height = t;
|
|
386
|
-
const
|
|
387
|
-
if (!
|
|
384
|
+
const a = n.getContext("2d");
|
|
385
|
+
if (!a)
|
|
388
386
|
throw new Error("Failed to get 2D context for export");
|
|
389
|
-
|
|
390
|
-
for (const
|
|
391
|
-
this.drawAreaContentOnContext(
|
|
392
|
-
return this.backgroundImage?.position === "overlay" && this.bgImageElement && this.drawBackgroundImageOnContext(
|
|
393
|
-
n.toBlob((
|
|
394
|
-
|
|
387
|
+
a.fillStyle = i, a.fillRect(0, 0, e, t), this.backgroundImage?.position === "behind" && this.bgImageElement && this.drawBackgroundImageOnContext(a, e, t);
|
|
388
|
+
for (const o of this.areas)
|
|
389
|
+
this.drawAreaContentOnContext(a, o);
|
|
390
|
+
return this.backgroundImage?.position === "overlay" && this.bgImageElement && this.drawBackgroundImageOnContext(a, e, t), new Promise((o, s) => {
|
|
391
|
+
n.toBlob((r) => {
|
|
392
|
+
r ? o(r) : s(new Error("Failed to create PNG blob"));
|
|
395
393
|
}, "image/png", 1);
|
|
396
394
|
});
|
|
397
395
|
}
|
|
@@ -424,17 +422,17 @@ class B {
|
|
|
424
422
|
e.save();
|
|
425
423
|
const i = window.devicePixelRatio || 1;
|
|
426
424
|
e.scale(i, i), e.scale(this.zoom, this.zoom), e.translate(this.pan.x, this.pan.y);
|
|
427
|
-
const { width: n, height:
|
|
428
|
-
e.fillStyle =
|
|
429
|
-
for (const
|
|
430
|
-
this.renderAreaWithContent(
|
|
425
|
+
const { width: n, height: a, backgroundColor: o } = this.artboard;
|
|
426
|
+
e.fillStyle = o, e.fillRect(0, 0, n, a), e.strokeStyle = "#d1d5db", e.lineWidth = 1 / this.zoom, e.strokeRect(0, 0, n, a), this.backgroundImage?.position === "behind" && this.renderBackgroundImage();
|
|
427
|
+
for (const s of this.areas)
|
|
428
|
+
this.renderAreaWithContent(s);
|
|
431
429
|
this.backgroundImage?.position === "overlay" && this.renderBackgroundImage(), this.renderSafeAreaGuide();
|
|
432
|
-
for (const
|
|
433
|
-
const
|
|
434
|
-
if (!
|
|
430
|
+
for (const s of this.safeAreaViolations) {
|
|
431
|
+
const r = this.areas.find((p) => p.id === s);
|
|
432
|
+
if (!r)
|
|
435
433
|
continue;
|
|
436
|
-
const { x: d, y: l, width:
|
|
437
|
-
e.save(), e.fillStyle = "rgba(239, 68, 68, 0.15)", e.fillRect(d, l,
|
|
434
|
+
const { x: d, y: l, width: c, height: u } = r.location;
|
|
435
|
+
e.save(), e.fillStyle = "rgba(239, 68, 68, 0.15)", e.fillRect(d, l, c, u), e.restore();
|
|
438
436
|
}
|
|
439
437
|
this.selectedAreaId && this.renderSelectionOverlay(this.selectedAreaId), e.restore();
|
|
440
438
|
}
|
|
@@ -444,36 +442,36 @@ class B {
|
|
|
444
442
|
renderSelectionOverlay(e) {
|
|
445
443
|
const t = this.getContentBounds(e);
|
|
446
444
|
if (!t) {
|
|
447
|
-
const l = this.areas.find((
|
|
445
|
+
const l = this.areas.find((v) => v.id === e);
|
|
448
446
|
if (!l)
|
|
449
447
|
return;
|
|
450
|
-
const { ctx:
|
|
451
|
-
|
|
448
|
+
const { ctx: c } = this, { x: u, y: p, width: g, height: m } = l.location;
|
|
449
|
+
c.save(), c.strokeStyle = "#3b82f6", c.lineWidth = 2 / this.zoom, c.setLineDash([]), c.strokeRect(u, p, g, m), c.restore();
|
|
452
450
|
return;
|
|
453
451
|
}
|
|
454
|
-
const { ctx: i } = this, { centerX: n, centerY:
|
|
455
|
-
i.save(), i.translate(n,
|
|
452
|
+
const { ctx: i } = this, { centerX: n, centerY: a, width: o, height: s, rotation: r } = t;
|
|
453
|
+
i.save(), i.translate(n, a), i.rotate(r * Math.PI / 180), i.strokeStyle = "#3b82f6", i.lineWidth = 2 / this.zoom, i.setLineDash([]), i.strokeRect(-o / 2, -s / 2, o, s), i.restore();
|
|
456
454
|
const d = this.getContentHandlePositions(e);
|
|
457
455
|
if (d)
|
|
458
456
|
for (const l of d) {
|
|
459
457
|
i.save();
|
|
460
|
-
const
|
|
458
|
+
const c = this.hoveredHandle === l.type, u = c ? "#dbeafe" : "#ffffff", p = c ? "#2563eb" : "#3b82f6";
|
|
461
459
|
if (l.type === "rotate") {
|
|
462
|
-
const
|
|
463
|
-
i.beginPath(), i.strokeStyle =
|
|
460
|
+
const g = this.getRotatedPoint(n, a - s / 2, n, a, r);
|
|
461
|
+
i.beginPath(), i.strokeStyle = p, 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 = u, i.strokeStyle = p, 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 = p, 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();
|
|
464
462
|
} else
|
|
465
|
-
i.fillStyle = u, i.strokeStyle =
|
|
463
|
+
i.fillStyle = u, i.strokeStyle = p, 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);
|
|
466
464
|
i.restore();
|
|
467
465
|
}
|
|
468
466
|
}
|
|
469
467
|
/**
|
|
470
468
|
* Rotate a point around a center
|
|
471
469
|
*/
|
|
472
|
-
getRotatedPoint(e, t, i, n,
|
|
473
|
-
const
|
|
470
|
+
getRotatedPoint(e, t, i, n, a) {
|
|
471
|
+
const o = a * Math.PI / 180, s = Math.cos(o), r = Math.sin(o), d = e - i, l = t - n;
|
|
474
472
|
return {
|
|
475
|
-
x: i + d *
|
|
476
|
-
y: n + d *
|
|
473
|
+
x: i + d * s - l * r,
|
|
474
|
+
y: n + d * r + l * s
|
|
477
475
|
};
|
|
478
476
|
}
|
|
479
477
|
/**
|
|
@@ -484,9 +482,9 @@ class B {
|
|
|
484
482
|
return;
|
|
485
483
|
const { ctx: e } = this, { opacity: t } = this.backgroundImage, { width: i, height: n } = this.artboard;
|
|
486
484
|
e.save(), e.globalAlpha = t;
|
|
487
|
-
const
|
|
488
|
-
let
|
|
489
|
-
|
|
485
|
+
const a = this.bgImageElement.naturalWidth / this.bgImageElement.naturalHeight, o = i / n;
|
|
486
|
+
let s, r, d, l;
|
|
487
|
+
a > o ? (r = n, s = n * a, d = (i - s) / 2, l = 0) : (s = i, r = i / a, d = 0, l = (n - r) / 2), e.drawImage(this.bgImageElement, d, l, s, r), e.restore();
|
|
490
488
|
}
|
|
491
489
|
/**
|
|
492
490
|
* Render safe area guide as dashed green rectangle
|
|
@@ -494,17 +492,17 @@ class B {
|
|
|
494
492
|
renderSafeAreaGuide() {
|
|
495
493
|
if (!this.safeArea || !this.artboard)
|
|
496
494
|
return;
|
|
497
|
-
const { ctx: e } = this, { top: t, right: i, bottom: n, left:
|
|
498
|
-
|
|
495
|
+
const { ctx: e } = this, { top: t, right: i, bottom: n, left: a } = this.safeArea, o = a, s = t, r = this.artboard.width - a - i, d = this.artboard.height - t - n;
|
|
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, s, r, d), e.setLineDash([]), e.restore());
|
|
499
497
|
}
|
|
500
498
|
/**
|
|
501
499
|
* Render an area with its content
|
|
502
500
|
*/
|
|
503
501
|
renderAreaWithContent(e) {
|
|
504
|
-
const { ctx: t } = this, { location: i, backgroundColor: n } = e, { x:
|
|
505
|
-
n && (t.fillStyle = n, t.fillRect(
|
|
502
|
+
const { ctx: t } = this, { location: i, backgroundColor: n } = e, { x: a, y: o, width: s, height: r } = i;
|
|
503
|
+
n && (t.fillStyle = n, t.fillRect(a, o, s, r));
|
|
506
504
|
const d = this.contents.get(e.id);
|
|
507
|
-
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(
|
|
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(a, o, s, r), t.setLineDash([]), t.restore());
|
|
508
506
|
}
|
|
509
507
|
/**
|
|
510
508
|
* Render text content in an area
|
|
@@ -512,22 +510,22 @@ class B {
|
|
|
512
510
|
renderTextContent(e, t) {
|
|
513
511
|
if (!t.text.trim())
|
|
514
512
|
return;
|
|
515
|
-
const { ctx: i } = this, { location: n } = e, { x:
|
|
516
|
-
i.save(), i.beginPath(), i.rect(
|
|
517
|
-
let
|
|
513
|
+
const { ctx: i } = this, { location: n } = e, { x: a, y: o, width: s, height: r } = n, d = t.offset || { x: 0, y: 0 }, l = t.scale ?? 1, c = t.rotation ?? 0, u = a + s / 2 + d.x, p = o + r / 2 + d.y;
|
|
514
|
+
i.save(), i.beginPath(), i.rect(a, o, s, r), i.clip(), i.translate(u, p), 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
|
+
let g;
|
|
518
516
|
switch (t.align) {
|
|
519
517
|
case "left":
|
|
520
|
-
i.textAlign = "left",
|
|
518
|
+
i.textAlign = "left", g = -s / 2 + 10;
|
|
521
519
|
break;
|
|
522
520
|
case "right":
|
|
523
|
-
i.textAlign = "right",
|
|
521
|
+
i.textAlign = "right", g = s / 2 - 10;
|
|
524
522
|
break;
|
|
525
523
|
case "center":
|
|
526
524
|
default:
|
|
527
|
-
i.textAlign = "center",
|
|
525
|
+
i.textAlign = "center", g = 0;
|
|
528
526
|
break;
|
|
529
527
|
}
|
|
530
|
-
i.fillText(t.text,
|
|
528
|
+
i.fillText(t.text, g, 0), i.restore();
|
|
531
529
|
}
|
|
532
530
|
/**
|
|
533
531
|
* Render image content in an area
|
|
@@ -536,13 +534,13 @@ class B {
|
|
|
536
534
|
const i = this.loadedImages.get(e.id);
|
|
537
535
|
if (!i)
|
|
538
536
|
return;
|
|
539
|
-
const { ctx: n } = this, { location:
|
|
540
|
-
n.save(), n.beginPath(), n.rect(
|
|
541
|
-
const
|
|
542
|
-
let
|
|
543
|
-
|
|
544
|
-
const
|
|
545
|
-
n.translate(
|
|
537
|
+
const { ctx: n } = this, { location: a } = e, { x: o, y: s, width: r, height: d } = a, l = t.offset || { x: 0, y: 0 }, c = t.scale ?? 1, u = t.rotation ?? 0;
|
|
538
|
+
n.save(), n.beginPath(), n.rect(o, s, r, d), n.clip();
|
|
539
|
+
const p = i.naturalWidth / i.naturalHeight, g = r / d;
|
|
540
|
+
let m, v;
|
|
541
|
+
p > g ? (m = r, v = r / p) : (v = d, m = d * p);
|
|
542
|
+
const b = m * c, y = v * c, x = o + r / 2 + l.x, w = s + d / 2 + l.y;
|
|
543
|
+
n.translate(x, w), n.rotate(u * Math.PI / 180), n.drawImage(i, -b / 2, -y / 2, b, y), n.restore();
|
|
546
544
|
}
|
|
547
545
|
/**
|
|
548
546
|
* Draw background image on a specific context (for export)
|
|
@@ -552,48 +550,48 @@ class B {
|
|
|
552
550
|
return;
|
|
553
551
|
const { opacity: n } = this.backgroundImage;
|
|
554
552
|
e.save(), e.globalAlpha = n;
|
|
555
|
-
const
|
|
556
|
-
let
|
|
557
|
-
|
|
553
|
+
const a = this.bgImageElement.naturalWidth / this.bgImageElement.naturalHeight, o = t / i;
|
|
554
|
+
let s, r, d, l;
|
|
555
|
+
a > o ? (r = i, s = i * a, d = (t - s) / 2, l = 0) : (s = t, r = t / a, d = 0, l = (i - r) / 2), e.drawImage(this.bgImageElement, d, l, s, r), e.restore();
|
|
558
556
|
}
|
|
559
557
|
/**
|
|
560
558
|
* Draw area content on a specific context (for export)
|
|
561
559
|
*/
|
|
562
560
|
drawAreaContentOnContext(e, t) {
|
|
563
|
-
const { location: i, backgroundColor: n } = t, { x:
|
|
564
|
-
n && (e.fillStyle = n, e.fillRect(
|
|
561
|
+
const { location: i, backgroundColor: n } = t, { x: a, y: o, width: s, height: r } = i;
|
|
562
|
+
n && (e.fillStyle = n, e.fillRect(a, o, s, r));
|
|
565
563
|
const d = this.contents.get(t.id);
|
|
566
564
|
if (d) {
|
|
567
565
|
if (d.type === "text") {
|
|
568
566
|
if (!d.text.trim())
|
|
569
567
|
return;
|
|
570
|
-
const l = d.offset || { x: 0, y: 0 },
|
|
571
|
-
e.save(), e.beginPath(), e.rect(
|
|
572
|
-
let
|
|
568
|
+
const l = d.offset || { x: 0, y: 0 }, c = d.scale ?? 1, u = d.rotation ?? 0, p = a + s / 2 + l.x, g = o + r / 2 + l.y;
|
|
569
|
+
e.save(), e.beginPath(), e.rect(a, o, s, r), e.clip(), e.translate(p, g), e.rotate(u * Math.PI / 180), e.scale(c, c), e.font = `${d.size}px ${d.font}, sans-serif`, e.fillStyle = d.color, e.textBaseline = "middle";
|
|
570
|
+
let m;
|
|
573
571
|
switch (d.align) {
|
|
574
572
|
case "left":
|
|
575
|
-
e.textAlign = "left",
|
|
573
|
+
e.textAlign = "left", m = -s / 2 + 10;
|
|
576
574
|
break;
|
|
577
575
|
case "right":
|
|
578
|
-
e.textAlign = "right",
|
|
576
|
+
e.textAlign = "right", m = s / 2 - 10;
|
|
579
577
|
break;
|
|
580
578
|
case "center":
|
|
581
579
|
default:
|
|
582
|
-
e.textAlign = "center",
|
|
580
|
+
e.textAlign = "center", m = 0;
|
|
583
581
|
break;
|
|
584
582
|
}
|
|
585
|
-
e.fillText(d.text,
|
|
583
|
+
e.fillText(d.text, m, 0), e.restore();
|
|
586
584
|
} else if (d.type === "image") {
|
|
587
585
|
const l = this.loadedImages.get(t.id);
|
|
588
586
|
if (!l)
|
|
589
587
|
return;
|
|
590
|
-
const
|
|
591
|
-
e.save(), e.beginPath(), e.rect(
|
|
592
|
-
const
|
|
593
|
-
let
|
|
594
|
-
|
|
595
|
-
const
|
|
596
|
-
e.translate(
|
|
588
|
+
const c = d.offset || { x: 0, y: 0 }, u = d.scale ?? 1, p = d.rotation ?? 0;
|
|
589
|
+
e.save(), e.beginPath(), e.rect(a, o, s, r), e.clip();
|
|
590
|
+
const g = l.naturalWidth / l.naturalHeight, m = s / r;
|
|
591
|
+
let v, b;
|
|
592
|
+
g > m ? (v = s, b = s / g) : (b = r, v = r * g);
|
|
593
|
+
const y = v * u, x = b * u, w = a + s / 2 + c.x, C = o + r / 2 + c.y;
|
|
594
|
+
e.translate(w, C), e.rotate(p * Math.PI / 180), e.drawImage(l, -y / 2, -x / 2, y, x), e.restore();
|
|
597
595
|
}
|
|
598
596
|
}
|
|
599
597
|
}
|
|
@@ -604,8 +602,8 @@ class B {
|
|
|
604
602
|
this.stop(), this.bgImageElement = null, this.loadedImages.clear(), this.loadingImages.clear(), this.eventListeners.clear();
|
|
605
603
|
}
|
|
606
604
|
}
|
|
607
|
-
function
|
|
608
|
-
const e =
|
|
605
|
+
function E(f) {
|
|
606
|
+
const e = f.textOptions;
|
|
609
607
|
return {
|
|
610
608
|
type: "text",
|
|
611
609
|
text: e?.defaultText || "",
|
|
@@ -615,14 +613,14 @@ function P(b) {
|
|
|
615
613
|
align: e?.defaultAlign || "center"
|
|
616
614
|
};
|
|
617
615
|
}
|
|
618
|
-
function
|
|
619
|
-
return
|
|
616
|
+
function S(f) {
|
|
617
|
+
return f.contentType === "text" || f.contentType === "both";
|
|
620
618
|
}
|
|
621
|
-
function
|
|
622
|
-
return
|
|
619
|
+
function k(f) {
|
|
620
|
+
return f.contentType === "image" || f.contentType === "both";
|
|
623
621
|
}
|
|
624
|
-
const
|
|
625
|
-
class
|
|
622
|
+
const U = 50;
|
|
623
|
+
class O {
|
|
626
624
|
constructor(e) {
|
|
627
625
|
this.history = [], this.historyIndex = -1, this.listeners = /* @__PURE__ */ new Set(), this.isRestoring = !1, this.state = this.cloneState(e), this.history.push({
|
|
628
626
|
state: this.cloneState(e),
|
|
@@ -673,7 +671,7 @@ class j {
|
|
|
673
671
|
state: this.cloneState(e),
|
|
674
672
|
timestamp: Date.now(),
|
|
675
673
|
description: t
|
|
676
|
-
}), this.history.length >
|
|
674
|
+
}), this.history.length > U && this.history.shift(), this.historyIndex = this.history.length - 1;
|
|
677
675
|
}
|
|
678
676
|
restoreFromHistory() {
|
|
679
677
|
const e = this.history[this.historyIndex];
|
|
@@ -696,14 +694,14 @@ class j {
|
|
|
696
694
|
return JSON.parse(JSON.stringify(e));
|
|
697
695
|
}
|
|
698
696
|
}
|
|
699
|
-
class
|
|
697
|
+
class T {
|
|
700
698
|
constructor(e) {
|
|
701
699
|
this.listeners = /* @__PURE__ */ new Set(), this.areas = e, this.contents = this.buildInitialContents();
|
|
702
700
|
}
|
|
703
701
|
buildInitialContents() {
|
|
704
702
|
const e = /* @__PURE__ */ new Map();
|
|
705
703
|
for (const t of this.areas)
|
|
706
|
-
|
|
704
|
+
S(t) && e.set(t.id, E(t));
|
|
707
705
|
return e;
|
|
708
706
|
}
|
|
709
707
|
getContents() {
|
|
@@ -713,7 +711,7 @@ class X {
|
|
|
713
711
|
return this.contents.get(e);
|
|
714
712
|
}
|
|
715
713
|
setTextContent(e, t) {
|
|
716
|
-
const i = this.contents.get(e), n = this.areas.find((
|
|
714
|
+
const i = this.contents.get(e), n = this.areas.find((o) => o.id === e), a = n ? E(n) : {
|
|
717
715
|
type: "text",
|
|
718
716
|
text: "",
|
|
719
717
|
font: "Inter",
|
|
@@ -721,7 +719,7 @@ class X {
|
|
|
721
719
|
color: "#000000",
|
|
722
720
|
align: "center"
|
|
723
721
|
};
|
|
724
|
-
i?.type === "text" ? this.contents.set(e, { ...i, ...t }) : this.contents.set(e, { ...
|
|
722
|
+
i?.type === "text" ? this.contents.set(e, { ...i, ...t }) : this.contents.set(e, { ...a, ...t }), this.notify();
|
|
725
723
|
}
|
|
726
724
|
setImageContent(e, t, i) {
|
|
727
725
|
const n = {
|
|
@@ -733,7 +731,7 @@ class X {
|
|
|
733
731
|
}
|
|
734
732
|
clearContent(e) {
|
|
735
733
|
const t = this.areas.find((i) => i.id === e);
|
|
736
|
-
t &&
|
|
734
|
+
t && S(t) ? this.contents.set(e, E(t)) : this.contents.delete(e), this.notify();
|
|
737
735
|
}
|
|
738
736
|
setContentOffset(e, t) {
|
|
739
737
|
const i = this.contents.get(e);
|
|
@@ -778,7 +776,7 @@ class X {
|
|
|
778
776
|
}
|
|
779
777
|
}
|
|
780
778
|
}
|
|
781
|
-
class
|
|
779
|
+
class I {
|
|
782
780
|
constructor() {
|
|
783
781
|
this.listeners = /* @__PURE__ */ new Map();
|
|
784
782
|
}
|
|
@@ -825,11 +823,11 @@ class T {
|
|
|
825
823
|
const i = this.listeners.get(e);
|
|
826
824
|
if (i) {
|
|
827
825
|
const n = Array.from(i);
|
|
828
|
-
for (const
|
|
826
|
+
for (const a of n)
|
|
829
827
|
try {
|
|
830
|
-
|
|
831
|
-
} catch (
|
|
832
|
-
console.error(`Error in event handler for "${String(e)}":`,
|
|
828
|
+
a(t);
|
|
829
|
+
} catch (o) {
|
|
830
|
+
console.error(`Error in event handler for "${String(e)}":`, o);
|
|
833
831
|
}
|
|
834
832
|
}
|
|
835
833
|
}
|
|
@@ -851,7 +849,7 @@ class T {
|
|
|
851
849
|
return this.listeners.get(e)?.size ?? 0;
|
|
852
850
|
}
|
|
853
851
|
}
|
|
854
|
-
class
|
|
852
|
+
class V extends I {
|
|
855
853
|
constructor() {
|
|
856
854
|
super(...arguments), this.activeTouches = /* @__PURE__ */ new Map(), this.initialPinchDistance = null, this.lastPinchCenter = null, this.isTwoFingerGesture = !1;
|
|
857
855
|
}
|
|
@@ -883,14 +881,14 @@ class W extends T {
|
|
|
883
881
|
}
|
|
884
882
|
if (this.activeTouches.size === 2 && this.initialPinchDistance !== null && this.lastPinchCenter) {
|
|
885
883
|
e.preventDefault();
|
|
886
|
-
const [t, i] = Array.from(this.activeTouches.values()), n = this.getDistance(t, i),
|
|
884
|
+
const [t, i] = Array.from(this.activeTouches.values()), n = this.getDistance(t, i), a = this.getCenter(t, i), o = n / this.initialPinchDistance;
|
|
887
885
|
this.emit("pinch", {
|
|
888
|
-
scale:
|
|
889
|
-
centerX:
|
|
890
|
-
centerY:
|
|
886
|
+
scale: o,
|
|
887
|
+
centerX: a.x,
|
|
888
|
+
centerY: a.y
|
|
891
889
|
});
|
|
892
|
-
const
|
|
893
|
-
(Math.abs(
|
|
890
|
+
const s = a.x - this.lastPinchCenter.x, r = a.y - this.lastPinchCenter.y;
|
|
891
|
+
(Math.abs(s) > 0.5 || Math.abs(r) > 0.5) && this.emit("pan", { deltaX: s, deltaY: r }), this.initialPinchDistance = n, this.lastPinchCenter = a;
|
|
894
892
|
}
|
|
895
893
|
}
|
|
896
894
|
handleTouchEnd(e) {
|
|
@@ -914,24 +912,24 @@ class W extends T {
|
|
|
914
912
|
};
|
|
915
913
|
}
|
|
916
914
|
}
|
|
917
|
-
class
|
|
915
|
+
class F extends I {
|
|
918
916
|
constructor(e, t, i, n) {
|
|
919
|
-
super(), this.interactionState = { mode: "idle" }, this.initialPinchZoom = 1, this.canvas = e, this.engine = t, this.getContents = i, this.getSelectedAreaId = n, this.gestureHandler = new
|
|
920
|
-
const
|
|
921
|
-
this.emit("zoom", { zoom:
|
|
922
|
-
}), this.gestureHandler.on("pan", ({ deltaX:
|
|
923
|
-
const
|
|
917
|
+
super(), this.interactionState = { mode: "idle" }, this.initialPinchZoom = 1, this.canvas = e, this.engine = t, this.getContents = i, this.getSelectedAreaId = n, this.gestureHandler = new V(), this.boundMouseDown = this.handleMouseDown.bind(this), this.boundMouseMove = this.handleMouseMove.bind(this), this.boundMouseUp = this.handleMouseUp.bind(this), this.boundMouseLeave = this.handleMouseLeave.bind(this), this.boundContextMenu = this.handleContextMenu.bind(this), this.boundDoubleClick = this.handleDoubleClick.bind(this), this.boundTouchStart = this.handleTouchStart.bind(this), this.boundTouchMove = this.handleTouchMove.bind(this), this.boundTouchEnd = this.handleTouchEnd.bind(this), e.addEventListener("mousedown", this.boundMouseDown), e.addEventListener("mousemove", this.boundMouseMove), e.addEventListener("mouseup", this.boundMouseUp), e.addEventListener("mouseleave", this.boundMouseLeave), e.addEventListener("contextmenu", this.boundContextMenu), e.addEventListener("dblclick", this.boundDoubleClick), e.addEventListener("touchstart", this.boundTouchStart, { passive: !1 }), e.addEventListener("touchmove", this.boundTouchMove, { passive: !1 }), e.addEventListener("touchend", this.boundTouchEnd), this.gestureHandler.on("pinch", ({ scale: a }) => {
|
|
918
|
+
const o = Math.max(0.1, Math.min(5, this.initialPinchZoom * a));
|
|
919
|
+
this.emit("zoom", { zoom: o });
|
|
920
|
+
}), this.gestureHandler.on("pan", ({ deltaX: a, deltaY: o }) => {
|
|
921
|
+
const s = this.engine.getPan(), r = this.engine.getZoom();
|
|
924
922
|
this.emit("pan", {
|
|
925
923
|
pan: {
|
|
926
|
-
x:
|
|
927
|
-
y:
|
|
924
|
+
x: s.x + a / r,
|
|
925
|
+
y: s.y + o / r
|
|
928
926
|
}
|
|
929
927
|
});
|
|
930
928
|
});
|
|
931
929
|
}
|
|
932
930
|
getCanvasPosition(e, t) {
|
|
933
|
-
const i = this.canvas.getBoundingClientRect(), n = window.devicePixelRatio || 1,
|
|
934
|
-
return this.engine.screenToCanvas(
|
|
931
|
+
const i = this.canvas.getBoundingClientRect(), n = window.devicePixelRatio || 1, a = (e - i.left) * n, o = (t - i.top) * n;
|
|
932
|
+
return this.engine.screenToCanvas(a, o);
|
|
935
933
|
}
|
|
936
934
|
handleMouseDown(e) {
|
|
937
935
|
const t = this.getCanvasPosition(e.clientX, e.clientY);
|
|
@@ -978,83 +976,83 @@ class G extends T {
|
|
|
978
976
|
processPointerDown(e) {
|
|
979
977
|
const t = this.getSelectedAreaId(), i = this.getContents();
|
|
980
978
|
if (t) {
|
|
981
|
-
const
|
|
982
|
-
if (
|
|
983
|
-
const
|
|
984
|
-
if (
|
|
985
|
-
const d = Math.atan2(e.y -
|
|
979
|
+
const o = this.engine.hitTestHandle(e.x, e.y, t);
|
|
980
|
+
if (o) {
|
|
981
|
+
const s = i.get(t), r = this.engine.getContentBounds(t);
|
|
982
|
+
if (o === "rotate" && s?.type === "image" && r) {
|
|
983
|
+
const d = Math.atan2(e.y - r.centerY, e.x - r.centerX) * (180 / Math.PI);
|
|
986
984
|
return this.interactionState = {
|
|
987
985
|
mode: "rotating",
|
|
988
986
|
areaId: t,
|
|
989
|
-
startRotation:
|
|
990
|
-
centerPoint: { x:
|
|
987
|
+
startRotation: s.rotation ?? 0,
|
|
988
|
+
centerPoint: { x: r.centerX, y: r.centerY },
|
|
991
989
|
startAngle: d
|
|
992
990
|
}, !0;
|
|
993
|
-
} else if (
|
|
991
|
+
} else if (o !== "rotate" && s?.type === "image" && r)
|
|
994
992
|
return this.interactionState = {
|
|
995
993
|
mode: "scaling",
|
|
996
994
|
areaId: t,
|
|
997
|
-
handle:
|
|
998
|
-
startScale:
|
|
995
|
+
handle: o,
|
|
996
|
+
startScale: s.scale ?? 1,
|
|
999
997
|
startMousePos: e,
|
|
1000
|
-
pivotPoint: { x:
|
|
998
|
+
pivotPoint: { x: r.centerX, y: r.centerY }
|
|
1001
999
|
}, !0;
|
|
1002
1000
|
}
|
|
1003
1001
|
}
|
|
1004
1002
|
const n = this.engine.hitTestContent(e.x, e.y);
|
|
1005
1003
|
if (n) {
|
|
1006
1004
|
if (this.emit("area:select", { areaId: n }), this.engine.isPositioningAllowed(n)) {
|
|
1007
|
-
const
|
|
1005
|
+
const o = i.get(n)?.offset ?? { x: 0, y: 0 };
|
|
1008
1006
|
return this.interactionState = {
|
|
1009
1007
|
mode: "dragging",
|
|
1010
1008
|
areaId: n,
|
|
1011
|
-
startOffset:
|
|
1009
|
+
startOffset: o,
|
|
1012
1010
|
startMousePos: e
|
|
1013
1011
|
}, !0;
|
|
1014
1012
|
}
|
|
1015
1013
|
return !0;
|
|
1016
1014
|
}
|
|
1017
|
-
const
|
|
1018
|
-
return
|
|
1015
|
+
const a = this.engine.hitTestArea(e.x, e.y);
|
|
1016
|
+
return a ? (this.emit("area:select", { areaId: a }), !1) : (this.emit("area:select", { areaId: null }), !1);
|
|
1019
1017
|
}
|
|
1020
1018
|
processPointerMove(e) {
|
|
1021
1019
|
if (this.interactionState.mode === "dragging") {
|
|
1022
|
-
const { areaId: t, startOffset: i, startMousePos: n } = this.interactionState,
|
|
1023
|
-
let
|
|
1024
|
-
x: i.x +
|
|
1025
|
-
y: i.y +
|
|
1020
|
+
const { areaId: t, startOffset: i, startMousePos: n } = this.interactionState, a = e.x - n.x, o = e.y - n.y;
|
|
1021
|
+
let s = {
|
|
1022
|
+
x: i.x + a,
|
|
1023
|
+
y: i.y + o
|
|
1026
1024
|
};
|
|
1027
|
-
const
|
|
1028
|
-
if (
|
|
1029
|
-
const d =
|
|
1030
|
-
|
|
1025
|
+
const r = this.engine.getAreaLocation(t);
|
|
1026
|
+
if (r) {
|
|
1027
|
+
const d = r.width / 2 - 10, l = r.height / 2 - 10;
|
|
1028
|
+
s.x = Math.max(-d, Math.min(d, s.x)), s.y = Math.max(-l, Math.min(l, s.y));
|
|
1031
1029
|
}
|
|
1032
|
-
this.emit("content:drag", { areaId: t, offset:
|
|
1030
|
+
this.emit("content:drag", { areaId: t, offset: s });
|
|
1033
1031
|
return;
|
|
1034
1032
|
}
|
|
1035
1033
|
if (this.interactionState.mode === "scaling") {
|
|
1036
|
-
const { areaId: t, startScale: i, startMousePos: n, pivotPoint:
|
|
1037
|
-
Math.pow(n.x -
|
|
1038
|
-
),
|
|
1039
|
-
Math.pow(e.x -
|
|
1034
|
+
const { areaId: t, startScale: i, startMousePos: n, pivotPoint: a } = this.interactionState, o = Math.sqrt(
|
|
1035
|
+
Math.pow(n.x - a.x, 2) + Math.pow(n.y - a.y, 2)
|
|
1036
|
+
), s = Math.sqrt(
|
|
1037
|
+
Math.pow(e.x - a.x, 2) + Math.pow(e.y - a.y, 2)
|
|
1040
1038
|
);
|
|
1041
|
-
if (
|
|
1042
|
-
const
|
|
1039
|
+
if (o > 0) {
|
|
1040
|
+
const r = s / o, d = Math.max(0.1, Math.min(5, i * r));
|
|
1043
1041
|
this.emit("content:scale", { areaId: t, scale: d });
|
|
1044
1042
|
}
|
|
1045
1043
|
return;
|
|
1046
1044
|
}
|
|
1047
1045
|
if (this.interactionState.mode === "rotating") {
|
|
1048
|
-
const { areaId: t, startRotation: i, centerPoint: n, startAngle:
|
|
1049
|
-
let
|
|
1050
|
-
|
|
1051
|
-
const
|
|
1052
|
-
for (const d of
|
|
1053
|
-
if (Math.abs(
|
|
1054
|
-
|
|
1046
|
+
const { areaId: t, startRotation: i, centerPoint: n, startAngle: a } = this.interactionState, o = Math.atan2(e.y - n.y, e.x - n.x) * (180 / Math.PI);
|
|
1047
|
+
let s = i + (o - a);
|
|
1048
|
+
s = s % 360, s < 0 && (s += 360);
|
|
1049
|
+
const r = [0, 90, 180, 270, 360];
|
|
1050
|
+
for (const d of r)
|
|
1051
|
+
if (Math.abs(s - d) < 5) {
|
|
1052
|
+
s = d === 360 ? 0 : d;
|
|
1055
1053
|
break;
|
|
1056
1054
|
}
|
|
1057
|
-
this.emit("content:rotate", { areaId: t, rotation:
|
|
1055
|
+
this.emit("content:rotate", { areaId: t, rotation: s });
|
|
1058
1056
|
return;
|
|
1059
1057
|
}
|
|
1060
1058
|
this.updateCursor(e);
|
|
@@ -1079,7 +1077,7 @@ class G extends T {
|
|
|
1079
1077
|
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();
|
|
1080
1078
|
}
|
|
1081
1079
|
}
|
|
1082
|
-
class
|
|
1080
|
+
class $ {
|
|
1083
1081
|
constructor(e = "/api") {
|
|
1084
1082
|
this.baseUrl = e;
|
|
1085
1083
|
}
|
|
@@ -1097,6 +1095,20 @@ class J {
|
|
|
1097
1095
|
throw t instanceof Error && t.message.includes("Failed to fetch") ? new Error(`Network error loading template: ${e}`) : t;
|
|
1098
1096
|
}
|
|
1099
1097
|
}
|
|
1098
|
+
async getProduct(e) {
|
|
1099
|
+
try {
|
|
1100
|
+
const t = await fetch(`${this.baseUrl}/products/${e}`);
|
|
1101
|
+
if (!t.ok) {
|
|
1102
|
+
let i = `Failed to load product (${t.status} ${t.statusText}): ${e}
|
|
1103
|
+
|
|
1104
|
+
`;
|
|
1105
|
+
throw t.status === 404 ? i += "Product not found." : t.status === 403 ? i += "Access forbidden." : t.status >= 500 && (i += "Server error."), new Error(i);
|
|
1106
|
+
}
|
|
1107
|
+
return t.json();
|
|
1108
|
+
} catch (t) {
|
|
1109
|
+
throw t instanceof Error && t.message.includes("Failed to fetch") ? new Error(`Network error loading product: ${e}`) : t;
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1100
1112
|
async finalizeDesign(e, t) {
|
|
1101
1113
|
try {
|
|
1102
1114
|
const i = await fetch(`${this.baseUrl}/finalize`, {
|
|
@@ -1113,8 +1125,8 @@ class J {
|
|
|
1113
1125
|
|
|
1114
1126
|
`;
|
|
1115
1127
|
if (i.status === 400) {
|
|
1116
|
-
const
|
|
1117
|
-
n +=
|
|
1128
|
+
const a = await i.json().catch(() => ({}));
|
|
1129
|
+
n += a.error || "Invalid design data.";
|
|
1118
1130
|
} else i.status === 429 ? n += "Rate limit exceeded." : i.status >= 500 && (n += "Server error.");
|
|
1119
1131
|
throw new Error(n);
|
|
1120
1132
|
}
|
|
@@ -1132,14 +1144,14 @@ class J {
|
|
|
1132
1144
|
body: i
|
|
1133
1145
|
});
|
|
1134
1146
|
if (!n.ok) {
|
|
1135
|
-
let
|
|
1147
|
+
let a = `Failed to upload asset (${n.status} ${n.statusText})
|
|
1136
1148
|
|
|
1137
1149
|
`;
|
|
1138
1150
|
if (n.status === 400) {
|
|
1139
|
-
const
|
|
1140
|
-
|
|
1141
|
-
} else n.status === 413 ?
|
|
1142
|
-
throw new Error(
|
|
1151
|
+
const o = await n.json().catch(() => ({}));
|
|
1152
|
+
a += o.error || "Invalid file.";
|
|
1153
|
+
} else n.status === 413 ? a += "File too large (max 15MB)." : n.status >= 500 && (a += "Server error.");
|
|
1154
|
+
throw new Error(a);
|
|
1143
1155
|
}
|
|
1144
1156
|
return n.json();
|
|
1145
1157
|
} catch (i) {
|
|
@@ -1157,96 +1169,125 @@ class J {
|
|
|
1157
1169
|
}
|
|
1158
1170
|
}
|
|
1159
1171
|
}
|
|
1160
|
-
function
|
|
1161
|
-
const i = document.createElement(
|
|
1162
|
-
return e && Object.entries(e).forEach(([n,
|
|
1163
|
-
n === "class" ? i.className =
|
|
1172
|
+
function h(f, e, ...t) {
|
|
1173
|
+
const i = document.createElement(f);
|
|
1174
|
+
return e && Object.entries(e).forEach(([n, a]) => {
|
|
1175
|
+
n === "class" ? i.className = a : n in i ? i[n] = a : i.setAttribute(n, String(a));
|
|
1164
1176
|
}), t.forEach((n) => {
|
|
1165
1177
|
typeof n == "string" ? i.appendChild(document.createTextNode(n)) : i.appendChild(n);
|
|
1166
1178
|
}), i;
|
|
1167
1179
|
}
|
|
1168
|
-
function
|
|
1180
|
+
function H(f, e) {
|
|
1169
1181
|
let t = null, i = null;
|
|
1170
|
-
const n = (...
|
|
1171
|
-
i =
|
|
1172
|
-
i &&
|
|
1182
|
+
const n = (...a) => {
|
|
1183
|
+
i = a, t && clearTimeout(t), t = setTimeout(() => {
|
|
1184
|
+
i && f(...i), t = null, i = null;
|
|
1173
1185
|
}, e);
|
|
1174
1186
|
};
|
|
1175
1187
|
return n.flush = () => {
|
|
1176
|
-
t && (clearTimeout(t), t = null), i && (
|
|
1188
|
+
t && (clearTimeout(t), t = null), i && (f(...i), i = null);
|
|
1177
1189
|
}, n.cancel = () => {
|
|
1178
1190
|
t && (clearTimeout(t), t = null), i = null;
|
|
1179
1191
|
}, n;
|
|
1180
1192
|
}
|
|
1181
|
-
class
|
|
1193
|
+
class B extends I {
|
|
1182
1194
|
constructor() {
|
|
1183
|
-
super(), this.percentLabel =
|
|
1184
|
-
const e =
|
|
1195
|
+
super(), this.percentLabel = h("span", { class: "zoom-percent" }, "100%");
|
|
1196
|
+
const e = h("button", {
|
|
1185
1197
|
class: "zoom-btn",
|
|
1186
1198
|
title: "Zoom out"
|
|
1187
1199
|
}, "−");
|
|
1188
1200
|
e.addEventListener("click", () => this.emit("zoom-out", void 0));
|
|
1189
|
-
const t =
|
|
1201
|
+
const t = h("button", {
|
|
1190
1202
|
class: "zoom-btn",
|
|
1191
1203
|
title: "Fit to view"
|
|
1192
1204
|
}, "Fit");
|
|
1193
1205
|
t.addEventListener("click", () => this.emit("zoom-fit", void 0));
|
|
1194
|
-
const i =
|
|
1206
|
+
const i = h("button", {
|
|
1195
1207
|
class: "zoom-btn",
|
|
1196
1208
|
title: "Zoom in"
|
|
1197
1209
|
}, "+");
|
|
1198
|
-
i.addEventListener("click", () => this.emit("zoom-in", void 0)), this.
|
|
1210
|
+
i.addEventListener("click", () => this.emit("zoom-in", void 0)), this.viewSelect = document.createElement("select"), this.viewSelect.className = "view-select", this.viewSelect.title = "Switch view", this.viewSelect.style.display = "none", this.viewSelect.addEventListener("change", () => {
|
|
1211
|
+
this.emit("view-change", { viewName: this.viewSelect.value });
|
|
1212
|
+
}), 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
|
+
const n = h("div", { class: "zoom-toolbar-spacer" });
|
|
1214
|
+
this.element.appendChild(n);
|
|
1215
|
+
const a = h("button", {
|
|
1216
|
+
class: "zoom-btn zoom-close-btn",
|
|
1217
|
+
title: "Close editor"
|
|
1218
|
+
}, "✕ Close");
|
|
1219
|
+
a.addEventListener("click", () => this.emit("close", void 0)), this.element.appendChild(a), this.saveBtn = h("button", {
|
|
1220
|
+
class: "zoom-btn zoom-save-btn",
|
|
1221
|
+
title: "Save customization"
|
|
1222
|
+
}, "Save Customization"), this.saveBtn.style.display = "none", this.saveBtn.addEventListener("click", () => this.emit("save", void 0)), this.element.appendChild(this.saveBtn);
|
|
1199
1223
|
}
|
|
1200
1224
|
setZoom(e) {
|
|
1201
1225
|
this.percentLabel.textContent = `${Math.round(e * 100)}%`;
|
|
1202
1226
|
}
|
|
1227
|
+
showSaveButton(e) {
|
|
1228
|
+
this.saveBtn.style.display = e ? "" : "none";
|
|
1229
|
+
}
|
|
1230
|
+
setSaveDisabled(e, t) {
|
|
1231
|
+
this.saveBtn.disabled = e, t && (this.saveBtn.textContent = t);
|
|
1232
|
+
}
|
|
1233
|
+
setViews(e) {
|
|
1234
|
+
this.viewSelect.innerHTML = "";
|
|
1235
|
+
for (const t of e) {
|
|
1236
|
+
const i = document.createElement("option");
|
|
1237
|
+
i.value = t.viewName, i.textContent = t.viewName, this.viewSelect.appendChild(i);
|
|
1238
|
+
}
|
|
1239
|
+
this.viewSelect.style.display = e.length > 1 ? "" : "none";
|
|
1240
|
+
}
|
|
1241
|
+
setActiveView(e) {
|
|
1242
|
+
this.viewSelect.value = e;
|
|
1243
|
+
}
|
|
1203
1244
|
getElement() {
|
|
1204
1245
|
return this.element;
|
|
1205
1246
|
}
|
|
1206
1247
|
}
|
|
1207
|
-
const
|
|
1248
|
+
const Z = [
|
|
1208
1249
|
{ label: "Inter", value: "Inter" },
|
|
1209
1250
|
{ label: "Arial", value: "Arial" },
|
|
1210
1251
|
{ label: "Georgia", value: "Georgia" },
|
|
1211
1252
|
{ label: "Times New Roman", value: "Times New Roman" },
|
|
1212
1253
|
{ label: "Courier New", value: "Courier New" }
|
|
1213
|
-
],
|
|
1254
|
+
], q = [
|
|
1214
1255
|
{ label: "L", value: "left" },
|
|
1215
1256
|
{ label: "C", value: "center" },
|
|
1216
1257
|
{ label: "R", value: "right" }
|
|
1217
|
-
],
|
|
1218
|
-
class
|
|
1258
|
+
], P = ["image/png", "image/jpeg", "image/webp", "image/svg+xml"], Y = 15 * 1024 * 1024;
|
|
1259
|
+
class L extends I {
|
|
1219
1260
|
constructor(e) {
|
|
1220
1261
|
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;
|
|
1221
|
-
const t =
|
|
1222
|
-
this.mode = i && !t ? "image" : "text", this.element =
|
|
1223
|
-
|
|
1262
|
+
const t = S(e), i = k(e);
|
|
1263
|
+
this.mode = i && !t ? "image" : "text", this.element = h("div", { class: "area-card" }), this.element.addEventListener("click", (r) => {
|
|
1264
|
+
r.target.tagName === "INPUT" || r.target.tagName === "SELECT" || r.target.tagName === "BUTTON" || this.emit("select", { areaId: e.id });
|
|
1224
1265
|
});
|
|
1225
|
-
const n =
|
|
1226
|
-
if (
|
|
1227
|
-
const
|
|
1228
|
-
|
|
1266
|
+
const n = h("div", { class: "area-card-header" }), a = h("div", { class: "area-card-name-row" }), o = h("span", { class: "area-card-name" }, e.name);
|
|
1267
|
+
if (a.appendChild(o), e.required) {
|
|
1268
|
+
const r = h("span", { class: "area-card-badge" }, "Required");
|
|
1269
|
+
a.appendChild(r);
|
|
1229
1270
|
}
|
|
1230
|
-
n.appendChild(
|
|
1231
|
-
const
|
|
1271
|
+
n.appendChild(a);
|
|
1272
|
+
const s = h("button", {
|
|
1232
1273
|
class: "area-card-reset-btn",
|
|
1233
1274
|
title: "Reset content"
|
|
1234
1275
|
}, "Reset");
|
|
1235
|
-
if (
|
|
1236
|
-
|
|
1237
|
-
}), n.appendChild(
|
|
1238
|
-
const
|
|
1276
|
+
if (s.addEventListener("click", (r) => {
|
|
1277
|
+
r.stopPropagation(), this.emit("clear", { areaId: e.id });
|
|
1278
|
+
}), n.appendChild(s), this.element.appendChild(n), t && i) {
|
|
1279
|
+
const r = h("div", { class: "area-card-mode-switcher" }), d = h("button", {
|
|
1239
1280
|
class: "mode-btn mode-btn-active"
|
|
1240
1281
|
}, "Text");
|
|
1241
1282
|
d.dataset.mode = "text";
|
|
1242
|
-
const l =
|
|
1243
|
-
l.dataset.mode = "image", d.addEventListener("click", (
|
|
1244
|
-
|
|
1245
|
-
}), l.addEventListener("click", (
|
|
1246
|
-
|
|
1247
|
-
}),
|
|
1283
|
+
const l = h("button", { class: "mode-btn" }, "Image");
|
|
1284
|
+
l.dataset.mode = "image", d.addEventListener("click", (c) => {
|
|
1285
|
+
c.stopPropagation(), this.setMode("text"), d.classList.add("mode-btn-active"), l.classList.remove("mode-btn-active"), this.currentContent?.type === "image" && this.emit("clear", { areaId: e.id });
|
|
1286
|
+
}), l.addEventListener("click", (c) => {
|
|
1287
|
+
c.stopPropagation(), this.setMode("image"), l.classList.add("mode-btn-active"), d.classList.remove("mode-btn-active"), this.currentContent?.type === "text" && this.emit("clear", { areaId: e.id });
|
|
1288
|
+
}), r.appendChild(d), r.appendChild(l), this.element.appendChild(r);
|
|
1248
1289
|
}
|
|
1249
|
-
this.contentContainer =
|
|
1290
|
+
this.contentContainer = h("div", { class: "area-card-content" }), this.element.appendChild(this.contentContainer), this.transformContainer = h("div", { class: "area-card-transforms" }), this.element.appendChild(this.transformContainer), this.renderContent();
|
|
1250
1291
|
}
|
|
1251
1292
|
setMode(e) {
|
|
1252
1293
|
this.mode = e, this.renderContent();
|
|
@@ -1269,11 +1310,11 @@ class te extends T {
|
|
|
1269
1310
|
}
|
|
1270
1311
|
renderContent() {
|
|
1271
1312
|
this.contentContainer.innerHTML = "", this.textInputEl = null, this.fontSelectEl = null, this.sizeInputEl = null, this.colorInputEl = null, this.alignGroupEl = null;
|
|
1272
|
-
const e =
|
|
1313
|
+
const e = S(this.area), t = k(this.area), i = !t || e && t && this.mode === "text", n = !e || e && t && this.mode === "image";
|
|
1273
1314
|
e && i && this.renderTextControls(), t && n && this.renderImageControls();
|
|
1274
1315
|
}
|
|
1275
1316
|
renderTextControls() {
|
|
1276
|
-
const e = this.currentContent, t = e?.type === "text" ? e :
|
|
1317
|
+
const e = this.currentContent, t = e?.type === "text" ? e : E(this.area), i = h("input", {
|
|
1277
1318
|
class: "area-input-text",
|
|
1278
1319
|
type: "text",
|
|
1279
1320
|
placeholder: this.area.placeholder || "Enter text..."
|
|
@@ -1281,79 +1322,79 @@ class te extends T {
|
|
|
1281
1322
|
i.value = t.text, this.area.textOptions?.maxLength && (i.maxLength = this.area.textOptions.maxLength), i.addEventListener("input", () => {
|
|
1282
1323
|
this.emit("text:change", { areaId: this.area.id, updates: { text: i.value } });
|
|
1283
1324
|
}), this.contentContainer.appendChild(i), this.textInputEl = i;
|
|
1284
|
-
const n =
|
|
1285
|
-
for (const l of
|
|
1286
|
-
const
|
|
1287
|
-
|
|
1325
|
+
const n = h("div", { class: "area-input-row" }), a = h("select", { class: "area-input-font" });
|
|
1326
|
+
for (const l of Z) {
|
|
1327
|
+
const c = h("option");
|
|
1328
|
+
c.value = l.value, c.textContent = l.label, l.value === t.font && (c.selected = !0), a.appendChild(c);
|
|
1288
1329
|
}
|
|
1289
|
-
|
|
1290
|
-
this.emit("text:change", { areaId: this.area.id, updates: { font:
|
|
1291
|
-
}), n.appendChild(
|
|
1292
|
-
const
|
|
1330
|
+
a.addEventListener("change", () => {
|
|
1331
|
+
this.emit("text:change", { areaId: this.area.id, updates: { font: a.value } });
|
|
1332
|
+
}), n.appendChild(a), this.fontSelectEl = a;
|
|
1333
|
+
const o = h("input", {
|
|
1293
1334
|
class: "area-input-size",
|
|
1294
1335
|
type: "number"
|
|
1295
1336
|
});
|
|
1296
|
-
|
|
1337
|
+
o.value = String(t.size), o.min = String(this.area.textOptions?.minSize || 8), o.max = String(this.area.textOptions?.maxSize || 200), o.addEventListener("change", () => {
|
|
1297
1338
|
this.emit("text:change", {
|
|
1298
1339
|
areaId: this.area.id,
|
|
1299
|
-
updates: { size: parseInt(
|
|
1340
|
+
updates: { size: parseInt(o.value, 10) || 24 }
|
|
1300
1341
|
});
|
|
1301
|
-
}), n.appendChild(
|
|
1302
|
-
const
|
|
1303
|
-
for (const l of
|
|
1304
|
-
const
|
|
1342
|
+
}), n.appendChild(o), this.sizeInputEl = o, this.contentContainer.appendChild(n);
|
|
1343
|
+
const s = h("div", { class: "area-input-row" }), r = h("div", { class: "area-input-align" });
|
|
1344
|
+
for (const l of q) {
|
|
1345
|
+
const c = h("button", {
|
|
1305
1346
|
class: `align-btn${t.align === l.value ? " align-btn-active" : ""}`,
|
|
1306
1347
|
title: l.label === "L" ? "Left" : l.label === "C" ? "Center" : "Right"
|
|
1307
1348
|
}, l.label);
|
|
1308
|
-
|
|
1309
|
-
u.stopPropagation(), this.emit("text:change", { areaId: this.area.id, updates: { align: l.value } }),
|
|
1310
|
-
}),
|
|
1349
|
+
c.addEventListener("click", (u) => {
|
|
1350
|
+
u.stopPropagation(), this.emit("text:change", { areaId: this.area.id, updates: { align: l.value } }), r.querySelectorAll(".align-btn").forEach((p) => p.classList.remove("align-btn-active")), c.classList.add("align-btn-active");
|
|
1351
|
+
}), r.appendChild(c);
|
|
1311
1352
|
}
|
|
1312
|
-
|
|
1313
|
-
const d =
|
|
1353
|
+
s.appendChild(r), this.alignGroupEl = r;
|
|
1354
|
+
const d = h("input", {
|
|
1314
1355
|
class: "area-input-color",
|
|
1315
1356
|
type: "color"
|
|
1316
1357
|
});
|
|
1317
1358
|
d.value = t.color, d.addEventListener("input", () => {
|
|
1318
1359
|
this.emit("text:change", { areaId: this.area.id, updates: { color: d.value } });
|
|
1319
|
-
}),
|
|
1360
|
+
}), s.appendChild(d), this.colorInputEl = d, this.contentContainer.appendChild(s);
|
|
1320
1361
|
}
|
|
1321
1362
|
renderImageControls() {
|
|
1322
1363
|
const e = this.currentContent;
|
|
1323
1364
|
if (e?.type === "image") {
|
|
1324
|
-
const t =
|
|
1365
|
+
const t = h("div", { class: "area-image-preview" }), i = h("img", { class: "area-image-thumb" });
|
|
1325
1366
|
i.src = e.dataUrl, i.alt = e.filename || "Uploaded image", t.appendChild(i);
|
|
1326
|
-
const n =
|
|
1327
|
-
n.appendChild(
|
|
1328
|
-
const
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
}), n.appendChild(
|
|
1367
|
+
const n = h("div", { class: "area-image-info" });
|
|
1368
|
+
n.appendChild(h("span", { class: "area-image-name" }, e.filename || "Image"));
|
|
1369
|
+
const a = h("button", { class: "area-image-remove-btn" }, "Remove");
|
|
1370
|
+
a.addEventListener("click", (o) => {
|
|
1371
|
+
o.stopPropagation(), this.emit("clear", { areaId: this.area.id });
|
|
1372
|
+
}), n.appendChild(a), t.appendChild(n), this.contentContainer.appendChild(t);
|
|
1332
1373
|
} else {
|
|
1333
|
-
const t =
|
|
1334
|
-
i.accept =
|
|
1335
|
-
const n =
|
|
1374
|
+
const t = h("button", { class: "area-upload-btn" }, "Upload Image"), i = h("input", { type: "file" });
|
|
1375
|
+
i.accept = P.join(","), i.style.display = "none";
|
|
1376
|
+
const n = h("div", { class: "area-validation-error" });
|
|
1336
1377
|
n.style.display = "none", i.addEventListener("change", () => {
|
|
1337
|
-
const
|
|
1338
|
-
if (!
|
|
1339
|
-
if (!
|
|
1340
|
-
const
|
|
1341
|
-
n.textContent =
|
|
1378
|
+
const a = i.files?.[0];
|
|
1379
|
+
if (!a) return;
|
|
1380
|
+
if (!P.includes(a.type)) {
|
|
1381
|
+
const s = "Only PNG, JPEG, WebP, and SVG images are accepted";
|
|
1382
|
+
n.textContent = s, n.style.display = "block", this.emit("validation:error", { areaId: this.area.id, message: s }), i.value = "";
|
|
1342
1383
|
return;
|
|
1343
1384
|
}
|
|
1344
|
-
if (
|
|
1345
|
-
const
|
|
1346
|
-
n.textContent =
|
|
1385
|
+
if (a.size > Y) {
|
|
1386
|
+
const s = "File must be smaller than 15MB";
|
|
1387
|
+
n.textContent = s, n.style.display = "block", this.emit("validation:error", { areaId: this.area.id, message: s }), i.value = "";
|
|
1347
1388
|
return;
|
|
1348
1389
|
}
|
|
1349
1390
|
n.style.display = "none";
|
|
1350
|
-
const
|
|
1351
|
-
|
|
1352
|
-
const
|
|
1353
|
-
this.emit("image:change", { areaId: this.area.id, dataUrl:
|
|
1354
|
-
},
|
|
1355
|
-
}), t.addEventListener("click", (
|
|
1356
|
-
|
|
1391
|
+
const o = new FileReader();
|
|
1392
|
+
o.onload = () => {
|
|
1393
|
+
const s = o.result;
|
|
1394
|
+
this.emit("image:change", { areaId: this.area.id, dataUrl: s, filename: a.name });
|
|
1395
|
+
}, o.readAsDataURL(a), i.value = "";
|
|
1396
|
+
}), t.addEventListener("click", (a) => {
|
|
1397
|
+
a.stopPropagation(), i.click();
|
|
1357
1398
|
}), this.contentContainer.appendChild(t), this.contentContainer.appendChild(i), this.contentContainer.appendChild(n);
|
|
1358
1399
|
}
|
|
1359
1400
|
}
|
|
@@ -1363,72 +1404,82 @@ class te extends T {
|
|
|
1363
1404
|
if (!this.isSelected || !e) return;
|
|
1364
1405
|
const t = e.type === "text" ? this.area.textOptions?.allowPositioning : this.area.imageOptions?.allowPositioning, i = e.type === "image" && this.area.imageOptions?.allowScaling, n = e.type === "image" && this.area.imageOptions?.allowRotation;
|
|
1365
1406
|
if (!t && !i && !n) return;
|
|
1366
|
-
const
|
|
1367
|
-
this.transformContainer.appendChild(
|
|
1368
|
-
const
|
|
1369
|
-
|
|
1370
|
-
const
|
|
1371
|
-
|
|
1407
|
+
const a = h("div", { class: "area-card-divider" });
|
|
1408
|
+
this.transformContainer.appendChild(a);
|
|
1409
|
+
const o = h("div", { class: "transform-header" });
|
|
1410
|
+
o.appendChild(h("span", { class: "transform-title" }, "Transform"));
|
|
1411
|
+
const s = h("button", { class: "transform-reset-btn" }, "Reset");
|
|
1412
|
+
s.addEventListener("click", (d) => {
|
|
1372
1413
|
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 }), n && this.emit("rotation:change", { areaId: this.area.id, rotation: 0 });
|
|
1373
|
-
}),
|
|
1374
|
-
const
|
|
1414
|
+
}), o.appendChild(s), this.transformContainer.appendChild(o);
|
|
1415
|
+
const r = e.offset ?? { x: 0, y: 0 };
|
|
1375
1416
|
if (t) {
|
|
1376
|
-
const d =
|
|
1377
|
-
|
|
1417
|
+
const d = h("div", { class: "area-input-row" }), l = h("label", { class: "transform-label" }, "X"), c = h("input", { class: "transform-input", type: "number" });
|
|
1418
|
+
c.value = String(Math.round(r.x)), c.addEventListener("change", () => {
|
|
1378
1419
|
this.emit("offset:change", {
|
|
1379
1420
|
areaId: this.area.id,
|
|
1380
|
-
offset: { ...
|
|
1421
|
+
offset: { ...r, x: parseInt(c.value, 10) || 0 }
|
|
1381
1422
|
});
|
|
1382
1423
|
});
|
|
1383
|
-
const u =
|
|
1384
|
-
|
|
1424
|
+
const u = h("label", { class: "transform-label" }, "Y"), p = h("input", { class: "transform-input", type: "number" });
|
|
1425
|
+
p.value = String(Math.round(r.y)), p.addEventListener("change", () => {
|
|
1385
1426
|
this.emit("offset:change", {
|
|
1386
1427
|
areaId: this.area.id,
|
|
1387
|
-
offset: { ...
|
|
1428
|
+
offset: { ...r, y: parseInt(p.value, 10) || 0 }
|
|
1388
1429
|
});
|
|
1389
|
-
}), d.appendChild(l), d.appendChild(
|
|
1430
|
+
}), d.appendChild(l), d.appendChild(c), d.appendChild(u), d.appendChild(p), this.transformContainer.appendChild(d);
|
|
1390
1431
|
}
|
|
1391
1432
|
if (i && e.type === "image") {
|
|
1392
|
-
const d = e.scale ?? 1, l =
|
|
1433
|
+
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)}%`), u = h("input", { class: "transform-slider", type: "range" });
|
|
1393
1434
|
u.min = "10", u.max = "500", u.step = "5", u.value = String(Math.round(d * 100)), u.addEventListener("input", () => {
|
|
1394
|
-
const
|
|
1395
|
-
|
|
1396
|
-
}), l.appendChild(
|
|
1435
|
+
const p = parseInt(u.value, 10) / 100;
|
|
1436
|
+
c.textContent = `Scale: ${Math.round(p * 100)}%`, this.emit("scale:change", { areaId: this.area.id, scale: p });
|
|
1437
|
+
}), l.appendChild(c), l.appendChild(u), this.transformContainer.appendChild(l);
|
|
1397
1438
|
}
|
|
1398
1439
|
if (n && e.type === "image") {
|
|
1399
|
-
const d = e.rotation ?? 0, l =
|
|
1440
|
+
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)}°`), u = h("input", { class: "transform-slider", type: "range" });
|
|
1400
1441
|
u.min = "0", u.max = "360", u.step = "1", u.value = String(Math.round(d)), u.addEventListener("input", () => {
|
|
1401
|
-
const
|
|
1402
|
-
|
|
1403
|
-
}), l.appendChild(
|
|
1442
|
+
const p = parseInt(u.value, 10);
|
|
1443
|
+
c.textContent = `Rotation: ${p}°`, this.emit("rotation:change", { areaId: this.area.id, rotation: p });
|
|
1444
|
+
}), l.appendChild(c), l.appendChild(u), this.transformContainer.appendChild(l);
|
|
1404
1445
|
}
|
|
1405
1446
|
}
|
|
1406
1447
|
getElement() {
|
|
1407
1448
|
return this.element;
|
|
1408
1449
|
}
|
|
1409
1450
|
}
|
|
1410
|
-
class
|
|
1451
|
+
class j extends I {
|
|
1411
1452
|
constructor(e) {
|
|
1412
1453
|
super(), this.cards = /* @__PURE__ */ new Map(), this.isMobile = !1, this.boundEscapeHandler = (n) => {
|
|
1413
1454
|
n.key === "Escape" && this.emit("dismiss", void 0);
|
|
1414
|
-
}, this.backdrop =
|
|
1455
|
+
}, this.backdrop = h("div", { class: "area-panel-backdrop" }), this.backdrop.addEventListener("click", () => {
|
|
1415
1456
|
this.emit("dismiss", void 0);
|
|
1416
|
-
}), this.element =
|
|
1417
|
-
const t =
|
|
1418
|
-
t.appendChild(
|
|
1419
|
-
const i =
|
|
1457
|
+
}), this.element = h("div", { class: "area-panel" });
|
|
1458
|
+
const t = h("div", { class: "area-panel-header" });
|
|
1459
|
+
t.appendChild(h("span", { class: "area-panel-title" }, "Customize"));
|
|
1460
|
+
const i = h("button", { class: "area-panel-close-btn", title: "Close" }, "×");
|
|
1420
1461
|
if (i.addEventListener("click", () => {
|
|
1421
1462
|
this.emit("dismiss", void 0);
|
|
1422
|
-
}), t.appendChild(i), this.element.appendChild(t), this.panelContent =
|
|
1423
|
-
const n =
|
|
1463
|
+
}), t.appendChild(i), this.element.appendChild(t), this.panelContent = h("div", { class: "area-panel-content" }), e.length === 0) {
|
|
1464
|
+
const n = h("p", { class: "area-panel-empty" }, "No customization areas defined.");
|
|
1424
1465
|
this.panelContent.appendChild(n);
|
|
1425
1466
|
} else
|
|
1426
1467
|
for (const n of e) {
|
|
1427
|
-
const
|
|
1428
|
-
this.cards.set(n.id,
|
|
1468
|
+
const a = new L(n);
|
|
1469
|
+
this.cards.set(n.id, a), a.on("text:change", (o) => this.emit("text:change", o)), a.on("image:change", (o) => this.emit("image:change", o)), a.on("clear", (o) => this.emit("clear", o)), a.on("select", (o) => this.emit("select", o)), a.on("offset:change", (o) => this.emit("offset:change", o)), a.on("scale:change", (o) => this.emit("scale:change", o)), a.on("rotation:change", (o) => this.emit("rotation:change", o)), a.on("validation:error", (o) => this.emit("validation:error", o)), this.panelContent.appendChild(a.getElement());
|
|
1429
1470
|
}
|
|
1430
1471
|
this.element.appendChild(this.panelContent);
|
|
1431
1472
|
}
|
|
1473
|
+
setAreas(e) {
|
|
1474
|
+
if (this.cards.clear(), this.panelContent.innerHTML = "", e.length === 0) {
|
|
1475
|
+
const t = h("p", { class: "area-panel-empty" }, "No customization areas defined.");
|
|
1476
|
+
this.panelContent.appendChild(t);
|
|
1477
|
+
} else
|
|
1478
|
+
for (const t of e) {
|
|
1479
|
+
const i = new L(t);
|
|
1480
|
+
this.cards.set(t.id, i), i.on("text:change", (n) => this.emit("text:change", n)), i.on("image:change", (n) => this.emit("image:change", n)), i.on("clear", (n) => this.emit("clear", n)), i.on("select", (n) => this.emit("select", n)), i.on("offset:change", (n) => this.emit("offset:change", n)), i.on("scale:change", (n) => this.emit("scale:change", n)), i.on("rotation:change", (n) => this.emit("rotation:change", n)), i.on("validation:error", (n) => this.emit("validation:error", n)), this.panelContent.appendChild(i.getElement());
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1432
1483
|
setContents(e) {
|
|
1433
1484
|
for (const [t, i] of this.cards)
|
|
1434
1485
|
i.setContent(e.get(t));
|
|
@@ -1450,7 +1501,7 @@ class ie extends T {
|
|
|
1450
1501
|
return this.backdrop;
|
|
1451
1502
|
}
|
|
1452
1503
|
}
|
|
1453
|
-
class
|
|
1504
|
+
class X {
|
|
1454
1505
|
constructor(e = window) {
|
|
1455
1506
|
this.target = e, this.commands = [], this.isEnabled = !0, this.handleKeyDown = (t) => {
|
|
1456
1507
|
if (!this.isEnabled) return;
|
|
@@ -1504,31 +1555,31 @@ class ne {
|
|
|
1504
1555
|
this.target.removeEventListener("keydown", this.handleKeyDown), this.commands = [];
|
|
1505
1556
|
}
|
|
1506
1557
|
}
|
|
1507
|
-
function
|
|
1558
|
+
function W() {
|
|
1508
1559
|
return /Mac|iPod|iPhone|iPad/.test(navigator.platform);
|
|
1509
1560
|
}
|
|
1510
|
-
function
|
|
1511
|
-
return
|
|
1561
|
+
function J() {
|
|
1562
|
+
return W() ? "meta" : "ctrl";
|
|
1512
1563
|
}
|
|
1513
|
-
function
|
|
1514
|
-
const n =
|
|
1515
|
-
return Math.min(n,
|
|
1564
|
+
function G(f, e, t, i) {
|
|
1565
|
+
const n = f / t, a = e / i;
|
|
1566
|
+
return Math.min(n, a);
|
|
1516
1567
|
}
|
|
1517
|
-
function
|
|
1518
|
-
const
|
|
1568
|
+
function K(f, e, t = 8, i = 10, n = 300) {
|
|
1569
|
+
const a = G(f, e, t, i), o = a >= n;
|
|
1519
1570
|
return {
|
|
1520
|
-
actualDPI: Math.round(
|
|
1571
|
+
actualDPI: Math.round(a),
|
|
1521
1572
|
targetDPI: n,
|
|
1522
|
-
width:
|
|
1573
|
+
width: f,
|
|
1523
1574
|
height: e,
|
|
1524
|
-
passed:
|
|
1525
|
-
warning:
|
|
1575
|
+
passed: o,
|
|
1576
|
+
warning: o ? void 0 : `Image resolution is ${Math.round(a)} DPI at 100% scale. For best print quality, use images with at least ${n} DPI. This may result in pixelated or blurry prints.`
|
|
1526
1577
|
};
|
|
1527
1578
|
}
|
|
1528
|
-
const F = ':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}.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}}', de = "2.0.0", le = 768;
|
|
1529
|
-
class
|
|
1579
|
+
const z = ':host{--editor-bg: #f3f4f6;--editor-surface: #ffffff;--editor-border: #e5e7eb;--editor-text: #111827;--editor-text-muted: #6b7280;--editor-primary: #3b82f6;--editor-primary-hover: #2563eb;--editor-danger: #ef4444;--editor-radius: 8px;--editor-radius-sm: 4px;--editor-font: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;--editor-panel-width: 280px;display:block;width:100%;height:100%;font-family:var(--editor-font);font-size:13px;color:var(--editor-text);line-height:1.4}:host([theme="dark"]){--editor-bg: #1f2937;--editor-surface: #374151;--editor-border: #4b5563;--editor-text: #f9fafb;--editor-text-muted: #9ca3af}.editor-container{display:flex;flex-direction:column;width:100%;height:100%;position:relative;overflow:hidden;background:var(--editor-bg)}.layout-desktop .editor-main{display:flex;flex:1;min-height:0}.layout-desktop .canvas-area{flex:1;min-width:0;position:relative;display:flex;flex-direction:column}.layout-desktop .area-panel{width:var(--editor-panel-width);border-left:1px solid var(--editor-border);background:var(--editor-surface);display:none;flex-direction:column;overflow:hidden}.layout-desktop .area-panel.area-panel-visible{display:flex}.layout-mobile .editor-main{display:flex;flex-direction:column;flex:1;min-height:0}.layout-mobile .canvas-area{flex:1;position:relative;display:flex;flex-direction:column;min-height:0}.layout-mobile .area-panel{position:absolute;bottom:0;left:0;right:0;max-height:50%;background:var(--editor-surface);border-top:1px solid var(--editor-border);border-radius:var(--editor-radius) var(--editor-radius) 0 0;box-shadow:0 -4px 20px #00000026;transform:translateY(100%);transition:transform .25s ease-out;z-index:20;display:flex;flex-direction:column;overflow:hidden}.layout-mobile .area-panel.area-panel-visible{transform:translateY(0)}.area-panel-backdrop{display:none}.layout-mobile .area-panel-backdrop{display:block;position:absolute;inset:0;background:#0000004d;z-index:15;opacity:0;pointer-events:none;transition:opacity .25s ease-out}.layout-mobile .area-panel-backdrop.area-panel-backdrop-visible{opacity:1;pointer-events:auto}.zoom-toolbar{display:flex;align-items:center;gap:4px;padding:8px 12px;background:var(--editor-surface);border-bottom:1px solid var(--editor-border)}.zoom-btn{display:inline-flex;align-items:center;justify-content:center;width:32px;height:32px;border:1px solid var(--editor-border);border-radius:var(--editor-radius-sm);background:var(--editor-surface);color:var(--editor-text);cursor:pointer;font-size:14px;font-family:var(--editor-font);transition:background .15s;-webkit-tap-highlight-color:transparent;touch-action:manipulation}.zoom-btn:hover{background:var(--editor-bg)}.zoom-btn:active{background:var(--editor-border)}.zoom-percent{margin-left:8px;font-size:12px;color:var(--editor-text-muted);min-width:40px}.view-select{margin-left:8px;padding:4px 8px;height:32px;border:1px solid var(--editor-border);border-radius:var(--editor-radius-sm);background:var(--editor-surface);color:var(--editor-text);font-family:var(--editor-font);font-size:12px;cursor:pointer;-webkit-tap-highlight-color:transparent}.view-select:hover{background:var(--editor-bg)}.view-select:focus{outline:2px solid var(--editor-primary);outline-offset:-1px}.zoom-toolbar-spacer{flex:1}.zoom-close-btn{width:auto;padding:0 12px;font-size:13px;gap:4px}.zoom-save-btn{width:auto;padding:0 16px;margin-left:8px;font-size:13px;font-weight:600;background:var(--editor-primary);color:#fff;border-color:var(--editor-primary)}.zoom-save-btn:hover{background:var(--editor-primary-hover)}.zoom-save-btn:disabled{opacity:.5;cursor:not-allowed}.canvas-wrapper{flex:1;position:relative;overflow:hidden;min-height:200px}.editor-canvas{position:absolute;top:0;left:0}.area-panel-header{display:flex;justify-content:space-between;align-items:center;padding:12px 16px;border-bottom:1px solid var(--editor-border);flex-shrink:0}.area-panel-title{font-weight:600;font-size:14px}.area-panel-close-btn{width:28px;height:28px;border:none;background:none;color:var(--editor-text-muted);cursor:pointer;font-size:18px;line-height:1;border-radius:var(--editor-radius-sm);display:flex;align-items:center;justify-content:center}.area-panel-close-btn:hover{background:var(--editor-bg);color:var(--editor-text)}.area-panel-content{flex:1;overflow-y:auto;padding:12px;display:flex;flex-direction:column;gap:12px}.area-panel-empty{color:var(--editor-text-muted);font-size:12px;text-align:center;padding:20px 0}.area-card{padding:12px;border:1px solid var(--editor-border);border-radius:var(--editor-radius);background:var(--editor-surface);cursor:pointer;transition:border-color .15s}.area-card:hover{border-color:var(--editor-primary)}.area-card-selected{border-color:var(--editor-primary);box-shadow:0 0 0 1px var(--editor-primary)}.area-card-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:8px}.area-card-name-row{display:flex;align-items:center;gap:6px}.area-card-name{font-weight:600;font-size:13px}.area-card-badge{display:inline-block;padding:1px 6px;font-size:10px;font-weight:500;background:#fef3c7;color:#92400e;border-radius:10px}.area-card-reset-btn{padding:2px 8px;border:1px solid var(--editor-border);border-radius:var(--editor-radius-sm);background:none;color:var(--editor-text-muted);cursor:pointer;font-size:11px;font-family:var(--editor-font)}.area-card-reset-btn:hover{background:var(--editor-bg);color:var(--editor-danger);border-color:var(--editor-danger)}.area-card-mode-switcher{display:flex;gap:4px;margin-bottom:8px}.mode-btn{flex:1;padding:4px 8px;border:1px solid var(--editor-border);border-radius:var(--editor-radius-sm);background:var(--editor-surface);color:var(--editor-text);cursor:pointer;font-size:12px;font-family:var(--editor-font);transition:all .15s}.mode-btn-active{background:var(--editor-primary);color:#fff;border-color:var(--editor-primary)}.area-input-text{width:100%;padding:6px 8px;border:1px solid var(--editor-border);border-radius:var(--editor-radius-sm);font-family:var(--editor-font);font-size:13px;color:var(--editor-text);background:var(--editor-surface);box-sizing:border-box;margin-bottom:6px}.area-input-text:focus{outline:none;border-color:var(--editor-primary);box-shadow:0 0 0 1px var(--editor-primary)}.area-input-row{display:flex;gap:6px;align-items:center;margin-bottom:6px}.area-input-font{flex:1;padding:4px 6px;border:1px solid var(--editor-border);border-radius:var(--editor-radius-sm);font-family:var(--editor-font);font-size:12px;color:var(--editor-text);background:var(--editor-surface);min-width:0}.area-input-size{width:56px;padding:4px 6px;border:1px solid var(--editor-border);border-radius:var(--editor-radius-sm);font-family:var(--editor-font);font-size:12px;color:var(--editor-text);background:var(--editor-surface)}.area-input-align{display:flex;gap:2px}.align-btn{width:28px;height:28px;border:1px solid var(--editor-border);border-radius:var(--editor-radius-sm);background:var(--editor-surface);color:var(--editor-text);cursor:pointer;font-size:12px;font-weight:600;font-family:var(--editor-font)}.align-btn-active{background:var(--editor-primary);color:#fff;border-color:var(--editor-primary)}.area-input-color{width:32px;height:28px;padding:0;border:1px solid var(--editor-border);border-radius:var(--editor-radius-sm);cursor:pointer;background:none}.area-upload-btn{width:100%;padding:10px;border:2px dashed var(--editor-border);border-radius:var(--editor-radius);background:var(--editor-bg);color:var(--editor-text-muted);cursor:pointer;font-family:var(--editor-font);font-size:12px;text-align:center;transition:all .15s;min-height:44px}.area-upload-btn:hover{border-color:var(--editor-primary);color:var(--editor-primary)}.area-image-preview{display:flex;gap:8px;align-items:center}.area-image-thumb{width:48px;height:48px;object-fit:cover;border-radius:var(--editor-radius-sm);border:1px solid var(--editor-border)}.area-image-info{flex:1;min-width:0;display:flex;flex-direction:column;gap:4px}.area-image-name{font-size:12px;color:var(--editor-text-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.area-image-remove-btn{padding:2px 8px;border:1px solid var(--editor-danger);border-radius:var(--editor-radius-sm);background:none;color:var(--editor-danger);cursor:pointer;font-size:11px;font-family:var(--editor-font);width:fit-content}.area-image-remove-btn:hover{background:#fef2f2}.area-card-divider{height:1px;background:var(--editor-border);margin:8px 0}.transform-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:6px}.transform-title{font-weight:600;font-size:12px}.transform-reset-btn{padding:2px 8px;border:1px solid var(--editor-border);border-radius:var(--editor-radius-sm);background:none;color:var(--editor-text-muted);cursor:pointer;font-size:11px;font-family:var(--editor-font)}.transform-reset-btn:hover{background:var(--editor-bg)}.transform-label{font-size:11px;color:var(--editor-text-muted);white-space:nowrap}.transform-input{width:60px;padding:3px 6px;border:1px solid var(--editor-border);border-radius:var(--editor-radius-sm);font-family:var(--editor-font);font-size:12px;color:var(--editor-text);background:var(--editor-surface)}.transform-slider-row{flex-direction:column;align-items:stretch}.transform-slider{width:100%;accent-color:var(--editor-primary)}.loading-container{display:flex;justify-content:center;align-items:center;width:100%;height:100%;min-height:200px}.loading-spinner{width:32px;height:32px;border:3px solid var(--editor-border);border-top-color:var(--editor-primary);border-radius:50%;animation:spin .8s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}.error-container{display:flex;flex-direction:column;justify-content:center;align-items:center;width:100%;height:100%;min-height:200px;padding:20px;text-align:center}.error-title{font-weight:600;font-size:16px;color:var(--editor-danger);margin-bottom:8px}.error-message{font-size:13px;color:var(--editor-text-muted);max-width:400px;white-space:pre-wrap}:host(:focus){outline:2px solid var(--editor-primary);outline-offset:-2px}input:focus,select:focus,button:focus-visible{outline:2px solid var(--editor-primary);outline-offset:-1px}.context-menu{position:absolute;z-index:100;min-width:160px;background:var(--editor-surface);border:1px solid var(--editor-border);border-radius:var(--editor-radius);box-shadow:0 4px 12px #00000026;padding:4px 0;font-size:13px}.context-menu-header{padding:6px 12px;font-weight:600;font-size:12px;color:var(--editor-text-muted);user-select:none}.context-menu-divider{height:1px;background:var(--editor-border);margin:4px 0}.context-menu-item{padding:6px 12px;cursor:pointer;color:var(--editor-text);user-select:none;transition:background .1s}.context-menu-item:hover{background:var(--editor-bg)}.context-menu-item:active{background:var(--editor-border)}.context-menu-item-disabled{color:var(--editor-text-muted);cursor:default;pointer-events:none}.editor-toast{position:absolute;top:16px;left:50%;transform:translate(-50%);padding:10px 20px;border-radius:var(--editor-radius);font-size:13px;font-family:var(--editor-font);line-height:1.4;z-index:200;max-width:400px;text-align:center;animation:toast-in .25s ease-out,toast-out .25s ease-in forwards;animation-delay:0s,var(--toast-duration, 3.75s);pointer-events:none;box-shadow:0 4px 12px #00000026}.editor-toast-warning{background:#fef3c7;color:#92400e;border:1px solid #f59e0b}.editor-toast-error{background:#fef2f2;color:#991b1b;border:1px solid var(--editor-danger)}.editor-toast-info{background:#eff6ff;color:#1e40af;border:1px solid var(--editor-primary)}@keyframes toast-in{0%{opacity:0;transform:translate(-50%) translateY(-10px)}to{opacity:1;transform:translate(-50%) translateY(0)}}@keyframes toast-out{0%{opacity:1;transform:translate(-50%) translateY(0)}to{opacity:0;transform:translate(-50%) translateY(-10px)}}.editor-modal-overlay{position:absolute;inset:0;background:#0006;z-index:300;display:flex;align-items:center;justify-content:center;animation:modal-overlay-in .2s ease-out}@keyframes modal-overlay-in{0%{opacity:0}to{opacity:1}}.editor-modal{background:var(--editor-surface);border-radius:var(--editor-radius);box-shadow:0 8px 32px #0003;max-width:380px;width:calc(100% - 32px);padding:20px;animation:modal-in .2s ease-out}@keyframes modal-in{0%{opacity:0;transform:scale(.95)}to{opacity:1;transform:scale(1)}}.editor-modal-title{font-weight:600;font-size:15px;margin-bottom:8px;color:var(--editor-text)}.editor-modal-body{font-size:13px;color:var(--editor-text-muted);margin-bottom:16px;line-height:1.5}.editor-modal-actions{display:flex;justify-content:flex-end;gap:8px}.editor-modal-btn{padding:8px 16px;border-radius:var(--editor-radius-sm);font-size:13px;font-family:var(--editor-font);cursor:pointer;border:1px solid var(--editor-border);background:var(--editor-surface);color:var(--editor-text);transition:background .15s}.editor-modal-btn:hover{background:var(--editor-bg)}.editor-modal-btn-primary{background:var(--editor-primary);color:#fff;border-color:var(--editor-primary)}.editor-modal-btn-primary:hover{background:var(--editor-primary-hover)}.area-validation-error{font-size:11px;color:var(--editor-danger);margin-top:4px}@media(pointer:coarse){.zoom-btn{min-width:44px;min-height:44px}.area-input-text,.area-input-font,.area-input-size{min-height:44px;font-size:16px}.align-btn{min-width:44px;min-height:44px}.area-upload-btn{min-height:48px}.view-select{min-height:44px;font-size:16px}}', Q = "2.0.0", _ = 768;
|
|
1580
|
+
class ee extends HTMLElement {
|
|
1530
1581
|
constructor() {
|
|
1531
|
-
super(), this.resizeObserver = null, this.isReady = !1, this.currentTemplate = null, this.loadingTemplateId = null, this.isMobileLayout = !1, this.subscriptions = [], this.storageUsage = null, this.storageUsageLastRefresh = 0, this.emitChange =
|
|
1582
|
+
super(), this.resizeObserver = null, this.isReady = !1, this.currentTemplate = null, this.loadingTemplateId = null, this.isMobileLayout = !1, this.productViews = [], this.activeViewName = null, this.perViewContents = /* @__PURE__ */ new Map(), this.subscriptions = [], this.storageUsage = null, this.storageUsageLastRefresh = 0, this.emitChange = H(() => {
|
|
1532
1583
|
this.dispatchEvent(
|
|
1533
1584
|
new CustomEvent("change", {
|
|
1534
1585
|
detail: { design: this.getDesign() },
|
|
@@ -1539,7 +1590,7 @@ class he extends HTMLElement {
|
|
|
1539
1590
|
}, 300), this.shadow = this.attachShadow({ mode: "open" });
|
|
1540
1591
|
}
|
|
1541
1592
|
static get observedAttributes() {
|
|
1542
|
-
return ["template-id", "theme", "mode", "store-id", "api-url"];
|
|
1593
|
+
return ["template-id", "product-id", "theme", "mode", "store-id", "api-url"];
|
|
1543
1594
|
}
|
|
1544
1595
|
async connectedCallback() {
|
|
1545
1596
|
try {
|
|
@@ -1552,14 +1603,14 @@ class he extends HTMLElement {
|
|
|
1552
1603
|
this.cleanup();
|
|
1553
1604
|
}
|
|
1554
1605
|
attributeChangedCallback(e, t, i) {
|
|
1555
|
-
t !== i && e === "template-id" && this.isReady && i && this.loadTemplate(i);
|
|
1606
|
+
t !== i && (e === "template-id" && this.isReady && i && this.loadTemplate(i), e === "product-id" && this.isReady && i && this.loadProduct(i));
|
|
1556
1607
|
}
|
|
1557
1608
|
// ─── Public API ───
|
|
1558
1609
|
getDesign() {
|
|
1559
1610
|
if (!this.currentTemplate) throw new Error("No design loaded");
|
|
1560
1611
|
return {
|
|
1561
1612
|
templateId: this.currentTemplate.metadata.id,
|
|
1562
|
-
engineVersion:
|
|
1613
|
+
engineVersion: Q,
|
|
1563
1614
|
contents: this.contentManager.toJSON(),
|
|
1564
1615
|
userData: {}
|
|
1565
1616
|
};
|
|
@@ -1583,7 +1634,7 @@ class he extends HTMLElement {
|
|
|
1583
1634
|
contents: this.contentManager.toJSON(),
|
|
1584
1635
|
selectedAreaId: null,
|
|
1585
1636
|
isDirty: !1
|
|
1586
|
-
}), this.engine.setSelectedArea(null), this.inputPanel.setSelectedArea(null), this.engine.fitToView(), this.stateManager.update({
|
|
1637
|
+
}), this.zoomToolbar?.showSaveButton(!1), this.engine.setSelectedArea(null), this.inputPanel.setSelectedArea(null), this.engine.fitToView(), this.stateManager.update({
|
|
1587
1638
|
zoom: this.engine.getZoom(),
|
|
1588
1639
|
pan: this.engine.getPan()
|
|
1589
1640
|
});
|
|
@@ -1594,6 +1645,24 @@ class he extends HTMLElement {
|
|
|
1594
1645
|
getSelectedAreaId() {
|
|
1595
1646
|
return this.stateManager.getState().selectedAreaId;
|
|
1596
1647
|
}
|
|
1648
|
+
getActiveView() {
|
|
1649
|
+
return this.activeViewName;
|
|
1650
|
+
}
|
|
1651
|
+
getViews() {
|
|
1652
|
+
return this.productViews.map((e) => ({
|
|
1653
|
+
viewName: e.viewName,
|
|
1654
|
+
viewOrder: e.viewOrder,
|
|
1655
|
+
isRequired: e.isRequired,
|
|
1656
|
+
isDefault: e.isDefault,
|
|
1657
|
+
templateId: e.template.id
|
|
1658
|
+
}));
|
|
1659
|
+
}
|
|
1660
|
+
setActiveView(e) {
|
|
1661
|
+
if (this.activeViewName === e) return;
|
|
1662
|
+
const t = this.productViews.find((i) => i.viewName === e);
|
|
1663
|
+
if (!t) throw new Error(`View not found: ${e}`);
|
|
1664
|
+
this.switchToView(t);
|
|
1665
|
+
}
|
|
1597
1666
|
setAreaContent(e, t) {
|
|
1598
1667
|
t.type === "text" ? this.contentManager.setTextContent(e, t) : this.contentManager.setImageContent(e, t.dataUrl, t.filename), this.syncEngineContents(), this.saveContentState();
|
|
1599
1668
|
}
|
|
@@ -1603,10 +1672,10 @@ class he extends HTMLElement {
|
|
|
1603
1672
|
async finalize() {
|
|
1604
1673
|
const e = this.engine.checkSafeAreaViolations();
|
|
1605
1674
|
if (e.length > 0) {
|
|
1606
|
-
const
|
|
1675
|
+
const a = this.currentTemplate?.areas || [], o = e.map((s) => a.find((r) => r.id === s)?.name || s).join(", ");
|
|
1607
1676
|
if (!await this.showConfirmation(
|
|
1608
1677
|
"Content outside safe area",
|
|
1609
|
-
`Content in ${
|
|
1678
|
+
`Content in ${o} extends beyond the safe area. It may be trimmed during printing. Continue?`,
|
|
1610
1679
|
"Cancel",
|
|
1611
1680
|
"Proceed Anyway"
|
|
1612
1681
|
))
|
|
@@ -1630,21 +1699,21 @@ class he extends HTMLElement {
|
|
|
1630
1699
|
// ─── Initialization ───
|
|
1631
1700
|
renderLoading() {
|
|
1632
1701
|
const e = document.createElement("style");
|
|
1633
|
-
e.textContent =
|
|
1634
|
-
const t =
|
|
1702
|
+
e.textContent = z, this.shadow.appendChild(e);
|
|
1703
|
+
const t = h(
|
|
1635
1704
|
"div",
|
|
1636
1705
|
{ class: "loading-container" },
|
|
1637
|
-
|
|
1706
|
+
h("div", { class: "loading-spinner" })
|
|
1638
1707
|
);
|
|
1639
1708
|
this.shadow.appendChild(t);
|
|
1640
1709
|
}
|
|
1641
1710
|
async initialize() {
|
|
1642
|
-
const e = this.getAttribute("api-url") || "http://localhost:4000
|
|
1643
|
-
this.apiClient = new
|
|
1644
|
-
const t = this.getAttribute("template-id");
|
|
1645
|
-
if (!t)
|
|
1646
|
-
throw new Error("template-id attribute is required");
|
|
1647
|
-
await this.
|
|
1711
|
+
const e = this.getAttribute("api-url") || "http://localhost:4000";
|
|
1712
|
+
this.apiClient = new $(`${e.replace(/\/+$/, "")}/public`);
|
|
1713
|
+
const t = this.getAttribute("product-id"), i = this.getAttribute("template-id");
|
|
1714
|
+
if (!t && !i)
|
|
1715
|
+
throw new Error("Either template-id or product-id attribute is required");
|
|
1716
|
+
t ? await this.loadProduct(t) : await this.loadTemplate(i), this.isReady = !0, this.dispatchEvent(new CustomEvent("ready", { bubbles: !0, composed: !0 }));
|
|
1648
1717
|
}
|
|
1649
1718
|
async loadTemplate(e) {
|
|
1650
1719
|
if (this.loadingTemplateId !== e) {
|
|
@@ -1652,57 +1721,120 @@ class he extends HTMLElement {
|
|
|
1652
1721
|
try {
|
|
1653
1722
|
const t = await this.apiClient.getTemplate(e);
|
|
1654
1723
|
if (this.loadingTemplateId !== e) return;
|
|
1655
|
-
this.
|
|
1656
|
-
const i = t.areas || [];
|
|
1657
|
-
this.contentManager = new X(i);
|
|
1658
|
-
const n = {
|
|
1659
|
-
template: t,
|
|
1660
|
-
contents: this.contentManager.toJSON(),
|
|
1661
|
-
selectedAreaId: null,
|
|
1662
|
-
zoom: 1,
|
|
1663
|
-
pan: { x: 0, y: 0 },
|
|
1664
|
-
isDirty: !1,
|
|
1665
|
-
warnings: []
|
|
1666
|
-
};
|
|
1667
|
-
this.stateManager = new j(n), this.buildUI(t, i), this.engine = new B(this.canvas), this.engine.setArtboard({
|
|
1668
|
-
width: t.artboard.width,
|
|
1669
|
-
height: t.artboard.height,
|
|
1670
|
-
backgroundColor: t.artboard.backgroundColor ?? "#ffffff"
|
|
1671
|
-
}), this.engine.setAreas(i), this.engine.setContents(this.contentManager.getContents()), t.backgroundImage && this.engine.setBackgroundImage(t.backgroundImage);
|
|
1672
|
-
const r = t.artboard.safeArea ?? t.safeArea;
|
|
1673
|
-
r && this.engine.setSafeArea(r), this.engine.start(), this.engine.fitToView(), this.stateManager.update({
|
|
1674
|
-
zoom: this.engine.getZoom(),
|
|
1675
|
-
pan: this.engine.getPan()
|
|
1676
|
-
}), this.zoomToolbar.setZoom(this.engine.getZoom()), this.interaction = new G(
|
|
1677
|
-
this.canvas,
|
|
1678
|
-
this.engine,
|
|
1679
|
-
() => this.contentManager.getContents(),
|
|
1680
|
-
() => this.stateManager.getState().selectedAreaId
|
|
1681
|
-
), this.wireInteractionEvents(), this.subscriptions.push(
|
|
1682
|
-
this.contentManager.subscribe(() => {
|
|
1683
|
-
this.syncEngineContents(), this.inputPanel.setContents(this.contentManager.getContents());
|
|
1684
|
-
})
|
|
1685
|
-
), this.keyboard = new ne(this), this.setupKeyboardShortcuts(), this.inputPanel.setContents(this.contentManager.getContents());
|
|
1686
|
-
const s = this.getAttribute("store-id");
|
|
1687
|
-
s && this.apiClient.getStorageUsage(s).then((a) => {
|
|
1688
|
-
this.storageUsage = a, a.usagePercent >= 100 ? this.showToast("Storage quota exceeded. Upgrade your plan for more storage.", "error") : a.usagePercent >= 80 && this.showToast(`Storage ${a.usagePercent}% full. Consider upgrading your plan.`, "warning");
|
|
1689
|
-
}).catch(() => {
|
|
1690
|
-
}), this.loadingTemplateId = null;
|
|
1724
|
+
await this.loadTemplateData(t), this.loadingTemplateId = null;
|
|
1691
1725
|
} catch (t) {
|
|
1692
1726
|
throw this.loadingTemplateId = null, new Error(`Failed to load template: ${t.message}`);
|
|
1693
1727
|
}
|
|
1694
1728
|
}
|
|
1695
1729
|
}
|
|
1730
|
+
async loadTemplateData(e) {
|
|
1731
|
+
this.currentTemplate = e;
|
|
1732
|
+
const t = e.areas || [];
|
|
1733
|
+
this.contentManager = new T(t);
|
|
1734
|
+
const i = {
|
|
1735
|
+
template: e,
|
|
1736
|
+
contents: this.contentManager.toJSON(),
|
|
1737
|
+
selectedAreaId: null,
|
|
1738
|
+
zoom: 1,
|
|
1739
|
+
pan: { x: 0, y: 0 },
|
|
1740
|
+
isDirty: !1,
|
|
1741
|
+
warnings: []
|
|
1742
|
+
};
|
|
1743
|
+
this.stateManager = new O(i), this.buildUI(e, t), this.engine = new N(this.canvas), this.engine.setArtboard({
|
|
1744
|
+
width: e.artboard.width,
|
|
1745
|
+
height: e.artboard.height,
|
|
1746
|
+
backgroundColor: e.artboard.backgroundColor ?? "#ffffff"
|
|
1747
|
+
}), this.engine.setAreas(t), this.engine.setContents(this.contentManager.getContents()), e.backgroundImage && this.engine.setBackgroundImage(e.backgroundImage);
|
|
1748
|
+
const n = e.artboard.safeArea ?? e.safeArea;
|
|
1749
|
+
n && this.engine.setSafeArea(n), this.engine.start(), this.engine.fitToView(), this.stateManager.update({
|
|
1750
|
+
zoom: this.engine.getZoom(),
|
|
1751
|
+
pan: this.engine.getPan()
|
|
1752
|
+
}), this.zoomToolbar.setZoom(this.engine.getZoom()), this.interaction = new F(
|
|
1753
|
+
this.canvas,
|
|
1754
|
+
this.engine,
|
|
1755
|
+
() => this.contentManager.getContents(),
|
|
1756
|
+
() => this.stateManager.getState().selectedAreaId
|
|
1757
|
+
), this.wireInteractionEvents(), this.subscriptions.push(
|
|
1758
|
+
this.contentManager.subscribe(() => {
|
|
1759
|
+
this.syncEngineContents(), this.inputPanel.setContents(this.contentManager.getContents());
|
|
1760
|
+
})
|
|
1761
|
+
), this.keyboard = new X(this), this.setupKeyboardShortcuts(), this.inputPanel.setContents(this.contentManager.getContents());
|
|
1762
|
+
const a = this.getAttribute("store-id");
|
|
1763
|
+
a && this.apiClient.getStorageUsage(a).then((o) => {
|
|
1764
|
+
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");
|
|
1765
|
+
}).catch(() => {
|
|
1766
|
+
});
|
|
1767
|
+
}
|
|
1768
|
+
async loadProduct(e) {
|
|
1769
|
+
try {
|
|
1770
|
+
const t = await this.apiClient.getProduct(e);
|
|
1771
|
+
if (t.views.length === 0)
|
|
1772
|
+
throw new Error("Product has no active templates");
|
|
1773
|
+
this.productViews = t.views, this.perViewContents.clear();
|
|
1774
|
+
const i = t.views.find((n) => n.isDefault) || t.views[0];
|
|
1775
|
+
await this.loadTemplateData(i.template.templateJson), this.activeViewName = i.viewName, this.zoomToolbar && t.views.length > 1 && (this.zoomToolbar.setViews(this.getViews()), this.zoomToolbar.setActiveView(i.viewName));
|
|
1776
|
+
} catch (t) {
|
|
1777
|
+
throw new Error(`Failed to load product: ${t.message}`);
|
|
1778
|
+
}
|
|
1779
|
+
}
|
|
1780
|
+
switchToView(e) {
|
|
1781
|
+
this.activeViewName && this.contentManager && this.perViewContents.set(this.activeViewName, this.contentManager.toJSON()), this.activeViewName = e.viewName, this.zoomToolbar?.setActiveView(e.viewName);
|
|
1782
|
+
const t = e.template.templateJson;
|
|
1783
|
+
this.currentTemplate = t;
|
|
1784
|
+
const i = t.areas || [];
|
|
1785
|
+
this.contentManager = new T(i);
|
|
1786
|
+
const n = this.perViewContents.get(e.viewName);
|
|
1787
|
+
n && this.contentManager.fromJSON(n), this.engine.setArtboard({
|
|
1788
|
+
width: t.artboard.width,
|
|
1789
|
+
height: t.artboard.height,
|
|
1790
|
+
backgroundColor: t.artboard.backgroundColor ?? "#ffffff"
|
|
1791
|
+
}), this.engine.setAreas(i), this.engine.setContents(this.contentManager.getContents()), this.engine.setBackgroundImage(t.backgroundImage ?? void 0);
|
|
1792
|
+
const a = t.artboard.safeArea ?? t.safeArea;
|
|
1793
|
+
this.engine.setSafeArea(a ?? null), this.engine.fitToView(), this.stateManager.setState({
|
|
1794
|
+
template: t,
|
|
1795
|
+
contents: this.contentManager.toJSON(),
|
|
1796
|
+
selectedAreaId: null,
|
|
1797
|
+
isDirty: this.stateManager.getState().isDirty,
|
|
1798
|
+
zoom: this.engine.getZoom(),
|
|
1799
|
+
pan: this.engine.getPan(),
|
|
1800
|
+
warnings: []
|
|
1801
|
+
}), this.zoomToolbar.setZoom(this.engine.getZoom()), this.inputPanel.setAreas(i), this.inputPanel.setContents(this.contentManager.getContents()), this.inputPanel.setSelectedArea(null), this.engine.setSelectedArea(null), this.dispatchEvent(
|
|
1802
|
+
new CustomEvent("view-change", {
|
|
1803
|
+
detail: { viewName: e.viewName },
|
|
1804
|
+
bubbles: !0,
|
|
1805
|
+
composed: !0
|
|
1806
|
+
})
|
|
1807
|
+
);
|
|
1808
|
+
}
|
|
1696
1809
|
buildUI(e, t) {
|
|
1697
1810
|
this.shadow.innerHTML = "";
|
|
1698
1811
|
const i = document.createElement("style");
|
|
1699
|
-
i.textContent =
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1812
|
+
i.textContent = z, this.shadow.appendChild(i), this.container = h("div", { class: "editor-container" }), this.zoomToolbar = new B(), 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 }) => {
|
|
1813
|
+
const s = this.productViews.find((r) => r.viewName === o);
|
|
1814
|
+
s && this.switchToView(s);
|
|
1815
|
+
})), this.subscriptions.push(this.zoomToolbar.on("close", () => {
|
|
1816
|
+
this.dispatchEvent(new CustomEvent("customizer:close", {
|
|
1817
|
+
bubbles: !0,
|
|
1818
|
+
composed: !0
|
|
1819
|
+
}));
|
|
1820
|
+
})), this.subscriptions.push(this.zoomToolbar.on("save", async () => {
|
|
1821
|
+
this.zoomToolbar.setSaveDisabled(!0, "Saving...");
|
|
1822
|
+
try {
|
|
1823
|
+
await this.finalize(), this.zoomToolbar.setSaveDisabled(!0, "Saved!"), this.showToast("Design saved successfully!", "info"), setTimeout(() => {
|
|
1824
|
+
this.zoomToolbar.setSaveDisabled(!1, "Save Customization"), this.zoomToolbar.showSaveButton(!1), this.stateManager.update({ isDirty: !1 });
|
|
1825
|
+
}, 2e3);
|
|
1826
|
+
} catch (o) {
|
|
1827
|
+
console.error("Customizer: finalize failed", o);
|
|
1828
|
+
const s = o instanceof Error ? o.message : "Save failed. Please try again.";
|
|
1829
|
+
this.showToast(s, "error"), this.zoomToolbar.setSaveDisabled(!1, "Save Customization");
|
|
1830
|
+
}
|
|
1831
|
+
}));
|
|
1832
|
+
const n = h("div", { class: "canvas-area" });
|
|
1833
|
+
n.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), n.appendChild(this.canvasWrapper), this.inputPanel = new j(t), this.wireInputPanelEvents();
|
|
1834
|
+
const a = h("div", { class: "editor-main" });
|
|
1835
|
+
a.appendChild(n), a.appendChild(this.inputPanel.getBackdrop()), a.appendChild(this.inputPanel.getElement()), this.container.appendChild(a), this.shadow.appendChild(this.container), this.resizeObserver = new ResizeObserver((o) => {
|
|
1836
|
+
for (const s of o)
|
|
1837
|
+
s.target === this.canvasWrapper && this.handleCanvasResize(s.contentRect), s.target === this.container && this.handleLayoutResize(s.contentRect);
|
|
1706
1838
|
}), this.resizeObserver.observe(this.canvasWrapper), this.resizeObserver.observe(this.container);
|
|
1707
1839
|
}
|
|
1708
1840
|
// ─── Event Wiring ───
|
|
@@ -1751,10 +1883,10 @@ class he extends HTMLElement {
|
|
|
1751
1883
|
), this.subscriptions.push(
|
|
1752
1884
|
this.inputPanel.on("image:change", async ({ areaId: e, dataUrl: t, filename: i }) => {
|
|
1753
1885
|
const n = this.contentManager.getContents();
|
|
1754
|
-
let
|
|
1755
|
-
for (const [
|
|
1756
|
-
|
|
1757
|
-
if (
|
|
1886
|
+
let a = 0;
|
|
1887
|
+
for (const [s, r] of n)
|
|
1888
|
+
r.type === "image" && s !== e && a++;
|
|
1889
|
+
if (a >= 10) {
|
|
1758
1890
|
this.showToast("Maximum of 10 images per design reached", "error");
|
|
1759
1891
|
return;
|
|
1760
1892
|
}
|
|
@@ -1762,28 +1894,28 @@ class he extends HTMLElement {
|
|
|
1762
1894
|
this.showToast("Storage quota exceeded. Upgrade your plan for more storage.", "error");
|
|
1763
1895
|
return;
|
|
1764
1896
|
}
|
|
1765
|
-
const
|
|
1766
|
-
if (
|
|
1767
|
-
const
|
|
1897
|
+
const o = this.currentTemplate?.areas.find((s) => s.id === e);
|
|
1898
|
+
if (o && this.currentTemplate) {
|
|
1899
|
+
const s = this.currentTemplate.print.targetDpi;
|
|
1768
1900
|
try {
|
|
1769
|
-
const
|
|
1770
|
-
|
|
1771
|
-
}), l =
|
|
1901
|
+
const r = new Image(), d = await new Promise((p, g) => {
|
|
1902
|
+
r.onload = () => p({ width: r.naturalWidth, height: r.naturalHeight }), r.onerror = () => g(new Error("Failed to load image")), r.src = t;
|
|
1903
|
+
}), l = o.location.width / s, c = o.location.height / s, u = K(
|
|
1772
1904
|
d.width,
|
|
1773
1905
|
d.height,
|
|
1774
1906
|
l,
|
|
1775
|
-
|
|
1776
|
-
|
|
1907
|
+
c,
|
|
1908
|
+
s
|
|
1777
1909
|
);
|
|
1778
1910
|
if (u.actualDPI < 150) {
|
|
1779
1911
|
if (!await this.showConfirmation(
|
|
1780
1912
|
"Low resolution detected",
|
|
1781
|
-
`This image is ${u.actualDPI} DPI at print size. Print may appear blurry. Recommended: ${
|
|
1913
|
+
`This image is ${u.actualDPI} DPI at print size. Print may appear blurry. Recommended: ${s} DPI.`,
|
|
1782
1914
|
"Cancel",
|
|
1783
1915
|
"Use Anyway"
|
|
1784
1916
|
)) return;
|
|
1785
|
-
} else u.actualDPI <
|
|
1786
|
-
`Image resolution is ${u.actualDPI} DPI (${
|
|
1917
|
+
} else u.actualDPI < s && (this.showToast(
|
|
1918
|
+
`Image resolution is ${u.actualDPI} DPI (${s} recommended)`,
|
|
1787
1919
|
"warning"
|
|
1788
1920
|
), this.stateManager.update({
|
|
1789
1921
|
warnings: [
|
|
@@ -1795,8 +1927,8 @@ class he extends HTMLElement {
|
|
|
1795
1927
|
}
|
|
1796
1928
|
}
|
|
1797
1929
|
if (this.contentManager.setImageContent(e, t, i), this.saveContentState(), this.storageUsage) {
|
|
1798
|
-
const
|
|
1799
|
-
this.storageUsage.storageUsed +=
|
|
1930
|
+
const s = Math.round(t.length * 0.75);
|
|
1931
|
+
this.storageUsage.storageUsed += s, this.storageUsage.usagePercent = this.storageUsage.storageQuota > 0 ? Math.round(this.storageUsage.storageUsed / this.storageUsage.storageQuota * 100) : 0;
|
|
1800
1932
|
}
|
|
1801
1933
|
})
|
|
1802
1934
|
), this.subscriptions.push(
|
|
@@ -1860,8 +1992,8 @@ class he extends HTMLElement {
|
|
|
1860
1992
|
this.canvas.width = t * n, this.canvas.height = i * n, 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()));
|
|
1861
1993
|
}
|
|
1862
1994
|
handleLayoutResize(e) {
|
|
1863
|
-
const t = e.width <
|
|
1864
|
-
|
|
1995
|
+
const t = e.width < _;
|
|
1996
|
+
this.container.classList.toggle("layout-desktop", !t), this.container.classList.toggle("layout-mobile", t), t !== this.isMobileLayout && (this.isMobileLayout = t, this.inputPanel.setMobile(t));
|
|
1865
1997
|
}
|
|
1866
1998
|
// ─── State Management ───
|
|
1867
1999
|
async refreshStorageUsage() {
|
|
@@ -1877,7 +2009,7 @@ class he extends HTMLElement {
|
|
|
1877
2009
|
this.stateManager.setState({
|
|
1878
2010
|
contents: this.contentManager.toJSON(),
|
|
1879
2011
|
isDirty: !0
|
|
1880
|
-
}), this.engine.checkSafeAreaViolations(), this.emitChange();
|
|
2012
|
+
}), this.zoomToolbar?.showSaveButton(!0), this.engine.checkSafeAreaViolations(), this.emitChange();
|
|
1881
2013
|
}
|
|
1882
2014
|
syncEngineContents() {
|
|
1883
2015
|
this.engine?.setContents(this.contentManager.getContents());
|
|
@@ -1888,7 +2020,7 @@ class he extends HTMLElement {
|
|
|
1888
2020
|
}
|
|
1889
2021
|
// ─── Keyboard ───
|
|
1890
2022
|
setupKeyboardShortcuts() {
|
|
1891
|
-
const e =
|
|
2023
|
+
const e = J();
|
|
1892
2024
|
this.keyboard.register({
|
|
1893
2025
|
key: "z",
|
|
1894
2026
|
[e]: !0,
|
|
@@ -1905,32 +2037,32 @@ class he extends HTMLElement {
|
|
|
1905
2037
|
}
|
|
1906
2038
|
// ─── Toast & Modal ───
|
|
1907
2039
|
showToast(e, t = "info") {
|
|
1908
|
-
const i =
|
|
2040
|
+
const i = h("div", { class: `editor-toast editor-toast-${t}` }, e);
|
|
1909
2041
|
this.shadow.appendChild(i), setTimeout(() => i.remove(), 4e3);
|
|
1910
2042
|
}
|
|
1911
2043
|
showConfirmation(e, t, i = "Cancel", n = "Continue") {
|
|
1912
|
-
return new Promise((
|
|
1913
|
-
const
|
|
1914
|
-
|
|
1915
|
-
const
|
|
2044
|
+
return new Promise((a) => {
|
|
2045
|
+
const o = h("div", { class: "editor-modal-overlay" }), s = h("div", { class: "editor-modal" });
|
|
2046
|
+
s.appendChild(h("div", { class: "editor-modal-title" }, e)), s.appendChild(h("div", { class: "editor-modal-body" }, t));
|
|
2047
|
+
const r = h("div", { class: "editor-modal-actions" }), d = h("button", { class: "editor-modal-btn" }, i);
|
|
1916
2048
|
d.addEventListener("click", () => {
|
|
1917
|
-
|
|
2049
|
+
o.remove(), a(!1);
|
|
1918
2050
|
});
|
|
1919
|
-
const l =
|
|
2051
|
+
const l = h("button", { class: "editor-modal-btn editor-modal-btn-primary" }, n);
|
|
1920
2052
|
l.addEventListener("click", () => {
|
|
1921
|
-
|
|
1922
|
-
}),
|
|
1923
|
-
|
|
1924
|
-
}), this.shadow.appendChild(
|
|
2053
|
+
o.remove(), a(!0);
|
|
2054
|
+
}), r.appendChild(d), r.appendChild(l), s.appendChild(r), o.appendChild(s), o.addEventListener("click", (c) => {
|
|
2055
|
+
c.target === o && (o.remove(), a(!1));
|
|
2056
|
+
}), this.shadow.appendChild(o);
|
|
1925
2057
|
});
|
|
1926
2058
|
}
|
|
1927
2059
|
// ─── Error / Cleanup ───
|
|
1928
2060
|
showError(e, t) {
|
|
1929
2061
|
this.shadow.innerHTML = "";
|
|
1930
2062
|
const i = document.createElement("style");
|
|
1931
|
-
i.textContent =
|
|
1932
|
-
const n =
|
|
1933
|
-
n.appendChild(
|
|
2063
|
+
i.textContent = z, this.shadow.appendChild(i);
|
|
2064
|
+
const n = h("div", { class: "error-container" });
|
|
2065
|
+
n.appendChild(h("div", { class: "error-title" }, e)), n.appendChild(h("div", { class: "error-message" }, t?.message ?? "")), this.shadow.appendChild(n), this.dispatchEvent(
|
|
1934
2066
|
new CustomEvent("error", {
|
|
1935
2067
|
detail: { message: e, error: t },
|
|
1936
2068
|
bubbles: !0,
|
|
@@ -1941,32 +2073,36 @@ class he extends HTMLElement {
|
|
|
1941
2073
|
cleanup() {
|
|
1942
2074
|
for (const e of this.subscriptions)
|
|
1943
2075
|
e();
|
|
1944
|
-
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;
|
|
2076
|
+
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();
|
|
1945
2077
|
}
|
|
1946
2078
|
}
|
|
1947
|
-
customElements.get("customizer-editor") || customElements.define("customizer-editor",
|
|
1948
|
-
function
|
|
1949
|
-
const t = typeof
|
|
2079
|
+
customElements.get("customizer-editor") || customElements.define("customizer-editor", ee);
|
|
2080
|
+
function te(f, e) {
|
|
2081
|
+
const t = typeof f == "string" ? document.querySelector(f) : f;
|
|
1950
2082
|
if (!t)
|
|
1951
2083
|
throw new Error(
|
|
1952
|
-
`Container not found: ${typeof
|
|
2084
|
+
`Container not found: ${typeof f == "string" ? f : "provided element is null"}`
|
|
1953
2085
|
);
|
|
2086
|
+
if (!e.templateId && !e.productId)
|
|
2087
|
+
throw new Error("Either templateId or productId must be provided");
|
|
2088
|
+
if (e.templateId && e.productId)
|
|
2089
|
+
throw new Error("Only one of templateId or productId should be provided, not both");
|
|
1954
2090
|
const i = document.createElement("customizer-editor");
|
|
1955
|
-
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), i.style.width = "100%", i.style.height = "100%";
|
|
1956
|
-
const n = [],
|
|
1957
|
-
i.addEventListener(
|
|
2091
|
+
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), i.style.width = "100%", i.style.height = "100%";
|
|
2092
|
+
const n = [], a = (s, r) => {
|
|
2093
|
+
i.addEventListener(s, r), n.push({ event: s, handler: r });
|
|
1958
2094
|
};
|
|
1959
2095
|
t.innerHTML = "", t.appendChild(i);
|
|
1960
|
-
const
|
|
2096
|
+
const o = {
|
|
1961
2097
|
getDesign() {
|
|
1962
2098
|
if (typeof i.getDesign != "function")
|
|
1963
2099
|
throw new Error("Editor not ready: getDesign method not available");
|
|
1964
2100
|
return i.getDesign();
|
|
1965
2101
|
},
|
|
1966
|
-
setDesign(
|
|
2102
|
+
setDesign(s) {
|
|
1967
2103
|
if (typeof i.setDesign != "function")
|
|
1968
2104
|
throw new Error("Editor not ready: setDesign method not available");
|
|
1969
|
-
i.setDesign(
|
|
2105
|
+
i.setDesign(s);
|
|
1970
2106
|
},
|
|
1971
2107
|
undo() {
|
|
1972
2108
|
if (typeof i.undo != "function")
|
|
@@ -1985,49 +2121,49 @@ function ce(b, e) {
|
|
|
1985
2121
|
return typeof i.canRedo != "function" ? !1 : i.canRedo();
|
|
1986
2122
|
},
|
|
1987
2123
|
async finalize() {
|
|
1988
|
-
const
|
|
2124
|
+
const s = this.getDesign(), r = e.apiUrl || "https://api.varianta.io";
|
|
1989
2125
|
try {
|
|
1990
|
-
const d = await fetch(`${
|
|
2126
|
+
const d = await fetch(`${r}/public/finalize`, {
|
|
1991
2127
|
method: "POST",
|
|
1992
2128
|
headers: {
|
|
1993
2129
|
"Content-Type": "application/json"
|
|
1994
2130
|
},
|
|
1995
2131
|
body: JSON.stringify({
|
|
1996
|
-
templateId: e.templateId,
|
|
1997
|
-
designJson:
|
|
2132
|
+
templateId: s.templateId || e.templateId,
|
|
2133
|
+
designJson: s
|
|
1998
2134
|
})
|
|
1999
2135
|
});
|
|
2000
2136
|
if (!d.ok) {
|
|
2001
|
-
const
|
|
2137
|
+
const m = await d.json().catch(() => ({
|
|
2002
2138
|
message: "Finalization failed"
|
|
2003
2139
|
}));
|
|
2004
|
-
throw new Error(
|
|
2140
|
+
throw new Error(m.message || "Finalization failed");
|
|
2005
2141
|
}
|
|
2006
2142
|
const l = await d.json();
|
|
2007
|
-
let
|
|
2143
|
+
let c = {
|
|
2008
2144
|
designId: l.designId,
|
|
2009
2145
|
status: l.status,
|
|
2010
2146
|
proofUrl: l.proofUrl ?? null,
|
|
2011
2147
|
errorMessage: l.errorMessage ?? null
|
|
2012
2148
|
};
|
|
2013
|
-
const u = 1500,
|
|
2014
|
-
let
|
|
2015
|
-
for (;
|
|
2016
|
-
await new Promise((
|
|
2017
|
-
const
|
|
2018
|
-
`${
|
|
2149
|
+
const u = 1500, p = 40;
|
|
2150
|
+
let g = 0;
|
|
2151
|
+
for (; c.status === "processing" && g < p; ) {
|
|
2152
|
+
await new Promise((b) => setTimeout(b, u)), g++;
|
|
2153
|
+
const m = await fetch(
|
|
2154
|
+
`${r}/public/designs/${c.designId}/status`
|
|
2019
2155
|
);
|
|
2020
|
-
if (!
|
|
2156
|
+
if (!m.ok)
|
|
2021
2157
|
throw new Error("Failed to check design status");
|
|
2022
|
-
const
|
|
2023
|
-
|
|
2024
|
-
designId:
|
|
2025
|
-
status:
|
|
2026
|
-
proofUrl:
|
|
2027
|
-
errorMessage:
|
|
2158
|
+
const v = await m.json();
|
|
2159
|
+
c = {
|
|
2160
|
+
designId: v.designId,
|
|
2161
|
+
status: v.status,
|
|
2162
|
+
proofUrl: v.proofUrl ?? null,
|
|
2163
|
+
errorMessage: v.errorMessage ?? null
|
|
2028
2164
|
};
|
|
2029
2165
|
}
|
|
2030
|
-
return
|
|
2166
|
+
return c.status === "processing" && (c = { ...c, status: "failed", errorMessage: "Render timed out" }), e.onFinalize && e.onFinalize(c), c;
|
|
2031
2167
|
} catch (d) {
|
|
2032
2168
|
const l = {
|
|
2033
2169
|
code: "FINALIZE_ERROR",
|
|
@@ -2037,341 +2173,82 @@ function ce(b, e) {
|
|
|
2037
2173
|
throw e.onError && e.onError(l), d;
|
|
2038
2174
|
}
|
|
2039
2175
|
},
|
|
2040
|
-
addTextLayer(
|
|
2176
|
+
addTextLayer(s) {
|
|
2041
2177
|
if (typeof i.addTextLayer != "function")
|
|
2042
2178
|
throw new Error("Editor not ready: addTextLayer method not available");
|
|
2043
|
-
i.addTextLayer(
|
|
2179
|
+
i.addTextLayer(s);
|
|
2044
2180
|
},
|
|
2045
|
-
async addImageLayer(
|
|
2181
|
+
async addImageLayer(s) {
|
|
2046
2182
|
if (typeof i.addImageLayer != "function")
|
|
2047
2183
|
throw new Error("Editor not ready: addImageLayer method not available");
|
|
2048
|
-
return i.addImageLayer(
|
|
2184
|
+
return i.addImageLayer(s);
|
|
2049
2185
|
},
|
|
2050
|
-
removeLayer(
|
|
2186
|
+
removeLayer(s) {
|
|
2051
2187
|
if (typeof i.removeLayer != "function")
|
|
2052
2188
|
throw new Error("Editor not ready: removeLayer method not available");
|
|
2053
|
-
i.removeLayer(
|
|
2189
|
+
i.removeLayer(s);
|
|
2054
2190
|
},
|
|
2055
|
-
selectLayer(
|
|
2191
|
+
selectLayer(s) {
|
|
2056
2192
|
if (typeof i.selectLayer != "function")
|
|
2057
2193
|
throw new Error("Editor not ready: selectLayer method not available");
|
|
2058
|
-
i.selectLayer(
|
|
2194
|
+
i.selectLayer(s);
|
|
2059
2195
|
},
|
|
2060
2196
|
getSelectedLayerId() {
|
|
2061
2197
|
return typeof i.getSelectedLayerId != "function" ? null : i.getSelectedLayerId();
|
|
2062
2198
|
},
|
|
2063
|
-
|
|
2064
|
-
i.
|
|
2199
|
+
getActiveView() {
|
|
2200
|
+
return typeof i.getActiveView != "function" ? null : i.getActiveView();
|
|
2201
|
+
},
|
|
2202
|
+
getViews() {
|
|
2203
|
+
return typeof i.getViews != "function" ? [] : i.getViews();
|
|
2204
|
+
},
|
|
2205
|
+
setActiveView(s) {
|
|
2206
|
+
if (typeof i.setActiveView != "function")
|
|
2207
|
+
throw new Error("Editor not ready: setActiveView method not available");
|
|
2208
|
+
i.setActiveView(s);
|
|
2065
2209
|
},
|
|
2066
|
-
|
|
2067
|
-
i.setAttribute("
|
|
2210
|
+
setTheme(s) {
|
|
2211
|
+
i.setAttribute("theme", s);
|
|
2212
|
+
},
|
|
2213
|
+
setMode(s) {
|
|
2214
|
+
i.setAttribute("mode", s);
|
|
2068
2215
|
},
|
|
2069
2216
|
destroy() {
|
|
2070
|
-
n.forEach(({ event:
|
|
2071
|
-
i.removeEventListener(
|
|
2217
|
+
n.forEach(({ event: s, handler: r }) => {
|
|
2218
|
+
i.removeEventListener(s, r);
|
|
2072
2219
|
}), i.parentNode && i.parentNode.removeChild(i);
|
|
2073
2220
|
},
|
|
2074
2221
|
getElement() {
|
|
2075
2222
|
return i;
|
|
2076
2223
|
}
|
|
2077
2224
|
};
|
|
2078
|
-
return e.onReady &&
|
|
2079
|
-
e.onReady?.(
|
|
2080
|
-
})), e.onChange &&
|
|
2081
|
-
const
|
|
2082
|
-
e.onChange?.(
|
|
2083
|
-
})), e.onLayerSelect &&
|
|
2084
|
-
e.onLayerSelect?.(
|
|
2085
|
-
})), e.onLayerAdd &&
|
|
2086
|
-
e.onLayerAdd?.(
|
|
2087
|
-
})), e.onLayerRemove &&
|
|
2088
|
-
e.onLayerRemove?.(
|
|
2089
|
-
})), e.onLayerUpdate &&
|
|
2090
|
-
e.onLayerUpdate?.(
|
|
2091
|
-
})), e.onError &&
|
|
2092
|
-
const
|
|
2093
|
-
e.onError?.(
|
|
2225
|
+
return e.onReady && a("ready", (() => {
|
|
2226
|
+
e.onReady?.(o);
|
|
2227
|
+
})), e.onChange && a("change", ((s) => {
|
|
2228
|
+
const r = s.detail.design;
|
|
2229
|
+
e.onChange?.(r);
|
|
2230
|
+
})), e.onLayerSelect && a("layer:select", ((s) => {
|
|
2231
|
+
e.onLayerSelect?.(s.detail.layerId);
|
|
2232
|
+
})), e.onLayerAdd && a("layer:add", ((s) => {
|
|
2233
|
+
e.onLayerAdd?.(s.detail.layerId);
|
|
2234
|
+
})), e.onLayerRemove && a("layer:remove", ((s) => {
|
|
2235
|
+
e.onLayerRemove?.(s.detail.layerId);
|
|
2236
|
+
})), e.onLayerUpdate && a("layer:update", ((s) => {
|
|
2237
|
+
e.onLayerUpdate?.(s.detail.layerId);
|
|
2238
|
+
})), e.onError && a("error", ((s) => {
|
|
2239
|
+
const r = s.detail.error;
|
|
2240
|
+
e.onError?.(r);
|
|
2241
|
+
})), e.onViewChange && a("view-change", ((s) => {
|
|
2242
|
+
e.onViewChange?.(s.detail.viewName);
|
|
2094
2243
|
})), e.initialDesign && i.addEventListener(
|
|
2095
2244
|
"ready",
|
|
2096
2245
|
() => {
|
|
2097
2246
|
e.initialDesign && i.setDesign(e.initialDesign);
|
|
2098
2247
|
},
|
|
2099
2248
|
{ once: !0 }
|
|
2100
|
-
), e.debug && (console.log("[Customizer SDK] Initialized with options:", e), console.log("[Customizer SDK] Instance:",
|
|
2101
|
-
}
|
|
2102
|
-
const ue = Z(
|
|
2103
|
-
(b, e) => {
|
|
2104
|
-
const {
|
|
2105
|
-
templateId: t,
|
|
2106
|
-
apiUrl: i,
|
|
2107
|
-
theme: n = "light",
|
|
2108
|
-
mode: r = "edit",
|
|
2109
|
-
debug: s = !1,
|
|
2110
|
-
className: a,
|
|
2111
|
-
style: o,
|
|
2112
|
-
initialDesign: d,
|
|
2113
|
-
onReady: l,
|
|
2114
|
-
onChange: h,
|
|
2115
|
-
onLayerSelect: u,
|
|
2116
|
-
onLayerAdd: g,
|
|
2117
|
-
onLayerRemove: m,
|
|
2118
|
-
onLayerUpdate: f,
|
|
2119
|
-
onError: y,
|
|
2120
|
-
onFinalize: v
|
|
2121
|
-
} = b, w = k(null), p = k(null), C = k({
|
|
2122
|
-
onReady: l,
|
|
2123
|
-
onChange: h,
|
|
2124
|
-
onLayerSelect: u,
|
|
2125
|
-
onLayerAdd: g,
|
|
2126
|
-
onLayerRemove: m,
|
|
2127
|
-
onLayerUpdate: f,
|
|
2128
|
-
onError: y,
|
|
2129
|
-
onFinalize: v
|
|
2130
|
-
});
|
|
2131
|
-
return L(() => {
|
|
2132
|
-
C.current = {
|
|
2133
|
-
onReady: l,
|
|
2134
|
-
onChange: h,
|
|
2135
|
-
onLayerSelect: u,
|
|
2136
|
-
onLayerAdd: g,
|
|
2137
|
-
onLayerRemove: m,
|
|
2138
|
-
onLayerUpdate: f,
|
|
2139
|
-
onError: y,
|
|
2140
|
-
onFinalize: v
|
|
2141
|
-
};
|
|
2142
|
-
}, [l, h, u, g, m, f, y, v]), L(() => {
|
|
2143
|
-
if (!w.current) return;
|
|
2144
|
-
const I = {
|
|
2145
|
-
templateId: t,
|
|
2146
|
-
apiUrl: i,
|
|
2147
|
-
theme: n,
|
|
2148
|
-
mode: r,
|
|
2149
|
-
debug: s,
|
|
2150
|
-
initialDesign: d,
|
|
2151
|
-
// Use callback refs to always get latest callbacks
|
|
2152
|
-
onReady: () => C.current.onReady?.(),
|
|
2153
|
-
onChange: (x) => C.current.onChange?.(x),
|
|
2154
|
-
onLayerSelect: (x) => C.current.onLayerSelect?.(x),
|
|
2155
|
-
onLayerAdd: (x) => C.current.onLayerAdd?.(x),
|
|
2156
|
-
onLayerRemove: (x) => C.current.onLayerRemove?.(x),
|
|
2157
|
-
onLayerUpdate: (x) => C.current.onLayerUpdate?.(x),
|
|
2158
|
-
onError: (x) => C.current.onError?.(x),
|
|
2159
|
-
onFinalize: (x) => C.current.onFinalize?.(x)
|
|
2160
|
-
};
|
|
2161
|
-
try {
|
|
2162
|
-
const x = ce(w.current, I);
|
|
2163
|
-
p.current = x;
|
|
2164
|
-
} catch (x) {
|
|
2165
|
-
console.error("[Customizer React] Failed to initialize:", x), C.current.onError?.({
|
|
2166
|
-
code: "INIT_ERROR",
|
|
2167
|
-
message: x instanceof Error ? x.message : "Unknown error",
|
|
2168
|
-
details: x
|
|
2169
|
-
});
|
|
2170
|
-
}
|
|
2171
|
-
return () => {
|
|
2172
|
-
p.current && (p.current.destroy(), p.current = null);
|
|
2173
|
-
};
|
|
2174
|
-
}, [t, i, s, d]), L(() => {
|
|
2175
|
-
p.current && p.current.setTheme(n);
|
|
2176
|
-
}, [n]), L(() => {
|
|
2177
|
-
p.current && p.current.setMode(r);
|
|
2178
|
-
}, [r]), q(
|
|
2179
|
-
e,
|
|
2180
|
-
() => ({
|
|
2181
|
-
getDesign: () => {
|
|
2182
|
-
if (!p.current)
|
|
2183
|
-
throw new Error("Editor not initialized");
|
|
2184
|
-
return p.current.getDesign();
|
|
2185
|
-
},
|
|
2186
|
-
setDesign: (I) => {
|
|
2187
|
-
if (!p.current)
|
|
2188
|
-
throw new Error("Editor not initialized");
|
|
2189
|
-
p.current.setDesign(I);
|
|
2190
|
-
},
|
|
2191
|
-
undo: () => {
|
|
2192
|
-
if (!p.current)
|
|
2193
|
-
throw new Error("Editor not initialized");
|
|
2194
|
-
p.current.undo();
|
|
2195
|
-
},
|
|
2196
|
-
redo: () => {
|
|
2197
|
-
if (!p.current)
|
|
2198
|
-
throw new Error("Editor not initialized");
|
|
2199
|
-
p.current.redo();
|
|
2200
|
-
},
|
|
2201
|
-
canUndo: () => p.current ? p.current.canUndo() : !1,
|
|
2202
|
-
canRedo: () => p.current ? p.current.canRedo() : !1,
|
|
2203
|
-
finalize: async () => {
|
|
2204
|
-
if (!p.current)
|
|
2205
|
-
throw new Error("Editor not initialized");
|
|
2206
|
-
return p.current.finalize();
|
|
2207
|
-
},
|
|
2208
|
-
addTextLayer: (I) => {
|
|
2209
|
-
if (!p.current)
|
|
2210
|
-
throw new Error("Editor not initialized");
|
|
2211
|
-
p.current.addTextLayer(I);
|
|
2212
|
-
},
|
|
2213
|
-
addImageLayer: async (I) => {
|
|
2214
|
-
if (!p.current)
|
|
2215
|
-
throw new Error("Editor not initialized");
|
|
2216
|
-
return p.current.addImageLayer(I);
|
|
2217
|
-
},
|
|
2218
|
-
removeLayer: (I) => {
|
|
2219
|
-
if (!p.current)
|
|
2220
|
-
throw new Error("Editor not initialized");
|
|
2221
|
-
p.current.removeLayer(I);
|
|
2222
|
-
},
|
|
2223
|
-
selectLayer: (I) => {
|
|
2224
|
-
if (!p.current)
|
|
2225
|
-
throw new Error("Editor not initialized");
|
|
2226
|
-
p.current.selectLayer(I);
|
|
2227
|
-
},
|
|
2228
|
-
getSelectedLayerId: () => p.current ? p.current.getSelectedLayerId() : null,
|
|
2229
|
-
setTheme: (I) => {
|
|
2230
|
-
if (!p.current)
|
|
2231
|
-
throw new Error("Editor not initialized");
|
|
2232
|
-
p.current.setTheme(I);
|
|
2233
|
-
},
|
|
2234
|
-
setMode: (I) => {
|
|
2235
|
-
if (!p.current)
|
|
2236
|
-
throw new Error("Editor not initialized");
|
|
2237
|
-
p.current.setMode(I);
|
|
2238
|
-
},
|
|
2239
|
-
destroy: () => {
|
|
2240
|
-
p.current && (p.current.destroy(), p.current = null);
|
|
2241
|
-
},
|
|
2242
|
-
getElement: () => {
|
|
2243
|
-
if (!p.current)
|
|
2244
|
-
throw new Error("Editor not initialized");
|
|
2245
|
-
return p.current.getElement();
|
|
2246
|
-
}
|
|
2247
|
-
}),
|
|
2248
|
-
[]
|
|
2249
|
-
// Empty deps is OK - we always read from instanceRef.current
|
|
2250
|
-
), /* @__PURE__ */ $(
|
|
2251
|
-
"div",
|
|
2252
|
-
{
|
|
2253
|
-
ref: w,
|
|
2254
|
-
className: a,
|
|
2255
|
-
style: {
|
|
2256
|
-
width: "100%",
|
|
2257
|
-
height: "100%",
|
|
2258
|
-
...o
|
|
2259
|
-
}
|
|
2260
|
-
}
|
|
2261
|
-
);
|
|
2262
|
-
}
|
|
2263
|
-
);
|
|
2264
|
-
ue.displayName = "Customizer";
|
|
2265
|
-
function me(b = {}) {
|
|
2266
|
-
const {
|
|
2267
|
-
autoSave: e = !1,
|
|
2268
|
-
autoSaveKey: t = "customizer-design",
|
|
2269
|
-
autoSaveDebounce: i = 1e3
|
|
2270
|
-
} = b, n = k(null), [r, s] = S(null), [a, o] = S(!1), [d, l] = S(!1), [h, u] = S(null), [g, m] = S(!1), [f, y] = S(
|
|
2271
|
-
null
|
|
2272
|
-
), v = k(void 0);
|
|
2273
|
-
L(() => {
|
|
2274
|
-
if (!(!e || !r))
|
|
2275
|
-
return v.current && clearTimeout(v.current), v.current = setTimeout(() => {
|
|
2276
|
-
try {
|
|
2277
|
-
localStorage.setItem(t, JSON.stringify(r));
|
|
2278
|
-
} catch (E) {
|
|
2279
|
-
console.error("[useCustomizer] Auto-save failed:", E);
|
|
2280
|
-
}
|
|
2281
|
-
}, i), () => {
|
|
2282
|
-
v.current && clearTimeout(v.current);
|
|
2283
|
-
};
|
|
2284
|
-
}, [r, e, t, i]);
|
|
2285
|
-
const w = M(() => {
|
|
2286
|
-
if (!n.current) return null;
|
|
2287
|
-
try {
|
|
2288
|
-
return n.current.getDesign();
|
|
2289
|
-
} catch (E) {
|
|
2290
|
-
return console.error("[useCustomizer] getDesign failed:", E), null;
|
|
2291
|
-
}
|
|
2292
|
-
}, []), p = M((E) => {
|
|
2293
|
-
if (n.current)
|
|
2294
|
-
try {
|
|
2295
|
-
n.current.setDesign(E), s(E);
|
|
2296
|
-
} catch (z) {
|
|
2297
|
-
console.error("[useCustomizer] setDesign failed:", z);
|
|
2298
|
-
}
|
|
2299
|
-
}, []), C = M(() => {
|
|
2300
|
-
if (n.current)
|
|
2301
|
-
try {
|
|
2302
|
-
n.current.undo(), o(n.current.canUndo()), l(n.current.canRedo());
|
|
2303
|
-
} catch (E) {
|
|
2304
|
-
console.error("[useCustomizer] undo failed:", E);
|
|
2305
|
-
}
|
|
2306
|
-
}, []), I = M(() => {
|
|
2307
|
-
if (n.current)
|
|
2308
|
-
try {
|
|
2309
|
-
n.current.redo(), o(n.current.canUndo()), l(n.current.canRedo());
|
|
2310
|
-
} catch (E) {
|
|
2311
|
-
console.error("[useCustomizer] redo failed:", E);
|
|
2312
|
-
}
|
|
2313
|
-
}, []), x = M(async () => {
|
|
2314
|
-
if (!n.current || g) return null;
|
|
2315
|
-
m(!0);
|
|
2316
|
-
try {
|
|
2317
|
-
const E = await n.current.finalize();
|
|
2318
|
-
return y(E), E;
|
|
2319
|
-
} catch (E) {
|
|
2320
|
-
return console.error("[useCustomizer] finalize failed:", E), null;
|
|
2321
|
-
} finally {
|
|
2322
|
-
m(!1);
|
|
2323
|
-
}
|
|
2324
|
-
}, [g]), R = M((E) => {
|
|
2325
|
-
if (n.current)
|
|
2326
|
-
try {
|
|
2327
|
-
n.current.addTextLayer(E);
|
|
2328
|
-
} catch (z) {
|
|
2329
|
-
console.error("[useCustomizer] addTextLayer failed:", z);
|
|
2330
|
-
}
|
|
2331
|
-
}, []), D = M(async (E) => {
|
|
2332
|
-
if (n.current)
|
|
2333
|
-
try {
|
|
2334
|
-
await n.current.addImageLayer(E);
|
|
2335
|
-
} catch (z) {
|
|
2336
|
-
console.error("[useCustomizer] addImageLayer failed:", z);
|
|
2337
|
-
}
|
|
2338
|
-
}, []), U = M((E) => {
|
|
2339
|
-
if (n.current)
|
|
2340
|
-
try {
|
|
2341
|
-
n.current.removeLayer(E);
|
|
2342
|
-
} catch (z) {
|
|
2343
|
-
console.error("[useCustomizer] removeLayer failed:", z);
|
|
2344
|
-
}
|
|
2345
|
-
}, []), N = M((E) => {
|
|
2346
|
-
if (n.current)
|
|
2347
|
-
try {
|
|
2348
|
-
n.current.selectLayer(E), u(E);
|
|
2349
|
-
} catch (z) {
|
|
2350
|
-
console.error("[useCustomizer] selectLayer failed:", z);
|
|
2351
|
-
}
|
|
2352
|
-
}, []);
|
|
2353
|
-
return {
|
|
2354
|
-
customizerRef: n,
|
|
2355
|
-
design: r,
|
|
2356
|
-
canUndo: a,
|
|
2357
|
-
canRedo: d,
|
|
2358
|
-
selectedLayerId: h,
|
|
2359
|
-
isFinalizing: g,
|
|
2360
|
-
finalizeResult: f,
|
|
2361
|
-
getDesign: w,
|
|
2362
|
-
setDesign: p,
|
|
2363
|
-
undo: C,
|
|
2364
|
-
redo: I,
|
|
2365
|
-
finalize: x,
|
|
2366
|
-
addTextLayer: R,
|
|
2367
|
-
addImageLayer: D,
|
|
2368
|
-
removeLayer: U,
|
|
2369
|
-
selectLayer: N
|
|
2370
|
-
};
|
|
2249
|
+
), e.debug && (console.log("[Customizer SDK] Initialized with options:", e), console.log("[Customizer SDK] Instance:", o)), o;
|
|
2371
2250
|
}
|
|
2372
2251
|
export {
|
|
2373
|
-
|
|
2374
|
-
ce as initCustomizer,
|
|
2375
|
-
me as useCustomizer
|
|
2252
|
+
te as initCustomizer
|
|
2376
2253
|
};
|
|
2377
2254
|
//# sourceMappingURL=index.esm.js.map
|