openlearn-next 0.7.5 → 0.7.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/assets/{ActivityWorkspaceWidget-Dq4kjBS2.js → ActivityWorkspaceWidget-0DUqiqFK.js} +1 -1
- package/dist/assets/{AdminDirectoryView-B6OAs5Rj.js → AdminDirectoryView-DL9zZi-_.js} +1 -1
- package/dist/assets/{AssessmentCenter-B3f1DxSG.js → AssessmentCenter-B2JzXBGe.js} +1 -1
- package/dist/assets/{AssignmentFilePreview-DQbvbgnd.js → AssignmentFilePreview-Bk751Blz.js} +1 -1
- package/dist/assets/{BatchPickerModal-CoOWloSS.js → BatchPickerModal-9ekXDUUj.js} +1 -1
- package/dist/assets/{ClassesView-Q56Kf4eL.js → ClassesView-PCt8I3MU.js} +1 -1
- package/dist/assets/{ClassroomCountdownWidget-C6luhy8P.js → ClassroomCountdownWidget-HxelcHUp.js} +1 -1
- package/dist/assets/{CloudDriveModal-DLf4fbKR.js → CloudDriveModal-Mp59sr-C.js} +1 -1
- package/dist/assets/{ComputerLabView-D9-Tk7yA.js → ComputerLabView-FStILvFC.js} +1 -1
- package/dist/assets/{CourseManagement-DNSfyre-.js → CourseManagement-BQw2idgZ.js} +1 -1
- package/dist/assets/{CourseWizardModal-DAueHXcl.js → CourseWizardModal-B7-e8otR.js} +1 -1
- package/dist/assets/{Dashboard-qMVadkd2.js → Dashboard-D3LXLQNC.js} +1 -1
- package/dist/assets/{ExportWeightModal-Dkh7OC8U.js → ExportWeightModal-vd7XgaJ0.js} +1 -1
- package/dist/assets/{HelpView-CTF6B9ml.js → HelpView-C4BHoNqR.js} +1 -1
- package/dist/assets/{ImportLessonsModal-Ra-OF_Vo.js → ImportLessonsModal-Y6uIIemO.js} +1 -1
- package/dist/assets/{InteractiveCoursewareViewer-BfHEDrCf.js → InteractiveCoursewareViewer-D9GzG28E.js} +1 -1
- package/dist/assets/{InteractiveWhiteboard-KBzmiNBG.js → InteractiveWhiteboard-DswIV-ES.js} +1 -1
- package/dist/assets/{LazyCourseware-BaZS8-i1.js → LazyCourseware-BEoTTg5h.js} +2 -2
- package/dist/assets/{LazyWhiteboard-DGyln3JC.js → LazyWhiteboard-CMBBxnFX.js} +2 -2
- package/dist/assets/{LessonEditorView-B5JXYyup.js → LessonEditorView-BgJtG8Ks.js} +1 -1
- package/dist/assets/{LiveClassroomView-DezAlve2.js → LiveClassroomView-Czwt5WW-.js} +1 -1
- package/dist/assets/{NotificationDetailModal-DbMMrIHQ.js → NotificationDetailModal-D42vEs3S.js} +1 -1
- package/dist/assets/{PluginView-CvIr-ANj.js → PluginView-BcOWIjLu.js} +1 -1
- package/dist/assets/{QuizGeneratorModal-CTj2moWM.js → QuizGeneratorModal-DiItok6z.js} +1 -1
- package/dist/assets/{StudentAssignmentEvalPanel-DrfZT8Lr.js → StudentAssignmentEvalPanel-DHoZc3g0.js} +1 -1
- package/dist/assets/{StudentAssignmentView-D_EhblVQ.js → StudentAssignmentView-UK_7_MF3.js} +1 -1
- package/dist/assets/{StudentLessonView-BHG50-4M.js → StudentLessonView-C11Qkdi_.js} +1 -1
- package/dist/assets/{StudentView-DKDhnJQx.js → StudentView-j0hx3IOi.js} +2 -2
- package/dist/assets/{SystemResourceLibraryModal-C5y8H5Pf.js → SystemResourceLibraryModal-dEdNKbk2.js} +1 -1
- package/dist/assets/{TeacherView-CWDK3DOe.js → TeacherView-COZw2tFl.js} +2 -2
- package/dist/assets/{TimetableView-C8kdJlf7.js → TimetableView-AkReskNG.js} +1 -1
- package/dist/assets/{index-BBznBaN5.js → index-DN3E-Fmw.js} +4 -4
- package/dist/assets/{pluginParsers-DkiO-BBu.js → pluginParsers-k90WUDrU.js} +1 -1
- package/dist/assets/{student-workspace-1yVTbWHC.js → student-workspace-BDw1i4Vl.js} +1 -1
- package/dist/index.html +1 -1
- package/dist/server.cjs +65 -27
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
import{a3 as _,a4 as l}from"./index-
|
|
1
|
+
import{a3 as _,a4 as l}from"./index-DN3E-Fmw.js";const L=s=>{const a=[];try{const i=s.match(/manifest\s*:\s*\{([\s\S]*?)\}(?:\s*,|\s*\})/),c=i?i[1]:s,m=c.match(/id\s*:\s*['"]([^'"]+)['"]/)||s.match(/id\s*:\s*['"]([^'"]+)['"]/),p=c.match(/name\s*:\s*['"]([^'"]+)['"]/)||s.match(/name\s*:\s*['"]([^'"]+)['"]/),o=c.match(/version\s*:\s*['"]([^'"]+)['"]/)||s.match(/version\s*:\s*['"]([^'"]+)['"]/),v=c.match(/description\s*:\s*['"]([^'"]+)['"]/)||s.match(/description\s*:\s*['"]([^'"]+)['"]/),h=c.match(/author\s*:\s*['"]([^'"]+)['"]/)||s.match(/author\s*:\s*['"]([^'"]+)['"]/);let r=[];const u=c.match(/capabilitiesProposed\s*:\s*\[([\s\S]*?)\]/)||s.match(/capabilitiesProposed\s*:\s*\[([\s\S]*?)\]/);u&&(r=u[1].split(",").map(t=>t.replace(/['"\s]/g,"")).filter(t=>t.length>0));const w={id:m?.[1]||void 0,name:p?.[1]||void 0,version:o?.[1]||void 0,description:v?.[1]||void 0,author:h?.[1]||void 0,capabilitiesProposed:r.length>0?r:void 0},x=/actionRegistry\.register\s*\(\s*\{([\s\S]*?)\}\s*\)/g;let f;const g=s;for(;(f=x.exec(g))!==null;){const t=f[1],n=t.match(/id\s*:\s*['"]([^'"]+)['"]/),e=t.match(/commandType\s*:\s*['"]([^'"]+)['"]/),d=t.match(/description\s*:\s*['"]([^'"]+)['"]/);(n||e)&&a.push({id:n?n[1]:"unknown",commandType:e?e[1]:"unknown",description:d?d[1]:""})}return{manifest:w,actions:a,error:null}}catch(i){return{manifest:null,actions:[],error:i.toString()}}},y=s=>{const a=s.split(/\r?\n/);if(a.length<2)return[];const i=a[0],c=[",",";"," "];let m=",",p=0;c.forEach(t=>{const n=i.split(t).length;n>p&&(p=n,m=t)});const o=_(i,m).map(t=>t.toLowerCase().replace(/["'\r]/g,"").trim()),v=l(o,["学生学号","student_number","studentnumber","student number","student_id","studentid","学号","编号"]),h=l(o,["学生姓名","student_name","studentname","student name","姓名","学生","name"]),r=l(o,["学生邮箱","student_email","studentemail","student email","邮箱","email","mail"]),u=l(o,["初始密码","initial password","password","passwd","pwd","密码"]),w=l(o,["学生备注","student notes","private_notes","备注","note","notes","remark"]),x=h>=0?h:0,f=r>=0?r:1,g=[];for(let t=1;t<a.length;t++){if(!a[t].trim())continue;const n=_(a[t],m),e=b=>b>=0&&n[b]?n[b].replace(/["'\r]/g,"").trim():"",d=e(x);if(!d)continue;const I=e(v),S=e(u),M=e(w);g.push({name:d,email:e(f),...I?{student_number:I}:{},...S?{password:S}:{},...M?{private_notes:M}:{}})}return g};export{y as a,L as p};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{r as d,j as r,R as x}from"./vendor-react-Kv_JATwr.js";import{E as I,h as M}from"./index-BBznBaN5.js";import{L as A}from"./LazyWhiteboard-DGyln3JC.js";import{S as W}from"./StudentAssignmentEvalPanel-DrfZT8Lr.js";import{an as C,o as z}from"./vendor-icons-hc6MjYFv.js";import{ActivityWorkspaceWidget as P}from"./ActivityWorkspaceWidget-Dq4kjBS2.js";import"./vendor-utils-aM7aYvPP.js";import"./vendor-pdf-By0fofA0.js";import"./vendor-motion-BFAPOs-b.js";import"./vendor-charts-SrpBsvM3.js";import"./AssignmentFilePreview-DQbvbgnd.js";class w{constructor(){this.providers=new Map}register(e){if(!e||!e.id)throw new Error("WorkspaceSlotRegistry Error: WorkspaceSlotProvider must have a valid ID.");this.providers.set(e.id,e)}unregister(e){return this.providers.delete(e)}getProviders(e){const t=Array.from(this.providers.values()).filter(s=>s.slot===e);return t.sort((s,i)=>(i.priority??0)-(s.priority??0)),Object.freeze(t)}listAll(){return Object.freeze(Array.from(this.providers.values()))}clear(){this.providers.clear()}}const k=d.createContext(null),N=({registry:n,children:e})=>{const[t]=d.useState(()=>n??new w),[,s]=d.useState(0),i=d.useCallback(()=>{s(l=>l+1)},[]),a=d.useCallback(l=>{t.register(l),i()},[t,i]),c=d.useCallback(l=>{t.unregister(l)&&i()},[t,i]),o=d.useCallback(l=>t.getProviders(l),[t]),g=d.useMemo(()=>({registry:t,registerSlotProvider:a,unregisterSlotProvider:c,getSlotProviders:o}),[t,a,c,o]);return r.jsx(k.Provider,{value:g,children:e})},_=()=>{const n=d.useContext(k);if(!n)throw new Error("useWorkspace must be used within a WorkspaceProvider");return n},D=n=>{const{getSlotProviders:e}=_();return e(n)},f=({slot:n,className:e})=>{const t=D(n);return t.length===0?null:r.jsx("div",{className:`workspace-slot workspace-slot-${n.toLowerCase()} ${e??""}`,children:t.map(s=>r.jsx(x.Fragment,{children:s.render()},s.id))})},L=({className:n,children:e})=>r.jsxs("div",{className:`workspace-shell flex flex-col h-screen w-screen overflow-hidden bg-slate-900 text-slate-100 ${n??""}`,children:[r.jsx(f,{slot:"TopBar",className:"w-full flex-none z-30"}),r.jsxs("div",{className:"flex flex-1 overflow-hidden relative",children:[r.jsx(f,{slot:"LeftSidebar",className:"flex-none z-20"}),r.jsxs("main",{className:"flex-1 relative overflow-hidden flex flex-col",children:[r.jsx(f,{slot:"MainCanvas",className:"flex-1 w-full h-full relative"}),e,r.jsx(f,{slot:"BottomPanel",className:"flex-none z-20"})]}),r.jsx(f,{slot:"RightSidebar",className:"flex-none z-20"})]}),r.jsx(f,{slot:"StatusBar",className:"w-full flex-none z-30"}),r.jsx(f,{slot:"FloatingArea",className:"absolute inset-0 pointer-events-none z-40"}),r.jsx(f,{slot:"DialogArea",className:"absolute inset-0 pointer-events-none z-50"})]});var b=(n=>(n[n.Immediate=0]="Immediate",n[n.High=1]="High",n[n.Normal=2]="Normal",n[n.Low=3]="Low",n[n.Idle=4]="Idle",n))(b||{});class E{constructor(){this.subscribers=new Map}subscribe(e,t){const s=String(e);this.subscribers.has(s)||this.subscribers.set(s,new Set);const i=this.subscribers.get(s);return i.add(t),()=>{i.delete(t)}}async publish(e,t,s="runtime.system"){const i={id:`evt_${globalThis.crypto.randomUUID()}`,type:e,payload:t,source:s,timestamp:Date.now()},a=this.subscribers.get(String(e))||new Set,c=this.subscribers.get("*")||new Set,o=[...Array.from(a),...Array.from(c)];return await Promise.all(o.map(g=>Promise.resolve(g(i)).catch(l=>{console.error(`[RuntimeEventBus] Error in subscriber for ${String(e)}:`,l)}))),i}clear(){this.subscribers.clear()}}class O{constructor(e=`rt_${globalThis.crypto.randomUUID()}`){this.listeners=new Set,this.currentState={runtime:{id:e,lifecycle:"Create",startTime:Date.now(),elapsedTime:0},lesson:{},stage:{index:0},activity:{},whiteboard:{objectCount:0,isLocked:!1},teachingObjects:[],students:[],plugin:[],ai:{isGenerating:!1},analytics:{totalInteractions:0,activeStudentCount:0,averageScore:0}}}getState(){return this.currentState}setState(e){this.currentState=Object.freeze(e),this.notify()}updateState(e){this.currentState=Object.freeze({...this.currentState,...e,runtime:e.runtime?{...this.currentState.runtime,...e.runtime}:this.currentState.runtime,lesson:e.lesson?{...this.currentState.lesson,...e.lesson}:this.currentState.lesson,stage:e.stage?{...this.currentState.stage,...e.stage}:this.currentState.stage,activity:e.activity?{...this.currentState.activity,...e.activity}:this.currentState.activity,whiteboard:e.whiteboard?{...this.currentState.whiteboard,...e.whiteboard}:this.currentState.whiteboard,ai:e.ai?{...this.currentState.ai,...e.ai}:this.currentState.ai,analytics:e.analytics?{...this.currentState.analytics,...e.analytics}:this.currentState.analytics}),this.notify()}setLifecycle(e){this.updateState({runtime:{...this.currentState.runtime,lifecycle:e}})}addStudent(e){this.currentState.students.some(s=>s.id===e.id)||this.updateState({students:Object.freeze([...this.currentState.students,e])})}removeStudent(e){this.updateState({students:Object.freeze(this.currentState.students.filter(t=>t.id!==e))})}subscribe(e){return this.listeners.add(e),e(this.getState()),()=>{this.listeners.delete(e)}}notify(){const e=this.getState();for(const t of this.listeners)try{t(e)}catch(s){console.error("[RuntimeStateManager] Listener error:",s)}}}class ${constructor(){this.services=new Map}registerService(e){if(!e.serviceId)throw new Error("[RuntimeServiceRegistry] Service must provide a non-empty serviceId.");this.services.set(e.serviceId,e)}async unregisterService(e){const t=this.services.get(e);return t?(await t.dispose(),this.services.delete(e)):!1}getService(e){return this.services.get(e)}listServices(){return Object.freeze(Array.from(this.services.keys()))}async initializeAll(e){for(const t of this.services.values())await t.initialize(e)}async disposeAll(){for(const e of this.services.values())try{await e.dispose()}catch(t){console.error(`[RuntimeServiceRegistry] Error disposing service ${e.serviceId}:`,t)}this.services.clear()}}class B{constructor(){this.modules=new Map,this.activeModuleIds=new Set}registerModule(e){if(!e.id)throw new Error("[RuntimeModuleRegistry] Module must specify a valid id.");this.modules.set(e.id,e)}async unregisterModule(e,t){const s=this.modules.get(e);return s?(this.activeModuleIds.has(e)&&t&&(await s.stop(t),this.activeModuleIds.delete(e)),await s.dispose(),this.modules.delete(e)):!1}getModule(e){return this.modules.get(e)}listModules(){return Object.freeze(Array.from(this.modules.values()))}async startAll(e){for(const t of this.modules.values())await t.initialize(e),await t.start(e),this.activeModuleIds.add(t.id)}async stopAll(e){for(const t of this.activeModuleIds){const s=this.modules.get(t);if(s)try{await s.stop(e)}catch(i){console.error(`[RuntimeModuleRegistry] Error stopping module ${t}:`,i)}}this.activeModuleIds.clear()}}class U{constructor(){this.taskQueue=[],this.isProcessing=!1,this.timer=null}schedule(e,t,s=b.Normal,i=0,a=3){return new Promise((c,o)=>{const g={id:`tsk_${globalThis.crypto.randomUUID()}`,name:e,priority:s,taskFn:async()=>{i>0&&await new Promise(l=>setTimeout(l,i));try{const l=await t();return c(l),l}catch(l){throw o(l),l}},delayMs:i,maxRetries:a,createdAt:Date.now()};this.taskQueue.push(g),this.sortQueue(),queueMicrotask(()=>{this.processQueue()})})}start(){this.timer||(this.timer=setInterval(()=>{this.processQueue()},50))}stop(){this.timer&&(clearInterval(this.timer),this.timer=null)}getPendingTaskCount(){return this.taskQueue.length}sortQueue(){this.taskQueue.sort((e,t)=>e.priority-t.priority||e.createdAt-t.createdAt)}async processQueue(){if(this.isProcessing||this.taskQueue.length===0)return;this.isProcessing=!0;const e=this.taskQueue.shift();if(e){let t=0;const s=e.maxRetries??1;for(;t<s;)try{await e.taskFn();break}catch(i){t+=1,t>=s&&console.error(`[RuntimeScheduler] Task ${e.name} (${e.id}) failed after ${t} attempts:`,i)}}this.isProcessing=!1}}class T{constructor(){this.rolePermissions=new Map([["Teacher",new Set(["lesson:control","stage:navigate","whiteboard:draw","quiz:submit","plugin:execute","ai:invoke","session:manage"])],["Assistant",new Set(["stage:navigate","whiteboard:draw","quiz:submit","plugin:execute","ai:invoke"])],["Student",new Set(["whiteboard:draw","quiz:submit","ai:invoke","plugin:execute"])],["Observer",new Set([])],["Plugin",new Set(["whiteboard:draw","plugin:execute","ai:invoke"])],["AI",new Set(["stage:navigate","whiteboard:draw","ai:invoke"])]])}hasPermission(e,t){const s=this.rolePermissions.get(e);return s?s.has(t):!1}grantPermission(e,t){this.rolePermissions.has(e)||this.rolePermissions.set(e,new Set),this.rolePermissions.get(e).add(t)}revokePermission(e,t){const s=this.rolePermissions.get(e);s&&s.delete(t)}getRolePermissions(e){const t=this.rolePermissions.get(e);return Object.freeze(t?Array.from(t):[])}}class F{constructor(){this.resources=new Map}registerResource(e,t,s){const i=Array.from(this.resources.values()).find(c=>c.url===e);if(i)return i;const a={id:`res_${globalThis.crypto.randomUUID()}`,type:t,url:e,sizeBytes:s,status:"loaded",cachedAt:Date.now()};return this.resources.set(a.id,a),a}getResource(e){return this.resources.get(e)}listResources(e){const t=Array.from(this.resources.values());return Object.freeze(e?t.filter(s=>s.type===e):t)}removeResource(e){return this.resources.delete(e)}clear(){this.resources.clear()}}class H{constructor(){this.snapshots=new Map}takeSnapshot(e,t,s,i){const a={snapshotId:`snap_${globalThis.crypto.randomUUID()}`,timestamp:Date.now(),stateTree:JSON.parse(JSON.stringify(e)),activeServices:[...t],loadedModules:[...s],resources:[...i]};return this.snapshots.set(a.snapshotId,a),this.lastAutoSnapshot=a,a}getLatestSnapshot(){return this.lastAutoSnapshot}getSnapshot(e){return this.snapshots.get(e)}clear(){this.snapshots.clear(),this.lastAutoSnapshot=void 0}}class Q{constructor(){this.hooks=new Map}registerHook(e,t){this.hooks.has(e)||this.hooks.set(e,new Set);const s=this.hooks.get(e);return s.add(t),()=>{s.delete(t)}}async executeHook(e,t,s){const i=this.hooks.get(e);if(!(!i||i.size===0))for(const a of i)try{await a(t,s)}catch(c){console.error(`[RuntimeHooksManager] Error executing hook "${e}":`,c)}}clear(){this.hooks.clear()}}class J{constructor(e,t){this.activeSession=null,this.eventBus=e,this.stateManager=t}async createSession(e,t,s,i){this.activeSession&&this.activeSession.status==="active"&&await this.destroySession();const a={sessionId:e,courseId:s,lessonId:i,teacher:t,students:Object.freeze([]),createdAt:Date.now(),status:"active"};return this.activeSession=a,this.stateManager.updateState({lesson:{activeLessonId:i,status:"ready"}}),await this.eventBus.publish("SessionCreated",{sessionId:e,teacherId:t.id,timestamp:Date.now()}),a}async joinSession(e){if(!this.activeSession||this.activeSession.status!=="active")return!1;const t=[...this.activeSession.students.filter(s=>s.id!==e.id),{...e,isOnline:!0}];return this.activeSession={...this.activeSession,students:Object.freeze(t)},this.stateManager.addStudent(e),await this.eventBus.publish("StudentJoined",{student:e,timestamp:Date.now()}),!0}async leaveSession(e){if(!this.activeSession||this.activeSession.status!=="active")return!1;const t=this.activeSession.students.map(s=>s.id===e?{...s,isOnline:!1}:s);return this.activeSession={...this.activeSession,students:Object.freeze(t)},this.stateManager.removeStudent(e),await this.eventBus.publish("StudentLeft",{studentId:e,timestamp:Date.now()}),!0}async reconnectSession(e){if(!this.activeSession||this.activeSession.status!=="active")return!1;const t=this.activeSession.students.find(i=>i.id===e);if(!t)return!1;const s={...t,isOnline:!0};return this.joinSession(s)}async destroySession(){this.activeSession&&(this.activeSession={...this.activeSession,status:"destroyed"},this.activeSession=null)}getActiveSession(){return this.activeSession}}class q{constructor(){this.eventCount=0}incrementEventCount(){this.eventCount+=1}getDiagnostics(e,t,s,i){const a=process.memoryUsage?process.memoryUsage().heapUsed/1048576:0;return{runtimeId:e.runtime.id,lifecycleState:e.runtime.lifecycle,activeServicesCount:t,registeredModulesCount:s,pendingSchedulerTasksCount:i,activeStudentsCount:e.students.length,totalInteractionsCount:e.analytics.totalInteractions,memoryUsageMB:Math.round(a*100)/100,timestamp:Date.now()}}}class K{constructor(e=`rt_${globalThis.crypto.randomUUID()}`){this.currentRole="Teacher",this.runtimeId=e,this.eventBus=new E,this.stateManager=new O(e),this.serviceRegistry=new $,this.moduleRegistry=new B,this.scheduler=new U,this.permissionManager=new T,this.resourceManager=new F,this.snapshotManager=new H,this.hooksManager=new Q,this.sessionManager=new J(this.eventBus,this.stateManager),this.monitor=new q,this.stateManager.setLifecycle("Create")}getContext(){const e=this.sessionManager.getActiveSession();return{runtimeId:this.runtimeId,sessionId:e?.sessionId||"sess_default",courseId:e?.courseId,lessonId:e?.lessonId,teacher:e?.teacher,currentUser:this.currentUser||e?.teacher,role:this.currentRole,permissions:this.permissionManager.getRolePermissions(this.currentRole),lifecycleState:this.stateManager.getState().runtime.lifecycle}}async initialize(){this.transitionState("Initialize"),this.scheduler.start();const e=this.getContext();await this.serviceRegistry.initializeAll(e),this.transitionState("Prepare")}async start(){const e=this.getContext();await this.hooksManager.executeHook("beforeLessonStart",{},e),this.transitionState("Running"),await this.moduleRegistry.startAll(e),await this.hooksManager.executeHook("afterLessonStart",{},e),await this.eventBus.publish("LessonStarted",{lessonId:e.lessonId||"les_active",timestamp:Date.now()}),this.takeSnapshot()}async pause(){this.transitionState("Pause"),this.scheduler.stop(),await this.eventBus.publish("RuntimePaused",{elapsedTime:this.stateManager.getState().runtime.elapsedTime,timestamp:Date.now()})}async resume(){this.transitionState("Resume"),this.scheduler.start(),this.transitionState("Running")}async stop(){const e=this.getContext();this.transitionState("Stop"),await this.moduleRegistry.stopAll(e),this.scheduler.stop(),this.takeSnapshot()}async dispose(){await this.stop(),this.transitionState("Dispose"),await this.serviceRegistry.disposeAll(),this.hooksManager.clear(),this.resourceManager.clear(),this.eventBus.clear()}transitionState(e){const t=this.stateManager.getState().runtime.lifecycle;this.stateManager.setLifecycle(e),this.eventBus.publish("LifecycleChanged",{from:t,to:e,timestamp:Date.now()})}registerModule(e){this.moduleRegistry.registerModule(e)}registerService(e){this.serviceRegistry.registerService(e)}registerRuntimeHook(e,t){return this.hooksManager.registerHook(e,t)}scheduleTask(e,t,s=b.Normal,i=0){return this.scheduler.schedule(e,t,s,i)}setUser(e){this.currentUser=e,this.currentRole=e.role}hasPermission(e){return this.permissionManager.hasPermission(this.currentRole,e)}takeSnapshot(){return this.snapshotManager.takeSnapshot(this.stateManager.getState(),this.serviceRegistry.listServices(),this.moduleRegistry.listModules().map(e=>e.id),this.resourceManager.listResources())}restoreLatestSnapshot(){const e=this.snapshotManager.getLatestSnapshot();return e?(this.stateManager.setState(e.stateTree),!0):!1}getDiagnostics(){return this.monitor.getDiagnostics(this.stateManager.getState(),this.serviceRegistry.listServices().length,this.moduleRegistry.listModules().length,this.scheduler.getPendingTaskCount())}}class V{constructor(e){this.init=e,this.kernel=new K,this.kernel.setUser({id:e.student.id,name:e.student.name,role:"Student",avatar:e.student.avatar,isOnline:!0,joinedAt:Date.now()})}getView(){const e=this.kernel.getContext();return{studentId:this.init.student.id,studentName:this.init.student.name,role:"Student",lessonId:this.init.lessonId??e.lessonId,courseId:this.init.courseId??e.courseId,permissions:e.permissions}}get permissions(){return this.kernel.getContext().permissions}hasPermission(e){return this.kernel.hasPermission(e)}subscribe(e,t){return this.kernel.eventBus.subscribe(e,s=>t(s.payload))}takeSnapshot(){return this.kernel.takeSnapshot()}restoreSnapshot(){return this.kernel.restoreLatestSnapshot()}get runtimeKernel(){return this.kernel}dispose(){return this.kernel.dispose()}}function G({isOpen:n,lang:e,activeStudentId:t,studentNotifications:s,readNotifications:i,setReadNotifications:a,studentDashboardData:c,setIsNotificationsOpen:o,setSelectedNotificationForModal:g}){if(!n)return null;const l=s.filter(u=>!i.has(u.id));return r.jsxs("div",{className:"absolute right-0 mt-2 w-80 bg-white border border-gray-200 shadow-lg rounded-xl z-50 overflow-hidden",children:[r.jsxs("div",{className:"p-3 border-b border-gray-100 flex justify-between items-center bg-gray-50",children:[r.jsx("h3",{className:"font-semibold text-gray-800",children:"Notifications"}),l.length>0&&r.jsx("button",{onClick:async()=>{if(!t)return;try{const h=s.filter(m=>!i.has(m.id)).map(m=>fetch(`/api/students/${t}/read_notifications`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({notificationId:m.id})}));await Promise.all(h)}catch(h){console.error(h)}const u=new Set(i);s.forEach(h=>u.add(h.id)),a(u)},className:"text-xs text-indigo-600 hover:text-indigo-800 font-medium",children:"Mark all as read"})]}),r.jsx("div",{className:"max-h-96 overflow-y-auto",children:s.length===0?r.jsx("div",{className:"p-4 text-center text-sm text-gray-500 italic",children:"No notifications."}):r.jsx("div",{className:"divide-y divide-gray-100",children:s.map(u=>{const h=!i.has(u.id);return r.jsx("div",{className:`p-3 hover:bg-gray-50 cursor-pointer ${h?"bg-indigo-50/30":""}`,onClick:()=>{if(h){t&&fetch(`/api/students/${t}/read_notifications`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({notificationId:u.id})}).catch(console.error);const S=new Set(i);S.add(u.id),a(S)}const m=c?.assignments?.find(S=>S.id===u.relatedId);g({...u,assignment:m}),o(!1)},children:r.jsxs("div",{className:"flex gap-3",children:[r.jsx("div",{className:"mt-0.5",children:u.type==="new_assignment"?r.jsx(C,{size:16,className:"text-indigo-500"}):r.jsx(z,{size:16,className:"text-green-500"})}),r.jsxs("div",{className:"flex-1",children:[r.jsx("div",{className:`text-sm ${h?"font-semibold text-gray-900":"font-medium text-gray-700"}`,children:u.title}),r.jsx("div",{className:`text-xs mt-0.5 ${h?"text-gray-600":"text-gray-500"}`,children:u.message})]}),h&&r.jsx("div",{className:"w-2 h-2 rounded-full bg-indigo-500 mt-1"})]})},u.id)})})})]})}const X=({widgetId:n="widget_ai_teacher_workspace",initialSection:e="Lesson Assistant",onActionExecute:t})=>{const[s,i]=d.useState({widgetId:n,visible:!0,pinned:!0,collapsed:!1,fullscreen:!1,dockPosition:"right",activeSection:e});if(!s.visible)return null;const a=["Lesson Assistant","Whiteboard Assistant","Resource Assistant","Activity Assistant","Student Assistant","Assessment Assistant","Summary Assistant","Plugin Assistant"],c=o=>{t&&t(o,{section:s.activeSection})};return r.jsxs("div",{"data-testid":"ai-teacher-workspace-widget",className:`ai-teacher-workspace-panel ${s.dockPosition} ${s.collapsed?"collapsed":""} ${s.fullscreen?"fullscreen":""}`,style:{border:"1px solid #e2e8f0",borderRadius:"8px",background:"#ffffff",padding:"12px",boxShadow:"0 4px 6px -1px rgba(0, 0, 0, 0.1)"},children:[r.jsxs("div",{className:"ai-widget-header",style:{display:"flex",justifyContent:"space-between",alignItems:"center",borderBottom:"1px solid #f1f5f9",paddingBottom:"8px",marginBottom:"8px"},children:[r.jsx("span",{style:{fontWeight:600,fontSize:"14px",color:"#1e293b"},children:"🤖 AI Teacher Assistant"}),r.jsxs("div",{className:"ai-widget-controls",style:{display:"flex",gap:"4px"},children:[r.jsx("button",{"data-testid":"btn-pin",onClick:()=>i(o=>({...o,pinned:!o.pinned})),style:{fontSize:"12px",cursor:"pointer"},children:s.pinned?"📌 Unpin":"📍 Pin"}),r.jsx("button",{"data-testid":"btn-dock",onClick:()=>i(o=>({...o,dockPosition:o.dockPosition==="right"?"float":"right"})),style:{fontSize:"12px",cursor:"pointer"},children:s.dockPosition==="right"?"🌊 Float":"📥 Dock"}),r.jsx("button",{"data-testid":"btn-fullscreen",onClick:()=>i(o=>({...o,fullscreen:!o.fullscreen})),style:{fontSize:"12px",cursor:"pointer"},children:s.fullscreen?"🗗 Restore":"⛶ Fullscreen"}),r.jsx("button",{"data-testid":"btn-collapse",onClick:()=>i(o=>({...o,collapsed:!o.collapsed})),style:{fontSize:"12px",cursor:"pointer"},children:s.collapsed?"➕ Expand":"➖ Collapse"})]})]}),!s.collapsed&&r.jsxs(r.Fragment,{children:[r.jsx("div",{className:"ai-section-tabs",style:{display:"flex",flexWrap:"wrap",gap:"4px",marginBottom:"12px"},children:a.map(o=>r.jsx("button",{"data-testid":`tab-${o.toLowerCase().replace(/\s+/g,"-")}`,onClick:()=>i(g=>({...g,activeSection:o})),style:{padding:"4px 8px",fontSize:"12px",borderRadius:"4px",border:"1px solid #cbd5e1",background:s.activeSection===o?"#3b82f6":"#f8fafc",color:s.activeSection===o?"#ffffff":"#334155",cursor:"pointer"},children:o},o))}),r.jsxs("div",{className:"ai-section-body",style:{minHeight:"100px"},children:[r.jsxs("div",{style:{fontSize:"13px",color:"#475569",marginBottom:"8px"},children:["Active Assistant: ",r.jsx("strong",{children:s.activeSection})]}),r.jsxs("div",{style:{display:"flex",gap:"8px",flexWrap:"wrap"},children:[r.jsx("button",{"data-testid":"btn-summarize",onClick:()=>c("ai_summarize_lesson"),style:{padding:"6px 12px",fontSize:"12px",background:"#eff6ff",border:"1px solid #bfdbfe",borderRadius:"4px",cursor:"pointer"},children:"📝 Summarize Content"}),r.jsx("button",{"data-testid":"btn-explain",onClick:()=>c("ai_explain_whiteboard"),style:{padding:"6px 12px",fontSize:"12px",background:"#f0fdf4",border:"1px solid #bbf7d0",borderRadius:"4px",cursor:"pointer"},children:"🎨 Explain Visuals"}),r.jsx("button",{"data-testid":"btn-quiz",onClick:()=>c("ai_generate_quiz"),style:{padding:"6px 12px",fontSize:"12px",background:"#fefce8",border:"1px solid #fef08a",borderRadius:"4px",cursor:"pointer"},children:"⚡ Generate Quiz"})]})]})]})]})};class Y{constructor(){this.resources=new Map,this.providers=new Map}registerProvider(e){if(!e||!e.id||!e.type)throw new Error("ResourceRegistry Error: ResourceProvider must have a valid ID and Type.");this.providers.set(e.type,e)}unregisterProvider(e){for(const[t,s]of this.providers.entries())if(s.id===e)return this.providers.delete(t);return!1}getProvider(e){return this.providers.get(e)}registerResource(e){if(!e||!e.id)throw new Error("ResourceRegistry Error: ResourceDescriptor must have a valid ID.");this.resources.set(e.id,{...e})}unregisterResource(e){return this.resources.delete(e)}getResource(e){return this.resources.get(e)}listResources(e){const t=Array.from(this.resources.values());return Object.freeze(e?t.filter(s=>s.type===e):t)}executeAction(e,t,s){const i=this.resources.get(e);if(!i)throw new Error(`ResourceRegistry Error: Resource '${e}' not found.`);const a=this.providers.get(i.type);switch(t){case"preview":return a?.preview?a.preview(i):{previewUrl:i.url};case"open":return a?.open?a.open(i):{openUrl:i.url};case"pin":return i.pinned=!i.pinned,{pinned:i.pinned};case"favorite":return i.favorited=!i.favorited,{favorited:i.favorited};case"annotate":return{annotate:!0,resourceId:e,params:s};case"share":return{shareUrl:i.url??`resource://${i.id}`};case"fullscreen":return{fullscreen:!0,resourceId:e};default:throw new Error(`ResourceRegistry Error: Unknown action '${t}'.`)}}clear(){this.resources.clear(),this.providers.clear()}}const y={padding:"12px",color:"#e2e8f0",fontSize:"13px"},Z=()=>r.jsx("div",{style:{width:"100%",height:"100%"},children:r.jsx(A,{})}),ee=({context:n})=>{const e=n.getView();return r.jsxs("div",{style:y,children:[r.jsx("div",{style:{fontWeight:600,marginBottom:6},children:"📘 Lesson"}),r.jsxs("div",{children:["Lesson ID: ",e.lessonId??"—"]}),r.jsxs("div",{children:["Course ID: ",e.courseId??"—"]})]})},te=()=>{const[n]=d.useState(()=>new Y().listResources());return r.jsxs("div",{style:y,children:[r.jsx("div",{style:{fontWeight:600,marginBottom:6},children:"📂 Resources"}),n.length===0?r.jsx("div",{style:{opacity:.7},children:"No resources shared yet."}):r.jsx("ul",{children:n.map(e=>r.jsxs("li",{children:[e.type,": ",e.url]},e.id))})]})},se=({studentId:n,lang:e="en"})=>r.jsx(P,{role:"student",actorId:n,lang:e}),ie=({studentId:n,lessonId:e,lang:t="en"})=>e?r.jsx(W,{lessonId:e,studentId:n,lang:t,addToast:()=>{}}):r.jsx("div",{style:y,children:"No active lesson — assignments unavailable."}),re=({context:n})=>{const[e,t]=d.useState([]);return d.useEffect(()=>{const s=n.subscribe("LessonStarted",a=>{t(c=>[...c,{id:`ls_${a.timestamp}`,text:`Lesson started (${a.lessonId})`}])}),i=n.subscribe("StageChanged",a=>{t(c=>[...c,{id:`sc_${a.timestamp}`,text:`Stage changed → ${a.toStageId}`}])});return()=>{s(),i()}},[n]),r.jsxs("div",{style:y,children:[r.jsx("div",{style:{fontWeight:600,marginBottom:6},children:"📣 Teacher Broadcast"}),e.length===0?r.jsx("div",{style:{opacity:.7},children:"No announcements yet."}):r.jsx("ul",{children:e.map(s=>r.jsx("li",{children:s.text},s.id))})]})},ne=()=>r.jsx(X,{}),ae=({studentId:n,lang:e="en"})=>{const[t,s]=d.useState(!0),[i,a]=d.useState(new Set),[c]=d.useState([]);return r.jsx(G,{isOpen:t,lang:e,activeStudentId:n,studentNotifications:c,readNotifications:i,setReadNotifications:a,studentDashboardData:null,setIsNotificationsOpen:s,setSelectedNotificationForModal:()=>{}})},oe=()=>r.jsx(I,{slot:"student.view"});class ce{constructor(e){this.registeredWidgets=new Map,this.slotRegistry=e??new w}get slotRegistryRef(){return this.slotRegistry}registerWidget(e){if(!e||!e.id)throw new Error("StudentWidgetRegistry Error: StudentWidgetDescriptor must have a valid ID.");this.registeredWidgets.set(e.id,e);const t=e.component;this.slotRegistry.register({id:e.id,slot:e.slot,priority:e.priority??10,render:s=>x.createElement(t,s)})}unregisterWidget(e){const t=this.registeredWidgets.delete(e);return t&&this.slotRegistry.unregister(e),t}registerDefaultWidgets(e){const{studentId:t,lessonId:s,lang:i="en",context:a,pluginHost:c}=e;this.mountWidget("widget_student_whiteboard","Whiteboard","MainCanvas",Z,{},"official"),this.mountWidget("widget_student_lesson","Lesson","LeftSidebar",ee,{context:a},"official"),this.mountWidget("widget_student_resources","Resources","LeftSidebar",te,{},"official"),this.mountWidget("widget_student_activities","Activities","BottomPanel",se,{},"official"),this.mountWidget("widget_student_assignments","Assignments","RightSidebar",ie,{studentId:t,lessonId:s,lang:i},"official",20),this.mountWidget("widget_student_ai_assistant","AI Learning Assistant","RightSidebar",ne,{},"official",10),this.mountWidget("widget_student_broadcast","Teacher Broadcast","BottomPanel",re,{context:a},"official"),this.mountWidget("widget_student_notifications","Notifications","FloatingArea",ae,{studentId:t,lang:i},"official"),c&&this.mountWidget("widget_student_plugins","Plugin Widgets","FloatingArea",oe,{},"plugin")}getWidget(e){return this.registeredWidgets.get(e)}listWidgets(){return Object.freeze(Array.from(this.registeredWidgets.values()))}clear(){for(const e of this.registeredWidgets.keys())this.slotRegistry.unregister(e);this.registeredWidgets.clear()}mountWidget(e,t,s,i,a,c,o=10){this.registeredWidgets.set(e,{id:e,name:t,slot:s,component:i,provider:c,priority:o}),this.slotRegistry.register({id:e,slot:s,priority:o,render:()=>x.createElement(i,a)})}}const R="openlearn_student_workspace_";function le(n){return{studentId:n,openedWidgets:[],layout:{},selectedResourceIds:[],activityState:{},aiConversation:[],updatedAt:Date.now()}}class de{constructor(e){this.key=`${R}${e}`}load(){try{if(typeof localStorage>"u")return null;const e=localStorage.getItem(this.key);if(!e)return null;const t=JSON.parse(e);return t.studentId!==this.studentIdFromKey()?null:t}catch{return null}}save(e){try{if(typeof localStorage>"u")return;localStorage.setItem(this.key,JSON.stringify({...e,updatedAt:Date.now()}))}catch{}}update(e){const s={...this.load()??le(this.studentIdFromKey()),...e,updatedAt:Date.now()};return this.save(s),s}clear(){try{if(typeof localStorage>"u")return;localStorage.removeItem(this.key)}catch{}}studentIdFromKey(){return this.key.slice(R.length)}}const be=n=>{const{student:e,lessonId:t,courseId:s,teacherId:i,teacherName:a,lang:c="en",autoRestore:o=!0,pluginHost:g}=n,[l]=d.useState(()=>new V({student:e,lessonId:t,courseId:s,teacherId:i,teacherName:a,lang:c})),[u]=d.useState(()=>new w),[h]=d.useState(()=>{const p=new ce(u);return p.registerDefaultWidgets({studentId:e.id,lessonId:t,lang:c,context:l,pluginHost:g}),p}),[m]=d.useState(()=>new de(e.id));d.useEffect(()=>{if(!o)return;l.restoreSnapshot();const p=m.load();p?.currentLessonId&&p.currentLessonId},[o,l,m]),d.useEffect(()=>{const p=l.subscribe("LessonStarted",v=>{m.update({currentLessonId:v.lessonId})});return()=>{p()}},[l,m]),d.useEffect(()=>()=>{l.takeSnapshot();const p=h.listWidgets().map(j=>j.id),v=m.load();m.save({studentId:e.id,openedWidgets:p,layout:v?.layout??{},currentLessonId:v?.currentLessonId??t,selectedResourceIds:v?.selectedResourceIds??[],activityState:v?.activityState??{},aiConversation:v?.aiConversation??[],updatedAt:Date.now()}),l.dispose()},[l,m,h,e.id,t]);const S=r.jsx(N,{registry:u,children:r.jsx(L,{})});return g?r.jsx(M,{host:g,children:S}):S};export{be as StudentWorkspace,be as default};
|
|
1
|
+
import{r as d,j as r,R as x}from"./vendor-react-Kv_JATwr.js";import{E as I,h as M}from"./index-DN3E-Fmw.js";import{L as A}from"./LazyWhiteboard-CMBBxnFX.js";import{S as W}from"./StudentAssignmentEvalPanel-DHoZc3g0.js";import{an as C,o as z}from"./vendor-icons-hc6MjYFv.js";import{ActivityWorkspaceWidget as P}from"./ActivityWorkspaceWidget-0DUqiqFK.js";import"./vendor-utils-aM7aYvPP.js";import"./vendor-pdf-By0fofA0.js";import"./vendor-motion-BFAPOs-b.js";import"./vendor-charts-SrpBsvM3.js";import"./AssignmentFilePreview-Bk751Blz.js";class w{constructor(){this.providers=new Map}register(e){if(!e||!e.id)throw new Error("WorkspaceSlotRegistry Error: WorkspaceSlotProvider must have a valid ID.");this.providers.set(e.id,e)}unregister(e){return this.providers.delete(e)}getProviders(e){const t=Array.from(this.providers.values()).filter(s=>s.slot===e);return t.sort((s,i)=>(i.priority??0)-(s.priority??0)),Object.freeze(t)}listAll(){return Object.freeze(Array.from(this.providers.values()))}clear(){this.providers.clear()}}const k=d.createContext(null),N=({registry:n,children:e})=>{const[t]=d.useState(()=>n??new w),[,s]=d.useState(0),i=d.useCallback(()=>{s(l=>l+1)},[]),a=d.useCallback(l=>{t.register(l),i()},[t,i]),c=d.useCallback(l=>{t.unregister(l)&&i()},[t,i]),o=d.useCallback(l=>t.getProviders(l),[t]),g=d.useMemo(()=>({registry:t,registerSlotProvider:a,unregisterSlotProvider:c,getSlotProviders:o}),[t,a,c,o]);return r.jsx(k.Provider,{value:g,children:e})},_=()=>{const n=d.useContext(k);if(!n)throw new Error("useWorkspace must be used within a WorkspaceProvider");return n},D=n=>{const{getSlotProviders:e}=_();return e(n)},f=({slot:n,className:e})=>{const t=D(n);return t.length===0?null:r.jsx("div",{className:`workspace-slot workspace-slot-${n.toLowerCase()} ${e??""}`,children:t.map(s=>r.jsx(x.Fragment,{children:s.render()},s.id))})},L=({className:n,children:e})=>r.jsxs("div",{className:`workspace-shell flex flex-col h-screen w-screen overflow-hidden bg-slate-900 text-slate-100 ${n??""}`,children:[r.jsx(f,{slot:"TopBar",className:"w-full flex-none z-30"}),r.jsxs("div",{className:"flex flex-1 overflow-hidden relative",children:[r.jsx(f,{slot:"LeftSidebar",className:"flex-none z-20"}),r.jsxs("main",{className:"flex-1 relative overflow-hidden flex flex-col",children:[r.jsx(f,{slot:"MainCanvas",className:"flex-1 w-full h-full relative"}),e,r.jsx(f,{slot:"BottomPanel",className:"flex-none z-20"})]}),r.jsx(f,{slot:"RightSidebar",className:"flex-none z-20"})]}),r.jsx(f,{slot:"StatusBar",className:"w-full flex-none z-30"}),r.jsx(f,{slot:"FloatingArea",className:"absolute inset-0 pointer-events-none z-40"}),r.jsx(f,{slot:"DialogArea",className:"absolute inset-0 pointer-events-none z-50"})]});var b=(n=>(n[n.Immediate=0]="Immediate",n[n.High=1]="High",n[n.Normal=2]="Normal",n[n.Low=3]="Low",n[n.Idle=4]="Idle",n))(b||{});class E{constructor(){this.subscribers=new Map}subscribe(e,t){const s=String(e);this.subscribers.has(s)||this.subscribers.set(s,new Set);const i=this.subscribers.get(s);return i.add(t),()=>{i.delete(t)}}async publish(e,t,s="runtime.system"){const i={id:`evt_${globalThis.crypto.randomUUID()}`,type:e,payload:t,source:s,timestamp:Date.now()},a=this.subscribers.get(String(e))||new Set,c=this.subscribers.get("*")||new Set,o=[...Array.from(a),...Array.from(c)];return await Promise.all(o.map(g=>Promise.resolve(g(i)).catch(l=>{console.error(`[RuntimeEventBus] Error in subscriber for ${String(e)}:`,l)}))),i}clear(){this.subscribers.clear()}}class O{constructor(e=`rt_${globalThis.crypto.randomUUID()}`){this.listeners=new Set,this.currentState={runtime:{id:e,lifecycle:"Create",startTime:Date.now(),elapsedTime:0},lesson:{},stage:{index:0},activity:{},whiteboard:{objectCount:0,isLocked:!1},teachingObjects:[],students:[],plugin:[],ai:{isGenerating:!1},analytics:{totalInteractions:0,activeStudentCount:0,averageScore:0}}}getState(){return this.currentState}setState(e){this.currentState=Object.freeze(e),this.notify()}updateState(e){this.currentState=Object.freeze({...this.currentState,...e,runtime:e.runtime?{...this.currentState.runtime,...e.runtime}:this.currentState.runtime,lesson:e.lesson?{...this.currentState.lesson,...e.lesson}:this.currentState.lesson,stage:e.stage?{...this.currentState.stage,...e.stage}:this.currentState.stage,activity:e.activity?{...this.currentState.activity,...e.activity}:this.currentState.activity,whiteboard:e.whiteboard?{...this.currentState.whiteboard,...e.whiteboard}:this.currentState.whiteboard,ai:e.ai?{...this.currentState.ai,...e.ai}:this.currentState.ai,analytics:e.analytics?{...this.currentState.analytics,...e.analytics}:this.currentState.analytics}),this.notify()}setLifecycle(e){this.updateState({runtime:{...this.currentState.runtime,lifecycle:e}})}addStudent(e){this.currentState.students.some(s=>s.id===e.id)||this.updateState({students:Object.freeze([...this.currentState.students,e])})}removeStudent(e){this.updateState({students:Object.freeze(this.currentState.students.filter(t=>t.id!==e))})}subscribe(e){return this.listeners.add(e),e(this.getState()),()=>{this.listeners.delete(e)}}notify(){const e=this.getState();for(const t of this.listeners)try{t(e)}catch(s){console.error("[RuntimeStateManager] Listener error:",s)}}}class ${constructor(){this.services=new Map}registerService(e){if(!e.serviceId)throw new Error("[RuntimeServiceRegistry] Service must provide a non-empty serviceId.");this.services.set(e.serviceId,e)}async unregisterService(e){const t=this.services.get(e);return t?(await t.dispose(),this.services.delete(e)):!1}getService(e){return this.services.get(e)}listServices(){return Object.freeze(Array.from(this.services.keys()))}async initializeAll(e){for(const t of this.services.values())await t.initialize(e)}async disposeAll(){for(const e of this.services.values())try{await e.dispose()}catch(t){console.error(`[RuntimeServiceRegistry] Error disposing service ${e.serviceId}:`,t)}this.services.clear()}}class B{constructor(){this.modules=new Map,this.activeModuleIds=new Set}registerModule(e){if(!e.id)throw new Error("[RuntimeModuleRegistry] Module must specify a valid id.");this.modules.set(e.id,e)}async unregisterModule(e,t){const s=this.modules.get(e);return s?(this.activeModuleIds.has(e)&&t&&(await s.stop(t),this.activeModuleIds.delete(e)),await s.dispose(),this.modules.delete(e)):!1}getModule(e){return this.modules.get(e)}listModules(){return Object.freeze(Array.from(this.modules.values()))}async startAll(e){for(const t of this.modules.values())await t.initialize(e),await t.start(e),this.activeModuleIds.add(t.id)}async stopAll(e){for(const t of this.activeModuleIds){const s=this.modules.get(t);if(s)try{await s.stop(e)}catch(i){console.error(`[RuntimeModuleRegistry] Error stopping module ${t}:`,i)}}this.activeModuleIds.clear()}}class U{constructor(){this.taskQueue=[],this.isProcessing=!1,this.timer=null}schedule(e,t,s=b.Normal,i=0,a=3){return new Promise((c,o)=>{const g={id:`tsk_${globalThis.crypto.randomUUID()}`,name:e,priority:s,taskFn:async()=>{i>0&&await new Promise(l=>setTimeout(l,i));try{const l=await t();return c(l),l}catch(l){throw o(l),l}},delayMs:i,maxRetries:a,createdAt:Date.now()};this.taskQueue.push(g),this.sortQueue(),queueMicrotask(()=>{this.processQueue()})})}start(){this.timer||(this.timer=setInterval(()=>{this.processQueue()},50))}stop(){this.timer&&(clearInterval(this.timer),this.timer=null)}getPendingTaskCount(){return this.taskQueue.length}sortQueue(){this.taskQueue.sort((e,t)=>e.priority-t.priority||e.createdAt-t.createdAt)}async processQueue(){if(this.isProcessing||this.taskQueue.length===0)return;this.isProcessing=!0;const e=this.taskQueue.shift();if(e){let t=0;const s=e.maxRetries??1;for(;t<s;)try{await e.taskFn();break}catch(i){t+=1,t>=s&&console.error(`[RuntimeScheduler] Task ${e.name} (${e.id}) failed after ${t} attempts:`,i)}}this.isProcessing=!1}}class T{constructor(){this.rolePermissions=new Map([["Teacher",new Set(["lesson:control","stage:navigate","whiteboard:draw","quiz:submit","plugin:execute","ai:invoke","session:manage"])],["Assistant",new Set(["stage:navigate","whiteboard:draw","quiz:submit","plugin:execute","ai:invoke"])],["Student",new Set(["whiteboard:draw","quiz:submit","ai:invoke","plugin:execute"])],["Observer",new Set([])],["Plugin",new Set(["whiteboard:draw","plugin:execute","ai:invoke"])],["AI",new Set(["stage:navigate","whiteboard:draw","ai:invoke"])]])}hasPermission(e,t){const s=this.rolePermissions.get(e);return s?s.has(t):!1}grantPermission(e,t){this.rolePermissions.has(e)||this.rolePermissions.set(e,new Set),this.rolePermissions.get(e).add(t)}revokePermission(e,t){const s=this.rolePermissions.get(e);s&&s.delete(t)}getRolePermissions(e){const t=this.rolePermissions.get(e);return Object.freeze(t?Array.from(t):[])}}class F{constructor(){this.resources=new Map}registerResource(e,t,s){const i=Array.from(this.resources.values()).find(c=>c.url===e);if(i)return i;const a={id:`res_${globalThis.crypto.randomUUID()}`,type:t,url:e,sizeBytes:s,status:"loaded",cachedAt:Date.now()};return this.resources.set(a.id,a),a}getResource(e){return this.resources.get(e)}listResources(e){const t=Array.from(this.resources.values());return Object.freeze(e?t.filter(s=>s.type===e):t)}removeResource(e){return this.resources.delete(e)}clear(){this.resources.clear()}}class H{constructor(){this.snapshots=new Map}takeSnapshot(e,t,s,i){const a={snapshotId:`snap_${globalThis.crypto.randomUUID()}`,timestamp:Date.now(),stateTree:JSON.parse(JSON.stringify(e)),activeServices:[...t],loadedModules:[...s],resources:[...i]};return this.snapshots.set(a.snapshotId,a),this.lastAutoSnapshot=a,a}getLatestSnapshot(){return this.lastAutoSnapshot}getSnapshot(e){return this.snapshots.get(e)}clear(){this.snapshots.clear(),this.lastAutoSnapshot=void 0}}class Q{constructor(){this.hooks=new Map}registerHook(e,t){this.hooks.has(e)||this.hooks.set(e,new Set);const s=this.hooks.get(e);return s.add(t),()=>{s.delete(t)}}async executeHook(e,t,s){const i=this.hooks.get(e);if(!(!i||i.size===0))for(const a of i)try{await a(t,s)}catch(c){console.error(`[RuntimeHooksManager] Error executing hook "${e}":`,c)}}clear(){this.hooks.clear()}}class J{constructor(e,t){this.activeSession=null,this.eventBus=e,this.stateManager=t}async createSession(e,t,s,i){this.activeSession&&this.activeSession.status==="active"&&await this.destroySession();const a={sessionId:e,courseId:s,lessonId:i,teacher:t,students:Object.freeze([]),createdAt:Date.now(),status:"active"};return this.activeSession=a,this.stateManager.updateState({lesson:{activeLessonId:i,status:"ready"}}),await this.eventBus.publish("SessionCreated",{sessionId:e,teacherId:t.id,timestamp:Date.now()}),a}async joinSession(e){if(!this.activeSession||this.activeSession.status!=="active")return!1;const t=[...this.activeSession.students.filter(s=>s.id!==e.id),{...e,isOnline:!0}];return this.activeSession={...this.activeSession,students:Object.freeze(t)},this.stateManager.addStudent(e),await this.eventBus.publish("StudentJoined",{student:e,timestamp:Date.now()}),!0}async leaveSession(e){if(!this.activeSession||this.activeSession.status!=="active")return!1;const t=this.activeSession.students.map(s=>s.id===e?{...s,isOnline:!1}:s);return this.activeSession={...this.activeSession,students:Object.freeze(t)},this.stateManager.removeStudent(e),await this.eventBus.publish("StudentLeft",{studentId:e,timestamp:Date.now()}),!0}async reconnectSession(e){if(!this.activeSession||this.activeSession.status!=="active")return!1;const t=this.activeSession.students.find(i=>i.id===e);if(!t)return!1;const s={...t,isOnline:!0};return this.joinSession(s)}async destroySession(){this.activeSession&&(this.activeSession={...this.activeSession,status:"destroyed"},this.activeSession=null)}getActiveSession(){return this.activeSession}}class q{constructor(){this.eventCount=0}incrementEventCount(){this.eventCount+=1}getDiagnostics(e,t,s,i){const a=process.memoryUsage?process.memoryUsage().heapUsed/1048576:0;return{runtimeId:e.runtime.id,lifecycleState:e.runtime.lifecycle,activeServicesCount:t,registeredModulesCount:s,pendingSchedulerTasksCount:i,activeStudentsCount:e.students.length,totalInteractionsCount:e.analytics.totalInteractions,memoryUsageMB:Math.round(a*100)/100,timestamp:Date.now()}}}class K{constructor(e=`rt_${globalThis.crypto.randomUUID()}`){this.currentRole="Teacher",this.runtimeId=e,this.eventBus=new E,this.stateManager=new O(e),this.serviceRegistry=new $,this.moduleRegistry=new B,this.scheduler=new U,this.permissionManager=new T,this.resourceManager=new F,this.snapshotManager=new H,this.hooksManager=new Q,this.sessionManager=new J(this.eventBus,this.stateManager),this.monitor=new q,this.stateManager.setLifecycle("Create")}getContext(){const e=this.sessionManager.getActiveSession();return{runtimeId:this.runtimeId,sessionId:e?.sessionId||"sess_default",courseId:e?.courseId,lessonId:e?.lessonId,teacher:e?.teacher,currentUser:this.currentUser||e?.teacher,role:this.currentRole,permissions:this.permissionManager.getRolePermissions(this.currentRole),lifecycleState:this.stateManager.getState().runtime.lifecycle}}async initialize(){this.transitionState("Initialize"),this.scheduler.start();const e=this.getContext();await this.serviceRegistry.initializeAll(e),this.transitionState("Prepare")}async start(){const e=this.getContext();await this.hooksManager.executeHook("beforeLessonStart",{},e),this.transitionState("Running"),await this.moduleRegistry.startAll(e),await this.hooksManager.executeHook("afterLessonStart",{},e),await this.eventBus.publish("LessonStarted",{lessonId:e.lessonId||"les_active",timestamp:Date.now()}),this.takeSnapshot()}async pause(){this.transitionState("Pause"),this.scheduler.stop(),await this.eventBus.publish("RuntimePaused",{elapsedTime:this.stateManager.getState().runtime.elapsedTime,timestamp:Date.now()})}async resume(){this.transitionState("Resume"),this.scheduler.start(),this.transitionState("Running")}async stop(){const e=this.getContext();this.transitionState("Stop"),await this.moduleRegistry.stopAll(e),this.scheduler.stop(),this.takeSnapshot()}async dispose(){await this.stop(),this.transitionState("Dispose"),await this.serviceRegistry.disposeAll(),this.hooksManager.clear(),this.resourceManager.clear(),this.eventBus.clear()}transitionState(e){const t=this.stateManager.getState().runtime.lifecycle;this.stateManager.setLifecycle(e),this.eventBus.publish("LifecycleChanged",{from:t,to:e,timestamp:Date.now()})}registerModule(e){this.moduleRegistry.registerModule(e)}registerService(e){this.serviceRegistry.registerService(e)}registerRuntimeHook(e,t){return this.hooksManager.registerHook(e,t)}scheduleTask(e,t,s=b.Normal,i=0){return this.scheduler.schedule(e,t,s,i)}setUser(e){this.currentUser=e,this.currentRole=e.role}hasPermission(e){return this.permissionManager.hasPermission(this.currentRole,e)}takeSnapshot(){return this.snapshotManager.takeSnapshot(this.stateManager.getState(),this.serviceRegistry.listServices(),this.moduleRegistry.listModules().map(e=>e.id),this.resourceManager.listResources())}restoreLatestSnapshot(){const e=this.snapshotManager.getLatestSnapshot();return e?(this.stateManager.setState(e.stateTree),!0):!1}getDiagnostics(){return this.monitor.getDiagnostics(this.stateManager.getState(),this.serviceRegistry.listServices().length,this.moduleRegistry.listModules().length,this.scheduler.getPendingTaskCount())}}class V{constructor(e){this.init=e,this.kernel=new K,this.kernel.setUser({id:e.student.id,name:e.student.name,role:"Student",avatar:e.student.avatar,isOnline:!0,joinedAt:Date.now()})}getView(){const e=this.kernel.getContext();return{studentId:this.init.student.id,studentName:this.init.student.name,role:"Student",lessonId:this.init.lessonId??e.lessonId,courseId:this.init.courseId??e.courseId,permissions:e.permissions}}get permissions(){return this.kernel.getContext().permissions}hasPermission(e){return this.kernel.hasPermission(e)}subscribe(e,t){return this.kernel.eventBus.subscribe(e,s=>t(s.payload))}takeSnapshot(){return this.kernel.takeSnapshot()}restoreSnapshot(){return this.kernel.restoreLatestSnapshot()}get runtimeKernel(){return this.kernel}dispose(){return this.kernel.dispose()}}function G({isOpen:n,lang:e,activeStudentId:t,studentNotifications:s,readNotifications:i,setReadNotifications:a,studentDashboardData:c,setIsNotificationsOpen:o,setSelectedNotificationForModal:g}){if(!n)return null;const l=s.filter(u=>!i.has(u.id));return r.jsxs("div",{className:"absolute right-0 mt-2 w-80 bg-white border border-gray-200 shadow-lg rounded-xl z-50 overflow-hidden",children:[r.jsxs("div",{className:"p-3 border-b border-gray-100 flex justify-between items-center bg-gray-50",children:[r.jsx("h3",{className:"font-semibold text-gray-800",children:"Notifications"}),l.length>0&&r.jsx("button",{onClick:async()=>{if(!t)return;try{const h=s.filter(m=>!i.has(m.id)).map(m=>fetch(`/api/students/${t}/read_notifications`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({notificationId:m.id})}));await Promise.all(h)}catch(h){console.error(h)}const u=new Set(i);s.forEach(h=>u.add(h.id)),a(u)},className:"text-xs text-indigo-600 hover:text-indigo-800 font-medium",children:"Mark all as read"})]}),r.jsx("div",{className:"max-h-96 overflow-y-auto",children:s.length===0?r.jsx("div",{className:"p-4 text-center text-sm text-gray-500 italic",children:"No notifications."}):r.jsx("div",{className:"divide-y divide-gray-100",children:s.map(u=>{const h=!i.has(u.id);return r.jsx("div",{className:`p-3 hover:bg-gray-50 cursor-pointer ${h?"bg-indigo-50/30":""}`,onClick:()=>{if(h){t&&fetch(`/api/students/${t}/read_notifications`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({notificationId:u.id})}).catch(console.error);const S=new Set(i);S.add(u.id),a(S)}const m=c?.assignments?.find(S=>S.id===u.relatedId);g({...u,assignment:m}),o(!1)},children:r.jsxs("div",{className:"flex gap-3",children:[r.jsx("div",{className:"mt-0.5",children:u.type==="new_assignment"?r.jsx(C,{size:16,className:"text-indigo-500"}):r.jsx(z,{size:16,className:"text-green-500"})}),r.jsxs("div",{className:"flex-1",children:[r.jsx("div",{className:`text-sm ${h?"font-semibold text-gray-900":"font-medium text-gray-700"}`,children:u.title}),r.jsx("div",{className:`text-xs mt-0.5 ${h?"text-gray-600":"text-gray-500"}`,children:u.message})]}),h&&r.jsx("div",{className:"w-2 h-2 rounded-full bg-indigo-500 mt-1"})]})},u.id)})})})]})}const X=({widgetId:n="widget_ai_teacher_workspace",initialSection:e="Lesson Assistant",onActionExecute:t})=>{const[s,i]=d.useState({widgetId:n,visible:!0,pinned:!0,collapsed:!1,fullscreen:!1,dockPosition:"right",activeSection:e});if(!s.visible)return null;const a=["Lesson Assistant","Whiteboard Assistant","Resource Assistant","Activity Assistant","Student Assistant","Assessment Assistant","Summary Assistant","Plugin Assistant"],c=o=>{t&&t(o,{section:s.activeSection})};return r.jsxs("div",{"data-testid":"ai-teacher-workspace-widget",className:`ai-teacher-workspace-panel ${s.dockPosition} ${s.collapsed?"collapsed":""} ${s.fullscreen?"fullscreen":""}`,style:{border:"1px solid #e2e8f0",borderRadius:"8px",background:"#ffffff",padding:"12px",boxShadow:"0 4px 6px -1px rgba(0, 0, 0, 0.1)"},children:[r.jsxs("div",{className:"ai-widget-header",style:{display:"flex",justifyContent:"space-between",alignItems:"center",borderBottom:"1px solid #f1f5f9",paddingBottom:"8px",marginBottom:"8px"},children:[r.jsx("span",{style:{fontWeight:600,fontSize:"14px",color:"#1e293b"},children:"🤖 AI Teacher Assistant"}),r.jsxs("div",{className:"ai-widget-controls",style:{display:"flex",gap:"4px"},children:[r.jsx("button",{"data-testid":"btn-pin",onClick:()=>i(o=>({...o,pinned:!o.pinned})),style:{fontSize:"12px",cursor:"pointer"},children:s.pinned?"📌 Unpin":"📍 Pin"}),r.jsx("button",{"data-testid":"btn-dock",onClick:()=>i(o=>({...o,dockPosition:o.dockPosition==="right"?"float":"right"})),style:{fontSize:"12px",cursor:"pointer"},children:s.dockPosition==="right"?"🌊 Float":"📥 Dock"}),r.jsx("button",{"data-testid":"btn-fullscreen",onClick:()=>i(o=>({...o,fullscreen:!o.fullscreen})),style:{fontSize:"12px",cursor:"pointer"},children:s.fullscreen?"🗗 Restore":"⛶ Fullscreen"}),r.jsx("button",{"data-testid":"btn-collapse",onClick:()=>i(o=>({...o,collapsed:!o.collapsed})),style:{fontSize:"12px",cursor:"pointer"},children:s.collapsed?"➕ Expand":"➖ Collapse"})]})]}),!s.collapsed&&r.jsxs(r.Fragment,{children:[r.jsx("div",{className:"ai-section-tabs",style:{display:"flex",flexWrap:"wrap",gap:"4px",marginBottom:"12px"},children:a.map(o=>r.jsx("button",{"data-testid":`tab-${o.toLowerCase().replace(/\s+/g,"-")}`,onClick:()=>i(g=>({...g,activeSection:o})),style:{padding:"4px 8px",fontSize:"12px",borderRadius:"4px",border:"1px solid #cbd5e1",background:s.activeSection===o?"#3b82f6":"#f8fafc",color:s.activeSection===o?"#ffffff":"#334155",cursor:"pointer"},children:o},o))}),r.jsxs("div",{className:"ai-section-body",style:{minHeight:"100px"},children:[r.jsxs("div",{style:{fontSize:"13px",color:"#475569",marginBottom:"8px"},children:["Active Assistant: ",r.jsx("strong",{children:s.activeSection})]}),r.jsxs("div",{style:{display:"flex",gap:"8px",flexWrap:"wrap"},children:[r.jsx("button",{"data-testid":"btn-summarize",onClick:()=>c("ai_summarize_lesson"),style:{padding:"6px 12px",fontSize:"12px",background:"#eff6ff",border:"1px solid #bfdbfe",borderRadius:"4px",cursor:"pointer"},children:"📝 Summarize Content"}),r.jsx("button",{"data-testid":"btn-explain",onClick:()=>c("ai_explain_whiteboard"),style:{padding:"6px 12px",fontSize:"12px",background:"#f0fdf4",border:"1px solid #bbf7d0",borderRadius:"4px",cursor:"pointer"},children:"🎨 Explain Visuals"}),r.jsx("button",{"data-testid":"btn-quiz",onClick:()=>c("ai_generate_quiz"),style:{padding:"6px 12px",fontSize:"12px",background:"#fefce8",border:"1px solid #fef08a",borderRadius:"4px",cursor:"pointer"},children:"⚡ Generate Quiz"})]})]})]})]})};class Y{constructor(){this.resources=new Map,this.providers=new Map}registerProvider(e){if(!e||!e.id||!e.type)throw new Error("ResourceRegistry Error: ResourceProvider must have a valid ID and Type.");this.providers.set(e.type,e)}unregisterProvider(e){for(const[t,s]of this.providers.entries())if(s.id===e)return this.providers.delete(t);return!1}getProvider(e){return this.providers.get(e)}registerResource(e){if(!e||!e.id)throw new Error("ResourceRegistry Error: ResourceDescriptor must have a valid ID.");this.resources.set(e.id,{...e})}unregisterResource(e){return this.resources.delete(e)}getResource(e){return this.resources.get(e)}listResources(e){const t=Array.from(this.resources.values());return Object.freeze(e?t.filter(s=>s.type===e):t)}executeAction(e,t,s){const i=this.resources.get(e);if(!i)throw new Error(`ResourceRegistry Error: Resource '${e}' not found.`);const a=this.providers.get(i.type);switch(t){case"preview":return a?.preview?a.preview(i):{previewUrl:i.url};case"open":return a?.open?a.open(i):{openUrl:i.url};case"pin":return i.pinned=!i.pinned,{pinned:i.pinned};case"favorite":return i.favorited=!i.favorited,{favorited:i.favorited};case"annotate":return{annotate:!0,resourceId:e,params:s};case"share":return{shareUrl:i.url??`resource://${i.id}`};case"fullscreen":return{fullscreen:!0,resourceId:e};default:throw new Error(`ResourceRegistry Error: Unknown action '${t}'.`)}}clear(){this.resources.clear(),this.providers.clear()}}const y={padding:"12px",color:"#e2e8f0",fontSize:"13px"},Z=()=>r.jsx("div",{style:{width:"100%",height:"100%"},children:r.jsx(A,{})}),ee=({context:n})=>{const e=n.getView();return r.jsxs("div",{style:y,children:[r.jsx("div",{style:{fontWeight:600,marginBottom:6},children:"📘 Lesson"}),r.jsxs("div",{children:["Lesson ID: ",e.lessonId??"—"]}),r.jsxs("div",{children:["Course ID: ",e.courseId??"—"]})]})},te=()=>{const[n]=d.useState(()=>new Y().listResources());return r.jsxs("div",{style:y,children:[r.jsx("div",{style:{fontWeight:600,marginBottom:6},children:"📂 Resources"}),n.length===0?r.jsx("div",{style:{opacity:.7},children:"No resources shared yet."}):r.jsx("ul",{children:n.map(e=>r.jsxs("li",{children:[e.type,": ",e.url]},e.id))})]})},se=({studentId:n,lang:e="en"})=>r.jsx(P,{role:"student",actorId:n,lang:e}),ie=({studentId:n,lessonId:e,lang:t="en"})=>e?r.jsx(W,{lessonId:e,studentId:n,lang:t,addToast:()=>{}}):r.jsx("div",{style:y,children:"No active lesson — assignments unavailable."}),re=({context:n})=>{const[e,t]=d.useState([]);return d.useEffect(()=>{const s=n.subscribe("LessonStarted",a=>{t(c=>[...c,{id:`ls_${a.timestamp}`,text:`Lesson started (${a.lessonId})`}])}),i=n.subscribe("StageChanged",a=>{t(c=>[...c,{id:`sc_${a.timestamp}`,text:`Stage changed → ${a.toStageId}`}])});return()=>{s(),i()}},[n]),r.jsxs("div",{style:y,children:[r.jsx("div",{style:{fontWeight:600,marginBottom:6},children:"📣 Teacher Broadcast"}),e.length===0?r.jsx("div",{style:{opacity:.7},children:"No announcements yet."}):r.jsx("ul",{children:e.map(s=>r.jsx("li",{children:s.text},s.id))})]})},ne=()=>r.jsx(X,{}),ae=({studentId:n,lang:e="en"})=>{const[t,s]=d.useState(!0),[i,a]=d.useState(new Set),[c]=d.useState([]);return r.jsx(G,{isOpen:t,lang:e,activeStudentId:n,studentNotifications:c,readNotifications:i,setReadNotifications:a,studentDashboardData:null,setIsNotificationsOpen:s,setSelectedNotificationForModal:()=>{}})},oe=()=>r.jsx(I,{slot:"student.view"});class ce{constructor(e){this.registeredWidgets=new Map,this.slotRegistry=e??new w}get slotRegistryRef(){return this.slotRegistry}registerWidget(e){if(!e||!e.id)throw new Error("StudentWidgetRegistry Error: StudentWidgetDescriptor must have a valid ID.");this.registeredWidgets.set(e.id,e);const t=e.component;this.slotRegistry.register({id:e.id,slot:e.slot,priority:e.priority??10,render:s=>x.createElement(t,s)})}unregisterWidget(e){const t=this.registeredWidgets.delete(e);return t&&this.slotRegistry.unregister(e),t}registerDefaultWidgets(e){const{studentId:t,lessonId:s,lang:i="en",context:a,pluginHost:c}=e;this.mountWidget("widget_student_whiteboard","Whiteboard","MainCanvas",Z,{},"official"),this.mountWidget("widget_student_lesson","Lesson","LeftSidebar",ee,{context:a},"official"),this.mountWidget("widget_student_resources","Resources","LeftSidebar",te,{},"official"),this.mountWidget("widget_student_activities","Activities","BottomPanel",se,{},"official"),this.mountWidget("widget_student_assignments","Assignments","RightSidebar",ie,{studentId:t,lessonId:s,lang:i},"official",20),this.mountWidget("widget_student_ai_assistant","AI Learning Assistant","RightSidebar",ne,{},"official",10),this.mountWidget("widget_student_broadcast","Teacher Broadcast","BottomPanel",re,{context:a},"official"),this.mountWidget("widget_student_notifications","Notifications","FloatingArea",ae,{studentId:t,lang:i},"official"),c&&this.mountWidget("widget_student_plugins","Plugin Widgets","FloatingArea",oe,{},"plugin")}getWidget(e){return this.registeredWidgets.get(e)}listWidgets(){return Object.freeze(Array.from(this.registeredWidgets.values()))}clear(){for(const e of this.registeredWidgets.keys())this.slotRegistry.unregister(e);this.registeredWidgets.clear()}mountWidget(e,t,s,i,a,c,o=10){this.registeredWidgets.set(e,{id:e,name:t,slot:s,component:i,provider:c,priority:o}),this.slotRegistry.register({id:e,slot:s,priority:o,render:()=>x.createElement(i,a)})}}const R="openlearn_student_workspace_";function le(n){return{studentId:n,openedWidgets:[],layout:{},selectedResourceIds:[],activityState:{},aiConversation:[],updatedAt:Date.now()}}class de{constructor(e){this.key=`${R}${e}`}load(){try{if(typeof localStorage>"u")return null;const e=localStorage.getItem(this.key);if(!e)return null;const t=JSON.parse(e);return t.studentId!==this.studentIdFromKey()?null:t}catch{return null}}save(e){try{if(typeof localStorage>"u")return;localStorage.setItem(this.key,JSON.stringify({...e,updatedAt:Date.now()}))}catch{}}update(e){const s={...this.load()??le(this.studentIdFromKey()),...e,updatedAt:Date.now()};return this.save(s),s}clear(){try{if(typeof localStorage>"u")return;localStorage.removeItem(this.key)}catch{}}studentIdFromKey(){return this.key.slice(R.length)}}const be=n=>{const{student:e,lessonId:t,courseId:s,teacherId:i,teacherName:a,lang:c="en",autoRestore:o=!0,pluginHost:g}=n,[l]=d.useState(()=>new V({student:e,lessonId:t,courseId:s,teacherId:i,teacherName:a,lang:c})),[u]=d.useState(()=>new w),[h]=d.useState(()=>{const p=new ce(u);return p.registerDefaultWidgets({studentId:e.id,lessonId:t,lang:c,context:l,pluginHost:g}),p}),[m]=d.useState(()=>new de(e.id));d.useEffect(()=>{if(!o)return;l.restoreSnapshot();const p=m.load();p?.currentLessonId&&p.currentLessonId},[o,l,m]),d.useEffect(()=>{const p=l.subscribe("LessonStarted",v=>{m.update({currentLessonId:v.lessonId})});return()=>{p()}},[l,m]),d.useEffect(()=>()=>{l.takeSnapshot();const p=h.listWidgets().map(j=>j.id),v=m.load();m.save({studentId:e.id,openedWidgets:p,layout:v?.layout??{},currentLessonId:v?.currentLessonId??t,selectedResourceIds:v?.selectedResourceIds??[],activityState:v?.activityState??{},aiConversation:v?.aiConversation??[],updatedAt:Date.now()}),l.dispose()},[l,m,h,e.id,t]);const S=r.jsx(N,{registry:u,children:r.jsx(L,{})});return g?r.jsx(M,{host:g,children:S}):S};export{be as StudentWorkspace,be as default};
|
package/dist/index.html
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
property="og:description"
|
|
14
14
|
content="An AI-powered Learning Management System featuring interactive classrooms, stage management, real-time student response systems, lesson editor, auto-grading, and an extensible third-party plugin ecosystem."
|
|
15
15
|
/>
|
|
16
|
-
<script type="module" crossorigin src="/assets/index-
|
|
16
|
+
<script type="module" crossorigin src="/assets/index-DN3E-Fmw.js"></script>
|
|
17
17
|
<link rel="modulepreload" crossorigin href="/assets/vendor-utils-aM7aYvPP.js">
|
|
18
18
|
<link rel="modulepreload" crossorigin href="/assets/vendor-react-Kv_JATwr.js">
|
|
19
19
|
<link rel="modulepreload" crossorigin href="/assets/vendor-icons-hc6MjYFv.js">
|
package/dist/server.cjs
CHANGED
|
@@ -3578,6 +3578,19 @@ var init_errors2 = __esm({
|
|
|
3578
3578
|
function scopedId(scopeType, scopeId, key) {
|
|
3579
3579
|
return `${scopeType}\0${scopeId}\0${key}`;
|
|
3580
3580
|
}
|
|
3581
|
+
function getSharedState(db2, manifestId) {
|
|
3582
|
+
let byManifest = sharedStates.get(db2);
|
|
3583
|
+
if (!byManifest) {
|
|
3584
|
+
byManifest = /* @__PURE__ */ new Map();
|
|
3585
|
+
sharedStates.set(db2, byManifest);
|
|
3586
|
+
}
|
|
3587
|
+
let state = byManifest.get(manifestId);
|
|
3588
|
+
if (!state) {
|
|
3589
|
+
state = { values: /* @__PURE__ */ new Map(), instances: /* @__PURE__ */ new Set() };
|
|
3590
|
+
byManifest.set(manifestId, state);
|
|
3591
|
+
}
|
|
3592
|
+
return state;
|
|
3593
|
+
}
|
|
3581
3594
|
function normalizeWriteScope(scope) {
|
|
3582
3595
|
const scopeType = scope?.scopeType ?? "platform";
|
|
3583
3596
|
const scopeId = scope?.scopeId?.trim() ?? "";
|
|
@@ -3586,21 +3599,29 @@ function normalizeWriteScope(scope) {
|
|
|
3586
3599
|
}
|
|
3587
3600
|
return { scopeType, scopeId: scopeType === "platform" ? "" : scopeId };
|
|
3588
3601
|
}
|
|
3589
|
-
var CONFIG_SCOPE_TYPES, ConfigService;
|
|
3602
|
+
var CONFIG_SCOPE_TYPES, sharedStates, instanceCleanup, ConfigService;
|
|
3590
3603
|
var init_config_service = __esm({
|
|
3591
3604
|
"packages/core/plugin-host/config-service.ts"() {
|
|
3592
3605
|
"use strict";
|
|
3593
3606
|
CONFIG_SCOPE_TYPES = ["platform", "school", "class"];
|
|
3607
|
+
sharedStates = /* @__PURE__ */ new WeakMap();
|
|
3608
|
+
instanceCleanup = new FinalizationRegistry(
|
|
3609
|
+
({ state, ref }) => {
|
|
3610
|
+
state.instances.delete(ref);
|
|
3611
|
+
}
|
|
3612
|
+
);
|
|
3594
3613
|
ConfigService = class _ConfigService {
|
|
3595
3614
|
constructor(db2, manifest) {
|
|
3596
|
-
/**
|
|
3597
|
-
this.values = /* @__PURE__ */ new Map();
|
|
3598
|
-
/** Change listeners. */
|
|
3615
|
+
/** Change listeners(每个实例独立,广播时按实例分发)。 */
|
|
3599
3616
|
this.listeners = /* @__PURE__ */ new Set();
|
|
3600
3617
|
this.tableEnsured = false;
|
|
3601
3618
|
this.db = db2;
|
|
3602
3619
|
this.manifestId = manifest.id;
|
|
3603
3620
|
this.schema = manifest.configuration?.properties ?? {};
|
|
3621
|
+
this.state = getSharedState(db2, manifest.id);
|
|
3622
|
+
const ref = new WeakRef(this);
|
|
3623
|
+
this.state.instances.add(ref);
|
|
3624
|
+
instanceCleanup.register(this, { state: this.state, ref });
|
|
3604
3625
|
}
|
|
3605
3626
|
static {
|
|
3606
3627
|
this.KEY_PREFIX = "config:";
|
|
@@ -3663,21 +3684,21 @@ var init_config_service = __esm({
|
|
|
3663
3684
|
}
|
|
3664
3685
|
readScoped(key, scopeType, scopeId) {
|
|
3665
3686
|
const cacheKey2 = scopedId(scopeType, scopeId, key);
|
|
3666
|
-
if (this.values.has(cacheKey2)) {
|
|
3667
|
-
const cached3 = this.values.get(cacheKey2);
|
|
3687
|
+
if (this.state.values.has(cacheKey2)) {
|
|
3688
|
+
const cached3 = this.state.values.get(cacheKey2);
|
|
3668
3689
|
return cached3 === null ? { found: false } : { found: true, value: cached3 };
|
|
3669
3690
|
}
|
|
3670
3691
|
const row = this.queryScoped(key, scopeType, scopeId);
|
|
3671
3692
|
if (!row) {
|
|
3672
|
-
this.values.set(cacheKey2, null);
|
|
3693
|
+
this.state.values.set(cacheKey2, null);
|
|
3673
3694
|
return { found: false };
|
|
3674
3695
|
}
|
|
3675
3696
|
try {
|
|
3676
3697
|
const parsed = JSON.parse(row.value);
|
|
3677
|
-
this.values.set(cacheKey2, parsed);
|
|
3698
|
+
this.state.values.set(cacheKey2, parsed);
|
|
3678
3699
|
return { found: true, value: parsed };
|
|
3679
3700
|
} catch {
|
|
3680
|
-
this.values.set(cacheKey2, row.value);
|
|
3701
|
+
this.state.values.set(cacheKey2, row.value);
|
|
3681
3702
|
return { found: true, value: row.value };
|
|
3682
3703
|
}
|
|
3683
3704
|
}
|
|
@@ -3693,21 +3714,21 @@ var init_config_service = __esm({
|
|
|
3693
3714
|
}
|
|
3694
3715
|
readLegacyPlatformValue(key) {
|
|
3695
3716
|
const cacheKey2 = scopedId("platform", "", `legacy:${key}`);
|
|
3696
|
-
if (this.values.has(cacheKey2)) {
|
|
3697
|
-
const cached3 = this.values.get(cacheKey2);
|
|
3717
|
+
if (this.state.values.has(cacheKey2)) {
|
|
3718
|
+
const cached3 = this.state.values.get(cacheKey2);
|
|
3698
3719
|
return cached3 === null ? { found: false } : { found: true, value: cached3 };
|
|
3699
3720
|
}
|
|
3700
3721
|
try {
|
|
3701
3722
|
const row = this.db.prepare("SELECT value FROM plugin_storage WHERE plugin_id = ? AND key = ?").get(this.manifestId, _ConfigService.KEY_PREFIX + key);
|
|
3702
3723
|
if (!row) {
|
|
3703
|
-
this.values.set(cacheKey2, null);
|
|
3724
|
+
this.state.values.set(cacheKey2, null);
|
|
3704
3725
|
return { found: false };
|
|
3705
3726
|
}
|
|
3706
3727
|
const parsed = JSON.parse(row.value);
|
|
3707
|
-
this.values.set(cacheKey2, parsed);
|
|
3728
|
+
this.state.values.set(cacheKey2, parsed);
|
|
3708
3729
|
return { found: true, value: parsed };
|
|
3709
3730
|
} catch {
|
|
3710
|
-
this.values.set(cacheKey2, null);
|
|
3731
|
+
this.state.values.set(cacheKey2, null);
|
|
3711
3732
|
return { found: false };
|
|
3712
3733
|
}
|
|
3713
3734
|
}
|
|
@@ -3763,8 +3784,8 @@ var init_config_service = __esm({
|
|
|
3763
3784
|
const { scopeType, scopeId } = normalizeWriteScope(scope);
|
|
3764
3785
|
this.assertScopeAllowed(key, scopeType);
|
|
3765
3786
|
const cacheKey2 = scopedId(scopeType, scopeId, key);
|
|
3766
|
-
const previous = this.values.get(cacheKey2);
|
|
3767
|
-
this.values.set(cacheKey2, value);
|
|
3787
|
+
const previous = this.state.values.get(cacheKey2);
|
|
3788
|
+
this.state.values.set(cacheKey2, value);
|
|
3768
3789
|
try {
|
|
3769
3790
|
this.ensureTable();
|
|
3770
3791
|
this.db.prepare(
|
|
@@ -3775,16 +3796,32 @@ var init_config_service = __esm({
|
|
|
3775
3796
|
).run(this.manifestId, scopeType, scopeId, key, JSON.stringify(value), Date.now());
|
|
3776
3797
|
} catch (e) {
|
|
3777
3798
|
if (previous === void 0) {
|
|
3778
|
-
this.values.delete(cacheKey2);
|
|
3799
|
+
this.state.values.delete(cacheKey2);
|
|
3779
3800
|
} else {
|
|
3780
|
-
this.values.set(cacheKey2, previous);
|
|
3801
|
+
this.state.values.set(cacheKey2, previous);
|
|
3781
3802
|
}
|
|
3782
3803
|
throw e;
|
|
3783
3804
|
}
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
|
|
3805
|
+
this.broadcast(key, value, previous ?? void 0);
|
|
3806
|
+
}
|
|
3807
|
+
/**
|
|
3808
|
+
* 通知所有共享同一 db + manifest.id 的活跃实例(含自身)。
|
|
3809
|
+
*
|
|
3810
|
+
* 管理端通过临时实例写入时,运行中插件的 `onChange` 也因此能收到。
|
|
3811
|
+
* 已回收的实例在遍历时顺手清理。
|
|
3812
|
+
*/
|
|
3813
|
+
broadcast(key, newValue, oldValue) {
|
|
3814
|
+
for (const ref of this.state.instances) {
|
|
3815
|
+
const instance = ref.deref();
|
|
3816
|
+
if (!instance) {
|
|
3817
|
+
this.state.instances.delete(ref);
|
|
3818
|
+
continue;
|
|
3819
|
+
}
|
|
3820
|
+
for (const listener of instance.listeners) {
|
|
3821
|
+
try {
|
|
3822
|
+
listener(key, newValue, oldValue);
|
|
3823
|
+
} catch {
|
|
3824
|
+
}
|
|
3788
3825
|
}
|
|
3789
3826
|
}
|
|
3790
3827
|
}
|
|
@@ -3817,11 +3854,12 @@ var init_config_service = __esm({
|
|
|
3817
3854
|
* 插件在 `activate()` 里通常会马上读配置,提前填充避免每条配置一次查询。
|
|
3818
3855
|
*/
|
|
3819
3856
|
loadFromDB() {
|
|
3857
|
+
this.state.values.clear();
|
|
3820
3858
|
try {
|
|
3821
3859
|
const rows = this.db.prepare(`SELECT scope_type, scope_id, key, value FROM ${_ConfigService.TABLE} WHERE plugin_id = ?`).all(this.manifestId);
|
|
3822
3860
|
for (const row of rows) {
|
|
3823
3861
|
const scopeType = CONFIG_SCOPE_TYPES.includes(row.scope_type) ? row.scope_type : "platform";
|
|
3824
|
-
this.values.set(scopedId(scopeType, row.scope_id ?? "", row.key), this.parseStored(row.value));
|
|
3862
|
+
this.state.values.set(scopedId(scopeType, row.scope_id ?? "", row.key), this.parseStored(row.value));
|
|
3825
3863
|
}
|
|
3826
3864
|
} catch {
|
|
3827
3865
|
}
|
|
@@ -3830,7 +3868,7 @@ var init_config_service = __esm({
|
|
|
3830
3868
|
const rows = this.db.prepare(`SELECT key, value FROM plugin_storage WHERE plugin_id = ? AND key LIKE ?`).all(this.manifestId, prefix + "%");
|
|
3831
3869
|
for (const row of rows) {
|
|
3832
3870
|
const key = row.key.slice(prefix.length);
|
|
3833
|
-
this.values.set(scopedId("platform", "", `legacy:${key}`), this.parseStored(row.value));
|
|
3871
|
+
this.state.values.set(scopedId("platform", "", `legacy:${key}`), this.parseStored(row.value));
|
|
3834
3872
|
}
|
|
3835
3873
|
} catch {
|
|
3836
3874
|
}
|
|
@@ -3842,9 +3880,9 @@ var init_config_service = __esm({
|
|
|
3842
3880
|
return raw;
|
|
3843
3881
|
}
|
|
3844
3882
|
}
|
|
3845
|
-
/**
|
|
3883
|
+
/** 测试/诊断:当前共享缓存里的作用域条目数。 */
|
|
3846
3884
|
cachedEntryCount() {
|
|
3847
|
-
return this.values.size;
|
|
3885
|
+
return this.state.values.size;
|
|
3848
3886
|
}
|
|
3849
3887
|
};
|
|
3850
3888
|
}
|
|
@@ -20999,7 +21037,7 @@ var import_node_fs11 = __toESM(require("node:fs"), 1);
|
|
|
20999
21037
|
var import_node_path13 = __toESM(require("node:path"), 1);
|
|
21000
21038
|
var import_node_url5 = require("node:url");
|
|
21001
21039
|
var import_meta7 = {};
|
|
21002
|
-
var FALLBACK_VERSION = "0.7.
|
|
21040
|
+
var FALLBACK_VERSION = "0.7.6";
|
|
21003
21041
|
function readPlatformVersion() {
|
|
21004
21042
|
const bases = [];
|
|
21005
21043
|
if (typeof __dirname !== "undefined") bases.push(__dirname);
|