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.
@@ -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?page=${data.page}&limit=${data.limit}&category=${data.category}&status=true`,
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?page=${data.page}&limit=${data.limit}&keyType=${data.keyType}&status=true${data.query ? `&query=${data.query}` : ""}`,
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-3 shrink-0", children: [
102215
- /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-lg font-semibold", children: "Vibrant" }),
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.getState();
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 handleChange = React.useCallback(
103153
- (e2) => {
103154
- const value = e2.target.value;
103155
- setSearch(value);
103156
- onSearch(value.trim());
103157
- },
103158
- [onSearch]
103159
- );
103160
- return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
103161
- /* @__PURE__ */ jsxRuntime.jsx("div", { onClick: onBack, className: "cursor-pointer mx-4 mb-2", children: "← Back" }),
103162
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-neutral-800 px-3 py-2 rounded flex items-center gap-2 mb-4 mx-4 relative", children: [
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-300",
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: handleChange,
103183
- onKeyDown: (e2) => e2.stopPropagation(),
103184
- className: "bg-transparent outline-none text-sm text-gray-300 w-full pr-8"
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: "16",
103198
- height: "16",
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-[calc(100vh-160px)] overflow-auto", ref: scrollRef, children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mx-4 py-5", children: elements2.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-gray-400 text-sm text-center", children: "No results found" }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
103214
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-4 md:grid-cols-3 gap-5", children: elements2.map((shape, i2) => /* @__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-24 object-contain"
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-sm text-gray-400", children: "Loading more..." })
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 [error2, setError] = React.useState(null);
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
- const request = type ? ShapeService.getAllExternal({
103287
- isVector: true,
103288
- page: pageNo,
103289
- limit: 30,
103290
- keyType: type,
103291
- query: categoryValue || null
103292
- }) : ShapeService.getAll({
103293
- category: categoryValue,
103294
- page: pageNo,
103295
- limit: 30
103296
- });
103297
- request.then((res) => {
103298
- const items = res.items;
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
- }).catch((err) => setError(err.message)).finally(() => stopLoading());
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
- const next = page + 1;
103313
- setPage(next);
103314
- getSVG(keyType, category, next);
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
- if (elementsSVG.length > 0) {
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
- setPage(1);
103328
- setHasMore(true);
103329
- getSVG(keyType, value ? value : category, 1);
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
- setKeyType("pixabay");
103352
- getSVG("pixabay", "", 1);
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
- setKeyType("flat");
103364
- getSVG("flat", "", 1);
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-180px)] overflow-auto custom-scrollbar", children: [
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
- "div",
103667
+ "button",
103388
103668
  {
103389
- className: "underline cursor-pointer",
103669
+ type: "button",
103670
+ className: "underline text-xs text-neutral-400 hover:text-white cursor-pointer",
103390
103671
  onClick: () => {
103391
- setElementsSVG(recentItems);
103392
- setCategory("Recent items");
103393
- setHasMore(false);
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-24 object-contain"
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
- "div",
103713
+ "button",
103429
103714
  {
103430
- className: "underline cursor-pointer",
103431
- onClick: () => getSVG(null, item?.category, 1),
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-24 object-contain"
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", hideDegree);
160491
- canvas2.on("mouse:up", hideDegree);
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", hideDegree);
160516
- canvas2.off("mouse:up", hideDegree);
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
- clipboardRef.current = cloned;
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 || !clipboardRef.current) return;
161038
- const clonedObj = await clipboardRef.current.clone();
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) + OFFSET,
161042
- top: (clonedObj.top ?? 0) + OFFSET,
161043
- evented: true
161558
+ left: (clonedObj.left ?? 0) + offset,
161559
+ top: (clonedObj.top ?? 0) + offset,
161560
+ evented: true,
161561
+ selectable: true
161044
161562
  });
161045
- clipboardRef.current.set({
161046
- left: (clipboardRef.current.left ?? 0) + OFFSET,
161047
- top: (clipboardRef.current.top ?? 0) + OFFSET
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
- clonedObj.canvas = activeCanvas;
161051
- clonedObj.forEachObject((obj) => {
161052
- activeCanvas.add(obj);
161053
- });
161054
- clonedObj.setCoords();
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 isTwoSided = productData?.optionsMap?.printing === "4/4";
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" }),