From 5278cf694d261da24093c6e00f6e94b883c7a726 Mon Sep 17 00:00:00 2001 From: Matei Adriel Date: Thu, 28 Nov 2019 16:57:45 +0200 Subject: [PATCH] elm(todolist): feat: basic todo operations Signed-off-by: prescientmoon --- elm/todolist/elm-analyse.json | 6 ++ elm/todolist/src/Main.elm | 141 ++++++++++++++++++++++++++++++---- elm/todolist/src/main.css | 97 +++++++++++++++++++++-- 3 files changed, 223 insertions(+), 21 deletions(-) create mode 100644 elm/todolist/elm-analyse.json diff --git a/elm/todolist/elm-analyse.json b/elm/todolist/elm-analyse.json new file mode 100644 index 0000000..2e716e1 --- /dev/null +++ b/elm/todolist/elm-analyse.json @@ -0,0 +1,6 @@ +{ + "checks": { + "ExposeAll": false, + "ImportAll": false + } +} diff --git a/elm/todolist/src/Main.elm b/elm/todolist/src/Main.elm index c3ab483..8413e38 100644 --- a/elm/todolist/src/Main.elm +++ b/elm/todolist/src/Main.elm @@ -1,20 +1,36 @@ module Main exposing (..) import Browser -import Html exposing (Html, text, div, h1, img) -import Html.Attributes exposing (src) +import Html exposing (..) +import Html.Attributes exposing (..) +import Html.Events exposing (..) + + + +-- HELPER TYPES + + +type alias Todo = + { done : Bool + , text : String + , id : Int + } + ---- MODEL ---- type alias Model = - {} + { todos : List Todo + , lastId : Int + , nextTodoText : String + } -init : ( Model, Cmd Msg ) +init : Model init = - ( {}, Cmd.none ) + Model [] 0 "" @@ -22,12 +38,48 @@ init = type Msg - = NoOp + = AddTodo + | DeleteTodo Int + | ToggleTodoCompletion Int + | SetNextTodoText String -update : Msg -> Model -> ( Model, Cmd Msg ) -update msg model = - ( model, Cmd.none ) +update : Msg -> Model -> Model +update message model = + case message of + AddTodo -> + if model.nextTodoText == "" then + model + + else + let + id = + model.lastId + 1 + + text = + model.nextTodoText + in + { model | lastId = id, todos = Todo False text id :: model.todos } + + DeleteTodo id -> + { model | todos = List.filter ((/=) id << .id) model.todos } + + ToggleTodoCompletion id -> + { model + | todos = + List.map + (\todo -> + if todo.id == id then + { todo | done = not todo.done } + + else + todo + ) + model.todos + } + + SetNextTodoText text -> + { model | nextTodoText = text } @@ -36,9 +88,69 @@ update msg model = view : Model -> Html Msg view model = - div [] - [ img [ src "/logo.svg" ] [] - , h1 [] [ text "Your Elm App is working!" ] + div [ class "container" ] + [ header model + , div [ class "todos" ] <| + List.map + todoView + model.todos + ] + + + +-- HEADER + + +header : Model -> Html Msg +header _ = + div [ class "header" ] + [ button + [ class "btn" + , onClick AddTodo + ] + [ text "add todo" ] + , input + [ placeholder "todo text" + , onInput SetNextTodoText + , class "todoTextInput" + ] + [] + ] + + + +-- _TODO VIEW + + +todoClasses : Todo -> String +todoClasses todo = + "todo" + ++ (if todo.done then + " completed" + + else + "" + ) + + +todoView : Todo -> Html Msg +todoView todo = + div [ class <| todoClasses todo ] + [ div [ class "todoCompleted" ] + [ input + [ checked todo.done + , type_ "checkbox" + , class "todoCheckbox" + , onCheck <| \_ -> ToggleTodoCompletion todo.id + ] + [] + ] + , div [ class "todoText" ] [ text todo.text ] + , button + [ class "btn" + , onClick <| DeleteTodo todo.id + ] + [ text "Delete todo" ] ] @@ -48,9 +160,8 @@ view model = main : Program () Model Msg main = - Browser.element + Browser.sandbox { view = view - , init = \_ -> init + , init = init , update = update - , subscriptions = always Sub.none } diff --git a/elm/todolist/src/main.css b/elm/todolist/src/main.css index d9f0b64..206b7b0 100644 --- a/elm/todolist/src/main.css +++ b/elm/todolist/src/main.css @@ -9,18 +9,103 @@ height: inherit; } +:root { + --secondary: #009fb7; + --primary: #1a213b; + --on-secondary: #eff1f4; + --disabled: #555555; +} + body { - font-family: 'Source Sans Pro', 'Trebuchet MS', 'Lucida Grande', 'Bitstream Vera Sans', 'Helvetica Neue', sans-serif; + font-family: "Source Sans Pro", "Trebuchet MS", "Lucida Grande", + "Bitstream Vera Sans", "Helvetica Neue", sans-serif; margin: 0; - text-align: center; - color: #293c4b; + background: var(--primary); + + color: var(--on-secondary); } h1 { font-size: 30px; } -img { - margin: 20px 0; - max-width: 200px; +.header { + display: flex; +} + +.btn { + background-color: var(--secondary); + color: var(--on-secondary); + padding: 1rem; + margin: 1rem; + border: none; +} + +input { + outline: none; +} + +.todos { + display: flex; + flex-direction: column; + min-height: 67vh; + border: 1px solid var(--secondary); +} + +.todo { + transition: filter 0.5s; + background: var(--primary); + display: flex; +} + +.todo:hover { + filter: brightness(1.4); +} + +.todo.completed { + color: var(--disabled); + + text-decoration: line-through; +} + +.todo.completed > .btn { + color: var(--disabled); +} + +.todo > * { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +} + +.todoText { + justify-content: flex-start; + flex-direction: row; + flex-grow: 1; +} + +.todoCompleted { + margin: 1rem; +} + +.todoTextInput { + background: var(--primary); + border: 1px var(--secondary) solid; + margin: 1rem; + padding: 2px; + box-sizing: border-box; + color: var(--on-secondary); + font-size: 1.5rem; + transition: filter 0.5s; +} + +.todoTextInput:focus { + filter: brightness(1.4); +} + +.todoCheckbox { + width: 1rem; + height: 1rem; + background: var(--secondary); }