Skip to content

Commit

Permalink
Merge pull request #11864 from nucleogenesis/fix--topics-checkbox-and…
Browse files Browse the repository at this point in the history
…-select-all

Quiz creation resource selection: Topic selection & "Select all"
  • Loading branch information
nucleogenesis authored Feb 27, 2024
2 parents 645c5dd + c6e8450 commit 3ddc601
Show file tree
Hide file tree
Showing 5 changed files with 133 additions and 39 deletions.
12 changes: 9 additions & 3 deletions kolibri/plugins/coach/assets/src/composables/useQuizResources.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,14 @@
</div>
</div>

<div
v-if="showTopicSizeWarning()"
class="shadow"
:style=" { padding: '1em', marginBottom: '1em', backgroundColor: $themePalette.grey.v_100 }"
>
{{ cannotSelectSomeTopicWarning$() }}
</div>

<ResourceSelectionBreadcrumbs
v-if="isTopicIdSet"
:ancestors="topic.ancestors"
Expand All @@ -62,18 +70,19 @@
@clear="clearSearchTerm"
@searchterm="handleSearchTermChange"
/>

<ContentCardList
:contentList="contentList"
:showSelectAll="true"
:showSelectAll="showSelectAll"
:viewMoreButtonState="viewMoreButtonState"
:selectAllChecked="isSelectAllChecked"
:selectAllChecked="selectAllChecked"
:selectAllIndeterminate="selectAllIndeterminate"
:contentIsChecked="contentPresentInWorkingResourcePool"
:contentHasCheckbox="hasCheckbox"
:contentCardMessage="selectionMetadata"
:contentCardLink="contentLink"
:selectAllIndeterminate="selectAllIndeterminate"
:loadingMoreState="loadingMore"
@changeselectall="toggleTopicInWorkingResources"
@changeselectall="handleSelectAll"
@change_content_card="toggleSelected"
@moreresults="fetchMoreResources"
/>
Expand Down Expand Up @@ -154,6 +163,7 @@
numberOfSelectedResources$,
numberOfResources$,
selectedResourcesInformation$,
cannotSelectSomeTopicWarning$,
} = enhancedQuizManagementStrings;
// TODO let's not use text for this
Expand All @@ -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;
}, []);
}
/**
Expand All @@ -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);
}
Expand All @@ -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,
Expand Down Expand Up @@ -341,6 +433,11 @@
}
return {
selectAllChecked,
selectAllIndeterminate,
showSelectAll,
handleSelectAll,
toggleSelected,
topic,
topicId,
contentList,
Expand All @@ -353,6 +450,7 @@
resetWorkingResourcePool,
contentPresentInWorkingResourcePool,
//contentList,
cannotSelectSomeTopicWarning$,
sectionSettings$,
selectFromBookmarks$,
numberOfSelectedBookmarks$,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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();
Expand All @@ -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,
Expand Down Expand Up @@ -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);
}
</style>
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,11 @@
:link="contentCardLink(content)"
:numCoachContents="content.num_coach_contents"
:isLeaf="content.is_leaf"
/>
>
<template #notice>
<slot name="notice" :content="content"></slot>
</template>
</LessonContentCard>
</li>
</ul>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
:isTopic="isTopic"
/>
</div>
<slot name="notice"></slot>
</div>

</router-link>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.',
},
});

0 comments on commit 3ddc601

Please sign in to comment.