-
Notifications
You must be signed in to change notification settings - Fork 0
/
Main.elm
244 lines (192 loc) · 6.51 KB
/
Main.elm
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
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
module Main exposing (..)
import Browser
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (..)
import List exposing (..)
import String exposing (join, split)
import Regex exposing (contains)
import Task
import Process
main =
Browser.element
{ init = init
, view = view
, update = update
, subscriptions = subscriptions
}
-- MODEL
type alias Model =
{ words : List String
, wpm : Int
, wordSpan : Int
, position : Int
, playing : Bool
}
init : () -> ( Model, Cmd Msg )
init _ =
( { words = [], wpm = 270, wordSpan = 3, position = 0, playing = False }, Cmd.none )
-- UPDATE
type Msg
= Tick ()
| Change String
| SpeedUp
| SpeedDown
| SpanUp
| SpanDown
| Rew
| Fw
| Pause
step : Model -> Model
step model =
{ model | position = Basics.min (length model.words - 1) (model.position + model.wordSpan) }
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
Tick _ ->
if model.playing then
let
newModel =
step model
atEnd =
newModel.position + newModel.wordSpan >= length newModel.words
in
if atEnd then
( { newModel | playing = False }, Cmd.none )
else
( newModel, waitNext newModel )
else
( model, Cmd.none )
Change newContent ->
( { model | words = split " " newContent, position = 0, playing = True }, waitNext model )
Pause ->
let
playing =
not model.playing
in
( { model | playing = playing }
, if playing then
waitNext model
else
Cmd.none
)
Rew ->
( { model | playing = False, position = Basics.max 0 (model.position - model.wordSpan) }, Cmd.none )
Fw ->
let
newModel =
step model
in
( { newModel | playing = False }, Cmd.none )
SpeedDown ->
( { model | wpm = Basics.max 100 (model.wpm - 5) }, Cmd.none )
SpeedUp ->
( { model | wpm = Basics.min 1000 (model.wpm + 5) }, Cmd.none )
SpanDown ->
( { model | wordSpan = Basics.max 1 (model.wordSpan - 1) }, Cmd.none )
SpanUp ->
( { model | wordSpan = Basics.min 8 (model.wordSpan + 1) }, Cmd.none )
-- SUBSCRIPTIONS
subscriptions : Model -> Sub Msg
subscriptions model =
Sub.none
-- TASKS
waitNext : Model -> Cmd Msg
waitNext model =
let
extraDelay =
if containsBreak (nextWords model) then
120000 // model.wpm
else
0
delay =
(toFloat (model.wordSpan * 60000 // model.wpm + extraDelay))
in
Task.perform Tick <| Process.sleep delay
containsBreak : List String -> Bool
containsBreak words =
contains (Maybe.withDefault Regex.never <| Regex.fromString "[,.-]") (join "" words)
nextWords : Model -> List String
nextWords model =
take model.wordSpan <| drop model.position model.words
-- VIEW
view : Model -> Html Msg
view model =
div
[ style "margin" "0 auto"
, style "maxWidth" "50em"
, style "fontFamily" "'Helvetica', 'Arial', 'sans-serif'"
, style "textAlign" "center"
, style "padding" "8px"
, style "line-height" "1.5"
]
[ div
[ style "background" "#eeeeee" ]
[ input [ style "line-height" "inherit", placeholder "Source text", onInput Change ] []
, fixedButton [ onClick Pause ]
[ text
(if model.playing then
"⏸"
else
"▶"
)
]
, fixedButton [ onClick Rew ] [ text "<<" ]
, fixedButton [ onClick Fw ] [ text ">>" ]
, text "WPM:"
, fixedButton [ onClick SpeedDown ] [ text "-" ]
, text (Debug.toString model.wpm)
, fixedButton [ onClick SpeedUp ] [ text "+" ]
, text "Span:"
, fixedButton [ onClick SpanDown ] [ text "-" ]
, text (Debug.toString model.wordSpan)
, fixedButton [ onClick SpanUp ] [ text "+" ]
, a [ href "http://github.com/DestyNova/blinkenwords-elm" ] [ text "Source" ]
]
, div []
[ makeProgressBar (model.position + model.wordSpan) (length model.words)
, makeReadingPane (List.concatMap (\w -> [ text w, br [] [] ]) (nextWords model)) model.wordSpan
]
]
fixedButton attrs content =
button (style "line-height" "inherit"::attrs) content
makeProgressBar : Int -> Int -> Html Msg
makeProgressBar position numWords =
div
[ style "border-radius" "15px"
, style "height" "12px"
, style "background" "#555"
, style "box-shadow" "inset 0 -1px 1px rgba(255,255,255,0.3)"
, style "padding" "2px"
, style "position" "relative"
]
[ span
[ style "height" "100%"
, style "display" "block"
, style "height" "100%"
, style "border-top-right-radius" "8px"
, style "border-bottom-right-radius" "8px"
, style "border-top-left-radius" "20px"
, style "border-bottom-left-radius" "20px"
, style "background-color" "rgb(43,194,83)"
, style "background-image" "linear-gradient( center bottom, rgb(43,194,83) 37%, rgb(84,240,84) 69%)"
, style "box-shadow" "inset 0 2px 9px rgba(255,255,255,0.3), inset 0 -2px 6px rgba(0,0,0,0.4)"
, style "position" "relative"
, style "overflow" "hidden"
, style "width" (Debug.toString (Basics.min 100 <| position * 100 // numWords) ++ "%")
]
[]
]
makeReadingPane : List (Html Msg) -> Int -> Html Msg
makeReadingPane nextChunk rows =
div
[ style "color" "red"
, style "backgroundColor" "cornsilk"
, style "border" "1px solid black"
, style "box-shadow" "4px 4px 4px 0px rgba(0,0,0,0.75)"
, style "lineHeight" "150%"
, style "height" (Debug.toString (1.5 * toFloat rows) ++ "em")
, style "margin" "0.5em"
, onClick Pause
]
nextChunk