openlearn-next 0.7.6 → 0.7.7

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 (66) hide show
  1. package/README.md +66 -150
  2. package/dist/assets/{AcademicYearFilter-DUUGpDKt.js → AcademicYearFilter-Dm3no4Nb.js} +1 -1
  3. package/dist/assets/ActivityWorkspaceWidget-BLJAc5g8.js +1 -0
  4. package/dist/assets/{AdminDirectoryView-DL9zZi-_.js → AdminDirectoryView-D6sMmobg.js} +1 -1
  5. package/dist/assets/{AssessmentCenter-B2JzXBGe.js → AssessmentCenter-3Cu5DE67.js} +1 -1
  6. package/dist/assets/{AssignmentFilePreview-Bk751Blz.js → AssignmentFilePreview-DvBdEpB3.js} +2 -2
  7. package/dist/assets/{BatchPickerModal-9ekXDUUj.js → BatchPickerModal-D1kEgc4L.js} +1 -1
  8. package/dist/assets/{ClassesView-PCt8I3MU.js → ClassesView-DpC-D2y5.js} +10 -10
  9. package/dist/assets/{ClassroomCountdownWidget-HxelcHUp.js → ClassroomCountdownWidget-DFGC4XNs.js} +2 -2
  10. package/dist/assets/{CloudDriveModal-Mp59sr-C.js → CloudDriveModal-drWgNbEq.js} +1 -1
  11. package/dist/assets/ComputerLabView-BnfpX3XL.js +1 -0
  12. package/dist/assets/CourseManagement-DuVTm_hD.js +1 -0
  13. package/dist/assets/{CourseWizardModal-B7-e8otR.js → CourseWizardModal-BvQ4d1tN.js} +1 -1
  14. package/dist/assets/{CoursewareHubPanel-DLjH-UzF.js → CoursewareHubPanel-Ct9hTAOu.js} +1 -1
  15. package/dist/assets/{Dashboard-D3LXLQNC.js → Dashboard-D3yj1oZj.js} +2 -2
  16. package/dist/assets/{ExportWeightModal-vd7XgaJ0.js → ExportWeightModal-CnbJuOav.js} +1 -1
  17. package/dist/assets/{HelpTour-BLuiS8rr.js → HelpTour-COz9zyR9.js} +1 -1
  18. package/dist/assets/{HelpView-C4BHoNqR.js → HelpView-PmIyAO8_.js} +2 -2
  19. package/dist/assets/{ImportLessonsModal-Y6uIIemO.js → ImportLessonsModal-CSlMGoet.js} +1 -1
  20. package/dist/assets/{ImportModal-H5s5MyZY.js → ImportModal-CCzFqKY0.js} +1 -1
  21. package/dist/assets/{InteractiveCoursewareViewer-D9GzG28E.js → InteractiveCoursewareViewer-bgyDq7MF.js} +1 -1
  22. package/dist/assets/{InteractiveWhiteboard-DswIV-ES.js → InteractiveWhiteboard-v8FqsMj3.js} +8 -8
  23. package/dist/assets/LazyCourseware-Bt31zjJT.js +2 -0
  24. package/dist/assets/LazyWhiteboard-Da0K2ZR1.js +2 -0
  25. package/dist/assets/LessonEditorView-DT3M9p_4.js +1 -0
  26. package/dist/assets/{LiveClassroomView-Czwt5WW-.js → LiveClassroomView-CrLRO8vI.js} +1 -1
  27. package/dist/assets/NotificationDetailModal-BgqbD3Gh.js +1 -0
  28. package/dist/assets/PluginView-CkICwR16.js +77 -0
  29. package/dist/assets/{ProcessLogsModal-_BwkmJVw.js → ProcessLogsModal-zO4lMuUa.js} +1 -1
  30. package/dist/assets/{QuizGeneratorModal-DiItok6z.js → QuizGeneratorModal-D60-HJGk.js} +1 -1
  31. package/dist/assets/StudentAssignmentEvalPanel-C336iZSv.js +1 -0
  32. package/dist/assets/{StudentAssignmentView-UK_7_MF3.js → StudentAssignmentView--JlShlDQ.js} +1 -1
  33. package/dist/assets/{StudentLessonView-C11Qkdi_.js → StudentLessonView-BgjjfEfD.js} +1 -1
  34. package/dist/assets/StudentView-Cjq1QOvV.js +2 -0
  35. package/dist/assets/{SystemResourceLibraryModal-dEdNKbk2.js → SystemResourceLibraryModal-pPdl2NHf.js} +1 -1
  36. package/dist/assets/TeacherView-dLr1v6tY.js +2 -0
  37. package/dist/assets/{TimetableView-AkReskNG.js → TimetableView-CVAD4LYw.js} +1 -1
  38. package/dist/assets/{UTIF-4E-wdh3X.js → UTIF-p6fKAG_V.js} +1 -1
  39. package/dist/assets/index-BfKoCpo_.js +131 -0
  40. package/dist/assets/{index-DPky2leG.css → index-DIcDOunT.css} +1 -1
  41. package/dist/assets/{index.es-WXdI0L-F.js → index.es-B8wsP9dg.js} +1 -1
  42. package/dist/assets/{pluginParsers-k90WUDrU.js → pluginParsers-D5PQwW_l.js} +1 -1
  43. package/dist/assets/{student-workspace-BDw1i4Vl.js → student-workspace-C2tppsk6.js} +1 -1
  44. package/dist/assets/{vendor-charts-SrpBsvM3.js → vendor-charts-skggaP9D.js} +1 -1
  45. package/dist/assets/{vendor-icons-hc6MjYFv.js → vendor-icons-DGdjNlGa.js} +2 -2
  46. package/dist/assets/{vendor-konva-Dne0Z-WC.js → vendor-konva-BOuJWFuE.js} +1 -1
  47. package/dist/assets/{vendor-pdf-By0fofA0.js → vendor-pdf-DmaV9d9t.js} +3 -3
  48. package/dist/assets/{vendor-pptx-EjNUtdws.js → vendor-pptx-CpYklkz2.js} +4 -4
  49. package/dist/assets/{vendor-react-Kv_JATwr.js → vendor-react-DHoBZpn2.js} +2 -2
  50. package/dist/assets/{vendor-utils-aM7aYvPP.js → vendor-utils-BxOuk3id.js} +1 -1
  51. package/dist/env-validation.cjs +7 -1
  52. package/dist/index.html +5 -5
  53. package/dist/server.cjs +288 -25
  54. package/package.json +1 -1
  55. package/dist/assets/ActivityWorkspaceWidget-0DUqiqFK.js +0 -1
  56. package/dist/assets/ComputerLabView-FStILvFC.js +0 -1
  57. package/dist/assets/CourseManagement-BQw2idgZ.js +0 -1
  58. package/dist/assets/LazyCourseware-BEoTTg5h.js +0 -2
  59. package/dist/assets/LazyWhiteboard-CMBBxnFX.js +0 -2
  60. package/dist/assets/LessonEditorView-BgJtG8Ks.js +0 -1
  61. package/dist/assets/NotificationDetailModal-D42vEs3S.js +0 -1
  62. package/dist/assets/PluginView-BcOWIjLu.js +0 -77
  63. package/dist/assets/StudentAssignmentEvalPanel-DHoZc3g0.js +0 -1
  64. package/dist/assets/StudentView-j0hx3IOi.js +0 -2
  65. package/dist/assets/TeacherView-COZw2tFl.js +0 -2
  66. package/dist/assets/index-DN3E-Fmw.js +0 -131
package/dist/server.cjs CHANGED
@@ -4587,14 +4587,33 @@ function wrapProcessManager(processService, tracker, pluginId, processOwnerLooku
4587
4587
  })
4588
4588
  };
4589
4589
  }
