vibrant-tool 0.1.3 → 0.1.4

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" }),
@@ -99855,8 +99881,13 @@ function setQueryParam(key, value) {
99855
99881
  const TemplateDesignService = {
99856
99882
  async getTemplates(data) {
99857
99883
  try {
99884
+ const params = new URLSearchParams();
99885
+ if (data.product) params.append("product", data.product);
99886
+ params.append("status", "true");
99887
+ params.append("page", String(data.page));
99888
+ params.append("limit", String(data.limit));
99858
99889
  const res = await apiFetch(
99859
- `/api/template-designs?product=${data.product}&status=true&page=${data.page}&limit=${data.limit}`,
99890
+ `/api/template-designs?${params.toString()}`,
99860
99891
  { method: "GET" }
99861
99892
  );
99862
99893
  return res || null;
@@ -99893,6 +99924,100 @@ const TemplateDesignService = {
99893
99924
  }
99894
99925
  }
99895
99926
  };
99927
+ const ShapeService = {
99928
+ async getAll(data) {
99929
+ const res = await apiFetch(
99930
+ `/api/cliparts?page=${data.page}&limit=${data.limit}&category=${data.category}&status=true`,
99931
+ {
99932
+ method: "GET"
99933
+ }
99934
+ );
99935
+ return {
99936
+ items: res.data || []
99937
+ };
99938
+ },
99939
+ async getAllExternal(data) {
99940
+ const res = await apiFetch(
99941
+ `/api/external/images?page=${data.page}&limit=${data.limit}&keyType=${data.keyType}&status=true${data.query ? `&query=${data.query}` : ""}`,
99942
+ {
99943
+ method: "GET"
99944
+ }
99945
+ );
99946
+ return {
99947
+ items: res.data || []
99948
+ };
99949
+ },
99950
+ async getTextTemplates(data) {
99951
+ const res = await apiFetch(
99952
+ `/api/text-templates?page=${data.page}&limit=${data.limit}&status=true`,
99953
+ {
99954
+ method: "GET"
99955
+ }
99956
+ );
99957
+ return {
99958
+ items: res.data || []
99959
+ };
99960
+ },
99961
+ async getTextTemplateById(id) {
99962
+ const res = await apiFetch(
99963
+ `/api/text-templates/${id}`,
99964
+ {
99965
+ method: "GET"
99966
+ }
99967
+ );
99968
+ return res.data || null;
99969
+ },
99970
+ async updateTextTemplate(id, body) {
99971
+ const res = await apiFetch(`/api/text-templates/${id}`, {
99972
+ method: "PUT",
99973
+ body
99974
+ });
99975
+ return res;
99976
+ },
99977
+ async convertImages(url) {
99978
+ const res = await apiFetch(`/api/convert-images`, {
99979
+ method: "POST",
99980
+ body: { file: url }
99981
+ });
99982
+ return res;
99983
+ }
99984
+ };
99985
+ function dataUrlToFile(dataUrl, filename = `design-${Date.now()}.png`) {
99986
+ const arr = dataUrl.split(",");
99987
+ const mime = arr[0].match(/:(.*?);/)?.[1] || "image/png";
99988
+ const bstr = atob(arr[1]);
99989
+ let n2 = bstr.length;
99990
+ const u8arr = new Uint8Array(n2);
99991
+ while (n2--) {
99992
+ u8arr[n2] = bstr.charCodeAt(n2);
99993
+ }
99994
+ return new File([u8arr], filename, { type: mime });
99995
+ }
99996
+ const UploadService = {
99997
+ async uploadFileApi(file, filename = `design-${Date.now()}.png`) {
99998
+ const formData = new FormData();
99999
+ formData.append("file", file, file.name || filename);
100000
+ formData.append("name", "vibrantprint");
100001
+ const res = await apiUploadImage(`/upload`, {
100002
+ method: "POST",
100003
+ body: formData
100004
+ });
100005
+ const data = res?.data || res;
100006
+ const url = data?.name || data?.Location || data?.url || data?.data?.name || data?.data?.Location || data?.data?.url || (typeof data === "string" ? data : "");
100007
+ if (!url) {
100008
+ throw new Error(res?.message || "Failed to upload file");
100009
+ }
100010
+ return url;
100011
+ },
100012
+ async uploadDataUrl(dataUrl, filename) {
100013
+ if (!dataUrl) return "";
100014
+ if (dataUrl.startsWith("http://") || dataUrl.startsWith("https://")) {
100015
+ return dataUrl;
100016
+ }
100017
+ const file = dataUrlToFile(dataUrl, filename || `design-${Date.now()}.png`);
100018
+ return await this.uploadFileApi(file, filename);
100019
+ }
100020
+ };
99896
100021
  function Topbar() {
99897
100022
  const [open2, setOpen] = useState(false);
99898
100023
  const [previewOpen, setPreviewOpen] = useState(false);
@@ -99900,8 +100025,40 @@ function Topbar() {
99900
100025
  const [saveSuccess, setSaveSuccess] = useState(false);
99901
100026
  const [saveError, setSaveError] = useState(false);
99902
100027
  const resetTour = useTourStore((s2) => s2.reset);
99903
- const templateId = getQueryParam("templateId");
99904
- const designType = getQueryParam("design");
100028
+ const templateId = getQueryParam("templateId") || getQueryParam("id");
100029
+ const designType = (getQueryParam("designType") || getQueryParam("designtype") || getQueryParam("type"))?.toLowerCase();
100030
+ const handleSaveTextTemplate = async () => {
100031
+ const canvas2 = useEditorStore.getState().canvas;
100032
+ if (!templateId || !canvas2) return;
100033
+ try {
100034
+ setIsSaving(true);
100035
+ useLoaderStore.getState().startLoading();
100036
+ const currentJson = canvas2.toObject(["name"]);
100037
+ const currentImage = getWorkareaDataUrl(canvas2);
100038
+ const uploadedImageUrl = await UploadService.uploadDataUrl(
100039
+ currentImage,
100040
+ `text-template-${templateId}-${Date.now()}.png`
100041
+ );
100042
+ const res = await ShapeService.updateTextTemplate(templateId, {
100043
+ jsonData: currentJson,
100044
+ image: uploadedImageUrl
100045
+ });
100046
+ if (res && (res.success || res.status || res.data || res._id)) {
100047
+ setSaveSuccess(true);
100048
+ setTimeout(() => setSaveSuccess(false), 3e3);
100049
+ window.location.href = "/design-configuration/text-templates";
100050
+ } else {
100051
+ throw new Error("Save failed");
100052
+ }
100053
+ } catch (err) {
100054
+ console.error("Save text template error:", err);
100055
+ setSaveError(true);
100056
+ setTimeout(() => setSaveError(false), 3e3);
100057
+ } finally {
100058
+ setIsSaving(false);
100059
+ useLoaderStore.getState().stopLoading();
100060
+ }
100061
+ };
99905
100062
  const handleSaveTemplate = async () => {
99906
100063
  const canvas2 = useEditorStore.getState().canvas;
99907
100064
  if (!templateId || !canvas2) return;
@@ -99919,26 +100076,33 @@ function Topbar() {
99919
100076
  const designData = isCurrent ? currentJson : editorPages[side] || historyPages[side]?.history?.at(-1) || null;
99920
100077
  if (!designData) return null;
99921
100078
  const sideImage = isCurrent ? currentImage : await renderPageDataUrl(designData, canvas2);
100079
+ const uploadedImageUrl = await UploadService.uploadDataUrl(
100080
+ sideImage,
100081
+ `template-${templateId}-${side}-${Date.now()}.png`
100082
+ );
99922
100083
  return {
99923
100084
  side,
99924
100085
  designData,
99925
- image: sideImage,
100086
+ image: uploadedImageUrl,
99926
100087
  order: i + 1
99927
100088
  };
99928
100089
  })
99929
100090
  );
99930
100091
  const validPages = pages.filter(Boolean);
99931
- const mainImage = validPages[0]?.image || currentImage;
100092
+ const primaryImage = validPages[0]?.image || "";
99932
100093
  const res = await TemplateDesignService.updateTemplateDesign(templateId, {
99933
- pages: validPages
100094
+ pages: validPages,
100095
+ image: primaryImage
99934
100096
  });
99935
100097
  if (res && res.success) {
99936
100098
  setSaveSuccess(true);
99937
100099
  setTimeout(() => setSaveSuccess(false), 3e3);
100100
+ window.location.href = "/design-configuration/template-designs";
99938
100101
  } else {
99939
100102
  throw new Error("Save failed");
99940
100103
  }
99941
- } catch {
100104
+ } catch (err) {
100105
+ console.error("Save template error:", err);
99942
100106
  setSaveError(true);
99943
100107
  setTimeout(() => setSaveError(false), 3e3);
99944
100108
  } finally {
@@ -99964,17 +100128,20 @@ function Topbar() {
99964
100128
  ]
99965
100129
  }
99966
100130
  ),
99967
- templateId && designType == "template" && /* @__PURE__ */ jsx(
100131
+ templateId && (designType === "template" || designType === "text") && /* @__PURE__ */ jsx(
99968
100132
  "button",
99969
100133
  {
99970
100134
  id: "save-template-button",
99971
- onClick: handleSaveTemplate,
100135
+ onClick: designType === "text" ? handleSaveTextTemplate : handleSaveTemplate,
99972
100136
  disabled: isSaving,
99973
100137
  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
100138
  children: isSaving ? /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1.5", children: [
99975
100139
  /* @__PURE__ */ jsx("span", { className: "h-3 w-3 border-2 border-white border-t-transparent rounded-full animate-spin" }),
99976
100140
  "Saving..."
99977
- ] }) : saveSuccess ? /* @__PURE__ */ jsx("span", { children: "✓ Saved!" }) : saveError ? /* @__PURE__ */ jsx("span", { children: "Save Failed" }) : /* @__PURE__ */ jsx("span", { children: "Save Template" })
100141
+ ] }) : saveSuccess ? /* @__PURE__ */ jsx("span", { children: "✓ Saved!" }) : saveError ? /* @__PURE__ */ jsx("span", { children: "Save Failed" }) : /* @__PURE__ */ jsxs("span", { children: [
100142
+ "Save ",
100143
+ designType === "text" ? "Text Template" : "Template"
100144
+ ] })
99978
100145
  }
99979
100146
  ),
99980
100147
  /* @__PURE__ */ jsxs("div", { className: "relative flex items-center", children: [
@@ -100276,20 +100443,39 @@ function Panel({ panelName }) {
100276
100443
  function deriveType(objects) {
100277
100444
  if (objects.length === 0) return "none";
100278
100445
  if (objects.length > 1) return "multiple";
100279
- const t2 = objects[0].type;
100280
- if (t2 === "group") return "group";
100446
+ const obj = objects[0];
100447
+ const t2 = obj.type?.toLowerCase();
100448
+ if (t2 === "activeselection") return "multiple";
100449
+ if (t2 === "group") {
100450
+ if (obj.name === "qr") return "qr";
100451
+ if (obj.name === "barcode") return "barcode";
100452
+ return "group";
100453
+ }
100281
100454
  if (t2 === "textbox" || t2 === "i-text" || t2 === "text") return "textbox";
100282
100455
  if (t2 === "image") return "image";
100283
- return t2;
100456
+ return t2 || "none";
100284
100457
  }
100285
100458
  const useSelectionStore = create((set) => ({
100286
100459
  type: "none",
100287
100460
  objects: [],
100461
+ activeColor: null,
100462
+ activeColorIndex: 0,
100288
100463
  setSelection: (objects) => set({
100289
100464
  objects,
100290
- type: deriveType(objects)
100465
+ type: deriveType(objects),
100466
+ activeColor: null,
100467
+ activeColorIndex: 0
100291
100468
  }),
100292
- clearSelection: () => set({ type: "none", objects: [] })
100469
+ setActiveColor: (color, index = 0) => set({
100470
+ activeColor: color,
100471
+ activeColorIndex: index
100472
+ }),
100473
+ clearSelection: () => set({
100474
+ type: "none",
100475
+ objects: [],
100476
+ activeColor: null,
100477
+ activeColorIndex: 0
100478
+ })
100293
100479
  }));
100294
100480
  function TemplatesPanel() {
100295
100481
  const { productData } = useProductStore();
@@ -100851,27 +101037,23 @@ function updateQRObjectColors(qrGroup, darkColor, lightColor) {
100851
101037
  });
100852
101038
  qrGroup.canvas?.requestRenderAll();
100853
101039
  }
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
- }
101040
+ const MAX_RECENT_ELEMENTS = 15;
101041
+ const useRecentElementsStore = create()(
101042
+ persist(
101043
+ (set) => ({
101044
+ recentItems: [],
101045
+ addRecentItem: (item) => set((state) => ({
101046
+ recentItems: [
101047
+ item,
101048
+ ...state.recentItems.filter((recent) => recent.image !== item.image)
101049
+ ].slice(0, MAX_RECENT_ELEMENTS)
101050
+ }))
101051
+ }),
101052
+ {
101053
+ name: "vibrant-recent-elements"
100871
101054
  }
100872
- });
100873
- return [...colors];
100874
- }
101055
+ )
101056
+ );
100875
101057
  function AllElements({
100876
101058
  elements: elements2,
100877
101059
  onBack,
@@ -100880,6 +101062,7 @@ function AllElements({
100880
101062
  }) {
100881
101063
  const [search, setSearch] = useState("");
100882
101064
  const { isLoading } = useLoaderStore.getState();
101065
+ const { addRecentItem } = useRecentElementsStore();
100883
101066
  const clearSearch = () => {
100884
101067
  setSearch("");
100885
101068
  onSearch("");
@@ -100956,6 +101139,7 @@ function AllElements({
100956
101139
  } else {
100957
101140
  addSVGToCanvas("svg", shape.image);
100958
101141
  }
101142
+ addRecentItem(shape);
100959
101143
  if (window.innerWidth < 768) {
100960
101144
  useSidePanelStore.getState().setActivePanel(null);
100961
101145
  }
@@ -100978,50 +101162,6 @@ function AllElements({
100978
101162
  ] }) }) })
100979
101163
  ] });
100980
101164
  }
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
101165
  const PixabayIcon = () => /* @__PURE__ */ jsx(
101026
101166
  "svg",
101027
101167
  {
@@ -101046,6 +101186,7 @@ const PixabayIcon = () => /* @__PURE__ */ jsx(
101046
101186
  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
101187
  function ElementsPanel() {
101048
101188
  const { startLoading, stopLoading, isLoading } = useLoaderStore();
101189
+ const { recentItems, addRecentItem } = useRecentElementsStore();
101049
101190
  const [elementsSVG, setElementsSVG] = useState([]);
101050
101191
  const [page, setPage] = useState(1);
101051
101192
  const [category, setCategory] = useState(null);
@@ -101155,45 +101296,96 @@ function ElementsPanel() {
101155
101296
  ] })
101156
101297
  ] }),
101157
101298
  /* @__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",
101299
+ /* @__PURE__ */ jsxs("div", { className: "h-[calc(100dvh-180px)] overflow-auto", children: [
101300
+ recentItems.length > 0 && /* @__PURE__ */ jsxs("div", { className: "mx-4", children: [
101301
+ /* @__PURE__ */ jsxs("div", { className: "py-5", children: [
101302
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
101303
+ /* @__PURE__ */ jsx("h2", { className: "text-sm font-semibold mb-3", children: "Recent items" }),
101304
+ recentItems.length > 3 && /* @__PURE__ */ jsx(
101305
+ "div",
101306
+ {
101307
+ className: "underline cursor-pointer",
101308
+ onClick: () => {
101309
+ setElementsSVG(recentItems);
101310
+ setCategory("Recent items");
101311
+ setHasMore(false);
101312
+ },
101313
+ children: "View all"
101314
+ }
101315
+ )
101316
+ ] }),
101317
+ /* @__PURE__ */ jsx("div", { className: "grid grid-cols-4 md:grid-cols-3 gap-3", children: recentItems.slice(0, 3).map((shape, i) => /* @__PURE__ */ jsx(
101318
+ "button",
101164
101319
  {
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);
101320
+ onClick: () => {
101321
+ addSVGToCanvas("svg", shape.image);
101322
+ addRecentItem(shape);
101323
+ },
101324
+ className: "w-full cursor-pointer flex items-center justify-center",
101325
+ children: /* @__PURE__ */ jsx(
101326
+ "img",
101327
+ {
101328
+ src: shape.image,
101329
+ alt: shape.category,
101330
+ width: 150,
101331
+ height: 150,
101332
+ className: "w-full h-24 object-contain"
101333
+ }
101334
+ )
101176
101335
  },
101177
- className: "w-full cursor-pointer flex items-center justify-center",
101178
- children: /* @__PURE__ */ jsx(
101179
- "img",
101336
+ shape._id || shape.image || i
101337
+ )) })
101338
+ ] }),
101339
+ /* @__PURE__ */ jsx("hr", { className: "text-neutral-800 h-[0.5px]" })
101340
+ ] }),
101341
+ elements.map((item, i) => /* @__PURE__ */ jsxs("div", { className: "mx-4", children: [
101342
+ /* @__PURE__ */ jsxs("div", { className: "py-5", children: [
101343
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
101344
+ /* @__PURE__ */ jsx("h2", { className: "text-sm font-semibold mb-3", children: item.category }),
101345
+ /* @__PURE__ */ jsx(
101346
+ "div",
101180
101347
  {
101181
- src: shape.image,
101182
- alt: item.category,
101183
- width: 150,
101184
- height: 150,
101185
- className: "w-full h-24 object-contain"
101348
+ className: "underline cursor-pointer",
101349
+ onClick: () => getSVG(null, item?.category, 1),
101350
+ children: "View all"
101186
101351
  }
101187
101352
  )
101188
- },
101189
- j2
101190
- )) })
101191
- ] }),
101192
- /* @__PURE__ */ jsx("hr", { className: "text-neutral-800 h-[0.5px]" })
101193
- ] }, i)) })
101353
+ ] }),
101354
+ /* @__PURE__ */ jsx("div", { className: "grid grid-cols-4 md:grid-cols-3 gap-3", children: item.data.map((shape, j2) => /* @__PURE__ */ jsx(
101355
+ "button",
101356
+ {
101357
+ onClick: () => {
101358
+ addSVGToCanvas("svg", shape.image);
101359
+ addRecentItem({
101360
+ _id: shape.image,
101361
+ status: true,
101362
+ category: item.category,
101363
+ image: shape.image,
101364
+ largeImage: shape.image,
101365
+ tags: []
101366
+ });
101367
+ },
101368
+ className: "w-full cursor-pointer flex items-center justify-center",
101369
+ children: /* @__PURE__ */ jsx(
101370
+ "img",
101371
+ {
101372
+ src: shape.image,
101373
+ alt: item.category,
101374
+ width: 150,
101375
+ height: 150,
101376
+ className: "w-full h-24 object-contain"
101377
+ }
101378
+ )
101379
+ },
101380
+ j2
101381
+ )) })
101382
+ ] }),
101383
+ /* @__PURE__ */ jsx("hr", { className: "text-neutral-800 h-[0.5px]" })
101384
+ ] }, i))
101385
+ ] })
101194
101386
  ] });
