sa2kit 1.6.68 → 1.6.70

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/dist/festivalCard/core/index.d.mts +8 -0
  2. package/dist/festivalCard/core/index.d.ts +8 -0
  3. package/dist/festivalCard/core/index.js +181 -0
  4. package/dist/festivalCard/core/index.js.map +1 -0
  5. package/dist/festivalCard/core/index.mjs +177 -0
  6. package/dist/festivalCard/core/index.mjs.map +1 -0
  7. package/dist/festivalCard/index.d.mts +5 -10
  8. package/dist/festivalCard/index.d.ts +5 -10
  9. package/dist/festivalCard/index.js +710 -173
  10. package/dist/festivalCard/index.js.map +1 -1
  11. package/dist/festivalCard/index.mjs +698 -153
  12. package/dist/festivalCard/index.mjs.map +1 -1
  13. package/dist/festivalCard/miniapp/index.d.mts +12 -0
  14. package/dist/festivalCard/miniapp/index.d.ts +12 -0
  15. package/dist/festivalCard/miniapp/index.js +371 -0
  16. package/dist/festivalCard/miniapp/index.js.map +1 -0
  17. package/dist/festivalCard/miniapp/index.mjs +359 -0
  18. package/dist/festivalCard/miniapp/index.mjs.map +1 -0
  19. package/dist/festivalCard/routes/index.d.mts +44 -0
  20. package/dist/festivalCard/routes/index.d.ts +44 -0
  21. package/dist/festivalCard/routes/index.js +266 -0
  22. package/dist/festivalCard/routes/index.js.map +1 -0
  23. package/dist/festivalCard/routes/index.mjs +261 -0
  24. package/dist/festivalCard/routes/index.mjs.map +1 -0
  25. package/dist/festivalCard/server/index.d.mts +6 -0
  26. package/dist/festivalCard/server/index.d.ts +6 -0
  27. package/dist/festivalCard/server/index.js +13 -0
  28. package/dist/festivalCard/server/index.js.map +1 -0
  29. package/dist/festivalCard/server/index.mjs +10 -0
  30. package/dist/festivalCard/server/index.mjs.map +1 -0
  31. package/dist/festivalCard/web/index.d.mts +45 -0
  32. package/dist/festivalCard/web/index.d.ts +45 -0
  33. package/dist/festivalCard/web/index.js +743 -0
  34. package/dist/festivalCard/web/index.js.map +1 -0
  35. package/dist/festivalCard/web/index.mjs +726 -0
  36. package/dist/festivalCard/web/index.mjs.map +1 -0
  37. package/dist/festivalCardService-CgNBOjjO.d.mts +27 -0
  38. package/dist/festivalCardService-ClJiAP6P.d.ts +27 -0
  39. package/dist/index.d.mts +4 -1
  40. package/dist/index.d.ts +4 -1
  41. package/dist/index.js +723 -164
  42. package/dist/index.js.map +1 -1
  43. package/dist/index.mjs +711 -163
  44. package/dist/index.mjs.map +1 -1
  45. package/dist/types-COyg0XDw.d.mts +68 -0
  46. package/dist/types-COyg0XDw.d.ts +68 -0
  47. package/package.json +26 -1
@@ -1,181 +1,726 @@
1
- import React, { useRef, useEffect } from 'react';
2
- import * as THREE from 'three';
1
+ import React3, { useState, useEffect, useCallback, useMemo } from 'react';
3
2
 
