summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYorhel <git@yorhel.nl>2019-11-05 11:13:48 +0100
committerYorhel <git@yorhel.nl>2019-11-10 12:44:55 +0100
commit1ae8c1e325bdf3d37400552aad21c7574f0b1f24 (patch)
tree2b39747cdbd14d8a5a21749a66f696999606cb91
parent24f03ae521a6b35def38788e0446201aa6095314 (diff)
ulist: Implement notes editing + small code reorg
-rw-r--r--data/style.css1
-rw-r--r--elm/UList/DateEdit.elm2
-rw-r--r--elm/UList/LabelEdit.elm2
-rw-r--r--elm/UList/ManageLabels.elm2
-rw-r--r--elm/UList/Opt.elm50
-rw-r--r--elm/UList/Opt.js5
-rw-r--r--elm/UList/VoteEdit.elm2
-rw-r--r--lib/VNWeb/User/Lists.pm249
8 files changed, 185 insertions, 128 deletions
diff --git a/data/style.css b/data/style.css
index 54d9bec8..4b2439f2 100644
--- a/data/style.css
+++ b/data/style.css
@@ -795,6 +795,7 @@ div.votelist td.tc2 { width: 50px; text-align: right; padding-right: 10px }
.ulist .tc1 input { display: none }
.ulist .tc1 label:before { content: '▸ ' }
.ulist .tc1 input:checked + label:before { content: '▾ ' }
+.ulist .tc1 span.blurred { opacity: 0.5 }
.ulist .tc2 b { margin-left: 10px }
.ulist .tc4 { white-space: nowrap; width: 60px; text-align: right; padding-right: 10px }
.ulist .tc4 input { width: 55px; text-align: right }
diff --git a/elm/UList/DateEdit.elm b/elm/UList/DateEdit.elm
index dc6b18e8..fc5da234 100644
--- a/elm/UList/DateEdit.elm
+++ b/elm/UList/DateEdit.elm
@@ -8,7 +8,7 @@ import Process
import Browser
import Lib.Api as Api
import Gen.Api as GApi
-import Gen.DateEdit as GDE
+import Gen.UListDateEdit as GDE
main : Program GDE.Send Model Msg
diff --git a/elm/UList/LabelEdit.elm b/elm/UList/LabelEdit.elm
index ce65e414..28e4b154 100644
--- a/elm/UList/LabelEdit.elm
+++ b/elm/UList/LabelEdit.elm
@@ -13,7 +13,7 @@ import Dict exposing (Dict)
import Lib.Html exposing (..)
import Lib.Api as Api
import Gen.Api as GApi
-import Gen.LabelEdit as GLE
+import Gen.UListLabelEdit as GLE
main : Program GLE.Recv Model Msg
diff --git a/elm/UList/ManageLabels.elm b/elm/UList/ManageLabels.elm
index 755c4e08..4cb82777 100644
--- a/elm/UList/ManageLabels.elm
+++ b/elm/UList/ManageLabels.elm
@@ -10,7 +10,7 @@ import Lib.Html exposing (..)
import Lib.Util exposing (..)
import Lib.Api as Api
import Gen.Api as GApi
-import Gen.ManageLabels as GML
+import Gen.UListManageLabels as GML
main : Program GML.Send Model Msg
diff --git a/elm/UList/Opt.elm b/elm/UList/Opt.elm
index edbdf187..916e723f 100644
--- a/elm/UList/Opt.elm
+++ b/elm/UList/Opt.elm
@@ -12,9 +12,10 @@ import Lib.Api as Api
import Gen.Types as T
import Gen.Api as GApi
import Gen.UListVNOpt as GVO
+import Gen.UListVNNotes as GVN
import Gen.UListDel as GDE
-main : Program GVO.Send Model Msg
+main : Program GVO.Recv Model Msg
main = Browser.element
{ init = \f -> (init f, Cmd.none)
, subscriptions = always Sub.none
@@ -23,24 +24,34 @@ main = Browser.element
}
port ulistVNDeleted : Bool -> Cmd msg
+port ulistNotesChanged : String -> Cmd msg
type alias Model =
- { flags : GVO.Send
- , del : Bool
- , delState : Api.State
+ { flags : GVO.Recv
+ , del : Bool
+ , delState : Api.State
+ , notes : String
+ , notesRev : Int
+ , notesState : Api.State
}
-init : GVO.Send -> Model
+init : GVO.Recv -> Model
init f =
- { flags = f
- , del = False
- , delState = Api.Normal
+ { flags = f
+ , del = False
+ , delState = Api.Normal
+ , notes = f.notes
+ , notesRev = 0
+ , notesState = Api.Normal
}
type Msg
= Del Bool
| Delete
| Deleted GApi.Response
+ | Notes String
+ | NotesSave Int
+ | NotesSaved Int GApi.Response
update : Msg -> Model -> (Model, Cmd Msg)
@@ -51,6 +62,19 @@ update msg model =
Deleted GApi.Success -> (model, ulistVNDeleted True)
Deleted e -> ({ model | delState = Api.Error e }, Cmd.none)
+ Notes s -> ({ model | notes = s, notesRev = model.notesRev + 1 }, Task.perform (\_ -> NotesSave (model.notesRev+1)) <| Process.sleep 1000)
+ NotesSave rev ->
+ if rev /= model.notesRev || model.notes == model.flags.notes
+ then (model, Cmd.none)
+ else ({ model | notesState = Api.Loading }, Api.post "/u/ulist/setnote.json" (GVN.encode { uid = model.flags.uid, vid = model.flags.vid, notes = model.notes }) (NotesSaved rev))
+ NotesSaved rev GApi.Success ->
+ let f = model.flags
+ nf = { f | notes = model.notes }
+ in if model.notesRev /= rev
+ then (model, Cmd.none)
+ else ({model | flags = nf, notesState = Api.Normal }, ulistNotesChanged model.notes)
+ NotesSaved _ e -> ({ model | notesState = Api.Error e }, Cmd.none)
+
view : Model -> Html Msg
view model =
@@ -58,10 +82,14 @@ view model =
opt =
[ tr []
[ td [ colspan 5 ]
- [ textarea ([ placeholder "Notes", rows 2, cols 100 ] ++ GVO.valNotes) [ text model.flags.notes ]
+ [ textarea ([ placeholder "Notes", rows 2, cols 80, onInput Notes, onBlur (NotesSave model.notesRev) ] ++ GVN.valNotes) [ text model.notes ]
, div [ ]
- [ div [ class "spinner invisible" ] []
- , br_ 2
+ [ div [ class "spinner", classList [("invisible", model.notesState /= Api.Loading)] ] []
+ , br [] []
+ , case model.notesState of
+ Api.Error e -> b [ class "standout" ] [ text <| Api.showResponse e ]
+ _ -> text ""
+ , br [] []
, a [ href "#", onClickD (Del True) ] [ text "Remove VN" ]
]
]
diff --git a/elm/UList/Opt.js b/elm/UList/Opt.js
index 6a4f126f..41bd1ce2 100644
--- a/elm/UList/Opt.js
+++ b/elm/UList/Opt.js
@@ -13,4 +13,9 @@ Elm.UList.Opt.init = function(opt) {
for(var i=0; i<rows.length; i++)
rows[i].classList.toggle('odd', Math.floor(i/2) % 2 == 0);
});
+
+ app.ports.ulistNotesChanged.subscribe(function(n) {
+ document.getElementById('ulist_notes_'+opt.flags.vid).innerText = n;
+ document.getElementById('ulist_noteflag_'+opt.flags.vid).classList.toggle('blurred', n.length == 0);
+ });
};
diff --git a/elm/UList/VoteEdit.elm b/elm/UList/VoteEdit.elm
index 8007d648..380cb6c8 100644
--- a/elm/UList/VoteEdit.elm
+++ b/elm/UList/VoteEdit.elm
@@ -10,7 +10,7 @@ import Lib.Html exposing (..)
import Lib.Api as Api
import Lib.Ffi as Ffi
import Gen.Api as GApi
-import Gen.VoteEdit as GVE
+import Gen.UListVoteEdit as GVE
main : Program GVE.Send Model Msg
diff --git a/lib/VNWeb/User/Lists.pm b/lib/VNWeb/User/Lists.pm
index fb09a8e4..7c8237c5 100644
--- a/lib/VNWeb/User/Lists.pm
+++ b/lib/VNWeb/User/Lists.pm
@@ -2,6 +2,8 @@ package VNWeb::User::Lists;
use VNWeb::Prelude;
+
+
my $LABELS = form_compile any => {
uid => { id => 1 },
labels => { aoh => {
@@ -13,7 +15,60 @@ my $LABELS = form_compile any => {
} }
};
-elm_form 'ManageLabels', undef, $LABELS;
+elm_form 'UListManageLabels', undef, $LABELS;
+
+json_api qr{/u/ulist/labels.json}, $LABELS, sub {
+ my($uid, $labels) = ($_[0]{uid}, $_[0]{labels});
+ return elm_Unauth if !auth || auth->uid != $uid;
+
+ # Insert new labels
+ my @new = grep $_->{id} < 0 && !$_->{delete}, @$labels;
+ # Subquery to get the lowest unused id
+ my $newid = sql '(
+ SELECT min(x.n)
+ FROM generate_series(10,
+ greatest((SELECT max(id)+1 from ulist_labels ul WHERE ul.uid =', \$uid, '), 10)
+ ) x(n)
+ WHERE NOT EXISTS(SELECT 1 FROM ulist_labels ul WHERE ul.uid =', \$uid, 'AND ul.id = x.n)
+ )';
+ tuwf->dbExeci(
+ 'INSERT INTO ulist_labels (id, uid, label, private)
+ VALUES (', sql_comma($newid, \$uid, \$_->{label}, \$_->{private}), ')'
+ ) for @new;
+
+ # Update private flag
+ tuwf->dbExeci(
+ 'UPDATE ulist_labels SET private =', \$_->{private},
+ 'WHERE uid =', \$uid, 'AND id =', \$_->{id}, 'AND private <>', \$_->{private}
+ ) for grep $_->{id} > 0 && !$_->{delete}, @$labels;
+
+ # Update label
+ tuwf->dbExeci(
+ 'UPDATE ulist_labels SET label =', \$_->{label},
+ 'WHERE uid =', \$uid, 'AND id =', \$_->{id}, 'AND label <>', \$_->{label}
+ ) for grep $_->{id} >= 10 && !$_->{delete}, @$labels;
+
+ # Delete labels
+ my @delete = grep $_->{id} >= 10 && $_->{delete}, @$labels;
+ my @delete_lblonly = map $_->{id}, grep $_->{delete} == 1, @delete;
+ my @delete_empty = map $_->{id}, grep $_->{delete} == 2, @delete;
+ my @delete_all = map $_->{id}, grep $_->{delete} == 3, @delete;
+
+ # delete vns with: (a label in option 3) OR ((a label in option 2) AND (no labels other than in option 1 or 2))
+ my @where =
+ @delete_all ? sql('vid IN(SELECT vid FROM ulist_vns_labels WHERE uid =', \$uid, 'AND lbl IN', \@delete_all, ')') : (),
+ @delete_empty ? sql(
+ 'vid IN(SELECT vid FROM ulist_vns_labels WHERE uid =', \$uid, 'AND lbl IN', \@delete_empty, ')',
+ 'AND NOT EXISTS(SELECT 1 FROM ulist_vns_labels WHERE uid =', \$uid, 'AND lbl NOT IN(', [ @delete_lblonly, @delete_empty ], '))'
+ ) : ();
+ tuwf->dbExeci('DELETE FROM ulist_vns WHERE uid =', \$uid, 'AND (', sql_or(@where), ')') if @where;
+
+ # (This will also delete all relevant vn<->label rows from ulist_vns_labels)
+ tuwf->dbExeci('DELETE FROM ulist_labels WHERE uid =', \$uid, 'AND id IN', [ map $_->{id}, @delete ]) if @delete;
+
+ elm_Success
+};
+
@@ -23,7 +78,20 @@ my $VNVOTE = form_compile any => {
vote => { vnvote => 1 },
};
-elm_form 'VoteEdit', undef, $VNVOTE;
+elm_form 'UListVoteEdit', undef, $VNVOTE;
+
+json_api qr{/u/ulist/setvote.json}, $VNVOTE, sub {
+ my($data) = @_;
+ return elm_Unauth if !auth || auth->uid != $data->{uid};
+ tuwf->dbExeci(
+ 'UPDATE ulist_vns
+ SET vote =', \$data->{vote},
+ ', vote_date = CASE WHEN', \$data->{vote}, '::smallint IS NULL THEN NULL WHEN vote IS NULL THEN NOW() ELSE vote_date END
+ WHERE uid =', \$data->{uid}, 'AND vid =', \$data->{vid}
+ );
+ elm_Success
+};
+
@@ -39,7 +107,26 @@ my $VNLABELS = {
my $VNLABELS_OUT = form_compile out => $VNLABELS;
my $VNLABELS_IN = form_compile in => $VNLABELS;
-elm_form 'LabelEdit', $VNLABELS_OUT, $VNLABELS_IN;
+elm_form 'UListLabelEdit', $VNLABELS_OUT, $VNLABELS_IN;
+
+json_api qr{/u/ulist/setlabel.json}, $VNLABELS_IN, sub {
+ my($data) = @_;
+ return elm_Unauth if !auth || auth->uid != $data->{uid};
+ die "Attempt to set vote label" if $data->{label} == 7;
+
+ tuwf->dbExeci(
+ 'DELETE FROM ulist_vns_labels
+ WHERE uid =', \$data->{uid}, 'AND vid =', \$data->{vid}, 'AND lbl =', \$data->{label}
+ ) if !$data->{applied};
+ tuwf->dbExeci(
+ 'INSERT INTO ulist_vns_labels (uid, vid, lbl)
+ VALUES (', sql_comma(\$data->{uid}, \$data->{vid}, \$data->{label}), ')
+ ON CONFLICT (uid, vid, lbl) DO NOTHING'
+ ) if $data->{applied};
+
+ elm_Success
+};
+
@@ -50,7 +137,18 @@ my $VNDATE = form_compile any => {
start => { anybool => 1 }, # Field selection, started/finished
};
-elm_form 'DateEdit', undef, $VNDATE;
+elm_form 'UListDateEdit', undef, $VNDATE;
+
+json_api qr{/u/ulist/setdate.json}, $VNDATE, sub {
+ my($data) = @_;
+ return elm_Unauth if !auth || auth->uid != $data->{uid};
+ tuwf->dbExeci(
+ 'UPDATE ulist_vns SET lastmod = NOW(), ', $data->{start} ? 'started' : 'finished', '=', \($data->{date}||undef),
+ 'WHERE uid =', \$data->{uid}, 'AND vid =', \$data->{vid}
+ );
+ elm_Success
+};
+
@@ -58,7 +156,7 @@ my $VNOPT = form_compile any => {
own => { anybool => 1 },
uid => { id => 1 },
vid => { id => 1 },
- notes => { required => 0, default => '', maxlength => 2000 },
+ notes => {},
rels => { aoh => {
id => { id => 1 },
title => {},
@@ -70,7 +168,29 @@ my $VNOPT = form_compile any => {
} },
};
-elm_form 'UListVNOpt', undef, $VNOPT;
+elm_form 'UListVNOpt', $VNOPT, undef;
+
+
+
+
+my $VNNOTES = form_compile any => {
+ uid => { id => 1 },
+ vid => { id => 1 },
+ notes => { required => 0, default => '', maxlength => 2000 },
+};
+
+elm_form 'UListVNNotes', undef, $VNNOTES;
+
+json_api qr{/u/ulist/setnote.json}, $VNNOTES, sub {
+ my($data) = @_;
+ return elm_Unauth if !auth || auth->uid != $data->{uid};
+ tuwf->dbExeci(
+ 'UPDATE ulist_vns SET lastmod = NOW(), notes = ', \$data->{notes},
+ 'WHERE uid =', \$data->{uid}, 'AND vid =', \$data->{vid}
+ );
+ elm_Success
+};
+
@@ -81,6 +201,14 @@ my $VNDEL = form_compile any => {
elm_form 'UListDel', undef, $VNDEL;
+json_api qr{/u/ulist/del.json}, $VNDEL, sub {
+ my($data) = @_;
+ return elm_Unauth if !auth || auth->uid != $data->{uid};
+ tuwf->dbExeci('DELETE FROM ulist_vns WHERE uid =', \$data->{uid}, 'AND vid =', \$data->{vid});
+ elm_Success
+};
+
+
# TODO: Filters to find unlabeled VNs or VNs with/without notes?
@@ -147,7 +275,7 @@ sub vn_ {
if($total && $obtained == $total) { b_ class => 'done', $txt }
elsif($obtained < $total) { b_ class => 'todo', $txt }
else { txt_ $txt }
- span_ $v->{notes} ? () : (style => 'opacity: 0.5'), ' 💬';
+ span_ id => 'ulist_noteflag_'.$v->{id}, mkclass(blurred => !$v->{notes}), ' 💬';
if($own) {
my $public = List::Util::any { $labels{$_->{id}} && !$_->{private} } @$labels;
my $publicLabel = List::Util::any { $_->{id} != 7 && $labels{$_->{id}} && !$_->{private} } @$labels;
@@ -161,7 +289,7 @@ sub vn_ {
};
td_ class => 'tc2', sub {
a_ href => "/v$v->{id}", title => $v->{original}||$v->{title}, shorten $v->{title}, 70;
- b_ class => 'grayedout', $v->{notes} if $v->{notes};
+ b_ class => 'grayedout', id => 'ulist_notes_'.$v->{id}, $v->{notes} if $v->{notes} || $own;
};
td_ class => 'tc3', sub {
my @l = grep $labels{$_->{id}} && $_->{id} != 7, @$labels;
@@ -294,109 +422,4 @@ TUWF::get qr{/$RE{uid}/ulist}, sub {
};
};
-
-json_api qr{/u/ulist/labels.json}, $LABELS, sub {
- my($uid, $labels) = ($_[0]{uid}, $_[0]{labels});
- return elm_Unauth if !auth || auth->uid != $uid;
-
- # Insert new labels
- my @new = grep $_->{id} < 0 && !$_->{delete}, @$labels;
- # Subquery to get the lowest unused id
- my $newid = sql '(
- SELECT min(x.n)
- FROM generate_series(10,
- greatest((SELECT max(id)+1 from ulist_labels ul WHERE ul.uid =', \$uid, '), 10)
- ) x(n)
- WHERE NOT EXISTS(SELECT 1 FROM ulist_labels ul WHERE ul.uid =', \$uid, 'AND ul.id = x.n)
- )';
- tuwf->dbExeci(
- 'INSERT INTO ulist_labels (id, uid, label, private)
- VALUES (', sql_comma($newid, \$uid, \$_->{label}, \$_->{private}), ')'
- ) for @new;
-
- # Update private flag
- tuwf->dbExeci(
- 'UPDATE ulist_labels SET private =', \$_->{private},
- 'WHERE uid =', \$uid, 'AND id =', \$_->{id}, 'AND private <>', \$_->{private}
- ) for grep $_->{id} > 0 && !$_->{delete}, @$labels;
-
- # Update label
- tuwf->dbExeci(
- 'UPDATE ulist_labels SET label =', \$_->{label},
- 'WHERE uid =', \$uid, 'AND id =', \$_->{id}, 'AND label <>', \$_->{label}
- ) for grep $_->{id} >= 10 && !$_->{delete}, @$labels;
-
- # Delete labels
- my @delete = grep $_->{id} >= 10 && $_->{delete}, @$labels;
- my @delete_lblonly = map $_->{id}, grep $_->{delete} == 1, @delete;
- my @delete_empty = map $_->{id}, grep $_->{delete} == 2, @delete;
- my @delete_all = map $_->{id}, grep $_->{delete} == 3, @delete;
-
- # delete vns with: (a label in option 3) OR ((a label in option 2) AND (no labels other than in option 1 or 2))
- my @where =
- @delete_all ? sql('vid IN(SELECT vid FROM ulist_vns_labels WHERE uid =', \$uid, 'AND lbl IN', \@delete_all, ')') : (),
- @delete_empty ? sql(
- 'vid IN(SELECT vid FROM ulist_vns_labels WHERE uid =', \$uid, 'AND lbl IN', \@delete_empty, ')',
- 'AND NOT EXISTS(SELECT 1 FROM ulist_vns_labels WHERE uid =', \$uid, 'AND lbl NOT IN(', [ @delete_lblonly, @delete_empty ], '))'
- ) : ();
- tuwf->dbExeci('DELETE FROM ulist_vns WHERE uid =', \$uid, 'AND (', sql_or(@where), ')') if @where;
-
- # (This will also delete all relevant vn<->label rows from ulist_vns_labels)
- tuwf->dbExeci('DELETE FROM ulist_labels WHERE uid =', \$uid, 'AND id IN', [ map $_->{id}, @delete ]) if @delete;
-
- elm_Success
-};
-
-
-# XXX: Doesn't add the VN to the list if it isn't in there, yet.
-json_api qr{/u/ulist/setvote.json}, $VNVOTE, sub {
- my($data) = @_;
- return elm_Unauth if !auth || auth->uid != $data->{uid};
- tuwf->dbExeci(
- 'UPDATE ulist_vns
- SET vote =', \$data->{vote},
- ', vote_date = CASE WHEN', \$data->{vote}, '::smallint IS NULL THEN NULL WHEN vote IS NULL THEN NOW() ELSE vote_date END
- WHERE uid =', \$data->{uid}, 'AND vid =', \$data->{vid}
- );
- elm_Success
-};
-
-
-json_api qr{/u/ulist/setlabel.json}, $VNLABELS_IN, sub {
- my($data) = @_;
- return elm_Unauth if !auth || auth->uid != $data->{uid};
- die "Attempt to set vote label" if $data->{label} == 7;
-
- tuwf->dbExeci(
- 'DELETE FROM ulist_vns_labels
- WHERE uid =', \$data->{uid}, 'AND vid =', \$data->{vid}, 'AND lbl =', \$data->{label}
- ) if !$data->{applied};
- tuwf->dbExeci(
- 'INSERT INTO ulist_vns_labels (uid, vid, lbl)
- VALUES (', sql_comma(\$data->{uid}, \$data->{vid}, \$data->{label}), ')
- ON CONFLICT (uid, vid, lbl) DO NOTHING'
- ) if $data->{applied};
-
- elm_Success
-};
-
-
-json_api qr{/u/ulist/setdate.json}, $VNDATE, sub {
- my($data) = @_;
- return elm_Unauth if !auth || auth->uid != $data->{uid};
- tuwf->dbExeci(
- 'UPDATE ulist_vns SET lastmod = NOW(), ', $data->{start} ? 'started' : 'finished', '=', \($data->{date}||undef),
- 'WHERE uid =', \$data->{uid}, 'AND vid =', \$data->{vid}
- );
- elm_Success
-};
-
-
-json_api qr{/u/ulist/del.json}, $VNDEL, sub {
- my($data) = @_;
- return elm_Unauth if !auth || auth->uid != $data->{uid};
- tuwf->dbExeci('DELETE FROM ulist_vns WHERE uid =', \$data->{uid}, 'AND vid =', \$data->{vid});
- elm_Success
-};
-
1;