101195
101387
  }
101196
- const isTextbox = (obj) => obj.type === "textbox";
101388
+ const isTextbox = (obj) => obj.type === "textbox" || obj.type === "i-text" || obj.type === "text";
101197
101389
  async function addTextToCanvas(fontSize, width, textName) {
101198
101390
  const canvas2 = useEditorStore.getState().canvas;
101199
101391
  if (!canvas2) return;
@@ -101274,13 +101466,10 @@ function TextPanel() {
101274
101466
  page,
101275
101467
  limit: 30
101276
101468
  }).then((res) => {
101277
- const newItems = res.items.filter(
101278
- (x2) => x2.status && x2.image
101279
- );
101280
101469
  setTextElements(
101281
- (prev) => page === 1 ? newItems : [...prev, ...newItems]
101470
+ (prev) => page === 1 ? res.items : [...prev, ...res.items]
101282
101471
  );
101283
- if (newItems.length < 20) setHasMore(false);
101472
+ if (res?.items?.length < 20) setHasMore(false);
101284
101473
  }).finally(() => stopLoading());
101285
101474
  }, [page]);
101286
101475
  useEffect(() => {
@@ -101714,10 +101903,19 @@ function PhotosPanel() {
101714
101903
  handleFacebookAuth,
101715
101904
  handleInstagramAuth
101716
101905
  } = useSocialAuth({
101717
- onImageSelect: (url) => {
101718
- addImageToCanvas(url);
101719
- if (window.innerWidth < 768) {
101720
- useSidePanelStore.getState().setActivePanel(null);
101906
+ onImageSelect: async (url) => {
101907
+ try {
101908
+ startLoading();
101909
+ const image = await ShapeService.convertImages(url);
101910
+ const imageUrl = image?.success ? image?.url : null;
101911
+ if (imageUrl) addImageToCanvas(imageUrl);
101912
+ } catch (error2) {
101913
+ console.error("Failed to convert image:", error2);
101914
+ } finally {
101915
+ stopLoading();
101916
+ if (window.innerWidth < 768) {
101917
+ useSidePanelStore.getState().setActivePanel(null);
101918
+ }
101721
101919
  }
101722
101920
  }
101723
101921
  });
@@ -101842,10 +102040,20 @@ function PhotosPanel() {
101842
102040
  "button",
101843
102041
  {
101844
102042
  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);
102043
+ onClick: async () => {
102044
+ try {
102045
+ startLoading();
102046
+ const image = await ShapeService.convertImages(item.largeImage);
102047
+ const imageUrl = image?.success ? image?.url : null;
102048
+ if (!imageUrl) return;
102049
+ addImageToCanvas(imageUrl);
102050
+ if (window.innerWidth < 768) {
102051
+ useSidePanelStore.getState().setActivePanel(null);
102052
+ }
102053
+ } catch (error2) {
102054
+ console.error("Failed to convert image:", error2);
102055
+ } finally {
102056
+ stopLoading();
101849
102057
  }
101850
102058
  },
101851
102059
  children: /* @__PURE__ */ jsx(
@@ -101912,319 +102120,281 @@ async function addBackgroundColor(color) {
101912
102120
  workarea.set("fill", color);
101913
102121
  canvas2.requestRenderAll();
101914
102122
  }
102123
+ function normalizeHexColor(c2) {
102124
+ if (!c2 || typeof c2 !== "string" || c2 === "none" || c2 === "transparent" || c2.trim() === "") return null;
102125
+ const trimmed = c2.trim();
102126
+ try {
102127
+ const hex = new Ie(trimmed).toHex();
102128
+ if (hex && hex.length === 6) {
102129
+ return `#${hex.toLowerCase()}`;
102130
+ }
102131
+ } catch {
102132
+ }
102133
+ if (trimmed.startsWith("#")) {
102134
+ let raw = trimmed.replace(/^#/, "");
102135
+ if (raw.length === 3) raw = raw[0] + raw[0] + raw[1] + raw[1] + raw[2] + raw[2];
102136
+ if (raw.length === 6) return `#${raw.toLowerCase()}`;
102137
+ }
102138
+ return null;
102139
+ }
102140
+ function extractAllTargetColors(targets) {
102141
+ if (!targets || !targets.length) return [];
102142
+ const colors = [];
102143
+ const seen = /* @__PURE__ */ new Set();
102144
+ const addColor = (raw) => {
102145
+ const norm = normalizeHexColor(raw);
102146
+ if (norm && !seen.has(norm)) {
102147
+ seen.add(norm);
102148
+ colors.push(norm);
102149
+ }
102150
+ };
102151
+ const traverse = (obj) => {
102152
+ if (!obj || obj.name === "workarea") return;
102153
+ if (obj.type === "image" && obj.name !== "qr" && obj.name !== "barcode" && !obj._objects) {
102154
+ return;
102155
+ }
102156
+ if (obj.type === "group" && obj.name === "qr") {
102157
+ if (obj.__qrDark) addColor(obj.__qrDark);
102158
+ if (obj.__qrLight) addColor(obj.__qrLight);
102159
+ return;
102160
+ }
102161
+ if (obj.type === "group" && obj.name === "barcode") {
102162
+ if (obj.__barcodeForeground) addColor(obj.__barcodeForeground);
102163
+ if (obj.__barcodeBackground) addColor(obj.__barcodeBackground);
102164
+ return;
102165
+ }
102166
+ if (obj.type === "group" || obj._objects) {
102167
+ const children = obj.getObjects ? obj.getObjects() : obj._objects || [];
102168
+ children.forEach(traverse);
102169
+ return;
102170
+ }
102171
+ const fill2 = obj.get?.("fill") ?? obj.fill;
102172
+ if (typeof fill2 === "string" && fill2 !== "none" && fill2 !== "transparent" && fill2 !== "") {
102173
+ addColor(fill2);
102174
+ }
102175
+ const stroke2 = obj.get?.("stroke") ?? obj.stroke;
102176
+ const strokeWidth = obj.get?.("strokeWidth") ?? obj.strokeWidth;
102177
+ if (typeof stroke2 === "string" && stroke2 !== "none" && stroke2 !== "transparent" && stroke2 !== "" && (typeof strokeWidth !== "number" || strokeWidth > 0)) {
102178
+ addColor(stroke2);
102179
+ }
102180
+ };
102181
+ targets.forEach(traverse);
102182
+ return colors;
102183
+ }
101915
102184
  function extractTargetColor(targets) {
101916
102185
  if (!targets.length) return {};
102186
+ const allColors = extractAllTargetColors(targets);
102187
+ const primaryColor = allColors[0] || void 0;
101917
102188
  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;
102189
+ let cmyk2 = obj?.__cmyk;
102190
+ if (primaryColor && !cmyk2) {
102191
+ cmyk2 = hexToCmyk(primaryColor);
101934
102192
  }
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 };
102193
+ const opacity = typeof obj?.opacity === "number" ? Math.round(obj.opacity * 100) : void 0;
102194
+ return { color: primaryColor, opacity, cmyk: cmyk2 };
101942
102195
  }
101943
- function applyFillToFabricObjects(objects, newColor, alpha, currentColor, canvas2, cmykValues) {
102196
+ function applyFillToFabricObjects(objects, newColor, alpha, targetOldColor, canvas2, cmykValues) {
101944
102197
  if (!canvas2 || !objects.length) return;
101945
- const resolvedCmyk = cmykValues || hexToCmyk(newColor);
102198
+ const targetNewHex = normalizeHexColor(newColor) || newColor.toLowerCase();
102199
+ const targetOldHex = targetOldColor ? normalizeHexColor(targetOldColor) : null;
102200
+ const resolvedCmyk = cmykValues || hexToCmyk(targetNewHex);
101946
102201
  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));
102202
+ if (typeof alpha === "number") {
102203
+ obj.set("opacity", alpha / 100);
102204
+ }
102205
+ if (obj.type === "group" && obj.name === "qr") {
102206
+ const dark = normalizeHexColor(obj.__qrDark) || "#000000";
102207
+ const light = normalizeHexColor(obj.__qrLight) || "#ffffff";
102208
+ let newDark = dark;
102209
+ let newLight = light;
102210
+ if (targetOldHex) {
102211
+ if (dark === targetOldHex) newDark = targetNewHex;
102212
+ if (light === targetOldHex) newLight = targetNewHex;
102213
+ } else {
102214
+ newDark = targetNewHex;
102215
+ }
102216
+ updateQRObjectColors(obj, newDark, newLight);
102217
+ return;
102218
+ }
102219
+ if (obj.type === "group" && obj.name === "barcode") {
102220
+ const fg = normalizeHexColor(obj.__barcodeForeground) || "#000000";
102221
+ const bg = normalizeHexColor(obj.__barcodeBackground) || "#ffffff";
102222
+ let newFg = fg;
102223
+ let newBg = bg;
102224
+ if (targetOldHex) {
102225
+ if (fg === targetOldHex) newFg = targetNewHex;
102226
+ if (bg === targetOldHex) newBg = targetNewHex;
102227
+ } else {
102228
+ newFg = targetNewHex;
102229
+ }
102230
+ updateBarcodeObjectColors(obj, newFg, newBg);
102231
+ return;
102232
+ }
102233
+ if (obj.type === "textbox" || obj.type === "i-text" || obj.type === "text") {
102234
+ obj.set("fill", targetNewHex);
102235
+ obj.set("__cmyk", { ...resolvedCmyk });
102236
+ obj.setCoords();
102237
+ return;
102238
+ }
102239
+ if (obj.type === "group" || obj._objects) {
102240
+ const replaceRecursive = (child) => {
102241
+ if (!child) return;
102242
+ if (typeof child.fill === "string") {
102243
+ const childFillHex = normalizeHexColor(child.fill);
102244
+ if (!targetOldHex || childFillHex === targetOldHex) {
102245
+ child.set("fill", targetNewHex);
102246
+ child.__cmyk = { ...resolvedCmyk };
102247
+ }
102248
+ }
102249
+ if (typeof child.stroke === "string") {
102250
+ const childStrokeHex = normalizeHexColor(child.stroke);
102251
+ if (targetOldHex && childStrokeHex === targetOldHex) {
102252
+ child.set("stroke", targetNewHex);
102253
+ child.__cmyk = { ...resolvedCmyk };
102254
+ }
102255
+ }
102256
+ if (child.type === "group" || child._objects) {
102257
+ const sub = child.getObjects ? child.getObjects() : child._objects || [];
102258
+ sub.forEach(replaceRecursive);
101960
102259
  }
101961
102260
  };
101962
- if (obj._objects) {
101963
- obj._objects.forEach((child) => applyColorRecursive(child));
101964
- }
102261
+ const children = obj.getObjects ? obj.getObjects() : obj._objects || [];
102262
+ children.forEach(replaceRecursive);
101965
102263
  } else {
101966
- obj.set("fill", newColor);
102264
+ const currentFillHex = normalizeHexColor(obj.fill);
102265
+ if (!targetOldHex || currentFillHex === targetOldHex) {
102266
+ obj.set("fill", targetNewHex);
102267
+ obj.set("__cmyk", { ...resolvedCmyk });
102268
+ }
102269
+ const currentStrokeHex = normalizeHexColor(obj.stroke);
102270
+ if (targetOldHex && currentStrokeHex === targetOldHex) {
102271
+ obj.set("stroke", targetNewHex);
102272
+ obj.set("__cmyk", { ...resolvedCmyk });
102273
+ }
101967
102274
  }
101968
102275
  obj.setCoords();
101969
102276
  });
101970
102277
  canvas2.requestRenderAll();
101971
102278
  }
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
- }) {
102279
+ const STEP$1 = 1;
102280
+ const STEP_LARGE = 10;
102281
+ function ChannelSlider({ value, min, max, gradient, ariaLabel, onChange, onCommit, className, thumbClassName }) {
102282
+ const trackRef = useRef(null);
102283
+ function pick(event) {
102284
+ const element = trackRef.current;
102285
+ if (!element) return;
102286
+ const bounds = element.getBoundingClientRect();
102287
+ const positionRatio = Math.max(0, Math.min(1, (event.clientX - bounds.left) / bounds.width));
102288
+ onChange(Math.round(min + positionRatio * (max - min)));
102289
+ }
102290
+ function nudge(event) {
102291
+ let next = value;
102292
+ switch (event.key) {
102293
+ case "ArrowRight":
102294
+ case "ArrowUp":
102295
+ next += event.shiftKey ? STEP_LARGE : STEP$1;
102296
+ break;
102297
+ case "ArrowLeft":
102298
+ case "ArrowDown":
102299
+ next -= event.shiftKey ? STEP_LARGE : STEP$1;
102300
+ break;
102301
+ case "PageUp":
102302
+ next += STEP_LARGE;
102303
+ break;
102304
+ case "PageDown":
102305
+ next -= STEP_LARGE;
102306
+ break;
102307
+ case "Home":
102308
+ next = min;
102309
+ break;
102310
+ case "End":
102311
+ next = max;
102312
+ break;
102313
+ default:
102314
+ return;
102315
+ }
102316
+ event.preventDefault();
102317
+ next = Math.max(min, Math.min(max, next));
102318
+ if (next !== value) onChange(next);
102319
+ }
102320
+ const percent = (value - min) / (max - min) * 100;
101993
102321
  return /* @__PURE__ */ jsx(
101994
102322
  "div",
101995
102323
  {
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})`
102324
+ ref: trackRef,
102325
+ className: className ? `pqc-slider ${className}` : "pqc-slider",
102326
+ style: { ["--pqc-_grad"]: gradient },
102327
+ role: "slider",
102328
+ tabIndex: 0,
102329
+ "aria-label": ariaLabel,
102330
+ "aria-valuemin": min,
102331
+ "aria-valuemax": max,
102332
+ "aria-valuenow": value,
102333
+ onKeyDown: nudge,
102334
+ onPointerDown: (event) => {
102335
+ event.currentTarget.setPointerCapture(event.pointerId);
102336
+ pick(event);
102000
102337
  },
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)"
102338
+ onPointerMove: (event) => {
102339
+ if (event.buttons === 0) return;
102340
+ pick(event);
102030
102341
  },
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
- )
102342
+ onPointerUp: () => onCommit?.(),
102343
+ onBlur: () => onCommit?.(),
102344
+ children: /* @__PURE__ */ jsx("div", { className: thumbClassName ? `pqc-slider-thumb ${thumbClassName}` : "pqc-slider-thumb", style: { ["--pqc-_x"]: `${percent}%` } })
102047
102345
  }
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
- ]
102119
- }
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
- ] });
102346
+ );
102182
102347
  }
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
- ),
102348
+ function ChannelRow({ label, labelColor, ariaLabel, value, min, max, gradient, onChange, onCommit, sliderClassName, sliderThumbClassName }) {
102349
+ const [rawInput, setRawInput] = useState(String(value));
102350
+ useEffect(() => {
102351
+ setRawInput(String(value));
102352
+ }, [value]);
102353
+ function commit(rawValue) {
102354
+ const parsedValue = parseInt(rawValue, 10);
102355
+ if (!isNaN(parsedValue)) onChange(Math.max(min, Math.min(max, parsedValue)));
102356
+ setRawInput(String(value));
102357
+ onCommit?.("input");
102358
+ }
102359
+ return /* @__PURE__ */ jsxs("div", { className: "pqc-channel-row", children: [
102360
+ /* @__PURE__ */ jsx("span", { className: "pqc-channel-label", style: { color: labelColor }, "aria-hidden": "true", children: label }),
102361
+ /* @__PURE__ */ jsx(ChannelSlider, { value, min, max, gradient, ariaLabel, onChange, onCommit: () => onCommit?.("slider"), className: sliderClassName, thumbClassName: sliderThumbClassName }),
102202
102362
  /* @__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",
102363
+ "input",
102214
102364
  {
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: "!"
102365
+ type: "text",
102366
+ inputMode: "numeric",
102367
+ "aria-label": ariaLabel,
102368
+ value: rawInput,
102369
+ onChange: (event) => {
102370
+ setRawInput(event.target.value);
102371
+ },
102372
+ onBlur: (event) => commit(event.target.value),
102373
+ onKeyDown: (event) => {
102374
+ if (event.key === "Enter") commit(event.target.value);
102375
+ },
102376
+ className: "pqc-channel-input"
102218
102377
  }
102219
102378
  )
102220
- ] }) });
102379
+ ] });
102221
102380
  }
102381
+ const DEFAULT_COLORS = ["#f97316", "#ef4444", "#22c55e", "#3b82f6", "#a855f7", "#ec4899", "#000000", "#ffffff"];
102382
+ const CMYK_CHANNELS$1 = [
102383
+ { key: "c", label: "C", color: "#009fe3", name: "Cyan" },
102384
+ { key: "m", label: "M", color: "#e6007e", name: "Magenta" },
102385
+ { key: "y", label: "Y", color: "#dcb000", name: "Yellow" },
102386
+ { key: "k", label: "K", color: "#222222", name: "Black" }
102387
+ ];
102222
102388
  function CMYKColorPicker({ value, onChange, className = "" } = {}) {
102223
102389
  const canvas2 = useEditorStore((s2) => s2.canvas);
102224
102390
  const saveState = useHistoryStore((s2) => s2.saveState);
102225
- const { objects } = useSelectionStore();
102226
- const targets = objects.filter((o2) => o2.name !== "workarea");
102391
+ const { objects, activeColor, activeColorIndex, setActiveColor } = useSelectionStore();
102392
+ const targets = useMemo(() => objects.filter((o2) => o2.name !== "workarea"), [objects]);
102227
102393
  const isControlled = typeof onChange === "function";
102394
+ const allTargetColors = useMemo(() => {
102395
+ if (isControlled) return [];
102396
+ return extractAllTargetColors(targets);
102397
+ }, [isControlled, targets, objects]);
102228
102398
  const initialCmyk = typeof value === "object" && value !== null ? value : typeof value === "string" ? hexToCmyk(value) : { c: 0, m: 100, y: 100, k: 0 };
102229
102399
  const initialHex = typeof value === "string" ? value : cmykToHex(initialCmyk.c, initialCmyk.m, initialCmyk.y, initialCmyk.k);
102230
102400
  const [color, setColor] = useState(initialHex);
@@ -102234,17 +102404,32 @@ function CMYKColorPicker({ value, onChange, className = "" } = {}) {
102234
102404
  return rgbToHsv(rgb2.r, rgb2.g, rgb2.b);
102235
102405
  });
102236
102406
  const [alpha, setAlpha] = useState(100);
102407
+ const [mode2, setMode] = useState("HEX");
102237
102408
  const [open2, setOpen] = useState(false);
102409
+ const [copied, setCopied] = useState(false);
102410
+ const [recentColors, setRecentColors] = useState([
102411
+ "#e11d48",
102412
+ "#9f1239",
102413
+ "#881337",
102414
+ "#a855f7",
102415
+ "#c084fc",
102416
+ "#475569",
102417
+ "#334155",
102418
+ "#22c55e",
102419
+ "#16a34a",
102420
+ "#6366f1",
102421
+ "#d97706"
102422
+ ]);
102238
102423
  const [coords, setCoords] = useState({ top: 0, left: 0 });
102239
102424
  const containerRef = useRef(null);
102240
102425
  const popoverRef = useRef(null);
102241
- const gradientRef = useRef(null);
102426
+ const svRef = useRef(null);
102242
102427
  const hueRef = useRef(null);
102243
102428
  const alphaRef = useRef(null);
102244
102429
  const updatePosition = useCallback(() => {
102245
102430
  if (!containerRef.current) return;
102246
102431
  const rect = containerRef.current.getBoundingClientRect();
102247
- const pickerW = 300, pickerH = 440;
102432
+ const pickerW = 270, pickerH = 430;
102248
102433
  let left = Math.max(10, Math.min(rect.left, window.innerWidth - pickerW - 10));
102249
102434
  let top = rect.bottom + 6;
102250
102435
  if (top + pickerH > window.innerHeight - 10) {
@@ -102255,29 +102440,23 @@ function CMYKColorPicker({ value, onChange, className = "" } = {}) {
102255
102440
  useEffect(() => {
102256
102441
  if (!open2) return;
102257
102442
  updatePosition();
102443
+ const close = (e3) => {
102444
+ const t2 = e3.target;
102445
+ if (containerRef.current?.contains(t2) || popoverRef.current?.contains(t2)) return;
102446
+ setOpen(false);
102447
+ };
102258
102448
  window.addEventListener("resize", updatePosition);
102259
- window.addEventListener("scroll", updatePosition, true);
102449
+ document.addEventListener("mousedown", close, { capture: true });
102260
102450
  return () => {
102261
102451
  window.removeEventListener("resize", updatePosition);
102262
- window.removeEventListener("scroll", updatePosition, true);
102452
+ document.removeEventListener("mousedown", close, { capture: true });
102263
102453
  };
102264
102454
  }, [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
102455
  useEffect(() => {
102276
102456
  if (isControlled && value) {
102277
102457
  if (typeof value === "object") {
102278
102458
  const rgb2 = cmykToRgb(value.c, value.m, value.y, value.k);
102279
- const hex = rgbToHex(rgb2.r, rgb2.g, rgb2.b);
102280
- setColor(hex);
102459
+ setColor(rgbToHex(rgb2.r, rgb2.g, rgb2.b));
102281
102460
  setCmyk(value);
102282
102461
  setHsv(rgbToHsv(rgb2.r, rgb2.g, rgb2.b));
102283
102462
  } else if (typeof value === "string") {
@@ -102294,35 +102473,32 @@ function CMYKColorPicker({ value, onChange, className = "" } = {}) {
102294
102473
  setOpen(false);
102295
102474
  return;
102296
102475
  }
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);
102476
+ const { color: tColor, opacity: tOpacity, cmyk: tCmyk } = extractTargetColor(targets);
102477
+ if (tCmyk) {
102478
+ setCmyk(tCmyk);
102479
+ const rgb2 = cmykToRgb(tCmyk.c, tCmyk.m, tCmyk.y, tCmyk.k);
102480
+ setColor(rgbToHex(rgb2.r, rgb2.g, rgb2.b));
102303
102481
  setHsv(rgbToHsv(rgb2.r, rgb2.g, rgb2.b));
102304
- } else if (targetColor && targetColor !== color) {
102305
- const rgb2 = hexToRgb(targetColor);
102306
- setColor(targetColor);
102482
+ } else if (tColor && tColor !== color) {
102483
+ const rgb2 = hexToRgb(tColor);
102484
+ setColor(tColor);
102307
102485
  setHsv(rgbToHsv(rgb2.r, rgb2.g, rgb2.b));
102308
102486
  setCmyk(rgbToCmyk(rgb2.r, rgb2.g, rgb2.b));
102309
102487
  }
102310
- if (typeof targetOpacity === "number" && targetOpacity !== alpha) {
102311
- setAlpha(targetOpacity);
102312
- }
102488
+ if (typeof tOpacity === "number" && tOpacity !== alpha) setAlpha(tOpacity);
102313
102489
  }, [isControlled, objects]);
102314
- const applyColor = useCallback(
102490
+ const applyControlledColor = useCallback(
102315
102491
  (newColor, newCmyk) => {
102316
102492
  const finalCmyk = newCmyk || cmyk2;
102317
102493
  setColor(newColor);
102318
102494
  if (isControlled && onChange) {
102319
102495
  onChange(newColor, finalCmyk);
102320
102496
  } else if (canvas2 && targets.length) {
102321
- applyFillToFabricObjects(targets, newColor, alpha, color, canvas2, finalCmyk);
102497
+ applyFillToFabricObjects(targets, newColor, alpha, activeColor || color, canvas2, finalCmyk);
102322
102498
  saveState();
102323
102499
  }
102324
102500
  },
102325
- [isControlled, onChange, canvas2, targets, alpha, color, cmyk2, saveState]
102501
+ [isControlled, onChange, canvas2, targets, alpha, color, activeColor, cmyk2, saveState]
102326
102502
  );
102327
102503
  const updateFromHsv = (newHsv) => {
102328
102504
  const rgb2 = hsvToRgb(newHsv.h, newHsv.s, newHsv.v);
@@ -102330,25 +102506,76 @@ function CMYKColorPicker({ value, onChange, className = "" } = {}) {
102330
102506
  const hex = rgbToHex(rgb2.r, rgb2.g, rgb2.b);
102331
102507
  setHsv(newHsv);
102332
102508
  setCmyk(newC);
102333
- applyColor(hex, newC);
102509
+ applyControlledColor(hex, newC);
102334
102510
  };
102335
102511
  const updateFromCmyk = (newCmyk) => {
102336
102512
  const rgb2 = cmykToRgb(newCmyk.c, newCmyk.m, newCmyk.y, newCmyk.k);
102337
102513
  const hex = rgbToHex(rgb2.r, rgb2.g, rgb2.b);
102338
102514
  setCmyk(newCmyk);
102339
102515
  setHsv(rgbToHsv(rgb2.r, rgb2.g, rgb2.b));
102340
- applyColor(hex, newCmyk);
102516
+ applyControlledColor(hex, newCmyk);
102517
+ };
102518
+ const updateFromHex = (inputHex) => {
102519
+ let clean = inputHex.trim().replace(/^#/, "");
102520
+ if (clean.length === 3) clean = clean[0] + clean[0] + clean[1] + clean[1] + clean[2] + clean[2];
102521
+ if (/^[0-9a-fA-F]{6}$/.test(clean)) {
102522
+ const hex = `#${clean.toLowerCase()}`;
102523
+ const rgb2 = hexToRgb(hex);
102524
+ const newC = rgbToCmyk(rgb2.r, rgb2.g, rgb2.b);
102525
+ setHsv(rgbToHsv(rgb2.r, rgb2.g, rgb2.b));
102526
+ setCmyk(newC);
102527
+ applyControlledColor(hex, newC);
102528
+ setRecentColors((prev) => [hex, ...prev.filter((c2) => c2.toLowerCase() !== hex)].slice(0, 16));
102529
+ }
102341
102530
  };
102342
- if (!isControlled && !targets.length) return null;
102531
+ const handleCopy = () => {
102532
+ if (navigator.clipboard) {
102533
+ navigator.clipboard.writeText(color);
102534
+ setCopied(true);
102535
+ setTimeout(() => setCopied(false), 2e3);
102536
+ }
102537
+ };
102538
+ const handleEyeDropper = async () => {
102539
+ if (typeof window === "undefined" || !("EyeDropper" in window)) return;
102540
+ try {
102541
+ const result = await new window.EyeDropper().open();
102542
+ if (result?.sRGBHex) updateFromHex(result.sRGBHex);
102543
+ } catch {
102544
+ }
102545
+ };
102546
+ if (!isControlled) {
102547
+ if (!targets.length || !allTargetColors.length) return null;
102548
+ return /* @__PURE__ */ jsx("div", { className: `flex items-center gap-1.5 ${className}`, children: allTargetColors.map((c2, idx) => {
102549
+ const isSelected = activeColor ? activeColor.toLowerCase() === c2.toLowerCase() : activeColorIndex === idx || idx === 0 && !activeColor;
102550
+ return /* @__PURE__ */ jsx(
102551
+ "button",
102552
+ {
102553
+ type: "button",
102554
+ onClick: () => {
102555
+ setActiveColor(c2, idx);
102556
+ const { activePanel, setActivePanel } = useSidePanelStore.getState();
102557
+ if (activePanel !== "color") {
102558
+ setActivePanel("color");
102559
+ }
102560
+ },
102561
+ 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"}`,
102562
+ style: { backgroundColor: c2 },
102563
+ title: `Edit color ${c2.toUpperCase()}`
102564
+ },
102565
+ c2 + "-" + idx
102566
+ );
102567
+ }) });
102568
+ }
102343
102569
  const hueRgb = hsvToRgb(hsv.h, 100, 100);
102344
102570
  const hueColor = rgbToHex(hueRgb.r, hueRgb.g, hueRgb.b);
102345
102571
  return /* @__PURE__ */ jsxs("div", { className: `relative ${className}`, ref: containerRef, children: [
102346
102572
  /* @__PURE__ */ jsx(
102347
102573
  "div",
102348
102574
  {
102349
- className: "cursor-pointer w-7 h-7 rounded-md border border-neutral-600 shadow-sm",
102575
+ className: "cursor-pointer w-7 h-7 rounded-md border border-neutral-600 shadow-sm transition-transform hover:scale-105",
102350
102576
  style: { background: color },
102351
- onClick: () => setOpen((v2) => !v2)
102577
+ onClick: () => setOpen((v2) => !v2),
102578
+ title: "Color Picker"
102352
102579
  }
102353
102580
  ),
102354
102581
  open2 && createPortal(
@@ -102357,46 +102584,202 @@ function CMYKColorPicker({ value, onChange, className = "" } = {}) {
102357
102584
  {
102358
102585
  ref: popoverRef,
102359
102586
  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",
102587
+ className: "bg-white rounded-2xl shadow-2xl p-3.5 w-[270px] text-gray-800 border border-gray-100 select-none font-sans",
102361
102588
  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,
102589
+ /* @__PURE__ */ jsx(
102590
+ "div",
102591
+ {
102592
+ ref: svRef,
102593
+ className: "w-full h-[135px] rounded-xl relative cursor-crosshair overflow-hidden touch-none",
102594
+ style: { background: `linear-gradient(to top, #000, transparent), linear-gradient(to right, #fff, ${hueColor})` },
102595
+ onPointerDown: (e3) => {
102596
+ e3.currentTarget.setPointerCapture(e3.pointerId);
102597
+ const rect = e3.currentTarget.getBoundingClientRect();
102598
+ updateFromHsv({
102599
+ ...hsv,
102600
+ s: Math.round(Math.max(0, Math.min(1, (e3.clientX - rect.left) / rect.width)) * 100),
102601
+ v: Math.round((1 - Math.max(0, Math.min(1, (e3.clientY - rect.top) / rect.height))) * 100)
102602
+ });
102603
+ },
102604
+ onPointerMove: (e3) => {
102605
+ if (e3.buttons === 0) return;
102606
+ const rect = e3.currentTarget.getBoundingClientRect();
102607
+ updateFromHsv({
102608
+ ...hsv,
102609
+ s: Math.round(Math.max(0, Math.min(1, (e3.clientX - rect.left) / rect.width)) * 100),
102610
+ v: Math.round((1 - Math.max(0, Math.min(1, (e3.clientY - rect.top) / rect.height))) * 100)
102611
+ });
102612
+ },
102613
+ children: /* @__PURE__ */ jsx(
102614
+ "div",
102376
102615
  {
102377
- alpha,
102378
- color,
102379
- refEl: alphaRef,
102380
- setAlpha,
102381
- canvas: canvas2,
102382
- targets,
102383
- saveState
102616
+ 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",
102617
+ style: { left: `${hsv.s}%`, top: `${100 - hsv.v}%`, backgroundColor: color }
102384
102618
  }
102385
102619
  )
102386
- ] })
102387
- ] }),
102620
+ }
102621
+ ),
102388
102622
  /* @__PURE__ */ jsx(
102389
- CMYKInputs,
102623
+ "div",
102390
102624
  {
102391
- cmyk: cmyk2,
102392
- alpha,
102393
- setAlpha,
102394
- updateColor: updateFromCmyk,
102395
- canvas: canvas2,
102396
- targets,
102397
- saveState
102625
+ ref: hueRef,
102626
+ className: "w-full h-3 rounded-full relative cursor-pointer mt-3 mb-2 touch-none shadow-inner",
102627
+ style: { background: "linear-gradient(to right, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%)" },
102628
+ onPointerDown: (e3) => {
102629
+ e3.currentTarget.setPointerCapture(e3.pointerId);
102630
+ const rect = e3.currentTarget.getBoundingClientRect();
102631
+ updateFromHsv({ ...hsv, h: Math.round(Math.max(0, Math.min(1, (e3.clientX - rect.left) / rect.width)) * 360) % 360 });
102632
+ },
102633
+ onPointerMove: (e3) => {
102634
+ if (e3.buttons === 0) return;
102635
+ const rect = e3.currentTarget.getBoundingClientRect();
102636
+ updateFromHsv({ ...hsv, h: Math.round(Math.max(0, Math.min(1, (e3.clientX - rect.left) / rect.width)) * 360) % 360 });
102637
+ },
102638
+ 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
102639
  }
102399
- )
102640
+ ),
102641
+ /* @__PURE__ */ jsxs(
102642
+ "div",
102643
+ {
102644
+ ref: alphaRef,
102645
+ className: "w-full h-3 rounded-full relative cursor-pointer mb-3 touch-none pqc-checkerboard shadow-inner overflow-hidden",
102646
+ onPointerDown: (e3) => {
102647
+ e3.currentTarget.setPointerCapture(e3.pointerId);
102648
+ const rect = e3.currentTarget.getBoundingClientRect();
102649
+ const newAlpha = Math.round(Math.max(0, Math.min(1, (e3.clientX - rect.left) / rect.width)) * 100);
102650
+ setAlpha(newAlpha);
102651
+ if (canvas2 && targets.length) {
102652
+ targets.forEach((obj) => obj.set("opacity", newAlpha / 100));
102653
+ canvas2.requestRenderAll();
102654
+ }
102655
+ },
102656
+ onPointerMove: (e3) => {
102657
+ if (e3.buttons === 0) return;
102658
+ const rect = e3.currentTarget.getBoundingClientRect();
102659
+ const newAlpha = Math.round(Math.max(0, Math.min(1, (e3.clientX - rect.left) / rect.width)) * 100);
102660
+ setAlpha(newAlpha);
102661
+ if (canvas2 && targets.length) {
102662
+ targets.forEach((obj) => obj.set("opacity", newAlpha / 100));
102663
+ canvas2.requestRenderAll();
102664
+ }
102665
+ },
102666
+ children: [
102667
+ /* @__PURE__ */ jsx(
102668
+ "div",
102669
+ {
102670
+ className: "absolute inset-0",
102671
+ style: {
102672
+ background: `linear-gradient(to right, transparent, ${color})`
102673
+ }
102674
+ }
102675
+ ),
102676
+ /* @__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}%` } })
102677
+ ]
102678
+ }
102679
+ ),
102680
+ /* @__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: [
102681
+ /* @__PURE__ */ jsx(
102682
+ "button",
102683
+ {
102684
+ onClick: () => setMode("HEX"),
102685
+ 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"}`,
102686
+ children: "HEX"
102687
+ }
102688
+ ),
102689
+ /* @__PURE__ */ jsx(
102690
+ "button",
102691
+ {
102692
+ onClick: () => setMode("CMYK"),
102693
+ 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"}`,
102694
+ children: "CMYK"
102695
+ }
102696
+ )
102697
+ ] }),
102698
+ mode2 === "HEX" ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 mb-3", children: [
102699
+ /* @__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: [
102700
+ /* @__PURE__ */ jsx("span", { className: "text-gray-400 text-xs font-mono", children: "#" }),
102701
+ /* @__PURE__ */ jsx(
102702
+ "input",
102703
+ {
102704
+ type: "text",
102705
+ value: color.replace("#", "").toUpperCase(),
102706
+ onChange: (e3) => updateFromHex(e3.target.value),
102707
+ className: "w-full bg-transparent text-xs font-mono font-medium focus:outline-none uppercase pl-1 text-gray-800"
102708
+ }
102709
+ )
102710
+ ] }),
102711
+ /* @__PURE__ */ jsx(
102712
+ "button",
102713
+ {
102714
+ onClick: handleCopy,
102715
+ 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",
102716
+ title: "Copy HEX",
102717
+ children: copied ? "✓" : "Copy"
102718
+ }
102719
+ ),
102720
+ typeof window !== "undefined" && "EyeDropper" in window && /* @__PURE__ */ jsx(
102721
+ "button",
102722
+ {
102723
+ onClick: handleEyeDropper,
102724
+ 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",
102725
+ title: "Eyedropper",
102726
+ children: /* @__PURE__ */ jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
102727
+ /* @__PURE__ */ jsx("path", { d: "m14 7 3 3" }),
102728
+ /* @__PURE__ */ jsx("path", { d: "M19 10 9 20l-4 1 1-4 10-10a2.828 2.828 0 0 1 4 4Z" }),
102729
+ /* @__PURE__ */ jsx("path", { d: "m15 4 2-2 4 4-2 2" })
102730
+ ] })
102731
+ }
102732
+ )
102733
+ ] }) : /* @__PURE__ */ jsx("div", { className: "space-y-1.5 mb-3", children: CMYK_CHANNELS$1.map(({ key, label, color: lColor, name }) => /* @__PURE__ */ jsx(
102734
+ ChannelRow,
102735
+ {
102736
+ label,
102737
+ labelColor: lColor,
102738
+ ariaLabel: name,
102739
+ value: cmyk2[key],
102740
+ min: 0,
102741
+ max: 100,
102742
+ gradient: `linear-gradient(to right, ${cmykToHex(
102743
+ key === "c" ? 0 : cmyk2.c,
102744
+ key === "m" ? 0 : cmyk2.m,
102745
+ key === "y" ? 0 : cmyk2.y,
102746
+ key === "k" ? 0 : cmyk2.k
102747
+ )}, ${cmykToHex(
102748
+ key === "c" ? 100 : cmyk2.c,
102749
+ key === "m" ? 100 : cmyk2.m,
102750
+ key === "y" ? 100 : cmyk2.y,
102751
+ key === "k" ? 100 : cmyk2.k
102752
+ )})`,
102753
+ onChange: (val) => updateFromCmyk({ ...cmyk2, [key]: val })
102754
+ },
102755
+ key
102756
+ )) }),
102757
+ /* @__PURE__ */ jsxs("div", { className: "pt-2 border-t border-gray-100", children: [
102758
+ /* @__PURE__ */ jsx("span", { className: "text-[10px] font-bold text-gray-400 uppercase tracking-wider block mb-1.5", children: "Preset Swatches" }),
102759
+ /* @__PURE__ */ jsx("div", { className: "grid grid-cols-8 gap-1.5", children: DEFAULT_COLORS.map((c2) => /* @__PURE__ */ jsx(
102760
+ "button",
102761
+ {
102762
+ className: "w-6 h-6 rounded-md border border-gray-200 shadow-xs cursor-pointer hover:scale-110 transition-transform",
102763
+ style: { backgroundColor: c2 },
102764
+ onClick: () => updateFromHex(c2),
102765
+ title: c2
102766
+ },
102767
+ c2
102768
+ )) })
102769
+ ] }),
102770
+ recentColors.length > 0 && /* @__PURE__ */ jsxs("div", { className: "pt-2 mt-2 border-t border-gray-100", children: [
102771
+ /* @__PURE__ */ jsx("span", { className: "text-[10px] font-bold text-gray-400 uppercase tracking-wider block mb-1.5", children: "Recent Colors" }),
102772
+ /* @__PURE__ */ jsx("div", { className: "grid grid-cols-8 gap-1.5", children: recentColors.map((c2, i) => /* @__PURE__ */ jsx(
102773
+ "button",
102774
+ {
102775
+ className: "w-6 h-6 rounded-md border border-gray-200 shadow-xs cursor-pointer hover:scale-110 transition-transform",
102776
+ style: { backgroundColor: c2 },
102777
+ onClick: () => updateFromHex(c2),
102778
+ title: c2
102779
+ },
102780
+ c2 + i
102781
+ )) })
102782
+ ] })
102400
102783
  ]
