sa2kit 1.6.89 → 1.6.91

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.
Files changed (73) hide show
  1. package/dist/{booking-473Db8Bo.d.mts → booking-BH7HM0D0.d.mts} +1 -0
  2. package/dist/{booking-473Db8Bo.d.ts → booking-BH7HM0D0.d.ts} +1 -0
  3. package/dist/{bookingAdminService-DqQ7hEGw.d.ts → bookingAdminService-nr1vOp6I.d.ts} +1 -1
  4. package/dist/{bookingAdminService-SBX4JA_U.d.mts → bookingAdminService-pvk2MY1r.d.mts} +1 -1
  5. package/dist/{client-Bkn6mRI7.d.ts → client-UDQ7uMFA.d.ts} +1 -1
  6. package/dist/{client-exYn2Qla.d.mts → client-jOToHJEx.d.mts} +1 -1
  7. package/dist/festivalCard/index.js +803 -212
  8. package/dist/festivalCard/index.js.map +1 -1
  9. package/dist/festivalCard/index.mjs +784 -193
  10. package/dist/festivalCard/index.mjs.map +1 -1
  11. package/dist/festivalCard/miniapp/index.js +162 -21
  12. package/dist/festivalCard/miniapp/index.js.map +1 -1
  13. package/dist/festivalCard/miniapp/index.mjs +153 -12
  14. package/dist/festivalCard/miniapp/index.mjs.map +1 -1
  15. package/dist/festivalCard/web/index.d.mts +17 -3
  16. package/dist/festivalCard/web/index.d.ts +17 -3
  17. package/dist/festivalCard/web/index.js +803 -212
  18. package/dist/festivalCard/web/index.js.map +1 -1
  19. package/dist/festivalCard/web/index.mjs +784 -193
  20. package/dist/festivalCard/web/index.mjs.map +1 -1
  21. package/dist/{index-z15F7afa.d.mts → index-Bs06cHTn.d.mts} +2 -2
  22. package/dist/{index-BJpxvH7X.d.ts → index-C-oNM7Gv.d.ts} +1 -1
  23. package/dist/{index-XTV6IU-M.d.ts → index-CUab5EBV.d.ts} +2 -2
  24. package/dist/{index-Cum2EknK.d.mts → index-CYDb3AKs.d.mts} +1 -1
  25. package/dist/{index-DyxLpkmm.d.mts → index-DBB4ad0S.d.mts} +2 -2
  26. package/dist/{index-CdTIsNsy.d.ts → index-DBHwbXrv.d.ts} +2 -2
  27. package/dist/index.js +575 -170
  28. package/dist/index.js.map +1 -1
  29. package/dist/index.mjs +575 -170
  30. package/dist/index.mjs.map +1 -1
  31. package/dist/showmasterpiece/core.d.mts +3 -3
  32. package/dist/showmasterpiece/core.d.ts +3 -3
  33. package/dist/showmasterpiece/db.d.mts +2 -0
  34. package/dist/showmasterpiece/db.d.ts +2 -0
  35. package/dist/showmasterpiece/db.js +4 -2
  36. package/dist/showmasterpiece/db.js.map +1 -1
  37. package/dist/showmasterpiece/db.mjs +4 -2
  38. package/dist/showmasterpiece/db.mjs.map +1 -1
  39. package/dist/showmasterpiece/index.js +18 -2
  40. package/dist/showmasterpiece/index.js.map +1 -1
  41. package/dist/showmasterpiece/index.mjs +18 -2
  42. package/dist/showmasterpiece/index.mjs.map +1 -1
  43. package/dist/showmasterpiece/logic/index.d.mts +2 -2
  44. package/dist/showmasterpiece/logic/index.d.ts +2 -2
  45. package/dist/showmasterpiece/server/index.js +4 -2
  46. package/dist/showmasterpiece/server/index.js.map +1 -1
  47. package/dist/showmasterpiece/server/index.mjs +4 -2
  48. package/dist/showmasterpiece/server/index.mjs.map +1 -1
  49. package/dist/showmasterpiece/service/api/index.d.mts +1 -1
  50. package/dist/showmasterpiece/service/api/index.d.ts +1 -1
  51. package/dist/showmasterpiece/service/client-business/index.d.mts +3 -3
  52. package/dist/showmasterpiece/service/client-business/index.d.ts +3 -3
  53. package/dist/showmasterpiece/service/index.d.mts +6 -6
  54. package/dist/showmasterpiece/service/index.d.ts +6 -6
  55. package/dist/showmasterpiece/service/miniapp/index.d.mts +2 -2
  56. package/dist/showmasterpiece/service/miniapp/index.d.ts +2 -2
  57. package/dist/showmasterpiece/service/web/index.d.mts +4 -4
  58. package/dist/showmasterpiece/service/web/index.d.ts +4 -4
  59. package/dist/showmasterpiece/ui/miniapp/index.d.mts +2 -2
  60. package/dist/showmasterpiece/ui/miniapp/index.d.ts +2 -2
  61. package/dist/showmasterpiece/ui/miniapp/index.js +4 -3
  62. package/dist/showmasterpiece/ui/miniapp/index.js.map +1 -1
  63. package/dist/showmasterpiece/ui/miniapp/index.mjs +4 -3
  64. package/dist/showmasterpiece/ui/miniapp/index.mjs.map +1 -1
  65. package/dist/showmasterpiece/ui/web/index.js +18 -2
  66. package/dist/showmasterpiece/ui/web/index.js.map +1 -1
  67. package/dist/showmasterpiece/ui/web/index.mjs +18 -2
  68. package/dist/showmasterpiece/ui/web/index.mjs.map +1 -1
  69. package/dist/showmasterpiece/web/index.js +18 -2
  70. package/dist/showmasterpiece/web/index.js.map +1 -1
  71. package/dist/showmasterpiece/web/index.mjs +18 -2
  72. package/dist/showmasterpiece/web/index.mjs.map +1 -1
  73. package/package.json +1 -1
package/dist/index.mjs CHANGED
@@ -7395,15 +7395,92 @@ var renderElement = (element) => {
7395
7395
  }
7396
7396
  );
7397
7397
  };
