@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.
- package/dist/_virtual/_@oxc-project_runtime@0.115.0/helpers/defineProperty.mjs +13 -0
- package/dist/_virtual/_@oxc-project_runtime@0.115.0/helpers/toPrimitive.mjs +15 -0
- package/dist/_virtual/_@oxc-project_runtime@0.115.0/helpers/toPropertyKey.mjs +10 -0
- package/dist/_virtual/_@oxc-project_runtime@0.115.0/helpers/typeof.mjs +12 -0
- package/dist/_virtual/_plugin-vue_export-helper.mjs +9 -0
- package/dist/_virtual/_rolldown/runtime.mjs +7 -0
- package/dist/admin/common-ui-admin.es.js +259 -23774
- package/dist/admin/common-ui-admin.es.js.map +1 -1
- package/dist/assets/index.css +2 -1
- package/dist/components/CardBrowser.mjs +63 -0
- package/dist/components/CardBrowser.mjs.map +1 -0
- package/dist/components/CardBrowser.vue_vue_type_script_lang.mjs +50 -0
- package/dist/components/CardBrowser.vue_vue_type_script_lang.mjs.map +1 -0
- package/dist/components/CardHistoryViewer.mjs +70 -0
- package/dist/components/CardHistoryViewer.mjs.map +1 -0
- package/dist/components/CardHistoryViewer.vue_vue_type_script_lang.mjs +108 -0
- package/dist/components/CardHistoryViewer.vue_vue_type_script_lang.mjs.map +1 -0
- package/dist/components/CardSearch.mjs +26 -0
- package/dist/components/CardSearch.mjs.map +1 -0
- package/dist/components/CardSearch.vue_vue_type_script_lang.mjs +17 -0
- package/dist/components/CardSearch.vue_vue_type_script_lang.mjs.map +1 -0
- package/dist/components/CardSearchResults.mjs +32 -0
- package/dist/components/CardSearchResults.mjs.map +1 -0
- package/dist/components/CardSearchResults.vue_vue_type_script_lang.mjs +71 -0
- package/dist/components/CardSearchResults.vue_vue_type_script_lang.mjs.map +1 -0
- package/dist/components/CourseCardBrowser.mjs +175 -0
- package/dist/components/CourseCardBrowser.mjs.map +1 -0
- package/dist/components/CourseCardBrowser.vue_vue_type_script_lang.mjs +173 -0
- package/dist/components/CourseCardBrowser.vue_vue_type_script_lang.mjs.map +1 -0
- package/dist/components/CourseInformation.mjs +132 -0
- package/dist/components/CourseInformation.mjs.map +1 -0
- package/dist/components/CourseInformation.vue_vue_type_script_lang.mjs +56 -0
- package/dist/components/CourseInformation.vue_vue_type_script_lang.mjs.map +1 -0
- package/dist/components/CourseTagFilterWidget.mjs +114 -0
- package/dist/components/CourseTagFilterWidget.mjs.map +1 -0
- package/dist/components/CourseTagFilterWidget.vue_vue_type_script_lang.mjs +65 -0
- package/dist/components/CourseTagFilterWidget.vue_vue_type_script_lang.mjs.map +1 -0
- package/dist/components/HeatMap.mjs +40 -0
- package/dist/components/HeatMap.mjs.map +1 -0
- package/dist/components/HeatMap.vue_vue_type_script_lang.mjs +211 -0
- package/dist/components/HeatMap.vue_vue_type_script_lang.mjs.map +1 -0
- package/dist/components/PaginatingToolbar.mjs +94 -0
- package/dist/components/PaginatingToolbar.mjs.map +1 -0
- package/dist/components/PaginatingToolbar.vue_vue_type_script_lang.mjs +36 -0
- package/dist/components/PaginatingToolbar.vue_vue_type_script_lang.mjs.map +1 -0
- package/dist/components/SessionControllerDebug.mjs +130 -0
- package/dist/components/SessionControllerDebug.mjs.map +1 -0
- package/dist/components/SessionControllerDebug.vue_vue_type_script_lang.mjs +45 -0
- package/dist/components/SessionControllerDebug.vue_vue_type_script_lang.mjs.map +1 -0
- package/dist/components/SkMouseTrap.mjs +66 -0
- package/dist/components/SkMouseTrap.mjs.map +1 -0
- package/dist/components/SkMouseTrap.vue_vue_type_script_lang.mjs +31 -0
- package/dist/components/SkMouseTrap.vue_vue_type_script_lang.mjs.map +1 -0
- package/dist/components/SkMouseTrapToolTip.mjs +28 -0
- package/dist/components/SkMouseTrapToolTip.mjs.map +1 -0
- package/dist/components/SkMouseTrapToolTip.vue_vue_type_script_lang.mjs +91 -0
- package/dist/components/SkMouseTrapToolTip.vue_vue_type_script_lang.mjs.map +1 -0
- package/dist/components/SnackbarService.mjs +24 -0
- package/dist/components/SnackbarService.mjs.map +1 -0
- package/dist/components/SnackbarService.vue_vue_type_script_lang.mjs +34 -0
- package/dist/components/SnackbarService.vue_vue_type_script_lang.mjs.map +1 -0
- package/dist/components/SnackbarService2.mjs +40 -0
- package/dist/components/SnackbarService2.mjs.map +1 -0
- package/dist/components/StudySession.mjs +123 -0
- package/dist/components/StudySession.mjs.map +1 -0
- package/dist/components/StudySession.vue_vue_type_script_lang.mjs +280 -0
- package/dist/components/StudySession.vue_vue_type_script_lang.mjs.map +1 -0
- package/dist/components/StudySessionTimer.mjs +50 -0
- package/dist/components/StudySessionTimer.mjs.map +1 -0
- package/dist/components/StudySessionTimer.vue_vue_type_script_lang.mjs +43 -0
- package/dist/components/StudySessionTimer.vue_vue_type_script_lang.mjs.map +1 -0
- package/dist/components/TagsInput.mjs +45 -0
- package/dist/components/TagsInput.mjs.map +1 -0
- package/dist/components/TagsInput.vue_vue_type_script_lang.mjs +117 -0
- package/dist/components/TagsInput.vue_vue_type_script_lang.mjs.map +1 -0
- package/dist/components/UserTagPreferences.mjs +205 -0
- package/dist/components/UserTagPreferences.mjs.map +1 -0
- package/dist/components/UserTagPreferences.vue_vue_type_script_lang.mjs +139 -0
- package/dist/components/UserTagPreferences.vue_vue_type_script_lang.mjs.map +1 -0
- package/dist/components/auth/RequestPasswordReset.mjs +96 -0
- package/dist/components/auth/RequestPasswordReset.mjs.map +1 -0
- package/dist/components/auth/RequestPasswordReset.vue_vue_type_script_lang.mjs +47 -0
- package/dist/components/auth/RequestPasswordReset.vue_vue_type_script_lang.mjs.map +1 -0
- package/dist/components/auth/ResetPassword.mjs +129 -0
- package/dist/components/auth/ResetPassword.mjs.map +1 -0
- package/dist/components/auth/ResetPassword.vue_vue_type_script_lang.mjs +69 -0
- package/dist/components/auth/ResetPassword.vue_vue_type_script_lang.mjs.map +1 -0
- package/dist/components/auth/UserChip.mjs +9 -0
- package/dist/components/auth/UserChip.mjs.map +1 -0
- package/dist/components/auth/UserChip.vue_vue_type_script_setup_true_lang.mjs +213 -0
- package/dist/components/auth/UserChip.vue_vue_type_script_setup_true_lang.mjs.map +1 -0
- package/dist/components/auth/UserLogin.mjs +9 -0
- package/dist/components/auth/UserLogin.mjs.map +1 -0
- package/dist/components/auth/UserLogin.vue_vue_type_script_setup_true_lang.mjs +155 -0
- package/dist/components/auth/UserLogin.vue_vue_type_script_setup_true_lang.mjs.map +1 -0
- package/dist/components/auth/UserLoginAndRegistrationContainer.mjs +9 -0
- package/dist/components/auth/UserLoginAndRegistrationContainer.mjs.map +1 -0
- package/dist/components/auth/UserLoginAndRegistrationContainer.vue_vue_type_script_setup_true_lang.mjs +126 -0
- package/dist/components/auth/UserLoginAndRegistrationContainer.vue_vue_type_script_setup_true_lang.mjs.map +1 -0
- package/dist/components/auth/UserRegistration.mjs +147 -0
- package/dist/components/auth/UserRegistration.mjs.map +1 -0
- package/dist/components/auth/UserRegistration.vue_vue_type_script_lang.mjs +130 -0
- package/dist/components/auth/UserRegistration.vue_vue_type_script_lang.mjs.map +1 -0
- package/dist/components/auth/VerifyEmail.mjs +118 -0
- package/dist/components/auth/VerifyEmail.mjs.map +1 -0
- package/dist/components/auth/VerifyEmail.vue_vue_type_script_lang.mjs +59 -0
- package/dist/components/auth/VerifyEmail.vue_vue_type_script_lang.mjs.map +1 -0
- package/dist/components/cardRendering/AudioAutoPlayer.mjs +9 -0
- package/dist/components/cardRendering/AudioAutoPlayer.mjs.map +1 -0
- package/dist/components/cardRendering/AudioAutoPlayer.vue_vue_type_script_setup_true_lang.mjs +69 -0
- package/dist/components/cardRendering/AudioAutoPlayer.vue_vue_type_script_setup_true_lang.mjs.map +1 -0
- package/dist/components/cardRendering/CardLoader.mjs +31 -0
- package/dist/components/cardRendering/CardLoader.mjs.map +1 -0
- package/dist/components/cardRendering/CardLoader.vue_vue_type_script_lang.mjs +75 -0
- package/dist/components/cardRendering/CardLoader.vue_vue_type_script_lang.mjs.map +1 -0
- package/dist/components/cardRendering/CardViewer.mjs +39 -0
- package/dist/components/cardRendering/CardViewer.mjs.map +1 -0
- package/dist/components/cardRendering/CardViewer.vue_vue_type_script_lang.mjs +66 -0
- package/dist/components/cardRendering/CardViewer.vue_vue_type_script_lang.mjs.map +1 -0
- package/dist/components/cardRendering/CodeBlockRenderer.mjs +8 -0
- package/dist/components/cardRendering/CodeBlockRenderer.mjs.map +1 -0
- package/dist/components/cardRendering/CodeBlockRenderer.vue_vue_type_script_setup_true_lang.mjs +44 -0
- package/dist/components/cardRendering/CodeBlockRenderer.vue_vue_type_script_setup_true_lang.mjs.map +1 -0
- package/dist/components/cardRendering/MarkdownRenderer.mjs +21 -0
- package/dist/components/cardRendering/MarkdownRenderer.mjs.map +1 -0
- package/dist/components/cardRendering/MarkdownRenderer.vue_vue_type_script_lang.mjs +24 -0
- package/dist/components/cardRendering/MarkdownRenderer.vue_vue_type_script_lang.mjs.map +1 -0
- package/dist/components/cardRendering/MarkdownRendererHelpers.mjs +79 -0
- package/dist/components/cardRendering/MarkdownRendererHelpers.mjs.map +1 -0
- package/dist/components/cardRendering/MdTokenRenderer.mjs +9 -0
- package/dist/components/cardRendering/MdTokenRenderer.mjs.map +1 -0
- package/dist/components/cardRendering/MdTokenRenderer.vue_vue_type_script_setup_true_lang.mjs +176 -0
- package/dist/components/cardRendering/MdTokenRenderer.vue_vue_type_script_setup_true_lang.mjs.map +1 -0
- package/dist/components/studentInputs/BaseUserInput.mjs +54 -0
- package/dist/components/studentInputs/BaseUserInput.mjs.map +1 -0
- package/dist/components/studentInputs/MultipleChoiceOption.mjs +26 -0
- package/dist/components/studentInputs/MultipleChoiceOption.mjs.map +1 -0
- package/dist/components/studentInputs/MultipleChoiceOption.vue_vue_type_script_lang.mjs +76 -0
- package/dist/components/studentInputs/MultipleChoiceOption.vue_vue_type_script_lang.mjs.map +1 -0
- package/dist/components/studentInputs/RadioMultipleChoice.mjs +33 -0
- package/dist/components/studentInputs/RadioMultipleChoice.mjs.map +1 -0
- package/dist/components/studentInputs/RadioMultipleChoice.vue_vue_type_script_lang.mjs +105 -0
- package/dist/components/studentInputs/RadioMultipleChoice.vue_vue_type_script_lang.mjs.map +1 -0
- package/dist/components/studentInputs/TrueFalse.mjs +19 -0
- package/dist/components/studentInputs/TrueFalse.mjs.map +1 -0
- package/dist/components/studentInputs/TrueFalse.vue_vue_type_script_lang.mjs +21 -0
- package/dist/components/studentInputs/TrueFalse.vue_vue_type_script_lang.mjs.map +1 -0
- package/dist/components/studentInputs/UserInputNumber.mjs +33 -0
- package/dist/components/studentInputs/UserInputNumber.mjs.map +1 -0
- package/dist/components/studentInputs/UserInputNumber.vue_vue_type_script_lang.mjs +25 -0
- package/dist/components/studentInputs/UserInputNumber.vue_vue_type_script_lang.mjs.map +1 -0
- package/dist/components/studentInputs/UserInputString.mjs +22 -0
- package/dist/components/studentInputs/UserInputString.mjs.map +1 -0
- package/dist/components/studentInputs/UserInputString.vue_vue_type_script_lang.mjs +23 -0
- package/dist/components/studentInputs/UserInputString.vue_vue_type_script_lang.mjs.map +1 -0
- package/dist/components/studentInputs/fillInInput.mjs +25 -0
- package/dist/components/studentInputs/fillInInput.mjs.map +1 -0
- package/dist/components/studentInputs/fillInInput.vue_vue_type_script_lang.mjs +26 -0
- package/dist/components/studentInputs/fillInInput.vue_vue_type_script_lang.mjs.map +1 -0
- package/dist/composables/CompositionViewable.mjs +65 -0
- package/dist/composables/CompositionViewable.mjs.map +1 -0
- package/dist/composables/Displayable.mjs +36 -0
- package/dist/composables/Displayable.mjs.map +1 -0
- package/dist/composables/useAuthUI.mjs +38 -0
- package/dist/composables/useAuthUI.mjs.map +1 -0
- package/dist/composables/useEntitlements.mjs +58 -0
- package/dist/composables/useEntitlements.mjs.map +1 -0
- package/dist/index.mjs +49 -0
- package/dist/plugins/pinia.mjs +12 -0
- package/dist/plugins/pinia.mjs.map +1 -0
- package/dist/services/authAPI.mjs +159 -0
- package/dist/services/authAPI.mjs.map +1 -0
- package/dist/stores/useAuthStore.mjs +68 -0
- package/dist/stores/useAuthStore.mjs.map +1 -0
- package/dist/stores/useCardPreviewModeStore.mjs +20 -0
- package/dist/stores/useCardPreviewModeStore.mjs.map +1 -0
- package/dist/stores/useConfigStore.mjs +60 -0
- package/dist/stores/useConfigStore.mjs.map +1 -0
- package/dist/utils/SkldrMouseTrap.mjs +48 -0
- package/dist/utils/SkldrMouseTrap.mjs.map +1 -0
- package/dist/utils/passwordValidation.mjs +12 -0
- package/dist/utils/passwordValidation.mjs.map +1 -0
- package/package.json +7 -8
- package/dist/MarkdownRenderer-U_iPiA2x.mjs +0 -4966
- package/dist/MarkdownRenderer-U_iPiA2x.mjs.map +0 -1
- package/dist/assets/MarkdownRenderer.css +0 -1
- package/dist/common-ui.es.js +0 -5090
- package/dist/common-ui.es.js.map +0 -1
- package/dist/common-ui.umd.js +0 -77
- package/dist/common-ui.umd.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"StudySession.vue_vue_type_script_lang.mjs","names":[],"sources":["../../src/components/StudySession.vue"],"sourcesContent":["<template>\n <div v-if=\"sessionPrepared\" class=\"StudySession\">\n <v-row v-if=\"!frameless\" align=\"center\">\n <!-- <h1 class=\"text-h3\" v-if=\"courseNames[courseID]\">{{ courseNames[courseID] }}:</h1> -->\n <v-spacer></v-spacer>\n <v-progress-circular v-if=\"loading\" color=\"primary\" indeterminate size=\"32\" width=\"4\" />\n </v-row>\n\n <!-- Debug Panel (only visible if window.debugMode is true) -->\n <session-controller-debug v-if=\"debugMode\" :session-controller=\"sessionController\" />\n\n <br v-if=\"!frameless\" />\n\n <div v-if=\"sessionFinished\" class=\"session-finished-area\">\n <slot name=\"session-finished\" :session-record=\"sessionRecord\" :report=\"sessionController?.report\">\n <div class=\"text-h4\">\n <p>Study session finished! Great job!</p>\n <p v-if=\"sessionController\">{{ sessionController.report }}</p>\n <!-- <p>\n Start <a @click=\"$emit('session-finished')\">another study session</a>, or try\n <router-link :to=\"`/edit/${courseID}`\">adding some new content</router-link> to challenge yourself and others!\n </p> -->\n <heat-map :activity-records-getter=\"() => user.getActivityRecords()\" />\n </div>\n </slot>\n </div>\n\n <div v-else ref=\"shadowWrapper\" class=\"card-transition-container\">\n <transition :name=\"transitionName\" :mode=\"resolvedTransitionMode\">\n <card-viewer\n v-if=\"view\"\n ref=\"cardViewer\"\n :key=\"cardID\"\n :class=\"loading ? 'muted' : ''\"\n :view=\"view\"\n :data=\"data\"\n :card_id=\"cardID\"\n :course_id=\"courseID\"\n :session-order=\"cardCount\"\n :user_elo=\"user_elo(courseID)\"\n :card_elo=\"card_elo\"\n :frameless=\"frameless\"\n @emit-response=\"processResponse($event)\"\n @request-replan=\"handleReplanRequest\"\n @ready-to-advance=\"handleReadyToAdvance\"\n />\n </transition>\n </div>\n\n <template v-if=\"!frameless\">\n <br />\n <div v-if=\"sessionController\">\n <span v-for=\"i in sessionController.failedCount\" :key=\"i\" class=\"text-h5\">•</span>\n </div>\n </template>\n\n <!--\n todo: reinstate tag editing at session-time ?\n\n <div v-if=\"!sessionFinished && editTags\">\n <p>Add tags to this card:</p>\n <sk-tags-input :course-i-d=\"courseID\" :card-i-d=\"cardID\" />\n </div> -->\n\n <v-row v-if=\"!hideFooter\" align=\"center\" class=\"footer-controls pa-5\">\n <v-col cols=\"auto\" class=\"d-flex flex-grow-0 mr-auto\">\n <StudySessionTimer\n :time-remaining=\"timeRemaining\"\n :session-time-limit=\"sessionTimeLimit\"\n @add-time=\"incrementSessionClock\"\n />\n </v-col>\n\n <v-spacer></v-spacer>\n\n <v-col cols=\"auto\" class=\"footer-right\">\n <SkMouseTrap />\n </v-col>\n </v-row>\n </div>\n</template>\n\n<script lang=\"ts\">\nimport { defineComponent, markRaw, PropType } from 'vue';\nimport { ViewComponent } from '../composables';\nimport { isQuestionView } from '../composables/CompositionViewable';\nimport HeatMap from './HeatMap.vue';\nimport SkMouseTrap from './SkMouseTrap.vue';\nimport { alertUser } from './SnackbarService';\nimport StudySessionTimer from './StudySessionTimer.vue';\nimport CardViewer from './cardRendering/CardViewer.vue';\nimport SessionControllerDebug from './SessionControllerDebug.vue';\n\nimport { CourseElo, Status, toCourseElo, ViewData } from '@vue-skuilder/common';\nimport {\n CardHistory,\n CardRecord,\n ClassroomDBInterface,\n ContentSourceID,\n CourseRegistrationDoc,\n DataLayerProvider,\n getStudySource,\n HydratedCard,\n isQuestionRecord,\n OutcomeObserver,\n ReplanOptions,\n ResponseResult,\n SessionController,\n StudyContentSource,\n StudySessionRecord,\n UserDBInterface,\n} from '@vue-skuilder/db';\nimport confetti from 'canvas-confetti';\n\nimport { StudySessionConfig, CardTransitionPreset, CardTransitionMode } from './StudySession.types';\nimport type { SessionStateSnapshotProvider } from '@vue-skuilder/db';\n\ninterface StudyRefs {\n shadowWrapper: HTMLDivElement;\n cardViewer: InstanceType<typeof CardViewer>;\n}\n\ntype StudyInstance = ReturnType<typeof defineComponent> & {\n $refs: StudyRefs;\n};\n\nexport default defineComponent({\n name: 'StudySession',\n\n ref: {} as StudyRefs,\n\n components: {\n CardViewer,\n StudySessionTimer,\n SkMouseTrap,\n HeatMap,\n SessionControllerDebug,\n },\n\n props: {\n sessionTimeLimit: {\n type: Number,\n required: true,\n },\n contentSources: {\n type: Array as PropType<ContentSourceID[]>,\n required: true,\n },\n user: {\n type: Object as PropType<UserDBInterface>,\n required: true,\n },\n dataLayer: {\n type: Object as PropType<DataLayerProvider>,\n required: true,\n },\n sessionConfig: {\n type: Object as PropType<StudySessionConfig>,\n default: () => ({ likesConfetti: false }),\n },\n getViewComponent: {\n type: Function as PropType<(viewId: string) => ViewComponent>,\n required: true,\n },\n frameless: {\n type: Boolean,\n default: false,\n },\n hideFooter: {\n type: Boolean,\n default: false,\n },\n transitionName: {\n type: String as PropType<CardTransitionPreset | string>,\n default: 'component-fade',\n },\n transitionMode: {\n type: String as PropType<CardTransitionMode>,\n default: 'out-in',\n },\n },\n\n emits: [\n 'session-finished',\n 'session-started',\n 'card-loaded',\n 'card-response',\n 'time-changed',\n 'session-prepared',\n 'session-error',\n 'replan-requested',\n ],\n\n data() {\n return {\n // editTags: false,\n cardID: '',\n view: null as ViewComponent | null,\n data: [] as ViewData[],\n courseID: '',\n card_elo: 1000,\n courseNames: {} as { [courseID: string]: string },\n cardCount: 1,\n sessionController: null as SessionController<ViewComponent> | null,\n sessionPrepared: false,\n sessionFinished: false,\n sessionRecord: [] as StudySessionRecord[],\n percentageRemaining: 100,\n timerIsActive: true,\n loading: false,\n userCourseRegDoc: null as CourseRegistrationDoc | null,\n sessionContentSources: [] as StudyContentSource[],\n timeRemaining: 300, // 5 minutes * 60 seconds\n replanPending: false,\n replanOptions: null as ReplanOptions | null,\n deferredNextCardAction: null as string | null,\n intervalHandler: null as NodeJS.Timeout | null,\n cardType: '',\n debugMode: (window as any).debugMode === true,\n };\n },\n\n computed: {\n currentCard(): StudySessionRecord {\n return this.sessionRecord[this.sessionRecord.length - 1];\n },\n /**\n * Vue's <transition> mode accepts 'out-in' | 'in-out' | undefined; our\n * 'default' preset value means \"simultaneous\", i.e. no mode. (This prop\n * was declared but never bound until 2026-07; presets documented as\n * \"use with mode=out-in\" ran simultaneous, leaving both cards in-flow\n * mid-transition.)\n */\n resolvedTransitionMode(): 'out-in' | 'in-out' | undefined {\n return this.transitionMode === 'default' ? undefined : this.transitionMode;\n },\n },\n\n // watch: {\n // editCard: {\n // async handler(value: boolean) {\n // if (value) {\n // this.dataInputFormStore.dataInputForm.dataShape = await getCardDataShape(this.courseID, this.cardID);\n\n // const cfg = await getCredentialledCourseConfig(this.courseID);\n // this.dataInputFormStore.dataInputForm.course = cfg!;\n\n // this.editCardReady = true;\n\n // for (const oldField in this.dataInputFormStore.dataInputForm.localStore) {\n // if (oldField) {\n // console.log(`[Study] Removing old data: ${oldField}`);\n // delete this.dataInputFormStore.dataInputForm.localStore[oldField];\n // }\n // }\n\n // for (const field in this.data[0]) {\n // if (field) {\n // console.log(`[Study] Writing ${field}: ${this.data[0][field]} to the dataInputForm state...`);\n // this.dataInputFormStore.dataInputForm.localStore[field] = this.data[0][field];\n // }\n // }\n // } else {\n // this.editCardReady = false;\n // }\n // },\n // },\n // },\n\n async created() {\n this.userCourseRegDoc = await this.user.getCourseRegistrationsDoc();\n console.log('[StudySession] Created lifecycle hook - starting initSession');\n await this.initSession();\n console.log('[StudySession] InitSession completed in created hook');\n },\n\n beforeUnmount() {\n // Torn down mid-session (route change, parent v-if) = abandoned.\n // `endSession` is idempotent, so a naturally-ended session keeps `closed`.\n if (this.sessionController && !this.sessionFinished) {\n void this.sessionController.endSession('abandoned');\n }\n if (this.intervalHandler) {\n clearInterval(this.intervalHandler);\n }\n },\n\n errorCaptured(err: unknown, _instance: unknown, info: string) {\n console.error(`[StudySession] Card render error (${info}), skipping card \"${this.cardID}\":`, err);\n if (this.sessionController) {\n void this.sessionController.nextCard().then((card) => this.loadCard(card));\n }\n return false; // prevent propagation\n },\n\n methods: {\n /**\n * Handle a `ready-to-advance` event from a card view that previously\n * submitted with `deferAdvance: true`. Triggers the stashed navigation.\n */\n async handleReadyToAdvance() {\n const action = this.deferredNextCardAction;\n if (!action) {\n console.warn('[StudySession] ready-to-advance received but no deferred action stashed — ignoring');\n return;\n }\n console.log(`[StudySession] ready-to-advance received — advancing with stashed action: ${action}`);\n this.deferredNextCardAction = null;\n this.loadCard(await this.sessionController!.nextCard(action));\n },\n\n /**\n * Handle a replan request from a card view component.\n *\n * Card views emit 'request-replan' when they've made state changes that\n * should be reflected in the session's content queue (e.g. a GPC intro\n * unlocking new exercise cards).\n *\n * The replan is deferred — not fired immediately. processResponse()\n * checks this flag after submitResponse() has recorded ELO/tag\n * interactions, ensuring the pipeline sees fully up-to-date state.\n *\n * Accepts either a ReplanOptions object (new API) or a bare hints\n * Record (legacy). SessionController.requestReplan() normalises both.\n */\n handleReplanRequest(options?: ReplanOptions) {\n if (this.sessionController) {\n const label = (options as ReplanOptions)?.label;\n const tag = label ? ` [${label}]` : '';\n console.log(`[StudySession] Replan requested by card view${tag}, deferring until after response processing`);\n this.replanPending = true;\n this.replanOptions = (options as ReplanOptions) ?? null;\n }\n },\n\n user_elo(courseID: string): CourseElo {\n const courseDoc = this.userCourseRegDoc!.courses.find((c) => c.courseID === courseID);\n if (courseDoc) {\n return toCourseElo(courseDoc.elo);\n }\n return toCourseElo(undefined);\n },\n\n handleClassroomMessage() {\n return (v: unknown) => {\n alertUser({\n text: this.user?.getUsername() || '[Unknown user]',\n status: Status.ok,\n });\n console.log(`[StudySession] There was a change in the classroom DB:`);\n console.log(`[StudySession] change: ${v}`);\n console.log(`[StudySession] Stringified change: ${JSON.stringify(v)}`);\n return {};\n };\n },\n\n incrementSessionClock() {\n const max = 60 * this.sessionTimeLimit - this.timeRemaining;\n this.sessionController!.addTime(Math.min(max, 60));\n this.tick();\n },\n\n tick() {\n this.timeRemaining = this.sessionController!.secondsRemaining;\n\n this.percentageRemaining =\n this.timeRemaining > 60\n ? 100 * (this.timeRemaining / (60 * this.sessionTimeLimit))\n : 100 * (this.timeRemaining / 60);\n\n this.$emit('time-changed', this.timeRemaining);\n\n if (this.timeRemaining === 0) {\n clearInterval(this.intervalHandler!);\n }\n },\n\n async initSession() {\n let sessionClassroomDBs: ClassroomDBInterface[] = [];\n try {\n console.log(`[StudySession] starting study session w/ sources: ${JSON.stringify(this.contentSources)}`);\n console.log('[StudySession] Beginning preparation process');\n\n this.sessionContentSources = markRaw(\n (\n await Promise.all(\n this.contentSources.map(async (s) => {\n try {\n return await getStudySource(s, this.user);\n } catch (e) {\n console.error(`Failed to load study source: ${s.type}/${s.id}`, e);\n return null;\n }\n })\n )\n ).filter((s: unknown) => s !== null)\n );\n\n this.timeRemaining = this.sessionTimeLimit * 60;\n\n sessionClassroomDBs = await Promise.all(\n this.contentSources\n .filter((s) => s.type === 'classroom')\n .map(async (c) => await this.dataLayer.getClassroomDB(c.id, 'student'))\n );\n\n sessionClassroomDBs.forEach((_db) => {\n // db.setChangeFcn(this.handleClassroomMessage());\n });\n\n const scOptions: {\n defaultBatchLimit?: number;\n outcomeObservers?: OutcomeObserver[];\n courseId?: string;\n stateSnapshotProvider?: SessionStateSnapshotProvider;\n } = {};\n if (this.sessionConfig?.defaultBatchLimit !== undefined) {\n scOptions.defaultBatchLimit = this.sessionConfig.defaultBatchLimit;\n }\n if (this.sessionConfig?.outcomeObservers?.length) {\n scOptions.outcomeObservers = this.sessionConfig.outcomeObservers;\n }\n // A session can span several sources; the first course-type one owns\n // the record. Absent it, the controller skips session recording.\n const recordingCourseId = this.contentSources.find((s) => s.type === 'course')?.id;\n if (recordingCourseId) {\n scOptions.courseId = recordingCourseId;\n }\n if (this.sessionConfig?.sessionStateSnapshot) {\n scOptions.stateSnapshotProvider = this.sessionConfig.sessionStateSnapshot;\n }\n\n this.sessionController = markRaw(\n new SessionController<ViewComponent>(\n this.sessionContentSources,\n 60 * this.sessionTimeLimit,\n this.dataLayer,\n this.getViewComponent,\n undefined, // mixer — use default\n scOptions\n )\n );\n this.sessionController.sessionRecord = this.sessionRecord;\n\n // Apply init hints as SESSION-DURABLE hints (e.g. post-lesson boost\n // tags). Routed through the controller's session-hints accumulator so\n // they are re-merged into every pipeline run for the rest of the\n // session — rather than being consumed by the initial plan alone and\n // then clobbered by the first (replace-mode) auto-replan.\n if (this.sessionConfig?.initHints) {\n this.sessionController.setSessionHints(this.sessionConfig.initHints);\n console.log('[StudySession] Applied init hints as session-durable hints');\n }\n\n await this.sessionController.prepareSession();\n this.intervalHandler = setInterval(this.tick, 1000);\n\n this.sessionPrepared = true;\n\n console.log('[StudySession] Session preparation complete, emitting session-prepared event');\n this.$emit('session-prepared');\n console.log('[StudySession] Event emission completed');\n } catch (error) {\n console.error('[StudySession] Error during session preparation:', error);\n // Notify parent component about the error\n this.$emit('session-error', { message: 'Failed to prepare study session', error });\n // Stop here. The controller exists (constructed before prepareSession),\n // so falling through would call nextCard() against the same failed\n // source: a doomed wedge-breaker loop and a second, redundant error.\n return;\n }\n\n try {\n this.contentSources\n .filter((s) => s.type === 'course')\n .forEach(\n async (c) => (this.courseNames[c.id] = (await this.dataLayer.getCoursesDB().getCourseConfig(c.id)).name)\n );\n\n console.log(`[StudySession] Session created:\n ${this.sessionController?.toString() || 'Session controller not initialized'}\n User courses: ${this.contentSources\n .filter((s) => s.type === 'course')\n .map((c) => c.id)\n .toString()}\n User classrooms: ${sessionClassroomDBs.map((db: any) => db._id).toString() || 'No classrooms'}\n `);\n } catch (error) {\n console.error('[StudySession] Error during final session setup:', error);\n }\n\n if (this.sessionController) {\n try {\n this.$emit('session-started');\n this.loadCard(await this.sessionController.nextCard());\n } catch (error) {\n console.error('[StudySession] Error loading next card:', error);\n this.$emit('session-error', { message: 'Failed to load study card', error });\n }\n } else {\n console.error('[StudySession] Cannot load card: session controller not initialized');\n this.$emit('session-error', { message: 'Study session initialization failed' });\n }\n },\n\n countCardViews(course_id: string, card_id: string): number {\n return this.sessionRecord.filter((r) => r.card.course_id === course_id && r.card.card_id === card_id).length;\n },\n\n async processResponse(this: StudyInstance, r: CardRecord) {\n this.$emit('card-response', r);\n\n this.timerIsActive = true;\n\n r.cardID = this.cardID;\n r.courseID = this.courseID;\n // The single chokepoint every response passes through en route to\n // putCardRecord.\n r.sessionId = this.sessionController?.sessionId;\n this.currentCard.records.push(r);\n\n console.log(`[StudySession] StudySession.processResponse is running...`);\n // DEBUG: Added logging to track hanging issue - can be removed if issue resolved\n // console.log(`[StudySession] About to call logCardRecord...`);\n \n const cardHistory = this.logCardRecord(r).catch((e: unknown) => {\n console.error(`[StudySession] putCardRecord failed:`, e);\n throw e;\n });\n // console.log(`[StudySession] logCardRecord called, cardHistory promise created...`);\n\n // Get view constraints for response processing\n let maxAttemptsPerView = 1;\n let maxSessionViews = 1;\n if (isQuestionView(this.$refs.cardViewer?.$refs.activeView)) {\n const view = this.$refs.cardViewer.$refs.activeView;\n maxAttemptsPerView = view.maxAttemptsPerView;\n maxSessionViews = view.maxSessionViews;\n }\n const sessionViews = this.countCardViews(this.courseID, this.cardID);\n\n // Process response through SessionController\n // DEBUG: Added logging to track hanging issue - can be removed if issue resolved\n // console.log(`[StudySession] About to call submitResponse...`);\n const result: ResponseResult = await this.sessionController!.submitResponse(\n r,\n cardHistory,\n this.userCourseRegDoc!,\n this.currentCard,\n this.courseID,\n this.cardID,\n maxAttemptsPerView,\n maxSessionViews,\n sessionViews\n );\n // DEBUG: Added logging to track hanging issue - can be removed if issue resolved\n // console.log(`[StudySession] submitResponse completed, result:`, result);\n\n // Handle UI feedback based on result\n try {\n this.handleUIFeedback(result);\n } catch (error) {\n console.error(`[StudySession] Error handling UI feedback: ${error}.\\n\\nResult: ${JSON.stringify(result)}`);\n }\n\n // Fire deferred replan if requested — state is now fully recorded\n if (this.replanPending) {\n const opts = this.replanOptions;\n const label = opts?.label;\n const tag = label ? ` [${label}]` : '';\n console.log(`[StudySession] Firing deferred replan (post-submitResponse)${tag}`);\n this.replanPending = false;\n this.replanOptions = null;\n void this.sessionController!.requestReplan(opts ?? undefined);\n this.$emit('replan-requested');\n }\n\n // Handle navigation based on result\n if (result.deferred) {\n // Card requested deferred advancement — stash the action and wait\n // for a `ready-to-advance` event from the view before navigating.\n console.log(`[StudySession] Deferred advance — stashing action: ${result.nextCardAction}`);\n this.deferredNextCardAction = result.nextCardAction;\n } else if (result.shouldLoadNextCard) {\n this.loadCard(await this.sessionController!.nextCard(result.nextCardAction));\n }\n\n // Clear feedback shadow if requested\n if (result.shouldClearFeedbackShadow) {\n this.clearFeedbackShadow();\n }\n },\n\n handleUIFeedback(result: ResponseResult) {\n if (result.isCorrect) {\n // Handle correct response UI\n if (!this.frameless) {\n try {\n if (this.$refs.shadowWrapper && result.performanceScore !== undefined) {\n this.$refs.shadowWrapper.setAttribute('style', `--r: ${255 * (1 - result.performanceScore)}; --g:${255}`);\n this.$refs.shadowWrapper.classList.add('correct');\n }\n } catch (e) {\n console.warn(`[StudySession] Error setting shadowWrapper style: ${e}`);\n }\n }\n\n // Show confetti for correct responses\n if (this.sessionConfig.likesConfetti) {\n confetti({\n origin: {\n y: 1,\n x: 0.25 + 0.5 * Math.random(),\n },\n disableForReducedMotion: true,\n angle: 60 + 60 * Math.random(),\n });\n }\n } else {\n // Handle incorrect response UI\n if (!this.frameless) {\n try {\n if (this.$refs.shadowWrapper) {\n this.$refs.shadowWrapper.classList.add('incorrect');\n }\n } catch (e) {\n console.warn(`[StudySession] Error setting shadowWrapper style: ${e}`);\n }\n }\n }\n },\n\n clearFeedbackShadow() {\n if (this.frameless) return;\n\n setTimeout(() => {\n try {\n if (this.$refs.shadowWrapper) {\n (this.$refs.shadowWrapper as HTMLElement).classList.remove('correct', 'incorrect');\n }\n } catch (e) {\n // swallow error\n console.warn(`[StudySession] Error clearing shadowWrapper style: ${e}`);\n }\n }, 1250);\n },\n\n async logCardRecord(r: CardRecord): Promise<CardHistory<CardRecord>> {\n console.log(`[StudySession] About to call user.putCardRecord...`);\n const result = await this.user!.putCardRecord(r);\n console.log(`[StudySession] user.putCardRecord completed`);\n return result;\n },\n\n async loadCard(card: HydratedCard | null) {\n if (this.loading) {\n console.warn(`Attempted to load card while loading another...`);\n return;\n }\n\n console.log(`[StudySession] loading: ${JSON.stringify(card)}`);\n if (card === null) {\n this.sessionFinished = true;\n this.$emit('session-finished', this.sessionRecord);\n return;\n }\n this.cardType = card.item.status;\n\n this.loading = true;\n\n this.cardCount++;\n this.data = card.data;\n this.view = markRaw(card.view);\n this.cardID = card.item.cardID;\n this.courseID = card.item.courseID;\n this.card_elo = card.item.elo || 1000;\n\n this.sessionRecord.push({\n card: {\n course_id: card.item.courseID,\n card_id: card.item.cardID,\n card_elo: this.card_elo,\n tags: card.tags ?? [],\n },\n item: card.item,\n records: [],\n });\n\n this.$emit('card-loaded', {\n courseID: card.item.courseID,\n cardID: card.item.cardID,\n cardCount: this.cardCount,\n });\n\n this.loading = false;\n },\n },\n});\n</script>\n\n<style scoped>\n.footer-controls {\n position: fixed;\n bottom: 0;\n background-color: var(--v-background); /* Match your app's background color */\n z-index: 100;\n}\n\n.footer-right {\n position: fixed;\n bottom: 0;\n right: 0;\n background-color: var(--v-background); /* Match your app's background color */\n z-index: 100;\n}\n\n.correct {\n animation: varFade 1250ms ease-out;\n}\n\n.incorrect {\n animation: purpleFade 1250ms ease-out;\n}\n\na {\n text-decoration: underline;\n}\n\n.StudySession {\n flex: 1;\n display: flex;\n flex-direction: column;\n min-height: 0;\n}\n\n/* Let session-finished slot content fill the remaining column height (e.g.\n full-bleed end-screen heroes). The default fallback content is height:auto\n and unaffected beyond stretching to full width. */\n.session-finished-area {\n flex: 1;\n min-height: 0;\n display: flex;\n flex-direction: column;\n}\n\n.card-transition-container {\n position: relative;\n overflow: hidden;\n flex: 1;\n min-height: 0;\n display: flex;\n flex-direction: column;\n justify-content: center;\n}\n\n@keyframes varFade {\n 0% {\n box-shadow:\n rgba(var(--r), var(--g), 0, 0.25) 0px 7px 8px -4px,\n rgba(var(--r), var(--g), 0, 0.25) 0px 12px 17px 2px,\n rgba(var(--r), var(--g), 0, 0.25) 0px 5px 22px 4px;\n }\n 100% {\n box-shadow: rgba(0, 150, 0, 0) 0px 0px;\n }\n}\n\n@keyframes greenFade {\n 0% {\n box-shadow:\n rgba(0, 150, 0, 0.25) 0px 7px 8px -4px,\n rgba(0, 150, 0, 0.25) 0px 12px 17px 2px,\n rgba(0, 150, 0, 0.25) 0px 5px 22px 4px;\n }\n 100% {\n box-shadow: rgba(0, 150, 0, 0) 0px 0px;\n }\n}\n@keyframes purpleFade {\n 0% {\n box-shadow:\n rgba(115, 0, 75, 0.25) 0px 7px 8px -4px,\n rgba(115, 0, 75, 0.25) 0px 12px 17px 2px,\n rgba(115, 0, 75, 0.25) 0px 5px 22px 4px;\n }\n 100% {\n box-shadow: rgba(115, 0, 75, 0) 0px 0px;\n }\n}\n</style>\n\n<style>\n/* Transition presets — unscoped so classes reach child component root elements.\n Vue's <Transition> adds these classes to the child's root DOM node, which\n may be a Vuetify component (v-card). Scoped selectors can fail to match\n when the scope attribute doesn't propagate through component boundaries. */\n\n/* Preset: component-fade (default) — simple opacity crossfade, use with mode=\"out-in\" */\n.component-fade-enter-active,\n.component-fade-leave-active {\n transition: opacity 0.2s ease;\n}\n.component-fade-enter-from,\n.component-fade-leave-to {\n opacity: 0;\n}\n\n/* Preset: card-slide — backOutLeft exit / slide-in-right enter, use with mode=\"default\" */\n/* !important on position/inset: child components with scoped styles (e.g.\n `.gpc-intro[data-v-xxx] { position: relative }`) have higher specificity\n than this unscoped class. Without !important the leaving element stays\n in-flow and the enter/leave elements split vertical space — visible as\n the new card sliding in \"low\" then popping to center. */\n.card-slide-leave-active {\n animation: cardBackOutLeft 400ms cubic-bezier(0.4, 0, 0.7, 0.2) both;\n position: absolute !important;\n inset: 0;\n margin: auto;\n height: fit-content;\n will-change: transform, opacity;\n pointer-events: none;\n}\n.card-slide-leave-active * {\n animation-play-state: paused !important;\n transition: none !important;\n}\n.card-slide-enter-active {\n animation: cardSlideInRight 250ms ease-out 100ms both;\n will-change: transform, opacity;\n}\n\n@keyframes cardBackOutLeft {\n 0% {\n transform: translateX(0) scale(1);\n opacity: 1;\n }\n 40% {\n transform: translateX(0) scale(0.88);\n opacity: 0.8;\n }\n 100% {\n transform: translateX(-100%) scale(0.88);\n opacity: 0;\n }\n}\n\n@keyframes cardSlideInRight {\n 0% {\n transform: translateX(60%);\n opacity: 0;\n }\n 100% {\n transform: translateX(0);\n opacity: 1;\n }\n}\n\n/* Preset: card-scale — scale-down exit / gentle overshoot enter, use with mode=\"out-in\" */\n.card-scale-leave-active {\n transition: transform 150ms ease-in, opacity 150ms ease-in;\n}\n.card-scale-enter-active {\n transition: transform 200ms cubic-bezier(0.34, 1.4, 0.64, 1), opacity 150ms ease-out;\n}\n.card-scale-leave-to {\n transform: scale(0.92);\n opacity: 0;\n}\n.card-scale-enter-from {\n transform: scale(1.03);\n opacity: 0;\n}\n</style>\n"],"mappings":"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"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import "../_virtual/_rolldown/runtime.mjs";
|
|
2
|
+
import e from "../_virtual/_plugin-vue_export-helper.mjs";
|
|
3
|
+
import t from "./StudySessionTimer.vue_vue_type_script_lang.mjs";
|
|
4
|
+
/* empty css */
|
|
5
|
+
import { createBlock as n, createCommentVNode as r, createElementVNode 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/StudySessionTimer.vue
|
|
7
|
+
function _sfc_render(e, t, f, p, m, h) {
|
|
8
|
+
let g = l("v-icon"), _ = l("v-btn"), v = l("v-progress-circular"), y = l("v-tooltip");
|
|
9
|
+
return c(), n(y, {
|
|
10
|
+
location: "right",
|
|
11
|
+
"open-delay": 0,
|
|
12
|
+
"close-delay": 200,
|
|
13
|
+
color: "secondary",
|
|
14
|
+
class: "text-subtitle-1"
|
|
15
|
+
}, {
|
|
16
|
+
activator: d(({ props: l }) => [i("div", s({ class: "timer-container" }, l, {
|
|
17
|
+
onMouseenter: t[0] || (t[0] = (t) => e.hovered = !0),
|
|
18
|
+
onMouseleave: t[1] || (t[1] = (t) => e.hovered = !1)
|
|
19
|
+
}), [o(v, {
|
|
20
|
+
alt: "Time remaining in study session",
|
|
21
|
+
size: "64",
|
|
22
|
+
width: "8",
|
|
23
|
+
rotate: "0",
|
|
24
|
+
color: e.timerColor,
|
|
25
|
+
"model-value": e.percentageRemaining
|
|
26
|
+
}, {
|
|
27
|
+
default: d(() => [e.allowAddTime && e.timeRemaining > 0 && e.hovered ? (c(), n(_, {
|
|
28
|
+
key: 0,
|
|
29
|
+
icon: "",
|
|
30
|
+
color: "transparent",
|
|
31
|
+
location: "bottom left",
|
|
32
|
+
onClick: e.addSessionTime
|
|
33
|
+
}, {
|
|
34
|
+
default: d(() => [o(g, { size: "large" }, {
|
|
35
|
+
default: d(() => t[2] || (t[2] = [a("mdi-plus")])),
|
|
36
|
+
_: 1
|
|
37
|
+
})]),
|
|
38
|
+
_: 1
|
|
39
|
+
}, 8, ["onClick"])) : r("", !0)]),
|
|
40
|
+
_: 1
|
|
41
|
+
}, 8, ["color", "model-value"])], 16)]),
|
|
42
|
+
default: d(() => [a(" " + u(e.formattedTimeRemaining), 1)]),
|
|
43
|
+
_: 1
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
var f = /* @__PURE__ */ e(t, [["render", _sfc_render], ["__scopeId", "data-v-5e104ccc"]]);
|
|
47
|
+
//#endregion
|
|
48
|
+
export { f as default };
|
|
49
|
+
|
|
50
|
+
//# sourceMappingURL=StudySessionTimer.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"StudySessionTimer.mjs","names":[],"sources":["../../src/components/StudySessionTimer.vue"],"sourcesContent":["<!-- @vue-skuilder/common-ui/src/components/StudySessionTimer.vue -->\n<template>\n <v-tooltip location=\"right\" :open-delay=\"0\" :close-delay=\"200\" color=\"secondary\" class=\"text-subtitle-1\">\n <template #activator=\"{ props }\">\n <div class=\"timer-container\" v-bind=\"props\" @mouseenter=\"hovered = true\" @mouseleave=\"hovered = false\">\n <v-progress-circular\n alt=\"Time remaining in study session\"\n size=\"64\"\n width=\"8\"\n rotate=\"0\"\n :color=\"timerColor\"\n :model-value=\"percentageRemaining\"\n >\n <v-btn\n v-if=\"allowAddTime && timeRemaining > 0 && hovered\"\n icon\n color=\"transparent\"\n location=\"bottom left\"\n @click=\"addSessionTime\"\n >\n <v-icon size=\"large\">mdi-plus</v-icon>\n </v-btn>\n </v-progress-circular>\n </div>\n </template>\n {{ formattedTimeRemaining }}\n </v-tooltip>\n</template>\n\n<script lang=\"ts\">\nimport { defineComponent, computed, ref } from 'vue';\n\nexport default defineComponent({\n name: 'StudySessionTimer',\n\n props: {\n /**\n * Time remaining in seconds\n */\n timeRemaining: {\n type: Number,\n required: true,\n },\n /**\n * Total session time limit in minutes\n */\n sessionTimeLimit: {\n type: Number,\n required: true,\n default: 5,\n },\n /**\n * Whether to offer the hover-reveal \"+\" (add-time) affordance at all.\n * Courses with a deliberate session-dose policy (fixed, age-measured\n * screen time) set this false so the control is absent rather than\n * present-but-inert.\n */\n allowAddTime: {\n type: Boolean,\n default: true,\n },\n },\n\n emits: ['add-time'],\n\n setup(props, { emit }) {\n const hovered = ref(false);\n\n /**\n * Formats the time remaining into a readable string\n */\n const formattedTimeRemaining = computed(() => {\n let timeString = '';\n const seconds = props.timeRemaining;\n\n if (seconds > 60) {\n timeString = Math.floor(seconds / 60).toString() + ':';\n }\n\n const secondsRemaining = seconds % 60;\n timeString += secondsRemaining >= 10 ? secondsRemaining : '0' + secondsRemaining;\n\n if (seconds <= 60) {\n timeString += ' seconds';\n }\n\n timeString += ' left!';\n\n return timeString;\n });\n\n /**\n * Calculates the percentage of time remaining for the progress indicator\n */\n const percentageRemaining = computed(() => {\n return props.timeRemaining > 60\n ? 100 * (props.timeRemaining / (60 * props.sessionTimeLimit))\n : 100 * (props.timeRemaining / 60);\n });\n\n /**\n * Determines the color of the timer based on time remaining\n */\n const timerColor = computed(() => {\n return props.timeRemaining > 60 ? 'primary' : 'orange darken-3';\n });\n\n /**\n * Handles adding time to the session\n */\n const addSessionTime = () => {\n emit('add-time');\n };\n\n return {\n hovered,\n formattedTimeRemaining,\n percentageRemaining,\n timerColor,\n addSessionTime,\n };\n },\n});\n</script>\n\n<style scoped>\n.timer-container {\n display: inline-flex;\n cursor: pointer;\n}\n</style>\n"],"mappings":";;;;;;;;aAEE,EAwBY,GAAA;EAxBD,UAAS;EAAS,cAAY;EAAI,eAAa;EAAK,OAAM;EAAY,OAAM;;EAC1E,WAAS,GAoBZ,EApBgB,eAAK,CAC3B,EAmBM,OAnBN,EAmBM,EAnBD,OAAM,mBAAiB,EAAS,GAAK;GAAG,cAAU,EAAA,OAAA,EAAA,MAAA,MAAE,EAAA,UAAO;GAAU,cAAU,EAAA,OAAA,EAAA,MAAA,MAAE,EAAA,UAAO;OAC3F,EAiBsB,GAAA;GAhBpB,KAAI;GACJ,MAAK;GACL,OAAM;GACN,QAAO;GACN,OAAO,EAAA;GACP,eAAa,EAAA;;GAXxB,SAAA,QAqBkB,CAPA,EAAA,gBAAgB,EAAA,gBAAa,KAAQ,EAAA,WAAA,GAAA,EAD7C,EAQQ,GAAA;IArBlB,KAAA;IAeY,MAAA;IACA,OAAM;IACN,UAAS;IACR,SAAO,EAAA;;IAlBpB,SAAA,QAoBkD,CAAtC,EAAsC,GAAA,EAA9B,MAAK,SAAO,EAAA;KApBhC,SAAA,QAoByC,EAAA,OAAA,EAAA,KAAA,CApBzC,EAoBiC,WAAQ,CAAA,EAAA;KApBzC,GAAA;;IAAA,GAAA;yBAAA,EAAA,IAAA,GAAA,CAAA,CAAA;GAAA,GAAA;;EAAA,SAAA,QAyBI,CAzBJ,EAwBe,MACX,EAAG,EAAA,uBAAsB,EAAA,EAAA,CAAA,CAAA;EAzB7B,GAAA"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import "../_virtual/_rolldown/runtime.mjs";
|
|
2
|
+
import { computed as e, defineComponent as t, ref as n } from "vue";
|
|
3
|
+
//#region src/components/StudySessionTimer.vue?vue&type=script&lang.ts
|
|
4
|
+
var r = t({
|
|
5
|
+
name: "StudySessionTimer",
|
|
6
|
+
props: {
|
|
7
|
+
timeRemaining: {
|
|
8
|
+
type: Number,
|
|
9
|
+
required: !0
|
|
10
|
+
},
|
|
11
|
+
sessionTimeLimit: {
|
|
12
|
+
type: Number,
|
|
13
|
+
required: !0,
|
|
14
|
+
default: 5
|
|
15
|
+
},
|
|
16
|
+
allowAddTime: {
|
|
17
|
+
type: Boolean,
|
|
18
|
+
default: !0
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
emits: ["add-time"],
|
|
22
|
+
setup(t, { emit: r }) {
|
|
23
|
+
let i = n(!1), a = e(() => {
|
|
24
|
+
let e = "", n = t.timeRemaining;
|
|
25
|
+
n > 60 && (e = Math.floor(n / 60).toString() + ":");
|
|
26
|
+
let r = n % 60;
|
|
27
|
+
return e += r >= 10 ? r : "0" + r, n <= 60 && (e += " seconds"), e += " left!", e;
|
|
28
|
+
}), o = e(() => t.timeRemaining > 60 ? 100 * (t.timeRemaining / (60 * t.sessionTimeLimit)) : 100 * (t.timeRemaining / 60)), s = e(() => t.timeRemaining > 60 ? "primary" : "orange darken-3"), addSessionTime = () => {
|
|
29
|
+
r("add-time");
|
|
30
|
+
};
|
|
31
|
+
return {
|
|
32
|
+
hovered: i,
|
|
33
|
+
formattedTimeRemaining: a,
|
|
34
|
+
percentageRemaining: o,
|
|
35
|
+
timerColor: s,
|
|
36
|
+
addSessionTime
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
//#endregion
|
|
41
|
+
export { r as default };
|
|
42
|
+
|
|
43
|
+
//# sourceMappingURL=StudySessionTimer.vue_vue_type_script_lang.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"StudySessionTimer.vue_vue_type_script_lang.mjs","names":[],"sources":["../../src/components/StudySessionTimer.vue"],"sourcesContent":["<!-- @vue-skuilder/common-ui/src/components/StudySessionTimer.vue -->\n<template>\n <v-tooltip location=\"right\" :open-delay=\"0\" :close-delay=\"200\" color=\"secondary\" class=\"text-subtitle-1\">\n <template #activator=\"{ props }\">\n <div class=\"timer-container\" v-bind=\"props\" @mouseenter=\"hovered = true\" @mouseleave=\"hovered = false\">\n <v-progress-circular\n alt=\"Time remaining in study session\"\n size=\"64\"\n width=\"8\"\n rotate=\"0\"\n :color=\"timerColor\"\n :model-value=\"percentageRemaining\"\n >\n <v-btn\n v-if=\"allowAddTime && timeRemaining > 0 && hovered\"\n icon\n color=\"transparent\"\n location=\"bottom left\"\n @click=\"addSessionTime\"\n >\n <v-icon size=\"large\">mdi-plus</v-icon>\n </v-btn>\n </v-progress-circular>\n </div>\n </template>\n {{ formattedTimeRemaining }}\n </v-tooltip>\n</template>\n\n<script lang=\"ts\">\nimport { defineComponent, computed, ref } from 'vue';\n\nexport default defineComponent({\n name: 'StudySessionTimer',\n\n props: {\n /**\n * Time remaining in seconds\n */\n timeRemaining: {\n type: Number,\n required: true,\n },\n /**\n * Total session time limit in minutes\n */\n sessionTimeLimit: {\n type: Number,\n required: true,\n default: 5,\n },\n /**\n * Whether to offer the hover-reveal \"+\" (add-time) affordance at all.\n * Courses with a deliberate session-dose policy (fixed, age-measured\n * screen time) set this false so the control is absent rather than\n * present-but-inert.\n */\n allowAddTime: {\n type: Boolean,\n default: true,\n },\n },\n\n emits: ['add-time'],\n\n setup(props, { emit }) {\n const hovered = ref(false);\n\n /**\n * Formats the time remaining into a readable string\n */\n const formattedTimeRemaining = computed(() => {\n let timeString = '';\n const seconds = props.timeRemaining;\n\n if (seconds > 60) {\n timeString = Math.floor(seconds / 60).toString() + ':';\n }\n\n const secondsRemaining = seconds % 60;\n timeString += secondsRemaining >= 10 ? secondsRemaining : '0' + secondsRemaining;\n\n if (seconds <= 60) {\n timeString += ' seconds';\n }\n\n timeString += ' left!';\n\n return timeString;\n });\n\n /**\n * Calculates the percentage of time remaining for the progress indicator\n */\n const percentageRemaining = computed(() => {\n return props.timeRemaining > 60\n ? 100 * (props.timeRemaining / (60 * props.sessionTimeLimit))\n : 100 * (props.timeRemaining / 60);\n });\n\n /**\n * Determines the color of the timer based on time remaining\n */\n const timerColor = computed(() => {\n return props.timeRemaining > 60 ? 'primary' : 'orange darken-3';\n });\n\n /**\n * Handles adding time to the session\n */\n const addSessionTime = () => {\n emit('add-time');\n };\n\n return {\n hovered,\n formattedTimeRemaining,\n percentageRemaining,\n timerColor,\n addSessionTime,\n };\n },\n});\n</script>\n\n<style scoped>\n.timer-container {\n display: inline-flex;\n cursor: pointer;\n}\n</style>\n"],"mappings":";;;AAgCA,IAAA,IAAe,EAAgB;CAC7B,MAAM;CAEN,OAAO;EAIL,eAAe;GACb,MAAM;GACN,UAAU;GACX;EAID,kBAAkB;GAChB,MAAM;GACN,UAAU;GACV,SAAS;GACV;EAOD,cAAc;GACZ,MAAM;GACN,SAAS;GACV;EACF;CAED,OAAO,CAAC,WAAW;CAEnB,MAAM,GAAO,EAAE,WAAQ;EACrB,IAAM,IAAU,EAAI,GAAM,EAKpB,IAAyB,QAAe;GAC5C,IAAI,IAAa,IACX,IAAU,EAAM;AAEtB,GAAI,IAAU,OACZ,IAAa,KAAK,MAAM,IAAU,GAAG,CAAC,UAAS,GAAI;GAGrD,IAAM,IAAmB,IAAU;AASnC,UARA,KAAc,KAAoB,KAAK,IAAmB,MAAM,GAE5D,KAAW,OACb,KAAc,aAGhB,KAAc,UAEP;IACP,EAKI,IAAsB,QACnB,EAAM,gBAAgB,KACzB,OAAO,EAAM,iBAAiB,KAAK,EAAM,qBACzC,OAAO,EAAM,gBAAgB,IACjC,EAKI,IAAa,QACV,EAAM,gBAAgB,KAAK,YAAY,kBAC9C,EAKI,uBAAuB;AAC3B,KAAK,WAAW;;AAGlB,SAAO;GACL;GACA;GACA;GACA;GACA;GACD;;CAEJ,CAAC"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import "../_virtual/_rolldown/runtime.mjs";
|
|
2
|
+
import e from "../_virtual/_plugin-vue_export-helper.mjs";
|
|
3
|
+
import t from "./TagsInput.vue_vue_type_script_lang.mjs";
|
|
4
|
+
/* empty css */
|
|
5
|
+
import { createBlock as n, createCommentVNode as r, createElementBlock as i, createElementVNode as a, createTextVNode as o, createVNode as s, normalizeClass as c, openBlock as l, resolveComponent as u, toDisplayString as d, withCtx as f } from "vue";
|
|
6
|
+
//#region src/components/TagsInput.vue
|
|
7
|
+
var p = { "data-cy": "tags-input" }, m = { class: "tag-name" }, h = {
|
|
8
|
+
key: 0,
|
|
9
|
+
class: "tag-snippet"
|
|
10
|
+
};
|
|
11
|
+
function _sfc_render(e, t, g, _, v, y) {
|
|
12
|
+
let b = u("vue-tags-input"), x = u("v-btn");
|
|
13
|
+
return l(), i("div", p, [s(b, {
|
|
14
|
+
modelValue: e.tag,
|
|
15
|
+
"onUpdate:modelValue": t[0] || (t[0] = (t) => e.tag = t),
|
|
16
|
+
tags: e.tags,
|
|
17
|
+
"autocomplete-items": e.autoCompleteSuggestions,
|
|
18
|
+
separators: e.separators,
|
|
19
|
+
"add-on-key": e.separators,
|
|
20
|
+
onTagsChanged: e.tagsChanged
|
|
21
|
+
}, {
|
|
22
|
+
"autocomplete-item": f((e) => [a("div", { class: c(["autocomplete-item", { "is-active": e.selected }]) }, [a("span", m, d(e.item.text), 1), e.item.data && e.item.data.snippet ? (l(), i("span", h, " - " + d(e.item.data.snippet), 1)) : r("", !0)], 2)]),
|
|
23
|
+
_: 1
|
|
24
|
+
}, 8, [
|
|
25
|
+
"modelValue",
|
|
26
|
+
"tags",
|
|
27
|
+
"autocomplete-items",
|
|
28
|
+
"separators",
|
|
29
|
+
"add-on-key",
|
|
30
|
+
"onTagsChanged"
|
|
31
|
+
]), e.hideSubmit ? r("", !0) : (l(), n(x, {
|
|
32
|
+
key: 0,
|
|
33
|
+
color: "success",
|
|
34
|
+
loading: e.loading,
|
|
35
|
+
onClick: e.submit
|
|
36
|
+
}, {
|
|
37
|
+
default: f(() => t[1] || (t[1] = [o("Save Changes")])),
|
|
38
|
+
_: 1
|
|
39
|
+
}, 8, ["loading", "onClick"]))]);
|
|
40
|
+
}
|
|
41
|
+
var g = /* @__PURE__ */ e(t, [["render", _sfc_render], ["__scopeId", "data-v-f7373dc0"]]);
|
|
42
|
+
//#endregion
|
|
43
|
+
export { g as default };
|
|
44
|
+
|
|
45
|
+
//# sourceMappingURL=TagsInput.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TagsInput.mjs","names":[],"sources":["../../src/components/TagsInput.vue"],"sourcesContent":["<template>\n <div data-cy=\"tags-input\">\n <vue-tags-input\n v-model=\"tag\"\n :tags=\"tags\"\n :autocomplete-items=\"autoCompleteSuggestions\"\n :separators=\"separators\"\n :add-on-key=\"separators\"\n @tags-changed=\"tagsChanged\"\n >\n <template #autocomplete-item=\"props\">\n <div class=\"autocomplete-item\" :class=\"{ 'is-active': props.selected }\">\n <span class=\"tag-name\">{{ props.item.text }}</span>\n <span v-if=\"props.item.data && props.item.data.snippet\" class=\"tag-snippet\">\n - {{ props.item.data.snippet }}\n </span>\n </div>\n </template>\n </vue-tags-input>\n\n <v-btn v-if=\"!hideSubmit\" color=\"success\" :loading=\"loading\" @click=\"submit\">Save Changes</v-btn>\n </div>\n</template>\n\n<script lang=\"ts\">\nimport { defineComponent } from 'vue';\nimport { getDataLayer, Tag, CourseDBInterface } from '@vue-skuilder/db';\n// @ts-expect-error - suppress TS error for VueTagsInput - no types available\nimport { VueTagsInput } from '@vojtechlanka/vue-tags-input';\n\nexport interface TagObject {\n text: string;\n style?: string;\n classes?: string;\n data?: {\n snippet: string;\n };\n}\n\nexport interface TagsInputInstance {\n tags: Array<{\n text: string;\n style?: string;\n classes?: string;\n data?: {\n snippet: string;\n };\n }>;\n submit: () => Promise<void>;\n updateAvailableCourseTags: () => Promise<void>;\n}\n\nexport default defineComponent({\n name: 'SkTagsInput',\n\n components: {\n VueTagsInput,\n },\n\n props: {\n courseID: {\n type: String,\n required: true,\n default: '',\n },\n cardID: {\n type: String,\n required: false,\n default: '',\n },\n hideSubmit: {\n type: Boolean,\n required: false,\n default: false,\n },\n },\n\n data() {\n return {\n loading: true,\n tag: '',\n tags: [] as TagObject[],\n initialTags: [] as string[],\n availableCourseTags: [] as Tag[],\n separators: [';', ',', ' '] as string[],\n courseDB: null as CourseDBInterface | null,\n };\n },\n\n computed: {\n autoCompleteSuggestions(): TagObject[] {\n return this.availableCourseTags\n .filter((availableTag) => {\n return availableTag.name.toLowerCase().indexOf(this.tag.toLowerCase()) !== -1;\n })\n .map((availableTag) => {\n return {\n text: availableTag.name,\n data: {\n snippet: availableTag.snippet,\n },\n };\n });\n },\n },\n\n watch: {\n async cardID() {\n await this.getAppliedTags();\n },\n async courseID() {\n this.courseDB = getDataLayer().getCourseDB(this.courseID);\n await this.updateAvailableCourseTags();\n },\n },\n\n async created() {\n this.courseDB = getDataLayer().getCourseDB(this.courseID);\n\n await this.updateAvailableCourseTags();\n await this.getAppliedTags();\n },\n\n methods: {\n tagsChanged(newTags: TagObject[]) {\n console.log(`[TagsInput] Tags changing: ${JSON.stringify(newTags)}`);\n this.tags = newTags;\n },\n\n async getAppliedTags() {\n this.initialTags = [];\n this.tags = [];\n try {\n const appliedDocsFindResult = await this.courseDB!.getAppliedTags(this.cardID);\n appliedDocsFindResult.rows.forEach((row) => {\n console.log(`[TagsInput] The following tag is applied:\n\\t${JSON.stringify(row)}`);\n this.tags.push({\n text: row!.value.name,\n style: '',\n classes: '',\n });\n });\n this.initialTags = this.tags.map((tag) => tag.text);\n } catch (e) {\n console.error(`Error in init-getAppliedTags: ${JSON.stringify(e)}, ${e}`);\n } finally {\n this.loading = false;\n }\n },\n\n async updateAvailableCourseTags() {\n try {\n this.availableCourseTags = (await this.courseDB!.getCourseTagStubs()).rows.map((row) => {\n // console.log(`[TagsInput] available tag: ${JSON.stringify(row)}`);\n return row.doc! as Tag;\n });\n } catch (e) {\n console.error(`Error in init-availableCourseTags: ${JSON.stringify(e)}`);\n }\n },\n\n async submit() {\n console.log(`[TagsInput] tagsInput is submitting...`);\n this.loading = true;\n\n try {\n await Promise.all(\n this.tags.map(async (currentTag) => {\n if (!this.initialTags.includes(currentTag.text)) {\n try {\n await this.courseDB!.addTagToCard(this.cardID, currentTag.text);\n console.log(`[TagsInput] Successfully added tag: ${currentTag.text}`);\n } catch (error) {\n console.error(`Failed to add tag ${currentTag.text}:`, error);\n }\n }\n })\n );\n } catch (e) {\n console.error(`Exception adding tags: ${JSON.stringify(e)}`);\n }\n\n try {\n await Promise.all(\n this.initialTags.map(async (initialTag) => {\n if (\n this.tags.filter((tag) => {\n return tag.text === initialTag;\n }).length === 0\n ) {\n try {\n await this.courseDB!.removeTagFromCard(this.cardID, initialTag);\n console.log(`[TagsInput] Successfully removed tag: ${initialTag}`);\n } catch (error) {\n console.error(`Failed to remove tag ${initialTag}:`, error);\n }\n }\n })\n );\n } catch (e) {\n console.error(`Exception removing tags: ${JSON.stringify(e)}`);\n }\n this.loading = false;\n },\n },\n});\n</script>\n\n<style scoped>\n.vue-tags-input {\n max-width: 100%;\n}\n\n.autocomplete-item {\n display: flex;\n align-items: center;\n padding: 5px;\n}\n\n.autocomplete-item.is-active {\n background-color: #e8e8e8;\n}\n\n.autocomplete-item.is-active > .tag-name {\n background-color: #5c6bc0;\n color: white;\n}\n\n.tag-name {\n background-color: #e0e0e0;\n color: #333;\n padding: 2px 6px;\n border-radius: 3px;\n font-weight: bold;\n margin-right: 5px;\n}\n\n.tag-snippet {\n color: #666;\n font-size: 0.9em;\n}\n\n.autocomplete-item.is-active .tag-snippet {\n color: #333; /* Ensure snippet text is visible when item is active */\n}\n</style>\n"],"mappings":";;;;;;UACO,WAAQ,cAAY,QAWX,OAAM,YAAU;CAZhC,KAAA;CAakE,OAAM;;;;aAZtE,EAoBM,OApBN,GAoBM,CAnBJ,EAgBiB,GAAA;EAlBrB,YAGe,EAAA;EAHf,uBAAA,EAAA,OAAA,EAAA,MAAA,MAAA,EAGe,MAAG;EACX,MAAM,EAAA;EACN,sBAAoB,EAAA;EACpB,YAAY,EAAA;EACZ,cAAY,EAAA;EACZ,eAAc,EAAA;;EAEJ,qBAAiB,GAMpB,MAN2B,CACjC,EAKM,OAAA,EALD,OAXb,EAAA,CAWmB,qBAAmB,EAAA,aAAwB,EAAM,UAAQ,CAAA,CAAA,EAAA,EAAA,CAClE,EAAmD,QAAnD,GAAmD,EAAzB,EAAM,KAAK,KAAI,EAAA,EAAA,EAC7B,EAAM,KAAK,QAAQ,EAAM,KAAK,KAAK,WAAA,GAAA,EAA/C,EAEO,QAFP,GAA4E,QACxE,EAAG,EAAM,KAAK,KAAK,QAAO,EAAA,EAAA,IAdxC,EAAA,IAAA,GAAA,CAAA,EAAA,EAAA,CAAA,CAAA;EAAA,GAAA;;;;;;;;KAoBkB,EAAA,aApBlB,EAAA,IAAA,GAAA,IAoBkB,GAAA,EAAd,EAAiG,GAAA;EApBrG,KAAA;EAoB8B,OAAM;EAAW,SAAS,EAAA;EAAU,SAAO,EAAA;;EApBzE,SAAA,QAoB6F,EAAA,OAAA,EAAA,KAAA,CApB7F,EAoBiF,eAAY,CAAA,EAAA;EApB7F,GAAA;gCAAA,CAAA"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import "../_virtual/_rolldown/runtime.mjs";
|
|
2
|
+
import { defineComponent as e } from "vue";
|
|
3
|
+
import { getDataLayer as t } from "@vue-skuilder/db";
|
|
4
|
+
import { VueTagsInput as n } from "@vojtechlanka/vue-tags-input";
|
|
5
|
+
//#region src/components/TagsInput.vue?vue&type=script&lang.ts
|
|
6
|
+
var r = e({
|
|
7
|
+
name: "SkTagsInput",
|
|
8
|
+
components: { VueTagsInput: n },
|
|
9
|
+
props: {
|
|
10
|
+
courseID: {
|
|
11
|
+
type: String,
|
|
12
|
+
required: !0,
|
|
13
|
+
default: ""
|
|
14
|
+
},
|
|
15
|
+
cardID: {
|
|
16
|
+
type: String,
|
|
17
|
+
required: !1,
|
|
18
|
+
default: ""
|
|
19
|
+
},
|
|
20
|
+
hideSubmit: {
|
|
21
|
+
type: Boolean,
|
|
22
|
+
required: !1,
|
|
23
|
+
default: !1
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
data() {
|
|
27
|
+
return {
|
|
28
|
+
loading: !0,
|
|
29
|
+
tag: "",
|
|
30
|
+
tags: [],
|
|
31
|
+
initialTags: [],
|
|
32
|
+
availableCourseTags: [],
|
|
33
|
+
separators: [
|
|
34
|
+
";",
|
|
35
|
+
",",
|
|
36
|
+
" "
|
|
37
|
+
],
|
|
38
|
+
courseDB: null
|
|
39
|
+
};
|
|
40
|
+
},
|
|
41
|
+
computed: { autoCompleteSuggestions() {
|
|
42
|
+
return this.availableCourseTags.filter((e) => e.name.toLowerCase().indexOf(this.tag.toLowerCase()) !== -1).map((e) => ({
|
|
43
|
+
text: e.name,
|
|
44
|
+
data: { snippet: e.snippet }
|
|
45
|
+
}));
|
|
46
|
+
} },
|
|
47
|
+
watch: {
|
|
48
|
+
async cardID() {
|
|
49
|
+
await this.getAppliedTags();
|
|
50
|
+
},
|
|
51
|
+
async courseID() {
|
|
52
|
+
this.courseDB = t().getCourseDB(this.courseID), await this.updateAvailableCourseTags();
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
async created() {
|
|
56
|
+
this.courseDB = t().getCourseDB(this.courseID), await this.updateAvailableCourseTags(), await this.getAppliedTags();
|
|
57
|
+
},
|
|
58
|
+
methods: {
|
|
59
|
+
tagsChanged(e) {
|
|
60
|
+
console.log(`[TagsInput] Tags changing: ${JSON.stringify(e)}`), this.tags = e;
|
|
61
|
+
},
|
|
62
|
+
async getAppliedTags() {
|
|
63
|
+
this.initialTags = [], this.tags = [];
|
|
64
|
+
try {
|
|
65
|
+
(await this.courseDB.getAppliedTags(this.cardID)).rows.forEach((e) => {
|
|
66
|
+
console.log(`[TagsInput] The following tag is applied:
|
|
67
|
+
\t${JSON.stringify(e)}`), this.tags.push({
|
|
68
|
+
text: e.value.name,
|
|
69
|
+
style: "",
|
|
70
|
+
classes: ""
|
|
71
|
+
});
|
|
72
|
+
}), this.initialTags = this.tags.map((e) => e.text);
|
|
73
|
+
} catch (e) {
|
|
74
|
+
console.error(`Error in init-getAppliedTags: ${JSON.stringify(e)}, ${e}`);
|
|
75
|
+
} finally {
|
|
76
|
+
this.loading = !1;
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
async updateAvailableCourseTags() {
|
|
80
|
+
try {
|
|
81
|
+
this.availableCourseTags = (await this.courseDB.getCourseTagStubs()).rows.map((e) => e.doc);
|
|
82
|
+
} catch (e) {
|
|
83
|
+
console.error(`Error in init-availableCourseTags: ${JSON.stringify(e)}`);
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
async submit() {
|
|
87
|
+
console.log("[TagsInput] tagsInput is submitting..."), this.loading = !0;
|
|
88
|
+
try {
|
|
89
|
+
await Promise.all(this.tags.map(async (e) => {
|
|
90
|
+
if (!this.initialTags.includes(e.text)) try {
|
|
91
|
+
await this.courseDB.addTagToCard(this.cardID, e.text), console.log(`[TagsInput] Successfully added tag: ${e.text}`);
|
|
92
|
+
} catch (t) {
|
|
93
|
+
console.error(`Failed to add tag ${e.text}:`, t);
|
|
94
|
+
}
|
|
95
|
+
}));
|
|
96
|
+
} catch (e) {
|
|
97
|
+
console.error(`Exception adding tags: ${JSON.stringify(e)}`);
|
|
98
|
+
}
|
|
99
|
+
try {
|
|
100
|
+
await Promise.all(this.initialTags.map(async (e) => {
|
|
101
|
+
if (this.tags.filter((t) => t.text === e).length === 0) try {
|
|
102
|
+
await this.courseDB.removeTagFromCard(this.cardID, e), console.log(`[TagsInput] Successfully removed tag: ${e}`);
|
|
103
|
+
} catch (t) {
|
|
104
|
+
console.error(`Failed to remove tag ${e}:`, t);
|
|
105
|
+
}
|
|
106
|
+
}));
|
|
107
|
+
} catch (e) {
|
|
108
|
+
console.error(`Exception removing tags: ${JSON.stringify(e)}`);
|
|
109
|
+
}
|
|
110
|
+
this.loading = !1;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
//#endregion
|
|
115
|
+
export { r as default };
|
|
116
|
+
|
|
117
|
+
//# sourceMappingURL=TagsInput.vue_vue_type_script_lang.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TagsInput.vue_vue_type_script_lang.mjs","names":[],"sources":["../../src/components/TagsInput.vue"],"sourcesContent":["<template>\n <div data-cy=\"tags-input\">\n <vue-tags-input\n v-model=\"tag\"\n :tags=\"tags\"\n :autocomplete-items=\"autoCompleteSuggestions\"\n :separators=\"separators\"\n :add-on-key=\"separators\"\n @tags-changed=\"tagsChanged\"\n >\n <template #autocomplete-item=\"props\">\n <div class=\"autocomplete-item\" :class=\"{ 'is-active': props.selected }\">\n <span class=\"tag-name\">{{ props.item.text }}</span>\n <span v-if=\"props.item.data && props.item.data.snippet\" class=\"tag-snippet\">\n - {{ props.item.data.snippet }}\n </span>\n </div>\n </template>\n </vue-tags-input>\n\n <v-btn v-if=\"!hideSubmit\" color=\"success\" :loading=\"loading\" @click=\"submit\">Save Changes</v-btn>\n </div>\n</template>\n\n<script lang=\"ts\">\nimport { defineComponent } from 'vue';\nimport { getDataLayer, Tag, CourseDBInterface } from '@vue-skuilder/db';\n// @ts-expect-error - suppress TS error for VueTagsInput - no types available\nimport { VueTagsInput } from '@vojtechlanka/vue-tags-input';\n\nexport interface TagObject {\n text: string;\n style?: string;\n classes?: string;\n data?: {\n snippet: string;\n };\n}\n\nexport interface TagsInputInstance {\n tags: Array<{\n text: string;\n style?: string;\n classes?: string;\n data?: {\n snippet: string;\n };\n }>;\n submit: () => Promise<void>;\n updateAvailableCourseTags: () => Promise<void>;\n}\n\nexport default defineComponent({\n name: 'SkTagsInput',\n\n components: {\n VueTagsInput,\n },\n\n props: {\n courseID: {\n type: String,\n required: true,\n default: '',\n },\n cardID: {\n type: String,\n required: false,\n default: '',\n },\n hideSubmit: {\n type: Boolean,\n required: false,\n default: false,\n },\n },\n\n data() {\n return {\n loading: true,\n tag: '',\n tags: [] as TagObject[],\n initialTags: [] as string[],\n availableCourseTags: [] as Tag[],\n separators: [';', ',', ' '] as string[],\n courseDB: null as CourseDBInterface | null,\n };\n },\n\n computed: {\n autoCompleteSuggestions(): TagObject[] {\n return this.availableCourseTags\n .filter((availableTag) => {\n return availableTag.name.toLowerCase().indexOf(this.tag.toLowerCase()) !== -1;\n })\n .map((availableTag) => {\n return {\n text: availableTag.name,\n data: {\n snippet: availableTag.snippet,\n },\n };\n });\n },\n },\n\n watch: {\n async cardID() {\n await this.getAppliedTags();\n },\n async courseID() {\n this.courseDB = getDataLayer().getCourseDB(this.courseID);\n await this.updateAvailableCourseTags();\n },\n },\n\n async created() {\n this.courseDB = getDataLayer().getCourseDB(this.courseID);\n\n await this.updateAvailableCourseTags();\n await this.getAppliedTags();\n },\n\n methods: {\n tagsChanged(newTags: TagObject[]) {\n console.log(`[TagsInput] Tags changing: ${JSON.stringify(newTags)}`);\n this.tags = newTags;\n },\n\n async getAppliedTags() {\n this.initialTags = [];\n this.tags = [];\n try {\n const appliedDocsFindResult = await this.courseDB!.getAppliedTags(this.cardID);\n appliedDocsFindResult.rows.forEach((row) => {\n console.log(`[TagsInput] The following tag is applied:\n\\t${JSON.stringify(row)}`);\n this.tags.push({\n text: row!.value.name,\n style: '',\n classes: '',\n });\n });\n this.initialTags = this.tags.map((tag) => tag.text);\n } catch (e) {\n console.error(`Error in init-getAppliedTags: ${JSON.stringify(e)}, ${e}`);\n } finally {\n this.loading = false;\n }\n },\n\n async updateAvailableCourseTags() {\n try {\n this.availableCourseTags = (await this.courseDB!.getCourseTagStubs()).rows.map((row) => {\n // console.log(`[TagsInput] available tag: ${JSON.stringify(row)}`);\n return row.doc! as Tag;\n });\n } catch (e) {\n console.error(`Error in init-availableCourseTags: ${JSON.stringify(e)}`);\n }\n },\n\n async submit() {\n console.log(`[TagsInput] tagsInput is submitting...`);\n this.loading = true;\n\n try {\n await Promise.all(\n this.tags.map(async (currentTag) => {\n if (!this.initialTags.includes(currentTag.text)) {\n try {\n await this.courseDB!.addTagToCard(this.cardID, currentTag.text);\n console.log(`[TagsInput] Successfully added tag: ${currentTag.text}`);\n } catch (error) {\n console.error(`Failed to add tag ${currentTag.text}:`, error);\n }\n }\n })\n );\n } catch (e) {\n console.error(`Exception adding tags: ${JSON.stringify(e)}`);\n }\n\n try {\n await Promise.all(\n this.initialTags.map(async (initialTag) => {\n if (\n this.tags.filter((tag) => {\n return tag.text === initialTag;\n }).length === 0\n ) {\n try {\n await this.courseDB!.removeTagFromCard(this.cardID, initialTag);\n console.log(`[TagsInput] Successfully removed tag: ${initialTag}`);\n } catch (error) {\n console.error(`Failed to remove tag ${initialTag}:`, error);\n }\n }\n })\n );\n } catch (e) {\n console.error(`Exception removing tags: ${JSON.stringify(e)}`);\n }\n this.loading = false;\n },\n },\n});\n</script>\n\n<style scoped>\n.vue-tags-input {\n max-width: 100%;\n}\n\n.autocomplete-item {\n display: flex;\n align-items: center;\n padding: 5px;\n}\n\n.autocomplete-item.is-active {\n background-color: #e8e8e8;\n}\n\n.autocomplete-item.is-active > .tag-name {\n background-color: #5c6bc0;\n color: white;\n}\n\n.tag-name {\n background-color: #e0e0e0;\n color: #333;\n padding: 2px 6px;\n border-radius: 3px;\n font-weight: bold;\n margin-right: 5px;\n}\n\n.tag-snippet {\n color: #666;\n font-size: 0.9em;\n}\n\n.autocomplete-item.is-active .tag-snippet {\n color: #333; /* Ensure snippet text is visible when item is active */\n}\n</style>\n"],"mappings":";;;;;AAoDA,IAAA,IAAe,EAAgB;CAC7B,MAAM;CAEN,YAAY,EACV,iBACD;CAED,OAAO;EACL,UAAU;GACR,MAAM;GACN,UAAU;GACV,SAAS;GACV;EACD,QAAQ;GACN,MAAM;GACN,UAAU;GACV,SAAS;GACV;EACD,YAAY;GACV,MAAM;GACN,UAAU;GACV,SAAS;GACV;EACF;CAED,OAAO;AACL,SAAO;GACL,SAAS;GACT,KAAK;GACL,MAAM,EAAC;GACP,aAAa,EAAC;GACd,qBAAqB,EAAC;GACtB,YAAY;IAAC;IAAK;IAAK;IAAG;GAC1B,UAAU;GACX;;CAGH,UAAU,EACR,0BAAuC;AACrC,SAAO,KAAK,oBACT,QAAQ,MACA,EAAa,KAAK,aAAa,CAAC,QAAQ,KAAK,IAAI,aAAa,CAAA,KAAM,GAC5E,CACA,KAAK,OACG;GACL,MAAM,EAAa;GACnB,MAAM,EACJ,SAAS,EAAa,SACvB;GACF,EACD;IAEP;CAED,OAAO;EACL,MAAM,SAAS;AACb,SAAM,KAAK,gBAAgB;;EAE7B,MAAM,WAAW;AAEf,GADA,KAAK,WAAW,GAAc,CAAC,YAAY,KAAK,SAAS,EACzD,MAAM,KAAK,2BAA2B;;EAEzC;CAED,MAAM,UAAU;AAId,EAHA,KAAK,WAAW,GAAc,CAAC,YAAY,KAAK,SAAS,EAEzD,MAAM,KAAK,2BAA2B,EACtC,MAAM,KAAK,gBAAgB;;CAG7B,SAAS;EACP,YAAY,GAAsB;AAEhC,GADA,QAAQ,IAAI,8BAA8B,KAAK,UAAU,EAAQ,GAAG,EACpE,KAAK,OAAO;;EAGd,MAAM,iBAAiB;AAErB,GADA,KAAK,cAAc,EAAE,EACrB,KAAK,OAAO,EAAE;AACd,OAAI;AAWF,KAV8B,MAAM,KAAK,SAAU,eAAe,KAAK,OAAO,EACxD,KAAK,SAAS,MAAQ;AAG1C,KAFA,QAAQ,IAAI;IAClB,KAAK,UAAU,EAAI,GAAG,EAChB,KAAK,KAAK,KAAK;MACb,MAAM,EAAK,MAAM;MACjB,OAAO;MACP,SAAS;MACV,CAAC;MACF,EACF,KAAK,cAAc,KAAK,KAAK,KAAK,MAAQ,EAAI,KAAK;YAC5C,GAAG;AACV,YAAQ,MAAM,iCAAiC,KAAK,UAAU,EAAE,CAAC,IAAI,IAAI;aACjE;AACR,SAAK,UAAU;;;EAInB,MAAM,4BAA4B;AAChC,OAAI;AACF,SAAK,uBAAuB,MAAM,KAAK,SAAU,mBAAmB,EAAE,KAAK,KAAK,MAEvE,EAAI,IACX;YACK,GAAG;AACV,YAAQ,MAAM,sCAAsC,KAAK,UAAU,EAAE,GAAG;;;EAI5E,MAAM,SAAS;AAEb,GADA,QAAQ,IAAI,yCAAyC,EACrD,KAAK,UAAU;AAEf,OAAI;AACF,UAAM,QAAQ,IACZ,KAAK,KAAK,IAAI,OAAO,MAAe;AAClC,SAAI,CAAC,KAAK,YAAY,SAAS,EAAW,KAAK,CAC7C,KAAI;AAEF,MADA,MAAM,KAAK,SAAU,aAAa,KAAK,QAAQ,EAAW,KAAK,EAC/D,QAAQ,IAAI,uCAAuC,EAAW,OAAO;cAC9D,GAAO;AACd,cAAQ,MAAM,qBAAqB,EAAW,KAAK,IAAI,EAAM;;MAGlE,CACF;YACM,GAAG;AACV,YAAQ,MAAM,0BAA0B,KAAK,UAAU,EAAE,GAAG;;AAG9D,OAAI;AACF,UAAM,QAAQ,IACZ,KAAK,YAAY,IAAI,OAAO,MAAe;AACzC,SACE,KAAK,KAAK,QAAQ,MACT,EAAI,SAAS,EACpB,CAAC,WAAW,EAEd,KAAI;AAEF,MADA,MAAM,KAAK,SAAU,kBAAkB,KAAK,QAAQ,EAAW,EAC/D,QAAQ,IAAI,yCAAyC,IAAa;cAC3D,GAAO;AACd,cAAQ,MAAM,wBAAwB,EAAW,IAAI,EAAM;;MAGhE,CACF;YACM,GAAG;AACV,YAAQ,MAAM,4BAA4B,KAAK,UAAU,EAAE,GAAG;;AAEhE,QAAK,UAAU;;EAElB;CACF,CAAC"}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import "../_virtual/_rolldown/runtime.mjs";
|
|
2
|
+
import e from "../_virtual/_plugin-vue_export-helper.mjs";
|
|
3
|
+
import t from "./UserTagPreferences.vue_vue_type_script_lang.mjs";
|
|
4
|
+
/* empty css */
|
|
5
|
+
import { Fragment as n, createBlock as r, createCommentVNode as i, createElementBlock as a, createElementVNode as o, createTextVNode as s, createVNode as c, guardReactiveProps as l, normalizeProps as u, openBlock as d, renderList as f, resolveComponent as p, toDisplayString as m, withCtx as h } from "vue";
|
|
6
|
+
//#region src/components/UserTagPreferences.vue
|
|
7
|
+
var g = { key: 1 }, _ = { class: "mb-6" }, v = { class: "text-subtitle-1 font-weight-bold mb-2" }, y = { class: "d-flex align-center ga-3 w-100" }, b = {
|
|
8
|
+
class: "text-body-2 flex-shrink-0",
|
|
9
|
+
style: {
|
|
10
|
+
"min-width": "60px",
|
|
11
|
+
"text-align": "right"
|
|
12
|
+
}
|
|
13
|
+
}, x = {
|
|
14
|
+
key: 2,
|
|
15
|
+
class: "d-flex justify-center py-8"
|
|
16
|
+
};
|
|
17
|
+
function _sfc_render(e, t, S, C, w, T) {
|
|
18
|
+
let E = p("v-icon"), D = p("v-card-title"), O = p("v-card-subtitle"), k = p("v-alert"), A = p("v-list-item"), j = p("v-autocomplete"), M = p("v-chip"), N = p("v-slider"), P = p("v-btn"), F = p("v-list"), I = p("v-progress-circular"), L = p("v-card-text"), R = p("v-spacer"), z = p("v-card-actions"), B = p("v-card");
|
|
19
|
+
return d(), r(B, { loading: e.loading }, {
|
|
20
|
+
default: h(() => [
|
|
21
|
+
c(D, null, {
|
|
22
|
+
default: h(() => [c(E, { start: "" }, {
|
|
23
|
+
default: h(() => t[3] || (t[3] = [s("mdi-tune")])),
|
|
24
|
+
_: 1
|
|
25
|
+
}), t[4] || (t[4] = s(" Learning Preferences "))]),
|
|
26
|
+
_: 1
|
|
27
|
+
}),
|
|
28
|
+
e.courseId ? (d(), r(O, { key: 0 }, {
|
|
29
|
+
default: h(() => t[5] || (t[5] = [s(" Customize how content is presented in this course ")])),
|
|
30
|
+
_: 1
|
|
31
|
+
})) : i("", !0),
|
|
32
|
+
c(L, null, {
|
|
33
|
+
default: h(() => [e.courseId ? e.loading ? (d(), a("div", x, [c(I, {
|
|
34
|
+
indeterminate: "",
|
|
35
|
+
color: "primary"
|
|
36
|
+
})])) : (d(), a("div", g, [
|
|
37
|
+
o("div", _, [
|
|
38
|
+
o("h3", v, [c(E, {
|
|
39
|
+
start: "",
|
|
40
|
+
size: "small"
|
|
41
|
+
}, {
|
|
42
|
+
default: h(() => t[7] || (t[7] = [s("mdi-tune-variant")])),
|
|
43
|
+
_: 1
|
|
44
|
+
}), t[8] || (t[8] = s(" Tag Preferences "))]),
|
|
45
|
+
t[10] || (t[10] = o("p", { class: "text-body-2 text-medium-emphasis mb-3" }, " Adjust how much you want to see cards with specific tags. 0 = exclude, 1 = neutral, higher = prefer more. ", -1)),
|
|
46
|
+
c(j, {
|
|
47
|
+
modelValue: e.tagToAdd,
|
|
48
|
+
"onUpdate:modelValue": [t[0] || (t[0] = (t) => e.tagToAdd = t), e.addTag],
|
|
49
|
+
items: e.availableTagsToAdd,
|
|
50
|
+
"item-title": "name",
|
|
51
|
+
"item-value": "name",
|
|
52
|
+
label: "Add tag preference",
|
|
53
|
+
placeholder: "Search tags...",
|
|
54
|
+
density: "compact",
|
|
55
|
+
variant: "outlined",
|
|
56
|
+
clearable: "",
|
|
57
|
+
"hide-details": "",
|
|
58
|
+
class: "mb-4"
|
|
59
|
+
}, {
|
|
60
|
+
item: h(({ props: e, item: t }) => [c(A, u(l(e)), {
|
|
61
|
+
subtitle: h(() => [s(m(t.raw.snippet), 1)]),
|
|
62
|
+
_: 2
|
|
63
|
+
}, 1040)]),
|
|
64
|
+
_: 1
|
|
65
|
+
}, 8, [
|
|
66
|
+
"modelValue",
|
|
67
|
+
"items",
|
|
68
|
+
"onUpdate:modelValue"
|
|
69
|
+
]),
|
|
70
|
+
e.tagNames.length > 0 ? (d(), r(F, {
|
|
71
|
+
key: 0,
|
|
72
|
+
class: "mt-4"
|
|
73
|
+
}, {
|
|
74
|
+
default: h(() => [(d(!0), a(n, null, f(e.tagNames, (t) => (d(), r(A, {
|
|
75
|
+
key: t,
|
|
76
|
+
class: "px-0"
|
|
77
|
+
}, {
|
|
78
|
+
default: h(() => [o("div", y, [
|
|
79
|
+
c(M, {
|
|
80
|
+
size: "small",
|
|
81
|
+
variant: "tonal",
|
|
82
|
+
class: "flex-shrink-0",
|
|
83
|
+
style: { "min-width": "120px" }
|
|
84
|
+
}, {
|
|
85
|
+
default: h(() => [s(m(t), 1)]),
|
|
86
|
+
_: 2
|
|
87
|
+
}, 1024),
|
|
88
|
+
c(N, {
|
|
89
|
+
"model-value": e.preferences.boost[t],
|
|
90
|
+
min: e.sliderConfigResolved.min,
|
|
91
|
+
max: e.globalSliderMax,
|
|
92
|
+
step: .01,
|
|
93
|
+
"hide-details": "",
|
|
94
|
+
class: "flex-grow-1",
|
|
95
|
+
"thumb-label": "",
|
|
96
|
+
"onUpdate:modelValue": (n) => e.updateBoost(t, n)
|
|
97
|
+
}, {
|
|
98
|
+
"thumb-label": h(({ modelValue: t }) => [s(m(e.formatMultiplier(t)), 1)]),
|
|
99
|
+
_: 2
|
|
100
|
+
}, 1032, [
|
|
101
|
+
"model-value",
|
|
102
|
+
"min",
|
|
103
|
+
"max",
|
|
104
|
+
"onUpdate:modelValue"
|
|
105
|
+
]),
|
|
106
|
+
o("span", b, m(e.formatMultiplier(e.preferences.boost[t])), 1),
|
|
107
|
+
c(P, {
|
|
108
|
+
icon: "mdi-plus",
|
|
109
|
+
size: "small",
|
|
110
|
+
variant: "text",
|
|
111
|
+
density: "compact",
|
|
112
|
+
disabled: e.globalSliderMax >= e.sliderConfigResolved.absoluteMax,
|
|
113
|
+
onClick: (n) => e.expandSliderRange(t)
|
|
114
|
+
}, null, 8, ["disabled", "onClick"]),
|
|
115
|
+
c(P, {
|
|
116
|
+
icon: "mdi-delete",
|
|
117
|
+
size: "small",
|
|
118
|
+
variant: "text",
|
|
119
|
+
density: "compact",
|
|
120
|
+
onClick: (n) => e.removeTag(t)
|
|
121
|
+
}, null, 8, ["onClick"])
|
|
122
|
+
])]),
|
|
123
|
+
_: 2
|
|
124
|
+
}, 1024))), 128))]),
|
|
125
|
+
_: 1
|
|
126
|
+
})) : (d(), r(k, {
|
|
127
|
+
key: 1,
|
|
128
|
+
type: "info",
|
|
129
|
+
variant: "tonal",
|
|
130
|
+
class: "mt-4"
|
|
131
|
+
}, {
|
|
132
|
+
default: h(() => t[9] || (t[9] = [s(" No tag preferences configured yet. Add tags above to get started. ")])),
|
|
133
|
+
_: 1
|
|
134
|
+
}))
|
|
135
|
+
]),
|
|
136
|
+
e.saveError ? (d(), r(k, {
|
|
137
|
+
key: 0,
|
|
138
|
+
type: "error",
|
|
139
|
+
variant: "tonal",
|
|
140
|
+
class: "mt-4",
|
|
141
|
+
closable: "",
|
|
142
|
+
"onClick:close": t[1] || (t[1] = (t) => e.saveError = "")
|
|
143
|
+
}, {
|
|
144
|
+
default: h(() => [s(m(e.saveError), 1)]),
|
|
145
|
+
_: 1
|
|
146
|
+
})) : i("", !0),
|
|
147
|
+
e.saveSuccess ? (d(), r(k, {
|
|
148
|
+
key: 1,
|
|
149
|
+
type: "success",
|
|
150
|
+
variant: "tonal",
|
|
151
|
+
class: "mt-4",
|
|
152
|
+
closable: "",
|
|
153
|
+
"onClick:close": t[2] || (t[2] = (t) => e.saveSuccess = !1)
|
|
154
|
+
}, {
|
|
155
|
+
default: h(() => t[11] || (t[11] = [s(" Preferences saved successfully ")])),
|
|
156
|
+
_: 1
|
|
157
|
+
})) : i("", !0)
|
|
158
|
+
])) : (d(), r(k, {
|
|
159
|
+
key: 0,
|
|
160
|
+
type: "info",
|
|
161
|
+
variant: "tonal",
|
|
162
|
+
class: "mb-4"
|
|
163
|
+
}, {
|
|
164
|
+
default: h(() => t[6] || (t[6] = [s(" Select a course to configure your learning preferences. ")])),
|
|
165
|
+
_: 1
|
|
166
|
+
}))]),
|
|
167
|
+
_: 1
|
|
168
|
+
}),
|
|
169
|
+
e.courseId && !e.loading ? (d(), r(z, { key: 1 }, {
|
|
170
|
+
default: h(() => [
|
|
171
|
+
c(R),
|
|
172
|
+
c(P, {
|
|
173
|
+
variant: "text",
|
|
174
|
+
disabled: !e.hasChanges,
|
|
175
|
+
onClick: e.resetToSaved
|
|
176
|
+
}, {
|
|
177
|
+
default: h(() => t[12] || (t[12] = [s(" Reset ")])),
|
|
178
|
+
_: 1
|
|
179
|
+
}, 8, ["disabled", "onClick"]),
|
|
180
|
+
c(P, {
|
|
181
|
+
color: "primary",
|
|
182
|
+
variant: "flat",
|
|
183
|
+
loading: e.saving,
|
|
184
|
+
disabled: !e.hasChanges,
|
|
185
|
+
onClick: e.savePreferences
|
|
186
|
+
}, {
|
|
187
|
+
default: h(() => t[13] || (t[13] = [s(" Save Preferences ")])),
|
|
188
|
+
_: 1
|
|
189
|
+
}, 8, [
|
|
190
|
+
"loading",
|
|
191
|
+
"disabled",
|
|
192
|
+
"onClick"
|
|
193
|
+
])
|
|
194
|
+
]),
|
|
195
|
+
_: 1
|
|
196
|
+
})) : i("", !0)
|
|
197
|
+
]),
|
|
198
|
+
_: 1
|
|
199
|
+
}, 8, ["loading"]);
|
|
200
|
+
}
|
|
201
|
+
var S = /* @__PURE__ */ e(t, [["render", _sfc_render], ["__scopeId", "data-v-8fb14605"]]);
|
|
202
|
+
//#endregion
|
|
203
|
+
export { S as default };
|
|
204
|
+
|
|
205
|
+
//# sourceMappingURL=UserTagPreferences.mjs.map
|