summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorYorhel <git@yorhel.nl>2020-02-13 15:28:05 +0100
committerYorhel <git@yorhel.nl>2020-02-13 15:28:07 +0100
commit23fb02e36defa7660ee871dd9e650906b0d2d616 (patch)
treeef96a9aad699df8a42da33930af373d4ea69e177 /lib
parentf95eab4d6547d402d79e443c5488d12b74650f74 (diff)
v2rw: WIP: Convert character pages
This uses an alternative (non-JS) approach to spoiler hiding, which is both much simpler to implement and properly handles all cases. Downside is that it requires a page reload. It also allows direct linking to a character page with a particular view, but I'm not entirely sure if this is a welcome feature. This is a WIP because it doesn't display instances yet.
Diffstat (limited to 'lib')
-rw-r--r--lib/VNDB/BBCode.pm15
-rw-r--r--lib/VNDB/Handler/Chars.pm2
-rw-r--r--lib/VNWeb/Chars/Page.pm216
-rw-r--r--lib/VNWeb/Validation.pm28
4 files changed, 256 insertions, 5 deletions
diff --git a/lib/VNDB/BBCode.pm b/lib/VNDB/BBCode.pm
index 3c8964dc..d11171c5 100644
--- a/lib/VNDB/BBCode.pm
+++ b/lib/VNDB/BBCode.pm
@@ -147,6 +147,11 @@ FINAL:
}
+# charspoil:
+# 0/undef/missing: Output <b class="spoiler">..
+# 1: Output 'charspoil_*' classes
+# 2: Just output 'hidden by spoiler setting' message
+# 3: Just output the spoilers, unmarked
sub bb2html {
my($input, $maxlength, $charspoil) = @_;
@@ -186,11 +191,13 @@ sub bb2html {
$ret .= $e->($raw);
} elsif($tag eq 'spoiler_start') {
- $ret .= !$charspoil
- ? '<b class="spoiler">'
- : '<b class="grayedout charspoil charspoil_-1">&lt;hidden by spoiler settings&gt;</b><span class="charspoil charspoil_2">';
+ $ret .= !$charspoil ? '<b class="spoiler">' :
+ $charspoil == 1 ? '<b class="grayedout charspoil charspoil_-1">&lt;hidden by spoiler settings&gt;</b><span class="charspoil charspoil_2">' :
+ $charspoil == 2 ? '<b class="grayedout charspoil charspoil_-1">&lt;hidden by spoiler settings&gt;</b><!--' : '';
} elsif($tag eq 'spoiler_end') {
- $ret .= !$charspoil ? '</b>' : '</span>';
+ $ret .= !$charspoil ? '</b>' :
+ $charspoil == 1 ? '</span>' :
+ $charspoil == 2 ? '-->' : '';
} elsif($tag eq 'quote_start') {
$ret .= '<div class="quote">' if !$maxlength;
diff --git a/lib/VNDB/Handler/Chars.pm b/lib/VNDB/Handler/Chars.pm
index cff84607..059c7b3f 100644
--- a/lib/VNDB/Handler/Chars.pm
+++ b/lib/VNDB/Handler/Chars.pm
@@ -12,7 +12,7 @@ use List::Util 'min';
our @EXPORT = ('charOps', 'charTable', 'charBrowseTable');
TUWF::register(
- qr{c([1-9]\d*)(?:\.([1-9]\d*))?} => \&page,
+ qr{oc([1-9]\d*)(?:\.([1-9]\d*))?} => \&page,
qr{c(?:([1-9]\d*)(?:\.([1-9]\d*))?/(edit|copy)|/new)}
=> \&edit,
qr{c/([a-z0]|all)} => \&list,
diff --git a/lib/VNWeb/Chars/Page.pm b/lib/VNWeb/Chars/Page.pm
new file mode 100644
index 00000000..06493de6
--- /dev/null
+++ b/lib/VNWeb/Chars/Page.pm
@@ -0,0 +1,216 @@
+package VNWeb::Chars::Page;
+
+use VNWeb::Prelude;
+
+
+sub enrich_seiyuu {
+ my($vid, @chars) = @_;
+ enrich seiyuu => id => cid => sub { sql '
+ SELECT DISTINCT vs.cid, sa.id, sa.name, sa.original, vs.note
+ FROM vn_seiyuu vs
+ JOIN staff_alias sa ON sa.aid = vs.aid
+ WHERE vs.cid IN', $_, $vid ? ('AND vs.id =', \$vid) : (), '
+ ORDER BY sa.name'
+ }, @chars;
+}
+
+
+sub enrich_item {
+ my($c) = @_;
+
+ enrich_merge vid => 'SELECT id AS vid, title, original FROM vn WHERE id IN', $c->{vns};
+ enrich_merge rid => 'SELECT id AS rid, title AS rtitle, original AS roriginal FROM releases WHERE id IN', grep $_->{rid}, $c->{vns}->@*;
+ enrich_merge tid =>
+ 'SELECT t.id AS tid, t.name, t.sexual, coalesce(g.id, t.id) AS group, coalesce(g.name, t.name) AS groupname, coalesce(g.order,0) AS order
+ FROM traits t LEFT JOIN traits g ON t.group = g.id WHERE t.id IN', $c->{traits};
+
+ $c->{vns} = [ sort { $a->{title} cmp $b->{title} || $a->{vid} <=> $b->{vid} || ($a->{rid}||999999) <=> ($b->{rid}||999999) } $c->{vns}->@* ];
+ $c->{traits} = [ sort { $a->{order} <=> $b->{order} || $a->{groupname} cmp $b->{groupname} || $a->{name} cmp $b->{name} } $c->{traits}->@* ];
+}
+
+
+sub _rev_ {
+ my($c) = @_;
+ revision_ c => $c, \&enrich_item,
+ [ name => 'Name' ],
+ [ original => 'Original name' ],
+ [ alias => 'Aliases' ],
+ [ desc => 'Description' ],
+ [ gender => 'Sex', fmt => \%GENDER ],
+ [ b_month => 'Birthday/month',empty => 0 ],
+ [ b_day => 'Birthday/day', empty => 0 ],
+ [ s_bust => 'Bust', empty => 0 ],
+ [ s_waist => 'Waist', empty => 0 ],
+ [ s_hip => 'Hip', empty => 0 ],
+ [ height => 'Height', empty => 0 ],
+ [ weight => 'Weight', ],
+ [ bloodt => 'Blood type', fmt => \%BLOOD_TYPE ],
+ [ cup_size => 'Cup size', fmt => \%CUP_SIZE ],
+ [ age => 'Age', empty => 0 ],
+ [ main => 'Main character',empty => 0, fmt => sub {
+ my $c = tuwf->dbRowi('SELECT id, name, original FROM chars WHERE id =', \$_);
+ a_ href => "/c$c->{id}", title => $c->{name}, "c$c->{id}"
+ } ],
+ [ main_spoil => 'Spoiler', fmt => sub { txt_ fmtspoil $_ } ],
+ [ image => 'Image', empty => 0, fmt => sub { img_ src => tuwf->imgurl(ch => $_) } ],
+ [ vns => 'Visual novels', fmt => sub {
+ a_ href => "/v$_->{vid}", title => $_->{original}||$_->{title}, "v$_->{vid}";
+ if($_->{rid}) {
+ txt_ ' ['; a_ href => "/r$_->{rid}", "r$_->{rid}"; txt_ ']';
+ }
+ txt_ " $CHAR_ROLE{$_->{role}}{txt} (".fmtspoil($_->{spoil}).')';
+ } ],
+ [ traits => 'Traits', fmt => sub {
+ b_ class => 'grayedout', "$_->{groupname} / " if $_->{group} != $_->{tid};
+ a_ href => "/i$_->{tid}", $_->{name};
+ txt_ ' ('.fmtspoil($_->{spoil}).')';
+ } ],
+}
+
+
+# TODO: Also to be used by the character listing on VN pages; But it's not
+# currently compatible with VNDB::Handler::VNPage because that uses a different
+# spoiler hiding mechanism.
+sub chartable_ {
+ my($c, $link, $sep, $vn) = @_;
+ my $view = viewget;
+
+ div_ mkclass(chardetails => 1, charsep => $sep), sub {
+ div_ class => 'charimg', sub {
+ p_ 'No image uploaded yet' if !$c->{image};
+ img_ src => tuwf->imgurl(ch => $c->{image}), alt => $c->{name} if $c->{image};
+ };
+ table_ class => 'stripe', sub {
+ thead_ sub { tr_ sub { td_ colspan => 2, sub {
+ $link
+ ? a_ href => "/c$c->{id}", style => 'margin-right: 10px; font-weight: bold', $c->{name}
+ : b_ style => 'margin-right: 10px', $c->{name};
+ b_ class => 'grayedout', style => 'margin-right: 10px', $c->{original} if $c->{original};
+ abbr_ class => "icons gen $c->{gender}", title => $GENDER{$c->{gender}}, '' if $c->{gender} ne 'unknown';
+ span_ $BLOOD_TYPE{$c->{bloodt}} if $c->{bloodt} ne 'unknown';
+ }}};
+
+ tr_ sub {
+ td_ class => 'key', 'Aliases';
+ td_ $c->{alias} =~ s/\n/, /rg;
+ } if $c->{alias};
+
+ tr_ sub {
+ td_ class => 'key', 'Measurements';
+ td_ join ', ',
+ $c->{height} ? "Height: $c->{height}cm" : (),
+ defined($c->{weight}) ? "Weight: $c->{weight}kg" : (),
+ $c->{s_bust} || $c->{s_waist} || $c->{s_hip} ?
+ sprintf 'Bust-Waist-Hips: %s-%s-%scm', $c->{s_bust}||'??', $c->{s_waist}||'??', $c->{s_hip}||'??' : (),
+ $c->{cup_size} ? "$CUP_SIZE{$c->{cup_size}} cup" : ();
+ } if defined($c->{weight}) || $c->{height} || $c->{s_bust} || $c->{s_waist} || $c->{s_hip} || $c->{cup_size};
+
+ tr_ sub {
+ td_ class => 'key', 'Birthday';
+ td_ $c->{b_day}.' '.[qw{January February March April May June July August September October November December}]->[$c->{b_month}-1];
+ } if $c->{b_day} && $c->{b_month};
+
+ tr_ sub {
+ td_ class => 'key', 'Age';
+ td_ $c->{age};
+ } if defined $c->{age};
+
+ my @groups;
+ for(grep $_->{spoil} <= $view->{spoilers} && (!$_->{sexual} || $view->{traits_sexual}), $c->{traits}->@*) {
+ push @groups, $_ if !@groups || $groups[$#groups]{group} != $_->{group};
+ push $groups[$#groups]{traits}->@*, $_;
+ }
+ tr_ sub {
+ td_ class => 'key', sub { a_ href => "/i$_->{group}", $_->{groupname} };
+ td_ sub { join_ ', ', sub { a_ href => "/i$_->{tid}", $_->{name} }, $_->{traits}->@* };
+ } for @groups;
+
+ my @visvns = grep $_->{spoil} <= $view->{spoilers}, $c->{vns}->@*;
+ tr_ sub {
+ td_ $vn ? 'Releases' : 'Visual novels';
+ td_ sub {
+ my @vns;
+ for(@visvns) {
+ push @vns, $_ if !@vns || $vns[$#vns]{vid} != $_->{vid};
+ push $vns[$#vns]{rels}->@*, $_;
+ }
+ join_ \&br_, sub {
+ my $v = $_;
+ # Just a VN link, no releases
+ if(!$vn && $v->{rels}->@* == 1 && !$v->{rels}[0]{rid}) {
+ txt_ $CHAR_ROLE{$v->{role}}{txt}.' - ';
+ a_ href => "/v$v->{vid}", title => $v->{original}||$v->{title}, $v->{title};
+ # With releases
+ } else {
+ a_ href => "/v$v->{vid}", title => $v->{original}||$v->{title}, $v->{title} if !$vn;
+ br_ if !$vn;
+ join_ \&br_, sub {
+ b_ class => 'grayedout', '> ';
+ txt_ $CHAR_ROLE{$v->{role}}{txt}.' - ';
+ if($_->{rid}) {
+ b_ class => 'grayedout', "r$_->{rid}:";
+ a_ href => "/r$_->{rid}", title => $_->{roriginal}||$_->{rtitle}, $_->{rtitle};
+ } else {
+ txt_ 'All other releases';
+ }
+ }, $v->{rels}->@*;
+ }
+ }, @vns;
+ };
+ } if @visvns && (!$vn || $vn && (@visvns > 1 || $visvns[0]{rid}));
+
+ tr_ sub {
+ td_ 'Voiced by';
+ td_ sub {
+ join_ \&br_, sub {
+ a_ href => "/s$_->{id}", title => $_->{original}||$_->{name}, $_->{name};
+ txt_ " ($_->{note})" if $_->{note};
+ }, $c->{seiyuu}->@*;
+ };
+ } if $c->{seiyuu}->@*;
+
+ tr_ class => 'nostripe', sub {
+ td_ colspan => 2, class => 'chardesc', sub {
+ h2_ 'Description';
+ p_ sub { lit_ bb2html $c->{desc}, 0, $view->{spoilers} == 2 ? 3 : 2 };
+ };
+ } if $c->{desc};
+ };
+ };
+ clearfloat_;
+}
+
+
+TUWF::get qr{/$RE{crev}} => sub {
+ my $c = db_entry c => tuwf->capture('id'), tuwf->capture('rev');
+ return tuwf->resNotFound if !$c;
+
+ enrich_item $c;
+ enrich_seiyuu undef, $c;
+
+ framework_ title => $c->{name}, index => !tuwf->capture('rev'), type => 'c', dbobj => $c, hiddenmsg => 1,
+ og => {
+ description => bb2text $c->{desc}
+ },
+ sub {
+ _rev_ $c if tuwf->capture('rev');
+ div_ class => 'mainbox', sub {
+ itemmsg_ c => $c;
+ p_ class => 'mainopts', sub {
+ my $view = viewget;
+ a_ mkclass(checked => $view->{spoilers} == 0), href => '?view='.viewset(spoilers=>0), 'Hide spoilers';
+ a_ mkclass(checked => $view->{spoilers} == 1), href => '?view='.viewset(spoilers=>1), 'Show minor spoilers';
+ a_ mkclass(standout =>$view->{spoilers} == 2), href => '?view='.viewset(spoilers=>2), 'Spoil me!';
+ b_ class => 'grayedout', ' | ';
+ a_ mkclass(checked => $view->{traits_sexual}), href => '?view='.viewset(traits_sexual=>!$view->{traits_sexual}), 'Show sexual traits';
+ };
+ h1_ sub { txt_ $c->{name}; debug_ $c };
+ h2_ class => 'alttitle', $c->{original} if length $c->{original};
+ chartable_ $c;
+ };
+
+ # TODO: Other instances
+ };
+};
+
+1;
diff --git a/lib/VNWeb/Validation.pm b/lib/VNWeb/Validation.pm
index c506903a..b8f1bcdc 100644
--- a/lib/VNWeb/Validation.pm
+++ b/lib/VNWeb/Validation.pm
@@ -16,6 +16,7 @@ our @EXPORT = qw/
form_changed
validate_dbid
can_edit
+ viewget viewset
/;
@@ -174,4 +175,31 @@ sub can_edit {
auth->permDbmod || (auth->permEdit && !($entry->{entry_hidden} || $entry->{entry_locked}));
}
+
+# Returns { spoilers => 0-2, traits_sexual => 0/1 }
+# Based on the view= query parameter or the user's preferences.
+# The query parameter has the following format:
+# view=1 -> spoilers=1, traits_sexual=<default>
+# view=2s -> spoilers=2, traits_sexual=1
+# view=2S -> spoilers=2, traits_sexual=0
+# view=S -> spoilers=<default>, traits_sexual=0
+# i.e. a list of single-character flags:
+# 0-2 -> spoilers
+# s/S -> 1/0 traits_sexual
+# Missing flags will use default.
+sub viewget {
+ (tuwf->reqGet('view')) =~ /^([0-2]?)([sS]?)$/;
+ {
+ spoilers => $1 // auth->pref('spoilers') || 0,
+ traits_sexual => !$2 ? auth->pref('traits_sexual') : $2 eq 's',
+ }
+}
+
+# Modifies the current view settings and serializes that into a view= value.
+# XXX: This may include more flags than the current page will use.
+sub viewset {
+ my %s = (viewget->%*, @_);
+ $s{spoilers}.($s{traits_sexual}?'s':'S')
+}
+
1;