diff --git a/kolibri/plugins/coach/assets/src/composables/useQuizResources.js b/kolibri/plugins/coach/assets/src/composables/useQuizResources.js
index 052ae64add..b243a8ca4e 100644
--- a/kolibri/plugins/coach/assets/src/composables/useQuizResources.js
+++ b/kolibri/plugins/coach/assets/src/composables/useQuizResources.js
@@ -128,11 +128,17 @@ export default function useQuizResources({ topicId } = {}) {
}
/** @returns {Boolean} Whether the given node should be displayed with a checkbox
- * @description Returns whether the given node is an exercise or not -- although, could be
- * extended in the future to permit topic-level selection if desired
+ * @description Returns true for exercises and for topics that have no topic children and no
+ * more children to load
*/
function hasCheckbox(node) {
- return node.kind === ContentNodeKinds.EXERCISE;
+ return (
+ node.kind === ContentNodeKinds.EXERCISE ||
+ // Has children, no more to load, and no children are topics
+ (node.children &&
+ !node.children.more &&
+ !node.children.results.some(c => c.kind === ContentNodeKinds.TOPIC))
+ );
}
function setResources(r) {
diff --git a/kolibri/plugins/coach/assets/src/views/plan/CreateExamPage/ResourceSelection.vue b/kolibri/plugins/coach/assets/src/views/plan/CreateExamPage/ResourceSelection.vue
index 8ac5bb5af5..d11589743b 100644
--- a/kolibri/plugins/coach/assets/src/views/plan/CreateExamPage/ResourceSelection.vue
+++ b/kolibri/plugins/coach/assets/src/views/plan/CreateExamPage/ResourceSelection.vue
@@ -50,6 +50,14 @@
+
+ {{ cannotSelectSomeTopicWarning$() }}
+
+
+
@@ -154,6 +163,7 @@
numberOfSelectedResources$,
numberOfResources$,
selectedResourcesInformation$,
+ cannotSelectSomeTopicWarning$,
} = enhancedQuizManagementStrings;
// TODO let's not use text for this
@@ -178,7 +188,29 @@
* a list of unique resources to avoid unnecessary duplication
*/
function addToWorkingResourcePool(resources = []) {
- workingResourcePool.value = uniqWith([...workingResourcePool.value, ...resources], isEqual);
+ workingResourcePool.value = uniqWith(
+ [
+ ...workingResourcePool.value,
+ ...resources.filter(r => r.kind === ContentNodeKinds.EXERCISE),
+ ],
+ isEqual
+ );
+ }
+
+ /**
+ * @description Returns the list of Exercises which can possibly be selected from the current
+ * contentList taking into consideration the logic for whether a topic can be selected or not.
+ * @returns {QuizExercise[]} - All contents which can be selected
+ */
+ function selectableContentList() {
+ return contentList.value.reduce((newList, content) => {
+ if (content.kind === ContentNodeKinds.TOPIC && hasCheckbox(content)) {
+ newList = [...newList, ...content.children.results];
+ } else {
+ newList.push(content);
+ }
+ return newList;
+ }, []);
}
/**
@@ -202,6 +234,9 @@
*/
function contentPresentInWorkingResourcePool(content) {
const workingResourceIds = workingResourcePool.value.map(wr => wr.id);
+ if (content.kind === ContentNodeKinds.TOPIC) {
+ return content.children.results.every(child => workingResourceIds.includes(child.id));
+ }
return workingResourceIds.includes(content.id);
}
@@ -226,6 +261,63 @@
});
}
+ const selectAllChecked = computed(() => {
+ // Returns true if all the resources in the topic are in the working resource pool
+ const workingResourceIds = workingResourcePool.value.map(wr => wr.id);
+ const selectableIds = selectableContentList().map(content => content.id);
+ return selectableIds.every(id => workingResourceIds.includes(id));
+ });
+
+ const selectAllIndeterminate = computed(() => {
+ // Returns true if some, but not all, of the resources in the topic are in the working
+ // resource
+ const workingResourceIds = workingResourcePool.value.map(wr => wr.id);
+ const selectableIds = selectableContentList().map(content => content.id);
+ return !selectAllChecked.value && selectableIds.some(id => workingResourceIds.includes(id));
+ });
+
+ const showSelectAll = computed(() => {
+ return contentList.value.every(content => hasCheckbox(content));
+ });
+
+ function handleSelectAll(isChecked) {
+ if (isChecked) {
+ this.addToWorkingResourcePool(selectableContentList());
+ } else {
+ this.contentList.forEach(content => {
+ var contentToRemove = [];
+ if (content.kind === ContentNodeKinds.TOPIC) {
+ contentToRemove = content.children.results;
+ } else {
+ contentToRemove.push(content);
+ }
+ contentToRemove.forEach(c => {
+ this.removeFromWorkingResourcePool(c);
+ });
+ });
+ }
+ }
+
+ /**
+ * @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. This assumes that topics which should not be added are not able to
+ * be checked and does not do any additional checks.
+ */
+ function toggleSelected({ content, checked }) {
+ content = content.kind === ContentNodeKinds.TOPIC ? content.children.results : [content];
+ if (checked) {
+ this.addToWorkingResourcePool(content);
+ } else {
+ content.forEach(c => {
+ this.removeFromWorkingResourcePool(c);
+ });
+ }
+ }
+
const {
hasCheckbox,
topic,
@@ -341,6 +433,11 @@
}
return {
+ selectAllChecked,
+ selectAllIndeterminate,
+ showSelectAll,
+ handleSelectAll,
+ toggleSelected,
topic,
topicId,
contentList,
@@ -353,6 +450,7 @@
resetWorkingResourcePool,
contentPresentInWorkingResourcePool,
//contentList,
+ cannotSelectSomeTopicWarning$,
sectionSettings$,
selectFromBookmarks$,
numberOfSelectedBookmarks$,
@@ -382,20 +480,6 @@
isTopicIdSet() {
return this.$route.params.topic_id;
},
- isSelectAllChecked() {
- // Returns true if all the resources in the topic are in the working resource pool
- const workingResourceIds = this.workingResourcePool.map(wr => wr.id);
- return this.contentList.every(content => workingResourceIds.includes(content.id));
- },
- selectAllIndeterminate() {
- // Returns true if some, but not all, of the resources in the topic are in the working
- // resource
- const workingResourceIds = this.workingResourcePool.map(wr => wr.id);
- return (
- !this.isSelectAllChecked &&
- this.contentList.some(content => workingResourceIds.includes(content.id))
- );
- },
getBookmarksLink() {
// Inject the showBookmarks parameter so that
@@ -423,6 +507,12 @@
},
},
methods: {
+ showTopicSizeWarningCard(content) {
+ return !this.hasCheckbox(content) && content.kind === ContentNodeKinds.TOPIC;
+ },
+ showTopicSizeWarning() {
+ return this.contentList.some(this.showTopicSizeWarningCard);
+ },
/** @public */
focusFirstEl() {
this.$refs.textbox.focus();
@@ -446,22 +536,6 @@
return {}; // or return {} if you prefer an empty object
},
- toggleSelected({ content, checked }) {
- if (checked) {
- this.addToWorkingResourcePool([content]);
- } else {
- this.removeFromWorkingResourcePool(content);
- }
- },
- toggleTopicInWorkingResources(isChecked) {
- if (isChecked) {
- this.addToWorkingResourcePool(this.contentList);
- } else {
- this.contentList.forEach(content => {
- this.removeFromWorkingResourcePool(content);
- });
- }
- },
topicListingLink({ topicId }) {
return this.$router.getRoute(
PageNames.QUIZ_SELECT_RESOURCES,
@@ -607,4 +681,9 @@
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);
+ }
+
diff --git a/kolibri/plugins/coach/assets/src/views/plan/LessonResourceSelectionPage/ContentCardList.vue b/kolibri/plugins/coach/assets/src/views/plan/LessonResourceSelectionPage/ContentCardList.vue
index d0dfd643cc..ffc72b8e6c 100644
--- a/kolibri/plugins/coach/assets/src/views/plan/LessonResourceSelectionPage/ContentCardList.vue
+++ b/kolibri/plugins/coach/assets/src/views/plan/LessonResourceSelectionPage/ContentCardList.vue
@@ -33,7 +33,11 @@
:link="contentCardLink(content)"
:numCoachContents="content.num_coach_contents"
:isLeaf="content.is_leaf"
- />
+ >
+
+
+
+
diff --git a/kolibri/plugins/coach/assets/src/views/plan/LessonResourceSelectionPage/LessonContentCard/index.vue b/kolibri/plugins/coach/assets/src/views/plan/LessonResourceSelectionPage/LessonContentCard/index.vue
index c64bf7f18c..e3ed3035d4 100644
--- a/kolibri/plugins/coach/assets/src/views/plan/LessonResourceSelectionPage/LessonContentCard/index.vue
+++ b/kolibri/plugins/coach/assets/src/views/plan/LessonResourceSelectionPage/LessonContentCard/index.vue
@@ -52,6 +52,7 @@
:isTopic="isTopic"
/>
+
diff --git a/packages/kolibri-common/strings/enhancedQuizManagementStrings.js b/packages/kolibri-common/strings/enhancedQuizManagementStrings.js
index 039a606f14..6cb7297274 100644
--- a/packages/kolibri-common/strings/enhancedQuizManagementStrings.js
+++ b/packages/kolibri-common/strings/enhancedQuizManagementStrings.js
@@ -154,4 +154,8 @@ export const enhancedQuizManagementStrings = createTranslator('EnhancedQuizManag
message:
'{count, number, integer} of {total, number, integer} {total, plural, one {resource selected} other {resources selected}}',
},
+ cannotSelectSomeTopicWarning: {
+ message:
+ 'You can only select folders with 12 or less exercises and no subfolders to avoid oversized quizzes.',
+ },
});