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
2 changes: 1 addition & 1 deletion htdocs/js/GatewayQuiz/gateway.scss
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ table.attemptResults {
border: 1px solid #ddd;
border-radius: 3px;

h2 {
h2.gw-problem-number {
display: inline-block;
font-size: 16px;
margin-right: 5px;
Expand Down
137 changes: 137 additions & 0 deletions htdocs/js/ProblemGrader/singleproblemgrader.js
Original file line number Diff line number Diff line change
Expand Up @@ -216,4 +216,141 @@
}
});
}

const settingStoreID = `WW.${document.getElementsByName('courseID')[0]?.value ?? 'unknownCourse'}.${
document.getElementsByName('user')[0]?.value ?? 'unknownUser'
}.problem_grader`;
let gradersOpen = localStorage.getItem(`${settingStoreID}.open`) === 'true';

const graderCollapses = [];

for (const grader of document.querySelectorAll('.problem-grader')) {
const problemId = grader.id.replace('problem-grader-');

grader.classList.add('accordion');

const accordionItem = document.createElement('div');
accordionItem.classList.add('accordion-item');

const accordionHeader = document.createElement('h2');
accordionHeader.classList.add('accordion-header');

const accordionButton = document.createElement('button');
accordionButton.classList.add('accordion-button');
accordionButton.type = 'button';
accordionButton.textContent = grader.dataset.graderTitle ?? 'Problem Grader';
accordionButton.dataset.bsToggle = 'collapse';
accordionButton.dataset.bsTarget = `#problem-grader-collapse-${problemId}`;
accordionButton.setAttribute('aria-controls', `#problem-grader-collapse-${problemId}`);
accordionButton.setAttribute('aria-expanded', gradersOpen);
if (!gradersOpen) accordionButton.classList.add('collapsed');

accordionHeader.append(accordionButton);

const accordionCollapse = document.createElement('div');
accordionCollapse.classList.add('accordion-collapse', 'collapse');
accordionCollapse.id = `problem-grader-collapse-${problemId}`;
accordionCollapse.dataset.bsParent = `problem-grader-${problemId}`;
if (gradersOpen) accordionCollapse.classList.add('show');

const accordionBody = grader.querySelector('.problem-grader-table');
accordionBody.classList.add('accordion-body');
accordionCollapse.append(accordionBody);

accordionItem.append(accordionHeader, accordionCollapse);
grader.append(accordionItem);

const graderCollapse = new bootstrap.Collapse(accordionCollapse, { toggle: false });
graderCollapses.push(graderCollapse);

grader.classList.remove('d-none');

// Expand or collapse all problem graders on the page when any one of them is expanded or collapsed.
let transitioning = false;
accordionCollapse.addEventListener('show.bs.collapse', () => {
if (transitioning) return;
transitioning = true;
for (const grader of graderCollapses) {
if (grader !== graderCollapse) grader.show();
}
transitioning = false;
});
accordionCollapse.addEventListener('hide.bs.collapse', () => {
if (transitioning) return;
transitioning = true;
for (const grader of graderCollapses) {
if (grader !== graderCollapse) grader.hide();
}
transitioning = false;
});

// Make sure that the "Reveal" button in feedback is not shown if a feedback button is used while the problem
// grader is open. However, also make sure that the "Reveal" button is shown for any feedback button that is
// not used while the problem grader is open.

const unrevealedFeedbackBtns = [];

for (const feedbackBtn of document.querySelectorAll('.ww-feedback-btn')) {
const container = document.createElement('div');
container.innerHTML = feedbackBtn.dataset.bsContent;
const button = container.querySelector('.reveal-correct-btn');
if (!button) continue;

button.nextElementSibling?.classList.remove('d-none');
button.remove();

const fragment = new DocumentFragment();
fragment.append(container);

unrevealedFeedbackBtns.push([feedbackBtn, fragment.firstElementChild.innerHTML]);

const handler = () => {
const index = unrevealedFeedbackBtns.findIndex((data) => data[0] === feedbackBtn);
if (index !== -1) {
if (gradersOpen) {
unrevealedFeedbackBtns.splice(index, 1);
feedbackBtn.removeEventListener('shown.bs.popover', handler);
} else {
bootstrap.Popover.getInstance(feedbackBtn)
?.tip?.querySelector('.reveal-correct-btn')
?.addEventListener(
'click',
() => {
unrevealedFeedbackBtns.splice(index, 1);
feedbackBtn.removeEventListener('shown.bs.popover', handler);
},
{ once: true }
);
}
}
};

feedbackBtn.addEventListener('shown.bs.popover', handler);
}

const removeRevealButtons = () => {
for (const data of unrevealedFeedbackBtns) {
const feedbackPopover = bootstrap.Popover.getInstance(data[0]);
feedbackPopover?.setContent({ '.popover-body': data[1] });
}
};

if (gradersOpen) removeRevealButtons();

// In addition to removing and putting back the feedback "Reveal" buttons as needed,
// preserve the collapsed/expanded status of the problem graders in local storage.
accordionCollapse.addEventListener('shown.bs.collapse', () => {
localStorage.setItem(`${settingStoreID}.open`, 'true');
gradersOpen = true;
removeRevealButtons();
});
accordionCollapse.addEventListener('hidden.bs.collapse', () => {
gradersOpen = false;
localStorage.setItem(`${settingStoreID}.open`, 'false');
for (const data of unrevealedFeedbackBtns) {
const feedbackPopover = bootstrap.Popover.getInstance(data[0]);
feedbackPopover?.setContent({ '.popover-body': data[0].dataset.bsContent });
}
});
}
})();
11 changes: 11 additions & 0 deletions htdocs/js/System/system.scss
Original file line number Diff line number Diff line change
Expand Up @@ -1018,6 +1018,17 @@ td.alt-source {
}
}

