- {{ $tr('manyItems', { item1: items[0], item2: items[1], count: items.length - 2 }) }}
+
+ {{ getTruncatedItemsString(items) }}
@@ -24,6 +15,8 @@
+
+
+
diff --git a/kolibri/plugins/coach/assets/src/views/common/assignments/SidePanelRecipientsSelector/LearnersSelectorSidePanel.vue b/kolibri/plugins/coach/assets/src/views/common/assignments/SidePanelRecipientsSelector/LearnersSelectorSidePanel.vue
new file mode 100644
index 00000000000..9e8670fb20a
--- /dev/null
+++ b/kolibri/plugins/coach/assets/src/views/common/assignments/SidePanelRecipientsSelector/LearnersSelectorSidePanel.vue
@@ -0,0 +1,218 @@
+
+
+
+
+
+ {{ $tr('selectGroupsAndIndividualLearnersTitle') }}
+
+
+
+
+
+ {{ coachString('groupsLabel') }}
+
+
+
+
+
+
+
+
+
+ {{ coachString('individualLearnersLabel') }}
+
+ {{ coachString('onlyShowingEnrolledLabel') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/kolibri/plugins/coach/assets/src/views/common/assignments/SidePanelRecipientsSelector/index.vue b/kolibri/plugins/coach/assets/src/views/common/assignments/SidePanelRecipientsSelector/index.vue
new file mode 100644
index 00000000000..5494bb73766
--- /dev/null
+++ b/kolibri/plugins/coach/assets/src/views/common/assignments/SidePanelRecipientsSelector/index.vue
@@ -0,0 +1,250 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ selectedMessage }}
+
+
+
+
+ {{ assignmentInvalidText }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/kolibri/plugins/coach/assets/src/views/common/commonCoachStrings.js b/kolibri/plugins/coach/assets/src/views/common/commonCoachStrings.js
index 2c602fff59e..06fa823fab9 100644
--- a/kolibri/plugins/coach/assets/src/views/common/commonCoachStrings.js
+++ b/kolibri/plugins/coach/assets/src/views/common/commonCoachStrings.js
@@ -200,6 +200,16 @@ const coachStrings = createTranslator('CommonCoachStrings', {
context:
'A group is a collection of learners created by a coach inside a class to help with differentiated learning. Quizzes and lessons can be assigned to individual groups as well as to the whole class.',
},
+ individualLearnersLabel: {
+ message: 'Individual learners',
+ context:
+ 'A label for a checkbox that allows the Coach to assign the quiz to individual learners who may not be in a selected group.',
+ },
+ onlyShowingEnrolledLabel: {
+ message: 'Only showing learners that are enrolled in this class',
+ context:
+ "Shows beneath 'Select individual learners' explaining that the table only includes enrolled learners.",
+ },
helpNeededLabel: {
message: 'Help needed',
context:
@@ -635,6 +645,11 @@ const coachStrings = createTranslator('CommonCoachStrings', {
context:
"In the 'Manage lesson resources' coaches can add new/remove resource material to a lesson.",
},
+ groupsAndLearnersLabel: {
+ message: 'Groups and individual learners',
+ context:
+ 'Label for the radio button that allows the coach to select groups or individual learners to assign a quiz to.',
+ },
});
// Strings for the Missing Content modals, tooltips, alerts, etc.
@@ -656,6 +671,24 @@ const MissingContentStrings = createTranslator('MissingContentStrings', {
},
});
+// Strings for showing lists of items that can be truncated
+const truncatedItemsStrings = createTranslator('TruncatedItemsStrings', {
+ twoItems: {
+ message: '{item1}, {item2}',
+ context:
+ "DO NOT TRANSLATE\nCopy the source string.\n\nFor reference: 'item' will be replaced by the name of the coach(es) in the list of classes.",
+ },
+ threeItems: {
+ message: '{item1}, {item2}, {item3}',
+ context:
+ "DO NOT TRANSLATE\nCopy the source string.\n\nFor reference: 'item' will be replaced by the name of the coach(es) in the list of classes.",
+ },
+ manyItems: {
+ message: '{item1}, {item2}, and {count, number, integer} others',
+ context: "'item' will be replaced by the name of the coach(es) in the list of classes.",
+ },
+});
+
function coachString(key, args) {
return coachStrings.$tr(key, args);
}
@@ -669,4 +702,28 @@ const coachStringsMixin = {
},
};
-export { coachString, coachStrings, coachStringsMixin };
+function getTruncatedItemsString(items) {
+ if (items.length <= 1) {
+ return items[0] || '';
+ }
+ if (items.length === 2) {
+ return truncatedItemsStrings.$tr('twoItems', {
+ item1: items[0],
+ item2: items[1],
+ });
+ }
+ if (items.length === 3) {
+ return truncatedItemsStrings.$tr('threeItems', {
+ item1: items[0],
+ item2: items[1],
+ item3: items[2],
+ });
+ }
+ return truncatedItemsStrings.$tr('manyItems', {
+ item1: items[0],
+ item2: items[1],
+ count: items.length - 2,
+ });
+}
+
+export { coachString, coachStrings, coachStringsMixin, getTruncatedItemsString };
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 c60b7065895..b160e7eac08 100644
--- a/kolibri/plugins/coach/assets/src/views/quizzes/CreateExamPage/index.vue
+++ b/kolibri/plugins/coach/assets/src/views/quizzes/CreateExamPage/index.vue
@@ -19,6 +19,7 @@
v-if="quizInitialized"
ref="detailsModal"
assignmentType="quiz"
+ :selectRecipientsWithSidePanel="true"
:assignment="quiz"
:classId="classId"
:groups="groups"
@@ -334,6 +335,10 @@
}
},
saveQuizAndRedirect(close = true) {
+ const errorText = this.$refs.detailsModal.validate();
+ if (errorText) {
+ return;
+ }
this.saveQuiz()
.then(exam => {
this.$refs.detailsModal.handleSubmitSuccess();
diff --git a/kolibri/plugins/coach/assets/src/views/quizzes/QuizSummaryPage/QuizOptionsDropdownMenu.vue b/kolibri/plugins/coach/assets/src/views/quizzes/QuizSummaryPage/QuizOptionsDropdownMenu.vue
index fb40a75fc74..68ef7bd45d0 100644
--- a/kolibri/plugins/coach/assets/src/views/quizzes/QuizSummaryPage/QuizOptionsDropdownMenu.vue
+++ b/kolibri/plugins/coach/assets/src/views/quizzes/QuizSummaryPage/QuizOptionsDropdownMenu.vue
@@ -26,26 +26,30 @@
name: 'QuizOptionsDropdownMenu',
mixins: [coachStringsMixin, commonCoreStrings],
props: {
- draft: {
- type: Boolean,
- default: false,
+ exam: {
+ type: Object,
+ required: false,
+ default: null,
},
},
computed: {
options() {
- return [
- {
- label: this.draft
- ? this.coreString('editAction')
- : this.coreString('editDetailsAction'),
- value: 'EDIT_DETAILS',
- },
+ const options = [
{
label: this.$tr('copyQuizAction'),
value: 'COPY',
},
{ label: this.coreString('deleteAction'), value: 'DELETE' },
];
+ if (!this.exam?.archive) {
+ options.unshift({
+ label: this.exam?.draft
+ ? this.coreString('editAction')
+ : this.coreString('editDetailsAction'),
+ value: 'EDIT_DETAILS',
+ });
+ }
+ return options;
},
},
$trs: {
diff --git a/kolibri/plugins/coach/assets/src/views/quizzes/QuizSummaryPage/index.vue b/kolibri/plugins/coach/assets/src/views/quizzes/QuizSummaryPage/index.vue
index c2f9348159c..7857db317cb 100644
--- a/kolibri/plugins/coach/assets/src/views/quizzes/QuizSummaryPage/index.vue
+++ b/kolibri/plugins/coach/assets/src/views/quizzes/QuizSummaryPage/index.vue
@@ -23,7 +23,7 @@
style="margin-right: 8px"
/>
diff --git a/packages/kolibri-common/components/PaginatedListContainer.vue b/packages/kolibri-common/components/PaginatedListContainer.vue
index 5facd29b654..58ab38d5a5f 100644
--- a/packages/kolibri-common/components/PaginatedListContainer.vue
+++ b/packages/kolibri-common/components/PaginatedListContainer.vue
@@ -2,16 +2,17 @@
-
+
@@ -75,6 +76,10 @@
required: false,
default: 30,
},
+ searchFieldBlock: {
+ type: Boolean,
+ required: false,
+ },
},
data() {
return {
diff --git a/packages/kolibri-common/components/SidePanelModal/index.vue b/packages/kolibri-common/components/SidePanelModal/index.vue
index 375714e843f..26b1029c8fe 100644
--- a/packages/kolibri-common/components/SidePanelModal/index.vue
+++ b/packages/kolibri-common/components/SidePanelModal/index.vue
@@ -88,8 +88,6 @@
/* Will be calculated in mounted() as it will get the height of the fixedHeader then */
// @type {RefImpl}
windowBreakpoint,
- fixedHeaderHeight: 0,
- fixedBottombarHeight: 0,
lastFocus: null,
};
},
@@ -181,11 +179,6 @@
mounted() {
const htmlTag = window.document.getElementsByTagName('html')[0];
htmlTag.style['overflow-y'] = 'hidden';
- // Gets the height of the fixed header - adds 40 to account for padding + 24 for closeButton
- this.fixedHeaderHeight = this.$refs.fixedHeader.clientHeight;
- if (this.$refs.fixedBottombar) {
- this.fixedBottombarHeight = this.$refs.fixedBottombar.clientHeight;
- }
this.$nextTick(() => {
this.$emit('shouldFocusFirstEl');
});