summaryrefslogtreecommitdiff
path: root/lib/VNWeb/TableOpts.pm
diff options
context:
space:
mode:
Diffstat (limited to 'lib/VNWeb/TableOpts.pm')
-rw-r--r--lib/VNWeb/TableOpts.pm130
1 files changed, 99 insertions, 31 deletions
diff --git a/lib/VNWeb/TableOpts.pm b/lib/VNWeb/TableOpts.pm
index fa976f40..42885fa1 100644
--- a/lib/VNWeb/TableOpts.pm
+++ b/lib/VNWeb/TableOpts.pm
@@ -31,6 +31,7 @@ package VNWeb::TableOpts;
# # may include '?o' placeholder that will be replaced with selected ASC/DESC,
# # or '!o' as placeholder for the opposite.
# # If no placeholders are present, the ASC/DESC will be added automatically.
+# sort_num => 0/1, # Whether this is a numeric field, used in the UI to display "1→9" instead of "A→Z".
# sort_default => 'asc', # Set to 'asc' or 'desc' if this column should be sorted on by default.
# },
# popularity => {
@@ -66,11 +67,11 @@ package VNWeb::TableOpts;
use v5.26;
use Carp 'croak';
use Exporter 'import';
-use TUWF;
+use TUWF ':html5_';
use VNWeb::Auth;
use VNWeb::HTML ();
use VNWeb::Validation;
-use VNWeb::Elm;
+use VNWeb::JS;
our @EXPORT = ('tableopts');
@@ -95,6 +96,7 @@ sub tableopts {
views => [], # supported views, as numbers
default => 0, # default settings, integer form
);
+ my @vis;
while(@_) {
my($k,$v) = (shift,shift);
if($k eq '_views') {
@@ -108,43 +110,63 @@ sub tableopts {
$o{columns}{$k} = $v;
$v->{id} = $k;
push $o{col_order}->@*, $v;
- $o{sort_ids}[$v->{sort_id}] = $v if defined $v->{sort_id};
+ if(defined $v->{sort_id}) {
+ die "Duplicate sort_id $v->{sort_id}\n" if $o{sort_ids}[$v->{sort_id}];
+ $o{sort_ids}[$v->{sort_id}] = $v;
+ }
+ die "Duplicate vis_id $v->{vis_id}\n" if defined $v->{vis_id} && $vis[$v->{vis_id}]++;
$o{default} |= ($v->{sort_id} << 6) | ({qw|asc 0 desc 32|}->{$v->{sort_default}}//croak("unknown sort_default: $v->{sort_default}")) if $v->{sort_default};
$o{default} |= 1 << ($v->{vis_id} + 12) if $v->{vis_default};
}
$o{views} ||= [0];
$o{default} |= $o{views}[0];
+ #warn "=== ".($o{pref}||'undef')."\n"; dump_ids(\%o);
\%o
}
+# COMPAT: For old URLs, we assume that this validation is used on the 's'
+# parameter, so we can accept two formats:
+# - "s=$compat_sort_column/$order"
+# - "s=$compat_sort_column&o=$order"
+# In the latter case, the validation will use reqGet() to get the 'o'
+# parameter.
TUWF::set('custom_validations')->{tableopts} = sub {
my($t) = @_;
+{ onerror => sub {
- my $d = $t->{pref} && auth ? tuwf->dbVali('SELECT', $t->{pref}, 'FROM users WHERE id =', \auth->uid) : undef;
- bless([$d // $t->{default},$t], __PACKAGE__)
+ my $d = $t->{pref} && auth->pref($t->{pref});
+ my $o = bless([$d // $t->{default},$t], __PACKAGE__);
+ $o->fixup;
}, func => sub {
my $obj = bless [undef, $t], __PACKAGE__;
- my $col = [grep $_->{compat} && $_->{compat} eq $_[0], values $t->{columns}->%*]->[0];
+ my($val,$ord) = $_[0] =~ m{^([^/]+)/([ad])$} ? ($1,$2) : ($_[0],undef);
+ my $col = [grep $_->{compat} && $_->{compat} eq $val, values $t->{columns}->%*]->[0];
if($col && defined $col->{sort_id}) {
$obj->[0] = $t->{default};
$obj->set_sort_col_id($col->{sort_id});
- my $ord = tuwf->reqGet('o');
+ $ord //= tuwf->reqGet('o');
$obj->set_order($ord && $ord eq 'd' ? 1 : 0);
} else {
$obj->[0] = _dec($_[0]) // return 0;
}
- $_[0] = $obj;
+ $_[0] = $obj->fixup;
# We could do strict validation on the individual fields, but the methods below can handle incorrect data.
1;
} }
};
-sub query_encode {
- my($v,$o) = $_[0]->@*;
- $v == $o->{default} ? undef : _enc $v;
+sub fixup {
+ my($obj) = @_;
+ # Reset sort_col and order to their default if the current sort_col id does not exist.
+ if(!$obj->[1]{sort_ids}[ $obj->sort_col_id ]) {
+ $obj->set_sort_col_id(sort_col_id([$obj->[1]{default}]));
+ $obj->set_order(order([$obj->[1]{default}]));
+ }
+ $obj
}
+sub query_encode { _enc $_[0][0] }
+
sub view { $views[$_[0][0] & 3] || $views[$_[0][1]{views}[0]] }
sub rows { shift->view eq 'rows' }
sub cards { shift->view eq 'cards' }
@@ -156,7 +178,16 @@ sub order { $_[0][0] & 32 }
sub set_order { if($_[1]) { $_[0][0] |= 32 } else { $_[0][0] &= ~32 } }
sub sort_col_id { ($_[0][0] >> 6) & 63 }
-sub set_sort_col_id { $_[0][0] = ($_[0][0] & (~1 - 0b111111000000)) | ($_[1] << 6) }
+sub set_sort_col_id { $_[0][0] = ($_[0][0] & (~0 - 0b111111000000)) | ($_[1] << 6) }
+
+# Given a view id, return a new object with that view selected.
+sub view_param {
+ my($self, $view) = @_;
+ my $n = bless [@$self], __PACKAGE__;
+ $n->[0] = ($n->[0] & ~3) | $view;
+ $n
+}
+
# Given the key of a column, returns whether it is currently sorted on ('' / 'a' / 'd')
sub sorted {
@@ -177,7 +208,7 @@ sub sort_param {
sub sql_order {
my($self) = @_;
my($v,$o) = $self->@*;
- my $col = $o->{sort_ids}[ $self->sort_col_id ] || $o->{sort_ids}[ sort_col_id([$o->{default}]) ];
+ my $col = $o->{sort_ids}[ $self->sort_col_id ];
die "No column to sort on" if !$col;
my $order = $self->order ? 'DESC' : 'ASC';
my $opposite_order = $self->order ? 'ASC' : 'DESC';
@@ -187,43 +218,80 @@ sub sql_order {
# Returns whether the given column key is visible.
-sub vis { $_[0][0] & (1 << (12+$_[0][1]{columns}{$_[1]}{vis_id})) }
+sub vis { my $c = $_[0][1]{columns}{$_[1]}; $c && defined $c->{vis_id} && ($_[0][0] & (1 << (12+$c->{vis_id}))) }
+
+# Given a list of column names, return a new object with only these columns visible
+sub vis_param {
+ my($self, @cols) = @_;
+ my $n = bless [@$self], __PACKAGE__;
+ $n->[0] = $n->[0] & 0b1111_1111_1111;
+ $n->[0] |= 1 << (12+$self->[1]{columns}{$_}{vis_id}) for @cols;
+ $n;
+}
my $FORM_OUT = form_compile any => {
- save => { required => 0 },
+ save => { default => undef },
views => { type => 'array', values => { uint => 1 } },
- default => { uint => 1 },
value => { uint => 1 },
- sorts => { aoh => { id => { uint => 1 }, name => {} } },
+ default => { uint => 1 },
+ usaved => { uint => 1, default => undef },
+ sorts => { aoh => { id => { uint => 1 }, name => {}, num => { anybool => 1 } } },
vis => { aoh => { id => { uint => 1 }, name => {} } },
};
-elm_api TableOptsSave => $FORM_OUT, {
+js_api TableOptsSave => {
save => { enum => ['tableopts_c', 'tableopts_v', 'tableopts_vt'] },
- value => { required => 0, uint => 1 }
+ value => { default => undef, uint => 1 }
}, sub {
my($f) = @_;
- return elm_Unauth if !auth;
- tuwf->dbExeci('UPDATE users SET', { $f->{save} => $f->{value} }, 'WHERE id =', \auth->uid);
- elm_Success
+ return tuwf->resDenied if !auth;
+ tuwf->dbExeci('UPDATE users_prefs SET', { $f->{save} => $f->{value} }, 'WHERE id =', \auth->uid);
+ {}
};
-sub elm_ {
- my $self = shift;
+
+sub widget_ {
+ my($self,$url) = @_;
my($v,$o) = $self->@*;
- VNWeb::HTML::elm_ TableOpts => $FORM_OUT, {
+ menu_ class => 'tableopts', VNWeb::HTML::widget(TableOpts => $FORM_OUT, {
save => auth ? $o->{pref} : undef,
views => $o->{views},
- default => $o->{default},
value => $v,
- sorts => [ map +{ id => $_->{sort_id}, name => $_->{name} }, grep defined $_->{sort_id}, values $o->{col_order}->@* ],
+ default => $o->{default},
+ usaved => $o->{pref} && auth->pref($o->{pref}),
+ sorts => [ map +{ id => $_->{sort_id}, name => $_->{name}, num => $_->{sort_num}||0 }, grep defined $_->{sort_id}, values $o->{col_order}->@* ],
vis => [ map +{ id => $_->{vis_id}, name => $_->{name} }, grep defined $_->{vis_id}, values $o->{col_order}->@* ],
- }, sub {
- TUWF::XML::div_ @_, sub {
- TUWF::XML::input_ type => 'hidden', name => 's', value => $self->query_encode if defined $self->query_encode
- }
+ }), sub {
+ li_ class => 'hidden', sub {
+ input_ type => 'hidden', name => 's', value => $self->query_encode;
+ };
+ li_ sub {
+ a_ href => $url->(s => $self->view_param($_)),
+ class => $_ == ($self->[0] & 3) ? 'highlightselected' : undef,
+ title => ['List view', 'Card view', 'Grid view']->[$_], sub {
+ # SVG icons from https://lucide.dev/, MIT
+ lit_ '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><g fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">'.
+ [ '<line x1="8" x2="21" y1="6" y2="6"/><line x1="8" x2="21" y1="12" y2="12"/><line x1="8" x2="21" y1="18" y2="18"/><line x1="3" x2="3.01" y1="6" y2="6"/><line x1="3" x2="3.01" y1="12" y2="12"/><line x1="3" x2="3.01" y1="18" y2="18"/>'
+ , '<rect width="18" height="18" x="3" y="3" rx="2" ry="2"/><line x1="3" x2="21" y1="12" y2="12"/>'
+ , '<rect width="7" height="7" x="3" y="3" rx="1"/><rect width="7" height="7" x="14" y="3" rx="1"/><rect width="7" height="7" x="14" y="14" rx="1"/><rect width="7" height="7" x="3" y="14" rx="1"/>'
+ ]->[$_].'</g></svg>';
+ };
+ } for $o->{views}->@*;
};
}
+
+# Helpful debugging function, dumps a quick overview of assigned numeric
+# identifiers for the given opts.
+sub dump_ids {
+ my($o) = @_;
+ warn sprintf "sort %2d %s %s\n", $_->{sort_id}, $_->{id}, $_->{name}
+ for sort { $a->{sort_id} <=> $b->{sort_id} }
+ grep defined $_->{sort_id}, values $o->{col_order}->@*;
+ warn sprintf "vis %2d %s %s\n", $_->{vis_id}, $_->{id}, $_->{name}
+ for sort { $a->{vis_id} <=> $b->{vis_id} }
+ grep defined $_->{vis_id}, values $o->{col_order}->@*;
+}
+
1;