summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYorhel <git@yorhel.nl>2019-11-10 14:30:20 +0100
committerYorhel <git@yorhel.nl>2019-11-10 14:34:08 +0100
commitf5ede9e020c47c49567ada2cfa52ca1b926b4c12 (patch)
tree54a5f4fe5c8f26179cfb1ddf6b6a9c08f5cfcc17
parent194d9d43593937d835cca00719a2b0639213ebb3 (diff)
ulist: Add feature to add releases to the list
-rw-r--r--elm/Lib/Api.elm1
-rw-r--r--elm/UList/Opt.elm76
-rw-r--r--lib/VNWeb/Elm.pm8
-rw-r--r--lib/VNWeb/Releases/JS.pm21
-rw-r--r--lib/VNWeb/User/Lists.pm10
5 files changed, 98 insertions, 18 deletions
diff --git a/elm/Lib/Api.elm b/elm/Lib/Api.elm
index 283cbe3c..1e028904 100644
--- a/elm/Lib/Api.elm
+++ b/elm/Lib/Api.elm
@@ -42,6 +42,7 @@ showResponse res =
DoubleIP -> "You can only register one account from the same IP within 24 hours."
BadCurPass -> "Current password is invalid."
MailChange -> unexp
+ Releases _ -> unexp
expectResponse : (Response -> msg) -> Http.Expect msg
diff --git a/elm/UList/Opt.elm b/elm/UList/Opt.elm
index 46ac6acd..5a8aaa1e 100644
--- a/elm/UList/Opt.elm
+++ b/elm/UList/Opt.elm
@@ -3,10 +3,12 @@ port module UList.Opt exposing (main)
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (..)
+import Json.Encode as JE
import Task
import Process
import Browser
import Date
+import Dict exposing (Dict)
import Lib.Util exposing (..)
import Lib.Html exposing (..)
import Lib.Api as Api
@@ -32,12 +34,20 @@ port ulistNotesChanged : String -> Cmd msg
port ulistRelChanged : (Int, Int) -> Cmd msg
type alias Rel =
- { nfo : GVO.RecvRels
+ { id : Int
, status : Int -- Special value -1 means 'delete this release from my list'
, state : Api.State
, dd : DD.Config Msg
}
+newrel : Int -> Int -> Int -> Rel
+newrel rid vid st =
+ { id = rid
+ , status = st
+ , state = Api.Normal
+ , dd = DD.init ("ulist_reldd" ++ String.fromInt vid ++ "_" ++ String.fromInt rid) (RelOpen rid)
+ }
+
type alias Model =
{ flags : GVO.Recv
, today : Date.Date
@@ -47,6 +57,9 @@ type alias Model =
, notesRev : Int
, notesState : Api.State
, rels : List Rel
+ , relNfo : Dict Int GApi.ApiReleases
+ , relOptions : Maybe (List (Int, String))
+ , relState : Api.State
}
init : GVO.Recv -> Model
@@ -58,10 +71,10 @@ init f =
, notes = f.notes
, notesRev = 0
, notesState = Api.Normal
- , rels = List.map (\r ->
- { nfo = r, status = r.status, state = Api.Normal
- , dd = DD.init ("ulist_reldd" ++ String.fromInt f.vid ++ "_" ++ String.fromInt r.id) (RelOpen r.id)
- } ) f.rels
+ , rels = List.map2 (\st nfo -> newrel nfo.id f.vid st) f.relstatus f.rels
+ , relNfo = Dict.fromList <| List.map (\r -> (r.id, r)) f.rels
+ , relOptions = Nothing
+ , relState = Api.Normal
}
type Msg
@@ -75,10 +88,17 @@ type Msg
| RelOpen Int Bool
| RelSet Int Int Bool
| RelSaved Int Int GApi.Response
+ | RelLoad
+ | RelLoaded GApi.Response
+ | RelAdd Int
modrel : Int -> (Rel -> Rel) -> List Rel -> List Rel
-modrel rid f = List.map (\r -> if r.nfo.id == rid then f r else r)
+modrel rid f = List.map (\r -> if r.id == rid then f r else r)
+
+
+showrel : GApi.ApiReleases -> String
+showrel r = "[" ++ (RDate.format (RDate.expand r.released)) ++ " " ++ (String.join "," r.lang) ++ "] " ++ r.title ++ " (r" ++ String.fromInt r.id ++ ")"
update : Msg -> Model -> (Model, Cmd Msg)
@@ -114,12 +134,26 @@ update msg model =
( { model | rels = modrel rid (\r -> { r | dd = DD.toggle r.dd False, status = st, state = Api.Loading }) model.rels }
, Api.post "/u/ulist/rstatus.json" (GRS.encode { uid = model.flags.uid, rid = rid, status = st }) (RelSaved rid st) )
RelSaved rid st GApi.Success ->
- let nr = if st == -1 then List.filter (\r -> r.nfo.id /= rid) model.rels
+ let nr = if st == -1 then List.filter (\r -> r.id /= rid) model.rels
else modrel rid (\r -> { r | state = Api.Normal }) model.rels
in ( { model | rels = nr }
, ulistRelChanged (List.length <| List.filter (\r -> r.status == 2) nr, List.length nr) )
RelSaved rid _ e -> ({ model | rels = modrel rid (\r -> { r | state = Api.Error e }) model.rels }, Cmd.none)
+ RelLoad ->
+ ( { model | relState = Api.Loading }
+ , Api.post "/r/get.json" (JE.object [("vid", JE.int model.flags.vid)]) RelLoaded )
+ RelLoaded (GApi.Releases rels) ->
+ ( { model
+ | relState = Api.Normal
+ , relNfo = Dict.union (Dict.fromList <| List.map (\r -> (r.id, r)) rels) model.relNfo
+ , relOptions = Just <| List.map (\r -> (r.id, showrel r)) rels
+ }, Cmd.none)
+ RelLoaded e -> ({ model | relState = Api.Error e }, Cmd.none)
+ RelAdd rid ->
+ ( { model | rels = model.rels ++ (if rid == 0 then [] else [newrel rid model.flags.vid 2]) }
+ , Task.perform (RelSet rid 2) <| Task.succeed True)
+
view : Model -> Html Msg
view model =
@@ -141,12 +175,26 @@ view model =
]
, tfoot []
[ tr []
- [ td [ colspan 5 ] [ a [ href "#" ] [ text "Add release" ] ] ]
+ [ td [ colspan 5 ] <|
+ -- TODO: This <select> solution is ugly as hell, a Lib.DropDown-based solution would be nicer.
+ -- Or just throw all releases in the table and use the status field for add stuff.
+ case (model.relOptions, model.relState) of
+ (Just opts, _) -> [ inputSelect "" 0 RelAdd [ style "width" "500px" ]
+ <| (0, "-- add release --") :: List.filter (\(rid,_) -> not <| List.any (\r -> r.id == rid) model.rels) opts ]
+ (_, Api.Normal) -> [ a [ href "#", onClickD RelLoad ] [ text "Add release" ] ]
+ (_, Api.Loading) -> [ span [ class "spinner" ] [], text "Loading releases..." ]
+ (_, Api.Error e) -> [ b [ class "standout" ] [ text <| Api.showResponse e ], text ". ", a [ href "#", onClickD RelLoad ] [ text "Try again" ] ]
+ ]
]
]
rel r =
- let name = "ulist_relstatus" ++ String.fromInt model.flags.vid ++ "_" ++ String.fromInt r.nfo.id ++ "_"
+ case Dict.get r.id model.relNfo of
+ Nothing -> text ""
+ Just nfo -> relnfo r nfo
+
+ relnfo r nfo =
+ let name = "ulist_relstatus" ++ String.fromInt model.flags.vid ++ "_" ++ String.fromInt nfo.id ++ "_"
in
tr []
[ td [ class "tco1" ]
@@ -154,16 +202,16 @@ view model =
<| \_ ->
[ ul [] <| List.map (\(n, status) ->
li [ class "linkradio" ]
- [ inputCheck (name ++ String.fromInt n) (n == r.status) (RelSet r.nfo.id n)
+ [ inputCheck (name ++ String.fromInt n) (n == r.status) (RelSet r.id n)
, label [ for <| name ++ String.fromInt n ] [ text status ]
]
) T.rlistStatus
- ++ [ li [] [ a [ href "#", onClickD (RelSet r.nfo.id -1 True) ] [ text "remove" ] ] ]
+ ++ [ li [] [ a [ href "#", onClickD (RelSet r.id -1 True) ] [ text "remove" ] ] ]
]
]
- , td [ class "tco2" ] [ RDate.display model.today r.nfo.released ]
- , td [ class "tco3" ] <| List.map langIcon r.nfo.lang ++ [ releaseTypeIcon r.nfo.rtype ]
- , td [ class "tco4" ] [ a [ href ("/r"++String.fromInt r.nfo.id), title r.nfo.original ] [ text r.nfo.title ] ]
+ , td [ class "tco2" ] [ RDate.display model.today nfo.released ]
+ , td [ class "tco3" ] <| List.map langIcon nfo.lang ++ [ releaseTypeIcon nfo.rtype ]
+ , td [ class "tco4" ] [ a [ href ("/r"++String.fromInt nfo.id), title nfo.original ] [ text nfo.title ] ]
]
confirm =
diff --git a/lib/VNWeb/Elm.pm b/lib/VNWeb/Elm.pm
index 80487839..6fa710f9 100644
--- a/lib/VNWeb/Elm.pm
+++ b/lib/VNWeb/Elm.pm
@@ -46,6 +46,14 @@ my %apis = (
DoubleIP => [], # Account with same IP already exists
BadCurPass => [], # Current password is incorrect when changing password
MailChange => [], # A confirmation mail has been sent to change a user's email address
+ Releases => [ { aoh => { # Response to /r/get.json
+ id => { id => 1 },
+ title => {},
+ original => { required => 0, default => '' },
+ released => { uint => 1 },
+ rtype => {},
+ lang => { type => 'array', values => {} },
+ } } ],
);
diff --git a/lib/VNWeb/Releases/JS.pm b/lib/VNWeb/Releases/JS.pm
new file mode 100644
index 00000000..0ecc05aa
--- /dev/null
+++ b/lib/VNWeb/Releases/JS.pm
@@ -0,0 +1,21 @@
+package VNWeb::Releases::JS;
+
+use VNWeb::Prelude;
+
+
+# Used by UList.Opt to fetch releases from a VN id.
+json_api qr{/r/get.json}, { vid => { id => 1 } }, sub {
+ my($data) = @_;
+ my $l = tuwf->dbAlli(
+ 'SELECT r.id, r.title, r.original, r.type AS rtype, r.released
+ FROM releases r
+ JOIN releases_vn rv ON rv.id = r.id
+ WHERE NOT r.hidden
+ AND rv.vid =', \$data->{vid},
+ 'ORDER BY r.released, r.title, r.id'
+ );
+ enrich_flatten lang => id => id => sub { sql('SELECT id, lang FROM releases_lang WHERE id IN', $_, 'ORDER BY lang') }, $l;
+ elm_Releases $l;
+};
+
+1;
diff --git a/lib/VNWeb/User/Lists.pm b/lib/VNWeb/User/Lists.pm
index 1514e72f..80ea90f4 100644
--- a/lib/VNWeb/User/Lists.pm
+++ b/lib/VNWeb/User/Lists.pm
@@ -157,15 +157,15 @@ my $VNOPT = form_compile any => {
uid => { id => 1 },
vid => { id => 1 },
notes => {},
- rels => { aoh => {
+ rels => { aoh => { # Same structure as 'elm_Releases' response
id => { id => 1 },
title => {},
original => {},
released => { uint => 1 },
rtype => {},
- status => { uint => 1 },
lang => { type => 'array', values => {} },
} },
+ relstatus => { type => 'array', values => { uint => 1 } }, # List of release statuses, same order as rels
};
elm_form 'UListVNOpt', $VNOPT, undef;
@@ -218,13 +218,14 @@ my $RSTATUS = form_compile any => {
elm_form 'UListRStatus', undef, $RSTATUS;
+# Adds the release when not in the list.
json_api qr{/u/ulist/rstatus.json}, $RSTATUS, sub {
my($data) = @_;
return elm_Unauth if !auth || auth->uid != $data->{uid};
if($data->{status} == -1) {
tuwf->dbExeci('DELETE FROM rlists WHERE uid =', \$data->{uid}, 'AND rid =', \$data->{rid})
} else {
- tuwf->dbExeci('UPDATE rlists SET status =', \$data->{status}, 'WHERE uid =', \$data->{uid}, 'AND rid =', \$data->{rid})
+ tuwf->dbExeci('INSERT INTO rlists', $data, 'ON CONFLICT (uid, rid) DO UPDATE SET status =', \$data->{status})
}
elm_Success
};
@@ -336,7 +337,8 @@ sub vn_ {
tr_ mkclass(hidden => 1, 'collapsed_vid'.$v->{id} => 1, odd => $n % 2 == 0), sub {
td_ colspan => 7, class => 'tc_opt', sub {
- elm_ 'UList.Opt' => $VNOPT, { own => $own, uid => $uid, vid => $v->{id}, notes => $v->{notes}, rels => $v->{rels} };
+ my $relstatus = [ map $_->{status}, $v->{rels}->@* ];
+ elm_ 'UList.Opt' => $VNOPT, { own => $own, uid => $uid, vid => $v->{id}, notes => $v->{notes}, rels => $v->{rels}, relstatus => $relstatus };
};
};
}