@vue-skuilder/common-ui 0.2.35 → 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 (190) hide show
  1. package/dist/_virtual/_@oxc-project_runtime@0.115.0/helpers/defineProperty.mjs +13 -0
  2. package/dist/_virtual/_@oxc-project_runtime@0.115.0/helpers/toPrimitive.mjs +15 -0
  3. package/dist/_virtual/_@oxc-project_runtime@0.115.0/helpers/toPropertyKey.mjs +10 -0
  4. package/dist/_virtual/_@oxc-project_runtime@0.115.0/helpers/typeof.mjs +12 -0
  5. package/dist/_virtual/_plugin-vue_export-helper.mjs +9 -0
  6. package/dist/_virtual/_rolldown/runtime.mjs +7 -0
  7. package/dist/admin/common-ui-admin.es.js +259 -23774
  8. package/dist/admin/common-ui-admin.es.js.map +1 -1
  9. package/dist/assets/index.css +2 -1
  10. package/dist/components/CardBrowser.mjs +63 -0
  11. package/dist/components/CardBrowser.mjs.map +1 -0
  12. package/dist/components/CardBrowser.vue_vue_type_script_lang.mjs +50 -0
  13. package/dist/components/CardBrowser.vue_vue_type_script_lang.mjs.map +1 -0
  14. package/dist/components/CardHistoryViewer.mjs +70 -0
  15. package/dist/components/CardHistoryViewer.mjs.map +1 -0
  16. package/dist/components/CardHistoryViewer.vue_vue_type_script_lang.mjs +108 -0
  17. package/dist/components/CardHistoryViewer.vue_vue_type_script_lang.mjs.map +1 -0
  18. package/dist/components/CardSearch.mjs +26 -0
  19. package/dist/components/CardSearch.mjs.map +1 -0
  20. package/dist/components/CardSearch.vue_vue_type_script_lang.mjs +17 -0
  21. package/dist/components/CardSearch.vue_vue_type_script_lang.mjs.map +1 -0
  22. package/dist/components/CardSearchResults.mjs +32 -0
  23. package/dist/components/CardSearchResults.mjs.map +1 -0
  24. package/dist/components/CardSearchResults.vue_vue_type_script_lang.mjs +71 -0
  25. package/dist/components/CardSearchResults.vue_vue_type_script_lang.mjs.map +1 -0
  26. package/dist/components/CourseCardBrowser.mjs +175 -0
  27. package/dist/components/CourseCardBrowser.mjs.map +1 -0
  28. package/dist/components/CourseCardBrowser.vue_vue_type_script_lang.mjs +173 -0
  29. package/dist/components/CourseCardBrowser.vue_vue_type_script_lang.mjs.map +1 -0
  30. package/dist/components/CourseInformation.mjs +132 -0
  31. package/dist/components/CourseInformation.mjs.map +1 -0
  32. package/dist/components/CourseInformation.vue_vue_type_script_lang.mjs +56 -0
  33. package/dist/components/CourseInformation.vue_vue_type_script_lang.mjs.map +1 -0
  34. package/dist/components/CourseTagFilterWidget.mjs +114 -0
  35. package/dist/components/CourseTagFilterWidget.mjs.map +1 -0
  36. package/dist/components/CourseTagFilterWidget.vue_vue_type_script_lang.mjs +65 -0
  37. package/dist/components/CourseTagFilterWidget.vue_vue_type_script_lang.mjs.map +1 -0
  38. package/dist/components/HeatMap.mjs +40 -0
  39. package/dist/components/HeatMap.mjs.map +1 -0
  40. package/dist/components/HeatMap.vue_vue_type_script_lang.mjs +211 -0
  41. package/dist/components/HeatMap.vue_vue_type_script_lang.mjs.map +1 -0
  42. package/dist/components/PaginatingToolbar.mjs +94 -0
  43. package/dist/components/PaginatingToolbar.mjs.map +1 -0
  44. package/dist/components/PaginatingToolbar.vue_vue_type_script_lang.mjs +36 -0
  45. package/dist/components/PaginatingToolbar.vue_vue_type_script_lang.mjs.map +1 -0
  46. package/dist/components/SessionControllerDebug.mjs +130 -0
  47. package/dist/components/SessionControllerDebug.mjs.map +1 -0
  48. package/dist/components/SessionControllerDebug.vue_vue_type_script_lang.mjs +45 -0
  49. package/dist/components/SessionControllerDebug.vue_vue_type_script_lang.mjs.map +1 -0
  50. package/dist/components/SkMouseTrap.mjs +66 -0
  51. package/dist/components/SkMouseTrap.mjs.map +1 -0
  52. package/dist/components/SkMouseTrap.vue_vue_type_script_lang.mjs +31 -0
  53. package/dist/components/SkMouseTrap.vue_vue_type_script_lang.mjs.map +1 -0
  54. package/dist/components/SkMouseTrapToolTip.mjs +28 -0
  55. package/dist/components/SkMouseTrapToolTip.mjs.map +1 -0
  56. package/dist/components/SkMouseTrapToolTip.vue_vue_type_script_lang.mjs +91 -0
  57. package/dist/components/SkMouseTrapToolTip.vue_vue_type_script_lang.mjs.map +1 -0
  58. package/dist/components/SnackbarService.mjs +24 -0
  59. package/dist/components/SnackbarService.mjs.map +1 -0
  60. package/dist/components/SnackbarService.vue_vue_type_script_lang.mjs +34 -0
  61. package/dist/components/SnackbarService.vue_vue_type_script_lang.mjs.map +1 -0
  62. package/dist/components/SnackbarService2.mjs +40 -0
  63. package/dist/components/SnackbarService2.mjs.map +1 -0
  64. package/dist/components/StudySession.mjs +123 -0
  65. package/dist/components/StudySession.mjs.map +1 -0
  66. package/dist/components/StudySession.vue_vue_type_script_lang.mjs +280 -0
  67. package/dist/components/StudySession.vue_vue_type_script_lang.mjs.map +1 -0
  68. package/dist/components/StudySessionTimer.mjs +50 -0
  69. package/dist/components/StudySessionTimer.mjs.map +1 -0
  70. package/dist/components/StudySessionTimer.vue_vue_type_script_lang.mjs +43 -0
  71. package/dist/components/StudySessionTimer.vue_vue_type_script_lang.mjs.map +1 -0
  72. package/dist/components/TagsInput.mjs +45 -0
  73. package/dist/components/TagsInput.mjs.map +1 -0
  74. package/dist/components/TagsInput.vue_vue_type_script_lang.mjs +117 -0
  75. package/dist/components/TagsInput.vue_vue_type_script_lang.mjs.map +1 -0
  76. package/dist/components/UserTagPreferences.mjs +205 -0
  77. package/dist/components/UserTagPreferences.mjs.map +1 -0
  78. package/dist/components/UserTagPreferences.vue_vue_type_script_lang.mjs +139 -0
  79. package/dist/components/UserTagPreferences.vue_vue_type_script_lang.mjs.map +1 -0
  80. package/dist/components/auth/RequestPasswordReset.mjs +96 -0
  81. package/dist/components/auth/RequestPasswordReset.mjs.map +1 -0
  82. package/dist/components/auth/RequestPasswordReset.vue_vue_type_script_lang.mjs +47 -0
  83. package/dist/components/auth/RequestPasswordReset.vue_vue_type_script_lang.mjs.map +1 -0
  84. package/dist/components/auth/ResetPassword.mjs +129 -0
  85. package/dist/components/auth/ResetPassword.mjs.map +1 -0
  86. package/dist/components/auth/ResetPassword.vue_vue_type_script_lang.mjs +69 -0
  87. package/dist/components/auth/ResetPassword.vue_vue_type_script_lang.mjs.map +1 -0
  88. package/dist/components/auth/UserChip.mjs +9 -0
  89. package/dist/components/auth/UserChip.mjs.map +1 -0
  90. package/dist/components/auth/UserChip.vue_vue_type_script_setup_true_lang.mjs +213 -0
  91. package/dist/components/auth/UserChip.vue_vue_type_script_setup_true_lang.mjs.map +1 -0
  92. package/dist/components/auth/UserLogin.mjs +9 -0
  93. package/dist/components/auth/UserLogin.mjs.map +1 -0
  94. package/dist/components/auth/UserLogin.vue_vue_type_script_setup_true_lang.mjs +155 -0
  95. package/dist/components/auth/UserLogin.vue_vue_type_script_setup_true_lang.mjs.map +1 -0
  96. package/dist/components/auth/UserLoginAndRegistrationContainer.mjs +9 -0
  97. package/dist/components/auth/UserLoginAndRegistrationContainer.mjs.map +1 -0
  98. package/dist/components/auth/UserLoginAndRegistrationContainer.vue_vue_type_script_setup_true_lang.mjs +126 -0
  99. package/dist/components/auth/UserLoginAndRegistrationContainer.vue_vue_type_script_setup_true_lang.mjs.map +1 -0
  100. package/dist/components/auth/UserRegistration.mjs +147 -0
  101. package/dist/components/auth/UserRegistration.mjs.map +1 -0
  102. package/dist/components/auth/UserRegistration.vue_vue_type_script_lang.mjs +130 -0
  103. package/dist/components/auth/UserRegistration.vue_vue_type_script_lang.mjs.map +1 -0
  104. package/dist/components/auth/VerifyEmail.mjs +118 -0
  105. package/dist/components/auth/VerifyEmail.mjs.map +1 -0
  106. package/dist/components/auth/VerifyEmail.vue_vue_type_script_lang.mjs +59 -0
  107. package/dist/components/auth/VerifyEmail.vue_vue_type_script_lang.mjs.map +1 -0
  108. package/dist/components/cardRendering/AudioAutoPlayer.mjs +9 -0
  109. package/dist/components/cardRendering/AudioAutoPlayer.mjs.map +1 -0
  110. package/dist/components/cardRendering/AudioAutoPlayer.vue_vue_type_script_setup_true_lang.mjs +69 -0
  111. package/dist/components/cardRendering/AudioAutoPlayer.vue_vue_type_script_setup_true_lang.mjs.map +1 -0
  112. package/dist/components/cardRendering/CardLoader.mjs +31 -0
  113. package/dist/components/cardRendering/CardLoader.mjs.map +1 -0
  114. package/dist/components/cardRendering/CardLoader.vue_vue_type_script_lang.mjs +75 -0
  115. package/dist/components/cardRendering/CardLoader.vue_vue_type_script_lang.mjs.map +1 -0
  116. package/dist/components/cardRendering/CardViewer.mjs +39 -0
  117. package/dist/components/cardRendering/CardViewer.mjs.map +1 -0
  118. package/dist/components/cardRendering/CardViewer.vue_vue_type_script_lang.mjs +66 -0
  119. package/dist/components/cardRendering/CardViewer.vue_vue_type_script_lang.mjs.map +1 -0
  120. package/dist/components/cardRendering/CodeBlockRenderer.mjs +8 -0
  121. package/dist/components/cardRendering/CodeBlockRenderer.mjs.map +1 -0
  122. package/dist/components/cardRendering/CodeBlockRenderer.vue_vue_type_script_setup_true_lang.mjs +44 -0
  123. package/dist/components/cardRendering/CodeBlockRenderer.vue_vue_type_script_setup_true_lang.mjs.map +1 -0
  124. package/dist/components/cardRendering/MarkdownRenderer.mjs +21 -0
  125. package/dist/components/cardRendering/MarkdownRenderer.mjs.map +1 -0
  126. package/dist/components/cardRendering/MarkdownRenderer.vue_vue_type_script_lang.mjs +24 -0
  127. package/dist/components/cardRendering/MarkdownRenderer.vue_vue_type_script_lang.mjs.map +1 -0
  128. package/dist/components/cardRendering/MarkdownRendererHelpers.mjs +79 -0
  129. package/dist/components/cardRendering/MarkdownRendererHelpers.mjs.map +1 -0
  130. package/dist/components/cardRendering/MdTokenRenderer.mjs +9 -0
  131. package/dist/components/cardRendering/MdTokenRenderer.mjs.map +1 -0
  132. package/dist/components/cardRendering/MdTokenRenderer.vue_vue_type_script_setup_true_lang.mjs +176 -0
  133. package/dist/components/cardRendering/MdTokenRenderer.vue_vue_type_script_setup_true_lang.mjs.map +1 -0
  134. package/dist/components/studentInputs/BaseUserInput.mjs +54 -0
  135. package/dist/components/studentInputs/BaseUserInput.mjs.map +1 -0
  136. package/dist/components/studentInputs/MultipleChoiceOption.mjs +26 -0
  137. package/dist/components/studentInputs/MultipleChoiceOption.mjs.map +1 -0
  138. package/dist/components/studentInputs/MultipleChoiceOption.vue_vue_type_script_lang.mjs +76 -0
  139. package/dist/components/studentInputs/MultipleChoiceOption.vue_vue_type_script_lang.mjs.map +1 -0
  140. package/dist/components/studentInputs/RadioMultipleChoice.mjs +33 -0
  141. package/dist/components/studentInputs/RadioMultipleChoice.mjs.map +1 -0
  142. package/dist/components/studentInputs/RadioMultipleChoice.vue_vue_type_script_lang.mjs +105 -0
  143. package/dist/components/studentInputs/RadioMultipleChoice.vue_vue_type_script_lang.mjs.map +1 -0
  144. package/dist/components/studentInputs/TrueFalse.mjs +19 -0
  145. package/dist/components/studentInputs/TrueFalse.mjs.map +1 -0
  146. package/dist/components/studentInputs/TrueFalse.vue_vue_type_script_lang.mjs +21 -0
  147. package/dist/components/studentInputs/TrueFalse.vue_vue_type_script_lang.mjs.map +1 -0
  148. package/dist/components/studentInputs/UserInputNumber.mjs +33 -0
  149. package/dist/components/studentInputs/UserInputNumber.mjs.map +1 -0
  150. package/dist/components/studentInputs/UserInputNumber.vue_vue_type_script_lang.mjs +25 -0
  151. package/dist/components/studentInputs/UserInputNumber.vue_vue_type_script_lang.mjs.map +1 -0
  152. package/dist/components/studentInputs/UserInputString.mjs +22 -0
  153. package/dist/components/studentInputs/UserInputString.mjs.map +1 -0
  154. package/dist/components/studentInputs/UserInputString.vue_vue_type_script_lang.mjs +23 -0
  155. package/dist/components/studentInputs/UserInputString.vue_vue_type_script_lang.mjs.map +1 -0
  156. package/dist/components/studentInputs/fillInInput.mjs +25 -0
  157. package/dist/components/studentInputs/fillInInput.mjs.map +1 -0
  158. package/dist/components/studentInputs/fillInInput.vue_vue_type_script_lang.mjs +26 -0
  159. package/dist/components/studentInputs/fillInInput.vue_vue_type_script_lang.mjs.map +1 -0
  160. package/dist/composables/CompositionViewable.mjs +65 -0
  161. package/dist/composables/CompositionViewable.mjs.map +1 -0
  162. package/dist/composables/Displayable.mjs +36 -0
  163. package/dist/composables/Displayable.mjs.map +1 -0
  164. package/dist/composables/useAuthUI.mjs +38 -0
  165. package/dist/composables/useAuthUI.mjs.map +1 -0
  166. package/dist/composables/useEntitlements.mjs +58 -0
  167. package/dist/composables/useEntitlements.mjs.map +1 -0
  168. package/dist/index.mjs +49 -0
  169. package/dist/plugins/pinia.mjs +12 -0
  170. package/dist/plugins/pinia.mjs.map +1 -0
  171. package/dist/services/authAPI.mjs +159 -0
  172. package/dist/services/authAPI.mjs.map +1 -0
  173. package/dist/stores/useAuthStore.mjs +68 -0
  174. package/dist/stores/useAuthStore.mjs.map +1 -0
  175. package/dist/stores/useCardPreviewModeStore.mjs +20 -0
  176. package/dist/stores/useCardPreviewModeStore.mjs.map +1 -0
  177. package/dist/stores/useConfigStore.mjs +60 -0
  178. package/dist/stores/useConfigStore.mjs.map +1 -0
  179. package/dist/utils/SkldrMouseTrap.mjs +48 -0
  180. package/dist/utils/SkldrMouseTrap.mjs.map +1 -0
  181. package/dist/utils/passwordValidation.mjs +12 -0
  182. package/dist/utils/passwordValidation.mjs.map +1 -0
  183. package/package.json +7 -8
  184. package/dist/MarkdownRenderer-U_iPiA2x.mjs +0 -4966
  185. package/dist/MarkdownRenderer-U_iPiA2x.mjs.map +0 -1
  186. package/dist/assets/MarkdownRenderer.css +0 -1
  187. package/dist/common-ui.es.js +0 -5090
  188. package/dist/common-ui.es.js.map +0 -1
  189. package/dist/common-ui.umd.js +0 -77
  190. package/dist/common-ui.umd.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CourseInformation.vue_vue_type_script_lang.mjs","names":[],"sources":["../../src/components/CourseInformation.vue"],"sourcesContent":["<template>\n <div v-if=\"!updatePending\">\n <slot name=\"header\" :course-config=\"courseConfig\" :course-id=\"courseId\">\n <h1 class=\"text-h4 mb-2\">{{ courseConfig.name }}</h1>\n </slot>\n\n <p class=\"text-body-2\">\n {{ courseConfig.description }}\n </p>\n\n <slot\n name=\"actions\"\n :user-is-registered=\"userIsRegistered\"\n :course-id=\"courseId\"\n :edit-mode=\"editMode\"\n :register=\"register\"\n :drop=\"drop\"\n >\n <!-- Default fallback content if no actions slot provided -->\n <transition name=\"component-fade\" mode=\"out-in\">\n <div v-if=\"userIsRegistered\">\n <v-btn color=\"success\" data-cy=\"focused-study-session-btn\" class=\"me-2\">Start ad study session</v-btn>\n <v-btn v-if=\"editMode === 'full'\" data-cy=\"add-content-btn\" color=\"indigo-lighten-1\" class=\"me-2\">\n <v-icon start>mdi-plus</v-icon>\n Add content\n </v-btn>\n <v-btn\n v-if=\"editMode === 'full'\"\n color=\"green-darken-2\"\n title=\"Rank course content for difficulty\"\n class=\"me-2\"\n >\n <v-icon start>mdi-format-list-numbered</v-icon>\n Arrange\n </v-btn>\n <v-btn v-if=\"editMode === 'full'\" color=\"error\" size=\"small\" variant=\"outlined\" @click=\"drop\">\n Drop this course\n </v-btn>\n </div>\n <div v-else>\n <v-btn data-cy=\"register-btn\" color=\"primary\" class=\"me-2\" @click=\"register\">Register</v-btn>\n <v-btn variant=\"outlined\" color=\"primary\" class=\"me-2\">Start a trial study session</v-btn>\n </div>\n </transition>\n </slot>\n\n <slot name=\"additional-content\"></slot>\n \n <v-card class=\"my-2\">\n <v-toolbar density=\"compact\">\n <v-toolbar-title>Tags</v-toolbar-title>\n <v-toolbar-items>\n <v-btn variant=\"text\">({{ tags.length }})</v-btn>\n </v-toolbar-items>\n </v-toolbar>\n <v-card-text>\n <span v-for=\"(tag, i) in tags\" :key=\"i\">\n <slot name=\"tag-link\" :tag=\"tag\" :course-id=\"courseId\">\n <v-chip variant=\"tonal\" class=\"me-2 mb-2\">\n {{ tag.name }}\n </v-chip>\n </slot>\n </span>\n </v-card-text>\n </v-card>\n\n <course-card-browser\n class=\"my-3\"\n :course-id=\"courseId\"\n :view-lookup-function=\"viewLookupFunction\"\n :edit-mode=\"editMode\"\n />\n </div>\n</template>\n\n<script lang=\"ts\">\nimport { defineComponent, PropType } from 'vue';\n// import { MidiConfig } from '@vue-skuilder/courseware'; // Removed to break circular dependency\nimport CourseCardBrowser from './CourseCardBrowser.vue';\nimport { log } from '@vue-skuilder/common';\nimport { CourseDBInterface, Tag, UserDBInterface, getDataLayer } from '@vue-skuilder/db';\nimport { CourseConfig } from '@vue-skuilder/common';\nimport { getCurrentUser } from '../stores/useAuthStore';\n\nexport default defineComponent({\n name: 'CourseInformation',\n\n components: {\n // MidiConfig, // Removed to break circular dependency\n CourseCardBrowser,\n },\n\n props: {\n courseId: {\n type: String as PropType<string>,\n required: true,\n },\n viewLookupFunction: {\n type: Function,\n required: false,\n default: (x: unknown) => {\n console.warn('No viewLookupFunction provided to CourseInformation');\n return null;\n },\n },\n editMode: {\n type: String as PropType<'none' | 'readonly' | 'full'>,\n required: false,\n default: 'full',\n },\n },\n\n data() {\n return {\n courseDB: null as CourseDBInterface | null,\n nameRules: [\n (value: string): string | boolean => {\n const max = 30;\n return value.length > max ? `Course name must be ${max} characters or less` : true;\n },\n ],\n updatePending: true,\n courseConfig: {} as CourseConfig,\n userIsRegistered: false,\n tags: [] as Tag[],\n user: null as UserDBInterface | null,\n };\n },\n\n computed: {\n // isPianoCourse removed - piano-specific logic should be in wrapper component\n },\n\n async created() {\n this.courseDB = getDataLayer().getCourseDB(this.courseId);\n this.user = await getCurrentUser();\n\n const userCourses = await this.user.getCourseRegistrationsDoc();\n\n // Admin users always have edit access (for studio mode)\n if (this.user.getUsername() === 'admin') {\n this.userIsRegistered = true;\n } else {\n this.userIsRegistered =\n userCourses.courses.filter((c) => {\n return c.courseID === this.courseId && (c.status === 'active' || c.status === undefined);\n }).length === 1;\n }\n\n this.courseConfig = (await this.courseDB!.getCourseConfig())!;\n this.tags = (await this.courseDB!.getCourseTagStubs()).rows.map((r) => r.doc!);\n this.updatePending = false;\n },\n\n methods: {\n async register() {\n log(`Registering for ${this.courseId}`);\n const res = await this.user!.registerForCourse(this.courseId);\n if (res.ok) {\n this.userIsRegistered = true;\n }\n },\n\n async drop() {\n log(`Dropping course ${this.courseId}`);\n const res = await this.user!.dropCourse(this.courseId);\n if (res.ok) {\n this.userIsRegistered = false;\n }\n },\n },\n});\n</script>\n\n<style scoped>\n.component-fade-enter-active,\n.component-fade-leave-active {\n transition: opacity 0.5s ease;\n}\n.component-fade-enter,\n.component-fade-leave-to {\n opacity: 0;\n}\n\n.component-scale-enter-active,\n.component-scale-leave-active {\n max-height: auto;\n transform: scale(1, 1);\n transform-origin: top;\n transition:\n transform 0.3s ease,\n max-height 0.3s ease;\n}\n.component-scale-enter,\n.component-fade-leave-to {\n max-height: 0px;\n transform: scale(1, 0);\n overflow: hidden;\n}\n</style>\n"],"mappings":";;;;;;;AAoFA,IAAA,IAAe,EAAgB;CAC7B,MAAM;CAEN,YAAY,EAEV,mBAAA,GACD;CAED,OAAO;EACL,UAAU;GACR,MAAM;GACN,UAAU;GACX;EACD,oBAAoB;GAClB,MAAM;GACN,UAAU;GACV,UAAU,OACR,QAAQ,KAAK,sDAAsD,EAC5D;GAEV;EACD,UAAU;GACR,MAAM;GACN,UAAU;GACV,SAAS;GACV;EACF;CAED,OAAO;AACL,SAAO;GACL,UAAU;GACV,WAAW,EACR,MAEQ,EAAM,SAAS,KAAM,8CAAkD,GAEjF;GACD,eAAe;GACf,cAAc,EAAC;GACf,kBAAkB;GAClB,MAAM,EAAC;GACP,MAAM;GACP;;CAGH,UAAU,EAET;CAED,MAAM,UAAU;AAEd,EADA,KAAK,WAAW,GAAc,CAAC,YAAY,KAAK,SAAS,EACzD,KAAK,OAAO,MAAM,GAAgB;EAElC,IAAM,IAAc,MAAM,KAAK,KAAK,2BAA2B;AAc/D,EAXI,KAAK,KAAK,aAAY,KAAM,UAC9B,KAAK,mBAAmB,KAExB,KAAK,mBACH,EAAY,QAAQ,QAAQ,MACnB,EAAE,aAAa,KAAK,aAAa,EAAE,WAAW,YAAY,EAAE,WAAW,KAAA,GAC9E,CAAC,WAAW,GAGlB,KAAK,eAAgB,MAAM,KAAK,SAAU,iBAAiB,EAC3D,KAAK,QAAQ,MAAM,KAAK,SAAU,mBAAmB,EAAE,KAAK,KAAK,MAAM,EAAE,IAAK,EAC9E,KAAK,gBAAgB;;CAGvB,SAAS;EACP,MAAM,WAAW;AAGf,GAFA,EAAI,mBAAmB,KAAK,WAAW,GAC3B,MAAM,KAAK,KAAM,kBAAkB,KAAK,SAAS,EACrD,OACN,KAAK,mBAAmB;;EAI5B,MAAM,OAAO;AAGX,GAFA,EAAI,mBAAmB,KAAK,WAAW,GAC3B,MAAM,KAAK,KAAM,WAAW,KAAK,SAAS,EAC9C,OACN,KAAK,mBAAmB;;EAG7B;CACF,CAAC"}
