summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYorhel <git@yorhel.nl>2021-08-09 10:33:57 +0200
committerYorhel <git@yorhel.nl>2021-08-09 10:34:54 +0200
commit02448a7dd976344d29f698b84027686a6e64c673 (patch)
tree93328f73f0e509569cc17fb3194858111da289ca
parent5e7673e4543c104985c23ff1b21c949cbf14d48a (diff)
VNLengthVote: Add support for multiple release selection
Using a Postgres array for this is ugly, but releases are purely informative so no strong need for referential integrity. Also, display languages in length vote listing.
-rw-r--r--css/v2.css12
-rw-r--r--elm/VNLengthVote.elm39
-rw-r--r--lib/VNWeb/VN/Length.pm14
-rw-r--r--lib/VNWeb/VN/Page.pm5
-rw-r--r--sql/schema.sql4
-rw-r--r--sql/tableattrs.sql1
-rw-r--r--util/updates/2021-08-09-vnlength-multirelease.sql4
7 files changed, 55 insertions, 24 deletions
diff --git a/css/v2.css b/css/v2.css
index 170c7df5..541af409 100644
--- a/css/v2.css
+++ b/css/v2.css
@@ -476,7 +476,15 @@ div.vndetails td.title abbr { float: right }
div.vndetails td.relations dt { float: none; font-style: normal; }
div.vndetails td.relations dd { margin-left: 15px; }
div.vndetails td.anime b { font-size: 10px; font-weight: normal; padding-right: 4px; }
-div.vndetails .lengthvotefrm { margin-top: -18px }
+div.vndetails .lengthvotefrm {
+ margin-top: -18px; text-align: right;
+ > form { text-align: left }
+ > form div > div {
+ display: flex;
+ select { flex: 1 }
+ input { flex: 0 0 30px }
+ }
+}
.ulistvn { padding: 5px 0 0 0 }
.ulistvn > b { font-size: 14px }
.ulistvn > span { float: right }
@@ -874,7 +882,7 @@ div.lengthlist {
.tc1 { width: 100px }
.tc3 { width: 60px; white-space: nowrap }
.tc4 { width: 100px; padding-left: 10px }
- .tc5 { width: 50px }
+ .tc5 { width: 70px; white-space: nowrap }
.tc7 { width: 10px; text-align: right; padding: 0 }
select { width: 70px }
}
diff --git a/elm/VNLengthVote.elm b/elm/VNLengthVote.elm
index 48fddcb2..16fd3ff3 100644
--- a/elm/VNLengthVote.elm
+++ b/elm/VNLengthVote.elm
@@ -8,6 +8,7 @@ import Browser.Dom exposing (focus)
import Task
import Date
import Lib.Html exposing (..)
+import Lib.Util exposing (..)
import Lib.Api as Api
import Lib.RDate as RDate
import Gen.Api as GApi
@@ -29,7 +30,7 @@ type alias Model =
, today : Int
, uid : String
, vid : String
- , rid : String
+ , rid : List String
, defrid : String
, hours : Maybe Int
, minutes : Maybe Int
@@ -46,10 +47,10 @@ init f =
, open = False
, uid = f.uid
, vid = f.vid
- , rid = Maybe.map (\v -> v.rid) f.vote |> Maybe.withDefault ""
+ , rid = Maybe.map (\v -> v.rid) f.vote |> Maybe.withDefault []
, defrid = ""
, hours = Maybe.map (\v -> v.length // 60 ) f.vote
- , minutes = Maybe.map (\v -> modBy 60 v.length) f.vote
+ , minutes = Maybe.andThen (\v -> let n = modBy 60 v.length in if n == 0 then Nothing else Just n) f.vote
, speed = Maybe.map (\v -> v.speed) f.vote |> Maybe.withDefault -1
, length = Maybe.map (\v -> v.length) f.vote |> Maybe.withDefault 0
, notes = Maybe.map (\v -> v.notes) f.vote |> Maybe.withDefault ""
@@ -73,7 +74,9 @@ type Msg
| Hours (Maybe Int)
| Minutes (Maybe Int)
| Speed Int
- | Release String
+ | Release Int String
+ | ReleaseAdd
+ | ReleaseDel Int
| Notes String
| RelLoaded GApi.Response
| Delete
@@ -93,7 +96,9 @@ update msg model =
Hours n -> ({ model | hours = n }, Cmd.none)
Minutes n -> ({ model | minutes = n }, Cmd.none)
Speed n -> ({ model | speed = n }, Cmd.none)
- Release s -> ({ model | rid = s }, Cmd.none)
+ Release n s -> ({ model | rid = modidx n (always s) model.rid }, Cmd.none)
+ ReleaseAdd -> ({ model | rid = model.rid ++ [""] }, Cmd.none)
+ ReleaseDel n -> ({ model | rid = delidx n model.rid }, Cmd.none)
Notes s -> ({ model | notes = s }, Cmd.none)
RelLoaded (GApi.Releases rels) ->
let rel r = if r.rtype /= "trial" && r.released <= model.today then Just (r.id, RDate.showrel r) else Nothing
@@ -104,10 +109,10 @@ update msg model =
in ({ model | state = Api.Normal
, rels = Just frels
, defrid = def
- , rid = if model.rid == "" then def else model.rid
+ , rid = if not (List.isEmpty model.rid) then model.rid else [def]
}, if model.hours == Nothing then Task.attempt (always Noop) (focus "vnlengthhours") else Cmd.none)
RelLoaded e -> ({ model | state = Api.Error e }, Cmd.none)
- Delete -> let m = { model | hours = Nothing, minutes = Nothing, rid = model.defrid, notes = "", state = Api.Loading } in (m, GV.send (encode m) Submitted)
+ Delete -> let m = { model | hours = Nothing, minutes = Nothing, rid = [model.defrid], notes = "", state = Api.Loading } in (m, GV.send (encode m) Submitted)
Submit -> ({ model | state = Api.Loading }, GV.send (encode model) Submitted)
Submitted (GApi.Success) ->
({ model | open = False, state = Api.Normal
@@ -119,10 +124,12 @@ update msg model =
view : Model -> Html Msg
view model = div [class "lengthvotefrm"] <|
let
- cansubmit = enclen model > 0 && model.speed /= -1 && model.rid /= ""
+ cansubmit = enclen model > 0 && model.speed /= -1
+ && not (List.isEmpty model.rid)
+ && not (List.any (\r -> r == "") model.rid)
rels = Maybe.withDefault [] model.rels
frm = [ form_ "" (if cansubmit then Submit else Noop) False
- [ br_ 2
+ [ br [] []
, text "How long did you take to finish this VN?"
, br [] []
, text "- Only vote if you've completed all normal/true endings."
@@ -135,10 +142,14 @@ view model = div [class "lengthvotefrm"] <|
, inputNumber "" model.minutes Minutes [ Html.Attributes.min "0", Html.Attributes.max "59" ]
, text " minutes"
, br [] []
- , if model.defrid /= "" then text "" else -- TODO: Handle missing model.rid
- inputSelect "" model.rid Release [style "width" "100%"]
- <| ("", "-- select release --") :: rels
- ++ if model.rid == "" || List.any (\(r,_) -> r == model.rid) rels then [] else [(model.rid, "[deleted/moved release: " ++ model.rid ++ "]")]
+ , if model.defrid /= "" then text "" else div [] <| List.indexedMap (\n rid -> div []
+ [ inputSelect "" rid (Release n) []
+ <| ("", "-- select release --") :: rels
+ ++ if rid == "" || List.any (\(r,_) -> r == rid) rels then [] else [(rid, "[deleted/moved release: " ++ rid ++ "]")]
+ , if n == 0
+ then inputButton "+" ReleaseAdd [title "Add release"]
+ else inputButton "-" (ReleaseDel n) [title "Remove release"]
+ ]) model.rid
, inputSelect "" model.speed Speed [style "width" "100%"]
[ (-1, "-- how do you estimate your read/play speed? --")
, (0, "Slow (e.g. low language proficiency or extra time spent on gameplay)")
@@ -154,7 +165,7 @@ view model = div [class "lengthvotefrm"] <|
] ]
in
[ text " "
- , a [ onClickD (Open (not model.open)), href "#", style "float" "right" ]
+ , a [ onClickD (Open (not model.open)), href "#" ]
[ text <| if model.length == 0 then "Vote ยป"
else "My vote: " ++ String.fromInt (model.length // 60) ++ "h"
++ if modBy 60 model.length /= 0 then String.fromInt (modBy 60 model.length) ++ "m" else "" ]
diff --git a/lib/VNWeb/VN/Length.pm b/lib/VNWeb/VN/Length.pm
index eeff981f..e64a620a 100644
--- a/lib/VNWeb/VN/Length.pm
+++ b/lib/VNWeb/VN/Length.pm
@@ -52,7 +52,11 @@ sub listing_ {
} if $mode ne 'v';
td_ class => 'tc3'.($_->{ignore}?' grayedout':''), sub { vnlength_ $_->{length} };
td_ class => 'tc4'.($_->{ignore}?' grayedout':''), ['Slow','Normal','Fast']->[$_->{speed}];
- td_ class => 'tc5', sub { a_ href => "/$_->{rid}", $_->{rid} };
+ td_ class => 'tc5', sub {
+ my %l = map +($_,1), map $_->{lang}->@*, $_->{rel}->@*;
+ abbr_ class => "icons lang $_", title => $LANGUAGE{$_}, '' for sort keys %l;
+ join_ ',', sub { a_ href => "/$_->{id}", $_->{id} }, sort { idcmp $a->{id}, $b->{id} } $_->{rel}->@*;
+ };
td_ class => 'tc6', sub { lit_ bb_format $_->{notes}, inline => 1 };
td_ class => 'tc7', sub {
select_ name => "$_->{vid}-$_->{uid}", sub {
@@ -119,7 +123,8 @@ TUWF::get qr{/(?:(?<thing>$RE{vid}|$RE{uid})/)?lengthvotes}, sub {
my $count = tuwf->dbVali('SELECT COUNT(*) FROM vn_length_votes l WHERE', $where);
my $lst = tuwf->dbPagei({results => $opt->{s}->results, page => $opt->{p}},
- 'SELECT l.uid, l.vid, l.length, l.speed, l.notes, l.rid, ', sql_totime('l.date'), 'AS date, l.ignore OR u.perm_lengthvote IS NOT DISTINCT FROM false AS ignore',
+ 'SELECT l.uid, l.vid, l.length, l.speed, l.notes, l.rid::text[] AS rel, '
+ , sql_totime('l.date'), 'AS date, l.ignore OR u.perm_lengthvote IS NOT DISTINCT FROM false AS ignore',
$mode ne 'u' ? (', ', sql_user()) : (),
$mode ne 'v' ? ', v.title, v.original' : (), '
FROM vn_length_votes l
@@ -128,6 +133,8 @@ TUWF::get qr{/(?:(?<thing>$RE{vid}|$RE{uid})/)?lengthvotes}, sub {
'WHERE', $where,
'ORDER BY', $opt->{s}->sql_order(),
);
+ $_->{rel} = [ map +{ id => $_ }, $_->{rel}->@* ] for @$lst;
+ enrich_flatten lang => id => id => 'SELECT id, lang FROM releases_lang WHERE id IN', map $_->{rel}, @$lst;
my $title = 'Length votes'.($mode ? ($mode eq 'v' ? ' for ' : ' by ').$o->{title} : '');
framework_ title => $title, dbobj => $o, sub {
@@ -166,7 +173,7 @@ our $LENGTHVOTE = form_compile any => {
uid => { vndbid => 'u' },
vid => { vndbid => 'v' },
vote => { type => 'hash', required => 0, keys => {
- rid => { vndbid => 'r' },
+ rid => { type => 'array', minlength => 1, values => { vndbid => 'r' } },
length => { uint => 1, range => [1,32767] },
speed => { uint => 1, enum => [0,1,2] },
notes => { required => 0, default => '' },
@@ -178,6 +185,7 @@ elm_api VNLengthVote => undef, $LENGTHVOTE, sub {
return elm_Unauth if !can_vote() || $data->{uid} ne auth->uid;
my %where = ( uid => $data->{uid}, vid => $data->{vid} );
tuwf->dbExeci('DELETE FROM vn_length_votes WHERE', \%where) if !$data->{vote};
+ $data->{vote}{rid} = sql sql_array($data->{vote}{rid}->@*), '::vndbid[]' if $data->{vote};
tuwf->dbExeci(
'INSERT INTO vn_length_votes', { %where, $data->{vote}->%* },
'ON CONFLICT (uid, vid) DO UPDATE SET', $data->{vote}
diff --git a/lib/VNWeb/VN/Page.pm b/lib/VNWeb/VN/Page.pm
index 78f93dc8..60876e1e 100644
--- a/lib/VNWeb/VN/Page.pm
+++ b/lib/VNWeb/VN/Page.pm
@@ -161,13 +161,14 @@ sub infobox_length_ {
my $stats = tuwf->dbRowi('
SELECT count(*) as count
- , percentile_cont(0.5) WITHIN GROUP (ORDER BY l.length + (l.length/4 * (l.speed-1))) AS median
+ , percentile_cont(', \0.5, ') WITHIN GROUP (ORDER BY l.length + (l.length/(1+1+1+1) * (l.speed-1))) AS median
FROM vn_length_votes l
LEFT JOIN users u ON u.id = l.uid
WHERE u.perm_lengthvote IS DISTINCT FROM false AND NOT l.ignore AND l.vid =', \$v->{id});
return if !$v->{length} && !$stats->{count} && !VNWeb::VN::Length::can_vote();
- my $my = VNWeb::VN::Length::can_vote() && tuwf->dbRowi('SELECT rid, length, speed, notes FROM vn_length_votes WHERE vid =', \$v->{id}, 'AND uid =', \auth->uid);
+ my $my = VNWeb::VN::Length::can_vote()
+ && tuwf->dbRowi('SELECT rid::text[] AS rid, length, speed, notes FROM vn_length_votes WHERE vid =', \$v->{id}, 'AND uid =', \auth->uid);
tr_ sub {
td_ 'Play time';
diff --git a/sql/schema.sql b/sql/schema.sql
index d730189d..d97051df 100644
--- a/sql/schema.sql
+++ b/sql/schema.sql
@@ -1154,13 +1154,13 @@ CREATE TABLE vn_staff_hist (
-- vn_length_votes
CREATE TABLE vn_length_votes (
vid vndbid NOT NULL, -- [pub]
- rid vndbid NOT NULL, -- [pub]
date timestamptz NOT NULL DEFAULT NOW(), -- [pub]
uid vndbid, -- [pub]
length smallint NOT NULL, -- [pub] minutes
speed smallint NOT NULL, -- [pub] 0=slow, 1=normal, 2=fast
notes text NOT NULL DEFAULT '', -- [pub]
- ignore boolean NOT NULL DEFAULT false -- [pub]
+ ignore boolean NOT NULL DEFAULT false, -- [pub]
+ rid vndbid[] NOT NULL -- [pub]
);
-- wikidata
diff --git a/sql/tableattrs.sql b/sql/tableattrs.sql
index 77b5be07..0cbb8043 100644
--- a/sql/tableattrs.sql
+++ b/sql/tableattrs.sql
@@ -115,7 +115,6 @@ ALTER TABLE vn_staff ADD CONSTRAINT vn_staff_id_fkey
ALTER TABLE vn_staff ADD CONSTRAINT vn_staff_aid_fkey FOREIGN KEY (aid) REFERENCES staff_alias (aid) DEFERRABLE INITIALLY DEFERRED;
ALTER TABLE vn_staff_hist ADD CONSTRAINT vn_staff_hist_chid_fkey FOREIGN KEY (chid) REFERENCES changes (id) ON DELETE CASCADE;
ALTER TABLE vn_length_votes ADD CONSTRAINT vn_length_votes_vid_fkey FOREIGN KEY (vid) REFERENCES vn (id);
-ALTER TABLE vn_length_votes ADD CONSTRAINT vn_length_votes_rid_fkey FOREIGN KEY (rid) REFERENCES releases (id);
ALTER TABLE vn_length_votes ADD CONSTRAINT vn_length_votes_uid_fkey FOREIGN KEY (uid) REFERENCES users (id) ON DELETE SET DEFAULT;
diff --git a/util/updates/2021-08-09-vnlength-multirelease.sql b/util/updates/2021-08-09-vnlength-multirelease.sql
new file mode 100644
index 00000000..e5917f34
--- /dev/null
+++ b/util/updates/2021-08-09-vnlength-multirelease.sql
@@ -0,0 +1,4 @@
+ALTER TABLE vn_length_votes ADD COLUMN rid2 vndbid[] NOT NULL DEFAULT '{}';
+UPDATE vn_length_votes SET rid2 = ARRAY[rid];
+ALTER TABLE vn_length_votes DROP COLUMN rid;
+ALTER TABLE vn_length_votes RENAME COLUMN rid2 TO rid;