Added question viewer functionality
Added view questions panel to editor interface Added view questions section of web site Added links to navbars
This commit is contained in:
		
							
								
								
									
										0
									
								
								ref-test/app/view/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								ref-test/app/view/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										260
									
								
								ref-test/app/view/static/css/style.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										260
									
								
								ref-test/app/view/static/css/style.css
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,260 @@
 | 
			
		||||
body {
 | 
			
		||||
    padding: 80px 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.site-footer {
 | 
			
		||||
    background-color: lightgray;
 | 
			
		||||
    font-size: small;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.site-footer p {
 | 
			
		||||
    margin: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.form-container {
 | 
			
		||||
    display: -ms-flexbox;
 | 
			
		||||
    display: flex;
 | 
			
		||||
    -ms-flex-align: center;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    padding-top: 40px;
 | 
			
		||||
    padding-bottom: 40px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.form-display {
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    max-width: 420px;
 | 
			
		||||
    padding: 15px;
 | 
			
		||||
    margin: auto;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.form-heading {
 | 
			
		||||
    margin-bottom: 2rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.form-label-group {
 | 
			
		||||
    position: relative;
 | 
			
		||||
    margin-bottom: 2rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.form-label-group input,
 | 
			
		||||
.form-label-group label {
 | 
			
		||||
    padding: var(--input-padding-y) var(--input-padding-x);
 | 
			
		||||
    font-size: 16pt;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.form-label-group label {
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    top: 0;
 | 
			
		||||
    left: 0;
 | 
			
		||||
    display: block;
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    margin-bottom: 0; /* Override default `<label>` margin */
 | 
			
		||||
    line-height: 1.5;
 | 
			
		||||
    color: #495057;
 | 
			
		||||
    cursor: text; /* Match the input under the label */
 | 
			
		||||
    border: 1px solid transparent;
 | 
			
		||||
    border-radius: .25rem;
 | 
			
		||||
    transition: all .1s ease-in-out;
 | 
			
		||||
    z-index: -1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.form-label-group input {
 | 
			
		||||
    background-color: transparent;
 | 
			
		||||
    border: none;
 | 
			
		||||
    border-radius: 0%;
 | 
			
		||||
    border-bottom: 2px solid #585858;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.form-label-group input:active, .form-label-group input:focus {
 | 
			
		||||
    background-color: transparent;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.form-label-group input::-webkit-input-placeholder {
 | 
			
		||||
    color: transparent;
 | 
			
		||||
}
 | 
			
		||||
  
 | 
			
		||||
.form-label-group input:-ms-input-placeholder {
 | 
			
		||||
    color: transparent;
 | 
			
		||||
}
 | 
			
		||||
  
 | 
			
		||||
.form-label-group input::-ms-input-placeholder {
 | 
			
		||||
    color: transparent;
 | 
			
		||||
}
 | 
			
		||||
  
 | 
			
		||||
.form-label-group input::-moz-placeholder {
 | 
			
		||||
    color: transparent;
 | 
			
		||||
}
 | 
			
		||||
  
 | 
			
		||||
.form-label-group input::placeholder {
 | 
			
		||||
    color: transparent;
 | 
			
		||||
}
 | 
			
		||||
  
 | 
			
		||||
.form-label-group input:not(:placeholder-shown) {
 | 
			
		||||
    padding-top: calc(var(--input-padding-y) + var(--input-padding-y) * (2 / 3));
 | 
			
		||||
    padding-bottom: calc(var(--input-padding-y) / 3);
 | 
			
		||||
}
 | 
			
		||||
  
 | 
			
		||||
.form-label-group input:not(:placeholder-shown) ~ label {
 | 
			
		||||
    padding-top: calc(var(--input-padding-y) / 3);
 | 
			
		||||
    padding-bottom: calc(var(--input-padding-y) / 3);
 | 
			
		||||
    font-size: 12px;
 | 
			
		||||
    color: #777;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.form-check {
 | 
			
		||||
    margin-bottom: 2rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.checkbox input {
 | 
			
		||||
    transform: scale(1.5);
 | 
			
		||||
    margin-right: 1rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.signin-forgot-password {
 | 
			
		||||
    font-size: 14pt;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.form-submission-button {
 | 
			
		||||
    margin-bottom: 2rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.form-submission-button button, .form-submission-button a {
 | 
			
		||||
    margin: 1rem;
 | 
			
		||||
    vertical-align: middle;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.form-submission-button button span, .form-submission-button button svg, .form-submission-button a span, .form-submission-button a svg {
 | 
			
		||||
    margin: 0 2px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
table.dataTable {
 | 
			
		||||
    border-collapse: collapse;
 | 
			
		||||
    width: 100%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.table-row {
 | 
			
		||||
    vertical-align: middle;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.row-actions {
 | 
			
		||||
    text-align: center;
 | 
			
		||||
    white-space: nowrap;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.dataTables_wrapper .dt-buttons {
 | 
			
		||||
    left: 50%;
 | 
			
		||||
    transform: translateX(-50%);
 | 
			
		||||
    float:none;  
 | 
			
		||||
    text-align:center;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.row-actions button, .row-actions a {
 | 
			
		||||
    margin: 0px 5px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#cookie-alert {
 | 
			
		||||
    padding-right: 16px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#dismiss-cookie-alert {
 | 
			
		||||
    margin-top: 16px;
 | 
			
		||||
    width: fit-content;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.alert-db-empty {
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    max-width: 720px;
 | 
			
		||||
    font-size: 14pt;
 | 
			
		||||
    margin: 20px auto;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.form-date-input, .form-select-input {
 | 
			
		||||
    position: relative;
 | 
			
		||||
    margin: 2rem 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.form-date-input input,
 | 
			
		||||
.form-date-input label, .form-select-input select, .form-select-input label {
 | 
			
		||||
    padding: var(--input-padding-y) var(--input-padding-x);
 | 
			
		||||
    font-size: 16pt;
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    background-color: transparent;
 | 
			
		||||
    border: none;
 | 
			
		||||
    border-bottom: 2px solid #585858;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.datepicker::-webkit-calendar-picker-indicator {    
 | 
			
		||||
    border: 1px;
 | 
			
		||||
    border-color: gray;
 | 
			
		||||
    border-radius: 10%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.form-date-input label, .form-select-input label {
 | 
			
		||||
    /* position: absolute; */
 | 
			
		||||
    /* top: 0;
 | 
			
		||||
    left: 0; */
 | 
			
		||||
    display: block;
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    margin-bottom: 0; /* Override default `<label>` margin */
 | 
			
		||||
    line-height: 1.5;
 | 
			
		||||
    color: #495057;
 | 
			
		||||
    cursor: text; /* Match the input under the label */
 | 
			
		||||
    border: 1px solid transparent;
 | 
			
		||||
    border-radius: .25rem;
 | 
			
		||||
    transition: all .1s ease-in-out;
 | 
			
		||||
    z-index: -1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.button-icon {
 | 
			
		||||
    font-size: 20px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.form-upload {
 | 
			
		||||
    margin: 2rem 0;
 | 
			
		||||
    font-size: 14pt;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.result-action-buttons, .test-action {
 | 
			
		||||
    margin: 5px auto;
 | 
			
		||||
    width: fit-content;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.accordion-item {
 | 
			
		||||
    background-color: unset;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Change Autocomplete styles in Chrome*/
 | 
			
		||||
input:-webkit-autofill,
 | 
			
		||||
input:-webkit-autofill:hover, 
 | 
			
		||||
input:-webkit-autofill:focus,
 | 
			
		||||
textarea:-webkit-autofill,
 | 
			
		||||
textarea:-webkit-autofill:hover,
 | 
			
		||||
textarea:-webkit-autofill:focus,
 | 
			
		||||
select:-webkit-autofill,
 | 
			
		||||
select:-webkit-autofill:hover,
 | 
			
		||||
select:-webkit-autofill:focus {
 | 
			
		||||
  transition: background-color 5000s ease-in-out 0s;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Fallback for Edge
 | 
			
		||||
-------------------------------------------------- */
 | 
			
		||||
@supports (-ms-ime-align: auto) {
 | 
			
		||||
    .form-label-group label {
 | 
			
		||||
      display: none;
 | 
			
		||||
    }
 | 
			
		||||
    .form-label-group input::-ms-input-placeholder {
 | 
			
		||||
      color: #777;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
  
 | 
			
		||||
  /* Fallback for IE
 | 
			
		||||
  -------------------------------------------------- */
 | 
			
		||||
@media all and (-ms-high-contrast: none), (-ms-high-contrast: active) {
 | 
			
		||||
    .form-label-group label {
 | 
			
		||||
        display: none;
 | 
			
		||||
    }
 | 
			
		||||
    .form-label-group input:-ms-input-placeholder {
 | 
			
		||||
        color: #777;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
  
 | 
			
		||||
							
								
								
									
										30
									
								
								ref-test/app/view/static/css/view.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								ref-test/app/view/static/css/view.css
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,30 @@
 | 
			
		||||
.info-panel {
 | 
			
		||||
    display: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.control-panel {
 | 
			
		||||
    margin-left: auto;
 | 
			
		||||
    margin-right: 0;
 | 
			
		||||
    width:fit-content;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#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;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										2
									
								
								ref-test/app/view/static/js/jquery-3.6.0.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								ref-test/app/view/static/js/jquery-3.6.0.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										115
									
								
								ref-test/app/view/static/js/script.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										115
									
								
								ref-test/app/view/static/js/script.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,115 @@
 | 
			
		||||
// Menu Highlight Scripts
 | 
			
		||||
const menuItems = document.getElementsByClassName('nav-link')
 | 
			
		||||
for(let i = 0; i < menuItems.length; i++) {
 | 
			
		||||
    if(menuItems[i].pathname == window.location.pathname) {
 | 
			
		||||
        menuItems[i].classList.add('active')
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
const dropdownItems = document.getElementsByClassName('dropdown-item')
 | 
			
		||||
for(let i = 0; i< dropdownItems.length; i++) {
 | 
			
		||||
    if(dropdownItems[i].pathname == window.location.pathname) {
 | 
			
		||||
        dropdownItems[i].classList.add('active')
 | 
			
		||||
        $( "#" + dropdownItems[i].id ).closest( '.dropdown' ).find('.dropdown-toggle').addClass('active')
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// General Post Method Form Processing Script
 | 
			
		||||
$('form.form-post').submit(function(event) {
 | 
			
		||||
    
 | 
			
		||||
    var $form = $(this)
 | 
			
		||||
    var data = $form.serialize()
 | 
			
		||||
    var url = $(this).prop('action')
 | 
			
		||||
    var rel_success = $(this).data('rel-success')
 | 
			
		||||
 | 
			
		||||
    $.ajax({
 | 
			
		||||
        url: url,
 | 
			
		||||
        type: 'POST',
 | 
			
		||||
        data: data,
 | 
			
		||||
        dataType: 'json',
 | 
			
		||||
        success: function(response) {
 | 
			
		||||
            if (response.redirect_to) {
 | 
			
		||||
                window.location.href = response.redirect_to
 | 
			
		||||
            }
 | 
			
		||||
            else {
 | 
			
		||||
                window.location.href = rel_success
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        error: function(response) {
 | 
			
		||||
            error_response(response)
 | 
			
		||||
        }
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    event.preventDefault()
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
function error_response(response) {
 | 
			
		||||
 | 
			
		||||
    const $alert = $("#alert-box")
 | 
			
		||||
    $alert.html('')
 | 
			
		||||
 | 
			
		||||
    if (typeof response.responseJSON.error === 'string' || response.responseJSON.error instanceof String) {
 | 
			
		||||
        $alert.html(`
 | 
			
		||||
        <div class="alert alert-danger alert-dismissible fade show" role="alert">
 | 
			
		||||
            <i class="bi bi-exclamation-triangle-fill" title="Danger"></i>
 | 
			
		||||
            ${response.responseJSON.error}
 | 
			
		||||
            <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
 | 
			
		||||
        </div>
 | 
			
		||||
        `)
 | 
			
		||||
    } else if (response.responseJSON.error instanceof Array) {
 | 
			
		||||
        var output = ''
 | 
			
		||||
        for (let i = 0; i < response.responseJSON.error.length; i ++) {
 | 
			
		||||
            output += `
 | 
			
		||||
            <div class="alert alert-danger alert-dismissible fade show" role="alert">
 | 
			
		||||
                <i class="bi bi-exclamation-triangle-fill" title="Danger"></i>
 | 
			
		||||
                ${response.responseJSON.error[i]}
 | 
			
		||||
                <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
 | 
			
		||||
            </div>
 | 
			
		||||
            `
 | 
			
		||||
            $alert.html(output)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $alert.focus()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Dismiss Cookie Alert
 | 
			
		||||
$('#dismiss-cookie-alert').click(function(event){
 | 
			
		||||
 | 
			
		||||
    $.ajax({
 | 
			
		||||
        url: '/cookies/',
 | 
			
		||||
        type: 'POST',
 | 
			
		||||
        data: {
 | 
			
		||||
            time: Date.now()
 | 
			
		||||
        },
 | 
			
		||||
        dataType: 'json',
 | 
			
		||||
        success: function(response){
 | 
			
		||||
            console.log(response)
 | 
			
		||||
        },
 | 
			
		||||
        error: function(response){
 | 
			
		||||
            console.log(response)
 | 
			
		||||
        }
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    event.preventDefault()
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
// Create New Dataset
 | 
			
		||||
$('.create-new-dataset').click(function(event){
 | 
			
		||||
    $.ajax({
 | 
			
		||||
        url: '/api/editor/new/',
 | 
			
		||||
        type: 'POST',
 | 
			
		||||
        data: {
 | 
			
		||||
            time: Date.now()
 | 
			
		||||
        },
 | 
			
		||||
        dataType: 'json',
 | 
			
		||||
        success: function(response){
 | 
			
		||||
            if (response.redirect_to) {
 | 
			
		||||
                window.location.href = response.redirect_to
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        error: function(response){
 | 
			
		||||
            console.log(response)
 | 
			
		||||
        }
 | 
			
		||||
    })
 | 
			
		||||
    event.preventDefault()
 | 
			
		||||
})
 | 
			
		||||
							
								
								
									
										130
									
								
								ref-test/app/view/static/js/view.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										130
									
								
								ref-test/app/view/static/js/view.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,130 @@
 | 
			
		||||
// Variable Declarations
 | 
			
		||||
const $control_panel = $('.control-panel')
 | 
			
		||||
const $info_panel = $('.info-panel')
 | 
			
		||||
const $viewer_panel = $('.viewer-panel')
 | 
			
		||||
 | 
			
		||||
var element_index = 0
 | 
			
		||||
 | 
			
		||||
// Info Button Listener
 | 
			
		||||
$control_panel.find('button').click(function(event){
 | 
			
		||||
    if ($info_panel.is(":hidden")) {
 | 
			
		||||
        $viewer_panel.hide()
 | 
			
		||||
        $info_panel.fadeIn()
 | 
			
		||||
        $(this).addClass('active')
 | 
			
		||||
    } else {
 | 
			
		||||
        $info_panel.hide()
 | 
			
		||||
        $viewer_panel.fadeIn()
 | 
			
		||||
        $(this).removeClass('active')
 | 
			
		||||
    }
 | 
			
		||||
    event.preventDefault()
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
function parse_data(data) {
 | 
			
		||||
    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 = `<strong>Question ${block['q_no'] + 1}.</strong> ${block['text']}`
 | 
			
		||||
            obj.append(text)
 | 
			
		||||
            question_body = document.createElement('div')
 | 
			
		||||
            question_body.className ='question-body'
 | 
			
		||||
            type = document.createElement('p')
 | 
			
		||||
            type.innerHTML = `<strong>Question Type:</strong> ${block['q_type']}`
 | 
			
		||||
            question_body.append(type)
 | 
			
		||||
            options = document.createElement('p')
 | 
			
		||||
            options.innerHTML = '<strong>Options:</strong>'
 | 
			
		||||
            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 += ' <span class="badge rounded-pill bg-success">Correct</span>'
 | 
			
		||||
                }
 | 
			
		||||
                option_list.append(option)
 | 
			
		||||
            }
 | 
			
		||||
            options.append(option_list)
 | 
			
		||||
            question_body.append(options)
 | 
			
		||||
            tags = document.createElement('p')
 | 
			
		||||
            tags.innerHTML = `<strong>Tags:</strong>`
 | 
			
		||||
            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 = `<strong>Block ${i+1}.</strong> ${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 = `<strong>Question ${question['q_no'] + 1}.</strong> ${question['text']}`
 | 
			
		||||
                block_question.append(text)
 | 
			
		||||
                question_body = document.createElement('div')
 | 
			
		||||
                question_body.className ='question-body'
 | 
			
		||||
                type = document.createElement('p')
 | 
			
		||||
                type.innerHTML = `<strong>Question Type:</strong> ${question['q_type']}`
 | 
			
		||||
                question_body.append(type)
 | 
			
		||||
                options = document.createElement('p')
 | 
			
		||||
                options.innerHTML = '<strong>Options:</strong>'
 | 
			
		||||
                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 += ' <span class="badge rounded-pill bg-success">Correct</span>'
 | 
			
		||||
                    }
 | 
			
		||||
                    option_list.append(option)
 | 
			
		||||
                }
 | 
			
		||||
                options.append(option_list)
 | 
			
		||||
                question_body.append(options)
 | 
			
		||||
                tags = document.createElement('p')
 | 
			
		||||
                tags.innerHTML = `<strong>Tags:</strong>`
 | 
			
		||||
                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)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Fetch data once page finishes loading
 | 
			
		||||
$(window).on('load', function() {
 | 
			
		||||
    $.ajax({
 | 
			
		||||
        url: target,
 | 
			
		||||
        type: 'POST',
 | 
			
		||||
        data: JSON.stringify({
 | 
			
		||||
            'id': id,
 | 
			
		||||
            'action': 'fetch'
 | 
			
		||||
        }),
 | 
			
		||||
        contentType: 'application/json',
 | 
			
		||||
        success: function(response) {
 | 
			
		||||
            parse_data(response['data'])
 | 
			
		||||
        },
 | 
			
		||||
        error: function(response) {
 | 
			
		||||
            console.log(response)
 | 
			
		||||
        }
 | 
			
		||||
    })
 | 
			
		||||
})
 | 
			
		||||
							
								
								
									
										84
									
								
								ref-test/app/view/templates/view/components/base.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								ref-test/app/view/templates/view/components/base.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,84 @@
 | 
			
		||||
<!DOCTYPE html>
 | 
			
		||||
<html>
 | 
			
		||||
    <head>
 | 
			
		||||
        <meta charset="utf8" />
 | 
			
		||||
        <meta name="viewport" content="width=device-width, initial-scale=1" />
 | 
			
		||||
        <link
 | 
			
		||||
            href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css"
 | 
			
		||||
            rel="stylesheet"
 | 
			
		||||
            integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3"
 | 
			
		||||
            crossorigin="anonymous">
 | 
			
		||||
        <link
 | 
			
		||||
            rel="stylesheet"
 | 
			
		||||
            href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.7.1/font/bootstrap-icons.css">
 | 
			
		||||
        <link 
 | 
			
		||||
            rel="stylesheet"
 | 
			
		||||
            href="{{ url_for('.static', filename='css/style.css') }}"
 | 
			
		||||
        />
 | 
			
		||||
        <link 
 | 
			
		||||
            rel="stylesheet"
 | 
			
		||||
            href="{{ url_for('.static', filename='css/view.css') }}"
 | 
			
		||||
        />
 | 
			
		||||
        {% block style %}
 | 
			
		||||
        {% endblock %}
 | 
			
		||||
        <title>{% block title %} SKA Referee Test | Admin Console {% endblock %}</title>
 | 
			
		||||
        {% include "view/components/og-meta.html" %}
 | 
			
		||||
    </head>
 | 
			
		||||
    <body class="bg-light">
 | 
			
		||||
 | 
			
		||||
        {% block navbar %}
 | 
			
		||||
            {% include "view/components/navbar.html" %}
 | 
			
		||||
        {% endblock %}
 | 
			
		||||
 | 
			
		||||
        <div class="container">
 | 
			
		||||
            {% block top_alerts %}
 | 
			
		||||
                {% include "view/components/server-alerts.html" %}
 | 
			
		||||
            {% endblock %}
 | 
			
		||||
            {% block content %}{% endblock %}
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <footer class="container site-footer mt-5">
 | 
			
		||||
            {% block footer %}
 | 
			
		||||
                {% include "view/components/footer.html" %}
 | 
			
		||||
            {% endblock %}
 | 
			
		||||
        </footer>
 | 
			
		||||
 | 
			
		||||
        <!-- JQuery, Popper, and Bootstrap js dependencies -->
 | 
			
		||||
        <script
 | 
			
		||||
            src="https://code.jquery.com/jquery-3.6.0.min.js"
 | 
			
		||||
            integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4="
 | 
			
		||||
            crossorigin="anonymous">
 | 
			
		||||
        </script>
 | 
			
		||||
        <script>
 | 
			
		||||
            window.jQuery || document.write(`<script src="{{ url_for('.static', filename='js/jquery-3.6.0.min.js') }}"><\/script>`)
 | 
			
		||||
        </script>
 | 
			
		||||
        <script
 | 
			
		||||
            src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.10.2/dist/umd/popper.min.js"
 | 
			
		||||
            integrity="sha384-7+zCNj/IqJ95wo16oMtfsKbZ9ccEh31eOz1HGyDuCQ6wgnyJNSYdrPa03rtR1zdB"
 | 
			
		||||
            crossorigin="anonymous">
 | 
			
		||||
        </script>
 | 
			
		||||
        <script
 | 
			
		||||
            src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.min.js"
 | 
			
		||||
            integrity="sha384-QJHtvGhmr9XOIpI6YVutG+2QOK9T+ZnN4kzFN1RtK3zEFEIsxhlmWl5/YESvpZ13"
 | 
			
		||||
            crossorigin="anonymous"
 | 
			
		||||
        ></script>
 | 
			
		||||
        <!-- Custom js -->
 | 
			
		||||
        <script type="text/javascript">
 | 
			
		||||
            var csrf_token = "{{ csrf_token() }}";
 | 
			
		||||
        
 | 
			
		||||
            $.ajaxSetup({
 | 
			
		||||
                beforeSend: function(xhr, settings) {
 | 
			
		||||
                    if (!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(settings.type) && !this.crossDomain) {
 | 
			
		||||
                        xhr.setRequestHeader("X-CSRFToken", csrf_token);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        </script>
 | 
			
		||||
        <script
 | 
			
		||||
            type="text/javascript"
 | 
			
		||||
            src="{{ url_for('.static', filename='js/script.js') }}"
 | 
			
		||||
        ></script>
 | 
			
		||||
        {% block script %}
 | 
			
		||||
        {% endblock %}
 | 
			
		||||
    </body>
 | 
			
		||||
</html>
 | 
			
		||||
@@ -0,0 +1 @@
 | 
			
		||||
<div id="alert-box" tabindex="-1"></div>
 | 
			
		||||
							
								
								
									
										28
									
								
								ref-test/app/view/templates/view/components/datatable.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								ref-test/app/view/templates/view/components/datatable.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,28 @@
 | 
			
		||||
{% extends "view/components/base.html" %}
 | 
			
		||||
{% block datatable_css %}
 | 
			
		||||
    <link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/1.11.3/css/dataTables.bootstrap5.min.css"/>
 | 
			
		||||
    <link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/buttons/2.0.1/css/buttons.bootstrap5.min.css"/>
 | 
			
		||||
    <link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/colreorder/1.5.5/css/colReorder.bootstrap5.min.css"/>
 | 
			
		||||
    <link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/fixedheader/3.2.0/css/fixedHeader.bootstrap5.min.css"/>
 | 
			
		||||
    <link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/keytable/2.6.4/css/keyTable.bootstrap5.min.css"/>
 | 
			
		||||
    <link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/responsive/2.2.9/css/responsive.bootstrap5.min.css"/>
 | 
			
		||||
    <link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/searchbuilder/1.3.0/css/searchBuilder.dataTables.min.css"/>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
{% block datatable_scripts %}
 | 
			
		||||
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jszip/2.5.0/jszip.min.js"></script>
 | 
			
		||||
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.1.36/pdfmake.min.js"></script>
 | 
			
		||||
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.1.36/vfs_fonts.js"></script>
 | 
			
		||||
<script type="text/javascript" src="https://cdn.datatables.net/1.11.3/js/jquery.dataTables.min.js"></script>
 | 
			
		||||
<script type="text/javascript" src="https://cdn.datatables.net/1.11.3/js/dataTables.bootstrap5.min.js"></script>
 | 
			
		||||
<script type="text/javascript" src="https://cdn.datatables.net/buttons/2.0.1/js/dataTables.buttons.min.js"></script>
 | 
			
		||||
<script type="text/javascript" src="https://cdn.datatables.net/buttons/2.0.1/js/buttons.bootstrap5.min.js"></script>
 | 
			
		||||
<script type="text/javascript" src="https://cdn.datatables.net/buttons/2.0.1/js/buttons.colVis.min.js"></script>
 | 
			
		||||
<script type="text/javascript" src="https://cdn.datatables.net/buttons/2.0.1/js/buttons.html5.min.js"></script>
 | 
			
		||||
<script type="text/javascript" src="https://cdn.datatables.net/buttons/2.0.1/js/buttons.print.min.js"></script>
 | 
			
		||||
<script type="text/javascript" src="https://cdn.datatables.net/colreorder/1.5.5/js/dataTables.colReorder.min.js"></script>
 | 
			
		||||
<script type="text/javascript" src="https://cdn.datatables.net/fixedheader/3.2.0/js/dataTables.fixedHeader.min.js"></script>
 | 
			
		||||
<script type="text/javascript" src="https://cdn.datatables.net/keytable/2.6.4/js/dataTables.keyTable.min.js"></script>
 | 
			
		||||
<script type="text/javascript" src="https://cdn.datatables.net/responsive/2.2.9/js/dataTables.responsive.min.js"></script>
 | 
			
		||||
<script type="text/javascript" src="https://cdn.datatables.net/responsive/2.2.9/js/responsive.bootstrap5.js"></script>
 | 
			
		||||
<script type="text/javascript" src="https://cdn.datatables.net/searchbuilder/1.3.0/js/dataTables.searchBuilder.min.js"></script>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
							
								
								
									
										2
									
								
								ref-test/app/view/templates/view/components/footer.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								ref-test/app/view/templates/view/components/footer.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,2 @@
 | 
			
		||||
<p>This web app was developed by Vivek Santayana. The source code for the web app, excluding any data pertaining to the questions in the quiz, is freely available at <a href="https://git.vsnt.uk/viveksantayana/ska-referee-test">Vivek’s personal GIT repository</a> under an MIT License.</p>
 | 
			
		||||
<p>All questions in the test are © The Scottish Korfball Association {{ now.year }}. All rights are reserved.</p>
 | 
			
		||||
@@ -0,0 +1,4 @@
 | 
			
		||||
{% extends "view/components/base.html" %}
 | 
			
		||||
{% import "bootstrap/wtf.html" as wtf %}
 | 
			
		||||
{% block top_alerts %}
 | 
			
		||||
{% endblock %}
 | 
			
		||||
							
								
								
									
										117
									
								
								ref-test/app/view/templates/view/components/navbar.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								ref-test/app/view/templates/view/components/navbar.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,117 @@
 | 
			
		||||
<nav class="navbar fixed-top navbar-expand-md navbar-dark bg-dark">
 | 
			
		||||
    <div class="container">
 | 
			
		||||
        <a href="{{ url_for('admin._home') }}" class="navbar-brand mb-0 h1">RefTest (Beta) | Admin</a>
 | 
			
		||||
        <button
 | 
			
		||||
            class="navbar-toggler"
 | 
			
		||||
            type="button"
 | 
			
		||||
            data-bs-toggle="collapse"
 | 
			
		||||
            data-bs-target="#navbar"
 | 
			
		||||
            aria-controls="navbar"
 | 
			
		||||
            aria-expanded="false"
 | 
			
		||||
            aria-label="Toggle Navigation"
 | 
			
		||||
        >
 | 
			
		||||
            <span class="navbar-toggler-icon"></span>
 | 
			
		||||
        </button>
 | 
			
		||||
        <div class="collapse navbar-collapse justify-content-end" id="navbar">
 | 
			
		||||
            <ul class="navbar-nav">
 | 
			
		||||
                {% if not current_user.is_authenticated %}
 | 
			
		||||
                    <li class="nav-item" id="nav-login">
 | 
			
		||||
                        <a href="{{ url_for('admin._login') }}" id="link-login" class="nav-link">Log In</a>
 | 
			
		||||
                    </li>
 | 
			
		||||
                {% endif %}
 | 
			
		||||
                {% if current_user.is_authenticated %}
 | 
			
		||||
                    <li class="nav-item" id="nav-results">
 | 
			
		||||
                        <a href="{{ url_for('admin._view_entries') }}" id="link-results" class="nav-link">View Results</a>
 | 
			
		||||
                    </li>
 | 
			
		||||
                    <li class="nav-item dropdown" id="nav-tests">
 | 
			
		||||
                        <a
 | 
			
		||||
                            class="nav-link dropdown-toggle"
 | 
			
		||||
                            id="dropdown-tests"
 | 
			
		||||
                            role="button"
 | 
			
		||||
                            href="{{ url_for('admin._tests') }}"
 | 
			
		||||
                            data-bs-toggle="dropdown"
 | 
			
		||||
                            aria-expanded="false"
 | 
			
		||||
                        >
 | 
			
		||||
                            Exams
 | 
			
		||||
                        </a>
 | 
			
		||||
                        <ul
 | 
			
		||||
                            class="dropdown-menu"
 | 
			
		||||
                            aria-labelledby="dropdown-settings"
 | 
			
		||||
                        >
 | 
			
		||||
                            <li>
 | 
			
		||||
                                <a href="{{ url_for('admin._tests', filter='active') }}" id="link-active" class="dropdown-item">Active</a>
 | 
			
		||||
                            </li>
 | 
			
		||||
                            <li>
 | 
			
		||||
                                <a href="{{ url_for('admin._tests', filter='scheduled') }}" id="link-scheduled" class="dropdown-item">Scheduled</a>
 | 
			
		||||
                            </li>
 | 
			
		||||
                            <li>
 | 
			
		||||
                                <a href="{{ url_for('admin._tests', filter='expired') }}" id="link-expired" class="dropdown-item">Expired</a>
 | 
			
		||||
                            </li>
 | 
			
		||||
                            <li>
 | 
			
		||||
                                <a href="{{ url_for('admin._tests', filter='all') }}" id="link-all" class="dropdown-item">All</a>
 | 
			
		||||
                            </li>
 | 
			
		||||
                            <li>
 | 
			
		||||
                                <a href="{{ url_for('admin._tests', filter='create') }}" id="link-create" class="dropdown-item">Create</a>
 | 
			
		||||
                            </li>
 | 
			
		||||
                        </ul>
 | 
			
		||||
                    </li>
 | 
			
		||||
                    <li class="nav-item dropdown" id="nav-settings">
 | 
			
		||||
                        <a
 | 
			
		||||
                            class="nav-link dropdown-toggle"
 | 
			
		||||
                            id="dropdown-account"
 | 
			
		||||
                            role="button"
 | 
			
		||||
                            href="{{ url_for('admin._settings') }}"
 | 
			
		||||
                            data-bs-toggle="dropdown"
 | 
			
		||||
                            aria-expanded="false"
 | 
			
		||||
                        >
 | 
			
		||||
                            Settings
 | 
			
		||||
                        </a>
 | 
			
		||||
                        <ul
 | 
			
		||||
                            class="dropdown-menu"
 | 
			
		||||
                            aria-labelledby="dropdown-settings"
 | 
			
		||||
                        >
 | 
			
		||||
                            <li>
 | 
			
		||||
                                <a href="{{ url_for('admin._settings') }}" id="link-settings" class="dropdown-item">View Settings</a>
 | 
			
		||||
                            </li>
 | 
			
		||||
                            <li>
 | 
			
		||||
                                <a href="{{ url_for('admin._users') }}" id="link-users" class="dropdown-item">Users</a>
 | 
			
		||||
                            </li>
 | 
			
		||||
                            <li>
 | 
			
		||||
                                <a href="{{ url_for('admin._questions') }}" id="link-questions" class="dropdown-item">Manage Questions</a>
 | 
			
		||||
                            </li>
 | 
			
		||||
                            <li>
 | 
			
		||||
                                <a href="{{ url_for('view._view') }}" id="link-editor" class="dropdown-item">View Questions</a>
 | 
			
		||||
                            </li>
 | 
			
		||||
                            <li>
 | 
			
		||||
                                <a href="{{ url_for('editor._editor') }}" id="link-editor" class="dropdown-item">Edit Questions</a>
 | 
			
		||||
                            </li>
 | 
			
		||||
                        </ul>
 | 
			
		||||
                    </li>
 | 
			
		||||
                    <li class="nav-item dropdown" id="nav-account">
 | 
			
		||||
                        <a
 | 
			
		||||
                            class="nav-link dropdown-toggle"
 | 
			
		||||
                            id="dropdown-account"
 | 
			
		||||
                            role="button"
 | 
			
		||||
                            href="{{ url_for('admin._update_user', id=current_user.id) }}"
 | 
			
		||||
                            data-bs-toggle="dropdown"
 | 
			
		||||
                            aria-expanded="false"
 | 
			
		||||
                        >
 | 
			
		||||
                            Account
 | 
			
		||||
                        </a>
 | 
			
		||||
                        <ul
 | 
			
		||||
                            class="dropdown-menu"
 | 
			
		||||
                            aria-labelledby="dropdown-account"
 | 
			
		||||
                        >
 | 
			
		||||
                            <li>
 | 
			
		||||
                                <a href="{{ url_for('admin._update_user', id=current_user.id) }}" id="link-account" class="dropdown-item">Account Settings</a>
 | 
			
		||||
                            </li>
 | 
			
		||||
                            <li>
 | 
			
		||||
                                <a href="{{ url_for('admin._logout') }}" id="link-logout" class="dropdown-item">Log Out</a>
 | 
			
		||||
                            </li>
 | 
			
		||||
                        </ul>
 | 
			
		||||
                    </li>
 | 
			
		||||
                {% endif %}
 | 
			
		||||
            </ul>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</nav>
 | 
			
		||||
							
								
								
									
										18
									
								
								ref-test/app/view/templates/view/components/og-meta.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								ref-test/app/view/templates/view/components/og-meta.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,18 @@
 | 
			
		||||
<meta name="description" content="A web app for taking the Scottish Korfball Association Refereeing Theory Exam on-line." />
 | 
			
		||||
<meta property="og:locale" content="en_UK" />
 | 
			
		||||
<meta property="og:type" content="website" />
 | 
			
		||||
<meta property="og:description" content="A web app for taking the Scottish Korfball Association Refereeing Theory Exam on-line." />
 | 
			
		||||
<meta property="og:url" content="{{ url_for(request.endpoint, _external = True, **(request.view_args or {})) }}" />
 | 
			
		||||
<meta property="og:site_name" content="Scottish Korfball Association Referee Theory Exam" />
 | 
			
		||||
<meta property="og:image" content="{{ url_for('.static', filename='favicon.png', _external = True) }}" />
 | 
			
		||||
<meta property="og:image:alt" content="Logo of the SKA Refereeing Exam App" />
 | 
			
		||||
<meta property="og:image:width" content="512" />
 | 
			
		||||
<meta property="og:image:height" content="512" />
 | 
			
		||||
<meta name="twitter:card" content="summary" />
 | 
			
		||||
<meta name="twitter:description" content="A web app for taking the Scottish Korfball Association Refereeing Theory Exam on-line." />
 | 
			
		||||
<meta name="twitter:image" content="{{ url_for('.static', filename='favicon.png', _external = True) }}" />
 | 
			
		||||
<meta name="twitter:image:alt" content="Logo of the SKA Refereeing Exam App" />
 | 
			
		||||
<meta name="twitter:creator" content="@viveksantayana" />
 | 
			
		||||
<meta name="twitter:site" content="@viveksantayana" />
 | 
			
		||||
<meta name="theme-color" content="#343a40" />
 | 
			
		||||
<link rel="shortcut icon" href="{{ url_for('.static', filename='favicon.ico') }}">
 | 
			
		||||
@@ -0,0 +1,23 @@
 | 
			
		||||
<div class="navbar navbar-expand-sm navbar-light bg-light">
 | 
			
		||||
    <div class="container-fluid">
 | 
			
		||||
    <div class="expand navbar-expand justify-content-center" id="navbar_secondary">
 | 
			
		||||
        <ul class="nav nav-pills">
 | 
			
		||||
            <li class="nav-item">
 | 
			
		||||
                <a class="nav-link" href="{{ url_for('admin._tests', filter='active') }}">Active</a>
 | 
			
		||||
            </li>
 | 
			
		||||
            <li class="nav-item">
 | 
			
		||||
                <a class="nav-link" href="{{ url_for('admin._tests', filter='scheduled') }}">Scheduled</a>
 | 
			
		||||
            </li>
 | 
			
		||||
            <li class="nav-item">
 | 
			
		||||
                <a class="nav-link" href="{{ url_for('admin._tests', filter='expired') }}">Expired</a>
 | 
			
		||||
            </li>
 | 
			
		||||
            <li class="nav-item">
 | 
			
		||||
                <a class="nav-link" href="{{ url_for('admin._tests', filter='all') }}">All</a>
 | 
			
		||||
            </li>
 | 
			
		||||
            <li class="nav-item">
 | 
			
		||||
                <a class="nav-link" href="{{ url_for('admin._tests', filter='create') }}">Create</a>
 | 
			
		||||
            </li>
 | 
			
		||||
        </ul>
 | 
			
		||||
    </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
@@ -0,0 +1,43 @@
 | 
			
		||||
{% with messages = get_flashed_messages(with_categories=true) %}
 | 
			
		||||
    {% if messages %}
 | 
			
		||||
        {% set cookie_flash_flag = namespace(value=False) %}
 | 
			
		||||
        {% for category, message in messages %}
 | 
			
		||||
            {% if category == "error" %}
 | 
			
		||||
                <div class="alert alert-danger alert-dismissible fade show" role="alert">
 | 
			
		||||
                    <i class="bi bi-exclamation-triangle-fill" title="Error" aria-title="Error"></i>
 | 
			
		||||
                    {{ message|safe }}
 | 
			
		||||
                    <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
 | 
			
		||||
                </div>
 | 
			
		||||
            {% elif category == "success" %}
 | 
			
		||||
                <div class="alert alert-success alert-dismissible fade show" role="alert">
 | 
			
		||||
                    <i class="bi bi-check2-circle" title="Success" aria-title="Success"></i>
 | 
			
		||||
                    {{ message|safe }}
 | 
			
		||||
                    <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
 | 
			
		||||
                </div>
 | 
			
		||||
            {% elif category == "warning" %}
 | 
			
		||||
                <div class="alert alert-warning alert-dismissible fade show" role="alert">
 | 
			
		||||
                    <i class="bi bi-info-circle-fill" aria-title="Warning" title="Warning"></i>
 | 
			
		||||
                    {{ message|safe }}
 | 
			
		||||
                    <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
 | 
			
		||||
                </div>
 | 
			
		||||
            {% elif category == "cookie_alert" %}
 | 
			
		||||
                {% if not cookie_flash_flag.value %}
 | 
			
		||||
                    <div class="alert alert-primary alert-dismissible fade show" id="cookie-alert" role="alert">
 | 
			
		||||
                        <i class="bi bi-info-circle-fill" title="Cookie Alert" aria-title="Cookie Alert"></i>
 | 
			
		||||
                        {{ message|safe }}
 | 
			
		||||
                        <div class="d-flex justify-content-center w-100">
 | 
			
		||||
                            <button type="button" id="dismiss-cookie-alert" class="btn btn-success" data-bs-dismiss="alert" aria-label="Close">Accept</button>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    {% set cookie_flash_flag.value = True %}
 | 
			
		||||
                {% endif %}
 | 
			
		||||
            {% else %}
 | 
			
		||||
                <div class="alert alert-primary alert-dismissible fade show" role="alert">
 | 
			
		||||
                    <i class="bi bi-info-circle-fill" title="Alert"></i>
 | 
			
		||||
                    {{ message|safe }}
 | 
			
		||||
                    <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
 | 
			
		||||
                </div>
 | 
			
		||||
            {% endif %}
 | 
			
		||||
        {% endfor %}
 | 
			
		||||
    {% endif %}
 | 
			
		||||
{% endwith %}
 | 
			
		||||
							
								
								
									
										116
									
								
								ref-test/app/view/templates/view/console.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										116
									
								
								ref-test/app/view/templates/view/console.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,116 @@
 | 
			
		||||
{% extends "view/components/base.html" %}
 | 
			
		||||
 | 
			
		||||
{% block style %}
 | 
			
		||||
<link 
 | 
			
		||||
    rel="stylesheet"
 | 
			
		||||
    href="{{ url_for('.static', filename='css/view.css') }}"
 | 
			
		||||
/>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
    <h1>View Questions</h1>
 | 
			
		||||
    <div class="container">
 | 
			
		||||
        <p class="lead">
 | 
			
		||||
            This page lists all the questions in the selected dataset.
 | 
			
		||||
        </p>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="container control-panel">
 | 
			
		||||
        <button class="btn btn-primary" aria-title="Information" title="Information"><i class="bi bi-info-circle-fill"></i></button>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="container info-panel">
 | 
			
		||||
        <h3>
 | 
			
		||||
            Information
 | 
			
		||||
        </h3>
 | 
			
		||||
        <p>
 | 
			
		||||
            Questions in the test are arranged in blocks. Blocks can be of two types: <strong>Blocks</strong> of multiple related questions, and <strong>Single Questions</strong> that are not part of a block.
 | 
			
		||||
            You can add, remove, or edit both Blockss and Questions through this editor.
 | 
			
		||||
        </p>
 | 
			
		||||
        <p>
 | 
			
		||||
            <strong>Blocks</strong> are useful when you have a section of the test that contains multiple questions that are related to each other, for example if there is a scenario-based section where a series of questions are about the same situation.
 | 
			
		||||
        </p>
 | 
			
		||||
        <p>
 | 
			
		||||
            Blocks can contain any number of questions within them, but cannot contain nested blocks.
 | 
			
		||||
        </p>
 | 
			
		||||
        <p>
 | 
			
		||||
            When you set up a block, you can also add <strong>header text</strong> that will be displayed with each question.
 | 
			
		||||
            You can use this to provide common information for a scenario across a series of questions.
 | 
			
		||||
        </p>
 | 
			
		||||
        <p>
 | 
			
		||||
            Questions come in three types:
 | 
			
		||||
            <ul>
 | 
			
		||||
                <li>
 | 
			
		||||
                    <strong>Yes/No</strong> for when there is only a yes or no option, 
 | 
			
		||||
                </li>
 | 
			
		||||
                <li>
 | 
			
		||||
                    <strong>Multiple Choice</strong> for your regular multiple choice questions, and
 | 
			
		||||
                </li>
 | 
			
		||||
                <li>
 | 
			
		||||
                    <strong>Ordered List</strong> for multiple choice questions that will be displayed in the same order as listed here.
 | 
			
		||||
                </li>
 | 
			
		||||
            </ul>
 | 
			
		||||
        </p>
 | 
			
		||||
        <p>
 | 
			
		||||
            Normally, multiple choice questions will have the order of the options randomised.
 | 
			
		||||
        </p>
 | 
			
		||||
        <p>
 | 
			
		||||
            Questions will be displayed to candidates in a randomised order.
 | 
			
		||||
            Blocks of questions will be kept together, but the order within the block will also be randomised.
 | 
			
		||||
        </p>
 | 
			
		||||
        <p>
 | 
			
		||||
            Questions can also be categorised using <strong>tags</strong>.
 | 
			
		||||
        </p>
 | 
			
		||||
        <p class="lead">
 | 
			
		||||
            Placeholder for Questions Remaining in a Block
 | 
			
		||||
        </p>
 | 
			
		||||
        <p>
 | 
			
		||||
            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’, the app uses the placeholder <code><block_remaining_questions></code>.
 | 
			
		||||
        </p>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="container viewer-panel">
 | 
			
		||||
        <h3>
 | 
			
		||||
            Question Dataset
 | 
			
		||||
        </h3>
 | 
			
		||||
        <div class="container dataset-metadata">
 | 
			
		||||
            <div class="input-group mb-3">
 | 
			
		||||
                <span class="input-group-text">Dataset Name</span>
 | 
			
		||||
                <span class="form-control">
 | 
			
		||||
                    {{ dataset.get_name() }}
 | 
			
		||||
                </span>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="input-group mb-3">
 | 
			
		||||
                <span class="input-group-text">Author</span>
 | 
			
		||||
                <span class="form-control">
 | 
			
		||||
                    {{ dataset.creator.get_username() }}
 | 
			
		||||
                </span>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="input-group mb-3">
 | 
			
		||||
                <span class="input-group-text">Last Updated</span>
 | 
			
		||||
                <span class="form-control">
 | 
			
		||||
                    {{ dataset.date.strftime('%d %b %Y %H:%M') }}
 | 
			
		||||
                </span>
 | 
			
		||||
            </div>
 | 
			
		||||
            {% if dataset.default %}
 | 
			
		||||
                <div class="input-group mb-3">
 | 
			
		||||
                    <span class="input-group-text">
 | 
			
		||||
                        <input type="checkbox" aria-label="Default" class="dataset-default" checked disabled>
 | 
			
		||||
                    </span>
 | 
			
		||||
                    <span class="form-control">
 | 
			
		||||
                        Default Dataset
 | 
			
		||||
                    </select>
 | 
			
		||||
                </div>
 | 
			
		||||
            {% endif %}
 | 
			
		||||
        </div>
 | 
			
		||||
        
 | 
			
		||||
    </div>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block script %}
 | 
			
		||||
<script>
 | 
			
		||||
    const target = "{{ url_for('api._editor') }}"
 | 
			
		||||
    const id = "{{ dataset.id }}"
 | 
			
		||||
</script>
 | 
			
		||||
<script
 | 
			
		||||
    type="text/javascript"
 | 
			
		||||
    src="{{ url_for('.static', filename='js/view.js') }}"
 | 
			
		||||
></script>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
							
								
								
									
										27
									
								
								ref-test/app/view/templates/view/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								ref-test/app/view/templates/view/index.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,27 @@
 | 
			
		||||
{% extends "view/components/input-forms.html" %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
    <div class="form-container">
 | 
			
		||||
        <form name="form-editor" class="form-display form-post" action="{{ url_for(request.endpoint, **request.view_args) }}" data-rel-success="{{ url_for(request.endpoint, **request.view_args) }}">
 | 
			
		||||
            {% include "admin/components/server-alerts.html" %}
 | 
			
		||||
            <h2 class="form">View Questions</h2>
 | 
			
		||||
            {{ form.hidden_tag() }}
 | 
			
		||||
            <div class="form-select-input">
 | 
			
		||||
                {{ form.dataset(placeholder="Select Question Dataset") }}
 | 
			
		||||
                {{ form.dataset.label }}
 | 
			
		||||
            </div>
 | 
			
		||||
            {% include "admin/components/client-alerts.html" %}
 | 
			
		||||
            <div class="container form-submission-button">
 | 
			
		||||
                <div class="row">
 | 
			
		||||
                    <div class="col text-center">
 | 
			
		||||
                        <button class="btn btn-md btn-success btn-block" type="submit">
 | 
			
		||||
                            <i class="bi bi-book-fill button-icon"></i>
 | 
			
		||||
                            View
 | 
			
		||||
                        </button>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </form>
 | 
			
		||||
    
 | 
			
		||||
    </div>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
							
								
								
									
										41
									
								
								ref-test/app/view/views.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								ref-test/app/view/views.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,41 @@
 | 
			
		||||
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
 | 
			
		||||
from flask_login import login_required
 | 
			
		||||
 | 
			
		||||
view = Blueprint(
 | 
			
		||||
    name='view',
 | 
			
		||||
    import_name=__name__,
 | 
			
		||||
    template_folder='templates',
 | 
			
		||||
    static_folder='static'
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@view.route('/', methods=['GET','POST'])
 | 
			
		||||
@login_required
 | 
			
		||||
@check_dataset_exists
 | 
			
		||||
def _view():
 | 
			
		||||
    form = EditDataset()
 | 
			
		||||
    form.dataset.choices = get_dataset_choices()
 | 
			
		||||
    if request.method == 'POST':
 | 
			
		||||
        if form.validate_on_submit():
 | 
			
		||||
            id = request.form.get('dataset')
 | 
			
		||||
            return jsonify({'success': 'Selected dataset', 'redirect_to': url_for('view._view_console', id=id)}),200
 | 
			
		||||
        return send_errors_to_client(form=form)
 | 
			
		||||
    form.process()
 | 
			
		||||
    return render_template('/view/index.html', form=form)
 | 
			
		||||
 | 
			
		||||
@view.route('/<string:id>/')
 | 
			
		||||
@login_required
 | 
			
		||||
@check_dataset_exists
 | 
			
		||||
def _view_console(id:str=None):
 | 
			
		||||
    dataset = Dataset.query.filter_by(id=id).first()
 | 
			
		||||
    datasets = Dataset.query.count()
 | 
			
		||||
    users = User.query.all()
 | 
			
		||||
    if not dataset:
 | 
			
		||||
        flash('Invalid dataset ID.', 'error')
 | 
			
		||||
        return redirect(url_for('admin._questions'))
 | 
			
		||||
    return render_template('/view/console.html', dataset=dataset, datasets=datasets, users=users)
 | 
			
		||||
		Reference in New Issue
	
	Block a user