@vue-skuilder/platform-ui 0.2.36 → 0.2.37
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/{About-74htcNLT.js → About-BkMENQtD.js} +2 -2
- package/dist/assets/{About-74htcNLT.js.map → About-BkMENQtD.js.map} +1 -1
- package/dist/assets/{AdminDashboard-B00qno2M.js → AdminDashboard-5R-xgY-M.js} +2 -2
- package/dist/assets/{AdminDashboard-B00qno2M.js.map → AdminDashboard-5R-xgY-M.js.map} +1 -1
- package/dist/assets/{AdminLearner-SJNtSyJ_.js → AdminLearner-Br10DFSt.js} +2 -2
- package/dist/assets/{AdminLearner-SJNtSyJ_.js.map → AdminLearner-Br10DFSt.js.map} +1 -1
- package/dist/assets/BaseUserDB-BRgNfdWx.js +1 -0
- package/dist/assets/{ClassroomCtrlPanel-BSAG3h8B.js → ClassroomCtrlPanel-DQJQ0B7M.js} +2 -2
- package/dist/assets/{ClassroomCtrlPanel-BSAG3h8B.js.map → ClassroomCtrlPanel-DQJQ0B7M.js.map} +1 -1
- package/dist/assets/{Classrooms-DXNEbtsM.js → Classrooms-B5tQPFEE.js} +2 -2
- package/dist/assets/{Classrooms-DXNEbtsM.js.map → Classrooms-B5tQPFEE.js.map} +1 -1
- package/dist/assets/CouchDBSyncStrategy-0EgX8AAz.js +1 -0
- package/dist/assets/{CourseRouter-CsVa_92r.js → CourseRouter-W6S7DpZN.js} +2 -2
- package/dist/assets/{CourseRouter-CsVa_92r.js.map → CourseRouter-W6S7DpZN.js.map} +1 -1
- package/dist/assets/CourseSyncService-CrHp0wsl.js +1 -0
- package/dist/assets/{CourseWare-BTFRjgBR-DBp_fIwH.js → CourseWare-BTFRjgBR-lpqcYpAW.js} +3 -3
- package/dist/assets/{CourseWare-BTFRjgBR-DBp_fIwH.js.map → CourseWare-BTFRjgBR-lpqcYpAW.js.map} +1 -1
- package/dist/assets/{Courses-CB6GGtdm.js → Courses-TzwgFdwn.js} +2 -2
- package/dist/assets/{Courses-CB6GGtdm.js.map → Courses-TzwgFdwn.js.map} +1 -1
- package/dist/assets/{EloModeration-DUBgs-Vh.js → EloModeration-BCz7VKla.js} +2 -2
- package/dist/assets/{EloModeration-DUBgs-Vh.js.map → EloModeration-BCz7VKla.js.map} +1 -1
- package/dist/assets/{JoinCode-BBvYapCA.js → JoinCode-CEeZkFOp.js} +2 -2
- package/dist/assets/{JoinCode-BBvYapCA.js.map → JoinCode-CEeZkFOp.js.map} +1 -1
- package/dist/assets/{NewCourseDialog-DBgFVnt3.js → NewCourseDialog-DNzvvz_H.js} +2 -2
- package/dist/assets/{NewCourseDialog-DBgFVnt3.js.map → NewCourseDialog-DNzvvz_H.js.map} +1 -1
- package/dist/assets/{PipelineDebugger-BHaqzqZw.js → PipelineDebugger-DhnKlMgD.js} +3 -3
- package/dist/assets/{PipelineDebugger-BHaqzqZw.js.map → PipelineDebugger-DhnKlMgD.js.map} +1 -1
- package/dist/assets/PouchDataLayerProvider-CAuhZYIc.js +2 -0
- package/dist/assets/PouchDataLayerProvider-CAuhZYIc.js.map +1 -0
- package/dist/assets/{ReleaseNotes-Dg6mLL1C.js → ReleaseNotes-Bvw24QDI.js} +2 -2
- package/dist/assets/{ReleaseNotes-Dg6mLL1C.js.map → ReleaseNotes-Bvw24QDI.js.map} +1 -1
- package/dist/assets/{RequestPasswordReset-CcFMTnTc.js → RequestPasswordReset-YOq1JJ_o.js} +2 -2
- package/dist/assets/{RequestPasswordReset-CcFMTnTc.js.map → RequestPasswordReset-YOq1JJ_o.js.map} +1 -1
- package/dist/assets/{ResetPassword-Dw_cBJU2.js → ResetPassword-CQCb3B_Z.js} +2 -2
- package/dist/assets/{ResetPassword-Dw_cBJU2.js.map → ResetPassword-CQCb3B_Z.js.map} +1 -1
- package/dist/assets/{StaticDataLayerProvider-CtgU3VBh.js → StaticDataLayerProvider-DbGLKpb1.js} +2 -2
- package/dist/assets/{StaticDataLayerProvider-CtgU3VBh.js.map → StaticDataLayerProvider-DbGLKpb1.js.map} +1 -1
- package/dist/assets/{Study-DDsGQCTJ.js → Study-D2mXR27s.js} +2 -2
- package/dist/assets/{Study-DDsGQCTJ.js.map → Study-D2mXR27s.js.map} +1 -1
- package/dist/assets/{TagInformation-DBLoXA2a.js → TagInformation-DHw5jRNt.js} +2 -2
- package/dist/assets/{TagInformation-DBLoXA2a.js.map → TagInformation-DHw5jRNt.js.map} +1 -1
- package/dist/assets/{User-DfXUXjFb.js → User-C0xEcKaU.js} +2 -2
- package/dist/assets/{User-DfXUXjFb.js.map → User-C0xEcKaU.js.map} +1 -1
- package/dist/assets/{UserDBDebugger-DPqlc4O_.js → UserDBDebugger-BXLoT28w.js} +2 -2
- package/dist/assets/{UserDBDebugger-DPqlc4O_.js.map → UserDBDebugger-BXLoT28w.js.map} +1 -1
- package/dist/assets/UserDBDebugger-DHONc_-t.js +1 -0
- package/dist/assets/{UserStats-C04YVYv7.js → UserStats-Blm_daoT.js} +2 -2
- package/dist/assets/{UserStats-C04YVYv7.js.map → UserStats-Blm_daoT.js.map} +1 -1
- package/dist/assets/{VerifyEmail-Y05BSR57.js → VerifyEmail-DTwA6sFY.js} +2 -2
- package/dist/assets/{VerifyEmail-Y05BSR57.js.map → VerifyEmail-DTwA6sFY.js.map} +1 -1
- package/dist/assets/{admin-DcFjvYJw.js → admin-hzNSXp0o.js} +2 -2
- package/dist/assets/{admin-DcFjvYJw.js.map → admin-hzNSXp0o.js.map} +1 -1
- package/dist/assets/{chess-E2uOAyQS-BZi0vihz.js → chess-E2uOAyQS-DDTgqZMJ.js} +2 -2
- package/dist/assets/{chess-E2uOAyQS-BZi0vihz.js.map → chess-E2uOAyQS-DDTgqZMJ.js.map} +1 -1
- package/dist/assets/chess-Lfm7Fsvs-RaFhFnIh.js +1 -0
- package/dist/assets/{contentSource-DwznMSMs.js → contentSource-FGhM_ge8.js} +8 -8
- package/dist/assets/contentSource-FGhM_ge8.js.map +1 -0
- package/dist/assets/dist-5Z6GjIpR.js +1 -0
- package/dist/assets/{dist-CAHkbl3M.js → dist-BJLhyG8r.js} +3 -3
- package/dist/assets/{dist-CAHkbl3M.js.map → dist-BJLhyG8r.js.map} +1 -1
- package/dist/assets/dist-C5-_1l75.js +1 -0
- package/dist/assets/{dist-BsRvt0gH.js → dist-C8EQUxWW.js} +3 -3
- package/dist/assets/{dist-BsRvt0gH.js.map → dist-C8EQUxWW.js.map} +1 -1
- package/dist/assets/{dist-Dp6eBE2S.js → dist-DGbpjpKB.js} +2 -2
- package/dist/assets/{dist-Dp6eBE2S.js.map → dist-DGbpjpKB.js.map} +1 -1
- package/dist/assets/dist-M4o-5YLq.js +1 -0
- package/dist/assets/edit-ui.es-BT7ftsox.js +1 -0
- package/dist/assets/{edit-ui.es-UaQFL_My.js → edit-ui.es-DJgikaS7.js} +2 -2
- package/dist/assets/{edit-ui.es-UaQFL_My.js.map → edit-ui.es-DJgikaS7.js.map} +1 -1
- package/dist/assets/elo-5Z2qkwtw.js +1 -0
- package/dist/assets/{elo-COgu6Hu5.js → elo-C4ZdbMIJ.js} +2 -2
- package/dist/assets/{elo-COgu6Hu5.js.map → elo-C4ZdbMIJ.js.map} +1 -1
- package/dist/assets/{eloDistance-BV7vfiK5.js → eloDistance-CRxxdYPJ.js} +2 -2
- package/dist/assets/{eloDistance-BV7vfiK5.js.map → eloDistance-CRxxdYPJ.js.map} +1 -1
- package/dist/assets/eloDistance-DF4pmPOI.js +1 -0
- package/dist/assets/{french-Dk7YG8Td-CD7Hms2j.js → french-Dk7YG8Td-C2awfrzV.js} +2 -2
- package/dist/assets/{french-Dk7YG8Td-CD7Hms2j.js.map → french-Dk7YG8Td-C2awfrzV.js.map} +1 -1
- package/dist/assets/french-evUMlbbq-4izQyIk4.js +1 -0
- package/dist/assets/{hierarchyDefinition-DLC5DjaA.js → hierarchyDefinition-BbqnGEfR.js} +2 -2
- package/dist/assets/{hierarchyDefinition-DLC5DjaA.js.map → hierarchyDefinition-BbqnGEfR.js.map} +1 -1
- package/dist/assets/{index-CK-rfSIx.js → index-BRaeVAZb.js} +4 -4
- package/dist/assets/{index-CK-rfSIx.js.map → index-BRaeVAZb.js.map} +1 -1
- package/dist/assets/{interferenceMitigator-D78KgOxz.js → interferenceMitigator-BtAQm-0d.js} +2 -2
- package/dist/assets/{interferenceMitigator-D78KgOxz.js.map → interferenceMitigator-BtAQm-0d.js.map} +1 -1
- package/dist/assets/math-DR3fXDcX-Bj6dcLDF.js +1 -0
- package/dist/assets/{math-D_8HiEQR-E4QDLrWp.js → math-D_8HiEQR-CQynsoTm.js} +2 -2
- package/dist/assets/{math-D_8HiEQR-E4QDLrWp.js.map → math-D_8HiEQR-CQynsoTm.js.map} +1 -1
- package/dist/assets/piano-BN5Btq91-onBUnVgA.js +1 -0
- package/dist/assets/{piano-DF1g6yaX-Ck3BH5EK.js → piano-DF1g6yaX-vXbP1jX_.js} +2 -2
- package/dist/assets/{piano-DF1g6yaX-Ck3BH5EK.js.map → piano-DF1g6yaX-vXbP1jX_.js.map} +1 -1
- package/dist/assets/{pitch-CgGJFkZ1-Dd0LzmEe.js → pitch-CgGJFkZ1-DVzOpIVa.js} +2 -2
- package/dist/assets/{pitch-CgGJFkZ1-Dd0LzmEe.js.map → pitch-CgGJFkZ1-DVzOpIVa.js.map} +1 -1
- package/dist/assets/pitch-Dn0iNqiS-L2k4d66s.js +1 -0
- package/dist/assets/prescribed-Cjutx2ct.js +2 -0
- package/dist/assets/prescribed-Cjutx2ct.js.map +1 -0
- package/dist/assets/{relativePriority-DwPMECAp.js → relativePriority-fx6BMJm9.js} +2 -2
- package/dist/assets/{relativePriority-DwPMECAp.js.map → relativePriority-fx6BMJm9.js.map} +1 -1
- package/dist/assets/{server-CqR7xtQy.js → server-CUqWbXPL.js} +2 -2
- package/dist/assets/{server-CqR7xtQy.js.map → server-CUqWbXPL.js.map} +1 -1
- package/dist/assets/{sightsing-BFQ7HRig-DV69ELZf.js → sightsing-BFQ7HRig-CYuNcd8z.js} +2 -2
- package/dist/assets/{sightsing-BFQ7HRig-DV69ELZf.js.map → sightsing-BFQ7HRig-CYuNcd8z.js.map} +1 -1
- package/dist/assets/sightsing-CJX3k0Fd-C4-zGBSZ.js +1 -0
- package/dist/assets/{srs-DOsG9Y-i.js → srs-B3R2C3TU.js} +2 -2
- package/dist/assets/{srs-DOsG9Y-i.js.map → srs-B3R2C3TU.js.map} +1 -1
- package/dist/assets/srs-BD4FD2rw.js +1 -0
- package/dist/assets/{typing-BevtfWlp-CvStu5BY.js → typing-BevtfWlp-C0NbuTKI.js} +2 -2
- package/dist/assets/{typing-BevtfWlp-CvStu5BY.js.map → typing-BevtfWlp-C0NbuTKI.js.map} +1 -1
- package/dist/assets/typing-k-ojjO7G-gZjZ4YQx.js +1 -0
- package/dist/assets/{userDBKeys-DYLGKnVC.js → userDBKeys-3SQjNVdK.js} +4 -4
- package/dist/assets/{userDBKeys-DYLGKnVC.js.map → userDBKeys-3SQjNVdK.js.map} +1 -1
- package/dist/assets/{userTagPreference-cZCPqadC.js → userTagPreference-B23ZX5J4.js} +2 -2
- package/dist/assets/{userTagPreference-cZCPqadC.js.map → userTagPreference-B23ZX5J4.js.map} +1 -1
- package/dist/assets/validators-CnxSFY0I.js +2 -0
- package/dist/assets/{validators-lpYAHmRR.js.map → validators-CnxSFY0I.js.map} +1 -1
- package/dist/assets/{word-work-BOnRlZgd-CCGZdwyf.js → word-work-BOnRlZgd-CsMkeNdI.js} +2 -2
- package/dist/assets/{word-work-BOnRlZgd-CCGZdwyf.js.map → word-work-BOnRlZgd-CsMkeNdI.js.map} +1 -1
- package/dist/assets/word-work-Dd6tIKrt-WI45cKxO.js +1 -0
- package/dist/index.html +9 -9
- package/dist/sw.js +1 -1
- package/dist/sw.js.map +1 -1
- package/package.json +7 -7
- package/dist/assets/BaseUserDB-CV2ZPZOR.js +0 -1
- package/dist/assets/CouchDBSyncStrategy-BjM7ROXC.js +0 -1
- package/dist/assets/CourseSyncService-CPU_Ll1h.js +0 -1
- package/dist/assets/PouchDataLayerProvider-DxsnSIKd.js +0 -2
- package/dist/assets/PouchDataLayerProvider-DxsnSIKd.js.map +0 -1
- package/dist/assets/UserDBDebugger-xmycWjck.js +0 -1
- package/dist/assets/chess-Lfm7Fsvs-DgN08Qx9.js +0 -1
- package/dist/assets/contentSource-DwznMSMs.js.map +0 -1
- package/dist/assets/dist-BcwaOf54.js +0 -1
- package/dist/assets/dist-CgW7D_Gh.js +0 -1
- package/dist/assets/dist-Dnehy0sn.js +0 -1
- package/dist/assets/edit-ui.es-BWqvElsJ.js +0 -1
- package/dist/assets/elo-Cwa8a3Yo.js +0 -1
- package/dist/assets/eloDistance-MReQeAuj.js +0 -1
- package/dist/assets/french-evUMlbbq-nGCjilG6.js +0 -1
- package/dist/assets/math-DR3fXDcX-C9KfHLHP.js +0 -1
- package/dist/assets/piano-BN5Btq91-CnKhyLGE.js +0 -1
- package/dist/assets/pitch-Dn0iNqiS-BxAdpDDb.js +0 -1
- package/dist/assets/prescribed-BWO17Gan.js +0 -2
- package/dist/assets/prescribed-BWO17Gan.js.map +0 -1
- package/dist/assets/sightsing-CJX3k0Fd-CG94OiDr.js +0 -1
- package/dist/assets/srs-DGscV6W4.js +0 -1
- package/dist/assets/typing-k-ojjO7G-AF8vU_q7.js +0 -1
- package/dist/assets/validators-lpYAHmRR.js +0 -2
- package/dist/assets/word-work-Dd6tIKrt-BNTSCq5F.js +0 -1
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import"./chunk-D6hFPZc3.js";import{$ as e,C as t,H as n,K as r,S as i,T as a,_ as o,v as s}from"./vue.runtime.esm-bundler--Fxmd6l9.js";import{t as c}from"./index-
|
|
2
|
-
//# sourceMappingURL=About-
|
|
1
|
+
import"./chunk-D6hFPZc3.js";import{$ as e,C as t,H as n,K as r,S as i,T as a,_ as o,v as s}from"./vue.runtime.esm-bundler--Fxmd6l9.js";import{t as c}from"./index-BRaeVAZb.js";var l=a({name:`AboutView`,components:{},data(){return{type:`Viewable`}}});function _sfc_render(a,c,l,u,d,f){let p=r(`v-btn`),m=r(`v-col`),h=r(`v-row`),g=r(`v-container`);return n(),s(g,{class:`about`},{default:e(()=>[t(h,null,{default:e(()=>[t(m,null,{default:e(()=>[c[1]||(c[1]=o(`h1`,{class:`text-h2 mb-6`},`Todo.`,-1)),c[2]||(c[2]=o(`p`,{class:`text-body-1 mb-4`},` This is a placeholder for the about page. It will be filled in with more information later. `,-1)),t(p,{to:`./notes`,variant:`text`,color:`primary`},{default:e(()=>c[0]||(c[0]=[i(` Release Notes `)])),_:1})]),_:1})]),_:1})]),_:1})}var u=c(l,[[`render`,_sfc_render]]);export{u as default};
|
|
2
|
+
//# sourceMappingURL=About-BkMENQtD.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"About-
|
|
1
|
+
{"version":3,"file":"About-BkMENQtD.js","names":[],"sources":["../../src/views/About.vue","../../src/views/About.vue"],"sourcesContent":["<template>\n <v-container class=\"about\">\n <v-row>\n <v-col>\n <h1 class=\"text-h2 mb-6\">Todo.</h1>\n\n <p class=\"text-body-1 mb-4\">\n This is a placeholder for the about page. It will be filled in with more information later.\n </p>\n\n <v-btn :to=\"'./notes'\" variant=\"text\" color=\"primary\"> Release Notes </v-btn>\n </v-col>\n </v-row>\n </v-container>\n</template>\n\n<script lang=\"ts\">\nimport { defineComponent } from 'vue';\n\nexport default defineComponent({\n name: 'AboutView',\n components: {},\n data() {\n return {\n type: 'Viewable',\n };\n },\n});\n</script>\n","<template>\n <v-container class=\"about\">\n <v-row>\n <v-col>\n <h1 class=\"text-h2 mb-6\">Todo.</h1>\n\n <p class=\"text-body-1 mb-4\">\n This is a placeholder for the about page. It will be filled in with more information later.\n </p>\n\n <v-btn :to=\"'./notes'\" variant=\"text\" color=\"primary\"> Release Notes </v-btn>\n </v-col>\n </v-row>\n </v-container>\n</template>\n\n<script lang=\"ts\">\nimport { defineComponent } from 'vue';\n\nexport default defineComponent({\n name: 'AboutView',\n components: {},\n data() {\n return {\n type: 'Viewable',\n };\n },\n});\n</script>\n"],"mappings":"+KCmBA,IAAA,EAAe,EAAgB,CAC7B,KAAM,YACN,WAAY,EAAE,CACd,MAAO,CACL,MAAO,CACL,KAAM,WACP,EAEJ,CAAC,4GA1BA,EAYc,EAAA,CAZD,MAAM,QAAO,CAAA,CAD5B,QAAA,MAYY,CAVR,EAUQ,EAAA,KAAA,CAZZ,QAAA,MAWc,CARR,EAQQ,EAAA,KAAA,CAXd,QAAA,MAI2C,aAAnC,EAAmC,KAAA,CAA/B,MAAM,eAAc,CAAC,QAAK,GAAA,cAE9B,EAEI,IAAA,CAFD,MAAM,mBAAkB,CAAC,gGAE5B,GAAA,EAEA,EAA6E,EAAA,CAArE,GAAI,UAAW,QAAQ,OAAO,MAAM,YAVpD,QAAA,MAU6E,EAAA,KAAA,EAAA,GAAA,CAV7E,EAU8D,kBAAe,CAAA,EAAA,CAV7E,EAAA,MAAA,EAAA,MAAA,EAAA,MAAA,EAAA"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import"./chunk-D6hFPZc3.js";import{$ as e,C as t,H as n,Ht as r,K as i,S as a,T as o,_ as s,b as c,v as l,y as u}from"./vue.runtime.esm-bundler--Fxmd6l9.js";import"./MarkdownRenderer-borh39Lq.js";import"./moment-DbT30ZpG.js";import{F as d,I as f,g as p,v as m}from"./dist-
|
|
2
|
-
//# sourceMappingURL=AdminDashboard-
|
|
1
|
+
import"./chunk-D6hFPZc3.js";import{$ as e,C as t,H as n,Ht as r,K as i,S as a,T as o,_ as s,b as c,v as l,y as u}from"./vue.runtime.esm-bundler--Fxmd6l9.js";import"./MarkdownRenderer-borh39Lq.js";import"./moment-DbT30ZpG.js";import{F as d,I as f,g as p,v as m}from"./dist-C8EQUxWW.js";import{z as h}from"./contentSource-FGhM_ge8.js";import"./dist-DGbpjpKB.js";import"./CourseWare-BTFRjgBR-lpqcYpAW.js";import"./PipelineDebugger-DhnKlMgD.js";import{d as g}from"./userDBKeys-3SQjNVdK.js";import"./eloDistance-CRxxdYPJ.js";import{r as _,t as v}from"./index-BRaeVAZb.js";import"./chess-E2uOAyQS-DDTgqZMJ.js";import"./math-D_8HiEQR-CQynsoTm.js";import"./piano-DF1g6yaX-vXbP1jX_.js";import"./sightsing-BFQ7HRig-CYuNcd8z.js";import"./typing-BevtfWlp-C0NbuTKI.js";import{t as y}from"./dist-BJLhyG8r.js";import{a as b,i as x,o as S,t as C}from"./admin-hzNSXp0o.js";var w=new Map;function learnerDump(e,t){let n=`${t}::${e}`;if(!w.has(n)){let r=b({couchUrl:`${_.COUCHDB_SERVER_PROTOCOL}://${_.COUCHDB_SERVER_URL}`,username:e,courseId:t,auth:{kind:`cookie`}});w.set(n,r),r.catch(()=>w.delete(n))}return w.get(n)}var T=o({name:`AdminDashboard`,components:{CardSearch:f,CardSearchResults:d,CardDossierDetail:C,CardLoader:m},data(){return{query:``,selectedCard:null,userId:``,userDB:null,dataLayer:null,adminDB:null,selectedUserId:``,cardDossier:null,dossierLoading:!1,dossierError:null,selectedCourseId:null,userOptions:[],courseOptions:[]}},computed:{learnerLink(){return!this.selectedCard||!this.selectedUserId?`/admin/learners`:`/admin/learners/${encodeURIComponent(this.selectedCard.courseId)}/${encodeURIComponent(this.selectedUserId)}`}},async created(){this.userDB=await p(),this.userId=this.userDB.getUsername(),this.dataLayer=await g(),this.adminDB=this.dataLayer.getAdminDB(),await this.loadUsers(),await this.loadCourses()},methods:{async loadUsers(){try{this.userOptions=(await this.adminDB.getUsers()).filter(e=>e.name&&!e.name.startsWith(`Guest_`)).map(e=>({label:e.name||`Unknown User`,value:e.name||``})).filter(e=>e.value!==``)}catch(e){console.error(`Failed to load users:`,e)}},onUserSelected(){this.loadDossier()},async loadDossier(){let e=this.selectedCard,t=this.selectedUserId;if(this.cardDossier=null,this.dossierError=null,!(!e||!t)){this.dossierLoading=!0;try{let n=S(await learnerDump(t,e.courseId),e.courseId);if(e!==this.selectedCard||t!==this.selectedUserId)return;this.cardDossier=x(n).find(t=>t.cardId===e.cardId)??null}catch(e){this.dossierError=e instanceof Error?e.message:String(e)}finally{this.dossierLoading=!1}}},onSearch(e){this.query=e},onCardSelected(e){this.selectedCard=e,this.loadDossier()},async loadCourses(){try{this.courseOptions=(await h.allCourseWare()).filter(e=>e._id).map(e=>{let t=e.name&&e.name.trim(),n,r;return t&&t!==e._id&&t!==``?(n=t,r=t.toLowerCase()):(n=e._id,r=e._id.toLowerCase()),{label:`📚 ${n}`,value:e._id,sortKey:r}}).sort((e,t)=>e.sortKey.localeCompare(t.sortKey)),console.log(`Loaded ${this.courseOptions.length} courses for filtering (lightweight mode)`)}catch(e){console.error(`Failed to load courses (lightweight):`,e),await this.loadCoursesExpensive()}},async loadCoursesExpensive(){try{console.log(`Using expensive course loading (full configs)...`),this.courseOptions=(await this.dataLayer.getAdminDB().getCourses()).filter(e=>e.courseID).map(e=>{let t=e.name&&e.name.trim(),n=e.creator?` (${e.creator})`:``,r,i;return t&&t!==e.courseID?(r=t,i=t.toLowerCase()):(r=e.courseID,i=e.courseID.toLowerCase()),{label:`${e.public?`🌐`:`🔒`} ${r}${n}`,value:e.courseID,sortKey:i}}).sort((e,t)=>e.sortKey.localeCompare(t.sortKey)),console.log(`Loaded ${this.courseOptions.length} courses for filtering (expensive mode)`)}catch(e){console.error(`Failed to load courses (expensive):`,e),this.loadCoursesBasic()}},async loadCoursesBasic(){try{console.log(`Using fallback course loading (IDs only)`),this.courseOptions=[{label:`⚠️ Course names not available - using IDs only`,value:``}]}catch(e){console.error(`Failed to load basic courses:`,e)}},onCourseFilterChanged(){this.query},getCourseNameById(e){let t=this.courseOptions.find(t=>t.value===e);return t?t.label:e},getSelectedCourseDetails(){return this.selectedCourseId?`ID: ${this.selectedCourseId}`:``},viewLookup(e){return y.getView(e)}}}),E={class:`admin-dashboard`},D={key:0,class:`mt-2`},O={key:0,class:`text-caption text-grey-darken-1`},k={key:3,class:`text-caption text-disabled`};function _sfc_render(o,d,f,p,m,h){let g=i(`v-btn`),_=i(`v-card-title`),v=i(`v-select`),y=i(`v-chip`),b=i(`v-card-text`),x=i(`v-card`),S=i(`card-search`),C=i(`card-search-results`),w=i(`v-col`),T=i(`card-loader`),A=i(`v-row`),j=i(`v-spacer`),M=i(`v-progress-linear`),N=i(`v-alert`),P=i(`card-dossier-detail`);return n(),c(`div`,E,[d[6]||(d[6]=s(`h1`,null,`Admin Dashboard`,-1)),t(g,{class:`mb-4`,variant:`tonal`,"prepend-icon":`mdi-account-search`,to:`/admin/learners`},{default:e(()=>d[2]||(d[2]=[a(` Learner diagnostics `)])),_:1}),t(x,{class:`mb-4`},{default:e(()=>[t(_,null,{default:e(()=>d[3]||(d[3]=[a(`User Selection`)])),_:1}),t(b,null,{default:e(()=>[t(v,{modelValue:o.selectedUserId,"onUpdate:modelValue":[d[0]||(d[0]=e=>o.selectedUserId=e),o.onUserSelected],items:o.userOptions,"item-title":`label`,"item-value":`value`,label:`Select User`},null,8,[`modelValue`,`items`,`onUpdate:modelValue`]),o.selectedUserId?(n(),l(y,{key:0,color:`primary`,class:`mt-2`},{default:e(()=>[a(` Viewing data for: `+r(o.selectedUserId),1)]),_:1})):u(``,!0)]),_:1})]),_:1}),t(x,{class:`mb-4`},{default:e(()=>[t(_,null,{default:e(()=>d[4]||(d[4]=[a(`Search Filters`)])),_:1}),t(b,null,{default:e(()=>[t(v,{modelValue:o.selectedCourseId,"onUpdate:modelValue":[d[1]||(d[1]=e=>o.selectedCourseId=e),o.onCourseFilterChanged],items:o.courseOptions,"item-title":`label`,"item-value":`value`,label:`Filter by Course (optional)`,clearable:``},null,8,[`modelValue`,`items`,`onUpdate:modelValue`]),o.selectedCourseId?(n(),c(`div`,D,[t(y,{color:`info`,class:`mb-1`},{default:e(()=>[a(` 🎯 `+r(o.getCourseNameById(o.selectedCourseId)),1)]),_:1}),o.getSelectedCourseDetails()?(n(),c(`div`,O,r(o.getSelectedCourseDetails()),1)):u(``,!0)])):(n(),l(y,{key:1,color:`warning`,class:`mt-2`},{default:e(()=>[a(` ⚠️ Searching ALL `+r(o.courseOptions.length)+` courses (may be slow) `,1)]),_:1}))]),_:1})]),_:1}),t(S,{onSearch:o.onSearch},null,8,[`onSearch`]),t(A,null,{default:e(()=>[t(w,{cols:`6`},{default:e(()=>[o.query?(n(),l(C,{key:0,query:o.query,"data-layer":o.dataLayer,"course-filter":o.selectedCourseId,onCardSelected:o.onCardSelected},null,8,[`query`,`data-layer`,`course-filter`,`onCardSelected`])):u(``,!0)]),_:1}),t(w,{cols:`6`},{default:e(()=>[o.selectedCard?(n(),l(T,{key:0,qualified_id:{courseID:o.selectedCard.courseId,cardID:o.selectedCard.cardId},"view-lookup":o.viewLookup},null,8,[`qualified_id`,`view-lookup`])):u(``,!0)]),_:1})]),_:1}),o.selectedCard&&o.selectedUserId?(n(),l(x,{key:0,class:`mt-4`,variant:`outlined`},{default:e(()=>[t(_,{class:`d-flex align-center`},{default:e(()=>[a(` Card history: `+r(o.selectedUserId)+` `,1),t(j),t(g,{variant:`text`,size:`small`,"prepend-icon":`mdi-account-search`,to:o.learnerLink},{default:e(()=>d[5]||(d[5]=[a(` Learner diagnostics `)])),_:1},8,[`to`])]),_:1}),t(b,null,{default:e(()=>[o.dossierLoading?(n(),l(M,{key:0,indeterminate:``})):o.dossierError?(n(),l(N,{key:1,type:`error`,variant:`tonal`,density:`compact`},{default:e(()=>[a(r(o.dossierError),1)]),_:1})):o.cardDossier?(n(),l(P,{key:2,dossier:o.cardDossier},null,8,[`dossier`])):(n(),c(`div`,k,r(o.selectedUserId)+` has no history with this card.`,1))]),_:1})]),_:1})):u(``,!0)])}var A=v(T,[[`render`,_sfc_render],[`__scopeId`,`data-v-d9211107`]]);export{A as default};
|
|
2
|
+
//# sourceMappingURL=AdminDashboard-5R-xgY-M.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AdminDashboard-B00qno2M.js","names":[],"sources":["../../src/views/AdminDashboard.vue","../../src/views/AdminDashboard.vue"],"sourcesContent":["<template>\n <div class=\"admin-dashboard\">\n <h1>Admin Dashboard</h1>\n <v-btn class=\"mb-4\" variant=\"tonal\" prepend-icon=\"mdi-account-search\" to=\"/admin/learners\">\n Learner diagnostics\n </v-btn>\n\n <!-- User Selection Section -->\n <v-card class=\"mb-4\">\n <v-card-title>User Selection</v-card-title>\n <v-card-text>\n <v-select\n v-model=\"selectedUserId\"\n :items=\"userOptions\"\n item-title=\"label\"\n item-value=\"value\"\n label=\"Select User\"\n @update:model-value=\"onUserSelected\"\n ></v-select>\n <v-chip v-if=\"selectedUserId\" color=\"primary\" class=\"mt-2\"> Viewing data for: {{ selectedUserId }} </v-chip>\n </v-card-text>\n </v-card>\n\n <!-- Course Filter Section -->\n <v-card class=\"mb-4\">\n <v-card-title>Search Filters</v-card-title>\n <v-card-text>\n <v-select\n v-model=\"selectedCourseId\"\n :items=\"courseOptions\"\n item-title=\"label\"\n item-value=\"value\"\n label=\"Filter by Course (optional)\"\n clearable\n @update:model-value=\"onCourseFilterChanged\"\n ></v-select>\n <div v-if=\"selectedCourseId\" class=\"mt-2\">\n <v-chip color=\"info\" class=\"mb-1\"> 🎯 {{ getCourseNameById(selectedCourseId) }} </v-chip>\n <div v-if=\"getSelectedCourseDetails()\" class=\"text-caption text-grey-darken-1\">\n {{ getSelectedCourseDetails() }}\n </div>\n </div>\n <v-chip v-else color=\"warning\" class=\"mt-2\">\n ⚠️ Searching ALL {{ courseOptions.length }} courses (may be slow)\n </v-chip>\n </v-card-text>\n </v-card>\n\n <!-- Card Search Section -->\n <card-search @search=\"onSearch\" />\n <v-row>\n <v-col cols=\"6\">\n <card-search-results\n v-if=\"query\"\n :query=\"query\"\n :data-layer=\"dataLayer\"\n :course-filter=\"selectedCourseId\"\n @card-selected=\"onCardSelected\"\n />\n </v-col>\n <v-col cols=\"6\">\n <card-loader\n v-if=\"selectedCard\"\n :qualified_id=\"{ courseID: selectedCard.courseId, cardID: selectedCard.cardId }\"\n :view-lookup=\"viewLookup\"\n />\n </v-col>\n </v-row>\n\n <!-- Card History Section -->\n <v-card v-if=\"selectedCard && selectedUserId\" class=\"mt-4\" variant=\"outlined\">\n <v-card-title class=\"d-flex align-center\">\n Card history: {{ selectedUserId }}\n <v-spacer />\n <v-btn variant=\"text\" size=\"small\" prepend-icon=\"mdi-account-search\" :to=\"learnerLink\">\n Learner diagnostics\n </v-btn>\n </v-card-title>\n <v-card-text>\n <v-progress-linear v-if=\"dossierLoading\" indeterminate />\n <v-alert v-else-if=\"dossierError\" type=\"error\" variant=\"tonal\" density=\"compact\">{{ dossierError }}</v-alert>\n <card-dossier-detail v-else-if=\"cardDossier\" :dossier=\"cardDossier\" />\n <div v-else class=\"text-caption text-disabled\">{{ selectedUserId }} has no history with this card.</div>\n </v-card-text>\n </v-card>\n </div>\n</template>\n\n<script lang=\"ts\">\nimport { defineComponent } from 'vue';\nimport { CardSearch, CardSearchResults, CardLoader, getCurrentUser } from '@vue-skuilder/common-ui';\nimport { CardDossierDetail } from '@vue-skuilder/common-ui/admin';\nimport '@vue-skuilder/common-ui/admin/style';\nimport { getDataLayer, CourseLookup } from '@vue-skuilder/db';\nimport {\n cardDossiers,\n fetchLearnerDump,\n fromDump,\n type CardDossier,\n type LearnerDump,\n} from '@vue-skuilder/db/diagnostics';\nimport { allCourseWare } from '@vue-skuilder/courseware';\nimport { UserDBInterface, DataLayerProvider, AdminDBInterface } from '@vue-skuilder/db';\nimport ENV from '../ENVIRONMENT_VARS';\n\n// A learner's records, per learner and course, fetched once per page load with\n// the admin's couch session cookie. Not through a data-layer UserDB: building\n// another user's UserDB replaces the logged-in one.\nconst dumps = new Map<string, Promise<LearnerDump>>();\n\nfunction learnerDump(username: string, courseId: string): Promise<LearnerDump> {\n const key = `${courseId}::${username}`;\n if (!dumps.has(key)) {\n const pending = fetchLearnerDump({\n couchUrl: `${ENV.COUCHDB_SERVER_PROTOCOL}://${ENV.COUCHDB_SERVER_URL}`,\n username,\n courseId,\n auth: { kind: 'cookie' },\n });\n dumps.set(key, pending);\n pending.catch(() => dumps.delete(key));\n }\n return dumps.get(key)!;\n}\n\ninterface UserOption {\n label: string;\n value: string;\n}\n\nexport default defineComponent({\n name: 'AdminDashboard',\n components: {\n CardSearch,\n CardSearchResults,\n CardDossierDetail,\n CardLoader,\n },\n data() {\n return {\n query: '',\n selectedCard: null as { cardId: string; courseId: string } | null,\n userId: '',\n userDB: null as UserDBInterface | null,\n dataLayer: null as DataLayerProvider | null,\n adminDB: null as AdminDBInterface | null,\n selectedUserId: '',\n cardDossier: null as CardDossier | null,\n dossierLoading: false,\n dossierError: null as string | null,\n selectedCourseId: null as string | null,\n userOptions: [] as UserOption[],\n courseOptions: [] as UserOption[],\n };\n },\n computed: {\n learnerLink(): string {\n if (!this.selectedCard || !this.selectedUserId) return '/admin/learners';\n const course = encodeURIComponent(this.selectedCard.courseId);\n return `/admin/learners/${course}/${encodeURIComponent(this.selectedUserId)}`;\n },\n },\n async created() {\n this.userDB = await getCurrentUser();\n this.userId = this.userDB.getUsername();\n this.dataLayer = await getDataLayer();\n this.adminDB = this.dataLayer.getAdminDB();\n\n // Load user list for selection\n await this.loadUsers();\n\n // Load available courses\n await this.loadCourses();\n },\n methods: {\n async loadUsers() {\n try {\n const users = await this.adminDB!.getUsers();\n this.userOptions = users\n .filter((user) => user.name && !user.name.startsWith('Guest_'))\n .map((user) => ({\n label: user.name || 'Unknown User',\n value: user.name || '',\n }))\n .filter((option) => option.value !== '');\n } catch (error) {\n console.error('Failed to load users:', error);\n }\n },\n\n onUserSelected() {\n void this.loadDossier();\n },\n\n /** The selected learner's record for the selected card, from their dataset. */\n async loadDossier() {\n const card = this.selectedCard;\n const user = this.selectedUserId;\n this.cardDossier = null;\n this.dossierError = null;\n if (!card || !user) return;\n this.dossierLoading = true;\n try {\n const dataset = fromDump(await learnerDump(user, card.courseId), card.courseId);\n if (card !== this.selectedCard || user !== this.selectedUserId) return;\n this.cardDossier = cardDossiers(dataset).find((d) => d.cardId === card.cardId) ?? null;\n } catch (error) {\n this.dossierError = error instanceof Error ? error.message : String(error);\n } finally {\n this.dossierLoading = false;\n }\n },\n\n onSearch(query: string) {\n this.query = query;\n },\n\n onCardSelected(card: { cardId: string; courseId: string }) {\n this.selectedCard = card;\n void this.loadDossier();\n },\n\n async loadCourses() {\n try {\n // Try efficient lookup-only approach first\n // const coursesDB = this.dataLayer!.getCoursesDB();\n\n // Get basic course list from lookup DB (lightweight, just IDs and basic names)\n const lookupCourses = await CourseLookup.allCourseWare();\n\n this.courseOptions = lookupCourses\n .filter((course) => course._id)\n .map((course) => {\n const courseName = course.name && course.name.trim();\n let displayName: string;\n let sortKey: string;\n\n if (courseName && courseName !== course._id && courseName !== '') {\n // Has a real name different from ID\n displayName = courseName;\n sortKey = courseName.toLowerCase();\n } else {\n // No name or name is same as ID - use ID\n displayName = course._id;\n sortKey = course._id.toLowerCase();\n }\n\n // Simple display without expensive metadata lookups\n return {\n label: `📚 ${displayName}`,\n value: course._id,\n sortKey,\n };\n })\n .sort((a, b) => a.sortKey.localeCompare(b.sortKey));\n\n console.log(`Loaded ${this.courseOptions.length} courses for filtering (lightweight mode)`);\n } catch (error) {\n console.error('Failed to load courses (lightweight):', error);\n // Fallback to expensive approach\n await this.loadCoursesExpensive();\n }\n },\n\n async loadCoursesExpensive() {\n try {\n console.log('Using expensive course loading (full configs)...');\n const adminDB = this.dataLayer!.getAdminDB();\n const basicCourseList = await adminDB.getCourses(); // This fetches full configs\n\n this.courseOptions = basicCourseList\n .filter((course) => course.courseID)\n .map((course) => {\n const courseName = course.name && course.name.trim();\n const creator = course.creator ? ` (${course.creator})` : '';\n\n let displayName: string;\n let sortKey: string;\n\n if (courseName && courseName !== course.courseID) {\n // Has a real name different from ID\n displayName = courseName;\n sortKey = courseName.toLowerCase();\n } else {\n // No name or name is same as ID - use ID\n displayName = course.courseID!;\n sortKey = course.courseID!.toLowerCase();\n }\n\n const visibility = course.public ? '🌐' : '🔒';\n\n return {\n label: `${visibility} ${displayName}${creator}`,\n value: course.courseID!,\n sortKey,\n };\n })\n .sort((a, b) => a.sortKey.localeCompare(b.sortKey));\n\n console.log(`Loaded ${this.courseOptions.length} courses for filtering (expensive mode)`);\n } catch (error) {\n console.error('Failed to load courses (expensive):', error);\n // Final fallback\n this.loadCoursesBasic();\n }\n },\n\n async loadCoursesBasic() {\n try {\n // Fallback: just get course IDs from lookup DB (fast)\n console.log('Using fallback course loading (IDs only)');\n // This would need a new method that just gets the lookup docs without full configs\n // For now, we'll show a basic message\n this.courseOptions = [{ label: '⚠️ Course names not available - using IDs only', value: '' }];\n } catch (error) {\n console.error('Failed to load basic courses:', error);\n }\n },\n\n onCourseFilterChanged() {\n // If there's an active search, re-run it with the new filter\n if (this.query) {\n // The CardSearchResults component will automatically re-search with the new filter\n }\n },\n\n getCourseNameById(courseId: string): string {\n const course = this.courseOptions.find((c) => c.value === courseId);\n return course ? course.label : courseId;\n },\n\n getSelectedCourseDetails(): string {\n if (!this.selectedCourseId) return '';\n\n // For now, just return the ID as additional context\n return `ID: ${this.selectedCourseId}`;\n },\n\n viewLookup(viewDescriptor: unknown) {\n return allCourseWare.getView(viewDescriptor);\n },\n },\n});\n</script>\n\n<style scoped>\n.admin-dashboard {\n padding: 2rem;\n}\n</style>\n","<template>\n <div class=\"admin-dashboard\">\n <h1>Admin Dashboard</h1>\n <v-btn class=\"mb-4\" variant=\"tonal\" prepend-icon=\"mdi-account-search\" to=\"/admin/learners\">\n Learner diagnostics\n </v-btn>\n\n <!-- User Selection Section -->\n <v-card class=\"mb-4\">\n <v-card-title>User Selection</v-card-title>\n <v-card-text>\n <v-select\n v-model=\"selectedUserId\"\n :items=\"userOptions\"\n item-title=\"label\"\n item-value=\"value\"\n label=\"Select User\"\n @update:model-value=\"onUserSelected\"\n ></v-select>\n <v-chip v-if=\"selectedUserId\" color=\"primary\" class=\"mt-2\"> Viewing data for: {{ selectedUserId }} </v-chip>\n </v-card-text>\n </v-card>\n\n <!-- Course Filter Section -->\n <v-card class=\"mb-4\">\n <v-card-title>Search Filters</v-card-title>\n <v-card-text>\n <v-select\n v-model=\"selectedCourseId\"\n :items=\"courseOptions\"\n item-title=\"label\"\n item-value=\"value\"\n label=\"Filter by Course (optional)\"\n clearable\n @update:model-value=\"onCourseFilterChanged\"\n ></v-select>\n <div v-if=\"selectedCourseId\" class=\"mt-2\">\n <v-chip color=\"info\" class=\"mb-1\"> 🎯 {{ getCourseNameById(selectedCourseId) }} </v-chip>\n <div v-if=\"getSelectedCourseDetails()\" class=\"text-caption text-grey-darken-1\">\n {{ getSelectedCourseDetails() }}\n </div>\n </div>\n <v-chip v-else color=\"warning\" class=\"mt-2\">\n ⚠️ Searching ALL {{ courseOptions.length }} courses (may be slow)\n </v-chip>\n </v-card-text>\n </v-card>\n\n <!-- Card Search Section -->\n <card-search @search=\"onSearch\" />\n <v-row>\n <v-col cols=\"6\">\n <card-search-results\n v-if=\"query\"\n :query=\"query\"\n :data-layer=\"dataLayer\"\n :course-filter=\"selectedCourseId\"\n @card-selected=\"onCardSelected\"\n />\n </v-col>\n <v-col cols=\"6\">\n <card-loader\n v-if=\"selectedCard\"\n :qualified_id=\"{ courseID: selectedCard.courseId, cardID: selectedCard.cardId }\"\n :view-lookup=\"viewLookup\"\n />\n </v-col>\n </v-row>\n\n <!-- Card History Section -->\n <v-card v-if=\"selectedCard && selectedUserId\" class=\"mt-4\" variant=\"outlined\">\n <v-card-title class=\"d-flex align-center\">\n Card history: {{ selectedUserId }}\n <v-spacer />\n <v-btn variant=\"text\" size=\"small\" prepend-icon=\"mdi-account-search\" :to=\"learnerLink\">\n Learner diagnostics\n </v-btn>\n </v-card-title>\n <v-card-text>\n <v-progress-linear v-if=\"dossierLoading\" indeterminate />\n <v-alert v-else-if=\"dossierError\" type=\"error\" variant=\"tonal\" density=\"compact\">{{ dossierError }}</v-alert>\n <card-dossier-detail v-else-if=\"cardDossier\" :dossier=\"cardDossier\" />\n <div v-else class=\"text-caption text-disabled\">{{ selectedUserId }} has no history with this card.</div>\n </v-card-text>\n </v-card>\n </div>\n</template>\n\n<script lang=\"ts\">\nimport { defineComponent } from 'vue';\nimport { CardSearch, CardSearchResults, CardLoader, getCurrentUser } from '@vue-skuilder/common-ui';\nimport { CardDossierDetail } from '@vue-skuilder/common-ui/admin';\nimport '@vue-skuilder/common-ui/admin/style';\nimport { getDataLayer, CourseLookup } from '@vue-skuilder/db';\nimport {\n cardDossiers,\n fetchLearnerDump,\n fromDump,\n type CardDossier,\n type LearnerDump,\n} from '@vue-skuilder/db/diagnostics';\nimport { allCourseWare } from '@vue-skuilder/courseware';\nimport { UserDBInterface, DataLayerProvider, AdminDBInterface } from '@vue-skuilder/db';\nimport ENV from '../ENVIRONMENT_VARS';\n\n// A learner's records, per learner and course, fetched once per page load with\n// the admin's couch session cookie. Not through a data-layer UserDB: building\n// another user's UserDB replaces the logged-in one.\nconst dumps = new Map<string, Promise<LearnerDump>>();\n\nfunction learnerDump(username: string, courseId: string): Promise<LearnerDump> {\n const key = `${courseId}::${username}`;\n if (!dumps.has(key)) {\n const pending = fetchLearnerDump({\n couchUrl: `${ENV.COUCHDB_SERVER_PROTOCOL}://${ENV.COUCHDB_SERVER_URL}`,\n username,\n courseId,\n auth: { kind: 'cookie' },\n });\n dumps.set(key, pending);\n pending.catch(() => dumps.delete(key));\n }\n return dumps.get(key)!;\n}\n\ninterface UserOption {\n label: string;\n value: string;\n}\n\nexport default defineComponent({\n name: 'AdminDashboard',\n components: {\n CardSearch,\n CardSearchResults,\n CardDossierDetail,\n CardLoader,\n },\n data() {\n return {\n query: '',\n selectedCard: null as { cardId: string; courseId: string } | null,\n userId: '',\n userDB: null as UserDBInterface | null,\n dataLayer: null as DataLayerProvider | null,\n adminDB: null as AdminDBInterface | null,\n selectedUserId: '',\n cardDossier: null as CardDossier | null,\n dossierLoading: false,\n dossierError: null as string | null,\n selectedCourseId: null as string | null,\n userOptions: [] as UserOption[],\n courseOptions: [] as UserOption[],\n };\n },\n computed: {\n learnerLink(): string {\n if (!this.selectedCard || !this.selectedUserId) return '/admin/learners';\n const course = encodeURIComponent(this.selectedCard.courseId);\n return `/admin/learners/${course}/${encodeURIComponent(this.selectedUserId)}`;\n },\n },\n async created() {\n this.userDB = await getCurrentUser();\n this.userId = this.userDB.getUsername();\n this.dataLayer = await getDataLayer();\n this.adminDB = this.dataLayer.getAdminDB();\n\n // Load user list for selection\n await this.loadUsers();\n\n // Load available courses\n await this.loadCourses();\n },\n methods: {\n async loadUsers() {\n try {\n const users = await this.adminDB!.getUsers();\n this.userOptions = users\n .filter((user) => user.name && !user.name.startsWith('Guest_'))\n .map((user) => ({\n label: user.name || 'Unknown User',\n value: user.name || '',\n }))\n .filter((option) => option.value !== '');\n } catch (error) {\n console.error('Failed to load users:', error);\n }\n },\n\n onUserSelected() {\n void this.loadDossier();\n },\n\n /** The selected learner's record for the selected card, from their dataset. */\n async loadDossier() {\n const card = this.selectedCard;\n const user = this.selectedUserId;\n this.cardDossier = null;\n this.dossierError = null;\n if (!card || !user) return;\n this.dossierLoading = true;\n try {\n const dataset = fromDump(await learnerDump(user, card.courseId), card.courseId);\n if (card !== this.selectedCard || user !== this.selectedUserId) return;\n this.cardDossier = cardDossiers(dataset).find((d) => d.cardId === card.cardId) ?? null;\n } catch (error) {\n this.dossierError = error instanceof Error ? error.message : String(error);\n } finally {\n this.dossierLoading = false;\n }\n },\n\n onSearch(query: string) {\n this.query = query;\n },\n\n onCardSelected(card: { cardId: string; courseId: string }) {\n this.selectedCard = card;\n void this.loadDossier();\n },\n\n async loadCourses() {\n try {\n // Try efficient lookup-only approach first\n // const coursesDB = this.dataLayer!.getCoursesDB();\n\n // Get basic course list from lookup DB (lightweight, just IDs and basic names)\n const lookupCourses = await CourseLookup.allCourseWare();\n\n this.courseOptions = lookupCourses\n .filter((course) => course._id)\n .map((course) => {\n const courseName = course.name && course.name.trim();\n let displayName: string;\n let sortKey: string;\n\n if (courseName && courseName !== course._id && courseName !== '') {\n // Has a real name different from ID\n displayName = courseName;\n sortKey = courseName.toLowerCase();\n } else {\n // No name or name is same as ID - use ID\n displayName = course._id;\n sortKey = course._id.toLowerCase();\n }\n\n // Simple display without expensive metadata lookups\n return {\n label: `📚 ${displayName}`,\n value: course._id,\n sortKey,\n };\n })\n .sort((a, b) => a.sortKey.localeCompare(b.sortKey));\n\n console.log(`Loaded ${this.courseOptions.length} courses for filtering (lightweight mode)`);\n } catch (error) {\n console.error('Failed to load courses (lightweight):', error);\n // Fallback to expensive approach\n await this.loadCoursesExpensive();\n }\n },\n\n async loadCoursesExpensive() {\n try {\n console.log('Using expensive course loading (full configs)...');\n const adminDB = this.dataLayer!.getAdminDB();\n const basicCourseList = await adminDB.getCourses(); // This fetches full configs\n\n this.courseOptions = basicCourseList\n .filter((course) => course.courseID)\n .map((course) => {\n const courseName = course.name && course.name.trim();\n const creator = course.creator ? ` (${course.creator})` : '';\n\n let displayName: string;\n let sortKey: string;\n\n if (courseName && courseName !== course.courseID) {\n // Has a real name different from ID\n displayName = courseName;\n sortKey = courseName.toLowerCase();\n } else {\n // No name or name is same as ID - use ID\n displayName = course.courseID!;\n sortKey = course.courseID!.toLowerCase();\n }\n\n const visibility = course.public ? '🌐' : '🔒';\n\n return {\n label: `${visibility} ${displayName}${creator}`,\n value: course.courseID!,\n sortKey,\n };\n })\n .sort((a, b) => a.sortKey.localeCompare(b.sortKey));\n\n console.log(`Loaded ${this.courseOptions.length} courses for filtering (expensive mode)`);\n } catch (error) {\n console.error('Failed to load courses (expensive):', error);\n // Final fallback\n this.loadCoursesBasic();\n }\n },\n\n async loadCoursesBasic() {\n try {\n // Fallback: just get course IDs from lookup DB (fast)\n console.log('Using fallback course loading (IDs only)');\n // This would need a new method that just gets the lookup docs without full configs\n // For now, we'll show a basic message\n this.courseOptions = [{ label: '⚠️ Course names not available - using IDs only', value: '' }];\n } catch (error) {\n console.error('Failed to load basic courses:', error);\n }\n },\n\n onCourseFilterChanged() {\n // If there's an active search, re-run it with the new filter\n if (this.query) {\n // The CardSearchResults component will automatically re-search with the new filter\n }\n },\n\n getCourseNameById(courseId: string): string {\n const course = this.courseOptions.find((c) => c.value === courseId);\n return course ? course.label : courseId;\n },\n\n getSelectedCourseDetails(): string {\n if (!this.selectedCourseId) return '';\n\n // For now, just return the ID as additional context\n return `ID: ${this.selectedCourseId}`;\n },\n\n viewLookup(viewDescriptor: unknown) {\n return allCourseWare.getView(viewDescriptor);\n },\n },\n});\n</script>\n\n<style scoped>\n.admin-dashboard {\n padding: 2rem;\n}\n</style>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AdminDashboard-5R-xgY-M.js","names":[],"sources":["../../src/views/AdminDashboard.vue","../../src/views/AdminDashboard.vue"],"sourcesContent":["<template>\n <div class=\"admin-dashboard\">\n <h1>Admin Dashboard</h1>\n <v-btn class=\"mb-4\" variant=\"tonal\" prepend-icon=\"mdi-account-search\" to=\"/admin/learners\">\n Learner diagnostics\n </v-btn>\n\n <!-- User Selection Section -->\n <v-card class=\"mb-4\">\n <v-card-title>User Selection</v-card-title>\n <v-card-text>\n <v-select\n v-model=\"selectedUserId\"\n :items=\"userOptions\"\n item-title=\"label\"\n item-value=\"value\"\n label=\"Select User\"\n @update:model-value=\"onUserSelected\"\n ></v-select>\n <v-chip v-if=\"selectedUserId\" color=\"primary\" class=\"mt-2\"> Viewing data for: {{ selectedUserId }} </v-chip>\n </v-card-text>\n </v-card>\n\n <!-- Course Filter Section -->\n <v-card class=\"mb-4\">\n <v-card-title>Search Filters</v-card-title>\n <v-card-text>\n <v-select\n v-model=\"selectedCourseId\"\n :items=\"courseOptions\"\n item-title=\"label\"\n item-value=\"value\"\n label=\"Filter by Course (optional)\"\n clearable\n @update:model-value=\"onCourseFilterChanged\"\n ></v-select>\n <div v-if=\"selectedCourseId\" class=\"mt-2\">\n <v-chip color=\"info\" class=\"mb-1\"> 🎯 {{ getCourseNameById(selectedCourseId) }} </v-chip>\n <div v-if=\"getSelectedCourseDetails()\" class=\"text-caption text-grey-darken-1\">\n {{ getSelectedCourseDetails() }}\n </div>\n </div>\n <v-chip v-else color=\"warning\" class=\"mt-2\">\n ⚠️ Searching ALL {{ courseOptions.length }} courses (may be slow)\n </v-chip>\n </v-card-text>\n </v-card>\n\n <!-- Card Search Section -->\n <card-search @search=\"onSearch\" />\n <v-row>\n <v-col cols=\"6\">\n <card-search-results\n v-if=\"query\"\n :query=\"query\"\n :data-layer=\"dataLayer\"\n :course-filter=\"selectedCourseId\"\n @card-selected=\"onCardSelected\"\n />\n </v-col>\n <v-col cols=\"6\">\n <card-loader\n v-if=\"selectedCard\"\n :qualified_id=\"{ courseID: selectedCard.courseId, cardID: selectedCard.cardId }\"\n :view-lookup=\"viewLookup\"\n />\n </v-col>\n </v-row>\n\n <!-- Card History Section -->\n <v-card v-if=\"selectedCard && selectedUserId\" class=\"mt-4\" variant=\"outlined\">\n <v-card-title class=\"d-flex align-center\">\n Card history: {{ selectedUserId }}\n <v-spacer />\n <v-btn variant=\"text\" size=\"small\" prepend-icon=\"mdi-account-search\" :to=\"learnerLink\">\n Learner diagnostics\n </v-btn>\n </v-card-title>\n <v-card-text>\n <v-progress-linear v-if=\"dossierLoading\" indeterminate />\n <v-alert v-else-if=\"dossierError\" type=\"error\" variant=\"tonal\" density=\"compact\">{{ dossierError }}</v-alert>\n <card-dossier-detail v-else-if=\"cardDossier\" :dossier=\"cardDossier\" />\n <div v-else class=\"text-caption text-disabled\">{{ selectedUserId }} has no history with this card.</div>\n </v-card-text>\n </v-card>\n </div>\n</template>\n\n<script lang=\"ts\">\nimport { defineComponent } from 'vue';\nimport { CardSearch, CardSearchResults, CardLoader, getCurrentUser } from '@vue-skuilder/common-ui';\nimport { CardDossierDetail } from '@vue-skuilder/common-ui/admin';\nimport '@vue-skuilder/common-ui/admin/style';\nimport { getDataLayer, CourseLookup } from '@vue-skuilder/db';\nimport {\n cardDossiers,\n fetchLearnerDump,\n fromDump,\n type CardDossier,\n type LearnerDump,\n} from '@vue-skuilder/db/diagnostics';\nimport { allCourseWare } from '@vue-skuilder/courseware';\nimport { UserDBInterface, DataLayerProvider, AdminDBInterface } from '@vue-skuilder/db';\nimport ENV from '../ENVIRONMENT_VARS';\n\n// A learner's records, per learner and course, fetched once per page load with\n// the admin's couch session cookie. Not through a data-layer UserDB: building\n// another user's UserDB replaces the logged-in one.\nconst dumps = new Map<string, Promise<LearnerDump>>();\n\nfunction learnerDump(username: string, courseId: string): Promise<LearnerDump> {\n const key = `${courseId}::${username}`;\n if (!dumps.has(key)) {\n const pending = fetchLearnerDump({\n couchUrl: `${ENV.COUCHDB_SERVER_PROTOCOL}://${ENV.COUCHDB_SERVER_URL}`,\n username,\n courseId,\n auth: { kind: 'cookie' },\n });\n dumps.set(key, pending);\n pending.catch(() => dumps.delete(key));\n }\n return dumps.get(key)!;\n}\n\ninterface UserOption {\n label: string;\n value: string;\n}\n\nexport default defineComponent({\n name: 'AdminDashboard',\n components: {\n CardSearch,\n CardSearchResults,\n CardDossierDetail,\n CardLoader,\n },\n data() {\n return {\n query: '',\n selectedCard: null as { cardId: string; courseId: string } | null,\n userId: '',\n userDB: null as UserDBInterface | null,\n dataLayer: null as DataLayerProvider | null,\n adminDB: null as AdminDBInterface | null,\n selectedUserId: '',\n cardDossier: null as CardDossier | null,\n dossierLoading: false,\n dossierError: null as string | null,\n selectedCourseId: null as string | null,\n userOptions: [] as UserOption[],\n courseOptions: [] as UserOption[],\n };\n },\n computed: {\n learnerLink(): string {\n if (!this.selectedCard || !this.selectedUserId) return '/admin/learners';\n const course = encodeURIComponent(this.selectedCard.courseId);\n return `/admin/learners/${course}/${encodeURIComponent(this.selectedUserId)}`;\n },\n },\n async created() {\n this.userDB = await getCurrentUser();\n this.userId = this.userDB.getUsername();\n this.dataLayer = await getDataLayer();\n this.adminDB = this.dataLayer.getAdminDB();\n\n // Load user list for selection\n await this.loadUsers();\n\n // Load available courses\n await this.loadCourses();\n },\n methods: {\n async loadUsers() {\n try {\n const users = await this.adminDB!.getUsers();\n this.userOptions = users\n .filter((user) => user.name && !user.name.startsWith('Guest_'))\n .map((user) => ({\n label: user.name || 'Unknown User',\n value: user.name || '',\n }))\n .filter((option) => option.value !== '');\n } catch (error) {\n console.error('Failed to load users:', error);\n }\n },\n\n onUserSelected() {\n void this.loadDossier();\n },\n\n /** The selected learner's record for the selected card, from their dataset. */\n async loadDossier() {\n const card = this.selectedCard;\n const user = this.selectedUserId;\n this.cardDossier = null;\n this.dossierError = null;\n if (!card || !user) return;\n this.dossierLoading = true;\n try {\n const dataset = fromDump(await learnerDump(user, card.courseId), card.courseId);\n if (card !== this.selectedCard || user !== this.selectedUserId) return;\n this.cardDossier = cardDossiers(dataset).find((d) => d.cardId === card.cardId) ?? null;\n } catch (error) {\n this.dossierError = error instanceof Error ? error.message : String(error);\n } finally {\n this.dossierLoading = false;\n }\n },\n\n onSearch(query: string) {\n this.query = query;\n },\n\n onCardSelected(card: { cardId: string; courseId: string }) {\n this.selectedCard = card;\n void this.loadDossier();\n },\n\n async loadCourses() {\n try {\n // Try efficient lookup-only approach first\n // const coursesDB = this.dataLayer!.getCoursesDB();\n\n // Get basic course list from lookup DB (lightweight, just IDs and basic names)\n const lookupCourses = await CourseLookup.allCourseWare();\n\n this.courseOptions = lookupCourses\n .filter((course) => course._id)\n .map((course) => {\n const courseName = course.name && course.name.trim();\n let displayName: string;\n let sortKey: string;\n\n if (courseName && courseName !== course._id && courseName !== '') {\n // Has a real name different from ID\n displayName = courseName;\n sortKey = courseName.toLowerCase();\n } else {\n // No name or name is same as ID - use ID\n displayName = course._id;\n sortKey = course._id.toLowerCase();\n }\n\n // Simple display without expensive metadata lookups\n return {\n label: `📚 ${displayName}`,\n value: course._id,\n sortKey,\n };\n })\n .sort((a, b) => a.sortKey.localeCompare(b.sortKey));\n\n console.log(`Loaded ${this.courseOptions.length} courses for filtering (lightweight mode)`);\n } catch (error) {\n console.error('Failed to load courses (lightweight):', error);\n // Fallback to expensive approach\n await this.loadCoursesExpensive();\n }\n },\n\n async loadCoursesExpensive() {\n try {\n console.log('Using expensive course loading (full configs)...');\n const adminDB = this.dataLayer!.getAdminDB();\n const basicCourseList = await adminDB.getCourses(); // This fetches full configs\n\n this.courseOptions = basicCourseList\n .filter((course) => course.courseID)\n .map((course) => {\n const courseName = course.name && course.name.trim();\n const creator = course.creator ? ` (${course.creator})` : '';\n\n let displayName: string;\n let sortKey: string;\n\n if (courseName && courseName !== course.courseID) {\n // Has a real name different from ID\n displayName = courseName;\n sortKey = courseName.toLowerCase();\n } else {\n // No name or name is same as ID - use ID\n displayName = course.courseID!;\n sortKey = course.courseID!.toLowerCase();\n }\n\n const visibility = course.public ? '🌐' : '🔒';\n\n return {\n label: `${visibility} ${displayName}${creator}`,\n value: course.courseID!,\n sortKey,\n };\n })\n .sort((a, b) => a.sortKey.localeCompare(b.sortKey));\n\n console.log(`Loaded ${this.courseOptions.length} courses for filtering (expensive mode)`);\n } catch (error) {\n console.error('Failed to load courses (expensive):', error);\n // Final fallback\n this.loadCoursesBasic();\n }\n },\n\n async loadCoursesBasic() {\n try {\n // Fallback: just get course IDs from lookup DB (fast)\n console.log('Using fallback course loading (IDs only)');\n // This would need a new method that just gets the lookup docs without full configs\n // For now, we'll show a basic message\n this.courseOptions = [{ label: '⚠️ Course names not available - using IDs only', value: '' }];\n } catch (error) {\n console.error('Failed to load basic courses:', error);\n }\n },\n\n onCourseFilterChanged() {\n // If there's an active search, re-run it with the new filter\n if (this.query) {\n // The CardSearchResults component will automatically re-search with the new filter\n }\n },\n\n getCourseNameById(courseId: string): string {\n const course = this.courseOptions.find((c) => c.value === courseId);\n return course ? course.label : courseId;\n },\n\n getSelectedCourseDetails(): string {\n if (!this.selectedCourseId) return '';\n\n // For now, just return the ID as additional context\n return `ID: ${this.selectedCourseId}`;\n },\n\n viewLookup(viewDescriptor: unknown) {\n return allCourseWare.getView(viewDescriptor);\n },\n },\n});\n</script>\n\n<style scoped>\n.admin-dashboard {\n padding: 2rem;\n}\n</style>\n","<template>\n <div class=\"admin-dashboard\">\n <h1>Admin Dashboard</h1>\n <v-btn class=\"mb-4\" variant=\"tonal\" prepend-icon=\"mdi-account-search\" to=\"/admin/learners\">\n Learner diagnostics\n </v-btn>\n\n <!-- User Selection Section -->\n <v-card class=\"mb-4\">\n <v-card-title>User Selection</v-card-title>\n <v-card-text>\n <v-select\n v-model=\"selectedUserId\"\n :items=\"userOptions\"\n item-title=\"label\"\n item-value=\"value\"\n label=\"Select User\"\n @update:model-value=\"onUserSelected\"\n ></v-select>\n <v-chip v-if=\"selectedUserId\" color=\"primary\" class=\"mt-2\"> Viewing data for: {{ selectedUserId }} </v-chip>\n </v-card-text>\n </v-card>\n\n <!-- Course Filter Section -->\n <v-card class=\"mb-4\">\n <v-card-title>Search Filters</v-card-title>\n <v-card-text>\n <v-select\n v-model=\"selectedCourseId\"\n :items=\"courseOptions\"\n item-title=\"label\"\n item-value=\"value\"\n label=\"Filter by Course (optional)\"\n clearable\n @update:model-value=\"onCourseFilterChanged\"\n ></v-select>\n <div v-if=\"selectedCourseId\" class=\"mt-2\">\n <v-chip color=\"info\" class=\"mb-1\"> 🎯 {{ getCourseNameById(selectedCourseId) }} </v-chip>\n <div v-if=\"getSelectedCourseDetails()\" class=\"text-caption text-grey-darken-1\">\n {{ getSelectedCourseDetails() }}\n </div>\n </div>\n <v-chip v-else color=\"warning\" class=\"mt-2\">\n ⚠️ Searching ALL {{ courseOptions.length }} courses (may be slow)\n </v-chip>\n </v-card-text>\n </v-card>\n\n <!-- Card Search Section -->\n <card-search @search=\"onSearch\" />\n <v-row>\n <v-col cols=\"6\">\n <card-search-results\n v-if=\"query\"\n :query=\"query\"\n :data-layer=\"dataLayer\"\n :course-filter=\"selectedCourseId\"\n @card-selected=\"onCardSelected\"\n />\n </v-col>\n <v-col cols=\"6\">\n <card-loader\n v-if=\"selectedCard\"\n :qualified_id=\"{ courseID: selectedCard.courseId, cardID: selectedCard.cardId }\"\n :view-lookup=\"viewLookup\"\n />\n </v-col>\n </v-row>\n\n <!-- Card History Section -->\n <v-card v-if=\"selectedCard && selectedUserId\" class=\"mt-4\" variant=\"outlined\">\n <v-card-title class=\"d-flex align-center\">\n Card history: {{ selectedUserId }}\n <v-spacer />\n <v-btn variant=\"text\" size=\"small\" prepend-icon=\"mdi-account-search\" :to=\"learnerLink\">\n Learner diagnostics\n </v-btn>\n </v-card-title>\n <v-card-text>\n <v-progress-linear v-if=\"dossierLoading\" indeterminate />\n <v-alert v-else-if=\"dossierError\" type=\"error\" variant=\"tonal\" density=\"compact\">{{ dossierError }}</v-alert>\n <card-dossier-detail v-else-if=\"cardDossier\" :dossier=\"cardDossier\" />\n <div v-else class=\"text-caption text-disabled\">{{ selectedUserId }} has no history with this card.</div>\n </v-card-text>\n </v-card>\n </div>\n</template>\n\n<script lang=\"ts\">\nimport { defineComponent } from 'vue';\nimport { CardSearch, CardSearchResults, CardLoader, getCurrentUser } from '@vue-skuilder/common-ui';\nimport { CardDossierDetail } from '@vue-skuilder/common-ui/admin';\nimport '@vue-skuilder/common-ui/admin/style';\nimport { getDataLayer, CourseLookup } from '@vue-skuilder/db';\nimport {\n cardDossiers,\n fetchLearnerDump,\n fromDump,\n type CardDossier,\n type LearnerDump,\n} from '@vue-skuilder/db/diagnostics';\nimport { allCourseWare } from '@vue-skuilder/courseware';\nimport { UserDBInterface, DataLayerProvider, AdminDBInterface } from '@vue-skuilder/db';\nimport ENV from '../ENVIRONMENT_VARS';\n\n// A learner's records, per learner and course, fetched once per page load with\n// the admin's couch session cookie. Not through a data-layer UserDB: building\n// another user's UserDB replaces the logged-in one.\nconst dumps = new Map<string, Promise<LearnerDump>>();\n\nfunction learnerDump(username: string, courseId: string): Promise<LearnerDump> {\n const key = `${courseId}::${username}`;\n if (!dumps.has(key)) {\n const pending = fetchLearnerDump({\n couchUrl: `${ENV.COUCHDB_SERVER_PROTOCOL}://${ENV.COUCHDB_SERVER_URL}`,\n username,\n courseId,\n auth: { kind: 'cookie' },\n });\n dumps.set(key, pending);\n pending.catch(() => dumps.delete(key));\n }\n return dumps.get(key)!;\n}\n\ninterface UserOption {\n label: string;\n value: string;\n}\n\nexport default defineComponent({\n name: 'AdminDashboard',\n components: {\n CardSearch,\n CardSearchResults,\n CardDossierDetail,\n CardLoader,\n },\n data() {\n return {\n query: '',\n selectedCard: null as { cardId: string; courseId: string } | null,\n userId: '',\n userDB: null as UserDBInterface | null,\n dataLayer: null as DataLayerProvider | null,\n adminDB: null as AdminDBInterface | null,\n selectedUserId: '',\n cardDossier: null as CardDossier | null,\n dossierLoading: false,\n dossierError: null as string | null,\n selectedCourseId: null as string | null,\n userOptions: [] as UserOption[],\n courseOptions: [] as UserOption[],\n };\n },\n computed: {\n learnerLink(): string {\n if (!this.selectedCard || !this.selectedUserId) return '/admin/learners';\n const course = encodeURIComponent(this.selectedCard.courseId);\n return `/admin/learners/${course}/${encodeURIComponent(this.selectedUserId)}`;\n },\n },\n async created() {\n this.userDB = await getCurrentUser();\n this.userId = this.userDB.getUsername();\n this.dataLayer = await getDataLayer();\n this.adminDB = this.dataLayer.getAdminDB();\n\n // Load user list for selection\n await this.loadUsers();\n\n // Load available courses\n await this.loadCourses();\n },\n methods: {\n async loadUsers() {\n try {\n const users = await this.adminDB!.getUsers();\n this.userOptions = users\n .filter((user) => user.name && !user.name.startsWith('Guest_'))\n .map((user) => ({\n label: user.name || 'Unknown User',\n value: user.name || '',\n }))\n .filter((option) => option.value !== '');\n } catch (error) {\n console.error('Failed to load users:', error);\n }\n },\n\n onUserSelected() {\n void this.loadDossier();\n },\n\n /** The selected learner's record for the selected card, from their dataset. */\n async loadDossier() {\n const card = this.selectedCard;\n const user = this.selectedUserId;\n this.cardDossier = null;\n this.dossierError = null;\n if (!card || !user) return;\n this.dossierLoading = true;\n try {\n const dataset = fromDump(await learnerDump(user, card.courseId), card.courseId);\n if (card !== this.selectedCard || user !== this.selectedUserId) return;\n this.cardDossier = cardDossiers(dataset).find((d) => d.cardId === card.cardId) ?? null;\n } catch (error) {\n this.dossierError = error instanceof Error ? error.message : String(error);\n } finally {\n this.dossierLoading = false;\n }\n },\n\n onSearch(query: string) {\n this.query = query;\n },\n\n onCardSelected(card: { cardId: string; courseId: string }) {\n this.selectedCard = card;\n void this.loadDossier();\n },\n\n async loadCourses() {\n try {\n // Try efficient lookup-only approach first\n // const coursesDB = this.dataLayer!.getCoursesDB();\n\n // Get basic course list from lookup DB (lightweight, just IDs and basic names)\n const lookupCourses = await CourseLookup.allCourseWare();\n\n this.courseOptions = lookupCourses\n .filter((course) => course._id)\n .map((course) => {\n const courseName = course.name && course.name.trim();\n let displayName: string;\n let sortKey: string;\n\n if (courseName && courseName !== course._id && courseName !== '') {\n // Has a real name different from ID\n displayName = courseName;\n sortKey = courseName.toLowerCase();\n } else {\n // No name or name is same as ID - use ID\n displayName = course._id;\n sortKey = course._id.toLowerCase();\n }\n\n // Simple display without expensive metadata lookups\n return {\n label: `📚 ${displayName}`,\n value: course._id,\n sortKey,\n };\n })\n .sort((a, b) => a.sortKey.localeCompare(b.sortKey));\n\n console.log(`Loaded ${this.courseOptions.length} courses for filtering (lightweight mode)`);\n } catch (error) {\n console.error('Failed to load courses (lightweight):', error);\n // Fallback to expensive approach\n await this.loadCoursesExpensive();\n }\n },\n\n async loadCoursesExpensive() {\n try {\n console.log('Using expensive course loading (full configs)...');\n const adminDB = this.dataLayer!.getAdminDB();\n const basicCourseList = await adminDB.getCourses(); // This fetches full configs\n\n this.courseOptions = basicCourseList\n .filter((course) => course.courseID)\n .map((course) => {\n const courseName = course.name && course.name.trim();\n const creator = course.creator ? ` (${course.creator})` : '';\n\n let displayName: string;\n let sortKey: string;\n\n if (courseName && courseName !== course.courseID) {\n // Has a real name different from ID\n displayName = courseName;\n sortKey = courseName.toLowerCase();\n } else {\n // No name or name is same as ID - use ID\n displayName = course.courseID!;\n sortKey = course.courseID!.toLowerCase();\n }\n\n const visibility = course.public ? '🌐' : '🔒';\n\n return {\n label: `${visibility} ${displayName}${creator}`,\n value: course.courseID!,\n sortKey,\n };\n })\n .sort((a, b) => a.sortKey.localeCompare(b.sortKey));\n\n console.log(`Loaded ${this.courseOptions.length} courses for filtering (expensive mode)`);\n } catch (error) {\n console.error('Failed to load courses (expensive):', error);\n // Final fallback\n this.loadCoursesBasic();\n }\n },\n\n async loadCoursesBasic() {\n try {\n // Fallback: just get course IDs from lookup DB (fast)\n console.log('Using fallback course loading (IDs only)');\n // This would need a new method that just gets the lookup docs without full configs\n // For now, we'll show a basic message\n this.courseOptions = [{ label: '⚠️ Course names not available - using IDs only', value: '' }];\n } catch (error) {\n console.error('Failed to load basic courses:', error);\n }\n },\n\n onCourseFilterChanged() {\n // If there's an active search, re-run it with the new filter\n if (this.query) {\n // The CardSearchResults component will automatically re-search with the new filter\n }\n },\n\n getCourseNameById(courseId: string): string {\n const course = this.courseOptions.find((c) => c.value === courseId);\n return course ? course.label : courseId;\n },\n\n getSelectedCourseDetails(): string {\n if (!this.selectedCourseId) return '';\n\n // For now, just return the ID as additional context\n return `ID: ${this.selectedCourseId}`;\n },\n\n viewLookup(viewDescriptor: unknown) {\n return allCourseWare.getView(viewDescriptor);\n },\n },\n});\n</script>\n\n<style scoped>\n.admin-dashboard {\n padding: 2rem;\n}\n</style>\n"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import"./chunk-D6hFPZc3.js";import{C as e,H as t,K as n,T as r,_ as i,b as a,ft as o,gt as s,ut as c,v as l,y as u,z as d}from"./vue.runtime.esm-bundler--Fxmd6l9.js";import{i as f}from"./vue-router-HoUBO3s7.js";import"./moment-DbT30ZpG.js";import{z as p}from"./contentSource-
|
|
2
|
-
//# sourceMappingURL=AdminLearner-
|
|
1
|
+
import"./chunk-D6hFPZc3.js";import{C as e,H as t,K as n,T as r,_ as i,b as a,ft as o,gt as s,ut as c,v as l,y as u,z as d}from"./vue.runtime.esm-bundler--Fxmd6l9.js";import{i as f}from"./vue-router-HoUBO3s7.js";import"./moment-DbT30ZpG.js";import{z as p}from"./contentSource-FGhM_ge8.js";import"./dist-DGbpjpKB.js";import"./PipelineDebugger-DhnKlMgD.js";import{d as m}from"./userDBKeys-3SQjNVdK.js";import"./eloDistance-CRxxdYPJ.js";import{r as h}from"./index-BRaeVAZb.js";import{n as g,r as _}from"./admin-hzNSXp0o.js";var v={class:`pa-8`},y={class:`d-flex flex-wrap ga-4 mb-4`},b=r({__name:`AdminLearner`,props:{courseId:{},username:{}},setup(r){let b=r,x=f(),S=`${h.COUCHDB_SERVER_PROTOCOL}://${h.COUCHDB_SERVER_URL}`,C=c([]),w=c([]),T=c(b.username),E=o(null);function syncUrl(e,t){let n=[`/admin/learners`,e,e?t:void 0].filter(e=>!!e);x.replace(n.map((e,t)=>t===0?e:encodeURIComponent(e)).join(`/`))}function pickCourse(e){T.value=void 0,E.value=null,syncUrl(e??void 0)}function pickUser(e){T.value=e??void 0,syncUrl(b.courseId,T.value)}function onLoaded(e){E.value=e,e.username!==b.username&&syncUrl(b.courseId,e.username)}return d(async()=>{try{C.value=(await p.allCourseWare()).filter(e=>e._id).map(e=>({label:e.name?.trim()||e._id,value:e._id})).sort((e,t)=>e.label.localeCompare(t.label))}catch(e){console.error(`[AdminLearner] course list failed`,e)}try{w.value=(await(await m()).getAdminDB().getUsers()).map(e=>e.name).filter(e=>!!e&&!e.startsWith(`Guest`)).sort()}catch(e){console.error(`[AdminLearner] user list failed`,e)}}),(r,o)=>{let c=n(`v-autocomplete`);return t(),a(`div`,v,[o[0]||(o[0]=i(`h1`,{class:`text-h5 mb-4`},`Learner`,-1)),i(`div`,y,[e(c,{"model-value":r.courseId,items:C.value,"item-title":`label`,"item-value":`value`,label:`Course`,density:`compact`,"hide-details":``,style:{"max-width":`28rem`},"onUpdate:modelValue":pickCourse},null,8,[`model-value`,`items`]),e(c,{"model-value":T.value,items:w.value,label:`Learner`,density:`compact`,"hide-details":``,style:{"max-width":`20rem`},disabled:!r.courseId,"onUpdate:modelValue":pickUser},null,8,[`model-value`,`items`,`disabled`])]),r.courseId?(t(),l(s(_),{key:`${r.courseId}/${T.value??``}`,"couch-url":S,"course-id":r.courseId,"initial-username":T.value,autoload:!!T.value,onLoaded},null,8,[`course-id`,`initial-username`,`autoload`])):u(``,!0),E.value?(t(),l(s(g),{key:1,dataset:E.value,class:`mt-6`},null,8,[`dataset`])):u(``,!0)])}}});export{b as default};
|
|
2
|
+
//# sourceMappingURL=AdminLearner-Br10DFSt.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AdminLearner-
|
|
1
|
+
{"version":3,"file":"AdminLearner-Br10DFSt.js","names":[],"sources":["../../src/views/AdminLearner.vue","../../src/views/AdminLearner.vue"],"sourcesContent":["<template>\n <div class=\"pa-8\">\n <h1 class=\"text-h5 mb-4\">Learner</h1>\n\n <div class=\"d-flex flex-wrap ga-4 mb-4\">\n <v-autocomplete\n :model-value=\"courseId\"\n :items=\"courseOptions\"\n item-title=\"label\"\n item-value=\"value\"\n label=\"Course\"\n density=\"compact\"\n hide-details\n style=\"max-width: 28rem\"\n @update:model-value=\"pickCourse\"\n />\n <v-autocomplete\n :model-value=\"selectedUser\"\n :items=\"userOptions\"\n label=\"Learner\"\n density=\"compact\"\n hide-details\n style=\"max-width: 20rem\"\n :disabled=\"!courseId\"\n @update:model-value=\"pickUser\"\n />\n </div>\n\n <learner-dataset-source\n v-if=\"courseId\"\n :key=\"`${courseId}/${selectedUser ?? ''}`\"\n :couch-url=\"couchUrl\"\n :course-id=\"courseId\"\n :initial-username=\"selectedUser\"\n :autoload=\"!!selectedUser\"\n @loaded=\"onLoaded\"\n />\n <learner-diagnostics v-if=\"dataset\" :dataset=\"dataset\" class=\"mt-6\" />\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { onMounted, ref, shallowRef } from 'vue';\nimport { useRouter } from 'vue-router';\nimport { CourseLookup, getDataLayer } from '@vue-skuilder/db';\nimport type { LearnerDataset } from '@vue-skuilder/db/diagnostics';\nimport { LearnerDatasetSource, LearnerDiagnostics } from '@vue-skuilder/common-ui/admin';\nimport '@vue-skuilder/common-ui/admin/style';\nimport ENV from '../ENVIRONMENT_VARS';\n\n/**\n * Admin view of one learner in one course: `/admin/learners/:courseId?/:username?`.\n * Reads through the admin's couch session cookie, same origin as the app.\n */\nconst props = defineProps<{ courseId?: string; username?: string }>();\n\nconst router = useRouter();\nconst couchUrl = `${ENV.COUCHDB_SERVER_PROTOCOL}://${ENV.COUCHDB_SERVER_URL}`;\n\nconst courseOptions = ref<Array<{ label: string; value: string }>>([]);\nconst userOptions = ref<string[]>([]);\n// Drives the source component's key. The route can follow a learner typed\n// into the component without remounting (and refetching) it.\nconst selectedUser = ref(props.username);\nconst dataset = shallowRef<LearnerDataset | null>(null);\n\nfunction syncUrl(courseId?: string, username?: string): void {\n const parts = ['/admin/learners', courseId, courseId ? username : undefined].filter((p): p is string => !!p);\n void router.replace(parts.map((p, i) => (i === 0 ? p : encodeURIComponent(p))).join('/'));\n}\n\nfunction pickCourse(courseId: string | null): void {\n selectedUser.value = undefined;\n dataset.value = null;\n syncUrl(courseId ?? undefined);\n}\n\nfunction pickUser(username: string | null): void {\n selectedUser.value = username ?? undefined;\n syncUrl(props.courseId, selectedUser.value);\n}\n\nfunction onLoaded(loaded: LearnerDataset): void {\n dataset.value = loaded;\n if (loaded.username !== props.username) syncUrl(props.courseId, loaded.username);\n}\n\nonMounted(async () => {\n try {\n const courses = await CourseLookup.allCourseWare();\n courseOptions.value = courses\n .filter((c) => c._id)\n .map((c) => ({ label: c.name?.trim() || c._id, value: c._id }))\n .sort((a, b) => a.label.localeCompare(b.label));\n } catch (e) {\n console.error('[AdminLearner] course list failed', e);\n }\n try {\n const users = await (await getDataLayer()).getAdminDB().getUsers();\n userOptions.value = users\n .map((u) => (u as { name?: string }).name)\n .filter((name): name is string => !!name && !name.startsWith('Guest'))\n .sort();\n } catch (e) {\n console.error('[AdminLearner] user list failed', e);\n }\n});\n</script>\n","<template>\n <div class=\"pa-8\">\n <h1 class=\"text-h5 mb-4\">Learner</h1>\n\n <div class=\"d-flex flex-wrap ga-4 mb-4\">\n <v-autocomplete\n :model-value=\"courseId\"\n :items=\"courseOptions\"\n item-title=\"label\"\n item-value=\"value\"\n label=\"Course\"\n density=\"compact\"\n hide-details\n style=\"max-width: 28rem\"\n @update:model-value=\"pickCourse\"\n />\n <v-autocomplete\n :model-value=\"selectedUser\"\n :items=\"userOptions\"\n label=\"Learner\"\n density=\"compact\"\n hide-details\n style=\"max-width: 20rem\"\n :disabled=\"!courseId\"\n @update:model-value=\"pickUser\"\n />\n </div>\n\n <learner-dataset-source\n v-if=\"courseId\"\n :key=\"`${courseId}/${selectedUser ?? ''}`\"\n :couch-url=\"couchUrl\"\n :course-id=\"courseId\"\n :initial-username=\"selectedUser\"\n :autoload=\"!!selectedUser\"\n @loaded=\"onLoaded\"\n />\n <learner-diagnostics v-if=\"dataset\" :dataset=\"dataset\" class=\"mt-6\" />\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { onMounted, ref, shallowRef } from 'vue';\nimport { useRouter } from 'vue-router';\nimport { CourseLookup, getDataLayer } from '@vue-skuilder/db';\nimport type { LearnerDataset } from '@vue-skuilder/db/diagnostics';\nimport { LearnerDatasetSource, LearnerDiagnostics } from '@vue-skuilder/common-ui/admin';\nimport '@vue-skuilder/common-ui/admin/style';\nimport ENV from '../ENVIRONMENT_VARS';\n\n/**\n * Admin view of one learner in one course: `/admin/learners/:courseId?/:username?`.\n * Reads through the admin's couch session cookie, same origin as the app.\n */\nconst props = defineProps<{ courseId?: string; username?: string }>();\n\nconst router = useRouter();\nconst couchUrl = `${ENV.COUCHDB_SERVER_PROTOCOL}://${ENV.COUCHDB_SERVER_URL}`;\n\nconst courseOptions = ref<Array<{ label: string; value: string }>>([]);\nconst userOptions = ref<string[]>([]);\n// Drives the source component's key. The route can follow a learner typed\n// into the component without remounting (and refetching) it.\nconst selectedUser = ref(props.username);\nconst dataset = shallowRef<LearnerDataset | null>(null);\n\nfunction syncUrl(courseId?: string, username?: string): void {\n const parts = ['/admin/learners', courseId, courseId ? username : undefined].filter((p): p is string => !!p);\n void router.replace(parts.map((p, i) => (i === 0 ? p : encodeURIComponent(p))).join('/'));\n}\n\nfunction pickCourse(courseId: string | null): void {\n selectedUser.value = undefined;\n dataset.value = null;\n syncUrl(courseId ?? undefined);\n}\n\nfunction pickUser(username: string | null): void {\n selectedUser.value = username ?? undefined;\n syncUrl(props.courseId, selectedUser.value);\n}\n\nfunction onLoaded(loaded: LearnerDataset): void {\n dataset.value = loaded;\n if (loaded.username !== props.username) syncUrl(props.courseId, loaded.username);\n}\n\nonMounted(async () => {\n try {\n const courses = await CourseLookup.allCourseWare();\n courseOptions.value = courses\n .filter((c) => c._id)\n .map((c) => ({ label: c.name?.trim() || c._id, value: c._id }))\n .sort((a, b) => a.label.localeCompare(b.label));\n } catch (e) {\n console.error('[AdminLearner] course list failed', e);\n }\n try {\n const users = await (await getDataLayer()).getAdminDB().getUsers();\n userOptions.value = users\n .map((u) => (u as { name?: string }).name)\n .filter((name): name is string => !!name && !name.startsWith('Guest'))\n .sort();\n } catch (e) {\n console.error('[AdminLearner] user list failed', e);\n }\n});\n</script>\n"],"mappings":"woBCsDA,IAAM,EAAQ,EAER,EAAS,GAAW,CACpB,EAAW,GAAG,EAAI,wBAAwB,KAAK,EAAI,qBAEnD,EAAgB,EAA6C,EAAE,CAAC,CAChE,EAAc,EAAc,EAAE,CAAC,CAG/B,EAAe,EAAI,EAAM,SAAS,CAClC,EAAU,EAAkC,KAAK,CAEvD,SAAS,QAAQ,EAAmB,EAAyB,CAC3D,IAAM,EAAQ,CAAC,kBAAmB,EAAU,EAAW,EAAW,IAAA,GAAU,CAAC,OAAQ,GAAmB,CAAC,CAAC,EAAE,CACvG,EAAO,QAAQ,EAAM,KAAK,EAAG,IAAO,IAAM,EAAI,EAAI,mBAAmB,EAAE,CAAE,CAAC,KAAK,IAAI,CAAC,CAG3F,SAAS,WAAW,EAA+B,CACjD,EAAa,MAAQ,IAAA,GACrB,EAAQ,MAAQ,KAChB,QAAQ,GAAY,IAAA,GAAU,CAGhC,SAAS,SAAS,EAA+B,CAC/C,EAAa,MAAQ,GAAY,IAAA,GACjC,QAAQ,EAAM,SAAU,EAAa,MAAM,CAG7C,SAAS,SAAS,EAA8B,CAC9C,EAAQ,MAAQ,EACZ,EAAO,WAAa,EAAM,UAAU,QAAQ,EAAM,SAAU,EAAO,SAAS,QAGlF,EAAU,SAAY,CACpB,GAAI,CAEF,EAAc,OADE,MAAM,EAAa,eAAe,EAE/C,OAAQ,GAAM,EAAE,IAAG,CACnB,IAAK,IAAO,CAAE,MAAO,EAAE,MAAM,MAAM,EAAI,EAAE,IAAK,MAAO,EAAE,IAAK,EAAC,CAC7D,MAAM,EAAG,IAAM,EAAE,MAAM,cAAc,EAAE,MAAM,CAAC,OAC1C,EAAG,CACV,QAAQ,MAAM,oCAAqC,EAAE,CAEvD,GAAI,CAEF,EAAY,OADE,MAAO,MAAM,GAAc,EAAE,YAAY,CAAC,UAAU,EAE/D,IAAK,GAAO,EAAwB,KAAI,CACxC,OAAQ,GAAyB,CAAC,CAAC,GAAQ,CAAC,EAAK,WAAW,QAAQ,CAAA,CACpE,MAAM,OACF,EAAG,CACV,QAAQ,MAAM,kCAAmC,EAAE,GAErD"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"./moment-DbT30ZpG.js";import{d as e}from"./contentSource-FGhM_ge8.js";import"./PipelineDebugger-DhnKlMgD.js";import"./eloDistance-CRxxdYPJ.js";export{e as BaseUser};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import"./chunk-D6hFPZc3.js";import{$ as e,C as t,H as n,Ht as r,K as i,S as a,T as o,W as s,_ as c,b as l,f as u,v as d,y as f}from"./vue.runtime.esm-bundler--Fxmd6l9.js";import"./MarkdownRenderer-borh39Lq.js";import{t as p}from"./moment-DbT30ZpG.js";import{g as m}from"./dist-
|
|
2
|
-
//# sourceMappingURL=ClassroomCtrlPanel-
|
|
1
|
+
import"./chunk-D6hFPZc3.js";import{$ as e,C as t,H as n,Ht as r,K as i,S as a,T as o,W as s,_ as c,b as l,f as u,v as d,y as f}from"./vue.runtime.esm-bundler--Fxmd6l9.js";import"./MarkdownRenderer-borh39Lq.js";import{t as p}from"./moment-DbT30ZpG.js";import{g as m}from"./dist-C8EQUxWW.js";import"./contentSource-FGhM_ge8.js";import"./dist-DGbpjpKB.js";import"./PipelineDebugger-DhnKlMgD.js";import{d as h}from"./userDBKeys-3SQjNVdK.js";import"./eloDistance-CRxxdYPJ.js";import{t as g}from"./index-BRaeVAZb.js";var _=o({name:`ClassroomCtrlPanel`,props:{classroomId:{type:String,required:!0}},data(){return{classroomCfg:null,classroomDB:null,assignedContent:[],nameRules:[e=>e.length>30?`Course name must be 30 characters or less`:!0],updatePending:!0,addingContent:!1,availableCourses:[],selectedCourse:``,availableTags:[],selectedTags:[]}},computed:{_assignedCourses(){return this.assignedContent.filter(e=>e.type===`course`)},_assignedTags(){return this.assignedContent.filter(e=>e.type===`tag`)}},watch:{async selectedCourse(){this.selectedCourse?this.availableTags=(await h().getCourseDB(this.selectedCourse).getCourseTagStubs()).rows.map(e=>e.doc):this.availableTags=[]}},async created(){try{this.classroomDB=await h().getClassroomDB(this.classroomId,`teacher`),this.assignedContent=await this.classroomDB.getAssignedContent(),this.classroomCfg=this.classroomDB.getConfig(),console.log(`[ClassroomCtrlPanel] Route loaded w/ (prop) _id: ${this.classroomId}`),console.log(`[ClassroomCtrlPanel] Config: ${JSON.stringify(this.classroomCfg)}`),this.availableCourses=await h().getCoursesDB().getCourseList(),this.updatePending=!1}catch(e){console.error(`[ClassroomCtrlPanel] Error initializing:`,e)}},methods:{async assignContent(){if(!this.classroomDB)return;let e=await m();this.selectedTags.length===0?await this.classroomDB.assignContent?.({assignedOn:p(),activeOn:p(),type:`course`,courseID:this.selectedCourse,assignedBy:e.getUsername()}):await Promise.all(this.selectedTags.map(t=>this.classroomDB.assignContent?.({assignedOn:p(),activeOn:p(),type:`tag`,courseID:this.selectedCourse,tagID:t,assignedBy:e.getUsername()}))),this.assignedContent=await this.classroomDB.getAssignedContent(),this.addingContent=!1,this.selectedCourse=``,this.selectedTags=[],this.availableTags=[]},async removeContent(e){this.classroomDB&&this.classroomDB.removeContent&&(await this.classroomDB.removeContent(e),this.assignedContent=await this.classroomDB.getAssignedContent())},async submit(){this.updatePending=!0}}}),v={key:0},y=[`onClick`],b=[`onClick`];function _sfc_render(o,p,m,h,g,_){let x=i(`router-link`),S=i(`v-btn`),C=i(`v-checkbox`),w=i(`v-col`),T=i(`v-icon`),E=i(`v-fade-transition`),D=i(`v-toolbar-title`),O=i(`v-spacer`),k=i(`v-toolbar`),A=i(`v-select`),j=i(`v-card-text`),M=i(`v-card-actions`),N=i(`v-card`),P=i(`v-row`);return o.updatePending?f(``,!0):(n(),l(`div`,v,[c(`h1`,null,[t(x,{to:`/classrooms`},{default:e(()=>p[5]||(p[5]=[a(`My Classrooms`)])),_:1}),a(` / `+r(o.classroomCfg.name),1)]),c(`h3`,null,[a(` Join code: `+r(o.classroomCfg.joinCode)+` `,1),t(x,{to:`/classrooms/${o.classroomId}/code`},{default:e(()=>[t(S,{size:`x-small`,icon:`mdi-fullscreen`,color:`accent`,alt:`Make Fullscreen`})]),_:1},8,[`to`])]),t(P,null,{default:e(()=>[t(w,{cols:`12`,sm:`6`,md:`4`},{default:e(()=>[t(C,{modelValue:o.classroomCfg.peerAssist,"onUpdate:modelValue":p[0]||(p[0]=e=>o.classroomCfg.peerAssist=e),label:`Allow peer instruction`},null,8,[`modelValue`])]),_:1}),o.classroomDB?(n(),d(w,{key:0,cols:`12`},{default:e(()=>[p[10]||(p[10]=c(`h2`,null,`Assigned Content:`,-1)),p[11]||(p[11]=c(`h3`,null,`Quilts:`,-1)),p[12]||(p[12]=c(`ul`,null,null,-1)),c(`ul`,null,[(n(!0),l(u,null,s(o._assignedCourses,e=>(n(),l(`li`,{key:e.courseID},[a(r(e.courseID)+` `,1),c(`a`,{onClick:t=>o.removeContent(e)},`Remove`,8,y)]))),128))]),p[13]||(p[13]=c(`h3`,null,`Tags:`,-1)),c(`ul`,null,[(n(!0),l(u,null,s(o._assignedTags,(e,t)=>(n(),l(`li`,{key:t},[a(r(e.courseID)+` - `+r(e.tagID)+` `,1),c(`a`,{onClick:t=>o.removeContent(e)},`Remove`,8,b)]))),128))]),t(E,null,{default:e(()=>[o.addingContent?f(``,!0):(n(),d(S,{key:0,color:`primary`,onClick:p[1]||(p[1]=e=>o.addingContent=!0)},{default:e(()=>[p[7]||(p[7]=a(` Assign New Content `)),t(T,{end:``},{default:e(()=>p[6]||(p[6]=[a(`mdi-plus`)])),_:1})]),_:1}))]),_:1}),o.addingContent?(n(),d(N,{key:0},{default:e(()=>[t(k,null,{default:e(()=>[t(D,null,{default:e(()=>p[8]||(p[8]=[a(`Add Content`)])),_:1}),t(O),t(S,{icon:`mdi-close`,color:`error`,onClick:p[2]||(p[2]=e=>o.addingContent=!1)})]),_:1}),t(j,null,{default:e(()=>[t(A,{modelValue:o.selectedCourse,"onUpdate:modelValue":p[3]||(p[3]=e=>o.selectedCourse=e),label:`Select Quilt`,items:o.availableCourses,"item-title":`name`,"item-value":`_id`,title:`Select Quilt`},null,8,[`modelValue`,`items`]),t(A,{modelValue:o.selectedTags,"onUpdate:modelValue":p[4]||(p[4]=e=>o.selectedTags=e),label:`Select Tags`,items:o.availableTags,"item-title":`name`,"item-value":`name`,multiple:``,chips:``,hint:``,"persistent-hint":``},null,8,[`modelValue`,`items`])]),_:1}),t(M,null,{default:e(()=>[o.selectedCourse===``?f(``,!0):(n(),d(S,{key:0,color:`primary`,onClick:o.assignContent},{default:e(()=>[a(r(o.selectedTags.length==0?`Add Entire Quilt`:`Add Tags`)+` `,1),t(T,{end:``},{default:e(()=>p[9]||(p[9]=[a(`mdi-plus`)])),_:1})]),_:1},8,[`onClick`]))]),_:1})]),_:1})):f(``,!0)]),_:1})):f(``,!0)]),_:1})]))}var x=g(_,[[`render`,_sfc_render]]);export{x as default};
|
|
2
|
+
//# sourceMappingURL=ClassroomCtrlPanel-DQJQ0B7M.js.map
|
package/dist/assets/{ClassroomCtrlPanel-BSAG3h8B.js.map → ClassroomCtrlPanel-DQJQ0B7M.js.map}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ClassroomCtrlPanel-BSAG3h8B.js","names":[],"sources":["../../src/components/Classrooms/ClassroomCtrlPanel.vue","../../src/components/Classrooms/ClassroomCtrlPanel.vue"],"sourcesContent":["<template>\n <div v-if=\"!updatePending\">\n <h1><router-link to=\"/classrooms\">My Classrooms</router-link> / {{ classroomCfg!.name }}</h1>\n\n <h3>\n Join code: {{ classroomCfg!.joinCode }}\n <router-link :to=\"`/classrooms/${classroomId}/code`\">\n <v-btn size=\"x-small\" icon=\"mdi-fullscreen\" color=\"accent\" alt=\"Make Fullscreen\"> </v-btn>\n </router-link>\n </h3>\n <v-row>\n <v-col cols=\"12\" sm=\"6\" md=\"4\">\n <v-checkbox v-model=\"classroomCfg!.peerAssist\" label=\"Allow peer instruction\"></v-checkbox>\n </v-col>\n <v-col v-if=\"classroomDB\" cols=\"12\">\n <h2>Assigned Content:</h2>\n <h3>Quilts:</h3>\n <ul></ul>\n <ul>\n <li v-for=\"c in _assignedCourses\" :key=\"c.courseID\">\n {{ c.courseID }} <a @click=\"removeContent(c)\">Remove</a>\n </li>\n </ul>\n <h3>Tags:</h3>\n <ul>\n <li v-for=\"(c, i) in _assignedTags\" :key=\"i\">\n {{ c.courseID }} - {{ c.tagID }} <a @click=\"removeContent(c)\">Remove</a>\n </li>\n </ul>\n <v-fade-transition>\n <v-btn v-if=\"!addingContent\" color=\"primary\" @click=\"addingContent = true\">\n Assign New Content\n <v-icon end>mdi-plus</v-icon>\n </v-btn>\n </v-fade-transition>\n <v-card v-if=\"addingContent\">\n <v-toolbar>\n <v-toolbar-title>Add Content</v-toolbar-title>\n <v-spacer></v-spacer>\n <v-btn icon=\"mdi-close\" color=\"error\" @click=\"addingContent = false\"> </v-btn>\n </v-toolbar>\n <v-card-text>\n <v-select\n v-model=\"selectedCourse\"\n label=\"Select Quilt\"\n :items=\"availableCourses\"\n item-title=\"name\"\n item-value=\"_id\"\n title=\"Select Quilt\"\n ></v-select>\n\n <v-select\n v-model=\"selectedTags\"\n label=\"Select Tags\"\n :items=\"availableTags\"\n item-title=\"name\"\n item-value=\"name\"\n multiple\n chips\n hint=\"\"\n persistent-hint\n ></v-select>\n </v-card-text>\n\n <v-card-actions>\n <v-btn v-if=\"selectedCourse !== ''\" color=\"primary\" @click=\"assignContent\">\n {{ selectedTags.length == 0 ? 'Add Entire Quilt' : 'Add Tags' }}\n <v-icon end>mdi-plus</v-icon>\n </v-btn>\n </v-card-actions>\n </v-card>\n </v-col>\n </v-row>\n </div>\n</template>\n\n<script lang=\"ts\">\nimport moment from 'moment';\nimport { TeacherClassroomDBInterface, AssignedContent, AssignedTag, Tag, getDataLayer } from '@vue-skuilder/db';\nimport { ClassroomConfig, CourseConfig } from '@vue-skuilder/common';\nimport { defineComponent } from 'vue';\nimport { getCurrentUser } from '@vue-skuilder/common-ui';\n\nexport default defineComponent({\n name: 'ClassroomCtrlPanel',\n\n props: {\n classroomId: {\n type: String,\n required: true,\n },\n },\n\n data() {\n return {\n classroomCfg: null as ClassroomConfig | null,\n classroomDB: null as TeacherClassroomDBInterface | null,\n assignedContent: [] as AssignedContent[],\n nameRules: [\n (value: string): string | boolean => {\n const max = 30;\n return value.length > max ? `Course name must be ${max} characters or less` : true;\n },\n ],\n updatePending: true,\n addingContent: false,\n availableCourses: [] as CourseConfig[],\n selectedCourse: '',\n availableTags: [] as Tag[],\n selectedTags: [] as string[],\n };\n },\n\n computed: {\n _assignedCourses(): AssignedContent[] {\n return this.assignedContent.filter((c) => c.type === 'course');\n },\n _assignedTags(): AssignedTag[] {\n return this.assignedContent.filter((c) => c.type === 'tag') as AssignedTag[];\n },\n },\n\n watch: {\n async selectedCourse() {\n if (this.selectedCourse) {\n // Get course DB from data layer\n const courseDB = getDataLayer().getCourseDB(this.selectedCourse);\n // Get tags from course DB\n const tagResponse = await courseDB.getCourseTagStubs();\n this.availableTags = tagResponse.rows.map((row) => row.doc!);\n } else {\n this.availableTags = [];\n }\n },\n },\n\n async created() {\n try {\n // Get classroom DB from data layer\n this.classroomDB = (await getDataLayer().getClassroomDB(\n this.classroomId,\n 'teacher'\n )) as TeacherClassroomDBInterface;\n this.assignedContent = await this.classroomDB.getAssignedContent();\n this.classroomCfg = this.classroomDB.getConfig();\n\n console.log(`[ClassroomCtrlPanel] Route loaded w/ (prop) _id: ${this.classroomId}`);\n console.log(`[ClassroomCtrlPanel] Config: ${JSON.stringify(this.classroomCfg)}`);\n\n // Get course list from data layer\n this.availableCourses = await getDataLayer().getCoursesDB().getCourseList();\n\n this.updatePending = false;\n } catch (error) {\n console.error('[ClassroomCtrlPanel] Error initializing:', error);\n }\n },\n\n methods: {\n async assignContent() {\n if (!this.classroomDB) return;\n const u = await getCurrentUser();\n\n if (this.selectedTags.length === 0) {\n await this.classroomDB.assignContent?.({\n assignedOn: moment(),\n activeOn: moment(),\n type: 'course',\n courseID: this.selectedCourse,\n assignedBy: u.getUsername(),\n });\n } else {\n await Promise.all(\n this.selectedTags.map((tag) =>\n this.classroomDB!.assignContent?.({\n assignedOn: moment(),\n activeOn: moment(),\n type: 'tag',\n courseID: this.selectedCourse,\n tagID: tag,\n assignedBy: u.getUsername(),\n })\n )\n );\n }\n\n this.assignedContent = await this.classroomDB.getAssignedContent();\n this.addingContent = false;\n this.selectedCourse = '';\n this.selectedTags = [];\n this.availableTags = [];\n },\n\n async removeContent(c: AssignedContent) {\n if (this.classroomDB && this.classroomDB.removeContent) {\n await this.classroomDB.removeContent(c);\n this.assignedContent = await this.classroomDB.getAssignedContent();\n }\n },\n\n async submit() {\n this.updatePending = true;\n },\n },\n});\n</script>\n","<template>\n <div v-if=\"!updatePending\">\n <h1><router-link to=\"/classrooms\">My Classrooms</router-link> / {{ classroomCfg!.name }}</h1>\n\n <h3>\n Join code: {{ classroomCfg!.joinCode }}\n <router-link :to=\"`/classrooms/${classroomId}/code`\">\n <v-btn size=\"x-small\" icon=\"mdi-fullscreen\" color=\"accent\" alt=\"Make Fullscreen\"> </v-btn>\n </router-link>\n </h3>\n <v-row>\n <v-col cols=\"12\" sm=\"6\" md=\"4\">\n <v-checkbox v-model=\"classroomCfg!.peerAssist\" label=\"Allow peer instruction\"></v-checkbox>\n </v-col>\n <v-col v-if=\"classroomDB\" cols=\"12\">\n <h2>Assigned Content:</h2>\n <h3>Quilts:</h3>\n <ul></ul>\n <ul>\n <li v-for=\"c in _assignedCourses\" :key=\"c.courseID\">\n {{ c.courseID }} <a @click=\"removeContent(c)\">Remove</a>\n </li>\n </ul>\n <h3>Tags:</h3>\n <ul>\n <li v-for=\"(c, i) in _assignedTags\" :key=\"i\">\n {{ c.courseID }} - {{ c.tagID }} <a @click=\"removeContent(c)\">Remove</a>\n </li>\n </ul>\n <v-fade-transition>\n <v-btn v-if=\"!addingContent\" color=\"primary\" @click=\"addingContent = true\">\n Assign New Content\n <v-icon end>mdi-plus</v-icon>\n </v-btn>\n </v-fade-transition>\n <v-card v-if=\"addingContent\">\n <v-toolbar>\n <v-toolbar-title>Add Content</v-toolbar-title>\n <v-spacer></v-spacer>\n <v-btn icon=\"mdi-close\" color=\"error\" @click=\"addingContent = false\"> </v-btn>\n </v-toolbar>\n <v-card-text>\n <v-select\n v-model=\"selectedCourse\"\n label=\"Select Quilt\"\n :items=\"availableCourses\"\n item-title=\"name\"\n item-value=\"_id\"\n title=\"Select Quilt\"\n ></v-select>\n\n <v-select\n v-model=\"selectedTags\"\n label=\"Select Tags\"\n :items=\"availableTags\"\n item-title=\"name\"\n item-value=\"name\"\n multiple\n chips\n hint=\"\"\n persistent-hint\n ></v-select>\n </v-card-text>\n\n <v-card-actions>\n <v-btn v-if=\"selectedCourse !== ''\" color=\"primary\" @click=\"assignContent\">\n {{ selectedTags.length == 0 ? 'Add Entire Quilt' : 'Add Tags' }}\n <v-icon end>mdi-plus</v-icon>\n </v-btn>\n </v-card-actions>\n </v-card>\n </v-col>\n </v-row>\n </div>\n</template>\n\n<script lang=\"ts\">\nimport moment from 'moment';\nimport { TeacherClassroomDBInterface, AssignedContent, AssignedTag, Tag, getDataLayer } from '@vue-skuilder/db';\nimport { ClassroomConfig, CourseConfig } from '@vue-skuilder/common';\nimport { defineComponent } from 'vue';\nimport { getCurrentUser } from '@vue-skuilder/common-ui';\n\nexport default defineComponent({\n name: 'ClassroomCtrlPanel',\n\n props: {\n classroomId: {\n type: String,\n required: true,\n },\n },\n\n data() {\n return {\n classroomCfg: null as ClassroomConfig | null,\n classroomDB: null as TeacherClassroomDBInterface | null,\n assignedContent: [] as AssignedContent[],\n nameRules: [\n (value: string): string | boolean => {\n const max = 30;\n return value.length > max ? `Course name must be ${max} characters or less` : true;\n },\n ],\n updatePending: true,\n addingContent: false,\n availableCourses: [] as CourseConfig[],\n selectedCourse: '',\n availableTags: [] as Tag[],\n selectedTags: [] as string[],\n };\n },\n\n computed: {\n _assignedCourses(): AssignedContent[] {\n return this.assignedContent.filter((c) => c.type === 'course');\n },\n _assignedTags(): AssignedTag[] {\n return this.assignedContent.filter((c) => c.type === 'tag') as AssignedTag[];\n },\n },\n\n watch: {\n async selectedCourse() {\n if (this.selectedCourse) {\n // Get course DB from data layer\n const courseDB = getDataLayer().getCourseDB(this.selectedCourse);\n // Get tags from course DB\n const tagResponse = await courseDB.getCourseTagStubs();\n this.availableTags = tagResponse.rows.map((row) => row.doc!);\n } else {\n this.availableTags = [];\n }\n },\n },\n\n async created() {\n try {\n // Get classroom DB from data layer\n this.classroomDB = (await getDataLayer().getClassroomDB(\n this.classroomId,\n 'teacher'\n )) as TeacherClassroomDBInterface;\n this.assignedContent = await this.classroomDB.getAssignedContent();\n this.classroomCfg = this.classroomDB.getConfig();\n\n console.log(`[ClassroomCtrlPanel] Route loaded w/ (prop) _id: ${this.classroomId}`);\n console.log(`[ClassroomCtrlPanel] Config: ${JSON.stringify(this.classroomCfg)}`);\n\n // Get course list from data layer\n this.availableCourses = await getDataLayer().getCoursesDB().getCourseList();\n\n this.updatePending = false;\n } catch (error) {\n console.error('[ClassroomCtrlPanel] Error initializing:', error);\n }\n },\n\n methods: {\n async assignContent() {\n if (!this.classroomDB) return;\n const u = await getCurrentUser();\n\n if (this.selectedTags.length === 0) {\n await this.classroomDB.assignContent?.({\n assignedOn: moment(),\n activeOn: moment(),\n type: 'course',\n courseID: this.selectedCourse,\n assignedBy: u.getUsername(),\n });\n } else {\n await Promise.all(\n this.selectedTags.map((tag) =>\n this.classroomDB!.assignContent?.({\n assignedOn: moment(),\n activeOn: moment(),\n type: 'tag',\n courseID: this.selectedCourse,\n tagID: tag,\n assignedBy: u.getUsername(),\n })\n )\n );\n }\n\n this.assignedContent = await this.classroomDB.getAssignedContent();\n this.addingContent = false;\n this.selectedCourse = '';\n this.selectedTags = [];\n this.availableTags = [];\n },\n\n async removeContent(c: AssignedContent) {\n if (this.classroomDB && this.classroomDB.removeContent) {\n await this.classroomDB.removeContent(c);\n this.assignedContent = await this.classroomDB.getAssignedContent();\n }\n },\n\n async submit() {\n this.updatePending = true;\n },\n },\n});\n</script>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ClassroomCtrlPanel-DQJQ0B7M.js","names":[],"sources":["../../src/components/Classrooms/ClassroomCtrlPanel.vue","../../src/components/Classrooms/ClassroomCtrlPanel.vue"],"sourcesContent":["<template>\n <div v-if=\"!updatePending\">\n <h1><router-link to=\"/classrooms\">My Classrooms</router-link> / {{ classroomCfg!.name }}</h1>\n\n <h3>\n Join code: {{ classroomCfg!.joinCode }}\n <router-link :to=\"`/classrooms/${classroomId}/code`\">\n <v-btn size=\"x-small\" icon=\"mdi-fullscreen\" color=\"accent\" alt=\"Make Fullscreen\"> </v-btn>\n </router-link>\n </h3>\n <v-row>\n <v-col cols=\"12\" sm=\"6\" md=\"4\">\n <v-checkbox v-model=\"classroomCfg!.peerAssist\" label=\"Allow peer instruction\"></v-checkbox>\n </v-col>\n <v-col v-if=\"classroomDB\" cols=\"12\">\n <h2>Assigned Content:</h2>\n <h3>Quilts:</h3>\n <ul></ul>\n <ul>\n <li v-for=\"c in _assignedCourses\" :key=\"c.courseID\">\n {{ c.courseID }} <a @click=\"removeContent(c)\">Remove</a>\n </li>\n </ul>\n <h3>Tags:</h3>\n <ul>\n <li v-for=\"(c, i) in _assignedTags\" :key=\"i\">\n {{ c.courseID }} - {{ c.tagID }} <a @click=\"removeContent(c)\">Remove</a>\n </li>\n </ul>\n <v-fade-transition>\n <v-btn v-if=\"!addingContent\" color=\"primary\" @click=\"addingContent = true\">\n Assign New Content\n <v-icon end>mdi-plus</v-icon>\n </v-btn>\n </v-fade-transition>\n <v-card v-if=\"addingContent\">\n <v-toolbar>\n <v-toolbar-title>Add Content</v-toolbar-title>\n <v-spacer></v-spacer>\n <v-btn icon=\"mdi-close\" color=\"error\" @click=\"addingContent = false\"> </v-btn>\n </v-toolbar>\n <v-card-text>\n <v-select\n v-model=\"selectedCourse\"\n label=\"Select Quilt\"\n :items=\"availableCourses\"\n item-title=\"name\"\n item-value=\"_id\"\n title=\"Select Quilt\"\n ></v-select>\n\n <v-select\n v-model=\"selectedTags\"\n label=\"Select Tags\"\n :items=\"availableTags\"\n item-title=\"name\"\n item-value=\"name\"\n multiple\n chips\n hint=\"\"\n persistent-hint\n ></v-select>\n </v-card-text>\n\n <v-card-actions>\n <v-btn v-if=\"selectedCourse !== ''\" color=\"primary\" @click=\"assignContent\">\n {{ selectedTags.length == 0 ? 'Add Entire Quilt' : 'Add Tags' }}\n <v-icon end>mdi-plus</v-icon>\n </v-btn>\n </v-card-actions>\n </v-card>\n </v-col>\n </v-row>\n </div>\n</template>\n\n<script lang=\"ts\">\nimport moment from 'moment';\nimport { TeacherClassroomDBInterface, AssignedContent, AssignedTag, Tag, getDataLayer } from '@vue-skuilder/db';\nimport { ClassroomConfig, CourseConfig } from '@vue-skuilder/common';\nimport { defineComponent } from 'vue';\nimport { getCurrentUser } from '@vue-skuilder/common-ui';\n\nexport default defineComponent({\n name: 'ClassroomCtrlPanel',\n\n props: {\n classroomId: {\n type: String,\n required: true,\n },\n },\n\n data() {\n return {\n classroomCfg: null as ClassroomConfig | null,\n classroomDB: null as TeacherClassroomDBInterface | null,\n assignedContent: [] as AssignedContent[],\n nameRules: [\n (value: string): string | boolean => {\n const max = 30;\n return value.length > max ? `Course name must be ${max} characters or less` : true;\n },\n ],\n updatePending: true,\n addingContent: false,\n availableCourses: [] as CourseConfig[],\n selectedCourse: '',\n availableTags: [] as Tag[],\n selectedTags: [] as string[],\n };\n },\n\n computed: {\n _assignedCourses(): AssignedContent[] {\n return this.assignedContent.filter((c) => c.type === 'course');\n },\n _assignedTags(): AssignedTag[] {\n return this.assignedContent.filter((c) => c.type === 'tag') as AssignedTag[];\n },\n },\n\n watch: {\n async selectedCourse() {\n if (this.selectedCourse) {\n // Get course DB from data layer\n const courseDB = getDataLayer().getCourseDB(this.selectedCourse);\n // Get tags from course DB\n const tagResponse = await courseDB.getCourseTagStubs();\n this.availableTags = tagResponse.rows.map((row) => row.doc!);\n } else {\n this.availableTags = [];\n }\n },\n },\n\n async created() {\n try {\n // Get classroom DB from data layer\n this.classroomDB = (await getDataLayer().getClassroomDB(\n this.classroomId,\n 'teacher'\n )) as TeacherClassroomDBInterface;\n this.assignedContent = await this.classroomDB.getAssignedContent();\n this.classroomCfg = this.classroomDB.getConfig();\n\n console.log(`[ClassroomCtrlPanel] Route loaded w/ (prop) _id: ${this.classroomId}`);\n console.log(`[ClassroomCtrlPanel] Config: ${JSON.stringify(this.classroomCfg)}`);\n\n // Get course list from data layer\n this.availableCourses = await getDataLayer().getCoursesDB().getCourseList();\n\n this.updatePending = false;\n } catch (error) {\n console.error('[ClassroomCtrlPanel] Error initializing:', error);\n }\n },\n\n methods: {\n async assignContent() {\n if (!this.classroomDB) return;\n const u = await getCurrentUser();\n\n if (this.selectedTags.length === 0) {\n await this.classroomDB.assignContent?.({\n assignedOn: moment(),\n activeOn: moment(),\n type: 'course',\n courseID: this.selectedCourse,\n assignedBy: u.getUsername(),\n });\n } else {\n await Promise.all(\n this.selectedTags.map((tag) =>\n this.classroomDB!.assignContent?.({\n assignedOn: moment(),\n activeOn: moment(),\n type: 'tag',\n courseID: this.selectedCourse,\n tagID: tag,\n assignedBy: u.getUsername(),\n })\n )\n );\n }\n\n this.assignedContent = await this.classroomDB.getAssignedContent();\n this.addingContent = false;\n this.selectedCourse = '';\n this.selectedTags = [];\n this.availableTags = [];\n },\n\n async removeContent(c: AssignedContent) {\n if (this.classroomDB && this.classroomDB.removeContent) {\n await this.classroomDB.removeContent(c);\n this.assignedContent = await this.classroomDB.getAssignedContent();\n }\n },\n\n async submit() {\n this.updatePending = true;\n },\n },\n});\n</script>\n","<template>\n <div v-if=\"!updatePending\">\n <h1><router-link to=\"/classrooms\">My Classrooms</router-link> / {{ classroomCfg!.name }}</h1>\n\n <h3>\n Join code: {{ classroomCfg!.joinCode }}\n <router-link :to=\"`/classrooms/${classroomId}/code`\">\n <v-btn size=\"x-small\" icon=\"mdi-fullscreen\" color=\"accent\" alt=\"Make Fullscreen\"> </v-btn>\n </router-link>\n </h3>\n <v-row>\n <v-col cols=\"12\" sm=\"6\" md=\"4\">\n <v-checkbox v-model=\"classroomCfg!.peerAssist\" label=\"Allow peer instruction\"></v-checkbox>\n </v-col>\n <v-col v-if=\"classroomDB\" cols=\"12\">\n <h2>Assigned Content:</h2>\n <h3>Quilts:</h3>\n <ul></ul>\n <ul>\n <li v-for=\"c in _assignedCourses\" :key=\"c.courseID\">\n {{ c.courseID }} <a @click=\"removeContent(c)\">Remove</a>\n </li>\n </ul>\n <h3>Tags:</h3>\n <ul>\n <li v-for=\"(c, i) in _assignedTags\" :key=\"i\">\n {{ c.courseID }} - {{ c.tagID }} <a @click=\"removeContent(c)\">Remove</a>\n </li>\n </ul>\n <v-fade-transition>\n <v-btn v-if=\"!addingContent\" color=\"primary\" @click=\"addingContent = true\">\n Assign New Content\n <v-icon end>mdi-plus</v-icon>\n </v-btn>\n </v-fade-transition>\n <v-card v-if=\"addingContent\">\n <v-toolbar>\n <v-toolbar-title>Add Content</v-toolbar-title>\n <v-spacer></v-spacer>\n <v-btn icon=\"mdi-close\" color=\"error\" @click=\"addingContent = false\"> </v-btn>\n </v-toolbar>\n <v-card-text>\n <v-select\n v-model=\"selectedCourse\"\n label=\"Select Quilt\"\n :items=\"availableCourses\"\n item-title=\"name\"\n item-value=\"_id\"\n title=\"Select Quilt\"\n ></v-select>\n\n <v-select\n v-model=\"selectedTags\"\n label=\"Select Tags\"\n :items=\"availableTags\"\n item-title=\"name\"\n item-value=\"name\"\n multiple\n chips\n hint=\"\"\n persistent-hint\n ></v-select>\n </v-card-text>\n\n <v-card-actions>\n <v-btn v-if=\"selectedCourse !== ''\" color=\"primary\" @click=\"assignContent\">\n {{ selectedTags.length == 0 ? 'Add Entire Quilt' : 'Add Tags' }}\n <v-icon end>mdi-plus</v-icon>\n </v-btn>\n </v-card-actions>\n </v-card>\n </v-col>\n </v-row>\n </div>\n</template>\n\n<script lang=\"ts\">\nimport moment from 'moment';\nimport { TeacherClassroomDBInterface, AssignedContent, AssignedTag, Tag, getDataLayer } from '@vue-skuilder/db';\nimport { ClassroomConfig, CourseConfig } from '@vue-skuilder/common';\nimport { defineComponent } from 'vue';\nimport { getCurrentUser } from '@vue-skuilder/common-ui';\n\nexport default defineComponent({\n name: 'ClassroomCtrlPanel',\n\n props: {\n classroomId: {\n type: String,\n required: true,\n },\n },\n\n data() {\n return {\n classroomCfg: null as ClassroomConfig | null,\n classroomDB: null as TeacherClassroomDBInterface | null,\n assignedContent: [] as AssignedContent[],\n nameRules: [\n (value: string): string | boolean => {\n const max = 30;\n return value.length > max ? `Course name must be ${max} characters or less` : true;\n },\n ],\n updatePending: true,\n addingContent: false,\n availableCourses: [] as CourseConfig[],\n selectedCourse: '',\n availableTags: [] as Tag[],\n selectedTags: [] as string[],\n };\n },\n\n computed: {\n _assignedCourses(): AssignedContent[] {\n return this.assignedContent.filter((c) => c.type === 'course');\n },\n _assignedTags(): AssignedTag[] {\n return this.assignedContent.filter((c) => c.type === 'tag') as AssignedTag[];\n },\n },\n\n watch: {\n async selectedCourse() {\n if (this.selectedCourse) {\n // Get course DB from data layer\n const courseDB = getDataLayer().getCourseDB(this.selectedCourse);\n // Get tags from course DB\n const tagResponse = await courseDB.getCourseTagStubs();\n this.availableTags = tagResponse.rows.map((row) => row.doc!);\n } else {\n this.availableTags = [];\n }\n },\n },\n\n async created() {\n try {\n // Get classroom DB from data layer\n this.classroomDB = (await getDataLayer().getClassroomDB(\n this.classroomId,\n 'teacher'\n )) as TeacherClassroomDBInterface;\n this.assignedContent = await this.classroomDB.getAssignedContent();\n this.classroomCfg = this.classroomDB.getConfig();\n\n console.log(`[ClassroomCtrlPanel] Route loaded w/ (prop) _id: ${this.classroomId}`);\n console.log(`[ClassroomCtrlPanel] Config: ${JSON.stringify(this.classroomCfg)}`);\n\n // Get course list from data layer\n this.availableCourses = await getDataLayer().getCoursesDB().getCourseList();\n\n this.updatePending = false;\n } catch (error) {\n console.error('[ClassroomCtrlPanel] Error initializing:', error);\n }\n },\n\n methods: {\n async assignContent() {\n if (!this.classroomDB) return;\n const u = await getCurrentUser();\n\n if (this.selectedTags.length === 0) {\n await this.classroomDB.assignContent?.({\n assignedOn: moment(),\n activeOn: moment(),\n type: 'course',\n courseID: this.selectedCourse,\n assignedBy: u.getUsername(),\n });\n } else {\n await Promise.all(\n this.selectedTags.map((tag) =>\n this.classroomDB!.assignContent?.({\n assignedOn: moment(),\n activeOn: moment(),\n type: 'tag',\n courseID: this.selectedCourse,\n tagID: tag,\n assignedBy: u.getUsername(),\n })\n )\n );\n }\n\n this.assignedContent = await this.classroomDB.getAssignedContent();\n this.addingContent = false;\n this.selectedCourse = '';\n this.selectedTags = [];\n this.availableTags = [];\n },\n\n async removeContent(c: AssignedContent) {\n if (this.classroomDB && this.classroomDB.removeContent) {\n await this.classroomDB.removeContent(c);\n this.assignedContent = await this.classroomDB.getAssignedContent();\n }\n },\n\n async submit() {\n this.updatePending = true;\n },\n },\n});\n</script>\n"],"mappings":"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"}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{i as e,s as t}from"./chunk-D6hFPZc3.js";import{$ as n,C as r,H as i,Ht as a,K as o,M as s,S as c,T as l,W as u,b as d,f,n as p,v as m,y as h}from"./vue.runtime.esm-bundler--Fxmd6l9.js";import{T as g}from"./MarkdownRenderer-borh39Lq.js";import{t as _}from"./moment-DbT30ZpG.js";import{g as v,z as y}from"./dist-
|
|
1
|
+
import{i as e,s as t}from"./chunk-D6hFPZc3.js";import{$ as n,C as r,H as i,Ht as a,K as o,M as s,S as c,T as l,W as u,b as d,f,n as p,v as m,y as h}from"./vue.runtime.esm-bundler--Fxmd6l9.js";import{T as g}from"./MarkdownRenderer-borh39Lq.js";import{t as _}from"./moment-DbT30ZpG.js";import{g as v,z as y}from"./dist-C8EQUxWW.js";import{G as b,K as x}from"./contentSource-FGhM_ge8.js";import{t as S}from"./logshim-YQI0WloD.js";import"./dist-DGbpjpKB.js";import"./PipelineDebugger-DhnKlMgD.js";import{d as C}from"./userDBKeys-3SQjNVdK.js";import"./eloDistance-CRxxdYPJ.js";import{t as w}from"./index-BRaeVAZb.js";import{t as T}from"./server-CUqWbXPL.js";var E=t(g()),D=l({emits:[`ClassroomEditingComplete`],data(){return{mousetrap:new E.default(this.$el),peerAssist:!0,name:``,birthYear:void 0,id:``,nameRules:[e=>e.length>30?`Course name must be 30 characters or less`:!0],description:``,banner:void 0,thumb:void 0,updatePending:!1,birthYears:[]}},created(){this.mousetrap.bind(`esc`,this.clearFormAndDismiss);let e=_().year();this.birthYears.push({text:`< ${e-17} (Adult Students)`,value:0});for(let t=17;t>=6;t--)this.birthYears.push({text:`${e-t} (Grade ${t-5})`,value:e-t});this.birthYears.push({text:`>${e-6} (K or younger)`,value:e-5})},methods:{async submit(){this.updatePending=!0;let e=await v(),t={name:this.name,teachers:[e.getUsername()],students:[],birthYear:this.birthYear,classMeetingSchedule:``,peerAssist:this.peerAssist,joinCode:``};S(`Class Config:
|
|
2
2
|
${JSON.stringify(t)}`);let n=await T({data:t,type:b.CREATE_CLASSROOM,response:null,user:e.getUsername()});n.response&&n.response.ok?(y({text:`Class created successfully. Join code: ${n.response.joincode}`,status:x.ok}),e.registerForClassroom(n.response.uuid,`teacher`)):y({text:`Failed to create class. Please try again.`,status:x.error}),this.clearFormAndDismiss(),this.updatePending=!1},clearFormAndDismiss(){this.name=``,this.description=``,this.$emit(`ClassroomEditingComplete`)}}});function _sfc_render$1(e,t,a,s,l,u){let d=o(`v-card-title`),f=o(`v-spacer`),p=o(`v-icon`),h=o(`v-btn`),g=o(`v-toolbar`),_=o(`v-text-field`),v=o(`v-col`),y=o(`v-checkbox`),b=o(`v-select`),x=o(`v-row`),S=o(`v-container`),C=o(`v-form`),w=o(`v-card`);return i(),m(w,null,{default:n(()=>[r(g,{color:`primary`},{default:n(()=>[r(d,{class:`text-h6 font-weight-regular`},{default:n(()=>t[3]||(t[3]=[c(`Start a New Class`)])),_:1}),r(f),r(h,{icon:``,onClick:e.clearFormAndDismiss},{default:n(()=>[r(p,null,{default:n(()=>t[4]||(t[4]=[c(`mdi-close`)])),_:1})]),_:1},8,[`onClick`])]),_:1}),r(C,null,{default:n(()=>[r(S,null,{default:n(()=>[r(x,null,{default:n(()=>[r(v,{cols:`12`,sm:`6`,md:`4`},{default:n(()=>[r(_,{modelValue:e.name,"onUpdate:modelValue":t[0]||(t[0]=t=>e.name=t),counter:`30`,rules:e.nameRules,label:`Class Name`,required:``,hint:`Eg: Smith, Chemistry, Period 3`},null,8,[`modelValue`,`rules`])]),_:1}),r(v,{cols:`12`,sm:`6`,md:`4`},{default:n(()=>[r(y,{modelValue:e.peerAssist,"onUpdate:modelValue":t[1]||(t[1]=t=>e.peerAssist=t),label:`Allow peer instruction`},null,8,[`modelValue`])]),_:1}),r(v,{cols:`12`,sm:`6`,md:`4`},{default:n(()=>[r(b,{modelValue:e.birthYear,"onUpdate:modelValue":t[2]||(t[2]=t=>e.birthYear=t),items:e.birthYears,label:`Approximate Birth Year of Students`,"item-title":`text`,"item-value":`value`},null,8,[`modelValue`,`items`])]),_:1}),r(v,{cols:`12`,sm:`6`,md:`4`},{default:n(()=>[r(h,{loading:e.updatePending,color:`primary`,onClick:e.submit},{default:n(()=>t[5]||(t[5]=[c(`Create This Class`)])),_:1},8,[`loading`,`onClick`])]),_:1})]),_:1})]),_:1})]),_:1})]),_:1})}e(_sfc_render$1,`_sfc_render`);var O=l({name:`ClassroomsView`,components:{ClassroomEditor:w(D,[[`render`,_sfc_render$1]])},beforeRouteEnter(e,t,n){n()},data(){return{classes:[],joinCode:``,studentClasses:[],teacherClasses:[],spinnerMap:{},newClassDialog:!1,user:null}},created(){this.refreshData()},methods:{async refreshData(){this.$data.user=await v();let e=(await this.user.getUserClassrooms()).registrations,t=[],n=[];e.forEach(async e=>{let r=(await C().getClassroomDB(e.classID,e.registeredAs==`teacher`||e.registeredAs==`student`?e.registeredAs:`student`)).getConfig();console.log(`Registered class: ${JSON.stringify(r)}`);let i={_id:e.classID,name:r.name};e.registeredAs===`student`?t.push(i):e.registeredAs===`teacher`&&n.push(i)}),this.studentClasses=t,this.teacherClasses=n},async deleteClass(e){let t=await T({type:b.DELETE_CLASSROOM,user:this.user?.getUsername()||``,classID:e,response:null});t.response&&t.response.ok?y({text:`Class deleted successfully.`,status:x.ok}):y({text:`Failed to delete class. Please try again.`,status:x.error})},async leaveClass(e){this.spinnerMap[e]=!0,console.log(`Attempting to drop class: ${e}`);let t=await T({type:b.LEAVE_CLASSROOM,data:{classID:e},user:this.user?.getUsername()||``,response:null});t.response&&t.response.ok&&this.user&&await this.user.dropFromClassroom(e),delete this.spinnerMap[e],this.refreshData()},async joinClass(){let e=await T({type:b.JOIN_CLASSROOM,data:{joinCode:this.joinCode,registerAs:`student`,user:this.user?.getUsername()||``},user:this.user?.getUsername()||``,response:null});e.response&&e.response.ok?(console.log(`Adding registration to userDB...`),this.user?(await registerUserForClassroom(this.user.getUsername(),e.response.id_course,`student`),y({text:`Successfully joined class: ${e.response.course_name}.`,status:x.ok})):y({text:`Failed to join class. [Unknown current user.]`,status:x.error})):e.response&&y({text:e.response.errorText,status:x.error}),this.refreshData()},async processResponse(){this.newClassDialog=!this.newClassDialog}}});function _sfc_render(e,t,l,g,_,v){let y=o(`v-toolbar-title`),b=o(`v-toolbar`),x=o(`v-list-item-title`),S=o(`v-btn`),C=o(`v-list-item`),w=o(`v-list`),T=o(`v-card`),E=o(`v-col`),D=o(`v-row`),O=o(`v-divider`),k=o(`v-text-field`),A=o(`v-card-text`),j=o(`classroom-editor`),M=o(`v-dialog`),N=o(`v-container`);return i(),m(N,{fluid:``},{default:n(()=>[r(D,{class:`pa-4`,justify:`space-around`},{default:n(()=>[e.studentClasses.length>0?(i(),m(E,{key:0,cols:`12`,sm:`12`,md:`4`},{default:n(()=>[r(T,null,{default:n(()=>[r(b,{color:`primary`},{default:n(()=>[r(y,null,{default:n(()=>t[2]||(t[2]=[c(`My Classes`)])),_:1})]),_:1}),r(w,null,{default:n(()=>[r(p,{name:`component-fade`,mode:`out-in`},{default:n(()=>[(i(!0),d(f,null,u(e.studentClasses,o=>(i(),m(C,{key:o._id,value:o},{append:n(()=>[r(S,{size:`small`,color:`secondary`,loading:e.spinnerMap[o._id]!==void 0,onClick:t=>e.leaveClass(o._id)},{default:n(()=>t[3]||(t[3]=[c(` Leave this class `)])),_:2},1032,[`loading`,`onClick`])]),default:n(()=>[r(x,null,{default:n(()=>[c(a(o.name),1)]),_:2},1024)]),_:2},1032,[`value`]))),128))]),_:1})]),_:1})]),_:1})]),_:1})):h(``,!0),e.teacherClasses.length>0?(i(),m(E,{key:1,cols:`12`,sm:`12`,md:`4`},{default:n(()=>[r(T,null,{default:n(()=>[r(b,{color:`primary`},{default:n(()=>[r(y,null,{default:n(()=>t[4]||(t[4]=[c(`Classes I Manage`)])),_:1})]),_:1}),r(w,null,{default:n(()=>[r(p,{name:`component-fade`,mode:`out-in`},{default:n(()=>[(i(!0),d(f,null,u(e.teacherClasses,o=>(i(),m(C,{key:o._id,value:o},{append:n(()=>[r(S,{size:`small`,color:`secondary`,loading:e.spinnerMap[o._id]!==void 0,to:`/classrooms/${o._id}`},{default:n(()=>t[5]||(t[5]=[c(` Open `)])),_:2},1032,[`loading`,`to`])]),default:n(()=>[r(x,null,{default:n(()=>[c(a(o.name),1)]),_:2},1024)]),_:2},1032,[`value`]))),128))]),_:1})]),_:1})]),_:1})]),_:1})):h(``,!0),e.studentClasses.length===0&&e.teacherClasses.length===0?(i(),m(E,{key:2,class:`text-h5 text-center`},{default:n(()=>t[6]||(t[6]=[c(` You are not in any classes! Join your class below if someone has given you a joincode. Or else, start your own! `)])),_:1})):h(``,!0)]),_:1}),r(O,{class:`my-4`}),r(D,{class:`pa-4`},{default:n(()=>[r(E,{cols:`12`,sm:`12`,md:`8`,lg:`6`,xl:`6`},{default:n(()=>[r(T,null,{default:n(()=>[r(b,{color:`primary`},{default:n(()=>[r(y,null,{default:n(()=>t[7]||(t[7]=[c(`Join a class`)])),_:1})]),_:1}),r(A,null,{default:n(()=>[r(k,{modelValue:e.joinCode,"onUpdate:modelValue":t[0]||(t[0]=t=>e.joinCode=t),label:`Class Code`,variant:`outlined`},null,8,[`modelValue`]),r(S,{color:`primary`,onClick:e.joinClass},{default:n(()=>t[8]||(t[8]=[c(`Join`)])),_:1},8,[`onClick`])]),_:1})]),_:1})]),_:1})]),_:1}),r(M,{modelValue:e.newClassDialog,"onUpdate:modelValue":t[1]||(t[1]=t=>e.newClassDialog=t),fullscreen:``,transition:`dialog-bottom-transition`,scrim:!1},{activator:n(({props:e})=>[r(S,s({color:`primary`},e),{default:n(()=>t[9]||(t[9]=[c(`Start a new Class`)])),_:2},1040)]),default:n(()=>[r(j,{onClassroomEditingComplete:e.processResponse},null,8,[`onClassroomEditingComplete`])]),_:1},8,[`modelValue`])]),_:1})}var k=w(O,[[`render`,_sfc_render]]);export{k as default};
|
|
3
|
-
//# sourceMappingURL=Classrooms-
|
|
3
|
+
//# sourceMappingURL=Classrooms-B5tQPFEE.js.map
|