@vue-skuilder/platform-ui 0.2.35 → 0.2.36

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (189) hide show
  1. package/dist/assets/{About-BWM7KlDK.js → About-74htcNLT.js} +2 -2
  2. package/dist/assets/{About-BWM7KlDK.js.map → About-74htcNLT.js.map} +1 -1
  3. package/dist/assets/AdminDashboard-B00qno2M.js +2 -0
  4. package/dist/assets/{AdminDashboard-CO2rxkF5.js.map → AdminDashboard-B00qno2M.js.map} +1 -1
  5. package/dist/assets/AdminLearner-SJNtSyJ_.js +2 -0
  6. package/dist/assets/{AdminLearner-CZO2Hlk1.js.map → AdminLearner-SJNtSyJ_.js.map} +1 -1
  7. package/dist/assets/BaseUserDB-CV2ZPZOR.js +1 -0
  8. package/dist/assets/ClassroomCtrlPanel-BSAG3h8B.js +2 -0
  9. package/dist/assets/{ClassroomCtrlPanel-cj6bokz5.js.map → ClassroomCtrlPanel-BSAG3h8B.js.map} +1 -1
  10. package/dist/assets/{Classrooms-BIC1PGz0.js → Classrooms-DXNEbtsM.js} +3 -3
  11. package/dist/assets/{Classrooms-BIC1PGz0.js.map → Classrooms-DXNEbtsM.js.map} +1 -1
  12. package/dist/assets/CouchDBSyncStrategy-BjM7ROXC.js +1 -0
  13. package/dist/assets/CourseRouter-CsVa_92r.js +7 -0
  14. package/dist/assets/{CourseRouter-CMMxD9us.js.map → CourseRouter-CsVa_92r.js.map} +1 -1
  15. package/dist/assets/CourseSyncService-CPU_Ll1h.js +1 -0
  16. package/dist/assets/CourseWare-BTFRjgBR-DBp_fIwH.js +3 -0
  17. package/dist/assets/{CourseWare-BTFRjgBR-C3wi5YHa.js.map → CourseWare-BTFRjgBR-DBp_fIwH.js.map} +1 -1
  18. package/dist/assets/{Courses-Dw7mgMnI.js → Courses-CB6GGtdm.js} +3 -3
  19. package/dist/assets/{Courses-Dw7mgMnI.js.map → Courses-CB6GGtdm.js.map} +1 -1
  20. package/dist/assets/EloModeration-DUBgs-Vh.js +2 -0
  21. package/dist/assets/{EloModeration-COgCrvb8.js.map → EloModeration-DUBgs-Vh.js.map} +1 -1
  22. package/dist/assets/{JoinCode-Fu9Tks7r.js → JoinCode-BBvYapCA.js} +2 -2
  23. package/dist/assets/{JoinCode-Fu9Tks7r.js.map → JoinCode-BBvYapCA.js.map} +1 -1
  24. package/dist/assets/MarkdownRenderer-WG9YrHpe.js +1 -0
  25. package/dist/assets/MarkdownRenderer-borh39Lq.js +53 -0
  26. package/dist/assets/MarkdownRenderer-borh39Lq.js.map +1 -0
  27. package/dist/assets/NewCourseDialog-DBgFVnt3.js +2 -0
  28. package/dist/assets/{NewCourseDialog-C11dSwWM.js.map → NewCourseDialog-DBgFVnt3.js.map} +1 -1
  29. package/dist/assets/PipelineDebugger-BHaqzqZw.js +177 -0
  30. package/dist/assets/PipelineDebugger-BHaqzqZw.js.map +1 -0
  31. package/dist/assets/PouchDataLayerProvider-DxsnSIKd.js +2 -0
  32. package/dist/assets/PouchDataLayerProvider-DxsnSIKd.js.map +1 -0
  33. package/dist/assets/{ReleaseNotes-CeTkuF5d.js → ReleaseNotes-Dg6mLL1C.js} +2 -2
  34. package/dist/assets/{ReleaseNotes-CeTkuF5d.js.map → ReleaseNotes-Dg6mLL1C.js.map} +1 -1
  35. package/dist/assets/RequestPasswordReset-CcFMTnTc.js +2 -0
  36. package/dist/assets/{RequestPasswordReset-vTzfGItp.js.map → RequestPasswordReset-CcFMTnTc.js.map} +1 -1
  37. package/dist/assets/ResetPassword-Dw_cBJU2.js +2 -0
  38. package/dist/assets/{ResetPassword-CmGaVYsI.js.map → ResetPassword-Dw_cBJU2.js.map} +1 -1
  39. package/dist/assets/StaticDataLayerProvider-CtgU3VBh.js +2 -0
  40. package/dist/assets/StaticDataLayerProvider-CtgU3VBh.js.map +1 -0
  41. package/dist/assets/Study-DDsGQCTJ.js +2 -0
  42. package/dist/assets/{Study-CkJvraIv.js.map → Study-DDsGQCTJ.js.map} +1 -1
  43. package/dist/assets/TagInformation-DBLoXA2a.js +2 -0
  44. package/dist/assets/{TagInformation-xZcQ1C1t.js.map → TagInformation-DBLoXA2a.js.map} +1 -1
  45. package/dist/assets/User-DfXUXjFb.js +2 -0
  46. package/dist/assets/User-DfXUXjFb.js.map +1 -0
  47. package/dist/assets/UserDBDebugger-DPqlc4O_.js +23 -0
  48. package/dist/assets/UserDBDebugger-DPqlc4O_.js.map +1 -0
  49. package/dist/assets/UserDBDebugger-xmycWjck.js +1 -0
  50. package/dist/assets/UserStats-C04YVYv7.js +2 -0
  51. package/dist/assets/{UserStats-CioNBbmL.js.map → UserStats-C04YVYv7.js.map} +1 -1
  52. package/dist/assets/VerifyEmail-Y05BSR57.js +2 -0
  53. package/dist/assets/{VerifyEmail-Bh-T9191.js.map → VerifyEmail-Y05BSR57.js.map} +1 -1
  54. package/dist/assets/admin-DcFjvYJw.js +21 -0
  55. package/dist/assets/admin-DcFjvYJw.js.map +1 -0
  56. package/dist/assets/chess-E2uOAyQS-BZi0vihz.js +7 -0
  57. package/dist/assets/{chess-E2uOAyQS-C8213VIQ.js.map → chess-E2uOAyQS-BZi0vihz.js.map} +1 -1
  58. package/dist/assets/chess-Lfm7Fsvs-DgN08Qx9.js +1 -0
  59. package/dist/assets/contentSource-DwznMSMs.js +34 -0
  60. package/dist/assets/contentSource-DwznMSMs.js.map +1 -0
  61. package/dist/assets/defineProperty-y9rbOPYz.js +1 -0
  62. package/dist/assets/dist-BcwaOf54.js +1 -0
  63. package/dist/assets/dist-BsRvt0gH.js +38 -0
  64. package/dist/assets/dist-BsRvt0gH.js.map +1 -0
  65. package/dist/assets/dist-CAHkbl3M.js +18 -0
  66. package/dist/assets/dist-CAHkbl3M.js.map +1 -0
  67. package/dist/assets/dist-CgW7D_Gh.js +1 -0
  68. package/dist/assets/dist-Dnehy0sn.js +1 -0
  69. package/dist/assets/dist-Dp6eBE2S.js +64 -0
  70. package/dist/assets/dist-Dp6eBE2S.js.map +1 -0
  71. package/dist/assets/edit-ui.es-BWqvElsJ.js +1 -0
  72. package/dist/assets/edit-ui.es-UaQFL_My.js +41 -0
  73. package/dist/assets/edit-ui.es-UaQFL_My.js.map +1 -0
  74. package/dist/assets/elo-B5qCwvSC.js +2 -0
  75. package/dist/assets/elo-B5qCwvSC.js.map +1 -0
  76. package/dist/assets/elo-COgu6Hu5.js +2 -0
  77. package/dist/assets/elo-COgu6Hu5.js.map +1 -0
  78. package/dist/assets/elo-Cwa8a3Yo.js +1 -0
  79. package/dist/assets/eloDistance-BV7vfiK5.js +2 -0
  80. package/dist/assets/eloDistance-BV7vfiK5.js.map +1 -0
  81. package/dist/assets/eloDistance-MReQeAuj.js +1 -0
  82. package/dist/assets/french-Dk7YG8Td-CD7Hms2j.js +2 -0
  83. package/dist/assets/{french-Dk7YG8Td-D9eV2DXL.js.map → french-Dk7YG8Td-CD7Hms2j.js.map} +1 -1
  84. package/dist/assets/french-evUMlbbq-nGCjilG6.js +1 -0
  85. package/dist/assets/hierarchyDefinition-DLC5DjaA.js +2 -0
  86. package/dist/assets/hierarchyDefinition-DLC5DjaA.js.map +1 -0
  87. package/dist/assets/{index-NFrxkZjT.css → index-B7mmpKZd.css} +1 -1
  88. package/dist/assets/index-CK-rfSIx.js +4 -0
  89. package/dist/assets/{index-CDxzcoT1.js.map → index-CK-rfSIx.js.map} +1 -1
  90. package/dist/assets/interferenceMitigator-D78KgOxz.js +2 -0
  91. package/dist/assets/interferenceMitigator-D78KgOxz.js.map +1 -0
  92. package/dist/assets/logshim-YQI0WloD.js +2 -0
  93. package/dist/assets/logshim-YQI0WloD.js.map +1 -0
  94. package/dist/assets/math-DR3fXDcX-C9KfHLHP.js +1 -0
  95. package/dist/assets/math-D_8HiEQR-E4QDLrWp.js +34 -0
  96. package/dist/assets/{math-D_8HiEQR-DdtcYQLB.js.map → math-D_8HiEQR-E4QDLrWp.js.map} +1 -1
  97. package/dist/assets/moment-DbT30ZpG.js +5 -0
  98. package/dist/assets/moment-DbT30ZpG.js.map +1 -0
  99. package/dist/assets/piano-BN5Btq91-CnKhyLGE.js +1 -0
  100. package/dist/assets/piano-DF1g6yaX-Ck3BH5EK.js +2 -0
  101. package/dist/assets/{piano-DF1g6yaX-B7f_kt1X.js.map → piano-DF1g6yaX-Ck3BH5EK.js.map} +1 -1
  102. package/dist/assets/pitch-CgGJFkZ1-Dd0LzmEe.js +2 -0
  103. package/dist/assets/{pitch-CgGJFkZ1-CuezrVTg.js.map → pitch-CgGJFkZ1-Dd0LzmEe.js.map} +1 -1
  104. package/dist/assets/pitch-Dn0iNqiS-BxAdpDDb.js +1 -0
  105. package/dist/assets/prescribed-BWO17Gan.js +2 -0
  106. package/dist/assets/prescribed-BWO17Gan.js.map +1 -0
  107. package/dist/assets/relativePriority-DwPMECAp.js +2 -0
  108. package/dist/assets/relativePriority-DwPMECAp.js.map +1 -0
  109. package/dist/assets/server-CqR7xtQy.js +2 -0
  110. package/dist/assets/server-CqR7xtQy.js.map +1 -0
  111. package/dist/assets/{sightsing-BFQ7HRig-CLh3Xcy7.js → sightsing-BFQ7HRig-DV69ELZf.js} +23 -23
  112. package/dist/assets/{sightsing-BFQ7HRig-CLh3Xcy7.js.map → sightsing-BFQ7HRig-DV69ELZf.js.map} +1 -1
  113. package/dist/assets/sightsing-CJX3k0Fd-CG94OiDr.js +1 -0
  114. package/dist/assets/srs-DGscV6W4.js +1 -0
  115. package/dist/assets/srs-DOsG9Y-i.js +2 -0
  116. package/dist/assets/srs-DOsG9Y-i.js.map +1 -0
  117. package/dist/assets/tagRoles-jR-PJAgj.js +2 -0
  118. package/dist/assets/tagRoles-jR-PJAgj.js.map +1 -0
  119. package/dist/assets/typing-BevtfWlp-CvStu5BY.js +2 -0
  120. package/dist/assets/{typing-BevtfWlp-B__x_Ia1.js.map → typing-BevtfWlp-CvStu5BY.js.map} +1 -1
  121. package/dist/assets/typing-k-ojjO7G-AF8vU_q7.js +1 -0
  122. package/dist/assets/userDBKeys-DYLGKnVC.js +4 -0
  123. package/dist/assets/userDBKeys-DYLGKnVC.js.map +1 -0
  124. package/dist/assets/userTagPreference-cZCPqadC.js +2 -0
  125. package/dist/assets/userTagPreference-cZCPqadC.js.map +1 -0
  126. package/dist/assets/validators-lpYAHmRR.js +2 -0
  127. package/dist/assets/validators-lpYAHmRR.js.map +1 -0
  128. package/dist/assets/word-work-BOnRlZgd-CCGZdwyf.js +2 -0
  129. package/dist/assets/{word-work-BOnRlZgd-CxcTzMPz.js.map → word-work-BOnRlZgd-CCGZdwyf.js.map} +1 -1
  130. package/dist/assets/word-work-Dd6tIKrt-BNTSCq5F.js +1 -0
  131. package/dist/assets/{workbox-window.prod.es5-r2bRTooK.js → workbox-window.prod.es5-YsLcd14J.js} +1 -1
  132. package/dist/assets/{workbox-window.prod.es5-r2bRTooK.js.map → workbox-window.prod.es5-YsLcd14J.js.map} +1 -1
  133. package/dist/index.html +17 -7
  134. package/dist/sw.js +1 -1
  135. package/dist/sw.js.map +1 -1
  136. package/package.json +7 -7
  137. package/dist/assets/AdminDashboard-CO2rxkF5.js +0 -2
  138. package/dist/assets/AdminLearner-CZO2Hlk1.js +0 -2
  139. package/dist/assets/ClassroomCtrlPanel-cj6bokz5.js +0 -2
  140. package/dist/assets/CourseRouter-CMMxD9us.js +0 -7
  141. package/dist/assets/CourseWare-BTFRjgBR-C3wi5YHa.js +0 -3
  142. package/dist/assets/EloModeration-COgCrvb8.js +0 -2
  143. package/dist/assets/MarkdownRenderer-U_iPiA2x-DDruDu7E.js +0 -53
  144. package/dist/assets/MarkdownRenderer-U_iPiA2x-DDruDu7E.js.map +0 -1
  145. package/dist/assets/MarkdownRenderer-U_iPiA2x-VCa5mXh1.js +0 -1
  146. package/dist/assets/NewCourseDialog-C11dSwWM.js +0 -2
  147. package/dist/assets/RequestPasswordReset-vTzfGItp.js +0 -2
  148. package/dist/assets/ResetPassword-CmGaVYsI.js +0 -2
  149. package/dist/assets/Study-CkJvraIv.js +0 -2
  150. package/dist/assets/TagInformation-xZcQ1C1t.js +0 -2
  151. package/dist/assets/User-MzKFSneO.js +0 -3
  152. package/dist/assets/User-MzKFSneO.js.map +0 -1
  153. package/dist/assets/UserStats-CioNBbmL.js +0 -2
  154. package/dist/assets/VerifyEmail-Bh-T9191.js +0 -2
  155. package/dist/assets/admin-Dt07rW2C.js +0 -21
  156. package/dist/assets/admin-Dt07rW2C.js.map +0 -1
  157. package/dist/assets/chess-E2uOAyQS-C8213VIQ.js +0 -7
  158. package/dist/assets/chess-Lfm7Fsvs-6ElY3d5h.js +0 -1
  159. package/dist/assets/common-ui.es-B9r6QiWN.js +0 -38
  160. package/dist/assets/common-ui.es-B9r6QiWN.js.map +0 -1
  161. package/dist/assets/common-ui.es-j6sezLNk.js +0 -1
  162. package/dist/assets/dist-BeppzTWu.js +0 -380
  163. package/dist/assets/dist-BeppzTWu.js.map +0 -1
  164. package/dist/assets/dist-C1hpwy00.js +0 -1
  165. package/dist/assets/dist-CKO_fXB-.js +0 -18
  166. package/dist/assets/dist-CKO_fXB-.js.map +0 -1
  167. package/dist/assets/dist-CkRURFlm.js +0 -1
  168. package/dist/assets/dist-V9Xbab2f.js +0 -12
  169. package/dist/assets/dist-V9Xbab2f.js.map +0 -1
  170. package/dist/assets/edit-ui.es-B00e6OcT.js +0 -36
  171. package/dist/assets/edit-ui.es-B00e6OcT.js.map +0 -1
  172. package/dist/assets/edit-ui.es-BGTYLTZO.js +0 -1
  173. package/dist/assets/french-Dk7YG8Td-D9eV2DXL.js +0 -2
  174. package/dist/assets/french-evUMlbbq-DOmp0PEx.js +0 -1
  175. package/dist/assets/index-CDxzcoT1.js +0 -4
  176. package/dist/assets/math-DR3fXDcX-Clr1rrj8.js +0 -1
  177. package/dist/assets/math-D_8HiEQR-DdtcYQLB.js +0 -34
  178. package/dist/assets/piano-BN5Btq91-BO9b38qV.js +0 -1
  179. package/dist/assets/piano-DF1g6yaX-B7f_kt1X.js +0 -2
  180. package/dist/assets/pitch-CgGJFkZ1-CuezrVTg.js +0 -2
  181. package/dist/assets/pitch-Dn0iNqiS-L0LRhcjZ.js +0 -1
  182. package/dist/assets/server-BP-NvFg7.js +0 -2
  183. package/dist/assets/server-BP-NvFg7.js.map +0 -1
  184. package/dist/assets/sightsing-CJX3k0Fd-DiVzeNly.js +0 -1
  185. package/dist/assets/typing-BevtfWlp-B__x_Ia1.js +0 -2
  186. package/dist/assets/typing-k-ojjO7G-Dn4uH4pS.js +0 -1
  187. package/dist/assets/word-work-BOnRlZgd-CxcTzMPz.js +0 -2
  188. package/dist/assets/word-work-Dd6tIKrt-BOmhWlr6.js +0 -1
  189. /package/dist/assets/{common-ui-BY2Ut5jf.css → dist-BY2Ut5jf.css} +0 -0
