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