aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--.gitignore2
-rw-r--r--document.css78
-rw-r--r--elm.json24
-rw-r--r--index.html31
-rw-r--r--src/Main.elm101
-rw-r--r--src/Protocol.elm24
6 files changed, 260 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..2bc193d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+elm-stuff/*
+app.js
diff --git a/document.css b/document.css
new file mode 100644
index 0000000..e4379b6
--- /dev/null
+++ b/document.css
@@ -0,0 +1,78 @@
+/*@font-face {
+ font-family: "sharetech";
+ src: url('{{ "/assets/ShareTech-Regular.ttf" | prepend: site.baseurl }}');
+}*/
+
+body {
+ color: white;
+ background-color: #222;
+ text-align: center;
+}
+
+img {
+ position: absolute;
+ top: 0;
+ left: 0;
+ object-fit: contain;
+ width: 100%;
+ height: 100%;
+}
+
+
+.slides {
+ width: 100%;
+ height: 90vh;
+ position: relative;
+}
+
+.controls {
+ margin: auto;
+}
+
+button, .button {
+ border-radius: 4px;
+ margin: 1em;
+ padding: 0.3em;
+ padding-right: 0.5em;
+ padding-left: 0.5em;
+ background-color: #fb0;
+ border: none;
+ box-shadow: 0px 0px 7px 7px #fb03;
+ font-size: 20px;
+ color: black !important;
+ text-decoration: none;
+ cursor: pointer;
+ transition: 0.2s;
+ font-family: "sharetech";
+}
+
+.button a {
+ color: #000;
+}
+
+button:enabled:hover, .button:hover {
+ box-shadow: 0px 0px 7px 7px #fb09;
+}
+
+button:enabled:active {
+ background-color: #615400;
+ color: #eee;
+}
+
+button:disabled {
+ filter: grayscale(100%);
+ -webkit-filter: grayscale(100%);
+ cursor: not-allowed;
+}
+
+button.pressed {
+ background-color: #615400;
+ color: #eee;
+}
+
+
+
+
+
+
+
diff --git a/elm.json b/elm.json
new file mode 100644
index 0000000..2d0d191
--- /dev/null
+++ b/elm.json
@@ -0,0 +1,24 @@
+{
+ "type": "application",
+ "source-directories": [
+ "src"
+ ],
+ "elm-version": "0.19.1",
+ "dependencies": {
+ "direct": {
+ "elm/browser": "1.0.2",
+ "elm/core": "1.0.5",
+ "elm/html": "1.0.0",
+ "elm/json": "1.1.3"
+ },
+ "indirect": {
+ "elm/time": "1.0.0",
+ "elm/url": "1.0.0",
+ "elm/virtual-dom": "1.0.2"
+ }
+ },
+ "test-dependencies": {
+ "direct": {},
+ "indirect": {}
+ }
+}
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..32c90e2
--- /dev/null
+++ b/index.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>Main</title>
+ <script src="app.js"></script>
+ <link rel="stylesheet" type="text/css" href="document.css" />
+</head>
+
+<body>
+ <div id="elm"></div>
+ <script>
+ var app = Elm.Main.init({
+ node: document.getElementById('elm')
+ });
+
+ let ws = new WebSocket("ws://localhost:9160")
+
+ ws.onopen = () => ws.send ("{\"room\":\"testroom\"}");
+
+ ws.onmessage = function(msg) {
+ console.log(msg.data)
+ app.ports.recvPort.send(msg.data)
+ }
+
+ app.ports.sendPort.subscribe(function(msg) {
+ ws.send(msg)
+ })
+ </script>
+</body>
+</html>
diff --git a/src/Main.elm b/src/Main.elm
new file mode 100644
index 0000000..5066ad6
--- /dev/null
+++ b/src/Main.elm
@@ -0,0 +1,101 @@
+port module Main exposing (..)
+
+import Browser
+import Html exposing (Html, button, div, text, br, input, img)
+import Html.Attributes exposing (style, class, value, placeholder, src)
+import Html.Events exposing (onInput, onClick)
+import Html.Lazy exposing (lazy)
+import List exposing (foldr)
+
+import Protocol exposing (decodeState, encodeState)
+import Json.Decode as D
+
+
+main = Browser.document
+ { init = init
+ , update = update
+ , view = view
+ , subscriptions = subscriptions
+ }
+
+ -- subscribe to messages from the websocket
+subscriptions : Model -> Sub Msg
+subscriptions model = recvPort GotMessage
+
+ -- corresponding ports for the subscriptions
+port recvPort : (String -> msg) -> Sub msg
+port sendPort : String -> Cmd msg
+
+
+{- STATE AND STATE TRANSITIONS -}
+
+ -- the main client state
+type alias Model = { slide : Int, max : Int }
+
+ -- start at the first slide, for now just assume that we have ten slides
+init : () -> (Model, Cmd Msg)
+init f = ( { slide = 0, max = 10 }, Cmd.none)
+
+ -- possible state transitions:
+type Msg = GotMessage String
+ | NextSlide
+ | PrevSlide
+
+ -- update stuff
+update : Msg -> Model -> (Model, Cmd Msg)
+update msg model = case msg of
+ NextSlide -> updateSlide ((+) 1) model
+ PrevSlide -> updateSlide ((+) -1) model
+ GotMessage text ->
+ case decodeState text of
+ Nothing -> (model, Cmd.none)
+ Just newstate -> ({model | slide = newstate.state }, Cmd.none)
+
+ -- while changing slides we must take care not to end up in an invalid
+ -- state (where slide > max), so this logic gets its own function
+tweakSlide : (Int -> Int) -> Model -> Model
+tweakSlide f model = { model | slide = modBy model.max (f model.slide) }
+
+updateSlide : (Int -> Int) -> Model -> (Model, Cmd Msg)
+updateSlide f model =
+ let new = tweakSlide f model
+ in (new, sendPort (encodeState { state = new.slide }))
+
+{- VIEW COMPONENTS -}
+
+ -- our body consists of a slide container and a couple controls
+body : Model -> Html Msg
+body model = div []
+ [ div [class "slides"] (slideView model.slide model.max)
+ , div [class "controls"]
+ [ button [ onClick PrevSlide ] [ text "←" ]
+ , text (String.fromInt (model.slide+1))
+ , button [ onClick NextSlide ] [ text "→" ]
+ ]
+ ]
+
+ -- the slide view is just a list of img tags, one for each slide; note
+ -- that these are all loaded on startup, with visibility done through
+ -- z indices so we won't have lags while switching between them.
+slideView : Int -> Int -> List (Html Msg)
+slideView i max = List.range 0 (max - 1)
+ |> List.map (\x -> ("example/" ++ (padNum 2 (x+1)) ++ ".png", x == i))
+ |> List.map (\(path,t) -> img [src path, onTop t] [])
+
+onTop t = style "z-index" (String.fromInt (if t then 10 else 0))
+
+ -- format an int with prefixed 0's — apparently elm doesn't have a
+ -- standard function for that, but it's needed for slide filenames
+padNum : Int -> Int -> String
+padNum l num =
+ let str = String.fromInt num
+ in if (String.length str) > l then str else
+ (String.repeat (l - String.length str) "0") ++ str
+
+
+view : Model -> Browser.Document Msg
+view model = { title = "Websockets example"
+ , body = [body model]
+ }
+
+
diff --git a/src/Protocol.elm b/src/Protocol.elm
new file mode 100644
index 0000000..371a23a
--- /dev/null
+++ b/src/Protocol.elm
@@ -0,0 +1,24 @@
+module Protocol exposing (State, encodeState, decodeState)
+
+import Json.Decode as D
+import Json.Encode as E
+
+{- PROTOCOL -}
+
+ -- for now, this is still very boring and just has one field:
+type alias State = { state : Int }
+
+encodeState : State -> String
+encodeState state =
+ E.object [ ("state", E.int state.state) ]
+ |> E.encode 0
+
+
+stateDecoder : D.Decoder State
+stateDecoder = D.map State (D.field "state" D.int)
+
+decodeState : String -> Maybe State
+decodeState text = case D.decodeString (D.nullable stateDecoder) text of
+ Err e -> Nothing
+ Ok value -> value
+