Skip to content

Instantly share code, notes, and snippets.

@martimatix
Last active August 16, 2021 09:25
Show Gist options
  • Save martimatix/53aa808c6dd82ecbb51f27f5afc64e02 to your computer and use it in GitHub Desktop.
Save martimatix/53aa808c6dd82ecbb51f27f5afc64e02 to your computer and use it in GitHub Desktop.
Elm Subscription Example - Pause and Resume the clock
import Html exposing (Html, p, label, br, input)
import Html.App as Html
import Html.Attributes exposing (checked)
import Html.Events exposing (onCheck)
import Svg exposing (..)
import Svg.Attributes exposing (..)
import Time exposing (Time, second)
main =
Html.program
{ init = init
, view = view
, update = update
, subscriptions = subscriptions
}
-- MODEL
type alias Model =
{ time : Time
, sub : Bool
}
init : (Model, Cmd Msg)
init =
(Model 0 True, Cmd.none)
-- UPDATE
type Msg
= Tick Time
| ClockSub Bool
update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
case msg of
Tick newTime ->
({ model | time = newTime }, Cmd.none)
ClockSub state ->
{ model | sub = state } ! []
-- SUBSCRIPTIONS
subscriptions : Model -> Sub Msg
subscriptions model =
if model.sub then
Time.every second Tick
else
Sub.none
-- VIEW
view : Model -> Html Msg
view model =
Html.div []
[ Html.p [] [ text "Turn subscription on or off" ]
, radio True "on" model
, radio False "off" model
, br [] []
, clock model
]
radio : Bool -> String -> Model -> Html Msg
radio state name model =
let
isSelected =
model.sub == state
in
label []
[ br [] []
, input [ type' "radio", checked isSelected, onCheck (\_ -> ClockSub state) ] []
, text name
]
clock : Model -> Html msg
clock model =
let
angle =
turns (Time.inMinutes model.time)
handX =
toString (50 + 40 * cos angle)
handY =
toString (50 + 40 * sin angle)
in
svg [ viewBox "0 0 100 100", width "300px" ]
[ circle [ cx "50", cy "50", r "45", fill "#0B79CE" ] []
, line [ x1 "50", y1 "50", x2 handX, y2 handY, stroke "#023963" ] []
]
@johnnadratowski
Copy link

johnnadratowski commented May 4, 2018

Line 47: ({ model | time = newTime }, Cmd.none)

The expected behavior is probably more like

( { model | time = model.time + second }, Cmd.none)

@rofrol
Copy link

rofrol commented Sep 3, 2018

Updated to Elm-0.19. Added hour and minute hand https://ellie-app.com/3dYXjr3bJNWa1

$ elm package install elm/svg
module Main exposing (main)

import Browser
import Html exposing (Html, br, input, label, p)
import Html.Attributes exposing (checked)
import Html.Events exposing (onCheck)
import Svg exposing (..)
import Svg.Attributes exposing (..)
import Task
import Time exposing (Posix)


main : Program () Model Msg
main =
    Browser.element
        { init = \_ -> init
        , view = view
        , update = update
        , subscriptions = subscriptions
        }



-- MODEL


type alias Model =
    { time : Posix
    , zone : Time.Zone
    , sub : Bool
    }


init : ( Model, Cmd Msg )
init =
    ( Model (Time.millisToPosix 0) Time.utc True, Task.perform Zone Time.here )



-- UPDATE


type Msg
    = Tick Posix
    | ClockSub Bool
    | Zone Time.Zone


update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        Tick newTime ->
            ( { model | time = newTime }, Cmd.none )

        ClockSub state ->
            ( { model | sub = state }, Cmd.none )

        Zone zone ->
            ( { model | zone = zone }, Cmd.none )



-- SUBSCRIPTIONS


subscriptions : Model -> Sub Msg
subscriptions model =
    if model.sub then
        Time.every 1000 Tick

    else
        Sub.none



-- VIEW


formatTime zone posix =
    (String.padLeft 2 '0' <| String.fromInt <| Time.toHour zone posix)
        ++ ":"
        ++ (String.padLeft 2 '0' <| String.fromInt <| Time.toMinute zone posix)
        ++ ":"
        ++ (String.padLeft 2 '0' <| String.fromInt <| Time.toSecond zone posix)


view : Model -> Html Msg
view model =
    Html.div []
        [ Html.p [] [ text "Turn subscription on or off" ]
        , text <| formatTime model.zone model.time
        , radio True "on" model
        , radio False "off" model
        , br [] []
        , clock model
        ]


radio : Bool -> String -> Model -> Html Msg
radio state name model =
    let
        isSelected =
            model.sub == state
    in
    label []
        [ br [] []
        , input [ type_ "radio", checked isSelected, onCheck (\_ -> ClockSub state) ] []
        , text name
        ]


startingAngle =
    90


secondToDegree : Time.Zone -> Time.Posix -> Float
secondToDegree zone posix =
    let
        seconds =
            toFloat <| Time.toSecond zone posix
    in
    360 - (360 / 60) * seconds + startingAngle


minuteToDegree : Time.Zone -> Time.Posix -> Float
minuteToDegree zone posix =
    let
        minutesSeconds =
            (toFloat <| Time.toMinute zone posix) + (toFloat <| Time.toSecond zone posix) / 60
    in
    360 - (360 / 60) * minutesSeconds + startingAngle


hourToDegree : Time.Zone -> Time.Posix -> Float
hourToDegree zone posix =
    let
        hour12 =
            toFloat <| remainderBy 12 (Time.toHour zone posix)

        hoursMinutesSeconds =
            hour12 + (toFloat <| Time.toMinute zone posix) / 60 + (toFloat <| Time.toSecond zone posix) / 60 / 60
    in
    360 - (360 / 12) * hoursMinutesSeconds + startingAngle


hand offset length angle =
    ( offset + length * cos angle
    , offset + length * -1 * sin angle
    )


handLine offset ( x2_, y2_ ) =
    line
        [ x1 <| String.fromInt offset
        , y1 <| String.fromInt offset
        , x2 <| String.fromFloat x2_
        , y2 <| String.fromFloat y2_
        , stroke "#023963"
        ]
        []


clock : Model -> Html msg
clock model =
    let
        handSecond =
            hand 50 40 <| degrees <| secondToDegree model.zone model.time

        handMinute =
            hand 50 30 <| degrees <| minuteToDegree model.zone model.time

        handHour =
            hand 50 20 <| degrees <| hourToDegree model.zone model.time
    in
    svg [ viewBox "0 0 100 100", width "300px" ]
        [ circle [ cx "50", cy "50", r "45", fill "#0B79CE" ] []
        , handLine 50 handSecond
        , handLine 50 handMinute
        , handLine 50 handHour
        ]

@csaltos
Copy link

csaltos commented Apr 12, 2021

Line 47: ({ model | time = newTime }, Cmd.none)

The expected behavior is probably more like

( { model | time = model.time + second }, Cmd.none)

The Time.every second call is NOT precise, it's just a suggestion to try to run every second ... meaning -> sometimes will run at the precise second but other times, if the system is busy it will run at slightly more than a second ... if you add always one second, the clock will be unsync more and more every tick ... it's more precise to use newTime.

@csaltos
Copy link

csaltos commented Apr 12, 2021

The turn call is really cool ... the only missing part was to calculate the turn correctly dividing seconds to 60 in one turn, or 60 for minutes or 12 for hours in 12 hours format.

Here a version with seconds (I update it to Elm 0.19 that is the one I have now) ->

module Main exposing (main)

import Browser
import Html exposing (Html, br, input, label, p, text)
import Html.Attributes exposing (checked, type_)
import Html.Events exposing (onCheck)
import Svg exposing (circle, line, svg)
import Svg.Attributes exposing (cx, cy, fill, r, stroke, viewBox, width, x1, x2, y1, y2)
import Time exposing (Posix, utc)


main : Program () Model Msg
main =
    Browser.element
        { init = init
        , view = view
        , update = update
        , subscriptions = subscriptions
        }



-- MODEL


type alias Model =
    { time : Posix
    , sub : Bool
    }


init : () -> ( Model, Cmd Msg )
init _ =
    ( Model (Time.millisToPosix 0) True, Cmd.none )



-- UPDATE


type Msg
    = Tick Posix
    | ClockSub Bool


update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        Tick newTime ->
            ( { model | time = newTime }, Cmd.none )

        ClockSub state ->
            ( { model | sub = state }, Cmd.none )



-- SUBSCRIPTIONS


subscriptions : Model -> Sub Msg
subscriptions model =
    if model.sub then
        Time.every 500 Tick

    else
        Sub.none



-- VIEW


view : Model -> Html Msg
view model =
    Html.div []
        [ p [] [ text "Turn subscription on or off" ]
        , radio True "on" model
        , radio False "off" model
        , br [] []
        , clock model
        ]


radio : Bool -> String -> Model -> Html Msg
radio state name model =
    let
        isSelected =
            model.sub == state
    in
    label []
        [ br [] []
        , input [ type_ "radio", checked isSelected, onCheck (\_ -> ClockSub state) ] []
        , text name
        ]


clock : Model -> Html msg
clock model =
    let
        angle =
            turns (toFloat (Time.toSecond utc model.time) / 60.0)

        handX =
            String.fromFloat (50 + 40 * cos angle)

        handY =
            String.fromFloat (50 + 40 * sin angle)
    in
    svg [ viewBox "0 0 100 100", width "300px" ]
        [ circle [ cx "50", cy "50", r "45", fill "#0B79CE" ] []
        , line [ x1 "50", y1 "50", x2 handX, y2 handY, stroke "#023963" ] []
        ]

The solution of Roman is also cool and more complete, but it can be simplified a little bit if turns is used.

Thanks for sharing guys !! ... Elm is great !! 👍 😎

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment