summaryrefslogtreecommitdiff
path: root/lib/VNWeb/Releases
diff options
context:
space:
mode:
Diffstat (limited to 'lib/VNWeb/Releases')
-rw-r--r--lib/VNWeb/Releases/DRM.pm120
-rw-r--r--lib/VNWeb/Releases/Edit.pm139
-rw-r--r--lib/VNWeb/Releases/Elm.pm28
-rw-r--r--lib/VNWeb/Releases/Engines.pm4
-rw-r--r--lib/VNWeb/Releases/Lib.pm126
-rw-r--r--lib/VNWeb/Releases/List.pm37
-rw-r--r--lib/VNWeb/Releases/Page.pm197
-rw-r--r--lib/VNWeb/Releases/VNTab.pm37
8 files changed, 512 insertions, 176 deletions
diff --git a/lib/VNWeb/Releases/DRM.pm b/lib/VNWeb/Releases/DRM.pm
new file mode 100644
index 00000000..7ac7add3
--- /dev/null
+++ b/lib/VNWeb/Releases/DRM.pm
@@ -0,0 +1,120 @@
+package VNWeb::Releases::DRM;
+
+use VNWeb::Prelude;
+use TUWF 'uri_escape';
+
+TUWF::get '/r/drm', sub {
+ my $opt = tuwf->validate(get =>
+ n => { onerror => '' },
+ s => { onerror => '' },
+ t => { onerror => undef, enum => [0,1,2] },
+ u => { anybool => 1 },
+ )->data;
+ my $where = sql_and
+ $opt->{s} ? sql 'name ILIKE', \('%'.sql_like($opt->{s}).'%') : (),
+ defined $opt->{t} ? sql 'state =', \$opt->{t} : ();
+
+ my $lst = tuwf->dbAlli('
+ SELECT id, state, name, description, c_ref, ', sql_comma(keys %DRM_PROPERTY), '
+ FROM drm
+ WHERE', $where, $opt->{u} ? () : 'AND c_ref > 0',
+ 'ORDER BY c_ref DESC
+ ');
+ my $missing = $opt->{u} ? 0 : tuwf->dbVali('SELECT COUNT(*) FROM drm WHERE', $where, 'AND c_ref = 0');
+
+ framework_ title => 'List of DRM implementations', sub {
+ article_ sub {
+ h1_ 'List of DRM implementations';
+ form_ action => '/r/drm', method => 'get', sub {
+ fieldset_ class => 'search', sub {
+ input_ type => 'text', name => 's', id => 's', class => 'text', value => $opt->{s};
+ input_ type => 'submit', class => 'submit', value => 'Search!';
+ }
+ };
+ my sub opt_ {
+ my($k,$v,$lbl) = @_;
+ a_ href => '?'.query_encode(%$opt,$k=>$v), defined $opt->{$k} eq defined $v && (!defined $v || $opt->{$k} == $v) ? (class => 'optselected') : (), $lbl;
+ }
+ p_ class => 'browseopts', sub {
+ a_ href => '?'.query_encode(%$opt,t=>undef), !defined $opt->{t} ? (class => 'optselected') : (), 'All';
+ a_ href => '?'.query_encode(%$opt,t=>0), defined $opt->{t} && $opt->{t} == 0 ? (class => 'optselected') : (), 'New';
+ a_ href => '?'.query_encode(%$opt,t=>1), defined $opt->{t} && $opt->{t} == 1 ? (class => 'optselected') : (), 'Approved';
+ a_ href => '?'.query_encode(%$opt,t=>2), defined $opt->{t} && $opt->{t} == 2 ? (class => 'optselected') : (), 'Deleted';
+ };
+ my $unused = 0;
+ section_ class => 'drmlist', sub {
+ my $d = $_;
+ h2_ !$d->{c_ref} && !$unused++ ? (id => 'unused') : (), sub {
+ span_ class => 'strikethrough', $d->{name} if $d->{state} == 2;
+ txt_ $d->{name} if $d->{state} != 2;
+ a_ href => '/r?f='.tuwf->compile({advsearch => 'r'})->validate(['drm','=',$d->{name}])->data->query_encode, " ($d->{c_ref})";
+ b_ ' (new)' if $d->{state} == 0;
+ a_ href => "/r/drm/edit/$d->{id}?ref=".uri_escape(query_encode(%$opt)), ' edit' if auth->permDbmod;
+ };
+ my @prop = grep $d->{$_}, keys %DRM_PROPERTY;
+ p_ sub {
+ join_ ' ', sub {
+ abbr_ class => "icon-drm-$_", title => $DRM_PROPERTY{$_}, '';
+ txt_ $DRM_PROPERTY{$_};
+ }, @prop;
+ if (!@prop) {
+ abbr_ class => 'icon-drm-free', title => 'DRM-free', '';
+ txt_ 'DRM-free';
+ }
+ };
+ div_ sub { lit_ bb_format $d->{description} if $d->{description} };
+ } for @$lst;
+ p_ class => 'center', sub {
+ txt_ "$missing unused DRM type(s) not shown. ";
+ a_ href => '?'.query_encode(%$opt,u=>1).'#unused', 'Show all';
+ } if $missing;
+ };
+ };
+};
+
+
+my $FORM = form_compile any => {
+ id => { uint => 1 },
+ state => { uint => 1, range => [0,2] },
+ name => { sl => 1, maxlength => 128 },
+ description => { default => '', maxlength => 10240 },
+ ref => { default => '' },
+ map +($_,{anybool=>1}), keys %DRM_PROPERTY
+};
+
+
+sub info_ {
+ tuwf->dbRowi('
+ SELECT id, state, name, description,', sql_comma(keys %DRM_PROPERTY), '
+ FROM drm WHERE id =', \shift
+ );
+}
+
+TUWF::get qr{/r/drm/edit/(0|$RE{num})}, sub {
+ return tuwf->resDenied if !auth->permDbmod;
+ my $d = info_ tuwf->capture(1);
+ return tuwf->resNotFound if !defined $d->{id};
+ $d->{ref} = tuwf->reqGet('ref');
+ framework_ title => "Edit DRM: $d->{name}", sub {
+ div_ widget(DRMEdit => $FORM, $d), '';
+ };
+};
+
+js_api DRMEdit => $FORM, sub {
+ my $data = shift;
+ return tuwf->resDenied if !auth->permDbmod;
+ my $d = info_ delete $data->{id};
+ return tuwf->resNotFound if !defined $d->{id};
+ my $ref = delete $data->{ref};
+
+ return +{ _er => 'Duplicate DRM name' }
+ if tuwf->dbVali('SELECT 1 FROM drm WHERE id <>', \$d->{id}, 'AND name =', \$d->{name});
+
+ tuwf->dbExeci('UPDATE drm SET', $data, 'WHERE id =', \$d->{id});
+
+ my @diff = grep $d->{$_} ne $data->{$_}, qw/state name description/, keys %DRM_PROPERTY;
+ auth->audit(undef, 'drm edit', join '; ', map "$_: $d->{$_} -> $data->{$_}", @diff) if @diff;
+ +{ _redir => "/r/drm?$ref" };
+};
+
+1;
diff --git a/lib/VNWeb/Releases/Edit.pm b/lib/VNWeb/Releases/Edit.pm
index 1c44ffb3..b004b7e1 100644
--- a/lib/VNWeb/Releases/Edit.pm
+++ b/lib/VNWeb/Releases/Edit.pm
@@ -4,36 +4,56 @@ use VNWeb::Prelude;
my $FORM = {
- id => { required => 0, vndbid => 'r' },
- title => { maxlength => 300 },
- original => { required => 0, default => '', maxlength => 250 },
+ id => { default => undef, vndbid => 'r' },
official => { anybool => 1 },
patch => { anybool => 1 },
freeware => { anybool => 1 },
doujin => { anybool => 1 },
- lang => { minlength => 1, sort_keys => 'lang', aoh => {
+ has_ero => { anybool => 1 },
+ titles => { minlength => 1, sort_keys => 'lang', aoh => {
lang => { enum => \%LANGUAGE },
mtl => { anybool => 1 },
+ title => { default => undef, sl => 1, maxlength => 300 },
+ latin => { default => undef, sl => 1, maxlength => 300 },
} },
+ # Titles fetched from the VN entry, for auto-filling
+ vntitles => { _when => 'out', aoh => {
+ lang => {},
+ title => {},
+ latin => { default => undef },
+ } },
+ olang => { enum => \%LANGUAGE, default => 'ja' },
platforms => { aoh => { platform => { enum => \%PLATFORM } } },
media => { aoh => {
medium => { enum => \%MEDIUM },
qty => { uint => 1, range => [0,40] },
} },
+ drm => { sort_keys => 'name', aoh => {
+ name => { sl => 1, maxlength => 128 },
+ notes => { default => '' },
+ description => { default => '', maxlength => 10240 },
+ map +($_,{anybool=>1}), keys %DRM_PROPERTY
+ } },
gtin => { gtin => 1 },
- catalog => { required => 0, default => '', maxlength => 50 },
+ catalog => { default => '', sl => 1, maxlength => 50 },
released => { default => 99999999, min => 1, rdate => 1 },
- minage => { required => 0, default => undef, int => 1, enum => \%AGE_RATING },
+ minage => { default => undef, int => 1, enum => \%AGE_RATING },
uncensored => { undefbool => 1 },
reso_x => { uint => 1, range => [0,32767] },
reso_y => { uint => 1, range => [0,32767] },
voiced => { uint => 1, enum => \%VOICED },
ani_story => { uint => 1, enum => \%ANIMATED },
ani_ero => { uint => 1, enum => \%ANIMATED },
- website => { required => 0, default => '', weburl => 1 },
- engine => { required => 0, default => '', maxlength => 50 },
- extlinks => validate_extlinks('r'),
- notes => { required => 0, default => '', maxlength => 10240 },
+ ani_story_sp => { default => undef, uint => 1, range => [0,32767] },
+ ani_story_cg => { default => undef, uint => 1, range => [0,32767] },
+ ani_cutscene => { default => undef, uint => 1, range => [0,32767] },
+ ani_ero_sp => { default => undef, uint => 1, range => [0,32767] },
+ ani_ero_cg => { default => undef, uint => 1, range => [0,32767] },
+ ani_face => { undefbool => 1 },
+ ani_bg => { undefbool => 1 },
+ website => { default => '', weburl => 1 },
+ engine => { default => '', sl => 1, maxlength => 50 },
+ notes => { default => '', maxlength => 10240 },
vn => { sort_keys => 'vid', aoh => {
vid => { vndbid => 'v' },
title => { _when => 'out' },
@@ -47,17 +67,14 @@ my $FORM = {
} },
hidden => { anybool => 1 },
locked => { anybool => 1 },
-
- authmod => { _when => 'out', anybool => 1 },
editsum => { _when => 'in out', editsum => 1 },
+ validate_extlinks 'r'
};
my $FORM_OUT = form_compile out => $FORM;
my $FORM_IN = form_compile in => $FORM;
my $FORM_CMP = form_compile cmp => $FORM;
-sub to_extlinks { $_[0]{extlinks} = { map +($_, delete $_[0]{$_}), grep /^l_/, keys $_[0]->%* } }
-
TUWF::get qr{/$RE{rrev}/(?<action>edit|copy)} => sub {
my $e = db_entry tuwf->captures('id', 'rev') or return tuwf->resNotFound;
@@ -65,46 +82,47 @@ TUWF::get qr{/$RE{rrev}/(?<action>edit|copy)} => sub {
return tuwf->resDenied if !can_edit r => $copy ? {} : $e;
$e->{editsum} = $copy ? "Copied from $e->{id}.$e->{chrev}" : $e->{chrev} == $e->{maxrev} ? '' : "Reverted to revision $e->{id}.$e->{chrev}";
- $e->{authmod} = auth->permDbmod;
- to_extlinks $e;
+ $e->{titles} = [ sort { $a->{lang} cmp $b->{lang} } $e->{titles}->@* ];
- enrich_merge vid => 'SELECT id AS vid, title FROM vn WHERE id IN', $e->{vn};
- enrich_merge pid => 'SELECT id AS pid, name FROM producers WHERE id IN', $e->{producers};
+ $e->{vntitles} = $e->{vn}->@* == 1 ? tuwf->dbAlli('SELECT lang, title, latin FROM vn_titles WHERE id =', \$e->{vn}[0]{vid}) : [];
- $e->@{qw/gtin catalog extlinks/} = elm_empty($FORM_OUT)->@{qw/gtin catalog extlinks/} if $copy;
+ enrich_merge vid => sql('SELECT id AS vid, title[1+1] FROM', vnt, 'v WHERE id IN'), $e->{vn};
+ enrich_merge pid => sql('SELECT id AS pid, title[1+1] AS name FROM', producerst, 'p WHERE id IN'), $e->{producers};
+ enrich_merge drm => sql('SELECT id AS drm, name FROM drm WHERE id IN'), $e->{drm};
- my $title = ($copy ? 'Copy ' : 'Edit ').$e->{title};
+ my @empty_fields = ('gtin', 'catalog', grep /^l_/, keys %$e);
+ $e->@{@empty_fields} = elm_empty($FORM_OUT)->@{@empty_fields} if $copy;
+
+ my $title = ($copy ? 'Copy ' : 'Edit ').titleprefs_obj($e->{olang}, $e->{titles})->[1];
framework_ title => $title, dbobj => $e, tab => tuwf->capture('action'),
sub {
editmsg_ r => $e, $title, $copy;
- elm_ ReleaseEdit => $FORM_OUT, $copy ? {%$e, id=>undef} : $e;
+ div_ widget(ReleaseEdit => $FORM_OUT, $copy ? {%$e, id=>undef} : $e), '';
};
};
TUWF::get qr{/$RE{vid}/add}, sub {
return tuwf->resDenied if !can_edit r => undef;
- my $v = tuwf->dbRowi('SELECT id, title, original FROM vn WHERE id =', \tuwf->capture('id'));
+ my $v = tuwf->dbRowi('SELECT id, title FROM', vnt, 'v WHERE NOT hidden AND v.id =', \tuwf->capture('id'));
return tuwf->resNotFound if !$v->{id};
- my $delrel = tuwf->dbAlli('SELECT r.id, r.title, r.original FROM releases r JOIN releases_vn rv ON rv.id = r.id WHERE r.hidden AND rv.vid =', \$v->{id}, 'ORDER BY id');
- enrich_flatten languages => id => id => 'SELECT id, lang FROM releases_lang WHERE id IN', $delrel;
+ my $delrel = tuwf->dbAlli('SELECT r.id, r.title FROM', releasest, 'r JOIN releases_vn rv ON rv.id = r.id WHERE r.hidden AND rv.vid =', \$v->{id}, 'ORDER BY id');
+ enrich_flatten languages => id => id => 'SELECT id, lang FROM releases_titles WHERE id IN', $delrel;
my $e = {
elm_empty($FORM_OUT)->%*,
- title => $v->{title},
- original => $v->{original},
- vn => [{vid => $v->{id}, title => $v->{title}, rtype => 'complete'}],
+ vn => [{vid => $v->{id}, title => $v->{title}[1], rtype => 'complete'}],
+ vntitles => tuwf->dbAlli('SELECT lang, title, latin FROM vn_titles WHERE id =', \$v->{id}),
official => 1,
};
- $e->{authmod} = auth->permDbmod;
- framework_ title => "Add release to $v->{title}",
+ framework_ title => "Add release to $v->{title}[1]",
sub {
- editmsg_ r => undef, "Add release to $v->{title}";
+ editmsg_ r => undef, "Add release to $v->{title}[1]";
- div_ class => 'mainbox', sub {
+ article_ sub {
h1_ 'Deleted releases';
div_ class => 'warning', sub {
p_ q{This visual novel has releases that have been deleted
@@ -114,22 +132,22 @@ TUWF::get qr{/$RE{vid}/add}, sub {
ul_ sub {
li_ sub {
txt_ '['.join(',', $_->{languages}->@*)."] $_->{id}:";
- a_ href => "/$_->{id}", title => $_->{original}||$_->{title}, $_->{title};
+ a_ href => "/$_->{id}", tattr $_;
} for @$delrel;
}
}
} if @$delrel;
- elm_ ReleaseEdit => $FORM_OUT, $e;
+ div_ widget(ReleaseEdit => $FORM_OUT, $e), '';
};
};
-elm_api ReleaseEdit => $FORM_OUT, $FORM_IN, sub {
+js_api ReleaseEdit => $FORM_IN, sub {
my $data = shift;
my $new = !$data->{id};
my $e = $new ? { id => 0 } : db_entry $data->{id} or return tuwf->resNotFound;
- return elm_Unauth if !can_edit r => $e;
+ return tuwf->resDenied if !can_edit r => $e;
if(!auth->permDbmod) {
$data->{hidden} = $e->{hidden}||0;
@@ -139,29 +157,64 @@ elm_api ReleaseEdit => $FORM_OUT, $FORM_IN, sub {
if($data->{patch}) {
$data->{doujin} = $data->{voiced} = $data->{ani_story} = $data->{ani_ero} = 0;
$data->{reso_x} = $data->{reso_y} = 0;
+ $data->{ani_story_sp} = $data->{ani_story_cg} = $data->{ani_cutscene} = $data->{ani_ero_sp} = $data->{ani_ero_cg} = $data->{ani_face} = $data->{ani_bg} = undef;
$data->{engine} = '';
}
-
- if(!defined $data->{minage} || $data->{minage} != 18) {
+ if(!$data->{has_ero}) {
$data->{uncensored} = undef;
$data->{ani_ero} = 0;
+ $data->{ani_ero_sp} = $data->{ani_ero_cg} = undef;
}
+ ani_compat($data, $e);
+
+ die "No title in main language" if !length [grep $_->{lang} eq $data->{olang}, $data->{titles}->@*]->[0]{title};
$_->{qty} = $MEDIUM{$_->{medium}}{qty} ? $_->{qty}||1 : 0 for $data->{media}->@*;
$data->{notes} = bb_subst_links $data->{notes};
die "No VNs selected" if !$data->{vn}->@*;
die "Invalid resolution: ($data->{reso_x},$data->{reso_y})" if (!$data->{reso_x} && $data->{reso_y} > 1) || ($data->{reso_x} && !$data->{reso_y});
- to_extlinks $e;
-
- return elm_Unchanged if !$new && !form_changed $FORM_CMP, $data, $e;
+ # We need the DRM names for form_changed()
+ enrich_merge drm => sql('SELECT id AS drm, name FROM drm WHERE id IN'), $e->{drm};
+ # And the DRM identifiers to actually save the new form.
+ enrich_merge name => sql('SELECT name, id AS drm FROM drm WHERE name IN'), $data->{drm};
+ for my $d ($data->{drm}->@*) {
+ $d->{notes} = bb_subst_links $d->{notes};
+ $d->{drm} = tuwf->dbVali('INSERT INTO drm', {map +($_,$d->{$_}), 'name', 'description', keys %DRM_PROPERTY}, 'RETURNING id')
+ if !defined $d->{drm};
+ }
- $data->{$_} = $data->{extlinks}{$_} for $data->{extlinks}->%*;
- delete $data->{extlinks};
+ return 'No changes' if !$new && !form_changed $FORM_CMP, $data, $e;
my $ch = db_edit r => $e->{id}, $data;
- elm_Redirect "/$ch->{nitemid}.$ch->{nrev}";
+ +{ _redir => "/$ch->{nitemid}.$ch->{nrev}" };
};
+# Set the old ani_story and ani_ero fields to some sort of value based on the
+# new ani_* fields, if they've been changed.
+sub ani_compat {
+ my($r, $old) = @_;
+ return if !grep +($r->{$_}//'_undef_') ne ($old->{$_}//'_undef_'),
+ qw{ ani_story_sp ani_story_cg ani_cutscene ani_ero_sp ani_ero_cg ani_face ani_bg };
+
+ my sub known($) { defined $r->{"ani_$_[0]"} }
+ my sub hasani($) { $r->{"ani_$_[0]"} && $r->{"ani_$_[0]"} > 1 }
+ my sub someani($) { hasani $_[0] && ($r->{"ani_$_[0]"} & 512) == 0 }
+ my sub fullani($) { defined $r->{"ani_$_[0]"} && ($r->{"ani_$_[0]"} & 512) > 0 }
+
+ $r->{ani_story} =
+ !known 'story_sp' && !known 'story_cg' && !known 'cutscene' ? 0 :
+ !hasani 'story_sp' && !hasani 'story_cg' && !hasani 'cutscene' ? 1 :
+ (fullani 'story_sp' || fullani 'story_cg') && !(someani 'story_sp' || someani 'story_cg') ? 4 : 3;
+
+ $r->{ani_ero} =
+ !known 'ero_sp' && !known 'ero_cg' ? 0 :
+ !hasani 'ero_sp' && !hasani 'ero_cg' ? 1 :
+ (fullani 'ero_sp' || fullani 'ero_cg') && !(someani 'ero_sp' || someani 'ero_cg') ? 4 : 3;
+
+ $r->{ani_story} = 2 if $r->{ani_story} < 2 && ($r->{ani_face} || $r->{ani_bg});
+}
+
+
1;
diff --git a/lib/VNWeb/Releases/Elm.pm b/lib/VNWeb/Releases/Elm.pm
index 09c9ede2..4abe0b12 100644
--- a/lib/VNWeb/Releases/Elm.pm
+++ b/lib/VNWeb/Releases/Elm.pm
@@ -26,4 +26,32 @@ elm_api Engines => undef, {}, sub {
});
};
+
+elm_api DRM => undef, {}, sub {
+ elm_DRM tuwf->dbAlli(q{
+ SELECT name, c_ref AS count FROM drm WHERE c_ref > 0 ORDER BY state = 1+1, c_ref DESC, name
+ });
+};
+
+
+js_api Resolutions => {}, sub {
+ +{ results => [ map +{ id => resolution($_), count => $_->{count} }, tuwf->dbAlli(q{
+ SELECT reso_x, reso_y, count(*) AS count FROM releases WHERE NOT hidden AND NOT (reso_x = 0 AND reso_y = 0)
+ GROUP BY reso_x, reso_y ORDER BY count(*) DESC
+ })->@* ] };
+};
+
+
+js_api Engines => {}, sub {
+ +{ results => tuwf->dbAlli(q{
+ SELECT engine AS id, count(*) AS count FROM releases WHERE NOT hidden AND engine <> ''
+ GROUP BY engine ORDER BY count(*) DESC, engine
+ }) };
+};
+
+
+js_api DRM => {}, sub {
+ +{ results => tuwf->dbAlli('SELECT name AS id, c_ref AS count, state FROM drm ORDER BY state = 1+1, c_ref DESC, name') };
+};
+
1;
diff --git a/lib/VNWeb/Releases/Engines.pm b/lib/VNWeb/Releases/Engines.pm
index c6e142e2..f5e7e812 100644
--- a/lib/VNWeb/Releases/Engines.pm
+++ b/lib/VNWeb/Releases/Engines.pm
@@ -14,7 +14,7 @@ TUWF::get qr{/r/engines}, sub {
);
framework_ title => 'Engine list', sub {
- div_ class => 'mainbox', sub {
+ article_ sub {
h1_ 'Engine list';
p_ sub {
lit_ q{
@@ -25,7 +25,7 @@ TUWF::get qr{/r/engines}, sub {
};
};
};
- div_ class => 'mainbox browse', sub {
+ article_ class => 'browse', sub {
table_ class => 'stripe', sub {
my $c = tuwf->compile({advsearch => 'r'});
tr_ sub {
diff --git a/lib/VNWeb/Releases/Lib.pm b/lib/VNWeb/Releases/Lib.pm
index 0da2f2a9..6688dedb 100644
--- a/lib/VNWeb/Releases/Lib.pm
+++ b/lib/VNWeb/Releases/Lib.pm
@@ -3,21 +3,21 @@ package VNWeb::Releases::Lib;
use VNWeb::Prelude;
use Exporter 'import';
-our @EXPORT = qw/enrich_release_elm releases_by_vn enrich_release release_row_/;
+our @EXPORT = qw/enrich_release_elm releases_by_vn enrich_release sort_releases release_row_/;
# Enrich a list of releases so that it's suitable as 'Releases' Elm response.
# Given objects must have 'id' and 'rtype' fields (appropriate for the VN in context).
sub enrich_release_elm {
- enrich_merge id => 'SELECT id, title, original, released, reso_x, reso_y FROM releases WHERE id IN', @_;
- enrich_flatten lang => id => id => sub { sql('SELECT id, lang FROM releases_lang WHERE id IN', $_, 'ORDER BY lang') }, @_;
+ enrich_merge id => sql('SELECT id, title[1+1] AS title, title[1+1+1+1] AS alttitle, released, reso_x, reso_y FROM', releasest, 'r WHERE id IN'), @_;
+ enrich_flatten lang => id => id => sub { sql('SELECT id, lang FROM releases_titles WHERE id IN', $_, 'ORDER BY lang') }, @_;
enrich_flatten platforms => id => id => sub { sql('SELECT id, platform FROM releases_platforms WHERE id IN', $_, 'ORDER BY platform') }, @_;
}
# Return the list of releases associated with a VN in the format suitable as 'Releases' Elm response.
sub releases_by_vn {
my($id) = @_;
- my $l = tuwf->dbAlli('SELECT r.id, rv.rtype FROM releases r JOIN releases_vn rv ON rv.id = r.id WHERE NOT r.hidden AND rv.vid =', \$id, 'ORDER BY r.released, r.title, r.id');
+ my $l = tuwf->dbAlli('SELECT r.id, rv.rtype FROM', releasest, 'r JOIN releases_vn rv ON rv.id = r.id WHERE NOT r.hidden AND rv.vid =', \$id, 'ORDER BY r.released, r.sorttitle, r.id');
enrich_release_elm $l;
$l
}
@@ -27,12 +27,31 @@ sub releases_by_vn {
# Assumption: Each release already has id, patch, released, gtin and enrich_extlinks().
sub enrich_release {
my($r) = @_;
- enrich_merge id => 'SELECT id, title, original, notes, minage, official, freeware, doujin, reso_x, reso_y, voiced, ani_story, ani_ero, uncensored FROM releases WHERE id IN', $r;
+ enrich_merge id => sql(
+ 'SELECT id, title, olang, notes, minage, official, freeware, has_ero, reso_x, reso_y, voiced, uncensored
+ , ani_story, ani_ero, ani_story_sp, ani_story_cg, ani_cutscene, ani_ero_sp, ani_ero_cg, ani_face, ani_bg
+ FROM', releasest, 'r WHERE id IN'), $r;
enrich_merge id => sub { sql 'SELECT id, MAX(rtype) AS rtype FROM releases_vn WHERE id IN', $_, 'GROUP BY id' }, grep !$_->{rtype}, ref $r ? @$r : $r;
enrich_merge id => sql('SELECT rid as id, status as rlist_status FROM rlists WHERE uid =', \auth->uid, 'AND rid IN'), $r if auth;
enrich_flatten platforms => id => id => sub { sql 'SELECT id, platform FROM releases_platforms WHERE id IN', $_, 'ORDER BY id, platform' }, $r;
- enrich lang => id => id => sub { 'SELECT id, lang, mtl FROM releases_lang WHERE id IN', $_, 'ORDER BY id, mtl, lang' }, $r;
+ enrich titles => id => id => sub { 'SELECT id, lang, mtl, title, latin FROM releases_titles WHERE id IN', $_, 'ORDER BY id, mtl, lang' }, $r;
enrich media => id => id => sub { 'SELECT id, medium, qty FROM releases_media WHERE id IN', $_, 'ORDER BY id, medium' }, $r;
+ enrich drm => id => id => sub { 'SELECT r.id, r.drm, r.notes, d.name,', sql_comma(keys %DRM_PROPERTY), 'FROM releases_drm r JOIN drm d ON d.id = r.drm WHERE r.id IN', $_, 'ORDER BY r.id, r.drm' }, $r;
+}
+
+
+# Sort an array of releases, assumes the objects come from enrich_release()
+# (Not always possible with an SQL ORDER BY due to rtype being context-dependent and platforms coming from other tables)
+sub sort_releases {
+ return [ sort {
+ $a->{released} <=> $b->{released} ||
+ $b->{rtype} cmp $a->{rtype} ||
+ $b->{official} cmp $a->{official} ||
+ $a->{patch} cmp $b->{patch} ||
+ ($a->{platforms}[0]||'') cmp ($b->{platforms}[0]||'') ||
+ $a->{title}[1] cmp $b->{title}[1] ||
+ idcmp($a->{id}, $b->{id})
+ } $_[0]->@* ];
}
@@ -41,8 +60,8 @@ sub release_extlinks_ {
return if !$r->{extlinks}->@*;
if($r->{extlinks}->@* == 1 && $r->{website}) {
- a_ href => $r->{extlinks}[0][1], sub {
- abbr_ class => 'icons external', title => 'Official website', '';
+ a_ href => $r->{extlinks}[0]{url2}, sub {
+ abbr_ class => 'icon-external', title => 'Official website', '';
};
return
}
@@ -51,15 +70,15 @@ sub release_extlinks_ {
div_ class => 'elm_dd', sub {
a_ href => $r->{website}||'#', sub {
txt_ scalar $r->{extlinks}->@*;
- abbr_ class => 'icons external', title => 'External link', '';
+ abbr_ class => 'icon-external', title => 'External link', '';
};
div_ sub {
div_ sub {
ul_ sub {
li_ sub {
- a_ href => $_->[1], sub {
- span_ $_->[2] if length $_->[2];
- txt_ $_->[0];
+ a_ href => $_->{url2}, sub {
+ span_ $_->{price} if length $_->{price};
+ txt_ $_->{label};
}
} for $r->{extlinks}->@*;
}
@@ -72,61 +91,96 @@ sub release_extlinks_ {
# Options
# id: unique identifier if the same release may be listed on a page twice.
-# lang: $lang, whether to display language icons and which language to use for the MTL flag.
+# lang: $lang, whether to display language icons and which language to use for the title and MTL flag.
# prod: 0/1 whether to display Pub/Dev indication
sub release_row_ {
my($r, $opt) = @_;
- my $mtl = $opt->{lang}
- ? [grep $_->{lang} eq $opt->{lang}, $r->{lang}->@*]->[0]{mtl}
- : (grep $_->{mtl}, $r->{lang}->@*) == $r->{lang}->@*;
+ my $lang = $opt->{lang} && (grep $_->{lang} eq $opt->{lang}, $r->{titles}->@*)[0];
+ my $mtl = $lang ? $lang->{mtl} : (grep $_->{mtl}, $r->{titles}->@*) == $r->{titles}->@*;
+
+ my $storyani = join "\n", map "$_.",
+ $r->{ani_story} == 1 ? 'Not animated' :
+ defined $r->{ani_story_sp} || defined $r->{ani_story_cg} || defined $r->{ani_cutscene} || defined $r->{ani_bg} || defined $r->{ani_face} ? (
+ defined $r->{ani_story_sp} ? fmtanimation $r->{ani_story_sp}, 'sprites' : (),
+ defined $r->{ani_story_cg} ? fmtanimation $r->{ani_story_cg}, 'CGs' : (),
+ defined $r->{ani_cutscene} ? fmtanimation $r->{ani_cutscene}, 'cutscenes' : (),
+ defined $r->{ani_bg} ? ($r->{ani_bg} ? 'Animated background effects' : 'No background effects') : (),
+ defined $r->{ani_face} ? ($r->{ani_face} ? 'Lip and/or eye movement' : 'No facial animations') : (),
+ ) : $ANIMATED{$r->{ani_story}}{txt};
+
+ my $eroani = join "\n", map "$_.",
+ $r->{ani_ero} == 1 ? 'Not animated' :
+ defined $r->{ani_ero_sp} || defined $r->{ani_ero_cg} ? (
+ defined $r->{ani_ero_sp} ? fmtanimation $r->{ani_ero_sp}, 'sprites' : (),
+ defined $r->{ani_ero_cg} ? fmtanimation $r->{ani_ero_cg}, 'CGs' : (),
+ ) : $ANIMATED{$r->{ani_ero}}{txt};
my sub icon_ {
my($img, $label, $class) = @_;
- $class = $class ? " release_icon_$class" : '';
- img_ src => config->{url_static}."/f/$img.svg", class => "release_icons$class", title => $label;
+ $class = $class ? " icon-rel-$class" : '';
+ abbr_ class => "icon-rel-$img$class", title => $label, '';
}
my sub icons_ {
my($r) = @_;
- icon_ 'voiced', $VOICED{$r->{voiced}}{txt}, "voiced$r->{voiced}" if $r->{voiced};
- icon_ 'story_animated', "Story: $ANIMATED{$r->{ani_story}}{txt}", "anim$r->{ani_story}" if $r->{ani_story};
- icon_ 'ero_animated', "Ero: $ANIMATED{$r->{ani_ero}}{txt}", "anim$r->{ani_ero}" if $r->{ani_ero};
- icon_ 'free', 'Freeware' if $r->{freeware};
- icon_ 'nonfree', 'Non-free' if !$r->{freeware};
+ icon_ 'notes', bb_format $r->{notes}, text => 1 if $r->{notes};
+ icon_ $MEDIUM{ $r->{media}[0]{medium} }{icon}, join ', ', map fmtmedia($_->{medium}, $_->{qty}), $r->{media}->@* if $r->{media}->@*;
if($r->{reso_y}) {
my $ratio = $r->{reso_x} / $r->{reso_y};
- my $type = $ratio == 4/3 ? '4-3' : $ratio == 16/9 ? '16-9' : 'custom';
+ my $type = $ratio == 4/3 ? '43' : $ratio == 16/9 ? '169' : 'custom';
# Ugly workaround: PC-98 has non-square pixels, thus not widescreen
- $type = '4-3' if $ratio > 4/3 && grep $_ eq 'p98', $r->{platforms}->@*;
- icon_ "resolution_$type", resolution $r;
+ $type = '43' if $ratio > 4/3 && grep $_ eq 'p98', $r->{platforms}->@*;
+ icon_ "reso-$type", resolution $r;
}
- icon_ $MEDIUM{ $r->{media}[0]{medium} }{icon}, join ', ', map fmtmedia($_->{medium}, $_->{qty}), $r->{media}->@* if $r->{media}->@*;
- icon_ 'uncensor', 'Uncensored' if $r->{uncensored};
- icon_ 'notes', bb_format $r->{notes}, text => 1 if $r->{notes};
+ icon_ 'free', 'Freeware' if $r->{freeware};
+ icon_ 'nonfree', 'Non-free' if !$r->{freeware};
+ icon_ 'ani-ero', "Erotic scene animation:\n$eroani", "a$r->{ani_ero}" if $r->{ani_ero};
+ icon_ 'ani-story', "Story scene animation:\n$storyani", "a$r->{ani_story}" if $r->{ani_story};
+ icon_ 'voiced', $VOICED{$r->{voiced}}{txt}, "v$r->{voiced}" if $r->{voiced};
}
tr_ $mtl ? (class => 'mtl') : (), sub {
- td_ class => 'tc1', sub { rdate_ [grep $_->{lang} eq $opt->{lang}, $opt->{lang}?$r->{lang}->@*:()]->[0]{released}//$r->{released} };
- td_ class => 'tc2', defined $r->{minage} ? minage $r->{minage} : '';
+ td_ class => 'tc1', sub { rdate_ $r->{released} };
+ td_ class => 'tc2', sub {
+ span_ class => 'releaseero releaseero_'.(!$r->{has_ero} ? 'no' : $r->{uncensored} ? 'unc' : defined $r->{uncensored} ? 'cen' : 'yes'),
+ title => !$r->{has_ero} ? 'No erotic scenes' :
+ $r->{uncensored} ? 'Contains uncensored erotic scenes'
+ : defined $r->{uncensored} ? 'Contains erotic scenes with optical censoring' : 'Contains erotic scenes', '♥';
+ txt_ !$r->{minage} ? 'All' : minage $r->{minage} if defined $r->{minage};
+ };
td_ class => 'tc3', sub {
platform_ $_ for $r->{platforms}->@*;
if(!$opt->{lang}) {
- abbr_ class => "icons lang $_->{lang}".($_->{mtl}?' mtl':''), title => $LANGUAGE{$_->{lang}}, '' for $r->{lang}->@*;
+ abbr_ class => "icon-lang-$_->{lang}".($_->{mtl}?' mtl':''), title => $LANGUAGE{$_->{lang}}{txt}, '' for $r->{titles}->@*;
}
- abbr_ class => "icons rt$r->{rtype}", title => $r->{rtype}, '';
+ abbr_ class => "icon-rt$r->{rtype}", title => $r->{rtype}, '';
};
td_ class => 'tc4', sub {
- a_ href => "/$r->{id}", title => $r->{original}||$r->{title}, $r->{title};
+ my $title =
+ $lang && defined $lang->{title} ? titleprefs_obj $lang->{lang}, [$lang] :
+ $lang ? titleprefs_obj $r->{olang}, [grep $_->{lang} eq $r->{olang}, $r->{titles}->@*]
+ : $r->{title};
+ a_ href => "/$r->{id}", tattr $title;
my $note = join ' ', $r->{official} ? () : 'unofficial', $mtl ? 'machine translation' : (), $r->{patch} ? 'patch' : ();
- b_ class => 'grayedout', " ($note)" if $note;
+ small_ " ($note)" if $note;
+ if ($r->{drm}->@*) {
+ my($free,$drm);
+ for my $d ($r->{drm}->@*) {
+ ${ (grep $d->{$_}, keys %DRM_PROPERTY)[0] ? \$drm : \$free } = 1
+ }
+ my $nfo = join "\n", map $_->{name}.($_->{notes} ? ' ('.bb_format($_->{notes}, text => 1).')' : ''), $r->{drm}->@*;
+ ($free && $drm ? \&span_ : $drm ? \&b_ : \&small_)->(title => $nfo, $free && !$drm ? ' (drm-free)' : ' (drm)');
+ }
};
td_ class => 'tc_icons', sub { icons_ $r };
td_ class => 'tc_prod', join ' & ', $r->{publisher} ? 'Pub' : (), $r->{developer} ? 'Dev' : () if $opt->{prod};
td_ class => 'tc5 elm_dd_left', sub {
elm_ 'UList.ReleaseEdit', $VNWeb::ULists::Elm::RLIST_STATUS, { rid => $r->{id}, uid => auth->uid, status => $r->{rlist_status}, empty => '--' } if auth;
};
- td_ class => 'tc6', sub { release_extlinks_ $r, "$opt->{id}_$r->{id}" };
+ td_ class => 'tc6', sub {
+ release_extlinks_ $r, "$opt->{id}_$r->{id}" if $r->{patch} || $r->{official} || !grep $_->{mtl}, $r->{titles}->@*;
+ };
}
}
diff --git a/lib/VNWeb/Releases/List.pm b/lib/VNWeb/Releases/List.pm
index a55a7a88..2e8db610 100644
--- a/lib/VNWeb/Releases/List.pm
+++ b/lib/VNWeb/Releases/List.pm
@@ -11,7 +11,7 @@ sub listing_ {
my($opt, $list, $count) = @_;
my sub url { '?'.query_encode %$opt, @_ }
paginate_ \&url, $opt->{p}, [$count, 50], 't';
- div_ class => 'mainbox browse', sub {
+ article_ class => 'browse', sub {
table_ class => 'stripe releases', sub {
thead_ sub { tr_ sub {
td_ class => 'tc1', sub { txt_ 'Date'; sortable_ 'released',$opt, \&url; debug_ $list; };
@@ -32,13 +32,15 @@ sub listing_ {
TUWF::get qr{/r}, sub {
my $opt = tuwf->validate(get =>
- q => { onerror => undef },
+ q => { searchquery => 1 },
p => { upage => 1 },
f => { advsearch_err => 'r' },
- s => { onerror => 'title', enum => [qw/released minage title/] },
+ s => { onerror => 'qscore', enum => [qw/qscore released minage title/] },
o => { onerror => 'a', enum => ['a','d'] },
- fil => { required => 0 },
+ fil => { onerror => '' },
)->data;
+ $opt->{s} = 'qscore' if $opt->{q} && tuwf->reqGet('sb');
+ $opt->{s} = 'title' if $opt->{s} eq 'qscore' && !$opt->{q};
# URL compatibility with old filters
if(!$opt->{f}->{query} && $opt->{fil}) {
@@ -50,41 +52,40 @@ TUWF::get qr{/r}, sub {
$opt->{f} = advsearch_default 'r' if !$opt->{f}{query} && !defined tuwf->reqGet('f');
- my $where = sql_and 'NOT r.hidden', $opt->{f}->sql_where(),
- !$opt->{q} ? () : sql_or
- sql('r.c_search LIKE ALL (search_query(', \$opt->{q}, '))'),
- $opt->{q} =~ /^\d+$/ && gtintype($opt->{q}) ? sql 'r.gtin =', \$opt->{q} : (),
- $opt->{q} =~ /^[a-zA-Z0-9-]+$/ ? sql 'r.catalog =', \$opt->{q} : ();
+ my $where = sql_and
+ 'NOT r.hidden',
+ 'r.official OR EXISTS(SELECT 1 FROM releases_titles rt WHERE rt.id = r.id AND NOT rt.mtl)',
+ $opt->{f}->sql_where();
my $time = time;
my($count, $list);
db_maytimeout {
- $count = tuwf->dbVali('SELECT count(*) FROM releases r WHERE', $where);
+ $count = tuwf->dbVali('SELECT count(*) FROM releases r WHERE', sql_and $where, $opt->{q}->sql_where('r', 'r.id'));
$list = $count ? tuwf->dbPagei({results => 50, page => $opt->{p}}, '
SELECT r.id, r.patch, r.released, r.gtin, ', sql_extlinks(r => 'r.'), '
- FROM releases r
+ FROM', releasest, 'r', $opt->{q}->sql_join('r', 'r.id'), '
WHERE', $where, '
ORDER BY', sprintf {
- title => 'r.title %s, r.released %1$s',
- minage => 'r.minage %s, r.title %1$s, r.released %1$s',
- released => 'r.released %s, r.id %1$s',
+ qscore => '10 - sc.score %s, r.sorttitle %1$s',
+ title => 'r.sorttitle %s, r.released %1$s',
+ minage => 'r.minage %s, r.sorttitle %1$s, r.released %1$s',
+ released => 'r.released %s, r.sorttitle %1$s, r.id %1$s',
}->{$opt->{s}}, $opt->{o} eq 'a' ? 'ASC' : 'DESC'
) : [];
} || (($count, $list) = (undef, []));
- enrich_extlinks r => $list;
+ enrich_extlinks r => 0, $list;
enrich_release $list;
$time = time - $time;
framework_ title => 'Browse releases', sub {
- div_ class => 'mainbox', sub {
+ article_ sub {
h1_ 'Browse releases';
form_ action => '/r', method => 'get', sub {
searchbox_ r => $opt->{q}//'';
input_ type => 'hidden', name => 'o', value => $opt->{o};
input_ type => 'hidden', name => 's', value => $opt->{s};
- $opt->{f}->elm_;
- advsearch_msg_ $count, $time;
+ $opt->{f}->elm_($count, $time);
};
};
listing_ $opt, $list, $count if $count;
diff --git a/lib/VNWeb/Releases/Page.pm b/lib/VNWeb/Releases/Page.pm
index 5078376a..47bd6b63 100644
--- a/lib/VNWeb/Releases/Page.pm
+++ b/lib/VNWeb/Releases/Page.pm
@@ -1,19 +1,23 @@
package VNWeb::Releases::Page;
use VNWeb::Prelude;
+use TUWF 'uri_escape';
+use VNWeb::Releases::Lib;
sub enrich_item {
my($r) = @_;
- enrich_merge pid => 'SELECT id AS pid, name, original FROM producers WHERE id IN', $r->{producers};
- enrich_merge vid => 'SELECT id AS vid, title, original FROM vn WHERE id IN', $r->{vn};
+ enrich_merge pid => sql('SELECT id AS pid, title, sorttitle FROM', producerst, 'p WHERE id IN'), $r->{producers};
+ enrich_merge vid => sql('SELECT id AS vid, title, sorttitle FROM', vnt, 'v WHERE id IN'), $r->{vn};
+ enrich_merge drm => sql('SELECT id AS drm, name,', sql_join(',', keys %DRM_PROPERTY), 'FROM drm WHERE id IN'), $r->{drm};
- $r->{lang} = [ sort { ($a->{mtl}?1:0) <=> ($b->{mtl}?1:0) || $a->{lang} cmp $b->{lang} } $r->{lang}->@* ];
+ $r->{titles} = [ sort { ($b->{lang} eq $r->{olang}) cmp ($a->{lang} eq $r->{olang}) || ($a->{mtl}?1:0) <=> ($b->{mtl}?1:0) || $a->{lang} cmp $b->{lang} } $r->{titles}->@* ];
$r->{platforms} = [ sort map $_->{platform}, $r->{platforms}->@* ];
- $r->{vn} = [ sort { $a->{title} cmp $b->{title} || idcmp($a->{vid}, $b->{vid}) } $r->{vn}->@* ];
- $r->{producers} = [ sort { $a->{name} cmp $b->{name} || idcmp($a->{pid}, $b->{pid}) } $r->{producers}->@* ];
+ $r->{vn} = [ sort { $a->{sorttitle} cmp $b->{sorttitle} || idcmp($a->{vid}, $b->{vid}) } $r->{vn}->@* ];
+ $r->{producers} = [ sort { $a->{sorttitle} cmp $b->{sorttitle} || idcmp($a->{pid}, $b->{pid}) } $r->{producers}->@* ];
$r->{media} = [ sort { $a->{medium} cmp $b->{medium} || $a->{qty} <=> $b->{qty} } $r->{media}->@* ];
+ $r->{drm} = [ sort { !$a->{drm} || !$b->{drm} ? $b->{drm} <=> $a->{drm} : $a->{name} cmp $b->{name} } $r->{drm}->@* ];
$r->{resolution} = resolution $r;
}
@@ -21,22 +25,28 @@ sub enrich_item {
sub _rev_ {
my($r) = @_;
+ # The old ani_* fields are automatically inferred from the new ani_* fields
+ # for edits made after the fields were introduced. Hide the old fields for
+ # such revisions to remove some clutter.
+ my $newani = $r->{chid} > 1110896;
revision_ $r, \&enrich_item,
[ vn => 'Relations', fmt => sub {
- abbr_ class => "icons rt$_->{rtype}", title => $_->{rtype}, ' ';
- a_ href => "/$_->{vid}", title => $_->{original}||$_->{title}, $_->{title};
+ abbr_ class => "icon-rt$_->{rtype}", title => $_->{rtype}, ' ';
+ a_ href => "/$_->{vid}", tattr $_;
txt_ " ($_->{rtype})" if $_->{rtype} ne 'complete';
} ],
[ official => 'Official', fmt => 'bool' ],
[ patch => 'Patch', fmt => 'bool' ],
[ freeware => 'Freeware', fmt => 'bool' ],
+ [ has_ero => 'Has ero', fmt => 'bool' ],
[ doujin => 'Doujin', fmt => 'bool' ],
[ uncensored => 'Uncensored', fmt => 'bool' ],
- [ title => 'Title (Romaji)' ],
- [ original => 'Original title' ],
- [ gtin => 'JAN/EAN/UPC', empty => 0 ],
+ [ gtin => 'JAN/EAN/UPC/ISBN',empty => 0 ],
[ catalog => 'Catalog number' ],
- [ lang => 'Languages', fmt => sub { txt_ $LANGUAGE{$_->{lang}}; txt_ ' (machine translation)' if $_->{mtl} } ],
+ [ titles => 'Languages', txt => sub {
+ '['.$_->{lang}.($_->{mtl} ? ' machine translation' : '').'] '.($_->{title}//'').(length $_->{latin} ? " / $_->{latin}" : '')
+ }],
+ [ olang => 'Main title', fmt => \%LANGUAGE ],
[ released => 'Release date', fmt => sub { rdate_ $_ } ],
[ minage => 'Age rating', fmt => sub { txt_ minage $_ } ],
[ notes => 'Notes' ],
@@ -44,19 +54,82 @@ sub _rev_ {
[ media => 'Media', fmt => sub { txt_ fmtmedia $_->{medium}, $_->{qty}; } ],
[ resolution => 'Resolution' ],
[ voiced => 'Voiced', fmt => \%VOICED ],
- [ ani_story => 'Story animation', fmt => \%ANIMATED ],
- [ ani_ero => 'Ero animation', fmt => \%ANIMATED ],
+ $newani ? () :
+ [ ani_story => 'Story animation', fmt => \%ANIMATED ],
+ [ ani_story_sp => 'Story animation/sprites',fmt => sub { txt_ fmtanimation $_, 'sprites' } ],
+ [ ani_story_cg => 'Story animation/cg', fmt => sub { txt_ fmtanimation $_, 'CGs' } ],
+ [ ani_cutscene => 'Cutscene animation', fmt => sub { txt_ fmtanimation $_, 'cutscenes' } ],
+ $newani ? () :
+ [ ani_ero => 'Ero animation', fmt => \%ANIMATED ],
+ [ ani_ero_sp => 'Ero animation/sprites',fmt=> sub { txt_ fmtanimation $_, 'sprites' } ],
+ [ ani_ero_cg => 'Ero animation/cg', fmt => sub { txt_ fmtanimation $_, 'CGs' } ],
+ [ ani_face => 'Lip/eye animation', fmt => 'bool' ],
+ [ ani_bg => 'Background effects', fmt => 'bool' ],
[ engine => 'Engine' ],
[ producers => 'Producers', fmt => sub {
- a_ href => "/$_->{pid}", title => $_->{original}||$_->{name}, $_->{name};
+ a_ href => "/$_->{pid}", tattr $_;
txt_ ' (';
txt_ join ', ', $_->{developer} ? 'developer' : (), $_->{publisher} ? 'publisher' : ();
txt_ ')';
} ],
+ [ drm => 'DRM', fmt => sub {
+ a_ href => '/r/drm?s='.uri_escape($_->{name}), $_->{name};
+ txt_ " ($_->{notes})" if length $_->{notes};
+ } ],
revision_extlinks 'r'
}
+sub _infotable_animation_ {
+ my($r) = @_;
+ state @fields = qw|ani_story_sp ani_story_cg ani_cutscene ani_ero_sp ani_ero_cg ani_bg ani_face|;
+
+ return if !$r->{ani_story} && !$r->{ani_ero};
+
+ my sub txtc {
+ my($bool, $txt) = @_;
+ +(sub { $bool ? txt_ $txt : small_ $txt })
+ }
+
+ my sub sect {
+ my($val, $lbl) = @_;
+ defined $val ? txtc $val > 2, fmtanimation $val, $lbl : ();
+ }
+
+ my @story = !$r->{ani_story} ? () :
+ defined $r->{ani_story_sp} || defined $r->{ani_story_cg} || defined $r->{ani_cutscene} || defined $r->{ani_bg} || defined $r->{ani_face} ? (
+ defined $r->{ani_story_sp} ? sect $r->{ani_story_sp}, 'sprites' : (),
+ defined $r->{ani_story_cg} ? sect $r->{ani_story_cg}, 'CGs' : (),
+ defined $r->{ani_cutscene} ? sect $r->{ani_cutscene}, 'cutscenes' : (),
+ ) : txtc $r->{ani_story} > 1, $ANIMATED{$r->{ani_story}}{txt};
+
+ my @ero = !$r->{ani_ero} ? () :
+ defined $r->{ani_ero_sp} || defined $r->{ani_ero_cg} ? (
+ defined $r->{ani_ero_sp} ? sect $r->{ani_ero_sp}, 'sprites' : (),
+ defined $r->{ani_ero_cg} ? sect $r->{ani_ero_cg}, 'CGs' : (),
+ ) : txtc $r->{ani_ero} > 1, $ANIMATED{$r->{ani_ero}}{txt};
+
+ tr_ sub {
+ td_ 'Animation';
+ td_ sub {
+ dl_ sub {
+ if(@story) {
+ dt_ 'Story scenes';
+ dd_ sub { join_ \&br_, sub { $_->() }, @story };
+ }
+ if(@ero) {
+ dt_ 'Erotic scenes';
+ dd_ sub { join_ \&br_, sub { $_->() }, @ero };
+ }
+ } if @story || @ero;
+ join_ \&br_, sub { $_->() },
+ defined $r->{ani_bg} ? (txtc $r->{ani_bg}, $r->{ani_bg} ? 'Animated background effects' : 'No background effects') : (),
+ defined $r->{ani_face} ? (txtc $r->{ani_face}, $r->{ani_face} ? 'Lip and/or eye movement' : 'No facial animations') : ();
+ };
+ };
+}
+
+
sub _infotable_ {
my($r) = @_;
@@ -65,46 +138,44 @@ sub _infotable_ {
td_ class => 'key', 'Relation';
td_ sub {
join_ \&br_, sub {
- abbr_ class => "icons rt$_->{rtype}", title => $_->{rtype}, ' ';
- a_ href => "/$_->{vid}", title => $_->{original}||$_->{title}, $_->{title};
+ abbr_ class => "icon-rt$_->{rtype}", title => $_->{rtype}, ' ';
+ a_ href => "/$_->{vid}", tattr $_;
txt_ " ($_->{rtype})" if $_->{rtype} ne 'complete';
}, $r->{vn}->@*
}
};
- tr_ sub {
- td_ 'Title';
- td_ $r->{title};
+ tr_ class => 'titles', sub {
+ td_ $r->{titles}->@* == 1 ? 'Title' : 'Titles';
+ td_ sub {
+ table_ sub {
+ my($olang) = grep $_->{lang} eq $r->{olang}, $r->{titles}->@*;
+ tr_ class => 'nostripe title', sub {
+ td_ style => 'white-space: nowrap', sub {
+ abbr_ class => "icon-lang-$_->{lang}", title => $LANGUAGE{$_->{lang}}{txt}, '';
+ };
+ td_ sub {
+ my $title = $_->{title}//$olang->{title};
+ span_ tlang($_->{lang}, $title), $title;
+ small_ ' (machine translation)' if $_->{mtl};
+ my $latin = defined $_->{title} ? $_->{latin} : $olang->{latin};
+ if(defined $latin) {
+ br_;
+ txt_ $latin;
+ }
+ }
+ } for $r->{titles}->@*;
+ };
+ };
};
tr_ sub {
- td_ 'Original title';
- td_ lang_attr($r->{lang}), $r->{original};
- } if $r->{original};
-
- tr_ sub {
td_ 'Type';
td_ !$r->{official} && $r->{patch} ? 'Unofficial patch' :
!$r->{official} ? 'Unofficial' : 'Patch';
} if !$r->{official} || $r->{patch};
tr_ sub {
- td_ 'Language';
- td_ sub {
- join_ \&br_, sub {
- abbr_ class => "icons lang $_->{lang}", title => $LANGUAGE{$_->{lang}}, ' ';
- txt_ ' ';
- if($_->{mtl}) {
- b_ class => 'grayedout', $LANGUAGE{$_->{lang}};
- txt_ ' (machine translation)';
- } else {
- txt_ $LANGUAGE{$_->{lang}};
- }
- }, $r->{lang}->@*;
- }
- };
-
- tr_ sub {
td_ 'Publication';
td_ $r->{freeware} ? 'Freeware' : 'Non-free';
};
@@ -136,14 +207,7 @@ sub _infotable_ {
td_ $VOICED{$r->{voiced}}{txt};
} if $r->{voiced};
- tr_ sub {
- td_ 'Animation';
- td_ sub {
- join_ \&br_, sub { txt_ $_ },
- $r->{ani_story} ? "Story: $ANIMATED{$r->{ani_story}}{txt}" : (),
- $r->{ani_ero} ? "Ero scenes: $ANIMATED{$r->{ani_ero}}{txt}" : ();
- }
- } if $r->{ani_story} || $r->{ani_ero};
+ _infotable_animation_ $r;
tr_ sub {
td_ 'Engine';
@@ -153,6 +217,18 @@ sub _infotable_ {
} if length $r->{engine};
tr_ sub {
+ td_ 'DRM';
+ td_ sub { join_ \&br_, sub {
+ my $d = $_;
+ my @prop = grep $d->{$_}, keys %DRM_PROPERTY;
+ abbr_ class => "icon-drm-$_", title => $DRM_PROPERTY{$_}, '' for @prop;
+ abbr_ class => 'icon-drm-free', title => 'DRM-free', '' if !@prop;
+ a_ href => '/r/drm?s='.uri_escape($d->{name}), $d->{name};
+ lit_ ' ('.bb_format($d->{notes}, inline => 1).')' if length $d->{notes};
+ }, $r->{drm}->@* };
+ } if $r->{drm}->@*;
+
+ tr_ sub {
td_ 'Released';
td_ sub { rdate_ $r->{released} };
};
@@ -163,9 +239,9 @@ sub _infotable_ {
} if defined $r->{minage};
tr_ sub {
- td_ 'Censoring';
- td_ $r->{uncensored} ? 'No optical censoring (like mosaics)' : 'Includes optical censoring (e.g. mosaics)';
- } if defined $r->{uncensored};
+ td_ 'Erotic content';
+ td_ $r->{uncensored} ? 'Contains uncensored erotic scenes' : defined $r->{uncensored} ? 'Contains erotic scenes with optical censoring' : 'Contains erotic scenes',
+ } if $r->{has_ero};
for my $t (qw|developer publisher|) {
my @prod = grep $_->{$t}, @{$r->{producers}};
@@ -173,7 +249,7 @@ sub _infotable_ {
td_ ucfirst($t).(@prod == 1 ? '' : 's');
td_ sub {
join_ \&br_, sub {
- a_ href => "/$_->{pid}", title => $_->{original}||$_->{name}, $_->{name};
+ a_ href => "/$_->{pid}", tattr $_;
}, @prod
}
} if @prod;
@@ -192,7 +268,11 @@ sub _infotable_ {
tr_ sub {
td_ 'Links';
td_ sub {
- join_ ', ', sub { a_ href => $_->[1], $_->[0] }, $r->{extlinks}->@*;
+ if ($r->{patch} || $r->{official} || !grep $_->{mtl}, $r->{titles}->@*) {
+ join_ ', ', sub { a_ href => $_->{url2}, $_->{label} }, $r->{extlinks}->@*;
+ } else {
+ small_ 'piracy link hidden';
+ }
}
} if $r->{extlinks}->@*;
@@ -213,19 +293,20 @@ TUWF::get qr{/$RE{rrev}} => sub {
my $r = db_entry tuwf->captures('id','rev');
return tuwf->resNotFound if !$r;
+ $r->{title} = titleprefs_obj $r->{olang}, $r->{titles};
enrich_item $r;
- enrich_extlinks r => $r;
+ enrich_extlinks r => 0, $r;
- framework_ title => $r->{title}, index => !tuwf->capture('rev'), dbobj => $r, hiddenmsg => 1,
+ framework_ title => $r->{title}[1], index => !tuwf->capture('rev'), dbobj => $r, hiddenmsg => 1,
og => {
description => bb_format $r->{notes}, text => 1
},
sub {
_rev_ $r if tuwf->capture('rev');
- div_ class => 'mainbox release', sub {
+ article_ class => 'release', sub {
itemmsg_ $r;
- h1_ sub { txt_ $r->{title}; debug_ $r };
- h2_ class => 'alttitle', lang_attr($r->{lang}), $r->{original} if length $r->{original};
+ h1_ tlang($r->{title}[0], $r->{title}[1]), $r->{title}[1];
+ h2_ class => 'alttitle', tlang(@{$r->{title}}[2,3]), $r->{title}[3] if $r->{title}[3] && $r->{title}[3] ne $r->{title}[1];
_infotable_ $r;
div_ class => 'description', sub { lit_ bb_format $r->{notes} } if $r->{notes};
};
diff --git a/lib/VNWeb/Releases/VNTab.pm b/lib/VNWeb/Releases/VNTab.pm
index dabad6dd..33df7207 100644
--- a/lib/VNWeb/Releases/VNTab.pm
+++ b/lib/VNWeb/Releases/VNTab.pm
@@ -28,26 +28,25 @@ my @rel_cols = (
{ # Title
id => 'tit',
sort_field => 'title',
- sort_sql => 'r.title %s, r.released %1$s',
+ sort_sql => 'r.sorttitle %s, r.released %1$s',
column_string => 'Title',
- draw => sub { a_ href => "/$_[0]{id}", $_[0]{title} },
+ draw => sub { a_ href => "/$_[0]{id}", tattr $_[0] },
}, { # Type
id => 'typ',
sort_field => 'type',
- sort_sql => 'r.patch %s, rv.rtype %1$s, r.released %1$s, r.title %1$s',
+ sort_sql => 'r.patch %s, rv.rtype %1$s, r.released %1$s, r.sorttitle %1$s',
button_string => 'Type',
default => 1,
- draw => sub { abbr_ class => "icons rt$_[0]{rtype}", title => $_[0]{rtype}, ''; txt_ '(patch)' if $_[0]{patch} },
+ draw => sub { abbr_ class => "icon-rt$_[0]{rtype}", title => $_[0]{rtype}, ''; txt_ '(patch)' if $_[0]{patch} },
}, { # Languages
id => 'lan',
button_string => 'Language',
default => 1,
- has_data => sub { !!@{$_[0]{lang}} },
- draw => sub { join_ \&br_, sub { abbr_ class => "icons lang $_->{lang}", title => $LANGUAGE{$_->{lang}}, ''; }, $_[0]{lang}->@* },
+ draw => sub { join_ \&br_, sub { abbr_ class => "icon-lang-$_->{lang}", title => $LANGUAGE{$_->{lang}}{txt}, ''; }, $_[0]{titles}->@* },
}, { # Publication
id => 'pub',
sort_field => 'publication',
- sort_sql => 'r.freeware %1$s, r.patch %1$s, r.released %1$s, r.title %1$s',
+ sort_sql => 'r.freeware %1$s, r.patch %1$s, r.released %1$s, r.sorttitle %1$s',
column_string => 'Publication',
column_width => 70,
button_string => 'Publication',
@@ -74,7 +73,7 @@ my @rel_cols = (
}, { # Resolution
id => 'res',
sort_field => 'resolution',
- sort_sql => 'r.reso_x %s, r.reso_y %1$s, r.patch %1$s, r.released %1$s, r.title %1$s',
+ sort_sql => 'r.reso_x %s, r.reso_y %1$s, r.patch %1$s, r.released %1$s, r.sorttitle %1$s',
column_string => 'Resolution',
button_string => 'Resolution',
na_for_patch => 1,
@@ -84,7 +83,7 @@ my @rel_cols = (
}, { # Voiced
id => 'voi',
sort_field => 'voiced',
- sort_sql => 'r.voiced %s, r.patch %1$s, r.released %1$s, r.title %1$s',
+ sort_sql => 'r.voiced %s, r.patch %1$s, r.released %1$s, r.sorttitle %1$s',
column_string => 'Voiced',
column_width => 70,
button_string => 'Voiced',
@@ -95,7 +94,7 @@ my @rel_cols = (
}, { # Animation
id => 'ani',
sort_field => 'ani_ero',
- sort_sql => 'r.ani_story %s, r.ani_ero %1$s, r.patch %1$s, r.released %1$s, r.title %1$s',
+ sort_sql => 'r.ani_story %s, r.ani_ero %1$s, r.patch %1$s, r.released %1$s, r.sorttitle %1$s',
column_string => 'Animation',
column_width => 110,
button_string => 'Animation',
@@ -118,7 +117,7 @@ my @rel_cols = (
}, { # Age rating
id => 'min',
sort_field => 'minage',
- sort_sql => 'r.minage %s, r.released %1$s, r.title %1$s',
+ sort_sql => 'r.minage %s, r.released %1$s, r.sorttitle %1$s',
button_string => 'Age rating',
default => 1,
has_data => sub { defined $_[0]{minage} },
@@ -126,7 +125,7 @@ my @rel_cols = (
}, { # Notes
id => 'not',
sort_field => 'notes',
- sort_sql => 'r.notes %s, r.released %1$s, r.title %1$s',
+ sort_sql => 'r.notes %s, r.released %1$s, r.sorttitle %1$s',
column_string => 'Notes',
column_width => 400,
button_string => 'Notes',
@@ -168,7 +167,7 @@ sub buttons_ {
}
};
pl 'os', \&platform_, map $_->{platforms}->@*, @$r if $opt->{pla};
- pl 'lang', sub { abbr_ class => "icons lang $_[0]", title => $LANGUAGE{$_[0]}, '' }, map $_->{lang}, map $_->{lang}->@*, @$r if $opt->{lan};
+ pl 'lang', sub { abbr_ class => "icon-lang-$_[0]", title => $LANGUAGE{$_[0]}{txt}, '' }, map $_->{lang}, map $_->{titles}->@*, @$r if $opt->{lan};
}
@@ -178,7 +177,7 @@ sub listing_ {
# Apply language and platform filters
my @r = grep +
($opt->{os} eq 'all' || ($_->{platforms} && grep $_ eq $opt->{os}, $_->{platforms}->@*)) &&
- ($opt->{lang} eq 'all' || ($_->{lang} && grep $_ eq $opt->{lang}, map $_->{lang}, $_->{lang}->@*)), @$r;
+ ($opt->{lang} eq 'all' || ($_->{titles} && grep $_ eq $opt->{lang}, map $_->{lang}, $_->{titles}->@*)), @$r;
# Figure out which columns to display
my @col;
@@ -187,7 +186,7 @@ sub listing_ {
push @col, $c if !@r || !$c->{has_data} || grep $c->{has_data}->($_), @r; # Must have relevant data
}
- div_ class => 'mainbox releases_compare', sub {
+ article_ class => 'releases_compare', sub {
table_ sub {
thead_ sub { tr_ sub {
td_ class => 'key', sub {
@@ -238,7 +237,7 @@ TUWF::get qr{/$RE{vid}/releases} => sub {
my $r = tuwf->dbAlli('
SELECT r.id, rv.rtype, r.patch, r.released, r.gtin
- FROM releases r
+ FROM', releasest, 'r
JOIN releases_vn rv ON rv.id = r.id
WHERE NOT hidden AND rv.vid =', \$v->{id}, '
ORDER BY', sprintf(+(grep $opt->{s} eq ($_->{sort_field}//''), @rel_cols)[0]{sort_sql}, $opt->{o} eq 'a' ? 'ASC' : 'DESC')
@@ -247,9 +246,9 @@ TUWF::get qr{/$RE{vid}/releases} => sub {
my sub url { '?'.query_encode %$opt, @_ }
- framework_ title => "Releases for $v->{title}", dbobj => $v, tab => 'releases', sub {
- div_ class => 'mainbox releases_compare', sub {
- h1_ "Releases for $v->{title}";
+ framework_ title => "Releases for $v->{title}[1]", dbobj => $v, tab => 'releases', sub {
+ article_ class => 'releases_compare', sub {
+ h1_ "Releases for $v->{title}[1]";
if(!@$r) {
p_ 'We don\'t have any information about releases of this visual novel yet...';
} else {