module Main exposing (..) import Array exposing (..) import Base exposing (..) import Browser import Html exposing (..) import Html.Attributes exposing (..) import Html.Events exposing (onInput) import Task import Time import Utils exposing (isJust, isNothing) import Views exposing (..) main = Browser.element { init = init , view = view , update = update , subscriptions = subscriptions } type Msg = Started Time.Posix | Finished Time.Posix | CorrectInput | CorrectSoFar | NextWord | WrongInput | InputChanged String | Redo subscriptions : Model -> Sub Msg subscriptions model = Sub.none generateWords : Array Word generateWords = "this is some sample text for the demoz" |> String.split " " |> Array.fromList |> Array.map (\w -> Word w Todo) init : () -> ( Model, Cmd Msg ) init _ = let words = generateWords in ( Model Nothing Nothing words Nothing (Array.length words) 0 "" , Cmd.none ) setStartTime : Cmd Msg setStartTime = Task.perform Started Time.now setEndTime : Cmd Msg setEndTime = Task.perform Finished Time.now firstWord : Model -> Bool firstWord model = String.length model.inputBox == 1 && model.currentWord == 0 lastWord : Model -> Bool lastWord model = model.currentWord == model.length - 1 wordEnded : String -> Bool wordEnded s = String.endsWith " " s handleWordEnded : Model -> ( Model, Cmd Msg ) handleWordEnded model = let s = model.inputBox current = Array.get model.currentWord model.words |> Maybe.withDefault (Word "" Todo) currentContent = current.content prevWordStatus = if s == currentContent then Correct else Wrong newWords = Array.set model.currentWord { current | status = prevWordStatus } model.words newModel = { model | inputBox = "", words = newWords, currentWord = model.currentWord + 1 } cmd = if lastWord model then setEndTime else Cmd.none in ( newModel, cmd ) update : Msg -> Model -> ( Model, Cmd Msg ) update msg model = case msg of Started t -> ( { model | begin = Just t } , Cmd.none ) Finished t -> ( { model | end = Just t } , Cmd.none ) InputChanged s -> let cmd = if firstWord model && isNothing model.begin then setStartTime else if s == currentContents model && lastWord model then setEndTime else Cmd.none in ( { model | inputBox = s } , cmd ) NextWord -> if isJust model.end then ( model, Cmd.none ) else handleWordEnded model _ -> ( model , Cmd.none ) wordStyle : WordStatus -> Attribute msg wordStyle w = case w of Correct -> style "color" "green" Wrong -> style "color" "red" Todo -> style "color" "black" CurrentWord -> style "color" "magenta" toPara : Array Word -> Int -> String -> Html Msg toPara words current content = words |> Array.map (\w -> span [ wordStyle w.status ] [ text (w.content ++ " ") ]) |> Array.set current (span [ wordStyle CurrentWord ] [ text (content ++ " ") ]) |> Array.toList |> p [] currentContents : Model -> String currentContents model = Array.get model.currentWord model.words |> Maybe.map .content |> Maybe.withDefault "" view : Model -> Html Msg view model = pre [] [ toPara model.words model.currentWord (currentContents model) -- , p [] [ text (Maybe.withDefault "XX" (Maybe.map displayTime model.begin)) ] -- , p [] [ text (Maybe.withDefault "XX" (Maybe.map displayTime model.end)) ] , p [] [ text ("POS: " ++ viewProgress (model.currentWord + 1) model.length) ] , p [] [ text ("WPM: " ++ viewWpm model) ] , p [] [ text ("ACC: " ++ viewAccuracy model.words) ] , input [ onInput handleInputChanged, value model.inputBox ] [] ] handleInputChanged : String -> Msg handleInputChanged s = if wordEnded s then NextWord else InputChanged s