Skip to content

Commit efdd045

Browse files
authored
Merge pull request #1247 from Alex-Jordan/image-descriptions
long image descriptions
2 parents 38889fc + 2ebfa7e commit efdd045

File tree

10 files changed

+258
-79
lines changed

10 files changed

+258
-79
lines changed
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
(() => {
2+
// Details accordions
3+
24
const setupAccordion = (accordion) => {
35
const collapseEl = accordion.querySelector('.collapse');
46
const button = accordion.querySelector('summary.accordion-button');
@@ -38,4 +40,30 @@
3840
});
3941
});
4042
observer.observe(document.body, { childList: true, subtree: true });
43+
44+
// Image long descriptions
45+
46+
for (const dismissButton of document.querySelectorAll('.image-details-dismiss')) {
47+
dismissButton.addEventListener('click', () =>
48+
dismissButton.parentElement?.parentElement?.parentElement?.removeAttribute('open')
49+
);
50+
}
51+
52+
for (const imageDescription of document.querySelectorAll('.image-details')) {
53+
const escapeCloseDetails = (e) => {
54+
if (e.key === 'Escape') imageDescription.removeAttribute('open');
55+
};
56+
const clickCloseDetails = (e) => {
57+
if (e.target.closest('.image-details') !== imageDescription) imageDescription.removeAttribute('open');
58+
};
59+
imageDescription.addEventListener('toggle', () => {
60+
if (imageDescription.open) {
61+
document.addEventListener('keydown', escapeCloseDetails);
62+
document.addEventListener('pointerdown', clickCloseDetails);
63+
} else {
64+
document.removeEventListener('keydown', escapeCloseDetails);
65+
document.removeEventListener('pointerdown', clickCloseDetails);
66+
}
67+
});
68+
}
4169
})();

htdocs/js/Problem/problem.scss

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,27 @@
203203
}
204204
}
205205

