From 572526d9a214a312c8fe79fa1c05c7630a96e8fe Mon Sep 17 00:00:00 2001 From: Alex Velez Date: Tue, 28 Jan 2025 13:22:35 -0500 Subject: [PATCH 01/15] Reorganize files structure --- .../coach/assets/src/constants/index.js | 7 +- .../coach/assets/src/routes/examRoutes.js | 92 ++++-- .../views/quizzes/CreateExamPage/index.vue | 4 +- .../QuizResourceSelection/_index.vue | 261 ++++++++++++++++++ .../QuizResourceSelection/index.vue} | 16 +- .../SectionSidePanel}/ReplaceQuestions.vue | 6 +- .../SectionSidePanel}/SectionEditor.vue | 6 +- .../SectionSidePanel}/SectionOrder.vue | 8 +- .../SectionSidePanel/index.vue} | 2 +- 9 files changed, 351 insertions(+), 51 deletions(-) create mode 100644 kolibri/plugins/coach/assets/src/views/quizzes/CreateExamPage/sidePanels/QuizResourceSelection/_index.vue rename kolibri/plugins/coach/assets/src/views/quizzes/CreateExamPage/{ResourceSelection.vue => sidePanels/QuizResourceSelection/index.vue} (97%) rename kolibri/plugins/coach/assets/src/views/quizzes/CreateExamPage/{ => sidePanels/SectionSidePanel}/ReplaceQuestions.vue (98%) rename kolibri/plugins/coach/assets/src/views/quizzes/CreateExamPage/{ => sidePanels/SectionSidePanel}/SectionEditor.vue (98%) rename kolibri/plugins/coach/assets/src/views/quizzes/CreateExamPage/{ => sidePanels/SectionSidePanel}/SectionOrder.vue (97%) rename kolibri/plugins/coach/assets/src/views/quizzes/CreateExamPage/{SectionSidePanel.vue => sidePanels/SectionSidePanel/index.vue} (97%) diff --git a/kolibri/plugins/coach/assets/src/constants/index.js b/kolibri/plugins/coach/assets/src/constants/index.js index 03a55ab7d3e..0201c627436 100644 --- a/kolibri/plugins/coach/assets/src/constants/index.js +++ b/kolibri/plugins/coach/assets/src/constants/index.js @@ -11,12 +11,17 @@ export const PageNames = { QUIZ_LEARNER_REPORT: 'QUIZ_LEARNER_REPORT', QUIZ_SECTION_EDITOR: 'QUIZ_SECTION_EDITOR', QUIZ_REPLACE_QUESTIONS: 'QUIZ_REPLACE_QUESTIONS', - QUIZ_SELECT_RESOURCES: 'QUIZ_SELECT_RESOURCES', + QUIZ_SELECT_RESOURCES_OLD: 'QUIZ_SELECT_RESOURCES_OLD', QUIZ_SELECT_PRACTICE_QUIZ: 'QUIZ_SELECT_PRACTICE_QUIZ', QUIZ_SECTION_ORDER: 'QUIZ_SECTION_ORDER', QUIZ_QUESTION_PAGE_ROOT: 'QUIZ_QUESTION_PAGE_ROOT', QUIZ_QUESTION_REPORT: 'QUIZ_QUESTION_REPORT', QUIZ_BOOK_MARKED_RESOURCES: 'QUIZ_BOOK_MARKED_RESOURCES', + QUIZ_SECTION_SIDE_PANEL: 'QUIZ_SECTION_SIDE_PANEL', + QUIZ_SELECT_RESOURCES: 'QUIZ_SELECT_RESOURCES', + QUIZ_SELECT_RESOURCES_INDEX: 'QUIZ_SELECT_RESOURCES_INDEX', + QUIZ_SELECT_RESOURCES_BOOKMARKS: 'QUIZ_SELECT_RESOURCES_BOOKMARKS', + QUIZ_SELECT_RESOURCES_TOPIC_TREE: 'QUIZ_SELECT_RESOURCES_TOPIC_TREE', /* Lessons */ LESSONS_ROOT: 'LESSONS_ROOT', diff --git a/kolibri/plugins/coach/assets/src/routes/examRoutes.js b/kolibri/plugins/coach/assets/src/routes/examRoutes.js index ec43607cf6f..c5d8429ea75 100644 --- a/kolibri/plugins/coach/assets/src/routes/examRoutes.js +++ b/kolibri/plugins/coach/assets/src/routes/examRoutes.js @@ -1,12 +1,14 @@ import store from 'kolibri/store'; import { PageNames } from '../constants'; import CreateExamPage from '../views/quizzes/CreateExamPage'; -import SectionEditor from '../views/quizzes/CreateExamPage/SectionEditor.vue'; -import ResourceSelection from '../views/quizzes/CreateExamPage/ResourceSelection.vue'; -import ReplaceQuestions from '../views/quizzes/CreateExamPage/ReplaceQuestions.vue'; +import SectionEditor from '../views/quizzes/CreateExamPage/sidePanels/SectionSidePanel/SectionEditor.vue'; +import ResourceSelection from '../views/quizzes/CreateExamPage/sidePanels/QuizResourceSelection/index.vue'; +import ReplaceQuestions from '../views/quizzes/CreateExamPage/sidePanels/SectionSidePanel/ReplaceQuestions.vue'; import ExamsRootPage from '../views/quizzes/ExamsRootPage'; import QuizSummaryPage from '../views/quizzes/QuizSummaryPage'; -import SectionOrder from '../views/quizzes/CreateExamPage/SectionOrder'; +import SectionOrder from '../views/quizzes/CreateExamPage/sidePanels/SectionSidePanel/SectionOrder.vue'; +import SectionSidePanel from '../views/quizzes/CreateExamPage/sidePanels/SectionSidePanel/index.vue'; +import QuizResourceSelection from '../views/quizzes/CreateExamPage/sidePanels/QuizResourceSelection/_index.vue'; import LearnerQuizPage from '../views/common/reports/LearnerQuizPage.vue'; import QuizPreviewPage from '../views/quizzes/reports/QuizPreviewPage.vue'; import { generateExamReportDetailHandler } from '../modules/examReportDetail/handlers'; @@ -52,32 +54,66 @@ export default [ }, children: [ { - name: PageNames.QUIZ_SECTION_EDITOR, - path: 'edit', - component: SectionEditor, + name: PageNames.QUIZ_SECTION_SIDE_PANEL, + path: 'details', + component: SectionSidePanel, + children: [ + { + name: PageNames.QUIZ_SECTION_EDITOR, + path: 'edit', + component: SectionEditor, + }, + { + name: PageNames.QUIZ_REPLACE_QUESTIONS, + path: 'replace-questions', + component: ReplaceQuestions, + }, + { + name: PageNames.QUIZ_SECTION_ORDER, + path: 'section-order', + component: SectionOrder, + }, + { + name: PageNames.QUIZ_SELECT_RESOURCES, + path: 'select-resources/:topic_id?', + component: ResourceSelection, + }, + { + name: PageNames.QUIZ_SELECT_PRACTICE_QUIZ, + path: 'select-quiz/:topic_id?', + component: ResourceSelection, + props: { + selectPracticeQuiz: true, + }, + }, + ], }, { - name: PageNames.QUIZ_REPLACE_QUESTIONS, - path: 'replace-questions', - component: ReplaceQuestions, - }, - { - name: PageNames.QUIZ_SELECT_RESOURCES, - path: 'select-resources/:topic_id?', - component: ResourceSelection, - }, - { - name: PageNames.QUIZ_SECTION_ORDER, - path: 'section-order', - component: SectionOrder, - }, - { - name: PageNames.QUIZ_SELECT_PRACTICE_QUIZ, - path: 'select-quiz/:topic_id?', - component: ResourceSelection, - props: { - selectPracticeQuiz: true, - }, + name: PageNames.QUIZ_SELECT_RESOURCES_OLD, + path: 'select-resources/', + component: QuizResourceSelection, + children: [ + // { + // name: PageNames.LESSON_SELECT_RESOURCES_INDEX, + // path: 'index', + // component: SelectionIndex, + // }, + // { + // name: PageNames.LESSON_SELECT_RESOURCES_BOOKMARKS, + // path: 'bookmarks', + // component: SelectFromBookmarks, + // }, + // { + // name: PageNames.LESSON_SELECT_RESOURCES_TOPIC_TREE, + // path: 'channels', + // component: SelectFromChannels, + // }, + // { + // name: PageNames.LESSON_PREVIEW_SELECTED_RESOURCES, + // path: 'preview-resources', + // component: ManageSelectedResources, + // }, + ], }, ], }, diff --git a/kolibri/plugins/coach/assets/src/views/quizzes/CreateExamPage/index.vue b/kolibri/plugins/coach/assets/src/views/quizzes/CreateExamPage/index.vue index ca1acd5343d..c57b25088c9 100644 --- a/kolibri/plugins/coach/assets/src/views/quizzes/CreateExamPage/index.vue +++ b/kolibri/plugins/coach/assets/src/views/quizzes/CreateExamPage/index.vue @@ -105,7 +105,7 @@ {{ closeConfirmationMessage$() }} - + @@ -129,7 +129,6 @@ import AssignmentDetailsModal from '../../common/assignments/AssignmentDetailsModal'; import useCoreCoach from '../../../composables/useCoreCoach'; import CreateQuizSection from './CreateQuizSection'; - import SectionSidePanel from './SectionSidePanel'; export default { name: 'CreateExamPage', @@ -138,7 +137,6 @@ BottomAppBar, CreateQuizSection, AssignmentDetailsModal, - SectionSidePanel, }, mixins: [commonCoreStrings], setup() { diff --git a/kolibri/plugins/coach/assets/src/views/quizzes/CreateExamPage/sidePanels/QuizResourceSelection/_index.vue b/kolibri/plugins/coach/assets/src/views/quizzes/CreateExamPage/sidePanels/QuizResourceSelection/_index.vue new file mode 100644 index 00000000000..ca2da01c8ea --- /dev/null +++ b/kolibri/plugins/coach/assets/src/views/quizzes/CreateExamPage/sidePanels/QuizResourceSelection/_index.vue @@ -0,0 +1,261 @@ + + + + + + + diff --git a/kolibri/plugins/coach/assets/src/views/quizzes/CreateExamPage/ResourceSelection.vue b/kolibri/plugins/coach/assets/src/views/quizzes/CreateExamPage/sidePanels/QuizResourceSelection/index.vue similarity index 97% rename from kolibri/plugins/coach/assets/src/views/quizzes/CreateExamPage/ResourceSelection.vue rename to kolibri/plugins/coach/assets/src/views/quizzes/CreateExamPage/sidePanels/QuizResourceSelection/index.vue index b9d284a8bbb..ae8ced90b1a 100644 --- a/kolibri/plugins/coach/assets/src/views/quizzes/CreateExamPage/ResourceSelection.vue +++ b/kolibri/plugins/coach/assets/src/views/quizzes/CreateExamPage/sidePanels/QuizResourceSelection/index.vue @@ -215,14 +215,14 @@ import useKResponsiveWindow from 'kolibri-design-system/lib/composables/useKResponsiveWindow'; import useBaseSearch from 'kolibri-common/composables/useBaseSearch'; import SearchFiltersPanel from 'kolibri-common/components/SearchFiltersPanel'; - import { coachStrings } from '../../common/commonCoachStrings'; - import { exerciseToQuestionArray } from '../../../utils/selectQuestions'; - import { PageNames, ViewMoreButtonStates } from '../../../constants/index'; - import BookmarkIcon from '../../lessons/LessonResourceSelectionPage/LessonContentCard/BookmarkIcon.vue'; - import useQuizResources from '../../../composables/useQuizResources'; - import { injectQuizCreation } from '../../../composables/useQuizCreation'; - import ContentCardList from '../../lessons/LessonResourceSelectionPage/ContentCardList.vue'; - import ResourceSelectionBreadcrumbs from '../../lessons/LessonResourceSelectionPage/SearchTools/ResourceSelectionBreadcrumbs.vue'; + import { coachStrings } from '../../../../common/commonCoachStrings'; + import { exerciseToQuestionArray } from '../../../../../utils/selectQuestions'; + import { PageNames, ViewMoreButtonStates } from '../../../../../constants/index'; + import BookmarkIcon from '../../../../lessons/LessonResourceSelectionPage/LessonContentCard/BookmarkIcon.vue'; + import useQuizResources from '../../../../../composables/useQuizResources'; + import { injectQuizCreation } from '../../../../../composables/useQuizCreation'; + import ContentCardList from '../../../../lessons/LessonResourceSelectionPage/ContentCardList.vue'; + import ResourceSelectionBreadcrumbs from '../../../../lessons/LessonResourceSelectionPage/SearchTools/ResourceSelectionBreadcrumbs.vue'; export default { name: 'ResourceSelection', diff --git a/kolibri/plugins/coach/assets/src/views/quizzes/CreateExamPage/ReplaceQuestions.vue b/kolibri/plugins/coach/assets/src/views/quizzes/CreateExamPage/sidePanels/SectionSidePanel/ReplaceQuestions.vue similarity index 98% rename from kolibri/plugins/coach/assets/src/views/quizzes/CreateExamPage/ReplaceQuestions.vue rename to kolibri/plugins/coach/assets/src/views/quizzes/CreateExamPage/sidePanels/SectionSidePanel/ReplaceQuestions.vue index f80db6fdcb1..d62d99611d7 100644 --- a/kolibri/plugins/coach/assets/src/views/quizzes/CreateExamPage/ReplaceQuestions.vue +++ b/kolibri/plugins/coach/assets/src/views/quizzes/CreateExamPage/sidePanels/SectionSidePanel/ReplaceQuestions.vue @@ -157,9 +157,9 @@ import AccordionContainer from 'kolibri-common/components/AccordionContainer'; import useAccordion from 'kolibri-common/components/useAccordion'; import useSnackbar from 'kolibri/composables/useSnackbar'; - import { injectQuizCreation } from '../../../composables/useQuizCreation'; - import { coachStrings } from '../../common/commonCoachStrings'; - import { PageNames } from '../../../constants/index'; + import { injectQuizCreation } from '../../../../../composables/useQuizCreation'; + import { coachStrings } from '../../../../common/commonCoachStrings'; + import { PageNames } from '../../../../../constants/index'; export default { name: 'ReplaceQuestions', diff --git a/kolibri/plugins/coach/assets/src/views/quizzes/CreateExamPage/SectionEditor.vue b/kolibri/plugins/coach/assets/src/views/quizzes/CreateExamPage/sidePanels/SectionSidePanel/SectionEditor.vue similarity index 98% rename from kolibri/plugins/coach/assets/src/views/quizzes/CreateExamPage/SectionEditor.vue rename to kolibri/plugins/coach/assets/src/views/quizzes/CreateExamPage/sidePanels/SectionSidePanel/SectionEditor.vue index b253ef0fd25..7b3fdb64a47 100644 --- a/kolibri/plugins/coach/assets/src/views/quizzes/CreateExamPage/SectionEditor.vue +++ b/kolibri/plugins/coach/assets/src/views/quizzes/CreateExamPage/sidePanels/SectionSidePanel/SectionEditor.vue @@ -135,9 +135,9 @@ import commonCoreStrings from 'kolibri/uiText/commonCoreStrings'; import { MAX_QUESTIONS_PER_QUIZ_SECTION } from 'kolibri/constants'; import useSnackbar from 'kolibri/composables/useSnackbar'; - import { PageNames } from '../../../constants/index'; - import { coachStrings } from '../../common/commonCoachStrings.js'; - import { injectQuizCreation } from '../../../composables/useQuizCreation.js'; + import { PageNames } from '../../../../../constants/index'; + import { coachStrings } from '../../../../common/commonCoachStrings.js'; + import { injectQuizCreation } from '../../../../../composables/useQuizCreation.js'; export default { name: 'SectionEditor', diff --git a/kolibri/plugins/coach/assets/src/views/quizzes/CreateExamPage/SectionOrder.vue b/kolibri/plugins/coach/assets/src/views/quizzes/CreateExamPage/sidePanels/SectionSidePanel/SectionOrder.vue similarity index 97% rename from kolibri/plugins/coach/assets/src/views/quizzes/CreateExamPage/SectionOrder.vue rename to kolibri/plugins/coach/assets/src/views/quizzes/CreateExamPage/sidePanels/SectionSidePanel/SectionOrder.vue index 9c1637efeb4..863265785ca 100644 --- a/kolibri/plugins/coach/assets/src/views/quizzes/CreateExamPage/SectionOrder.vue +++ b/kolibri/plugins/coach/assets/src/views/quizzes/CreateExamPage/sidePanels/SectionSidePanel/SectionOrder.vue @@ -98,10 +98,10 @@ import DragHandle from 'kolibri-common/components/sortable/DragHandle'; import DragSortWidget from 'kolibri-common/components/sortable/DragSortWidget'; import { searchAndFilterStrings } from 'kolibri-common/strings/searchAndFilterStrings'; - import { PageNames } from '../../../constants/index'; - import { coachStrings } from '../../common/commonCoachStrings.js'; - import { injectQuizCreation } from '../../../composables/useQuizCreation'; - import useDrag from '../../common/useDrag'; + import { PageNames } from '../../../../../constants/index'; + import { coachStrings } from '../../../../common/commonCoachStrings.js'; + import { injectQuizCreation } from '../../../../../composables/useQuizCreation.js'; + import useDrag from '../../../../common/useDrag.js'; export default { name: 'SectionOrder', diff --git a/kolibri/plugins/coach/assets/src/views/quizzes/CreateExamPage/SectionSidePanel.vue b/kolibri/plugins/coach/assets/src/views/quizzes/CreateExamPage/sidePanels/SectionSidePanel/index.vue similarity index 97% rename from kolibri/plugins/coach/assets/src/views/quizzes/CreateExamPage/SectionSidePanel.vue rename to kolibri/plugins/coach/assets/src/views/quizzes/CreateExamPage/sidePanels/SectionSidePanel/index.vue index 00511270c22..c7e634b1b21 100644 --- a/kolibri/plugins/coach/assets/src/views/quizzes/CreateExamPage/SectionSidePanel.vue +++ b/kolibri/plugins/coach/assets/src/views/quizzes/CreateExamPage/sidePanels/SectionSidePanel/index.vue @@ -26,7 +26,7 @@ import SidePanelModal from 'kolibri-common/components/SidePanelModal'; import { ref, watch, computed, getCurrentInstance } from 'vue'; - import { PageNames } from '../../../constants'; + import { PageNames } from '../../../../../constants'; export default { name: 'SectionSidePanel', From 88ec01e37971bbd5c226540ce670678c1b5fc989 Mon Sep 17 00:00:00 2001 From: Alex Velez Date: Tue, 28 Jan 2025 16:09:05 -0500 Subject: [PATCH 02/15] Add questions settings subpage --- .../subPages/QuestionsSettings.vue | 161 ++++++++++++++++++ 1 file changed, 161 insertions(+) create mode 100644 kolibri/plugins/coach/assets/src/views/quizzes/CreateExamPage/sidePanels/QuizResourceSelection/subPages/QuestionsSettings.vue diff --git a/kolibri/plugins/coach/assets/src/views/quizzes/CreateExamPage/sidePanels/QuizResourceSelection/subPages/QuestionsSettings.vue b/kolibri/plugins/coach/assets/src/views/quizzes/CreateExamPage/sidePanels/QuizResourceSelection/subPages/QuestionsSettings.vue new file mode 100644 index 00000000000..e7484686cb5 --- /dev/null +++ b/kolibri/plugins/coach/assets/src/views/quizzes/CreateExamPage/sidePanels/QuizResourceSelection/subPages/QuestionsSettings.vue @@ -0,0 +1,161 @@ + + + + + + + From b5073383cb6cccf712b43331e4a57778635bfbf3 Mon Sep 17 00:00:00 2001 From: Alex Velez Date: Tue, 28 Jan 2025 23:45:01 -0500 Subject: [PATCH 03/15] Add Index, Channels and Bookmarks subpages --- kolibri/plugins/coach/assets/src/app.js | 4 ++ .../coach/assets/src/constants/index.js | 1 + .../coach/assets/src/routes/examRoutes.js | 56 ++++++++++----- .../coach/assets/src/routes/lessonsRoutes.js | 7 +- .../UpdatedResourceSelection.vue | 12 ++-- .../common/resourceSelection/contants.js | 4 ++ .../subPages/SelectFromBookmarks.vue | 36 ++++++++-- .../subPages/SelectFromChannels.vue | 71 +++++++++++++------ .../subPages/SelectionIndex.vue | 48 +++++++++++-- .../LessonResourceSelection/index.vue | 3 + .../strings/enhancedQuizManagementStrings.js | 14 ++++ 11 files changed, 197 insertions(+), 59 deletions(-) rename kolibri/plugins/coach/assets/src/views/{lessons/LessonSummaryPage => common/resourceSelection}/UpdatedResourceSelection.vue (96%) create mode 100644 kolibri/plugins/coach/assets/src/views/common/resourceSelection/contants.js rename kolibri/plugins/coach/assets/src/views/{lessons/LessonSummaryPage/sidePanels/LessonResourceSelection => common/resourceSelection}/subPages/SelectFromBookmarks.vue (66%) rename kolibri/plugins/coach/assets/src/views/{lessons/LessonSummaryPage/sidePanels/LessonResourceSelection => common/resourceSelection}/subPages/SelectFromChannels.vue (62%) rename kolibri/plugins/coach/assets/src/views/{lessons/LessonSummaryPage/sidePanels/LessonResourceSelection => common/resourceSelection}/subPages/SelectionIndex.vue (71%) diff --git a/kolibri/plugins/coach/assets/src/app.js b/kolibri/plugins/coach/assets/src/app.js index 3b790935915..01905bade8e 100644 --- a/kolibri/plugins/coach/assets/src/app.js +++ b/kolibri/plugins/coach/assets/src/app.js @@ -62,6 +62,10 @@ class CoachToolsModule extends KolibriApp { PageNames.QUIZ_REPLACE_QUESTIONS, PageNames.QUIZ_SELECT_PRACTICE_QUIZ, PageNames.QUIZ_SELECT_RESOURCES, + PageNames.QUIZ_SELECT_RESOURCES_LANDING, + PageNames.QUIZ_SELECT_RESOURCES_INDEX, + PageNames.QUIZ_SELECT_RESOURCES_BOOKMARKS, + PageNames.QUIZ_SELECT_RESOURCES_TOPIC_TREE, PageNames.QUIZ_SECTION_ORDER, PageNames.QUIZ_BOOK_MARKED_RESOURCES, PageNames.QUIZ_LEARNER_REPORT, diff --git a/kolibri/plugins/coach/assets/src/constants/index.js b/kolibri/plugins/coach/assets/src/constants/index.js index 0201c627436..1d06cc96be5 100644 --- a/kolibri/plugins/coach/assets/src/constants/index.js +++ b/kolibri/plugins/coach/assets/src/constants/index.js @@ -19,6 +19,7 @@ export const PageNames = { QUIZ_BOOK_MARKED_RESOURCES: 'QUIZ_BOOK_MARKED_RESOURCES', QUIZ_SECTION_SIDE_PANEL: 'QUIZ_SECTION_SIDE_PANEL', QUIZ_SELECT_RESOURCES: 'QUIZ_SELECT_RESOURCES', + QUIZ_SELECT_RESOURCES_LANDING: 'QUIZ_SELECT_RESOURCES_LANDING', QUIZ_SELECT_RESOURCES_INDEX: 'QUIZ_SELECT_RESOURCES_INDEX', QUIZ_SELECT_RESOURCES_BOOKMARKS: 'QUIZ_SELECT_RESOURCES_BOOKMARKS', QUIZ_SELECT_RESOURCES_TOPIC_TREE: 'QUIZ_SELECT_RESOURCES_TOPIC_TREE', diff --git a/kolibri/plugins/coach/assets/src/routes/examRoutes.js b/kolibri/plugins/coach/assets/src/routes/examRoutes.js index c5d8429ea75..0b7a609c548 100644 --- a/kolibri/plugins/coach/assets/src/routes/examRoutes.js +++ b/kolibri/plugins/coach/assets/src/routes/examRoutes.js @@ -2,21 +2,24 @@ import store from 'kolibri/store'; import { PageNames } from '../constants'; import CreateExamPage from '../views/quizzes/CreateExamPage'; import SectionEditor from '../views/quizzes/CreateExamPage/sidePanels/SectionSidePanel/SectionEditor.vue'; -import ResourceSelection from '../views/quizzes/CreateExamPage/sidePanels/QuizResourceSelection/index.vue'; import ReplaceQuestions from '../views/quizzes/CreateExamPage/sidePanels/SectionSidePanel/ReplaceQuestions.vue'; import ExamsRootPage from '../views/quizzes/ExamsRootPage'; import QuizSummaryPage from '../views/quizzes/QuizSummaryPage'; import SectionOrder from '../views/quizzes/CreateExamPage/sidePanels/SectionSidePanel/SectionOrder.vue'; import SectionSidePanel from '../views/quizzes/CreateExamPage/sidePanels/SectionSidePanel/index.vue'; -import QuizResourceSelection from '../views/quizzes/CreateExamPage/sidePanels/QuizResourceSelection/_index.vue'; +import QuizResourceSelection from '../views/quizzes/CreateExamPage/sidePanels/QuizResourceSelection/index.vue'; import LearnerQuizPage from '../views/common/reports/LearnerQuizPage.vue'; import QuizPreviewPage from '../views/quizzes/reports/QuizPreviewPage.vue'; import { generateExamReportDetailHandler } from '../modules/examReportDetail/handlers'; import QuestionLearnersPage from '../views/common/reports/QuestionLearnersPage.vue'; +import SelectionIndex from '../views/common/resourceSelection/subPages/SelectionIndex.vue'; +import SelectFromChannels from '../views/common/resourceSelection/subPages/SelectFromChannels.vue'; +import SelectFromBookmarks from '../views/common/resourceSelection/subPages/SelectFromBookmarks.vue'; import { generateQuestionDetailHandler, questionRootRedirectHandler, } from '../modules/questionDetail/handlers'; +import QuestionsSettings from '../views/quizzes/CreateExamPage/sidePanels/QuizResourceSelection/subPages/QuestionsSettings.vue'; import { classIdParamRequiredGuard, RouteSegments } from './utils'; const { @@ -73,26 +76,47 @@ export default [ path: 'section-order', component: SectionOrder, }, - { - name: PageNames.QUIZ_SELECT_RESOURCES, - path: 'select-resources/:topic_id?', - component: ResourceSelection, - }, - { - name: PageNames.QUIZ_SELECT_PRACTICE_QUIZ, - path: 'select-quiz/:topic_id?', - component: ResourceSelection, - props: { - selectPracticeQuiz: true, - }, - }, + // { + // name: PageNames.QUIZ_SELECT_RESOURCES, + // path: 'select-resources/:topic_id?', + // component: ResourceSelection, + // }, + // { + // name: PageNames.QUIZ_SELECT_PRACTICE_QUIZ, + // path: 'select-quiz/:topic_id?', + // component: ResourceSelection, + // props: { + // selectPracticeQuiz: true, + // }, + // }, ], }, { - name: PageNames.QUIZ_SELECT_RESOURCES_OLD, + name: PageNames.QUIZ_SELECT_RESOURCES, path: 'select-resources/', component: QuizResourceSelection, + redirect: 'select-resources/landing', children: [ + { + name: PageNames.QUIZ_SELECT_RESOURCES_LANDING, + path: 'landing', + component: QuestionsSettings, + }, + { + name: PageNames.QUIZ_SELECT_RESOURCES_INDEX, + path: 'index', + component: SelectionIndex, + }, + { + name: PageNames.QUIZ_SELECT_RESOURCES_BOOKMARKS, + path: 'bookmarks', + component: SelectFromBookmarks, + }, + { + name: PageNames.QUIZ_SELECT_RESOURCES_TOPIC_TREE, + path: 'channels', + component: SelectFromChannels, + }, // { // name: PageNames.LESSON_SELECT_RESOURCES_INDEX, // path: 'index', diff --git a/kolibri/plugins/coach/assets/src/routes/lessonsRoutes.js b/kolibri/plugins/coach/assets/src/routes/lessonsRoutes.js index debb48d68d6..3943251dc49 100644 --- a/kolibri/plugins/coach/assets/src/routes/lessonsRoutes.js +++ b/kolibri/plugins/coach/assets/src/routes/lessonsRoutes.js @@ -34,16 +34,15 @@ import { generateQuestionDetailHandler, questionRootRedirectHandler, } from '../modules/questionDetail/handlers'; +import SelectionIndex from '../views/common/resourceSelection/subPages/SelectionIndex.vue'; import LessonLearnerExercisePage from '../views/lessons/reports/LessonLearnerExercisePage.vue'; import QuestionLearnersPage from '../views/common/reports/QuestionLearnersPage.vue'; import EditLessonDetails from '../views/lessons/LessonSummaryPage/sidePanels/EditLessonDetails'; import PreviewSelectedResources from '../views/lessons/LessonSummaryPage/sidePanels/PreviewSelectedResources'; import LessonResourceSelection from '../views/lessons/LessonSummaryPage/sidePanels/LessonResourceSelection'; -import SelectionIndex from '../views/lessons/LessonSummaryPage/sidePanels/LessonResourceSelection/subPages/SelectionIndex.vue'; -import SelectFromBookmarks from '../views/lessons/LessonSummaryPage/sidePanels/LessonResourceSelection/subPages/SelectFromBookmarks.vue'; -import SelectFromChannels from '../views/lessons/LessonSummaryPage/sidePanels/LessonResourceSelection/subPages/SelectFromChannels.vue'; +import SelectFromBookmarks from '../views/common/resourceSelection/subPages/SelectFromBookmarks.vue'; +import SelectFromChannels from '../views/common/resourceSelection/subPages/SelectFromChannels.vue'; import ManageSelectedResources from '../views/lessons/LessonSummaryPage/sidePanels/LessonResourceSelection/subPages/ManageSelectedResources.vue'; - import { classIdParamRequiredGuard, RouteSegments } from './utils'; const { diff --git a/kolibri/plugins/coach/assets/src/views/lessons/LessonSummaryPage/UpdatedResourceSelection.vue b/kolibri/plugins/coach/assets/src/views/common/resourceSelection/UpdatedResourceSelection.vue similarity index 96% rename from kolibri/plugins/coach/assets/src/views/lessons/LessonSummaryPage/UpdatedResourceSelection.vue rename to kolibri/plugins/coach/assets/src/views/common/resourceSelection/UpdatedResourceSelection.vue index 79619501c06..92dfaaad6a6 100644 --- a/kolibri/plugins/coach/assets/src/views/lessons/LessonSummaryPage/UpdatedResourceSelection.vue +++ b/kolibri/plugins/coach/assets/src/views/common/resourceSelection/UpdatedResourceSelection.vue @@ -36,7 +36,7 @@ import { ContentNodeKinds } from 'kolibri/constants'; import ContentCardList from '../../lessons/LessonResourceSelectionPage/ContentCardList.vue'; import ResourceSelectionBreadcrumbs from '../../lessons/LessonResourceSelectionPage/SearchTools/ResourceSelectionBreadcrumbs.vue'; - import { PageNames, ViewMoreButtonStates } from '../../../constants'; + import { ViewMoreButtonStates } from '../../../constants'; export default { name: 'UpdatedResourceSelection', @@ -90,17 +90,17 @@ required: false, default: null, }, + channelsLink: { + type: Object, + required: false, + default: null, + }, disabled: { type: Boolean, default: false, }, }, computed: { - channelsLink() { - return { - name: PageNames.LESSON_SELECT_RESOURCES_INDEX, - }; - }, selectAllIndeterminate() { return ( !this.selectAllChecked && diff --git a/kolibri/plugins/coach/assets/src/views/common/resourceSelection/contants.js b/kolibri/plugins/coach/assets/src/views/common/resourceSelection/contants.js new file mode 100644 index 00000000000..2c635f07eff --- /dev/null +++ b/kolibri/plugins/coach/assets/src/views/common/resourceSelection/contants.js @@ -0,0 +1,4 @@ +export const SelectionTarget = { + QUIZ: 'quiz', + LESSON: 'lesson', +}; diff --git a/kolibri/plugins/coach/assets/src/views/lessons/LessonSummaryPage/sidePanels/LessonResourceSelection/subPages/SelectFromBookmarks.vue b/kolibri/plugins/coach/assets/src/views/common/resourceSelection/subPages/SelectFromBookmarks.vue similarity index 66% rename from kolibri/plugins/coach/assets/src/views/lessons/LessonSummaryPage/sidePanels/LessonResourceSelection/subPages/SelectFromBookmarks.vue rename to kolibri/plugins/coach/assets/src/views/common/resourceSelection/subPages/SelectFromBookmarks.vue index 5459c050b1f..f872bcc0e7a 100644 --- a/kolibri/plugins/coach/assets/src/views/lessons/LessonSummaryPage/sidePanels/LessonResourceSelection/subPages/SelectFromBookmarks.vue +++ b/kolibri/plugins/coach/assets/src/views/common/resourceSelection/subPages/SelectFromBookmarks.vue @@ -6,6 +6,7 @@ :contentList="contentList" :hasMore="hasMore" :disabled="disabled" + :channelsLink="channelsLink" :fetchMore="fetchMore" :loadingMore="loadingMore" :selectionRules="selectionRules" @@ -23,11 +24,12 @@ import { getCurrentInstance } from 'vue'; import { coreStrings } from 'kolibri/uiText/commonCoreStrings'; - import UpdatedResourceSelection from '../../../UpdatedResourceSelection.vue'; - import { PageNames } from '../../../../../../constants'; + import UpdatedResourceSelection from '../UpdatedResourceSelection.vue'; + import { PageNames } from '../../../../constants'; + import { SelectionTarget } from '../contants'; /** - * @typedef {import('../../../../../../composables/useFetch').FetchObject} FetchObject + * @typedef {import('../../../../composables/useFetch').FetchObject} FetchObject */ export default { @@ -40,15 +42,27 @@ const instance = getCurrentInstance(); props.setTitle(selectFromBookmarks$()); - props.setGoBack(() => { + + const redirectBack = () => { instance.proxy.$router.push({ - name: PageNames.LESSON_SELECT_RESOURCES_INDEX, + name: + props.target === SelectionTarget.LESSON + ? PageNames.LESSON_SELECT_RESOURCES_INDEX + : PageNames.QUIZ_SELECT_RESOURCES_INDEX, }); - }); + }; + props.setGoBack(redirectBack); - const { data, hasMore, fetchMore, loadingMore } = props.bookmarksFetch; + const channelsLink = { + name: + props.target === SelectionTarget.LESSON + ? PageNames.LESSON_SELECT_RESOURCES_INDEX + : PageNames.QUIZ_SELECT_RESOURCES_INDEX, + }; + const { data, hasMore, fetchMore, loadingMore } = props.bookmarksFetch; return { + channelsLink, contentList: data, hasMore, fetchMore, @@ -90,6 +104,14 @@ type: Boolean, default: false, }, + /** + * The target entity for the selection. + * It can be either 'quiz' or 'lesson'. + */ + target: { + type: String, + required: true, + }, }, }; diff --git a/kolibri/plugins/coach/assets/src/views/lessons/LessonSummaryPage/sidePanels/LessonResourceSelection/subPages/SelectFromChannels.vue b/kolibri/plugins/coach/assets/src/views/common/resourceSelection/subPages/SelectFromChannels.vue similarity index 62% rename from kolibri/plugins/coach/assets/src/views/lessons/LessonSummaryPage/sidePanels/LessonResourceSelection/subPages/SelectFromChannels.vue rename to kolibri/plugins/coach/assets/src/views/common/resourceSelection/subPages/SelectFromChannels.vue index 65e9f78e117..a2cf46942dd 100644 --- a/kolibri/plugins/coach/assets/src/views/lessons/LessonSummaryPage/sidePanels/LessonResourceSelection/subPages/SelectFromChannels.vue +++ b/kolibri/plugins/coach/assets/src/views/common/resourceSelection/subPages/SelectFromChannels.vue @@ -30,6 +30,7 @@ canSelectAll :topic="topic" :disabled="disabled" + :channelsLink="channelsLink" :contentList="contentList" :hasMore="hasMore" :fetchMore="fetchMore" @@ -49,12 +50,14 @@ import { getCurrentInstance } from 'vue'; import { coreStrings } from 'kolibri/uiText/commonCoreStrings'; - import UpdatedResourceSelection from '../../../UpdatedResourceSelection.vue'; - import { coachStrings } from '../../../../../common/commonCoachStrings'; - import { PageNames } from '../../../../../../constants'; + import { enhancedQuizManagementStrings } from 'kolibri-common/strings/enhancedQuizManagementStrings'; + import { coachStrings } from '../../commonCoachStrings'; + import { PageNames } from '../../../../constants'; + import UpdatedResourceSelection from '../UpdatedResourceSelection.vue'; + import { SelectionTarget } from '../contants'; /** - * @typedef {import('../../../../../../composables/useFetch').FetchObject} FetchObject + * @typedef {import('../../../../composables/useFetch').FetchObject} FetchObject */ export default { @@ -67,15 +70,38 @@ const { manageLessonResourcesTitle$ } = coachStrings; const instance = getCurrentInstance(); - props.setTitle(manageLessonResourcesTitle$()); - props.setGoBack(() => { + const { selectResourcesDescription$ } = enhancedQuizManagementStrings; + const title = + props.target === SelectionTarget.LESSON + ? manageLessonResourcesTitle$() + : selectResourcesDescription$({ sectionTitle: props.sectionTitle }); + + props.setTitle(title); + + const redirectBack = () => { instance.proxy.$router.push({ - name: PageNames.LESSON_SELECT_RESOURCES_INDEX, + name: + props.target === SelectionTarget.LESSON + ? PageNames.LESSON_SELECT_RESOURCES_INDEX + : PageNames.QUIZ_SELECT_RESOURCES_INDEX, }); - }); + }; + const { topicId } = instance.proxy.$route.query; + if (!topicId) { + redirectBack(); + } + props.setGoBack(redirectBack); + + const channelsLink = { + name: + props.target === SelectionTarget.LESSON + ? PageNames.LESSON_SELECT_RESOURCES_INDEX + : PageNames.QUIZ_SELECT_RESOURCES_INDEX, + }; const { data, hasMore, fetchMore, loadingMore } = props.treeFetch; return { + channelsLink, contentList: data, hasMore, fetchMore, @@ -123,18 +149,23 @@ type: Boolean, default: false, }, - }, - beforeRouteEnter(to, _, next) { - const { topicId } = to.query; - if (!topicId) { - return next({ - name: PageNames.LESSON_SELECT_RESOURCES_INDEX, - params: { - ...to.params, - }, - }); - } - return next(); + /** + * The target entity for the selection. + * It can be either 'quiz' or 'lesson'. + */ + target: { + type: String, + required: true, + }, + /** + * The title of the section (valid just for quizzes). + * @type {string} + */ + sectionTitle: { + type: String, + required: false, + default: null, + }, }, }; diff --git a/kolibri/plugins/coach/assets/src/views/lessons/LessonSummaryPage/sidePanels/LessonResourceSelection/subPages/SelectionIndex.vue b/kolibri/plugins/coach/assets/src/views/common/resourceSelection/subPages/SelectionIndex.vue similarity index 71% rename from kolibri/plugins/coach/assets/src/views/lessons/LessonSummaryPage/sidePanels/LessonResourceSelection/subPages/SelectionIndex.vue rename to kolibri/plugins/coach/assets/src/views/common/resourceSelection/subPages/SelectionIndex.vue index 8e54c8c761f..8a5304847be 100644 --- a/kolibri/plugins/coach/assets/src/views/lessons/LessonSummaryPage/sidePanels/LessonResourceSelection/subPages/SelectionIndex.vue +++ b/kolibri/plugins/coach/assets/src/views/common/resourceSelection/subPages/SelectionIndex.vue @@ -69,11 +69,13 @@ import { coreStrings } from 'kolibri/uiText/commonCoreStrings'; import AccessibleChannelCard from 'kolibri-common/components/Cards/AccessibleChannelCard.vue'; - import { PageNames } from '../../../../../../constants'; - import { coachStrings } from '../../../../../common/commonCoachStrings'; + import { enhancedQuizManagementStrings } from 'kolibri-common/strings/enhancedQuizManagementStrings'; + import { PageNames } from '../../../../constants'; + import { coachStrings } from '../../commonCoachStrings'; + import { SelectionTarget } from '../contants'; /** - * @typedef {import('../../../../../../composables/useFetch').FetchObject} FetchObject + * @typedef {import('../../../../composables/useFetch').FetchObject} FetchObject */ export default { @@ -96,9 +98,15 @@ searchLabel$, } = coreStrings; + const { selectResourcesDescription$ } = enhancedQuizManagementStrings; const { manageLessonResourcesTitle$ } = coachStrings; - props.setTitle(manageLessonResourcesTitle$()); + const title = + props.target === SelectionTarget.LESSON + ? manageLessonResourcesTitle$() + : selectResourcesDescription$({ sectionTitle: props.sectionTitle }); + + props.setTitle(title); props.setGoBack(null); return { @@ -137,18 +145,46 @@ type: Object, required: true, }, + /** + * The target entity for the selection. + * It can be either 'quiz' or 'lesson'. + */ + target: { + type: String, + required: true, + }, + /** + * The title of the section (valid just for quizzes). + * @type {string} + */ + sectionTitle: { + type: String, + required: false, + default: null, + }, }, computed: { selectFromBookmarksLink() { + if (this.target === SelectionTarget.LESSON) { + return { + name: PageNames.LESSON_SELECT_RESOURCES_BOOKMARKS, + }; + } return { - name: PageNames.LESSON_SELECT_RESOURCES_BOOKMARKS, + name: PageNames.QUIZ_SELECT_RESOURCES_BOOKMARKS, }; }, }, methods: { selectFromChannelsLink(channel) { + if (this.target === SelectionTarget.LESSON) { + return { + name: PageNames.LESSON_SELECT_RESOURCES_TOPIC_TREE, + query: { topicId: channel.id }, + }; + } return { - name: PageNames.LESSON_SELECT_RESOURCES_TOPIC_TREE, + name: PageNames.QUIZ_SELECT_RESOURCES_TOPIC_TREE, query: { topicId: channel.id }, }; }, diff --git a/kolibri/plugins/coach/assets/src/views/lessons/LessonSummaryPage/sidePanels/LessonResourceSelection/index.vue b/kolibri/plugins/coach/assets/src/views/lessons/LessonSummaryPage/sidePanels/LessonResourceSelection/index.vue index df1d8c5f135..141b312cc21 100644 --- a/kolibri/plugins/coach/assets/src/views/lessons/LessonSummaryPage/sidePanels/LessonResourceSelection/index.vue +++ b/kolibri/plugins/coach/assets/src/views/lessons/LessonSummaryPage/sidePanels/LessonResourceSelection/index.vue @@ -34,6 +34,7 @@ :selectedResources="selectedResources" :unselectableResourceIds="unselectableResourceIds" :selectedResourcesSize="selectedResourcesSize" + :target="SelectionTarget.LESSON" @selectResources="selectResources" @deselectResources="deselectResources" @setSelectedResources="setSelectedResources" @@ -89,6 +90,7 @@ import useSnackbar from 'kolibri/composables/useSnackbar'; import { PageNames } from '../../../../../constants'; import { coachStrings } from '../../../../common/commonCoachStrings'; + import { SelectionTarget } from '../../../../common/resourceSelection/contants'; import useResourceSelection from '../../../../../composables/useResourceSelection'; export default { @@ -131,6 +133,7 @@ bookmarksFetch, treeFetch, selectionRules, + SelectionTarget, selectResources, deselectResources, setSelectedResources, diff --git a/packages/kolibri-common/strings/enhancedQuizManagementStrings.js b/packages/kolibri-common/strings/enhancedQuizManagementStrings.js index 83c14223065..1e3e5fc5295 100644 --- a/packages/kolibri-common/strings/enhancedQuizManagementStrings.js +++ b/packages/kolibri-common/strings/enhancedQuizManagementStrings.js @@ -205,6 +205,20 @@ export const enhancedQuizManagementStrings = createTranslator('EnhancedQuizManag saveAndClose: { message: 'Save and close', }, + questionsSettingsLabel: { + message: "Questions settings for '{ sectionTitle }'", + context: + 'A title label for the section of the page that contains settings for questions selection', + }, + maxNumberOfQuestionsInfo: { + message: + 'You can add up to { count, number } { count, plural, one { question } other { questions }} to this section', + context: 'A message that informs the user about the maximum number of questions they can add', + }, + chooseQuestionsManuallyLabel: { + message: 'Choose questions manually', + context: 'A label for a checkbox that allows the user to manually select questions', + }, }); const { sectionLabel$ } = enhancedQuizManagementStrings; From f964888310a51f98c7899e9bcb8dd220657a5a92 Mon Sep 17 00:00:00 2001 From: Alex Velez Date: Wed, 29 Jan 2025 17:02:20 -0500 Subject: [PATCH 04/15] Refactor quiz resource selection component --- .../src/composables/useResourceSelection.js | 49 +- .../QuizResourceSelection/_index.vue | 261 ----- .../QuizResourceSelection/index.vue | 957 ++++-------------- 3 files changed, 213 insertions(+), 1054 deletions(-) delete mode 100644 kolibri/plugins/coach/assets/src/views/quizzes/CreateExamPage/sidePanels/QuizResourceSelection/_index.vue diff --git a/kolibri/plugins/coach/assets/src/composables/useResourceSelection.js b/kolibri/plugins/coach/assets/src/composables/useResourceSelection.js index 86e35f9bd56..d6068cf5a92 100644 --- a/kolibri/plugins/coach/assets/src/composables/useResourceSelection.js +++ b/kolibri/plugins/coach/assets/src/composables/useResourceSelection.js @@ -38,7 +38,7 @@ import useFetch from './useFetch'; * * @returns {UseResourceSelectionResponse} */ -export default function useResourceSelection() { +export default function useResourceSelection({ bookmarks, channels, topicTree } = {}) { const store = getCurrentInstance().proxy.$store; const route = computed(() => store.state.route); const topicId = computed(() => route.value.query.topicId); @@ -47,10 +47,21 @@ export default function useResourceSelection() { const selectedResources = ref([]); const topic = ref(null); + const fetchBookmarks = async params => { + const response = await ContentNodeResource.fetchBookmarks(params); + if (bookmarks?.annotator) { + const annotatedResults = await bookmarks.annotator(response.results); + return { + ...response, + results: annotatedResults, + }; + } + return response; + }; const bookmarksFetch = useFetch({ fetchMethod: () => - ContentNodeResource.fetchBookmarks({ - params: { limit: 25, available: true }, + fetchBookmarks({ + params: { limit: 25, available: true, ...bookmarks?.filters }, }), fetchMoreMethod: more => ContentNodeResource.fetchBookmarks({ @@ -58,22 +69,40 @@ export default function useResourceSelection() { }), }); + const fetchChannels = async () => { + const result = await ChannelResource.fetchCollection({ + getParams: { + available: true, + ...channels?.filters, + }, + }); + if (channels?.annotator) { + return channels.annotator(result); + } + return result; + }; const channelsFetch = useFetch({ - fetchMethod: () => - ChannelResource.fetchCollection({ - getParams: { - available: true, - }, - }), + fetchMethod: fetchChannels, }); const fetchTree = async (params = {}) => { topic.value = await ContentNodeResource.fetchTree(params); + if (topicTree?.annotator) { + const annotatedResults = await topicTree.annotator(topic.value.children.results); + return { + ...topic.value.children, + results: annotatedResults, + }; + } return topic.value.children; }; const treeFetch = useFetch({ - fetchMethod: () => fetchTree({ id: topicId.value, params: { include_coach_content: true } }), + fetchMethod: () => + fetchTree({ + id: topicId.value, + params: { include_coach_content: true, ...topicTree?.filters }, + }), fetchMoreMethod: more => fetchTree(more), }); diff --git a/kolibri/plugins/coach/assets/src/views/quizzes/CreateExamPage/sidePanels/QuizResourceSelection/_index.vue b/kolibri/plugins/coach/assets/src/views/quizzes/CreateExamPage/sidePanels/QuizResourceSelection/_index.vue deleted file mode 100644 index ca2da01c8ea..00000000000 --- a/kolibri/plugins/coach/assets/src/views/quizzes/CreateExamPage/sidePanels/QuizResourceSelection/_index.vue +++ /dev/null @@ -1,261 +0,0 @@ - - - - - - - diff --git a/kolibri/plugins/coach/assets/src/views/quizzes/CreateExamPage/sidePanels/QuizResourceSelection/index.vue b/kolibri/plugins/coach/assets/src/views/quizzes/CreateExamPage/sidePanels/QuizResourceSelection/index.vue index ae8ced90b1a..944b846b90a 100644 --- a/kolibri/plugins/coach/assets/src/views/quizzes/CreateExamPage/sidePanels/QuizResourceSelection/index.vue +++ b/kolibri/plugins/coach/assets/src/views/quizzes/CreateExamPage/sidePanels/QuizResourceSelection/index.vue @@ -1,151 +1,58 @@ @@ -209,43 +105,28 @@ } from 'kolibri-common/strings/enhancedQuizManagementStrings'; import { computed, ref, getCurrentInstance, watch } from 'vue'; import commonCoreStrings from 'kolibri/uiText/commonCoreStrings'; - import ContentNodeResource from 'kolibri-common/apiResources/ContentNodeResource'; - import ChannelResource from 'kolibri-common/apiResources/ChannelResource'; import { ContentNodeKinds, MAX_QUESTIONS_PER_QUIZ_SECTION } from 'kolibri/constants'; - import useKResponsiveWindow from 'kolibri-design-system/lib/composables/useKResponsiveWindow'; - import useBaseSearch from 'kolibri-common/composables/useBaseSearch'; - import SearchFiltersPanel from 'kolibri-common/components/SearchFiltersPanel'; + import SidePanelModal from 'kolibri-common/components/SidePanelModal'; import { coachStrings } from '../../../../common/commonCoachStrings'; import { exerciseToQuestionArray } from '../../../../../utils/selectQuestions'; - import { PageNames, ViewMoreButtonStates } from '../../../../../constants/index'; - import BookmarkIcon from '../../../../lessons/LessonResourceSelectionPage/LessonContentCard/BookmarkIcon.vue'; + import { PageNames } from '../../../../../constants/index'; import useQuizResources from '../../../../../composables/useQuizResources'; import { injectQuizCreation } from '../../../../../composables/useQuizCreation'; - import ContentCardList from '../../../../lessons/LessonResourceSelectionPage/ContentCardList.vue'; - import ResourceSelectionBreadcrumbs from '../../../../lessons/LessonResourceSelectionPage/SearchTools/ResourceSelectionBreadcrumbs.vue'; + import useResourceSelection from '../../../../../composables/useResourceSelection'; + import { SelectionTarget } from '../../../../common/resourceSelection/contants'; export default { name: 'ResourceSelection', components: { - SearchFiltersPanel, - ContentCardList, - BookmarkIcon, - ResourceSelectionBreadcrumbs, + SidePanelModal, }, mixins: [commonCoreStrings], - setup(props, context) { - const store = getCurrentInstance().proxy.$store; - const route = computed(() => store.state.route); - const topicId = computed(() => route.value.params.topic_id); - // We use this query parameter to decide if we want to show the Bookmarks Card - // or the actual exercises that are bookmarked and can be selected - // to be added to Quiz Section. - const showBookmarks = computed(() => route.value.query.showBookmarks); - const searchQuery = computed(() => route.value.query.search); + setup(props) { + const { $store, $router } = getCurrentInstance().proxy; + const route = computed(() => $store.state.route); const { activeSection, activeSectionIndex, - allResourceMap, updateSection, addQuestionsToSectionFromResources, allQuestionsInQuiz, @@ -253,39 +134,38 @@ addSection, } = injectQuizCreation(); const showCloseConfirmation = ref(false); - const maxQuestions = computed( - () => MAX_QUESTIONS_PER_QUIZ_SECTION - activeQuestions.value.length, - ); - - const questionCount = ref(Math.min(10, maxQuestions.value)); - // Make the maxSectionQuestionOptions a computed property based on the questionCount - // that the user has selected, so if they want to add 10 questions, only let them - // choose a total of 100 to select those from. - const maxSectionQuestionOptions = computed(() => questionCount.value * 10); + const settings = ref({ + maxQuestions: null, + questionCount: null, + isChoosingManually: null, + }); + watch( + activeQuestions, + () => { + const newSettings = { ...settings.value }; + newSettings.maxQuestions = MAX_QUESTIONS_PER_QUIZ_SECTION - activeQuestions.value.length; + if (newSettings.questionCount === null) { + // initialize questionCount if it hasn't been set yet + newSettings.questionCount = Math.min(10, newSettings.maxQuestions); + } + settings.value = newSettings; + }, + { immediate: true }, + ); const selectPracticeQuiz = computed(() => props.selectPracticeQuiz); const { - numberOfSelectedBookmarks$, - selectResourcesDescription$, questionsFromResources$, - cannotSelectSomeTopicWarning$, questionsUnusedInSection$, - numberOfQuestionsSelected$, - numberOfQuestionsToAdd$, - maxNumberOfQuestions$, tooManyQuestions$, selectQuiz$, - selectPracticeQuizLabel$, - numberOfQuestionsLabel$, addNumberOfQuestions$, } = enhancedQuizManagementStrings; const { closeConfirmationTitle$, closeConfirmationMessage$ } = coachStrings; - const { windowIsSmall } = useKResponsiveWindow(); - /** * @type {Ref} - The uncommitted version of the section's resource_pool */ @@ -310,365 +190,81 @@ * @param {QuizExercise} content * @affects workingResourcePool - Remove given quiz exercise from workingResourcePool */ - function removeFromWorkingResourcePool(content) { - workingResourcePool.value = workingResourcePool.value.filter(obj => obj.id !== content.id); - } - - /** - * @affects workingResourcePool - Resets the workingResourcePool to the previous state - */ - function resetWorkingResourcePool() { - workingResourcePool.value = []; - } - - // Function to calculate the total number of questions currently selected for a node - // use this in order to do accurate counts for enabling/disabling checkboxes - function selectedQuestionsFromNode(content) { - if (content.kind === ContentNodeKinds.EXERCISE) { - return workingResourcePool.value.some(wr => wr.id === content.id) - ? unusedQuestionsCount(content) - : 0; - } - return workingResourcePool.value.reduce((acc, wr) => { - return ( - acc + - (wr.ancestors.some(ancestor => ancestor.id === content.id) - ? unusedQuestionsCount(wr) - : 0) - ); - }, 0); - } - - /** - * @param {QuizExercise} content - * Check if the content is present in workingResourcePool - */ - function contentPresentInWorkingResourcePool(content) { - if (content.kind === ContentNodeKinds.TOPIC) { - const selectedQuestionsFromTopic = selectedQuestionsFromNode(content); - return selectedQuestionsFromTopic >= unusedQuestionsCount(content); - } - return workingResourcePool.value.some(wr => wr.id === content.id); - } - - /** - * @param {QuizExercise} content - * Check if the content is partly present in workingResourcePool - * Only really useful for folders, as resources cannot be partially selected. - */ - function contentPartlyPresentInWorkingResourcePool(content) { - if (content.kind !== ContentNodeKinds.TOPIC) { - return false; - } - const selectedQuestionsFromTopic = selectedQuestionsFromNode(content); - return ( - selectedQuestionsFromTopic > 0 && selectedQuestionsFromTopic < content.num_assessments + function removeFromWorkingResourcePool(resources = []) { + workingResourcePool.value = workingResourcePool.value.filter( + obj => !resources.some(r => r.id === obj.id), ); } - function fetchSearchResults() { - if (!searchQuery.value) { - return; - } - const getParams = { - max_results: 25, - keywords: searchQuery.value, - kind: ContentNodeKinds.EXERCISE, - }; - if (selectPracticeQuiz.value) { - getParams.contains_quiz = true; - } - return ContentNodeResource.fetchCollection({ getParams }).then(response => { - searchResults.value = response.results; - moreSearchResults.value = response.more; - }); - } - - function fetchMoreSearchResults() { - return ContentNodeResource.fetchCollection({ - getParams: moreSearchResults.value, - }).then(response => { - searchResults.value = searchResults.value.concat(response.results); - moreSearchResults.value = response.more; - }); - } - - const _selectAllState = computed(() => { - return contentList.value.map(contentPresentInWorkingResourcePool); - }); - - const selectAllChecked = computed(() => { - return _selectAllState.value.every(Boolean); - }); - - const selectAllIndeterminate = computed(() => { - return ( - (!selectAllChecked.value && _selectAllState.value.some(Boolean)) || - contentList.value.some(contentPartlyPresentInWorkingResourcePool) - ); - }); - - const showSelectAll = computed(() => { - return ( - !selectPracticeQuiz.value && - contentList.value.every(content => nodeIsSelectableOrUnselectable(content)) && - // We only show the select all button if both all the checkboxes are enabled, - // and adding all the currently unselected questions in resources/folders plus - // those that are already selected (both in this level of the topic tree and elsewhere) - // would not exceed the maxSectionQuestionOptions. - // Do this by taking away the selected questions from the total questions in the - // resource/folder and checking if the sum of all of these is less than or equal to - // the remaining number of questions that can be added. - contentList.value.reduce( - (acc, content) => - unusedQuestionsCount(content) - selectedQuestionsFromNode(content) + acc, - 0, - ) <= - maxSectionQuestionOptions.value - workingPoolUnusedQuestions.value - ); - }); - /** - * @param {Object} param - * @param {ContentNode} param.content - * @param {boolean} param.checked - * @affects workingResourcePool - Adds or removes the content from the workingResourcePool - * When given a topic, it adds or removes all the exercises in the topic from the - * workingResourcePool. + * @affects workingResourcePool - Resets the workingResourcePool to the previous state */ - async function toggleSelected({ content, checked }) { - if (content.is_leaf) { - content = [content]; - } else { - // If we already have all of the children locally, and every child is a leaf, we can - // just add them all to the working resource pool - if (!content.children.more && !content.children.results.some(n => !n.is_leaf)) { - content = content.children.results; - } else { - // If we don't have all of the children locally, we need to fetch them - const children = await ContentNodeResource.fetchCollection({ - getParams: { - descendant_of: content.id, - available: true, - kind: ContentNodeKinds.EXERCISE, - }, - }); - content = children; - } - } - if (checked) { - if (selectPracticeQuiz.value) { - resetWorkingResourcePool(); - } - addToWorkingResourcePool(content); - } else { - content.forEach(c => { - removeFromWorkingResourcePool(c); - }); - } + function setWorkingResourcePool(resources = []) { + workingResourcePool.value = resources; } - const { - topic, - resources, - loading: quizResourcesLoading, - fetchQuizResources, - fetchMoreQuizResources, - hasMore, - annotateTopicsWithDescendantCounts, - setResources, - loadingMore, - } = useQuizResources({ topicId, practiceQuiz: selectPracticeQuiz.value }); - - // TODO let's not use text for this - const viewMoreButtonState = computed(() => { - if (loadingMore.value) { - return ViewMoreButtonStates.LOADING; - } - if (hasMore.value || moreSearchResults.value) { - return ViewMoreButtonStates.HAS_MORE; - } - return ViewMoreButtonStates.NO_MORE; - }); - - const { searchTerms, search } = useBaseSearch({ descendant: topic }); - search(); - - const _loading = ref(true); - - const channels = ref([]); - const bookmarks = ref([]); - const searchResults = ref([]); - const moreSearchResults = ref(null); + const { annotateTopicsWithDescendantCounts } = useQuizResources(); const unusedQuestionsCount = useMemoize(content => { - if (content.kind === ContentNodeKinds.EXERCISE) { - const questionItems = content.assessmentmetadata.assessment_item_ids.map( - aid => `${content.id}:${aid}`, - ); - const questionsItemsAlreadyUsed = allQuestionsInQuiz.value - .map(q => q.item) - .filter(i => questionItems.includes(i)); - const questionItemsAvailable = questionItems.length - questionsItemsAlreadyUsed.length; - return questionItemsAvailable; - } - if (content.kind === ContentNodeKinds.TOPIC || content.kind === ContentNodeKinds.CHANNEL) { - const total = content.num_assessments; - const numberOfQuestionsSelected = allQuestionsInQuiz.value.filter(question => { - const questionNode = allResourceMap.value[question.exercise_id]; - for (const ancestor of questionNode.ancestors) { - if (ancestor.id === content.id) { - return true; - } - } - return false; - }).length; - return total - numberOfQuestionsSelected; - } - return -1; - }); - - /** @returns {Boolean} Whether the given node should be displayed with a checkbox - * @description Returns true for exercises or folders that have more than 0 unused questions - * and adding it would give us fewer than maxSectionQuestionOptions questions - */ - function nodeIsSelectableOrUnselectable(node) { - // For practice quizzes, we only want to allow selection of resources, not folders. - if (selectPracticeQuiz.value && node.kind === ContentNodeKinds.EXERCISE) { - return true; - } - if (selectPracticeQuiz.value) { - return false; - } - if ( - contentPresentInWorkingResourcePool(node) || - contentPartlyPresentInWorkingResourcePool(node) - ) { - // If a node has been selected or partly selected, always allow it to be deselected. - return true; - } - // If a node has not been selected, only allow it to be selected if it has unused questions - // and adding it would not exceed the remaining maxSectionQuestionOptions. - const count = unusedQuestionsCount(node); - return ( - count > 0 && count + workingPoolUnusedQuestions.value <= maxSectionQuestionOptions.value + const questionItems = content.assessmentmetadata.assessment_item_ids.map( + aid => `${content.id}:${aid}`, ); - } - - function showCheckbox(node) { - // We only show checkboxes for exercises, not topics for practice quizzes - if (selectPracticeQuiz.value) { - return node.kind === ContentNodeKinds.EXERCISE; - } - // Otherwise we show checkboxes for exercises and topics - // but not channels. - return node.kind === ContentNodeKinds.EXERCISE || node.kind === ContentNodeKinds.TOPIC; - } - - // Load up the channels - function handleTopicIdChange() { - const promises = []; - if (topicId.value) { - promises.push(fetchQuizResources()); - } else { - promises.push( - ContentNodeResource.fetchBookmarks({ - params: { limit: 25, available: true, kind: ContentNodeKinds.EXERCISE }, - }).then(data => { - const isPracticeQuiz = item => - !selectPracticeQuiz.value || get(item, ['options', 'modality'], false) === 'QUIZ'; - bookmarks.value = data.results ? data.results.filter(isPracticeQuiz) : []; - }), - ); - - promises.push( - ChannelResource.fetchCollection({ - getParams: { - contains_exercise: true, - available: true, - contains_quiz: selectPracticeQuiz.value ? true : null, - }, - }).then(response => { - setResources( - annotateTopicsWithDescendantCounts( - response.map(chnl => { - return { - ...chnl, - id: chnl.root, - title: chnl.name, - kind: ContentNodeKinds.CHANNEL, - is_leaf: false, - }; - }), - ).then(annotatedResources => { - // When we don't have a topicId we're setting the value of - // useQuizResources.resources to the value of the channels - // (treating those channels as the topics) -- we then call - // this annotateTopicsWithDescendantCounts method to ensure - // that the channels are annotated with their num_assessments - setResources(annotatedResources); - channels.value = annotatedResources; - }), - ); - }), - ); - } - Promise.all(promises).then(() => { - _loading.value = false; - }); - } - - // Do initial data loading on create - if (searchQuery.value) { - fetchSearchResults(); - } else { - handleTopicIdChange(); - } - - const loading = computed(() => { - return _loading.value || quizResourcesLoading.value; + const questionsItemsAlreadyUsed = allQuestionsInQuiz.value + .map(q => q.item) + .filter(i => questionItems.includes(i)); + const questionItemsAvailable = questionItems.length - questionsItemsAlreadyUsed.length; + return questionItemsAvailable; }); - const contentList = computed(() => { - /* - if (!topicId.value) { - return channels.value; - } - */ - if (showBookmarks.value) { - return bookmarks.value.map(item => ({ ...item, is_leaf: true })); - } - - if (searchQuery.value) { - return searchResults.value; - } - - if (!topicId.value) { - return channels.value; - } - - return resources.value; + const isPracticeQuiz = item => + !selectPracticeQuiz.value || get(item, ['options', 'modality'], false) === 'QUIZ'; + + const { topic, loading, treeFetch, channelsFetch, bookmarksFetch } = useResourceSelection({ + bookmarks: { + filters: { kind: ContentNodeKinds.EXERCISE }, + annotator: results => results.filter(isPracticeQuiz), + }, + channels: { + filters: { + contains_exercise: true, + contains_quiz: selectPracticeQuiz.value ? true : null, + }, + annotator: results => + annotateTopicsWithDescendantCounts( + results.map(channel => { + return { + ...channel, + id: channel.root, + title: channel.name, + kind: ContentNodeKinds.CHANNEL, + is_leaf: false, + }; + }), + ), + }, + topicTree: { + filters: { + kind_in: [ContentNodeKinds.EXERCISE, ContentNodeKinds.TOPIC], + contains_quiz: selectPracticeQuiz.value ? true : null, + }, + annotator: annotateTopicsWithDescendantCounts, + }, }); - // This ought to be sure that we're updating our resources whenever the topicId changes - // without remounting the whole component - watch(topicId, handleTopicIdChange); - - watch(searchQuery, fetchSearchResults); - - function fetchMoreResources() { - if (searchQuery.value) { - return fetchMoreSearchResults(); - } - return fetchMoreQuizResources(); - } - function handleCancelClose() { showCloseConfirmation.value = false; } - function handleConfirmClose() { - context.emit('closePanel'); + function handleClosePanel() { + $router.push({ + name: PageNames.EXAM_CREATION_ROOT, + params: { + classId: route.value.params.classId, + quizId: route.value.params.quizId, + sectionIndex: route.value.params.sectionIndex, + }, + query: { ...route.value.query }, + }); } const workingPoolHasChanged = computed(() => { @@ -682,13 +278,7 @@ }); const tooManyQuestions = computed(() => { - // Always allow one resource to be selected, just in case - // the exercise a user wants to use exceeds the maxSectionQuestionOptions - // with only its own unused questions. - return ( - workingResourcePool.value.length !== 1 && - workingPoolUnusedQuestions.value > maxSectionQuestionOptions.value - ); + return workingResourcePool.value.length > settings.value.questionCount; }); const disableSave = computed(() => { @@ -697,77 +287,55 @@ } return ( !workingPoolHasChanged.value || - workingPoolUnusedQuestions.value < questionCount.value || - questionCount.value < 1 || + workingPoolUnusedQuestions.value < settings.value.questionCount || + settings.value.questionCount < 1 || tooManyQuestions.value || - questionCount.value > maxQuestions.value + settings.value.questionCount.value > settings.value.maxQuestions ); }); - function handleSelectAll(isChecked) { - for (const content of contentList.value) { - if (nodeIsSelectableOrUnselectable(content)) { - toggleSelected({ content, checked: isChecked }); - } - } - } + const title = ref(''); + const goBack = ref(null); + const continueAction = ref(null); + const sectionTitle = computed(() => + displaySectionTitle(activeSection.value, activeSectionIndex.value), + ); return { - showSearch: ref(false), - nodeIsSelectableOrUnselectable, - showCheckbox, - displaySectionTitle, + title, + goBack, + continueAction, + SelectionTarget, + sectionTitle, unusedQuestionsCount, - activeSection, activeSectionIndex, - activeQuestions, addSection, - selectAllChecked, - selectAllIndeterminate, - showSelectAll, - handleSelectAll, - toggleSelected, workingPoolHasChanged, tooManyQuestions, - handleConfirmClose, + handleClosePanel, handleCancelClose, topic, - contentList, showCloseConfirmation, + treeFetch, + channelsFetch, + bookmarksFetch, loading, - loadingMore, - fetchMoreResources, - resetWorkingResourcePool, - contentPresentInWorkingResourcePool, - contentPartlyPresentInWorkingResourcePool, - questionCount, - maxQuestions, - maxSectionQuestionOptions, + addToWorkingResourcePool, + removeFromWorkingResourcePool, + setWorkingResourcePool, + settings, workingPoolUnusedQuestions, disableSave, - cannotSelectSomeTopicWarning$, closeConfirmationMessage$, closeConfirmationTitle$, - numberOfQuestionsSelected$, tooManyQuestions$, - maxNumberOfQuestions$, - numberOfSelectedBookmarks$, questionsUnusedInSection$, - selectResourcesDescription$, questionsFromResources$, - windowIsSmall, - bookmarks, - viewMoreButtonState, updateSection, addQuestionsToSectionFromResources, workingResourcePool, - showBookmarks, selectQuiz$, - numberOfQuestionsToAdd$, - selectPracticeQuizLabel$, - numberOfQuestionsLabel$, addNumberOfQuestions$, - searchTerms, }; }, props: { @@ -776,54 +344,6 @@ default: false, }, }, - computed: { - isTopicIdSet() { - return this.$route.params.topic_id; - }, - - getBookmarksLink() { - // Inject the showBookmarks parameter so that - // the resourceSelection component now renderes only the - // the exercises that are bookmarked for the Quiz selection. - return { - ...this.$route, - query: { showBookmarks: true }, - }; - }, - channelsLink() { - return { - name: this.$route.name, - params: { - ...this.$route.params, - topic_id: null, - }, - }; - }, - showNumberOfQuestionsWarning() { - if (this.selectPracticeQuiz) { - return false; - } - return !this.showSelectAll; - }, - borderStyle() { - return `border: 1px solid ${this.$themeTokens.fineLine}`; - }, - dividerStyle() { - return `color : ${this.$themeTokens.fineLine}`; - }, - /* - selectAllIsVisible() { - TO BE IMPLEMENTED IN https://github.com/learningequality/kolibri/issues/11734 - Should only be visible if there are any checkboxes at all -- we'll only be showing - checkboxes for Exercises, not topics - }, - */ - }, - watch: { - bookmarks(newVal) { - this.bookmarksCount = newVal.length; - }, - }, beforeRouteLeave(_, __, next) { if (!this.showCloseConfirmation && this.workingPoolHasChanged) { this.showCloseConfirmation = true; @@ -833,28 +353,6 @@ } }, methods: { - /** @public */ - focusFirstEl() { - this.$refs.textbox.focus(); - }, - contentLink(content) { - const { name, params, query } = this.$route; - if (!content.is_leaf) { - // Link folders to their page - return { - name, - params: { - ...params, - topic_id: content.id, - }, - }; - } - // Just return the current route; router-link will handle the no-op from here - return { name, params, query }; - }, - topicsLink(topic_id) { - return this.contentLink({ id: topic_id }); - }, saveSelectedResource() { if (this.selectPracticeQuiz) { if (this.workingResourcePool.length !== 1) { @@ -879,11 +377,11 @@ this.addQuestionsToSectionFromResources({ sectionIndex: this.activeSectionIndex, resourcePool: this.workingResourcePool, - questionCount: this.questionCount, + questionCount: this.settings.questionCount, }); } - this.resetWorkingResourcePool(); + this.setWorkingResourcePool(); this.$router.replace({ name: PageNames.EXAM_CREATION_ROOT, params: { @@ -892,7 +390,7 @@ }); }, // The message put onto the content's card when listed - selectionMetadata(content) { + getUnusedQuestionsMessage(content) { if (this.selectPracticeQuiz) { return; } @@ -919,122 +417,15 @@ @import '~kolibri-design-system/lib/styles/definitions'; - .select-resource { - padding-bottom: 6em; - } - - .title-style { - font-size: 1.4em; - font-weight: 600; - } - - .bookmark-container { - display: flex; - min-height: 141px; - margin-bottom: 24px; - border-radius: 2px; - box-shadow: - 0 1px 5px 0 #a1a1a1, - 0 2px 2px 0 #e6e6e6, - 0 3px 1px -2px #ffffff; - transition: box-shadow 0.25s ease; - } - - .mobile-bookmark-container { - @extend %dropshadow-2dp; - - display: flex; - max-width: 100%; - min-height: 141px; - margin: auto; - margin-bottom: 24px; - border-radius: 2px; - - .ease:hover { - @extend %dropshadow-6dp; - @extend %md-decelerate-func; - - transition: all $core-time; - } - } - - .mobile-bookmark-icon { - left: 24px !important; - } - - .mobile-text { + .side-panel-title { margin-top: 20px; - margin-left: 60px; + font-size: 18px; } - .bookmark-container:hover { - box-shadow: - 0 5px 5px -3px #a1a1a1, - 0 8px 10px 1px #d1d1d1, - 0 3px 14px 2px #d4d4d4; - } - - .text { - margin-left: 15rem; - } - - .bottom-navigation { - position: absolute; - right: 0; - bottom: 0; - left: 0; + .bottom-nav-container { display: flex; - justify-content: space-between; + justify-content: flex-end; width: 100%; - padding: 1em; - line-height: 2.5em; - text-align: center; - background-color: white; - border-top: 1px solid black; - } - - .select-folder-style { - margin-top: 0.5em; - margin-bottom: 0.5em; - } - - .align-select-folder-style { - margin-top: 2em; - } - - .shadow { - box-shadow: - 0 1px 3px 0 rgba(0, 0, 0, 0.2), - 0 1px 1px 0 rgba(0, 0, 0, 0.14), - 0 2px 1px -1px rgba(0, 0, 0, 0.12); - } - - // Force the leaf nodes not to look like a link - /deep/ .is-leaf.content-card { - cursor: default; - box-shadow: - 0 1px 5px 0 #a1a1a1, - 0 2px 2px 0 #e6e6e6, - 0 3px 1px -2px #ffffff; - } - - .number-question { - display: inline-flex; - } - - .group-button-border { - display: inline-flex; - align-items: center; - height: 3.5em; - border: 1px solid; - } - - .divider { - display: block; - min-width: 100%; - height: 1px; - margin: 24px 0; - overflow-y: hidden; } From 63fa921f30e31b2afdab188e993e170dff7a8d58 Mon Sep 17 00:00:00 2001 From: Alex Velez Date: Thu, 30 Jan 2025 09:08:02 -0500 Subject: [PATCH 05/15] Add shopping cart to quiz resources selection --- kolibri/plugins/coach/assets/src/app.js | 4 +- .../coach/assets/src/constants/index.js | 4 +- .../coach/assets/src/routes/examRoutes.js | 20 ++++++- .../coach/assets/src/routes/lessonsRoutes.js | 2 +- .../subPages/ManageSelectedResources.vue | 43 ++++++++++---- .../QuizResourceSelection/index.vue | 59 +++++++++++-------- 6 files changed, 92 insertions(+), 40 deletions(-) rename kolibri/plugins/coach/assets/src/views/{lessons/LessonSummaryPage/sidePanels/LessonResourceSelection => common/resourceSelection}/subPages/ManageSelectedResources.vue (87%) diff --git a/kolibri/plugins/coach/assets/src/app.js b/kolibri/plugins/coach/assets/src/app.js index 01905bade8e..2aec1255b87 100644 --- a/kolibri/plugins/coach/assets/src/app.js +++ b/kolibri/plugins/coach/assets/src/app.js @@ -62,10 +62,12 @@ class CoachToolsModule extends KolibriApp { PageNames.QUIZ_REPLACE_QUESTIONS, PageNames.QUIZ_SELECT_PRACTICE_QUIZ, PageNames.QUIZ_SELECT_RESOURCES, - PageNames.QUIZ_SELECT_RESOURCES_LANDING, PageNames.QUIZ_SELECT_RESOURCES_INDEX, PageNames.QUIZ_SELECT_RESOURCES_BOOKMARKS, PageNames.QUIZ_SELECT_RESOURCES_TOPIC_TREE, + PageNames.QUIZ_PREVIEW_SELECTED_RESOURCES, + PageNames.QUIZ_SELECT_RESOURCES_SETTINGS, + PageNames.QUIZ_SELECT_RESOURCES_LANDING_SETTINGS, PageNames.QUIZ_SECTION_ORDER, PageNames.QUIZ_BOOK_MARKED_RESOURCES, PageNames.QUIZ_LEARNER_REPORT, diff --git a/kolibri/plugins/coach/assets/src/constants/index.js b/kolibri/plugins/coach/assets/src/constants/index.js index 1d06cc96be5..8be7b7f8d13 100644 --- a/kolibri/plugins/coach/assets/src/constants/index.js +++ b/kolibri/plugins/coach/assets/src/constants/index.js @@ -19,10 +19,12 @@ export const PageNames = { QUIZ_BOOK_MARKED_RESOURCES: 'QUIZ_BOOK_MARKED_RESOURCES', QUIZ_SECTION_SIDE_PANEL: 'QUIZ_SECTION_SIDE_PANEL', QUIZ_SELECT_RESOURCES: 'QUIZ_SELECT_RESOURCES', - QUIZ_SELECT_RESOURCES_LANDING: 'QUIZ_SELECT_RESOURCES_LANDING', + QUIZ_PREVIEW_SELECTED_RESOURCES: 'QUIZ_PREVIEW_SELECTED_RESOURCES', QUIZ_SELECT_RESOURCES_INDEX: 'QUIZ_SELECT_RESOURCES_INDEX', QUIZ_SELECT_RESOURCES_BOOKMARKS: 'QUIZ_SELECT_RESOURCES_BOOKMARKS', QUIZ_SELECT_RESOURCES_TOPIC_TREE: 'QUIZ_SELECT_RESOURCES_TOPIC_TREE', + QUIZ_SELECT_RESOURCES_SETTINGS: 'QUIZ_SELECT_RESOURCES_SETTINGS', + QUIZ_SELECT_RESOURCES_LANDING_SETTINGS: 'QUIZ_SELECT_RESOURCES_LANDING_SETTINGS', /* Lessons */ LESSONS_ROOT: 'LESSONS_ROOT', diff --git a/kolibri/plugins/coach/assets/src/routes/examRoutes.js b/kolibri/plugins/coach/assets/src/routes/examRoutes.js index 0b7a609c548..770cc8307d1 100644 --- a/kolibri/plugins/coach/assets/src/routes/examRoutes.js +++ b/kolibri/plugins/coach/assets/src/routes/examRoutes.js @@ -15,6 +15,7 @@ import QuestionLearnersPage from '../views/common/reports/QuestionLearnersPage.v import SelectionIndex from '../views/common/resourceSelection/subPages/SelectionIndex.vue'; import SelectFromChannels from '../views/common/resourceSelection/subPages/SelectFromChannels.vue'; import SelectFromBookmarks from '../views/common/resourceSelection/subPages/SelectFromBookmarks.vue'; +import ManageSelectedResources from '../views/common/resourceSelection/subPages/ManageSelectedResources.vue'; import { generateQuestionDetailHandler, questionRootRedirectHandler, @@ -95,12 +96,15 @@ export default [ name: PageNames.QUIZ_SELECT_RESOURCES, path: 'select-resources/', component: QuizResourceSelection, - redirect: 'select-resources/landing', + redirect: 'select-resources/landing-settings', children: [ { - name: PageNames.QUIZ_SELECT_RESOURCES_LANDING, - path: 'landing', + name: PageNames.QUIZ_SELECT_RESOURCES_LANDING_SETTINGS, + path: 'landing-settings', component: QuestionsSettings, + props: { + isLanding: true, + }, }, { name: PageNames.QUIZ_SELECT_RESOURCES_INDEX, @@ -117,6 +121,16 @@ export default [ path: 'channels', component: SelectFromChannels, }, + { + name: PageNames.QUIZ_PREVIEW_SELECTED_RESOURCES, + path: 'preview-resources', + component: ManageSelectedResources, + }, + { + name: PageNames.QUIZ_SELECT_RESOURCES_SETTINGS, + path: 'landing-settings', + component: QuestionsSettings, + }, // { // name: PageNames.LESSON_SELECT_RESOURCES_INDEX, // path: 'index', diff --git a/kolibri/plugins/coach/assets/src/routes/lessonsRoutes.js b/kolibri/plugins/coach/assets/src/routes/lessonsRoutes.js index 3943251dc49..f4cdaaf9617 100644 --- a/kolibri/plugins/coach/assets/src/routes/lessonsRoutes.js +++ b/kolibri/plugins/coach/assets/src/routes/lessonsRoutes.js @@ -42,7 +42,7 @@ import PreviewSelectedResources from '../views/lessons/LessonSummaryPage/sidePan import LessonResourceSelection from '../views/lessons/LessonSummaryPage/sidePanels/LessonResourceSelection'; import SelectFromBookmarks from '../views/common/resourceSelection/subPages/SelectFromBookmarks.vue'; import SelectFromChannels from '../views/common/resourceSelection/subPages/SelectFromChannels.vue'; -import ManageSelectedResources from '../views/lessons/LessonSummaryPage/sidePanels/LessonResourceSelection/subPages/ManageSelectedResources.vue'; +import ManageSelectedResources from '../views/common/resourceSelection/subPages/ManageSelectedResources.vue'; import { classIdParamRequiredGuard, RouteSegments } from './utils'; const { diff --git a/kolibri/plugins/coach/assets/src/views/lessons/LessonSummaryPage/sidePanels/LessonResourceSelection/subPages/ManageSelectedResources.vue b/kolibri/plugins/coach/assets/src/views/common/resourceSelection/subPages/ManageSelectedResources.vue similarity index 87% rename from kolibri/plugins/coach/assets/src/views/lessons/LessonSummaryPage/sidePanels/LessonResourceSelection/subPages/ManageSelectedResources.vue rename to kolibri/plugins/coach/assets/src/views/common/resourceSelection/subPages/ManageSelectedResources.vue index 083e6baf7df..8dd4a0a0449 100644 --- a/kolibri/plugins/coach/assets/src/views/lessons/LessonSummaryPage/sidePanels/LessonResourceSelection/subPages/ManageSelectedResources.vue +++ b/kolibri/plugins/coach/assets/src/views/common/resourceSelection/subPages/ManageSelectedResources.vue @@ -1,8 +1,11 @@