vibrant-tool 0.1.10 → 0.1.11
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/types/shape.types.d.ts +1 -0
- package/dist/lib/utils/alignment-guidelines.d.ts +9 -0
- package/dist/lib/utils/page-manager.d.ts +1 -0
- package/dist/modules/components/all-elements/index.d.ts +4 -1
- package/dist/vibrant-tool.css +130 -10
- package/dist/vibrant-tool.es.js +988 -361
- package/dist/vibrant-tool.umd.js +988 -361
- package/package.json +1 -1
package/dist/vibrant-tool.umd.js
CHANGED
|
@@ -9018,6 +9018,248 @@
|
|
|
9018
9018
|
}
|
|
9019
9019
|
}
|
|
9020
9020
|
}));
|
|
9021
|
+
const FontService = {
|
|
9022
|
+
async getAll(data) {
|
|
9023
|
+
const res = await apiFetch(`/api/fonts?page=${data.page}&limit=${data.limit}&status=true`, {
|
|
9024
|
+
method: "GET"
|
|
9025
|
+
});
|
|
9026
|
+
return res;
|
|
9027
|
+
}
|
|
9028
|
+
};
|
|
9029
|
+
let cachedFontsPromise = null;
|
|
9030
|
+
async function fetchAllFontsCached() {
|
|
9031
|
+
if (!cachedFontsPromise) {
|
|
9032
|
+
cachedFontsPromise = FontService.getAll({ page: 1, limit: 1e3 }).then((res) => res?.data || []).catch((err) => {
|
|
9033
|
+
console.warn("Failed to fetch all fonts:", err);
|
|
9034
|
+
cachedFontsPromise = null;
|
|
9035
|
+
return [];
|
|
9036
|
+
});
|
|
9037
|
+
}
|
|
9038
|
+
return cachedFontsPromise;
|
|
9039
|
+
}
|
|
9040
|
+
async function injectFontFace(fontName, fontUrl) {
|
|
9041
|
+
if (!fontName || !fontUrl || typeof document === "undefined") return;
|
|
9042
|
+
const cleanedName = fontName.trim().replace(/^['"]+|['"]+$/g, "");
|
|
9043
|
+
const id = `font-${cleanedName.replace(/\s+/g, "-").toLowerCase()}`;
|
|
9044
|
+
if (!document.getElementById(id)) {
|
|
9045
|
+
const style = document.createElement("style");
|
|
9046
|
+
style.id = id;
|
|
9047
|
+
style.innerHTML = `
|
|
9048
|
+
@font-face {
|
|
9049
|
+
font-family: '${cleanedName}';
|
|
9050
|
+
src: url('${fontUrl}');
|
|
9051
|
+
font-display: swap;
|
|
9052
|
+
}
|
|
9053
|
+
`;
|
|
9054
|
+
document.head.appendChild(style);
|
|
9055
|
+
}
|
|
9056
|
+
try {
|
|
9057
|
+
await document.fonts.load(`16px "${cleanedName}"`);
|
|
9058
|
+
await document.fonts.ready;
|
|
9059
|
+
} catch {
|
|
9060
|
+
}
|
|
9061
|
+
}
|
|
9062
|
+
async function loadTemplateFontsIntoHead(templateFonts, pagesDesignData) {
|
|
9063
|
+
if (typeof document === "undefined") return;
|
|
9064
|
+
const loadPromises = [];
|
|
9065
|
+
if (Array.isArray(templateFonts) && templateFonts.length > 0) {
|
|
9066
|
+
templateFonts.forEach((f) => {
|
|
9067
|
+
if (f && typeof f === "object") {
|
|
9068
|
+
const fontName = f.fontName || f.fontFamily;
|
|
9069
|
+
const fontUrl = f.fontFile;
|
|
9070
|
+
if (fontName && fontUrl) {
|
|
9071
|
+
loadPromises.push(injectFontFace(fontName, fontUrl));
|
|
9072
|
+
}
|
|
9073
|
+
if (f.fontFamily && f.fontFamily !== fontName && fontUrl) {
|
|
9074
|
+
loadPromises.push(injectFontFace(f.fontFamily, fontUrl));
|
|
9075
|
+
}
|
|
9076
|
+
}
|
|
9077
|
+
});
|
|
9078
|
+
}
|
|
9079
|
+
const designs = Array.isArray(pagesDesignData) ? pagesDesignData : [pagesDesignData];
|
|
9080
|
+
const usedFontNames = /* @__PURE__ */ new Set();
|
|
9081
|
+
designs.forEach((d2) => {
|
|
9082
|
+
if (d2) {
|
|
9083
|
+
const extracted = extractFontFamilies(d2);
|
|
9084
|
+
extracted.forEach((name) => usedFontNames.add(name));
|
|
9085
|
+
}
|
|
9086
|
+
});
|
|
9087
|
+
if (usedFontNames.size > 0) {
|
|
9088
|
+
try {
|
|
9089
|
+
const allFonts = await fetchAllFontsCached();
|
|
9090
|
+
usedFontNames.forEach((name) => {
|
|
9091
|
+
const lower = name.toLowerCase();
|
|
9092
|
+
const matched = allFonts.find((f) => {
|
|
9093
|
+
if (f.fontName && f.fontName.toLowerCase() === lower) return true;
|
|
9094
|
+
if (f.fontFamily && f.fontFamily.toLowerCase() === lower) return true;
|
|
9095
|
+
const children = f.childFonts || f.chieldFonts || [];
|
|
9096
|
+
return children.some(
|
|
9097
|
+
(c2) => c2.fontName && c2.fontName.toLowerCase() === lower || c2.fontFamily && c2.fontFamily.toLowerCase() === lower
|
|
9098
|
+
);
|
|
9099
|
+
});
|
|
9100
|
+
if (matched) {
|
|
9101
|
+
if (matched.fontName && matched.fontFile) {
|
|
9102
|
+
loadPromises.push(injectFontFace(matched.fontName, matched.fontFile));
|
|
9103
|
+
}
|
|
9104
|
+
if (matched.fontFamily && matched.fontFile) {
|
|
9105
|
+
loadPromises.push(injectFontFace(matched.fontFamily, matched.fontFile));
|
|
9106
|
+
}
|
|
9107
|
+
const children = matched.childFonts || matched.chieldFonts || [];
|
|
9108
|
+
children.forEach((c2) => {
|
|
9109
|
+
if (c2.fontName && c2.fontFile) {
|
|
9110
|
+
loadPromises.push(injectFontFace(c2.fontName, c2.fontFile));
|
|
9111
|
+
}
|
|
9112
|
+
});
|
|
9113
|
+
}
|
|
9114
|
+
});
|
|
9115
|
+
} catch (err) {
|
|
9116
|
+
console.warn("Failed to fetch fonts list for font injection:", err);
|
|
9117
|
+
}
|
|
9118
|
+
}
|
|
9119
|
+
await Promise.allSettled(loadPromises);
|
|
9120
|
+
try {
|
|
9121
|
+
await document.fonts.ready;
|
|
9122
|
+
} catch {
|
|
9123
|
+
}
|
|
9124
|
+
}
|
|
9125
|
+
function extractFontFamilies(designJson) {
|
|
9126
|
+
if (!designJson) return [];
|
|
9127
|
+
const fonts = /* @__PURE__ */ new Set();
|
|
9128
|
+
const traverse = (obj) => {
|
|
9129
|
+
if (!obj) return;
|
|
9130
|
+
if (obj.fontFamily && typeof obj.fontFamily === "string") {
|
|
9131
|
+
const cleaned = obj.fontFamily.trim().replace(/^['"]+|['"]+$/g, "");
|
|
9132
|
+
if (cleaned) fonts.add(cleaned);
|
|
9133
|
+
}
|
|
9134
|
+
if (obj.fontId && typeof obj.fontId === "string") {
|
|
9135
|
+
fonts.add(obj.fontId.trim());
|
|
9136
|
+
}
|
|
9137
|
+
if (obj.styles && typeof obj.styles === "object") {
|
|
9138
|
+
Object.values(obj.styles).forEach((line) => {
|
|
9139
|
+
if (line && typeof line === "object") {
|
|
9140
|
+
Object.values(line).forEach((charStyle) => {
|
|
9141
|
+
if (charStyle?.fontFamily && typeof charStyle.fontFamily === "string") {
|
|
9142
|
+
const cleaned = charStyle.fontFamily.trim().replace(/^['"]+|['"]+$/g, "");
|
|
9143
|
+
if (cleaned) fonts.add(cleaned);
|
|
9144
|
+
}
|
|
9145
|
+
});
|
|
9146
|
+
}
|
|
9147
|
+
});
|
|
9148
|
+
}
|
|
9149
|
+
if (Array.isArray(obj.objects)) {
|
|
9150
|
+
obj.objects.forEach(traverse);
|
|
9151
|
+
}
|
|
9152
|
+
};
|
|
9153
|
+
if (Array.isArray(designJson.objects)) {
|
|
9154
|
+
designJson.objects.forEach(traverse);
|
|
9155
|
+
} else if (Array.isArray(designJson)) {
|
|
9156
|
+
designJson.forEach(traverse);
|
|
9157
|
+
} else {
|
|
9158
|
+
traverse(designJson);
|
|
9159
|
+
}
|
|
9160
|
+
return Array.from(fonts);
|
|
9161
|
+
}
|
|
9162
|
+
async function resolveFontIds(fontNames) {
|
|
9163
|
+
if (!fontNames || !fontNames.length) return [];
|
|
9164
|
+
try {
|
|
9165
|
+
const allFonts = await fetchAllFontsCached();
|
|
9166
|
+
const matchedIds = /* @__PURE__ */ new Set();
|
|
9167
|
+
for (const rawName of fontNames) {
|
|
9168
|
+
const name = rawName.trim().replace(/^['"]+|['"]+$/g, "");
|
|
9169
|
+
const lower = name.toLowerCase();
|
|
9170
|
+
if (/^[0-9a-fA-F]{24}$/.test(name)) {
|
|
9171
|
+
matchedIds.add(name);
|
|
9172
|
+
continue;
|
|
9173
|
+
}
|
|
9174
|
+
const matchedFont = allFonts.find((f) => {
|
|
9175
|
+
if (f._id === name) return true;
|
|
9176
|
+
if (f.fontFamily && f.fontFamily.toLowerCase() === lower) return true;
|
|
9177
|
+
if (f.fontName && f.fontName.toLowerCase() === lower) return true;
|
|
9178
|
+
const children = f.childFonts || f.chieldFonts || [];
|
|
9179
|
+
return children.some(
|
|
9180
|
+
(c2) => c2._id === name || c2.fontName && c2.fontName.toLowerCase() === lower || c2.fontFamily && c2.fontFamily.toLowerCase() === lower
|
|
9181
|
+
);
|
|
9182
|
+
});
|
|
9183
|
+
if (matchedFont?._id) {
|
|
9184
|
+
matchedIds.add(matchedFont._id);
|
|
9185
|
+
}
|
|
9186
|
+
}
|
|
9187
|
+
return Array.from(matchedIds);
|
|
9188
|
+
} catch (err) {
|
|
9189
|
+
console.warn("Failed to resolve font IDs:", err);
|
|
9190
|
+
return [];
|
|
9191
|
+
}
|
|
9192
|
+
}
|
|
9193
|
+
async function resolveFontsForExport(designs, templateFonts) {
|
|
9194
|
+
const result = [];
|
|
9195
|
+
const foundNames = /* @__PURE__ */ new Set();
|
|
9196
|
+
if (Array.isArray(templateFonts) && templateFonts.length > 0) {
|
|
9197
|
+
templateFonts.forEach((f) => {
|
|
9198
|
+
if (f && typeof f === "object") {
|
|
9199
|
+
const fontName = f.fontName || f.fontFamily;
|
|
9200
|
+
const fontFile = f.fontFile;
|
|
9201
|
+
if (fontName && fontFile) {
|
|
9202
|
+
result.push({ fontName, fontFamily: f.fontFamily, fontFile });
|
|
9203
|
+
foundNames.add(fontName.toLowerCase());
|
|
9204
|
+
if (f.fontFamily) foundNames.add(f.fontFamily.toLowerCase());
|
|
9205
|
+
}
|
|
9206
|
+
}
|
|
9207
|
+
});
|
|
9208
|
+
}
|
|
9209
|
+
const usedFontNames = /* @__PURE__ */ new Set();
|
|
9210
|
+
const designsArr = Array.isArray(designs) ? designs : [designs];
|
|
9211
|
+
designsArr.forEach((d2) => {
|
|
9212
|
+
if (d2) {
|
|
9213
|
+
const extracted = extractFontFamilies(d2);
|
|
9214
|
+
extracted.forEach((name) => {
|
|
9215
|
+
if (!foundNames.has(name.toLowerCase())) {
|
|
9216
|
+
usedFontNames.add(name);
|
|
9217
|
+
}
|
|
9218
|
+
});
|
|
9219
|
+
}
|
|
9220
|
+
});
|
|
9221
|
+
if (usedFontNames.size > 0) {
|
|
9222
|
+
try {
|
|
9223
|
+
const allFonts = await fetchAllFontsCached();
|
|
9224
|
+
usedFontNames.forEach((name) => {
|
|
9225
|
+
const lower = name.toLowerCase();
|
|
9226
|
+
const matched = allFonts.find((f) => {
|
|
9227
|
+
if (f.fontName && f.fontName.toLowerCase() === lower) return true;
|
|
9228
|
+
if (f.fontFamily && f.fontFamily.toLowerCase() === lower) return true;
|
|
9229
|
+
const children = f.childFonts || f.chieldFonts || [];
|
|
9230
|
+
return children.some(
|
|
9231
|
+
(c2) => c2.fontName && c2.fontName.toLowerCase() === lower || c2.fontFamily && c2.fontFamily.toLowerCase() === lower
|
|
9232
|
+
);
|
|
9233
|
+
});
|
|
9234
|
+
if (matched) {
|
|
9235
|
+
if (matched.fontName && matched.fontFile && !foundNames.has(matched.fontName.toLowerCase())) {
|
|
9236
|
+
result.push({
|
|
9237
|
+
fontName: matched.fontName,
|
|
9238
|
+
fontFamily: matched.fontFamily,
|
|
9239
|
+
fontFile: matched.fontFile
|
|
9240
|
+
});
|
|
9241
|
+
foundNames.add(matched.fontName.toLowerCase());
|
|
9242
|
+
}
|
|
9243
|
+
const children = matched.childFonts || matched.chieldFonts || [];
|
|
9244
|
+
children.forEach((c2) => {
|
|
9245
|
+
const childName = c2.fontName || name;
|
|
9246
|
+
if (c2.fontFile && !foundNames.has(childName.toLowerCase())) {
|
|
9247
|
+
result.push({
|
|
9248
|
+
fontName: childName,
|
|
9249
|
+
fontFamily: c2.fontFamily,
|
|
9250
|
+
fontFile: c2.fontFile
|
|
9251
|
+
});
|
|
9252
|
+
foundNames.add(childName.toLowerCase());
|
|
9253
|
+
}
|
|
9254
|
+
});
|
|
9255
|
+
}
|
|
9256
|
+
});
|
|
9257
|
+
} catch (err) {
|
|
9258
|
+
console.warn("Failed to resolve fonts for export:", err);
|
|
9259
|
+
}
|
|
9260
|
+
}
|
|
9261
|
+
return result;
|
|
9262
|
+
}
|
|
9021
9263
|
async function loadPageToCanvas(canvas2, json, pageSide) {
|
|
9022
9264
|
if (!canvas2) return;
|
|
9023
9265
|
canvas2.discardActiveObject();
|
|
@@ -9025,6 +9267,7 @@
|
|
|
9025
9267
|
if (json) {
|
|
9026
9268
|
try {
|
|
9027
9269
|
const parsedJson = typeof json === "string" ? JSON.parse(json) : json;
|
|
9270
|
+
await loadTemplateFontsIntoHead(null, parsedJson);
|
|
9028
9271
|
const loadPromise = canvas2.loadFromJSON(parsedJson);
|
|
9029
9272
|
const timeoutPromise = new Promise(
|
|
9030
9273
|
(_2, reject) => setTimeout(() => reject(new Error("Canvas loadFromJSON timeout")), 4e3)
|
|
@@ -9366,7 +9609,23 @@
|
|
|
9366
9609
|
"__borderRect",
|
|
9367
9610
|
"__rotationControlSize",
|
|
9368
9611
|
"__showDegree",
|
|
9369
|
-
"textAlign"
|
|
9612
|
+
"textAlign",
|
|
9613
|
+
"fontFamily",
|
|
9614
|
+
"fontSize",
|
|
9615
|
+
"fontWeight",
|
|
9616
|
+
"fontStyle",
|
|
9617
|
+
"underline",
|
|
9618
|
+
"linethrough",
|
|
9619
|
+
"overline",
|
|
9620
|
+
"fill",
|
|
9621
|
+
"stroke",
|
|
9622
|
+
"strokeWidth",
|
|
9623
|
+
"lineHeight",
|
|
9624
|
+
"charSpacing",
|
|
9625
|
+
"styles",
|
|
9626
|
+
"paintFirst",
|
|
9627
|
+
"strokeUniform",
|
|
9628
|
+
"objectCaching"
|
|
9370
9629
|
];
|
|
9371
9630
|
const useHistoryStore = create((set, get) => ({
|
|
9372
9631
|
canvas: null,
|
|
@@ -9484,248 +9743,6 @@
|
|
|
9484
9743
|
}
|
|
9485
9744
|
}
|
|
9486
9745
|
}));
|
|
9487
|
-
const FontService = {
|
|
9488
|
-
async getAll(data) {
|
|
9489
|
-
const res = await apiFetch(`/api/fonts?page=${data.page}&limit=${data.limit}&status=true`, {
|
|
9490
|
-
method: "GET"
|
|
9491
|
-
});
|
|
9492
|
-
return res;
|
|
9493
|
-
}
|
|
9494
|
-
};
|
|
9495
|
-
let cachedFontsPromise = null;
|
|
9496
|
-
async function fetchAllFontsCached() {
|
|
9497
|
-
if (!cachedFontsPromise) {
|
|
9498
|
-
cachedFontsPromise = FontService.getAll({ page: 1, limit: 1e3 }).then((res) => res?.data || []).catch((err) => {
|
|
9499
|
-
console.warn("Failed to fetch all fonts:", err);
|
|
9500
|
-
cachedFontsPromise = null;
|
|
9501
|
-
return [];
|
|
9502
|
-
});
|
|
9503
|
-
}
|
|
9504
|
-
return cachedFontsPromise;
|
|
9505
|
-
}
|
|
9506
|
-
async function injectFontFace(fontName, fontUrl) {
|
|
9507
|
-
if (!fontName || !fontUrl || typeof document === "undefined") return;
|
|
9508
|
-
const cleanedName = fontName.trim().replace(/^['"]+|['"]+$/g, "");
|
|
9509
|
-
const id = `font-${cleanedName.replace(/\s+/g, "-").toLowerCase()}`;
|
|
9510
|
-
if (!document.getElementById(id)) {
|
|
9511
|
-
const style = document.createElement("style");
|
|
9512
|
-
style.id = id;
|
|
9513
|
-
style.innerHTML = `
|
|
9514
|
-
@font-face {
|
|
9515
|
-
font-family: '${cleanedName}';
|
|
9516
|
-
src: url('${fontUrl}');
|
|
9517
|
-
font-display: swap;
|
|
9518
|
-
}
|
|
9519
|
-
`;
|
|
9520
|
-
document.head.appendChild(style);
|
|
9521
|
-
}
|
|
9522
|
-
try {
|
|
9523
|
-
await document.fonts.load(`16px "${cleanedName}"`);
|
|
9524
|
-
await document.fonts.ready;
|
|
9525
|
-
} catch {
|
|
9526
|
-
}
|
|
9527
|
-
}
|
|
9528
|
-
async function loadTemplateFontsIntoHead(templateFonts, pagesDesignData) {
|
|
9529
|
-
if (typeof document === "undefined") return;
|
|
9530
|
-
const loadPromises = [];
|
|
9531
|
-
if (Array.isArray(templateFonts) && templateFonts.length > 0) {
|
|
9532
|
-
templateFonts.forEach((f) => {
|
|
9533
|
-
if (f && typeof f === "object") {
|
|
9534
|
-
const fontName = f.fontName || f.fontFamily;
|
|
9535
|
-
const fontUrl = f.fontFile;
|
|
9536
|
-
if (fontName && fontUrl) {
|
|
9537
|
-
loadPromises.push(injectFontFace(fontName, fontUrl));
|
|
9538
|
-
}
|
|
9539
|
-
if (f.fontFamily && f.fontFamily !== fontName && fontUrl) {
|
|
9540
|
-
loadPromises.push(injectFontFace(f.fontFamily, fontUrl));
|
|
9541
|
-
}
|
|
9542
|
-
}
|
|
9543
|
-
});
|
|
9544
|
-
}
|
|
9545
|
-
const designs = Array.isArray(pagesDesignData) ? pagesDesignData : [pagesDesignData];
|
|
9546
|
-
const usedFontNames = /* @__PURE__ */ new Set();
|
|
9547
|
-
designs.forEach((d2) => {
|
|
9548
|
-
if (d2) {
|
|
9549
|
-
const extracted = extractFontFamilies(d2);
|
|
9550
|
-
extracted.forEach((name) => usedFontNames.add(name));
|
|
9551
|
-
}
|
|
9552
|
-
});
|
|
9553
|
-
if (usedFontNames.size > 0) {
|
|
9554
|
-
try {
|
|
9555
|
-
const allFonts = await fetchAllFontsCached();
|
|
9556
|
-
usedFontNames.forEach((name) => {
|
|
9557
|
-
const lower = name.toLowerCase();
|
|
9558
|
-
const matched = allFonts.find((f) => {
|
|
9559
|
-
if (f.fontName && f.fontName.toLowerCase() === lower) return true;
|
|
9560
|
-
if (f.fontFamily && f.fontFamily.toLowerCase() === lower) return true;
|
|
9561
|
-
const children = f.childFonts || f.chieldFonts || [];
|
|
9562
|
-
return children.some(
|
|
9563
|
-
(c2) => c2.fontName && c2.fontName.toLowerCase() === lower || c2.fontFamily && c2.fontFamily.toLowerCase() === lower
|
|
9564
|
-
);
|
|
9565
|
-
});
|
|
9566
|
-
if (matched) {
|
|
9567
|
-
if (matched.fontName && matched.fontFile) {
|
|
9568
|
-
loadPromises.push(injectFontFace(matched.fontName, matched.fontFile));
|
|
9569
|
-
}
|
|
9570
|
-
if (matched.fontFamily && matched.fontFile) {
|
|
9571
|
-
loadPromises.push(injectFontFace(matched.fontFamily, matched.fontFile));
|
|
9572
|
-
}
|
|
9573
|
-
const children = matched.childFonts || matched.chieldFonts || [];
|
|
9574
|
-
children.forEach((c2) => {
|
|
9575
|
-
if (c2.fontName && c2.fontFile) {
|
|
9576
|
-
loadPromises.push(injectFontFace(c2.fontName, c2.fontFile));
|
|
9577
|
-
}
|
|
9578
|
-
});
|
|
9579
|
-
}
|
|
9580
|
-
});
|
|
9581
|
-
} catch (err) {
|
|
9582
|
-
console.warn("Failed to fetch fonts list for font injection:", err);
|
|
9583
|
-
}
|
|
9584
|
-
}
|
|
9585
|
-
await Promise.allSettled(loadPromises);
|
|
9586
|
-
try {
|
|
9587
|
-
await document.fonts.ready;
|
|
9588
|
-
} catch {
|
|
9589
|
-
}
|
|
9590
|
-
}
|
|
9591
|
-
function extractFontFamilies(designJson) {
|
|
9592
|
-
if (!designJson) return [];
|
|
9593
|
-
const fonts = /* @__PURE__ */ new Set();
|
|
9594
|
-
const traverse = (obj) => {
|
|
9595
|
-
if (!obj) return;
|
|
9596
|
-
if (obj.fontFamily && typeof obj.fontFamily === "string") {
|
|
9597
|
-
const cleaned = obj.fontFamily.trim().replace(/^['"]+|['"]+$/g, "");
|
|
9598
|
-
if (cleaned) fonts.add(cleaned);
|
|
9599
|
-
}
|
|
9600
|
-
if (obj.fontId && typeof obj.fontId === "string") {
|
|
9601
|
-
fonts.add(obj.fontId.trim());
|
|
9602
|
-
}
|
|
9603
|
-
if (obj.styles && typeof obj.styles === "object") {
|
|
9604
|
-
Object.values(obj.styles).forEach((line) => {
|
|
9605
|
-
if (line && typeof line === "object") {
|
|
9606
|
-
Object.values(line).forEach((charStyle) => {
|
|
9607
|
-
if (charStyle?.fontFamily && typeof charStyle.fontFamily === "string") {
|
|
9608
|
-
const cleaned = charStyle.fontFamily.trim().replace(/^['"]+|['"]+$/g, "");
|
|
9609
|
-
if (cleaned) fonts.add(cleaned);
|
|
9610
|
-
}
|
|
9611
|
-
});
|
|
9612
|
-
}
|
|
9613
|
-
});
|
|
9614
|
-
}
|
|
9615
|
-
if (Array.isArray(obj.objects)) {
|
|
9616
|
-
obj.objects.forEach(traverse);
|
|
9617
|
-
}
|
|
9618
|
-
};
|
|
9619
|
-
if (Array.isArray(designJson.objects)) {
|
|
9620
|
-
designJson.objects.forEach(traverse);
|
|
9621
|
-
} else if (Array.isArray(designJson)) {
|
|
9622
|
-
designJson.forEach(traverse);
|
|
9623
|
-
} else {
|
|
9624
|
-
traverse(designJson);
|
|
9625
|
-
}
|
|
9626
|
-
return Array.from(fonts);
|
|
9627
|
-
}
|
|
9628
|
-
async function resolveFontIds(fontNames) {
|
|
9629
|
-
if (!fontNames || !fontNames.length) return [];
|
|
9630
|
-
try {
|
|
9631
|
-
const allFonts = await fetchAllFontsCached();
|
|
9632
|
-
const matchedIds = /* @__PURE__ */ new Set();
|
|
9633
|
-
for (const rawName of fontNames) {
|
|
9634
|
-
const name = rawName.trim().replace(/^['"]+|['"]+$/g, "");
|
|
9635
|
-
const lower = name.toLowerCase();
|
|
9636
|
-
if (/^[0-9a-fA-F]{24}$/.test(name)) {
|
|
9637
|
-
matchedIds.add(name);
|
|
9638
|
-
continue;
|
|
9639
|
-
}
|
|
9640
|
-
const matchedFont = allFonts.find((f) => {
|
|
9641
|
-
if (f._id === name) return true;
|
|
9642
|
-
if (f.fontFamily && f.fontFamily.toLowerCase() === lower) return true;
|
|
9643
|
-
if (f.fontName && f.fontName.toLowerCase() === lower) return true;
|
|
9644
|
-
const children = f.childFonts || f.chieldFonts || [];
|
|
9645
|
-
return children.some(
|
|
9646
|
-
(c2) => c2._id === name || c2.fontName && c2.fontName.toLowerCase() === lower || c2.fontFamily && c2.fontFamily.toLowerCase() === lower
|
|
9647
|
-
);
|
|
9648
|
-
});
|
|
9649
|
-
if (matchedFont?._id) {
|
|
9650
|
-
matchedIds.add(matchedFont._id);
|
|
9651
|
-
}
|
|
9652
|
-
}
|
|
9653
|
-
return Array.from(matchedIds);
|
|
9654
|
-
} catch (err) {
|
|
9655
|
-
console.warn("Failed to resolve font IDs:", err);
|
|
9656
|
-
return [];
|
|
9657
|
-
}
|
|
9658
|
-
}
|
|
9659
|
-
async function resolveFontsForExport(designs, templateFonts) {
|
|
9660
|
-
const result = [];
|
|
9661
|
-
const foundNames = /* @__PURE__ */ new Set();
|
|
9662
|
-
if (Array.isArray(templateFonts) && templateFonts.length > 0) {
|
|
9663
|
-
templateFonts.forEach((f) => {
|
|
9664
|
-
if (f && typeof f === "object") {
|
|
9665
|
-
const fontName = f.fontName || f.fontFamily;
|
|
9666
|
-
const fontFile = f.fontFile;
|
|
9667
|
-
if (fontName && fontFile) {
|
|
9668
|
-
result.push({ fontName, fontFamily: f.fontFamily, fontFile });
|
|
9669
|
-
foundNames.add(fontName.toLowerCase());
|
|
9670
|
-
if (f.fontFamily) foundNames.add(f.fontFamily.toLowerCase());
|
|
9671
|
-
}
|
|
9672
|
-
}
|
|
9673
|
-
});
|
|
9674
|
-
}
|
|
9675
|
-
const usedFontNames = /* @__PURE__ */ new Set();
|
|
9676
|
-
const designsArr = Array.isArray(designs) ? designs : [designs];
|
|
9677
|
-
designsArr.forEach((d2) => {
|
|
9678
|
-
if (d2) {
|
|
9679
|
-
const extracted = extractFontFamilies(d2);
|
|
9680
|
-
extracted.forEach((name) => {
|
|
9681
|
-
if (!foundNames.has(name.toLowerCase())) {
|
|
9682
|
-
usedFontNames.add(name);
|
|
9683
|
-
}
|
|
9684
|
-
});
|
|
9685
|
-
}
|
|
9686
|
-
});
|
|
9687
|
-
if (usedFontNames.size > 0) {
|
|
9688
|
-
try {
|
|
9689
|
-
const allFonts = await fetchAllFontsCached();
|
|
9690
|
-
usedFontNames.forEach((name) => {
|
|
9691
|
-
const lower = name.toLowerCase();
|
|
9692
|
-
const matched = allFonts.find((f) => {
|
|
9693
|
-
if (f.fontName && f.fontName.toLowerCase() === lower) return true;
|
|
9694
|
-
if (f.fontFamily && f.fontFamily.toLowerCase() === lower) return true;
|
|
9695
|
-
const children = f.childFonts || f.chieldFonts || [];
|
|
9696
|
-
return children.some(
|
|
9697
|
-
(c2) => c2.fontName && c2.fontName.toLowerCase() === lower || c2.fontFamily && c2.fontFamily.toLowerCase() === lower
|
|
9698
|
-
);
|
|
9699
|
-
});
|
|
9700
|
-
if (matched) {
|
|
9701
|
-
if (matched.fontName && matched.fontFile && !foundNames.has(matched.fontName.toLowerCase())) {
|
|
9702
|
-
result.push({
|
|
9703
|
-
fontName: matched.fontName,
|
|
9704
|
-
fontFamily: matched.fontFamily,
|
|
9705
|
-
fontFile: matched.fontFile
|
|
9706
|
-
});
|
|
9707
|
-
foundNames.add(matched.fontName.toLowerCase());
|
|
9708
|
-
}
|
|
9709
|
-
const children = matched.childFonts || matched.chieldFonts || [];
|
|
9710
|
-
children.forEach((c2) => {
|
|
9711
|
-
const childName = c2.fontName || name;
|
|
9712
|
-
if (c2.fontFile && !foundNames.has(childName.toLowerCase())) {
|
|
9713
|
-
result.push({
|
|
9714
|
-
fontName: childName,
|
|
9715
|
-
fontFamily: c2.fontFamily,
|
|
9716
|
-
fontFile: c2.fontFile
|
|
9717
|
-
});
|
|
9718
|
-
foundNames.add(childName.toLowerCase());
|
|
9719
|
-
}
|
|
9720
|
-
});
|
|
9721
|
-
}
|
|
9722
|
-
});
|
|
9723
|
-
} catch (err) {
|
|
9724
|
-
console.warn("Failed to resolve fonts for export:", err);
|
|
9725
|
-
}
|
|
9726
|
-
}
|
|
9727
|
-
return result;
|
|
9728
|
-
}
|
|
9729
9746
|
const WATERMARK_TEXT = "vibrantprintshop";
|
|
9730
9747
|
function createWatermark(curWorkarea, pageSide) {
|
|
9731
9748
|
const curW = curWorkarea.getScaledWidth ? curWorkarea.getScaledWidth() : curWorkarea.width * (curWorkarea.scaleX || 1);
|
|
@@ -9818,8 +9835,18 @@
|
|
|
9818
9835
|
}
|
|
9819
9836
|
const ShapeService = {
|
|
9820
9837
|
async getAll(data) {
|
|
9838
|
+
const params = new URLSearchParams();
|
|
9839
|
+
if (data.page) params.append("page", String(data.page));
|
|
9840
|
+
if (data.limit) params.append("limit", String(data.limit));
|
|
9841
|
+
if (data.category && data.category !== "ALL" && data.category !== "null" && data.category !== "undefined") {
|
|
9842
|
+
params.append("category", data.category);
|
|
9843
|
+
}
|
|
9844
|
+
if (data.search && data.search.trim()) {
|
|
9845
|
+
params.append("search", data.search.trim());
|
|
9846
|
+
}
|
|
9847
|
+
params.append("status", "true");
|
|
9821
9848
|
const res = await apiFetch(
|
|
9822
|
-
`/api/cliparts
|
|
9849
|
+
`/api/cliparts?${params.toString()}`,
|
|
9823
9850
|
{
|
|
9824
9851
|
method: "GET"
|
|
9825
9852
|
}
|
|
@@ -9829,8 +9856,17 @@
|
|
|
9829
9856
|
};
|
|
9830
9857
|
},
|
|
9831
9858
|
async getAllExternal(data) {
|
|
9859
|
+
const params = new URLSearchParams();
|
|
9860
|
+
if (data.page) params.append("page", String(data.page));
|
|
9861
|
+
if (data.limit) params.append("limit", String(data.limit));
|
|
9862
|
+
if (data.keyType) params.append("keyType", data.keyType);
|
|
9863
|
+
if (data.isVector) params.append("isVector", "true");
|
|
9864
|
+
if (data.query && data.query.trim()) {
|
|
9865
|
+
params.append("query", data.query.trim());
|
|
9866
|
+
}
|
|
9867
|
+
params.append("status", "true");
|
|
9832
9868
|
const res = await apiFetch(
|
|
9833
|
-
`/api/external/images
|
|
9869
|
+
`/api/external/images?${params.toString()}`,
|
|
9834
9870
|
{
|
|
9835
9871
|
method: "GET"
|
|
9836
9872
|
}
|
|
@@ -101911,8 +101947,43 @@ end`);
|
|
|
101911
101947
|
if (!uploadedImageUrl) {
|
|
101912
101948
|
throw new Error("Server upload returned empty image URL");
|
|
101913
101949
|
}
|
|
101950
|
+
const canvas22 = useEditorStore.getState().canvas;
|
|
101951
|
+
if (!templateId || !canvas22) return;
|
|
101952
|
+
const { pages: historyPages, currentPage } = useHistoryStore.getState();
|
|
101953
|
+
const editorPages = useEditorStore.getState().pages || {};
|
|
101954
|
+
const currentJson = canvas22.toObject(["name"]);
|
|
101955
|
+
const currentImage = getWorkareaDataUrl(canvas22, 1, currentPage);
|
|
101956
|
+
const pages = await Promise.all(
|
|
101957
|
+
["front", "back"].map(async (side, i2) => {
|
|
101958
|
+
const isCurrent = currentPage === side;
|
|
101959
|
+
const designData = isCurrent ? currentJson : editorPages[side] || historyPages[side]?.history?.at(-1) || null;
|
|
101960
|
+
if (!designData) return null;
|
|
101961
|
+
const sideImage = isCurrent ? currentImage : await renderPageDataUrl(designData, canvas22, 1, side);
|
|
101962
|
+
const uploadedImageUrl2 = await UploadService.uploadDataUrl(
|
|
101963
|
+
sideImage,
|
|
101964
|
+
`template-${templateId}-${side}-${Date.now()}.png`
|
|
101965
|
+
);
|
|
101966
|
+
return {
|
|
101967
|
+
side,
|
|
101968
|
+
designData,
|
|
101969
|
+
image: uploadedImageUrl2,
|
|
101970
|
+
order: i2 + 1
|
|
101971
|
+
};
|
|
101972
|
+
})
|
|
101973
|
+
);
|
|
101974
|
+
const validPages = pages.filter(Boolean);
|
|
101975
|
+
const usedFontNames = /* @__PURE__ */ new Set();
|
|
101976
|
+
validPages.forEach((p2) => {
|
|
101977
|
+
if (p2?.designData) {
|
|
101978
|
+
const fontsInPage = extractFontFamilies(p2.designData);
|
|
101979
|
+
fontsInPage.forEach((f) => usedFontNames.add(f));
|
|
101980
|
+
}
|
|
101981
|
+
});
|
|
101982
|
+
const resolvedFontIds = await resolveFontIds(Array.from(usedFontNames));
|
|
101914
101983
|
const res = await TemplateDesignService.updateTemplateDesign(templateId, {
|
|
101915
|
-
image: uploadedImageUrl
|
|
101984
|
+
image: uploadedImageUrl,
|
|
101985
|
+
pages: validPages,
|
|
101986
|
+
fonts: resolvedFontIds
|
|
101916
101987
|
});
|
|
101917
101988
|
if (res && res.success) {
|
|
101918
101989
|
setGenerateSuccess(true);
|
|
@@ -102117,6 +102188,16 @@ end`);
|
|
|
102117
102188
|
const resetTour = useTourStore((s2) => s2.reset);
|
|
102118
102189
|
const templateId = getQueryParam("templateId") || getQueryParam("id");
|
|
102119
102190
|
const designType = (getQueryParam("designType") || getQueryParam("designtype") || getQueryParam("type"))?.toLowerCase();
|
|
102191
|
+
const handleLeavePage = () => {
|
|
102192
|
+
if (designType === "text") {
|
|
102193
|
+
spaNavigate("/design-configuration/text-templates");
|
|
102194
|
+
return;
|
|
102195
|
+
}
|
|
102196
|
+
if (designType === "template") {
|
|
102197
|
+
spaNavigate("/design-configuration/template-designs");
|
|
102198
|
+
return;
|
|
102199
|
+
}
|
|
102200
|
+
};
|
|
102120
102201
|
const handleSaveTextTemplate = async () => {
|
|
102121
102202
|
const canvas2 = useEditorStore.getState().canvas;
|
|
102122
102203
|
if (!templateId || !canvas2) return;
|
|
@@ -102211,8 +102292,42 @@ end`);
|
|
|
102211
102292
|
}
|
|
102212
102293
|
};
|
|
102213
102294
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "w-full h-14 bg-[#111] text-white flex items-center justify-between px-4 border-b border-gray-800 relative z-[9999] whitespace-nowrap", children: [
|
|
102214
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-
|
|
102215
|
-
/* @__PURE__ */ jsxRuntime.
|
|
102295
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2.5 shrink-0", children: [
|
|
102296
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
102297
|
+
"button",
|
|
102298
|
+
{
|
|
102299
|
+
id: "topbar-back-button",
|
|
102300
|
+
type: "button",
|
|
102301
|
+
onClick: handleLeavePage,
|
|
102302
|
+
disabled: isSaving,
|
|
102303
|
+
className: `flex items-center gap-1.5 px-2.5 py-1.5 bg-[#222] hover:bg-[#2c2c2c] active:bg-[#1a1a1a] text-gray-300 hover:text-white border border-gray-700/70 hover:border-gray-500 rounded-lg text-xs font-medium transition-all group shadow-sm ${isSaving ? "opacity-50 cursor-not-allowed" : "cursor-pointer"}`,
|
|
102304
|
+
title: "Back / Leave page",
|
|
102305
|
+
"aria-label": "Back / Leave page",
|
|
102306
|
+
children: [
|
|
102307
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
102308
|
+
"svg",
|
|
102309
|
+
{
|
|
102310
|
+
className: "w-4 h-4 transition-transform duration-150 group-hover:-translate-x-0.5 text-gray-300 group-hover:text-white",
|
|
102311
|
+
fill: "none",
|
|
102312
|
+
viewBox: "0 0 24 24",
|
|
102313
|
+
stroke: "currentColor",
|
|
102314
|
+
strokeWidth: 2.2,
|
|
102315
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
102316
|
+
"path",
|
|
102317
|
+
{
|
|
102318
|
+
strokeLinecap: "round",
|
|
102319
|
+
strokeLinejoin: "round",
|
|
102320
|
+
d: "M10 19l-7-7m0 0l7-7m-7 7h18"
|
|
102321
|
+
}
|
|
102322
|
+
)
|
|
102323
|
+
}
|
|
102324
|
+
),
|
|
102325
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-semibold tracking-wide", children: "Back" })
|
|
102326
|
+
]
|
|
102327
|
+
}
|
|
102328
|
+
),
|
|
102329
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-px h-5 bg-gray-700/80 hidden sm:block" }),
|
|
102330
|
+
/* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-lg font-semibold tracking-tight", children: "Vibrant" }),
|
|
102216
102331
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-px h-6 bg-gray-600 hidden md:block" })
|
|
102217
102332
|
] }),
|
|
102218
102333
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2.5 shrink-0 ml-4", children: [
|
|
@@ -102586,6 +102701,7 @@ end`);
|
|
|
102586
102701
|
const [page, setPage] = React.useState(1);
|
|
102587
102702
|
const scrollRef = React.useRef(null);
|
|
102588
102703
|
const [hasMore, setHasMore] = React.useState(true);
|
|
102704
|
+
const templateId = getQueryParam("templateId");
|
|
102589
102705
|
React.useEffect(() => {
|
|
102590
102706
|
if (!productData?._id) return;
|
|
102591
102707
|
startLoading();
|
|
@@ -102677,7 +102793,7 @@ end`);
|
|
|
102677
102793
|
)
|
|
102678
102794
|
] }),
|
|
102679
102795
|
/* @__PURE__ */ jsxRuntime.jsx("div", { ref: scrollRef, className: "h-[calc(100vh-125px)] overflow-auto custom-scrollbar", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-2 gap-3 pb-12 mx-4", children: [
|
|
102680
|
-
templates.map((tpl) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
102796
|
+
templates.filter((t2) => t2.image && t2._id !== templateId).map((tpl) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
102681
102797
|
"button",
|
|
102682
102798
|
{
|
|
102683
102799
|
className: "rounded overflow-hidden group relative cursor-pointer border border-transparent hover:border-[#02bc71] transition-all",
|
|
@@ -103140,26 +103256,63 @@ end`);
|
|
|
103140
103256
|
elements: elements2,
|
|
103141
103257
|
onBack,
|
|
103142
103258
|
scrollRef,
|
|
103143
|
-
onSearch
|
|
103259
|
+
onSearch,
|
|
103260
|
+
initialSearch = "",
|
|
103261
|
+
title,
|
|
103262
|
+
backText
|
|
103144
103263
|
}) {
|
|
103145
|
-
const [search, setSearch] = React.useState(
|
|
103146
|
-
const { isLoading } = useLoaderStore
|
|
103264
|
+
const [search, setSearch] = React.useState(initialSearch);
|
|
103265
|
+
const { isLoading } = useLoaderStore();
|
|
103147
103266
|
const { addRecentItem } = useRecentElementsStore();
|
|
103267
|
+
React.useEffect(() => {
|
|
103268
|
+
setSearch(initialSearch);
|
|
103269
|
+
}, [initialSearch]);
|
|
103148
103270
|
const clearSearch = () => {
|
|
103149
103271
|
setSearch("");
|
|
103150
103272
|
onSearch("");
|
|
103151
103273
|
};
|
|
103152
|
-
const
|
|
103153
|
-
(
|
|
103154
|
-
|
|
103155
|
-
|
|
103156
|
-
|
|
103157
|
-
|
|
103158
|
-
|
|
103159
|
-
|
|
103160
|
-
|
|
103161
|
-
|
|
103162
|
-
|
|
103274
|
+
const handleSearchSubmit = () => {
|
|
103275
|
+
onSearch(search.trim());
|
|
103276
|
+
};
|
|
103277
|
+
const handleKeyDown = (e2) => {
|
|
103278
|
+
e2.stopPropagation();
|
|
103279
|
+
if (e2.key === "Enter") {
|
|
103280
|
+
handleSearchSubmit();
|
|
103281
|
+
}
|
|
103282
|
+
};
|
|
103283
|
+
const dynamicBackLabel = backText || (title ? title : "Back");
|
|
103284
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col h-full", children: [
|
|
103285
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mx-4 mb-2.5 flex items-center", children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
103286
|
+
"button",
|
|
103287
|
+
{
|
|
103288
|
+
type: "button",
|
|
103289
|
+
onClick: onBack,
|
|
103290
|
+
className: "cursor-pointer flex items-center gap-1.5 text-xs font-semibold text-neutral-300 hover:text-white transition-colors group",
|
|
103291
|
+
title: `Back to ${dynamicBackLabel}`,
|
|
103292
|
+
children: [
|
|
103293
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
103294
|
+
"svg",
|
|
103295
|
+
{
|
|
103296
|
+
width: "16",
|
|
103297
|
+
height: "16",
|
|
103298
|
+
viewBox: "0 0 24 24",
|
|
103299
|
+
fill: "none",
|
|
103300
|
+
stroke: "currentColor",
|
|
103301
|
+
strokeWidth: "2.5",
|
|
103302
|
+
strokeLinecap: "round",
|
|
103303
|
+
strokeLinejoin: "round",
|
|
103304
|
+
className: "group-hover:-translate-x-0.5 transition-transform text-neutral-400 group-hover:text-white",
|
|
103305
|
+
children: [
|
|
103306
|
+
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "19", y1: "12", x2: "5", y2: "12" }),
|
|
103307
|
+
/* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "12 19 5 12 12 5" })
|
|
103308
|
+
]
|
|
103309
|
+
}
|
|
103310
|
+
),
|
|
103311
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "capitalize truncate max-w-[240px]", children: dynamicBackLabel })
|
|
103312
|
+
]
|
|
103313
|
+
}
|
|
103314
|
+
) }),
|
|
103315
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-neutral-800 px-3 py-2 rounded flex items-center gap-2 mb-4 mx-4 relative focus-within:ring-1 focus-within:ring-neutral-600", children: [
|
|
103163
103316
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
103164
103317
|
"svg",
|
|
103165
103318
|
{
|
|
@@ -103167,7 +103320,8 @@ end`);
|
|
|
103167
103320
|
stroke: "currentColor",
|
|
103168
103321
|
fill: "none",
|
|
103169
103322
|
viewBox: "0 0 24 24",
|
|
103170
|
-
className: "text-gray-
|
|
103323
|
+
className: "text-gray-400 shrink-0 cursor-pointer hover:text-white transition-colors",
|
|
103324
|
+
onClick: handleSearchSubmit,
|
|
103171
103325
|
children: [
|
|
103172
103326
|
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "11", cy: "11", r: "8" }),
|
|
103173
103327
|
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "21", y1: "21", x2: "16.65", y2: "16.65" })
|
|
@@ -103179,9 +103333,10 @@ end`);
|
|
|
103179
103333
|
{
|
|
103180
103334
|
type: "text",
|
|
103181
103335
|
value: search,
|
|
103182
|
-
onChange:
|
|
103183
|
-
onKeyDown:
|
|
103184
|
-
|
|
103336
|
+
onChange: (e2) => setSearch(e2.target.value),
|
|
103337
|
+
onKeyDown: handleKeyDown,
|
|
103338
|
+
placeholder: title ? `Search in ${title}...` : "Search elements & shapes...",
|
|
103339
|
+
className: "bg-transparent outline-none text-sm text-gray-200 placeholder-gray-500 w-full pr-8"
|
|
103185
103340
|
}
|
|
103186
103341
|
),
|
|
103187
103342
|
search && /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -103189,13 +103344,13 @@ end`);
|
|
|
103189
103344
|
{
|
|
103190
103345
|
type: "button",
|
|
103191
103346
|
onClick: clearSearch,
|
|
103192
|
-
className: "absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-white transition-colors p-1",
|
|
103347
|
+
className: "absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-white transition-colors p-1 cursor-pointer",
|
|
103193
103348
|
"aria-label": "Clear search",
|
|
103194
103349
|
children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
103195
103350
|
"svg",
|
|
103196
103351
|
{
|
|
103197
|
-
width: "
|
|
103198
|
-
height: "
|
|
103352
|
+
width: "14",
|
|
103353
|
+
height: "14",
|
|
103199
103354
|
viewBox: "0 0 24 24",
|
|
103200
103355
|
fill: "none",
|
|
103201
103356
|
stroke: "currentColor",
|
|
@@ -103209,9 +103364,20 @@ end`);
|
|
|
103209
103364
|
}
|
|
103210
103365
|
)
|
|
103211
103366
|
] }),
|
|
103212
|
-
/* @__PURE__ */ jsxRuntime.jsx("hr", { className: "text-neutral-800 h-[0.5px]" }),
|
|
103213
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-
|
|
103214
|
-
/* @__PURE__ */ jsxRuntime.jsx("
|
|
103367
|
+
/* @__PURE__ */ jsxRuntime.jsx("hr", { className: "text-neutral-800 h-[0.5px] border-neutral-800" }),
|
|
103368
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 min-h-0 overflow-auto custom-scrollbar", ref: scrollRef, children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mx-4 py-5", children: elements2.length === 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "py-12 text-center", children: [
|
|
103369
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-gray-400 text-sm", children: search.trim() ? `No results found for "${search}"` : "No elements found" }),
|
|
103370
|
+
search.trim() && /* @__PURE__ */ jsxRuntime.jsx(
|
|
103371
|
+
"button",
|
|
103372
|
+
{
|
|
103373
|
+
type: "button",
|
|
103374
|
+
onClick: clearSearch,
|
|
103375
|
+
className: "mt-3 text-xs text-emerald-400 hover:underline cursor-pointer",
|
|
103376
|
+
children: "Clear search"
|
|
103377
|
+
}
|
|
103378
|
+
)
|
|
103379
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
103380
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-4 md:grid-cols-3 gap-3", children: elements2.map((shape, i2) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
103215
103381
|
"button",
|
|
103216
103382
|
{
|
|
103217
103383
|
type: "button",
|
|
@@ -103226,21 +103392,22 @@ end`);
|
|
|
103226
103392
|
useSidePanelStore.getState().setActivePanel(null);
|
|
103227
103393
|
}
|
|
103228
103394
|
},
|
|
103229
|
-
className: "w-full cursor-pointer flex items-center justify-center",
|
|
103395
|
+
className: "w-full cursor-pointer flex items-center justify-center p-2 rounded-lg bg-neutral-900/40 hover:bg-neutral-800 border border-neutral-800/60 hover:border-neutral-700 transition-colors",
|
|
103230
103396
|
children: shape.image && /* @__PURE__ */ jsxRuntime.jsx(
|
|
103231
103397
|
"img",
|
|
103232
103398
|
{
|
|
103233
103399
|
src: shape.image,
|
|
103234
|
-
alt: shape.category,
|
|
103400
|
+
alt: shape.category || "element",
|
|
103235
103401
|
width: 150,
|
|
103236
103402
|
height: 150,
|
|
103237
|
-
className: "w-full h-
|
|
103403
|
+
className: "w-full h-20 object-contain",
|
|
103404
|
+
loading: "lazy"
|
|
103238
103405
|
}
|
|
103239
103406
|
)
|
|
103240
103407
|
},
|
|
103241
|
-
i2
|
|
103408
|
+
shape._id || shape.image || i2
|
|
103242
103409
|
)) }),
|
|
103243
|
-
isLoading && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-center py-4 text-
|
|
103410
|
+
isLoading && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-center py-4 text-xs text-gray-400 animate-pulse", children: "Loading more elements..." })
|
|
103244
103411
|
] }) }) })
|
|
103245
103412
|
] });
|
|
103246
103413
|
}
|
|
@@ -103273,72 +103440,181 @@ end`);
|
|
|
103273
103440
|
const [page, setPage] = React.useState(1);
|
|
103274
103441
|
const [category, setCategory] = React.useState(null);
|
|
103275
103442
|
const [keyType, setKeyType] = React.useState(null);
|
|
103276
|
-
const [
|
|
103443
|
+
const [searchQuery, setSearchQuery] = React.useState("");
|
|
103444
|
+
const [mainSearch, setMainSearch] = React.useState("");
|
|
103445
|
+
const [isViewingAll, setIsViewingAll] = React.useState(false);
|
|
103446
|
+
const [title, setTitle] = React.useState("");
|
|
103277
103447
|
const [hasMore, setHasMore] = React.useState(true);
|
|
103278
103448
|
const scrollRef = React.useRef(null);
|
|
103279
|
-
const getSVG = async (type = null, categoryValue, pageNo = 1) => {
|
|
103449
|
+
const getSVG = async (type = null, categoryValue = null, searchVal = null, pageNo = 1) => {
|
|
103280
103450
|
startLoading();
|
|
103281
103451
|
if (pageNo === 1) {
|
|
103282
103452
|
setHasMore(true);
|
|
103283
103453
|
setPage(1);
|
|
103284
103454
|
setCategory(categoryValue);
|
|
103455
|
+
setKeyType(type);
|
|
103456
|
+
setSearchQuery(searchVal || "");
|
|
103457
|
+
}
|
|
103458
|
+
if (categoryValue === "Recent items") {
|
|
103459
|
+
const q2 = (searchVal || "").toLowerCase().trim();
|
|
103460
|
+
const filtered = q2 ? recentItems.filter(
|
|
103461
|
+
(item) => item.category?.toLowerCase().includes(q2) || item.tags?.some((t2) => t2.toLowerCase().includes(q2))
|
|
103462
|
+
) : recentItems;
|
|
103463
|
+
setElementsSVG(filtered);
|
|
103464
|
+
setHasMore(false);
|
|
103465
|
+
stopLoading();
|
|
103466
|
+
return;
|
|
103285
103467
|
}
|
|
103286
|
-
|
|
103287
|
-
|
|
103288
|
-
|
|
103289
|
-
|
|
103290
|
-
|
|
103291
|
-
|
|
103292
|
-
|
|
103293
|
-
|
|
103294
|
-
|
|
103295
|
-
|
|
103296
|
-
|
|
103297
|
-
|
|
103298
|
-
|
|
103468
|
+
try {
|
|
103469
|
+
const request = type ? ShapeService.getAllExternal({
|
|
103470
|
+
isVector: true,
|
|
103471
|
+
page: pageNo,
|
|
103472
|
+
limit: 30,
|
|
103473
|
+
keyType: type,
|
|
103474
|
+
query: searchVal || categoryValue || null
|
|
103475
|
+
}) : ShapeService.getAll({
|
|
103476
|
+
category: categoryValue || null,
|
|
103477
|
+
search: searchVal || null,
|
|
103478
|
+
page: pageNo,
|
|
103479
|
+
limit: 30
|
|
103480
|
+
});
|
|
103481
|
+
const res = await request;
|
|
103482
|
+
const items = res.items || [];
|
|
103299
103483
|
const newItems = items.filter((x2) => x2.status && x2.image);
|
|
103300
103484
|
setElementsSVG(
|
|
103301
103485
|
(prev) => pageNo === 1 ? newItems : [...prev, ...newItems]
|
|
103302
103486
|
);
|
|
103303
103487
|
if (newItems.length < 20) setHasMore(false);
|
|
103304
|
-
|
|
103488
|
+
else setHasMore(true);
|
|
103489
|
+
} catch (err) {
|
|
103490
|
+
console.error("Failed to load elements:", err);
|
|
103491
|
+
if (pageNo === 1) setElementsSVG([]);
|
|
103492
|
+
setHasMore(false);
|
|
103493
|
+
} finally {
|
|
103494
|
+
stopLoading();
|
|
103495
|
+
}
|
|
103305
103496
|
};
|
|
103306
103497
|
React.useEffect(() => {
|
|
103307
103498
|
const el = scrollRef.current;
|
|
103308
|
-
if (!el) return;
|
|
103499
|
+
if (!el || !isViewingAll) return;
|
|
103309
103500
|
const onScroll = () => {
|
|
103310
103501
|
if (!hasMore || isLoading) return;
|
|
103311
103502
|
if (el.scrollTop + el.clientHeight >= el.scrollHeight - 200) {
|
|
103312
|
-
|
|
103313
|
-
|
|
103314
|
-
|
|
103503
|
+
setPage((prev) => {
|
|
103504
|
+
const next = prev + 1;
|
|
103505
|
+
getSVG(keyType, category, searchQuery, next);
|
|
103506
|
+
return next;
|
|
103507
|
+
});
|
|
103315
103508
|
}
|
|
103316
103509
|
};
|
|
103317
103510
|
el.addEventListener("scroll", onScroll);
|
|
103318
103511
|
return () => el.removeEventListener("scroll", onScroll);
|
|
103319
|
-
}, [hasMore, isLoading]);
|
|
103320
|
-
|
|
103512
|
+
}, [hasMore, isLoading, isViewingAll, keyType, category, searchQuery]);
|
|
103513
|
+
const handleMainSearch = (val) => {
|
|
103514
|
+
const q2 = val.trim();
|
|
103515
|
+
if (!q2) return;
|
|
103516
|
+
setIsViewingAll(true);
|
|
103517
|
+
setTitle(`Search: "${q2}"`);
|
|
103518
|
+
getSVG(null, null, q2, 1);
|
|
103519
|
+
};
|
|
103520
|
+
const handleBack = () => {
|
|
103521
|
+
if (searchQuery && category && category !== "Recent items") {
|
|
103522
|
+
setSearchQuery("");
|
|
103523
|
+
setPage(1);
|
|
103524
|
+
setTitle(category);
|
|
103525
|
+
getSVG(keyType, category, "", 1);
|
|
103526
|
+
return;
|
|
103527
|
+
}
|
|
103528
|
+
setIsViewingAll(false);
|
|
103529
|
+
setElementsSVG([]);
|
|
103530
|
+
setCategory(null);
|
|
103531
|
+
setKeyType(null);
|
|
103532
|
+
setSearchQuery("");
|
|
103533
|
+
setMainSearch("");
|
|
103534
|
+
setTitle("");
|
|
103535
|
+
setPage(1);
|
|
103536
|
+
setHasMore(true);
|
|
103537
|
+
};
|
|
103538
|
+
if (isViewingAll) {
|
|
103321
103539
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
103322
103540
|
AllElements,
|
|
103323
103541
|
{
|
|
103324
103542
|
elements: elementsSVG,
|
|
103325
103543
|
scrollRef,
|
|
103544
|
+
title,
|
|
103545
|
+
backText: title || "Elements",
|
|
103546
|
+
initialSearch: searchQuery,
|
|
103326
103547
|
onSearch: (value) => {
|
|
103327
|
-
|
|
103328
|
-
|
|
103329
|
-
|
|
103548
|
+
if (value.trim()) {
|
|
103549
|
+
setTitle(category ? `${category} - "${value.trim()}"` : `Search: "${value.trim()}"`);
|
|
103550
|
+
} else {
|
|
103551
|
+
setTitle(
|
|
103552
|
+
category || (keyType === "pixabay" ? "Pixabay Vectors" : keyType === "flat" ? "FlatIcon Icons" : "All Elements")
|
|
103553
|
+
);
|
|
103554
|
+
}
|
|
103555
|
+
getSVG(keyType, category, value, 1);
|
|
103330
103556
|
},
|
|
103331
|
-
onBack:
|
|
103332
|
-
setElementsSVG([]);
|
|
103333
|
-
setPage(1);
|
|
103334
|
-
setCategory(null);
|
|
103335
|
-
setKeyType(null);
|
|
103336
|
-
setHasMore(true);
|
|
103337
|
-
}
|
|
103557
|
+
onBack: handleBack
|
|
103338
103558
|
}
|
|
103339
103559
|
);
|
|
103340
103560
|
}
|
|
103341
103561
|
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
103562
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-neutral-800 px-3 py-2 rounded flex items-center gap-2 mb-4 mx-4 relative focus-within:ring-1 focus-within:ring-neutral-600", children: [
|
|
103563
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
103564
|
+
"svg",
|
|
103565
|
+
{
|
|
103566
|
+
width: "18",
|
|
103567
|
+
stroke: "currentColor",
|
|
103568
|
+
fill: "none",
|
|
103569
|
+
viewBox: "0 0 24 24",
|
|
103570
|
+
className: "text-gray-400 shrink-0",
|
|
103571
|
+
children: [
|
|
103572
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "11", cy: "11", r: "8" }),
|
|
103573
|
+
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "21", y1: "21", x2: "16.65", y2: "16.65" })
|
|
103574
|
+
]
|
|
103575
|
+
}
|
|
103576
|
+
),
|
|
103577
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
103578
|
+
"input",
|
|
103579
|
+
{
|
|
103580
|
+
type: "text",
|
|
103581
|
+
placeholder: "Search elements & shapes...",
|
|
103582
|
+
value: mainSearch,
|
|
103583
|
+
onChange: (e2) => setMainSearch(e2.target.value),
|
|
103584
|
+
onKeyDown: (e2) => {
|
|
103585
|
+
e2.stopPropagation();
|
|
103586
|
+
if (e2.key === "Enter" && mainSearch.trim()) {
|
|
103587
|
+
handleMainSearch(mainSearch.trim());
|
|
103588
|
+
}
|
|
103589
|
+
},
|
|
103590
|
+
className: "bg-transparent outline-none text-sm text-gray-200 placeholder-gray-500 w-full pr-7"
|
|
103591
|
+
}
|
|
103592
|
+
),
|
|
103593
|
+
mainSearch && /* @__PURE__ */ jsxRuntime.jsx(
|
|
103594
|
+
"button",
|
|
103595
|
+
{
|
|
103596
|
+
type: "button",
|
|
103597
|
+
onClick: () => setMainSearch(""),
|
|
103598
|
+
className: "absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 hover:text-white transition-colors p-1 cursor-pointer",
|
|
103599
|
+
"aria-label": "Clear search",
|
|
103600
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
103601
|
+
"svg",
|
|
103602
|
+
{
|
|
103603
|
+
width: "14",
|
|
103604
|
+
height: "14",
|
|
103605
|
+
viewBox: "0 0 24 24",
|
|
103606
|
+
fill: "none",
|
|
103607
|
+
stroke: "currentColor",
|
|
103608
|
+
strokeWidth: "2",
|
|
103609
|
+
children: [
|
|
103610
|
+
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
|
|
103611
|
+
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
|
|
103612
|
+
]
|
|
103613
|
+
}
|
|
103614
|
+
)
|
|
103615
|
+
}
|
|
103616
|
+
)
|
|
103617
|
+
] }),
|
|
103342
103618
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pb-5 mx-4", children: [
|
|
103343
103619
|
/* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-md font-medium mb-3", children: "Upload From Other Media" }),
|
|
103344
103620
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-4", children: [
|
|
@@ -103346,11 +103622,13 @@ end`);
|
|
|
103346
103622
|
"button",
|
|
103347
103623
|
{
|
|
103348
103624
|
type: "button",
|
|
103349
|
-
className: "cursor-pointer",
|
|
103625
|
+
className: "cursor-pointer hover:opacity-80 transition-opacity",
|
|
103350
103626
|
onClick: () => {
|
|
103351
|
-
|
|
103352
|
-
|
|
103627
|
+
setIsViewingAll(true);
|
|
103628
|
+
setTitle("Pixabay Vectors");
|
|
103629
|
+
getSVG("pixabay", "", "", 1);
|
|
103353
103630
|
},
|
|
103631
|
+
title: "Search Pixabay Vectors",
|
|
103354
103632
|
children: /* @__PURE__ */ jsxRuntime.jsx(PixabayIcon, {})
|
|
103355
103633
|
}
|
|
103356
103634
|
),
|
|
@@ -103358,11 +103636,13 @@ end`);
|
|
|
103358
103636
|
"button",
|
|
103359
103637
|
{
|
|
103360
103638
|
type: "button",
|
|
103361
|
-
className: "cursor-pointer",
|
|
103639
|
+
className: "cursor-pointer hover:opacity-80 transition-opacity",
|
|
103362
103640
|
onClick: () => {
|
|
103363
|
-
|
|
103364
|
-
|
|
103641
|
+
setIsViewingAll(true);
|
|
103642
|
+
setTitle("FlatIcon Icons");
|
|
103643
|
+
getSVG("flat", "", "", 1);
|
|
103365
103644
|
},
|
|
103645
|
+
title: "Search FlatIcon Icons",
|
|
103366
103646
|
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
103367
103647
|
"img",
|
|
103368
103648
|
{
|
|
@@ -103377,20 +103657,21 @@ end`);
|
|
|
103377
103657
|
)
|
|
103378
103658
|
] })
|
|
103379
103659
|
] }),
|
|
103380
|
-
/* @__PURE__ */ jsxRuntime.jsx("hr", { className: "text-neutral-800 h-[0.5px]" }),
|
|
103381
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "h-[calc(100dvh-
|
|
103660
|
+
/* @__PURE__ */ jsxRuntime.jsx("hr", { className: "text-neutral-800 h-[0.5px] border-neutral-800" }),
|
|
103661
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "h-[calc(100dvh-200px)] overflow-auto custom-scrollbar", children: [
|
|
103382
103662
|
recentItems.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mx-4", children: [
|
|
103383
103663
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "py-5", children: [
|
|
103384
103664
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
|
|
103385
103665
|
/* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-sm font-semibold mb-3", children: "Recent items" }),
|
|
103386
103666
|
recentItems.length > 3 && /* @__PURE__ */ jsxRuntime.jsx(
|
|
103387
|
-
"
|
|
103667
|
+
"button",
|
|
103388
103668
|
{
|
|
103389
|
-
|
|
103669
|
+
type: "button",
|
|
103670
|
+
className: "underline text-xs text-neutral-400 hover:text-white cursor-pointer",
|
|
103390
103671
|
onClick: () => {
|
|
103391
|
-
|
|
103392
|
-
|
|
103393
|
-
|
|
103672
|
+
setIsViewingAll(true);
|
|
103673
|
+
setTitle("Recent items");
|
|
103674
|
+
getSVG(null, "Recent items", "", 1);
|
|
103394
103675
|
},
|
|
103395
103676
|
children: "View all"
|
|
103396
103677
|
}
|
|
@@ -103399,36 +103680,45 @@ end`);
|
|
|
103399
103680
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-4 md:grid-cols-3 gap-3", children: recentItems.slice(0, 3).map((shape, i2) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
103400
103681
|
"button",
|
|
103401
103682
|
{
|
|
103683
|
+
type: "button",
|
|
103402
103684
|
onClick: () => {
|
|
103403
103685
|
addSVGToCanvas("svg", shape.image);
|
|
103404
103686
|
addRecentItem(shape);
|
|
103687
|
+
if (window.innerWidth < 768) {
|
|
103688
|
+
useSidePanelStore.getState().setActivePanel(null);
|
|
103689
|
+
}
|
|
103405
103690
|
},
|
|
103406
|
-
className: "w-full cursor-pointer flex items-center justify-center",
|
|
103691
|
+
className: "w-full cursor-pointer flex items-center justify-center p-2 rounded-lg bg-neutral-900/40 hover:bg-neutral-800 border border-neutral-800/60 hover:border-neutral-700 transition-colors",
|
|
103407
103692
|
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
103408
103693
|
"img",
|
|
103409
103694
|
{
|
|
103410
103695
|
src: shape.image,
|
|
103411
|
-
alt: shape.category,
|
|
103696
|
+
alt: shape.category || "recent element",
|
|
103412
103697
|
width: 150,
|
|
103413
103698
|
height: 150,
|
|
103414
|
-
className: "w-full h-
|
|
103699
|
+
className: "w-full h-20 object-contain"
|
|
103415
103700
|
}
|
|
103416
103701
|
)
|
|
103417
103702
|
},
|
|
103418
103703
|
shape._id || shape.image || i2
|
|
103419
103704
|
)) })
|
|
103420
103705
|
] }),
|
|
103421
|
-
/* @__PURE__ */ jsxRuntime.jsx("hr", { className: "text-neutral-800 h-[0.5px]" })
|
|
103706
|
+
/* @__PURE__ */ jsxRuntime.jsx("hr", { className: "text-neutral-800 h-[0.5px] border-neutral-800" })
|
|
103422
103707
|
] }),
|
|
103423
103708
|
elements.map((item, i2) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mx-4", children: [
|
|
103424
103709
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "py-5", children: [
|
|
103425
103710
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
|
|
103426
103711
|
/* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-sm font-semibold mb-3", children: item.category }),
|
|
103427
103712
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
103428
|
-
"
|
|
103713
|
+
"button",
|
|
103429
103714
|
{
|
|
103430
|
-
|
|
103431
|
-
|
|
103715
|
+
type: "button",
|
|
103716
|
+
className: "underline text-xs text-neutral-400 hover:text-white cursor-pointer",
|
|
103717
|
+
onClick: () => {
|
|
103718
|
+
setIsViewingAll(true);
|
|
103719
|
+
setTitle(item.category);
|
|
103720
|
+
getSVG(null, item.category, "", 1);
|
|
103721
|
+
},
|
|
103432
103722
|
children: "View all"
|
|
103433
103723
|
}
|
|
103434
103724
|
)
|
|
@@ -103436,6 +103726,7 @@ end`);
|
|
|
103436
103726
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-4 md:grid-cols-3 gap-3", children: item.data.map((shape, j2) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
103437
103727
|
"button",
|
|
103438
103728
|
{
|
|
103729
|
+
type: "button",
|
|
103439
103730
|
onClick: () => {
|
|
103440
103731
|
addSVGToCanvas("svg", shape.image);
|
|
103441
103732
|
addRecentItem({
|
|
@@ -103446,8 +103737,11 @@ end`);
|
|
|
103446
103737
|
largeImage: shape.image,
|
|
103447
103738
|
tags: []
|
|
103448
103739
|
});
|
|
103740
|
+
if (window.innerWidth < 768) {
|
|
103741
|
+
useSidePanelStore.getState().setActivePanel(null);
|
|
103742
|
+
}
|
|
103449
103743
|
},
|
|
103450
|
-
className: "w-full cursor-pointer flex items-center justify-center",
|
|
103744
|
+
className: "w-full cursor-pointer flex items-center justify-center p-2 rounded-lg bg-neutral-900/40 hover:bg-neutral-800 border border-neutral-800/60 hover:border-neutral-700 transition-colors",
|
|
103451
103745
|
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
103452
103746
|
"img",
|
|
103453
103747
|
{
|
|
@@ -103455,14 +103749,15 @@ end`);
|
|
|
103455
103749
|
alt: item.category,
|
|
103456
103750
|
width: 150,
|
|
103457
103751
|
height: 150,
|
|
103458
|
-
className: "w-full h-
|
|
103752
|
+
className: "w-full h-20 object-contain",
|
|
103753
|
+
loading: "lazy"
|
|
103459
103754
|
}
|
|
103460
103755
|
)
|
|
103461
103756
|
},
|
|
103462
103757
|
j2
|
|
103463
103758
|
)) })
|
|
103464
103759
|
] }),
|
|
103465
|
-
/* @__PURE__ */ jsxRuntime.jsx("hr", { className: "text-neutral-800 h-[0.5px]" })
|
|
103760
|
+
/* @__PURE__ */ jsxRuntime.jsx("hr", { className: "text-neutral-800 h-[0.5px] border-neutral-800" })
|
|
103466
103761
|
] }, i2))
|
|
103467
103762
|
] })
|
|
103468
103763
|
] });
|
|
@@ -158364,6 +158659,20 @@ ${elements2.join("\n")}
|
|
|
158364
158659
|
const duplicate = async () => {
|
|
158365
158660
|
const active = canvas2.getActiveObject();
|
|
158366
158661
|
if (!active) return;
|
|
158662
|
+
const currentSide = useHistoryStore.getState().currentPage;
|
|
158663
|
+
const ensureTextLoaded = async (obj) => {
|
|
158664
|
+
if (obj.type === "textbox" || obj.type === "text" || obj.type === "i-text") {
|
|
158665
|
+
if (obj.fontFamily) {
|
|
158666
|
+
try {
|
|
158667
|
+
await document.fonts.load(`16px "${obj.fontFamily}"`);
|
|
158668
|
+
} catch {
|
|
158669
|
+
}
|
|
158670
|
+
}
|
|
158671
|
+
obj.initDimensions?.();
|
|
158672
|
+
obj.set("dirty", true);
|
|
158673
|
+
}
|
|
158674
|
+
obj.__pageSide = currentSide;
|
|
158675
|
+
};
|
|
158367
158676
|
if (active.type?.toLowerCase() === "activeselection") {
|
|
158368
158677
|
const selection = active;
|
|
158369
158678
|
const originals = selection.getObjects().filter((o2) => o2.name !== "workarea");
|
|
@@ -158371,13 +158680,14 @@ ${elements2.join("\n")}
|
|
|
158371
158680
|
canvas2.discardActiveObject();
|
|
158372
158681
|
const clones = await Promise.all(
|
|
158373
158682
|
originals.map(async (obj) => {
|
|
158374
|
-
const cloned2 = await obj.clone();
|
|
158683
|
+
const cloned2 = await obj.clone(HISTORY_CUSTOM_PROPERTIES);
|
|
158375
158684
|
cloned2.set({
|
|
158376
158685
|
left: (obj.left ?? 0) + OFFSET$1,
|
|
158377
158686
|
top: (obj.top ?? 0) + OFFSET$1,
|
|
158378
158687
|
selectable: true,
|
|
158379
158688
|
evented: true
|
|
158380
158689
|
});
|
|
158690
|
+
await ensureTextLoaded(cloned2);
|
|
158381
158691
|
canvas2.add(cloned2);
|
|
158382
158692
|
return cloned2;
|
|
158383
158693
|
})
|
|
@@ -158390,18 +158700,21 @@ ${elements2.join("\n")}
|
|
|
158390
158700
|
return;
|
|
158391
158701
|
}
|
|
158392
158702
|
if (active.name === "workarea") return;
|
|
158393
|
-
const cloned = await active.clone();
|
|
158703
|
+
const cloned = await active.clone(HISTORY_CUSTOM_PROPERTIES);
|
|
158394
158704
|
cloned.set({
|
|
158395
158705
|
left: (active.left ?? 0) + OFFSET$1,
|
|
158396
158706
|
top: (active.top ?? 0) + OFFSET$1,
|
|
158397
158707
|
selectable: true,
|
|
158398
158708
|
evented: true
|
|
158399
158709
|
});
|
|
158710
|
+
await ensureTextLoaded(cloned);
|
|
158711
|
+
cloned.setCoords();
|
|
158400
158712
|
canvas2.discardActiveObject();
|
|
158401
158713
|
canvas2.add(cloned);
|
|
158402
158714
|
canvas2.setActiveObject(cloned);
|
|
158403
158715
|
bringOverlaysToFront(canvas2);
|
|
158404
158716
|
canvas2.requestRenderAll();
|
|
158717
|
+
saveState();
|
|
158405
158718
|
};
|
|
158406
158719
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
158407
158720
|
"button",
|
|
@@ -160321,6 +160634,175 @@ ${elements2.join("\n")}
|
|
|
160321
160634
|
createOverlays(canvas2, workarea, currentSide);
|
|
160322
160635
|
return workarea;
|
|
160323
160636
|
}
|
|
160637
|
+
const SNAP_THRESHOLD_PX = 7;
|
|
160638
|
+
const GUIDELINE_COLOR = "#28a128";
|
|
160639
|
+
const GUIDELINE_WIDTH = 1.2;
|
|
160640
|
+
let activeVLine = null;
|
|
160641
|
+
let activeHLine = null;
|
|
160642
|
+
function clearAlignmentGuidelines(canvas2) {
|
|
160643
|
+
if (!canvas2) return;
|
|
160644
|
+
let changed = false;
|
|
160645
|
+
if (activeVLine) {
|
|
160646
|
+
canvas2.remove(activeVLine);
|
|
160647
|
+
activeVLine = null;
|
|
160648
|
+
changed = true;
|
|
160649
|
+
}
|
|
160650
|
+
if (activeHLine) {
|
|
160651
|
+
canvas2.remove(activeHLine);
|
|
160652
|
+
activeHLine = null;
|
|
160653
|
+
changed = true;
|
|
160654
|
+
}
|
|
160655
|
+
canvas2.getObjects().forEach((o2) => {
|
|
160656
|
+
if (o2.name === "guideline-alignment-v" || o2.name === "guideline-alignment-h") {
|
|
160657
|
+
canvas2.remove(o2);
|
|
160658
|
+
changed = true;
|
|
160659
|
+
}
|
|
160660
|
+
});
|
|
160661
|
+
if (changed) {
|
|
160662
|
+
canvas2.requestRenderAll();
|
|
160663
|
+
}
|
|
160664
|
+
}
|
|
160665
|
+
function handleObjectAlignmentSnap(canvas2, target) {
|
|
160666
|
+
if (!canvas2 || !target) return;
|
|
160667
|
+
const targetName = target.name || "";
|
|
160668
|
+
if (targetName === "workarea" || targetName === "bg-image" || targetName === "canvas-grid" || targetName.startsWith("overlay-") || targetName.startsWith("guideline-")) {
|
|
160669
|
+
return;
|
|
160670
|
+
}
|
|
160671
|
+
const workarea = canvas2.getObjects().find((o2) => o2.name === "workarea");
|
|
160672
|
+
if (!workarea) return;
|
|
160673
|
+
const zoom = canvas2.getZoom() || 1;
|
|
160674
|
+
const threshold = SNAP_THRESHOLD_PX / zoom;
|
|
160675
|
+
const waL = workarea.left ?? 0;
|
|
160676
|
+
const waT = workarea.top ?? 0;
|
|
160677
|
+
const waW = workarea.getScaledWidth();
|
|
160678
|
+
const waH = workarea.getScaledHeight();
|
|
160679
|
+
const waCenterX = waL + waW / 2;
|
|
160680
|
+
const waCenterY = waT + waH / 2;
|
|
160681
|
+
const targetCenter = target.getCenterPoint();
|
|
160682
|
+
target.getBoundingRect();
|
|
160683
|
+
const curLeft = target.left ?? 0;
|
|
160684
|
+
target.top ?? 0;
|
|
160685
|
+
let snappedX = false;
|
|
160686
|
+
let snappedY = false;
|
|
160687
|
+
let snapLineX = waCenterX;
|
|
160688
|
+
let snapLineY = waCenterY;
|
|
160689
|
+
const distCenterX = Math.abs(targetCenter.x - waCenterX);
|
|
160690
|
+
if (distCenterX <= threshold) {
|
|
160691
|
+
const diffX = waCenterX - targetCenter.x;
|
|
160692
|
+
target.set("left", curLeft + diffX);
|
|
160693
|
+
target.setCoords();
|
|
160694
|
+
snappedX = true;
|
|
160695
|
+
snapLineX = waCenterX;
|
|
160696
|
+
} else {
|
|
160697
|
+
const otherObjects = canvas2.getObjects().filter(
|
|
160698
|
+
(o2) => o2 !== target && o2.selectable !== false && o2.name !== "workarea" && o2.name !== "bg-image" && !o2.name?.startsWith("overlay-") && !o2.name?.startsWith("guideline-") && o2.name !== "canvas-grid"
|
|
160699
|
+
);
|
|
160700
|
+
for (const other of otherObjects) {
|
|
160701
|
+
const otherCenter = other.getCenterPoint();
|
|
160702
|
+
if (Math.abs(targetCenter.x - otherCenter.x) <= threshold) {
|
|
160703
|
+
const diffX = otherCenter.x - targetCenter.x;
|
|
160704
|
+
target.set("left", curLeft + diffX);
|
|
160705
|
+
target.setCoords();
|
|
160706
|
+
snappedX = true;
|
|
160707
|
+
snapLineX = otherCenter.x;
|
|
160708
|
+
break;
|
|
160709
|
+
}
|
|
160710
|
+
}
|
|
160711
|
+
}
|
|
160712
|
+
const updatedCenter = target.getCenterPoint();
|
|
160713
|
+
const distCenterY = Math.abs(updatedCenter.y - waCenterY);
|
|
160714
|
+
if (distCenterY <= threshold) {
|
|
160715
|
+
const diffY = waCenterY - updatedCenter.y;
|
|
160716
|
+
target.set("top", (target.top ?? 0) + diffY);
|
|
160717
|
+
target.setCoords();
|
|
160718
|
+
snappedY = true;
|
|
160719
|
+
snapLineY = waCenterY;
|
|
160720
|
+
} else {
|
|
160721
|
+
const otherObjects = canvas2.getObjects().filter(
|
|
160722
|
+
(o2) => o2 !== target && o2.selectable !== false && o2.name !== "workarea" && o2.name !== "bg-image" && !o2.name?.startsWith("overlay-") && !o2.name?.startsWith("guideline-") && o2.name !== "canvas-grid"
|
|
160723
|
+
);
|
|
160724
|
+
for (const other of otherObjects) {
|
|
160725
|
+
const otherCenter = other.getCenterPoint();
|
|
160726
|
+
if (Math.abs(updatedCenter.y - otherCenter.y) <= threshold) {
|
|
160727
|
+
const diffY = otherCenter.y - updatedCenter.y;
|
|
160728
|
+
target.set("top", (target.top ?? 0) + diffY);
|
|
160729
|
+
target.setCoords();
|
|
160730
|
+
snappedY = true;
|
|
160731
|
+
snapLineY = otherCenter.y;
|
|
160732
|
+
break;
|
|
160733
|
+
}
|
|
160734
|
+
}
|
|
160735
|
+
}
|
|
160736
|
+
if (snappedX) {
|
|
160737
|
+
const y1 = waT;
|
|
160738
|
+
const y2 = waT + waH;
|
|
160739
|
+
if (!activeVLine) {
|
|
160740
|
+
activeVLine = new vo([snapLineX, y1, snapLineX, y2], {
|
|
160741
|
+
name: "guideline-alignment-v",
|
|
160742
|
+
stroke: GUIDELINE_COLOR,
|
|
160743
|
+
strokeWidth: GUIDELINE_WIDTH / zoom,
|
|
160744
|
+
strokeUniform: true,
|
|
160745
|
+
selectable: false,
|
|
160746
|
+
evented: false,
|
|
160747
|
+
excludeFromExport: true,
|
|
160748
|
+
hasControls: false,
|
|
160749
|
+
hasBorders: false,
|
|
160750
|
+
lockMovementX: true,
|
|
160751
|
+
lockMovementY: true
|
|
160752
|
+
});
|
|
160753
|
+
canvas2.add(activeVLine);
|
|
160754
|
+
} else {
|
|
160755
|
+
activeVLine.set({
|
|
160756
|
+
x1: snapLineX,
|
|
160757
|
+
y1,
|
|
160758
|
+
x2: snapLineX,
|
|
160759
|
+
y2,
|
|
160760
|
+
strokeWidth: GUIDELINE_WIDTH / zoom,
|
|
160761
|
+
visible: true
|
|
160762
|
+
});
|
|
160763
|
+
activeVLine.setCoords();
|
|
160764
|
+
}
|
|
160765
|
+
canvas2.bringObjectToFront(activeVLine);
|
|
160766
|
+
} else if (activeVLine) {
|
|
160767
|
+
canvas2.remove(activeVLine);
|
|
160768
|
+
activeVLine = null;
|
|
160769
|
+
}
|
|
160770
|
+
if (snappedY) {
|
|
160771
|
+
const x1 = waL;
|
|
160772
|
+
const x2 = waL + waW;
|
|
160773
|
+
if (!activeHLine) {
|
|
160774
|
+
activeHLine = new vo([x1, snapLineY, x2, snapLineY], {
|
|
160775
|
+
name: "guideline-alignment-h",
|
|
160776
|
+
stroke: GUIDELINE_COLOR,
|
|
160777
|
+
strokeWidth: GUIDELINE_WIDTH / zoom,
|
|
160778
|
+
strokeUniform: true,
|
|
160779
|
+
selectable: false,
|
|
160780
|
+
evented: false,
|
|
160781
|
+
excludeFromExport: true,
|
|
160782
|
+
hasControls: false,
|
|
160783
|
+
hasBorders: false,
|
|
160784
|
+
lockMovementX: true,
|
|
160785
|
+
lockMovementY: true
|
|
160786
|
+
});
|
|
160787
|
+
canvas2.add(activeHLine);
|
|
160788
|
+
} else {
|
|
160789
|
+
activeHLine.set({
|
|
160790
|
+
x1,
|
|
160791
|
+
y1: snapLineY,
|
|
160792
|
+
x2,
|
|
160793
|
+
y2: snapLineY,
|
|
160794
|
+
strokeWidth: GUIDELINE_WIDTH / zoom,
|
|
160795
|
+
visible: true
|
|
160796
|
+
});
|
|
160797
|
+
activeHLine.setCoords();
|
|
160798
|
+
}
|
|
160799
|
+
canvas2.bringObjectToFront(activeHLine);
|
|
160800
|
+
} else if (activeHLine) {
|
|
160801
|
+
canvas2.remove(activeHLine);
|
|
160802
|
+
activeHLine = null;
|
|
160803
|
+
}
|
|
160804
|
+
canvas2.requestRenderAll();
|
|
160805
|
+
}
|
|
160324
160806
|
function setupHistoryRecording(canvas2) {
|
|
160325
160807
|
let modifiedTimer = null;
|
|
160326
160808
|
const handleObjectAddedOrRemoved = (e2) => {
|
|
@@ -160483,13 +160965,21 @@ ${elements2.join("\n")}
|
|
|
160483
160965
|
configureSelectionControls();
|
|
160484
160966
|
bringOverlaysToFront(canvas2);
|
|
160485
160967
|
};
|
|
160486
|
-
const handleObjectMoving = () => {
|
|
160968
|
+
const handleObjectMoving = (e2) => {
|
|
160969
|
+
if (e2.target) {
|
|
160970
|
+
handleObjectAlignmentSnap(canvas2, e2.target);
|
|
160971
|
+
}
|
|
160487
160972
|
bringOverlaysToFront(canvas2);
|
|
160488
160973
|
};
|
|
160974
|
+
const handleMovementEnd = (e2) => {
|
|
160975
|
+
clearAlignmentGuidelines(canvas2);
|
|
160976
|
+
hideDegree(e2);
|
|
160977
|
+
};
|
|
160489
160978
|
canvas2.on("object:moving", handleObjectMoving);
|
|
160490
|
-
canvas2.on("object:modified",
|
|
160491
|
-
canvas2.on("mouse:up",
|
|
160979
|
+
canvas2.on("object:modified", handleMovementEnd);
|
|
160980
|
+
canvas2.on("mouse:up", handleMovementEnd);
|
|
160492
160981
|
canvas2.on("selection:cleared", () => {
|
|
160982
|
+
clearAlignmentGuidelines(canvas2);
|
|
160493
160983
|
const bgImg = canvas2.getObjects().find((o2) => o2.name === "bg-image");
|
|
160494
160984
|
if (bgImg) {
|
|
160495
160985
|
bgImg.set({
|
|
@@ -160507,13 +160997,14 @@ ${elements2.join("\n")}
|
|
|
160507
160997
|
clearSelection();
|
|
160508
160998
|
});
|
|
160509
160999
|
return () => {
|
|
161000
|
+
clearAlignmentGuidelines(canvas2);
|
|
160510
161001
|
canvas2.off("selection:created");
|
|
160511
161002
|
canvas2.off("selection:updated");
|
|
160512
161003
|
canvas2.off("object:scaling", configureSelectionControls);
|
|
160513
161004
|
canvas2.off("object:rotating");
|
|
160514
161005
|
canvas2.off("object:moving", handleObjectMoving);
|
|
160515
|
-
canvas2.off("object:modified",
|
|
160516
|
-
canvas2.off("mouse:up",
|
|
161006
|
+
canvas2.off("object:modified", handleMovementEnd);
|
|
161007
|
+
canvas2.off("mouse:up", handleMovementEnd);
|
|
160517
161008
|
canvas2.off("selection:cleared");
|
|
160518
161009
|
};
|
|
160519
161010
|
}
|
|
@@ -160570,6 +161061,9 @@ ${elements2.join("\n")}
|
|
|
160570
161061
|
"lockMovementY",
|
|
160571
161062
|
"rx",
|
|
160572
161063
|
"ry",
|
|
161064
|
+
"src",
|
|
161065
|
+
"crossOrigin",
|
|
161066
|
+
"__maskPath",
|
|
160573
161067
|
"__radiusOfCanvas",
|
|
160574
161068
|
"__shapeType",
|
|
160575
161069
|
"__pageSide",
|
|
@@ -160580,10 +161074,28 @@ ${elements2.join("\n")}
|
|
|
160580
161074
|
"__initialZoom",
|
|
160581
161075
|
"__borderRect",
|
|
160582
161076
|
"__rotationControlSize",
|
|
160583
|
-
"__showDegree"
|
|
161077
|
+
"__showDegree",
|
|
161078
|
+
"textAlign",
|
|
161079
|
+
"fontFamily",
|
|
161080
|
+
"fontSize",
|
|
161081
|
+
"fontWeight",
|
|
161082
|
+
"fontStyle",
|
|
161083
|
+
"underline",
|
|
161084
|
+
"linethrough",
|
|
161085
|
+
"overline",
|
|
161086
|
+
"fill",
|
|
161087
|
+
"stroke",
|
|
161088
|
+
"strokeWidth",
|
|
161089
|
+
"lineHeight",
|
|
161090
|
+
"charSpacing",
|
|
161091
|
+
"styles",
|
|
161092
|
+
"paintFirst",
|
|
161093
|
+
"strokeUniform",
|
|
161094
|
+
"objectCaching"
|
|
160584
161095
|
];
|
|
160585
161096
|
async function switchCanvasPage(canvas2, currentPage, targetPage, pageJSON, clearSelection, containerRef) {
|
|
160586
161097
|
if (targetPage === currentPage) return;
|
|
161098
|
+
clearAlignmentGuidelines(canvas2);
|
|
160587
161099
|
const historyStore = useHistoryStore.getState();
|
|
160588
161100
|
useLoaderStore.getState().startLoading();
|
|
160589
161101
|
try {
|
|
@@ -161016,8 +161528,9 @@ ${elements2.join("\n")}
|
|
|
161016
161528
|
] }) });
|
|
161017
161529
|
}
|
|
161018
161530
|
const OFFSET = 20;
|
|
161531
|
+
let globalClipboard = null;
|
|
161532
|
+
let clipboardSourcePage = null;
|
|
161019
161533
|
function useKeyboardEvents(canvasParam) {
|
|
161020
|
-
const clipboardRef = React.useRef(null);
|
|
161021
161534
|
const storeCanvas = useEditorStore((s2) => s2.canvas);
|
|
161022
161535
|
const historyCanvas = useHistoryStore((s2) => s2.canvas);
|
|
161023
161536
|
const canvas2 = canvasParam || storeCanvas || historyCanvas;
|
|
@@ -161029,30 +161542,56 @@ ${elements2.join("\n")}
|
|
|
161029
161542
|
if (!activeCanvas) return;
|
|
161030
161543
|
const activeObject = activeCanvas.getActiveObject();
|
|
161031
161544
|
if (!activeObject || activeObject.name === "workarea") return;
|
|
161032
|
-
const cloned = await activeObject.clone();
|
|
161033
|
-
|
|
161545
|
+
const cloned = await activeObject.clone(HISTORY_CUSTOM_PROPERTIES);
|
|
161546
|
+
globalClipboard = cloned;
|
|
161547
|
+
clipboardSourcePage = useHistoryStore.getState().currentPage;
|
|
161034
161548
|
};
|
|
161035
161549
|
const handlePaste = async () => {
|
|
161036
161550
|
const activeCanvas = getActiveCanvas();
|
|
161037
|
-
if (!activeCanvas || !
|
|
161038
|
-
const
|
|
161551
|
+
if (!activeCanvas || !globalClipboard) return;
|
|
161552
|
+
const currentSide = useHistoryStore.getState().currentPage;
|
|
161553
|
+
const isCrossPagePaste = clipboardSourcePage && clipboardSourcePage !== currentSide;
|
|
161554
|
+
const clonedObj = await globalClipboard.clone(HISTORY_CUSTOM_PROPERTIES);
|
|
161039
161555
|
activeCanvas.discardActiveObject();
|
|
161556
|
+
const offset = isCrossPagePaste ? 0 : OFFSET;
|
|
161040
161557
|
clonedObj.set({
|
|
161041
|
-
left: (clonedObj.left ?? 0) +
|
|
161042
|
-
top: (clonedObj.top ?? 0) +
|
|
161043
|
-
evented: true
|
|
161558
|
+
left: (clonedObj.left ?? 0) + offset,
|
|
161559
|
+
top: (clonedObj.top ?? 0) + offset,
|
|
161560
|
+
evented: true,
|
|
161561
|
+
selectable: true
|
|
161044
161562
|
});
|
|
161045
|
-
|
|
161046
|
-
|
|
161047
|
-
|
|
161563
|
+
clonedObj.__pageSide = currentSide;
|
|
161564
|
+
globalClipboard.set({
|
|
161565
|
+
left: (globalClipboard.left ?? 0) + OFFSET,
|
|
161566
|
+
top: (globalClipboard.top ?? 0) + OFFSET
|
|
161048
161567
|
});
|
|
161568
|
+
clipboardSourcePage = currentSide;
|
|
161569
|
+
const ensureTextLoaded = async (obj) => {
|
|
161570
|
+
if (obj.type === "textbox" || obj.type === "text" || obj.type === "i-text") {
|
|
161571
|
+
if (obj.fontFamily) {
|
|
161572
|
+
try {
|
|
161573
|
+
await document.fonts.load(`16px "${obj.fontFamily}"`);
|
|
161574
|
+
} catch {
|
|
161575
|
+
}
|
|
161576
|
+
}
|
|
161577
|
+
obj.initDimensions?.();
|
|
161578
|
+
obj.set("dirty", true);
|
|
161579
|
+
}
|
|
161580
|
+
};
|
|
161049
161581
|
if (clonedObj.type?.toLowerCase() === "activeselection") {
|
|
161050
|
-
|
|
161051
|
-
|
|
161052
|
-
|
|
161053
|
-
|
|
161054
|
-
|
|
161582
|
+
const selection = clonedObj;
|
|
161583
|
+
selection.canvas = activeCanvas;
|
|
161584
|
+
await Promise.all(
|
|
161585
|
+
selection.getObjects().map(async (obj) => {
|
|
161586
|
+
await ensureTextLoaded(obj);
|
|
161587
|
+
obj.__pageSide = currentSide;
|
|
161588
|
+
activeCanvas.add(obj);
|
|
161589
|
+
})
|
|
161590
|
+
);
|
|
161591
|
+
selection.setCoords();
|
|
161055
161592
|
} else {
|
|
161593
|
+
await ensureTextLoaded(clonedObj);
|
|
161594
|
+
clonedObj.setCoords();
|
|
161056
161595
|
activeCanvas.add(clonedObj);
|
|
161057
161596
|
}
|
|
161058
161597
|
activeCanvas.setActiveObject(clonedObj);
|
|
@@ -161070,6 +161609,7 @@ ${elements2.join("\n")}
|
|
|
161070
161609
|
const isY = e2.key?.toLowerCase() === "y" || e2.code === "KeyY";
|
|
161071
161610
|
const isC = e2.key?.toLowerCase() === "c" || e2.code === "KeyC";
|
|
161072
161611
|
const isV = e2.key?.toLowerCase() === "v" || e2.code === "KeyV";
|
|
161612
|
+
const isA = e2.key?.toLowerCase() === "a" || e2.code === "KeyA";
|
|
161073
161613
|
if (isZ && !e2.shiftKey) {
|
|
161074
161614
|
e2.preventDefault();
|
|
161075
161615
|
useHistoryStore.getState().undo();
|
|
@@ -161090,6 +161630,22 @@ ${elements2.join("\n")}
|
|
|
161090
161630
|
handlePaste();
|
|
161091
161631
|
return;
|
|
161092
161632
|
}
|
|
161633
|
+
if (isA) {
|
|
161634
|
+
const activeCanvas = getActiveCanvas();
|
|
161635
|
+
if (activeCanvas) {
|
|
161636
|
+
e2.preventDefault();
|
|
161637
|
+
const selectableObjects = activeCanvas.getObjects().filter(
|
|
161638
|
+
(obj) => obj.name !== "workarea" && !obj.name?.startsWith("overlay-") && !obj.name?.startsWith("guideline-") && obj.selectable !== false
|
|
161639
|
+
);
|
|
161640
|
+
if (selectableObjects.length > 0) {
|
|
161641
|
+
activeCanvas.discardActiveObject();
|
|
161642
|
+
const sel = new Qo(selectableObjects, { canvas: activeCanvas });
|
|
161643
|
+
activeCanvas.setActiveObject(sel);
|
|
161644
|
+
activeCanvas.requestRenderAll();
|
|
161645
|
+
}
|
|
161646
|
+
}
|
|
161647
|
+
return;
|
|
161648
|
+
}
|
|
161093
161649
|
const isQuote = e2.key === "'" || e2.code === "Quote";
|
|
161094
161650
|
if (isQuote) {
|
|
161095
161651
|
e2.preventDefault();
|
|
@@ -161119,13 +161675,14 @@ ${elements2.join("\n")}
|
|
|
161119
161675
|
const drawTool = useDrawStore((s2) => s2.drawTool);
|
|
161120
161676
|
const setDrawTool = useDrawStore((s2) => s2.setDrawTool);
|
|
161121
161677
|
const productData = useProductStore((s2) => s2.productData);
|
|
161122
|
-
const
|
|
161123
|
-
const [currentPage, setCurrentPage] = React.useState("front");
|
|
161124
|
-
const [isPanning, setIsPanning] = React.useState(false);
|
|
161678
|
+
const editorPages = useEditorStore((s2) => s2.pages);
|
|
161125
161679
|
const pageJSON = React.useRef({
|
|
161126
161680
|
front: null,
|
|
161127
161681
|
back: null
|
|
161128
161682
|
});
|
|
161683
|
+
const isTwoSided = productData?.optionsMap?.printing === "4/4" || !!pageJSON.current?.back || !!editorPages?.back;
|
|
161684
|
+
const [currentPage, setCurrentPage] = React.useState("front");
|
|
161685
|
+
const [isPanning, setIsPanning] = React.useState(false);
|
|
161129
161686
|
React.useEffect(() => {
|
|
161130
161687
|
if (!canvasRef.current || !containerRef.current) return;
|
|
161131
161688
|
if (fabricRef.current) return;
|
|
@@ -161303,9 +161860,48 @@ ${elements2.join("\n")}
|
|
|
161303
161860
|
setCurrentPage(page);
|
|
161304
161861
|
renderGrid(fabricRef.current);
|
|
161305
161862
|
};
|
|
161863
|
+
const copyFrontToBack = async () => {
|
|
161864
|
+
if (!fabricRef.current) return;
|
|
161865
|
+
useLoaderStore.getState().startLoading();
|
|
161866
|
+
try {
|
|
161867
|
+
if (currentPage === "front") {
|
|
161868
|
+
pageJSON.current.front = fabricRef.current.toObject(CUSTOM_PROPERTIES);
|
|
161869
|
+
}
|
|
161870
|
+
let frontData = pageJSON.current.front || useEditorStore.getState().pages?.front;
|
|
161871
|
+
if (!frontData) {
|
|
161872
|
+
console.warn("No front design found to copy");
|
|
161873
|
+
return;
|
|
161874
|
+
}
|
|
161875
|
+
const clonedFrontJson = JSON.parse(JSON.stringify(frontData));
|
|
161876
|
+
await loadTemplateFontsIntoHead(null, clonedFrontJson);
|
|
161877
|
+
pageJSON.current.back = clonedFrontJson;
|
|
161878
|
+
useEditorStore.getState().setPages({
|
|
161879
|
+
...useEditorStore.getState().pages,
|
|
161880
|
+
...pageJSON.current,
|
|
161881
|
+
back: clonedFrontJson
|
|
161882
|
+
});
|
|
161883
|
+
clearSelection();
|
|
161884
|
+
useHistoryStore.getState().setPage("back");
|
|
161885
|
+
setCurrentPage("back");
|
|
161886
|
+
await loadPageToCanvas(fabricRef.current, clonedFrontJson, "back");
|
|
161887
|
+
const workarea = fabricRef.current.getObjects().find((obj) => obj.name === "workarea");
|
|
161888
|
+
if (workarea) {
|
|
161889
|
+
workarea.__pageSide = "back";
|
|
161890
|
+
createOverlays(fabricRef.current, workarea, "back");
|
|
161891
|
+
bringOverlaysToFront(fabricRef.current);
|
|
161892
|
+
}
|
|
161893
|
+
fabricRef.current.requestRenderAll();
|
|
161894
|
+
renderGrid(fabricRef.current);
|
|
161895
|
+
useHistoryStore.getState().saveState();
|
|
161896
|
+
} catch (err) {
|
|
161897
|
+
console.error("Failed to copy front design to back:", err);
|
|
161898
|
+
} finally {
|
|
161899
|
+
useLoaderStore.getState().stopLoading();
|
|
161900
|
+
}
|
|
161901
|
+
};
|
|
161306
161902
|
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
161307
161903
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute bottom-6 z-50 flex items-center bg-white/80 backdrop-blur-xl rounded-2xl shadow-[0_8px_32px_rgba(0,0,0,0.12)] px-1.5 py-1.5 border border-white/40 ring-1 ring-black/5", children: [
|
|
161308
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex bg-gray-200/50 p-1 rounded-xl", children: [
|
|
161904
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex bg-gray-200/50 p-1 rounded-xl items-center gap-1", children: [
|
|
161309
161905
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
161310
161906
|
"button",
|
|
161311
161907
|
{
|
|
@@ -161321,6 +161917,37 @@ ${elements2.join("\n")}
|
|
|
161321
161917
|
className: `px-4 py-2 rounded-lg text-[11px] font-bold tracking-wider transition-all duration-200 cursor-pointer ${currentPage === "back" ? "bg-white text-[#02bc71] shadow-sm" : "text-gray-500 hover:text-gray-700"}`,
|
|
161322
161918
|
children: "BACK"
|
|
161323
161919
|
}
|
|
161920
|
+
),
|
|
161921
|
+
isTwoSided && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
161922
|
+
"button",
|
|
161923
|
+
{
|
|
161924
|
+
id: "copy-front-to-back-btn",
|
|
161925
|
+
type: "button",
|
|
161926
|
+
onClick: copyFrontToBack,
|
|
161927
|
+
title: "Copy front design to back design",
|
|
161928
|
+
className: "flex items-center gap-1.5 px-3 py-1.5 ml-1 rounded-lg text-[10px] font-bold tracking-wider bg-white/90 hover:bg-white text-gray-700 hover:text-[#02bc71] border border-gray-300/60 shadow-xs transition-all duration-150 cursor-pointer active:scale-95",
|
|
161929
|
+
children: [
|
|
161930
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
161931
|
+
"svg",
|
|
161932
|
+
{
|
|
161933
|
+
width: "13",
|
|
161934
|
+
height: "13",
|
|
161935
|
+
viewBox: "0 0 24 24",
|
|
161936
|
+
fill: "none",
|
|
161937
|
+
stroke: "currentColor",
|
|
161938
|
+
strokeWidth: "2.2",
|
|
161939
|
+
strokeLinecap: "round",
|
|
161940
|
+
strokeLinejoin: "round",
|
|
161941
|
+
className: "text-[#02bc71]",
|
|
161942
|
+
children: [
|
|
161943
|
+
/* @__PURE__ */ jsxRuntime.jsx("rect", { x: "9", y: "9", width: "13", height: "13", rx: "2", ry: "2" }),
|
|
161944
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" })
|
|
161945
|
+
]
|
|
161946
|
+
}
|
|
161947
|
+
),
|
|
161948
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "COPY FRONT TO BACK" })
|
|
161949
|
+
]
|
|
161950
|
+
}
|
|
161324
161951
|
)
|
|
161325
161952
|
] }),
|
|
161326
161953
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-[1px] h-6 bg-gray-300/50 mx-3" }),
|