102401
102784
  }
102402
102785
  ),
@@ -102492,11 +102875,26 @@ function BackgroundPanel() {
102492
102875
  hex
102493
102876
  ))
102494
102877
  ] }),
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(
102878
+ /* @__PURE__ */ jsx("div", { className: "h-[calc(100vh-165px)] 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(
102496
102879
  "button",
102497
102880
  {
102498
102881
  className: "relative aspect-square rounded overflow-hidden group cursor-pointer",
102499
- onClick: () => handleApplyBackground("image", item.largeImage),
102882
+ onClick: async () => {
102883
+ try {
102884
+ startLoading();
102885
+ const image = await ShapeService.convertImages(item.largeImage);
102886
+ const imageUrl = image?.success ? image?.url : null;
102887
+ if (!imageUrl) return;
102888
+ handleApplyBackground("image", imageUrl);
102889
+ if (window.innerWidth < 768) {
102890
+ useSidePanelStore.getState().setActivePanel(null);
102891
+ }
102892
+ } catch (error2) {
102893
+ console.error("Failed to convert image:", error2);
102894
+ } finally {
102895
+ stopLoading();
102896
+ }
102897
+ },
102500
102898
  children: /* @__PURE__ */ jsx(
102501
102899
  "img",
102502
102900
  {
@@ -102514,14 +102912,40 @@ function BackgroundPanel() {
102514
102912
  }
102515
102913
  function UploadPanel() {
102516
102914
  const [assets, setAssets] = useState([]);
102517
- function handleFileChange(e3) {
102915
+ const { startLoading, stopLoading } = useLoaderStore();
102916
+ async function handleFileChange(e3) {
102518
102917
  const file = e3.target.files?.[0];
102519
102918
  if (!file) return;
102520
102919
  if (!file.type.startsWith("image/")) return;
102521
- const url = URL.createObjectURL(file);
102522
- setAssets((prev) => [...prev, { id: crypto.randomUUID(), url }]);
102920
+ const previewUrl = URL.createObjectURL(file);
102921
+ const newAsset = { id: crypto.randomUUID(), url: previewUrl, file };
102922
+ setAssets((prev) => [newAsset, ...prev]);
102523
102923
  e3.target.value = "";
102524
102924
  }
102925
+ async function handleAssetClick(asset) {
102926
+ try {
102927
+ startLoading();
102928
+ let targetUrl = asset.uploadedUrl;
102929
+ if (!targetUrl && asset.file) {
102930
+ targetUrl = await UploadService.uploadFileApi(asset.file);
102931
+ setAssets(
102932
+ (prev) => prev.map((a2) => a2.id === asset.id ? { ...a2, uploadedUrl: targetUrl } : a2)
102933
+ );
102934
+ }
102935
+ const finalUrl = targetUrl || asset.url;
102936
+ if (finalUrl) {
102937
+ addImageToCanvas(finalUrl);
102938
+ if (window.innerWidth < 768) {
102939
+ useSidePanelStore.getState().setActivePanel(null);
102940
+ }
102941
+ }
102942
+ } catch (error2) {
102943
+ console.error("Failed to add image to canvas:", error2);
102944
+ addImageToCanvas(asset.url);
102945
+ } finally {
102946
+ stopLoading();
102947
+ }
102948
+ }
102525
102949
  return /* @__PURE__ */ jsxs("div", { className: "mx-4", children: [
102526
102950
  /* @__PURE__ */ jsx("h2", { className: "text-sm font-semibold mb-4 mt-2", children: "Upload your assets" }),
102527
102951
  /* @__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 +102964,12 @@ function UploadPanel() {
102540
102964
  /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 gap-3 mt-3", children: assets.map((asset) => /* @__PURE__ */ jsx(
102541
102965
  "button",
102542
102966
  {
102543
- onClick: () => {
102544
- addImageToCanvas(asset.url);
102545
- if (window.innerWidth < 768) {
102546
- useSidePanelStore.getState().setActivePanel(null);
102547
- }
102548
- },
102967
+ onClick: () => handleAssetClick(asset),
102549
102968
  className: "rounded-md overflow-hidden transition cursor-pointer",
102550
102969
  children: /* @__PURE__ */ jsx(
102551
102970
  "img",
102552
102971
  {
102553
- src: asset.url,
102972
+ src: asset.uploadedUrl || asset.url,
102554
102973
  alt: "asset",
102555
102974
  height: 300,
102556
102975
  width: 300,
@@ -104836,7 +105255,7 @@ async function generateQRSVG(value, dark = "#000000", light = "#ffffff") {
104836
105255
  <path fill="${dark}" d="${pathData.trim()}" />
104837
105256
  </svg>`;
104838
105257
  }
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";
105258
+ 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
105259
  const FormField$1 = ({
104841
105260
  label,
104842
105261
  error: error2,
@@ -105641,17 +106060,38 @@ function useBlurEffect(canvas2, active) {
105641
106060
  canvas2.requestRenderAll();
105642
106061
  }, [canvas2, active, blur.enabled, blur.value]);
105643
106062
  }
105644
- function useBrightnessEffect(canvas2, enabled) {
105645
- const { brightness } = useEffectStore();
106063
+ function useBrightnessEffect(canvas2, active) {
106064
+ const brightness = useEffectStore((s2) => s2.brightness);
105646
106065
  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);
106066
+ if (!canvas2 || !active) return;
106067
+ const obj = getActiveEffectTarget(canvas2);
106068
+ if (!obj) return;
106069
+ if (obj.type !== "image" && obj.type !== "FabricImage") return;
106070
+ const image = obj;
106071
+ image.filters = image.filters || [];
106072
+ const BRIGHTNESS_INDEX = image.filters.findIndex(
106073
+ (f) => f && (f.type === "Brightness" || f instanceof Sh.Brightness)
106074
+ );
106075
+ if (!brightness.enabled || brightness.value === 0) {
106076
+ if (BRIGHTNESS_INDEX !== -1) {
106077
+ image.filters.splice(BRIGHTNESS_INDEX, 1);
106078
+ image.applyFilters();
106079
+ canvas2.requestRenderAll();
106080
+ }
106081
+ return;
106082
+ }
106083
+ const strength = Math.max(-1, Math.min(1, brightness.value / 100));
106084
+ const brightnessFilter = new Sh.Brightness({
106085
+ brightness: strength
106086
+ });
106087
+ if (BRIGHTNESS_INDEX !== -1) {
106088
+ image.filters[BRIGHTNESS_INDEX] = brightnessFilter;
106089
+ } else {
106090
+ image.filters.push(brightnessFilter);
106091
+ }
106092
+ image.applyFilters();
105653
106093
  canvas2.requestRenderAll();
105654
- }, [canvas2, enabled, brightness.enabled, brightness.value]);
106094
+ }, [canvas2, active, brightness.enabled, brightness.value]);
105655
106095
  }
105656
106096
  function EffectSection({
105657
106097
  label,
@@ -106245,7 +106685,6 @@ function EffectsPanel() {
106245
106685
  const { toggleShadow, setShadow } = useEffectStore();
106246
106686
  useBlurEffect(canvas2, isOpen);
106247
106687
  useBrightnessEffect(canvas2, isOpen);
106248
- useBrightnessEffect(canvas2, isOpen);
106249
106688
  useTemperatureEffect(canvas2, isOpen);
106250
106689
  useContrastEffect(canvas2, isOpen);
106251
106690
  useShadowEffect(canvas2, isOpen);
@@ -107093,7 +107532,7 @@ function FontPanel() {
107093
107532
  )
107094
107533
  ] }),
107095
107534
  /* @__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: [
107535
+ /* @__PURE__ */ jsx("div", { className: "h-[calc(100vh-126px)] 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: [
107097
107536
  /* @__PURE__ */ jsx("div", { className: "grid grid-cols-1 gap-4", children: fontData.map((item, i) => /* @__PURE__ */ jsx(
107098
107537
  "button",
107099
107538
  {
@@ -152463,82 +152902,99 @@ function BarcodePanel() {
152463
152902
  )
152464
152903
  ] }) });
152465
152904
  }
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"
152905
+ const QuoteService = {
152906
+ async getAllQuotes(params = {}) {
152907
+ try {
152908
+ const query = new URLSearchParams();
152909
+ if (params.page) query.append("page", String(params.page));
152910
+ if (params.limit) query.append("limit", String(params.limit));
152911
+ if (params.search) query.append("search", params.search);
152912
+ query.append("status", "true");
152913
+ const res = await apiFetch(`/api/quotes?${query.toString()}`, {
152914
+ method: "GET"
152915
+ });
152916
+ return Array.isArray(res?.data) ? res.data : [];
152917
+ } catch (err) {
152918
+ console.error("Failed to fetch quotes:", err);
152919
+ return [];
152920
+ }
152514
152921
  }
152515
- ];
152922
+ };
152516
152923
  function QuotesPanel() {
152924
+ const [quotes, setQuotes] = useState([]);
152925
+ const [page, setPage] = useState(1);
152926
+ const [hasMore, setHasMore] = useState(true);
152927
+ const [isLoading, setIsLoading] = useState(false);
152928
+ const scrollRef = useRef(null);
152929
+ const fetchQuotes = useCallback(async (pageNo) => {
152930
+ setIsLoading(true);
152931
+ try {
152932
+ const data = await QuoteService.getAllQuotes({ page: pageNo, limit: 20 });
152933
+ if (data && data.length > 0) {
152934
+ setQuotes((prev) => pageNo === 1 ? data : [...prev, ...data]);
152935
+ if (data.length < 20) {
152936
+ setHasMore(false);
152937
+ }
152938
+ } else {
152939
+ setHasMore(false);
152940
+ }
152941
+ } catch (err) {
152942
+ console.error("Failed to load quotes:", err);
152943
+ } finally {
152944
+ setIsLoading(false);
152945
+ }
152946
+ }, []);
152947
+ useEffect(() => {
152948
+ fetchQuotes(1);
152949
+ }, [fetchQuotes]);
152950
+ useEffect(() => {
152951
+ const el = scrollRef.current;
152952
+ if (!el) return;
152953
+ const onScroll = () => {
152954
+ if (!hasMore || isLoading) return;
152955
+ if (el.scrollTop + el.clientHeight >= el.scrollHeight - 150) {
152956
+ setPage((prev) => {
152957
+ const next = prev + 1;
152958
+ fetchQuotes(next);
152959
+ return next;
152960
+ });
152961
+ }
152962
+ };
152963
+ el.addEventListener("scroll", onScroll);
152964
+ return () => el.removeEventListener("scroll", onScroll);
152965
+ }, [hasMore, isLoading, fetchQuotes]);
152517
152966
  const addQuote = async (content, author) => {
152518
- await addTextToCanvas(18, 300, `${content}
152967
+ await addTextToCanvas(18, 300, author ? `${content}
152519
152968
 
152520
- — ${author}`);
152969
+ — ${author}` : content);
152970
+ if (window.innerWidth < 768) {
152971
+ useSidePanelStore.getState().setActivePanel(null);
152972
+ }
152521
152973
  };
152522
152974
  return /* @__PURE__ */ jsxs("div", { className: "flex flex-col h-full", children: [
152523
152975
  /* @__PURE__ */ jsx("div", { className: "px-4 pb-3", children: /* @__PURE__ */ jsx("h2", { className: "text-base font-semibold text-white", children: "Quotes" }) }),
152524
152976
  /* @__PURE__ */ jsx("hr", { className: "border-neutral-800 mx-4 mb-3" }),
152525
- /* @__PURE__ */ jsx(
152977
+ /* @__PURE__ */ jsxs(
152526
152978
  "div",
152527
152979
  {
152980
+ ref: scrollRef,
152528
152981
  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
- ))
152982
+ children: [
152983
+ quotes.map((q2, i) => /* @__PURE__ */ jsxs(
152984
+ "button",
152985
+ {
152986
+ onClick: () => addQuote(q2.content, q2.author),
152987
+ title: "Click to add to canvas",
152988
+ 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 ",
152989
+ children: [
152990
+ /* @__PURE__ */ jsx("p", { className: "text-neutral-200 text-[13px] leading-relaxed mb-2 group-hover:text-white transition-colors", children: q2.content }),
152991
+ q2.author && /* @__PURE__ */ jsx("p", { className: "text-right text-[12px] font-semibold text-[#02bc71]", children: q2.author })
152992
+ ]
152993
+ },
152994
+ q2._id || i
152995
+ )),
152996
+ isLoading && /* @__PURE__ */ jsx("div", { className: "text-center py-4 text-xs text-neutral-500", children: "Loading more quotes..." })
152997
+ ]
152542
152998
  }
152543
152999
  )
152544
153000
  ] });
@@ -152566,6 +153022,559 @@ const QuotesIcon = () => /* @__PURE__ */ jsx(
152566
153022
  ) })
152567
153023
  }
152568
153024
  );
153025
+ const ColorService = {
153026
+ async getAll(params) {
153027
+ try {
153028
+ const searchParams = new URLSearchParams();
153029
+ if (params?.search) searchParams.append("search", params.search);
153030
+ if (params?.status !== void 0) searchParams.append("status", String(params.status));
153031
+ searchParams.append("limit", String(params?.limit || 100));
153032
+ if (params?.page) searchParams.append("page", String(params.page));
153033
+ const res = await apiFetch(`/colors?${searchParams.toString()}`, {
153034
+ method: "GET"
153035
+ });
153036
+ if (Array.isArray(res)) return res;
153037
+ if (Array.isArray(res?.data)) return res.data;
153038
+ return [];
153039
+ } catch (err) {
153040
+ console.error("Failed to fetch colors:", err);
153041
+ return [];
153042
+ }
153043
+ }
153044
+ };
153045
+ const DEFAULT_PRESET_COLORS = [
153046
+ "#ffffff",
153047
+ "#000000",
153048
+ "#1e293b",
153049
+ "#475569",
153050
+ "#94a3b8",
153051
+ "#e2e8f0",
153052
+ "#ef4444",
153053
+ "#f97316",
153054
+ "#f59e0b",
153055
+ "#eab308",
153056
+ "#84cc16",
153057
+ "#22c55e",
153058
+ "#10b981",
153059
+ "#06b6d4",
153060
+ "#0ea5e9",
153061
+ "#3b82f6",
153062
+ "#6366f1",
153063
+ "#8b5cf6",
153064
+ "#a855f7",
153065
+ "#d946ef",
153066
+ "#ec4899",
153067
+ "#f43f5e"
153068
+ ];
153069
+ const CMYK_CHANNELS = [
153070
+ { key: "c", label: "C", color: "#009fe3", name: "Cyan" },
153071
+ { key: "m", label: "M", color: "#e6007e", name: "Magenta" },
153072
+ { key: "y", label: "Y", color: "#dcb000", name: "Yellow" },
153073
+ { key: "k", label: "K", color: "#222222", name: "Black" }
153074
+ ];
153075
+ function ColorPanel() {
153076
+ const canvas2 = useEditorStore((s2) => s2.canvas);
153077
+ const { objects, activeColor, activeColorIndex, setActiveColor } = useSelectionStore();
153078
+ const { setActivePanel } = useSidePanelStore();
153079
+ const saveState = useHistoryStore((s2) => s2.saveState);
153080
+ const targets = useMemo(
153081
+ () => objects.filter((o2) => o2.name !== "workarea"),
153082
+ [objects]
153083
+ );
153084
+ const objectColors = useMemo(() => extractAllTargetColors(targets), [targets, objects]);
153085
+ const targetInfo = useMemo(() => extractTargetColor(targets), [targets]);
153086
+ const initialColor = activeColor || (objectColors.length > 0 ? objectColors[0] : targetInfo.color || "#000000");
153087
+ const [currentColor, setCurrentColor] = useState(initialColor);
153088
+ const [currentCmyk, setCurrentCmyk] = useState(() => hexToCmyk(initialColor));
153089
+ const [hsv, setHsv] = useState(() => {
153090
+ const rgb2 = hexToRgb(initialColor);
153091
+ return rgbToHsv(rgb2.r, rgb2.g, rgb2.b);
153092
+ });
153093
+ const [alpha, setAlpha] = useState(() => targetInfo.opacity ?? 100);
153094
+ const [colorMode, setColorMode] = useState("HEX");
153095
+ const [dbColors, setDbColors] = useState([]);
153096
+ const [isLoadingColors, setIsLoadingColors] = useState(false);
153097
+ const [copied, setCopied] = useState(false);
153098
+ const svRef = useRef(null);
153099
+ const hueRef = useRef(null);
153100
+ const alphaRef = useRef(null);
153101
+ useEffect(() => {
153102
+ if (targets.length > 0) {
153103
+ const colors = extractAllTargetColors(targets);
153104
+ if (colors.length > 0) {
153105
+ let chosenColor = activeColor;
153106
+ if (!chosenColor || !colors.includes(chosenColor)) {
153107
+ chosenColor = colors[activeColorIndex] || colors[0];
153108
+ }
153109
+ setCurrentColor(chosenColor);
153110
+ const c2 = hexToCmyk(chosenColor);
153111
+ setCurrentCmyk(c2);
153112
+ const rgb2 = hexToRgb(chosenColor);
153113
+ setHsv(rgbToHsv(rgb2.r, rgb2.g, rgb2.b));
153114
+ const info = extractTargetColor(targets);
153115
+ if (info.opacity !== void 0) setAlpha(info.opacity);
153116
+ if (activeColor !== chosenColor) {
153117
+ const idx = colors.indexOf(chosenColor);
153118
+ setActiveColor(chosenColor, idx >= 0 ? idx : 0);
153119
+ }
153120
+ }
153121
+ }
153122
+ }, [targets, activeColor, activeColorIndex]);
153123
+ useEffect(() => {
153124
+ let isMounted = true;
153125
+ const fetchColors = async () => {
153126
+ setIsLoadingColors(true);
153127
+ try {
153128
+ const data = await ColorService.getAll({ status: true, limit: 100 });
153129
+ if (isMounted) setDbColors(data);
153130
+ } finally {
153131
+ if (isMounted) setIsLoadingColors(false);
153132
+ }
153133
+ };
153134
+ fetchColors();
153135
+ return () => {
153136
+ isMounted = false;
153137
+ };
153138
+ }, []);
153139
+ const documentColors = useMemo(() => {
153140
+ if (!canvas2) return [];
153141
+ const allObjs = canvas2.getObjects ? canvas2.getObjects() : [];
153142
+ return extractAllTargetColors(allObjs);
153143
+ }, [canvas2, objects]);
153144
+ const applyColor = useCallback(
153145
+ (newHex, cmykVal, opacityVal = alpha) => {
153146
+ const resolvedCmyk = cmykVal || hexToCmyk(newHex);
153147
+ const oldTargetColor = currentColor;
153148
+ setCurrentColor(newHex);
153149
+ setCurrentCmyk(resolvedCmyk);
153150
+ if (targets.length > 0) {
153151
+ applyFillToFabricObjects(targets, newHex, opacityVal, oldTargetColor, canvas2, resolvedCmyk);
153152
+ setActiveColor(newHex, activeColorIndex);
153153
+ } else {
153154
+ addBackgroundColor(newHex);
153155
+ }
153156
+ saveState();
153157
+ },
153158
+ [targets, canvas2, alpha, currentColor, activeColorIndex, setActiveColor, saveState]
153159
+ );
153160
+ const updateFromHsv = useCallback(
153161
+ (newHsv) => {
153162
+ const rgb2 = hsvToRgb(newHsv.h, newHsv.s, newHsv.v);
153163
+ const newC = rgbToCmyk(rgb2.r, rgb2.g, rgb2.b);
153164
+ const hex = rgbToHex(rgb2.r, rgb2.g, rgb2.b);
153165
+ setHsv(newHsv);
153166
+ setCurrentCmyk(newC);
153167
+ applyColor(hex, newC, alpha);
153168
+ },
153169
+ [alpha, applyColor]
153170
+ );
153171
+ const updateFromCmyk = useCallback(
153172
+ (newCmyk) => {
153173
+ const rgb2 = cmykToRgb(newCmyk.c, newCmyk.m, newCmyk.y, newCmyk.k);
153174
+ const hex = rgbToHex(rgb2.r, rgb2.g, rgb2.b);
153175
+ setCurrentCmyk(newCmyk);
153176
+ setHsv(rgbToHsv(rgb2.r, rgb2.g, rgb2.b));
153177
+ applyColor(hex, newCmyk, alpha);
153178
+ },
153179
+ [alpha, applyColor]
153180
+ );
153181
+ const updateFromHex = useCallback(
153182
+ (inputHex) => {
153183
+ let clean = inputHex.trim().replace(/^#/, "");
153184
+ if (clean.length === 3) clean = clean[0] + clean[0] + clean[1] + clean[1] + clean[2] + clean[2];
153185
+ if (/^[0-9a-fA-F]{6}$/.test(clean)) {
153186
+ const hex = `#${clean.toLowerCase()}`;
153187
+ const rgb2 = hexToRgb(hex);
153188
+ const newC = rgbToCmyk(rgb2.r, rgb2.g, rgb2.b);
153189
+ setHsv(rgbToHsv(rgb2.r, rgb2.g, rgb2.b));
153190
+ setCurrentCmyk(newC);
153191
+ applyColor(hex, newC, alpha);
153192
+ }
153193
+ },
153194
+ [alpha, applyColor]
153195
+ );
153196
+ const handleSelectObjectColor = (hex, idx) => {
153197
+ setActiveColor(hex, idx);
153198
+ setCurrentColor(hex);
153199
+ const c2 = hexToCmyk(hex);
153200
+ setCurrentCmyk(c2);
153201
+ const rgb2 = hexToRgb(hex);
153202
+ setHsv(rgbToHsv(rgb2.r, rgb2.g, rgb2.b));
153203
+ };
153204
+ const handleCopyHex = () => {
153205
+ navigator.clipboard.writeText(currentColor);
153206
+ setCopied(true);
153207
+ setTimeout(() => setCopied(false), 1500);
153208
+ };
153209
+ const handleEyeDropper = async () => {
153210
+ if (typeof window !== "undefined" && "EyeDropper" in window) {
153211
+ try {
153212
+ const eyeDropper = new window.EyeDropper();
153213
+ const result = await eyeDropper.open();
153214
+ if (result?.sRGBHex) {
153215
+ updateFromHex(result.sRGBHex);
153216
+ }
153217
+ } catch (err) {
153218
+ console.error("EyeDropper error:", err);
153219
+ }
153220
+ }
153221
+ };
153222
+ const groupedDbColors = useMemo(() => {
153223
+ const map = /* @__PURE__ */ new Map();
153224
+ dbColors.forEach((c2) => {
153225
+ const group = c2.colorGroup || "Custom Palettes";
153226
+ if (!map.has(group)) map.set(group, []);
153227
+ map.get(group).push(c2);
153228
+ });
153229
+ return map;
153230
+ }, [dbColors]);
153231
+ const hueRgb = hsvToRgb(hsv.h, 100, 100);
153232
+ const hueColor = rgbToHex(hueRgb.r, hueRgb.g, hueRgb.b);
153233
+ 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: [
153234
+ /* @__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: [
153235
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
153236
+ /* @__PURE__ */ jsx("span", { className: "w-2 h-2 rounded-full bg-[#02bc71] animate-pulse" }),
153237
+ /* @__PURE__ */ jsx("h2", { className: "font-semibold text-sm text-neutral-100", children: "Color Palette" })
153238
+ ] }),
153239
+ /* @__PURE__ */ jsx(
153240
+ "button",
153241
+ {
153242
+ onClick: () => setActivePanel(null),
153243
+ className: "p-1 rounded-md text-neutral-400 hover:text-white hover:bg-neutral-800 transition-colors cursor-pointer",
153244
+ title: "Close Panel",
153245
+ 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" }) })
153246
+ }
153247
+ )
153248
+ ] }) }),
153249
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 overflow-y-auto space-y-5 p-4 pr-3", children: [
153250
+ objectColors.length > 0 && /* @__PURE__ */ jsxs("div", { children: [
153251
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-2", children: [
153252
+ /* @__PURE__ */ jsxs("span", { className: "text-[11px] font-bold text-neutral-400 uppercase tracking-wider", children: [
153253
+ "Object Colors (",
153254
+ objectColors.length,
153255
+ ")"
153256
+ ] }),
153257
+ /* @__PURE__ */ jsx("span", { className: "text-[10px] text-neutral-500", children: "Select to customize" })
153258
+ ] }),
153259
+ /* @__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) => {
153260
+ const isSelected = currentColor.toLowerCase() === hex.toLowerCase();
153261
+ return /* @__PURE__ */ jsx(
153262
+ "button",
153263
+ {
153264
+ type: "button",
153265
+ onClick: () => handleSelectObjectColor(hex, idx),
153266
+ 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"}`,
153267
+ style: { backgroundColor: hex },
153268
+ title: `Object color ${idx + 1}: ${hex.toUpperCase()}`,
153269
+ children: isSelected && /* @__PURE__ */ jsx("span", { className: "absolute inset-0 flex items-center justify-center text-xs font-bold mix-blend-difference text-white", children: "✓" })
153270
+ },
153271
+ hex + "-" + idx
153272
+ );
153273
+ }) })
153274
+ ] }),
153275
+ /* @__PURE__ */ jsxs("div", { className: "p-3.5 bg-neutral-950 border border-neutral-800 rounded-2xl shadow-xl space-y-3", children: [
153276
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between pb-1", children: [
153277
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
153278
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-bold text-neutral-300", children: "Custom Color" }),
153279
+ /* @__PURE__ */ jsx(
153280
+ "span",
153281
+ {
153282
+ className: "w-3.5 h-3.5 rounded-full border border-neutral-700 shadow-inner",
153283
+ style: { backgroundColor: currentColor }
153284
+ }
153285
+ )
153286
+ ] }),
153287
+ /* @__PURE__ */ jsxs("div", { className: "flex bg-neutral-900 rounded-lg p-0.5 border border-neutral-800", children: [
153288
+ /* @__PURE__ */ jsx(
153289
+ "button",
153290
+ {
153291
+ type: "button",
153292
+ onClick: () => setColorMode("HEX"),
153293
+ 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"}`,
153294
+ children: "HEX"
153295
+ }
153296
+ ),
153297
+ /* @__PURE__ */ jsx(
153298
+ "button",
153299
+ {
153300
+ type: "button",
153301
+ onClick: () => setColorMode("CMYK"),
153302
+ 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"}`,
153303
+ children: "CMYK"
153304
+ }
153305
+ )
153306
+ ] })
153307
+ ] }),
153308
+ /* @__PURE__ */ jsx(
153309
+ "div",
153310
+ {
153311
+ ref: svRef,
153312
+ className: "relative w-full h-36 rounded-xl overflow-hidden cursor-crosshair select-none border border-neutral-800 touch-none",
153313
+ style: {
153314
+ background: `linear-gradient(to top, #000, transparent), linear-gradient(to right, #fff, ${hueColor})`
153315
+ },
153316
+ onPointerDown: (e3) => {
153317
+ e3.currentTarget.setPointerCapture(e3.pointerId);
153318
+ const rect = e3.currentTarget.getBoundingClientRect();
153319
+ updateFromHsv({
153320
+ ...hsv,
153321
+ s: Math.round(Math.max(0, Math.min(1, (e3.clientX - rect.left) / rect.width)) * 100),
153322
+ v: Math.round((1 - Math.max(0, Math.min(1, (e3.clientY - rect.top) / rect.height))) * 100)
153323
+ });
153324
+ },
153325
+ onPointerMove: (e3) => {
153326
+ if (e3.buttons === 0) return;
153327
+ const rect = e3.currentTarget.getBoundingClientRect();
153328
+ updateFromHsv({
153329
+ ...hsv,
153330
+ s: Math.round(Math.max(0, Math.min(1, (e3.clientX - rect.left) / rect.width)) * 100),
153331
+ v: Math.round((1 - Math.max(0, Math.min(1, (e3.clientY - rect.top) / rect.height))) * 100)
153332
+ });
153333
+ },
153334
+ children: /* @__PURE__ */ jsx(
153335
+ "div",
153336
+ {
153337
+ 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",
153338
+ style: {
153339
+ left: `${hsv.s}%`,
153340
+ top: `${100 - hsv.v}%`,
153341
+ backgroundColor: currentColor
153342
+ }
153343
+ }
153344
+ )
153345
+ }
153346
+ ),
153347
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
153348
+ /* @__PURE__ */ jsxs("div", { className: "flex justify-between text-[10px] text-neutral-400 font-medium", children: [
153349
+ /* @__PURE__ */ jsx("span", { children: "Hue" }),
153350
+ /* @__PURE__ */ jsxs("span", { children: [
153351
+ Math.round(hsv.h),
153352
+ "°"
153353
+ ] })
153354
+ ] }),
153355
+ /* @__PURE__ */ jsx(
153356
+ "div",
153357
+ {
153358
+ ref: hueRef,
153359
+ className: "relative h-4 rounded-lg cursor-pointer select-none border border-neutral-800 touch-none",
153360
+ style: {
153361
+ background: "linear-gradient(to right, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%)"
153362
+ },
153363
+ onPointerDown: (e3) => {
153364
+ e3.currentTarget.setPointerCapture(e3.pointerId);
153365
+ const rect = e3.currentTarget.getBoundingClientRect();
153366
+ updateFromHsv({
153367
+ ...hsv,
153368
+ h: Math.round(Math.max(0, Math.min(1, (e3.clientX - rect.left) / rect.width)) * 360) % 360
153369
+ });
153370
+ },
153371
+ onPointerMove: (e3) => {
153372
+ if (e3.buttons === 0) return;
153373
+ const rect = e3.currentTarget.getBoundingClientRect();
153374
+ updateFromHsv({
153375
+ ...hsv,
153376
+ h: Math.round(Math.max(0, Math.min(1, (e3.clientX - rect.left) / rect.width)) * 360) % 360
153377
+ });
153378
+ },
153379
+ children: /* @__PURE__ */ jsx(
153380
+ "div",
153381
+ {
153382
+ 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",
153383
+ style: { left: `${hsv.h / 360 * 100}%`, backgroundColor: hueColor }
153384
+ }
153385
+ )
153386
+ }
153387
+ )
153388
+ ] }),
153389
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
153390
+ /* @__PURE__ */ jsxs("div", { className: "flex justify-between text-[10px] text-neutral-400 font-medium", children: [
153391
+ /* @__PURE__ */ jsx("span", { children: "Opacity" }),
153392
+ /* @__PURE__ */ jsxs("span", { children: [
153393
+ alpha,
153394
+ "%"
153395
+ ] })
153396
+ ] }),
153397
+ /* @__PURE__ */ jsxs(
153398
+ "div",
153399
+ {
153400
+ ref: alphaRef,
153401
+ className: "relative h-4 rounded-lg cursor-pointer select-none border border-neutral-800 overflow-hidden touch-none pqc-checkerboard",
153402
+ onPointerDown: (e3) => {
153403
+ e3.currentTarget.setPointerCapture(e3.pointerId);
153404
+ const rect = e3.currentTarget.getBoundingClientRect();
153405
+ const newAlpha = Math.round(Math.max(0, Math.min(1, (e3.clientX - rect.left) / rect.width)) * 100);
153406
+ setAlpha(newAlpha);
153407
+ applyColor(currentColor, currentCmyk, newAlpha);
153408
+ },
153409
+ onPointerMove: (e3) => {
153410
+ if (e3.buttons === 0) return;
153411
+ const rect = e3.currentTarget.getBoundingClientRect();
153412
+ const newAlpha = Math.round(Math.max(0, Math.min(1, (e3.clientX - rect.left) / rect.width)) * 100);
153413
+ setAlpha(newAlpha);
153414
+ applyColor(currentColor, currentCmyk, newAlpha);
153415
+ },
153416
+ children: [
153417
+ /* @__PURE__ */ jsx(
153418
+ "div",
153419
+ {
153420
+ className: "absolute inset-0",
153421
+ style: {
153422
+ background: `linear-gradient(to right, transparent, ${currentColor})`
153423
+ }
153424
+ }
153425
+ ),
153426
+ /* @__PURE__ */ jsx(
153427
+ "div",
153428
+ {
153429
+ 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",
153430
+ style: { left: `${alpha}%` }
153431
+ }
153432
+ )
153433
+ ]
153434
+ }
153435
+ )
153436
+ ] }),
153437
+ colorMode === "HEX" && /* @__PURE__ */ jsx("div", { className: "space-y-2 pt-1", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
153438
+ /* @__PURE__ */ jsxs("div", { className: "relative flex-1", children: [
153439
+ /* @__PURE__ */ jsx("span", { className: "absolute left-2.5 top-2 text-xs font-mono text-neutral-500", children: "#" }),
153440
+ /* @__PURE__ */ jsx(
153441
+ "input",
153442
+ {
153443
+ type: "text",
153444
+ maxLength: 7,
153445
+ value: currentColor.replace("#", "").toUpperCase(),
153446
+ onChange: (e3) => {
153447
+ const val = e3.target.value.replace(/[^0-9A-Fa-f]/g, "");
153448
+ if (val.length <= 6) {
153449
+ const newHex = `#${val}`;
153450
+ setCurrentColor(newHex);
153451
+ if (val.length === 6) {
153452
+ updateFromHex(newHex);
153453
+ }
153454
+ }
153455
+ },
153456
+ 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]"
153457
+ }
153458
+ )
153459
+ ] }),
153460
+ /* @__PURE__ */ jsx(
153461
+ "button",
153462
+ {
153463
+ type: "button",
153464
+ onClick: handleCopyHex,
153465
+ 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",
153466
+ title: "Copy Hex Code",
153467
+ children: copied ? /* @__PURE__ */ jsx("span", { className: "text-[#02bc71]", children: "Copied!" }) : /* @__PURE__ */ jsx("span", { children: "Copy" })
153468
+ }
153469
+ ),
153470
+ typeof window !== "undefined" && "EyeDropper" in window && /* @__PURE__ */ jsx(
153471
+ "button",
153472
+ {
153473
+ type: "button",
153474
+ onClick: handleEyeDropper,
153475
+ 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",
153476
+ title: "Eyedropper Tool",
153477
+ children: /* @__PURE__ */ jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
153478
+ /* @__PURE__ */ jsx("path", { d: "m14 2 8 8-4 4-8-8 4-4Z" }),
153479
+ /* @__PURE__ */ jsx("path", { d: "m18 10-4-4" }),
153480
+ /* @__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" })
153481
+ ] })
153482
+ }
153483
+ )
153484
+ ] }) }),
153485
+ colorMode === "CMYK" && /* @__PURE__ */ jsx("div", { className: "space-y-1.5 pt-1", children: CMYK_CHANNELS.map(({ key, label, color: lColor, name }) => /* @__PURE__ */ jsx(
153486
+ ChannelRow,
153487
+ {
153488
+ label,
153489
+ labelColor: lColor,
153490
+ ariaLabel: name,
153491
+ value: currentCmyk[key],
153492
+ min: 0,
153493
+ max: 100,
153494
+ gradient: `linear-gradient(to right, ${cmykToHex(
153495
+ key === "c" ? 0 : currentCmyk.c,
153496
+ key === "m" ? 0 : currentCmyk.m,
153497
+ key === "y" ? 0 : currentCmyk.y,
153498
+ key === "k" ? 0 : currentCmyk.k
153499
+ )}, ${cmykToHex(
153500
+ key === "c" ? 100 : currentCmyk.c,
153501
+ key === "m" ? 100 : currentCmyk.m,
153502
+ key === "y" ? 100 : currentCmyk.y,
153503
+ key === "k" ? 100 : currentCmyk.k
153504
+ )})`,
153505
+ onChange: (val) => updateFromCmyk({ ...currentCmyk, [key]: val })
153506
+ },
153507
+ key
153508
+ )) })
153509
+ ] }),
153510
+ /* @__PURE__ */ jsxs("div", { children: [
153511
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-2", children: [
153512
+ /* @__PURE__ */ jsx("span", { className: "text-[11px] font-bold text-neutral-400 uppercase tracking-wider", children: "Document Colors" }),
153513
+ /* @__PURE__ */ jsxs("span", { className: "text-[10px] text-neutral-500", children: [
153514
+ documentColors.length,
153515
+ " colors"
153516
+ ] })
153517
+ ] }),
153518
+ 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(
153519
+ "button",
153520
+ {
153521
+ onClick: () => updateFromHex(hex),
153522
+ 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"}`,
153523
+ style: { backgroundColor: hex },
153524
+ title: hex.toUpperCase(),
153525
+ 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: "✓" })
153526
+ },
153527
+ hex
153528
+ )) })
153529
+ ] }),
153530
+ /* @__PURE__ */ jsxs("div", { children: [
153531
+ /* @__PURE__ */ jsx("span", { className: "text-[11px] font-bold text-neutral-400 uppercase tracking-wider block mb-2", children: "Default Colors" }),
153532
+ /* @__PURE__ */ jsx("div", { className: "grid grid-cols-6 gap-2", children: DEFAULT_PRESET_COLORS.map((hex) => /* @__PURE__ */ jsx(
153533
+ "button",
153534
+ {
153535
+ type: "button",
153536
+ onClick: () => updateFromHex(hex),
153537
+ 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"}`,
153538
+ style: { backgroundColor: hex },
153539
+ title: hex,
153540
+ 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: "✓" })
153541
+ },
153542
+ hex
153543
+ )) })
153544
+ ] }),
153545
+ 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: [
153546
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
153547
+ /* @__PURE__ */ jsx("span", { className: "text-[11px] font-bold text-neutral-400 uppercase tracking-wider", children: groupName }),
153548
+ /* @__PURE__ */ jsx("span", { className: "text-[10px] text-neutral-500", children: colors.length })
153549
+ ] }),
153550
+ /* @__PURE__ */ jsx("div", { className: "grid grid-cols-6 gap-2", children: colors.map((c2) => /* @__PURE__ */ jsx(
153551
+ "button",
153552
+ {
153553
+ onClick: () => updateFromHex(c2.hexCode),
153554
+ 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"}`,
153555
+ style: { backgroundColor: c2.hexCode },
153556
+ title: `${c2.colorName} (${c2.hexCode})`,
153557
+ 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: "✓" })
153558
+ },
153559
+ c2._id || c2.hexCode
153560
+ )) })
153561
+ ] }, groupName))
153562
+ ] })
153563
+ ] });
153564
+ }
153565
+ const ColorPickerIcon = () => /* @__PURE__ */ jsx(
153566
+ "svg",
153567
+ {
153568
+ xmlns: "http://www.w3.org/2000/svg",
153569
+ viewBox: "0 0 512 512",
153570
+ width: "80",
153571
+ height: "80",
153572
+ fill: "currentColor",
153573
+ "aria-hidden": "true",
153574
+ style: { mixBlendMode: "difference" },
153575
+ 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" })
153576
+ }
153577
+ );
152569
153578
  const DEFAULT_SECTIONS = [
152570
153579
  {
152571
153580
  id: "templates",
@@ -152627,6 +153636,13 @@ const DEFAULT_SECTIONS = [
152627
153636
  Tab: /* @__PURE__ */ jsx(QRCodeIcon, {}),
152628
153637
  Panel: /* @__PURE__ */ jsx(BarcodePanel, {})
152629
153638
  },
153639
+ {
153640
+ id: "color",
153641
+ name: "Color",
153642
+ Tab: /* @__PURE__ */ jsx(ColorPickerIcon, {}),
153643
+ Panel: /* @__PURE__ */ jsx(ColorPanel, {}),
153644
+ visibleInList: false
153645
+ },
152630
153646
  {
152631
153647
  id: "effects",
152632
153648
  name: "Effects",
@@ -152820,6 +153836,18 @@ function StrikethroughIcon() {
152820
153836
  }
152821
153837
  );
152822
153838
  }
153839
+ const STROKE_WIDTH_OPTIONS = [0, 1, 2, 3, 4, 5];
153840
+ const getStrokeDashArray = (style) => {
153841
+ switch (style) {
153842
+ case "Dashed":
153843
+ return [10, 5];
153844
+ case "Dotted":
153845
+ return [2, 4];
153846
+ case "Line":
153847
+ default:
153848
+ return null;
153849
+ }
153850
+ };
152823
153851
  function TextFontSize({
152824
153852
  value,
152825
153853
  onChange
@@ -152861,6 +153889,10 @@ function TextToolbar() {
152861
153889
  const [charSpacing, setCharSpacing] = useState(0);
152862
153890
  const [align, setAlign] = useState("left");
152863
153891
  const [showTextPopup, setShowTextPopup] = useState(false);
153892
+ const [showStrokeControls, setShowStrokeControls] = useState(false);
153893
+ const [strokeColor, setStrokeColor] = useState("#000000");
153894
+ const [strokeSize, setStrokeSize] = useState(0);
153895
+ const [strokeStyle, setStrokeStyle] = useState("Line");
152864
153896
  const text = useMemo(() => {
152865
153897
  if (type !== "textbox") return null;
152866
153898
  return isTextbox(objects[0]) ? objects[0] : null;
@@ -152871,6 +153903,11 @@ function TextToolbar() {
152871
153903
  setLineHeight2((text.lineHeight ?? 1) * 100);
152872
153904
  setCharSpacing(text.charSpacing ?? 0);
152873
153905
  setAlign(text.textAlign ?? "left");
153906
+ const dashArray = text.strokeDashArray;
153907
+ 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";
153908
+ setStrokeColor(typeof text.stroke === "string" && text.stroke ? text.stroke : "#000000");
153909
+ setStrokeSize(Math.max(0, Number(text.strokeWidth ?? 0)));
153910
+ setStrokeStyle(currentStrokeStyle);
152874
153911
  }, [text]);
152875
153912
  const updateText = useCallback(
152876
153913
  (props) => {
@@ -152883,6 +153920,24 @@ function TextToolbar() {
152883
153920
  },
152884
153921
  [text]
152885
153922
  );
153923
+ const applyTextStroke = useCallback(
153924
+ (nextColor, nextSize, nextStyle) => {
153925
+ if (!text) return;
153926
+ const canvas2 = text.canvas;
153927
+ if (!canvas2) return;
153928
+ const normalizedSize = Number.isFinite(nextSize) ? Math.min(5, Math.max(0, Math.round(nextSize))) : 0;
153929
+ text.set({
153930
+ stroke: normalizedSize > 0 ? nextColor : null,
153931
+ strokeWidth: normalizedSize,
153932
+ strokeDashArray: normalizedSize === 0 ? null : getStrokeDashArray(nextStyle),
153933
+ strokeUniform: true,
153934
+ paintFirst: "stroke"
153935
+ });
153936
+ canvas2.requestRenderAll();
153937
+ useHistoryStore.getState().saveState();
153938
+ },
153939
+ [text]
153940
+ );
152886
153941
  const applyTextCase = (mode2) => {
152887
153942
  if (!text?.text) return;
152888
153943
  const anyText = text;
@@ -152964,7 +154019,9 @@ function TextToolbar() {
152964
154019
  /* @__PURE__ */ jsx(
152965
154020
  "button",
152966
154021
  {
152967
- className: "cursor-pointer p-1 hover:bg-neutral-700 rounded",
154022
+ type: "button",
154023
+ "aria-expanded": showTextPopup,
154024
+ 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
154025
  onClick: () => setShowTextPopup((prev) => !prev),
152969
154026
  children: /* @__PURE__ */ jsx(
152970
154027
  "svg",
@@ -152983,10 +154040,9 @@ function TextToolbar() {
152983
154040
  showTextPopup && /* @__PURE__ */ jsxs(
152984
154041
  "div",
152985
154042
  {
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",
154043
+ 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
154044
  style: {
152988
- width: "230px",
152989
- border: "1px solid #333"
154045
+ width: "230px"
152990
154046
  },
152991
154047
  children: [
152992
154048
  /* @__PURE__ */ jsxs("div", { className: "flex justify-between mb-2", children: [
@@ -153075,7 +154131,75 @@ function TextToolbar() {
153075
154131
  },
153076
154132
  children: "Aa"
153077
154133
  }
153078
- )
154134
+ ),
154135
+ /* @__PURE__ */ jsxs("div", { className: "relative", children: [
154136
+ /* @__PURE__ */ jsx(
154137
+ "button",
154138
+ {
154139
+ type: "button",
154140
+ onClick: () => setShowStrokeControls((prev) => !prev),
154141
+ className: `text-left text-[15px] font-semibold ${text?.fontWeight === "bold" ? "text-blue-400" : ""} transition hover:text-blue-400`,
154142
+ children: "Stroke"
154143
+ }
154144
+ ),
154145
+ 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: [
154146
+ /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-2 text-[13px] font-medium text-white", children: [
154147
+ /* @__PURE__ */ jsx("span", { children: "Color" }),
154148
+ /* @__PURE__ */ jsx(
154149
+ "input",
154150
+ {
154151
+ type: "color",
154152
+ value: strokeColor,
154153
+ onChange: (e3) => {
154154
+ const nextColor = e3.target.value;
154155
+ setStrokeColor(nextColor);
154156
+ applyTextStroke(nextColor, strokeSize, strokeStyle);
154157
+ },
154158
+ className: "h-8 w-10 cursor-pointer rounded border border-neutral-600 bg-transparent p-0",
154159
+ "aria-label": "Text stroke color"
154160
+ }
154161
+ )
154162
+ ] }),
154163
+ /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-2 text-[13px] font-medium text-white", children: [
154164
+ /* @__PURE__ */ jsx("span", { children: "Size" }),
154165
+ /* @__PURE__ */ jsx(
154166
+ "select",
154167
+ {
154168
+ value: String(strokeSize),
154169
+ onChange: (e3) => {
154170
+ const nextSize = Math.min(5, Number(e3.target.value));
154171
+ setStrokeSize(nextSize);
154172
+ applyTextStroke(strokeColor, nextSize, strokeStyle);
154173
+ },
154174
+ className: "h-8 min-w-[72px] rounded border border-neutral-600 bg-neutral-800 px-2 text-[13px] text-white outline-none",
154175
+ "aria-label": "Text stroke size",
154176
+ children: STROKE_WIDTH_OPTIONS.map((size) => /* @__PURE__ */ jsx("option", { value: String(size), className: "bg-neutral-800 text-white", children: size }, size))
154177
+ }
154178
+ )
154179
+ ] }),
154180
+ /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-2 text-[13px] font-medium text-white", children: [
154181
+ /* @__PURE__ */ jsx("span", { children: "Style" }),
154182
+ /* @__PURE__ */ jsxs(
154183
+ "select",
154184
+ {
154185
+ value: strokeStyle,
154186
+ onChange: (e3) => {
154187
+ const nextStyle = e3.target.value;
154188
+ setStrokeStyle(nextStyle);
154189
+ applyTextStroke(strokeColor, strokeSize, nextStyle);
154190
+ },
154191
+ className: "h-8 min-w-[90px] rounded border border-neutral-600 bg-neutral-800 px-2 text-[13px] text-white outline-none",
154192
+ "aria-label": "Text stroke style",
154193
+ children: [
154194
+ /* @__PURE__ */ jsx("option", { value: "Dotted", className: "bg-neutral-800 text-white", children: "Dotted" }),
154195
+ /* @__PURE__ */ jsx("option", { value: "Line", className: "bg-neutral-800 text-white", children: "Line" }),
154196
+ /* @__PURE__ */ jsx("option", { value: "Dashed", className: "bg-neutral-800 text-white", children: "Dashed" })
154197
+ ]
154198
+ }
154199
+ )
154200
+ ] })
154201
+ ] })
154202
+ ] })
153079
154203
  ] }) });
