From 444990e4d924903d28b3f33c53f7df37c23b3f32 Mon Sep 17 00:00:00 2001 From: Yorhel Date: Tue, 24 Dec 2019 10:51:27 +0100 Subject: ulist: Add list management widget on VN pages Minimal version. It reuses the LabelEdit and VoteEdit widgets, but doesn't allow setting a note or start/finish date at the moment. VN pages now have both v2rw.js and the old vndb.js; Those two scripts aren't meant to be used together on a single page, so I'm hoping this will be temporary. I removed the 'checkall' handling from vndb.js as that might conflict. It's only used on the old list pages anyway. --- data/js/misc.js | 15 ------ data/style.css | 8 +-- elm/UList/LabelEdit.elm | 8 +-- elm/UList/VNPage.elm | 127 ++++++++++++++++++++++++++++++++++++++++++++ elm/UList/VoteEdit.elm | 3 +- lib/VNDB/Handler/VNPage.pm | 53 +++++++----------- lib/VNDB/Util/LayoutHTML.pm | 2 + lib/VNWeb/HTML.pm | 17 +++--- lib/VNWeb/User/Lists.pm | 19 +++++++ 9 files changed, 188 insertions(+), 64 deletions(-) create mode 100644 elm/UList/VNPage.elm diff --git a/data/js/misc.js b/data/js/misc.js index e5b135d0..fd042524 100644 --- a/data/js/misc.js +++ b/data/js/misc.js @@ -248,21 +248,6 @@ if(byId('not') && byId('vns')) })(); -// "check all" checkbox -(function(){ - function set() { - var l = byName('input'); - for(var i=0; i table { float: left; width: 500px; } +div.vndetails > table td.key { width: 90px; } +div.vndetails > table dt { float: left; font-style: italic; } +div.vndetails > table dd { margin-left: 90px; } 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; } diff --git a/elm/UList/LabelEdit.elm b/elm/UList/LabelEdit.elm index 28f2cc68..471f6eb6 100644 --- a/elm/UList/LabelEdit.elm +++ b/elm/UList/LabelEdit.elm @@ -1,4 +1,4 @@ -port module UList.LabelEdit exposing (main) +port module UList.LabelEdit exposing (main, init, update, view, isPublic, Model, Msg) import Html exposing (..) import Html.Attributes exposing (..) @@ -50,6 +50,9 @@ type Msg | Saved Int Bool GApi.Response +isPublic : Model -> Bool +isPublic model = List.any (\lb -> lb.id /= 7 && not lb.private && Set.member lb.id model.sel) model.labels + update : Msg -> Model -> (Model, Cmd Msg) update msg model = case msg of @@ -65,8 +68,7 @@ update msg model = Saved l b (GApi.Success) -> let nmodel = { model | sel = if b then Set.insert l model.sel else Set.remove l model.sel, state = Dict.remove l model.state } - public = List.any (\lb -> lb.id /= 7 && not lb.private && Set.member lb.id nmodel.sel) nmodel.labels - in (nmodel, ulistLabelChanged public) + in (nmodel, ulistLabelChanged (isPublic nmodel)) Saved l b e -> ({ model | state = Dict.insert l (Api.Error e) model.state }, Cmd.none) diff --git a/elm/UList/VNPage.elm b/elm/UList/VNPage.elm new file mode 100644 index 00000000..03fbaf6b --- /dev/null +++ b/elm/UList/VNPage.elm @@ -0,0 +1,127 @@ +module UList.VNPage exposing (main) + +import Html exposing (..) +import Html.Attributes exposing (..) +import Html.Events exposing (..) +import Browser +import Lib.Html exposing (..) +import Lib.Api as Api +import Lib.DropDown as DD +import Gen.Api as GApi +import Gen.UListDel as GDE +import Gen.UListAdd as GAD +import UList.LabelEdit as LE +import UList.VoteEdit as VE + +-- We don't have a Gen.* module for this (yet), so define these manually +type alias RecvLabels = + { id : Int + , label : String + , private : Bool + } + +type alias Recv = + { uid : Int + , vid : Int + , onlist : Bool + , canvote : Bool + , vote : Maybe String + , labels : List RecvLabels + , selected : List Int + } + + +main : Program Recv Model Msg +main = Browser.element + { init = \f -> (init f, Cmd.none) + , subscriptions = \model -> Sub.map Labels (DD.sub model.labels.dd) + , view = view + , update = update + } + +type alias Model = + { flags : Recv + , onlist : Bool + , del : Bool + , state : Api.State -- For adding/deleting; Vote and label edit widgets have their own state + , labels : LE.Model + , vote : VE.Model + } + +init : Recv -> Model +init f = + { flags = f + , onlist = f.onlist + , del = False + , state = Api.Normal + , labels = LE.init { uid = f.uid, vid = f.vid, labels = f.labels, selected = f.selected } + , vote = VE.init { uid = f.uid, vid = f.vid, vote = f.vote } + } + +type Msg + = Add + | Added GApi.Response + | Labels LE.Msg + | Vote VE.Msg + | Del Bool + | Delete + | Deleted GApi.Response + + +update : Msg -> Model -> (Model, Cmd Msg) +update msg model = + case msg of + Labels m -> let (nm, cmd) = LE.update m model.labels in ({ model | labels = nm}, Cmd.map Labels cmd) + Vote m -> let (nm, cmd) = VE.update m model.vote in ({ model | vote = nm}, Cmd.map Vote cmd) + + Add -> ({ model | state = Api.Loading }, Api.post "/u/ulist/add.json" (GAD.encode { uid = model.flags.uid, vid = model.flags.vid }) Added) + Added GApi.Success -> ({ model | state = Api.Normal, onlist = True }, Cmd.none) + Added e -> ({ model | state = Api.Error e }, Cmd.none) + + Del b -> ({ model | del = b }, Cmd.none) + Delete -> ({ model | state = Api.Loading }, Api.post "/u/ulist/del.json" (GDE.encode { uid = model.flags.uid, vid = model.flags.vid }) Deleted) + Deleted GApi.Success -> ({ model | state = Api.Normal, onlist = False, del = False }, Cmd.none) + Deleted e -> ({ model | state = Api.Error e }, Cmd.none) + + +isPublic : Model -> Bool +isPublic model = + LE.isPublic model.labels + || (model.vote.text /= "" && model.vote.text /= "-" && List.any (\l -> l.id == 7 && not l.private) model.labels.labels) + + +view : Model -> Html Msg +view model = + case model.state of + Api.Loading -> div [ class "spinner" ] [] + Api.Error e -> b [ class "standout" ] [ text <| Api.showResponse e ] + Api.Normal -> + if not model.onlist + then a [ href "#", onClickD Add ] [ text "Add to list" ] + else if model.del + then + span [] + [ text "Sure you want to remove this VN from your list? " + , a [ onClickD Delete ] [ text "Yes" ] + , text " | " + , a [ onClickD (Del False) ] [ text "Cancel" ] + ] + else + table [ style "width" "100%" ] + [ tr [ class "nostripe" ] + [ td [ style "width" "70px" ] [ text "Labels:" ] + , td [] [ Html.map Labels (LE.view model.labels) ] + ] + , if model.flags.canvote || (Maybe.withDefault "-" model.flags.vote /= "-") + then tr [ class "nostripe" ] + [ td [] [ text "Vote:" ] + , td [ class "compact stealth" ] [ Html.map Vote (VE.view model.vote) ] + ] + else text "" + , tr [ class "nostripe" ] + [ td [ colspan 2 ] + [ span [ classList [("invisible", not (isPublic model))], title "This visual novel is on your public list" ] [ text "👁 " ] + , a [ onClickD (Del True) ] [ text "Remove from list" ] + ] + ] + ] diff --git a/elm/UList/VoteEdit.elm b/elm/UList/VoteEdit.elm index 380cb6c8..058eb5aa 100644 --- a/elm/UList/VoteEdit.elm +++ b/elm/UList/VoteEdit.elm @@ -1,4 +1,4 @@ -port module UList.VoteEdit exposing (main) +port module UList.VoteEdit exposing (main, init, update, view, Model, Msg) import Html exposing (..) import Html.Attributes exposing (..) @@ -85,6 +85,7 @@ view model = , onBlur Save , onFocus Focus , placeholder "7.5" + , style "width" "55px" , custom "keydown" -- Grab enter key <| JD.andThen (\c -> if c == "Enter" then JD.succeed { preventDefault = True, stopPropagation = True, message = Save } else JD.fail "") <| JD.field "key" JD.string diff --git a/lib/VNDB/Handler/VNPage.pm b/lib/VNDB/Handler/VNPage.pm index 87c9244c..3556476c 100644 --- a/lib/VNDB/Handler/VNPage.pm +++ b/lib/VNDB/Handler/VNPage.pm @@ -531,7 +531,7 @@ sub page { _screenshots($self, $v, $r) if @{$v->{screenshots}}; } - $self->htmlFooter; + $self->htmlFooter(v2rwjs => $self->authInfo->{id}); } @@ -715,45 +715,28 @@ sub _useroptions { # Voting option is hidden if nothing has been released yet my $minreleased = min grep $_, map $_->{released}, @$r; - my $canvote = $minreleased && $minreleased < strftime '%Y%m%d', gmtime; - my $vote = $self->dbVoteGet(uid => $self->authInfo->{id}, vid => $v->{id})->[0]; - my $list = $self->dbVNListGet(uid => $self->authInfo->{id}, vid => $v->{id})->[0]; - my $wish = $self->dbWishListGet(uid => $self->authInfo->{id}, vid => $v->{id})->[0]; + my $labels = tuwf->dbAlli( + 'SELECT l.id, l.label, l.private, uvl.vid IS NOT NULL as assigned + FROM ulist_labels l + LEFT JOIN ulist_vns_labels uvl ON uvl.uid = l.uid AND uvl.lbl = l.id AND uvl.vid =', \$v->{id}, ' + WHERE l.uid =', \$self->authInfo->{id}, ' + ORDER BY CASE WHEN l.id < 10 THEN l.id ELSE 10 END, l.label' + ); + my $lst = tuwf->dbRowi('SELECT vid, vote FROM ulist_vns WHERE uid =', \$self->authInfo->{id}, 'AND vid =', \$v->{id}); Tr; td 'User options'; td; - if($vote || ($canvote && !$wish)) { - Select id => 'votesel', name => $self->authGetCode("/v$v->{id}/vote"); - option value => -3, $vote ? 'your vote: '.fmtvote($vote->{vote}) : 'not voted yet'; - optgroup label => $vote ? 'Change vote' : 'Vote'; - option value => $_, "$_ (".fmtrating($_).')' for (reverse 1..10); - option value => -2, 'Other'; - end; - option value => -1, 'revoke' if $vote; - end; - br; - } - - Select id => 'listsel', name => $self->authGetCode("/v$v->{id}/list"); - option $list ? "VN list: $VNLIST_STATUS{$list->{status}}" : 'not on your VN list'; - optgroup label => $list ? 'Change status' : 'Add to VN list'; - option value => $_, $VNLIST_STATUS{$_} for (keys %VNLIST_STATUS); - end; - option value => -1, 'remove from VN list' if $list; - end; - br; - - if(!$vote || $wish) { - Select id => 'wishsel', name => $self->authGetCode("/v$v->{id}/wish"); - option $wish ? "wishlist: $WISHLIST_STATUS{$wish->{wstat}}" : 'not on your wishlist'; - optgroup label => $wish ? 'Change status' : 'Add to wishlist'; - option value => $_, $WISHLIST_STATUS{$_} for (keys %WISHLIST_STATUS); - end; - option value => -1, 'remove from wishlist' if $wish; - end; - } + VNWeb::HTML::elm_('UList.VNPage', undef, { + uid => 1*$self->authInfo->{id}, + vid => 1*$v->{id}, + onlist => $lst->{vid}?\1:\0, + canvote => $minreleased && $minreleased < strftime('%Y%m%d', gmtime) ? \1 : \0, + vote => fmtvote($lst->{vote}).'', + labels => [ map +{ id => 1*$_->{id}, label => $_->{label}, private => $_->{private}?\1:\0 }, @$labels ], + selected => [ map $_->{id}, grep $_->{assigned}, @$labels ], + }); end; end 'tr'; } diff --git a/lib/VNDB/Util/LayoutHTML.pm b/lib/VNDB/Util/LayoutHTML.pm index 1b6ce1de..6bafbeda 100644 --- a/lib/VNDB/Util/LayoutHTML.pm +++ b/lib/VNDB/Util/LayoutHTML.pm @@ -11,6 +11,7 @@ our @EXPORT = qw|htmlHeader htmlFooter|; sub htmlHeader { # %options->{ title, noindex, search, feeds, metadata } my($self, %o) = @_; + %VNWeb::HTML::pagevars = (); $o{og} = $o{metadata} ? +{ map +(s/og://r, $o{metadata}{$_}), keys $o{metadata}->%* } : undef; $o{index} = !$o{noindex}; @@ -34,6 +35,7 @@ sub htmlFooter { # %options => { pref_code => 1 } noscript id => 'pref_code', title => $self->authGetCode('/xml/prefs.xml'), '' if $o{pref_code} && $self->authInfo->{id}; script type => 'text/javascript', src => $self->{url_static}.'/f/vndb.js?'.$self->{version}, ''; + VNWeb::HTML::v2rwjs_() if $o{v2rwjs}; end 'body'; end 'html'; } diff --git a/lib/VNWeb/HTML.pm b/lib/VNWeb/HTML.pm index 9dad1902..1d6731c2 100644 --- a/lib/VNWeb/HTML.pm +++ b/lib/VNWeb/HTML.pm @@ -36,7 +36,7 @@ our @EXPORT = qw/ # Encoded as JSON and appended to the end of the page, to be read by pagevars.js. -my %pagevars; +our %pagevars; # Ugly hack to move rendering down below the float object. @@ -406,6 +406,15 @@ sub _hidden_msg_ { } +sub v2rwjs_ { # Also used by VNDB::Util::LayoutHTML. + script_ type => 'application/json', id => 'pagevars', sub { + # Escaping rules for a JSON