7398
- var FestivalCardPageRenderer = ({ page }) => {
7399
- const backgroundElement = page.elements.find(
7400
- (element) => element.type === "image" && Boolean(element.isBackground)
7398
+ var clamp = (value, min, max) => Math.min(max, Math.max(min, value));
7399
+ var FestivalCardPageRenderer = ({
7400
+ page,
7401
+ editable = false,
7402
+ selectedElementId = null,
7403
+ onElementSelect,
7404
+ onElementChange
7405
+ }) => {
7406
+ const [draggingElementId, setDraggingElementId] = useState(null);
7407
+ const [resizingElementId, setResizingElementId] = useState(null);
7408
+ const stageRef = useRef(null);
7409
+ const interactionRef = useRef(null);
7410
+ const backgroundElement = useMemo(
7411
+ () => page.elements.find(
7412
+ (element) => element.type === "image" && Boolean(element.isBackground)
7413
+ ),
7414
+ [page]
7401
7415
  );
7402
- const foregroundElements = page.elements.filter((element) => !(element.type === "image" && element.isBackground));
7416
+ const foregroundElements = useMemo(
7417
+ () => page.elements.filter((element) => !(element.type === "image" && element.isBackground)),
7418
+ [page]
7419
+ );
7420
+ const updateElementByPointer = (element, interaction, clientX, clientY) => {
7421
+ if (!onElementChange || interaction.rect.width <= 0 || interaction.rect.height <= 0) return;
7422
+ const xPercent = clamp((clientX - interaction.rect.left) / interaction.rect.width * 100, 0, 100);
7423
+ const yPercent = clamp((clientY - interaction.rect.top) / interaction.rect.height * 100, 0, 100);
7424
+ if (interaction.mode === "move") {
7425
+ onElementChange(element.id, { x: xPercent, y: yPercent });
7426
+ return;
7427
+ }
7428
+ const nextWidth = clamp(Math.abs(xPercent - element.x) * 2, 4, 100);
7429
+ if (element.type === "image") {
7430
+ const nextHeight = clamp(Math.abs(yPercent - element.y) * 2, 4, 100);
7431
+ onElementChange(element.id, { width: nextWidth, height: nextHeight });
7432
+ return;
7433
+ }
7434
+ onElementChange(element.id, { width: nextWidth });
7435
+ };
7436
+ const beginInteraction = (event, elementId, mode) => {
7437
+ if (!editable || !stageRef.current) return;
7438
+ event.preventDefault();
7439
+ event.stopPropagation();
7440
+ const rect = stageRef.current.getBoundingClientRect();
7441
+ interactionRef.current = {
7442
+ pointerId: event.pointerId,
7443
+ elementId,
7444
+ mode,
7445
+ rect
7446
+ };
7447
+ event.currentTarget.setPointerCapture(event.pointerId);
7448
+ onElementSelect?.(elementId);
7449
+ if (mode === "move") {
7450
+ setDraggingElementId(elementId);
7451
+ setResizingElementId(null);
7452
+ } else {
7453
+ setResizingElementId(elementId);
7454
+ setDraggingElementId(null);
7455
+ }
7456
+ const element = foregroundElements.find((item) => item.id === elementId);
7457
+ if (element) {
7458
+ updateElementByPointer(element, interactionRef.current, event.clientX, event.clientY);
7459
+ }
7460
+ };
7461
+ const handlePointerMove = (event) => {
7462
+ const interaction = interactionRef.current;
7463
+ if (!interaction || interaction.pointerId !== event.pointerId) return;
7464
+ const element = foregroundElements.find((item) => item.id === interaction.elementId);
7465
+ if (!element) return;
7466
+ updateElementByPointer(element, interaction, event.clientX, event.clientY);
7467
+ };
7468
+ const endInteraction = (event) => {
7469
+ const interaction = interactionRef.current;
7470
+ if (!interaction || interaction.pointerId !== event.pointerId) return;
7471
+ interactionRef.current = null;
7472
+ setDraggingElementId(null);
7473
+ setResizingElementId(null);
7474
+ };
7403
7475
  return /* @__PURE__ */ React69__default.createElement(
7404
7476
  "div",
7405
7477
  {
7406
- className: "relative h-full w-full overflow-hidden rounded-2xl",
7478
+ ref: stageRef,
7479
+ onPointerMove: editable ? handlePointerMove : void 0,
7480
+ onPointerUp: editable ? endInteraction : void 0,
7481
+ onPointerCancel: editable ? endInteraction : void 0,
7482
+ onClick: editable ? () => onElementSelect?.(null) : void 0,
7483
+ className: `relative h-full w-full overflow-hidden rounded-2xl ${editable ? "touch-none" : ""}`,
7407
7484
  style: {
7408
7485
  backgroundColor: page.background?.color || "#0f172a",
7409
7486
  backgroundImage: backgroundElement ? `url(${backgroundElement.src})` : page.background?.image ? `url(${page.background.image})` : void 0,
@@ -7412,17 +7489,249 @@ var FestivalCardPageRenderer = ({ page }) => {
7412
7489
  }
7413
7490
  },
7414
7491
  /* @__PURE__ */ React69__default.createElement("div", { className: "absolute inset-0 bg-slate-950/20" }),
7415
- foregroundElements.map(renderElement)
7492
+ foregroundElements.map((element) => {
7493
+ if (!editable) {
7494
+ return renderElement(element);
7495
+ }
7496
+ const isSelected = selectedElementId === element.id;
7497
+ const isDragging = draggingElementId === element.id;
7498
+ const isResizing = resizingElementId === element.id;
7499
+ return /* @__PURE__ */ React69__default.createElement(
7500
+ "div",
7501
+ {
7502
+ key: element.id,
7503
+ role: "button",
7504
+ tabIndex: 0,
7505
+ onClick: (event) => {
7506
+ event.stopPropagation();
7507
+ onElementSelect?.(element.id);
7508
+ },
7509
+ onPointerDown: (event) => beginInteraction(event, element.id, "move"),
7510
+ className: `absolute select-none touch-none rounded-md ${isDragging ? "cursor-grabbing" : isResizing ? "cursor-se-resize" : "cursor-grab"} ${isSelected ? "ring-2 ring-sky-300" : "ring-1 ring-white/40"}`,
7511
+ style: {
7512
+ ...elementStyle(element),
7513
+ zIndex: isSelected ? 4 : 2
7514
+ }
7515
+ },
7516
+ element.type === "text" ? /* @__PURE__ */ React69__default.createElement(
7517
+ "div",
7518
+ {
7519
+ className: "rounded-md bg-black/20 px-2 py-1",
7520
+ style: {
7521
+ color: element.color || "#f8fafc",
7522
+ fontSize: element.fontSize || 18,
7523
+ fontWeight: element.fontWeight || 500,
7524
+ fontFamily: element.fontFamily || "inherit",
7525
+ textAlign: element.align || "left",
7526
+ lineHeight: 1.45,
7527
+ whiteSpace: "pre-wrap"
7528
+ }
7529
+ },
7530
+ element.content
7531
+ ) : /* @__PURE__ */ React69__default.createElement(
7532
+ "img",
7533
+ {
7534
+ src: element.src,
7535
+ alt: element.alt || "festival-card-image",
7536
+ draggable: false,
7537
+ className: "pointer-events-none h-full w-full",
7538
+ style: {
7539
+ objectFit: element.fit || "cover",
7540
+ borderRadius: element.borderRadius || 0,
7541
+ overflow: "hidden",
7542
+ boxShadow: "0 12px 30px rgba(2, 6, 23, 0.32)"
7543
+ }
7544
+ }
7545
+ ),
7546
+ /* @__PURE__ */ React69__default.createElement(
7547
+ "button",
7548
+ {
7549
+ type: "button",
7550
+ "aria-label": "resize",
7551
+ onPointerDown: (event) => beginInteraction(event, element.id, "resize"),
7552
+ className: "absolute -bottom-2 -right-2 h-4 w-4 rounded-full border border-white bg-sky-500 shadow"
7553
+ }
7554
+ )
7555
+ );
7556
+ })
7416
7557
  );
7417
7558
  };
7418
7559
 
7419
7560
  // src/festivalCard/components/FestivalCardBook3D.tsx
7420
- var FestivalCardBook3D = ({ config, className }) => {
7421
- const [currentPage, setCurrentPage] = useState(0);
7561
+ var loadImage2 = (src) => new Promise((resolve, reject) => {
7562
+ const image = new window.Image();
7563
+ image.crossOrigin = "anonymous";
7564
+ image.decoding = "async";
7565
+ image.onload = () => resolve(image);
7566
+ image.onerror = () => reject(new Error(`\u56FE\u7247\u52A0\u8F7D\u5931\u8D25: ${src}`));
7567
+ image.src = src;
7568
+ });
7569
+ var drawImageWithFit = (ctx, image, left, top, width, height, fit) => {
7570
+ const imageRatio = image.width / image.height;
7571
+ const boxRatio = width / height;
7572
+ let drawWidth = width;
7573
+ let drawHeight = height;
7574
+ let offsetX = left;
7575
+ let offsetY = top;
7576
+ if (fit === "cover") {
7577
+ if (imageRatio > boxRatio) {
7578
+ drawHeight = height;
7579
+ drawWidth = height * imageRatio;
7580
+ offsetX = left - (drawWidth - width) / 2;
7581
+ } else {
7582
+ drawWidth = width;
7583
+ drawHeight = width / imageRatio;
7584
+ offsetY = top - (drawHeight - height) / 2;
7585
+ }
7586
+ } else if (imageRatio > boxRatio) {
7587
+ drawWidth = width;
7588
+ drawHeight = width / imageRatio;
7589
+ offsetY = top + (height - drawHeight) / 2;
7590
+ } else {
7591
+ drawHeight = height;
7592
+ drawWidth = height * imageRatio;
7593
+ offsetX = left + (width - drawWidth) / 2;
7594
+ }
7595
+ ctx.drawImage(image, offsetX, offsetY, drawWidth, drawHeight);
7596
+ };
7597
+ var withRoundedClip = (ctx, left, top, width, height, radius, draw) => {
7598
+ const safeRadius = Math.max(0, Math.min(radius, Math.min(width, height) / 2));
7599
+ if (safeRadius <= 0) {
7600
+ draw();
7601
+ return;
7602
+ }
7603
+ ctx.save();
7604
+ ctx.beginPath();
7605
+ ctx.moveTo(left + safeRadius, top);
7606
+ ctx.lineTo(left + width - safeRadius, top);
7607
+ ctx.quadraticCurveTo(left + width, top, left + width, top + safeRadius);
7608
+ ctx.lineTo(left + width, top + height - safeRadius);
7609
+ ctx.quadraticCurveTo(left + width, top + height, left + width - safeRadius, top + height);
7610
+ ctx.lineTo(left + safeRadius, top + height);
7611
+ ctx.quadraticCurveTo(left, top + height, left, top + height - safeRadius);
7612
+ ctx.lineTo(left, top + safeRadius);
7613
+ ctx.quadraticCurveTo(left, top, left + safeRadius, top);
7614
+ ctx.closePath();
7615
+ ctx.clip();
7616
+ draw();
7617
+ ctx.restore();
7618
+ };
7619
+ var drawMultilineText = (ctx, text5, left, top, maxWidth, lineHeight) => {
7620
+ const paragraphs = text5.split("\n");
7621
+ let currentY = top;
7622
+ paragraphs.forEach((paragraph, index) => {
7623
+ const words = paragraph.split("");
7624
+ let line = "";
7625
+ for (const word of words) {
7626
+ const testLine = line + word;
7627
+ if (ctx.measureText(testLine).width > maxWidth && line) {
7628
+ ctx.fillText(line, left, currentY);
7629
+ line = word;
7630
+ currentY += lineHeight;
7631
+ } else {
7632
+ line = testLine;
7633
+ }
7634
+ }
7635
+ ctx.fillText(line, left, currentY);
7636
+ currentY += lineHeight;
7637
+ if (index < paragraphs.length - 1) {
7638
+ currentY += lineHeight * 0.2;
7639
+ }
7640
+ });
7641
+ };
7642
+ var exportPageToPng = async (page, fileName) => {
7643
+ const width = 1080;
7644
+ const height = 1440;
7645
+ const canvas = document.createElement("canvas");
7646
+ canvas.width = width;
7647
+ canvas.height = height;
7648
+ const ctx = canvas.getContext("2d");
7649
+ if (!ctx) throw new Error("\u65E0\u6CD5\u521B\u5EFA Canvas \u4E0A\u4E0B\u6587");
7650
+ ctx.fillStyle = page.background?.color || "#0f172a";
7651
+ ctx.fillRect(0, 0, width, height);
7652
+ const backgroundElement = page.elements.find(
7653
+ (element) => element.type === "image" && Boolean(element.isBackground)
7654
+ );
7655
+ const backgroundImageSrc = backgroundElement?.src || page.background?.image;
7656
+ if (backgroundImageSrc) {
7657
+ const image = await loadImage2(backgroundImageSrc);
7658
+ drawImageWithFit(ctx, image, 0, 0, width, height, "cover");
7659
+ }
7660
+ const foregroundElements = page.elements.filter((element) => !(element.type === "image" && element.isBackground));
7661
+ for (const element of foregroundElements) {
7662
+ const elementWidth = width * (element.width ?? 70) / 100;
7663
+ const elementHeight = element.height ? height * element.height / 100 : void 0;
7664
+ const centerX = width * element.x / 100;
7665
+ const centerY = height * element.y / 100;
7666
+ const left = centerX - elementWidth / 2;
7667
+ if (element.type === "image") {
7668
+ const image = await loadImage2(element.src);
7669
+ const drawHeight = elementHeight ?? elementWidth;
7670
+ const boxTop = centerY - drawHeight / 2;
7671
+ withRoundedClip(ctx, left, boxTop, elementWidth, drawHeight, element.borderRadius ?? 0, () => {
7672
+ drawImageWithFit(ctx, image, left, boxTop, elementWidth, drawHeight, element.fit || "cover");
7673
+ });
7674
+ continue;
7675
+ }
7676
+ const fontSize = (element.fontSize || 18) * 1.5;
7677
+ ctx.fillStyle = element.color || "#f8fafc";
7678
+ ctx.font = `${element.fontWeight || 500} ${fontSize}px ${element.fontFamily || "sans-serif"}`;
7679
+ ctx.textBaseline = "top";
7680
+ ctx.textAlign = element.align || "left";
7681
+ const textX = element.align === "center" ? centerX : element.align === "right" ? left + elementWidth : left;
7682
+ drawMultilineText(ctx, element.content || "", textX, centerY - fontSize * 0.72, elementWidth, fontSize * 1.45);
7683
+ }
7684
+ const blob = await new Promise((resolve) => canvas.toBlob(resolve, "image/png"));
7685
+ if (!blob) throw new Error("\u5BFC\u51FA\u5931\u8D25\uFF0C\u8BF7\u91CD\u8BD5");
7686
+ const url = URL.createObjectURL(blob);
7687
+ const anchor = document.createElement("a");
7688
+ anchor.href = url;
7689
+ anchor.download = fileName;
7690
+ anchor.click();
7691
+ URL.revokeObjectURL(url);
7692
+ };
7693
+ var FestivalCardBook3D = ({
7694
+ config,
7695
+ className,
7696
+ editable = false,
7697
+ enableExportImage = !editable,
7698
+ currentPage: currentPageProp,
7699
+ onCurrentPageChange,
7700
+ selectedElementId = null,
7701
+ onSelectedElementChange,
7702
+ onElementChange
7703
+ }) => {
7704
+ const [internalCurrentPage, setInternalCurrentPage] = useState(0);
7705
+ const [exporting, setExporting] = useState(false);
7422
7706
  const normalized = useMemo(() => normalizeFestivalCardConfig(config), [config]);
7423
7707
  const pages = normalized.pages;
7708
+ const currentPage = typeof currentPageProp === "number" ? currentPageProp : internalCurrentPage;
7709
+ const setCurrentPage = (updater) => {
7710
+ const prev = currentPage;
7711
+ const nextValue = typeof updater === "function" ? updater(prev) : updater;
7712
+ if (typeof currentPageProp === "number") {
7713
+ onCurrentPageChange?.(nextValue);
7714
+ return;
7715
+ }
7716
+ setInternalCurrentPage(nextValue);
7717
+ onCurrentPageChange?.(nextValue);
7718
+ };
7424
7719
  const canPrev = currentPage > 0;
7425
7720
  const canNext = currentPage < pages.length - 1;
7721
+ const currentPageData = pages[currentPage];
7722
+ const handleExportCurrentPage = async () => {
7723
+ if (!currentPageData || exporting) return;
7724
+ setExporting(true);
7725
+ try {
7726
+ const base = normalized.id || "festival-card";
7727
+ const fileName = `${base}-page-${currentPage + 1}.png`;
7728
+ await exportPageToPng(currentPageData, fileName);
7729
+ } catch (error) {
7730
+ window.alert(error.message || "\u5BFC\u51FA\u56FE\u7247\u5931\u8D25");
7731
+ } finally {
7732
+ setExporting(false);
7733
+ }
7734
+ };
7426
7735
  return /* @__PURE__ */ React69__default.createElement("div", { className }, /* @__PURE__ */ React69__default.createElement("div", { className: "w-full min-h-screen px-0 py-4" }, /* @__PURE__ */ React69__default.createElement("div", { className: "mx-auto w-full text-center text-slate-100" }, /* @__PURE__ */ React69__default.createElement("h3", { className: "mb-3 text-lg font-semibold" }, normalized.coverTitle || "Festival Card")), /* @__PURE__ */ React69__default.createElement("div", { className: "mx-auto w-full" }, /* @__PURE__ */ React69__default.createElement("div", { className: "relative h-[calc(100vh-170px)] min-h-[460px]" }, pages.map((page, index) => /* @__PURE__ */ React69__default.createElement(
7427
7736
  "div",
7428
7737
  {
@@ -7433,7 +7742,16 @@ var FestivalCardBook3D = ({ config, className }) => {
7433
7742
  pointerEvents: index === currentPage ? "auto" : "none"
7434
7743
  }
7435
7744
  },
7436
- /* @__PURE__ */ React69__default.createElement(FestivalCardPageRenderer, { page })
7745
+ /* @__PURE__ */ React69__default.createElement(
7746
+ FestivalCardPageRenderer,
7747
+ {
7748
+ page,
7749
+ editable: editable && index === currentPage,
7750
+ selectedElementId,
7751
+ onElementSelect: onSelectedElementChange,
7752
+ onElementChange: (elementId, patch) => onElementChange?.(index, elementId, patch)
7753
+ }
7754
+ )
7437
7755
  )))), /* @__PURE__ */ React69__default.createElement("div", { className: "mt-4 flex justify-center gap-3" }, /* @__PURE__ */ React69__default.createElement(
7438
7756
  "button",
7439
7757
  {
@@ -7461,6 +7779,24 @@ var FestivalCardBook3D = ({ config, className }) => {
7461
7779
  controls: true,
7462
7780
  className: "mt-3 w-full"
7463
7781
  }
7782
+ ) : null, enableExportImage ? /* @__PURE__ */ React69__default.createElement(
7783
+ FloatingMenu_default,
7784
+ {
7785
+ initialPosition: { x: 24, y: 120 },
7786
+ trigger: /* @__PURE__ */ React69__default.createElement("div", { className: "text-lg leading-none text-slate-700", "aria-hidden": true }, "\u2301"),
7787
+ menu: /* @__PURE__ */ React69__default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React69__default.createElement("div", { className: "text-xs font-semibold tracking-wide text-slate-500" }, "\u8D3A\u5361\u5DE5\u5177"), /* @__PURE__ */ React69__default.createElement(
7788
+ "button",
7789
+ {
7790
+ type: "button",
7791
+ onClick: () => void handleExportCurrentPage(),
7792
+ disabled: exporting,
7793
+ className: "rounded-lg bg-sky-600 px-3 py-2 text-left text-sm font-medium text-white disabled:opacity-60"
7794
+ },
7795
+ exporting ? "\u5BFC\u51FA\u4E2D..." : `\u5BFC\u51FA\u7B2C ${currentPage + 1} \u9875 PNG`
7796
+ )),
7797
+ triggerClassName: "bg-white/95 backdrop-blur",
7798
+ menuClassName: "bg-white/95 backdrop-blur"
7799
+ }
7464
7800
  ) : null);
7465
7801
  };
7466
7802
  var createTextElement = (pageIndex) => ({
@@ -7487,8 +7823,22 @@ var createImageElement = (pageIndex) => ({
7487
7823
  fit: "cover",
7488
7824
  borderRadius: 12
7489
7825
  });
7490
- var FestivalCardConfigEditor = ({ value, onChange }) => {
7491
- const [activePageIndex, setActivePageIndex] = useState(0);
7826
+ var FestivalCardConfigEditor = ({
7827
+ value,
7828
+ onChange,
7829
+ activePageIndex: activePageIndexProp,
7830
+ onActivePageIndexChange,
7831
+ selectedElementId
7832
+ }) => {
7833
+ const [internalActivePageIndex, setInternalActivePageIndex] = useState(0);
7834
+ const activePageIndex = activePageIndexProp ?? internalActivePageIndex;
7835
+ const setActivePageIndex = (index) => {
7836
+ if (typeof activePageIndexProp === "number") {
7837
+ onActivePageIndexChange?.(index);
7838
+ return;
7839
+ }
7840
+ setInternalActivePageIndex(index);
7841
+ };
7492
7842
  const page = value.pages[activePageIndex];
7493
7843
  const canEditPage = Boolean(page);
7494
7844
  const pageOptions = useMemo(() => value.pages.map((_, index) => index), [value.pages]);
@@ -7621,166 +7971,174 @@ var FestivalCardConfigEditor = ({ value, onChange }) => {
7621
7971
  className: "rounded-lg bg-sky-600 px-3 py-2 text-sm font-medium text-white"
7622
7972
  },
7623
7973
  "+ \u56FE\u7247"
7624
- )), /* @__PURE__ */ React69__default.createElement("div", { className: "grid max-h-[340px] gap-2.5 overflow-auto pr-1" }, (page?.elements ?? []).map((element) => /* @__PURE__ */ React69__default.createElement("div", { key: element.id, className: "rounded-xl border border-slate-200 bg-slate-50 p-3" }, /* @__PURE__ */ React69__default.createElement("div", { className: "mb-2 flex items-center justify-between" }, /* @__PURE__ */ React69__default.createElement("div", { className: "text-xs font-semibold tracking-wide text-slate-500" }, element.type.toUpperCase()), /* @__PURE__ */ React69__default.createElement(
7625
- "button",
7626
- {
7627
- type: "button",
7628
- onClick: () => removeElement(element.id),
7629
- className: "rounded-md border border-rose-300 bg-rose-50 px-2 py-1 text-xs font-medium text-rose-700"
7630
- },
7631
- "\u5220\u9664"
7632
- )), element.type === "text" ? /* @__PURE__ */ React69__default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React69__default.createElement(
7633
- "textarea",
7634
- {
7635
- value: element.content,
7636
- onChange: (event) => updateElement(element.id, { content: event.target.value }),
7637
- rows: 3,
7638
- className: "w-full rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm text-slate-900 outline-none focus:border-sky-400 focus:ring-2 focus:ring-sky-100"
7639
- }
7640
- ), /* @__PURE__ */ React69__default.createElement("div", { className: "grid gap-2 sm:grid-cols-2" }, /* @__PURE__ */ React69__default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "X(%)", /* @__PURE__ */ React69__default.createElement(
7641
- "input",
7642
- {
7643
- type: "number",
7644
- value: element.x,
7645
- onChange: (event) => updateElement(element.id, { x: Number(event.target.value) }),
7646
- className: numberFieldClassName
7647
- }
7648
- )), /* @__PURE__ */ React69__default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "Y(%)", /* @__PURE__ */ React69__default.createElement(
7649
- "input",
7650
- {
7651
- type: "number",
7652
- value: element.y,
7653
- onChange: (event) => updateElement(element.id, { y: Number(event.target.value) }),
7654
- className: numberFieldClassName
7655
- }
7656
- )), /* @__PURE__ */ React69__default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "\u5BBD\u5EA6(%)", /* @__PURE__ */ React69__default.createElement(
7657
- "input",
7658
- {
7659
- type: "number",
7660
- value: element.width ?? 70,
7661
- onChange: (event) => updateElement(element.id, { width: Number(event.target.value) }),
7662
- className: numberFieldClassName
7663
- }
7664
- )), /* @__PURE__ */ React69__default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "\u5B57\u53F7(px)", /* @__PURE__ */ React69__default.createElement(
7665
- "input",
7666
- {
7667
- type: "number",
7668
- value: element.fontSize ?? 18,
7669
- onChange: (event) => updateElement(element.id, { fontSize: Number(event.target.value) }),
7670
- className: numberFieldClassName
7671
- }
7672
- )), /* @__PURE__ */ React69__default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "\u5B57\u91CD", /* @__PURE__ */ React69__default.createElement(
7673
- "input",
7674
- {
7675
- type: "number",
7676
- min: 100,
7677
- max: 900,
7678
- step: 100,
7679
- value: element.fontWeight ?? 500,
7680
- onChange: (event) => updateElement(element.id, { fontWeight: Number(event.target.value) }),
7681
- className: numberFieldClassName
7682
- }
7683
- )), /* @__PURE__ */ React69__default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "\u5BF9\u9F50", /* @__PURE__ */ React69__default.createElement(
7684
- "select",
7685
- {
7686
- value: element.align || "left",
7687
- onChange: (event) => updateElement(element.id, { align: event.target.value }),
7688
- className: numberFieldClassName
7689
- },
7690
- /* @__PURE__ */ React69__default.createElement("option", { value: "left" }, "left"),
7691
- /* @__PURE__ */ React69__default.createElement("option", { value: "center" }, "center"),
7692
- /* @__PURE__ */ React69__default.createElement("option", { value: "right" }, "right")
7693
- )), /* @__PURE__ */ React69__default.createElement("label", { className: "grid gap-1 text-xs text-slate-600 sm:col-span-2" }, "\u5B57\u4F53", /* @__PURE__ */ React69__default.createElement(
7694
- "input",
7695
- {
7696
- type: "text",
7697
- value: element.fontFamily || "",
7698
- onChange: (event) => updateElement(element.id, { fontFamily: event.target.value }),
7699
- placeholder: "inherit / serif / sans-serif / PingFang SC",
7700
- className: numberFieldClassName
7701
- }
7702
- )), /* @__PURE__ */ React69__default.createElement("label", { className: "grid gap-1 text-xs text-slate-600 sm:col-span-2" }, "\u6587\u5B57\u989C\u8272", /* @__PURE__ */ React69__default.createElement("div", { className: "grid grid-cols-[64px_1fr] gap-2" }, /* @__PURE__ */ React69__default.createElement(
7703
- "input",
7704
- {
7705
- type: "color",
7706
- value: element.color || "#ffffff",
7707
- onChange: (event) => updateElement(element.id, { color: event.target.value }),
7708
- className: "h-10 rounded-lg border border-slate-300 bg-white p-1"
7709
- }
7710
- ), /* @__PURE__ */ React69__default.createElement(
7711
- "input",
7712
- {
7713
- type: "text",
7714
- value: element.color || "#ffffff",
7715
- onChange: (event) => updateElement(element.id, { color: event.target.value }),
7716
- className: numberFieldClassName
7717
- }
7718
- ))))) : /* @__PURE__ */ React69__default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React69__default.createElement(
7719
- "input",
7720
- {
7721
- type: "url",
7722
- value: element.src,
7723
- onChange: (event) => updateElement(element.id, { src: event.target.value }),
7724
- className: "w-full rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm text-slate-900 outline-none focus:border-sky-400 focus:ring-2 focus:ring-sky-100"
7725
- }
7726
- ), /* @__PURE__ */ React69__default.createElement("div", { className: "grid gap-2 sm:grid-cols-2" }, /* @__PURE__ */ React69__default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "X(%)", /* @__PURE__ */ React69__default.createElement(
7727
- "input",
7728
- {
7729
- type: "number",
7730
- value: element.x,
7731
- onChange: (event) => updateElement(element.id, { x: Number(event.target.value) }),
7732
- className: numberFieldClassName
7733
- }
7734
- )), /* @__PURE__ */ React69__default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "Y(%)", /* @__PURE__ */ React69__default.createElement(
7735
- "input",
7736
- {
7737
- type: "number",
7738
- value: element.y,
7739
- onChange: (event) => updateElement(element.id, { y: Number(event.target.value) }),
7740
- className: numberFieldClassName
7741
- }
7742
- )), /* @__PURE__ */ React69__default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "\u5BBD\u5EA6(%)", /* @__PURE__ */ React69__default.createElement(
7743
- "input",
7744
- {
7745
- type: "number",
7746
- value: element.width ?? 60,
7747
- onChange: (event) => updateElement(element.id, { width: Number(event.target.value) }),
7748
- className: numberFieldClassName
7749
- }
7750
- )), /* @__PURE__ */ React69__default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "\u9AD8\u5EA6(%)", /* @__PURE__ */ React69__default.createElement(
7751
- "input",
7752
- {
7753
- type: "number",
7754
- value: element.height ?? 40,
7755
- onChange: (event) => updateElement(element.id, { height: Number(event.target.value) }),
7756
- className: numberFieldClassName
7757
- }
7758
- )), /* @__PURE__ */ React69__default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "\u5706\u89D2(px)", /* @__PURE__ */ React69__default.createElement(
7759
- "input",
7760
- {
7761
- type: "number",
7762
- value: element.borderRadius ?? 0,
7763
- onChange: (event) => updateElement(element.id, { borderRadius: Number(event.target.value) }),
7764
- className: numberFieldClassName
7765
- }
7766
- )), /* @__PURE__ */ React69__default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "\u586B\u5145", /* @__PURE__ */ React69__default.createElement(
7767
- "select",
7974
+ )), /* @__PURE__ */ React69__default.createElement("div", { className: "grid max-h-[340px] gap-2.5 overflow-auto pr-1" }, (page?.elements ?? []).map((element) => /* @__PURE__ */ React69__default.createElement(
7975
+ "div",
7768
7976
  {
7769
- value: element.fit || "cover",
7770
- onChange: (event) => updateElement(element.id, { fit: event.target.value }),
7771
- className: numberFieldClassName
7977
+ key: element.id,
7978
+ className: `rounded-xl border bg-slate-50 p-3 ${selectedElementId === element.id ? "border-sky-400 ring-2 ring-sky-100" : "border-slate-200"}`
7772
7979
  },
7773
- /* @__PURE__ */ React69__default.createElement("option", { value: "cover" }, "cover"),
7774
- /* @__PURE__ */ React69__default.createElement("option", { value: "contain" }, "contain")
7775
- ))), /* @__PURE__ */ React69__default.createElement("label", { className: "inline-flex items-center gap-2 text-sm text-slate-700" }, /* @__PURE__ */ React69__default.createElement(
7776
- "input",
7777
- {
7778
- type: "checkbox",
7779
- checked: Boolean(element.isBackground),
7780
- onChange: (event) => updateElement(element.id, { isBackground: event.target.checked }),
7781
- className: "h-4 w-4 rounded border-slate-300 text-sky-600"
7782
- }
7783
- ), "\u4F5C\u4E3A\u672C\u9875\u80CC\u666F\u56FE")))))) : null);
7980
+ /* @__PURE__ */ React69__default.createElement("div", { className: "mb-2 flex items-center justify-between" }, /* @__PURE__ */ React69__default.createElement("div", { className: "text-xs font-semibold tracking-wide text-slate-500" }, element.type.toUpperCase()), /* @__PURE__ */ React69__default.createElement(
7981
+ "button",
7982
+ {
7983
+ type: "button",
7984
+ onClick: () => removeElement(element.id),
7985
+ className: "rounded-md border border-rose-300 bg-rose-50 px-2 py-1 text-xs font-medium text-rose-700"
7986
+ },
7987
+ "\u5220\u9664"
7988
+ )),
7989
+ element.type === "text" ? /* @__PURE__ */ React69__default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React69__default.createElement(
7990
+ "textarea",
7991
+ {
7992
+ value: element.content,
7993
+ onChange: (event) => updateElement(element.id, { content: event.target.value }),
7994
+ rows: 3,
7995
+ className: "w-full rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm text-slate-900 outline-none focus:border-sky-400 focus:ring-2 focus:ring-sky-100"
7996
+ }
7997
+ ), /* @__PURE__ */ React69__default.createElement("div", { className: "grid gap-2 sm:grid-cols-2" }, /* @__PURE__ */ React69__default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "X(%)", /* @__PURE__ */ React69__default.createElement(
7998
+ "input",
7999
+ {
8000
+ type: "number",
8001
+ value: element.x,
8002
+ onChange: (event) => updateElement(element.id, { x: Number(event.target.value) }),
8003
+ className: numberFieldClassName
8004
+ }
8005
+ )), /* @__PURE__ */ React69__default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "Y(%)", /* @__PURE__ */ React69__default.createElement(
8006
+ "input",
8007
+ {
8008
+ type: "number",
8009
+ value: element.y,
8010
+ onChange: (event) => updateElement(element.id, { y: Number(event.target.value) }),
8011
+ className: numberFieldClassName
8012
+ }
8013
+ )), /* @__PURE__ */ React69__default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "\u5BBD\u5EA6(%)", /* @__PURE__ */ React69__default.createElement(
8014
+ "input",
8015
+ {
8016
+ type: "number",
8017
+ value: element.width ?? 70,
8018
+ onChange: (event) => updateElement(element.id, { width: Number(event.target.value) }),
8019
+ className: numberFieldClassName
8020
+ }
8021
+ )), /* @__PURE__ */ React69__default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "\u5B57\u53F7(px)", /* @__PURE__ */ React69__default.createElement(
8022
+ "input",
8023
+ {
8024
+ type: "number",
8025
+ value: element.fontSize ?? 18,
8026
+ onChange: (event) => updateElement(element.id, { fontSize: Number(event.target.value) }),
8027
+ className: numberFieldClassName
8028
+ }
8029
+ )), /* @__PURE__ */ React69__default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "\u5B57\u91CD", /* @__PURE__ */ React69__default.createElement(
8030
+ "input",
8031
+ {
8032
+ type: "number",
8033
+ min: 100,
8034
+ max: 900,
8035
+ step: 100,
8036
+ value: element.fontWeight ?? 500,
8037
+ onChange: (event) => updateElement(element.id, { fontWeight: Number(event.target.value) }),
8038
+ className: numberFieldClassName
8039
+ }
8040
+ )), /* @__PURE__ */ React69__default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "\u5BF9\u9F50", /* @__PURE__ */ React69__default.createElement(
8041
+ "select",
8042
+ {
8043
+ value: element.align || "left",
8044
+ onChange: (event) => updateElement(element.id, { align: event.target.value }),
8045
+ className: numberFieldClassName
8046
+ },
8047
+ /* @__PURE__ */ React69__default.createElement("option", { value: "left" }, "left"),
8048
+ /* @__PURE__ */ React69__default.createElement("option", { value: "center" }, "center"),
8049
+ /* @__PURE__ */ React69__default.createElement("option", { value: "right" }, "right")
8050
+ )), /* @__PURE__ */ React69__default.createElement("label", { className: "grid gap-1 text-xs text-slate-600 sm:col-span-2" }, "\u5B57\u4F53", /* @__PURE__ */ React69__default.createElement(
8051
+ "input",
8052
+ {
8053
+ type: "text",
8054
+ value: element.fontFamily || "",
8055
+ onChange: (event) => updateElement(element.id, { fontFamily: event.target.value }),
8056
+ placeholder: "inherit / serif / sans-serif / PingFang SC",
8057
+ className: numberFieldClassName
8058
+ }
8059
+ )), /* @__PURE__ */ React69__default.createElement("label", { className: "grid gap-1 text-xs text-slate-600 sm:col-span-2" }, "\u6587\u5B57\u989C\u8272", /* @__PURE__ */ React69__default.createElement("div", { className: "grid grid-cols-[64px_1fr] gap-2" }, /* @__PURE__ */ React69__default.createElement(
8060
+ "input",
8061
+ {
8062
+ type: "color",
8063
+ value: element.color || "#ffffff",
8064
+ onChange: (event) => updateElement(element.id, { color: event.target.value }),
8065
+ className: "h-10 rounded-lg border border-slate-300 bg-white p-1"
8066
+ }
8067
+ ), /* @__PURE__ */ React69__default.createElement(
8068
+ "input",
8069
+ {
8070
+ type: "text",
8071
+ value: element.color || "#ffffff",
8072
+ onChange: (event) => updateElement(element.id, { color: event.target.value }),
8073
+ className: numberFieldClassName
8074
+ }
8075
+ ))))) : /* @__PURE__ */ React69__default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React69__default.createElement(
8076
+ "input",
8077
+ {
8078
+ type: "url",
8079
+ value: element.src,
8080
+ onChange: (event) => updateElement(element.id, { src: event.target.value }),
8081
+ className: "w-full rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm text-slate-900 outline-none focus:border-sky-400 focus:ring-2 focus:ring-sky-100"
8082
+ }
8083
+ ), /* @__PURE__ */ React69__default.createElement("div", { className: "grid gap-2 sm:grid-cols-2" }, /* @__PURE__ */ React69__default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "X(%)", /* @__PURE__ */ React69__default.createElement(
8084
+ "input",
8085
+ {
8086
+ type: "number",
8087
+ value: element.x,
8088
+ onChange: (event) => updateElement(element.id, { x: Number(event.target.value) }),
8089
+ className: numberFieldClassName
8090
+ }
8091
+ )), /* @__PURE__ */ React69__default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "Y(%)", /* @__PURE__ */ React69__default.createElement(
8092
+ "input",
8093
+ {
8094
+ type: "number",
8095
+ value: element.y,
8096
+ onChange: (event) => updateElement(element.id, { y: Number(event.target.value) }),
8097
+ className: numberFieldClassName
8098
+ }
8099
+ )), /* @__PURE__ */ React69__default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "\u5BBD\u5EA6(%)", /* @__PURE__ */ React69__default.createElement(
8100
+ "input",
8101
+ {
8102
+ type: "number",
8103
+ value: element.width ?? 60,
8104
+ onChange: (event) => updateElement(element.id, { width: Number(event.target.value) }),
8105
+ className: numberFieldClassName
8106
+ }
8107
+ )), /* @__PURE__ */ React69__default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "\u9AD8\u5EA6(%)", /* @__PURE__ */ React69__default.createElement(
8108
+ "input",
8109
+ {
8110
+ type: "number",
8111
+ value: element.height ?? 40,
8112
+ onChange: (event) => updateElement(element.id, { height: Number(event.target.value) }),
8113
+ className: numberFieldClassName
8114
+ }
8115
+ )), /* @__PURE__ */ React69__default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "\u5706\u89D2(px)", /* @__PURE__ */ React69__default.createElement(
8116
+ "input",
8117
+ {
8118
+ type: "number",
8119
+ value: element.borderRadius ?? 0,
8120
+ onChange: (event) => updateElement(element.id, { borderRadius: Number(event.target.value) }),
8121
+ className: numberFieldClassName
8122
+ }
8123
+ )), /* @__PURE__ */ React69__default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "\u586B\u5145", /* @__PURE__ */ React69__default.createElement(
8124
+ "select",
8125
+ {
8126
+ value: element.fit || "cover",
8127
+ onChange: (event) => updateElement(element.id, { fit: event.target.value }),
8128
+ className: numberFieldClassName
8129
+ },
8130
+ /* @__PURE__ */ React69__default.createElement("option", { value: "cover" }, "cover"),
8131
+ /* @__PURE__ */ React69__default.createElement("option", { value: "contain" }, "contain")
8132
+ ))), /* @__PURE__ */ React69__default.createElement("label", { className: "inline-flex items-center gap-2 text-sm text-slate-700" }, /* @__PURE__ */ React69__default.createElement(
8133
+ "input",
8134
+ {
8135
+ type: "checkbox",
8136
+ checked: Boolean(element.isBackground),
8137
+ onChange: (event) => updateElement(element.id, { isBackground: event.target.checked }),
8138
+ className: "h-4 w-4 rounded border-slate-300 text-sky-600"
8139
+ }
8140
+ ), "\u4F5C\u4E3A\u672C\u9875\u80CC\u666F\u56FE"))
8141
+ )))) : null);
7784
8142
  };
