vibrant-tool 0.1.3 → 0.1.5

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.
@@ -8367,9 +8367,15 @@ t(bh, "type", "Vibrance"), t(bh, "defaults", { vibrance: 0 }), t(bh, "uniformLoc
8367
8367
  var Sh = Object.freeze({ __proto__: null, BaseFilter: Va, BlackWhite: nh, BlendColor: Ga, BlendImage: Ua, Blur: qa, Brightness: Ka, Brownie: $a$1, ColorMatrix: Qa, Composed: oh, Contrast: ah, Convolute: ch, Gamma: uh, Grayscale: gh, HueRotation: ph, Invert: mh, Kodachrome: eh, Noise: vh, Pixelate: yh, Polaroid: ih, RemoveColor: _h, Resize: xh, Saturation: Ch, Sepia: rh, Technicolor: sh, Vibrance: bh, Vintage: th });
8368
8368
  function createOverlays(canvas2, workarea) {
8369
8369
  canvas2.getObjects().filter((o2) => o2.name?.startsWith("overlay-") || o2.name?.startsWith("guideline-")).forEach((o2) => canvas2.remove(o2));
8370
- const zoom = canvas2.getZoom();
8371
- const cvW = canvas2.getWidth() / zoom;
8372
- const cvH = canvas2.getHeight() / zoom;
8370
+ const vt2 = canvas2.viewportTransform;
8371
+ const zoom = vt2 ? vt2[0] : canvas2.getZoom() || 1;
8372
+ const panX = vt2 ? vt2[4] : 0;
8373
+ const panY = vt2 ? vt2[5] : 0;
8374
+ const vX0 = -panX / zoom;
8375
+ const vY0 = -panY / zoom;
8376
+ const vX1 = (canvas2.getWidth() - panX) / zoom;
8377
+ const vY1 = (canvas2.getHeight() - panY) / zoom;
8378
+ const cvW = vX1 - vX0;
8373
8379
  const waL = workarea.left ?? 0;
8374
8380
  const waT = workarea.top ?? 0;
8375
8381
  const waW = workarea.getScaledWidth();
@@ -8383,79 +8389,81 @@ function createOverlays(canvas2, workarea) {
8383
8389
  });
8384
8390
  const topOverlay = makeOverlay({
8385
8391
  name: "overlay-top",
8386
- left: 0,
8387
- top: 0,
8392
+ left: vX0,
8393
+ top: vY0,
8388
8394
  width: cvW,
8389
- height: Math.max(0, waT)
8395
+ height: Math.max(0, waT - vY0)
8390
8396
  });
8391
8397
  const bottomOverlay = makeOverlay({
8392
8398
  name: "overlay-bottom",
8393
- left: 0,
8399
+ left: vX0,
8394
8400
  top: waT + waH,
8395
8401
  width: cvW,
8396
- height: Math.max(0, cvH - (waT + waH))
8402
+ height: Math.max(0, vY1 - (waT + waH))
8397
8403
  });
8398
8404
  const leftOverlay = makeOverlay({
8399
8405
  name: "overlay-left",
8400
- left: 0,
8406
+ left: vX0,
8401
8407
  top: waT,
8402
- width: Math.max(0, waL),
8408
+ width: Math.max(0, waL - vX0),
8403
8409
  height: waH
8404
8410
  });
8405
8411
  const rightOverlay = makeOverlay({
8406
8412
  name: "overlay-right",
8407
8413
  left: waL + waW,
8408
8414
  top: waT,
8409
- width: Math.max(0, cvW - (waL + waW)),
8415
+ width: Math.max(0, vX1 - (waL + waW)),
8410
8416
  height: waH
8411
8417
  });
8412
- const printDPI = workarea.__printDPI || 72;
8413
- const bleedArea = workarea.__bleedArea !== void 0 ? Number(workarea.__bleedArea) : 0.125;
8414
- const safetyArea = workarea.__safetyArea !== void 0 ? Number(workarea.__safetyArea) : 0.125;
8415
- const bleedInsetX = Math.max(4, (bleedArea > 0 ? bleedArea : 0.0125) * printDPI * 1);
8416
- const bleedInsetY = Math.max(4, (bleedArea > 0 ? bleedArea : 0.0125) * printDPI * 1);
8417
- const safeInsetX = Math.max(6, (safetyArea > 0 ? safetyArea : 0.125) * printDPI * 1);
8418
- const safeInsetY = Math.max(6, (safetyArea > 0 ? safetyArea : 0.125) * printDPI * 1);
8419
- const trimLine = new jr({
8420
- name: "guideline-trim",
8421
- left: waL + bleedInsetX,
8422
- top: waT + bleedInsetY,
8423
- width: Math.max(10, waW - bleedInsetX * 2),
8424
- height: Math.max(10, waH - bleedInsetY * 2),
8425
- fill: "transparent",
8426
- stroke: "#ef4444",
8427
- // Red dashed trim line
8428
- strokeWidth: 1.2,
8429
- strokeDashArray: [4, 4],
8430
- strokeUniform: true,
8431
- selectable: false,
8432
- evented: false,
8433
- excludeFromExport: true
8434
- });
8435
- const safeLine = new jr({
8436
- name: "guideline-safe",
8437
- left: waL + bleedInsetX + safeInsetX,
8438
- top: waT + bleedInsetY + safeInsetY,
8439
- width: Math.max(10, waW - (bleedInsetX + safeInsetX) * 2),
8440
- height: Math.max(10, waH - (bleedInsetY + safeInsetY) * 2),
8441
- fill: "transparent",
8442
- stroke: "#10b981",
8443
- // Green dashed safe line
8444
- strokeWidth: 1.2,
8445
- strokeDashArray: [4, 4],
8446
- strokeUniform: true,
8447
- selectable: false,
8448
- evented: false,
8449
- excludeFromExport: true
8450
- });
8451
- canvas2.add(trimLine, safeLine, topOverlay, bottomOverlay, leftOverlay, rightOverlay);
8418
+ const isTextTemplate = workarea.__isTextTemplate === true || workarea.__hideGuidelines === true || workarea.__bleedArea === 0 && workarea.__safetyArea === 0 || typeof window !== "undefined" && ["text", "texttemplate", "text-template"].includes(
8419
+ (new URLSearchParams(window.location.search).get("designType") || new URLSearchParams(window.location.search).get("designtype") || new URLSearchParams(window.location.search).get("type") || "")?.toLowerCase()
8420
+ );
8421
+ const objectsToAdd = [topOverlay, bottomOverlay, leftOverlay, rightOverlay];
8422
+ if (!isTextTemplate) {
8423
+ const printDPI = workarea.__printDPI || 72;
8424
+ const bleedArea = workarea.__bleedArea !== void 0 ? Number(workarea.__bleedArea) : 0.125;
8425
+ const safetyArea = workarea.__safetyArea !== void 0 ? Number(workarea.__safetyArea) : 0.125;
8426
+ const bleedInsetX = Math.max(4, (bleedArea > 0 ? bleedArea : 0.0125) * printDPI * 1);
8427
+ const bleedInsetY = Math.max(4, (bleedArea > 0 ? bleedArea : 0.0125) * printDPI * 1);
8428
+ const safeInsetX = Math.max(6, (safetyArea > 0 ? safetyArea : 0.125) * printDPI * 1);
8429
+ const safeInsetY = Math.max(6, (safetyArea > 0 ? safetyArea : 0.125) * printDPI * 1);
8430
+ const trimLine = new jr({
8431
+ name: "guideline-trim",
8432
+ left: waL + bleedInsetX,
8433
+ top: waT + bleedInsetY,
8434
+ width: Math.max(10, waW - bleedInsetX * 2),
8435
+ height: Math.max(10, waH - bleedInsetY * 2),
8436
+ fill: "transparent",
8437
+ stroke: "#ef4444",
8438
+ // Red dashed trim line
8439
+ strokeWidth: 1.2,
8440
+ strokeDashArray: [4, 4],
8441
+ strokeUniform: true,
8442
+ selectable: false,
8443
+ evented: false,
8444
+ excludeFromExport: true
8445
+ });
8446
+ const safeLine = new jr({
8447
+ name: "guideline-safe",
8448
+ left: waL + bleedInsetX + safeInsetX,
8449
+ top: waT + bleedInsetY + safeInsetY,
8450
+ width: Math.max(10, waW - (bleedInsetX + safeInsetX) * 2),
8451
+ height: Math.max(10, waH - (bleedInsetY + safeInsetY) * 2),
8452
+ fill: "transparent",
8453
+ stroke: "#10b981",
8454
+ // Green dashed safe line
8455
+ strokeWidth: 1.2,
8456
+ strokeDashArray: [4, 4],
8457
+ strokeUniform: true,
8458
+ selectable: false,
8459
+ evented: false,
8460
+ excludeFromExport: true
8461
+ });
8462
+ objectsToAdd.unshift(trimLine, safeLine);
8463
+ }
8464
+ canvas2.add(...objectsToAdd);
8452
8465
  canvas2.sendObjectToBack(workarea);
8453
- canvas2.bringObjectToFront(trimLine);
8454
- canvas2.bringObjectToFront(safeLine);
8455
- canvas2.bringObjectToFront(topOverlay);
8456
- canvas2.bringObjectToFront(bottomOverlay);
8457
- canvas2.bringObjectToFront(leftOverlay);
8458
- canvas2.bringObjectToFront(rightOverlay);
8466
+ objectsToAdd.forEach((obj) => canvas2.bringObjectToFront(obj));
8459
8467
  canvas2.requestRenderAll();
8460
8468
  }
8461
8469
  function bringOverlaysToFront(canvas2) {
@@ -98807,15 +98815,18 @@ function rgbToCmyk(r2, g2, b2) {
98807
98815
  };
98808
98816
  }
98809
98817
  function cmykToRgb(c2, m2, y2, k2) {
98810
- c2 = c2 / 100;
98811
- m2 = m2 / 100;
98812
- y2 = y2 / 100;
98813
- k2 = k2 / 100;
98814
- return {
98815
- r: Math.round(255 * (1 - c2) * (1 - k2)),
98816
- g: Math.round(255 * (1 - m2) * (1 - k2)),
98817
- b: Math.round(255 * (1 - y2) * (1 - k2))
98818
- };
98818
+ const cn2 = c2 / 100;
98819
+ const mn2 = m2 / 100;
98820
+ const yn2 = y2 / 100;
98821
+ const kn2 = k2 / 100;
98822
+ const base = 1 - kn2;
98823
+ const rTrans = (1 - cn2 * 0.9 - mn2 * 0.05 - yn2 * 0.02) * base;
98824
+ const gTrans = (1 - mn2 * 0.85 - cn2 * 0.12 - yn2 * 0.03) * base;
98825
+ const bTrans = (1 - yn2 * 0.92 - mn2 * 0.22 - cn2 * 0.02) * base;
98826
+ const r2 = Math.round(Math.max(0, Math.min(255, rTrans * 255)));
98827
+ const g2 = Math.round(Math.max(0, Math.min(255, gTrans * 255)));
98828
+ const b2 = Math.round(Math.max(0, Math.min(255, bTrans * 255)));
98829
+ return { r: r2, g: g2, b: b2 };
98819
98830
  }
