@vue-skuilder/platform-ui 0.2.36 → 0.2.37

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (146) hide show
  1. package/dist/assets/{About-74htcNLT.js → About-BkMENQtD.js} +2 -2
  2. package/dist/assets/{About-74htcNLT.js.map → About-BkMENQtD.js.map} +1 -1
  3. package/dist/assets/{AdminDashboard-B00qno2M.js → AdminDashboard-5R-xgY-M.js} +2 -2
  4. package/dist/assets/{AdminDashboard-B00qno2M.js.map → AdminDashboard-5R-xgY-M.js.map} +1 -1
  5. package/dist/assets/{AdminLearner-SJNtSyJ_.js → AdminLearner-Br10DFSt.js} +2 -2
  6. package/dist/assets/{AdminLearner-SJNtSyJ_.js.map → AdminLearner-Br10DFSt.js.map} +1 -1
  7. package/dist/assets/BaseUserDB-BRgNfdWx.js +1 -0
  8. package/dist/assets/{ClassroomCtrlPanel-BSAG3h8B.js → ClassroomCtrlPanel-DQJQ0B7M.js} +2 -2
  9. package/dist/assets/{ClassroomCtrlPanel-BSAG3h8B.js.map → ClassroomCtrlPanel-DQJQ0B7M.js.map} +1 -1
  10. package/dist/assets/{Classrooms-DXNEbtsM.js → Classrooms-B5tQPFEE.js} +2 -2
  11. package/dist/assets/{Classrooms-DXNEbtsM.js.map → Classrooms-B5tQPFEE.js.map} +1 -1
  12. package/dist/assets/CouchDBSyncStrategy-0EgX8AAz.js +1 -0
  13. package/dist/assets/{CourseRouter-CsVa_92r.js → CourseRouter-W6S7DpZN.js} +2 -2
  14. package/dist/assets/{CourseRouter-CsVa_92r.js.map → CourseRouter-W6S7DpZN.js.map} +1 -1
  15. package/dist/assets/CourseSyncService-CrHp0wsl.js +1 -0
  16. package/dist/assets/{CourseWare-BTFRjgBR-DBp_fIwH.js → CourseWare-BTFRjgBR-lpqcYpAW.js} +3 -3
  17. package/dist/assets/{CourseWare-BTFRjgBR-DBp_fIwH.js.map → CourseWare-BTFRjgBR-lpqcYpAW.js.map} +1 -1
  18. package/dist/assets/{Courses-CB6GGtdm.js → Courses-TzwgFdwn.js} +2 -2
  19. package/dist/assets/{Courses-CB6GGtdm.js.map → Courses-TzwgFdwn.js.map} +1 -1
  20. package/dist/assets/{EloModeration-DUBgs-Vh.js → EloModeration-BCz7VKla.js} +2 -2
  21. package/dist/assets/{EloModeration-DUBgs-Vh.js.map → EloModeration-BCz7VKla.js.map} +1 -1
  22. package/dist/assets/{JoinCode-BBvYapCA.js → JoinCode-CEeZkFOp.js} +2 -2
  23. package/dist/assets/{JoinCode-BBvYapCA.js.map → JoinCode-CEeZkFOp.js.map} +1 -1
  24. package/dist/assets/{NewCourseDialog-DBgFVnt3.js → NewCourseDialog-DNzvvz_H.js} +2 -2
  25. package/dist/assets/{NewCourseDialog-DBgFVnt3.js.map → NewCourseDialog-DNzvvz_H.js.map} +1 -1
  26. package/dist/assets/{PipelineDebugger-BHaqzqZw.js → PipelineDebugger-DhnKlMgD.js} +3 -3
  27. package/dist/assets/{PipelineDebugger-BHaqzqZw.js.map → PipelineDebugger-DhnKlMgD.js.map} +1 -1
  28. package/dist/assets/PouchDataLayerProvider-CAuhZYIc.js +2 -0
  29. package/dist/assets/PouchDataLayerProvider-CAuhZYIc.js.map +1 -0
  30. package/dist/assets/{ReleaseNotes-Dg6mLL1C.js → ReleaseNotes-Bvw24QDI.js} +2 -2
  31. package/dist/assets/{ReleaseNotes-Dg6mLL1C.js.map → ReleaseNotes-Bvw24QDI.js.map} +1 -1
  32. package/dist/assets/{RequestPasswordReset-CcFMTnTc.js → RequestPasswordReset-YOq1JJ_o.js} +2 -2
  33. package/dist/assets/{RequestPasswordReset-CcFMTnTc.js.map → RequestPasswordReset-YOq1JJ_o.js.map} +1 -1
  34. package/dist/assets/{ResetPassword-Dw_cBJU2.js → ResetPassword-CQCb3B_Z.js} +2 -2
  35. package/dist/assets/{ResetPassword-Dw_cBJU2.js.map → ResetPassword-CQCb3B_Z.js.map} +1 -1
  36. package/dist/assets/{StaticDataLayerProvider-CtgU3VBh.js → StaticDataLayerProvider-DbGLKpb1.js} +2 -2
  37. package/dist/assets/{StaticDataLayerProvider-CtgU3VBh.js.map → StaticDataLayerProvider-DbGLKpb1.js.map} +1 -1
  38. package/dist/assets/{Study-DDsGQCTJ.js → Study-D2mXR27s.js} +2 -2
  39. package/dist/assets/{Study-DDsGQCTJ.js.map → Study-D2mXR27s.js.map} +1 -1
  40. package/dist/assets/{TagInformation-DBLoXA2a.js → TagInformation-DHw5jRNt.js} +2 -2
  41. package/dist/assets/{TagInformation-DBLoXA2a.js.map → TagInformation-DHw5jRNt.js.map} +1 -1
  42. package/dist/assets/{User-DfXUXjFb.js → User-C0xEcKaU.js} +2 -2
  43. package/dist/assets/{User-DfXUXjFb.js.map → User-C0xEcKaU.js.map} +1 -1
  44. package/dist/assets/{UserDBDebugger-DPqlc4O_.js → UserDBDebugger-BXLoT28w.js} +2 -2
  45. package/dist/assets/{UserDBDebugger-DPqlc4O_.js.map → UserDBDebugger-BXLoT28w.js.map} +1 -1
  46. package/dist/assets/UserDBDebugger-DHONc_-t.js +1 -0
  47. package/dist/assets/{UserStats-C04YVYv7.js → UserStats-Blm_daoT.js} +2 -2
  48. package/dist/assets/{UserStats-C04YVYv7.js.map → UserStats-Blm_daoT.js.map} +1 -1
  49. package/dist/assets/{VerifyEmail-Y05BSR57.js → VerifyEmail-DTwA6sFY.js} +2 -2
  50. package/dist/assets/{VerifyEmail-Y05BSR57.js.map → VerifyEmail-DTwA6sFY.js.map} +1 -1
  51. package/dist/assets/{admin-DcFjvYJw.js → admin-hzNSXp0o.js} +2 -2
  52. package/dist/assets/{admin-DcFjvYJw.js.map → admin-hzNSXp0o.js.map} +1 -1
  53. package/dist/assets/{chess-E2uOAyQS-BZi0vihz.js → chess-E2uOAyQS-DDTgqZMJ.js} +2 -2
  54. package/dist/assets/{chess-E2uOAyQS-BZi0vihz.js.map → chess-E2uOAyQS-DDTgqZMJ.js.map} +1 -1
  55. package/dist/assets/chess-Lfm7Fsvs-RaFhFnIh.js +1 -0
  56. package/dist/assets/{contentSource-DwznMSMs.js → contentSource-FGhM_ge8.js} +8 -8
  57. package/dist/assets/contentSource-FGhM_ge8.js.map +1 -0
  58. package/dist/assets/dist-5Z6GjIpR.js +1 -0
  59. package/dist/assets/{dist-CAHkbl3M.js → dist-BJLhyG8r.js} +3 -3
  60. package/dist/assets/{dist-CAHkbl3M.js.map → dist-BJLhyG8r.js.map} +1 -1
  61. package/dist/assets/dist-C5-_1l75.js +1 -0
  62. package/dist/assets/{dist-BsRvt0gH.js → dist-C8EQUxWW.js} +3 -3
  63. package/dist/assets/{dist-BsRvt0gH.js.map → dist-C8EQUxWW.js.map} +1 -1
  64. package/dist/assets/{dist-Dp6eBE2S.js → dist-DGbpjpKB.js} +2 -2
  65. package/dist/assets/{dist-Dp6eBE2S.js.map → dist-DGbpjpKB.js.map} +1 -1
  66. package/dist/assets/dist-M4o-5YLq.js +1 -0
  67. package/dist/assets/edit-ui.es-BT7ftsox.js +1 -0
  68. package/dist/assets/{edit-ui.es-UaQFL_My.js → edit-ui.es-DJgikaS7.js} +2 -2
  69. package/dist/assets/{edit-ui.es-UaQFL_My.js.map → edit-ui.es-DJgikaS7.js.map} +1 -1
  70. package/dist/assets/elo-5Z2qkwtw.js +1 -0
  71. package/dist/assets/{elo-COgu6Hu5.js → elo-C4ZdbMIJ.js} +2 -2
  72. package/dist/assets/{elo-COgu6Hu5.js.map → elo-C4ZdbMIJ.js.map} +1 -1
  73. package/dist/assets/{eloDistance-BV7vfiK5.js → eloDistance-CRxxdYPJ.js} +2 -2
  74. package/dist/assets/{eloDistance-BV7vfiK5.js.map → eloDistance-CRxxdYPJ.js.map} +1 -1
  75. package/dist/assets/eloDistance-DF4pmPOI.js +1 -0
  76. package/dist/assets/{french-Dk7YG8Td-CD7Hms2j.js → french-Dk7YG8Td-C2awfrzV.js} +2 -2
  77. package/dist/assets/{french-Dk7YG8Td-CD7Hms2j.js.map → french-Dk7YG8Td-C2awfrzV.js.map} +1 -1
  78. package/dist/assets/french-evUMlbbq-4izQyIk4.js +1 -0
  79. package/dist/assets/{hierarchyDefinition-DLC5DjaA.js → hierarchyDefinition-BbqnGEfR.js} +2 -2
  80. package/dist/assets/{hierarchyDefinition-DLC5DjaA.js.map → hierarchyDefinition-BbqnGEfR.js.map} +1 -1
  81. package/dist/assets/{index-CK-rfSIx.js → index-BRaeVAZb.js} +4 -4
  82. package/dist/assets/{index-CK-rfSIx.js.map → index-BRaeVAZb.js.map} +1 -1
  83. package/dist/assets/{interferenceMitigator-D78KgOxz.js → interferenceMitigator-BtAQm-0d.js} +2 -2
  84. package/dist/assets/{interferenceMitigator-D78KgOxz.js.map → interferenceMitigator-BtAQm-0d.js.map} +1 -1
  85. package/dist/assets/math-DR3fXDcX-Bj6dcLDF.js +1 -0
  86. package/dist/assets/{math-D_8HiEQR-E4QDLrWp.js → math-D_8HiEQR-CQynsoTm.js} +2 -2
  87. package/dist/assets/{math-D_8HiEQR-E4QDLrWp.js.map → math-D_8HiEQR-CQynsoTm.js.map} +1 -1
  88. package/dist/assets/piano-BN5Btq91-onBUnVgA.js +1 -0
  89. package/dist/assets/{piano-DF1g6yaX-Ck3BH5EK.js → piano-DF1g6yaX-vXbP1jX_.js} +2 -2
  90. package/dist/assets/{piano-DF1g6yaX-Ck3BH5EK.js.map → piano-DF1g6yaX-vXbP1jX_.js.map} +1 -1
  91. package/dist/assets/{pitch-CgGJFkZ1-Dd0LzmEe.js → pitch-CgGJFkZ1-DVzOpIVa.js} +2 -2
  92. package/dist/assets/{pitch-CgGJFkZ1-Dd0LzmEe.js.map → pitch-CgGJFkZ1-DVzOpIVa.js.map} +1 -1
  93. package/dist/assets/pitch-Dn0iNqiS-L2k4d66s.js +1 -0
  94. package/dist/assets/prescribed-Cjutx2ct.js +2 -0
  95. package/dist/assets/prescribed-Cjutx2ct.js.map +1 -0
  96. package/dist/assets/{relativePriority-DwPMECAp.js → relativePriority-fx6BMJm9.js} +2 -2
  97. package/dist/assets/{relativePriority-DwPMECAp.js.map → relativePriority-fx6BMJm9.js.map} +1 -1
  98. package/dist/assets/{server-CqR7xtQy.js → server-CUqWbXPL.js} +2 -2
  99. package/dist/assets/{server-CqR7xtQy.js.map → server-CUqWbXPL.js.map} +1 -1
  100. package/dist/assets/{sightsing-BFQ7HRig-DV69ELZf.js → sightsing-BFQ7HRig-CYuNcd8z.js} +2 -2
  101. package/dist/assets/{sightsing-BFQ7HRig-DV69ELZf.js.map → sightsing-BFQ7HRig-CYuNcd8z.js.map} +1 -1
  102. package/dist/assets/sightsing-CJX3k0Fd-C4-zGBSZ.js +1 -0
  103. package/dist/assets/{srs-DOsG9Y-i.js → srs-B3R2C3TU.js} +2 -2
  104. package/dist/assets/{srs-DOsG9Y-i.js.map → srs-B3R2C3TU.js.map} +1 -1
  105. package/dist/assets/srs-BD4FD2rw.js +1 -0
  106. package/dist/assets/{typing-BevtfWlp-CvStu5BY.js → typing-BevtfWlp-C0NbuTKI.js} +2 -2
  107. package/dist/assets/{typing-BevtfWlp-CvStu5BY.js.map → typing-BevtfWlp-C0NbuTKI.js.map} +1 -1
  108. package/dist/assets/typing-k-ojjO7G-gZjZ4YQx.js +1 -0
  109. package/dist/assets/{userDBKeys-DYLGKnVC.js → userDBKeys-3SQjNVdK.js} +4 -4
  110. package/dist/assets/{userDBKeys-DYLGKnVC.js.map → userDBKeys-3SQjNVdK.js.map} +1 -1
  111. package/dist/assets/{userTagPreference-cZCPqadC.js → userTagPreference-B23ZX5J4.js} +2 -2
  112. package/dist/assets/{userTagPreference-cZCPqadC.js.map → userTagPreference-B23ZX5J4.js.map} +1 -1
  113. package/dist/assets/validators-CnxSFY0I.js +2 -0
  114. package/dist/assets/{validators-lpYAHmRR.js.map → validators-CnxSFY0I.js.map} +1 -1
  115. package/dist/assets/{word-work-BOnRlZgd-CCGZdwyf.js → word-work-BOnRlZgd-CsMkeNdI.js} +2 -2
  116. package/dist/assets/{word-work-BOnRlZgd-CCGZdwyf.js.map → word-work-BOnRlZgd-CsMkeNdI.js.map} +1 -1
  117. package/dist/assets/word-work-Dd6tIKrt-WI45cKxO.js +1 -0
  118. package/dist/index.html +9 -9
  119. package/dist/sw.js +1 -1
  120. package/dist/sw.js.map +1 -1
  121. package/package.json +7 -7
  122. package/dist/assets/BaseUserDB-CV2ZPZOR.js +0 -1
  123. package/dist/assets/CouchDBSyncStrategy-BjM7ROXC.js +0 -1
  124. package/dist/assets/CourseSyncService-CPU_Ll1h.js +0 -1
  125. package/dist/assets/PouchDataLayerProvider-DxsnSIKd.js +0 -2
  126. package/dist/assets/PouchDataLayerProvider-DxsnSIKd.js.map +0 -1
  127. package/dist/assets/UserDBDebugger-xmycWjck.js +0 -1
  128. package/dist/assets/chess-Lfm7Fsvs-DgN08Qx9.js +0 -1
  129. package/dist/assets/contentSource-DwznMSMs.js.map +0 -1
  130. package/dist/assets/dist-BcwaOf54.js +0 -1
  131. package/dist/assets/dist-CgW7D_Gh.js +0 -1
  132. package/dist/assets/dist-Dnehy0sn.js +0 -1
  133. package/dist/assets/edit-ui.es-BWqvElsJ.js +0 -1
  134. package/dist/assets/elo-Cwa8a3Yo.js +0 -1
  135. package/dist/assets/eloDistance-MReQeAuj.js +0 -1
  136. package/dist/assets/french-evUMlbbq-nGCjilG6.js +0 -1
  137. package/dist/assets/math-DR3fXDcX-C9KfHLHP.js +0 -1
  138. package/dist/assets/piano-BN5Btq91-CnKhyLGE.js +0 -1
  139. package/dist/assets/pitch-Dn0iNqiS-BxAdpDDb.js +0 -1
  140. package/dist/assets/prescribed-BWO17Gan.js +0 -2
  141. package/dist/assets/prescribed-BWO17Gan.js.map +0 -1
  142. package/dist/assets/sightsing-CJX3k0Fd-CG94OiDr.js +0 -1
  143. package/dist/assets/srs-DGscV6W4.js +0 -1
  144. package/dist/assets/typing-k-ojjO7G-AF8vU_q7.js +0 -1
  145. package/dist/assets/validators-lpYAHmRR.js +0 -2
  146. package/dist/assets/word-work-Dd6tIKrt-BNTSCq5F.js +0 -1
