Compare commits
No commits in common. "1634ea7056963ddee7bca3d7e64dbca1ef6bb088" and "38bfd3bf18774c1196a5ce5f679195ab041094bb" have entirely different histories.
1634ea7056
...
38bfd3bf18
16
.gitignore
vendored
16
.gitignore
vendored
|
@ -1,10 +1,6 @@
|
||||||
frontend/elm-stuff/
|
elm-stuff/
|
||||||
frontend/elm.js
|
elm.js
|
||||||
frontend/elm.min.js
|
elm.min.js
|
||||||
frontend/node_modules/
|
node_modules/
|
||||||
frontend/package-lock.json
|
package-lock.json
|
||||||
frontend/package.json
|
package.json
|
||||||
backend/target
|
|
||||||
public/*
|
|
||||||
elm-stuff/*
|
|
||||||
result
|
|
54
Makefile
54
Makefile
|
@ -1,50 +1,10 @@
|
||||||
# Directories
|
SRC_FILES := $(shell find src -name "*.elm")
|
||||||
FRONTEND_DIR := ./frontend
|
|
||||||
BACKEND_DIR := ./backend
|
|
||||||
PUBLIC_DIR := ./public
|
|
||||||
ASSET_DIR := ./assets
|
|
||||||
|
|
||||||
# Commands
|
all: elm.min.js
|
||||||
ELM_MAKE := elm make
|
|
||||||
CARGO_BUILD := cargo build
|
|
||||||
CARGO_RUN := cargo run
|
|
||||||
|
|
||||||
ifeq ($(DEBUG)$(RELEASE),) # Both are empty
|
serve: all
|
||||||
$(error You must set exactly one of DEBUG=1 or RELEASE=1)
|
python3 -m http.server
|
||||||
endif
|
|
||||||
|
|
||||||
ifeq ($(DEBUG)$(RELEASE),11) # Both are set
|
elm.min.js: $(SRC_FILES)
|
||||||
$(error Both DEBUG and RELEASE cannot be set at the same time)
|
rm -f elm.min.js elm.js
|
||||||
endif
|
./optimize.sh src/Main.elm
|
||||||
|
|
||||||
# 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
|
|
||||||
|
|
28
README.md
28
README.md
|
@ -2,25 +2,25 @@
|
||||||
Example demonstrating how one might architect a single page application
|
Example demonstrating how one might architect a single page application
|
||||||
Elm app.
|
Elm app.
|
||||||
|
|
||||||
|
# Dependencies MacOS
|
||||||
# Building
|
|
||||||
```bash
|
```bash
|
||||||
nix-shell -p elmPackages.elm cargo uglify-js
|
brew install node elm
|
||||||
make serve RELEASE=1 # can also do DEBUG=1 instead
|
npm install -g uglify-js@2.4.11
|
||||||
```
|
```
|
||||||
|
|
||||||
Now open `http://127.0.0.1:8080` in your browser.
|
# Building
|
||||||
|
```
|
||||||
|
make serve
|
||||||
|
```
|
||||||
|
|
||||||
|
Now open `http://localhost:8000` in your browser.
|
||||||
|
|
||||||
# TODO
|
# TODO
|
||||||
|
|
||||||
- [x] Add Makefile
|
- [x] Add Makefile
|
||||||
- [ ] `EventHandlers.onMessage` should inject successfully decoded message into
|
- [ ] Determine if `src/Body.elm` or pages in `sr/Page` should have subscription functions
|
||||||
Msg type directly...
|
- [ ] use actix backend that maps most root requests to serve `actix_file::Files`
|
||||||
- [ ] Run `uglify` twice as per [this link](https://github.com/rtfeldman/elm-spa-example/tree/master?tab=readme-ov-file#production-build)
|
- [ ] Submit to slack for feedback...
|
||||||
- [ ] JSONify backend code for all send/receive
|
- [ ] Refactor into router page
|
||||||
- [ ] Backend should only communicate over websocket
|
- [ ] Handle back-navigation
|
||||||
- [ ] 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`
|
- [ ] Add `default.nix`
|
||||||
|
|
1846
backend/Cargo.lock
generated
1846
backend/Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -1,15 +0,0 @@
|
||||||
[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"
|
|
|
@ -1,14 +0,0 @@
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
|
|
||||||
#[derive(Serialize)]
|
|
||||||
pub enum DownMsg {
|
|
||||||
Greeting(String),
|
|
||||||
TimeUpdate(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
pub enum UpMsg {
|
|
||||||
RequestGreet(String)
|
|
||||||
}
|
|
|
@ -1,104 +0,0 @@
|
||||||
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
25
default.nix
|
@ -1,25 +0,0 @@
|
||||||
{
|
|
||||||
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
|
|
|
@ -9,12 +9,11 @@
|
||||||
"elm/browser": "1.0.2",
|
"elm/browser": "1.0.2",
|
||||||
"elm/core": "1.0.5",
|
"elm/core": "1.0.5",
|
||||||
"elm/html": "1.0.0",
|
"elm/html": "1.0.0",
|
||||||
"elm/json": "1.1.3",
|
|
||||||
"elm/url": "1.0.0",
|
"elm/url": "1.0.0",
|
||||||
"kageurufu/elm-websockets": "1.0.1",
|
|
||||||
"mdgriffith/elm-ui": "1.1.8"
|
"mdgriffith/elm-ui": "1.1.8"
|
||||||
},
|
},
|
||||||
"indirect": {
|
"indirect": {
|
||||||
|
"elm/json": "1.1.3",
|
||||||
"elm/time": "1.0.0",
|
"elm/time": "1.0.0",
|
||||||
"elm/virtual-dom": "1.0.3"
|
"elm/virtual-dom": "1.0.3"
|
||||||
}
|
}
|
197
flake.lock
197
flake.lock
|
@ -1,197 +0,0 @@
|
||||||
{
|
|
||||||
"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
119
flake.nix
|
@ -1,119 +0,0 @@
|
||||||
{
|
|
||||||
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 ];
|
|
||||||
};
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,24 +0,0 @@
|
||||||
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
|
|
|
@ -1,22 +0,0 @@
|
||||||
#!/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"
|
|
|
@ -1,94 +0,0 @@
|
||||||
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,138 +0,0 @@
|
||||||
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,26 +0,0 @@
|
||||||
module Page.NotFound exposing
|
|
||||||
( Model
|
|
||||||
, Msg
|
|
||||||
, view
|
|
||||||
, init
|
|
||||||
, update
|
|
||||||
, subscriptions
|
|
||||||
)
|
|
||||||
import Element exposing (Element)
|
|
||||||
|
|
||||||
type alias Model = {}
|
|
||||||
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 "I'm sorry, it seems we don't have that page!"
|
|
|
@ -1,31 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
|
@ -1,60 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -7,12 +7,10 @@
|
||||||
<body>
|
<body>
|
||||||
<div id="elm"></div>
|
<div id="elm"></div>
|
||||||
<script src="elm.min.js"></script>
|
<script src="elm.min.js"></script>
|
||||||
<script src="ports.websocket.js"></script>
|
|
||||||
<script>
|
<script>
|
||||||
var app = Elm.Main.init({
|
var app = Elm.Main.init({
|
||||||
node: document.getElementById('elm')
|
node: document.getElementById('elm')
|
||||||
});
|
});
|
||||||
initSockets(app);
|
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
15
optimize.sh
Executable file
15
optimize.sh
Executable file
|
@ -0,0 +1,15 @@
|
||||||
|
#!/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
25
shell.nix
|
@ -1,25 +0,0 @@
|
||||||
{
|
|
||||||
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
Normal file
66
src/Body.elm
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
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
|
|
@ -1,11 +1,4 @@
|
||||||
module Header exposing
|
module Header exposing (Model, Msg, view, init, update)
|
||||||
( Model
|
|
||||||
, Msg
|
|
||||||
, view
|
|
||||||
, init
|
|
||||||
, subscriptions
|
|
||||||
, update
|
|
||||||
)
|
|
||||||
import Element exposing (Element)
|
import Element exposing (Element)
|
||||||
import Element.Events
|
import Element.Events
|
||||||
import Element.Border
|
import Element.Border
|
||||||
|
@ -16,9 +9,6 @@ type alias Msg = {}
|
||||||
init : () -> Model
|
init : () -> Model
|
||||||
init flags = {}
|
init flags = {}
|
||||||
|
|
||||||
subscriptions : Model -> Sub Msg
|
|
||||||
subscriptions _ = Sub.none
|
|
||||||
|
|
||||||
update : Msg -> Model -> (Model, Cmd Msg)
|
update : Msg -> Model -> (Model, Cmd Msg)
|
||||||
update msg model = (model, Cmd.none)
|
update msg model = (model, Cmd.none)
|
||||||
|
|
||||||
|
@ -37,11 +27,6 @@ view model =
|
||||||
{ url = "/" ++ string
|
{ url = "/" ++ string
|
||||||
, label = Element.text string
|
, label = Element.text string
|
||||||
}
|
}
|
||||||
title = Element.link
|
|
||||||
[Element.alignLeft]
|
|
||||||
{ url = "/"
|
|
||||||
, label = Element.text "Elm Example App"
|
|
||||||
}
|
|
||||||
|
|
||||||
products = headerButton "Products"
|
products = headerButton "Products"
|
||||||
resources = headerButton "Resources"
|
resources = headerButton "Resources"
|
||||||
|
@ -52,7 +37,7 @@ view model =
|
||||||
Element.spacing 15,
|
Element.spacing 15,
|
||||||
Element.paddingXY 30 25,
|
Element.paddingXY 30 25,
|
||||||
dropShadow]
|
dropShadow]
|
||||||
[ title
|
[ Element.el [Element.alignLeft] (Element.text "Elm Example App")
|
||||||
, products
|
, products
|
||||||
, resources
|
, resources
|
||||||
, about
|
, about
|
|
@ -9,7 +9,6 @@ import String exposing (right)
|
||||||
import Browser.Navigation
|
import Browser.Navigation
|
||||||
import Browser exposing (UrlRequest)
|
import Browser exposing (UrlRequest)
|
||||||
import Html exposing (header)
|
import Html exposing (header)
|
||||||
import Ports
|
|
||||||
|
|
||||||
-- internal imports
|
-- internal imports
|
||||||
import Body
|
import Body
|
||||||
|
@ -35,12 +34,11 @@ init flags url key =
|
||||||
model =
|
model =
|
||||||
{ key = key
|
{ key = key
|
||||||
, url = url
|
, url = url
|
||||||
, page = Body.handleRoute url.path
|
, page = page
|
||||||
, header = header
|
, header = header
|
||||||
}
|
}
|
||||||
in
|
in
|
||||||
(model, Ports.socketOpen)
|
(model, Cmd.none)
|
||||||
|
|
||||||
|
|
||||||
update : Msg -> Model -> (Model, Cmd Msg)
|
update : Msg -> Model -> (Model, Cmd Msg)
|
||||||
update msg model =
|
update msg model =
|
||||||
|
@ -51,19 +49,15 @@ update msg model =
|
||||||
in
|
in
|
||||||
( {model | page = newPage}, cmd |> Cmd.map Body )
|
( {model | page = newPage}, cmd |> Cmd.map Body )
|
||||||
UrlChanged url ->
|
UrlChanged url ->
|
||||||
let
|
( {model | url = url}, Cmd.none )
|
||||||
newModel = {model | page = Body.handleRoute url.path, url = url}
|
|
||||||
in
|
|
||||||
( newModel, Cmd.none )
|
|
||||||
UrlRequest (Browser.Internal url) ->
|
UrlRequest (Browser.Internal url) ->
|
||||||
( model, Browser.Navigation.pushUrl model.key (Url.toString url) )
|
( model, Browser.Navigation.pushUrl model.key (Url.toString url) )
|
||||||
_ -> (model, Cmd.none)
|
_ -> (model, Cmd.none)
|
||||||
|
|
||||||
subscriptions : Model -> Sub Msg
|
subscriptions : Model -> Sub Msg
|
||||||
subscriptions model = Sub.batch
|
subscriptions _ =
|
||||||
[ model.header |> Header.subscriptions |> Sub.map Header
|
Sub.none
|
||||||
, model.page |> Body.subscriptions |> Sub.map Body
|
|
||||||
]
|
|
||||||
|
|
||||||
view : Model -> Browser.Document Msg
|
view : Model -> Browser.Document Msg
|
||||||
view model =
|
view model =
|
|
@ -1,11 +1,4 @@
|
||||||
module Page.About exposing
|
module Page.About exposing (Model, Msg, view, init, update)
|
||||||
( Model
|
|
||||||
, Msg
|
|
||||||
, view
|
|
||||||
, init
|
|
||||||
, update
|
|
||||||
, subscriptions
|
|
||||||
)
|
|
||||||
import Element exposing (Element)
|
import Element exposing (Element)
|
||||||
|
|
||||||
type alias Model = {}
|
type alias Model = {}
|
||||||
|
@ -14,9 +7,6 @@ type alias Msg = {}
|
||||||
init : () -> Model
|
init : () -> Model
|
||||||
init flags = {}
|
init flags = {}
|
||||||
|
|
||||||
subscriptions : Model -> Sub Msg
|
|
||||||
subscriptions _ = Sub.none
|
|
||||||
|
|
||||||
update : Msg -> Model -> (Model, Cmd Msg)
|
update : Msg -> Model -> (Model, Cmd Msg)
|
||||||
update msg model = (model, Cmd.none)
|
update msg model = (model, Cmd.none)
|
||||||
|
|
|
@ -1,11 +1,4 @@
|
||||||
module Page.Contact exposing
|
module Page.Contact exposing (Model, Msg, view, init, update)
|
||||||
( Model
|
|
||||||
, Msg
|
|
||||||
, view
|
|
||||||
, init
|
|
||||||
, update
|
|
||||||
, subscriptions
|
|
||||||
)
|
|
||||||
import Element exposing (Element)
|
import Element exposing (Element)
|
||||||
|
|
||||||
type alias Model = {}
|
type alias Model = {}
|
||||||
|
@ -14,10 +7,6 @@ type alias Msg = {}
|
||||||
init : () -> Model
|
init : () -> Model
|
||||||
init flags = {}
|
init flags = {}
|
||||||
|
|
||||||
subscriptions : Model -> Sub Msg
|
|
||||||
subscriptions _ = Sub.none
|
|
||||||
|
|
||||||
|
|
||||||
update : Msg -> Model -> (Model, Cmd Msg)
|
update : Msg -> Model -> (Model, Cmd Msg)
|
||||||
update msg model = (model, Cmd.none)
|
update msg model = (model, Cmd.none)
|
||||||
|
|
16
src/Page/Landing.elm
Normal file
16
src/Page/Landing.elm
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
module Page.Landing exposing (Model, Msg, view, init, update)
|
||||||
|
import Element exposing (Element)
|
||||||
|
|
||||||
|
type alias Model = {}
|
||||||
|
type alias Msg = {}
|
||||||
|
|
||||||
|
init : () -> Model
|
||||||
|
init flags = {}
|
||||||
|
|
||||||
|
update : Msg -> Model -> (Model, Cmd Msg)
|
||||||
|
update msg model = (model, Cmd.none)
|
||||||
|
|
||||||
|
view : Model -> Element Msg
|
||||||
|
view model =
|
||||||
|
Element.el []
|
||||||
|
<| Element.text "Landing"
|
|
@ -1,11 +1,4 @@
|
||||||
module Page.Products exposing
|
module Page.Products exposing (Model, Msg, view, init, update)
|
||||||
( Model
|
|
||||||
, Msg
|
|
||||||
, view
|
|
||||||
, init
|
|
||||||
, update
|
|
||||||
, subscriptions
|
|
||||||
)
|
|
||||||
import Element exposing (Element)
|
import Element exposing (Element)
|
||||||
|
|
||||||
type alias Model = {}
|
type alias Model = {}
|
||||||
|
@ -14,9 +7,6 @@ type alias Msg = {}
|
||||||
init : () -> Model
|
init : () -> Model
|
||||||
init flags = {}
|
init flags = {}
|
||||||
|
|
||||||
subscriptions : Model -> Sub Msg
|
|
||||||
subscriptions _ = Sub.none
|
|
||||||
|
|
||||||
update : Msg -> Model -> (Model, Cmd Msg)
|
update : Msg -> Model -> (Model, Cmd Msg)
|
||||||
update msg model = (model, Cmd.none)
|
update msg model = (model, Cmd.none)
|
||||||
|
|
|
@ -1,11 +1,4 @@
|
||||||
module Page.Resources exposing
|
module Page.Resources exposing (Model, Msg, view, init, update)
|
||||||
( Model
|
|
||||||
, Msg
|
|
||||||
, view
|
|
||||||
, init
|
|
||||||
, update
|
|
||||||
, subscriptions
|
|
||||||
)
|
|
||||||
import Element exposing (Element)
|
import Element exposing (Element)
|
||||||
|
|
||||||
type alias Model = {}
|
type alias Model = {}
|
||||||
|
@ -14,9 +7,6 @@ type alias Msg = {}
|
||||||
init : () -> Model
|
init : () -> Model
|
||||||
init flags = {}
|
init flags = {}
|
||||||
|
|
||||||
subscriptions : Model -> Sub Msg
|
|
||||||
subscriptions _ = Sub.none
|
|
||||||
|
|
||||||
update : Msg -> Model -> (Model, Cmd Msg)
|
update : Msg -> Model -> (Model, Cmd Msg)
|
||||||
update msg model = (model, Cmd.none)
|
update msg model = (model, Cmd.none)
|
||||||
|
|
Loading…
Reference in a new issue