4590
- function wrapActionRegistry(actionRegistry, tracker, pluginId) {
4590
+ function wrapActionRegistry(actionRegistry, tracker, pluginId, manifestId) {
4591
+ const prefix = manifestId || pluginId;
4592
+ const isKernel = prefix.startsWith("@openlearn/");
4591
4593
  return {
4592
4594
  register: createSafeFunction((descriptor) => {
4593
- return Promise.resolve(actionRegistry.register(descriptor)).then(() => {
4595
+ const commandType = descriptor.commandType;
4596
+ const prefixed = isKernel || !commandType || commandType.startsWith(`${prefix}.`) ? commandType : `${prefix}.${commandType}`;
4597
+ const effectiveDescriptor = prefixed !== commandType ? { ...descriptor, commandType: prefixed } : descriptor;
4598
+ return Promise.resolve(actionRegistry.register(effectiveDescriptor)).then(() => {
4599
+ if (prefixed !== commandType) {
4600
+ Promise.resolve(
4601
+ actionRegistry.register({
4602
+ ...descriptor,
4603
+ id: `${descriptor.id}__raw_alias`,
4604
+ exposeToAgent: false
4605
+ })
4606
+ ).catch(() => {
4607
+ });
4608
+ }
4594
4609
  tracker.track(pluginId, {
4595
4610
  dispose: () => {
4596
- Promise.resolve(actionRegistry.unregister(descriptor.id)).catch(() => {
4611
+ Promise.resolve(actionRegistry.unregister(effectiveDescriptor.id)).catch(() => {
4597
4612
  });
4613
+ if (prefixed !== commandType) {
4614
+ Promise.resolve(actionRegistry.unregister(`${descriptor.id}__raw_alias`)).catch(() => {
4615
+ });
4616
+ }
4598
4617
  }
4599
4618
  });
4600
4619
  });
@@ -4835,7 +4854,7 @@ async function buildContext(serviceRegistry, tracker, pluginId, manifest, db2, s
4835
4854
  // 可选方法,mock 或旧部署可缺省 —— 缺省时 isOwnProcess 退化为「不拦截」,与旧行为一致。
4836
4855
  typeof processService.getProcessOwner === "function" ? (processId) => processService.getProcessOwner(processId) : void 0
4837
4856
  );
4838
- const wrappedActionRegistry = wrapActionRegistry(actionRegistryService, tracker, pluginId);
4857
+ const wrappedActionRegistry = wrapActionRegistry(actionRegistryService, tracker, pluginId, manifest.id);
4839
4858
  const wrappedCapability = wrapCapability(capabilityService);
4840
4859
  const wrappedStorage = wrapStorage(storageService, db2, manifest.id);
4841
4860
  const wrappedAI = wrapAI(aiService, pluginId, tracker);
@@ -8480,10 +8499,28 @@ var init_service_host = __esm({
8480
8499
  const actionRegistry = service;
8481
8500
  if (msg.method === "register") {
8482
8501
  const [descriptor] = effectiveArgs;
8483
- await actionRegistry.register(descriptor);
8484
- this.registeredActionIds.add(descriptor.id);
8485
- if (typeof descriptor?.commandType === "string") {
8486
- this.registeredActionCommandTypes.set(descriptor.id, descriptor.commandType);
8502
+ const pluginId = this.pluginId || this.pluginActorId.replace(/^plugin:/, "");
8503
+ const isKernel = pluginId.startsWith("@openlearn/");
8504
+ const rawCommandType = descriptor.commandType;
8505
+ const prefixed = isKernel || !rawCommandType || rawCommandType.startsWith(`${pluginId}.`) ? rawCommandType : resolvePluginCommandType(rawCommandType, pluginId);
8506
+ const effectiveDescriptor = prefixed !== rawCommandType ? { ...descriptor, commandType: prefixed } : descriptor;
8507
+ await actionRegistry.register(effectiveDescriptor);
8508
+ this.registeredActionIds.add(effectiveDescriptor.id);
8509
+ if (typeof effectiveDescriptor?.commandType === "string") {
8510
+ this.registeredActionCommandTypes.set(effectiveDescriptor.id, effectiveDescriptor.commandType);
8511
+ }
8512
+ if (prefixed !== rawCommandType) {
8513
+ try {
8514
+ const aliasId = `${descriptor.id}__raw_alias`;
8515
+ await actionRegistry.register({
8516
+ ...descriptor,
8517
+ id: aliasId,
8518
+ exposeToAgent: false
8519
+ });
8520
+ this.registeredActionIds.add(aliasId);
8521
+ this.registeredActionCommandTypes.set(aliasId, rawCommandType);
8522
+ } catch {
8523
+ }
8487
8524
  }
8488
8525
  transport.postMessage({
8489
8526
  type: "result",
@@ -8494,8 +8531,14 @@ var init_service_host = __esm({
8494
8531
  } else if (msg.method === "unregister") {
8495
8532
  const [actionId] = msg.args;
8496
8533
  await actionRegistry.unregister(actionId);
8534
+ try {
8535
+ await actionRegistry.unregister(`${actionId}__raw_alias`);
8536
+ } catch {
8537
+ }
8497
8538
  this.registeredActionIds.delete(actionId);
8539
+ this.registeredActionIds.delete(`${actionId}__raw_alias`);
8498
8540
  this.registeredActionCommandTypes.delete(actionId);
8541
+ this.registeredActionCommandTypes.delete(`${actionId}__raw_alias`);
8499
8542
  transport.postMessage({
8500
8543
  type: "result",
8501
8544
  invokeId: msg.invokeId,
@@ -15228,6 +15271,26 @@ var BuiltinPlugin = {
15228
15271
  "page_meta"
15229
15272
  // 分页与大纲元数据
15230
15273
  ];
15274
+ const isAllowedElementType = (type) => {
15275
+ if (!type || typeof type !== "string") return false;
15276
+ if (KNOWN_ELEMENT_TYPES2.includes(type)) return true;
15277
+ if (type.includes("/") || type.startsWith("plugin:") || type.startsWith("ext-") || type.startsWith("plugin-")) {
15278
+ return true;
15279
+ }
15280
+ try {
15281
+ const reg = pluginHost?.getContributionRegistry?.();
15282
+ if (reg) {
15283
+ const paletteItems = reg.getBySlot("editor.palette_item") || [];
15284
+ if (paletteItems.some((item) => item.type === type || item.id === type)) return true;
15285
+ const renderers = reg.getBySlot("whiteboard.renderer") || [];
15286
+ if (renderers.some((item) => item.targetType === type || item.id === type || item.elementType === type)) {
15287
+ return true;
15288
+ }
15289
+ }
15290
+ } catch {
15291
+ }
15292
+ return false;
15293
+ };
15231
15294
  await actionRegistry.register({
15232
15295
  id: "core-whiteboard-draw",
15233
15296
  commandType: drawWhiteboardCmdType,
@@ -15251,8 +15314,8 @@ var BuiltinPlugin = {
15251
15314
  await commandBus.registerHandler(drawWhiteboardCmdType, {
15252
15315
  async execute(command) {
15253
15316
  const payload = command.payload;
15254
- if (!KNOWN_ELEMENT_TYPES2.includes(payload.type)) {
15255
- const hint = payload.type === "quiz" ? "" : payload.type && (payload.type.includes("quiz") || payload.type.includes("question")) ? ` \u63D0\u793A\uFF1A\u521B\u5EFA\u6D4B\u9A8C\u8BF7\u4F7F\u7528 quiz.create \u6216 quiz_pro.create \u5DE5\u5177\u3002` : ` \u5DF2\u77E5\u7C7B\u578B\uFF1A${KNOWN_ELEMENT_TYPES2.join(", ")}\u3002`;
15317
+ if (!isAllowedElementType(payload.type)) {
15318
+ const hint = payload.type === "quiz" ? "" : payload.type && (payload.type.includes("quiz") || payload.type.includes("question")) ? ` \u63D0\u793A\uFF1A\u521B\u5EFA\u6D4B\u9A8C\u8BF7\u4F7F\u7528 quiz.create \u6216 quiz_pro.create \u5DE5\u5177\u3002` : ` \u5DF2\u77E5\u7C7B\u578B\uFF1A${KNOWN_ELEMENT_TYPES2.join(", ")}\uFF0C\u6216\u63D2\u4EF6\u6269\u5C55\u56FE\u5143 (\u5982 ext-xxx/yyy)\u3002`;
15256
15319
  throw new Error(`\u4E0D\u652F\u6301\u7684\u5143\u7D20\u7C7B\u578B "${payload.type}"\u3002${hint}`);
15257
15320
  }
15258
15321
  const elementId = (0, import_uuid7.v7)();
@@ -15494,7 +15557,7 @@ var BuiltinPlugin = {
15494
15557
  );
15495
15558
  const batchInsert = db2.transaction(() => {
15496
15559
  for (const el of elements) {
15497
- if (!KNOWN_ELEMENT_TYPES2.includes(el.type)) {
15560
+ if (!isAllowedElementType(el.type)) {
15498
15561
  throw new Error(`\u4E0D\u652F\u6301\u7684\u5143\u7D20\u7C7B\u578B "${el.type}"\u3002\u5DF2\u77E5\u7C7B\u578B\uFF1A${KNOWN_ELEMENT_TYPES2.join(", ")}`);
15499
15562
  }
15500
15563
  const elementId = (0, import_uuid7.v7)();
@@ -19027,6 +19090,19 @@ function trustedInlineAllowlist(env = process.env) {
19027
19090
  (env.OPENLEARN_TRUSTED_INLINE_PLUGINS ?? "").split(",").map((item) => item.trim()).filter((item) => item.length > 0)
19028
19091
  );
19029
19092
  }
19093
+ function matchesTrustedInlinePattern(pattern, manifestId) {
19094
+ if (!manifestId) return false;
19095
+ if (pattern === "*") return true;
19096
+ if (pattern.endsWith("*")) return manifestId.startsWith(pattern.slice(0, -1));
19097
+ return pattern === manifestId;
19098
+ }
19099
+ function isInTrustedInlineAllowlist(manifestId, env = process.env) {
19100
+ if (!manifestId) return false;
19101
+ for (const pattern of trustedInlineAllowlist(env)) {
19102
+ if (matchesTrustedInlinePattern(pattern, manifestId)) return true;
19103
+ }
19104
+ return false;
19105
+ }
19030
19106
  function builtinPluginsDir() {
19031
19107
  try {
19032
19108
  const url = import_meta5.url;
@@ -19049,7 +19125,7 @@ function isInsideBuiltinPluginsDir(sourcePath, builtinDir = builtinPluginsDir())
19049
19125
  }
19050
19126
  function isTrustedInProcessPlugin(manifestId, env = process.env, sourcePath) {
19051
19127
  if (isInsideBuiltinPluginsDir(sourcePath)) return true;
19052
- if (manifestId && trustedInlineAllowlist(env).has(manifestId)) return true;
19128
+ if (isInTrustedInlineAllowlist(manifestId, env)) return true;
19053
19129
  if (env.VITEST && env.OPENLEARN_ENFORCE_PLUGIN_ISOLATION !== "1") return true;
19054
19130
  return false;
19055
19131
  }
@@ -21037,7 +21113,7 @@ var import_node_fs11 = __toESM(require("node:fs"), 1);
21037
21113
  var import_node_path13 = __toESM(require("node:path"), 1);
21038
21114
  var import_node_url5 = require("node:url");
21039
21115
  var import_meta7 = {};
21040
- var FALLBACK_VERSION = "0.7.6";
21116
+ var FALLBACK_VERSION = "0.7.7";
21041
21117
  function readPlatformVersion() {
21042
21118
  const bases = [];
21043
21119
  if (typeof __dirname !== "undefined") bases.push(__dirname);
@@ -32843,11 +32919,17 @@ function validateStartupEnv(env = process.env) {
32843
32919
  severity: "warning",
32844
32920
  message: "OPENLEARN_TRUSTED_INLINE_PLUGINS \u53EA\u542B\u7A7A\u767D/\u9017\u53F7\uFF0C\u7B49\u540C\u4E8E\u672A\u914D\u7F6E\u3002"
32845
32921
  });
32922
+ } else if (ids.includes("*")) {
32923
+ warnings.push({
32924
+ key: "OPENLEARN_TRUSTED_INLINE_PLUGINS",
32925
+ severity: "warning",
32926
+ message: "OPENLEARN_TRUSTED_INLINE_PLUGINS \u542B\u901A\u914D\u7B26 `*`\uFF1A**\u6240\u6709**\u63D2\u4EF6\u90FD\u88AB\u89C6\u4E3A\u53D7\u4FE1\uFF0C\u53EF\u5728\u8FDB\u7A0B\u5185\u4EE5 inline/worker \u8FD0\u884C\uFF08\u542B\u65E5\u540E\u5B89\u88C5\u7684\u4EFB\u610F\u7B2C\u4E09\u65B9\u5305\uFF09\u3002\u5B83\u4EEC\u4E0E\u5BBF\u4E3B\u5171\u4EAB\u73AF\u5883\u53D8\u91CF\u4E0E\u7F51\u7EDC\u6808\uFF0C\u51FA\u7AD9\u7F51\u7EDC\u62E6\u622A\u5BF9\u5B83\u4EEC\u4E0D\u751F\u6548\uFF0C\u672A\u6355\u83B7\u5F02\u5E38\u53EF\u62D6\u57AE\u670D\u52A1\u3002\u4EC5\u9650\u672C\u5730\u5F00\u53D1\uFF1B\u751F\u4EA7\u73AF\u5883\u8BF7\u6539\u4E3A\u7CBE\u786E id \u6216\u524D\u7F00\u5F0F\uFF08\u5982 `@ext/*`\uFF09\u3002"
32927
+ });
32846
32928
  } else if (isProduction) {
32847
32929
  warnings.push({
32848
32930
  key: "OPENLEARN_TRUSTED_INLINE_PLUGINS",
32849
32931
  severity: "warning",
32850
- message: `\u751F\u4EA7\u73AF\u5883\u4FE1\u4EFB\u4E86 ${ids.length} \u4E2A\u63D2\u4EF6\u5728**\u8FDB\u7A0B\u5185**\u8FD0\u884C\uFF1A${ids.join(", ")}\u3002 \u5B83\u4EEC\u4E0E\u5BBF\u4E3B\u5171\u4EAB\u73AF\u5883\u53D8\u91CF\u4E0E\u7F51\u7EDC\u6808\uFF0C\u51FA\u7AD9\u7F51\u7EDC\u62E6\u622A\u5BF9\u5B83\u4EEC\u4E0D\u751F\u6548\u3002 \u786E\u8BA4\u8FD9\u662F\u6709\u610F\u4E3A\u4E4B\uFF1B\u5426\u5219\u6E05\u7A7A\u8BE5\u53D8\u91CF\u8BA9\u5B83\u4EEC\u56DE\u5230 process \u5B50\u8FDB\u7A0B\u9694\u79BB\u3002`
32932
+ message: `\u751F\u4EA7\u73AF\u5883\u4FE1\u4EFB\u4E86 ${ids.length} \u4E2A\u6761\u76EE\u5728**\u8FDB\u7A0B\u5185**\u8FD0\u884C\uFF1A${ids.join(", ")}\u3002 \u5B83\u4EEC\u4E0E\u5BBF\u4E3B\u5171\u4EAB\u73AF\u5883\u53D8\u91CF\u4E0E\u7F51\u7EDC\u6808\uFF0C\u51FA\u7AD9\u7F51\u7EDC\u62E6\u622A\u5BF9\u5B83\u4EEC\u4E0D\u751F\u6548\u3002 \u786E\u8BA4\u8FD9\u662F\u6709\u610F\u4E3A\u4E4B\uFF1B\u5426\u5219\u6E05\u7A7A\u8BE5\u53D8\u91CF\u8BA9\u5B83\u4EEC\u56DE\u5230 process \u5B50\u8FDB\u7A0B\u9694\u79BB\u3002`
32851
32933
  });
32852
32934
  }
32853
32935
  }
@@ -33671,6 +33753,7 @@ var normalizeToolSchema = (schema) => {
33671
33753
  };
33672
33754
  var buildOpenAITools = (callerRole) => {
33673
33755
  let actions = kernelContainer.actionRegistry.getAllActions();
33756
+ actions = actions.filter((action) => action.exposeToAgent !== false);
33674
33757
  if (callerRole === "student") {
33675
33758
  actions = actions.filter((action) => {
33676
33759
  const cap = action.capabilityRequired || "";
@@ -36080,6 +36163,142 @@ post({ type: 'ready' });
36080
36163
  </script>
36081
36164
  </body></html>`;
36082
36165
 
36166
+ // server/utils/plugin-view-bridge.ts
36167
+ var PLUGIN_VIEW_CSP = "sandbox allow-scripts allow-forms allow-downloads; frame-ancestors 'self'; default-src 'self' 'unsafe-inline' data: blob: https:; script-src 'self' 'unsafe-inline' 'unsafe-eval' data: blob: https:; script-src-attr 'unsafe-inline'; style-src 'self' 'unsafe-inline' data: blob: https:; style-src-attr 'unsafe-inline'; connect-src 'self' blob: data: https:;";
36168
+ var PLUGIN_VIEW_BRIDGE_JS = `(function () {
36169
+ 'use strict';
36170
+ const CHANNEL = 'ol-plugin-view';
36171
+ let targetOrigin = '*';
36172
+ const pending = new Map();
36173
+ const initCallbacks = [];
36174
+ const contextUpdateCallbacks = [];
36175
+ let currentContext = null;
36176
+
36177
+ function post(msg) {
36178
+ if (window.parent && window.parent !== window) {
36179
+ window.parent.postMessage(Object.assign({ channel: CHANNEL }, msg), targetOrigin);
36180
+ }
36181
+ }
36182
+
36183
+ function call(op, args) {
36184
+ const callId = Math.random().toString(16).slice(2) + Date.now().toString(16);
36185
+ return new Promise(function (resolve, reject) {
36186
+ pending.set(callId, { resolve: resolve, reject: reject });
36187
+ post({ type: 'call', callId: callId, op: op, args: args || [] });
36188
+ });
36189
+ }
36190
+
36191
+ window.addEventListener('message', function (event) {
36192
+ const data = event.data;
36193
+ if (!data || data.channel !== CHANNEL) return;
36194
+ if (event.source !== window.parent) return;
36195
+
36196
+ if (data.type === 'init') {
36197
+ currentContext = data.payload || {};
36198
+ targetOrigin = event.origin && event.origin !== 'null' ? event.origin : '*';
36199
+ for (let i = 0; i < initCallbacks.length; i++) {
36200
+ try {
36201
+ initCallbacks[i](currentContext);
36202
+ } catch (e) {
36203
+ console.error('[OpenLearnPluginBridge] init callback error:', e);
36204
+ }
36205
+ }
36206
+ return;
36207
+ }
36208
+
36209
+ if (data.type === 'context-update') {
36210
+ currentContext = Object.assign({}, currentContext, data.payload || {});
36211
+ for (let i = 0; i < contextUpdateCallbacks.length; i++) {
36212
+ try {
36213
+ contextUpdateCallbacks[i](currentContext);
36214
+ } catch (e) {
36215
+ console.error('[OpenLearnPluginBridge] contextUpdate callback error:', e);
36216
+ }
36217
+ }
36218
+ return;
36219
+ }
36220
+
36221
+ if (data.type === 'reply') {
36222
+ const waiter = pending.get(data.callId);
36223
+ if (!waiter) return;
36224
+ pending.delete(data.callId);
36225
+ if (data.ok) {
36226
+ waiter.resolve(data.value);
36227
+ } else {
36228
+ waiter.reject(new Error(data.error || 'bridge call failed'));
36229
+ }
36230
+ return;
36231
+ }
36232
+ });
36233
+
36234
+ const bridge = {
36235
+ CHANNEL: CHANNEL,
36236
+ onInit: function (callback) {
36237
+ if (typeof callback === 'function') {
36238
+ if (currentContext) {
36239
+ try {
36240
+ callback(currentContext);
36241
+ } catch (e) {
36242
+ console.error('[OpenLearnPluginBridge] onInit callback error:', e);
36243
+ }
36244
+ }
36245
+ initCallbacks.push(callback);
36246
+ }
36247
+ return bridge;
36248
+ },
36249
+ onContextUpdate: function (callback) {
36250
+ if (typeof callback === 'function') {
36251
+ contextUpdateCallbacks.push(callback);
36252
+ }
36253
+ return bridge;
36254
+ },
36255
+ getContext: function () {
36256
+ return currentContext;
36257
+ },
36258
+ ui: {
36259
+ toast: function (message, type) {
36260
+ return call('ui.toast', [message, type || 'info']);
36261
+ },
36262
+ showToast: function (message, type) {
36263
+ return call('ui.toast', [message, type || 'info']);
36264
+ },
36265
+ resize: function (height) {
36266
+ post({ type: 'resize', height: Number(height) || 0 });
36267
+ },
36268
+ },
36269
+ invokeCommand: function (type, payload) {
36270
+ return call('command.invoke', [type, payload]);
36271
+ },
36272
+ api: {
36273
+ get: function (path) {
36274
+ return call('api.get', [path]);
36275
+ },
36276
+ post: function (path, body) {
36277
+ return call('api.post', [path, body]);
36278
+ },
36279
+ del: function (path) {
36280
+ return call('api.del', [path]);
36281
+ },
36282
+ },
36283
+ storage: {
36284
+ get: function (key) {
36285
+ return call('storage.get', [key]);
36286
+ },
36287
+ set: function (key, value) {
36288
+ return call('storage.set', [key, value]);
36289
+ },
36290
+ delete: function (key) {
36291
+ return call('storage.delete', [key]);
36292
+ },
36293
+ },
36294
+ };
36295
+
36296
+ window.OpenLearnPluginBridge = bridge;
36297
+ // \u901A\u77E5\u5BBF\u4E3B\u89C6\u56FE\u6C99\u7BB1 iframe \u5DF2\u5C31\u7EEA
36298
+ post({ type: 'ready' });
36299
+ })();
36300
+ `;
36301
+
36083
36302
  // packages/core/bootstrap/adapter/bootstrap-registration.ts
36084
36303
  var BootstrapRegistration = class {
36085
36304
  static registerConfiguration(builder, context) {
@@ -42515,7 +42734,8 @@ async function importCoursePackage(pkg, options) {
42515
42734
  let elements = [];
42516
42735
  try {
42517
42736
  elements = pkg.elements.map((element) => {
42518
- if (!KNOWN_ELEMENT_TYPES.has(element.type)) {
42737
+ const isAllowed = KNOWN_ELEMENT_TYPES.has(element.type) || typeof element.type === "string" && (element.type.includes("/") || element.type.startsWith("plugin:") || element.type.startsWith("ext-") || element.type.startsWith("plugin-"));
42738
+ if (!isAllowed) {
42519
42739
  throw new Error(`\u8BFE\u7A0B\u5305\u5305\u542B\u4E0D\u652F\u6301\u7684\u767D\u677F\u5143\u7D20\u7C7B\u578B\uFF1A${element.type}`);
42520
42740
  }
42521
42741
  const data = resolveAssetPlaceholders(element.data, materialized);
@@ -49881,7 +50101,9 @@ async function assertCommunityZipAttestation(req, zipPath, targetPluginId) {
49881
50101
  integrity: verifyRes.integrity
49882
50102
  };
49883
50103
  }
49884
- if (!allowUnsigned) {
50104
+ const allowUnsignedEnv = String(process.env.OPENLEARN_ALLOW_UNSIGNED ?? "").toLowerCase();
50105
+ const allowUnsignedEffective = allowUnsigned || !["0", "false"].includes(allowUnsignedEnv);
50106
+ if (!allowUnsignedEffective) {
49885
50107
  return {
49886
50108
  ok: false,
49887
50109
  status: 400,
@@ -50123,6 +50345,25 @@ function registerPluginsRoutes(ctx) {
50123
50345
  })
50124
50346
  );
50125
50347
  });
50348
+ app.get("/api/plugins/isolation-policy", requireAuth("administrator"), (req, res) => {
50349
+ const manifestId = String(req.query.manifestId || "").trim();
50350
+ if (!manifestId) {
50351
+ return res.status(400).json({ success: false, error: "manifestId is required" });
50352
+ }
50353
+ const requestedRaw = String(req.query.requestedMode || "").toLowerCase();
50354
+ const requested = isValidExecutionMode(requestedRaw) ? requestedRaw : void 0;
50355
+ const trusted = isTrustedInProcessPlugin(manifestId);
50356
+ const decision = resolvePluginExecutionMode(manifestId, requested);
50357
+ res.json({
50358
+ success: true,
50359
+ manifestId,
50360
+ trusted,
50361
+ requestedMode: requested ?? null,
50362
+ resolvedMode: decision.mode,
50363
+ coerced: decision.coerced,
50364
+ allowlistSize: trustedInlineAllowlist().size
50365
+ });
50366
+ });
50126
50367
  app.get("/api/plugins/by-manifest/:manifestId(*)", requireScopedAuth("authenticated"), (req, res) => {
50127
50368
  try {
50128
50369
  const manifestId = decodeURIComponent(req.params.manifestId);
@@ -50139,7 +50380,12 @@ function registerPluginsRoutes(ctx) {
50139
50380
  status: found.status,
50140
50381
  state: found.state,
50141
50382
  manifest: found.manifest,
50142
- isSystem: kernelContainer.pluginHost.isOfficialPlugin(found.pluginId, manifestId)
50383
+ isSystem: kernelContainer.pluginHost.isOfficialPlugin(found.pluginId, manifestId),
50384
+ // 升级路径的回填:按库里存的请求值 + 当前信任策略算出**实际会用的**档位。
50385
+ isolationMode: resolvePluginExecutionMode(
50386
+ manifestId,
50387
+ kernelContainer.db.prepare("SELECT execution_mode FROM plugins WHERE id = ?").get(found.pluginId)?.execution_mode
50388
+ ).mode
50143
50389
  });
50144
50390
  } catch (e) {
50145
50391
  sendSafeError(res, e);
@@ -57742,18 +57988,35 @@ async function startServer() {
57742
57988
  app.use(
57743
57989
  "/plugins",
57744
57990
  (req, res, next) => {
57745
- const token = getCookieToken(req);
57746
- if (!token) {
57747
- return res.status(401).json({ error: "Authentication required" });
57991
+ const token = getCookieToken(req) || (typeof req.query.token === "string" ? req.query.token : null) || (req.headers.authorization?.startsWith("Bearer ") ? req.headers.authorization.slice(7).trim() : null);
57992
+ if (token) {
57993
+ const session = getValidSession(token);
57994
+ if (session) {
57995
+ return next();
57996
+ }
57748
57997
  }
57749
- const session = getValidSession(token);
57750
- if (!session) {
57751
- return res.status(401).json({ error: "Session expired or invalid" });
57998
+ const dest = req.headers["sec-fetch-dest"];
57999
+ const site = req.headers["sec-fetch-site"];
58000
+ if (site === "same-origin" || typeof dest === "string" && dest !== "" && dest !== "document") {
58001
+ return next();
57752
58002
  }
57753
- next();
58003
+ return res.status(401).json({ error: "Authentication required" });
57754
58004
  },
57755
- import_express4.default.static(getPluginsDir(), { index: false, dotfiles: "ignore", fallthrough: false, maxAge: 0 })
58005
+ import_express4.default.static(getPluginsDir(), {
58006
+ index: false,
58007
+ dotfiles: "ignore",
58008
+ fallthrough: false,
58009
+ maxAge: 0,
58010
+ setHeaders: (res, filePath) => {
58011
+ if (filePath.endsWith(".html") || filePath.endsWith(".htm")) {
58012
+ res.setHeader("Content-Security-Policy", PLUGIN_VIEW_CSP);
58013
+ }
58014
+ }
58015
+ })
57756
58016
  );
58017
+ app.get("/plugin-sandbox/view-bridge.js", (_req, res) => {
58018
+ res.status(200).setHeader("Content-Type", "application/javascript; charset=utf-8").setHeader("Cache-Control", "public, max-age=3600").send(PLUGIN_VIEW_BRIDGE_JS);
58019
+ });
57757
58020
  app.get("/plugin-sandbox/frame", (req, res) => {
57758
58021
  const token = getCookieToken(req);
57759
58022
  const session = token ? getValidSession(token) : null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openlearn-next",
3
- "version": "0.7.6",
3
+ "version": "0.7.7",
4
4
  "private": false,
5
5
  "description": "A plugin-driven Educational OS / LMS platform with AI agent teaching assistant",
6
6
  "keywords": [
@@ -1 +0,0 @@
1
- import{r as o,j as e}from"./vendor-react-Kv_JATwr.js";import{e as D}from"./index-DN3E-Fmw.js";import{$ as q,X as H,j as J,i as Q,B as V,o as X,y as _,w as G,U as K,b1 as Y,b0 as Z,b2 as ee,e as te,P as se}from"./vendor-icons-hc6MjYFv.js";import"./vendor-utils-aM7aYvPP.js";import"./vendor-pdf-By0fofA0.js";import"./vendor-motion-BFAPOs-b.js";import"./vendor-charts-SrpBsvM3.js";async function ie(n="all"){const a=await fetch(`/api/activities?role=${encodeURIComponent(n)}`,{headers:{Accept:"application/json"}});if(!a.ok)throw new Error(`Failed to load activities (${a.status})`);return(await a.json()).activities??[]}async function k(){const n=await fetch("/api/activities/running",{headers:{Accept:"application/json"}});if(!n.ok)throw new Error(`Failed to load running activities (${n.status})`);return(await n.json()).activities??[]}async function ne(n){const a=await fetch(`/api/activities/${encodeURIComponent(n)}/finish`,{method:"POST",headers:{"Content-Type":"application/json"}});if(!a.ok){const s=await a.json().catch(()=>({}));throw new Error(s.error||`Failed to finish activity (${a.status})`)}}async function ae(n){const a=await fetch(`/api/activities/${encodeURIComponent(n)}/pause`,{method:"POST",headers:{"Content-Type":"application/json"}});if(!a.ok){const s=await a.json().catch(()=>({}));throw new Error(s.error||`Failed to pause activity (${a.status})`)}}async function re(n){const a=await fetch(`/api/activities/${encodeURIComponent(n)}/resume`,{method:"POST",headers:{"Content-Type":"application/json"}});if(!a.ok){const s=await a.json().catch(()=>({}));throw new Error(s.error||`Failed to resume activity (${a.status})`)}}async function oe(n,a,s){const l=await fetch(`/api/activities/${encodeURIComponent(n)}/start`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({payload:a??{},actorId:s})}),c=await l.json();if(!l.ok||c.ok===!1)throw new Error(c.error||`Failed to start activity (${l.status})`);return c}const x={padding:"12px",color:"#e2e8f0",fontSize:"13px"},ce={HelpCircle:te,Vote:ee,BarChart3:Z,MessagesSquare:Y,Users:K,FileText:G,Trophy:_,CheckCircle2:X,BookOpen:V};function le({name:n,className:a}){const s=n&&ce[n]||se;return e.jsx(s,{size:18,className:a})}function de(n,a){if(!n)return a==="zh"?"进行中":"In progress";const s=Math.max(0,Math.floor((Date.now()-n)/1e3)),l=Math.floor(s/60),c=s%60;return a==="zh"?`${l} 分 ${c} 秒`:`${l}m ${c}s`}const ge=({role:n,actorId:a,lang:s="en",classroomId:l,mode:c="launcher"})=>{const[p,E]=o.useState([]),[A,j]=o.useState(!0),[b,$]=o.useState(null),[v,m]=o.useState({}),[h,w]=o.useState([]),[T,R]=o.useState(!0),[P,I]=o.useState(null),[y,u]=o.useState(null),[f,z]=o.useState(!1),[S,C]=o.useState(null),B=D(i=>i.setTeacherTab),F=o.useMemo(()=>{const i=new Map;for(const t of p){const r=t.category;i.has(r)||i.set(r,[]),i.get(r).push(t)}return Array.from(i.entries())},[p]);o.useEffect(()=>{if(c!=="launcher")return;let i=!1;return j(!0),ie(n).then(t=>{i||E(t)}).catch(t=>{i||$(t instanceof Error?t.message:String(t))}).finally(()=>{i||j(!1)}),()=>{i=!0}},[n,c]),o.useEffect(()=>{if(c!=="status")return;let i=!1;const t=async()=>{try{const d=await k();i||w(d)}catch(d){i||I(d instanceof Error?d.message:String(d))}finally{i||R(!1)}};t();const r=setInterval(t,5e3);return()=>{i=!0,clearInterval(r)}},[c]);const g=async i=>{if(y){z(!0),C(null);try{await i(y),w(await k())}catch(t){C(t instanceof Error?t.message:String(t))}finally{z(!1)}}},W=()=>g(ne),M=()=>g(ae),O=()=>g(re),L=()=>{u(null),B("live_class")};if(c==="status"){if(T)return e.jsx("div",{className:"bg-white border border-slate-200/80 rounded-2xl p-5 shadow-sm h-24 animate-pulse"});if(P||h.length===0)return null;const i=h.find(t=>t.id===y)||null;return e.jsxs("div",{className:"bg-white border border-slate-200/80 rounded-2xl p-5 shadow-sm",children:[e.jsxs("div",{className:"flex items-center justify-between mb-3",children:[e.jsxs("div",{className:"flex items-center gap-2 text-slate-800 font-extrabold",children:[e.jsx("span",{children:"🧩"}),e.jsx("span",{children:s==="zh"?"进行中的活动":"Activities in Progress"})]}),e.jsx("span",{className:"text-xs font-bold bg-indigo-50 text-indigo-700 px-2 py-0.5 rounded-full",children:h.length})]}),e.jsx("div",{className:"flex flex-col gap-2",children:h.map(t=>{const r=t.state==="running";return e.jsxs("button",{onClick:()=>u(t.id),className:"w-full flex items-center gap-3 text-left bg-slate-50 hover:bg-slate-100 border border-slate-200/70 rounded-xl p-3 transition-colors",children:[e.jsx(le,{name:t.icon,className:"text-indigo-600 shrink-0"}),e.jsxs("div",{className:"min-w-0 flex-1",children:[e.jsx("div",{className:"font-bold text-slate-800 truncate",children:t.name}),t.category&&e.jsx("div",{className:"text-xs text-slate-500 truncate",children:t.category})]}),e.jsxs("div",{className:"flex flex-col items-end gap-1 shrink-0",children:[e.jsx("span",{className:`text-xs font-bold px-2 py-0.5 rounded-full ${r?"bg-emerald-100 text-emerald-700":"bg-amber-100 text-amber-700"}`,children:r?s==="zh"?"进行中":"Running":s==="zh"?"已暂停":"Paused"}),e.jsxs("span",{className:"text-xs text-slate-400 flex items-center gap-1",children:[e.jsx(q,{size:10})," ",de(t.startedAt,s)]})]})]},t.id)})}),i&&e.jsx("div",{className:"fixed inset-0 z-50 flex items-center justify-center bg-black/30 p-4",onClick:()=>u(null),children:e.jsxs("div",{className:"bg-white rounded-2xl shadow-xl w-full max-w-sm p-5",onClick:t=>t.stopPropagation(),children:[e.jsxs("div",{className:"flex items-center justify-between mb-3",children:[e.jsx("h3",{className:"font-bold text-slate-800",children:i.name}),e.jsx("button",{onClick:()=>u(null),className:"text-slate-400 hover:text-slate-600","aria-label":"close",children:e.jsx(H,{size:18})})]}),e.jsxs("div",{className:"text-sm text-slate-500 mb-1",children:[s==="zh"?"状态":"Status",":"," ",i.state==="running"?s==="zh"?"进行中":"Running":s==="zh"?"已暂停":"Paused"]}),e.jsxs("div",{className:"text-sm text-slate-500 mb-4",children:[s==="zh"?"开始时间":"Started",":"," ",i.startedAt?new Date(i.startedAt).toLocaleString():"-"]}),S&&e.jsx("div",{className:"text-xs text-rose-600 mb-2",children:S}),e.jsxs("div",{className:"flex gap-2",children:[i.state==="running"?e.jsxs("button",{disabled:f,onClick:M,className:"flex-1 flex items-center justify-center gap-1 bg-amber-500 hover:bg-amber-600 disabled:opacity-60 text-white rounded-lg py-2 text-sm font-bold",children:[e.jsx(J,{size:14})," ",s==="zh"?"暂停":"Pause"]}):e.jsxs("button",{disabled:f,onClick:O,className:"flex-1 flex items-center justify-center gap-1 bg-indigo-500 hover:bg-indigo-600 disabled:opacity-60 text-white rounded-lg py-2 text-sm font-bold",children:[e.jsx(Q,{size:14})," ",s==="zh"?"恢复":"Resume"]}),e.jsx("button",{disabled:f,onClick:W,className:"flex-1 flex items-center justify-center gap-1 bg-rose-600 hover:bg-rose-700 disabled:opacity-60 text-white rounded-lg py-2 text-sm font-bold",children:f?s==="zh"?"处理中…":"Working…":s==="zh"?"结束":"End"})]}),e.jsx("button",{onClick:L,className:"w-full mt-2 bg-slate-100 hover:bg-slate-200 text-slate-700 rounded-lg py-2 text-sm font-bold",children:s==="zh"?"进入课堂":"Enter Classroom"})]})})]})}const U=async i=>{m(t=>({...t,[i.id]:s==="zh"?"启动中…":"Starting…"}));try{const t=await oe(i.id,{classroomId:l,role:n},a);m(r=>({...r,[i.id]:t.dispatched?s==="zh"?"已启动 ✓":"Started ✓":s==="zh"?"已通知 ✓":"Notified ✓"}))}catch(t){m(r=>({...r,[i.id]:t instanceof Error?t.message:String(t)}))}};if(A)return e.jsx("div",{style:x,children:s==="zh"?"加载活动中…":"Loading activities…"});if(b)return e.jsxs("div",{style:x,children:[e.jsxs("div",{style:{fontWeight:600,marginBottom:6},children:["🧩 ",s==="zh"?"活动中心":"Activity Center"]}),e.jsx("div",{style:{opacity:.7},children:b})]});const N=n==="teacher"?s==="zh"?"活动中心(教师)":"Activity Center":s==="zh"?"活动(学生)":"Activities";return p.length===0?e.jsxs("div",{style:x,children:[e.jsxs("div",{style:{fontWeight:600,marginBottom:6},children:["🧩 ",N]}),e.jsx("div",{style:{opacity:.7},children:s==="zh"?"暂无可用活动。":"No activities available."})]}):e.jsxs("div",{style:x,children:[e.jsxs("div",{style:{fontWeight:600,marginBottom:8},children:["🧩 ",N]}),F.map(([i,t])=>e.jsxs("div",{style:{marginBottom:10},children:[e.jsx("div",{style:{opacity:.6,fontSize:12,textTransform:"uppercase",marginBottom:4},children:i}),e.jsx("div",{style:{display:"flex",flexWrap:"wrap",gap:8},children:t.map(r=>e.jsxs("div",{style:{border:"1px solid #334155",borderRadius:8,padding:"8px 10px",minWidth:150,background:"#0f172a"},children:[e.jsxs("div",{style:{fontWeight:600},children:[r.icon?`${r.icon} `:"▫️ ",r.name]}),r.description&&e.jsx("div",{style:{opacity:.7,fontSize:12,margin:"2px 0 6px"},children:r.description}),e.jsxs("div",{style:{display:"flex",alignItems:"center",gap:6},children:[e.jsx("button",{onClick:()=>U(r),style:{cursor:"pointer",background:n==="teacher"?"#2563eb":"#0ea5e9",color:"#fff",border:"none",borderRadius:6,padding:"4px 10px",fontSize:12},children:n==="teacher"?s==="zh"?"启动":"Start":s==="zh"?"参与":"Open"}),v[r.id]&&e.jsx("span",{style:{fontSize:12,opacity:.85},children:v[r.id]})]}),e.jsx("div",{style:{opacity:.5,fontSize:12,marginTop:4},children:r.provider==="official"?"official":r.provider})]},r.id))})]},i))]})};export{ge as ActivityWorkspaceWidget};
@@ -1 +0,0 @@
1
- import{r as l,j as e}from"./vendor-react-Kv_JATwr.js";import{_ as Ae,E as M,c as Te}from"./index-DN3E-Fmw.js";import{am as fe,ak as Le,bu as J,bt as Pe,ac as Me,X as te,V as Re,a9 as be,U as je,z as Oe,ad as Fe,c as ye,ae as Ue,C as Be,Z as Je}from"./vendor-icons-hc6MjYFv.js";import"./vendor-utils-aM7aYvPP.js";import"./vendor-pdf-By0fofA0.js";import"./vendor-motion-BFAPOs-b.js";import"./vendor-charts-SrpBsvM3.js";function qe({computerLabs:h,onRefresh:y,lang:n,classes:b}){const[q,w]=l.useState(!1),[R,V]=l.useState(null),[G,H]=l.useState(""),[N,K]=l.useState(5),[S,Y]=l.useState(6),[O,p]=l.useState(""),[se,ae]=l.useState(!1),ne="openlearn_computer_lab_selected",X="openlearn_computer_lab_selected_class",[A,re]=l.useState(()=>{try{const t=localStorage.getItem(ne);if(t&&h.some(s=>s.id===t))return t}catch{}return h.length>0?h[0].id:null}),o=h.find(t=>t.id===(A||h[0]?.id)),[ve,ie]=l.useState([]),[d,F]=l.useState(()=>{const t=o??h[0];if(!t)return null;const s=b||[];try{const a=localStorage.getItem(X),i=a?s.find(c=>c.id===a):void 0;if(i&&i.lab_id===t.id)return i.id}catch{}const r=s.find(a=>a.lab_id===t.id);return r?r.id:null}),[E,oe]=l.useState([]),[g,C]=l.useState([]),[le,de]=l.useState(!1),[Z,ce]=l.useState(!1),[xe,Q]=l.useState(""),[W,k]=l.useState(!1),[f,v]=l.useState(null),[we,$]=l.useState(null),[x,T]=l.useState(null),[I,L]=l.useState(null);l.useEffect(()=>{b&&b.length>0?ie(b):fetch("/api/classes?pageSize=all").then(t=>t.ok?t.json():{data:[]}).then(t=>{const s=Ae(t);(s.length>0||Array.isArray(t?.data))&&ie(s)}).catch(t=>console.error("Failed to fetch classes in ComputerLabManager:",t))},[b]);const _=b&&b.length>0?b:ve,ue=l.useMemo(()=>o?_.filter(t=>t.lab_id===o.id):[],[_,o]);l.useEffect(()=>{if(!o)return;if(d){_.find(s=>s.id===d);return}try{const s=localStorage.getItem(X),r=s?_.find(a=>a.id===s):void 0;if(r&&r.lab_id===o.id){F(r.id);return}}catch{}const t=_.find(s=>s.lab_id===o.id);t&&F(t.id)},[o?.id,_]),l.useEffect(()=>{try{A&&localStorage.setItem(ne,A)}catch{}},[A]),l.useEffect(()=>{try{d&&localStorage.setItem(X,d)}catch{}},[d]);const me=l.useCallback(async(t,s)=>{de(!0),p(""),v(null),$(null);try{const[r,a]=await Promise.all([fetch(`/api/classes/${t}/seats`),fetch(`/api/classes/${t}/students`)]);let i=[];if(a.ok&&(i=await a.json(),oe(i)),r.ok){const u=(await r.json()).seats||[],D=new Map(i.map(j=>[j.id,j])),U=u.map(j=>{const B=D.get(j.student_id);return{...j,student_name:j.student_name||B?.name||"",student_number:j.student_number||B?.student_number||""}});C(U)}k(!1)}catch(r){console.error("Error loading seating:",r),p(r.message||"Failed to load class seating data")}finally{de(!1)}},[]);l.useEffect(()=>{d?me(d,o?.id):(oe([]),C([]),k(!1),v(null),$(null))},[d,me]);const ee=l.useMemo(()=>{const t=new Map;for(const s of g)t.set(`${s.row_idx}_${s.col_idx}`,s);return t},[g]),he=l.useMemo(()=>new Set(g.map(t=>t.student_id)),[g]),z=l.useMemo(()=>E.filter(t=>!he.has(t.id)).sort((t,s)=>String(t.student_number??t.name??"").localeCompare(String(s.student_number??s.name??""),"zh-Hans-CN",{numeric:!0})),[E,he]),pe=(t,s,r)=>{const a=g.find(u=>u.row_idx===s.row&&u.col_idx===s.col),i=!!r&&!!a&&a.student_id!==t.id;let c=g.filter(u=>!(u.row_idx===s.row&&u.col_idx===s.col)&&u.student_id!==t.id);i&&r&&(c=c.filter(u=>!(u.row_idx===r.row&&u.col_idx===r.col)),c=[...c,{...a,row_idx:r.row,col_idx:r.col}]),c=[...c,{student_id:t.id,row_idx:s.row,col_idx:s.col,student_name:t.name,student_number:t.student_number}],C(c),k(!0)},Ne=(t,s)=>{if(!d||!o)return;const r=ee.get(`${t}_${s}`);if(f){pe(f,{row:t,col:s},we),v(null),$(null);return}if(r){const a=E.find(i=>i.id===r.student_id)||{id:r.student_id,name:r.student_name||"Student",student_number:r.student_number||""};v(a),$({row:t,col:s})}},Se=(t,s,r)=>{t.stopPropagation(),C(a=>a.filter(i=>!(i.row_idx===s&&i.col_idx===r))),k(!0)},_e=()=>{if(!o||z.length===0)return;const t=[];for(let a=0;a<o.rows;a++)for(let i=0;i<o.cols;i++)ee.has(`${a}_${i}`)||t.push([a,i]);if(t.length===0)return;const r=z.slice(0,t.length).map((a,i)=>{const[c,u]=t[i];return{student_id:a.id,row_idx:c,col_idx:u,student_name:a.name,student_number:a.student_number}});C(a=>[...a,...r]),k(!0),v(null)},ze=()=>{g.length!==0&&confirm(n==="zh"?"确定要清空当前班级在该机房的所有机位排座吗?":"Are you sure you want to clear all seat assignments for this class in this lab?")&&(C([]),k(!0),v(null))},Ce=async()=>{if(!(!d||!o))try{ce(!0),p(""),Q("");const t=await fetch(`/api/classes/${d}/seats`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({lab_id:o.id,seats:g.map(s=>({student_id:s.student_id,row_idx:s.row_idx,col_idx:s.col_idx}))})});if(t.ok)k(!1),Q(n==="zh"?"统一机房排座已成功持久化至系统 student_seats 数据表!":"Seating assignments saved to system student_seats table successfully!"),y(),setTimeout(()=>Q(""),4e3);else{const s=await t.json();p(s.error||"Failed to save seats")}}catch(t){p(t.message||"Network error saving seats")}finally{ce(!1)}},ke=()=>{H(""),K(5),Y(6),p(""),V(null),w(!0)},$e=t=>{H(t.room_number),K(t.rows),Y(t.cols),p(""),V(t.id),w(!0)},Ee=async t=>{if(t.preventDefault(),!G.trim()){p(n==="zh"?"请输入机房编号/名称":"Please enter room name or number");return}if(N<=0||N>15){p(n==="zh"?"行数应在 1 至 15 之间":"Rows should be between 1 and 15");return}if(S<=0||S>15){p(n==="zh"?"列数应在 1 至 15 之间":"Columns should be between 1 and 15");return}try{ae(!0),p("");const s=R?`/api/labs/${R}`:"/api/labs",a=await fetch(s,{method:R?"PUT":"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({room_number:G.trim(),rows:N,cols:S})});if(a.ok)y(),w(!1),V(null);else{const i=await a.json();p(i.error||"Server error saving lab")}}catch(s){p(s.message||"Network error")}finally{ae(!1)}},De=async t=>{if(confirm(n==="zh"?"确定要删除该机房吗?删除后对应的座位分配也会被重置。":"Are you sure you want to delete this lab? Seat mappings will be cleared."))try{(await fetch(`/api/labs/${t}`,{method:"DELETE"})).ok&&(y(),A===t&&re(null))}catch(s){console.error(s)}};return e.jsxs("div",{className:"flex-1 flex flex-col h-full overflow-hidden",id:"lab_manager_root",children:[e.jsxs("div",{className:"flex flex-col md:flex-row md:items-center justify-between pb-4 border-b border-gray-200 gap-4 shrink-0",children:[e.jsxs("div",{children:[e.jsxs("h2",{className:"text-xl font-bold text-gray-800 flex items-center gap-2",children:[e.jsx(fe,{className:"text-indigo-600",size:24}),n==="zh"?"统一机房座位管理":"Unified Computer Lab Seating"]}),e.jsx("p",{className:"text-xs text-gray-500 mt-1",children:n==="zh"?"维护物理机房规格及其行列排列规则,并直接根据班级信息读取并编排系统中已存的统一机房座位数据(student_seats 表)。":"Maintain physical lab layouts and manage unified student seat allocations directly synced with existing system data tables."})]}),e.jsxs("div",{className:"flex items-center gap-2 flex-wrap",children:[e.jsxs("button",{onClick:ke,className:"bg-indigo-600 hover:bg-indigo-700 text-white px-4 py-2 rounded-xl text-xs font-semibold flex items-center gap-1.5 shadow-sm transition-all focus:ring-2 focus:ring-indigo-500 cursor-pointer",children:[e.jsx(Le,{size:14}),n==="zh"?"新增机房规则":"New Lab Config"]}),e.jsx(M,{slot:"computer_lab.toolbar.action",slotProps:{activeLab:o,computerLabs:h,onRefresh:y,lang:n}})]})]}),e.jsxs("div",{className:"flex-1 min-h-0 grid grid-cols-1 lg:grid-cols-12 gap-6 pt-5 overflow-hidden",children:[e.jsxs("div",{className:"lg:col-span-4 flex flex-col min-h-0 bg-white border border-gray-200 rounded-2xl shadow-xs overflow-hidden",children:[e.jsxs("div",{className:"p-4 border-b border-gray-100 bg-gray-50 flex justify-between items-center shrink-0",children:[e.jsx("span",{className:"text-xs font-bold text-gray-700 uppercase tracking-wider block",children:n==="zh"?"机房列表":"Computer Labs"}),e.jsx("span",{className:"bg-gray-200 text-gray-700 font-bold px-2 py-0.5 rounded-full text-xs",children:h.length})]}),e.jsx("div",{className:"flex-1 overflow-y-auto p-4 space-y-3",children:h.length===0?e.jsxs("div",{className:"flex flex-col items-center justify-center p-8 text-center text-gray-400 h-full",children:[e.jsx(fe,{size:40,className:"mb-2 opacity-30"}),e.jsx("p",{className:"text-xs font-medium",children:n==="zh"?"暂无机房编号规则":"No Computer Labs configured yet."}),e.jsx("p",{className:"text-xs text-gray-500 mt-1",children:n==="zh"?"点击右上角按钮添加首个机房规格。":"Click the button above to add your first computer room."})]}):h.map(t=>{const s=o?.id===t.id,r=_.filter(a=>a.lab_id===t.id);return e.jsxs("div",{onClick:()=>{re(t.id),w(!1)},className:`p-3 rounded-xl border transition-all cursor-pointer flex flex-col gap-2 group ${s?"border-indigo-300 bg-indigo-50/60 shadow-xs ring-1 ring-indigo-200":"border-gray-150 hover:border-gray-300 hover:bg-gray-50"}`,children:[e.jsxs("div",{className:"flex items-center justify-between",children:[e.jsxs("div",{className:"flex items-center gap-3",children:[e.jsx("div",{className:`p-2 rounded-lg ${s?"bg-indigo-600 text-white":"bg-gray-100 text-gray-500 group-hover:bg-gray-200"}`,children:e.jsx(J,{size:16})}),e.jsxs("div",{children:[e.jsx("div",{className:"text-sm font-bold text-gray-800 flex items-center gap-1.5",children:t.room_number}),e.jsxs("div",{className:"text-xs text-gray-400 font-mono mt-0.5",children:[t.rows," ",n==="zh"?"行":"Rows"," × ",t.cols," ",n==="zh"?"列":"Cols",e.jsx("span",{className:"mx-1.5",children:"•"}),t.rows*t.cols," ",n==="zh"?"个机位":"Seats"]})]})]}),e.jsxs("div",{className:"flex items-center gap-1.5 opacity-0 group-hover:opacity-100 focus-within:opacity-100 transition-opacity",children:[e.jsx("button",{onClick:a=>{a.stopPropagation(),$e(t)},className:"p-1 text-gray-500 hover:text-indigo-600 hover:bg-white rounded transition-colors",title:n==="zh"?"编辑规则":"Edit Rule",children:e.jsx(Pe,{size:13})}),e.jsx("button",{onClick:a=>{a.stopPropagation(),De(t.id)},className:"p-1 text-gray-500 hover:text-rose-600 hover:bg-white rounded transition-colors",title:n==="zh"?"删除机房间":"Delete Lab",children:e.jsx(Me,{size:14})})]})]}),r.length>0&&e.jsxs("div",{className:"flex items-center gap-1 flex-wrap pt-1 border-t border-gray-100/80",children:[e.jsx("span",{className:"text-[10px] text-gray-400 font-medium",children:n==="zh"?"绑定班级:":"Bound:"}),r.map(a=>e.jsx("span",{className:"text-[10px] font-semibold bg-indigo-100/70 text-indigo-700 px-1.5 py-0.5 rounded-md",children:a.name},a.id))]})]},t.id)})})]}),e.jsx("div",{className:"lg:col-span-8 flex flex-col min-h-0 bg-white border border-gray-200 rounded-2xl shadow-xs overflow-hidden",children:q?e.jsxs("form",{onSubmit:Ee,className:"flex-1 flex flex-col min-h-0",children:[e.jsxs("div",{className:"p-4 border-b border-gray-100 bg-gray-50 font-bold text-xs text-gray-700 uppercase tracking-wider shrink-0 flex items-center justify-between",children:[e.jsx("span",{children:R?n==="zh"?"编辑机房规则":"Edit Lab Seating":n==="zh"?"创建全新上机机房":"Create Computer Lab"}),e.jsx("button",{type:"button",onClick:()=>w(!1),className:"text-gray-400 hover:text-gray-600",children:e.jsx(te,{size:16})})]}),e.jsxs("div",{className:"flex-1 overflow-y-auto p-6 space-y-4",children:[O&&e.jsxs("div",{className:"p-3 bg-red-50 border border-red-200 text-red-700 text-xs rounded-xl flex items-center gap-1.5",children:[e.jsx(Re,{size:14}),e.jsx("span",{children:O})]}),e.jsxs("div",{className:"space-y-1",children:[e.jsx("label",{className:"block text-xs font-bold text-gray-500 uppercase tracking-wide",children:n==="zh"?"机房编号 / 场所名称 *":"Lab Name / Number *"}),e.jsx("input",{type:"text",value:G,onChange:t=>H(t.target.value),placeholder:n==="zh"?"例如:305综合机房、软创中心、Lab A":"e.g. Lab 404, Software Sandbox, Suite C",className:"w-full text-xs p-2.5 border border-gray-300 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 rounded-xl mt-1.5",maxLength:32,required:!0})]}),e.jsxs("div",{className:"grid grid-cols-2 gap-4",children:[e.jsxs("div",{className:"space-y-1",children:[e.jsx("label",{className:"block text-xs font-bold text-gray-500 uppercase tracking-wide",children:n==="zh"?"排列行数 (Rows) *":"Rows Direction *"}),e.jsx("input",{type:"number",value:N,onChange:t=>K(Math.max(1,parseInt(t.target.value)||1)),min:1,max:15,className:"w-full text-xs p-2.5 border border-gray-300 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 rounded-xl mt-1.5",required:!0}),e.jsx("span",{className:"text-xs text-gray-400 block mt-0.5",children:n==="zh"?"上下方向机位数 (最大15行)":"Horizontal sets count (Max 15)"})]}),e.jsxs("div",{className:"space-y-1",children:[e.jsx("label",{className:"block text-xs font-bold text-gray-500 uppercase tracking-wide",children:n==="zh"?"排列列数 (Columns) *":"Columns Direction *"}),e.jsx("input",{type:"number",value:S,onChange:t=>Y(Math.max(1,parseInt(t.target.value)||1)),min:1,max:15,className:"w-full text-xs p-2.5 border border-gray-300 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 rounded-xl mt-1.5",required:!0}),e.jsx("span",{className:"text-xs text-gray-400 block mt-0.5",children:n==="zh"?"左右方向机位数 (最大15列)":"Vertical sets count (Max 15)"})]})]}),e.jsxs("div",{className:"border border-dashed border-gray-200 bg-gray-50/50 p-4 rounded-xl",children:[e.jsx("span",{className:"text-xs font-bold text-gray-400 uppercase tracking-wider block mb-2",children:n==="zh"?"布局网格缩略结构":"Layout Aspect Schema"}),e.jsxs("div",{className:"flex flex-col gap-1 items-center justify-center p-3 bg-white rounded-lg border border-gray-100 min-h-[120px]",children:[e.jsxs("div",{className:"flex flex-col gap-1",children:[Array.from({length:Math.min(N,6)}).map((t,s)=>e.jsxs("div",{className:"flex gap-1 justify-center",children:[Array.from({length:Math.min(S,8)}).map((r,a)=>e.jsx("div",{className:"w-4 h-4 rounded bg-indigo-100 border border-indigo-200 shrink-0"},a)),S>8&&e.jsx("div",{className:"w-4 text-xs text-gray-400 flex items-center justify-center",children:"..."})]},s)),N>6&&e.jsx("div",{className:"text-xs text-center text-gray-400 mt-1",children:"..."})]}),e.jsxs("span",{className:"text-xs text-gray-450 mt-3 font-medium",children:["配置容量:",N," × ",S," = ",N*S," 座位"]})]})]})]}),e.jsxs("div",{className:"p-4 border-t border-gray-100 shrink-0 flex items-center justify-end gap-2 bg-gray-50",children:[e.jsx("button",{type:"button",onClick:()=>w(!1),className:"px-4 py-2 text-xs font-semibold text-gray-500 hover:text-gray-700 bg-white hover:bg-gray-100 border border-gray-200 rounded-xl transition-all cursor-pointer",children:n==="zh"?"取消":"Cancel"}),e.jsxs("button",{type:"submit",disabled:se,className:"px-4 py-2 text-xs font-bold text-white bg-indigo-600 hover:bg-indigo-700 disabled:opacity-50 rounded-xl flex items-center gap-1 shadow-sm transition-all cursor-pointer",children:[e.jsx(be,{size:14}),e.jsx("span",{children:se?n==="zh"?"提交中...":"Saving...":n==="zh"?"提交保存":"Save Config"})]})]})]}):e.jsxs("div",{className:"flex-1 flex flex-col min-h-0",children:[e.jsxs("div",{className:"p-3 border-b border-gray-100 bg-gray-50 flex flex-wrap items-center justify-between gap-3 shrink-0",children:[e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsxs("span",{className:"text-xs font-bold text-gray-700 uppercase tracking-wider flex items-center gap-1.5",children:[e.jsx(je,{size:14,className:"text-indigo-600"}),n==="zh"?"班级机位联动:":"Class Seating Link:"]}),e.jsxs("select",{value:d||"",onChange:t=>F(t.target.value?t.target.value:null),className:"text-xs font-semibold bg-white border border-gray-300 rounded-lg px-2.5 py-1.5 text-gray-700 focus:outline-none focus:ring-1 focus:ring-indigo-500 cursor-pointer shadow-2xs",children:[e.jsx("option",{value:"",children:n==="zh"?"📐 物理规格总览 (无班级)":"📐 Physical Blueprint (No Class)"}),_.map(t=>{const s=t.lab_id===o?.id;return e.jsxs("option",{value:t.id,children:[t.name," ",s?n==="zh"?"★ 已绑定此机房":"★ Bound":""]},t.id)})]})]}),d&&o&&e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsxs("button",{type:"button",onClick:_e,disabled:z.length===0,className:"px-2.5 py-1.5 text-xs font-semibold bg-indigo-50 hover:bg-indigo-100 text-indigo-700 border border-indigo-200 rounded-lg flex items-center gap-1 transition-colors cursor-pointer disabled:opacity-40 disabled:cursor-not-allowed",title:n==="zh"?"将未排座学生按学号依次填入空闲机位":"Auto assign unplaced students",children:[e.jsx(Oe,{size:13}),e.jsx("span",{children:n==="zh"?"一键排座":"Auto Fill"})]}),e.jsxs("button",{type:"button",onClick:ze,disabled:g.length===0,className:"px-2.5 py-1.5 text-xs font-semibold bg-gray-100 hover:bg-rose-50 text-gray-600 hover:text-rose-600 border border-gray-200 rounded-lg flex items-center gap-1 transition-colors cursor-pointer disabled:opacity-40 disabled:cursor-not-allowed",title:n==="zh"?"清空当前班级排座":"Clear Seating",children:[e.jsx(Fe,{size:13}),e.jsx("span",{children:n==="zh"?"清空":"Clear"})]}),e.jsxs("button",{type:"button",onClick:Ce,disabled:Z||!W&&!o,className:`px-3 py-1.5 text-xs font-bold rounded-lg flex items-center gap-1.5 shadow-xs transition-all cursor-pointer ${W?"bg-emerald-600 hover:bg-emerald-700 text-white animate-pulse":"bg-indigo-600 hover:bg-indigo-700 text-white disabled:opacity-60 disabled:cursor-not-allowed"}`,children:[Z?e.jsx(ye,{size:13,className:"animate-spin"}):e.jsx(Ue,{size:13}),e.jsx("span",{children:Z?n==="zh"?"保存中...":"Saving...":W?n==="zh"?"保存排座 (已修改)":"Save (Modified)":n==="zh"?"已保存":"Saved"})]}),e.jsx(M,{slot:"computer_lab.seating.toolbar",slotProps:{labId:o.id,classId:d,assignedSeats:g,setAssignedSeats:C,lang:n}})]})]}),xe&&e.jsxs("div",{className:"bg-emerald-50 border-b border-emerald-200 text-emerald-800 text-xs px-4 py-2 flex items-center gap-1.5 animate-fadeIn",children:[e.jsx(be,{size:14,className:"text-emerald-600 shrink-0"}),e.jsx("span",{className:"font-medium",children:xe})]}),O&&e.jsxs("div",{className:"bg-red-50 border-b border-red-200 text-red-700 text-xs px-4 py-2 flex items-center gap-1.5",children:[e.jsx(Be,{size:14,className:"text-red-500 shrink-0"}),e.jsx("span",{children:O})]}),o?e.jsxs("div",{className:"flex-1 flex flex-col min-h-0 bg-slate-950 text-white overflow-hidden relative",children:[e.jsx("div",{className:"py-2.5 flex justify-center shrink-0 border-b border-slate-900 bg-slate-950/80 backdrop-blur-xs z-10",children:e.jsx("div",{className:"w-56 bg-slate-900 border border-slate-700 py-1 rounded text-center text-xs font-bold text-slate-300 uppercase tracking-widest select-none shadow-sm flex items-center justify-center gap-1.5",children:e.jsxs("span",{children:["📽️ ",n==="zh"?"讲台 / 教师主荧幕":"Classroom Stage / Screen"]})})}),e.jsx("div",{className:"flex-1 overflow-auto p-4 flex flex-col items-center",children:le?e.jsxs("div",{className:"flex flex-col items-center justify-center h-48 gap-2 text-slate-400",children:[e.jsx(ye,{size:24,className:"animate-spin text-indigo-400"}),e.jsx("span",{className:"text-xs",children:n==="zh"?"正在读取统一机房座位数据...":"Loading lab seating data..."})]}):e.jsx("div",{className:"flex flex-col gap-2.5 py-2 max-w-full",children:Array.from({length:o.rows}).map((t,s)=>e.jsxs("div",{className:"flex gap-2.5 justify-center items-center",children:[e.jsxs("span",{className:"text-xs font-bold font-mono text-slate-500 w-6 text-right select-none pr-0.5",children:["R",s+1]}),Array.from({length:o.cols}).map((r,a)=>{const i=ee.get(`${s}_${a}`),c=!!i,u=f&&(!c||i.student_id!==f.id),D=!!x?.from&&x.from.row===s&&x.from.col===a,U=!!x&&!!I&&I.row===s&&I.col===a&&!D,j=U&&c&&x?.type==="seat"&&i.student_id!==x.studentId,B=U&&(!c||x?.type==="pool");return e.jsx("div",{"data-seat-id":`${s}_${a}`,onClick:()=>Ne(s,a),draggable:!!d&&c,onDragStart:m=>{!d||!i||(T({type:"seat",studentId:i.student_id,from:{row:s,col:a}}),m.dataTransfer.setData("text/plain",JSON.stringify({type:"seat",studentId:i.student_id,from:{row:s,col:a}})),m.dataTransfer.effectAllowed="move")},onDragEnd:()=>{T(null),L(null)},onDragOver:m=>{!x||D||(m.preventDefault(),m.dataTransfer.dropEffect="move",L({row:s,col:a}))},onDragLeave:()=>{L(m=>m&&m.row===s&&m.col===a?null:m)},onDrop:m=>{if(m.preventDefault(),x&&!D){const ge=x.type==="pool"?z.find(P=>P.id===x.studentId)||E.find(P=>P.id===x.studentId):E.find(P=>P.id===x.studentId);ge&&(pe(ge,{row:s,col:a},x.from),v(null),$(null))}T(null),L(null)},className:`group relative rounded-xl flex flex-col items-center justify-center transition-all select-none shadow-sm ${d&&c?"cursor-grab":"cursor-pointer"} ${d?"w-20 h-14":"w-11 h-11"} ${c?"bg-slate-850 border border-indigo-500/70 hover:border-indigo-400 shadow-indigo-950/50":u?"bg-indigo-950/40 border border-dashed border-indigo-400 hover:bg-indigo-900/50 ring-1 ring-indigo-400/50 animate-pulse":"bg-slate-900 border border-slate-800 hover:border-slate-700 hover:bg-slate-850"}${j?" ring-2 ring-amber-400 border-amber-400 bg-amber-950/40":""}${B&&!j?" ring-2 ring-emerald-400 border-emerald-400 bg-emerald-950/40":""}${D?" opacity-40":""}`,children:c?e.jsxs(e.Fragment,{children:[e.jsxs("div",{className:"w-full h-full p-1.5 flex flex-col justify-between items-center text-center",children:[e.jsxs("div",{className:"flex items-center justify-between w-full px-0.5",children:[e.jsxs("span",{className:"text-[10px] font-mono text-indigo-300/80",children:[s+1,"-",a+1]}),d&&e.jsx("button",{type:"button",onClick:m=>Se(m,s,a),className:"opacity-0 group-hover:opacity-100 text-slate-400 hover:text-rose-400 p-0.5 rounded transition-opacity",title:n==="zh"?"移出此座位":"Unseat student",children:e.jsx(te,{size:11})})]}),e.jsx("div",{className:"font-bold text-xs text-white truncate max-w-[70px] leading-tight",children:i.student_name||"Student"}),e.jsx("div",{className:"text-[9px] font-mono text-slate-400 truncate max-w-[70px]",children:i.student_number||`ID:${i.student_id.slice(-4)}`}),e.jsx("div",{className:"w-full min-w-0",onClick:m=>m.stopPropagation(),children:e.jsx(M,{slot:"computer_lab.seat.overlay",slotProps:{labId:o.id,row:s,col:a,seat:i,isOccupied:!0}})})]}),e.jsxs("div",{className:"absolute bottom-16 bg-slate-900 text-white text-xs rounded-lg px-2.5 py-1.5 hidden group-hover:block whitespace-nowrap z-30 font-sans border border-indigo-700 shadow-2xl pointer-events-none",children:[e.jsx("div",{className:"font-bold text-indigo-300",children:i.student_name}),e.jsxs("div",{className:"text-[10px] text-slate-400 font-mono",children:[n==="zh"?"学号:":"No: ",i.student_number||i.student_id]}),e.jsx("div",{className:"text-[10px] text-slate-300 mt-0.5",children:n==="zh"?`第 ${s+1} 排,第 ${a+1} 列`:`Row ${s+1}, Col ${a+1}`})]})]}):e.jsxs(e.Fragment,{children:[e.jsx(J,{size:d?14:15,className:u?"text-indigo-400 animate-bounce":"text-slate-600 opacity-60"}),e.jsxs("span",{className:"text-[10px] font-mono text-slate-500 mt-0.5",children:[s+1,"-",a+1]}),e.jsx("div",{className:"absolute bottom-12 bg-slate-900 text-white text-xs rounded px-2 py-1 hidden group-hover:block whitespace-nowrap z-30 font-sans border border-slate-700 shadow-xl pointer-events-none",children:f?n==="zh"?`点击安排 [${f.name}] 到第 ${s+1} 排第 ${a+1} 列`:`Place [${f.name}] here`:n==="zh"?`第 ${s+1} 排第 ${a+1} 列 (空闲)`:`Row ${s+1} / Column ${a+1} (Empty)`})]})},a)})]},s))})}),d&&o&&!le&&e.jsx("div",{className:"shrink-0 px-4 pb-2",children:e.jsx(M,{slot:"computer_lab.seating.sidebar",slotProps:{labId:o.id,classId:d,assignedSeats:g,unassignedStudents:z}})}),d?e.jsxs("div",{className:"border-t border-slate-900 bg-slate-900/90 p-3 shrink-0 flex flex-col gap-2",children:[e.jsxs("div",{className:"flex items-center justify-between text-xs",children:[e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsxs("span",{className:"font-bold text-slate-300 flex items-center gap-1.5",children:[e.jsx(je,{size:13,className:"text-indigo-400"}),n==="zh"?"待排座学生池:":"Unassigned Students Pool:"]}),e.jsxs("span",{className:"text-[11px] font-mono text-slate-400",children:[z.length," ",n==="zh"?"人未排":"unplaced"," / ",E.length," ",n==="zh"?"人总计":"total"]}),f&&e.jsxs("span",{className:"bg-indigo-900/80 text-indigo-300 border border-indigo-700 text-[11px] px-2 py-0.5 rounded-full flex items-center gap-1 animate-pulse",children:[e.jsx("span",{children:n==="zh"?`当前选择: ${f.name} (点击上方空位落座)`:`Selected: ${f.name} (click empty seat)`}),e.jsx("button",{type:"button",onClick:()=>{v(null),$(null)},className:"hover:text-white",children:e.jsx(te,{size:10})})]})]}),e.jsxs("div",{className:"text-[11px] font-mono text-slate-400",children:[n==="zh"?"机房已占:":"Occupied: ",e.jsx("span",{className:"text-indigo-400 font-bold",children:g.length})," /"," ",o.rows*o.cols]})]}),z.length===0?e.jsxs("div",{className:"text-xs text-slate-500 py-1 italic flex items-center gap-1",children:[e.jsx(Je,{size:13,className:"text-emerald-400"}),e.jsx("span",{children:n==="zh"?"当前班级全部学生均已成功分配机位!":"All students in this class have been assigned seats!"})]}):e.jsx("div",{className:"flex items-center gap-1.5 overflow-x-auto py-1 max-h-16 pr-2",children:z.map(t=>{const s=f?.id===t.id,r=x?.type==="pool"&&x.studentId===t.id;return e.jsxs("div",{className:"shrink-0 flex items-center gap-1","data-unassigned-id":t.id,children:[e.jsxs("button",{type:"button",draggable:!0,onDragStart:a=>{T({type:"pool",studentId:t.id}),a.dataTransfer.setData("text/plain",JSON.stringify({type:"pool",studentId:t.id})),a.dataTransfer.effectAllowed="move"},onDragEnd:()=>{T(null),L(null)},onClick:()=>{v(s?null:t),$(null)},className:`px-2.5 py-1 rounded-lg text-xs font-medium border transition-all cursor-grab active:cursor-grabbing flex items-center gap-1.5 ${s?"bg-indigo-600 text-white border-indigo-400 ring-2 ring-indigo-400/50 shadow-md":"bg-slate-800 text-slate-300 border-slate-700 hover:border-indigo-500 hover:bg-slate-750"}${r?" opacity-40":""}`,children:[e.jsx("span",{children:t.name}),t.student_number&&e.jsx("span",{className:"text-[10px] font-mono text-slate-400",children:t.student_number})]}),e.jsx(M,{slot:"computer_lab.unassigned.item",slotProps:{student:t,classId:d,labId:o?.id}})]},t.id)})})]}):e.jsxs("div",{className:"border-t border-slate-900 bg-slate-900/60 p-3 shrink-0 flex items-center justify-between text-xs text-slate-400 select-none",children:[e.jsxs("div",{className:"flex items-center gap-3",children:[e.jsxs("div",{className:"flex items-center gap-1.5",children:[e.jsx("div",{className:"w-3.5 h-3.5 rounded bg-slate-800 border border-slate-700 flex items-center justify-center p-0.5",children:e.jsx(J,{size:10,className:"text-indigo-400 opacity-60"})}),e.jsx("span",{children:n==="zh"?"标准上机终端":"Console Terminals"})]}),ue.length>0&&e.jsxs("div",{className:"flex items-center gap-1",children:[e.jsx("span",{className:"text-slate-500",children:n==="zh"?"已绑定班级:":"Bound Classes:"}),ue.map(t=>e.jsx("button",{type:"button",onClick:()=>F(t.id),className:"text-indigo-400 hover:text-indigo-300 underline font-semibold text-xs cursor-pointer ml-1",children:t.name},t.id))]})]}),e.jsxs("div",{className:"font-mono text-slate-500",children:[n==="zh"?"总行数:":"Rows: ",e.jsx("span",{className:"text-indigo-400 font-bold",children:o.rows})," |",n==="zh"?" 总列数:":" Columns: ",e.jsx("span",{className:"text-indigo-400 font-bold",children:o.cols})," |",n==="zh"?" 配置席位:":" Total: ",e.jsx("span",{className:"text-white font-bold",children:o.rows*o.cols})]})]})]}):e.jsxs("div",{className:"flex-1 flex flex-col items-center justify-center text-center p-8 text-gray-400 h-full",children:[e.jsx(J,{size:48,className:"opacity-20 mb-3"}),e.jsx("p",{className:"text-xs font-medium",children:n==="zh"?"请选择一个机房以预览其座位图":"Please select a lab config to preview details."})]})]})})]})]})}function Qe(h={}){const y=Te(),n=h.computerLabs??y?.computerLabs??[],b=h.onRefresh??y?.fetchLabs??(async()=>{}),q=h.lang??y?.lang??"zh",w=h.classes??y?.classes??[];return e.jsx(qe,{computerLabs:n,onRefresh:b,lang:q,classes:w})}export{Qe as ComputerLabView};
@@ -1 +0,0 @@
1
- import{r as i,j as e}from"./vendor-react-Kv_JATwr.js";import{b as ee,E as y}from"./index-DN3E-Fmw.js";import{B as j,a8 as se,aW as te,ak as _,a$ as D,X as re,Z as ae,U as T,bX as ne,bY as ie,t as oe,u as le,af as de,ac as I,T as ce,c as O}from"./vendor-icons-hc6MjYFv.js";import"./vendor-utils-aM7aYvPP.js";import"./vendor-pdf-By0fofA0.js";import"./vendor-motion-BFAPOs-b.js";import"./vendor-charts-SrpBsvM3.js";function xe(s){if(typeof s!="string")return"";let a=s;return a=a.replace(/<(think|thinking|reasoning|analysis)(?:\s+[^>]*)?>[\s\S]*?<\/\1>/gi,""),a=a.replace(/<(think|thinking|reasoning|analysis)(?:\s+[^>]*)?>[\s\S]*$/gi,""),a=a.replace(/```(?:think|thinking|reasoning|analysis)[\s\S]*?```/gi,""),a=a.replace(/^```(?:json|markdown|md)?\s*/i,"").replace(/```\s*$/i,""),a.trim()}function me(s,a=160){if(!s||typeof s!="string")return"";let r=xe(s);return r?(r=r.replace(/```[\s\S]*?```/g," "),r=r.replace(/!\[.*?\]\(.*?\)/g,""),r=r.replace(/\[(.*?)\]\(.*?\)/g,"$1"),r=r.replace(/^#+\s+/gm,""),r=r.replace(/^\s*[-*+]\s+/gm,""),r=r.replace(/^\s*\d+\.\s+/gm,""),r=r.replace(/^\s*>\s+/gm,""),r=r.replace(/\*\*([^*]+)\*\*/g,"$1"),r=r.replace(/\*([^*]+)\*/g,"$1"),r=r.replace(/__([^_]+)__/g,"$1"),r=r.replace(/_([^_]+)_/g,"$1"),r=r.replace(/~~(.*?)~~/g,"$1"),r=r.replace(/`([^`]+)`/g,"$1"),r=r.replace(/\s+/g," ").trim(),r.length<=a?r:r.slice(0,a).trimEnd()+"..."):""}function ve(){const{lang:s,session:a,lessons:r,lessonsSearchQuery:g,setLessonsSearchQuery:h,lessonsSortOrder:E,setLessonsSortOrder:A,filteredAndSortedLessons:u,isImportLessonsOpen:ge,setIsImportLessonsOpen:P,isCourseWizardOpen:he,setIsCourseWizardOpen:F,onViewCourse:N,onDeleteCourse:M,onCopyCourse:R,filterEnrollment:v,setFilterEnrollment:W,filterHasContent:C,setFilterHasContent:U,filterThisMonth:w,setFilterThisMonth:X,copyingLessonId:B,fetchLessons:H,downloadCourseTemplate:Q}=ee(),V=()=>P(!0),z=()=>F(!0),n=a,Z=n?.username==="admin"||n?.userId==="usr_admin"||n?.role==="administrator"||n?.subRole==="administrator",[p,J]=i.useState(!1),[o,b]=i.useState(null),[l,d]=i.useState(null),[k,S]=i.useState(!1),[Y,L]=i.useState(!1),[x,$]=i.useState(null),q=t=>{navigator.clipboard.writeText(t).then(()=>{$(t),setTimeout(()=>$(null),1500)})},G=async t=>{b(t),d(null),L(!0);try{const m=await fetch(`/api/lessons/${t.id}/stats`);if(m.ok){const c=await m.json();d(c)}}catch{d({whiteboardCount:0,scheduleCount:0,enrollmentCount:0,assignmentCount:0})}finally{L(!1)}},K=async()=>{if(o){S(!0);try{await M(o.id),b(null),d(null)}finally{S(!1)}}};return e.jsxs("div",{className:"flex-1 flex flex-col gap-6 h-full overflow-y-auto",children:[e.jsxs("div",{className:"flex-1 bg-white border border-gray-200 rounded-xl shadow-sm flex flex-col min-h-0",children:[e.jsxs("div",{className:"p-4 border-b border-gray-100 flex items-center justify-between shrink-0",children:[e.jsxs("h3",{className:"font-medium text-gray-700 flex items-center gap-2",children:[e.jsx(j,{size:16,className:"text-gray-400"}),s==="zh"?"课程与教学环节管理 (SQLite)":"Courses & Lessons Management"]}),e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsxs("button",{id:"import-lessons-csv-btn",onClick:V,className:"flex items-center gap-1.5 px-3 py-1.5 text-xs bg-indigo-50 hover:bg-indigo-100 text-indigo-700 border border-indigo-200 font-semibold rounded-lg shadow-3xs transition-all hover:shadow-xs hover:-translate-y-0.5 cursor-pointer",children:[e.jsx(se,{size:14}),s==="zh"?"批量导入课程":"Import Lessons"]}),e.jsxs("button",{id:"download-course-template-btn",onClick:()=>void Q(),title:s==="zh"?"下载自包含的课程模板(教学环节 + 全部预设组件 + PDF/PPTX 课件附件)":"Download a self-contained course template (segments + every preset component + PDF/PPTX assets)",className:"flex items-center gap-1.5 px-3 py-1.5 text-xs bg-emerald-50 hover:bg-emerald-100 text-emerald-700 border border-emerald-200 font-semibold rounded-lg shadow-3xs transition-all hover:shadow-xs hover:-translate-y-0.5 cursor-pointer",children:[e.jsx(te,{size:14}),s==="zh"?"下载课程模板":"Course Template"]}),e.jsxs("button",{id:"add-course-wizard-btn",onClick:z,className:"flex items-center gap-1.5 px-3 py-1.5 text-xs bg-indigo-600 hover:bg-indigo-700 text-white font-semibold rounded-lg shadow-sm transition-all hover:shadow-md hover:-translate-y-0.5 cursor-pointer",children:[e.jsx(_,{size:14}),s==="zh"?"手动添加课程 (向导)":"Add Course Wizard"]}),e.jsx(y,{slot:"course.list.toolbar.action",slotProps:{lang:s,onRefresh:H}})]})]}),r.length>0&&e.jsxs("div",{className:"px-5 py-3.5 border-b border-gray-100 bg-gray-50/50 flex flex-col sm:flex-row items-center justify-between gap-3 shrink-0",children:[e.jsxs("div",{className:"flex flex-wrap items-center gap-3 w-full sm:w-auto",children:[e.jsxs("div",{className:"relative w-full sm:w-64",children:[e.jsx("span",{className:"absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none text-gray-400",children:e.jsx(D,{size:14})}),e.jsx("input",{type:"text",placeholder:"Search courses by title...",value:g,onChange:t=>h(t.target.value),className:"w-full pl-9 pr-8 py-1.5 text-xs bg-white border border-gray-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 text-gray-800 shadow-sm"}),g&&e.jsx("button",{onClick:()=>h(""),className:"absolute inset-y-0 right-0 flex items-center pr-2.5 text-gray-400 hover:text-gray-600",children:e.jsx(re,{size:12,className:"bg-gray-100 hover:bg-gray-200 rounded-full p-0.5"})})]}),e.jsxs("div",{className:"flex items-center gap-1.5",children:[e.jsx("span",{className:"text-xs font-bold text-subtle uppercase tracking-wider",children:s==="zh"?"排序方式:":"Sort by:"}),e.jsxs("select",{"aria-label":s==="zh"?"课程排序方式":"Course sort order",value:E,onChange:t=>A(t.target.value),className:"bg-white border border-gray-200 text-xs text-gray-755 font-bold px-2.5 py-1.5 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 shadow-sm",id:"courses-sort-select",children:[e.jsx("option",{value:"recent",children:s==="zh"?"最新创建":"Most Recent"}),e.jsx("option",{value:"alphabetical",children:s==="zh"?"按名称 (A-Z)":"Alphabetical (A-Z)"}),e.jsx("option",{value:"enrollment",children:s==="zh"?"学生选课人次":"Student Enrollment Count"})]})]})]}),e.jsxs("div",{className:"flex items-center gap-3 flex-wrap",children:[e.jsxs("div",{className:"flex items-center gap-1.5",children:[e.jsxs("button",{onClick:()=>J(!p),className:`px-2 py-1 text-xs font-bold rounded-md border transition-all cursor-pointer ${p?"bg-indigo-600 text-white border-indigo-600 shadow-2xs":"bg-white text-gray-600 border-gray-200 hover:border-gray-300"}`,title:s==="zh"?"只看我创建的课程":"Only show courses created by me",children:[e.jsx(ae,{size:11,className:"inline mr-0.5 -mt-0.5"}),s==="zh"?"我的备课":"My Courses"]}),e.jsxs("button",{onClick:()=>W(!v),className:`px-2 py-1 text-xs font-bold rounded-md border transition-all cursor-pointer ${v?"bg-emerald-50 text-emerald-700 border-emerald-200":"bg-white text-gray-500 border-gray-200 hover:border-gray-300"}`,children:[e.jsx(T,{size:11,className:"inline mr-0.5 -mt-0.5"}),s==="zh"?"有人选":"Enrolled"]}),e.jsxs("button",{onClick:()=>U(!C),className:`px-2 py-1 text-xs font-bold rounded-md border transition-all cursor-pointer ${C?"bg-indigo-50 text-indigo-700 border-indigo-200":"bg-white text-gray-500 border-gray-200 hover:border-gray-300"}`,children:[e.jsx(j,{size:11,className:"inline mr-0.5 -mt-0.5"}),s==="zh"?"有内容":"Has Content"]}),e.jsx("button",{onClick:()=>X(!w),className:`px-2 py-1 text-xs font-bold rounded-md border transition-all cursor-pointer ${w?"bg-amber-50 text-amber-700 border-amber-200":"bg-white text-gray-500 border-gray-200 hover:border-gray-300"}`,children:s==="zh"?"本月":"This Month"})]}),e.jsxs("div",{className:"text-xs font-semibold text-gray-500",children:["Found ",e.jsx("span",{className:"text-indigo-650 font-bold",children:u.length})," of"," ",e.jsx("span",{className:"text-gray-700 font-bold",children:r.length})," course",r.length===1?"":"s"]})]})]}),e.jsx("div",{className:"flex-1 overflow-y-auto p-4",children:r.length===0?e.jsxs("div",{className:"flex flex-col items-center justify-center h-full text-gray-400 min-h-[300px]",children:[e.jsx(j,{size:48,className:"mb-4 opacity-30 text-indigo-500"}),e.jsx("h3",{className:"text-lg font-bold text-gray-800",children:s==="zh"?"暂无可用课程":"No Courses Available"}),e.jsx("p",{className:"mt-2 text-sm text-gray-500 text-center max-w-xs",children:s==="zh"?"系统中暂未部署任何课程。请通过下方按钮启动添加向导指南。":"There are no courses active in the system yet. Build your first curriculum!"}),e.jsxs("button",{id:"empty-add-course-btn",onClick:z,className:"mt-5 flex items-center gap-2 px-5 py-2.5 bg-indigo-600 hover:bg-indigo-700 text-white font-bold rounded-xl shadow-md transition-all cursor-pointer",children:[e.jsx(_,{size:16}),s==="zh"?"使用向导指南来创建新课程":"Create Course via Wizard"]})]}):u.length===0?e.jsxs("div",{className:"flex flex-col items-center justify-center h-full py-12 text-gray-450 text-center",children:[e.jsx(D,{size:44,className:"mb-3 opacity-30 text-gray-450"}),e.jsxs("h4",{className:"font-semibold text-gray-700 text-sm",children:['No Courses Match "',g,'"']}),e.jsx("p",{className:"text-xs text-gray-500 mt-1 max-w-xs",children:"Double-check the spelling or try searching for another curriculum keyword."}),e.jsx("button",{onClick:()=>h(""),className:"mt-3 text-xs bg-indigo-50 hover:bg-indigo-100 text-indigo-700 border border-indigo-200 font-semibold px-3 py-1.5 rounded-lg transition-colors",children:"Clear Search Filter"})]}):e.jsx("div",{className:"grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4",children:u.filter(t=>p?!t.creator_id||t.creator_id===n?.userId||t.creator_id===n?.username:!0).map(t=>{const m=B===t.id,c=!t.creator_id||t.creator_id===n?.userId||t.creator_id===n?.username,f=Z||c;return e.jsxs("div",{className:`border border-gray-200 hover:border-indigo-300 rounded-xl p-4 flex flex-col bg-gray-50/50 hover:shadow-md transition-all ${m?"opacity-60 pointer-events-none":""}`,children:[e.jsxs("div",{className:"flex items-center justify-between mb-2 gap-2",children:[e.jsx("div",{className:"font-semibold text-gray-800 text-lg truncate cursor-pointer hover:text-indigo-600 transition-colors",title:t.title,onClick:()=>N(t.id),children:t.title}),e.jsxs("div",{className:"flex items-center gap-1.5 shrink-0",children:[e.jsx(y,{slot:"course.card.badge",slotProps:{course:t}}),t.creator_name?e.jsxs("span",{className:`text-xs font-medium px-1.5 py-0.5 rounded border flex items-center gap-0.5 ${c?"bg-indigo-50 text-indigo-700 border-indigo-200 font-semibold":"bg-slate-100 text-slate-600 border-slate-200"}`,title:s==="zh"?`创建教师: ${t.creator_name}`:`Created by: ${t.creator_name}`,children:[c?"👑 ":"👤 ",t.creator_name]}):null,e.jsxs("span",{className:"inline-flex items-center gap-1 bg-indigo-50 text-indigo-700 px-1.5 py-0.5 border border-indigo-100 rounded text-xs font-bold",children:[e.jsx(T,{size:10,className:"text-indigo-500"}),t.enrollment_count||0]})]})]}),e.jsx("div",{className:"text-sm text-gray-500 line-clamp-3 mb-4 flex-1 leading-relaxed",children:me(t.content)||e.jsx("span",{className:"italic text-gray-400",children:s==="zh"?"暂无课程大纲与简介":"No outline or summary available."})}),e.jsxs("div",{className:"flex justify-between items-center mt-auto",children:[e.jsxs("button",{onClick:()=>q(t.id),className:`flex items-center gap-1 text-xs rounded px-1.5 py-0.5 transition-all cursor-pointer ${x===t.id?"text-emerald-700 bg-emerald-50 border border-emerald-200":"text-subtle hover:text-indigo-600 hover:bg-indigo-50 border border-transparent"}`,title:x===t.id?s==="zh"?"已复制!":"Copied!":s==="zh"?"复制课程 ID":"Copy Course ID",children:[x===t.id?e.jsx(ne,{size:12}):e.jsx(ie,{size:12}),e.jsx("span",{className:"font-mono",children:x===t.id?s==="zh"?"已复制!":"Copied!":`${t.id.substring(0,8)}…`})]}),e.jsxs("div",{className:"flex items-center gap-0.5",children:[e.jsx("button",{onClick:()=>N(t.id),className:"p-1.5 text-gray-400 hover:text-indigo-600 hover:bg-indigo-50 rounded transition-colors cursor-pointer",title:f?s==="zh"?"查看与编辑":"View & Edit":s==="zh"?"查看只读 (他人课程)":"View Read-Only",children:f?e.jsx(oe,{size:15}):e.jsx(le,{size:15})}),e.jsx("button",{onClick:()=>R(t.id),className:"p-1.5 text-gray-400 hover:text-emerald-600 hover:bg-emerald-50 rounded transition-colors cursor-pointer",title:s==="zh"?"克隆为我的备课":"Clone Course",children:e.jsx(de,{size:14})}),f?e.jsx("button",{onClick:()=>G(t),className:"p-1.5 text-gray-400 hover:text-red-600 hover:bg-red-50 rounded transition-colors cursor-pointer",title:s==="zh"?"删除课程":"Delete Course",children:e.jsx(I,{size:14})}):e.jsx("span",{className:"p-1.5 text-gray-300 cursor-not-allowed",title:s==="zh"?"仅创建教师或管理员可删除":"Only creator or admin can delete",children:e.jsx(I,{size:14})}),e.jsx(y,{slot:"course.card.action",slotProps:{course:t,lang:s}})]})]})]},t.id)})})})]}),o&&e.jsx("div",{className:"fixed inset-0 bg-black/40 backdrop-blur-[2px] flex items-center justify-center z-[9999] p-4",children:e.jsxs("div",{className:"bg-white rounded-xl shadow-2xl max-w-md w-full border border-gray-100 overflow-hidden",children:[e.jsxs("div",{className:"px-5 py-4 border-b border-gray-100 bg-red-50 flex items-center gap-2",children:[e.jsx(ce,{size:18,className:"text-red-600"}),e.jsx("h3",{className:"font-bold text-red-800 text-sm",children:s==="zh"?"确认删除课程":"Confirm Course Deletion"})]}),e.jsxs("div",{className:"p-5 space-y-3",children:[e.jsx("p",{className:"text-sm text-gray-700",children:s==="zh"?e.jsxs(e.Fragment,{children:["确定要删除课程 ",e.jsx("span",{className:"font-bold text-red-600",children:o.title})," 吗?"]}):e.jsxs(e.Fragment,{children:["Are you sure you want to delete ",e.jsx("span",{className:"font-bold text-red-600",children:o.title}),"?"]})}),e.jsx("p",{className:"text-xs text-red-600 font-medium",children:s==="zh"?"此操作不可撤销,将删除课程的所有关联数据。":"This action is irreversible and will delete all associated data."}),Y?e.jsxs("div",{className:"flex items-center gap-2 text-xs text-gray-500 py-2",children:[e.jsx(O,{size:14,className:"animate-spin"}),s==="zh"?"正在统计关联数据...":"Counting related data..."]}):l?e.jsxs("div",{className:"bg-gray-50 rounded-lg p-3 space-y-1.5 text-xs",children:[e.jsxs("div",{className:"flex justify-between",children:[e.jsx("span",{className:"text-gray-600",children:s==="zh"?"白板元素":"Whiteboard Elements"}),e.jsx("span",{className:"font-bold text-gray-800",children:l.whiteboardCount})]}),e.jsxs("div",{className:"flex justify-between",children:[e.jsx("span",{className:"text-gray-600",children:s==="zh"?"排课记录":"Schedules"}),e.jsx("span",{className:"font-bold text-gray-800",children:l.scheduleCount})]}),e.jsxs("div",{className:"flex justify-between",children:[e.jsx("span",{className:"text-gray-600",children:s==="zh"?"选课学生":"Enrollments"}),e.jsx("span",{className:"font-bold text-gray-800",children:l.enrollmentCount})]}),e.jsxs("div",{className:"flex justify-between",children:[e.jsx("span",{className:"text-gray-600",children:s==="zh"?"作业任务":"Assignments"}),e.jsx("span",{className:"font-bold text-gray-800",children:l.assignmentCount})]})]}):null]}),e.jsxs("div",{className:"px-5 py-3 border-t border-gray-100 bg-gray-50 flex justify-end gap-2",children:[e.jsx("button",{onClick:()=>{b(null),d(null)},className:"px-4 py-2 text-xs font-medium text-gray-600 hover:bg-gray-100 border border-gray-200 rounded-lg transition-colors cursor-pointer",children:s==="zh"?"取消":"Cancel"}),e.jsxs("button",{onClick:K,disabled:k,className:"px-4 py-2 text-xs font-bold text-white bg-red-600 hover:bg-red-700 rounded-lg transition-colors cursor-pointer disabled:opacity-50 flex items-center gap-1.5",children:[k&&e.jsx(O,{size:12,className:"animate-spin"}),s==="zh"?"确认删除":"Confirm Delete"]})]})]})})]})}export{ve as CourseManagement};
@@ -1,2 +0,0 @@
1
- const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/InteractiveCoursewareViewer-D9GzG28E.js","assets/vendor-react-Kv_JATwr.js","assets/vendor-utils-aM7aYvPP.js","assets/index-DN3E-Fmw.js","assets/vendor-icons-hc6MjYFv.js","assets/vendor-motion-BFAPOs-b.js","assets/index-DPky2leG.css"])))=>i.map(i=>d[i]);
2
- import{_ as a}from"./vendor-pdf-By0fofA0.js";import{j as e,r}from"./vendor-react-Kv_JATwr.js";const i=r.lazy(()=>a(()=>import("./InteractiveCoursewareViewer-D9GzG28E.js"),__vite__mapDeps([0,1,2,3,4,5,6])));function n(s){return e.jsx(r.Suspense,{fallback:e.jsx("div",{className:"flex items-center justify-center h-full bg-gray-50",children:e.jsx("div",{className:"animate-spin rounded-full h-8 w-8 border-b-2 border-indigo-500"})}),children:e.jsx(i,{...s})})}export{n as L};
@@ -1,2 +0,0 @@
1
- const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/InteractiveWhiteboard-DswIV-ES.js","assets/vendor-react-Kv_JATwr.js","assets/vendor-utils-aM7aYvPP.js","assets/index-DN3E-Fmw.js","assets/vendor-icons-hc6MjYFv.js","assets/vendor-motion-BFAPOs-b.js","assets/AssignmentFilePreview-Bk751Blz.js","assets/index-DPky2leG.css","assets/vendor-pptx-DIWdXF1L.css","assets/vendor-reveal-DcQQzuI9.css"])))=>i.map(i=>d[i]);
2
- import{_ as s}from"./vendor-pdf-By0fofA0.js";import{r as t,j as e}from"./vendor-react-Kv_JATwr.js";const i=t.lazy(()=>s(()=>import("./InteractiveWhiteboard-DswIV-ES.js"),__vite__mapDeps([0,1,2,3,4,5,6,7,8,9])).then(r=>({default:r.InteractiveWhiteboard}))),n=t.forwardRef((r,a)=>e.jsx(t.Suspense,{fallback:e.jsx("div",{className:"flex items-center justify-center h-full bg-gray-50",children:e.jsx("div",{className:"animate-spin rounded-full h-8 w-8 border-b-2 border-indigo-500"})}),children:e.jsx(i,{ref:a,...r})}));export{n as L};