aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAkshay <[email protected]>2020-11-22 10:20:07 +0000
committerAkshay <[email protected]>2020-11-22 10:20:07 +0000
commitb90171e1f1862885a1efdfb915869eb5fad13b01 (patch)
tree57cbef5b586c7dc11e20348a72ed1c2a4dac7636
parent767b3310aa1589109ed211919f1d2e2984827757 (diff)
dump basic typing test PWA
-rw-r--r--elm.json24
-rw-r--r--shell.nix11
-rw-r--r--src/Main.elm336
3 files changed, 371 insertions, 0 deletions
diff --git a/elm.json b/elm.json
new file mode 100644
index 0000000..4c4956d
--- /dev/null
+++ b/elm.json
@@ -0,0 +1,24 @@
1{
2 "type": "application",
3 "source-directories": [
4 "src"
5 ],
6 "elm-version": "0.19.1",
7 "dependencies": {
8 "direct": {
9 "elm/browser": "1.0.2",
10 "elm/core": "1.0.5",
11 "elm/html": "1.0.0",
12 "elm/time": "1.0.0"
13 },
14 "indirect": {
15 "elm/json": "1.1.3",
16 "elm/url": "1.0.0",
17 "elm/virtual-dom": "1.0.2"
18 }
19 },
20 "test-dependencies": {
21 "direct": {},
22 "indirect": {}
23 }
24}
diff --git a/shell.nix b/shell.nix
new file mode 100644
index 0000000..b5f20b4
--- /dev/null
+++ b/shell.nix
@@ -0,0 +1,11 @@
1{ pkgs ? import <nixpkgs> {} }:
2
3pkgs.mkShell {
4 buildInputs = [
5 pkgs.elmPackages.elm
6 pkgs.elmPackages.elm-language-server
7 pkgs.elmPackages.elm-format
8 pkgs.nodePackages.elm-oracle
9 pkgs.elmPackages.elm-test
10 ];
11}
diff --git a/src/Main.elm b/src/Main.elm
new file mode 100644
index 0000000..9a35631
--- /dev/null
+++ b/src/Main.elm
@@ -0,0 +1,336 @@
1module Main exposing (..)
2
3import Array exposing (..)
4import Browser
5import Html exposing (..)
6import Html.Attributes exposing (..)
7import Html.Events exposing (onInput)
8import Task
9import Time
10
11
12main =
13 Browser.element
14 { init = init
15 , view = view
16 , update = update
17 , subscriptions = subscriptions
18 }
19
20
21type WordStatus
22 = Correct
23 | Wrong
24 | Todo
25 | CurrentWord
26
27
28type alias Word =
29 { content : String
30 , status : WordStatus
31 }
32
33
34type alias Model =
35 { begin : Maybe Time.Posix
36 , end : Maybe Time.Posix
37 , words : Array Word
38 , accuracy : Maybe Float
39 , length : Int
40 , currentWord : Int
41 , inputBox : String
42 }
43
44
45type Msg
46 = Started Time.Posix
47 | Finished Time.Posix
48 | CorrectInput
49 | CorrectSoFar
50 | NextWord
51 | WrongInput
52 | InputChanged String
53 | Redo
54
55
56subscriptions : Model -> Sub Msg
57subscriptions model =
58 Sub.none
59
60
61generateWords : Array Word
62generateWords =
63 "this is some sample text for the demoz"
64 |> String.split " "
65 |> Array.fromList
66 |> Array.map (\w -> Word w Todo)
67
68
69init : () -> ( Model, Cmd Msg )
70init _ =
71 let
72 words =
73 generateWords
74 in
75 ( Model Nothing Nothing words Nothing (Array.length words) 0 ""
76 , Cmd.none
77 )
78
79
80setStartTime : Cmd Msg
81setStartTime =
82 Task.perform Started Time.now
83
84
85setEndTime : Cmd Msg
86setEndTime =
87 Task.perform Finished Time.now
88
89
90firstWord : Model -> Bool
91firstWord model =
92 String.length model.inputBox == 1 && model.currentWord == 0
93
94
95lastWord : Model -> Bool
96lastWord model =
97 model.currentWord == model.length - 1
98
99
100wordEnded : String -> Bool
101wordEnded s =
102 String.endsWith " " s
103
104
105handleWordEnded : Model -> ( Model, Cmd Msg )
106handleWordEnded model =
107 let
108 s =
109 model.inputBox
110
111 current =
112 Array.get model.currentWord model.words |> Maybe.withDefault (Word "" Todo)
113
114 currentContent =
115 current.content
116
117 prevWordStatus =
118 if s == currentContent then
119 Correct
120
121 else
122 Wrong
123
124 newWords =
125 Array.set model.currentWord { current | status = prevWordStatus } model.words
126
127 newModel =
128 { model | inputBox = "", words = newWords, currentWord = model.currentWord + 1 }
129
130 cmd =
131 if lastWord model then
132 setEndTime
133
134 else
135 Cmd.none
136 in
137 ( newModel, cmd )
138
139
140isNothing : Maybe a -> Bool
141isNothing p =
142 case p of
143 Nothing ->
144 True
145
146 Just a ->
147 False
148
149
150isJust : Maybe a -> Bool
151isJust p =
152 not (isNothing p)
153
154
155flip : (a -> b -> c) -> (b -> a -> c)
156flip f =
157 \x y -> f y x
158
159
160update : Msg -> Model -> ( Model, Cmd Msg )
161update msg model =
162 case msg of
163 Started t ->
164 ( { model | begin = Just t }
165 , Cmd.none
166 )
167
168 Finished t ->
169 ( { model | end = Just t }
170 , Cmd.none
171 )
172
173 InputChanged s ->
174 let
175 cmd =
176 if firstWord model && isNothing model.begin then
177 setStartTime
178
179 else if s == currentContents model && lastWord model then
180 setEndTime
181
182 else
183 Cmd.none
184 in
185 ( { model | inputBox = s }
186 , cmd
187 )
188
189 NextWord ->
190 if isJust model.end then
191 ( model, Cmd.none )
192
193 else
194 handleWordEnded model
195
196 _ ->
197 ( model
198 , Cmd.none
199 )
200
201
202displayTime : Time.Posix -> String
203displayTime t =
204 let
205 hh =
206 Time.toHour Time.utc t
207
208 mm =
209 Time.toMinute Time.utc t
210
211 ss =
212 Time.toSecond Time.utc t
213 in
214 String.join ":" (List.map String.fromInt [ hh, mm, ss ])
215
216
217wordStyle : WordStatus -> Attribute msg
218wordStyle w =
219 case w of
220 Correct ->
221 style "color" "green"
222
223 Wrong ->
224 style "color" "red"
225
226 Todo ->
227 style "color" "black"
228
229 CurrentWord ->
230 style "color" "magenta"
231
232
233toPara : Array Word -> Int -> String -> Html Msg
234toPara words current content =
235 words
236 |> Array.map (\w -> span [ wordStyle w.status ] [ text (w.content ++ " ") ])
237 |> Array.set current (span [ wordStyle CurrentWord ] [ text (content ++ " ") ])
238 |> Array.toList
239 |> p []
240
241
242currentContents : Model -> String
243currentContents model =
244 Array.get model.currentWord model.words
245 |> Maybe.map .content
246 |> Maybe.withDefault ""
247
248
249diffDuration : Time.Posix -> Time.Posix -> Float
250diffDuration t1 t2 =
251 let
252 m1 =
253 Time.posixToMillis t1
254
255 m2 =
256 Time.posixToMillis t2
257 in
258 toFloat (m2 - m1) / 1000
259
260
261viewWpm : Model -> String
262viewWpm model =
263 let
264 t1 =
265 model.begin
266
267 t2 =
268 model.end
269
270 duration =
271 Maybe.map2 diffDuration t1 t2
272
273 correctWords =
274 wordCount model.words ((==) Correct)
275
276 wpm =
277 Maybe.map (String.fromInt << truncate << (*) 60 << (/) (toFloat correctWords)) duration
278 in
279 Maybe.withDefault "XX" wpm
280
281
282wordCount : Array Word -> (WordStatus -> Bool) -> Int
283wordCount words predicate =
284 words |> Array.map .status |> Array.filter predicate |> Array.length
285
286
287viewProgress : Model -> String
288viewProgress model =
289 String.fromInt model.currentWord ++ "/" ++ String.fromInt model.length
290
291
292viewAccuracy : Model -> String
293viewAccuracy model =
294 let
295 wordsAttempted =
296 toFloat <| wordCount model.words ((/=) Todo)
297
298 correctCount =
299 toFloat <| wordCount model.words ((==) Correct)
300
301 accuracy =
302 if wordsAttempted == 0.0 then
303 Nothing
304
305 else
306 Just <| correctCount / wordsAttempted * 100
307 in
308 case accuracy of
309 Nothing ->
310 "XX"
311
312 Just a ->
313 String.fromInt <| truncate a
314
315
316view : Model -> Html Msg
317view model =
318 div []
319 [ p [] [ text (String.fromInt model.currentWord) ]
320 , toPara model.words model.currentWord (currentContents model)
321
322 -- , p [] [ text (Maybe.withDefault "XX" (Maybe.map displayTime model.begin)) ]
323 -- , p [] [ text (Maybe.withDefault "XX" (Maybe.map displayTime model.end)) ]
324 , p [] [ text ("WPM: " ++ viewWpm model) ]
325 , p [] [ text ("ACC: " ++ viewAccuracy model) ]
326 , input [ onInput handleInputChanged, value model.inputBox ] []
327 ]
328
329
330handleInputChanged : String -> Msg
331handleInputChanged s =
332 if wordEnded s then
333 NextWord
334
335 else
336 InputChanged s