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
@@ -1,10 +1,12 @@
1
1
  'use strict';
2
2
 
3
- var React3 = require('react');
3
+ var React4 = require('react');
4
+ var reactDom = require('react-dom');
5
+ var clsx = require('clsx');
4
6
 
5
7
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
6
8
 
7
- var React3__default = /*#__PURE__*/_interopDefault(React3);
9
+ var React4__default = /*#__PURE__*/_interopDefault(React4);
8
10
 
9
11
  // src/festivalCard/core/defaults.ts
10
12
  var DEFAULT_FESTIVAL_CARD_CONFIG = {
@@ -182,12 +184,12 @@ var resizeFestivalCardPages = (config, pageCount) => {
182
184
  };
183
185
  };
184
186
  var useFestivalCardConfig = (options) => {
185
- const [config, setConfig] = React3.useState(
187
+ const [config, setConfig] = React4.useState(
186
188
  () => normalizeFestivalCardConfig(options?.initialConfig || DEFAULT_FESTIVAL_CARD_CONFIG)
187
189
  );
188
- const [loading, setLoading] = React3.useState(Boolean(options?.fetchConfig));
189
- const [saving, setSaving] = React3.useState(false);
190
- React3.useEffect(() => {
190
+ const [loading, setLoading] = React4.useState(Boolean(options?.fetchConfig));
191
+ const [saving, setSaving] = React4.useState(false);
192
+ React4.useEffect(() => {
191
193
  const fetchConfig = options?.fetchConfig;
192
194
  if (!fetchConfig) return;
193
195
  let active = true;
@@ -201,7 +203,7 @@ var useFestivalCardConfig = (options) => {
201
203
  active = false;
202
204
  };
203
205
  }, [options?.fetchConfig]);
204
- const save = React3.useCallback(async () => {
206
+ const save = React4.useCallback(async () => {
205
207
  const onSave = options?.onSave;
206
208
  if (!onSave) return;
207
209
  setSaving(true);
@@ -211,7 +213,7 @@ var useFestivalCardConfig = (options) => {
211
213
  setSaving(false);
212
214
  }
213
215
  }, [config, options?.onSave]);
214
- return React3.useMemo(
216
+ return React4.useMemo(
215
217
  () => ({
216
218
  config,
217
219
  setConfig,
@@ -222,6 +224,190 @@ var useFestivalCardConfig = (options) => {
222
224
  [config, loading, save, saving]
223
225
  );
224
226
  };
227
+ var FloatingMenu = ({
228
+ trigger,
229
+ menu,
230
+ initialPosition = { x: 20, y: 20 },
231
+ defaultOpen = false,
232
+ className = "",
233
+ menuClassName = "",
234
+ triggerClassName = "",
235
+ zIndex = 1e3
236
+ }) => {
237
+ const [position, setPosition] = React4.useState(initialPosition);
238
+ const [isMenuOpen, setIsMenuOpen] = React4.useState(defaultOpen);
239
+ const [menuDirection, setMenuDirection] = React4.useState("right");
240
+ const [isDragging, setIsDragging] = React4.useState(false);
241
+ const [dragOffset, setDragOffset] = React4.useState({ x: 0, y: 0 });
242
+ const containerRef = React4.useRef(null);
243
+ const [mounted, setMounted] = React4.useState(false);
244
+ const [hasDragged, setHasDragged] = React4.useState(false);
245
+ const dragTimerRef = React4.useRef(null);
246
+ const mouseDownPosRef = React4.useRef(null);
247
+ React4.useEffect(() => {
248
+ setMounted(true);
249
+ return () => setMounted(false);
250
+ }, []);
251
+ React4.useEffect(() => {
252
+ if (!mounted || !containerRef.current) return;
253
+ const updateMenuDirection = () => {
254
+ const rect = containerRef.current?.getBoundingClientRect();
255
+ if (!rect) return;
256
+ const windowWidth = window.innerWidth;
257
+ const middlePoint = windowWidth / 2;
258
+ setMenuDirection(rect.left < middlePoint ? "right" : "left");
259
+ };
260
+ updateMenuDirection();
261
+ window.addEventListener("resize", updateMenuDirection);
262
+ window.addEventListener("scroll", updateMenuDirection);
263
+ return () => {
264
+ window.removeEventListener("resize", updateMenuDirection);
265
+ window.removeEventListener("scroll", updateMenuDirection);
266
+ };
267
+ }, [mounted]);
268
+ const handleMouseDown = (e) => {
269
+ if (!containerRef.current) return;
270
+ e.stopPropagation();
271
+ mouseDownPosRef.current = { x: e.clientX, y: e.clientY };
272
+ const rect = containerRef.current.getBoundingClientRect();
273
+ setDragOffset({
274
+ x: e.clientX - rect.left,
275
+ y: e.clientY - rect.top
276
+ });
277
+ setHasDragged(false);
278
+ setIsDragging(true);
279
+ };
280
+ React4.useEffect(() => {
281
+ if (!isDragging) return;
282
+ const handleMouseMove = (e) => {
283
+ if (mouseDownPosRef.current) {
284
+ const dx = Math.abs(e.clientX - mouseDownPosRef.current.x);
285
+ const dy = Math.abs(e.clientY - mouseDownPosRef.current.y);
286
+ if (dx > 3 || dy > 3) {
287
+ setHasDragged(true);
288
+ }
289
+ }
290
+ const newX = e.clientX - dragOffset.x;
291
+ const newY = e.clientY - dragOffset.y;
292
+ const windowWidth = window.innerWidth;
293
+ const windowHeight = window.innerHeight;
294
+ setPosition({
295
+ x: Math.min(Math.max(newX, 0), windowWidth - 50),
296
+ y: Math.min(Math.max(newY, 0), windowHeight - 50)
297
+ });
298
+ };
299
+ const handleMouseUp = () => {
300
+ setIsDragging(false);
301
+ mouseDownPosRef.current = null;
302
+ if (dragTimerRef.current) {
303
+ window.clearTimeout(dragTimerRef.current);
304
+ }
305
+ dragTimerRef.current = window.setTimeout(() => {
306
+ setHasDragged(false);
307
+ }, 300);
308
+ };
309
+ document.addEventListener("mousemove", handleMouseMove);
310
+ document.addEventListener("mouseup", handleMouseUp);
311
+ return () => {
312
+ document.removeEventListener("mousemove", handleMouseMove);
313
+ document.removeEventListener("mouseup", handleMouseUp);
314
+ };
315
+ }, [isDragging, dragOffset]);
316
+ React4.useEffect(() => {
317
+ return () => {
318
+ if (dragTimerRef.current) {
319
+ window.clearTimeout(dragTimerRef.current);
320
+ }
321
+ };
322
+ }, []);
323
+ const toggleMenu = (e) => {
324
+ e.stopPropagation();
325
+ if (hasDragged) {
326
+ return;
327
+ }
328
+ setIsMenuOpen(!isMenuOpen);
329
+ };
330
+ React4.useEffect(() => {
331
+ if (!isMenuOpen) return;
332
+ const handleClickOutside = (e) => {
333
+ if (containerRef.current && !containerRef.current.contains(e.target)) {
334
+ setIsMenuOpen(false);
335
+ }
336
+ };
337
+ document.addEventListener("mousedown", handleClickOutside);
338
+ return () => {
339
+ document.removeEventListener("mousedown", handleClickOutside);
340
+ };
341
+ }, [isMenuOpen]);
342
+ React4.useEffect(() => {
343
+ if (!mounted) return;
344
+ const checkBoundaries = () => {
345
+ const windowWidth = window.innerWidth;
346
+ const windowHeight = window.innerHeight;
347
+ setPosition((prev) => {
348
+ const newX = Math.min(Math.max(prev.x, 0), windowWidth - 50);
349
+ const newY = Math.min(Math.max(prev.y, 0), windowHeight - 50);
350
+ if (newX !== prev.x || newY !== prev.y) {
351
+ return { x: newX, y: newY };
352
+ }
353
+ return prev;
354
+ });
355
+ };
356
+ window.addEventListener("resize", checkBoundaries);
357
+ return () => {
358
+ window.removeEventListener("resize", checkBoundaries);
359
+ };
360
+ }, [mounted]);
361
+ if (!mounted) return null;
362
+ return reactDom.createPortal(
363
+ /* @__PURE__ */ React4__default.default.createElement(
364
+ "div",
365
+ {
366
+ ref: containerRef,
367
+ className: clsx.clsx("fixed select-none box-border", className),
368
+ style: {
369
+ left: position.x + "px",
370
+ top: position.y + "px",
371
+ zIndex
372
+ }
373
+ },
374
+ /* @__PURE__ */ React4__default.default.createElement(
375
+ "div",
376
+ {
377
+ className: clsx.clsx(
378
+ "flex items-center justify-center w-12 h-12 md:w-12 md:h-12 bg-white rounded-full shadow-md hover:shadow-lg cursor-grab active:cursor-grabbing transition-all duration-200 hover:scale-105 active:scale-95",
379
+ triggerClassName
380
+ ),
381
+ onMouseDown: handleMouseDown,
382
+ onClick: toggleMenu
383
+ },
384
+ trigger
385
+ ),
386
+ isMenuOpen && /* @__PURE__ */ React4__default.default.createElement(
387
+ "div",
388
+ {
389
+ className: clsx.clsx(
390
+ "absolute top-0 bg-white rounded-lg shadow-xl p-3 min-w-[200px] md:min-w-[200px] max-w-[300px] z-[1000] transition-all duration-200",
391
+ isMenuOpen ? "opacity-100 scale-100" : "opacity-0 scale-95",
392
+ menuDirection === "left" ? "right-[calc(100%+10px)]" : "left-[calc(100%+10px)]",
393
+ menuClassName
394
+ ),
395
+ onClick: (e) => e.stopPropagation(),
396
+ onMouseDown: (e) => e.stopPropagation(),
397
+ onMouseUp: (e) => e.stopPropagation(),
398
+ onTouchStart: (e) => e.stopPropagation(),
399
+ onTouchMove: (e) => e.stopPropagation(),
400
+ onTouchEnd: (e) => e.stopPropagation(),
401
+ onPointerDown: (e) => e.stopPropagation(),
402
+ onPointerUp: (e) => e.stopPropagation()
403
+ },
404
+ menu
405
+ )
406
+ ),
407
+ document.body
408
+ );
409
+ };
410
+ var FloatingMenu_default = FloatingMenu;
225
411
  var elementStyle = (element) => ({
226
412
  position: "absolute",
227
413
  zIndex: 2,
@@ -233,7 +419,7 @@ var elementStyle = (element) => ({
233
419
  });
234
420
  var renderElement = (element) => {
235
421
  if (element.type === "text") {
236
- return /* @__PURE__ */ React3__default.default.createElement(
422
+ return /* @__PURE__ */ React4__default.default.createElement(
237
423
  "div",
238
424
  {
239
425
  key: element.id,
@@ -251,7 +437,7 @@ var renderElement = (element) => {
251
437
  element.content
252
438
  );
253
439
  }
254
- return /* @__PURE__ */ React3__default.default.createElement(
440
+ return /* @__PURE__ */ React4__default.default.createElement(
255
441
  "img",
256
442
  {
257
443
  key: element.id,
@@ -267,15 +453,92 @@ var renderElement = (element) => {
267
453
  }
268
454
  );
269
455
  };
270
- var FestivalCardPageRenderer = ({ page }) => {
271
- const backgroundElement = page.elements.find(
272
- (element) => element.type === "image" && Boolean(element.isBackground)
456
+ var clamp = (value, min, max) => Math.min(max, Math.max(min, value));
457
+ var FestivalCardPageRenderer = ({
458
+ page,
459
+ editable = false,
460
+ selectedElementId = null,
461
+ onElementSelect,
462
+ onElementChange
463
+ }) => {
464
+ const [draggingElementId, setDraggingElementId] = React4.useState(null);
465
+ const [resizingElementId, setResizingElementId] = React4.useState(null);
466
+ const stageRef = React4.useRef(null);
467
+ const interactionRef = React4.useRef(null);
468
+ const backgroundElement = React4.useMemo(
469
+ () => page.elements.find(
470
+ (element) => element.type === "image" && Boolean(element.isBackground)
471
+ ),
472
+ [page]
273
473
  );
274
- const foregroundElements = page.elements.filter((element) => !(element.type === "image" && element.isBackground));
275
- return /* @__PURE__ */ React3__default.default.createElement(
474
+ const foregroundElements = React4.useMemo(
475
+ () => page.elements.filter((element) => !(element.type === "image" && element.isBackground)),
476
+ [page]
477
+ );
478
+ const updateElementByPointer = (element, interaction, clientX, clientY) => {
479
+ if (!onElementChange || interaction.rect.width <= 0 || interaction.rect.height <= 0) return;
480
+ const xPercent = clamp((clientX - interaction.rect.left) / interaction.rect.width * 100, 0, 100);
481
+ const yPercent = clamp((clientY - interaction.rect.top) / interaction.rect.height * 100, 0, 100);
482
+ if (interaction.mode === "move") {
483
+ onElementChange(element.id, { x: xPercent, y: yPercent });
484
+ return;
485
+ }
486
+ const nextWidth = clamp(Math.abs(xPercent - element.x) * 2, 4, 100);
487
+ if (element.type === "image") {
488
+ const nextHeight = clamp(Math.abs(yPercent - element.y) * 2, 4, 100);
489
+ onElementChange(element.id, { width: nextWidth, height: nextHeight });
490
+ return;
491
+ }
492
+ onElementChange(element.id, { width: nextWidth });
493
+ };
494
+ const beginInteraction = (event, elementId, mode) => {
495
+ if (!editable || !stageRef.current) return;
496
+ event.preventDefault();
497
+ event.stopPropagation();
498
+ const rect = stageRef.current.getBoundingClientRect();
499
+ interactionRef.current = {
500
+ pointerId: event.pointerId,
501
+ elementId,
502
+ mode,
503
+ rect
504
+ };
505
+ event.currentTarget.setPointerCapture(event.pointerId);
506
+ onElementSelect?.(elementId);
507
+ if (mode === "move") {
508
+ setDraggingElementId(elementId);
509
+ setResizingElementId(null);
510
+ } else {
511
+ setResizingElementId(elementId);
512
+ setDraggingElementId(null);
513
+ }
514
+ const element = foregroundElements.find((item) => item.id === elementId);
515
+ if (element) {
516
+ updateElementByPointer(element, interactionRef.current, event.clientX, event.clientY);
517
+ }
518
+ };
519
+ const handlePointerMove = (event) => {
520
+ const interaction = interactionRef.current;
521
+ if (!interaction || interaction.pointerId !== event.pointerId) return;
522
+ const element = foregroundElements.find((item) => item.id === interaction.elementId);
523
+ if (!element) return;
524
+ updateElementByPointer(element, interaction, event.clientX, event.clientY);
525
+ };
526
+ const endInteraction = (event) => {
527
+ const interaction = interactionRef.current;
528
+ if (!interaction || interaction.pointerId !== event.pointerId) return;
529
+ interactionRef.current = null;
530
+ setDraggingElementId(null);
531
+ setResizingElementId(null);
532
+ };
533
+ return /* @__PURE__ */ React4__default.default.createElement(
276
534
  "div",
277
535
  {
278
- className: "relative h-full w-full overflow-hidden rounded-2xl",
536
+ ref: stageRef,
537
+ onPointerMove: editable ? handlePointerMove : void 0,
538
+ onPointerUp: editable ? endInteraction : void 0,
539
+ onPointerCancel: editable ? endInteraction : void 0,
540
+ onClick: editable ? () => onElementSelect?.(null) : void 0,
541
+ className: `relative h-full w-full overflow-hidden rounded-2xl ${editable ? "touch-none" : ""}`,
279
542
  style: {
280
543
  backgroundColor: page.background?.color || "#0f172a",
281
544
  backgroundImage: backgroundElement ? `url(${backgroundElement.src})` : page.background?.image ? `url(${page.background.image})` : void 0,
@@ -283,19 +546,251 @@ var FestivalCardPageRenderer = ({ page }) => {
283
546
  backgroundPosition: "center"
284
547
  }
285
548
  },
286
- /* @__PURE__ */ React3__default.default.createElement("div", { className: "absolute inset-0 bg-slate-950/20" }),
287
- foregroundElements.map(renderElement)
549
+ /* @__PURE__ */ React4__default.default.createElement("div", { className: "absolute inset-0 bg-slate-950/20" }),
550
+ foregroundElements.map((element) => {
551
+ if (!editable) {
552
+ return renderElement(element);
553
+ }
554
+ const isSelected = selectedElementId === element.id;
555
+ const isDragging = draggingElementId === element.id;
556
+ const isResizing = resizingElementId === element.id;
557
+ return /* @__PURE__ */ React4__default.default.createElement(
558
+ "div",
559
+ {
560
+ key: element.id,
561
+ role: "button",
562
+ tabIndex: 0,
563
+ onClick: (event) => {
564
+ event.stopPropagation();
565
+ onElementSelect?.(element.id);
566
+ },
567
+ onPointerDown: (event) => beginInteraction(event, element.id, "move"),
568
+ 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"}`,
569
+ style: {
570
+ ...elementStyle(element),
571
+ zIndex: isSelected ? 4 : 2
572
+ }
573
+ },
574
+ element.type === "text" ? /* @__PURE__ */ React4__default.default.createElement(
575
+ "div",
576
+ {
577
+ className: "rounded-md bg-black/20 px-2 py-1",
578
+ style: {
579
+ color: element.color || "#f8fafc",
580
+ fontSize: element.fontSize || 18,
581
+ fontWeight: element.fontWeight || 500,
582
+ fontFamily: element.fontFamily || "inherit",
583
+ textAlign: element.align || "left",
584
+ lineHeight: 1.45,
585
+ whiteSpace: "pre-wrap"
586
+ }
587
+ },
588
+ element.content
589
+ ) : /* @__PURE__ */ React4__default.default.createElement(
590
+ "img",
591
+ {
592
+ src: element.src,
593
+ alt: element.alt || "festival-card-image",
594
+ draggable: false,
595
+ className: "pointer-events-none h-full w-full",
596
+ style: {
597
+ objectFit: element.fit || "cover",
598
+ borderRadius: element.borderRadius || 0,
599
+ overflow: "hidden",
600
+ boxShadow: "0 12px 30px rgba(2, 6, 23, 0.32)"
601
+ }
602
+ }
603
+ ),
604
+ /* @__PURE__ */ React4__default.default.createElement(
605
+ "button",
606
+ {
607
+ type: "button",
608
+ "aria-label": "resize",
609
+ onPointerDown: (event) => beginInteraction(event, element.id, "resize"),
610
+ className: "absolute -bottom-2 -right-2 h-4 w-4 rounded-full border border-white bg-sky-500 shadow"
611
+ }
612
+ )
613
+ );
614
+ })
288
615
  );
289
616
  };
290
617
 
291
618
  // src/festivalCard/components/FestivalCardBook3D.tsx
292
- var FestivalCardBook3D = ({ config, className }) => {
293
- const [currentPage, setCurrentPage] = React3.useState(0);
294
- const normalized = React3.useMemo(() => normalizeFestivalCardConfig(config), [config]);
619
+ var loadImage = (src) => new Promise((resolve, reject) => {
620
+ const image = new window.Image();
621
+ image.crossOrigin = "anonymous";
622
+ image.decoding = "async";
623
+ image.onload = () => resolve(image);
624
+ image.onerror = () => reject(new Error(`\u56FE\u7247\u52A0\u8F7D\u5931\u8D25: ${src}`));
625
+ image.src = src;
626
+ });
627
+ var drawImageWithFit = (ctx, image, left, top, width, height, fit) => {
628
+ const imageRatio = image.width / image.height;
629
+ const boxRatio = width / height;
630
+ let drawWidth = width;
631
+ let drawHeight = height;
632
+ let offsetX = left;
633
+ let offsetY = top;
634
+ if (fit === "cover") {
635
+ if (imageRatio > boxRatio) {
636
+ drawHeight = height;
637
+ drawWidth = height * imageRatio;
638
+ offsetX = left - (drawWidth - width) / 2;
639
+ } else {
640
+ drawWidth = width;
641
+ drawHeight = width / imageRatio;
642
+ offsetY = top - (drawHeight - height) / 2;
643
+ }
644
+ } else if (imageRatio > boxRatio) {
645
+ drawWidth = width;
646
+ drawHeight = width / imageRatio;
647
+ offsetY = top + (height - drawHeight) / 2;
648
+ } else {
649
+ drawHeight = height;
650
+ drawWidth = height * imageRatio;
651
+ offsetX = left + (width - drawWidth) / 2;
652
+ }
653
+ ctx.drawImage(image, offsetX, offsetY, drawWidth, drawHeight);
654
+ };
655
+ var withRoundedClip = (ctx, left, top, width, height, radius, draw) => {
656
+ const safeRadius = Math.max(0, Math.min(radius, Math.min(width, height) / 2));
657
+ if (safeRadius <= 0) {
658
+ draw();
659
+ return;
660
+ }
661
+ ctx.save();
662
+ ctx.beginPath();
663
+ ctx.moveTo(left + safeRadius, top);
664
+ ctx.lineTo(left + width - safeRadius, top);
665
+ ctx.quadraticCurveTo(left + width, top, left + width, top + safeRadius);
666
+ ctx.lineTo(left + width, top + height - safeRadius);
667
+ ctx.quadraticCurveTo(left + width, top + height, left + width - safeRadius, top + height);
668
+ ctx.lineTo(left + safeRadius, top + height);
669
+ ctx.quadraticCurveTo(left, top + height, left, top + height - safeRadius);
670
+ ctx.lineTo(left, top + safeRadius);
671
+ ctx.quadraticCurveTo(left, top, left + safeRadius, top);
672
+ ctx.closePath();
673
+ ctx.clip();
674
+ draw();
675
+ ctx.restore();
676
+ };
677
+ var drawMultilineText = (ctx, text, left, top, maxWidth, lineHeight) => {
678
+ const paragraphs = text.split("\n");
679
+ let currentY = top;
680
+ paragraphs.forEach((paragraph, index) => {
681
+ const words = paragraph.split("");
682
+ let line = "";
683
+ for (const word of words) {
684
+ const testLine = line + word;
685
+ if (ctx.measureText(testLine).width > maxWidth && line) {
686
+ ctx.fillText(line, left, currentY);
687
+ line = word;
688
+ currentY += lineHeight;
689
+ } else {
690
+ line = testLine;
691
+ }
692
+ }
693
+ ctx.fillText(line, left, currentY);
694
+ currentY += lineHeight;
695
+ if (index < paragraphs.length - 1) {
696
+ currentY += lineHeight * 0.2;
697
+ }
698
+ });
699
+ };
700
+ var exportPageToPng = async (page, fileName) => {
701
+ const width = 1080;
702
+ const height = 1440;
703
+ const canvas = document.createElement("canvas");
704
+ canvas.width = width;
705
+ canvas.height = height;
706
+ const ctx = canvas.getContext("2d");
707
+ if (!ctx) throw new Error("\u65E0\u6CD5\u521B\u5EFA Canvas \u4E0A\u4E0B\u6587");
708
+ ctx.fillStyle = page.background?.color || "#0f172a";
709
+ ctx.fillRect(0, 0, width, height);
710
+ const backgroundElement = page.elements.find(
711
+ (element) => element.type === "image" && Boolean(element.isBackground)
712
+ );
713
+ const backgroundImageSrc = backgroundElement?.src || page.background?.image;
714
+ if (backgroundImageSrc) {
715
+ const image = await loadImage(backgroundImageSrc);
716
+ drawImageWithFit(ctx, image, 0, 0, width, height, "cover");
717
+ }
718
+ const foregroundElements = page.elements.filter((element) => !(element.type === "image" && element.isBackground));
719
+ for (const element of foregroundElements) {
720
+ const elementWidth = width * (element.width ?? 70) / 100;
721
+ const elementHeight = element.height ? height * element.height / 100 : void 0;
722
+ const centerX = width * element.x / 100;
723
+ const centerY = height * element.y / 100;
724
+ const left = centerX - elementWidth / 2;
725
+ if (element.type === "image") {
726
+ const image = await loadImage(element.src);
727
+ const drawHeight = elementHeight ?? elementWidth;
728
+ const boxTop = centerY - drawHeight / 2;
729
+ withRoundedClip(ctx, left, boxTop, elementWidth, drawHeight, element.borderRadius ?? 0, () => {
730
+ drawImageWithFit(ctx, image, left, boxTop, elementWidth, drawHeight, element.fit || "cover");
731
+ });
732
+ continue;
733
+ }
734
+ const fontSize = (element.fontSize || 18) * 1.5;
735
+ ctx.fillStyle = element.color || "#f8fafc";
736
+ ctx.font = `${element.fontWeight || 500} ${fontSize}px ${element.fontFamily || "sans-serif"}`;
737
+ ctx.textBaseline = "top";
738
+ ctx.textAlign = element.align || "left";
739
+ const textX = element.align === "center" ? centerX : element.align === "right" ? left + elementWidth : left;
740
+ drawMultilineText(ctx, element.content || "", textX, centerY - fontSize * 0.72, elementWidth, fontSize * 1.45);
741
+ }
742
+ const blob = await new Promise((resolve) => canvas.toBlob(resolve, "image/png"));
743
+ if (!blob) throw new Error("\u5BFC\u51FA\u5931\u8D25\uFF0C\u8BF7\u91CD\u8BD5");
744
+ const url = URL.createObjectURL(blob);
745
+ const anchor = document.createElement("a");
746
+ anchor.href = url;
747
+ anchor.download = fileName;
748
+ anchor.click();
749
+ URL.revokeObjectURL(url);
750
+ };
751
+ var FestivalCardBook3D = ({
752
+ config,
753
+ className,
754
+ editable = false,
755
+ enableExportImage = !editable,
756
+ currentPage: currentPageProp,
757
+ onCurrentPageChange,
758
+ selectedElementId = null,
759
+ onSelectedElementChange,
760
+ onElementChange
761
+ }) => {
762
+ const [internalCurrentPage, setInternalCurrentPage] = React4.useState(0);
763
+ const [exporting, setExporting] = React4.useState(false);
764
+ const normalized = React4.useMemo(() => normalizeFestivalCardConfig(config), [config]);
295
765
  const pages = normalized.pages;
766
+ const currentPage = typeof currentPageProp === "number" ? currentPageProp : internalCurrentPage;
767
+ const setCurrentPage = (updater) => {
768
+ const prev = currentPage;
769
+ const nextValue = typeof updater === "function" ? updater(prev) : updater;
770
+ if (typeof currentPageProp === "number") {
771
+ onCurrentPageChange?.(nextValue);
772
+ return;
773
+ }
774
+ setInternalCurrentPage(nextValue);
775
+ onCurrentPageChange?.(nextValue);
776
+ };
296
777
  const canPrev = currentPage > 0;
297
778
  const canNext = currentPage < pages.length - 1;
298
- return /* @__PURE__ */ React3__default.default.createElement("div", { className }, /* @__PURE__ */ React3__default.default.createElement("div", { className: "w-full min-h-screen px-0 py-4" }, /* @__PURE__ */ React3__default.default.createElement("div", { className: "mx-auto w-full text-center text-slate-100" }, /* @__PURE__ */ React3__default.default.createElement("h3", { className: "mb-3 text-lg font-semibold" }, normalized.coverTitle || "Festival Card")), /* @__PURE__ */ React3__default.default.createElement("div", { className: "mx-auto w-full" }, /* @__PURE__ */ React3__default.default.createElement("div", { className: "relative h-[calc(100vh-170px)] min-h-[460px]" }, pages.map((page, index) => /* @__PURE__ */ React3__default.default.createElement(
779
+ const currentPageData = pages[currentPage];
780
+ const handleExportCurrentPage = async () => {
781
+ if (!currentPageData || exporting) return;
782
+ setExporting(true);
783
+ try {
784
+ const base = normalized.id || "festival-card";
785
+ const fileName = `${base}-page-${currentPage + 1}.png`;
786
+ await exportPageToPng(currentPageData, fileName);
787
+ } catch (error) {
788
+ window.alert(error.message || "\u5BFC\u51FA\u56FE\u7247\u5931\u8D25");
789
+ } finally {
790
+ setExporting(false);
791
+ }
792
+ };
793
+ return /* @__PURE__ */ React4__default.default.createElement("div", { className }, /* @__PURE__ */ React4__default.default.createElement("div", { className: "w-full min-h-screen px-0 py-4" }, /* @__PURE__ */ React4__default.default.createElement("div", { className: "mx-auto w-full text-center text-slate-100" }, /* @__PURE__ */ React4__default.default.createElement("h3", { className: "mb-3 text-lg font-semibold" }, normalized.coverTitle || "Festival Card")), /* @__PURE__ */ React4__default.default.createElement("div", { className: "mx-auto w-full" }, /* @__PURE__ */ React4__default.default.createElement("div", { className: "relative h-[calc(100vh-170px)] min-h-[460px]" }, pages.map((page, index) => /* @__PURE__ */ React4__default.default.createElement(
299
794
  "div",
300
795
  {
301
796
  key: page.id,
@@ -305,8 +800,17 @@ var FestivalCardBook3D = ({ config, className }) => {
305
800
  pointerEvents: index === currentPage ? "auto" : "none"
306
801
  }
307
802
  },
308
- /* @__PURE__ */ React3__default.default.createElement(FestivalCardPageRenderer, { page })
309
- )))), /* @__PURE__ */ React3__default.default.createElement("div", { className: "mt-4 flex justify-center gap-3" }, /* @__PURE__ */ React3__default.default.createElement(
803
+ /* @__PURE__ */ React4__default.default.createElement(
804
+ FestivalCardPageRenderer,
805
+ {
806
+ page,
807
+ editable: editable && index === currentPage,
808
+ selectedElementId,
809
+ onElementSelect: onSelectedElementChange,
810
+ onElementChange: (elementId, patch) => onElementChange?.(index, elementId, patch)
811
+ }
812
+ )
813
+ )))), /* @__PURE__ */ React4__default.default.createElement("div", { className: "mt-4 flex justify-center gap-3" }, /* @__PURE__ */ React4__default.default.createElement(
310
814
  "button",
311
815
  {
312
816
  type: "button",
@@ -315,7 +819,7 @@ var FestivalCardBook3D = ({ config, className }) => {
315
819
  className: "rounded-full bg-white px-5 py-2 text-sm font-medium text-slate-900 disabled:cursor-not-allowed disabled:opacity-45"
316
820
  },
317
821
  "\u4E0A\u4E00\u9875"
318
- ), /* @__PURE__ */ React3__default.default.createElement(
822
+ ), /* @__PURE__ */ React4__default.default.createElement(
319
823
  "button",
320
824
  {
321
825
  type: "button",
@@ -324,7 +828,7 @@ var FestivalCardBook3D = ({ config, className }) => {
324
828
  className: "rounded-full bg-sky-300 px-5 py-2 text-sm font-medium text-slate-900 disabled:cursor-not-allowed disabled:opacity-45"
325
829
  },
326
830
  "\u4E0B\u4E00\u9875"
327
- ))), normalized.backgroundMusic?.src ? /* @__PURE__ */ React3__default.default.createElement(
831
+ ))), normalized.backgroundMusic?.src ? /* @__PURE__ */ React4__default.default.createElement(
328
832
  "audio",
329
833
  {
330
834
  src: normalized.backgroundMusic.src,
@@ -333,6 +837,24 @@ var FestivalCardBook3D = ({ config, className }) => {
333
837
  controls: true,
334
838
  className: "mt-3 w-full"
335
839
  }
840
+ ) : null, enableExportImage ? /* @__PURE__ */ React4__default.default.createElement(
841
+ FloatingMenu_default,
842
+ {
843
+ initialPosition: { x: 24, y: 120 },
844
+ trigger: /* @__PURE__ */ React4__default.default.createElement("div", { className: "text-lg leading-none text-slate-700", "aria-hidden": true }, "\u2301"),
845
+ menu: /* @__PURE__ */ React4__default.default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React4__default.default.createElement("div", { className: "text-xs font-semibold tracking-wide text-slate-500" }, "\u8D3A\u5361\u5DE5\u5177"), /* @__PURE__ */ React4__default.default.createElement(
846
+ "button",
847
+ {
848
+ type: "button",
849
+ onClick: () => void handleExportCurrentPage(),
850
+ disabled: exporting,
851
+ className: "rounded-lg bg-sky-600 px-3 py-2 text-left text-sm font-medium text-white disabled:opacity-60"
852
+ },
853
+ exporting ? "\u5BFC\u51FA\u4E2D..." : `\u5BFC\u51FA\u7B2C ${currentPage + 1} \u9875 PNG`
854
+ )),
855
+ triggerClassName: "bg-white/95 backdrop-blur",
856
+ menuClassName: "bg-white/95 backdrop-blur"
857
+ }
336
858
  ) : null);
337
859
  };
338
860
  var createTextElement = (pageIndex) => ({
@@ -359,11 +881,25 @@ var createImageElement = (pageIndex) => ({
359
881
  fit: "cover",
360
882
  borderRadius: 12
361
883
  });
362
- var FestivalCardConfigEditor = ({ value, onChange }) => {
363
- const [activePageIndex, setActivePageIndex] = React3.useState(0);
884
+ var FestivalCardConfigEditor = ({
885
+ value,
886
+ onChange,
887
+ activePageIndex: activePageIndexProp,
888
+ onActivePageIndexChange,
889
+ selectedElementId
890
+ }) => {
891
+ const [internalActivePageIndex, setInternalActivePageIndex] = React4.useState(0);
892
+ const activePageIndex = activePageIndexProp ?? internalActivePageIndex;
893
+ const setActivePageIndex = (index) => {
894
+ if (typeof activePageIndexProp === "number") {
895
+ onActivePageIndexChange?.(index);
896
+ return;
897
+ }
898
+ setInternalActivePageIndex(index);
899
+ };
364
900
  const page = value.pages[activePageIndex];
365
901
  const canEditPage = Boolean(page);
366
- const pageOptions = React3.useMemo(() => value.pages.map((_, index) => index), [value.pages]);
902
+ const pageOptions = React4.useMemo(() => value.pages.map((_, index) => index), [value.pages]);
367
903
  const handlePageCountChange = (nextRaw) => {
368
904
  const next = Number.isFinite(nextRaw) ? Math.max(1, Math.min(12, Math.floor(nextRaw))) : value.pages.length;
369
905
  const resized = resizeFestivalCardPages(value, next);
@@ -401,7 +937,7 @@ var FestivalCardConfigEditor = ({ value, onChange }) => {
401
937
  });
402
938
  };
403
939
  const numberFieldClassName = "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";
404
- return /* @__PURE__ */ React3__default.default.createElement("div", { className: "rounded-2xl border border-slate-200 bg-white p-4 text-slate-900 shadow-sm" }, /* @__PURE__ */ React3__default.default.createElement("div", { className: "grid gap-3" }, /* @__PURE__ */ React3__default.default.createElement("label", { className: "grid gap-1.5" }, /* @__PURE__ */ React3__default.default.createElement("span", { className: "text-sm font-medium text-slate-700" }, "\u9875\u9762\u6570\u91CF"), /* @__PURE__ */ React3__default.default.createElement(
940
+ return /* @__PURE__ */ React4__default.default.createElement("div", { className: "rounded-2xl border border-slate-200 bg-white p-4 text-slate-900 shadow-sm" }, /* @__PURE__ */ React4__default.default.createElement("div", { className: "grid gap-3" }, /* @__PURE__ */ React4__default.default.createElement("label", { className: "grid gap-1.5" }, /* @__PURE__ */ React4__default.default.createElement("span", { className: "text-sm font-medium text-slate-700" }, "\u9875\u9762\u6570\u91CF"), /* @__PURE__ */ React4__default.default.createElement(
405
941
  "input",
406
942
  {
407
943
  type: "number",
@@ -411,7 +947,7 @@ var FestivalCardConfigEditor = ({ value, onChange }) => {
411
947
  onChange: (event) => handlePageCountChange(Number(event.target.value)),
412
948
  className: "rounded-lg border border-slate-300 bg-white px-3 py-2 text-slate-900 outline-none focus:border-sky-400 focus:ring-2 focus:ring-sky-100"
413
949
  }
414
- )), /* @__PURE__ */ React3__default.default.createElement("label", { className: "grid gap-1.5" }, /* @__PURE__ */ React3__default.default.createElement("span", { className: "text-sm font-medium text-slate-700" }, "\u80CC\u666F\u97F3\u4E50 URL"), /* @__PURE__ */ React3__default.default.createElement(
950
+ )), /* @__PURE__ */ React4__default.default.createElement("label", { className: "grid gap-1.5" }, /* @__PURE__ */ React4__default.default.createElement("span", { className: "text-sm font-medium text-slate-700" }, "\u80CC\u666F\u97F3\u4E50 URL"), /* @__PURE__ */ React4__default.default.createElement(
415
951
  "input",
416
952
  {
417
953
  type: "url",
@@ -425,7 +961,7 @@ var FestivalCardConfigEditor = ({ value, onChange }) => {
425
961
  }),
426
962
  className: "rounded-lg border border-slate-300 bg-white px-3 py-2 text-slate-900 outline-none focus:border-sky-400 focus:ring-2 focus:ring-sky-100"
427
963
  }
428
- )), /* @__PURE__ */ React3__default.default.createElement("label", { className: "grid gap-1.5" }, /* @__PURE__ */ React3__default.default.createElement("span", { className: "text-sm font-medium text-slate-700" }, "\u9875\u9762\u6807\u9898"), /* @__PURE__ */ React3__default.default.createElement(
964
+ )), /* @__PURE__ */ React4__default.default.createElement("label", { className: "grid gap-1.5" }, /* @__PURE__ */ React4__default.default.createElement("span", { className: "text-sm font-medium text-slate-700" }, "\u9875\u9762\u6807\u9898"), /* @__PURE__ */ React4__default.default.createElement(
429
965
  "input",
430
966
  {
431
967
  type: "text",
@@ -433,7 +969,7 @@ var FestivalCardConfigEditor = ({ value, onChange }) => {
433
969
  onChange: (event) => updatePage({ title: event.target.value }),
434
970
  className: "rounded-lg border border-slate-300 bg-white px-3 py-2 text-slate-900 outline-none focus:border-sky-400 focus:ring-2 focus:ring-sky-100"
435
971
  }
436
- )), /* @__PURE__ */ React3__default.default.createElement("div", { className: "grid gap-2 sm:grid-cols-2" }, /* @__PURE__ */ React3__default.default.createElement("label", { className: "grid gap-1.5" }, /* @__PURE__ */ React3__default.default.createElement("span", { className: "text-sm font-medium text-slate-700" }, "\u9875\u9762\u80CC\u666F\u8272"), /* @__PURE__ */ React3__default.default.createElement(
972
+ )), /* @__PURE__ */ React4__default.default.createElement("div", { className: "grid gap-2 sm:grid-cols-2" }, /* @__PURE__ */ React4__default.default.createElement("label", { className: "grid gap-1.5" }, /* @__PURE__ */ React4__default.default.createElement("span", { className: "text-sm font-medium text-slate-700" }, "\u9875\u9762\u80CC\u666F\u8272"), /* @__PURE__ */ React4__default.default.createElement(
437
973
  "input",
438
974
  {
439
975
  type: "color",
@@ -446,7 +982,7 @@ var FestivalCardConfigEditor = ({ value, onChange }) => {
446
982
  }),
447
983
  className: "h-10 w-full rounded-lg border border-slate-300 bg-white p-1"
448
984
  }
449
- )), /* @__PURE__ */ React3__default.default.createElement("label", { className: "grid gap-1.5" }, /* @__PURE__ */ React3__default.default.createElement("span", { className: "text-sm font-medium text-slate-700" }, "\u9875\u9762\u80CC\u666F\u56FE URL"), /* @__PURE__ */ React3__default.default.createElement(
985
+ )), /* @__PURE__ */ React4__default.default.createElement("label", { className: "grid gap-1.5" }, /* @__PURE__ */ React4__default.default.createElement("span", { className: "text-sm font-medium text-slate-700" }, "\u9875\u9762\u80CC\u666F\u56FE URL"), /* @__PURE__ */ React4__default.default.createElement(
450
986
  "input",
451
987
  {
452
988
  type: "url",
@@ -459,15 +995,15 @@ var FestivalCardConfigEditor = ({ value, onChange }) => {
459
995
  }),
460
996
  className: "rounded-lg border border-slate-300 bg-white px-3 py-2 text-slate-900 outline-none focus:border-sky-400 focus:ring-2 focus:ring-sky-100"
461
997
  }
462
- ))), /* @__PURE__ */ React3__default.default.createElement("label", { className: "grid gap-1.5" }, /* @__PURE__ */ React3__default.default.createElement("span", { className: "text-sm font-medium text-slate-700" }, "\u7F16\u8F91\u9875\u9762"), /* @__PURE__ */ React3__default.default.createElement(
998
+ ))), /* @__PURE__ */ React4__default.default.createElement("label", { className: "grid gap-1.5" }, /* @__PURE__ */ React4__default.default.createElement("span", { className: "text-sm font-medium text-slate-700" }, "\u7F16\u8F91\u9875\u9762"), /* @__PURE__ */ React4__default.default.createElement(
463
999
  "select",
464
1000
  {
465
1001
  value: activePageIndex,
466
1002
  onChange: (event) => setActivePageIndex(Number(event.target.value)),
467
1003
  className: "rounded-lg border border-slate-300 bg-white px-3 py-2 text-slate-900 outline-none focus:border-sky-400 focus:ring-2 focus:ring-sky-100"
468
1004
  },
469
- pageOptions.map((index) => /* @__PURE__ */ React3__default.default.createElement("option", { key: index, value: index }, "\u7B2C ", index + 1, " \u9875"))
470
- ))), canEditPage ? /* @__PURE__ */ React3__default.default.createElement("div", { className: "mt-4" }, /* @__PURE__ */ React3__default.default.createElement("div", { className: "mb-3 flex gap-2" }, /* @__PURE__ */ React3__default.default.createElement(
1005
+ pageOptions.map((index) => /* @__PURE__ */ React4__default.default.createElement("option", { key: index, value: index }, "\u7B2C ", index + 1, " \u9875"))
1006
+ ))), canEditPage ? /* @__PURE__ */ React4__default.default.createElement("div", { className: "mt-4" }, /* @__PURE__ */ React4__default.default.createElement("div", { className: "mb-3 flex gap-2" }, /* @__PURE__ */ React4__default.default.createElement(
471
1007
  "button",
472
1008
  {
473
1009
  type: "button",
@@ -480,7 +1016,7 @@ var FestivalCardConfigEditor = ({ value, onChange }) => {
480
1016
  className: "rounded-lg bg-slate-900 px-3 py-2 text-sm font-medium text-white"
481
1017
  },
482
1018
  "+ \u6587\u5B57"
483
- ), /* @__PURE__ */ React3__default.default.createElement(
1019
+ ), /* @__PURE__ */ React4__default.default.createElement(
484
1020
  "button",
485
1021
  {
486
1022
  type: "button",
@@ -493,166 +1029,174 @@ var FestivalCardConfigEditor = ({ value, onChange }) => {
493
1029
  className: "rounded-lg bg-sky-600 px-3 py-2 text-sm font-medium text-white"
494
1030
  },
495
1031
  "+ \u56FE\u7247"
496
- )), /* @__PURE__ */ React3__default.default.createElement("div", { className: "grid max-h-[340px] gap-2.5 overflow-auto pr-1" }, (page?.elements ?? []).map((element) => /* @__PURE__ */ React3__default.default.createElement("div", { key: element.id, className: "rounded-xl border border-slate-200 bg-slate-50 p-3" }, /* @__PURE__ */ React3__default.default.createElement("div", { className: "mb-2 flex items-center justify-between" }, /* @__PURE__ */ React3__default.default.createElement("div", { className: "text-xs font-semibold tracking-wide text-slate-500" }, element.type.toUpperCase()), /* @__PURE__ */ React3__default.default.createElement(
497
- "button",
498
- {
499
- type: "button",
500
- onClick: () => removeElement(element.id),
501
- className: "rounded-md border border-rose-300 bg-rose-50 px-2 py-1 text-xs font-medium text-rose-700"
502
- },
503
- "\u5220\u9664"
504
- )), element.type === "text" ? /* @__PURE__ */ React3__default.default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React3__default.default.createElement(
505
- "textarea",
506
- {
507
- value: element.content,
508
- onChange: (event) => updateElement(element.id, { content: event.target.value }),
509
- rows: 3,
510
- 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"
511
- }
512
- ), /* @__PURE__ */ React3__default.default.createElement("div", { className: "grid gap-2 sm:grid-cols-2" }, /* @__PURE__ */ React3__default.default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "X(%)", /* @__PURE__ */ React3__default.default.createElement(
513
- "input",
514
- {
515
- type: "number",
516
- value: element.x,
517
- onChange: (event) => updateElement(element.id, { x: Number(event.target.value) }),
518
- className: numberFieldClassName
519
- }
520
- )), /* @__PURE__ */ React3__default.default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "Y(%)", /* @__PURE__ */ React3__default.default.createElement(
521
- "input",
522
- {
523
- type: "number",
524
- value: element.y,
525
- onChange: (event) => updateElement(element.id, { y: Number(event.target.value) }),
526
- className: numberFieldClassName
527
- }
528
- )), /* @__PURE__ */ React3__default.default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "\u5BBD\u5EA6(%)", /* @__PURE__ */ React3__default.default.createElement(
529
- "input",
530
- {
531
- type: "number",
532
- value: element.width ?? 70,
533
- onChange: (event) => updateElement(element.id, { width: Number(event.target.value) }),
534
- className: numberFieldClassName
535
- }
536
- )), /* @__PURE__ */ React3__default.default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "\u5B57\u53F7(px)", /* @__PURE__ */ React3__default.default.createElement(
537
- "input",
538
- {
539
- type: "number",
540
- value: element.fontSize ?? 18,
541
- onChange: (event) => updateElement(element.id, { fontSize: Number(event.target.value) }),
542
- className: numberFieldClassName
543
- }
544
- )), /* @__PURE__ */ React3__default.default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "\u5B57\u91CD", /* @__PURE__ */ React3__default.default.createElement(
545
- "input",
546
- {
547
- type: "number",
548
- min: 100,
549
- max: 900,
550
- step: 100,
551
- value: element.fontWeight ?? 500,
552
- onChange: (event) => updateElement(element.id, { fontWeight: Number(event.target.value) }),
553
- className: numberFieldClassName
554
- }
555
- )), /* @__PURE__ */ React3__default.default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "\u5BF9\u9F50", /* @__PURE__ */ React3__default.default.createElement(
556
- "select",
557
- {
558
- value: element.align || "left",
559
- onChange: (event) => updateElement(element.id, { align: event.target.value }),
560
- className: numberFieldClassName
561
- },
562
- /* @__PURE__ */ React3__default.default.createElement("option", { value: "left" }, "left"),
563
- /* @__PURE__ */ React3__default.default.createElement("option", { value: "center" }, "center"),
564
- /* @__PURE__ */ React3__default.default.createElement("option", { value: "right" }, "right")
565
- )), /* @__PURE__ */ React3__default.default.createElement("label", { className: "grid gap-1 text-xs text-slate-600 sm:col-span-2" }, "\u5B57\u4F53", /* @__PURE__ */ React3__default.default.createElement(
566
- "input",
567
- {
568
- type: "text",
569
- value: element.fontFamily || "",
570
- onChange: (event) => updateElement(element.id, { fontFamily: event.target.value }),
571
- placeholder: "inherit / serif / sans-serif / PingFang SC",
572
- className: numberFieldClassName
573
- }
574
- )), /* @__PURE__ */ React3__default.default.createElement("label", { className: "grid gap-1 text-xs text-slate-600 sm:col-span-2" }, "\u6587\u5B57\u989C\u8272", /* @__PURE__ */ React3__default.default.createElement("div", { className: "grid grid-cols-[64px_1fr] gap-2" }, /* @__PURE__ */ React3__default.default.createElement(
575
- "input",
576
- {
577
- type: "color",
578
- value: element.color || "#ffffff",
579
- onChange: (event) => updateElement(element.id, { color: event.target.value }),
580
- className: "h-10 rounded-lg border border-slate-300 bg-white p-1"
581
- }
582
- ), /* @__PURE__ */ React3__default.default.createElement(
583
- "input",
584
- {
585
- type: "text",
586
- value: element.color || "#ffffff",
587
- onChange: (event) => updateElement(element.id, { color: event.target.value }),
588
- className: numberFieldClassName
589
- }
590
- ))))) : /* @__PURE__ */ React3__default.default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React3__default.default.createElement(
591
- "input",
592
- {
593
- type: "url",
594
- value: element.src,
595
- onChange: (event) => updateElement(element.id, { src: event.target.value }),
596
- 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"
597
- }
598
- ), /* @__PURE__ */ React3__default.default.createElement("div", { className: "grid gap-2 sm:grid-cols-2" }, /* @__PURE__ */ React3__default.default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "X(%)", /* @__PURE__ */ React3__default.default.createElement(
599
- "input",
600
- {
601
- type: "number",
602
- value: element.x,
603
- onChange: (event) => updateElement(element.id, { x: Number(event.target.value) }),
604
- className: numberFieldClassName
605
- }
606
- )), /* @__PURE__ */ React3__default.default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "Y(%)", /* @__PURE__ */ React3__default.default.createElement(
607
- "input",
608
- {
609
- type: "number",
610
- value: element.y,
611
- onChange: (event) => updateElement(element.id, { y: Number(event.target.value) }),
612
- className: numberFieldClassName
613
- }
614
- )), /* @__PURE__ */ React3__default.default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "\u5BBD\u5EA6(%)", /* @__PURE__ */ React3__default.default.createElement(
615
- "input",
616
- {
617
- type: "number",
618
- value: element.width ?? 60,
619
- onChange: (event) => updateElement(element.id, { width: Number(event.target.value) }),
620
- className: numberFieldClassName
621
- }
622
- )), /* @__PURE__ */ React3__default.default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "\u9AD8\u5EA6(%)", /* @__PURE__ */ React3__default.default.createElement(
623
- "input",
624
- {
625
- type: "number",
626
- value: element.height ?? 40,
627
- onChange: (event) => updateElement(element.id, { height: Number(event.target.value) }),
628
- className: numberFieldClassName
629
- }
630
- )), /* @__PURE__ */ React3__default.default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "\u5706\u89D2(px)", /* @__PURE__ */ React3__default.default.createElement(
631
- "input",
632
- {
633
- type: "number",
634
- value: element.borderRadius ?? 0,
635
- onChange: (event) => updateElement(element.id, { borderRadius: Number(event.target.value) }),
636
- className: numberFieldClassName
637
- }
638
- )), /* @__PURE__ */ React3__default.default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "\u586B\u5145", /* @__PURE__ */ React3__default.default.createElement(
639
- "select",
1032
+ )), /* @__PURE__ */ React4__default.default.createElement("div", { className: "grid max-h-[340px] gap-2.5 overflow-auto pr-1" }, (page?.elements ?? []).map((element) => /* @__PURE__ */ React4__default.default.createElement(
1033
+ "div",
640
1034
  {
641
- value: element.fit || "cover",
642
- onChange: (event) => updateElement(element.id, { fit: event.target.value }),
643
- className: numberFieldClassName
1035
+ key: element.id,
1036
+ className: `rounded-xl border bg-slate-50 p-3 ${selectedElementId === element.id ? "border-sky-400 ring-2 ring-sky-100" : "border-slate-200"}`
644
1037
  },
645
- /* @__PURE__ */ React3__default.default.createElement("option", { value: "cover" }, "cover"),
646
- /* @__PURE__ */ React3__default.default.createElement("option", { value: "contain" }, "contain")
647
- ))), /* @__PURE__ */ React3__default.default.createElement("label", { className: "inline-flex items-center gap-2 text-sm text-slate-700" }, /* @__PURE__ */ React3__default.default.createElement(
648
- "input",
649
- {
650
- type: "checkbox",
651
- checked: Boolean(element.isBackground),
652
- onChange: (event) => updateElement(element.id, { isBackground: event.target.checked }),
653
- className: "h-4 w-4 rounded border-slate-300 text-sky-600"
654
- }
655
- ), "\u4F5C\u4E3A\u672C\u9875\u80CC\u666F\u56FE")))))) : null);
1038
+ /* @__PURE__ */ React4__default.default.createElement("div", { className: "mb-2 flex items-center justify-between" }, /* @__PURE__ */ React4__default.default.createElement("div", { className: "text-xs font-semibold tracking-wide text-slate-500" }, element.type.toUpperCase()), /* @__PURE__ */ React4__default.default.createElement(
1039
+ "button",
1040
+ {
1041
+ type: "button",
1042
+ onClick: () => removeElement(element.id),
1043
+ className: "rounded-md border border-rose-300 bg-rose-50 px-2 py-1 text-xs font-medium text-rose-700"
1044
+ },
1045
+ "\u5220\u9664"
1046
+ )),
1047
+ element.type === "text" ? /* @__PURE__ */ React4__default.default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React4__default.default.createElement(
1048
+ "textarea",
1049
+ {
1050
+ value: element.content,
1051
+ onChange: (event) => updateElement(element.id, { content: event.target.value }),
1052
+ rows: 3,
1053
+ 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"
1054
+ }
1055
+ ), /* @__PURE__ */ React4__default.default.createElement("div", { className: "grid gap-2 sm:grid-cols-2" }, /* @__PURE__ */ React4__default.default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "X(%)", /* @__PURE__ */ React4__default.default.createElement(
1056
+ "input",
1057
+ {
1058
+ type: "number",
1059
+ value: element.x,
1060
+ onChange: (event) => updateElement(element.id, { x: Number(event.target.value) }),
1061
+ className: numberFieldClassName
1062
+ }
1063
+ )), /* @__PURE__ */ React4__default.default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "Y(%)", /* @__PURE__ */ React4__default.default.createElement(
1064
+ "input",
1065
+ {
1066
+ type: "number",
1067
+ value: element.y,
1068
+ onChange: (event) => updateElement(element.id, { y: Number(event.target.value) }),
1069
+ className: numberFieldClassName
1070
+ }
1071
+ )), /* @__PURE__ */ React4__default.default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "\u5BBD\u5EA6(%)", /* @__PURE__ */ React4__default.default.createElement(
1072
+ "input",
1073
+ {
1074
+ type: "number",
1075
+ value: element.width ?? 70,
1076
+ onChange: (event) => updateElement(element.id, { width: Number(event.target.value) }),
1077
+ className: numberFieldClassName
1078
+ }
1079
+ )), /* @__PURE__ */ React4__default.default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "\u5B57\u53F7(px)", /* @__PURE__ */ React4__default.default.createElement(
1080
+ "input",
1081
+ {
1082
+ type: "number",
1083
+ value: element.fontSize ?? 18,
1084
+ onChange: (event) => updateElement(element.id, { fontSize: Number(event.target.value) }),
1085
+ className: numberFieldClassName
1086
+ }
1087
+ )), /* @__PURE__ */ React4__default.default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "\u5B57\u91CD", /* @__PURE__ */ React4__default.default.createElement(
1088
+ "input",
1089
+ {
1090
+ type: "number",
1091
+ min: 100,
1092
+ max: 900,
1093
+ step: 100,
1094
+ value: element.fontWeight ?? 500,
1095
+ onChange: (event) => updateElement(element.id, { fontWeight: Number(event.target.value) }),
1096
+ className: numberFieldClassName
1097
+ }
1098
+ )), /* @__PURE__ */ React4__default.default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "\u5BF9\u9F50", /* @__PURE__ */ React4__default.default.createElement(
1099
+ "select",
1100
+ {
1101
+ value: element.align || "left",
1102
+ onChange: (event) => updateElement(element.id, { align: event.target.value }),
1103
+ className: numberFieldClassName
1104
+ },
1105
+ /* @__PURE__ */ React4__default.default.createElement("option", { value: "left" }, "left"),
1106
+ /* @__PURE__ */ React4__default.default.createElement("option", { value: "center" }, "center"),
1107
+ /* @__PURE__ */ React4__default.default.createElement("option", { value: "right" }, "right")
1108
+ )), /* @__PURE__ */ React4__default.default.createElement("label", { className: "grid gap-1 text-xs text-slate-600 sm:col-span-2" }, "\u5B57\u4F53", /* @__PURE__ */ React4__default.default.createElement(
1109
+ "input",
1110
+ {
1111
+ type: "text",
1112
+ value: element.fontFamily || "",
1113
+ onChange: (event) => updateElement(element.id, { fontFamily: event.target.value }),
1114
+ placeholder: "inherit / serif / sans-serif / PingFang SC",
1115
+ className: numberFieldClassName
1116
+ }
1117
+ )), /* @__PURE__ */ React4__default.default.createElement("label", { className: "grid gap-1 text-xs text-slate-600 sm:col-span-2" }, "\u6587\u5B57\u989C\u8272", /* @__PURE__ */ React4__default.default.createElement("div", { className: "grid grid-cols-[64px_1fr] gap-2" }, /* @__PURE__ */ React4__default.default.createElement(
1118
+ "input",
1119
+ {
1120
+ type: "color",
1121
+ value: element.color || "#ffffff",
1122
+ onChange: (event) => updateElement(element.id, { color: event.target.value }),
1123
+ className: "h-10 rounded-lg border border-slate-300 bg-white p-1"
1124
+ }
1125
+ ), /* @__PURE__ */ React4__default.default.createElement(
1126
+ "input",
1127
+ {
1128
+ type: "text",
1129
+ value: element.color || "#ffffff",
1130
+ onChange: (event) => updateElement(element.id, { color: event.target.value }),
1131
+ className: numberFieldClassName
1132
+ }
1133
+ ))))) : /* @__PURE__ */ React4__default.default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React4__default.default.createElement(
1134
+ "input",
1135
+ {
1136
+ type: "url",
1137
+ value: element.src,
1138
+ onChange: (event) => updateElement(element.id, { src: event.target.value }),
1139
+ 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"
1140
+ }
1141
+ ), /* @__PURE__ */ React4__default.default.createElement("div", { className: "grid gap-2 sm:grid-cols-2" }, /* @__PURE__ */ React4__default.default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "X(%)", /* @__PURE__ */ React4__default.default.createElement(
1142
+ "input",
1143
+ {
1144
+ type: "number",
1145
+ value: element.x,
1146
+ onChange: (event) => updateElement(element.id, { x: Number(event.target.value) }),
1147
+ className: numberFieldClassName
1148
+ }
1149
+ )), /* @__PURE__ */ React4__default.default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "Y(%)", /* @__PURE__ */ React4__default.default.createElement(
1150
+ "input",
1151
+ {
1152
+ type: "number",
1153
+ value: element.y,
1154
+ onChange: (event) => updateElement(element.id, { y: Number(event.target.value) }),
1155
+ className: numberFieldClassName
1156
+ }
1157
+ )), /* @__PURE__ */ React4__default.default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "\u5BBD\u5EA6(%)", /* @__PURE__ */ React4__default.default.createElement(
1158
+ "input",
1159
+ {
1160
+ type: "number",
1161
+ value: element.width ?? 60,
1162
+ onChange: (event) => updateElement(element.id, { width: Number(event.target.value) }),
1163
+ className: numberFieldClassName
1164
+ }
1165
+ )), /* @__PURE__ */ React4__default.default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "\u9AD8\u5EA6(%)", /* @__PURE__ */ React4__default.default.createElement(
1166
+ "input",
1167
+ {
1168
+ type: "number",
1169
+ value: element.height ?? 40,
1170
+ onChange: (event) => updateElement(element.id, { height: Number(event.target.value) }),
1171
+ className: numberFieldClassName
1172
+ }
1173
+ )), /* @__PURE__ */ React4__default.default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "\u5706\u89D2(px)", /* @__PURE__ */ React4__default.default.createElement(
1174
+ "input",
1175
+ {
1176
+ type: "number",
1177
+ value: element.borderRadius ?? 0,
1178
+ onChange: (event) => updateElement(element.id, { borderRadius: Number(event.target.value) }),
1179
+ className: numberFieldClassName
1180
+ }
1181
+ )), /* @__PURE__ */ React4__default.default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "\u586B\u5145", /* @__PURE__ */ React4__default.default.createElement(
1182
+ "select",
1183
+ {
1184
+ value: element.fit || "cover",
1185
+ onChange: (event) => updateElement(element.id, { fit: event.target.value }),
1186
+ className: numberFieldClassName
1187
+ },
1188
+ /* @__PURE__ */ React4__default.default.createElement("option", { value: "cover" }, "cover"),
1189
+ /* @__PURE__ */ React4__default.default.createElement("option", { value: "contain" }, "contain")
1190
+ ))), /* @__PURE__ */ React4__default.default.createElement("label", { className: "inline-flex items-center gap-2 text-sm text-slate-700" }, /* @__PURE__ */ React4__default.default.createElement(
1191
+ "input",
1192
+ {
1193
+ type: "checkbox",
1194
+ checked: Boolean(element.isBackground),
1195
+ onChange: (event) => updateElement(element.id, { isBackground: event.target.checked }),
1196
+ className: "h-4 w-4 rounded border-slate-300 text-sky-600"
1197
+ }
1198
+ ), "\u4F5C\u4E3A\u672C\u9875\u80CC\u666F\u56FE"))
1199
+ )))) : null);
656
1200
  };
