diff options
Diffstat (limited to 'picarones-elm')
-rw-r--r-- | picarones-elm/default.nix | 51 | ||||
-rw-r--r-- | picarones-elm/document.css | 78 | ||||
-rw-r--r-- | picarones-elm/elm-srcs.nix | 37 | ||||
-rw-r--r-- | picarones-elm/elm.json | 24 | ||||
-rw-r--r-- | picarones-elm/index.html | 31 | ||||
-rw-r--r-- | picarones-elm/registry.dat | bin | 0 -> 106150 bytes | |||
-rw-r--r-- | picarones-elm/src/Main.elm | 101 | ||||
-rw-r--r-- | picarones-elm/src/Protocol.elm | 24 |
8 files changed, 346 insertions, 0 deletions
diff --git a/picarones-elm/default.nix b/picarones-elm/default.nix new file mode 100644 index 0000000..d597f70 --- /dev/null +++ b/picarones-elm/default.nix @@ -0,0 +1,51 @@ +{ nixpkgs ? <nixpkgs> +, config ? {} +}: + +with (import nixpkgs config); + +let + mkDerivation = + { srcs ? ./elm-srcs.nix + , src + , name + , srcdir ? "./src" + , targets ? [] + , registryDat ? ./registry.dat + , outputJavaScript ? true + }: + stdenv.mkDerivation { + inherit name src; + + buildInputs = [ elmPackages.elm ]; + + buildPhase = pkgs.elmPackages.fetchElmDeps { + elmPackages = import srcs; + elmVersion = "0.19.1"; + inherit registryDat; + }; + + installPhase = let + elmfile = module: "${srcdir}/${builtins.replaceStrings ["."] ["/"] module}.elm"; + extension = if outputJavaScript then "js" else "html"; + in '' + mkdir -p $out/share/doc + ${lib.concatStrings (map (module: '' + echo "compiling ${elmfile module}" + elm make ${elmfile module} --output $out/${module}.${extension} + '') targets)} + cp *.html $out + cp *.css $out + cp -r example $out/ + ''; + }; +in mkDerivation { + name = "picarones"; + + srcs = ./elm-srcs.nix; + src = ./.; + targets = ["Main"]; + srcdir = "./src"; + outputJavaScript = true; +} + diff --git a/picarones-elm/document.css b/picarones-elm/document.css new file mode 100644 index 0000000..e4379b6 --- /dev/null +++ b/picarones-elm/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/picarones-elm/elm-srcs.nix b/picarones-elm/elm-srcs.nix new file mode 100644 index 0000000..becbc8c --- /dev/null +++ b/picarones-elm/elm-srcs.nix @@ -0,0 +1,37 @@ +{ + + "elm/json" = { + sha256 = "0kjwrz195z84kwywaxhhlnpl3p251qlbm5iz6byd6jky2crmyqyh"; + version = "1.1.3"; + }; + + "elm/html" = { + sha256 = "1n3gpzmpqqdsldys4ipgyl1zacn0kbpc3g4v3hdpiyfjlgh8bf3k"; + version = "1.0.0"; + }; + + "elm/browser" = { + sha256 = "0nagb9ajacxbbg985r4k9h0jadqpp0gp84nm94kcgbr5sf8i9x13"; + version = "1.0.2"; + }; + + "elm/core" = { + sha256 = "19w0iisdd66ywjayyga4kv2p1v9rxzqjaxhckp8ni6n8i0fb2dvf"; + version = "1.0.5"; + }; + + "elm/url" = { + sha256 = "0av8x5syid40sgpl5vd7pry2rq0q4pga28b4yykn9gd9v12rs3l4"; + version = "1.0.0"; + }; + + "elm/time" = { + sha256 = "0vch7i86vn0x8b850w1p69vplll1bnbkp8s383z7pinyg94cm2z1"; + version = "1.0.0"; + }; + + "elm/virtual-dom" = { + sha256 = "0q1v5gi4g336bzz1lgwpn5b1639lrn63d8y6k6pimcyismp2i1yg"; + version = "1.0.2"; + }; +} diff --git a/picarones-elm/elm.json b/picarones-elm/elm.json new file mode 100644 index 0000000..2d0d191 --- /dev/null +++ b/picarones-elm/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/picarones-elm/index.html b/picarones-elm/index.html new file mode 100644 index 0000000..66fe158 --- /dev/null +++ b/picarones-elm/index.html @@ -0,0 +1,31 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="UTF-8"> + <title>Main</title> + <script src="Main.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/picarones-elm/registry.dat b/picarones-elm/registry.dat Binary files differnew file mode 100644 index 0000000..53c3803 --- /dev/null +++ b/picarones-elm/registry.dat diff --git a/picarones-elm/src/Main.elm b/picarones-elm/src/Main.elm new file mode 100644 index 0000000..5066ad6 --- /dev/null +++ b/picarones-elm/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/picarones-elm/src/Protocol.elm b/picarones-elm/src/Protocol.elm new file mode 100644 index 0000000..371a23a --- /dev/null +++ b/picarones-elm/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 + |