Merge pull request 'convert_to_actix' (#2) from convert_to_actix into main
Reviewed-on: Yehowshua/example-spa-elm-app#2
This commit is contained in:
commit
1634ea7056
16
.gitignore
vendored
16
.gitignore
vendored
|
@ -1,6 +1,10 @@
|
|||
elm-stuff/
|
||||
elm.js
|
||||
elm.min.js
|
||||
node_modules/
|
||||
package-lock.json
|
||||
package.json
|
||||
frontend/elm-stuff/
|
||||
frontend/elm.js
|
||||
frontend/elm.min.js
|
||||
frontend/node_modules/
|
||||
frontend/package-lock.json
|
||||
frontend/package.json
|
||||
backend/target
|
||||
public/*
|
||||
elm-stuff/*
|
||||
result
|
||||
|
|
54
Makefile
54
Makefile
|
@ -1,10 +1,50 @@
|
|||
SRC_FILES := $(shell find src -name "*.elm")
|
||||
# Directories
|
||||
FRONTEND_DIR := ./frontend
|
||||
BACKEND_DIR := ./backend
|
||||
PUBLIC_DIR := ./public
|
||||
ASSET_DIR := ./assets
|
||||
|
||||
all: elm.min.js
|
||||
# Commands
|
||||
ELM_MAKE := elm make
|
||||
CARGO_BUILD := cargo build
|
||||
CARGO_RUN := cargo run
|
||||
|
||||
serve: all
|
||||
python3 -m http.server
|
||||
ifeq ($(DEBUG)$(RELEASE),) # Both are empty
|
||||
$(error You must set exactly one of DEBUG=1 or RELEASE=1)
|
||||
endif
|
||||
|
||||
elm.min.js: $(SRC_FILES)
|
||||
rm -f elm.min.js elm.js
|
||||
./optimize.sh src/Main.elm
|
||||
ifeq ($(DEBUG)$(RELEASE),11) # Both are set
|
||||
$(error Both DEBUG and RELEASE cannot be set at the same time)
|
||||
endif
|
||||
|
||||
# Targets
|
||||
.PHONY: all frontend backend clean serve
|
||||
|
||||
$(PUBLIC_DIR):
|
||||
mkdir -p $(PUBLIC_DIR)
|
||||
|
||||
$(PUBLIC_DIR)/index.html: $(FRONTEND_DIR)/index.html $(PUBLIC_DIR)
|
||||
cp $< $@
|
||||
|
||||
.PHONY: frontend backend
|
||||
|
||||
frontend: $(PUBLIC_DIR)/index.html $(PUBLIC_DIR)
|
||||
make -C $(FRONTEND_DIR)
|
||||
cp $(FRONTEND_DIR)/elm.min.js $(PUBLIC_DIR)/
|
||||
cp $(FRONTEND_DIR)/src/ports.websocket.js $(PUBLIC_DIR)/
|
||||
mkdir -p $(PUBLIC_DIR)/assets
|
||||
|
||||
backend:
|
||||
$(CARGO_BUILD) --manifest-path=$(BACKEND_DIR)/Cargo.toml
|
||||
|
||||
serve: frontend backend
|
||||
ifeq ($(DEBUG),1)
|
||||
RUST_LOG=info,actix_web=debug $(CARGO_RUN) --manifest-path=$(BACKEND_DIR)/Cargo.toml
|
||||
else ifeq ($(RELEASE),1)
|
||||
$(CARGO_RUN) --release --manifest-path=$(BACKEND_DIR)/Cargo.toml
|
||||
endif
|
||||
|
||||
clean:
|
||||
rm -rf $(PUBLIC_DIR)/*
|
||||
rm -rf $(BACKEND_DIR)/target
|
||||
make -C $(FRONTEND_DIR) clean
|
||||
|
|
26
README.md
26
README.md
|
@ -2,25 +2,25 @@
|
|||
Example demonstrating how one might architect a single page application
|
||||
Elm app.
|
||||
|
||||
# Dependencies MacOS
|
||||
```bash
|
||||
brew install node elm
|
||||
npm install -g uglify-js@2.4.11
|
||||
```
|
||||
|
||||
# Building
|
||||
```
|
||||
make serve
|
||||
```bash
|
||||
nix-shell -p elmPackages.elm cargo uglify-js
|
||||
make serve RELEASE=1 # can also do DEBUG=1 instead
|
||||
```
|
||||
|
||||
Now open `http://localhost:8000` in your browser.
|
||||
Now open `http://127.0.0.1:8080` in your browser.
|
||||
|
||||
# TODO
|
||||
|
||||
- [x] Add Makefile
|
||||
- [ ] Determine if `src/Body.elm` or pages in `sr/Page` should have subscription functions
|
||||
- [ ] use actix backend that maps most root requests to serve `actix_file::Files`
|
||||
- [ ] Submit to slack for feedback...
|
||||
- [ ] Refactor into router page
|
||||
- [ ] Handle back-navigation
|
||||
- [ ] `EventHandlers.onMessage` should inject successfully decoded message into
|
||||
Msg type directly...
|
||||
- [ ] Run `uglify` twice as per [this link](https://github.com/rtfeldman/elm-spa-example/tree/master?tab=readme-ov-file#production-build)
|
||||
- [ ] JSONify backend code for all send/receive
|
||||
- [ ] Backend should only communicate over websocket
|
||||
- [ ] Close websocket after 15s of no response(from frontend) to websocket pings
|
||||
- [ ] Implement dark mode
|
||||
- [ ] Add GPLV3 License
|
||||
- [ ] Add `make release` target that is nix ready...
|
||||
- [ ] Add `default.nix`
|
||||
|
|
1846
backend/Cargo.lock
generated
Normal file
1846
backend/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
15
backend/Cargo.toml
Normal file
15
backend/Cargo.toml
Normal file
|
@ -0,0 +1,15 @@
|
|||
[package]
|
||||
name = "backend"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
actix-web = "4.0"
|
||||
actix-files = "0.6"
|
||||
actix-web-actors = "4.0"
|
||||
actix = "0.13"
|
||||
chrono = "0.4" # For timestamp generation
|
||||
env_logger = "0.10" # For logging
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
log = "0.4"
|
14
backend/src/landing.rs
Normal file
14
backend/src/landing.rs
Normal file
|
@ -0,0 +1,14 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub enum DownMsg {
|
||||
Greeting(String),
|
||||
TimeUpdate(String),
|
||||
}
|
||||
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub enum UpMsg {
|
||||
RequestGreet(String)
|
||||
}
|
104
backend/src/main.rs
Normal file
104
backend/src/main.rs
Normal file
|
@ -0,0 +1,104 @@
|
|||
use actix::prelude::*;
|
||||
use actix_files::Files;
|
||||
use actix_web::{web, App, Error, HttpRequest, HttpResponse, HttpServer, Responder};
|
||||
use actix_web_actors::ws;
|
||||
use log::info;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{fs, time::Duration};
|
||||
use chrono::Local;
|
||||
|
||||
mod landing;
|
||||
|
||||
#[derive(Serialize)]
|
||||
enum DownMsg {
|
||||
Landing(landing::DownMsg),
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
enum UpMsg {
|
||||
Landing(landing::UpMsg),
|
||||
}
|
||||
|
||||
/// WebSocket actor
|
||||
struct MyWebSocket;
|
||||
|
||||
impl Actor for MyWebSocket {
|
||||
type Context = ws::WebsocketContext<Self>;
|
||||
|
||||
fn started(&mut self, ctx: &mut Self::Context) {
|
||||
info!("WebSocket actor started");
|
||||
ctx.run_interval(Duration::from_secs(1), |_, ctx| {
|
||||
let current_time = Local::now().format("%H:%M:%S").to_string();
|
||||
let message = DownMsg::Landing(landing::DownMsg::TimeUpdate(current_time));
|
||||
|
||||
if let Ok(json_message) = serde_json::to_string(&message) {
|
||||
ctx.text(json_message);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl StreamHandler<Result<ws::Message, ws::ProtocolError>> for MyWebSocket {
|
||||
fn handle(
|
||||
&mut self,
|
||||
msg: Result<ws::Message, ws::ProtocolError>,
|
||||
ctx: &mut ws::WebsocketContext<Self>,
|
||||
) {
|
||||
match msg {
|
||||
Ok(ws::Message::Text(text)) => {
|
||||
// Attempt to decode the incoming message
|
||||
if let Ok(up_msg) = serde_json::from_str::<UpMsg>(&text) {
|
||||
match up_msg {
|
||||
UpMsg::Landing(landing::UpMsg::RequestGreet(name)) => {
|
||||
let greeting = format!("Hello, {}!", name);
|
||||
let response = DownMsg::Landing(landing::DownMsg::Greeting(greeting));
|
||||
|
||||
if let Ok(json_response) = serde_json::to_string(&response) {
|
||||
ctx.text(json_response);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(ws::Message::Ping(msg)) => {
|
||||
ctx.pong(&msg);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async fn websocket_handler(req: HttpRequest, stream: web::Payload) -> Result<HttpResponse, Error> {
|
||||
ws::start(MyWebSocket {}, &req, stream)
|
||||
}
|
||||
|
||||
#[actix_web::main]
|
||||
async fn main() -> std::io::Result<()> {
|
||||
env_logger::init();
|
||||
|
||||
let address = "127.0.0.1";
|
||||
let port = std::env::var("EXAMPLE_ELM_APP_PORT")
|
||||
.unwrap_or("8080".to_string())
|
||||
.parse()
|
||||
.unwrap();
|
||||
|
||||
info!("Starting server at http://{}:{}", address, port);
|
||||
|
||||
HttpServer::new(|| {
|
||||
App::new()
|
||||
.route("/ws/", web::get().to(websocket_handler)) // WebSocket endpoint
|
||||
.service(Files::new("/assets", "./public/assets"))
|
||||
.service(Files::new("/", "./public").index_file("index.html")) // Serve frontend
|
||||
.default_service(web::route().to(|| async {
|
||||
let index_html = fs::read_to_string("./public/index.html")
|
||||
.unwrap_or_else(|_| "404 Not Found".to_string());
|
||||
HttpResponse::Ok()
|
||||
.content_type("text/html; charset=utf-8")
|
||||
.body(index_html)
|
||||
}))
|
||||
})
|
||||
.bind((address, port))?
|
||||
.run()
|
||||
.await
|
||||
}
|
25
default.nix
Normal file
25
default.nix
Normal file
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
system ? builtins.currentSystem,
|
||||
}:
|
||||
let
|
||||
lock = builtins.fromJSON (builtins.readFile ./flake.lock);
|
||||
|
||||
root = lock.nodes.${lock.root};
|
||||
inherit (lock.nodes.${root.inputs.flake-compat}.locked)
|
||||
owner
|
||||
repo
|
||||
rev
|
||||
narHash
|
||||
;
|
||||
|
||||
flake-compat = fetchTarball {
|
||||
url = "https://github.com/${owner}/${repo}/archive/${rev}.tar.gz";
|
||||
sha256 = narHash;
|
||||
};
|
||||
|
||||
flake = import flake-compat {
|
||||
inherit system;
|
||||
src = ./.;
|
||||
};
|
||||
in
|
||||
flake.defaultNix
|
197
flake.lock
Normal file
197
flake.lock
Normal file
|
@ -0,0 +1,197 @@
|
|||
{
|
||||
"nodes": {
|
||||
"elm-spa": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"mkElmDerivation",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1706301604,
|
||||
"narHash": "sha256-n6LDjnPCTLbKTrRgeZhlLTfY6V45xNYcb4NYEMuO4jg=",
|
||||
"owner": "jeslie0",
|
||||
"repo": "elm-spa",
|
||||
"rev": "4c82e18d5fcf9d4c027f0ef0e89204dd87584f7f",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "jeslie0",
|
||||
"repo": "elm-spa",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"elm-watch": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"mkElmDerivation",
|
||||
"nixpkgs"
|
||||
],
|
||||
"npm-fix": "npm-fix",
|
||||
"npmlock2nix": "npmlock2nix"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1706304401,
|
||||
"narHash": "sha256-992cypnhoRbsGkDc5/X241rafBML4EP0EuT6cBcaY/8=",
|
||||
"owner": "jeslie0",
|
||||
"repo": "elm-watch",
|
||||
"rev": "2f1c6c0e69b163c15e2ce66f543c38021b2a0ea3",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "jeslie0",
|
||||
"repo": "elm-watch",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-compat": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1733328505,
|
||||
"narHash": "sha256-NeCCThCEP3eCl2l/+27kNNK7QrwZB1IJCrXfrbv5oqU=",
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"rev": "ff81ac966bb2cae68946d5ed5fc4994f96d0ffec",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"mkElmDerivation": {
|
||||
"inputs": {
|
||||
"elm-spa": "elm-spa",
|
||||
"elm-watch": "elm-watch",
|
||||
"nixpkgs": "nixpkgs"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1735436157,
|
||||
"narHash": "sha256-G7gF5z0HEQVaLJuygUoCSJs27wQdautyMsciFYdpNCo=",
|
||||
"owner": "jeslie0",
|
||||
"repo": "mkElmDerivation",
|
||||
"rev": "f369fd6ff78205821c3f409814e840691be645e7",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "jeslie0",
|
||||
"repo": "mkElmDerivation",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1696757521,
|
||||
"narHash": "sha256-cfgtLNCBLFx2qOzRLI6DHfqTdfWI+UbvsKYa3b3fvaA=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "2646b294a146df2781b1ca49092450e8a32814e1",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nixos",
|
||||
"ref": "nixpkgs-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1735821806,
|
||||
"narHash": "sha256-cuNapx/uQeCgeuhUhdck3JKbgpsml259sjUQnWM7zW8=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "d6973081434f88088e5321f83ebafe9a1167c367",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixpkgs-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"npm-fix": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"mkElmDerivation",
|
||||
"elm-watch",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1706304213,
|
||||
"narHash": "sha256-XN9ESRSOANR0iFbEMMY1C1jvgZlYJsXQYVAHxxRmn+c=",
|
||||
"owner": "jeslie0",
|
||||
"repo": "npm-lockfile-fix",
|
||||
"rev": "e9851274afa12b04d98e694ed089aa9cde8d7349",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "jeslie0",
|
||||
"repo": "npm-lockfile-fix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"npmlock2nix": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1673447413,
|
||||
"narHash": "sha256-sJM82Sj8yfQYs9axEmGZ9Evzdv/kDcI9sddqJ45frrU=",
|
||||
"owner": "nix-community",
|
||||
"repo": "npmlock2nix",
|
||||
"rev": "9197bbf397d76059a76310523d45df10d2e4ca81",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"repo": "npmlock2nix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"flake-compat": "flake-compat",
|
||||
"mkElmDerivation": "mkElmDerivation",
|
||||
"nixpkgs": "nixpkgs_2",
|
||||
"utils": "utils"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1731533236,
|
||||
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
119
flake.nix
Normal file
119
flake.nix
Normal file
|
@ -0,0 +1,119 @@
|
|||
{
|
||||
inputs = {
|
||||
nixpkgs = {
|
||||
url = "github:NixOS/nixpkgs/nixpkgs-unstable";
|
||||
};
|
||||
utils.url = "github:numtide/flake-utils";
|
||||
mkElmDerivation.url = "github:jeslie0/mkElmDerivation";
|
||||
flake-compat = {
|
||||
url = "github:edolstra/flake-compat";
|
||||
flake = false;
|
||||
};
|
||||
};
|
||||
|
||||
outputs =
|
||||
inputs:
|
||||
inputs.utils.lib.eachDefaultSystem (
|
||||
system:
|
||||
let
|
||||
pkgs = import inputs.nixpkgs {
|
||||
localSystem = system;
|
||||
overlays = [
|
||||
inputs.mkElmDerivation.overlays.default
|
||||
(final: prev: {
|
||||
example-spa-elm-app = prev.callPackage (
|
||||
{
|
||||
stdenv,
|
||||
lib,
|
||||
elmPackages,
|
||||
uglify-js,
|
||||
rustPlatform,
|
||||
cargo,
|
||||
rustc,
|
||||
}:
|
||||
stdenv.mkDerivation (
|
||||
let
|
||||
allPackagesJsonPath = "${inputs.mkElmDerivation}/mkElmDerivation/all-packages.json";
|
||||
elmHashesJsonPath = "${inputs.mkElmDerivation}/mkElmDerivation/elm-hashes.json";
|
||||
snapshot = import "${inputs.mkElmDerivation}/src/snapshot/default.nix" (
|
||||
prev.haskellPackages // { inherit lib; }
|
||||
);
|
||||
elmJson = ./frontend/elm.json;
|
||||
in
|
||||
{
|
||||
pname = "example-spa-elm-app";
|
||||
version = "0.1.0";
|
||||
src = inputs.self;
|
||||
|
||||
nativeBuildInputs = [
|
||||
elmPackages.elm
|
||||
uglify-js
|
||||
rustPlatform.cargoSetupHook
|
||||
cargo
|
||||
rustc
|
||||
];
|
||||
|
||||
cargoDeps = pkgs.rustPlatform.importCargoLock {
|
||||
lockFile = ./backend/Cargo.lock;
|
||||
allowBuiltinFetchGit = true;
|
||||
};
|
||||
|
||||
cargoRoot = "backend";
|
||||
|
||||
makeFlags = [
|
||||
"RELEASE=1"
|
||||
"frontend"
|
||||
"backend"
|
||||
];
|
||||
|
||||
postPatch = ''
|
||||
patchShebangs ./frontend/build.sh
|
||||
'';
|
||||
|
||||
preBuild = ''
|
||||
(
|
||||
cd frontend
|
||||
${
|
||||
(import "${inputs.mkElmDerivation}/nix/lib.nix" {
|
||||
inherit
|
||||
stdenv
|
||||
lib
|
||||
snapshot
|
||||
allPackagesJsonPath
|
||||
;
|
||||
}).mkDotElmCommand
|
||||
elmHashesJsonPath
|
||||
elmJson
|
||||
}
|
||||
)
|
||||
export ELM_HOME=$PWD/frontend/.elm
|
||||
'';
|
||||
|
||||
installPhase = ''
|
||||
runHook preInstall
|
||||
mkdir -p $out
|
||||
cp -r ./public $out/
|
||||
cp ./backend/target/debug/backend $out/
|
||||
runHook postInstall
|
||||
'';
|
||||
}
|
||||
)
|
||||
) { };
|
||||
})
|
||||
];
|
||||
};
|
||||
in
|
||||
{
|
||||
packages = {
|
||||
default = inputs.self.packages."${system}".example-spa-elm-app;
|
||||
example-spa-elm-app = pkgs.example-spa-elm-app;
|
||||
};
|
||||
|
||||
devShells.default =
|
||||
with pkgs;
|
||||
mkShell {
|
||||
inputsFrom = [ example-spa-elm-app ];
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
24
frontend/Makefile
Normal file
24
frontend/Makefile
Normal file
|
@ -0,0 +1,24 @@
|
|||
SRC_FILES := $(shell find src -name "*.elm")
|
||||
|
||||
all: elm.min.js
|
||||
|
||||
.PHONY: elm.min.js
|
||||
|
||||
ifeq ($(DEBUG)$(RELEASE),) # Both are empty
|
||||
$(error You must set exactly one of DEBUG=1 or RELEASE=1)
|
||||
endif
|
||||
|
||||
ifeq ($(DEBUG)$(RELEASE),11) # Both are set
|
||||
$(error Both DEBUG and RELEASE cannot be set at the same time)
|
||||
endif
|
||||
|
||||
|
||||
elm.min.js:
|
||||
ifeq ($(DEBUG),1)
|
||||
./build.sh --debug src/Main.elm
|
||||
else ifeq ($(RELEASE),1)
|
||||
./build.sh --optimize src/Main.elm
|
||||
endif
|
||||
|
||||
clean:
|
||||
rm elm.js elm.min.js
|
22
frontend/build.sh
Executable file
22
frontend/build.sh
Executable file
|
@ -0,0 +1,22 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -ex
|
||||
|
||||
js="elm.js"
|
||||
min="elm.min.js"
|
||||
|
||||
if [ "$1" = "--debug" ]; then
|
||||
elm make --debug --output=$js "${@:2}"
|
||||
elif [ "$1" = "--optimize" ]; then
|
||||
elm make --optimize --output=$js "${@:2}"
|
||||
else
|
||||
echo "Error: You must specify either --debug or --optimize."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
uglifyjs elm.js --compress 'pure_funcs="F2,F3,F4,F5,F6,F7,F8,F9,A2,A3,A4,A5,A6,A7,A8,A9",pure_getters=true,keep_fargs=false,unsafe_comps=true,unsafe=true,passes=2' --output elm.js
|
||||
uglifyjs elm.js --mangle --output $min
|
||||
|
||||
echo "Compiled size: $(wc -c < $js) bytes ($js)"
|
||||
echo "Minified size: $(wc -c < $min) bytes ($min)"
|
||||
echo "Gzipped size: $(gzip -c $min | wc -c) bytes"
|
|
@ -9,11 +9,12 @@
|
|||
"elm/browser": "1.0.2",
|
||||
"elm/core": "1.0.5",
|
||||
"elm/html": "1.0.0",
|
||||
"elm/json": "1.1.3",
|
||||
"elm/url": "1.0.0",
|
||||
"kageurufu/elm-websockets": "1.0.1",
|
||||
"mdgriffith/elm-ui": "1.1.8"
|
||||
},
|
||||
"indirect": {
|
||||
"elm/json": "1.1.3",
|
||||
"elm/time": "1.0.0",
|
||||
"elm/virtual-dom": "1.0.3"
|
||||
}
|
|
@ -7,10 +7,12 @@
|
|||
<body>
|
||||
<div id="elm"></div>
|
||||
<script src="elm.min.js"></script>
|
||||
<script src="ports.websocket.js"></script>
|
||||
<script>
|
||||
var app = Elm.Main.init({
|
||||
node: document.getElementById('elm')
|
||||
});
|
||||
initSockets(app);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
94
frontend/src/Body.elm
Normal file
94
frontend/src/Body.elm
Normal file
|
@ -0,0 +1,94 @@
|
|||
module Body exposing (Msg(..), Model(..), init, update, view, handleRoute, subscriptions)
|
||||
|
||||
import Element
|
||||
|
||||
import Page.About
|
||||
import Page.Contact
|
||||
import Page.Landing
|
||||
import Page.Products
|
||||
import Page.Resources
|
||||
import Page.NotFound
|
||||
|
||||
type Msg
|
||||
= MsgLanding Page.Landing.Msg
|
||||
| MsgProducts Page.Products.Msg
|
||||
| MsgResources Page.Resources.Msg
|
||||
| MsgAbout Page.About.Msg
|
||||
| MsgContact Page.Contact.Msg
|
||||
| MsgNotFound Page.NotFound.Msg
|
||||
|
||||
type Model
|
||||
= ModelLanding Page.Landing.Model
|
||||
| ModelProducts Page.Products.Model
|
||||
| ModelResources Page.Resources.Model
|
||||
| ModelAbout Page.About.Model
|
||||
| ModelContact Page.Contact.Model
|
||||
| ModelNotFound Page.NotFound.Model
|
||||
|
||||
init : () -> Model
|
||||
init flags = ModelLanding (Page.Landing.init flags)
|
||||
|
||||
subscriptions : Model -> Sub Msg
|
||||
subscriptions model =
|
||||
case model of
|
||||
ModelLanding m -> Page.Landing.subscriptions m |> Sub.map MsgLanding
|
||||
ModelProducts m -> Page.Products.subscriptions m |> Sub.map MsgProducts
|
||||
ModelResources m -> Page.Resources.subscriptions m |> Sub.map MsgResources
|
||||
ModelAbout m -> Page.About.subscriptions m |> Sub.map MsgAbout
|
||||
ModelContact m -> Page.Contact.subscriptions m |> Sub.map MsgContact
|
||||
ModelNotFound m -> Page.NotFound.subscriptions m |> Sub.map MsgNotFound
|
||||
|
||||
handleRoute : String -> Model
|
||||
handleRoute path =
|
||||
let
|
||||
page =
|
||||
case path of
|
||||
"/" -> ModelLanding <| Page.Landing.init ()
|
||||
"/Products" -> ModelProducts <| Page.Products.init ()
|
||||
"/Resources" -> ModelResources <| Page.Resources.init ()
|
||||
"/About" -> ModelAbout <| Page.About.init ()
|
||||
"/Contact" -> ModelContact <| Page.Contact.init ()
|
||||
_ -> ModelNotFound <| Page.NotFound.init ()
|
||||
in
|
||||
page
|
||||
|
||||
update : Msg -> Model -> (Model, Cmd Msg)
|
||||
update bodyMsg bodyModel =
|
||||
let
|
||||
updatePage msg model updateFn wrapMsg toBodyModel =
|
||||
let
|
||||
(newModel, cmd) = updateFn msg model
|
||||
in
|
||||
(toBodyModel newModel, Cmd.map wrapMsg cmd)
|
||||
in
|
||||
case (bodyMsg, bodyModel) of
|
||||
(MsgLanding msg, ModelLanding m) ->
|
||||
updatePage msg m Page.Landing.update MsgLanding ModelLanding
|
||||
|
||||
(MsgProducts msg, ModelProducts m) ->
|
||||
updatePage msg m Page.Products.update MsgProducts ModelProducts
|
||||
|
||||
(MsgResources msg, ModelResources m) ->
|
||||
updatePage msg m Page.Resources.update MsgResources ModelResources
|
||||
|
||||
(MsgAbout msg, ModelAbout m) ->
|
||||
updatePage msg m Page.About.update MsgAbout ModelAbout
|
||||
|
||||
(MsgContact msg, ModelContact m) ->
|
||||
updatePage msg m Page.Contact.update MsgContact ModelContact
|
||||
|
||||
_ ->
|
||||
(bodyModel, Cmd.none)
|
||||
|
||||
view : Model -> Element.Element Msg
|
||||
view model =
|
||||
let
|
||||
content = case model of
|
||||
ModelLanding m -> Page.Landing.view m |> Element.map MsgLanding
|
||||
ModelProducts m -> Page.Products.view m |> Element.map MsgProducts
|
||||
ModelResources m -> Page.Resources.view m |> Element.map MsgResources
|
||||
ModelAbout m -> Page.About.view m |> Element.map MsgAbout
|
||||
ModelContact m -> Page.Contact.view m |> Element.map MsgContact
|
||||
ModelNotFound m -> Page.NotFound.view m |> Element.map MsgNotFound
|
||||
in
|
||||
Element.el [Element.centerY ,Element.centerX] content
|
|
@ -1,4 +1,11 @@
|
|||
module Header exposing (Model, Msg, view, init, update)
|
||||
module Header exposing
|
||||
( Model
|
||||
, Msg
|
||||
, view
|
||||
, init
|
||||
, subscriptions
|
||||
, update
|
||||
)
|
||||
import Element exposing (Element)
|
||||
import Element.Events
|
||||
import Element.Border
|
||||
|
@ -9,6 +16,9 @@ type alias Msg = {}
|
|||
init : () -> Model
|
||||
init flags = {}
|
||||
|
||||
subscriptions : Model -> Sub Msg
|
||||
subscriptions _ = Sub.none
|
||||
|
||||
update : Msg -> Model -> (Model, Cmd Msg)
|
||||
update msg model = (model, Cmd.none)
|
||||
|
||||
|
@ -27,6 +37,11 @@ view model =
|
|||
{ url = "/" ++ string
|
||||
, label = Element.text string
|
||||
}
|
||||
title = Element.link
|
||||
[Element.alignLeft]
|
||||
{ url = "/"
|
||||
, label = Element.text "Elm Example App"
|
||||
}
|
||||
|
||||
products = headerButton "Products"
|
||||
resources = headerButton "Resources"
|
||||
|
@ -37,7 +52,7 @@ view model =
|
|||
Element.spacing 15,
|
||||
Element.paddingXY 30 25,
|
||||
dropShadow]
|
||||
[ Element.el [Element.alignLeft] (Element.text "Elm Example App")
|
||||
[ title
|
||||
, products
|
||||
, resources
|
||||
, about
|
|
@ -9,6 +9,7 @@ import String exposing (right)
|
|||
import Browser.Navigation
|
||||
import Browser exposing (UrlRequest)
|
||||
import Html exposing (header)
|
||||
import Ports
|
||||
|
||||
-- internal imports
|
||||
import Body
|
||||
|
@ -34,11 +35,12 @@ init flags url key =
|
|||
model =
|
||||
{ key = key
|
||||
, url = url
|
||||
, page = page
|
||||
, page = Body.handleRoute url.path
|
||||
, header = header
|
||||
}
|
||||
in
|
||||
(model, Cmd.none)
|
||||
(model, Ports.socketOpen)
|
||||
|
||||
|
||||
update : Msg -> Model -> (Model, Cmd Msg)
|
||||
update msg model =
|
||||
|
@ -49,15 +51,19 @@ update msg model =
|
|||
in
|
||||
( {model | page = newPage}, cmd |> Cmd.map Body )
|
||||
UrlChanged url ->
|
||||
( {model | url = url}, Cmd.none )
|
||||
let
|
||||
newModel = {model | page = Body.handleRoute url.path, url = url}
|
||||
in
|
||||
( newModel, Cmd.none )
|
||||
UrlRequest (Browser.Internal url) ->
|
||||
( model, Browser.Navigation.pushUrl model.key (Url.toString url) )
|
||||
_ -> (model, Cmd.none)
|
||||
|
||||
subscriptions : Model -> Sub Msg
|
||||
subscriptions _ =
|
||||
Sub.none
|
||||
|
||||
subscriptions model = Sub.batch
|
||||
[ model.header |> Header.subscriptions |> Sub.map Header
|
||||
, model.page |> Body.subscriptions |> Sub.map Body
|
||||
]
|
||||
|
||||
view : Model -> Browser.Document Msg
|
||||
view model =
|
|
@ -1,4 +1,11 @@
|
|||
module Page.About exposing (Model, Msg, view, init, update)
|
||||
module Page.About exposing
|
||||
( Model
|
||||
, Msg
|
||||
, view
|
||||
, init
|
||||
, update
|
||||
, subscriptions
|
||||
)
|
||||
import Element exposing (Element)
|
||||
|
||||
type alias Model = {}
|
||||
|
@ -7,6 +14,9 @@ type alias Msg = {}
|
|||
init : () -> Model
|
||||
init flags = {}
|
||||
|
||||
subscriptions : Model -> Sub Msg
|
||||
subscriptions _ = Sub.none
|
||||
|
||||
update : Msg -> Model -> (Model, Cmd Msg)
|
||||
update msg model = (model, Cmd.none)
|
||||
|
|
@ -1,4 +1,11 @@
|
|||
module Page.Contact exposing (Model, Msg, view, init, update)
|
||||
module Page.Contact exposing
|
||||
( Model
|
||||
, Msg
|
||||
, view
|
||||
, init
|
||||
, update
|
||||
, subscriptions
|
||||
)
|
||||
import Element exposing (Element)
|
||||
|
||||
type alias Model = {}
|
||||
|
@ -7,6 +14,10 @@ type alias Msg = {}
|
|||
init : () -> Model
|
||||
init flags = {}
|
||||
|
||||
subscriptions : Model -> Sub Msg
|
||||
subscriptions _ = Sub.none
|
||||
|
||||
|
||||
update : Msg -> Model -> (Model, Cmd Msg)
|
||||
update msg model = (model, Cmd.none)
|
||||
|
138
frontend/src/Page/Landing.elm
Normal file
138
frontend/src/Page/Landing.elm
Normal file
|
@ -0,0 +1,138 @@
|
|||
module Page.Landing exposing
|
||||
( Model
|
||||
, Msg
|
||||
, view
|
||||
, init
|
||||
, update
|
||||
, subscriptions
|
||||
)
|
||||
import Element exposing (Element)
|
||||
import Websockets
|
||||
import Ports
|
||||
import Json.Decode as Decode
|
||||
import Json.Encode as Encode exposing (Value)
|
||||
import Html.Attributes exposing (placeholder)
|
||||
import Element.Input
|
||||
import Element.Background
|
||||
|
||||
type alias Model = {
|
||||
time : String,
|
||||
greetWidgetText : String,
|
||||
greeting : String
|
||||
}
|
||||
type DownMsgLanding
|
||||
= Greeting String
|
||||
| TimeUpdate String
|
||||
|
||||
decodeDownMsgLanding : Decode.Decoder DownMsgLanding
|
||||
decodeDownMsgLanding =
|
||||
let
|
||||
t = Decode.map Greeting (Decode.field "Greeting" Decode.string)
|
||||
in
|
||||
Decode.oneOf
|
||||
[ Decode.map Greeting (Decode.field "Greeting" Decode.string)
|
||||
, Decode.map TimeUpdate (Decode.field "TimeUpdate" Decode.string)
|
||||
]
|
||||
|
||||
decodeDownMsg : Decode.Decoder Msg
|
||||
decodeDownMsg =
|
||||
let
|
||||
decoder = Decode.field "Landing" decodeDownMsgLanding
|
||||
in
|
||||
Decode.map DownMsg decoder
|
||||
|
||||
type UpMsgLanding = RequestGreet String
|
||||
|
||||
encodeUpMsgLanding : UpMsgLanding -> Value
|
||||
encodeUpMsgLanding msg =
|
||||
case msg of
|
||||
RequestGreet name ->
|
||||
Encode.object
|
||||
[ ( "Landing", Encode.object
|
||||
[ ( "RequestGreet", Encode.string name ) ]
|
||||
)
|
||||
]
|
||||
|
||||
type Msg
|
||||
= DownMsg DownMsgLanding
|
||||
| UpMsg UpMsgLanding
|
||||
| DecodeError Decode.Error
|
||||
| GreetWidgetText String
|
||||
| NoOp
|
||||
|
||||
init : () -> Model
|
||||
init flags = {
|
||||
time = "time not yet set",
|
||||
greetWidgetText = "",
|
||||
greeting = ""
|
||||
}
|
||||
|
||||
subscriptions : Model -> Sub Msg
|
||||
subscriptions model =
|
||||
Ports.socketOnEvent
|
||||
(Websockets.EventHandlers
|
||||
(\_ -> NoOp)
|
||||
(\_ -> NoOp)
|
||||
(\_ -> NoOp)
|
||||
(\message ->
|
||||
case Decode.decodeString decodeDownMsg message.data of
|
||||
Ok msg -> msg
|
||||
Err err -> DecodeError err
|
||||
)
|
||||
(\_ -> NoOp)
|
||||
)
|
||||
|
||||
update : Msg -> Model -> (Model, Cmd Msg)
|
||||
update msg model =
|
||||
case msg of
|
||||
DownMsg (TimeUpdate time) -> ( {model | time = time}, Cmd.none )
|
||||
DownMsg (Greeting greeting) -> ( {model | greeting = greeting}, Cmd.none)
|
||||
UpMsg upMsgLanding -> (
|
||||
model,
|
||||
upMsgLanding |> encodeUpMsgLanding |> Ports.socketSend
|
||||
)
|
||||
GreetWidgetText text -> ( {model | greetWidgetText = text}, Cmd.none )
|
||||
_ -> (model, Cmd.none)
|
||||
|
||||
greetWidget : Model -> Element Msg
|
||||
greetWidget model =
|
||||
let
|
||||
idleBlue = Element.rgb255 18 147 217
|
||||
focusBlue = Element.rgb255 18 125 184
|
||||
|
||||
myButton = Element.Input.button
|
||||
[ Element.Background.color idleBlue
|
||||
, Element.mouseOver [Element.Background.color focusBlue]
|
||||
, Element.width (Element.fill |> Element.maximum 100)
|
||||
, Element.height (Element.fill |> Element.maximum 50)
|
||||
]
|
||||
{ onPress = Just <| UpMsg <| RequestGreet model.greetWidgetText
|
||||
, label = Element.text "Greet"
|
||||
}
|
||||
placeholder = case model.greetWidgetText of
|
||||
"" -> Just <| Element.Input.placeholder [] <| Element.text "Type Your Name"
|
||||
_ -> Just <| Element.Input.placeholder [] <| Element.text ""
|
||||
textInput =
|
||||
Element.Input.text
|
||||
[ Element.width (Element.fill |> Element.maximum 400)
|
||||
, Element.height Element.fill
|
||||
]
|
||||
{ onChange = GreetWidgetText
|
||||
, text = model.greetWidgetText
|
||||
, placeholder = placeholder
|
||||
, label = Element.Input.labelHidden "Enter your name"
|
||||
}
|
||||
nameInput = Element.row [] [ textInput , myButton]
|
||||
in
|
||||
Element.column []
|
||||
[ nameInput
|
||||
, Element.text model.greeting
|
||||
]
|
||||
|
||||
view : Model -> Element Msg
|
||||
view model =
|
||||
Element.column []
|
||||
[ Element.text "Landing"
|
||||
, Element.text <| "Current time is : " ++ model.time
|
||||
, greetWidget model
|
||||
]
|
|
@ -1,4 +1,11 @@
|
|||
module Page.Landing exposing (Model, Msg, view, init, update)
|
||||
module Page.NotFound exposing
|
||||
( Model
|
||||
, Msg
|
||||
, view
|
||||
, init
|
||||
, update
|
||||
, subscriptions
|
||||
)
|
||||
import Element exposing (Element)
|
||||
|
||||
type alias Model = {}
|
||||
|
@ -7,10 +14,13 @@ type alias Msg = {}
|
|||
init : () -> Model
|
||||
init flags = {}
|
||||
|
||||
subscriptions : Model -> Sub Msg
|
||||
subscriptions _ = Sub.none
|
||||
|
||||
update : Msg -> Model -> (Model, Cmd Msg)
|
||||
update msg model = (model, Cmd.none)
|
||||
|
||||
view : Model -> Element Msg
|
||||
view model =
|
||||
Element.el []
|
||||
<| Element.text "Landing"
|
||||
<| Element.text "I'm sorry, it seems we don't have that page!"
|
|
@ -1,4 +1,11 @@
|
|||
module Page.Products exposing (Model, Msg, view, init, update)
|
||||
module Page.Products exposing
|
||||
( Model
|
||||
, Msg
|
||||
, view
|
||||
, init
|
||||
, update
|
||||
, subscriptions
|
||||
)
|
||||
import Element exposing (Element)
|
||||
|
||||
type alias Model = {}
|
||||
|
@ -7,6 +14,9 @@ type alias Msg = {}
|
|||
init : () -> Model
|
||||
init flags = {}
|
||||
|
||||
subscriptions : Model -> Sub Msg
|
||||
subscriptions _ = Sub.none
|
||||
|
||||
update : Msg -> Model -> (Model, Cmd Msg)
|
||||
update msg model = (model, Cmd.none)
|
||||
|
|
@ -1,4 +1,11 @@
|
|||
module Page.Resources exposing (Model, Msg, view, init, update)
|
||||
module Page.Resources exposing
|
||||
( Model
|
||||
, Msg
|
||||
, view
|
||||
, init
|
||||
, update
|
||||
, subscriptions
|
||||
)
|
||||
import Element exposing (Element)
|
||||
|
||||
type alias Model = {}
|
||||
|
@ -7,6 +14,9 @@ type alias Msg = {}
|
|||
init : () -> Model
|
||||
init flags = {}
|
||||
|
||||
subscriptions : Model -> Sub Msg
|
||||
subscriptions _ = Sub.none
|
||||
|
||||
update : Msg -> Model -> (Model, Cmd Msg)
|
||||
update msg model = (model, Cmd.none)
|
||||
|
31
frontend/src/Ports.elm
Normal file
31
frontend/src/Ports.elm
Normal file
|
@ -0,0 +1,31 @@
|
|||
port module Ports exposing
|
||||
( socketOnEvent
|
||||
, socketSend
|
||||
, socketOpen
|
||||
)
|
||||
|
||||
import Websockets
|
||||
import Json.Encode as Encode
|
||||
|
||||
port webSocketCommand : Websockets.CommandPort msg
|
||||
port webSocketEvent : Websockets.EventPort msg
|
||||
|
||||
socketName : String
|
||||
socketName = "app"
|
||||
|
||||
socketOnEvent : Websockets.EventHandlers msg -> Sub msg
|
||||
socketOnEvent eventHandlers =
|
||||
socket.onEvent eventHandlers
|
||||
|
||||
socketSend : Encode.Value -> Cmd msg
|
||||
socketSend data = socket.send socketName data
|
||||
|
||||
socketOpen : Cmd msg
|
||||
socketOpen = socket.open socketName "/ws/" []
|
||||
|
||||
socket : Websockets.Methods msg
|
||||
socket =
|
||||
Websockets.withPorts
|
||||
{ command = webSocketCommand
|
||||
, event = webSocketEvent
|
||||
}
|
60
frontend/src/ports.websocket.js
Normal file
60
frontend/src/ports.websocket.js
Normal file
|
@ -0,0 +1,60 @@
|
|||
function initSockets(app) {
|
||||
var sockets = new Map();
|
||||
app.ports.webSocketCommand.subscribe(function (command) {
|
||||
switch (command.type) {
|
||||
case "open": {
|
||||
var name_1 = command.name, url = command.url, meta_1 = command.meta;
|
||||
var oldSocket = sockets.get(name_1);
|
||||
if (oldSocket) {
|
||||
oldSocket.ws.close(-1, "New socket opened with the same name");
|
||||
sockets.delete(name_1);
|
||||
}
|
||||
var socket = {
|
||||
ws: new WebSocket(url),
|
||||
name: name_1,
|
||||
meta: meta_1,
|
||||
};
|
||||
sockets.set(name_1, socket);
|
||||
socket.ws.addEventListener("open", function (ev) {
|
||||
app.ports.webSocketEvent.send({ type: "opened", name: name_1, meta: meta_1 });
|
||||
});
|
||||
socket.ws.addEventListener("message", function (_a) {
|
||||
var data = _a.data;
|
||||
app.ports.webSocketEvent.send({ type: "message", name: name_1, meta: meta_1, data: data });
|
||||
});
|
||||
socket.ws.addEventListener("close", function (_a) {
|
||||
var reason = _a.reason;
|
||||
app.ports.webSocketEvent.send({ type: "closed", name: name_1, meta: meta_1, reason: reason });
|
||||
sockets.delete(name_1);
|
||||
});
|
||||
socket.ws.addEventListener("error", function (ev) {
|
||||
app.ports.webSocketEvent.send({
|
||||
type: "error",
|
||||
name: name_1,
|
||||
meta: meta_1,
|
||||
error: null,
|
||||
});
|
||||
sockets.delete(name_1);
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "send": {
|
||||
var name_2 = command.name, data = command.data;
|
||||
var socket = sockets.get(name_2);
|
||||
if (socket) {
|
||||
socket.ws.send(typeof data === "object" ? JSON.stringify(data) : data);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "close": {
|
||||
var name_3 = command.name;
|
||||
var socket = sockets.get(name_3);
|
||||
if (socket) {
|
||||
socket.ws.close();
|
||||
sockets.delete(name_3);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
15
optimize.sh
15
optimize.sh
|
@ -1,15 +0,0 @@
|
|||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
js="elm.js"
|
||||
min="elm.min.js"
|
||||
|
||||
elm make --debug --output=$js "$@"
|
||||
# elm make --optimize --debug --output=$js "$@"
|
||||
|
||||
uglifyjs $js --compress 'pure_funcs=[F2,F3,F4,F5,F6,F7,F8,F9,A2,A3,A4,A5,A6,A7,A8,A9],pure_getters,keep_fargs=false,unsafe_comps,unsafe' | uglifyjs --mangle --output $min
|
||||
|
||||
echo "Compiled size:$(wc $js -c) bytes ($js)"
|
||||
echo "Minified size:$(wc $min -c) bytes ($min)"
|
||||
echo "Gzipped size: $(gzip $min -c | wc -c) bytes"
|
25
shell.nix
Normal file
25
shell.nix
Normal file
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
system ? builtins.currentSystem,
|
||||
}:
|
||||
let
|
||||
lock = builtins.fromJSON (builtins.readFile ./flake.lock);
|
||||
|
||||
root = lock.nodes.${lock.root};
|
||||
inherit (lock.nodes.${root.inputs.flake-compat}.locked)
|
||||
owner
|
||||
repo
|
||||
rev
|
||||
narHash
|
||||
;
|
||||
|
||||
flake-compat = fetchTarball {
|
||||
url = "https://github.com/${owner}/${repo}/archive/${rev}.tar.gz";
|
||||
sha256 = narHash;
|
||||
};
|
||||
|
||||
flake = import flake-compat {
|
||||
inherit system;
|
||||
src = ./.;
|
||||
};
|
||||
in
|
||||
flake.shellNix
|
66
src/Body.elm
66
src/Body.elm
|
@ -1,66 +0,0 @@
|
|||
module Body exposing (Msg(..), Model(..), init, update, view)
|
||||
|
||||
import Element
|
||||
|
||||
import Page.About
|
||||
import Page.Contact
|
||||
import Page.Landing
|
||||
import Page.Products
|
||||
import Page.Resources
|
||||
|
||||
type Msg
|
||||
= MsgLanding Page.Landing.Msg
|
||||
| MsgProducts Page.Products.Msg
|
||||
| MsgResources Page.Resources.Msg
|
||||
| MsgAbout Page.About.Msg
|
||||
| MsgContact Page.Contact.Msg
|
||||
|
||||
type Model
|
||||
= ModelLanding Page.Landing.Model
|
||||
| ModelProducts Page.Products.Model
|
||||
| ModelResources Page.Resources.Model
|
||||
| ModelAbout Page.About.Model
|
||||
| ModelContact Page.Contact.Model
|
||||
|
||||
init : () -> Model
|
||||
init flags = ModelLanding (Page.Landing.init flags)
|
||||
|
||||
update : Msg -> Model -> (Model, Cmd Msg)
|
||||
update bodyMsg bodyModel =
|
||||
let
|
||||
updatePage msg model updateFn wrapMsg toBodyModel =
|
||||
let
|
||||
(newModel, cmd) = updateFn msg model
|
||||
in
|
||||
(toBodyModel newModel, Cmd.map wrapMsg cmd)
|
||||
in
|
||||
case (bodyMsg, bodyModel) of
|
||||
(MsgLanding msg, ModelLanding m) ->
|
||||
updatePage msg m Page.Landing.update MsgLanding ModelLanding
|
||||
|
||||
(MsgProducts msg, ModelProducts m) ->
|
||||
updatePage msg m Page.Products.update MsgProducts ModelProducts
|
||||
|
||||
(MsgResources msg, ModelResources m) ->
|
||||
updatePage msg m Page.Resources.update MsgResources ModelResources
|
||||
|
||||
(MsgAbout msg, ModelAbout m) ->
|
||||
updatePage msg m Page.About.update MsgAbout ModelAbout
|
||||
|
||||
(MsgContact msg, ModelContact m) ->
|
||||
updatePage msg m Page.Contact.update MsgContact ModelContact
|
||||
|
||||
_ ->
|
||||
(bodyModel, Cmd.none)
|
||||
|
||||
view : Model -> Element.Element Msg
|
||||
view model =
|
||||
let
|
||||
content = case model of
|
||||
ModelLanding m -> Page.Landing.view m |> Element.map MsgLanding
|
||||
ModelProducts m -> Page.Products.view m |> Element.map MsgProducts
|
||||
ModelResources m -> Page.Resources.view m |> Element.map MsgResources
|
||||
ModelAbout m -> Page.About.view m |> Element.map MsgAbout
|
||||
ModelContact m -> Page.Contact.view m |> Element.map MsgContact
|
||||
in
|
||||
Element.el [Element.centerY ,Element.centerX] content
|
Loading…
Reference in a new issue