module Main exposing (..) import Array exposing (..) import Base exposing (..) import Browser import Css import Css.Global exposing (body, everything, global, selector) import Data exposing (..) import Html import Html.Styled exposing (..) import Html.Styled.Attributes exposing (..) import Html.Styled.Events exposing (onClick, onInput) import Random import Styles exposing (styledButton, styledInput, styledTextBox, wordStyle) import Task import Time import Utils exposing (isJust, isNothing) import Views exposing (..) main = Browser.element { init = init , view = view >> toUnstyled , update = update , subscriptions = subscriptions } subscriptions : Model -> Sub Msg subscriptions model = Sub.none stringsToWords : List String -> List Word stringsToWords ls = List.map (\w -> Word w Todo) ls generateWords : Config -> Cmd Msg generateWords config = Random.generate LoadWords (randomParagraph config) defaultModel : Model defaultModel = Model Nothing Nothing Array.empty Nothing defaultConfig 0 "" False init : () -> ( Model, Cmd Msg ) init _ = ( defaultModel , generateWords defaultConfig ) 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.config.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 ) LoadWords ls -> ( { model | words = Array.fromList <| stringsToWords ls } , 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 inpStat = String.startsWith s (currentContents model) in ( { model | inputBox = s, inputCorrect = inpStat } , cmd ) WordLengthChanged n -> let oldConfig = model.config newConfig = { oldConfig | length = n } in ( { defaultModel | config = newConfig } , generateWords newConfig ) Redo -> let oldConfig = model.config newModel = { defaultModel | config = oldConfig } in ( newModel, generateWords oldConfig ) NextWord -> if isJust model.end then ( model, Cmd.none ) else handleWordEnded model _ -> ( model , Cmd.none ) toPara : Array Word -> Int -> String -> Html Msg toPara words current content = words |> Array.map (\w -> span [ css [ wordStyle w.status ] ] [ text w.content ]) |> Array.set current (span [ css [ wordStyle CurrentWord ] ] [ text content ]) |> Array.toList |> List.intersperse (span [] [ text " " ]) |> styledTextBox [] currentContents : Model -> String currentContents model = Array.get model.currentWord model.words |> Maybe.map .content |> Maybe.withDefault "" view : Model -> Html Msg view model = div [ style "width" "95%" , style "max-width" "650px" , style "margin" "auto" , style "padding-top" "15%" ] [ div [ style "width" "100%" ] [ viewWordLengthOptions , div [ style "float" "right" ] [ styledButton [ onClick Redo ] [ text "redo" ] ] ] , div [] [ toPara model.words model.currentWord (currentContents model) , div [] [ text "> " , styledInput model.inputCorrect [ onInput handleInputChanged, value model.inputBox ] [] ] ] , div [] [ viewStats model ] -- , global [ selector "li:not(:last-child)::after" [ Css.property "content" "' ยท '" ] ] , global [ everything [ Css.fontFamily Css.monospace, Css.fontSize (Css.px 18) ] ] -- , global [ body [ Css.backgroundColor (Css.hex "#000"), Css.color (Css.hex "#fff") ] ] ] handleInputChanged : String -> Msg handleInputChanged s = if wordEnded s then NextWord else InputChanged s