summaryrefslogtreecommitdiff
path: root/elm/VNLengthVote.elm
blob: 16fd3ff3140a4811bb2da515421d3f57bb85a654 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
module VNLengthVote exposing (main)

import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (..)
import Browser
import Browser.Dom exposing (focus)
import Task
import Date
import Lib.Html exposing (..)
import Lib.Util exposing (..)
import Lib.Api as Api
import Lib.RDate as RDate
import Gen.Api as GApi
import Gen.VNLengthVote as GV
import Gen.Release as GR


main : Program GV.Send Model Msg
main = Browser.element
  { init   = \e -> (init e, Date.today |> Task.perform Today)
  , view   = view
  , update = update
  , subscriptions = always Sub.none
  }

type alias Model =
  { state   : Api.State
  , open    : Bool
  , today   : Int
  , uid     : String
  , vid     : String
  , rid     : List String
  , defrid  : String
  , hours   : Maybe Int
  , minutes : Maybe Int
  , speed   : Int
  , length  : Int -- last saved length
  , notes   : String
  , rels    : Maybe (List (String, String))
  }

init : GV.Send -> Model
init f =
  { state   = Api.Normal
  , today   = 0
  , open    = False
  , uid     = f.uid
  , vid     = f.vid
  , rid     = Maybe.map (\v -> v.rid) f.vote |> Maybe.withDefault []
  , defrid  = ""
  , hours   = Maybe.map (\v -> v.length // 60   ) f.vote
  , minutes = Maybe.andThen (\v -> let n = modBy 60 v.length in if n == 0 then Nothing else Just n) f.vote
  , speed   = Maybe.map (\v -> v.speed)  f.vote |> Maybe.withDefault -1
  , length  = Maybe.map (\v -> v.length) f.vote |> Maybe.withDefault 0
  , notes   = Maybe.map (\v -> v.notes)  f.vote |> Maybe.withDefault ""
  , rels    = Nothing
  }

enclen : Model -> Int
enclen m = (Maybe.withDefault 0 m.hours) * 60 + Maybe.withDefault 0 m.minutes

encode : Model -> GV.Send
encode m =
  { uid = m.uid
  , vid = m.vid
  , vote = if enclen m == 0 then Nothing else Just { rid = m.rid, notes = m.notes, speed = m.speed, length = enclen m }
  }

type Msg
  = Noop
  | Open Bool
  | Today Date.Date
  | Hours (Maybe Int)
  | Minutes (Maybe Int)
  | Speed Int
  | Release Int String
  | ReleaseAdd
  | ReleaseDel Int
  | Notes String
  | RelLoaded GApi.Response
  | Delete
  | Submit
  | Submitted GApi.Response


update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
  case msg of
    Noop -> (model, Cmd.none)
    Open b ->
      if b && model.rels == Nothing
      then ({ model | open = b, state = Api.Loading }, GR.send { vid = model.vid } RelLoaded)
      else ({ model | open = b }, Cmd.none)
    Today d   -> ({ model | today = RDate.fromDate d |> RDate.compact }, Cmd.none)
    Hours n   -> ({ model | hours = n }, Cmd.none)
    Minutes n -> ({ model | minutes = n }, Cmd.none)
    Speed n   -> ({ model | speed = n }, Cmd.none)
    Release n s  -> ({ model | rid = modidx n (always s) model.rid }, Cmd.none)
    ReleaseAdd   -> ({ model | rid = model.rid ++ [""] }, Cmd.none)
    ReleaseDel n -> ({ model | rid = delidx n model.rid }, Cmd.none)
    Notes s   -> ({ model | notes  = s }, Cmd.none)
    RelLoaded (GApi.Releases rels) ->
      let rel r = if r.rtype /= "trial" && r.released <= model.today then Just (r.id, RDate.showrel r) else Nothing
          frels = List.filterMap rel rels
          def = case frels of
                  [(r,_)] -> r
                  _ -> ""
      in ({ model | state = Api.Normal
          , rels = Just frels
          , defrid = def
          , rid = if not (List.isEmpty model.rid) then model.rid else [def]
         }, if model.hours == Nothing then Task.attempt (always Noop) (focus "vnlengthhours") else Cmd.none)
    RelLoaded e -> ({ model | state = Api.Error e }, Cmd.none)
    Delete      -> let m = { model | hours = Nothing, minutes = Nothing, rid = [model.defrid], notes = "", state = Api.Loading } in (m, GV.send (encode m) Submitted)
    Submit      -> ({ model | state = Api.Loading }, GV.send (encode model) Submitted)
    Submitted (GApi.Success) ->
      ({ model | open = False, state = Api.Normal
       , length = (Maybe.withDefault 0 model.hours) * 60 + Maybe.withDefault 0 model.minutes
       }, Cmd.none)
    Submitted r -> ({ model | state = Api.Error r }, Cmd.none)


view : Model -> Html Msg
view model = div [class "lengthvotefrm"] <|
  let
    cansubmit = enclen model > 0 && model.speed /= -1
      && not (List.isEmpty model.rid)
      && not (List.any (\r -> r == "") model.rid)
    rels = Maybe.withDefault [] model.rels
    frm = [ form_ "" (if cansubmit then Submit else Noop) False
      [ br [] []
      , text "How long did you take to finish this VN?"
      , br [] []
      , text "- Only vote if you've completed all normal/true endings."
      , br [] []
      , text "- Exact measurements preferred, but rough estimates are accepted too."
      , br [] []
      , text "Play time: "
      , inputNumber "vnlengthhours" model.hours Hours [ Html.Attributes.min "0", Html.Attributes.max "500" ]
      , text " hours "
      , inputNumber "" model.minutes Minutes [ Html.Attributes.min "0", Html.Attributes.max "59" ]
      , text " minutes"
      , br [] []
      , if model.defrid /= "" then text "" else div [] <| List.indexedMap (\n rid -> div []
        [ inputSelect "" rid (Release n) []
            <| ("", "-- select release --") :: rels
            ++ if rid == "" || List.any (\(r,_) -> r == rid) rels then [] else [(rid, "[deleted/moved release: " ++ rid ++ "]")]
        , if n == 0
          then inputButton "+" ReleaseAdd [title "Add release"]
          else inputButton "-" (ReleaseDel n) [title "Remove release"]
        ]) model.rid
      , inputSelect "" model.speed Speed [style "width" "100%"]
        [ (-1, "-- how do you estimate your read/play speed? --")
        , (0, "Slow (e.g. low language proficiency or extra time spent on gameplay)")
        , (1, "Normal (no content skipped, all voices listened to end)")
        , (2, "Fast (e.g. fast reader or skipping through voices and gameplay)")
        ]
      , inputTextArea "" model.notes Notes
        [rows 2, cols 30, style "width" "100%", placeholder "(Optional) comments that may be helpful. For example, did you complete all the bad endings, how did you measure? etc." ]
      , if model.length == 0 then text "" else inputButton "Delete my vote" Delete [style "float" "right"]
      , if cansubmit then submitButton "Save" model.state True else text ""
      , inputButton "Cancel" (Open False) []
      , br_ 2
      ] ]
  in
    [ text " "
    , a [ onClickD (Open (not model.open)), href "#" ]
      [ text <| if model.length == 0 then "Vote »"
        else "My vote: " ++ String.fromInt (model.length // 60) ++ "h"
                         ++ if modBy 60 model.length /= 0 then String.fromInt (modBy 60 model.length) ++ "m" else "" ]
    ] ++ case (model.open, model.state) of
          (False, _) -> []
          (_, Api.Normal) -> frm
          (_, Api.Error e) -> [ br_ 2, b [ class "standout" ] [ text ("Error: " ++ Api.showResponse e) ] ]
          (_, Api.Loading) -> [ span [ style "float" "right", class "spinner" ] [] ]