98820
98831
  function rgbToHex(r2, g2, b2) {
98821
98832
  const toHex = (n2) => {
@@ -98840,16 +98851,6 @@ function cmykToHex(c2, m2, y2, k2) {
98840
98851
  const rgb2 = cmykToRgb(c2, m2, y2, k2);
98841
98852
  return rgbToHex(rgb2.r, rgb2.g, rgb2.b);
98842
98853
  }
98843
- function isOutOfCmykGamut(hex, threshold = 8) {
98844
- const rgb2 = hexToRgb(hex);
98845
- const cmyk2 = rgbToCmyk(rgb2.r, rgb2.g, rgb2.b);
98846
- const roundTrip = cmykToRgb(cmyk2.c, cmyk2.m, cmyk2.y, cmyk2.k);
98847
- const dr2 = rgb2.r - roundTrip.r;
98848
- const dg = rgb2.g - roundTrip.g;
98849
- const db = rgb2.b - roundTrip.b;
98850
- const distance = Math.sqrt(dr2 * dr2 + dg * dg + db * db);
98851
- return distance > threshold;
98852
- }
98853
98854
  function convertSvgColorsToCmyk(svg) {
98854
98855
  if (!svg) return svg;
98855
98856
  const hexToCmykString = (hex) => {
@@ -98952,6 +98953,7 @@ async function convertPdfToDeviceCMYK(rawBuffer) {
98952
98953
  return pdfDoc.save({ useObjectStreams: false });
98953
98954
  }
98954
98955
  const BASE_URL = "https://www.api.vibrantprintshop.com";
98956
+ const IMAGE_UPLOAD_URL = "https://upload.vibrantprint.com";
98955
98957
  async function apiFetch(endpoint, options = {}) {
98956
98958
  const token = localStorage.getItem("token");
98957
98959
  const response = await fetch(`${BASE_URL}${endpoint}`, {
@@ -98972,6 +98974,30 @@ async function apiFetch(endpoint, options = {}) {
98972
98974
  }
98973
98975
  return response.json();
98974
98976
  }
98977
+ async function apiUploadImage(endpoint, options = {}) {
98978
+ const token = localStorage.getItem("token");
98979
+ const isFormData = options.body instanceof FormData;
98980
+ const headers = {
98981
+ ...token && { Authorization: `Bearer ${token}` },
98982
+ ...options.headers
98983
+ };
98984
+ if (!isFormData && !("Content-Type" in (options.headers || {}))) {
98985
+ headers["Content-Type"] = "application/json";
98986
+ }
98987
+ const response = await fetch(`${IMAGE_UPLOAD_URL}${endpoint}`, {
98988
+ method: options.method ?? "POST",
98989
+ headers,
98990
+ body: isFormData ? options.body : options.body ? JSON.stringify(options.body) : void 0
98991
+ });
98992
+ if (!response.ok) {
98993
+ const message = await response.text();
98994
+ throw {
98995
+ status: response.status,
98996
+ message: message || "Something went wrong"
98997
+ };
98998
+ }
98999
+ return response.json();
99000
+ }
98975
99001
  const ProductService = {
98976
99002
  async getProductOptions(id) {
98977
99003
  const res = await apiFetch(
@@ -99471,7 +99497,7 @@ function DownloadPopover({ onClose }) {
99471
99497
  {
99472
99498
  value: fileType,
99473
99499
  onChange: (e3) => setFileType(e3.target.value),
99474
- className: "w-full bg-[#1e1e1e] border border-gray-600 rounded-lg px-3 py-2 text-sm mb-5 focus:outline-none focus:ring-2 focus:ring-blue-500",
99500
+ className: "w-full bg-[#1e1e1e] border border-gray-600 rounded-lg px-3 py-2 text-sm mb-5 focus:outline-none focus:ring-2 focus:ring-[#02bc71]",
99475
99501
  children: [
99476
99502
  /* @__PURE__ */ jsx("option", { value: "png", children: "PNG" }),
99477
99503
  /* @__PURE__ */ jsx("option", { value: "jpeg", children: "JPEG" }),
@@ -99574,7 +99600,7 @@ function PreviewModal({ onClose }) {
99574
99600
  const currentSide = useHistoryStore.getState().currentPage || "front";
99575
99601
  const editorPages = useEditorStore.getState().pages || {};
99576
99602
  const historyPages = useHistoryStore.getState().pages || {};
99577
- const currentUrl = getWorkareaDataUrl(canvas2);
99603
+ const currentUrl = getWorkareaDataUrl(canvas2, 2);
99578
99604
  const opposingSide = currentSide === "front" ? "back" : "front";
99579
99605
  const opposingJson = editorPages[opposingSide] || historyPages[opposingSide]?.history?.at(-1) || null;
99580
99606
  let opposingUrl = "";
@@ -99612,6 +99638,10 @@ function PreviewModal({ onClose }) {
99612
99638
  setRotY((dragRef.current.rotY + (e3.clientX - dragRef.current.x) * 0.5) % 360);
99613
99639
  setRotX(Math.max(-60, Math.min(60, dragRef.current.rotX - (e3.clientY - dragRef.current.y) * 0.5)));
99614
99640
  };
99641
+ const workarea = canvas2 ? canvas2.getObjects().find((o2) => o2.name === "workarea") : null;
99642
+ const waW = (workarea?.width ?? 1050) * (workarea?.scaleX ?? 1);
99643
+ const waH = (workarea?.height ?? 600) * (workarea?.scaleY ?? 1);
99644
+ const cardAspectRatio = waW && waH ? waW / waH : 1.75;
99615
99645
  const currentProof = activeSide === "front" ? frontUrl : backUrl;
99616
99646
  return /* @__PURE__ */ jsx("div", { className: "fixed inset-0 z-[99999] bg-black/60 backdrop-blur-sm flex items-center justify-center p-4 select-none", children: /* @__PURE__ */ jsxs("div", { className: "relative w-full max-w-4xl bg-white border border-gray-200 rounded-2xl shadow-2xl overflow-hidden flex flex-col h-[85vh]", children: [
99617
99647
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-5 py-3 border-b border-gray-200 bg-gray-50/90", children: [
@@ -99657,7 +99687,7 @@ function PreviewModal({ onClose }) {
99657
99687
  onMouseUp: () => setIsDragging(false),
99658
99688
  onMouseLeave: () => setIsDragging(false),
99659
99689
  className: `flex-1 relative overflow-hidden flex items-center justify-center bg-gradient-to-b from-slate-50 to-slate-100 ${viewMode === "3d" ? "cursor-grab active:cursor-grabbing" : ""}`,
99660
- children: viewMode === "3d" ? /* @__PURE__ */ jsxs("div", { className: "relative w-full h-full flex items-center justify-center [perspective:1200px]", children: [
99690
+ children: viewMode === "3d" ? /* @__PURE__ */ jsxs("div", { className: "relative w-full h-full flex items-center justify-center [perspective:1200px] p-6", children: [
99661
99691
  /* @__PURE__ */ jsxs(
99662
99692
  "div",
99663
99693
  {
@@ -99665,10 +99695,13 @@ function PreviewModal({ onClose }) {
99665
99695
  transform: `rotateX(${rotX}deg) rotateY(${rotY}deg)`,
99666
99696
  transformStyle: "preserve-3d",
99667
99697
  transition: isDragging ? "none" : "transform 0.15s cubic-bezier(0.1, 0.9, 0.2, 1)",
99668
- width: "420px",
99669
- height: "260px"
99698
+ aspectRatio: cardAspectRatio,
99699
+ width: cardAspectRatio >= 1 ? "min(85%, 680px)" : "auto",
99700
+ height: cardAspectRatio < 1 ? "min(80%, 520px)" : "auto",
99701
+ maxHeight: "80%",
99702
+ maxWidth: "85%"
99670
99703
  },
99671
- className: "relative will-change-transform shadow-2xl",
99704
+ className: "relative will-change-transform shadow-2xl rounded-xl",
99672
99705
  children: [
99673
99706
  /* @__PURE__ */ jsxs(
99674
99707
  "div",
@@ -99676,12 +99709,12 @@ function PreviewModal({ onClose }) {
99676
99709
  style: { backfaceVisibility: "hidden" },
99677
99710
  className: "absolute inset-0 rounded-xl overflow-hidden bg-white shadow-xl border border-gray-200",
99678
99711
  children: [
99679
- frontUrl ? /* @__PURE__ */ jsx("img", { src: frontUrl, alt: "Front", className: "w-full h-full object-contain pointer-events-none" }) : /* @__PURE__ */ jsx("div", { className: "w-full h-full flex items-center justify-center text-xs text-gray-400", children: "Front Design" }),
99712
+ frontUrl ? /* @__PURE__ */ jsx("img", { src: frontUrl, alt: "Front", className: "w-full h-full object-fill pointer-events-none block" }) : /* @__PURE__ */ jsx("div", { className: "w-full h-full flex items-center justify-center text-xs text-gray-400", children: "Front Design" }),
99680
99713
  /* @__PURE__ */ jsx(
99681
99714
  "div",
99682
99715
  {
99683
99716
  style: {
99684
- background: `linear-gradient(${120 + rotY * 0.8}deg, rgba(255,255,255,0.6) 0%, rgba(255,255,255,0) 50%, rgba(0,0,0,0.1) 100%)`
99717
+ background: `linear-gradient(${120 + rotY * 0.8}deg, rgba(255,255,255,0.4) 0%, rgba(255,255,255,0) 50%, rgba(0,0,0,0.08) 100%)`
99685
99718
  },
99686
99719
  className: "absolute inset-0 pointer-events-none mix-blend-overlay opacity-80"
99687
99720
  }
@@ -99695,7 +99728,7 @@ function PreviewModal({ onClose }) {
99695
99728
  style: { transform: "rotateY(180deg)", backfaceVisibility: "hidden" },
99696
99729
  className: "absolute inset-0 rounded-xl overflow-hidden bg-white shadow-xl border border-gray-200",
99697
99730
  children: [
99698
- isTwoSided ? backUrl ? /* @__PURE__ */ jsx("img", { src: backUrl, alt: "Back", className: "w-full h-full object-contain pointer-events-none" }) : /* @__PURE__ */ jsxs("div", { className: "w-full h-full flex flex-col items-center justify-center text-xs text-gray-400 p-4 text-center", children: [
99731
+ isTwoSided ? backUrl ? /* @__PURE__ */ jsx("img", { src: backUrl, alt: "Back", className: "w-full h-full object-fill pointer-events-none block" }) : /* @__PURE__ */ jsxs("div", { className: "w-full h-full flex flex-col items-center justify-center text-xs text-gray-400 p-4 text-center", children: [
99699
99732
  /* @__PURE__ */ jsx("span", { className: "font-semibold text-gray-600", children: "Back Side" }),
99700
99733
  /* @__PURE__ */ jsx("span", { className: "text-[10px] text-gray-400 mt-1", children: "Design added to Back tab will render here" })
99701
99734
  ] }) : /* @__PURE__ */ jsx("div", { className: "w-full h-full bg-slate-50 flex items-center justify-center text-xs text-gray-400", children: "Single-sided Product" }),
@@ -99703,37 +99736,29 @@ function PreviewModal({ onClose }) {
99703
99736
  "div",
99704
99737
  {
99705
99738
  style: {
99706
- background: `linear-gradient(${120 - rotY * 0.8}deg, rgba(255,255,255,0.6) 0%, rgba(255,255,255,0) 50%, rgba(0,0,0,0.1) 100%)`
99739
+ background: `linear-gradient(${120 - rotY * 0.8}deg, rgba(255,255,255,0.4) 0%, rgba(255,255,255,0) 50%, rgba(0,0,0,0.08) 100%)`
99707
99740
  },
99708
99741
  className: "absolute inset-0 pointer-events-none mix-blend-overlay opacity-80"
99709
99742
  }
99710
99743
  )
99711
99744
  ]
99712
99745
  }
99713
- ),
99714
- /* @__PURE__ */ jsx("div", { style: { transform: "translateZ(-1px)" }, className: "absolute inset-0 rounded-xl bg-gray-300 -z-10 shadow-md" })
99746
+ )
99715
99747
  ]
99716
99748
  }
99717
99749
  ),
99718
- /* @__PURE__ */ jsx(
99719
- "div",
99720
- {
99721
- style: { transform: `rotateX(90deg) translateZ(-150px)` },
99722
- className: "absolute w-72 h-24 bg-black/20 blur-xl rounded-full pointer-events-none"
99723
- }
99724
- ),
99725
99750
  /* @__PURE__ */ jsxs("div", { className: "absolute top-4 left-4 bg-white/80 backdrop-blur-md px-3 py-1 rounded-full border border-gray-200 text-[11px] text-gray-600 pointer-events-none flex items-center gap-1.5 shadow-sm", children: [
99726
99751
  /* @__PURE__ */ jsx("span", { className: "w-2 h-2 rounded-full bg-[#02bc71] animate-ping" }),
99727
99752
  /* @__PURE__ */ jsx("span", { children: "Drag to Rotate 360°" })
99728
99753
  ] })
99729
- ] }) : /* @__PURE__ */ jsxs("div", { className: "p-6 flex flex-col items-center justify-center", children: [
99730
- isTwoSided && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mb-4 bg-gray-200 p-0.5 rounded-lg border border-gray-300 text-xs", children: [
99754
+ ] }) : /* @__PURE__ */ jsxs("div", { className: "relative w-full h-full flex items-center justify-center p-6", children: [
99755
+ isTwoSided && /* @__PURE__ */ jsxs("div", { className: "absolute top-4 left-1/2 -translate-x-1/2 flex items-center gap-2 bg-gray-200/90 backdrop-blur-sm p-1 rounded-xl border border-gray-300 text-xs shadow-sm z-20", children: [
99731
99756
  /* @__PURE__ */ jsx(
99732
99757
  "button",
99733
99758
  {
99734
99759
  type: "button",
99735
99760
  onClick: () => setActiveSide("front"),
99736
- className: `px-3 py-1 rounded-md font-semibold cursor-pointer ${activeSide === "front" ? "bg-[#02bc71] text-white shadow-sm" : "text-gray-600 hover:text-gray-900"}`,
99761
+ className: `px-3 py-1 rounded-lg font-semibold cursor-pointer transition-all ${activeSide === "front" ? "bg-[#02bc71] text-white shadow-sm" : "text-gray-600 hover:text-gray-900"}`,
99737
99762
  children: "Front Proof"
99738
99763
  }
99739
99764
  ),
@@ -99742,12 +99767,32 @@ function PreviewModal({ onClose }) {
99742
99767
  {
99743
99768
  type: "button",
99744
99769
  onClick: () => setActiveSide("back"),
99745
- className: `px-3 py-1 rounded-md font-semibold cursor-pointer ${activeSide === "back" ? "bg-[#02bc71] text-white shadow-sm" : "text-gray-600 hover:text-gray-900"}`,
99770
+ className: `px-3 py-1 rounded-lg font-semibold cursor-pointer transition-all ${activeSide === "back" ? "bg-[#02bc71] text-white shadow-sm" : "text-gray-600 hover:text-gray-900"}`,
99746
99771
  children: "Back Proof"
99747
99772
  }
99748
99773
  )
99749
99774
  ] }),
99750
- currentProof ? /* @__PURE__ */ jsx("div", { className: "rounded-xl border border-gray-200 bg-white p-2 shadow-xl max-w-full", children: /* @__PURE__ */ jsx("img", { src: currentProof, alt: "2D Proof", className: "max-h-[50vh] w-auto object-contain rounded-lg" }) }) : /* @__PURE__ */ jsxs("div", { className: "p-8 text-center bg-gray-50 rounded-xl border border-gray-200 text-xs text-gray-400", children: [
99775
+ currentProof ? /* @__PURE__ */ jsx(
99776
+ "div",
99777
+ {
99778
+ style: {
99779
+ aspectRatio: cardAspectRatio,
99780
+ width: cardAspectRatio >= 1 ? "min(85%, 680px)" : "auto",
99781
+ height: cardAspectRatio < 1 ? "min(80%, 520px)" : "auto",
99782
+ maxHeight: "80%",
99783
+ maxWidth: "85%"
99784
+ },
99785
+ className: "relative rounded-xl overflow-hidden bg-white shadow-2xl border border-gray-200 flex items-center justify-center",
99786
+ children: /* @__PURE__ */ jsx(
99787
+ "img",
99788
+ {
99789
+ src: currentProof,
99790
+ alt: `${activeSide} 2D Proof`,
99791
+ className: "w-full h-full object-fill pointer-events-none block"
99792
+ }
99793
+ )
99794
+ }
99795
+ ) : /* @__PURE__ */ jsxs("div", { className: "p-8 text-center bg-gray-50 rounded-xl border border-gray-200 text-xs text-gray-400", children: [
99751
99796
  "No preview available for ",
99752
99797
  activeSide,
99753
99798
  " side."
@@ -99855,8 +99900,13 @@ function setQueryParam(key, value) {
99855
99900
  const TemplateDesignService = {
99856
99901
  async getTemplates(data) {
99857
99902
  try {
99903
+ const params = new URLSearchParams();
99904
+ if (data.product) params.append("product", data.product);
99905
+ params.append("status", "true");
99906
+ params.append("page", String(data.page));
99907
+ params.append("limit", String(data.limit));
99858
99908
  const res = await apiFetch(
99859
- `/api/template-designs?product=${data.product}&status=true&page=${data.page}&limit=${data.limit}`,
99909
+ `/api/template-designs?${params.toString()}`,
99860
99910
  { method: "GET" }
99861
99911
  );
99862
99912
  return res || null;
@@ -99893,6 +99943,100 @@ const TemplateDesignService = {
99893
99943
  }
99894
99944
  }
99895
99945
  };
99946
+ const ShapeService = {
99947
+ async getAll(data) {
99948
+ const res = await apiFetch(
99949
+ `/api/cliparts?page=${data.page}&limit=${data.limit}&category=${data.category}&status=true`,
99950
+ {
99951
+ method: "GET"
99952
+ }
99953
+ );
99954
+ return {
99955
+ items: res.data || []
99956
+ };
99957
+ },
99958
+ async getAllExternal(data) {
99959
+ const res = await apiFetch(
99960
+ `/api/external/images?page=${data.page}&limit=${data.limit}&keyType=${data.keyType}&status=true${data.query ? `&query=${data.query}` : ""}`,
99961
+ {
99962
+ method: "GET"
99963
+ }
99964
+ );
99965
+ return {
99966
+ items: res.data || []
99967
+ };
99968
+ },
99969
+ async getTextTemplates(data) {
99970
+ const res = await apiFetch(
99971
+ `/api/text-templates?page=${data.page}&limit=${data.limit}&status=true`,
99972
+ {
99973
+ method: "GET"
99974
+ }
99975
+ );
99976
+ return {
99977
+ items: res.data || []
99978
+ };
99979
+ },
99980
+ async getTextTemplateById(id) {
99981
+ const res = await apiFetch(
99982
+ `/api/text-templates/${id}`,
99983
+ {
99984
+ method: "GET"
99985
+ }
99986
+ );
99987
+ return res.data || null;
99988
+ },
99989
+ async updateTextTemplate(id, body) {
99990
+ const res = await apiFetch(`/api/text-templates/${id}`, {
99991
+ method: "PUT",
99992
+ body
99993
+ });
99994
+ return res;
99995
+ },
99996
+ async convertImages(url) {
99997
+ const res = await apiFetch(`/api/convert-images`, {
99998
+ method: "POST",
99999
+ body: { file: url }
100000
+ });
100001
+ return res;
100002
+ }
100003
+ };
100004
+ function dataUrlToFile(dataUrl, filename = `design-${Date.now()}.png`) {
100005
+ const arr = dataUrl.split(",");
100006
+ const mime = arr[0].match(/:(.*?);/)?.[1] || "image/png";
100007
+ const bstr = atob(arr[1]);
100008
+ let n2 = bstr.length;
100009
+ const u8arr = new Uint8Array(n2);
100010
+ while (n2--) {
100011
+ u8arr[n2] = bstr.charCodeAt(n2);
100012
+ }
100013
+ return new File([u8arr], filename, { type: mime });
100014
+ }
100015
+ const UploadService = {
100016
+ async uploadFileApi(file, filename = `design-${Date.now()}.png`) {
100017
+ const formData = new FormData();
100018
+ formData.append("file", file, file.name || filename);
100019
+ formData.append("name", "vibrantprint");
100020
+ const res = await apiUploadImage(`/upload`, {
100021
+ method: "POST",
100022
+ body: formData
100023
+ });
100024
+ const data = res?.data || res;
100025
+ const url = data?.name || data?.Location || data?.url || data?.data?.name || data?.data?.Location || data?.data?.url || (typeof data === "string" ? data : "");
100026
+ if (!url) {
100027
+ throw new Error(res?.message || "Failed to upload file");
100028
+ }
100029
+ return url;
100030
+ },
100031
+ async uploadDataUrl(dataUrl, filename) {
100032
+ if (!dataUrl) return "";
100033
+ if (dataUrl.startsWith("http://") || dataUrl.startsWith("https://")) {
100034
+ return dataUrl;
100035
+ }
100036
+ const file = dataUrlToFile(dataUrl, filename || `design-${Date.now()}.png`);
100037
+ return await this.uploadFileApi(file, filename);
100038
+ }
100039
+ };
99896
100040
  function Topbar() {
99897
100041
  const [open2, setOpen] = useState(false);
99898
100042
  const [previewOpen, setPreviewOpen] = useState(false);
@@ -99900,8 +100044,40 @@ function Topbar() {
99900
100044
  const [saveSuccess, setSaveSuccess] = useState(false);
99901
100045
  const [saveError, setSaveError] = useState(false);
99902
100046
  const resetTour = useTourStore((s2) => s2.reset);
99903
- const templateId = getQueryParam("templateId");
99904
- const designType = getQueryParam("design");
100047
+ const templateId = getQueryParam("templateId") || getQueryParam("id");
100048
+ const designType = (getQueryParam("designType") || getQueryParam("designtype") || getQueryParam("type"))?.toLowerCase();
100049
+ const handleSaveTextTemplate = async () => {
100050
+ const canvas2 = useEditorStore.getState().canvas;
100051
+ if (!templateId || !canvas2) return;
100052
+ try {
100053
+ setIsSaving(true);
100054
+ useLoaderStore.getState().startLoading();
100055
+ const currentJson = canvas2.toObject(["name"]);
100056
+ const currentImage = getWorkareaDataUrl(canvas2);
100057
+ const uploadedImageUrl = await UploadService.uploadDataUrl(
100058
+ currentImage,
100059
+ `text-template-${templateId}-${Date.now()}.png`
100060
+ );
100061
+ const res = await ShapeService.updateTextTemplate(templateId, {
100062
+ jsonData: currentJson,
100063
+ image: uploadedImageUrl
100064
+ });
100065
+ if (res && (res.success || res.status || res.data || res._id)) {
100066
+ setSaveSuccess(true);
100067
+ setTimeout(() => setSaveSuccess(false), 3e3);
100068
+ window.location.href = "/design-configuration/text-templates";
100069
+ } else {
100070
+ throw new Error("Save failed");
100071
+ }
100072
+ } catch (err) {
100073
+ console.error("Save text template error:", err);
100074
+ setSaveError(true);
100075
+ setTimeout(() => setSaveError(false), 3e3);
100076
+ } finally {
100077
+ setIsSaving(false);
100078
+ useLoaderStore.getState().stopLoading();
100079
+ }
100080
+ };
99905
100081
  const handleSaveTemplate = async () => {
99906
100082
  const canvas2 = useEditorStore.getState().canvas;
99907
100083
  if (!templateId || !canvas2) return;
@@ -99919,26 +100095,33 @@ function Topbar() {
99919
100095
  const designData = isCurrent ? currentJson : editorPages[side] || historyPages[side]?.history?.at(-1) || null;
99920
100096
  if (!designData) return null;
99921
100097
  const sideImage = isCurrent ? currentImage : await renderPageDataUrl(designData, canvas2);
100098
+ const uploadedImageUrl = await UploadService.uploadDataUrl(
100099
+ sideImage,
100100
+ `template-${templateId}-${side}-${Date.now()}.png`
100101
+ );
99922
100102
  return {
99923
100103
  side,
99924
100104
  designData,
99925
- image: sideImage,
100105
+ image: uploadedImageUrl,
99926
100106
  order: i + 1
99927
100107
  };
99928
100108
  })
99929
100109
  );
99930
100110
  const validPages = pages.filter(Boolean);
99931
- const mainImage = validPages[0]?.image || currentImage;
100111
+ const primaryImage = validPages[0]?.image || "";
99932
100112
  const res = await TemplateDesignService.updateTemplateDesign(templateId, {
99933
- pages: validPages
100113
+ pages: validPages,
100114
+ image: primaryImage
99934
100115
  });
99935
100116
  if (res && res.success) {
99936
100117
  setSaveSuccess(true);
99937
100118
  setTimeout(() => setSaveSuccess(false), 3e3);
100119
+ window.location.href = "/design-configuration/template-designs";
99938
100120
  } else {
99939
100121
  throw new Error("Save failed");
99940
100122
  }
99941
- } catch {
100123
+ } catch (err) {
100124
+ console.error("Save template error:", err);
99942
100125
  setSaveError(true);
99943
100126
  setTimeout(() => setSaveError(false), 3e3);
99944
100127
  } finally {
@@ -99964,17 +100147,20 @@ function Topbar() {
99964
100147
  ]
99965
100148
  }
99966
100149
  ),
99967
- templateId && designType == "template" && /* @__PURE__ */ jsx(
100150
+ templateId && (designType === "template" || designType === "text") && /* @__PURE__ */ jsx(
99968
100151
  "button",
99969
100152
  {
99970
100153
  id: "save-template-button",
99971
- onClick: handleSaveTemplate,
100154
+ onClick: designType === "text" ? handleSaveTextTemplate : handleSaveTemplate,
99972
100155
  disabled: isSaving,
99973
100156
  className: `flex items-center gap-1.5 px-3.5 py-1.5 rounded-lg text-xs font-semibold cursor-pointer transition-all shadow-sm ${saveSuccess ? "bg-[#02bc71] text-white" : saveError ? "bg-rose-600 text-white" : "bg-[#02bc71] hover:bg-[#03945a] text-white"}`,
99974
100157
  children: isSaving ? /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1.5", children: [
99975
100158
  /* @__PURE__ */ jsx("span", { className: "h-3 w-3 border-2 border-white border-t-transparent rounded-full animate-spin" }),
99976
100159
  "Saving..."
99977
- ] }) : saveSuccess ? /* @__PURE__ */ jsx("span", { children: "✓ Saved!" }) : saveError ? /* @__PURE__ */ jsx("span", { children: "Save Failed" }) : /* @__PURE__ */ jsx("span", { children: "Save Template" })
100160
+ ] }) : saveSuccess ? /* @__PURE__ */ jsx("span", { children: "✓ Saved!" }) : saveError ? /* @__PURE__ */ jsx("span", { children: "Save Failed" }) : /* @__PURE__ */ jsxs("span", { children: [
100161
+ "Save ",
100162
+ designType === "text" ? "Text Template" : "Template"
100163
+ ] })
99978
100164
  }
99979
100165
  ),
99980
100166
  /* @__PURE__ */ jsxs("div", { className: "relative flex items-center", children: [
@@ -100220,7 +100406,7 @@ function Panel({ panelName }) {
100220
100406
  onClick: () => setActivePanel(null)
100221
100407
  }
100222
100408
  ),
100223
- /* @__PURE__ */ jsxs("div", { className: `fixed md:relative bottom-0 left-0 right-0 md:bottom-auto md:left-auto z-50 transition-all duration-300 ease-in-out`, children: [
100409
+ /* @__PURE__ */ jsxs("div", { className: `fixed md:relative bottom-0 left-0 right-0 md:bottom-auto md:left-auto z-50 transition-all duration-300 ease-in-out md:h-full`, children: [
100224
100410
  /* @__PURE__ */ jsxs(
100225
100411
  "div",
100226
100412
  {
@@ -100240,7 +100426,7 @@ function Panel({ panelName }) {
100240
100426
  }
100241
100427
  )
100242
100428
  ] }),
100243
- /* @__PURE__ */ jsx("div", { className: "flex-1 overflow-y-auto pt-4", children: panelName })
100429
+ /* @__PURE__ */ jsx("div", { className: "flex-1 min-h-0 overflow-y-auto pt-4 flex flex-col custom-scrollbar", children: panelName })
100244
100430
  ]
100245
100431
  }
100246
100432
  ),
@@ -100276,20 +100462,39 @@ function Panel({ panelName }) {
100276
100462
  function deriveType(objects) {
100277
100463
  if (objects.length === 0) return "none";
100278
100464
  if (objects.length > 1) return "multiple";
100279
- const t2 = objects[0].type;
100280
- if (t2 === "group") return "group";
100465
+ const obj = objects[0];
100466
+ const t2 = obj.type?.toLowerCase();
100467
+ if (t2 === "activeselection") return "multiple";
100468
+ if (t2 === "group") {
100469
+ if (obj.name === "qr") return "qr";
100470
+ if (obj.name === "barcode") return "barcode";
100471
+ return "group";
100472
+ }
100281
100473
  if (t2 === "textbox" || t2 === "i-text" || t2 === "text") return "textbox";
100282
100474
  if (t2 === "image") return "image";
100283
- return t2;
100475
+ return t2 || "none";
100284
100476
  }
100285
100477
  const useSelectionStore = create((set) => ({
100286
100478
  type: "none",
100287
100479
  objects: [],
100480
+ activeColor: null,
100481
+ activeColorIndex: 0,
100288
100482
  setSelection: (objects) => set({
100289
100483
  objects,
100290
- type: deriveType(objects)
100484
+ type: deriveType(objects),
100485
+ activeColor: null,
100486
+ activeColorIndex: 0
100487
+ }),
100488
+ setActiveColor: (color, index = 0) => set({
100489
+ activeColor: color,
100490
+ activeColorIndex: index
100291
100491
  }),
100292
- clearSelection: () => set({ type: "none", objects: [] })
100492
+ clearSelection: () => set({
100493
+ type: "none",
100494
+ objects: [],
100495
+ activeColor: null,
100496
+ activeColorIndex: 0
100497
+ })
100293
100498
  }));
100294
100499
  function TemplatesPanel() {
100295
100500
  const { productData } = useProductStore();
@@ -100386,7 +100591,7 @@ function TemplatesPanel() {
100386
100591
  }
100387
100592
  )
100388
100593
  ] }),
100389
- /* @__PURE__ */ jsx("div", { ref: scrollRef, className: "h-[calc(100vh-125px)] overflow-auto", children: /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-3 pb-12 mx-4", children: [
100594
+ /* @__PURE__ */ jsx("div", { ref: scrollRef, className: "h-[calc(100vh-125px)] overflow-auto custom-scrollbar", children: /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-3 pb-12 mx-4", children: [
100390
100595
  templates.map((tpl) => /* @__PURE__ */ jsx(
100391
100596
  "button",
100392
100597
  {
@@ -100851,27 +101056,23 @@ function updateQRObjectColors(qrGroup, darkColor, lightColor) {
100851
101056
  });
100852
101057
  qrGroup.canvas?.requestRenderAll();
100853
101058
  }
100854
- function extractSVGColors(svg) {
100855
- if (svg.type !== "group") return [];
100856
- const colors = /* @__PURE__ */ new Set();
100857
- svg.getObjects().forEach((obj) => {
100858
- if (typeof obj.fill === "string" && obj.fill !== "none" && obj.fill !== "" && obj.fill !== "transparent") {
100859
- try {
100860
- const hex = new Ie(obj.fill).toHex();
100861
- colors.add(`#${hex}`);
100862
- } catch (e3) {
100863
- }
100864
- }
100865
- if (typeof obj.stroke === "string" && obj.stroke !== "none" && obj.stroke !== "" && obj.stroke !== "transparent") {
100866
- try {
100867
- const hex = new Ie(obj.stroke).toHex();
100868
- colors.add(`#${hex}`);
100869
- } catch (e3) {
100870
- }
101059
+ const MAX_RECENT_ELEMENTS = 15;
101060
+ const useRecentElementsStore = create()(
101061
+ persist(
101062
+ (set) => ({
101063
+ recentItems: [],
101064
+ addRecentItem: (item) => set((state) => ({
101065
+ recentItems: [
101066
+ item,
101067
+ ...state.recentItems.filter((recent) => recent.image !== item.image)
101068
+ ].slice(0, MAX_RECENT_ELEMENTS)
101069
+ }))
101070
+ }),
101071
+ {
101072
+ name: "vibrant-recent-elements"
100871
101073
  }
100872
- });
100873
- return [...colors];
100874
- }
101074
+ )
101075
+ );
100875
101076
  function AllElements({
100876
101077
  elements: elements2,
100877
101078
  onBack,
@@ -100880,6 +101081,7 @@ function AllElements({
100880
101081
  }) {
100881
101082
  const [search, setSearch] = useState("");
100882
101083
  const { isLoading } = useLoaderStore.getState();
101084
+ const { addRecentItem } = useRecentElementsStore();
100883
101085
  const clearSearch = () => {
100884
101086
  setSearch("");
100885
101087
  onSearch("");
@@ -100956,6 +101158,7 @@ function AllElements({
100956
101158
  } else {
100957
101159
  addSVGToCanvas("svg", shape.image);
100958
101160
  }
101161
+ addRecentItem(shape);
100959
101162
  if (window.innerWidth < 768) {
100960
101163
  useSidePanelStore.getState().setActivePanel(null);
100961
101164
  }
@@ -100978,50 +101181,6 @@ function AllElements({
100978
101181
  ] }) }) })
100979
101182
  ] });
100980
101183
  }
100981
- const ShapeService = {
100982
- async getAll(data) {
100983
- const res = await apiFetch(
100984
- `/api/cliparts?page=${data.page}&limit=${data.limit}&category=${data.category}&status=true`,
100985
- {
100986
- method: "GET"
100987
- }
100988
- );
100989
- return {
100990
- items: res.data || []
100991
- };
100992
- },
100993
- async getAllExternal(data) {
100994
- const res = await apiFetch(
100995
- `/api/external/images?page=${data.page}&limit=${data.limit}&keyType=${data.keyType}&status=true${data.query ? `&query=${data.query}` : ""}`,
100996
- {
100997
- method: "GET"
100998
- }
100999
- );
101000
- return {
101001
- items: res.data || []
101002
- };
101003
- },
101004
- async getTextTemplates(data) {
101005
- const res = await apiFetch(
101006
- `/api/text-templates?page=${data.page}&limit=${data.limit}&status=true`,
101007
- {
101008
- method: "GET"
101009
- }
101010
- );
101011
- return {
101012
- items: res.data || []
101013
- };
101014
- },
101015
- async getTextTemplateById(id) {
101016
- const res = await apiFetch(
101017
- `/api/text-templates/${id}`,
101018
- {
101019
- method: "GET"
101020
- }
101021
- );
101022
- return res.data || null;
101023
- }
101024
- };
101025
101184
  const PixabayIcon = () => /* @__PURE__ */ jsx(
101026
101185
  "svg",
101027
101186
  {
@@ -101046,6 +101205,7 @@ const PixabayIcon = () => /* @__PURE__ */ jsx(
101046
101205
  const FlatIcon = "data:image/webp;base64,UklGRhAEAABXRUJQVlA4TAMEAAAvX8AXEFfHoG0bQS5/0Lm7fxAQ2kiSJGX1Dn+Iy2Lv7Uo7bNtGkpi7/vv9LSaSJEXK/38MjSeQOCTsvS0kSKoJb6QA1AFJDYEC0UiSGiAkSEDSRtKmzkf5RO0kGlIhCSGQgKQ7JdAOkIRAYlOnHSCknWCnph0CJe7UEE0CdTTdAdKd7kBGUthgjsE/hLQhDv1HAxJDG/9TQwASAp7T5wcItCEElDYEbPqbMhKAb28hwBP8OpTAT6BIQM5ylM8jkUABYI4cB08Mhr2eDM4xeI5hzzFnz+kVcYhXDBFxiiIiqohoIqKLOCanjJ7er6WUGBxcAOSQj0xAASAFcPCT5cHQZn/0M5KCRAJqt22bkqRTttm2bdu2bdu6+TLqlW3bbHetiOhg27aN+zPinRe452N9iOj/BHi1WizkMDNXKL3h66xkMTdbSfNeHpPz7ypvYvZbCe9h+Ns1ecte995XML3ifda2rK9ifLVoXbFgXSFnXY4tWrt4XDPHJMXjYhfM1OV3X3n5FQ2//OpRNWMfuOrupVMC+fDs3tLU/TYB0cudRXqd+1EQ80+W5nZYFgFrDpPaU+YFMOJRafLpM4H4MUl+dETrzTqmSYM+c8APOynHzGq9FXs26ZrxwMRLRd1reettOrA5u/wCuPf7agcvaL0JFzbn6Qww5yTRrxnXeu6LQc04YhUQLRR92+8IcNwtKfa57do6LxvtgE/2S/FwHAK/7qrJs1PGpR/rgNF3in7QxwSZeTzF7l/TwJ+20Tq91h4GK47W5Ppx9U05X/RT5xBotKiHNvCfWM8kuN+7aQO+dKEw/RxNTn7w/uT77srUbDhG9OvGE+5/Q7W0R84GMk+LvvNvBDz69gb0WtcGfLZHikfikPh0r/oumAKMvVn0w1cTdPtzHerZ+l+AvwdqXZdEYbHp+HruHQnMOFP0s6YTuPure7qDPgaixV21wX8T/KSLUnV6KQLWHCb6rWPD49vt0pw2G4gfFH3PzzBw9D0p+v0PsHLnFM9kLOCjAzR5aCQw+Wzt2A2YGL2QYocfAff18KRufzobmHOSJhdPAsbeknT+FIx03/TX+qx1wFd71mzzM2ZOvESTE+YC7Ztr7hxtBz/sqMmLEbDxaJF9P8HQ+MkUe38KtK3vKc9HlrDmUE3uGA1MO+O4OZgavdJF2+ongDFjMHbmGZqcNwWL3fdDtG5/OIsYd5Mmx2wwiV930+SpjEmZJ1LsscEkVhylnTjDJvfBsKTtv3c20b54fxHpeMjnbVgdrdx84w2vbnAYHo0ZHbGF7Zx1uYJ1haJ1xap1VZ+1Let9xbaK9z5vWd5779+z7L0a/6Zdb/rkd/I25d/zKctZe7IVX2e1WMjZkSsUq14FAA==";
101047
101206
  function ElementsPanel() {
101048
101207
  const { startLoading, stopLoading, isLoading } = useLoaderStore();
101208
+ const { recentItems, addRecentItem } = useRecentElementsStore();
101049
101209
  const [elementsSVG, setElementsSVG] = useState([]);
101050
101210
  const [page, setPage] = useState(1);
101051
101211
  const [category, setCategory] = useState(null);
@@ -101155,45 +101315,96 @@ function ElementsPanel() {
101155
101315
  ] })
101156
101316
  ] }),
101157
101317
  /* @__PURE__ */ jsx("hr", { className: "text-neutral-800 h-[0.5px]" }),
101158
- /* @__PURE__ */ jsx("div", { className: "h-[calc(100dvh-180px)] overflow-auto", children: elements.map((item, i) => /* @__PURE__ */ jsxs("div", { className: "mx-4", children: [
101159
- /* @__PURE__ */ jsxs("div", { className: "py-5", children: [
101160
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
101161
- /* @__PURE__ */ jsx("h2", { className: "text-sm font-semibold mb-3", children: item.category }),
101162
- /* @__PURE__ */ jsx(
101163
- "div",
101318
+ /* @__PURE__ */ jsxs("div", { className: "h-[calc(100dvh-180px)] overflow-auto custom-scrollbar", children: [
101319
+ recentItems.length > 0 && /* @__PURE__ */ jsxs("div", { className: "mx-4", children: [
101320
+ /* @__PURE__ */ jsxs("div", { className: "py-5", children: [
101321
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
101322
+ /* @__PURE__ */ jsx("h2", { className: "text-sm font-semibold mb-3", children: "Recent items" }),
101323
+ recentItems.length > 3 && /* @__PURE__ */ jsx(
101324
+ "div",
101325
+ {
101326
+ className: "underline cursor-pointer",
101327
+ onClick: () => {
101328
+ setElementsSVG(recentItems);
101329
+ setCategory("Recent items");
101330
+ setHasMore(false);
101331
+ },
101332
+ children: "View all"
101333
+ }
101334
+ )
101335
+ ] }),
101336
+ /* @__PURE__ */ jsx("div", { className: "grid grid-cols-4 md:grid-cols-3 gap-3", children: recentItems.slice(0, 3).map((shape, i) => /* @__PURE__ */ jsx(
101337
+ "button",
101164
101338
  {
101165
- className: "underline cursor-pointer",
101166
- onClick: () => getSVG(null, item?.category, 1),
101167
- children: "View all"
101168
- }
101169
- )
101170
- ] }),
101171
- /* @__PURE__ */ jsx("div", { className: "grid grid-cols-4 md:grid-cols-3 gap-3", children: item.data.map((shape, j2) => /* @__PURE__ */ jsx(
101172
- "button",
101173
- {
101174
- onClick: () => {
101175
- addSVGToCanvas("svg", shape.image);
101339
+ onClick: () => {
101340
+ addSVGToCanvas("svg", shape.image);
101341
+ addRecentItem(shape);
101342
+ },
101343
+ className: "w-full cursor-pointer flex items-center justify-center",
101344
+ children: /* @__PURE__ */ jsx(
101345
+ "img",
101346
+ {
101347
+ src: shape.image,
101348
+ alt: shape.category,
101349
+ width: 150,
101350
+ height: 150,
101351
+ className: "w-full h-24 object-contain"
101352
+ }
101353
+ )
101176
101354
  },
101177
- className: "w-full cursor-pointer flex items-center justify-center",
101178
- children: /* @__PURE__ */ jsx(
101179
- "img",
101355
+ shape._id || shape.image || i
101356
+ )) })
101357
+ ] }),
101358
+ /* @__PURE__ */ jsx("hr", { className: "text-neutral-800 h-[0.5px]" })
101359
+ ] }),
101360
+ elements.map((item, i) => /* @__PURE__ */ jsxs("div", { className: "mx-4", children: [
101361
+ /* @__PURE__ */ jsxs("div", { className: "py-5", children: [
101362
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
101363
+ /* @__PURE__ */ jsx("h2", { className: "text-sm font-semibold mb-3", children: item.category }),
101364
+ /* @__PURE__ */ jsx(
101365
+ "div",
101180
101366
  {
101181
- src: shape.image,
101182
- alt: item.category,
101183
- width: 150,
101184
- height: 150,
101185
- className: "w-full h-24 object-contain"
101367
+ className: "underline cursor-pointer",
101368
+ onClick: () => getSVG(null, item?.category, 1),
101369
+ children: "View all"
101186
101370
  }
101187
101371
  )
101188
- },
101189
- j2
101190
- )) })
101191
- ] }),
101192
- /* @__PURE__ */ jsx("hr", { className: "text-neutral-800 h-[0.5px]" })
101193
- ] }, i)) })
101372
+ ] }),
101373
+ /* @__PURE__ */ jsx("div", { className: "grid grid-cols-4 md:grid-cols-3 gap-3", children: item.data.map((shape, j2) => /* @__PURE__ */ jsx(
101374
+ "button",
101375
+ {
101376
+ onClick: () => {
101377
+ addSVGToCanvas("svg", shape.image);
101378
+ addRecentItem({
101379
+ _id: shape.image,
101380
+ status: true,
101381
+ category: item.category,
101382
+ image: shape.image,
101383
+ largeImage: shape.image,
101384
+ tags: []
101385
+ });
101386
+ },
101387
+ className: "w-full cursor-pointer flex items-center justify-center",
101388
+ children: /* @__PURE__ */ jsx(
101389
+ "img",
101390
+ {
101391
+ src: shape.image,
101392
+ alt: item.category,
101393
+ width: 150,
101394
+ height: 150,
101395
+ className: "w-full h-24 object-contain"
101396
+ }
101397
+ )
101398
+ },
101399
+ j2
101400
+ )) })
101401
+ ] }),
101402
+ /* @__PURE__ */ jsx("hr", { className: "text-neutral-800 h-[0.5px]" })
101403
+ ] }, i))
101404
+ ] })
101194
101405
  ] });
101195
101406
  }
101196
- const isTextbox = (obj) => obj.type === "textbox";
101407
+ const isTextbox = (obj) => obj.type === "textbox" || obj.type === "i-text" || obj.type === "text";
101197
101408
  async function addTextToCanvas(fontSize, width, textName) {
101198
101409
  const canvas2 = useEditorStore.getState().canvas;
101199
101410
  if (!canvas2) return;
@@ -101274,13 +101485,10 @@ function TextPanel() {
101274
101485
  page,
101275
101486
  limit: 30
101276
101487
  }).then((res) => {
101277
- const newItems = res.items.filter(
101278
- (x2) => x2.status && x2.image
101279
- );
101280
101488
  setTextElements(
101281
- (prev) => page === 1 ? newItems : [...prev, ...newItems]
101489
+ (prev) => page === 1 ? res.items : [...prev, ...res.items]
101282
101490
  );
101283
- if (newItems.length < 20) setHasMore(false);
101491
+ if (res?.items?.length < 20) setHasMore(false);
101284
101492
  }).finally(() => stopLoading());
101285
101493
  }, [page]);
101286
101494
  useEffect(() => {
@@ -101364,7 +101572,7 @@ function TextPanel() {
101364
101572
  )
101365
101573
  ] }),
101366
101574
  /* @__PURE__ */ jsx("hr", { className: "text-neutral-800 h-[0.5px] my-4" }),
101367
- /* @__PURE__ */ jsx("div", { ref: scrollRef, className: "h-[calc(100vh-250px)] overflow-auto", children: /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-3 pb-12 mx-4", children: [
101575
+ /* @__PURE__ */ jsx("div", { ref: scrollRef, className: "h-[calc(100vh-250px)] overflow-auto custom-scrollbar", children: /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-3 pb-12 mx-4", children: [
101368
101576
  textElements.map((tpl, index) => /* @__PURE__ */ jsx(
101369
101577
  "button",
101370
101578
  {
@@ -101546,6 +101754,23 @@ function InstagramIcon() {
101546
101754
  );
101547
101755
  }
101548
101756
  const VibrantLogo = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAABGCAMAAAC+PCsEAAAC7lBMVEUAAAAyMjIzMzMwMDAzMzNtmZJxfnoyMjI1NTUwMDBvmpYaGhoaFxdtmZIyMjJtmJIzMzNsmZFum5RtmZJiqJ8xMTEyMjJtmJIyLzAzMzNum5RwmpJtmZJtmZJtmZJtmZImJCVtmZJtmZImJiZtmZJtmpMvLy91pZRtmZJsmZJtmZI2NTYpJidum5QxMTFtmZJvmpUkIiRtmZJtmZJtmZJtmZIsLCwvLy9tmZIzMzMzMzMuLi5tmZIvLy8sLCxtmZL/Uml3qqIyMjL+DQEwMDAkIiPeanUmobj/YXoqKir2aXr2gY0p98tvopkvLy/xeoiS2V/+cI78ZX4F9PQaGhoJ4eYX7+mQ8YQ1NTUB/f32e5L2Y3EB/v4Lv6T4GRkC/fv8Umb57CYH+PME9fPeKjWBuqVUSE3bV2AL7Oxq7XT8OUcA/O0D9/P0R1n8U2T4d4gD9vXuFBo1Ozz+FBBKSUz/+yOiSVtJTE3rJS+Za2kQ7erGXGZXrWV1dEL/PVDhZXtN98fqGyA/aI72jJuA/pH5eIhXmnlvXl9MSk1mYj+//1k8+s0D/fT/rRL/bgv7GiEMkcbQ/1s8o16k/3wzMzM2NjYA//84ODgA//MA/+oA/u//OQMB/+AC/9b/gQv/Wgr/RAf/HwQA//gF/80E/az/dQ3/FAED7aNtm4//vgP/bBn/Xxj/4UX/yAb/KgQD/7z/TgT/R17/TxQC29b/Ywj/5Qb/bAMAztD/oAn/0ggMzMMC5rP/RUv/MkX/8Qb/2AX/BwID1Lf/UTT+3y//Kyn//QUP/OVS6qr/7C7/3R0Cvbz/Xkn/0jD//Sz/6B///h3/rgYA4ekC88w/7bPNTGP/bkjiwhT/jw0HqqkEnp1k55GhgFX/7Dv/NhID8OsWvLA7t62a3Xj/bmzM51mz2VNMTE5JSkz/ey/hWyWFkGrj40y2cUo4TkXDcj/sTDXQvij0xQ1WkHQ74myOr2nJ1l4/qlnNz1FJPUvHai7J1CjznehDAAAAlHRSTlMAzZQW274Fpk8ICWo5xOc3yS4VtA24RxwN8GZc3s/JqkH16I2BeFYR1YudOzkmdVQ/H6FJuKabh2/247+urmqW/ZT5tmQrD/v7el4h/jDWMv710n5hTTj87bmie/b22tnMs6+PWEwuHBn69unStJp8bjf89LezonVzaSj08t/TuKahj4dxRUD67NvWxMKzs66hmIeEWUQ3oQAADLhJREFUaN7UlkloE1EYx19U3JEcVOxNEPSgCF4sWpTgRtDgUq1SkZaqYLUuVRHEi4p6crvMvMyW2coMzkoakpCIJqExTUpXKG2hVsEFFEEUFMGbLyMdzTSJgh4mP0KYvI/M+/75/v83AWB7kQdXrrS13WhubgJ/YOnx5uNtbW1X7m9HtJwELmK7XCQ5WBh6A+FZ8AcOQnhkqDA4zGdkOZO5CNzETVkWMmZqsBA7AuEJUJUmCPOxQmE4nhFkuWUXcBfbZUHIcMNThb48hFXN1X4M9maHpoYTGUGQL58CLmNuiyyYmYnhqWw2AI+1g8qchfBNnz5oCIIpuMxXFrsuC6YpK8N69oMIW6sHBOmI0UiH7KqgT3NRNk2Bjg3qfdVichjC0aw+lYsLtClfBa7kqmDSQjxX0JG54GFQnlZIfdD1XMKkaeEhcCk3BZo3R3K6/oaoZK4TyFhZPZfkadpsmQvcSovJ83wypw9MQniwgrECWX0oJb3g6csuDPo0p57RYV5KDekDXRC2lz2xiLcDQ7kEH+afuTQgPzn5LMxFErm+gVcibJ5Z7kRJH+hLJblI+MU54GrOveA4LZ3qG0B5b5r5KBTJr8lYiuU4/ozbnuhOc52JRLWJVMx4JRKtM5IuinkjnRrRotzT3cDl7H4SlVQllTQCIuwsLV2DJPXNiCUlNd5zHriecz2SxKZjyitIHltaUjlOiKNKOjahSprP5cayzOVTWTURMz73EsTBkqNXJOFbJaaorNTtemMVueBjWVVJK5OOkTRTMKAk06zEjjWCmqBxjFXZtPFJRCMpHch+JZ1Q2Xf+faAm2HfgHasl0sooJDuW/jYQsXfcUFSJfX0U1AhHX7OqZCj7IUF12gMhSJhXkhOaNHagBpJuj0TSEsnxXki12kcWRcD9xogWl/y3QM1w9LUUVRUlDwmiCVi0UxREzmKLA3Hvn94Z7DkwFo9MGEVv3QMWB9FA8sYIF1f9NXH0TnPLr0a1kXFKJKhrALGwg6LQmSVx8f4aOXrtkfTHI6wxCinxBkA0UQRBfE9EojU2EJQSn8pxI6chKQbWWFEnYeCzFIn3H1ho1VesmL0A1AJ7/P3RCPsWkqR4G4BrJMnASTQQzXfBKs/2bF5Sb12t9TjxLqtfDmzWVy7bLFjm8a50LG3xLgZO5nmq0LCg0kg0LjzeSzDiXRT1opD98Ui037/rp5BNGDbPupqPlcNj97WxXNm7FvzOoiBacgipw+s2AAdzsGqsqPAs8fdEeTYAGbJ3DThLMhTxKcxZA7GELMGwWdbVcnQLPBjEEUELHEPg2Hzwk1lW3QaVi1xaDX4xd0lxcXmpkDkYPsfZmte+V3E33HHnCkJAY3c0HJ6EDEMeXkoyIbGL5bl+/54yQvCNhxb9xtZZqDG8bsW0EHxJSXX9vLqi0J3AZoclzusUguENM4TgHnurQxvR11b92vlQpczu9vVw9BfIhJgTnWQoBPPhsNZ9HZQTUmoUVG1Aje2wJ+JxlDcsw7Dgsl89b8awopSVTiFYcItTCNrUZh360gbwZ3b5n3D0R4IJhTo60Bt8RHM9vjvlhCAbOUARwrfYQuaAcma3e9iKPtRvRr+/UwhSUl8qZDqYdjoX/9UJ3K3x71+SoS4GDYSBz2nuSSOoLsQGjcRbRQjqHV89nZDNWHAvqEddL3ZaC61t/Q9C9vl6wvxjKtQVYtCL+shHui/8rZAt1YWstK2HRKFAHQIbtmHBBoeQhjpUmv/vQkDjD9Lr5yWROAoA+JuUbNiQLsJ4Exa8KEEXRSWJ0EKFuuxBkAXPwXbYw16C9rT/wXyxXVcIRqjbJHMYMD0UnropHVpiq2MRdN7jvnnojPMrmr7vIjqOMx/f9/vem8FZ++GooygGRHs8vtyovRWyI8tzS2vRH0IlKyrS91jKBmEfEghppfkhjd5l+/RIUdROR/nZPzvGlfVGSBI/LL6SkRKdZJas4rQzFeyQj1Bpyaxe5obUNgbtp7uJomJKfj3gymr4QBxdWSpuUvn1hwhMzoXMfZ+njGXph+wQyKBEEHkh0By0b8cIUX9P7k7bg17NByKsx+Yjjx+F02BCdpw1LYLLKOLMHaXECYEYw0rNDWn02s9jDSGdyRghTfCEeMY6WJBocilpRWnBgH5dm+YG+6VoDjMs4YSIAkoyvJBab/RP0xW1q2jjp7+9RgCIvLJmQjwil5gmhMlsVmGxcLGCEwKhMEoqnBBojh51faJ2VW18O+rt+kEyxVVbZPAoy0v+kHBqVt0Y7RDLvOyEQDlOWD5IY/jS188NiHY7ai76QVLgtwvo7jbDZhirKh4piXPVrWSdhSmJuSCQaGFjXOKD/Bi+3OsXarerac+jfYC3ll9q7XJ5VrUkUaLAV0xB1NZuVsCKVfpTnBAoYunKlbkgW3t7M8jBsBoEkjYGKI/ym2CUKishK9kFMyL4PuaGQBYl4RAPBJrDh/5Vt3tyrl8Pa0EgkKd7ckFgHSVpKyHuSLsh1E52RB7I/nB7BjmEQBC8qOAxotBGEMyS5REFJ4T4LaOdxN4PqSLk/pwg+8EgAi1/d0ZonZTM/h6xRcaYeZc9IIsCw/cckBpCbhByol9XA0GkHDZFT4gUZ6wuAkAKz6w4n7kQUnBC6EAdj8jvh8DhFNK/3g0EqeB1V70gNCayLNDiq0vgiAijwuWCQHmTyTyQL9tXNxcIuTjY8ockvB44mByyIO6Rkb5SAWeUaZe4INROuDLS2L76Q5Dv4A9JSiFbpHCSpbnCG7KMKckYOzfuSghta7nsBYFPXJAqQj4bkG+vQHLR+FzU47JMjd0HQiX4fztmsupEEIXhH6UKbSxiD9LSU263HdLdNyGxB40SCVdQV0E3oht3DrgVUQTdiDitHPAZzDu48i18AV25URC3Vp30zXAdEnSj4AeVFKmbc853T1d1kpOHaId8z2FZ7okfishvJ38gclaJvH79+s2dZZGLKz40njyKmchxEMtH8K6TOxqy+IteB3uP7xSh28nvi2hn3smOvHn9to8F6MvDkZ+KHNt/9PDix3iO7/63B7CLdsgPWnJM3Wi4FDmCnRx4dWJBhI7qtXl299ylS9evn9GwyN7OxsbhqdI+SWezIx868nlTzjq7F+va6JzCd2zuxt7NvfghpzY6G0BnHyXYwcY8lszc2XcQa3P+09P0/vPP9/Gvwx9deGi//HIZy7hbW1t9AO2WnIwBjK8AXSiMKzC20tYoBoouDBuKBl2aWmyKoYsF9OVOt+S6Q2mdOgFF1KYx4q0tR6312pSG05i/awW3rgMP7p3GElrkMxaqUieMidAFUgGUMeXzuM38CasA34HtkV5VqpUoSQdV5DdQYwvP6y9YeV46yKOkD9ihoARE4kBhRowljMOKdAB9z5JDA3QZ1ck8X8evuXxXv/riHpYxTAvoejrcJoBe0kZLTqpJocphbfUHlLoHPgk1AGkG2BMqzBIxaprMGHjYxgpH1IxhCugmVIIxJP2JCUWzB8AfQBNdAA2m0TDCFIpysOraenLj68c7O0VKQwUt4A5VBZ5NIqNR0iARSjMVsZtNUYtUJnbgDJHn2CZlqKlFkBSQCMek50qJZCNYggNoM0tm4cgY1uTWxw8f6HTQMMMSrZ4rSo4iKRqOl4FEmo1+aKCxLMJQDUlE+0Hz87Di2EYUmNEN3IbrM7VY+HBKANK518uiLrjfzLJsKLjMAgR9rMnmh/c36dofL7TJ9wKRa0A/CifNGLWIgzSBvSwSaPBipDkM36KdmWVxd7vcZOJgBqMEA7muw07CiZlb9PIAluo0RpEQTE54IsqyFEEtYmNN+OP3e4Br7pU2ZrRLHYSbj0MbcxE0c61cFrFhhH0nB59eKKnZDAcg7NBpJwOMLRCBC0ks12PoQ93rTWUnvi8mGYCqftv80iKRAuty8/1BXL3zcHmPdGsRE25kLYiAidxaEjEA3WcxMPIwhekgqlxVZeYGiFaEKWYfOoMeUo7mqFEUhSfnlQNicbNzpD5qP6zi9k3sub1n87BlzUXYdkcYMApUFUOADnMtDFUaoURKKeIbdPCMAC0Sam54GaaIGNDYpIUa37epziGkiAEnUq2NOCR5JZUGtUigTzPQ4JGp0Qk3wCp271EeV6/ap+ciwbgWEQCCEUdsAqaKVZ9agYouXNiJAUkqRWAlUVkNIxM1RTjKvJYezUyCkFVDT2iArhqZMQ1mCkU36k7DUxgd0wx9OWSGqFmVoWlgJQev7j64l3OOGZprgNBd9dCSMXtArzF9TYrYLleaOixHo+QkbsSBH8TzOC0hBoA7E9Fi4Qcp3bddDWjHbTg2CKerwhPcMUAZaMhZGvishf/8hXwDf/LQfNeBDzwAAAAASUVORK5CYII=";
101757
+ const VibrantImageService = {
101758
+ async getFolderData(params = {}) {
101759
+ const { item = "Small", page = 1, limit = 30, search = "" } = params;
101760
+ const query = new URLSearchParams();
101761
+ if (item) query.set("item", item);
101762
+ if (page) query.set("page", String(page));
101763
+ if (limit) query.set("limit", String(limit));
101764
+ if (search) query.set("search", search);
101765
+ const res = await apiUploadImage(
101766
+ `/folder-data?${query.toString()}`,
101767
+ {
101768
+ method: "GET"
101769
+ }
101770
+ );
101771
+ return res;
101772
+ }
101773
+ };
101549
101774
  function useSocialAuth({
101550
101775
  onImageSelect
101551
101776
  }) {
@@ -101703,10 +101928,12 @@ const imageOptions = [
101703
101928
  ];
101704
101929
  function PhotosPanel() {
101705
101930
  const { startLoading, stopLoading, isLoading } = useLoaderStore();
101931
+ const [source, setSource] = useState("external");
101706
101932
  const [socialImages, setSocialImages] = useState([]);
101707
101933
  const [page, setPage] = useState(1);
101708
101934
  const [hasMore, setHasMore] = useState(true);
101709
101935
  const [category, setCategory] = useState(imageOptions[0]);
101936
+ const [searchQuery, setSearchQuery] = useState("");
101710
101937
  const scrollRef = useRef(null);
101711
101938
  const {
101712
101939
  handleGoogleAuth,
@@ -101714,40 +101941,74 @@ function PhotosPanel() {
101714
101941
  handleFacebookAuth,
101715
101942
  handleInstagramAuth
101716
101943
  } = useSocialAuth({
101717
- onImageSelect: (url) => {
101718
- addImageToCanvas(url);
101719
- if (window.innerWidth < 768) {
101720
- useSidePanelStore.getState().setActivePanel(null);
101944
+ onImageSelect: async (url) => {
101945
+ try {
101946
+ startLoading();
101947
+ const image = await ShapeService.convertImages(url);
101948
+ const imageUrl = image?.success ? image?.url : null;
101949
+ if (imageUrl) addImageToCanvas(imageUrl);
101950
+ } catch (error2) {
101951
+ console.error("Failed to convert image:", error2);
101952
+ } finally {
101953
+ stopLoading();
101954
+ if (window.innerWidth < 768) {
101955
+ useSidePanelStore.getState().setActivePanel(null);
101956
+ }
101721
101957
  }
101722
101958
  }
101723
101959
  });
101724
101960
  const fetchImages = useCallback(
101725
- async (q2, pageNo) => {
101961
+ async (q2, pageNo, currentSource = source) => {
101726
101962
  startLoading();
101727
101963
  try {
101728
- const res = await ShapeService.getAllExternal({
101729
- page: pageNo,
101730
- limit: 30,
101731
- keyType: "all",
101732
- isVector: false,
101733
- query: q2
101734
- });
101735
- const newItems = res.items.filter(
101736
- (x2) => x2.status && x2.image
101737
- );
101738
- setSocialImages(
101739
- (prev) => pageNo === 1 ? newItems : [...prev, ...newItems]
101740
- );
101741
- if (newItems.length < 20) setHasMore(false);
101964
+ if (currentSource === "vibrant") {
101965
+ const res = await VibrantImageService.getFolderData({
101966
+ item: "Small",
101967
+ page: pageNo,
101968
+ limit: 30,
101969
+ search: q2 || void 0
101970
+ });
101971
+ const newItems = (res.files || []).map((f) => ({
101972
+ _id: f.key,
101973
+ status: true,
101974
+ category: "vibrant",
101975
+ image: f.smallUrl || f.url,
101976
+ largeImage: f.largeUrl || f.url,
101977
+ tags: [f.name]
101978
+ }));
101979
+ setSocialImages(
101980
+ (prev) => pageNo === 1 ? newItems : [...prev, ...newItems]
101981
+ );
101982
+ if (newItems.length < 20) setHasMore(false);
101983
+ else setHasMore(true);
101984
+ } else {
101985
+ const res = await ShapeService.getAllExternal({
101986
+ page: pageNo,
101987
+ limit: 30,
101988
+ keyType: "all",
101989
+ isVector: false,
101990
+ query: q2
101991
+ });
101992
+ const newItems = res.items.filter(
101993
+ (x2) => x2.status && x2.image
101994
+ );
101995
+ setSocialImages(
101996
+ (prev) => pageNo === 1 ? newItems : [...prev, ...newItems]
101997
+ );
101998
+ if (newItems.length < 20) setHasMore(false);
101999
+ else setHasMore(true);
102000
+ }
102001
+ } catch (err) {
102002
+ console.error("Failed to fetch images:", err);
101742
102003
  } finally {
101743
102004
  stopLoading();
101744
102005
  }
101745
102006
  },
101746
- [startLoading, stopLoading]
102007
+ [startLoading, stopLoading, source]
101747
102008
  );
101748
102009
  useEffect(() => {
101749
- fetchImages(category, 1);
101750
- }, []);
102010
+ fetchImages(searchQuery || (source === "vibrant" ? "" : category), 1, source);
102011
+ }, [source]);
101751
102012
  useEffect(() => {
101752
102013
  const el = scrollRef.current;
101753
102014
  if (!el) return;
@@ -101756,13 +102017,13 @@ function PhotosPanel() {
101756
102017
  if (el.scrollTop + el.clientHeight >= el.scrollHeight - 200) {
101757
102018
  const next = page + 1;
101758
102019
  setPage(next);
101759
- fetchImages(category, next);
102020
+ fetchImages(searchQuery || (source === "vibrant" ? "" : category), next, source);
101760
102021
  }
101761
102022
  };
101762
102023
  el.addEventListener("scroll", onScroll);
101763
102024
  return () => el.removeEventListener("scroll", onScroll);
101764
- }, [hasMore, isLoading]);
101765
- return /* @__PURE__ */ jsxs(Fragment, { children: [
102025
+ }, [hasMore, isLoading, page, searchQuery, category, source, fetchImages]);
102026
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col h-full min-h-0", children: [
101766
102027
  /* @__PURE__ */ jsx("script", { src: "https://apis.google.com/js/api.js" }),
101767
102028
  /* @__PURE__ */ jsx(
101768
102029
  "script",
@@ -101782,70 +102043,166 @@ function PhotosPanel() {
101782
102043
  });
101783
102044
  }
101784
102045
  } }),
101785
- /* @__PURE__ */ jsx("div", { className: "bg-neutral-800 px-3 py-2 rounded flex items-center gap-2 mb-4 mx-4", children: /* @__PURE__ */ jsx(
101786
- "input",
101787
- {
101788
- className: "bg-transparent outline-none text-sm text-gray-300 w-full",
101789
- onKeyDown: (e3) => {
101790
- e3.stopPropagation();
101791
- if (e3.key === "Enter") {
101792
- fetchImages(e3.target.value, 1);
102046
+ /* @__PURE__ */ jsxs("div", { className: "shrink-0 pt-4", children: [
102047
+ /* @__PURE__ */ jsx("div", { className: "bg-neutral-800 px-3 py-2 rounded flex items-center gap-2 mb-3 mx-4", children: /* @__PURE__ */ jsx(
102048
+ "input",
102049
+ {
102050
+ className: "bg-transparent outline-none text-sm text-gray-300 w-full",
102051
+ placeholder: source === "vibrant" ? "Search Vibrant Images..." : "Search Stock Photos...",
102052
+ value: searchQuery,
102053
+ onChange: (e3) => setSearchQuery(e3.target.value),
102054
+ onKeyDown: (e3) => {
102055
+ e3.stopPropagation();
102056
+ if (e3.key === "Enter") {
102057
+ setPage(1);
102058
+ fetchImages(searchQuery, 1, source);
102059
+ }
101793
102060
  }
101794
102061
  }
101795
- }
101796
- ) }),
101797
- /* @__PURE__ */ jsxs("div", { className: "m-4", children: [
101798
- /* @__PURE__ */ jsx("h2", { className: "text-md font-medium mb-3", children: "Upload From Other Media" }),
101799
- /* @__PURE__ */ jsxs("div", { className: "flex gap-4", children: [
101800
- /* @__PURE__ */ jsx("button", { type: "button", className: "cursor-pointer", onClick: () => handleGoogleAuth("googledrives"), children: /* @__PURE__ */ jsx(GoogleDriveIcon, {}) }),
101801
- /* @__PURE__ */ jsx("button", { type: "button", className: "cursor-pointer", onClick: () => handleGoogleAuth("googlephotos"), children: /* @__PURE__ */ jsx(GooglePhotoIcon, {}) }),
101802
- /* @__PURE__ */ jsx("button", { type: "button", className: "cursor-pointer", onClick: handleDropboxAuth, children: /* @__PURE__ */ jsx(
101803
- "img",
101804
- {
101805
- src: "https://vibrantprintshopimage.s3.amazonaws.com/dropbox.svg",
101806
- alt: "dropbox",
101807
- height: 30,
101808
- width: 30,
101809
- className: "object-contain"
101810
- }
101811
- ) }),
101812
- /* @__PURE__ */ jsx("button", { type: "button", className: "cursor-pointer", onClick: handleFacebookAuth, children: /* @__PURE__ */ jsx(FacebookIcon, {}) }),
101813
- /* @__PURE__ */ jsx("button", { type: "button", className: "cursor-pointer", onClick: handleInstagramAuth, children: /* @__PURE__ */ jsx(InstagramIcon, {}) }),
101814
- /* @__PURE__ */ jsx("button", { type: "button", className: "cursor-pointer", children: /* @__PURE__ */ jsx(
101815
- "img",
102062
+ ) }),
102063
+ /* @__PURE__ */ jsxs("div", { className: "mx-4 mb-3", children: [
102064
+ /* @__PURE__ */ jsx("h2", { className: "text-md font-medium mb-2 text-gray-200", children: "Upload From Other Media" }),
102065
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
102066
+ /* @__PURE__ */ jsx(
102067
+ "button",
102068
+ {
102069
+ type: "button",
102070
+ className: "cursor-pointer p-1.5 rounded-lg hover:bg-white/10 transition-colors",
102071
+ onClick: () => handleGoogleAuth("googledrives"),
102072
+ title: "Google Drive",
102073
+ children: /* @__PURE__ */ jsx(GoogleDriveIcon, {})
102074
+ }
102075
+ ),
102076
+ /* @__PURE__ */ jsx(
102077
+ "button",
102078
+ {
102079
+ type: "button",
102080
+ className: "cursor-pointer p-1.5 rounded-lg hover:bg-white/10 transition-colors",
102081
+ onClick: () => handleGoogleAuth("googlephotos"),
102082
+ title: "Google Photos",
102083
+ children: /* @__PURE__ */ jsx(GooglePhotoIcon, {})
102084
+ }
102085
+ ),
102086
+ /* @__PURE__ */ jsx(
102087
+ "button",
102088
+ {
102089
+ type: "button",
102090
+ className: "cursor-pointer p-1.5 rounded-lg hover:bg-white/10 transition-colors",
102091
+ onClick: handleDropboxAuth,
102092
+ title: "Dropbox",
102093
+ children: /* @__PURE__ */ jsx(
102094
+ "img",
102095
+ {
102096
+ src: "https://vibrantprintshopimage.s3.amazonaws.com/dropbox.svg",
102097
+ alt: "dropbox",
102098
+ height: 26,
102099
+ width: 26,
102100
+ className: "object-contain"
102101
+ }
102102
+ )
102103
+ }
102104
+ ),
102105
+ /* @__PURE__ */ jsx(
102106
+ "button",
102107
+ {
102108
+ type: "button",
102109
+ className: "cursor-pointer p-1.5 rounded-lg hover:bg-white/10 transition-colors",
102110
+ onClick: handleFacebookAuth,
102111
+ title: "Facebook",
102112
+ children: /* @__PURE__ */ jsx(FacebookIcon, {})
102113
+ }
102114
+ ),
102115
+ /* @__PURE__ */ jsx(
102116
+ "button",
102117
+ {
102118
+ type: "button",
102119
+ className: "cursor-pointer p-1.5 rounded-lg hover:bg-white/10 transition-colors",
102120
+ onClick: handleInstagramAuth,
102121
+ title: "Instagram",
102122
+ children: /* @__PURE__ */ jsx(InstagramIcon, {})
102123
+ }
102124
+ ),
102125
+ /* @__PURE__ */ jsx(
102126
+ "button",
102127
+ {
102128
+ type: "button",
102129
+ onClick: () => {
102130
+ setSource("vibrant");
102131
+ setPage(1);
102132
+ setSearchQuery("");
102133
+ fetchImages("", 1, "vibrant");
102134
+ },
102135
+ title: "Vibrant Stock Images",
102136
+ className: "cursor-pointer",
102137
+ children: /* @__PURE__ */ jsx(
102138
+ "img",
102139
+ {
102140
+ src: VibrantLogo,
102141
+ alt: "vibrant",
102142
+ height: 64,
102143
+ width: 64,
102144
+ className: "object-contain"
102145
+ }
102146
+ )
102147
+ }
102148
+ )
102149
+ ] })
102150
+ ] }),
102151
+ /* @__PURE__ */ jsx("hr", { className: "text-neutral-800 h-[0.5px] my-3" }),
102152
+ source === "external" ? /* @__PURE__ */ jsx("div", { className: "mx-4 mb-3", children: /* @__PURE__ */ jsx("div", { className: "flex gap-2 overflow-auto scrollbar-none custom-scrollbar", children: imageOptions.map((item) => /* @__PURE__ */ jsx(
102153
+ "button",
102154
+ {
102155
+ className: `px-2 py-1 rounded-sm shrink-0 text-xs ${item === category ? "bg-[#02bc71] text-white" : "bg-[#8f99a826]"}`,
102156
+ onClick: () => {
102157
+ setCategory(item);
102158
+ setSocialImages([]);
102159
+ setPage(1);
102160
+ fetchImages(item.toLowerCase(), 1, "external");
102161
+ },
102162
+ children: item
102163
+ },
102164
+ item
102165
+ )) }) }) : /* @__PURE__ */ jsxs("div", { className: "mx-4 mb-3 flex items-center justify-between", children: [
102166
+ /* @__PURE__ */ jsxs("span", { className: "text-xs font-semibold text-[#02bc71] flex items-center gap-1.5", children: [
102167
+ /* @__PURE__ */ jsx("span", { className: "inline-block w-2 h-2 rounded-full bg-[#02bc71]" }),
102168
+ "Vibrant Cloud Images"
102169
+ ] }),
102170
+ /* @__PURE__ */ jsx(
102171
+ "button",
101816
102172
  {
101817
- src: VibrantLogo,
101818
- alt: "vibrant",
101819
- height: 60,
101820
- width: 60,
101821
- className: "object-contain"
102173
+ type: "button",
102174
+ onClick: () => {
102175
+ setSource("external");
102176
+ setPage(1);
102177
+ setSearchQuery("");
102178
+ fetchImages(category.toLowerCase(), 1, "external");
102179
+ },
102180
+ className: "text-[11px] text-gray-400 hover:text-white cursor-pointer underline",
102181
+ children: "Explore Stock Categories"
101822
102182
  }
101823
- ) })
102183
+ )
101824
102184
  ] })
101825
102185
  ] }),
101826
- /* @__PURE__ */ jsx("hr", { className: "text-neutral-800 h-[0.5px] my-4" }),
101827
- /* @__PURE__ */ jsx("div", { className: "m-4", children: /* @__PURE__ */ jsx("div", { className: "flex gap-2 overflow-auto", children: imageOptions.map((item) => /* @__PURE__ */ jsx(
101828
- "button",
101829
- {
101830
- className: `px-2 py-1 rounded-sm shrink-0 ${item === category ? "bg-[#02bc71] text-white" : "bg-[#8f99a826]"}`,
101831
- onClick: () => {
101832
- setCategory(item);
101833
- setSocialImages([]);
101834
- fetchImages(item.toLowerCase(), 1);
101835
- },
101836
- children: item
101837
- },
101838
- item
101839
- )) }) }),
101840
- /* @__PURE__ */ jsx("div", { className: "h-[calc(100vh-288px)] overflow-auto", ref: scrollRef, children: /* @__PURE__ */ jsx("div", { className: "grid grid-cols-4 md:grid-cols-3 gap-3 mx-4 pb-5", children: socialImages.length > 0 && socialImages.map((item, i) => {
102186
+ /* @__PURE__ */ jsx("div", { className: "flex-1 min-h-0 overflow-y-auto pb-8 custom-scrollbar", ref: scrollRef, children: /* @__PURE__ */ jsx("div", { className: "grid grid-cols-4 md:grid-cols-3 gap-3 mx-4 pb-5", children: socialImages.length > 0 ? socialImages.map((item, i) => {
101841
102187
  return /* @__PURE__ */ jsx(
101842
102188
  "button",
101843
102189
  {
101844
- className: "relative aspect-square rounded overflow-hidden group cursor-pointer",
101845
- onClick: () => {
101846
- addImageToCanvas(item.largeImage);
101847
- if (window.innerWidth < 768) {
101848
- useSidePanelStore.getState().setActivePanel(null);
102190
+ className: "relative aspect-square rounded overflow-hidden group cursor-pointer bg-neutral-900 transition-all",
102191
+ onClick: async () => {
102192
+ try {
102193
+ startLoading();
102194
+ const targetUrl = item.largeImage || item.image;
102195
+ if (!targetUrl) return;
102196
+ const image = await ShapeService.convertImages(targetUrl);
102197
+ const imageUrl = image?.success ? image?.url : targetUrl;
102198
+ if (imageUrl) addImageToCanvas(imageUrl);
102199
+ if (window.innerWidth < 768) {
102200
+ useSidePanelStore.getState().setActivePanel(null);
102201
+ }
102202
+ } catch (error2) {
102203
+ console.error("Failed to convert image:", error2);
102204
+ } finally {
102205
+ stopLoading();
101849
102206
  }
101850
102207
  },
101851
102208
  children: /* @__PURE__ */ jsx(
@@ -101854,14 +102211,14 @@ function PhotosPanel() {
101854
102211
  src: item.image,
101855
102212
  height: 300,
101856
102213
  width: 300,
101857
- alt: "stock",
102214
+ alt: item.tags?.[0] || "stock",
101858
102215
  className: "object-cover w-full h-full group-hover:scale-105 transition-transform"
101859
102216
  }
101860
102217
  )
101861
102218
  },
101862
102219
  i
101863
102220
  );
101864
- }) }) })
102221
+ }) : !isLoading ? /* @__PURE__ */ jsx("div", { className: "col-span-full py-8 text-center text-xs text-gray-500", children: "No images found" }) : null }) })
101865
102222
  ] });
101866
102223
  }
101867
102224
  async function addBackgroundImage(url) {
@@ -101876,7 +102233,7 @@ async function addBackgroundImage(url) {
101876
102233
  const bounds = workarea.getBoundingRect();
101877
102234
  const centerX = bounds.left + bounds.width / 2;
101878
102235
  const centerY = bounds.top + bounds.height / 2;
101879
- oa.fromURL(url, { crossOrigin: "anonymous" }).then((img) => {
102236
+ oa.fromURL(url + "?t=vb", { crossOrigin: "anonymous" }).then((img) => {
101880
102237
  img.set({
101881
102238
  left: centerX,
101882
102239
  top: centerY,
@@ -101912,319 +102269,288 @@ async function addBackgroundColor(color) {
101912
102269
  workarea.set("fill", color);
101913
102270
  canvas2.requestRenderAll();
101914
102271
  }
102272
+ function normalizeHexColor(c2) {
102273
+ if (!c2 || typeof c2 !== "string" || c2 === "none" || c2 === "transparent" || c2.trim() === "") return null;
102274
+ const trimmed = c2.trim();
102275
+ try {
102276
+ const hex = new Ie(trimmed).toHex();
102277
+ if (hex && hex.length === 6) {
102278
+ return `#${hex.toLowerCase()}`;
102279
+ }
102280
+ } catch {
102281
+ }
102282
+ if (trimmed.startsWith("#")) {
102283
+ let raw = trimmed.replace(/^#/, "");
102284
+ if (raw.length === 3) raw = raw[0] + raw[0] + raw[1] + raw[1] + raw[2] + raw[2];
102285
+ if (raw.length === 6) return `#${raw.toLowerCase()}`;
102286
+ }
102287
+ return null;
102288
+ }
102289
+ function extractAllTargetColors(targets) {
102290
+ if (!targets || !targets.length) return [];
102291
+ const colors = [];
102292
+ const seen = /* @__PURE__ */ new Set();
102293
+ const addColor = (raw) => {
102294
+ const norm = normalizeHexColor(raw);
102295
+ if (norm && !seen.has(norm)) {
102296
+ seen.add(norm);
102297
+ colors.push(norm);
102298
+ }
102299
+ };
102300
+ const traverse = (obj) => {
102301
+ if (!obj || obj.name === "workarea") return;
102302
+ if (obj.type === "image" && obj.name !== "qr" && obj.name !== "barcode" && !obj._objects) {
102303
+ return;
102304
+ }
102305
+ if (obj.type === "textbox" || obj.type === "i-text" || obj.type === "text") {
102306
+ const fill22 = obj.get?.("fill") ?? obj.fill;
102307
+ if (typeof fill22 === "string" && fill22 !== "none" && fill22 !== "transparent" && fill22 !== "") {
102308
+ addColor(fill22);
102309
+ }
102310
+ return;
102311
+ }
102312
+ if (obj.type === "group" && obj.name === "qr") {
102313
+ if (obj.__qrDark) addColor(obj.__qrDark);
102314
+ if (obj.__qrLight) addColor(obj.__qrLight);
102315
+ return;
102316
+ }
102317
+ if (obj.type === "group" && obj.name === "barcode") {
102318
+ if (obj.__barcodeForeground) addColor(obj.__barcodeForeground);
102319
+ if (obj.__barcodeBackground) addColor(obj.__barcodeBackground);
102320
+ return;
102321
+ }
102322
+ if (obj.type === "group" || obj._objects) {
102323
+ const children = obj.getObjects ? obj.getObjects() : obj._objects || [];
102324
+ children.forEach(traverse);
102325
+ return;
102326
+ }
102327
+ const fill2 = obj.get?.("fill") ?? obj.fill;
102328
+ if (typeof fill2 === "string" && fill2 !== "none" && fill2 !== "transparent" && fill2 !== "") {
102329
+ addColor(fill2);
102330
+ }
102331
+ const stroke2 = obj.get?.("stroke") ?? obj.stroke;
102332
+ const strokeWidth = obj.get?.("strokeWidth") ?? obj.strokeWidth;
102333
+ if (typeof stroke2 === "string" && stroke2 !== "none" && stroke2 !== "transparent" && stroke2 !== "" && (typeof strokeWidth !== "number" || strokeWidth > 0)) {
102334
+ addColor(stroke2);
102335
+ }
102336
+ };
102337
+ targets.forEach(traverse);
102338
+ return colors;
102339
+ }
101915
102340
  function extractTargetColor(targets) {
101916
102341
  if (!targets.length) return {};
102342
+ const allColors = extractAllTargetColors(targets);
102343
+ const primaryColor = allColors[0] || void 0;
101917
102344
  const obj = targets[0];
101918
- let color;
101919
- let cmyk2 = obj.__cmyk;
101920
- if (obj.type === "group") {
101921
- if (obj.name === "qr" && obj.__qrDark) {
101922
- color = obj.__qrDark;
101923
- if (obj.__qrDarkCmyk) cmyk2 = obj.__qrDarkCmyk;
101924
- } else if (obj.name === "barcode" && obj.__barcodeForeground) {
101925
- color = obj.__barcodeForeground;
101926
- if (obj.__barcodeFgCmyk) cmyk2 = obj.__barcodeFgCmyk;
101927
- } else {
101928
- const colors = extractSVGColors(obj);
101929
- if (colors.length > 0) color = colors[0];
101930
- }
101931
- } else {
101932
- const fill2 = obj.get?.("fill") ?? obj.fill;
101933
- if (typeof fill2 === "string") color = fill2;
102345
+ let cmyk2 = obj?.__cmyk;
102346
+ if (primaryColor && !cmyk2) {
102347
+ cmyk2 = hexToCmyk(primaryColor);
101934
102348
  }
101935
- if (cmyk2 && !color) {
101936
- color = cmykToHex(cmyk2.c, cmyk2.m, cmyk2.y, cmyk2.k);
101937
- } else if (color && !cmyk2) {
101938
- cmyk2 = hexToCmyk(color);
101939
- }
101940
- const opacity = typeof obj.opacity === "number" ? Math.round(obj.opacity * 100) : void 0;
101941
- return { color, opacity, cmyk: cmyk2 };
102349
+ const opacity = typeof obj?.opacity === "number" ? Math.round(obj.opacity * 100) : void 0;
102350
+ return { color: primaryColor, opacity, cmyk: cmyk2 };
101942
102351
  }
101943
- function applyFillToFabricObjects(objects, newColor, alpha, currentColor, canvas2, cmykValues) {
102352
+ function applyFillToFabricObjects(objects, newColor, alpha, targetOldColor, canvas2, cmykValues) {
101944
102353
  if (!canvas2 || !objects.length) return;
101945
- const resolvedCmyk = cmykValues || hexToCmyk(newColor);
102354
+ const targetNewHex = normalizeHexColor(newColor) || newColor.toLowerCase();
102355
+ const targetOldHex = targetOldColor ? normalizeHexColor(targetOldColor) : null;
102356
+ const resolvedCmyk = cmykValues || hexToCmyk(targetNewHex);
101946
102357
  objects.forEach((obj) => {
101947
- obj.set("opacity", alpha / 100);
101948
- obj.set("__cmyk", { ...resolvedCmyk });
101949
- if (obj.type === "textbox" || obj.type === "i-text") {
101950
- obj.set("fill", newColor);
101951
- } else if (obj.type === "group" && obj.name === "qr") {
101952
- updateQRObjectColors(obj, newColor, obj.__qrLight || "#ffffff");
101953
- } else if (obj.type === "group" && obj.name === "barcode") {
101954
- updateBarcodeObjectColors(obj, newColor, obj.__barcodeBackground || "#ffffff");
101955
- } else if (obj.type === "group") {
101956
- const applyColorRecursive = (child) => {
101957
- child.set("fill", newColor);
101958
- if (child.type === "group" && child._objects) {
101959
- child._objects.forEach((grandchild) => applyColorRecursive(grandchild));
102358
+ if (typeof alpha === "number") {
102359
+ obj.set("opacity", alpha / 100);
102360
+ }
102361
+ if (obj.type === "group" && obj.name === "qr") {
102362
+ const dark = normalizeHexColor(obj.__qrDark) || "#000000";
102363
+ const light = normalizeHexColor(obj.__qrLight) || "#ffffff";
102364
+ let newDark = dark;
102365
+ let newLight = light;
102366
+ if (targetOldHex) {
102367
+ if (dark === targetOldHex) newDark = targetNewHex;
102368
+ if (light === targetOldHex) newLight = targetNewHex;
102369
+ } else {
102370
+ newDark = targetNewHex;
102371
+ }
102372
+ updateQRObjectColors(obj, newDark, newLight);
102373
+ return;
102374
+ }
102375
+ if (obj.type === "group" && obj.name === "barcode") {
102376
+ const fg = normalizeHexColor(obj.__barcodeForeground) || "#000000";
102377
+ const bg = normalizeHexColor(obj.__barcodeBackground) || "#ffffff";
102378
+ let newFg = fg;
102379
+ let newBg = bg;
102380
+ if (targetOldHex) {
102381
+ if (fg === targetOldHex) newFg = targetNewHex;
102382
+ if (bg === targetOldHex) newBg = targetNewHex;
102383
+ } else {
102384
+ newFg = targetNewHex;
102385
+ }
102386
+ updateBarcodeObjectColors(obj, newFg, newBg);
102387
+ return;
102388
+ }
102389
+ if (obj.type === "textbox" || obj.type === "i-text" || obj.type === "text") {
102390
+ obj.set("fill", targetNewHex);
102391
+ obj.set("__cmyk", { ...resolvedCmyk });
102392
+ obj.setCoords();
102393
+ return;
102394
+ }
102395
+ if (obj.type === "group" || obj._objects) {
102396
+ const replaceRecursive = (child) => {
102397
+ if (!child) return;
102398
+ if (typeof child.fill === "string") {
102399
+ const childFillHex = normalizeHexColor(child.fill);
102400
+ if (!targetOldHex || childFillHex === targetOldHex) {
102401
+ child.set("fill", targetNewHex);
102402
+ child.__cmyk = { ...resolvedCmyk };
102403
+ }
102404
+ }
102405
+ if (typeof child.stroke === "string") {
102406
+ const childStrokeHex = normalizeHexColor(child.stroke);
102407
+ if (targetOldHex && childStrokeHex === targetOldHex) {
102408
+ child.set("stroke", targetNewHex);
102409
+ child.__cmyk = { ...resolvedCmyk };
102410
+ }
102411
+ }
102412
+ if (child.type === "group" || child._objects) {
102413
+ const sub = child.getObjects ? child.getObjects() : child._objects || [];
102414
+ sub.forEach(replaceRecursive);
101960
102415
  }
101961
102416
  };
101962
- if (obj._objects) {
101963
- obj._objects.forEach((child) => applyColorRecursive(child));
101964
- }
102417
+ const children = obj.getObjects ? obj.getObjects() : obj._objects || [];
102418
+ children.forEach(replaceRecursive);
101965
102419
  } else {
101966
- obj.set("fill", newColor);
102420
+ const currentFillHex = normalizeHexColor(obj.fill);
102421
+ if (!targetOldHex || currentFillHex === targetOldHex) {
102422
+ obj.set("fill", targetNewHex);
102423
+ obj.set("__cmyk", { ...resolvedCmyk });
102424
+ }
102425
+ const currentStrokeHex = normalizeHexColor(obj.stroke);
102426
+ if (targetOldHex && currentStrokeHex === targetOldHex) {
102427
+ obj.set("stroke", targetNewHex);
102428
+ obj.set("__cmyk", { ...resolvedCmyk });
102429
+ }
101967
102430
  }
101968
102431
  obj.setCoords();
101969
102432
  });
101970
102433
  canvas2.requestRenderAll();
101971
102434
  }
101972
- const useDraggable = (onDrag, onEnd) => {
101973
- return (e3) => {
101974
- const startX = e3.clientX;
101975
- const startY = e3.clientY;
101976
- onDrag(startX, startY);
101977
- const handleMove = (e22) => onDrag(e22.clientX, e22.clientY);
101978
- const handleUp = () => {
101979
- document.removeEventListener("mousemove", handleMove);
101980
- document.removeEventListener("mouseup", handleUp);
101981
- onEnd?.();
101982
- };
101983
- document.addEventListener("mousemove", handleMove);
101984
- document.addEventListener("mouseup", handleUp);
101985
- };
101986
- };
101987
- function GradientSelector({
101988
- hsv,
101989
- hueColor,
101990
- refEl,
101991
- onChange
101992
- }) {
102435
+ const STEP$1 = 1;
102436
+ const STEP_LARGE = 10;
102437
+ function ChannelSlider({ value, min, max, gradient, ariaLabel, onChange, onCommit, className, thumbClassName }) {
102438
+ const trackRef = useRef(null);
102439
+ function pick(event) {
102440
+ const element = trackRef.current;
102441
+ if (!element) return;
102442
+ const bounds = element.getBoundingClientRect();
102443
+ const positionRatio = Math.max(0, Math.min(1, (event.clientX - bounds.left) / bounds.width));
102444
+ onChange(Math.round(min + positionRatio * (max - min)));
102445
+ }
102446
+ function nudge(event) {
102447
+ let next = value;
102448
+ switch (event.key) {
102449
+ case "ArrowRight":
102450
+ case "ArrowUp":
102451
+ next += event.shiftKey ? STEP_LARGE : STEP$1;
102452
+ break;
102453
+ case "ArrowLeft":
102454
+ case "ArrowDown":
102455
+ next -= event.shiftKey ? STEP_LARGE : STEP$1;
102456
+ break;
102457
+ case "PageUp":
102458
+ next += STEP_LARGE;
102459
+ break;
102460
+ case "PageDown":
102461
+ next -= STEP_LARGE;
102462
+ break;
102463
+ case "Home":
102464
+ next = min;
102465
+ break;
102466
+ case "End":
102467
+ next = max;
102468
+ break;
102469
+ default:
102470
+ return;
102471
+ }
102472
+ event.preventDefault();
102473
+ next = Math.max(min, Math.min(max, next));
102474
+ if (next !== value) onChange(next);
102475
+ }
102476
+ const percent = (value - min) / (max - min) * 100;
101993
102477
  return /* @__PURE__ */ jsx(
101994
102478
  "div",
101995
102479
  {
101996
- ref: refEl,
101997
- className: "w-full h-48 mb-3 rounded cursor-crosshair relative",
101998
- style: {
101999
- background: `linear-gradient(to bottom, transparent, black), linear-gradient(to right, white, ${hueColor})`
102480
+ ref: trackRef,
102481
+ className: className ? `pqc-slider ${className}` : "pqc-slider",
102482
+ style: { ["--pqc-_grad"]: gradient },
102483
+ role: "slider",
102484
+ tabIndex: 0,
102485
+ "aria-label": ariaLabel,
102486
+ "aria-valuemin": min,
102487
+ "aria-valuemax": max,
102488
+ "aria-valuenow": value,
102489
+ onKeyDown: nudge,
102490
+ onPointerDown: (event) => {
102491
+ event.currentTarget.setPointerCapture(event.pointerId);
102492
+ pick(event);
102000
102493
  },
102001
- onMouseDown: useDraggable((x2, y2) => {
102002
- if (!refEl.current) return;
102003
- const rect = refEl.current.getBoundingClientRect();
102004
- const s2 = Math.max(0, Math.min(x2 - rect.left, rect.width)) / rect.width * 100;
102005
- const v2 = 100 - Math.max(0, Math.min(y2 - rect.top, rect.height)) / rect.height * 100;
102006
- onChange({ ...hsv, s: s2, v: v2 });
102007
- }),
102008
- children: /* @__PURE__ */ jsx(
102009
- "div",
102010
- {
102011
- className: "absolute w-4 h-4 border-2 border-white rounded-full shadow-lg pointer-events-none",
102012
- style: {
102013
- left: `${hsv.s}%`,
102014
- top: `${100 - hsv.v}%`,
102015
- transform: "translate(-50%, -50%)"
102016
- }
102017
- }
102018
- )
102019
- }
102020
- );
102021
- }
102022
- function HueSlider({ hsv, refEl, onChange }) {
102023
- return /* @__PURE__ */ jsx("div", { className: "mb-2", children: /* @__PURE__ */ jsx(
102024
- "div",
102025
- {
102026
- ref: refEl,
102027
- className: "w-full h-4 rounded-full cursor-pointer relative",
102028
- style: {
102029
- background: "linear-gradient(to right, #ff0000, #ffff00, #00ff00, #00ffff, #0000ff, #ff00ff, #ff0000)"
102494
+ onPointerMove: (event) => {
102495
+ if (event.buttons === 0) return;
102496
+ pick(event);
102030
102497
  },
102031
- onMouseDown: useDraggable((x2) => {
102032
- if (!refEl.current) return;
102033
- const rect = refEl.current.getBoundingClientRect();
102034
- const h2 = Math.max(0, Math.min(x2 - rect.left, rect.width)) / rect.width * 360;
102035
- onChange({ ...hsv, h: h2 });
102036
- }),
102037
- children: /* @__PURE__ */ jsx(
102038
- "div",
102039
- {
102040
- className: "absolute top-1/2 w-5 h-5 bg-white border-2 border-gray-400 rounded-full shadow-lg pointer-events-none",
102041
- style: {
102042
- left: `${hsv.h / 360 * 100}%`,
102043
- transform: "translate(-50%, -50%)"
102044
- }
102045
- }
102046
- )
102047
- }
102048
- ) });
102049
- }
102050
- function AlphaSlider({
102051
- alpha,
102052
- color,
102053
- refEl,
102054
- setAlpha,
102055
- canvas: canvas2,
102056
- targets,
102057
- saveState
102058
- }) {
102059
- return /* @__PURE__ */ jsx("div", { className: "mt-2", children: /* @__PURE__ */ jsxs(
102060
- "div",
102061
- {
102062
- ref: refEl,
102063
- className: "w-full h-4 rounded-full cursor-pointer relative overflow-hidden",
102064
- onMouseDown: useDraggable(
102065
- (x2) => {
102066
- if (!refEl.current) return;
102067
- const rect = refEl.current.getBoundingClientRect();
102068
- const newAlpha = Math.round(
102069
- Math.max(0, Math.min(x2 - rect.left, rect.width)) / rect.width * 100
102070
- );
102071
- setAlpha(newAlpha);
102072
- if (canvas2 && targets.length) {
102073
- targets.forEach((obj) => {
102074
- obj.set("opacity", newAlpha / 100);
102075
- obj.setCoords();
102076
- });
102077
- canvas2.requestRenderAll();
102078
- }
102079
- },
102080
- saveState
102081
- ),
102082
- children: [
102083
- /* @__PURE__ */ jsx(
102084
- "div",
102085
- {
102086
- className: "absolute inset-0",
102087
- style: {
102088
- backgroundImage: `
102089
- linear-gradient(45deg, #ccc 25%, transparent 25%),
102090
- linear-gradient(-45deg, #ccc 25%, transparent 25%),
102091
- linear-gradient(45deg, transparent 75%, #ccc 75%),
102092
- linear-gradient(-45deg, transparent 75%, #ccc 75%)
102093
- `,
102094
- backgroundSize: "8px 8px",
102095
- backgroundPosition: "0 0, 0 4px, 4px -4px, -4px 0px"
102096
- }
102097
- }
102098
- ),
102099
- /* @__PURE__ */ jsx(
102100
- "div",
102101
- {
102102
- className: "absolute inset-0",
102103
- style: {
102104
- background: `linear-gradient(to right, transparent, ${color})`
102105
- }
102106
- }
102107
- ),
102108
- /* @__PURE__ */ jsx(
102109
- "div",
102110
- {
102111
- className: "absolute top-1/2 w-5 h-5 bg-white border-2 border-gray-400 rounded-full shadow-lg pointer-events-none",
102112
- style: {
102113
- left: `${alpha}%`,
102114
- transform: "translate(-50%, -50%)"
102115
- }
102116
- }
102117
- )
102118
- ]
102498
+ onPointerUp: () => onCommit?.(),
102499
+ onBlur: () => onCommit?.(),
102500
+ children: /* @__PURE__ */ jsx("div", { className: thumbClassName ? `pqc-slider-thumb ${thumbClassName}` : "pqc-slider-thumb", style: { ["--pqc-_x"]: `${percent}%` } })
102119
102501
  }
102120
- ) });
102121
- }
102122
- function CMYKInputs({
102123
- cmyk: cmyk2,
102124
- alpha,
102125
- setAlpha,
102126
- updateColor,
102127
- canvas: canvas2,
102128
- targets,
102129
- saveState
102130
- }) {
102131
- const inputClass = "w-full px-1.5 py-1.5 text-xs border border-gray-300 rounded text-center focus:outline-none focus:ring-1 focus:ring-cyan-500 focus:border-cyan-500";
102132
- return /* @__PURE__ */ jsxs("div", { className: "mt-3", children: [
102133
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 mb-2", children: [
102134
- /* @__PURE__ */ jsx("span", { className: "text-[10px] font-bold tracking-wider text-gray-500 uppercase", children: "CMYK" }),
102135
- /* @__PURE__ */ jsx("span", { className: "text-[10px] text-gray-400", children: "Print Values (0–100%)" })
102136
- ] }),
102137
- /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-5 gap-1.5", children: [
102138
- ["c", "m", "y", "k"].map((key) => /* @__PURE__ */ jsxs("div", { children: [
102139
- /* @__PURE__ */ jsx(
102140
- "input",
102141
- {
102142
- type: "number",
102143
- min: 0,
102144
- max: 100,
102145
- value: cmyk2[key],
102146
- onChange: (e3) => {
102147
- const val = Math.max(0, Math.min(100, +e3.target.value || 0));
102148
- updateColor({ ...cmyk2, [key]: val });
102149
- },
102150
- className: inputClass
102151
- }
102152
- ),
102153
- /* @__PURE__ */ jsx("label", { className: "text-[10px] text-gray-500 block mt-0.5 text-center font-semibold", children: key.toUpperCase() })
102154
- ] }, key)),
102155
- /* @__PURE__ */ jsxs("div", { children: [
102156
- /* @__PURE__ */ jsx(
102157
- "input",
102158
- {
102159
- type: "number",
102160
- min: 0,
102161
- max: 100,
102162
- value: alpha,
102163
- onChange: (e3) => {
102164
- const newAlpha = Math.max(0, Math.min(100, +e3.target.value));
102165
- setAlpha(newAlpha);
102166
- if (canvas2 && targets.length) {
102167
- targets.forEach((obj) => {
102168
- obj.set("opacity", newAlpha / 100);
102169
- obj.setCoords();
102170
- });
102171
- canvas2.requestRenderAll();
102172
- saveState();
102173
- }
102174
- },
102175
- className: inputClass
102176
- }
102177
- ),
102178
- /* @__PURE__ */ jsx("label", { className: "text-[10px] text-gray-500 block mt-0.5 text-center font-semibold", children: "A" })
102179
- ] })
102180
- ] })
102181
- ] });
102502
+ );
102182
102503
  }
102183
- function ColorPreviewBox({ color, alpha }) {
102184
- const outOfGamut = isOutOfCmykGamut(color);
102185
- return /* @__PURE__ */ jsx("div", { className: "flex items-start gap-3 mb-3", children: /* @__PURE__ */ jsxs("div", { className: "relative w-12 h-12 rounded border border-gray-300 flex-shrink-0", children: [
102186
- /* @__PURE__ */ jsx(
102187
- "div",
102188
- {
102189
- className: "absolute inset-0 rounded",
102190
- style: {
102191
- backgroundImage: `
102192
- linear-gradient(45deg, #ccc 25%, transparent 25%),
102193
- linear-gradient(-45deg, #ccc 25%, transparent 25%),
102194
- linear-gradient(45deg, transparent 75%, #ccc 75%),
102195
- linear-gradient(-45deg, transparent 75%, #ccc 75%)
102196
- `,
102197
- backgroundSize: "8px 8px",
102198
- backgroundPosition: "0 0, 0 4px, 4px -4px, -4px 0px"
102199
- }
102200
- }
102201
- ),
102504
+ function ChannelRow({ label, labelColor, ariaLabel, value, min, max, gradient, onChange, onCommit, sliderClassName, sliderThumbClassName }) {
102505
+ const [rawInput, setRawInput] = useState(String(value));
102506
+ useEffect(() => {
102507
+ setRawInput(String(value));
102508
+ }, [value]);
102509
+ function commit(rawValue) {
102510
+ const parsedValue = parseInt(rawValue, 10);
102511
+ if (!isNaN(parsedValue)) onChange(Math.max(min, Math.min(max, parsedValue)));
102512
+ setRawInput(String(value));
102513
+ onCommit?.("input");
102514
+ }
102515
+ return /* @__PURE__ */ jsxs("div", { className: "pqc-channel-row", children: [
102516
+ /* @__PURE__ */ jsx("span", { className: "pqc-channel-label", style: { color: labelColor }, "aria-hidden": "true", children: label }),
102517
+ /* @__PURE__ */ jsx(ChannelSlider, { value, min, max, gradient, ariaLabel, onChange, onCommit: () => onCommit?.("slider"), className: sliderClassName, thumbClassName: sliderThumbClassName }),
102202
102518
  /* @__PURE__ */ jsx(
102203
- "div",
102204
- {
102205
- className: "absolute inset-0 rounded",
102206
- style: {
102207
- background: color,
102208
- opacity: alpha / 100
102209
- }
102210
- }
102211
- ),
102212
- outOfGamut && /* @__PURE__ */ jsx(
102213
- "div",
102519
+ "input",
102214
102520
  {
102215
- className: "absolute -top-1.5 -right-1.5 w-4 h-4 bg-amber-500 rounded-full flex items-center justify-center text-white text-[8px] font-bold shadow-sm cursor-help",
102216
- title: "This color is outside the CMYK gamut. It may look different when printed.",
102217
- children: "!"
102521
+ type: "text",
102522
+ inputMode: "numeric",
102523
+ "aria-label": ariaLabel,
102524
+ value: rawInput,
102525
+ onChange: (event) => {
102526
+ setRawInput(event.target.value);
102527
+ },
102528
+ onBlur: (event) => commit(event.target.value),
102529
+ onKeyDown: (event) => {
102530
+ if (event.key === "Enter") commit(event.target.value);
102531
+ },
102532
+ className: "pqc-channel-input"
102218
102533
  }
102219
102534
  )
102220
- ] }) });
102535
+ ] });
102221
102536
  }
102537
+ const DEFAULT_COLORS = ["#f97316", "#ef4444", "#22c55e", "#3b82f6", "#a855f7", "#ec4899", "#000000", "#ffffff"];
102538
+ const CMYK_CHANNELS$1 = [
102539
+ { key: "c", label: "C", color: "#009fe3", name: "Cyan" },
102540
+ { key: "m", label: "M", color: "#e6007e", name: "Magenta" },
102541
+ { key: "y", label: "Y", color: "#dcb000", name: "Yellow" },
102542
+ { key: "k", label: "K", color: "#222222", name: "Black" }
102543
+ ];
102222
102544
  function CMYKColorPicker({ value, onChange, className = "" } = {}) {
102223
102545
  const canvas2 = useEditorStore((s2) => s2.canvas);
102224
102546
  const saveState = useHistoryStore((s2) => s2.saveState);
102225
- const { objects } = useSelectionStore();
102226
- const targets = objects.filter((o2) => o2.name !== "workarea");
102547
+ const { objects, activeColor, activeColorIndex, setActiveColor } = useSelectionStore();
102548
+ const targets = useMemo(() => objects.filter((o2) => o2.name !== "workarea"), [objects]);
102227
102549
  const isControlled = typeof onChange === "function";
102550
+ const allTargetColors = useMemo(() => {
102551
+ if (isControlled) return [];
102552
+ return extractAllTargetColors(targets);
102553
+ }, [isControlled, targets, objects]);
102228
102554
  const initialCmyk = typeof value === "object" && value !== null ? value : typeof value === "string" ? hexToCmyk(value) : { c: 0, m: 100, y: 100, k: 0 };
102229
102555
  const initialHex = typeof value === "string" ? value : cmykToHex(initialCmyk.c, initialCmyk.m, initialCmyk.y, initialCmyk.k);
102230
102556
  const [color, setColor] = useState(initialHex);
@@ -102234,17 +102560,32 @@ function CMYKColorPicker({ value, onChange, className = "" } = {}) {
102234
102560
  return rgbToHsv(rgb2.r, rgb2.g, rgb2.b);
102235
102561
  });
102236
102562
  const [alpha, setAlpha] = useState(100);
102563
+ const [mode2, setMode] = useState("HEX");
102237
102564
  const [open2, setOpen] = useState(false);
102565
+ const [copied, setCopied] = useState(false);
102566
+ const [recentColors, setRecentColors] = useState([
102567
+ "#e11d48",
102568
+ "#9f1239",
102569
+ "#881337",
102570
+ "#a855f7",
102571
+ "#c084fc",
102572
+ "#475569",
102573
+ "#334155",
102574
+ "#22c55e",
102575
+ "#16a34a",
102576
+ "#6366f1",
102577
+ "#d97706"
102578
+ ]);
102238
102579
  const [coords, setCoords] = useState({ top: 0, left: 0 });
102239
102580
  const containerRef = useRef(null);
102240
102581
  const popoverRef = useRef(null);
102241
- const gradientRef = useRef(null);
102582
+ const svRef = useRef(null);
102242
102583
  const hueRef = useRef(null);
102243
102584
  const alphaRef = useRef(null);
102244
102585
  const updatePosition = useCallback(() => {
102245
102586
  if (!containerRef.current) return;
102246
102587
  const rect = containerRef.current.getBoundingClientRect();
102247
- const pickerW = 300, pickerH = 440;
102588
+ const pickerW = 270, pickerH = 430;
102248
102589
  let left = Math.max(10, Math.min(rect.left, window.innerWidth - pickerW - 10));
102249
102590
  let top = rect.bottom + 6;
102250
102591
  if (top + pickerH > window.innerHeight - 10) {
@@ -102255,29 +102596,23 @@ function CMYKColorPicker({ value, onChange, className = "" } = {}) {
102255
102596
  useEffect(() => {
102256
102597
  if (!open2) return;
102257
102598
  updatePosition();
102599
+ const close = (e3) => {
102600
+ const t2 = e3.target;
102601
+ if (containerRef.current?.contains(t2) || popoverRef.current?.contains(t2)) return;
102602
+ setOpen(false);
102603
+ };
102258
102604
  window.addEventListener("resize", updatePosition);
102259
- window.addEventListener("scroll", updatePosition, true);
102605
+ document.addEventListener("mousedown", close, { capture: true });
102260
102606
  return () => {
102261
102607
  window.removeEventListener("resize", updatePosition);
102262
- window.removeEventListener("scroll", updatePosition, true);
102608
+ document.removeEventListener("mousedown", close, { capture: true });
102263
102609
  };
102264
102610
  }, [open2, updatePosition]);
102265
- useEffect(() => {
102266
- if (!open2) return;
102267
- const handleClickOutside = (e3) => {
102268
- const t2 = e3.target;
102269
- if (containerRef.current?.contains(t2) || popoverRef.current?.contains(t2)) return;
102270
- setOpen(false);
102271
- };
102272
- document.addEventListener("mousedown", handleClickOutside, { capture: true });
102273
- return () => document.removeEventListener("mousedown", handleClickOutside, { capture: true });
102274
- }, [open2]);
102275
102611
  useEffect(() => {
102276
102612
  if (isControlled && value) {
102277
102613
  if (typeof value === "object") {
102278
102614
  const rgb2 = cmykToRgb(value.c, value.m, value.y, value.k);
102279
- const hex = rgbToHex(rgb2.r, rgb2.g, rgb2.b);
102280
- setColor(hex);
102615
+ setColor(rgbToHex(rgb2.r, rgb2.g, rgb2.b));
102281
102616
  setCmyk(value);
102282
102617
  setHsv(rgbToHsv(rgb2.r, rgb2.g, rgb2.b));
102283
102618
  } else if (typeof value === "string") {
@@ -102294,35 +102629,32 @@ function CMYKColorPicker({ value, onChange, className = "" } = {}) {
102294
102629
  setOpen(false);
102295
102630
  return;
102296
102631
  }
102297
- const { color: targetColor, opacity: targetOpacity, cmyk: targetCmyk } = extractTargetColor(targets);
102298
- if (targetCmyk) {
102299
- setCmyk(targetCmyk);
102300
- const rgb2 = cmykToRgb(targetCmyk.c, targetCmyk.m, targetCmyk.y, targetCmyk.k);
102301
- const hex = rgbToHex(rgb2.r, rgb2.g, rgb2.b);
102302
- setColor(hex);
102632
+ const { color: tColor, opacity: tOpacity, cmyk: tCmyk } = extractTargetColor(targets);
102633
+ if (tCmyk) {
102634
+ setCmyk(tCmyk);
102635
+ const rgb2 = cmykToRgb(tCmyk.c, tCmyk.m, tCmyk.y, tCmyk.k);
102636
+ setColor(rgbToHex(rgb2.r, rgb2.g, rgb2.b));
102303
102637
  setHsv(rgbToHsv(rgb2.r, rgb2.g, rgb2.b));
102304
- } else if (targetColor && targetColor !== color) {
102305
- const rgb2 = hexToRgb(targetColor);
102306
- setColor(targetColor);
102638
+ } else if (tColor && tColor !== color) {
102639
+ const rgb2 = hexToRgb(tColor);
102640
+ setColor(tColor);
102307
102641
  setHsv(rgbToHsv(rgb2.r, rgb2.g, rgb2.b));
102308
102642
  setCmyk(rgbToCmyk(rgb2.r, rgb2.g, rgb2.b));
102309
102643
  }
102310
- if (typeof targetOpacity === "number" && targetOpacity !== alpha) {
102311
- setAlpha(targetOpacity);
102312
- }
102644
+ if (typeof tOpacity === "number" && tOpacity !== alpha) setAlpha(tOpacity);
102313
102645
  }, [isControlled, objects]);
102314
- const applyColor = useCallback(
102646
+ const applyControlledColor = useCallback(
102315
102647
  (newColor, newCmyk) => {
102316
102648
  const finalCmyk = newCmyk || cmyk2;
102317
102649
  setColor(newColor);
102318
102650
  if (isControlled && onChange) {
102319
102651
  onChange(newColor, finalCmyk);
102320
102652
  } else if (canvas2 && targets.length) {
102321
- applyFillToFabricObjects(targets, newColor, alpha, color, canvas2, finalCmyk);
102653
+ applyFillToFabricObjects(targets, newColor, alpha, activeColor || color, canvas2, finalCmyk);
102322
102654
  saveState();
102323
102655
  }
102324
102656
  },
102325
- [isControlled, onChange, canvas2, targets, alpha, color, cmyk2, saveState]
102657
+ [isControlled, onChange, canvas2, targets, alpha, color, activeColor, cmyk2, saveState]
102326
102658
  );
102327
102659
  const updateFromHsv = (newHsv) => {
102328
102660
  const rgb2 = hsvToRgb(newHsv.h, newHsv.s, newHsv.v);
@@ -102330,25 +102662,76 @@ function CMYKColorPicker({ value, onChange, className = "" } = {}) {
102330
102662
  const hex = rgbToHex(rgb2.r, rgb2.g, rgb2.b);
102331
102663
  setHsv(newHsv);
102332
102664
  setCmyk(newC);
102333
- applyColor(hex, newC);
102665
+ applyControlledColor(hex, newC);
102334
102666
  };
102335
102667
  const updateFromCmyk = (newCmyk) => {
102336
102668
  const rgb2 = cmykToRgb(newCmyk.c, newCmyk.m, newCmyk.y, newCmyk.k);
102337
102669
  const hex = rgbToHex(rgb2.r, rgb2.g, rgb2.b);
102338
102670
  setCmyk(newCmyk);
102339
102671
  setHsv(rgbToHsv(rgb2.r, rgb2.g, rgb2.b));
102340
- applyColor(hex, newCmyk);
102672
+ applyControlledColor(hex, newCmyk);
102673
+ };
102674
+ const updateFromHex = (inputHex) => {
102675
+ let clean = inputHex.trim().replace(/^#/, "");
102676
+ if (clean.length === 3) clean = clean[0] + clean[0] + clean[1] + clean[1] + clean[2] + clean[2];
102677
+ if (/^[0-9a-fA-F]{6}$/.test(clean)) {
102678
+ const hex = `#${clean.toLowerCase()}`;
102679
+ const rgb2 = hexToRgb(hex);
102680
+ const newC = rgbToCmyk(rgb2.r, rgb2.g, rgb2.b);
102681
+ setHsv(rgbToHsv(rgb2.r, rgb2.g, rgb2.b));
102682
+ setCmyk(newC);
102683
+ applyControlledColor(hex, newC);
102684
+ setRecentColors((prev) => [hex, ...prev.filter((c2) => c2.toLowerCase() !== hex)].slice(0, 16));
102685
+ }
102341
102686
  };
102342
- if (!isControlled && !targets.length) return null;
102687
+ const handleCopy = () => {
102688
+ if (navigator.clipboard) {
102689
+ navigator.clipboard.writeText(color);
102690
+ setCopied(true);
102691
+ setTimeout(() => setCopied(false), 2e3);
102692
+ }
102693
+ };
102694
+ const handleEyeDropper = async () => {
102695
+ if (typeof window === "undefined" || !("EyeDropper" in window)) return;
102696
+ try {
102697
+ const result = await new window.EyeDropper().open();
102698
+ if (result?.sRGBHex) updateFromHex(result.sRGBHex);
102699
+ } catch {
102700
+ }
102701
+ };
102702
+ if (!isControlled) {
102703
+ if (!targets.length || !allTargetColors.length) return null;
102704
+ return /* @__PURE__ */ jsx("div", { className: `flex items-center gap-1.5 ${className}`, children: allTargetColors.map((c2, idx) => {
102705
+ const isSelected = activeColor ? activeColor.toLowerCase() === c2.toLowerCase() : activeColorIndex === idx || idx === 0 && !activeColor;
102706
+ return /* @__PURE__ */ jsx(
102707
+ "button",
102708
+ {
102709
+ type: "button",
102710
+ onClick: () => {
102711
+ setActiveColor(c2, idx);
102712
+ const { activePanel, setActivePanel } = useSidePanelStore.getState();
102713
+ if (activePanel !== "color") {
102714
+ setActivePanel("color");
102715
+ }
102716
+ },
102717
+ className: `w-7 h-7 rounded-md border shadow-xs transition-transform hover:scale-105 cursor-pointer relative ${isSelected ? "border-white ring-2 ring-[#02bc71] scale-105 z-10" : "border-neutral-600 hover:border-neutral-400"}`,
102718
+ style: { backgroundColor: c2 },
102719
+ title: `Edit color ${c2.toUpperCase()}`
102720
+ },
102721
+ c2 + "-" + idx
102722
+ );
102723
+ }) });
102724
+ }
102343
102725
  const hueRgb = hsvToRgb(hsv.h, 100, 100);
102344
102726
  const hueColor = rgbToHex(hueRgb.r, hueRgb.g, hueRgb.b);
102345
102727
  return /* @__PURE__ */ jsxs("div", { className: `relative ${className}`, ref: containerRef, children: [
102346
102728
  /* @__PURE__ */ jsx(
102347
102729
  "div",
102348
102730
  {
102349
- className: "cursor-pointer w-7 h-7 rounded-md border border-neutral-600 shadow-sm",
102731
+ className: "cursor-pointer w-7 h-7 rounded-md border border-neutral-600 shadow-sm transition-transform hover:scale-105",
102350
102732
  style: { background: color },
102351
- onClick: () => setOpen((v2) => !v2)
102733
+ onClick: () => setOpen((v2) => !v2),
102734
+ title: "Color Picker"
102352
102735
  }
102353
102736
  ),
102354
102737
  open2 && createPortal(
@@ -102356,47 +102739,204 @@ function CMYKColorPicker({ value, onChange, className = "" } = {}) {
102356
102739
  "div",
102357
102740
  {
102358
102741
  ref: popoverRef,
102742
+ "data-cmyk-popover": "true",
102359
102743
  style: { position: "fixed", top: `${coords.top}px`, left: `${coords.left}px`, zIndex: 999999 },
102360
- className: "bg-white border border-gray-300 rounded-lg shadow-2xl p-3 w-[290px] text-gray-800",
102744
+ className: "cmyk-picker-popover bg-white rounded-2xl shadow-2xl p-3.5 w-[270px] text-gray-800 border border-gray-100 select-none font-sans",
102361
102745
  children: [
102362
- /* @__PURE__ */ jsxs("div", { className: "flex justify-between items-center mb-3", children: [
102363
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
102364
- /* @__PURE__ */ jsx("h3", { className: "text-gray-800 font-semibold text-sm", children: "Color Picker" }),
102365
- /* @__PURE__ */ jsx("span", { className: "text-[9px] font-bold tracking-wider text-cyan-600 bg-cyan-50 px-1.5 py-0.5 rounded uppercase", children: "CMYK" })
102366
- ] }),
102367
- /* @__PURE__ */ jsx("button", { onClick: () => setOpen(false), className: "text-gray-400 hover:text-gray-600 text-lg leading-none", children: "✕" })
102368
- ] }),
102369
- /* @__PURE__ */ jsx(GradientSelector, { hsv, hueColor, refEl: gradientRef, onChange: updateFromHsv }),
102370
- /* @__PURE__ */ jsxs("div", { className: "flex gap-4", children: [
102371
- /* @__PURE__ */ jsx(ColorPreviewBox, { color, alpha }),
102372
- /* @__PURE__ */ jsxs("div", { className: "flex-1", children: [
102373
- /* @__PURE__ */ jsx(HueSlider, { hsv, refEl: hueRef, onChange: updateFromHsv }),
102374
- !isControlled && /* @__PURE__ */ jsx(
102375
- AlphaSlider,
102746
+ /* @__PURE__ */ jsx(
102747
+ "div",
102748
+ {
102749
+ ref: svRef,
102750
+ className: "w-full h-[135px] rounded-xl relative cursor-crosshair overflow-hidden touch-none",
102751
+ style: { background: `linear-gradient(to top, #000, transparent), linear-gradient(to right, #fff, ${hueColor})` },
102752
+ onPointerDown: (e3) => {
102753
+ e3.currentTarget.setPointerCapture(e3.pointerId);
102754
+ const rect = e3.currentTarget.getBoundingClientRect();
102755
+ updateFromHsv({
102756
+ ...hsv,
102757
+ s: Math.round(Math.max(0, Math.min(1, (e3.clientX - rect.left) / rect.width)) * 100),
102758
+ v: Math.round((1 - Math.max(0, Math.min(1, (e3.clientY - rect.top) / rect.height))) * 100)
102759
+ });
102760
+ },
102761
+ onPointerMove: (e3) => {
102762
+ if (e3.buttons === 0) return;
102763
+ const rect = e3.currentTarget.getBoundingClientRect();
102764
+ updateFromHsv({
102765
+ ...hsv,
102766
+ s: Math.round(Math.max(0, Math.min(1, (e3.clientX - rect.left) / rect.width)) * 100),
102767
+ v: Math.round((1 - Math.max(0, Math.min(1, (e3.clientY - rect.top) / rect.height))) * 100)
102768
+ });
102769
+ },
102770
+ children: /* @__PURE__ */ jsx(
102771
+ "div",
102376
102772
  {
102377
- alpha,
102378
- color,
102379
- refEl: alphaRef,
102380
- setAlpha,
102381
- canvas: canvas2,
102382
- targets,
102383
- saveState
102773
+ className: "w-4 h-4 rounded-full border-2 border-white shadow-md absolute pointer-events-none -translate-x-1/2 -translate-y-1/2",
102774
+ style: { left: `${hsv.s}%`, top: `${100 - hsv.v}%`, backgroundColor: color }
102384
102775
  }
102385
102776
  )
102386
- ] })
102387
- ] }),
102777
+ }
102778
+ ),
102388
102779
  /* @__PURE__ */ jsx(
102389
- CMYKInputs,
102780
+ "div",
102390
102781
  {
102391
- cmyk: cmyk2,
102392
- alpha,
102393
- setAlpha,
102394
- updateColor: updateFromCmyk,
102395
- canvas: canvas2,
102396
- targets,
102397
- saveState
102782
+ ref: hueRef,
102783
+ className: "w-full h-3 rounded-full relative cursor-pointer mt-3 mb-2 touch-none shadow-inner",
102784
+ style: { background: "linear-gradient(to right, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%)" },
102785
+ onPointerDown: (e3) => {
102786
+ e3.currentTarget.setPointerCapture(e3.pointerId);
102787
+ const rect = e3.currentTarget.getBoundingClientRect();
102788
+ updateFromHsv({ ...hsv, h: Math.round(Math.max(0, Math.min(1, (e3.clientX - rect.left) / rect.width)) * 360) % 360 });
102789
+ },
102790
+ onPointerMove: (e3) => {
102791
+ if (e3.buttons === 0) return;
102792
+ const rect = e3.currentTarget.getBoundingClientRect();
102793
+ updateFromHsv({ ...hsv, h: Math.round(Math.max(0, Math.min(1, (e3.clientX - rect.left) / rect.width)) * 360) % 360 });
102794
+ },
102795
+ children: /* @__PURE__ */ jsx("div", { className: "w-4 h-4 rounded-full border-2 border-white shadow-md absolute top-1/2 -translate-y-1/2 -translate-x-1/2 pointer-events-none", style: { left: `${hsv.h / 360 * 100}%`, backgroundColor: hueColor } })
102398
102796
  }
102399
- )
102797
+ ),
102798
+ /* @__PURE__ */ jsxs(
102799
+ "div",
102800
+ {
102801
+ ref: alphaRef,
102802
+ className: "w-full h-3 rounded-full relative cursor-pointer mb-3 touch-none pqc-checkerboard shadow-inner overflow-hidden",
102803
+ onPointerDown: (e3) => {
102804
+ e3.currentTarget.setPointerCapture(e3.pointerId);
102805
+ const rect = e3.currentTarget.getBoundingClientRect();
102806
+ const newAlpha = Math.round(Math.max(0, Math.min(1, (e3.clientX - rect.left) / rect.width)) * 100);
102807
+ setAlpha(newAlpha);
102808
+ if (canvas2 && targets.length) {
102809
+ targets.forEach((obj) => obj.set("opacity", newAlpha / 100));
102810
+ canvas2.requestRenderAll();
102811
+ }
102812
+ },
102813
+ onPointerMove: (e3) => {
102814
+ if (e3.buttons === 0) return;
102815
+ const rect = e3.currentTarget.getBoundingClientRect();
102816
+ const newAlpha = Math.round(Math.max(0, Math.min(1, (e3.clientX - rect.left) / rect.width)) * 100);
102817
+ setAlpha(newAlpha);
102818
+ if (canvas2 && targets.length) {
102819
+ targets.forEach((obj) => obj.set("opacity", newAlpha / 100));
102820
+ canvas2.requestRenderAll();
102821
+ }
102822
+ },
102823
+ children: [
102824
+ /* @__PURE__ */ jsx(
102825
+ "div",
102826
+ {
102827
+ className: "absolute inset-0",
102828
+ style: {
102829
+ background: `linear-gradient(to right, transparent, ${color})`
102830
+ }
102831
+ }
102832
+ ),
102833
+ /* @__PURE__ */ jsx("div", { className: "w-4 h-4 rounded-full border-2 border-white shadow-md absolute top-1/2 -translate-y-1/2 -translate-x-1/2 pointer-events-none", style: { left: `${alpha}%` } })
102834
+ ]
102835
+ }
102836
+ ),
102837
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-1.5 mb-3 bg-gray-50 p-1 rounded-xl border border-gray-100", children: [
102838
+ /* @__PURE__ */ jsx(
102839
+ "button",
102840
+ {
102841
+ onClick: () => setMode("HEX"),
102842
+ className: `flex-1 py-1 text-xs font-semibold rounded-lg transition-all ${mode2 === "HEX" ? "bg-white text-gray-900 shadow-sm" : "text-gray-500 hover:text-gray-900"}`,
102843
+ children: "HEX"
102844
+ }
102845
+ ),
102846
+ /* @__PURE__ */ jsx(
102847
+ "button",
102848
+ {
102849
+ onClick: () => setMode("CMYK"),
102850
+ className: `flex-1 py-1 text-xs font-semibold rounded-lg transition-all ${mode2 === "CMYK" ? "bg-white text-gray-900 shadow-sm" : "text-gray-500 hover:text-gray-900"}`,
102851
+ children: "CMYK"
102852
+ }
102853
+ )
102854
+ ] }),
102855
+ mode2 === "HEX" ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 mb-3", children: [
102856
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 flex items-center bg-gray-50 border border-gray-200 rounded-xl px-2.5 py-1.5", children: [
102857
+ /* @__PURE__ */ jsx("span", { className: "text-gray-400 text-xs font-mono", children: "#" }),
102858
+ /* @__PURE__ */ jsx(
102859
+ "input",
102860
+ {
102861
+ type: "text",
102862
+ value: color.replace("#", "").toUpperCase(),
102863
+ onChange: (e3) => updateFromHex(e3.target.value),
102864
+ className: "w-full bg-transparent text-xs font-mono font-medium focus:outline-none uppercase pl-1 text-gray-800"
102865
+ }
102866
+ )
102867
+ ] }),
102868
+ /* @__PURE__ */ jsx(
102869
+ "button",
102870
+ {
102871
+ onClick: handleCopy,
102872
+ className: "p-2 rounded-xl bg-gray-50 hover:bg-gray-100 border border-gray-200 text-gray-600 transition-colors cursor-pointer text-xs",
102873
+ title: "Copy HEX",
102874
+ children: copied ? "✓" : "Copy"
102875
+ }
102876
+ ),
102877
+ typeof window !== "undefined" && "EyeDropper" in window && /* @__PURE__ */ jsx(
102878
+ "button",
102879
+ {
102880
+ onClick: handleEyeDropper,
102881
+ className: "p-2 rounded-xl bg-gray-50 hover:bg-gray-100 border border-gray-200 text-gray-600 hover:text-cyan-600 transition-colors cursor-pointer",
102882
+ title: "Eyedropper",
102883
+ children: /* @__PURE__ */ jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
102884
+ /* @__PURE__ */ jsx("path", { d: "m14 7 3 3" }),
102885
+ /* @__PURE__ */ jsx("path", { d: "M19 10 9 20l-4 1 1-4 10-10a2.828 2.828 0 0 1 4 4Z" }),
102886
+ /* @__PURE__ */ jsx("path", { d: "m15 4 2-2 4 4-2 2" })
102887
+ ] })
102888
+ }
102889
+ )
102890
+ ] }) : /* @__PURE__ */ jsx("div", { className: "space-y-1.5 mb-3", children: CMYK_CHANNELS$1.map(({ key, label, color: lColor, name }) => /* @__PURE__ */ jsx(
102891
+ ChannelRow,
102892
+ {
102893
+ label,
102894
+ labelColor: lColor,
102895
+ ariaLabel: name,
102896
+ value: cmyk2[key],
102897
+ min: 0,
102898
+ max: 100,
102899
+ gradient: `linear-gradient(to right, ${cmykToHex(
102900
+ key === "c" ? 0 : cmyk2.c,
102901
+ key === "m" ? 0 : cmyk2.m,
102902
+ key === "y" ? 0 : cmyk2.y,
102903
+ key === "k" ? 0 : cmyk2.k
102904
+ )}, ${cmykToHex(
102905
+ key === "c" ? 100 : cmyk2.c,
102906
+ key === "m" ? 100 : cmyk2.m,
102907
+ key === "y" ? 100 : cmyk2.y,
102908
+ key === "k" ? 100 : cmyk2.k
102909
+ )})`,
102910
+ onChange: (val) => updateFromCmyk({ ...cmyk2, [key]: val })
102911
+ },
102912
+ key
102913
+ )) }),
102914
+ /* @__PURE__ */ jsxs("div", { className: "pt-2 border-t border-gray-100", children: [
102915
+ /* @__PURE__ */ jsx("span", { className: "text-[10px] font-bold text-gray-400 uppercase tracking-wider block mb-1.5", children: "Preset Swatches" }),
102916
+ /* @__PURE__ */ jsx("div", { className: "grid grid-cols-8 gap-1.5", children: DEFAULT_COLORS.map((c2) => /* @__PURE__ */ jsx(
102917
+ "button",
102918
+ {
102919
+ className: "w-6 h-6 rounded-md border border-gray-200 shadow-xs cursor-pointer hover:scale-110 transition-transform",
102920
+ style: { backgroundColor: c2 },
102921
+ onClick: () => updateFromHex(c2),
102922
+ title: c2
102923
+ },
102924
+ c2
102925
+ )) })
102926
+ ] }),
102927
+ recentColors.length > 0 && /* @__PURE__ */ jsxs("div", { className: "pt-2 mt-2 border-t border-gray-100", children: [
102928
+ /* @__PURE__ */ jsx("span", { className: "text-[10px] font-bold text-gray-400 uppercase tracking-wider block mb-1.5", children: "Recent Colors" }),
102929
+ /* @__PURE__ */ jsx("div", { className: "grid grid-cols-8 gap-1.5", children: recentColors.map((c2, i) => /* @__PURE__ */ jsx(
102930
+ "button",
102931
+ {
102932
+ className: "w-6 h-6 rounded-md border border-gray-200 shadow-xs cursor-pointer hover:scale-110 transition-transform",
102933
+ style: { backgroundColor: c2 },
102934
+ onClick: () => updateFromHex(c2),
102935
+ title: c2
102936
+ },
102937
+ c2 + i
102938
+ )) })
102939
+ ] })
102400
102940
  ]
102401
102941
  }
102402
102942
  ),
@@ -102492,11 +103032,26 @@ function BackgroundPanel() {
102492
103032
  hex
102493
103033
  ))
102494
103034
  ] }),
102495
- /* @__PURE__ */ jsx("div", { className: "h-[calc(100vh-122px)] overflow-auto", ref: scrollRef, children: /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 gap-3 mx-4 pb-5", children: backgroundImages.map((item, i) => /* @__PURE__ */ jsx(
103035
+ /* @__PURE__ */ jsx("div", { className: "h-[calc(100vh-165px)] overflow-auto custom-scrollbar", ref: scrollRef, children: /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 gap-3 mx-4 pb-5", children: backgroundImages.map((item, i) => /* @__PURE__ */ jsx(
102496
103036
  "button",
102497
103037
  {
102498
103038
  className: "relative aspect-square rounded overflow-hidden group cursor-pointer",
102499
- onClick: () => handleApplyBackground("image", item.largeImage),
103039
+ onClick: async () => {
103040
+ try {
103041
+ startLoading();
103042
+ const image = await ShapeService.convertImages(item.largeImage);
103043
+ const imageUrl = image?.success ? image?.url : null;
103044
+ if (!imageUrl) return;
103045
+ handleApplyBackground("image", imageUrl);
103046
+ if (window.innerWidth < 768) {
103047
+ useSidePanelStore.getState().setActivePanel(null);
103048
+ }
103049
+ } catch (error2) {
103050
+ console.error("Failed to convert image:", error2);
103051
+ } finally {
103052
+ stopLoading();
103053
+ }
103054
+ },
102500
103055
  children: /* @__PURE__ */ jsx(
102501
103056
  "img",
102502
103057
  {
@@ -102514,14 +103069,40 @@ function BackgroundPanel() {
102514
103069
  }
102515
103070
  function UploadPanel() {
102516
103071
  const [assets, setAssets] = useState([]);
102517
- function handleFileChange(e3) {
103072
+ const { startLoading, stopLoading } = useLoaderStore();
103073
+ async function handleFileChange(e3) {
102518
103074
  const file = e3.target.files?.[0];
102519
103075
  if (!file) return;
102520
103076
  if (!file.type.startsWith("image/")) return;
102521
- const url = URL.createObjectURL(file);
102522
- setAssets((prev) => [...prev, { id: crypto.randomUUID(), url }]);
103077
+ const previewUrl = URL.createObjectURL(file);
103078
+ const newAsset = { id: crypto.randomUUID(), url: previewUrl, file };
103079
+ setAssets((prev) => [newAsset, ...prev]);
102523
103080
  e3.target.value = "";
102524
103081
  }
103082
+ async function handleAssetClick(asset) {
103083
+ try {
103084
+ startLoading();
103085
+ let targetUrl = asset.uploadedUrl;
103086
+ if (!targetUrl && asset.file) {
103087
+ targetUrl = await UploadService.uploadFileApi(asset.file);
103088
+ setAssets(
103089
+ (prev) => prev.map((a2) => a2.id === asset.id ? { ...a2, uploadedUrl: targetUrl } : a2)
103090
+ );
103091
+ }
103092
+ const finalUrl = targetUrl || asset.url;
103093
+ if (finalUrl) {
103094
+ addImageToCanvas(finalUrl);
103095
+ if (window.innerWidth < 768) {
103096
+ useSidePanelStore.getState().setActivePanel(null);
103097
+ }
103098
+ }
103099
+ } catch (error2) {
103100
+ console.error("Failed to add image to canvas:", error2);
103101
+ addImageToCanvas(asset.url);
103102
+ } finally {
103103
+ stopLoading();
103104
+ }
103105
+ }
102525
103106
  return /* @__PURE__ */ jsxs("div", { className: "mx-4", children: [
102526
103107
  /* @__PURE__ */ jsx("h2", { className: "text-sm font-semibold mb-4 mt-2", children: "Upload your assets" }),
102527
103108
  /* @__PURE__ */ jsxs("label", { className: "w-full bg-[#02bc71] border border-neutral-700 hover:bg-[#03945a] py-2 rounded-md font-medium flex items-center justify-center gap-2 cursor-pointer", children: [
@@ -102540,17 +103121,12 @@ function UploadPanel() {
102540
103121
  /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 gap-3 mt-3", children: assets.map((asset) => /* @__PURE__ */ jsx(
102541
103122
  "button",
102542
103123
  {
102543
- onClick: () => {
102544
- addImageToCanvas(asset.url);
102545
- if (window.innerWidth < 768) {
102546
- useSidePanelStore.getState().setActivePanel(null);
102547
- }
102548
- },
103124
+ onClick: () => handleAssetClick(asset),
102549
103125
  className: "rounded-md overflow-hidden transition cursor-pointer",
102550
103126
  children: /* @__PURE__ */ jsx(
102551
103127
  "img",
102552
103128
  {
102553
- src: asset.url,
103129
+ src: asset.uploadedUrl || asset.url,
102554
103130
  alt: "asset",
102555
103131
  height: 300,
102556
103132
  width: 300,
@@ -104836,7 +105412,7 @@ async function generateQRSVG(value, dark = "#000000", light = "#ffffff") {
104836
105412
  <path fill="${dark}" d="${pathData.trim()}" />
104837
105413
  </svg>`;
104838
105414
  }
104839
- const inputStyle$1 = "w-full bg-neutral-800 border border-neutral-600 rounded-md px-3 py-2 text-sm outline-none focus:border-blue-500 focus:ring-1 focus:ring-blue-500 transition";
105415
+ const inputStyle$1 = "w-full bg-neutral-800 border border-neutral-600 rounded-md px-3 py-2 text-sm outline-none focus:border-[#02bc71] focus:ring-1 focus:ring-[#02bc71] transition";
104840
105416
  const FormField$1 = ({
104841
105417
  label,
104842
105418
  error: error2,
@@ -105043,7 +105619,7 @@ END:VCARD`;
105043
105619
  ]
105044
105620
  }
105045
105621
  ) }),
105046
- /* @__PURE__ */ jsxs("div", { className: "h-[calc(100vh-200px)] overflow-auto", children: [
105622
+ /* @__PURE__ */ jsxs("div", { className: "h-[calc(100vh-200px)] overflow-auto custom-scrollbar", children: [
105047
105623
  type === "url" && /* @__PURE__ */ jsx(FormField$1, { label: "Website URL", error: errors.url, children: /* @__PURE__ */ jsx(
105048
105624
  "input",
105049
105625
  {
@@ -105641,17 +106217,38 @@ function useBlurEffect(canvas2, active) {
105641
106217
  canvas2.requestRenderAll();
105642
106218
  }, [canvas2, active, blur.enabled, blur.value]);
105643
106219
  }
105644
- function useBrightnessEffect(canvas2, enabled) {
105645
- const { brightness } = useEffectStore();
106220
+ function useBrightnessEffect(canvas2, active) {
106221
+ const brightness = useEffectStore((s2) => s2.brightness);
105646
106222
  useEffect(() => {
105647
- if (!enabled) return;
105648
- if (!canvas2) return;
105649
- if (!brightness.enabled) return;
105650
- const target = getActiveEffectTarget(canvas2);
105651
- if (!target) return;
105652
- target.set("opacity", 1 + brightness.value / 100);
106223
+ if (!canvas2 || !active) return;
106224
+ const obj = getActiveEffectTarget(canvas2);
106225
+ if (!obj) return;
106226
+ if (obj.type !== "image" && obj.type !== "FabricImage") return;
106227
+ const image = obj;
106228
+ image.filters = image.filters || [];
106229
+ const BRIGHTNESS_INDEX = image.filters.findIndex(
106230
+ (f) => f && (f.type === "Brightness" || f instanceof Sh.Brightness)
106231
+ );
106232
+ if (!brightness.enabled || brightness.value === 0) {
106233
+ if (BRIGHTNESS_INDEX !== -1) {
106234
+ image.filters.splice(BRIGHTNESS_INDEX, 1);
106235
+ image.applyFilters();
106236
+ canvas2.requestRenderAll();
106237
+ }
106238
+ return;
106239
+ }
106240
+ const strength = Math.max(-1, Math.min(1, brightness.value / 100));
106241
+ const brightnessFilter = new Sh.Brightness({
106242
+ brightness: strength
106243
+ });
106244
+ if (BRIGHTNESS_INDEX !== -1) {
106245
+ image.filters[BRIGHTNESS_INDEX] = brightnessFilter;
106246
+ } else {
106247
+ image.filters.push(brightnessFilter);
106248
+ }
106249
+ image.applyFilters();
105653
106250
  canvas2.requestRenderAll();
105654
- }, [canvas2, enabled, brightness.enabled, brightness.value]);
106251
+ }, [canvas2, active, brightness.enabled, brightness.value]);
105655
106252
  }
105656
106253
  function EffectSection({
105657
106254
  label,
@@ -106245,7 +106842,6 @@ function EffectsPanel() {
106245
106842
  const { toggleShadow, setShadow } = useEffectStore();
106246
106843
  useBlurEffect(canvas2, isOpen);
106247
106844
  useBrightnessEffect(canvas2, isOpen);
106248
- useBrightnessEffect(canvas2, isOpen);
106249
106845
  useTemperatureEffect(canvas2, isOpen);
106250
106846
  useContrastEffect(canvas2, isOpen);
106251
106847
  useShadowEffect(canvas2, isOpen);
@@ -106333,7 +106929,7 @@ function EffectsPanel() {
106333
106929
  { id: "natural", label: "Natural" },
106334
106930
  { id: "warm", label: "Warm" }
106335
106931
  ];
106336
- return /* @__PURE__ */ jsxs("div", { className: "flex-1 overflow-y-auto custom-scrollbar overflow-x-hidden px-4", children: [
106932
+ return /* @__PURE__ */ jsxs("div", { className: "flex-1 overflow-y-auto custom-scrollbar overflow-x-hidden px-4 custom-scrollbar", children: [
106337
106933
  /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 gap-3 mb-6", children: PRESETS.map((p2) => /* @__PURE__ */ jsx(
106338
106934
  "button",
106339
106935
  {
@@ -106919,7 +107515,7 @@ function MaskPanel() {
106919
107515
  saveState();
106920
107516
  setActivePanel(null);
106921
107517
  };
106922
- return /* @__PURE__ */ jsxs("aside", { className: "bg-neutral-900 border-r border-neutral-800 p-4 text-white h-full overflow-y-auto", children: [
107518
+ return /* @__PURE__ */ jsxs("aside", { className: "bg-neutral-900 border-r border-neutral-800 p-4 text-white h-full overflow-y-auto custom-scrollbar", children: [
106923
107519
  /* @__PURE__ */ jsx("div", { onClick: () => setActivePanel(null), className: "cursor-pointer mb-2", children: "← Back" }),
106924
107520
  /* @__PURE__ */ jsx("div", { className: "flex justify-between items-center mb-4", children: /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold ", children: "Mask image" }) }),
106925
107521
  /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 gap-3", children: mask.map((m2, index) => /* @__PURE__ */ jsx(
@@ -107093,7 +107689,7 @@ function FontPanel() {
107093
107689
  )
107094
107690
  ] }),
107095
107691
  /* @__PURE__ */ jsx("hr", { className: "text-neutral-800 h-[0.5px]" }),
107096
- /* @__PURE__ */ jsx("div", { className: "h-[calc(100vh-125px)] overflow-auto", ref: scrollRef, children: /* @__PURE__ */ jsx("div", { className: "mx-4 py-5", children: fontData.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-gray-400 text-sm text-center", children: "No results found" }) : /* @__PURE__ */ jsxs(Fragment, { children: [
107692
+ /* @__PURE__ */ jsx("div", { className: "h-[calc(100vh-126px)] overflow-auto custom-scrollbar", ref: scrollRef, children: /* @__PURE__ */ jsx("div", { className: "mx-4 py-5", children: fontData.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-gray-400 text-sm text-center", children: "No results found" }) : /* @__PURE__ */ jsxs(Fragment, { children: [
107097
107693
  /* @__PURE__ */ jsx("div", { className: "grid grid-cols-1 gap-4", children: fontData.map((item, i) => /* @__PURE__ */ jsx(
107098
107694
  "button",
107099
107695
  {
@@ -152405,7 +153001,7 @@ function BarcodePanel() {
152405
153001
  }
152406
153002
  };
152407
153003
  const stopPropagation = (e3) => e3.stopPropagation();
152408
- return /* @__PURE__ */ jsx("div", { className: "flex flex-col h-full mx-4", children: /* @__PURE__ */ jsxs("div", { className: "flex-1 overflow-y-auto space-y-5 pr-2", children: [
153004
+ return /* @__PURE__ */ jsx("div", { className: "flex flex-col h-full mx-4", children: /* @__PURE__ */ jsxs("div", { className: "flex-1 overflow-y-auto space-y-5 pr-2 custom-scrollbar", children: [
152409
153005
  /* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold text-white", children: "Bar Code" }),
152410
153006
  /* @__PURE__ */ jsx(FormField, { label: "Select Bar Code Type", children: /* @__PURE__ */ jsx(
152411
153007
  "select",
@@ -152463,82 +153059,99 @@ function BarcodePanel() {
152463
153059
  )
152464
153060
  ] }) });
152465
153061
  }
152466
- const QUOTES = [
152467
- {
152468
- content: "There are no absolutes in this world, and there will always be mistakes that are made within brokerage firms; there will always be people who set out to deceive the regulators and even deceive their own senior management.",
152469
- author: "Mary Schapiro"
152470
- },
152471
- {
152472
- content: "Things will get better if you just hold out long enough.",
152473
- author: "Desmond Dekker"
152474
- },
152475
- {
152476
- content: "The thing I am attracted to is just good writing and stories that are based somewhat in reality.",
152477
- author: "Jennifer Aniston"
152478
- },
152479
- {
152480
- content: "Tax reform and expanded trade are going to be so important to the economy of Illinois.",
152481
- author: "Bill Foster"
152482
- },
152483
- {
152484
- content: "In the middle of every difficulty lies opportunity.",
152485
- author: "Albert Einstein"
152486
- },
152487
- {
152488
- content: "It does not matter how slowly you go as long as you do not stop.",
152489
- author: "Confucius"
152490
- },
152491
- {
152492
- content: "Life is what happens when you're busy making other plans.",
152493
- author: "John Lennon"
152494
- },
152495
- {
152496
- content: "The future belongs to those who believe in the beauty of their dreams.",
152497
- author: "Eleanor Roosevelt"
152498
- },
152499
- {
152500
- content: "Success is not final, failure is not fatal: it is the courage to continue that counts.",
152501
- author: "Winston Churchill"
152502
- },
152503
- {
152504
- content: "You miss 100% of the shots you don't take.",
152505
- author: "Wayne Gretzky"
152506
- },
152507
- {
152508
- content: "Whether you think you can or you think you can't, you're right.",
152509
- author: "Henry Ford"
152510
- },
152511
- {
152512
- content: "I have not failed. I've just found 10,000 ways that won't work.",
152513
- author: "Thomas Edison"
153062
+ const QuoteService = {
153063
+ async getAllQuotes(params = {}) {
153064
+ try {
153065
+ const query = new URLSearchParams();
153066
+ if (params.page) query.append("page", String(params.page));
153067
+ if (params.limit) query.append("limit", String(params.limit));
153068
+ if (params.search) query.append("search", params.search);
153069
+ query.append("status", "true");
153070
+ const res = await apiFetch(`/api/quotes?${query.toString()}`, {
153071
+ method: "GET"
153072
+ });
153073
+ return Array.isArray(res?.data) ? res.data : [];
153074
+ } catch (err) {
153075
+ console.error("Failed to fetch quotes:", err);
153076
+ return [];
153077
+ }
152514
153078
  }
152515
- ];
153079
+ };
152516
153080
  function QuotesPanel() {
153081
+ const [quotes, setQuotes] = useState([]);
153082
+ const [page, setPage] = useState(1);
153083
+ const [hasMore, setHasMore] = useState(true);
153084
+ const [isLoading, setIsLoading] = useState(false);
153085
+ const scrollRef = useRef(null);
153086
+ const fetchQuotes = useCallback(async (pageNo) => {
153087
+ setIsLoading(true);
153088
+ try {
153089
+ const data = await QuoteService.getAllQuotes({ page: pageNo, limit: 20 });
153090
+ if (data && data.length > 0) {
153091
+ setQuotes((prev) => pageNo === 1 ? data : [...prev, ...data]);
153092
+ if (data.length < 20) {
153093
+ setHasMore(false);
153094
+ }
153095
+ } else {
153096
+ setHasMore(false);
153097
+ }
153098
+ } catch (err) {
153099
+ console.error("Failed to load quotes:", err);
153100
+ } finally {
153101
+ setIsLoading(false);
153102
+ }
153103
+ }, []);
153104
+ useEffect(() => {
153105
+ fetchQuotes(1);
153106
+ }, [fetchQuotes]);
153107
+ useEffect(() => {
153108
+ const el = scrollRef.current;
153109
+ if (!el) return;
153110
+ const onScroll = () => {
153111
+ if (!hasMore || isLoading) return;
153112
+ if (el.scrollTop + el.clientHeight >= el.scrollHeight - 150) {
153113
+ setPage((prev) => {
153114
+ const next = prev + 1;
153115
+ fetchQuotes(next);
153116
+ return next;
153117
+ });
153118
+ }
153119
+ };
153120
+ el.addEventListener("scroll", onScroll);
153121
+ return () => el.removeEventListener("scroll", onScroll);
153122
+ }, [hasMore, isLoading, fetchQuotes]);
152517
153123
  const addQuote = async (content, author) => {
152518
- await addTextToCanvas(18, 300, `${content}
153124
+ await addTextToCanvas(18, 300, author ? `${content}
152519
153125
 
152520
- — ${author}`);
153126
+ — ${author}` : content);
153127
+ if (window.innerWidth < 768) {
153128
+ useSidePanelStore.getState().setActivePanel(null);
153129
+ }
152521
153130
  };
152522
153131
  return /* @__PURE__ */ jsxs("div", { className: "flex flex-col h-full", children: [
152523
153132
  /* @__PURE__ */ jsx("div", { className: "px-4 pb-3", children: /* @__PURE__ */ jsx("h2", { className: "text-base font-semibold text-white", children: "Quotes" }) }),
152524
153133
  /* @__PURE__ */ jsx("hr", { className: "border-neutral-800 mx-4 mb-3" }),
152525
- /* @__PURE__ */ jsx(
153134
+ /* @__PURE__ */ jsxs(
152526
153135
  "div",
152527
153136
  {
152528
- className: "flex-1 min-h-0 overflow-y-auto flex flex-col gap-3 px-4 py-2",
152529
- children: QUOTES.map((q2, i) => /* @__PURE__ */ jsxs(
152530
- "button",
152531
- {
152532
- onClick: () => addQuote(q2.content, q2.author),
152533
- title: "Click to add to canvas",
152534
- className: "\n w-full text-left rounded-lg\n bg-neutral-800 hover:bg-neutral-700\n border border-neutral-700 hover:border-[#02bc71]/40\n px-4 py-3.5 cursor-pointer\n transition-all duration-150 active:scale-[0.98]\n group\n ",
152535
- children: [
152536
- /* @__PURE__ */ jsx("p", { className: "text-neutral-200 text-[13px] leading-relaxed mb-2 group-hover:text-white transition-colors", children: q2.content }),
152537
- /* @__PURE__ */ jsx("p", { className: "text-right text-[12px] font-semibold text-[#02bc71]", children: q2.author })
152538
- ]
152539
- },
152540
- i
152541
- ))
153137
+ ref: scrollRef,
153138
+ className: "flex-1 min-h-0 overflow-y-auto flex flex-col gap-3 px-4 py-2 custom-scrollbar",
153139
+ children: [
153140
+ quotes.map((q2, i) => /* @__PURE__ */ jsxs(
153141
+ "button",
153142
+ {
153143
+ onClick: () => addQuote(q2.content, q2.author),
153144
+ title: "Click to add to canvas",
153145
+ className: "\n w-full text-left rounded-lg\n bg-neutral-800 hover:bg-neutral-700\n border border-neutral-700 hover:border-[#02bc71]/40\n px-4 py-3.5 cursor-pointer\n transition-all duration-150 active:scale-[0.98]\n group\n ",
153146
+ children: [
153147
+ /* @__PURE__ */ jsx("p", { className: "text-neutral-200 text-[13px] leading-relaxed mb-2 group-hover:text-white transition-colors", children: q2.content }),
153148
+ q2.author && /* @__PURE__ */ jsx("p", { className: "text-right text-[12px] font-semibold text-[#02bc71]", children: q2.author })
153149
+ ]
153150
+ },
153151
+ q2._id || i
153152
+ )),
153153
+ isLoading && /* @__PURE__ */ jsx("div", { className: "text-center py-4 text-xs text-neutral-500", children: "Loading more quotes..." })
153154
+ ]
152542
153155
  }
152543
153156
  )
152544
153157
  ] });
@@ -152566,6 +153179,559 @@ const QuotesIcon = () => /* @__PURE__ */ jsx(
152566
153179
  ) })
152567
153180
  }
152568
153181
  );
153182
+ const ColorService = {
153183
+ async getAll(params) {
153184
+ try {
153185
+ const searchParams = new URLSearchParams();
153186
+ if (params?.search) searchParams.append("search", params.search);
153187
+ if (params?.status !== void 0) searchParams.append("status", String(params.status));
153188
+ searchParams.append("limit", String(params?.limit || 100));
153189
+ if (params?.page) searchParams.append("page", String(params.page));
153190
+ const res = await apiFetch(`/api/colors?${searchParams.toString()}`, {
153191
+ method: "GET"
153192
+ });
153193
+ if (Array.isArray(res)) return res;
153194
+ if (Array.isArray(res?.data)) return res.data;
153195
+ return [];
153196
+ } catch (err) {
153197
+ console.error("Failed to fetch colors:", err);
153198
+ return [];
153199
+ }
153200
+ }
153201
+ };
153202
+ const DEFAULT_PRESET_COLORS = [
153203
+ "#ffffff",
153204
+ "#000000",
153205
+ "#1e293b",
153206
+ "#475569",
153207
+ "#94a3b8",
153208
+ "#e2e8f0",
153209
+ "#ef4444",
153210
+ "#f97316",
153211
+ "#f59e0b",
153212
+ "#eab308",
153213
+ "#84cc16",
153214
+ "#22c55e",
153215
+ "#10b981",
153216
+ "#06b6d4",
153217
+ "#0ea5e9",
153218
+ "#3b82f6",
153219
+ "#6366f1",
153220
+ "#8b5cf6",
153221
+ "#a855f7",
153222
+ "#d946ef",
153223
+ "#ec4899",
153224
+ "#f43f5e"
153225
+ ];
153226
+ const CMYK_CHANNELS = [
153227
+ { key: "c", label: "C", color: "#009fe3", name: "Cyan" },
153228
+ { key: "m", label: "M", color: "#e6007e", name: "Magenta" },
153229
+ { key: "y", label: "Y", color: "#dcb000", name: "Yellow" },
153230
+ { key: "k", label: "K", color: "#222222", name: "Black" }
153231
+ ];
153232
+ function ColorPanel() {
153233
+ const canvas2 = useEditorStore((s2) => s2.canvas);
153234
+ const { objects, activeColor, activeColorIndex, setActiveColor } = useSelectionStore();
153235
+ const { setActivePanel } = useSidePanelStore();
153236
+ const saveState = useHistoryStore((s2) => s2.saveState);
153237
+ const targets = useMemo(
153238
+ () => objects.filter((o2) => o2.name !== "workarea"),
153239
+ [objects]
153240
+ );
153241
+ const objectColors = useMemo(() => extractAllTargetColors(targets), [targets, objects]);
153242
+ const targetInfo = useMemo(() => extractTargetColor(targets), [targets]);
153243
+ const initialColor = activeColor || (objectColors.length > 0 ? objectColors[0] : targetInfo.color || "#000000");
153244
+ const [currentColor, setCurrentColor] = useState(initialColor);
153245
+ const [currentCmyk, setCurrentCmyk] = useState(() => hexToCmyk(initialColor));
153246
+ const [hsv, setHsv] = useState(() => {
153247
+ const rgb2 = hexToRgb(initialColor);
153248
+ return rgbToHsv(rgb2.r, rgb2.g, rgb2.b);
153249
+ });
153250
+ const [alpha, setAlpha] = useState(() => targetInfo.opacity ?? 100);
153251
+ const [colorMode, setColorMode] = useState("HEX");
153252
+ const [dbColors, setDbColors] = useState([]);
153253
+ const [isLoadingColors, setIsLoadingColors] = useState(false);
153254
+ const [copied, setCopied] = useState(false);
153255
+ const svRef = useRef(null);
153256
+ const hueRef = useRef(null);
153257
+ const alphaRef = useRef(null);
153258
+ useEffect(() => {
153259
+ if (targets.length > 0) {
153260
+ const colors = extractAllTargetColors(targets);
153261
+ if (colors.length > 0) {
153262
+ let chosenColor = activeColor;
153263
+ if (!chosenColor || !colors.includes(chosenColor)) {
153264
+ chosenColor = colors[activeColorIndex] || colors[0];
153265
+ }
153266
+ setCurrentColor(chosenColor);
153267
+ const c2 = hexToCmyk(chosenColor);
153268
+ setCurrentCmyk(c2);
153269
+ const rgb2 = hexToRgb(chosenColor);
153270
+ setHsv(rgbToHsv(rgb2.r, rgb2.g, rgb2.b));
153271
+ const info = extractTargetColor(targets);
153272
+ if (info.opacity !== void 0) setAlpha(info.opacity);
153273
+ if (activeColor !== chosenColor) {
153274
+ const idx = colors.indexOf(chosenColor);
153275
+ setActiveColor(chosenColor, idx >= 0 ? idx : 0);
153276
+ }
153277
+ }
153278
+ }
153279
+ }, [targets, activeColor, activeColorIndex]);
153280
+ useEffect(() => {
153281
+ let isMounted = true;
153282
+ const fetchColors = async () => {
153283
+ setIsLoadingColors(true);
153284
+ try {
153285
+ const data = await ColorService.getAll({ status: true, limit: 100 });
153286
+ if (isMounted) setDbColors(data);
153287
+ } finally {
153288
+ if (isMounted) setIsLoadingColors(false);
153289
+ }
153290
+ };
153291
+ fetchColors();
153292
+ return () => {
153293
+ isMounted = false;
153294
+ };
153295
+ }, []);
153296
+ const documentColors = useMemo(() => {
153297
+ if (!canvas2) return [];
153298
+ const allObjs = canvas2.getObjects ? canvas2.getObjects() : [];
153299
+ return extractAllTargetColors(allObjs);
153300
+ }, [canvas2, objects]);
153301
+ const applyColor = useCallback(
153302
+ (newHex, cmykVal, opacityVal = alpha) => {
153303
+ const resolvedCmyk = cmykVal || hexToCmyk(newHex);
153304
+ const oldTargetColor = currentColor;
153305
+ setCurrentColor(newHex);
153306
+ setCurrentCmyk(resolvedCmyk);
153307
+ if (targets.length > 0) {
153308
+ applyFillToFabricObjects(targets, newHex, opacityVal, oldTargetColor, canvas2, resolvedCmyk);
153309
+ setActiveColor(newHex, activeColorIndex);
153310
+ } else {
153311
+ addBackgroundColor(newHex);
153312
+ }
153313
+ saveState();
153314
+ },
153315
+ [targets, canvas2, alpha, currentColor, activeColorIndex, setActiveColor, saveState]
153316
+ );
153317
+ const updateFromHsv = useCallback(
153318
+ (newHsv) => {
153319
+ const rgb2 = hsvToRgb(newHsv.h, newHsv.s, newHsv.v);
153320
+ const newC = rgbToCmyk(rgb2.r, rgb2.g, rgb2.b);
153321
+ const hex = rgbToHex(rgb2.r, rgb2.g, rgb2.b);
153322
+ setHsv(newHsv);
153323
+ setCurrentCmyk(newC);
153324
+ applyColor(hex, newC, alpha);
153325
+ },
153326
+ [alpha, applyColor]
153327
+ );
153328
+ const updateFromCmyk = useCallback(
153329
+ (newCmyk) => {
153330
+ const rgb2 = cmykToRgb(newCmyk.c, newCmyk.m, newCmyk.y, newCmyk.k);
153331
+ const hex = rgbToHex(rgb2.r, rgb2.g, rgb2.b);
153332
+ setCurrentCmyk(newCmyk);
153333
+ setHsv(rgbToHsv(rgb2.r, rgb2.g, rgb2.b));
153334
+ applyColor(hex, newCmyk, alpha);
153335
+ },
153336
+ [alpha, applyColor]
153337
+ );
153338
+ const updateFromHex = useCallback(
153339
+ (inputHex) => {
153340
+ let clean = inputHex.trim().replace(/^#/, "");
153341
+ if (clean.length === 3) clean = clean[0] + clean[0] + clean[1] + clean[1] + clean[2] + clean[2];
153342
+ if (/^[0-9a-fA-F]{6}$/.test(clean)) {
153343
+ const hex = `#${clean.toLowerCase()}`;
153344
+ const rgb2 = hexToRgb(hex);
153345
+ const newC = rgbToCmyk(rgb2.r, rgb2.g, rgb2.b);
153346
+ setHsv(rgbToHsv(rgb2.r, rgb2.g, rgb2.b));
153347
+ setCurrentCmyk(newC);
153348
+ applyColor(hex, newC, alpha);
153349
+ }
153350
+ },
153351
+ [alpha, applyColor]
153352
+ );
153353
+ const handleSelectObjectColor = (hex, idx) => {
153354
+ setActiveColor(hex, idx);
153355
+ setCurrentColor(hex);
153356
+ const c2 = hexToCmyk(hex);
153357
+ setCurrentCmyk(c2);
153358
+ const rgb2 = hexToRgb(hex);
153359
+ setHsv(rgbToHsv(rgb2.r, rgb2.g, rgb2.b));
153360
+ };
153361
+ const handleCopyHex = () => {
153362
+ navigator.clipboard.writeText(currentColor);
153363
+ setCopied(true);
153364
+ setTimeout(() => setCopied(false), 1500);
153365
+ };
153366
+ const handleEyeDropper = async () => {
153367
+ if (typeof window !== "undefined" && "EyeDropper" in window) {
153368
+ try {
153369
+ const eyeDropper = new window.EyeDropper();
153370
+ const result = await eyeDropper.open();
153371
+ if (result?.sRGBHex) {
153372
+ updateFromHex(result.sRGBHex);
153373
+ }
153374
+ } catch (err) {
153375
+ console.error("EyeDropper error:", err);
153376
+ }
153377
+ }
153378
+ };
153379
+ const groupedDbColors = useMemo(() => {
153380
+ const map = /* @__PURE__ */ new Map();
153381
+ dbColors.forEach((c2) => {
153382
+ const group = c2.colorGroup || "Custom Palettes";
153383
+ if (!map.has(group)) map.set(group, []);
153384
+ map.get(group).push(c2);
153385
+ });
153386
+ return map;
153387
+ }, [dbColors]);
153388
+ const hueRgb = hsvToRgb(hsv.h, 100, 100);
153389
+ const hueColor = rgbToHex(hueRgb.r, hueRgb.g, hueRgb.b);
153390
+ return /* @__PURE__ */ jsxs("div", { className: "w-full h-full bg-neutral-900 border-r border-neutral-800 flex flex-col select-none text-white text-xs", children: [
153391
+ /* @__PURE__ */ jsx("div", { className: "px-3 py-2 flex flex-col gap-3 shrink-0", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
153392
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
153393
+ /* @__PURE__ */ jsx("span", { className: "w-2 h-2 rounded-full bg-[#02bc71] animate-pulse" }),
153394
+ /* @__PURE__ */ jsx("h2", { className: "font-semibold text-sm text-neutral-100", children: "Color Palette" })
153395
+ ] }),
153396
+ /* @__PURE__ */ jsx(
153397
+ "button",
153398
+ {
153399
+ onClick: () => setActivePanel(null),
153400
+ className: "p-1 rounded-md text-neutral-400 hover:text-white hover:bg-neutral-800 transition-colors cursor-pointer",
153401
+ title: "Close Panel",
153402
+ children: /* @__PURE__ */ jsx("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ jsx("path", { d: "M18 6 6 18M6 6l12 12" }) })
153403
+ }
153404
+ )
153405
+ ] }) }),
153406
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 overflow-y-auto space-y-5 p-4 pr-3 custom-scrollbar", children: [
153407
+ objectColors.length > 1 && /* @__PURE__ */ jsxs("div", { children: [
153408
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-2", children: [
153409
+ /* @__PURE__ */ jsxs("span", { className: "text-[11px] font-bold text-neutral-400 uppercase tracking-wider", children: [
153410
+ "Object Colors (",
153411
+ objectColors.length,
153412
+ ")"
153413
+ ] }),
153414
+ /* @__PURE__ */ jsx("span", { className: "text-[10px] text-neutral-500", children: "Select to customize" })
153415
+ ] }),
153416
+ /* @__PURE__ */ jsx("div", { className: "flex flex-wrap items-center gap-2 p-2.5 bg-neutral-950 border border-neutral-800 rounded-xl", children: objectColors.map((hex, idx) => {
153417
+ const isSelected = currentColor.toLowerCase() === hex.toLowerCase();
153418
+ return /* @__PURE__ */ jsx(
153419
+ "button",
153420
+ {
153421
+ type: "button",
153422
+ onClick: () => handleSelectObjectColor(hex, idx),
153423
+ className: `w-8 h-8 rounded-lg border transition-all cursor-pointer hover:scale-105 shadow-xs relative ${isSelected ? "border-white ring-2 ring-[#02bc71] scale-105 z-10" : "border-neutral-700 hover:border-neutral-500"}`,
153424
+ style: { backgroundColor: hex },
153425
+ title: `Object color ${idx + 1}: ${hex.toUpperCase()}`,
153426
+ children: isSelected && /* @__PURE__ */ jsx("span", { className: "absolute inset-0 flex items-center justify-center text-xs font-bold mix-blend-difference text-white", children: "✓" })
153427
+ },
153428
+ hex + "-" + idx
153429
+ );
153430
+ }) })
153431
+ ] }),
153432
+ /* @__PURE__ */ jsxs("div", { className: "p-3.5 bg-neutral-950 border border-neutral-800 rounded-2xl shadow-xl space-y-3", children: [
153433
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between pb-1", children: [
153434
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
153435
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-bold text-neutral-300", children: "Custom Color" }),
153436
+ /* @__PURE__ */ jsx(
153437
+ "span",
153438
+ {
153439
+ className: "w-3.5 h-3.5 rounded-full border border-neutral-700 shadow-inner",
153440
+ style: { backgroundColor: currentColor }
153441
+ }
153442
+ )
153443
+ ] }),
153444
+ /* @__PURE__ */ jsxs("div", { className: "flex bg-neutral-900 rounded-lg p-0.5 border border-neutral-800", children: [
153445
+ /* @__PURE__ */ jsx(
153446
+ "button",
153447
+ {
153448
+ type: "button",
153449
+ onClick: () => setColorMode("HEX"),
153450
+ className: `px-2 py-0.5 rounded text-[10px] font-bold transition-colors cursor-pointer ${colorMode === "HEX" ? "bg-[#02bc71] text-white" : "text-neutral-400 hover:text-white"}`,
153451
+ children: "HEX"
153452
+ }
153453
+ ),
153454
+ /* @__PURE__ */ jsx(
153455
+ "button",
153456
+ {
153457
+ type: "button",
153458
+ onClick: () => setColorMode("CMYK"),
153459
+ className: `px-2 py-0.5 rounded text-[10px] font-bold transition-colors cursor-pointer ${colorMode === "CMYK" ? "bg-[#02bc71] text-white" : "text-neutral-400 hover:text-white"}`,
153460
+ children: "CMYK"
153461
+ }
153462
+ )
153463
+ ] })
153464
+ ] }),
153465
+ /* @__PURE__ */ jsx(
153466
+ "div",
153467
+ {
153468
+ ref: svRef,
153469
+ className: "relative w-full h-36 rounded-xl overflow-hidden cursor-crosshair select-none border border-neutral-800 touch-none",
153470
+ style: {
153471
+ background: `linear-gradient(to top, #000, transparent), linear-gradient(to right, #fff, ${hueColor})`
153472
+ },
153473
+ onPointerDown: (e3) => {
153474
+ e3.currentTarget.setPointerCapture(e3.pointerId);
153475
+ const rect = e3.currentTarget.getBoundingClientRect();
153476
+ updateFromHsv({
153477
+ ...hsv,
153478
+ s: Math.round(Math.max(0, Math.min(1, (e3.clientX - rect.left) / rect.width)) * 100),
153479
+ v: Math.round((1 - Math.max(0, Math.min(1, (e3.clientY - rect.top) / rect.height))) * 100)
153480
+ });
153481
+ },
153482
+ onPointerMove: (e3) => {
153483
+ if (e3.buttons === 0) return;
153484
+ const rect = e3.currentTarget.getBoundingClientRect();
153485
+ updateFromHsv({
153486
+ ...hsv,
153487
+ s: Math.round(Math.max(0, Math.min(1, (e3.clientX - rect.left) / rect.width)) * 100),
153488
+ v: Math.round((1 - Math.max(0, Math.min(1, (e3.clientY - rect.top) / rect.height))) * 100)
153489
+ });
153490
+ },
153491
+ children: /* @__PURE__ */ jsx(
153492
+ "div",
153493
+ {
153494
+ className: "absolute w-4 h-4 rounded-full border-2 border-white shadow-md -translate-x-1/2 -translate-y-1/2 pointer-events-none ring-1 ring-black/40",
153495
+ style: {
153496
+ left: `${hsv.s}%`,
153497
+ top: `${100 - hsv.v}%`,
153498
+ backgroundColor: currentColor
153499
+ }
153500
+ }
153501
+ )
153502
+ }
153503
+ ),
153504
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
153505
+ /* @__PURE__ */ jsxs("div", { className: "flex justify-between text-[10px] text-neutral-400 font-medium", children: [
153506
+ /* @__PURE__ */ jsx("span", { children: "Hue" }),
153507
+ /* @__PURE__ */ jsxs("span", { children: [
153508
+ Math.round(hsv.h),
153509
+ "°"
153510
+ ] })
153511
+ ] }),
153512
+ /* @__PURE__ */ jsx(
153513
+ "div",
153514
+ {
153515
+ ref: hueRef,
153516
+ className: "relative h-4 rounded-lg cursor-pointer select-none border border-neutral-800 touch-none",
153517
+ style: {
153518
+ background: "linear-gradient(to right, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%)"
153519
+ },
153520
+ onPointerDown: (e3) => {
153521
+ e3.currentTarget.setPointerCapture(e3.pointerId);
153522
+ const rect = e3.currentTarget.getBoundingClientRect();
153523
+ updateFromHsv({
153524
+ ...hsv,
153525
+ h: Math.round(Math.max(0, Math.min(1, (e3.clientX - rect.left) / rect.width)) * 360) % 360
153526
+ });
153527
+ },
153528
+ onPointerMove: (e3) => {
153529
+ if (e3.buttons === 0) return;
153530
+ const rect = e3.currentTarget.getBoundingClientRect();
153531
+ updateFromHsv({
153532
+ ...hsv,
153533
+ h: Math.round(Math.max(0, Math.min(1, (e3.clientX - rect.left) / rect.width)) * 360) % 360
153534
+ });
153535
+ },
153536
+ children: /* @__PURE__ */ jsx(
153537
+ "div",
153538
+ {
153539
+ className: "absolute top-1/2 w-4 h-4 -translate-y-1/2 -translate-x-1/2 rounded-full border-2 border-white shadow-md pointer-events-none ring-1 ring-black/30",
153540
+ style: { left: `${hsv.h / 360 * 100}%`, backgroundColor: hueColor }
153541
+ }
153542
+ )
153543
+ }
153544
+ )
153545
+ ] }),
153546
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
153547
+ /* @__PURE__ */ jsxs("div", { className: "flex justify-between text-[10px] text-neutral-400 font-medium", children: [
153548
+ /* @__PURE__ */ jsx("span", { children: "Opacity" }),
153549
+ /* @__PURE__ */ jsxs("span", { children: [
153550
+ alpha,
153551
+ "%"
153552
+ ] })
153553
+ ] }),
153554
+ /* @__PURE__ */ jsxs(
153555
+ "div",
153556
+ {
153557
+ ref: alphaRef,
153558
+ className: "relative h-4 rounded-lg cursor-pointer select-none border border-neutral-800 overflow-hidden touch-none pqc-checkerboard",
153559
+ onPointerDown: (e3) => {
153560
+ e3.currentTarget.setPointerCapture(e3.pointerId);
153561
+ const rect = e3.currentTarget.getBoundingClientRect();
153562
+ const newAlpha = Math.round(Math.max(0, Math.min(1, (e3.clientX - rect.left) / rect.width)) * 100);
153563
+ setAlpha(newAlpha);
153564
+ applyColor(currentColor, currentCmyk, newAlpha);
153565
+ },
153566
+ onPointerMove: (e3) => {
153567
+ if (e3.buttons === 0) return;
153568
+ const rect = e3.currentTarget.getBoundingClientRect();
153569
+ const newAlpha = Math.round(Math.max(0, Math.min(1, (e3.clientX - rect.left) / rect.width)) * 100);
153570
+ setAlpha(newAlpha);
153571
+ applyColor(currentColor, currentCmyk, newAlpha);
153572
+ },
153573
+ children: [
153574
+ /* @__PURE__ */ jsx(
153575
+ "div",
153576
+ {
153577
+ className: "absolute inset-0",
153578
+ style: {
153579
+ background: `linear-gradient(to right, transparent, ${currentColor})`
153580
+ }
153581
+ }
153582
+ ),
153583
+ /* @__PURE__ */ jsx(
153584
+ "div",
153585
+ {
153586
+ className: "absolute top-1/2 w-4 h-4 -translate-y-1/2 -translate-x-1/2 rounded-full border-2 border-white shadow-md bg-white pointer-events-none ring-1 ring-black/30",
153587
+ style: { left: `${alpha}%` }
153588
+ }
153589
+ )
153590
+ ]
153591
+ }
153592
+ )
153593
+ ] }),
153594
+ colorMode === "HEX" && /* @__PURE__ */ jsx("div", { className: "space-y-2 pt-1", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
153595
+ /* @__PURE__ */ jsxs("div", { className: "relative flex-1", children: [
153596
+ /* @__PURE__ */ jsx("span", { className: "absolute left-2.5 top-2 text-xs font-mono text-neutral-500", children: "#" }),
153597
+ /* @__PURE__ */ jsx(
153598
+ "input",
153599
+ {
153600
+ type: "text",
153601
+ maxLength: 7,
153602
+ value: currentColor.replace("#", "").toUpperCase(),
153603
+ onChange: (e3) => {
153604
+ const val = e3.target.value.replace(/[^0-9A-Fa-f]/g, "");
153605
+ if (val.length <= 6) {
153606
+ const newHex = `#${val}`;
153607
+ setCurrentColor(newHex);
153608
+ if (val.length === 6) {
153609
+ updateFromHex(newHex);
153610
+ }
153611
+ }
153612
+ },
153613
+ className: "w-full bg-neutral-900 border border-neutral-800 rounded-lg pl-6 pr-2 py-1.5 text-xs text-white font-mono uppercase focus:outline-none focus:border-[#02bc71]"
153614
+ }
153615
+ )
153616
+ ] }),
153617
+ /* @__PURE__ */ jsx(
153618
+ "button",
153619
+ {
153620
+ type: "button",
153621
+ onClick: handleCopyHex,
153622
+ className: "px-2.5 py-1.5 bg-neutral-900 hover:bg-neutral-800 border border-neutral-800 hover:border-neutral-700 rounded-lg text-[11px] text-neutral-300 font-medium flex items-center gap-1 transition-colors cursor-pointer",
153623
+ title: "Copy Hex Code",
153624
+ children: copied ? /* @__PURE__ */ jsx("span", { className: "text-[#02bc71]", children: "Copied!" }) : /* @__PURE__ */ jsx("span", { children: "Copy" })
153625
+ }
153626
+ ),
153627
+ typeof window !== "undefined" && "EyeDropper" in window && /* @__PURE__ */ jsx(
153628
+ "button",
153629
+ {
153630
+ type: "button",
153631
+ onClick: handleEyeDropper,
153632
+ className: "p-1.5 bg-neutral-900 hover:bg-neutral-800 border border-neutral-800 hover:border-neutral-700 rounded-lg text-neutral-300 hover:text-white transition-colors cursor-pointer",
153633
+ title: "Eyedropper Tool",
153634
+ children: /* @__PURE__ */ jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
153635
+ /* @__PURE__ */ jsx("path", { d: "m14 2 8 8-4 4-8-8 4-4Z" }),
153636
+ /* @__PURE__ */ jsx("path", { d: "m18 10-4-4" }),
153637
+ /* @__PURE__ */ jsx("path", { d: "m6.5 13.5-3 3c-.8.8-.8 2.2 0 3 .8.8 2.2.8 3 0l3-3" })
153638
+ ] })
153639
+ }
153640
+ )
153641
+ ] }) }),
153642
+ colorMode === "CMYK" && /* @__PURE__ */ jsx("div", { className: "space-y-1.5 pt-1", children: CMYK_CHANNELS.map(({ key, label, color: lColor, name }) => /* @__PURE__ */ jsx(
153643
+ ChannelRow,
153644
+ {
153645
+ label,
153646
+ labelColor: lColor,
153647
+ ariaLabel: name,
153648
+ value: currentCmyk[key],
153649
+ min: 0,
153650
+ max: 100,
153651
+ gradient: `linear-gradient(to right, ${cmykToHex(
153652
+ key === "c" ? 0 : currentCmyk.c,
153653
+ key === "m" ? 0 : currentCmyk.m,
153654
+ key === "y" ? 0 : currentCmyk.y,
153655
+ key === "k" ? 0 : currentCmyk.k
153656
+ )}, ${cmykToHex(
153657
+ key === "c" ? 100 : currentCmyk.c,
153658
+ key === "m" ? 100 : currentCmyk.m,
153659
+ key === "y" ? 100 : currentCmyk.y,
153660
+ key === "k" ? 100 : currentCmyk.k
153661
+ )})`,
153662
+ onChange: (val) => updateFromCmyk({ ...currentCmyk, [key]: val })
153663
+ },
153664
+ key
153665
+ )) })
153666
+ ] }),
153667
+ /* @__PURE__ */ jsxs("div", { children: [
153668
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-2", children: [
153669
+ /* @__PURE__ */ jsx("span", { className: "text-[11px] font-bold text-neutral-400 uppercase tracking-wider", children: "Document Colors" }),
153670
+ /* @__PURE__ */ jsxs("span", { className: "text-[10px] text-neutral-500", children: [
153671
+ documentColors.length,
153672
+ " colors"
153673
+ ] })
153674
+ ] }),
153675
+ documentColors.length === 0 ? /* @__PURE__ */ jsx("div", { className: "p-3 text-center rounded-xl bg-neutral-900/40 border border-dashed border-neutral-800", children: /* @__PURE__ */ jsx("span", { className: "text-xs text-neutral-500", children: "No colors detected in document yet" }) }) : /* @__PURE__ */ jsx("div", { className: "flex flex-wrap items-center gap-2", children: documentColors.map((hex) => /* @__PURE__ */ jsx(
153676
+ "button",
153677
+ {
153678
+ onClick: () => updateFromHex(hex),
153679
+ className: `w-8 h-8 rounded-lg border transition-all cursor-pointer hover:scale-105 shadow-xs relative ${currentColor.toLowerCase() === hex.toLowerCase() ? "border-white ring-2 ring-[#02bc71]" : "border-neutral-700/80"}`,
153680
+ style: { backgroundColor: hex },
153681
+ title: hex.toUpperCase(),
153682
+ children: currentColor.toLowerCase() === hex.toLowerCase() && /* @__PURE__ */ jsx("span", { className: "absolute inset-0 flex items-center justify-center text-xs font-bold mix-blend-difference text-white", children: "✓" })
153683
+ },
153684
+ hex
153685
+ )) })
153686
+ ] }),
153687
+ /* @__PURE__ */ jsxs("div", { children: [
153688
+ /* @__PURE__ */ jsx("span", { className: "text-[11px] font-bold text-neutral-400 uppercase tracking-wider block mb-2", children: "Default Colors" }),
153689
+ /* @__PURE__ */ jsx("div", { className: "grid grid-cols-6 gap-2", children: DEFAULT_PRESET_COLORS.map((hex) => /* @__PURE__ */ jsx(
153690
+ "button",
153691
+ {
153692
+ type: "button",
153693
+ onClick: () => updateFromHex(hex),
153694
+ className: `w-full aspect-square rounded-lg border transition-all cursor-pointer hover:scale-105 shadow-xs relative ${currentColor.toLowerCase() === hex.toLowerCase() ? "border-white ring-2 ring-[#02bc71]" : "border-neutral-800"}`,
153695
+ style: { backgroundColor: hex },
153696
+ title: hex,
153697
+ children: currentColor.toLowerCase() === hex.toLowerCase() && /* @__PURE__ */ jsx("span", { className: "absolute inset-0 flex items-center justify-center text-xs font-bold mix-blend-difference text-white", children: "✓" })
153698
+ },
153699
+ hex
153700
+ )) })
153701
+ ] }),
153702
+ isLoadingColors ? /* @__PURE__ */ jsx("div", { className: "text-center py-4 text-xs text-neutral-500", children: "Loading color library..." }) : Array.from(groupedDbColors.entries()).map(([groupName, colors]) => /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
153703
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
153704
+ /* @__PURE__ */ jsx("span", { className: "text-[11px] font-bold text-neutral-400 uppercase tracking-wider", children: groupName }),
153705
+ /* @__PURE__ */ jsx("span", { className: "text-[10px] text-neutral-500", children: colors.length })
153706
+ ] }),
153707
+ /* @__PURE__ */ jsx("div", { className: "grid grid-cols-6 gap-2", children: colors.map((c2) => /* @__PURE__ */ jsx(
153708
+ "button",
153709
+ {
153710
+ onClick: () => updateFromHex(c2.hexCode),
153711
+ className: `w-full aspect-square rounded-lg border transition-all cursor-pointer hover:scale-105 shadow-xs relative group ${currentColor.toLowerCase() === c2.hexCode.toLowerCase() ? "border-white ring-2 ring-[#02bc71]" : "border-neutral-800"}`,
153712
+ style: { backgroundColor: c2.hexCode },
153713
+ title: `${c2.colorName} (${c2.hexCode})`,
153714
+ children: currentColor.toLowerCase() === c2.hexCode.toLowerCase() && /* @__PURE__ */ jsx("span", { className: "absolute inset-0 flex items-center justify-center text-xs font-bold mix-blend-difference text-white", children: "✓" })
153715
+ },
153716
+ c2._id || c2.hexCode
153717
+ )) })
153718
+ ] }, groupName))
153719
+ ] })
153720
+ ] });
153721
+ }
153722
+ const ColorPickerIcon = () => /* @__PURE__ */ jsx(
153723
+ "svg",
153724
+ {
153725
+ xmlns: "http://www.w3.org/2000/svg",
153726
+ viewBox: "0 0 512 512",
153727
+ width: "80",
153728
+ height: "80",
153729
+ fill: "currentColor",
153730
+ "aria-hidden": "true",
153731
+ style: { mixBlendMode: "difference" },
153732
+ children: /* @__PURE__ */ jsx("path", { d: "M430.1 347.9c-6.6-6.1-16.3-7.6-24.6-9-11.5-1.9-15.9-4-22.6-10-14.3-12.7-14.3-31.1 0-43.8l30.3-26.9c46.4-41 46.4-108.2 0-149.2-34.2-30.1-80.1-45-127.8-45-55.7 0-113.9 20.3-158.8 60.1-83.5 73.8-83.5 194.7 0 268.5 41.5 36.7 97.5 55 152.9 55.4h1.7c55.4 0 110-17.9 148.8-52.4 14.4-12.7 12-36.6.1-47.7zM120 216c0-17.7 14.3-32 32-32s32 14.3 32 32-14.3 32-32 32-32-14.3-32-32zm40 126c-17.7 0-32-14.3-32-32s14.3-32 32-32 32 14.3 32 32-14.3 32-32 32zm64-161c-17.7 0-32-14.3-32-32s14.3-32 32-32 32 14.3 32 32-14.3 32-32 32zm72 219c-26.5 0-48-21.5-48-48s21.5-48 48-48 48 21.5 48 48-21.5 48-48 48zm24-208c-17.7 0-32-14.3-32-32s14.3-32 32-32 32 14.3 32 32-14.3 32-32 32z" })
153733
+ }
153734
+ );
152569
153735
  const DEFAULT_SECTIONS = [
152570
153736
  {
152571
153737
  id: "templates",
@@ -152627,6 +153793,13 @@ const DEFAULT_SECTIONS = [
152627
153793
  Tab: /* @__PURE__ */ jsx(QRCodeIcon, {}),
152628
153794
  Panel: /* @__PURE__ */ jsx(BarcodePanel, {})
152629
153795
  },
153796
+ {
153797
+ id: "color",
153798
+ name: "Color",
153799
+ Tab: /* @__PURE__ */ jsx(ColorPickerIcon, {}),
153800
+ Panel: /* @__PURE__ */ jsx(ColorPanel, {}),
153801
+ visibleInList: false
153802
+ },
152630
153803
  {
152631
153804
  id: "effects",
152632
153805
  name: "Effects",
@@ -152820,6 +153993,18 @@ function StrikethroughIcon() {
152820
153993
  }
152821
153994
  );
152822
153995
  }
153996
+ const STROKE_WIDTH_OPTIONS = [0, 1, 2, 3, 4, 5];
153997
+ const getStrokeDashArray = (style) => {
153998
+ switch (style) {
153999
+ case "Dashed":
154000
+ return [10, 5];
154001
+ case "Dotted":
154002
+ return [2, 4];
154003
+ case "Line":
154004
+ default:
154005
+ return null;
154006
+ }
154007
+ };
152823
154008
  function TextFontSize({
152824
154009
  value,
152825
154010
  onChange
@@ -152861,6 +154046,40 @@ function TextToolbar() {
152861
154046
  const [charSpacing, setCharSpacing] = useState(0);
152862
154047
  const [align, setAlign] = useState("left");
152863
154048
  const [showTextPopup, setShowTextPopup] = useState(false);
154049
+ const [showStrokeControls, setShowStrokeControls] = useState(false);
154050
+ const [strokeColor, setStrokeColor] = useState("#000000");
154051
+ const [strokeSize, setStrokeSize] = useState(0);
154052
+ const [strokeStyle, setStrokeStyle] = useState("Line");
154053
+ const strokeControlsRef = useRef(null);
154054
+ const textPopupRef = useRef(null);
154055
+ const textPopupButtonRef = useRef(null);
154056
+ useEffect(() => {
154057
+ if (!showStrokeControls) return;
154058
+ const handleClickOutside = (e3) => {
154059
+ const target = e3.target;
154060
+ if (!target) return;
154061
+ if (strokeControlsRef.current?.contains(target)) return;
154062
+ if (target.closest?.('[data-cmyk-popover="true"], .cmyk-picker-popover')) return;
154063
+ setShowStrokeControls(false);
154064
+ };
154065
+ document.addEventListener("mousedown", handleClickOutside);
154066
+ return () => {
154067
+ document.removeEventListener("mousedown", handleClickOutside);
154068
+ };
154069
+ }, [showStrokeControls]);
154070
+ useEffect(() => {
154071
+ if (!showTextPopup) return;
154072
+ const handleClickOutside = (e3) => {
154073
+ const target = e3.target;
154074
+ if (!target) return;
154075
+ if (textPopupRef.current?.contains(target) || textPopupButtonRef.current?.contains(target)) return;
154076
+ setShowTextPopup(false);
154077
+ };
154078
+ document.addEventListener("mousedown", handleClickOutside);
154079
+ return () => {
154080
+ document.removeEventListener("mousedown", handleClickOutside);
154081
+ };
154082
+ }, [showTextPopup]);
152864
154083
  const text = useMemo(() => {
152865
154084
  if (type !== "textbox") return null;
152866
154085
  return isTextbox(objects[0]) ? objects[0] : null;
@@ -152871,6 +154090,11 @@ function TextToolbar() {
152871
154090
  setLineHeight2((text.lineHeight ?? 1) * 100);
152872
154091
  setCharSpacing(text.charSpacing ?? 0);
152873
154092
  setAlign(text.textAlign ?? "left");
154093
+ const dashArray = text.strokeDashArray;
154094
+ const currentStrokeStyle = Array.isArray(dashArray) && dashArray.length === 2 && dashArray[0] === 2 && dashArray[1] === 4 ? "Dotted" : Array.isArray(dashArray) && dashArray.length === 2 && dashArray[0] === 10 && dashArray[1] === 5 ? "Dashed" : "Line";
154095
+ setStrokeColor(typeof text.stroke === "string" && text.stroke ? text.stroke : "#000000");
154096
+ setStrokeSize(Math.max(0, Number(text.strokeWidth ?? 0)));
154097
+ setStrokeStyle(currentStrokeStyle);
152874
154098
  }, [text]);
152875
154099
  const updateText = useCallback(
152876
154100
  (props) => {
@@ -152883,6 +154107,24 @@ function TextToolbar() {
152883
154107
  },
152884
154108
  [text]
152885
154109
  );
154110
+ const applyTextStroke = useCallback(
154111
+ (nextColor, nextSize, nextStyle) => {
154112
+ if (!text) return;
154113
+ const canvas2 = text.canvas;
154114
+ if (!canvas2) return;
154115
+ const normalizedSize = Number.isFinite(nextSize) ? Math.min(5, Math.max(0, Math.round(nextSize))) : 0;
154116
+ text.set({
154117
+ stroke: normalizedSize > 0 ? nextColor : null,
154118
+ strokeWidth: normalizedSize,
154119
+ strokeDashArray: normalizedSize === 0 ? null : getStrokeDashArray(nextStyle),
154120
+ strokeUniform: true,
154121
+ paintFirst: "stroke"
154122
+ });
154123
+ canvas2.requestRenderAll();
154124
+ useHistoryStore.getState().saveState();
154125
+ },
154126
+ [text]
154127
+ );
152886
154128
  const applyTextCase = (mode2) => {
152887
154129
  if (!text?.text) return;
152888
154130
  const anyText = text;
@@ -152964,7 +154206,10 @@ function TextToolbar() {
152964
154206
  /* @__PURE__ */ jsx(
152965
154207
  "button",
152966
154208
  {
152967
- className: "cursor-pointer p-1 hover:bg-neutral-700 rounded",
154209
+ ref: textPopupButtonRef,
154210
+ type: "button",
154211
+ "aria-expanded": showTextPopup,
154212
+ className: `cursor-pointer rounded p-1 transition-colors duration-150 ${showTextPopup ? "bg-neutral-700 text-white ring-1 ring-neutral-600 shadow-sm" : "hover:bg-neutral-700"}`,
152968
154213
  onClick: () => setShowTextPopup((prev) => !prev),
152969
154214
  children: /* @__PURE__ */ jsx(
152970
154215
  "svg",
@@ -152983,10 +154228,10 @@ function TextToolbar() {
152983
154228
  showTextPopup && /* @__PURE__ */ jsxs(
152984
154229
  "div",
152985
154230
  {
152986
- className: "fixed md:absolute z-[9999] md:z-50 bg-neutral-900 text-white shadow-xl rounded-lg p-4 top-[112px] md:top-[40px] left-1/2 md:left-auto md:right-0 -translate-x-1/2 md:-translate-x-0",
154231
+ ref: textPopupRef,
154232
+ className: "fixed md:absolute z-[9999] md:z-50 bg-[#1d1d1d] text-white shadow-[0_12px_30px_rgba(0,0,0,0.35)] rounded-lg p-4 top-[112px] md:top-[40px] left-1/2 md:left-auto md:right-0 -translate-x-1/2 md:-translate-x-0 border border-neutral-700",
152987
154233
  style: {
152988
- width: "230px",
152989
- border: "1px solid #333"
154234
+ width: "230px"
152990
154235
  },
152991
154236
  children: [
152992
154237
  /* @__PURE__ */ jsxs("div", { className: "flex justify-between mb-2", children: [
@@ -153075,7 +154320,72 @@ function TextToolbar() {
153075
154320
  },
153076
154321
  children: "Aa"
153077
154322
  }
153078
- )
154323
+ ),
154324
+ /* @__PURE__ */ jsxs("div", { className: "relative", ref: strokeControlsRef, children: [
154325
+ /* @__PURE__ */ jsx(
154326
+ "button",
154327
+ {
154328
+ type: "button",
154329
+ onClick: () => setShowStrokeControls((prev) => !prev),
154330
+ className: `text-left text-[15px] font-semibold ${text?.fontWeight === "bold" ? "text-blue-400" : ""} transition hover:text-blue-400`,
154331
+ children: "Stroke"
154332
+ }
154333
+ ),
154334
+ showStrokeControls && /* @__PURE__ */ jsxs("div", { className: "absolute left-0 top-full z-50 mt-2 flex items-center gap-3 rounded-md border border-neutral-700 bg-[#1d1d1d] px-3 py-2 text-white shadow-[0_12px_30px_rgba(0,0,0,0.35)]", children: [
154335
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-[13px] font-medium text-white", children: [
154336
+ /* @__PURE__ */ jsx("span", { children: "Color" }),
154337
+ /* @__PURE__ */ jsx(
154338
+ CMYKColorPicker,
154339
+ {
154340
+ value: strokeColor,
154341
+ onChange: (hex) => {
154342
+ const nextColor = typeof hex === "string" ? hex : "#000000";
154343
+ setStrokeColor(nextColor);
154344
+ applyTextStroke(nextColor, strokeSize, strokeStyle);
154345
+ }
154346
+ }
154347
+ )
154348
+ ] }),
154349
+ /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-2 text-[13px] font-medium text-white", children: [
154350
+ /* @__PURE__ */ jsx("span", { children: "Size" }),
154351
+ /* @__PURE__ */ jsx(
154352
+ "select",
154353
+ {
154354
+ value: String(strokeSize),
154355
+ onChange: (e3) => {
154356
+ const nextSize = Math.min(5, Number(e3.target.value));
154357
+ setStrokeSize(nextSize);
154358
+ applyTextStroke(strokeColor, nextSize, strokeStyle);
154359
+ },
154360
+ className: "h-8 min-w-[72px] rounded border border-neutral-600 bg-neutral-800 px-2 text-[13px] text-white outline-none",
154361
+ "aria-label": "Text stroke size",
154362
+ children: STROKE_WIDTH_OPTIONS.map((size) => /* @__PURE__ */ jsx("option", { value: String(size), className: "bg-neutral-800 text-white", children: size }, size))
154363
+ }
154364
+ )
154365
+ ] }),
154366
+ /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-2 text-[13px] font-medium text-white", children: [
154367
+ /* @__PURE__ */ jsx("span", { children: "Style" }),
154368
+ /* @__PURE__ */ jsxs(
154369
+ "select",
154370
+ {
154371
+ value: strokeStyle,
154372
+ onChange: (e3) => {
154373
+ const nextStyle = e3.target.value;
154374
+ setStrokeStyle(nextStyle);
154375
+ applyTextStroke(strokeColor, strokeSize, nextStyle);
154376
+ },
154377
+ className: "h-8 min-w-[90px] rounded border border-neutral-600 bg-neutral-800 px-2 text-[13px] text-white outline-none",
154378
+ "aria-label": "Text stroke style",
154379
+ children: [
154380
+ /* @__PURE__ */ jsx("option", { value: "Dotted", className: "bg-neutral-800 text-white", children: "Dotted" }),
154381
+ /* @__PURE__ */ jsx("option", { value: "Line", className: "bg-neutral-800 text-white", children: "Line" }),
154382
+ /* @__PURE__ */ jsx("option", { value: "Dashed", className: "bg-neutral-800 text-white", children: "Dashed" })
154383
+ ]
154384
+ }
154385
+ )
154386
+ ] })
154387
+ ] })
154388
+ ] })
153079
154389
  ] }) });
153080
154390
  }
153081
154391
  function CropIcon() {
@@ -153193,7 +154503,7 @@ class CropService {
153193
154503
  stroke: "#2196F3",
153194
154504
  strokeWidth: 2,
153195
154505
  cornerColor: "#ffffff",
153196
- cornerStrokeColor: "#202020",
154506
+ cornerStrokeColor: "#2196F3",
153197
154507
  cornerSize: 12,
153198
154508
  cornerStyle: "circle",
153199
154509
  transparentCorners: false,
@@ -153529,7 +154839,7 @@ function QrEffectToolbar() {
153529
154839
  /* @__PURE__ */ jsxs(
153530
154840
  "button",
153531
154841
  {
153532
- className: "hover:text-blue-400 flex",
154842
+ className: "hover:text-emerald-400 flex items-center gap-1.5 cursor-pointer",
153533
154843
  onClick: () => setActivePanel("effects"),
153534
154844
  children: [
153535
154845
  /* @__PURE__ */ jsx(EffectIcon, {}),
@@ -153556,7 +154866,7 @@ function QrEffectToolbar() {
153556
154866
  /* @__PURE__ */ jsx(
153557
154867
  "button",
153558
154868
  {
153559
- className: "hover:text-blue-400",
154869
+ className: "hover:text-emerald-400 cursor-pointer",
153560
154870
  onClick: () => setActivePanel("animate"),
153561
154871
  children: "Animate"
153562
154872
  }
@@ -153669,7 +154979,7 @@ function DuplicateButton() {
153669
154979
  const duplicate = async () => {
153670
154980
  const active = canvas2.getActiveObject();
153671
154981
  if (!active) return;
153672
- if (active.type === "activeselection") {
154982
+ if (active.type?.toLowerCase() === "activeselection") {
153673
154983
  const selection = active;
153674
154984
  const originals = selection.getObjects().filter((o2) => o2.name !== "workarea");
153675
154985
  if (!originals.length) return;
@@ -153796,7 +155106,7 @@ function FlipButton() {
153796
155106
  const getTargets = () => {
153797
155107
  const active = canvas2.getActiveObject();
153798
155108
  if (!active) return [];
153799
- const objects = active.type === "activeselection" ? active.getObjects() : [active];
155109
+ const objects = active.type?.toLowerCase() === "activeselection" ? active.getObjects() : [active];
153800
155110
  return objects.filter((o2) => o2.name !== "workarea");
153801
155111
  };
153802
155112
  const handleFlip = (flip) => {
@@ -153941,7 +155251,7 @@ function LockButton() {
153941
155251
  const getTargets = () => {
153942
155252
  const active = canvas2?.getActiveObject();
153943
155253
  if (!active) return [];
153944
- const objects = active.type === "activeselection" ? active.getObjects() : [active];
155254
+ const objects = active.type?.toLowerCase() === "activeselection" ? active.getObjects() : [active];
153945
155255
  return objects.filter((o2) => o2.name !== "workarea");
153946
155256
  };
153947
155257
  useEffect(() => {
@@ -154034,12 +155344,12 @@ function OpacityPicker() {
154034
155344
  const getTargets = () => {
154035
155345
  const active = getActive();
154036
155346
  if (!active) return [];
154037
- return active.type === "activeselection" ? active.getObjects() : [active];
155347
+ return active.type?.toLowerCase() === "activeselection" ? active.getObjects() : [active];
154038
155348
  };
154039
155349
  const readOpacityFromSelection = () => {
154040
155350
  const active = getActive();
154041
155351
  if (!active) return 100;
154042
- if (active.type === "activeselection") {
155352
+ if (active.type?.toLowerCase() === "activeselection") {
154043
155353
  const objects = active.getObjects();
154044
155354
  return Math.round((objects[0]?.opacity ?? 1) * 100);
154045
155355
  }
@@ -154159,7 +155469,7 @@ function PositionPicker() {
154159
155469
  const getTargets = () => {
154160
155470
  const active = getActive();
154161
155471
  if (!active) return [];
154162
- return active.type === "activeselection" ? active.getObjects() : [active];
155472
+ return active.type?.toLowerCase() === "activeselection" ? active.getObjects() : [active];
154163
155473
  };
154164
155474
  const getWorkarea2 = () => canvas2.getObjects().find((o2) => o2.name === "workarea");
154165
155475
  const normalizeOrigin = (obj) => {
@@ -154207,7 +155517,7 @@ function PositionPicker() {
154207
155517
  const handleAlign = (align) => {
154208
155518
  const active = getActive();
154209
155519
  if (!active) return;
154210
- if (active.type === "activeselection") {
155520
+ if (active.type?.toLowerCase() === "activeselection") {
154211
155521
  alignInsideSelection(align);
154212
155522
  } else {
154213
155523
  applyAlign(align);
@@ -154374,7 +155684,7 @@ function RemoveButton() {
154374
155684
  const getTargets = () => {
154375
155685
  const active = canvas2?.getActiveObject();
154376
155686
  if (!active) return [];
154377
- const objects = active.type === "activeselection" ? active.getObjects() : [active];
155687
+ const objects = active.type?.toLowerCase() === "activeselection" ? active.getObjects() : [active];
154378
155688
  return objects.filter((o2) => o2.name !== "workarea");
154379
155689
  };
154380
155690
  const deleteObjects = () => {
@@ -154448,7 +155758,7 @@ function RotateButton() {
154448
155758
  const getTargets = () => {
154449
155759
  const active = canvas2.getActiveObject();
154450
155760
  if (!active) return [];
154451
- const objects = active.type === "activeselection" ? active.getObjects() : [active];
155761
+ const objects = active.type?.toLowerCase() === "activeselection" ? active.getObjects() : [active];
154452
155762
  return objects.filter((o2) => o2.name !== "workarea");
154453
155763
  };
154454
155764
  const handleRotate = (dir, step = 45) => {
@@ -154537,13 +155847,19 @@ function GroupButton() {
154537
155847
  const handleGroup = () => {
154538
155848
  if (!canvas2) return;
154539
155849
  const active = canvas2.getActiveObject();
154540
- if (!active || active.type !== "activeselection") return;
155850
+ if (!active || active.type?.toLowerCase() !== "activeselection") return;
154541
155851
  const selection = active;
154542
155852
  const objects2 = [...selection.getObjects()];
155853
+ const canvasObjects = canvas2.getObjects();
155854
+ const minZIndex = Math.min(...objects2.map((o2) => canvasObjects.indexOf(o2)));
154543
155855
  canvas2.discardActiveObject();
154544
155856
  objects2.forEach((obj) => canvas2.remove(obj));
154545
155857
  const group = new Ur(objects2);
154546
- canvas2.add(group);
155858
+ if (minZIndex !== -1 && minZIndex < canvas2.getObjects().length) {
155859
+ canvas2.insertAt(minZIndex, group);
155860
+ } else {
155861
+ canvas2.add(group);
155862
+ }
154547
155863
  canvas2.setActiveObject(group);
154548
155864
  canvas2.requestRenderAll();
154549
155865
  useSelectionStore.getState().setSelection([group]);
@@ -154592,6 +155908,14 @@ const rotateSVG = `
154592
155908
  </g>
154593
155909
  </svg>`;
154594
155910
  let rotateImg = null;
155911
+ const SMALL_OBJECT_THRESHOLD = 40;
155912
+ const SMALL_CONTROL_SIZE = 6;
155913
+ const NORMAL_CONTROL_SIZE = 10;
155914
+ const ROTATION_GAP = 6;
155915
+ const isTextObject = (obj) => obj.type === "textbox" || obj.type === "i-text" || obj.type === "text";
155916
+ function normalizeAngle(angle = 0) {
155917
+ return (Math.round(angle) % 360 + 360) % 360;
155918
+ }
154595
155919
  function renderRotateIcon(ctx, left, top, fabricObject) {
154596
155920
  if (!rotateImg) {
154597
155921
  rotateImg = new Image();
@@ -154600,7 +155924,7 @@ function renderRotateIcon(ctx, left, top, fabricObject) {
154600
155924
  fabricObject.canvas?.requestRenderAll();
154601
155925
  };
154602
155926
  }
154603
- const size = 24;
155927
+ const size = fabricObject.__rotationControlSize || 24;
154604
155928
  ctx.save();
154605
155929
  ctx.translate(left, top);
154606
155930
  if (rotateImg.complete && rotateImg.naturalHeight !== 0) {
@@ -154608,11 +155932,13 @@ function renderRotateIcon(ctx, left, top, fabricObject) {
154608
155932
  }
154609
155933
  ctx.restore();
154610
155934
  }
154611
- function createRotateControl() {
155935
+ function createRotateControl(size = 24, offsetY = -30) {
154612
155936
  return new ai({
154613
155937
  x: 0,
154614
155938
  y: -0.5,
154615
- offsetY: -30,
155939
+ offsetY,
155940
+ sizeX: size,
155941
+ sizeY: size,
154616
155942
  cursorStyle: "crosshair",
154617
155943
  actionHandler: Fa.rotationWithSnapping,
154618
155944
  actionName: "rotate",
@@ -154620,23 +155946,147 @@ function createRotateControl() {
154620
155946
  withConnection: true
154621
155947
  });
154622
155948
  }
155949
+ function renderRotationAngle(ctx, left, top, _styleOverride, fabricObject) {
155950
+ if (!fabricObject.__showDegree) {
155951
+ return;
155952
+ }
155953
+ const angle = normalizeAngle(fabricObject.angle);
155954
+ const label = `${angle}°`;
155955
+ const paddingX = 8;
155956
+ const height = 22;
155957
+ const radius = 6;
155958
+ ctx.save();
155959
+ ctx.font = "bold 12px sans-serif";
155960
+ ctx.textAlign = "center";
155961
+ ctx.textBaseline = "middle";
155962
+ const textWidth = ctx.measureText(label).width;
155963
+ const width = Math.max(34, textWidth + paddingX * 2);
155964
+ const x2 = left - width / 2;
155965
+ const y2 = top - height / 2;
155966
+ ctx.beginPath();
155967
+ ctx.fillStyle = "rgba(15, 23, 42, 0.9)";
155968
+ ctx.strokeStyle = "#02bc71";
155969
+ ctx.lineWidth = 1.5;
155970
+ if (typeof ctx.roundRect === "function") {
155971
+ ctx.roundRect(x2, y2, width, height, radius);
155972
+ } else {
155973
+ ctx.rect(x2, y2, width, height);
155974
+ }
155975
+ ctx.fill();
155976
+ ctx.stroke();
155977
+ ctx.fillStyle = "#ffffff";
155978
+ ctx.fillText(label, left, top + 0.5);
155979
+ ctx.restore();
155980
+ }
155981
+ function createRotationAngleControl() {
155982
+ return new ai({
155983
+ x: -0.5,
155984
+ y: 0,
155985
+ offsetX: -28,
155986
+ offsetY: 0,
155987
+ sizeX: 1,
155988
+ sizeY: 1,
155989
+ cursorStyle: "default",
155990
+ render: renderRotationAngle
155991
+ });
155992
+ }
155993
+ const isImageObject = (obj) => obj.type === "image" || obj.type === "FabricImage" || typeof obj.isType === "function" && (obj.isType("image") || obj.isType("FabricImage"));
154623
155994
  function configureObjectControls(obj) {
155995
+ const zoom = obj.canvas ? obj.canvas.getZoom() : 1;
155996
+ const scaledWidth = obj.getScaledWidth() * zoom;
155997
+ const scaledHeight = obj.getScaledHeight() * zoom;
155998
+ const renderedSize = Math.min(scaledWidth, scaledHeight);
155999
+ const isSmall = renderedSize < SMALL_OBJECT_THRESHOLD;
156000
+ const cornerSize = isSmall ? Math.min(SMALL_CONTROL_SIZE, Math.max(4, renderedSize * 0.3)) : NORMAL_CONTROL_SIZE;
156001
+ const rotationSize = isSmall ? Math.min(16, Math.max(12, renderedSize * 0.4)) : 24;
156002
+ const rotationOffset = -(rotationSize / 2 + ROTATION_GAP);
154624
156003
  obj.set({
154625
156004
  transparentCorners: false,
154626
156005
  cornerColor: "#ffffff",
154627
156006
  cornerStrokeColor: "#02bc71",
154628
156007
  borderColor: "#02bc71",
154629
- cornerSize: 10,
156008
+ cornerSize,
154630
156009
  cornerStyle: "rect",
154631
156010
  borderScaleFactor: 1
154632
156011
  });
154633
- obj.controls.mtr = createRotateControl();
156012
+ obj.__rotationControlSize = rotationSize;
156013
+ obj.controls.mtr = createRotateControl(rotationSize, rotationOffset);
156014
+ const textObject = isTextObject(obj);
156015
+ if (!obj.controls.mb) {
156016
+ obj.controls.mb = new ai({
156017
+ x: 0,
156018
+ y: 0.5,
156019
+ cursorStyleHandler: Fa.scaleSkewCursorStyleHandler,
156020
+ actionHandler: textObject ? Fa.changeWidth : Fa.scalingYOrSkewingX,
156021
+ actionName: "resizing"
156022
+ });
156023
+ }
156024
+ const rotationControl = createRotationAngleControl();
156025
+ obj.controls.rotationAngle = rotationControl;
156026
+ delete obj.controls.bmc;
156027
+ delete obj.controls.textAngle;
156028
+ if (isSmall) {
156029
+ obj.setControlsVisibility({
156030
+ mtr: true,
156031
+ br: true,
156032
+ tl: false,
156033
+ tr: false,
156034
+ bl: false,
156035
+ mb: false,
156036
+ ml: false,
156037
+ mr: false,
156038
+ mt: false,
156039
+ bmc: false,
156040
+ rotationAngle: true
156041
+ });
156042
+ return;
156043
+ }
156044
+ if (isImageObject(obj)) {
156045
+ obj.setControlsVisibility({
156046
+ mtr: true,
156047
+ tl: true,
156048
+ tr: true,
156049
+ bl: true,
156050
+ br: true,
156051
+ ml: false,
156052
+ mr: false,
156053
+ mb: true,
156054
+ mt: false,
156055
+ bmc: false,
156056
+ rotationAngle: true
156057
+ });
156058
+ return;
156059
+ }
156060
+ if (!obj.controls.ml) {
156061
+ obj.controls.ml = new ai({
156062
+ x: -0.5,
156063
+ y: 0,
156064
+ cursorStyleHandler: Fa.scaleSkewCursorStyleHandler,
156065
+ actionHandler: textObject ? Fa.changeWidth : Fa.scalingXOrSkewingY,
156066
+ actionName: "resizing"
156067
+ });
156068
+ }
156069
+ if (!obj.controls.mr) {
156070
+ obj.controls.mr = new ai({
156071
+ x: 0.5,
156072
+ y: 0,
156073
+ cursorStyleHandler: Fa.scaleSkewCursorStyleHandler,
156074
+ actionHandler: textObject ? Fa.changeWidth : Fa.scalingXOrSkewingY,
156075
+ actionName: "resizing"
156076
+ });
156077
+ }
154634
156078
  obj.setControlsVisibility({
154635
156079
  mtr: true,
154636
- ml: false,
154637
- mr: false,
156080
+ tl: true,
156081
+ tr: true,
156082
+ bl: true,
156083
+ br: true,
156084
+ ml: true,
156085
+ mr: true,
156086
+ mb: true,
154638
156087
  mt: false,
154639
- mb: false
156088
+ bmc: false,
156089
+ rotationAngle: true
154640
156090
  });
154641
156091
  }
154642
156092
  function initializeRotateImage() {
@@ -154653,20 +156103,38 @@ function UngroupButton() {
154653
156103
  const handleUngroup = () => {
154654
156104
  if (!canvas2) return;
154655
156105
  const active = canvas2.getActiveObject();
154656
- if (!active || active.type !== "group") return;
156106
+ if (!active || active.type?.toLowerCase() !== "group") return;
154657
156107
  const group = active;
154658
- const objects2 = [...group.getObjects()];
154659
- group.remove(...objects2);
156108
+ const groupIndex = canvas2.getObjects().indexOf(group);
156109
+ const childObjects = [...group.getObjects()];
156110
+ canvas2.discardActiveObject();
156111
+ group.remove(...childObjects);
154660
156112
  canvas2.remove(group);
154661
- objects2.forEach((obj) => canvas2.add(obj));
154662
- const sel = new Qo(objects2, { canvas: canvas2 });
154663
- configureObjectControls(sel);
154664
- canvas2.setActiveObject(sel);
154665
- canvas2.renderAll();
154666
- useSelectionStore.getState().setSelection(objects2);
156113
+ if (typeof group.dispose === "function") {
156114
+ group.dispose();
156115
+ }
156116
+ childObjects.forEach((obj, idx) => {
156117
+ obj.set({
156118
+ selectable: true,
156119
+ evented: true,
156120
+ hasControls: true,
156121
+ hasBorders: true
156122
+ });
156123
+ obj.setCoords();
156124
+ if (groupIndex !== -1) {
156125
+ canvas2.insertAt(groupIndex + idx, obj);
156126
+ } else {
156127
+ canvas2.add(obj);
156128
+ }
156129
+ configureObjectControls(obj);
156130
+ });
156131
+ canvas2.discardActiveObject();
156132
+ useSelectionStore.getState().clearSelection();
156133
+ canvas2.requestRenderAll();
154667
156134
  saveState();
154668
156135
  };
154669
- if (selectionType !== "multiple" || objects.length !== 1) return null;
156136
+ const isGroupSelected = selectionType === "group" || objects.length === 1 && objects[0]?.type?.toLowerCase() === "group" && objects[0]?.name !== "qr" && objects[0]?.name !== "barcode";
156137
+ if (!isGroupSelected) return null;
154670
156138
  return /* @__PURE__ */ jsx(
154671
156139
  "button",
154672
156140
  {
@@ -155012,6 +156480,9 @@ function createWorkarea(canvas2, container, productData) {
155012
156480
  initialZoom = 1;
155013
156481
  }
155014
156482
  const initialScale = baseScale * initialZoom;
156483
+ const isTextTemplate = productData?.isTextTemplate || productData?.hideGuidelines || typeof window !== "undefined" && ["text", "texttemplate", "text-template"].includes(
156484
+ (new URLSearchParams(window.location.search).get("designType") || new URLSearchParams(window.location.search).get("designtype") || new URLSearchParams(window.location.search).get("type") || "")?.toLowerCase()
156485
+ );
155015
156486
  const workarea = new jr({
155016
156487
  width: fullWidth,
155017
156488
  height: fullHeight,
@@ -155027,6 +156498,8 @@ function createWorkarea(canvas2, container, productData) {
155027
156498
  workarea.__printDPI = printDPI;
155028
156499
  workarea.__baseScale = baseScale;
155029
156500
  workarea.__initialZoom = initialZoom;
156501
+ workarea.__hideGuidelines = isTextTemplate || productData?.hideGuidelines;
156502
+ workarea.__isTextTemplate = isTextTemplate;
155030
156503
  workarea.__bleedArea = productData?.bleedArea ?? 0.125;
155031
156504
  workarea.__safetyArea = productData?.safetyArea ?? 0.125;
155032
156505
  workarea.__trimArea = productData?.trimArea ?? 0;
@@ -155074,27 +156547,63 @@ function setupSelectionHandlers(canvas2, setSelection, clearSelection) {
155074
156547
  clearSelection();
155075
156548
  return;
155076
156549
  }
155077
- const objects = active.type === "activeselection" ? active.getObjects() : [active];
156550
+ const isMulti = active.type?.toLowerCase() === "activeselection";
156551
+ const objects = isMulti ? active.getObjects() : [active];
155078
156552
  setSelection(objects);
155079
156553
  };
155080
- const configureSelectionControls = (e3) => {
156554
+ const configureSelectionControls = () => {
155081
156555
  const activeObject = canvas2.getActiveObject();
155082
156556
  if (!activeObject) return;
155083
156557
  configureObjectControls(activeObject);
155084
156558
  canvas2.requestRenderAll();
155085
156559
  };
155086
156560
  canvas2.on("selection:created", (e3) => {
156561
+ if (e3.selected) {
156562
+ e3.selected.forEach((o2) => {
156563
+ o2.__showDegree = false;
156564
+ });
156565
+ }
155087
156566
  updateSelection();
155088
156567
  configureSelectionControls();
155089
156568
  });
155090
156569
  canvas2.on("selection:updated", (e3) => {
156570
+ if (e3.selected) {
156571
+ e3.selected.forEach((o2) => {
156572
+ o2.__showDegree = false;
156573
+ });
156574
+ }
155091
156575
  updateSelection();
155092
156576
  configureSelectionControls();
155093
156577
  });
155094
- canvas2.on("selection:cleared", clearSelection);
156578
+ canvas2.on("object:scaling", configureSelectionControls);
156579
+ canvas2.on("object:rotating", (e3) => {
156580
+ if (e3.target) {
156581
+ e3.target.__showDegree = true;
156582
+ }
156583
+ configureSelectionControls();
156584
+ });
156585
+ const hideDegree = (e3) => {
156586
+ if (e3?.target) {
156587
+ e3.target.__showDegree = false;
156588
+ }
156589
+ canvas2.getObjects().forEach((o2) => {
156590
+ o2.__showDegree = false;
156591
+ });
156592
+ configureSelectionControls();
156593
+ };
156594
+ canvas2.on("object:modified", hideDegree);
156595
+ canvas2.on("mouse:up", hideDegree);
156596
+ canvas2.on("selection:cleared", () => {
156597
+ hideDegree();
156598
+ clearSelection();
156599
+ });
155095
156600
  return () => {
155096
156601
  canvas2.off("selection:created");
155097
156602
  canvas2.off("selection:updated");
156603
+ canvas2.off("object:scaling", configureSelectionControls);
156604
+ canvas2.off("object:rotating");
156605
+ canvas2.off("object:modified", hideDegree);
156606
+ canvas2.off("mouse:up", hideDegree);
155098
156607
  canvas2.off("selection:cleared");
155099
156608
  };
155100
156609
  }
@@ -155139,6 +156648,23 @@ function setupBorderSync(canvas2) {
155139
156648
  canvas2.off("object:rotating", syncBorder);
155140
156649
  };
155141
156650
  }
156651
+ const CUSTOM_PROPERTIES = [
156652
+ "name",
156653
+ "id",
156654
+ "selectable",
156655
+ "evented",
156656
+ "hasControls",
156657
+ "lockMovementX",
156658
+ "lockMovementY",
156659
+ "__printWidth",
156660
+ "__printHeight",
156661
+ "__printDPI",
156662
+ "__baseScale",
156663
+ "__initialZoom",
156664
+ "__borderRect",
156665
+ "__rotationControlSize",
156666
+ "__showDegree"
156667
+ ];
155142
156668
  async function switchCanvasPage(canvas2, currentPage, targetPage, pageJSON, clearSelection, containerRef) {
155143
156669
  if (targetPage === currentPage) return;
155144
156670
  const historyStore = useHistoryStore.getState();
@@ -155147,8 +156673,7 @@ async function switchCanvasPage(canvas2, currentPage, targetPage, pageJSON, clea
155147
156673
  const storePages = useEditorStore.getState().pages || {};
155148
156674
  if (storePages.front && !pageJSON.front) pageJSON.front = storePages.front;
155149
156675
  if (storePages.back && !pageJSON.back) pageJSON.back = storePages.back;
155150
- const currentObject = canvas2.toObject(["name"]);
155151
- pageJSON[currentPage] = currentObject;
156676
+ pageJSON[currentPage] = canvas2.toObject(CUSTOM_PROPERTIES);
155152
156677
  useEditorStore.getState().setPages({ ...pageJSON });
155153
156678
  clearSelection();
155154
156679
  historyStore.setPage(targetPage);
@@ -155170,6 +156695,16 @@ async function switchCanvasPage(canvas2, currentPage, targetPage, pageJSON, clea
155170
156695
  } finally {
155171
156696
  useLoaderStore.getState().stopLoading();
155172
156697
  }
156698
+ const currentVt = canvas2.viewportTransform ? [...canvas2.viewportTransform] : [canvas2.getZoom(), 0, 0, canvas2.getZoom(), 0, 0];
156699
+ canvas2.setViewportTransform(currentVt);
156700
+ const workarea = canvas2.getObjects().find((obj) => obj.name === "workarea");
156701
+ if (workarea) {
156702
+ createOverlays(canvas2, workarea);
156703
+ bringOverlaysToFront(canvas2);
156704
+ }
156705
+ canvas2.requestRenderAll();
156706
+ canvas2.fire("after:render");
156707
+ useHistoryStore.setState({ isRestoring: false });
155173
156708
  }
155174
156709
  const MIN_ZOOM = 0.09;
155175
156710
  const MAX_ZOOM = 5;
@@ -155182,53 +156717,28 @@ const fromSlider = (v2) => MIN_ZOOM * Math.pow(MAX_ZOOM / MIN_ZOOM, v2);
155182
156717
  function getWorkarea(canvas2) {
155183
156718
  return canvas2.getObjects().find((o2) => o2.name === "workarea") ?? null;
155184
156719
  }
155185
- function normalizeViewportPan(canvas2) {
155186
- const vt2 = canvas2.viewportTransform;
155187
- const zoom = vt2[0];
155188
- const panX = vt2[4];
155189
- const panY = vt2[5];
155190
- if (Math.abs(panX) < 0.01 && Math.abs(panY) < 0.01) return;
155191
- const dx = panX / zoom;
155192
- const dy = panY / zoom;
155193
- canvas2.getObjects().forEach((obj) => {
155194
- obj.set({
155195
- left: (obj.left ?? 0) + dx,
155196
- top: (obj.top ?? 0) + dy
155197
- });
155198
- obj.setCoords();
155199
- });
155200
- canvas2.setViewportTransform([zoom, 0, 0, zoom, 0, 0]);
155201
- }
155202
156720
  function doZoom(canvas2, newZoom, pivot) {
155203
156721
  const clamped = clamp(newZoom, MIN_ZOOM, MAX_ZOOM);
155204
156722
  canvas2.zoomToPoint(pivot, clamped);
155205
- normalizeViewportPan(canvas2);
155206
156723
  const workarea = getWorkarea(canvas2);
155207
156724
  if (workarea) createOverlays(canvas2, workarea);
156725
+ const activeObject = canvas2.getActiveObject();
156726
+ if (activeObject) configureObjectControls(activeObject);
155208
156727
  canvas2.requestRenderAll();
155209
156728
  return clamped;
155210
156729
  }
155211
156730
  function doReset(canvas2, setZoom) {
155212
- const center = new ot(canvas2.getWidth() / 2, canvas2.getHeight() / 2);
155213
- canvas2.zoomToPoint(center, 1);
155214
- normalizeViewportPan(canvas2);
155215
156731
  const workarea = getWorkarea(canvas2);
156732
+ const initialZoom = workarea ? workarea.__initialZoom || 1 : 1;
156733
+ const center = new ot(canvas2.getWidth() / 2, canvas2.getHeight() / 2);
156734
+ canvas2.zoomToPoint(center, initialZoom);
155216
156735
  if (workarea) {
155217
- const waW = workarea.getScaledWidth();
155218
- const waH = workarea.getScaledHeight();
155219
- const dx = canvas2.getWidth() / 2 - ((workarea.left ?? 0) + waW / 2);
155220
- const dy = canvas2.getHeight() / 2 - ((workarea.top ?? 0) + waH / 2);
155221
- canvas2.getObjects().forEach((obj) => {
155222
- obj.set({
155223
- left: (obj.left ?? 0) + dx,
155224
- top: (obj.top ?? 0) + dy
155225
- });
155226
- obj.setCoords();
155227
- });
155228
- createOverlays(canvas2, getWorkarea(canvas2));
156736
+ createOverlays(canvas2, workarea);
155229
156737
  }
156738
+ const activeObject = canvas2.getActiveObject();
156739
+ if (activeObject) configureObjectControls(activeObject);
155230
156740
  canvas2.requestRenderAll();
155231
- setZoom(1);
156741
+ setZoom(initialZoom);
155232
156742
  }
155233
156743
  function useCanvasZoom(canvas2) {
155234
156744
  const [zoom, setZoom] = useState(1);
@@ -155555,7 +157065,7 @@ function useKeyboardEvents(canvas2) {
155555
157065
  left: (clipboardRef.current.left ?? 0) + OFFSET,
155556
157066
  top: (clipboardRef.current.top ?? 0) + OFFSET
155557
157067
  });
155558
- if (clonedObj.type === "activeselection") {
157068
+ if (clonedObj.type?.toLowerCase() === "activeselection") {
155559
157069
  clonedObj.canvas = canvas2;
155560
157070
  clonedObj.forEachObject((obj) => {
155561
157071
  canvas2.add(obj);
@@ -155611,8 +157121,11 @@ function Workspace() {
155611
157121
  if (fabricRef.current) return;
155612
157122
  let isMounted = true;
155613
157123
  const setupCanvasWithProduct = async () => {
157124
+ const designType = (getQueryParam("designType") || getQueryParam("designtype") || getQueryParam("type"))?.toLowerCase();
157125
+ const isTextDesign = designType === "text";
155614
157126
  const productId = getQueryParam("id") || getQueryParam("productId");
155615
- if (!productId) {
157127
+ const templateId = getQueryParam("templateId") || (isTextDesign ? getQueryParam("id") : null);
157128
+ if (!productId && !isTextDesign) {
155616
157129
  useProductStore.setState({
155617
157130
  isNotFound: true,
155618
157131
  error: "Product ID is missing from URL query parameters (e.g. ?id=...)."
@@ -155621,9 +157134,26 @@ function Workspace() {
155621
157134
  }
155622
157135
  useLoaderStore.getState().startLoading();
155623
157136
  try {
155624
- const product = await useProductStore.getState().fetchProduct(productId);
155625
- if (!product || !product._id) {
155626
- return;
157137
+ let product = null;
157138
+ if (isTextDesign) {
157139
+ product = {
157140
+ _id: "text-template-product",
157141
+ bleedArea: 0,
157142
+ trimArea: 0,
157143
+ safetyArea: 0,
157144
+ hideGuidelines: true,
157145
+ isTextTemplate: true,
157146
+ optionsMap: {
157147
+ size: '2" x 2"',
157148
+ productorientation: "Horizontal"
157149
+ }
157150
+ };
157151
+ useProductStore.setState({ productData: product, isNotFound: false, error: null });
157152
+ } else if (productId) {
157153
+ product = await useProductStore.getState().fetchProduct(productId);
157154
+ if (!product || !product._id) {
157155
+ return;
157156
+ }
155627
157157
  }
155628
157158
  if (!isMounted || !canvasRef.current || !containerRef.current || fabricRef.current) return;
155629
157159
  initializeRotateImage();
@@ -155641,11 +157171,20 @@ function Workspace() {
155641
157171
  clearSelection
155642
157172
  );
155643
157173
  const cleanupObjectAdded = setupObjectAddedHandler(canvas2);
155644
- const twoSided = product?.optionsMap?.printing === "4/4";
155645
- const templateId = getQueryParam("templateId");
155646
- let frontData = null;
155647
- let backData = null;
155648
- if (templateId) {
157174
+ if (isTextDesign && templateId) {
157175
+ const item = await ShapeService.getTextTemplateById(templateId);
157176
+ if (item?.jsonData) {
157177
+ const parsed = typeof item.jsonData === "string" ? JSON.parse(item.jsonData) : item.jsonData;
157178
+ pageJSON.current.front = parsed;
157179
+ useHistoryStore.setState({ isRestoring: true });
157180
+ await loadPageToCanvas(canvas2, parsed);
157181
+ useHistoryStore.setState({ isRestoring: false });
157182
+ useHistoryStore.getState().saveState();
157183
+ }
157184
+ } else if (templateId) {
157185
+ const twoSided = product?.optionsMap?.printing === "4/4";
157186
+ let frontData = null;
157187
+ let backData = null;
155649
157188
  const res = await TemplateDesignService.getTemplateDesignById(templateId);
155650
157189
  const tpl = res?.data;
155651
157190
  if (tpl?.pages && Array.isArray(tpl.pages)) {
@@ -155654,16 +157193,16 @@ function Workspace() {
155654
157193
  if (frontPage?.designData) frontData = frontPage.designData;
155655
157194
  if (backPage?.designData) backData = backPage.designData;
155656
157195
  }
155657
- }
155658
- if (frontData) {
155659
- pageJSON.current.front = frontData;
155660
- useHistoryStore.setState({ isRestoring: true });
155661
- await loadPageToCanvas(canvas2, frontData);
155662
- useHistoryStore.setState({ isRestoring: false });
155663
- useHistoryStore.getState().saveState();
155664
- }
155665
- if (twoSided && backData) {
155666
- pageJSON.current.back = backData;
157196
+ if (frontData) {
157197
+ pageJSON.current.front = frontData;
157198
+ useHistoryStore.setState({ isRestoring: true });
157199
+ await loadPageToCanvas(canvas2, frontData);
157200
+ useHistoryStore.setState({ isRestoring: false });
157201
+ useHistoryStore.getState().saveState();
157202
+ }
157203
+ if (twoSided && backData) {
157204
+ pageJSON.current.back = backData;
157205
+ }
155667
157206
  }
155668
157207
  useEditorStore.getState().setPages({ ...pageJSON.current });
155669
157208
  return () => {