Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion frontend/components/quizzes/questions_ui.html
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ <h6 class="card-subtitle mb-2 text-muted">
<div class="col" data-bind="markdowned: {value: $data}"></div>
<div class="col">
<select class="custom-select"
data-bind="options: $parent.retainOrder ? $parent.answers : $parent.answers.sort(() => Math.random() - 0.5),
data-bind="options: $parent.retainOrder ? $parent.answers : [...$parent.answers].sort(() => Math.random() - 0.5),
disable: $component.isReadOnly(),
optionsCaption: '',
value: $parent.student[$index()],
Expand Down
611 changes: 611 additions & 0 deletions frontend/components/quizzes/quiz_editor_state.ts

Large diffs are not rendered by default.

490 changes: 490 additions & 0 deletions frontend/components/quizzes/quiz_editor_ui.html

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion frontend/components/quizzes/quiz_ui.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import QUESTIONS_SUBMISSION_UI from "./questions_ui.html"
import QUIZ_EDITOR_UI from "./quiz_editor_ui.html"

export const QUIZ_PREVIEW = `
<div data-bind="switch: quiz()?.attemptStatus()">
Expand Down Expand Up @@ -242,7 +243,7 @@ export const QUIZZER_HTML = `
<!-- /ko -->

<!-- ko if: editorMode() === 'QUIZ_EDITOR' -->
Quiz Editor is not yet ready.
${QUIZ_EDITOR_UI}
<!-- /ko -->

${INSTRUCTIONS_BAR_HTML('above')}
Expand Down
40 changes: 36 additions & 4 deletions frontend/components/quizzes/quizzer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {Quiz, QuizMode} from './quiz';
import {Question, subscribeToStudent} from './questions';
import "./quizzer_question_status";
import {QUIZZER_HTML} from './quiz_ui';
import {QuizEditorState} from './quiz_editor_state';

// Maybe TODO: Add bookmarking
// Add a question mark button that let's them flag this to return to later
Expand Down Expand Up @@ -49,20 +50,25 @@ export class Quizzer extends AssignmentInterface {

errorMessage: ko.Observable<string>;

/** Visual quiz editor state; populated when editorMode switches to QUIZ_EDITOR. */
quizEditor: ko.Observable<QuizEditorState>;

subscriptions: {
quiz: ko.Subscription
currentAssignmentId: ko.Subscription
questions: ko.Subscription[]
editorMode: ko.Subscription
}

visibleQuestions: ko.PureComputed<Question[]>;


constructor(params: AssignmentInterfaceJson) {
super(params);
this.subscriptions = {quiz: null, currentAssignmentId: null, questions: null};
this.subscriptions = {quiz: null, currentAssignmentId: null, questions: null, editorMode: null};

this.quiz = ko.observable(null);
this.quizEditor = ko.observable(null);

// UI state
this.isDirty = ko.observable(false);
Expand All @@ -77,15 +83,25 @@ export class Quizzer extends AssignmentInterface {

this.subscriptions.questions = [] as ko.Subscription[];
this.subscriptions.quiz = this.quiz.subscribe((quiz) => {
this.quiz().questions().map((question: Question) => {
quiz.questions().map((question: Question) => {
subscribeToStudent(question).map((subscribable) => {
let subscription = subscribable.subscribe((value: any) => {
this.onChange();
});
this.subscriptions.questions.push(subscription);
})
});
this.quiz().hidePools();
quiz.hidePools();
});

// Rebuild the quiz editor state whenever the editor mode switches to QUIZ_EDITOR
this.subscriptions.editorMode = this.editorMode.subscribe((mode) => {
if (mode === 'QUIZ_EDITOR' && this.assignment()) {
this.quizEditor(new QuizEditorState(
this.assignment().instructions(),
this.assignment().onRun()
));
}
});

// this.visibleQuestions = ko.pureComputed<Question[]>( () => {
Expand All @@ -94,7 +110,7 @@ export class Quizzer extends AssignmentInterface {
// }, this);

this.isReadOnly = ko.pureComputed<boolean>(() => {
return !this.quiz().attempting();
return this.quiz() ? !this.quiz().attempting() : true;
}, this);
}

Expand All @@ -103,6 +119,9 @@ export class Quizzer extends AssignmentInterface {
this.subscriptions.currentAssignmentId.dispose();
this.subscriptions.quiz.dispose();
this.subscriptions.questions.map((question: ko.Subscription) => question.dispose());
if (this.subscriptions.editorMode) {
this.subscriptions.editorMode.dispose();
}
}

lookupReading(readingUrl: string): Promise<number> {
Expand Down Expand Up @@ -204,6 +223,19 @@ export class Quizzer extends AssignmentInterface {
});
}

/**
* Called by the "Save Quiz" button in the visual Quiz Editor.
* Serialises the editor state back to the instructions and on_run JSON
* and persists them via saveAssignment().
*/
saveQuizEditor() {
if (!this.quizEditor()) { return; }
const editor = this.quizEditor();
this.assignment().instructions(editor.toInstructionsJson());
this.assignment().onRun(editor.toChecksJson());
this.saveAssignment();
}

submit() {
let BlockPyServer = window['$MAIN_BLOCKPY_EDITOR'].components.server;
let now = new Date();
Expand Down
6 changes: 3 additions & 3 deletions frontend/components/quizzes/quizzer_question_status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,15 @@ export interface QuizzerQuestionStatusJson {
status: ko.Observable<string>[];
asStudent: ko.Observable<boolean>;
question: Question;
quiz: ko.Observable<Quiz>;
quiz: Quiz;
isAnchor: boolean;
indexId: number
}

export class QuizzerQuestionStatus {
private status: ko.Observable<string>[];
private asStudent: ko.Observable<boolean>;
private quiz: ko.Observable<Quiz>;
private quiz: Quiz;
private question: Question;
private isAnchor: boolean;
private indexId: number;
Expand All @@ -60,7 +60,7 @@ export class QuizzerQuestionStatus {
const graded = this.question && this.question.feedback();
const errored = graded && this.question.feedback().status === "error";
const correct = graded && this.question.feedback().correct;
if (graded && (!this.asStudent() || this.quiz().feedbackType() === QuizFeedbackType.IMMEDIATE)) {
if (graded && (!this.asStudent() || this.quiz.feedbackType() === QuizFeedbackType.IMMEDIATE)) {
if (errored) {
return 'error';
} else if (correct) {
Expand Down
Loading