@varianta/sdk 0.1.7 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs.js +6 -6
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.esm.js +1053 -902
- 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 +204 -185
- package/dist/react.esm.js.map +1 -1
- package/dist/types/react/Customizer.d.ts.map +1 -1
- package/dist/types/react/useCustomizer.d.ts +19 -3
- package/dist/types/react/useCustomizer.d.ts.map +1 -1
- package/dist/types/types/index.d.ts +33 -1
- package/dist/types/types/index.d.ts.map +1 -1
- package/dist/types/vanilla/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 N {
|
|
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 = d, this.pan = { x:
|
|
153
|
+
const a = n / t, r = o / i, d = Math.min(a, r), l = this.canvas.width / s, c = this.canvas.height / s, p = (l / d - t) / 2, u = (c / d - i) / 2;
|
|
154
|
+
this.zoom = d, this.pan = { x: p, y: u }, this.requestRender();
|
|
155
155
|
}
|
|
156
156
|
/**
|
|
157
157
|
* Set the selected area
|
|
@@ -180,46 +180,46 @@ 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((d) => d.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 d = i.offset || { x: 0, y: 0 }, l = i.scale ?? 1, c = i.rotation ?? 0, p = a * l, u = r * l, g = n + a / 2 + d.x, m = o + r / 2 + d.y;
|
|
200
200
|
return {
|
|
201
|
-
x:
|
|
202
|
-
y:
|
|
203
|
-
width:
|
|
204
|
-
height:
|
|
205
|
-
rotation:
|
|
206
|
-
centerX:
|
|
207
|
-
centerY:
|
|
201
|
+
x: g - p / 2,
|
|
202
|
+
y: m - u / 2,
|
|
203
|
+
width: p,
|
|
204
|
+
height: u,
|
|
205
|
+
rotation: c,
|
|
206
|
+
centerX: g,
|
|
207
|
+
centerY: m
|
|
208
208
|
};
|
|
209
|
-
} else if (
|
|
209
|
+
} else if (i.type === "image") {
|
|
210
210
|
const d = this.loadedImages.get(e);
|
|
211
211
|
if (!d)
|
|
212
212
|
return null;
|
|
213
|
-
const l =
|
|
214
|
-
let
|
|
215
|
-
|
|
216
|
-
const b =
|
|
213
|
+
const l = i.offset || { x: 0, y: 0 }, c = i.scale ?? 1, p = i.rotation ?? 0, u = d.naturalWidth / d.naturalHeight, g = a / r;
|
|
214
|
+
let m, v;
|
|
215
|
+
u > g ? (m = a, v = a / u) : (v = r, m = r * u);
|
|
216
|
+
const b = m * c, w = v * c, y = n + a / 2 + l.x, x = o + r / 2 + l.y;
|
|
217
217
|
return {
|
|
218
218
|
x: y - b / 2,
|
|
219
219
|
y: x - w / 2,
|
|
220
220
|
width: b,
|
|
221
221
|
height: w,
|
|
222
|
-
rotation:
|
|
222
|
+
rotation: p,
|
|
223
223
|
centerX: y,
|
|
224
224
|
centerY: x
|
|
225
225
|
};
|
|
@@ -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
|
|
260
|
+
isPointInBounds(e, t, i) {
|
|
261
|
+
const { centerX: s, centerY: n, width: o, height: a, rotation: r } = i, d = -r * Math.PI / 180, l = Math.cos(d), c = Math.sin(d), p = e - s, u = t - n, g = p * l - u * c, m = p * c + u * l;
|
|
262
|
+
return g >= -o / 2 && g <= o / 2 && m >= -a / 2 && m <= a / 2;
|
|
263
263
|
}
|
|
264
264
|
/**
|
|
265
265
|
* Get handle positions for the selected content
|
|
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 *
|
|
280
|
-
}),
|
|
281
|
-
if (
|
|
282
|
-
const b =
|
|
283
|
-
|
|
284
|
-
const x =
|
|
285
|
-
|
|
286
|
-
const C =
|
|
287
|
-
|
|
288
|
-
const M =
|
|
289
|
-
|
|
277
|
+
const { centerX: n, centerY: o, width: a, height: r, rotation: d } = s, l = 6 / this.zoom, c = 30 / this.zoom, p = d * Math.PI / 180, u = Math.cos(p), g = Math.sin(p), m = (b, w) => ({
|
|
278
|
+
x: n + b * u - w * g,
|
|
279
|
+
y: o + b * g + w * u
|
|
280
|
+
}), v = [];
|
|
281
|
+
if (i.type === "image" && t.imageOptions?.allowScaling || i.type === "text" && t.textOptions?.allowScaling) {
|
|
282
|
+
const b = a / 2, w = r / 2, y = m(-b, -w);
|
|
283
|
+
v.push({ type: "nw", ...y, radius: l });
|
|
284
|
+
const x = m(b, -w);
|
|
285
|
+
v.push({ type: "ne", ...x, radius: l });
|
|
286
|
+
const C = m(b, w);
|
|
287
|
+
v.push({ type: "se", ...C, radius: l });
|
|
288
|
+
const M = m(-b, w);
|
|
289
|
+
v.push({ type: "sw", ...M, radius: l });
|
|
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 = m(0, -r / 2 - c);
|
|
293
|
+
v.push({ type: "rotate", ...b, radius: l });
|
|
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:
|
|
347
|
+
const e = [], { top: t, right: i, bottom: s, left: n } = this.safeArea, o = n, a = t, r = this.artboard.width - i, d = this.artboard.height - s;
|
|
348
348
|
for (const l of this.areas) {
|
|
349
349
|
if (!this.contents.get(l.id))
|
|
350
350
|
continue;
|
|
351
|
-
const
|
|
352
|
-
if (!
|
|
351
|
+
const c = this.getContentBounds(l.id);
|
|
352
|
+
if (!c)
|
|
353
353
|
continue;
|
|
354
|
-
const { centerX:
|
|
355
|
-
(M < o || R <
|
|
354
|
+
const { centerX: p, centerY: u, width: g, height: m, rotation: v } = c, b = v * Math.PI / 180, w = Math.abs(Math.cos(b)), y = Math.abs(Math.sin(b)), x = g * w + m * y, C = g * y + m * w, M = p - x / 2, R = u - C / 2, U = p + x / 2, D = u + C / 2;
|
|
355
|
+
(M < o || R < a || U > r || D > d) && e.push(l.id);
|
|
356
356
|
}
|
|
357
357
|
return this.safeAreaViolations = new Set(e), this.requestRender(), e;
|
|
358
358
|
}
|
|
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((u) => u.id === a);
|
|
436
436
|
if (!r)
|
|
437
437
|
continue;
|
|
438
|
-
const { x: d, y: l, width:
|
|
439
|
-
e.save(), e.fillStyle = "rgba(239, 68, 68, 0.15)", e.fillRect(d, l,
|
|
438
|
+
const { x: d, y: l, width: c, height: p } = r.location;
|
|
439
|
+
e.save(), e.fillStyle = "rgba(239, 68, 68, 0.15)", e.fillRect(d, l, c, p), e.restore();
|
|
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 l = this.areas.find((
|
|
447
|
+
const t = this.getContentBounds(e);
|
|
448
|
+
if (!t) {
|
|
449
|
+
const l = this.areas.find((v) => v.id === e);
|
|
450
450
|
if (!l)
|
|
451
451
|
return;
|
|
452
|
-
const { ctx:
|
|
453
|
-
|
|
452
|
+
const { ctx: c } = this, { x: p, y: u, width: g, height: m } = l.location;
|
|
453
|
+
c.save(), c.strokeStyle = "#3b82f6", c.lineWidth = 2 / this.zoom, c.setLineDash([]), c.strokeRect(p, u, g, m), c.restore();
|
|
454
454
|
return;
|
|
455
455
|
}
|
|
456
|
-
const { ctx:
|
|
457
|
-
|
|
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
458
|
const d = this.getContentHandlePositions(e);
|
|
459
459
|
if (d)
|
|
460
460
|
for (const l of d) {
|
|
461
|
-
|
|
462
|
-
const
|
|
461
|
+
i.save();
|
|
462
|
+
const c = this.hoveredHandle === l.type, p = c ? "#dbeafe" : "#ffffff", u = c ? "#2563eb" : "#3b82f6";
|
|
463
463
|
if (l.type === "rotate") {
|
|
464
|
-
const
|
|
465
|
-
|
|
464
|
+
const g = this.getRotatedPoint(s, n - a / 2, s, n, r);
|
|
465
|
+
i.beginPath(), i.strokeStyle = u, i.lineWidth = 1 / this.zoom, i.setLineDash([4 / this.zoom, 4 / this.zoom]), i.moveTo(g.x, g.y), i.lineTo(l.x, l.y), i.stroke(), i.setLineDash([]), i.beginPath(), i.fillStyle = p, i.strokeStyle = u, i.lineWidth = 2 / this.zoom, i.arc(l.x, l.y, l.radius, 0, Math.PI * 2), i.fill(), i.stroke(), i.beginPath(), i.strokeStyle = u, i.lineWidth = 1.5 / this.zoom, i.arc(l.x, l.y, l.radius * 0.5, -Math.PI * 0.8, Math.PI * 0.5), i.stroke();
|
|
466
466
|
} else
|
|
467
|
-
|
|
468
|
-
|
|
467
|
+
i.fillStyle = p, i.strokeStyle = u, i.lineWidth = 2 / this.zoom, i.fillRect(l.x - l.radius, l.y - l.radius, l.radius * 2, l.radius * 2), i.strokeRect(l.x - l.radius, l.y - l.radius, l.radius * 2, l.radius * 2);
|
|
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), d = e - i, l = t - s;
|
|
476
476
|
return {
|
|
477
|
-
x:
|
|
478
|
-
y: s + d * r + l *
|
|
477
|
+
x: i + d * a - l * r,
|
|
478
|
+
y: s + d * r + l * 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, d, l;
|
|
491
|
+
n > o ? (r = s, a = s * n, d = (i - a) / 2, l = 0) : (a = i, r = i / n, d = 0, l = (s - r) / 2), e.drawImage(this.bgImageElement, d, l, a, r), e.restore();
|
|
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 || d <= 0 || (e.save(), e.strokeStyle = "#22c55e", e.lineWidth = 1 / this.zoom, e.setLineDash([6 / this.zoom, 4 / this.zoom]), e.strokeRect(o,
|
|
499
|
+
const { ctx: e } = this, { top: t, right: i, bottom: s, left: n } = this.safeArea, o = n, a = t, r = this.artboard.width - n - i, d = this.artboard.height - t - s;
|
|
500
|
+
r <= 0 || d <= 0 || (e.save(), e.strokeStyle = "#22c55e", e.lineWidth = 1 / this.zoom, e.setLineDash([6 / this.zoom, 4 / this.zoom]), e.strokeRect(o, a, r, d), e.setLineDash([]), e.restore());
|
|
501
501
|
}
|
|
502
502
|
/**
|
|
503
503
|
* Render an area with its content
|
|
504
504
|
*/
|
|
505
505
|
renderAreaWithContent(e) {
|
|
506
|
-
const { ctx:
|
|
507
|
-
s && (
|
|
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
508
|
const d = this.contents.get(e.id);
|
|
509
|
-
d && (d.type === "text" ? this.renderTextContent(e, d) : d.type === "image" && this.renderImageContent(e, d)), this.showAreaBorders && e.showBorder && (
|
|
509
|
+
d && (d.type === "text" ? this.renderTextContent(e, d) : d.type === "image" && this.renderImageContent(e, d)), this.showAreaBorders && e.showBorder && (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
|
-
|
|
519
|
-
let
|
|
520
|
-
switch (
|
|
517
|
+
const { ctx: i } = this, { location: s } = e, { x: n, y: o, width: a, height: r } = s, d = t.offset || { x: 0, y: 0 }, l = t.scale ?? 1, c = t.rotation ?? 0, p = n + a / 2 + d.x, u = o + r / 2 + d.y;
|
|
518
|
+
i.save(), i.beginPath(), i.rect(n, o, a, r), i.clip(), i.translate(p, u), i.rotate(c * Math.PI / 180), i.scale(l, l), i.font = `${t.size}px ${t.font}, sans-serif`, i.fillStyle = t.color, i.textBaseline = "middle";
|
|
519
|
+
let g;
|
|
520
|
+
switch (t.align) {
|
|
521
521
|
case "left":
|
|
522
|
-
|
|
522
|
+
i.textAlign = "left", g = -a / 2 + 10;
|
|
523
523
|
break;
|
|
524
524
|
case "right":
|
|
525
|
-
|
|
525
|
+
i.textAlign = "right", g = a / 2 - 10;
|
|
526
526
|
break;
|
|
527
527
|
case "center":
|
|
528
528
|
default:
|
|
529
|
-
|
|
529
|
+
i.textAlign = "center", g = 0;
|
|
530
530
|
break;
|
|
531
531
|
}
|
|
532
|
-
|
|
532
|
+
i.fillText(t.text, g, 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(y, x), s.rotate(
|
|
541
|
+
const { ctx: s } = this, { location: n } = e, { x: o, y: a, width: r, height: d } = n, l = t.offset || { x: 0, y: 0 }, c = t.scale ?? 1, p = t.rotation ?? 0;
|
|
542
|
+
s.save(), s.beginPath(), s.rect(o, a, r, d), s.clip();
|
|
543
|
+
const u = i.naturalWidth / i.naturalHeight, g = r / d;
|
|
544
|
+
let m, v;
|
|
545
|
+
u > g ? (m = r, v = r / u) : (v = d, m = d * u);
|
|
546
|
+
const b = m * c, w = v * c, y = o + r / 2 + l.x, x = a + d / 2 + l.y;
|
|
547
|
+
s.translate(y, x), s.rotate(p * Math.PI / 180), s.drawImage(i, -b / 2, -w / 2, b, w), s.restore();
|
|
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, d, l;
|
|
559
|
+
n > o ? (r = i, a = i * n, d = (t - a) / 2, l = 0) : (a = t, r = t / n, d = 0, l = (i - r) / 2), e.drawImage(this.bgImageElement, d, l, a, r), e.restore();
|
|
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 d = this.contents.get(
|
|
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 d = this.contents.get(t.id);
|
|
568
568
|
if (d) {
|
|
569
569
|
if (d.type === "text") {
|
|
570
570
|
if (!d.text.trim())
|
|
571
571
|
return;
|
|
572
|
-
const l = d.offset || { x: 0, y: 0 },
|
|
573
|
-
e.save(), e.beginPath(), e.rect(
|
|
574
|
-
let
|
|
572
|
+
const l = d.offset || { x: 0, y: 0 }, c = d.scale ?? 1, p = d.rotation ?? 0, u = n + a / 2 + l.x, g = o + r / 2 + l.y;
|
|
573
|
+
e.save(), e.beginPath(), e.rect(n, o, a, r), e.clip(), e.translate(u, g), e.rotate(p * Math.PI / 180), e.scale(c, c), e.font = `${d.size}px ${d.font}, sans-serif`, e.fillStyle = d.color, e.textBaseline = "middle";
|
|
574
|
+
let m;
|
|
575
575
|
switch (d.align) {
|
|
576
576
|
case "left":
|
|
577
|
-
e.textAlign = "left",
|
|
577
|
+
e.textAlign = "left", m = -a / 2 + 10;
|
|
578
578
|
break;
|
|
579
579
|
case "right":
|
|
580
|
-
e.textAlign = "right",
|
|
580
|
+
e.textAlign = "right", m = a / 2 - 10;
|
|
581
581
|
break;
|
|
582
582
|
case "center":
|
|
583
583
|
default:
|
|
584
|
-
e.textAlign = "center",
|
|
584
|
+
e.textAlign = "center", m = 0;
|
|
585
585
|
break;
|
|
586
586
|
}
|
|
587
|
-
e.fillText(d.text,
|
|
587
|
+
e.fillText(d.text, m, 0), e.restore();
|
|
588
588
|
} else if (d.type === "image") {
|
|
589
|
-
const l = this.loadedImages.get(
|
|
589
|
+
const l = this.loadedImages.get(t.id);
|
|
590
590
|
if (!l)
|
|
591
591
|
return;
|
|
592
|
-
const
|
|
593
|
-
e.save(), e.beginPath(), e.rect(
|
|
594
|
-
const
|
|
595
|
-
let
|
|
596
|
-
|
|
597
|
-
const w =
|
|
598
|
-
e.translate(x, C), e.rotate(
|
|
592
|
+
const c = d.offset || { x: 0, y: 0 }, p = d.scale ?? 1, u = d.rotation ?? 0;
|
|
593
|
+
e.save(), e.beginPath(), e.rect(n, o, a, r), e.clip();
|
|
594
|
+
const g = l.naturalWidth / l.naturalHeight, m = a / r;
|
|
595
|
+
let v, b;
|
|
596
|
+
g > m ? (v = a, b = a / g) : (b = r, v = r * g);
|
|
597
|
+
const w = v * p, y = b * p, x = n + a / 2 + c.x, C = o + r / 2 + c.y;
|
|
598
|
+
e.translate(x, C), e.rotate(u * Math.PI / 180), e.drawImage(l, -w / 2, -y / 2, w, y), e.restore();
|
|
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 S(
|
|
610
|
-
const e =
|
|
609
|
+
function S(f) {
|
|
610
|
+
const e = f.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 E(f) {
|
|
621
|
+
return f.contentType === "text" || f.contentType === "both";
|
|
622
622
|
}
|
|
623
|
-
function T(
|
|
624
|
-
return
|
|
623
|
+
function T(f) {
|
|
624
|
+
return f.contentType === "image" || f.contentType === "both";
|
|
625
625
|
}
|
|
626
626
|
const V = 50;
|
|
627
|
-
class
|
|
627
|
+
class F {
|
|
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,11 +670,11 @@ 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:
|
|
677
|
+
description: t
|
|
678
678
|
}), this.history.length > V && this.history.shift(), this.historyIndex = this.history.length - 1;
|
|
679
679
|
}
|
|
680
680
|
restoreFromHistory() {
|
|
@@ -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
|
+
E(t) && e.set(t.id, S(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 ? S(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 && E(t) ? this.contents.set(e, S(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 z {
|
|
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 O extends z {
|
|
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 $ extends z {
|
|
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 O(), this.boundMouseDown = this.handleMouseDown.bind(this), this.boundMouseMove = this.handleMouseMove.bind(this), this.boundMouseUp = this.handleMouseUp.bind(this), this.boundMouseLeave = this.handleMouseLeave.bind(this), this.boundContextMenu = this.handleContextMenu.bind(this), this.boundDoubleClick = this.handleDoubleClick.bind(this), this.boundTouchStart = this.handleTouchStart.bind(this), this.boundTouchMove = this.handleTouchMove.bind(this), this.boundTouchEnd = this.handleTouchEnd.bind(this), e.addEventListener("mousedown", this.boundMouseDown), e.addEventListener("mousemove", this.boundMouseMove), e.addEventListener("mouseup", this.boundMouseUp), e.addEventListener("mouseleave", this.boundMouseLeave), e.addEventListener("contextmenu", this.boundContextMenu), e.addEventListener("dblclick", this.boundDoubleClick), e.addEventListener("touchstart", this.boundTouchStart, { passive: !1 }), e.addEventListener("touchmove", this.boundTouchMove, { passive: !1 }), e.addEventListener("touchend", this.boundTouchEnd), this.gestureHandler.on("pinch", ({ scale: n }) => {
|
|
930
|
+
const o = Math.max(0.1, Math.min(5, this.initialPinchZoom * n));
|
|
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" &&
|
|
993
|
+
const a = i.get(t), r = this.engine.getContentBounds(t);
|
|
994
|
+
if (o === "rotate" && a?.type === "image" && r) {
|
|
987
995
|
const d = 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
1001
|
startAngle: d
|
|
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
1037
|
const d = r.width / 2 - 10, l = r.height / 2 - 10;
|
|
1030
|
-
|
|
1038
|
+
a.x = Math.max(-d, Math.min(d, a.x)), a.y = Math.max(-l, Math.min(l, 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, d = Math.max(0.1, Math.min(5, i * r));
|
|
1051
|
+
this.emit("content:scale", { areaId: t, scale: d });
|
|
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
1060
|
for (const d of r)
|
|
1053
|
-
if (Math.abs(
|
|
1054
|
-
|
|
1061
|
+
if (Math.abs(a - d) < 5) {
|
|
1062
|
+
a = d === 360 ? 0 : d;
|
|
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);
|
|
@@ -1085,203 +1093,214 @@ class B {
|
|
|
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,
|
|
1122
|
+
async finalizeDesign(e, t) {
|
|
1115
1123
|
try {
|
|
1116
|
-
const
|
|
1124
|
+
const i = 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
|
|
1123
1131
|
})
|
|
1124
1132
|
});
|
|
1125
|
-
if (!
|
|
1126
|
-
let s = `Failed to finalize design (${
|
|
1133
|
+
if (!i.ok) {
|
|
1134
|
+
let s = `Failed to finalize design (${i.status} ${i.statusText})
|
|
1127
1135
|
|
|
1128
1136
|
`;
|
|
1129
|
-
if (
|
|
1130
|
-
const
|
|
1131
|
-
s +=
|
|
1132
|
-
} else
|
|
1137
|
+
if (i.status === 400) {
|
|
1138
|
+
const n = await i.json().catch(() => ({}));
|
|
1139
|
+
s += n.error || "Invalid design data.";
|
|
1140
|
+
} else i.status === 429 ? s += "Rate limit exceeded." : i.status >= 500 && (s += "Server error.");
|
|
1133
1141
|
throw new Error(s);
|
|
1134
1142
|
}
|
|
1135
|
-
return
|
|
1136
|
-
} catch (
|
|
1137
|
-
throw
|
|
1143
|
+
return i.json();
|
|
1144
|
+
} catch (i) {
|
|
1145
|
+
throw i instanceof Error && i.message.includes("Failed to fetch") ? new Error("Network error finalizing design") : i;
|
|
1138
1146
|
}
|
|
1139
1147
|
}
|
|
1140
1148
|
async finalizeMultiView(e) {
|
|
1141
1149
|
try {
|
|
1142
|
-
const
|
|
1150
|
+
const t = e.designs.map((s) => ({
|
|
1143
1151
|
templateId: s.templateId,
|
|
1144
1152
|
viewName: s.viewName,
|
|
1145
|
-
areaContents: s.contents.map(([
|
|
1146
|
-
const
|
|
1147
|
-
return o.type === "text" ?
|
|
1153
|
+
areaContents: s.contents.map(([n, o]) => {
|
|
1154
|
+
const a = { areaId: n, isModified: !0 };
|
|
1155
|
+
return o.type === "text" ? a.text = {
|
|
1148
1156
|
content: o.text,
|
|
1149
1157
|
font: o.font,
|
|
1150
1158
|
size: o.size,
|
|
1151
1159
|
color: o.color,
|
|
1152
1160
|
align: o.align,
|
|
1153
1161
|
position: { x: o.offset?.x ?? 0, y: o.offset?.y ?? 0 }
|
|
1154
|
-
} : o.type === "image" && (
|
|
1155
|
-
assetId: "inline",
|
|
1156
|
-
assetUrl: "",
|
|
1162
|
+
} : o.type === "image" && (a.image = {
|
|
1163
|
+
assetId: o.assetId || "inline",
|
|
1164
|
+
assetUrl: o.assetUrl || "",
|
|
1157
1165
|
rotation: o.rotation ?? 0,
|
|
1158
1166
|
scale: o.scale ?? 1,
|
|
1159
1167
|
position: { x: o.offset?.x ?? 0, y: o.offset?.y ?? 0 },
|
|
1160
|
-
dataUrl
|
|
1161
|
-
|
|
1168
|
+
// Only send dataUrl if the image wasn't pre-uploaded
|
|
1169
|
+
...o.assetId ? {} : { dataUrl: o.dataUrl }
|
|
1170
|
+
}), a;
|
|
1162
1171
|
})
|
|
1163
|
-
})),
|
|
1172
|
+
})), i = await fetch(`${this.baseUrl}/finalize`, {
|
|
1164
1173
|
method: "POST",
|
|
1165
1174
|
headers: { "Content-Type": "application/json" },
|
|
1166
1175
|
body: JSON.stringify({
|
|
1167
1176
|
productId: e.productId,
|
|
1168
|
-
designs:
|
|
1177
|
+
designs: t,
|
|
1169
1178
|
cartContext: e.cartContext,
|
|
1170
1179
|
customerId: e.customerId,
|
|
1171
1180
|
customerEmail: e.customerEmail,
|
|
1172
1181
|
sessionId: e.sessionId
|
|
1173
1182
|
})
|
|
1174
1183
|
});
|
|
1175
|
-
if (!
|
|
1176
|
-
let s = `Failed to finalize multi-view design (${
|
|
1184
|
+
if (!i.ok) {
|
|
1185
|
+
let s = `Failed to finalize multi-view design (${i.status} ${i.statusText})
|
|
1177
1186
|
|
|
1178
1187
|
`;
|
|
1179
|
-
if (
|
|
1180
|
-
const
|
|
1181
|
-
s +=
|
|
1182
|
-
} else
|
|
1188
|
+
if (i.status === 400) {
|
|
1189
|
+
const n = await i.json().catch(() => ({}));
|
|
1190
|
+
s += n.error || "Invalid design data.";
|
|
1191
|
+
} else i.status === 429 ? s += "Rate limit exceeded." : i.status >= 500 && (s += "Server error.");
|
|
1183
1192
|
throw new Error(s);
|
|
1184
1193
|
}
|
|
1194
|
+
return i.json();
|
|
1195
|
+
} catch (t) {
|
|
1196
|
+
throw t instanceof Error && t.message.includes("Failed to fetch") ? new Error("Network error finalizing multi-view design") : t;
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
async getDesignStatus(e) {
|
|
1200
|
+
try {
|
|
1201
|
+
const t = await fetch(`${this.baseUrl}/designs/${e}/status`);
|
|
1202
|
+
if (!t.ok)
|
|
1203
|
+
throw new Error(`Failed to check design status (${t.status})`);
|
|
1185
1204
|
return t.json();
|
|
1186
|
-
} catch (
|
|
1187
|
-
throw
|
|
1205
|
+
} catch (t) {
|
|
1206
|
+
throw t instanceof Error && t.message.includes("Failed to fetch") ? new Error("Network error checking design status") : t;
|
|
1188
1207
|
}
|
|
1189
1208
|
}
|
|
1190
1209
|
async pollMultiViewStatus(e) {
|
|
1191
1210
|
try {
|
|
1192
|
-
const
|
|
1193
|
-
`${this.baseUrl}/
|
|
1211
|
+
const t = await fetch(
|
|
1212
|
+
`${this.baseUrl}/finalize-status?designIds=${e.join(",")}`
|
|
1194
1213
|
);
|
|
1195
|
-
if (!
|
|
1196
|
-
throw new Error(`Failed to check multi-view design status (${
|
|
1197
|
-
return
|
|
1198
|
-
} catch (
|
|
1199
|
-
throw
|
|
1214
|
+
if (!t.ok)
|
|
1215
|
+
throw new Error(`Failed to check multi-view design status (${t.status})`);
|
|
1216
|
+
return t.json();
|
|
1217
|
+
} catch (t) {
|
|
1218
|
+
throw t instanceof Error && t.message.includes("Failed to fetch") ? new Error("Network error checking multi-view design status") : t;
|
|
1200
1219
|
}
|
|
1201
1220
|
}
|
|
1202
|
-
async uploadAsset(e,
|
|
1221
|
+
async uploadAsset(e, t) {
|
|
1203
1222
|
try {
|
|
1204
|
-
const
|
|
1205
|
-
|
|
1223
|
+
const i = new FormData();
|
|
1224
|
+
i.append("shopId", t), i.append("file", e);
|
|
1206
1225
|
const s = await fetch(`${this.baseUrl}/assets/upload`, {
|
|
1207
1226
|
method: "POST",
|
|
1208
|
-
body:
|
|
1227
|
+
body: i
|
|
1209
1228
|
});
|
|
1210
1229
|
if (!s.ok) {
|
|
1211
|
-
let
|
|
1230
|
+
let n = `Failed to upload asset (${s.status} ${s.statusText})
|
|
1212
1231
|
|
|
1213
1232
|
`;
|
|
1214
1233
|
if (s.status === 400) {
|
|
1215
1234
|
const o = await s.json().catch(() => ({}));
|
|
1216
|
-
|
|
1217
|
-
} else s.status === 413 ?
|
|
1218
|
-
throw new Error(
|
|
1235
|
+
n += o.error || "Invalid file.";
|
|
1236
|
+
} else s.status === 413 ? n += "File too large (max 15MB)." : s.status >= 500 && (n += "Server error.");
|
|
1237
|
+
throw new Error(n);
|
|
1219
1238
|
}
|
|
1220
1239
|
return s.json();
|
|
1221
|
-
} catch (
|
|
1222
|
-
throw
|
|
1240
|
+
} catch (i) {
|
|
1241
|
+
throw i instanceof Error && i.message.includes("Failed to fetch") ? new Error("Network error uploading asset") : i;
|
|
1223
1242
|
}
|
|
1224
1243
|
}
|
|
1225
1244
|
async getStorageUsage(e) {
|
|
1226
1245
|
try {
|
|
1227
|
-
const
|
|
1228
|
-
if (!
|
|
1229
|
-
throw new Error(`Failed to fetch storage usage (${
|
|
1230
|
-
return
|
|
1231
|
-
} catch (
|
|
1232
|
-
throw
|
|
1246
|
+
const t = await fetch(`${this.baseUrl}/assets/usage?shopId=${encodeURIComponent(e)}`);
|
|
1247
|
+
if (!t.ok)
|
|
1248
|
+
throw new Error(`Failed to fetch storage usage (${t.status})`);
|
|
1249
|
+
return t.json();
|
|
1250
|
+
} catch (t) {
|
|
1251
|
+
throw t instanceof Error && t.message.includes("Failed to fetch") ? new Error("Network error fetching storage usage") : t;
|
|
1233
1252
|
}
|
|
1234
1253
|
}
|
|
1235
1254
|
}
|
|
1236
|
-
function
|
|
1237
|
-
const
|
|
1238
|
-
return e && Object.entries(e).forEach(([s,
|
|
1239
|
-
s === "class" ?
|
|
1240
|
-
}),
|
|
1241
|
-
typeof s == "string" ?
|
|
1242
|
-
}),
|
|
1255
|
+
function h(f, e, ...t) {
|
|
1256
|
+
const i = document.createElement(f);
|
|
1257
|
+
return e && Object.entries(e).forEach(([s, n]) => {
|
|
1258
|
+
s === "class" ? i.className = n : s in i ? i[s] = n : i.setAttribute(s, String(n));
|
|
1259
|
+
}), t.forEach((s) => {
|
|
1260
|
+
typeof s == "string" ? i.appendChild(document.createTextNode(s)) : i.appendChild(s);
|
|
1261
|
+
}), i;
|
|
1243
1262
|
}
|
|
1244
|
-
function q(
|
|
1245
|
-
let
|
|
1246
|
-
const s = (...
|
|
1247
|
-
|
|
1248
|
-
|
|
1263
|
+
function q(f, e) {
|
|
1264
|
+
let t = null, i = null;
|
|
1265
|
+
const s = (...n) => {
|
|
1266
|
+
i = n, t && clearTimeout(t), t = setTimeout(() => {
|
|
1267
|
+
i && f(...i), t = null, i = null;
|
|
1249
1268
|
}, e);
|
|
1250
1269
|
};
|
|
1251
1270
|
return s.flush = () => {
|
|
1252
|
-
|
|
1271
|
+
t && (clearTimeout(t), t = null), i && (f(...i), i = null);
|
|
1253
1272
|
}, s.cancel = () => {
|
|
1254
|
-
|
|
1273
|
+
t && (clearTimeout(t), t = null), i = null;
|
|
1255
1274
|
}, s;
|
|
1256
1275
|
}
|
|
1257
|
-
class H extends
|
|
1276
|
+
class H extends z {
|
|
1258
1277
|
constructor() {
|
|
1259
|
-
super(), this.saveEnabled = !0, this.percentLabel =
|
|
1260
|
-
const e =
|
|
1278
|
+
super(), this.saveEnabled = !0, this.percentLabel = h("span", { class: "zoom-percent" }, "100%");
|
|
1279
|
+
const e = h("button", {
|
|
1261
1280
|
class: "zoom-btn",
|
|
1262
1281
|
title: "Zoom out"
|
|
1263
1282
|
}, "−");
|
|
1264
1283
|
e.addEventListener("click", () => this.emit("zoom-out", void 0));
|
|
1265
|
-
const
|
|
1284
|
+
const t = h("button", {
|
|
1266
1285
|
class: "zoom-btn",
|
|
1267
1286
|
title: "Fit to view"
|
|
1268
1287
|
}, "Fit");
|
|
1269
|
-
|
|
1270
|
-
const
|
|
1288
|
+
t.addEventListener("click", () => this.emit("zoom-fit", void 0));
|
|
1289
|
+
const i = h("button", {
|
|
1271
1290
|
class: "zoom-btn",
|
|
1272
1291
|
title: "Zoom in"
|
|
1273
1292
|
}, "+");
|
|
1274
|
-
|
|
1293
|
+
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
1294
|
this.emit("view-change", { viewName: this.viewSelect.value });
|
|
1276
|
-
}), this.element =
|
|
1277
|
-
const s =
|
|
1278
|
-
this.element.appendChild(s), this.saveBtn =
|
|
1295
|
+
}), this.element = h("div", { class: "zoom-toolbar" }), this.element.appendChild(e), this.element.appendChild(t), this.element.appendChild(i), this.element.appendChild(this.percentLabel), this.element.appendChild(this.viewSelect);
|
|
1296
|
+
const s = h("div", { class: "zoom-toolbar-spacer" });
|
|
1297
|
+
this.element.appendChild(s), this.saveBtn = h("button", {
|
|
1279
1298
|
class: "zoom-btn zoom-save-btn",
|
|
1280
1299
|
title: "Save customization"
|
|
1281
|
-
}, "
|
|
1300
|
+
}, "✓"), this.saveBtn.style.display = "none", this.saveBtn.addEventListener("click", () => this.emit("save", void 0)), this.element.appendChild(this.saveBtn), this.closeBtn = h("button", {
|
|
1282
1301
|
class: "zoom-btn zoom-close-btn",
|
|
1283
1302
|
title: "Close editor"
|
|
1284
|
-
}, "✕
|
|
1303
|
+
}, "✕"), this.closeBtn.addEventListener("click", () => this.emit("close", void 0)), this.element.appendChild(this.closeBtn);
|
|
1285
1304
|
}
|
|
1286
1305
|
setZoom(e) {
|
|
1287
1306
|
this.percentLabel.textContent = `${Math.round(e * 100)}%`;
|
|
@@ -1295,14 +1314,14 @@ class H extends E {
|
|
|
1295
1314
|
setSaveButtonEnabled(e) {
|
|
1296
1315
|
this.saveEnabled = e, e || (this.saveBtn.style.display = "none");
|
|
1297
1316
|
}
|
|
1298
|
-
setSaveDisabled(e,
|
|
1299
|
-
this.saveBtn.disabled = e,
|
|
1317
|
+
setSaveDisabled(e, t) {
|
|
1318
|
+
this.saveBtn.disabled = e, t && (this.saveBtn.textContent = t);
|
|
1300
1319
|
}
|
|
1301
1320
|
setViews(e) {
|
|
1302
1321
|
this.viewSelect.innerHTML = "";
|
|
1303
|
-
for (const
|
|
1304
|
-
const
|
|
1305
|
-
|
|
1322
|
+
for (const t of e) {
|
|
1323
|
+
const i = document.createElement("option");
|
|
1324
|
+
i.value = t.viewName, i.textContent = t.viewName, this.viewSelect.appendChild(i);
|
|
1306
1325
|
}
|
|
1307
1326
|
this.viewSelect.style.display = e.length > 1 ? "" : "none";
|
|
1308
1327
|
}
|
|
@@ -1323,242 +1342,258 @@ const Z = [
|
|
|
1323
1342
|
{ label: "L", value: "left" },
|
|
1324
1343
|
{ label: "C", value: "center" },
|
|
1325
1344
|
{ label: "R", value: "right" }
|
|
1326
|
-
],
|
|
1327
|
-
class
|
|
1345
|
+
], A = ["image/png", "image/jpeg", "image/webp", "image/svg+xml"], Y = 15 * 1024 * 1024;
|
|
1346
|
+
class L extends z {
|
|
1328
1347
|
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 =
|
|
1348
|
+
super(), this.mode = "text", this.isSelected = !1, this.isUploading = !1, this.textInputEl = null, this.fontSelectEl = null, this.sizeInputEl = null, this.colorInputEl = null, this.alignGroupEl = null, this.area = e;
|
|
1349
|
+
const t = E(e), i = T(e);
|
|
1350
|
+
this.mode = i && !t ? "image" : "text", this.element = h("div", { class: "area-card" }), this.element.addEventListener("click", (r) => {
|
|
1332
1351
|
r.target.tagName === "INPUT" || r.target.tagName === "SELECT" || r.target.tagName === "BUTTON" || this.emit("select", { areaId: e.id });
|
|
1333
1352
|
});
|
|
1334
|
-
const s =
|
|
1335
|
-
if (
|
|
1336
|
-
const r =
|
|
1337
|
-
|
|
1353
|
+
const s = h("div", { class: "area-card-header" }), n = h("div", { class: "area-card-name-row" }), o = h("span", { class: "area-card-name" }, e.name);
|
|
1354
|
+
if (n.appendChild(o), e.required) {
|
|
1355
|
+
const r = h("span", { class: "area-card-badge" }, "Required");
|
|
1356
|
+
n.appendChild(r);
|
|
1338
1357
|
}
|
|
1339
|
-
s.appendChild(
|
|
1340
|
-
const
|
|
1358
|
+
s.appendChild(n);
|
|
1359
|
+
const a = h("button", {
|
|
1341
1360
|
class: "area-card-reset-btn",
|
|
1342
1361
|
title: "Reset content"
|
|
1343
1362
|
}, "Reset");
|
|
1344
|
-
if (
|
|
1363
|
+
if (a.addEventListener("click", (r) => {
|
|
1345
1364
|
r.stopPropagation(), this.emit("clear", { areaId: e.id });
|
|
1346
|
-
}), s.appendChild(
|
|
1347
|
-
const r =
|
|
1365
|
+
}), s.appendChild(a), this.element.appendChild(s), t && i) {
|
|
1366
|
+
const r = h("div", { class: "area-card-mode-switcher" }), d = h("button", {
|
|
1348
1367
|
class: "mode-btn mode-btn-active"
|
|
1349
1368
|
}, "Text");
|
|
1350
1369
|
d.dataset.mode = "text";
|
|
1351
|
-
const l =
|
|
1352
|
-
l.dataset.mode = "image", d.addEventListener("click", (
|
|
1353
|
-
|
|
1354
|
-
}), l.addEventListener("click", (
|
|
1355
|
-
|
|
1370
|
+
const l = h("button", { class: "mode-btn" }, "Image");
|
|
1371
|
+
l.dataset.mode = "image", d.addEventListener("click", (c) => {
|
|
1372
|
+
c.stopPropagation(), this.setMode("text"), d.classList.add("mode-btn-active"), l.classList.remove("mode-btn-active"), this.currentContent?.type === "image" && this.emit("clear", { areaId: e.id });
|
|
1373
|
+
}), l.addEventListener("click", (c) => {
|
|
1374
|
+
c.stopPropagation(), this.setMode("image"), l.classList.add("mode-btn-active"), d.classList.remove("mode-btn-active"), this.currentContent?.type === "text" && this.emit("clear", { areaId: e.id });
|
|
1356
1375
|
}), r.appendChild(d), r.appendChild(l), this.element.appendChild(r);
|
|
1357
1376
|
}
|
|
1358
|
-
this.contentContainer =
|
|
1377
|
+
this.contentContainer = h("div", { class: "area-card-content" }), this.element.appendChild(this.contentContainer), this.transformContainer = h("div", { class: "area-card-transforms" }), this.element.appendChild(this.transformContainer), this.renderContent();
|
|
1359
1378
|
}
|
|
1360
1379
|
setMode(e) {
|
|
1361
1380
|
this.mode = e, this.renderContent();
|
|
1362
1381
|
}
|
|
1363
1382
|
setContent(e) {
|
|
1364
|
-
const
|
|
1365
|
-
this.currentContent = e, e?.type === "image" && (this.mode = "image"), this.element.querySelectorAll(".mode-btn").forEach((
|
|
1366
|
-
const s =
|
|
1383
|
+
const t = this.currentContent?.type;
|
|
1384
|
+
this.currentContent = e, e?.type === "image" ? this.mode = "image" : t === "image" && (this.mode = E(this.area) ? "text" : "image"), this.element.querySelectorAll(".mode-btn").forEach((i) => {
|
|
1385
|
+
const s = i;
|
|
1367
1386
|
s.classList.toggle("mode-btn-active", s.dataset.mode === this.mode);
|
|
1368
|
-
}), e?.type !==
|
|
1387
|
+
}), e?.type !== t || e?.type === "image" || e === void 0 ? this.renderContent() : e?.type === "text" && this.updateTextValues(e), this.renderTransforms();
|
|
1369
1388
|
}
|
|
1370
1389
|
setSelected(e) {
|
|
1371
1390
|
this.isSelected = e, this.element.classList.toggle("area-card-selected", e), this.renderTransforms();
|
|
1372
1391
|
}
|
|
1373
1392
|
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
|
-
|
|
1393
|
+
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) => {
|
|
1394
|
+
const i = t, s = i.title === "Left" ? "left" : i.title === "Center" ? "center" : "right";
|
|
1395
|
+
i.classList.toggle("align-btn-active", s === e.align);
|
|
1377
1396
|
});
|
|
1378
1397
|
}
|
|
1379
1398
|
renderContent() {
|
|
1380
1399
|
this.contentContainer.innerHTML = "", this.textInputEl = null, this.fontSelectEl = null, this.sizeInputEl = null, this.colorInputEl = null, this.alignGroupEl = null;
|
|
1381
|
-
const e =
|
|
1382
|
-
e &&
|
|
1400
|
+
const e = E(this.area), t = T(this.area), i = !t || e && t && this.mode === "text", s = !e || e && t && this.mode === "image";
|
|
1401
|
+
e && i && this.renderTextControls(), t && s && this.renderImageControls(), this.applyUploadSpinner();
|
|
1383
1402
|
}
|
|
1384
1403
|
renderTextControls() {
|
|
1385
|
-
const e = this.currentContent,
|
|
1404
|
+
const e = this.currentContent, t = e?.type === "text" ? e : S(this.area), i = h("input", {
|
|
1386
1405
|
class: "area-input-text",
|
|
1387
1406
|
type: "text",
|
|
1388
1407
|
placeholder: this.area.placeholder || "Enter text..."
|
|
1389
1408
|
});
|
|
1390
|
-
|
|
1391
|
-
this.emit("text:change", { areaId: this.area.id, updates: { text:
|
|
1392
|
-
}), this.contentContainer.appendChild(
|
|
1393
|
-
const s =
|
|
1409
|
+
i.value = t.text, this.area.textOptions?.maxLength && (i.maxLength = this.area.textOptions.maxLength), i.addEventListener("input", () => {
|
|
1410
|
+
this.emit("text:change", { areaId: this.area.id, updates: { text: i.value } });
|
|
1411
|
+
}), this.contentContainer.appendChild(i), this.textInputEl = i;
|
|
1412
|
+
const s = h("div", { class: "area-input-row" }), n = h("select", { class: "area-input-font" });
|
|
1394
1413
|
for (const l of Z) {
|
|
1395
|
-
const
|
|
1396
|
-
|
|
1414
|
+
const c = h("option");
|
|
1415
|
+
c.value = l.value, c.textContent = l.label, l.value === t.font && (c.selected = !0), n.appendChild(c);
|
|
1397
1416
|
}
|
|
1398
|
-
|
|
1399
|
-
this.emit("text:change", { areaId: this.area.id, updates: { font:
|
|
1400
|
-
}), s.appendChild(
|
|
1401
|
-
const o =
|
|
1417
|
+
n.addEventListener("change", () => {
|
|
1418
|
+
this.emit("text:change", { areaId: this.area.id, updates: { font: n.value } });
|
|
1419
|
+
}), s.appendChild(n), this.fontSelectEl = n;
|
|
1420
|
+
const o = h("input", {
|
|
1402
1421
|
class: "area-input-size",
|
|
1403
1422
|
type: "number"
|
|
1404
1423
|
});
|
|
1405
|
-
o.value = String(
|
|
1424
|
+
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
1425
|
this.emit("text:change", {
|
|
1407
1426
|
areaId: this.area.id,
|
|
1408
1427
|
updates: { size: parseInt(o.value, 10) || 24 }
|
|
1409
1428
|
});
|
|
1410
1429
|
}), s.appendChild(o), this.sizeInputEl = o, this.contentContainer.appendChild(s);
|
|
1411
|
-
const
|
|
1430
|
+
const a = h("div", { class: "area-input-row" }), r = h("div", { class: "area-input-align" });
|
|
1412
1431
|
for (const l of j) {
|
|
1413
|
-
const
|
|
1414
|
-
class: `align-btn${
|
|
1432
|
+
const c = h("button", {
|
|
1433
|
+
class: `align-btn${t.align === l.value ? " align-btn-active" : ""}`,
|
|
1415
1434
|
title: l.label === "L" ? "Left" : l.label === "C" ? "Center" : "Right"
|
|
1416
1435
|
}, l.label);
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
}), r.appendChild(
|
|
1436
|
+
c.addEventListener("click", (p) => {
|
|
1437
|
+
p.stopPropagation(), this.emit("text:change", { areaId: this.area.id, updates: { align: l.value } }), r.querySelectorAll(".align-btn").forEach((u) => u.classList.remove("align-btn-active")), c.classList.add("align-btn-active");
|
|
1438
|
+
}), r.appendChild(c);
|
|
1420
1439
|
}
|
|
1421
|
-
|
|
1422
|
-
const d =
|
|
1440
|
+
a.appendChild(r), this.alignGroupEl = r;
|
|
1441
|
+
const d = h("input", {
|
|
1423
1442
|
class: "area-input-color",
|
|
1424
1443
|
type: "color"
|
|
1425
1444
|
});
|
|
1426
|
-
d.value =
|
|
1445
|
+
d.value = t.color, d.addEventListener("input", () => {
|
|
1427
1446
|
this.emit("text:change", { areaId: this.area.id, updates: { color: d.value } });
|
|
1428
|
-
}),
|
|
1447
|
+
}), a.appendChild(d), this.colorInputEl = d, this.contentContainer.appendChild(a);
|
|
1429
1448
|
}
|
|
1430
1449
|
renderImageControls() {
|
|
1431
1450
|
const e = this.currentContent;
|
|
1432
1451
|
if (e?.type === "image") {
|
|
1433
|
-
const
|
|
1434
|
-
|
|
1435
|
-
const s =
|
|
1436
|
-
s.appendChild(
|
|
1437
|
-
const
|
|
1438
|
-
|
|
1452
|
+
const t = h("div", { class: "area-image-preview" }), i = h("img", { class: "area-image-thumb" });
|
|
1453
|
+
i.src = e.dataUrl, i.alt = e.filename || "Uploaded image", t.appendChild(i);
|
|
1454
|
+
const s = h("div", { class: "area-image-info" });
|
|
1455
|
+
s.appendChild(h("span", { class: "area-image-name" }, e.filename || "Image"));
|
|
1456
|
+
const n = h("button", { class: "area-image-remove-btn" }, "Remove");
|
|
1457
|
+
n.addEventListener("click", (o) => {
|
|
1439
1458
|
o.stopPropagation(), this.emit("clear", { areaId: this.area.id });
|
|
1440
|
-
}), s.appendChild(
|
|
1459
|
+
}), s.appendChild(n), t.appendChild(s), this.contentContainer.appendChild(t);
|
|
1441
1460
|
} else {
|
|
1442
|
-
const
|
|
1443
|
-
|
|
1444
|
-
const s =
|
|
1445
|
-
s.style.display = "none",
|
|
1446
|
-
const
|
|
1447
|
-
if (!
|
|
1448
|
-
if (!
|
|
1449
|
-
const
|
|
1450
|
-
s.textContent =
|
|
1461
|
+
const t = h("button", { class: "area-upload-btn" }, "Upload Image"), i = h("input", { type: "file" });
|
|
1462
|
+
i.accept = A.join(","), i.style.display = "none";
|
|
1463
|
+
const s = h("div", { class: "area-validation-error" });
|
|
1464
|
+
s.style.display = "none", i.addEventListener("change", () => {
|
|
1465
|
+
const n = i.files?.[0];
|
|
1466
|
+
if (!n) return;
|
|
1467
|
+
if (!A.includes(n.type)) {
|
|
1468
|
+
const a = "Only PNG, JPEG, WebP, and SVG images are accepted";
|
|
1469
|
+
s.textContent = a, s.style.display = "block", this.emit("validation:error", { areaId: this.area.id, message: a }), i.value = "";
|
|
1451
1470
|
return;
|
|
1452
1471
|
}
|
|
1453
|
-
if (
|
|
1454
|
-
const
|
|
1455
|
-
s.textContent =
|
|
1472
|
+
if (n.size > Y) {
|
|
1473
|
+
const a = "File must be smaller than 15MB";
|
|
1474
|
+
s.textContent = a, s.style.display = "block", this.emit("validation:error", { areaId: this.area.id, message: a }), i.value = "";
|
|
1456
1475
|
return;
|
|
1457
1476
|
}
|
|
1458
1477
|
s.style.display = "none";
|
|
1459
1478
|
const o = new FileReader();
|
|
1460
1479
|
o.onload = () => {
|
|
1461
|
-
const
|
|
1462
|
-
this.emit("image:change", { areaId: this.area.id, dataUrl:
|
|
1463
|
-
}, o.readAsDataURL(
|
|
1464
|
-
}),
|
|
1465
|
-
|
|
1466
|
-
}), this.contentContainer.appendChild(
|
|
1480
|
+
const a = o.result;
|
|
1481
|
+
this.emit("image:change", { areaId: this.area.id, dataUrl: a, filename: n.name });
|
|
1482
|
+
}, o.readAsDataURL(n), i.value = "";
|
|
1483
|
+
}), t.addEventListener("click", (n) => {
|
|
1484
|
+
n.stopPropagation(), i.click();
|
|
1485
|
+
}), this.contentContainer.appendChild(t), this.contentContainer.appendChild(i), this.contentContainer.appendChild(s);
|
|
1467
1486
|
}
|
|
1468
1487
|
}
|
|
1469
1488
|
renderTransforms() {
|
|
1470
1489
|
this.transformContainer.innerHTML = "";
|
|
1471
1490
|
const e = this.currentContent;
|
|
1472
1491
|
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
|
-
d.stopPropagation(),
|
|
1482
|
-
}), o.appendChild(
|
|
1492
|
+
const t = e.type === "text" ? this.area.textOptions?.allowPositioning : this.area.imageOptions?.allowPositioning, i = e.type === "image" && this.area.imageOptions?.allowScaling, s = e.type === "image" && this.area.imageOptions?.allowRotation;
|
|
1493
|
+
if (!t && !i && !s) return;
|
|
1494
|
+
const n = h("div", { class: "area-card-divider" });
|
|
1495
|
+
this.transformContainer.appendChild(n);
|
|
1496
|
+
const o = h("div", { class: "transform-header" });
|
|
1497
|
+
o.appendChild(h("span", { class: "transform-title" }, "Transform"));
|
|
1498
|
+
const a = h("button", { class: "transform-reset-btn" }, "Reset");
|
|
1499
|
+
a.addEventListener("click", (d) => {
|
|
1500
|
+
d.stopPropagation(), t && this.emit("offset:change", { areaId: this.area.id, offset: { x: 0, y: 0 } }), i && this.emit("scale:change", { areaId: this.area.id, scale: 1 }), s && this.emit("rotation:change", { areaId: this.area.id, rotation: 0 });
|
|
1501
|
+
}), o.appendChild(a), this.transformContainer.appendChild(o);
|
|
1483
1502
|
const r = e.offset ?? { x: 0, y: 0 };
|
|
1484
|
-
if (
|
|
1485
|
-
const d =
|
|
1486
|
-
|
|
1503
|
+
if (t) {
|
|
1504
|
+
const d = h("div", { class: "area-input-row" }), l = h("label", { class: "transform-label" }, "X"), c = h("input", { class: "transform-input", type: "number" });
|
|
1505
|
+
c.value = String(Math.round(r.x)), c.addEventListener("change", () => {
|
|
1487
1506
|
this.emit("offset:change", {
|
|
1488
1507
|
areaId: this.area.id,
|
|
1489
|
-
offset: { ...r, x: parseInt(
|
|
1508
|
+
offset: { ...r, x: parseInt(c.value, 10) || 0 }
|
|
1490
1509
|
});
|
|
1491
1510
|
});
|
|
1492
|
-
const
|
|
1493
|
-
|
|
1511
|
+
const p = h("label", { class: "transform-label" }, "Y"), u = h("input", { class: "transform-input", type: "number" });
|
|
1512
|
+
u.value = String(Math.round(r.y)), u.addEventListener("change", () => {
|
|
1494
1513
|
this.emit("offset:change", {
|
|
1495
1514
|
areaId: this.area.id,
|
|
1496
|
-
offset: { ...r, y: parseInt(
|
|
1515
|
+
offset: { ...r, y: parseInt(u.value, 10) || 0 }
|
|
1497
1516
|
});
|
|
1498
|
-
}), d.appendChild(l), d.appendChild(
|
|
1517
|
+
}), d.appendChild(l), d.appendChild(c), d.appendChild(p), d.appendChild(u), this.transformContainer.appendChild(d);
|
|
1499
1518
|
}
|
|
1500
|
-
if (
|
|
1501
|
-
const d = e.scale ?? 1, l =
|
|
1502
|
-
|
|
1503
|
-
const
|
|
1504
|
-
|
|
1505
|
-
}), l.appendChild(
|
|
1519
|
+
if (i && e.type === "image") {
|
|
1520
|
+
const d = e.scale ?? 1, l = h("div", { class: "area-input-row transform-slider-row" }), c = h("label", { class: "transform-label" }, `Scale: ${Math.round(d * 100)}%`), p = h("input", { class: "transform-slider", type: "range" });
|
|
1521
|
+
p.min = "10", p.max = "500", p.step = "5", p.value = String(Math.round(d * 100)), p.addEventListener("input", () => {
|
|
1522
|
+
const u = parseInt(p.value, 10) / 100;
|
|
1523
|
+
c.textContent = `Scale: ${Math.round(u * 100)}%`, this.emit("scale:change", { areaId: this.area.id, scale: u });
|
|
1524
|
+
}), l.appendChild(c), l.appendChild(p), this.transformContainer.appendChild(l);
|
|
1506
1525
|
}
|
|
1507
1526
|
if (s && e.type === "image") {
|
|
1508
|
-
const d = e.rotation ?? 0, l =
|
|
1509
|
-
|
|
1510
|
-
const
|
|
1511
|
-
|
|
1512
|
-
}), l.appendChild(
|
|
1527
|
+
const d = e.rotation ?? 0, l = h("div", { class: "area-input-row transform-slider-row" }), c = h("label", { class: "transform-label" }, `Rotation: ${Math.round(d)}°`), p = h("input", { class: "transform-slider", type: "range" });
|
|
1528
|
+
p.min = "0", p.max = "360", p.step = "1", p.value = String(Math.round(d)), p.addEventListener("input", () => {
|
|
1529
|
+
const u = parseInt(p.value, 10);
|
|
1530
|
+
c.textContent = `Rotation: ${u}°`, this.emit("rotation:change", { areaId: this.area.id, rotation: u });
|
|
1531
|
+
}), l.appendChild(c), l.appendChild(p), this.transformContainer.appendChild(l);
|
|
1513
1532
|
}
|
|
1514
1533
|
}
|
|
1534
|
+
setUploading(e) {
|
|
1535
|
+
this.isUploading = e, this.applyUploadSpinner();
|
|
1536
|
+
}
|
|
1537
|
+
applyUploadSpinner() {
|
|
1538
|
+
const e = this.contentContainer.querySelector(".area-image-preview");
|
|
1539
|
+
if (!e) return;
|
|
1540
|
+
const t = e.querySelector(".area-upload-spinner-overlay");
|
|
1541
|
+
if (this.isUploading && !t) {
|
|
1542
|
+
const i = h("div", { class: "area-upload-spinner-overlay" });
|
|
1543
|
+
i.appendChild(h("div", { class: "area-upload-spinner" })), e.appendChild(i);
|
|
1544
|
+
} else !this.isUploading && t && t.remove();
|
|
1545
|
+
}
|
|
1515
1546
|
getElement() {
|
|
1516
1547
|
return this.element;
|
|
1517
1548
|
}
|
|
1518
1549
|
}
|
|
1519
|
-
class J extends
|
|
1550
|
+
class J extends z {
|
|
1520
1551
|
constructor(e) {
|
|
1521
1552
|
super(), this.cards = /* @__PURE__ */ new Map(), this.isMobile = !1, this.boundEscapeHandler = (s) => {
|
|
1522
1553
|
s.key === "Escape" && this.emit("dismiss", void 0);
|
|
1523
|
-
}, this.backdrop =
|
|
1554
|
+
}, this.backdrop = h("div", { class: "area-panel-backdrop" }), this.backdrop.addEventListener("click", () => {
|
|
1524
1555
|
this.emit("dismiss", void 0);
|
|
1525
|
-
}), this.element =
|
|
1526
|
-
const
|
|
1527
|
-
|
|
1528
|
-
const
|
|
1529
|
-
if (
|
|
1556
|
+
}), this.element = h("div", { class: "area-panel" });
|
|
1557
|
+
const t = h("div", { class: "area-panel-header" });
|
|
1558
|
+
t.appendChild(h("span", { class: "area-panel-title" }, "Customize"));
|
|
1559
|
+
const i = h("button", { class: "area-panel-close-btn", title: "Close" }, "×");
|
|
1560
|
+
if (i.addEventListener("click", () => {
|
|
1530
1561
|
this.emit("dismiss", void 0);
|
|
1531
|
-
}),
|
|
1532
|
-
const s =
|
|
1562
|
+
}), t.appendChild(i), this.element.appendChild(t), this.panelContent = h("div", { class: "area-panel-content" }), e.length === 0) {
|
|
1563
|
+
const s = h("p", { class: "area-panel-empty" }, "No customization areas defined.");
|
|
1533
1564
|
this.panelContent.appendChild(s);
|
|
1534
1565
|
} else
|
|
1535
1566
|
for (const s of e) {
|
|
1536
|
-
const
|
|
1537
|
-
this.cards.set(s.id,
|
|
1567
|
+
const n = new L(s);
|
|
1568
|
+
this.cards.set(s.id, n), n.on("text:change", (o) => this.emit("text:change", o)), n.on("image:change", (o) => this.emit("image:change", o)), n.on("clear", (o) => this.emit("clear", o)), n.on("select", (o) => this.emit("select", o)), n.on("offset:change", (o) => this.emit("offset:change", o)), n.on("scale:change", (o) => this.emit("scale:change", o)), n.on("rotation:change", (o) => this.emit("rotation:change", o)), n.on("validation:error", (o) => this.emit("validation:error", o)), this.panelContent.appendChild(n.getElement());
|
|
1538
1569
|
}
|
|
1539
1570
|
this.element.appendChild(this.panelContent);
|
|
1540
1571
|
}
|
|
1541
1572
|
setAreas(e) {
|
|
1542
1573
|
if (this.cards.clear(), this.panelContent.innerHTML = "", e.length === 0) {
|
|
1543
|
-
const
|
|
1544
|
-
this.panelContent.appendChild(
|
|
1574
|
+
const t = h("p", { class: "area-panel-empty" }, "No customization areas defined.");
|
|
1575
|
+
this.panelContent.appendChild(t);
|
|
1545
1576
|
} else
|
|
1546
|
-
for (const
|
|
1547
|
-
const
|
|
1548
|
-
this.cards.set(
|
|
1577
|
+
for (const t of e) {
|
|
1578
|
+
const i = new L(t);
|
|
1579
|
+
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
1580
|
}
|
|
1550
1581
|
}
|
|
1551
1582
|
setContents(e) {
|
|
1552
|
-
for (const [
|
|
1553
|
-
|
|
1583
|
+
for (const [t, i] of this.cards)
|
|
1584
|
+
i.setContent(e.get(t));
|
|
1554
1585
|
}
|
|
1555
1586
|
setSelectedArea(e) {
|
|
1556
|
-
for (const [
|
|
1557
|
-
const s =
|
|
1558
|
-
|
|
1587
|
+
for (const [t, i] of this.cards) {
|
|
1588
|
+
const s = t === e;
|
|
1589
|
+
i.setSelected(s), i.getElement().style.display = s ? "" : "none";
|
|
1559
1590
|
}
|
|
1560
1591
|
e ? (this.element.classList.add("area-panel-visible"), this.isMobile && this.backdrop.classList.add("area-panel-backdrop-visible"), document.addEventListener("keydown", this.boundEscapeHandler)) : (this.element.classList.remove("area-panel-visible"), this.isMobile && this.backdrop.classList.remove("area-panel-backdrop-visible"), document.removeEventListener("keydown", this.boundEscapeHandler));
|
|
1561
1592
|
}
|
|
1593
|
+
setUploading(e, t) {
|
|
1594
|
+
const i = this.cards.get(e);
|
|
1595
|
+
i && i.setUploading(t);
|
|
1596
|
+
}
|
|
1562
1597
|
setMobile(e) {
|
|
1563
1598
|
this.isMobile = e, this.element.classList.toggle("area-panel-mobile", e), e || (this.element.classList.remove("area-panel-visible"), this.backdrop.classList.remove("area-panel-backdrop-visible"));
|
|
1564
1599
|
}
|
|
@@ -1569,15 +1604,15 @@ class J extends E {
|
|
|
1569
1604
|
return this.backdrop;
|
|
1570
1605
|
}
|
|
1571
1606
|
}
|
|
1572
|
-
class
|
|
1607
|
+
class W {
|
|
1573
1608
|
constructor(e = window) {
|
|
1574
|
-
this.target = e, this.commands = [], this.isEnabled = !0, this.handleKeyDown = (
|
|
1609
|
+
this.target = e, this.commands = [], this.isEnabled = !0, this.handleKeyDown = (t) => {
|
|
1575
1610
|
if (!this.isEnabled) return;
|
|
1576
|
-
const
|
|
1577
|
-
if (!(
|
|
1611
|
+
const i = t.target;
|
|
1612
|
+
if (!(i.tagName === "INPUT" || i.tagName === "TEXTAREA" || i.isContentEditable)) {
|
|
1578
1613
|
for (const s of this.commands)
|
|
1579
|
-
if (this.matchesCommand(
|
|
1580
|
-
|
|
1614
|
+
if (this.matchesCommand(t, s)) {
|
|
1615
|
+
t.preventDefault(), s.handler(t);
|
|
1581
1616
|
break;
|
|
1582
1617
|
}
|
|
1583
1618
|
}
|
|
@@ -1588,8 +1623,8 @@ class X {
|
|
|
1588
1623
|
*/
|
|
1589
1624
|
register(e) {
|
|
1590
1625
|
return this.commands.push(e), () => {
|
|
1591
|
-
const
|
|
1592
|
-
|
|
1626
|
+
const t = this.commands.indexOf(e);
|
|
1627
|
+
t !== -1 && this.commands.splice(t, 1);
|
|
1593
1628
|
};
|
|
1594
1629
|
}
|
|
1595
1630
|
/**
|
|
@@ -1613,8 +1648,8 @@ class X {
|
|
|
1613
1648
|
/**
|
|
1614
1649
|
* Check if event matches a command
|
|
1615
1650
|
*/
|
|
1616
|
-
matchesCommand(e,
|
|
1617
|
-
return !(e.key.toLowerCase() !==
|
|
1651
|
+
matchesCommand(e, t) {
|
|
1652
|
+
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
1653
|
}
|
|
1619
1654
|
/**
|
|
1620
1655
|
* Cleanup
|
|
@@ -1623,31 +1658,31 @@ class X {
|
|
|
1623
1658
|
this.target.removeEventListener("keydown", this.handleKeyDown), this.commands = [];
|
|
1624
1659
|
}
|
|
1625
1660
|
}
|
|
1626
|
-
function
|
|
1661
|
+
function X() {
|
|
1627
1662
|
return /Mac|iPod|iPhone|iPad/.test(navigator.platform);
|
|
1628
1663
|
}
|
|
1629
1664
|
function G() {
|
|
1630
|
-
return
|
|
1665
|
+
return X() ? "meta" : "ctrl";
|
|
1631
1666
|
}
|
|
1632
|
-
function K(
|
|
1633
|
-
const s =
|
|
1634
|
-
return Math.min(s,
|
|
1667
|
+
function K(f, e, t, i) {
|
|
1668
|
+
const s = f / t, n = e / i;
|
|
1669
|
+
return Math.min(s, n);
|
|
1635
1670
|
}
|
|
1636
|
-
function Q(
|
|
1637
|
-
const
|
|
1671
|
+
function Q(f, e, t = 8, i = 10, s = 300) {
|
|
1672
|
+
const n = K(f, e, t, i), o = n >= s;
|
|
1638
1673
|
return {
|
|
1639
|
-
actualDPI: Math.round(
|
|
1674
|
+
actualDPI: Math.round(n),
|
|
1640
1675
|
targetDPI: s,
|
|
1641
|
-
width:
|
|
1676
|
+
width: f,
|
|
1642
1677
|
height: e,
|
|
1643
1678
|
passed: o,
|
|
1644
|
-
warning: o ? void 0 : `Image resolution is ${Math.round(
|
|
1679
|
+
warning: o ? void 0 : `Image resolution is ${Math.round(n)} DPI at 100% scale. For best print quality, use images with at least ${s} DPI. This may result in pixelated or blurry prints.`
|
|
1645
1680
|
};
|
|
1646
1681
|
}
|
|
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;
|
|
1682
|
+
const k = ':host{--editor-bg: #f3f4f6;--editor-surface: #ffffff;--editor-border: #e5e7eb;--editor-text: #111827;--editor-text-muted: #6b7280;--editor-primary: #3b82f6;--editor-primary-hover: #2563eb;--editor-danger: #ef4444;--editor-radius: 8px;--editor-radius-sm: 4px;--editor-font: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;--editor-panel-width: 280px;display:block;width:100%;height:100%;font-family:var(--editor-font);font-size:13px;color:var(--editor-text);line-height:1.4}:host([theme="dark"]){--editor-bg: #1f2937;--editor-surface: #374151;--editor-border: #4b5563;--editor-text: #f9fafb;--editor-text-muted: #9ca3af}.editor-container{display:flex;flex-direction:column;width:100%;height:100%;position:relative;overflow:hidden;background:var(--editor-bg)}.layout-desktop .editor-main{display:flex;flex:1;min-height:0}.layout-desktop .canvas-area{flex:1;min-width:0;position:relative;display:flex;flex-direction:column}.layout-desktop .area-panel{width:var(--editor-panel-width);border-left:1px solid var(--editor-border);background:var(--editor-surface);display:none;flex-direction:column;overflow:hidden}.layout-desktop .area-panel.area-panel-visible{display:flex}.layout-mobile .editor-main{display:flex;flex-direction:column;flex:1;min-height:0}.layout-mobile .canvas-area{flex:1;position:relative;display:flex;flex-direction:column;min-height:0}.layout-mobile .area-panel{position:absolute;bottom:0;left:0;right:0;max-height:50%;background:var(--editor-surface);border-top:1px solid var(--editor-border);border-radius:var(--editor-radius) var(--editor-radius) 0 0;box-shadow:0 -4px 20px #00000026;transform:translateY(100%);transition:transform .25s ease-out;z-index:20;display:flex;flex-direction:column;overflow:hidden}.layout-mobile .area-panel.area-panel-visible{transform:translateY(0)}.area-panel-backdrop{display:none}.layout-mobile .area-panel-backdrop{display:block;position:absolute;inset:0;background:#0000004d;z-index:15;opacity:0;pointer-events:none;transition:opacity .25s ease-out}.layout-mobile .area-panel-backdrop.area-panel-backdrop-visible{opacity:1;pointer-events:auto}.zoom-toolbar{display:flex;align-items:center;gap:4px;padding:8px 12px;background:var(--editor-surface);border-bottom:1px solid var(--editor-border)}.zoom-btn{display:inline-flex;align-items:center;justify-content:center;width:32px;height:32px;border:1px solid var(--editor-border);border-radius:var(--editor-radius-sm);background:var(--editor-surface);color:var(--editor-text);cursor:pointer;font-size:14px;font-family:var(--editor-font);transition:background .15s;-webkit-tap-highlight-color:transparent;touch-action:manipulation}.zoom-btn:hover{background:var(--editor-bg)}.zoom-btn:active{background:var(--editor-border)}.zoom-percent{margin-left:8px;font-size:12px;color:var(--editor-text-muted);min-width:40px}.view-select{margin-left:8px;padding:4px 8px;height:32px;border:1px solid var(--editor-border);border-radius:var(--editor-radius-sm);background:var(--editor-surface);color:var(--editor-text);font-family:var(--editor-font);font-size:12px;cursor:pointer;-webkit-tap-highlight-color:transparent}.view-select:hover{background:var(--editor-bg)}.view-select:focus{outline:2px solid var(--editor-primary);outline-offset:-1px}.zoom-toolbar-spacer{flex:1}.zoom-close-btn{width:auto;padding:0 12px;font-size:13px;gap:4px}.zoom-save-btn{width:auto;padding:0 16px;margin-left:8px;font-size:13px;font-weight:600;background:var(--editor-primary);color:#fff;border-color:var(--editor-primary)}.zoom-save-btn:hover{background:var(--editor-primary-hover)}.zoom-save-btn:disabled{opacity:.5;cursor:not-allowed}.canvas-wrapper{flex:1;position:relative;overflow:hidden;min-height:200px}.editor-canvas{position:absolute;top:0;left:0}.area-panel-header{display:flex;justify-content:space-between;align-items:center;padding:12px 16px;border-bottom:1px solid var(--editor-border);flex-shrink:0}.area-panel-title{font-weight:600;font-size:14px}.area-panel-close-btn{width:28px;height:28px;border:none;background:none;color:var(--editor-text-muted);cursor:pointer;font-size:18px;line-height:1;border-radius:var(--editor-radius-sm);display:flex;align-items:center;justify-content:center}.area-panel-close-btn:hover{background:var(--editor-bg);color:var(--editor-text)}.area-panel-content{flex:1;overflow-y:auto;padding:12px;display:flex;flex-direction:column;gap:12px}.area-panel-empty{color:var(--editor-text-muted);font-size:12px;text-align:center;padding:20px 0}.area-card{padding:12px;border:1px solid var(--editor-border);border-radius:var(--editor-radius);background:var(--editor-surface);cursor:pointer;transition:border-color .15s}.area-card:hover{border-color:var(--editor-primary)}.area-card-selected{border-color:var(--editor-primary);box-shadow:0 0 0 1px var(--editor-primary)}.area-card-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:8px}.area-card-name-row{display:flex;align-items:center;gap:6px}.area-card-name{font-weight:600;font-size:13px}.area-card-badge{display:inline-block;padding:1px 6px;font-size:10px;font-weight:500;background:#fef3c7;color:#92400e;border-radius:10px}.area-card-reset-btn{padding:2px 8px;border:1px solid var(--editor-border);border-radius:var(--editor-radius-sm);background:none;color:var(--editor-text-muted);cursor:pointer;font-size:11px;font-family:var(--editor-font)}.area-card-reset-btn:hover{background:var(--editor-bg);color:var(--editor-danger);border-color:var(--editor-danger)}.area-card-mode-switcher{display:flex;gap:4px;margin-bottom:8px}.mode-btn{flex:1;padding:4px 8px;border:1px solid var(--editor-border);border-radius:var(--editor-radius-sm);background:var(--editor-surface);color:var(--editor-text);cursor:pointer;font-size:12px;font-family:var(--editor-font);transition:all .15s}.mode-btn-active{background:var(--editor-primary);color:#fff;border-color:var(--editor-primary)}.area-input-text{width:100%;padding:6px 8px;border:1px solid var(--editor-border);border-radius:var(--editor-radius-sm);font-family:var(--editor-font);font-size:13px;color:var(--editor-text);background:var(--editor-surface);box-sizing:border-box;margin-bottom:6px}.area-input-text:focus{outline:none;border-color:var(--editor-primary);box-shadow:0 0 0 1px var(--editor-primary)}.area-input-row{display:flex;gap:6px;align-items:center;margin-bottom:6px}.area-input-font{flex:1;padding:4px 6px;border:1px solid var(--editor-border);border-radius:var(--editor-radius-sm);font-family:var(--editor-font);font-size:12px;color:var(--editor-text);background:var(--editor-surface);min-width:0}.area-input-size{width:56px;padding:4px 6px;border:1px solid var(--editor-border);border-radius:var(--editor-radius-sm);font-family:var(--editor-font);font-size:12px;color:var(--editor-text);background:var(--editor-surface)}.area-input-align{display:flex;gap:2px}.align-btn{width:28px;height:28px;border:1px solid var(--editor-border);border-radius:var(--editor-radius-sm);background:var(--editor-surface);color:var(--editor-text);cursor:pointer;font-size:12px;font-weight:600;font-family:var(--editor-font)}.align-btn-active{background:var(--editor-primary);color:#fff;border-color:var(--editor-primary)}.area-input-color{width:32px;height:28px;padding:0;border:1px solid var(--editor-border);border-radius:var(--editor-radius-sm);cursor:pointer;background:none}.area-upload-btn{width:100%;padding:10px;border:2px dashed var(--editor-border);border-radius:var(--editor-radius);background:var(--editor-bg);color:var(--editor-text-muted);cursor:pointer;font-family:var(--editor-font);font-size:12px;text-align:center;transition:all .15s;min-height:44px}.area-upload-btn:hover{border-color:var(--editor-primary);color:var(--editor-primary)}.area-image-preview{display:flex;gap:8px;align-items:center}.area-image-thumb{width:48px;height:48px;object-fit:cover;border-radius:var(--editor-radius-sm);border:1px solid var(--editor-border)}.area-image-info{flex:1;min-width:0;display:flex;flex-direction:column;gap:4px}.area-image-name{font-size:12px;color:var(--editor-text-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.area-image-remove-btn{padding:2px 8px;border:1px solid var(--editor-danger);border-radius:var(--editor-radius-sm);background:none;color:var(--editor-danger);cursor:pointer;font-size:11px;font-family:var(--editor-font);width:fit-content}.area-image-remove-btn:hover{background:#fef2f2}.area-card-divider{height:1px;background:var(--editor-border);margin:8px 0}.transform-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:6px}.transform-title{font-weight:600;font-size:12px}.transform-reset-btn{padding:2px 8px;border:1px solid var(--editor-border);border-radius:var(--editor-radius-sm);background:none;color:var(--editor-text-muted);cursor:pointer;font-size:11px;font-family:var(--editor-font)}.transform-reset-btn:hover{background:var(--editor-bg)}.transform-label{font-size:11px;color:var(--editor-text-muted);white-space:nowrap}.transform-input{width:60px;padding:3px 6px;border:1px solid var(--editor-border);border-radius:var(--editor-radius-sm);font-family:var(--editor-font);font-size:12px;color:var(--editor-text);background:var(--editor-surface)}.transform-slider-row{flex-direction:column;align-items:stretch}.transform-slider{width:100%;accent-color:var(--editor-primary)}.area-image-preview{position:relative}.area-upload-spinner-overlay{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background:#fff9;border-radius:var(--editor-radius-sm);z-index:1}:host([theme="dark"]) .area-upload-spinner-overlay{background:#37415199}.area-upload-spinner{width:16px;height:16px;border:2px solid var(--editor-border);border-top-color:var(--editor-primary);border-radius:50%;animation:spin .8s linear infinite}.loading-container{display:flex;justify-content:center;align-items:center;width:100%;height:100%;min-height:200px}.loading-spinner{width:32px;height:32px;border:3px solid var(--editor-border);border-top-color:var(--editor-primary);border-radius:50%;animation:spin .8s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}.error-container{display:flex;flex-direction:column;justify-content:center;align-items:center;width:100%;height:100%;min-height:200px;padding:20px;text-align:center}.error-title{font-weight:600;font-size:16px;color:var(--editor-danger);margin-bottom:8px}.error-message{font-size:13px;color:var(--editor-text-muted);max-width:400px;white-space:pre-wrap}:host(:focus){outline:2px solid var(--editor-primary);outline-offset:-2px}input:focus,select:focus,button:focus-visible{outline:2px solid var(--editor-primary);outline-offset:-1px}.context-menu{position:absolute;z-index:100;min-width:160px;background:var(--editor-surface);border:1px solid var(--editor-border);border-radius:var(--editor-radius);box-shadow:0 4px 12px #00000026;padding:4px 0;font-size:13px}.context-menu-header{padding:6px 12px;font-weight:600;font-size:12px;color:var(--editor-text-muted);user-select:none}.context-menu-divider{height:1px;background:var(--editor-border);margin:4px 0}.context-menu-item{padding:6px 12px;cursor:pointer;color:var(--editor-text);user-select:none;transition:background .1s}.context-menu-item:hover{background:var(--editor-bg)}.context-menu-item:active{background:var(--editor-border)}.context-menu-item-disabled{color:var(--editor-text-muted);cursor:default;pointer-events:none}.editor-toast{position:absolute;top:16px;left:50%;transform:translate(-50%);padding:10px 20px;border-radius:var(--editor-radius);font-size:13px;font-family:var(--editor-font);line-height:1.4;z-index:200;max-width:400px;text-align:center;animation:toast-in .25s ease-out,toast-out .25s ease-in forwards;animation-delay:0s,var(--toast-duration, 3.75s);pointer-events:none;box-shadow:0 4px 12px #00000026}.editor-toast-warning{background:#fef3c7;color:#92400e;border:1px solid #f59e0b}.editor-toast-error{background:#fef2f2;color:#991b1b;border:1px solid var(--editor-danger)}.editor-toast-info{background:#eff6ff;color:#1e40af;border:1px solid var(--editor-primary)}@keyframes toast-in{0%{opacity:0;transform:translate(-50%) translateY(-10px)}to{opacity:1;transform:translate(-50%) translateY(0)}}@keyframes toast-out{0%{opacity:1;transform:translate(-50%) translateY(0)}to{opacity:0;transform:translate(-50%) translateY(-10px)}}.editor-modal-overlay{position:absolute;inset:0;background:#0006;z-index:300;display:flex;align-items:center;justify-content:center;animation:modal-overlay-in .2s ease-out}@keyframes modal-overlay-in{0%{opacity:0}to{opacity:1}}.editor-modal{background:var(--editor-surface);border-radius:var(--editor-radius);box-shadow:0 8px 32px #0003;max-width:380px;width:calc(100% - 32px);padding:20px;animation:modal-in .2s ease-out}@keyframes modal-in{0%{opacity:0;transform:scale(.95)}to{opacity:1;transform:scale(1)}}.editor-modal-title{font-weight:600;font-size:15px;margin-bottom:8px;color:var(--editor-text)}.editor-modal-body{font-size:13px;color:var(--editor-text-muted);margin-bottom:16px;line-height:1.5}.editor-modal-actions{display:flex;justify-content:flex-end;gap:8px}.editor-modal-btn{padding:8px 16px;border-radius:var(--editor-radius-sm);font-size:13px;font-family:var(--editor-font);cursor:pointer;border:1px solid var(--editor-border);background:var(--editor-surface);color:var(--editor-text);transition:background .15s}.editor-modal-btn:hover{background:var(--editor-bg)}.editor-modal-btn-primary{background:var(--editor-primary);color:#fff;border-color:var(--editor-primary)}.editor-modal-btn-primary:hover{background:var(--editor-primary-hover)}.area-validation-error{font-size:11px;color:var(--editor-danger);margin-top:4px}@media(pointer:coarse){.zoom-toolbar{padding:6px 8px;gap:2px}.zoom-btn{min-width:44px;min-height:44px}.zoom-percent{display:none}.view-select{max-width:110px;text-overflow:ellipsis;overflow:hidden;min-height:44px;font-size:16px}.zoom-close-btn,.zoom-save-btn{padding:0 8px}.area-input-text,.area-input-font,.area-input-size{min-height:44px;font-size:16px}.align-btn{min-width:44px;min-height:44px}.area-upload-btn{min-height:48px}}', _ = "2.0.0", ee = 768;
|
|
1648
1683
|
class I extends HTMLElement {
|
|
1649
1684
|
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 = q(() => {
|
|
1685
|
+
super(), this.resizeObserver = null, this.pendingUploads = /* @__PURE__ */ new Map(), this.isReady = !1, this.currentTemplate = null, this.loadingTemplateId = null, this.isMobileLayout = !1, this.productViews = [], this.activeViewName = null, this.perViewContents = /* @__PURE__ */ new Map(), this.subscriptions = [], this.renderAbortController = null, this.contentManagerUnsub = null, this.refitRafId = null, this.storageUsage = null, this.storageUsageLastRefresh = 0, this.emitChange = q(() => {
|
|
1651
1686
|
this.dispatchEvent(
|
|
1652
1687
|
new CustomEvent("change", {
|
|
1653
1688
|
detail: { design: this.getDesign() },
|
|
@@ -1670,8 +1705,8 @@ class I extends HTMLElement {
|
|
|
1670
1705
|
disconnectedCallback() {
|
|
1671
1706
|
this.cleanup();
|
|
1672
1707
|
}
|
|
1673
|
-
attributeChangedCallback(e,
|
|
1674
|
-
|
|
1708
|
+
attributeChangedCallback(e, t, i) {
|
|
1709
|
+
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
1710
|
}
|
|
1676
1711
|
// ─── Public API ───
|
|
1677
1712
|
getDesign() {
|
|
@@ -1727,12 +1762,12 @@ class I extends HTMLElement {
|
|
|
1727
1762
|
}
|
|
1728
1763
|
setActiveView(e) {
|
|
1729
1764
|
if (this.activeViewName === e) return;
|
|
1730
|
-
const
|
|
1731
|
-
if (!
|
|
1732
|
-
this.switchToView(
|
|
1765
|
+
const t = this.productViews.find((i) => i.viewName === e);
|
|
1766
|
+
if (!t) throw new Error(`View not found: ${e}`);
|
|
1767
|
+
this.switchToView(t);
|
|
1733
1768
|
}
|
|
1734
|
-
setAreaContent(e,
|
|
1735
|
-
|
|
1769
|
+
setAreaContent(e, t) {
|
|
1770
|
+
t.type === "text" ? this.contentManager.setTextContent(e, t) : this.contentManager.setImageContent(e, t.dataUrl, t.filename), this.syncEngineContents(), this.saveContentState();
|
|
1736
1771
|
}
|
|
1737
1772
|
getAreaContent(e) {
|
|
1738
1773
|
return this.contentManager.getContent(e);
|
|
@@ -1744,112 +1779,95 @@ class I extends HTMLElement {
|
|
|
1744
1779
|
const e = [];
|
|
1745
1780
|
if (this.productViews.length > 1) {
|
|
1746
1781
|
this.activeViewName && this.contentManager && this.perViewContents.set(this.activeViewName, this.contentManager.toJSON());
|
|
1747
|
-
for (const
|
|
1748
|
-
const
|
|
1749
|
-
if (
|
|
1750
|
-
e.push(`View "${
|
|
1782
|
+
for (const t of this.productViews) {
|
|
1783
|
+
const i = this.perViewContents.get(t.viewName), s = i?.some(([, o]) => I.hasEditorContent(o)) ?? !1;
|
|
1784
|
+
if (t.isRequired && !s) {
|
|
1785
|
+
e.push(`View "${t.viewName}" is required`);
|
|
1751
1786
|
continue;
|
|
1752
1787
|
}
|
|
1753
|
-
const
|
|
1754
|
-
for (const o of
|
|
1788
|
+
const n = t.template.templateJson.areas || [];
|
|
1789
|
+
for (const o of n) {
|
|
1755
1790
|
if (!o.required) continue;
|
|
1756
|
-
const
|
|
1757
|
-
|
|
1791
|
+
const a = i?.find(([r]) => r === o.id);
|
|
1792
|
+
a && I.hasEditorContent(a[1]) || e.push(`"${o.name}" in view "${t.viewName}" is required`);
|
|
1758
1793
|
}
|
|
1759
1794
|
}
|
|
1760
1795
|
} else {
|
|
1761
|
-
const
|
|
1762
|
-
for (const s of
|
|
1796
|
+
const t = this.currentTemplate?.areas || [], i = this.contentManager.getContents();
|
|
1797
|
+
for (const s of t) {
|
|
1763
1798
|
if (!s.required) continue;
|
|
1764
|
-
const
|
|
1765
|
-
|
|
1799
|
+
const n = i.get(s.id);
|
|
1800
|
+
n && I.hasEditorContent(n) || e.push(`"${s.name}" is required`);
|
|
1766
1801
|
}
|
|
1767
1802
|
}
|
|
1768
1803
|
return e;
|
|
1769
1804
|
}
|
|
1770
1805
|
showValidationErrors(e) {
|
|
1771
|
-
return new Promise((
|
|
1772
|
-
const
|
|
1773
|
-
s.appendChild(
|
|
1774
|
-
const
|
|
1806
|
+
return new Promise((t) => {
|
|
1807
|
+
const i = h("div", { class: "editor-modal-overlay" }), s = h("div", { class: "editor-modal" });
|
|
1808
|
+
s.appendChild(h("div", { class: "editor-modal-title" }, "Required content missing"));
|
|
1809
|
+
const n = h("div", { class: "editor-modal-body" }), o = document.createElement("ul");
|
|
1775
1810
|
o.style.margin = "8px 0", o.style.paddingLeft = "20px";
|
|
1776
1811
|
for (const d of e) {
|
|
1777
1812
|
const l = document.createElement("li");
|
|
1778
1813
|
l.textContent = d, o.appendChild(l);
|
|
1779
1814
|
}
|
|
1780
|
-
|
|
1781
|
-
const
|
|
1815
|
+
n.appendChild(o), s.appendChild(n);
|
|
1816
|
+
const a = h("div", { class: "editor-modal-actions" }), r = h("button", { class: "editor-modal-btn editor-modal-btn-primary" }, "OK");
|
|
1782
1817
|
r.addEventListener("click", () => {
|
|
1783
|
-
|
|
1784
|
-
}),
|
|
1785
|
-
d.target ===
|
|
1786
|
-
}), this.shadow.appendChild(
|
|
1818
|
+
i.remove(), t();
|
|
1819
|
+
}), a.appendChild(r), s.appendChild(a), i.appendChild(s), i.addEventListener("click", (d) => {
|
|
1820
|
+
d.target === i && (i.remove(), t());
|
|
1821
|
+
}), this.shadow.appendChild(i);
|
|
1787
1822
|
});
|
|
1788
1823
|
}
|
|
1789
1824
|
async finalize() {
|
|
1790
1825
|
const e = this.validateRequiredContent();
|
|
1791
1826
|
if (e.length > 0)
|
|
1792
1827
|
return await this.showValidationErrors(e), null;
|
|
1793
|
-
const
|
|
1794
|
-
if (
|
|
1795
|
-
const o = this.currentTemplate?.areas || [],
|
|
1828
|
+
const t = this.engine.checkSafeAreaViolations();
|
|
1829
|
+
if (t.length > 0) {
|
|
1830
|
+
const o = this.currentTemplate?.areas || [], a = t.map((r) => o.find((d) => d.id === r)?.name || r).join(", ");
|
|
1796
1831
|
if (!await this.showConfirmation(
|
|
1797
1832
|
"Content outside safe area",
|
|
1798
|
-
`Content in ${
|
|
1833
|
+
`Content in ${a} extends beyond the safe area. It may be trimmed during printing. Continue?`,
|
|
1799
1834
|
"Cancel",
|
|
1800
1835
|
"Proceed Anyway"
|
|
1801
1836
|
))
|
|
1802
1837
|
throw new Error("Finalization cancelled by user");
|
|
1803
1838
|
}
|
|
1804
|
-
const
|
|
1805
|
-
if (this.productViews.length > 1) {
|
|
1839
|
+
const i = this.getAttribute("store-id") || "demo-shop";
|
|
1840
|
+
if (this.pendingUploads.size > 0 && await Promise.allSettled(this.pendingUploads.values()), this.productViews.length > 1) {
|
|
1806
1841
|
this.activeViewName && this.contentManager && this.perViewContents.set(this.activeViewName, this.contentManager.toJSON());
|
|
1807
1842
|
const o = [];
|
|
1808
|
-
for (const
|
|
1809
|
-
const
|
|
1810
|
-
!
|
|
1811
|
-
templateId:
|
|
1812
|
-
viewName:
|
|
1813
|
-
contents:
|
|
1843
|
+
for (const c of this.productViews) {
|
|
1844
|
+
const p = this.perViewContents.get(c.viewName);
|
|
1845
|
+
!p || p.length === 0 || !p.some(([, u]) => I.hasEditorContent(u)) || o.push({
|
|
1846
|
+
templateId: c.template.id,
|
|
1847
|
+
viewName: c.viewName,
|
|
1848
|
+
contents: p
|
|
1814
1849
|
});
|
|
1815
1850
|
}
|
|
1816
1851
|
if (o.length === 0)
|
|
1817
1852
|
throw new Error("No views have content to finalize");
|
|
1818
|
-
const
|
|
1819
|
-
if (!
|
|
1853
|
+
const a = this.getAttribute("product-id");
|
|
1854
|
+
if (!a)
|
|
1820
1855
|
throw new Error("product-id attribute is required for multi-view finalize");
|
|
1821
1856
|
const r = await this.apiClient.finalizeMultiView({
|
|
1822
|
-
productId:
|
|
1857
|
+
productId: a,
|
|
1823
1858
|
designs: o
|
|
1824
|
-
})
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
await new Promise((f) => setTimeout(f, u)), m++;
|
|
1830
|
-
const g = await this.apiClient.pollMultiViewStatus(h);
|
|
1831
|
-
if (g.allCompleted) {
|
|
1832
|
-
for (const f of g.designs) {
|
|
1833
|
-
const b = r.views.find((w) => w.designId === f.designId);
|
|
1834
|
-
b && (b.status = f.status, b.proofUrl = f.proofUrl, b.errorMessage = f.errorMessage);
|
|
1835
|
-
}
|
|
1836
|
-
r.allCompleted = !0;
|
|
1837
|
-
break;
|
|
1838
|
-
}
|
|
1839
|
-
}
|
|
1840
|
-
}
|
|
1841
|
-
const d = r.views.map((h) => ({
|
|
1842
|
-
viewName: h.viewName,
|
|
1843
|
-
designId: h.designId,
|
|
1844
|
-
proofUrl: h.proofUrl || "",
|
|
1845
|
-
status: h.status
|
|
1859
|
+
}), d = r.views.map((c) => ({
|
|
1860
|
+
viewName: c.viewName,
|
|
1861
|
+
designId: c.designId,
|
|
1862
|
+
proofUrl: c.proofUrl || "",
|
|
1863
|
+
status: c.status
|
|
1846
1864
|
})), l = d[0];
|
|
1847
1865
|
return this.dispatchEvent(
|
|
1848
1866
|
new CustomEvent("customizer:finalize", {
|
|
1849
1867
|
detail: {
|
|
1850
1868
|
designId: l.designId,
|
|
1851
1869
|
proofUrl: l.proofUrl,
|
|
1852
|
-
status:
|
|
1870
|
+
status: "processing",
|
|
1853
1871
|
views: d,
|
|
1854
1872
|
requestId: r.requestId
|
|
1855
1873
|
},
|
|
@@ -1858,57 +1876,104 @@ class I extends HTMLElement {
|
|
|
1858
1876
|
})
|
|
1859
1877
|
), { ...l, views: d };
|
|
1860
1878
|
}
|
|
1861
|
-
const s = this.getDesign(),
|
|
1879
|
+
const s = this.getDesign(), n = await this.apiClient.finalizeDesign(i, s);
|
|
1862
1880
|
return this.dispatchEvent(
|
|
1863
1881
|
new CustomEvent("customizer:finalize", {
|
|
1864
1882
|
detail: {
|
|
1865
|
-
designId:
|
|
1866
|
-
proofUrl:
|
|
1883
|
+
designId: n.designId,
|
|
1884
|
+
proofUrl: n.proofUrl,
|
|
1867
1885
|
templateId: s.templateId,
|
|
1868
1886
|
designJson: s,
|
|
1869
|
-
status:
|
|
1887
|
+
status: n.status
|
|
1870
1888
|
},
|
|
1871
1889
|
bubbles: !0,
|
|
1872
1890
|
composed: !0
|
|
1873
1891
|
})
|
|
1892
|
+
), n;
|
|
1893
|
+
}
|
|
1894
|
+
async waitForResult(e, t) {
|
|
1895
|
+
const i = t?.pollInterval ?? 1500, s = t?.maxPolls ?? 40, n = t?.signal;
|
|
1896
|
+
let o = 0;
|
|
1897
|
+
for (; o < s; ) {
|
|
1898
|
+
if (n?.aborted)
|
|
1899
|
+
throw new DOMException("Polling aborted", "AbortError");
|
|
1900
|
+
await new Promise((d, l) => {
|
|
1901
|
+
const c = setTimeout(d, i);
|
|
1902
|
+
n?.addEventListener("abort", () => {
|
|
1903
|
+
clearTimeout(c), l(new DOMException("Polling aborted", "AbortError"));
|
|
1904
|
+
}, { once: !0 });
|
|
1905
|
+
}), o++;
|
|
1906
|
+
let r;
|
|
1907
|
+
if (e.length === 1) {
|
|
1908
|
+
const d = await this.apiClient.getDesignStatus(e[0]);
|
|
1909
|
+
r = {
|
|
1910
|
+
designs: [d],
|
|
1911
|
+
allCompleted: d.status !== "processing"
|
|
1912
|
+
};
|
|
1913
|
+
} else
|
|
1914
|
+
r = await this.apiClient.pollMultiViewStatus(e);
|
|
1915
|
+
if (r.allCompleted)
|
|
1916
|
+
return this.dispatchEvent(
|
|
1917
|
+
new CustomEvent("customizer:render-complete", {
|
|
1918
|
+
detail: r,
|
|
1919
|
+
bubbles: !0,
|
|
1920
|
+
composed: !0
|
|
1921
|
+
})
|
|
1922
|
+
), r;
|
|
1923
|
+
}
|
|
1924
|
+
const a = {
|
|
1925
|
+
designs: e.map((r) => ({
|
|
1926
|
+
designId: r,
|
|
1927
|
+
status: "failed",
|
|
1928
|
+
proofUrl: null,
|
|
1929
|
+
errorMessage: "Render timed out"
|
|
1930
|
+
})),
|
|
1931
|
+
allCompleted: !0
|
|
1932
|
+
};
|
|
1933
|
+
return this.dispatchEvent(
|
|
1934
|
+
new CustomEvent("customizer:render-complete", {
|
|
1935
|
+
detail: a,
|
|
1936
|
+
bubbles: !0,
|
|
1937
|
+
composed: !0
|
|
1938
|
+
})
|
|
1874
1939
|
), a;
|
|
1875
1940
|
}
|
|
1876
1941
|
// ─── Initialization ───
|
|
1877
1942
|
renderLoading() {
|
|
1878
1943
|
const e = document.createElement("style");
|
|
1879
1944
|
e.textContent = k, this.shadow.appendChild(e);
|
|
1880
|
-
const
|
|
1945
|
+
const t = h(
|
|
1881
1946
|
"div",
|
|
1882
1947
|
{ class: "loading-container" },
|
|
1883
|
-
|
|
1948
|
+
h("div", { class: "loading-spinner" })
|
|
1884
1949
|
);
|
|
1885
|
-
this.shadow.appendChild(
|
|
1950
|
+
this.shadow.appendChild(t);
|
|
1886
1951
|
}
|
|
1887
1952
|
async initialize() {
|
|
1888
1953
|
const e = this.getAttribute("api-url") || "http://localhost:4000";
|
|
1889
1954
|
this.apiClient = new B(`${e.replace(/\/+$/, "")}/public`);
|
|
1890
|
-
const
|
|
1891
|
-
if (!
|
|
1955
|
+
const t = this.getAttribute("product-id"), i = this.getAttribute("template-id");
|
|
1956
|
+
if (!t && !i)
|
|
1892
1957
|
throw new Error("Either template-id or product-id attribute is required");
|
|
1893
|
-
|
|
1958
|
+
t ? await this.loadProduct(t) : await this.loadTemplate(i), this.isReady = !0, this.dispatchEvent(new CustomEvent("ready", { bubbles: !0, composed: !0 }));
|
|
1894
1959
|
}
|
|
1895
1960
|
async loadTemplate(e) {
|
|
1896
1961
|
if (this.loadingTemplateId !== e) {
|
|
1897
1962
|
this.loadingTemplateId = e;
|
|
1898
1963
|
try {
|
|
1899
|
-
const
|
|
1964
|
+
const t = await this.apiClient.getTemplate(e);
|
|
1900
1965
|
if (this.loadingTemplateId !== e) return;
|
|
1901
|
-
await this.loadTemplateData(
|
|
1902
|
-
} catch (
|
|
1903
|
-
throw this.loadingTemplateId = null, new Error(`Failed to load template: ${
|
|
1966
|
+
await this.loadTemplateData(t), this.loadingTemplateId = null;
|
|
1967
|
+
} catch (t) {
|
|
1968
|
+
throw this.loadingTemplateId = null, new Error(`Failed to load template: ${t.message}`);
|
|
1904
1969
|
}
|
|
1905
1970
|
}
|
|
1906
1971
|
}
|
|
1907
1972
|
async loadTemplateData(e) {
|
|
1908
1973
|
this.currentTemplate = e;
|
|
1909
|
-
const
|
|
1910
|
-
this.contentManager = new P(
|
|
1911
|
-
const
|
|
1974
|
+
const t = e.areas || [];
|
|
1975
|
+
this.contentManager = new P(t);
|
|
1976
|
+
const i = {
|
|
1912
1977
|
template: e,
|
|
1913
1978
|
contents: this.contentManager.toJSON(),
|
|
1914
1979
|
selectedAreaId: null,
|
|
@@ -1917,65 +1982,62 @@ class I extends HTMLElement {
|
|
|
1917
1982
|
isDirty: !1,
|
|
1918
1983
|
warnings: []
|
|
1919
1984
|
};
|
|
1920
|
-
this.stateManager = new
|
|
1985
|
+
this.stateManager = new F(i), this.buildUI(e, t), this.engine = new N(this.canvas), this.engine.setArtboard({
|
|
1921
1986
|
width: e.artboard.width,
|
|
1922
1987
|
height: e.artboard.height,
|
|
1923
1988
|
backgroundColor: e.artboard.backgroundColor ?? "#ffffff"
|
|
1924
|
-
}), this.engine.setAreas(
|
|
1989
|
+
}), this.engine.setAreas(t), this.engine.setContents(this.contentManager.getContents()), e.backgroundImage && this.engine.setBackgroundImage(e.backgroundImage);
|
|
1925
1990
|
const s = e.artboard.safeArea ?? e.safeArea;
|
|
1926
|
-
s && this.engine.setSafeArea(s), this.engine.start(), this.
|
|
1927
|
-
zoom: this.engine.getZoom(),
|
|
1928
|
-
pan: this.engine.getPan()
|
|
1929
|
-
}), this.zoomToolbar.setZoom(this.engine.getZoom()), this.interaction = new F(
|
|
1991
|
+
s && this.engine.setSafeArea(s), this.engine.start(), this.scheduleCanvasRefit(), this.interaction = new $(
|
|
1930
1992
|
this.canvas,
|
|
1931
1993
|
this.engine,
|
|
1932
1994
|
() => this.contentManager.getContents(),
|
|
1933
1995
|
() => 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) => {
|
|
1996
|
+
), this.wireInteractionEvents(), this.contentManagerUnsub = this.contentManager.subscribe(() => {
|
|
1997
|
+
this.syncEngineContents(), this.inputPanel.setContents(this.contentManager.getContents());
|
|
1998
|
+
}), this.subscriptions.push(this.contentManagerUnsub), this.keyboard = new W(this), this.setupKeyboardShortcuts(), this.inputPanel.setContents(this.contentManager.getContents());
|
|
1999
|
+
const n = this.getAttribute("store-id");
|
|
2000
|
+
n && this.apiClient.getStorageUsage(n).then((o) => {
|
|
1941
2001
|
this.storageUsage = o, o.usagePercent >= 100 ? this.showToast("Storage quota exceeded. Upgrade your plan for more storage.", "error") : o.usagePercent >= 80 && this.showToast(`Storage ${o.usagePercent}% full. Consider upgrading your plan.`, "warning");
|
|
1942
2002
|
}).catch(() => {
|
|
1943
2003
|
});
|
|
1944
2004
|
}
|
|
1945
2005
|
async loadProduct(e) {
|
|
1946
2006
|
try {
|
|
1947
|
-
const
|
|
1948
|
-
if (
|
|
2007
|
+
const t = await this.apiClient.getProduct(e);
|
|
2008
|
+
if (t.views.length === 0)
|
|
1949
2009
|
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: ${
|
|
2010
|
+
this.productViews = t.views, this.perViewContents.clear();
|
|
2011
|
+
const i = t.views.find((s) => s.isDefault) || t.views[0];
|
|
2012
|
+
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));
|
|
2013
|
+
} catch (t) {
|
|
2014
|
+
throw new Error(`Failed to load product: ${t.message}`);
|
|
1955
2015
|
}
|
|
1956
2016
|
}
|
|
1957
2017
|
switchToView(e) {
|
|
1958
2018
|
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(
|
|
2019
|
+
const t = e.template.templateJson;
|
|
2020
|
+
this.currentTemplate = t;
|
|
2021
|
+
const i = t.areas || [];
|
|
2022
|
+
this.contentManager = new P(i), this.contentManagerUnsub && this.contentManagerUnsub(), this.contentManagerUnsub = this.contentManager.subscribe(() => {
|
|
2023
|
+
this.syncEngineContents(), this.inputPanel.setContents(this.contentManager.getContents());
|
|
2024
|
+
});
|
|
1963
2025
|
const s = this.perViewContents.get(e.viewName);
|
|
1964
2026
|
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:
|
|
2027
|
+
width: t.artboard.width,
|
|
2028
|
+
height: t.artboard.height,
|
|
2029
|
+
backgroundColor: t.artboard.backgroundColor ?? "#ffffff"
|
|
2030
|
+
}), this.engine.setAreas(i), this.engine.setContents(this.contentManager.getContents()), this.engine.setBackgroundImage(t.backgroundImage ?? void 0);
|
|
2031
|
+
const n = t.artboard.safeArea ?? t.safeArea;
|
|
2032
|
+
this.engine.setSafeArea(n ?? null), this.engine.fitToView(), this.stateManager.setState({
|
|
2033
|
+
template: t,
|
|
1972
2034
|
contents: this.contentManager.toJSON(),
|
|
1973
2035
|
selectedAreaId: null,
|
|
1974
2036
|
isDirty: this.stateManager.getState().isDirty,
|
|
1975
2037
|
zoom: this.engine.getZoom(),
|
|
1976
2038
|
pan: this.engine.getPan(),
|
|
1977
2039
|
warnings: []
|
|
1978
|
-
}), this.zoomToolbar.setZoom(this.engine.getZoom()), this.inputPanel.setAreas(
|
|
2040
|
+
}), 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
2041
|
new CustomEvent("view-change", {
|
|
1980
2042
|
detail: { viewName: e.viewName },
|
|
1981
2043
|
bubbles: !0,
|
|
@@ -1983,44 +2045,48 @@ class I extends HTMLElement {
|
|
|
1983
2045
|
})
|
|
1984
2046
|
);
|
|
1985
2047
|
}
|
|
1986
|
-
buildUI(e,
|
|
2048
|
+
buildUI(e, t) {
|
|
1987
2049
|
this.shadow.innerHTML = "";
|
|
1988
|
-
const
|
|
1989
|
-
|
|
1990
|
-
const
|
|
1991
|
-
|
|
2050
|
+
const i = document.createElement("style");
|
|
2051
|
+
i.textContent = k, this.shadow.appendChild(i), this.container = h("div", { class: "editor-container" }), this.zoomToolbar = new H(), this.subscriptions.push(this.zoomToolbar.on("zoom-in", () => this.handleZoomIn())), this.subscriptions.push(this.zoomToolbar.on("zoom-out", () => this.handleZoomOut())), this.subscriptions.push(this.zoomToolbar.on("zoom-fit", () => this.handleZoomFit())), this.subscriptions.push(this.zoomToolbar.on("view-change", ({ viewName: o }) => {
|
|
2052
|
+
const a = this.productViews.find((r) => r.viewName === o);
|
|
2053
|
+
a && this.switchToView(a);
|
|
1992
2054
|
})), this.subscriptions.push(this.zoomToolbar.on("close", () => {
|
|
1993
2055
|
this.dispatchEvent(new CustomEvent("customizer:close", {
|
|
1994
2056
|
bubbles: !0,
|
|
1995
2057
|
composed: !0
|
|
1996
2058
|
}));
|
|
1997
2059
|
})), this.subscriptions.push(this.zoomToolbar.on("save", async () => {
|
|
1998
|
-
this.zoomToolbar.setSaveDisabled(!0, "Saving...");
|
|
2060
|
+
this.pendingUploads.size > 0 && (this.zoomToolbar.setSaveDisabled(!0, "Uploading..."), await Promise.allSettled(this.pendingUploads.values())), this.zoomToolbar.setSaveDisabled(!0, "Saving...");
|
|
1999
2061
|
try {
|
|
2000
2062
|
const o = await this.finalize();
|
|
2001
2063
|
if (!o) {
|
|
2002
|
-
this.zoomToolbar.setSaveDisabled(!1, "
|
|
2064
|
+
this.zoomToolbar.setSaveDisabled(!1, "✓");
|
|
2003
2065
|
return;
|
|
2004
2066
|
}
|
|
2005
2067
|
this.zoomToolbar.setSaveDisabled(!0, "Saved!"), this.showToast("Design saved successfully!", "info"), this.dispatchEvent(new CustomEvent("customizer:save", {
|
|
2006
2068
|
detail: o,
|
|
2007
2069
|
bubbles: !0,
|
|
2008
2070
|
composed: !0
|
|
2009
|
-
}))
|
|
2010
|
-
|
|
2071
|
+
}));
|
|
2072
|
+
const a = o.views ? o.views.map((r) => r.designId) : [o.designId];
|
|
2073
|
+
this.renderAbortController?.abort(), this.renderAbortController = new AbortController(), this.waitForResult(a, { signal: this.renderAbortController.signal }).catch((r) => {
|
|
2074
|
+
r instanceof DOMException && r.name === "AbortError" || console.error("Customizer: background render polling failed", r);
|
|
2075
|
+
}), setTimeout(() => {
|
|
2076
|
+
this.zoomToolbar.setSaveDisabled(!1, "✓"), this.zoomToolbar.showSaveButton(!1), this.stateManager.update({ isDirty: !1 });
|
|
2011
2077
|
}, 2e3);
|
|
2012
2078
|
} catch (o) {
|
|
2013
2079
|
console.error("Customizer: finalize failed", o);
|
|
2014
|
-
const
|
|
2015
|
-
this.showToast(
|
|
2080
|
+
const a = o instanceof Error ? o.message : "Save failed. Please try again.";
|
|
2081
|
+
this.showToast(a, "error"), this.zoomToolbar.setSaveDisabled(!1, "✓");
|
|
2016
2082
|
}
|
|
2017
2083
|
})), 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
|
-
for (const
|
|
2023
|
-
|
|
2084
|
+
const s = h("div", { class: "canvas-area" });
|
|
2085
|
+
s.appendChild(this.zoomToolbar.getElement()), this.canvasWrapper = h("div", { class: "canvas-wrapper" }), this.canvas = document.createElement("canvas"), this.canvas.className = "editor-canvas", this.canvasWrapper.appendChild(this.canvas), s.appendChild(this.canvasWrapper), this.inputPanel = new J(t), this.wireInputPanelEvents();
|
|
2086
|
+
const n = h("div", { class: "editor-main" });
|
|
2087
|
+
n.appendChild(s), n.appendChild(this.inputPanel.getBackdrop()), n.appendChild(this.inputPanel.getElement()), this.container.appendChild(n), this.shadow.appendChild(this.container), this.resizeObserver = new ResizeObserver((o) => {
|
|
2088
|
+
for (const a of o)
|
|
2089
|
+
a.target === this.canvasWrapper && this.handleCanvasResize(a.contentRect), a.target === this.container && this.handleLayoutResize(a.contentRect);
|
|
2024
2090
|
}), this.resizeObserver.observe(this.canvasWrapper), this.resizeObserver.observe(this.container);
|
|
2025
2091
|
}
|
|
2026
2092
|
// ─── Event Wiring ───
|
|
@@ -2030,22 +2096,22 @@ class I extends HTMLElement {
|
|
|
2030
2096
|
this.handleAreaSelect(e);
|
|
2031
2097
|
})
|
|
2032
2098
|
), this.subscriptions.push(
|
|
2033
|
-
this.interaction.on("content:drag", ({ areaId: e, offset:
|
|
2034
|
-
this.contentManager.setContentOffset(e,
|
|
2099
|
+
this.interaction.on("content:drag", ({ areaId: e, offset: t }) => {
|
|
2100
|
+
this.contentManager.setContentOffset(e, t), this.saveContentState();
|
|
2035
2101
|
})
|
|
2036
2102
|
), this.subscriptions.push(
|
|
2037
|
-
this.interaction.on("content:scale", ({ areaId: e, scale:
|
|
2038
|
-
this.contentManager.setImageScale(e,
|
|
2103
|
+
this.interaction.on("content:scale", ({ areaId: e, scale: t }) => {
|
|
2104
|
+
this.contentManager.setImageScale(e, t), this.saveContentState();
|
|
2039
2105
|
})
|
|
2040
2106
|
), this.subscriptions.push(
|
|
2041
|
-
this.interaction.on("content:rotate", ({ areaId: e, rotation:
|
|
2042
|
-
this.contentManager.setImageRotation(e,
|
|
2107
|
+
this.interaction.on("content:rotate", ({ areaId: e, rotation: t }) => {
|
|
2108
|
+
this.contentManager.setImageRotation(e, t), this.saveContentState();
|
|
2043
2109
|
})
|
|
2044
2110
|
), this.subscriptions.push(
|
|
2045
2111
|
this.interaction.on("zoom", ({ zoom: e }) => {
|
|
2046
2112
|
this.engine.setZoom(e);
|
|
2047
|
-
const
|
|
2048
|
-
|
|
2113
|
+
const t = this.engine.getCenteredPan(e);
|
|
2114
|
+
t && this.engine.setPan(t), this.stateManager.update({ zoom: this.engine.getZoom(), pan: this.engine.getPan() }), this.zoomToolbar.setZoom(this.engine.getZoom());
|
|
2049
2115
|
})
|
|
2050
2116
|
), this.subscriptions.push(
|
|
2051
2117
|
this.interaction.on("pan", ({ pan: e }) => {
|
|
@@ -2056,23 +2122,23 @@ class I extends HTMLElement {
|
|
|
2056
2122
|
this.canvas.style.cursor = e;
|
|
2057
2123
|
})
|
|
2058
2124
|
), this.subscriptions.push(
|
|
2059
|
-
this.interaction.on("context-menu", ({ areaId: e, clientX:
|
|
2060
|
-
this.handleContextMenu(e,
|
|
2125
|
+
this.interaction.on("context-menu", ({ areaId: e, clientX: t, clientY: i }) => {
|
|
2126
|
+
this.handleContextMenu(e, t, i);
|
|
2061
2127
|
})
|
|
2062
2128
|
);
|
|
2063
2129
|
}
|
|
2064
2130
|
wireInputPanelEvents() {
|
|
2065
2131
|
this.subscriptions.push(
|
|
2066
|
-
this.inputPanel.on("text:change", ({ areaId: e, updates:
|
|
2067
|
-
this.contentManager.setTextContent(e,
|
|
2132
|
+
this.inputPanel.on("text:change", ({ areaId: e, updates: t }) => {
|
|
2133
|
+
this.contentManager.setTextContent(e, t), this.saveContentState();
|
|
2068
2134
|
})
|
|
2069
2135
|
), this.subscriptions.push(
|
|
2070
|
-
this.inputPanel.on("image:change", async ({ areaId: e, dataUrl:
|
|
2136
|
+
this.inputPanel.on("image:change", async ({ areaId: e, dataUrl: t, filename: i }) => {
|
|
2071
2137
|
const s = this.contentManager.getContents();
|
|
2072
|
-
let
|
|
2073
|
-
for (const [
|
|
2074
|
-
|
|
2075
|
-
if (
|
|
2138
|
+
let n = 0;
|
|
2139
|
+
for (const [r, d] of s)
|
|
2140
|
+
d.type === "image" && r !== e && n++;
|
|
2141
|
+
if (n >= 10) {
|
|
2076
2142
|
this.showToast("Maximum of 10 images per design reached", "error");
|
|
2077
2143
|
return;
|
|
2078
2144
|
}
|
|
@@ -2080,30 +2146,30 @@ class I extends HTMLElement {
|
|
|
2080
2146
|
this.showToast("Storage quota exceeded. Upgrade your plan for more storage.", "error");
|
|
2081
2147
|
return;
|
|
2082
2148
|
}
|
|
2083
|
-
const o = this.currentTemplate?.areas.find((
|
|
2149
|
+
const o = this.currentTemplate?.areas.find((r) => r.id === e);
|
|
2084
2150
|
if (o && this.currentTemplate) {
|
|
2085
|
-
const
|
|
2151
|
+
const r = this.currentTemplate.print.targetDpi;
|
|
2086
2152
|
try {
|
|
2087
|
-
const
|
|
2088
|
-
|
|
2089
|
-
const
|
|
2090
|
-
|
|
2091
|
-
}),
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2153
|
+
const d = new Image();
|
|
2154
|
+
d.crossOrigin = "anonymous";
|
|
2155
|
+
const l = await new Promise((g, m) => {
|
|
2156
|
+
d.onload = () => g({ width: d.naturalWidth, height: d.naturalHeight }), d.onerror = () => m(new Error("Failed to load image")), d.src = t;
|
|
2157
|
+
}), c = o.location.width / r, p = o.location.height / r, u = Q(
|
|
2158
|
+
l.width,
|
|
2159
|
+
l.height,
|
|
2160
|
+
c,
|
|
2161
|
+
p,
|
|
2162
|
+
r
|
|
2097
2163
|
);
|
|
2098
2164
|
if (u.actualDPI < 150) {
|
|
2099
2165
|
if (!await this.showConfirmation(
|
|
2100
2166
|
"Low resolution detected",
|
|
2101
|
-
`This image is ${u.actualDPI} DPI at print size. Print may appear blurry. Recommended: ${
|
|
2167
|
+
`This image is ${u.actualDPI} DPI at print size. Print may appear blurry. Recommended: ${r} DPI.`,
|
|
2102
2168
|
"Cancel",
|
|
2103
2169
|
"Use Anyway"
|
|
2104
2170
|
)) return;
|
|
2105
|
-
} else u.actualDPI <
|
|
2106
|
-
`Image resolution is ${u.actualDPI} DPI (${
|
|
2171
|
+
} else u.actualDPI < r && (this.showToast(
|
|
2172
|
+
`Image resolution is ${u.actualDPI} DPI (${r} recommended)`,
|
|
2107
2173
|
"warning"
|
|
2108
2174
|
), this.stateManager.update({
|
|
2109
2175
|
warnings: [
|
|
@@ -2114,9 +2180,11 @@ class I extends HTMLElement {
|
|
|
2114
2180
|
} catch {
|
|
2115
2181
|
}
|
|
2116
2182
|
}
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2183
|
+
this.contentManager.setImageContent(e, t, i), this.saveContentState();
|
|
2184
|
+
const a = this.getAttribute("store-id");
|
|
2185
|
+
if (a && this.preUploadImage(e, t, a), this.storageUsage) {
|
|
2186
|
+
const r = Math.round(t.length * 0.75);
|
|
2187
|
+
this.storageUsage.storageUsed += r, this.storageUsage.usagePercent = this.storageUsage.storageQuota > 0 ? Math.round(this.storageUsage.storageUsed / this.storageUsage.storageQuota * 100) : 0;
|
|
2120
2188
|
}
|
|
2121
2189
|
})
|
|
2122
2190
|
), this.subscriptions.push(
|
|
@@ -2125,23 +2193,23 @@ class I extends HTMLElement {
|
|
|
2125
2193
|
})
|
|
2126
2194
|
), this.subscriptions.push(
|
|
2127
2195
|
this.inputPanel.on("clear", ({ areaId: e }) => {
|
|
2128
|
-
this.contentManager.clearContent(e), this.saveContentState();
|
|
2196
|
+
this.contentManager.clearContent(e), this.syncEngineContents(), this.inputPanel.setContents(this.contentManager.getContents()), this.saveContentState();
|
|
2129
2197
|
})
|
|
2130
2198
|
), this.subscriptions.push(
|
|
2131
2199
|
this.inputPanel.on("select", ({ areaId: e }) => {
|
|
2132
2200
|
this.handleAreaSelect(e);
|
|
2133
2201
|
})
|
|
2134
2202
|
), this.subscriptions.push(
|
|
2135
|
-
this.inputPanel.on("offset:change", ({ areaId: e, offset:
|
|
2136
|
-
this.contentManager.setContentOffset(e,
|
|
2203
|
+
this.inputPanel.on("offset:change", ({ areaId: e, offset: t }) => {
|
|
2204
|
+
this.contentManager.setContentOffset(e, t), this.saveContentState();
|
|
2137
2205
|
})
|
|
2138
2206
|
), this.subscriptions.push(
|
|
2139
|
-
this.inputPanel.on("scale:change", ({ areaId: e, scale:
|
|
2140
|
-
this.contentManager.setImageScale(e,
|
|
2207
|
+
this.inputPanel.on("scale:change", ({ areaId: e, scale: t }) => {
|
|
2208
|
+
this.contentManager.setImageScale(e, t), this.saveContentState();
|
|
2141
2209
|
})
|
|
2142
2210
|
), this.subscriptions.push(
|
|
2143
|
-
this.inputPanel.on("rotation:change", ({ areaId: e, rotation:
|
|
2144
|
-
this.contentManager.setImageRotation(e,
|
|
2211
|
+
this.inputPanel.on("rotation:change", ({ areaId: e, rotation: t }) => {
|
|
2212
|
+
this.contentManager.setImageRotation(e, t), this.saveContentState();
|
|
2145
2213
|
})
|
|
2146
2214
|
), this.subscriptions.push(
|
|
2147
2215
|
this.inputPanel.on("dismiss", () => {
|
|
@@ -2150,8 +2218,8 @@ class I extends HTMLElement {
|
|
|
2150
2218
|
);
|
|
2151
2219
|
}
|
|
2152
2220
|
// ─── Handlers ───
|
|
2153
|
-
handleAreaSelect(e,
|
|
2154
|
-
this.stateManager.update({ selectedAreaId: e }), this.engine.setSelectedArea(e), this.inputPanel.setSelectedArea(
|
|
2221
|
+
handleAreaSelect(e, t = !1) {
|
|
2222
|
+
this.stateManager.update({ selectedAreaId: e }), this.engine.setSelectedArea(e), this.inputPanel.setSelectedArea(t ? e : null), e && document.activeElement !== this && this.focus(), this.dispatchEvent(
|
|
2155
2223
|
new CustomEvent("area:select", {
|
|
2156
2224
|
detail: { areaId: e },
|
|
2157
2225
|
bubbles: !0,
|
|
@@ -2159,29 +2227,42 @@ class I extends HTMLElement {
|
|
|
2159
2227
|
})
|
|
2160
2228
|
);
|
|
2161
2229
|
}
|
|
2162
|
-
handleContextMenu(e,
|
|
2230
|
+
handleContextMenu(e, t, i) {
|
|
2163
2231
|
this.handleAreaSelect(e, !!e);
|
|
2164
2232
|
}
|
|
2165
2233
|
handleZoomIn() {
|
|
2166
|
-
const e = this.engine.getZoom(),
|
|
2167
|
-
this.engine.setZoom(
|
|
2234
|
+
const e = this.engine.getZoom(), t = Math.min(e * 1.25, 5), i = this.engine.getCenteredPan(t);
|
|
2235
|
+
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
2236
|
}
|
|
2169
2237
|
handleZoomOut() {
|
|
2170
|
-
const e = this.engine.getZoom(),
|
|
2171
|
-
this.engine.setZoom(
|
|
2238
|
+
const e = this.engine.getZoom(), t = Math.max(e * 0.8, 0.1), i = this.engine.getCenteredPan(t);
|
|
2239
|
+
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
2240
|
}
|
|
2173
2241
|
handleZoomFit() {
|
|
2174
2242
|
this.engine.fitToView(), this.stateManager.update({ zoom: this.engine.getZoom(), pan: this.engine.getPan() }), this.zoomToolbar.setZoom(this.engine.getZoom());
|
|
2175
2243
|
}
|
|
2244
|
+
/**
|
|
2245
|
+
* Schedule a canvas refit after layout settles (double-rAF ensures
|
|
2246
|
+
* ResizeObserver callbacks and layout-class changes have been applied).
|
|
2247
|
+
*/
|
|
2248
|
+
scheduleCanvasRefit() {
|
|
2249
|
+
this.refitRafId != null && cancelAnimationFrame(this.refitRafId), this.refitRafId = requestAnimationFrame(() => {
|
|
2250
|
+
this.refitRafId = requestAnimationFrame(() => {
|
|
2251
|
+
if (this.refitRafId = null, !this.canvasWrapper || !this.engine) return;
|
|
2252
|
+
const e = this.canvasWrapper.getBoundingClientRect();
|
|
2253
|
+
e.width > 0 && e.height > 0 && this.handleCanvasResize(e);
|
|
2254
|
+
});
|
|
2255
|
+
});
|
|
2256
|
+
}
|
|
2176
2257
|
handleCanvasResize(e) {
|
|
2177
|
-
const { width:
|
|
2178
|
-
if (
|
|
2258
|
+
const { width: t, height: i } = e;
|
|
2259
|
+
if (t === 0 || i === 0) return;
|
|
2179
2260
|
const s = window.devicePixelRatio || 1;
|
|
2180
|
-
this.canvas.width =
|
|
2261
|
+
this.canvas.width = t * s, this.canvas.height = i * s, this.canvas.style.width = `${t}px`, this.canvas.style.height = `${i}px`, this.engine && (this.engine.fitToView(), this.stateManager.update({ zoom: this.engine.getZoom(), pan: this.engine.getPan() }), this.zoomToolbar.setZoom(this.engine.getZoom()));
|
|
2181
2262
|
}
|
|
2182
2263
|
handleLayoutResize(e) {
|
|
2183
|
-
const
|
|
2184
|
-
this.container.classList.toggle("layout-desktop", !
|
|
2264
|
+
const t = e.width < ee;
|
|
2265
|
+
this.container.classList.toggle("layout-desktop", !t), this.container.classList.toggle("layout-mobile", t), t !== this.isMobileLayout && (this.isMobileLayout = t, this.inputPanel.setMobile(t), this.scheduleCanvasRefit());
|
|
2185
2266
|
}
|
|
2186
2267
|
// ─── State Management ───
|
|
2187
2268
|
async refreshStorageUsage() {
|
|
@@ -2193,6 +2274,42 @@ class I extends HTMLElement {
|
|
|
2193
2274
|
} catch {
|
|
2194
2275
|
}
|
|
2195
2276
|
}
|
|
2277
|
+
/**
|
|
2278
|
+
* Pre-upload an image to the server so finalize doesn't need to send base64.
|
|
2279
|
+
* On success, updates the content manager with assetId/assetUrl.
|
|
2280
|
+
* Tracks the promise so finalize() can await it.
|
|
2281
|
+
*/
|
|
2282
|
+
preUploadImage(e, t, i) {
|
|
2283
|
+
const s = this.activeViewName, n = this._doPreUpload(e, t, i, s);
|
|
2284
|
+
return this.pendingUploads.set(e, n), this.inputPanel?.setUploading(e, !0), this.updateSaveButtonForUploads(), n.finally(() => {
|
|
2285
|
+
this.pendingUploads.get(e) === n && this.pendingUploads.delete(e), this.inputPanel?.setUploading(e, !1), this.updateSaveButtonForUploads();
|
|
2286
|
+
}), n;
|
|
2287
|
+
}
|
|
2288
|
+
async _doPreUpload(e, t, i, s) {
|
|
2289
|
+
const [n, o] = t.split(","), a = n.match(/:(.*?);/)?.[1] || "image/png", r = atob(o), d = new Uint8Array(r.length);
|
|
2290
|
+
for (let g = 0; g < r.length; g++) d[g] = r.charCodeAt(g);
|
|
2291
|
+
const l = new Blob([d], { type: a }), c = a.split("/")[1] || "png", p = new File([l], `upload.${c}`, { type: a });
|
|
2292
|
+
let u;
|
|
2293
|
+
try {
|
|
2294
|
+
u = await this.apiClient.uploadAsset(p, i);
|
|
2295
|
+
} catch (g) {
|
|
2296
|
+
console.error("Customizer: image pre-upload failed", g), this.showToast("Image upload failed. Will retry on save.", "warning");
|
|
2297
|
+
return;
|
|
2298
|
+
}
|
|
2299
|
+
if (this.activeViewName === s) {
|
|
2300
|
+
const g = this.contentManager.getContent(e);
|
|
2301
|
+
g?.type === "image" && g.dataUrl === t && this.contentManager.setImageContent(e, u.publicUrl, g.filename, {
|
|
2302
|
+
assetId: u.assetId,
|
|
2303
|
+
assetUrl: u.publicUrl
|
|
2304
|
+
});
|
|
2305
|
+
} else if (s) {
|
|
2306
|
+
const g = this.perViewContents.get(s);
|
|
2307
|
+
if (g) {
|
|
2308
|
+
const m = g.map(([v, b]) => v === e && b.type === "image" && b.dataUrl === t ? [v, { ...b, dataUrl: u.publicUrl, assetId: u.assetId, assetUrl: u.publicUrl }] : [v, b]);
|
|
2309
|
+
this.perViewContents.set(s, m);
|
|
2310
|
+
}
|
|
2311
|
+
}
|
|
2312
|
+
}
|
|
2196
2313
|
saveContentState() {
|
|
2197
2314
|
this.stateManager.setState({
|
|
2198
2315
|
contents: this.contentManager.toJSON(),
|
|
@@ -2223,106 +2340,110 @@ class I extends HTMLElement {
|
|
|
2223
2340
|
handler: () => this.handleAreaSelect(null)
|
|
2224
2341
|
});
|
|
2225
2342
|
}
|
|
2343
|
+
updateSaveButtonForUploads() {
|
|
2344
|
+
this.zoomToolbar && this.stateManager?.getState().isDirty && (this.pendingUploads.size > 0 ? this.zoomToolbar.setSaveDisabled(!0, "Uploading...") : this.zoomToolbar.setSaveDisabled(!1, "✓"));
|
|
2345
|
+
}
|
|
2226
2346
|
// ─── 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 =
|
|
2347
|
+
showToast(e, t = "info") {
|
|
2348
|
+
const i = h("div", { class: `editor-toast editor-toast-${t}` }, e);
|
|
2349
|
+
this.shadow.appendChild(i), setTimeout(() => i.remove(), 4e3);
|
|
2350
|
+
}
|
|
2351
|
+
showConfirmation(e, t, i = "Cancel", s = "Continue") {
|
|
2352
|
+
return new Promise((n) => {
|
|
2353
|
+
const o = h("div", { class: "editor-modal-overlay" }), a = h("div", { class: "editor-modal" });
|
|
2354
|
+
a.appendChild(h("div", { class: "editor-modal-title" }, e)), a.appendChild(h("div", { class: "editor-modal-body" }, t));
|
|
2355
|
+
const r = h("div", { class: "editor-modal-actions" }), d = h("button", { class: "editor-modal-btn" }, i);
|
|
2236
2356
|
d.addEventListener("click", () => {
|
|
2237
|
-
o.remove(),
|
|
2357
|
+
o.remove(), n(!1);
|
|
2238
2358
|
});
|
|
2239
|
-
const l =
|
|
2359
|
+
const l = h("button", { class: "editor-modal-btn editor-modal-btn-primary" }, s);
|
|
2240
2360
|
l.addEventListener("click", () => {
|
|
2241
|
-
o.remove(),
|
|
2242
|
-
}), r.appendChild(d), r.appendChild(l),
|
|
2243
|
-
|
|
2361
|
+
o.remove(), n(!0);
|
|
2362
|
+
}), r.appendChild(d), r.appendChild(l), a.appendChild(r), o.appendChild(a), o.addEventListener("click", (c) => {
|
|
2363
|
+
c.target === o && (o.remove(), n(!1));
|
|
2244
2364
|
}), this.shadow.appendChild(o);
|
|
2245
2365
|
});
|
|
2246
2366
|
}
|
|
2247
2367
|
// ─── Error / Cleanup ───
|
|
2248
|
-
showError(e,
|
|
2368
|
+
showError(e, t) {
|
|
2249
2369
|
this.shadow.innerHTML = "";
|
|
2250
|
-
const
|
|
2251
|
-
|
|
2252
|
-
const s =
|
|
2253
|
-
s.appendChild(
|
|
2370
|
+
const i = document.createElement("style");
|
|
2371
|
+
i.textContent = k, this.shadow.appendChild(i);
|
|
2372
|
+
const s = h("div", { class: "error-container" });
|
|
2373
|
+
s.appendChild(h("div", { class: "error-title" }, e)), s.appendChild(h("div", { class: "error-message" }, t?.message ?? "")), this.shadow.appendChild(s), this.dispatchEvent(
|
|
2254
2374
|
new CustomEvent("error", {
|
|
2255
|
-
detail: { message: e, error:
|
|
2375
|
+
detail: { message: e, error: t },
|
|
2256
2376
|
bubbles: !0,
|
|
2257
2377
|
composed: !0
|
|
2258
2378
|
})
|
|
2259
2379
|
);
|
|
2260
2380
|
}
|
|
2261
2381
|
cleanup() {
|
|
2382
|
+
this.renderAbortController?.abort(), this.renderAbortController = null;
|
|
2262
2383
|
for (const e of this.subscriptions)
|
|
2263
2384
|
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();
|
|
2385
|
+
this.subscriptions = [], this.refitRafId != null && (cancelAnimationFrame(this.refitRafId), this.refitRafId = null), this.resizeObserver && (this.resizeObserver.disconnect(), this.resizeObserver = null), this.engine?.destroy(), this.interaction?.destroy(), this.keyboard?.destroy(), this.isReady = !1, this.currentTemplate = null, this.productViews = [], this.activeViewName = null, this.perViewContents.clear();
|
|
2265
2386
|
}
|
|
2266
2387
|
}
|
|
2267
2388
|
customElements.get("customizer-editor") || customElements.define("customizer-editor", I);
|
|
2268
|
-
function te(
|
|
2269
|
-
const
|
|
2270
|
-
if (!
|
|
2389
|
+
function te(f, e) {
|
|
2390
|
+
const t = typeof f == "string" ? document.querySelector(f) : f;
|
|
2391
|
+
if (!t)
|
|
2271
2392
|
throw new Error(
|
|
2272
|
-
`Container not found: ${typeof
|
|
2393
|
+
`Container not found: ${typeof f == "string" ? f : "provided element is null"}`
|
|
2273
2394
|
);
|
|
2274
2395
|
if (!e.templateId && !e.productId)
|
|
2275
2396
|
throw new Error("Either templateId or productId must be provided");
|
|
2276
2397
|
if (e.templateId && e.productId)
|
|
2277
2398
|
throw new Error("Only one of templateId or productId should be provided, not both");
|
|
2278
|
-
const
|
|
2279
|
-
e.productId ?
|
|
2280
|
-
const s = [],
|
|
2281
|
-
|
|
2399
|
+
const i = document.createElement("customizer-editor");
|
|
2400
|
+
e.productId ? i.setAttribute("product-id", e.productId) : i.setAttribute("template-id", e.templateId), e.theme && i.setAttribute("theme", e.theme), e.mode && i.setAttribute("mode", e.mode), e.className && i.classList.add(e.className), e.showCloseButton === !1 && i.setAttribute("show-close-button", "false"), e.showSaveButton === !1 && i.setAttribute("show-save-button", "false"), i.style.width = "100%", i.style.height = "100%";
|
|
2401
|
+
const s = [], n = (a, r) => {
|
|
2402
|
+
i.addEventListener(a, r), s.push({ event: a, handler: r });
|
|
2282
2403
|
};
|
|
2283
|
-
|
|
2404
|
+
t.innerHTML = "", t.appendChild(i);
|
|
2284
2405
|
const o = {
|
|
2285
2406
|
getDesign() {
|
|
2286
|
-
if (typeof
|
|
2407
|
+
if (typeof i.getDesign != "function")
|
|
2287
2408
|
throw new Error("Editor not ready: getDesign method not available");
|
|
2288
|
-
return
|
|
2409
|
+
return i.getDesign();
|
|
2289
2410
|
},
|
|
2290
|
-
setDesign(
|
|
2291
|
-
if (typeof
|
|
2411
|
+
setDesign(a) {
|
|
2412
|
+
if (typeof i.setDesign != "function")
|
|
2292
2413
|
throw new Error("Editor not ready: setDesign method not available");
|
|
2293
|
-
|
|
2414
|
+
i.setDesign(a);
|
|
2294
2415
|
},
|
|
2295
2416
|
undo() {
|
|
2296
|
-
if (typeof
|
|
2417
|
+
if (typeof i.undo != "function")
|
|
2297
2418
|
throw new Error("Editor not ready: undo method not available");
|
|
2298
|
-
|
|
2419
|
+
i.undo();
|
|
2299
2420
|
},
|
|
2300
2421
|
redo() {
|
|
2301
|
-
if (typeof
|
|
2422
|
+
if (typeof i.redo != "function")
|
|
2302
2423
|
throw new Error("Editor not ready: redo method not available");
|
|
2303
|
-
|
|
2424
|
+
i.redo();
|
|
2304
2425
|
},
|
|
2305
2426
|
canUndo() {
|
|
2306
|
-
return typeof
|
|
2427
|
+
return typeof i.canUndo != "function" ? !1 : i.canUndo();
|
|
2307
2428
|
},
|
|
2308
2429
|
canRedo() {
|
|
2309
|
-
return typeof
|
|
2430
|
+
return typeof i.canRedo != "function" ? !1 : i.canRedo();
|
|
2310
2431
|
},
|
|
2311
2432
|
async finalize() {
|
|
2312
|
-
const
|
|
2433
|
+
const a = e.apiUrl || "https://api.varianta.io";
|
|
2313
2434
|
try {
|
|
2314
|
-
if (e.productId && typeof
|
|
2315
|
-
const
|
|
2316
|
-
designId:
|
|
2317
|
-
status:
|
|
2318
|
-
proofUrl:
|
|
2435
|
+
if (e.productId && typeof i.finalize == "function") {
|
|
2436
|
+
const p = await i.finalize(), u = {
|
|
2437
|
+
designId: p.designId,
|
|
2438
|
+
status: "processing",
|
|
2439
|
+
proofUrl: p.proofUrl ?? null,
|
|
2319
2440
|
errorMessage: null,
|
|
2320
|
-
requestId:
|
|
2321
|
-
views:
|
|
2441
|
+
requestId: p.requestId,
|
|
2442
|
+
views: p.views
|
|
2322
2443
|
};
|
|
2323
|
-
return e.onFinalize && e.onFinalize(
|
|
2444
|
+
return e.onFinalize && e.onFinalize(u), u;
|
|
2324
2445
|
}
|
|
2325
|
-
const r = this.getDesign(), d = await fetch(`${
|
|
2446
|
+
const r = this.getDesign(), d = await fetch(`${a}/public/finalize`, {
|
|
2326
2447
|
method: "POST",
|
|
2327
2448
|
headers: {
|
|
2328
2449
|
"Content-Type": "application/json"
|
|
@@ -2333,36 +2454,18 @@ function te(v, e) {
|
|
|
2333
2454
|
})
|
|
2334
2455
|
});
|
|
2335
2456
|
if (!d.ok) {
|
|
2336
|
-
const
|
|
2457
|
+
const p = await d.json().catch(() => ({
|
|
2337
2458
|
message: "Finalization failed"
|
|
2338
2459
|
}));
|
|
2339
|
-
throw new Error(
|
|
2460
|
+
throw new Error(p.message || "Finalization failed");
|
|
2340
2461
|
}
|
|
2341
|
-
const l = await d.json()
|
|
2342
|
-
let h = {
|
|
2462
|
+
const l = await d.json(), c = {
|
|
2343
2463
|
designId: l.designId,
|
|
2344
2464
|
status: l.status,
|
|
2345
2465
|
proofUrl: l.proofUrl ?? null,
|
|
2346
2466
|
errorMessage: l.errorMessage ?? null
|
|
2347
2467
|
};
|
|
2348
|
-
|
|
2349
|
-
let m = 0;
|
|
2350
|
-
for (; h.status === "processing" && m < p; ) {
|
|
2351
|
-
await new Promise((b) => setTimeout(b, u)), m++;
|
|
2352
|
-
const g = await fetch(
|
|
2353
|
-
`${n}/public/designs/${h.designId}/status`
|
|
2354
|
-
);
|
|
2355
|
-
if (!g.ok)
|
|
2356
|
-
throw new Error("Failed to check design status");
|
|
2357
|
-
const f = await g.json();
|
|
2358
|
-
h = {
|
|
2359
|
-
designId: f.designId,
|
|
2360
|
-
status: f.status,
|
|
2361
|
-
proofUrl: f.proofUrl ?? null,
|
|
2362
|
-
errorMessage: f.errorMessage ?? null
|
|
2363
|
-
};
|
|
2364
|
-
}
|
|
2365
|
-
return h.status === "processing" && (h = { ...h, status: "failed", errorMessage: "Render timed out" }), e.onFinalize && e.onFinalize(h), h;
|
|
2468
|
+
return e.onFinalize && e.onFinalize(c), c;
|
|
2366
2469
|
} catch (r) {
|
|
2367
2470
|
const d = {
|
|
2368
2471
|
code: "FINALIZE_ERROR",
|
|
@@ -2372,81 +2475,129 @@ function te(v, e) {
|
|
|
2372
2475
|
throw e.onError && e.onError(d), r;
|
|
2373
2476
|
}
|
|
2374
2477
|
},
|
|
2375
|
-
|
|
2376
|
-
if (typeof
|
|
2478
|
+
async waitForResult(a, r) {
|
|
2479
|
+
if (typeof i.waitForResult == "function") {
|
|
2480
|
+
const m = await i.waitForResult(a, r);
|
|
2481
|
+
return e.onRenderComplete && e.onRenderComplete(m), m;
|
|
2482
|
+
}
|
|
2483
|
+
const d = e.apiUrl || "https://api.varianta.io", l = r?.pollInterval ?? 1500, c = r?.maxPolls ?? 40, p = r?.signal;
|
|
2484
|
+
let u = 0;
|
|
2485
|
+
for (; u < c; ) {
|
|
2486
|
+
if (p?.aborted)
|
|
2487
|
+
throw new DOMException("Polling aborted", "AbortError");
|
|
2488
|
+
await new Promise((v, b) => {
|
|
2489
|
+
const w = setTimeout(v, l);
|
|
2490
|
+
p?.addEventListener("abort", () => {
|
|
2491
|
+
clearTimeout(w), b(new DOMException("Polling aborted", "AbortError"));
|
|
2492
|
+
}, { once: !0 });
|
|
2493
|
+
}), u++;
|
|
2494
|
+
let m;
|
|
2495
|
+
if (a.length === 1) {
|
|
2496
|
+
const v = await fetch(`${d}/public/designs/${a[0]}/status`);
|
|
2497
|
+
if (!v.ok) throw new Error("Failed to check design status");
|
|
2498
|
+
const b = await v.json();
|
|
2499
|
+
m = {
|
|
2500
|
+
designs: [b],
|
|
2501
|
+
allCompleted: b.status !== "processing"
|
|
2502
|
+
};
|
|
2503
|
+
} else {
|
|
2504
|
+
const v = await fetch(
|
|
2505
|
+
`${d}/public/finalize-status?designIds=${a.join(",")}`
|
|
2506
|
+
);
|
|
2507
|
+
if (!v.ok) throw new Error("Failed to check design status");
|
|
2508
|
+
m = await v.json();
|
|
2509
|
+
}
|
|
2510
|
+
if (m.allCompleted)
|
|
2511
|
+
return e.onRenderComplete && e.onRenderComplete(m), m;
|
|
2512
|
+
}
|
|
2513
|
+
const g = {
|
|
2514
|
+
designs: a.map((m) => ({
|
|
2515
|
+
designId: m,
|
|
2516
|
+
status: "failed",
|
|
2517
|
+
proofUrl: null,
|
|
2518
|
+
errorMessage: "Render timed out"
|
|
2519
|
+
})),
|
|
2520
|
+
allCompleted: !0
|
|
2521
|
+
};
|
|
2522
|
+
return e.onRenderComplete && e.onRenderComplete(g), g;
|
|
2523
|
+
},
|
|
2524
|
+
addTextLayer(a) {
|
|
2525
|
+
if (typeof i.addTextLayer != "function")
|
|
2377
2526
|
throw new Error("Editor not ready: addTextLayer method not available");
|
|
2378
|
-
|
|
2527
|
+
i.addTextLayer(a);
|
|
2379
2528
|
},
|
|
2380
|
-
async addImageLayer(
|
|
2381
|
-
if (typeof
|
|
2529
|
+
async addImageLayer(a) {
|
|
2530
|
+
if (typeof i.addImageLayer != "function")
|
|
2382
2531
|
throw new Error("Editor not ready: addImageLayer method not available");
|
|
2383
|
-
return
|
|
2532
|
+
return i.addImageLayer(a);
|
|
2384
2533
|
},
|
|
2385
|
-
removeLayer(
|
|
2386
|
-
if (typeof
|
|
2534
|
+
removeLayer(a) {
|
|
2535
|
+
if (typeof i.removeLayer != "function")
|
|
2387
2536
|
throw new Error("Editor not ready: removeLayer method not available");
|
|
2388
|
-
|
|
2537
|
+
i.removeLayer(a);
|
|
2389
2538
|
},
|
|
2390
|
-
selectLayer(
|
|
2391
|
-
if (typeof
|
|
2539
|
+
selectLayer(a) {
|
|
2540
|
+
if (typeof i.selectLayer != "function")
|
|
2392
2541
|
throw new Error("Editor not ready: selectLayer method not available");
|
|
2393
|
-
|
|
2542
|
+
i.selectLayer(a);
|
|
2394
2543
|
},
|
|
2395
2544
|
getSelectedLayerId() {
|
|
2396
|
-
return typeof
|
|
2545
|
+
return typeof i.getSelectedLayerId != "function" ? null : i.getSelectedLayerId();
|
|
2397
2546
|
},
|
|
2398
2547
|
getActiveView() {
|
|
2399
|
-
return typeof
|
|
2548
|
+
return typeof i.getActiveView != "function" ? null : i.getActiveView();
|
|
2400
2549
|
},
|
|
2401
2550
|
getViews() {
|
|
2402
|
-
return typeof
|
|
2551
|
+
return typeof i.getViews != "function" ? [] : i.getViews();
|
|
2403
2552
|
},
|
|
2404
|
-
setActiveView(
|
|
2405
|
-
if (typeof
|
|
2553
|
+
setActiveView(a) {
|
|
2554
|
+
if (typeof i.setActiveView != "function")
|
|
2406
2555
|
throw new Error("Editor not ready: setActiveView method not available");
|
|
2407
|
-
|
|
2556
|
+
i.setActiveView(a);
|
|
2408
2557
|
},
|
|
2409
|
-
setTheme(
|
|
2410
|
-
|
|
2558
|
+
setTheme(a) {
|
|
2559
|
+
i.setAttribute("theme", a);
|
|
2411
2560
|
},
|
|
2412
|
-
setMode(
|
|
2413
|
-
|
|
2561
|
+
setMode(a) {
|
|
2562
|
+
i.setAttribute("mode", a);
|
|
2414
2563
|
},
|
|
2415
2564
|
destroy() {
|
|
2416
|
-
s.forEach(({ event:
|
|
2417
|
-
|
|
2418
|
-
}),
|
|
2565
|
+
s.forEach(({ event: a, handler: r }) => {
|
|
2566
|
+
i.removeEventListener(a, r);
|
|
2567
|
+
}), i.parentNode && i.parentNode.removeChild(i);
|
|
2419
2568
|
},
|
|
2420
2569
|
getElement() {
|
|
2421
|
-
return
|
|
2570
|
+
return i;
|
|
2422
2571
|
}
|
|
2423
2572
|
};
|
|
2424
|
-
return e.onReady &&
|
|
2573
|
+
return e.onReady && n("ready", (() => {
|
|
2425
2574
|
e.onReady?.(o);
|
|
2426
|
-
})), e.onChange &&
|
|
2427
|
-
const r =
|
|
2575
|
+
})), e.onChange && n("change", ((a) => {
|
|
2576
|
+
const r = a.detail.design;
|
|
2428
2577
|
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 =
|
|
2578
|
+
})), e.onLayerSelect && n("layer:select", ((a) => {
|
|
2579
|
+
e.onLayerSelect?.(a.detail.layerId);
|
|
2580
|
+
})), e.onLayerAdd && n("layer:add", ((a) => {
|
|
2581
|
+
e.onLayerAdd?.(a.detail.layerId);
|
|
2582
|
+
})), e.onLayerRemove && n("layer:remove", ((a) => {
|
|
2583
|
+
e.onLayerRemove?.(a.detail.layerId);
|
|
2584
|
+
})), e.onLayerUpdate && n("layer:update", ((a) => {
|
|
2585
|
+
e.onLayerUpdate?.(a.detail.layerId);
|
|
2586
|
+
})), e.onError && n("error", ((a) => {
|
|
2587
|
+
const r = a.detail.error;
|
|
2439
2588
|
e.onError?.(r);
|
|
2440
|
-
})), e.onViewChange &&
|
|
2441
|
-
e.onViewChange?.(
|
|
2442
|
-
})), e.
|
|
2589
|
+
})), e.onViewChange && n("view-change", ((a) => {
|
|
2590
|
+
e.onViewChange?.(a.detail.viewName);
|
|
2591
|
+
})), e.onRenderComplete && n("customizer:render-complete", ((a) => {
|
|
2592
|
+
e.onRenderComplete?.(a.detail);
|
|
2593
|
+
})), e.onClose && n("customizer:close", (() => {
|
|
2443
2594
|
e.onClose?.();
|
|
2444
|
-
})), e.onSave &&
|
|
2445
|
-
e.onSave?.(
|
|
2446
|
-
})), e.initialDesign &&
|
|
2595
|
+
})), e.onSave && n("customizer:save", ((a) => {
|
|
2596
|
+
e.onSave?.(a.detail);
|
|
2597
|
+
})), e.initialDesign && i.addEventListener(
|
|
2447
2598
|
"ready",
|
|
2448
2599
|
() => {
|
|
2449
|
-
e.initialDesign &&
|
|
2600
|
+
e.initialDesign && i.setDesign(e.initialDesign);
|
|
2450
2601
|
},
|
|
2451
2602
|
{ once: !0 }
|
|
2452
2603
|
), e.debug && (console.log("[Customizer SDK] Initialized with options:", e), console.log("[Customizer SDK] Instance:", o)), o;
|