Built client interface
This commit is contained in:
parent
1088dc76e4
commit
1e953fe21c
@ -74,23 +74,29 @@ def generate_questions(dataset:dict):
|
|||||||
for block in randomise_list(questions_list):
|
for block in randomise_list(questions_list):
|
||||||
if block['type'] == 'question':
|
if block['type'] == 'question':
|
||||||
question = {
|
question = {
|
||||||
'q_type': 'question',
|
'type': 'question',
|
||||||
'q_no': block['q_no'],
|
'q_no': block['q_no'],
|
||||||
'question_header': '',
|
'question_header': '',
|
||||||
'text': block['text'],
|
'text': block['text']
|
||||||
'options': randomise_list(block['options'])
|
|
||||||
}
|
}
|
||||||
|
if block['q_type'] == 'Multiple Choice':
|
||||||
|
question['options'] = randomise_list(block['options'])
|
||||||
|
else:
|
||||||
|
question['options'] = block['options'].copy()
|
||||||
output.append(question)
|
output.append(question)
|
||||||
if block['type'] == 'block':
|
if block['type'] == 'block':
|
||||||
for key, _question in enumerate(randomise_list(block['questions'])):
|
for key, _question in enumerate(randomise_list(block['questions'])):
|
||||||
question = {
|
question = {
|
||||||
'q_type': 'block',
|
'type': 'block',
|
||||||
'q_no': _question['q_no'],
|
'q_no': _question['q_no'],
|
||||||
'question_header': block['question_header'] if 'question_header' in block else '',
|
'question_header': block['question_header'] if 'question_header' in block else '',
|
||||||
'block_length': len(block['questions']),
|
'block_length': len(block['questions']),
|
||||||
'block_q_no': key,
|
'block_q_no': key,
|
||||||
'text': _question['text'],
|
'text': _question['text']
|
||||||
'options': randomise_list(_question['options'])
|
|
||||||
}
|
}
|
||||||
|
if _question['q_type'] == 'Multiple Choice':
|
||||||
|
question['options'] = randomise_list(_question['options'])
|
||||||
|
else:
|
||||||
|
question['options'] = _question['options'].copy()
|
||||||
output.append(question)
|
output.append(question)
|
||||||
return output
|
return output
|
145
ref-test/quiz/static/css/quiz.css
Normal file
145
ref-test/quiz/static/css/quiz.css
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
/*! Generated by Font Squirrel (https://www.fontsquirrel.com) on November 23, 2021 */
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'opendyslexic3bold';
|
||||||
|
src: url('../fonts/opendyslexic3-bold-webfont.woff2') format('woff2'),
|
||||||
|
url('../fonts/opendyslexic3-bold-webfont.woff') format('woff');
|
||||||
|
font-weight: normal;
|
||||||
|
font-style: normal;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'opendyslexic3regular';
|
||||||
|
src: url('../fonts/opendyslexic3-regular-webfont.woff2') format('woff2'),
|
||||||
|
url('../fonts/opendyslexic3-regular-webfont.woff') format('woff');
|
||||||
|
font-weight: normal;
|
||||||
|
font-style: normal;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'opendyslexicmonoregular';
|
||||||
|
src: url('../fonts/opendyslexicmono-regular-webfont.woff2') format('woff2'),
|
||||||
|
url('../fonts/opendyslexicmono-regular-webfont.woff') format('woff');
|
||||||
|
font-weight: normal;
|
||||||
|
font-style: normal;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Class Definitions */
|
||||||
|
|
||||||
|
.form-quiz-configure {
|
||||||
|
width: 100%;
|
||||||
|
padding: 15px;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.q-f-opendyslexic {
|
||||||
|
font-family: 'opendyslexic3bold';
|
||||||
|
}
|
||||||
|
|
||||||
|
.q-f-comicsans {
|
||||||
|
font-family: 'Comic Sans MS', 'Comic Sans';
|
||||||
|
}
|
||||||
|
|
||||||
|
.q-f-osdefault {
|
||||||
|
font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.q-f-verdana {
|
||||||
|
font-family: Verdana, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.q-f-tahoma {
|
||||||
|
font-family: Tahoma, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.q-f-arial {
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.q-f-12pt {
|
||||||
|
font-size: 12pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
.q-f-14pt {
|
||||||
|
font-size: 14pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
.q-f-16pt {
|
||||||
|
font-size: 16pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
.q-f-18pt {
|
||||||
|
font-size: 18pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
.q-settings-element {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.q-bg-light-1 {
|
||||||
|
background-color: beige;
|
||||||
|
}
|
||||||
|
|
||||||
|
.q-bg-light-2 {
|
||||||
|
background-color: #EBE3E1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sample-question {
|
||||||
|
margin: 2rem auto;
|
||||||
|
padding: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.question-container {
|
||||||
|
margin: 2rem auto;
|
||||||
|
padding: 2 rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.question-title {
|
||||||
|
margin: 2rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.question-header {
|
||||||
|
margin: 1rem auto 3rem;
|
||||||
|
width: 90%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-quiz-control {
|
||||||
|
width: fit-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
#q-topbar a.btn {
|
||||||
|
padding: 2px 6px 0px 6px;
|
||||||
|
font-size: 14pt;
|
||||||
|
height: fit-content;
|
||||||
|
width: fit-content;
|
||||||
|
margin: 0px 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.q-timer {
|
||||||
|
padding-top: 0px;
|
||||||
|
margin: 0px auto;
|
||||||
|
font-size: 16pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
.q-navigator-button {
|
||||||
|
margin: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-button-container {
|
||||||
|
width: fit-content;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Layout for Mobile Devices */
|
||||||
|
@media only screen and (max-width: 576px) {
|
||||||
|
body {
|
||||||
|
padding-top: 140px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar .container {
|
||||||
|
justify-content: space-evenly;
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,3 @@
|
|||||||
.bg-light {
|
|
||||||
background-color: #EBE3E1!important;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
body {
|
||||||
padding: 80px 0;
|
padding: 80px 0;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
@ -111,7 +107,7 @@ body {
|
|||||||
color: #777;
|
color: #777;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-check {
|
.form-check-margin {
|
||||||
margin-bottom: 2rem;
|
margin-bottom: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -137,33 +133,17 @@ body {
|
|||||||
margin: 0 2px;
|
margin: 0 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*! Generated by Font Squirrel (https://www.fontsquirrel.com) on November 23, 2021 */
|
/* Change Autocomplete styles in Chrome*/
|
||||||
|
input:-webkit-autofill,
|
||||||
@font-face {
|
input:-webkit-autofill:hover,
|
||||||
font-family: 'opendyslexic3bold';
|
input:-webkit-autofill:focus,
|
||||||
src: url('../fonts/opendyslexic3-bold-webfont.woff2') format('woff2'),
|
textarea:-webkit-autofill,
|
||||||
url('../fonts/opendyslexic3-bold-webfont.woff') format('woff');
|
textarea:-webkit-autofill:hover,
|
||||||
font-weight: normal;
|
textarea:-webkit-autofill:focus,
|
||||||
font-style: normal;
|
select:-webkit-autofill,
|
||||||
|
select:-webkit-autofill:hover,
|
||||||
}
|
select:-webkit-autofill:focus {
|
||||||
|
transition: background-color 5000s ease-in-out 0s;
|
||||||
@font-face {
|
|
||||||
font-family: 'opendyslexic3regular';
|
|
||||||
src: url('../fonts/opendyslexic3-regular-webfont.woff2') format('woff2'),
|
|
||||||
url('../fonts/opendyslexic3-regular-webfont.woff') format('woff');
|
|
||||||
font-weight: normal;
|
|
||||||
font-style: normal;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'opendyslexicmonoregular';
|
|
||||||
src: url('../fonts/opendyslexicmono-regular-webfont.woff2') format('woff2'),
|
|
||||||
url('../fonts/opendyslexicmono-regular-webfont.woff') format('woff');
|
|
||||||
font-weight: normal;
|
|
||||||
font-style: normal;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Fallback for Edge
|
/* Fallback for Edge
|
||||||
|
302
ref-test/quiz/static/js/quiz.js
Normal file
302
ref-test/quiz/static/js/quiz.js
Normal file
@ -0,0 +1,302 @@
|
|||||||
|
// Click Listeners
|
||||||
|
|
||||||
|
$("input[name='font-select']").change(function(){
|
||||||
|
let $choice = $(this).val();
|
||||||
|
set_font($choice);
|
||||||
|
});
|
||||||
|
|
||||||
|
$("input[name='font-size']").change(function(){
|
||||||
|
let $choice = $(this).val();
|
||||||
|
set_font_size($choice);
|
||||||
|
});
|
||||||
|
|
||||||
|
$("input[name='bg-select']").change(function(){
|
||||||
|
let $choice = $(this).val();
|
||||||
|
set_bg_colour($choice);
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#btn-toggle-navigator").click(function(event){
|
||||||
|
if ($quiz_navigator.is(":hidden")) {
|
||||||
|
if ($quiz_settings.is(":visible")) {
|
||||||
|
toggle_settings = true;
|
||||||
|
$quiz_settings.hide();
|
||||||
|
}
|
||||||
|
$quiz_render.hide();
|
||||||
|
$quiz_navigator.show();
|
||||||
|
toggle_navigator = false;
|
||||||
|
} else {
|
||||||
|
$quiz_navigator.hide();
|
||||||
|
if (toggle_settings) {
|
||||||
|
$quiz_settings.show();
|
||||||
|
toggle_settings = false;
|
||||||
|
} else {
|
||||||
|
$quiz_render.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
event.preventDefault();
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#btn-toggle-settings").click(function(event){
|
||||||
|
if (($quiz_settings).is(":hidden")) {
|
||||||
|
if ($quiz_navigator.is(":visible")) {
|
||||||
|
toggle_navigator = true;
|
||||||
|
$quiz_navigator.hide();
|
||||||
|
}
|
||||||
|
$quiz_render.hide();
|
||||||
|
$quiz_settings.show();
|
||||||
|
toggle_settings = false;
|
||||||
|
} else {
|
||||||
|
$quiz_settings.hide();
|
||||||
|
if (toggle_navigator) {
|
||||||
|
$quiz_navigator.show();
|
||||||
|
toggle_navigator = false;
|
||||||
|
} else {
|
||||||
|
$quiz_render.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
event.preventDefault();
|
||||||
|
});
|
||||||
|
|
||||||
|
$(".btn-dummy").click(function(event){
|
||||||
|
event.preventDefault();
|
||||||
|
});
|
||||||
|
|
||||||
|
$(".q-question-nav").click(function(event){
|
||||||
|
check_answered();
|
||||||
|
if ($(this).attr("id") == "q-nav-next") {
|
||||||
|
if (current_question < questions.length) {
|
||||||
|
current_question ++;
|
||||||
|
}
|
||||||
|
} else if ($(this).attr("id") == "q-nav-prev") {
|
||||||
|
if (current_question > 0) {
|
||||||
|
current_question --;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
render_question();
|
||||||
|
check_flag();
|
||||||
|
event.preventDefault();
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#q-nav-flag").click(function(event){
|
||||||
|
if (question_status[current_question] != 1) {
|
||||||
|
question_status[current_question] = 1;
|
||||||
|
$(this).removeClass().addClass("btn btn-warning");
|
||||||
|
} else {
|
||||||
|
question_status[current_question] = 0;
|
||||||
|
$(this).removeClass().addClass("btn btn-secondary");
|
||||||
|
}
|
||||||
|
event.preventDefault();
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#btn-start-quiz").click(function(event){
|
||||||
|
$(this).hide();
|
||||||
|
$(".btn-quiz-return").show();
|
||||||
|
$(".quiz-console").show();
|
||||||
|
$("#quiz-settings").hide();
|
||||||
|
$("#quiz-navigator").hide();
|
||||||
|
$(".quiz-start-text").hide();
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: `/api/questions/`,
|
||||||
|
type: 'POST',
|
||||||
|
data: JSON.stringify({'_id': _id}),
|
||||||
|
contentType: "application/json",
|
||||||
|
success: function(response) {
|
||||||
|
time_limit_data = response.time_limit;
|
||||||
|
questions = response.questions;
|
||||||
|
total_questions = questions.length;
|
||||||
|
window.localStorage.setItem('questions', JSON.stringify(questions));
|
||||||
|
render_question();
|
||||||
|
check_flag();
|
||||||
|
},
|
||||||
|
error: function(response) {
|
||||||
|
console.log(response);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Functions
|
||||||
|
|
||||||
|
function set_font(value = 'osdefault') {
|
||||||
|
let font_styles = ['arial', 'comicsans', 'opendyslexic', 'tahoma', 'verdana']
|
||||||
|
|
||||||
|
for (let i = 0; i < font_styles.length; i ++) {
|
||||||
|
if (font_styles[i] != value) {
|
||||||
|
$("body").removeClass( `q-f-${font_styles[i]}` );
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
if (value != 'osdefault') {
|
||||||
|
$("body").addClass(`q-f-${value}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
display_settings['font-select'] = value;
|
||||||
|
window.localStorage.setItem('display_settings', JSON.stringify(display_settings));
|
||||||
|
$('input[name="font-select"][value="' + value + '"]').prop('checked', true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_font_size(value = '14pt') {
|
||||||
|
let font_sizes = ['12pt', '16pt', '18pt']
|
||||||
|
|
||||||
|
for (let i = 0; i < font_sizes.length; i ++) {
|
||||||
|
if (font_sizes[i] != value) {
|
||||||
|
$("body").removeClass( `q-f-${font_sizes[i]}` );
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
if (value != '14pt') {
|
||||||
|
$("body").addClass(`q-f-${value}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
display_settings['font-size'] = value;
|
||||||
|
window.localStorage.setItem('display_settings', JSON.stringify(display_settings));
|
||||||
|
$('input[name="font-size"][value="' + value + '"]').prop('checked', true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_bg_colour(value = 'bg-light') {
|
||||||
|
let backgrounds = ['bg-light', 'q-bg-light-1', 'q-bg-light-2', 'alert-primary', 'alert-secondary', 'alert-dark', 'bg-dark']
|
||||||
|
|
||||||
|
for (let i = 0; i < backgrounds.length; i ++) {
|
||||||
|
if (backgrounds[i] != value) {
|
||||||
|
$("body").removeClass(backgrounds[i]);
|
||||||
|
if (backgrounds[i] == 'bg-dark') {
|
||||||
|
$("body").removeClass('text-light');
|
||||||
|
};
|
||||||
|
if (backgrounds[i] == 'alert-primary' || backgrounds[i] == 'alert-secondary' || backgrounds[i] == 'alert-dark') {
|
||||||
|
$("body").removeClass('text-dark');
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
$("body").addClass(value);
|
||||||
|
if (value == 'bg-dark') {
|
||||||
|
$("body").addClass('text-light');
|
||||||
|
};
|
||||||
|
if (value == 'alert-primary' || value == 'alert-secondary' || value == 'alert-dark') {
|
||||||
|
$("body").addClass('text-dark');
|
||||||
|
};
|
||||||
|
|
||||||
|
display_settings['bg-select'] = value;
|
||||||
|
window.localStorage.setItem('display_settings', JSON.stringify(display_settings));
|
||||||
|
$('input[name="bg-select"][value="' + value + '"]').prop('checked', true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_settings_from_storage() {
|
||||||
|
let display_settings = window.localStorage.getItem('display_settings')
|
||||||
|
if (display_settings != null) {
|
||||||
|
return JSON.parse(display_settings);
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
'font-select': 'osdefault',
|
||||||
|
'font-size': '14pt',
|
||||||
|
'bg-select': 'bg-light'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function apply_settings(settings) {
|
||||||
|
set_font(settings['font-select']);
|
||||||
|
set_font_size(settings['font-size']);
|
||||||
|
set_bg_colour(settings['bg-select']);
|
||||||
|
}
|
||||||
|
|
||||||
|
function render_question() {
|
||||||
|
if (current_question == 0) {
|
||||||
|
$nav_prev.addClass('disabled');
|
||||||
|
}
|
||||||
|
if (current_question == questions.length - 1) {
|
||||||
|
$nav_next.addClass('disabled');
|
||||||
|
}
|
||||||
|
if ($nav_prev.hasClass('disabled') && current_question > 0) {
|
||||||
|
$nav_prev.removeClass('disabled');
|
||||||
|
}
|
||||||
|
if ($nav_next.hasClass('disabled') && current_question < questions.length - 1) {
|
||||||
|
$nav_next.removeClass('disabled');
|
||||||
|
}
|
||||||
|
var question = questions[current_question];
|
||||||
|
let header_text = question.question_header;
|
||||||
|
var block_length = 0;
|
||||||
|
if ('block_length' in question) {
|
||||||
|
block_length = question['block_length'];
|
||||||
|
};
|
||||||
|
var block_q_no = 0;
|
||||||
|
if ('block_q_no' in question) {
|
||||||
|
block_q_no = question['block_q_no'];
|
||||||
|
}
|
||||||
|
header_text = header_text.replace('<block_remaining>', (block_length - block_q_no).toString());
|
||||||
|
$question_header.html(header_text);
|
||||||
|
$question_text.html(question.text);
|
||||||
|
$question_title.html(`Question ${current_question + 1} of ${ questions.length }.`);
|
||||||
|
|
||||||
|
var options = question.options;
|
||||||
|
var options_output = '';
|
||||||
|
for (let i = 0; i < options.length; i ++) {
|
||||||
|
options_output += `<div class="form-check">
|
||||||
|
<input type="radio" class="form-check-input quiz-option" id="q${current_question}-${i}" name="${question.q_no}" value="${options[i]}">
|
||||||
|
<label for="q${current_question}-${i}" class="form-check-label">${options[i]}</label>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
$question_options.html(options_output);
|
||||||
|
}
|
||||||
|
|
||||||
|
function check_answered() {
|
||||||
|
var question = questions[current_question];
|
||||||
|
var name = question.q_no;
|
||||||
|
if (!$(`input[name='${name}']:checked`).val() && question_status[current_question] == 0) {
|
||||||
|
question_status[current_question] = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function check_flag() {
|
||||||
|
if (!(current_question in question_status)) {
|
||||||
|
question_status[current_question] = 0;
|
||||||
|
}
|
||||||
|
switch (question_status[current_question]) {
|
||||||
|
case -1:
|
||||||
|
$nav_flag.removeClass().addClass('btn btn-danger');
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
$nav_flag.removeClass().addClass('btn btn-warning');
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$nav_flag.removeClass().addClass('btn btn-secondary');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Variable Definitions
|
||||||
|
|
||||||
|
const _id = window.localStorage.getItem('_id');
|
||||||
|
|
||||||
|
var current_question = 0;
|
||||||
|
var total_questions = 0;
|
||||||
|
var question_status = {};
|
||||||
|
var answers = {};
|
||||||
|
var questions = []
|
||||||
|
var time_limit_data = ''
|
||||||
|
|
||||||
|
var display_settings = get_settings_from_storage();
|
||||||
|
const $quiz_settings = $("#quiz-settings");
|
||||||
|
const $quiz_navigator = $("#quiz-navigator");
|
||||||
|
const $quiz_render = $("#quiz-render");
|
||||||
|
const $nav_flag = $("#q-nav-flag");
|
||||||
|
const $nav_next = $("#q-nav-next");
|
||||||
|
const $nav_prev = $("#q-nav-prev");
|
||||||
|
|
||||||
|
var toggle_settings = false;
|
||||||
|
var toggle_navigator = false;
|
||||||
|
|
||||||
|
const $question_title = $("#quiz-question-title");
|
||||||
|
const $question_header = $("#quiz-question-header");
|
||||||
|
const $question_text = $("#quiz-question-text");
|
||||||
|
const $question_options = $("#quiz-question-options");
|
||||||
|
|
||||||
|
// Execution on Load
|
||||||
|
|
||||||
|
apply_settings(display_settings);
|
||||||
|
|
||||||
|
// TODO Build navigator
|
||||||
|
// TODO Navigator Link button behaviour
|
||||||
|
// TODO Resume Exam button
|
||||||
|
// TODO Load state from storage
|
||||||
|
// TODO Answer Registry
|
@ -27,7 +27,8 @@ $('form[name=form-quiz-start]').submit(function(event) {
|
|||||||
dataType: 'json',
|
dataType: 'json',
|
||||||
success: function(response) {
|
success: function(response) {
|
||||||
var _id = response._id
|
var _id = response._id
|
||||||
window.location.href = `/api/questions/${_id}`;
|
window.localStorage.setItem('_id', _id);
|
||||||
|
window.location.href = `/test/`;
|
||||||
},
|
},
|
||||||
error: function(response) {
|
error: function(response) {
|
||||||
if (typeof response.responseJSON.error === 'string' || response.responseJSON.error instanceof String) {
|
if (typeof response.responseJSON.error === 'string' || response.responseJSON.error instanceof String) {
|
||||||
|
199
ref-test/quiz/templates/quiz/client.html
Normal file
199
ref-test/quiz/templates/quiz/client.html
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
{% extends "quiz/components/base.html" %}
|
||||||
|
|
||||||
|
{% block style %}
|
||||||
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
href="{{ url_for('.static', filename='css/quiz.css') }}"
|
||||||
|
/>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<div class="container quiz-panel" id="quiz-settings">
|
||||||
|
<h1>Adjust Display Settings</h1>
|
||||||
|
<div class="container quiz-start-text">
|
||||||
|
You can use this panel to adjust the display settings for the exam. Please use the menu below to select the font face and font size. Below is a sample question so you can see how the exam will render with your chosen settings.
|
||||||
|
</div>
|
||||||
|
<div class="alert alert-primary quiz-start-text" role="alert">
|
||||||
|
<strong>Note</strong>: Some fonts may not be available depending on your device and/or operating system.
|
||||||
|
</div>
|
||||||
|
<form action="#" name="quiz-configuration">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row gx-5 gy-5">
|
||||||
|
<div class="col">
|
||||||
|
<h5>
|
||||||
|
Select Font
|
||||||
|
</h5>
|
||||||
|
<div class="form-check">
|
||||||
|
<input type="radio" class="form-check-input" id="osdefault" name="font-select" value="osdefault" checked>
|
||||||
|
<label for="osdefault" class="form-check-label q-f-osdefault">OS Default</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-check">
|
||||||
|
<input type="radio" class="form-check-input" id="arial" name="font-select" value="arial">
|
||||||
|
<label for="arial" class="form-check-label q-f-arial">Arial</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-check">
|
||||||
|
<input type="radio" class="form-check-input" id="comicsans" name="font-select" value="comicsans">
|
||||||
|
<label for="comicsans" class="form-check-label q-f-comicsans">Comic Sans</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-check">
|
||||||
|
<input type="radio" class="form-check-input" id="opendyslexic" name="font-select" value="opendyslexic">
|
||||||
|
<label for="opendyslexic" class="form-check-label q-f-opendyslexic">OpenDyslexic</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-check">
|
||||||
|
<input type="radio" class="form-check-input" id="tahoma" name="font-select" value="tahoma">
|
||||||
|
<label for="tahoma" class="form-check-label q-f-tahoma">Tahoma</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-check">
|
||||||
|
<input type="radio" class="form-check-input" id="verdana" name="font-select" value="verdana">
|
||||||
|
<label for="verdana" class="form-check-label q-f-verdana">Verdana</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<h5>
|
||||||
|
Select Font Size
|
||||||
|
</h5>
|
||||||
|
<div class="form-check">
|
||||||
|
<input type="radio" class="form-check-input" id="12pt" name="font-size" value="12pt" checked>
|
||||||
|
<label for="12pt" class="form-check-label q-f-12pt">12pt</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-check">
|
||||||
|
<input type="radio" class="form-check-input" id="14pt" name="font-size" value="14pt" checked>
|
||||||
|
<label for="14pt" class="form-check-label q-f-14pt">14pt (Default)</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-check">
|
||||||
|
<input type="radio" class="form-check-input" id="16pt" name="font-size" value="16pt">
|
||||||
|
<label for="16pt" class="form-check-label q-f-16pt">16pt</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-check">
|
||||||
|
<input type="radio" class="form-check-input" id="18pt" name="font-size" value="18pt">
|
||||||
|
<label for="18pt" class="form-check-label q-f-18pt">18pt</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row gx-5 gy-5 mt-1">
|
||||||
|
<div class="col">
|
||||||
|
<h5>Select Background Colour</h5>
|
||||||
|
<div class="p-3 bg-light text-dark">
|
||||||
|
<div class="form-check">
|
||||||
|
<input type="radio" class="form-check-input" id="bg-light" name="bg-select" value="bg-light" checked>
|
||||||
|
<label for="bg-light" class="form-check-label">Default Light</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="p-3 q-bg-light-1 text-dark">
|
||||||
|
<div class="form-check">
|
||||||
|
<input type="radio" class="form-check-input" id="q-bg-light-1" name="bg-select" value="q-bg-light-1">
|
||||||
|
<label for="q-bg-light-1" class="form-check-label">Light Shade 1</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="p-3 q-bg-light-2 text-dark">
|
||||||
|
<div class="form-check">
|
||||||
|
<input type="radio" class="form-check-input" id="q-bg-light-2" name="bg-select" value="q-bg-light-2">
|
||||||
|
<label for="q-bg-light-2" class="form-check-label">Light Shade 2</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="p-3 alert-primary text-dark">
|
||||||
|
<div class="form-check">
|
||||||
|
<input type="radio" class="form-check-input" id="alert-primary" name="bg-select" value="alert-primary">
|
||||||
|
<label for="alert-primary" class="form-check-label">Blue</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="p-3 alert-secondary text-dark">
|
||||||
|
<div class="form-check">
|
||||||
|
<input type="radio" class="form-check-input" id="alert-secondary" name="bg-select" value="alert-secondary">
|
||||||
|
<label for="alert-secondary" class="form-check-label">Grey 1</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="p-3 alert-dark text-dark">
|
||||||
|
<div class="form-check">
|
||||||
|
<input type="radio" class="form-check-input" id="alert-dark" name="bg-select" value="alert-dark">
|
||||||
|
<label for="alert-dark" class="form-check-label">Grey 2</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="p-3 bg-dark text-light">
|
||||||
|
<div class="form-check">
|
||||||
|
<input type="radio" class="form-check-input" id="bg-dark" name="bg-select" value="bg-dark">
|
||||||
|
<label for="bg-dark" class="form-check-label">Dark</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<div class="container question-container quiz-start-text">
|
||||||
|
<h4 class="question-title">Sample Question</h4>
|
||||||
|
<p class="question-header">
|
||||||
|
Korfball is a mixed-sex, controlled-contact, indoor, invasion ball sport. The sport originated in the Netherlands. It is a mixed-sex team sport. Its governing body is the International Korball Federation. There are numerous korfball leagues and associations around the world. A korfball match is officiated by a referee.
|
||||||
|
</p>
|
||||||
|
<p class="question-text">
|
||||||
|
In order to be a referee, what do you need to know?
|
||||||
|
</p>
|
||||||
|
<div class="options">
|
||||||
|
<div class="form-check">
|
||||||
|
<input type="radio" class="form-check-input" id="sample0" name="sample" value="0">
|
||||||
|
<label for="sample0" class="form-check-label">The rules of korfball</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-check">
|
||||||
|
<input type="radio" class="form-check-input" id="sample1" name="sample" value="1">
|
||||||
|
<label for="sample1" class="form-check-label">The way of the Jedi Order</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-check">
|
||||||
|
<input type="radio" class="form-check-input" id="sample2" name="sample" value="2">
|
||||||
|
<label for="sample2" class="form-check-label">The <i>Dungeons & Dragons Fifth Edition Monster Manual</i>.</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mt-3">
|
||||||
|
<p class="quiz-start-text">
|
||||||
|
When you are happy with the settings, click <strong>‘Start the Exam’</strong> below to proceed. You can change these settings at any time using the red gear <a class="btn btn-danger btn-dummy" tabindex="-1" aria-title="Settings" title="Settings"><i class="bi bi-gear-fill"></i></a> button on the exam console.
|
||||||
|
</p>
|
||||||
|
<div class="control-button-container">
|
||||||
|
<a href="#" class="btn btn-success btn-quiz-control" id="btn-start-quiz">Start the Exam</a>
|
||||||
|
<a href="#" class="btn btn-success btn-quiz-control btn-quiz-return" style="display: none;">Resume Exam</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="container quiz-panel" style="display: none;" id="quiz-navigator">
|
||||||
|
<h1>
|
||||||
|
Navigator
|
||||||
|
</h1>
|
||||||
|
<div id="navigator-container">
|
||||||
|
<a href="#" class="q-navigator-button btn btn-success">Q666</a>
|
||||||
|
<a href="#" class="q-navigator-button btn btn-warning">Q666</a>
|
||||||
|
<a href="#" class="q-navigator-button btn btn-secondary disabled">Q666</a>
|
||||||
|
</div>
|
||||||
|
<div class="control-button-container">
|
||||||
|
<a href="#" class="btn btn-success btn-quiz-control btn-quiz-return">Resume Exam</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="container quiz-panel quiz-console" style="display: none" id="quiz-render">
|
||||||
|
<h1>
|
||||||
|
Exam Console
|
||||||
|
</h1>
|
||||||
|
<div class="container question-container">
|
||||||
|
<h4 class="question-title" id="quiz-question-title">
|
||||||
|
Question x.
|
||||||
|
</h4>
|
||||||
|
<p class="question-header" id="quiz-question-header">
|
||||||
|
Question Header
|
||||||
|
</p>
|
||||||
|
<p class="question-text" id="quiz-question-text">
|
||||||
|
Question Text
|
||||||
|
</p>
|
||||||
|
<div class="options" id="quiz-question-options">
|
||||||
|
Options
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="control-button-container">
|
||||||
|
<a href="#" class="btn btn-success q-question-nav" id="q-nav-prev" title="Previous Question"><i class="bi bi-caret-left-square-fill"></i> Back</a>
|
||||||
|
<a href="#" class="btn btn-secondary" id="q-nav-flag" title="Flag Question"><i class="bi bi-flag-fill"></i></a>
|
||||||
|
<a href="#" class="btn btn-success q-question-nav" id="q-nav-next" title="Next Question">Next <i class="bi bi-caret-right-square-fill"></i></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
{% block script %}
|
||||||
|
<script
|
||||||
|
type="text/javascript"
|
||||||
|
src="{{ url_for('.static', filename='js/quiz.js') }}"
|
||||||
|
></script>
|
||||||
|
{% endblock %}
|
@ -15,6 +15,8 @@
|
|||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
href="{{ url_for('.static', filename='css/style.css') }}"
|
href="{{ url_for('.static', filename='css/style.css') }}"
|
||||||
/>
|
/>
|
||||||
|
{% block style %}
|
||||||
|
{% endblock %}
|
||||||
<title>{% block title %} SKA Referee Test {% endblock %}</title>
|
<title>{% block title %} SKA Referee Test {% endblock %}</title>
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-light">
|
<body class="bg-light">
|
||||||
@ -66,5 +68,7 @@
|
|||||||
type="text/javascript"
|
type="text/javascript"
|
||||||
src="{{ url_for('.static', filename='js/script.js') }}"
|
src="{{ url_for('.static', filename='js/script.js') }}"
|
||||||
></script>
|
></script>
|
||||||
|
{% block script %}
|
||||||
|
{% endblock %}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
@ -1,5 +1,14 @@
|
|||||||
<nav class="navbar fixed-top navbar-expand-md navbar-dark bg-dark">
|
<nav class="navbar fixed-top navbar-expand-md navbar-dark bg-dark" id="primary-nav">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<a href="/" class="navbar-brand mb-0 h1">SKA Refereeing Test </a>
|
<a href="/" class="navbar-brand mb-0 h1">SKA Refereeing Test </a>
|
||||||
|
<div class="quiz-console w-100" style="display: none;" id="q-topbar">
|
||||||
|
<div class="d-flex justify-content align-middle">
|
||||||
|
<div class="container d-flex justify-content-center">
|
||||||
|
<span class="text-light q-timer" id="q-timer-widget"><i class="bi bi-stopwatch-fill"></i> <span id="q-timer-display">1:58:57</span></span>
|
||||||
|
</div>
|
||||||
|
<a href="#" class="btn btn-warning" aria-title="Navigate" title="Navigate" id="btn-toggle-navigator"><i class="bi bi-table"></i></a>
|
||||||
|
<a href="#" class="btn btn-danger" aria-title="Settings" title="Settings" id="btn-toggle-settings"><i class="bi bi-gear-fill"></i></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
@ -1,4 +1,4 @@
|
|||||||
from flask import Blueprint, render_template, request, redirect, jsonify, session, abort
|
from flask import Blueprint, render_template, request, redirect, jsonify, session, abort, flash
|
||||||
from flask.helpers import url_for
|
from flask.helpers import url_for
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
@ -48,26 +48,27 @@ def start():
|
|||||||
'club': encrypt(club),
|
'club': encrypt(club),
|
||||||
'test_code': test_code,
|
'test_code': test_code,
|
||||||
'user_code': user_code,
|
'user_code': user_code,
|
||||||
'start_time': datetime.utcnow(),
|
# 'start_time': datetime.utcnow(), TODO move start time to after configuration.
|
||||||
'status': 'started'
|
# 'status': 'started'
|
||||||
}
|
}
|
||||||
if db.entries.insert(entry):
|
if db.entries.insert(entry):
|
||||||
session['_id'] = entry['_id'] # Change this to return _id via JSON so client can access. Client will not be able to decrypt session cookie.
|
session['_id'] = entry['_id'] # TODO Change this to return _id via JSON so client can access. Client will not be able to decrypt session cookie.
|
||||||
return jsonify({
|
return jsonify({
|
||||||
'success': 'Success! An exam entry was started.',
|
'success': 'Received and validated test and/or user code. Redirecting to test client.',
|
||||||
'_id': entry['_id']
|
'_id': entry['_id']
|
||||||
}), 200
|
}), 200
|
||||||
else:
|
else:
|
||||||
errors = [*form.errors]
|
errors = [*form.errors]
|
||||||
return jsonify({ 'error': errors}), 400
|
return jsonify({ 'error': errors}), 400
|
||||||
|
|
||||||
@views.route('/api/questions/<_id>')
|
@views.route('/api/questions/', methods=['POST'])
|
||||||
def fetch_questions(_id):
|
def fetch_questions():
|
||||||
|
_id = request.get_json()['_id']
|
||||||
entry = db.entries.find_one({'_id': _id})
|
entry = db.entries.find_one({'_id': _id})
|
||||||
if not entry:
|
if not entry:
|
||||||
return abort(404)
|
return abort(404)
|
||||||
test_code = entry['test_code']
|
test_code = entry['test_code']
|
||||||
# user_code = entry['user_code'] Implement functionality for adjustments
|
# user_code = entry['user_code'] TODO Implement functionality for adjustments
|
||||||
|
|
||||||
test = db.tests.find_one({'test_code' : test_code})
|
test = db.tests.find_one({'test_code' : test_code})
|
||||||
dataset = test['dataset']
|
dataset = test['dataset']
|
||||||
@ -83,6 +84,13 @@ def fetch_questions(_id):
|
|||||||
'questions': questions
|
'questions': questions
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@views.route('/test/')
|
||||||
|
def start_quiz():
|
||||||
|
_id = session.get('_id')
|
||||||
|
if not _id or not db.entries.find_one({'_id': _id}):
|
||||||
|
flash('Your log in was not recognised. Please sign in to the quiz again.', 'error')
|
||||||
|
return redirect(url_for('quiz_views.start'))
|
||||||
|
return render_template('quiz/client.html')
|
||||||
|
|
||||||
|
|
||||||
@views.route('/privacy/')
|
@views.route('/privacy/')
|
||||||
|
Loading…
Reference in New Issue
Block a user