├── screenshot.png ├── .gitignore ├── src ├── Bridge.elm ├── Env.elm ├── Shared │ ├── Msg.elm │ └── Model.elm ├── Types.elm ├── Frontend.elm ├── Backend.elm ├── Shared.elm └── Pages │ └── Home_.elm ├── elm-land.json ├── elm.json └── README.md /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elm-land/lamdera/HEAD/screenshot.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /dist 2 | /.elm-land 3 | /.env 4 | /elm-stuff 5 | /node_modules 6 | .DS_Store 7 | *.pem -------------------------------------------------------------------------------- /src/Bridge.elm: -------------------------------------------------------------------------------- 1 | module Bridge exposing (..) 2 | 3 | 4 | type ToBackend 5 | = SmashedLikeButton 6 | -------------------------------------------------------------------------------- /src/Env.elm: -------------------------------------------------------------------------------- 1 | module Env exposing (..) 2 | 3 | -- The Env.elm file is for per-environment configuration. 4 | -- See https://dashboard.lamdera.app/docs/environment for more info. 5 | 6 | 7 | dummyConfigItem = 8 | "" -------------------------------------------------------------------------------- /src/Shared/Msg.elm: -------------------------------------------------------------------------------- 1 | module Shared.Msg exposing (Msg(..)) 2 | 3 | {-| -} 4 | 5 | 6 | {-| Normally, this value would live in "Shared.elm" 7 | but that would lead to a circular dependency import cycle. 8 | 9 | For that reason, both `Shared.Model` and `Shared.Msg` are in their 10 | own file, so they can be imported by `Effect.elm` 11 | 12 | -} 13 | type Msg 14 | = GotNewSmashedLikes Int 15 | -------------------------------------------------------------------------------- /src/Shared/Model.elm: -------------------------------------------------------------------------------- 1 | module Shared.Model exposing (Model) 2 | 3 | {-| -} 4 | 5 | 6 | {-| Normally, this value would live in "Shared.elm" 7 | but that would lead to a circular dependency import cycle. 8 | 9 | For that reason, both `Shared.Model` and `Shared.Msg` are in their 10 | own file, so they can be imported by `Effect.elm` 11 | 12 | -} 13 | type alias Model = 14 | { smashedLikes : Int } 15 | -------------------------------------------------------------------------------- /src/Types.elm: -------------------------------------------------------------------------------- 1 | module Types exposing (..) 2 | 3 | import Bridge 4 | import Browser exposing (UrlRequest) 5 | import Browser.Navigation exposing (Key) 6 | import Lamdera exposing (ClientId, SessionId) 7 | import Main as ElmLand 8 | import Url exposing (Url) 9 | 10 | 11 | type alias FrontendModel = 12 | ElmLand.Model 13 | 14 | 15 | type alias BackendModel = 16 | { smashedLikes : Int 17 | } 18 | 19 | 20 | type alias FrontendMsg = 21 | ElmLand.Msg 22 | 23 | 24 | type alias ToBackend = 25 | Bridge.ToBackend 26 | 27 | 28 | type BackendMsg 29 | = OnConnect SessionId ClientId 30 | 31 | 32 | type ToFrontend 33 | = NewSmashedLikes Int 34 | -------------------------------------------------------------------------------- /elm-land.json: -------------------------------------------------------------------------------- 1 | { 2 | "app": { 3 | "elm": { 4 | "development": { "debugger": true }, 5 | "production": { "debugger": false } 6 | }, 7 | "env": [], 8 | "html": { 9 | "attributes": { 10 | "html": { "lang": "en" }, 11 | "head": {} 12 | }, 13 | "title": "Elm Land", 14 | "meta": [ 15 | { "charset": "UTF-8" }, 16 | { "http-equiv": "X-UA-Compatible", "content": "IE=edge" }, 17 | { "name": "viewport", "content": "width=device-width, initial-scale=1.0" } 18 | ], 19 | "link": [], 20 | "script": [] 21 | }, 22 | "router": { 23 | "useHashRouting": false 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /elm.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "application", 3 | "source-directories": [ 4 | "src", 5 | ".elm-land/src" 6 | ], 7 | "elm-version": "0.19.1", 8 | "dependencies": { 9 | "direct": { 10 | "elm/browser": "1.0.2", 11 | "elm/core": "1.0.5", 12 | "elm/html": "1.0.0", 13 | "elm/http": "2.0.0", 14 | "elm/json": "1.1.3", 15 | "elm/time": "1.0.0", 16 | "elm/url": "1.0.0", 17 | "lamdera/codecs": "1.0.0", 18 | "lamdera/core": "1.0.0" 19 | }, 20 | "indirect": { 21 | "elm/bytes": "1.0.8", 22 | "elm/file": "1.0.5", 23 | "elm/virtual-dom": "1.0.3" 24 | } 25 | }, 26 | "test-dependencies": { 27 | "direct": {}, 28 | "indirect": {} 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # @elm-land/lamdera 2 | > How to use [Lamdera](https://lamdera.com/) and [Elm Land](https://elm.land) together! 3 | 4 | ![A screenshot of the app running](./screenshot.png) 5 | 6 | Live demo: https://elm-land.lamdera.app 7 | 8 | ## Local development 9 | 10 | Once you have the latest Lamdera and Elm Land v0.19.0 on your local machine, you can run this project with two separate commands: 11 | 12 | ```sh 13 | elm-land server 14 | ``` 15 | 16 | ```sh 17 | lamdera live 18 | ``` 19 | 20 | And open up the running project at `http://0.0.0.0:8000` 21 | 22 | 23 | ( __Note:__ There's no special `elm-land watch` command for now, so please excuse the extra unused dev server! ) 24 | 25 | 26 | ## Deploying 27 | 28 | Lamdera doesn't currently build `elm-land` remotely, so to deploy we'll need to commit all the gen files. 29 | 30 | ``` 31 | elm-land build 32 | git add -f .elm-land/src 33 | lamdera check 34 | lamdera deploy 35 | ``` 36 | -------------------------------------------------------------------------------- /src/Frontend.elm: -------------------------------------------------------------------------------- 1 | module Frontend exposing (..) 2 | 3 | import Browser exposing (UrlRequest(..)) 4 | import Browser.Navigation as Nav 5 | import Html 6 | import Html.Attributes as Attr 7 | import Json.Encode 8 | import Lamdera 9 | import Main as ElmLand 10 | import Pages.Home_ 11 | import Shared.Msg 12 | import Task 13 | import Time 14 | import Types exposing (..) 15 | import Url 16 | 17 | 18 | type alias Model = 19 | FrontendModel 20 | 21 | 22 | app = 23 | Lamdera.frontend 24 | { init = ElmLand.init Json.Encode.null 25 | , onUrlRequest = ElmLand.UrlRequested 26 | , onUrlChange = ElmLand.UrlChanged 27 | , update = ElmLand.update 28 | , updateFromBackend = updateFromBackend 29 | , subscriptions = ElmLand.subscriptions 30 | , view = ElmLand.view 31 | } 32 | 33 | 34 | updateFromBackend : ToFrontend -> Model -> ( Model, Cmd FrontendMsg ) 35 | updateFromBackend msg model = 36 | case msg of 37 | NewSmashedLikes smashedLikes -> 38 | ( model, sendSharedMsg <| Shared.Msg.GotNewSmashedLikes smashedLikes ) 39 | 40 | 41 | sendSharedMsg : Shared.Msg.Msg -> Cmd FrontendMsg 42 | sendSharedMsg msg = 43 | Time.now |> Task.perform (always (ElmLand.Shared msg)) 44 | -------------------------------------------------------------------------------- /src/Backend.elm: -------------------------------------------------------------------------------- 1 | module Backend exposing (..) 2 | 3 | import Bridge exposing (..) 4 | import Html 5 | import Lamdera exposing (ClientId, SessionId) 6 | import Types exposing (BackendModel, BackendMsg(..), ToFrontend(..)) 7 | 8 | 9 | type alias Model = 10 | BackendModel 11 | 12 | 13 | app = 14 | Lamdera.backend 15 | { init = init 16 | , update = update 17 | , updateFromFrontend = updateFromFrontend 18 | , subscriptions = \m -> Lamdera.onConnect OnConnect 19 | } 20 | 21 | 22 | init : ( Model, Cmd BackendMsg ) 23 | init = 24 | ( { smashedLikes = 0 } 25 | , Cmd.none 26 | ) 27 | 28 | 29 | update : BackendMsg -> Model -> ( Model, Cmd BackendMsg ) 30 | update msg model = 31 | case msg of 32 | OnConnect sid cid -> 33 | ( model, Lamdera.sendToFrontend cid <| NewSmashedLikes model.smashedLikes ) 34 | 35 | 36 | updateFromFrontend : SessionId -> ClientId -> ToBackend -> Model -> ( Model, Cmd BackendMsg ) 37 | updateFromFrontend sessionId clientId msg model = 38 | case msg of 39 | SmashedLikeButton -> 40 | let 41 | newSmashedLikes = 42 | model.smashedLikes + 1 43 | in 44 | ( { model | smashedLikes = newSmashedLikes }, Lamdera.broadcast <| NewSmashedLikes newSmashedLikes ) 45 | -------------------------------------------------------------------------------- /src/Shared.elm: -------------------------------------------------------------------------------- 1 | module Shared exposing 2 | ( Flags, decoder 3 | , Model, Msg 4 | , init, update, subscriptions 5 | ) 6 | 7 | {-| 8 | 9 | @docs Flags, decoder 10 | @docs Model, Msg 11 | @docs init, update, subscriptions 12 | 13 | -} 14 | 15 | import Effect exposing (Effect) 16 | import Json.Decode 17 | import Route exposing (Route) 18 | import Route.Path 19 | import Shared.Model 20 | import Shared.Msg 21 | 22 | 23 | 24 | -- FLAGS 25 | 26 | 27 | type alias Flags = 28 | {} 29 | 30 | 31 | decoder : Json.Decode.Decoder Flags 32 | decoder = 33 | Json.Decode.succeed {} 34 | 35 | 36 | 37 | -- INIT 38 | 39 | 40 | type alias Model = 41 | Shared.Model.Model 42 | 43 | 44 | init : Result Json.Decode.Error Flags -> Route () -> ( Model, Effect Msg ) 45 | init flagsResult route = 46 | ( { smashedLikes = 0 } 47 | , Effect.none 48 | ) 49 | 50 | 51 | 52 | -- UPDATE 53 | 54 | 55 | type alias Msg = 56 | Shared.Msg.Msg 57 | 58 | 59 | update : Route () -> Msg -> Model -> ( Model, Effect Msg ) 60 | update route msg model = 61 | case msg of 62 | Shared.Msg.GotNewSmashedLikes count -> 63 | ( { model | smashedLikes = count } 64 | , Effect.none 65 | ) 66 | 67 | 68 | 69 | -- SUBSCRIPTIONS 70 | 71 | 72 | subscriptions : Route () -> Model -> Sub Msg 73 | subscriptions route model = 74 | Sub.none 75 | -------------------------------------------------------------------------------- /src/Pages/Home_.elm: -------------------------------------------------------------------------------- 1 | module Pages.Home_ exposing (Model, Msg(..), page) 2 | 3 | import Bridge 4 | import Effect exposing (..) 5 | import Html exposing (..) 6 | import Html.Attributes exposing (..) 7 | import Html.Events exposing (onClick) 8 | import Lamdera 9 | import Page exposing (Page) 10 | import Route exposing (Route) 11 | import Shared 12 | import View exposing (View) 13 | 14 | 15 | page : Shared.Model -> Route () -> Page Model Msg 16 | page shared route = 17 | Page.new 18 | { init = init 19 | , update = update 20 | , subscriptions = subscriptions 21 | , view = view shared 22 | } 23 | 24 | 25 | 26 | -- INIT 27 | 28 | 29 | type alias Model = 30 | {} 31 | 32 | 33 | init : () -> ( Model, Effect Msg ) 34 | init _ = 35 | ( {} 36 | , Effect.none 37 | ) 38 | 39 | 40 | 41 | -- UPDATE 42 | 43 | 44 | type Msg 45 | = SmashedLikeButton 46 | 47 | 48 | update : Msg -> Model -> ( Model, Effect Msg ) 49 | update msg model = 50 | case msg of 51 | SmashedLikeButton -> 52 | ( model 53 | , Effect.sendCmd <| Lamdera.sendToBackend Bridge.SmashedLikeButton 54 | ) 55 | 56 | 57 | 58 | -- SUBSCRIPTIONS 59 | 60 | 61 | subscriptions : Model -> Sub Msg 62 | subscriptions model = 63 | Sub.none 64 | 65 | 66 | 67 | -- VIEW 68 | 69 | 70 | view : Shared.Model -> Model -> View Msg 71 | view shared model = 72 | { title = "Elm Land ❤️ Lamdera" 73 | , body = 74 | [ node "style" [] [ text """ 75 | @import url('https://fonts.googleapis.com/css2?family=Lora:wght@600&family=Nunito+Sans&display=swap'); 76 | 77 | html { 78 | height: 100%; 79 | color: white; 80 | background: linear-gradient(dodgerblue, #339); 81 | } 82 | body { 83 | display: flex; 84 | flex-direction: column; 85 | margin: 0; 86 | justify-content: center; 87 | align-items: center; 88 | height: 90vh; 89 | font-family: 'Lora'; 90 | } 91 | h1 { 92 | margin: 0; 93 | font-weight: 600 !important; 94 | } 95 | """ ] 96 | , div [ style "display" "flex", style "gap" "1rem" ] 97 | [ img 98 | [ alt "Lando, the Elm Land Rainbow" 99 | , src "https://elm.land/images/logo-480.png" 100 | , style "width" "128px" 101 | , style "margin-right" "2.5rem" 102 | ] 103 | [] 104 | , img 105 | [ alt "Laurie, the Lamdera Lambda Llamba" 106 | , src "https://lamdera.com/images/llama/floaty.png" 107 | , style "width" "81.4px" 108 | , style "margin-right" "1.5rem" 109 | , style "height" "108.4px" 110 | ] 111 | [] 112 | ] 113 | , h1 [] [ text "Elm Land ❤️ Lamdera" ] 114 | , p 115 | [ style "font-family" "Nunito Sans" 116 | , style "opacity" "0.75" 117 | ] 118 | [ text "It's working, Mario!!" ] 119 | , p 120 | [ style "font-family" "Nunito Sans" 121 | , style "cursor" "pointer" 122 | , style "background-color" "#ffffff40" 123 | , style "padding" "5px" 124 | , style "border-radius" "5px" 125 | , style "user-select" "none" 126 | , onClick SmashedLikeButton 127 | ] 128 | [ text <| "👍 " ++ String.fromInt shared.smashedLikes ] 129 | ] 130 | } 131 | --------------------------------------------------------------------------------