.problem-grader.accordion {
.accordion-header {
.accordion-button {
--bs-accordion-btn-padding-x: 0.75rem;
--bs-accordion-btn-padding-y: 0.375rem;
--bs-accordion-btn-bg: var(--bs-primary, #038);
--bs-accordion-btn-color: var(--ww-primary-foreground-color, white);
}
}
}

.problem-grader-table {
.col-fixed {
width: 11rem;
Expand Down
3 changes: 2 additions & 1 deletion lib/WeBWorK/ConfigValues.pm
Original file line number Diff line number Diff line change
Expand Up @@ -820,7 +820,8 @@ sub getConfigValues ($ce) {
doc2 => x(
'A "Reveal" button must be clicked to make a correct answer visible any time that correct '
. 'answers for a problem are shown. Note that this is always the case for instructors '
. 'before answers are available to students, and in "Show Me Another" problems.'
. 'before answers are available to students (except when the problem grader is open), and '
. 'in "Show Me Another" problems.'
),
type => 'boolean'
}
Expand Down
10 changes: 3 additions & 7 deletions lib/WeBWorK/ContentGenerator/GatewayQuiz.pm
Original file line number Diff line number Diff line change
Expand Up @@ -780,14 +780,11 @@ async sub pre_header_initialize ($c) {
return;
}

# Unset the showProblemGrader parameter if the "Hide Problem Grader" button was clicked.
$c->param(showProblemGrader => undef) if $c->param('hideProblemGrader');

# What does the user want to do?
my %want = (
showOldAnswers => $user->showOldAnswers ne '' ? $user->showOldAnswers : $ce->{pg}{options}{showOldAnswers},
showCorrectAnswers => 1,
showProblemGrader => $c->param('showProblemGrader') || 0,
showProblemGrader => $userID ne $effectiveUserID,
showHints => 0, # Hints are not yet implemented in gateway quzzes.
showSolutions => 1,
recordAnswers => $c->{submitAnswers} && !$authz->hasPermissions($userID, 'avoid_recording_answers'),
Expand Down Expand Up @@ -1491,10 +1488,9 @@ async sub getProblemHTML ($c, $effectiveUser, $set, $formFields, $mergedProblem)
&& $c->can_showCorrectAnswersForAll($set, $c->{problem}, $c->{tmplSet})),
showMessages => !$showOnlyCorrectAnswers,
showCorrectAnswers => (
$c->{will}{showProblemGrader} ? 2
: !$c->{previewAnswers} && $c->can_showCorrectAnswersForAll($set, $c->{problem}, $c->{tmplSet})
!$c->{previewAnswers} && $c->can_showCorrectAnswersForAll($set, $c->{problem}, $c->{tmplSet})
? ($c->ce->{pg}{options}{correctRevealBtnAlways} ? 1 : 2)
: !$c->{previewAnswers} && $c->{will}{showCorrectAnswers} ? 1
: $c->{will}{showProblemGrader} || (!$c->{previewAnswers} && $c->{will}{showCorrectAnswers}) ? 1
: 0
),
debuggingOptions => getTranslatorDebuggingOptions($c->authz, $c->{userID}),
Expand Down
39 changes: 15 additions & 24 deletions lib/WeBWorK/ContentGenerator/Problem.pm
Original file line number Diff line number Diff line change
Expand Up @@ -431,20 +431,17 @@ async sub pre_header_initialize ($c) {
Count => $problem->{showMeAnotherCount},
};

# Unset the showProblemGrader parameter if the "Hide Problem Grader" button was clicked.
$c->param(showProblemGrader => undef) if $c->param('hideProblemGrader');

# Permissions

# What does the user want to do?
my %want = (
showOldAnswers => $user->showOldAnswers ne '' ? $user->showOldAnswers : $ce->{pg}{options}{showOldAnswers},
showCorrectAnswers => 1,
showProblemGrader => $c->param('showProblemGrader') || 0,
showAnsGroupInfo => $c->param('showAnsGroupInfo') || $ce->{pg}{options}{showAnsGroupInfo},
showAnsHashInfo => $c->param('showAnsHashInfo') || $ce->{pg}{options}{showAnsHashInfo},
showPGInfo => $c->param('showPGInfo') || $ce->{pg}{options}{showPGInfo},
showResourceInfo => $c->param('showResourceInfo') || $ce->{pg}{options}{showResourceInfo},
showProblemGrader => $userID ne $effectiveUserID,
showAnsGroupInfo => $c->param('showAnsGroupInfo') || $ce->{pg}{options}{showAnsGroupInfo},
showAnsHashInfo => $c->param('showAnsHashInfo') || $ce->{pg}{options}{showAnsHashInfo},
showPGInfo => $c->param('showPGInfo') || $ce->{pg}{options}{showPGInfo},
showResourceInfo => $c->param('showResourceInfo') || $ce->{pg}{options}{showResourceInfo},
showHints => 1,
showSolutions => 1,
useMathView => $user->useMathView ne '' ? $user->useMathView : $ce->{pg}{options}{useMathView},
Expand Down Expand Up @@ -581,10 +578,10 @@ async sub pre_header_initialize ($c) {
&& after($c->{set}->answer_date, $c->submitTime)),
showMessages => !$showOnlyCorrectAnswers,
showCorrectAnswers => (
$will{showProblemGrader} || ($c->{submitAnswers} && $c->{showCorrectOnRandomize}) ? 2
$c->{submitAnswers} && $c->{showCorrectOnRandomize} ? 2
: !$c->{previewAnswers} && after($c->{set}->answer_date, $c->submitTime)
? ($ce->{pg}{options}{correctRevealBtnAlways} ? 1 : 2)
: !$c->{previewAnswers} && $will{showCorrectAnswers} ? 1
: $will{showProblemGrader} || (!$c->{previewAnswers} && $will{showCorrectAnswers}) ? 1
: 0
),
debuggingOptions => getTranslatorDebuggingOptions($authz, $userID),
Expand Down Expand Up @@ -722,9 +719,6 @@ sub siblings ($c) {

my @items;

# Keep the grader open when linking to problems if it is already open.
my %problemGraderLink = $c->{will}{showProblemGrader} ? (params => { showProblemGrader => 1 }) : ();

for my $problemID (@problemIDs) {
if ($isJitarSet
&& !$authz->hasPermissions($eUserID, 'view_unopened_sets')
Expand Down Expand Up @@ -795,7 +789,7 @@ sub siblings ($c) {
@items,
$c->tag(
'a',
$active ? () : (href => $c->systemLink($problemPage, %problemGraderLink)),
$active ? () : (href => $c->systemLink($problemPage)),
class => $class,
$c->b($c->maketext('Problem [_1]', join('.', @seq)) . $status_symbol)
)
Expand All @@ -806,7 +800,7 @@ sub siblings ($c) {
@items,
$c->tag(
'a',
$active ? () : (href => $c->systemLink($problemPage, %problemGraderLink)),
$active ? () : (href => $c->systemLink($problemPage)),
class => 'nav-link' . ($active ? ' active' : ''),
$c->b($c->maketext('Problem [_1]', $problemID) . $status_symbol)
)
Expand Down Expand Up @@ -970,10 +964,9 @@ sub nav ($c, $args) {
}

my %tail;
$tail{displayMode} = $c->{displayMode} if defined $c->{displayMode};
$tail{showOldAnswers} = 1 if $c->{will}{showOldAnswers};
$tail{showProblemGrader} = 1 if $c->{will}{showProblemGrader};
$tail{studentNavFilter} = $c->param('studentNavFilter') if $c->param('studentNavFilter');
$tail{displayMode} = $c->{displayMode} if defined $c->{displayMode};
$tail{showOldAnswers} = 1 if $c->{will}{showOldAnswers};
$tail{studentNavFilter} = $c->param('studentNavFilter') if $c->param('studentNavFilter');

return $c->tag(
'div',
Expand Down Expand Up @@ -1133,10 +1126,8 @@ sub output_message ($c) {

# Output the problem grader if the user has permissions to grade problems
sub output_grader ($c) {
if ($c->{will}{showProblemGrader}) {
return WeBWorK::HTML::SingleProblemGrader->new($c, $c->{pg}, $c->{problem})->insertGrader;
}

return WeBWorK::HTML::SingleProblemGrader->new($c, $c->{pg}, $c->{problem})->insertGrader
if $c->{will}{showProblemGrader};
return '';
}

Expand Down Expand Up @@ -1444,7 +1435,7 @@ sub output_summary ($c) {
# Attempt summary
if ($c->{submitAnswers}) {
push(@$output, $c->attemptResults($pg));
} elsif ($will{checkAnswers} || $c->{will}{showProblemGrader}) {
} elsif ($will{checkAnswers}) {
push(
@$output,
$c->tag(
Expand Down
7 changes: 3 additions & 4 deletions lib/WeBWorK/ContentGenerator/ShowMeAnother.pm
Original file line number Diff line number Diff line change
Expand Up @@ -155,10 +155,9 @@ async sub pre_header_initialize ($c) {
}

# Disable options that are not applicable for showMeAnother.
$c->{can}{recordAnswers} = 0;
$c->{can}{checkAnswers} = 0; # This is turned on if the showMeAnother conditions are met below.
$c->{can}{getSubmitButton} = 0;
$c->{can}{showProblemGrader} = 0;
$c->{can}{recordAnswers} = 0;
$c->{can}{checkAnswers} = 0; # This is turned on if the showMeAnother conditions are met below.
$c->{can}{getSubmitButton} = 0;

if ($c->stash->{isPossible}) {
$c->{can}{showCorrectAnswers} =
Expand Down
1 change: 0 additions & 1 deletion lib/WeBWorK/Utils.pm
Original file line number Diff line number Diff line change
Expand Up @@ -357,7 +357,6 @@ sub generateURLs ($c, %params) {
for my $name ('displayMode', 'showCorrectAnswers', 'showHints', 'showOldAnswers', 'showSolutions') {
$args{$name} = [ $c->param($name) ] if defined $c->param($name) && $c->param($name) ne '';
}
$args{showProblemGrader} = 1;
} else {
$routePath = $c->url_for('problem_list', setID => $params{set_id});
}
Expand Down
21 changes: 6 additions & 15 deletions templates/ContentGenerator/GatewayQuiz.html.ep
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,7 @@
%
<%= form_for $action, name => 'gwquiz', method => 'POST', class => 'problem-main-form mt-0', begin =%>
<%= $c->hidden_authen_fields =%>
<%= hidden_field courseID => $ce->{courseName} =%>
%
% # Hacks to use a javascript link to trigger previews and jump to subsequent pages of a multipage test.
<%= hidden_field pageChangeHack => '' =%>
Expand Down Expand Up @@ -533,7 +534,7 @@
% $recordMessage = tag('div', class => 'alert alert-danger d-inline-block mb-2 p-1',
% maketext('CORRECT ANSWERS SHOWN ONLY -- ANSWERS NOT RECORDED')
% );
% } elsif ($c->{will}{checkAnswers} || $c->{will}{showProblemGrader}) {
% } elsif ($c->{will}{checkAnswers}) {
% $recordMessage = tag('div', class => 'alert alert-danger d-inline-block mb-2 p-1',
% maketext('ANSWERS ONLY CHECKED -- ANSWERS NOT RECORDED')
% );
Expand All @@ -549,7 +550,7 @@
% # Show the jump to anchor.
<div id="<%= "prob$i" %>" tabindex="-1"><%= $recordMessage %></div>
% # Output the problem header.
<h2><%= maketext('Problem [_1].', $i + 1) %></h2>
<h2 class="gw-problem-number"><%= maketext('Problem [_1].', $i + 1) %></h2>
<span class="problem-sub-header">
% if ($c->{can}{showTemplateIds}) {
<%= '('
Expand Down Expand Up @@ -738,20 +739,10 @@
<p><em><%= maketext('Note: grading the test grades all problems, not just those on this page.') %></em></p>
% }
%
% if ($c->{can}{showProblemGrader}) {
% if ($c->{can}{showCorrectAnswers}) {
<div class="submit-buttons-container col-12 my-2">
% if ($c->{can}{showCorrectAnswers}) {
<%= submit_button maketext('Show Correct Answers'), name => 'showCorrectAnswers',
class => 'btn btn-primary mb-1' =%>
% }
% if ($c->{will}{showProblemGrader}) {
<%= submit_button maketext('Hide Problem Graders'), name => 'hideProblemGrader',
class => 'btn btn-primary mb-1' =%>
<%= hidden_field showProblemGrader => 1 =%>
% } else {
<%= submit_button maketext('Show Problem Graders'), name => 'showProblemGrader',
class => 'btn btn-primary mb-1' =%>
% }
<%= submit_button maketext('Show Correct Answers'), name => 'showCorrectAnswers',
class => 'btn btn-primary mb-1' =%>
</div>
% }
%
Expand Down
Loading