sa2kit 1.6.90 → 1.6.92
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/client-BlkUL2To.d.ts +26 -0
- package/dist/client-DpMIhrlS.d.mts +26 -0
- 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-C7yh6b5Q.d.mts +17 -0
- package/dist/index-CDapUIT5.d.mts +51 -0
- package/dist/index-Cv9jlnNz.d.ts +17 -0
- package/dist/index-D3UbkUai.d.ts +51 -0
- package/dist/index.d.mts +11 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +1925 -355
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1894 -358
- package/dist/index.mjs.map +1 -1
- package/dist/mikuContest/index.d.mts +13 -0
- package/dist/mikuContest/index.d.ts +13 -0
- package/dist/mikuContest/index.js +1310 -0
- package/dist/mikuContest/index.js.map +1 -0
- package/dist/mikuContest/index.mjs +1253 -0
- package/dist/mikuContest/index.mjs.map +1 -0
- package/dist/mikuContest/logic/index.d.mts +32 -0
- package/dist/mikuContest/logic/index.d.ts +32 -0
- package/dist/mikuContest/logic/index.js +511 -0
- package/dist/mikuContest/logic/index.js.map +1 -0
- package/dist/mikuContest/logic/index.mjs +483 -0
- package/dist/mikuContest/logic/index.mjs.map +1 -0
- package/dist/mikuContest/routes/index.d.mts +80 -0
- package/dist/mikuContest/routes/index.d.ts +80 -0
- package/dist/mikuContest/routes/index.js +821 -0
- package/dist/mikuContest/routes/index.js.map +1 -0
- package/dist/mikuContest/routes/index.mjs +791 -0
- package/dist/mikuContest/routes/index.mjs.map +1 -0
- package/dist/mikuContest/server/index.d.mts +766 -0
- package/dist/mikuContest/server/index.d.ts +766 -0
- package/dist/mikuContest/server/index.js +705 -0
- package/dist/mikuContest/server/index.js.map +1 -0
- package/dist/mikuContest/server/index.mjs +672 -0
- package/dist/mikuContest/server/index.mjs.map +1 -0
- package/dist/mikuContest/service/index.d.mts +30 -0
- package/dist/mikuContest/service/index.d.ts +30 -0
- package/dist/mikuContest/service/index.js +139 -0
- package/dist/mikuContest/service/index.js.map +1 -0
- package/dist/mikuContest/service/index.mjs +135 -0
- package/dist/mikuContest/service/index.mjs.map +1 -0
- package/dist/mikuContest/types/index.d.mts +179 -0
- package/dist/mikuContest/types/index.d.ts +179 -0
- package/dist/mikuContest/types/index.js +4 -0
- package/dist/mikuContest/types/index.js.map +1 -0
- package/dist/mikuContest/types/index.mjs +3 -0
- package/dist/mikuContest/types/index.mjs.map +1 -0
- package/dist/mikuContest/ui/miniapp/index.d.mts +3 -0
- package/dist/mikuContest/ui/miniapp/index.d.ts +3 -0
- package/dist/mikuContest/ui/miniapp/index.js +566 -0
- package/dist/mikuContest/ui/miniapp/index.js.map +1 -0
- package/dist/mikuContest/ui/miniapp/index.mjs +540 -0
- package/dist/mikuContest/ui/miniapp/index.mjs.map +1 -0
- package/dist/mikuContest/ui/web/index.d.mts +4 -0
- package/dist/mikuContest/ui/web/index.d.ts +4 -0
- package/dist/mikuContest/ui/web/index.js +353 -0
- package/dist/mikuContest/ui/web/index.js.map +1 -0
- package/dist/mikuContest/ui/web/index.mjs +343 -0
- package/dist/mikuContest/ui/web/index.mjs.map +1 -0
- package/dist/service-D7DM1wW-.d.ts +38 -0
- package/dist/service-DPr2rlvH.d.mts +38 -0
- package/dist/types-BS7Xz09b.d.mts +14 -0
- package/dist/types-k4koMp4m.d.ts +14 -0
- package/package.json +41 -1
package/dist/index.mjs
CHANGED
|
@@ -11,19 +11,26 @@ import Link from 'next/link';
|
|
|
11
11
|
import { useSensors, useSensor, PointerSensor, TouchSensor, KeyboardSensor, DndContext, closestCenter } from '@dnd-kit/core';
|
|
12
12
|
import { useSortable, sortableKeyboardCoordinates, SortableContext, rectSortingStrategy, arrayMove } from '@dnd-kit/sortable';
|
|
13
13
|
import { CSS } from '@dnd-kit/utilities';
|
|
14
|
-
import { pgEnum, pgTable, boolean, timestamp, jsonb, text, uniqueIndex, foreignKey } from 'drizzle-orm/pg-core';
|
|
15
|
-
import { sql, relations } from 'drizzle-orm';
|
|
14
|
+
import { pgEnum, pgTable, boolean, timestamp, jsonb, text, uniqueIndex, foreignKey, integer } from 'drizzle-orm/pg-core';
|
|
15
|
+
import { sql, relations, eq } from 'drizzle-orm';
|
|
16
16
|
import { randomUUID } from 'crypto';
|
|
17
17
|
import 'bcryptjs';
|
|
18
18
|
import 'jsonwebtoken';
|
|
19
19
|
import * as THREE2 from 'three';
|
|
20
|
+
import * as XLSX from 'xlsx';
|
|
21
|
+
import { NextResponse } from 'next/server';
|
|
20
22
|
|
|
23
|
+
var __defProp = Object.defineProperty;
|
|
21
24
|
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
22
25
|
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
23
26
|
}) : x)(function(x) {
|
|
24
27
|
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
25
28
|
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
26
29
|
});
|
|
30
|
+
var __export = (target, all) => {
|
|
31
|
+
for (var name in all)
|
|
32
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
33
|
+
};
|
|
27
34
|
|
|
28
35
|
// src/logger/types.ts
|
|
29
36
|
var LogLevel = /* @__PURE__ */ ((LogLevel2) => {
|
|
@@ -51,10 +58,10 @@ var ConsoleLoggerAdapter = class {
|
|
|
51
58
|
};
|
|
52
59
|
}
|
|
53
60
|
log(entry) {
|
|
54
|
-
const { level, message, timestamp:
|
|
61
|
+
const { level, message, timestamp: timestamp6, data, context, error } = entry;
|
|
55
62
|
let logMessage = "";
|
|
56
|
-
if (
|
|
57
|
-
logMessage += "[" + this.formatTimestamp(
|
|
63
|
+
if (timestamp6) {
|
|
64
|
+
logMessage += "[" + this.formatTimestamp(timestamp6) + "] ";
|
|
58
65
|
}
|
|
59
66
|
const levelName = this.getLevelName(level);
|
|
60
67
|
logMessage += levelName + ": ";
|
|
@@ -547,7 +554,7 @@ var useSentimentAnalysis = (options = {}) => {
|
|
|
547
554
|
result: null
|
|
548
555
|
});
|
|
549
556
|
const pipelineRef = useRef(null);
|
|
550
|
-
const analyze = useCallback(async (
|
|
557
|
+
const analyze = useCallback(async (text6) => {
|
|
551
558
|
setState((prev) => ({
|
|
552
559
|
...prev,
|
|
553
560
|
isProcessing: true,
|
|
@@ -594,12 +601,12 @@ var useSentimentAnalysis = (options = {}) => {
|
|
|
594
601
|
pipelineRef.current = await pipeline("sentiment-analysis", options.model || defaultModel);
|
|
595
602
|
}
|
|
596
603
|
setState((prev) => ({ ...prev, status: "analyzing" }));
|
|
597
|
-
const output = await pipelineRef.current(
|
|
604
|
+
const output = await pipelineRef.current(text6);
|
|
598
605
|
const resultData = output[0];
|
|
599
606
|
const label = resultData.label.toLowerCase();
|
|
600
607
|
let sentiment = "neutral";
|
|
601
608
|
const negativeKeywords = ["\u7D2F", "\u60E8", "\u7EDD\u671B", "\u96BE\u53D7", "\u4F24\u5FC3", "\u5DEE", "\u574F", "\u7CDF", "\u4E0D\u884C"];
|
|
602
|
-
const hasNegativeKeyword = negativeKeywords.some((k) =>
|
|
609
|
+
const hasNegativeKeyword = negativeKeywords.some((k) => text6.includes(k));
|
|
603
610
|
if (label.includes("positive") && !hasNegativeKeyword) {
|
|
604
611
|
sentiment = "positive";
|
|
605
612
|
} else if (label.includes("negative") || label.includes("0") || hasNegativeKeyword) {
|
|
@@ -631,12 +638,12 @@ var SentimentAnalyzer = ({
|
|
|
631
638
|
className = "",
|
|
632
639
|
placeholder = "\u8F93\u5165\u4E00\u6BB5\u4E2D\u6587\u6216\u82F1\u6587\uFF0C\u5206\u6790\u5176\u60C5\u611F\u503E\u5411..."
|
|
633
640
|
}) => {
|
|
634
|
-
const [
|
|
641
|
+
const [text6, setText] = useState("");
|
|
635
642
|
const { analyze, isProcessing, status, result, error } = useSentimentAnalysis();
|
|
636
643
|
const handleAnalyze = async () => {
|
|
637
|
-
if (!
|
|
644
|
+
if (!text6.trim() || isProcessing) return;
|
|
638
645
|
try {
|
|
639
|
-
const res = await analyze(
|
|
646
|
+
const res = await analyze(text6);
|
|
640
647
|
onResult?.(res);
|
|
641
648
|
} catch (err) {
|
|
642
649
|
console.error("Sentiment Analysis Error:", err);
|
|
@@ -667,7 +674,7 @@ var SentimentAnalyzer = ({
|
|
|
667
674
|
return /* @__PURE__ */ React69__default.createElement("div", { className: clsx("p-6 border rounded-xl bg-white dark:bg-gray-800 shadow-sm", className) }, /* @__PURE__ */ React69__default.createElement("div", { className: "flex items-center gap-2 mb-4 text-gray-700 dark:text-gray-300 font-medium" }, /* @__PURE__ */ React69__default.createElement(MessageSquare, { size: 20 }), /* @__PURE__ */ React69__default.createElement("span", null, "\u6587\u672C\u60C5\u611F\u5206\u6790")), /* @__PURE__ */ React69__default.createElement("div", { className: "relative" }, /* @__PURE__ */ React69__default.createElement(
|
|
668
675
|
"textarea",
|
|
669
676
|
{
|
|
670
|
-
value:
|
|
677
|
+
value: text6,
|
|
671
678
|
onChange: (e) => setText(e.target.value),
|
|
672
679
|
placeholder,
|
|
673
680
|
className: "w-full h-32 p-4 bg-gray-50 dark:bg-gray-900 border border-gray-200 dark:border-gray-700 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all outline-none resize-none text-gray-800 dark:text-gray-200",
|
|
@@ -677,7 +684,7 @@ var SentimentAnalyzer = ({
|
|
|
677
684
|
"button",
|
|
678
685
|
{
|
|
679
686
|
onClick: handleAnalyze,
|
|
680
|
-
disabled: !
|
|
687
|
+
disabled: !text6.trim() || isProcessing,
|
|
681
688
|
className: "absolute bottom-3 right-3 p-2 bg-blue-600 hover:bg-blue-700 disabled:bg-gray-400 text-white rounded-md transition-colors shadow-sm"
|
|
682
689
|
},
|
|
683
690
|
isProcessing ? /* @__PURE__ */ React69__default.createElement(Loader2, { className: "animate-spin", size: 18 }) : /* @__PURE__ */ React69__default.createElement(Send, { size: 18 })
|
|
@@ -865,13 +872,13 @@ var requestJson = async (options) => {
|
|
|
865
872
|
body: body ? JSON.stringify(body) : void 0,
|
|
866
873
|
signal: controller?.signal
|
|
867
874
|
});
|
|
868
|
-
const
|
|
875
|
+
const text6 = await response.text();
|
|
869
876
|
let data = null;
|
|
870
|
-
if (
|
|
877
|
+
if (text6) {
|
|
871
878
|
try {
|
|
872
|
-
data = JSON.parse(
|
|
879
|
+
data = JSON.parse(text6);
|
|
873
880
|
} catch {
|
|
874
|
-
data =
|
|
881
|
+
data = text6;
|
|
875
882
|
}
|
|
876
883
|
}
|
|
877
884
|
if (!response.ok) {
|
|
@@ -1332,20 +1339,20 @@ var japaneseUtils = {
|
|
|
1332
1339
|
/**
|
|
1333
1340
|
* 提取文本中的汉字
|
|
1334
1341
|
*/
|
|
1335
|
-
extractKanji(
|
|
1336
|
-
return
|
|
1342
|
+
extractKanji(text6) {
|
|
1343
|
+
return text6.match(/[\u4E00-\u9FAF]/g) || [];
|
|
1337
1344
|
},
|
|
1338
1345
|
/**
|
|
1339
1346
|
* 提取文本中的假名
|
|
1340
1347
|
*/
|
|
1341
|
-
extractKana(
|
|
1342
|
-
return
|
|
1348
|
+
extractKana(text6) {
|
|
1349
|
+
return text6.match(/[\u3040-\u309F\u30A0-\u30FF]/g) || [];
|
|
1343
1350
|
},
|
|
1344
1351
|
/**
|
|
1345
1352
|
* 清理文本,移除特殊字符但保留日语字符
|
|
1346
1353
|
*/
|
|
1347
|
-
cleanText(
|
|
1348
|
-
return
|
|
1354
|
+
cleanText(text6) {
|
|
1355
|
+
return text6.replace(/[^\u3040-\u309F\u30A0-\u30FF\u4E00-\u9FAF\w\s]/g, "");
|
|
1349
1356
|
}
|
|
1350
1357
|
};
|
|
1351
1358
|
|
|
@@ -1434,11 +1441,11 @@ var fileUtils = {
|
|
|
1434
1441
|
* 生成唯一文件名
|
|
1435
1442
|
*/
|
|
1436
1443
|
generateUniqueFileName(originalName) {
|
|
1437
|
-
const
|
|
1444
|
+
const timestamp6 = Date.now();
|
|
1438
1445
|
const random = Math.random().toString(36).substring(2, 15);
|
|
1439
1446
|
const extension = this.getFileExtension(originalName);
|
|
1440
1447
|
const baseName = originalName.replace("." + extension, "");
|
|
1441
|
-
return extension ? baseName + "_" +
|
|
1448
|
+
return extension ? baseName + "_" + timestamp6 + "_" + random + "." + extension : baseName + "_" + timestamp6 + "_" + random;
|
|
1442
1449
|
},
|
|
1443
1450
|
/**
|
|
1444
1451
|
* 验证文件名是否有效
|
|
@@ -1508,28 +1515,28 @@ var stringUtils = {
|
|
|
1508
1515
|
/**
|
|
1509
1516
|
* 截断文本
|
|
1510
1517
|
*/
|
|
1511
|
-
truncate(
|
|
1512
|
-
if (
|
|
1513
|
-
return
|
|
1518
|
+
truncate(text6, length, suffix = "...") {
|
|
1519
|
+
if (text6.length <= length) return text6;
|
|
1520
|
+
return text6.substring(0, length - suffix.length) + suffix;
|
|
1514
1521
|
},
|
|
1515
1522
|
/**
|
|
1516
1523
|
* 首字母大写
|
|
1517
1524
|
*/
|
|
1518
|
-
capitalize(
|
|
1519
|
-
if (!
|
|
1520
|
-
return
|
|
1525
|
+
capitalize(text6) {
|
|
1526
|
+
if (!text6) return "";
|
|
1527
|
+
return text6.charAt(0).toUpperCase() + text6.slice(1).toLowerCase();
|
|
1521
1528
|
},
|
|
1522
1529
|
/**
|
|
1523
1530
|
* 驼峰转下划线
|
|
1524
1531
|
*/
|
|
1525
|
-
camelToSnake(
|
|
1526
|
-
return
|
|
1532
|
+
camelToSnake(text6) {
|
|
1533
|
+
return text6.replace(/[A-Z]/g, (letter) => "_" + letter.toLowerCase());
|
|
1527
1534
|
},
|
|
1528
1535
|
/**
|
|
1529
1536
|
* 下划线转驼峰
|
|
1530
1537
|
*/
|
|
1531
|
-
snakeToCamel(
|
|
1532
|
-
return
|
|
1538
|
+
snakeToCamel(text6) {
|
|
1539
|
+
return text6.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
1533
1540
|
},
|
|
1534
1541
|
/**
|
|
1535
1542
|
* 生成随机字符串
|
|
@@ -5648,9 +5655,9 @@ var UserInfoBar = ({ apiClient }) => {
|
|
|
5648
5655
|
))));
|
|
5649
5656
|
};
|
|
5650
5657
|
function DanmakuPanel({ onSend }) {
|
|
5651
|
-
const [
|
|
5658
|
+
const [text6, setText] = useState("");
|
|
5652
5659
|
const emit = () => {
|
|
5653
|
-
const value =
|
|
5660
|
+
const value = text6.trim();
|
|
5654
5661
|
if (!value) {
|
|
5655
5662
|
return;
|
|
5656
5663
|
}
|
|
@@ -5661,7 +5668,7 @@ function DanmakuPanel({ onSend }) {
|
|
|
5661
5668
|
"input",
|
|
5662
5669
|
{
|
|
5663
5670
|
type: "text",
|
|
5664
|
-
value:
|
|
5671
|
+
value: text6,
|
|
5665
5672
|
onChange: (event) => setText(event.target.value),
|
|
5666
5673
|
onKeyDown: (event) => {
|
|
5667
5674
|
if (event.key === "Enter") {
|
|
@@ -5777,8 +5784,8 @@ function useDanmakuController(options) {
|
|
|
5777
5784
|
cursorRef.current += 1;
|
|
5778
5785
|
}, []);
|
|
5779
5786
|
const send = useCallback(
|
|
5780
|
-
(
|
|
5781
|
-
const trimmed =
|
|
5787
|
+
(text6, color, sendOptions) => {
|
|
5788
|
+
const trimmed = text6.trim();
|
|
5782
5789
|
if (!trimmed) {
|
|
5783
5790
|
return null;
|
|
5784
5791
|
}
|
|
@@ -5815,26 +5822,26 @@ function useDanmakuController(options) {
|
|
|
5815
5822
|
[addIncoming, items, removeItem, send]
|
|
5816
5823
|
);
|
|
5817
5824
|
}
|
|
5818
|
-
function parseCommand(
|
|
5819
|
-
if (
|
|
5820
|
-
return { launchKind: "miku", content:
|
|
5825
|
+
function parseCommand(text6) {
|
|
5826
|
+
if (text6.startsWith("/miku ")) {
|
|
5827
|
+
return { launchKind: "miku", content: text6.replace("/miku ", "").trim() };
|
|
5821
5828
|
}
|
|
5822
|
-
if (
|
|
5829
|
+
if (text6 === "/miku") {
|
|
5823
5830
|
return { launchKind: "miku", content: "MIKU!" };
|
|
5824
5831
|
}
|
|
5825
|
-
if (
|
|
5826
|
-
return { launchKind: "avatar", content:
|
|
5832
|
+
if (text6.startsWith("/avatar ")) {
|
|
5833
|
+
return { launchKind: "avatar", content: text6.replace("/avatar ", "").trim() };
|
|
5827
5834
|
}
|
|
5828
|
-
if (
|
|
5835
|
+
if (text6 === "/avatar") {
|
|
5829
5836
|
return { launchKind: "avatar", content: "Avatar Firework!" };
|
|
5830
5837
|
}
|
|
5831
|
-
if (
|
|
5832
|
-
return { launchKind: "normal", content:
|
|
5838
|
+
if (text6.startsWith("/normal ")) {
|
|
5839
|
+
return { launchKind: "normal", content: text6.replace("/normal ", "").trim() };
|
|
5833
5840
|
}
|
|
5834
|
-
if (
|
|
5841
|
+
if (text6 === "/normal") {
|
|
5835
5842
|
return { launchKind: "normal", content: "Fireworks!" };
|
|
5836
5843
|
}
|
|
5837
|
-
return { content:
|
|
5844
|
+
return { content: text6 };
|
|
5838
5845
|
}
|
|
5839
5846
|
function createCircularSpriteTexture() {
|
|
5840
5847
|
const size = 64;
|
|
@@ -6516,12 +6523,12 @@ var WebSocketTransport = class {
|
|
|
6516
6523
|
}
|
|
6517
6524
|
};
|
|
6518
6525
|
function parseServerMessage(raw) {
|
|
6519
|
-
const
|
|
6520
|
-
if (!
|
|
6526
|
+
const text6 = decodeMessage(raw);
|
|
6527
|
+
if (!text6) {
|
|
6521
6528
|
return null;
|
|
6522
6529
|
}
|
|
6523
6530
|
try {
|
|
6524
|
-
return JSON.parse(
|
|
6531
|
+
return JSON.parse(text6);
|
|
6525
6532
|
} catch {
|
|
6526
6533
|
return null;
|
|
6527
6534
|
}
|
|
@@ -6788,8 +6795,8 @@ function MikuFireworks3D({
|
|
|
6788
6795
|
}
|
|
6789
6796
|
launch(payload);
|
|
6790
6797
|
};
|
|
6791
|
-
const handleSendDanmaku = (
|
|
6792
|
-
const result = send(
|
|
6798
|
+
const handleSendDanmaku = (text6) => {
|
|
6799
|
+
const result = send(text6, void 0, {
|
|
6793
6800
|
optimistic: !realtimeEnabled
|
|
6794
6801
|
});
|
|
6795
6802
|
if (!result) {
|
|
@@ -6888,9 +6895,9 @@ function useScreenReceiver(options) {
|
|
|
6888
6895
|
const peerRef = useRef({ pendingCandidates: [] });
|
|
6889
6896
|
const videoRef = useRef(null);
|
|
6890
6897
|
const appendLog = useCallback(
|
|
6891
|
-
(
|
|
6898
|
+
(text6) => {
|
|
6892
6899
|
logIdRef.current += 1;
|
|
6893
|
-
setLogs((prev) => [...prev, { id: logIdRef.current, text:
|
|
6900
|
+
setLogs((prev) => [...prev, { id: logIdRef.current, text: text6 }].slice(-maxLogs));
|
|
6894
6901
|
},
|
|
6895
6902
|
[maxLogs]
|
|
6896
6903
|
);
|
|
@@ -7395,15 +7402,92 @@ var renderElement = (element) => {
|
|
|
7395
7402
|
}
|
|
7396
7403
|
);
|
|
7397
7404
|
};
|
|
7398
|
-
var
|
|
7399
|
-
|
|
7400
|
-
|
|
7405
|
+
var clamp = (value, min, max) => Math.min(max, Math.max(min, value));
|
|
7406
|
+
var FestivalCardPageRenderer = ({
|
|
7407
|
+
page,
|
|
7408
|
+
editable = false,
|
|
7409
|
+
selectedElementId = null,
|
|
7410
|
+
onElementSelect,
|
|
7411
|
+
onElementChange
|
|
7412
|
+
}) => {
|
|
7413
|
+
const [draggingElementId, setDraggingElementId] = useState(null);
|
|
7414
|
+
const [resizingElementId, setResizingElementId] = useState(null);
|
|
7415
|
+
const stageRef = useRef(null);
|
|
7416
|
+
const interactionRef = useRef(null);
|
|
7417
|
+
const backgroundElement = useMemo(
|
|
7418
|
+
() => page.elements.find(
|
|
7419
|
+
(element) => element.type === "image" && Boolean(element.isBackground)
|
|
7420
|
+
),
|
|
7421
|
+
[page]
|
|
7401
7422
|
);
|
|
7402
|
-
const foregroundElements =
|
|
7423
|
+
const foregroundElements = useMemo(
|
|
7424
|
+
() => page.elements.filter((element) => !(element.type === "image" && element.isBackground)),
|
|
7425
|
+
[page]
|
|
7426
|
+
);
|
|
7427
|
+
const updateElementByPointer = (element, interaction, clientX, clientY) => {
|
|
7428
|
+
if (!onElementChange || interaction.rect.width <= 0 || interaction.rect.height <= 0) return;
|
|
7429
|
+
const xPercent = clamp((clientX - interaction.rect.left) / interaction.rect.width * 100, 0, 100);
|
|
7430
|
+
const yPercent = clamp((clientY - interaction.rect.top) / interaction.rect.height * 100, 0, 100);
|
|
7431
|
+
if (interaction.mode === "move") {
|
|
7432
|
+
onElementChange(element.id, { x: xPercent, y: yPercent });
|
|
7433
|
+
return;
|
|
7434
|
+
}
|
|
7435
|
+
const nextWidth = clamp(Math.abs(xPercent - element.x) * 2, 4, 100);
|
|
7436
|
+
if (element.type === "image") {
|
|
7437
|
+
const nextHeight = clamp(Math.abs(yPercent - element.y) * 2, 4, 100);
|
|
7438
|
+
onElementChange(element.id, { width: nextWidth, height: nextHeight });
|
|
7439
|
+
return;
|
|
7440
|
+
}
|
|
7441
|
+
onElementChange(element.id, { width: nextWidth });
|
|
7442
|
+
};
|
|
7443
|
+
const beginInteraction = (event, elementId, mode) => {
|
|
7444
|
+
if (!editable || !stageRef.current) return;
|
|
7445
|
+
event.preventDefault();
|
|
7446
|
+
event.stopPropagation();
|
|
7447
|
+
const rect = stageRef.current.getBoundingClientRect();
|
|
7448
|
+
interactionRef.current = {
|
|
7449
|
+
pointerId: event.pointerId,
|
|
7450
|
+
elementId,
|
|
7451
|
+
mode,
|
|
7452
|
+
rect
|
|
7453
|
+
};
|
|
7454
|
+
event.currentTarget.setPointerCapture(event.pointerId);
|
|
7455
|
+
onElementSelect?.(elementId);
|
|
7456
|
+
if (mode === "move") {
|
|
7457
|
+
setDraggingElementId(elementId);
|
|
7458
|
+
setResizingElementId(null);
|
|
7459
|
+
} else {
|
|
7460
|
+
setResizingElementId(elementId);
|
|
7461
|
+
setDraggingElementId(null);
|
|
7462
|
+
}
|
|
7463
|
+
const element = foregroundElements.find((item) => item.id === elementId);
|
|
7464
|
+
if (element) {
|
|
7465
|
+
updateElementByPointer(element, interactionRef.current, event.clientX, event.clientY);
|
|
7466
|
+
}
|
|
7467
|
+
};
|
|
7468
|
+
const handlePointerMove = (event) => {
|
|
7469
|
+
const interaction = interactionRef.current;
|
|
7470
|
+
if (!interaction || interaction.pointerId !== event.pointerId) return;
|
|
7471
|
+
const element = foregroundElements.find((item) => item.id === interaction.elementId);
|
|
7472
|
+
if (!element) return;
|
|
7473
|
+
updateElementByPointer(element, interaction, event.clientX, event.clientY);
|
|
7474
|
+
};
|
|
7475
|
+
const endInteraction = (event) => {
|
|
7476
|
+
const interaction = interactionRef.current;
|
|
7477
|
+
if (!interaction || interaction.pointerId !== event.pointerId) return;
|
|
7478
|
+
interactionRef.current = null;
|
|
7479
|
+
setDraggingElementId(null);
|
|
7480
|
+
setResizingElementId(null);
|
|
7481
|
+
};
|
|
7403
7482
|
return /* @__PURE__ */ React69__default.createElement(
|
|
7404
7483
|
"div",
|
|
7405
7484
|
{
|
|
7406
|
-
|
|
7485
|
+
ref: stageRef,
|
|
7486
|
+
onPointerMove: editable ? handlePointerMove : void 0,
|
|
7487
|
+
onPointerUp: editable ? endInteraction : void 0,
|
|
7488
|
+
onPointerCancel: editable ? endInteraction : void 0,
|
|
7489
|
+
onClick: editable ? () => onElementSelect?.(null) : void 0,
|
|
7490
|
+
className: `relative h-full w-full overflow-hidden rounded-2xl ${editable ? "touch-none" : ""}`,
|
|
7407
7491
|
style: {
|
|
7408
7492
|
backgroundColor: page.background?.color || "#0f172a",
|
|
7409
7493
|
backgroundImage: backgroundElement ? `url(${backgroundElement.src})` : page.background?.image ? `url(${page.background.image})` : void 0,
|
|
@@ -7412,17 +7496,249 @@ var FestivalCardPageRenderer = ({ page }) => {
|
|
|
7412
7496
|
}
|
|
7413
7497
|
},
|
|
7414
7498
|
/* @__PURE__ */ React69__default.createElement("div", { className: "absolute inset-0 bg-slate-950/20" }),
|
|
7415
|
-
foregroundElements.map(
|
|
7499
|
+
foregroundElements.map((element) => {
|
|
7500
|
+
if (!editable) {
|
|
7501
|
+
return renderElement(element);
|
|
7502
|
+
}
|
|
7503
|
+
const isSelected = selectedElementId === element.id;
|
|
7504
|
+
const isDragging = draggingElementId === element.id;
|
|
7505
|
+
const isResizing = resizingElementId === element.id;
|
|
7506
|
+
return /* @__PURE__ */ React69__default.createElement(
|
|
7507
|
+
"div",
|
|
7508
|
+
{
|
|
7509
|
+
key: element.id,
|
|
7510
|
+
role: "button",
|
|
7511
|
+
tabIndex: 0,
|
|
7512
|
+
onClick: (event) => {
|
|
7513
|
+
event.stopPropagation();
|
|
7514
|
+
onElementSelect?.(element.id);
|
|
7515
|
+
},
|
|
7516
|
+
onPointerDown: (event) => beginInteraction(event, element.id, "move"),
|
|
7517
|
+
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"}`,
|
|
7518
|
+
style: {
|
|
7519
|
+
...elementStyle(element),
|
|
7520
|
+
zIndex: isSelected ? 4 : 2
|
|
7521
|
+
}
|
|
7522
|
+
},
|
|
7523
|
+
element.type === "text" ? /* @__PURE__ */ React69__default.createElement(
|
|
7524
|
+
"div",
|
|
7525
|
+
{
|
|
7526
|
+
className: "rounded-md bg-black/20 px-2 py-1",
|
|
7527
|
+
style: {
|
|
7528
|
+
color: element.color || "#f8fafc",
|
|
7529
|
+
fontSize: element.fontSize || 18,
|
|
7530
|
+
fontWeight: element.fontWeight || 500,
|
|
7531
|
+
fontFamily: element.fontFamily || "inherit",
|
|
7532
|
+
textAlign: element.align || "left",
|
|
7533
|
+
lineHeight: 1.45,
|
|
7534
|
+
whiteSpace: "pre-wrap"
|
|
7535
|
+
}
|
|
7536
|
+
},
|
|
7537
|
+
element.content
|
|
7538
|
+
) : /* @__PURE__ */ React69__default.createElement(
|
|
7539
|
+
"img",
|
|
7540
|
+
{
|
|
7541
|
+
src: element.src,
|
|
7542
|
+
alt: element.alt || "festival-card-image",
|
|
7543
|
+
draggable: false,
|
|
7544
|
+
className: "pointer-events-none h-full w-full",
|
|
7545
|
+
style: {
|
|
7546
|
+
objectFit: element.fit || "cover",
|
|
7547
|
+
borderRadius: element.borderRadius || 0,
|
|
7548
|
+
overflow: "hidden",
|
|
7549
|
+
boxShadow: "0 12px 30px rgba(2, 6, 23, 0.32)"
|
|
7550
|
+
}
|
|
7551
|
+
}
|
|
7552
|
+
),
|
|
7553
|
+
/* @__PURE__ */ React69__default.createElement(
|
|
7554
|
+
"button",
|
|
7555
|
+
{
|
|
7556
|
+
type: "button",
|
|
7557
|
+
"aria-label": "resize",
|
|
7558
|
+
onPointerDown: (event) => beginInteraction(event, element.id, "resize"),
|
|
7559
|
+
className: "absolute -bottom-2 -right-2 h-4 w-4 rounded-full border border-white bg-sky-500 shadow"
|
|
7560
|
+
}
|
|
7561
|
+
)
|
|
7562
|
+
);
|
|
7563
|
+
})
|
|
7416
7564
|
);
|
|
7417
7565
|
};
|
|
7418
7566
|
|
|
7419
7567
|
// src/festivalCard/components/FestivalCardBook3D.tsx
|
|
7420
|
-
var
|
|
7421
|
-
const
|
|
7568
|
+
var loadImage2 = (src) => new Promise((resolve, reject) => {
|
|
7569
|
+
const image = new window.Image();
|
|
7570
|
+
image.crossOrigin = "anonymous";
|
|
7571
|
+
image.decoding = "async";
|
|
7572
|
+
image.onload = () => resolve(image);
|
|
7573
|
+
image.onerror = () => reject(new Error(`\u56FE\u7247\u52A0\u8F7D\u5931\u8D25: ${src}`));
|
|
7574
|
+
image.src = src;
|
|
7575
|
+
});
|
|
7576
|
+
var drawImageWithFit = (ctx, image, left, top, width, height, fit) => {
|
|
7577
|
+
const imageRatio = image.width / image.height;
|
|
7578
|
+
const boxRatio = width / height;
|
|
7579
|
+
let drawWidth = width;
|
|
7580
|
+
let drawHeight = height;
|
|
7581
|
+
let offsetX = left;
|
|
7582
|
+
let offsetY = top;
|
|
7583
|
+
if (fit === "cover") {
|
|
7584
|
+
if (imageRatio > boxRatio) {
|
|
7585
|
+
drawHeight = height;
|
|
7586
|
+
drawWidth = height * imageRatio;
|
|
7587
|
+
offsetX = left - (drawWidth - width) / 2;
|
|
7588
|
+
} else {
|
|
7589
|
+
drawWidth = width;
|
|
7590
|
+
drawHeight = width / imageRatio;
|
|
7591
|
+
offsetY = top - (drawHeight - height) / 2;
|
|
7592
|
+
}
|
|
7593
|
+
} else if (imageRatio > boxRatio) {
|
|
7594
|
+
drawWidth = width;
|
|
7595
|
+
drawHeight = width / imageRatio;
|
|
7596
|
+
offsetY = top + (height - drawHeight) / 2;
|
|
7597
|
+
} else {
|
|
7598
|
+
drawHeight = height;
|
|
7599
|
+
drawWidth = height * imageRatio;
|
|
7600
|
+
offsetX = left + (width - drawWidth) / 2;
|
|
7601
|
+
}
|
|
7602
|
+
ctx.drawImage(image, offsetX, offsetY, drawWidth, drawHeight);
|
|
7603
|
+
};
|
|
7604
|
+
var withRoundedClip = (ctx, left, top, width, height, radius, draw) => {
|
|
7605
|
+
const safeRadius = Math.max(0, Math.min(radius, Math.min(width, height) / 2));
|
|
7606
|
+
if (safeRadius <= 0) {
|
|
7607
|
+
draw();
|
|
7608
|
+
return;
|
|
7609
|
+
}
|
|
7610
|
+
ctx.save();
|
|
7611
|
+
ctx.beginPath();
|
|
7612
|
+
ctx.moveTo(left + safeRadius, top);
|
|
7613
|
+
ctx.lineTo(left + width - safeRadius, top);
|
|
7614
|
+
ctx.quadraticCurveTo(left + width, top, left + width, top + safeRadius);
|
|
7615
|
+
ctx.lineTo(left + width, top + height - safeRadius);
|
|
7616
|
+
ctx.quadraticCurveTo(left + width, top + height, left + width - safeRadius, top + height);
|
|
7617
|
+
ctx.lineTo(left + safeRadius, top + height);
|
|
7618
|
+
ctx.quadraticCurveTo(left, top + height, left, top + height - safeRadius);
|
|
7619
|
+
ctx.lineTo(left, top + safeRadius);
|
|
7620
|
+
ctx.quadraticCurveTo(left, top, left + safeRadius, top);
|
|
7621
|
+
ctx.closePath();
|
|
7622
|
+
ctx.clip();
|
|
7623
|
+
draw();
|
|
7624
|
+
ctx.restore();
|
|
7625
|
+
};
|
|
7626
|
+
var drawMultilineText = (ctx, text6, left, top, maxWidth, lineHeight) => {
|
|
7627
|
+
const paragraphs = text6.split("\n");
|
|
7628
|
+
let currentY = top;
|
|
7629
|
+
paragraphs.forEach((paragraph, index) => {
|
|
7630
|
+
const words = paragraph.split("");
|
|
7631
|
+
let line = "";
|
|
7632
|
+
for (const word of words) {
|
|
7633
|
+
const testLine = line + word;
|
|
7634
|
+
if (ctx.measureText(testLine).width > maxWidth && line) {
|
|
7635
|
+
ctx.fillText(line, left, currentY);
|
|
7636
|
+
line = word;
|
|
7637
|
+
currentY += lineHeight;
|
|
7638
|
+
} else {
|
|
7639
|
+
line = testLine;
|
|
7640
|
+
}
|
|
7641
|
+
}
|
|
7642
|
+
ctx.fillText(line, left, currentY);
|
|
7643
|
+
currentY += lineHeight;
|
|
7644
|
+
if (index < paragraphs.length - 1) {
|
|
7645
|
+
currentY += lineHeight * 0.2;
|
|
7646
|
+
}
|
|
7647
|
+
});
|
|
7648
|
+
};
|
|
7649
|
+
var exportPageToPng = async (page, fileName) => {
|
|
7650
|
+
const width = 1080;
|
|
7651
|
+
const height = 1440;
|
|
7652
|
+
const canvas = document.createElement("canvas");
|
|
7653
|
+
canvas.width = width;
|
|
7654
|
+
canvas.height = height;
|
|
7655
|
+
const ctx = canvas.getContext("2d");
|
|
7656
|
+
if (!ctx) throw new Error("\u65E0\u6CD5\u521B\u5EFA Canvas \u4E0A\u4E0B\u6587");
|
|
7657
|
+
ctx.fillStyle = page.background?.color || "#0f172a";
|
|
7658
|
+
ctx.fillRect(0, 0, width, height);
|
|
7659
|
+
const backgroundElement = page.elements.find(
|
|
7660
|
+
(element) => element.type === "image" && Boolean(element.isBackground)
|
|
7661
|
+
);
|
|
7662
|
+
const backgroundImageSrc = backgroundElement?.src || page.background?.image;
|
|
7663
|
+
if (backgroundImageSrc) {
|
|
7664
|
+
const image = await loadImage2(backgroundImageSrc);
|
|
7665
|
+
drawImageWithFit(ctx, image, 0, 0, width, height, "cover");
|
|
7666
|
+
}
|
|
7667
|
+
const foregroundElements = page.elements.filter((element) => !(element.type === "image" && element.isBackground));
|
|
7668
|
+
for (const element of foregroundElements) {
|
|
7669
|
+
const elementWidth = width * (element.width ?? 70) / 100;
|
|
7670
|
+
const elementHeight = element.height ? height * element.height / 100 : void 0;
|
|
7671
|
+
const centerX = width * element.x / 100;
|
|
7672
|
+
const centerY = height * element.y / 100;
|
|
7673
|
+
const left = centerX - elementWidth / 2;
|
|
7674
|
+
if (element.type === "image") {
|
|
7675
|
+
const image = await loadImage2(element.src);
|
|
7676
|
+
const drawHeight = elementHeight ?? elementWidth;
|
|
7677
|
+
const boxTop = centerY - drawHeight / 2;
|
|
7678
|
+
withRoundedClip(ctx, left, boxTop, elementWidth, drawHeight, element.borderRadius ?? 0, () => {
|
|
7679
|
+
drawImageWithFit(ctx, image, left, boxTop, elementWidth, drawHeight, element.fit || "cover");
|
|
7680
|
+
});
|
|
7681
|
+
continue;
|
|
7682
|
+
}
|
|
7683
|
+
const fontSize = (element.fontSize || 18) * 1.5;
|
|
7684
|
+
ctx.fillStyle = element.color || "#f8fafc";
|
|
7685
|
+
ctx.font = `${element.fontWeight || 500} ${fontSize}px ${element.fontFamily || "sans-serif"}`;
|
|
7686
|
+
ctx.textBaseline = "top";
|
|
7687
|
+
ctx.textAlign = element.align || "left";
|
|
7688
|
+
const textX = element.align === "center" ? centerX : element.align === "right" ? left + elementWidth : left;
|
|
7689
|
+
drawMultilineText(ctx, element.content || "", textX, centerY - fontSize * 0.72, elementWidth, fontSize * 1.45);
|
|
7690
|
+
}
|
|
7691
|
+
const blob = await new Promise((resolve) => canvas.toBlob(resolve, "image/png"));
|
|
7692
|
+
if (!blob) throw new Error("\u5BFC\u51FA\u5931\u8D25\uFF0C\u8BF7\u91CD\u8BD5");
|
|
7693
|
+
const url = URL.createObjectURL(blob);
|
|
7694
|
+
const anchor = document.createElement("a");
|
|
7695
|
+
anchor.href = url;
|
|
7696
|
+
anchor.download = fileName;
|
|
7697
|
+
anchor.click();
|
|
7698
|
+
URL.revokeObjectURL(url);
|
|
7699
|
+
};
|
|
7700
|
+
var FestivalCardBook3D = ({
|
|
7701
|
+
config,
|
|
7702
|
+
className,
|
|
7703
|
+
editable = false,
|
|
7704
|
+
enableExportImage = !editable,
|
|
7705
|
+
currentPage: currentPageProp,
|
|
7706
|
+
onCurrentPageChange,
|
|
7707
|
+
selectedElementId = null,
|
|
7708
|
+
onSelectedElementChange,
|
|
7709
|
+
onElementChange
|
|
7710
|
+
}) => {
|
|
7711
|
+
const [internalCurrentPage, setInternalCurrentPage] = useState(0);
|
|
7712
|
+
const [exporting, setExporting] = useState(false);
|
|
7422
7713
|
const normalized = useMemo(() => normalizeFestivalCardConfig(config), [config]);
|
|
7423
7714
|
const pages = normalized.pages;
|
|
7715
|
+
const currentPage = typeof currentPageProp === "number" ? currentPageProp : internalCurrentPage;
|
|
7716
|
+
const setCurrentPage = (updater) => {
|
|
7717
|
+
const prev = currentPage;
|
|
7718
|
+
const nextValue = typeof updater === "function" ? updater(prev) : updater;
|
|
7719
|
+
if (typeof currentPageProp === "number") {
|
|
7720
|
+
onCurrentPageChange?.(nextValue);
|
|
7721
|
+
return;
|
|
7722
|
+
}
|
|
7723
|
+
setInternalCurrentPage(nextValue);
|
|
7724
|
+
onCurrentPageChange?.(nextValue);
|
|
7725
|
+
};
|
|
7424
7726
|
const canPrev = currentPage > 0;
|
|
7425
7727
|
const canNext = currentPage < pages.length - 1;
|
|
7728
|
+
const currentPageData = pages[currentPage];
|
|
7729
|
+
const handleExportCurrentPage = async () => {
|
|
7730
|
+
if (!currentPageData || exporting) return;
|
|
7731
|
+
setExporting(true);
|
|
7732
|
+
try {
|
|
7733
|
+
const base = normalized.id || "festival-card";
|
|
7734
|
+
const fileName = `${base}-page-${currentPage + 1}.png`;
|
|
7735
|
+
await exportPageToPng(currentPageData, fileName);
|
|
7736
|
+
} catch (error) {
|
|
7737
|
+
window.alert(error.message || "\u5BFC\u51FA\u56FE\u7247\u5931\u8D25");
|
|
7738
|
+
} finally {
|
|
7739
|
+
setExporting(false);
|
|
7740
|
+
}
|
|
7741
|
+
};
|
|
7426
7742
|
return /* @__PURE__ */ React69__default.createElement("div", { className }, /* @__PURE__ */ React69__default.createElement("div", { className: "w-full min-h-screen px-0 py-4" }, /* @__PURE__ */ React69__default.createElement("div", { className: "mx-auto w-full text-center text-slate-100" }, /* @__PURE__ */ React69__default.createElement("h3", { className: "mb-3 text-lg font-semibold" }, normalized.coverTitle || "Festival Card")), /* @__PURE__ */ React69__default.createElement("div", { className: "mx-auto w-full" }, /* @__PURE__ */ React69__default.createElement("div", { className: "relative h-[calc(100vh-170px)] min-h-[460px]" }, pages.map((page, index) => /* @__PURE__ */ React69__default.createElement(
|
|
7427
7743
|
"div",
|
|
7428
7744
|
{
|
|
@@ -7433,7 +7749,16 @@ var FestivalCardBook3D = ({ config, className }) => {
|
|
|
7433
7749
|
pointerEvents: index === currentPage ? "auto" : "none"
|
|
7434
7750
|
}
|
|
7435
7751
|
},
|
|
7436
|
-
/* @__PURE__ */ React69__default.createElement(
|
|
7752
|
+
/* @__PURE__ */ React69__default.createElement(
|
|
7753
|
+
FestivalCardPageRenderer,
|
|
7754
|
+
{
|
|
7755
|
+
page,
|
|
7756
|
+
editable: editable && index === currentPage,
|
|
7757
|
+
selectedElementId,
|
|
7758
|
+
onElementSelect: onSelectedElementChange,
|
|
7759
|
+
onElementChange: (elementId, patch) => onElementChange?.(index, elementId, patch)
|
|
7760
|
+
}
|
|
7761
|
+
)
|
|
7437
7762
|
)))), /* @__PURE__ */ React69__default.createElement("div", { className: "mt-4 flex justify-center gap-3" }, /* @__PURE__ */ React69__default.createElement(
|
|
7438
7763
|
"button",
|
|
7439
7764
|
{
|
|
@@ -7461,6 +7786,24 @@ var FestivalCardBook3D = ({ config, className }) => {
|
|
|
7461
7786
|
controls: true,
|
|
7462
7787
|
className: "mt-3 w-full"
|
|
7463
7788
|
}
|
|
7789
|
+
) : null, enableExportImage ? /* @__PURE__ */ React69__default.createElement(
|
|
7790
|
+
FloatingMenu_default,
|
|
7791
|
+
{
|
|
7792
|
+
initialPosition: { x: 24, y: 120 },
|
|
7793
|
+
trigger: /* @__PURE__ */ React69__default.createElement("div", { className: "text-lg leading-none text-slate-700", "aria-hidden": true }, "\u2301"),
|
|
7794
|
+
menu: /* @__PURE__ */ React69__default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React69__default.createElement("div", { className: "text-xs font-semibold tracking-wide text-slate-500" }, "\u8D3A\u5361\u5DE5\u5177"), /* @__PURE__ */ React69__default.createElement(
|
|
7795
|
+
"button",
|
|
7796
|
+
{
|
|
7797
|
+
type: "button",
|
|
7798
|
+
onClick: () => void handleExportCurrentPage(),
|
|
7799
|
+
disabled: exporting,
|
|
7800
|
+
className: "rounded-lg bg-sky-600 px-3 py-2 text-left text-sm font-medium text-white disabled:opacity-60"
|
|
7801
|
+
},
|
|
7802
|
+
exporting ? "\u5BFC\u51FA\u4E2D..." : `\u5BFC\u51FA\u7B2C ${currentPage + 1} \u9875 PNG`
|
|
7803
|
+
)),
|
|
7804
|
+
triggerClassName: "bg-white/95 backdrop-blur",
|
|
7805
|
+
menuClassName: "bg-white/95 backdrop-blur"
|
|
7806
|
+
}
|
|
7464
7807
|
) : null);
|
|
7465
7808
|
};
|
|
7466
7809
|
var createTextElement = (pageIndex) => ({
|
|
@@ -7487,26 +7830,25 @@ var createImageElement = (pageIndex) => ({
|
|
|
7487
7830
|
fit: "cover",
|
|
7488
7831
|
borderRadius: 12
|
|
7489
7832
|
});
|
|
7490
|
-
var FestivalCardConfigEditor = ({
|
|
7491
|
-
|
|
7492
|
-
|
|
7493
|
-
|
|
7494
|
-
|
|
7495
|
-
|
|
7833
|
+
var FestivalCardConfigEditor = ({
|
|
7834
|
+
value,
|
|
7835
|
+
onChange,
|
|
7836
|
+
activePageIndex: activePageIndexProp,
|
|
7837
|
+
onActivePageIndexChange,
|
|
7838
|
+
selectedElementId
|
|
7839
|
+
}) => {
|
|
7840
|
+
const [internalActivePageIndex, setInternalActivePageIndex] = useState(0);
|
|
7841
|
+
const activePageIndex = activePageIndexProp ?? internalActivePageIndex;
|
|
7842
|
+
const setActivePageIndex = (index) => {
|
|
7843
|
+
if (typeof activePageIndexProp === "number") {
|
|
7844
|
+
onActivePageIndexChange?.(index);
|
|
7845
|
+
return;
|
|
7846
|
+
}
|
|
7847
|
+
setInternalActivePageIndex(index);
|
|
7848
|
+
};
|
|
7496
7849
|
const page = value.pages[activePageIndex];
|
|
7497
7850
|
const canEditPage = Boolean(page);
|
|
7498
7851
|
const pageOptions = useMemo(() => value.pages.map((_, index) => index), [value.pages]);
|
|
7499
|
-
const backgroundElement = useMemo(
|
|
7500
|
-
() => page?.elements.find(
|
|
7501
|
-
(element) => element.type === "image" && Boolean(element.isBackground)
|
|
7502
|
-
),
|
|
7503
|
-
[page]
|
|
7504
|
-
);
|
|
7505
|
-
const foregroundElements = useMemo(
|
|
7506
|
-
() => (page?.elements ?? []).filter((element) => !(element.type === "image" && element.isBackground)),
|
|
7507
|
-
[page]
|
|
7508
|
-
);
|
|
7509
|
-
const clampPercent = (valueToClamp) => Math.max(0, Math.min(100, Number.isFinite(valueToClamp) ? valueToClamp : 0));
|
|
7510
7852
|
const handlePageCountChange = (nextRaw) => {
|
|
7511
7853
|
const next = Number.isFinite(nextRaw) ? Math.max(1, Math.min(12, Math.floor(nextRaw))) : value.pages.length;
|
|
7512
7854
|
const resized = resizeFestivalCardPages(value, next);
|
|
@@ -7543,38 +7885,6 @@ var FestivalCardConfigEditor = ({ value, onChange }) => {
|
|
|
7543
7885
|
pages: value.pages.map((p, index) => index === activePageIndex ? { ...p, ...patch } : p)
|
|
7544
7886
|
});
|
|
7545
7887
|
};
|
|
7546
|
-
const moveElementWithPointer = (elementId, clientX, clientY, rect) => {
|
|
7547
|
-
const bounds = rect || previewRef.current?.getBoundingClientRect();
|
|
7548
|
-
if (!bounds || bounds.width <= 0 || bounds.height <= 0) return;
|
|
7549
|
-
const x = clampPercent((clientX - bounds.left) / bounds.width * 100);
|
|
7550
|
-
const y = clampPercent((clientY - bounds.top) / bounds.height * 100);
|
|
7551
|
-
updateElement(elementId, { x, y });
|
|
7552
|
-
};
|
|
7553
|
-
const handleElementPointerDown = (event, elementId) => {
|
|
7554
|
-
if (!previewRef.current) return;
|
|
7555
|
-
event.preventDefault();
|
|
7556
|
-
const rect = previewRef.current.getBoundingClientRect();
|
|
7557
|
-
dragStateRef.current = {
|
|
7558
|
-
pointerId: event.pointerId,
|
|
7559
|
-
elementId,
|
|
7560
|
-
rect
|
|
7561
|
-
};
|
|
7562
|
-
event.currentTarget.setPointerCapture(event.pointerId);
|
|
7563
|
-
setActiveElementId(elementId);
|
|
7564
|
-
setDraggingElementId(elementId);
|
|
7565
|
-
moveElementWithPointer(elementId, event.clientX, event.clientY, rect);
|
|
7566
|
-
};
|
|
7567
|
-
const handlePreviewPointerMove = (event) => {
|
|
7568
|
-
const dragState = dragStateRef.current;
|
|
7569
|
-
if (!dragState || dragState.pointerId !== event.pointerId) return;
|
|
7570
|
-
moveElementWithPointer(dragState.elementId, event.clientX, event.clientY, dragState.rect);
|
|
7571
|
-
};
|
|
7572
|
-
const endPointerDrag = (event) => {
|
|
7573
|
-
const dragState = dragStateRef.current;
|
|
7574
|
-
if (!dragState || dragState.pointerId !== event.pointerId) return;
|
|
7575
|
-
dragStateRef.current = null;
|
|
7576
|
-
setDraggingElementId(null);
|
|
7577
|
-
};
|
|
7578
7888
|
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";
|
|
7579
7889
|
return /* @__PURE__ */ React69__default.createElement("div", { className: "rounded-2xl border border-slate-200 bg-white p-4 text-slate-900 shadow-sm" }, /* @__PURE__ */ React69__default.createElement("div", { className: "grid gap-3" }, /* @__PURE__ */ React69__default.createElement("label", { className: "grid gap-1.5" }, /* @__PURE__ */ React69__default.createElement("span", { className: "text-sm font-medium text-slate-700" }, "\u9875\u9762\u6570\u91CF"), /* @__PURE__ */ React69__default.createElement(
|
|
7580
7890
|
"input",
|
|
@@ -7668,250 +7978,238 @@ var FestivalCardConfigEditor = ({ value, onChange }) => {
|
|
|
7668
7978
|
className: "rounded-lg bg-sky-600 px-3 py-2 text-sm font-medium text-white"
|
|
7669
7979
|
},
|
|
7670
7980
|
"+ \u56FE\u7247"
|
|
7671
|
-
)), /* @__PURE__ */ React69__default.createElement("div", { className: "
|
|
7981
|
+
)), /* @__PURE__ */ React69__default.createElement("div", { className: "grid max-h-[340px] gap-2.5 overflow-auto pr-1" }, (page?.elements ?? []).map((element) => /* @__PURE__ */ React69__default.createElement(
|
|
7672
7982
|
"div",
|
|
7673
7983
|
{
|
|
7674
|
-
|
|
7675
|
-
|
|
7676
|
-
|
|
7677
|
-
|
|
7678
|
-
|
|
7679
|
-
|
|
7680
|
-
|
|
7681
|
-
|
|
7682
|
-
|
|
7683
|
-
|
|
7984
|
+
key: element.id,
|
|
7985
|
+
className: `rounded-xl border bg-slate-50 p-3 ${selectedElementId === element.id ? "border-sky-400 ring-2 ring-sky-100" : "border-slate-200"}`
|
|
7986
|
+
},
|
|
7987
|
+
/* @__PURE__ */ React69__default.createElement("div", { className: "mb-2 flex items-center justify-between" }, /* @__PURE__ */ React69__default.createElement("div", { className: "text-xs font-semibold tracking-wide text-slate-500" }, element.type.toUpperCase()), /* @__PURE__ */ React69__default.createElement(
|
|
7988
|
+
"button",
|
|
7989
|
+
{
|
|
7990
|
+
type: "button",
|
|
7991
|
+
onClick: () => removeElement(element.id),
|
|
7992
|
+
className: "rounded-md border border-rose-300 bg-rose-50 px-2 py-1 text-xs font-medium text-rose-700"
|
|
7993
|
+
},
|
|
7994
|
+
"\u5220\u9664"
|
|
7995
|
+
)),
|
|
7996
|
+
element.type === "text" ? /* @__PURE__ */ React69__default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React69__default.createElement(
|
|
7997
|
+
"textarea",
|
|
7998
|
+
{
|
|
7999
|
+
value: element.content,
|
|
8000
|
+
onChange: (event) => updateElement(element.id, { content: event.target.value }),
|
|
8001
|
+
rows: 3,
|
|
8002
|
+
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"
|
|
8003
|
+
}
|
|
8004
|
+
), /* @__PURE__ */ React69__default.createElement("div", { className: "grid gap-2 sm:grid-cols-2" }, /* @__PURE__ */ React69__default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "X(%)", /* @__PURE__ */ React69__default.createElement(
|
|
8005
|
+
"input",
|
|
8006
|
+
{
|
|
8007
|
+
type: "number",
|
|
8008
|
+
value: element.x,
|
|
8009
|
+
onChange: (event) => updateElement(element.id, { x: Number(event.target.value) }),
|
|
8010
|
+
className: numberFieldClassName
|
|
8011
|
+
}
|
|
8012
|
+
)), /* @__PURE__ */ React69__default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "Y(%)", /* @__PURE__ */ React69__default.createElement(
|
|
8013
|
+
"input",
|
|
8014
|
+
{
|
|
8015
|
+
type: "number",
|
|
8016
|
+
value: element.y,
|
|
8017
|
+
onChange: (event) => updateElement(element.id, { y: Number(event.target.value) }),
|
|
8018
|
+
className: numberFieldClassName
|
|
8019
|
+
}
|
|
8020
|
+
)), /* @__PURE__ */ React69__default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "\u5BBD\u5EA6(%)", /* @__PURE__ */ React69__default.createElement(
|
|
8021
|
+
"input",
|
|
8022
|
+
{
|
|
8023
|
+
type: "number",
|
|
8024
|
+
value: element.width ?? 70,
|
|
8025
|
+
onChange: (event) => updateElement(element.id, { width: Number(event.target.value) }),
|
|
8026
|
+
className: numberFieldClassName
|
|
8027
|
+
}
|
|
8028
|
+
)), /* @__PURE__ */ React69__default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "\u5B57\u53F7(px)", /* @__PURE__ */ React69__default.createElement(
|
|
8029
|
+
"input",
|
|
8030
|
+
{
|
|
8031
|
+
type: "number",
|
|
8032
|
+
value: element.fontSize ?? 18,
|
|
8033
|
+
onChange: (event) => updateElement(element.id, { fontSize: Number(event.target.value) }),
|
|
8034
|
+
className: numberFieldClassName
|
|
8035
|
+
}
|
|
8036
|
+
)), /* @__PURE__ */ React69__default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "\u5B57\u91CD", /* @__PURE__ */ React69__default.createElement(
|
|
8037
|
+
"input",
|
|
8038
|
+
{
|
|
8039
|
+
type: "number",
|
|
8040
|
+
min: 100,
|
|
8041
|
+
max: 900,
|
|
8042
|
+
step: 100,
|
|
8043
|
+
value: element.fontWeight ?? 500,
|
|
8044
|
+
onChange: (event) => updateElement(element.id, { fontWeight: Number(event.target.value) }),
|
|
8045
|
+
className: numberFieldClassName
|
|
8046
|
+
}
|
|
8047
|
+
)), /* @__PURE__ */ React69__default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "\u5BF9\u9F50", /* @__PURE__ */ React69__default.createElement(
|
|
8048
|
+
"select",
|
|
8049
|
+
{
|
|
8050
|
+
value: element.align || "left",
|
|
8051
|
+
onChange: (event) => updateElement(element.id, { align: event.target.value }),
|
|
8052
|
+
className: numberFieldClassName
|
|
8053
|
+
},
|
|
8054
|
+
/* @__PURE__ */ React69__default.createElement("option", { value: "left" }, "left"),
|
|
8055
|
+
/* @__PURE__ */ React69__default.createElement("option", { value: "center" }, "center"),
|
|
8056
|
+
/* @__PURE__ */ React69__default.createElement("option", { value: "right" }, "right")
|
|
8057
|
+
)), /* @__PURE__ */ React69__default.createElement("label", { className: "grid gap-1 text-xs text-slate-600 sm:col-span-2" }, "\u5B57\u4F53", /* @__PURE__ */ React69__default.createElement(
|
|
8058
|
+
"input",
|
|
8059
|
+
{
|
|
8060
|
+
type: "text",
|
|
8061
|
+
value: element.fontFamily || "",
|
|
8062
|
+
onChange: (event) => updateElement(element.id, { fontFamily: event.target.value }),
|
|
8063
|
+
placeholder: "inherit / serif / sans-serif / PingFang SC",
|
|
8064
|
+
className: numberFieldClassName
|
|
8065
|
+
}
|
|
8066
|
+
)), /* @__PURE__ */ React69__default.createElement("label", { className: "grid gap-1 text-xs text-slate-600 sm:col-span-2" }, "\u6587\u5B57\u989C\u8272", /* @__PURE__ */ React69__default.createElement("div", { className: "grid grid-cols-[64px_1fr] gap-2" }, /* @__PURE__ */ React69__default.createElement(
|
|
8067
|
+
"input",
|
|
8068
|
+
{
|
|
8069
|
+
type: "color",
|
|
8070
|
+
value: element.color || "#ffffff",
|
|
8071
|
+
onChange: (event) => updateElement(element.id, { color: event.target.value }),
|
|
8072
|
+
className: "h-10 rounded-lg border border-slate-300 bg-white p-1"
|
|
8073
|
+
}
|
|
8074
|
+
), /* @__PURE__ */ React69__default.createElement(
|
|
8075
|
+
"input",
|
|
8076
|
+
{
|
|
8077
|
+
type: "text",
|
|
8078
|
+
value: element.color || "#ffffff",
|
|
8079
|
+
onChange: (event) => updateElement(element.id, { color: event.target.value }),
|
|
8080
|
+
className: numberFieldClassName
|
|
8081
|
+
}
|
|
8082
|
+
))))) : /* @__PURE__ */ React69__default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React69__default.createElement(
|
|
8083
|
+
"input",
|
|
8084
|
+
{
|
|
8085
|
+
type: "url",
|
|
8086
|
+
value: element.src,
|
|
8087
|
+
onChange: (event) => updateElement(element.id, { src: event.target.value }),
|
|
8088
|
+
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"
|
|
8089
|
+
}
|
|
8090
|
+
), /* @__PURE__ */ React69__default.createElement("div", { className: "grid gap-2 sm:grid-cols-2" }, /* @__PURE__ */ React69__default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "X(%)", /* @__PURE__ */ React69__default.createElement(
|
|
8091
|
+
"input",
|
|
8092
|
+
{
|
|
8093
|
+
type: "number",
|
|
8094
|
+
value: element.x,
|
|
8095
|
+
onChange: (event) => updateElement(element.id, { x: Number(event.target.value) }),
|
|
8096
|
+
className: numberFieldClassName
|
|
8097
|
+
}
|
|
8098
|
+
)), /* @__PURE__ */ React69__default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "Y(%)", /* @__PURE__ */ React69__default.createElement(
|
|
8099
|
+
"input",
|
|
8100
|
+
{
|
|
8101
|
+
type: "number",
|
|
8102
|
+
value: element.y,
|
|
8103
|
+
onChange: (event) => updateElement(element.id, { y: Number(event.target.value) }),
|
|
8104
|
+
className: numberFieldClassName
|
|
8105
|
+
}
|
|
8106
|
+
)), /* @__PURE__ */ React69__default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "\u5BBD\u5EA6(%)", /* @__PURE__ */ React69__default.createElement(
|
|
8107
|
+
"input",
|
|
8108
|
+
{
|
|
8109
|
+
type: "number",
|
|
8110
|
+
value: element.width ?? 60,
|
|
8111
|
+
onChange: (event) => updateElement(element.id, { width: Number(event.target.value) }),
|
|
8112
|
+
className: numberFieldClassName
|
|
8113
|
+
}
|
|
8114
|
+
)), /* @__PURE__ */ React69__default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "\u9AD8\u5EA6(%)", /* @__PURE__ */ React69__default.createElement(
|
|
8115
|
+
"input",
|
|
8116
|
+
{
|
|
8117
|
+
type: "number",
|
|
8118
|
+
value: element.height ?? 40,
|
|
8119
|
+
onChange: (event) => updateElement(element.id, { height: Number(event.target.value) }),
|
|
8120
|
+
className: numberFieldClassName
|
|
8121
|
+
}
|
|
8122
|
+
)), /* @__PURE__ */ React69__default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "\u5706\u89D2(px)", /* @__PURE__ */ React69__default.createElement(
|
|
8123
|
+
"input",
|
|
8124
|
+
{
|
|
8125
|
+
type: "number",
|
|
8126
|
+
value: element.borderRadius ?? 0,
|
|
8127
|
+
onChange: (event) => updateElement(element.id, { borderRadius: Number(event.target.value) }),
|
|
8128
|
+
className: numberFieldClassName
|
|
8129
|
+
}
|
|
8130
|
+
)), /* @__PURE__ */ React69__default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "\u586B\u5145", /* @__PURE__ */ React69__default.createElement(
|
|
8131
|
+
"select",
|
|
8132
|
+
{
|
|
8133
|
+
value: element.fit || "cover",
|
|
8134
|
+
onChange: (event) => updateElement(element.id, { fit: event.target.value }),
|
|
8135
|
+
className: numberFieldClassName
|
|
8136
|
+
},
|
|
8137
|
+
/* @__PURE__ */ React69__default.createElement("option", { value: "cover" }, "cover"),
|
|
8138
|
+
/* @__PURE__ */ React69__default.createElement("option", { value: "contain" }, "contain")
|
|
8139
|
+
))), /* @__PURE__ */ React69__default.createElement("label", { className: "inline-flex items-center gap-2 text-sm text-slate-700" }, /* @__PURE__ */ React69__default.createElement(
|
|
8140
|
+
"input",
|
|
8141
|
+
{
|
|
8142
|
+
type: "checkbox",
|
|
8143
|
+
checked: Boolean(element.isBackground),
|
|
8144
|
+
onChange: (event) => updateElement(element.id, { isBackground: event.target.checked }),
|
|
8145
|
+
className: "h-4 w-4 rounded border-slate-300 text-sky-600"
|
|
7684
8146
|
}
|
|
7685
|
-
|
|
7686
|
-
|
|
7687
|
-
|
|
7688
|
-
|
|
7689
|
-
|
|
7690
|
-
|
|
7691
|
-
|
|
7692
|
-
|
|
7693
|
-
|
|
7694
|
-
|
|
7695
|
-
|
|
7696
|
-
|
|
7697
|
-
|
|
7698
|
-
|
|
7699
|
-
|
|
7700
|
-
|
|
7701
|
-
|
|
7702
|
-
|
|
7703
|
-
|
|
7704
|
-
|
|
7705
|
-
|
|
7706
|
-
|
|
7707
|
-
|
|
7708
|
-
|
|
7709
|
-
|
|
7710
|
-
|
|
7711
|
-
|
|
7712
|
-
|
|
7713
|
-
|
|
7714
|
-
|
|
7715
|
-
|
|
7716
|
-
|
|
7717
|
-
|
|
7718
|
-
|
|
7719
|
-
|
|
7720
|
-
|
|
7721
|
-
|
|
7722
|
-
|
|
7723
|
-
|
|
7724
|
-
|
|
7725
|
-
|
|
7726
|
-
|
|
7727
|
-
|
|
7728
|
-
|
|
7729
|
-
|
|
7730
|
-
|
|
7731
|
-
|
|
7732
|
-
|
|
7733
|
-
|
|
7734
|
-
|
|
7735
|
-
|
|
7736
|
-
|
|
7737
|
-
|
|
7738
|
-
|
|
7739
|
-
|
|
7740
|
-
|
|
7741
|
-
|
|
7742
|
-
|
|
7743
|
-
|
|
7744
|
-
|
|
7745
|
-
"
|
|
7746
|
-
|
|
7747
|
-
|
|
7748
|
-
|
|
7749
|
-
|
|
7750
|
-
|
|
7751
|
-
rows: 3,
|
|
7752
|
-
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"
|
|
7753
|
-
}
|
|
7754
|
-
), /* @__PURE__ */ React69__default.createElement("div", { className: "grid gap-2 sm:grid-cols-2" }, /* @__PURE__ */ React69__default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "X(%)", /* @__PURE__ */ React69__default.createElement(
|
|
7755
|
-
"input",
|
|
7756
|
-
{
|
|
7757
|
-
type: "number",
|
|
7758
|
-
value: element.x,
|
|
7759
|
-
onChange: (event) => updateElement(element.id, { x: Number(event.target.value) }),
|
|
7760
|
-
className: numberFieldClassName
|
|
7761
|
-
}
|
|
7762
|
-
)), /* @__PURE__ */ React69__default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "Y(%)", /* @__PURE__ */ React69__default.createElement(
|
|
7763
|
-
"input",
|
|
7764
|
-
{
|
|
7765
|
-
type: "number",
|
|
7766
|
-
value: element.y,
|
|
7767
|
-
onChange: (event) => updateElement(element.id, { y: Number(event.target.value) }),
|
|
7768
|
-
className: numberFieldClassName
|
|
7769
|
-
}
|
|
7770
|
-
)), /* @__PURE__ */ React69__default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "\u5BBD\u5EA6(%)", /* @__PURE__ */ React69__default.createElement(
|
|
7771
|
-
"input",
|
|
7772
|
-
{
|
|
7773
|
-
type: "number",
|
|
7774
|
-
value: element.width ?? 70,
|
|
7775
|
-
onChange: (event) => updateElement(element.id, { width: Number(event.target.value) }),
|
|
7776
|
-
className: numberFieldClassName
|
|
7777
|
-
}
|
|
7778
|
-
)), /* @__PURE__ */ React69__default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "\u5B57\u53F7(px)", /* @__PURE__ */ React69__default.createElement(
|
|
7779
|
-
"input",
|
|
7780
|
-
{
|
|
7781
|
-
type: "number",
|
|
7782
|
-
value: element.fontSize ?? 18,
|
|
7783
|
-
onChange: (event) => updateElement(element.id, { fontSize: Number(event.target.value) }),
|
|
7784
|
-
className: numberFieldClassName
|
|
7785
|
-
}
|
|
7786
|
-
)), /* @__PURE__ */ React69__default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "\u5B57\u91CD", /* @__PURE__ */ React69__default.createElement(
|
|
7787
|
-
"input",
|
|
7788
|
-
{
|
|
7789
|
-
type: "number",
|
|
7790
|
-
min: 100,
|
|
7791
|
-
max: 900,
|
|
7792
|
-
step: 100,
|
|
7793
|
-
value: element.fontWeight ?? 500,
|
|
7794
|
-
onChange: (event) => updateElement(element.id, { fontWeight: Number(event.target.value) }),
|
|
7795
|
-
className: numberFieldClassName
|
|
7796
|
-
}
|
|
7797
|
-
)), /* @__PURE__ */ React69__default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "\u5BF9\u9F50", /* @__PURE__ */ React69__default.createElement(
|
|
7798
|
-
"select",
|
|
7799
|
-
{
|
|
7800
|
-
value: element.align || "left",
|
|
7801
|
-
onChange: (event) => updateElement(element.id, { align: event.target.value }),
|
|
7802
|
-
className: numberFieldClassName
|
|
7803
|
-
},
|
|
7804
|
-
/* @__PURE__ */ React69__default.createElement("option", { value: "left" }, "left"),
|
|
7805
|
-
/* @__PURE__ */ React69__default.createElement("option", { value: "center" }, "center"),
|
|
7806
|
-
/* @__PURE__ */ React69__default.createElement("option", { value: "right" }, "right")
|
|
7807
|
-
)), /* @__PURE__ */ React69__default.createElement("label", { className: "grid gap-1 text-xs text-slate-600 sm:col-span-2" }, "\u5B57\u4F53", /* @__PURE__ */ React69__default.createElement(
|
|
7808
|
-
"input",
|
|
7809
|
-
{
|
|
7810
|
-
type: "text",
|
|
7811
|
-
value: element.fontFamily || "",
|
|
7812
|
-
onChange: (event) => updateElement(element.id, { fontFamily: event.target.value }),
|
|
7813
|
-
placeholder: "inherit / serif / sans-serif / PingFang SC",
|
|
7814
|
-
className: numberFieldClassName
|
|
7815
|
-
}
|
|
7816
|
-
)), /* @__PURE__ */ React69__default.createElement("label", { className: "grid gap-1 text-xs text-slate-600 sm:col-span-2" }, "\u6587\u5B57\u989C\u8272", /* @__PURE__ */ React69__default.createElement("div", { className: "grid grid-cols-[64px_1fr] gap-2" }, /* @__PURE__ */ React69__default.createElement(
|
|
7817
|
-
"input",
|
|
7818
|
-
{
|
|
7819
|
-
type: "color",
|
|
7820
|
-
value: element.color || "#ffffff",
|
|
7821
|
-
onChange: (event) => updateElement(element.id, { color: event.target.value }),
|
|
7822
|
-
className: "h-10 rounded-lg border border-slate-300 bg-white p-1"
|
|
7823
|
-
}
|
|
7824
|
-
), /* @__PURE__ */ React69__default.createElement(
|
|
7825
|
-
"input",
|
|
7826
|
-
{
|
|
7827
|
-
type: "text",
|
|
7828
|
-
value: element.color || "#ffffff",
|
|
7829
|
-
onChange: (event) => updateElement(element.id, { color: event.target.value }),
|
|
7830
|
-
className: numberFieldClassName
|
|
7831
|
-
}
|
|
7832
|
-
))))) : /* @__PURE__ */ React69__default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React69__default.createElement(
|
|
7833
|
-
"input",
|
|
7834
|
-
{
|
|
7835
|
-
type: "url",
|
|
7836
|
-
value: element.src,
|
|
7837
|
-
onChange: (event) => updateElement(element.id, { src: event.target.value }),
|
|
7838
|
-
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"
|
|
7839
|
-
}
|
|
7840
|
-
), /* @__PURE__ */ React69__default.createElement("div", { className: "grid gap-2 sm:grid-cols-2" }, /* @__PURE__ */ React69__default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "X(%)", /* @__PURE__ */ React69__default.createElement(
|
|
7841
|
-
"input",
|
|
7842
|
-
{
|
|
7843
|
-
type: "number",
|
|
7844
|
-
value: element.x,
|
|
7845
|
-
onChange: (event) => updateElement(element.id, { x: Number(event.target.value) }),
|
|
7846
|
-
className: numberFieldClassName
|
|
7847
|
-
}
|
|
7848
|
-
)), /* @__PURE__ */ React69__default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "Y(%)", /* @__PURE__ */ React69__default.createElement(
|
|
7849
|
-
"input",
|
|
7850
|
-
{
|
|
7851
|
-
type: "number",
|
|
7852
|
-
value: element.y,
|
|
7853
|
-
onChange: (event) => updateElement(element.id, { y: Number(event.target.value) }),
|
|
7854
|
-
className: numberFieldClassName
|
|
7855
|
-
}
|
|
7856
|
-
)), /* @__PURE__ */ React69__default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "\u5BBD\u5EA6(%)", /* @__PURE__ */ React69__default.createElement(
|
|
7857
|
-
"input",
|
|
7858
|
-
{
|
|
7859
|
-
type: "number",
|
|
7860
|
-
value: element.width ?? 60,
|
|
7861
|
-
onChange: (event) => updateElement(element.id, { width: Number(event.target.value) }),
|
|
7862
|
-
className: numberFieldClassName
|
|
7863
|
-
}
|
|
7864
|
-
)), /* @__PURE__ */ React69__default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "\u9AD8\u5EA6(%)", /* @__PURE__ */ React69__default.createElement(
|
|
7865
|
-
"input",
|
|
7866
|
-
{
|
|
7867
|
-
type: "number",
|
|
7868
|
-
value: element.height ?? 40,
|
|
7869
|
-
onChange: (event) => updateElement(element.id, { height: Number(event.target.value) }),
|
|
7870
|
-
className: numberFieldClassName
|
|
7871
|
-
}
|
|
7872
|
-
)), /* @__PURE__ */ React69__default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "\u5706\u89D2(px)", /* @__PURE__ */ React69__default.createElement(
|
|
7873
|
-
"input",
|
|
7874
|
-
{
|
|
7875
|
-
type: "number",
|
|
7876
|
-
value: element.borderRadius ?? 0,
|
|
7877
|
-
onChange: (event) => updateElement(element.id, { borderRadius: Number(event.target.value) }),
|
|
7878
|
-
className: numberFieldClassName
|
|
7879
|
-
}
|
|
7880
|
-
)), /* @__PURE__ */ React69__default.createElement("label", { className: "grid gap-1 text-xs text-slate-600" }, "\u586B\u5145", /* @__PURE__ */ React69__default.createElement(
|
|
7881
|
-
"select",
|
|
7882
|
-
{
|
|
7883
|
-
value: element.fit || "cover",
|
|
7884
|
-
onChange: (event) => updateElement(element.id, { fit: event.target.value }),
|
|
7885
|
-
className: numberFieldClassName
|
|
7886
|
-
},
|
|
7887
|
-
/* @__PURE__ */ React69__default.createElement("option", { value: "cover" }, "cover"),
|
|
7888
|
-
/* @__PURE__ */ React69__default.createElement("option", { value: "contain" }, "contain")
|
|
7889
|
-
))), /* @__PURE__ */ React69__default.createElement("label", { className: "inline-flex items-center gap-2 text-sm text-slate-700" }, /* @__PURE__ */ React69__default.createElement(
|
|
7890
|
-
"input",
|
|
7891
|
-
{
|
|
7892
|
-
type: "checkbox",
|
|
7893
|
-
checked: Boolean(element.isBackground),
|
|
7894
|
-
onChange: (event) => updateElement(element.id, { isBackground: event.target.checked }),
|
|
7895
|
-
className: "h-4 w-4 rounded border-slate-300 text-sky-600"
|
|
7896
|
-
}
|
|
7897
|
-
), "\u4F5C\u4E3A\u672C\u9875\u80CC\u666F\u56FE")))))) : null);
|
|
7898
|
-
};
|
|
7899
|
-
|
|
7900
|
-
// src/festivalCard/components/FestivalCardStudio.tsx
|
|
7901
|
-
var FestivalCardStudio = ({ initialConfig, fetchConfig, onSave }) => {
|
|
7902
|
-
const { config, setConfig, loading, save, saving } = useFestivalCardConfig({
|
|
7903
|
-
initialConfig: normalizeFestivalCardConfig(initialConfig),
|
|
7904
|
-
fetchConfig,
|
|
7905
|
-
onSave
|
|
7906
|
-
});
|
|
7907
|
-
if (loading) return /* @__PURE__ */ React69__default.createElement("div", null, "\u52A0\u8F7D\u4E2D...");
|
|
7908
|
-
return /* @__PURE__ */ React69__default.createElement("div", { className: "grid items-start gap-4 lg:grid-cols-[1.45fr_1fr]" }, /* @__PURE__ */ React69__default.createElement(FestivalCardBook3D, { config, className: "h-full" }), /* @__PURE__ */ React69__default.createElement("div", { className: "lg:sticky lg:top-4" }, /* @__PURE__ */ React69__default.createElement(FestivalCardConfigEditor, { value: config, onChange: setConfig }), onSave ? /* @__PURE__ */ React69__default.createElement(
|
|
7909
|
-
"button",
|
|
7910
|
-
{
|
|
7911
|
-
type: "button",
|
|
7912
|
-
onClick: () => void save(),
|
|
7913
|
-
disabled: saving,
|
|
7914
|
-
className: "mt-3 w-full rounded-lg bg-slate-900 px-4 py-2.5 text-sm font-medium text-white disabled:opacity-60"
|
|
8147
|
+
), "\u4F5C\u4E3A\u672C\u9875\u80CC\u666F\u56FE"))
|
|
8148
|
+
)))) : null);
|
|
8149
|
+
};
|
|
8150
|
+
|
|
8151
|
+
// src/festivalCard/components/FestivalCardStudio.tsx
|
|
8152
|
+
var FestivalCardStudio = ({ initialConfig, fetchConfig, onSave }) => {
|
|
8153
|
+
const { config, setConfig, loading, save, saving } = useFestivalCardConfig({
|
|
8154
|
+
initialConfig: normalizeFestivalCardConfig(initialConfig),
|
|
8155
|
+
fetchConfig,
|
|
8156
|
+
onSave
|
|
8157
|
+
});
|
|
8158
|
+
const [activePageIndex, setActivePageIndex] = useState(0);
|
|
8159
|
+
const [selectedElementId, setSelectedElementId] = useState(null);
|
|
8160
|
+
useEffect(() => {
|
|
8161
|
+
if (config.pages.length === 0) return;
|
|
8162
|
+
if (activePageIndex <= config.pages.length - 1) return;
|
|
8163
|
+
setActivePageIndex(config.pages.length - 1);
|
|
8164
|
+
}, [activePageIndex, config.pages.length]);
|
|
8165
|
+
const updateElementByPreview = (pageIndex, elementId, patch) => {
|
|
8166
|
+
setConfig((prev) => ({
|
|
8167
|
+
...prev,
|
|
8168
|
+
pages: prev.pages.map(
|
|
8169
|
+
(page, index) => index === pageIndex ? {
|
|
8170
|
+
...page,
|
|
8171
|
+
elements: page.elements.map(
|
|
8172
|
+
(element) => element.id === elementId ? { ...element, ...patch } : element
|
|
8173
|
+
)
|
|
8174
|
+
} : page
|
|
8175
|
+
)
|
|
8176
|
+
}));
|
|
8177
|
+
};
|
|
8178
|
+
if (loading) return /* @__PURE__ */ React69__default.createElement("div", null, "\u52A0\u8F7D\u4E2D...");
|
|
8179
|
+
return /* @__PURE__ */ React69__default.createElement("div", { className: "grid items-start gap-4 lg:grid-cols-[1.45fr_1fr]" }, /* @__PURE__ */ React69__default.createElement(
|
|
8180
|
+
FestivalCardBook3D,
|
|
8181
|
+
{
|
|
8182
|
+
config,
|
|
8183
|
+
className: "h-full",
|
|
8184
|
+
editable: true,
|
|
8185
|
+
currentPage: activePageIndex,
|
|
8186
|
+
onCurrentPageChange: (index) => {
|
|
8187
|
+
setActivePageIndex(index);
|
|
8188
|
+
setSelectedElementId(null);
|
|
8189
|
+
},
|
|
8190
|
+
selectedElementId,
|
|
8191
|
+
onSelectedElementChange: setSelectedElementId,
|
|
8192
|
+
onElementChange: updateElementByPreview
|
|
8193
|
+
}
|
|
8194
|
+
), /* @__PURE__ */ React69__default.createElement("div", { className: "lg:sticky lg:top-4" }, /* @__PURE__ */ React69__default.createElement(
|
|
8195
|
+
FestivalCardConfigEditor,
|
|
8196
|
+
{
|
|
8197
|
+
value: config,
|
|
8198
|
+
onChange: setConfig,
|
|
8199
|
+
activePageIndex,
|
|
8200
|
+
onActivePageIndexChange: (index) => {
|
|
8201
|
+
setActivePageIndex(index);
|
|
8202
|
+
setSelectedElementId(null);
|
|
8203
|
+
},
|
|
8204
|
+
selectedElementId
|
|
8205
|
+
}
|
|
8206
|
+
), onSave ? /* @__PURE__ */ React69__default.createElement(
|
|
8207
|
+
"button",
|
|
8208
|
+
{
|
|
8209
|
+
type: "button",
|
|
8210
|
+
onClick: () => void save(),
|
|
8211
|
+
disabled: saving,
|
|
8212
|
+
className: "mt-3 w-full rounded-lg bg-slate-900 px-4 py-2.5 text-sm font-medium text-white disabled:opacity-60"
|
|
7915
8213
|
},
|
|
7916
8214
|
saving ? "\u4FDD\u5B58\u4E2D..." : "\u4FDD\u5B58\u914D\u7F6E"
|
|
7917
8215
|
) : null));
|
|
@@ -8483,6 +8781,1244 @@ var BoothConfigPage = ({ initialConfig, onSave }) => {
|
|
|
8483
8781
|
)), /* @__PURE__ */ React69__default.createElement("div", { className: "flex gap-2" }, /* @__PURE__ */ React69__default.createElement("button", { className: "rounded bg-indigo-600 px-3 py-2 text-white", disabled: saving, onClick: save }, saving ? "\u4FDD\u5B58\u4E2D..." : "\u4FDD\u5B58\u914D\u7F6E"), /* @__PURE__ */ React69__default.createElement("button", { className: "rounded border px-3 py-2", onClick: reset }, "\u6062\u590D\u9ED8\u8BA4")));
|
|
8484
8782
|
};
|
|
8485
8783
|
|
|
8784
|
+
// src/mikuContest/logic/shared/defaults.ts
|
|
8785
|
+
var defaultMikuVotingRules = {
|
|
8786
|
+
maxVotesPerDay: 3,
|
|
8787
|
+
forbidDuplicateVotePerWork: true,
|
|
8788
|
+
maxVotesPerDevicePerDay: 20,
|
|
8789
|
+
maxVotesPerIpPerDay: 100
|
|
8790
|
+
};
|
|
8791
|
+
var createDefaultMikuContestConfig = (overrides) => ({
|
|
8792
|
+
id: overrides?.id || "miku-contest-default",
|
|
8793
|
+
name: overrides?.name || "\u521D\u97F3\u672A\u6765\u793E\u56E2\u5F81\u7A3F\u5927\u8D5B",
|
|
8794
|
+
theme: overrides?.theme || "\u521D\u97F3\u672A\u6765\u4E3B\u9898\u521B\u4F5C\u5F81\u7A3F",
|
|
8795
|
+
organizer: overrides?.organizer || "\u521D\u97F3\u672A\u6765\u793E\u56E2",
|
|
8796
|
+
awards: overrides?.awards || ["\u4E00\u7B49\u5956", "\u4E8C\u7B49\u5956", "\u4E09\u7B49\u5956", "\u4EBA\u6C14\u5956"],
|
|
8797
|
+
rules: overrides?.rules || "\u8BF7\u786E\u4FDD\u4F5C\u54C1\u539F\u521B\u4E14\u7B26\u5408\u793E\u56E2\u89C4\u8303\u3002",
|
|
8798
|
+
copyright: overrides?.copyright || "\u6295\u7A3F\u5373\u89C6\u4E3A\u6388\u6743\u8D5B\u4E8B\u5C55\u793A\u4E0E\u516C\u793A\u3002",
|
|
8799
|
+
timeline: overrides?.timeline || {
|
|
8800
|
+
submissionStartAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8801
|
+
submissionEndAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8802
|
+
votingStartAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8803
|
+
votingEndAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8804
|
+
publicResultAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
8805
|
+
},
|
|
8806
|
+
votingRules: {
|
|
8807
|
+
...defaultMikuVotingRules,
|
|
8808
|
+
...overrides?.votingRules || {}
|
|
8809
|
+
},
|
|
8810
|
+
toggles: {
|
|
8811
|
+
submissionEnabled: overrides?.toggles?.submissionEnabled ?? true,
|
|
8812
|
+
votingEnabled: overrides?.toggles?.votingEnabled ?? true,
|
|
8813
|
+
resultEnabled: overrides?.toggles?.resultEnabled ?? false
|
|
8814
|
+
}
|
|
8815
|
+
});
|
|
8816
|
+
|
|
8817
|
+
// src/mikuContest/logic/shared/validators.ts
|
|
8818
|
+
var DESCRIPTION_LIMIT = 500;
|
|
8819
|
+
var MINIAPP_DESCRIPTION_LIMIT = 200;
|
|
8820
|
+
var TEXT_CONTENT_LIMIT = 2e3;
|
|
8821
|
+
var MAX_TAGS = 3;
|
|
8822
|
+
var hasValue = (value) => {
|
|
8823
|
+
return typeof value === "string" ? value.trim().length > 0 : value !== null && value !== void 0;
|
|
8824
|
+
};
|
|
8825
|
+
var validateByType = (type, input) => {
|
|
8826
|
+
const errors = [];
|
|
8827
|
+
const { content } = input;
|
|
8828
|
+
switch (type) {
|
|
8829
|
+
case "visual": {
|
|
8830
|
+
const imageCount = content.images?.length || 0;
|
|
8831
|
+
if (imageCount < 1 || imageCount > 3) {
|
|
8832
|
+
errors.push("\u89C6\u89C9\u7C7B\u4F5C\u54C1\u9700\u4E0A\u4F20 1-3 \u5F20\u56FE\u7247");
|
|
8833
|
+
}
|
|
8834
|
+
break;
|
|
8835
|
+
}
|
|
8836
|
+
case "video": {
|
|
8837
|
+
if (!hasValue(content.videoLink)) {
|
|
8838
|
+
errors.push("\u89C6\u9891\u7C7B\u4F5C\u54C1\u9700\u63D0\u4F9B\u89C6\u9891\u94FE\u63A5");
|
|
8839
|
+
}
|
|
8840
|
+
if (!hasValue(content.coverImage)) {
|
|
8841
|
+
errors.push("\u89C6\u9891\u7C7B\u4F5C\u54C1\u9700\u63D0\u4F9B\u5C01\u9762\u56FE");
|
|
8842
|
+
}
|
|
8843
|
+
break;
|
|
8844
|
+
}
|
|
8845
|
+
case "text": {
|
|
8846
|
+
const text6 = content.textContent || "";
|
|
8847
|
+
if (!text6.trim()) {
|
|
8848
|
+
errors.push("\u6587\u5B57\u7C7B\u4F5C\u54C1\u9700\u586B\u5199\u6B63\u6587");
|
|
8849
|
+
}
|
|
8850
|
+
if (text6.length > TEXT_CONTENT_LIMIT) {
|
|
8851
|
+
errors.push(`\u6587\u5B57\u6B63\u6587\u4E0D\u80FD\u8D85\u8FC7 ${TEXT_CONTENT_LIMIT} \u5B57`);
|
|
8852
|
+
}
|
|
8853
|
+
break;
|
|
8854
|
+
}
|
|
8855
|
+
case "audio": {
|
|
8856
|
+
if (!hasValue(content.audioLink)) {
|
|
8857
|
+
errors.push("\u97F3\u9891\u7C7B\u4F5C\u54C1\u9700\u63D0\u4F9B\u97F3\u9891\u94FE\u63A5");
|
|
8858
|
+
}
|
|
8859
|
+
break;
|
|
8860
|
+
}
|
|
8861
|
+
}
|
|
8862
|
+
return errors;
|
|
8863
|
+
};
|
|
8864
|
+
var validateMikuSubmissionInput = (input, mode = "web") => {
|
|
8865
|
+
const errors = [];
|
|
8866
|
+
if (!input.contestId.trim()) errors.push("contestId \u4E0D\u80FD\u4E3A\u7A7A");
|
|
8867
|
+
if (!input.authorId.trim()) errors.push("authorId \u4E0D\u80FD\u4E3A\u7A7A");
|
|
8868
|
+
if (!input.authorNickname.trim()) errors.push("\u4F5C\u8005\u6635\u79F0\u4E0D\u80FD\u4E3A\u7A7A");
|
|
8869
|
+
if (!input.title.trim()) errors.push("\u4F5C\u54C1\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A");
|
|
8870
|
+
const descriptionLimit = mode === "miniapp" ? MINIAPP_DESCRIPTION_LIMIT : DESCRIPTION_LIMIT;
|
|
8871
|
+
if (input.description.length > descriptionLimit) {
|
|
8872
|
+
errors.push(`\u4F5C\u54C1\u7B80\u4ECB\u4E0D\u80FD\u8D85\u8FC7 ${descriptionLimit} \u5B57`);
|
|
8873
|
+
}
|
|
8874
|
+
if ((input.tags?.length || 0) > MAX_TAGS) {
|
|
8875
|
+
errors.push(`\u6807\u7B7E\u6700\u591A ${MAX_TAGS} \u4E2A`);
|
|
8876
|
+
}
|
|
8877
|
+
errors.push(...validateByType(input.type, input));
|
|
8878
|
+
return errors;
|
|
8879
|
+
};
|
|
8880
|
+
|
|
8881
|
+
// src/mikuContest/logic/shared/voting.ts
|
|
8882
|
+
var toVoteDayKey = (date = /* @__PURE__ */ new Date()) => {
|
|
8883
|
+
const y = date.getUTCFullYear();
|
|
8884
|
+
const m = String(date.getUTCMonth() + 1).padStart(2, "0");
|
|
8885
|
+
const d = String(date.getUTCDate()).padStart(2, "0");
|
|
8886
|
+
return `${y}-${m}-${d}`;
|
|
8887
|
+
};
|
|
8888
|
+
var checkVoteEligibility = (context) => {
|
|
8889
|
+
const { existingVotes, submissionId, voterId, dayKey, rules } = context;
|
|
8890
|
+
const userTodayVotes = existingVotes.filter((vote) => vote.voterId === voterId && vote.dayKey === dayKey);
|
|
8891
|
+
if (userTodayVotes.length >= rules.maxVotesPerDay) {
|
|
8892
|
+
return { ok: false, reason: "\u5DF2\u8FBE\u5230\u4ECA\u65E5\u6295\u7968\u4E0A\u9650" };
|
|
8893
|
+
}
|
|
8894
|
+
if (rules.forbidDuplicateVotePerWork) {
|
|
8895
|
+
const duplicated = userTodayVotes.some((vote) => vote.submissionId === submissionId);
|
|
8896
|
+
if (duplicated) return { ok: false, reason: "\u4E0D\u53EF\u91CD\u590D\u6295\u540C\u4E00\u4F5C\u54C1" };
|
|
8897
|
+
}
|
|
8898
|
+
return { ok: true };
|
|
8899
|
+
};
|
|
8900
|
+
var sortByVotesDesc = (items) => {
|
|
8901
|
+
return [...items].sort((a, b) => {
|
|
8902
|
+
if (b.voteCount === a.voteCount) {
|
|
8903
|
+
return (a.createdAt || "").localeCompare(b.createdAt || "");
|
|
8904
|
+
}
|
|
8905
|
+
return b.voteCount - a.voteCount;
|
|
8906
|
+
});
|
|
8907
|
+
};
|
|
8908
|
+
var randomId = (prefix) => {
|
|
8909
|
+
return `${prefix}_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
8910
|
+
};
|
|
8911
|
+
var serialNo = () => {
|
|
8912
|
+
const now = /* @__PURE__ */ new Date();
|
|
8913
|
+
const y = now.getFullYear();
|
|
8914
|
+
const m = String(now.getMonth() + 1).padStart(2, "0");
|
|
8915
|
+
const d = String(now.getDate()).padStart(2, "0");
|
|
8916
|
+
const seq = Math.floor(Math.random() * 9e3 + 1e3);
|
|
8917
|
+
return `MIKU-${y}${m}${d}-${seq}`;
|
|
8918
|
+
};
|
|
8919
|
+
var MikuContestService = class {
|
|
8920
|
+
constructor(options = {}) {
|
|
8921
|
+
this.submissions = /* @__PURE__ */ new Map();
|
|
8922
|
+
this.votes = [];
|
|
8923
|
+
this.announcements = /* @__PURE__ */ new Map();
|
|
8924
|
+
this.voterRestrictions = /* @__PURE__ */ new Map();
|
|
8925
|
+
this.contest = createDefaultMikuContestConfig(options.contestConfig);
|
|
8926
|
+
}
|
|
8927
|
+
getContestConfig() {
|
|
8928
|
+
return this.contest;
|
|
8929
|
+
}
|
|
8930
|
+
updateContestConfig(patch) {
|
|
8931
|
+
this.contest = {
|
|
8932
|
+
...this.contest,
|
|
8933
|
+
...patch,
|
|
8934
|
+
votingRules: {
|
|
8935
|
+
...this.contest.votingRules,
|
|
8936
|
+
...patch.votingRules || {}
|
|
8937
|
+
},
|
|
8938
|
+
toggles: {
|
|
8939
|
+
...this.contest.toggles,
|
|
8940
|
+
...patch.toggles || {}
|
|
8941
|
+
},
|
|
8942
|
+
timeline: {
|
|
8943
|
+
...this.contest.timeline,
|
|
8944
|
+
...patch.timeline || {}
|
|
8945
|
+
}
|
|
8946
|
+
};
|
|
8947
|
+
return this.contest;
|
|
8948
|
+
}
|
|
8949
|
+
createSubmission(input, mode = "web") {
|
|
8950
|
+
if (!this.contest.toggles.submissionEnabled) {
|
|
8951
|
+
throw new Error("\u5F53\u524D\u672A\u5F00\u653E\u6295\u7A3F");
|
|
8952
|
+
}
|
|
8953
|
+
const errors = validateMikuSubmissionInput(input, mode);
|
|
8954
|
+
if (errors.length > 0) {
|
|
8955
|
+
throw new Error(errors.join("\uFF1B"));
|
|
8956
|
+
}
|
|
8957
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
8958
|
+
const next = {
|
|
8959
|
+
id: randomId("submission"),
|
|
8960
|
+
serialNo: serialNo(),
|
|
8961
|
+
contestId: input.contestId,
|
|
8962
|
+
authorId: input.authorId,
|
|
8963
|
+
authorNickname: input.authorNickname,
|
|
8964
|
+
title: input.title,
|
|
8965
|
+
type: input.type,
|
|
8966
|
+
description: input.description,
|
|
8967
|
+
tags: input.tags || [],
|
|
8968
|
+
content: input.content,
|
|
8969
|
+
voteCount: 0,
|
|
8970
|
+
status: "pending",
|
|
8971
|
+
createdAt: now,
|
|
8972
|
+
updatedAt: now
|
|
8973
|
+
};
|
|
8974
|
+
this.submissions.set(next.id, next);
|
|
8975
|
+
return next;
|
|
8976
|
+
}
|
|
8977
|
+
listSubmissions(filter) {
|
|
8978
|
+
const authorKeyword = filter?.authorKeyword?.trim().toLowerCase();
|
|
8979
|
+
const titleKeyword = filter?.titleKeyword?.trim().toLowerCase();
|
|
8980
|
+
return [...this.submissions.values()].filter((item) => {
|
|
8981
|
+
if (filter?.status && item.status !== filter.status) return false;
|
|
8982
|
+
if (filter?.type && item.type !== filter.type) return false;
|
|
8983
|
+
if (filter?.authorId && item.authorId !== filter.authorId) return false;
|
|
8984
|
+
if (authorKeyword && !item.authorNickname.toLowerCase().includes(authorKeyword)) return false;
|
|
8985
|
+
if (titleKeyword && !item.title.toLowerCase().includes(titleKeyword)) return false;
|
|
8986
|
+
return true;
|
|
8987
|
+
});
|
|
8988
|
+
}
|
|
8989
|
+
getSubmission(submissionId) {
|
|
8990
|
+
return this.submissions.get(submissionId) || null;
|
|
8991
|
+
}
|
|
8992
|
+
reviewSubmission(input) {
|
|
8993
|
+
const current = this.submissions.get(input.submissionId);
|
|
8994
|
+
if (!current) throw new Error("\u6295\u7A3F\u4E0D\u5B58\u5728");
|
|
8995
|
+
if (input.action === "reject" && !input.rejectReason?.trim()) {
|
|
8996
|
+
throw new Error("\u9A73\u56DE\u9700\u586B\u5199\u539F\u56E0");
|
|
8997
|
+
}
|
|
8998
|
+
const reviewed = {
|
|
8999
|
+
...current,
|
|
9000
|
+
status: input.action === "approve" ? "approved" : "rejected",
|
|
9001
|
+
rejectReason: input.action === "reject" ? input.rejectReason?.trim() : void 0,
|
|
9002
|
+
reviewedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9003
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
9004
|
+
};
|
|
9005
|
+
this.submissions.set(reviewed.id, reviewed);
|
|
9006
|
+
return reviewed;
|
|
9007
|
+
}
|
|
9008
|
+
vote(input) {
|
|
9009
|
+
if (!this.contest.toggles.votingEnabled) {
|
|
9010
|
+
throw new Error("\u5F53\u524D\u672A\u5F00\u653E\u6295\u7968");
|
|
9011
|
+
}
|
|
9012
|
+
const restriction = this.voterRestrictions.get(input.voterId);
|
|
9013
|
+
if (restriction?.banned) {
|
|
9014
|
+
throw new Error("\u5F53\u524D\u8D26\u53F7\u5DF2\u88AB\u9650\u5236\u6295\u7968");
|
|
9015
|
+
}
|
|
9016
|
+
const target = this.submissions.get(input.submissionId);
|
|
9017
|
+
if (!target) throw new Error("\u4F5C\u54C1\u4E0D\u5B58\u5728");
|
|
9018
|
+
if (target.status !== "approved") throw new Error("\u4EC5\u53EF\u5BF9\u5DF2\u8FC7\u5BA1\u4F5C\u54C1\u6295\u7968");
|
|
9019
|
+
const dayKey = toVoteDayKey();
|
|
9020
|
+
const eligible = checkVoteEligibility({
|
|
9021
|
+
existingVotes: this.votes,
|
|
9022
|
+
submissionId: input.submissionId,
|
|
9023
|
+
voterId: input.voterId,
|
|
9024
|
+
dayKey,
|
|
9025
|
+
rules: this.contest.votingRules
|
|
9026
|
+
});
|
|
9027
|
+
if (!eligible.ok) {
|
|
9028
|
+
throw new Error(eligible.reason || "\u6295\u7968\u5931\u8D25");
|
|
9029
|
+
}
|
|
9030
|
+
const vote = {
|
|
9031
|
+
id: randomId("vote"),
|
|
9032
|
+
contestId: input.contestId,
|
|
9033
|
+
submissionId: input.submissionId,
|
|
9034
|
+
voterId: input.voterId,
|
|
9035
|
+
votedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9036
|
+
dayKey,
|
|
9037
|
+
deviceId: input.deviceId,
|
|
9038
|
+
ip: input.ip
|
|
9039
|
+
};
|
|
9040
|
+
this.votes.push(vote);
|
|
9041
|
+
const updated = {
|
|
9042
|
+
...target,
|
|
9043
|
+
voteCount: target.voteCount + 1,
|
|
9044
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
9045
|
+
};
|
|
9046
|
+
this.submissions.set(updated.id, updated);
|
|
9047
|
+
return updated;
|
|
9048
|
+
}
|
|
9049
|
+
getVoterRestriction(voterId) {
|
|
9050
|
+
return this.voterRestrictions.get(voterId) || null;
|
|
9051
|
+
}
|
|
9052
|
+
setVoterRestriction(input) {
|
|
9053
|
+
const next = {
|
|
9054
|
+
voterId: input.voterId,
|
|
9055
|
+
banned: input.banned,
|
|
9056
|
+
reason: input.reason,
|
|
9057
|
+
operatorId: input.operatorId,
|
|
9058
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
9059
|
+
};
|
|
9060
|
+
this.voterRestrictions.set(input.voterId, next);
|
|
9061
|
+
return next;
|
|
9062
|
+
}
|
|
9063
|
+
resetVotes(input) {
|
|
9064
|
+
if (!input.submissionId && !input.voterId) {
|
|
9065
|
+
throw new Error("submissionId \u4E0E voterId \u81F3\u5C11\u63D0\u4F9B\u4E00\u4E2A");
|
|
9066
|
+
}
|
|
9067
|
+
const before = this.votes.length;
|
|
9068
|
+
const affected = /* @__PURE__ */ new Set();
|
|
9069
|
+
const remained = this.votes.filter((vote) => {
|
|
9070
|
+
const matchSubmission = input.submissionId ? vote.submissionId === input.submissionId : true;
|
|
9071
|
+
const matchVoter = input.voterId ? vote.voterId === input.voterId : true;
|
|
9072
|
+
const shouldRemove = matchSubmission && matchVoter;
|
|
9073
|
+
if (shouldRemove) affected.add(vote.submissionId);
|
|
9074
|
+
return !shouldRemove;
|
|
9075
|
+
});
|
|
9076
|
+
this.votes.length = 0;
|
|
9077
|
+
this.votes.push(...remained);
|
|
9078
|
+
this.recalculateVoteCounts();
|
|
9079
|
+
return {
|
|
9080
|
+
removedVotes: before - remained.length,
|
|
9081
|
+
affectedSubmissions: [...affected]
|
|
9082
|
+
};
|
|
9083
|
+
}
|
|
9084
|
+
listAnnouncements(contestId) {
|
|
9085
|
+
const all = [...this.announcements.values()];
|
|
9086
|
+
return contestId ? all.filter((item) => item.contestId === contestId) : all;
|
|
9087
|
+
}
|
|
9088
|
+
publishAnnouncement(input) {
|
|
9089
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
9090
|
+
const announcement = {
|
|
9091
|
+
id: randomId("notice"),
|
|
9092
|
+
contestId: input.contestId,
|
|
9093
|
+
title: input.title,
|
|
9094
|
+
content: input.content,
|
|
9095
|
+
type: input.type,
|
|
9096
|
+
createdBy: input.createdBy,
|
|
9097
|
+
createdAt: now,
|
|
9098
|
+
updatedAt: now
|
|
9099
|
+
};
|
|
9100
|
+
this.announcements.set(announcement.id, announcement);
|
|
9101
|
+
return announcement;
|
|
9102
|
+
}
|
|
9103
|
+
getLeaderboard(limit = 10) {
|
|
9104
|
+
const ranked = sortByVotesDesc(this.listSubmissions({ status: "approved" })).slice(0, limit);
|
|
9105
|
+
return ranked.map((item, index) => ({
|
|
9106
|
+
submissionId: item.id,
|
|
9107
|
+
title: item.title,
|
|
9108
|
+
authorNickname: item.authorNickname,
|
|
9109
|
+
voteCount: item.voteCount,
|
|
9110
|
+
rank: index + 1
|
|
9111
|
+
}));
|
|
9112
|
+
}
|
|
9113
|
+
getSnapshot() {
|
|
9114
|
+
return {
|
|
9115
|
+
contest: this.contest,
|
|
9116
|
+
submissions: this.listSubmissions(),
|
|
9117
|
+
announcements: this.listAnnouncements(),
|
|
9118
|
+
leaderboard: this.getLeaderboard()
|
|
9119
|
+
};
|
|
9120
|
+
}
|
|
9121
|
+
getSubmissionExportRows(filter) {
|
|
9122
|
+
return this.listSubmissions(filter).map((item) => ({
|
|
9123
|
+
\u6295\u7A3F\u7F16\u53F7: item.serialNo,
|
|
9124
|
+
\u6295\u7A3FID: item.id,
|
|
9125
|
+
\u8D5B\u4E8BID: item.contestId,
|
|
9126
|
+
\u4F5C\u8005ID: item.authorId,
|
|
9127
|
+
\u4F5C\u8005\u6635\u79F0: item.authorNickname,
|
|
9128
|
+
\u4F5C\u54C1\u540D\u79F0: item.title,
|
|
9129
|
+
\u4F5C\u54C1\u7C7B\u578B: item.type,
|
|
9130
|
+
\u7B80\u4ECB: item.description,
|
|
9131
|
+
\u6807\u7B7E: item.tags.join(","),
|
|
9132
|
+
\u5BA1\u6838\u72B6\u6001: item.status,
|
|
9133
|
+
\u9A73\u56DE\u539F\u56E0: item.rejectReason || "",
|
|
9134
|
+
\u7968\u6570: item.voteCount,
|
|
9135
|
+
\u63D0\u4EA4\u65F6\u95F4: item.createdAt,
|
|
9136
|
+
\u66F4\u65B0\u65F6\u95F4: item.updatedAt
|
|
9137
|
+
}));
|
|
9138
|
+
}
|
|
9139
|
+
exportSubmissionExcel(filter) {
|
|
9140
|
+
const rows = this.getSubmissionExportRows(filter);
|
|
9141
|
+
const workbook = XLSX.utils.book_new();
|
|
9142
|
+
const worksheet = XLSX.utils.json_to_sheet(rows);
|
|
9143
|
+
XLSX.utils.book_append_sheet(workbook, worksheet, "submissions");
|
|
9144
|
+
return XLSX.write(workbook, { bookType: "xlsx", type: "buffer" });
|
|
9145
|
+
}
|
|
9146
|
+
recalculateVoteCounts() {
|
|
9147
|
+
const counts = /* @__PURE__ */ new Map();
|
|
9148
|
+
for (const vote of this.votes) {
|
|
9149
|
+
counts.set(vote.submissionId, (counts.get(vote.submissionId) || 0) + 1);
|
|
9150
|
+
}
|
|
9151
|
+
for (const [id, submission] of this.submissions.entries()) {
|
|
9152
|
+
const nextCount = counts.get(id) || 0;
|
|
9153
|
+
if (submission.voteCount === nextCount) continue;
|
|
9154
|
+
this.submissions.set(id, {
|
|
9155
|
+
...submission,
|
|
9156
|
+
voteCount: nextCount,
|
|
9157
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
9158
|
+
});
|
|
9159
|
+
}
|
|
9160
|
+
}
|
|
9161
|
+
exportPersistenceState() {
|
|
9162
|
+
return {
|
|
9163
|
+
contest: this.contest,
|
|
9164
|
+
submissions: [...this.submissions.values()],
|
|
9165
|
+
votes: [...this.votes],
|
|
9166
|
+
announcements: [...this.announcements.values()],
|
|
9167
|
+
voterRestrictions: [...this.voterRestrictions.values()],
|
|
9168
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
9169
|
+
};
|
|
9170
|
+
}
|
|
9171
|
+
importPersistenceState(state) {
|
|
9172
|
+
this.contest = state.contest;
|
|
9173
|
+
this.submissions.clear();
|
|
9174
|
+
this.announcements.clear();
|
|
9175
|
+
this.voterRestrictions.clear();
|
|
9176
|
+
this.votes.length = 0;
|
|
9177
|
+
for (const item of state.submissions) {
|
|
9178
|
+
this.submissions.set(item.id, item);
|
|
9179
|
+
}
|
|
9180
|
+
for (const item of state.announcements) {
|
|
9181
|
+
this.announcements.set(item.id, item);
|
|
9182
|
+
}
|
|
9183
|
+
for (const item of state.voterRestrictions) {
|
|
9184
|
+
this.voterRestrictions.set(item.voterId, item);
|
|
9185
|
+
}
|
|
9186
|
+
this.votes.push(...state.votes);
|
|
9187
|
+
}
|
|
9188
|
+
};
|
|
9189
|
+
var createMikuContestService = (options) => {
|
|
9190
|
+
return new MikuContestService(options);
|
|
9191
|
+
};
|
|
9192
|
+
var mikuContestConfigs = pgTable("miku_contest_configs", {
|
|
9193
|
+
contestId: text("contest_id").primaryKey(),
|
|
9194
|
+
config: jsonb("config").$type().notNull(),
|
|
9195
|
+
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
|
|
9196
|
+
updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow().notNull()
|
|
9197
|
+
});
|
|
9198
|
+
var mikuContestSubmissions = pgTable("miku_contest_submissions", {
|
|
9199
|
+
id: text("id").primaryKey(),
|
|
9200
|
+
contestId: text("contest_id").notNull(),
|
|
9201
|
+
serialNo: text("serial_no").notNull(),
|
|
9202
|
+
authorId: text("author_id").notNull(),
|
|
9203
|
+
authorNickname: text("author_nickname").notNull(),
|
|
9204
|
+
title: text("title").notNull(),
|
|
9205
|
+
type: text("type").notNull(),
|
|
9206
|
+
description: text("description").notNull(),
|
|
9207
|
+
tags: jsonb("tags").$type().notNull(),
|
|
9208
|
+
content: jsonb("content").$type().notNull(),
|
|
9209
|
+
voteCount: integer("vote_count").notNull().default(0),
|
|
9210
|
+
status: text("status").notNull(),
|
|
9211
|
+
rejectReason: text("reject_reason"),
|
|
9212
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull(),
|
|
9213
|
+
reviewedAt: timestamp("reviewed_at", { withTimezone: true }),
|
|
9214
|
+
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull()
|
|
9215
|
+
});
|
|
9216
|
+
var mikuContestVotes = pgTable("miku_contest_votes", {
|
|
9217
|
+
id: text("id").primaryKey(),
|
|
9218
|
+
contestId: text("contest_id").notNull(),
|
|
9219
|
+
submissionId: text("submission_id").notNull(),
|
|
9220
|
+
voterId: text("voter_id").notNull(),
|
|
9221
|
+
votedAt: text("voted_at").notNull(),
|
|
9222
|
+
dayKey: text("day_key").notNull(),
|
|
9223
|
+
deviceId: text("device_id"),
|
|
9224
|
+
ip: text("ip")
|
|
9225
|
+
});
|
|
9226
|
+
var mikuContestNotices = pgTable("miku_contest_notices", {
|
|
9227
|
+
id: text("id").primaryKey(),
|
|
9228
|
+
contestId: text("contest_id").notNull(),
|
|
9229
|
+
title: text("title").notNull(),
|
|
9230
|
+
content: text("content").notNull(),
|
|
9231
|
+
type: text("type").notNull(),
|
|
9232
|
+
createdBy: text("created_by").notNull(),
|
|
9233
|
+
createdAt: text("created_at").notNull(),
|
|
9234
|
+
updatedAt: text("updated_at").notNull()
|
|
9235
|
+
});
|
|
9236
|
+
var mikuContestVoterRestrictions = pgTable("miku_contest_voter_restrictions", {
|
|
9237
|
+
id: text("id").primaryKey(),
|
|
9238
|
+
contestId: text("contest_id").notNull(),
|
|
9239
|
+
data: jsonb("data").$type().notNull()
|
|
9240
|
+
});
|
|
9241
|
+
var MikuContestStateDbService = class {
|
|
9242
|
+
constructor(db) {
|
|
9243
|
+
this.db = db;
|
|
9244
|
+
}
|
|
9245
|
+
async loadState(contestId) {
|
|
9246
|
+
const configRows = await this.db.select({ config: mikuContestConfigs.config }).from(mikuContestConfigs).where(eq(mikuContestConfigs.contestId, contestId)).limit(1);
|
|
9247
|
+
const config = configRows[0]?.config;
|
|
9248
|
+
if (!config) return null;
|
|
9249
|
+
const [submissions, votes, announcements, restrictions] = await Promise.all([
|
|
9250
|
+
this.db.select().from(mikuContestSubmissions).where(eq(mikuContestSubmissions.contestId, contestId)),
|
|
9251
|
+
this.db.select().from(mikuContestVotes).where(eq(mikuContestVotes.contestId, contestId)),
|
|
9252
|
+
this.db.select().from(mikuContestNotices).where(eq(mikuContestNotices.contestId, contestId)),
|
|
9253
|
+
this.db.select({ data: mikuContestVoterRestrictions.data }).from(mikuContestVoterRestrictions).where(eq(mikuContestVoterRestrictions.contestId, contestId))
|
|
9254
|
+
]);
|
|
9255
|
+
return {
|
|
9256
|
+
contest: config,
|
|
9257
|
+
submissions,
|
|
9258
|
+
votes,
|
|
9259
|
+
announcements,
|
|
9260
|
+
voterRestrictions: restrictions.map((item) => item.data),
|
|
9261
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
9262
|
+
};
|
|
9263
|
+
}
|
|
9264
|
+
async saveState(state) {
|
|
9265
|
+
const contestId = state.contest.id;
|
|
9266
|
+
const exists = await this.db.select({ contestId: mikuContestConfigs.contestId }).from(mikuContestConfigs).where(eq(mikuContestConfigs.contestId, contestId)).limit(1);
|
|
9267
|
+
if (exists[0]) {
|
|
9268
|
+
await this.db.update(mikuContestConfigs).set({
|
|
9269
|
+
config: state.contest,
|
|
9270
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
9271
|
+
}).where(eq(mikuContestConfigs.contestId, contestId));
|
|
9272
|
+
} else {
|
|
9273
|
+
await this.db.insert(mikuContestConfigs).values({
|
|
9274
|
+
contestId,
|
|
9275
|
+
config: state.contest
|
|
9276
|
+
});
|
|
9277
|
+
}
|
|
9278
|
+
await this.db.delete(mikuContestSubmissions).where(eq(mikuContestSubmissions.contestId, contestId));
|
|
9279
|
+
await this.db.delete(mikuContestVotes).where(eq(mikuContestVotes.contestId, contestId));
|
|
9280
|
+
await this.db.delete(mikuContestNotices).where(eq(mikuContestNotices.contestId, contestId));
|
|
9281
|
+
await this.db.delete(mikuContestVoterRestrictions).where(eq(mikuContestVoterRestrictions.contestId, contestId));
|
|
9282
|
+
if (state.submissions.length > 0) {
|
|
9283
|
+
await this.db.insert(mikuContestSubmissions).values(
|
|
9284
|
+
state.submissions.map((item) => ({
|
|
9285
|
+
...item,
|
|
9286
|
+
contestId,
|
|
9287
|
+
createdAt: new Date(item.createdAt),
|
|
9288
|
+
reviewedAt: item.reviewedAt ? new Date(item.reviewedAt) : null,
|
|
9289
|
+
updatedAt: new Date(item.updatedAt)
|
|
9290
|
+
}))
|
|
9291
|
+
);
|
|
9292
|
+
}
|
|
9293
|
+
if (state.votes.length > 0) {
|
|
9294
|
+
await this.db.insert(mikuContestVotes).values(state.votes);
|
|
9295
|
+
}
|
|
9296
|
+
if (state.announcements.length > 0) {
|
|
9297
|
+
await this.db.insert(mikuContestNotices).values(state.announcements);
|
|
9298
|
+
}
|
|
9299
|
+
if (state.voterRestrictions.length > 0) {
|
|
9300
|
+
await this.db.insert(mikuContestVoterRestrictions).values(
|
|
9301
|
+
state.voterRestrictions.map((item) => ({
|
|
9302
|
+
id: `${contestId}:${item.voterId}`,
|
|
9303
|
+
contestId,
|
|
9304
|
+
data: item
|
|
9305
|
+
}))
|
|
9306
|
+
);
|
|
9307
|
+
}
|
|
9308
|
+
}
|
|
9309
|
+
};
|
|
9310
|
+
|
|
9311
|
+
// src/mikuContest/server/persistence/drizzle-adapter.ts
|
|
9312
|
+
var createMikuContestDrizzlePersistenceAdapter = (db) => {
|
|
9313
|
+
const service = new MikuContestStateDbService(db);
|
|
9314
|
+
return {
|
|
9315
|
+
loadState: (contestId) => service.loadState(contestId),
|
|
9316
|
+
saveState: (state) => service.saveState(state)
|
|
9317
|
+
};
|
|
9318
|
+
};
|
|
9319
|
+
|
|
9320
|
+
// src/mikuContest/server/persistence/service.ts
|
|
9321
|
+
var MikuContestPersistentService = class {
|
|
9322
|
+
constructor(options) {
|
|
9323
|
+
this.options = options;
|
|
9324
|
+
this.hydrated = false;
|
|
9325
|
+
this.hydrationPromise = null;
|
|
9326
|
+
this.engine = createMikuContestService(options);
|
|
9327
|
+
}
|
|
9328
|
+
async ensureHydrated() {
|
|
9329
|
+
if (this.hydrated) return;
|
|
9330
|
+
if (this.hydrationPromise) return this.hydrationPromise;
|
|
9331
|
+
this.hydrationPromise = (async () => {
|
|
9332
|
+
const contestId = this.engine.getContestConfig().id;
|
|
9333
|
+
const loaded = await this.options.persistenceAdapter.loadState(contestId);
|
|
9334
|
+
if (loaded) {
|
|
9335
|
+
this.engine.importPersistenceState(loaded);
|
|
9336
|
+
} else {
|
|
9337
|
+
await this.options.persistenceAdapter.saveState(this.engine.exportPersistenceState());
|
|
9338
|
+
}
|
|
9339
|
+
this.hydrated = true;
|
|
9340
|
+
})();
|
|
9341
|
+
await this.hydrationPromise;
|
|
9342
|
+
}
|
|
9343
|
+
async persist() {
|
|
9344
|
+
await this.options.persistenceAdapter.saveState(this.engine.exportPersistenceState());
|
|
9345
|
+
}
|
|
9346
|
+
async getContestConfig() {
|
|
9347
|
+
await this.ensureHydrated();
|
|
9348
|
+
return this.engine.getContestConfig();
|
|
9349
|
+
}
|
|
9350
|
+
async updateContestConfig(patch) {
|
|
9351
|
+
await this.ensureHydrated();
|
|
9352
|
+
const data = this.engine.updateContestConfig(patch);
|
|
9353
|
+
await this.persist();
|
|
9354
|
+
return data;
|
|
9355
|
+
}
|
|
9356
|
+
async createSubmission(input, mode = "web") {
|
|
9357
|
+
await this.ensureHydrated();
|
|
9358
|
+
const data = this.engine.createSubmission(input, mode);
|
|
9359
|
+
await this.persist();
|
|
9360
|
+
return data;
|
|
9361
|
+
}
|
|
9362
|
+
async listSubmissions(filter) {
|
|
9363
|
+
await this.ensureHydrated();
|
|
9364
|
+
return this.engine.listSubmissions(filter);
|
|
9365
|
+
}
|
|
9366
|
+
async getSubmission(submissionId) {
|
|
9367
|
+
await this.ensureHydrated();
|
|
9368
|
+
return this.engine.getSubmission(submissionId);
|
|
9369
|
+
}
|
|
9370
|
+
async reviewSubmission(input) {
|
|
9371
|
+
await this.ensureHydrated();
|
|
9372
|
+
const data = this.engine.reviewSubmission(input);
|
|
9373
|
+
await this.persist();
|
|
9374
|
+
return data;
|
|
9375
|
+
}
|
|
9376
|
+
async vote(input) {
|
|
9377
|
+
await this.ensureHydrated();
|
|
9378
|
+
const data = this.engine.vote(input);
|
|
9379
|
+
await this.persist();
|
|
9380
|
+
return data;
|
|
9381
|
+
}
|
|
9382
|
+
async getVoterRestriction(voterId) {
|
|
9383
|
+
await this.ensureHydrated();
|
|
9384
|
+
return this.engine.getVoterRestriction(voterId);
|
|
9385
|
+
}
|
|
9386
|
+
async setVoterRestriction(input) {
|
|
9387
|
+
await this.ensureHydrated();
|
|
9388
|
+
const data = this.engine.setVoterRestriction(input);
|
|
9389
|
+
await this.persist();
|
|
9390
|
+
return data;
|
|
9391
|
+
}
|
|
9392
|
+
async resetVotes(input) {
|
|
9393
|
+
await this.ensureHydrated();
|
|
9394
|
+
const data = this.engine.resetVotes(input);
|
|
9395
|
+
await this.persist();
|
|
9396
|
+
return data;
|
|
9397
|
+
}
|
|
9398
|
+
async listAnnouncements(contestId) {
|
|
9399
|
+
await this.ensureHydrated();
|
|
9400
|
+
return this.engine.listAnnouncements(contestId);
|
|
9401
|
+
}
|
|
9402
|
+
async publishAnnouncement(input) {
|
|
9403
|
+
await this.ensureHydrated();
|
|
9404
|
+
const data = this.engine.publishAnnouncement(input);
|
|
9405
|
+
await this.persist();
|
|
9406
|
+
return data;
|
|
9407
|
+
}
|
|
9408
|
+
async getLeaderboard(limit = 10) {
|
|
9409
|
+
await this.ensureHydrated();
|
|
9410
|
+
return this.engine.getLeaderboard(limit);
|
|
9411
|
+
}
|
|
9412
|
+
async getSnapshot() {
|
|
9413
|
+
await this.ensureHydrated();
|
|
9414
|
+
return this.engine.getSnapshot();
|
|
9415
|
+
}
|
|
9416
|
+
async getSubmissionExportRows(filter) {
|
|
9417
|
+
await this.ensureHydrated();
|
|
9418
|
+
return this.engine.getSubmissionExportRows(filter);
|
|
9419
|
+
}
|
|
9420
|
+
async exportSubmissionExcel(filter) {
|
|
9421
|
+
await this.ensureHydrated();
|
|
9422
|
+
return this.engine.exportSubmissionExcel(filter);
|
|
9423
|
+
}
|
|
9424
|
+
};
|
|
9425
|
+
var createMikuContestPersistentService = (options) => {
|
|
9426
|
+
return new MikuContestPersistentService(options);
|
|
9427
|
+
};
|
|
9428
|
+
|
|
9429
|
+
// src/mikuContest/server/db.ts
|
|
9430
|
+
var MikuContestDbService = class {
|
|
9431
|
+
constructor() {
|
|
9432
|
+
this._db = null;
|
|
9433
|
+
}
|
|
9434
|
+
setDb(db) {
|
|
9435
|
+
this._db = db;
|
|
9436
|
+
}
|
|
9437
|
+
isConfigured() {
|
|
9438
|
+
return Boolean(this._db);
|
|
9439
|
+
}
|
|
9440
|
+
get db() {
|
|
9441
|
+
if (!this._db) {
|
|
9442
|
+
throw new Error("MikuContestDbService: Database instance not set. Call setDb(db) first.");
|
|
9443
|
+
}
|
|
9444
|
+
return this._db;
|
|
9445
|
+
}
|
|
9446
|
+
};
|
|
9447
|
+
var mikuContestDbService = new MikuContestDbService();
|
|
9448
|
+
|
|
9449
|
+
// src/mikuContest/logic/hooks/useMikuContest.ts
|
|
9450
|
+
var useMikuContest = (options) => {
|
|
9451
|
+
const [service] = useState(() => createMikuContestService(options));
|
|
9452
|
+
const [version, setVersion] = useState(0);
|
|
9453
|
+
const refresh = () => setVersion((value) => value + 1);
|
|
9454
|
+
const snapshot = useMemo(() => {
|
|
9455
|
+
return service.getSnapshot();
|
|
9456
|
+
}, [service, version]);
|
|
9457
|
+
return {
|
|
9458
|
+
service,
|
|
9459
|
+
snapshot,
|
|
9460
|
+
refresh
|
|
9461
|
+
};
|
|
9462
|
+
};
|
|
9463
|
+
|
|
9464
|
+
// src/mikuContest/service/api/client.ts
|
|
9465
|
+
var toQueryString = (filter) => {
|
|
9466
|
+
if (!filter) return "";
|
|
9467
|
+
const params = new URLSearchParams();
|
|
9468
|
+
if (filter.status) params.set("status", filter.status);
|
|
9469
|
+
if (filter.type) params.set("type", filter.type);
|
|
9470
|
+
if (filter.authorId) params.set("authorId", filter.authorId);
|
|
9471
|
+
if (filter.authorKeyword) params.set("authorKeyword", filter.authorKeyword);
|
|
9472
|
+
if (filter.titleKeyword) params.set("titleKeyword", filter.titleKeyword);
|
|
9473
|
+
const query = params.toString();
|
|
9474
|
+
return query ? `?${query}` : "";
|
|
9475
|
+
};
|
|
9476
|
+
var unwrap = (result) => {
|
|
9477
|
+
if (!result.success || result.data === void 0) {
|
|
9478
|
+
throw new Error(result.error || "\u8BF7\u6C42\u5931\u8D25");
|
|
9479
|
+
}
|
|
9480
|
+
return result.data;
|
|
9481
|
+
};
|
|
9482
|
+
var createMikuContestApiClient = (basePath, requester) => {
|
|
9483
|
+
return {
|
|
9484
|
+
async getSnapshot() {
|
|
9485
|
+
const result = await requester(`${basePath}/contest`, { method: "GET" });
|
|
9486
|
+
return unwrap(result);
|
|
9487
|
+
},
|
|
9488
|
+
async updateContestConfig(patch) {
|
|
9489
|
+
const result = await requester(`${basePath}/contest`, {
|
|
9490
|
+
method: "PATCH",
|
|
9491
|
+
body: patch
|
|
9492
|
+
});
|
|
9493
|
+
return unwrap(result);
|
|
9494
|
+
},
|
|
9495
|
+
async createSubmission(input, mode = "web") {
|
|
9496
|
+
const result = await requester(`${basePath}/submissions`, {
|
|
9497
|
+
method: "POST",
|
|
9498
|
+
body: { payload: input, mode }
|
|
9499
|
+
});
|
|
9500
|
+
return unwrap(result);
|
|
9501
|
+
},
|
|
9502
|
+
async listSubmissions(filter) {
|
|
9503
|
+
const result = await requester(
|
|
9504
|
+
`${basePath}/submissions${toQueryString(filter)}`,
|
|
9505
|
+
{ method: "GET" }
|
|
9506
|
+
);
|
|
9507
|
+
return unwrap(result);
|
|
9508
|
+
},
|
|
9509
|
+
async reviewSubmission(input) {
|
|
9510
|
+
const result = await requester(`${basePath}/submissions/review`, {
|
|
9511
|
+
method: "POST",
|
|
9512
|
+
body: input
|
|
9513
|
+
});
|
|
9514
|
+
return unwrap(result);
|
|
9515
|
+
},
|
|
9516
|
+
async vote(input) {
|
|
9517
|
+
const result = await requester(`${basePath}/votes`, {
|
|
9518
|
+
method: "POST",
|
|
9519
|
+
body: input
|
|
9520
|
+
});
|
|
9521
|
+
return unwrap(result);
|
|
9522
|
+
},
|
|
9523
|
+
async setVoterRestriction(input) {
|
|
9524
|
+
const result = await requester(`${basePath}/admin/voter-restrictions`, {
|
|
9525
|
+
method: "POST",
|
|
9526
|
+
body: input
|
|
9527
|
+
});
|
|
9528
|
+
return unwrap(result);
|
|
9529
|
+
},
|
|
9530
|
+
async resetVotes(input) {
|
|
9531
|
+
const result = await requester(
|
|
9532
|
+
`${basePath}/admin/votes/reset`,
|
|
9533
|
+
{
|
|
9534
|
+
method: "POST",
|
|
9535
|
+
body: input
|
|
9536
|
+
}
|
|
9537
|
+
);
|
|
9538
|
+
return unwrap(result);
|
|
9539
|
+
},
|
|
9540
|
+
async exportSubmissions(filter) {
|
|
9541
|
+
const response = await fetch(`${basePath}/admin/submissions/export${toQueryString(filter)}`);
|
|
9542
|
+
if (!response.ok) {
|
|
9543
|
+
throw new Error(`\u5BFC\u51FA\u5931\u8D25: ${response.status}`);
|
|
9544
|
+
}
|
|
9545
|
+
return response.arrayBuffer();
|
|
9546
|
+
}
|
|
9547
|
+
};
|
|
9548
|
+
};
|
|
9549
|
+
|
|
9550
|
+
// src/mikuContest/service/web/index.ts
|
|
9551
|
+
var web_exports = {};
|
|
9552
|
+
__export(web_exports, {
|
|
9553
|
+
createMikuContestWebClient: () => createMikuContestWebClient
|
|
9554
|
+
});
|
|
9555
|
+
|
|
9556
|
+
// src/mikuContest/service/web/client.ts
|
|
9557
|
+
var defaultRequester = (options) => {
|
|
9558
|
+
const baseUrl = options.baseUrl || "";
|
|
9559
|
+
const commonHeaders = options.headers || {};
|
|
9560
|
+
return async (url, requestOptions) => {
|
|
9561
|
+
const response = await fetch(`${baseUrl}${url}`, {
|
|
9562
|
+
method: requestOptions?.method || "GET",
|
|
9563
|
+
headers: {
|
|
9564
|
+
"Content-Type": "application/json",
|
|
9565
|
+
...commonHeaders
|
|
9566
|
+
},
|
|
9567
|
+
body: requestOptions?.body ? JSON.stringify(requestOptions.body) : void 0
|
|
9568
|
+
});
|
|
9569
|
+
const json = await response.json();
|
|
9570
|
+
return json;
|
|
9571
|
+
};
|
|
9572
|
+
};
|
|
9573
|
+
var createMikuContestWebClient = (options = {}) => {
|
|
9574
|
+
const basePath = options.basePath || "/api/miku-contest";
|
|
9575
|
+
return createMikuContestApiClient(basePath, defaultRequester(options));
|
|
9576
|
+
};
|
|
9577
|
+
|
|
9578
|
+
// src/mikuContest/service/miniapp/index.ts
|
|
9579
|
+
var miniapp_exports = {};
|
|
9580
|
+
__export(miniapp_exports, {
|
|
9581
|
+
createMikuContestMiniappClient: () => createMikuContestMiniappClient
|
|
9582
|
+
});
|
|
9583
|
+
|
|
9584
|
+
// src/mikuContest/service/miniapp/client.ts
|
|
9585
|
+
var createMikuContestMiniappClient = (options) => {
|
|
9586
|
+
const basePath = options.basePath || "/api/miku-contest";
|
|
9587
|
+
return createMikuContestApiClient(basePath, options.requester);
|
|
9588
|
+
};
|
|
9589
|
+
var isDrizzleDb = (value) => {
|
|
9590
|
+
if (!value || typeof value !== "object") return false;
|
|
9591
|
+
const candidate = value;
|
|
9592
|
+
return typeof candidate.select === "function" && typeof candidate.insert === "function" && typeof candidate.update === "function" && typeof candidate.delete === "function";
|
|
9593
|
+
};
|
|
9594
|
+
var resolveService = (config) => {
|
|
9595
|
+
if (config?.service) return config.service;
|
|
9596
|
+
const adapter = config?.persistenceAdapter || (isDrizzleDb(config?.db) ? createMikuContestDrizzlePersistenceAdapter(config.db) : null);
|
|
9597
|
+
if (adapter) {
|
|
9598
|
+
return createMikuContestPersistentService({
|
|
9599
|
+
persistenceAdapter: adapter
|
|
9600
|
+
});
|
|
9601
|
+
}
|
|
9602
|
+
return createMikuContestService();
|
|
9603
|
+
};
|
|
9604
|
+
var createGetContestSnapshotHandler = (config) => {
|
|
9605
|
+
const service = resolveService(config);
|
|
9606
|
+
return async (_request) => {
|
|
9607
|
+
const data = await service.getSnapshot();
|
|
9608
|
+
return NextResponse.json({ success: true, data });
|
|
9609
|
+
};
|
|
9610
|
+
};
|
|
9611
|
+
var createUpdateContestConfigHandler = (config) => {
|
|
9612
|
+
const service = resolveService(config);
|
|
9613
|
+
return async (request) => {
|
|
9614
|
+
try {
|
|
9615
|
+
const payload = await request.json();
|
|
9616
|
+
const data = await service.updateContestConfig(payload);
|
|
9617
|
+
return NextResponse.json({ success: true, data });
|
|
9618
|
+
} catch (error) {
|
|
9619
|
+
return NextResponse.json({ success: false, error: error.message }, { status: 400 });
|
|
9620
|
+
}
|
|
9621
|
+
};
|
|
9622
|
+
};
|
|
9623
|
+
var createCreateSubmissionHandler = (config) => {
|
|
9624
|
+
const service = resolveService(config);
|
|
9625
|
+
return async (request) => {
|
|
9626
|
+
try {
|
|
9627
|
+
const body = await request.json();
|
|
9628
|
+
const mode = body.mode || "web";
|
|
9629
|
+
const payload = body.payload;
|
|
9630
|
+
if (!payload) {
|
|
9631
|
+
return NextResponse.json({ success: false, error: "payload \u4E0D\u80FD\u4E3A\u7A7A" }, { status: 400 });
|
|
9632
|
+
}
|
|
9633
|
+
const data = await service.createSubmission(payload, mode);
|
|
9634
|
+
return NextResponse.json({ success: true, data });
|
|
9635
|
+
} catch (error) {
|
|
9636
|
+
return NextResponse.json({ success: false, error: error.message }, { status: 400 });
|
|
9637
|
+
}
|
|
9638
|
+
};
|
|
9639
|
+
};
|
|
9640
|
+
var createVoteHandler = (config) => {
|
|
9641
|
+
const service = resolveService(config);
|
|
9642
|
+
return async (request) => {
|
|
9643
|
+
try {
|
|
9644
|
+
const payload = await request.json();
|
|
9645
|
+
const data = await service.vote(payload);
|
|
9646
|
+
return NextResponse.json({ success: true, data });
|
|
9647
|
+
} catch (error) {
|
|
9648
|
+
return NextResponse.json({ success: false, error: error.message }, { status: 400 });
|
|
9649
|
+
}
|
|
9650
|
+
};
|
|
9651
|
+
};
|
|
9652
|
+
var createReviewSubmissionHandler = (config) => {
|
|
9653
|
+
const service = resolveService(config);
|
|
9654
|
+
return async (request) => {
|
|
9655
|
+
try {
|
|
9656
|
+
const payload = await request.json();
|
|
9657
|
+
const data = await service.reviewSubmission(payload);
|
|
9658
|
+
return NextResponse.json({ success: true, data });
|
|
9659
|
+
} catch (error) {
|
|
9660
|
+
return NextResponse.json({ success: false, error: error.message }, { status: 400 });
|
|
9661
|
+
}
|
|
9662
|
+
};
|
|
9663
|
+
};
|
|
9664
|
+
var buildSubmissionFilterFromQuery = (request) => {
|
|
9665
|
+
const search = request.nextUrl.searchParams;
|
|
9666
|
+
const status = search.get("status");
|
|
9667
|
+
const type = search.get("type");
|
|
9668
|
+
return {
|
|
9669
|
+
status: status ? status : void 0,
|
|
9670
|
+
type: type ? type : void 0,
|
|
9671
|
+
authorId: search.get("authorId") || void 0,
|
|
9672
|
+
authorKeyword: search.get("authorKeyword") || void 0,
|
|
9673
|
+
titleKeyword: search.get("titleKeyword") || void 0
|
|
9674
|
+
};
|
|
9675
|
+
};
|
|
9676
|
+
var createListSubmissionsHandler = (config) => {
|
|
9677
|
+
const service = resolveService(config);
|
|
9678
|
+
return async (request) => {
|
|
9679
|
+
const filter = buildSubmissionFilterFromQuery(request);
|
|
9680
|
+
const data = await service.listSubmissions(filter);
|
|
9681
|
+
return NextResponse.json({ success: true, data });
|
|
9682
|
+
};
|
|
9683
|
+
};
|
|
9684
|
+
var createSetVoterRestrictionHandler = (config) => {
|
|
9685
|
+
const service = resolveService(config);
|
|
9686
|
+
return async (request) => {
|
|
9687
|
+
try {
|
|
9688
|
+
const payload = await request.json();
|
|
9689
|
+
const data = await service.setVoterRestriction(payload);
|
|
9690
|
+
return NextResponse.json({ success: true, data });
|
|
9691
|
+
} catch (error) {
|
|
9692
|
+
return NextResponse.json({ success: false, error: error.message }, { status: 400 });
|
|
9693
|
+
}
|
|
9694
|
+
};
|
|
9695
|
+
};
|
|
9696
|
+
var createResetVotesHandler = (config) => {
|
|
9697
|
+
const service = resolveService(config);
|
|
9698
|
+
return async (request) => {
|
|
9699
|
+
try {
|
|
9700
|
+
const payload = await request.json();
|
|
9701
|
+
const data = await service.resetVotes(payload);
|
|
9702
|
+
return NextResponse.json({ success: true, data });
|
|
9703
|
+
} catch (error) {
|
|
9704
|
+
return NextResponse.json({ success: false, error: error.message }, { status: 400 });
|
|
9705
|
+
}
|
|
9706
|
+
};
|
|
9707
|
+
};
|
|
9708
|
+
var createExportSubmissionsHandler = (config) => {
|
|
9709
|
+
const service = resolveService(config);
|
|
9710
|
+
return async (request) => {
|
|
9711
|
+
const filter = buildSubmissionFilterFromQuery(request);
|
|
9712
|
+
const data = await service.exportSubmissionExcel(filter);
|
|
9713
|
+
const body = data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
|
|
9714
|
+
return new NextResponse(body, {
|
|
9715
|
+
status: 200,
|
|
9716
|
+
headers: {
|
|
9717
|
+
"Content-Type": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
9718
|
+
"Content-Disposition": 'attachment; filename="miku-submissions.xlsx"'
|
|
9719
|
+
}
|
|
9720
|
+
});
|
|
9721
|
+
};
|
|
9722
|
+
};
|
|
9723
|
+
|
|
9724
|
+
// src/mikuContest/ui/web/index.ts
|
|
9725
|
+
var web_exports2 = {};
|
|
9726
|
+
__export(web_exports2, {
|
|
9727
|
+
MikuContestAdminPage: () => MikuContestAdminPage_default,
|
|
9728
|
+
MikuContestArtistPage: () => MikuContestArtistPage_default,
|
|
9729
|
+
MikuContestAudiencePage: () => MikuContestAudiencePage_default,
|
|
9730
|
+
MikuContestDashboard: () => MikuContestDashboard_default,
|
|
9731
|
+
MikuContestPage: () => MikuContestPage_default
|
|
9732
|
+
});
|
|
9733
|
+
var MikuContestDashboard = ({ snapshot }) => {
|
|
9734
|
+
return /* @__PURE__ */ React69__default.createElement("div", null, /* @__PURE__ */ React69__default.createElement("h2", null, snapshot.contest.name), /* @__PURE__ */ React69__default.createElement("p", null, snapshot.contest.theme), /* @__PURE__ */ React69__default.createElement("p", null, "\u6295\u7A3F\u6570\uFF1A", snapshot.submissions.length), /* @__PURE__ */ React69__default.createElement("p", null, "\u516C\u544A\u6570\uFF1A", snapshot.announcements.length), /* @__PURE__ */ React69__default.createElement("ul", null, snapshot.leaderboard.map((item) => /* @__PURE__ */ React69__default.createElement("li", { key: item.submissionId }, "#", item.rank, " ", item.title, " - ", item.voteCount, "\u7968"))));
|
|
9735
|
+
};
|
|
9736
|
+
var MikuContestDashboard_default = MikuContestDashboard;
|
|
9737
|
+
var MikuContestAudiencePage = ({
|
|
9738
|
+
client,
|
|
9739
|
+
voterId,
|
|
9740
|
+
title = "\u89C2\u4F17\u6295\u7968\u533A"
|
|
9741
|
+
}) => {
|
|
9742
|
+
const api = useMemo(() => client || createMikuContestWebClient(), [client]);
|
|
9743
|
+
const [snapshot, setSnapshot] = useState(null);
|
|
9744
|
+
const [loading, setLoading] = useState(false);
|
|
9745
|
+
const [error, setError] = useState(null);
|
|
9746
|
+
const approvedWorks = useMemo(() => {
|
|
9747
|
+
if (!snapshot) return [];
|
|
9748
|
+
return snapshot.submissions.filter((item) => item.status === "approved");
|
|
9749
|
+
}, [snapshot]);
|
|
9750
|
+
const loadSnapshot = async () => {
|
|
9751
|
+
setLoading(true);
|
|
9752
|
+
setError(null);
|
|
9753
|
+
try {
|
|
9754
|
+
const data = await api.getSnapshot();
|
|
9755
|
+
setSnapshot(data);
|
|
9756
|
+
} catch (e) {
|
|
9757
|
+
setError(e.message);
|
|
9758
|
+
} finally {
|
|
9759
|
+
setLoading(false);
|
|
9760
|
+
}
|
|
9761
|
+
};
|
|
9762
|
+
useEffect(() => {
|
|
9763
|
+
void loadSnapshot();
|
|
9764
|
+
}, []);
|
|
9765
|
+
const handleVote = async (submission) => {
|
|
9766
|
+
if (!snapshot) return;
|
|
9767
|
+
try {
|
|
9768
|
+
await api.vote({
|
|
9769
|
+
contestId: snapshot.contest.id,
|
|
9770
|
+
submissionId: submission.id,
|
|
9771
|
+
voterId
|
|
9772
|
+
});
|
|
9773
|
+
await loadSnapshot();
|
|
9774
|
+
} catch (e) {
|
|
9775
|
+
setError(e.message);
|
|
9776
|
+
}
|
|
9777
|
+
};
|
|
9778
|
+
return /* @__PURE__ */ React69__default.createElement("section", null, /* @__PURE__ */ React69__default.createElement("h2", null, title), /* @__PURE__ */ React69__default.createElement("button", { onClick: () => void loadSnapshot(), disabled: loading }, loading ? "\u5237\u65B0\u4E2D..." : "\u5237\u65B0\u6570\u636E"), error ? /* @__PURE__ */ React69__default.createElement("p", { style: { color: "crimson" } }, "\u9519\u8BEF\uFF1A", error) : null, !snapshot ? null : /* @__PURE__ */ React69__default.createElement(React69__default.Fragment, null, /* @__PURE__ */ React69__default.createElement("p", null, "\u8D5B\u4E8B\uFF1A", snapshot.contest.name, "\uFF5C\u4E3B\u9898\uFF1A", snapshot.contest.theme), /* @__PURE__ */ React69__default.createElement("p", null, "\u5DF2\u8FC7\u5BA1\u4F5C\u54C1\uFF1A", approvedWorks.length, "\uFF5C\u6BCF\u65E5\u4E0A\u9650\uFF1A", snapshot.contest.votingRules.maxVotesPerDay), /* @__PURE__ */ React69__default.createElement("ul", null, approvedWorks.map((work) => /* @__PURE__ */ React69__default.createElement("li", { key: work.id }, /* @__PURE__ */ React69__default.createElement("strong", null, work.title), "\uFF08", work.authorNickname, "\uFF09- \u5F53\u524D ", work.voteCount, " \u7968", " ", /* @__PURE__ */ React69__default.createElement("button", { onClick: () => void handleVote(work) }, "\u6295\u7968"))))));
|
|
9779
|
+
};
|
|
9780
|
+
var MikuContestAudiencePage_default = MikuContestAudiencePage;
|
|
9781
|
+
var workTypes = ["visual", "video", "text", "audio"];
|
|
9782
|
+
var MikuContestArtistPage = ({
|
|
9783
|
+
client,
|
|
9784
|
+
authorId,
|
|
9785
|
+
authorNickname,
|
|
9786
|
+
title = "\u753B\u5E08\u6295\u7A3F\u533A"
|
|
9787
|
+
}) => {
|
|
9788
|
+
const api = useMemo(() => client || createMikuContestWebClient(), [client]);
|
|
9789
|
+
const [snapshot, setSnapshot] = useState(null);
|
|
9790
|
+
const [mySubmissions, setMySubmissions] = useState([]);
|
|
9791
|
+
const [submitting, setSubmitting] = useState(false);
|
|
9792
|
+
const [loading, setLoading] = useState(false);
|
|
9793
|
+
const [error, setError] = useState(null);
|
|
9794
|
+
const [titleInput, setTitleInput] = useState("");
|
|
9795
|
+
const [descInput, setDescInput] = useState("");
|
|
9796
|
+
const [coverImage, setCoverImage] = useState("");
|
|
9797
|
+
const [workType, setWorkType] = useState("visual");
|
|
9798
|
+
const loadData = async () => {
|
|
9799
|
+
setLoading(true);
|
|
9800
|
+
setError(null);
|
|
9801
|
+
try {
|
|
9802
|
+
const [contest, mine] = await Promise.all([
|
|
9803
|
+
api.getSnapshot(),
|
|
9804
|
+
api.listSubmissions({ authorId })
|
|
9805
|
+
]);
|
|
9806
|
+
setSnapshot(contest);
|
|
9807
|
+
setMySubmissions(mine);
|
|
9808
|
+
} catch (e) {
|
|
9809
|
+
setError(e.message);
|
|
9810
|
+
} finally {
|
|
9811
|
+
setLoading(false);
|
|
9812
|
+
}
|
|
9813
|
+
};
|
|
9814
|
+
useEffect(() => {
|
|
9815
|
+
void loadData();
|
|
9816
|
+
}, []);
|
|
9817
|
+
const submitWork = async () => {
|
|
9818
|
+
if (!snapshot) return;
|
|
9819
|
+
const payload = {
|
|
9820
|
+
contestId: snapshot.contest.id,
|
|
9821
|
+
authorId,
|
|
9822
|
+
authorNickname,
|
|
9823
|
+
title: titleInput,
|
|
9824
|
+
description: descInput,
|
|
9825
|
+
type: workType,
|
|
9826
|
+
tags: ["web"],
|
|
9827
|
+
content: {
|
|
9828
|
+
coverImage,
|
|
9829
|
+
images: coverImage ? [coverImage] : void 0
|
|
9830
|
+
}
|
|
9831
|
+
};
|
|
9832
|
+
setSubmitting(true);
|
|
9833
|
+
setError(null);
|
|
9834
|
+
try {
|
|
9835
|
+
await api.createSubmission(payload, "web");
|
|
9836
|
+
setTitleInput("");
|
|
9837
|
+
setDescInput("");
|
|
9838
|
+
setCoverImage("");
|
|
9839
|
+
await loadData();
|
|
9840
|
+
} catch (e) {
|
|
9841
|
+
setError(e.message);
|
|
9842
|
+
} finally {
|
|
9843
|
+
setSubmitting(false);
|
|
9844
|
+
}
|
|
9845
|
+
};
|
|
9846
|
+
return /* @__PURE__ */ React69__default.createElement("section", null, /* @__PURE__ */ React69__default.createElement("h2", null, title), /* @__PURE__ */ React69__default.createElement("button", { onClick: () => void loadData(), disabled: loading }, loading ? "\u5237\u65B0\u4E2D..." : "\u5237\u65B0\u6570\u636E"), error ? /* @__PURE__ */ React69__default.createElement("p", { style: { color: "crimson" } }, "\u9519\u8BEF\uFF1A", error) : null, /* @__PURE__ */ React69__default.createElement("div", null, /* @__PURE__ */ React69__default.createElement("h3", null, "\u65B0\u5EFA\u6295\u7A3F"), /* @__PURE__ */ React69__default.createElement("input", { value: titleInput, onChange: (e) => setTitleInput(e.target.value), placeholder: "\u4F5C\u54C1\u6807\u9898" }), /* @__PURE__ */ React69__default.createElement("br", null), /* @__PURE__ */ React69__default.createElement("textarea", { value: descInput, onChange: (e) => setDescInput(e.target.value), placeholder: "\u4F5C\u54C1\u7B80\u4ECB" }), /* @__PURE__ */ React69__default.createElement("br", null), /* @__PURE__ */ React69__default.createElement("input", { value: coverImage, onChange: (e) => setCoverImage(e.target.value), placeholder: "\u5C01\u9762 URL" }), /* @__PURE__ */ React69__default.createElement("br", null), /* @__PURE__ */ React69__default.createElement("select", { value: workType, onChange: (e) => setWorkType(e.target.value) }, workTypes.map((item) => /* @__PURE__ */ React69__default.createElement("option", { value: item, key: item }, item))), /* @__PURE__ */ React69__default.createElement("button", { onClick: () => void submitWork(), disabled: submitting || !snapshot }, submitting ? "\u63D0\u4EA4\u4E2D..." : "\u63D0\u4EA4\u7A3F\u4EF6")), /* @__PURE__ */ React69__default.createElement("div", null, /* @__PURE__ */ React69__default.createElement("h3", null, "\u6211\u7684\u6295\u7A3F\uFF08", mySubmissions.length, "\uFF09"), /* @__PURE__ */ React69__default.createElement("ul", null, mySubmissions.map((item) => /* @__PURE__ */ React69__default.createElement("li", { key: item.id }, item.title, "\uFF5C\u72B6\u6001\uFF1A", item.status, "\uFF5C\u7968\u6570\uFF1A", item.voteCount, item.rejectReason ? `\uFF5C\u9A73\u56DE\uFF1A${item.rejectReason}` : "")))));
|
|
9847
|
+
};
|
|
9848
|
+
var MikuContestArtistPage_default = MikuContestArtistPage;
|
|
9849
|
+
var MikuContestAdminPage = ({
|
|
9850
|
+
client,
|
|
9851
|
+
adminId,
|
|
9852
|
+
title = "\u7BA1\u7406\u5458\u9762\u677F"
|
|
9853
|
+
}) => {
|
|
9854
|
+
const api = useMemo(() => client || createMikuContestWebClient(), [client]);
|
|
9855
|
+
const [snapshot, setSnapshot] = useState(null);
|
|
9856
|
+
const [submissions, setSubmissions] = useState([]);
|
|
9857
|
+
const [loading, setLoading] = useState(false);
|
|
9858
|
+
const [error, setError] = useState(null);
|
|
9859
|
+
const [voterId, setVoterId] = useState("");
|
|
9860
|
+
const loadData = async () => {
|
|
9861
|
+
setLoading(true);
|
|
9862
|
+
setError(null);
|
|
9863
|
+
try {
|
|
9864
|
+
const [contest, list] = await Promise.all([api.getSnapshot(), api.listSubmissions()]);
|
|
9865
|
+
setSnapshot(contest);
|
|
9866
|
+
setSubmissions(list);
|
|
9867
|
+
} catch (e) {
|
|
9868
|
+
setError(e.message);
|
|
9869
|
+
} finally {
|
|
9870
|
+
setLoading(false);
|
|
9871
|
+
}
|
|
9872
|
+
};
|
|
9873
|
+
useEffect(() => {
|
|
9874
|
+
void loadData();
|
|
9875
|
+
}, []);
|
|
9876
|
+
const review = async (item, action) => {
|
|
9877
|
+
try {
|
|
9878
|
+
await api.reviewSubmission({
|
|
9879
|
+
submissionId: item.id,
|
|
9880
|
+
reviewerId: adminId,
|
|
9881
|
+
action,
|
|
9882
|
+
rejectReason: action === "reject" ? "\u7BA1\u7406\u5458\u9A73\u56DE" : void 0
|
|
9883
|
+
});
|
|
9884
|
+
await loadData();
|
|
9885
|
+
} catch (e) {
|
|
9886
|
+
setError(e.message);
|
|
9887
|
+
}
|
|
9888
|
+
};
|
|
9889
|
+
const toggleVoting = async (enabled) => {
|
|
9890
|
+
if (!snapshot) return;
|
|
9891
|
+
try {
|
|
9892
|
+
await api.updateContestConfig({
|
|
9893
|
+
toggles: {
|
|
9894
|
+
...snapshot.contest.toggles,
|
|
9895
|
+
votingEnabled: enabled
|
|
9896
|
+
}
|
|
9897
|
+
});
|
|
9898
|
+
await loadData();
|
|
9899
|
+
} catch (e) {
|
|
9900
|
+
setError(e.message);
|
|
9901
|
+
}
|
|
9902
|
+
};
|
|
9903
|
+
const setRestriction = async (banned) => {
|
|
9904
|
+
if (!snapshot || !voterId.trim()) return;
|
|
9905
|
+
try {
|
|
9906
|
+
await api.setVoterRestriction({
|
|
9907
|
+
voterId: voterId.trim(),
|
|
9908
|
+
banned,
|
|
9909
|
+
reason: banned ? "\u7BA1\u7406\u5458\u624B\u52A8\u5C01\u7981" : "\u7BA1\u7406\u5458\u89E3\u9664\u5C01\u7981",
|
|
9910
|
+
operatorId: adminId
|
|
9911
|
+
});
|
|
9912
|
+
setVoterId("");
|
|
9913
|
+
} catch (e) {
|
|
9914
|
+
setError(e.message);
|
|
9915
|
+
}
|
|
9916
|
+
};
|
|
9917
|
+
const resetVotesByVoter = async () => {
|
|
9918
|
+
if (!voterId.trim()) return;
|
|
9919
|
+
try {
|
|
9920
|
+
await api.resetVotes({ voterId: voterId.trim() });
|
|
9921
|
+
setVoterId("");
|
|
9922
|
+
await loadData();
|
|
9923
|
+
} catch (e) {
|
|
9924
|
+
setError(e.message);
|
|
9925
|
+
}
|
|
9926
|
+
};
|
|
9927
|
+
const exportExcel = async () => {
|
|
9928
|
+
try {
|
|
9929
|
+
const data = await api.exportSubmissions();
|
|
9930
|
+
const blob = new Blob([data], {
|
|
9931
|
+
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
|
9932
|
+
});
|
|
9933
|
+
const url = URL.createObjectURL(blob);
|
|
9934
|
+
const a = document.createElement("a");
|
|
9935
|
+
a.href = url;
|
|
9936
|
+
a.download = "miku-submissions.xlsx";
|
|
9937
|
+
a.click();
|
|
9938
|
+
URL.revokeObjectURL(url);
|
|
9939
|
+
} catch (e) {
|
|
9940
|
+
setError(e.message);
|
|
9941
|
+
}
|
|
9942
|
+
};
|
|
9943
|
+
return /* @__PURE__ */ React69__default.createElement("section", null, /* @__PURE__ */ React69__default.createElement("h2", null, title), /* @__PURE__ */ React69__default.createElement("button", { onClick: () => void loadData(), disabled: loading }, loading ? "\u5237\u65B0\u4E2D..." : "\u5237\u65B0\u6570\u636E"), error ? /* @__PURE__ */ React69__default.createElement("p", { style: { color: "crimson" } }, "\u9519\u8BEF\uFF1A", error) : null, /* @__PURE__ */ React69__default.createElement("div", null, /* @__PURE__ */ React69__default.createElement("h3", null, "\u8D5B\u4E8B\u5F00\u5173"), /* @__PURE__ */ React69__default.createElement("button", { onClick: () => void toggleVoting(true), disabled: !snapshot }, "\u5F00\u542F\u6295\u7968"), /* @__PURE__ */ React69__default.createElement("button", { onClick: () => void toggleVoting(false), disabled: !snapshot }, "\u5173\u95ED\u6295\u7968")), /* @__PURE__ */ React69__default.createElement("div", null, /* @__PURE__ */ React69__default.createElement("h3", null, "\u6295\u7A3F\u5BA1\u6838\uFF08", submissions.length, "\uFF09"), /* @__PURE__ */ React69__default.createElement("ul", null, submissions.map((item) => /* @__PURE__ */ React69__default.createElement("li", { key: item.id }, /* @__PURE__ */ React69__default.createElement("strong", null, item.title), "\uFF5C\u4F5C\u8005\uFF1A", item.authorNickname, "\uFF5C\u72B6\u6001\uFF1A", item.status, "\uFF5C\u7968\u6570\uFF1A", item.voteCount, item.status === "pending" ? /* @__PURE__ */ React69__default.createElement(React69__default.Fragment, null, " ", /* @__PURE__ */ React69__default.createElement("button", { onClick: () => void review(item, "approve") }, "\u901A\u8FC7"), /* @__PURE__ */ React69__default.createElement("button", { onClick: () => void review(item, "reject") }, "\u9A73\u56DE")) : null)))), /* @__PURE__ */ React69__default.createElement("div", null, /* @__PURE__ */ React69__default.createElement("h3", null, "\u6295\u7968\u98CE\u63A7"), /* @__PURE__ */ React69__default.createElement("input", { value: voterId, onChange: (e) => setVoterId(e.target.value), placeholder: "voterId" }), /* @__PURE__ */ React69__default.createElement("button", { onClick: () => void setRestriction(true), disabled: !snapshot }, "\u5C01\u7981\u6295\u7968"), /* @__PURE__ */ React69__default.createElement("button", { onClick: () => void setRestriction(false), disabled: !snapshot }, "\u89E3\u9664\u5C01\u7981"), /* @__PURE__ */ React69__default.createElement("button", { onClick: () => void resetVotesByVoter() }, "\u6E05\u96F6\u8BE5\u7528\u6237\u7968\u6570")), /* @__PURE__ */ React69__default.createElement("div", null, /* @__PURE__ */ React69__default.createElement("h3", null, "\u5BFC\u51FA"), /* @__PURE__ */ React69__default.createElement("button", { onClick: () => void exportExcel() }, "\u5BFC\u51FA\u6295\u7A3F Excel")));
|
|
9944
|
+
};
|
|
9945
|
+
var MikuContestAdminPage_default = MikuContestAdminPage;
|
|
9946
|
+
|
|
9947
|
+
// src/mikuContest/ui/web/pages/MikuContestPage.tsx
|
|
9948
|
+
var MikuContestPage = ({
|
|
9949
|
+
defaultView = "audience",
|
|
9950
|
+
viewerVoterId = "viewer-demo",
|
|
9951
|
+
artistId = "artist-demo",
|
|
9952
|
+
artistNickname = "Demo \u753B\u5E08",
|
|
9953
|
+
adminId = "admin-demo"
|
|
9954
|
+
}) => {
|
|
9955
|
+
const [view, setView] = useState(defaultView);
|
|
9956
|
+
return /* @__PURE__ */ React69__default.createElement("div", null, /* @__PURE__ */ React69__default.createElement("h1", null, "Miku Contest"), /* @__PURE__ */ React69__default.createElement("div", null, /* @__PURE__ */ React69__default.createElement("button", { onClick: () => setView("audience") }, "\u89C2\u4F17\u7AEF"), /* @__PURE__ */ React69__default.createElement("button", { onClick: () => setView("artist") }, "\u753B\u5E08\u7AEF"), /* @__PURE__ */ React69__default.createElement("button", { onClick: () => setView("admin") }, "\u7BA1\u7406\u5458\u7AEF")), view === "audience" ? /* @__PURE__ */ React69__default.createElement(MikuContestAudiencePage_default, { voterId: viewerVoterId }) : null, view === "artist" ? /* @__PURE__ */ React69__default.createElement(MikuContestArtistPage_default, { authorId: artistId, authorNickname: artistNickname }) : null, view === "admin" ? /* @__PURE__ */ React69__default.createElement(MikuContestAdminPage_default, { adminId }) : null);
|
|
9957
|
+
};
|
|
9958
|
+
var MikuContestPage_default = MikuContestPage;
|
|
9959
|
+
|
|
9960
|
+
// src/mikuContest/ui/miniapp/index.ts
|
|
9961
|
+
var miniapp_exports2 = {};
|
|
9962
|
+
__export(miniapp_exports2, {
|
|
9963
|
+
MikuContestMiniappHome: () => MikuContestMiniappHome_default,
|
|
9964
|
+
MikuContestMiniappPage: () => MikuContestMiniappPage_default
|
|
9965
|
+
});
|
|
9966
|
+
var MikuContestMiniappHome = ({ snapshot }) => {
|
|
9967
|
+
return /* @__PURE__ */ React69__default.createElement("div", null, /* @__PURE__ */ React69__default.createElement("h3", null, snapshot.contest.name), /* @__PURE__ */ React69__default.createElement("p", null, "\u6295\u7A3F\uFF1A", snapshot.submissions.length, " | \u516C\u544A\uFF1A", snapshot.announcements.length), /* @__PURE__ */ React69__default.createElement("ol", null, snapshot.leaderboard.slice(0, 3).map((item) => /* @__PURE__ */ React69__default.createElement("li", { key: item.submissionId }, item.title, "\uFF08", item.voteCount, "\u7968\uFF09"))));
|
|
9968
|
+
};
|
|
9969
|
+
var MikuContestMiniappHome_default = MikuContestMiniappHome;
|
|
9970
|
+
var MikuContestMiniappPage = () => {
|
|
9971
|
+
const { service, snapshot, refresh } = useMikuContest();
|
|
9972
|
+
const [tab, setTab] = useState("vote");
|
|
9973
|
+
const [voterId, setVoterId] = useState("miniapp-voter");
|
|
9974
|
+
const [authorId, setAuthorId] = useState("miniapp-author");
|
|
9975
|
+
const [authorNickname, setAuthorNickname] = useState("\u5C0F\u7A0B\u5E8F\u753B\u5E08");
|
|
9976
|
+
const [title, setTitle] = useState("");
|
|
9977
|
+
const [desc, setDesc] = useState("");
|
|
9978
|
+
const [type, setType] = useState("visual");
|
|
9979
|
+
const [error, setError] = useState(null);
|
|
9980
|
+
const approvedWorks = useMemo(() => {
|
|
9981
|
+
return snapshot.submissions.filter((item) => item.status === "approved");
|
|
9982
|
+
}, [snapshot.submissions]);
|
|
9983
|
+
const vote = (submissionId) => {
|
|
9984
|
+
try {
|
|
9985
|
+
service.vote({
|
|
9986
|
+
contestId: snapshot.contest.id,
|
|
9987
|
+
submissionId,
|
|
9988
|
+
voterId
|
|
9989
|
+
});
|
|
9990
|
+
refresh();
|
|
9991
|
+
setError(null);
|
|
9992
|
+
} catch (e) {
|
|
9993
|
+
setError(e.message);
|
|
9994
|
+
}
|
|
9995
|
+
};
|
|
9996
|
+
const submit = () => {
|
|
9997
|
+
try {
|
|
9998
|
+
service.createSubmission(
|
|
9999
|
+
{
|
|
10000
|
+
contestId: snapshot.contest.id,
|
|
10001
|
+
authorId,
|
|
10002
|
+
authorNickname,
|
|
10003
|
+
title,
|
|
10004
|
+
description: desc,
|
|
10005
|
+
type,
|
|
10006
|
+
content: {}
|
|
10007
|
+
},
|
|
10008
|
+
"miniapp"
|
|
10009
|
+
);
|
|
10010
|
+
setTitle("");
|
|
10011
|
+
setDesc("");
|
|
10012
|
+
refresh();
|
|
10013
|
+
setError(null);
|
|
10014
|
+
} catch (e) {
|
|
10015
|
+
setError(e.message);
|
|
10016
|
+
}
|
|
10017
|
+
};
|
|
10018
|
+
return /* @__PURE__ */ React69__default.createElement("div", null, /* @__PURE__ */ React69__default.createElement("h3", null, snapshot.contest.name), /* @__PURE__ */ React69__default.createElement("p", null, "\u5C0F\u7A0B\u5E8F\u7AEF\u793A\u4F8B\u9875\u9762"), /* @__PURE__ */ React69__default.createElement("button", { onClick: () => setTab("vote") }, "\u89C2\u4F17\u6295\u7968"), /* @__PURE__ */ React69__default.createElement("button", { onClick: () => setTab("submit") }, "\u753B\u5E08\u6295\u7A3F"), error ? /* @__PURE__ */ React69__default.createElement("p", { style: { color: "crimson" } }, "\u9519\u8BEF\uFF1A", error) : null, tab === "vote" ? /* @__PURE__ */ React69__default.createElement("div", null, /* @__PURE__ */ React69__default.createElement("input", { value: voterId, onChange: (e) => setVoterId(e.target.value), placeholder: "voterId" }), /* @__PURE__ */ React69__default.createElement("ul", null, approvedWorks.map((item) => /* @__PURE__ */ React69__default.createElement("li", { key: item.id }, item.title, "\uFF08", item.voteCount, "\u7968\uFF09", /* @__PURE__ */ React69__default.createElement("button", { onClick: () => vote(item.id) }, "\u6295\u7968"))))) : null, tab === "submit" ? /* @__PURE__ */ React69__default.createElement("div", null, /* @__PURE__ */ React69__default.createElement("input", { value: authorId, onChange: (e) => setAuthorId(e.target.value), placeholder: "authorId" }), /* @__PURE__ */ React69__default.createElement("input", { value: authorNickname, onChange: (e) => setAuthorNickname(e.target.value), placeholder: "\u4F5C\u8005\u6635\u79F0" }), /* @__PURE__ */ React69__default.createElement("input", { value: title, onChange: (e) => setTitle(e.target.value), placeholder: "\u4F5C\u54C1\u6807\u9898" }), /* @__PURE__ */ React69__default.createElement("input", { value: desc, onChange: (e) => setDesc(e.target.value), placeholder: "\u4F5C\u54C1\u7B80\u4ECB" }), /* @__PURE__ */ React69__default.createElement("select", { value: type, onChange: (e) => setType(e.target.value) }, /* @__PURE__ */ React69__default.createElement("option", { value: "visual" }, "visual"), /* @__PURE__ */ React69__default.createElement("option", { value: "video" }, "video"), /* @__PURE__ */ React69__default.createElement("option", { value: "text" }, "text"), /* @__PURE__ */ React69__default.createElement("option", { value: "audio" }, "audio")), /* @__PURE__ */ React69__default.createElement("button", { onClick: submit }, "\u63D0\u4EA4")) : null);
|
|
10019
|
+
};
|
|
10020
|
+
var MikuContestMiniappPage_default = MikuContestMiniappPage;
|
|
10021
|
+
|
|
8486
10022
|
// src/storage/adapters/react-native-adapter.ts
|
|
8487
10023
|
var AsyncStorage = null;
|
|
8488
10024
|
try {
|
|
@@ -8836,6 +10372,6 @@ function useElectronStorage(key, defaultValue) {
|
|
|
8836
10372
|
return useStorage(electronStorage, key, defaultValue);
|
|
8837
10373
|
}
|
|
8838
10374
|
|
|
8839
|
-
export { About_default as About, Dialog as AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, DialogDescription as AlertDialogDescription, DialogFooter as AlertDialogFooter, DialogHeader as AlertDialogHeader, DialogOverlay as AlertDialogOverlay, DialogPortal as AlertDialogPortal, DialogTitle as AlertDialogTitle, DialogTrigger as AlertDialogTrigger, AutoOpenModal, Avatar, AvatarFallback, AvatarImage, BackButton, BackgroundRemover, Badge, BoothConfigPage, BoothRedeemPanel, BoothSuccessCard, BoothUploadPanel, BoothVaultService, Button, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, CategoryFilter, CollisionBalls, CompletionFilterComponent, ConfirmModal, ConsoleLoggerAdapter, Contact_default as Contact, DANMAKU_MAX_LENGTH, DANMAKU_TRACK_COUNT, DEFAULT_FESTIVAL_CARD_CONFIG, DEFAULT_MAX_ACTIVE_FIREWORKS, DEFAULT_MAX_PARTICLES, DEFAULT_OPENAI_BASE_URL, DEFAULT_OPENAI_MODEL, DanmakuPanel, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DraggableExperimentGrid, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, EmptyState, EnhancedAvatar, ExperimentCard, ExperimentGrid, ExperimentItemGrid, FIREWORK_KIND_LABELS, FestivalCardBook3D, FestivalCardConfigEditor, FestivalCardConfigPage, FestivalCardManagedPage, FestivalCardPageRenderer, FestivalCardService, FestivalCardStudio, FilterButtonGroup, FireworksCanvas, FireworksControlPanel, FloatingMenu_default as FloatingMenu, FloatingMenuExample_default as FloatingMenuExample, GenericOrderManager, Grid, Home_default as Home, ImageMappingPanel, InMemorySkillRegistry, Input, Label, LocalImageMappingPanel, LogLevel, Logger, MIKU_PALETTE, MikuFireworks3D, Modal, NORMAL_PALETTE, Navigation_default as Navigation, NavigationItem_default as NavigationItem, NavigationToggle_default as NavigationToggle, OCRScanner, PageHeader, PermissionGuard, Popover, PopoverAnchor, PopoverContent, PopoverTrigger, ProfileButton, ProfileModal, Progress, ProjectCarousel, ScreenReceiverPanel, ScrollArea, ScrollBar, SearchBox, SearchResultHint, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, SentimentAnalyzer, Separator, Dialog as Sheet, DialogClose as SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, DialogOverlay as SheetOverlay, DialogPortal as SheetPortal, SheetTitle, DialogTrigger as SheetTrigger, SmartAssistant, SortControl, SortModeToggle, SortableExperimentItem, Tabs, TabsContent, TabsList, TabsTrigger, Textarea, Timeline, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, UserInfoBar, WebSocketTransport, applyPromptTemplate, arrayUtils, badgeVariants, buttonVariants, cn, createAiClient, createChatSession, createInMemoryFestivalCardDb, createLogger, createOpenAICompatibleProvider, createSkillRegistry, debugUtils, defaultVocaloidBoothConfig, errorUtils, fileUtils, filterExperiments, formatTime, generateMatchCode, getAllTags, getCategoryColor, getCategoryDisplayName, getCompletionFilterDisplayName, getCompletionStatusColor, getCompletionStatusText, getExperimentCounts, japaneseUtils, logger, normalizeFestivalCardConfig, normalizeMatchCode, normalizePromptVariables, normalizeVocaloidBoothConfig, resizeFestivalCardPages, resolveScreenReceiverSignalUrl, skillToToolDefinition, sortExperiments, stringUtils, useAiChat, useAsyncStorage, useBackgroundRemoval, useDanmakuController, useElectronStorage, useFestivalCardConfig, useFireworksEngine, useFireworksRealtime, useLocalStorage, useOCR, useScreenReceiver, useSentimentAnalysis, useStorage, useTaroStorage, useTextGeneration, validateExperiment, validators };
|
|
10375
|
+
export { About_default as About, Dialog as AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, DialogDescription as AlertDialogDescription, DialogFooter as AlertDialogFooter, DialogHeader as AlertDialogHeader, DialogOverlay as AlertDialogOverlay, DialogPortal as AlertDialogPortal, DialogTitle as AlertDialogTitle, DialogTrigger as AlertDialogTrigger, AutoOpenModal, Avatar, AvatarFallback, AvatarImage, BackButton, BackgroundRemover, Badge, BoothConfigPage, BoothRedeemPanel, BoothSuccessCard, BoothUploadPanel, BoothVaultService, Button, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, CategoryFilter, CollisionBalls, CompletionFilterComponent, ConfirmModal, ConsoleLoggerAdapter, Contact_default as Contact, DANMAKU_MAX_LENGTH, DANMAKU_TRACK_COUNT, DEFAULT_FESTIVAL_CARD_CONFIG, DEFAULT_MAX_ACTIVE_FIREWORKS, DEFAULT_MAX_PARTICLES, DEFAULT_OPENAI_BASE_URL, DEFAULT_OPENAI_MODEL, DanmakuPanel, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DraggableExperimentGrid, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, EmptyState, EnhancedAvatar, ExperimentCard, ExperimentGrid, ExperimentItemGrid, FIREWORK_KIND_LABELS, FestivalCardBook3D, FestivalCardConfigEditor, FestivalCardConfigPage, FestivalCardManagedPage, FestivalCardPageRenderer, FestivalCardService, FestivalCardStudio, FilterButtonGroup, FireworksCanvas, FireworksControlPanel, FloatingMenu_default as FloatingMenu, FloatingMenuExample_default as FloatingMenuExample, GenericOrderManager, Grid, Home_default as Home, ImageMappingPanel, InMemorySkillRegistry, Input, Label, LocalImageMappingPanel, LogLevel, Logger, MIKU_PALETTE, MikuContestPersistentService, MikuContestService, MikuContestStateDbService, MikuFireworks3D, Modal, NORMAL_PALETTE, Navigation_default as Navigation, NavigationItem_default as NavigationItem, NavigationToggle_default as NavigationToggle, OCRScanner, PageHeader, PermissionGuard, Popover, PopoverAnchor, PopoverContent, PopoverTrigger, ProfileButton, ProfileModal, Progress, ProjectCarousel, ScreenReceiverPanel, ScrollArea, ScrollBar, SearchBox, SearchResultHint, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, SentimentAnalyzer, Separator, Dialog as Sheet, DialogClose as SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, DialogOverlay as SheetOverlay, DialogPortal as SheetPortal, SheetTitle, DialogTrigger as SheetTrigger, SmartAssistant, SortControl, SortModeToggle, SortableExperimentItem, Tabs, TabsContent, TabsList, TabsTrigger, Textarea, Timeline, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, UserInfoBar, WebSocketTransport, applyPromptTemplate, arrayUtils, badgeVariants, buttonVariants, checkVoteEligibility, cn, createAiClient, createChatSession, createCreateSubmissionHandler, createDefaultMikuContestConfig, createExportSubmissionsHandler, createGetContestSnapshotHandler, createInMemoryFestivalCardDb, createListSubmissionsHandler, createLogger, createMikuContestApiClient, createMikuContestDrizzlePersistenceAdapter, createMikuContestPersistentService, createMikuContestService, createOpenAICompatibleProvider, createResetVotesHandler, createReviewSubmissionHandler, createSetVoterRestrictionHandler, createSkillRegistry, createUpdateContestConfigHandler, createVoteHandler, debugUtils, defaultMikuVotingRules, defaultVocaloidBoothConfig, errorUtils, fileUtils, filterExperiments, formatTime, generateMatchCode, getAllTags, getCategoryColor, getCategoryDisplayName, getCompletionFilterDisplayName, getCompletionStatusColor, getCompletionStatusText, getExperimentCounts, japaneseUtils, logger, mikuContestConfigs, mikuContestDbService, mikuContestNotices, mikuContestSubmissions, mikuContestVoterRestrictions, mikuContestVotes, miniapp_exports as miniappService, miniapp_exports2 as miniappUI, normalizeFestivalCardConfig, normalizeMatchCode, normalizePromptVariables, normalizeVocaloidBoothConfig, resizeFestivalCardPages, resolveScreenReceiverSignalUrl, skillToToolDefinition, sortByVotesDesc, sortExperiments, stringUtils, toVoteDayKey, useAiChat, useAsyncStorage, useBackgroundRemoval, useDanmakuController, useElectronStorage, useFestivalCardConfig, useFireworksEngine, useFireworksRealtime, useLocalStorage, useMikuContest, useOCR, useScreenReceiver, useSentimentAnalysis, useStorage, useTaroStorage, useTextGeneration, validateExperiment, validateMikuSubmissionInput, validators, web_exports as webService, web_exports2 as webUI };
|
|
8840
10376
|
//# sourceMappingURL=index.mjs.map
|
|
8841
10377
|
//# sourceMappingURL=index.mjs.map
|