7785
8143
 
7786
8144
  // src/festivalCard/components/FestivalCardStudio.tsx
@@ -7790,8 +8148,55 @@ var FestivalCardStudio = ({ initialConfig, fetchConfig, onSave }) => {
7790
8148
  fetchConfig,
7791
8149
  onSave
7792
8150
  });
8151
+ const [activePageIndex, setActivePageIndex] = useState(0);
8152
+ const [selectedElementId, setSelectedElementId] = useState(null);
8153
+ useEffect(() => {
8154
+ if (config.pages.length === 0) return;
8155
+ if (activePageIndex <= config.pages.length - 1) return;
8156
+ setActivePageIndex(config.pages.length - 1);
8157
+ }, [activePageIndex, config.pages.length]);
8158
+ const updateElementByPreview = (pageIndex, elementId, patch) => {
8159
+ setConfig((prev) => ({
8160
+ ...prev,
8161
+ pages: prev.pages.map(
8162
+ (page, index) => index === pageIndex ? {
8163
+ ...page,
8164
+ elements: page.elements.map(
8165
+ (element) => element.id === elementId ? { ...element, ...patch } : element
8166
+ )
8167
+ } : page
8168
+ )
8169
+ }));
8170
+ };
7793
8171
  if (loading) return /* @__PURE__ */ React69__default.createElement("div", null, "\u52A0\u8F7D\u4E2D...");