4
- // src/festivalCard/components/FestivalCard3D.tsx
5
- var createSnow = (count) => {
6
- const positions = new Float32Array(count * 3);
7
- const speeds = new Float32Array(count);
8
- for (let i = 0; i < count; i++) {
9
- const i3 = i * 3;
10
- positions[i3] = (Math.random() - 0.5) * 16;
11
- positions[i3 + 1] = Math.random() * 10 + 1;
12
- positions[i3 + 2] = (Math.random() - 0.5) * 16;
13
- speeds[i] = 4e-3 + Math.random() * 0.01;
3
+ // src/festivalCard/core/defaults.ts
4
+ var DEFAULT_FESTIVAL_CARD_CONFIG = {
5
+ id: "default-festival-card",
6
+ name: "Holiday Card",
7
+ theme: "winter",
8
+ coverTitle: "Happy Holidays",
9
+ coverSubtitle: "Warm wishes for you",
10
+ background: {
11
+ colorA: "#0c1a34",
12
+ colorB: "#1f4f8a"
13
+ },
14
+ backgroundMusic: {
15
+ src: "",
16
+ loop: true,
17
+ autoPlay: false,
18
+ volume: 0.5
19
+ },
20
+ pages: [
21
+ {
22
+ id: "page-1",
23
+ title: "\u5C01\u9762",
24
+ background: { color: "#11284d" },
25
+ elements: [
26
+ {
27
+ id: "p1-text-1",
28
+ type: "text",
29
+ x: 50,
30
+ y: 20,
31
+ content: "\u65B0\u5E74\u5FEB\u4E50",
32
+ fontSize: 34,
33
+ fontWeight: 700,
34
+ align: "center",
35
+ color: "#f8fafc"
36
+ },
37
+ {
38
+ id: "p1-text-2",
39
+ type: "text",
40
+ x: 50,
41
+ y: 36,
42
+ content: "\u613F\u8FD9\u4E00\u5E74\u5E73\u5B89\u559C\u4E50",
43
+ fontSize: 18,
44
+ fontWeight: 500,
45
+ align: "center",
46
+ color: "#dbeafe"
47
+ },
48
+ {
49
+ id: "p1-image-1",
50
+ type: "image",
51
+ x: 50,
52
+ y: 68,
53
+ width: 72,
54
+ height: 42,
55
+ src: "https://images.unsplash.com/photo-1512389142860-9c449e58a543?auto=format&fit=crop&w=1200&q=80",
56
+ fit: "cover",
57
+ borderRadius: 16,
58
+ alt: "holiday"
59
+ }
60
+ ]
61
+ },
62
+ {
63
+ id: "page-2",
64
+ title: "\u795D\u798F",
65
+ background: { color: "#1a3766" },
66
+ elements: [
67
+ {
68
+ id: "p2-text-1",
69
+ type: "text",
70
+ x: 50,
71
+ y: 28,
72
+ content: "\u613F\u4F60\u65B0\u5C81\uFF1A",
73
+ fontSize: 28,
74
+ fontWeight: 700,
75
+ align: "center",
76
+ color: "#fef3c7"
77
+ },
78
+ {
79
+ id: "p2-text-2",
80
+ type: "text",
81
+ x: 50,
82
+ y: 42,
83
+ content: "\u6240\u5F97\u7686\u6240\u671F\uFF0C\u6240\u5931\u4EA6\u65E0\u788D",
84
+ fontSize: 18,
85
+ fontWeight: 500,
86
+ align: "center",
87
+ color: "#f8fafc"
88
+ },
89
+ {
90
+ id: "p2-text-3",
91
+ type: "text",
92
+ x: 50,
93
+ y: 55,
94
+ content: "\u613F\u4F60\u7684\u6BCF\u4E00\u6B65\u90FD\u8D70\u5411\u5149\u4EAE",
95
+ fontSize: 16,
96
+ fontWeight: 400,
97
+ align: "center",
98
+ color: "#dbeafe"
99
+ }
100
+ ]
101
+ },
102
+ {
103
+ id: "page-3",
104
+ title: "\u843D\u6B3E",
105
+ background: { color: "#11284d" },
106
+ elements: [
107
+ {
108
+ id: "p3-image-1",
109
+ type: "image",
110
+ x: 50,
111
+ y: 34,
112
+ width: 60,
113
+ height: 42,
114
+ src: "https://images.unsplash.com/photo-1456324504439-367cee3b3c32?auto=format&fit=crop&w=1200&q=80",
115
+ fit: "cover",
116
+ borderRadius: 14,
117
+ alt: "gift"
118
+ },
119
+ {
120
+ id: "p3-text-1",
121
+ type: "text",
122
+ x: 50,
123
+ y: 72,
124
+ content: "Best wishes, from SA2Kit",
125
+ fontSize: 18,
126
+ fontWeight: 600,
127
+ align: "center",
128
+ color: "#f8fafc"
129
+ }
130
+ ]
131
+ }
132
+ ]
133
+ };
134
+
135
+ // src/festivalCard/core/normalize.ts
136
+ var ensurePage = (page, index) => ({
137
+ id: page.id || `page-${index + 1}`,
138
+ title: page.title || `\u7B2C ${index + 1} \u9875`,
139
+ elements: Array.isArray(page.elements) ? page.elements : [],
140
+ background: page.background || {}
141
+ });
142
+ var normalizeFestivalCardConfig = (config) => {
143
+ const pages = config?.pages && config.pages.length > 0 ? config.pages : DEFAULT_FESTIVAL_CARD_CONFIG.pages;
144
+ return {
145
+ ...DEFAULT_FESTIVAL_CARD_CONFIG,
146
+ ...config,
147
+ background: {
148
+ ...DEFAULT_FESTIVAL_CARD_CONFIG.background,
149
+ ...config?.background || {}
150
+ },
151
+ backgroundMusic: {
152
+ ...DEFAULT_FESTIVAL_CARD_CONFIG.backgroundMusic,
153
+ ...config?.backgroundMusic || {},
154
+ src: config?.backgroundMusic?.src ?? DEFAULT_FESTIVAL_CARD_CONFIG.backgroundMusic?.src ?? ""
155
+ },
156
+ pages: pages.map(ensurePage)
157
+ };
158
+ };
159
+ var resizeFestivalCardPages = (config, pageCount) => {
160
+ const safeCount = Math.max(1, Math.min(12, Math.floor(pageCount || 1)));
161
+ const nextPages = [...config.pages];
162
+ while (nextPages.length < safeCount) {
163
+ const idx = nextPages.length;
164
+ nextPages.push({
165
+ id: `page-${idx + 1}`,
166
+ title: `\u7B2C ${idx + 1} \u9875`,
167
+ elements: [],
168
+ background: {}
169
+ });
14
170
  }
15
- return { positions, speeds };
171
+ return {
172
+ ...config,
173
+ pages: nextPages.slice(0, safeCount)
174
+ };
16
175
  };
17
- var FestivalCard3D = ({
18
- title = "Happy Holidays",
19
- subtitle = "Wishing you joy and peace",
20
- className
21
- }) => {
22
- const mountRef = useRef(null);
176
+ var useFestivalCardConfig = (options) => {
177
+ const [config, setConfig] = useState(
178
+ () => normalizeFestivalCardConfig(options?.initialConfig || DEFAULT_FESTIVAL_CARD_CONFIG)
179
+ );
180
+ const [loading, setLoading] = useState(Boolean(options?.fetchConfig));
181
+ const [saving, setSaving] = useState(false);
23
182
  useEffect(() => {
24
- const mount = mountRef.current;
25
- if (!mount) return;
26
- const scene = new THREE.Scene();
27
- scene.fog = new THREE.Fog(528934, 12, 28);
28
- const camera = new THREE.PerspectiveCamera(50, mount.clientWidth / mount.clientHeight, 0.1, 100);
29
- camera.position.set(0, 2.6, 7.5);
30
- const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
31
- renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
32
- renderer.setSize(mount.clientWidth, mount.clientHeight);
33
- renderer.outputColorSpace = THREE.SRGBColorSpace;
34
- mount.appendChild(renderer.domElement);
35
- const ambientLight = new THREE.AmbientLight(9090303, 0.8);
36
- scene.add(ambientLight);
37
- const keyLight = new THREE.DirectionalLight(16777215, 1.25);
38
- keyLight.position.set(2, 5, 4);
39
- scene.add(keyLight);
40
- const fillLight = new THREE.PointLight(8050687, 0.9, 24);
41
- fillLight.position.set(-4, 3, -2);
42
- scene.add(fillLight);
43
- const floor = new THREE.Mesh(
44
- new THREE.CircleGeometry(12, 48),
45
- new THREE.MeshStandardMaterial({ color: 1056826, roughness: 0.95, metalness: 0.05 })
46
- );
47
- floor.rotation.x = -Math.PI / 2;
48
- floor.position.y = -1.2;
49
- scene.add(floor);
50
- const giftGroup = new THREE.Group();
51
- scene.add(giftGroup);
52
- const box = new THREE.Mesh(
53
- new THREE.BoxGeometry(2, 1.6, 2),
54
- new THREE.MeshStandardMaterial({ color: 14238053, roughness: 0.55, metalness: 0.2 })
55
- );
56
- giftGroup.add(box);
57
- const ribbonMaterial = new THREE.MeshStandardMaterial({ color: 16767354, roughness: 0.3, metalness: 0.5 });
58
- const verticalRibbon = new THREE.Mesh(new THREE.BoxGeometry(0.24, 1.7, 2.02), ribbonMaterial);
59
- const horizontalRibbon = new THREE.Mesh(new THREE.BoxGeometry(2.02, 1.7, 0.24), ribbonMaterial);
60
- giftGroup.add(verticalRibbon);
61
- giftGroup.add(horizontalRibbon);
62
- const lid = new THREE.Mesh(
63
- new THREE.BoxGeometry(2.15, 0.34, 2.15),
64
- new THREE.MeshStandardMaterial({ color: 13448278, roughness: 0.52, metalness: 0.2 })
65
- );
66
- lid.position.y = 0.98;
67
- giftGroup.add(lid);
68
- const bowLeft = new THREE.Mesh(new THREE.TorusGeometry(0.26, 0.08, 12, 32), ribbonMaterial);
69
- bowLeft.rotation.set(Math.PI / 2, Math.PI / 6, 0);
70
- bowLeft.position.set(-0.22, 1.14, 0);
71
- const bowRight = bowLeft.clone();
72
- bowRight.rotation.y = -Math.PI / 6;
73
- bowRight.position.x = 0.22;
74
- giftGroup.add(bowLeft, bowRight);
75
- giftGroup.position.y = -0.15;
76
- const { positions, speeds } = createSnow(260);
77
- const snowGeometry = new THREE.BufferGeometry();
78
- snowGeometry.setAttribute("position", new THREE.BufferAttribute(positions, 3));
79
- const snow = new THREE.Points(
80
- snowGeometry,
81
- new THREE.PointsMaterial({
82
- color: 15267071,
83
- size: 0.08,
84
- transparent: true,
85
- opacity: 0.92,
86
- depthWrite: false
87
- })
88
- );
89
- scene.add(snow);
90
- let rafId = 0;
91
- const clock = new THREE.Clock();
92
- const animate = () => {
93
- const elapsed = clock.getElapsedTime();
94
- const pos = snowGeometry.attributes.position;
95
- for (let i = 0; i < pos.count; i++) {
96
- const speed = speeds[i] ?? 0.01;
97
- const y = pos.getY(i) - speed;
98
- pos.setY(i, y < -1.1 ? 10 + Math.random() * 2 : y);
99
- pos.setX(i, pos.getX(i) + Math.sin(elapsed + i * 0.04) * 16e-4);
100
- }
101
- pos.needsUpdate = true;
102
- giftGroup.rotation.y = elapsed * 0.35;
103
- giftGroup.position.y = -0.15 + Math.sin(elapsed * 1.4) * 0.08;
104
- lid.position.y = 0.98 + Math.sin(elapsed * 2.2) * 0.08;
105
- renderer.render(scene, camera);
106
- rafId = window.requestAnimationFrame(animate);
107
- };
108
- const handleResize = () => {
109
- if (!mount) return;
110
- const width = mount.clientWidth;
111
- const height = mount.clientHeight;
112
- camera.aspect = width / height;
113
- camera.updateProjectionMatrix();
114
- renderer.setSize(width, height);
115
- };
116
- window.addEventListener("resize", handleResize);
117
- animate();
183
+ if (!options?.fetchConfig) return;
184
+ let active = true;
185
+ void options.fetchConfig().then((value) => {
186
+ if (!active) return;
187
+ setConfig(normalizeFestivalCardConfig(value));
188
+ }).finally(() => {
189
+ if (active) setLoading(false);
190
+ });
118
191
  return () => {
119
- window.cancelAnimationFrame(rafId);
120
- window.removeEventListener("resize", handleResize);
121
- scene.traverse((obj) => {
122
- const mesh = obj;
123
- if (mesh.geometry) mesh.geometry.dispose();
124
- const material = mesh.material;
125
- if (Array.isArray(material)) material.forEach((m) => m.dispose());
126
- else material?.dispose();
127
- });
128
- renderer.dispose();
129
- mount.removeChild(renderer.domElement);
192
+ active = false;
130
193
  };
131
- }, []);
132
- return /* @__PURE__ */ React.createElement(
194
+ }, [options]);
195
+ const save = useCallback(async () => {
196
+ if (!options?.onSave) return;
197
+ setSaving(true);
198
+ try {
199
+ await options.onSave(config);
200
+ } finally {
201
+ setSaving(false);
202
+ }
203
+ }, [config, options]);
204
+ return useMemo(
205
+ () => ({
206
+ config,
207
+ setConfig,
208
+ loading,
209
+ saving,
210
+ save
211
+ }),
212
+ [config, loading, save, saving]
213
+ );
214
+ };
215
+ var elementStyle = (element) => ({
216
+ position: "absolute",
217
+ left: `${element.x}%`,
218
+ top: `${element.y}%`,
219
+ width: `${element.width ?? 70}%`,
220
+ height: element.height ? `${element.height}%` : void 0,
221
+ transform: "translate(-50%, -50%)"
222
+ });
223
+ var renderElement = (element) => {
224
+ if (element.type === "text") {
225
+ return /* @__PURE__ */ React3.createElement(
226
+ "div",
227
+ {
228
+ key: element.id,
229
+ style: {
230
+ ...elementStyle(element),
231
+ color: element.color || "#f8fafc",
232
+ fontSize: element.fontSize || 18,
233
+ fontWeight: element.fontWeight || 500,
234
+ textAlign: element.align || "left",
235
+ lineHeight: 1.45,
236
+ whiteSpace: "pre-wrap"
237
+ }
238
+ },
239
+ element.content
240
+ );
241
+ }
242
+ return /* @__PURE__ */ React3.createElement(
243
+ "img",
244
+ {
245
+ key: element.id,
246
+ src: element.src,
247
+ alt: element.alt || "festival-card-image",
248
+ style: {
249
+ ...elementStyle(element),
250
+ objectFit: element.fit || "cover",
251
+ borderRadius: element.borderRadius || 0,
252
+ overflow: "hidden",
253
+ boxShadow: "0 12px 30px rgba(2, 6, 23, 0.32)"
254
+ }
255
+ }
256
+ );
257
+ };
258
+ var FestivalCardPageRenderer = ({ page }) => {
259
+ return /* @__PURE__ */ React3.createElement(
133
260
  "div",
134
261
  {
135
- className,
136
262
  style: {
137
263
  position: "relative",
138
264
  width: "100%",
139
- minHeight: 420,
140
- borderRadius: 20,
265
+ height: "100%",
141
266
  overflow: "hidden",
142
- background: "radial-gradient(circle at 20% 20%, #244d8c 0%, #0c1a34 45%, #060d1f 100%)"
267
+ borderRadius: 16,
268
+ backgroundColor: page.background?.color || "#0f172a",
269
+ backgroundImage: page.background?.image ? `url(${page.background.image})` : void 0,
270
+ backgroundSize: "cover",
271
+ backgroundPosition: "center"
143
272
  }
144
273
  },
145
- /* @__PURE__ */ React.createElement("div", { ref: mountRef, style: { position: "absolute", inset: 0 } }),
146
- /* @__PURE__ */ React.createElement(
274
+ page.elements.map(renderElement)
275
+ );
276
+ };
277
+
278
+ // src/festivalCard/components/FestivalCardBook3D.tsx
279
+ var FestivalCardBook3D = ({ config, className }) => {
280
+ const [currentPage, setCurrentPage] = useState(0);
281
+ const normalized = useMemo(() => normalizeFestivalCardConfig(config), [config]);
282
+ const pages = normalized.pages;
283
+ const canPrev = currentPage > 0;
284
+ const canNext = currentPage < pages.length - 1;
285
+ return /* @__PURE__ */ React3.createElement("div", { className }, /* @__PURE__ */ React3.createElement(
286
+ "div",
287
+ {
288
+ style: {
289
+ width: "100%",
290
+ minHeight: 560,
291
+ borderRadius: 24,
292
+ padding: 24,
293
+ background: `linear-gradient(145deg, ${normalized.background?.colorA || "#0c1a34"} 0%, ${normalized.background?.colorB || "#1f4f8a"} 100%)`,
294
+ boxShadow: "0 26px 70px rgba(2, 6, 23, 0.45)"
295
+ }
296
+ },
297
+ /* @__PURE__ */ React3.createElement(
147
298
  "div",
148
299
  {
149
300
  style: {
150
- position: "absolute",
151
- inset: 0,
152
- pointerEvents: "none",
153
- background: "linear-gradient(180deg, rgba(255,255,255,0.18) 0%, rgba(255,255,255,0.04) 35%, rgba(4,8,20,0.36) 100%)"
301
+ marginBottom: 14,
302
+ color: "#f8fafc",
303
+ fontSize: 14,
304
+ opacity: 0.9,
305
+ textAlign: "center"
154
306
  }
155
- }
307
+ },
308
+ normalized.coverTitle || "Festival Card",
309
+ " \xB7 \u7B2C ",
310
+ currentPage + 1,
311
+ " / ",
312
+ pages.length,
313
+ " \u9875"
156
314
  ),
157
- /* @__PURE__ */ React.createElement(
315
+ /* @__PURE__ */ React3.createElement("div", { style: { perspective: 1400, width: "100%", maxWidth: 920, margin: "0 auto" } }, /* @__PURE__ */ React3.createElement(
158
316
  "div",
159
317
  {
160
318
  style: {
161
- position: "absolute",
162
- left: 20,
163
- right: 20,
164
- bottom: 20,
165
- zIndex: 2,
166
- padding: "16px 18px",
167
- borderRadius: 14,
168
- backgroundColor: "rgba(8, 16, 35, 0.66)",
169
- border: "1px solid rgba(255, 255, 255, 0.16)",
170
- color: "#f8fafc"
319
+ position: "relative",
320
+ height: 460,
321
+ transformStyle: "preserve-3d"
171
322
  }
172
323
  },
173
- /* @__PURE__ */ React.createElement("div", { style: { fontSize: 26, fontWeight: 700, lineHeight: 1.2 } }, title),
174
- /* @__PURE__ */ React.createElement("div", { style: { marginTop: 6, fontSize: 15, opacity: 0.92 } }, subtitle)
175
- )
176
- );
324
+ pages.map((page, index) => {
325
+ const isFlipped = index < currentPage;
326
+ const zIndex = pages.length - index;
327
+ return /* @__PURE__ */ React3.createElement(
328
+ "div",
329
+ {
330
+ key: page.id,
331
+ style: {
332
+ position: "absolute",
333
+ inset: 0,
334
+ transformStyle: "preserve-3d",
335
+ transformOrigin: "left center",
336
+ transform: `rotateY(${isFlipped ? -170 : 0}deg)`,
337
+ transition: "transform 600ms cubic-bezier(0.2, 0.8, 0.2, 1)",
338
+ zIndex
339
+ }
340
+ },
341
+ /* @__PURE__ */ React3.createElement(
342
+ "div",
343
+ {
344
+ style: {
345
+ position: "absolute",
346
+ inset: 0,
347
+ backfaceVisibility: "hidden"
348
+ }
349
+ },
350
+ /* @__PURE__ */ React3.createElement(FestivalCardPageRenderer, { page })
351
+ ),
352
+ /* @__PURE__ */ React3.createElement(
353
+ "div",
354
+ {
355
+ style: {
356
+ position: "absolute",
357
+ inset: 0,
358
+ transform: "rotateY(180deg)",
359
+ backfaceVisibility: "hidden",
360
+ borderRadius: 16,
361
+ background: "#0f172a"
362
+ }
363
+ }
364
+ )
365
+ );
366
+ })
367
+ )),
368
+ /* @__PURE__ */ React3.createElement("div", { style: { display: "flex", justifyContent: "center", gap: 12, marginTop: 18 } }, /* @__PURE__ */ React3.createElement(
369
+ "button",
370
+ {
371
+ type: "button",
372
+ disabled: !canPrev,
373
+ onClick: () => setCurrentPage((p) => Math.max(0, p - 1)),
374
+ style: {
375
+ border: "none",
376
+ borderRadius: 999,
377
+ padding: "9px 16px",
378
+ fontSize: 14,
379
+ cursor: canPrev ? "pointer" : "not-allowed",
380
+ opacity: canPrev ? 1 : 0.4
381
+ }
382
+ },
383
+ "\u4E0A\u4E00\u9875"
384
+ ), /* @__PURE__ */ React3.createElement(
385
+ "button",
386
+ {
387
+ type: "button",
388
+ disabled: !canNext,
389
+ onClick: () => setCurrentPage((p) => Math.min(pages.length - 1, p + 1)),
390
+ style: {
391
+ border: "none",
392
+ borderRadius: 999,
393
+ padding: "9px 16px",
394
+ fontSize: 14,
395
+ cursor: canNext ? "pointer" : "not-allowed",
396
+ opacity: canNext ? 1 : 0.4
397
+ }
398
+ },
399
+ "\u4E0B\u4E00\u9875"
400
+ ))
401
+ ), normalized.backgroundMusic?.src ? /* @__PURE__ */ React3.createElement(
402
+ "audio",
403
+ {
404
+ src: normalized.backgroundMusic.src,
405
+ autoPlay: normalized.backgroundMusic.autoPlay,
406
+ loop: normalized.backgroundMusic.loop,
407
+ controls: true,
408
+ style: { width: "100%", marginTop: 10 }
409
+ }
410
+ ) : null);
411
+ };
412
+ var createTextElement = (pageIndex) => ({
413
+ id: `text-${Date.now()}-${pageIndex}`,
414
+ type: "text",
415
+ x: 50,
416
+ y: 50,
417
+ content: "\u8BF7\u8F93\u5165\u6587\u5B57",
418
+ fontSize: 18,
419
+ fontWeight: 500,
420
+ align: "center",
421
+ color: "#ffffff"
422
+ });
423
+ var createImageElement = (pageIndex) => ({
424
+ id: `image-${Date.now()}-${pageIndex}`,
425
+ type: "image",
426
+ x: 50,
427
+ y: 50,
428
+ width: 60,
429
+ height: 40,
430
+ src: "https://images.unsplash.com/photo-1482517967863-00e15c9b44be?auto=format&fit=crop&w=1200&q=80",
431
+ fit: "cover",
432
+ borderRadius: 12
433
+ });
434
+ var FestivalCardConfigEditor = ({ value, onChange }) => {
435
+ const [activePageIndex, setActivePageIndex] = useState(0);
436
+ const page = value.pages[activePageIndex];
437
+ const canEditPage = Boolean(page);
438
+ const pageOptions = useMemo(() => value.pages.map((_, index) => index), [value.pages]);
439
+ const updateElement = (elementId, patch) => {
440
+ onChange({
441
+ ...value,
442
+ pages: value.pages.map(
443
+ (p, pIndex) => pIndex === activePageIndex ? {
444
+ ...p,
445
+ elements: p.elements.map((el) => el.id === elementId ? { ...el, ...patch } : el)
446
+ } : p
447
+ )
448
+ });
449
+ };
450
+ return /* @__PURE__ */ React3.createElement("div", { style: { borderRadius: 16, background: "#0f172a", color: "#e2e8f0", padding: 16 } }, /* @__PURE__ */ React3.createElement("div", { style: { display: "grid", gap: 12 } }, /* @__PURE__ */ React3.createElement("label", { style: { display: "grid", gap: 6 } }, /* @__PURE__ */ React3.createElement("span", null, "\u9875\u9762\u6570\u91CF"), /* @__PURE__ */ React3.createElement(
451
+ "input",
452
+ {
453
+ type: "number",
454
+ min: 1,
455
+ max: 12,
456
+ value: value.pages.length,
457
+ onChange: (event) => onChange(resizeFestivalCardPages(value, Number(event.target.value)))
458
+ }
459
+ )), /* @__PURE__ */ React3.createElement("label", { style: { display: "grid", gap: 6 } }, /* @__PURE__ */ React3.createElement("span", null, "\u80CC\u666F\u97F3\u4E50 URL"), /* @__PURE__ */ React3.createElement(
460
+ "input",
461
+ {
462
+ type: "url",
463
+ value: value.backgroundMusic?.src || "",
464
+ onChange: (event) => onChange({
465
+ ...value,
466
+ backgroundMusic: {
467
+ ...value.backgroundMusic,
468
+ src: event.target.value
469
+ }
470
+ })
471
+ }
472
+ )), /* @__PURE__ */ React3.createElement("label", { style: { display: "grid", gap: 6 } }, /* @__PURE__ */ React3.createElement("span", null, "\u7F16\u8F91\u9875\u9762"), /* @__PURE__ */ React3.createElement("select", { value: activePageIndex, onChange: (event) => setActivePageIndex(Number(event.target.value)) }, pageOptions.map((index) => /* @__PURE__ */ React3.createElement("option", { key: index, value: index }, "\u7B2C ", index + 1, " \u9875"))))), canEditPage ? /* @__PURE__ */ React3.createElement("div", { style: { marginTop: 16 } }, /* @__PURE__ */ React3.createElement("div", { style: { display: "flex", gap: 8, marginBottom: 12 } }, /* @__PURE__ */ React3.createElement(
473
+ "button",
474
+ {
475
+ type: "button",
476
+ onClick: () => onChange({
477
+ ...value,
478
+ pages: value.pages.map(
479
+ (p, index) => index === activePageIndex ? { ...p, elements: [...p.elements, createTextElement(index)] } : p
480
+ )
481
+ })
482
+ },
483
+ "+ \u6587\u5B57"
484
+ ), /* @__PURE__ */ React3.createElement(
485
+ "button",
486
+ {
487
+ type: "button",
488
+ onClick: () => onChange({
489
+ ...value,
490
+ pages: value.pages.map(
491
+ (p, index) => index === activePageIndex ? { ...p, elements: [...p.elements, createImageElement(index)] } : p
492
+ )
493
+ })
494
+ },
495
+ "+ \u56FE\u7247"
496
+ )), /* @__PURE__ */ React3.createElement("div", { style: { display: "grid", gap: 10, maxHeight: 340, overflow: "auto" } }, (page?.elements ?? []).map((element) => /* @__PURE__ */ React3.createElement("div", { key: element.id, style: { border: "1px solid #334155", borderRadius: 10, padding: 10 } }, /* @__PURE__ */ React3.createElement("div", { style: { marginBottom: 8 } }, element.type.toUpperCase()), element.type === "text" ? /* @__PURE__ */ React3.createElement(
497
+ "textarea",
498
+ {
499
+ value: element.content,
500
+ onChange: (event) => updateElement(element.id, { content: event.target.value }),
501
+ rows: 3,
502
+ style: { width: "100%" }
503
+ }
504
+ ) : /* @__PURE__ */ React3.createElement(
505
+ "input",
506
+ {
507
+ type: "url",
508
+ value: element.src,
509
+ onChange: (event) => updateElement(element.id, { src: event.target.value }),
510
+ style: { width: "100%" }
511
+ }
512
+ ))))) : null);
513
+ };
514
+
515
+ // src/festivalCard/components/FestivalCardStudio.tsx
516
+ var FestivalCardStudio = ({ initialConfig, fetchConfig, onSave }) => {
517
+ const { config, setConfig, loading, save, saving } = useFestivalCardConfig({
518
+ initialConfig: normalizeFestivalCardConfig(initialConfig),
519
+ fetchConfig,
520
+ onSave
521
+ });
522
+ if (loading) return /* @__PURE__ */ React3.createElement("div", null, "\u52A0\u8F7D\u4E2D...");
523
+ return /* @__PURE__ */ React3.createElement("div", { style: { display: "grid", gridTemplateColumns: "1.4fr 1fr", gap: 16 } }, /* @__PURE__ */ React3.createElement(FestivalCardBook3D, { config }), /* @__PURE__ */ React3.createElement("div", null, /* @__PURE__ */ React3.createElement(FestivalCardConfigEditor, { value: config, onChange: setConfig }), onSave ? /* @__PURE__ */ React3.createElement(
524
+ "button",
525
+ {
526
+ type: "button",
527
+ onClick: () => void save(),
528
+ disabled: saving,
529
+ style: { marginTop: 12, width: "100%", padding: "10px 16px" }
530
+ },
531
+ saving ? "\u4FDD\u5B58\u4E2D..." : "\u4FDD\u5B58\u914D\u7F6E"
532
+ ) : null));
177
533
  };
178
534
 
179
- export { FestivalCard3D };
535
+ // src/festivalCard/components/FestivalCardConfigPage.tsx
536
+ var FestivalCardConfigPage = ({
537
+ apiBase = "/api/festivalCard",
538
+ cardId,
539
+ mainPagePath = "/festivalCard"
540
+ }) => {
541
+ const [list, setList] = useState([]);
542
+ const [selectedId, setSelectedId] = useState(cardId || "default-festival-card");
543
+ const parseListResponse2 = (data) => {
544
+ if (!data || typeof data !== "object") return [];
545
+ const payload = data.data;
546
+ if (!Array.isArray(payload)) return [];
547
+ return payload.filter((item) => Boolean(item && typeof item === "object" && typeof item.id === "string")).map((item) => ({ id: item.id, name: item.name }));
548
+ };
549
+ const parseConfigResponse2 = (data) => {
550
+ if (!data || typeof data !== "object") return normalizeFestivalCardConfig();
551
+ const payload = data.data;
552
+ if (!payload || typeof payload !== "object") return normalizeFestivalCardConfig();
553
+ return normalizeFestivalCardConfig(payload);
554
+ };
555
+ const reloadList = useCallback(async () => {
556
+ const response = await fetch(apiBase, { cache: "no-store" });
557
+ const data = await response.json();
558
+ setList(parseListResponse2(data));
559
+ }, [apiBase]);
560
+ useEffect(() => {
561
+ void reloadList();
562
+ }, [reloadList]);
563
+ const fetchConfig = async () => {
564
+ const response = await fetch(`${apiBase}/${encodeURIComponent(selectedId)}`, { cache: "no-store" });
565
+ const data = await response.json();
566
+ return parseConfigResponse2(data);
567
+ };
568
+ const saveConfig = async (config) => {
569
+ await fetch(`${apiBase}/${encodeURIComponent(selectedId)}`, {
570
+ method: "PUT",
571
+ headers: { "Content-Type": "application/json" },
572
+ body: JSON.stringify({ config })
573
+ });
574
+ await reloadList();
575
+ };
576
+ const createNew = async () => {
577
+ const name = window.prompt("\u8BF7\u8F93\u5165\u65B0\u5361\u7247\u540D\u79F0");
578
+ if (!name) return;
579
+ const id = `festival-${Date.now()}`;
580
+ const config = normalizeFestivalCardConfig({
581
+ id,
582
+ name
583
+ });
584
+ await fetch(`${apiBase}/${encodeURIComponent(id)}`, {
585
+ method: "PUT",
586
+ headers: { "Content-Type": "application/json" },
587
+ body: JSON.stringify({ config })
588
+ });
589
+ setSelectedId(id);
590
+ await reloadList();
591
+ };
592
+ const mainLink = useMemo(() => `${mainPagePath}?cardId=${encodeURIComponent(selectedId)}`, [mainPagePath, selectedId]);
593
+ return /* @__PURE__ */ React3.createElement("div", { style: { display: "grid", gap: 12 } }, /* @__PURE__ */ React3.createElement("div", { style: { display: "flex", gap: 10, alignItems: "center" } }, /* @__PURE__ */ React3.createElement("select", { value: selectedId, onChange: (event) => setSelectedId(event.target.value) }, list.map((item) => /* @__PURE__ */ React3.createElement("option", { key: item.id, value: item.id }, item.name || item.id))), /* @__PURE__ */ React3.createElement("button", { type: "button", onClick: () => void createNew() }, "\u65B0\u5EFA\u5361\u7247"), /* @__PURE__ */ React3.createElement("a", { href: mainLink, style: { color: "#2563eb", fontSize: 14 } }, "\u6253\u5F00\u4E3B\u9875\u9762")), /* @__PURE__ */ React3.createElement(FestivalCardStudio, { fetchConfig, onSave: saveConfig }));
594
+ };
595
+ var isSummary = (value) => {
596
+ if (!value || typeof value !== "object") return false;
597
+ return typeof value.id === "string";
598
+ };
599
+ var parseListResponse = (data) => {
600
+ if (!data || typeof data !== "object") return [];
601
+ const payload = data.data;
602
+ if (!Array.isArray(payload)) return [];
603
+ return payload.filter(isSummary).map((item) => ({ id: item.id, name: item.name }));
604
+ };
605
+ var parseConfigResponse = (data) => {
606
+ if (!data || typeof data !== "object") return null;
607
+ const payload = data.data;
608
+ if (!payload || typeof payload !== "object") return null;
609
+ if (!Array.isArray(payload.pages)) return null;
610
+ return payload;
611
+ };
612
+ var FestivalCardManagedPage = ({
613
+ apiBase = "/api/festivalCard",
614
+ cardId,
615
+ configPagePath = "/festivalCard/config"
616
+ }) => {
617
+ const [list, setList] = useState([]);
618
+ const [currentCardId, setCurrentCardId] = useState(cardId || "");
619
+ const [config, setConfig] = useState(null);
620
+ const [loading, setLoading] = useState(true);
621
+ useEffect(() => {
622
+ const fetchList = async () => {
623
+ const response = await fetch(apiBase, { cache: "no-store" });
624
+ const data = await response.json();
625
+ const items = parseListResponse(data);
626
+ setList(items);
627
+ const first = items[0];
628
+ if (!currentCardId && first) {
629
+ setCurrentCardId(first.id);
630
+ }
631
+ };
632
+ void fetchList();
633
+ }, [apiBase, currentCardId]);
634
+ useEffect(() => {
635
+ if (!currentCardId) return;
636
+ setLoading(true);
637
+ void fetch(`${apiBase}/${encodeURIComponent(currentCardId)}`, { cache: "no-store" }).then((res) => res.json()).then((data) => setConfig(parseConfigResponse(data))).finally(() => setLoading(false));
638
+ }, [apiBase, currentCardId]);
639
+ const configLink = useMemo(() => {
640
+ if (!currentCardId) return configPagePath;
641
+ return `${configPagePath}?cardId=${encodeURIComponent(currentCardId)}`;
642
+ }, [configPagePath, currentCardId]);
643
+ return /* @__PURE__ */ React3.createElement("div", { style: { display: "grid", gridTemplateColumns: "240px 1fr", gap: 14 } }, /* @__PURE__ */ React3.createElement("aside", { style: { borderRadius: 16, padding: 12, background: "#0f172a", color: "#e2e8f0" } }, /* @__PURE__ */ React3.createElement("div", { style: { fontSize: 14, marginBottom: 8 } }, "\u5361\u7247\u5217\u8868"), /* @__PURE__ */ React3.createElement("div", { style: { display: "grid", gap: 8 } }, list.map((item) => /* @__PURE__ */ React3.createElement(
644
+ "button",
645
+ {
646
+ key: item.id,
647
+ type: "button",
648
+ onClick: () => setCurrentCardId(item.id),
649
+ style: {
650
+ borderRadius: 8,
651
+ border: "1px solid #334155",
652
+ padding: "8px 10px",
653
+ textAlign: "left",
654
+ background: currentCardId === item.id ? "#1e293b" : "#0b1220",
655
+ color: "#e2e8f0"
656
+ }
657
+ },
658
+ item.name || item.id
659
+ ))), /* @__PURE__ */ React3.createElement("a", { href: configLink, style: { display: "inline-block", marginTop: 12, color: "#93c5fd", fontSize: 13 } }, "\u8FDB\u5165\u914D\u7F6E\u9875")), /* @__PURE__ */ React3.createElement("div", null, loading || !config ? /* @__PURE__ */ React3.createElement("div", null, "\u52A0\u8F7D\u4E2D...") : /* @__PURE__ */ React3.createElement(FestivalCardBook3D, { config })));
660
+ };
661
+
662
+ // src/festivalCard/server/db.ts
663
+ var dbAdapter = null;
664
+ var getFestivalCardDb = () => dbAdapter;
665
+
666
+ // src/festivalCard/services/festivalCardService.ts
667
+ var FestivalCardService = class {
668
+ constructor(options) {
669
+ this.db = options?.db || getFestivalCardDb();
670
+ }
671
+ async listConfigs() {
672
+ if (!this.db) {
673
+ return [{ id: DEFAULT_FESTIVAL_CARD_CONFIG.id || "default-festival-card", name: DEFAULT_FESTIVAL_CARD_CONFIG.name }];
674
+ }
675
+ if (this.db.listConfigs) {
676
+ const list = await this.db.listConfigs();
677
+ return list.length > 0 ? list : [{ id: DEFAULT_FESTIVAL_CARD_CONFIG.id || "default-festival-card", name: DEFAULT_FESTIVAL_CARD_CONFIG.name }];
678
+ }
679
+ return [{ id: DEFAULT_FESTIVAL_CARD_CONFIG.id || "default-festival-card", name: DEFAULT_FESTIVAL_CARD_CONFIG.name }];
680
+ }
681
+ async getConfig(cardId = "default-festival-card") {
682
+ if (!this.db) return DEFAULT_FESTIVAL_CARD_CONFIG;
683
+ const config = await this.db.getConfig(cardId);
684
+ return normalizeFestivalCardConfig(config);
685
+ }
686
+ async saveConfig(cardId, config) {
687
+ const normalized = normalizeFestivalCardConfig(config);
688
+ if (!this.db) return normalized;
689
+ await this.db.saveConfig(cardId, normalized);
690
+ return normalized;
691
+ }
692
+ async deleteConfig(cardId) {
693
+ if (!this.db?.deleteConfig) return;
694
+ await this.db.deleteConfig(cardId);
695
+ }
696
+ };
697
+ var memoryStore = /* @__PURE__ */ new Map();
698
+ var defaultId = DEFAULT_FESTIVAL_CARD_CONFIG.id || "default-festival-card";
699
+ if (!memoryStore.has(defaultId)) {
700
+ memoryStore.set(defaultId, DEFAULT_FESTIVAL_CARD_CONFIG);
701
+ }
702
+ var createInMemoryFestivalCardDb = () => ({
703
+ listConfigs() {
704
+ return Promise.resolve(
705
+ Array.from(memoryStore.entries()).map(([id, config]) => ({
706
+ id,
707
+ name: config.name || id
708
+ }))
709
+ );
710
+ },
711
+ getConfig(id) {
712
+ return Promise.resolve(memoryStore.get(id) || null);
713
+ },
714
+ saveConfig(id, config) {
715
+ memoryStore.set(id, config);
716
+ return Promise.resolve();
717
+ },
718
+ deleteConfig(id) {
719
+ memoryStore.delete(id);
720
+ return Promise.resolve();
721
+ }
722
+ });
723
+
724
+ export { DEFAULT_FESTIVAL_CARD_CONFIG, FestivalCardBook3D, FestivalCardConfigEditor, FestivalCardConfigPage, FestivalCardManagedPage, FestivalCardPageRenderer, FestivalCardService, FestivalCardStudio, createInMemoryFestivalCardDb, normalizeFestivalCardConfig, resizeFestivalCardPages, useFestivalCardConfig };
180
725
  //# sourceMappingURL=index.mjs.map
181
726
  //# sourceMappingURL=index.mjs.map