657
1201
 
658
1202
  // src/festivalCard/components/FestivalCardStudio.tsx
@@ -662,8 +1206,55 @@ var FestivalCardStudio = ({ initialConfig, fetchConfig, onSave }) => {
662
1206
  fetchConfig,
663
1207
  onSave
664
1208
  });
665
- if (loading) return /* @__PURE__ */ React3__default.default.createElement("div", null, "\u52A0\u8F7D\u4E2D...");
666
- return /* @__PURE__ */ React3__default.default.createElement("div", { className: "grid items-start gap-4 lg:grid-cols-[1.45fr_1fr]" }, /* @__PURE__ */ React3__default.default.createElement(FestivalCardBook3D, { config, className: "h-full" }), /* @__PURE__ */ React3__default.default.createElement("div", { className: "lg:sticky lg:top-4" }, /* @__PURE__ */ React3__default.default.createElement(FestivalCardConfigEditor, { value: config, onChange: setConfig }), onSave ? /* @__PURE__ */ React3__default.default.createElement(
1209
+ const [activePageIndex, setActivePageIndex] = React4.useState(0);
1210
+ const [selectedElementId, setSelectedElementId] = React4.useState(null);
1211
+ React4.useEffect(() => {
1212
+ if (config.pages.length === 0) return;
1213
+ if (activePageIndex <= config.pages.length - 1) return;
1214
+ setActivePageIndex(config.pages.length - 1);
1215
+ }, [activePageIndex, config.pages.length]);
1216
+ const updateElementByPreview = (pageIndex, elementId, patch) => {
1217
+ setConfig((prev) => ({
1218
+ ...prev,
1219
+ pages: prev.pages.map(
1220
+ (page, index) => index === pageIndex ? {
1221
+ ...page,
1222
+ elements: page.elements.map(
1223
+ (element) => element.id === elementId ? { ...element, ...patch } : element
1224
+ )
1225
+ } : page
1226
+ )
1227
+ }));
1228
+ };
1229
+ if (loading) return /* @__PURE__ */ React4__default.default.createElement("div", null, "\u52A0\u8F7D\u4E2D...");
1230
+ return /* @__PURE__ */ React4__default.default.createElement("div", { className: "grid items-start gap-4 lg:grid-cols-[1.45fr_1fr]" }, /* @__PURE__ */ React4__default.default.createElement(
1231
+ FestivalCardBook3D,
1232
+ {
1233
+ config,
1234
+ className: "h-full",
1235
+ editable: true,
1236
+ currentPage: activePageIndex,
1237
+ onCurrentPageChange: (index) => {
1238
+ setActivePageIndex(index);
1239
+ setSelectedElementId(null);
1240
+ },
1241
+ selectedElementId,
1242
+ onSelectedElementChange: setSelectedElementId,
1243
+ onElementChange: updateElementByPreview
1244
+ }
1245
+ ), /* @__PURE__ */ React4__default.default.createElement("div", { className: "lg:sticky lg:top-4" }, /* @__PURE__ */ React4__default.default.createElement(
1246
+ FestivalCardConfigEditor,
1247
+ {
1248
+ value: config,
1249
+ onChange: setConfig,
1250
+ activePageIndex,
1251
+ onActivePageIndexChange: (index) => {
1252
+ setActivePageIndex(index);
1253
+ setSelectedElementId(null);
1254
+ },
1255
+ selectedElementId
1256
+ }
1257
+ ), onSave ? /* @__PURE__ */ React4__default.default.createElement(
667
1258
  "button",
668
1259
  {
669
1260
  type: "button",
@@ -681,8 +1272,8 @@ var FestivalCardConfigPage = ({
681
1272
  cardId,
682
1273
  mainPagePath = "/festivalCard"
683
1274
  }) => {
684
- const [list, setList] = React3.useState([]);
685
- const [selectedId, setSelectedId] = React3.useState(cardId || "default-festival-card");
1275
+ const [list, setList] = React4.useState([]);
1276
+ const [selectedId, setSelectedId] = React4.useState(cardId || "default-festival-card");
686
1277
  const parseListResponse2 = (data) => {
687
1278
  if (!data || typeof data !== "object") return [];
688
1279
  const payload = data.data;
@@ -695,7 +1286,7 @@ var FestivalCardConfigPage = ({
695
1286
  if (!payload || typeof payload !== "object") return normalizeFestivalCardConfig();
696
1287
  return normalizeFestivalCardConfig(payload);
697
1288
  };
698
- const reloadList = React3.useCallback(async () => {
1289
+ const reloadList = React4.useCallback(async () => {
699
1290
  try {
700
1291
  const response = await fetch(apiBase, { cache: "no-store" });
701
1292
  if (!response.ok) throw new Error(`\u52A0\u8F7D\u5361\u7247\u5217\u8868\u5931\u8D25: ${response.status}`);
@@ -718,7 +1309,7 @@ var FestivalCardConfigPage = ({
718
1309
  window.alert(error.message || "\u52A0\u8F7D\u5361\u7247\u5217\u8868\u5931\u8D25");
719
1310
  }
720
1311
  }, [apiBase]);
721
- React3.useEffect(() => {
1312
+ React4.useEffect(() => {
722
1313
  void reloadList();
723
1314
  }, [reloadList]);
724
1315
  const fetchConfig = async () => {
@@ -768,16 +1359,16 @@ var FestivalCardConfigPage = ({
768
1359
  window.alert(error.message || "\u521B\u5EFA\u5931\u8D25");
769
1360
  }
770
1361
  };
771
- const mainLink = React3.useMemo(() => `${mainPagePath}?cardId=${encodeURIComponent(selectedId)}`, [mainPagePath, selectedId]);
772
- return /* @__PURE__ */ React3__default.default.createElement("div", { className: "grid gap-4" }, /* @__PURE__ */ React3__default.default.createElement("div", { className: "flex flex-wrap items-center gap-2 rounded-xl border border-slate-800 bg-slate-900/50 p-3" }, /* @__PURE__ */ React3__default.default.createElement(
1362
+ const mainLink = React4.useMemo(() => `${mainPagePath}?cardId=${encodeURIComponent(selectedId)}`, [mainPagePath, selectedId]);
1363
+ return /* @__PURE__ */ React4__default.default.createElement("div", { className: "grid gap-4" }, /* @__PURE__ */ React4__default.default.createElement("div", { className: "flex flex-wrap items-center gap-2 rounded-xl border border-slate-800 bg-slate-900/50 p-3" }, /* @__PURE__ */ React4__default.default.createElement(
773
1364
  "select",
774
1365
  {
775
1366
  value: selectedId,
776
1367
  onChange: (event) => setSelectedId(event.target.value),
777
1368
  className: "min-w-[240px] 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"
778
1369
  },
779
- list.map((item) => /* @__PURE__ */ React3__default.default.createElement("option", { key: item.id, value: item.id }, item.name || item.id))
780
- ), /* @__PURE__ */ React3__default.default.createElement("button", { type: "button", onClick: () => void createNew(), className: "rounded-lg bg-slate-900 px-3 py-2 text-sm font-medium text-white" }, "\u65B0\u5EFA\u5361\u7247"), /* @__PURE__ */ React3__default.default.createElement("a", { href: mainLink, className: "rounded-lg border border-sky-200 bg-sky-50 px-3 py-2 text-sm text-sky-700" }, "\u6253\u5F00\u4E3B\u9875\u9762")), /* @__PURE__ */ React3__default.default.createElement(FestivalCardStudio, { fetchConfig, onSave: saveConfig }));
1370
+ list.map((item) => /* @__PURE__ */ React4__default.default.createElement("option", { key: item.id, value: item.id }, item.name || item.id))
1371
+ ), /* @__PURE__ */ React4__default.default.createElement("button", { type: "button", onClick: () => void createNew(), className: "rounded-lg bg-slate-900 px-3 py-2 text-sm font-medium text-white" }, "\u65B0\u5EFA\u5361\u7247"), /* @__PURE__ */ React4__default.default.createElement("a", { href: mainLink, className: "rounded-lg border border-sky-200 bg-sky-50 px-3 py-2 text-sm text-sky-700" }, "\u6253\u5F00\u4E3B\u9875\u9762")), /* @__PURE__ */ React4__default.default.createElement(FestivalCardStudio, { fetchConfig, onSave: saveConfig }));
781
1372
  };
782
1373
  var isSummary = (value) => {
783
1374
  if (!value || typeof value !== "object") return false;
@@ -800,10 +1391,10 @@ var FestivalCardManagedPage = ({
800
1391
  apiBase = "/api/festivalCard",
801
1392
  cardId
802
1393
  }) => {
803
- const [currentCardId, setCurrentCardId] = React3.useState(cardId || "");
804
- const [config, setConfig] = React3.useState(null);
805
- const [loading, setLoading] = React3.useState(true);
806
- React3.useEffect(() => {
1394
+ const [currentCardId, setCurrentCardId] = React4.useState(cardId || "");
1395
+ const [config, setConfig] = React4.useState(null);
1396
+ const [loading, setLoading] = React4.useState(true);
1397
+ React4.useEffect(() => {
807
1398
  const fetchList = async () => {
808
1399
  const response = await fetch(apiBase, { cache: "no-store" });
809
1400
  const data = await response.json();
@@ -815,12 +1406,12 @@ var FestivalCardManagedPage = ({
815
1406
  };
816
1407
  void fetchList();
817
1408
  }, [apiBase, currentCardId]);
818
- React3.useEffect(() => {
1409
+ React4.useEffect(() => {
819
1410
  if (!currentCardId) return;
820
1411
  setLoading(true);
821
1412
  void fetch(`${apiBase}/${encodeURIComponent(currentCardId)}`, { cache: "no-store" }).then((res) => res.json()).then((data) => setConfig(parseConfigResponse(data))).finally(() => setLoading(false));
822
1413
  }, [apiBase, currentCardId]);
823
- return /* @__PURE__ */ React3__default.default.createElement("div", null, loading || !config ? /* @__PURE__ */ React3__default.default.createElement("div", { className: "py-12 text-center text-slate-400" }, "\u52A0\u8F7D\u4E2D...") : /* @__PURE__ */ React3__default.default.createElement(FestivalCardBook3D, { config }));
1414
+ return /* @__PURE__ */ React4__default.default.createElement("div", null, loading || !config ? /* @__PURE__ */ React4__default.default.createElement("div", { className: "py-12 text-center text-slate-400" }, "\u52A0\u8F7D\u4E2D...") : /* @__PURE__ */ React4__default.default.createElement(FestivalCardBook3D, { config }));
824
1415
  };
825
1416
 
826
1417
  // src/festivalCard/server/db.ts