commit 8da2472488c46a2eda7121f051e1f5d86f4515c9 Author: Joakim Hulthe Date: Fri Jul 29 01:36:18 2022 +0200 Initial Commit diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..f1a0c7c --- /dev/null +++ b/.dockerignore @@ -0,0 +1,2 @@ +Dockerfile +target* diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6dd0daa --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +target* +dist +config.toml + diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..2c709c8 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1913 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + +[[package]] +name = "anyhow" +version = "1.0.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb07d2053ccdbe10e2af2995a2f116c1330396493dc1269f6a91d0ae82e19704" + +[[package]] +name = "async-trait" +version = "0.1.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96cf8829f67d2eab0b2dfa42c5d0ef737e0724e4a82b01b3e292456202b19716" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-buffer" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" +dependencies = [ + "generic-array", +] + +[[package]] +name = "buf_redux" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b953a6887648bb07a535631f2bc00fbdb2a2216f135552cb3f534ed136b9c07f" +dependencies = [ + "memchr", + "safemem", +] + +[[package]] +name = "bumpalo" +version = "3.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" + +[[package]] +name = "cc" +version = "1.0.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clap" +version = "3.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f1fe12880bae935d142c8702d500c63a4e8634b6c3c57ad72bf978fc7b6249a" +dependencies = [ + "atty", + "bitflags", + "clap_derive", + "clap_lex", + "indexmap", + "once_cell", + "strsim", + "termcolor", + "textwrap", +] + +[[package]] +name = "clap_derive" +version = "3.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed6db9e867166a43a53f7199b5e4d1f522a1e5bd626654be263c999ce59df39a" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87eba3c8c7f42ef17f6c659fc7416d0f4758cd3e58861ee63c5fa4a4dde649e4" +dependencies = [ + "os_str_bytes", +] + +[[package]] +name = "common" +version = "0.1.0" +dependencies = [ + "lighter_lib", + "serde", +] + +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + +[[package]] +name = "cookie" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94d4706de1b0fa5b132270cddffa8585166037822e260a944fe161acd137ca05" +dependencies = [ + "percent-encoding", + "time", + "version_check 0.9.4", +] + +[[package]] +name = "cpufeatures" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "css_typegen" +version = "0.2.0" +source = "git+https://github.com/hulthe/css_typegen.git?branch=master#ed1c4b1b7c8bf19e1ce045cafa12f3886041134c" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "syn", +] + +[[package]] +name = "dbg" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4677188513e0e9d7adced5997cf9a1e7a3c996c994f90093325c5332c1a8b221" +dependencies = [ + "version_check 0.1.5", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "digest" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" +dependencies = [ + "block-buffer 0.10.2", + "crypto-common", +] + +[[package]] +name = "enclose" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1056f553da426e9c025a662efa48b52e62e0a3a7648aa2d15aeaaf7f0d329357" + +[[package]] +name = "encoding_rs" +version = "0.8.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "env_logger" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "fastrand" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" +dependencies = [ + "instant", +] + +[[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.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +dependencies = [ + "matches", + "percent-encoding", +] + +[[package]] +name = "futures" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" + +[[package]] +name = "futures-executor" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" + +[[package]] +name = "futures-macro" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" + +[[package]] +name = "futures-task" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" + +[[package]] +name = "futures-util" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" +dependencies = [ + "typenum", + "version_check 0.9.4", +] + +[[package]] +name = "getrandom" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "gloo-events" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68b107f8abed8105e4182de63845afcc7b69c098b7852a813ea7462a320992fc" +dependencies = [ + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-file" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa5d6084efa4a2b182ef3a8649cb6506cb4843f22cf907c6e0a799944248ae90" +dependencies = [ + "futures-channel", + "gloo-events", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-timers" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fb7d06c1c8cc2a29bee7ec961009a0b2caa0793ee4900c2ffb348734ba1c8f9" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "gloo-utils" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "929c53c913bb7a88d75d9dc3e9705f963d8c2b9001510b25ddaf671b9fb7049d" +dependencies = [ + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "h2" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37a82c6d637fc9515a4694bbf1cb2457b79d81ce52b3108bdeea58b07dd34a57" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util 0.7.3", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db0d4cf898abf0081f964436dc980e96670a0f36863e4b83aaacdb65c9d7ccc3" + +[[package]] +name = "headers" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cff78e5788be1e0ab65b04d306b2ed5092c815ec97ec70f4ebd5aee158aa55d" +dependencies = [ + "base64", + "bitflags", + "bytes", + "headers-core", + "http", + "httpdate", + "mime", + "sha-1 0.10.0", +] + +[[package]] +name = "headers-core" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" +dependencies = [ + "http", +] + +[[package]] +name = "heck" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" + +[[package]] +name = "hemma" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-trait", + "clap", + "common", + "futures", + "futures-util", + "lighter_lib", + "lighter_manager", + "log", + "markdown", + "pretty_env_logger", + "reqwest", + "ron", + "serde", + "tokio", + "toml", + "warp", +] + +[[package]] +name = "hemma_web" +version = "0.1.0" +dependencies = [ + "anyhow", + "common", + "css_typegen", + "lighter_lib", + "ron", + "seed", + "seed_router", + "serde", + "serde_json", + "wasm-bindgen", +] + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "http" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "496ce29bb5a52785b44e0f7ca2847ae0bb839c9bd28f69acac9b99d461c0c04c" + +[[package]] +name = "httpdate" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" + +[[package]] +name = "humantime" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" +dependencies = [ + "quick-error", +] + +[[package]] +name = "hyper" +version = "0.14.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42dc3c131584288d375f2d07f822b0cb012d8c6fb899a5b9fdb3cb7eb9b6004f" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d87c48c02e0dc5e3b849a2041db3029fd066650f8f717c07bf8ed78ccb895cac" +dependencies = [ + "http", + "hyper", + "rustls", + "tokio", + "tokio-rustls", +] + +[[package]] +name = "idna" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "ipnet" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b" + +[[package]] +name = "itoa" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" + +[[package]] +name = "js-sys" +version = "0.3.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "671a26f820db17c2a2750743f1dd03bafd15b98c9f30c7c2628c024c05d73397" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" + +[[package]] +name = "lighter_lib" +version = "0.1.0" +source = "git+https://git.nubo.sh/hulthe/lighter.git#d6a25648cec65dc4be166cc27b2c75b95aa5001d" +dependencies = [ + "serde", +] + +[[package]] +name = "lighter_manager" +version = "0.1.0" +source = "git+https://git.nubo.sh/hulthe/lighter.git#d6a25648cec65dc4be166cc27b2c75b95aa5001d" +dependencies = [ + "anyhow", + "clap", + "lighter_lib", + "log", + "mqtt-protocol", + "pretty_env_logger", + "serde", + "serde_json", + "tokio", + "toml", + "uuid 1.1.2", +] + +[[package]] +name = "lock_api" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "markdown" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef3aab6a1d529b112695f72beec5ee80e729cb45af58663ec902c8fac764ecdd" +dependencies = [ + "lazy_static", + "pipeline", + "regex", +] + +[[package]] +name = "matches" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "mime" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" + +[[package]] +name = "mime_guess" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "mio" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys", +] + +[[package]] +name = "mqtt-protocol" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca0b17380dc69fbcf5f967828cfd10e55028ba83a57da1f580c5b0792ab807ac" +dependencies = [ + "byteorder", + "lazy_static", + "log", + "regex", + "thiserror", + "tokio", +] + +[[package]] +name = "multipart" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00dec633863867f29cb39df64a397cdf4a6354708ddd7759f70c7fb51c5f9182" +dependencies = [ + "buf_redux", + "httparse", + "log", + "mime", + "mime_guess", + "quick-error", + "rand", + "safemem", + "tempfile", + "twoway", +] + +[[package]] +name = "num_cpus" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "num_threads" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" +dependencies = [ + "libc", +] + +[[package]] +name = "once_cell" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225" + +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "os_str_bytes" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21326818e99cfe6ce1e524c2a805c189a99b5ae555a35d19f9a284b427d86afa" + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-sys", +] + +[[package]] +name = "percent-encoding" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" + +[[package]] +name = "pin-project" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58ad3879ad3baf4e44784bc6a718a8698867bb991f8ce24d1bcbe2cfb4c3a75e" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "744b6f092ba29c3650faf274db506afd39944f48420f6c86b17cfe0ee1cb36bb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pipeline" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d15b6607fa632996eb8a17c9041cb6071cb75ac057abd45dece578723ea8c7c0" + +[[package]] +name = "ppv-lite86" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" + +[[package]] +name = "pretty_env_logger" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "926d36b9553851b8b0005f1275891b392ee4d2d833852c417ed025477350fb9d" +dependencies = [ + "env_logger", + "log", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check 0.9.4", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check 0.9.4", +] + +[[package]] +name = "proc-macro2" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quote" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" +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.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +dependencies = [ + "getrandom", +] + +[[package]] +name = "redox_syscall" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d83f127d94bdbcda4c8cc2e50f6f84f4b611f69c902699ca385a39c3a75f9ff1" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49b3de9ec5dc0a3417da371aab17d729997c15010e7fd24ff707773a33bddb64" + +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + +[[package]] +name = "reqwest" +version = "0.11.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b75aa69a3f06bbcc66ede33af2af253c6f7a86b1ca0033f60c580a27074fbf92" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-rustls", + "ipnet", + "js-sys", + "lazy_static", + "log", + "mime", + "percent-encoding", + "pin-project-lite", + "rustls", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-rustls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots", + "winreg", +] + +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin", + "untrusted", + "web-sys", + "winapi", +] + +[[package]] +name = "ron" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88073939a61e5b7680558e6be56b419e208420c2adb92be54921fa6b72283f1a" +dependencies = [ + "base64", + "bitflags", + "serde", +] + +[[package]] +name = "rustls" +version = "0.20.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aab8ee6c7097ed6057f43c187a62418d0c05a4bd5f18b3571db50ee0f9ce033" +dependencies = [ + "log", + "ring", + "sct", + "webpki", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7522c9de787ff061458fe9a829dc790a3f5b22dc571694fc5883f448b94d9a9" +dependencies = [ + "base64", +] + +[[package]] +name = "ryu" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" + +[[package]] +name = "safemem" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" + +[[package]] +name = "scoped-tls" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "sct" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "seed" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aadc99b3d823229f987d3b5277c00dcff69d5011ed0ed38c20d6aaa66ed7372" +dependencies = [ + "console_error_panic_hook", + "cookie", + "dbg", + "enclose", + "futures", + "getrandom", + "gloo-file", + "gloo-timers", + "gloo-utils", + "indexmap", + "js-sys", + "rand", + "serde", + "serde_json", + "uuid 0.8.2", + "version_check 0.9.4", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "seed_router" +version = "0.1.0" +source = "git+https://git.nubo.sh/hulthe/seed_router.git#e0a428a8f9186a4590acb2b3771e50290aacbde1" +dependencies = [ + "seed", + "seed_router_derive", +] + +[[package]] +name = "seed_router_derive" +version = "0.1.0" +source = "git+https://git.nubo.sh/hulthe/seed_router.git#e0a428a8f9186a4590acb2b3771e50290aacbde1" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "seed", + "serde", + "syn", +] + +[[package]] +name = "serde" +version = "1.0.138" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1578c6245786b9d168c5447eeacfb96856573ca56c9d68fdcf394be134882a47" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.138" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "023e9b1467aef8a10fb88f25611870ada9800ef7e22afce356bb0d2387b6f27c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c" +dependencies = [ + "itoa", + "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 = "sha-1" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if", + "cpufeatures", + "digest 0.9.0", + "opaque-debug", +] + +[[package]] +name = "sha-1" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.3", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32" + +[[package]] +name = "smallvec" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc88c725d61fc6c3132893370cac4a0200e3fedf5da8331c570664b1987f5ca2" + +[[package]] +name = "socket2" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "syn" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tempfile" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +dependencies = [ + "cfg-if", + "fastrand", + "libc", + "redox_syscall", + "remove_dir_all", + "winapi", +] + +[[package]] +name = "termcolor" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" + +[[package]] +name = "thiserror" +version = "1.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "time" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72c91f41dcb2f096c05f0873d667dceec1087ce5bcf984ec8ffb19acddbb3217" +dependencies = [ + "itoa", + "libc", + "num_threads", + "time-macros", +] + +[[package]] +name = "time-macros" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792" + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + +[[package]] +name = "tokio" +version = "1.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c51a52ed6686dd62c320f9b89299e9dfb46f730c7a48e635c19f21d116cb1439" +dependencies = [ + "bytes", + "libc", + "memchr", + "mio", + "num_cpus", + "once_cell", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "winapi", +] + +[[package]] +name = "tokio-macros" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-rustls" +version = "0.23.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" +dependencies = [ + "rustls", + "tokio", + "webpki", +] + +[[package]] +name = "tokio-stream" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df54d54117d6fdc4e4fea40fe1e4e566b3505700e148a6827e59b34b0d2600d9" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "511de3f85caf1c98983545490c3d09685fa8eb634e57eec22bb4db271f46cbd8" +dependencies = [ + "futures-util", + "log", + "pin-project", + "tokio", + "tungstenite", +] + +[[package]] +name = "tokio-util" +version = "0.6.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36943ee01a6d67977dd3f84a5a1d2efeb4ada3a1ae771cadfaa535d9d9fc6507" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "log", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc463cd8deddc3770d20f9852143d50bf6094e640b485cb2e189a2099085ff45" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] +name = "toml" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" +dependencies = [ + "serde", +] + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a400e31aa60b9d44a52a8ee0343b5b18566b03a8321e0d321f695cf56e940160" +dependencies = [ + "cfg-if", + "log", + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b7358be39f2f274f322d2aaed611acc57f382e8eb1e5b48cb9ae30933495ce7" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" + +[[package]] +name = "tungstenite" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0b2d8558abd2e276b0a8df5c05a2ec762609344191e5fd23e292c910e9165b5" +dependencies = [ + "base64", + "byteorder", + "bytes", + "http", + "httparse", + "log", + "rand", + "sha-1 0.9.8", + "thiserror", + "url", + "utf-8", +] + +[[package]] +name = "twoway" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59b11b2b5241ba34be09c3cc85a36e56e48f9888862e19cedf23336d35316ed1" +dependencies = [ + "memchr", +] + +[[package]] +name = "typenum" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" + +[[package]] +name = "unicase" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" +dependencies = [ + "version_check 0.9.4", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" + +[[package]] +name = "unicode-ident" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" + +[[package]] +name = "unicode-normalization" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81dee68f85cab8cf68dec42158baf3a79a1cdc065a8b103025965d6ccb7f6cbd" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "url" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" +dependencies = [ + "form_urlencoded", + "idna", + "matches", + "percent-encoding", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "uuid" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" +dependencies = [ + "getrandom", +] + +[[package]] +name = "uuid" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd6469f4314d5f1ffec476e05f17cc9a78bc7a27a6a857842170bdf8d6f98d2f" +dependencies = [ + "getrandom", +] + +[[package]] +name = "version_check" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log", + "try-lock", +] + +[[package]] +name = "warp" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cef4e1e9114a4b7f1ac799f16ce71c14de5778500c5450ec6b7b920c55b587e" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "headers", + "http", + "hyper", + "log", + "mime", + "mime_guess", + "multipart", + "percent-encoding", + "pin-project", + "scoped-tls", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-stream", + "tokio-tungstenite", + "tokio-util 0.6.10", + "tower-service", + "tracing", +] + +[[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.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27370197c907c55e3f1a9fbe26f44e937fe6451368324e009cba39e139dc08ad" +dependencies = [ + "cfg-if", + "serde", + "serde_json", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e04185bfa3a779273da532f5025e33398409573f348985af9a1cbf3774d3f4" +dependencies = [ + "bumpalo", + "lazy_static", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f741de44b75e14c35df886aff5f1eb73aa114fa5d4d00dcd37b5e01259bf3b2" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17cae7ff784d7e83a2fe7611cfe766ecf034111b49deb850a3dc7699c08251f5" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99ec0dc7a4756fffc231aab1b9f2f578d23cd391390ab27f952ae0c9b3ece20b" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d554b7f530dee5964d9a9468d95c1f8b8acae4f282807e7d27d4b03099a46744" + +[[package]] +name = "web-sys" +version = "0.3.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b17e741662c70c8bd24ac5c5b18de314a2c26c32bf8346ee1e6f53de919c283" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "webpki-roots" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d8de8415c823c8abd270ad483c6feeac771fad964890779f9a8cb24fbbc1bf" +dependencies = [ + "webpki", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +dependencies = [ + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" + +[[package]] +name = "windows_i686_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" + +[[package]] +name = "windows_i686_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" + +[[package]] +name = "winreg" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +dependencies = [ + "winapi", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..ea3cd4d --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,12 @@ +[workspace] +members = ["backend", "common", "frontend"] + +[profile.dev] +# Issue with const-generics +incremental = false + +[profile.release] +lto = true +opt-level = 's' +# Issue with const-generics +incremental = false diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..2e034d4 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,73 @@ +################## +### BASE STAGE ### +################## +FROM rust:1.62.1 as base + +# Install build dependencies +RUN cargo install --locked cargo-make trunk strip_cargo_version +RUN rustup target add wasm32-unknown-unknown +RUN rustup target add x86_64-unknown-linux-musl + +# required by "ring" +RUN apt-get update && apt-get install -y musl-tools + +WORKDIR /app +RUN mkdir frontend backend common + +########################### +### STRIP-VERSION STAGE ### +########################### +FROM base AS strip-version + +COPY Cargo.lock Cargo.toml ./ +COPY frontend/Cargo.toml ./frontend/ +COPY backend/Cargo.toml ./backend/ +COPY common/Cargo.toml ./common/ +RUN strip_cargo_version + +################### +### BUILD STAGE ### +################### +FROM base AS build + +RUN cargo init --lib frontend +RUN cargo init --bin backend +RUN cargo init --lib common + +COPY --from=strip-version /app/frontend/Cargo.toml /app/frontend/ +COPY --from=strip-version /app/backend/Cargo.toml /app/backend/ +COPY --from=strip-version /app/common/Cargo.toml /app/common/ +COPY --from=strip-version /app/Cargo.toml /app/Cargo.lock /app/ + +WORKDIR /app/backend +RUN cargo build --release --target x86_64-unknown-linux-musl + +WORKDIR /app/frontend +RUN cargo build --release --target wasm32-unknown-unknown + +WORKDIR /app +COPY . . + +WORKDIR /app/backend +RUN cargo build --release --target x86_64-unknown-linux-musl +RUN strip /app/target/x86_64-unknown-linux-musl/release/hemma + +WORKDIR /app/frontend +RUN trunk build --release + +######################## +### PRODUCTION STAGE ### +######################## +FROM scratch + +ENV RUST_LOG="info" + +WORKDIR / + +# Copy application binary +COPY --from=build /app/target/x86_64-unknown-linux-musl/release/hemma / + +# Copy static web files +COPY --from=build /app/frontend/dist /www + +CMD ["/hemma", "--bind", "0.0.0.0:8000", "--frontend", "/www/"] diff --git a/backend/Cargo.toml b/backend/Cargo.toml new file mode 100644 index 0000000..957bf92 --- /dev/null +++ b/backend/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "hemma" +version = "0.1.0" +edition = "2021" + +[dependencies] +futures-util = "0.3.21" +log = "0.4.17" +pretty_env_logger = "0.4.0" +tokio = { version = "1.19.2", features = ["full"] } +warp = "0.3.2" +ron = "0.7.1" +reqwest = { version = "0.11.11", default_features = false, features = ["rustls-tls"] } +async-trait = "0.1.56" +anyhow = "1.0.58" +markdown = "0.3.0" +clap = { version = "3.2.6", features = ["derive"] } +toml = "0.5.9" +serde = { version = "1.0.138", features = ["derive"] } +futures = "0.3.21" + +[dependencies.common] +path = "../common" + +[dependencies.lighter_lib] +git = "https://git.nubo.sh/hulthe/lighter.git" +#path = "../../lighter/lib" + +[dependencies.lighter_manager] +git = "https://git.nubo.sh/hulthe/lighter.git" +#path = "../../lighter/manager" diff --git a/backend/example.config.toml b/backend/example.config.toml new file mode 100644 index 0000000..b75a662 --- /dev/null +++ b/backend/example.config.toml @@ -0,0 +1,31 @@ +[mqtt] +#address = "hostname" +#port = 1883 +#username = "user" +#password = "password" + +[collectors] +markdown_web_links = [ + "https://example.org/lmao.md" +] + +[[bulbs]] +id = "light/bedroom" + +[[bulbs]] +id = "light/living_room" + +[[groups]] +name = "Living Room" +bulbs = ["light/living_room"] +x = 0 +y = 0 +shape = { Rectangle = { w = 10, h = 10 } } + +[[groups]] +name = "Bedroom" +bulbs = ["light/bedroom"] +x = 11 +y = 0 +shape = { Rectangle = { w = 10, h = 10 } } + diff --git a/backend/src/collector.rs b/backend/src/collector.rs new file mode 100644 index 0000000..27c4e19 --- /dev/null +++ b/backend/src/collector.rs @@ -0,0 +1,15 @@ +mod markdown_web; + +pub use markdown_web::MarkdownWeb; + +use serde::Deserialize; + +#[async_trait::async_trait] +pub trait Collector { + async fn collect(&mut self) -> anyhow::Result; +} + +#[derive(Deserialize)] +pub struct CollectorConfig { + pub markdown_web_links: Vec, +} diff --git a/backend/src/collector/markdown_web.rs b/backend/src/collector/markdown_web.rs new file mode 100644 index 0000000..82405ab --- /dev/null +++ b/backend/src/collector/markdown_web.rs @@ -0,0 +1,16 @@ +use crate::collector::Collector; +use reqwest::get; + +pub struct MarkdownWeb { + pub url: String, +} + +#[async_trait::async_trait] +impl Collector for MarkdownWeb { + async fn collect(&mut self) -> anyhow::Result { + let text = get(&self.url).await?.text().await?; + let html = markdown::to_html(&text); + + Ok(html) + } +} diff --git a/backend/src/main.rs b/backend/src/main.rs new file mode 100644 index 0000000..503571d --- /dev/null +++ b/backend/src/main.rs @@ -0,0 +1,285 @@ +mod collector; + +use clap::Parser; +use collector::{Collector, CollectorConfig, MarkdownWeb}; +use common::{BulbMap, ClientMessage, ServerMessage}; +use futures_util::{SinkExt, StreamExt}; +use lighter_manager::{ + manager::{BulbCommand, BulbManager, BulbSelector, BulbsConfig}, + mqtt_conf::MqttConfig, +}; +use log::LevelFilter; +use serde::Deserialize; +use std::convert::Infallible; +use std::net::SocketAddr; +use std::path::PathBuf; +use tokio::sync::broadcast::error::RecvError; +use tokio::sync::{broadcast, mpsc}; +use tokio::time::{sleep, Duration}; +use tokio::{fs, select, task}; +use warp::ws::{self, WebSocket}; +use warp::{Filter, Rejection, Reply}; + +#[macro_use] +extern crate log; + +#[derive(Parser)] +struct Opt { + /// More logging + #[clap(short, long, parse(from_occurrences))] + verbose: u8, + + /// Supress non-error logs + #[clap(short, long)] + quiet: bool, + + #[clap(long, short, default_value = "127.0.0.0:8000")] + bind: SocketAddr, + + #[clap(long, default_value = "./www")] + frontend: String, + + #[clap(long, short)] + config: PathBuf, +} + +#[derive(Deserialize)] +struct Config { + mqtt: MqttConfig, + + collectors: CollectorConfig, + + #[serde(flatten)] + bulbs: BulbsConfig, + + #[serde(flatten)] + bulb_map: BulbMap, +} + +struct State { + config: Config, + client_message: broadcast::Sender, + server_message: broadcast::Sender, +} + +#[tokio::main] +async fn main() { + let opt = Opt::parse(); + + let log_level = match opt.verbose { + _ if opt.quiet => LevelFilter::Error, + 0 => LevelFilter::Info, + 1 => LevelFilter::Debug, + 2.. => LevelFilter::Trace, + }; + + pretty_env_logger::formatted_builder() + .default_format() + .filter_level(log_level) + .init(); + + let config = fs::read_to_string(&opt.config) + .await + .expect("Failed to read config"); + let config: Config = toml::from_str(&config).expect("Failed to parse config"); + + // we keep a receiver here to keep the channel active when no clients are connected + let (server_message, _receiver) = broadcast::channel(100); + let (client_message, _) = broadcast::channel(100); + + let state = State { + config, + client_message, + server_message, + }; + let state = Box::leak(Box::new(state)); + + task::spawn(info_collector(state)); + task::spawn(lights_collector(state)); + + let ws = warp::path("ws") + // The `ws()` filter will prepare the Websocket handshake. + .and(warp::ws()) + .map(|ws: warp::ws::Ws| { + // And then our closure will be called when it completes... + ws.on_upgrade(|websocket| { + info!("Client connected {:?}", websocket); + + client_handler(websocket, state) + }) + }); + + let api = warp::path("api").and(ws.recover(not_found)); + + let index_html = format!("{}/index.html", opt.frontend); + let frontend = warp::fs::dir(opt.frontend).or(warp::fs::file(index_html)); + + let routes = api.or(frontend); + + warp::serve(routes).run(opt.bind).await; +} + +async fn not_found(_: Rejection) -> Result { + Ok(warp::http::StatusCode::NOT_FOUND) +} + +#[derive(Clone)] +struct ClientRequest { + message: ClientMessage, + response: mpsc::Sender, +} + +async fn lights_collector(state: &State) { + let config = &state.config; + let server_message = &state.server_message; + let mut client_message = state.client_message.subscribe(); + + let (cmd, bulb_states) = BulbManager::launch(config.bulbs.clone(), config.mqtt.clone()) + .await + .expect("Failed to launch bulb manager"); + + loop { + let notify = bulb_states.notify_on_change(); + sleep(Duration::from_millis(1000 / 10)).await; // limit to 10 updates/second + select! { + _ = notify => { + for (id, mode) in bulb_states.bulbs().await.clone().into_iter() { + if let Err(e) = server_message.send(ServerMessage::BulbMode { id, mode }) { + error!("broadcast channel error: {e}"); + return; + } + } + } + request = client_message.recv() => { + let request = match request { + Ok(r) => r, + Err(_) => continue, + }; + + match request.message { + ClientMessage::SetBulbColor { id, color } => { + if let Err(e) = cmd.send(BulbCommand::SetColor(BulbSelector::Id(id), color)).await { + error!("bulb manager error: {e}"); + } + } + ClientMessage::SetBulbPower { id, power } => { + if let Err(e) = cmd.send(BulbCommand::SetPower(BulbSelector::Id(id), power)).await { + error!("bulb manager error: {e}"); + } + } + ClientMessage::GetBulbs => { + if let Err(e) = request.response.send(ServerMessage::BulbMap(config.bulb_map.clone())).await { + error!("GetBulbs response channel error: {e}"); + return; + } + for (id, mode) in bulb_states.bulbs().await.clone().into_iter() { + if let Err(e) = request.response.send(ServerMessage::BulbMode { id, mode }).await { + error!("GetBulbs response channel error: {e}"); + return; + } + } + } + _ => {} + } + } + } + } +} + +async fn info_collector(state: &State) { + let mut collectors: Vec> = vec![]; + + for url in &state.config.collectors.markdown_web_links { + collectors.push(Box::new(MarkdownWeb { + url: url.to_string(), + })); + } + + let mut collectors = collectors.into_boxed_slice(); + + let server_message = &state.server_message; + + let collectors_len = collectors.len(); + let next = move |i: usize| (i + 1) % collectors_len; + let mut i = 0; + + loop { + sleep(Duration::from_secs(30)).await; + + // don't bother collecting if no clients are connected + // there is always 1 receiver held by main process + if server_message.receiver_count() <= 1 { + continue; + } + + i = next(i); + let collector = &mut collectors[i]; + + let msg = match collector.collect().await { + Ok(html) => ServerMessage::InfoPage { html }, + Err(e) => { + warn!("collector error: {e}"); + continue; + } + }; + + if let Err(e) = server_message.send(msg) { + error!("broadcast channel error: {e}"); + return; + } + } +} + +async fn client_handler(mut socket: WebSocket, state: &State) { + let mut server_message = state.server_message.subscribe(); + let (server_responder, mut server_responses) = mpsc::channel(100); + + async fn handle_server_message(socket: &mut WebSocket, message: ServerMessage) { + let message = match ron::to_string(&message) { + Ok(msg) => msg, + Err(e) => return error!("Failed to serialize message: {e}"), + }; + + if let Err(e) = socket.send(ws::Message::text(message)).await { + return warn!("client error: {e}"); + } + } + + loop { + select! { + response = server_responses.recv() => { + if let Some(message) = response { + handle_server_message(&mut socket, message).await; + } + } + message = server_message.recv() => { + let message = match message { + Ok(msg) => msg, + Err(RecvError::Lagged(_)) => continue, + Err(RecvError::Closed) => return, + }; + handle_server_message(&mut socket, message).await; + + } + message = socket.next() => { + match message { + None => return info!("stream closed"), + Some(Err(e)) => return warn!("client error: {e}"), + Some(Ok(message)) => { + let message = ron::from_str(message.to_str().unwrap()).unwrap(); + + let request = ClientRequest { + message, + response: server_responder.clone(), + }; + + if let Err(e) = state.client_message.send(request) { + return error!("client message handlers error: {e}"); + } + } + + } + } + } + } +} diff --git a/common/Cargo.toml b/common/Cargo.toml new file mode 100644 index 0000000..57b538b --- /dev/null +++ b/common/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "common" +version = "0.1.0" +edition = "2021" + +[dependencies] +serde = { version = "1.0.137", features = ["derive"] } + +[dependencies.lighter_lib] +git = "https://git.nubo.sh/hulthe/lighter.git" +#path = "../../lighter/lib" diff --git a/common/src/lib.rs b/common/src/lib.rs new file mode 100644 index 0000000..3ddcac0 --- /dev/null +++ b/common/src/lib.rs @@ -0,0 +1,65 @@ +use lighter_lib::{BulbColor, BulbId, BulbMode}; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Debug, Clone)] +#[non_exhaustive] +pub enum ServerMessage { + InfoPage { + html: String, + }, + + /// Update the state of a bulb + BulbMode { + id: BulbId, + mode: BulbMode, + }, + + BulbMap(BulbMap), +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +#[non_exhaustive] +pub enum ClientMessage { + //SubscribeToInfo, + //SubscribeToBulbs, + GetBulbs, + SetBulbColor { id: BulbId, color: BulbColor }, + SetBulbPower { id: BulbId, power: bool }, +} + +/// A geometric description of rooms/groups of light bulbs +#[derive(Default, Deserialize, Serialize, Debug, Clone)] +pub struct BulbMap { + pub groups: Vec, +} + +#[derive(Deserialize, Serialize, Debug, Clone)] +pub struct BulbGroup { + pub name: String, + pub bulbs: Vec, + pub x: u32, + pub y: u32, + pub shape: BulbGroupShape, +} + +#[derive(Deserialize, Serialize, Debug, Clone, Copy)] +pub enum BulbGroupShape { + Circle { r: u32 }, + Rectangle { w: u32, h: u32 }, +} + +impl BulbGroupShape { + pub fn height(&self) -> u32 { + match self { + &Self::Circle { r } => r, + &Self::Rectangle { h, .. } => h, + } + } + + pub fn width(&self) -> u32 { + match self { + &Self::Circle { r } => r, + &Self::Rectangle { w, .. } => w, + } + } +} diff --git a/frontend/Cargo.lock b/frontend/Cargo.lock new file mode 100644 index 0000000..4656e96 --- /dev/null +++ b/frontend/Cargo.lock @@ -0,0 +1,609 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + +[[package]] +name = "anyhow" +version = "1.0.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb07d2053ccdbe10e2af2995a2f116c1330396493dc1269f6a91d0ae82e19704" + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bumpalo" +version = "3.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + +[[package]] +name = "cookie" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94d4706de1b0fa5b132270cddffa8585166037822e260a944fe161acd137ca05" +dependencies = [ + "percent-encoding", + "time", + "version_check 0.9.4", +] + +[[package]] +name = "css_typegen" +version = "0.2.0" +source = "git+https://github.com/hulthe/css_typegen.git?branch=master#ed1c4b1b7c8bf19e1ce045cafa12f3886041134c" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "syn", +] + +[[package]] +name = "dbg" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4677188513e0e9d7adced5997cf9a1e7a3c996c994f90093325c5332c1a8b221" +dependencies = [ + "version_check 0.1.5", +] + +[[package]] +name = "enclose" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1056f553da426e9c025a662efa48b52e62e0a3a7648aa2d15aeaaf7f0d329357" + +[[package]] +name = "futures" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" + +[[package]] +name = "futures-executor" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" + +[[package]] +name = "futures-macro" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" + +[[package]] +name = "futures-task" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" + +[[package]] +name = "futures-util" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "getrandom" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "gloo-events" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68b107f8abed8105e4182de63845afcc7b69c098b7852a813ea7462a320992fc" +dependencies = [ + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-file" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa5d6084efa4a2b182ef3a8649cb6506cb4843f22cf907c6e0a799944248ae90" +dependencies = [ + "futures-channel", + "gloo-events", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-timers" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fb7d06c1c8cc2a29bee7ec961009a0b2caa0793ee4900c2ffb348734ba1c8f9" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "gloo-utils" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "929c53c913bb7a88d75d9dc3e9705f963d8c2b9001510b25ddaf671b9fb7049d" +dependencies = [ + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "hashbrown" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db0d4cf898abf0081f964436dc980e96670a0f36863e4b83aaacdb65c9d7ccc3" + +[[package]] +name = "indexmap" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "itoa" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" + +[[package]] +name = "js-sys" +version = "0.3.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "671a26f820db17c2a2750743f1dd03bafd15b98c9f30c7c2628c024c05d73397" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "num_threads" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" +dependencies = [ + "libc", +] + +[[package]] +name = "percent-encoding" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "ppv-lite86" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" + +[[package]] +name = "proc-macro2" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" +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.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +dependencies = [ + "getrandom", +] + +[[package]] +name = "regex" +version = "1.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d83f127d94bdbcda4c8cc2e50f6f84f4b611f69c902699ca385a39c3a75f9ff1" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49b3de9ec5dc0a3417da371aab17d729997c15010e7fd24ff707773a33bddb64" + +[[package]] +name = "ryu" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" + +[[package]] +name = "seed" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aadc99b3d823229f987d3b5277c00dcff69d5011ed0ed38c20d6aaa66ed7372" +dependencies = [ + "console_error_panic_hook", + "cookie", + "dbg", + "enclose", + "futures", + "getrandom", + "gloo-file", + "gloo-timers", + "gloo-utils", + "indexmap", + "js-sys", + "rand", + "serde", + "serde_json", + "uuid", + "version_check 0.9.4", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "serde" +version = "1.0.137" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.137" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "singit2" +version = "0.2.2" +dependencies = [ + "anyhow", + "css_typegen", + "seed", + "serde", + "serde_json", + "wasm-bindgen", +] + +[[package]] +name = "slab" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32" + +[[package]] +name = "syn" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "time" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72c91f41dcb2f096c05f0873d667dceec1087ce5bcf984ec8ffb19acddbb3217" +dependencies = [ + "itoa", + "libc", + "num_threads", + "time-macros", +] + +[[package]] +name = "time-macros" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792" + +[[package]] +name = "unicode-ident" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" + +[[package]] +name = "uuid" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" +dependencies = [ + "getrandom", +] + +[[package]] +name = "version_check" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[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.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27370197c907c55e3f1a9fbe26f44e937fe6451368324e009cba39e139dc08ad" +dependencies = [ + "cfg-if", + "serde", + "serde_json", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e04185bfa3a779273da532f5025e33398409573f348985af9a1cbf3774d3f4" +dependencies = [ + "bumpalo", + "lazy_static", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f741de44b75e14c35df886aff5f1eb73aa114fa5d4d00dcd37b5e01259bf3b2" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17cae7ff784d7e83a2fe7611cfe766ecf034111b49deb850a3dc7699c08251f5" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99ec0dc7a4756fffc231aab1b9f2f578d23cd391390ab27f952ae0c9b3ece20b" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d554b7f530dee5964d9a9468d95c1f8b8acae4f282807e7d27d4b03099a46744" + +[[package]] +name = "web-sys" +version = "0.3.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b17e741662c70c8bd24ac5c5b18de314a2c26c32bf8346ee1e6f53de919c283" +dependencies = [ + "js-sys", + "wasm-bindgen", +] diff --git a/frontend/Cargo.toml b/frontend/Cargo.toml new file mode 100644 index 0000000..6ac585b --- /dev/null +++ b/frontend/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "hemma_web" +version = "0.1.0" +authors = ["Joakim Hulthe `/resource/x/y/z` +#rewrite = "/api/" +#backend = "http://localhost:8000/api/" + diff --git a/frontend/index.html b/frontend/index.html new file mode 100755 index 0000000..0f9de7e --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + hemma + + + + +
+ + diff --git a/frontend/src/app.rs b/frontend/src/app.rs new file mode 100644 index 0000000..3c0a4c6 --- /dev/null +++ b/frontend/src/app.rs @@ -0,0 +1,151 @@ +use crate::page; +use common::{ClientMessage, ServerMessage}; +use seed::app::orders::OrdersContainer; +use seed::prelude::*; +use seed::{log, window}; +use seed_router::Router; +use std::collections::VecDeque; + +pub type AppOrders = OrdersContainer>>; + +/// Delays between successive attempts to reconnect in case the socket breaks. In seconds. +const TIMEOUT_CONNECT_DELAYS: &[u32] = &[2, 5, 10, 10, 10, 20, 30, 60, 120, 300]; + +pub struct Model { + page: Pages, + send_queue: VecDeque, + socket: WebSocket, + ws_url: String, + timeout_count: usize, +} + +#[derive(Router)] +pub enum Pages { + #[page("404", NotFound)] + NotFound(page::not_found::Model), + + #[page("info", Info)] + Info(page::info::Model), + + #[page("lights", Lights)] + Lights(page::lights::Model), +} + +#[derive(Debug)] +pub enum PageMsg { + NotFound(page::not_found::Msg), + Info(page::info::Msg), + Lights(page::lights::Msg), +} + +#[derive(Debug)] +pub enum Msg { + Page(PageMsg), + + SendMessage(ClientMessage), + FlushMessageQueue, + + // Global + Connect, + SocketOpened(), + SocketClosed(CloseEvent), + SocketError(), + SocketMessage(WebSocketMessage), +} + +pub fn init(url: Url, orders: &mut impl Orders) -> Model { + orders.subscribe(Msg::SendMessage); + + let location = window().location(); + let host = location.host().expect("Failed to get hostname"); + let ws_protocol = match location.protocol().ok().as_deref() { + Some("http:") => "ws", + _ => "wss", + }; + + let ws_url = format!("{ws_protocol}://{host}/api/ws"); + + Model { + page: Pages::from_url(url, &mut orders.proxy(Msg::Page)) + .unwrap_or(Pages::NotFound(Default::default())), + send_queue: Default::default(), + socket: open_socket(&ws_url, orders), + ws_url, + timeout_count: 0, + } +} + +fn open_socket(url: &str, orders: &mut impl Orders) -> WebSocket { + WebSocket::builder(url, orders) + .on_open(Msg::SocketOpened) + .on_close(Msg::SocketClosed) + .on_error(Msg::SocketError) + .on_message(Msg::SocketMessage) + .build_and_open() + .expect("failed to open websocket") +} + +pub fn update(msg: Msg, model: &mut Model, orders: &mut AppOrders) { + #[cfg(debug_assertions)] + log!(format!("{msg:?}")); + + match msg { + Msg::Page(msg) => model.page.update(msg, &mut orders.proxy(Msg::Page)), + Msg::FlushMessageQueue => { + while let Some(message) = model.send_queue.pop_front() { + let serialized = ron::to_string(&message).unwrap(); + + if let Err(e) = model.socket.send_text(serialized) { + model.send_queue.push_front(message); + log!(e); + return; + } + } + } + Msg::Connect => { + model.socket = open_socket(&model.ws_url, orders); + } + Msg::SendMessage(message) => { + model.send_queue.push_back(message); + orders.send_msg(Msg::FlushMessageQueue); + } + Msg::SocketOpened() => { + model.timeout_count = 0; + orders.send_msg(Msg::FlushMessageQueue); + } + Msg::SocketClosed(_event) => { + let timeout_sec = TIMEOUT_CONNECT_DELAYS[model.timeout_count]; + let timeout_ms = timeout_sec * 1000; + orders.perform_cmd(cmds::timeout(timeout_ms, || Msg::Connect)); + + log!(format!( + "Socket closed, trying to reconnect in {timeout_sec} seconds" + )); + + model.timeout_count = TIMEOUT_CONNECT_DELAYS.len().min(model.timeout_count + 1); + } + Msg::SocketError() => {} + Msg::SocketMessage(message) => { + if let Err(e) = handle_ws_msg(message, orders) { + log!(e); + } + } + } +} + +fn handle_ws_msg(message: WebSocketMessage, orders: &mut impl Orders) -> anyhow::Result<()> { + let message = message.text().map_err(|e| anyhow::format_err!("{e:?}"))?; + let message: ServerMessage = ron::from_str(&message)?; + orders.notify(message); + + Ok(()) +} + +pub fn view(model: &Model) -> Vec> { + vec![model.page.view().map_msg(Msg::Page)] + //match &model.page { + // Pages::NotFound => vec![h1!["Not Found"]], + // Pages::InfoScreen => vec![div![C![C.info_box], raw![&model.info_page]]], + // Pages::Lights(page) => vec![], + //} +} diff --git a/frontend/src/components/color_picker.rs b/frontend/src/components/color_picker.rs new file mode 100644 index 0000000..49e6e9e --- /dev/null +++ b/frontend/src/components/color_picker.rs @@ -0,0 +1,225 @@ +use crate::css::C; +use lighter_lib::BulbColor; +use seed::prelude::*; +use seed::{attrs, div, C}; +use std::f32::consts::PI; +use web_sys::MouseEvent; + +#[derive(Default)] +pub struct ColorPicker { + hue: f32, + saturation: f32, + brightness: f32, + temperature: f32, + + mode: ColorPickerSetting, + dragging: Option, +} + +#[derive(Default)] +enum ColorPickerSetting { + #[default] + HSB, + Kelvin, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum ColorPickerAttr { + HueSat, + Brightness, + Temperature, +} + +#[derive(Debug)] +pub enum ColorPickerMsg { + MouseDown(ColorPickerAttr, MouseEvent), + MouseMove(ColorPickerAttr, MouseEvent), + MouseUp(Option, MouseEvent), + + SetColor(BulbColor), +} + +impl ColorPicker { + pub fn update(&mut self, msg: ColorPickerMsg, orders: &mut impl Orders) { + match msg { + ColorPickerMsg::MouseDown(target, event) => { + self.dragging = Some(target); + self.handle_mouse_event(target, event); + } + ColorPickerMsg::MouseMove(target, event) => { + self.handle_mouse_event(target, event); + } + ColorPickerMsg::MouseUp(target, event) => { + if let Some(target) = target { + self.handle_mouse_event(target, event); + } + + match self.dragging.take() { + Some(ColorPickerAttr::HueSat) => self.mode = ColorPickerSetting::HSB, + Some(ColorPickerAttr::Brightness) => {} + Some(ColorPickerAttr::Temperature) => self.mode = ColorPickerSetting::Kelvin, + None => return, + } + + let color = match self.mode { + ColorPickerSetting::HSB => { + BulbColor::hsb(self.hue, self.saturation, self.brightness) + } + ColorPickerSetting::Kelvin => { + BulbColor::kelvin(self.temperature, self.brightness) + } + }; + + orders.send_msg(ColorPickerMsg::SetColor(color)); + } + ColorPickerMsg::SetColor { .. } => {} + } + } + + fn handle_mouse_event(&mut self, target: ColorPickerAttr, event: MouseEvent) { + if Some(target) != self.dragging { + return; + } + + let handle_bar = || { + let y = event.offset_y() as f32; + let height = 200.0; + 1.0 - (y / height).clamp(0.0, 1.0) + }; + + match target { + ColorPickerAttr::HueSat => { + let (x, y) = (event.offset_x() as f32, event.offset_y() as f32); + let radius = 100.0; + + let x = x - radius; + let y = y - radius; + + let angle = (x.atan2(y) + PI) / (2.0 * PI); + self.hue = 1.0 - angle; + self.saturation = ((x.powi(2) + y.powi(2)).sqrt() / radius).clamp(0.0, 1.0); + } + ColorPickerAttr::Brightness => self.brightness = handle_bar(), + ColorPickerAttr::Temperature => self.temperature = handle_bar(), + } + } + + pub fn view(&self) -> Node { + use ColorPickerAttr::{Brightness, HueSat, Temperature}; + use ColorPickerMsg::{MouseDown, MouseMove, MouseUp}; + + let (hs_x, hs_y) = { + let radius = 100.0; + let angle = -self.hue * PI * 2.0; + let x = -self.saturation * radius * angle.sin(); + let y = -self.saturation * radius * angle.cos(); + (x + radius - 5.0, y + radius - 5.0) + }; + + let br_y = { + let height = 200.0; + let marker_height = 10.0; + (1.0 - self.brightness) * height - marker_height / 2.0 + }; + + let temp_y = { + let height = 200.0; + let marker_height = 10.0; + (1.0 - self.temperature) * height - marker_height / 2.0 + }; + + let (r, g, b) = hsb_to_rgb(self.hue, 1.0, 1.0); + let saturation_gradient = match self.mode { + ColorPickerSetting::HSB => { + format!("background: linear-gradient(0deg, #000, rgba({r},{g},{b},1));") + } + ColorPickerSetting::Kelvin => format!("background: linear-gradient(0deg, #000, #fff);"), + }; + + div![ + C![C.color_picker], + mouse_ev(Ev::MouseUp, |ev| MouseUp(None, ev)), + div![ + C![C.color_wheel], + div![ + C![C.color_wheel_marker], + attrs! { + At::Style => format!("margin-left: {hs_x}px; margin-top: {hs_y}px;"), + }, + ], + mouse_ev(Ev::MouseDown, |ev| MouseDown(HueSat, ev)), + mouse_ev(Ev::MouseMove, |ev| MouseMove(HueSat, ev)), + mouse_ev(Ev::MouseUp, |ev| MouseUp(Some(HueSat), ev)), + //mouse_ev(Ev::MouseLeave, |ev| MouseUp(HueSat, ev)), + ], + div![ + C![C.brightness_bar], + attrs! { At::Style => saturation_gradient }, + div![ + C![C.color_bar_marker], + attrs! { + At::Style => format!("margin-top: {br_y}px;"), + }, + ], + mouse_ev(Ev::MouseDown, |ev| MouseDown(Brightness, ev)), + mouse_ev(Ev::MouseMove, |ev| MouseMove(Brightness, ev)), + mouse_ev(Ev::MouseUp, |ev| MouseUp(Some(Brightness), ev)), + //mouse_ev(Ev::MouseLeave, |ev| MouseUp(Brightness, ev)), + ], + div![ + C![C.temperature_bar], + div![ + C![C.color_bar_marker], + attrs! { + At::Style => format!("margin-top: {temp_y}px;"), + }, + ], + mouse_ev(Ev::MouseDown, |ev| MouseDown(Temperature, ev)), + mouse_ev(Ev::MouseMove, |ev| MouseMove(Temperature, ev)), + mouse_ev(Ev::MouseUp, |ev| MouseUp(Some(Temperature), ev)), + //mouse_ev(Ev::MouseLeave, |ev| MouseUp(Temperature, ev)), + ], + ] + } + + pub fn set_hsb(&mut self, h: f32, s: f32, b: f32) { + self.hue = h; + self.saturation = s; + self.brightness = b; + self.mode = ColorPickerSetting::HSB; + } + + pub fn set_kelvin(&mut self, t: f32, b: f32) { + self.temperature = t; + self.brightness = b; + self.mode = ColorPickerSetting::Kelvin; + } + + pub fn set_color(&mut self, color: BulbColor) { + match color { + BulbColor::HSB { h, s, b } => self.set_hsb(h, s, b), + BulbColor::Kelvin { t, b } => self.set_kelvin(t, b), + } + } +} + +fn hsb_to_rgb(h: f32, s: f32, b: f32) -> (u8, u8, u8) { + let h = 360.0 * h.clamp(0.0, 1.0); + + let c = b * s; // chroma + + let x = c * (1. - ((h / 60.) % 2. - 1.).abs()); + let m = b - c; + let m = |v| ((v + m) * 255.0) as u8; + + let (r, g, b) = match h { + _ if h < 60. => (c, x, 0.0), + _ if h < 120.0 => (x, c, 0.0), + _ if h < 180.0 => (0.0, c, x), + _ if h < 240.0 => (0.0, x, c), + _ if h < 300.0 => (x, 0.0, c), + _ => (c, 0.0, x), + }; + + (m(r), m(g), m(b)) +} diff --git a/frontend/src/components/mod.rs b/frontend/src/components/mod.rs new file mode 100644 index 0000000..5f83f0b --- /dev/null +++ b/frontend/src/components/mod.rs @@ -0,0 +1 @@ +pub mod color_picker; diff --git a/frontend/src/css.rs b/frontend/src/css.rs new file mode 100644 index 0000000..a6f664c --- /dev/null +++ b/frontend/src/css.rs @@ -0,0 +1,7 @@ +use css_typegen::css_typegen; + +// NOTE: Remember to edit index.html when adding new css-files! + +// Generate rust types for css-classes. +// Used for autocompletion and extra compile-time checks. +css_typegen!("frontend/static/styles"); diff --git a/frontend/src/lib.rs b/frontend/src/lib.rs new file mode 100644 index 0000000..f030495 --- /dev/null +++ b/frontend/src/lib.rs @@ -0,0 +1,12 @@ +mod app; +mod components; +mod css; +mod page; + +use seed::prelude::wasm_bindgen; +use seed::App; + +#[wasm_bindgen(start)] +pub fn start() { + App::start("app", app::init, app::update, app::view); +} diff --git a/frontend/src/page/info.rs b/frontend/src/page/info.rs new file mode 100644 index 0000000..167dbf8 --- /dev/null +++ b/frontend/src/page/info.rs @@ -0,0 +1,39 @@ +use common::ServerMessage; +use seed::prelude::*; +use seed::{div, raw}; +use seed_router::Page; + +#[derive(Default)] +pub struct Model { + content: String, +} + +#[derive(Debug)] +pub enum Msg { + ServerMessage(ServerMessage), +} + +impl Page for Model { + type Msg = Msg; + + fn new(orders: &mut impl Orders) -> Self { + orders.subscribe(Msg::ServerMessage); + + Model { + content: r#"
"#.into(), + } + } + + fn update(&mut self, msg: Self::Msg, _orders: &mut impl Orders) { + match msg { + Msg::ServerMessage(ServerMessage::InfoPage { html }) => { + self.content = html; + } + Msg::ServerMessage(_) => {} + } + } + + fn view(&self) -> Node { + div![raw![&self.content]] + } +} diff --git a/frontend/src/page/lights.rs b/frontend/src/page/lights.rs new file mode 100644 index 0000000..e30e230 --- /dev/null +++ b/frontend/src/page/lights.rs @@ -0,0 +1,240 @@ +use crate::components::color_picker::{ColorPicker, ColorPickerMsg}; +use crate::css::C; +use common::{BulbGroup, BulbGroupShape, BulbMap, ClientMessage, ServerMessage}; +use lighter_lib::{BulbId, BulbMode}; +use seed::prelude::*; +use seed::{attrs, button, div, C}; +use seed_router::Page; +use std::collections::{BTreeMap, HashSet}; +use std::fmt::Write; + +/// /lights page +#[derive(Default)] +pub struct Model { + bulb_states: BTreeMap, + + bulb_map: BulbMap, + + /// The currently selected bulb map groups + selected_groups: HashSet, + + /// Whether the currently selected map groups have been interacted with + groups_interacted: bool, + + color_picker: ColorPicker, +} + +#[derive(Debug)] +pub enum Msg { + ServerMessage(ServerMessage), + + SelectGroup(usize), + DeselectGroups, + ColorPicker(ColorPickerMsg), + SetBulbPower(bool), +} + +impl Page for Model { + type Msg = Msg; + + fn new(orders: &mut impl Orders) -> Self { + orders.subscribe(Msg::ServerMessage); + orders.notify(ClientMessage::GetBulbs); + + Model::default() + } + + fn update(&mut self, msg: Self::Msg, orders: &mut impl Orders) { + match msg { + Msg::ServerMessage(msg) => match msg { + ServerMessage::BulbMode { id, mode: new_mode } => { + *self.bulb_states.entry(id).or_default() = new_mode + //color_picker.set_color(mode.color); + } + ServerMessage::BulbMap(bulb_map) => { + self.bulb_map = bulb_map; + self.selected_groups.clear(); + } + _ => {} + }, + Msg::DeselectGroups => { + self.selected_groups.clear(); + } + Msg::SelectGroup(index) => { + if self.groups_interacted { + self.groups_interacted = false; + // If this group is the only selected group, don't clear it + if self.selected_groups.len() != 1 || !self.selected_groups.contains(&index) { + self.selected_groups.clear(); + } + } + + if !self.selected_groups.insert(index) { + self.selected_groups.remove(&index); + } else { + let bulb = self + .bulb_map + .groups + .get(index) + .and_then(|group| group.bulbs.first()) + .and_then(|id| self.bulb_states.get(id)); + + if let Some(bulb) = bulb { + self.color_picker.set_color(bulb.color); + } + } + } + Msg::ColorPicker(ColorPickerMsg::SetColor(color)) => { + self.groups_interacted = true; + self.for_selected_bulbs(|id, _| { + let message = ClientMessage::SetBulbColor { + id: id.clone(), + color, + }; + orders.notify(message); + }); + } + Msg::ColorPicker(msg) => { + self.color_picker + .update(msg, &mut orders.proxy(|msg| Msg::ColorPicker(msg))); + } + Msg::SetBulbPower(power) => { + self.groups_interacted = true; + self.for_selected_bulbs(|id, _| { + let message = ClientMessage::SetBulbPower { + id: id.clone(), + power, + }; + orders.notify(message); + }); + } + } + } + + fn view(&self) -> Node { + //let view_bulb = |(id, (mode, color_picker)): (&BulbId, &(BulbMode, ColorPicker))| { + // div![ + // C![C.bulb_box], + // h1![id], + // div![ + // C![C.bulb_controls], + // { + // let id = id.clone(); + // color_picker.view().map_msg(|msg| Msg::ColorPicker(id, msg)) + // }, + // button![ + // if mode.power { + // C![C.bulb_power_button, C.bulb_power_button_on] + // } else { + // C![C.bulb_power_button] + // }, + // { + // let id = id.clone(); + // let power = !mode.power; + // ev(Ev::Click, move |_| Msg::SetBulbPower(id, power)) + // }, + // ], + // ], + // ] + //}; + + let bulb_map_width = self + .bulb_map + .groups + .iter() + .map(|group| group.x + group.shape.width()) + .max() + .unwrap_or(0); + + let bulb_map_height = self + .bulb_map + .groups + .iter() + .map(|group| group.y + group.shape.height()) + .max() + .unwrap_or(0); + + let view_bulb_group = |(i, group): (usize, &BulbGroup)| { + let (w, h) = (group.shape.width(), group.shape.height()); + let mut style = String::new(); + write!( + &mut style, + "margin-left: {}rem; margin-top: {}rem; width: {}rem; height: {}rem;", + group.x, group.y, w, h + ) + .ok(); + + if let BulbGroupShape::Circle { r } = group.shape { + write!(&mut style, " border-radius: {r}rem;").ok(); + } + + div![ + &group.name[..1], + if self.selected_groups.contains(&i) { + C![C.bulb_group, C.bulb_group_selected] + } else { + C![C.bulb_group] + }, + attrs! { + At::Style => style, + }, + ev(Ev::Click, move |event| { + event.stop_propagation(); + Msg::SelectGroup(i) + }), + ] + }; + + let (_color, power) = self + .selected_groups + .iter() + .next() + .and_then(|&index| self.bulb_map.groups.get(index)) + .and_then(|group| group.bulbs.first()) + .and_then(|id| self.bulb_states.get(id)) + .map(|bulb| (bulb.color, bulb.power)) + .unwrap_or_default(); + + div![ + C![C.bulb_box], + div![ + C![C.bulb_map], + attrs! { + At::Style => format!("width: {}rem; height: {}rem;", bulb_map_width, bulb_map_height), + }, + ev(Ev::Click, |_| Msg::DeselectGroups), + self.bulb_map.groups.iter().enumerate().map(view_bulb_group), + ], + div![ + C![C.bulb_controls], + self.color_picker + .view() + .map_msg(|msg| Msg::ColorPicker(msg)), + button![ + if power { + C![C.bulb_power_button, C.bulb_power_button_on] + } else { + C![C.bulb_power_button] + }, + ev(Ev::Click, move |_| Msg::SetBulbPower(!power)), + div![attrs! { At::Id => "switch_socket" }], + div![attrs! { At::Id => "off_label" }, "Off"], + div![attrs! { At::Id => "on_label" }, "On"], + div![attrs! { At::Id => "lever_stem" }], + div![attrs! { At::Id => "lever_face" }], + ], + ], + ] + } +} + +impl Model { + fn for_selected_bulbs(&self, mut f: impl FnMut(&BulbId, &BulbMode)) { + self.selected_groups + .iter() + .filter_map(|&index| self.bulb_map.groups.get(index)) + .flat_map(|group| group.bulbs.iter()) + .filter_map(|id| self.bulb_states.get(id).map(|bulb| (id, bulb))) + .for_each(|(id, bulb)| f(id, bulb)); + } +} diff --git a/frontend/src/page/mod.rs b/frontend/src/page/mod.rs new file mode 100644 index 0000000..fe122ff --- /dev/null +++ b/frontend/src/page/mod.rs @@ -0,0 +1,3 @@ +pub mod info; +pub mod lights; +pub mod not_found; diff --git a/frontend/src/page/not_found.rs b/frontend/src/page/not_found.rs new file mode 100644 index 0000000..0a2e3b6 --- /dev/null +++ b/frontend/src/page/not_found.rs @@ -0,0 +1,25 @@ +use seed::h1; +use seed::prelude::*; +use seed_router::Page; + +#[derive(Default)] +pub struct Model; + +#[derive(Debug)] +pub enum Msg {} + +impl Page for Model { + type Msg = Msg; + + fn new(_orders: &mut impl Orders) -> Self { + Model + } + + fn update(&mut self, msg: Self::Msg, _orders: &mut impl Orders) { + match msg {} + } + + fn view(&self) -> Node { + h1!["404: Not found"] + } +} diff --git a/frontend/static/images/blueprint_bg.png b/frontend/static/images/blueprint_bg.png new file mode 100644 index 0000000..82ea8b8 Binary files /dev/null and b/frontend/static/images/blueprint_bg.png differ diff --git a/frontend/static/images/hsb.png b/frontend/static/images/hsb.png new file mode 100644 index 0000000..2b72815 Binary files /dev/null and b/frontend/static/images/hsb.png differ diff --git a/frontend/static/images/penguin1.svg b/frontend/static/images/penguin1.svg new file mode 100644 index 0000000..7a3c662 --- /dev/null +++ b/frontend/static/images/penguin1.svg @@ -0,0 +1,84 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + diff --git a/frontend/static/images/penguin2.svg b/frontend/static/images/penguin2.svg new file mode 100644 index 0000000..f639671 --- /dev/null +++ b/frontend/static/images/penguin2.svg @@ -0,0 +1,66 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/frontend/static/images/penguin3.svg b/frontend/static/images/penguin3.svg new file mode 100644 index 0000000..a8adfb6 --- /dev/null +++ b/frontend/static/images/penguin3.svg @@ -0,0 +1,67 @@ + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/frontend/static/styles/common.scss b/frontend/static/styles/common.scss new file mode 100644 index 0000000..48f7d55 --- /dev/null +++ b/frontend/static/styles/common.scss @@ -0,0 +1,267 @@ +body { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; + -webkit-box-pack: center; + -ms-flex-pack: center; + justify-content: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + background-color: #302f3b; + color: #ffffff; + height: 100%; + font-family: Ubuntu Mono,monospace; + font-size: x-large; +} + +@media (max-width: 460px) { + body { + font-size: large; + } +} + +.info_box { + margin: auto; + max-width: 40em; +} + +.nobr { + white-space: nowrap; +} + +.center { + margin: auto; +} + +.hidden { + visibility: hidden; +} + + /* Tooltip container */ + .tooltip { + position: relative; + display: inline-block; + border-bottom: 1px dotted black; /* If you want dots under the hoverable text */ +} + +/* Tooltip text */ +.tooltip .tooltiptext { + visibility: hidden; + width: 120px; + background-color: #555; + color: #fff; + text-align: center; + padding: 5px 0; + border-radius: 6px; + + /* Position the tooltip text */ + position: absolute; + z-index: 1; + top: 125%; + left: 50%; + margin-left: -100px; + + /* Fade in tooltip */ + opacity: 0; + transition: opacity 0.3s; +} + +/* Tooltip arrow */ +.tooltip .tooltiptext::after { + content: ""; + position: absolute; + bottom: 100%; + left: 100px; + margin-left: -5px; + border-width: 5px; + border-style: solid; + border-color: transparent transparent #555 transparent; +} + +/* Show the tooltip text when you mouse over the tooltip container */ +.tooltip:hover .tooltiptext { + visibility: visible; + opacity: 1; +} + +.bulb_box { + display: flex; + flex-direction: row; + flex-wrap: wrap; + align-items: center; + justify-content: space-around; + width: fit-content; +} + +.bulb_box > * { + margin: 0.5em; +} + +.bulb_controls { + display: flex; + flex-direction: row; + align-items: center; + padding-right: 0.5em; + background: #56636e; + border: solid 0.25em #5b3f63; + +} + +.bulb_map { + background: url(images/blueprint_bg.png); + background-size: auto; + background-size: 1em; + padding: 2rem; + border: solid 0.4rem #5b3f63; +} + +.bulb_map::after { + position: absolute; + width: 100%; + height: 100%; + content: #0003; +} + +.bulb_group { + color: black; + text-align: center; + background: #aaaaaaaa; + border: solid 0.25rem white; + border-radius: 0.25rem; + font-size: 2em; + position: absolute; + box-shadow: #0006 0.1em 0.1em 0.1em; + transition: 0.3s ease-in-out; +} + +.bulb_group_selected { + border-color: gold; + scale: 1.05; + transition: 0.3s ease-in-out; +} + +.bulb_power_button { + width: 70px; + height: 175px; + border-radius: 50px; + margin-top: 1em; + margin-bottom: 1em; + background-color: #b5b5b5; + transition: background-color 0.2s ease-in-out; +} + +.bulb_power_button_on { +} + +.bulb_power_button > div { + position: absolute; + width: 40px; + margin-left: 8.75px; + color: #000; + font-weight: 700; +} + +.bulb_power_button > #lever_face { + height: 40px; + background-color: #cccccc; + margin-top: 25px; + transition: 0.3s ease-in-out; +} + +.bulb_power_button_on > #lever_face { + margin-top: -65px; +} + +.bulb_power_button > #lever_stem { + height: 30px; + background-color: #eeeeee; + margin-top: 0px; + transition: 0.3s ease-in-out; +} + +.bulb_power_button_on > #lever_stem { + margin-top: -30px; +} + +.bulb_power_button > #off_label { + height: 30px; + background-color: #cccccc; + padding-top: 15px; + margin-top: -45px; +} + +.bulb_power_button > #on_label { + height: 30px; + background-color: #cccccc; + padding-top: 15px; + margin-top: 0px; +} + +.bulb_power_button > #switch_socket { + width: 47.5px; + margin-top: -55px; + margin-left: 0; + height: 100px; + background-color: #4f4f4f; + border: solid 5px #fff; + border-radius: 5px; +} + +.color_picker { + display: flex; + flex-direction: row; + padding: 0.5em; +} + +.color_wheel { + background-image: url(/images/hsb.png); + width: 200px; + height: 200px; + background-size: contain; + border: solid 5px #a4829c; + border-radius: 200px; +} + +.color_wheel_marker { + width: 6px; + height: 6px; + background: white; + border: solid 2px black; + border-radius: 10px; + position: absolute; + pointer-events: none; + + transition: margin 0.1s ease-out; +} + +.brightness_bar { + width: 30px; + height: 200px; + margin-left: 10px; + border: solid 5px #a4829c; +} + +.temperature_bar { + width: 30px; + height: 200px; + margin-left: 10px; + background: linear-gradient(0deg, #f81, #eff); + border: solid 5px #a4829c; +} + +.color_bar_marker { + width: 30px; + height: 8px; + border-top: solid 2px black; + border-bottom: solid 2px black; + position: absolute; + background: white; + pointer-events: none; + + transition: margin 0.1s ease-out; +} diff --git a/frontend/static/styles/penguin.scss b/frontend/static/styles/penguin.scss new file mode 100644 index 0000000..d934142 --- /dev/null +++ b/frontend/static/styles/penguin.scss @@ -0,0 +1,59 @@ +@keyframes penguin-spin { + 0% { + transform: perspective(36em) rotateY(-90deg); + background-image: url(/images/penguin1.svg); + } + 24.99% { + transform: perspective(36em) rotateY(90deg); + background-image: url(/images/penguin1.svg); + } + 25% { + transform: perspective(36em) rotateY(90deg); + background-image: url(/images/penguin2.svg); + } + 49.99% { + transform: perspective(36em) rotateY(270deg); + background-image: url(/images/penguin2.svg); + } + 50% { + transform: perspective(36em) rotateY(270deg); + background-image: url(/images/penguin3.svg); + } + 74.99% { + transform: perspective(36em) rotateY(450deg); + background-image: url(/images/penguin3.svg); + } + 75% { + transform: perspective(36em) rotateY(450deg); + background-image: url(/images/penguin2.svg); + } + 99.99% { + transform: perspective(36em) rotateY(630deg); + background-image: url(/images/penguin2.svg); + } + 100% { + transform: perspective(36em) rotateY(630deg); + background-image: url(/images/penguin1.svg); + } +} + +.penguin { + width: 12em; + height: 12em; + margin: auto; + margin-top: 1em; + margin-bottom: 1em; + background-image: url(/images/penguin1.svg); + background-size: contain; + animation-name: penguin-spin; + animation-duration: 4s; + animation-iteration-count: infinite; + animation-timing-function: cubic-bezier(0.1, 0.5, 0.9, 0.5); +} + +.penguin_small { + width: 1.5em; + height: 1.5em; + margin-top: 0em; + margin-bottom: 0em; +}