Arnaud Chenyensu |||

A better Google Calendar popup

Google Calendar screenshot

The current Google Calendar popup does not allow you to change any information about an event without going to a different page.

Here’s a little implementation of a better popup made in Elm.

You can change:

  • color
  • title
  • date
  • time

Demo

Elm Source Code

  
    import Browser
    import Browser.Dom as Dom
    import Browser.Events as Events
    import Html exposing (Html, div, text, input, span)
    import Html.Attributes exposing (class, style, type_, id, value, checked, disabled)
    import Html.Events exposing (onClick, onInput, stopPropagationOn)
    import Json.Decode as Decode
    import Task


    type Element
      = Title
      | Day
      | TimeStart
      | TimeEnd

    getElementId : Element -> String
    getElementId el =
      case el of
        Title -> "title"
        Day -> "day"
        TimeStart -> "timeStart"
        TimeEnd -> "timeEnd"

    type alias Editable =
      { id : Element
      , value : String
      }

    {-| Display the specified editable,
    given the element that is currently editing
    -}
    displayEditable : Maybe Element -> Editable -> Html Msg
    displayEditable editing ed =
      let
        isEditing =
          case editing of
            Just element ->
              if element == ed.id then True
              else False
            Nothing ->
              False
      in
        if isEditing then
          input
            [ id (getElementId ed.id)
            , value ed.value
            , onInput (Update ed.id)
            ]
            []
        else
          span
            [ stopPropagationOn "click" (Decode.succeed (Select ed.id, True))
            , class "editable"
            ]
            [ text ed.value ]

    updateEditable : String -> Editable -> Editable
    updateEditable value ed =
      { ed | value = value }

    type TimeOfDay
      = AllDay
      | Hours Editable Editable

    type alias Model =
      { title : Editable
      , day : Editable
      , time : TimeOfDay
      , editing : Maybe Element
      , color : String
      , showColors : Bool
      }

    main =
      Browser.element
        { init = init
        , view = view
        , update = update
        , subscriptions = subscriptions
        }

    type Msg
      = ToggleAllDay
      | Select Element
      | Focus (Result Dom.Error ())
      | Unselect
      | Update Element String
      | ToggleColors
      | SelectColor String

    init : () -> (Model, Cmd Msg)
    init _ =
      let
        model =
          { title =
            { id = Title
            , value = "Contact Arnaud"
            }
          , day =
            { id = Day
            , value = "Monday, 27 May"
            }
          , time = AllDay
          , editing = Nothing
          , color = "#B996D4"
          , showColors = False
          }
      in
        (model, Cmd.none)

    update : Msg -> Model -> (Model, Cmd Msg)
    update msg model =
      case msg of
        ToggleAllDay ->
          case model.time of
            AllDay ->
              ({ model
                | time = Hours { id = TimeStart, value = "10:00" } { id = TimeEnd, value = "10:30" }
              }, Cmd.none)
            Hours _ _ ->
              ({ model
                | time = AllDay
                , editing = Nothing
              }, Cmd.none)
        Select element ->
          ({ model | editing = Just element }
          , Task.attempt Focus (Dom.focus (getElementId element))
          )
        Focus res -> -- we don't handle the error
          (model, Cmd.none)
        Unselect ->
          ({ model
            | editing = Nothing
            , showColors = False
          }, Cmd.none)
        Update element value ->
          case element of
            Title ->
              ({ model | title = updateEditable value model.title }, Cmd.none)
            Day ->
              ({ model | day = updateEditable value model.day }, Cmd.none)
            TimeStart ->
              case model.time of
                AllDay ->
                  (model, Cmd.none)
                Hours start end ->
                  ({ model | time = Hours (updateEditable value start) end }, Cmd.none)
            TimeEnd ->
              case model.time of
                AllDay ->
                  (model, Cmd.none)
                Hours start end ->
                  ({ model | time = Hours start (updateEditable value end) }, Cmd.none)
        ToggleColors ->
          ({ model | showColors = not model.showColors }, Cmd.none)
        SelectColor color ->
          ({ model | color = color }, Cmd.none)

    subscriptions : Model -> Sub Msg
    subscriptions model =
      Sub.none

    viewTime : Model -> Html Msg
    viewTime model =
      case model.time of
        AllDay ->
          div
            []
            [ div
              []
              [ input
                [ type_ "checkbox"
                , checked True
                , onClick ToggleAllDay
                ]
                []
              , text "All day"
              ]
            ]
        Hours start end ->
          div
            []
            [ div
              []
              [ input
                [ type_ "checkbox"
                , checked False
                , onClick ToggleAllDay
                ] []
              , text "All day"
              ]
            , div
              []
              [ displayEditable model.editing start
              , span [] [ text " – " ]
              , displayEditable model.editing end
              ]
            ]

    availableColors =
      [ "#B996D4"
      , "#FFC0C0"
      , "#A7E3D1"
      , "#96DFFF"
      , "#FF5454"
      ]

    displayColors : List String -> Html Msg
    displayColors colors =
      div
        [ class "color-boxes" ]
        (List.map (\color ->
          div
            [ class "color-box"
            , style "background" color
            , onClick (SelectColor color)
            ] []
          ) colors
        )

    view : Model -> Html Msg
    view model =
      div
        [ class "background"
        , onClick Unselect
        ]
        [ div
          [ class "popup" ]
          [ div
            [ class "color" ]
            [ div
              [ class "color-box"
              , style "background" model.color
              , stopPropagationOn "click" (Decode.succeed (ToggleColors, True))
              ] []
            , if model.showColors then
                displayColors availableColors
              else
                span [] []
            ]
          , div
            [ class "info" ]
            [ div [] [ displayEditable model.editing model.title ]
            , div [] [ displayEditable model.editing model.day ]
            , viewTime model
            ]
          ]
        ]
  

CSS Source Code

  
    .background {
      position: fixed;
      padding: 0;
      margin: 0;
      top:0;
      left:0;
      width: 100%;
      height: 100%;
    }

    .popup {
      background: #FFFFFF;
      border-radius: 6px;
      box-shadow: 0px 1px 4px rgba(0, 0, 0, 0.25);
      margin: 20px;
      padding: 16px;
      width: 300px;
    }

    .editable:hover {
      background: #E5E5E5;
    }

    .color {
      display: inline-block;
      margin-right: 10px;
      vertical-align: top;
    }

    .color-box {
      display: inline-block;
      width: 20px;
      height: 20px;
      border-radius: 4px;
      margin-right: 10px;
    }

    .color-boxes {
      position: absolute;
      background: #FFFFFF;
      border-radius: 4px;
      box-shadow: 0px 1px 4px rgba(0, 0, 0, 0.25);
      padding: 10px;
    }

    .info {
      display: inline-block;
    }