diff --git a/.gitignore b/.gitignore index a22bdea..83c9314 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,10 @@ -elm-stuff/ -elm.js -elm.min.js -node_modules/ -package-lock.json -package.json \ No newline at end of file +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 diff --git a/Makefile b/Makefile index c6f0e2c..ae86c3e 100644 --- a/Makefile +++ b/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 diff --git a/README.md b/README.md index 16526c2..007244b 100644 --- a/README.md +++ b/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` diff --git a/backend/Cargo.lock b/backend/Cargo.lock new file mode 100644 index 0000000..9c845a6 --- /dev/null +++ b/backend/Cargo.lock @@ -0,0 +1,1846 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "actix" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de7fa236829ba0841304542f7614c42b80fca007455315c45c785ccfa873a85b" +dependencies = [ + "actix-macros", + "actix-rt", + "actix_derive", + "bitflags", + "bytes", + "crossbeam-channel", + "futures-core", + "futures-sink", + "futures-task", + "futures-util", + "log", + "once_cell", + "parking_lot", + "pin-project-lite", + "smallvec", + "tokio", + "tokio-util", +] + +[[package]] +name = "actix-codec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a" +dependencies = [ + "bitflags", + "bytes", + "futures-core", + "futures-sink", + "memchr", + "pin-project-lite", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "actix-files" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0773d59061dedb49a8aed04c67291b9d8cf2fe0b60130a381aab53c6dd86e9be" +dependencies = [ + "actix-http", + "actix-service", + "actix-utils", + "actix-web", + "bitflags", + "bytes", + "derive_more", + "futures-core", + "http-range", + "log", + "mime", + "mime_guess", + "percent-encoding", + "pin-project-lite", + "v_htmlescape", +] + +[[package]] +name = "actix-http" +version = "3.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d48f96fc3003717aeb9856ca3d02a8c7de502667ad76eeacd830b48d2e91fac4" +dependencies = [ + "actix-codec", + "actix-rt", + "actix-service", + "actix-utils", + "ahash", + "base64", + "bitflags", + "brotli", + "bytes", + "bytestring", + "derive_more", + "encoding_rs", + "flate2", + "futures-core", + "h2", + "http", + "httparse", + "httpdate", + "itoa", + "language-tags", + "local-channel", + "mime", + "percent-encoding", + "pin-project-lite", + "rand", + "sha1", + "smallvec", + "tokio", + "tokio-util", + "tracing", + "zstd", +] + +[[package]] +name = "actix-macros" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "actix-router" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13d324164c51f63867b57e73ba5936ea151b8a41a1d23d1031eeb9f70d0236f8" +dependencies = [ + "bytestring", + "cfg-if", + "http", + "regex", + "regex-lite", + "serde", + "tracing", +] + +[[package]] +name = "actix-rt" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24eda4e2a6e042aa4e55ac438a2ae052d3b5da0ecf83d7411e1a368946925208" +dependencies = [ + "futures-core", + "tokio", +] + +[[package]] +name = "actix-server" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ca2549781d8dd6d75c40cf6b6051260a2cc2f3c62343d761a969a0640646894" +dependencies = [ + "actix-rt", + "actix-service", + "actix-utils", + "futures-core", + "futures-util", + "mio", + "socket2", + "tokio", + "tracing", +] + +[[package]] +name = "actix-service" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b894941f818cfdc7ccc4b9e60fa7e53b5042a2e8567270f9147d5591893373a" +dependencies = [ + "futures-core", + "paste", + "pin-project-lite", +] + +[[package]] +name = "actix-utils" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a1dcdff1466e3c2488e1cb5c36a71822750ad43839937f85d2f4d9f8b705d8" +dependencies = [ + "local-waker", + "pin-project-lite", +] + +[[package]] +name = "actix-web" +version = "4.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9180d76e5cc7ccbc4d60a506f2c727730b154010262df5b910eb17dbe4b8cb38" +dependencies = [ + "actix-codec", + "actix-http", + "actix-macros", + "actix-router", + "actix-rt", + "actix-server", + "actix-service", + "actix-utils", + "actix-web-codegen", + "ahash", + "bytes", + "bytestring", + "cfg-if", + "cookie", + "derive_more", + "encoding_rs", + "futures-core", + "futures-util", + "impl-more", + "itoa", + "language-tags", + "log", + "mime", + "once_cell", + "pin-project-lite", + "regex", + "regex-lite", + "serde", + "serde_json", + "serde_urlencoded", + "smallvec", + "socket2", + "time", + "url", +] + +[[package]] +name = "actix-web-actors" +version = "4.3.1+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f98c5300b38fd004fe7d2a964f9a90813fdbe8a81fed500587e78b1b71c6f980" +dependencies = [ + "actix", + "actix-codec", + "actix-http", + "actix-web", + "bytes", + "bytestring", + "futures-core", + "pin-project-lite", + "tokio", + "tokio-util", +] + +[[package]] +name = "actix-web-codegen" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f591380e2e68490b5dfaf1dd1aa0ebe78d84ba7067078512b4ea6e4492d622b8" +dependencies = [ + "actix-router", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "actix_derive" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6ac1e58cded18cb28ddc17143c4dea5345b3ad575e14f32f66e4054a56eb271" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "getrandom", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "backend" +version = "0.1.0" +dependencies = [ + "actix", + "actix-files", + "actix-web", + "actix-web-actors", + "chrono", + "env_logger", + "log", + "serde", + "serde_json", +] + +[[package]] +name = "backtrace" +version = "0.3.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "brotli" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74f7971dbd9326d58187408ab83117d8ac1bb9c17b085fdacd1cf2f598719b6b" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "4.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a45bd2e4095a8b518033b128020dd4a55aab1c0a381ba4404a472630f4bc362" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" + +[[package]] +name = "bytestring" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e465647ae23b2823b0753f50decb2d5a86d2bb2cac04788fafd1f80e45378e5f" +dependencies = [ + "bytes", +] + +[[package]] +name = "cc" +version = "1.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d6dbb628b8f8555f86d0323c2eb39e3ec81901f4b83e091db8a6a76d316a333" +dependencies = [ + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-targets", +] + +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "cookie" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" +dependencies = [ + "percent-encoding", + "time", + "version_check", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ba6d68e24814cb8de6bb986db8222d3a027d15872cabc0d18817bc3c0e4471" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "derive_more" +version = "0.99.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f33878137e4dafd7fa914ad4e259e18a4e8e532b9617a2d0150262bf53abfce" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "env_logger" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580" +dependencies = [ + "humantime", + "is-terminal", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "flate2" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "pin-utils", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "h2" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" + +[[package]] +name = "hermit-abi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" + +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-range" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21dec9db110f5f872ed9699c3ecf50cf16f423502706ba5c72462e28d3157573" + +[[package]] +name = "httparse" +version = "1.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "iana-time-zone" +version = "0.1.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "idna" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "impl-more" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a5a9a0ff0086c7a148acb942baaabeadf9504d10400b5a05645853729b9cd2" + +[[package]] +name = "indexmap" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "is-terminal" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "itoa" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" + +[[package]] +name = "jobserver" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" +dependencies = [ + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "language-tags" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" + +[[package]] +name = "libc" +version = "0.2.169" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" + +[[package]] +name = "litemap" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" + +[[package]] +name = "local-channel" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6cbc85e69b8df4b8bb8b89ec634e7189099cea8927a276b7384ce5488e53ec8" +dependencies = [ + "futures-core", + "futures-sink", + "local-waker", +] + +[[package]] +name = "local-waker" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d873d7c67ce09b42110d801813efbc9364414e356be9935700d368351657487" + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "miniz_oxide" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ffbe83022cedc1d264172192511ae958937694cd57ce297164951b8b3568394" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.52.0", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-project-lite" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "redox_syscall" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-lite" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53a49587ad06b26609c52e423de037e7f57f20d53535d66e08c695f347df952a" + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cb6eb87a131f756572d7fb904f6e7b68633f09cca868c5df1c4b8d1a694bbba" + +[[package]] +name = "serde" +version = "1.0.217" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.217" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.134" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d00f4175c42ee48b15416f6193a959ba3a0d67fc699a0db9ad12df9f83991c7d" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "socket2" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "syn" +version = "2.0.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c786062daee0d6db1132800e623df74274a0a87322d8e183338e01b3d98d058" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "time" +version = "0.3.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tokio" +version = "1.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cec9b21b0450273377fc97bd4c33a8acffc8c996c987a7c5b319a0083707551" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "windows-sys 0.52.0", +] + +[[package]] +name = "tokio-util" +version = "0.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "log", + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +dependencies = [ + "once_cell", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unicase" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" + +[[package]] +name = "unicode-ident" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" + +[[package]] +name = "url" +version = "2.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "v_htmlescape" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e8257fbc510f0a46eb602c10215901938b5c2a7d5e70fc11483b1d3c9b5b18c" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" +dependencies = [ + "cfg-if", + "once_cell", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + +[[package]] +name = "yoke" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerofrom" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zstd" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcf2b778a664581e31e389454a7072dab1647606d44f7feea22cd5abb9c9f3f9" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54a3ab4db68cea366acc5c897c7b4d4d1b8994a9cd6e6f841f8964566a419059" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.13+zstd.1.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38ff0f21cfee8f97d94cef41359e0c89aa6113028ab0291aa8ca0038995a95aa" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/backend/Cargo.toml b/backend/Cargo.toml new file mode 100644 index 0000000..5ff9bae --- /dev/null +++ b/backend/Cargo.toml @@ -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" diff --git a/backend/src/landing.rs b/backend/src/landing.rs new file mode 100644 index 0000000..163bbe2 --- /dev/null +++ b/backend/src/landing.rs @@ -0,0 +1,14 @@ +use serde::{Deserialize, Serialize}; + + +#[derive(Serialize)] +pub enum DownMsg { + Greeting(String), + TimeUpdate(String), +} + + +#[derive(Deserialize)] +pub enum UpMsg { + RequestGreet(String) +} diff --git a/backend/src/main.rs b/backend/src/main.rs new file mode 100644 index 0000000..4e1f604 --- /dev/null +++ b/backend/src/main.rs @@ -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; + + 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> for MyWebSocket { + fn handle( + &mut self, + msg: Result, + ctx: &mut ws::WebsocketContext, + ) { + match msg { + Ok(ws::Message::Text(text)) => { + // Attempt to decode the incoming message + if let Ok(up_msg) = serde_json::from_str::(&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 { + 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 +} diff --git a/default.nix b/default.nix new file mode 100644 index 0000000..2ba2a3e --- /dev/null +++ b/default.nix @@ -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 diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..2450164 --- /dev/null +++ b/flake.lock @@ -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 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..aa446c5 --- /dev/null +++ b/flake.nix @@ -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 ]; + }; + } + ); +} diff --git a/frontend/Makefile b/frontend/Makefile new file mode 100644 index 0000000..31d4a7f --- /dev/null +++ b/frontend/Makefile @@ -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 diff --git a/frontend/build.sh b/frontend/build.sh new file mode 100755 index 0000000..017fe35 --- /dev/null +++ b/frontend/build.sh @@ -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" diff --git a/elm.json b/frontend/elm.json similarity index 91% rename from elm.json rename to frontend/elm.json index 4217bec..9104ca4 100644 --- a/elm.json +++ b/frontend/elm.json @@ -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" } diff --git a/index.html b/frontend/index.html similarity index 80% rename from index.html rename to frontend/index.html index f77df87..2149339 100644 --- a/index.html +++ b/frontend/index.html @@ -7,10 +7,12 @@
+ diff --git a/frontend/src/Body.elm b/frontend/src/Body.elm new file mode 100644 index 0000000..3cdc0f3 --- /dev/null +++ b/frontend/src/Body.elm @@ -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 diff --git a/src/Header.elm b/frontend/src/Header.elm similarity index 76% rename from src/Header.elm rename to frontend/src/Header.elm index 4f7dd33..82e2e72 100644 --- a/src/Header.elm +++ b/frontend/src/Header.elm @@ -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 diff --git a/src/Main.elm b/frontend/src/Main.elm similarity index 84% rename from src/Main.elm rename to frontend/src/Main.elm index aaedc4b..38e8b90 100644 --- a/src/Main.elm +++ b/frontend/src/Main.elm @@ -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 = diff --git a/src/Page/About.elm b/frontend/src/Page/About.elm similarity index 62% rename from src/Page/About.elm rename to frontend/src/Page/About.elm index 92c02a8..8df3cbc 100644 --- a/src/Page/About.elm +++ b/frontend/src/Page/About.elm @@ -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) diff --git a/src/Page/Contact.elm b/frontend/src/Page/Contact.elm similarity index 62% rename from src/Page/Contact.elm rename to frontend/src/Page/Contact.elm index 3000024..32fc41d 100644 --- a/src/Page/Contact.elm +++ b/frontend/src/Page/Contact.elm @@ -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) diff --git a/frontend/src/Page/Landing.elm b/frontend/src/Page/Landing.elm new file mode 100644 index 0000000..8c039c0 --- /dev/null +++ b/frontend/src/Page/Landing.elm @@ -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 + ] diff --git a/src/Page/Landing.elm b/frontend/src/Page/NotFound.elm similarity index 50% rename from src/Page/Landing.elm rename to frontend/src/Page/NotFound.elm index 3201232..c68f107 100644 --- a/src/Page/Landing.elm +++ b/frontend/src/Page/NotFound.elm @@ -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!" diff --git a/src/Page/Products.elm b/frontend/src/Page/Products.elm similarity index 62% rename from src/Page/Products.elm rename to frontend/src/Page/Products.elm index 125f98e..d7ebdc1 100644 --- a/src/Page/Products.elm +++ b/frontend/src/Page/Products.elm @@ -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) diff --git a/src/Page/Resources.elm b/frontend/src/Page/Resources.elm similarity index 62% rename from src/Page/Resources.elm rename to frontend/src/Page/Resources.elm index 2a7272c..93e8a48 100644 --- a/src/Page/Resources.elm +++ b/frontend/src/Page/Resources.elm @@ -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) diff --git a/frontend/src/Ports.elm b/frontend/src/Ports.elm new file mode 100644 index 0000000..a12d9ff --- /dev/null +++ b/frontend/src/Ports.elm @@ -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 + } diff --git a/frontend/src/ports.websocket.js b/frontend/src/ports.websocket.js new file mode 100644 index 0000000..f652ad3 --- /dev/null +++ b/frontend/src/ports.websocket.js @@ -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; + } + } + }); +} diff --git a/optimize.sh b/optimize.sh deleted file mode 100755 index 7016792..0000000 --- a/optimize.sh +++ /dev/null @@ -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" diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000..9332b75 --- /dev/null +++ b/shell.nix @@ -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 diff --git a/src/Body.elm b/src/Body.elm deleted file mode 100644 index a88b20e..0000000 --- a/src/Body.elm +++ /dev/null @@ -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