aboutsummaryrefslogtreecommitdiff
path: root/picarones-elm
diff options
context:
space:
mode:
Diffstat (limited to 'picarones-elm')
-rw-r--r--picarones-elm/default.nix51
-rw-r--r--picarones-elm/document.css78
-rw-r--r--picarones-elm/elm-srcs.nix37
-rw-r--r--picarones-elm/elm.json24
-rw-r--r--picarones-elm/index.html31
-rw-r--r--picarones-elm/registry.datbin0 -> 106150 bytes
-rw-r--r--picarones-elm/src/Main.elm101
-rw-r--r--picarones-elm/src/Protocol.elm24
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
new file mode 100644
index 0000000..53c3803
--- /dev/null
+++ b/picarones-elm/registry.dat
Binary files differ
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
+