vibrant-tool 0.1.7 → 0.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/hooks/useCanvasZoom.d.ts +4 -1
- package/dist/lib/services/background-removal.service.d.ts +29 -0
- package/dist/lib/services/upload.service.d.ts +1 -0
- package/dist/lib/utils/canvas-setup.d.ts +6 -0
- package/dist/lib/utils/fabric-helper.d.ts +5 -2
- package/dist/lib/utils/image-utils.d.ts +4 -0
- package/dist/modules/components/position-picker/position-icons.d.ts +12 -0
- package/dist/modules/components/zoom-controls/index.d.ts +5 -1
- package/dist/modules/icons/bgremoveicon/index.d.ts +4 -0
- package/dist/modules/topbar/preview-modal.d.ts +8 -2
- package/dist/vibrant-tool.css +194 -21
- package/dist/vibrant-tool.es.js +1601 -310
- package/dist/vibrant-tool.umd.js +1601 -310
- package/dist/zustand/historyStore.d.ts +1 -0
- package/package.json +2 -1
package/dist/vibrant-tool.umd.js
CHANGED
|
@@ -1075,9 +1075,9 @@
|
|
|
1075
1075
|
function GlobalLoader() {
|
|
1076
1076
|
const { isLoading, text } = useLoaderStore();
|
|
1077
1077
|
if (!isLoading) return null;
|
|
1078
|
-
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "fixed inset-0 z-[999999] flex flex-col items-center justify-center bg-black/
|
|
1079
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-
|
|
1080
|
-
|
|
1078
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "fixed inset-0 z-[999999] flex flex-col items-center justify-center bg-black/65 backdrop-blur-[2px] select-none", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center gap-3.5 bg-neutral-900/95 border border-neutral-700/80 px-8 py-6 rounded-2xl shadow-2xl min-w-[240px] max-w-[360px] text-center", children: [
|
|
1079
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-10 w-10 animate-spin rounded-full border-3 border-[#02bc71] border-t-transparent shadow-sm" }) }),
|
|
1080
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-medium text-neutral-100 tracking-wide", children: text || "Please wait..." })
|
|
1081
1081
|
] }) });
|
|
1082
1082
|
}
|
|
1083
1083
|
function DownloadIcon() {
|
|
@@ -9027,7 +9027,7 @@
|
|
|
9027
9027
|
waH,
|
|
9028
9028
|
isVertical
|
|
9029
9029
|
);
|
|
9030
|
-
workarea.set({ rx, ry });
|
|
9030
|
+
workarea.set({ rx, ry, strokeWidth: 0, stroke: null });
|
|
9031
9031
|
workarea.__radiusOfCanvas = radiusOfCanvas;
|
|
9032
9032
|
workarea.__shapeType = shapeType;
|
|
9033
9033
|
workarea.__isVertical = isVertical;
|
|
@@ -9046,8 +9046,61 @@
|
|
|
9046
9046
|
top: (obj.top ?? 0) + dy
|
|
9047
9047
|
});
|
|
9048
9048
|
obj.setCoords();
|
|
9049
|
+
if (obj.clipPath) {
|
|
9050
|
+
obj.clipPath.set({
|
|
9051
|
+
left: (obj.clipPath.left ?? 0) + dx,
|
|
9052
|
+
top: (obj.clipPath.top ?? 0) + dy
|
|
9053
|
+
});
|
|
9054
|
+
obj.clipPath.setCoords();
|
|
9055
|
+
}
|
|
9049
9056
|
}
|
|
9050
9057
|
});
|
|
9058
|
+
if (workarea.clipPath) {
|
|
9059
|
+
workarea.clipPath.set({
|
|
9060
|
+
left: (workarea.clipPath.left ?? 0) + dx,
|
|
9061
|
+
top: (workarea.clipPath.top ?? 0) + dy
|
|
9062
|
+
});
|
|
9063
|
+
workarea.clipPath.setCoords();
|
|
9064
|
+
}
|
|
9065
|
+
if (canvas2.backgroundImage) {
|
|
9066
|
+
const bg = canvas2.backgroundImage;
|
|
9067
|
+
bg.set({
|
|
9068
|
+
left: (bg.left ?? 0) + dx,
|
|
9069
|
+
top: (bg.top ?? 0) + dy
|
|
9070
|
+
});
|
|
9071
|
+
bg.setCoords();
|
|
9072
|
+
if (bg.clipPath) {
|
|
9073
|
+
bg.clipPath.set({
|
|
9074
|
+
left: (bg.clipPath.left ?? 0) + dx,
|
|
9075
|
+
top: (bg.clipPath.top ?? 0) + dy
|
|
9076
|
+
});
|
|
9077
|
+
bg.clipPath.setCoords();
|
|
9078
|
+
}
|
|
9079
|
+
}
|
|
9080
|
+
}
|
|
9081
|
+
const bgImg = canvas2.getObjects().find((o2) => o2.name === "bg-image");
|
|
9082
|
+
if (bgImg) {
|
|
9083
|
+
const center = workarea.getCenterPoint();
|
|
9084
|
+
const curWaW = workarea.getScaledWidth();
|
|
9085
|
+
const curWaH = workarea.getScaledHeight();
|
|
9086
|
+
bgImg.set({
|
|
9087
|
+
left: center.x,
|
|
9088
|
+
top: center.y,
|
|
9089
|
+
originX: "center",
|
|
9090
|
+
originY: "center"
|
|
9091
|
+
});
|
|
9092
|
+
bgImg.setCoords();
|
|
9093
|
+
const exportLeft = Math.round(center.x - curWaW / 2);
|
|
9094
|
+
const exportTop = Math.round(center.y - curWaH / 2);
|
|
9095
|
+
const clip2 = createShapeExportClip(
|
|
9096
|
+
shapeType,
|
|
9097
|
+
{ left: exportLeft, top: exportTop, width: curWaW, height: curWaH, rx, ry },
|
|
9098
|
+
currentSide
|
|
9099
|
+
);
|
|
9100
|
+
if (clip2) {
|
|
9101
|
+
clip2.absolutePositioned = true;
|
|
9102
|
+
bgImg.clipPath = clip2;
|
|
9103
|
+
}
|
|
9051
9104
|
}
|
|
9052
9105
|
createOverlays(canvas2, workarea, currentSide);
|
|
9053
9106
|
canvas2.sendObjectToBack(workarea);
|
|
@@ -9066,8 +9119,7 @@
|
|
|
9066
9119
|
} else {
|
|
9067
9120
|
obj.set({
|
|
9068
9121
|
selectable: false,
|
|
9069
|
-
evented: false
|
|
9070
|
-
hasControls: false
|
|
9122
|
+
evented: false
|
|
9071
9123
|
});
|
|
9072
9124
|
}
|
|
9073
9125
|
obj.setCoords();
|
|
@@ -9085,69 +9137,89 @@
|
|
|
9085
9137
|
}, 40);
|
|
9086
9138
|
});
|
|
9087
9139
|
}
|
|
9088
|
-
function getWorkareaDataUrl(cvs, multiplier = 1, pageSide) {
|
|
9140
|
+
function getWorkareaDataUrl(cvs, multiplier = 1, pageSide, includeGuidelines = false) {
|
|
9089
9141
|
const wa2 = cvs.getObjects().find((o2) => o2.name === "workarea");
|
|
9090
9142
|
cvs.discardActiveObject();
|
|
9091
|
-
const
|
|
9143
|
+
const guidesAndOverlays = cvs.getObjects().filter(
|
|
9092
9144
|
(o2) => o2.name?.startsWith("guideline-") || o2.name?.startsWith("overlay-") || o2.name === "guideline-trim" || o2.name === "guideline-safe" || o2.excludeFromExport === true
|
|
9093
9145
|
);
|
|
9094
|
-
const prevVis =
|
|
9095
|
-
|
|
9096
|
-
g2.
|
|
9146
|
+
const prevVis = guidesAndOverlays.map((g2) => g2.visible);
|
|
9147
|
+
guidesAndOverlays.forEach((g2) => {
|
|
9148
|
+
const isGuideline = g2.name?.startsWith("guideline-") || g2.name === "guideline-trim" || g2.name === "guideline-safe";
|
|
9149
|
+
if (includeGuidelines && isGuideline) {
|
|
9150
|
+
g2.visible = true;
|
|
9151
|
+
} else {
|
|
9152
|
+
g2.visible = false;
|
|
9153
|
+
}
|
|
9097
9154
|
});
|
|
9155
|
+
if (includeGuidelines) {
|
|
9156
|
+
const activeGuides = guidesAndOverlays.filter(
|
|
9157
|
+
(g2) => g2.name?.startsWith("guideline-") || g2.name === "guideline-trim" || g2.name === "guideline-safe"
|
|
9158
|
+
);
|
|
9159
|
+
activeGuides.forEach((g2) => cvs.bringObjectToFront(g2));
|
|
9160
|
+
}
|
|
9098
9161
|
const prevBg = cvs.backgroundColor;
|
|
9099
9162
|
cvs.backgroundColor = "";
|
|
9163
|
+
const prevVt = cvs.viewportTransform ? [...cvs.viewportTransform] : [1, 0, 0, 1, 0, 0];
|
|
9164
|
+
const prevZoom = cvs.getZoom();
|
|
9165
|
+
cvs.setViewportTransform([1, 0, 0, 1, 0, 0]);
|
|
9166
|
+
cvs.setZoom(1);
|
|
9100
9167
|
let dataUrl = "";
|
|
9101
|
-
|
|
9102
|
-
|
|
9103
|
-
|
|
9104
|
-
|
|
9105
|
-
|
|
9106
|
-
|
|
9107
|
-
|
|
9108
|
-
|
|
9109
|
-
|
|
9110
|
-
|
|
9111
|
-
|
|
9112
|
-
|
|
9113
|
-
|
|
9114
|
-
|
|
9115
|
-
|
|
9116
|
-
|
|
9117
|
-
|
|
9118
|
-
|
|
9119
|
-
|
|
9120
|
-
|
|
9121
|
-
|
|
9122
|
-
|
|
9123
|
-
|
|
9124
|
-
|
|
9125
|
-
|
|
9126
|
-
|
|
9127
|
-
|
|
9128
|
-
|
|
9129
|
-
|
|
9130
|
-
|
|
9168
|
+
try {
|
|
9169
|
+
if (!wa2) {
|
|
9170
|
+
cvs.renderAll();
|
|
9171
|
+
dataUrl = cvs.toDataURL({ format: "png", multiplier });
|
|
9172
|
+
} else {
|
|
9173
|
+
const center = wa2.getCenterPoint();
|
|
9174
|
+
const scaledW = wa2.getScaledWidth();
|
|
9175
|
+
const scaledH = wa2.getScaledHeight();
|
|
9176
|
+
const shapeType = detectShape(wa2.__shapeType);
|
|
9177
|
+
const activeSide = pageSide || wa2.__pageSide || useHistoryStore.getState().currentPage || "front";
|
|
9178
|
+
const baseRx = (wa2.rx || wa2.__radiusOfCanvas || 0) * (wa2.scaleX || 1);
|
|
9179
|
+
const baseRy = (wa2.ry || wa2.__radiusOfCanvas || 0) * (wa2.scaleY || 1);
|
|
9180
|
+
let exportW = scaledW;
|
|
9181
|
+
let exportH = scaledH;
|
|
9182
|
+
let exportLeft = center.x - exportW / 2;
|
|
9183
|
+
let exportTop = center.y - exportH / 2;
|
|
9184
|
+
if (shapeType === "circle") {
|
|
9185
|
+
const diam = Math.min(scaledW, scaledH);
|
|
9186
|
+
exportW = diam;
|
|
9187
|
+
exportH = diam;
|
|
9188
|
+
exportLeft = center.x - diam / 2;
|
|
9189
|
+
exportTop = center.y - diam / 2;
|
|
9190
|
+
}
|
|
9191
|
+
const clipObj = createShapeExportClip(
|
|
9192
|
+
shapeType,
|
|
9193
|
+
{ left: exportLeft, top: exportTop, width: exportW, height: exportH, rx: baseRx, ry: baseRy },
|
|
9194
|
+
activeSide
|
|
9195
|
+
);
|
|
9196
|
+
const prevClip = cvs.clipPath;
|
|
9197
|
+
if (clipObj) {
|
|
9198
|
+
cvs.clipPath = clipObj;
|
|
9199
|
+
}
|
|
9200
|
+
cvs.renderAll();
|
|
9201
|
+
dataUrl = cvs.toDataURL({
|
|
9202
|
+
format: "png",
|
|
9203
|
+
multiplier,
|
|
9204
|
+
left: exportLeft,
|
|
9205
|
+
top: exportTop,
|
|
9206
|
+
width: exportW,
|
|
9207
|
+
height: exportH
|
|
9208
|
+
});
|
|
9209
|
+
cvs.clipPath = prevClip;
|
|
9131
9210
|
}
|
|
9132
|
-
|
|
9133
|
-
|
|
9134
|
-
|
|
9135
|
-
|
|
9136
|
-
|
|
9137
|
-
top: exportTop,
|
|
9138
|
-
width: exportW,
|
|
9139
|
-
height: exportH
|
|
9211
|
+
} finally {
|
|
9212
|
+
cvs.setViewportTransform(prevVt);
|
|
9213
|
+
cvs.setZoom(prevZoom);
|
|
9214
|
+
guidesAndOverlays.forEach((g2, i2) => {
|
|
9215
|
+
g2.visible = prevVis[i2];
|
|
9140
9216
|
});
|
|
9141
|
-
cvs.
|
|
9217
|
+
cvs.backgroundColor = prevBg;
|
|
9218
|
+
cvs.renderAll();
|
|
9142
9219
|
}
|
|
9143
|
-
guides.forEach((g2, i2) => {
|
|
9144
|
-
g2.visible = prevVis[i2];
|
|
9145
|
-
});
|
|
9146
|
-
cvs.backgroundColor = prevBg;
|
|
9147
|
-
cvs.renderAll();
|
|
9148
9220
|
return dataUrl;
|
|
9149
9221
|
}
|
|
9150
|
-
async function renderPageDataUrl(json, baseCanvas, multiplier = 1, targetSide) {
|
|
9222
|
+
async function renderPageDataUrl(json, baseCanvas, multiplier = 1, targetSide, includeGuidelines = false) {
|
|
9151
9223
|
if (!json) return "";
|
|
9152
9224
|
try {
|
|
9153
9225
|
const hiddenEl = document.createElement("canvas");
|
|
@@ -9165,7 +9237,7 @@
|
|
|
9165
9237
|
hiddenCanvas.requestRenderAll();
|
|
9166
9238
|
await new Promise((r2) => setTimeout(r2, 60));
|
|
9167
9239
|
hiddenCanvas.renderAll();
|
|
9168
|
-
const url = getWorkareaDataUrl(hiddenCanvas, multiplier, targetSide);
|
|
9240
|
+
const url = getWorkareaDataUrl(hiddenCanvas, multiplier, targetSide, includeGuidelines);
|
|
9169
9241
|
hiddenCanvas.dispose();
|
|
9170
9242
|
hiddenEl.remove();
|
|
9171
9243
|
return url;
|
|
@@ -9174,6 +9246,28 @@
|
|
|
9174
9246
|
return "";
|
|
9175
9247
|
}
|
|
9176
9248
|
}
|
|
9249
|
+
const HISTORY_CUSTOM_PROPERTIES = [
|
|
9250
|
+
"name",
|
|
9251
|
+
"id",
|
|
9252
|
+
"selectable",
|
|
9253
|
+
"evented",
|
|
9254
|
+
"hasControls",
|
|
9255
|
+
"lockMovementX",
|
|
9256
|
+
"lockMovementY",
|
|
9257
|
+
"rx",
|
|
9258
|
+
"ry",
|
|
9259
|
+
"__radiusOfCanvas",
|
|
9260
|
+
"__shapeType",
|
|
9261
|
+
"__pageSide",
|
|
9262
|
+
"__printWidth",
|
|
9263
|
+
"__printHeight",
|
|
9264
|
+
"__printDPI",
|
|
9265
|
+
"__baseScale",
|
|
9266
|
+
"__initialZoom",
|
|
9267
|
+
"__borderRect",
|
|
9268
|
+
"__rotationControlSize",
|
|
9269
|
+
"__showDegree"
|
|
9270
|
+
];
|
|
9177
9271
|
const useHistoryStore = create((set, get) => ({
|
|
9178
9272
|
canvas: null,
|
|
9179
9273
|
currentPage: "front",
|
|
@@ -9183,34 +9277,26 @@
|
|
|
9183
9277
|
back: { history: [], redo: [] }
|
|
9184
9278
|
},
|
|
9185
9279
|
setCanvas: (canvas2) => {
|
|
9186
|
-
set(
|
|
9280
|
+
set({
|
|
9187
9281
|
canvas: canvas2,
|
|
9282
|
+
isRestoring: false,
|
|
9188
9283
|
pages: {
|
|
9189
9284
|
front: { history: [], redo: [] },
|
|
9190
9285
|
back: { history: [], redo: [] }
|
|
9191
9286
|
}
|
|
9192
|
-
})
|
|
9287
|
+
});
|
|
9193
9288
|
},
|
|
9194
|
-
// setCanvas: (canvas) => {
|
|
9195
|
-
// set((state) => ({
|
|
9196
|
-
// canvas,
|
|
9197
|
-
// pages: {
|
|
9198
|
-
// ...state.pages,
|
|
9199
|
-
// front: {
|
|
9200
|
-
// history: [
|
|
9201
|
-
// { objects: [] }
|
|
9202
|
-
// ],
|
|
9203
|
-
// redo: [],
|
|
9204
|
-
// },
|
|
9205
|
-
// },
|
|
9206
|
-
// }));
|
|
9207
|
-
// },
|
|
9208
9289
|
setPage: (page) => set({ currentPage: page }),
|
|
9209
9290
|
saveState: () => {
|
|
9210
9291
|
const { canvas: canvas2, currentPage, pages, isRestoring } = get();
|
|
9211
9292
|
if (!canvas2 || isRestoring) return;
|
|
9212
|
-
const
|
|
9213
|
-
const
|
|
9293
|
+
const rawJson = canvas2.toObject(HISTORY_CUSTOM_PROPERTIES);
|
|
9294
|
+
const cleanObjects = (rawJson.objects || []).filter((obj) => {
|
|
9295
|
+
const name = obj.name || "";
|
|
9296
|
+
return name !== "watermark" && !name.startsWith("overlay-") && !name.startsWith("guideline-");
|
|
9297
|
+
});
|
|
9298
|
+
const json = { ...rawJson, objects: cleanObjects };
|
|
9299
|
+
const history = pages[currentPage]?.history ?? [];
|
|
9214
9300
|
const last2 = history[history.length - 1];
|
|
9215
9301
|
if (last2 && JSON.stringify(last2) === JSON.stringify(json)) {
|
|
9216
9302
|
return;
|
|
@@ -9226,9 +9312,11 @@
|
|
|
9226
9312
|
});
|
|
9227
9313
|
},
|
|
9228
9314
|
undo: async () => {
|
|
9229
|
-
const { canvas: canvas2, currentPage, pages } = get();
|
|
9315
|
+
const { canvas: canvas2, currentPage, pages, isRestoring } = get();
|
|
9316
|
+
if (!canvas2 || isRestoring) return;
|
|
9230
9317
|
const page = pages[currentPage];
|
|
9231
|
-
if (!
|
|
9318
|
+
if (!page || page.history.length <= 1) return;
|
|
9319
|
+
canvas2.discardActiveObject();
|
|
9232
9320
|
const current = page.history.at(-1);
|
|
9233
9321
|
const previous = page.history.at(-2);
|
|
9234
9322
|
set({
|
|
@@ -9241,14 +9329,28 @@
|
|
|
9241
9329
|
}
|
|
9242
9330
|
}
|
|
9243
9331
|
});
|
|
9244
|
-
|
|
9245
|
-
|
|
9246
|
-
|
|
9332
|
+
try {
|
|
9333
|
+
await loadPageToCanvas(canvas2, previous, currentPage);
|
|
9334
|
+
canvas2.requestRenderAll();
|
|
9335
|
+
const editorStore = useEditorStore.getState();
|
|
9336
|
+
if (editorStore.pages) {
|
|
9337
|
+
editorStore.setPages({
|
|
9338
|
+
...editorStore.pages,
|
|
9339
|
+
[currentPage]: previous
|
|
9340
|
+
});
|
|
9341
|
+
}
|
|
9342
|
+
} finally {
|
|
9343
|
+
setTimeout(() => {
|
|
9344
|
+
set({ isRestoring: false });
|
|
9345
|
+
}, 80);
|
|
9346
|
+
}
|
|
9247
9347
|
},
|
|
9248
9348
|
redoAction: async () => {
|
|
9249
|
-
const { canvas: canvas2, currentPage, pages } = get();
|
|
9349
|
+
const { canvas: canvas2, currentPage, pages, isRestoring } = get();
|
|
9350
|
+
if (!canvas2 || isRestoring) return;
|
|
9250
9351
|
const page = pages[currentPage];
|
|
9251
|
-
if (!
|
|
9352
|
+
if (!page || page.redo.length === 0) return;
|
|
9353
|
+
canvas2.discardActiveObject();
|
|
9252
9354
|
const next = page.redo[0];
|
|
9253
9355
|
set({
|
|
9254
9356
|
isRestoring: true,
|
|
@@ -9260,9 +9362,21 @@
|
|
|
9260
9362
|
}
|
|
9261
9363
|
}
|
|
9262
9364
|
});
|
|
9263
|
-
|
|
9264
|
-
|
|
9265
|
-
|
|
9365
|
+
try {
|
|
9366
|
+
await loadPageToCanvas(canvas2, next, currentPage);
|
|
9367
|
+
canvas2.requestRenderAll();
|
|
9368
|
+
const editorStore = useEditorStore.getState();
|
|
9369
|
+
if (editorStore.pages) {
|
|
9370
|
+
editorStore.setPages({
|
|
9371
|
+
...editorStore.pages,
|
|
9372
|
+
[currentPage]: next
|
|
9373
|
+
});
|
|
9374
|
+
}
|
|
9375
|
+
} finally {
|
|
9376
|
+
setTimeout(() => {
|
|
9377
|
+
set({ isRestoring: false });
|
|
9378
|
+
}, 80);
|
|
9379
|
+
}
|
|
9266
9380
|
}
|
|
9267
9381
|
}));
|
|
9268
9382
|
const FontService = {
|
|
@@ -9597,6 +9711,189 @@
|
|
|
9597
9711
|
});
|
|
9598
9712
|
triggerDownload(URL.createObjectURL(blob), "design.json");
|
|
9599
9713
|
}
|
|
9714
|
+
const ShapeService = {
|
|
9715
|
+
async getAll(data) {
|
|
9716
|
+
const res = await apiFetch(
|
|
9717
|
+
`/api/cliparts?page=${data.page}&limit=${data.limit}&category=${data.category}&status=true`,
|
|
9718
|
+
{
|
|
9719
|
+
method: "GET"
|
|
9720
|
+
}
|
|
9721
|
+
);
|
|
9722
|
+
return {
|
|
9723
|
+
items: res.data || []
|
|
9724
|
+
};
|
|
9725
|
+
},
|
|
9726
|
+
async getAllExternal(data) {
|
|
9727
|
+
const res = await apiFetch(
|
|
9728
|
+
`/api/external/images?page=${data.page}&limit=${data.limit}&keyType=${data.keyType}&status=true${data.query ? `&query=${data.query}` : ""}`,
|
|
9729
|
+
{
|
|
9730
|
+
method: "GET"
|
|
9731
|
+
}
|
|
9732
|
+
);
|
|
9733
|
+
return {
|
|
9734
|
+
items: res.data || []
|
|
9735
|
+
};
|
|
9736
|
+
},
|
|
9737
|
+
async getTextTemplates(data) {
|
|
9738
|
+
const res = await apiFetch(
|
|
9739
|
+
`/api/text-templates?page=${data.page}&limit=${data.limit}&status=true`,
|
|
9740
|
+
{
|
|
9741
|
+
method: "GET"
|
|
9742
|
+
}
|
|
9743
|
+
);
|
|
9744
|
+
return {
|
|
9745
|
+
items: res.data || []
|
|
9746
|
+
};
|
|
9747
|
+
},
|
|
9748
|
+
async getTextTemplateById(id) {
|
|
9749
|
+
const res = await apiFetch(
|
|
9750
|
+
`/api/text-templates/${id}`,
|
|
9751
|
+
{
|
|
9752
|
+
method: "GET"
|
|
9753
|
+
}
|
|
9754
|
+
);
|
|
9755
|
+
return res.data || null;
|
|
9756
|
+
},
|
|
9757
|
+
async updateTextTemplate(id, body) {
|
|
9758
|
+
const res = await apiFetch(`/api/text-templates/${id}`, {
|
|
9759
|
+
method: "PUT",
|
|
9760
|
+
body
|
|
9761
|
+
});
|
|
9762
|
+
return res;
|
|
9763
|
+
},
|
|
9764
|
+
async convertImages(url) {
|
|
9765
|
+
const res = await apiFetch(`/api/convert-images`, {
|
|
9766
|
+
method: "POST",
|
|
9767
|
+
body: { file: url }
|
|
9768
|
+
});
|
|
9769
|
+
return res;
|
|
9770
|
+
}
|
|
9771
|
+
};
|
|
9772
|
+
async function getBase64ImageFromURL(url) {
|
|
9773
|
+
if (!url) return url;
|
|
9774
|
+
if (url.startsWith("data:image/png") || url.startsWith("data:image/jpeg")) {
|
|
9775
|
+
return url;
|
|
9776
|
+
}
|
|
9777
|
+
const cleanUrl = url.replace(/&/g, "&").trim();
|
|
9778
|
+
const fullUrl = cleanUrl.startsWith("http://") || cleanUrl.startsWith("https://") || cleanUrl.startsWith("blob:") || cleanUrl.startsWith("data:") ? cleanUrl : cleanUrl.startsWith("/") ? `https://upload.vibrantprint.com${cleanUrl}` : `https://upload.vibrantprint.com/${cleanUrl}`;
|
|
9779
|
+
if (typeof window === "undefined" || typeof document === "undefined") {
|
|
9780
|
+
return fullUrl;
|
|
9781
|
+
}
|
|
9782
|
+
try {
|
|
9783
|
+
const dataUrl = await new Promise((resolve, reject) => {
|
|
9784
|
+
const timer = setTimeout(() => reject(new Error("Image load timeout")), 4e3);
|
|
9785
|
+
const img = new Image();
|
|
9786
|
+
img.crossOrigin = "anonymous";
|
|
9787
|
+
img.onload = () => {
|
|
9788
|
+
clearTimeout(timer);
|
|
9789
|
+
try {
|
|
9790
|
+
const cvs = document.createElement("canvas");
|
|
9791
|
+
cvs.width = img.naturalWidth || img.width || 1;
|
|
9792
|
+
cvs.height = img.naturalHeight || img.height || 1;
|
|
9793
|
+
const ctx = cvs.getContext("2d");
|
|
9794
|
+
if (!ctx) return reject(new Error("No 2d context"));
|
|
9795
|
+
ctx.drawImage(img, 0, 0);
|
|
9796
|
+
resolve(cvs.toDataURL("image/png"));
|
|
9797
|
+
} catch (e2) {
|
|
9798
|
+
reject(e2);
|
|
9799
|
+
}
|
|
9800
|
+
};
|
|
9801
|
+
img.onerror = (e2) => {
|
|
9802
|
+
clearTimeout(timer);
|
|
9803
|
+
reject(e2);
|
|
9804
|
+
};
|
|
9805
|
+
img.src = fullUrl;
|
|
9806
|
+
});
|
|
9807
|
+
if (dataUrl && (dataUrl.startsWith("data:image/png") || dataUrl.startsWith("data:image/jpeg"))) {
|
|
9808
|
+
return dataUrl;
|
|
9809
|
+
}
|
|
9810
|
+
} catch {
|
|
9811
|
+
}
|
|
9812
|
+
try {
|
|
9813
|
+
const controller = new AbortController();
|
|
9814
|
+
const fetchTimer = setTimeout(() => controller.abort(), 4e3);
|
|
9815
|
+
const res = await fetch(fullUrl, { signal: controller.signal });
|
|
9816
|
+
clearTimeout(fetchTimer);
|
|
9817
|
+
const blob = await res.blob();
|
|
9818
|
+
const dataUrl = await new Promise((resolve, reject) => {
|
|
9819
|
+
const reader = new FileReader();
|
|
9820
|
+
reader.onloadend = () => {
|
|
9821
|
+
const result = reader.result;
|
|
9822
|
+
if (result && !result.startsWith("data:image/png") && !result.startsWith("data:image/jpeg")) {
|
|
9823
|
+
const img = new Image();
|
|
9824
|
+
const imgTimer = setTimeout(() => resolve(result), 3e3);
|
|
9825
|
+
img.onload = () => {
|
|
9826
|
+
clearTimeout(imgTimer);
|
|
9827
|
+
const cvs = document.createElement("canvas");
|
|
9828
|
+
cvs.width = img.naturalWidth || img.width || 1;
|
|
9829
|
+
cvs.height = img.naturalHeight || img.height || 1;
|
|
9830
|
+
const ctx = cvs.getContext("2d");
|
|
9831
|
+
if (ctx) {
|
|
9832
|
+
ctx.drawImage(img, 0, 0);
|
|
9833
|
+
resolve(cvs.toDataURL("image/png"));
|
|
9834
|
+
} else {
|
|
9835
|
+
resolve(result);
|
|
9836
|
+
}
|
|
9837
|
+
};
|
|
9838
|
+
img.onerror = () => {
|
|
9839
|
+
clearTimeout(imgTimer);
|
|
9840
|
+
resolve(result);
|
|
9841
|
+
};
|
|
9842
|
+
img.src = result;
|
|
9843
|
+
} else {
|
|
9844
|
+
resolve(result);
|
|
9845
|
+
}
|
|
9846
|
+
};
|
|
9847
|
+
reader.onerror = reject;
|
|
9848
|
+
reader.readAsDataURL(blob);
|
|
9849
|
+
});
|
|
9850
|
+
return dataUrl;
|
|
9851
|
+
} catch {
|
|
9852
|
+
try {
|
|
9853
|
+
const backendRes = await ShapeService.convertImages(fullUrl);
|
|
9854
|
+
const converted = backendRes?.url || backendRes?.dataUrl;
|
|
9855
|
+
if (converted && (converted.startsWith("data:image/png") || converted.startsWith("data:image/jpeg"))) {
|
|
9856
|
+
return converted;
|
|
9857
|
+
}
|
|
9858
|
+
} catch {
|
|
9859
|
+
}
|
|
9860
|
+
return fullUrl;
|
|
9861
|
+
}
|
|
9862
|
+
}
|
|
9863
|
+
async function addImageToCanvas(url) {
|
|
9864
|
+
const canvas2 = useEditorStore.getState().canvas;
|
|
9865
|
+
if (!canvas2) return;
|
|
9866
|
+
const workarea = canvas2.getObjects().find((o2) => o2.name === "workarea");
|
|
9867
|
+
if (!workarea) return;
|
|
9868
|
+
let finalUrl = url;
|
|
9869
|
+
try {
|
|
9870
|
+
const b64 = await getBase64ImageFromURL(url);
|
|
9871
|
+
if (b64 && (b64.startsWith("data:image/png") || b64.startsWith("data:image/jpeg"))) {
|
|
9872
|
+
finalUrl = b64;
|
|
9873
|
+
}
|
|
9874
|
+
} catch {
|
|
9875
|
+
}
|
|
9876
|
+
oa.fromURL(finalUrl, { crossOrigin: "anonymous" }).then((img) => {
|
|
9877
|
+
const workareaCenter = workarea.getCenterPoint();
|
|
9878
|
+
const workareaBounds = workarea.getBoundingRect();
|
|
9879
|
+
const maxWidth = workareaBounds.width * 0.7;
|
|
9880
|
+
const maxHeight = workareaBounds.height * 0.7;
|
|
9881
|
+
img.scale(Math.min(maxWidth / img.width, maxHeight / img.height));
|
|
9882
|
+
img.set({
|
|
9883
|
+
left: workareaCenter.x,
|
|
9884
|
+
top: workareaCenter.y,
|
|
9885
|
+
originX: "center",
|
|
9886
|
+
originY: "center",
|
|
9887
|
+
selectable: true,
|
|
9888
|
+
hasControls: true
|
|
9889
|
+
});
|
|
9890
|
+
canvas2.add(img);
|
|
9891
|
+
canvas2.setActiveObject(img);
|
|
9892
|
+
canvas2.requestRenderAll();
|
|
9893
|
+
}).catch((err) => {
|
|
9894
|
+
console.warn("Fabric failed to load image from URL:", url, err);
|
|
9895
|
+
});
|
|
9896
|
+
}
|
|
9600
9897
|
function captureWorkareaSVG(canvas2, workarea, pageSide) {
|
|
9601
9898
|
const targetWorkarea = workarea || canvas2.getObjects().find((obj) => obj.name === "workarea");
|
|
9602
9899
|
if (!targetWorkarea) return null;
|
|
@@ -9676,32 +9973,23 @@
|
|
|
9676
9973
|
];
|
|
9677
9974
|
const urls = [
|
|
9678
9975
|
...new Set(
|
|
9679
|
-
matches.map((m2) => m2[1]).filter((url) => url && (url.startsWith("http") || url.startsWith("blob:")))
|
|
9976
|
+
matches.map((m2) => m2[1]).filter((url) => url && (url.startsWith("http") || url.startsWith("blob:") || url.startsWith("data:") && !url.startsWith("data:image/png") && !url.startsWith("data:image/jpeg")))
|
|
9680
9977
|
)
|
|
9681
9978
|
];
|
|
9682
9979
|
if (urls.length === 0) return svg;
|
|
9683
9980
|
await Promise.all(
|
|
9684
9981
|
urls.map(async (url) => {
|
|
9685
|
-
|
|
9686
|
-
|
|
9982
|
+
try {
|
|
9983
|
+
const base64 = await getBase64ImageFromURL(url);
|
|
9984
|
+
if (base64 && (base64.startsWith("data:image/png") || base64.startsWith("data:image/jpeg"))) {
|
|
9985
|
+
result = result.split(`"${url}"`).join(`"${base64}"`);
|
|
9986
|
+
}
|
|
9987
|
+
} catch {
|
|
9988
|
+
}
|
|
9687
9989
|
})
|
|
9688
9990
|
);
|
|
9689
9991
|
return result;
|
|
9690
9992
|
}
|
|
9691
|
-
async function fetchImageAsBase64(url) {
|
|
9692
|
-
try {
|
|
9693
|
-
const res = await fetch(url, { mode: "cors" });
|
|
9694
|
-
const blob = await res.blob();
|
|
9695
|
-
return new Promise((resolve, reject) => {
|
|
9696
|
-
const reader = new FileReader();
|
|
9697
|
-
reader.onloadend = () => resolve(reader.result);
|
|
9698
|
-
reader.onerror = reject;
|
|
9699
|
-
reader.readAsDataURL(blob);
|
|
9700
|
-
});
|
|
9701
|
-
} catch {
|
|
9702
|
-
return null;
|
|
9703
|
-
}
|
|
9704
|
-
}
|
|
9705
9993
|
var commonjsGlobal = typeof globalThis !== "undefined" ? globalThis : typeof window !== "undefined" ? window : typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : {};
|
|
9706
9994
|
function getDefaultExportFromCjs(x2) {
|
|
9707
9995
|
return x2 && x2.__esModule && Object.prototype.hasOwnProperty.call(x2, "default") ? x2["default"] : x2;
|
|
@@ -99900,7 +100188,16 @@ end`);
|
|
|
99900
100188
|
});
|
|
99901
100189
|
};
|
|
99902
100190
|
for (const [, obj] of context.enumerateIndirectObjects()) {
|
|
100191
|
+
const dict = obj.dict || (obj instanceof PDFDict ? obj : null);
|
|
100192
|
+
const subtype = dict?.get(PDFName.of("Subtype"))?.toString();
|
|
100193
|
+
const type = dict?.get(PDFName.of("Type"))?.toString();
|
|
100194
|
+
if (subtype === "/Image") {
|
|
100195
|
+
continue;
|
|
100196
|
+
}
|
|
99903
100197
|
if (obj && obj.contents && obj.dict) {
|
|
100198
|
+
if (type === "/Font" || type === "/FontDescriptor" || type === "/Metadata") {
|
|
100199
|
+
continue;
|
|
100200
|
+
}
|
|
99904
100201
|
let rawBytes = obj.contents;
|
|
99905
100202
|
const filterStr = obj.dict.get(PDFName.of("Filter"))?.toString() || "";
|
|
99906
100203
|
const isFlate = filterStr.includes("FlateDecode");
|
|
@@ -99922,7 +100219,7 @@ end`);
|
|
|
99922
100219
|
} catch {
|
|
99923
100220
|
}
|
|
99924
100221
|
}
|
|
99925
|
-
if (obj instanceof PDFDict) {
|
|
100222
|
+
if (obj instanceof PDFDict && subtype !== "/Image" && type !== "/Font") {
|
|
99926
100223
|
for (const [k2, v2] of obj.entries()) {
|
|
99927
100224
|
if (v2?.toString() === "/DeviceRGB") obj.set(k2, PDFName.of("DeviceCMYK"));
|
|
99928
100225
|
}
|
|
@@ -99944,7 +100241,6 @@ end`);
|
|
|
99944
100241
|
p2.node.set(PDFName.of("Resources"), res);
|
|
99945
100242
|
}
|
|
99946
100243
|
res.set(PDFName.of("ColorSpace"), context.obj({
|
|
99947
|
-
DefaultRGB: PDFName.of("DeviceCMYK"),
|
|
99948
100244
|
DefaultCMYK: PDFName.of("DeviceCMYK")
|
|
99949
100245
|
}));
|
|
99950
100246
|
}
|
|
@@ -100024,6 +100320,20 @@ end`);
|
|
|
100024
100320
|
function sanitizeSvgForPdf(svg) {
|
|
100025
100321
|
let result = svg;
|
|
100026
100322
|
result = result.replace(/<\?xml[^>]*\?>/gi, "").replace(/<!DOCTYPE[^>]*>/gi, "").trim();
|
|
100323
|
+
if (!result.includes("xmlns:xlink=") && (result.includes("xlink:href") || result.includes("<image"))) {
|
|
100324
|
+
result = result.replace(/<svg\b([^>]*)>/i, '<svg$1 xmlns:xlink="http://www.w3.org/1999/xlink">');
|
|
100325
|
+
}
|
|
100326
|
+
result = result.replace(/<image\b([^>]*)>/gi, (match, attrs) => {
|
|
100327
|
+
let newAttrs = attrs;
|
|
100328
|
+
const xlinkMatch = attrs.match(/xlink:href="([^"]*)"/i);
|
|
100329
|
+
const hrefMatch = attrs.match(/\bhref="([^"]*)"/i);
|
|
100330
|
+
if (xlinkMatch && !hrefMatch) {
|
|
100331
|
+
newAttrs += ` href="${xlinkMatch[1]}"`;
|
|
100332
|
+
} else if (hrefMatch && !xlinkMatch) {
|
|
100333
|
+
newAttrs += ` xlink:href="${hrefMatch[1]}"`;
|
|
100334
|
+
}
|
|
100335
|
+
return `<image${newAttrs}>`;
|
|
100336
|
+
});
|
|
100027
100337
|
result = result.replace(/font-family="([^"]+)"/g, (_2, p1) => {
|
|
100028
100338
|
const first = p1.split(",")[0].replace(/"/g, "").replace(/['"]/g, "").trim();
|
|
100029
100339
|
return `font-family="${first}"`;
|
|
@@ -100070,17 +100380,114 @@ end`);
|
|
|
100070
100380
|
}
|
|
100071
100381
|
}
|
|
100072
100382
|
if (rawBuffer && rawBuffer.byteLength > 0) {
|
|
100073
|
-
|
|
100074
|
-
|
|
100075
|
-
|
|
100076
|
-
|
|
100077
|
-
|
|
100383
|
+
try {
|
|
100384
|
+
const cmykBytes = await convertPdfToDeviceCMYK(rawBuffer);
|
|
100385
|
+
const cmykBlob = new Blob([cmykBytes], { type: "application/pdf" });
|
|
100386
|
+
triggerDownload(URL.createObjectURL(cmykBlob), filename);
|
|
100387
|
+
return;
|
|
100388
|
+
} catch (cmykErr) {
|
|
100389
|
+
console.warn("CMYK tagging failed, falling back to direct clean PDF buffer:", cmykErr);
|
|
100390
|
+
const fallbackBlob = new Blob([rawBuffer], { type: "application/pdf" });
|
|
100391
|
+
triggerDownload(URL.createObjectURL(fallbackBlob), filename);
|
|
100392
|
+
return;
|
|
100393
|
+
}
|
|
100394
|
+
}
|
|
100395
|
+
if (typeof doc.getBlob === "function") {
|
|
100396
|
+
const blob = await doc.getBlob();
|
|
100397
|
+
triggerDownload(URL.createObjectURL(blob), filename);
|
|
100398
|
+
return;
|
|
100078
100399
|
}
|
|
100400
|
+
doc.download(filename);
|
|
100079
100401
|
} catch (err) {
|
|
100080
|
-
console.error("
|
|
100402
|
+
console.error("PDF generation/download error, trying fallback:", err);
|
|
100403
|
+
try {
|
|
100404
|
+
if (typeof doc.getBlob === "function") {
|
|
100405
|
+
const blob = await doc.getBlob();
|
|
100406
|
+
triggerDownload(URL.createObjectURL(blob), filename);
|
|
100407
|
+
return;
|
|
100408
|
+
}
|
|
100409
|
+
} catch {
|
|
100410
|
+
}
|
|
100081
100411
|
doc.download(filename);
|
|
100082
100412
|
}
|
|
100083
100413
|
}
|
|
100414
|
+
function getAllCanvasImages(container) {
|
|
100415
|
+
const list = [];
|
|
100416
|
+
const objs = typeof container.getObjects === "function" ? container.getObjects() : [];
|
|
100417
|
+
for (const o2 of objs) {
|
|
100418
|
+
if (o2.type === "image" || o2.type === "fabricImage") {
|
|
100419
|
+
list.push(o2);
|
|
100420
|
+
} else if (o2.type === "group" && typeof o2.getObjects === "function") {
|
|
100421
|
+
list.push(...getAllCanvasImages(o2));
|
|
100422
|
+
}
|
|
100423
|
+
}
|
|
100424
|
+
return list;
|
|
100425
|
+
}
|
|
100426
|
+
async function inlineImageForExport(obj) {
|
|
100427
|
+
try {
|
|
100428
|
+
const el = obj._element || obj._originalElement;
|
|
100429
|
+
const currentSrc = el?.src || obj.src || (typeof obj.getSrc === "function" ? obj.getSrc() : "");
|
|
100430
|
+
if (currentSrc.startsWith("data:image/png") || currentSrc.startsWith("data:image/jpeg")) {
|
|
100431
|
+
return;
|
|
100432
|
+
}
|
|
100433
|
+
let base64 = null;
|
|
100434
|
+
if (el) {
|
|
100435
|
+
try {
|
|
100436
|
+
if (typeof el.toDataURL === "function") {
|
|
100437
|
+
base64 = el.toDataURL("image/png");
|
|
100438
|
+
} else {
|
|
100439
|
+
const w2 = el.naturalWidth || el.width;
|
|
100440
|
+
const h2 = el.naturalHeight || el.height;
|
|
100441
|
+
if (w2 > 0 && h2 > 0) {
|
|
100442
|
+
const cvs = document.createElement("canvas");
|
|
100443
|
+
cvs.width = w2;
|
|
100444
|
+
cvs.height = h2;
|
|
100445
|
+
const ctx = cvs.getContext("2d");
|
|
100446
|
+
if (ctx) {
|
|
100447
|
+
ctx.drawImage(el, 0, 0);
|
|
100448
|
+
base64 = cvs.toDataURL("image/png");
|
|
100449
|
+
}
|
|
100450
|
+
}
|
|
100451
|
+
}
|
|
100452
|
+
} catch {
|
|
100453
|
+
}
|
|
100454
|
+
}
|
|
100455
|
+
if (!base64 || !base64.startsWith("data:image/png") && !base64.startsWith("data:image/jpeg")) {
|
|
100456
|
+
if (currentSrc) {
|
|
100457
|
+
base64 = await getBase64ImageFromURL(currentSrc);
|
|
100458
|
+
}
|
|
100459
|
+
}
|
|
100460
|
+
if (base64 && (base64.startsWith("data:image/png") || base64.startsWith("data:image/jpeg"))) {
|
|
100461
|
+
if (obj._element) {
|
|
100462
|
+
obj._element.src = base64;
|
|
100463
|
+
if (typeof obj._element.setAttribute === "function") {
|
|
100464
|
+
try {
|
|
100465
|
+
obj._element.setAttribute("src", base64);
|
|
100466
|
+
} catch {
|
|
100467
|
+
}
|
|
100468
|
+
}
|
|
100469
|
+
if (typeof obj._element.decode === "function") {
|
|
100470
|
+
try {
|
|
100471
|
+
await obj._element.decode();
|
|
100472
|
+
} catch {
|
|
100473
|
+
}
|
|
100474
|
+
}
|
|
100475
|
+
}
|
|
100476
|
+
if (obj._originalElement) {
|
|
100477
|
+
obj._originalElement.src = base64;
|
|
100478
|
+
if (typeof obj._originalElement.setAttribute === "function") {
|
|
100479
|
+
try {
|
|
100480
|
+
obj._originalElement.setAttribute("src", base64);
|
|
100481
|
+
} catch {
|
|
100482
|
+
}
|
|
100483
|
+
}
|
|
100484
|
+
}
|
|
100485
|
+
obj.src = base64;
|
|
100486
|
+
}
|
|
100487
|
+
} catch (e2) {
|
|
100488
|
+
console.warn("Failed to inline image for export:", e2);
|
|
100489
|
+
}
|
|
100490
|
+
}
|
|
100084
100491
|
async function downloadCanvasWorkarea(canvas2, type, scale2 = 1, pages, currentPageSide, dpi) {
|
|
100085
100492
|
const workarea = canvas2.getObjects().find((obj) => obj.name === "workarea");
|
|
100086
100493
|
if (!workarea) {
|
|
@@ -100091,6 +100498,10 @@ end`);
|
|
|
100091
100498
|
exportDesignJSON(canvas2, workarea);
|
|
100092
100499
|
return;
|
|
100093
100500
|
}
|
|
100501
|
+
const activeImages = getAllCanvasImages(canvas2);
|
|
100502
|
+
for (const imgObj of activeImages) {
|
|
100503
|
+
await inlineImageForExport(imgObj);
|
|
100504
|
+
}
|
|
100094
100505
|
const hiddenEl = document.createElement("canvas");
|
|
100095
100506
|
hiddenEl.width = canvas2.width;
|
|
100096
100507
|
hiddenEl.height = canvas2.height;
|
|
@@ -100112,6 +100523,8 @@ end`);
|
|
|
100112
100523
|
"cropY",
|
|
100113
100524
|
"rx",
|
|
100114
100525
|
"ry",
|
|
100526
|
+
"crossOrigin",
|
|
100527
|
+
"src",
|
|
100115
100528
|
"__radiusOfCanvas",
|
|
100116
100529
|
"__shapeType",
|
|
100117
100530
|
"__pageSide"
|
|
@@ -100121,8 +100534,31 @@ end`);
|
|
|
100121
100534
|
const activeSide = pageSide || target?.__pageSide || currentSide;
|
|
100122
100535
|
const curW = target.getScaledWidth ? target.getScaledWidth() : target.width * (target.scaleX || 1);
|
|
100123
100536
|
const curH = target.getScaledHeight ? target.getScaledHeight() : target.height * (target.scaleY || 1);
|
|
100124
|
-
const
|
|
100125
|
-
const
|
|
100537
|
+
const center = target.getCenterPoint ? target.getCenterPoint() : { x: (target.left || 0) + curW / 2, y: (target.top || 0) + curH / 2 };
|
|
100538
|
+
const bgImg = hiddenCanvas.getObjects().find((o2) => o2.name === "bg-image");
|
|
100539
|
+
if (bgImg) {
|
|
100540
|
+
bgImg.set({
|
|
100541
|
+
left: center.x,
|
|
100542
|
+
top: center.y,
|
|
100543
|
+
originX: "center",
|
|
100544
|
+
originY: "center"
|
|
100545
|
+
});
|
|
100546
|
+
bgImg.setCoords();
|
|
100547
|
+
const shapeType2 = detectShape(target.__shapeType);
|
|
100548
|
+
const baseRx2 = (target.rx || target.__radiusOfCanvas || 0) * (target.scaleX || 1);
|
|
100549
|
+
const baseRy2 = (target.ry || target.__radiusOfCanvas || 0) * (target.scaleY || 1);
|
|
100550
|
+
const exportLeft2 = Math.round(center.x - curW / 2);
|
|
100551
|
+
const exportTop2 = Math.round(center.y - curH / 2);
|
|
100552
|
+
const clip2 = createShapeExportClip(
|
|
100553
|
+
shapeType2,
|
|
100554
|
+
{ left: exportLeft2, top: exportTop2, width: curW, height: curH, rx: baseRx2, ry: baseRy2 },
|
|
100555
|
+
activeSide
|
|
100556
|
+
);
|
|
100557
|
+
if (clip2) {
|
|
100558
|
+
clip2.absolutePositioned = true;
|
|
100559
|
+
bgImg.clipPath = clip2;
|
|
100560
|
+
}
|
|
100561
|
+
}
|
|
100126
100562
|
if (format === "jpeg") hiddenCanvas.backgroundColor = "#ffffff";
|
|
100127
100563
|
const wm = createWatermark(target, activeSide);
|
|
100128
100564
|
hiddenCanvas.add(wm);
|
|
@@ -100136,35 +100572,40 @@ end`);
|
|
|
100136
100572
|
const shapeType = detectShape(target.__shapeType);
|
|
100137
100573
|
const baseRx = (target.rx || target.__radiusOfCanvas || 0) * (target.scaleX || 1);
|
|
100138
100574
|
const baseRy = (target.ry || target.__radiusOfCanvas || 0) * (target.scaleY || 1);
|
|
100139
|
-
let exportLeft = curLeft;
|
|
100140
|
-
let exportTop = curTop;
|
|
100141
100575
|
let exportW = curW;
|
|
100142
100576
|
let exportH = curH;
|
|
100577
|
+
let exportLeft = center.x - exportW / 2;
|
|
100578
|
+
let exportTop = center.y - exportH / 2;
|
|
100143
100579
|
if (shapeType === "circle") {
|
|
100144
100580
|
const diam = Math.min(curW, curH);
|
|
100145
|
-
exportLeft = curLeft + (curW - diam) / 2;
|
|
100146
|
-
exportTop = curTop + (curH - diam) / 2;
|
|
100147
100581
|
exportW = diam;
|
|
100148
100582
|
exportH = diam;
|
|
100583
|
+
exportLeft = center.x - diam / 2;
|
|
100584
|
+
exportTop = center.y - diam / 2;
|
|
100149
100585
|
}
|
|
100150
100586
|
const exportClip = createShapeExportClip(
|
|
100151
100587
|
shapeType,
|
|
100152
|
-
{ left:
|
|
100588
|
+
{ left: exportLeft, top: exportTop, width: exportW, height: exportH, rx: baseRx, ry: baseRy },
|
|
100153
100589
|
activeSide
|
|
100154
100590
|
);
|
|
100155
100591
|
if (exportClip) {
|
|
100156
100592
|
hiddenCanvas.clipPath = exportClip;
|
|
100157
100593
|
}
|
|
100158
|
-
|
|
100159
|
-
|
|
100160
|
-
|
|
100161
|
-
|
|
100162
|
-
|
|
100163
|
-
|
|
100164
|
-
|
|
100165
|
-
|
|
100166
|
-
|
|
100167
|
-
|
|
100594
|
+
let dataURL = "";
|
|
100595
|
+
try {
|
|
100596
|
+
dataURL = hiddenCanvas.toDataURL({
|
|
100597
|
+
format,
|
|
100598
|
+
quality: 1,
|
|
100599
|
+
multiplier: scale2,
|
|
100600
|
+
left: exportLeft,
|
|
100601
|
+
top: exportTop,
|
|
100602
|
+
width: exportW,
|
|
100603
|
+
height: exportH,
|
|
100604
|
+
enableRetinaScaling: false
|
|
100605
|
+
});
|
|
100606
|
+
} catch (taintErr) {
|
|
100607
|
+
console.warn("hiddenCanvas.toDataURL failed (tainted canvas):", taintErr);
|
|
100608
|
+
}
|
|
100168
100609
|
hiddenCanvas.clipPath = void 0;
|
|
100169
100610
|
hiddenCanvas.remove(wm);
|
|
100170
100611
|
if (format === "jpeg") hiddenCanvas.backgroundColor = "";
|
|
@@ -100223,18 +100664,41 @@ end`);
|
|
|
100223
100664
|
if (curWorkarea) {
|
|
100224
100665
|
curWorkarea.__pageSide = page.side;
|
|
100225
100666
|
}
|
|
100667
|
+
const canvasImages = getAllCanvasImages(hiddenCanvas);
|
|
100668
|
+
for (const imgObj of canvasImages) {
|
|
100669
|
+
await inlineImageForExport(imgObj);
|
|
100670
|
+
}
|
|
100671
|
+
hiddenCanvas.renderAll();
|
|
100226
100672
|
const rawSvg = captureWithWatermark("svg", curWorkarea, page.side);
|
|
100227
100673
|
if (rawSvg) {
|
|
100228
100674
|
try {
|
|
100229
100675
|
const processed = await processSVGWithEmbeds(rawSvg);
|
|
100230
100676
|
const sanitized = sanitizeSvgForPdf(processed);
|
|
100231
|
-
|
|
100677
|
+
const hasUnembedded = /<image\b[^>]*(?:href="https?:\/\/|href="blob:)[^>]*>/i.test(sanitized);
|
|
100678
|
+
if (hasUnembedded) {
|
|
100679
|
+
const rasterImage = captureWithWatermark("png", curWorkarea, page.side);
|
|
100680
|
+
if (rasterImage) {
|
|
100681
|
+
vectorPages.push({ image: rasterImage });
|
|
100682
|
+
} else {
|
|
100683
|
+
vectorPages.push({ svg: sanitized });
|
|
100684
|
+
}
|
|
100685
|
+
} else {
|
|
100686
|
+
vectorPages.push({ svg: sanitized });
|
|
100687
|
+
}
|
|
100232
100688
|
} catch (err) {
|
|
100233
100689
|
console.error("Vector SVG processing failed, falling back to image:", err);
|
|
100234
|
-
|
|
100690
|
+
const rasterImage = captureWithWatermark("png", curWorkarea, page.side);
|
|
100691
|
+
if (rasterImage) {
|
|
100692
|
+
vectorPages.push({ image: rasterImage });
|
|
100693
|
+
} else {
|
|
100694
|
+
vectorPages.push({ svg: rawSvg });
|
|
100695
|
+
}
|
|
100235
100696
|
}
|
|
100236
100697
|
} else {
|
|
100237
|
-
|
|
100698
|
+
const rasterImage = captureWithWatermark("png", curWorkarea, page.side);
|
|
100699
|
+
if (rasterImage) {
|
|
100700
|
+
vectorPages.push({ image: rasterImage });
|
|
100701
|
+
}
|
|
100238
100702
|
}
|
|
100239
100703
|
}
|
|
100240
100704
|
await generateAndDownloadPDF(vectorPages, workarea.width, workarea.height);
|
|
@@ -100431,15 +100895,17 @@ end`);
|
|
|
100431
100895
|
}
|
|
100432
100896
|
);
|
|
100433
100897
|
}
|
|
100434
|
-
function PreviewModal({ onClose }) {
|
|
100898
|
+
function PreviewModal({ onClose, onUpload }) {
|
|
100435
100899
|
const canvas2 = useEditorStore((s2) => s2.canvas);
|
|
100436
100900
|
const { currentPage } = useHistoryStore();
|
|
100437
100901
|
const productData = useProductStore((s2) => s2.productData);
|
|
100438
100902
|
const isTwoSided = productData?.optionsMap?.printing === "4/4";
|
|
100439
100903
|
const [viewMode, setViewMode] = React.useState("3d");
|
|
100904
|
+
const [showGuidelines, setShowGuidelines] = React.useState(true);
|
|
100440
100905
|
const [activeSide, setActiveSide] = React.useState(currentPage || "front");
|
|
100441
|
-
const [
|
|
100442
|
-
const [
|
|
100906
|
+
const [frontSnapshot, setFrontSnapshot] = React.useState("");
|
|
100907
|
+
const [backSnapshot, setBackSnapshot] = React.useState("");
|
|
100908
|
+
const [isLoading, setIsLoading] = React.useState(true);
|
|
100443
100909
|
const [rotX, setRotX] = React.useState(15);
|
|
100444
100910
|
const [rotY, setRotY] = React.useState(-25);
|
|
100445
100911
|
const [isAutoSpin, setIsAutoSpin] = React.useState(true);
|
|
@@ -100449,24 +100915,27 @@ end`);
|
|
|
100449
100915
|
if (!canvas2) return;
|
|
100450
100916
|
useHistoryStore.getState().saveState();
|
|
100451
100917
|
let isMounted = true;
|
|
100918
|
+
setIsLoading(true);
|
|
100452
100919
|
const loadSides = async () => {
|
|
100453
100920
|
const currentSide = useHistoryStore.getState().currentPage || "front";
|
|
100454
100921
|
const editorPages = useEditorStore.getState().pages || {};
|
|
100455
100922
|
const historyPages = useHistoryStore.getState().pages || {};
|
|
100456
|
-
const
|
|
100923
|
+
const currentSnapshot = getWorkareaDataUrl(canvas2, 2, currentSide, showGuidelines);
|
|
100457
100924
|
const opposingSide = currentSide === "front" ? "back" : "front";
|
|
100458
100925
|
const opposingJson = editorPages[opposingSide] || historyPages[opposingSide]?.history?.at(-1) || null;
|
|
100459
|
-
let
|
|
100926
|
+
let opposingSnapshot = "";
|
|
100460
100927
|
if (isTwoSided && opposingJson) {
|
|
100461
|
-
|
|
100928
|
+
opposingSnapshot = await renderPageDataUrl(opposingJson, canvas2, 2, opposingSide, showGuidelines);
|
|
100462
100929
|
}
|
|
100930
|
+
const initialFront = currentSide === "front" ? currentSnapshot : opposingSnapshot;
|
|
100931
|
+
const initialBack = currentSide === "front" ? opposingSnapshot : currentSnapshot;
|
|
100463
100932
|
if (isMounted) {
|
|
100464
|
-
|
|
100465
|
-
|
|
100466
|
-
|
|
100467
|
-
|
|
100468
|
-
|
|
100469
|
-
|
|
100933
|
+
setFrontSnapshot(initialFront);
|
|
100934
|
+
setBackSnapshot(initialBack);
|
|
100935
|
+
setIsLoading(false);
|
|
100936
|
+
if (onUpload) {
|
|
100937
|
+
const activeUrl = (activeSide === "front" ? initialFront : initialBack) || initialFront;
|
|
100938
|
+
onUpload({ front: initialFront, back: initialBack || void 0, active: activeUrl });
|
|
100470
100939
|
}
|
|
100471
100940
|
}
|
|
100472
100941
|
};
|
|
@@ -100474,7 +100943,7 @@ end`);
|
|
|
100474
100943
|
return () => {
|
|
100475
100944
|
isMounted = false;
|
|
100476
100945
|
};
|
|
100477
|
-
}, [canvas2, isTwoSided]);
|
|
100946
|
+
}, [canvas2, isTwoSided, showGuidelines]);
|
|
100478
100947
|
React.useEffect(() => {
|
|
100479
100948
|
if (!isAutoSpin || isDragging || viewMode !== "3d") return;
|
|
100480
100949
|
let animId;
|
|
@@ -100507,6 +100976,9 @@ end`);
|
|
|
100507
100976
|
const isCircle = shapeType === "circle";
|
|
100508
100977
|
const cardAspectRatio = isCircle ? 1 : waW && waH ? waW / waH : 1.75;
|
|
100509
100978
|
const getShapeStyle = (side) => {
|
|
100979
|
+
if (showGuidelines) {
|
|
100980
|
+
return {};
|
|
100981
|
+
}
|
|
100510
100982
|
const baseRadius = workarea?.rx || workarea?.__radiusOfCanvas || 0;
|
|
100511
100983
|
const rxPct = waW > 0 ? (baseRadius || waW / 8) / waW * 100 : 12.5;
|
|
100512
100984
|
const ryPct = waH > 0 ? (baseRadius || waH / 8) / waH * 100 : 12.5;
|
|
@@ -100548,14 +101020,14 @@ end`);
|
|
|
100548
101020
|
return {};
|
|
100549
101021
|
}
|
|
100550
101022
|
};
|
|
100551
|
-
const currentProof = activeSide === "front" ?
|
|
101023
|
+
const currentProof = activeSide === "front" ? frontSnapshot : backSnapshot;
|
|
100552
101024
|
const normalizedRotY = (rotY % 360 + 360) % 360;
|
|
100553
101025
|
const isFrontFacing = normalizedRotY <= 90 || normalizedRotY >= 270;
|
|
100554
101026
|
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "fixed inset-0 z-[99999] bg-black/60 backdrop-blur-sm flex items-center justify-center p-4 select-none", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative w-full max-w-6xl bg-white border border-gray-200 rounded-2xl shadow-2xl overflow-hidden flex flex-col h-[85vh]", children: [
|
|
100555
101027
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-5 py-3 border-b border-gray-200 bg-gray-50/90", children: [
|
|
100556
101028
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
|
|
100557
101029
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "h-2.5 w-2.5 rounded-full bg-[#02bc71] animate-pulse" }),
|
|
100558
|
-
/* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-sm font-bold text-gray-900", children: "
|
|
101030
|
+
/* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-sm font-bold text-gray-900", children: "Design Preview" }),
|
|
100559
101031
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center bg-gray-200 p-0.5 rounded-lg border border-gray-300 text-xs", children: [
|
|
100560
101032
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
100561
101033
|
"button",
|
|
@@ -100575,7 +101047,23 @@ end`);
|
|
|
100575
101047
|
children: "2D Proof"
|
|
100576
101048
|
}
|
|
100577
101049
|
)
|
|
100578
|
-
] })
|
|
101050
|
+
] }),
|
|
101051
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
101052
|
+
"button",
|
|
101053
|
+
{
|
|
101054
|
+
type: "button",
|
|
101055
|
+
onClick: () => setShowGuidelines((prev) => !prev),
|
|
101056
|
+
className: `flex items-center gap-1.5 px-3 py-1 rounded-lg text-xs font-semibold cursor-pointer border transition-all ${showGuidelines ? "bg-emerald-50 text-emerald-800 border-emerald-300 shadow-sm" : "bg-white text-gray-600 border-gray-300 hover:bg-gray-100"}`,
|
|
101057
|
+
title: showGuidelines ? "Click to view finished trimmed cut" : "Click to view bleed, trim & safe line guidelines",
|
|
101058
|
+
children: [
|
|
101059
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: `w-2 h-2 rounded-full ${showGuidelines ? "bg-emerald-500 animate-pulse" : "bg-gray-400"}` }),
|
|
101060
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
|
|
101061
|
+
"Guidelines: ",
|
|
101062
|
+
showGuidelines ? "ON" : "OFF"
|
|
101063
|
+
] })
|
|
101064
|
+
]
|
|
101065
|
+
}
|
|
101066
|
+
)
|
|
100579
101067
|
] }),
|
|
100580
101068
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
100581
101069
|
"button",
|
|
@@ -100587,6 +101075,27 @@ end`);
|
|
|
100587
101075
|
}
|
|
100588
101076
|
)
|
|
100589
101077
|
] }),
|
|
101078
|
+
showGuidelines && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center justify-between px-5 py-2 bg-emerald-50/70 border-b border-emerald-100 text-xs text-gray-700 gap-2", children: [
|
|
101079
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-4 flex-wrap", children: [
|
|
101080
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "font-semibold text-emerald-950 flex items-center gap-1.5", children: [
|
|
101081
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "inline-block w-2 h-2 rounded-full bg-emerald-600" }),
|
|
101082
|
+
"Print Guidelines:"
|
|
101083
|
+
] }),
|
|
101084
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5", children: [
|
|
101085
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "w-4 h-0 border-t-2 border-dashed border-red-500 inline-block" }),
|
|
101086
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium text-red-700", children: "Trim Line (Cut line)" })
|
|
101087
|
+
] }),
|
|
101088
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5", children: [
|
|
101089
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "w-4 h-0 border-t-2 border-dashed border-emerald-600 inline-block" }),
|
|
101090
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium text-emerald-700", children: "Safe Line (Keep text inside)" })
|
|
101091
|
+
] }),
|
|
101092
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5", children: [
|
|
101093
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "w-3.5 h-3 rounded-sm border border-dashed border-gray-400 bg-white inline-block" }),
|
|
101094
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium text-gray-600", children: "Bleed Area (Outer edge)" })
|
|
101095
|
+
] })
|
|
101096
|
+
] }),
|
|
101097
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[11px] text-gray-500 italic hidden md:inline", children: "Toggle Guidelines OFF for finished cut preview" })
|
|
101098
|
+
] }),
|
|
100590
101099
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
100591
101100
|
"div",
|
|
100592
101101
|
{
|
|
@@ -100620,10 +101129,10 @@ end`);
|
|
|
100620
101129
|
...getShapeStyle("front")
|
|
100621
101130
|
},
|
|
100622
101131
|
className: "absolute inset-0 overflow-hidden bg-transparent",
|
|
100623
|
-
children:
|
|
101132
|
+
children: frontSnapshot ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
100624
101133
|
"img",
|
|
100625
101134
|
{
|
|
100626
|
-
src:
|
|
101135
|
+
src: frontSnapshot,
|
|
100627
101136
|
alt: "Front",
|
|
100628
101137
|
style: getShapeStyle("front"),
|
|
100629
101138
|
className: "w-full h-full object-contain pointer-events-none block"
|
|
@@ -100632,8 +101141,8 @@ end`);
|
|
|
100632
101141
|
"div",
|
|
100633
101142
|
{
|
|
100634
101143
|
style: getShapeStyle("front"),
|
|
100635
|
-
className: "w-full h-full bg-white flex items-center justify-center text-xs text-gray-
|
|
100636
|
-
children: "Front Design"
|
|
101144
|
+
className: "w-full h-full bg-white flex flex-col items-center justify-center text-xs text-gray-500 gap-2 p-4 text-center",
|
|
101145
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Front Design" })
|
|
100637
101146
|
}
|
|
100638
101147
|
)
|
|
100639
101148
|
}
|
|
@@ -100648,10 +101157,10 @@ end`);
|
|
|
100648
101157
|
...getShapeStyle("back")
|
|
100649
101158
|
},
|
|
100650
101159
|
className: "absolute inset-0 overflow-hidden bg-transparent",
|
|
100651
|
-
children: isTwoSided ?
|
|
101160
|
+
children: isTwoSided ? backSnapshot ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
100652
101161
|
"img",
|
|
100653
101162
|
{
|
|
100654
|
-
src:
|
|
101163
|
+
src: backSnapshot,
|
|
100655
101164
|
alt: "Back",
|
|
100656
101165
|
style: getShapeStyle("back"),
|
|
100657
101166
|
className: "w-full h-full object-contain pointer-events-none block"
|
|
@@ -100660,7 +101169,8 @@ end`);
|
|
|
100660
101169
|
"div",
|
|
100661
101170
|
{
|
|
100662
101171
|
style: getShapeStyle("back"),
|
|
100663
|
-
className: "w-full h-full bg-white flex flex-col items-center justify-center text-xs text-gray-
|
|
101172
|
+
className: "w-full h-full bg-white flex flex-col items-center justify-center text-xs text-gray-500 gap-2 p-4 text-center",
|
|
101173
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Back Design" })
|
|
100664
101174
|
}
|
|
100665
101175
|
) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
100666
101176
|
"div",
|
|
@@ -100711,7 +101221,7 @@ end`);
|
|
|
100711
101221
|
maxWidth: "85%",
|
|
100712
101222
|
...getShapeStyle(activeSide)
|
|
100713
101223
|
},
|
|
100714
|
-
className:
|
|
101224
|
+
className: `relative overflow-hidden flex items-center justify-center transition-all ${showGuidelines ? "bg-white shadow-xl ring-1 ring-black/10" : "bg-transparent shadow-lg"}`,
|
|
100715
101225
|
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
100716
101226
|
"img",
|
|
100717
101227
|
{
|
|
@@ -100722,7 +101232,10 @@ end`);
|
|
|
100722
101232
|
}
|
|
100723
101233
|
)
|
|
100724
101234
|
}
|
|
100725
|
-
) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-8 text-center bg-
|
|
101235
|
+
) : isLoading ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-8 text-center bg-white rounded-2xl border border-gray-200 shadow-sm flex flex-col items-center justify-center gap-3", children: [
|
|
101236
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-8 h-8 border-3 border-[#02bc71] border-t-transparent rounded-full animate-spin" }),
|
|
101237
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-semibold text-gray-800", children: "Generating preview..." })
|
|
101238
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-8 text-center bg-gray-50 rounded-xl border border-gray-200 text-xs text-gray-400", children: [
|
|
100726
101239
|
"No preview available for ",
|
|
100727
101240
|
activeSide,
|
|
100728
101241
|
" side."
|
|
@@ -100780,7 +101293,10 @@ end`);
|
|
|
100780
101293
|
children: "Reset"
|
|
100781
101294
|
}
|
|
100782
101295
|
)
|
|
100783
|
-
] }) : /* @__PURE__ */ jsxRuntime.
|
|
101296
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2.5 text-xs", children: [
|
|
101297
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-gray-500 font-medium", children: "Flat 2D High-Resolution Render" }),
|
|
101298
|
+
showGuidelines ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "px-2 py-0.5 rounded-full bg-emerald-100 text-emerald-800 font-semibold text-[11px] border border-emerald-300", children: "Bleed, Trim & Safe Lines" }) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "px-2 py-0.5 rounded-full bg-gray-200 text-gray-700 font-semibold text-[11px]", children: "Finished Trimmed Cut" })
|
|
101299
|
+
] }),
|
|
100784
101300
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
100785
101301
|
"button",
|
|
100786
101302
|
{
|
|
@@ -101473,64 +101989,6 @@ end`);
|
|
|
101473
101989
|
}
|
|
101474
101990
|
window.history.replaceState({}, "", url.toString());
|
|
101475
101991
|
}
|
|
101476
|
-
const ShapeService = {
|
|
101477
|
-
async getAll(data) {
|
|
101478
|
-
const res = await apiFetch(
|
|
101479
|
-
`/api/cliparts?page=${data.page}&limit=${data.limit}&category=${data.category}&status=true`,
|
|
101480
|
-
{
|
|
101481
|
-
method: "GET"
|
|
101482
|
-
}
|
|
101483
|
-
);
|
|
101484
|
-
return {
|
|
101485
|
-
items: res.data || []
|
|
101486
|
-
};
|
|
101487
|
-
},
|
|
101488
|
-
async getAllExternal(data) {
|
|
101489
|
-
const res = await apiFetch(
|
|
101490
|
-
`/api/external/images?page=${data.page}&limit=${data.limit}&keyType=${data.keyType}&status=true${data.query ? `&query=${data.query}` : ""}`,
|
|
101491
|
-
{
|
|
101492
|
-
method: "GET"
|
|
101493
|
-
}
|
|
101494
|
-
);
|
|
101495
|
-
return {
|
|
101496
|
-
items: res.data || []
|
|
101497
|
-
};
|
|
101498
|
-
},
|
|
101499
|
-
async getTextTemplates(data) {
|
|
101500
|
-
const res = await apiFetch(
|
|
101501
|
-
`/api/text-templates?page=${data.page}&limit=${data.limit}&status=true`,
|
|
101502
|
-
{
|
|
101503
|
-
method: "GET"
|
|
101504
|
-
}
|
|
101505
|
-
);
|
|
101506
|
-
return {
|
|
101507
|
-
items: res.data || []
|
|
101508
|
-
};
|
|
101509
|
-
},
|
|
101510
|
-
async getTextTemplateById(id) {
|
|
101511
|
-
const res = await apiFetch(
|
|
101512
|
-
`/api/text-templates/${id}`,
|
|
101513
|
-
{
|
|
101514
|
-
method: "GET"
|
|
101515
|
-
}
|
|
101516
|
-
);
|
|
101517
|
-
return res.data || null;
|
|
101518
|
-
},
|
|
101519
|
-
async updateTextTemplate(id, body) {
|
|
101520
|
-
const res = await apiFetch(`/api/text-templates/${id}`, {
|
|
101521
|
-
method: "PUT",
|
|
101522
|
-
body
|
|
101523
|
-
});
|
|
101524
|
-
return res;
|
|
101525
|
-
},
|
|
101526
|
-
async convertImages(url) {
|
|
101527
|
-
const res = await apiFetch(`/api/convert-images`, {
|
|
101528
|
-
method: "POST",
|
|
101529
|
-
body: { file: url }
|
|
101530
|
-
});
|
|
101531
|
-
return res;
|
|
101532
|
-
}
|
|
101533
|
-
};
|
|
101534
101992
|
function spaNavigate(path) {
|
|
101535
101993
|
if (typeof window === "undefined") return;
|
|
101536
101994
|
try {
|
|
@@ -102437,32 +102895,6 @@ end`);
|
|
|
102437
102895
|
]
|
|
102438
102896
|
}
|
|
102439
102897
|
];
|
|
102440
|
-
async function addImageToCanvas(url) {
|
|
102441
|
-
const canvas2 = useEditorStore.getState().canvas;
|
|
102442
|
-
if (!canvas2) return;
|
|
102443
|
-
const workarea = canvas2.getObjects().find((o2) => o2.name === "workarea");
|
|
102444
|
-
if (!workarea) return;
|
|
102445
|
-
oa.fromURL(url, { crossOrigin: "anonymous" }).then((img) => {
|
|
102446
|
-
const workareaCenter = workarea.getCenterPoint();
|
|
102447
|
-
const workareaBounds = workarea.getBoundingRect();
|
|
102448
|
-
const maxWidth = workareaBounds.width * 0.7;
|
|
102449
|
-
const maxHeight = workareaBounds.height * 0.7;
|
|
102450
|
-
img.scale(Math.min(maxWidth / img.width, maxHeight / img.height));
|
|
102451
|
-
img.set({
|
|
102452
|
-
left: workareaCenter.x,
|
|
102453
|
-
top: workareaCenter.y,
|
|
102454
|
-
originX: "center",
|
|
102455
|
-
originY: "center",
|
|
102456
|
-
selectable: true,
|
|
102457
|
-
hasControls: true
|
|
102458
|
-
});
|
|
102459
|
-
canvas2.add(img);
|
|
102460
|
-
canvas2.setActiveObject(img);
|
|
102461
|
-
canvas2.requestRenderAll();
|
|
102462
|
-
}).catch((err) => {
|
|
102463
|
-
console.warn("Fabric failed to load image from URL:", url, err);
|
|
102464
|
-
});
|
|
102465
|
-
}
|
|
102466
102898
|
function getWorkareaMetrics(canvas2) {
|
|
102467
102899
|
const workarea = canvas2.getObjects().find((obj) => obj.name === "workarea");
|
|
102468
102900
|
if (!workarea) return null;
|
|
@@ -103489,10 +103921,15 @@ end`);
|
|
|
103489
103921
|
try {
|
|
103490
103922
|
startLoading();
|
|
103491
103923
|
const image = await ShapeService.convertImages(url);
|
|
103492
|
-
const imageUrl = image?.success ? image?.url : null;
|
|
103493
|
-
if (imageUrl)
|
|
103924
|
+
const imageUrl = image?.success || image?.status ? image?.url : null;
|
|
103925
|
+
if (imageUrl) {
|
|
103926
|
+
addImageToCanvas(imageUrl);
|
|
103927
|
+
} else {
|
|
103928
|
+
alert(image?.data || image?.message || "Could not load image. Provider rate limit reached (429).");
|
|
103929
|
+
}
|
|
103494
103930
|
} catch (error2) {
|
|
103495
103931
|
console.error("Failed to convert image:", error2);
|
|
103932
|
+
alert(error2?.message || "Failed to convert image");
|
|
103496
103933
|
} finally {
|
|
103497
103934
|
stopLoading();
|
|
103498
103935
|
if (window.innerWidth < 768) {
|
|
@@ -103737,14 +104174,24 @@ end`);
|
|
|
103737
104174
|
startLoading();
|
|
103738
104175
|
const targetUrl = item.largeImage || item.image;
|
|
103739
104176
|
if (!targetUrl) return;
|
|
103740
|
-
|
|
103741
|
-
|
|
103742
|
-
if (imageUrl)
|
|
103743
|
-
|
|
103744
|
-
|
|
104177
|
+
let image = await ShapeService.convertImages(targetUrl);
|
|
104178
|
+
let imageUrl = image?.success || image?.status ? image?.url : null;
|
|
104179
|
+
if (!imageUrl && item.image && item.image !== targetUrl) {
|
|
104180
|
+
const fallbackRes = await ShapeService.convertImages(item.image);
|
|
104181
|
+
imageUrl = fallbackRes?.success || fallbackRes?.status ? fallbackRes?.url : item.image;
|
|
104182
|
+
}
|
|
104183
|
+
if (imageUrl) {
|
|
104184
|
+
await addImageToCanvas(imageUrl);
|
|
104185
|
+
if (window.innerWidth < 768) {
|
|
104186
|
+
useSidePanelStore.getState().setActivePanel(null);
|
|
104187
|
+
}
|
|
104188
|
+
} else {
|
|
104189
|
+
const errMsg = image?.data || image?.message || "Stock image provider rate limit reached (429). Please try another image.";
|
|
104190
|
+
alert(`Unable to load image: ${errMsg}`);
|
|
103745
104191
|
}
|
|
103746
104192
|
} catch (error2) {
|
|
103747
104193
|
console.error("Failed to convert image:", error2);
|
|
104194
|
+
alert(`Image loading error: ${error2?.message || "Please try another image."}`);
|
|
103748
104195
|
} finally {
|
|
103749
104196
|
stopLoading();
|
|
103750
104197
|
}
|
|
@@ -103774,17 +104221,21 @@ end`);
|
|
|
103774
104221
|
canvas2.getObjects("image").forEach((o2) => {
|
|
103775
104222
|
if (o2.name === "bg-image") canvas2.remove(o2);
|
|
103776
104223
|
});
|
|
103777
|
-
const
|
|
103778
|
-
const
|
|
103779
|
-
const
|
|
104224
|
+
const center = workarea.getCenterPoint();
|
|
104225
|
+
const waW = workarea.getScaledWidth();
|
|
104226
|
+
const waH = workarea.getScaledHeight();
|
|
104227
|
+
const shapeType = detectShape(workarea.__shapeType);
|
|
104228
|
+
const activeSide = workarea.__pageSide || useHistoryStore.getState().currentPage || "front";
|
|
104229
|
+
const baseRx = (workarea.rx || workarea.__radiusOfCanvas || 0) * (workarea.scaleX || 1);
|
|
104230
|
+
const baseRy = (workarea.ry || workarea.__radiusOfCanvas || 0) * (workarea.scaleY || 1);
|
|
103780
104231
|
oa.fromURL(url + "?t=vb", { crossOrigin: "anonymous" }).then((img) => {
|
|
103781
104232
|
const imgW = img.width || 1;
|
|
103782
104233
|
const imgH = img.height || 1;
|
|
103783
|
-
const scale2 = Math.max(
|
|
104234
|
+
const scale2 = Math.max(waW / imgW, waH / imgH);
|
|
103784
104235
|
img.scale(scale2);
|
|
103785
104236
|
img.set({
|
|
103786
|
-
left:
|
|
103787
|
-
top:
|
|
104237
|
+
left: center.x,
|
|
104238
|
+
top: center.y,
|
|
103788
104239
|
originX: "center",
|
|
103789
104240
|
originY: "center",
|
|
103790
104241
|
selectable: false,
|
|
@@ -103794,15 +104245,22 @@ end`);
|
|
|
103794
104245
|
lockMovementY: false,
|
|
103795
104246
|
name: "bg-image"
|
|
103796
104247
|
});
|
|
103797
|
-
|
|
103798
|
-
|
|
103799
|
-
|
|
103800
|
-
|
|
103801
|
-
height:
|
|
104248
|
+
const exportLeft = Math.round(center.x - waW / 2);
|
|
104249
|
+
const exportTop = Math.round(center.y - waH / 2);
|
|
104250
|
+
const clip2 = createShapeExportClip(
|
|
104251
|
+
shapeType,
|
|
104252
|
+
{ left: exportLeft, top: exportTop, width: waW, height: waH, rx: baseRx, ry: baseRy },
|
|
104253
|
+
activeSide
|
|
104254
|
+
) || new jr({
|
|
104255
|
+
left: center.x,
|
|
104256
|
+
top: center.y,
|
|
104257
|
+
width: waW,
|
|
104258
|
+
height: waH,
|
|
103802
104259
|
originX: "center",
|
|
103803
104260
|
originY: "center",
|
|
103804
104261
|
absolutePositioned: true
|
|
103805
104262
|
});
|
|
104263
|
+
img.clipPath = clip2;
|
|
103806
104264
|
canvas2.add(img);
|
|
103807
104265
|
canvas2.sendObjectToBack(img);
|
|
103808
104266
|
canvas2.sendObjectToBack(workarea);
|
|
@@ -109453,6 +109911,282 @@ END:VCARD`;
|
|
|
109453
109911
|
] })
|
|
109454
109912
|
] });
|
|
109455
109913
|
}
|
|
109914
|
+
function BgRemoveIcon({ size = 16, className = "" }) {
|
|
109915
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
109916
|
+
"svg",
|
|
109917
|
+
{
|
|
109918
|
+
width: size,
|
|
109919
|
+
height: size,
|
|
109920
|
+
viewBox: "0 0 24 24",
|
|
109921
|
+
fill: "none",
|
|
109922
|
+
stroke: "currentColor",
|
|
109923
|
+
strokeWidth: "2",
|
|
109924
|
+
strokeLinecap: "round",
|
|
109925
|
+
strokeLinejoin: "round",
|
|
109926
|
+
className: `shrink-0 ${className}`,
|
|
109927
|
+
children: [
|
|
109928
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 3v3" }),
|
|
109929
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M18.5 5.5l-2.1 2.1" }),
|
|
109930
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M21 12h-3" }),
|
|
109931
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M3 21l9-9" }),
|
|
109932
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12.2 7.8a2 2 0 0 1 2.8 2.8L6.5 19.1a2 2 0 0 1-1.4.6H3v-2.1a2 2 0 0 1 .6-1.4l8.6-8.4z" })
|
|
109933
|
+
]
|
|
109934
|
+
}
|
|
109935
|
+
);
|
|
109936
|
+
}
|
|
109937
|
+
const IMGLY_CDN_PATH = "https://staticimgly.com/@imgly/background-removal-data/1.7.0/dist/";
|
|
109938
|
+
let removeBackgroundFn = null;
|
|
109939
|
+
async function getRemoveBackground() {
|
|
109940
|
+
if (!removeBackgroundFn) {
|
|
109941
|
+
const mod = await import("@imgly/background-removal");
|
|
109942
|
+
removeBackgroundFn = mod.removeBackground || mod.default;
|
|
109943
|
+
}
|
|
109944
|
+
return removeBackgroundFn;
|
|
109945
|
+
}
|
|
109946
|
+
function blobToDataUrl(blob) {
|
|
109947
|
+
return new Promise((resolve, reject) => {
|
|
109948
|
+
const reader = new FileReader();
|
|
109949
|
+
reader.onloadend = () => resolve(reader.result);
|
|
109950
|
+
reader.onerror = reject;
|
|
109951
|
+
reader.readAsDataURL(blob);
|
|
109952
|
+
});
|
|
109953
|
+
}
|
|
109954
|
+
function loadImageElement(src) {
|
|
109955
|
+
return new Promise((resolve, reject) => {
|
|
109956
|
+
const img = new Image();
|
|
109957
|
+
img.crossOrigin = "anonymous";
|
|
109958
|
+
img.onload = () => resolve(img);
|
|
109959
|
+
img.onerror = (err) => reject(err);
|
|
109960
|
+
img.src = src;
|
|
109961
|
+
});
|
|
109962
|
+
}
|
|
109963
|
+
async function extractImageBlob(imageObj) {
|
|
109964
|
+
const currentElement = imageObj.getElement();
|
|
109965
|
+
const currentSrc = imageObj.getSrc() || currentElement?.src;
|
|
109966
|
+
if (currentSrc && (currentSrc.startsWith("data:") || currentSrc.startsWith("blob:"))) {
|
|
109967
|
+
try {
|
|
109968
|
+
const res = await fetch(currentSrc);
|
|
109969
|
+
const blob = await res.blob();
|
|
109970
|
+
if (blob && blob.size > 0) return blob;
|
|
109971
|
+
} catch (e2) {
|
|
109972
|
+
console.warn("Direct data/blob URL fetch failed, trying canvas export:", e2);
|
|
109973
|
+
}
|
|
109974
|
+
}
|
|
109975
|
+
if (currentElement && (currentElement.naturalWidth || currentElement.width)) {
|
|
109976
|
+
try {
|
|
109977
|
+
const w2 = currentElement.naturalWidth || currentElement.width;
|
|
109978
|
+
const h2 = currentElement.naturalHeight || currentElement.height;
|
|
109979
|
+
const offscreen = document.createElement("canvas");
|
|
109980
|
+
offscreen.width = w2;
|
|
109981
|
+
offscreen.height = h2;
|
|
109982
|
+
const ctx = offscreen.getContext("2d");
|
|
109983
|
+
if (ctx) {
|
|
109984
|
+
ctx.drawImage(currentElement, 0, 0, w2, h2);
|
|
109985
|
+
const blob = await new Promise((resolve) => {
|
|
109986
|
+
offscreen.toBlob((b2) => resolve(b2), "image/png");
|
|
109987
|
+
});
|
|
109988
|
+
if (blob && blob.size > 0) return blob;
|
|
109989
|
+
}
|
|
109990
|
+
} catch (e2) {
|
|
109991
|
+
console.warn("Offscreen canvas render failed (possible CORS):", e2);
|
|
109992
|
+
}
|
|
109993
|
+
}
|
|
109994
|
+
try {
|
|
109995
|
+
const dataUrl = imageObj.toDataURL({ format: "png" });
|
|
109996
|
+
if (dataUrl && dataUrl.startsWith("data:")) {
|
|
109997
|
+
const res = await fetch(dataUrl);
|
|
109998
|
+
const blob = await res.blob();
|
|
109999
|
+
if (blob && blob.size > 0) return blob;
|
|
110000
|
+
}
|
|
110001
|
+
} catch (e2) {
|
|
110002
|
+
console.warn("Fabric toDataURL failed:", e2);
|
|
110003
|
+
}
|
|
110004
|
+
if (currentSrc) {
|
|
110005
|
+
try {
|
|
110006
|
+
const res = await fetch(currentSrc, { mode: "cors" });
|
|
110007
|
+
const blob = await res.blob();
|
|
110008
|
+
if (blob && blob.size > 0) return blob;
|
|
110009
|
+
} catch (e2) {
|
|
110010
|
+
console.warn("Remote URL fetch failed:", e2);
|
|
110011
|
+
}
|
|
110012
|
+
}
|
|
110013
|
+
throw new Error("Unable to extract valid image blob from object.");
|
|
110014
|
+
}
|
|
110015
|
+
class BackgroundRemovalService {
|
|
110016
|
+
/**
|
|
110017
|
+
* Automatically cuts out the background of a FabricImage using client-side AI
|
|
110018
|
+
*/
|
|
110019
|
+
static async removeBackgroundAI(canvas2, imageObj, onProgress) {
|
|
110020
|
+
if (!canvas2 || !imageObj) return false;
|
|
110021
|
+
const loader = useLoaderStore.getState();
|
|
110022
|
+
try {
|
|
110023
|
+
loader.startLoading("Extracting image data...");
|
|
110024
|
+
onProgress?.("Extracting image data...");
|
|
110025
|
+
const updateProgress = (msg) => {
|
|
110026
|
+
loader.setLoading(true, msg);
|
|
110027
|
+
onProgress?.(msg);
|
|
110028
|
+
};
|
|
110029
|
+
const currentElement = imageObj.getElement();
|
|
110030
|
+
const currentSrc = imageObj.getSrc() || currentElement?.src;
|
|
110031
|
+
if (!currentSrc && !currentElement) {
|
|
110032
|
+
throw new Error("Unable to locate image source");
|
|
110033
|
+
}
|
|
110034
|
+
if (!imageObj.__originalSrc) {
|
|
110035
|
+
imageObj.__originalSrc = currentSrc;
|
|
110036
|
+
}
|
|
110037
|
+
const imageBlob = await extractImageBlob(imageObj);
|
|
110038
|
+
updateProgress("Initializing AI model...");
|
|
110039
|
+
const removeBackground = await getRemoveBackground();
|
|
110040
|
+
let blob;
|
|
110041
|
+
try {
|
|
110042
|
+
blob = await removeBackground(imageBlob, {
|
|
110043
|
+
publicPath: IMGLY_CDN_PATH,
|
|
110044
|
+
model: "isnet_quint8",
|
|
110045
|
+
output: {
|
|
110046
|
+
format: "image/png",
|
|
110047
|
+
quality: 0.95
|
|
110048
|
+
},
|
|
110049
|
+
progress: (key, current, total) => {
|
|
110050
|
+
if (total > 0) {
|
|
110051
|
+
const pct = Math.round(current / total * 100);
|
|
110052
|
+
updateProgress(`Downloading AI model: ${pct}%`);
|
|
110053
|
+
} else {
|
|
110054
|
+
updateProgress("Processing AI model...");
|
|
110055
|
+
}
|
|
110056
|
+
}
|
|
110057
|
+
});
|
|
110058
|
+
} catch (err1) {
|
|
110059
|
+
console.warn("isnet_quint8 failed, attempting isnet_fp16 fallback:", err1);
|
|
110060
|
+
blob = await removeBackground(imageBlob, {
|
|
110061
|
+
publicPath: IMGLY_CDN_PATH,
|
|
110062
|
+
model: "isnet_fp16",
|
|
110063
|
+
output: {
|
|
110064
|
+
format: "image/png",
|
|
110065
|
+
quality: 0.95
|
|
110066
|
+
},
|
|
110067
|
+
progress: (key, current, total) => {
|
|
110068
|
+
if (total > 0) {
|
|
110069
|
+
const pct = Math.round(current / total * 100);
|
|
110070
|
+
updateProgress(`Downloading AI model: ${pct}%`);
|
|
110071
|
+
} else {
|
|
110072
|
+
updateProgress("Processing AI model...");
|
|
110073
|
+
}
|
|
110074
|
+
}
|
|
110075
|
+
});
|
|
110076
|
+
}
|
|
110077
|
+
updateProgress("Applying transparent cutout...");
|
|
110078
|
+
const transparentDataUrl = await blobToDataUrl(blob);
|
|
110079
|
+
const newImgElement = await loadImageElement(transparentDataUrl);
|
|
110080
|
+
const origScaleX = imageObj.scaleX ?? 1;
|
|
110081
|
+
const origScaleY = imageObj.scaleY ?? 1;
|
|
110082
|
+
const origWidth = imageObj.width ?? 1;
|
|
110083
|
+
const origHeight = imageObj.height ?? 1;
|
|
110084
|
+
imageObj.setElement(newImgElement);
|
|
110085
|
+
if (newImgElement.naturalWidth && newImgElement.naturalHeight) {
|
|
110086
|
+
imageObj.set({
|
|
110087
|
+
scaleX: origWidth * origScaleX / newImgElement.naturalWidth,
|
|
110088
|
+
scaleY: origHeight * origScaleY / newImgElement.naturalHeight
|
|
110089
|
+
});
|
|
110090
|
+
}
|
|
110091
|
+
imageObj.__isBgRemoved = true;
|
|
110092
|
+
imageObj.setCoords();
|
|
110093
|
+
canvas2.requestRenderAll();
|
|
110094
|
+
useHistoryStore.getState().saveState();
|
|
110095
|
+
return true;
|
|
110096
|
+
} catch (err) {
|
|
110097
|
+
console.error("Background removal error:", err);
|
|
110098
|
+
throw err;
|
|
110099
|
+
} finally {
|
|
110100
|
+
loader.stopLoading();
|
|
110101
|
+
}
|
|
110102
|
+
}
|
|
110103
|
+
/**
|
|
110104
|
+
* Restores the original image background prior to AI cutout
|
|
110105
|
+
*/
|
|
110106
|
+
static async restoreOriginalBackground(canvas2, imageObj) {
|
|
110107
|
+
if (!canvas2 || !imageObj) return false;
|
|
110108
|
+
const originalSrc = imageObj.__originalSrc;
|
|
110109
|
+
if (!originalSrc) return false;
|
|
110110
|
+
const loader = useLoaderStore.getState();
|
|
110111
|
+
try {
|
|
110112
|
+
loader.startLoading("Restoring original background...");
|
|
110113
|
+
const origImgElement = await loadImageElement(originalSrc);
|
|
110114
|
+
const origScaleX = imageObj.scaleX ?? 1;
|
|
110115
|
+
const origScaleY = imageObj.scaleY ?? 1;
|
|
110116
|
+
const origWidth = imageObj.width ?? 1;
|
|
110117
|
+
const origHeight = imageObj.height ?? 1;
|
|
110118
|
+
imageObj.setElement(origImgElement);
|
|
110119
|
+
if (origImgElement.naturalWidth && origImgElement.naturalHeight) {
|
|
110120
|
+
imageObj.set({
|
|
110121
|
+
scaleX: origWidth * origScaleX / origImgElement.naturalWidth,
|
|
110122
|
+
scaleY: origHeight * origScaleY / origImgElement.naturalHeight
|
|
110123
|
+
});
|
|
110124
|
+
}
|
|
110125
|
+
delete imageObj.__originalSrc;
|
|
110126
|
+
imageObj.__isBgRemoved = false;
|
|
110127
|
+
this.removeColorRemovalFilter(canvas2, imageObj, false);
|
|
110128
|
+
imageObj.setCoords();
|
|
110129
|
+
canvas2.requestRenderAll();
|
|
110130
|
+
useHistoryStore.getState().saveState();
|
|
110131
|
+
return true;
|
|
110132
|
+
} catch (err) {
|
|
110133
|
+
console.error("Failed to restore original background:", err);
|
|
110134
|
+
return false;
|
|
110135
|
+
} finally {
|
|
110136
|
+
loader.stopLoading();
|
|
110137
|
+
}
|
|
110138
|
+
}
|
|
110139
|
+
/**
|
|
110140
|
+
* Applies Fabric native RemoveColor filter for instant solid-color transparency
|
|
110141
|
+
*/
|
|
110142
|
+
static applyColorRemovalFilter(canvas2, imageObj, color = "#FFFFFF", distance = 0.15, saveHistory = true) {
|
|
110143
|
+
if (!canvas2 || !imageObj) return;
|
|
110144
|
+
const normDistance = Math.max(0.01, Math.min(0.99, distance));
|
|
110145
|
+
let filter = (imageObj.filters || []).find(
|
|
110146
|
+
(f) => f.type === "RemoveColor"
|
|
110147
|
+
);
|
|
110148
|
+
if (!filter) {
|
|
110149
|
+
filter = new Sh.RemoveColor({
|
|
110150
|
+
color,
|
|
110151
|
+
distance: normDistance
|
|
110152
|
+
});
|
|
110153
|
+
imageObj.filters = [...imageObj.filters || [], filter];
|
|
110154
|
+
} else {
|
|
110155
|
+
filter.color = color;
|
|
110156
|
+
filter.distance = normDistance;
|
|
110157
|
+
}
|
|
110158
|
+
imageObj.applyFilters();
|
|
110159
|
+
canvas2.requestRenderAll();
|
|
110160
|
+
if (saveHistory) {
|
|
110161
|
+
useHistoryStore.getState().saveState();
|
|
110162
|
+
}
|
|
110163
|
+
}
|
|
110164
|
+
/**
|
|
110165
|
+
* Removes the solid-color background filter
|
|
110166
|
+
*/
|
|
110167
|
+
static removeColorRemovalFilter(canvas2, imageObj, saveHistory = true) {
|
|
110168
|
+
if (!canvas2 || !imageObj || !imageObj.filters) return;
|
|
110169
|
+
imageObj.filters = imageObj.filters.filter(
|
|
110170
|
+
(f) => f.type !== "RemoveColor"
|
|
110171
|
+
);
|
|
110172
|
+
imageObj.applyFilters();
|
|
110173
|
+
canvas2.requestRenderAll();
|
|
110174
|
+
if (saveHistory) {
|
|
110175
|
+
useHistoryStore.getState().saveState();
|
|
110176
|
+
}
|
|
110177
|
+
}
|
|
110178
|
+
/**
|
|
110179
|
+
* Checks if an image has active background removal (AI or color filter)
|
|
110180
|
+
*/
|
|
110181
|
+
static hasBackgroundRemoval(imageObj) {
|
|
110182
|
+
if (!imageObj) return false;
|
|
110183
|
+
const hasAI = !!imageObj.__originalSrc || !!imageObj.__isBgRemoved;
|
|
110184
|
+
const hasFilter = !!(imageObj.filters || []).some(
|
|
110185
|
+
(f) => f.type === "RemoveColor"
|
|
110186
|
+
);
|
|
110187
|
+
return hasAI || hasFilter;
|
|
110188
|
+
}
|
|
110189
|
+
}
|
|
109456
110190
|
function EffectsPanel() {
|
|
109457
110191
|
const canvas2 = useEditorStore((s2) => s2.canvas);
|
|
109458
110192
|
const saveState = useHistoryStore((s2) => s2.saveState);
|
|
@@ -109474,6 +110208,84 @@ END:VCARD`;
|
|
|
109474
110208
|
const { objects } = useSelectionStore();
|
|
109475
110209
|
const replaceState = useEffectStore((s2) => s2.replaceState);
|
|
109476
110210
|
const activeObject = objects[0];
|
|
110211
|
+
const [isRemovingBg, setIsRemovingBg] = React.useState(false);
|
|
110212
|
+
const [bgProgress, setBgProgress] = React.useState("");
|
|
110213
|
+
const [colorRemovalEnabled, setColorRemovalEnabled] = React.useState(false);
|
|
110214
|
+
const [colorRemovalColor, setColorRemovalColor] = React.useState("#FFFFFF");
|
|
110215
|
+
const [colorRemovalDistance, setColorRemovalDistance] = React.useState(15);
|
|
110216
|
+
const isImg = !!activeObject && (activeObject.type === "image" || activeObject.type === "FabricImage" || activeObject.isType?.("image") || activeObject.isType?.("FabricImage") || typeof activeObject.getElement === "function");
|
|
110217
|
+
const hasOriginal = !!activeObject?.__originalSrc;
|
|
110218
|
+
const handleRemoveBgAI = async () => {
|
|
110219
|
+
if (!canvas2 || !activeObject || isRemovingBg) return;
|
|
110220
|
+
try {
|
|
110221
|
+
setIsRemovingBg(true);
|
|
110222
|
+
setBgProgress("Starting AI cutout...");
|
|
110223
|
+
await BackgroundRemovalService.removeBackgroundAI(
|
|
110224
|
+
canvas2,
|
|
110225
|
+
activeObject,
|
|
110226
|
+
(msg) => setBgProgress(msg)
|
|
110227
|
+
);
|
|
110228
|
+
} catch (err) {
|
|
110229
|
+
console.error("Failed to remove background:", err);
|
|
110230
|
+
alert("Could not remove background. Please ensure the image is accessible.");
|
|
110231
|
+
} finally {
|
|
110232
|
+
setIsRemovingBg(false);
|
|
110233
|
+
setBgProgress("");
|
|
110234
|
+
}
|
|
110235
|
+
};
|
|
110236
|
+
const handleRestoreBg = async () => {
|
|
110237
|
+
if (!canvas2 || !activeObject || isRemovingBg) return;
|
|
110238
|
+
await BackgroundRemovalService.restoreOriginalBackground(canvas2, activeObject);
|
|
110239
|
+
setColorRemovalEnabled(false);
|
|
110240
|
+
};
|
|
110241
|
+
const handleToggleColorRemoval = (enabled) => {
|
|
110242
|
+
if (!canvas2 || !activeObject) return;
|
|
110243
|
+
setColorRemovalEnabled(enabled);
|
|
110244
|
+
if (enabled) {
|
|
110245
|
+
BackgroundRemovalService.applyColorRemovalFilter(
|
|
110246
|
+
canvas2,
|
|
110247
|
+
activeObject,
|
|
110248
|
+
colorRemovalColor,
|
|
110249
|
+
colorRemovalDistance / 100,
|
|
110250
|
+
true
|
|
110251
|
+
);
|
|
110252
|
+
} else {
|
|
110253
|
+
BackgroundRemovalService.removeColorRemovalFilter(
|
|
110254
|
+
canvas2,
|
|
110255
|
+
activeObject,
|
|
110256
|
+
true
|
|
110257
|
+
);
|
|
110258
|
+
}
|
|
110259
|
+
};
|
|
110260
|
+
const handleColorChange = (hex) => {
|
|
110261
|
+
if (!canvas2 || !activeObject) return;
|
|
110262
|
+
setColorRemovalColor(hex);
|
|
110263
|
+
if (colorRemovalEnabled) {
|
|
110264
|
+
BackgroundRemovalService.applyColorRemovalFilter(
|
|
110265
|
+
canvas2,
|
|
110266
|
+
activeObject,
|
|
110267
|
+
hex,
|
|
110268
|
+
colorRemovalDistance / 100,
|
|
110269
|
+
true
|
|
110270
|
+
);
|
|
110271
|
+
}
|
|
110272
|
+
};
|
|
110273
|
+
const handleDistanceChange = (distPct) => {
|
|
110274
|
+
if (!canvas2 || !activeObject) return;
|
|
110275
|
+
setColorRemovalDistance(distPct);
|
|
110276
|
+
if (colorRemovalEnabled) {
|
|
110277
|
+
BackgroundRemovalService.applyColorRemovalFilter(
|
|
110278
|
+
canvas2,
|
|
110279
|
+
activeObject,
|
|
110280
|
+
colorRemovalColor,
|
|
110281
|
+
distPct / 100,
|
|
110282
|
+
false
|
|
110283
|
+
);
|
|
110284
|
+
}
|
|
110285
|
+
};
|
|
110286
|
+
const handleDistanceCommit = () => {
|
|
110287
|
+
saveState();
|
|
110288
|
+
};
|
|
109477
110289
|
React.useLayoutEffect(() => {
|
|
109478
110290
|
const newState = {
|
|
109479
110291
|
...initialState,
|
|
@@ -109502,6 +110314,16 @@ END:VCARD`;
|
|
|
109502
110314
|
newState.contrast = { enabled: true, value: Math.round(val * 100) };
|
|
109503
110315
|
}
|
|
109504
110316
|
});
|
|
110317
|
+
const rmFilter = obj.filters.find((f) => f.type === "RemoveColor");
|
|
110318
|
+
if (rmFilter) {
|
|
110319
|
+
setColorRemovalEnabled(true);
|
|
110320
|
+
setColorRemovalColor(rmFilter.color || "#FFFFFF");
|
|
110321
|
+
setColorRemovalDistance(Math.round((rmFilter.distance ?? 0.15) * 100));
|
|
110322
|
+
} else {
|
|
110323
|
+
setColorRemovalEnabled(false);
|
|
110324
|
+
}
|
|
110325
|
+
} else {
|
|
110326
|
+
setColorRemovalEnabled(false);
|
|
109505
110327
|
}
|
|
109506
110328
|
if (obj.shadow) {
|
|
109507
110329
|
try {
|
|
@@ -109549,6 +110371,105 @@ END:VCARD`;
|
|
|
109549
110371
|
{ id: "warm", label: "Warm" }
|
|
109550
110372
|
];
|
|
109551
110373
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 overflow-y-auto custom-scrollbar overflow-x-hidden px-4 custom-scrollbar", children: [
|
|
110374
|
+
isImg && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-neutral-850 border border-neutral-700/70 rounded-xl p-3 mb-5 space-y-3", children: [
|
|
110375
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
|
|
110376
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
110377
|
+
/* @__PURE__ */ jsxRuntime.jsx(BgRemoveIcon, { size: 18, className: "text-[#02bc71]" }),
|
|
110378
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-medium text-white", children: "Background Removal" })
|
|
110379
|
+
] }),
|
|
110380
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px] font-medium tracking-wide uppercase px-1.5 py-0.5 rounded bg-[#02bc71]/20 text-[#02bc71] border border-[#02bc71]/30", children: "AI Powered" })
|
|
110381
|
+
] }),
|
|
110382
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
|
|
110383
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2", children: [
|
|
110384
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
110385
|
+
"button",
|
|
110386
|
+
{
|
|
110387
|
+
type: "button",
|
|
110388
|
+
disabled: isRemovingBg,
|
|
110389
|
+
onClick: handleRemoveBgAI,
|
|
110390
|
+
className: `flex-1 flex items-center justify-center gap-2 py-2 px-3 rounded-lg text-xs font-medium transition-all ${isRemovingBg ? "bg-neutral-700 text-neutral-400 cursor-not-allowed" : "bg-[#02bc71] hover:bg-[#029a5d] text-white shadow active:scale-95 cursor-pointer"}`,
|
|
110391
|
+
children: isRemovingBg ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
110392
|
+
/* @__PURE__ */ jsxRuntime.jsxs("svg", { className: "animate-spin h-3.5 w-3.5 text-white", viewBox: "0 0 24 24", fill: "none", children: [
|
|
110393
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }),
|
|
110394
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { className: "opacity-75", fill: "currentColor", d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" })
|
|
110395
|
+
] }),
|
|
110396
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "Processing..." })
|
|
110397
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
110398
|
+
/* @__PURE__ */ jsxRuntime.jsx(BgRemoveIcon, { size: 15 }),
|
|
110399
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "Cutout Subject (AI)" })
|
|
110400
|
+
] })
|
|
110401
|
+
}
|
|
110402
|
+
),
|
|
110403
|
+
hasOriginal && /* @__PURE__ */ jsxRuntime.jsx(
|
|
110404
|
+
"button",
|
|
110405
|
+
{
|
|
110406
|
+
type: "button",
|
|
110407
|
+
disabled: isRemovingBg,
|
|
110408
|
+
onClick: handleRestoreBg,
|
|
110409
|
+
title: "Restore original image background",
|
|
110410
|
+
className: "px-3 py-2 rounded-lg text-xs font-medium bg-neutral-700 hover:bg-neutral-600 text-neutral-200 border border-neutral-600 transition cursor-pointer hover:text-white",
|
|
110411
|
+
children: "Restore"
|
|
110412
|
+
}
|
|
110413
|
+
)
|
|
110414
|
+
] }),
|
|
110415
|
+
bgProgress && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[11px] text-[#02bc71] italic text-center animate-pulse pt-0.5", children: bgProgress })
|
|
110416
|
+
] }),
|
|
110417
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pt-2 border-t border-neutral-700/50", children: [
|
|
110418
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-between items-center mb-2", children: [
|
|
110419
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-neutral-300 font-medium", children: "Remove Solid Color" }),
|
|
110420
|
+
/* @__PURE__ */ jsxRuntime.jsxs("label", { className: "relative inline-flex items-center cursor-pointer", children: [
|
|
110421
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
110422
|
+
"input",
|
|
110423
|
+
{
|
|
110424
|
+
type: "checkbox",
|
|
110425
|
+
checked: colorRemovalEnabled,
|
|
110426
|
+
onChange: (e2) => handleToggleColorRemoval(e2.target.checked),
|
|
110427
|
+
className: "sr-only peer"
|
|
110428
|
+
}
|
|
110429
|
+
),
|
|
110430
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-8 h-4 bg-neutral-700 rounded-full peer-checked:bg-[#02bc71] transition" }),
|
|
110431
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute left-0.5 top-0.5 w-3 h-3 bg-white rounded-full transition peer-checked:left-4.5" })
|
|
110432
|
+
] })
|
|
110433
|
+
] }),
|
|
110434
|
+
colorRemovalEnabled && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2.5 pt-1", children: [
|
|
110435
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
|
|
110436
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs opacity-70", children: "Target Color" }),
|
|
110437
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
110438
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[11px] text-neutral-400 font-mono", children: colorRemovalColor.toUpperCase() }),
|
|
110439
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
110440
|
+
CMYKColorPicker,
|
|
110441
|
+
{
|
|
110442
|
+
value: colorRemovalColor,
|
|
110443
|
+
onChange: handleColorChange
|
|
110444
|
+
}
|
|
110445
|
+
)
|
|
110446
|
+
] })
|
|
110447
|
+
] }),
|
|
110448
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1", children: [
|
|
110449
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-between items-center text-xs", children: [
|
|
110450
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "opacity-70", children: "Tolerance / Distance" }),
|
|
110451
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "font-mono text-neutral-400", children: [
|
|
110452
|
+
colorRemovalDistance,
|
|
110453
|
+
"%"
|
|
110454
|
+
] })
|
|
110455
|
+
] }),
|
|
110456
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
110457
|
+
"input",
|
|
110458
|
+
{
|
|
110459
|
+
type: "range",
|
|
110460
|
+
min: 1,
|
|
110461
|
+
max: 99,
|
|
110462
|
+
value: colorRemovalDistance,
|
|
110463
|
+
onChange: (e2) => handleDistanceChange(Number(e2.target.value)),
|
|
110464
|
+
onMouseUp: handleDistanceCommit,
|
|
110465
|
+
onTouchEnd: handleDistanceCommit,
|
|
110466
|
+
className: "w-full accent-[#02bc71] cursor-pointer"
|
|
110467
|
+
}
|
|
110468
|
+
)
|
|
110469
|
+
] })
|
|
110470
|
+
] })
|
|
110471
|
+
] })
|
|
110472
|
+
] }),
|
|
109552
110473
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-2 gap-3 mb-6", children: PRESETS.map((p2) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
109553
110474
|
"button",
|
|
109554
110475
|
{
|
|
@@ -156144,7 +157065,7 @@ ${elements2.join("\n")}
|
|
|
156144
157065
|
}
|
|
156145
157066
|
);
|
|
156146
157067
|
}
|
|
156147
|
-
function AlignLeftIcon() {
|
|
157068
|
+
function AlignLeftIcon$1() {
|
|
156148
157069
|
return /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "18", height: "18", viewBox: "0 0 16 16", fill: "currentColor", children: [
|
|
156149
157070
|
/* @__PURE__ */ jsxRuntime.jsx("rect", { x: "1", y: "2", width: "10", height: "2" }),
|
|
156150
157071
|
/* @__PURE__ */ jsxRuntime.jsx("rect", { x: "1", y: "6", width: "14", height: "2" }),
|
|
@@ -156152,7 +157073,7 @@ ${elements2.join("\n")}
|
|
|
156152
157073
|
/* @__PURE__ */ jsxRuntime.jsx("rect", { x: "1", y: "14", width: "14", height: "2" })
|
|
156153
157074
|
] });
|
|
156154
157075
|
}
|
|
156155
|
-
function AlignCenterIcon() {
|
|
157076
|
+
function AlignCenterIcon$1() {
|
|
156156
157077
|
return /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "18", height: "18", viewBox: "0 0 16 16", fill: "currentColor", children: [
|
|
156157
157078
|
/* @__PURE__ */ jsxRuntime.jsx("rect", { x: "3", y: "2", width: "10", height: "2" }),
|
|
156158
157079
|
/* @__PURE__ */ jsxRuntime.jsx("rect", { x: "1", y: "6", width: "14", height: "2" }),
|
|
@@ -156160,7 +157081,7 @@ ${elements2.join("\n")}
|
|
|
156160
157081
|
/* @__PURE__ */ jsxRuntime.jsx("rect", { x: "1", y: "14", width: "14", height: "2" })
|
|
156161
157082
|
] });
|
|
156162
157083
|
}
|
|
156163
|
-
function AlignRightIcon() {
|
|
157084
|
+
function AlignRightIcon$1() {
|
|
156164
157085
|
return /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "18", height: "18", viewBox: "0 0 16 16", fill: "currentColor", children: [
|
|
156165
157086
|
/* @__PURE__ */ jsxRuntime.jsx("rect", { x: "5", y: "2", width: "10", height: "2" }),
|
|
156166
157087
|
/* @__PURE__ */ jsxRuntime.jsx("rect", { x: "1", y: "6", width: "14", height: "2" }),
|
|
@@ -156229,9 +157150,9 @@ ${elements2.join("\n")}
|
|
|
156229
157150
|
onClick: () => onChange(next),
|
|
156230
157151
|
className: "p-1 bg-neutral-800 rounded hover:bg-neutral-700",
|
|
156231
157152
|
children: [
|
|
156232
|
-
value === "left" && /* @__PURE__ */ jsxRuntime.jsx(AlignLeftIcon, {}),
|
|
156233
|
-
value === "center" && /* @__PURE__ */ jsxRuntime.jsx(AlignCenterIcon, {}),
|
|
156234
|
-
value === "right" && /* @__PURE__ */ jsxRuntime.jsx(AlignRightIcon, {})
|
|
157153
|
+
value === "left" && /* @__PURE__ */ jsxRuntime.jsx(AlignLeftIcon$1, {}),
|
|
157154
|
+
value === "center" && /* @__PURE__ */ jsxRuntime.jsx(AlignCenterIcon$1, {}),
|
|
157155
|
+
value === "right" && /* @__PURE__ */ jsxRuntime.jsx(AlignRightIcon$1, {})
|
|
156235
157156
|
]
|
|
156236
157157
|
}
|
|
156237
157158
|
);
|
|
@@ -156944,8 +157865,13 @@ ${elements2.join("\n")}
|
|
|
156944
157865
|
const { type, objects } = useSelectionStore();
|
|
156945
157866
|
const { canvas: canvas2, startCropping } = useEditorStore();
|
|
156946
157867
|
const saveState = useHistoryStore((s2) => s2.saveState);
|
|
156947
|
-
const hasMask = !!canvas2?.getActiveObject()?.clipPath;
|
|
156948
157868
|
const setActivePanel = useSidePanelStore((s2) => s2.setActivePanel);
|
|
157869
|
+
const [isRemovingBg, setIsRemovingBg] = React.useState(false);
|
|
157870
|
+
const [bgStatus, setBgStatus] = React.useState(null);
|
|
157871
|
+
const activeObj = canvas2?.getActiveObject();
|
|
157872
|
+
const hasMask = !!activeObj?.clipPath;
|
|
157873
|
+
activeObj && (activeObj.type === "image" || activeObj.isType?.("image"));
|
|
157874
|
+
const isBgRemoved = !!activeObj?.__originalSrc || !!activeObj?.__isBgRemoved;
|
|
156949
157875
|
if (type !== "image" || objects?.[0]?.name === "qr" || objects?.[0]?.name === "barcode")
|
|
156950
157876
|
return null;
|
|
156951
157877
|
const handleApplyMaskClick = () => {
|
|
@@ -156968,6 +157894,33 @@ ${elements2.join("\n")}
|
|
|
156968
157894
|
startCrop(canvas2, obj);
|
|
156969
157895
|
startCropping();
|
|
156970
157896
|
};
|
|
157897
|
+
const handleRemoveBgClick = async () => {
|
|
157898
|
+
if (!canvas2 || !activeObj || isRemovingBg) return;
|
|
157899
|
+
if (isBgRemoved) {
|
|
157900
|
+
setIsRemovingBg(true);
|
|
157901
|
+
try {
|
|
157902
|
+
await BackgroundRemovalService.restoreOriginalBackground(canvas2, activeObj);
|
|
157903
|
+
} finally {
|
|
157904
|
+
setIsRemovingBg(false);
|
|
157905
|
+
}
|
|
157906
|
+
return;
|
|
157907
|
+
}
|
|
157908
|
+
try {
|
|
157909
|
+
setIsRemovingBg(true);
|
|
157910
|
+
setBgStatus("Removing BG...");
|
|
157911
|
+
await BackgroundRemovalService.removeBackgroundAI(
|
|
157912
|
+
canvas2,
|
|
157913
|
+
activeObj,
|
|
157914
|
+
(status) => setBgStatus(status)
|
|
157915
|
+
);
|
|
157916
|
+
} catch (err) {
|
|
157917
|
+
console.error("BG removal failed:", err);
|
|
157918
|
+
alert("Background removal could not be completed: " + (err.message || err));
|
|
157919
|
+
} finally {
|
|
157920
|
+
setIsRemovingBg(false);
|
|
157921
|
+
setBgStatus(null);
|
|
157922
|
+
}
|
|
157923
|
+
};
|
|
156971
157924
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 text-white py-2 rounded-md shadow-lg z-50", children: [
|
|
156972
157925
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
156973
157926
|
"button",
|
|
@@ -156980,14 +157933,27 @@ ${elements2.join("\n")}
|
|
|
156980
157933
|
]
|
|
156981
157934
|
}
|
|
156982
157935
|
),
|
|
157936
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
157937
|
+
"button",
|
|
157938
|
+
{
|
|
157939
|
+
className: `text-sm px-2 py-[5px] hover:bg-[#8f99a826] hover:rounded-sm flex gap-1.5 items-center cursor-pointer transition-colors ${isBgRemoved ? "text-emerald-400 font-medium" : ""} ${isRemovingBg ? "opacity-75 cursor-wait" : ""}`,
|
|
157940
|
+
onClick: handleRemoveBgClick,
|
|
157941
|
+
disabled: isRemovingBg,
|
|
157942
|
+
title: isBgRemoved ? "Click to restore original background" : "Automatically remove image background with AI",
|
|
157943
|
+
children: [
|
|
157944
|
+
isRemovingBg ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-3.5 h-3.5 border-2 border-white/30 border-t-white rounded-full animate-spin shrink-0" }) : /* @__PURE__ */ jsxRuntime.jsx(BgRemoveIcon, { size: 15 }),
|
|
157945
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: isRemovingBg ? bgStatus || "Processing..." : isBgRemoved ? "Restore BG" : "Remove BG" })
|
|
157946
|
+
]
|
|
157947
|
+
}
|
|
157948
|
+
),
|
|
156983
157949
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
156984
157950
|
"button",
|
|
156985
157951
|
{
|
|
156986
157952
|
className: "text-sm px-1 py-[5px] hover:bg-[#8f99a826] hover:rounded-sm cursor-pointer",
|
|
156987
157953
|
onClick: () => {
|
|
156988
|
-
const
|
|
156989
|
-
if (!
|
|
156990
|
-
fitImageToPage(
|
|
157954
|
+
const cvs = useEditorStore.getState().canvas;
|
|
157955
|
+
if (!cvs) return;
|
|
157956
|
+
fitImageToPage(cvs);
|
|
156991
157957
|
saveState();
|
|
156992
157958
|
},
|
|
156993
157959
|
children: "Fit to page"
|
|
@@ -157001,10 +157967,17 @@ ${elements2.join("\n")}
|
|
|
157001
157967
|
children: hasMask ? "Remove Mask" : "Apply Mask"
|
|
157002
157968
|
}
|
|
157003
157969
|
),
|
|
157004
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
157005
|
-
|
|
157006
|
-
|
|
157007
|
-
|
|
157970
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
157971
|
+
"button",
|
|
157972
|
+
{
|
|
157973
|
+
className: "text-sm px-1 py-[5px] hover:bg-[#8f99a826] hover:rounded-sm cursor-pointer flex items-center gap-1",
|
|
157974
|
+
onClick: handleCropClick,
|
|
157975
|
+
children: [
|
|
157976
|
+
/* @__PURE__ */ jsxRuntime.jsx(CropIcon, {}),
|
|
157977
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "Crop" })
|
|
157978
|
+
]
|
|
157979
|
+
}
|
|
157980
|
+
)
|
|
157008
157981
|
] });
|
|
157009
157982
|
}
|
|
157010
157983
|
function QrEffectToolbar() {
|
|
@@ -157645,6 +158618,102 @@ ${elements2.join("\n")}
|
|
|
157645
158618
|
)
|
|
157646
158619
|
}
|
|
157647
158620
|
);
|
|
158621
|
+
const AlignTopIcon = ({ className = "", size = 18 }) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
158622
|
+
"svg",
|
|
158623
|
+
{
|
|
158624
|
+
width: size,
|
|
158625
|
+
height: size,
|
|
158626
|
+
viewBox: "0 0 18 18",
|
|
158627
|
+
fill: "none",
|
|
158628
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
158629
|
+
className: `shrink-0 ${className}`,
|
|
158630
|
+
children: [
|
|
158631
|
+
/* @__PURE__ */ jsxRuntime.jsx("rect", { x: "2", y: "2", width: "14", height: "2", rx: "0.5", fill: "currentColor" }),
|
|
158632
|
+
/* @__PURE__ */ jsxRuntime.jsx("rect", { x: "4.5", y: "5.5", width: "2.5", height: "7.5", rx: "0.5", fill: "currentColor" }),
|
|
158633
|
+
/* @__PURE__ */ jsxRuntime.jsx("rect", { x: "10", y: "5.5", width: "2.5", height: "4.5", rx: "0.5", fill: "currentColor" })
|
|
158634
|
+
]
|
|
158635
|
+
}
|
|
158636
|
+
);
|
|
158637
|
+
const AlignLeftIcon = ({ className = "", size = 18 }) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
158638
|
+
"svg",
|
|
158639
|
+
{
|
|
158640
|
+
width: size,
|
|
158641
|
+
height: size,
|
|
158642
|
+
viewBox: "0 0 18 18",
|
|
158643
|
+
fill: "none",
|
|
158644
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
158645
|
+
className: `shrink-0 ${className}`,
|
|
158646
|
+
children: [
|
|
158647
|
+
/* @__PURE__ */ jsxRuntime.jsx("rect", { x: "2", y: "2", width: "2", height: "14", rx: "0.5", fill: "currentColor" }),
|
|
158648
|
+
/* @__PURE__ */ jsxRuntime.jsx("rect", { x: "5.5", y: "4.5", width: "7.5", height: "2.5", rx: "0.5", fill: "currentColor" }),
|
|
158649
|
+
/* @__PURE__ */ jsxRuntime.jsx("rect", { x: "5.5", y: "10", width: "4.5", height: "2.5", rx: "0.5", fill: "currentColor" })
|
|
158650
|
+
]
|
|
158651
|
+
}
|
|
158652
|
+
);
|
|
158653
|
+
const AlignMiddleIcon = ({ className = "", size = 18 }) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
158654
|
+
"svg",
|
|
158655
|
+
{
|
|
158656
|
+
width: size,
|
|
158657
|
+
height: size,
|
|
158658
|
+
viewBox: "0 0 18 18",
|
|
158659
|
+
fill: "none",
|
|
158660
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
158661
|
+
className: `shrink-0 ${className}`,
|
|
158662
|
+
children: [
|
|
158663
|
+
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "2", y1: "9", x2: "16", y2: "9", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" }),
|
|
158664
|
+
/* @__PURE__ */ jsxRuntime.jsx("rect", { x: "4.5", y: "6", width: "2.5", height: "6", rx: "0.5", fill: "currentColor" }),
|
|
158665
|
+
/* @__PURE__ */ jsxRuntime.jsx("rect", { x: "10", y: "3.5", width: "2.5", height: "11", rx: "0.5", fill: "currentColor" })
|
|
158666
|
+
]
|
|
158667
|
+
}
|
|
158668
|
+
);
|
|
158669
|
+
const AlignCenterIcon = ({ className = "", size = 18 }) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
158670
|
+
"svg",
|
|
158671
|
+
{
|
|
158672
|
+
width: size,
|
|
158673
|
+
height: size,
|
|
158674
|
+
viewBox: "0 0 18 18",
|
|
158675
|
+
fill: "none",
|
|
158676
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
158677
|
+
className: `shrink-0 ${className}`,
|
|
158678
|
+
children: [
|
|
158679
|
+
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "9", y1: "2", x2: "9", y2: "16", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" }),
|
|
158680
|
+
/* @__PURE__ */ jsxRuntime.jsx("rect", { x: "6", y: "4.5", width: "6", height: "2.5", rx: "0.5", fill: "currentColor" }),
|
|
158681
|
+
/* @__PURE__ */ jsxRuntime.jsx("rect", { x: "3.5", y: "10", width: "11", height: "2.5", rx: "0.5", fill: "currentColor" })
|
|
158682
|
+
]
|
|
158683
|
+
}
|
|
158684
|
+
);
|
|
158685
|
+
const AlignBottomIcon = ({ className = "", size = 18 }) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
158686
|
+
"svg",
|
|
158687
|
+
{
|
|
158688
|
+
width: size,
|
|
158689
|
+
height: size,
|
|
158690
|
+
viewBox: "0 0 18 18",
|
|
158691
|
+
fill: "none",
|
|
158692
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
158693
|
+
className: `shrink-0 ${className}`,
|
|
158694
|
+
children: [
|
|
158695
|
+
/* @__PURE__ */ jsxRuntime.jsx("rect", { x: "2", y: "14", width: "14", height: "2", rx: "0.5", fill: "currentColor" }),
|
|
158696
|
+
/* @__PURE__ */ jsxRuntime.jsx("rect", { x: "4.5", y: "8", width: "2.5", height: "4.5", rx: "0.5", fill: "currentColor" }),
|
|
158697
|
+
/* @__PURE__ */ jsxRuntime.jsx("rect", { x: "10", y: "5", width: "2.5", height: "7.5", rx: "0.5", fill: "currentColor" })
|
|
158698
|
+
]
|
|
158699
|
+
}
|
|
158700
|
+
);
|
|
158701
|
+
const AlignRightIcon = ({ className = "", size = 18 }) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
158702
|
+
"svg",
|
|
158703
|
+
{
|
|
158704
|
+
width: size,
|
|
158705
|
+
height: size,
|
|
158706
|
+
viewBox: "0 0 18 18",
|
|
158707
|
+
fill: "none",
|
|
158708
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
158709
|
+
className: `shrink-0 ${className}`,
|
|
158710
|
+
children: [
|
|
158711
|
+
/* @__PURE__ */ jsxRuntime.jsx("rect", { x: "14", y: "2", width: "2", height: "14", rx: "0.5", fill: "currentColor" }),
|
|
158712
|
+
/* @__PURE__ */ jsxRuntime.jsx("rect", { x: "8", y: "4.5", width: "4.5", height: "2.5", rx: "0.5", fill: "currentColor" }),
|
|
158713
|
+
/* @__PURE__ */ jsxRuntime.jsx("rect", { x: "5", y: "10", width: "7.5", height: "2.5", rx: "0.5", fill: "currentColor" })
|
|
158714
|
+
]
|
|
158715
|
+
}
|
|
158716
|
+
);
|
|
157648
158717
|
function PositionPicker() {
|
|
157649
158718
|
const canvas2 = useEditorStore((s2) => s2.canvas);
|
|
157650
158719
|
const saveState = useHistoryStore((s2) => s2.saveState);
|
|
@@ -157828,26 +158897,114 @@ ${elements2.join("\n")}
|
|
|
157828
158897
|
showPosition && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
157829
158898
|
"div",
|
|
157830
158899
|
{
|
|
157831
|
-
className: "fixed md:absolute top-[112px] md:top-10 left-1/2 -translate-x-1/2 bg-neutral-800 text-white rounded-xl shadow-xl p-4 w-[
|
|
158900
|
+
className: "fixed md:absolute top-[112px] md:top-10 left-1/2 -translate-x-1/2 bg-neutral-800 text-white rounded-xl shadow-xl p-4 w-[280px] z-[9999]",
|
|
157832
158901
|
onClick: (e2) => e2.stopPropagation(),
|
|
157833
158902
|
onMouseDown: (e2) => e2.stopPropagation(),
|
|
157834
158903
|
children: [
|
|
157835
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-xs uppercase opacity-60 mb-2", children: "Layering" }),
|
|
157836
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-2 gap-
|
|
157837
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
157838
|
-
|
|
157839
|
-
|
|
157840
|
-
|
|
158904
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-xs uppercase opacity-60 mb-2 font-medium tracking-wide", children: "Layering" }),
|
|
158905
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-2 gap-1.5 mb-3", children: [
|
|
158906
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
158907
|
+
"button",
|
|
158908
|
+
{
|
|
158909
|
+
onClick: () => bringForward(),
|
|
158910
|
+
className: "flex items-center px-2 py-1.5 rounded text-sm text-gray-200 hover:text-white hover:bg-white/10 transition-colors text-left",
|
|
158911
|
+
children: "Forward"
|
|
158912
|
+
}
|
|
158913
|
+
),
|
|
158914
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
158915
|
+
"button",
|
|
158916
|
+
{
|
|
158917
|
+
onClick: () => sendBackward(),
|
|
158918
|
+
className: "flex items-center px-2 py-1.5 rounded text-sm text-gray-200 hover:text-white hover:bg-white/10 transition-colors text-left",
|
|
158919
|
+
children: "Backward"
|
|
158920
|
+
}
|
|
158921
|
+
),
|
|
158922
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
158923
|
+
"button",
|
|
158924
|
+
{
|
|
158925
|
+
onClick: () => bringToFront(),
|
|
158926
|
+
className: "flex items-center px-2 py-1.5 rounded text-sm text-gray-200 hover:text-white hover:bg-white/10 transition-colors text-left",
|
|
158927
|
+
children: "To Front"
|
|
158928
|
+
}
|
|
158929
|
+
),
|
|
158930
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
158931
|
+
"button",
|
|
158932
|
+
{
|
|
158933
|
+
onClick: () => sendToBack(),
|
|
158934
|
+
className: "flex items-center px-2 py-1.5 rounded text-sm text-gray-200 hover:text-white hover:bg-white/10 transition-colors text-left",
|
|
158935
|
+
children: "To Back"
|
|
158936
|
+
}
|
|
158937
|
+
)
|
|
157841
158938
|
] }),
|
|
157842
158939
|
/* @__PURE__ */ jsxRuntime.jsx("hr", { className: "border-neutral-700 my-2" }),
|
|
157843
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-xs uppercase opacity-60 mb-2", children: "
|
|
157844
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-2 gap-2", children: [
|
|
157845
|
-
/* @__PURE__ */ jsxRuntime.
|
|
157846
|
-
|
|
157847
|
-
|
|
157848
|
-
|
|
157849
|
-
|
|
157850
|
-
|
|
158940
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-xs uppercase opacity-60 mb-2 font-medium tracking-wide", children: "Alignments" }),
|
|
158941
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-2 gap-x-2 gap-y-1.5", children: [
|
|
158942
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
158943
|
+
"button",
|
|
158944
|
+
{
|
|
158945
|
+
onClick: () => handleAlign("top"),
|
|
158946
|
+
className: "flex items-center gap-2.5 px-2 py-1.5 rounded text-sm text-gray-200 hover:text-white hover:bg-white/10 transition-colors text-left",
|
|
158947
|
+
children: [
|
|
158948
|
+
/* @__PURE__ */ jsxRuntime.jsx(AlignTopIcon, { size: 18 }),
|
|
158949
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "Top" })
|
|
158950
|
+
]
|
|
158951
|
+
}
|
|
158952
|
+
),
|
|
158953
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
158954
|
+
"button",
|
|
158955
|
+
{
|
|
158956
|
+
onClick: () => handleAlign("left"),
|
|
158957
|
+
className: "flex items-center gap-2.5 px-2 py-1.5 rounded text-sm text-gray-200 hover:text-white hover:bg-white/10 transition-colors text-left",
|
|
158958
|
+
children: [
|
|
158959
|
+
/* @__PURE__ */ jsxRuntime.jsx(AlignLeftIcon, { size: 18 }),
|
|
158960
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "Left" })
|
|
158961
|
+
]
|
|
158962
|
+
}
|
|
158963
|
+
),
|
|
158964
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
158965
|
+
"button",
|
|
158966
|
+
{
|
|
158967
|
+
onClick: () => handleAlign("middle"),
|
|
158968
|
+
className: "flex items-center gap-2.5 px-2 py-1.5 rounded text-sm text-gray-200 hover:text-white hover:bg-white/10 transition-colors text-left",
|
|
158969
|
+
children: [
|
|
158970
|
+
/* @__PURE__ */ jsxRuntime.jsx(AlignMiddleIcon, { size: 18 }),
|
|
158971
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "Middle" })
|
|
158972
|
+
]
|
|
158973
|
+
}
|
|
158974
|
+
),
|
|
158975
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
158976
|
+
"button",
|
|
158977
|
+
{
|
|
158978
|
+
onClick: () => handleAlign("center"),
|
|
158979
|
+
className: "flex items-center gap-2.5 px-2 py-1.5 rounded text-sm text-gray-200 hover:text-white hover:bg-white/10 transition-colors text-left",
|
|
158980
|
+
children: [
|
|
158981
|
+
/* @__PURE__ */ jsxRuntime.jsx(AlignCenterIcon, { size: 18 }),
|
|
158982
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "Center" })
|
|
158983
|
+
]
|
|
158984
|
+
}
|
|
158985
|
+
),
|
|
158986
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
158987
|
+
"button",
|
|
158988
|
+
{
|
|
158989
|
+
onClick: () => handleAlign("bottom"),
|
|
158990
|
+
className: "flex items-center gap-2.5 px-2 py-1.5 rounded text-sm text-gray-200 hover:text-white hover:bg-white/10 transition-colors text-left",
|
|
158991
|
+
children: [
|
|
158992
|
+
/* @__PURE__ */ jsxRuntime.jsx(AlignBottomIcon, { size: 18 }),
|
|
158993
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "Bottom" })
|
|
158994
|
+
]
|
|
158995
|
+
}
|
|
158996
|
+
),
|
|
158997
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
158998
|
+
"button",
|
|
158999
|
+
{
|
|
159000
|
+
onClick: () => handleAlign("right"),
|
|
159001
|
+
className: "flex items-center gap-2.5 px-2 py-1.5 rounded text-sm text-gray-200 hover:text-white hover:bg-white/10 transition-colors text-left",
|
|
159002
|
+
children: [
|
|
159003
|
+
/* @__PURE__ */ jsxRuntime.jsx(AlignRightIcon, { size: 18 }),
|
|
159004
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "Right" })
|
|
159005
|
+
]
|
|
159006
|
+
}
|
|
159007
|
+
)
|
|
157851
159008
|
] })
|
|
157852
159009
|
]
|
|
157853
159010
|
}
|
|
@@ -158527,14 +159684,45 @@ ${elements2.join("\n")}
|
|
|
158527
159684
|
top: (workarea.top ?? 0) + dy
|
|
158528
159685
|
});
|
|
158529
159686
|
workarea.setCoords();
|
|
159687
|
+
if (workarea.clipPath) {
|
|
159688
|
+
workarea.clipPath.set({
|
|
159689
|
+
left: (workarea.clipPath.left ?? 0) + dx,
|
|
159690
|
+
top: (workarea.clipPath.top ?? 0) + dy
|
|
159691
|
+
});
|
|
159692
|
+
workarea.clipPath.setCoords();
|
|
159693
|
+
}
|
|
158530
159694
|
canvas2.getObjects().forEach((obj) => {
|
|
158531
|
-
if (obj.name === "workarea" || obj.name?.startsWith("overlay-"))
|
|
159695
|
+
if (obj.name === "workarea" || obj.name?.startsWith("overlay-") || obj.name?.startsWith("guideline-")) {
|
|
159696
|
+
return;
|
|
159697
|
+
}
|
|
158532
159698
|
obj.set({
|
|
158533
159699
|
left: (obj.left ?? 0) + dx,
|
|
158534
159700
|
top: (obj.top ?? 0) + dy
|
|
158535
159701
|
});
|
|
158536
159702
|
obj.setCoords();
|
|
159703
|
+
if (obj.clipPath) {
|
|
159704
|
+
obj.clipPath.set({
|
|
159705
|
+
left: (obj.clipPath.left ?? 0) + dx,
|
|
159706
|
+
top: (obj.clipPath.top ?? 0) + dy
|
|
159707
|
+
});
|
|
159708
|
+
obj.clipPath.setCoords();
|
|
159709
|
+
}
|
|
158537
159710
|
});
|
|
159711
|
+
if (canvas2.backgroundImage) {
|
|
159712
|
+
const bg = canvas2.backgroundImage;
|
|
159713
|
+
bg.set({
|
|
159714
|
+
left: (bg.left ?? 0) + dx,
|
|
159715
|
+
top: (bg.top ?? 0) + dy
|
|
159716
|
+
});
|
|
159717
|
+
bg.setCoords();
|
|
159718
|
+
if (bg.clipPath) {
|
|
159719
|
+
bg.clipPath.set({
|
|
159720
|
+
left: (bg.clipPath.left ?? 0) + dx,
|
|
159721
|
+
top: (bg.clipPath.top ?? 0) + dy
|
|
159722
|
+
});
|
|
159723
|
+
bg.clipPath.setCoords();
|
|
159724
|
+
}
|
|
159725
|
+
}
|
|
158538
159726
|
createOverlays(canvas2, workarea);
|
|
158539
159727
|
canvas2.requestRenderAll();
|
|
158540
159728
|
};
|
|
@@ -158560,7 +159748,7 @@ ${elements2.join("\n")}
|
|
|
158560
159748
|
canvas2.selection = true;
|
|
158561
159749
|
canvas2.defaultCursor = "default";
|
|
158562
159750
|
canvas2.forEachObject((obj) => {
|
|
158563
|
-
if (obj.name !== "workarea" && !obj.name?.startsWith("overlay-")) {
|
|
159751
|
+
if (obj.name !== "workarea" && obj.name !== "bg-image" && !obj.name?.startsWith("overlay-") && !obj.name?.startsWith("guideline-")) {
|
|
158564
159752
|
obj.selectable = true;
|
|
158565
159753
|
obj.evented = true;
|
|
158566
159754
|
} else {
|
|
@@ -158649,8 +159837,12 @@ ${elements2.join("\n")}
|
|
|
158649
159837
|
const containerWidth = container.clientWidth;
|
|
158650
159838
|
const containerHeight = container.clientHeight;
|
|
158651
159839
|
const size = productData?.optionsMap?.size?.split(" x ") || ["0", "0"];
|
|
158652
|
-
const
|
|
158653
|
-
const
|
|
159840
|
+
const bleed = Number(productData?.bleedArea || 0);
|
|
159841
|
+
const rawW = Number(size[0].replace('"', "")) || 3.5;
|
|
159842
|
+
const rawH = Number(size[1].replace('"', "")) || 2;
|
|
159843
|
+
const isVertical = productData?.optionsMap?.productorientation === "Vertical" || productData?.optionsMap?.orientation === "Vertical";
|
|
159844
|
+
const printWidth = (isVertical ? rawW : rawH) + bleed;
|
|
159845
|
+
const printHeight = (isVertical ? rawH : rawW) + bleed;
|
|
158654
159846
|
const printDPI = 72;
|
|
158655
159847
|
const fullWidth = printWidth * printDPI;
|
|
158656
159848
|
const fullHeight = printHeight * printDPI;
|
|
@@ -158681,7 +159873,6 @@ ${elements2.join("\n")}
|
|
|
158681
159873
|
);
|
|
158682
159874
|
const rawShape = productData?.optionsMap?.shape || productData?.optionsMap?.Shape || productData?.shape || "";
|
|
158683
159875
|
const shapeType = detectShape(rawShape);
|
|
158684
|
-
const isVertical = productData?.optionsMap?.productorientation === "Vertical" || productData?.optionsMap?.orientation === "Vertical";
|
|
158685
159876
|
const { rx, ry, radiusOfCanvas } = calculateShapeRadius(
|
|
158686
159877
|
shapeType,
|
|
158687
159878
|
fullWidth,
|
|
@@ -158692,6 +159883,8 @@ ${elements2.join("\n")}
|
|
|
158692
159883
|
width: fullWidth,
|
|
158693
159884
|
height: fullHeight,
|
|
158694
159885
|
fill: "#ffffff",
|
|
159886
|
+
strokeWidth: 0,
|
|
159887
|
+
stroke: null,
|
|
158695
159888
|
selectable: false,
|
|
158696
159889
|
evented: false,
|
|
158697
159890
|
name: "workarea",
|
|
@@ -158721,22 +159914,48 @@ ${elements2.join("\n")}
|
|
|
158721
159914
|
return workarea;
|
|
158722
159915
|
}
|
|
158723
159916
|
function setupHistoryRecording(canvas2) {
|
|
158724
|
-
|
|
159917
|
+
let modifiedTimer = null;
|
|
159918
|
+
const handleObjectAddedOrRemoved = (e2) => {
|
|
158725
159919
|
const target = e2?.target;
|
|
158726
159920
|
if (!target) return;
|
|
158727
|
-
|
|
159921
|
+
const name = target.name || "";
|
|
159922
|
+
if (name === "workarea" || name === "watermark" || name.startsWith("overlay-") || name.startsWith("guideline-")) {
|
|
159923
|
+
return;
|
|
159924
|
+
}
|
|
158728
159925
|
if (useHistoryStore.getState().isRestoring) return;
|
|
159926
|
+
if (modifiedTimer) {
|
|
159927
|
+
clearTimeout(modifiedTimer);
|
|
159928
|
+
modifiedTimer = null;
|
|
159929
|
+
}
|
|
158729
159930
|
requestAnimationFrame(() => {
|
|
158730
|
-
useHistoryStore.getState().
|
|
159931
|
+
if (!useHistoryStore.getState().isRestoring) {
|
|
159932
|
+
useHistoryStore.getState().saveState();
|
|
159933
|
+
}
|
|
158731
159934
|
});
|
|
158732
159935
|
};
|
|
158733
|
-
|
|
158734
|
-
|
|
158735
|
-
|
|
159936
|
+
const handleObjectModified = (e2) => {
|
|
159937
|
+
const target = e2?.target;
|
|
159938
|
+
if (!target) return;
|
|
159939
|
+
const name = target.name || "";
|
|
159940
|
+
if (name === "workarea" || name === "watermark" || name.startsWith("overlay-") || name.startsWith("guideline-")) {
|
|
159941
|
+
return;
|
|
159942
|
+
}
|
|
159943
|
+
if (useHistoryStore.getState().isRestoring) return;
|
|
159944
|
+
clearTimeout(modifiedTimer);
|
|
159945
|
+
modifiedTimer = setTimeout(() => {
|
|
159946
|
+
if (!useHistoryStore.getState().isRestoring) {
|
|
159947
|
+
useHistoryStore.getState().saveState();
|
|
159948
|
+
}
|
|
159949
|
+
}, 150);
|
|
159950
|
+
};
|
|
159951
|
+
canvas2.on("object:added", handleObjectAddedOrRemoved);
|
|
159952
|
+
canvas2.on("object:removed", handleObjectAddedOrRemoved);
|
|
159953
|
+
canvas2.on("object:modified", handleObjectModified);
|
|
158736
159954
|
return () => {
|
|
158737
|
-
|
|
158738
|
-
canvas2.off("object:
|
|
158739
|
-
canvas2.off("object:removed",
|
|
159955
|
+
if (modifiedTimer) clearTimeout(modifiedTimer);
|
|
159956
|
+
canvas2.off("object:added", handleObjectAddedOrRemoved);
|
|
159957
|
+
canvas2.off("object:removed", handleObjectAddedOrRemoved);
|
|
159958
|
+
canvas2.off("object:modified", handleObjectModified);
|
|
158740
159959
|
};
|
|
158741
159960
|
}
|
|
158742
159961
|
function setupDoublClickCrop(canvas2) {
|
|
@@ -158948,8 +160167,10 @@ ${elements2.join("\n")}
|
|
|
158948
160167
|
createWorkarea(canvas2, containerRef.current, productData, targetPage);
|
|
158949
160168
|
}
|
|
158950
160169
|
}
|
|
158951
|
-
historyStore.saveState();
|
|
158952
160170
|
useHistoryStore.setState({ isRestoring: false });
|
|
160171
|
+
if ((useHistoryStore.getState().pages[targetPage]?.history?.length ?? 0) === 0) {
|
|
160172
|
+
historyStore.saveState();
|
|
160173
|
+
}
|
|
158953
160174
|
} finally {
|
|
158954
160175
|
useLoaderStore.getState().stopLoading();
|
|
158955
160176
|
}
|
|
@@ -158989,9 +160210,62 @@ ${elements2.join("\n")}
|
|
|
158989
160210
|
function doReset(canvas2, setZoom) {
|
|
158990
160211
|
const workarea = getWorkarea(canvas2);
|
|
158991
160212
|
const initialZoom = workarea ? workarea.__initialZoom || 1 : 1;
|
|
158992
|
-
const
|
|
158993
|
-
canvas2.
|
|
160213
|
+
const canvasW = canvas2.getWidth();
|
|
160214
|
+
const canvasH = canvas2.getHeight();
|
|
160215
|
+
canvas2.setViewportTransform([1, 0, 0, 1, 0, 0]);
|
|
160216
|
+
canvas2.setZoom(1);
|
|
158994
160217
|
if (workarea) {
|
|
160218
|
+
const prevLeft = workarea.left ?? 0;
|
|
160219
|
+
const prevTop = workarea.top ?? 0;
|
|
160220
|
+
canvas2.centerObject(workarea);
|
|
160221
|
+
workarea.setCoords();
|
|
160222
|
+
const dx = (workarea.left ?? 0) - prevLeft;
|
|
160223
|
+
const dy = (workarea.top ?? 0) - prevTop;
|
|
160224
|
+
if (Math.abs(dx) > 1e-3 || Math.abs(dy) > 1e-3) {
|
|
160225
|
+
canvas2.getObjects().forEach((obj) => {
|
|
160226
|
+
if (obj.name === "workarea" || obj.name?.startsWith("overlay-") || obj.name?.startsWith("guideline-")) {
|
|
160227
|
+
return;
|
|
160228
|
+
}
|
|
160229
|
+
obj.set({
|
|
160230
|
+
left: (obj.left ?? 0) + dx,
|
|
160231
|
+
top: (obj.top ?? 0) + dy
|
|
160232
|
+
});
|
|
160233
|
+
obj.setCoords();
|
|
160234
|
+
if (obj.clipPath) {
|
|
160235
|
+
obj.clipPath.set({
|
|
160236
|
+
left: (obj.clipPath.left ?? 0) + dx,
|
|
160237
|
+
top: (obj.clipPath.top ?? 0) + dy
|
|
160238
|
+
});
|
|
160239
|
+
obj.clipPath.setCoords();
|
|
160240
|
+
}
|
|
160241
|
+
});
|
|
160242
|
+
if (workarea.clipPath) {
|
|
160243
|
+
workarea.clipPath.set({
|
|
160244
|
+
left: (workarea.clipPath.left ?? 0) + dx,
|
|
160245
|
+
top: (workarea.clipPath.top ?? 0) + dy
|
|
160246
|
+
});
|
|
160247
|
+
workarea.clipPath.setCoords();
|
|
160248
|
+
}
|
|
160249
|
+
if (canvas2.backgroundImage) {
|
|
160250
|
+
const bg = canvas2.backgroundImage;
|
|
160251
|
+
bg.set({
|
|
160252
|
+
left: (bg.left ?? 0) + dx,
|
|
160253
|
+
top: (bg.top ?? 0) + dy
|
|
160254
|
+
});
|
|
160255
|
+
bg.setCoords();
|
|
160256
|
+
if (bg.clipPath) {
|
|
160257
|
+
bg.clipPath.set({
|
|
160258
|
+
left: (bg.clipPath.left ?? 0) + dx,
|
|
160259
|
+
top: (bg.clipPath.top ?? 0) + dy
|
|
160260
|
+
});
|
|
160261
|
+
bg.clipPath.setCoords();
|
|
160262
|
+
}
|
|
160263
|
+
}
|
|
160264
|
+
}
|
|
160265
|
+
if (initialZoom !== 1) {
|
|
160266
|
+
const center = new ot(canvasW / 2, canvasH / 2);
|
|
160267
|
+
canvas2.zoomToPoint(center, initialZoom);
|
|
160268
|
+
}
|
|
158995
160269
|
createOverlays(canvas2, workarea);
|
|
158996
160270
|
}
|
|
158997
160271
|
const activeObject = canvas2.getActiveObject();
|
|
@@ -158999,7 +160273,7 @@ ${elements2.join("\n")}
|
|
|
158999
160273
|
canvas2.requestRenderAll();
|
|
159000
160274
|
setZoom(initialZoom);
|
|
159001
160275
|
}
|
|
159002
|
-
function useCanvasZoom(canvas2) {
|
|
160276
|
+
function useCanvasZoom(canvas2, options) {
|
|
159003
160277
|
const [zoom, setZoom] = React.useState(1);
|
|
159004
160278
|
const rafRef = React.useRef(null);
|
|
159005
160279
|
const wheelAccum = React.useRef(0);
|
|
@@ -159090,12 +160364,13 @@ ${elements2.join("\n")}
|
|
|
159090
160364
|
} else if (e2.key === "0") {
|
|
159091
160365
|
e2.preventDefault();
|
|
159092
160366
|
doReset(canvas2, setZoom);
|
|
160367
|
+
options?.onReset?.();
|
|
159093
160368
|
}
|
|
159094
160369
|
}
|
|
159095
160370
|
};
|
|
159096
160371
|
window.addEventListener("keydown", onKey);
|
|
159097
160372
|
return () => window.removeEventListener("keydown", onKey);
|
|
159098
|
-
}, [canvas2, applyZoom]);
|
|
160373
|
+
}, [canvas2, applyZoom, options]);
|
|
159099
160374
|
const zoomIn = React.useCallback(() => {
|
|
159100
160375
|
if (!canvas2) return;
|
|
159101
160376
|
applyZoom(clamp(canvas2.getZoom() + STEP, MIN_ZOOM, MAX_ZOOM));
|
|
@@ -159107,7 +160382,8 @@ ${elements2.join("\n")}
|
|
|
159107
160382
|
const zoomReset = React.useCallback(() => {
|
|
159108
160383
|
if (!canvas2) return;
|
|
159109
160384
|
doReset(canvas2, setZoom);
|
|
159110
|
-
|
|
160385
|
+
options?.onReset?.();
|
|
160386
|
+
}, [canvas2, options]);
|
|
159111
160387
|
const zoomTo = React.useCallback(
|
|
159112
160388
|
(value) => applyZoom(clamp(value, MIN_ZOOM, MAX_ZOOM)),
|
|
159113
160389
|
[applyZoom]
|
|
@@ -159122,9 +160398,9 @@ ${elements2.join("\n")}
|
|
|
159122
160398
|
zoomTo
|
|
159123
160399
|
};
|
|
159124
160400
|
}
|
|
159125
|
-
function ZoomControls() {
|
|
160401
|
+
function ZoomControls({ onReset }) {
|
|
159126
160402
|
const canvas2 = useEditorStore((s2) => s2.canvas);
|
|
159127
|
-
const { zoom, atMin, atMax, zoomIn, zoomOut, zoomReset, zoomTo } = useCanvasZoom(canvas2);
|
|
160403
|
+
const { zoom, atMin, atMax, zoomIn, zoomOut, zoomReset, zoomTo } = useCanvasZoom(canvas2, { onReset });
|
|
159128
160404
|
const [sliderOpen, setSliderOpen] = React.useState(false);
|
|
159129
160405
|
const sliderRef = React.useRef(null);
|
|
159130
160406
|
React.useEffect(() => {
|
|
@@ -159350,6 +160626,18 @@ ${elements2.join("\n")}
|
|
|
159350
160626
|
e2.preventDefault();
|
|
159351
160627
|
handlePaste();
|
|
159352
160628
|
}
|
|
160629
|
+
if (isModifier && e2.key.toLowerCase() === "z") {
|
|
160630
|
+
e2.preventDefault();
|
|
160631
|
+
if (e2.shiftKey) {
|
|
160632
|
+
useHistoryStore.getState().redoAction();
|
|
160633
|
+
} else {
|
|
160634
|
+
useHistoryStore.getState().undo();
|
|
160635
|
+
}
|
|
160636
|
+
}
|
|
160637
|
+
if (isModifier && e2.key.toLowerCase() === "y") {
|
|
160638
|
+
e2.preventDefault();
|
|
160639
|
+
useHistoryStore.getState().redoAction();
|
|
160640
|
+
}
|
|
159353
160641
|
};
|
|
159354
160642
|
window.addEventListener("keydown", handleKeyDown);
|
|
159355
160643
|
return () => {
|
|
@@ -159466,6 +160754,9 @@ ${elements2.join("\n")}
|
|
|
159466
160754
|
pageJSON.current.back = backData;
|
|
159467
160755
|
}
|
|
159468
160756
|
}
|
|
160757
|
+
if (useHistoryStore.getState().pages.front.history.length === 0) {
|
|
160758
|
+
useHistoryStore.getState().saveState();
|
|
160759
|
+
}
|
|
159469
160760
|
useEditorStore.getState().setPages({ ...pageJSON.current });
|
|
159470
160761
|
return () => {
|
|
159471
160762
|
cleanupHistory();
|
|
@@ -159583,7 +160874,7 @@ ${elements2.join("\n")}
|
|
|
159583
160874
|
)
|
|
159584
160875
|
] }),
|
|
159585
160876
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-[1px] h-6 bg-gray-300/50 mx-3" }),
|
|
159586
|
-
/* @__PURE__ */ jsxRuntime.jsx(ZoomControls, {})
|
|
160877
|
+
/* @__PURE__ */ jsxRuntime.jsx(ZoomControls, { onReset: () => setIsPanning(false) })
|
|
159587
160878
|
] }),
|
|
159588
160879
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
159589
160880
|
"div",
|