summaryrefslogtreecommitdiff
path: root/lib/VNWeb/Releases/VNTab.pm
blob: 604f24156199c607c23858a0ca258ec55cf372c4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
# TODO: This code is kind of obsolete. It's not been updated with recently
# added release fields and all fields are already displayed more concisely in
# the releases box on the main VN page. The filtering and display options on
# this page can still be useful, though, so need to figure out what to do with
# this in the future.
# Maybe update/modernize this page with the latest fields and icons and
# shorten/simplify the long list of releases on the main VN page? Or expand the
# list on VN pages with filters and display options?

package VNWeb::Releases::VNTab;

use VNWeb::Prelude;
use VNWeb::Releases::Lib 'enrich_release';


# Description of each column, field:
#   id:            Identifier used in URLs
#   sort_field:    Name of the field when sorting
#   sort_sql:      ORDER BY clause when sorting
#   column_string: String to use as column header
#   column_width:  Maximum width (in pixels) of the column in 'restricted width' mode
#   button_string: String to use for the hide/unhide button
#   na_for_patch:  When the field is N/A for patch releases
#   default:       Set when it's visible by default
#   has_data:      Subroutine called with a release object, should return true if the release has data for the column
#   draw:          Subroutine called with a release object, should draw its column contents
my @rel_cols = (
  {    # Title
    id            => 'tit',
    sort_field    => 'title',
    sort_sql      => 'r.title %s, r.released %1$s',
    column_string => 'Title',
    draw          => sub { a_ href => "/$_[0]{id}", $_[0]{title} },
  }, { # Type
    id            => 'typ',
    sort_field    => 'type',
    sort_sql      => 'r.patch %s, r.type %1$s, r.released %1$s, r.title %1$s',
    button_string => 'Type',
    default       => 1,
    draw          => sub { abbr_ class => "icons rt$_[0]{type}", title => $_[0]{type}, ''; 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 $_", title => $LANGUAGE{$_}, ''; }, $_[0]{lang}->@* },
  }, { # Publication
    id            => 'pub',
    sort_field    => 'publication',
    sort_sql      => 'r.doujin %s, r.freeware %1$s,   r.patch %1$s, r.released %1$s, r.title %1$s',
    column_string => 'Publication',
    column_width  => 70,
    button_string => 'Publication',
    default       => 1,
    draw          => sub { txt_ join ', ', $_[0]{freeware} ? 'Freeware' : 'Non-free', $_[0]{patch} ? () : ($_[0]{doujin} ? 'doujin' : 'commercial') },
  }, { # Platforms
    id             => 'pla',
    button_string => 'Platforms',
    default       => 1,
    has_data      => sub { !!@{$_[0]{platforms}} },
    draw          => sub {
        join_ \&br_, sub { platform_ $_ }, $_[0]{platforms}->@*;
        txt_ 'Unknown' if !$_[0]{platforms}->@*;
    },
  }, { # Media
    id            => 'med',
    column_string => 'Media',
    button_string => 'Media',
    has_data      => sub { !!@{$_[0]{media}} },
    draw          => sub {
        join_ \&br_, sub { txt_ fmtmedia $_->{medium}, $_->{qty} }, $_[0]{media}->@*;
        txt_ 'Unknown' if !$_[0]{media}->@*;
    },
  }, { # 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',
    column_string => 'Resolution',
    button_string => 'Resolution',
    na_for_patch  => 1,
    default       => 1,
    has_data      => sub { !!$_[0]{reso_y} },
    draw          => sub { txt_ resolution($_[0]) || 'Unknown' },
  }, { # Voiced
    id            => 'voi',
    sort_field    => 'voiced',
    sort_sql      => 'r.voiced %s,                    r.patch %1$s, r.released %1$s, r.title %1$s',
    column_string => 'Voiced',
    column_width  => 70,
    button_string => 'Voiced',
    na_for_patch  => 1,
    default       => 1,
    has_data      => sub { !!$_[0]{voiced} },
    draw          => sub { txt_ $VOICED{$_[0]{voiced}}{txt} },
  }, { # 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',
    column_string => 'Animation',
    column_width  => 110,
    button_string => 'Animation',
    na_for_patch  => '1',
    has_data      => sub { !!($_[0]{ani_story} || $_[0]{ani_ero}) },
    draw          => sub {
      txt_ join ', ',
        $_[0]{ani_story} ? "Story: $ANIMATED{$_[0]{ani_story}}{txt}"   :(),
        $_[0]{ani_ero}   ? "Ero scenes: $ANIMATED{$_[0]{ani_ero}}{txt}":();
      txt_ 'Unknown' if !$_[0]{ani_story} && !$_[0]{ani_ero};
    },
  }, { # Released
    id            => 'rel',
    sort_field    => 'released',
    sort_sql      => 'r.released %s, r.id %1$s',
    column_string => 'Released',
    button_string => 'Released',
    default       => 1,
    draw          => sub { rdate_ $_[0]{released} },
  }, { # Age rating
    id            => 'min',
    sort_field    => 'minage',
    sort_sql      => 'r.minage %s,                                  r.released %1$s, r.title %1$s',
    button_string => 'Age rating',
    default       => 1,
    has_data      => sub { defined $_[0]{minage} },
    draw          => sub { txt_ minage $_[0]{minage} },
  }, { # Notes
    id            => 'not',
    sort_field    => 'notes',
    sort_sql      => 'r.notes %s,                                   r.released %1$s, r.title %1$s',
    column_string => 'Notes',
    column_width  => 400,
    button_string => 'Notes',
    default       => 1,
    has_data      => sub { !!$_[0]{notes} },
    draw          => sub { lit_ bb_format $_[0]{notes} },
  }
);



sub buttons_ {
    my($opt, $url, $r) = @_;

    # Column visibility
    p_ class => 'browseopts', sub {
        a_ href => $url->($_->{id}, $opt->{$_->{id}} ? 0 : 1), $opt->{$_->{id}} ? (class => 'optselected') : (), $_->{button_string}
        for grep $_->{button_string}, @rel_cols;
    };

    # Misc options
    my $all_selected   = !grep $_->{button_string} && !$opt->{$_->{id}}, @rel_cols;
    my $all_unselected = !grep $_->{button_string} &&  $opt->{$_->{id}}, @rel_cols;
    my $all_url = sub { $url->(map +($_->{id},$_[0]), grep $_->{button_string}, @rel_cols); };
    p_ class => 'browseopts', sub {
        a_ href => $all_url->(1),                    $all_selected   ? (class => 'optselected') : (), 'All on';
        a_ href => $all_url->(0),                    $all_unselected ? (class => 'optselected') : (), 'All off';
        a_ href => $url->('cw', $opt->{cw} ? 0 : 1), $opt->{cw}      ? (class => 'optselected') : (), 'Restrict column width';
    };

    my sub pl {
        my($row, $option, $txt, $icon) = @_;
        my %opts = map +($_,1), map $_->{$row}->@*, @$r;
        return if !keys %opts;
        p_ class => 'browseopts', sub {
            a_ href => $url->($option, $_), $_ eq $opt->{$option} ? (class => 'optselected') : (), sub {
                $_ eq 'all' ? txt_ 'All' : $icon->($_);
            } for ('all', sort keys %opts);
        }
    };
    pl 'platforms', 'os', \%PLATFORM, \&platform_ if $opt->{pla};
    pl 'lang',     'lang',\%LANGUAGE, sub { abbr_ class => "icons lang $_[0]", title => $LANGUAGE{$_[0]}, '' } if $opt->{lan};
}


sub listing_ {
    my($opt, $url, $r) = @_;

    # 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}, $_->{lang}->@*)), @$r;

    # Figure out which columns to display
    my @col;
    for my $c (@rel_cols) {
        next if $c->{button_string} && !$opt->{$c->{id}}; # Hidden by settings
        push @col, $c if !@r || !$c->{has_data} || grep $c->{has_data}->($_), @r; # Must have relevant data
    }

    div_ class => 'mainbox releases_compare', sub {
        table_ sub {
            thead_ sub { tr_ sub {
                td_ class => 'key', sub {
                    txt_ $_->{column_string} if $_->{column_string};
                    sortable_ $_->{sort_field}, $opt, $url if $_->{sort_field};
                } for @col;
            } };
            tr_ sub {
                my $r = $_;
                # Combine "N/A for patches" columns
                my $cspan = 1;
                for my $c (0..$#col) {
                    if($r->{patch} && $col[$c]{na_for_patch} && $c < $#col && $col[$c+1]{na_for_patch}) {
                        $cspan++;
                        next;
                    }
                    td_ $cspan > 1 ? (colspan => $cspan) : (),
                    $col[$c]{column_width} && $opt->{cw} ? (style => "max-width: $col[$c]{column_width}px") : ();
                    if($r->{patch} && $col[$c]{na_for_patch}) {
                        txt_ 'NA for patches';
                    } else {
                        $col[$c]{draw}->($r);
                    }
                    end_;
                    $cspan = 1;
                }
            } for @r;
        }
    }
}


TUWF::get qr{/$RE{vid}/releases} => sub {
    my $v = dbobj tuwf->capture('id');
    return tuwf->resNotFound if !$v->{id};

    my $opt = tuwf->validate(get =>
        cw   => { anybool => 1 },
        o    => { onerror => 'a', enum => [0,1,'d','a'] },
        s    => { onerror => 'released', enum => [ map $_->{sort_field}, grep $_->{sort_field}, @rel_cols ]},
        os   => { onerror => 'all',      enum => [ 'all', keys %PLATFORM ] },
        lang => { onerror => 'all',      enum => [ 'all', keys %LANGUAGE ] },
        map +($_->{id}, { anybool => 1, default => $_->{default} }), grep $_->{button_string}, @rel_cols
    )->data;
    # Compat with old URLs
    $opt->{o} = 'a' if $opt->{o} eq 0;
    $opt->{o} = 'd' if $opt->{o} eq 1;

    my $r = tuwf->dbAlli('
        SELECT r.id, r.type, r.patch, r.released, r.gtin
          FROM releases 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')
    );
    enrich_release $r;

    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}";
            if(!@$r) {
                p_ 'We don\'t have any information about releases of this visual novel yet...';
            } else {
                buttons_($opt, \&url, $r);
            }
        };
        listing_ $opt, \&url, $r if @$r;
    };
};


1;