@@ -1 +1 @@
1
- {"version":3,"file":"ResetPassword-CmGaVYsI.js","names":[],"sources":["../../src/views/ResetPassword.vue","../../src/views/ResetPassword.vue"],"sourcesContent":["<template>\n <reset-password @complete=\"handleComplete\" @error=\"handleError\">\n <template #success-action>\n <v-btn color=\"primary\" @click=\"goToLogin\">\n Continue to Login\n </v-btn>\n </template>\n </reset-password>\n</template>\n\n<script lang=\"ts\">\nimport { defineComponent } from 'vue';\nimport { ResetPassword } from '@vue-skuilder/common-ui';\n\nexport default defineComponent({\n name: 'ResetPasswordView',\n\n components: {\n ResetPassword,\n },\n\n methods: {\n handleComplete() {\n console.log('Password reset successfully');\n },\n\n handleError(error: string) {\n console.error('Password reset error:', error);\n },\n\n goToLogin() {\n this.$router.push({ name: 'login' });\n },\n },\n});\n</script>\n","<template>\n <reset-password @complete=\"handleComplete\" @error=\"handleError\">\n <template #success-action>\n <v-btn color=\"primary\" @click=\"goToLogin\">\n Continue to Login\n </v-btn>\n </template>\n </reset-password>\n</template>\n\n<script lang=\"ts\">\nimport { defineComponent } from 'vue';\nimport { ResetPassword } from '@vue-skuilder/common-ui';\n\nexport default defineComponent({\n name: 'ResetPasswordView',\n\n components: {\n ResetPassword,\n },\n\n methods: {\n handleComplete() {\n console.log('Password reset successfully');\n },\n\n handleError(error: string) {\n console.error('Password reset error:', error);\n },\n\n goToLogin() {\n this.$router.push({ name: 'login' });\n },\n },\n});\n</script>\n"],"mappings":"6TCcA,IAAA,EAAe,EAAgB,CAC7B,KAAM,oBAEN,WAAY,CACV,cAAA,EACD,CAED,QAAS,CACP,gBAAiB,CACf,QAAQ,IAAI,8BAA8B,EAG5C,YAAY,EAAe,CACzB,QAAQ,MAAM,wBAAyB,EAAM,EAG/C,WAAY,CACV,KAAK,QAAQ,KAAK,CAAE,KAAM,QAAS,CAAC,EAEvC,CACF,CAAC,wFAjCA,EAMiB,EAAA,CANA,WAAU,EAAA,eAAiB,QAAO,EAAA,cACtC,iBAAc,MAGf,CAFR,EAEQ,EAAA,CAFD,MAAM,UAAW,QAAO,EAAA,YAHrC,QAAA,MAKM,EAAA,KAAA,EAAA,GAAA,CALN,EAGgD,sBAE1C,CAAA,EAAA,CALN,EAAA,oBAAA,EAAA"}
1
+ {"version":3,"file":"ResetPassword-Dw_cBJU2.js","names":[],"sources":["../../src/views/ResetPassword.vue","../../src/views/ResetPassword.vue"],"sourcesContent":["<template>\n <reset-password @complete=\"handleComplete\" @error=\"handleError\">\n <template #success-action>\n <v-btn color=\"primary\" @click=\"goToLogin\">\n Continue to Login\n </v-btn>\n </template>\n </reset-password>\n</template>\n\n<script lang=\"ts\">\nimport { defineComponent } from 'vue';\nimport { ResetPassword } from '@vue-skuilder/common-ui';\n\nexport default defineComponent({\n name: 'ResetPasswordView',\n\n components: {\n ResetPassword,\n },\n\n methods: {\n handleComplete() {\n console.log('Password reset successfully');\n },\n\n handleError(error: string) {\n console.error('Password reset error:', error);\n },\n\n goToLogin() {\n this.$router.push({ name: 'login' });\n },\n },\n});\n</script>\n","<template>\n <reset-password @complete=\"handleComplete\" @error=\"handleError\">\n <template #success-action>\n <v-btn color=\"primary\" @click=\"goToLogin\">\n Continue to Login\n </v-btn>\n </template>\n </reset-password>\n</template>\n\n<script lang=\"ts\">\nimport { defineComponent } from 'vue';\nimport { ResetPassword } from '@vue-skuilder/common-ui';\n\nexport default defineComponent({\n name: 'ResetPasswordView',\n\n components: {\n ResetPassword,\n },\n\n methods: {\n handleComplete() {\n console.log('Password reset successfully');\n },\n\n handleError(error: string) {\n console.error('Password reset error:', error);\n },\n\n goToLogin() {\n this.$router.push({ name: 'login' });\n },\n },\n});\n</script>\n"],"mappings":"sZCcA,IAAA,EAAe,EAAgB,CAC7B,KAAM,oBAEN,WAAY,CACV,cAAA,EACD,CAED,QAAS,CACP,gBAAiB,CACf,QAAQ,IAAI,8BAA8B,EAG5C,YAAY,EAAe,CACzB,QAAQ,MAAM,wBAAyB,EAAM,EAG/C,WAAY,CACV,KAAK,QAAQ,KAAK,CAAE,KAAM,QAAS,CAAC,EAEvC,CACF,CAAC,wFAjCA,EAMiB,EAAA,CANA,WAAU,EAAA,eAAiB,QAAO,EAAA,cACtC,iBAAc,MAGf,CAFR,EAEQ,EAAA,CAFD,MAAM,UAAW,QAAO,EAAA,YAHrC,QAAA,MAKM,EAAA,KAAA,EAAA,GAAA,CALN,EAGgD,sBAE1C,CAAA,EAAA,CALN,EAAA,oBAAA,EAAA"}
@@ -0,0 +1,2 @@
1
+ import{i as __name}from"./chunk-D6hFPZc3.js";import"./moment-DbT30ZpG.js";import{K as Status,d as BaseUser,f as accomodateGuest,g as PipelineAssembler,h as getLocalUserDB,k as createDefaultPipeline}from"./contentSource-DwznMSMs.js";import{t as _defineProperty}from"./defineProperty-y9rbOPYz.js";import{k as logger}from"./PipelineDebugger-BHaqzqZw.js";import{o as DocType,s as DocTypePrefixes}from"./userDBKeys-DYLGKnVC.js";import"./eloDistance-BV7vfiK5.js";var pathUtils={isAbsolute:e=>!!(/^[a-zA-Z]:[\\/]/.test(e)||/^\\\\/.test(e)||e.startsWith(`/`))},nodeFS=null;try{typeof window>`u`&&typeof process<`u`&&process.versions?.node&&(nodeFS=eval(`require`)(`fs`))}catch{}var StaticDataUnpacker=class{constructor(e,t){_defineProperty(this,`manifest`,void 0),_defineProperty(this,`basePath`,void 0),_defineProperty(this,`documentCache`,new Map),_defineProperty(this,`chunkCache`,new Map),_defineProperty(this,`indexCache`,new Map),this.manifest=e,this.basePath=t}async getDocument(e){if(this.documentCache.has(e)){let t=this.documentCache.get(e);return await this.hydrateAttachments(t)}let t=await this.findChunkForDocument(e);if(!t)throw logger.error(`Document ${e} not found in any chunk. Available chunks:`,this.manifest.chunks.map(e=>`${e.id} (${e.docType}): ${e.startKey} - ${e.endKey}`)),Error(`Document ${e} not found in any chunk`);if(await this.loadChunk(t.id),this.documentCache.has(e)){let t=this.documentCache.get(e);return await this.hydrateAttachments(t)}throw logger.error(`Document ${e} not found in chunk ${t.id}`),Error(`Document ${e} not found in chunk ${t.id}`)}async getAllDocumentsByPrefix(e){let t=this.manifest.chunks.filter(t=>{let n=e+`￰`;return t.startKey<=n&&t.endKey>=e});if(t.length===0)return logger.debug(`[StaticDataUnpacker] No chunks found for prefix: ${e}`),[];await Promise.all(t.map(e=>this.loadChunk(e.id)));let n=[];for(let[t,r]of this.documentCache.entries())t.startsWith(e)&&n.push(await this.hydrateAttachments(r));return logger.debug(`[StaticDataUnpacker] Found ${n.length} documents with prefix: ${e}`),n}async queryByElo(e,t=25){let n=await this.loadIndex(`elo`);if(!n||!n.sorted)return logger.warn(`ELO index not found or malformed, returning empty results`),[];let r=n.sorted,i=0,a=0,o=r.length-1;for(;a<=o;){let t=Math.floor((a+o)/2);r[t].elo<e?a=t+1:o=t-1}i=a;let s=[],c=Math.floor(t/2);for(let e=Math.max(0,i-c);e<i&&s.length<t;e++)s.push(r[e].cardId);for(let e=i;e<r.length&&s.length<t;e++)s.push(r[e].cardId);return s}async getTagsIndex(){try{return await this.loadIndex(`tags`)}catch{return{byCard:{},byTag:{}}}}getDocTypeFromId(e){for(let t in DocTypePrefixes){let n=DocTypePrefixes[t];if(e.startsWith(`${n}-`))return t}}async findChunkForDocument(e){let t=this.getDocTypeFromId(e);if(t){let n=this.manifest.chunks.filter(e=>e.docType===t);for(let t of n)if(e>=t.startKey&&e<=t.endKey&&await this.verifyDocumentInChunk(e,t))return t}else{for(let t of this.manifest.chunks)if(e>=t.startKey&&e<=t.endKey&&await this.verifyDocumentInChunk(e,t))return t;let t=this.manifest.chunks.filter(e=>e.docType!==`CARD`&&e.docType!==`DISPLAYABLE_DATA`&&e.docType!==`TAG`);for(let n of t)if(e>=n.startKey&&e<=n.endKey&&await this.verifyDocumentInChunk(e,n))return n;return}}async verifyDocumentInChunk(e,t){try{return await this.loadChunk(t.id),this.documentCache.has(e)}catch{return!1}}async loadChunk(e){if(this.chunkCache.has(e))return;let t=this.manifest.chunks.find(t=>t.id===e);if(!t)throw Error(`Chunk ${e} not found in manifest`);try{let n=`${this.basePath}/${t.path}`;logger.debug(`Loading chunk from ${n}`);let r;if(this.isLocalPath(n)&&nodeFS){let e=await nodeFS.promises.readFile(n,`utf8`);r=JSON.parse(e)}else{let t=await fetch(n);if(!t.ok)throw Error(`Failed to fetch chunk ${e}: ${t.status} ${t.statusText}`);r=await t.json()}this.chunkCache.set(e,r);for(let e of r)e._id&&this.documentCache.set(e._id,e);logger.debug(`Loaded ${r.length} documents from chunk ${e}`)}catch(t){throw logger.error(`Failed to load chunk ${e}:`,t),t}}async loadIndex(e){if(this.indexCache.has(e))return this.indexCache.get(e);let t=this.manifest.indices.find(t=>t.name===e);if(!t)throw Error(`Index ${e} not found in manifest`);try{let n=`${this.basePath}/${t.path}`;logger.debug(`Loading index from ${n}`);let r;if(this.isLocalPath(n)&&nodeFS){let e=await nodeFS.promises.readFile(n,`utf8`);r=JSON.parse(e)}else{let t=await fetch(n);if(!t.ok)throw Error(`Failed to fetch index ${e}: ${t.status} ${t.statusText}`);r=await t.json()}return this.indexCache.set(e,r),logger.debug(`Loaded index ${e}`),r}catch(t){throw logger.error(`Failed to load index ${e}:`,t),t}}async getRawDocument(e){if(this.documentCache.has(e))return this.documentCache.get(e);let t=await this.findChunkForDocument(e);if(!t)throw logger.error(`Document ${e} not found in any chunk. Available chunks:`,this.manifest.chunks.map(e=>`${e.id} (${e.docType}): ${e.startKey} - ${e.endKey}`)),Error(`Document ${e} not found in any chunk`);if(await this.loadChunk(t.id),this.documentCache.has(e))return this.documentCache.get(e);throw logger.error(`Document ${e} not found in chunk ${t.id}`),Error(`Document ${e} not found in chunk ${t.id}`)}getAttachmentUrl(e,t){return`${this.basePath}/attachments/${e}/${t}`}async getAttachmentPath(e,t){try{let n=await this.getRawDocument(e);if(n._attachments&&n._attachments[t]){let e=n._attachments[t];if(e.path)return`${this.basePath}/${e.path}`}return null}catch{return null}}async getAttachmentBlob(e,t){let n=await this.getAttachmentPath(e,t);if(!n)return null;try{if(this.isLocalPath(n)&&nodeFS)return await nodeFS.promises.readFile(n);{let r=await fetch(n);if(!r.ok)throw Error(`Failed to fetch attachment ${e}/${t}: ${r.status} ${r.statusText}`);return await r.blob()}}catch(n){return logger.error(`Failed to load attachment ${e}/${t}:`,n),null}}async hydrateAttachments(e){let t=e;if(!t._attachments)return e;let n=JSON.parse(JSON.stringify(e));for(let[e,r]of Object.entries(t._attachments)){let i=r;if(i.path)try{let r=await this.getAttachmentBlob(t._id,e);r?typeof window<`u`&&window.URL?n._attachments[e]={...i,data:r,stub:!1}:n._attachments[e]={...i,buffer:r}:logger.warn(`[hydrateAttachments] getAttachmentBlob returned null for ${t._id}/${e}. Skipping hydration for this attachment.`)}catch(n){logger.warn(`[hydrateAttachments] Failed to hydrate attachment ${t._id}/${e}:`,n)}else logger.debug(`[hydrateAttachments] Attachment ${e} for doc ${t._id} has no path. Skipping blob conversion.`)}return n}clearCaches(){this.documentCache.clear(),this.chunkCache.clear(),this.indexCache.clear()}getCacheStats(){return{documents:this.documentCache.size,chunks:this.chunkCache.size,indices:this.indexCache.size}}isLocalPath(e){return!e.startsWith(`http://`)&&!e.startsWith(`https://`)&&(pathUtils.isAbsolute(e)||e.startsWith(`./`)||e.startsWith(`../`))}},StaticCourseDB=class{constructor(e,t,n,r){_defineProperty(this,`_pendingHints`,null),this.courseId=e,this.unpacker=t,this.userDB=n,this.manifest=r}getCourseID(){return this.courseId}async getCourseConfig(){if(this.manifest.courseConfig!=null)return this.manifest.courseConfig;throw Error(`Course config not found for course ${this.courseId}`)}async updateCourseConfig(e){throw Error(`Cannot update course config in static mode`)}async getCourseInfo(){return{cardCount:this.manifest.chunks.filter(e=>e.docType===DocType.CARD).reduce((e,t)=>e+t.documentCount,0),registeredUsers:0}}async getCourseDoc(e,t){return this.unpacker.getDocument(e)}async getCourseDocs(e,t){let n=await Promise.all(e.map(async e=>{try{return{id:e,key:e,value:{rev:`1-static`},doc:await this.unpacker.getDocument(e)}}catch{return{key:e,error:`not_found`}}}));return{total_rows:e.length,offset:0,rows:n}}async getCardsByELO(e,t){return(await this.unpacker.queryByElo(e,t||25)).map(e=>{let[t,n,r]=e.split(`-`);return{courseID:t,cardID:n,elo:r?parseInt(r):void 0}})}async getCardEloData(e){return await Promise.all(e.map(async e=>{try{return(await this.unpacker.getDocument(e)).elo||{global:{score:1e3,count:0},tags:{},misc:{}}}catch{return{global:{score:1e3,count:0},tags:{},misc:{}}}}))}async updateCardElo(e,t){return{ok:!0,id:e,rev:`1-static`}}async getCardsCenteredAtELO(e,t){let n=typeof e.elo==`number`?e.elo:1e3;if(e.elo===`user`)try{let e=(await this.userDB.getCourseRegistrationsDoc()).courses.find(e=>e.courseID===this.courseId);e&&typeof e.elo==`object`&&(n=e.elo.global.score)}catch{n=1e3}else e.elo===`random`&&(n=800+Math.random()*400);let r=(await this.unpacker.queryByElo(n,e.limit*2)).map(e=>({cardID:e,courseID:this.courseId}));return t&&(r=r.filter(t)),r.slice(0,e.limit).map(e=>({status:`new`,cardID:e.cardID,contentSourceType:`course`,contentSourceID:this.courseId,courseID:this.courseId}))}async getAppliedTags(e){try{let t=await this.unpacker.getTagsIndex(),n=t.byCard[e]||[],r=await Promise.all(n.map(async n=>{let r=`${DocType.TAG}-${n}`;try{let t=await this.unpacker.getDocument(r);return{id:r,key:e,value:{name:t.name,snippet:t.snippet,count:t.taggedCards?.length||0}}}catch(i){if(i&&i.status===404)logger.warn(`Tag document not found for ${n}, creating stub`);else throw logger.error(`Error getting tag document for ${n}:`,i),i;return{id:r,key:e,value:{name:n,snippet:`Tag: ${n}`,count:t.byTag[n]?.length||0}}}}));return{total_rows:r.length,offset:0,rows:r}}catch(t){return logger.error(`Error getting applied tags for card ${e}:`,t),{total_rows:0,offset:0,rows:[]}}}async getAppliedTagsBatch(e){let t=await this.unpacker.getTagsIndex(),n=new Map;for(let r of e)n.set(r,t.byCard[r]||[]);return n}async getAllCardIds(){let e=await this.unpacker.getTagsIndex();return Object.keys(e.byCard)}async addTagToCard(e,t){throw Error(`Cannot modify tags in static mode`)}async removeTagFromCard(e,t){throw Error(`Cannot modify tags in static mode`)}async createTag(e){throw Error(`Cannot create tags in static mode`)}async getTag(e){return this.unpacker.getDocument(`${DocType.TAG}-${e}`)}async updateTag(e){throw Error(`Cannot update tags in static mode`)}async getCourseTagStubs(){try{let e=await this.unpacker.getTagsIndex();if(!e||!e.byTag)return logger.warn(`Tags index not found or empty`),{total_rows:0,offset:0,rows:[]};let t=Object.keys(e.byTag),n=await Promise.all(t.map(async t=>{let n=e.byTag[t]||[],r=`${DocType.TAG}-${t}`;try{return{id:r,key:r,value:{rev:`1-static`},doc:await this.unpacker.getDocument(r)}}catch(e){if(e&&e.status===404)return logger.warn(`Tag document not found for ${t}, creating stub`),{id:r,key:r,value:{rev:`1-static`},doc:{_id:r,_rev:`1-static`,course:this.courseId,docType:DocType.TAG,name:t,snippet:`Tag: ${t}`,wiki:``,taggedCards:n,author:`system`}};throw logger.error(`Error getting tag document for ${t}:`,e),e}}));return{total_rows:n.length,offset:0,rows:n}}catch(e){return logger.error(`Failed to get course tag stubs:`,e),{total_rows:0,offset:0,rows:[]}}}async addNote(e,t,n,r,i,a,o){return{status:Status.error,message:`Cannot add notes in static mode`}}async removeCard(e){throw Error(`Cannot remove cards in static mode`)}async getInexperiencedCards(){return[]}async getNavigationStrategy(e){try{return await this.unpacker.getDocument(e)}catch(t){throw logger.error(`[static/courseDB] Strategy ${e} not found: ${t}`),t}}async getAllNavigationStrategies(){let e=DocTypePrefixes[DocType.NAVIGATION_STRATEGY];try{return await this.unpacker.getAllDocumentsByPrefix(e)}catch(e){return logger.warn(`[static/courseDB] Error loading navigation strategies: ${e}`),[]}}async addNavigationStrategy(e){throw Error(`Cannot add navigation strategies in static mode`)}async updateNavigationStrategy(e,t){throw Error(`Cannot update navigation strategies in static mode`)}async createNavigator(e){try{let t=await this.getAllNavigationStrategies();if(t.length===0)return logger.debug(`[static/courseDB] No strategy documents found, using default Pipeline(Composite(ELO, SRS), [eloDistanceFilter])`),createDefaultPipeline(e,this);let{pipeline:n,generatorStrategies:r,filterStrategies:i,warnings:a}=await new PipelineAssembler().assemble({strategies:t,user:e,course:this});for(let e of a)logger.warn(`[PipelineAssembler] ${e}`);return n?(logger.debug(`[static/courseDB] Using assembled pipeline with ${r.length} generator(s) and ${i.length} filter(s)`),n):(logger.debug(`[static/courseDB] Pipeline assembly failed, using default pipeline`),createDefaultPipeline(e,this))}catch(e){throw logger.error(`[static/courseDB] Error creating navigator: ${e}`),e}}setEphemeralHints(e){this._pendingHints=e}notePresented(e){e.status===`new`&&this.createNavigator(this.userDB).then(t=>t.notePresented?.(e)).catch(e=>logger.warn(`[static/courseDB] Could not note a presentation: ${e}`))}async getWeightedCards(e){try{let t=await this.createNavigator(this.userDB);return this._pendingHints&&(t.setEphemeralHints(this._pendingHints),this._pendingHints=null),t.getWeightedCards(e)}catch(e){throw logger.error(`[static/courseDB] Error getting weighted cards: ${e}`),e}}getAttachmentUrl(e,t){return this.unpacker.getAttachmentUrl(e,t)}async getAttachmentBlob(e,t){return this.unpacker.getAttachmentBlob(e,t)}async searchCards(e){return[]}async find(e){return{docs:[],warning:`Find operations not supported in static mode`}}},StaticCoursesDB=class{constructor(e,t){this.manifests=e,this.dependencyNameToCourseId=t}async getCourseConfig(e){let t=this.manifests[e];if(!t&&this.dependencyNameToCourseId){let n=this.dependencyNameToCourseId.get(e);n&&(t=this.manifests[n])}if(!t)throw logger.warn(`Course manifest for ${e} not found`),Error(`Course ${e} not found`);if(t.courseConfig)return t.courseConfig;throw logger.warn(`Course config not found in manifest for course ${e}`),Error(`Course config not found for course ${e}`)}async getCourseList(){return Object.keys(this.manifests).map(e=>({courseID:e,name:this.manifests[e].courseName}))}async disambiguateCourse(e,t){logger.warn(`Cannot disambiguate courses in static mode`)}},NoOpSyncStrategy=class{constructor(){_defineProperty(this,`currentUsername`,accomodateGuest().username)}setupRemoteDB(e){return getLocalUserDB(e)}getWriteDB(e){return getLocalUserDB(e)}startSync(e,t){}stopSync(){}canCreateAccount(){return!1}canAuthenticate(){return!1}async createAccount(e,t){throw Error(`Account creation not supported in static mode. Use local account switching instead.`)}async authenticate(e,t){throw Error(`Remote authentication not supported in static mode. Use local account switching instead.`)}async logout(){return this.currentUsername=accomodateGuest().username,{ok:!0}}async getCurrentUsername(){return this.currentUsername}setCurrentUsername(e){this.currentUsername=e}},StaticDataLayerProvider=class{constructor(e){_defineProperty(this,`config`,void 0),_defineProperty(this,`initialized`,!1),_defineProperty(this,`courseUnpackers`,new Map),_defineProperty(this,`manifests`,{}),_defineProperty(this,`dependencyNameToCourseId`,new Map),this.config={localStoragePrefix:e.localStoragePrefix||`skuilder-static`,rootManifest:e.rootManifest||{dependencies:{}},rootManifestUrl:e.rootManifestUrl||`/`}}async resolveCourseDependencies(){logger.info(`[StaticDataLayerProvider] Starting course dependency resolution...`);let e=this.config.rootManifest;for(let[t,n]of Object.entries(e.dependencies||{}))try{logger.debug(`[StaticDataLayerProvider] Resolving dependency: ${t} from ${n}`);let e=new URL(n,this.config.rootManifestUrl).href,r=await fetch(e);if(!r.ok)throw Error(`Failed to fetch course manifest for ${t}`);let i=await r.json();if(i.content&&i.content.manifest){let n=new URL(`.`,e).href,r=new URL(i.content.manifest,e).href,a=await fetch(r);if(!a.ok)throw Error(`Failed to fetch final content manifest for ${t} at ${r}`);let o=await a.json(),s=o.courseId||o.courseConfig?.courseID;if(!s)throw Error(`Course manifest for ${t} missing courseId`);this.manifests[s]=o;let c=new StaticDataUnpacker(o,n);this.courseUnpackers.set(s,c),this.dependencyNameToCourseId.set(t,s),logger.info(`[StaticDataLayerProvider] Successfully resolved and prepared course: ${t} (courseId: ${s})`)}}catch(e){logger.error(`[StaticDataLayerProvider] Failed to resolve dependency ${t}:`,e)}logger.info(`[StaticDataLayerProvider] Course dependency resolution complete.`)}async initialize(){this.initialized||(logger.info(`Initializing static data layer provider`),await this.resolveCourseDependencies(),this.initialized=!0)}async teardown(){this.courseUnpackers.clear(),this.initialized=!1}getUserDB(){let e=new NoOpSyncStrategy;return BaseUser.Dummy(e)}getCourseDB(e){let t=this.courseUnpackers.get(e),n=e;if(!t){let r=this.dependencyNameToCourseId.get(e);r&&(t=this.courseUnpackers.get(r),n=r)}if(!t)throw Error(`Course ${e} not found or failed to initialize in static data layer.`);let r=this.manifests[n];return new StaticCourseDB(n,t,this.getUserDB(),r)}getCoursesDB(){return new StaticCoursesDB(this.manifests,this.dependencyNameToCourseId)}async getClassroomDB(e,t){throw Error(`Classrooms not supported in static mode`)}getAdminDB(){throw Error(`Admin functions not supported in static mode`)}isReadOnly(){return!0}};export{StaticDataLayerProvider};
2
+ //# sourceMappingURL=StaticDataLayerProvider-CtgU3VBh.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StaticDataLayerProvider-CtgU3VBh.js","names":[],"sources":["../../../db/dist/impl/static/StaticDataUnpacker.mjs","../../../db/dist/impl/static/courseDB.mjs","../../../db/dist/impl/static/coursesDB.mjs","../../../db/dist/impl/static/NoOpSyncStrategy.mjs","../../../db/dist/impl/static/StaticDataLayerProvider.mjs"],"sourcesContent":["import { __name } from \"../../_virtual/_rolldown/runtime.mjs\";\nimport { logger } from \"../../util/logger.mjs\";\nimport { DocTypePrefixes } from \"../../core/types/types-legacy.mjs\";\n//#region src/impl/static/StaticDataUnpacker.ts\nconst pathUtils = { isAbsolute: (path) => {\n\tif (/^[a-zA-Z]:[\\\\/]/.test(path) || /^\\\\\\\\/.test(path)) return true;\n\tif (path.startsWith(\"/\")) return true;\n\treturn false;\n} };\nlet nodeFS = null;\ntry {\n\tif (typeof window === \"undefined\" && typeof process !== \"undefined\" && process.versions?.node) nodeFS = eval(\"require\")(\"fs\");\n} catch {}\nvar StaticDataUnpacker = class {\n\tmanifest;\n\tbasePath;\n\tdocumentCache = /* @__PURE__ */ new Map();\n\tchunkCache = /* @__PURE__ */ new Map();\n\tindexCache = /* @__PURE__ */ new Map();\n\tconstructor(manifest, basePath) {\n\t\tthis.manifest = manifest;\n\t\tthis.basePath = basePath;\n\t}\n\t/**\n\t* Get a document by ID, loading from appropriate chunk if needed\n\t*/\n\tasync getDocument(id) {\n\t\tif (this.documentCache.has(id)) {\n\t\t\tconst doc = this.documentCache.get(id);\n\t\t\treturn await this.hydrateAttachments(doc);\n\t\t}\n\t\tconst chunk = await this.findChunkForDocument(id);\n\t\tif (!chunk) {\n\t\t\tlogger.error(`Document ${id} not found in any chunk. Available chunks:`, this.manifest.chunks.map((c) => `${c.id} (${c.docType}): ${c.startKey} - ${c.endKey}`));\n\t\t\tthrow new Error(`Document ${id} not found in any chunk`);\n\t\t}\n\t\tawait this.loadChunk(chunk.id);\n\t\tif (this.documentCache.has(id)) {\n\t\t\tconst doc = this.documentCache.get(id);\n\t\t\treturn await this.hydrateAttachments(doc);\n\t\t}\n\t\tlogger.error(`Document ${id} not found in chunk ${chunk.id}`);\n\t\tthrow new Error(`Document ${id} not found in chunk ${chunk.id}`);\n\t}\n\t/**\n\t* Get all documents with IDs starting with a specific prefix.\n\t*\n\t* This method loads the relevant chunk(s) and returns all matching documents.\n\t* Useful for querying documents by type (e.g., all NAVIGATION_STRATEGY documents).\n\t*\n\t* @param prefix - Document ID prefix to match (e.g., \"NAVIGATION_STRATEGY\")\n\t* @returns Array of all documents with IDs starting with the prefix\n\t*/\n\tasync getAllDocumentsByPrefix(prefix) {\n\t\tconst relevantChunks = this.manifest.chunks.filter((chunk) => {\n\t\t\tconst prefixEnd = prefix + \"￰\";\n\t\t\treturn chunk.startKey <= prefixEnd && chunk.endKey >= prefix;\n\t\t});\n\t\tif (relevantChunks.length === 0) {\n\t\t\tlogger.debug(`[StaticDataUnpacker] No chunks found for prefix: ${prefix}`);\n\t\t\treturn [];\n\t\t}\n\t\tawait Promise.all(relevantChunks.map((chunk) => this.loadChunk(chunk.id)));\n\t\tconst matchingDocs = [];\n\t\tfor (const [docId, doc] of this.documentCache.entries()) if (docId.startsWith(prefix)) matchingDocs.push(await this.hydrateAttachments(doc));\n\t\tlogger.debug(`[StaticDataUnpacker] Found ${matchingDocs.length} documents with prefix: ${prefix}`);\n\t\treturn matchingDocs;\n\t}\n\t/**\n\t* Query cards by ELO score, returning card IDs sorted by ELO\n\t*/\n\tasync queryByElo(targetElo, limit = 25) {\n\t\tconst eloIndex = await this.loadIndex(\"elo\");\n\t\tif (!eloIndex || !eloIndex.sorted) {\n\t\t\tlogger.warn(\"ELO index not found or malformed, returning empty results\");\n\t\t\treturn [];\n\t\t}\n\t\tconst sorted = eloIndex.sorted;\n\t\tlet startIndex = 0;\n\t\tlet left = 0;\n\t\tlet right = sorted.length - 1;\n\t\twhile (left <= right) {\n\t\t\tconst mid = Math.floor((left + right) / 2);\n\t\t\tif (sorted[mid].elo < targetElo) left = mid + 1;\n\t\t\telse right = mid - 1;\n\t\t}\n\t\tstartIndex = left;\n\t\tconst result = [];\n\t\tconst halfLimit = Math.floor(limit / 2);\n\t\tfor (let i = Math.max(0, startIndex - halfLimit); i < startIndex && result.length < limit; i++) result.push(sorted[i].cardId);\n\t\tfor (let i = startIndex; i < sorted.length && result.length < limit; i++) result.push(sorted[i].cardId);\n\t\treturn result;\n\t}\n\t/**\n\t* Get all tag names mapped to their card arrays\n\t*/\n\tasync getTagsIndex() {\n\t\ttry {\n\t\t\treturn await this.loadIndex(\"tags\");\n\t\t} catch {\n\t\t\treturn {\n\t\t\t\tbyCard: {},\n\t\t\t\tbyTag: {}\n\t\t\t};\n\t\t}\n\t}\n\tgetDocTypeFromId(id) {\n\t\tfor (const docTypeKey in DocTypePrefixes) {\n\t\t\tconst prefix = DocTypePrefixes[docTypeKey];\n\t\t\tif (id.startsWith(`${prefix}-`)) return docTypeKey;\n\t\t}\n\t}\n\t/**\n\t* Find which chunk contains a specific document ID\n\t*/\n\tasync findChunkForDocument(docId) {\n\t\tconst expectedDocType = this.getDocTypeFromId(docId);\n\t\tif (expectedDocType) {\n\t\t\tconst typeChunks = this.manifest.chunks.filter((c) => c.docType === expectedDocType);\n\t\t\tfor (const chunk of typeChunks) if (docId >= chunk.startKey && docId <= chunk.endKey) {\n\t\t\t\tif (await this.verifyDocumentInChunk(docId, chunk)) return chunk;\n\t\t\t}\n\t\t} else {\n\t\t\tfor (const chunk of this.manifest.chunks) if (docId >= chunk.startKey && docId <= chunk.endKey) {\n\t\t\t\tif (await this.verifyDocumentInChunk(docId, chunk)) return chunk;\n\t\t\t}\n\t\t\tconst otherChunks = this.manifest.chunks.filter((c) => c.docType !== \"CARD\" && c.docType !== \"DISPLAYABLE_DATA\" && c.docType !== \"TAG\");\n\t\t\tfor (const chunk of otherChunks) if (docId >= chunk.startKey && docId <= chunk.endKey) {\n\t\t\t\tif (await this.verifyDocumentInChunk(docId, chunk)) return chunk;\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t}\n\t/**\n\t* Verify that a document actually exists in a specific chunk by loading and checking it\n\t*/\n\tasync verifyDocumentInChunk(docId, chunk) {\n\t\ttry {\n\t\t\tawait this.loadChunk(chunk.id);\n\t\t\treturn this.documentCache.has(docId);\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n\t/**\n\t* Load a chunk file and cache its documents\n\t*/\n\tasync loadChunk(chunkId) {\n\t\tif (this.chunkCache.has(chunkId)) return;\n\t\tconst chunk = this.manifest.chunks.find((c) => c.id === chunkId);\n\t\tif (!chunk) throw new Error(`Chunk ${chunkId} not found in manifest`);\n\t\ttry {\n\t\t\tconst chunkPath = `${this.basePath}/${chunk.path}`;\n\t\t\tlogger.debug(`Loading chunk from ${chunkPath}`);\n\t\t\tlet documents;\n\t\t\tif (this.isLocalPath(chunkPath) && nodeFS) {\n\t\t\t\tconst fileContent = await nodeFS.promises.readFile(chunkPath, \"utf8\");\n\t\t\t\tdocuments = JSON.parse(fileContent);\n\t\t\t} else {\n\t\t\t\tconst response = await fetch(chunkPath);\n\t\t\t\tif (!response.ok) throw new Error(`Failed to fetch chunk ${chunkId}: ${response.status} ${response.statusText}`);\n\t\t\t\tdocuments = await response.json();\n\t\t\t}\n\t\t\tthis.chunkCache.set(chunkId, documents);\n\t\t\tfor (const doc of documents) if (doc._id) this.documentCache.set(doc._id, doc);\n\t\t\tlogger.debug(`Loaded ${documents.length} documents from chunk ${chunkId}`);\n\t\t} catch (error) {\n\t\t\tlogger.error(`Failed to load chunk ${chunkId}:`, error);\n\t\t\tthrow error;\n\t\t}\n\t}\n\t/**\n\t* Load an index file and cache it\n\t*/\n\tasync loadIndex(indexName) {\n\t\tif (this.indexCache.has(indexName)) return this.indexCache.get(indexName);\n\t\tconst indexMeta = this.manifest.indices.find((idx) => idx.name === indexName);\n\t\tif (!indexMeta) throw new Error(`Index ${indexName} not found in manifest`);\n\t\ttry {\n\t\t\tconst indexPath = `${this.basePath}/${indexMeta.path}`;\n\t\t\tlogger.debug(`Loading index from ${indexPath}`);\n\t\t\tlet indexData;\n\t\t\tif (this.isLocalPath(indexPath) && nodeFS) {\n\t\t\t\tconst fileContent = await nodeFS.promises.readFile(indexPath, \"utf8\");\n\t\t\t\tindexData = JSON.parse(fileContent);\n\t\t\t} else {\n\t\t\t\tconst response = await fetch(indexPath);\n\t\t\t\tif (!response.ok) throw new Error(`Failed to fetch index ${indexName}: ${response.status} ${response.statusText}`);\n\t\t\t\tindexData = await response.json();\n\t\t\t}\n\t\t\tthis.indexCache.set(indexName, indexData);\n\t\t\tlogger.debug(`Loaded index ${indexName}`);\n\t\t\treturn indexData;\n\t\t} catch (error) {\n\t\t\tlogger.error(`Failed to load index ${indexName}:`, error);\n\t\t\tthrow error;\n\t\t}\n\t}\n\t/**\n\t* Get a document by ID without hydration (raw document access)\n\t* Used internally to avoid recursion during attachment hydration\n\t*/\n\tasync getRawDocument(id) {\n\t\tif (this.documentCache.has(id)) return this.documentCache.get(id);\n\t\tconst chunk = await this.findChunkForDocument(id);\n\t\tif (!chunk) {\n\t\t\tlogger.error(`Document ${id} not found in any chunk. Available chunks:`, this.manifest.chunks.map((c) => `${c.id} (${c.docType}): ${c.startKey} - ${c.endKey}`));\n\t\t\tthrow new Error(`Document ${id} not found in any chunk`);\n\t\t}\n\t\tawait this.loadChunk(chunk.id);\n\t\tif (this.documentCache.has(id)) return this.documentCache.get(id);\n\t\tlogger.error(`Document ${id} not found in chunk ${chunk.id}`);\n\t\tthrow new Error(`Document ${id} not found in chunk ${chunk.id}`);\n\t}\n\t/**\n\t* Get attachment URL for a given document and attachment name\n\t*/\n\tgetAttachmentUrl(docId, attachmentName) {\n\t\treturn `${this.basePath}/attachments/${docId}/${attachmentName}`;\n\t}\n\t/**\n\t* Load attachment data from the document and get the correct file path\n\t* Uses raw document access to avoid recursion with hydration\n\t*/\n\tasync getAttachmentPath(docId, attachmentName) {\n\t\ttry {\n\t\t\tconst doc = await this.getRawDocument(docId);\n\t\t\tif (doc._attachments && doc._attachments[attachmentName]) {\n\t\t\t\tconst attachment = doc._attachments[attachmentName];\n\t\t\t\tif (attachment.path) return `${this.basePath}/${attachment.path}`;\n\t\t\t}\n\t\t\treturn null;\n\t\t} catch {\n\t\t\treturn null;\n\t\t}\n\t}\n\t/**\n\t* Load attachment as a blob (for browser) or buffer (for Node.js)\n\t*/\n\tasync getAttachmentBlob(docId, attachmentName) {\n\t\tconst attachmentPath = await this.getAttachmentPath(docId, attachmentName);\n\t\tif (!attachmentPath) return null;\n\t\ttry {\n\t\t\tif (this.isLocalPath(attachmentPath) && nodeFS) return await nodeFS.promises.readFile(attachmentPath);\n\t\t\telse {\n\t\t\t\tconst response = await fetch(attachmentPath);\n\t\t\t\tif (!response.ok) throw new Error(`Failed to fetch attachment ${docId}/${attachmentName}: ${response.status} ${response.statusText}`);\n\t\t\t\treturn await response.blob();\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tlogger.error(`Failed to load attachment ${docId}/${attachmentName}:`, error);\n\t\t\treturn null;\n\t\t}\n\t}\n\t/**\n\t* Hydrate document attachments by converting file paths to blob URLs\n\t*/\n\tasync hydrateAttachments(doc) {\n\t\tconst typedDoc = doc;\n\t\tif (!typedDoc._attachments) return doc;\n\t\tconst hydratedDoc = JSON.parse(JSON.stringify(doc));\n\t\tfor (const [attachmentName, attachment] of Object.entries(typedDoc._attachments)) {\n\t\t\tconst attachmentData = attachment;\n\t\t\tif (attachmentData.path) try {\n\t\t\t\tconst blob = await this.getAttachmentBlob(typedDoc._id, attachmentName);\n\t\t\t\tif (blob) if (typeof window !== \"undefined\" && window.URL) hydratedDoc._attachments[attachmentName] = {\n\t\t\t\t\t...attachmentData,\n\t\t\t\t\tdata: blob,\n\t\t\t\t\tstub: false\n\t\t\t\t};\n\t\t\t\telse hydratedDoc._attachments[attachmentName] = {\n\t\t\t\t\t...attachmentData,\n\t\t\t\t\tbuffer: blob\n\t\t\t\t};\n\t\t\t\telse logger.warn(`[hydrateAttachments] getAttachmentBlob returned null for ${typedDoc._id}/${attachmentName}. Skipping hydration for this attachment.`);\n\t\t\t} catch (error) {\n\t\t\t\tlogger.warn(`[hydrateAttachments] Failed to hydrate attachment ${typedDoc._id}/${attachmentName}:`, error);\n\t\t\t}\n\t\t\telse logger.debug(`[hydrateAttachments] Attachment ${attachmentName} for doc ${typedDoc._id} has no path. Skipping blob conversion.`);\n\t\t}\n\t\treturn hydratedDoc;\n\t}\n\t/**\n\t* Clear all caches (useful for testing or memory management)\n\t*/\n\tclearCaches() {\n\t\tthis.documentCache.clear();\n\t\tthis.chunkCache.clear();\n\t\tthis.indexCache.clear();\n\t}\n\t/**\n\t* Get cache statistics for debugging\n\t*/\n\tgetCacheStats() {\n\t\treturn {\n\t\t\tdocuments: this.documentCache.size,\n\t\t\tchunks: this.chunkCache.size,\n\t\t\tindices: this.indexCache.size\n\t\t};\n\t}\n\t/**\n\t* Check if a path is a local file path (vs URL)\n\t*/\n\tisLocalPath(filePath) {\n\t\treturn !filePath.startsWith(\"http://\") && !filePath.startsWith(\"https://\") && (pathUtils.isAbsolute(filePath) || filePath.startsWith(\"./\") || filePath.startsWith(\"../\"));\n\t}\n};\n//#endregion\nexport { StaticDataUnpacker };\n\n//# sourceMappingURL=StaticDataUnpacker.mjs.map","import \"../../_virtual/_rolldown/runtime.mjs\";\nimport { logger } from \"../../util/logger.mjs\";\nimport { DocType, DocTypePrefixes } from \"../../core/types/types-legacy.mjs\";\nimport { createDefaultPipeline } from \"../../core/navigators/defaults.mjs\";\nimport { PipelineAssembler } from \"../../core/navigators/PipelineAssembler.mjs\";\nimport { Status } from \"@vue-skuilder/common\";\n//#region src/impl/static/courseDB.ts\nvar StaticCourseDB = class {\n\tconstructor(courseId, unpacker, userDB, manifest) {\n\t\tthis.courseId = courseId;\n\t\tthis.unpacker = unpacker;\n\t\tthis.userDB = userDB;\n\t\tthis.manifest = manifest;\n\t}\n\tgetCourseID() {\n\t\treturn this.courseId;\n\t}\n\tasync getCourseConfig() {\n\t\tif (this.manifest.courseConfig != null) return this.manifest.courseConfig;\n\t\telse throw new Error(`Course config not found for course ${this.courseId}`);\n\t}\n\tasync updateCourseConfig(_cfg) {\n\t\tthrow new Error(\"Cannot update course config in static mode\");\n\t}\n\tasync getCourseInfo() {\n\t\treturn {\n\t\t\tcardCount: this.manifest.chunks.filter((chunk) => chunk.docType === DocType.CARD).reduce((total, chunk) => total + chunk.documentCount, 0),\n\t\t\tregisteredUsers: 0\n\t\t};\n\t}\n\tasync getCourseDoc(id, _options) {\n\t\treturn this.unpacker.getDocument(id);\n\t}\n\tasync getCourseDocs(ids, _options) {\n\t\tconst rows = await Promise.all(ids.map(async (id) => {\n\t\t\ttry {\n\t\t\t\treturn {\n\t\t\t\t\tid,\n\t\t\t\t\tkey: id,\n\t\t\t\t\tvalue: { rev: \"1-static\" },\n\t\t\t\t\tdoc: await this.unpacker.getDocument(id)\n\t\t\t\t};\n\t\t\t} catch {\n\t\t\t\treturn {\n\t\t\t\t\tkey: id,\n\t\t\t\t\terror: \"not_found\"\n\t\t\t\t};\n\t\t\t}\n\t\t}));\n\t\treturn {\n\t\t\ttotal_rows: ids.length,\n\t\t\toffset: 0,\n\t\t\trows\n\t\t};\n\t}\n\tasync getCardsByELO(elo, limit) {\n\t\treturn (await this.unpacker.queryByElo(elo, limit || 25)).map((card) => {\n\t\t\tconst [courseID, cardID, elo] = card.split(\"-\");\n\t\t\treturn {\n\t\t\t\tcourseID,\n\t\t\t\tcardID,\n\t\t\t\telo: elo ? parseInt(elo) : void 0\n\t\t\t};\n\t\t});\n\t}\n\tasync getCardEloData(cardIds) {\n\t\treturn await Promise.all(cardIds.map(async (id) => {\n\t\t\ttry {\n\t\t\t\treturn (await this.unpacker.getDocument(id)).elo || {\n\t\t\t\t\tglobal: {\n\t\t\t\t\t\tscore: 1e3,\n\t\t\t\t\t\tcount: 0\n\t\t\t\t\t},\n\t\t\t\t\ttags: {},\n\t\t\t\t\tmisc: {}\n\t\t\t\t};\n\t\t\t} catch {\n\t\t\t\treturn {\n\t\t\t\t\tglobal: {\n\t\t\t\t\t\tscore: 1e3,\n\t\t\t\t\t\tcount: 0\n\t\t\t\t\t},\n\t\t\t\t\ttags: {},\n\t\t\t\t\tmisc: {}\n\t\t\t\t};\n\t\t\t}\n\t\t}));\n\t}\n\tasync updateCardElo(cardId, _elo) {\n\t\treturn {\n\t\t\tok: true,\n\t\t\tid: cardId,\n\t\t\trev: \"1-static\"\n\t\t};\n\t}\n\tasync getCardsCenteredAtELO(options, filter) {\n\t\tlet targetElo = typeof options.elo === \"number\" ? options.elo : 1e3;\n\t\tif (options.elo === \"user\") try {\n\t\t\tconst courseReg = (await this.userDB.getCourseRegistrationsDoc()).courses.find((c) => c.courseID === this.courseId);\n\t\t\tif (courseReg && typeof courseReg.elo === \"object\") targetElo = courseReg.elo.global.score;\n\t\t} catch {\n\t\t\ttargetElo = 1e3;\n\t\t}\n\t\telse if (options.elo === \"random\") targetElo = 800 + Math.random() * 400;\n\t\tlet cardIds = (await this.unpacker.queryByElo(targetElo, options.limit * 2)).map((c) => {\n\t\t\treturn {\n\t\t\t\tcardID: c,\n\t\t\t\tcourseID: this.courseId\n\t\t\t};\n\t\t});\n\t\tif (filter) cardIds = cardIds.filter(filter);\n\t\treturn cardIds.slice(0, options.limit).map((card) => ({\n\t\t\tstatus: \"new\",\n\t\t\tcardID: card.cardID,\n\t\t\tcontentSourceType: \"course\",\n\t\t\tcontentSourceID: this.courseId,\n\t\t\tcourseID: this.courseId\n\t\t}));\n\t}\n\tasync getAppliedTags(cardId) {\n\t\ttry {\n\t\t\tconst tagsIndex = await this.unpacker.getTagsIndex();\n\t\t\tconst cardTags = tagsIndex.byCard[cardId] || [];\n\t\t\tconst rows = await Promise.all(cardTags.map(async (tagName) => {\n\t\t\t\tconst tagId = `${DocType.TAG}-${tagName}`;\n\t\t\t\ttry {\n\t\t\t\t\tconst tagDoc = await this.unpacker.getDocument(tagId);\n\t\t\t\t\treturn {\n\t\t\t\t\t\tid: tagId,\n\t\t\t\t\t\tkey: cardId,\n\t\t\t\t\t\tvalue: {\n\t\t\t\t\t\t\tname: tagDoc.name,\n\t\t\t\t\t\t\tsnippet: tagDoc.snippet,\n\t\t\t\t\t\t\tcount: tagDoc.taggedCards?.length || 0\n\t\t\t\t\t\t}\n\t\t\t\t\t};\n\t\t\t\t} catch (error) {\n\t\t\t\t\tif (error && error.status === 404) logger.warn(`Tag document not found for ${tagName}, creating stub`);\n\t\t\t\t\telse {\n\t\t\t\t\t\tlogger.error(`Error getting tag document for ${tagName}:`, error);\n\t\t\t\t\t\tthrow error;\n\t\t\t\t\t}\n\t\t\t\t\treturn {\n\t\t\t\t\t\tid: tagId,\n\t\t\t\t\t\tkey: cardId,\n\t\t\t\t\t\tvalue: {\n\t\t\t\t\t\t\tname: tagName,\n\t\t\t\t\t\t\tsnippet: `Tag: ${tagName}`,\n\t\t\t\t\t\t\tcount: tagsIndex.byTag[tagName]?.length || 0\n\t\t\t\t\t\t}\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t}));\n\t\t\treturn {\n\t\t\t\ttotal_rows: rows.length,\n\t\t\t\toffset: 0,\n\t\t\t\trows\n\t\t\t};\n\t\t} catch (error) {\n\t\t\tlogger.error(`Error getting applied tags for card ${cardId}:`, error);\n\t\t\treturn {\n\t\t\t\ttotal_rows: 0,\n\t\t\t\toffset: 0,\n\t\t\t\trows: []\n\t\t\t};\n\t\t}\n\t}\n\tasync getAppliedTagsBatch(cardIds) {\n\t\tconst tagsIndex = await this.unpacker.getTagsIndex();\n\t\tconst tagsByCard = /* @__PURE__ */ new Map();\n\t\tfor (const cardId of cardIds) tagsByCard.set(cardId, tagsIndex.byCard[cardId] || []);\n\t\treturn tagsByCard;\n\t}\n\tasync getAllCardIds() {\n\t\tconst tagsIndex = await this.unpacker.getTagsIndex();\n\t\treturn Object.keys(tagsIndex.byCard);\n\t}\n\tasync addTagToCard(_cardId, _tagId) {\n\t\tthrow new Error(\"Cannot modify tags in static mode\");\n\t}\n\tasync removeTagFromCard(_cardId, _tagId) {\n\t\tthrow new Error(\"Cannot modify tags in static mode\");\n\t}\n\tasync createTag(_tagName) {\n\t\tthrow new Error(\"Cannot create tags in static mode\");\n\t}\n\tasync getTag(tagName) {\n\t\treturn this.unpacker.getDocument(`${DocType.TAG}-${tagName}`);\n\t}\n\tasync updateTag(_tag) {\n\t\tthrow new Error(\"Cannot update tags in static mode\");\n\t}\n\tasync getCourseTagStubs() {\n\t\ttry {\n\t\t\tconst tagsIndex = await this.unpacker.getTagsIndex();\n\t\t\tif (!tagsIndex || !tagsIndex.byTag) {\n\t\t\t\tlogger.warn(\"Tags index not found or empty\");\n\t\t\t\treturn {\n\t\t\t\t\ttotal_rows: 0,\n\t\t\t\t\toffset: 0,\n\t\t\t\t\trows: []\n\t\t\t\t};\n\t\t\t}\n\t\t\tconst tagNames = Object.keys(tagsIndex.byTag);\n\t\t\tconst rows = await Promise.all(tagNames.map(async (tagName) => {\n\t\t\t\tconst cardIds = tagsIndex.byTag[tagName] || [];\n\t\t\t\tconst tagId = `${DocType.TAG}-${tagName}`;\n\t\t\t\ttry {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tid: tagId,\n\t\t\t\t\t\tkey: tagId,\n\t\t\t\t\t\tvalue: { rev: \"1-static\" },\n\t\t\t\t\t\tdoc: await this.unpacker.getDocument(tagId)\n\t\t\t\t\t};\n\t\t\t\t} catch (error) {\n\t\t\t\t\tif (error && error.status === 404) {\n\t\t\t\t\t\tlogger.warn(`Tag document not found for ${tagName}, creating stub`);\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tid: tagId,\n\t\t\t\t\t\t\tkey: tagId,\n\t\t\t\t\t\t\tvalue: { rev: \"1-static\" },\n\t\t\t\t\t\t\tdoc: {\n\t\t\t\t\t\t\t\t_id: tagId,\n\t\t\t\t\t\t\t\t_rev: \"1-static\",\n\t\t\t\t\t\t\t\tcourse: this.courseId,\n\t\t\t\t\t\t\t\tdocType: DocType.TAG,\n\t\t\t\t\t\t\t\tname: tagName,\n\t\t\t\t\t\t\t\tsnippet: `Tag: ${tagName}`,\n\t\t\t\t\t\t\t\twiki: \"\",\n\t\t\t\t\t\t\t\ttaggedCards: cardIds,\n\t\t\t\t\t\t\t\tauthor: \"system\"\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t};\n\t\t\t\t\t} else {\n\t\t\t\t\t\tlogger.error(`Error getting tag document for ${tagName}:`, error);\n\t\t\t\t\t\tthrow error;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}));\n\t\t\treturn {\n\t\t\t\ttotal_rows: rows.length,\n\t\t\t\toffset: 0,\n\t\t\t\trows\n\t\t\t};\n\t\t} catch (error) {\n\t\t\tlogger.error(\"Failed to get course tag stubs:\", error);\n\t\t\treturn {\n\t\t\t\ttotal_rows: 0,\n\t\t\t\toffset: 0,\n\t\t\t\trows: []\n\t\t\t};\n\t\t}\n\t}\n\tasync addNote(_codeCourse, _shape, _data, _author, _tags, _uploads, _elo) {\n\t\treturn {\n\t\t\tstatus: Status.error,\n\t\t\tmessage: \"Cannot add notes in static mode\"\n\t\t};\n\t}\n\tasync removeCard(_cardId) {\n\t\tthrow new Error(\"Cannot remove cards in static mode\");\n\t}\n\tasync getInexperiencedCards() {\n\t\treturn [];\n\t}\n\tasync getNavigationStrategy(id) {\n\t\ttry {\n\t\t\treturn await this.unpacker.getDocument(id);\n\t\t} catch (error) {\n\t\t\tlogger.error(`[static/courseDB] Strategy ${id} not found: ${error}`);\n\t\t\tthrow error;\n\t\t}\n\t}\n\tasync getAllNavigationStrategies() {\n\t\tconst prefix = DocTypePrefixes[DocType.NAVIGATION_STRATEGY];\n\t\ttry {\n\t\t\treturn await this.unpacker.getAllDocumentsByPrefix(prefix);\n\t\t} catch (error) {\n\t\t\tlogger.warn(`[static/courseDB] Error loading navigation strategies: ${error}`);\n\t\t\treturn [];\n\t\t}\n\t}\n\tasync addNavigationStrategy(_data) {\n\t\tthrow new Error(\"Cannot add navigation strategies in static mode\");\n\t}\n\tasync updateNavigationStrategy(_id, _data) {\n\t\tthrow new Error(\"Cannot update navigation strategies in static mode\");\n\t}\n\t/**\n\t* Create a ContentNavigator for this course.\n\t*\n\t* Loads navigation strategy documents from static data and uses PipelineAssembler\n\t* to build a Pipeline. Falls back to default pipeline if no strategies found.\n\t*/\n\tasync createNavigator(user) {\n\t\ttry {\n\t\t\tconst allStrategies = await this.getAllNavigationStrategies();\n\t\t\tif (allStrategies.length === 0) {\n\t\t\t\tlogger.debug(\"[static/courseDB] No strategy documents found, using default Pipeline(Composite(ELO, SRS), [eloDistanceFilter])\");\n\t\t\t\treturn createDefaultPipeline(user, this);\n\t\t\t}\n\t\t\tconst { pipeline, generatorStrategies, filterStrategies, warnings } = await new PipelineAssembler().assemble({\n\t\t\t\tstrategies: allStrategies,\n\t\t\t\tuser,\n\t\t\t\tcourse: this\n\t\t\t});\n\t\t\tfor (const warning of warnings) logger.warn(`[PipelineAssembler] ${warning}`);\n\t\t\tif (!pipeline) {\n\t\t\t\tlogger.debug(\"[static/courseDB] Pipeline assembly failed, using default pipeline\");\n\t\t\t\treturn createDefaultPipeline(user, this);\n\t\t\t}\n\t\t\tlogger.debug(`[static/courseDB] Using assembled pipeline with ${generatorStrategies.length} generator(s) and ${filterStrategies.length} filter(s)`);\n\t\t\treturn pipeline;\n\t\t} catch (e) {\n\t\t\tlogger.error(`[static/courseDB] Error creating navigator: ${e}`);\n\t\t\tthrow e;\n\t\t}\n\t}\n\t_pendingHints = null;\n\tsetEphemeralHints(hints) {\n\t\tthis._pendingHints = hints;\n\t}\n\t/** Forward a presentation to the navigator (a new card restarts its intake clock). */\n\tnotePresented(card) {\n\t\tif (card.status !== \"new\") return;\n\t\tthis.createNavigator(this.userDB).then((navigator) => navigator.notePresented?.(card)).catch((e) => logger.warn(`[static/courseDB] Could not note a presentation: ${e}`));\n\t}\n\tasync getWeightedCards(limit) {\n\t\ttry {\n\t\t\tconst navigator = await this.createNavigator(this.userDB);\n\t\t\tif (this._pendingHints) {\n\t\t\t\tnavigator.setEphemeralHints(this._pendingHints);\n\t\t\t\tthis._pendingHints = null;\n\t\t\t}\n\t\t\treturn navigator.getWeightedCards(limit);\n\t\t} catch (e) {\n\t\t\tlogger.error(`[static/courseDB] Error getting weighted cards: ${e}`);\n\t\t\tthrow e;\n\t\t}\n\t}\n\t/**\n\t* Get attachment URL for a document and attachment name\n\t* Internal helper method for static attachment serving\n\t*/\n\tgetAttachmentUrl(docId, attachmentName) {\n\t\treturn this.unpacker.getAttachmentUrl(docId, attachmentName);\n\t}\n\t/**\n\t* Load attachment as blob/buffer\n\t* Internal helper method for static attachment serving\n\t*/\n\tasync getAttachmentBlob(docId, attachmentName) {\n\t\treturn this.unpacker.getAttachmentBlob(docId, attachmentName);\n\t}\n\tasync searchCards(_query) {\n\t\treturn [];\n\t}\n\tasync find(_request) {\n\t\treturn {\n\t\t\tdocs: [],\n\t\t\twarning: \"Find operations not supported in static mode\"\n\t\t};\n\t}\n};\n//#endregion\nexport { StaticCourseDB };\n\n//# sourceMappingURL=courseDB.mjs.map","import \"../../_virtual/_rolldown/runtime.mjs\";\nimport { logger } from \"../../util/logger.mjs\";\n//#region src/impl/static/coursesDB.ts\nvar StaticCoursesDB = class {\n\tconstructor(manifests, dependencyNameToCourseId) {\n\t\tthis.manifests = manifests;\n\t\tthis.dependencyNameToCourseId = dependencyNameToCourseId;\n\t}\n\tasync getCourseConfig(courseId) {\n\t\tlet manifest = this.manifests[courseId];\n\t\tif (!manifest && this.dependencyNameToCourseId) {\n\t\t\tconst mappedCourseId = this.dependencyNameToCourseId.get(courseId);\n\t\t\tif (mappedCourseId) manifest = this.manifests[mappedCourseId];\n\t\t}\n\t\tif (!manifest) {\n\t\t\tlogger.warn(`Course manifest for ${courseId} not found`);\n\t\t\tthrow new Error(`Course ${courseId} not found`);\n\t\t}\n\t\tif (manifest.courseConfig) return manifest.courseConfig;\n\t\telse {\n\t\t\tlogger.warn(`Course config not found in manifest for course ${courseId}`);\n\t\t\tthrow new Error(`Course config not found for course ${courseId}`);\n\t\t}\n\t}\n\tasync getCourseList() {\n\t\treturn Object.keys(this.manifests).map((courseId) => ({\n\t\t\tcourseID: courseId,\n\t\t\tname: this.manifests[courseId].courseName\n\t\t}));\n\t}\n\tasync disambiguateCourse(_courseId, _disambiguator) {\n\t\tlogger.warn(\"Cannot disambiguate courses in static mode\");\n\t}\n};\n//#endregion\nexport { StaticCoursesDB };\n\n//# sourceMappingURL=coursesDB.mjs.map","import \"../../_virtual/_rolldown/runtime.mjs\";\nimport { getLocalUserDB } from \"../common/userDBHelpers.mjs\";\nimport { accomodateGuest } from \"../common/BaseUserDB.mjs\";\n//#region src/impl/static/NoOpSyncStrategy.ts\n/**\n* Sync strategy for static deployments that provides no-op implementations\n* for remote operations. Uses only local storage with no remote synchronization.\n*/\nvar NoOpSyncStrategy = class {\n\tcurrentUsername = accomodateGuest().username;\n\tsetupRemoteDB(username) {\n\t\treturn getLocalUserDB(username);\n\t}\n\tgetWriteDB(username) {\n\t\treturn getLocalUserDB(username);\n\t}\n\tstartSync(_localDB, _remoteDB) {}\n\tstopSync() {}\n\tcanCreateAccount() {\n\t\treturn false;\n\t}\n\tcanAuthenticate() {\n\t\treturn false;\n\t}\n\tasync createAccount(_username, _password) {\n\t\tthrow new Error(\"Account creation not supported in static mode. Use local account switching instead.\");\n\t}\n\tasync authenticate(_username, _password) {\n\t\tthrow new Error(\"Remote authentication not supported in static mode. Use local account switching instead.\");\n\t}\n\tasync logout() {\n\t\tthis.currentUsername = accomodateGuest().username;\n\t\treturn { ok: true };\n\t}\n\tasync getCurrentUsername() {\n\t\treturn this.currentUsername;\n\t}\n\t/**\n\t* Set the current username (for local account switching)\n\t* This method is specific to NoOpSyncStrategy and not part of the base interface\n\t*/\n\tsetCurrentUsername(username) {\n\t\tthis.currentUsername = username;\n\t}\n};\n//#endregion\nexport { NoOpSyncStrategy };\n\n//# sourceMappingURL=NoOpSyncStrategy.mjs.map","import \"../../_virtual/_rolldown/runtime.mjs\";\nimport { logger } from \"../../util/logger.mjs\";\nimport { BaseUser } from \"../common/BaseUserDB.mjs\";\nimport { StaticDataUnpacker } from \"./StaticDataUnpacker.mjs\";\nimport { StaticCourseDB } from \"./courseDB.mjs\";\nimport { StaticCoursesDB } from \"./coursesDB.mjs\";\nimport { NoOpSyncStrategy } from \"./NoOpSyncStrategy.mjs\";\n//#region src/impl/static/StaticDataLayerProvider.ts\nvar StaticDataLayerProvider = class {\n\tconfig;\n\tinitialized = false;\n\tcourseUnpackers = /* @__PURE__ */ new Map();\n\tmanifests = {};\n\tdependencyNameToCourseId = /* @__PURE__ */ new Map();\n\tconstructor(config) {\n\t\tthis.config = {\n\t\t\tlocalStoragePrefix: config.localStoragePrefix || \"skuilder-static\",\n\t\t\trootManifest: config.rootManifest || { dependencies: {} },\n\t\t\trootManifestUrl: config.rootManifestUrl || \"/\"\n\t\t};\n\t}\n\tasync resolveCourseDependencies() {\n\t\tlogger.info(\"[StaticDataLayerProvider] Starting course dependency resolution...\");\n\t\tconst rootManifest = this.config.rootManifest;\n\t\tfor (const [courseName, courseUrl] of Object.entries(rootManifest.dependencies || {})) try {\n\t\t\tlogger.debug(`[StaticDataLayerProvider] Resolving dependency: ${courseName} from ${courseUrl}`);\n\t\t\tconst courseManifestUrl = new URL(courseUrl, this.config.rootManifestUrl).href;\n\t\t\tconst courseJsonResponse = await fetch(courseManifestUrl);\n\t\t\tif (!courseJsonResponse.ok) throw new Error(`Failed to fetch course manifest for ${courseName}`);\n\t\t\tconst courseJson = await courseJsonResponse.json();\n\t\t\tif (courseJson.content && courseJson.content.manifest) {\n\t\t\t\tconst baseUrl = new URL(\".\", courseManifestUrl).href;\n\t\t\t\tconst finalManifestUrl = new URL(courseJson.content.manifest, courseManifestUrl).href;\n\t\t\t\tconst finalManifestResponse = await fetch(finalManifestUrl);\n\t\t\t\tif (!finalManifestResponse.ok) throw new Error(`Failed to fetch final content manifest for ${courseName} at ${finalManifestUrl}`);\n\t\t\t\tconst finalManifest = await finalManifestResponse.json();\n\t\t\t\tconst courseId = finalManifest.courseId || finalManifest.courseConfig?.courseID;\n\t\t\t\tif (!courseId) throw new Error(`Course manifest for ${courseName} missing courseId`);\n\t\t\t\tthis.manifests[courseId] = finalManifest;\n\t\t\t\tconst unpacker = new StaticDataUnpacker(finalManifest, baseUrl);\n\t\t\t\tthis.courseUnpackers.set(courseId, unpacker);\n\t\t\t\tthis.dependencyNameToCourseId.set(courseName, courseId);\n\t\t\t\tlogger.info(`[StaticDataLayerProvider] Successfully resolved and prepared course: ${courseName} (courseId: ${courseId})`);\n\t\t\t}\n\t\t} catch (e) {\n\t\t\tlogger.error(`[StaticDataLayerProvider] Failed to resolve dependency ${courseName}:`, e);\n\t\t}\n\t\tlogger.info(\"[StaticDataLayerProvider] Course dependency resolution complete.\");\n\t}\n\tasync initialize() {\n\t\tif (this.initialized) return;\n\t\tlogger.info(\"Initializing static data layer provider\");\n\t\tawait this.resolveCourseDependencies();\n\t\tthis.initialized = true;\n\t}\n\tasync teardown() {\n\t\tthis.courseUnpackers.clear();\n\t\tthis.initialized = false;\n\t}\n\tgetUserDB() {\n\t\tconst syncStrategy = new NoOpSyncStrategy();\n\t\treturn BaseUser.Dummy(syncStrategy);\n\t}\n\tgetCourseDB(courseId) {\n\t\tlet unpacker = this.courseUnpackers.get(courseId);\n\t\tlet actualCourseId = courseId;\n\t\tif (!unpacker) {\n\t\t\tconst mappedCourseId = this.dependencyNameToCourseId.get(courseId);\n\t\t\tif (mappedCourseId) {\n\t\t\t\tunpacker = this.courseUnpackers.get(mappedCourseId);\n\t\t\t\tactualCourseId = mappedCourseId;\n\t\t\t}\n\t\t}\n\t\tif (!unpacker) throw new Error(`Course ${courseId} not found or failed to initialize in static data layer.`);\n\t\tconst manifest = this.manifests[actualCourseId];\n\t\treturn new StaticCourseDB(actualCourseId, unpacker, this.getUserDB(), manifest);\n\t}\n\tgetCoursesDB() {\n\t\treturn new StaticCoursesDB(this.manifests, this.dependencyNameToCourseId);\n\t}\n\tasync getClassroomDB(_classId, _type) {\n\t\tthrow new Error(\"Classrooms not supported in static mode\");\n\t}\n\tgetAdminDB() {\n\t\tthrow new Error(\"Admin functions not supported in static mode\");\n\t}\n\tisReadOnly() {\n\t\treturn true;\n\t}\n};\n//#endregion\nexport { StaticDataLayerProvider };\n\n//# sourceMappingURL=StaticDataLayerProvider.mjs.map"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import{i as e}from"./chunk-D6hFPZc3.js";import{$ as t,C as n,H as r,Ht as i,K as a,Lt as o,S as s,T as c,W as l,_ as u,b as d,f,v as p,y as m}from"./vue.runtime.esm-bundler--Fxmd6l9.js";import{w as h}from"./MarkdownRenderer-borh39Lq.js";import"./moment-DbT30ZpG.js";import{B as g,C as _,g as v,h as y,n as b}from"./dist-BsRvt0gH.js";import{V as x}from"./contentSource-DwznMSMs.js";import{h as S}from"./edit-ui.es-UaQFL_My.js";import"./dist-Dp6eBE2S.js";import"./CourseWare-BTFRjgBR-DBp_fIwH.js";import"./PipelineDebugger-BHaqzqZw.js";import{d as C}from"./userDBKeys-DYLGKnVC.js";import"./eloDistance-BV7vfiK5.js";import{t as w}from"./index-CK-rfSIx.js";import"./chess-E2uOAyQS-BZi0vihz.js";import"./math-D_8HiEQR-E4QDLrWp.js";import"./piano-DF1g6yaX-Ck3BH5EK.js";import"./sightsing-BFQ7HRig-DV69ELZf.js";import"./typing-BevtfWlp-CvStu5BY.js";import{t as T}from"./dist-CAHkbl3M.js";var E=c({name:`SessionConfiguration`,components:{SkMouseTrapToolTip:g,CourseTagFilterWidget:b},props:{initialTimeLimit:{type:Number,required:!0,default:5}},emits:[`initStudySession`],data(){return{registeredHotkeys:[],allSelected:!0,activeCourses:[],activeClasses:[],hasRegistrations:!0,user:null,timeLimit:this.initialTimeLimit,courseLoadError:!1,classroomLoadError:!1,expandedFilters:{}}},computed:{hasSelectedSources(){return this.activeCourses.some(e=>e.selected)||this.activeClasses.some(e=>e.selected)},hasAnyActiveFilter(){return this.activeCourses.some(e=>e.selected&&this.hasActiveTagFilter(e))},activeFilterCount(){return this.activeCourses.filter(e=>e.selected&&this.hasActiveTagFilter(e)).length}},watch:{timeLimit:{handler(){this.timeLimit<=0&&(this.timeLimit=1)}}},beforeUnmount(){this.registeredHotkeys&&this.registeredHotkeys.forEach(e=>{h.removeBinding(e)})},async created(){this.user=await v(),this.timeLimit=this.initialTimeLimit,this.setHotkeys();let[e,t]=await Promise.allSettled([this.getActiveCourses(),this.getActiveClassrooms()]);e.status===`rejected`&&(console.error(`Failed to load courses:`,e.reason),this.courseLoadError=!0),t.status===`rejected`&&(console.error(`Failed to load classrooms:`,t.reason),this.classroomLoadError=!0),this.activeCourses.length===0&&this.activeClasses.length===0&&!this.courseLoadError&&!this.classroomLoadError&&(this.hasRegistrations=!1)},mounted(){document.getElementById(`SelectAll`)?.focus()},unmounted(){this.registeredHotkeys&&this.registeredHotkeys.forEach(e=>{h.removeBinding(e)})},methods:{inc(){this.timeLimit+=1,console.log(`inc to ${this.timeLimit}`)},dec(){this.timeLimit--,console.log(`dec to ${this.timeLimit}`)},update(){console.log(JSON.stringify(this.activeCourses)),console.log(JSON.stringify(this.activeClasses))},toggleAll(){console.log(`Toggling all courses`),this.activeCourses.forEach(e=>{e.selected=this.allSelected}),this.activeClasses.forEach(e=>{e.selected=this.allSelected}),console.log(JSON.stringify(this.activeCourses))},toggleTagFilter(e){this.expandedFilters[e]=!this.expandedFilters[e]},hasActiveTagFilter(e){return x(e.tagFilter)},startSession(){this.registeredHotkeys&&this.registeredHotkeys.forEach(e=>{h.removeBinding(e)});let e=this.activeCourses.filter(e=>e.selected).map(e=>{let t={type:`course`,id:e.courseID};return x(e.tagFilter)&&(t.tagFilter=e.tagFilter),t}),t=this.activeClasses.filter(e=>e.selected).map(e=>({type:`classroom`,id:e.classID})),n=[...e,...t];this.$emit(`initStudySession`,n,this.timeLimit)},async getActiveClassrooms(){let e=await(await v()).getActiveClasses(),t=[];console.log(`Active classes: ${JSON.stringify(e)}`),await Promise.all(e.map(e=>(async e=>{let n=await C().getClassroomDB(e,`student`);t.push({classID:e,name:n.getConfig().name,selected:!0,reviews:0})})(e))),this.activeClasses=t},async getActiveCourses(){this.activeCourses=(await this.user.getActiveCourses()).map(e=>({...e,selected:!0,name:``,reviews:0,tagFilter:void 0})),Promise.all(this.activeCourses.map(async(e,t)=>{let n=await C().getCoursesDB().getCourseConfig(e.courseID),r=await this.user?.getCourseInterface(e.courseID);return(async()=>Promise.all([this.activeCourses[t].name=n.name,this.activeCourses[t].reviews=await r.getScheduledReviewCount()]))()}))},setHotkeys(){let e=[{hotkey:`right`,callback:()=>{this.timeLimit++},command:`Increase time limit`},{hotkey:`left`,callback:()=>{this.timeLimit--},command:`Decrease time limit`}];h.addBinding(e),this.registeredHotkeys=e.map(e=>e.hotkey)}}}),D={key:0},O={class:`session-layout`},k={class:`course-selection-container`},A={class:`course-row-header`},j={class:`course-row-header`},M={class:`course-row-actions`},N={class:`reviews-count`},P={key:0,class:`tag-filter-container`},F={class:`select-all-row mt-3`},I={class:`fixed-controls-container`},L={class:`fixed-controls`},R={class:`mb-5`},z={key:0,class:`filter-summary mb-4`},B={class:`text-caption`},V={key:1,class:`text-h4`},H={key:2,class:`text-h4`};function _sfc_render$1(e,o,c,h,g,_){let v=a(`v-alert`),y=a(`v-checkbox`),b=a(`v-icon`),x=a(`v-btn`),S=a(`CourseTagFilterWidget`),C=a(`v-expand-transition`),w=a(`v-text-field`),T=a(`SkMouseTrapToolTip`),E=a(`router-link`);return e.hasRegistrations||e.courseLoadError||e.classroomLoadError?(r(),d(`div`,D,[o[11]||(o[11]=u(`div`,{"data-cy":`select-quilts-header`,class:`text-h4 mb-4`},`Study Session Setup`,-1)),e.courseLoadError?(r(),p(v,{key:0,type:`warning`,class:`mb-4`},{default:t(()=>o[2]||(o[2]=[s(` Unable to load course data. Some features may be unavailable. `)])),_:1})):m(``,!0),e.classroomLoadError?(r(),p(v,{key:1,type:`warning`,class:`mb-4`},{default:t(()=>o[3]||(o[3]=[s(` Unable to load classroom data. You can still study individual courses. `)])),_:1})):m(``,!0),u(`div`,O,[u(`div`,k,[o[6]||(o[6]=u(`div`,{class:`text-h6 mb-3`},`Select Quilts to Study`,-1)),(r(!0),d(f,null,l(e.activeClasses,t=>(r(),d(`div`,{key:t.classID,class:`course-row`},[u(`div`,A,[n(y,{modelValue:t.selected,"onUpdate:modelValue":e=>t.selected=e,label:`Class: ${t.name}`,"hide-details":``,onClickCapture:e.update},null,8,[`modelValue`,`onUpdate:modelValue`,`label`,`onClickCapture`]),o[4]||(o[4]=u(`span`,{class:`reviews-count`},`-`,-1))])]))),128)),(r(!0),d(f,null,l(e.activeCourses,a=>(r(),d(`div`,{key:a.courseID,class:`course-row`},[u(`div`,j,[n(y,{modelValue:a.selected,"onUpdate:modelValue":e=>a.selected=e,"data-cy":`course-checkbox`,label:`q/${a.name}`,"hide-details":``,onClickCapture:e.update},null,8,[`modelValue`,`onUpdate:modelValue`,`label`,`onClickCapture`]),u(`div`,M,[u(`span`,N,i(a.reviews),1),a.selected?(r(),p(x,{key:0,variant:`text`,size:`small`,color:e.hasActiveTagFilter(a)?`primary`:`default`,onClick:t=>e.toggleTagFilter(a.courseID)},{default:t(()=>[n(b,{start:``,size:`small`},{default:t(()=>o[5]||(o[5]=[s(`mdi-filter-variant`)])),_:1}),s(` `+i(e.hasActiveTagFilter(a)?`Filtered`:`Filter`)+` `,1),n(b,{end:``,size:`small`},{default:t(()=>[s(i(e.expandedFilters[a.courseID]?`mdi-chevron-up`:`mdi-chevron-down`),1)]),_:2},1024)]),_:2},1032,[`color`,`onClick`])):m(``,!0)])]),n(C,null,{default:t(()=>[a.selected&&e.expandedFilters[a.courseID]?(r(),d(`div`,P,[n(S,{modelValue:a.tagFilter,"onUpdate:modelValue":e=>a.tagFilter=e,"course-id":a.courseID},null,8,[`modelValue`,`onUpdate:modelValue`,`course-id`])])):m(``,!0)]),_:2},1024)]))),128)),u(`div`,F,[n(y,{id:`SelectAll`,ref:`selectAll`,modelValue:e.allSelected,"onUpdate:modelValue":[o[0]||(o[0]=t=>e.allSelected=t),e.toggleAll],autofocus:``,label:`Select All`,"hide-details":``},null,8,[`modelValue`,`onUpdate:modelValue`])])]),u(`div`,I,[u(`div`,L,[o[10]||(o[10]=u(`div`,{class:`text-h6 mb-3`},`Session Settings`,-1)),u(`div`,R,[n(w,{ref:`numberField`,modelValue:e.timeLimit,"onUpdate:modelValue":o[1]||(o[1]=t=>e.timeLimit=t),class:`time-limit-field`,variant:`outlined`,label:`Study Session Timelimit`,"prepend-inner-icon":`mdi-clock-outline`,"prepend-icon":`mdi-minus`,"append-icon":`mdi-plus`,suffix:e.timeLimit>1?`minutes`:`minute`,mask:`##`,type:`number`,"onClick:prepend":e.dec,"onClick:append":e.inc},null,8,[`modelValue`,`suffix`,`onClick:prepend`,`onClick:append`])]),e.hasAnyActiveFilter?(r(),d(`div`,z,[n(b,{size:`small`,color:`primary`,class:`mr-1`},{default:t(()=>o[7]||(o[7]=[s(`mdi-filter`)])),_:1}),u(`span`,B,i(e.activeFilterCount)+` course`+i(e.activeFilterCount>1?`s`:``)+` with tag filters `,1)])):m(``,!0),n(T,{hotkey:`enter`,command:`Start Session`,disabled:!e.hasSelectedSources,"highlight-effect":`scale`},{default:t(()=>[n(x,{"data-cy":`start-studying-button`,color:`success`,size:`large`,block:``,class:`start-btn`,disabled:!e.hasSelectedSources,onClick:e.startSession},{default:t(()=>[n(b,{start:``},{default:t(()=>o[8]||(o[8]=[s(`mdi-play`)])),_:1}),o[9]||(o[9]=s(` Start! `))]),_:1},8,[`disabled`,`onClick`])]),_:1},8,[`disabled`])])])])])):!e.courseLoadError&&!e.classroomLoadError?(r(),d(`div`,V,[o[15]||(o[15]=u(`p`,null,`You don't have anything to study!`,-1)),u(`p`,null,[o[13]||(o[13]=s(`Head over to the `)),n(E,{to:`/quilts`},{default:t(()=>o[12]||(o[12]=[s(`Quilts`)])),_:1}),o[14]||(o[14]=s(` page to find something for you.`))])])):(r(),d(`div`,H,o[16]||(o[16]=[u(`p`,null,`Unable to load study data due to technical issues.`,-1),u(`p`,null,`Please try refreshing the page or contact support if the problem persists.`,-1)])))}e(_sfc_render$1,`_sfc_render`);var U=w(E,[[`render`,_sfc_render$1],[`__scopeId`,`data-v-c5ff4b5f`]]);function randomInt(e,t){return Math.floor(Math.random()*(t-e+1))+e}var W=c({name:`StudyView`,components:{SessionConfiguration:U,StudySession:_},inject:{router:{from:`router`}},props:{previewCourseID:{type:String,required:!1,default:``},randomPreview:{type:Boolean,required:!1},focusCourseID:{type:String,required:!1,default:``}},data(){return{user:null,studySessionConfig:void 0,previewCourseConfig:void 0,previewMode:!1,sessionTimeLimit:5,inSession:!1,sessionPrepared:!1,sessionError:!1,errorMessage:``,sessionContentSources:[],dataInputFormStore:S(),getViewComponent:e=>T.getView(e),dataLayer:C()}},async created(){this.user=await v(),this.studySessionConfig={likesConfetti:y().config.likesConfetti};let e=``;if(this.randomPreview){let t=await this.user.getCourseRegistrationsDoc(),n=(await C().getCoursesDB().getCourseList()).map(e=>e.courseID),r=n.filter(e=>!t.courses.some(t=>t.courseID===e));e=r.length>0?r[randomInt(0,r.length)]:n[randomInt(0,n.length)]}if(this.previewCourseID&&(this.previewMode=!0,C().getCoursesDB().getCourseList().then(e=>{e.forEach(e=>{e.courseID===this.previewCourseID&&(this.previewCourseConfig=e,this.previewCourseConfig.courseID=e.courseID)})}),console.log(`[Study] COURSE PREVIEW MODE FOR ${this.previewCourseID}`),await this.user.registerForCourse(this.previewCourseID,!0),e=this.previewCourseID),this.focusCourseID&&(console.log(`[Study] FOCUS study session: ${this.focusCourseID}`),e=this.focusCourseID),e){let t=this.parseTagFilterFromQuery(),n={type:`course`,id:e};x(t)&&(n.tagFilter=t,console.log(`[Study] Tag filter from query params:`,t)),this.initStudySession([n],this.sessionTimeLimit)}},methods:{refreshRoute(){this.router.go(0)},async initStudySession(e,t){console.log(`[Study] starting study session w/ sources: ${JSON.stringify(e)}`),this.sessionContentSources=e,this.sessionTimeLimit=t,this.inSession=!0,this.sessionPrepared=!1,console.log(`[Study] Waiting for session-prepared event from StudySession component`)},registerUserForPreviewCourse(){this.user.registerForCourse(this.previewCourseConfig.courseID).then(()=>this.router.push(`/quilts/${this.previewCourseConfig.courseID}`))},handleSessionFinished(){this.refreshRoute()},handleSessionPrepared(){console.log(`[Study] Session preparation complete - received session-prepared event`),this.sessionPrepared=!0,this.sessionError=!1,this.errorMessage=``},handleSessionError({message:e,error:t}){console.error(`[Study] Session error:`,e,t),this.sessionError=!0,this.errorMessage=e||`An error occurred while preparing your study session.`,this.sessionPrepared=!1},parseTagFilterFromQuery(){let e=this.$route.query.include,t=this.$route.query.exclude,parseParam=e=>e?Array.isArray(e)?e.flatMap(e=>e?e.split(`,`).map(e=>e.trim()):[]).filter(Boolean):e.split(`,`).map(e=>e.trim()).filter(Boolean):[];return{include:parseParam(e),exclude:parseParam(t)}}}}),G={key:0},K={key:1},q={key:0,align:`center`},J={class:`text-h5`},Y={key:1},X={key:2},Z={key:0,class:`session-loading`},Q={key:1,class:`session-error`},$={class:`text-subtitle-1 mt-2`};function _sfc_render(e,c,l,f,p,h){let g=a(`SessionConfiguration`),_=a(`v-btn`),v=a(`router-link`),y=a(`v-spacer`),b=a(`v-col`),x=a(`v-row`),S=a(`v-progress-circular`),C=a(`v-container`),w=a(`v-icon`),T=a(`StudySession`);return e.inSession?(r(),d(`div`,K,[e.previewMode&&e.previewCourseConfig?(r(),d(`div`,q,[n(x,null,{default:t(()=>[n(b,null,{default:t(()=>[u(`span`,J,[c[1]||(c[1]=s(` Quilt preview for `)),u(`em`,null,i(e.previewCourseConfig.name),1)]),n(_,{size:`small`,color:`primary`,onClick:e.registerUserForPreviewCourse},{default:t(()=>c[2]||(c[2]=[s(`Join`)])),_:1},8,[`onClick`]),n(v,{to:`/quilts/${e.previewCourseConfig.courseID}`},{default:t(()=>[n(_,{size:`small`,color:`secondary`},{default:t(()=>c[3]||(c[3]=[s(`More info`)])),_:1})]),_:1},8,[`to`]),n(y)]),_:1})]),_:1})])):e.previewMode?(r(),d(`div`,Y,[n(x,null,{default:t(()=>[n(b,null,{default:t(()=>c[4]||(c[4]=[u(`span`,{class:`text-h5`},`... No course was specified for the preview.`,-1),u(`div`,null,`(this shouldn't happen)...`,-1)])),_:1})]),_:1})])):m(``,!0),e.inSession?(r(),d(`div`,X,[!e.sessionPrepared&&!e.sessionError?(r(),d(`div`,Z,[n(C,{class:`text-center`},{default:t(()=>[n(x,{justify:`center`,align:`center`,style:{"min-height":`50vh`}},{default:t(()=>[n(b,{cols:`12`},{default:t(()=>[n(S,{size:`70`,width:`7`,color:`primary`,indeterminate:``}),c[5]||(c[5]=u(`div`,{class:`text-h5 mt-4`},`Preparing your study session...`,-1)),c[6]||(c[6]=u(`div`,{class:`text-subtitle-1 mt-2`},`Getting your learning materials ready`,-1))]),_:1})]),_:1})]),_:1})])):m(``,!0),e.sessionError?(r(),d(`div`,Q,[n(C,{class:`text-center`},{default:t(()=>[n(x,{justify:`center`,align:`center`,style:{"min-height":`50vh`}},{default:t(()=>[n(b,{cols:`12`},{default:t(()=>[n(w,{size:`64`,color:`error`},{default:t(()=>c[7]||(c[7]=[s(`mdi-alert-circle`)])),_:1}),c[9]||(c[9]=u(`div`,{class:`text-h5 mt-4 text-error`},`Session Preparation Failed`,-1)),u(`div`,$,i(e.errorMessage||`There was a problem preparing your study session.`),1),n(_,{color:`primary`,class:`mt-6`,onClick:e.refreshRoute},{default:t(()=>c[8]||(c[8]=[s(`Try Again`)])),_:1},8,[`onClick`])]),_:1})]),_:1})]),_:1})])):m(``,!0),n(T,{"content-sources":e.sessionContentSources,"session-time-limit":e.sessionTimeLimit,user:e.user,"session-config":e.studySessionConfig,"data-layer":e.dataLayer,"get-view-component":e.getViewComponent,class:o({"hidden-session":!e.sessionPrepared}),onSessionFinished:e.handleSessionFinished,onSessionPrepared:e.handleSessionPrepared,onSessionError:e.handleSessionError},null,8,[`content-sources`,`session-time-limit`,`user`,`session-config`,`data-layer`,`get-view-component`,`class`,`onSessionFinished`,`onSessionPrepared`,`onSessionError`])])):m(``,!0)])):(r(),d(`div`,G,[n(g,{"initial-time-limit":e.sessionTimeLimit,onInitStudySession:c[0]||(c[0]=(t,n)=>e.initStudySession(t,n))},null,8,[`initial-time-limit`])]))}var ee=w(W,[[`render`,_sfc_render],[`__scopeId`,`data-v-dcbc1fde`]]);export{ee as default};
2
+ //# sourceMappingURL=Study-DDsGQCTJ.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"Study-CkJvraIv.js","names":[],"sources":["../../src/components/Study/SessionConfiguration.vue","../../src/components/Study/SessionConfiguration.vue","../../src/views/Study.vue","../../src/views/Study.vue"],"sourcesContent":["<template>\n <div v-if=\"hasRegistrations || courseLoadError || classroomLoadError\">\n <div data-cy=\"select-quilts-header\" class=\"text-h4 mb-4\">Study Session Setup</div>\n\n <!-- Error Messages -->\n <v-alert v-if=\"courseLoadError\" type=\"warning\" class=\"mb-4\">\n Unable to load course data. Some features may be unavailable.\n </v-alert>\n\n <v-alert v-if=\"classroomLoadError\" type=\"warning\" class=\"mb-4\">\n Unable to load classroom data. You can still study individual courses.\n </v-alert>\n\n <div class=\"session-layout\">\n <!-- Left Column: Course Selection -->\n <div class=\"course-selection-container\">\n <div class=\"text-h6 mb-3\">Select Quilts to Study</div>\n\n <!-- Classrooms -->\n <div v-for=\"classroom in activeClasses\" :key=\"classroom.classID\" class=\"course-row\">\n <div class=\"course-row-header\">\n <v-checkbox\n v-model=\"classroom.selected\"\n :label=\"`Class: ${classroom.name}`\"\n hide-details\n @click.capture=\"update\"\n />\n <span class=\"reviews-count\">-</span>\n </div>\n </div>\n\n <!-- Courses -->\n <div v-for=\"course in activeCourses\" :key=\"course.courseID\" class=\"course-row\">\n <div class=\"course-row-header\">\n <v-checkbox\n v-model=\"course.selected\"\n data-cy=\"course-checkbox\"\n :label=\"`q/${course.name}`\"\n hide-details\n @click.capture=\"update\"\n />\n <div class=\"course-row-actions\">\n <span class=\"reviews-count\">{{ course.reviews }}</span>\n <v-btn\n v-if=\"course.selected\"\n variant=\"text\"\n size=\"small\"\n :color=\"hasActiveTagFilter(course) ? 'primary' : 'default'\"\n @click=\"toggleTagFilter(course.courseID)\"\n >\n <v-icon start size=\"small\">mdi-filter-variant</v-icon>\n {{ hasActiveTagFilter(course) ? 'Filtered' : 'Filter' }}\n <v-icon end size=\"small\">\n {{ expandedFilters[course.courseID] ? 'mdi-chevron-up' : 'mdi-chevron-down' }}\n </v-icon>\n </v-btn>\n </div>\n </div>\n\n <!-- Tag Filter Widget (expandable) -->\n <v-expand-transition>\n <div v-if=\"course.selected && expandedFilters[course.courseID]\" class=\"tag-filter-container\">\n <CourseTagFilterWidget v-model=\"course.tagFilter\" :course-id=\"course.courseID\" />\n </div>\n </v-expand-transition>\n </div>\n\n <!-- Select All -->\n <div class=\"select-all-row mt-3\">\n <v-checkbox\n id=\"SelectAll\"\n ref=\"selectAll\"\n v-model=\"allSelected\"\n autofocus\n label=\"Select All\"\n hide-details\n @update:model-value=\"toggleAll\"\n ></v-checkbox>\n </div>\n </div>\n\n <!-- Right Column: Time Configuration and Start Button -->\n <div class=\"fixed-controls-container\">\n <div class=\"fixed-controls\">\n <div class=\"text-h6 mb-3\">Session Settings</div>\n\n <div class=\"mb-5\">\n <v-text-field\n ref=\"numberField\"\n v-model=\"timeLimit\"\n class=\"time-limit-field\"\n variant=\"outlined\"\n label=\"Study Session Timelimit\"\n prepend-inner-icon=\"mdi-clock-outline\"\n prepend-icon=\"mdi-minus\"\n append-icon=\"mdi-plus\"\n :suffix=\"timeLimit > 1 ? 'minutes' : 'minute'\"\n mask=\"##\"\n type=\"number\"\n @click:prepend=\"dec\"\n @click:append=\"inc\"\n />\n </div>\n\n <!-- Filter Summary -->\n <div v-if=\"hasAnyActiveFilter\" class=\"filter-summary mb-4\">\n <v-icon size=\"small\" color=\"primary\" class=\"mr-1\">mdi-filter</v-icon>\n <span class=\"text-caption\">\n {{ activeFilterCount }} course{{ activeFilterCount > 1 ? 's' : '' }} with tag filters\n </span>\n </div>\n\n <SkMouseTrapToolTip\n hotkey=\"enter\"\n command=\"Start Session\"\n :disabled=\"!hasSelectedSources\"\n highlight-effect=\"scale\"\n >\n <v-btn\n data-cy=\"start-studying-button\"\n color=\"success\"\n size=\"large\"\n block\n class=\"start-btn\"\n :disabled=\"!hasSelectedSources\"\n @click=\"startSession\"\n >\n <v-icon start>mdi-play</v-icon>\n Start!\n </v-btn>\n </SkMouseTrapToolTip>\n </div>\n </div>\n </div>\n </div>\n <div v-else-if=\"!courseLoadError && !classroomLoadError\" class=\"text-h4\">\n <p>You don't have anything to study!</p>\n <p>Head over to the <router-link to=\"/quilts\">Quilts</router-link> page to find something for you.</p>\n </div>\n <div v-else class=\"text-h4\">\n <p>Unable to load study data due to technical issues.</p>\n <p>Please try refreshing the page or contact support if the problem persists.</p>\n </div>\n</template>\n\n<script lang=\"ts\">\nimport { defineComponent } from 'vue';\nimport { SkldrMouseTrap, getCurrentUser, SkMouseTrapToolTip, CourseTagFilterWidget } from '@vue-skuilder/common-ui';\nimport { CourseRegistration, UserDBInterface, getDataLayer, ContentSourceID } from '@vue-skuilder/db';\nimport { TagFilter, hasActiveFilter } from '@vue-skuilder/common';\n\nexport interface SessionConfigMetaData {\n selected: boolean;\n name: string;\n reviews: number;\n tagFilter?: TagFilter;\n}\n\n// Extended ContentSourceID that can carry tag filter configuration\nexport interface TagFilteredContentSourceID extends ContentSourceID {\n tagFilter?: TagFilter;\n}\n\nexport default defineComponent({\n name: 'SessionConfiguration',\n\n components: {\n SkMouseTrapToolTip,\n CourseTagFilterWidget,\n },\n\n props: {\n initialTimeLimit: {\n type: Number,\n required: true,\n default: 5,\n },\n },\n\n emits: ['initStudySession'],\n\n data() {\n return {\n registeredHotkeys: [] as (string | string[])[],\n allSelected: true,\n activeCourses: [] as (CourseRegistration & SessionConfigMetaData)[],\n activeClasses: [] as ({ classID: string } & SessionConfigMetaData)[],\n hasRegistrations: true,\n user: null as UserDBInterface | null,\n timeLimit: this.initialTimeLimit,\n courseLoadError: false,\n classroomLoadError: false,\n expandedFilters: {} as Record<string, boolean>,\n };\n },\n\n computed: {\n hasSelectedSources(): boolean {\n return this.activeCourses.some((c) => c.selected) || this.activeClasses.some((c) => c.selected);\n },\n\n hasAnyActiveFilter(): boolean {\n return this.activeCourses.some((c) => c.selected && this.hasActiveTagFilter(c));\n },\n\n activeFilterCount(): number {\n return this.activeCourses.filter((c) => c.selected && this.hasActiveTagFilter(c)).length;\n },\n },\n\n watch: {\n timeLimit: {\n handler() {\n if (this.timeLimit <= 0) {\n this.timeLimit = 1;\n }\n },\n },\n },\n\n beforeUnmount() {\n // Clean up registered hotkeys when component unmounts\n if (this.registeredHotkeys) {\n this.registeredHotkeys.forEach((key) => {\n SkldrMouseTrap.removeBinding(key);\n });\n }\n },\n\n async created() {\n this.user = await getCurrentUser();\n this.timeLimit = this.initialTimeLimit;\n\n this.setHotkeys();\n const [coursesResult, classroomsResult] = await Promise.allSettled([\n this.getActiveCourses(),\n this.getActiveClassrooms(),\n ]);\n\n // Handle course loading failure\n if (coursesResult.status === 'rejected') {\n console.error('Failed to load courses:', coursesResult.reason);\n this.courseLoadError = true;\n }\n\n // Handle classroom loading failure\n if (classroomsResult.status === 'rejected') {\n console.error('Failed to load classrooms:', classroomsResult.reason);\n this.classroomLoadError = true;\n }\n\n if (\n this.activeCourses.length === 0 &&\n this.activeClasses.length === 0 &&\n !this.courseLoadError &&\n !this.classroomLoadError\n ) {\n this.hasRegistrations = false;\n }\n },\n\n mounted() {\n document.getElementById('SelectAll')?.focus();\n },\n\n unmounted() {\n // Clean up registered hotkeys when component unmounts\n if (this.registeredHotkeys) {\n this.registeredHotkeys.forEach((key) => {\n SkldrMouseTrap.removeBinding(key);\n });\n }\n },\n\n methods: {\n inc() {\n this.timeLimit = this.timeLimit + 1;\n console.log(`inc to ${this.timeLimit}`);\n },\n\n dec() {\n this.timeLimit--;\n console.log(`dec to ${this.timeLimit}`);\n },\n\n update() {\n console.log(JSON.stringify(this.activeCourses));\n console.log(JSON.stringify(this.activeClasses));\n },\n\n toggleAll(): void {\n console.log(`Toggling all courses`);\n this.activeCourses.forEach((crs) => {\n crs.selected = this.allSelected;\n });\n this.activeClasses.forEach((cl) => {\n cl.selected = this.allSelected;\n });\n console.log(JSON.stringify(this.activeCourses));\n },\n\n toggleTagFilter(courseID: string): void {\n this.expandedFilters[courseID] = !this.expandedFilters[courseID];\n },\n\n hasActiveTagFilter(course: CourseRegistration & SessionConfigMetaData): boolean {\n return hasActiveFilter(course.tagFilter);\n },\n\n startSession() {\n // Clean up any registered hotkeys before starting session\n if (this.registeredHotkeys) {\n this.registeredHotkeys.forEach((key) => {\n SkldrMouseTrap.removeBinding(key);\n });\n }\n\n // Build sources with optional tag filters\n const selectedCourses: TagFilteredContentSourceID[] = this.activeCourses\n .filter((c) => c.selected)\n .map((c) => {\n const source: TagFilteredContentSourceID = {\n type: 'course',\n id: c.courseID,\n };\n // Only include tagFilter if it has active constraints\n if (hasActiveFilter(c.tagFilter)) {\n source.tagFilter = c.tagFilter;\n }\n return source;\n });\n\n const selectedClassrooms: ContentSourceID[] = this.activeClasses\n .filter((cl) => cl.selected)\n .map((cl) => ({\n type: 'classroom',\n id: cl.classID,\n }));\n\n const allSelectedSources = [...selectedCourses, ...selectedClassrooms];\n this.$emit('initStudySession', allSelectedSources, this.timeLimit);\n },\n\n async getActiveClassrooms() {\n const classes = await (await getCurrentUser()).getActiveClasses();\n const activeClasses: ({ classID: string } & SessionConfigMetaData)[] = [];\n\n console.log(`Active classes: ${JSON.stringify(classes)}`);\n\n await Promise.all(\n classes.map((c) =>\n (async (classID: string) => {\n const classDb = await getDataLayer().getClassroomDB(classID, `student`);\n activeClasses.push({\n classID,\n name: classDb.getConfig().name,\n selected: true,\n reviews: 0,\n });\n })(c)\n )\n );\n this.activeClasses = activeClasses;\n },\n\n async getActiveCourses() {\n this.activeCourses = (await this.user!.getActiveCourses()).map((c) => ({\n ...c,\n selected: true,\n name: '',\n reviews: 0,\n tagFilter: undefined,\n }));\n\n Promise.all(\n this.activeCourses.map(async (c, i) => {\n const cfg = await getDataLayer().getCoursesDB().getCourseConfig(c.courseID);\n const crsInterface = await this.user?.getCourseInterface(c.courseID);\n return (async () => {\n return Promise.all([\n (this.activeCourses[i].name = cfg.name),\n (this.activeCourses[i].reviews = await crsInterface!.getScheduledReviewCount()),\n ]);\n })();\n })\n );\n },\n\n setHotkeys() {\n const hotkeys = [\n {\n hotkey: 'right',\n callback: () => {\n this.timeLimit++;\n },\n command: 'Increase time limit',\n },\n {\n hotkey: 'left',\n callback: () => {\n this.timeLimit--;\n },\n command: 'Decrease time limit',\n },\n ];\n SkldrMouseTrap.addBinding(hotkeys);\n this.registeredHotkeys = hotkeys.map((k) => k.hotkey);\n },\n },\n});\n</script>\n\n<style scoped>\n/* Layout for session configuration */\n.session-layout {\n display: flex;\n flex-direction: column;\n}\n\n/* Fixed controls container */\n.fixed-controls-container {\n width: 100%;\n margin-bottom: 20px;\n}\n\n.fixed-controls {\n display: flex;\n flex-direction: column;\n}\n\n.time-limit-field {\n width: 100%;\n margin-bottom: 16px;\n}\n\n.start-btn {\n margin-top: 8px;\n max-height: 150px;\n}\n\n/* Course selection styles */\n.course-selection-container {\n width: 100%;\n}\n\n.course-row {\n border-bottom: 1px solid rgba(var(--v-border-color), 0.12);\n padding: 8px 0;\n}\n\n.course-row-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 16px;\n}\n\n.course-row-actions {\n display: flex;\n align-items: center;\n gap: 12px;\n}\n\n.reviews-count {\n min-width: 32px;\n text-align: center;\n color: rgba(var(--v-theme-on-surface), 0.6);\n}\n\n.tag-filter-container {\n padding: 12px 0 12px 32px;\n background-color: rgba(var(--v-theme-surface-variant), 0.3);\n border-radius: 4px;\n margin-top: 8px;\n}\n\n.select-all-row {\n padding-top: 8px;\n border-top: 2px solid rgba(var(--v-border-color), 0.24);\n}\n\n.filter-summary {\n display: flex;\n align-items: center;\n padding: 8px 12px;\n background-color: rgba(var(--v-theme-primary), 0.08);\n border-radius: 4px;\n}\n\n/* Media queries for desktop layout */\n@media (min-width: 960px) {\n .session-layout {\n flex-direction: row;\n gap: 40px;\n }\n\n .fixed-controls-container {\n width: 300px;\n flex-shrink: 0;\n }\n\n .fixed-controls {\n position: sticky;\n top: 20px;\n padding-left: 20px;\n }\n\n .course-selection-container {\n flex-grow: 1;\n border-right: 1px solid rgba(0, 0, 0, 0.12);\n padding-right: 20px;\n }\n}\n</style>\n","<template>\n <div v-if=\"hasRegistrations || courseLoadError || classroomLoadError\">\n <div data-cy=\"select-quilts-header\" class=\"text-h4 mb-4\">Study Session Setup</div>\n\n <!-- Error Messages -->\n <v-alert v-if=\"courseLoadError\" type=\"warning\" class=\"mb-4\">\n Unable to load course data. Some features may be unavailable.\n </v-alert>\n\n <v-alert v-if=\"classroomLoadError\" type=\"warning\" class=\"mb-4\">\n Unable to load classroom data. You can still study individual courses.\n </v-alert>\n\n <div class=\"session-layout\">\n <!-- Left Column: Course Selection -->\n <div class=\"course-selection-container\">\n <div class=\"text-h6 mb-3\">Select Quilts to Study</div>\n\n <!-- Classrooms -->\n <div v-for=\"classroom in activeClasses\" :key=\"classroom.classID\" class=\"course-row\">\n <div class=\"course-row-header\">\n <v-checkbox\n v-model=\"classroom.selected\"\n :label=\"`Class: ${classroom.name}`\"\n hide-details\n @click.capture=\"update\"\n />\n <span class=\"reviews-count\">-</span>\n </div>\n </div>\n\n <!-- Courses -->\n <div v-for=\"course in activeCourses\" :key=\"course.courseID\" class=\"course-row\">\n <div class=\"course-row-header\">\n <v-checkbox\n v-model=\"course.selected\"\n data-cy=\"course-checkbox\"\n :label=\"`q/${course.name}`\"\n hide-details\n @click.capture=\"update\"\n />\n <div class=\"course-row-actions\">\n <span class=\"reviews-count\">{{ course.reviews }}</span>\n <v-btn\n v-if=\"course.selected\"\n variant=\"text\"\n size=\"small\"\n :color=\"hasActiveTagFilter(course) ? 'primary' : 'default'\"\n @click=\"toggleTagFilter(course.courseID)\"\n >\n <v-icon start size=\"small\">mdi-filter-variant</v-icon>\n {{ hasActiveTagFilter(course) ? 'Filtered' : 'Filter' }}\n <v-icon end size=\"small\">\n {{ expandedFilters[course.courseID] ? 'mdi-chevron-up' : 'mdi-chevron-down' }}\n </v-icon>\n </v-btn>\n </div>\n </div>\n\n <!-- Tag Filter Widget (expandable) -->\n <v-expand-transition>\n <div v-if=\"course.selected && expandedFilters[course.courseID]\" class=\"tag-filter-container\">\n <CourseTagFilterWidget v-model=\"course.tagFilter\" :course-id=\"course.courseID\" />\n </div>\n </v-expand-transition>\n </div>\n\n <!-- Select All -->\n <div class=\"select-all-row mt-3\">\n <v-checkbox\n id=\"SelectAll\"\n ref=\"selectAll\"\n v-model=\"allSelected\"\n autofocus\n label=\"Select All\"\n hide-details\n @update:model-value=\"toggleAll\"\n ></v-checkbox>\n </div>\n </div>\n\n <!-- Right Column: Time Configuration and Start Button -->\n <div class=\"fixed-controls-container\">\n <div class=\"fixed-controls\">\n <div class=\"text-h6 mb-3\">Session Settings</div>\n\n <div class=\"mb-5\">\n <v-text-field\n ref=\"numberField\"\n v-model=\"timeLimit\"\n class=\"time-limit-field\"\n variant=\"outlined\"\n label=\"Study Session Timelimit\"\n prepend-inner-icon=\"mdi-clock-outline\"\n prepend-icon=\"mdi-minus\"\n append-icon=\"mdi-plus\"\n :suffix=\"timeLimit > 1 ? 'minutes' : 'minute'\"\n mask=\"##\"\n type=\"number\"\n @click:prepend=\"dec\"\n @click:append=\"inc\"\n />\n </div>\n\n <!-- Filter Summary -->\n <div v-if=\"hasAnyActiveFilter\" class=\"filter-summary mb-4\">\n <v-icon size=\"small\" color=\"primary\" class=\"mr-1\">mdi-filter</v-icon>\n <span class=\"text-caption\">\n {{ activeFilterCount }} course{{ activeFilterCount > 1 ? 's' : '' }} with tag filters\n </span>\n </div>\n\n <SkMouseTrapToolTip\n hotkey=\"enter\"\n command=\"Start Session\"\n :disabled=\"!hasSelectedSources\"\n highlight-effect=\"scale\"\n >\n <v-btn\n data-cy=\"start-studying-button\"\n color=\"success\"\n size=\"large\"\n block\n class=\"start-btn\"\n :disabled=\"!hasSelectedSources\"\n @click=\"startSession\"\n >\n <v-icon start>mdi-play</v-icon>\n Start!\n </v-btn>\n </SkMouseTrapToolTip>\n </div>\n </div>\n </div>\n </div>\n <div v-else-if=\"!courseLoadError && !classroomLoadError\" class=\"text-h4\">\n <p>You don't have anything to study!</p>\n <p>Head over to the <router-link to=\"/quilts\">Quilts</router-link> page to find something for you.</p>\n </div>\n <div v-else class=\"text-h4\">\n <p>Unable to load study data due to technical issues.</p>\n <p>Please try refreshing the page or contact support if the problem persists.</p>\n </div>\n</template>\n\n<script lang=\"ts\">\nimport { defineComponent } from 'vue';\nimport { SkldrMouseTrap, getCurrentUser, SkMouseTrapToolTip, CourseTagFilterWidget } from '@vue-skuilder/common-ui';\nimport { CourseRegistration, UserDBInterface, getDataLayer, ContentSourceID } from '@vue-skuilder/db';\nimport { TagFilter, hasActiveFilter } from '@vue-skuilder/common';\n\nexport interface SessionConfigMetaData {\n selected: boolean;\n name: string;\n reviews: number;\n tagFilter?: TagFilter;\n}\n\n// Extended ContentSourceID that can carry tag filter configuration\nexport interface TagFilteredContentSourceID extends ContentSourceID {\n tagFilter?: TagFilter;\n}\n\nexport default defineComponent({\n name: 'SessionConfiguration',\n\n components: {\n SkMouseTrapToolTip,\n CourseTagFilterWidget,\n },\n\n props: {\n initialTimeLimit: {\n type: Number,\n required: true,\n default: 5,\n },\n },\n\n emits: ['initStudySession'],\n\n data() {\n return {\n registeredHotkeys: [] as (string | string[])[],\n allSelected: true,\n activeCourses: [] as (CourseRegistration & SessionConfigMetaData)[],\n activeClasses: [] as ({ classID: string } & SessionConfigMetaData)[],\n hasRegistrations: true,\n user: null as UserDBInterface | null,\n timeLimit: this.initialTimeLimit,\n courseLoadError: false,\n classroomLoadError: false,\n expandedFilters: {} as Record<string, boolean>,\n };\n },\n\n computed: {\n hasSelectedSources(): boolean {\n return this.activeCourses.some((c) => c.selected) || this.activeClasses.some((c) => c.selected);\n },\n\n hasAnyActiveFilter(): boolean {\n return this.activeCourses.some((c) => c.selected && this.hasActiveTagFilter(c));\n },\n\n activeFilterCount(): number {\n return this.activeCourses.filter((c) => c.selected && this.hasActiveTagFilter(c)).length;\n },\n },\n\n watch: {\n timeLimit: {\n handler() {\n if (this.timeLimit <= 0) {\n this.timeLimit = 1;\n }\n },\n },\n },\n\n beforeUnmount() {\n // Clean up registered hotkeys when component unmounts\n if (this.registeredHotkeys) {\n this.registeredHotkeys.forEach((key) => {\n SkldrMouseTrap.removeBinding(key);\n });\n }\n },\n\n async created() {\n this.user = await getCurrentUser();\n this.timeLimit = this.initialTimeLimit;\n\n this.setHotkeys();\n const [coursesResult, classroomsResult] = await Promise.allSettled([\n this.getActiveCourses(),\n this.getActiveClassrooms(),\n ]);\n\n // Handle course loading failure\n if (coursesResult.status === 'rejected') {\n console.error('Failed to load courses:', coursesResult.reason);\n this.courseLoadError = true;\n }\n\n // Handle classroom loading failure\n if (classroomsResult.status === 'rejected') {\n console.error('Failed to load classrooms:', classroomsResult.reason);\n this.classroomLoadError = true;\n }\n\n if (\n this.activeCourses.length === 0 &&\n this.activeClasses.length === 0 &&\n !this.courseLoadError &&\n !this.classroomLoadError\n ) {\n this.hasRegistrations = false;\n }\n },\n\n mounted() {\n document.getElementById('SelectAll')?.focus();\n },\n\n unmounted() {\n // Clean up registered hotkeys when component unmounts\n if (this.registeredHotkeys) {\n this.registeredHotkeys.forEach((key) => {\n SkldrMouseTrap.removeBinding(key);\n });\n }\n },\n\n methods: {\n inc() {\n this.timeLimit = this.timeLimit + 1;\n console.log(`inc to ${this.timeLimit}`);\n },\n\n dec() {\n this.timeLimit--;\n console.log(`dec to ${this.timeLimit}`);\n },\n\n update() {\n console.log(JSON.stringify(this.activeCourses));\n console.log(JSON.stringify(this.activeClasses));\n },\n\n toggleAll(): void {\n console.log(`Toggling all courses`);\n this.activeCourses.forEach((crs) => {\n crs.selected = this.allSelected;\n });\n this.activeClasses.forEach((cl) => {\n cl.selected = this.allSelected;\n });\n console.log(JSON.stringify(this.activeCourses));\n },\n\n toggleTagFilter(courseID: string): void {\n this.expandedFilters[courseID] = !this.expandedFilters[courseID];\n },\n\n hasActiveTagFilter(course: CourseRegistration & SessionConfigMetaData): boolean {\n return hasActiveFilter(course.tagFilter);\n },\n\n startSession() {\n // Clean up any registered hotkeys before starting session\n if (this.registeredHotkeys) {\n this.registeredHotkeys.forEach((key) => {\n SkldrMouseTrap.removeBinding(key);\n });\n }\n\n // Build sources with optional tag filters\n const selectedCourses: TagFilteredContentSourceID[] = this.activeCourses\n .filter((c) => c.selected)\n .map((c) => {\n const source: TagFilteredContentSourceID = {\n type: 'course',\n id: c.courseID,\n };\n // Only include tagFilter if it has active constraints\n if (hasActiveFilter(c.tagFilter)) {\n source.tagFilter = c.tagFilter;\n }\n return source;\n });\n\n const selectedClassrooms: ContentSourceID[] = this.activeClasses\n .filter((cl) => cl.selected)\n .map((cl) => ({\n type: 'classroom',\n id: cl.classID,\n }));\n\n const allSelectedSources = [...selectedCourses, ...selectedClassrooms];\n this.$emit('initStudySession', allSelectedSources, this.timeLimit);\n },\n\n async getActiveClassrooms() {\n const classes = await (await getCurrentUser()).getActiveClasses();\n const activeClasses: ({ classID: string } & SessionConfigMetaData)[] = [];\n\n console.log(`Active classes: ${JSON.stringify(classes)}`);\n\n await Promise.all(\n classes.map((c) =>\n (async (classID: string) => {\n const classDb = await getDataLayer().getClassroomDB(classID, `student`);\n activeClasses.push({\n classID,\n name: classDb.getConfig().name,\n selected: true,\n reviews: 0,\n });\n })(c)\n )\n );\n this.activeClasses = activeClasses;\n },\n\n async getActiveCourses() {\n this.activeCourses = (await this.user!.getActiveCourses()).map((c) => ({\n ...c,\n selected: true,\n name: '',\n reviews: 0,\n tagFilter: undefined,\n }));\n\n Promise.all(\n this.activeCourses.map(async (c, i) => {\n const cfg = await getDataLayer().getCoursesDB().getCourseConfig(c.courseID);\n const crsInterface = await this.user?.getCourseInterface(c.courseID);\n return (async () => {\n return Promise.all([\n (this.activeCourses[i].name = cfg.name),\n (this.activeCourses[i].reviews = await crsInterface!.getScheduledReviewCount()),\n ]);\n })();\n })\n );\n },\n\n setHotkeys() {\n const hotkeys = [\n {\n hotkey: 'right',\n callback: () => {\n this.timeLimit++;\n },\n command: 'Increase time limit',\n },\n {\n hotkey: 'left',\n callback: () => {\n this.timeLimit--;\n },\n command: 'Decrease time limit',\n },\n ];\n SkldrMouseTrap.addBinding(hotkeys);\n this.registeredHotkeys = hotkeys.map((k) => k.hotkey);\n },\n },\n});\n</script>\n\n<style scoped>\n/* Layout for session configuration */\n.session-layout {\n display: flex;\n flex-direction: column;\n}\n\n/* Fixed controls container */\n.fixed-controls-container {\n width: 100%;\n margin-bottom: 20px;\n}\n\n.fixed-controls {\n display: flex;\n flex-direction: column;\n}\n\n.time-limit-field {\n width: 100%;\n margin-bottom: 16px;\n}\n\n.start-btn {\n margin-top: 8px;\n max-height: 150px;\n}\n\n/* Course selection styles */\n.course-selection-container {\n width: 100%;\n}\n\n.course-row {\n border-bottom: 1px solid rgba(var(--v-border-color), 0.12);\n padding: 8px 0;\n}\n\n.course-row-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 16px;\n}\n\n.course-row-actions {\n display: flex;\n align-items: center;\n gap: 12px;\n}\n\n.reviews-count {\n min-width: 32px;\n text-align: center;\n color: rgba(var(--v-theme-on-surface), 0.6);\n}\n\n.tag-filter-container {\n padding: 12px 0 12px 32px;\n background-color: rgba(var(--v-theme-surface-variant), 0.3);\n border-radius: 4px;\n margin-top: 8px;\n}\n\n.select-all-row {\n padding-top: 8px;\n border-top: 2px solid rgba(var(--v-border-color), 0.24);\n}\n\n.filter-summary {\n display: flex;\n align-items: center;\n padding: 8px 12px;\n background-color: rgba(var(--v-theme-primary), 0.08);\n border-radius: 4px;\n}\n\n/* Media queries for desktop layout */\n@media (min-width: 960px) {\n .session-layout {\n flex-direction: row;\n gap: 40px;\n }\n\n .fixed-controls-container {\n width: 300px;\n flex-shrink: 0;\n }\n\n .fixed-controls {\n position: sticky;\n top: 20px;\n padding-left: 20px;\n }\n\n .course-selection-container {\n flex-grow: 1;\n border-right: 1px solid rgba(0, 0, 0, 0.12);\n padding-right: 20px;\n }\n}\n</style>\n","<template>\n <div v-if=\"!inSession\">\n <SessionConfiguration\n :initial-time-limit=\"sessionTimeLimit\"\n @init-study-session=\"(sources, timeLimit) => initStudySession(sources, timeLimit)\"\n />\n </div>\n <div v-else>\n <div v-if=\"previewMode && previewCourseConfig\" align=\"center\">\n <v-row>\n <v-col>\n <span class=\"text-h5\">\n Quilt preview for <em>{{ previewCourseConfig.name }}</em>\n </span>\n <v-btn size=\"small\" color=\"primary\" @click=\"registerUserForPreviewCourse\">Join</v-btn>\n <router-link :to=\"`/quilts/${previewCourseConfig.courseID}`\">\n <v-btn size=\"small\" color=\"secondary\">More info</v-btn>\n </router-link>\n <v-spacer></v-spacer>\n </v-col>\n </v-row>\n </div>\n <div v-else-if=\"previewMode\">\n <v-row>\n <v-col>\n <span class=\"text-h5\">... No course was specified for the preview.</span>\n <div>(this shouldn't happen)...</div>\n </v-col>\n </v-row>\n </div>\n\n <!-- Study Session Component (may be in loading state) -->\n <div v-if=\"inSession\">\n <!-- Loading indicator while session is being prepared -->\n <div v-if=\"!sessionPrepared && !sessionError\" class=\"session-loading\">\n <v-container class=\"text-center\">\n <v-row justify=\"center\" align=\"center\" style=\"min-height: 50vh\">\n <v-col cols=\"12\">\n <v-progress-circular size=\"70\" width=\"7\" color=\"primary\" indeterminate></v-progress-circular>\n <div class=\"text-h5 mt-4\">Preparing your study session...</div>\n <div class=\"text-subtitle-1 mt-2\">Getting your learning materials ready</div>\n </v-col>\n </v-row>\n </v-container>\n </div>\n\n <!-- Error state -->\n <div v-if=\"sessionError\" class=\"session-error\">\n <v-container class=\"text-center\">\n <v-row justify=\"center\" align=\"center\" style=\"min-height: 50vh\">\n <v-col cols=\"12\">\n <v-icon size=\"64\" color=\"error\">mdi-alert-circle</v-icon>\n <div class=\"text-h5 mt-4 text-error\">Session Preparation Failed</div>\n <div class=\"text-subtitle-1 mt-2\">\n {{ errorMessage || 'There was a problem preparing your study session.' }}\n </div>\n <v-btn color=\"primary\" class=\"mt-6\" @click=\"refreshRoute\">Try Again</v-btn>\n </v-col>\n </v-row>\n </v-container>\n </div>\n\n <StudySession\n :content-sources=\"sessionContentSources\"\n :session-time-limit=\"sessionTimeLimit\"\n :user=\"user as UserDBInterface\"\n :session-config=\"studySessionConfig\"\n :data-layer=\"dataLayer\"\n :get-view-component=\"getViewComponent\"\n :class=\"{ 'hidden-session': !sessionPrepared }\"\n @session-finished=\"handleSessionFinished\"\n @session-prepared=\"handleSessionPrepared\"\n @session-error=\"handleSessionError\"\n />\n </div>\n </div>\n</template>\n\n<script lang=\"ts\">\nimport SessionConfiguration from '@pui/components/Study/SessionConfiguration.vue';\nimport { getCurrentUser, useConfigStore } from '@vue-skuilder/common-ui';\nimport { useDataInputFormStore } from '@vue-skuilder/edit-ui';\nimport { CourseConfig, TagFilter, hasActiveFilter } from '@vue-skuilder/common';\nimport { StudySession, type StudySessionConfig } from '@vue-skuilder/common-ui';\nimport { allCourseWare } from '@vue-skuilder/courseware';\nimport { ContentSourceID, UserDBInterface, getDataLayer } from '@vue-skuilder/db';\nimport { defineComponent } from 'vue';\nimport { Router } from 'vue-router';\n\nfunction randomInt(min: number, max: number): number {\n return Math.floor(Math.random() * (max - min + 1)) + min;\n}\n\nexport default defineComponent({\n name: 'StudyView',\n\n components: {\n SessionConfiguration,\n StudySession,\n },\n\n inject: {\n router: {\n from: 'router',\n },\n },\n\n props: {\n /**\n * If present, user will engage in a study session for the specified (non-registered) course.\n */\n previewCourseID: {\n type: String,\n required: false,\n default: '',\n },\n /**\n * If true, the user will engage in a study session for a\n * random (public) course they are not already registered for.\n */\n randomPreview: {\n type: Boolean,\n required: false,\n },\n /**\n * If present, user will engage in a study session for the specified (registered) course.\n */\n focusCourseID: {\n type: String,\n required: false,\n default: '',\n },\n },\n\n data() {\n return {\n user: null as UserDBInterface | null,\n studySessionConfig: undefined as StudySessionConfig | undefined,\n previewCourseConfig: undefined as CourseConfig | undefined,\n previewMode: false,\n sessionTimeLimit: 5,\n inSession: false,\n sessionPrepared: false,\n sessionError: false,\n errorMessage: '',\n sessionContentSources: [] as ContentSourceID[],\n dataInputFormStore: useDataInputFormStore(),\n getViewComponent: (view_id: string) => allCourseWare.getView(view_id),\n dataLayer: getDataLayer(),\n };\n },\n\n async created() {\n this.user = await getCurrentUser();\n this.studySessionConfig = {\n likesConfetti: useConfigStore().config.likesConfetti,\n };\n\n let singletonStudyCourseID = '';\n\n if (this.randomPreview) {\n const userCourseRegDoc = await this.user.getCourseRegistrationsDoc();\n const allCourseWare = (await getDataLayer().getCoursesDB().getCourseList()).map((r) => r.courseID);\n const unRegisteredCourses = allCourseWare.filter((c) => {\n return !userCourseRegDoc.courses.some((rc) => rc.courseID === c);\n });\n if (unRegisteredCourses.length > 0) {\n singletonStudyCourseID = unRegisteredCourses[randomInt(0, unRegisteredCourses.length)]!;\n } else {\n singletonStudyCourseID = allCourseWare[randomInt(0, allCourseWare.length)]!;\n }\n }\n\n if (this.previewCourseID) {\n this.previewMode = true;\n getDataLayer()\n .getCoursesDB()\n .getCourseList()\n .then((courses) => {\n courses.forEach((c) => {\n if (c.courseID === this.previewCourseID) {\n this.previewCourseConfig = c;\n this.previewCourseConfig!.courseID = c.courseID;\n }\n });\n });\n\n console.log(`[Study] COURSE PREVIEW MODE FOR ${this.previewCourseID}`);\n await this.user!.registerForCourse(this.previewCourseID, true);\n\n singletonStudyCourseID = this.previewCourseID;\n }\n\n if (this.focusCourseID) {\n console.log(`[Study] FOCUS study session: ${this.focusCourseID}`);\n singletonStudyCourseID = this.focusCourseID;\n }\n\n if (singletonStudyCourseID) {\n // Parse tag filter from query params\n const tagFilter = this.parseTagFilterFromQuery();\n const source: ContentSourceID = {\n type: 'course',\n id: singletonStudyCourseID,\n };\n\n if (hasActiveFilter(tagFilter)) {\n source.tagFilter = tagFilter;\n console.log(`[Study] Tag filter from query params:`, tagFilter);\n }\n\n this.initStudySession([source], this.sessionTimeLimit);\n }\n },\n\n methods: {\n refreshRoute() {\n (this.router as Router).go(0);\n },\n\n async initStudySession(sources: ContentSourceID[], timeLimit: number) {\n console.log(`[Study] starting study session w/ sources: ${JSON.stringify(sources)}`);\n\n this.sessionContentSources = sources;\n this.sessionTimeLimit = timeLimit;\n this.inSession = true;\n this.sessionPrepared = false;\n\n // Adding a console log to debug event handling\n console.log('[Study] Waiting for session-prepared event from StudySession component');\n },\n\n registerUserForPreviewCourse() {\n this.user!.registerForCourse(this.previewCourseConfig!.courseID!).then(() =>\n (this.router as Router).push(`/quilts/${this.previewCourseConfig!.courseID!}`)\n );\n },\n\n handleSessionFinished() {\n this.refreshRoute();\n },\n\n handleSessionPrepared() {\n console.log('[Study] Session preparation complete - received session-prepared event');\n this.sessionPrepared = true;\n this.sessionError = false;\n this.errorMessage = '';\n },\n\n handleSessionError({ message, error }) {\n console.error('[Study] Session error:', message, error);\n this.sessionError = true;\n this.errorMessage = message || 'An error occurred while preparing your study session.';\n this.sessionPrepared = false;\n },\n\n /**\n * Parse tag filter from URL query parameters.\n *\n * Supports:\n * - ?include=tagA,tagB&exclude=tagC\n * - Comma-separated values for multiple tags\n *\n * @returns TagFilter parsed from query, or empty filter if no params\n */\n parseTagFilterFromQuery(): TagFilter {\n const includeParam = this.$route.query.include;\n const excludeParam = this.$route.query.exclude;\n\n const parseParam = (param: string | string[] | undefined | null): string[] => {\n if (!param) return [];\n if (Array.isArray(param)) {\n // Handle repeated params: ?include=a&include=b\n return param.flatMap((p) => (p ? p.split(',').map((t) => t.trim()) : [])).filter(Boolean);\n }\n // Handle comma-separated: ?include=a,b\n return param\n .split(',')\n .map((t) => t.trim())\n .filter(Boolean);\n };\n\n return {\n include: parseParam(includeParam as string | string[] | undefined),\n exclude: parseParam(excludeParam as string | string[] | undefined),\n };\n },\n },\n});\n</script>\n\n<style scoped>\n.hidden-session {\n visibility: hidden;\n position: absolute;\n z-index: -1;\n}\n\n.session-error {\n color: var(--v-error-base);\n}\n</style>\n","<template>\n <div v-if=\"!inSession\">\n <SessionConfiguration\n :initial-time-limit=\"sessionTimeLimit\"\n @init-study-session=\"(sources, timeLimit) => initStudySession(sources, timeLimit)\"\n />\n </div>\n <div v-else>\n <div v-if=\"previewMode && previewCourseConfig\" align=\"center\">\n <v-row>\n <v-col>\n <span class=\"text-h5\">\n Quilt preview for <em>{{ previewCourseConfig.name }}</em>\n </span>\n <v-btn size=\"small\" color=\"primary\" @click=\"registerUserForPreviewCourse\">Join</v-btn>\n <router-link :to=\"`/quilts/${previewCourseConfig.courseID}`\">\n <v-btn size=\"small\" color=\"secondary\">More info</v-btn>\n </router-link>\n <v-spacer></v-spacer>\n </v-col>\n </v-row>\n </div>\n <div v-else-if=\"previewMode\">\n <v-row>\n <v-col>\n <span class=\"text-h5\">... No course was specified for the preview.</span>\n <div>(this shouldn't happen)...</div>\n </v-col>\n </v-row>\n </div>\n\n <!-- Study Session Component (may be in loading state) -->\n <div v-if=\"inSession\">\n <!-- Loading indicator while session is being prepared -->\n <div v-if=\"!sessionPrepared && !sessionError\" class=\"session-loading\">\n <v-container class=\"text-center\">\n <v-row justify=\"center\" align=\"center\" style=\"min-height: 50vh\">\n <v-col cols=\"12\">\n <v-progress-circular size=\"70\" width=\"7\" color=\"primary\" indeterminate></v-progress-circular>\n <div class=\"text-h5 mt-4\">Preparing your study session...</div>\n <div class=\"text-subtitle-1 mt-2\">Getting your learning materials ready</div>\n </v-col>\n </v-row>\n </v-container>\n </div>\n\n <!-- Error state -->\n <div v-if=\"sessionError\" class=\"session-error\">\n <v-container class=\"text-center\">\n <v-row justify=\"center\" align=\"center\" style=\"min-height: 50vh\">\n <v-col cols=\"12\">\n <v-icon size=\"64\" color=\"error\">mdi-alert-circle</v-icon>\n <div class=\"text-h5 mt-4 text-error\">Session Preparation Failed</div>\n <div class=\"text-subtitle-1 mt-2\">\n {{ errorMessage || 'There was a problem preparing your study session.' }}\n </div>\n <v-btn color=\"primary\" class=\"mt-6\" @click=\"refreshRoute\">Try Again</v-btn>\n </v-col>\n </v-row>\n </v-container>\n </div>\n\n <StudySession\n :content-sources=\"sessionContentSources\"\n :session-time-limit=\"sessionTimeLimit\"\n :user=\"user as UserDBInterface\"\n :session-config=\"studySessionConfig\"\n :data-layer=\"dataLayer\"\n :get-view-component=\"getViewComponent\"\n :class=\"{ 'hidden-session': !sessionPrepared }\"\n @session-finished=\"handleSessionFinished\"\n @session-prepared=\"handleSessionPrepared\"\n @session-error=\"handleSessionError\"\n />\n </div>\n </div>\n</template>\n\n<script lang=\"ts\">\nimport SessionConfiguration from '@pui/components/Study/SessionConfiguration.vue';\nimport { getCurrentUser, useConfigStore } from '@vue-skuilder/common-ui';\nimport { useDataInputFormStore } from '@vue-skuilder/edit-ui';\nimport { CourseConfig, TagFilter, hasActiveFilter } from '@vue-skuilder/common';\nimport { StudySession, type StudySessionConfig } from '@vue-skuilder/common-ui';\nimport { allCourseWare } from '@vue-skuilder/courseware';\nimport { ContentSourceID, UserDBInterface, getDataLayer } from '@vue-skuilder/db';\nimport { defineComponent } from 'vue';\nimport { Router } from 'vue-router';\n\nfunction randomInt(min: number, max: number): number {\n return Math.floor(Math.random() * (max - min + 1)) + min;\n}\n\nexport default defineComponent({\n name: 'StudyView',\n\n components: {\n SessionConfiguration,\n StudySession,\n },\n\n inject: {\n router: {\n from: 'router',\n },\n },\n\n props: {\n /**\n * If present, user will engage in a study session for the specified (non-registered) course.\n */\n previewCourseID: {\n type: String,\n required: false,\n default: '',\n },\n /**\n * If true, the user will engage in a study session for a\n * random (public) course they are not already registered for.\n */\n randomPreview: {\n type: Boolean,\n required: false,\n },\n /**\n * If present, user will engage in a study session for the specified (registered) course.\n */\n focusCourseID: {\n type: String,\n required: false,\n default: '',\n },\n },\n\n data() {\n return {\n user: null as UserDBInterface | null,\n studySessionConfig: undefined as StudySessionConfig | undefined,\n previewCourseConfig: undefined as CourseConfig | undefined,\n previewMode: false,\n sessionTimeLimit: 5,\n inSession: false,\n sessionPrepared: false,\n sessionError: false,\n errorMessage: '',\n sessionContentSources: [] as ContentSourceID[],\n dataInputFormStore: useDataInputFormStore(),\n getViewComponent: (view_id: string) => allCourseWare.getView(view_id),\n dataLayer: getDataLayer(),\n };\n },\n\n async created() {\n this.user = await getCurrentUser();\n this.studySessionConfig = {\n likesConfetti: useConfigStore().config.likesConfetti,\n };\n\n let singletonStudyCourseID = '';\n\n if (this.randomPreview) {\n const userCourseRegDoc = await this.user.getCourseRegistrationsDoc();\n const allCourseWare = (await getDataLayer().getCoursesDB().getCourseList()).map((r) => r.courseID);\n const unRegisteredCourses = allCourseWare.filter((c) => {\n return !userCourseRegDoc.courses.some((rc) => rc.courseID === c);\n });\n if (unRegisteredCourses.length > 0) {\n singletonStudyCourseID = unRegisteredCourses[randomInt(0, unRegisteredCourses.length)]!;\n } else {\n singletonStudyCourseID = allCourseWare[randomInt(0, allCourseWare.length)]!;\n }\n }\n\n if (this.previewCourseID) {\n this.previewMode = true;\n getDataLayer()\n .getCoursesDB()\n .getCourseList()\n .then((courses) => {\n courses.forEach((c) => {\n if (c.courseID === this.previewCourseID) {\n this.previewCourseConfig = c;\n this.previewCourseConfig!.courseID = c.courseID;\n }\n });\n });\n\n console.log(`[Study] COURSE PREVIEW MODE FOR ${this.previewCourseID}`);\n await this.user!.registerForCourse(this.previewCourseID, true);\n\n singletonStudyCourseID = this.previewCourseID;\n }\n\n if (this.focusCourseID) {\n console.log(`[Study] FOCUS study session: ${this.focusCourseID}`);\n singletonStudyCourseID = this.focusCourseID;\n }\n\n if (singletonStudyCourseID) {\n // Parse tag filter from query params\n const tagFilter = this.parseTagFilterFromQuery();\n const source: ContentSourceID = {\n type: 'course',\n id: singletonStudyCourseID,\n };\n\n if (hasActiveFilter(tagFilter)) {\n source.tagFilter = tagFilter;\n console.log(`[Study] Tag filter from query params:`, tagFilter);\n }\n\n this.initStudySession([source], this.sessionTimeLimit);\n }\n },\n\n methods: {\n refreshRoute() {\n (this.router as Router).go(0);\n },\n\n async initStudySession(sources: ContentSourceID[], timeLimit: number) {\n console.log(`[Study] starting study session w/ sources: ${JSON.stringify(sources)}`);\n\n this.sessionContentSources = sources;\n this.sessionTimeLimit = timeLimit;\n this.inSession = true;\n this.sessionPrepared = false;\n\n // Adding a console log to debug event handling\n console.log('[Study] Waiting for session-prepared event from StudySession component');\n },\n\n registerUserForPreviewCourse() {\n this.user!.registerForCourse(this.previewCourseConfig!.courseID!).then(() =>\n (this.router as Router).push(`/quilts/${this.previewCourseConfig!.courseID!}`)\n );\n },\n\n handleSessionFinished() {\n this.refreshRoute();\n },\n\n handleSessionPrepared() {\n console.log('[Study] Session preparation complete - received session-prepared event');\n this.sessionPrepared = true;\n this.sessionError = false;\n this.errorMessage = '';\n },\n\n handleSessionError({ message, error }) {\n console.error('[Study] Session error:', message, error);\n this.sessionError = true;\n this.errorMessage = message || 'An error occurred while preparing your study session.';\n this.sessionPrepared = false;\n },\n\n /**\n * Parse tag filter from URL query parameters.\n *\n * Supports:\n * - ?include=tagA,tagB&exclude=tagC\n * - Comma-separated values for multiple tags\n *\n * @returns TagFilter parsed from query, or empty filter if no params\n */\n parseTagFilterFromQuery(): TagFilter {\n const includeParam = this.$route.query.include;\n const excludeParam = this.$route.query.exclude;\n\n const parseParam = (param: string | string[] | undefined | null): string[] => {\n if (!param) return [];\n if (Array.isArray(param)) {\n // Handle repeated params: ?include=a&include=b\n return param.flatMap((p) => (p ? p.split(',').map((t) => t.trim()) : [])).filter(Boolean);\n }\n // Handle comma-separated: ?include=a,b\n return param\n .split(',')\n .map((t) => t.trim())\n .filter(Boolean);\n };\n\n return {\n include: parseParam(includeParam as string | string[] | undefined),\n exclude: parseParam(excludeParam as string | string[] | undefined),\n };\n },\n },\n});\n</script>\n\n<style scoped>\n.hidden-session {\n visibility: hidden;\n position: absolute;\n z-index: -1;\n}\n\n.session-error {\n color: var(--v-error-base);\n}\n</style>\n"],"mappings":"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"}
1
+ {"version":3,"file":"Study-DDsGQCTJ.js","names":[],"sources":["../../src/components/Study/SessionConfiguration.vue","../../src/components/Study/SessionConfiguration.vue","../../src/views/Study.vue","../../src/views/Study.vue"],"sourcesContent":["<template>\n <div v-if=\"hasRegistrations || courseLoadError || classroomLoadError\">\n <div data-cy=\"select-quilts-header\" class=\"text-h4 mb-4\">Study Session Setup</div>\n\n <!-- Error Messages -->\n <v-alert v-if=\"courseLoadError\" type=\"warning\" class=\"mb-4\">\n Unable to load course data. Some features may be unavailable.\n </v-alert>\n\n <v-alert v-if=\"classroomLoadError\" type=\"warning\" class=\"mb-4\">\n Unable to load classroom data. You can still study individual courses.\n </v-alert>\n\n <div class=\"session-layout\">\n <!-- Left Column: Course Selection -->\n <div class=\"course-selection-container\">\n <div class=\"text-h6 mb-3\">Select Quilts to Study</div>\n\n <!-- Classrooms -->\n <div v-for=\"classroom in activeClasses\" :key=\"classroom.classID\" class=\"course-row\">\n <div class=\"course-row-header\">\n <v-checkbox\n v-model=\"classroom.selected\"\n :label=\"`Class: ${classroom.name}`\"\n hide-details\n @click.capture=\"update\"\n />\n <span class=\"reviews-count\">-</span>\n </div>\n </div>\n\n <!-- Courses -->\n <div v-for=\"course in activeCourses\" :key=\"course.courseID\" class=\"course-row\">\n <div class=\"course-row-header\">\n <v-checkbox\n v-model=\"course.selected\"\n data-cy=\"course-checkbox\"\n :label=\"`q/${course.name}`\"\n hide-details\n @click.capture=\"update\"\n />\n <div class=\"course-row-actions\">\n <span class=\"reviews-count\">{{ course.reviews }}</span>\n <v-btn\n v-if=\"course.selected\"\n variant=\"text\"\n size=\"small\"\n :color=\"hasActiveTagFilter(course) ? 'primary' : 'default'\"\n @click=\"toggleTagFilter(course.courseID)\"\n >\n <v-icon start size=\"small\">mdi-filter-variant</v-icon>\n {{ hasActiveTagFilter(course) ? 'Filtered' : 'Filter' }}\n <v-icon end size=\"small\">\n {{ expandedFilters[course.courseID] ? 'mdi-chevron-up' : 'mdi-chevron-down' }}\n </v-icon>\n </v-btn>\n </div>\n </div>\n\n <!-- Tag Filter Widget (expandable) -->\n <v-expand-transition>\n <div v-if=\"course.selected && expandedFilters[course.courseID]\" class=\"tag-filter-container\">\n <CourseTagFilterWidget v-model=\"course.tagFilter\" :course-id=\"course.courseID\" />\n </div>\n </v-expand-transition>\n </div>\n\n <!-- Select All -->\n <div class=\"select-all-row mt-3\">\n <v-checkbox\n id=\"SelectAll\"\n ref=\"selectAll\"\n v-model=\"allSelected\"\n autofocus\n label=\"Select All\"\n hide-details\n @update:model-value=\"toggleAll\"\n ></v-checkbox>\n </div>\n </div>\n\n <!-- Right Column: Time Configuration and Start Button -->\n <div class=\"fixed-controls-container\">\n <div class=\"fixed-controls\">\n <div class=\"text-h6 mb-3\">Session Settings</div>\n\n <div class=\"mb-5\">\n <v-text-field\n ref=\"numberField\"\n v-model=\"timeLimit\"\n class=\"time-limit-field\"\n variant=\"outlined\"\n label=\"Study Session Timelimit\"\n prepend-inner-icon=\"mdi-clock-outline\"\n prepend-icon=\"mdi-minus\"\n append-icon=\"mdi-plus\"\n :suffix=\"timeLimit > 1 ? 'minutes' : 'minute'\"\n mask=\"##\"\n type=\"number\"\n @click:prepend=\"dec\"\n @click:append=\"inc\"\n />\n </div>\n\n <!-- Filter Summary -->\n <div v-if=\"hasAnyActiveFilter\" class=\"filter-summary mb-4\">\n <v-icon size=\"small\" color=\"primary\" class=\"mr-1\">mdi-filter</v-icon>\n <span class=\"text-caption\">\n {{ activeFilterCount }} course{{ activeFilterCount > 1 ? 's' : '' }} with tag filters\n </span>\n </div>\n\n <SkMouseTrapToolTip\n hotkey=\"enter\"\n command=\"Start Session\"\n :disabled=\"!hasSelectedSources\"\n highlight-effect=\"scale\"\n >\n <v-btn\n data-cy=\"start-studying-button\"\n color=\"success\"\n size=\"large\"\n block\n class=\"start-btn\"\n :disabled=\"!hasSelectedSources\"\n @click=\"startSession\"\n >\n <v-icon start>mdi-play</v-icon>\n Start!\n </v-btn>\n </SkMouseTrapToolTip>\n </div>\n </div>\n </div>\n </div>\n <div v-else-if=\"!courseLoadError && !classroomLoadError\" class=\"text-h4\">\n <p>You don't have anything to study!</p>\n <p>Head over to the <router-link to=\"/quilts\">Quilts</router-link> page to find something for you.</p>\n </div>\n <div v-else class=\"text-h4\">\n <p>Unable to load study data due to technical issues.</p>\n <p>Please try refreshing the page or contact support if the problem persists.</p>\n </div>\n</template>\n\n<script lang=\"ts\">\nimport { defineComponent } from 'vue';\nimport { SkldrMouseTrap, getCurrentUser, SkMouseTrapToolTip, CourseTagFilterWidget } from '@vue-skuilder/common-ui';\nimport { CourseRegistration, UserDBInterface, getDataLayer, ContentSourceID } from '@vue-skuilder/db';\nimport { TagFilter, hasActiveFilter } from '@vue-skuilder/common';\n\nexport interface SessionConfigMetaData {\n selected: boolean;\n name: string;\n reviews: number;\n tagFilter?: TagFilter;\n}\n\n// Extended ContentSourceID that can carry tag filter configuration\nexport interface TagFilteredContentSourceID extends ContentSourceID {\n tagFilter?: TagFilter;\n}\n\nexport default defineComponent({\n name: 'SessionConfiguration',\n\n components: {\n SkMouseTrapToolTip,\n CourseTagFilterWidget,\n },\n\n props: {\n initialTimeLimit: {\n type: Number,\n required: true,\n default: 5,\n },\n },\n\n emits: ['initStudySession'],\n\n data() {\n return {\n registeredHotkeys: [] as (string | string[])[],\n allSelected: true,\n activeCourses: [] as (CourseRegistration & SessionConfigMetaData)[],\n activeClasses: [] as ({ classID: string } & SessionConfigMetaData)[],\n hasRegistrations: true,\n user: null as UserDBInterface | null,\n timeLimit: this.initialTimeLimit,\n courseLoadError: false,\n classroomLoadError: false,\n expandedFilters: {} as Record<string, boolean>,\n };\n },\n\n computed: {\n hasSelectedSources(): boolean {\n return this.activeCourses.some((c) => c.selected) || this.activeClasses.some((c) => c.selected);\n },\n\n hasAnyActiveFilter(): boolean {\n return this.activeCourses.some((c) => c.selected && this.hasActiveTagFilter(c));\n },\n\n activeFilterCount(): number {\n return this.activeCourses.filter((c) => c.selected && this.hasActiveTagFilter(c)).length;\n },\n },\n\n watch: {\n timeLimit: {\n handler() {\n if (this.timeLimit <= 0) {\n this.timeLimit = 1;\n }\n },\n },\n },\n\n beforeUnmount() {\n // Clean up registered hotkeys when component unmounts\n if (this.registeredHotkeys) {\n this.registeredHotkeys.forEach((key) => {\n SkldrMouseTrap.removeBinding(key);\n });\n }\n },\n\n async created() {\n this.user = await getCurrentUser();\n this.timeLimit = this.initialTimeLimit;\n\n this.setHotkeys();\n const [coursesResult, classroomsResult] = await Promise.allSettled([\n this.getActiveCourses(),\n this.getActiveClassrooms(),\n ]);\n\n // Handle course loading failure\n if (coursesResult.status === 'rejected') {\n console.error('Failed to load courses:', coursesResult.reason);\n this.courseLoadError = true;\n }\n\n // Handle classroom loading failure\n if (classroomsResult.status === 'rejected') {\n console.error('Failed to load classrooms:', classroomsResult.reason);\n this.classroomLoadError = true;\n }\n\n if (\n this.activeCourses.length === 0 &&\n this.activeClasses.length === 0 &&\n !this.courseLoadError &&\n !this.classroomLoadError\n ) {\n this.hasRegistrations = false;\n }\n },\n\n mounted() {\n document.getElementById('SelectAll')?.focus();\n },\n\n unmounted() {\n // Clean up registered hotkeys when component unmounts\n if (this.registeredHotkeys) {\n this.registeredHotkeys.forEach((key) => {\n SkldrMouseTrap.removeBinding(key);\n });\n }\n },\n\n methods: {\n inc() {\n this.timeLimit = this.timeLimit + 1;\n console.log(`inc to ${this.timeLimit}`);\n },\n\n dec() {\n this.timeLimit--;\n console.log(`dec to ${this.timeLimit}`);\n },\n\n update() {\n console.log(JSON.stringify(this.activeCourses));\n console.log(JSON.stringify(this.activeClasses));\n },\n\n toggleAll(): void {\n console.log(`Toggling all courses`);\n this.activeCourses.forEach((crs) => {\n crs.selected = this.allSelected;\n });\n this.activeClasses.forEach((cl) => {\n cl.selected = this.allSelected;\n });\n console.log(JSON.stringify(this.activeCourses));\n },\n\n toggleTagFilter(courseID: string): void {\n this.expandedFilters[courseID] = !this.expandedFilters[courseID];\n },\n\n hasActiveTagFilter(course: CourseRegistration & SessionConfigMetaData): boolean {\n return hasActiveFilter(course.tagFilter);\n },\n\n startSession() {\n // Clean up any registered hotkeys before starting session\n if (this.registeredHotkeys) {\n this.registeredHotkeys.forEach((key) => {\n SkldrMouseTrap.removeBinding(key);\n });\n }\n\n // Build sources with optional tag filters\n const selectedCourses: TagFilteredContentSourceID[] = this.activeCourses\n .filter((c) => c.selected)\n .map((c) => {\n const source: TagFilteredContentSourceID = {\n type: 'course',\n id: c.courseID,\n };\n // Only include tagFilter if it has active constraints\n if (hasActiveFilter(c.tagFilter)) {\n source.tagFilter = c.tagFilter;\n }\n return source;\n });\n\n const selectedClassrooms: ContentSourceID[] = this.activeClasses\n .filter((cl) => cl.selected)\n .map((cl) => ({\n type: 'classroom',\n id: cl.classID,\n }));\n\n const allSelectedSources = [...selectedCourses, ...selectedClassrooms];\n this.$emit('initStudySession', allSelectedSources, this.timeLimit);\n },\n\n async getActiveClassrooms() {\n const classes = await (await getCurrentUser()).getActiveClasses();\n const activeClasses: ({ classID: string } & SessionConfigMetaData)[] = [];\n\n console.log(`Active classes: ${JSON.stringify(classes)}`);\n\n await Promise.all(\n classes.map((c) =>\n (async (classID: string) => {\n const classDb = await getDataLayer().getClassroomDB(classID, `student`);\n activeClasses.push({\n classID,\n name: classDb.getConfig().name,\n selected: true,\n reviews: 0,\n });\n })(c)\n )\n );\n this.activeClasses = activeClasses;\n },\n\n async getActiveCourses() {\n this.activeCourses = (await this.user!.getActiveCourses()).map((c) => ({\n ...c,\n selected: true,\n name: '',\n reviews: 0,\n tagFilter: undefined,\n }));\n\n Promise.all(\n this.activeCourses.map(async (c, i) => {\n const cfg = await getDataLayer().getCoursesDB().getCourseConfig(c.courseID);\n const crsInterface = await this.user?.getCourseInterface(c.courseID);\n return (async () => {\n return Promise.all([\n (this.activeCourses[i].name = cfg.name),\n (this.activeCourses[i].reviews = await crsInterface!.getScheduledReviewCount()),\n ]);\n })();\n })\n );\n },\n\n setHotkeys() {\n const hotkeys = [\n {\n hotkey: 'right',\n callback: () => {\n this.timeLimit++;\n },\n command: 'Increase time limit',\n },\n {\n hotkey: 'left',\n callback: () => {\n this.timeLimit--;\n },\n command: 'Decrease time limit',\n },\n ];\n SkldrMouseTrap.addBinding(hotkeys);\n this.registeredHotkeys = hotkeys.map((k) => k.hotkey);\n },\n },\n});\n</script>\n\n<style scoped>\n/* Layout for session configuration */\n.session-layout {\n display: flex;\n flex-direction: column;\n}\n\n/* Fixed controls container */\n.fixed-controls-container {\n width: 100%;\n margin-bottom: 20px;\n}\n\n.fixed-controls {\n display: flex;\n flex-direction: column;\n}\n\n.time-limit-field {\n width: 100%;\n margin-bottom: 16px;\n}\n\n.start-btn {\n margin-top: 8px;\n max-height: 150px;\n}\n\n/* Course selection styles */\n.course-selection-container {\n width: 100%;\n}\n\n.course-row {\n border-bottom: 1px solid rgba(var(--v-border-color), 0.12);\n padding: 8px 0;\n}\n\n.course-row-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 16px;\n}\n\n.course-row-actions {\n display: flex;\n align-items: center;\n gap: 12px;\n}\n\n.reviews-count {\n min-width: 32px;\n text-align: center;\n color: rgba(var(--v-theme-on-surface), 0.6);\n}\n\n.tag-filter-container {\n padding: 12px 0 12px 32px;\n background-color: rgba(var(--v-theme-surface-variant), 0.3);\n border-radius: 4px;\n margin-top: 8px;\n}\n\n.select-all-row {\n padding-top: 8px;\n border-top: 2px solid rgba(var(--v-border-color), 0.24);\n}\n\n.filter-summary {\n display: flex;\n align-items: center;\n padding: 8px 12px;\n background-color: rgba(var(--v-theme-primary), 0.08);\n border-radius: 4px;\n}\n\n/* Media queries for desktop layout */\n@media (min-width: 960px) {\n .session-layout {\n flex-direction: row;\n gap: 40px;\n }\n\n .fixed-controls-container {\n width: 300px;\n flex-shrink: 0;\n }\n\n .fixed-controls {\n position: sticky;\n top: 20px;\n padding-left: 20px;\n }\n\n .course-selection-container {\n flex-grow: 1;\n border-right: 1px solid rgba(0, 0, 0, 0.12);\n padding-right: 20px;\n }\n}\n</style>\n","<template>\n <div v-if=\"hasRegistrations || courseLoadError || classroomLoadError\">\n <div data-cy=\"select-quilts-header\" class=\"text-h4 mb-4\">Study Session Setup</div>\n\n <!-- Error Messages -->\n <v-alert v-if=\"courseLoadError\" type=\"warning\" class=\"mb-4\">\n Unable to load course data. Some features may be unavailable.\n </v-alert>\n\n <v-alert v-if=\"classroomLoadError\" type=\"warning\" class=\"mb-4\">\n Unable to load classroom data. You can still study individual courses.\n </v-alert>\n\n <div class=\"session-layout\">\n <!-- Left Column: Course Selection -->\n <div class=\"course-selection-container\">\n <div class=\"text-h6 mb-3\">Select Quilts to Study</div>\n\n <!-- Classrooms -->\n <div v-for=\"classroom in activeClasses\" :key=\"classroom.classID\" class=\"course-row\">\n <div class=\"course-row-header\">\n <v-checkbox\n v-model=\"classroom.selected\"\n :label=\"`Class: ${classroom.name}`\"\n hide-details\n @click.capture=\"update\"\n />\n <span class=\"reviews-count\">-</span>\n </div>\n </div>\n\n <!-- Courses -->\n <div v-for=\"course in activeCourses\" :key=\"course.courseID\" class=\"course-row\">\n <div class=\"course-row-header\">\n <v-checkbox\n v-model=\"course.selected\"\n data-cy=\"course-checkbox\"\n :label=\"`q/${course.name}`\"\n hide-details\n @click.capture=\"update\"\n />\n <div class=\"course-row-actions\">\n <span class=\"reviews-count\">{{ course.reviews }}</span>\n <v-btn\n v-if=\"course.selected\"\n variant=\"text\"\n size=\"small\"\n :color=\"hasActiveTagFilter(course) ? 'primary' : 'default'\"\n @click=\"toggleTagFilter(course.courseID)\"\n >\n <v-icon start size=\"small\">mdi-filter-variant</v-icon>\n {{ hasActiveTagFilter(course) ? 'Filtered' : 'Filter' }}\n <v-icon end size=\"small\">\n {{ expandedFilters[course.courseID] ? 'mdi-chevron-up' : 'mdi-chevron-down' }}\n </v-icon>\n </v-btn>\n </div>\n </div>\n\n <!-- Tag Filter Widget (expandable) -->\n <v-expand-transition>\n <div v-if=\"course.selected && expandedFilters[course.courseID]\" class=\"tag-filter-container\">\n <CourseTagFilterWidget v-model=\"course.tagFilter\" :course-id=\"course.courseID\" />\n </div>\n </v-expand-transition>\n </div>\n\n <!-- Select All -->\n <div class=\"select-all-row mt-3\">\n <v-checkbox\n id=\"SelectAll\"\n ref=\"selectAll\"\n v-model=\"allSelected\"\n autofocus\n label=\"Select All\"\n hide-details\n @update:model-value=\"toggleAll\"\n ></v-checkbox>\n </div>\n </div>\n\n <!-- Right Column: Time Configuration and Start Button -->\n <div class=\"fixed-controls-container\">\n <div class=\"fixed-controls\">\n <div class=\"text-h6 mb-3\">Session Settings</div>\n\n <div class=\"mb-5\">\n <v-text-field\n ref=\"numberField\"\n v-model=\"timeLimit\"\n class=\"time-limit-field\"\n variant=\"outlined\"\n label=\"Study Session Timelimit\"\n prepend-inner-icon=\"mdi-clock-outline\"\n prepend-icon=\"mdi-minus\"\n append-icon=\"mdi-plus\"\n :suffix=\"timeLimit > 1 ? 'minutes' : 'minute'\"\n mask=\"##\"\n type=\"number\"\n @click:prepend=\"dec\"\n @click:append=\"inc\"\n />\n </div>\n\n <!-- Filter Summary -->\n <div v-if=\"hasAnyActiveFilter\" class=\"filter-summary mb-4\">\n <v-icon size=\"small\" color=\"primary\" class=\"mr-1\">mdi-filter</v-icon>\n <span class=\"text-caption\">\n {{ activeFilterCount }} course{{ activeFilterCount > 1 ? 's' : '' }} with tag filters\n </span>\n </div>\n\n <SkMouseTrapToolTip\n hotkey=\"enter\"\n command=\"Start Session\"\n :disabled=\"!hasSelectedSources\"\n highlight-effect=\"scale\"\n >\n <v-btn\n data-cy=\"start-studying-button\"\n color=\"success\"\n size=\"large\"\n block\n class=\"start-btn\"\n :disabled=\"!hasSelectedSources\"\n @click=\"startSession\"\n >\n <v-icon start>mdi-play</v-icon>\n Start!\n </v-btn>\n </SkMouseTrapToolTip>\n </div>\n </div>\n </div>\n </div>\n <div v-else-if=\"!courseLoadError && !classroomLoadError\" class=\"text-h4\">\n <p>You don't have anything to study!</p>\n <p>Head over to the <router-link to=\"/quilts\">Quilts</router-link> page to find something for you.</p>\n </div>\n <div v-else class=\"text-h4\">\n <p>Unable to load study data due to technical issues.</p>\n <p>Please try refreshing the page or contact support if the problem persists.</p>\n </div>\n</template>\n\n<script lang=\"ts\">\nimport { defineComponent } from 'vue';\nimport { SkldrMouseTrap, getCurrentUser, SkMouseTrapToolTip, CourseTagFilterWidget } from '@vue-skuilder/common-ui';\nimport { CourseRegistration, UserDBInterface, getDataLayer, ContentSourceID } from '@vue-skuilder/db';\nimport { TagFilter, hasActiveFilter } from '@vue-skuilder/common';\n\nexport interface SessionConfigMetaData {\n selected: boolean;\n name: string;\n reviews: number;\n tagFilter?: TagFilter;\n}\n\n// Extended ContentSourceID that can carry tag filter configuration\nexport interface TagFilteredContentSourceID extends ContentSourceID {\n tagFilter?: TagFilter;\n}\n\nexport default defineComponent({\n name: 'SessionConfiguration',\n\n components: {\n SkMouseTrapToolTip,\n CourseTagFilterWidget,\n },\n\n props: {\n initialTimeLimit: {\n type: Number,\n required: true,\n default: 5,\n },\n },\n\n emits: ['initStudySession'],\n\n data() {\n return {\n registeredHotkeys: [] as (string | string[])[],\n allSelected: true,\n activeCourses: [] as (CourseRegistration & SessionConfigMetaData)[],\n activeClasses: [] as ({ classID: string } & SessionConfigMetaData)[],\n hasRegistrations: true,\n user: null as UserDBInterface | null,\n timeLimit: this.initialTimeLimit,\n courseLoadError: false,\n classroomLoadError: false,\n expandedFilters: {} as Record<string, boolean>,\n };\n },\n\n computed: {\n hasSelectedSources(): boolean {\n return this.activeCourses.some((c) => c.selected) || this.activeClasses.some((c) => c.selected);\n },\n\n hasAnyActiveFilter(): boolean {\n return this.activeCourses.some((c) => c.selected && this.hasActiveTagFilter(c));\n },\n\n activeFilterCount(): number {\n return this.activeCourses.filter((c) => c.selected && this.hasActiveTagFilter(c)).length;\n },\n },\n\n watch: {\n timeLimit: {\n handler() {\n if (this.timeLimit <= 0) {\n this.timeLimit = 1;\n }\n },\n },\n },\n\n beforeUnmount() {\n // Clean up registered hotkeys when component unmounts\n if (this.registeredHotkeys) {\n this.registeredHotkeys.forEach((key) => {\n SkldrMouseTrap.removeBinding(key);\n });\n }\n },\n\n async created() {\n this.user = await getCurrentUser();\n this.timeLimit = this.initialTimeLimit;\n\n this.setHotkeys();\n const [coursesResult, classroomsResult] = await Promise.allSettled([\n this.getActiveCourses(),\n this.getActiveClassrooms(),\n ]);\n\n // Handle course loading failure\n if (coursesResult.status === 'rejected') {\n console.error('Failed to load courses:', coursesResult.reason);\n this.courseLoadError = true;\n }\n\n // Handle classroom loading failure\n if (classroomsResult.status === 'rejected') {\n console.error('Failed to load classrooms:', classroomsResult.reason);\n this.classroomLoadError = true;\n }\n\n if (\n this.activeCourses.length === 0 &&\n this.activeClasses.length === 0 &&\n !this.courseLoadError &&\n !this.classroomLoadError\n ) {\n this.hasRegistrations = false;\n }\n },\n\n mounted() {\n document.getElementById('SelectAll')?.focus();\n },\n\n unmounted() {\n // Clean up registered hotkeys when component unmounts\n if (this.registeredHotkeys) {\n this.registeredHotkeys.forEach((key) => {\n SkldrMouseTrap.removeBinding(key);\n });\n }\n },\n\n methods: {\n inc() {\n this.timeLimit = this.timeLimit + 1;\n console.log(`inc to ${this.timeLimit}`);\n },\n\n dec() {\n this.timeLimit--;\n console.log(`dec to ${this.timeLimit}`);\n },\n\n update() {\n console.log(JSON.stringify(this.activeCourses));\n console.log(JSON.stringify(this.activeClasses));\n },\n\n toggleAll(): void {\n console.log(`Toggling all courses`);\n this.activeCourses.forEach((crs) => {\n crs.selected = this.allSelected;\n });\n this.activeClasses.forEach((cl) => {\n cl.selected = this.allSelected;\n });\n console.log(JSON.stringify(this.activeCourses));\n },\n\n toggleTagFilter(courseID: string): void {\n this.expandedFilters[courseID] = !this.expandedFilters[courseID];\n },\n\n hasActiveTagFilter(course: CourseRegistration & SessionConfigMetaData): boolean {\n return hasActiveFilter(course.tagFilter);\n },\n\n startSession() {\n // Clean up any registered hotkeys before starting session\n if (this.registeredHotkeys) {\n this.registeredHotkeys.forEach((key) => {\n SkldrMouseTrap.removeBinding(key);\n });\n }\n\n // Build sources with optional tag filters\n const selectedCourses: TagFilteredContentSourceID[] = this.activeCourses\n .filter((c) => c.selected)\n .map((c) => {\n const source: TagFilteredContentSourceID = {\n type: 'course',\n id: c.courseID,\n };\n // Only include tagFilter if it has active constraints\n if (hasActiveFilter(c.tagFilter)) {\n source.tagFilter = c.tagFilter;\n }\n return source;\n });\n\n const selectedClassrooms: ContentSourceID[] = this.activeClasses\n .filter((cl) => cl.selected)\n .map((cl) => ({\n type: 'classroom',\n id: cl.classID,\n }));\n\n const allSelectedSources = [...selectedCourses, ...selectedClassrooms];\n this.$emit('initStudySession', allSelectedSources, this.timeLimit);\n },\n\n async getActiveClassrooms() {\n const classes = await (await getCurrentUser()).getActiveClasses();\n const activeClasses: ({ classID: string } & SessionConfigMetaData)[] = [];\n\n console.log(`Active classes: ${JSON.stringify(classes)}`);\n\n await Promise.all(\n classes.map((c) =>\n (async (classID: string) => {\n const classDb = await getDataLayer().getClassroomDB(classID, `student`);\n activeClasses.push({\n classID,\n name: classDb.getConfig().name,\n selected: true,\n reviews: 0,\n });\n })(c)\n )\n );\n this.activeClasses = activeClasses;\n },\n\n async getActiveCourses() {\n this.activeCourses = (await this.user!.getActiveCourses()).map((c) => ({\n ...c,\n selected: true,\n name: '',\n reviews: 0,\n tagFilter: undefined,\n }));\n\n Promise.all(\n this.activeCourses.map(async (c, i) => {\n const cfg = await getDataLayer().getCoursesDB().getCourseConfig(c.courseID);\n const crsInterface = await this.user?.getCourseInterface(c.courseID);\n return (async () => {\n return Promise.all([\n (this.activeCourses[i].name = cfg.name),\n (this.activeCourses[i].reviews = await crsInterface!.getScheduledReviewCount()),\n ]);\n })();\n })\n );\n },\n\n setHotkeys() {\n const hotkeys = [\n {\n hotkey: 'right',\n callback: () => {\n this.timeLimit++;\n },\n command: 'Increase time limit',\n },\n {\n hotkey: 'left',\n callback: () => {\n this.timeLimit--;\n },\n command: 'Decrease time limit',\n },\n ];\n SkldrMouseTrap.addBinding(hotkeys);\n this.registeredHotkeys = hotkeys.map((k) => k.hotkey);\n },\n },\n});\n</script>\n\n<style scoped>\n/* Layout for session configuration */\n.session-layout {\n display: flex;\n flex-direction: column;\n}\n\n/* Fixed controls container */\n.fixed-controls-container {\n width: 100%;\n margin-bottom: 20px;\n}\n\n.fixed-controls {\n display: flex;\n flex-direction: column;\n}\n\n.time-limit-field {\n width: 100%;\n margin-bottom: 16px;\n}\n\n.start-btn {\n margin-top: 8px;\n max-height: 150px;\n}\n\n/* Course selection styles */\n.course-selection-container {\n width: 100%;\n}\n\n.course-row {\n border-bottom: 1px solid rgba(var(--v-border-color), 0.12);\n padding: 8px 0;\n}\n\n.course-row-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 16px;\n}\n\n.course-row-actions {\n display: flex;\n align-items: center;\n gap: 12px;\n}\n\n.reviews-count {\n min-width: 32px;\n text-align: center;\n color: rgba(var(--v-theme-on-surface), 0.6);\n}\n\n.tag-filter-container {\n padding: 12px 0 12px 32px;\n background-color: rgba(var(--v-theme-surface-variant), 0.3);\n border-radius: 4px;\n margin-top: 8px;\n}\n\n.select-all-row {\n padding-top: 8px;\n border-top: 2px solid rgba(var(--v-border-color), 0.24);\n}\n\n.filter-summary {\n display: flex;\n align-items: center;\n padding: 8px 12px;\n background-color: rgba(var(--v-theme-primary), 0.08);\n border-radius: 4px;\n}\n\n/* Media queries for desktop layout */\n@media (min-width: 960px) {\n .session-layout {\n flex-direction: row;\n gap: 40px;\n }\n\n .fixed-controls-container {\n width: 300px;\n flex-shrink: 0;\n }\n\n .fixed-controls {\n position: sticky;\n top: 20px;\n padding-left: 20px;\n }\n\n .course-selection-container {\n flex-grow: 1;\n border-right: 1px solid rgba(0, 0, 0, 0.12);\n padding-right: 20px;\n }\n}\n</style>\n","<template>\n <div v-if=\"!inSession\">\n <SessionConfiguration\n :initial-time-limit=\"sessionTimeLimit\"\n @init-study-session=\"(sources, timeLimit) => initStudySession(sources, timeLimit)\"\n />\n </div>\n <div v-else>\n <div v-if=\"previewMode && previewCourseConfig\" align=\"center\">\n <v-row>\n <v-col>\n <span class=\"text-h5\">\n Quilt preview for <em>{{ previewCourseConfig.name }}</em>\n </span>\n <v-btn size=\"small\" color=\"primary\" @click=\"registerUserForPreviewCourse\">Join</v-btn>\n <router-link :to=\"`/quilts/${previewCourseConfig.courseID}`\">\n <v-btn size=\"small\" color=\"secondary\">More info</v-btn>\n </router-link>\n <v-spacer></v-spacer>\n </v-col>\n </v-row>\n </div>\n <div v-else-if=\"previewMode\">\n <v-row>\n <v-col>\n <span class=\"text-h5\">... No course was specified for the preview.</span>\n <div>(this shouldn't happen)...</div>\n </v-col>\n </v-row>\n </div>\n\n <!-- Study Session Component (may be in loading state) -->\n <div v-if=\"inSession\">\n <!-- Loading indicator while session is being prepared -->\n <div v-if=\"!sessionPrepared && !sessionError\" class=\"session-loading\">\n <v-container class=\"text-center\">\n <v-row justify=\"center\" align=\"center\" style=\"min-height: 50vh\">\n <v-col cols=\"12\">\n <v-progress-circular size=\"70\" width=\"7\" color=\"primary\" indeterminate></v-progress-circular>\n <div class=\"text-h5 mt-4\">Preparing your study session...</div>\n <div class=\"text-subtitle-1 mt-2\">Getting your learning materials ready</div>\n </v-col>\n </v-row>\n </v-container>\n </div>\n\n <!-- Error state -->\n <div v-if=\"sessionError\" class=\"session-error\">\n <v-container class=\"text-center\">\n <v-row justify=\"center\" align=\"center\" style=\"min-height: 50vh\">\n <v-col cols=\"12\">\n <v-icon size=\"64\" color=\"error\">mdi-alert-circle</v-icon>\n <div class=\"text-h5 mt-4 text-error\">Session Preparation Failed</div>\n <div class=\"text-subtitle-1 mt-2\">\n {{ errorMessage || 'There was a problem preparing your study session.' }}\n </div>\n <v-btn color=\"primary\" class=\"mt-6\" @click=\"refreshRoute\">Try Again</v-btn>\n </v-col>\n </v-row>\n </v-container>\n </div>\n\n <StudySession\n :content-sources=\"sessionContentSources\"\n :session-time-limit=\"sessionTimeLimit\"\n :user=\"user as UserDBInterface\"\n :session-config=\"studySessionConfig\"\n :data-layer=\"dataLayer\"\n :get-view-component=\"getViewComponent\"\n :class=\"{ 'hidden-session': !sessionPrepared }\"\n @session-finished=\"handleSessionFinished\"\n @session-prepared=\"handleSessionPrepared\"\n @session-error=\"handleSessionError\"\n />\n </div>\n </div>\n</template>\n\n<script lang=\"ts\">\nimport SessionConfiguration from '@pui/components/Study/SessionConfiguration.vue';\nimport { getCurrentUser, useConfigStore } from '@vue-skuilder/common-ui';\nimport { useDataInputFormStore } from '@vue-skuilder/edit-ui';\nimport { CourseConfig, TagFilter, hasActiveFilter } from '@vue-skuilder/common';\nimport { StudySession, type StudySessionConfig } from '@vue-skuilder/common-ui';\nimport { allCourseWare } from '@vue-skuilder/courseware';\nimport { ContentSourceID, UserDBInterface, getDataLayer } from '@vue-skuilder/db';\nimport { defineComponent } from 'vue';\nimport { Router } from 'vue-router';\n\nfunction randomInt(min: number, max: number): number {\n return Math.floor(Math.random() * (max - min + 1)) + min;\n}\n\nexport default defineComponent({\n name: 'StudyView',\n\n components: {\n SessionConfiguration,\n StudySession,\n },\n\n inject: {\n router: {\n from: 'router',\n },\n },\n\n props: {\n /**\n * If present, user will engage in a study session for the specified (non-registered) course.\n */\n previewCourseID: {\n type: String,\n required: false,\n default: '',\n },\n /**\n * If true, the user will engage in a study session for a\n * random (public) course they are not already registered for.\n */\n randomPreview: {\n type: Boolean,\n required: false,\n },\n /**\n * If present, user will engage in a study session for the specified (registered) course.\n */\n focusCourseID: {\n type: String,\n required: false,\n default: '',\n },\n },\n\n data() {\n return {\n user: null as UserDBInterface | null,\n studySessionConfig: undefined as StudySessionConfig | undefined,\n previewCourseConfig: undefined as CourseConfig | undefined,\n previewMode: false,\n sessionTimeLimit: 5,\n inSession: false,\n sessionPrepared: false,\n sessionError: false,\n errorMessage: '',\n sessionContentSources: [] as ContentSourceID[],\n dataInputFormStore: useDataInputFormStore(),\n getViewComponent: (view_id: string) => allCourseWare.getView(view_id),\n dataLayer: getDataLayer(),\n };\n },\n\n async created() {\n this.user = await getCurrentUser();\n this.studySessionConfig = {\n likesConfetti: useConfigStore().config.likesConfetti,\n };\n\n let singletonStudyCourseID = '';\n\n if (this.randomPreview) {\n const userCourseRegDoc = await this.user.getCourseRegistrationsDoc();\n const allCourseWare = (await getDataLayer().getCoursesDB().getCourseList()).map((r) => r.courseID);\n const unRegisteredCourses = allCourseWare.filter((c) => {\n return !userCourseRegDoc.courses.some((rc) => rc.courseID === c);\n });\n if (unRegisteredCourses.length > 0) {\n singletonStudyCourseID = unRegisteredCourses[randomInt(0, unRegisteredCourses.length)]!;\n } else {\n singletonStudyCourseID = allCourseWare[randomInt(0, allCourseWare.length)]!;\n }\n }\n\n if (this.previewCourseID) {\n this.previewMode = true;\n getDataLayer()\n .getCoursesDB()\n .getCourseList()\n .then((courses) => {\n courses.forEach((c) => {\n if (c.courseID === this.previewCourseID) {\n this.previewCourseConfig = c;\n this.previewCourseConfig!.courseID = c.courseID;\n }\n });\n });\n\n console.log(`[Study] COURSE PREVIEW MODE FOR ${this.previewCourseID}`);\n await this.user!.registerForCourse(this.previewCourseID, true);\n\n singletonStudyCourseID = this.previewCourseID;\n }\n\n if (this.focusCourseID) {\n console.log(`[Study] FOCUS study session: ${this.focusCourseID}`);\n singletonStudyCourseID = this.focusCourseID;\n }\n\n if (singletonStudyCourseID) {\n // Parse tag filter from query params\n const tagFilter = this.parseTagFilterFromQuery();\n const source: ContentSourceID = {\n type: 'course',\n id: singletonStudyCourseID,\n };\n\n if (hasActiveFilter(tagFilter)) {\n source.tagFilter = tagFilter;\n console.log(`[Study] Tag filter from query params:`, tagFilter);\n }\n\n this.initStudySession([source], this.sessionTimeLimit);\n }\n },\n\n methods: {\n refreshRoute() {\n (this.router as Router).go(0);\n },\n\n async initStudySession(sources: ContentSourceID[], timeLimit: number) {\n console.log(`[Study] starting study session w/ sources: ${JSON.stringify(sources)}`);\n\n this.sessionContentSources = sources;\n this.sessionTimeLimit = timeLimit;\n this.inSession = true;\n this.sessionPrepared = false;\n\n // Adding a console log to debug event handling\n console.log('[Study] Waiting for session-prepared event from StudySession component');\n },\n\n registerUserForPreviewCourse() {\n this.user!.registerForCourse(this.previewCourseConfig!.courseID!).then(() =>\n (this.router as Router).push(`/quilts/${this.previewCourseConfig!.courseID!}`)\n );\n },\n\n handleSessionFinished() {\n this.refreshRoute();\n },\n\n handleSessionPrepared() {\n console.log('[Study] Session preparation complete - received session-prepared event');\n this.sessionPrepared = true;\n this.sessionError = false;\n this.errorMessage = '';\n },\n\n handleSessionError({ message, error }) {\n console.error('[Study] Session error:', message, error);\n this.sessionError = true;\n this.errorMessage = message || 'An error occurred while preparing your study session.';\n this.sessionPrepared = false;\n },\n\n /**\n * Parse tag filter from URL query parameters.\n *\n * Supports:\n * - ?include=tagA,tagB&exclude=tagC\n * - Comma-separated values for multiple tags\n *\n * @returns TagFilter parsed from query, or empty filter if no params\n */\n parseTagFilterFromQuery(): TagFilter {\n const includeParam = this.$route.query.include;\n const excludeParam = this.$route.query.exclude;\n\n const parseParam = (param: string | string[] | undefined | null): string[] => {\n if (!param) return [];\n if (Array.isArray(param)) {\n // Handle repeated params: ?include=a&include=b\n return param.flatMap((p) => (p ? p.split(',').map((t) => t.trim()) : [])).filter(Boolean);\n }\n // Handle comma-separated: ?include=a,b\n return param\n .split(',')\n .map((t) => t.trim())\n .filter(Boolean);\n };\n\n return {\n include: parseParam(includeParam as string | string[] | undefined),\n exclude: parseParam(excludeParam as string | string[] | undefined),\n };\n },\n },\n});\n</script>\n\n<style scoped>\n.hidden-session {\n visibility: hidden;\n position: absolute;\n z-index: -1;\n}\n\n.session-error {\n color: var(--v-error-base);\n}\n</style>\n","<template>\n <div v-if=\"!inSession\">\n <SessionConfiguration\n :initial-time-limit=\"sessionTimeLimit\"\n @init-study-session=\"(sources, timeLimit) => initStudySession(sources, timeLimit)\"\n />\n </div>\n <div v-else>\n <div v-if=\"previewMode && previewCourseConfig\" align=\"center\">\n <v-row>\n <v-col>\n <span class=\"text-h5\">\n Quilt preview for <em>{{ previewCourseConfig.name }}</em>\n </span>\n <v-btn size=\"small\" color=\"primary\" @click=\"registerUserForPreviewCourse\">Join</v-btn>\n <router-link :to=\"`/quilts/${previewCourseConfig.courseID}`\">\n <v-btn size=\"small\" color=\"secondary\">More info</v-btn>\n </router-link>\n <v-spacer></v-spacer>\n </v-col>\n </v-row>\n </div>\n <div v-else-if=\"previewMode\">\n <v-row>\n <v-col>\n <span class=\"text-h5\">... No course was specified for the preview.</span>\n <div>(this shouldn't happen)...</div>\n </v-col>\n </v-row>\n </div>\n\n <!-- Study Session Component (may be in loading state) -->\n <div v-if=\"inSession\">\n <!-- Loading indicator while session is being prepared -->\n <div v-if=\"!sessionPrepared && !sessionError\" class=\"session-loading\">\n <v-container class=\"text-center\">\n <v-row justify=\"center\" align=\"center\" style=\"min-height: 50vh\">\n <v-col cols=\"12\">\n <v-progress-circular size=\"70\" width=\"7\" color=\"primary\" indeterminate></v-progress-circular>\n <div class=\"text-h5 mt-4\">Preparing your study session...</div>\n <div class=\"text-subtitle-1 mt-2\">Getting your learning materials ready</div>\n </v-col>\n </v-row>\n </v-container>\n </div>\n\n <!-- Error state -->\n <div v-if=\"sessionError\" class=\"session-error\">\n <v-container class=\"text-center\">\n <v-row justify=\"center\" align=\"center\" style=\"min-height: 50vh\">\n <v-col cols=\"12\">\n <v-icon size=\"64\" color=\"error\">mdi-alert-circle</v-icon>\n <div class=\"text-h5 mt-4 text-error\">Session Preparation Failed</div>\n <div class=\"text-subtitle-1 mt-2\">\n {{ errorMessage || 'There was a problem preparing your study session.' }}\n </div>\n <v-btn color=\"primary\" class=\"mt-6\" @click=\"refreshRoute\">Try Again</v-btn>\n </v-col>\n </v-row>\n </v-container>\n </div>\n\n <StudySession\n :content-sources=\"sessionContentSources\"\n :session-time-limit=\"sessionTimeLimit\"\n :user=\"user as UserDBInterface\"\n :session-config=\"studySessionConfig\"\n :data-layer=\"dataLayer\"\n :get-view-component=\"getViewComponent\"\n :class=\"{ 'hidden-session': !sessionPrepared }\"\n @session-finished=\"handleSessionFinished\"\n @session-prepared=\"handleSessionPrepared\"\n @session-error=\"handleSessionError\"\n />\n </div>\n </div>\n</template>\n\n<script lang=\"ts\">\nimport SessionConfiguration from '@pui/components/Study/SessionConfiguration.vue';\nimport { getCurrentUser, useConfigStore } from '@vue-skuilder/common-ui';\nimport { useDataInputFormStore } from '@vue-skuilder/edit-ui';\nimport { CourseConfig, TagFilter, hasActiveFilter } from '@vue-skuilder/common';\nimport { StudySession, type StudySessionConfig } from '@vue-skuilder/common-ui';\nimport { allCourseWare } from '@vue-skuilder/courseware';\nimport { ContentSourceID, UserDBInterface, getDataLayer } from '@vue-skuilder/db';\nimport { defineComponent } from 'vue';\nimport { Router } from 'vue-router';\n\nfunction randomInt(min: number, max: number): number {\n return Math.floor(Math.random() * (max - min + 1)) + min;\n}\n\nexport default defineComponent({\n name: 'StudyView',\n\n components: {\n SessionConfiguration,\n StudySession,\n },\n\n inject: {\n router: {\n from: 'router',\n },\n },\n\n props: {\n /**\n * If present, user will engage in a study session for the specified (non-registered) course.\n */\n previewCourseID: {\n type: String,\n required: false,\n default: '',\n },\n /**\n * If true, the user will engage in a study session for a\n * random (public) course they are not already registered for.\n */\n randomPreview: {\n type: Boolean,\n required: false,\n },\n /**\n * If present, user will engage in a study session for the specified (registered) course.\n */\n focusCourseID: {\n type: String,\n required: false,\n default: '',\n },\n },\n\n data() {\n return {\n user: null as UserDBInterface | null,\n studySessionConfig: undefined as StudySessionConfig | undefined,\n previewCourseConfig: undefined as CourseConfig | undefined,\n previewMode: false,\n sessionTimeLimit: 5,\n inSession: false,\n sessionPrepared: false,\n sessionError: false,\n errorMessage: '',\n sessionContentSources: [] as ContentSourceID[],\n dataInputFormStore: useDataInputFormStore(),\n getViewComponent: (view_id: string) => allCourseWare.getView(view_id),\n dataLayer: getDataLayer(),\n };\n },\n\n async created() {\n this.user = await getCurrentUser();\n this.studySessionConfig = {\n likesConfetti: useConfigStore().config.likesConfetti,\n };\n\n let singletonStudyCourseID = '';\n\n if (this.randomPreview) {\n const userCourseRegDoc = await this.user.getCourseRegistrationsDoc();\n const allCourseWare = (await getDataLayer().getCoursesDB().getCourseList()).map((r) => r.courseID);\n const unRegisteredCourses = allCourseWare.filter((c) => {\n return !userCourseRegDoc.courses.some((rc) => rc.courseID === c);\n });\n if (unRegisteredCourses.length > 0) {\n singletonStudyCourseID = unRegisteredCourses[randomInt(0, unRegisteredCourses.length)]!;\n } else {\n singletonStudyCourseID = allCourseWare[randomInt(0, allCourseWare.length)]!;\n }\n }\n\n if (this.previewCourseID) {\n this.previewMode = true;\n getDataLayer()\n .getCoursesDB()\n .getCourseList()\n .then((courses) => {\n courses.forEach((c) => {\n if (c.courseID === this.previewCourseID) {\n this.previewCourseConfig = c;\n this.previewCourseConfig!.courseID = c.courseID;\n }\n });\n });\n\n console.log(`[Study] COURSE PREVIEW MODE FOR ${this.previewCourseID}`);\n await this.user!.registerForCourse(this.previewCourseID, true);\n\n singletonStudyCourseID = this.previewCourseID;\n }\n\n if (this.focusCourseID) {\n console.log(`[Study] FOCUS study session: ${this.focusCourseID}`);\n singletonStudyCourseID = this.focusCourseID;\n }\n\n if (singletonStudyCourseID) {\n // Parse tag filter from query params\n const tagFilter = this.parseTagFilterFromQuery();\n const source: ContentSourceID = {\n type: 'course',\n id: singletonStudyCourseID,\n };\n\n if (hasActiveFilter(tagFilter)) {\n source.tagFilter = tagFilter;\n console.log(`[Study] Tag filter from query params:`, tagFilter);\n }\n\n this.initStudySession([source], this.sessionTimeLimit);\n }\n },\n\n methods: {\n refreshRoute() {\n (this.router as Router).go(0);\n },\n\n async initStudySession(sources: ContentSourceID[], timeLimit: number) {\n console.log(`[Study] starting study session w/ sources: ${JSON.stringify(sources)}`);\n\n this.sessionContentSources = sources;\n this.sessionTimeLimit = timeLimit;\n this.inSession = true;\n this.sessionPrepared = false;\n\n // Adding a console log to debug event handling\n console.log('[Study] Waiting for session-prepared event from StudySession component');\n },\n\n registerUserForPreviewCourse() {\n this.user!.registerForCourse(this.previewCourseConfig!.courseID!).then(() =>\n (this.router as Router).push(`/quilts/${this.previewCourseConfig!.courseID!}`)\n );\n },\n\n handleSessionFinished() {\n this.refreshRoute();\n },\n\n handleSessionPrepared() {\n console.log('[Study] Session preparation complete - received session-prepared event');\n this.sessionPrepared = true;\n this.sessionError = false;\n this.errorMessage = '';\n },\n\n handleSessionError({ message, error }) {\n console.error('[Study] Session error:', message, error);\n this.sessionError = true;\n this.errorMessage = message || 'An error occurred while preparing your study session.';\n this.sessionPrepared = false;\n },\n\n /**\n * Parse tag filter from URL query parameters.\n *\n * Supports:\n * - ?include=tagA,tagB&exclude=tagC\n * - Comma-separated values for multiple tags\n *\n * @returns TagFilter parsed from query, or empty filter if no params\n */\n parseTagFilterFromQuery(): TagFilter {\n const includeParam = this.$route.query.include;\n const excludeParam = this.$route.query.exclude;\n\n const parseParam = (param: string | string[] | undefined | null): string[] => {\n if (!param) return [];\n if (Array.isArray(param)) {\n // Handle repeated params: ?include=a&include=b\n return param.flatMap((p) => (p ? p.split(',').map((t) => t.trim()) : [])).filter(Boolean);\n }\n // Handle comma-separated: ?include=a,b\n return param\n .split(',')\n .map((t) => t.trim())\n .filter(Boolean);\n };\n\n return {\n include: parseParam(includeParam as string | string[] | undefined),\n exclude: parseParam(excludeParam as string | string[] | undefined),\n };\n },\n },\n});\n</script>\n\n<style scoped>\n.hidden-session {\n visibility: hidden;\n position: absolute;\n z-index: -1;\n}\n\n.session-error {\n color: var(--v-error-base);\n}\n</style>\n"],"mappings":"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"}
@@ -0,0 +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{a as d,z as f}from"./dist-BsRvt0gH.js";import{K as p}from"./contentSource-DwznMSMs.js";import"./dist-Dp6eBE2S.js";import"./CourseWare-BTFRjgBR-DBp_fIwH.js";import"./PipelineDebugger-BHaqzqZw.js";import{d as m,o as h}from"./userDBKeys-DYLGKnVC.js";import"./eloDistance-BV7vfiK5.js";import{t as g}from"./index-CK-rfSIx.js";import"./chess-E2uOAyQS-BZi0vihz.js";import"./math-D_8HiEQR-E4QDLrWp.js";import"./piano-DF1g6yaX-Ck3BH5EK.js";import"./sightsing-BFQ7HRig-DV69ELZf.js";import"./typing-BevtfWlp-CvStu5BY.js";import{t as _}from"./dist-CAHkbl3M.js";var v=o({name:`TagInformation`,components:{CourseCardBrowser:d},props:{tagId:{type:String,required:!0},courseId:{type:String,required:!0}},data(){return{snippetModel:``,editingSnippet:!1,snippetSaving:!1,wikiModel:``,editingWiki:!1,wikiSaving:!1,courseDB:null,tag:{course:this.courseId,name:this.tagId,snippet:``,wiki:``,taggedCards:[],docType:h.TAG},course:{courseID:this.courseId,name:``,description:``,public:!1,deleted:!1,dataShapes:[],questionTypes:[],creator:``,admins:[],moderators:[]}}},async created(){this.courseDB=m().getCourseDB(this.courseId),this.tag=await this.courseDB.getTag(this.tagId),this.snippetModel=this.tag.snippet,this.wikiModel=this.tag.wiki,this.course=await this.courseDB.getCourseConfig()},methods:{viewLookup(e){return _.getView(e)},editSnippet(){console.log(`[TagInformation] EditSnip`),this.editingSnippet=!0,this.$refs.snippetEditor.focus()},editWiki(){console.log(`[TagInformation] EditWiki`),this.editingWiki=!0,this.$refs.wikiEditor.focus()},async saveSnippet(){this.snippetSaving=!0,(await this.courseDB.updateTag({...this.tag,snippet:this.snippetModel})).ok?(console.log(`[TagInformation] OK`),this.tag.snippet=this.snippetModel,f({text:`Updated applied - thanks!`,status:p.ok})):f({text:`error in applying update!`,status:p.error}),this.editingSnippet=!1,this.snippetSaving=!1},async saveWiki(){this.wikiSaving=!0,(await this.courseDB.updateTag({...this.tag,wiki:this.wikiModel})).ok?(this.tag.wiki=this.wikiModel,f({text:`Updated applied - thanks!`,status:p.ok})):f({text:`error in applying update!`,status:p.error}),this.editingWiki=!1,this.wikiSaving=!1},cancelEditSnippet(){console.log(`[TagInformation] Cancelling EditSnip`),this.editingSnippet=!1,this.snippetModel=this.tag.snippet},cancelEditWiki(){this.editingWiki=!1,this.wikiModel=this.tag.wiki}}}),y={key:0},b={key:0};function _sfc_render(o,d,f,p,m,h){let g=i(`router-link`),_=i(`v-icon`),v=i(`v-progress-circular`),x=i(`v-fade-transition`),S=i(`v-text-field`),C=i(`course-card-browser`);return n(),c(`div`,null,[s(`h1`,null,[t(g,{to:`/q/${o.course.name}`},{default:e(()=>[a(r(o.course.name),1)]),_:1},8,[`to`]),a(` > Tag: `+r(o.tag.name),1)]),d[8]||(d[8]=s(`br`,null,null,-1)),s(`p`,null,r(o.tag.taggedCards.length)+` card`+r(o.tag.taggedCards.length===1?``:`s`),1),t(S,{ref:`snippetEditor`,modelValue:o.snippetModel,"onUpdate:modelValue":d[0]||(d[0]=e=>o.snippetModel=e),variant:`outlined`,readonly:!o.editingSnippet,counter:o.editingSnippet,label:`Brief tag description:`,placeholder:`No snippet yet - add one!`,type:`text`},{prepend:e(()=>[o.editingSnippet?(n(),c(`span`,y,[t(_,{color:`primary`,onClick:o.saveSnippet},{default:e(()=>d[2]||(d[2]=[a(`mdi-content-save`)])),_:1},8,[`onClick`])])):(n(),l(_,{key:1,color:`primary`,onClick:o.editSnippet},{default:e(()=>d[3]||(d[3]=[a(`mdi-pencil`)])),_:1},8,[`onClick`]))]),append:e(()=>[o.editingSnippet?(n(),l(_,{key:0,onClick:o.cancelEditSnippet},{default:e(()=>d[4]||(d[4]=[a(`mdi-cancel`)])),_:1},8,[`onClick`])):u(``,!0),t(x,{"leave-absolute":``},{default:e(()=>[o.snippetSaving?(n(),l(v,{key:0,size:`20`,color:`info`,indeterminate:``})):u(``,!0)]),_:1})]),_:1},8,[`modelValue`,`readonly`,`counter`]),t(S,{ref:`wikiEditor`,modelValue:o.wikiModel,"onUpdate:modelValue":d[1]||(d[1]=e=>o.wikiModel=e),variant:`outlined`,readonly:!o.editingWiki,counter:o.editingWiki,label:`Extended tag description:`,placeholder:`No wiki yet - consider adding one!`,textarea:``},{prepend:e(()=>[o.editingWiki?(n(),c(`span`,b,[t(_,{color:`primary`,onClick:o.saveWiki},{default:e(()=>d[5]||(d[5]=[a(`mdi-content-save`)])),_:1},8,[`onClick`])])):(n(),l(_,{key:1,color:`primary`,onClick:o.editWiki},{default:e(()=>d[6]||(d[6]=[a(`mdi-pencil`)])),_:1},8,[`onClick`]))]),append:e(()=>[o.editingWiki?(n(),l(_,{key:0,onClick:o.cancelEditWiki},{default:e(()=>d[7]||(d[7]=[a(`mdi-cancel`)])),_:1},8,[`onClick`])):u(``,!0),t(x,{"leave-absolute":``},{default:e(()=>[o.wikiSaving?(n(),l(v,{key:0,size:`20`,color:`info`,indeterminate:``})):u(``,!0)]),_:1})]),_:1},8,[`modelValue`,`readonly`,`counter`]),t(C,{"course-id":o.courseId,"tag-id":o.tagId,"view-lookup-function":o.viewLookup},null,8,[`course-id`,`tag-id`,`view-lookup-function`])])}var x=g(v,[[`render`,_sfc_render]]);export{x as default};
2
+ //# sourceMappingURL=TagInformation-DBLoXA2a.js.map