@@ -1 +1 @@
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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"}
1
+ {"version":3,"file":"Study-D2mXR27s.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,2 +1,2 @@
1
- import"./chunk-D6hFPZc3.js";import{$ as e,C as t,H as n,Ht as r,K as i,S as a,T as o,_ as s,b as c,v as l,y as u}from"./vue.runtime.esm-bundler--Fxmd6l9.js";import"./MarkdownRenderer-borh39Lq.js";import"./moment-DbT30ZpG.js";import{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
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-C8EQUxWW.js";import{K as p}from"./contentSource-FGhM_ge8.js";import"./dist-DGbpjpKB.js";import"./CourseWare-BTFRjgBR-lpqcYpAW.js";import"./PipelineDebugger-DhnKlMgD.js";import{d as m,o as h}from"./userDBKeys-3SQjNVdK.js";import"./eloDistance-CRxxdYPJ.js";import{t as g}from"./index-BRaeVAZb.js";import"./chess-E2uOAyQS-DDTgqZMJ.js";import"./math-D_8HiEQR-CQynsoTm.js";import"./piano-DF1g6yaX-vXbP1jX_.js";import"./sightsing-BFQ7HRig-CYuNcd8z.js";import"./typing-BevtfWlp-C0NbuTKI.js";import{t as _}from"./dist-BJLhyG8r.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-DHw5jRNt.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"TagInformation-DBLoXA2a.js","names":[],"sources":["../../src/components/Courses/TagInformation.vue","../../src/components/Courses/TagInformation.vue"],"sourcesContent":["<template>\n <div>\n <!-- todo: -->\n <h1>\n <router-link :to=\"`/q/${course.name}`\">{{ course.name }}</router-link> > Tag: {{ tag.name }}\n </h1>\n <br />\n <p>{{ tag.taggedCards.length }} card{{ tag.taggedCards.length === 1 ? '' : 's' }}</p>\n\n <v-text-field\n ref=\"snippetEditor\"\n v-model=\"snippetModel\"\n variant=\"outlined\"\n :readonly=\"!editingSnippet\"\n :counter=\"editingSnippet\"\n label=\"Brief tag description:\"\n placeholder=\"No snippet yet - add one!\"\n type=\"text\"\n >\n <template #prepend>\n <span v-if=\"editingSnippet\">\n <v-icon color=\"primary\" @click=\"saveSnippet\">mdi-content-save</v-icon>\n </span>\n <v-icon v-else color=\"primary\" @click=\"editSnippet\">mdi-pencil</v-icon>\n </template>\n <template #append>\n <v-icon v-if=\"editingSnippet\" @click=\"cancelEditSnippet\">mdi-cancel</v-icon>\n <v-fade-transition leave-absolute>\n <!-- spinner while awaiting async write of edits -->\n <v-progress-circular v-if=\"snippetSaving\" size=\"20\" color=\"info\" indeterminate></v-progress-circular>\n </v-fade-transition>\n </template>\n </v-text-field>\n\n <v-text-field\n ref=\"wikiEditor\"\n v-model=\"wikiModel\"\n variant=\"outlined\"\n :readonly=\"!editingWiki\"\n :counter=\"editingWiki\"\n label=\"Extended tag description:\"\n placeholder=\"No wiki yet - consider adding one!\"\n textarea\n >\n <template #prepend>\n <span v-if=\"editingWiki\">\n <v-icon color=\"primary\" @click=\"saveWiki\">mdi-content-save</v-icon>\n </span>\n <v-icon v-else color=\"primary\" @click=\"editWiki\">mdi-pencil</v-icon>\n </template>\n <template #append>\n <v-icon v-if=\"editingWiki\" @click=\"cancelEditWiki\">mdi-cancel</v-icon>\n <v-fade-transition leave-absolute>\n <!-- spinner while awaiting async write of edits -->\n <v-progress-circular v-if=\"wikiSaving\" size=\"20\" color=\"info\" indeterminate></v-progress-circular>\n </v-fade-transition>\n </template>\n </v-text-field>\n\n <course-card-browser :course-id=\"courseId\" :tag-id=\"tagId\" :view-lookup-function=\"viewLookup\" />\n </div>\n</template>\n\n<script lang=\"ts\">\nimport { defineComponent } from 'vue';\nimport { DocType, Tag, getDataLayer, CourseDBInterface } from '@vue-skuilder/db';\nimport { Status, CourseConfig } from '@vue-skuilder/common';\nimport { CourseCardBrowser } from '@vue-skuilder/common-ui';\nimport { alertUser } from '@vue-skuilder/common-ui';\nimport { allCourseWare } from '@vue-skuilder/courseware';\n\nexport default defineComponent({\n name: 'TagInformation',\n\n components: {\n CourseCardBrowser,\n },\n\n props: {\n tagId: {\n type: String,\n required: true,\n },\n courseId: {\n type: String,\n required: true,\n },\n },\n\n data() {\n return {\n snippetModel: '',\n editingSnippet: false,\n snippetSaving: false,\n\n wikiModel: '',\n editingWiki: false,\n wikiSaving: false,\n\n courseDB: null as CourseDBInterface | null,\n\n tag: {\n course: this.courseId,\n name: this.tagId,\n snippet: '',\n wiki: '',\n taggedCards: [],\n docType: DocType.TAG,\n } as Tag,\n\n course: {\n courseID: this.courseId,\n name: '',\n description: '',\n public: false,\n deleted: false,\n dataShapes: [],\n questionTypes: [],\n creator: '',\n admins: [],\n moderators: [],\n } as CourseConfig,\n };\n },\n\n async created() {\n this.courseDB = getDataLayer().getCourseDB(this.courseId);\n this.tag = await this.courseDB.getTag(this.tagId);\n this.snippetModel = this.tag.snippet;\n this.wikiModel = this.tag.wiki;\n this.course = await this.courseDB.getCourseConfig();\n },\n\n methods: {\n viewLookup(x: unknown) {\n return allCourseWare.getView(x);\n },\n\n editSnippet() {\n console.log('[TagInformation] EditSnip');\n this.editingSnippet = true;\n (this.$refs.snippetEditor as HTMLInputElement).focus();\n },\n\n editWiki() {\n console.log('[TagInformation] EditWiki');\n this.editingWiki = true;\n (this.$refs.wikiEditor as HTMLInputElement).focus();\n },\n\n async saveSnippet() {\n this.snippetSaving = true;\n\n const update = await this.courseDB!.updateTag({\n ...this.tag,\n snippet: this.snippetModel,\n });\n\n if (update.ok) {\n console.log('[TagInformation] OK');\n this.tag.snippet = this.snippetModel;\n alertUser({\n text: `Updated applied - thanks!`,\n status: Status.ok,\n });\n } else {\n alertUser({\n text: `error in applying update!`,\n status: Status.error,\n });\n }\n\n this.editingSnippet = false;\n this.snippetSaving = false;\n },\n\n async saveWiki() {\n this.wikiSaving = true;\n\n const update = await this.courseDB!.updateTag({\n ...this.tag,\n wiki: this.wikiModel,\n });\n\n if (update.ok) {\n this.tag.wiki = this.wikiModel;\n alertUser({\n text: `Updated applied - thanks!`,\n status: Status.ok,\n });\n } else {\n alertUser({\n text: `error in applying update!`,\n status: Status.error,\n });\n }\n\n this.editingWiki = false;\n this.wikiSaving = false;\n },\n\n cancelEditSnippet() {\n console.log('[TagInformation] Cancelling EditSnip');\n this.editingSnippet = false;\n this.snippetModel = this.tag.snippet;\n },\n\n cancelEditWiki() {\n this.editingWiki = false;\n this.wikiModel = this.tag.wiki;\n },\n },\n});\n</script>\n","<template>\n <div>\n <!-- todo: -->\n <h1>\n <router-link :to=\"`/q/${course.name}`\">{{ course.name }}</router-link> > Tag: {{ tag.name }}\n </h1>\n <br />\n <p>{{ tag.taggedCards.length }} card{{ tag.taggedCards.length === 1 ? '' : 's' }}</p>\n\n <v-text-field\n ref=\"snippetEditor\"\n v-model=\"snippetModel\"\n variant=\"outlined\"\n :readonly=\"!editingSnippet\"\n :counter=\"editingSnippet\"\n label=\"Brief tag description:\"\n placeholder=\"No snippet yet - add one!\"\n type=\"text\"\n >\n <template #prepend>\n <span v-if=\"editingSnippet\">\n <v-icon color=\"primary\" @click=\"saveSnippet\">mdi-content-save</v-icon>\n </span>\n <v-icon v-else color=\"primary\" @click=\"editSnippet\">mdi-pencil</v-icon>\n </template>\n <template #append>\n <v-icon v-if=\"editingSnippet\" @click=\"cancelEditSnippet\">mdi-cancel</v-icon>\n <v-fade-transition leave-absolute>\n <!-- spinner while awaiting async write of edits -->\n <v-progress-circular v-if=\"snippetSaving\" size=\"20\" color=\"info\" indeterminate></v-progress-circular>\n </v-fade-transition>\n </template>\n </v-text-field>\n\n <v-text-field\n ref=\"wikiEditor\"\n v-model=\"wikiModel\"\n variant=\"outlined\"\n :readonly=\"!editingWiki\"\n :counter=\"editingWiki\"\n label=\"Extended tag description:\"\n placeholder=\"No wiki yet - consider adding one!\"\n textarea\n >\n <template #prepend>\n <span v-if=\"editingWiki\">\n <v-icon color=\"primary\" @click=\"saveWiki\">mdi-content-save</v-icon>\n </span>\n <v-icon v-else color=\"primary\" @click=\"editWiki\">mdi-pencil</v-icon>\n </template>\n <template #append>\n <v-icon v-if=\"editingWiki\" @click=\"cancelEditWiki\">mdi-cancel</v-icon>\n <v-fade-transition leave-absolute>\n <!-- spinner while awaiting async write of edits -->\n <v-progress-circular v-if=\"wikiSaving\" size=\"20\" color=\"info\" indeterminate></v-progress-circular>\n </v-fade-transition>\n </template>\n </v-text-field>\n\n <course-card-browser :course-id=\"courseId\" :tag-id=\"tagId\" :view-lookup-function=\"viewLookup\" />\n </div>\n</template>\n\n<script lang=\"ts\">\nimport { defineComponent } from 'vue';\nimport { DocType, Tag, getDataLayer, CourseDBInterface } from '@vue-skuilder/db';\nimport { Status, CourseConfig } from '@vue-skuilder/common';\nimport { CourseCardBrowser } from '@vue-skuilder/common-ui';\nimport { alertUser } from '@vue-skuilder/common-ui';\nimport { allCourseWare } from '@vue-skuilder/courseware';\n\nexport default defineComponent({\n name: 'TagInformation',\n\n components: {\n CourseCardBrowser,\n },\n\n props: {\n tagId: {\n type: String,\n required: true,\n },\n courseId: {\n type: String,\n required: true,\n },\n },\n\n data() {\n return {\n snippetModel: '',\n editingSnippet: false,\n snippetSaving: false,\n\n wikiModel: '',\n editingWiki: false,\n wikiSaving: false,\n\n courseDB: null as CourseDBInterface | null,\n\n tag: {\n course: this.courseId,\n name: this.tagId,\n snippet: '',\n wiki: '',\n taggedCards: [],\n docType: DocType.TAG,\n } as Tag,\n\n course: {\n courseID: this.courseId,\n name: '',\n description: '',\n public: false,\n deleted: false,\n dataShapes: [],\n questionTypes: [],\n creator: '',\n admins: [],\n moderators: [],\n } as CourseConfig,\n };\n },\n\n async created() {\n this.courseDB = getDataLayer().getCourseDB(this.courseId);\n this.tag = await this.courseDB.getTag(this.tagId);\n this.snippetModel = this.tag.snippet;\n this.wikiModel = this.tag.wiki;\n this.course = await this.courseDB.getCourseConfig();\n },\n\n methods: {\n viewLookup(x: unknown) {\n return allCourseWare.getView(x);\n },\n\n editSnippet() {\n console.log('[TagInformation] EditSnip');\n this.editingSnippet = true;\n (this.$refs.snippetEditor as HTMLInputElement).focus();\n },\n\n editWiki() {\n console.log('[TagInformation] EditWiki');\n this.editingWiki = true;\n (this.$refs.wikiEditor as HTMLInputElement).focus();\n },\n\n async saveSnippet() {\n this.snippetSaving = true;\n\n const update = await this.courseDB!.updateTag({\n ...this.tag,\n snippet: this.snippetModel,\n });\n\n if (update.ok) {\n console.log('[TagInformation] OK');\n this.tag.snippet = this.snippetModel;\n alertUser({\n text: `Updated applied - thanks!`,\n status: Status.ok,\n });\n } else {\n alertUser({\n text: `error in applying update!`,\n status: Status.error,\n });\n }\n\n this.editingSnippet = false;\n this.snippetSaving = false;\n },\n\n async saveWiki() {\n this.wikiSaving = true;\n\n const update = await this.courseDB!.updateTag({\n ...this.tag,\n wiki: this.wikiModel,\n });\n\n if (update.ok) {\n this.tag.wiki = this.wikiModel;\n alertUser({\n text: `Updated applied - thanks!`,\n status: Status.ok,\n });\n } else {\n alertUser({\n text: `error in applying update!`,\n status: Status.error,\n });\n }\n\n this.editingWiki = false;\n this.wikiSaving = false;\n },\n\n cancelEditSnippet() {\n console.log('[TagInformation] Cancelling EditSnip');\n this.editingSnippet = false;\n this.snippetModel = this.tag.snippet;\n },\n\n cancelEditWiki() {\n this.editingWiki = false;\n this.wikiModel = this.tag.wiki;\n },\n },\n});\n</script>\n"],"mappings":"6wBCuEA,IAAA,EAAe,EAAgB,CAC7B,KAAM,iBAEN,WAAY,CACV,kBAAA,EACD,CAED,MAAO,CACL,MAAO,CACL,KAAM,OACN,SAAU,GACX,CACD,SAAU,CACR,KAAM,OACN,SAAU,GACX,CACF,CAED,MAAO,CACL,MAAO,CACL,aAAc,GACd,eAAgB,GAChB,cAAe,GAEf,UAAW,GACX,YAAa,GACb,WAAY,GAEZ,SAAU,KAEV,IAAK,CACH,OAAQ,KAAK,SACb,KAAM,KAAK,MACX,QAAS,GACT,KAAM,GACN,YAAa,EAAE,CACf,QAAS,EAAQ,IACnB,CAEA,OAAQ,CACN,SAAU,KAAK,SACf,KAAM,GACN,YAAa,GACb,OAAQ,GACR,QAAS,GACT,WAAY,EAAE,CACd,cAAe,EAAE,CACjB,QAAS,GACT,OAAQ,EAAE,CACV,WAAY,EAAE,CAChB,CACD,EAGH,MAAM,SAAU,CACd,KAAK,SAAW,GAAc,CAAC,YAAY,KAAK,SAAS,CACzD,KAAK,IAAM,MAAM,KAAK,SAAS,OAAO,KAAK,MAAM,CACjD,KAAK,aAAe,KAAK,IAAI,QAC7B,KAAK,UAAY,KAAK,IAAI,KAC1B,KAAK,OAAS,MAAM,KAAK,SAAS,iBAAiB,EAGrD,QAAS,CACP,WAAW,EAAY,CACrB,OAAO,EAAc,QAAQ,EAAE,EAGjC,aAAc,CACZ,QAAQ,IAAI,4BAA4B,CACxC,KAAK,eAAiB,GACrB,KAAK,MAAM,cAAmC,OAAO,EAGxD,UAAW,CACT,QAAQ,IAAI,4BAA4B,CACxC,KAAK,YAAc,GAClB,KAAK,MAAM,WAAgC,OAAO,EAGrD,MAAM,aAAc,CAClB,KAAK,cAAgB,IAEN,MAAM,KAAK,SAAU,UAAU,CAC5C,GAAG,KAAK,IACR,QAAS,KAAK,aACf,CAAC,EAES,IACT,QAAQ,IAAI,sBAAsB,CAClC,KAAK,IAAI,QAAU,KAAK,aACxB,EAAU,CACR,KAAM,4BACN,OAAQ,EAAO,GAChB,CAAC,EAEF,EAAU,CACR,KAAM,4BACN,OAAQ,EAAO,MAChB,CAAC,CAGJ,KAAK,eAAiB,GACtB,KAAK,cAAgB,IAGvB,MAAM,UAAW,CACf,KAAK,WAAa,IAEH,MAAM,KAAK,SAAU,UAAU,CAC5C,GAAG,KAAK,IACR,KAAM,KAAK,UACZ,CAAC,EAES,IACT,KAAK,IAAI,KAAO,KAAK,UACrB,EAAU,CACR,KAAM,4BACN,OAAQ,EAAO,GAChB,CAAC,EAEF,EAAU,CACR,KAAM,4BACN,OAAQ,EAAO,MAChB,CAAC,CAGJ,KAAK,YAAc,GACnB,KAAK,WAAa,IAGpB,mBAAoB,CAClB,QAAQ,IAAI,uCAAuC,CACnD,KAAK,eAAiB,GACtB,KAAK,aAAe,KAAK,IAAI,SAG/B,gBAAiB,CACf,KAAK,YAAc,GACnB,KAAK,UAAY,KAAK,IAAI,MAE7B,CACF,CAAC,IApNF,IAAA,EAAA,IAAA,IAAA,EAAA,sLACE,EA2DM,MAAA,KAAA,CAzDJ,EAEK,KAAA,KAAA,CADH,EAAsE,EAAA,CAAxD,GAAE,MAAQ,EAAA,OAAO,OAAA,CAAA,CAJrC,QAAA,MAI8D,CAJ9D,EAAA,EAIgD,EAAA,OAAO,KAAI,CAAA,EAAA,CAAA,CAAA,CAJ3D,EAAA,aAAA,EAI4E,WAAQ,EAAG,EAAA,IAAI,KAAI,CAAA,EAAA,CAAA,CAAA,aAE3F,EAAM,KAAA,KAAA,KAAA,GAAA,EACN,EAAqF,IAAA,KAAA,EAA/E,EAAA,IAAI,YAAY,OAAM,CAAG,QAAK,EAAG,EAAA,IAAI,YAAY,SAAM,EAAA,GAAA,IAAA,CAAA,EAAA,CAE7D,EAuBe,EAAA,CAtBb,IAAI,gBAVV,WAWe,EAAA,aAXf,sBAAA,EAAA,KAAA,EAAA,GAAA,GAAA,EAWe,aAAY,GACrB,QAAQ,WACP,SAAQ,CAAG,EAAA,eACX,QAAS,EAAA,eACV,MAAM,yBACN,YAAY,4BACZ,KAAK,SAEM,QAAO,MAGT,CAFK,EAAA,gBAAA,GAAA,CAAZ,EAEO,OAtBf,EAAA,CAqBU,EAAsE,EAAA,CAA9D,MAAM,UAAW,QAAO,EAAA,cArB1C,QAAA,MAqBuE,EAAA,KAAA,EAAA,GAAA,CArBvE,EAqBuD,mBAAgB,CAAA,EAAA,CArBvE,EAAA,0BAuBQ,EAAuE,EAAA,CAvB/E,IAAA,EAuBuB,MAAM,UAAW,QAAO,EAAA,cAvB/C,QAAA,MAuBsE,EAAA,KAAA,EAAA,GAAA,CAvBtE,EAuB4D,aAAU,CAAA,EAAA,CAvBtE,EAAA,qBAyBiB,OAAM,MAC6D,CAA9D,EAAA,gBAAA,GAAA,CAAd,EAA4E,EAAA,CA1BpF,IAAA,EA0BuC,QAAO,EAAA,oBA1B9C,QAAA,MA0B2E,EAAA,KAAA,EAAA,GAAA,CA1B3E,EA0BiE,aAAU,CAAA,EAAA,CA1B3E,EAAA,mBAAA,EAAA,GAAA,GAAA,CA2BQ,EAGoB,EAAA,CAHD,iBAAA,GAAc,CAAA,CA3BzC,QAAA,MA6B+G,CAA1E,EAAA,eAAA,GAAA,CAA3B,EAAqG,EAAA,CA7B/G,IAAA,EA6BoD,KAAK,KAAK,MAAM,OAAO,cAAA,MA7B3E,EAAA,GAAA,GAAA,CAAA,CAAA,CAAA,EAAA,MAAA,EAAA,0CAkCI,EAuBe,EAAA,CAtBb,IAAI,aAnCV,WAoCe,EAAA,UApCf,sBAAA,EAAA,KAAA,EAAA,GAAA,GAAA,EAoCe,UAAS,GAClB,QAAQ,WACP,SAAQ,CAAG,EAAA,YACX,QAAS,EAAA,YACV,MAAM,4BACN,YAAY,qCACZ,SAAA,KAEW,QAAO,MAGT,CAFK,EAAA,aAAA,GAAA,CAAZ,EAEO,OA/Cf,EAAA,CA8CU,EAAmE,EAAA,CAA3D,MAAM,UAAW,QAAO,EAAA,WA9C1C,QAAA,MA8CoE,EAAA,KAAA,EAAA,GAAA,CA9CpE,EA8CoD,mBAAgB,CAAA,EAAA,CA9CpE,EAAA,0BAgDQ,EAAoE,EAAA,CAhD5E,IAAA,EAgDuB,MAAM,UAAW,QAAO,EAAA,WAhD/C,QAAA,MAgDmE,EAAA,KAAA,EAAA,GAAA,CAhDnE,EAgDyD,aAAU,CAAA,EAAA,CAhDnE,EAAA,qBAkDiB,OAAM,MACuD,CAAxD,EAAA,aAAA,GAAA,CAAd,EAAsE,EAAA,CAnD9E,IAAA,EAmDoC,QAAO,EAAA,iBAnD3C,QAAA,MAmDqE,EAAA,KAAA,EAAA,GAAA,CAnDrE,EAmD2D,aAAU,CAAA,EAAA,CAnDrE,EAAA,mBAAA,EAAA,GAAA,GAAA,CAoDQ,EAGoB,EAAA,CAHD,iBAAA,GAAc,CAAA,CApDzC,QAAA,MAsD4G,CAAvE,EAAA,YAAA,GAAA,CAA3B,EAAkG,EAAA,CAtD5G,IAAA,EAsDiD,KAAK,KAAK,MAAM,OAAO,cAAA,MAtDxE,EAAA,GAAA,GAAA,CAAA,CAAA,CAAA,EAAA,MAAA,EAAA,0CA2DI,EAAgG,EAAA,CAA1E,YAAW,EAAA,SAAW,SAAQ,EAAA,MAAQ,uBAAsB,EAAA"}
1
+ {"version":3,"file":"TagInformation-DHw5jRNt.js","names":[],"sources":["../../src/components/Courses/TagInformation.vue","../../src/components/Courses/TagInformation.vue"],"sourcesContent":["<template>\n <div>\n <!-- todo: -->\n <h1>\n <router-link :to=\"`/q/${course.name}`\">{{ course.name }}</router-link> > Tag: {{ tag.name }}\n </h1>\n <br />\n <p>{{ tag.taggedCards.length }} card{{ tag.taggedCards.length === 1 ? '' : 's' }}</p>\n\n <v-text-field\n ref=\"snippetEditor\"\n v-model=\"snippetModel\"\n variant=\"outlined\"\n :readonly=\"!editingSnippet\"\n :counter=\"editingSnippet\"\n label=\"Brief tag description:\"\n placeholder=\"No snippet yet - add one!\"\n type=\"text\"\n >\n <template #prepend>\n <span v-if=\"editingSnippet\">\n <v-icon color=\"primary\" @click=\"saveSnippet\">mdi-content-save</v-icon>\n </span>\n <v-icon v-else color=\"primary\" @click=\"editSnippet\">mdi-pencil</v-icon>\n </template>\n <template #append>\n <v-icon v-if=\"editingSnippet\" @click=\"cancelEditSnippet\">mdi-cancel</v-icon>\n <v-fade-transition leave-absolute>\n <!-- spinner while awaiting async write of edits -->\n <v-progress-circular v-if=\"snippetSaving\" size=\"20\" color=\"info\" indeterminate></v-progress-circular>\n </v-fade-transition>\n </template>\n </v-text-field>\n\n <v-text-field\n ref=\"wikiEditor\"\n v-model=\"wikiModel\"\n variant=\"outlined\"\n :readonly=\"!editingWiki\"\n :counter=\"editingWiki\"\n label=\"Extended tag description:\"\n placeholder=\"No wiki yet - consider adding one!\"\n textarea\n >\n <template #prepend>\n <span v-if=\"editingWiki\">\n <v-icon color=\"primary\" @click=\"saveWiki\">mdi-content-save</v-icon>\n </span>\n <v-icon v-else color=\"primary\" @click=\"editWiki\">mdi-pencil</v-icon>\n </template>\n <template #append>\n <v-icon v-if=\"editingWiki\" @click=\"cancelEditWiki\">mdi-cancel</v-icon>\n <v-fade-transition leave-absolute>\n <!-- spinner while awaiting async write of edits -->\n <v-progress-circular v-if=\"wikiSaving\" size=\"20\" color=\"info\" indeterminate></v-progress-circular>\n </v-fade-transition>\n </template>\n </v-text-field>\n\n <course-card-browser :course-id=\"courseId\" :tag-id=\"tagId\" :view-lookup-function=\"viewLookup\" />\n </div>\n</template>\n\n<script lang=\"ts\">\nimport { defineComponent } from 'vue';\nimport { DocType, Tag, getDataLayer, CourseDBInterface } from '@vue-skuilder/db';\nimport { Status, CourseConfig } from '@vue-skuilder/common';\nimport { CourseCardBrowser } from '@vue-skuilder/common-ui';\nimport { alertUser } from '@vue-skuilder/common-ui';\nimport { allCourseWare } from '@vue-skuilder/courseware';\n\nexport default defineComponent({\n name: 'TagInformation',\n\n components: {\n CourseCardBrowser,\n },\n\n props: {\n tagId: {\n type: String,\n required: true,\n },\n courseId: {\n type: String,\n required: true,\n },\n },\n\n data() {\n return {\n snippetModel: '',\n editingSnippet: false,\n snippetSaving: false,\n\n wikiModel: '',\n editingWiki: false,\n wikiSaving: false,\n\n courseDB: null as CourseDBInterface | null,\n\n tag: {\n course: this.courseId,\n name: this.tagId,\n snippet: '',\n wiki: '',\n taggedCards: [],\n docType: DocType.TAG,\n } as Tag,\n\n course: {\n courseID: this.courseId,\n name: '',\n description: '',\n public: false,\n deleted: false,\n dataShapes: [],\n questionTypes: [],\n creator: '',\n admins: [],\n moderators: [],\n } as CourseConfig,\n };\n },\n\n async created() {\n this.courseDB = getDataLayer().getCourseDB(this.courseId);\n this.tag = await this.courseDB.getTag(this.tagId);\n this.snippetModel = this.tag.snippet;\n this.wikiModel = this.tag.wiki;\n this.course = await this.courseDB.getCourseConfig();\n },\n\n methods: {\n viewLookup(x: unknown) {\n return allCourseWare.getView(x);\n },\n\n editSnippet() {\n console.log('[TagInformation] EditSnip');\n this.editingSnippet = true;\n (this.$refs.snippetEditor as HTMLInputElement).focus();\n },\n\n editWiki() {\n console.log('[TagInformation] EditWiki');\n this.editingWiki = true;\n (this.$refs.wikiEditor as HTMLInputElement).focus();\n },\n\n async saveSnippet() {\n this.snippetSaving = true;\n\n const update = await this.courseDB!.updateTag({\n ...this.tag,\n snippet: this.snippetModel,\n });\n\n if (update.ok) {\n console.log('[TagInformation] OK');\n this.tag.snippet = this.snippetModel;\n alertUser({\n text: `Updated applied - thanks!`,\n status: Status.ok,\n });\n } else {\n alertUser({\n text: `error in applying update!`,\n status: Status.error,\n });\n }\n\n this.editingSnippet = false;\n this.snippetSaving = false;\n },\n\n async saveWiki() {\n this.wikiSaving = true;\n\n const update = await this.courseDB!.updateTag({\n ...this.tag,\n wiki: this.wikiModel,\n });\n\n if (update.ok) {\n this.tag.wiki = this.wikiModel;\n alertUser({\n text: `Updated applied - thanks!`,\n status: Status.ok,\n });\n } else {\n alertUser({\n text: `error in applying update!`,\n status: Status.error,\n });\n }\n\n this.editingWiki = false;\n this.wikiSaving = false;\n },\n\n cancelEditSnippet() {\n console.log('[TagInformation] Cancelling EditSnip');\n this.editingSnippet = false;\n this.snippetModel = this.tag.snippet;\n },\n\n cancelEditWiki() {\n this.editingWiki = false;\n this.wikiModel = this.tag.wiki;\n },\n },\n});\n</script>\n","<template>\n <div>\n <!-- todo: -->\n <h1>\n <router-link :to=\"`/q/${course.name}`\">{{ course.name }}</router-link> > Tag: {{ tag.name }}\n </h1>\n <br />\n <p>{{ tag.taggedCards.length }} card{{ tag.taggedCards.length === 1 ? '' : 's' }}</p>\n\n <v-text-field\n ref=\"snippetEditor\"\n v-model=\"snippetModel\"\n variant=\"outlined\"\n :readonly=\"!editingSnippet\"\n :counter=\"editingSnippet\"\n label=\"Brief tag description:\"\n placeholder=\"No snippet yet - add one!\"\n type=\"text\"\n >\n <template #prepend>\n <span v-if=\"editingSnippet\">\n <v-icon color=\"primary\" @click=\"saveSnippet\">mdi-content-save</v-icon>\n </span>\n <v-icon v-else color=\"primary\" @click=\"editSnippet\">mdi-pencil</v-icon>\n </template>\n <template #append>\n <v-icon v-if=\"editingSnippet\" @click=\"cancelEditSnippet\">mdi-cancel</v-icon>\n <v-fade-transition leave-absolute>\n <!-- spinner while awaiting async write of edits -->\n <v-progress-circular v-if=\"snippetSaving\" size=\"20\" color=\"info\" indeterminate></v-progress-circular>\n </v-fade-transition>\n </template>\n </v-text-field>\n\n <v-text-field\n ref=\"wikiEditor\"\n v-model=\"wikiModel\"\n variant=\"outlined\"\n :readonly=\"!editingWiki\"\n :counter=\"editingWiki\"\n label=\"Extended tag description:\"\n placeholder=\"No wiki yet - consider adding one!\"\n textarea\n >\n <template #prepend>\n <span v-if=\"editingWiki\">\n <v-icon color=\"primary\" @click=\"saveWiki\">mdi-content-save</v-icon>\n </span>\n <v-icon v-else color=\"primary\" @click=\"editWiki\">mdi-pencil</v-icon>\n </template>\n <template #append>\n <v-icon v-if=\"editingWiki\" @click=\"cancelEditWiki\">mdi-cancel</v-icon>\n <v-fade-transition leave-absolute>\n <!-- spinner while awaiting async write of edits -->\n <v-progress-circular v-if=\"wikiSaving\" size=\"20\" color=\"info\" indeterminate></v-progress-circular>\n </v-fade-transition>\n </template>\n </v-text-field>\n\n <course-card-browser :course-id=\"courseId\" :tag-id=\"tagId\" :view-lookup-function=\"viewLookup\" />\n </div>\n</template>\n\n<script lang=\"ts\">\nimport { defineComponent } from 'vue';\nimport { DocType, Tag, getDataLayer, CourseDBInterface } from '@vue-skuilder/db';\nimport { Status, CourseConfig } from '@vue-skuilder/common';\nimport { CourseCardBrowser } from '@vue-skuilder/common-ui';\nimport { alertUser } from '@vue-skuilder/common-ui';\nimport { allCourseWare } from '@vue-skuilder/courseware';\n\nexport default defineComponent({\n name: 'TagInformation',\n\n components: {\n CourseCardBrowser,\n },\n\n props: {\n tagId: {\n type: String,\n required: true,\n },\n courseId: {\n type: String,\n required: true,\n },\n },\n\n data() {\n return {\n snippetModel: '',\n editingSnippet: false,\n snippetSaving: false,\n\n wikiModel: '',\n editingWiki: false,\n wikiSaving: false,\n\n courseDB: null as CourseDBInterface | null,\n\n tag: {\n course: this.courseId,\n name: this.tagId,\n snippet: '',\n wiki: '',\n taggedCards: [],\n docType: DocType.TAG,\n } as Tag,\n\n course: {\n courseID: this.courseId,\n name: '',\n description: '',\n public: false,\n deleted: false,\n dataShapes: [],\n questionTypes: [],\n creator: '',\n admins: [],\n moderators: [],\n } as CourseConfig,\n };\n },\n\n async created() {\n this.courseDB = getDataLayer().getCourseDB(this.courseId);\n this.tag = await this.courseDB.getTag(this.tagId);\n this.snippetModel = this.tag.snippet;\n this.wikiModel = this.tag.wiki;\n this.course = await this.courseDB.getCourseConfig();\n },\n\n methods: {\n viewLookup(x: unknown) {\n return allCourseWare.getView(x);\n },\n\n editSnippet() {\n console.log('[TagInformation] EditSnip');\n this.editingSnippet = true;\n (this.$refs.snippetEditor as HTMLInputElement).focus();\n },\n\n editWiki() {\n console.log('[TagInformation] EditWiki');\n this.editingWiki = true;\n (this.$refs.wikiEditor as HTMLInputElement).focus();\n },\n\n async saveSnippet() {\n this.snippetSaving = true;\n\n const update = await this.courseDB!.updateTag({\n ...this.tag,\n snippet: this.snippetModel,\n });\n\n if (update.ok) {\n console.log('[TagInformation] OK');\n this.tag.snippet = this.snippetModel;\n alertUser({\n text: `Updated applied - thanks!`,\n status: Status.ok,\n });\n } else {\n alertUser({\n text: `error in applying update!`,\n status: Status.error,\n });\n }\n\n this.editingSnippet = false;\n this.snippetSaving = false;\n },\n\n async saveWiki() {\n this.wikiSaving = true;\n\n const update = await this.courseDB!.updateTag({\n ...this.tag,\n wiki: this.wikiModel,\n });\n\n if (update.ok) {\n this.tag.wiki = this.wikiModel;\n alertUser({\n text: `Updated applied - thanks!`,\n status: Status.ok,\n });\n } else {\n alertUser({\n text: `error in applying update!`,\n status: Status.error,\n });\n }\n\n this.editingWiki = false;\n this.wikiSaving = false;\n },\n\n cancelEditSnippet() {\n console.log('[TagInformation] Cancelling EditSnip');\n this.editingSnippet = false;\n this.snippetModel = this.tag.snippet;\n },\n\n cancelEditWiki() {\n this.editingWiki = false;\n this.wikiModel = this.tag.wiki;\n },\n },\n});\n</script>\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import"./chunk-D6hFPZc3.js";import{$ as e,C as t,D as n,H as r,Ht as i,K as a,Rt as o,S as s,T as c,_ as l,b as u,v as d,y as f}from"./vue.runtime.esm-bundler--Fxmd6l9.js";import{r as p}from"./vue-router-HoUBO3s7.js";import"./MarkdownRenderer-borh39Lq.js";import"./moment-DbT30ZpG.js";import{g as m,h,t as g,w as _}from"./dist-BsRvt0gH.js";import"./contentSource-DwznMSMs.js";import"./dist-Dp6eBE2S.js";import"./PipelineDebugger-BHaqzqZw.js";import{d as v}from"./userDBKeys-DYLGKnVC.js";import"./eloDistance-BV7vfiK5.js";import{t as y}from"./index-CK-rfSIx.js";var b=c({name:`UserSettings`,components:{UserTagPreferences:g},props:{username:{type:String,required:!0}},setup(){let e=h(),t=p();return{configStore:e,darkMode:e.config.darkMode,likesConfetti:e.config.likesConfetti,route:t,isNewUser:t.path.endsWith(`new`)}},data(){return{u:{},configLanguages:[{name:`English`,code:`en`},{name:`French`,code:`fr`}],selectedLanguages:[],registeredCourses:[],selectedCourseId:``}},async created(){this.u=await m(),this.configLanguages.forEach(e=>{console.log(`afweatifvwzeatfvwzeta`+e.name)}),await this.loadRegisteredCourses()},methods:{updateDark(){this.configStore.updateDarkMode(this.configStore.config.darkMode)},updateConfetti(){this.configStore.updateLikesConfetti(this.configStore.config.likesConfetti),this.configStore.config.likesConfetti&&_({origin:{x:.5,y:1}})},async loadRegisteredCourses(){try{let e=await this.u.getActiveCourses(),t=v();this.registeredCourses=(await Promise.all(e.map(async e=>{try{return await t.getCourseDB(e.courseID).getCourseConfig()}catch{return{courseID:e.courseID,name:e.courseID}}}))).filter(Boolean)}catch(e){console.error(`Failed to load registered courses:`,e)}},onPreferencesSaved(){console.log(`Preferences saved for course:`,this.selectedCourseId)}}}),x={class:`text-subtitle-1`};function _sfc_render(c,p,m,h,g,_){let v=a(`v-alert`),y=a(`v-checkbox`),b=a(`v-divider`),S=a(`v-list-item`),C=a(`v-select`),w=a(`user-tag-preferences`);return r(),u(`div`,x,[c.isNewUser?(r(),d(v,{key:0,type:`success`,class:`text-subtitle-1`,variant:`tonal`,"prepend-icon":`mdi-check`},{default:e(()=>[s(` Welcome, `+i(c.username)+`! Please take a moment to look through these settings: `,1)]),_:1})):f(``,!0),p[3]||(p[3]=l(`h1`,{class:`text-h3`},`Account Settings`,-1)),p[4]||(p[4]=l(`h2`,{class:`text-h4`},`General:`,-1)),t(y,{modelValue:c.configStore.config.likesConfetti,"onUpdate:modelValue":[p[0]||(p[0]=e=>c.configStore.config.likesConfetti=e),c.updateConfetti],label:`I like confetti`},null,8,[`modelValue`,`onUpdate:modelValue`]),t(y,{modelValue:c.configStore.config.darkMode,"onUpdate:modelValue":[p[1]||(p[1]=e=>c.configStore.config.darkMode=e),c.updateDark],label:`I like the dark`},null,8,[`modelValue`,`onUpdate:modelValue`]),t(b,{class:`my-6`}),p[5]||(p[5]=l(`h2`,{class:`text-h4 mb-4`},`Learning Preferences:`,-1)),p[6]||(p[6]=l(`p`,{class:`text-body-2 text-medium-emphasis mb-4`},`Customize how content is presented in your registered courses.`,-1)),t(C,{modelValue:c.selectedCourseId,"onUpdate:modelValue":p[2]||(p[2]=e=>c.selectedCourseId=e),items:c.registeredCourses,"item-title":`name`,"item-value":`courseID`,label:`Select a course`,variant:`outlined`,density:`comfortable`,class:`mb-4`,style:{"max-width":`400px`}},{item:e(({props:r,item:a})=>[t(S,o(n(r)),{subtitle:e(()=>[s(i(a.raw.courseID),1)]),_:2},1040)]),_:1},8,[`modelValue`,`items`]),c.selectedCourseId?(r(),d(w,{key:1,"course-id":c.selectedCourseId,onPreferencesSaved:c.onPreferencesSaved},null,8,[`course-id`,`onPreferencesSaved`])):f(``,!0)])}var S=y(b,[[`render`,_sfc_render]]);export{S as default};
2
- //# sourceMappingURL=User-DfXUXjFb.js.map
1
+ import"./chunk-D6hFPZc3.js";import{$ as e,C as t,D as n,H as r,Ht as i,K as a,Rt as o,S as s,T as c,_ as l,b as u,v as d,y as f}from"./vue.runtime.esm-bundler--Fxmd6l9.js";import{r as p}from"./vue-router-HoUBO3s7.js";import"./MarkdownRenderer-borh39Lq.js";import"./moment-DbT30ZpG.js";import{g as m,h,t as g,w as _}from"./dist-C8EQUxWW.js";import"./contentSource-FGhM_ge8.js";import"./dist-DGbpjpKB.js";import"./PipelineDebugger-DhnKlMgD.js";import{d as v}from"./userDBKeys-3SQjNVdK.js";import"./eloDistance-CRxxdYPJ.js";import{t as y}from"./index-BRaeVAZb.js";var b=c({name:`UserSettings`,components:{UserTagPreferences:g},props:{username:{type:String,required:!0}},setup(){let e=h(),t=p();return{configStore:e,darkMode:e.config.darkMode,likesConfetti:e.config.likesConfetti,route:t,isNewUser:t.path.endsWith(`new`)}},data(){return{u:{},configLanguages:[{name:`English`,code:`en`},{name:`French`,code:`fr`}],selectedLanguages:[],registeredCourses:[],selectedCourseId:``}},async created(){this.u=await m(),this.configLanguages.forEach(e=>{console.log(`afweatifvwzeatfvwzeta`+e.name)}),await this.loadRegisteredCourses()},methods:{updateDark(){this.configStore.updateDarkMode(this.configStore.config.darkMode)},updateConfetti(){this.configStore.updateLikesConfetti(this.configStore.config.likesConfetti),this.configStore.config.likesConfetti&&_({origin:{x:.5,y:1}})},async loadRegisteredCourses(){try{let e=await this.u.getActiveCourses(),t=v();this.registeredCourses=(await Promise.all(e.map(async e=>{try{return await t.getCourseDB(e.courseID).getCourseConfig()}catch{return{courseID:e.courseID,name:e.courseID}}}))).filter(Boolean)}catch(e){console.error(`Failed to load registered courses:`,e)}},onPreferencesSaved(){console.log(`Preferences saved for course:`,this.selectedCourseId)}}}),x={class:`text-subtitle-1`};function _sfc_render(c,p,m,h,g,_){let v=a(`v-alert`),y=a(`v-checkbox`),b=a(`v-divider`),S=a(`v-list-item`),C=a(`v-select`),w=a(`user-tag-preferences`);return r(),u(`div`,x,[c.isNewUser?(r(),d(v,{key:0,type:`success`,class:`text-subtitle-1`,variant:`tonal`,"prepend-icon":`mdi-check`},{default:e(()=>[s(` Welcome, `+i(c.username)+`! Please take a moment to look through these settings: `,1)]),_:1})):f(``,!0),p[3]||(p[3]=l(`h1`,{class:`text-h3`},`Account Settings`,-1)),p[4]||(p[4]=l(`h2`,{class:`text-h4`},`General:`,-1)),t(y,{modelValue:c.configStore.config.likesConfetti,"onUpdate:modelValue":[p[0]||(p[0]=e=>c.configStore.config.likesConfetti=e),c.updateConfetti],label:`I like confetti`},null,8,[`modelValue`,`onUpdate:modelValue`]),t(y,{modelValue:c.configStore.config.darkMode,"onUpdate:modelValue":[p[1]||(p[1]=e=>c.configStore.config.darkMode=e),c.updateDark],label:`I like the dark`},null,8,[`modelValue`,`onUpdate:modelValue`]),t(b,{class:`my-6`}),p[5]||(p[5]=l(`h2`,{class:`text-h4 mb-4`},`Learning Preferences:`,-1)),p[6]||(p[6]=l(`p`,{class:`text-body-2 text-medium-emphasis mb-4`},`Customize how content is presented in your registered courses.`,-1)),t(C,{modelValue:c.selectedCourseId,"onUpdate:modelValue":p[2]||(p[2]=e=>c.selectedCourseId=e),items:c.registeredCourses,"item-title":`name`,"item-value":`courseID`,label:`Select a course`,variant:`outlined`,density:`comfortable`,class:`mb-4`,style:{"max-width":`400px`}},{item:e(({props:r,item:a})=>[t(S,o(n(r)),{subtitle:e(()=>[s(i(a.raw.courseID),1)]),_:2},1040)]),_:1},8,[`modelValue`,`items`]),c.selectedCourseId?(r(),d(w,{key:1,"course-id":c.selectedCourseId,onPreferencesSaved:c.onPreferencesSaved},null,8,[`course-id`,`onPreferencesSaved`])):f(``,!0)])}var S=y(b,[[`render`,_sfc_render]]);export{S as default};
2
+ //# sourceMappingURL=User-C0xEcKaU.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"User-DfXUXjFb.js","names":[],"sources":["../../src/views/User.vue","../../src/views/User.vue"],"sourcesContent":["<template>\n <div class=\"text-subtitle-1\">\n <v-alert v-if=\"isNewUser\" type=\"success\" class=\"text-subtitle-1\" variant=\"tonal\" :prepend-icon=\"'mdi-check'\">\n Welcome, {{ username }}! Please take a moment to look through these settings:\n </v-alert>\n\n <h1 class=\"text-h3\">Account Settings</h1>\n <h2 class=\"text-h4\">General:</h2>\n\n <v-checkbox\n v-model=\"configStore.config.likesConfetti\"\n label=\"I like confetti\"\n @update:model-value=\"updateConfetti\"\n />\n <v-checkbox v-model=\"configStore.config.darkMode\" label=\"I like the dark\" @update:model-value=\"updateDark\" />\n <!-- <h2 class=\"text-h4\">Languages:</h2>\n I am near-fluent or better in the following languages:\n {{ selectedLanguages.toString() }}\n <v-checkbox\n v-for=\"(language, i) in configLanguages\"\n :key=\"i\"\n :label=\"language.name\"\n :value=\"language.code\"\n v-model=\"selectedLanguages\"\n @update:model-value=\"updateLanguage\"\n /> -->\n\n <v-divider class=\"my-6\" />\n\n <h2 class=\"text-h4 mb-4\">Learning Preferences:</h2>\n <p class=\"text-body-2 text-medium-emphasis mb-4\">Customize how content is presented in your registered courses.</p>\n\n <v-select\n v-model=\"selectedCourseId\"\n :items=\"registeredCourses\"\n item-title=\"name\"\n item-value=\"courseID\"\n label=\"Select a course\"\n variant=\"outlined\"\n density=\"comfortable\"\n class=\"mb-4\"\n style=\"max-width: 400px\"\n >\n <template #item=\"{ props, item }\">\n <v-list-item v-bind=\"props\">\n <template #subtitle>\n {{ item.raw.courseID }}\n </template>\n </v-list-item>\n </template>\n </v-select>\n\n <user-tag-preferences\n v-if=\"selectedCourseId\"\n :course-id=\"selectedCourseId\"\n @preferences-saved=\"onPreferencesSaved\"\n />\n </div>\n</template>\n\n<script lang=\"ts\">\nimport { getCurrentUser, useConfigStore, UserTagPreferences } from '@vue-skuilder/common-ui';\nimport { UserDBInterface, getDataLayer } from '@vue-skuilder/db';\nimport { CourseConfig } from '@vue-skuilder/common';\nimport confetti from 'canvas-confetti';\nimport { defineComponent, PropType } from 'vue';\nimport { useRoute } from 'vue-router';\n\ninterface Language {\n name: string;\n code: string;\n}\n\nexport default defineComponent({\n name: 'UserSettings',\n\n components: {\n UserTagPreferences,\n },\n\n props: {\n username: {\n type: String as PropType<string>,\n required: true,\n },\n },\n\n setup() {\n const configStore = useConfigStore();\n const route = useRoute();\n\n const darkMode = configStore.config.darkMode;\n const likesConfetti = configStore.config.likesConfetti;\n\n const isNewUser = route.path.endsWith('new');\n\n return { configStore, darkMode, likesConfetti, route, isNewUser };\n },\n\n data() {\n return {\n u: {} as UserDBInterface,\n configLanguages: [\n {\n name: 'English',\n code: 'en',\n },\n {\n name: 'French',\n code: 'fr',\n },\n ] as Language[],\n selectedLanguages: [] as string[],\n registeredCourses: [] as CourseConfig[],\n selectedCourseId: '' as string,\n };\n },\n\n async created() {\n this.u = await getCurrentUser();\n this.configLanguages.forEach((l) => {\n console.log(`afweatifvwzeatfvwzeta` + l.name);\n });\n\n // Load registered courses for preferences selector\n await this.loadRegisteredCourses();\n },\n\n methods: {\n updateDark(): void {\n this.configStore.updateDarkMode(this.configStore.config.darkMode);\n },\n\n updateConfetti(): void {\n this.configStore.updateLikesConfetti(this.configStore.config.likesConfetti);\n\n if (this.configStore.config.likesConfetti) {\n confetti({\n origin: {\n x: 0.5,\n y: 1,\n },\n });\n }\n },\n\n async loadRegisteredCourses(): Promise<void> {\n try {\n const activeCourses = await this.u.getActiveCourses();\n const dataLayer = getDataLayer();\n\n // Fetch course configs for display names\n const courseConfigs = await Promise.all(\n activeCourses.map(async (reg) => {\n try {\n const courseDB = dataLayer.getCourseDB(reg.courseID);\n return await courseDB.getCourseConfig();\n } catch {\n // Return minimal config if fetch fails\n return { courseID: reg.courseID, name: reg.courseID } as CourseConfig;\n }\n })\n );\n\n this.registeredCourses = courseConfigs.filter(Boolean);\n } catch (e) {\n console.error('Failed to load registered courses:', e);\n }\n },\n\n onPreferencesSaved(): void {\n // Could show a snackbar or other feedback here\n console.log('Preferences saved for course:', this.selectedCourseId);\n },\n },\n});\n</script>\n\n<style scoped></style>\n","<template>\n <div class=\"text-subtitle-1\">\n <v-alert v-if=\"isNewUser\" type=\"success\" class=\"text-subtitle-1\" variant=\"tonal\" :prepend-icon=\"'mdi-check'\">\n Welcome, {{ username }}! Please take a moment to look through these settings:\n </v-alert>\n\n <h1 class=\"text-h3\">Account Settings</h1>\n <h2 class=\"text-h4\">General:</h2>\n\n <v-checkbox\n v-model=\"configStore.config.likesConfetti\"\n label=\"I like confetti\"\n @update:model-value=\"updateConfetti\"\n />\n <v-checkbox v-model=\"configStore.config.darkMode\" label=\"I like the dark\" @update:model-value=\"updateDark\" />\n <!-- <h2 class=\"text-h4\">Languages:</h2>\n I am near-fluent or better in the following languages:\n {{ selectedLanguages.toString() }}\n <v-checkbox\n v-for=\"(language, i) in configLanguages\"\n :key=\"i\"\n :label=\"language.name\"\n :value=\"language.code\"\n v-model=\"selectedLanguages\"\n @update:model-value=\"updateLanguage\"\n /> -->\n\n <v-divider class=\"my-6\" />\n\n <h2 class=\"text-h4 mb-4\">Learning Preferences:</h2>\n <p class=\"text-body-2 text-medium-emphasis mb-4\">Customize how content is presented in your registered courses.</p>\n\n <v-select\n v-model=\"selectedCourseId\"\n :items=\"registeredCourses\"\n item-title=\"name\"\n item-value=\"courseID\"\n label=\"Select a course\"\n variant=\"outlined\"\n density=\"comfortable\"\n class=\"mb-4\"\n style=\"max-width: 400px\"\n >\n <template #item=\"{ props, item }\">\n <v-list-item v-bind=\"props\">\n <template #subtitle>\n {{ item.raw.courseID }}\n </template>\n </v-list-item>\n </template>\n </v-select>\n\n <user-tag-preferences\n v-if=\"selectedCourseId\"\n :course-id=\"selectedCourseId\"\n @preferences-saved=\"onPreferencesSaved\"\n />\n </div>\n</template>\n\n<script lang=\"ts\">\nimport { getCurrentUser, useConfigStore, UserTagPreferences } from '@vue-skuilder/common-ui';\nimport { UserDBInterface, getDataLayer } from '@vue-skuilder/db';\nimport { CourseConfig } from '@vue-skuilder/common';\nimport confetti from 'canvas-confetti';\nimport { defineComponent, PropType } from 'vue';\nimport { useRoute } from 'vue-router';\n\ninterface Language {\n name: string;\n code: string;\n}\n\nexport default defineComponent({\n name: 'UserSettings',\n\n components: {\n UserTagPreferences,\n },\n\n props: {\n username: {\n type: String as PropType<string>,\n required: true,\n },\n },\n\n setup() {\n const configStore = useConfigStore();\n const route = useRoute();\n\n const darkMode = configStore.config.darkMode;\n const likesConfetti = configStore.config.likesConfetti;\n\n const isNewUser = route.path.endsWith('new');\n\n return { configStore, darkMode, likesConfetti, route, isNewUser };\n },\n\n data() {\n return {\n u: {} as UserDBInterface,\n configLanguages: [\n {\n name: 'English',\n code: 'en',\n },\n {\n name: 'French',\n code: 'fr',\n },\n ] as Language[],\n selectedLanguages: [] as string[],\n registeredCourses: [] as CourseConfig[],\n selectedCourseId: '' as string,\n };\n },\n\n async created() {\n this.u = await getCurrentUser();\n this.configLanguages.forEach((l) => {\n console.log(`afweatifvwzeatfvwzeta` + l.name);\n });\n\n // Load registered courses for preferences selector\n await this.loadRegisteredCourses();\n },\n\n methods: {\n updateDark(): void {\n this.configStore.updateDarkMode(this.configStore.config.darkMode);\n },\n\n updateConfetti(): void {\n this.configStore.updateLikesConfetti(this.configStore.config.likesConfetti);\n\n if (this.configStore.config.likesConfetti) {\n confetti({\n origin: {\n x: 0.5,\n y: 1,\n },\n });\n }\n },\n\n async loadRegisteredCourses(): Promise<void> {\n try {\n const activeCourses = await this.u.getActiveCourses();\n const dataLayer = getDataLayer();\n\n // Fetch course configs for display names\n const courseConfigs = await Promise.all(\n activeCourses.map(async (reg) => {\n try {\n const courseDB = dataLayer.getCourseDB(reg.courseID);\n return await courseDB.getCourseConfig();\n } catch {\n // Return minimal config if fetch fails\n return { courseID: reg.courseID, name: reg.courseID } as CourseConfig;\n }\n })\n );\n\n this.registeredCourses = courseConfigs.filter(Boolean);\n } catch (e) {\n console.error('Failed to load registered courses:', e);\n }\n },\n\n onPreferencesSaved(): void {\n // Could show a snackbar or other feedback here\n console.log('Preferences saved for course:', this.selectedCourseId);\n },\n },\n});\n</script>\n\n<style scoped></style>\n"],"mappings":"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"}
1
+ {"version":3,"file":"User-C0xEcKaU.js","names":[],"sources":["../../src/views/User.vue","../../src/views/User.vue"],"sourcesContent":["<template>\n <div class=\"text-subtitle-1\">\n <v-alert v-if=\"isNewUser\" type=\"success\" class=\"text-subtitle-1\" variant=\"tonal\" :prepend-icon=\"'mdi-check'\">\n Welcome, {{ username }}! Please take a moment to look through these settings:\n </v-alert>\n\n <h1 class=\"text-h3\">Account Settings</h1>\n <h2 class=\"text-h4\">General:</h2>\n\n <v-checkbox\n v-model=\"configStore.config.likesConfetti\"\n label=\"I like confetti\"\n @update:model-value=\"updateConfetti\"\n />\n <v-checkbox v-model=\"configStore.config.darkMode\" label=\"I like the dark\" @update:model-value=\"updateDark\" />\n <!-- <h2 class=\"text-h4\">Languages:</h2>\n I am near-fluent or better in the following languages:\n {{ selectedLanguages.toString() }}\n <v-checkbox\n v-for=\"(language, i) in configLanguages\"\n :key=\"i\"\n :label=\"language.name\"\n :value=\"language.code\"\n v-model=\"selectedLanguages\"\n @update:model-value=\"updateLanguage\"\n /> -->\n\n <v-divider class=\"my-6\" />\n\n <h2 class=\"text-h4 mb-4\">Learning Preferences:</h2>\n <p class=\"text-body-2 text-medium-emphasis mb-4\">Customize how content is presented in your registered courses.</p>\n\n <v-select\n v-model=\"selectedCourseId\"\n :items=\"registeredCourses\"\n item-title=\"name\"\n item-value=\"courseID\"\n label=\"Select a course\"\n variant=\"outlined\"\n density=\"comfortable\"\n class=\"mb-4\"\n style=\"max-width: 400px\"\n >\n <template #item=\"{ props, item }\">\n <v-list-item v-bind=\"props\">\n <template #subtitle>\n {{ item.raw.courseID }}\n </template>\n </v-list-item>\n </template>\n </v-select>\n\n <user-tag-preferences\n v-if=\"selectedCourseId\"\n :course-id=\"selectedCourseId\"\n @preferences-saved=\"onPreferencesSaved\"\n />\n </div>\n</template>\n\n<script lang=\"ts\">\nimport { getCurrentUser, useConfigStore, UserTagPreferences } from '@vue-skuilder/common-ui';\nimport { UserDBInterface, getDataLayer } from '@vue-skuilder/db';\nimport { CourseConfig } from '@vue-skuilder/common';\nimport confetti from 'canvas-confetti';\nimport { defineComponent, PropType } from 'vue';\nimport { useRoute } from 'vue-router';\n\ninterface Language {\n name: string;\n code: string;\n}\n\nexport default defineComponent({\n name: 'UserSettings',\n\n components: {\n UserTagPreferences,\n },\n\n props: {\n username: {\n type: String as PropType<string>,\n required: true,\n },\n },\n\n setup() {\n const configStore = useConfigStore();\n const route = useRoute();\n\n const darkMode = configStore.config.darkMode;\n const likesConfetti = configStore.config.likesConfetti;\n\n const isNewUser = route.path.endsWith('new');\n\n return { configStore, darkMode, likesConfetti, route, isNewUser };\n },\n\n data() {\n return {\n u: {} as UserDBInterface,\n configLanguages: [\n {\n name: 'English',\n code: 'en',\n },\n {\n name: 'French',\n code: 'fr',\n },\n ] as Language[],\n selectedLanguages: [] as string[],\n registeredCourses: [] as CourseConfig[],\n selectedCourseId: '' as string,\n };\n },\n\n async created() {\n this.u = await getCurrentUser();\n this.configLanguages.forEach((l) => {\n console.log(`afweatifvwzeatfvwzeta` + l.name);\n });\n\n // Load registered courses for preferences selector\n await this.loadRegisteredCourses();\n },\n\n methods: {\n updateDark(): void {\n this.configStore.updateDarkMode(this.configStore.config.darkMode);\n },\n\n updateConfetti(): void {\n this.configStore.updateLikesConfetti(this.configStore.config.likesConfetti);\n\n if (this.configStore.config.likesConfetti) {\n confetti({\n origin: {\n x: 0.5,\n y: 1,\n },\n });\n }\n },\n\n async loadRegisteredCourses(): Promise<void> {\n try {\n const activeCourses = await this.u.getActiveCourses();\n const dataLayer = getDataLayer();\n\n // Fetch course configs for display names\n const courseConfigs = await Promise.all(\n activeCourses.map(async (reg) => {\n try {\n const courseDB = dataLayer.getCourseDB(reg.courseID);\n return await courseDB.getCourseConfig();\n } catch {\n // Return minimal config if fetch fails\n return { courseID: reg.courseID, name: reg.courseID } as CourseConfig;\n }\n })\n );\n\n this.registeredCourses = courseConfigs.filter(Boolean);\n } catch (e) {\n console.error('Failed to load registered courses:', e);\n }\n },\n\n onPreferencesSaved(): void {\n // Could show a snackbar or other feedback here\n console.log('Preferences saved for course:', this.selectedCourseId);\n },\n },\n});\n</script>\n\n<style scoped></style>\n","<template>\n <div class=\"text-subtitle-1\">\n <v-alert v-if=\"isNewUser\" type=\"success\" class=\"text-subtitle-1\" variant=\"tonal\" :prepend-icon=\"'mdi-check'\">\n Welcome, {{ username }}! Please take a moment to look through these settings:\n </v-alert>\n\n <h1 class=\"text-h3\">Account Settings</h1>\n <h2 class=\"text-h4\">General:</h2>\n\n <v-checkbox\n v-model=\"configStore.config.likesConfetti\"\n label=\"I like confetti\"\n @update:model-value=\"updateConfetti\"\n />\n <v-checkbox v-model=\"configStore.config.darkMode\" label=\"I like the dark\" @update:model-value=\"updateDark\" />\n <!-- <h2 class=\"text-h4\">Languages:</h2>\n I am near-fluent or better in the following languages:\n {{ selectedLanguages.toString() }}\n <v-checkbox\n v-for=\"(language, i) in configLanguages\"\n :key=\"i\"\n :label=\"language.name\"\n :value=\"language.code\"\n v-model=\"selectedLanguages\"\n @update:model-value=\"updateLanguage\"\n /> -->\n\n <v-divider class=\"my-6\" />\n\n <h2 class=\"text-h4 mb-4\">Learning Preferences:</h2>\n <p class=\"text-body-2 text-medium-emphasis mb-4\">Customize how content is presented in your registered courses.</p>\n\n <v-select\n v-model=\"selectedCourseId\"\n :items=\"registeredCourses\"\n item-title=\"name\"\n item-value=\"courseID\"\n label=\"Select a course\"\n variant=\"outlined\"\n density=\"comfortable\"\n class=\"mb-4\"\n style=\"max-width: 400px\"\n >\n <template #item=\"{ props, item }\">\n <v-list-item v-bind=\"props\">\n <template #subtitle>\n {{ item.raw.courseID }}\n </template>\n </v-list-item>\n </template>\n </v-select>\n\n <user-tag-preferences\n v-if=\"selectedCourseId\"\n :course-id=\"selectedCourseId\"\n @preferences-saved=\"onPreferencesSaved\"\n />\n </div>\n</template>\n\n<script lang=\"ts\">\nimport { getCurrentUser, useConfigStore, UserTagPreferences } from '@vue-skuilder/common-ui';\nimport { UserDBInterface, getDataLayer } from '@vue-skuilder/db';\nimport { CourseConfig } from '@vue-skuilder/common';\nimport confetti from 'canvas-confetti';\nimport { defineComponent, PropType } from 'vue';\nimport { useRoute } from 'vue-router';\n\ninterface Language {\n name: string;\n code: string;\n}\n\nexport default defineComponent({\n name: 'UserSettings',\n\n components: {\n UserTagPreferences,\n },\n\n props: {\n username: {\n type: String as PropType<string>,\n required: true,\n },\n },\n\n setup() {\n const configStore = useConfigStore();\n const route = useRoute();\n\n const darkMode = configStore.config.darkMode;\n const likesConfetti = configStore.config.likesConfetti;\n\n const isNewUser = route.path.endsWith('new');\n\n return { configStore, darkMode, likesConfetti, route, isNewUser };\n },\n\n data() {\n return {\n u: {} as UserDBInterface,\n configLanguages: [\n {\n name: 'English',\n code: 'en',\n },\n {\n name: 'French',\n code: 'fr',\n },\n ] as Language[],\n selectedLanguages: [] as string[],\n registeredCourses: [] as CourseConfig[],\n selectedCourseId: '' as string,\n };\n },\n\n async created() {\n this.u = await getCurrentUser();\n this.configLanguages.forEach((l) => {\n console.log(`afweatifvwzeatfvwzeta` + l.name);\n });\n\n // Load registered courses for preferences selector\n await this.loadRegisteredCourses();\n },\n\n methods: {\n updateDark(): void {\n this.configStore.updateDarkMode(this.configStore.config.darkMode);\n },\n\n updateConfetti(): void {\n this.configStore.updateLikesConfetti(this.configStore.config.likesConfetti);\n\n if (this.configStore.config.likesConfetti) {\n confetti({\n origin: {\n x: 0.5,\n y: 1,\n },\n });\n }\n },\n\n async loadRegisteredCourses(): Promise<void> {\n try {\n const activeCourses = await this.u.getActiveCourses();\n const dataLayer = getDataLayer();\n\n // Fetch course configs for display names\n const courseConfigs = await Promise.all(\n activeCourses.map(async (reg) => {\n try {\n const courseDB = dataLayer.getCourseDB(reg.courseID);\n return await courseDB.getCourseConfig();\n } catch {\n // Return minimal config if fetch fails\n return { courseID: reg.courseID, name: reg.courseID } as CourseConfig;\n }\n })\n );\n\n this.registeredCourses = courseConfigs.filter(Boolean);\n } catch (e) {\n console.error('Failed to load registered courses:', e);\n }\n },\n\n onPreferencesSaved(): void {\n // Could show a snackbar or other feedback here\n console.log('Preferences saved for course:', this.selectedCourseId);\n },\n },\n});\n</script>\n\n<style scoped></style>\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import"./chunk-D6hFPZc3.js";import{k as e}from"./PipelineDebugger-BHaqzqZw.js";import{d as t,n,o as r,s as i}from"./userDBKeys-DYLGKnVC.js";function getUserDB(){try{return t().getUserDB()}catch{return e.print(`[UserDB Debug] Data layer not initialized yet.`),null}}function getRawDB(){let t=getUserDB();return t?t.localDB||(e.print(`[UserDB Debug] Unable to access raw database instance.`),null):null}function formatTimestamp(e){return new Date(e).toLocaleString()}var a={showUser(){let t=getUserDB();t&&(console.group(`👤 User Information`),e.print(`Username: ${t.getUsername()}`),e.print(`Logged in: ${t.isLoggedIn()?`Yes ✅`:`No (Guest) ❌`}`),t.getConfig().then(t=>{e.print(`Configuration:`),e.print(JSON.stringify(t,null,2))}).catch(t=>{e.print(`Error loading config: ${t.message}`)}).finally(()=>{console.groupEnd()}))},async showScheduledReviews(t){let n=getUserDB();if(!n){e.print(`[UserDB Debug] Data layer not available`);return}e.print(`[UserDB Debug] Fetching pending reviews${t?` for course: ${t}`:``}...`);try{let r=await n.getPendingReviews(t);if(e.print(`[UserDB Debug] Got ${r.length} reviews`),console.group(`📅 Scheduled Reviews${t?` (${t})`:``}`),e.print(`Total: ${r.length}`),r.length>0){let t=new Map;for(let e of r)t.has(e.courseId)||t.set(e.courseId,[]),t.get(e.courseId).push(e);for(let[n,r]of t){console.group(`Course: ${n} (${r.length} reviews)`);let t=r.sort((e,t)=>{let n=typeof e.reviewTime==`string`?e.reviewTime:e.reviewTime.toISOString(),r=typeof t.reviewTime==`string`?t.reviewTime:t.reviewTime.toISOString();return new Date(n).getTime()-new Date(r).getTime()});for(let n of t.slice(0,10)){let t=typeof n.reviewTime==`string`?n.reviewTime:n.reviewTime.toISOString();e.print(` ${n.cardId.slice(0,12)}... @ ${formatTimestamp(t)} [${n.scheduledFor}/${n.schedulingAgentId}]`)}t.length>10&&e.print(` ... and ${t.length-10} more`),console.groupEnd()}}console.groupEnd()}catch(t){e.print(`Error loading scheduled reviews: ${t.message}`)}},async showCourseRegistrations(){let t=getUserDB();if(t)try{let n=await t.getActiveCourses();console.group(`📚 Course Registrations`),e.print(`Total: ${n.length}`),n.length>0&&console.table(n.map(e=>({courseId:e.courseID,status:e.status||`active`,elo:typeof e.elo==`number`?e.elo.toFixed(0):e.elo?.global?.score?.toFixed(0)||`N/A`}))),console.groupEnd()}catch(t){e.print(`Error loading course registrations: ${t.message}`)}},async showCardHistory(t){let a=getRawDB();if(a)try{let o=(await n(a,i[r.CARDRECORD])).rows.filter(e=>e.doc&&e.doc.cardID===t).map(e=>e.doc);if(console.group(`🎴 Card History: ${t}`),e.print(`Total interactions: ${o.length}`),o.length>0){let t=o.sort((e,t)=>new Date(t.timestamp).getTime()-new Date(e.timestamp).getTime());console.table(t.slice(0,20).map(e=>({time:formatTimestamp(e.timestamp),outcome:e.outcome||`N/A`,duration:e.duration?`${(e.duration/1e3).toFixed(1)}s`:`N/A`,courseId:e.courseId}))),t.length>20&&e.print(`... and ${t.length-20} more interactions`)}console.groupEnd()}catch(t){e.print(`Error loading card history: ${t.message}`)}},async queryByType(t,a=50){let o=getRawDB();if(o)try{let s=i[r[t]];if(!s){e.print(`Unknown document type: ${t}`);return}let c=await n(o,s);if(console.group(`📄 Documents: ${t}`),e.print(`Total: ${c.rows.length}`),e.print(`Prefix: ${s}`),c.rows.length>0){e.print(`Sample documents:`);let t=c.rows.slice(0,Math.min(a,c.rows.length));for(let n of t)e.print(`\n${n.id}:`),e.print(JSON.stringify(n.doc,null,2));c.rows.length>a&&e.print(`\n... and ${c.rows.length-a} more documents`)}console.groupEnd()}catch(t){e.print(`Error querying documents: ${t.message}`)}},async dbInfo(){let t=getRawDB();if(t)try{let n=await t.info();console.group(`ℹ️ Database Information`),e.print(`Database name: ${n.db_name}`),e.print(`Total documents: ${n.doc_count}`),e.print(`Update sequence: ${n.update_seq}`),`disk_size`in n&&e.print(`Disk size: ${(n.disk_size||0)/1024/1024} MB`),e.print(`
1
+ import"./chunk-D6hFPZc3.js";import{k as e}from"./PipelineDebugger-DhnKlMgD.js";import{d as t,n,o as r,s as i}from"./userDBKeys-3SQjNVdK.js";function getUserDB(){try{return t().getUserDB()}catch{return e.print(`[UserDB Debug] Data layer not initialized yet.`),null}}function getRawDB(){let t=getUserDB();return t?t.localDB||(e.print(`[UserDB Debug] Unable to access raw database instance.`),null):null}function formatTimestamp(e){return new Date(e).toLocaleString()}var a={showUser(){let t=getUserDB();t&&(console.group(`👤 User Information`),e.print(`Username: ${t.getUsername()}`),e.print(`Logged in: ${t.isLoggedIn()?`Yes ✅`:`No (Guest) ❌`}`),t.getConfig().then(t=>{e.print(`Configuration:`),e.print(JSON.stringify(t,null,2))}).catch(t=>{e.print(`Error loading config: ${t.message}`)}).finally(()=>{console.groupEnd()}))},async showScheduledReviews(t){let n=getUserDB();if(!n){e.print(`[UserDB Debug] Data layer not available`);return}e.print(`[UserDB Debug] Fetching pending reviews${t?` for course: ${t}`:``}...`);try{let r=await n.getPendingReviews(t);if(e.print(`[UserDB Debug] Got ${r.length} reviews`),console.group(`📅 Scheduled Reviews${t?` (${t})`:``}`),e.print(`Total: ${r.length}`),r.length>0){let t=new Map;for(let e of r)t.has(e.courseId)||t.set(e.courseId,[]),t.get(e.courseId).push(e);for(let[n,r]of t){console.group(`Course: ${n} (${r.length} reviews)`);let t=r.sort((e,t)=>{let n=typeof e.reviewTime==`string`?e.reviewTime:e.reviewTime.toISOString(),r=typeof t.reviewTime==`string`?t.reviewTime:t.reviewTime.toISOString();return new Date(n).getTime()-new Date(r).getTime()});for(let n of t.slice(0,10)){let t=typeof n.reviewTime==`string`?n.reviewTime:n.reviewTime.toISOString();e.print(` ${n.cardId.slice(0,12)}... @ ${formatTimestamp(t)} [${n.scheduledFor}/${n.schedulingAgentId}]`)}t.length>10&&e.print(` ... and ${t.length-10} more`),console.groupEnd()}}console.groupEnd()}catch(t){e.print(`Error loading scheduled reviews: ${t.message}`)}},async showCourseRegistrations(){let t=getUserDB();if(t)try{let n=await t.getActiveCourses();console.group(`📚 Course Registrations`),e.print(`Total: ${n.length}`),n.length>0&&console.table(n.map(e=>({courseId:e.courseID,status:e.status||`active`,elo:typeof e.elo==`number`?e.elo.toFixed(0):e.elo?.global?.score?.toFixed(0)||`N/A`}))),console.groupEnd()}catch(t){e.print(`Error loading course registrations: ${t.message}`)}},async showCardHistory(t){let a=getRawDB();if(a)try{let o=(await n(a,i[r.CARDRECORD])).rows.filter(e=>e.doc&&e.doc.cardID===t).map(e=>e.doc);if(console.group(`🎴 Card History: ${t}`),e.print(`Total interactions: ${o.length}`),o.length>0){let t=o.sort((e,t)=>new Date(t.timestamp).getTime()-new Date(e.timestamp).getTime());console.table(t.slice(0,20).map(e=>({time:formatTimestamp(e.timestamp),outcome:e.outcome||`N/A`,duration:e.duration?`${(e.duration/1e3).toFixed(1)}s`:`N/A`,courseId:e.courseId}))),t.length>20&&e.print(`... and ${t.length-20} more interactions`)}console.groupEnd()}catch(t){e.print(`Error loading card history: ${t.message}`)}},async queryByType(t,a=50){let o=getRawDB();if(o)try{let s=i[r[t]];if(!s){e.print(`Unknown document type: ${t}`);return}let c=await n(o,s);if(console.group(`📄 Documents: ${t}`),e.print(`Total: ${c.rows.length}`),e.print(`Prefix: ${s}`),c.rows.length>0){e.print(`Sample documents:`);let t=c.rows.slice(0,Math.min(a,c.rows.length));for(let n of t)e.print(`\n${n.id}:`),e.print(JSON.stringify(n.doc,null,2));c.rows.length>a&&e.print(`\n... and ${c.rows.length-a} more documents`)}console.groupEnd()}catch(t){e.print(`Error querying documents: ${t.message}`)}},async dbInfo(){let t=getRawDB();if(t)try{let n=await t.info();console.group(`ℹ️ Database Information`),e.print(`Database name: ${n.db_name}`),e.print(`Total documents: ${n.doc_count}`),e.print(`Update sequence: ${n.update_seq}`),`disk_size`in n&&e.print(`Disk size: ${(n.disk_size||0)/1024/1024} MB`),e.print(`
2
2
  Document counts by type:`);let r=await t.allDocs({include_docs:!1}),a=new Map;for(let e of r.rows){let t=`other`;for(let[n,r]of Object.entries(i))if(e.id.startsWith(r)){t=n;break}a.set(t,(a.get(t)||0)+1)}console.table(Array.from(a.entries()).sort((e,t)=>t[1]-e[1]).map(([e,t])=>({type:e,count:t}))),console.groupEnd()}catch(t){e.print(`Error getting database info: ${t.message}`)}},listDocTypes(){console.group(`📋 Available Document Types`),e.print(`Use with queryByType(type):`);for(let[t,n]of Object.entries(i))e.print(` ${t.padEnd(30)} → prefix: "${n}"`);console.groupEnd()},async export(t=!1){let n=getRawDB(),r=getUserDB();if(!n||!r)return`{}`;try{let a={username:r.getUsername(),loggedIn:r.isLoggedIn(),timestamp:new Date().toISOString()};if(t){let e=await n.allDocs({include_docs:!0});a.documents=e.rows.map(e=>({id:e.id,doc:e.doc})),a.totalDocs=e.rows.length}else{let e=await n.allDocs({include_docs:!1});a.totalDocs=e.rows.length;let t=new Map;for(let n of e.rows){let e=`other`;for(let[t,r]of Object.entries(i))if(n.id.startsWith(r)){e=t;break}t.set(e,(t.get(e)||0)+1)}a.docCounts=Object.fromEntries(t)}let o=JSON.stringify(a,null,2);return e.print(`[UserDB Debug] Database info exported. Copy the returned string or use:`),e.print(` copy(window.skuilder.userdb.export())`),t||e.print(` For full content export: window.skuilder.userdb.export(true)`),o}catch(t){return e.print(`Error exporting database: ${t.message}`),`{}`}},async raw(t){let n=getRawDB();if(n)try{let r=await t(n);e.print(`[UserDB Debug] Query result:`),e.print(r)}catch(t){e.print(`[UserDB Debug] Query error: ${t.message}`)}},help(){e.print(`
3
3
  🔧 UserDB Debug API
4
4
 
@@ -20,4 +20,4 @@ Examples:
20
20
  window.skuilder.userdb.queryByType('SCHEDULED_CARD', 10)
21
21
  window.skuilder.userdb.raw(db => db.allDocs({ limit: 5 }))
22
22
  `)}};function mountUserDBDebugger(){if(typeof window>`u`)return;let e=window;e.skuilder=e.skuilder||{},e.skuilder.userdb=a}export{a as n,mountUserDBDebugger as t};
23
- //# sourceMappingURL=UserDBDebugger-DPqlc4O_.js.map
23
+ //# sourceMappingURL=UserDBDebugger-BXLoT28w.js.map