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,10 +1,12 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
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
|
|
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] =
|
|
187
|
+
const [config, setConfig] = React4.useState(
|
|
186
188
|
() => normalizeFestivalCardConfig(options?.initialConfig || DEFAULT_FESTIVAL_CARD_CONFIG)
|
|
187
189
|
);
|
|
188
|
-
const [loading, setLoading] =
|
|
189
|
-
const [saving, setSaving] =
|
|
190
|
-
|
|
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 =
|
|
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
|
|
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__ */
|
|
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__ */
|
|
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
|
|
271
|
-
|
|
272
|
-
|
|
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 =
|
|
275
|
-
|
|
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
|
-
|
|
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__ */
|
|
287
|
-
foregroundElements.map(
|
|
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
|
|
293
|
-
const
|
|
294
|
-
|
|
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
|
-
|
|
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__ */
|
|
309
|
-
|
|
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__ */
|
|
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__ */
|
|
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,26 +881,25 @@ var createImageElement = (pageIndex) => ({
|
|
|
359
881
|
fit: "cover",
|
|
360
882
|
borderRadius: 12
|
|
361
883
|
});
|
|
362
|
-
var FestivalCardConfigEditor = ({
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
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
|
+
};
|
|
368
900
|
const page = value.pages[activePageIndex];
|
|
369
901
|
const canEditPage = Boolean(page);
|
|
370
|
-
const pageOptions =
|
|
371
|
-
const backgroundElement = React3.useMemo(
|
|
372
|
-
() => page?.elements.find(
|
|
373
|
-
(element) => element.type === "image" && Boolean(element.isBackground)
|
|
374
|
-
),
|
|
375
|
-
[page]
|
|
376
|
-
);
|
|
377
|
-
const foregroundElements = React3.useMemo(
|
|
378
|
-
() => (page?.elements ?? []).filter((element) => !(element.type === "image" && element.isBackground)),
|
|
379
|
-
[page]
|
|
380
|
-
);
|
|
381
|
-
const clampPercent = (valueToClamp) => Math.max(0, Math.min(100, Number.isFinite(valueToClamp) ? valueToClamp : 0));
|
|
902
|
+
const pageOptions = React4.useMemo(() => value.pages.map((_, index) => index), [value.pages]);
|
|
382
903
|
const handlePageCountChange = (nextRaw) => {
|
|
383
904
|
const next = Number.isFinite(nextRaw) ? Math.max(1, Math.min(12, Math.floor(nextRaw))) : value.pages.length;
|
|
384
905
|
const resized = resizeFestivalCardPages(value, next);
|
|
@@ -415,40 +936,8 @@ var FestivalCardConfigEditor = ({ value, onChange }) => {
|
|
|
415
936
|
pages: value.pages.map((p, index) => index === activePageIndex ? { ...p, ...patch } : p)
|
|
416
937
|
});
|
|
417
938
|
};
|
|
418
|
-
const moveElementWithPointer = (elementId, clientX, clientY, rect) => {
|
|
419
|
-
const bounds = rect || previewRef.current?.getBoundingClientRect();
|
|
420
|
-
if (!bounds || bounds.width <= 0 || bounds.height <= 0) return;
|
|
421
|
-
const x = clampPercent((clientX - bounds.left) / bounds.width * 100);
|
|
422
|
-
const y = clampPercent((clientY - bounds.top) / bounds.height * 100);
|
|
423
|
-
updateElement(elementId, { x, y });
|
|
424
|
-
};
|
|
425
|
-
const handleElementPointerDown = (event, elementId) => {
|
|
426
|
-
if (!previewRef.current) return;
|
|
427
|
-
event.preventDefault();
|
|
428
|
-
const rect = previewRef.current.getBoundingClientRect();
|
|
429
|
-
dragStateRef.current = {
|
|
430
|
-
pointerId: event.pointerId,
|
|
431
|
-
elementId,
|
|
432
|
-
rect
|
|
433
|
-
};
|
|
434
|
-
event.currentTarget.setPointerCapture(event.pointerId);
|
|
435
|
-
setActiveElementId(elementId);
|
|
436
|
-
setDraggingElementId(elementId);
|
|
437
|
-
moveElementWithPointer(elementId, event.clientX, event.clientY, rect);
|
|
438
|
-
};
|
|
439
|
-
const handlePreviewPointerMove = (event) => {
|
|
440
|
-
const dragState = dragStateRef.current;
|
|
441
|
-
if (!dragState || dragState.pointerId !== event.pointerId) return;
|
|
442
|
-
moveElementWithPointer(dragState.elementId, event.clientX, event.clientY, dragState.rect);
|
|
443
|
-
};
|
|
444
|
-
const endPointerDrag = (event) => {
|
|
445
|
-
const dragState = dragStateRef.current;
|
|
446
|
-
if (!dragState || dragState.pointerId !== event.pointerId) return;
|
|
447
|
-
dragStateRef.current = null;
|
|
448
|
-
setDraggingElementId(null);
|
|
449
|
-
};
|
|
450
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";
|
|
451
|
-
return /* @__PURE__ */
|
|
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(
|
|
452
941
|
"input",
|
|
453
942
|
{
|
|
454
943
|
type: "number",
|
|
@@ -458,7 +947,7 @@ var FestivalCardConfigEditor = ({ value, onChange }) => {
|
|
|
458
947
|
onChange: (event) => handlePageCountChange(Number(event.target.value)),
|
|
459
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"
|
|
460
949
|
}
|
|
461
|
-
)), /* @__PURE__ */
|
|
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(
|
|
462
951
|
"input",
|
|
463
952
|
{
|
|
464
953
|
type: "url",
|
|
@@ -472,7 +961,7 @@ var FestivalCardConfigEditor = ({ value, onChange }) => {
|
|
|
472
961
|
}),
|
|
473
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"
|
|
474
963
|
}
|
|
475
|
-
)), /* @__PURE__ */
|
|
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(
|
|
476
965
|
"input",
|
|
477
966
|
{
|
|
478
967
|
type: "text",
|
|
@@ -480,7 +969,7 @@ var FestivalCardConfigEditor = ({ value, onChange }) => {
|
|
|
480
969
|
onChange: (event) => updatePage({ title: event.target.value }),
|
|
481
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"
|
|
482
971
|
}
|
|
483
|
-
)), /* @__PURE__ */
|
|
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(
|
|
484
973
|
"input",
|
|
485
974
|
{
|
|
486
975
|
type: "color",
|
|
@@ -493,7 +982,7 @@ var FestivalCardConfigEditor = ({ value, onChange }) => {
|
|
|
493
982
|
}),
|
|
494
983
|
className: "h-10 w-full rounded-lg border border-slate-300 bg-white p-1"
|
|
495
984
|
}
|
|
496
|
-
)), /* @__PURE__ */
|
|
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(
|
|
497
986
|
"input",
|
|
498
987
|
{
|
|
499
988
|
type: "url",
|
|
@@ -506,15 +995,15 @@ var FestivalCardConfigEditor = ({ value, onChange }) => {
|
|
|
506
995
|
}),
|
|
507
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"
|
|
508
997
|
}
|
|
509
|
-
))), /* @__PURE__ */
|
|
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(
|
|
510
999
|
"select",
|
|
511
1000
|
{
|
|
512
1001
|
value: activePageIndex,
|
|
513
1002
|
onChange: (event) => setActivePageIndex(Number(event.target.value)),
|
|
514
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"
|
|
515
1004
|
},
|
|
516
|
-
pageOptions.map((index) => /* @__PURE__ */
|
|
517
|
-
))), canEditPage ? /* @__PURE__ */
|
|
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(
|
|
518
1007
|
"button",
|
|
519
1008
|
{
|
|
520
1009
|
type: "button",
|
|
@@ -527,7 +1016,7 @@ var FestivalCardConfigEditor = ({ value, onChange }) => {
|
|
|
527
1016
|
className: "rounded-lg bg-slate-900 px-3 py-2 text-sm font-medium text-white"
|
|
528
1017
|
},
|
|
529
1018
|
"+ \u6587\u5B57"
|
|
530
|
-
), /* @__PURE__ */
|
|
1019
|
+
), /* @__PURE__ */ React4__default.default.createElement(
|
|
531
1020
|
"button",
|
|
532
1021
|
{
|
|
533
1022
|
type: "button",
|
|
@@ -540,233 +1029,174 @@ var FestivalCardConfigEditor = ({ value, onChange }) => {
|
|
|
540
1029
|
className: "rounded-lg bg-sky-600 px-3 py-2 text-sm font-medium text-white"
|
|
541
1030
|
},
|
|
542
1031
|
"+ \u56FE\u7247"
|
|
543
|
-
)), /* @__PURE__ */
|
|
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(
|
|
544
1033
|
"div",
|
|
545
1034
|
{
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
onPointerUp: endPointerDrag,
|
|
549
|
-
onPointerCancel: endPointerDrag,
|
|
550
|
-
className: "relative aspect-[3/4] w-full touch-none overflow-hidden rounded-xl border border-slate-300 bg-slate-900",
|
|
551
|
-
style: {
|
|
552
|
-
backgroundColor: page?.background?.color || "#0f172a",
|
|
553
|
-
backgroundImage: backgroundElement ? `url(${backgroundElement.src})` : page?.background?.image ? `url(${page.background.image})` : void 0,
|
|
554
|
-
backgroundSize: "cover",
|
|
555
|
-
backgroundPosition: "center"
|
|
556
|
-
}
|
|
557
|
-
},
|
|
558
|
-
/* @__PURE__ */ React3__default.default.createElement("div", { className: "absolute inset-0 bg-slate-950/20" }),
|
|
559
|
-
foregroundElements.map((element) => {
|
|
560
|
-
const isActive = activeElementId === element.id;
|
|
561
|
-
const isDragging = draggingElementId === element.id;
|
|
562
|
-
return /* @__PURE__ */ React3__default.default.createElement(
|
|
563
|
-
"div",
|
|
564
|
-
{
|
|
565
|
-
key: element.id,
|
|
566
|
-
role: "button",
|
|
567
|
-
tabIndex: 0,
|
|
568
|
-
onPointerDown: (event) => handleElementPointerDown(event, element.id),
|
|
569
|
-
onClick: () => setActiveElementId(element.id),
|
|
570
|
-
className: `absolute select-none rounded-md ${isDragging ? "cursor-grabbing" : "cursor-grab"} ${isActive ? "ring-2 ring-sky-300" : "ring-1 ring-white/40"}`,
|
|
571
|
-
style: {
|
|
572
|
-
left: `${element.x}%`,
|
|
573
|
-
top: `${element.y}%`,
|
|
574
|
-
width: `${element.width ?? 70}%`,
|
|
575
|
-
height: element.height ? `${element.height}%` : void 0,
|
|
576
|
-
transform: "translate(-50%, -50%)",
|
|
577
|
-
zIndex: isActive ? 4 : 2
|
|
578
|
-
}
|
|
579
|
-
},
|
|
580
|
-
element.type === "text" ? /* @__PURE__ */ React3__default.default.createElement(
|
|
581
|
-
"div",
|
|
582
|
-
{
|
|
583
|
-
className: "w-full rounded-md bg-black/20 px-2 py-1",
|
|
584
|
-
style: {
|
|
585
|
-
color: element.color || "#f8fafc",
|
|
586
|
-
fontSize: element.fontSize || 18,
|
|
587
|
-
fontWeight: element.fontWeight || 500,
|
|
588
|
-
fontFamily: element.fontFamily || "inherit",
|
|
589
|
-
textAlign: element.align || "left",
|
|
590
|
-
lineHeight: 1.35,
|
|
591
|
-
whiteSpace: "pre-wrap"
|
|
592
|
-
}
|
|
593
|
-
},
|
|
594
|
-
element.content || "\u6587\u672C"
|
|
595
|
-
) : /* @__PURE__ */ React3__default.default.createElement(
|
|
596
|
-
"img",
|
|
597
|
-
{
|
|
598
|
-
src: element.src,
|
|
599
|
-
alt: element.alt || "festival-card-image",
|
|
600
|
-
draggable: false,
|
|
601
|
-
className: "h-full w-full pointer-events-none",
|
|
602
|
-
style: {
|
|
603
|
-
objectFit: element.fit || "cover",
|
|
604
|
-
borderRadius: element.borderRadius || 0
|
|
605
|
-
}
|
|
606
|
-
}
|
|
607
|
-
)
|
|
608
|
-
);
|
|
609
|
-
})
|
|
610
|
-
)), /* @__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(
|
|
611
|
-
"button",
|
|
612
|
-
{
|
|
613
|
-
type: "button",
|
|
614
|
-
onClick: () => removeElement(element.id),
|
|
615
|
-
className: "rounded-md border border-rose-300 bg-rose-50 px-2 py-1 text-xs font-medium text-rose-700"
|
|
616
|
-
},
|
|
617
|
-
"\u5220\u9664"
|
|
618
|
-
)), element.type === "text" ? /* @__PURE__ */ React3__default.default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React3__default.default.createElement(
|
|
619
|
-
"textarea",
|
|
620
|
-
{
|
|
621
|
-
value: element.content,
|
|
622
|
-
onChange: (event) => updateElement(element.id, { content: event.target.value }),
|
|
623
|
-
rows: 3,
|
|
624
|
-
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"
|
|
625
|
-
}
|
|
626
|
-
), /* @__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(
|
|
627
|
-
"input",
|
|
628
|
-
{
|
|
629
|
-
type: "number",
|
|
630
|
-
value: element.x,
|
|
631
|
-
onChange: (event) => updateElement(element.id, { x: Number(event.target.value) }),
|
|
632
|
-
className: numberFieldClassName
|
|
633
|
-
}
|
|
634
|
-
)), /* @__PURE__ */ React3__default.default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "Y(%)", /* @__PURE__ */ React3__default.default.createElement(
|
|
635
|
-
"input",
|
|
636
|
-
{
|
|
637
|
-
type: "number",
|
|
638
|
-
value: element.y,
|
|
639
|
-
onChange: (event) => updateElement(element.id, { y: Number(event.target.value) }),
|
|
640
|
-
className: numberFieldClassName
|
|
641
|
-
}
|
|
642
|
-
)), /* @__PURE__ */ React3__default.default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "\u5BBD\u5EA6(%)", /* @__PURE__ */ React3__default.default.createElement(
|
|
643
|
-
"input",
|
|
644
|
-
{
|
|
645
|
-
type: "number",
|
|
646
|
-
value: element.width ?? 70,
|
|
647
|
-
onChange: (event) => updateElement(element.id, { width: Number(event.target.value) }),
|
|
648
|
-
className: numberFieldClassName
|
|
649
|
-
}
|
|
650
|
-
)), /* @__PURE__ */ React3__default.default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "\u5B57\u53F7(px)", /* @__PURE__ */ React3__default.default.createElement(
|
|
651
|
-
"input",
|
|
652
|
-
{
|
|
653
|
-
type: "number",
|
|
654
|
-
value: element.fontSize ?? 18,
|
|
655
|
-
onChange: (event) => updateElement(element.id, { fontSize: Number(event.target.value) }),
|
|
656
|
-
className: numberFieldClassName
|
|
657
|
-
}
|
|
658
|
-
)), /* @__PURE__ */ React3__default.default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "\u5B57\u91CD", /* @__PURE__ */ React3__default.default.createElement(
|
|
659
|
-
"input",
|
|
660
|
-
{
|
|
661
|
-
type: "number",
|
|
662
|
-
min: 100,
|
|
663
|
-
max: 900,
|
|
664
|
-
step: 100,
|
|
665
|
-
value: element.fontWeight ?? 500,
|
|
666
|
-
onChange: (event) => updateElement(element.id, { fontWeight: Number(event.target.value) }),
|
|
667
|
-
className: numberFieldClassName
|
|
668
|
-
}
|
|
669
|
-
)), /* @__PURE__ */ React3__default.default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "\u5BF9\u9F50", /* @__PURE__ */ React3__default.default.createElement(
|
|
670
|
-
"select",
|
|
671
|
-
{
|
|
672
|
-
value: element.align || "left",
|
|
673
|
-
onChange: (event) => updateElement(element.id, { align: event.target.value }),
|
|
674
|
-
className: numberFieldClassName
|
|
675
|
-
},
|
|
676
|
-
/* @__PURE__ */ React3__default.default.createElement("option", { value: "left" }, "left"),
|
|
677
|
-
/* @__PURE__ */ React3__default.default.createElement("option", { value: "center" }, "center"),
|
|
678
|
-
/* @__PURE__ */ React3__default.default.createElement("option", { value: "right" }, "right")
|
|
679
|
-
)), /* @__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(
|
|
680
|
-
"input",
|
|
681
|
-
{
|
|
682
|
-
type: "text",
|
|
683
|
-
value: element.fontFamily || "",
|
|
684
|
-
onChange: (event) => updateElement(element.id, { fontFamily: event.target.value }),
|
|
685
|
-
placeholder: "inherit / serif / sans-serif / PingFang SC",
|
|
686
|
-
className: numberFieldClassName
|
|
687
|
-
}
|
|
688
|
-
)), /* @__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(
|
|
689
|
-
"input",
|
|
690
|
-
{
|
|
691
|
-
type: "color",
|
|
692
|
-
value: element.color || "#ffffff",
|
|
693
|
-
onChange: (event) => updateElement(element.id, { color: event.target.value }),
|
|
694
|
-
className: "h-10 rounded-lg border border-slate-300 bg-white p-1"
|
|
695
|
-
}
|
|
696
|
-
), /* @__PURE__ */ React3__default.default.createElement(
|
|
697
|
-
"input",
|
|
698
|
-
{
|
|
699
|
-
type: "text",
|
|
700
|
-
value: element.color || "#ffffff",
|
|
701
|
-
onChange: (event) => updateElement(element.id, { color: event.target.value }),
|
|
702
|
-
className: numberFieldClassName
|
|
703
|
-
}
|
|
704
|
-
))))) : /* @__PURE__ */ React3__default.default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React3__default.default.createElement(
|
|
705
|
-
"input",
|
|
706
|
-
{
|
|
707
|
-
type: "url",
|
|
708
|
-
value: element.src,
|
|
709
|
-
onChange: (event) => updateElement(element.id, { src: event.target.value }),
|
|
710
|
-
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"
|
|
711
|
-
}
|
|
712
|
-
), /* @__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(
|
|
713
|
-
"input",
|
|
714
|
-
{
|
|
715
|
-
type: "number",
|
|
716
|
-
value: element.x,
|
|
717
|
-
onChange: (event) => updateElement(element.id, { x: Number(event.target.value) }),
|
|
718
|
-
className: numberFieldClassName
|
|
719
|
-
}
|
|
720
|
-
)), /* @__PURE__ */ React3__default.default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "Y(%)", /* @__PURE__ */ React3__default.default.createElement(
|
|
721
|
-
"input",
|
|
722
|
-
{
|
|
723
|
-
type: "number",
|
|
724
|
-
value: element.y,
|
|
725
|
-
onChange: (event) => updateElement(element.id, { y: Number(event.target.value) }),
|
|
726
|
-
className: numberFieldClassName
|
|
727
|
-
}
|
|
728
|
-
)), /* @__PURE__ */ React3__default.default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "\u5BBD\u5EA6(%)", /* @__PURE__ */ React3__default.default.createElement(
|
|
729
|
-
"input",
|
|
730
|
-
{
|
|
731
|
-
type: "number",
|
|
732
|
-
value: element.width ?? 60,
|
|
733
|
-
onChange: (event) => updateElement(element.id, { width: Number(event.target.value) }),
|
|
734
|
-
className: numberFieldClassName
|
|
735
|
-
}
|
|
736
|
-
)), /* @__PURE__ */ React3__default.default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "\u9AD8\u5EA6(%)", /* @__PURE__ */ React3__default.default.createElement(
|
|
737
|
-
"input",
|
|
738
|
-
{
|
|
739
|
-
type: "number",
|
|
740
|
-
value: element.height ?? 40,
|
|
741
|
-
onChange: (event) => updateElement(element.id, { height: Number(event.target.value) }),
|
|
742
|
-
className: numberFieldClassName
|
|
743
|
-
}
|
|
744
|
-
)), /* @__PURE__ */ React3__default.default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "\u5706\u89D2(px)", /* @__PURE__ */ React3__default.default.createElement(
|
|
745
|
-
"input",
|
|
746
|
-
{
|
|
747
|
-
type: "number",
|
|
748
|
-
value: element.borderRadius ?? 0,
|
|
749
|
-
onChange: (event) => updateElement(element.id, { borderRadius: Number(event.target.value) }),
|
|
750
|
-
className: numberFieldClassName
|
|
751
|
-
}
|
|
752
|
-
)), /* @__PURE__ */ React3__default.default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "\u586B\u5145", /* @__PURE__ */ React3__default.default.createElement(
|
|
753
|
-
"select",
|
|
754
|
-
{
|
|
755
|
-
value: element.fit || "cover",
|
|
756
|
-
onChange: (event) => updateElement(element.id, { fit: event.target.value }),
|
|
757
|
-
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"}`
|
|
758
1037
|
},
|
|
759
|
-
/* @__PURE__ */
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
}
|
|
769
|
-
|
|
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);
|
|
770
1200
|
};
|
|
771
1201
|
|
|
772
1202
|
// src/festivalCard/components/FestivalCardStudio.tsx
|
|
@@ -776,8 +1206,55 @@ var FestivalCardStudio = ({ initialConfig, fetchConfig, onSave }) => {
|
|
|
776
1206
|
fetchConfig,
|
|
777
1207
|
onSave
|
|
778
1208
|
});
|
|
779
|
-
|
|
780
|
-
|
|
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(
|
|
781
1258
|
"button",
|
|
782
1259
|
{
|
|
783
1260
|
type: "button",
|
|
@@ -795,8 +1272,8 @@ var FestivalCardConfigPage = ({
|
|
|
795
1272
|
cardId,
|
|
796
1273
|
mainPagePath = "/festivalCard"
|
|
797
1274
|
}) => {
|
|
798
|
-
const [list, setList] =
|
|
799
|
-
const [selectedId, setSelectedId] =
|
|
1275
|
+
const [list, setList] = React4.useState([]);
|
|
1276
|
+
const [selectedId, setSelectedId] = React4.useState(cardId || "default-festival-card");
|
|
800
1277
|
const parseListResponse2 = (data) => {
|
|
801
1278
|
if (!data || typeof data !== "object") return [];
|
|
802
1279
|
const payload = data.data;
|
|
@@ -809,7 +1286,7 @@ var FestivalCardConfigPage = ({
|
|
|
809
1286
|
if (!payload || typeof payload !== "object") return normalizeFestivalCardConfig();
|
|
810
1287
|
return normalizeFestivalCardConfig(payload);
|
|
811
1288
|
};
|
|
812
|
-
const reloadList =
|
|
1289
|
+
const reloadList = React4.useCallback(async () => {
|
|
813
1290
|
try {
|
|
814
1291
|
const response = await fetch(apiBase, { cache: "no-store" });
|
|
815
1292
|
if (!response.ok) throw new Error(`\u52A0\u8F7D\u5361\u7247\u5217\u8868\u5931\u8D25: ${response.status}`);
|
|
@@ -832,7 +1309,7 @@ var FestivalCardConfigPage = ({
|
|
|
832
1309
|
window.alert(error.message || "\u52A0\u8F7D\u5361\u7247\u5217\u8868\u5931\u8D25");
|
|
833
1310
|
}
|
|
834
1311
|
}, [apiBase]);
|
|
835
|
-
|
|
1312
|
+
React4.useEffect(() => {
|
|
836
1313
|
void reloadList();
|
|
837
1314
|
}, [reloadList]);
|
|
838
1315
|
const fetchConfig = async () => {
|
|
@@ -882,16 +1359,16 @@ var FestivalCardConfigPage = ({
|
|
|
882
1359
|
window.alert(error.message || "\u521B\u5EFA\u5931\u8D25");
|
|
883
1360
|
}
|
|
884
1361
|
};
|
|
885
|
-
const mainLink =
|
|
886
|
-
return /* @__PURE__ */
|
|
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(
|
|
887
1364
|
"select",
|
|
888
1365
|
{
|
|
889
1366
|
value: selectedId,
|
|
890
1367
|
onChange: (event) => setSelectedId(event.target.value),
|
|
891
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"
|
|
892
1369
|
},
|
|
893
|
-
list.map((item) => /* @__PURE__ */
|
|
894
|
-
), /* @__PURE__ */
|
|
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 }));
|
|
895
1372
|
};
|
|
896
1373
|
var isSummary = (value) => {
|
|
897
1374
|
if (!value || typeof value !== "object") return false;
|
|
@@ -914,10 +1391,10 @@ var FestivalCardManagedPage = ({
|
|
|
914
1391
|
apiBase = "/api/festivalCard",
|
|
915
1392
|
cardId
|
|
916
1393
|
}) => {
|
|
917
|
-
const [currentCardId, setCurrentCardId] =
|
|
918
|
-
const [config, setConfig] =
|
|
919
|
-
const [loading, setLoading] =
|
|
920
|
-
|
|
1394
|
+
const [currentCardId, setCurrentCardId] = React4.useState(cardId || "");
|
|
1395
|
+
const [config, setConfig] = React4.useState(null);
|
|
1396
|
+
const [loading, setLoading] = React4.useState(true);
|
|
1397
|
+
React4.useEffect(() => {
|
|
921
1398
|
const fetchList = async () => {
|
|
922
1399
|
const response = await fetch(apiBase, { cache: "no-store" });
|
|
923
1400
|
const data = await response.json();
|
|
@@ -929,12 +1406,12 @@ var FestivalCardManagedPage = ({
|
|
|
929
1406
|
};
|
|
930
1407
|
void fetchList();
|
|
931
1408
|
}, [apiBase, currentCardId]);
|
|
932
|
-
|
|
1409
|
+
React4.useEffect(() => {
|
|
933
1410
|
if (!currentCardId) return;
|
|
934
1411
|
setLoading(true);
|
|
935
1412
|
void fetch(`${apiBase}/${encodeURIComponent(currentCardId)}`, { cache: "no-store" }).then((res) => res.json()).then((data) => setConfig(parseConfigResponse(data))).finally(() => setLoading(false));
|
|
936
1413
|
}, [apiBase, currentCardId]);
|
|
937
|
-
return /* @__PURE__ */
|
|
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 }));
|
|
938
1415
|
};
|
|
939
1416
|
|
|
940
1417
|
// src/festivalCard/server/db.ts
|