sa2kit 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/dist/{UniversalFileService-CEZRJ87g.d.mts → UniversalFileService-BuHN-jrR.d.ts} +47 -259
  2. package/dist/{UniversalFileService-CEZRJ87g.d.ts → UniversalFileService-CGGzYeeF.d.mts} +47 -259
  3. package/dist/{chunk-3XG5OHFD.mjs → chunk-CIVO4R6N.mjs} +2 -2
  4. package/dist/{chunk-3XG5OHFD.mjs.map → chunk-CIVO4R6N.mjs.map} +1 -1
  5. package/dist/chunk-EV6BCVOQ.mjs +204 -0
  6. package/dist/chunk-EV6BCVOQ.mjs.map +1 -0
  7. package/dist/chunk-W35VTQAW.js +211 -0
  8. package/dist/chunk-W35VTQAW.js.map +1 -0
  9. package/dist/{chunk-HWJ34NL6.js → chunk-ZRAW3HXA.js} +2 -2
  10. package/dist/{chunk-HWJ34NL6.js.map → chunk-ZRAW3HXA.js.map} +1 -1
  11. package/dist/drizzle-schema-BNhqj2AZ.d.mts +1114 -0
  12. package/dist/drizzle-schema-BNhqj2AZ.d.ts +1114 -0
  13. package/dist/mmd/admin/index.d.mts +487 -0
  14. package/dist/mmd/admin/index.d.ts +487 -0
  15. package/dist/mmd/admin/index.js +871 -0
  16. package/dist/mmd/admin/index.js.map +1 -0
  17. package/dist/mmd/admin/index.mjs +822 -0
  18. package/dist/mmd/admin/index.mjs.map +1 -0
  19. package/dist/mmd/index.d.mts +4 -193
  20. package/dist/mmd/index.d.ts +4 -193
  21. package/dist/mmd/server/index.d.mts +138 -0
  22. package/dist/mmd/server/index.d.ts +138 -0
  23. package/dist/mmd/server/index.js +245 -0
  24. package/dist/mmd/server/index.js.map +1 -0
  25. package/dist/mmd/server/index.mjs +207 -0
  26. package/dist/mmd/server/index.mjs.map +1 -0
  27. package/dist/testYourself/index.d.mts +145 -0
  28. package/dist/testYourself/index.d.ts +145 -0
  29. package/dist/testYourself/index.js +1004 -0
  30. package/dist/testYourself/index.js.map +1 -0
  31. package/dist/testYourself/index.mjs +993 -0
  32. package/dist/testYourself/index.mjs.map +1 -0
  33. package/dist/types-Bc_p-zAR.d.mts +194 -0
  34. package/dist/types-Bc_p-zAR.d.ts +194 -0
  35. package/dist/types-CK4We_aI.d.mts +270 -0
  36. package/dist/types-CK4We_aI.d.ts +270 -0
  37. package/dist/universalFile/index.d.mts +3 -2
  38. package/dist/universalFile/index.d.ts +3 -2
  39. package/dist/universalFile/index.js +48 -10
  40. package/dist/universalFile/index.js.map +1 -1
  41. package/dist/universalFile/index.mjs +43 -5
  42. package/dist/universalFile/index.mjs.map +1 -1
  43. package/dist/universalFile/server/index.d.mts +3 -2
  44. package/dist/universalFile/server/index.d.ts +3 -2
  45. package/dist/universalFile/server/index.js +239 -7
  46. package/dist/universalFile/server/index.js.map +1 -1
  47. package/dist/universalFile/server/index.mjs +234 -2
  48. package/dist/universalFile/server/index.mjs.map +1 -1
  49. package/package.json +19 -1