206+
.image-container {
207+
.image-details-content {
208+
position: fixed;
209+
bottom: 0;
210+
left: 50%;
211+
transform: translateX(-50%);
212+
width: max-content;
213+
max-width: 90%;
214+
box-shadow: 0 0 100px black;
215+
border-radius: 4px;
216+
z-index: 10;
217+
}
218+
219+
.image-details-btn {
220+
--bs-btn-padding-y: 0.1rem;
221+
--bs-btn-padding-x: 0.35rem;
222+
--bs-btn-font-size: 0.65rem;
223+
--bs-btn-border-radius: 0.375rem;
224+
}
225+
}
226+
206227
/* rtl:raw:
207228
.problem-content[dir=ltr] input[type=radio] {
208229
margin-right: 0.25rem;

lib/Plots/JSXGraph.pm

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,14 @@ sub HTML {
3434

3535
my $imageviewClass = $self->plots->axes->style('jsx_navigation') ? '' : ' image-view-elt';
3636
my $tabindex = $self->plots->axes->style('jsx_navigation') ? '' : ' tabindex="0"';
37+
my $details = $self->plots->{description_details} =~ s/LONG-DESCRIPTION-ID/${name}_details/r;
38+
my $aria_details = $details ? qq! aria-details="${name}_details"! : '';
39+
my $divs = qq!<div id="jsxgraph-plot-$name" class="jxgbox plots-jsxgraph$imageviewClass"$tabindex!
40+
. qq!style="width: ${width}px; height: ${height}px;"$aria_details></div>!;
41+
$divs = qq!<div class="image-container">$divs$details</div>! if ($details);
3742

3843
return <<~ "END_HTML";
39-
<div id="jsxgraph-plot-$name" class="jxgbox plots-jsxgraph$imageviewClass"$tabindex
40-
style="width: ${width}px; height: ${height}px;"></div>
44+
$divs
4145
<script>
4246
(async () => {
4347
const drawBoard = (id) => {

macros/PG.pl

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -381,12 +381,12 @@ sub ADD_JS_FILE {
381381
# although those problems should also be rewritten to not use jquery-ui.
382382
sub load_js() {
383383

384-
ADD_JS_FILE('js/Feedback/feedback.js', 0, { defer => undef });
385-
ADD_JS_FILE('js/Base64/Base64.js', 0, { defer => undef });
386-
ADD_JS_FILE('js/Knowls/knowl.js', 0, { defer => undef });
387-
ADD_JS_FILE('js/Problem/details-accordion.js', 0, { defer => undef });
388-
ADD_JS_FILE('js/ImageView/imageview.js', 0, { defer => undef });
389-
ADD_JS_FILE('js/Essay/essay.js', 0, { defer => undef });
384+
ADD_JS_FILE('js/Feedback/feedback.js', 0, { defer => undef });
385+
ADD_JS_FILE('js/Base64/Base64.js', 0, { defer => undef });
386+
ADD_JS_FILE('js/Knowls/knowl.js', 0, { defer => undef });
387+
ADD_JS_FILE('js/Problem/generic.js', 0, { defer => undef });
388+
ADD_JS_FILE('js/ImageView/imageview.js', 0, { defer => undef });
389+
ADD_JS_FILE('js/Essay/essay.js', 0, { defer => undef });
390390

391391
if ($envir{useMathQuill}) {
392392
ADD_JS_FILE('node_modules/@openwebwork/mathquill/dist/mathquill.js', 0, { defer => undef });

macros/core/PGML.pl

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -687,7 +687,7 @@ sub NOOP {
687687
terminator => qr/!\]/,
688688
terminateMethod => 'terminateGetString',
689689
cancelNL => 1,
690-
options => [ "source", "width", "height", "image_options" ]
690+
options => [ "source", "width", "height", "image_options", "long_description", "long_description_width" ]
691691
},
692692
"[<" => {
693693
type => 'tag',
@@ -1484,12 +1484,22 @@ sub Text {
14841484

14851485
sub Image {
14861486
my ($self, $item) = @_;
1487-
my $text = $item->{text};
1488-
my $source = $item->{source};
1489-
my $width = $item->{width} || '';
1490-
my $height = $item->{height} || '';
1491-
my $image_options = $item->{image_options} || {};
1492-
return (main::image($source, alt => $text, width => $width, height => $height, %$image_options));
1487+
my $text = $item->{text};
1488+
my $source = $item->{source};
1489+
my $width = $item->{width} || '';
1490+
my $height = $item->{height} || '';
1491+
my $image_options = $item->{image_options} || {};
1492+
my $long_description = $item->{long_description} || '';
1493+
my $long_description_width = $item->{long_description_width} || 1;
1494+
return (main::image(
1495+
$source,
1496+
alt => $text,
1497+
width => $width,
1498+
height => $height,
1499+
long_description => $long_description,
1500+
long_description_width => $long_description_width,
1501+
%$image_options
1502+
));
14931503
}
14941504

14951505
######################################################################

macros/core/PGbasicmacros.pl

Lines changed: 123 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2737,10 +2737,10 @@ =head2 Macros for displaying images
27372737

27382738
Usage:
27392739

2740-
image($image, width => 200, height => 200, tex_size => 800, valign => 'middle', alt => 'alt text', extra_html_tags => 'style="border:solid black 1pt"');
2740+
image($image, width => ..., height => ..., tex_size => ..., valign => ..., alt => ..., long_description => ... );
27412741

27422742
where C<$image> can be a local file path, URL, WWPlot object, PGlateximage object,
2743-
PGtikz object, or parser::GraphTool object.
2743+
PGtikz object, Plots::Plot object, or parser::GraphTool object.
27442744

27452745
C<width> and C<height> are positive integer pixel counts for HTML display. If both
27462746
are missing, C<width> will default to 200 and C<height> will remain undeclared,
@@ -2757,10 +2757,34 @@ =head2 Macros for displaying images
27572757
C<valign> can be 'top', 'middle', or 'bottom'. This aligns the image relative to
27582758
the surrounding line of text.
27592759

2760-
image([$image1,$image2], width => 200, height => 200, tex_size => 800, alt => ['alt text 1','alt text 2'], extra_html_tags => 'style="border:solid black 1pt"');
2761-
image([$image1,$image2], width => 200, height => 200, tex_size => 800, alt => 'common alt text', extra_html_tags => 'style="border:solid black 1pt"');
2760+
C<alt> should be a string, ideally with fewer than 125 characters, that describes the
2761+
most important features of the image. This should always be used. If the image is
2762+
decorative, C<< alt => '' >> should be used.
27622763

2763-
this produces an array in array context and joins the elements with C<' '> in scalar context
2764+
C<long_description> provides an optional way to give a more complete description of
2765+
an image. This may include a table (for example to describe complex data in a graph).
2766+
It may be helpful to generate blocks of text and tables and store them in a variable,
2767+
and pass that variable to C<long_description>.
2768+
2769+
C<long_description_width> defaults to 1. This should be a positive number at most 1.
2770+
In hardcopy output, this portion of the line width will be used to cap the width
2771+
of the long description (if there is one). This is useful for example when the image
2772+
is inside a table.
2773+
2774+
C<extra_html_tags> [DEPRECATED] can be a string will directly be placed into the
2775+
HTML img element. For example, C<< extra_html_tags => 'style="border:solid black 1pt"' >>.
2776+
2777+
The first argument to C<image()> can alternatively be an array of images:
2778+
2779+
image([$image1, $image2], ...);
2780+
2781+
If so then if C<alt> or C<long_description> are not arrays, they will be used
2782+
repeatedly for each image. Each of C<alt> and C<long_description> can instead be
2783+
arrays of the same length and their entries will be used with the corresponding
2784+
image.
2785+
2786+
In array context, using C<image()> this way will produces an array in array context
2787+
and join the elements with C<' '> in scalar context.
27642788

27652789
=cut
27662790

@@ -2778,8 +2802,10 @@ sub image {
27782802
tex_size => '',
27792803
valign => 'middle',
27802804
# default value for alt is undef, since an empty string is the explicit indicator of a decorative image
2781-
alt => undef,
2782-
extra_html_tags => '',
2805+
alt => undef,
2806+
long_description => undef,
2807+
long_description_width => 1,
2808+
extra_html_tags => '',
27832809
);
27842810
# handle options
27852811
my %out_options = %known_options;
@@ -2802,9 +2828,12 @@ sub image {
28022828
$tex_size = 1000 if $tex_size > 1000;
28032829

28042830
my $alt = $out_options{alt};
2805-
my $width_ratio = $tex_size * (.001);
2831+
my $desc = $out_options{long_description};
2832+
my $ldw = $out_options{long_description_width};
2833+
my $width_ratio = $tex_size * 0.001;
28062834
my @image_list = ();
28072835
my @alt_list = ();
2836+
my @desc_list = ();
28082837
my $valign = 'middle';
28092838
$valign = 'top' if ($out_options{valign} eq 'top');
28102839
$valign = 'bottom' if ($out_options{valign} eq 'bottom');
@@ -2823,16 +2852,58 @@ sub image {
28232852
} else {
28242853
for my $i (@image_list) { push(@alt_list, $alt) }
28252854
}
2855+
if (ref($desc) =~ /ARRAY/) {
2856+
@desc_list = @{$desc};
2857+
} else {
2858+
for my $i (@image_list) { push(@desc_list, $desc) }
2859+
}
28262860

28272861
my @output_list = ();
28282862
while (@image_list) {
2829-
my $image_item = shift @image_list;
2863+
my $image_item = shift @image_list;
2864+
my $description_details = $desc ? shift(@desc_list) : '';
2865+
if ($desc && $displayMode ne 'PTX' && $displayMode ne 'TeX') {
2866+
$description_details = tag(
2867+
'details',
2868+
'aria-live' => 'polite',
2869+
class => 'image-details',
2870+
name => 'image-details',
2871+
tag(
2872+
'summary',
2873+
class => 'mt-1',
2874+
title => 'details',
2875+
tag(
2876+
'span',
2877+
class => 'image-details-btn btn btn-sm btn-secondary fw-bold',
2878+
'aria-hidden' => 'true',
2879+
maketext('image description')
2880+
)
2881+
)
2882+
. tag(
2883+
'div',
2884+
id => 'LONG-DESCRIPTION-ID',
2885+
class => 'image-details-content bg-white py-2 px-3 my-2 border',
2886+
$description_details
2887+
. tag(
2888+
'div',
2889+
class => 'd-flex justify-content-end mt-2',
2890+
tag(
2891+
'button',
2892+
class => 'image-details-dismiss btn btn-sm btn-secondary',
2893+
type => 'button',
2894+
maketext('Close image description')
2895+
)
2896+
)
2897+
)
2898+
);
2899+
}
28302900
if (ref $image_item eq 'parser::GraphTool') {
28312901
push(
28322902
@output_list,
28332903
$image_item->generateAnswerGraph(
2834-
$out_options{width} || $out_options{height} ? (width => $width, height => $height) : (),
2835-
$out_options{tex_size} || $out_options{width} ? (texSize => $tex_size) : (),
2904+
$out_options{width} || $out_options{height} ? (width => $width, height => $height) : (),
2905+
$out_options{tex_size} || $out_options{width} ? (texSize => $tex_size) : (),
2906+
$desc ? (longDescription => $description_details) : (),
28362907
ariaDescription => shift @alt_list // ''
28372908
)
28382909
);
@@ -2846,6 +2917,7 @@ sub image {
28462917
$image_item->axes->style(aria_description => shift @alt_list) if $out_options{alt};
28472918

28482919
if ($image_item->ext eq 'html') {
2920+
$image_item->{description_details} = $description_details;
28492921
push(@output_list, $image_item->draw);
28502922
next;
28512923
}
@@ -2862,25 +2934,36 @@ sub image {
28622934
|| ref $image_item eq 'PGtikz');
28632935
my $imageURL = alias($image_item) // '';
28642936
$imageURL = ($envir{use_site_prefix}) ? $envir{use_site_prefix} . $imageURL : $imageURL;
2865-
my $out = "";
2937+
my $id = $main::PG->getUniqueName('img');
2938+
my $out = '';
28662939

28672940
if ($displayMode eq 'TeX') {
28682941
my $imagePath = $imageURL; # in TeX mode, alias gives us a path, not a URL
28692942

28702943
# We're going to create PDF files with our TeX (using LaTeX), so
28712944
# alias should have given us the path to a PNG image.
28722945
if ($imagePath) {
2946+
if ($desc) {
2947+
$out .= "\\parbox{$ldw\\linewidth}{";
2948+
$width_ratio = $width_ratio / $ldw;
2949+
}
28732950
if ($valign eq 'top') {
2874-
$out = '\settoheight{\strutheight}{\strut}'
2875-
. "\\raisebox{-\\height + \\strutheight}{\\includegraphics[width=$width_ratio\\linewidth]{$imagePath}}\n";
2951+
$out .= '\settoheight{\strutheight}{\strut}\raisebox{-\height + \strutheight}'
2952+
. "{\\includegraphics[width=$width_ratio\\linewidth]{$imagePath}}\n";
28762953
} elsif ($valign eq 'bottom') {
2877-
$out = "\\includegraphics[width=$width_ratio\\linewidth]{$imagePath}\n";
2954+
$out .= "\\includegraphics[width=$width_ratio\\linewidth]{$imagePath}\n";
28782955
} else {
2879-
$out = '\settoheight{\strutheight}{\strut}'
2880-
. "\\raisebox{-0.5\\height + 0.5\\strutheight}{\\includegraphics[width=$width_ratio\\linewidth]{$imagePath}}\n";
2956+
$out .= '\settoheight{\strutheight}{\strut}\raisebox{-0.5\height + 0.5\strutheight}'
2957+
. "{\\includegraphics[width=$width_ratio\\linewidth]{$imagePath}}\n";
2958+
}
2959+
if ($desc) {
2960+
$out .=
2961+
"\\newline\\par\\parbox{\\linewidth}{{\\scshape\\underline{"
2962+
. maketext('image description')
2963+
. "}}\\newline{}$description_details\\par\\hfill\\(\\overline{\\mbox{\\scshape "
2964+
. maketext('end image description')
2965+
. "}}\\)}}\\par\n";
28812966
}
2882-
} else {
2883-
$out = "";
28842967
}
28852968
} elsif ($displayMode eq 'HTML_MathJax'
28862969
|| $displayMode eq 'HTML_dpng'
@@ -2889,15 +2972,31 @@ sub image {
28892972
{
28902973
my $altattrib = '';
28912974
if (defined $alt_list[0]) { $altattrib = 'alt="' . encode_pg_and_html(shift @alt_list) . '"' }
2892-
$out =
2893-
qq!<IMG SRC="$imageURL" class="image-view-elt $valign" tabindex="0" role="button"$width_attrib$height_attrib $out_options{extra_html_tags} $altattrib>!;
2975+
if ($desc) {
2976+
$out .= tag(
2977+
'div',
2978+
class => 'image-container pb-2',
2979+
qq!<img src="$imageURL" class="image-view-elt $valign" tabindex="0" role="button"!
2980+
. qq!$width_attrib$height_attrib aria-details="${id}_details" $out_options{extra_html_tags} $altattrib>!
2981+
. ($description_details =~ s/LONG-DESCRIPTION-ID/${id}_details/r)
2982+
);
2983+
} else {
2984+
$out .= qq!<img src="$imageURL" class="image-view-elt $valign" tabindex="0" role="button"!
2985+
. qq!$width_attrib$height_attrib $out_options{extra_html_tags} $altattrib>!;
2986+
}
28942987
} elsif ($displayMode eq 'PTX') {
28952988
my $ptxwidth = ($width ? int($width / 6) : 80);
2989+
$out = qq!<image width="$ptxwidth%" source="$imageURL">!;
28962990
if (defined $alt) {
2897-
$out = qq!<image width="$ptxwidth%" source="$imageURL"><description>$alt</description></image>!;
2898-
} else {
2899-
$out = qq!<image width="$ptxwidth%" source="$imageURL" />!;
2991+
$out .= "\n<shortdescription>$alt</shortdescription>";
2992+
}
2993+
if (defined $desc) {
2994+
$out .= "\n<description>\n" . PTX_cleanup($description_details) . "\n</description>";
2995+
}
2996+
if (defined $alt || defined $desc) {
2997+
$out .= "\n";
29002998
}
2999+
$out .= '</image>';
29013000
} else {
29023001
$out = "Error: PGbasicmacros: image: Unknown displayMode: $displayMode.\n";
29033002
}

0 commit comments

Comments
 (0)