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