diff options
-rw-r--r-- | elm/Lib/Api.elm | 1 | ||||
-rw-r--r-- | elm/UList/Opt.elm | 76 | ||||
-rw-r--r-- | lib/VNWeb/Elm.pm | 8 | ||||
-rw-r--r-- | lib/VNWeb/Releases/JS.pm | 21 | ||||
-rw-r--r-- | lib/VNWeb/User/Lists.pm | 10 |
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 }; }; }; } |