From 02290e968c923e8b6d17c5a5f1eb6bf1ee348b25 Mon Sep 17 00:00:00 2001 From: Vivek Santayana Date: Wed, 17 Aug 2022 16:32:58 +0100 Subject: [PATCH 1/6] Added question viewer functionality Added view questions panel to editor interface Added view questions section of web site Added links to navbars --- nginx/conf.d/ref-test-app.conf | 7 + ref-test/app/__init__.py | 2 + .../templates/admin/components/navbar.html | 7 +- .../templates/admin/settings/questions.html | 23 +- ref-test/app/editor/static/css/editor.css | 18 +- ref-test/app/editor/static/js/editor.js | 163 ++++++++++- .../templates/editor/components/navbar.html | 7 +- .../app/editor/templates/editor/console.html | 11 +- ref-test/app/view/__init__.py | 0 ref-test/app/view/static/css/style.css | 260 ++++++++++++++++++ ref-test/app/view/static/css/view.css | 30 ++ .../app/view/static/js/jquery-3.6.0.min.js | 2 + ref-test/app/view/static/js/script.js | 115 ++++++++ ref-test/app/view/static/js/view.js | 130 +++++++++ .../view/templates/view/components/base.html | 84 ++++++ .../view/components/client-alerts.html | 1 + .../templates/view/components/datatable.html | 28 ++ .../templates/view/components/footer.html | 2 + .../view/components/input-forms.html | 4 + .../templates/view/components/navbar.html | 117 ++++++++ .../templates/view/components/og-meta.html | 18 ++ .../view/components/secondary-navs/tests.html | 23 ++ .../view/components/server-alerts.html | 43 +++ ref-test/app/view/templates/view/console.html | 116 ++++++++ ref-test/app/view/templates/view/index.html | 27 ++ ref-test/app/view/views.py | 41 +++ 26 files changed, 1253 insertions(+), 26 deletions(-) create mode 100644 ref-test/app/view/__init__.py create mode 100644 ref-test/app/view/static/css/style.css create mode 100644 ref-test/app/view/static/css/view.css create mode 100644 ref-test/app/view/static/js/jquery-3.6.0.min.js create mode 100644 ref-test/app/view/static/js/script.js create mode 100644 ref-test/app/view/static/js/view.js create mode 100644 ref-test/app/view/templates/view/components/base.html create mode 100644 ref-test/app/view/templates/view/components/client-alerts.html create mode 100644 ref-test/app/view/templates/view/components/datatable.html create mode 100644 ref-test/app/view/templates/view/components/footer.html create mode 100644 ref-test/app/view/templates/view/components/input-forms.html create mode 100644 ref-test/app/view/templates/view/components/navbar.html create mode 100644 ref-test/app/view/templates/view/components/og-meta.html create mode 100644 ref-test/app/view/templates/view/components/secondary-navs/tests.html create mode 100644 ref-test/app/view/templates/view/components/server-alerts.html create mode 100644 ref-test/app/view/templates/view/console.html create mode 100644 ref-test/app/view/templates/view/index.html create mode 100644 ref-test/app/view/views.py diff --git a/nginx/conf.d/ref-test-app.conf b/nginx/conf.d/ref-test-app.conf index 36c1247..24c2694 100644 --- a/nginx/conf.d/ref-test-app.conf +++ b/nginx/conf.d/ref-test-app.conf @@ -19,6 +19,7 @@ server { include /etc/nginx/ssl.conf; include /etc/nginx/certbot-challenge.conf; + # Define locations for static files to be served by Nginx location ^~ /quiz/static/ { include /etc/nginx/mime.types; alias /usr/share/nginx/html/quiz/static/; @@ -34,6 +35,12 @@ server { alias /usr/share/nginx/html/admin/editor/static/; } + location ^~ /admin/view/static/ { + include /etc/nginx/mime.types; + alias /usr/share/nginx/html/admin/view/static/; + } + + # Proxy to the main app for all other requests location / { include /etc/nginx/conf.d/proxy_headers.conf; proxy_pass http://reftest; diff --git a/ref-test/app/__init__.py b/ref-test/app/__init__.py index 4122f2c..66dfc25 100644 --- a/ref-test/app/__init__.py +++ b/ref-test/app/__init__.py @@ -46,11 +46,13 @@ def create_app(): from .quiz.views import quiz from .views import views from .editor.views import editor + from .view.views import view app.register_blueprint(admin, url_prefix='/admin') app.register_blueprint(api, url_prefix='/api') app.register_blueprint(views) app.register_blueprint(quiz) app.register_blueprint(editor, url_prefix='/admin/editor') + app.register_blueprint(view, url_prefix='/admin/view') return app \ No newline at end of file diff --git a/ref-test/app/admin/templates/admin/components/navbar.html b/ref-test/app/admin/templates/admin/components/navbar.html index 48686d4..4dbe334 100644 --- a/ref-test/app/admin/templates/admin/components/navbar.html +++ b/ref-test/app/admin/templates/admin/components/navbar.html @@ -77,10 +77,13 @@ Users
  • - Question Datasets + Manage Questions
  • - Question Editor + View Questions +
  • +
  • + Edit Questions
  • diff --git a/ref-test/app/admin/templates/admin/settings/questions.html b/ref-test/app/admin/templates/admin/settings/questions.html index eed4611..d9e941a 100644 --- a/ref-test/app/admin/templates/admin/settings/questions.html +++ b/ref-test/app/admin/templates/admin/settings/questions.html @@ -57,28 +57,37 @@ class="btn btn-primary edit-question-dataset" data-id="{{ element.id }}" data-action="download" - title="Download Dataset" + title="Download Questions" > - + + + + - - + + - + {% endfor %} diff --git a/ref-test/app/editor/static/css/editor.css b/ref-test/app/editor/static/css/editor.css index 84544fd..133e762 100644 --- a/ref-test/app/editor/static/css/editor.css +++ b/ref-test/app/editor/static/css/editor.css @@ -71,7 +71,7 @@ margin: 30pt auto; } -.info-panel { +.info-panel, .viewer-panel { display: none; } @@ -84,4 +84,20 @@ #alert-box { margin: 30px auto; max-width: 460px; +} + +.block { + border: 2px solid black; + border-radius: 10px; + margin: 10px; + padding: 5px; +} + +.question-body, .question-block { + padding: 0px 2em; +} + +blockquote { + padding: 0px 2em; + font-style: italic; } \ No newline at end of file diff --git a/ref-test/app/editor/static/js/editor.js b/ref-test/app/editor/static/js/editor.js index 85e9fd1..f7eb64e 100644 --- a/ref-test/app/editor/static/js/editor.js +++ b/ref-test/app/editor/static/js/editor.js @@ -5,24 +5,69 @@ const id = $root.data('id') const $control_panel = $('.control-panel') const $info_panel = $('.info-panel') +const $viewer_panel = $('.viewer-panel') const $editor_panel = $('.editor-panel') +var toggle_info = false +var toggle_viewer = false + var element_index = 0 // Initialise Sortable and trigger renumbering on end of drag Sortable.create($root.get(0), {handle: '.move-handle', onEnd: function(evt) {renumber_blocks()}}) -// Info Button Listener +// Info and Viewer Button Listener $control_panel.find('button').click(function(event){ - if ($info_panel.is(":hidden")) { - $editor_panel.hide() - $info_panel.fadeIn() - $(this).addClass('active') - } else { - $info_panel.hide() - $editor_panel.fadeIn() - $(this).removeClass('active') + var action = $(this).data('action'); + + if (action == 'info') { + if ($info_panel.is(":hidden")) { + if ($viewer_panel.is(":visible")) { + toggle_viewer = true + $viewer_panel.hide() + } + $editor_panel.hide() + $info_panel.fadeIn() + $(window).scrollTop(0) + toggle_info = false + $(this).addClass('active') + } else { + $info_panel.hide() + if (toggle_viewer) { + render_viewer() + $(window).scrollTop(0) + toggle_viewer = false + } else { + $editor_panel.fadeIn() + $(window).scrollTop(0) + } + $(this).removeClass('active') + } + } else if (action == 'view') { + if ($viewer_panel.is(":hidden")) { + if ($info_panel.is(':visible')) { + toggle_info = true + $info_panel.hide() + } + $editor_panel.hide() + render_viewer() + $(window).scrollTop(0) + toggle_viewer = false + $(this).addClass('active') + } else { + $viewer_panel.hide() + if (toggle_info) { + $info_panel.fadeIn() + $(window).scrollTop(0) + toggle_info = false + } else { + $editor_panel.fadeIn() + $(window).scrollTop(0) + } + $(this).removeClass('active') + } } + event.preventDefault() }) @@ -496,4 +541,102 @@ $(window).on('load', function() { console.log(response) } }) -}) \ No newline at end of file +}) + +// Viewer Render Function +function render_viewer() { + $viewer_panel.fadeIn() + $viewer_panel.empty() + var heading = document.createElement('h3') + heading.innerText = 'View Questions' + $viewer_panel.append(heading) + var data = parse_input() + var block + var obj + for (let i = 0; i < data.length; i++) { + block = data[i] + obj = document.createElement('div') + obj.classList = 'block' + if (block['type'] == 'question') { + text = document.createElement('p') + text.innerHTML = `Question ${block['q_no'] + 1}. ${block['text']}` + obj.append(text) + question_body = document.createElement('div') + question_body.className ='question-body' + type = document.createElement('p') + type.innerHTML = `Question Type: ${block['q_type']}` + question_body.append(type) + options = document.createElement('p') + options.innerHTML = 'Options:' + option_list = document.createElement('ul') + for (let _i = 0; _i < block['options'].length; _i++) { + option = document.createElement('li') + option.innerHTML = block['options'][_i] + if (block['correct'] == _i) { + option.innerHTML += ' Correct' + } + option_list.append(option) + } + options.append(option_list) + question_body.append(options) + tags = document.createElement('p') + tags.innerHTML = `Tags:` + tag_list = document.createElement('ul') + for (let _i = 0; _i < block['tags'].length; _i++) { + tag = document.createElement('li') + tag.innerHTML = block['tags'][_i] + tag_list.append(tag) + } + tags.append(tag_list) + question_body.append(tags) + obj.append(question_body) + } else if (block['type'] == 'block') { + meta = document.createElement('p') + meta.innerHTML = `Block ${i+1}. ${block['questions'].length} questions.` + obj.append(meta) + header = document.createElement('blockquote') + header.innerText = block['question_header'] + obj.append(header) + var block_question = document.createElement('div') + var question + block_question.className = 'question-block' + for (let _i = 0; _i < block['questions'].length; _i++) { + question = block['questions'][_i] + text = document.createElement('p') + text.innerHTML = `Question ${question['q_no'] + 1}. ${question['text']}` + block_question.append(text) + question_body = document.createElement('div') + question_body.className ='question-body' + type = document.createElement('p') + type.innerHTML = `Question Type: ${question['q_type']}` + question_body.append(type) + options = document.createElement('p') + options.innerHTML = 'Options:' + option_list = document.createElement('ul') + for (let __i = 0; __i < question['options'].length; __i++) { + option = document.createElement('li') + option.innerHTML = question['options'][__i] + if (question['correct'] == __i) { + option.innerHTML += ' Correct' + } + option_list.append(option) + } + options.append(option_list) + question_body.append(options) + tags = document.createElement('p') + tags.innerHTML = `Tags:` + tag_list = document.createElement('ul') + for (let __i = 0; __i < question['tags'].length; __i++) { + tag = document.createElement('li') + tag.innerHTML = question['tags'][__i] + tag_list.append(tag) + } + tags.append(tag_list) + question_body.append(tags) + block_question.append(question_body) + obj.append(block_question) + } + } + $viewer_panel.append(obj) + } +} \ No newline at end of file diff --git a/ref-test/app/editor/templates/editor/components/navbar.html b/ref-test/app/editor/templates/editor/components/navbar.html index 48686d4..4dbe334 100644 --- a/ref-test/app/editor/templates/editor/components/navbar.html +++ b/ref-test/app/editor/templates/editor/components/navbar.html @@ -77,10 +77,13 @@ Users
  • - Question Datasets + Manage Questions
  • - Question Editor + View Questions +
  • +
  • + Edit Questions
  • diff --git a/ref-test/app/editor/templates/editor/console.html b/ref-test/app/editor/templates/editor/console.html index b532cf5..e244e73 100644 --- a/ref-test/app/editor/templates/editor/console.html +++ b/ref-test/app/editor/templates/editor/console.html @@ -11,11 +11,12 @@

    Editor

    - Use this console to edit the questions in this dataset. For more information on using the editor console, click on the the blue information button. + Use this console to edit the questions in this dataset. For more information on using the editor console, click on the the blue Information button. To preview the questions in the current dataset, click on the green View Questions button.

    - + +

    @@ -77,9 +78,11 @@ In order to show how many questions are remaining inside a block, e.g. to say ‘the next n questions are about a specific scenario’, use the placeholder <block_remaining_questions>.

    +
    +

    - Edit Dataset + Edit Questions

    - `); + `) } else if (response.responseJSON.error instanceof Array) { var output = '' - for (var i = 0; i < response.responseJSON.error.length; i ++) { + for (let i = 0; i < response.responseJSON.error.length; i ++) { output += ` - `; - $alert.html(output); + ` + $alert.html(output) } } @@ -167,20 +169,20 @@ $('#dismiss-cookie-alert').click(function(event){ }, dataType: 'json', success: function(response){ - console.log(response); + console.log(response) }, error: function(response){ - console.log(response); + console.log(response) } }) - event.preventDefault(); + event.preventDefault() }) // Script for Result Actions $('.result-action-buttons').click(function(event){ - var id = $(this).data('id'); + var id = $(this).data('id') if ($(this).data('result-action') == 'generate') { $.ajax({ @@ -190,13 +192,13 @@ $('.result-action-buttons').click(function(event){ contentType: 'application/json', dataType: 'html', success: function(response) { - var display_window = window.open(); - display_window.document.write(response); + var display_window = window.open() + display_window.document.write(response) }, error: function(response){ - error_response(response); + error_response(response) }, - }); + }) } else { var action = $(this).data('result-action') $.ajax({ @@ -206,23 +208,23 @@ $('.result-action-buttons').click(function(event){ contentType: 'application/json', success: function(response) { if (action == 'delete') { - window.location.href = '/admin/results/'; - } else window.location.reload(); + window.location.href = '/admin/results/' + } else window.location.reload() }, error: function(response){ - error_response(response); + error_response(response) }, - }); + }) } - event.preventDefault(); -}); + event.preventDefault() +}) // Script for Deleting Time Adjustment $('.adjustment-delete').click(function(event){ - var user_code = $(this).data('user_code'); - var location = window.location.href; + var user_code = $(this).data('user_code') + var location = window.location.href location = location.replace('#', '') $.ajax({ @@ -231,12 +233,15 @@ $('.adjustment-delete').click(function(event){ data: JSON.stringify({'user_code': user_code}), contentType: 'application/json', success: function(response) { - window.location.reload(); + window.location.reload() }, error: function(response){ - error_response(response); + error_response(response) }, - }); + }) + + event.preventDefault() +}) event.preventDefault(); }); \ No newline at end of file diff --git a/ref-test/app/editor/static/js/script.js b/ref-test/app/editor/static/js/script.js index bba3bde..f9a08ad 100644 --- a/ref-test/app/editor/static/js/script.js +++ b/ref-test/app/editor/static/js/script.js @@ -57,7 +57,7 @@ function error_response(response) { `); } else if (response.responseJSON.error instanceof Array) { var output = '' - for (var i = 0; i < response.responseJSON.error.length; i ++) { + for (let i = 0; i < response.responseJSON.error.length; i ++) { output += ` ` } - $question_options.html(options_output); - let skipped = count_questions(-1); - let answered = count_questions(2); - let flagged = count_questions(1); + $question_options.html(options_output) + let skipped = count_questions(-1) + let answered = count_questions(2) + let flagged = count_questions(1) - $progress_skipped.prop('title', `Skipped: ${skipped}`); - $progress_skipped.prop('aria-valuenow', skipped); - $progress_skipped.css('width', `${skipped}%`); - $skipped_count.text(`Skipped: ${skipped}`); + $progress_skipped.prop('title', `Skipped: ${skipped}`) + $progress_skipped.prop('aria-valuenow', skipped) + $progress_skipped.css('width', `${skipped}%`) + $skipped_count.text(`Skipped: ${skipped}`) if (skipped < 1) { $skipped_count.fadeOut() } else { $skipped_count.fadeIn() } - $progress_flagged.prop('title', `Flagged: ${flagged}`); - $progress_flagged.prop('aria-valuenow', flagged); - $progress_flagged.css('width', `${flagged}%`); - $flagged_count.text(`Flagged: ${flagged}`); + $progress_flagged.prop('title', `Flagged: ${flagged}`) + $progress_flagged.prop('aria-valuenow', flagged) + $progress_flagged.css('width', `${flagged}%`) + $flagged_count.text(`Flagged: ${flagged}`) if (flagged < 1) { $flagged_count.fadeOut() } else { $flagged_count.fadeIn() } - $progress_answered.prop('title', `Answered: ${answered}`); - $progress_answered.prop('aria-valuenow', answered); - $progress_answered.css('width', `${answered}%`); - $answered_count.text(`Answered: ${answered}`); + $progress_answered.prop('title', `Answered: ${answered}`) + $progress_answered.prop('aria-valuenow', answered) + $progress_answered.css('width', `${answered}%`) + $answered_count.text(`Answered: ${answered}`) if (answered < 1) { $answered_count.fadeOut() } else { $answered_count.fadeIn() } - $question_title.focus(); - $(window).scrollTop(0); + $question_title.focus() + $(window).scrollTop(0) } function check_answered() { - var question = questions[current_question]; - var name = question.q_no; + var question = questions[current_question] + var name = question.q_no if (question_status[current_question] == 0 || question_status[current_question] == -1) { if (!$(`input[name='${name}']:checked`).val()) { - question_status[current_question] = -1; + question_status[current_question] = -1 } else { - question_status[current_question] = 2; + question_status[current_question] = 2 } - window.localStorage.setItem('question_status', JSON.stringify(question_status)); + window.localStorage.setItem('question_status', JSON.stringify(question_status)) } } function check_flag() { if (!(current_question in question_status)) { - question_status[current_question] = 0; - window.localStorage.setItem('question_status', JSON.stringify(question_status)); + question_status[current_question] = 0 + window.localStorage.setItem('question_status', JSON.stringify(question_status)) } switch (question_status[current_question]) { case -1: - $nav_flag.removeClass().addClass('btn btn-danger progress-bar-striped'); - $nav_flag.prop("title", "Question Incomplete. Click to flag for revision."); - break; + $nav_flag.removeClass().addClass('btn btn-danger progress-bar-striped') + $nav_flag.prop("title", "Question Incomplete. Click to flag for revision.") + break case 1: - $nav_flag.removeClass().addClass('btn btn-warning'); - $nav_flag.prop("title", "Question Flagged for revision. Click to un-flag."); - break; + $nav_flag.removeClass().addClass('btn btn-warning') + $nav_flag.prop("title", "Question Flagged for revision. Click to un-flag.") + break case 2: - $nav_flag.removeClass().addClass('btn btn-success'); - $nav_flag.prop("title", "Question Answered. Click to flag for revision."); - break; + $nav_flag.removeClass().addClass('btn btn-success') + $nav_flag.prop("title", "Question Answered. Click to flag for revision.") + break default: - $nav_flag.removeClass().addClass('btn btn-secondary'); - $nav_flag.prop("title", "Question Un-Flagged. Click to flag for revision."); + $nav_flag.removeClass().addClass('btn btn-secondary') + $nav_flag.prop("title", "Question Un-Flagged. Click to flag for revision.") } } @@ -457,31 +457,31 @@ function build_navigator() { $nav_container.html('') var output = '' for (let i = 0; i < questions.length; i ++) { - let add_class, add_href, add_status = ''; + let add_class, add_href, add_status = '' switch (question_status[i]) { case -1: - add_class = 'btn-danger progress-bar-striped'; - add_href = 'href="#"'; - add_status = 'Incomplete'; - break; + add_class = 'btn-danger progress-bar-striped' + add_href = 'href="#"' + add_status = 'Incomplete' + break case 1: - add_class = 'btn-warning'; - add_href = 'href="#"'; - add_status = 'Flagged'; - break; + add_class = 'btn-warning' + add_href = 'href="#"' + add_status = 'Flagged' + break case 2: - add_class = 'btn-success'; - add_href = 'href="#"'; - add_status = 'Answered'; - break; + add_class = 'btn-success' + add_href = 'href="#"' + add_status = 'Answered' + break default: - add_class = 'btn-secondary disabled'; - add_href = ''; - add_status = 'Unseen'; + add_class = 'btn-secondary disabled' + add_href = '' + add_status = 'Unseen' } - output += `Q${i + 1}`; + output += `Q${i + 1}` } - $nav_container.html(output); + $nav_container.html(output) } function update_navigator() { @@ -489,162 +489,162 @@ function update_navigator() { if (current_question in question_status) { switch (question_status[current_question]) { case -1: - button.removeClass().addClass("q-navigator-button btn btn-danger progress-bar-striped"); - button.prop("title", `Question ${current_question + 1}: Incomplete`); - break; + button.removeClass().addClass("q-navigator-button btn btn-danger progress-bar-striped") + button.prop("title", `Question ${current_question + 1}: Incomplete`) + break case 1: - button.removeClass().addClass("q-navigator-button btn btn-warning"); - button.prop("title", `Question ${current_question + 1}: Flagged`); - break; + button.removeClass().addClass("q-navigator-button btn btn-warning") + button.prop("title", `Question ${current_question + 1}: Flagged`) + break case 2: - button.removeClass().addClass("q-navigator-button btn btn-success"); - button.prop("title", `Question ${current_question + 1}: Answered`); - break; + button.removeClass().addClass("q-navigator-button btn btn-success") + button.prop("title", `Question ${current_question + 1}: Answered`) + break default: - button.removeClass().addClass("q-navigator-button btn btn-secondary disabled"); - button.prop("title", `Question ${current_question + 1}: Unseen`); + button.removeClass().addClass("q-navigator-button btn btn-secondary disabled") + button.prop("title", `Question ${current_question + 1}: Unseen`) } } } function start() { - $("#btn-start-quiz").fadeOut(); - $(".btn-quiz-return").fadeIn(); - $(".quiz-console").fadeIn(); - $("#quiz-settings").fadeOut(); - $("#quiz-navigator").fadeOut(); - $(".quiz-start-text").fadeOut(); + $("#btn-start-quiz").fadeOut() + $(".btn-quiz-return").fadeIn() + $(".quiz-console").fadeIn() + $("#quiz-settings").fadeOut() + $("#quiz-navigator").fadeOut() + $(".quiz-start-text").fadeOut() - questions = JSON.parse(window.localStorage.getItem('questions')); - total_questions = questions.length; - start_time = window.localStorage.getItem('start_time'); - time_limit = window.localStorage.getItem('time_limit'); + questions = JSON.parse(window.localStorage.getItem('questions')) + total_questions = questions.length + start_time = window.localStorage.getItem('start_time') + time_limit = window.localStorage.getItem('time_limit') - let get_answers = window.localStorage.getItem('answers'); + let get_answers = window.localStorage.getItem('answers') if (get_answers != null) { - answers = JSON.parse(get_answers); + answers = JSON.parse(get_answers) } - let get_status = window.localStorage.getItem('question_status'); + let get_status = window.localStorage.getItem('question_status') if (get_status != null) { - question_status = JSON.parse(get_status); + question_status = JSON.parse(get_status) } - render_question(); - build_navigator(); - check_flag(); + render_question() + build_navigator() + check_flag() if (time_limit != 'null' && time_limit != null) { - $("#q-timer-widget").fadeIn(); - time_remaining = get_time_remaining(); - clock = setInterval(timer, 1000); + $("#q-timer-widget").fadeIn() + time_remaining = get_time_remaining() + clock = setInterval(timer, 1000) } } function check_started() { - let questions = window.localStorage.getItem('questions'); - let time_limit = window.localStorage.getItem('time_limit'); + let questions = window.localStorage.getItem('questions') + let time_limit = window.localStorage.getItem('time_limit') let start_time = window.localStorage.getItem('start_time') if (questions != null && start_time != null && time_limit != null) { - start(); + start() } } function get_time_remaining() { - var end_time = new Date(time_limit).getTime(); - var _start_time = new Date().getTime(); - return end_time - _start_time; + var end_time = new Date(time_limit).getTime() + var _start_time = new Date().getTime() + return end_time - _start_time } function timer() { - var hours = Math.floor((time_remaining % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)); - var minutes = Math.floor((time_remaining % (1000 * 60 * 60)) / (1000 * 60)); - var seconds = Math.floor((time_remaining % (1000 * 60)) / 1000); + var hours = Math.floor((time_remaining % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)) + var minutes = Math.floor((time_remaining % (1000 * 60 * 60)) / (1000 * 60)) + var seconds = Math.floor((time_remaining % (1000 * 60)) / 1000) if (time_remaining > 0) { - var timer_display = ''; + var timer_display = '' if (hours > 0) { - timer_display = `${hours.toString()}:`; + timer_display = `${hours.toString()}:` } if (minutes > 0 || hours > 0) { if (minutes < 10) { - timer_display += `0${minutes.toString()}:`; + timer_display += `0${minutes.toString()}:` } else { - timer_display += `${minutes.toString()}:`; + timer_display += `${minutes.toString()}:` } } if (seconds < 10) { - timer_display += `0${seconds.toString()}`; + timer_display += `0${seconds.toString()}` } else { - timer_display += seconds.toString(); + timer_display += seconds.toString() } - $timer.html(timer_display); + $timer.html(timer_display) time_remaining -= 1000 } else { - $timer.html('Expired'); - clearInterval(clock); + $timer.html('Expired') + clearInterval(clock) stop() } } function stop() { - $quiz_render.fadeOut(); - $quiz_navigator.fadeOut(); - $quiz_timeout.fadeIn(); - $("#btn-toggle-navigator").addClass('disabled'); + $quiz_render.fadeOut() + $quiz_navigator.fadeOut() + $quiz_timeout.fadeIn() + $("#btn-toggle-navigator").addClass('disabled') $("#btn-toggle-settings").addClass('disabled') } function count_questions(status) { - output = 0; + output = 0 for (let i = 0; i < Object.keys(question_status).length; i++) { - key = Object.keys(question_status)[i]; + key = Object.keys(question_status)[i] if (question_status[key] == status){ - output ++; + output ++ } } - return output; + return output } // Variable Definitions -const id = window.localStorage.getItem('id'); +const id = window.localStorage.getItem('id') -var current_question = 0; -var total_questions = 0; -var question_status = {}; -var answers = {}; -var questions = []; -var time_limit, start_time, time_remaining; +var current_question = 0 +var total_questions = 0 +var question_status = {} +var answers = {} +var questions = [] +var time_limit, start_time, time_remaining -var display_settings = get_settings_from_storage(); +var display_settings = get_settings_from_storage() -const $quiz_settings = $("#quiz-settings"); -const $quiz_navigator = $("#quiz-navigator"); -const $quiz_render = $("#quiz-render"); -const $quiz_timeout = $("#quiz-timeout"); -const $nav_flag = $("#q-nav-flag"); -const $nav_next = $("#q-nav-next"); -const $nav_prev = $("#q-nav-prev"); -const $nav_container = $("#navigator-container"); -const $timer = $("#q-timer-display"); +const $quiz_settings = $("#quiz-settings") +const $quiz_navigator = $("#quiz-navigator") +const $quiz_render = $("#quiz-render") +const $quiz_timeout = $("#quiz-timeout") +const $nav_flag = $("#q-nav-flag") +const $nav_next = $("#q-nav-next") +const $nav_prev = $("#q-nav-prev") +const $nav_container = $("#navigator-container") +const $timer = $("#q-timer-display") var clock -var toggle_settings = false; -var toggle_navigator = false; +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"); +const $question_title = $("#quiz-question-title") +const $question_header = $("#quiz-question-header") +const $question_text = $("#quiz-question-text") +const $question_options = $("#quiz-question-options") -const $progress_skipped = $("#skipped-bar"); -const $progress_answered = $("#answered-bar"); -const $progress_flagged = $("#flagged-bar"); -const $skipped_count = $("#skipped-count"); -const $answered_count = $("#answered-count"); -const $flagged_count = $("#flagged-count"); +const $progress_skipped = $("#skipped-bar") +const $progress_answered = $("#answered-bar") +const $progress_flagged = $("#flagged-bar") +const $skipped_count = $("#skipped-count") +const $answered_count = $("#answered-count") +const $flagged_count = $("#flagged-count") // Execution on Load -apply_settings(display_settings); -check_started(); \ No newline at end of file +apply_settings(display_settings) +check_started() \ No newline at end of file diff --git a/ref-test/app/quiz/static/js/script.js b/ref-test/app/quiz/static/js/script.js index ba30892..0dfdf97 100644 --- a/ref-test/app/quiz/static/js/script.js +++ b/ref-test/app/quiz/static/js/script.js @@ -1,21 +1,21 @@ $(document).ready(function() { $("#od-font-test").click(function(){ $("body").css("font-family", "opendyslexic3regular") - }); + }) $('.test-code-input').keyup(function() { - var input = $(this).val().split("-").join("").split("—").join(""); + var input = $(this).val().split("-").join("").split("—").join("") if (input.length > 0) { - input = input.match(new RegExp('.{1,4}', 'g')).join("—"); + input = input.match(new RegExp('.{1,4}', 'g')).join("—") } - $(this).val(input); - }); -}); + $(this).val(input) + }) +}) $('form[name=form-quiz-start]').submit(function(event) { - var $form = $(this); - var data = $form.serialize(); + var $form = $(this) + var data = $form.serialize() $.ajax({ url: window.location.pathname, @@ -24,21 +24,21 @@ $('form[name=form-quiz-start]').submit(function(event) { dataType: 'json', success: function(response) { var id = response.id - window.localStorage.setItem('id', id); - window.location.href = `/quiz/`; + window.localStorage.setItem('id', id) + window.location.href = `/quiz/` }, error: function(response) { - error_response(response); + error_response(response) } - }); + }) - event.preventDefault(); -}); + event.preventDefault() +}) function error_response(response) { - const $alert = $("#alert-box"); - $alert.html(''); + const $alert = $("#alert-box") + $alert.html('') if (typeof response.responseJSON.error === 'string' || response.responseJSON.error instanceof String) { $alert.html(` @@ -47,18 +47,18 @@ function error_response(response) { ${response.responseJSON.error}
    - `); + `) } else if (response.responseJSON.error instanceof Array) { var output = '' - for (var i = 0; i < response.responseJSON.error.length; i ++) { + for (let i = 0; i < response.responseJSON.error.length; i ++) { output += ` - `; - $alert.html(output); + ` + $alert.html(output) } } } @@ -74,13 +74,13 @@ $('#dismiss-cookie-alert').click(function(event){ }, dataType: 'json', success: function(response){ - console.log(response); + console.log(response) }, error: function(response){ - console.log(response); + console.log(response) } }) - event.preventDefault(); + event.preventDefault() }) \ No newline at end of file diff --git a/ref-test/app/root/js/script.js b/ref-test/app/root/js/script.js index ba30892..0dfdf97 100644 --- a/ref-test/app/root/js/script.js +++ b/ref-test/app/root/js/script.js @@ -1,21 +1,21 @@ $(document).ready(function() { $("#od-font-test").click(function(){ $("body").css("font-family", "opendyslexic3regular") - }); + }) $('.test-code-input').keyup(function() { - var input = $(this).val().split("-").join("").split("—").join(""); + var input = $(this).val().split("-").join("").split("—").join("") if (input.length > 0) { - input = input.match(new RegExp('.{1,4}', 'g')).join("—"); + input = input.match(new RegExp('.{1,4}', 'g')).join("—") } - $(this).val(input); - }); -}); + $(this).val(input) + }) +}) $('form[name=form-quiz-start]').submit(function(event) { - var $form = $(this); - var data = $form.serialize(); + var $form = $(this) + var data = $form.serialize() $.ajax({ url: window.location.pathname, @@ -24,21 +24,21 @@ $('form[name=form-quiz-start]').submit(function(event) { dataType: 'json', success: function(response) { var id = response.id - window.localStorage.setItem('id', id); - window.location.href = `/quiz/`; + window.localStorage.setItem('id', id) + window.location.href = `/quiz/` }, error: function(response) { - error_response(response); + error_response(response) } - }); + }) - event.preventDefault(); -}); + event.preventDefault() +}) function error_response(response) { - const $alert = $("#alert-box"); - $alert.html(''); + const $alert = $("#alert-box") + $alert.html('') if (typeof response.responseJSON.error === 'string' || response.responseJSON.error instanceof String) { $alert.html(` @@ -47,18 +47,18 @@ function error_response(response) { ${response.responseJSON.error} - `); + `) } else if (response.responseJSON.error instanceof Array) { var output = '' - for (var i = 0; i < response.responseJSON.error.length; i ++) { + for (let i = 0; i < response.responseJSON.error.length; i ++) { output += ` - `; - $alert.html(output); + ` + $alert.html(output) } } } @@ -74,13 +74,13 @@ $('#dismiss-cookie-alert').click(function(event){ }, dataType: 'json', success: function(response){ - console.log(response); + console.log(response) }, error: function(response){ - console.log(response); + console.log(response) } }) - event.preventDefault(); + event.preventDefault() }) \ No newline at end of file From 3a0abaac6ac9c166c0535096e11d298f29974a43 Mon Sep 17 00:00:00 2001 From: Vivek Santayana Date: Wed, 17 Aug 2022 16:35:22 +0100 Subject: [PATCH 3/6] Stylistic change of name dataset to questions --- ref-test/app/editor/templates/editor/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ref-test/app/editor/templates/editor/index.html b/ref-test/app/editor/templates/editor/index.html index a4519b8..e0de22c 100644 --- a/ref-test/app/editor/templates/editor/index.html +++ b/ref-test/app/editor/templates/editor/index.html @@ -4,7 +4,7 @@
    {% include "admin/components/server-alerts.html" %} -

    Dataset Editor

    +

    Edit Questions

    {{ form.hidden_tag() }}
    {{ form.dataset(placeholder="Select Question Dataset") }} From 2da8eb771271c67352fa15bb1913b1f8eb590d73 Mon Sep 17 00:00:00 2001 From: Vivek Santayana Date: Wed, 17 Aug 2022 16:36:16 +0100 Subject: [PATCH 4/6] Added cross-reference to question viewer Changed question number countint to be consistent with viewer --- ref-test/app/admin/static/js/script.js | 10 +++++++--- ref-test/app/admin/templates/admin/result-detail.html | 5 +++-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/ref-test/app/admin/static/js/script.js b/ref-test/app/admin/static/js/script.js index 3b45528..caaee59 100644 --- a/ref-test/app/admin/static/js/script.js +++ b/ref-test/app/admin/static/js/script.js @@ -242,6 +242,10 @@ $('.adjustment-delete').click(function(event){ event.preventDefault() }) - - event.preventDefault(); -}); \ No newline at end of file + +// Detailed Results view questions +$('.view-full-questions').click(function(event) { + var dataset = $(this).data('dataset') + window.open(`/admin/view/${dataset}`, '_blank') + event.preventDefault() +}) \ No newline at end of file diff --git a/ref-test/app/admin/templates/admin/result-detail.html b/ref-test/app/admin/templates/admin/result-detail.html index 4c60d80..6a8bd6b 100644 --- a/ref-test/app/admin/templates/admin/result-detail.html +++ b/ref-test/app/admin/templates/admin/result-detail.html @@ -114,7 +114,7 @@ {{ scores.scored }} - {{scores.max}} + {{ scores.max }} {% endfor %} @@ -131,6 +131,7 @@
    + View Questions @@ -146,7 +147,7 @@ {% for question, answer in entry.answers.items() %}
    - {{ question }} + {{ question|int + 1 }} {{ answers[question|int][answer|int] }} From be5343a4bd3ed191c056ad682d19520c4d968e83 Mon Sep 17 00:00:00 2001 From: Vivek Santayana Date: Wed, 17 Aug 2022 16:37:03 +0100 Subject: [PATCH 5/6] Added decorator to test availability of datasets Used decorator tool to validate dataset exists on views --- ref-test/app/admin/views.py | 7 ++----- ref-test/app/editor/views.py | 2 ++ ref-test/app/tools/data.py | 17 ++++++++++++++++- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/ref-test/app/admin/views.py b/ref-test/app/admin/views.py index 39a2599..d8463ef 100644 --- a/ref-test/app/admin/views.py +++ b/ref-test/app/admin/views.py @@ -2,7 +2,7 @@ from ..forms.admin import AddTimeAdjustment, CreateTest, CreateUser, DeleteUser, from ..models import Dataset, Entry, Test, User from ..tools.auth import disable_if_logged_in, require_account_creation from ..tools.forms import get_dataset_choices, get_time_options, send_errors_to_client -from ..tools.data import check_is_json, validate_json +from ..tools.data import check_dataset_exists, check_is_json, validate_json from ..tools.test import answer_options, get_correct_answers from flask import abort, Blueprint, jsonify, render_template, redirect, request, send_file, session @@ -247,15 +247,12 @@ def _download(id:str): @admin.route('/tests//', methods=['GET']) @admin.route('/tests/', methods=['GET']) @login_required +@check_dataset_exists def _tests(filter:str=None): - datasets = Dataset.query.all() tests = None _tests = Test.query.all() form = None now = datetime.now() - if not datasets: - flash('There are no available question datasets. Please upload a question dataset in order to set up an exam.', 'error') - return redirect(url_for('admin._questions')) if filter not in ['create','active','scheduled','expired','all']: return redirect(url_for('admin._tests', filter='active')) if filter == 'create': form = CreateTest() diff --git a/ref-test/app/editor/views.py b/ref-test/app/editor/views.py index b2c1887..c002d1d 100644 --- a/ref-test/app/editor/views.py +++ b/ref-test/app/editor/views.py @@ -1,6 +1,7 @@ from ..forms.admin import EditDataset from ..models import Dataset, User from ..tools.forms import get_dataset_choices, send_errors_to_client +from ..tools.data import check_dataset_exists from flask import Blueprint, flash, jsonify, redirect, render_template, request from flask.helpers import url_for @@ -27,6 +28,7 @@ def _editor(): return render_template('/editor/index.html', form=form) @editor.route('//') +@check_dataset_exists @login_required def _editor_console(id:str=None): dataset = Dataset.query.filter_by(id=id).first() diff --git a/ref-test/app/tools/data.py b/ref-test/app/tools/data.py index 7eab4d9..c4bfce6 100644 --- a/ref-test/app/tools/data.py +++ b/ref-test/app/tools/data.py @@ -1,8 +1,13 @@ +from ..models import Dataset + from flask import current_app as app +from flask import flash, redirect +from flask.helpers import url_for import json from pathlib import Path from random import shuffle +from functools import wraps def load(filename:str): data_dir = Path(app.config.get('DATA')) @@ -66,4 +71,14 @@ def get_tag_list(dataset:list): if block['type'] == 'question': output = list(set(output) | set(block['tags'])) if block['type'] == 'block': for question in block['questions']: output = list(set(output) | set(question['tags'])) - return output \ No newline at end of file + return output + +def check_dataset_exists(function): + @wraps(function) + def wrapper(*args, **kwargs): + datasets = Dataset.query.all() + if not datasets: + flash('There are no available question datasets. Please upload a question dataset first, or use the question editor to create a new dataset.', 'error') + return redirect(url_for('admin._questions')) + return function(*args, **kwargs) + return wrapper \ No newline at end of file From f14085f4c1d76cf7d20181e365d60bae929a49bb Mon Sep 17 00:00:00 2001 From: Vivek Santayana Date: Wed, 17 Aug 2022 16:38:31 +0100 Subject: [PATCH 6/6] Typo correction --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4b77a35..0427bcb 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ The exam client is made with accessibility in mind, and has been designed to be ## Set Up and Installation -The clien is designed to work on a server. +The app is designed to be hosted on a server. ### Pre-Requisites