package VNDB::Util::BrowseHTML; use strict; use warnings; use TUWF ':html', 'xml_escape'; use Exporter 'import'; use VNDB::Func; use POSIX 'ceil'; our @EXPORT = qw| htmlBrowse htmlBrowseNavigate htmlBrowseHist htmlBrowseVN |; # generates a browse box, arguments: # items => arrayref with the list items # options => hashref containing at least the keys s (sort key), o (order) and p (page) # nextpage => whether there's a next page or not # sorturl => base URL to append the sort options to (if there are any sortable columns) # pageurl => base URL to append the page option to # class => classname of the mainbox # header => # can be either an arrayref or subroutine reference, # in the case of a subroutine, it will be called when the header should be written, # in the case of an arrayref, the array should contain the header items. Each item # can again be either an arrayref or subroutine ref. The arrayref would consist of # two elements: the name of the header, and the name of the sorting column if it can # be sorted # row => subroutine ref, which is called for each item in $list, arguments will be # $self, $item_number (starting from 0), $item_value # footer => subroutine ref, called after all rows have been processed sub htmlBrowse { my($self, %opt) = @_; $opt{sorturl} .= $opt{sorturl} =~ /\?/ ? ';' : '?' if $opt{sorturl}; # top navigation $self->htmlBrowseNavigate($opt{pageurl}, $opt{options}{p}, $opt{nextpage}, 't') if $opt{pageurl}; div class => 'mainbox browse'.($opt{class} ? ' '.$opt{class} : ''); table class => 'stripe'; # header thead; Tr; if(ref $opt{header} eq 'CODE') { $opt{header}->($self); } else { for(0..$#{$opt{header}}) { if(ref $opt{header}[$_] eq 'CODE') { $opt{header}[$_]->($self, $_+1); } else { td class => $opt{header}[$_][3]||'tc'.($_+1), $opt{header}[$_][2] ? (colspan => $opt{header}[$_][2]) : (); lit $opt{header}[$_][0]; if($opt{header}[$_][1]) { lit ' '; $opt{options}{s} eq $opt{header}[$_][1] && $opt{options}{o} eq 'a' ? lit "\x{25B4}" : a href => "$opt{sorturl}o=a;s=$opt{header}[$_][1]", "\x{25B4}"; $opt{options}{s} eq $opt{header}[$_][1] && $opt{options}{o} eq 'd' ? lit "\x{25BE}" : a href => "$opt{sorturl}o=d;s=$opt{header}[$_][1]", "\x{25BE}"; } end; } } } end; end 'thead'; # footer if($opt{footer}) { tfoot; $opt{footer}->($self); end; } # rows $opt{row}->($self, $_+1, $opt{items}[$_]) for 0..$#{$opt{items}}; end 'table'; end 'div'; # bottom navigation $self->htmlBrowseNavigate($opt{pageurl}, $opt{options}{p}, $opt{nextpage}, 'b') if $opt{pageurl}; } # creates next/previous buttons (tabs), if needed # Arguments: page url, current page (1..n), nextpage (0/1 or [$total, $perpage]), alignment (t/b), noappend (0/1) sub htmlBrowseNavigate { my($self, $url, $p, $np, $al, $na) = @_; my($cnt, $pp) = ref($np) ? @$np : ($p+$np, 1); return if $p == 1 && $cnt <= $pp; $url .= $url =~ /\?/ ? ';p=' : '?p=' unless $na; my $tab = sub { my($left, $page, $label) = @_; li $left ? (class => 'left') : (); a href => $url.$page; lit $label; end; end; }; my $ell = sub { use utf8; li class => 'ellipsis'.(shift() ? ' left' : ''); b '⋯'; end; }; my $nc = 5; # max. number of buttons on each side ul class => 'maintabs browsetabs ' . ($al eq 't' ? 'notfirst' : 'bottom'); $p > 2 and ref $np and $tab->(1, 1, '« first'); $p > $nc+1 and ref $np and $ell->(1); $p > $_ and ref $np and $tab->(1, $p-$_, $p-$_) for (reverse 2..($nc>$p-2?$p-2:$nc-1)); $p > 1 and $tab->(1, $p-1, '‹ previous'); my $l = ceil($cnt/$pp)-$p+1; $l > 2 and $tab->(0, $l+$p-1, 'last »'); $l > $nc+1 and $ell->(0); $l > $_ and $tab->(0, $p+$_, $p+$_) for (reverse 2..($nc>$l-2?$l-2:$nc-1)); $l > 1 and $tab->(0, $p+1, 'next ›'); end 'ul'; } sub htmlBrowseHist { my($self, $list, $f, $np, $url) = @_; $self->htmlBrowse( items => $list, options => $f, nextpage => $np, pageurl => $url, class => 'history', header => [ sub { td class => 'tc1_1', 'Rev.'; td class => 'tc1_2', ''; }, [ 'Date' ], [ 'User' ], [ 'Page' ], ], row => sub { my($s, $n, $i) = @_; my $revurl = "/$i->{type}$i->{itemid}.$i->{rev}"; Tr; td class => 'tc1_1'; a href => $revurl, "$i->{type}$i->{itemid}"; end; td class => 'tc1_2'; a href => $revurl, ".$i->{rev}"; end; td class => 'tc2', fmtdate $i->{added}, 'full'; td class => 'tc3'; lit fmtuser $i; end; td class => 'tc4'; a href => $revurl, title => $i->{ioriginal}, shorten $i->{ititle}, 80; b class => 'grayedout'; lit bb2html $i->{comments}, 150; end; end; end 'tr'; }, ); } sub htmlBrowseVN { my($self, $list, $f, $np, $url, $tagscore) = @_; $self->htmlBrowse( class => 'vnbrowse', items => $list, options => $f, nextpage => $np, pageurl => "$url;o=$f->{o};s=$f->{s}", sorturl => $url, header => [ $tagscore ? [ 'Score', 'tagscore', undef, 'tc_s' ] : (), [ 'Title', 'title', undef, $tagscore ? 'tc_t' : 'tc1' ], $f->{vnlist} ? [ '', 0, undef, 'tc7' ] : (), $f->{wish} ? [ '', 0, undef, 'tc8' ] : (), [ '', 0, undef, 'tc2' ], [ '', 0, undef, 'tc3' ], [ 'Released', 'rel', undef, 'tc4' ], [ 'Popularity', 'pop', undef, 'tc5' ], [ 'Rating', 'rating', undef, 'tc6' ], ], row => sub { my($s, $n, $l) = @_; Tr; if($tagscore) { td class => 'tc_s'; tagscore $l->{tagscore}, 0; end; } td class => $tagscore ? 'tc_t' : 'tc1'; a href => '/v'.$l->{id}, title => $l->{original}||$l->{title}, shorten $l->{title}, 100; end; if($f->{vnlist}) { td class => 'tc7'; lit sprintf '%d/%d', $l->{userlist_obtained} == $l->{userlist_all} ? 'done' : 'todo', $l->{userlist_obtained}, $l->{userlist_all} if $l->{userlist_all}; end 'td'; } td class => 'tc8', defined($l->{wstat}) ? $self->{wishlist_status}[$l->{wstat}] : '' if $f->{wish}; td class => 'tc2'; $_ ne 'oth' && cssicon $_, $self->{platforms}{$_} for (sort @{$l->{c_platforms}}); end; td class => 'tc3'; cssicon "lang $_", $self->{languages}{$_} for (reverse sort @{$l->{c_languages}}); end; td class => 'tc4'; lit fmtdatestr $l->{c_released}; end; td class => 'tc5', sprintf '%.2f', ($l->{c_popularity}||0)*100; td class => 'tc6'; txt sprintf '%.2f', ($l->{c_rating}||0)/10; b class => 'grayedout', sprintf ' (%d)', $l->{c_votecount}; end; end 'tr'; }, ); } 1;