X-Git-Url: http://git.ieval.ro/?p=gruntmaster-page.git;a=blobdiff_plain;f=lib%2FPlack%2FApp%2FGruntmaster%2FHTML.pm;h=40b47a7494568448be9eb768bdaf50146a1e490b;hp=81a33c0dc59a12e0d4822ea95f12989ecca76dcd;hb=37220f63b3da2607b803e74e647298472bd5f5e0;hpb=28ec8060441f56601f8cb167fafd32fdfb3c1a8a diff --git a/lib/Plack/App/Gruntmaster/HTML.pm b/lib/Plack/App/Gruntmaster/HTML.pm index 81a33c0..40b47a7 100644 --- a/lib/Plack/App/Gruntmaster/HTML.pm +++ b/lib/Plack/App/Gruntmaster/HTML.pm @@ -4,12 +4,25 @@ use parent qw/Exporter/; our @EXPORT = qw/render render_article/; use File::Slurp qw/read_file/; -use HTML::Seamstress; +use List::Util qw[min max]; +use HTML::Element::Library; +use HTML::TreeBuilder; use POSIX qw//; use Data::Dumper qw/Dumper/; +use Sort::ByExample + sorter => {-as => 'ct_sort', example => [qw/Running Pending Finished/], xform => sub {$_->{status}}}; + +my $optional_end_tags = {%HTML::Tagset::optionalEndTag, tr => 1, td => 1, th => 1}; sub ftime ($) { POSIX::strftime '%c', localtime shift } -sub literal ($) { HTML::Element::Library::super_literal shift // '' } +sub literal ($) { + my ($html) = @_; + return '' unless $html; + my $b = HTML::TreeBuilder->new; + $b->ignore_unknown(0); + $b->parse($html); + HTML::Element::Library::super_literal $b->guts->as_HTML(undef, undef, $optional_end_tags); +} sub HTML::Element::edit_href { my ($self, $sub) = @_; @@ -36,37 +49,53 @@ sub HTML::Element::fclass { shift->look_down(class => qr/\b$_[0]\b/) } sub HTML::Element::namedlink { my ($self, $id, $name) = @_; - $name = $id unless $name =~ /[[:graph:]]/; + $name = $id unless $name && $name =~ /[[:graph:]]/; $self = $self->find('a'); $self->edit_href(sub {s/id/$id/}); $self->replace_content($name); } +my %page_cache; +for () { + my ($tmpl, $lang) = m,tmpl/(\w+)\.(\w+),; + my $builder = HTML::TreeBuilder->new; + $builder->ignore_unknown(0); + $page_cache{$tmpl, $lang} = $builder->parse_file($_); +} + sub render { my ($tmpl, $lang, %args) = @_; $lang //= 'en'; my $meat = _render($tmpl, $lang, %args); - _render('skel', $lang, %args, meat => $meat) + my $html = _render('skel', $lang, %args, meat => $meat); + if ($tmpl eq 'pb_entry') { # Move sidebar to correct position + my $builder = HTML::TreeBuilder->new; + $builder->ignore_unknown(0); + my $tree = $builder->parse_content($html); + $tree->fid('content')->postinsert($tree->fid('sidebar')); + $html = $tree->as_HTML(undef, undef, $optional_end_tags) + } + $html } sub render_article { - my ($art, $lang) = @_; + my ($art, $lang, %args) = @_; $lang //= 'en'; my $title = read_file "a/$art.$lang.title"; + chomp $title; my $meat = read_file "a/$art.$lang"; - _render('skel', $lang, title => $title , meat => $meat) + _render('skel', $lang, title => $title , meat => $meat, %args) } sub _render { my ($tmpl, $lang, %args) = @_; - my $builder = HTML::Seamstress->new; - $builder->ignore_unknown(0); - my $tree = $builder->parse_file("tmpl/$tmpl.$lang"); + my $tree = $page_cache{$tmpl, $lang}->clone or die "No such template/language combination: $tmpl/$lang\n"; $tree = $tree->guts unless $tmpl eq 'skel'; $tree->defmap(smap => \%args); my $process = __PACKAGE__->can("process_$tmpl"); $process->($tree, %args) if $process; - $tree->as_HTML; + $_->attr('smap', undef) for $tree->look_down(sub {$_[0]->attr('smap')}); + $tree->as_HTML(undef, undef, $optional_end_tags); } sub process_skel { @@ -114,6 +143,7 @@ sub process_us { sub process_ct_entry { my ($tree, %args) = @_; $_->edit_href (sub {s/contest_id/$args{id}/}) for $tree->find('a'); + $tree->fid('editorial')->detach unless $args{finished}; $tree->fid('links')->detach unless $args{started}; my $status = ($args{time} < $args{start} ? 'starts' : 'ends'); $tree->fclass('timer')->attr('data-stop', $status eq 'ends' ? $args{stop} : $args{start}); @@ -122,6 +152,7 @@ sub process_ct_entry { stop => ftime $args{stop}, status => $status, description => literal $args{description}); + $tree->fid('ctcountdown')->detach if $args{time} >= $args{stop}; } sub process_ct { @@ -133,7 +164,8 @@ sub process_ct { $tr->fclass('name')->namedlink($data->{id}, $data->{name}); $tr->fclass('owner')->namedlink($data->{owner}, $data->{owner_name}); }; - $args{$_} ? $tree->fid($_)->find('tbody')->find('tr')->iter3($args{$_}, $iter) : $tree->fid($_)->detach for qw/running pending finished/; + $_->{status} = $_->{finished} ? 'Finished' : $_->{started} ? 'Running' : 'Pending' for @{$args{ct}}; + $tree->find('tbody')->find('tr')->iter3([ct_sort @{$args{ct}}], $iter); } sub process_pb_entry { @@ -141,12 +173,16 @@ sub process_pb_entry { $tree->fid('owner')->edit_href(sub{s/owner_id/$args{owner}/}); $tree->fid('job_log')->edit_href(sub{s/problem_id/$args{id}/}); $tree->fid('solution')->edit_href(sub{s/problem_id/$args{id}/}); - $tree->fid('job_log')->edit_href(sub{$_ .= "&private=$args{private}"}) if $args{private}; $tree->content_handler( statement => literal $args{statement}, level => ucfirst $args{level}, author => $args{author}, owner => $args{owner_name} || $args{owner}); + if ($args{limits}) { + my @limits = (@{$args{limits}}, {format => 'Other', timeout => $args{timeout} }); + @limits = map { sprintf '%s (%s)', @{$_}{qw/timeout format/} } @limits; + $tree->look_down(smap => 'timeout')->replace_content(join ', ', @limits); + } if ($args{contest_stop}) { $tree->fid('solution')->detach; $tree->fid('solution_modal')->detach; @@ -156,18 +192,16 @@ sub process_pb_entry { $score->attr('data-value' => $args{value}); $tree->fid('countdown')->attr('data-stop' => $args{contest_stop}); } else { + $tree->fid('job_log')->edit_href(sub{$_ .= "&private=$args{private}"}) if $args{private}; $tree->fid('solution')->detach unless $args{solution}; $_->detach for $tree->fclass('rc'); # requires contest - $tree->fid('solution_modal')->fclass('modal-body')->replace_content(literal $args{solution}); - } - if ($args{cansubmit}) { - $tree->look_down(name => 'problem')->attr(value => $args{id}); - my $contest = $tree->look_down(name => 'contest'); - $contest->attr(value => $args{contest}) if $args{contest}; - $contest->detach unless $args{contest} - } else { - $tree->fid('submit')->detach + $tree->fid('solution_modal')->replace_content(literal $args{solution}); } + + $tree->look_down(name => 'problem')->attr(value => $args{id}); + my $contest = $tree->look_down(name => 'contest'); + $contest->attr(value => $args{args}{contest}) if $args{args}{contest}; + $contest->detach unless $args{args}{contest} } sub process_sol { @@ -177,21 +211,18 @@ sub process_sol { sub process_pb { my ($tree, %args) = @_; - my $titer = sub { + my $iter = sub { my ($data, $tr) = @_; $tr->set_child_content(class => 'author', $data->{author}); + $tr->set_child_content(class => 'level', ucfirst $data->{level}); $tr->fclass('name')->namedlink($data->{id}, $data->{name}); - $tr->fclass('name')->find('a')->edit_href(sub {$_ .= "?contest=$args{contest}"}) if $args{contest}; + $tr->fclass('name')->find('a')->edit_href(sub {$_ .= "?contest=$args{args}{contest}"}) if $args{args}{contest}; $tr->fclass('owner')->namedlink($data->{owner}, $data->{owner_name}); - $tr->find('td')->attr(class => $tr->find('td')->attr('class').' warning') if $data->{private}; + $tr->find('td')->attr(class => $tr->find('td')->attr('class').' warning') if $data->{private} && !$args{args}{contest}; }; - my $iter = sub { - my ($data, $div) = @_; - $div->attr(id => $data); - $div->find('h2')->replace_content(ucfirst $data); - $div->find('tbody')->find('tr')->iter3($args{$data}, $titer); - }; - $tree->fid('beginner')->iter3([grep {$args{$_}} qw/beginner easy medium hard/], $iter); + + $tree->find('tbody')->find('tr')->iter3([sort { $a->{value} <=> $b->{value} } @{$args{pb}}], $iter); + $tree->fid('open-alert')->detach unless $args{args}{contest}; } sub process_log_entry { @@ -226,13 +257,11 @@ sub process_log { my ($data, $tr) = @_; $tr->fclass('id')->namedlink($data->{id}); $tr->fclass('problem')->namedlink($data->{problem}, $data->{problem_name}); - $tr->fclass('problem')->find('a')->edit_href(sub{$_ .= "?contest=$args{contest}"}) if $args{contest}; + $tr->fclass('problem')->find('a')->edit_href(sub{$_ .= '?contest='.$data->{contest}}) if $data->{contest}; $tr->fclass('contest')->namedlink($data->{contest}, $data->{contest_name}) if $data->{contest}; $tr->fclass('contest')->replace_content('None') unless $data->{contest}; $tr->fclass('date')->replace_content(ftime $data->{date}); - $tr->fclass('format')->replace_content($data->{format}); - $tr->fclass('size')->namedlink("$data->{id}.$data->{extension}", sprintf "%.2fKB", $data->{size}/1024); - $tr->fclass('size')->attr('data-private', '') if $data->{private}; + $tr->fclass('source')->namedlink("$data->{id}.$data->{extension}", sprintf "%.2fKB %s", $data->{size}/1024, Plack::App::Gruntmaster::FORMAT_EXTENSION()->{$data->{format}}); $tr->fclass('owner')->namedlink($data->{owner}, $data->{owner_name}); $tr->fclass('result_text')->replace_content($data->{result_text}); $tr->fclass('result_text')->attr(class => "r$data->{result}"); @@ -246,9 +275,11 @@ sub process_log { next unless $elem; delete $args{args}{page}; my $str = join '&', map { $_ . '=' . $args{args}{$_} } keys %{$args{args}}; - $elem->find('a')->edit_href(sub{s/$/&$str/}); + $elem->find('a')->edit_href(sub{s/$/&$str/}) if $str; } - $tree->fclass('current')->replace_content("Page $args{current_page} of $args{last_page}"); + + my $total_pages = max(1, $args{last_page}); + $tree->fclass('current')->replace_content("Page $args{current_page} of $total_pages"); } sub process_st { @@ -274,12 +305,16 @@ sub process_st { sub process_ed { my ($tree, %args) = @_; + $tree->content_handler(editorial => literal $args{editorial}); my $iter = sub { my ($data, $div) = @_; $div->set_child_content(class => 'value', $data->{value}); $div->set_child_content(class => 'solution', literal $data->{solution}); $div->fclass('problem')->namedlink($data->{id}, $data->{name}); }; - my @pb = map { @{$args{$_}} } qw/beginner easy medium hard/; + my @pb = sort { $a->{value} <=> $b->{value} } @{$args{pb}}; $tree->fclass('well')->iter3(\@pb, $iter); } + +1; +__END__