Skip to content

Instantly share code, notes, and snippets.

@n1215
Last active January 18, 2020 01:20
Show Gist options
  • Save n1215/85e5d4a36a6bf5e6c21344dd63c570a2 to your computer and use it in GitHub Desktop.
Save n1215/85e5d4a36a6bf5e6c21344dd63c570a2 to your computer and use it in GitHub Desktop.
timer event sourcing elm
module Main exposing (main)
import Browser
import Browser.Events exposing (onAnimationFrame)
import Html exposing (Html, button, div, p, table, tbody, td, text, th, thead, tr)
import Html.Events exposing (onClick)
import List
import Task
import Time
import Tuple
main =
Browser.element { init = init, update = update, view = view, subscriptions = subscriptions }
type alias ElapsedTimeMillis =
Int
type EventType
= Started
| Stopped
eventTypeToString : EventType -> String
eventTypeToString eventType =
case eventType of
Started ->
"started"
Stopped ->
"stopped"
type alias Event =
{ eventType : EventType
, occurredAt : Time.Posix
}
type alias Model =
{ events : List Event
, now : Time.Posix
}
init : () -> ( Model, Cmd Msg )
init _ =
( { events = [], now = Time.millisToPosix 0 }, Task.perform Tick Time.now )
isStopped : Model -> Bool
isStopped model =
let
isEven : Int -> Bool
isEven int =
modBy 2 int == 0
in
isEven (List.length model.events)
getElapsedTime : Model -> ElapsedTimeMillis
getElapsedTime model =
let
chunk : Int -> List a -> List (List a)
chunk i list =
case List.take i list of
[] ->
[]
listHead ->
listHead :: chunk i (List.drop i list)
calcTimeForSpan : Time.Posix -> List Event -> ElapsedTimeMillis
calcTimeForSpan now span =
case span of
[ startedEvent, stoppedEvent ] ->
Time.posixToMillis stoppedEvent.occurredAt - Time.posixToMillis startedEvent.occurredAt
[ startedEvent ] ->
Time.posixToMillis now - Time.posixToMillis startedEvent.occurredAt
_ ->
0
in
chunk 2 model.events
|> List.map (calcTimeForSpan model.now)
|> List.sum
type Msg
= Tick Time.Posix
| StartStop Time.Posix
| Click
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
Tick currentTime ->
( { model | now = currentTime }, Cmd.none )
StartStop occurredAt ->
let
getEventType =
if isStopped model then
Started
else
Stopped
in
( { model
| events = List.append model.events [ { eventType = getEventType, occurredAt = occurredAt } ]
, now = occurredAt
}
, Cmd.none
)
Click ->
( model, Task.perform StartStop Time.now )
eventTableRow : ( Int, Event ) -> Html Msg
eventTableRow ( index, event ) =
tr []
[ td [] [ text <| String.fromInt (index + 1) ]
, td [] [ text <| eventTypeToString event.eventType ]
, td [] [ text <| String.fromInt <| Time.posixToMillis event.occurredAt ]
]
view : Model -> Html Msg
view model =
div []
[ p [] [ text <| "timer (sec): " ++ (String.fromInt <| getElapsedTime model // 1000) ]
, p [] [ text <| "timer (ms): " ++ String.fromInt (getElapsedTime model) ]
, button [ onClick Click ]
[ text <|
if isStopped model then
"start"
else
"stop"
]
, table []
[ thead []
[ tr []
[ th [] [ text <| "id" ]
, th [] [ text <| "event" ]
, th [] [ text <| "occurred at" ]
]
]
, tbody [] (List.map eventTableRow (List.indexedMap Tuple.pair model.events))
]
]
subscriptions : Model -> Sub Msg
subscriptions _ =
onAnimationFrame Tick
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment