diff --git a/src/components/creator/creator-app.js b/src/components/creator/creator-app.js index a02a476..9300ebd 100644 --- a/src/components/creator/creator-app.js +++ b/src/components/creator/creator-app.js @@ -9,6 +9,7 @@ import CreatorTutorial from './creator-tutorial'; import CreatorHintsModal from './creator-hints-modal'; import CreatorFakeoutModal from './creator-fakeout-modal' import CreatorBankModal from './creator-bank-modal' +import CreatorSubmissionSettingsModal from './creator-submission-settings-modal' import CreatorErrorModal from './creator-error-modal' const CreatorApp = (props) => { @@ -127,6 +128,7 @@ const CreatorApp = (props) => { options: { legend: global.state.legend, enableQuestionBank: global.state.enableQuestionBank, + requireAllQuestions: global.state.requireAllQuestions, numAsk: global.state.numAsk } } @@ -163,6 +165,10 @@ const CreatorApp = (props) => { dispatch({type: 'toggle_bank_modal', payload: {}}) } + const toggleSubmissionSettings = () => { + dispatch({type: 'toggle_submission_settings_modal', payload: {}}) + } + const toggleHintModal = () => { dispatch({type: 'toggle_hint_modal'}) } @@ -184,14 +190,17 @@ const CreatorApp = (props) => { enableQuestionBank={global.state.enableQuestionBank} numAsk={global.state.numAsk} questionCount={global.state.items.length}> + - +
+
diff --git a/src/components/creator/creator-submission-settings-modal.js b/src/components/creator/creator-submission-settings-modal.js new file mode 100644 index 0000000..ea1629f --- /dev/null +++ b/src/components/creator/creator-submission-settings-modal.js @@ -0,0 +1,45 @@ +import React, {useContext} from 'react' +import { store } from '../../creator-store' + +const CreatorSubmissionSettingsModal = (props) => { + + const global = useContext(store) + const dispatch = global.dispatch + + const handleToggleSubmissionSettings = (event) => { + dispatch({type:'toggle_require_all_questions', payload: event.target.value == 'true' ? true : false}) + } + + const dismiss = () => { + dispatch({type: 'toggle_submission_settings_modal'}) + } + + return ( +
+
+

Submission Settings

+ + Require all questions? + + + + Yes + + + + + No + + + + If enabled, students must respond to all questions before submission by sorting at least one token per question. Empty responses are not allowed. + + + +
+
+
+ ) +} + +export default CreatorSubmissionSettingsModal diff --git a/src/components/player/player-app.js b/src/components/player/player-app.js index 985d56b..9efbd8b 100644 --- a/src/components/player/player-app.js +++ b/src/components/player/player-app.js @@ -40,12 +40,15 @@ const PlayerApp = (props) => { const emptyQuestionCheck = () => { let isEmpty = false + let i = 0; for (let item of global.state.items) { - if (item.sorted.length <= 0) { + if (item.sorted.length <= 0) { + dispatch({type: 'select_question', payload: i}) isEmpty = true break } + i++ } return isEmpty } @@ -81,7 +84,9 @@ const PlayerApp = (props) => { return(
- +
{global.state.title} @@ -94,7 +99,7 @@ const PlayerApp = (props) => {

{questionText}

0 && + global.state.items[global.state.currentIndex]?.attemptsUsed > 0 && global.state.items[global.state.currentIndex]?.attemptsUsed < global.state.items[global.state.currentIndex]?.attempts && global.state.items[global.state.currentIndex]?.responseState != 'correct' && global.state.items[global.state.currentIndex]?.responseState != 'incorrect-no-attempts' && diff --git a/src/components/player/question-select.js b/src/components/player/question-select.js index f61c5ae..c4d1520 100644 --- a/src/components/player/question-select.js +++ b/src/components/player/question-select.js @@ -5,7 +5,7 @@ const QuestionSelect = (props) => { const global = useContext(store) const dispatch = global.dispatch - + const [state, setState] = useState({paginateMin: 0, paginateMax: 8, visibleQuestions: []}) const currentIndex = global.state.currentIndex @@ -14,7 +14,7 @@ const QuestionSelect = (props) => { let questionList = global.state.items.map((item, index) => { return }) - + // if the list of questions gets too long, we have to start computing the subset to display if (questionList.length > 10) { if (currentIndex < state.paginateMin) { @@ -23,7 +23,7 @@ const QuestionSelect = (props) => { else if (currentIndex > state.paginateMax) { setState(state => ({...state, paginateMin: currentIndex - 8, paginateMax: currentIndex})) } - + setState(state => ({...state, visibleQuestions: [ ...questionList.slice(state.paginateMin, currentIndex), ...questionList.slice(currentIndex, state.paginateMax + 1) @@ -37,7 +37,7 @@ const QuestionSelect = (props) => { const selectQuestion = (index) => { dispatch({type: 'select_question', payload: index}) } - + return (
@@ -47,4 +47,4 @@ const QuestionSelect = (props) => { ) } -export default QuestionSelect \ No newline at end of file +export default QuestionSelect diff --git a/src/components/player/warning-modal.js b/src/components/player/warning-modal.js index 729b2aa..02ed1a6 100644 --- a/src/components/player/warning-modal.js +++ b/src/components/player/warning-modal.js @@ -2,7 +2,6 @@ import React, {useContext} from 'react' import { store } from '../../player-store' const WarningModal = (props) => { - const global = useContext(store) const dispatch = global.dispatch @@ -14,11 +13,22 @@ const WarningModal = (props) => {
You still have unfinished questions. -

Are you sure you want to submit?

-
- - -
+ {props.requireAllQuestions ? +
+

Please answer all questions before submitting.

+
+ +
+
+ : +
+

Are you sure you want to submit?

+
+ + +
+
+ }
diff --git a/src/creator-store.js b/src/creator-store.js index 4522cae..0ea78ef 100644 --- a/src/creator-store.js +++ b/src/creator-store.js @@ -10,6 +10,7 @@ const init = { showHintModal: false, showFakeoutModal: false, showBankModal: false, + showSubmissionSettingsModal: false, showErrorModal: false, errors: [], selectedTokenIndex: -1, @@ -33,6 +34,7 @@ const init = { ], numAsk: 1, enableQuestionBank: false, + requireAllQuestions: false, showLegend: false, legendColorPickerTarget: -1, onboarding: true, @@ -63,7 +65,8 @@ const importFromQset = (qset) => { items: items, legend: qset.options.legend, numAsk: qset.options.numAsk, - enableQuestionBank: qset.options.enableQuestionBank + enableQuestionBank: qset.options.enableQuestionBank, + requireAllQuestions: qset.options.requireAllQuestions ? qset.options.requireAllQuestions : false // this value will not exist for older qsets } } @@ -134,7 +137,7 @@ const questionItemReducer = (items, action) => { ...item, attempts: parseInt(action.payload.pref) } - + } else return item }) @@ -288,7 +291,7 @@ const StateProvider = ( { children } ) => { return {...state, requireInit: false} case 'init-existing': let imported = importFromQset(action.payload.qset) - return {...state, title: action.payload.title, items: imported.items, legend: imported.legend, numAsk: imported.numAsk, enableQuestionBank: imported.enableQuestionBank, requireInit: false, onboarding: false, showTokenTutorial: false} + return {...state, title: action.payload.title, items: imported.items, legend: imported.legend, numAsk: imported.numAsk, enableQuestionBank: imported.enableQuestionBank, requireAllQuestions: imported.requireAllQuestions, requireInit: false, onboarding: false, showTokenTutorial: false} case 'dismiss_tutorial': return {...state, showTutorial: false} case 'toggle_token_tutorial': @@ -309,7 +312,7 @@ const StateProvider = ( { children } ) => { case 'phrase_input_to_token': return {...state, items: questionItemReducer(state.items, action), showTokenTutorial: false} case 'phrase_token_to_input': - return {...state, items: questionItemReducer(state.items, action), selectedTokenIndex: action.payload.phraseIndex == state.selectedTokenIndex ? -1 : state.selectedTokenIndex } + return {...state, items: questionItemReducer(state.items, action), selectedTokenIndex: action.payload.phraseIndex == state.selectedTokenIndex ? -1 : state.selectedTokenIndex } case 'fakeout_token_to_input': return {...state, items: questionItemReducer(state.items, action), selectedFakeoutIndex: action.payload.fakeoutIndex == state.selectedFakeoutIndex ? -1 : state.selectedFakeoutIndex } case 'phrase_token_type_select': @@ -341,12 +344,16 @@ const StateProvider = ( { children } ) => { return {...state, showFakeoutModal: !state.showFakeoutModal} case 'toggle_bank_modal': return {...state, showBankModal: !state.showBankModal} + case 'toggle_submission_settings_modal': + return {...state, showSubmissionSettingsModal: !state.showSubmissionSettingsModal} case 'toggle_error_modal': return {...state, errors: action.payload.error, showErrorModal: !state.showErrorModal} case 'update_num_ask': return {...state, numAsk: action.payload} case 'toggle_ask_limit': return {...state, enableQuestionBank: action.payload} + case 'toggle_require_all_questions': + return {...state, requireAllQuestions: action.payload} default: throw new Error('Base reducer: this action type was not defined') } diff --git a/src/creator.scss b/src/creator.scss index de61c77..7fe9edc 100644 --- a/src/creator.scss +++ b/src/creator.scss @@ -74,7 +74,8 @@ div.startupTooltip { } .toggleLegend, - .toggleBank { + .toggleBank, + .toggleSubmissionSettings { float: right; padding: 8px 28px; outline: none; @@ -85,7 +86,7 @@ div.startupTooltip { border: solid 1px $colorAccentHover; font-weight: bold; - &.toggleBank { + &.toggleBank, &.toggleSubmissionSettings { margin-right: 10px; } diff --git a/src/demo.json b/src/demo.json index c3b4125..9d347be 100644 --- a/src/demo.json +++ b/src/demo.json @@ -293,6 +293,7 @@ } ], "enableQuestionBank": false, + "requireAllQuestions": true, "numAsk": 1 } } diff --git a/src/player-store.js b/src/player-store.js index a1cdc88..ba71557 100644 --- a/src/player-store.js +++ b/src/player-store.js @@ -9,7 +9,8 @@ const init = { showTutorial: true, showWarning: false, numAsk: 1, - enableQuestionBank: "no", + enableQuestionBank: false, + requireAllQuestions: false, questionsAsked: [] } @@ -44,7 +45,7 @@ const importFromQset = (qset) => { let fakes = item.options.fakes.map((token) => { return {...token, status: 'unsorted', fakeout: true, id: createTokenKey()}}) let reals = item.answers[0].options.phrase.map((token) => { return {...token, status: 'unsorted', fakeout: false, id: createTokenKey()} }) - + return { question: item.questions[0].text, answer: item.answers[0].text, @@ -63,6 +64,7 @@ const importFromQset = (qset) => { legend: qset.options.legend, numAsk: qset.options.numAsk, enableQuestionBank: qset.options.enableQuestionBank, + requireAllQuestions: qset.options.requireAllQuestions ? qset.options.requireAllQuestions : false, questionsAsked: [] } } @@ -84,9 +86,9 @@ const calcResponseState = (item) => { state = 'pending' } else state = 'ready' - } + } break - + case 'pending': if (item.fakeout.length == 0 && item.phrase.length == 0) { state = 'ready' @@ -142,7 +144,7 @@ const tokenSortedPhraseReducer = (list, action) => { ...list.slice(0, action.payload.tokenIndex), ...list.slice(action.payload.tokenIndex + 1) ] - + return sorted.map((token) => ({ ...token, reqPositionUpdate: true @@ -179,13 +181,13 @@ const tokenSortedPhraseReducer = (list, action) => { }, ...list.slice(action.payload.targetIndex) ] - + return sorted.map((token) => ({ ...token, reqPositionUpdate: true }) ) - } + } case 'response_token_rearrange': let target = action.payload.targetIndex if (action.payload.originIndex < target) target--