diff --git a/README.md b/README.md
index 3c5b4a6..ac67598 100644
--- a/README.md
+++ b/README.md
@@ -13,3 +13,4 @@ The experiments are currently organized based on the language they use:
- [Idris](./idris/)
- [Typst](./typst/)
- [Python](./python/)
+- [Elm](./elm/)
diff --git a/elm/README.md b/elm/README.md
new file mode 100644
index 0000000..558568d
--- /dev/null
+++ b/elm/README.md
@@ -0,0 +1,5 @@
+# Elm
+
+| Name | Description |
+| ----------------------- | ----------------------------- |
+| [todolist](./todolist/) | Basic todolist implementation |
diff --git a/elm/todolist/.github/workflows/elm.yml b/elm/todolist/.github/workflows/elm.yml
new file mode 100644
index 0000000..ad95080
--- /dev/null
+++ b/elm/todolist/.github/workflows/elm.yml
@@ -0,0 +1,29 @@
+name: Elm
+
+on: [push]
+
+jobs:
+ deploy:
+ runs-on: ubuntu-latest
+
+ strategy:
+ matrix:
+ node-version: [12.x]
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v1
+ - name: Use node 12
+ uses: actions/setup-node@v1
+
+ with:
+ node-version: ${{ matrix.node-version }}
+ - run: npm i -g elm create-elm-app
+ - run: elm-app build
+ - name: Deploy to github pages
+ uses: JamesIves/github-pages-deploy-action@master
+ env:
+ ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }}
+ BASE_BRANCH: master
+ BRANCH: gh-pages
+ FOLDER: build
diff --git a/elm/todolist/.gitignore b/elm/todolist/.gitignore
new file mode 100644
index 0000000..e7e7f12
--- /dev/null
+++ b/elm/todolist/.gitignore
@@ -0,0 +1,14 @@
+# Distribution
+build/
+
+# elm-package generated files
+elm-stuff
+
+# elm-repl generated files
+repl-temp-*
+
+# Dependency directories
+node_modules
+
+# Desktop Services Store on macOS
+.DS_Store
diff --git a/elm/todolist/README.md b/elm/todolist/README.md
new file mode 100644
index 0000000..3b963fc
--- /dev/null
+++ b/elm/todolist/README.md
@@ -0,0 +1,3 @@
+# Elm todo app
+
+Todo app made in 1h for getting a better understanding of the elm architecture
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/elm.json b/elm/todolist/elm.json
new file mode 100644
index 0000000..5e90087
--- /dev/null
+++ b/elm/todolist/elm.json
@@ -0,0 +1,26 @@
+{
+ "type": "application",
+ "source-directories": ["src"],
+ "elm-version": "0.19.1",
+ "dependencies": {
+ "direct": {
+ "elm/browser": "1.0.1",
+ "elm/core": "1.0.2",
+ "elm/html": "1.0.0"
+ },
+ "indirect": {
+ "elm/json": "1.1.2",
+ "elm/time": "1.0.0",
+ "elm/url": "1.0.0",
+ "elm/virtual-dom": "1.0.2"
+ }
+ },
+ "test-dependencies": {
+ "direct": {
+ "elm-explorations/test": "1.0.0"
+ },
+ "indirect": {
+ "elm/random": "1.0.0"
+ }
+ }
+}
diff --git a/elm/todolist/elmapp.config.js b/elm/todolist/elmapp.config.js
new file mode 100644
index 0000000..ae3810d
--- /dev/null
+++ b/elm/todolist/elmapp.config.js
@@ -0,0 +1,3 @@
+module.exports = {
+ homepage: "https://mateadrielrafael.github.io/elmTodolist"
+};
diff --git a/elm/todolist/public/favicon.ico b/elm/todolist/public/favicon.ico
new file mode 100644
index 0000000..d7057bd
Binary files /dev/null and b/elm/todolist/public/favicon.ico differ
diff --git a/elm/todolist/public/index.html b/elm/todolist/public/index.html
new file mode 100644
index 0000000..7bef8ed
--- /dev/null
+++ b/elm/todolist/public/index.html
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+ Elm App
+
+
+
+
+
+
diff --git a/elm/todolist/public/logo.svg b/elm/todolist/public/logo.svg
new file mode 100644
index 0000000..4321d4d
--- /dev/null
+++ b/elm/todolist/public/logo.svg
@@ -0,0 +1,39 @@
+
+
+
+
diff --git a/elm/todolist/public/manifest.json b/elm/todolist/public/manifest.json
new file mode 100644
index 0000000..9b7dc41
--- /dev/null
+++ b/elm/todolist/public/manifest.json
@@ -0,0 +1,15 @@
+{
+ "short_name": "Elm App",
+ "name": "Create Elm App Sample",
+ "icons": [
+ {
+ "src": "favicon.ico",
+ "sizes": "192x192",
+ "type": "image/png"
+ }
+ ],
+ "start_url": "./index.html",
+ "display": "standalone",
+ "theme_color": "#000000",
+ "background_color": "#ffffff"
+}
diff --git a/elm/todolist/src/Main.elm b/elm/todolist/src/Main.elm
new file mode 100644
index 0000000..8413e38
--- /dev/null
+++ b/elm/todolist/src/Main.elm
@@ -0,0 +1,167 @@
+module Main exposing (..)
+
+import Browser
+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
+init =
+ Model [] 0 ""
+
+
+
+---- UPDATE ----
+
+
+type Msg
+ = AddTodo
+ | DeleteTodo Int
+ | ToggleTodoCompletion Int
+ | SetNextTodoText String
+
+
+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 }
+
+
+
+---- VIEW ----
+
+
+view : Model -> Html Msg
+view model =
+ 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" ]
+ ]
+
+
+
+---- PROGRAM ----
+
+
+main : Program () Model Msg
+main =
+ Browser.sandbox
+ { view = view
+ , init = init
+ , update = update
+ }
diff --git a/elm/todolist/src/index.js b/elm/todolist/src/index.js
new file mode 100644
index 0000000..c9bbd20
--- /dev/null
+++ b/elm/todolist/src/index.js
@@ -0,0 +1,12 @@
+import './main.css';
+import { Elm } from './Main.elm';
+import * as serviceWorker from './serviceWorker';
+
+Elm.Main.init({
+ node: document.getElementById('root')
+});
+
+// If you want your app to work offline and load faster, you can change
+// unregister() to register() below. Note this comes with some pitfalls.
+// Learn more about service workers: https://bit.ly/CRA-PWA
+serviceWorker.unregister();
diff --git a/elm/todolist/src/main.css b/elm/todolist/src/main.css
new file mode 100644
index 0000000..206b7b0
--- /dev/null
+++ b/elm/todolist/src/main.css
@@ -0,0 +1,111 @@
+/*
+ elm-hot creates an additional div wrapper around the app to make HMR possible.
+ This could break styling in development mode if you are using Elm UI.
+
+ More context in the issue:
+ https://github.com/halfzebra/create-elm-app/issues/320
+*/
+[data-elm-hot="true"] {
+ 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;
+ margin: 0;
+ background: var(--primary);
+
+ color: var(--on-secondary);
+}
+
+h1 {
+ font-size: 30px;
+}
+
+.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);
+}
diff --git a/elm/todolist/src/serviceWorker.js b/elm/todolist/src/serviceWorker.js
new file mode 100644
index 0000000..f8c7e50
--- /dev/null
+++ b/elm/todolist/src/serviceWorker.js
@@ -0,0 +1,135 @@
+// This optional code is used to register a service worker.
+// register() is not called by default.
+
+// This lets the app load faster on subsequent visits in production, and gives
+// it offline capabilities. However, it also means that developers (and users)
+// will only see deployed updates on subsequent visits to a page, after all the
+// existing tabs open on the page have been closed, since previously cached
+// resources are updated in the background.
+
+// To learn more about the benefits of this model and instructions on how to
+// opt-in, read https://bit.ly/CRA-PWA
+
+const isLocalhost = Boolean(
+ window.location.hostname === 'localhost' ||
+ // [::1] is the IPv6 localhost address.
+ window.location.hostname === '[::1]' ||
+ // 127.0.0.1/8 is considered localhost for IPv4.
+ window.location.hostname.match(
+ /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
+ )
+);
+
+export function register(config) {
+ if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
+ // The URL constructor is available in all browsers that support SW.
+ const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
+ if (publicUrl.origin !== window.location.origin) {
+ // Our service worker won't work if PUBLIC_URL is on a different origin
+ // from what our page is served on. This might happen if a CDN is used to
+ // serve assets; see https://github.com/facebook/create-react-app/issues/2374
+ return;
+ }
+
+ window.addEventListener('load', () => {
+ const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
+
+ if (isLocalhost) {
+ // This is running on localhost. Let's check if a service worker still exists or not.
+ checkValidServiceWorker(swUrl, config);
+
+ // Add some additional logging to localhost, pointing developers to the
+ // service worker/PWA documentation.
+ navigator.serviceWorker.ready.then(() => {
+ console.log(
+ 'This web app is being served cache-first by a service ' +
+ 'worker. To learn more, visit https://bit.ly/CRA-PWA'
+ );
+ });
+ } else {
+ // Is not localhost. Just register service worker
+ registerValidSW(swUrl, config);
+ }
+ });
+ }
+}
+
+function registerValidSW(swUrl, config) {
+ navigator.serviceWorker
+ .register(swUrl)
+ .then(registration => {
+ registration.onupdatefound = () => {
+ const installingWorker = registration.installing;
+ if (installingWorker == null) {
+ return;
+ }
+ installingWorker.onstatechange = () => {
+ if (installingWorker.state === 'installed') {
+ if (navigator.serviceWorker.controller) {
+ // At this point, the updated precached content has been fetched,
+ // but the previous service worker will still serve the older
+ // content until all client tabs are closed.
+ console.log(
+ 'New content is available and will be used when all ' +
+ 'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
+ );
+
+ // Execute callback
+ if (config && config.onUpdate) {
+ config.onUpdate(registration);
+ }
+ } else {
+ // At this point, everything has been precached.
+ // It's the perfect time to display a
+ // "Content is cached for offline use." message.
+ console.log('Content is cached for offline use.');
+
+ // Execute callback
+ if (config && config.onSuccess) {
+ config.onSuccess(registration);
+ }
+ }
+ }
+ };
+ };
+ })
+ .catch(error => {
+ console.error('Error during service worker registration:', error);
+ });
+}
+
+function checkValidServiceWorker(swUrl, config) {
+ // Check if the service worker can be found. If it can't reload the page.
+ fetch(swUrl)
+ .then(response => {
+ // Ensure service worker exists, and that we really are getting a JS file.
+ const contentType = response.headers.get('content-type');
+ if (
+ response.status === 404 ||
+ (contentType != null && contentType.indexOf('javascript') === -1)
+ ) {
+ // No service worker found. Probably a different app. Reload the page.
+ navigator.serviceWorker.ready.then(registration => {
+ registration.unregister().then(() => {
+ window.location.reload();
+ });
+ });
+ } else {
+ // Service worker found. Proceed as normal.
+ registerValidSW(swUrl, config);
+ }
+ })
+ .catch(() => {
+ console.log(
+ 'No internet connection found. App is running in offline mode.'
+ );
+ });
+}
+
+export function unregister() {
+ if ('serviceWorker' in navigator) {
+ navigator.serviceWorker.ready.then(registration => {
+ registration.unregister();
+ });
+ }
+}