@@ -0,0 +1,871 @@
1
+ 'use strict';
2
+
3
+ var chunkW35VTQAW_js = require('../../chunk-W35VTQAW.js');
4
+ require('../../chunk-DGUM43GV.js');
5
+ var React2 = require('react');
6
+ var lucideReact = require('lucide-react');
7
+ var JSZip = require('jszip');
8
+
9
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
10
+
11
+ var React2__default = /*#__PURE__*/_interopDefault(React2);
12
+ var JSZip__default = /*#__PURE__*/_interopDefault(JSZip);
13
+
14
+ // src/mmd/admin/types.ts
15
+ var MMD_RESOURCE_TYPE_CONFIGS = {
16
+ model: {
17
+ moduleId: "mmd-models",
18
+ acceptedTypes: [".zip"],
19
+ maxFileSize: 150,
20
+ description: "MMD\u6A21\u578B\u538B\u7F29\u5305 (ZIP)",
21
+ hint: "\u8BF7\u4E0A\u4F20\u5305\u542B PMX/\u8D34\u56FE\u7B49\u5B8C\u6574\u76EE\u5F55\u7684 ZIP \u538B\u7F29\u5305\uFF0C\u4FDD\u6301\u539F\u59CB\u6587\u4EF6\u7ED3\u6784"
22
+ },
23
+ motion: {
24
+ moduleId: "mmd-motions",
25
+ acceptedTypes: [".vmd"],
26
+ maxFileSize: 20,
27
+ description: "MMD\u52A8\u4F5C\u6587\u4EF6"
28
+ },
29
+ camera: {
30
+ moduleId: "mmd-cameras",
31
+ acceptedTypes: [".vmd"],
32
+ maxFileSize: 10,
33
+ description: "MMD\u76F8\u673A\u52A8\u753B\u6587\u4EF6"
34
+ },
35
+ audio: {
36
+ moduleId: "mmd-audios",
37
+ acceptedTypes: [".mp3", ".wav", ".ogg", ".m4a"],
38
+ maxFileSize: 20,
39
+ description: "\u97F3\u9891\u6587\u4EF6"
40
+ },
41
+ stage: {
42
+ moduleId: "mmd-stages",
43
+ acceptedTypes: [".zip"],
44
+ maxFileSize: 200,
45
+ description: "\u821E\u53F0/\u573A\u666F\u538B\u7F29\u5305 (ZIP)",
46
+ hint: "\u9700\u5C06\u821E\u53F0\u6A21\u578B\u4E0E\u4F9D\u8D56\u8D34\u56FE\u4E00\u8D77\u6253\u5305 ZIP\uFF0C\u5E76\u4FDD\u6301\u76EE\u5F55\u7ED3\u6784"
47
+ },
48
+ thumbnail: {
49
+ moduleId: "mmd-thumbnails",
50
+ acceptedTypes: [".jpg", ".jpeg", ".png", ".webp"],
51
+ maxFileSize: 5,
52
+ description: "\u7F29\u7565\u56FE"
53
+ }
54
+ };
55
+
56
+ // src/mmd/admin/components/MmdResourceSelector.tsx
57
+ var MmdResourceSelector = ({
58
+ resourceType,
59
+ fileService,
60
+ userId,
61
+ value,
62
+ onChange,
63
+ required = false
64
+ }) => {
65
+ const [selectedFileId, setSelectedFileId] = React2.useState(value);
66
+ const [selectedFile, setSelectedFile] = React2.useState(null);
67
+ const [files, setFiles] = React2.useState([]);
68
+ const [loading, setLoading] = React2.useState(false);
69
+ const [uploading, setUploading] = React2.useState(false);
70
+ const [searchTerm, setSearchTerm] = React2.useState("");
71
+ const [showUploader, setShowUploader] = React2.useState(false);
72
+ const [zipValidationError, setZipValidationError] = React2.useState(null);
73
+ const config = MMD_RESOURCE_TYPE_CONFIGS[resourceType];
74
+ if (!config) {
75
+ return /* @__PURE__ */ React2__default.default.createElement("div", { className: "p-4 bg-red-50 text-red-600 rounded-lg" }, "\u672A\u627E\u5230\u8D44\u6E90\u7C7B\u578B\u914D\u7F6E\uFF1A", resourceType);
76
+ }
77
+ const getFileIcon = () => {
78
+ switch (resourceType) {
79
+ case "model":
80
+ case "stage":
81
+ return /* @__PURE__ */ React2__default.default.createElement(lucideReact.Film, { className: "w-5 h-5" });
82
+ case "motion":
83
+ case "camera":
84
+ return /* @__PURE__ */ React2__default.default.createElement(lucideReact.FileText, { className: "w-5 h-5" });
85
+ case "audio":
86
+ return /* @__PURE__ */ React2__default.default.createElement(lucideReact.Music, { className: "w-5 h-5" });
87
+ default:
88
+ return /* @__PURE__ */ React2__default.default.createElement(lucideReact.FileText, { className: "w-5 h-5" });
89
+ }
90
+ };
91
+ const loadFiles = React2.useCallback(async () => {
92
+ if (!fileService || !config) return;
93
+ setLoading(true);
94
+ try {
95
+ const result = await fileService.queryFiles({
96
+ moduleId: config.moduleId,
97
+ pageSize: 50,
98
+ page: 1,
99
+ sortBy: "uploadTime",
100
+ sortOrder: "desc"
101
+ });
102
+ setFiles(result.items || []);
103
+ } catch (error) {
104
+ console.error("\u52A0\u8F7D\u6587\u4EF6\u5217\u8868\u5931\u8D25:", error);
105
+ } finally {
106
+ setLoading(false);
107
+ }
108
+ }, [fileService, config]);
109
+ React2.useEffect(() => {
110
+ loadFiles();
111
+ }, [loadFiles]);
112
+ React2.useEffect(() => {
113
+ if (selectedFileId && fileService) {
114
+ fileService.getFileMetadata(selectedFileId).then((file) => setSelectedFile(file)).catch((error) => console.error("\u52A0\u8F7D\u6587\u4EF6\u4FE1\u606F\u5931\u8D25:", error));
115
+ } else {
116
+ setSelectedFile(null);
117
+ }
118
+ }, [selectedFileId, fileService]);
119
+ const handleFileSelect = (file) => {
120
+ setSelectedFileId(file.id);
121
+ setSelectedFile(file);
122
+ fileService.getFileUrl(file.id).then((url) => onChange(file.id, url)).catch((error) => console.error("\u83B7\u53D6\u6587\u4EF6URL\u5931\u8D25:", error));
123
+ };
124
+ const validateZipContents = async (buffer, type) => {
125
+ const zip = await JSZip__default.default.loadAsync(buffer);
126
+ const entries = Object.keys(zip.files);
127
+ if (!entries.length) {
128
+ throw new Error("\u538B\u7F29\u5305\u4E3A\u7A7A\uFF0C\u8BF7\u68C0\u67E5\u6587\u4EF6\u5185\u5BB9");
129
+ }
130
+ const hasModel = entries.some((name) => /\.[pP][mM][xX]$/.test(name)) || type === "stage" && entries.some((name) => /\.[pP][mM][dD]$/.test(name));
131
+ const hasAssets = entries.some(
132
+ (name) => /\.(png|jpg|jpeg|bmp|tga|dds|spa|sph)$/i.test(name)
133
+ );
134
+ if (!hasModel) {
135
+ throw new Error(type === "stage" ? "\u538B\u7F29\u5305\u4E2D\u672A\u627E\u5230 PMX/PMD \u821E\u53F0\u6A21\u578B\u6587\u4EF6" : "\u538B\u7F29\u5305\u4E2D\u672A\u627E\u5230 PMX \u6A21\u578B\u6587\u4EF6");
136
+ }
137
+ if (!hasAssets) {
138
+ throw new Error("\u538B\u7F29\u5305\u4E2D\u672A\u53D1\u73B0\u8D34\u56FE\u6587\u4EF6\uFF0C\u8BF7\u786E\u8BA4\u662F\u5426\u5305\u542B texture \u76EE\u5F55");
139
+ }
140
+ };
141
+ const handleFileUpload = async (file) => {
142
+ if (!fileService) return;
143
+ setUploading(true);
144
+ setZipValidationError(null);
145
+ try {
146
+ if (resourceType === "model" || resourceType === "stage") {
147
+ const arrayBuffer = await file.arrayBuffer();
148
+ await validateZipContents(arrayBuffer, resourceType);
149
+ }
150
+ const fileMetadata = await fileService.uploadFile({
151
+ file,
152
+ moduleId: config.moduleId,
153
+ businessId: userId,
154
+ permission: "public"
155
+ });
156
+ await loadFiles();
157
+ handleFileSelect(fileMetadata);
158
+ setShowUploader(false);
159
+ } catch (error) {
160
+ console.error("\u6587\u4EF6\u4E0A\u4F20\u5931\u8D25:", error);
161
+ const message = error instanceof Error ? error.message : "\u672A\u77E5\u9519\u8BEF";
162
+ setZipValidationError(message);
163
+ alert(`\u4E0A\u4F20\u5931\u8D25: ${message}`);
164
+ } finally {
165
+ setUploading(false);
166
+ }
167
+ };
168
+ const filteredFiles = files.filter(
169
+ (file) => file.originalName.toLowerCase().includes(searchTerm.toLowerCase())
170
+ );
171
+ return /* @__PURE__ */ React2__default.default.createElement("div", { className: "space-y-4" }, /* @__PURE__ */ React2__default.default.createElement("div", { className: "flex items-center justify-between" }, /* @__PURE__ */ React2__default.default.createElement("label", { className: "flex items-center gap-2 text-sm font-medium text-gray-700 dark:text-gray-300" }, getFileIcon(), config.description, required && /* @__PURE__ */ React2__default.default.createElement("span", { className: "text-red-500" }, "*")), /* @__PURE__ */ React2__default.default.createElement(
172
+ "button",
173
+ {
174
+ onClick: () => setShowUploader(!showUploader),
175
+ className: "px-3 py-1 text-sm bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors flex items-center gap-2"
176
+ },
177
+ /* @__PURE__ */ React2__default.default.createElement(lucideReact.Upload, { className: "w-4 h-4" }),
178
+ "\u4E0A\u4F20\u65B0\u6587\u4EF6"
179
+ )), selectedFile && /* @__PURE__ */ React2__default.default.createElement("div", { className: "p-3 bg-green-50 dark:bg-green-900/20 border border-green-200 dark:border-green-800 rounded-lg flex items-center justify-between" }, /* @__PURE__ */ React2__default.default.createElement("div", { className: "flex items-center gap-3" }, /* @__PURE__ */ React2__default.default.createElement(lucideReact.CheckCircle2, { className: "w-5 h-5 text-green-600 dark:text-green-400" }), /* @__PURE__ */ React2__default.default.createElement("div", null, /* @__PURE__ */ React2__default.default.createElement("div", { className: "text-sm font-medium text-gray-900 dark:text-white" }, selectedFile.originalName), /* @__PURE__ */ React2__default.default.createElement("div", { className: "text-xs text-gray-500 dark:text-gray-400" }, (selectedFile.size / 1024 / 1024).toFixed(2), " MB"))), /* @__PURE__ */ React2__default.default.createElement(
180
+ "button",
181
+ {
182
+ onClick: () => {
183
+ setSelectedFileId(void 0);
184
+ setSelectedFile(null);
185
+ onChange("", "");
186
+ },
187
+ className: "p-1 hover:bg-white/50 dark:hover:bg-black/20 rounded transition-colors"
188
+ },
189
+ /* @__PURE__ */ React2__default.default.createElement(lucideReact.X, { className: "w-4 h-4" })
190
+ )), showUploader && /* @__PURE__ */ React2__default.default.createElement("div", { className: "p-4 bg-gray-50 dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg" }, /* @__PURE__ */ React2__default.default.createElement("div", { className: "text-sm text-gray-600 dark:text-gray-400 mb-3 space-y-1" }, /* @__PURE__ */ React2__default.default.createElement("p", null, "\u652F\u6301\u7684\u6587\u4EF6\u7C7B\u578B: ", config.acceptedTypes.join(", ")), /* @__PURE__ */ React2__default.default.createElement("p", null, "\u6700\u5927\u6587\u4EF6\u5927\u5C0F: ", config.maxFileSize, "MB"), config.hint && /* @__PURE__ */ React2__default.default.createElement("p", { className: "text-xs text-amber-600 dark:text-amber-400" }, config.hint)), /* @__PURE__ */ React2__default.default.createElement(
191
+ "input",
192
+ {
193
+ type: "file",
194
+ accept: config.acceptedTypes.join(","),
195
+ onChange: (e) => {
196
+ const file = e.target.files?.[0];
197
+ if (file) {
198
+ if (file.size > config.maxFileSize * 1024 * 1024) {
199
+ alert(`\u6587\u4EF6\u5927\u5C0F\u8D85\u8FC7\u9650\u5236\uFF08\u6700\u5927 ${config.maxFileSize}MB\uFF09`);
200
+ return;
201
+ }
202
+ handleFileUpload(file);
203
+ }
204
+ },
205
+ disabled: uploading,
206
+ className: "w-full text-sm text-gray-500 file:mr-4 file:py-2 file:px-4 file:rounded-lg file:border-0 file:text-sm file:font-semibold file:bg-blue-50 file:text-blue-700 hover:file:bg-blue-100 disabled:opacity-50"
207
+ }
208
+ ), uploading && /* @__PURE__ */ React2__default.default.createElement("div", { className: "mt-3 flex items-center gap-2 text-sm text-blue-600" }, /* @__PURE__ */ React2__default.default.createElement(lucideReact.Loader2, { className: "w-4 h-4 animate-spin" }), "\u4E0A\u4F20\u4E2D..."), zipValidationError && /* @__PURE__ */ React2__default.default.createElement("div", { className: "mt-3 flex items-center gap-2 text-sm text-amber-600 dark:text-amber-400" }, /* @__PURE__ */ React2__default.default.createElement(lucideReact.AlertTriangle, { className: "w-4 h-4" }), zipValidationError)), /* @__PURE__ */ React2__default.default.createElement("div", { className: "relative" }, /* @__PURE__ */ React2__default.default.createElement(lucideReact.Search, { className: "absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-gray-400" }), /* @__PURE__ */ React2__default.default.createElement(
209
+ "input",
210
+ {
211
+ type: "text",
212
+ placeholder: "\u641C\u7D22\u6587\u4EF6...",
213
+ value: searchTerm,
214
+ onChange: (e) => setSearchTerm(e.target.value),
215
+ className: "w-full pl-10 pr-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent"
216
+ }
217
+ )), /* @__PURE__ */ React2__default.default.createElement("div", { className: "max-h-96 overflow-y-auto border border-gray-200 dark:border-gray-700 rounded-lg divide-y divide-gray-200 dark:divide-gray-700" }, loading ? /* @__PURE__ */ React2__default.default.createElement("div", { className: "p-8 text-center text-gray-500" }, /* @__PURE__ */ React2__default.default.createElement(lucideReact.Loader2, { className: "w-6 h-6 animate-spin mx-auto mb-2" }), "\u52A0\u8F7D\u4E2D...") : filteredFiles.length === 0 ? /* @__PURE__ */ React2__default.default.createElement("div", { className: "p-8 text-center text-gray-500" }, searchTerm ? "\u6CA1\u6709\u627E\u5230\u5339\u914D\u7684\u6587\u4EF6" : "\u6682\u65E0\u6587\u4EF6\uFF0C\u8BF7\u4E0A\u4F20") : filteredFiles.map((file) => /* @__PURE__ */ React2__default.default.createElement(
218
+ "button",
219
+ {
220
+ key: file.id,
221
+ onClick: () => handleFileSelect(file),
222
+ className: `w-full p-3 text-left hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors ${selectedFileId === file.id ? "bg-blue-50 dark:bg-blue-900/20" : ""}`
223
+ },
224
+ /* @__PURE__ */ React2__default.default.createElement("div", { className: "flex items-center justify-between" }, /* @__PURE__ */ React2__default.default.createElement("div", { className: "flex-1 min-w-0" }, /* @__PURE__ */ React2__default.default.createElement("div", { className: "text-sm font-medium text-gray-900 dark:text-white truncate" }, file.originalName), /* @__PURE__ */ React2__default.default.createElement("div", { className: "text-xs text-gray-500 dark:text-gray-400 mt-0.5" }, (file.size / 1024 / 1024).toFixed(2), " MB \u2022", " ", new Date(file.uploadTime).toLocaleDateString())), selectedFileId === file.id && /* @__PURE__ */ React2__default.default.createElement(lucideReact.CheckCircle2, { className: "w-5 h-5 text-blue-600 dark:text-blue-400 flex-shrink-0 ml-2" }))
225
+ ))), required && !selectedFileId && /* @__PURE__ */ React2__default.default.createElement("div", { className: "text-sm text-red-600 dark:text-red-400" }, "\u8BF7\u9009\u62E9\u4E00\u4E2A", config.description));
226
+ };
227
+
228
+ // src/mmd/admin/components/MmdPlaylistEditor.tsx
229
+ var MmdPlaylistEditor = ({
230
+ playlistId,
231
+ fileService,
232
+ userId,
233
+ onSave,
234
+ onCancel
235
+ }) => {
236
+ const [playlistName, setPlaylistName] = React2.useState("");
237
+ const [playlistDescription, setPlaylistDescription] = React2.useState("");
238
+ const [loop, setLoop] = React2.useState(false);
239
+ const [autoPlay, setAutoPlay] = React2.useState(false);
240
+ const [preloadStrategy, setPreloadStrategy] = React2.useState("none");
241
+ const [nodes, setNodes] = React2.useState([]);
242
+ const [expandedNodes, setExpandedNodes] = React2.useState(/* @__PURE__ */ new Set([0]));
243
+ const [saving, setSaving] = React2.useState(false);
244
+ const [showAdvanced, setShowAdvanced] = React2.useState(false);
245
+ const addNode = () => {
246
+ const newNode = {
247
+ name: `\u8282\u70B9 ${nodes.length + 1}`,
248
+ description: "",
249
+ loop: false,
250
+ sortOrder: nodes.length,
251
+ modelFileId: ""
252
+ };
253
+ setNodes([...nodes, newNode]);
254
+ setExpandedNodes(/* @__PURE__ */ new Set([...expandedNodes, nodes.length]));
255
+ };
256
+ const removeNode = (index) => {
257
+ const newNodes = nodes.filter((_, i) => i !== index);
258
+ newNodes.forEach((node, i) => {
259
+ node.sortOrder = i;
260
+ });
261
+ setNodes(newNodes);
262
+ const newExpanded = /* @__PURE__ */ new Set();
263
+ expandedNodes.forEach((i) => {
264
+ if (i < index) newExpanded.add(i);
265
+ else if (i > index) newExpanded.add(i - 1);
266
+ });
267
+ setExpandedNodes(newExpanded);
268
+ };
269
+ const moveNode = (index, direction) => {
270
+ const newNodes = [...nodes];
271
+ const targetIndex = direction === "up" ? index - 1 : index + 1;
272
+ if (targetIndex < 0 || targetIndex >= newNodes.length) return;
273
+ if (!newNodes[index] || !newNodes[targetIndex]) return;
274
+ [newNodes[index], newNodes[targetIndex]] = [newNodes[targetIndex], newNodes[index]];
275
+ newNodes.forEach((node, i) => {
276
+ node.sortOrder = i;
277
+ });
278
+ setNodes(newNodes);
279
+ const newExpanded = /* @__PURE__ */ new Set();
280
+ expandedNodes.forEach((i) => {
281
+ if (i === index) newExpanded.add(targetIndex);
282
+ else if (i === targetIndex) newExpanded.add(index);
283
+ else newExpanded.add(i);
284
+ });
285
+ setExpandedNodes(newExpanded);
286
+ };
287
+ const toggleNode = (index) => {
288
+ const newExpanded = new Set(expandedNodes);
289
+ if (newExpanded.has(index)) {
290
+ newExpanded.delete(index);
291
+ } else {
292
+ newExpanded.add(index);
293
+ }
294
+ setExpandedNodes(newExpanded);
295
+ };
296
+ const updateNode = (index, updates) => {
297
+ const newNodes = [...nodes];
298
+ if (!newNodes[index]) return;
299
+ newNodes[index] = { ...newNodes[index], ...updates };
300
+ setNodes(newNodes);
301
+ };
302
+ const validateForm = () => {
303
+ if (!playlistName.trim()) {
304
+ alert("\u8BF7\u8F93\u5165\u64AD\u653E\u5217\u8868\u540D\u79F0");
305
+ return false;
306
+ }
307
+ if (nodes.length === 0) {
308
+ alert("\u8BF7\u81F3\u5C11\u6DFB\u52A0\u4E00\u4E2A\u64AD\u653E\u8282\u70B9");
309
+ return false;
310
+ }
311
+ for (let i = 0; i < nodes.length; i++) {
312
+ const node = nodes[i];
313
+ if (!node || !node.name?.trim()) {
314
+ alert(`\u8282\u70B9 ${i + 1}: \u8BF7\u8F93\u5165\u8282\u70B9\u540D\u79F0`);
315
+ return false;
316
+ }
317
+ if (!node.modelFileId) {
318
+ alert(`\u8282\u70B9 ${i + 1}: \u8BF7\u9009\u62E9\u6A21\u578B\u6587\u4EF6`);
319
+ return false;
320
+ }
321
+ }
322
+ return true;
323
+ };
324
+ const handleSave = async () => {
325
+ if (!validateForm()) return;
326
+ setSaving(true);
327
+ try {
328
+ console.log("\u4FDD\u5B58\u64AD\u653E\u5217\u8868:", {
329
+ name: playlistName,
330
+ description: playlistDescription,
331
+ loop,
332
+ autoPlay,
333
+ preloadStrategy,
334
+ nodes
335
+ });
336
+ alert("\u4FDD\u5B58\u6210\u529F\uFF01");
337
+ onSave?.(null);
338
+ } catch (error) {
339
+ console.error("\u4FDD\u5B58\u5931\u8D25:", error);
340
+ alert(`\u4FDD\u5B58\u5931\u8D25: ${error instanceof Error ? error.message : "\u672A\u77E5\u9519\u8BEF"}`);
341
+ } finally {
342
+ setSaving(false);
343
+ }
344
+ };
345
+ return /* @__PURE__ */ React2__default.default.createElement("div", { className: "max-w-6xl mx-auto p-6 bg-white dark:bg-gray-900 rounded-lg shadow-lg" }, /* @__PURE__ */ React2__default.default.createElement("div", { className: "flex items-center justify-between mb-6" }, /* @__PURE__ */ React2__default.default.createElement("h2", { className: "text-2xl font-bold text-gray-900 dark:text-white" }, playlistId ? "\u7F16\u8F91\u64AD\u653E\u5217\u8868" : "\u521B\u5EFA\u64AD\u653E\u5217\u8868"), /* @__PURE__ */ React2__default.default.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ React2__default.default.createElement(
346
+ "button",
347
+ {
348
+ onClick: handleSave,
349
+ disabled: saving,
350
+ className: "px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors disabled:opacity-50 flex items-center gap-2"
351
+ },
352
+ /* @__PURE__ */ React2__default.default.createElement(lucideReact.Save, { className: "w-4 h-4" }),
353
+ saving ? "\u4FDD\u5B58\u4E2D..." : "\u4FDD\u5B58"
354
+ ), /* @__PURE__ */ React2__default.default.createElement(
355
+ "button",
356
+ {
357
+ onClick: onCancel,
358
+ className: "px-4 py-2 bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-300 rounded-lg hover:bg-gray-300 dark:hover:bg-gray-600 transition-colors flex items-center gap-2"
359
+ },
360
+ /* @__PURE__ */ React2__default.default.createElement(lucideReact.X, { className: "w-4 h-4" }),
361
+ "\u53D6\u6D88"
362
+ ))), /* @__PURE__ */ React2__default.default.createElement("div", { className: "space-y-4 mb-8 p-6 bg-gray-50 dark:bg-gray-800 rounded-lg" }, /* @__PURE__ */ React2__default.default.createElement("h3", { className: "text-lg font-semibold text-gray-900 dark:text-white mb-4" }, "\u57FA\u672C\u4FE1\u606F"), /* @__PURE__ */ React2__default.default.createElement("div", null, /* @__PURE__ */ React2__default.default.createElement("label", { className: "block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2" }, "\u64AD\u653E\u5217\u8868\u540D\u79F0 ", /* @__PURE__ */ React2__default.default.createElement("span", { className: "text-red-500" }, "*")), /* @__PURE__ */ React2__default.default.createElement(
363
+ "input",
364
+ {
365
+ type: "text",
366
+ value: playlistName,
367
+ onChange: (e) => setPlaylistName(e.target.value),
368
+ placeholder: "\u8F93\u5165\u64AD\u653E\u5217\u8868\u540D\u79F0...",
369
+ className: "w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-900 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent"
370
+ }
371
+ )), /* @__PURE__ */ React2__default.default.createElement("div", null, /* @__PURE__ */ React2__default.default.createElement("label", { className: "block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2" }, "\u63CF\u8FF0"), /* @__PURE__ */ React2__default.default.createElement(
372
+ "textarea",
373
+ {
374
+ value: playlistDescription,
375
+ onChange: (e) => setPlaylistDescription(e.target.value),
376
+ placeholder: "\u8F93\u5165\u63CF\u8FF0\u4FE1\u606F...",
377
+ rows: 3,
378
+ className: "w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-900 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent"
379
+ }
380
+ )), /* @__PURE__ */ React2__default.default.createElement("div", { className: "flex items-center gap-6" }, /* @__PURE__ */ React2__default.default.createElement("label", { className: "flex items-center gap-2 cursor-pointer" }, /* @__PURE__ */ React2__default.default.createElement(
381
+ "input",
382
+ {
383
+ type: "checkbox",
384
+ checked: loop,
385
+ onChange: (e) => setLoop(e.target.checked),
386
+ className: "w-4 h-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500"
387
+ }
388
+ ), /* @__PURE__ */ React2__default.default.createElement("span", { className: "text-sm text-gray-700 dark:text-gray-300" }, "\u5217\u8868\u5FAA\u73AF")), /* @__PURE__ */ React2__default.default.createElement("label", { className: "flex items-center gap-2 cursor-pointer" }, /* @__PURE__ */ React2__default.default.createElement(
389
+ "input",
390
+ {
391
+ type: "checkbox",
392
+ checked: autoPlay,
393
+ onChange: (e) => setAutoPlay(e.target.checked),
394
+ className: "w-4 h-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500"
395
+ }
396
+ ), /* @__PURE__ */ React2__default.default.createElement("span", { className: "text-sm text-gray-700 dark:text-gray-300" }, "\u81EA\u52A8\u64AD\u653E"))), /* @__PURE__ */ React2__default.default.createElement(
397
+ "button",
398
+ {
399
+ onClick: () => setShowAdvanced(!showAdvanced),
400
+ className: "flex items-center gap-2 text-sm text-blue-600 dark:text-blue-400 hover:text-blue-700 dark:hover:text-blue-300"
401
+ },
402
+ /* @__PURE__ */ React2__default.default.createElement(lucideReact.Settings, { className: "w-4 h-4" }),
403
+ "\u9AD8\u7EA7\u9009\u9879",
404
+ showAdvanced ? /* @__PURE__ */ React2__default.default.createElement(lucideReact.ChevronUp, { className: "w-4 h-4" }) : /* @__PURE__ */ React2__default.default.createElement(lucideReact.ChevronDown, { className: "w-4 h-4" })
405
+ ), showAdvanced && /* @__PURE__ */ React2__default.default.createElement("div", { className: "space-y-4 pt-4 border-t border-gray-200 dark:border-gray-700" }, /* @__PURE__ */ React2__default.default.createElement("div", null, /* @__PURE__ */ React2__default.default.createElement("label", { className: "block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2" }, "\u9884\u52A0\u8F7D\u7B56\u7565"), /* @__PURE__ */ React2__default.default.createElement(
406
+ "select",
407
+ {
408
+ value: preloadStrategy,
409
+ onChange: (e) => setPreloadStrategy(e.target.value),
410
+ className: "w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-900 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent"
411
+ },
412
+ /* @__PURE__ */ React2__default.default.createElement("option", { value: "none" }, "\u4E0D\u9884\u52A0\u8F7D"),
413
+ /* @__PURE__ */ React2__default.default.createElement("option", { value: "next" }, "\u9884\u52A0\u8F7D\u4E0B\u4E00\u4E2A"),
414
+ /* @__PURE__ */ React2__default.default.createElement("option", { value: "all" }, "\u9884\u52A0\u8F7D\u5168\u90E8")
415
+ )))), /* @__PURE__ */ React2__default.default.createElement("div", { className: "space-y-4" }, /* @__PURE__ */ React2__default.default.createElement("div", { className: "flex items-center justify-between" }, /* @__PURE__ */ React2__default.default.createElement("h3", { className: "text-lg font-semibold text-gray-900 dark:text-white" }, "\u64AD\u653E\u8282\u70B9 (", nodes.length, ")"), /* @__PURE__ */ React2__default.default.createElement(
416
+ "button",
417
+ {
418
+ onClick: addNode,
419
+ className: "px-4 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700 transition-colors flex items-center gap-2"
420
+ },
421
+ /* @__PURE__ */ React2__default.default.createElement(lucideReact.Plus, { className: "w-4 h-4" }),
422
+ "\u6DFB\u52A0\u8282\u70B9"
423
+ )), nodes.length === 0 ? /* @__PURE__ */ React2__default.default.createElement("div", { className: "p-8 text-center text-gray-500 border-2 border-dashed border-gray-300 dark:border-gray-700 rounded-lg" }, '\u6682\u65E0\u8282\u70B9\uFF0C\u8BF7\u70B9\u51FB"\u6DFB\u52A0\u8282\u70B9"\u6309\u94AE\u5F00\u59CB') : nodes.map((node, index) => /* @__PURE__ */ React2__default.default.createElement(
424
+ "div",
425
+ {
426
+ key: index,
427
+ className: "border border-gray-200 dark:border-gray-700 rounded-lg overflow-hidden"
428
+ },
429
+ /* @__PURE__ */ React2__default.default.createElement("div", { className: "flex items-center gap-3 p-4 bg-gray-50 dark:bg-gray-800" }, /* @__PURE__ */ React2__default.default.createElement(
430
+ "button",
431
+ {
432
+ className: "cursor-move text-gray-400 hover:text-gray-600 dark:hover:text-gray-300",
433
+ title: "\u62D6\u62FD\u6392\u5E8F"
434
+ },
435
+ /* @__PURE__ */ React2__default.default.createElement(lucideReact.GripVertical, { className: "w-5 h-5" })
436
+ ), /* @__PURE__ */ React2__default.default.createElement(
437
+ "button",
438
+ {
439
+ onClick: () => toggleNode(index),
440
+ className: "flex-1 flex items-center justify-between text-left"
441
+ },
442
+ /* @__PURE__ */ React2__default.default.createElement("span", { className: "font-medium text-gray-900 dark:text-white" }, index + 1, ". ", node.name || "\u672A\u547D\u540D\u8282\u70B9"),
443
+ expandedNodes.has(index) ? /* @__PURE__ */ React2__default.default.createElement(lucideReact.ChevronUp, { className: "w-5 h-5 text-gray-400" }) : /* @__PURE__ */ React2__default.default.createElement(lucideReact.ChevronDown, { className: "w-5 h-5 text-gray-400" })
444
+ ), /* @__PURE__ */ React2__default.default.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ React2__default.default.createElement(
445
+ "button",
446
+ {
447
+ onClick: () => moveNode(index, "up"),
448
+ disabled: index === 0,
449
+ className: "p-1 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 disabled:opacity-30",
450
+ title: "\u4E0A\u79FB"
451
+ },
452
+ /* @__PURE__ */ React2__default.default.createElement(lucideReact.ChevronUp, { className: "w-5 h-5" })
453
+ ), /* @__PURE__ */ React2__default.default.createElement(
454
+ "button",
455
+ {
456
+ onClick: () => moveNode(index, "down"),
457
+ disabled: index === nodes.length - 1,
458
+ className: "p-1 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 disabled:opacity-30",
459
+ title: "\u4E0B\u79FB"
460
+ },
461
+ /* @__PURE__ */ React2__default.default.createElement(lucideReact.ChevronDown, { className: "w-5 h-5" })
462
+ ), /* @__PURE__ */ React2__default.default.createElement(
463
+ "button",
464
+ {
465
+ onClick: () => removeNode(index),
466
+ className: "p-1 text-red-400 hover:text-red-600 dark:hover:text-red-300",
467
+ title: "\u5220\u9664"
468
+ },
469
+ /* @__PURE__ */ React2__default.default.createElement(lucideReact.Trash2, { className: "w-5 h-5" })
470
+ ))),
471
+ expandedNodes.has(index) && /* @__PURE__ */ React2__default.default.createElement("div", { className: "p-6 space-y-6 bg-white dark:bg-gray-900" }, /* @__PURE__ */ React2__default.default.createElement("div", { className: "grid grid-cols-2 gap-4" }, /* @__PURE__ */ React2__default.default.createElement("div", null, /* @__PURE__ */ React2__default.default.createElement("label", { className: "block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2" }, "\u8282\u70B9\u540D\u79F0 ", /* @__PURE__ */ React2__default.default.createElement("span", { className: "text-red-500" }, "*")), /* @__PURE__ */ React2__default.default.createElement(
472
+ "input",
473
+ {
474
+ type: "text",
475
+ value: node.name,
476
+ onChange: (e) => updateNode(index, { name: e.target.value }),
477
+ className: "w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent"
478
+ }
479
+ )), /* @__PURE__ */ React2__default.default.createElement("div", null, /* @__PURE__ */ React2__default.default.createElement("label", { className: "block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2" }, "\u65F6\u957F\uFF08\u79D2\uFF09"), /* @__PURE__ */ React2__default.default.createElement(
480
+ "input",
481
+ {
482
+ type: "number",
483
+ value: node.duration || "",
484
+ onChange: (e) => updateNode(index, { duration: parseInt(e.target.value) || void 0 }),
485
+ className: "w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent"
486
+ }
487
+ ))), /* @__PURE__ */ React2__default.default.createElement("div", null, /* @__PURE__ */ React2__default.default.createElement("label", { className: "block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2" }, "\u63CF\u8FF0"), /* @__PURE__ */ React2__default.default.createElement(
488
+ "textarea",
489
+ {
490
+ value: node.description || "",
491
+ onChange: (e) => updateNode(index, { description: e.target.value }),
492
+ rows: 2,
493
+ className: "w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent"
494
+ }
495
+ )), /* @__PURE__ */ React2__default.default.createElement("label", { className: "flex items-center gap-2 cursor-pointer" }, /* @__PURE__ */ React2__default.default.createElement(
496
+ "input",
497
+ {
498
+ type: "checkbox",
499
+ checked: node.loop,
500
+ onChange: (e) => updateNode(index, { loop: e.target.checked }),
501
+ className: "w-4 h-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500"
502
+ }
503
+ ), /* @__PURE__ */ React2__default.default.createElement("span", { className: "text-sm text-gray-700 dark:text-gray-300" }, "\u5355\u66F2\u5FAA\u73AF")), /* @__PURE__ */ React2__default.default.createElement("div", { className: "grid grid-cols-1 gap-6 pt-4 border-t border-gray-200 dark:border-gray-700" }, /* @__PURE__ */ React2__default.default.createElement(
504
+ MmdResourceSelector,
505
+ {
506
+ resourceType: "model",
507
+ fileService,
508
+ userId,
509
+ value: node.modelFileId,
510
+ onChange: (fileId) => updateNode(index, { modelFileId: fileId }),
511
+ required: true
512
+ }
513
+ ), /* @__PURE__ */ React2__default.default.createElement(
514
+ MmdResourceSelector,
515
+ {
516
+ resourceType: "motion",
517
+ fileService,
518
+ userId,
519
+ value: node.motionFileId,
520
+ onChange: (fileId) => updateNode(index, { motionFileId: fileId })
521
+ }
522
+ ), /* @__PURE__ */ React2__default.default.createElement(
523
+ MmdResourceSelector,
524
+ {
525
+ resourceType: "audio",
526
+ fileService,
527
+ userId,
528
+ value: node.audioFileId,
529
+ onChange: (fileId) => updateNode(index, { audioFileId: fileId })
530
+ }
531
+ )))
532
+ ))));
533
+ };
534
+
535
+ // src/mmd/admin/components/MmdAdminPanel.tsx
536
+ var MmdAdminPanel = ({
537
+ fileService,
538
+ userId,
539
+ apiBaseUrl = "/api/mmd",
540
+ showAdvancedOptions = true,
541
+ className = ""
542
+ }) => {
543
+ const [activeTab, setActiveTab] = React2.useState("playlists");
544
+ const [showEditor, setShowEditor] = React2.useState(false);
545
+ const [editingPlaylistId, setEditingPlaylistId] = React2.useState();
546
+ const tabs = [
547
+ { id: "playlists", label: "\u64AD\u653E\u5217\u8868", icon: lucideReact.List },
548
+ { id: "presets", label: "\u9884\u8BBE\u9879", icon: lucideReact.Database },
549
+ { id: "resources", label: "\u8D44\u6E90\u7BA1\u7406", icon: lucideReact.Settings },
550
+ ...showAdvancedOptions ? [{ id: "stats", label: "\u7EDF\u8BA1", icon: lucideReact.BarChart3 }] : []
551
+ ];
552
+ const handleCreatePlaylist = () => {
553
+ setEditingPlaylistId(void 0);
554
+ setShowEditor(true);
555
+ };
556
+ const handleCloseEditor = () => {
557
+ setShowEditor(false);
558
+ setEditingPlaylistId(void 0);
559
+ };
560
+ const handleSaveSuccess = (playlist) => {
561
+ console.log("\u4FDD\u5B58\u6210\u529F:", playlist);
562
+ handleCloseEditor();
563
+ };
564
+ if (showEditor) {
565
+ return /* @__PURE__ */ React2__default.default.createElement(
566
+ MmdPlaylistEditor,
567
+ {
568
+ playlistId: editingPlaylistId,
569
+ fileService,
570
+ userId,
571
+ onSave: handleSaveSuccess,
572
+ onCancel: handleCloseEditor
573
+ }
574
+ );
575
+ }
576
+ return /* @__PURE__ */ React2__default.default.createElement("div", { className: `min-h-screen bg-gray-50 dark:bg-gray-900 ${className}` }, /* @__PURE__ */ React2__default.default.createElement("div", { className: "bg-white dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700" }, /* @__PURE__ */ React2__default.default.createElement("div", { className: "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8" }, /* @__PURE__ */ React2__default.default.createElement("div", { className: "flex items-center justify-between h-16" }, /* @__PURE__ */ React2__default.default.createElement("h1", { className: "text-2xl font-bold text-gray-900 dark:text-white" }, "MMD \u540E\u53F0\u7BA1\u7406"), activeTab === "playlists" && /* @__PURE__ */ React2__default.default.createElement(
577
+ "button",
578
+ {
579
+ onClick: handleCreatePlaylist,
580
+ className: "px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors flex items-center gap-2"
581
+ },
582
+ /* @__PURE__ */ React2__default.default.createElement(lucideReact.Plus, { className: "w-4 h-4" }),
583
+ "\u521B\u5EFA\u64AD\u653E\u5217\u8868"
584
+ )), /* @__PURE__ */ React2__default.default.createElement("div", { className: "flex space-x-8" }, tabs.map((tab) => {
585
+ const Icon = tab.icon;
586
+ return /* @__PURE__ */ React2__default.default.createElement(
587
+ "button",
588
+ {
589
+ key: tab.id,
590
+ onClick: () => setActiveTab(tab.id),
591
+ className: `flex items-center gap-2 px-1 py-4 border-b-2 font-medium text-sm transition-colors ${activeTab === tab.id ? "border-blue-600 text-blue-600 dark:text-blue-400" : "border-transparent text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300"}`
592
+ },
593
+ /* @__PURE__ */ React2__default.default.createElement(Icon, { className: "w-4 h-4" }),
594
+ tab.label
595
+ );
596
+ })))), /* @__PURE__ */ React2__default.default.createElement("div", { className: "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8" }, activeTab === "playlists" && /* @__PURE__ */ React2__default.default.createElement("div", { className: "space-y-4" }, /* @__PURE__ */ React2__default.default.createElement("div", { className: "text-center py-12 text-gray-500" }, /* @__PURE__ */ React2__default.default.createElement(lucideReact.List, { className: "w-12 h-12 mx-auto mb-4 opacity-50" }), /* @__PURE__ */ React2__default.default.createElement("p", null, "\u64AD\u653E\u5217\u8868\u7BA1\u7406\u529F\u80FD\u5F00\u53D1\u4E2D..."), /* @__PURE__ */ React2__default.default.createElement("p", { className: "text-sm mt-2" }, '\u70B9\u51FB\u53F3\u4E0A\u89D2"\u521B\u5EFA\u64AD\u653E\u5217\u8868"\u6309\u94AE\u5F00\u59CB'))), activeTab === "presets" && /* @__PURE__ */ React2__default.default.createElement("div", { className: "text-center py-12 text-gray-500" }, /* @__PURE__ */ React2__default.default.createElement(lucideReact.Database, { className: "w-12 h-12 mx-auto mb-4 opacity-50" }), /* @__PURE__ */ React2__default.default.createElement("p", null, "\u9884\u8BBE\u9879\u7BA1\u7406\u529F\u80FD\u5F00\u53D1\u4E2D...")), activeTab === "resources" && /* @__PURE__ */ React2__default.default.createElement("div", { className: "text-center py-12 text-gray-500" }, /* @__PURE__ */ React2__default.default.createElement(lucideReact.Settings, { className: "w-12 h-12 mx-auto mb-4 opacity-50" }), /* @__PURE__ */ React2__default.default.createElement("p", null, "\u8D44\u6E90\u7BA1\u7406\u529F\u80FD\u5F00\u53D1\u4E2D...")), activeTab === "stats" && /* @__PURE__ */ React2__default.default.createElement("div", { className: "text-center py-12 text-gray-500" }, /* @__PURE__ */ React2__default.default.createElement(lucideReact.BarChart3, { className: "w-12 h-12 mx-auto mb-4 opacity-50" }), /* @__PURE__ */ React2__default.default.createElement("p", null, "\u7EDF\u8BA1\u529F\u80FD\u5F00\u53D1\u4E2D..."))));
597
+ };
598
+
599
+ // src/mmd/admin/utils.ts
600
+ function extractFileIdsFromPlaylist(playlist, nodes) {
601
+ const fileIds = /* @__PURE__ */ new Set();
602
+ if (playlist.thumbnailFileId) {
603
+ fileIds.add(playlist.thumbnailFileId);
604
+ }
605
+ for (const node of nodes) {
606
+ if (node.thumbnailFileId) fileIds.add(node.thumbnailFileId);
607
+ if (node.modelFileId) fileIds.add(node.modelFileId);
608
+ if (node.motionFileId) fileIds.add(node.motionFileId);
609
+ if (node.cameraFileId) fileIds.add(node.cameraFileId);
610
+ if (node.audioFileId) fileIds.add(node.audioFileId);
611
+ if (node.stageModelFileId) fileIds.add(node.stageModelFileId);
612
+ if (node.additionalMotionFileIds) {
613
+ node.additionalMotionFileIds.forEach((id) => fileIds.add(id));
614
+ }
615
+ }
616
+ return Array.from(fileIds);
617
+ }
618
+ function extractFileIdsFromResourceOptions(options) {
619
+ const fileIds = /* @__PURE__ */ new Set();
620
+ for (const option of options) {
621
+ fileIds.add(option.fileId);
622
+ if (option.thumbnailFileId) {
623
+ fileIds.add(option.thumbnailFileId);
624
+ }
625
+ }
626
+ return Array.from(fileIds);
627
+ }
628
+ function extractFileIdsFromPresetItem(item) {
629
+ const fileIds = /* @__PURE__ */ new Set();
630
+ if (item.thumbnailFileId) fileIds.add(item.thumbnailFileId);
631
+ if (item.modelFileId) fileIds.add(item.modelFileId);
632
+ if (item.motionFileId) fileIds.add(item.motionFileId);
633
+ if (item.cameraFileId) fileIds.add(item.cameraFileId);
634
+ if (item.audioFileId) fileIds.add(item.audioFileId);
635
+ if (item.stageModelFileId) fileIds.add(item.stageModelFileId);
636
+ if (item.additionalMotionFileIds) {
637
+ item.additionalMotionFileIds.forEach((id) => fileIds.add(id));
638
+ }
639
+ return Array.from(fileIds);
640
+ }
641
+ function convertPlaylistNodeToFrontend(node, fileUrls) {
642
+ return {
643
+ id: node.id,
644
+ playlistId: node.playlistId,
645
+ name: node.name,
646
+ description: node.description,
647
+ loop: node.loop,
648
+ duration: node.duration,
649
+ sortOrder: node.sortOrder,
650
+ config: node.config,
651
+ createdAt: node.createdAt,
652
+ updatedAt: node.updatedAt,
653
+ // URL 映射
654
+ thumbnailUrl: node.thumbnailFileId ? fileUrls[node.thumbnailFileId] : void 0,
655
+ modelUrl: fileUrls[node.modelFileId] || "",
656
+ motionUrl: node.motionFileId ? fileUrls[node.motionFileId] : void 0,
657
+ cameraUrl: node.cameraFileId ? fileUrls[node.cameraFileId] : void 0,
658
+ audioUrl: node.audioFileId ? fileUrls[node.audioFileId] : void 0,
659
+ stageModelUrl: node.stageModelFileId ? fileUrls[node.stageModelFileId] : void 0,
660
+ additionalMotionUrls: node.additionalMotionFileIds?.map((id) => fileUrls[id]).filter((url) => Boolean(url))
661
+ };
662
+ }
663
+ function convertPlaylistToFrontend(playlist, nodes, fileUrls) {
664
+ return {
665
+ id: playlist.id,
666
+ name: playlist.name,
667
+ description: playlist.description,
668
+ loop: playlist.loop,
669
+ preloadStrategy: playlist.preloadStrategy,
670
+ autoPlay: playlist.autoPlay,
671
+ status: playlist.status,
672
+ sortOrder: playlist.sortOrder,
673
+ config: playlist.config,
674
+ createdBy: playlist.createdBy,
675
+ createdAt: playlist.createdAt,
676
+ updatedAt: playlist.updatedAt,
677
+ deletedAt: playlist.deletedAt,
678
+ // URL 映射
679
+ thumbnailUrl: playlist.thumbnailFileId ? fileUrls[playlist.thumbnailFileId] : void 0,
680
+ // 转换节点
681
+ nodes: nodes.sort((a, b) => a.sortOrder - b.sortOrder).map((node) => convertPlaylistNodeToFrontend(node, fileUrls))
682
+ };
683
+ }
684
+ function convertResourceOptionToFrontend(option, fileUrls) {
685
+ return {
686
+ id: option.id,
687
+ name: option.name,
688
+ description: option.description,
689
+ resourceType: option.resourceType,
690
+ tags: option.tags,
691
+ sortOrder: option.sortOrder,
692
+ isActive: option.isActive,
693
+ createdBy: option.createdBy,
694
+ createdAt: option.createdAt,
695
+ updatedAt: option.updatedAt,
696
+ // URL 映射
697
+ fileUrl: fileUrls[option.fileId] || "",
698
+ thumbnailUrl: option.thumbnailFileId ? fileUrls[option.thumbnailFileId] : void 0
699
+ };
700
+ }
701
+ function convertPresetItemToFrontend(item, fileUrls) {
702
+ return {
703
+ id: item.id,
704
+ name: item.name,
705
+ description: item.description,
706
+ sortOrder: item.sortOrder,
707
+ isActive: item.isActive,
708
+ tags: item.tags,
709
+ createdBy: item.createdBy,
710
+ createdAt: item.createdAt,
711
+ updatedAt: item.updatedAt,
712
+ // URL 映射
713
+ thumbnailUrl: item.thumbnailFileId ? fileUrls[item.thumbnailFileId] : void 0,
714
+ modelUrl: fileUrls[item.modelFileId] || "",
715
+ motionUrl: item.motionFileId ? fileUrls[item.motionFileId] : void 0,
716
+ cameraUrl: item.cameraFileId ? fileUrls[item.cameraFileId] : void 0,
717
+ audioUrl: item.audioFileId ? fileUrls[item.audioFileId] : void 0,
718
+ stageModelUrl: item.stageModelFileId ? fileUrls[item.stageModelFileId] : void 0,
719
+ additionalMotionUrls: item.additionalMotionFileIds?.map((id) => fileUrls[id]).filter((url) => Boolean(url))
720
+ };
721
+ }
722
+ function convertNodeToMmdFormat(node) {
723
+ return {
724
+ id: node.id,
725
+ name: node.name,
726
+ loop: node.loop,
727
+ duration: node.duration,
728
+ thumbnail: node.thumbnailUrl,
729
+ resources: {
730
+ modelPath: node.modelUrl,
731
+ motionPath: node.motionUrl,
732
+ cameraPath: node.cameraUrl,
733
+ audioPath: node.audioUrl,
734
+ stageModelPath: node.stageModelUrl,
735
+ additionalMotions: node.additionalMotionUrls
736
+ }
737
+ };
738
+ }
739
+ function convertPlaylistToMmdConfig(playlist) {
740
+ return {
741
+ id: playlist.id,
742
+ name: playlist.name,
743
+ nodes: playlist.nodes.map(convertNodeToMmdFormat),
744
+ loop: playlist.loop,
745
+ preload: playlist.preloadStrategy,
746
+ autoPlay: playlist.autoPlay
747
+ };
748
+ }
749
+ function convertPresetItemToMmdResource(item) {
750
+ return {
751
+ id: item.id,
752
+ name: item.name,
753
+ thumbnail: item.thumbnailUrl,
754
+ description: item.description,
755
+ resources: {
756
+ modelPath: item.modelUrl,
757
+ motionPath: item.motionUrl,
758
+ cameraPath: item.cameraUrl,
759
+ audioPath: item.audioUrl,
760
+ stageModelPath: item.stageModelUrl,
761
+ additionalMotions: item.additionalMotionUrls
762
+ }
763
+ };
764
+ }
765
+ function convertResourceOptionsToMmdFormat(options) {
766
+ const grouped = {
767
+ models: [],
768
+ motions: [],
769
+ cameras: [],
770
+ audios: [],
771
+ stages: []
772
+ };
773
+ for (const option of options) {
774
+ const resourceOption = {
775
+ id: option.id,
776
+ name: option.name,
777
+ path: option.fileUrl,
778
+ thumbnail: option.thumbnailUrl
779
+ };
780
+ switch (option.resourceType) {
781
+ case "model":
782
+ grouped.models.push(resourceOption);
783
+ break;
784
+ case "motion":
785
+ grouped.motions.push(resourceOption);
786
+ break;
787
+ case "camera":
788
+ grouped.cameras.push(resourceOption);
789
+ break;
790
+ case "audio":
791
+ grouped.audios.push(resourceOption);
792
+ break;
793
+ case "stage":
794
+ grouped.stages.push(resourceOption);
795
+ break;
796
+ }
797
+ }
798
+ return grouped;
799
+ }
800
+ function validateFileUrls(requiredFileIds, fileUrls) {
801
+ const missingIds = requiredFileIds.filter((id) => !fileUrls[id]);
802
+ return {
803
+ valid: missingIds.length === 0,
804
+ missingIds
805
+ };
806
+ }
807
+ function generateMockFileUrls(fileIds) {
808
+ const urls = {};
809
+ for (const id of fileIds) {
810
+ urls[id] = `/mock/files/${id}`;
811
+ }
812
+ return urls;
813
+ }
814
+ function mergeFileUrlMaps(...maps) {
815
+ return Object.assign({}, ...maps);
816
+ }
817
+ function extractPathsFromMmdResources(resources) {
818
+ const paths = [resources.modelPath];
819
+ if (resources.motionPath) paths.push(resources.motionPath);
820
+ if (resources.cameraPath) paths.push(resources.cameraPath);
821
+ if (resources.audioPath) paths.push(resources.audioPath);
822
+ if (resources.stageModelPath) paths.push(resources.stageModelPath);
823
+ if (resources.additionalMotions) paths.push(...resources.additionalMotions);
824
+ return paths;
825
+ }
826
+
827
+ Object.defineProperty(exports, "mmdPlaylistNodes", {
828
+ enumerable: true,
829
+ get: function () { return chunkW35VTQAW_js.mmdPlaylistNodes; }
830
+ });
831
+ Object.defineProperty(exports, "mmdPlaylistNodesRelations", {
832
+ enumerable: true,
833
+ get: function () { return chunkW35VTQAW_js.mmdPlaylistNodesRelations; }
834
+ });
835
+ Object.defineProperty(exports, "mmdPlaylists", {
836
+ enumerable: true,
837
+ get: function () { return chunkW35VTQAW_js.mmdPlaylists; }
838
+ });
839
+ Object.defineProperty(exports, "mmdPlaylistsRelations", {
840
+ enumerable: true,
841
+ get: function () { return chunkW35VTQAW_js.mmdPlaylistsRelations; }
842
+ });
843
+ Object.defineProperty(exports, "mmdPresetItems", {
844
+ enumerable: true,
845
+ get: function () { return chunkW35VTQAW_js.mmdPresetItems; }
846
+ });
847
+ Object.defineProperty(exports, "mmdResourceOptions", {
848
+ enumerable: true,
849
+ get: function () { return chunkW35VTQAW_js.mmdResourceOptions; }
850
+ });
851
+ exports.MMD_RESOURCE_TYPE_CONFIGS = MMD_RESOURCE_TYPE_CONFIGS;
852
+ exports.MmdAdminPanel = MmdAdminPanel;
853
+ exports.MmdPlaylistEditor = MmdPlaylistEditor;
854
+ exports.MmdResourceSelector = MmdResourceSelector;
855
+ exports.convertNodeToMmdFormat = convertNodeToMmdFormat;
856
+ exports.convertPlaylistNodeToFrontend = convertPlaylistNodeToFrontend;
857
+ exports.convertPlaylistToFrontend = convertPlaylistToFrontend;
858
+ exports.convertPlaylistToMmdConfig = convertPlaylistToMmdConfig;
859
+ exports.convertPresetItemToFrontend = convertPresetItemToFrontend;
860
+ exports.convertPresetItemToMmdResource = convertPresetItemToMmdResource;
861
+ exports.convertResourceOptionToFrontend = convertResourceOptionToFrontend;
862
+ exports.convertResourceOptionsToMmdFormat = convertResourceOptionsToMmdFormat;
863
+ exports.extractFileIdsFromPlaylist = extractFileIdsFromPlaylist;
864
+ exports.extractFileIdsFromPresetItem = extractFileIdsFromPresetItem;
865
+ exports.extractFileIdsFromResourceOptions = extractFileIdsFromResourceOptions;
866
+ exports.extractPathsFromMmdResources = extractPathsFromMmdResources;
867
+ exports.generateMockFileUrls = generateMockFileUrls;
868
+ exports.mergeFileUrlMaps = mergeFileUrlMaps;
869
+ exports.validateFileUrls = validateFileUrls;
870
+ //# sourceMappingURL=index.js.map
871
+ //# sourceMappingURL=index.js.map