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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 52 additions & 2 deletions htdocs/js/GatewayQuiz/gateway.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,49 @@
return date.toISOString().substring(11, 19);
};

// Raise a toast to report the remaining time.
const showTimeToast = (text) => {
const toastContainer = document.createElement('div');
toastContainer.classList.add(
'gwAlert',
'toast-container',
'position-fixed',
'top-0',
'start-50',
'translate-middle-x',
'p-3'
);
toastContainer.innerHTML =
'<div class="toast bg-white" role="status" aria-live="polite" aria-atomic="true">' +
'<div class="toast-body mb-0 text-center d-flex align-items-center justify-content-center">' +
'<span class="me-2"></span>' +
'<button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="close"></button>' +
'</div>' +
'</div>';
document.body.prepend(toastContainer);
const bsToast = new bootstrap.Toast(toastContainer.firstElementChild, { delay: 4000 });
toastContainer.addEventListener('hidden.bs.toast', () => {
bsToast.dispose();
toastContainer.remove();
});

requestAnimationFrame(() => {
toastContainer.querySelector('.toast-body span').textContent = text;
bsToast.show();
});
};

// Show the current amount of time remaining in a toast, in response to a click on the announce button.
const announceTimeRemaining = () => {
const remainingTime = serverDueTime - Math.round(new Date().getTime() / 1000) + timeDelta;
const text =
remainingTime >= 0
? `${remainingTimeString}${formatTime(remainingTime)}`
: `${remainingTimeString}00:00:00`;

showTimeToast(text);
};

const alertToast = (message, delay = 5000) => {
const toastContainer = document.createElement('div');
toastContainer.classList.add(
Expand All @@ -41,21 +84,26 @@
'translate-middle-x',
'p-3'
);
// Start the body empty and populate its content later, to have reliable capture of the update by screen readers.
toastContainer.innerHTML =
'<div class="toast bg-white" role="alert" aria-live="assertive" aria-atomic="true">' +
'<div class="toast-header">' +
`<strong class="me-auto">${timerDiv.dataset.alertTitle ?? 'Test Time Notification'}</strong>` +
'<button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="close"></button>' +
'</div>' +
`<div class="toast-body alert alert-danger mb-0 text-center">${message}</div>` +
'<div class="toast-body alert alert-danger mb-0 text-center"></div>' +
'</div>';
document.body.prepend(toastContainer);
const bsToast = new bootstrap.Toast(toastContainer.firstElementChild, { delay });
toastContainer.addEventListener('hidden.bs.toast', () => {
bsToast.dispose();
toastContainer.remove();
});
bsToast.show();

requestAnimationFrame(() => {
toastContainer.querySelector('.toast-body').innerHTML = message;
bsToast.show();
});
};

// Update the timer
Expand Down Expand Up @@ -156,6 +204,8 @@
updateTimeDelta();
setInterval(updateTimeDelta, Math.min((parseInt(timerDiv.dataset.sessionTimeout) - 60) * 1000, 2147483646));

document.getElementById('gwAnnounceTimeBtn')?.addEventListener('click', announceTimeRemaining);

const remainingTime = serverDueTime - browserTime + timeDelta;

if (!timerDiv.dataset.acting && remainingTime <= 10 - gracePeriod) {
Expand Down
12 changes: 10 additions & 2 deletions htdocs/js/GatewayQuiz/gateway.scss
Original file line number Diff line number Diff line change
@@ -1,17 +1,25 @@
/* gateway styles */

#gwTimer {
#gwTimerContainer {
position: sticky;
width: 15em;
top: 2.75rem;
left: calc(100% - 15em - 0.75rem);
right: 0.75rem;
text-align: center;
z-index: 31;
}

#gwTimer {
text-align: center;
}

.gwAlert {
z-index: 32;

.toast-header + .toast-body {
border-top-left-radius: 0;
border-top-right-radius: 0;
}
}

#gwScoreSummary {
Expand Down
63 changes: 36 additions & 27 deletions templates/ContentGenerator/GatewayQuiz.html.ep
Original file line number Diff line number Diff line change
Expand Up @@ -224,33 +224,42 @@
%
% # Print the timer if there is less than 24 hours left.
% if ($timeLeft < 86400) {
<%= tag('div',
id => 'gwTimer',
class => 'alert alert-warning p-1',
data => {
server_time => int($submitTime),
server_due_time => $c->{set}->due_date,
session_timeout => $ce->{sessionTimeout},
course_id => $ce->{courseName},
grace_period => $ce->{gatewayGracePeriod},
alert_title => maketext('Test Time Notification'),
alert_three => maketext(
'You have less than 90 seconds left to complete this assignment. You should finish it soon!'),
alert_two => ('<div>' . maketext('You have less than 45 seconds left!') . '</div>')
. (
($c->{set}->attempts_per_version > 1 && $c->{attemptNumber} > 0) ? ''
: '<div>' . maketext('Press "Grade Test" soon!') . '</div>'
),
alert_one => ('<div>' . maketext('You are out of time!') . '</div>')
. (
($c->{set}->attempts_per_version > 1 && $c->{attemptNumber} > 0) ? ''
: '<div>' . maketext('Press "Grade Test" now!') . '</div>'
),
$userID ne $effectiveUserID ? (acting => 1) : ()
},
# '00:00:00' is a placeholder that is replaced by the actual time remaining via javascript.
maketext('Remaining time: [_1]', '00:00:00')
) =%>
<div id="gwTimerContainer" class="d-flex flex-column gap-2 mb-2">
<%= tag('div',
id => 'gwTimer',
class => 'alert alert-warning p-1 mb-0',
'aria-hidden' => 'true',
data => {
server_time => int($submitTime),
server_due_time => $c->{set}->due_date,
session_timeout => $ce->{sessionTimeout},
course_id => $ce->{courseName},
grace_period => $ce->{gatewayGracePeriod},
alert_title => maketext('Test Time Notification'),
alert_three => maketext(
'You have less than 90 seconds left to complete this assignment. You should finish it soon!'),
alert_two => ('<div>' . maketext('You have less than 45 seconds left!') . '</div>')
. (
($c->{set}->attempts_per_version > 1 && $c->{attemptNumber} > 0) ? ''
: '<div>' . maketext('Press "Grade Test" soon!') . '</div>'
),
alert_one => ('<div>' . maketext('You are out of time!') . '</div>')
. (
($c->{set}->attempts_per_version > 1 && $c->{attemptNumber} > 0) ? ''
: '<div>' . maketext('Press "Grade Test" now!') . '</div>'
),
$userID ne $effectiveUserID ? (acting => 1) : ()
},
# '00:00:00' is a placeholder that is replaced by the actual time remaining via javascript.
maketext('Remaining time: [_1]', '00:00:00')
) =%>
<%= tag('button',
id => 'gwAnnounceTimeBtn',
type => 'button',
class => 'btn btn-sm btn-secondary',
maketext('Announce time remaining')
) =%>
</div>
% }
%
% if ($timeLeft < 60 && $timeLeft > 0 && !$c->{can}{gradeUnsubmittedTest}
Expand Down