summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--data/js/misc.js15
-rw-r--r--data/style.css8
-rw-r--r--elm/UList/LabelEdit.elm8
-rw-r--r--elm/UList/VNPage.elm127
-rw-r--r--elm/UList/VoteEdit.elm3
-rw-r--r--lib/VNDB/Handler/VNPage.pm53
-rw-r--r--lib/VNDB/Util/LayoutHTML.pm2
-rw-r--r--lib/VNWeb/HTML.pm17
-rw-r--r--lib/VNWeb/User/Lists.pm19
9 files changed, 188 insertions, 64 deletions
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<l.length; i++)
- if(l[i].type == this.type && l[i].name == this.name && !hasClass(l[i], 'hidden'))
- l[i].checked = this.checked;
- }
- var l = byClass('input', 'checkall');
- for(var i=0; i<l.length; i++)
- if(l[i].type == 'checkbox')
- l[i].onclick = set;
-})();
-
-
// search tabs
(function(){
function click() {
diff --git a/data/style.css b/data/style.css
index d9736401..0d7266a0 100644
--- a/data/style.css
+++ b/data/style.css
@@ -418,10 +418,10 @@ div.vnimg p { text-align: center; padding: 0px; margin: 0; }
.vndesc h2 { margin: 5px 0 0 0; }
.vndesc p { padding: 0 0 0 5px; }
p#nsfw_hid { display: block; cursor: pointer; }
-div.vndetails 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 > 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 <script> context are kinda weird, but more efficient than regular xml_escape().
+ lit_(JSON::XS->new->canonical->encode(\%pagevars) =~ s{</}{<\\/}rg =~ s/<!--/<\\u0021--/rg);
+ } if keys %pagevars;
+ script_ type => 'application/javascript', src => config->{url_static}.'/f/v2rw.js?'.config->{version}, '';
+}
+
+
# Options:
# title => $title
# index => 1/0, default 0
@@ -435,11 +444,7 @@ sub framework_ {
$cont->() unless $o{hiddenmsg} && _hidden_msg_ \%o;
div_ id => 'footer', \&_footer_;
};
- script_ type => 'application/json', id => 'pagevars', sub {
- # Escaping rules for a JSON <script> context are kinda weird, but more efficient than regular xml_escape().
- lit_(JSON::XS->new->canonical->encode(\%pagevars) =~ s{</}{<\\/}rg =~ s/<!--/<\\u0021--/rg);
- } if keys %pagevars;
- script_ type => 'application/javascript', src => config->{url_static}.'/f/v2rw.js?'.config->{version}, '';
+ v2rwjs_;
}
}
}
diff --git a/lib/VNWeb/User/Lists.pm b/lib/VNWeb/User/Lists.pm
index 4e5ff7f9..e909fa2e 100644
--- a/lib/VNWeb/User/Lists.pm
+++ b/lib/VNWeb/User/Lists.pm
@@ -227,6 +227,25 @@ json_api qr{/u/ulist/del\.json}, $VNDEL, sub {
+
+my $VNADD = form_compile any => {
+ uid => { id => 1 },
+ vid => { id => 1 },
+};
+
+elm_form 'UListAdd', undef, $VNADD;
+
+json_api qr{/u/ulist/add\.json}, $VNDEL, sub {
+ my($data) = @_;
+ return elm_Unauth if !own $data->{uid};
+ tuwf->dbExeci('INSERT INTO ulist_vns', $data, 'ON CONFLICT (uid, vid) DO NOTHING');
+ updcache $data->{uid};
+ elm_Success
+};
+
+
+
+
my $RSTATUS = form_compile any => {
uid => { id => 1 },
rid => { id => 1 },