11import { v4 as uuidv4 } from 'uuid' ;
2+ import isEqual from 'lodash/isEqual' ;
23import { enhancedQuizManagementStrings } from 'kolibri-common/strings/enhancedQuizManagementStrings' ;
34import uniq from 'lodash/uniq' ;
45import { ContentNodeKinds } from 'kolibri.coreVue.vuex.constants' ;
@@ -8,7 +9,7 @@ import { get, set } from '@vueuse/core';
89import { computed , ref } from 'kolibri.lib.vueCompositionApi' ;
910// TODO: Probably move this to this file's local dir
1011import selectQuestions from '../modules/examCreation/selectQuestions.js' ;
11- import { Quiz , QuizSection } from './quizCreationSpecs.js' ;
12+ import { Quiz , QuizSection , QuizQuestion } from './quizCreationSpecs.js' ;
1213
1314/** Validators **/
1415/* objectSpecs expects every property to be available -- but we don't want to have to make an
@@ -30,7 +31,7 @@ function isExercise(o) {
3031/**
3132 * Composable function presenting primary interface for Quiz Creation
3233 */
33- export default ( ) => {
34+ export default ( DEBUG = false ) => {
3435 // -----------
3536 // Local state
3637 // -----------
@@ -43,16 +44,50 @@ export default () => {
4344 * The section that is currently selected for editing */
4445 const _activeSectionId = ref ( null ) ;
4546
46- /** @type {ref<QuizQuestion []> }
47- * The questions that are currently selected for action in the active section */
48- const _selectedQuestions = ref ( [ ] ) ;
47+ /** @type {ref<String []> }
48+ * The question_ids that are currently selected for action in the active section */
49+ const _selectedQuestionIds = ref ( [ ] ) ;
4950
5051 /** @type {ref<Array> } A list of all channels available which have exercises */
5152 const _channels = ref ( [ ] ) ;
5253
5354 /** @type {ref<Number> } A counter for use in naming new sections */
5455 const _sectionLabelCounter = ref ( 1 ) ;
5556
57+ //--
58+ // Debug Data Generators
59+ //--
60+ function _quizQuestions ( num = 5 ) {
61+ const questions = [ ] ;
62+ for ( let i = 0 ; i <= num ; i ++ ) {
63+ const overrides = {
64+ title : `Quiz Question ${ i } ` ,
65+ question_id : uuidv4 ( ) ,
66+ } ;
67+ questions . push ( objectWithDefaults ( overrides , QuizQuestion ) ) ;
68+ }
69+ return questions ;
70+ }
71+
72+ function _quizSections ( num = 5 , numQuestions = 5 ) {
73+ const sections = [ ] ;
74+ for ( let i = 0 ; i <= num ; i ++ ) {
75+ const overrides = {
76+ section_id : uuidv4 ( ) ,
77+ section_title : `Test section ${ i } ` ,
78+ questions : _quizQuestions ( numQuestions ) ,
79+ } ;
80+ sections . push ( objectWithDefaults ( overrides , QuizSection ) ) ;
81+ }
82+ return sections ;
83+ }
84+
85+ function _generateTestData ( numSections = 5 , numQuestions = 5 ) {
86+ const sections = _quizSections ( numSections , numQuestions ) ;
87+ updateQuiz ( { question_sources : sections } ) ;
88+ setActiveSection ( sections [ 0 ] . section_id ) ;
89+ }
90+
5691 // ------------------
5792 // Section Management
5893 // ------------------
@@ -103,10 +138,10 @@ export default () => {
103138 /**
104139 * @param {QuizQuestion[] } newQuestions
105140 * @affects _quiz - Updates the active section's `questions` property
106- * @affects _selectedQuestions - Clears this back to an empty array
141+ * @affects _selectedQuestionIds - Clears this back to an empty array
107142 * @throws {TypeError } if newQuestions is not a valid array of QuizQuestions
108143 * Updates the active section's `questions` property with the given newQuestions, and clears
109- * _selectedQuestions from it. Then it resets _selectedQuestions to an empty array */
144+ * _selectedQuestionIds from it. Then it resets _selectedQuestionIds to an empty array */
110145 // TODO WRITE THIS FUNCTION
111146 function replaceSelectedQuestions ( newQuestions ) {
112147 return newQuestions ;
@@ -162,8 +197,12 @@ export default () => {
162197 * use */
163198 function initializeQuiz ( ) {
164199 set ( _quiz , objectWithDefaults ( { } , Quiz ) ) ;
165- const newSection = addSection ( ) ;
166- setActiveSection ( newSection . section_id ) ;
200+ if ( DEBUG ) {
201+ _generateTestData ( ) ;
202+ } else {
203+ const newSection = addSection ( ) ;
204+ setActiveSection ( newSection . section_id ) ;
205+ }
167206 _fetchChannels ( ) ;
168207 }
169208
@@ -195,21 +234,41 @@ export default () => {
195234 // --------------------------------
196235
197236 /** @param {QuizQuestion } question
198- * @affects _selectedQuestions - Adds question to _selectedQuestions if it isn't there already */
237+ * @affects _selectedQuestionIds - Adds question to _selectedQuestionIds if it isn't
238+ * there already */
199239 function addQuestionToSelection ( question_id ) {
200- set ( _selectedQuestions , uniq ( [ ...get ( _selectedQuestions ) , question_id ] ) ) ;
240+ set ( _selectedQuestionIds , uniq ( [ ...get ( _selectedQuestionIds ) , question_id ] ) ) ;
201241 }
202242
203243 /**
204244 * @param {QuizQuestion } question
205- * @affects _selectedQuestions - Removes question from _selectedQuestions if it is there */
245+ * @affects _selectedQuestionIds - Removes question from _selectedQuestionIds if it is there */
206246 function removeQuestionFromSelection ( question_id ) {
207247 set (
208- _selectedQuestions ,
209- get ( _selectedQuestions ) . filter ( id => id !== question_id )
248+ _selectedQuestionIds ,
249+ get ( _selectedQuestionIds ) . filter ( id => id !== question_id )
210250 ) ;
211251 }
212252
253+ function toggleQuestionInSelection ( question_id ) {
254+ if ( get ( _selectedQuestionIds ) . includes ( question_id ) ) {
255+ removeQuestionFromSelection ( question_id ) ;
256+ } else {
257+ addQuestionToSelection ( question_id ) ;
258+ }
259+ }
260+
261+ function selectAllQuestions ( ) {
262+ if ( get ( allQuestionsSelected ) ) {
263+ set ( _selectedQuestionIds , [ ] ) ;
264+ } else {
265+ set (
266+ _selectedQuestionIds ,
267+ get ( activeQuestions ) . map ( q => q . question_id )
268+ ) ;
269+ }
270+ }
271+
213272 /**
214273 * @affects _channels - Fetches all channels with exercises and sets them to _channels */
215274 function _fetchChannels ( ) {
@@ -271,15 +330,56 @@ export default () => {
271330 /** @type {ComputedRef<QuizQuestion[]> } All questions in the active section's `questions` property
272331 * those which are currently set to be used in the section */
273332 const activeQuestions = computed ( ( ) => get ( activeSection ) . questions ) ;
274- /** @type {ComputedRef<QuizQuestion[]> } All questions the user has selected for the active
275- * section */
276- const selectedActiveQuestions = computed ( ( ) => get ( _selectedQuestions ) ) ;
333+ /** @type {ComputedRef<String[]> } All question_ids the user has selected for the active section */
334+ const selectedActiveQuestions = computed ( ( ) => get ( _selectedQuestionIds ) ) ;
277335 /** @type {ComputedRef<QuizQuestion[]> } Questions in the active section's `resource_pool` that
278336 * are not in `questions` */
279337 const replacementQuestionPool = computed ( ( ) => { } ) ;
280338 /** @type {ComputedRef<Array> } A list of all channels available which have exercises */
281339 const channels = computed ( ( ) => get ( _channels ) ) ;
282340
341+ /** Handling the Select All Checkbox
342+ * See: remove/toggleQuestionFromSelection() & selectAllQuestions() for more */
343+
344+ /** @type {ComputedRef<Boolean> } Whether all active questions are selected */
345+ const allQuestionsSelected = computed ( ( ) => {
346+ return isEqual (
347+ get ( selectedActiveQuestions ) . sort ( ) ,
348+ get ( activeQuestions )
349+ . map ( q => q . question_id )
350+ . sort ( )
351+ ) ;
352+ } ) ;
353+
354+ /**
355+ * Deletes and clears the selected questions from the active section
356+ */
357+ function deleteActiveSelectedQuestions ( ) {
358+ const { section_id, questions } = get ( activeSection ) ;
359+ const selectedIds = get ( selectedActiveQuestions ) ;
360+ const newQuestions = questions . filter ( q => ! selectedIds . includes ( q . question_id ) ) ;
361+ updateSection ( { section_id, questions : newQuestions } ) ;
362+ set ( _selectedQuestionIds , [ ] ) ;
363+ }
364+
365+ const noQuestionsSelected = computed ( ( ) => get ( selectedActiveQuestions ) . length === 0 ) ;
366+ /** @type {ComputedRef<String> } The label that should be shown alongside the "Select all" checkbox
367+ */
368+ const selectAllLabel = computed ( ( ) => {
369+ if ( get ( noQuestionsSelected ) ) {
370+ const { selectAllLabel$ } = enhancedQuizManagementStrings ;
371+ return selectAllLabel$ ( ) ;
372+ } else {
373+ const { numberOfSelectedQuestions$ } = enhancedQuizManagementStrings ;
374+ return numberOfSelectedQuestions$ ( { count : get ( selectedActiveQuestions ) . length } ) ;
375+ }
376+ } ) ;
377+
378+ /** @type {ComputedRef<Boolean> } Whether the select all checkbox should be indeterminate */
379+ const selectAllIsIndeterminate = computed ( ( ) => {
380+ return ! get ( allQuestionsSelected ) && ! get ( noQuestionsSelected ) ;
381+ } ) ;
382+
283383 return {
284384 // Methods
285385 saveQuiz,
@@ -290,8 +390,11 @@ export default () => {
290390 setActiveSection,
291391 initializeQuiz,
292392 updateQuiz,
393+ deleteActiveSelectedQuestions,
293394 addQuestionToSelection,
294395 removeQuestionFromSelection,
396+ toggleQuestionInSelection,
397+ selectAllQuestions,
295398
296399 // Computed
297400 channels,
@@ -304,5 +407,9 @@ export default () => {
304407 activeQuestions,
305408 selectedActiveQuestions,
306409 replacementQuestionPool,
410+ selectAllIsIndeterminate,
411+ selectAllLabel,
412+ allQuestionsSelected,
413+ noQuestionsSelected,
307414 } ;
308415} ;
0 commit comments