diff options
Diffstat (limited to 'lib/VN3/HTML.pm')
-rw-r--r-- | lib/VN3/HTML.pm | 375 |
1 files changed, 0 insertions, 375 deletions
diff --git a/lib/VN3/HTML.pm b/lib/VN3/HTML.pm deleted file mode 100644 index 0dcd7241..00000000 --- a/lib/VN3/HTML.pm +++ /dev/null @@ -1,375 +0,0 @@ -# Convention: -# All HTML-generating functions are in CamelCase -# -# TODO: HTML generation for dropdowns can be abstracted more nicely. - -package VN3::HTML; - -use strict; -use warnings; -use v5.10; -use utf8; -use List::Util 'pairs', 'max', 'sum'; -use TUWF ':Html5', 'mkclass', 'uri_escape'; -use VNWeb::Auth; -use VN3::Types; -use VN3::Validation; -use base 'Exporter'; - -our @EXPORT = qw/Framework EntryEdit Switch Debug Join FullPageForm VoteGraph ListIcon GridIcon/; - - -sub Navbar { - Div class => 'nav navbar__nav navbar__main-nav', sub { - Div class => 'nav__item navbar__menu dropdown', sub { - A href => 'javascript:;', class => 'nav__link dropdown__toggle', sub { Txt 'Database '; Span class => 'caret', '' }; - Div class => 'dropdown-menu database-menu', sub { - A class => 'dropdown-menu__item', href => '/v/all', 'Visual novels'; - A class => 'dropdown-menu__item', href => '/g', 'Tags'; - A class => 'dropdown-menu__item', href => '/c/all', 'Characters'; - A class => 'dropdown-menu__item', href => '/i', 'Traits'; - A class => 'dropdown-menu__item', href => '/p/all', 'Producers'; - A class => 'dropdown-menu__item', href => '/s/all', 'Staff'; - A class => 'dropdown-menu__item', href => '/r', 'Releases'; - }; - }; - Div class => 'nav__item navbar__menu', sub { A class => 'nav__link', href => '/d6', 'FAQ' }; - Div class => 'nav__item navbar__menu', sub { A class => 'nav__link', href => '/t', 'Forums' }; - Div class => 'nav__item navbar__menu dropdown', sub { - A href => 'javascript:;', class => 'nav__link dropdown__toggle', sub { Txt 'Contribute '; Span class => 'caret', '' }; - Div class => 'dropdown-menu', sub { - A class => 'dropdown-menu__item', href => '/hist', 'Recent changes'; - A class => 'dropdown-menu__item', href => '/v/add', 'Add Visual Novel'; - A class => 'dropdown-menu__item', href => '/p/add', 'Add Producer'; - A class => 'dropdown-menu__item', href => '/s/new', 'Add Staff'; - }; - }; - Div class => 'nav__item navbar__menu', sub { - A href => '/v/all', class => 'nav__link', sub { - Span class => 'icon-desc d-md-none', 'Search '; - Img class => 'svg-icon', src => tuwf->conf->{url_static}.'/v3/heavy/search.svg'; - }; - }; - }; - - Div class => 'nav navbar__nav', sub { - my $notifies = auth && tuwf->dbVali('SELECT count(*) FROM notifications WHERE uid =', \auth->uid, 'AND read IS NULL'); - Div class => 'nav__item navbar__menu', sub { - A href => '/'.auth->uid.'/notifies', class => 'nav__link notification-icon', sub { - Span class => 'icon-desc d-md-none', 'Notifications '; - Div class => 'icon-group', sub { - Img class => 'svg-icon', src => tuwf->conf->{url_static}.'/v3/bell.svg'; - Div class => 'notification-icon__indicator', $notifies; - }; - }; - } if $notifies; - Div class => 'nav__item navbar__menu dropdown', sub { - A href => 'javascript:;', class => 'nav__link dropdown__toggle', sub { Txt auth->username.' '; Span class => 'caret'; }; - Div class => 'dropdown-menu dropdown-menu--right', sub { - my $id = auth->uid; - A class => 'dropdown-menu__item', href => "/u$id", 'Profile'; - A class => 'dropdown-menu__item', href => "/u$id/edit", 'Settings'; - A class => 'dropdown-menu__item', href => "/u$id/list", 'List'; - A class => 'dropdown-menu__item', href => "/u$id/wish", 'Wishlist'; - A class => 'dropdown-menu__item', href => "/u$id/hist", 'Recent changes'; - A class => 'dropdown-menu__item', href => "/g/links?u=$id", 'Tags'; - Div class => 'dropdown__separator', ''; - A class => 'dropdown-menu__item', href => "/u$id/logout", 'Log out'; - }; - } if auth; - if(!auth) { - Div class => 'nav__item navbar__menu', sub { A class => 'nav__link', href => '/u/register', 'Register'; }; - Div class => 'nav__item navbar__menu', sub { A class => 'nav__link', href => '/u/login', 'Login'; }; - } - }; -} - - -sub Top { - my($opt) = @_; - Div class => 'raised-top-container', sub { - Div class => 'raised-top', sub { - Div class => 'container', sub { - Div class => 'navbar navbar--expand-md', sub { - Div class => 'navbar__logo', sub { - A href => '/', 'vndb'; - }; - A href => 'javascript:;', class => 'navbar__toggler', sub { - Div class => 'navbar__toggler-icon', ''; - }; - Div class => 'navbar__collapse', \&Navbar; - }; - Div class => 'row', $opt->{top} if $opt->{top}; - }; - }; - }; -} - - -sub Bottom { - Div class => 'col-md col-md--1', sub { - Div class => 'footer__logo', sub { - A href => '/', class => 'link-subtle', 'vndb'; - }; - }; - - state $sep = sub { Span class => 'footer__sep', sub { Lit '·'; }; }; - state $lnk = sub { A href => $_[0], class => 'link--subtle', $_[1]; }; - state $root = tuwf->root; - state $ver = `git -C "$root" describe` =~ /^(.+)$/ ? $1 : ''; - - Div class => 'col-md col-md--4', sub { - Div class => 'footer__nav', sub { - $lnk->('/d7', 'about us'); - $sep->(); - $lnk->('irc://irc.synirc.net/vndb', '#vndb'); - $sep->(); - $lnk->('mailto:contact@vndb.org', 'contact@vndb.org'); - $sep->(); - $lnk->('https://code.blicky.net/yorhel/vndb/src/branch/v3', 'source'); - $sep->(); - A href => '/v/rand', class => 'link--subtle footer__random', sub { - Txt 'random vn '; - Img class => 'svg-icon', src => tuwf->conf->{url_static}.'/v3/heavy/random.svg'; - }; - $sep->(); - Txt $ver; - }; - - my $q = tuwf->dbRow('SELECT vid, quote FROM quotes ORDER BY random() LIMIT 1'); - Div class => 'footer__quote', sub { - $lnk->('/v'.$q->{vid}, $q->{quote}); - } if $q; - }; -} - - -sub Framework { - my $body = pop; - my %opt = @_; - Html sub { - Head prefix => 'og: http://ogp.me/ns#', sub { - Meta name => 'viewport', content => 'width=device-width, initial-scale=1, shrink-to-fit=no'; - Meta name => 'csrf-token', content => auth->csrftoken; - Meta charset => 'utf-8'; - Meta name => 'robots', content => 'noindex, follow' if exists $opt{index} && !$opt{index}; - Title $opt{title} . ' | vndb'; - Link rel => 'stylesheet', href => tuwf->conf->{url_static}.'/v3/style.css'; - Link rel => 'shortcut icon', href => '/favicon.ico', type => 'image/x-icon'; - Link rel => 'search', type => 'application/opensearchdescription+xml', title => 'VNDB VN Search', href => tuwf->reqBaseURI().'/opensearch.xml'; - - # TODO: Link to RSS feeds. - - # Opengraph metadata - if($opt{og}) { - $opt{og}{site_name} ||= 'The Visual Novel Database'; - $opt{og}{type} ||= 'object'; - $opt{og}{image} ||= 'https://s.vndb.org/s/angel/bg.jpg'; # TODO: Something better - $opt{og}{url} ||= tuwf->reqURI; - $opt{og}{title} ||= $opt{title}; - Meta property => "og:$_", content => ($opt{og}{$_} =~ s/\n/ /gr) for sort keys %{$opt{og}}; - } - }; - Body sub { - Div class => 'top-bar', id => 'top', ''; - Top \%opt; - Div class => 'page-container', sub { - Div mkclass( - container => 1, - 'main-container' => 1, - 'container--narrow' => $opt{narrow}, - 'flex-center-container' => $opt{center}, - 'main-container--single-col' => $opt{single_col}, - $opt{main_classes} ? %{$opt{main_classes}} :() - ), $body; - Div class => 'container', sub { - Div class => 'footer', sub { - Div class => 'row', \&Bottom; - }; - }; - }; - Script type => 'text/javascript', src => tuwf->conf->{url_static}.'/v3/elm.js', ''; - Script type => 'text/javascript', src => tuwf->conf->{url_static}.'/v3/vndb.js', ''; - #Script type => 'text/javascript', src => tuwf->conf->{url_static}.'/v3/min.js', ''; - }; - }; - if(tuwf->debug) { - tuwf->dbCommit; # Hack to measure the commit time - - my $sql = uri_escape join "\n", map { - my($sql, $params, $time) = @$_; - sprintf " [%6.2fms] %s | %s", $time*1000, $sql, - join ', ', map "$_:".DBI::neat($params->{$_}), - sort { $a =~ /^[0-9]+$/ && $b =~ /^[0-9]+$/ ? $a <=> $b : $a cmp $b } - keys %$params; - } @{ tuwf->{_TUWF}{DB}{queries} }; - A href => 'data:text/plain,'.$sql, 'SQL'; - - my $modules = uri_escape join "\n", sort keys %INC; - A href => 'data:text/plain,'.$modules, 'Modules'; - } -} - - -sub EntryEdit { - my($type, $e) = @_; - - return if $type eq 'u' && !auth->permUsermod; - - Div class => 'dropdown pull-right', sub { - A href => 'javascript:;', class => 'btn d-block dropdown__toggle', sub { - Div class => 'opacity-muted', sub { - Img class => 'svg-icon', src => tuwf->conf->{url_static}.'/v3/edit.svg'; - Span class => 'caret', ''; - }; - }; - Div class => 'dropdown-menu dropdown-menu--right database-menu', sub { - A class => 'dropdown-menu__item', href => "/$type$e->{id}", 'Details'; - A class => 'dropdown-menu__item', href => "/$type$e->{id}/hist", 'History' if $type ne 'u'; - A class => 'dropdown-menu__item', href => "/$type$e->{id}/edit", 'Edit' if can_edit $type, $e; - A class => 'dropdown-menu__item', href => "/$type$e->{id}/add", 'Add release' if $type eq 'v' && can_edit $type, $e; - A class => 'dropdown-menu__item', href => "/$type$e->{id}/addchar",'Add character' if $type eq 'v' && can_edit $type, $e; - A class => 'dropdown-menu__item', href => "/$type$e->{id}/copy", 'Copy' if $type =~ /[cr]/ && can_edit $type, $e; - }; - } -} - - -sub Switch { - my $label = shift; - my $on = shift; - my @class = mkclass - 'switch' => 1, - 'switch--on' => $on, - @_; - - A @class, href => 'javascript:;', sub { - Div class => 'switch__label', $label; - Div class => 'switch__toggle', ''; - }; -} - - -# Throw any data structure on the page for inspection. -sub Debug { - return if !tuwf->debug; - require JSON::XS; - # This provides a nice JSON browser in FF, not sure how other browsers render it. - my $data = uri_escape(JSON::XS->new->canonical->encode($_[0])); - A style => 'margin: 0 5px', title => 'Debug', href => 'data:application/json,'.$data, ' ⚙ '; -} - - -# Similar to join($sep, map $item->($_), @list), but works for HTML generation functions. -# Join ', ', sub { A href => '#', $_[0] }, @list; -# Join \&Br, \&Txt, @list; -sub Join { - my($sep, $item, @list) = @_; - for my $i (0..$#list) { - ref $sep ? $sep->() : Txt $sep if $i > 0; - $item->($list[$i]); - } -} - - -# Full-page form, optionally with sections. Options: -# -# module => '', # Elm module to load -# data => $form_data, -# schema => $tuwf_validate_schema, # Optional TUWF::Validate schema to use to encode the data -# sections => [ # Optional list of sections -# anchor1 => 'Section 1', -# .. -# ] -# -# If no sections are given, the parent Framework() should have narrow => 1. -sub FullPageForm { - my %o = @_; - - my $form = sub { Div - 'data-elm-module' => $o{module}, - 'data-elm-flags' => JSON::XS->new->encode($o{schema} ? $o{schema}->analyze->coerce_for_json($o{data}) : $o{data}), - '' - }; - - Div class => 'row', $o{sections} ? sub { - - Div class => 'col-md col-md--1', sub { - Div class => 'nav-sidebar nav-sidebar--expand-md', sub { - A href => 'javascript:;', class => 'nav-sidebar__selection', sub { - Txt $o{sections}[1]; - Div class => 'caret', ''; - }; - Div class => 'nav nav--vertical', sub { - my $x = 0; - for my $s (pairs @{$o{sections}}) { - Div mkclass(nav__item => 1, 'nav__item--active' => !$x++), sub { - A class => 'nav__link', href => '#'.$s->key, $s->value; - } - } - }; - } - }; - Div class => 'col-md col-md--4', $form; - } : sub { - Div class => 'col-md col-md--1', $form; - }; -} - - -sub VoteGraph { - my($type, $id) = @_; - - my %histogram = map +($_->{vote}, $_), @{ tuwf->dbAlli(q{ - SELECT (vote::numeric/10)::int AS vote, COUNT(vote) as votes, SUM(vote) AS total - FROM votes}, - $type eq 'v' ? (q{ - JOIN users ON id = uid AND NOT ign_votes - WHERE vid =}, \$id - ) : (q{ - WHERE uid =}, \$id - ), q{ - GROUP BY (vote::numeric/10)::int - })}; - - my $max = max map $_->{votes}, values %histogram; - my $count = sum map $_->{votes}, values %histogram; - my $sum = sum map $_->{total}, values %histogram; - - my $Graph = sub { - Div class => 'vote-graph', sub { - Div class => 'vote-graph__scores', sub { - Div class => 'vote-graph__score', $_ for (reverse 1..10); - }; - Div class => 'vote-graph__bars', sub { - Div class => 'vote-graph__bar', style => sprintf('width: %.2f%%', ($histogram{$_}{votes}||0)/$max*100), sub { - Div class => 'vote-graph__bar-label', $histogram{$_}{votes}||'1'; - } for (reverse 1..10); - }; - }; - Div class => 'final-text', - sprintf '%d vote%s total, average %.2f%s', - $count, $count == 1 ? '' : 's', $sum/$count/10, - $type eq 'v' ? ' ('.vote_string($sum/$count).')' : ''; - }; - return ($count, $Graph); -} - -sub ListIcon { - Lit q{<svg class="svg-icon" xmlns="http://www.w3.org/2000/svg" width="14" height="14" version="1">} - .q{<g fill="currentColor" fill-rule="nonzero">} - .q{<path d="M0 2h14v2H0zM0 6h14v2H0zM0 10h14v2H0z"/>} - .q{</g>} - .q{</svg>}; -} - - -sub GridIcon { - Lit q{<svg class="svg-icon" xmlns="http://www.w3.org/2000/svg" width="14" height="14" version="1">} - .q{<g fill="currentColor" fill-rule="nonzero">} - .q{<path d="M0 0h3v3H0zM0 5h3v3H0zM0 10h3v3H0zM5 0h3v3H5zM5 5h3v3H5zM5 10h3v3H5zM10 0h3v3h-3zM10 5h3v3h-3zM10 10h3v3h-3z"/>} - .q{</g>} - .q{</svg>}; -} - -1; |