@@ -0,0 +1,114 @@
1
+ import "../_virtual/_rolldown/runtime.mjs";
2
+ import e from "../_virtual/_plugin-vue_export-helper.mjs";
3
+ import t from "./CourseTagFilterWidget.vue_vue_type_script_lang.mjs";
4
+ /* empty css */
5
+ import { createCommentVNode as n, createElementBlock as r, createSlots as i, createTextVNode as a, createVNode as o, mergeProps as s, openBlock as c, resolveComponent as l, toDisplayString as u, withCtx as d } from "vue";
6
+ //#region src/components/CourseTagFilterWidget.vue
7
+ var f = { class: "course-tag-filter-widget" }, p = {
8
+ key: 0,
9
+ class: "filter-summary text-caption mt-2"
10
+ }, m = { key: 0 }, h = { key: 1 }, g = { key: 2 };
11
+ function _sfc_render(e, t, _, v, y, b) {
12
+ let x = l("v-chip"), S = l("v-list-item"), C = l("v-autocomplete"), w = l("v-icon");
13
+ return c(), r("div", f, [
14
+ o(C, {
15
+ modelValue: e.localFilter.include,
16
+ "onUpdate:modelValue": t[0] || (t[0] = (t) => e.localFilter.include = t),
17
+ items: e.availableTags,
18
+ loading: e.loading,
19
+ disabled: e.loading,
20
+ label: "Include tags",
21
+ placeholder: "Type to search tags...",
22
+ hint: "Cards must have at least one of these tags",
23
+ "persistent-hint": "",
24
+ multiple: "",
25
+ chips: "",
26
+ "closable-chips": "",
27
+ clearable: "",
28
+ density: "compact",
29
+ variant: "outlined",
30
+ class: "mb-2",
31
+ "custom-filter": e.fuzzyFilter,
32
+ "no-data-text": "No matching tags found"
33
+ }, {
34
+ chip: d(({ props: e, item: t }) => [o(x, s(e, {
35
+ color: "primary",
36
+ variant: "tonal",
37
+ size: "small"
38
+ }), {
39
+ default: d(() => [a(u(t.raw), 1)]),
40
+ _: 2
41
+ }, 1040)]),
42
+ item: d(({ props: t, item: n }) => [o(S, s(t, { title: n.raw }), i({ _: 2 }, [e.tagSnippets[n.raw] ? {
43
+ name: "subtitle",
44
+ fn: d(() => [a(u(e.tagSnippets[n.raw]), 1)]),
45
+ key: "0"
46
+ } : void 0]), 1040, ["title"])]),
47
+ _: 1
48
+ }, 8, [
49
+ "modelValue",
50
+ "items",
51
+ "loading",
52
+ "disabled",
53
+ "custom-filter"
54
+ ]),
55
+ o(C, {
56
+ modelValue: e.localFilter.exclude,
57
+ "onUpdate:modelValue": t[1] || (t[1] = (t) => e.localFilter.exclude = t),
58
+ items: e.availableTagsForExclude,
59
+ loading: e.loading,
60
+ disabled: e.loading,
61
+ label: "Exclude tags",
62
+ placeholder: "Type to search tags...",
63
+ hint: "Cards with these tags will be excluded",
64
+ "persistent-hint": "",
65
+ multiple: "",
66
+ chips: "",
67
+ "closable-chips": "",
68
+ clearable: "",
69
+ density: "compact",
70
+ variant: "outlined",
71
+ "custom-filter": e.fuzzyFilter,
72
+ "no-data-text": "No matching tags found"
73
+ }, {
74
+ chip: d(({ props: e, item: t }) => [o(x, s(e, {
75
+ color: "error",
76
+ variant: "tonal",
77
+ size: "small"
78
+ }), {
79
+ default: d(() => [a(u(t.raw), 1)]),
80
+ _: 2
81
+ }, 1040)]),
82
+ item: d(({ props: t, item: n }) => [o(S, s(t, { title: n.raw }), i({ _: 2 }, [e.tagSnippets[n.raw] ? {
83
+ name: "subtitle",
84
+ fn: d(() => [a(u(e.tagSnippets[n.raw]), 1)]),
85
+ key: "0"
86
+ } : void 0]), 1040, ["title"])]),
87
+ _: 1
88
+ }, 8, [
89
+ "modelValue",
90
+ "items",
91
+ "loading",
92
+ "disabled",
93
+ "custom-filter"
94
+ ]),
95
+ e.hasActiveFilter ? (c(), r("div", p, [
96
+ o(w, {
97
+ size: "small",
98
+ color: "info",
99
+ class: "mr-1"
100
+ }, {
101
+ default: d(() => t[2] || (t[2] = [a("mdi-filter")])),
102
+ _: 1
103
+ }),
104
+ e.localFilter.include.length ? (c(), r("span", m, " Including: " + u(e.localFilter.include.join(", ")), 1)) : n("", !0),
105
+ e.localFilter.include.length && e.localFilter.exclude.length ? (c(), r("span", h, " · ")) : n("", !0),
106
+ e.localFilter.exclude.length ? (c(), r("span", g, " Excluding: " + u(e.localFilter.exclude.join(", ")), 1)) : n("", !0)
107
+ ])) : n("", !0)
108
+ ]);
109
+ }
110
+ var _ = /* @__PURE__ */ e(t, [["render", _sfc_render], ["__scopeId", "data-v-e1ba963b"]]);
111
+ //#endregion
112
+ export { _ as default };
113
+
114
+ //# sourceMappingURL=CourseTagFilterWidget.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CourseTagFilterWidget.mjs","names":[],"sources":["../../src/components/CourseTagFilterWidget.vue"],"sourcesContent":["<template>\n <div class=\"course-tag-filter-widget\">\n <v-autocomplete\n v-model=\"localFilter.include\"\n :items=\"availableTags\"\n :loading=\"loading\"\n :disabled=\"loading\"\n label=\"Include tags\"\n placeholder=\"Type to search tags...\"\n hint=\"Cards must have at least one of these tags\"\n persistent-hint\n multiple\n chips\n closable-chips\n clearable\n density=\"compact\"\n variant=\"outlined\"\n class=\"mb-2\"\n :custom-filter=\"fuzzyFilter\"\n no-data-text=\"No matching tags found\"\n >\n <template #chip=\"{ props, item }\">\n <v-chip\n v-bind=\"props\"\n color=\"primary\"\n variant=\"tonal\"\n size=\"small\"\n >\n {{ item.raw }}\n </v-chip>\n </template>\n <template #item=\"{ props, item }\">\n <v-list-item v-bind=\"props\" :title=\"item.raw\">\n <template #subtitle v-if=\"tagSnippets[item.raw]\">\n {{ tagSnippets[item.raw] }}\n </template>\n </v-list-item>\n </template>\n </v-autocomplete>\n\n <v-autocomplete\n v-model=\"localFilter.exclude\"\n :items=\"availableTagsForExclude\"\n :loading=\"loading\"\n :disabled=\"loading\"\n label=\"Exclude tags\"\n placeholder=\"Type to search tags...\"\n hint=\"Cards with these tags will be excluded\"\n persistent-hint\n multiple\n chips\n closable-chips\n clearable\n density=\"compact\"\n variant=\"outlined\"\n :custom-filter=\"fuzzyFilter\"\n no-data-text=\"No matching tags found\"\n >\n <template #chip=\"{ props, item }\">\n <v-chip\n v-bind=\"props\"\n color=\"error\"\n variant=\"tonal\"\n size=\"small\"\n >\n {{ item.raw }}\n </v-chip>\n </template>\n <template #item=\"{ props, item }\">\n <v-list-item v-bind=\"props\" :title=\"item.raw\">\n <template #subtitle v-if=\"tagSnippets[item.raw]\">\n {{ tagSnippets[item.raw] }}\n </template>\n </v-list-item>\n </template>\n </v-autocomplete>\n\n <div v-if=\"hasActiveFilter\" class=\"filter-summary text-caption mt-2\">\n <v-icon size=\"small\" color=\"info\" class=\"mr-1\">mdi-filter</v-icon>\n <span v-if=\"localFilter.include.length\">\n Including: {{ localFilter.include.join(', ') }}\n </span>\n <span v-if=\"localFilter.include.length && localFilter.exclude.length\"> · </span>\n <span v-if=\"localFilter.exclude.length\">\n Excluding: {{ localFilter.exclude.join(', ') }}\n </span>\n </div>\n </div>\n</template>\n\n<script lang=\"ts\">\nimport { defineComponent, PropType, ref, computed, watch, onMounted } from 'vue';\nimport { TagFilter, emptyTagFilter, hasActiveFilter as checkActiveFilter } from '@vue-skuilder/common';\nimport { getDataLayer, Tag } from '@vue-skuilder/db';\n\nexport default defineComponent({\n name: 'CourseTagFilterWidget',\n\n props: {\n courseId: {\n type: String,\n required: true,\n },\n modelValue: {\n type: Object as PropType<TagFilter | undefined>,\n default: undefined,\n },\n },\n\n emits: ['update:modelValue'],\n\n setup(props, { emit }) {\n const loading = ref(true);\n const availableTags = ref<string[]>([]);\n const tagSnippets = ref<Record<string, string>>({});\n\n // Local copy of the filter for editing\n const localFilter = ref<TagFilter>(\n props.modelValue ? { ...props.modelValue } : emptyTagFilter()\n );\n\n // Computed: tags available for exclude (not already in include)\n const availableTagsForExclude = computed(() => {\n return availableTags.value.filter(\n (tag) => !localFilter.value.include.includes(tag)\n );\n });\n\n // Computed: whether filter has any active constraints\n const hasActiveFilter = computed(() => {\n return checkActiveFilter(localFilter.value);\n });\n\n // Fuzzy filter function for autocomplete\n const fuzzyFilter = (itemText: string, queryText: string): boolean => {\n if (!queryText) return true;\n\n const query = queryText.toLowerCase();\n const text = itemText.toLowerCase();\n\n // Simple fuzzy match: all query characters appear in order\n let queryIndex = 0;\n for (let i = 0; i < text.length && queryIndex < query.length; i++) {\n if (text[i] === query[queryIndex]) {\n queryIndex++;\n }\n }\n return queryIndex === query.length;\n };\n\n // Watch for external changes to modelValue\n watch(\n () => props.modelValue,\n (newValue) => {\n if (newValue) {\n localFilter.value = { ...newValue };\n } else {\n localFilter.value = emptyTagFilter();\n }\n },\n { deep: true }\n );\n\n // Emit changes when local filter changes\n watch(\n localFilter,\n (newFilter) => {\n emit('update:modelValue', { ...newFilter });\n },\n { deep: true }\n );\n\n // Watch for courseId changes and reload tags\n watch(\n () => props.courseId,\n async (newCourseId) => {\n if (newCourseId) {\n await loadTags();\n }\n }\n );\n\n // Load tags for the course\n async function loadTags() {\n loading.value = true;\n try {\n const courseDB = getDataLayer().getCourseDB(props.courseId);\n const response = await courseDB.getCourseTagStubs();\n\n availableTags.value = [];\n tagSnippets.value = {};\n\n for (const row of response.rows) {\n if (row.doc) {\n const tag = row.doc as Tag;\n availableTags.value.push(tag.name);\n if (tag.snippet) {\n tagSnippets.value[tag.name] = tag.snippet;\n }\n }\n }\n\n // Sort alphabetically\n availableTags.value.sort((a, b) => a.localeCompare(b));\n } catch (error) {\n console.error('[CourseTagFilterWidget] Failed to load tags:', error);\n availableTags.value = [];\n tagSnippets.value = {};\n } finally {\n loading.value = false;\n }\n }\n\n onMounted(() => {\n if (props.courseId) {\n loadTags();\n }\n });\n\n return {\n loading,\n availableTags,\n availableTagsForExclude,\n tagSnippets,\n localFilter,\n hasActiveFilter,\n fuzzyFilter,\n };\n },\n});\n</script>\n\n<style scoped>\n.course-tag-filter-widget {\n width: 100%;\n}\n\n.filter-summary {\n color: rgba(var(--v-theme-on-surface), 0.7);\n}\n</style>\n"],"mappings":"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"}
@@ -0,0 +1,65 @@
1
+ import "../_virtual/_rolldown/runtime.mjs";
2
+ import { computed as e, defineComponent as t, onMounted as n, ref as r, watch as i } from "vue";
3
+ import { emptyTagFilter as a, hasActiveFilter as o } from "@vue-skuilder/common";
4
+ import { getDataLayer as s } from "@vue-skuilder/db";
5
+ //#region src/components/CourseTagFilterWidget.vue?vue&type=script&lang.ts
6
+ var c = t({
7
+ name: "CourseTagFilterWidget",
8
+ props: {
9
+ courseId: {
10
+ type: String,
11
+ required: !0
12
+ },
13
+ modelValue: {
14
+ type: Object,
15
+ default: void 0
16
+ }
17
+ },
18
+ emits: ["update:modelValue"],
19
+ setup(t, { emit: c }) {
20
+ let l = r(!0), u = r([]), d = r({}), f = r(t.modelValue ? { ...t.modelValue } : a()), p = e(() => u.value.filter((e) => !f.value.include.includes(e))), m = e(() => o(f.value)), fuzzyFilter = (e, t) => {
21
+ if (!t) return !0;
22
+ let n = t.toLowerCase(), r = e.toLowerCase(), i = 0;
23
+ for (let e = 0; e < r.length && i < n.length; e++) r[e] === n[i] && i++;
24
+ return i === n.length;
25
+ };
26
+ i(() => t.modelValue, (e) => {
27
+ e ? f.value = { ...e } : f.value = a();
28
+ }, { deep: !0 }), i(f, (e) => {
29
+ c("update:modelValue", { ...e });
30
+ }, { deep: !0 }), i(() => t.courseId, async (e) => {
31
+ e && await loadTags();
32
+ });
33
+ async function loadTags() {
34
+ l.value = !0;
35
+ try {
36
+ let e = await s().getCourseDB(t.courseId).getCourseTagStubs();
37
+ u.value = [], d.value = {};
38
+ for (let t of e.rows) if (t.doc) {
39
+ let e = t.doc;
40
+ u.value.push(e.name), e.snippet && (d.value[e.name] = e.snippet);
41
+ }
42
+ u.value.sort((e, t) => e.localeCompare(t));
43
+ } catch (e) {
44
+ console.error("[CourseTagFilterWidget] Failed to load tags:", e), u.value = [], d.value = {};
45
+ } finally {
46
+ l.value = !1;
47
+ }
48
+ }
49
+ return n(() => {
50
+ t.courseId && loadTags();
51
+ }), {
52
+ loading: l,
53
+ availableTags: u,
54
+ availableTagsForExclude: p,
55
+ tagSnippets: d,
56
+ localFilter: f,
57
+ hasActiveFilter: m,
58
+ fuzzyFilter
59
+ };
60
+ }
61
+ });
62
+ //#endregion
63
+ export { c as default };
64
+
65
+ //# sourceMappingURL=CourseTagFilterWidget.vue_vue_type_script_lang.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CourseTagFilterWidget.vue_vue_type_script_lang.mjs","names":[],"sources":["../../src/components/CourseTagFilterWidget.vue"],"sourcesContent":["<template>\n <div class=\"course-tag-filter-widget\">\n <v-autocomplete\n v-model=\"localFilter.include\"\n :items=\"availableTags\"\n :loading=\"loading\"\n :disabled=\"loading\"\n label=\"Include tags\"\n placeholder=\"Type to search tags...\"\n hint=\"Cards must have at least one of these tags\"\n persistent-hint\n multiple\n chips\n closable-chips\n clearable\n density=\"compact\"\n variant=\"outlined\"\n class=\"mb-2\"\n :custom-filter=\"fuzzyFilter\"\n no-data-text=\"No matching tags found\"\n >\n <template #chip=\"{ props, item }\">\n <v-chip\n v-bind=\"props\"\n color=\"primary\"\n variant=\"tonal\"\n size=\"small\"\n >\n {{ item.raw }}\n </v-chip>\n </template>\n <template #item=\"{ props, item }\">\n <v-list-item v-bind=\"props\" :title=\"item.raw\">\n <template #subtitle v-if=\"tagSnippets[item.raw]\">\n {{ tagSnippets[item.raw] }}\n </template>\n </v-list-item>\n </template>\n </v-autocomplete>\n\n <v-autocomplete\n v-model=\"localFilter.exclude\"\n :items=\"availableTagsForExclude\"\n :loading=\"loading\"\n :disabled=\"loading\"\n label=\"Exclude tags\"\n placeholder=\"Type to search tags...\"\n hint=\"Cards with these tags will be excluded\"\n persistent-hint\n multiple\n chips\n closable-chips\n clearable\n density=\"compact\"\n variant=\"outlined\"\n :custom-filter=\"fuzzyFilter\"\n no-data-text=\"No matching tags found\"\n >\n <template #chip=\"{ props, item }\">\n <v-chip\n v-bind=\"props\"\n color=\"error\"\n variant=\"tonal\"\n size=\"small\"\n >\n {{ item.raw }}\n </v-chip>\n </template>\n <template #item=\"{ props, item }\">\n <v-list-item v-bind=\"props\" :title=\"item.raw\">\n <template #subtitle v-if=\"tagSnippets[item.raw]\">\n {{ tagSnippets[item.raw] }}\n </template>\n </v-list-item>\n </template>\n </v-autocomplete>\n\n <div v-if=\"hasActiveFilter\" class=\"filter-summary text-caption mt-2\">\n <v-icon size=\"small\" color=\"info\" class=\"mr-1\">mdi-filter</v-icon>\n <span v-if=\"localFilter.include.length\">\n Including: {{ localFilter.include.join(', ') }}\n </span>\n <span v-if=\"localFilter.include.length && localFilter.exclude.length\"> · </span>\n <span v-if=\"localFilter.exclude.length\">\n Excluding: {{ localFilter.exclude.join(', ') }}\n </span>\n </div>\n </div>\n</template>\n\n<script lang=\"ts\">\nimport { defineComponent, PropType, ref, computed, watch, onMounted } from 'vue';\nimport { TagFilter, emptyTagFilter, hasActiveFilter as checkActiveFilter } from '@vue-skuilder/common';\nimport { getDataLayer, Tag } from '@vue-skuilder/db';\n\nexport default defineComponent({\n name: 'CourseTagFilterWidget',\n\n props: {\n courseId: {\n type: String,\n required: true,\n },\n modelValue: {\n type: Object as PropType<TagFilter | undefined>,\n default: undefined,\n },\n },\n\n emits: ['update:modelValue'],\n\n setup(props, { emit }) {\n const loading = ref(true);\n const availableTags = ref<string[]>([]);\n const tagSnippets = ref<Record<string, string>>({});\n\n // Local copy of the filter for editing\n const localFilter = ref<TagFilter>(\n props.modelValue ? { ...props.modelValue } : emptyTagFilter()\n );\n\n // Computed: tags available for exclude (not already in include)\n const availableTagsForExclude = computed(() => {\n return availableTags.value.filter(\n (tag) => !localFilter.value.include.includes(tag)\n );\n });\n\n // Computed: whether filter has any active constraints\n const hasActiveFilter = computed(() => {\n return checkActiveFilter(localFilter.value);\n });\n\n // Fuzzy filter function for autocomplete\n const fuzzyFilter = (itemText: string, queryText: string): boolean => {\n if (!queryText) return true;\n\n const query = queryText.toLowerCase();\n const text = itemText.toLowerCase();\n\n // Simple fuzzy match: all query characters appear in order\n let queryIndex = 0;\n for (let i = 0; i < text.length && queryIndex < query.length; i++) {\n if (text[i] === query[queryIndex]) {\n queryIndex++;\n }\n }\n return queryIndex === query.length;\n };\n\n // Watch for external changes to modelValue\n watch(\n () => props.modelValue,\n (newValue) => {\n if (newValue) {\n localFilter.value = { ...newValue };\n } else {\n localFilter.value = emptyTagFilter();\n }\n },\n { deep: true }\n );\n\n // Emit changes when local filter changes\n watch(\n localFilter,\n (newFilter) => {\n emit('update:modelValue', { ...newFilter });\n },\n { deep: true }\n );\n\n // Watch for courseId changes and reload tags\n watch(\n () => props.courseId,\n async (newCourseId) => {\n if (newCourseId) {\n await loadTags();\n }\n }\n );\n\n // Load tags for the course\n async function loadTags() {\n loading.value = true;\n try {\n const courseDB = getDataLayer().getCourseDB(props.courseId);\n const response = await courseDB.getCourseTagStubs();\n\n availableTags.value = [];\n tagSnippets.value = {};\n\n for (const row of response.rows) {\n if (row.doc) {\n const tag = row.doc as Tag;\n availableTags.value.push(tag.name);\n if (tag.snippet) {\n tagSnippets.value[tag.name] = tag.snippet;\n }\n }\n }\n\n // Sort alphabetically\n availableTags.value.sort((a, b) => a.localeCompare(b));\n } catch (error) {\n console.error('[CourseTagFilterWidget] Failed to load tags:', error);\n availableTags.value = [];\n tagSnippets.value = {};\n } finally {\n loading.value = false;\n }\n }\n\n onMounted(() => {\n if (props.courseId) {\n loadTags();\n }\n });\n\n return {\n loading,\n availableTags,\n availableTagsForExclude,\n tagSnippets,\n localFilter,\n hasActiveFilter,\n fuzzyFilter,\n };\n },\n});\n</script>\n\n<style scoped>\n.course-tag-filter-widget {\n width: 100%;\n}\n\n.filter-summary {\n color: rgba(var(--v-theme-on-surface), 0.7);\n}\n</style>\n"],"mappings":";;;;;AA+FA,IAAA,IAAe,EAAgB;CAC7B,MAAM;CAEN,OAAO;EACL,UAAU;GACR,MAAM;GACN,UAAU;GACX;EACD,YAAY;GACV,MAAM;GACN,SAAS,KAAA;GACV;EACF;CAED,OAAO,CAAC,oBAAoB;CAE5B,MAAM,GAAO,EAAE,WAAQ;EACrB,IAAM,IAAU,EAAI,GAAK,EACnB,IAAgB,EAAc,EAAE,CAAC,EACjC,IAAc,EAA4B,EAAE,CAAC,EAG7C,IAAc,EAClB,EAAM,aAAa,EAAE,GAAG,EAAM,YAAW,GAAI,GAAe,CAC7D,EAGK,IAA0B,QACvB,EAAc,MAAM,QACxB,MAAQ,CAAC,EAAY,MAAM,QAAQ,SAAS,EAAG,CACjD,CACD,EAGI,IAAkB,QACf,EAAkB,EAAY,MAAM,CAC3C,EAGI,eAAe,GAAkB,MAA+B;AACpE,OAAI,CAAC,EAAW,QAAO;GAEvB,IAAM,IAAQ,EAAU,aAAa,EAC/B,IAAO,EAAS,aAAa,EAG/B,IAAa;AACjB,QAAK,IAAI,IAAI,GAAG,IAAI,EAAK,UAAU,IAAa,EAAM,QAAQ,IAC5D,CAAI,EAAK,OAAO,EAAM,MACpB;AAGJ,UAAO,MAAe,EAAM;;AA0B9B,EAtBA,QACQ,EAAM,aACX,MAAa;AACZ,GAAI,IACF,EAAY,QAAQ,EAAE,GAAG,GAAU,GAEnC,EAAY,QAAQ,GAAgB;KAGxC,EAAE,MAAM,IAAK,CACd,EAGD,EACE,IACC,MAAc;AACb,KAAK,qBAAqB,EAAE,GAAG,GAAW,CAAC;KAE7C,EAAE,MAAM,IAAK,CACd,EAGD,QACQ,EAAM,UACZ,OAAO,MAAgB;AACrB,GAAI,KACF,MAAM,UAAU;IAGrB;EAGD,eAAe,WAAW;AACxB,KAAQ,QAAQ;AAChB,OAAI;IAEF,IAAM,IAAW,MADA,GAAc,CAAC,YAAY,EAAM,SAAS,CAC3B,mBAAmB;AAGnD,IADA,EAAc,QAAQ,EAAE,EACxB,EAAY,QAAQ,EAAE;AAEtB,SAAK,IAAM,KAAO,EAAS,KACzB,KAAI,EAAI,KAAK;KACX,IAAM,IAAM,EAAI;AAEhB,KADA,EAAc,MAAM,KAAK,EAAI,KAAK,EAC9B,EAAI,YACN,EAAY,MAAM,EAAI,QAAQ,EAAI;;AAMxC,MAAc,MAAM,MAAM,GAAG,MAAM,EAAE,cAAc,EAAE,CAAC;YAC/C,GAAO;AAGd,IAFA,QAAQ,MAAM,gDAAgD,EAAM,EACpE,EAAc,QAAQ,EAAE,EACxB,EAAY,QAAQ,EAAE;aACd;AACR,MAAQ,QAAQ;;;AAUpB,SANA,QAAgB;AACd,GAAI,EAAM,YACR,UAAU;IAEZ,EAEK;GACL;GACA;GACA;GACA;GACA;GACA,iBAAA;GACA;GACD;;CAEJ,CAAC"}
@@ -0,0 +1,40 @@
1
+ import "../_virtual/_rolldown/runtime.mjs";
2
+ import e from "./HeatMap.vue_vue_type_script_lang.mjs";
3
+ /* empty css */
4
+ import t from "../_virtual/_plugin-vue_export-helper.mjs";
5
+ import { Fragment as n, createCommentVNode as r, createElementBlock as i, normalizeStyle as a, openBlock as o, renderList as s, toDisplayString as c } from "vue";
6
+ //#region src/components/HeatMap.vue
7
+ var l = ["width", "height"], u = ["transform"], d = [
8
+ "y",
9
+ "width",
10
+ "height",
11
+ "fill",
12
+ "onMouseover"
13
+ ];
14
+ function _sfc_render(e, t, f, p, m, h) {
15
+ return o(), i("div", null, [(o(), i("svg", {
16
+ width: e.width,
17
+ height: e.height
18
+ }, [(o(!0), i(n, null, s(e.weeks, (r, a) => (o(), i("g", {
19
+ key: a,
20
+ transform: `translate(${a * (e.cellSize + e.cellMargin)}, 0)`
21
+ }, [(o(!0), i(n, null, s(r, (n, r) => (o(), i("rect", {
22
+ key: n.date,
23
+ x: 0,
24
+ y: r * (e.cellSize + e.cellMargin),
25
+ width: e.cellSize,
26
+ height: e.cellSize,
27
+ fill: e.getColor(n.count),
28
+ onMouseover: (t) => e.showTooltip(n, t),
29
+ onMouseout: t[0] || (t[0] = (...t) => e.hideTooltip && e.hideTooltip(...t))
30
+ }, null, 40, d))), 128))], 8, u))), 128))], 8, l)), e.tooltipData ? (o(), i("div", {
31
+ key: 0,
32
+ class: "tooltip",
33
+ style: a(e.tooltipStyle)
34
+ }, c(e.tooltipData.count) + " review" + c(e.tooltipData.count === 1 ? "" : "s") + " on " + c(e.toDateString(e.tooltipData.date)), 5)) : r("", !0)]);
35
+ }
36
+ var f = /* @__PURE__ */ t(e, [["render", _sfc_render], ["__scopeId", "data-v-ca46239a"]]);
37
+ //#endregion
38
+ export { f as default };
39
+
40
+ //# sourceMappingURL=HeatMap.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HeatMap.mjs","names":[],"sources":["../../src/components/HeatMap.vue"],"sourcesContent":["<template>\n <div>\n <svg :width=\"width\" :height=\"height\">\n <g\n v-for=\"(week, weekIndex) in weeks\"\n :key=\"weekIndex\"\n :transform=\"`translate(${weekIndex * (cellSize + cellMargin)}, 0)`\"\n >\n <rect\n v-for=\"(day, dayIndex) in week\"\n :key=\"day.date\"\n :x=\"0\"\n :y=\"dayIndex * (cellSize + cellMargin)\"\n :width=\"cellSize\"\n :height=\"cellSize\"\n :fill=\"getColor(day.count)\"\n @mouseover=\"showTooltip(day, $event)\"\n @mouseout=\"hideTooltip\"\n />\n </g>\n </svg>\n <div v-if=\"tooltipData\" class=\"tooltip\" :style=\"tooltipStyle\">\n {{ tooltipData.count }} review{{ tooltipData.count !== 1 ? 's' : '' }} on {{ toDateString(tooltipData.date) }}\n </div>\n </div>\n</template>\n\n<script lang=\"ts\">\nimport { defineComponent, PropType } from 'vue';\nimport moment from 'moment';\nimport { DayData, Color, ActivityRecord } from './HeatMap.types';\n\nexport default defineComponent({\n name: 'HeatMap',\n\n props: {\n // Accept activity records directly as a prop\n activityRecords: {\n type: Array as PropType<ActivityRecord[]>,\n default: () => [],\n },\n // Accept a function that can retrieve activity records\n activityRecordsGetter: {\n type: Function as PropType<() => Promise<ActivityRecord[]>>,\n default: null,\n },\n // Customize colors\n inactiveColor: {\n type: Object as PropType<Color>,\n default: () => ({ h: 0, s: 0, l: 0.9 }),\n },\n activeColor: {\n type: Object as PropType<Color>,\n default: () => ({ h: 155, s: 1, l: 0.5 }),\n },\n // Customize size\n cellSize: {\n type: Number,\n default: 12,\n },\n cellMargin: {\n type: Number,\n default: 3,\n },\n // Enable/disable seasonal colors\n enableSeasonalColors: {\n type: Boolean,\n default: true,\n },\n },\n\n data() {\n return {\n isLoading: false,\n localActivityRecords: [] as ActivityRecord[],\n heatmapData: {} as { [key: string]: number },\n weeks: [] as DayData[][],\n tooltipData: null as DayData | null,\n tooltipStyle: {} as { [key: string]: string },\n maxInRange: 0,\n };\n },\n\n computed: {\n width(): number {\n return 53 * (this.cellSize + this.cellMargin);\n },\n height(): number {\n return 7 * (this.cellSize + this.cellMargin);\n },\n effectiveActivityRecords(): ActivityRecord[] {\n const useLocal = Array.isArray(this.localActivityRecords) && this.localActivityRecords.length > 0;\n const records = useLocal ? this.localActivityRecords : this.activityRecords || [];\n console.log('Using effectiveActivityRecords, count:', records.length, 'source:', useLocal ? 'local' : 'prop');\n return records;\n },\n },\n\n watch: {\n activityRecords: {\n handler() {\n this.processRecords();\n this.createWeeksData();\n },\n immediate: true,\n },\n },\n\n async created() {\n if (this.activityRecordsGetter) {\n try {\n this.isLoading = true;\n console.log('Fetching activity records using getter...');\n \n // Ensure the getter is called safely with proper error handling\n let result = await this.activityRecordsGetter();\n \n // Handle the result - ensure it's an array of activity records\n if (Array.isArray(result)) {\n // Filter out records with invalid timestamps before processing\n this.localActivityRecords = result.filter(record => {\n if (!record || !record.timeStamp) return false;\n \n // Basic validation check for timestamps\n try {\n const m = moment(record.timeStamp);\n return m.isValid() && m.year() > 2000 && m.year() < 2100;\n } catch (e) {\n return false;\n }\n });\n \n console.log(`Received ${result.length} records, ${this.localActivityRecords.length} valid after filtering`);\n \n // Process the loaded records\n this.processRecords();\n this.createWeeksData();\n } else {\n console.error('Activity records getter did not return an array:', result);\n this.localActivityRecords = [];\n }\n } catch (error) {\n console.error('Error fetching activity records:', error);\n this.localActivityRecords = [];\n } finally {\n this.isLoading = false;\n }\n } else {\n console.log('No activityRecordsGetter provided, using direct activityRecords prop');\n }\n },\n\n methods: {\n toDateString(d: string): string {\n const m = moment(d);\n return moment.months()[m.month()] + ' ' + m.date();\n },\n\n processRecords() {\n const records = this.effectiveActivityRecords || [];\n console.log(`Processing ${records.length} records`);\n\n const data: { [key: string]: number } = {};\n\n if (records.length === 0) {\n console.log('No records to process');\n this.heatmapData = data;\n return;\n }\n\n // Sample logging of a few records to understand structure without flooding console\n const uniqueDates = new Set<string>();\n const dateDistribution: Record<string, number> = {};\n let validCount = 0;\n let invalidCount = 0;\n \n for (let i = 0; i < records.length; i++) {\n const record = records[i];\n \n if (!record || typeof record !== 'object' || !record.timeStamp) {\n invalidCount++;\n continue;\n }\n \n try {\n // Attempt to normalize the timestamp\n let normalizedDate: string;\n \n if (typeof record.timeStamp === 'string') {\n // For ISO strings, parse directly with moment\n normalizedDate = moment(record.timeStamp).format('YYYY-MM-DD');\n } else if (typeof record.timeStamp === 'number') {\n // For numeric timestamps, use Date constructor then moment\n normalizedDate = moment(new Date(record.timeStamp)).format('YYYY-MM-DD');\n } else if (typeof record.timeStamp === 'object') {\n // For objects (like Moment), try toString() or direct parsing\n if (typeof record.timeStamp.format === 'function') {\n // It's likely a Moment object\n normalizedDate = record.timeStamp.format('YYYY-MM-DD');\n } else if (record.timeStamp instanceof Date) {\n normalizedDate = moment(record.timeStamp).format('YYYY-MM-DD');\n } else {\n // Try to parse it as a string representation\n normalizedDate = moment(String(record.timeStamp)).format('YYYY-MM-DD');\n }\n } else {\n // Unhandled type\n invalidCount++;\n continue;\n }\n \n // Verify the date is valid before using it\n if (moment(normalizedDate, 'YYYY-MM-DD', true).isValid()) {\n data[normalizedDate] = (data[normalizedDate] || 0) + 1;\n uniqueDates.add(normalizedDate);\n \n // Track distribution by month for debugging\n const month = normalizedDate.substring(0, 7); // YYYY-MM\n dateDistribution[month] = (dateDistribution[month] || 0) + 1;\n \n validCount++;\n } else {\n invalidCount++;\n }\n } catch (e) {\n invalidCount++;\n }\n }\n \n // Log summary statistics\n console.log(`Processed ${validCount} valid dates, ${invalidCount} invalid dates`);\n console.log(`Found ${uniqueDates.size} unique dates`);\n console.log('Date distribution by month:', dateDistribution);\n\n this.heatmapData = data;\n },\n\n createWeeksData() {\n // Reset weeks and max count\n this.weeks = [];\n this.maxInRange = 0;\n \n const end = moment();\n const start = end.clone().subtract(52, 'weeks');\n const day = start.clone().startOf('week');\n \n console.log('Creating weeks data from', start.format('YYYY-MM-DD'), 'to', end.format('YYYY-MM-DD'));\n \n // Ensure we have data to display\n if (Object.keys(this.heatmapData).length === 0) {\n console.log('No heatmap data available to display');\n }\n\n // For debugging, log some sample dates from the heatmap data\n const sampleDates = Object.keys(this.heatmapData).slice(0, 5);\n console.log('Sample dates in heatmap data:', sampleDates);\n \n // Build the week data structure\n while (day.isSameOrBefore(end)) {\n const weekData: DayData[] = [];\n for (let i = 0; i < 7; i++) {\n const date = day.format('YYYY-MM-DD');\n const count = this.heatmapData[date] || 0;\n const dayData: DayData = {\n date,\n count,\n };\n weekData.push(dayData);\n if (dayData.count > this.maxInRange) {\n this.maxInRange = dayData.count;\n }\n\n day.add(1, 'day');\n }\n this.weeks.push(weekData);\n }\n \n console.log('Weeks data created, maxInRange:', this.maxInRange);\n \n // Calculate summary stats for display\n let totalDaysWithActivity = 0;\n let totalActivity = 0;\n \n Object.values(this.heatmapData).forEach(count => {\n totalDaysWithActivity++;\n totalActivity += count;\n });\n \n console.log(`Activity summary: ${totalActivity} activities across ${totalDaysWithActivity} days`);\n },\n\n getColor(count: number): string {\n if (this.maxInRange === 0) return this.hslToString(this.inactiveColor);\n\n const t = count === 0 ? 0 : Math.min((2 * count) / this.maxInRange, 1);\n\n let seasonalColor: Color = this.activeColor;\n\n if (this.enableSeasonalColors) {\n const now = moment();\n if (now.month() === 11 && now.date() >= 5) {\n // Christmas colors\n seasonalColor = Math.random() > 0.5 ? { h: 350, s: 0.8, l: 0.5 } : { h: 135, s: 0.8, l: 0.4 };\n } else if (now.month() === 9 && now.date() >= 25) {\n // Halloween colors\n seasonalColor =\n Math.random() > 0.5\n ? { h: 0, s: 0, l: 0 }\n : Math.random() > 0.5\n ? { h: 30, s: 1, l: 0.5 }\n : { h: 270, s: 1, l: 0.5 };\n }\n }\n\n const h = seasonalColor.h;\n const s = this.interpolate(this.inactiveColor.s, seasonalColor.s, t);\n const l = this.interpolate(this.inactiveColor.l, seasonalColor.l, t);\n\n return this.hslToString({ h, s, l });\n },\n\n interpolate(start: number, end: number, t: number): number {\n return start + (end - start) * t;\n },\n\n hslToString(color: Color): string {\n return `hsl(${color.h}, ${color.s * 100}%, ${color.l * 100}%)`;\n },\n\n showTooltip(day: DayData, event: MouseEvent) {\n this.tooltipData = day;\n this.tooltipStyle = {\n position: 'absolute',\n left: `${event.pageX + 10}px`,\n top: `${event.pageY + 10}px`,\n };\n },\n\n hideTooltip() {\n this.tooltipData = null;\n },\n },\n});\n</script>\n\n<style scoped>\n.tooltip {\n background-color: rgba(0, 0, 0, 0.8);\n color: white;\n padding: 5px;\n border-radius: 3px;\n font-size: 12px;\n}\n</style>\n"],"mappings":";;;;;;QAAA,CAAA,SAAA,SAAA,MAAA,CAAA,YAAA,MAAA;CAAA;CAAA;CAAA;CAAA;CAAA;CAAA;;aACE,EAuBM,OAAA,MAAA,EAAA,GAAA,EAtBJ,EAkBM,OAAA;EAlBA,OAAO,EAAA;EAAQ,QAAQ,EAAA;aAC3B,EAgBI,GAAA,MAnBV,EAIoC,EAAA,QAApB,GAAM,YADhB,EAgBI,KAAA;EAdD,KAAK;EACL,WAAS,aAAe,KAAa,EAAA,WAAW,EAAA,YAAU;aAE3D,EAUE,GAAA,MAlBV,EASoC,IAAlB,GAAK,YADf,EAUE,QAAA;EARC,KAAK,EAAI;EACT,GAAG;EACH,GAAG,KAAY,EAAA,WAAW,EAAA;EAC1B,OAAO,EAAA;EACP,QAAQ,EAAA;EACR,MAAM,EAAA,SAAS,EAAI,MAAK;EACxB,cAAS,MAAE,EAAA,YAAY,GAAK,EAAM;EAClC,YAAQ,EAAA,OAAA,EAAA,MAAA,GAAA,MAAE,EAAA,eAAA,EAAA,YAAA,GAAA,EAAW;cAjBhC,EAAA,eAAA,EAAA,eAAA,EAAA,GAqBe,EAAA,eAAA,GAAA,EAAX,EAEM,OAAA;EAvBV,KAAA;EAqB4B,OAAM;EAAW,OArB7C,EAqBoD,EAAA,aAAY;MACvD,EAAA,YAAY,MAAK,GAAG,YAAO,EAAG,EAAA,YAAY,UAAK,IAAA,KAAA,IAAA,GAAoB,SAAI,EAAG,EAAA,aAAa,EAAA,YAAY,KAAI,CAAA,EAAA,EAAA,IAtBhH,EAAA,IAAA,GAAA,CAAA,CAAA"}
@@ -0,0 +1,211 @@
1
+ import "../_virtual/_rolldown/runtime.mjs";
2
+ import { defineComponent as e } from "vue";
3
+ import t from "moment";
4
+ //#region src/components/HeatMap.vue?vue&type=script&lang.ts
5
+ var n = e({
6
+ name: "HeatMap",
7
+ props: {
8
+ activityRecords: {
9
+ type: Array,
10
+ default: () => []
11
+ },
12
+ activityRecordsGetter: {
13
+ type: Function,
14
+ default: null
15
+ },
16
+ inactiveColor: {
17
+ type: Object,
18
+ default: () => ({
19
+ h: 0,
20
+ s: 0,
21
+ l: .9
22
+ })
23
+ },
24
+ activeColor: {
25
+ type: Object,
26
+ default: () => ({
27
+ h: 155,
28
+ s: 1,
29
+ l: .5
30
+ })
31
+ },
32
+ cellSize: {
33
+ type: Number,
34
+ default: 12
35
+ },
36
+ cellMargin: {
37
+ type: Number,
38
+ default: 3
39
+ },
40
+ enableSeasonalColors: {
41
+ type: Boolean,
42
+ default: !0
43
+ }
44
+ },
45
+ data() {
46
+ return {
47
+ isLoading: !1,
48
+ localActivityRecords: [],
49
+ heatmapData: {},
50
+ weeks: [],
51
+ tooltipData: null,
52
+ tooltipStyle: {},
53
+ maxInRange: 0
54
+ };
55
+ },
56
+ computed: {
57
+ width() {
58
+ return 53 * (this.cellSize + this.cellMargin);
59
+ },
60
+ height() {
61
+ return 7 * (this.cellSize + this.cellMargin);
62
+ },
63
+ effectiveActivityRecords() {
64
+ let e = Array.isArray(this.localActivityRecords) && this.localActivityRecords.length > 0, t = e ? this.localActivityRecords : this.activityRecords || [];
65
+ return console.log("Using effectiveActivityRecords, count:", t.length, "source:", e ? "local" : "prop"), t;
66
+ }
67
+ },
68
+ watch: { activityRecords: {
69
+ handler() {
70
+ this.processRecords(), this.createWeeksData();
71
+ },
72
+ immediate: !0
73
+ } },
74
+ async created() {
75
+ if (this.activityRecordsGetter) try {
76
+ this.isLoading = !0, console.log("Fetching activity records using getter...");
77
+ let e = await this.activityRecordsGetter();
78
+ Array.isArray(e) ? (this.localActivityRecords = e.filter((e) => {
79
+ if (!e || !e.timeStamp) return !1;
80
+ try {
81
+ let n = t(e.timeStamp);
82
+ return n.isValid() && n.year() > 2e3 && n.year() < 2100;
83
+ } catch {
84
+ return !1;
85
+ }
86
+ }), console.log(`Received ${e.length} records, ${this.localActivityRecords.length} valid after filtering`), this.processRecords(), this.createWeeksData()) : (console.error("Activity records getter did not return an array:", e), this.localActivityRecords = []);
87
+ } catch (e) {
88
+ console.error("Error fetching activity records:", e), this.localActivityRecords = [];
89
+ } finally {
90
+ this.isLoading = !1;
91
+ }
92
+ else console.log("No activityRecordsGetter provided, using direct activityRecords prop");
93
+ },
94
+ methods: {
95
+ toDateString(e) {
96
+ let n = t(e);
97
+ return t.months()[n.month()] + " " + n.date();
98
+ },
99
+ processRecords() {
100
+ let e = this.effectiveActivityRecords || [];
101
+ console.log(`Processing ${e.length} records`);
102
+ let n = {};
103
+ if (e.length === 0) {
104
+ console.log("No records to process"), this.heatmapData = n;
105
+ return;
106
+ }
107
+ let r = /* @__PURE__ */ new Set(), i = {}, a = 0, o = 0;
108
+ for (let s = 0; s < e.length; s++) {
109
+ let c = e[s];
110
+ if (!c || typeof c != "object" || !c.timeStamp) {
111
+ o++;
112
+ continue;
113
+ }
114
+ try {
115
+ let e;
116
+ if (typeof c.timeStamp == "string") e = t(c.timeStamp).format("YYYY-MM-DD");
117
+ else if (typeof c.timeStamp == "number") e = t(new Date(c.timeStamp)).format("YYYY-MM-DD");
118
+ else if (typeof c.timeStamp == "object") e = typeof c.timeStamp.format == "function" ? c.timeStamp.format("YYYY-MM-DD") : c.timeStamp instanceof Date ? t(c.timeStamp).format("YYYY-MM-DD") : t(String(c.timeStamp)).format("YYYY-MM-DD");
119
+ else {
120
+ o++;
121
+ continue;
122
+ }
123
+ if (t(e, "YYYY-MM-DD", !0).isValid()) {
124
+ n[e] = (n[e] || 0) + 1, r.add(e);
125
+ let t = e.substring(0, 7);
126
+ i[t] = (i[t] || 0) + 1, a++;
127
+ } else o++;
128
+ } catch {
129
+ o++;
130
+ }
131
+ }
132
+ console.log(`Processed ${a} valid dates, ${o} invalid dates`), console.log(`Found ${r.size} unique dates`), console.log("Date distribution by month:", i), this.heatmapData = n;
133
+ },
134
+ createWeeksData() {
135
+ this.weeks = [], this.maxInRange = 0;
136
+ let e = t(), n = e.clone().subtract(52, "weeks"), r = n.clone().startOf("week");
137
+ console.log("Creating weeks data from", n.format("YYYY-MM-DD"), "to", e.format("YYYY-MM-DD")), Object.keys(this.heatmapData).length === 0 && console.log("No heatmap data available to display");
138
+ let i = Object.keys(this.heatmapData).slice(0, 5);
139
+ for (console.log("Sample dates in heatmap data:", i); r.isSameOrBefore(e);) {
140
+ let e = [];
141
+ for (let t = 0; t < 7; t++) {
142
+ let t = r.format("YYYY-MM-DD"), n = {
143
+ date: t,
144
+ count: this.heatmapData[t] || 0
145
+ };
146
+ e.push(n), n.count > this.maxInRange && (this.maxInRange = n.count), r.add(1, "day");
147
+ }
148
+ this.weeks.push(e);
149
+ }
150
+ console.log("Weeks data created, maxInRange:", this.maxInRange);
151
+ let a = 0, o = 0;
152
+ Object.values(this.heatmapData).forEach((e) => {
153
+ a++, o += e;
154
+ }), console.log(`Activity summary: ${o} activities across ${a} days`);
155
+ },
156
+ getColor(e) {
157
+ if (this.maxInRange === 0) return this.hslToString(this.inactiveColor);
158
+ let n = e === 0 ? 0 : Math.min(2 * e / this.maxInRange, 1), r = this.activeColor;
159
+ if (this.enableSeasonalColors) {
160
+ let e = t();
161
+ e.month() === 11 && e.date() >= 5 ? r = Math.random() > .5 ? {
162
+ h: 350,
163
+ s: .8,
164
+ l: .5
165
+ } : {
166
+ h: 135,
167
+ s: .8,
168
+ l: .4
169
+ } : e.month() === 9 && e.date() >= 25 && (r = Math.random() > .5 ? {
170
+ h: 0,
171
+ s: 0,
172
+ l: 0
173
+ } : Math.random() > .5 ? {
174
+ h: 30,
175
+ s: 1,
176
+ l: .5
177
+ } : {
178
+ h: 270,
179
+ s: 1,
180
+ l: .5
181
+ });
182
+ }
183
+ let i = r.h, a = this.interpolate(this.inactiveColor.s, r.s, n), o = this.interpolate(this.inactiveColor.l, r.l, n);
184
+ return this.hslToString({
185
+ h: i,
186
+ s: a,
187
+ l: o
188
+ });
189
+ },
190
+ interpolate(e, t, n) {
191
+ return e + (t - e) * n;
192
+ },
193
+ hslToString(e) {
194
+ return `hsl(${e.h}, ${e.s * 100}%, ${e.l * 100}%)`;
195
+ },
196
+ showTooltip(e, t) {
197
+ this.tooltipData = e, this.tooltipStyle = {
198
+ position: "absolute",
199
+ left: `${t.pageX + 10}px`,
200
+ top: `${t.pageY + 10}px`
201
+ };
202
+ },
203
+ hideTooltip() {
204
+ this.tooltipData = null;
205
+ }
206
+ }
207
+ });
208
+ //#endregion
209
+ export { n as default };
210
+
211
+ //# sourceMappingURL=HeatMap.vue_vue_type_script_lang.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HeatMap.vue_vue_type_script_lang.mjs","names":[],"sources":["../../src/components/HeatMap.vue"],"sourcesContent":["<template>\n <div>\n <svg :width=\"width\" :height=\"height\">\n <g\n v-for=\"(week, weekIndex) in weeks\"\n :key=\"weekIndex\"\n :transform=\"`translate(${weekIndex * (cellSize + cellMargin)}, 0)`\"\n >\n <rect\n v-for=\"(day, dayIndex) in week\"\n :key=\"day.date\"\n :x=\"0\"\n :y=\"dayIndex * (cellSize + cellMargin)\"\n :width=\"cellSize\"\n :height=\"cellSize\"\n :fill=\"getColor(day.count)\"\n @mouseover=\"showTooltip(day, $event)\"\n @mouseout=\"hideTooltip\"\n />\n </g>\n </svg>\n <div v-if=\"tooltipData\" class=\"tooltip\" :style=\"tooltipStyle\">\n {{ tooltipData.count }} review{{ tooltipData.count !== 1 ? 's' : '' }} on {{ toDateString(tooltipData.date) }}\n </div>\n </div>\n</template>\n\n<script lang=\"ts\">\nimport { defineComponent, PropType } from 'vue';\nimport moment from 'moment';\nimport { DayData, Color, ActivityRecord } from './HeatMap.types';\n\nexport default defineComponent({\n name: 'HeatMap',\n\n props: {\n // Accept activity records directly as a prop\n activityRecords: {\n type: Array as PropType<ActivityRecord[]>,\n default: () => [],\n },\n // Accept a function that can retrieve activity records\n activityRecordsGetter: {\n type: Function as PropType<() => Promise<ActivityRecord[]>>,\n default: null,\n },\n // Customize colors\n inactiveColor: {\n type: Object as PropType<Color>,\n default: () => ({ h: 0, s: 0, l: 0.9 }),\n },\n activeColor: {\n type: Object as PropType<Color>,\n default: () => ({ h: 155, s: 1, l: 0.5 }),\n },\n // Customize size\n cellSize: {\n type: Number,\n default: 12,\n },\n cellMargin: {\n type: Number,\n default: 3,\n },\n // Enable/disable seasonal colors\n enableSeasonalColors: {\n type: Boolean,\n default: true,\n },\n },\n\n data() {\n return {\n isLoading: false,\n localActivityRecords: [] as ActivityRecord[],\n heatmapData: {} as { [key: string]: number },\n weeks: [] as DayData[][],\n tooltipData: null as DayData | null,\n tooltipStyle: {} as { [key: string]: string },\n maxInRange: 0,\n };\n },\n\n computed: {\n width(): number {\n return 53 * (this.cellSize + this.cellMargin);\n },\n height(): number {\n return 7 * (this.cellSize + this.cellMargin);\n },\n effectiveActivityRecords(): ActivityRecord[] {\n const useLocal = Array.isArray(this.localActivityRecords) && this.localActivityRecords.length > 0;\n const records = useLocal ? this.localActivityRecords : this.activityRecords || [];\n console.log('Using effectiveActivityRecords, count:', records.length, 'source:', useLocal ? 'local' : 'prop');\n return records;\n },\n },\n\n watch: {\n activityRecords: {\n handler() {\n this.processRecords();\n this.createWeeksData();\n },\n immediate: true,\n },\n },\n\n async created() {\n if (this.activityRecordsGetter) {\n try {\n this.isLoading = true;\n console.log('Fetching activity records using getter...');\n \n // Ensure the getter is called safely with proper error handling\n let result = await this.activityRecordsGetter();\n \n // Handle the result - ensure it's an array of activity records\n if (Array.isArray(result)) {\n // Filter out records with invalid timestamps before processing\n this.localActivityRecords = result.filter(record => {\n if (!record || !record.timeStamp) return false;\n \n // Basic validation check for timestamps\n try {\n const m = moment(record.timeStamp);\n return m.isValid() && m.year() > 2000 && m.year() < 2100;\n } catch (e) {\n return false;\n }\n });\n \n console.log(`Received ${result.length} records, ${this.localActivityRecords.length} valid after filtering`);\n \n // Process the loaded records\n this.processRecords();\n this.createWeeksData();\n } else {\n console.error('Activity records getter did not return an array:', result);\n this.localActivityRecords = [];\n }\n } catch (error) {\n console.error('Error fetching activity records:', error);\n this.localActivityRecords = [];\n } finally {\n this.isLoading = false;\n }\n } else {\n console.log('No activityRecordsGetter provided, using direct activityRecords prop');\n }\n },\n\n methods: {\n toDateString(d: string): string {\n const m = moment(d);\n return moment.months()[m.month()] + ' ' + m.date();\n },\n\n processRecords() {\n const records = this.effectiveActivityRecords || [];\n console.log(`Processing ${records.length} records`);\n\n const data: { [key: string]: number } = {};\n\n if (records.length === 0) {\n console.log('No records to process');\n this.heatmapData = data;\n return;\n }\n\n // Sample logging of a few records to understand structure without flooding console\n const uniqueDates = new Set<string>();\n const dateDistribution: Record<string, number> = {};\n let validCount = 0;\n let invalidCount = 0;\n \n for (let i = 0; i < records.length; i++) {\n const record = records[i];\n \n if (!record || typeof record !== 'object' || !record.timeStamp) {\n invalidCount++;\n continue;\n }\n \n try {\n // Attempt to normalize the timestamp\n let normalizedDate: string;\n \n if (typeof record.timeStamp === 'string') {\n // For ISO strings, parse directly with moment\n normalizedDate = moment(record.timeStamp).format('YYYY-MM-DD');\n } else if (typeof record.timeStamp === 'number') {\n // For numeric timestamps, use Date constructor then moment\n normalizedDate = moment(new Date(record.timeStamp)).format('YYYY-MM-DD');\n } else if (typeof record.timeStamp === 'object') {\n // For objects (like Moment), try toString() or direct parsing\n if (typeof record.timeStamp.format === 'function') {\n // It's likely a Moment object\n normalizedDate = record.timeStamp.format('YYYY-MM-DD');\n } else if (record.timeStamp instanceof Date) {\n normalizedDate = moment(record.timeStamp).format('YYYY-MM-DD');\n } else {\n // Try to parse it as a string representation\n normalizedDate = moment(String(record.timeStamp)).format('YYYY-MM-DD');\n }\n } else {\n // Unhandled type\n invalidCount++;\n continue;\n }\n \n // Verify the date is valid before using it\n if (moment(normalizedDate, 'YYYY-MM-DD', true).isValid()) {\n data[normalizedDate] = (data[normalizedDate] || 0) + 1;\n uniqueDates.add(normalizedDate);\n \n // Track distribution by month for debugging\n const month = normalizedDate.substring(0, 7); // YYYY-MM\n dateDistribution[month] = (dateDistribution[month] || 0) + 1;\n \n validCount++;\n } else {\n invalidCount++;\n }\n } catch (e) {\n invalidCount++;\n }\n }\n \n // Log summary statistics\n console.log(`Processed ${validCount} valid dates, ${invalidCount} invalid dates`);\n console.log(`Found ${uniqueDates.size} unique dates`);\n console.log('Date distribution by month:', dateDistribution);\n\n this.heatmapData = data;\n },\n\n createWeeksData() {\n // Reset weeks and max count\n this.weeks = [];\n this.maxInRange = 0;\n \n const end = moment();\n const start = end.clone().subtract(52, 'weeks');\n const day = start.clone().startOf('week');\n \n console.log('Creating weeks data from', start.format('YYYY-MM-DD'), 'to', end.format('YYYY-MM-DD'));\n \n // Ensure we have data to display\n if (Object.keys(this.heatmapData).length === 0) {\n console.log('No heatmap data available to display');\n }\n\n // For debugging, log some sample dates from the heatmap data\n const sampleDates = Object.keys(this.heatmapData).slice(0, 5);\n console.log('Sample dates in heatmap data:', sampleDates);\n \n // Build the week data structure\n while (day.isSameOrBefore(end)) {\n const weekData: DayData[] = [];\n for (let i = 0; i < 7; i++) {\n const date = day.format('YYYY-MM-DD');\n const count = this.heatmapData[date] || 0;\n const dayData: DayData = {\n date,\n count,\n };\n weekData.push(dayData);\n if (dayData.count > this.maxInRange) {\n this.maxInRange = dayData.count;\n }\n\n day.add(1, 'day');\n }\n this.weeks.push(weekData);\n }\n \n console.log('Weeks data created, maxInRange:', this.maxInRange);\n \n // Calculate summary stats for display\n let totalDaysWithActivity = 0;\n let totalActivity = 0;\n \n Object.values(this.heatmapData).forEach(count => {\n totalDaysWithActivity++;\n totalActivity += count;\n });\n \n console.log(`Activity summary: ${totalActivity} activities across ${totalDaysWithActivity} days`);\n },\n\n getColor(count: number): string {\n if (this.maxInRange === 0) return this.hslToString(this.inactiveColor);\n\n const t = count === 0 ? 0 : Math.min((2 * count) / this.maxInRange, 1);\n\n let seasonalColor: Color = this.activeColor;\n\n if (this.enableSeasonalColors) {\n const now = moment();\n if (now.month() === 11 && now.date() >= 5) {\n // Christmas colors\n seasonalColor = Math.random() > 0.5 ? { h: 350, s: 0.8, l: 0.5 } : { h: 135, s: 0.8, l: 0.4 };\n } else if (now.month() === 9 && now.date() >= 25) {\n // Halloween colors\n seasonalColor =\n Math.random() > 0.5\n ? { h: 0, s: 0, l: 0 }\n : Math.random() > 0.5\n ? { h: 30, s: 1, l: 0.5 }\n : { h: 270, s: 1, l: 0.5 };\n }\n }\n\n const h = seasonalColor.h;\n const s = this.interpolate(this.inactiveColor.s, seasonalColor.s, t);\n const l = this.interpolate(this.inactiveColor.l, seasonalColor.l, t);\n\n return this.hslToString({ h, s, l });\n },\n\n interpolate(start: number, end: number, t: number): number {\n return start + (end - start) * t;\n },\n\n hslToString(color: Color): string {\n return `hsl(${color.h}, ${color.s * 100}%, ${color.l * 100}%)`;\n },\n\n showTooltip(day: DayData, event: MouseEvent) {\n this.tooltipData = day;\n this.tooltipStyle = {\n position: 'absolute',\n left: `${event.pageX + 10}px`,\n top: `${event.pageY + 10}px`,\n };\n },\n\n hideTooltip() {\n this.tooltipData = null;\n },\n },\n});\n</script>\n\n<style scoped>\n.tooltip {\n background-color: rgba(0, 0, 0, 0.8);\n color: white;\n padding: 5px;\n border-radius: 3px;\n font-size: 12px;\n}\n</style>\n"],"mappings":"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"}