153080
154204
  }
153081
154205
  function CropIcon() {
@@ -153193,7 +154317,7 @@ class CropService {
153193
154317
  stroke: "#2196F3",
153194
154318
  strokeWidth: 2,
153195
154319
  cornerColor: "#ffffff",
153196
- cornerStrokeColor: "#202020",
154320
+ cornerStrokeColor: "#2196F3",
153197
154321
  cornerSize: 12,
153198
154322
  cornerStyle: "circle",
153199
154323
  transparentCorners: false,
@@ -153529,7 +154653,7 @@ function QrEffectToolbar() {
153529
154653
  /* @__PURE__ */ jsxs(
153530
154654
  "button",
153531
154655
  {
153532
- className: "hover:text-blue-400 flex",
154656
+ className: "hover:text-emerald-400 flex items-center gap-1.5 cursor-pointer",
153533
154657
  onClick: () => setActivePanel("effects"),
153534
154658
  children: [
153535
154659
  /* @__PURE__ */ jsx(EffectIcon, {}),
@@ -153556,7 +154680,7 @@ function QrEffectToolbar() {
153556
154680
  /* @__PURE__ */ jsx(
153557
154681
  "button",
153558
154682
  {
153559
- className: "hover:text-blue-400",
154683
+ className: "hover:text-emerald-400 cursor-pointer",
153560
154684
  onClick: () => setActivePanel("animate"),
153561
154685
  children: "Animate"
153562
154686
  }
@@ -153669,7 +154793,7 @@ function DuplicateButton() {
153669
154793
  const duplicate = async () => {
153670
154794
  const active = canvas2.getActiveObject();
153671
154795
  if (!active) return;
153672
- if (active.type === "activeselection") {
154796
+ if (active.type?.toLowerCase() === "activeselection") {
153673
154797
  const selection = active;
153674
154798
  const originals = selection.getObjects().filter((o2) => o2.name !== "workarea");
153675
154799
  if (!originals.length) return;
@@ -153796,7 +154920,7 @@ function FlipButton() {
153796
154920
  const getTargets = () => {
153797
154921
  const active = canvas2.getActiveObject();
153798
154922
  if (!active) return [];
153799
- const objects = active.type === "activeselection" ? active.getObjects() : [active];
154923
+ const objects = active.type?.toLowerCase() === "activeselection" ? active.getObjects() : [active];
153800
154924
  return objects.filter((o2) => o2.name !== "workarea");
153801
154925
  };
153802
154926
  const handleFlip = (flip) => {
@@ -153941,7 +155065,7 @@ function LockButton() {
153941
155065
  const getTargets = () => {
153942
155066
  const active = canvas2?.getActiveObject();
153943
155067
  if (!active) return [];
153944
- const objects = active.type === "activeselection" ? active.getObjects() : [active];
155068
+ const objects = active.type?.toLowerCase() === "activeselection" ? active.getObjects() : [active];
153945
155069
  return objects.filter((o2) => o2.name !== "workarea");
153946
155070
  };
153947
155071
  useEffect(() => {
@@ -154034,12 +155158,12 @@ function OpacityPicker() {
154034
155158
  const getTargets = () => {
154035
155159
  const active = getActive();
154036
155160
  if (!active) return [];
154037
- return active.type === "activeselection" ? active.getObjects() : [active];
155161
+ return active.type?.toLowerCase() === "activeselection" ? active.getObjects() : [active];
154038
155162
  };
154039
155163
  const readOpacityFromSelection = () => {
154040
155164
  const active = getActive();
154041
155165
  if (!active) return 100;
154042
- if (active.type === "activeselection") {
155166
+ if (active.type?.toLowerCase() === "activeselection") {
154043
155167
  const objects = active.getObjects();
154044
155168
  return Math.round((objects[0]?.opacity ?? 1) * 100);
154045
155169
  }
@@ -154159,7 +155283,7 @@ function PositionPicker() {
154159
155283
  const getTargets = () => {
154160
155284
  const active = getActive();
154161
155285
  if (!active) return [];
154162
- return active.type === "activeselection" ? active.getObjects() : [active];
155286
+ return active.type?.toLowerCase() === "activeselection" ? active.getObjects() : [active];
154163
155287
  };
154164
155288
  const getWorkarea2 = () => canvas2.getObjects().find((o2) => o2.name === "workarea");
154165
155289
  const normalizeOrigin = (obj) => {
@@ -154207,7 +155331,7 @@ function PositionPicker() {
154207
155331
  const handleAlign = (align) => {
154208
155332
  const active = getActive();
154209
155333
  if (!active) return;
154210
- if (active.type === "activeselection") {
155334
+ if (active.type?.toLowerCase() === "activeselection") {
154211
155335
  alignInsideSelection(align);
154212
155336
  } else {
154213
155337
  applyAlign(align);
@@ -154374,7 +155498,7 @@ function RemoveButton() {
154374
155498
  const getTargets = () => {
154375
155499
  const active = canvas2?.getActiveObject();
154376
155500
  if (!active) return [];
154377
- const objects = active.type === "activeselection" ? active.getObjects() : [active];
155501
+ const objects = active.type?.toLowerCase() === "activeselection" ? active.getObjects() : [active];
154378
155502
  return objects.filter((o2) => o2.name !== "workarea");
154379
155503
  };
154380
155504
  const deleteObjects = () => {
@@ -154448,7 +155572,7 @@ function RotateButton() {
154448
155572
  const getTargets = () => {
154449
155573
  const active = canvas2.getActiveObject();
154450
155574
  if (!active) return [];
154451
- const objects = active.type === "activeselection" ? active.getObjects() : [active];
155575
+ const objects = active.type?.toLowerCase() === "activeselection" ? active.getObjects() : [active];
154452
155576
  return objects.filter((o2) => o2.name !== "workarea");
154453
155577
  };
154454
155578
  const handleRotate = (dir, step = 45) => {
@@ -154537,13 +155661,19 @@ function GroupButton() {
154537
155661
  const handleGroup = () => {
154538
155662
  if (!canvas2) return;
154539
155663
  const active = canvas2.getActiveObject();
154540
- if (!active || active.type !== "activeselection") return;
155664
+ if (!active || active.type?.toLowerCase() !== "activeselection") return;
154541
155665
  const selection = active;
154542
155666
  const objects2 = [...selection.getObjects()];
155667
+ const canvasObjects = canvas2.getObjects();
155668
+ const minZIndex = Math.min(...objects2.map((o2) => canvasObjects.indexOf(o2)));
154543
155669
  canvas2.discardActiveObject();
154544
155670
  objects2.forEach((obj) => canvas2.remove(obj));
154545
155671
  const group = new Ur(objects2);
154546
- canvas2.add(group);
155672
+ if (minZIndex !== -1 && minZIndex < canvas2.getObjects().length) {
155673
+ canvas2.insertAt(minZIndex, group);
155674
+ } else {
155675
+ canvas2.add(group);
155676
+ }
154547
155677
  canvas2.setActiveObject(group);
154548
155678
  canvas2.requestRenderAll();
154549
155679
  useSelectionStore.getState().setSelection([group]);
@@ -154592,6 +155722,14 @@ const rotateSVG = `
154592
155722
  </g>
154593
155723
  </svg>`;
154594
155724
  let rotateImg = null;
155725
+ const SMALL_OBJECT_THRESHOLD = 40;
155726
+ const SMALL_CONTROL_SIZE = 6;
155727
+ const NORMAL_CONTROL_SIZE = 10;
155728
+ const ROTATION_GAP = 6;
155729
+ const isTextObject = (obj) => obj.type === "textbox" || obj.type === "i-text" || obj.type === "text";
155730
+ function normalizeAngle(angle = 0) {
155731
+ return (Math.round(angle) % 360 + 360) % 360;
155732
+ }
154595
155733
  function renderRotateIcon(ctx, left, top, fabricObject) {
154596
155734
  if (!rotateImg) {
154597
155735
  rotateImg = new Image();
@@ -154600,7 +155738,7 @@ function renderRotateIcon(ctx, left, top, fabricObject) {
154600
155738
  fabricObject.canvas?.requestRenderAll();
154601
155739
  };
154602
155740
  }
154603
- const size = 24;
155741
+ const size = fabricObject.__rotationControlSize || 24;
154604
155742
  ctx.save();
154605
155743
  ctx.translate(left, top);
154606
155744
  if (rotateImg.complete && rotateImg.naturalHeight !== 0) {
@@ -154608,11 +155746,13 @@ function renderRotateIcon(ctx, left, top, fabricObject) {
154608
155746
  }
154609
155747
  ctx.restore();
154610
155748
  }
154611
- function createRotateControl() {
155749
+ function createRotateControl(size = 24, offsetY = -30) {
154612
155750
  return new ai({
154613
155751
  x: 0,
154614
155752
  y: -0.5,
154615
- offsetY: -30,
155753
+ offsetY,
155754
+ sizeX: size,
155755
+ sizeY: size,
154616
155756
  cursorStyle: "crosshair",
154617
155757
  actionHandler: Fa.rotationWithSnapping,
154618
155758
  actionName: "rotate",
@@ -154620,23 +155760,147 @@ function createRotateControl() {
154620
155760
  withConnection: true
154621
155761
  });
154622
155762
  }
155763
+ function renderRotationAngle(ctx, left, top, _styleOverride, fabricObject) {
155764
+ if (!fabricObject.__showDegree) {
155765
+ return;
155766
+ }
155767
+ const angle = normalizeAngle(fabricObject.angle);
155768
+ const label = `${angle}°`;
155769
+ const paddingX = 8;
155770
+ const height = 22;
155771
+ const radius = 6;
155772
+ ctx.save();
155773
+ ctx.font = "bold 12px sans-serif";
155774
+ ctx.textAlign = "center";
155775
+ ctx.textBaseline = "middle";
155776
+ const textWidth = ctx.measureText(label).width;
155777
+ const width = Math.max(34, textWidth + paddingX * 2);
155778
+ const x2 = left - width / 2;
155779
+ const y2 = top - height / 2;
155780
+ ctx.beginPath();
155781
+ ctx.fillStyle = "rgba(15, 23, 42, 0.9)";
155782
+ ctx.strokeStyle = "#02bc71";
155783
+ ctx.lineWidth = 1.5;
155784
+ if (typeof ctx.roundRect === "function") {
155785
+ ctx.roundRect(x2, y2, width, height, radius);
155786
+ } else {
155787
+ ctx.rect(x2, y2, width, height);
155788
+ }
155789
+ ctx.fill();
155790
+ ctx.stroke();
155791
+ ctx.fillStyle = "#ffffff";
155792
+ ctx.fillText(label, left, top + 0.5);
155793
+ ctx.restore();
155794
+ }
155795
+ function createRotationAngleControl() {
155796
+ return new ai({
155797
+ x: -0.5,
155798
+ y: 0,
155799
+ offsetX: -28,
155800
+ offsetY: 0,
155801
+ sizeX: 1,
155802
+ sizeY: 1,
155803
+ cursorStyle: "default",
155804
+ render: renderRotationAngle
155805
+ });
155806
+ }
155807
+ const isImageObject = (obj) => obj.type === "image" || obj.type === "FabricImage" || typeof obj.isType === "function" && (obj.isType("image") || obj.isType("FabricImage"));
154623
155808
  function configureObjectControls(obj) {
155809
+ const zoom = obj.canvas ? obj.canvas.getZoom() : 1;
155810
+ const scaledWidth = obj.getScaledWidth() * zoom;
155811
+ const scaledHeight = obj.getScaledHeight() * zoom;
155812
+ const renderedSize = Math.min(scaledWidth, scaledHeight);
155813
+ const isSmall = renderedSize < SMALL_OBJECT_THRESHOLD;
155814
+ const cornerSize = isSmall ? Math.min(SMALL_CONTROL_SIZE, Math.max(4, renderedSize * 0.3)) : NORMAL_CONTROL_SIZE;
155815
+ const rotationSize = isSmall ? Math.min(16, Math.max(12, renderedSize * 0.4)) : 24;
155816
+ const rotationOffset = -(rotationSize / 2 + ROTATION_GAP);
154624
155817
  obj.set({
154625
155818
  transparentCorners: false,
154626
155819
  cornerColor: "#ffffff",
154627
155820
  cornerStrokeColor: "#02bc71",
154628
155821
  borderColor: "#02bc71",
154629
- cornerSize: 10,
155822
+ cornerSize,
154630
155823
  cornerStyle: "rect",
154631
155824
  borderScaleFactor: 1
154632
155825
  });
154633
- obj.controls.mtr = createRotateControl();
155826
+ obj.__rotationControlSize = rotationSize;
155827
+ obj.controls.mtr = createRotateControl(rotationSize, rotationOffset);
155828
+ const textObject = isTextObject(obj);
155829
+ if (!obj.controls.mb) {
155830
+ obj.controls.mb = new ai({
155831
+ x: 0,
155832
+ y: 0.5,
155833
+ cursorStyleHandler: Fa.scaleSkewCursorStyleHandler,
155834
+ actionHandler: textObject ? Fa.changeWidth : Fa.scalingYOrSkewingX,
155835
+ actionName: "resizing"
155836
+ });
155837
+ }
155838
+ const rotationControl = createRotationAngleControl();
155839
+ obj.controls.rotationAngle = rotationControl;
155840
+ delete obj.controls.bmc;
155841
+ delete obj.controls.textAngle;
155842
+ if (isSmall) {
155843
+ obj.setControlsVisibility({
155844
+ mtr: true,
155845
+ br: true,
155846
+ tl: false,
155847
+ tr: false,
155848
+ bl: false,
155849
+ mb: false,
155850
+ ml: false,
155851
+ mr: false,
155852
+ mt: false,
155853
+ bmc: false,
155854
+ rotationAngle: true
155855
+ });
155856
+ return;
155857
+ }
155858
+ if (isImageObject(obj)) {
155859
+ obj.setControlsVisibility({
155860
+ mtr: true,
155861
+ tl: true,
155862
+ tr: true,
155863
+ bl: true,
155864
+ br: true,
155865
+ ml: false,
155866
+ mr: false,
155867
+ mb: true,
155868
+ mt: false,
155869
+ bmc: false,
155870
+ rotationAngle: true
155871
+ });
155872
+ return;
155873
+ }
155874
+ if (!obj.controls.ml) {
155875
+ obj.controls.ml = new ai({
155876
+ x: -0.5,
155877
+ y: 0,
155878
+ cursorStyleHandler: Fa.scaleSkewCursorStyleHandler,
155879
+ actionHandler: textObject ? Fa.changeWidth : Fa.scalingXOrSkewingY,
155880
+ actionName: "resizing"
155881
+ });
155882
+ }
155883
+ if (!obj.controls.mr) {
155884
+ obj.controls.mr = new ai({
155885
+ x: 0.5,
155886
+ y: 0,
155887
+ cursorStyleHandler: Fa.scaleSkewCursorStyleHandler,
155888
+ actionHandler: textObject ? Fa.changeWidth : Fa.scalingXOrSkewingY,
155889
+ actionName: "resizing"
155890
+ });
155891
+ }
154634
155892
  obj.setControlsVisibility({
154635
155893
  mtr: true,
154636
- ml: false,
154637
- mr: false,
155894
+ tl: true,
155895
+ tr: true,
155896
+ bl: true,
155897
+ br: true,
155898
+ ml: true,
155899
+ mr: true,
155900
+ mb: true,
154638
155901
  mt: false,
154639
- mb: false
155902
+ bmc: false,
155903
+ rotationAngle: true
154640
155904
  });
154641
155905
  }
154642
155906
  function initializeRotateImage() {
@@ -154653,20 +155917,38 @@ function UngroupButton() {
154653
155917
  const handleUngroup = () => {
154654
155918
  if (!canvas2) return;
154655
155919
  const active = canvas2.getActiveObject();
154656
- if (!active || active.type !== "group") return;
155920
+ if (!active || active.type?.toLowerCase() !== "group") return;
154657
155921
  const group = active;
154658
- const objects2 = [...group.getObjects()];
154659
- group.remove(...objects2);
155922
+ const groupIndex = canvas2.getObjects().indexOf(group);
155923
+ const childObjects = [...group.getObjects()];
155924
+ canvas2.discardActiveObject();
155925
+ group.remove(...childObjects);
154660
155926
  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);
155927
+ if (typeof group.dispose === "function") {
155928
+ group.dispose();
155929
+ }
155930
+ childObjects.forEach((obj, idx) => {
155931
+ obj.set({
155932
+ selectable: true,
155933
+ evented: true,
155934
+ hasControls: true,
155935
+ hasBorders: true
155936
+ });
155937
+ obj.setCoords();
155938
+ if (groupIndex !== -1) {
155939
+ canvas2.insertAt(groupIndex + idx, obj);
155940
+ } else {
155941
+ canvas2.add(obj);
155942
+ }
155943
+ configureObjectControls(obj);
155944
+ });
155945
+ canvas2.discardActiveObject();
155946
+ useSelectionStore.getState().clearSelection();
155947
+ canvas2.requestRenderAll();
154667
155948
  saveState();
154668
155949
  };
154669
- if (selectionType !== "multiple" || objects.length !== 1) return null;
155950
+ const isGroupSelected = selectionType === "group" || objects.length === 1 && objects[0]?.type?.toLowerCase() === "group" && objects[0]?.name !== "qr" && objects[0]?.name !== "barcode";
155951
+ if (!isGroupSelected) return null;
154670
155952
  return /* @__PURE__ */ jsx(
154671
155953
  "button",
154672
155954
  {
@@ -155012,6 +156294,9 @@ function createWorkarea(canvas2, container, productData) {
155012
156294
  initialZoom = 1;
155013
156295
  }
155014
156296
  const initialScale = baseScale * initialZoom;
156297
+ const isTextTemplate = productData?.isTextTemplate || productData?.hideGuidelines || typeof window !== "undefined" && ["text", "texttemplate", "text-template"].includes(
156298
+ (new URLSearchParams(window.location.search).get("designType") || new URLSearchParams(window.location.search).get("designtype") || new URLSearchParams(window.location.search).get("type") || "")?.toLowerCase()
156299
+ );
155015
156300
  const workarea = new jr({
155016
156301
  width: fullWidth,
155017
156302
  height: fullHeight,
@@ -155027,6 +156312,8 @@ function createWorkarea(canvas2, container, productData) {
155027
156312
  workarea.__printDPI = printDPI;
155028
156313
  workarea.__baseScale = baseScale;
155029
156314
  workarea.__initialZoom = initialZoom;
156315
+ workarea.__hideGuidelines = isTextTemplate || productData?.hideGuidelines;
156316
+ workarea.__isTextTemplate = isTextTemplate;
155030
156317
  workarea.__bleedArea = productData?.bleedArea ?? 0.125;
155031
156318
  workarea.__safetyArea = productData?.safetyArea ?? 0.125;
155032
156319
  workarea.__trimArea = productData?.trimArea ?? 0;
@@ -155074,27 +156361,63 @@ function setupSelectionHandlers(canvas2, setSelection, clearSelection) {
155074
156361
  clearSelection();
155075
156362
  return;
155076
156363
  }
155077
- const objects = active.type === "activeselection" ? active.getObjects() : [active];
156364
+ const isMulti = active.type?.toLowerCase() === "activeselection";
156365
+ const objects = isMulti ? active.getObjects() : [active];
155078
156366
  setSelection(objects);
155079
156367
  };
155080
- const configureSelectionControls = (e3) => {
156368
+ const configureSelectionControls = () => {
155081
156369
  const activeObject = canvas2.getActiveObject();
155082
156370
  if (!activeObject) return;
155083
156371
  configureObjectControls(activeObject);
155084
156372
  canvas2.requestRenderAll();
155085
156373
  };
155086
156374
  canvas2.on("selection:created", (e3) => {
156375
+ if (e3.selected) {
156376
+ e3.selected.forEach((o2) => {
156377
+ o2.__showDegree = false;
156378
+ });
156379
+ }
155087
156380
  updateSelection();
155088
156381
  configureSelectionControls();
155089
156382
  });
155090
156383
  canvas2.on("selection:updated", (e3) => {
156384
+ if (e3.selected) {
156385
+ e3.selected.forEach((o2) => {
156386
+ o2.__showDegree = false;
156387
+ });
156388
+ }
155091
156389
  updateSelection();
155092
156390
  configureSelectionControls();
155093
156391
  });
155094
- canvas2.on("selection:cleared", clearSelection);
156392
+ canvas2.on("object:scaling", configureSelectionControls);
156393
+ canvas2.on("object:rotating", (e3) => {
156394
+ if (e3.target) {
156395
+ e3.target.__showDegree = true;
156396
+ }
156397
+ configureSelectionControls();
156398
+ });
156399
+ const hideDegree = (e3) => {
156400
+ if (e3?.target) {
156401
+ e3.target.__showDegree = false;
156402
+ }
156403
+ canvas2.getObjects().forEach((o2) => {
156404
+ o2.__showDegree = false;
156405
+ });
156406
+ configureSelectionControls();
156407
+ };
156408
+ canvas2.on("object:modified", hideDegree);
156409
+ canvas2.on("mouse:up", hideDegree);
156410
+ canvas2.on("selection:cleared", () => {
156411
+ hideDegree();
156412
+ clearSelection();
156413
+ });
155095
156414
  return () => {
155096
156415
  canvas2.off("selection:created");
155097
156416
  canvas2.off("selection:updated");
156417
+ canvas2.off("object:scaling", configureSelectionControls);
156418
+ canvas2.off("object:rotating");
156419
+ canvas2.off("object:modified", hideDegree);
156420
+ canvas2.off("mouse:up", hideDegree);
155098
156421
  canvas2.off("selection:cleared");
155099
156422
  };
155100
156423
  }
@@ -155139,6 +156462,23 @@ function setupBorderSync(canvas2) {
155139
156462
  canvas2.off("object:rotating", syncBorder);
155140
156463
  };
155141
156464
  }
156465
+ const CUSTOM_PROPERTIES = [
156466
+ "name",
156467
+ "id",
156468
+ "selectable",
156469
+ "evented",
156470
+ "hasControls",
156471
+ "lockMovementX",
156472
+ "lockMovementY",
156473
+ "__printWidth",
156474
+ "__printHeight",
156475
+ "__printDPI",
156476
+ "__baseScale",
156477
+ "__initialZoom",
156478
+ "__borderRect",
156479
+ "__rotationControlSize",
156480
+ "__showDegree"
156481
+ ];
155142
156482
  async function switchCanvasPage(canvas2, currentPage, targetPage, pageJSON, clearSelection, containerRef) {
155143
156483
  if (targetPage === currentPage) return;
155144
156484
  const historyStore = useHistoryStore.getState();
@@ -155147,8 +156487,7 @@ async function switchCanvasPage(canvas2, currentPage, targetPage, pageJSON, clea
155147
156487
  const storePages = useEditorStore.getState().pages || {};
155148
156488
  if (storePages.front && !pageJSON.front) pageJSON.front = storePages.front;
155149
156489
  if (storePages.back && !pageJSON.back) pageJSON.back = storePages.back;
155150
- const currentObject = canvas2.toObject(["name"]);
155151
- pageJSON[currentPage] = currentObject;
156490
+ pageJSON[currentPage] = canvas2.toObject(CUSTOM_PROPERTIES);
155152
156491
  useEditorStore.getState().setPages({ ...pageJSON });
155153
156492
  clearSelection();
155154
156493
  historyStore.setPage(targetPage);
@@ -155170,6 +156509,16 @@ async function switchCanvasPage(canvas2, currentPage, targetPage, pageJSON, clea
155170
156509
  } finally {
155171
156510
  useLoaderStore.getState().stopLoading();
155172
156511
  }
156512
+ const currentVt = canvas2.viewportTransform ? [...canvas2.viewportTransform] : [canvas2.getZoom(), 0, 0, canvas2.getZoom(), 0, 0];
156513
+ canvas2.setViewportTransform(currentVt);
156514
+ const workarea = canvas2.getObjects().find((obj) => obj.name === "workarea");
156515
+ if (workarea) {
156516
+ createOverlays(canvas2, workarea);
156517
+ bringOverlaysToFront(canvas2);
156518
+ }
156519
+ canvas2.requestRenderAll();
156520
+ canvas2.fire("after:render");
156521
+ useHistoryStore.setState({ isRestoring: false });
155173
156522
  }
155174
156523
  const MIN_ZOOM = 0.09;
155175
156524
  const MAX_ZOOM = 5;
@@ -155182,53 +156531,28 @@ const fromSlider = (v2) => MIN_ZOOM * Math.pow(MAX_ZOOM / MIN_ZOOM, v2);
155182
156531
  function getWorkarea(canvas2) {
155183
156532
  return canvas2.getObjects().find((o2) => o2.name === "workarea") ?? null;
155184
156533
  }
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
156534
  function doZoom(canvas2, newZoom, pivot) {
155203
156535
  const clamped = clamp(newZoom, MIN_ZOOM, MAX_ZOOM);
155204
156536
  canvas2.zoomToPoint(pivot, clamped);
155205
- normalizeViewportPan(canvas2);
155206
156537
  const workarea = getWorkarea(canvas2);
155207
156538
  if (workarea) createOverlays(canvas2, workarea);
156539
+ const activeObject = canvas2.getActiveObject();
156540
+ if (activeObject) configureObjectControls(activeObject);
155208
156541
  canvas2.requestRenderAll();
155209
156542
  return clamped;
155210
156543
  }
155211
156544
  function doReset(canvas2, setZoom) {
155212
- const center = new ot(canvas2.getWidth() / 2, canvas2.getHeight() / 2);
155213
- canvas2.zoomToPoint(center, 1);
155214
- normalizeViewportPan(canvas2);
155215
156545
  const workarea = getWorkarea(canvas2);
156546
+ const initialZoom = workarea ? workarea.__initialZoom || 1 : 1;
156547
+ const center = new ot(canvas2.getWidth() / 2, canvas2.getHeight() / 2);
156548
+ canvas2.zoomToPoint(center, initialZoom);
155216
156549
  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));
156550
+ createOverlays(canvas2, workarea);
155229
156551
  }
156552
+ const activeObject = canvas2.getActiveObject();
156553
+ if (activeObject) configureObjectControls(activeObject);
155230
156554
  canvas2.requestRenderAll();
155231
- setZoom(1);
156555
+ setZoom(initialZoom);
155232
156556
  }
155233
156557
  function useCanvasZoom(canvas2) {
155234
156558
  const [zoom, setZoom] = useState(1);
@@ -155555,7 +156879,7 @@ function useKeyboardEvents(canvas2) {
155555
156879
  left: (clipboardRef.current.left ?? 0) + OFFSET,
155556
156880
  top: (clipboardRef.current.top ?? 0) + OFFSET
155557
156881
  });
155558
- if (clonedObj.type === "activeselection") {
156882
+ if (clonedObj.type?.toLowerCase() === "activeselection") {
155559
156883
  clonedObj.canvas = canvas2;
155560
156884
  clonedObj.forEachObject((obj) => {
155561
156885
  canvas2.add(obj);
@@ -155611,8 +156935,11 @@ function Workspace() {
155611
156935
  if (fabricRef.current) return;
155612
156936
  let isMounted = true;
155613
156937
  const setupCanvasWithProduct = async () => {
156938
+ const designType = (getQueryParam("designType") || getQueryParam("designtype") || getQueryParam("type"))?.toLowerCase();
156939
+ const isTextDesign = designType === "text";
155614
156940
  const productId = getQueryParam("id") || getQueryParam("productId");
155615
- if (!productId) {
156941
+ const templateId = getQueryParam("templateId") || (isTextDesign ? getQueryParam("id") : null);
156942
+ if (!productId && !isTextDesign) {
155616
156943
  useProductStore.setState({
155617
156944
  isNotFound: true,
155618
156945
  error: "Product ID is missing from URL query parameters (e.g. ?id=...)."
@@ -155621,9 +156948,26 @@ function Workspace() {
155621
156948
  }
155622
156949
  useLoaderStore.getState().startLoading();
155623
156950
  try {
155624
- const product = await useProductStore.getState().fetchProduct(productId);
155625
- if (!product || !product._id) {
155626
- return;
156951
+ let product = null;
156952
+ if (isTextDesign) {
156953
+ product = {
156954
+ _id: "text-template-product",
156955
+ bleedArea: 0,
156956
+ trimArea: 0,
156957
+ safetyArea: 0,
156958
+ hideGuidelines: true,
156959
+ isTextTemplate: true,
156960
+ optionsMap: {
156961
+ size: '2" x 2"',
156962
+ productorientation: "Horizontal"
156963
+ }
156964
+ };
156965
+ useProductStore.setState({ productData: product, isNotFound: false, error: null });
156966
+ } else if (productId) {
156967
+ product = await useProductStore.getState().fetchProduct(productId);
156968
+ if (!product || !product._id) {
156969
+ return;
156970
+ }
155627
156971
  }
155628
156972
  if (!isMounted || !canvasRef.current || !containerRef.current || fabricRef.current) return;
155629
156973
  initializeRotateImage();
@@ -155641,11 +156985,20 @@ function Workspace() {
155641
156985
  clearSelection
155642
156986
  );
155643
156987
  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) {
156988
+ if (isTextDesign && templateId) {
156989
+ const item = await ShapeService.getTextTemplateById(templateId);
156990
+ if (item?.jsonData) {
156991
+ const parsed = typeof item.jsonData === "string" ? JSON.parse(item.jsonData) : item.jsonData;
156992
+ pageJSON.current.front = parsed;
156993
+ useHistoryStore.setState({ isRestoring: true });
156994
+ await loadPageToCanvas(canvas2, parsed);
156995
+ useHistoryStore.setState({ isRestoring: false });
156996
+ useHistoryStore.getState().saveState();
156997
+ }
156998
+ } else if (templateId) {
156999
+ const twoSided = product?.optionsMap?.printing === "4/4";
157000
+ let frontData = null;
157001
+ let backData = null;
155649
157002
  const res = await TemplateDesignService.getTemplateDesignById(templateId);
155650
157003
  const tpl = res?.data;
155651
157004
  if (tpl?.pages && Array.isArray(tpl.pages)) {
@@ -155654,16 +157007,16 @@ function Workspace() {
155654
157007
  if (frontPage?.designData) frontData = frontPage.designData;
155655
157008
  if (backPage?.designData) backData = backPage.designData;
155656
157009
  }
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;
157010
+ if (frontData) {
157011
+ pageJSON.current.front = frontData;
157012
+ useHistoryStore.setState({ isRestoring: true });
157013
+ await loadPageToCanvas(canvas2, frontData);
157014
+ useHistoryStore.setState({ isRestoring: false });
157015
+ useHistoryStore.getState().saveState();
157016
+ }
157017
+ if (twoSided && backData) {
157018
+ pageJSON.current.back = backData;
157019
+ }
155667
157020
  }
155668
157021
  useEditorStore.getState().setPages({ ...pageJSON.current });
155669
157022
  return () => {