7794
- return /* @__PURE__ */ React69__default.createElement("div", { className: "grid items-start gap-4 lg:grid-cols-[1.45fr_1fr]" }, /* @__PURE__ */ React69__default.createElement(FestivalCardBook3D, { config, className: "h-full" }), /* @__PURE__ */ React69__default.createElement("div", { className: "lg:sticky lg:top-4" }, /* @__PURE__ */ React69__default.createElement(FestivalCardConfigEditor, { value: config, onChange: setConfig }), onSave ? /* @__PURE__ */ React69__default.createElement(
8172
+ return /* @__PURE__ */ React69__default.createElement("div", { className: "grid items-start gap-4 lg:grid-cols-[1.45fr_1fr]" }, /* @__PURE__ */ React69__default.createElement(
8173
+ FestivalCardBook3D,
8174
+ {
8175
+ config,
8176
+ className: "h-full",
8177
+ editable: true,
8178
+ currentPage: activePageIndex,
8179
+ onCurrentPageChange: (index) => {
8180
+ setActivePageIndex(index);
8181
+ setSelectedElementId(null);
8182
+ },
8183
+ selectedElementId,
8184
+ onSelectedElementChange: setSelectedElementId,
8185
+ onElementChange: updateElementByPreview
8186
+ }
8187
+ ), /* @__PURE__ */ React69__default.createElement("div", { className: "lg:sticky lg:top-4" }, /* @__PURE__ */ React69__default.createElement(
8188
+ FestivalCardConfigEditor,
8189
+ {
8190
+ value: config,
8191
+ onChange: setConfig,
8192
+ activePageIndex,
8193
+ onActivePageIndexChange: (index) => {
8194
+ setActivePageIndex(index);
8195
+ setSelectedElementId(null);
8196
+ },
8197
+ selectedElementId
8198
+ }
8199
+ ), onSave ? /* @__PURE__ */ React69__default.createElement(
7795
8200
  "button",
7796
8201
  {
7797
8202
  type: "button",