diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index d4b98526..511120a6 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1,2 @@ * @ia0 +/crates/runner-host/crates/web-client/ @chris-dietz @ia0 diff --git a/crates/runner-host/Cargo.lock b/crates/runner-host/Cargo.lock index 1ad01e05..ee2a3788 100644 --- a/crates/runner-host/Cargo.lock +++ b/crates/runner-host/Cargo.lock @@ -213,6 +213,38 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" +[[package]] +name = "camino" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0ec6b951b160caa93cc0c7b209e5a3bff7aae9062213451ac99493cd844c239" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo-platform" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24b1f0365a6c6bb4020cd05806fd0d33c44d38046b8bd7f0e40814b9763cabfc" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror", +] + [[package]] name = "cc" version = "1.0.98" @@ -849,6 +881,18 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "libusb1-sys" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da050ade7ac4ff1ba5379af847a10a10a8e284181e060105bf8d86960ce9ce0f" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "lock_api" version = "0.4.12" @@ -1252,6 +1296,7 @@ dependencies = [ "usbd-serial", "usbip-device", "wasefire-board-api", + "wasefire-cli-tools", "wasefire-error", "wasefire-interpreter", "wasefire-logger", @@ -1261,6 +1306,16 @@ dependencies = [ "web-server", ] +[[package]] +name = "rusb" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab9f9ff05b63a786553a4c02943b74b34a988448671001e9a27e2f0565cc05a4" +dependencies = [ + "libc", + "libusb1-sys", +] + [[package]] name = "rustc-demangle" version = "0.1.24" @@ -1310,6 +1365,15 @@ dependencies = [ "zeroize", ] +[[package]] +name = "semver" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" +dependencies = [ + "serde", +] + [[package]] name = "serde" version = "1.0.202" @@ -1341,6 +1405,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_spanned" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0" +dependencies = [ + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -1558,6 +1631,40 @@ dependencies = [ "tokio", ] +[[package]] +name = "toml" +version = "0.8.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f49eb2ab21d2f26bd6db7bf383edc527a7ebaee412d17af4d40fdccd442f335" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d59a3a72298453f564e2b111fa896f8d07fabb36f51f06d7e875fc5e0b5a3ef1" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + [[package]] name = "tower-service" version = "0.3.2" @@ -1711,6 +1818,12 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.4" @@ -1815,6 +1928,20 @@ dependencies = [ "wasefire-store", ] +[[package]] +name = "wasefire-cli-tools" +version = "0.1.1-git" +dependencies = [ + "anyhow", + "cargo_metadata", + "clap", + "rusb", + "serde", + "toml", + "wasefire-protocol", + "wasefire-protocol-usb", +] + [[package]] name = "wasefire-error" version = "0.1.1" @@ -1850,10 +1977,14 @@ dependencies = [ name = "wasefire-protocol-usb" version = "0.1.0" dependencies = [ + "anyhow", + "rusb", "usb-device", "wasefire-board-api", "wasefire-error", "wasefire-logger", + "wasefire-protocol", + "wasefire-wire", ] [[package]] @@ -2092,6 +2223,15 @@ version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" +[[package]] +name = "winnow" +version = "0.6.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59b5e5f6c299a3c7890b876a2a587f3115162487e704907d9b6cd29473052ba1" +dependencies = [ + "memchr", +] + [[package]] name = "zeroize" version = "1.7.0" diff --git a/crates/runner-host/Cargo.toml b/crates/runner-host/Cargo.toml index e656d4a5..e7c77796 100644 --- a/crates/runner-host/Cargo.toml +++ b/crates/runner-host/Cargo.toml @@ -17,6 +17,7 @@ usb-device = { version = "0.3.2", optional = true } usbd-serial = { version = "0.2.2", optional = true } usbip-device = { version = "0.2.0", optional = true } wasefire-board-api = { path = "../board", features = ["std"] } +wasefire-cli-tools = { path = "../cli-tools", optional = true } wasefire-error = { path = "../error" } wasefire-interpreter = { path = "../interpreter", optional = true } wasefire-logger = { path = "../logger" } @@ -55,7 +56,7 @@ usb = [ "wasefire-scheduler/board-api-platform-protocol", "wasefire-scheduler/board-api-usb-serial", ] -web = ["dep:web-server"] +web = ["dep:wasefire-cli-tools", "dep:web-server"] # Exactly one is enabled by xtask. debug = ["wasefire-logger/log", "wasefire-protocol-usb?/log", "wasefire-scheduler/log"] release = [] diff --git a/crates/runner-host/crates/web-client/.gitignore b/crates/runner-host/crates/web-client/.gitignore new file mode 100644 index 00000000..178135c2 --- /dev/null +++ b/crates/runner-host/crates/web-client/.gitignore @@ -0,0 +1 @@ +/dist/ diff --git a/crates/runner-host/crates/web-client/Cargo.lock b/crates/runner-host/crates/web-client/Cargo.lock new file mode 100644 index 00000000..3634c18c --- /dev/null +++ b/crates/runner-host/crates/web-client/Cargo.lock @@ -0,0 +1,1259 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "anymap2" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d301b3b94cb4b2f23d7917810addbbaff90738e0ca2be692bd027e70d7e0330c" + +[[package]] +name = "autocfg" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" + +[[package]] +name = "backtrace" +version = "0.3.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "boolinator" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfa8873f51c92e232f9bac4065cddef41b714152812bfc5f7672ba16d6ef8cd9" + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "bytes" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" + +[[package]] +name = "cc" +version = "1.0.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c51067fd44124faa7f870b4b1c969379ad32b2ba805aa959430ceaa384f695" + +[[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 = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" + +[[package]] +name = "futures-io" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" + +[[package]] +name = "futures-macro" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", +] + +[[package]] +name = "futures-sink" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" + +[[package]] +name = "futures-task" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" + +[[package]] +name = "futures-util" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +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.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "gimli" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" + +[[package]] +name = "gloo" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28999cda5ef6916ffd33fb4a7b87e1de633c47c0dc6d97905fee1cdaa142b94d" +dependencies = [ + "gloo-console 0.2.3", + "gloo-dialogs 0.1.1", + "gloo-events 0.1.2", + "gloo-file 0.2.3", + "gloo-history 0.1.5", + "gloo-net 0.3.1", + "gloo-render 0.1.1", + "gloo-storage 0.2.2", + "gloo-timers 0.2.6", + "gloo-utils 0.1.7", + "gloo-worker 0.2.1", +] + +[[package]] +name = "gloo" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd35526c28cc55c1db77aed6296de58677dbab863b118483a27845631d870249" +dependencies = [ + "gloo-console 0.3.0", + "gloo-dialogs 0.2.0", + "gloo-events 0.2.0", + "gloo-file 0.3.0", + "gloo-history 0.2.2", + "gloo-net 0.4.0", + "gloo-render 0.2.0", + "gloo-storage 0.3.0", + "gloo-timers 0.3.0", + "gloo-utils 0.2.0", + "gloo-worker 0.4.0", +] + +[[package]] +name = "gloo" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d15282ece24eaf4bd338d73ef580c6714c8615155c4190c781290ee3fa0fd372" +dependencies = [ + "gloo-console 0.3.0", + "gloo-dialogs 0.2.0", + "gloo-events 0.2.0", + "gloo-file 0.3.0", + "gloo-history 0.2.2", + "gloo-net 0.5.0", + "gloo-render 0.2.0", + "gloo-storage 0.3.0", + "gloo-timers 0.3.0", + "gloo-utils 0.2.0", + "gloo-worker 0.5.0", +] + +[[package]] +name = "gloo-console" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b7ce3c05debe147233596904981848862b068862e9ec3e34be446077190d3f" +dependencies = [ + "gloo-utils 0.1.7", + "js-sys", + "serde", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-console" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a17868f56b4a24f677b17c8cb69958385102fa879418052d60b50bc1727e261" +dependencies = [ + "gloo-utils 0.2.0", + "js-sys", + "serde", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-dialogs" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67062364ac72d27f08445a46cab428188e2e224ec9e37efdba48ae8c289002e6" +dependencies = [ + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-dialogs" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf4748e10122b01435750ff530095b1217cf6546173459448b83913ebe7815df" +dependencies = [ + "wasm-bindgen", + "web-sys", +] + +[[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-events" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27c26fb45f7c385ba980f5fa87ac677e363949e065a083722697ef1b2cc91e41" +dependencies = [ + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-file" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d5564e570a38b43d78bdc063374a0c3098c4f0d64005b12f9bbe87e869b6d7" +dependencies = [ + "gloo-events 0.1.2", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-file" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97563d71863fb2824b2e974e754a81d19c4a7ec47b09ced8a0e6656b6d54bd1f" +dependencies = [ + "gloo-events 0.2.0", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-history" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85725d90bf0ed47063b3930ef28e863658a7905989e9929a8708aab74a1d5e7f" +dependencies = [ + "gloo-events 0.1.2", + "gloo-utils 0.1.7", + "serde", + "serde-wasm-bindgen 0.5.0", + "serde_urlencoded", + "thiserror", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-history" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "903f432be5ba34427eac5e16048ef65604a82061fe93789f2212afc73d8617d6" +dependencies = [ + "getrandom", + "gloo-events 0.2.0", + "gloo-utils 0.2.0", + "serde", + "serde-wasm-bindgen 0.6.5", + "serde_urlencoded", + "thiserror", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-net" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a66b4e3c7d9ed8d315fd6b97c8b1f74a7c6ecbbc2320e65ae7ed38b7068cc620" +dependencies = [ + "futures-channel", + "futures-core", + "futures-sink", + "gloo-utils 0.1.7", + "http", + "js-sys", + "pin-project", + "serde", + "serde_json", + "thiserror", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "gloo-net" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ac9e8288ae2c632fa9f8657ac70bfe38a1530f345282d7ba66a1f70b72b7dc4" +dependencies = [ + "futures-channel", + "futures-core", + "futures-sink", + "gloo-utils 0.2.0", + "http", + "js-sys", + "pin-project", + "serde", + "serde_json", + "thiserror", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "gloo-net" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43aaa242d1239a8822c15c645f02166398da4f8b5c4bae795c1f5b44e9eee173" +dependencies = [ + "futures-channel", + "futures-core", + "futures-sink", + "gloo-utils 0.2.0", + "http", + "js-sys", + "pin-project", + "serde", + "serde_json", + "thiserror", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "gloo-render" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd9306aef67cfd4449823aadcd14e3958e0800aa2183955a309112a84ec7764" +dependencies = [ + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-render" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56008b6744713a8e8d98ac3dcb7d06543d5662358c9c805b4ce2167ad4649833" +dependencies = [ + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-storage" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d6ab60bf5dbfd6f0ed1f7843da31b41010515c745735c970e821945ca91e480" +dependencies = [ + "gloo-utils 0.1.7", + "js-sys", + "serde", + "serde_json", + "thiserror", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-storage" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbc8031e8c92758af912f9bc08fbbadd3c6f3cfcbf6b64cdf3d6a81f0139277a" +dependencies = [ + "gloo-utils 0.2.0", + "js-sys", + "serde", + "serde_json", + "thiserror", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-timers" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "gloo-timers" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "gloo-utils" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037fcb07216cb3a30f7292bd0176b050b7b9a052ba830ef7d5d65f6dc64ba58e" +dependencies = [ + "js-sys", + "serde", + "serde_json", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-utils" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5555354113b18c547c1d3a98fbf7fb32a9ff4f6fa112ce823a21641a0ba3aa" +dependencies = [ + "js-sys", + "serde", + "serde_json", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-worker" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13471584da78061a28306d1359dd0178d8d6fc1c7c80e5e35d27260346e0516a" +dependencies = [ + "anymap2", + "bincode", + "gloo-console 0.2.3", + "gloo-utils 0.1.7", + "js-sys", + "serde", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "gloo-worker" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76495d3dd87de51da268fa3a593da118ab43eb7f8809e17eb38d3319b424e400" +dependencies = [ + "bincode", + "futures", + "gloo-utils 0.2.0", + "gloo-worker-macros", + "js-sys", + "pinned", + "serde", + "thiserror", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "gloo-worker" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "085f262d7604911c8150162529cefab3782e91adb20202e8658f7275d2aefe5d" +dependencies = [ + "bincode", + "futures", + "gloo-utils 0.2.0", + "gloo-worker-macros", + "js-sys", + "pinned", + "serde", + "thiserror", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "gloo-worker-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "956caa58d4857bc9941749d55e4bd3000032d8212762586fa5705632967140e7" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.66", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "implicit-clone" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8a9aa791c7b5a71b636b7a68207fdebf171ddfc593d9c8506ec4cbc527b6a84" +dependencies = [ + "implicit-clone-derive", + "indexmap", +] + +[[package]] +name = "implicit-clone-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9311685eb9a34808bbb0608ad2fcab9ae216266beca5848613e95553ac914e3b" +dependencies = [ + "quote", + "syn 2.0.66", +] + +[[package]] +name = "indexmap" +version = "2.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "js-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.155" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" + +[[package]] +name = "log" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "miniz_oxide" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87dfd01fe195c66b572b37921ad8803d010623c0aca821bea2302239d155cdae" +dependencies = [ + "adler", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "object" +version = "0.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "576dfe1fc8f9df304abb159d767a29d0476f7750fbf8aa7ad07816004a207434" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-project" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pinned" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a829027bd95e54cfe13e3e258a1ae7b645960553fb82b75ff852c29688ee595b" +dependencies = [ + "futures", + "rustversion", + "thiserror", +] + +[[package]] +name = "prettyplease" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e" +dependencies = [ + "proc-macro2", + "syn 2.0.66", +] + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit", +] + +[[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 1.0.109", + "version_check", +] + +[[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", +] + +[[package]] +name = "proc-macro2" +version = "1.0.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prokio" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03b55e106e5791fa5a13abd13c85d6127312e8e09098059ca2bc9b03ca4cf488" +dependencies = [ + "futures", + "gloo 0.8.1", + "num_cpus", + "once_cell", + "pin-project", + "pinned", + "tokio", + "tokio-stream", + "wasm-bindgen-futures", +] + +[[package]] +name = "quote" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustversion" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "serde" +version = "1.0.203" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde-wasm-bindgen" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3b143e2833c57ab9ad3ea280d21fd34e285a42837aeb0ee301f4f41890fa00e" +dependencies = [ + "js-sys", + "serde", + "wasm-bindgen", +] + +[[package]] +name = "serde-wasm-bindgen" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8302e169f0eddcc139c70f139d19d6467353af16f9fce27e8c30158036a1e16b" +dependencies = [ + "js-sys", + "serde", + "wasm-bindgen", +] + +[[package]] +name = "serde_derive" +version = "1.0.203" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", +] + +[[package]] +name = "serde_json" +version = "1.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" +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 = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", +] + +[[package]] +name = "tokio" +version = "1.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a" +dependencies = [ + "backtrace", + "pin-project-lite", +] + +[[package]] +name = "tokio-stream" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml_datetime" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[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.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.66", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" + +[[package]] +name = "wasm-logger" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "074649a66bb306c8f2068c9016395fa65d8e08d2affcbf95acf3c24c3ab19718" +dependencies = [ + "log", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "web-client" +version = "0.1.0" +dependencies = [ + "gloo-utils 0.2.0", + "log", + "serde_json", + "wasm-bindgen", + "wasm-logger", + "web-common", + "web-sys", + "yew", + "yew-hooks", +] + +[[package]] +name = "web-common" +version = "0.1.0" +dependencies = [ + "serde", +] + +[[package]] +name = "web-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + +[[package]] +name = "yew" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f1a03f255c70c7aa3e9c62e15292f142ede0564123543c1cc0c7a4f31660cac" +dependencies = [ + "console_error_panic_hook", + "futures", + "gloo 0.10.0", + "implicit-clone", + "indexmap", + "js-sys", + "prokio", + "rustversion", + "serde", + "slab", + "thiserror", + "tokio", + "tracing", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "yew-macro", +] + +[[package]] +name = "yew-hooks" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbe7d30ef9d9afb9be38b1b310c42d59963c6d1c950f0e0435b78b346b4b24bf" +dependencies = [ + "gloo 0.11.0", + "js-sys", + "log", + "serde", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "yew", +] + +[[package]] +name = "yew-macro" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02fd8ca5166d69e59f796500a2ce432ff751edecbbb308ca59fd3fe4d0343de2" +dependencies = [ + "boolinator", + "once_cell", + "prettyplease", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.66", +] diff --git a/crates/runner-host/crates/web-client/Cargo.toml b/crates/runner-host/crates/web-client/Cargo.toml new file mode 100644 index 00000000..d503e2f9 --- /dev/null +++ b/crates/runner-host/crates/web-client/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "web-client" +version = "0.1.0" +authors = ["csdietz@google.com"] +license = "Apache-2.0" +publish = false +edition = "2021" + +[dependencies] +gloo-utils = "0.2.0" +log = "0.4.21" +serde_json = "1.0.117" +wasm-bindgen = "0.2.92" +wasm-logger = "0.2.0" +web-common = { path = "../web-common" } +web-sys = "0.3.69" +yew = { version = "0.21", features = ["csr"] } +yew-hooks = "0.3.2" + +[lints] +clippy.unit-arg = "allow" +rust.unsafe-op-in-unsafe-fn = "warn" +rust.unused-crate-dependencies = "warn" diff --git a/crates/runner-host/crates/web-client/README.md b/crates/runner-host/crates/web-client/README.md new file mode 100644 index 00000000..61e5a44c --- /dev/null +++ b/crates/runner-host/crates/web-client/README.md @@ -0,0 +1,25 @@ +# Wasefire Web Client + +This is a fairly minimal web app for interacting with the host runner. It is built with +[Yew](https://yew.rs) and [Trunk](https://trunkrs.dev/). + +## Running + +```bash +trunk serve +``` + +Rebuilds the app whenever a change is detected and runs a local server to host it. + +There's also the `trunk watch` command which does the same thing but without hosting it. + +## Release + +```bash +trunk build --release +``` + +This builds the app in release mode similar to `cargo build --release`. You can also pass the +`--release` flag to `trunk serve` if you need to get every last drop of performance. + +Unless overwritten, the output will be located in the `dist` directory. diff --git a/crates/runner-host/crates/web-client/data/components/button_pressed.svg b/crates/runner-host/crates/web-client/data/components/button_pressed.svg new file mode 100644 index 00000000..ae559d07 --- /dev/null +++ b/crates/runner-host/crates/web-client/data/components/button_pressed.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/crates/runner-host/crates/web-server/public/components/button.svg b/crates/runner-host/crates/web-client/data/components/button_unpressed.svg similarity index 90% rename from crates/runner-host/crates/web-server/public/components/button.svg rename to crates/runner-host/crates/web-client/data/components/button_unpressed.svg index e13771a8..c5906eb6 100644 --- a/crates/runner-host/crates/web-server/public/components/button.svg +++ b/crates/runner-host/crates/web-client/data/components/button_unpressed.svg @@ -10,13 +10,8 @@ - - - - - diff --git a/crates/runner-host/crates/web-client/data/components/monochrome_led_off.svg b/crates/runner-host/crates/web-client/data/components/monochrome_led_off.svg new file mode 100644 index 00000000..f1b0768e --- /dev/null +++ b/crates/runner-host/crates/web-client/data/components/monochrome_led_off.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/crates/runner-host/crates/web-server/public/components/monochrome_led.svg b/crates/runner-host/crates/web-client/data/components/monochrome_led_on.svg similarity index 100% rename from crates/runner-host/crates/web-server/public/components/monochrome_led.svg rename to crates/runner-host/crates/web-client/data/components/monochrome_led_on.svg diff --git a/crates/runner-host/crates/web-server/public/favicon.png b/crates/runner-host/crates/web-client/data/favicon.png similarity index 100% rename from crates/runner-host/crates/web-server/public/favicon.png rename to crates/runner-host/crates/web-client/data/favicon.png diff --git a/crates/runner-host/crates/web-client/data/index.scss b/crates/runner-host/crates/web-client/data/index.scss new file mode 100644 index 00000000..fa4ef266 --- /dev/null +++ b/crates/runner-host/crates/web-client/data/index.scss @@ -0,0 +1,76 @@ +/* Scrollbar */ +* { + scrollbar-width: thin; + scrollbar-color: rgb(12, 74, 19) rgb(10, 10, 10); +} + +*::-webkit-scrollbar { + width: 12px; +} + +*::-webkit-scrollbar-track { + background: rgba(10, 10, 10, .9); +} + +*::-webkit-scrollbar-thumb { + background-color: blue; + border-radius: 20px; + border: 3px solid rgb(12, 74, 19); +} + +svg { + pointer-events: none; +} + +.board { + padding: 18px; + width: 30px; // Matches size of icons. +} + +.button { + /* + Prevents bug where button gets stuck on drag. + */ + user-drag: none; + -webkit-user-drag: none; + user-select: none; + -moz-user-select: none; + -webkit-user-select: none; + -ms-user-select: none; +} + +.console { + position: absolute; + min-width: 20px; + min-height: 20px; + background: rgba(45, 45, 45, 0.95); + box-shadow: rgba(10, 10, 10, 0.95) 3px 3px 0px; + inset: 100px 20px 20px 79px; + display: flex; + flex-direction: column; +} + +.console-form { + display: flex; +} + +input#consolein { + flex-grow: 1; +} + +.console-display { + flex-grow: 1; + color: white; + overflow-y: auto; + flex-direction: column-reverse; + display: flex; + font-family: "Fira Code", monospace; +} + +body { + background-color: rgb(51, 126, 58); + border: 7px solid white; + border-top: 14px solid white; + min-height: 100vh; + margin: 0; +} diff --git a/crates/runner-host/crates/web-server/public/manifest.json b/crates/runner-host/crates/web-client/data/manifest.json similarity index 100% rename from crates/runner-host/crates/web-server/public/manifest.json rename to crates/runner-host/crates/web-client/data/manifest.json diff --git a/crates/runner-host/crates/web-server/public/title.svg b/crates/runner-host/crates/web-client/data/title.svg similarity index 100% rename from crates/runner-host/crates/web-server/public/title.svg rename to crates/runner-host/crates/web-client/data/title.svg diff --git a/crates/runner-host/crates/web-client/index.html b/crates/runner-host/crates/web-client/index.html new file mode 100644 index 00000000..dbcd0571 --- /dev/null +++ b/crates/runner-host/crates/web-client/index.html @@ -0,0 +1,18 @@ + + + + Wasefire + + + + + + + + + + + + + + diff --git a/crates/runner-host/crates/web-client/src/app.rs b/crates/runner-host/crates/web-client/src/app.rs new file mode 100644 index 00000000..5848a7af --- /dev/null +++ b/crates/runner-host/crates/web-client/src/app.rs @@ -0,0 +1,73 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use gloo_utils::window; +use log::{info, warn}; +use wasm_bindgen::UnwrapThrowExt; +use web_common::Command; +use yew::prelude::*; + +use crate::board::Board; +use crate::console::Console; +use crate::hooks::use_runner_connection; + +#[function_component(App)] +pub fn app() -> Html { + let runner_connection = { + let host = window().location().host().unwrap_throw(); + let web_socket_url = format!("ws://{host}/board"); + info!("Connecting to runner at {web_socket_url}"); + use_runner_connection(web_socket_url) + }; + let on_new_console_msg = Callback::from({ + let runner_connection = runner_connection.clone(); + move |msg| runner_connection.send_console_event(msg) + }); + let on_board_ready = Callback::from({ + let runner_connection = runner_connection.clone(); + move |()| runner_connection.send_board_ready() + }); + let send_event_callback = Callback::from({ + let runner_connection = runner_connection.clone(); + move |event| runner_connection.send_event(event) + }); + + use_effect_with(runner_connection.command_state.clone(), move |command_state| { + if let Some(command) = &**command_state { + info!("Command: {command:?}"); + match command { + Command::Connected => info!("Connected to runner"), + Command::Disconnected => warn!("Disconnected from runner"), + _ => (), // Command for other component so ignoring. + } + } + || () + }); + + html! { +
+ + + + + } +} diff --git a/crates/runner-host/crates/web-client/src/board.rs b/crates/runner-host/crates/web-client/src/board.rs new file mode 100644 index 00000000..d9e34953 --- /dev/null +++ b/crates/runner-host/crates/web-client/src/board.rs @@ -0,0 +1,69 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use log::info; +use web_common::{Command, Component, Event}; +use yew::prelude::*; +use yew::{function_component, html, Html, Properties}; + +use crate::board_components::button::Button; +use crate::board_components::led::LED; + +#[derive(Properties, PartialEq)] +pub struct Props { + pub command_state: UseStateHandle>, + pub on_board_ready: Callback<()>, + pub on_event: Callback, +} + +#[function_component] +pub fn Board(Props { command_state, on_board_ready, on_event }: &Props) -> Html { + let board_config = use_state(|| None); + + use_effect_with(command_state.clone(), { + let board_config = board_config.clone(); + let on_board_ready = on_board_ready.clone(); + move |command_state| { + if let Some(command) = &**command_state { + match command { + Command::BoardConfig { components } => { + info!("Board config: {components:?}"); + board_config.set(Some(components.clone())); + on_board_ready.emit(()); + } + Command::Disconnected => { + board_config.set(None); + } + _ => (), + } + } + || () + } + }); + + html! { +
{ + if let Some(board_config) = &*board_config { + board_config.iter().map(|component| match component { + Component::Button { id } => html!(
+ } +} diff --git a/crates/runner-host/crates/web-client/src/board_components.rs b/crates/runner-host/crates/web-client/src/board_components.rs new file mode 100644 index 00000000..f0e88b2f --- /dev/null +++ b/crates/runner-host/crates/web-client/src/board_components.rs @@ -0,0 +1,16 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +pub mod button; +pub mod led; diff --git a/crates/runner-host/crates/web-client/src/board_components/button.rs b/crates/runner-host/crates/web-client/src/board_components/button.rs new file mode 100644 index 00000000..10592212 --- /dev/null +++ b/crates/runner-host/crates/web-client/src/board_components/button.rs @@ -0,0 +1,76 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use log::info; +use web_common::{ButtonState, Event}; +use yew::prelude::*; + +#[derive(Properties, PartialEq)] +pub struct Props { + #[prop_or_default] + pub id: usize, + pub on_event: Callback, +} + +#[function_component] +pub fn Button(Props { id, on_event }: &Props) -> Html { + let id = *id; + let pressed = use_state(|| false); + let press = Callback::from({ + let pressed = pressed.clone(); + let on_event = on_event.clone(); + move |_| { + info!("Pressed button id: {id}"); + pressed.set(true); + on_event.emit(Event::Button { component_id: id, state: ButtonState::Pressed }); + } + }); + let unpress = Callback::from({ + let pressed = pressed.clone(); + let on_event = on_event.clone(); + move |_| { + info!("Unpressed button id: {id}"); + // Necessary because it may also be triggered when the mouse stops hovering over the + // button. + if *pressed { + pressed.set(false); + on_event.emit(Event::Button { component_id: id, state: ButtonState::Released }); + } + } + }); + + html! { +
+ + +
+ } +} diff --git a/crates/runner-host/crates/web-client/src/board_components/led.rs b/crates/runner-host/crates/web-client/src/board_components/led.rs new file mode 100644 index 00000000..fe816da3 --- /dev/null +++ b/crates/runner-host/crates/web-client/src/board_components/led.rs @@ -0,0 +1,58 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use log::info; +use web_common::Command; +use yew::prelude::*; + +#[derive(Properties, PartialEq)] +pub struct Props { + #[prop_or_default] + pub id: usize, + pub command_state: UseStateHandle>, +} + +#[function_component] +pub fn LED(Props { id, command_state }: &Props) -> Html { + let id = *id; + let lit = use_state(|| false); + + use_effect_with(command_state.clone(), { + let lit = lit.clone(); + move |command_state| { + if let Some(Command::Set { component_id, state }) = &**command_state { + if *component_id == id { + info!("Set command: component_id: {component_id} state: {state}"); + lit.set(*state); + } + } + } + }); + + html! { +
+ + + + } +} diff --git a/crates/runner-host/crates/web-client/src/console.rs b/crates/runner-host/crates/web-client/src/console.rs new file mode 100644 index 00000000..bc9b0bc3 --- /dev/null +++ b/crates/runner-host/crates/web-client/src/console.rs @@ -0,0 +1,87 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use log::info; +use wasm_bindgen::JsCast; +use web_common::Command; +use web_sys::HtmlInputElement; +use yew::prelude::*; +use yew_hooks::prelude::*; + +#[derive(Properties, PartialEq)] +pub struct Props { + #[prop_or_default] + pub id: usize, + pub command_state: UseStateHandle>, + pub on_new_console_msg: Callback, +} + +#[function_component(Console)] +pub fn console(Props { id, command_state, on_new_console_msg }: &Props) -> Html { + let history = use_list(vec![]); + let console_ref = use_node_ref(); + let button_enabled = use_state(|| false); + let onsubmit = Callback::from({ + let history = history.clone(); + let console_ref = console_ref.clone(); + let on_new_console_msg = on_new_console_msg.clone(); + move |e: SubmitEvent| { + e.prevent_default(); + let input_form: HtmlInputElement = + console_ref.get().unwrap().value_of().dyn_into().unwrap(); + let value = input_form.value(); + info!("sending console message: {value}"); + history.push(format!("[send]: {value}")); + on_new_console_msg.emit(value); + input_form.set_value(""); + } + }); + + use_effect_with(command_state.clone(), { + let history = history.clone(); + let button_enabled = button_enabled.clone(); + move |command_state| { + if let Some(command) = &**command_state { + info!("Command: {command:?}"); + match command { + Command::Log { message } => { + history.push(format!("[recv]: {message}")); + } + Command::Disconnected => { + history.push("Disconnected from runner".to_string()); + button_enabled.set(false); + } + Command::Connected => { + history.push("Connected to runner".to_string()); + button_enabled.set(true); + } + _ => (), + } + } + || () + } + }); + + html! { +
+
{ + for history.current().iter().rev().map(|message| html!(
{ message }
)) + }
+
+ + +
+
+ } +} diff --git a/crates/runner-host/crates/web-client/src/hooks.rs b/crates/runner-host/crates/web-client/src/hooks.rs new file mode 100644 index 00000000..c7eb49a7 --- /dev/null +++ b/crates/runner-host/crates/web-client/src/hooks.rs @@ -0,0 +1,83 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use log::{info, warn}; +use web_common::{Command, Event}; +use yew::prelude::*; +use yew_hooks::prelude::*; + +#[derive(Clone)] +pub struct UseRunnerConnectionHandle { + pub ws: UseWebSocketHandle, + pub command_state: UseStateHandle>, +} + +impl UseRunnerConnectionHandle { + pub fn send_console_event(&self, console_msg: String) { + warn!("Backend processing of console input not yet implemented."); + warn!("Ignoring message: {console_msg}"); + } + pub fn send_board_ready(&self) { + self.ws.send(serde_json::to_string(&Event::BoardReady).unwrap()); + } + pub fn send_event(&self, event: Event) { + self.ws.send(serde_json::to_string(&event).unwrap()); + } +} + +fn set_disconnected(command_state: UseStateHandle>) { + match &*command_state { + None | Some(Command::Disconnected) => (), + _ => command_state.set(Some(Command::Disconnected)), + } +} + +#[hook] +pub fn use_runner_connection(backend_address: String) -> UseRunnerConnectionHandle { + let command_state = use_state(|| None); + let ws = use_websocket_with_options( + backend_address, + UseWebSocketOptions { + reconnect_interval: Some(1000), + onmessage: Some(Box::new({ + let command_state = command_state.clone(); + move |message| { + info!("Message: {message}"); + match serde_json::from_str::(&message) { + Ok(command) => command_state.set(Some(command)), + Err(err) => warn!("Error parsing message: {err}"), + } + } + })), + onclose: Some(Box::new({ + let command_state = command_state.clone(); + move |close_event| { + let close_event_type = close_event.type_(); + warn!("Socket closed {close_event_type} setting disconnect event."); + set_disconnected(command_state.clone()); + } + })), + onerror: Some(Box::new({ + let command_state = command_state.clone(); + move |event| { + let event_type = event.type_(); + warn!("Socket error: {event_type} setting disconnect event."); + set_disconnected(command_state.clone()); + } + })), + ..Default::default() + }, + ); + UseRunnerConnectionHandle { ws, command_state } +} diff --git a/crates/runner-host/crates/web-client/src/main.rs b/crates/runner-host/crates/web-client/src/main.rs new file mode 100644 index 00000000..ffb17310 --- /dev/null +++ b/crates/runner-host/crates/web-client/src/main.rs @@ -0,0 +1,24 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +mod app; +mod board; +mod board_components; +mod console; +mod hooks; + +fn main() { + wasm_logger::init(wasm_logger::Config::default()); + yew::Renderer::::new().render(); +} diff --git a/crates/runner-host/crates/web-client/test.sh b/crates/runner-host/crates/web-client/test.sh new file mode 100755 index 00000000..b3e064be --- /dev/null +++ b/crates/runner-host/crates/web-client/test.sh @@ -0,0 +1,22 @@ +#!/bin/sh +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -e + +. "$(git rev-parse --show-toplevel)"/scripts/test-helper.sh + +test_helper + +cargo test --bin=web-client diff --git a/crates/runner-host/crates/web-server/public/board.js b/crates/runner-host/crates/web-server/public/board.js deleted file mode 100644 index d5b78ba0..00000000 --- a/crates/runner-host/crates/web-server/public/board.js +++ /dev/null @@ -1,368 +0,0 @@ -// Copyright 2023 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -/** - * @fileoverview Implements the main UI for the board. - */ - -// How many linkes to keep in the log. -const MAX_LOG_LINES = 300; - -/** Websocket wrapper class. - * - * Ensures that a connection to the backend is healthy, or tries to fix it. - * Serializes messages to and from the backend. - * - * @class MessageChannel - */ -class MessageChannel { - constructor({urlEndpoint, onOpen, onMessage, onClose}) { - this._args = {urlEndpoint, onOpen, onMessage, onClose}; - this._connect(this._args); - } - - _connect({urlEndpoint, onOpen, onMessage, onClose}) { - this.ws = new WebSocket(urlEndpoint); - this.ws.onopen = (event) => onOpen(); - this.ws.onmessage = (event) => onMessage(this._parseMessage(event)); - this.ws.onclose = (event) => { - setTimeout(() => this._connect(this._args), 1000); - onClose(); - }; - this.ws.onerror = (event) => { - this.ws.close(); - }; - } - - send(message) { - this.ws.send(this._encodeMessage(message)); - } - - _encodeMessage(message) { - return JSON.stringify(message); - } - - _parseMessage(event) { - const message = JSON.parse(event.data); - return message; - } -} - -/** Draws the board. - * - * Since the app is simple, this draws the board directly as an SVG. - * If the complexity of the UI increases, we'll move to a JS framework like - * Vue or Lit. - * - * @class BoardDrawer - */ -class BoardDrawer { - constructor(outputElement) { - this.ioPanel = document.createElement("div"); - this.log = document.createElement("div"); - this.inputWrapper = document.createElement("div"); - this.input = document.createElement("input"); - this.button = document.createElement("button"); - this.board = document.createElement("div"); - this.overlay = document.createElement("div"); - this.overlayCentered = document.createElement("div"); - this.overlayCenteredTitle = document.createElement("div"); - this.overlayCenteredMessage = document.createElement("div"); - this.overlay.appendChild(this.overlayCentered); - this.overlayCentered.appendChild(this.overlayCenteredTitle); - this.overlayCentered.appendChild(this.overlayCenteredMessage); - this.inputWrapper.appendChild(this.input); - this.inputWrapper.appendChild(this.button); - this.ioPanel.appendChild(this.log); - this.ioPanel.appendChild(this.inputWrapper); - outputElement.appendChild(this.ioPanel); - outputElement.appendChild(this.board); - outputElement.appendChild(this.overlay); - this._svg = SVG().addTo(this.board).size("100%", "100%"); - this.setVisibility(false); - this.reset(); - } - - /** Places a component on the board. - * - */ - async placeComponent({type}) { - const response = await fetch(`components/${type}.svg`); - const component = this._svg - .group() - .svg(await response.text()) - .move(10, this._nextComponentY); - this._nextComponentY = component.height() + 10 + component.y(); - this._nextComponentX = Math.max( - component.width() + component.x(), - this._nextComponentX, - ); - this.ioPanel.style.left = `${this._nextComponentX + 40}px`; - return component; - } - - async reset() { - this._nextComponentY = 90; - this._nextComponentX = 0; - // Destroy log. - this.log.textContent = ""; - } - - async start() { - await this._build(); - } - - /** Saves the callback to call when the user types something. - * - */ - async setInputCallback(callback) { - this._inputCallback = callback; - } - - async _build() { - this.overlay.style.position = "fixed"; - this.overlay.style.top = "0"; - this.overlay.style.bottom = "0"; - this.overlay.style.right = "0"; - this.overlay.style.left = "0"; - this.overlay.style.background = "white"; - this.overlay.style.display = "flex"; - this.overlay.style.justifyContent = "center"; - this.overlay.style.alignItems = "center"; - this.overlayCentered.style.display = "flex"; - this.overlayCentered.style.flexDirection = "column"; - this.overlayCentered.style.justifyContent = "center"; - this.overlayCentered.style.alignItems = "center"; - this.overlayCenteredTitle.style.fontFamily = "'Montserrat', sans-serif"; - this.overlayCenteredTitle.style.fontSize = "1.5rem"; - this.overlayCenteredTitle.textContent = "Wasefire"; - this.overlayCenteredMessage.style.minHeight = "1rem"; - this.overlayCenteredMessage.style.marginTop = "1rem"; - this.overlayCenteredMessage.style.fontFamily = "'Montserrat', sans-serif"; - this.overlayCenteredMessage.style.fontSize = ".9rem"; - this.overlayCenteredMessage.style.color = "rgba(100,100,100,1)"; - this.board.style.minHeight = "300px"; - this.board.style.minWidth = "100%"; - this._svg - .rect("100%", "100%") - .fill("rgb(51, 126, 58)") - .move(0, 12) - .stroke({color: "rgb(12, 74, 19)", width: 2}) - .radius(5); - const response = await fetch("title.svg"); - const title = this._svg.svg(await response.text()); - this.ioPanel.style.position = "absolute"; - this.ioPanel.style.minWidth = "20px"; - this.ioPanel.style.minHeight = "20px"; - this.ioPanel.style.background = "rgba(45,45,45,.95)"; - this.ioPanel.style.boxShadow = "3px 3px 0 rgba(10,10,10,.95)"; - this.ioPanel.style.top = "100px"; - this.ioPanel.style.right = "20px"; - this.ioPanel.style.left = "20px"; - this.ioPanel.style.bottom = "20px"; - this.ioPanel.style.display = "flex"; - this.ioPanel.style.flexDirection = "column"; - this.log.style.flexGrow = "1"; - this.log.style.color = "white"; - this.log.style.paddingLeft = "1rem"; - this.log.style.overflowY = "auto"; - this.log.style.flexDirection = "column-reverse"; - this.log.style.display = "flex"; - this.log.style.fontFamily = "'Fira Code'"; - this.inputWrapper.style.display = "flex"; - this.input.style.flexGrow = "1"; - this.input.focus(); - this.input.onkeyup = (event) => { - if (event.key != "Enter") return; - this._sendInput(); - }; - this.button.textContent = "Send"; - this.button.style.cursor = "pointer"; - this.button.onclick = () => this._sendInput(); - } - - async _sendInput() { - this.button.disabled = true; - if (this._inputCallback) { - await this._inputCallback({ - type: "input", - message: this.input.value, - }); - this.appendInput(this.input.value); - } - this.input.value = ""; - this.button.disabled = false; - } - - _dropOldLogElements() { - [...this.log.children] - .slice(MAX_LOG_LINES) - .map((c) => this.log.removeChild(c)); - } - - _appendLog(message) { - const p = document.createElement("p"); - p.innerText = message; - p.style.margin = "3px 0"; - p.style.transition = "opacity .1s linear"; - p.style.opacity = "0.5"; - this.log.prepend(p); - setTimeout(() => { - p.style.opacity = "1"; - }, 1); - this._dropOldLogElements(); - return p; - } - - appendInput(message) { - const p = this._appendLog(message); - p.style.marginLeft = "auto"; - p.style.marginRight = "1rem"; - p.style.color = "rgba(200,255,255,0.7)"; - } - - appendStatus(message) { - const p = this._appendLog(message); - p.style.color = "rgba(0,255,255,0.7)"; - this.overlayCenteredMessage.innerText = message; - } - - appendEvent(message) { - const p = this._appendLog(message); - p.style.color = "rgba(0,100,255,0.7)"; - } - - appendLog(message) { - this._appendLog(message); - } - - setVisibility(visible) { - const overlayVisibility = visible ? "hidden" : "visible"; - if (this.overlay.style.visibility == overlayVisibility) return; - this.overlay.style.visibility = overlayVisibility; - this.overlayCenteredTitle.classList.remove("loading-title"); - this.overlayCenteredMessage.classList.remove("loading-message"); - if (visible) return; - setTimeout(() => { - this.overlayCenteredTitle.classList.add("loading-title"); - this.overlayCenteredMessage.classList.add("loading-message"); - }, 50); - } -} - -/** Top controller class. - * - * Creates a board and a message channel. Processes user messages. - * - * @class Board - */ -class Board { - constructor(urlEndpoint, outputElement) { - this._connected = false; - this._components = []; - this._drawer = new BoardDrawer(outputElement); - this._channel = new MessageChannel({ - urlEndpoint, - onOpen: () => { - this._connected = true; - this._drawer.setVisibility(false); - this._drawer.appendStatus( - "Connection established! Waiting for runner configuration...", - ); - }, - onClose: () => { - if (this._connected) { - this._drawer.appendStatus( - "Backend disconnected. Waiting for it to restart...", - ); - this._connected = false; - } - }, - onMessage: (message) => this.processMessage(message), - }); - this._drawer.setInputCallback((message) => this._channel.send(message)); - this._ensureOnlyThisTabIsOpen(); - } - - getComponentFromId(id) { - for (const component of this._components) { - if (component.id == id) return component; - } - } - - async processMessage(message) { - const messageType = message["type"]; - if (messageType == "board_config") { - this._drawer.appendStatus("Board configuration received."); - this._drawer.setVisibility(true); - this._setupBoard(message["components"]); - } else if (messageType == "set") { - const component = this.getComponentFromId(message["componentId"]); - if (component) component.set(message["state"]); - } else if (messageType == "disconnected") { - this._drawer.appendStatus( - "Runner disconnected. Waiting for new connection...", - ); - this._drawer.setVisibility(false); - } else if (messageType == "connected") { - this._drawer.appendStatus("Backend connected. Waiting for runner..."); - this._drawer.setVisibility(false); - } else if (messageType == "log") { - this._drawer.appendLog(message["message"]); - } else { - this._drawer.appendStatus(`Unknown message: ${JSON.stringify(message)}`); - } - } - - async _setupBoard(componentsSpecs) { - this._components.map((c) => c.destroy()); - this._drawer.reset(); - this._components = []; - - for (const componentSpecs of componentsSpecs) { - const componentType = componentSpecs["type"]; - const componentId = componentSpecs["id"]; - const componentClass = { - monochrome_led: MonochromeLedComponent, - button: ButtonComponent, - }[componentType]; - const component = new componentClass( - componentId, - this._drawer, - this._channel, - ); - await component.place(); - await component.reset(); - this._components.push(component); - } - this._drawer.appendStatus("Board is ready!"); - this._channel.send({ - componentType: "board_ready", - }); - } - - async start() { - await this._drawer.start(); - } - - _ensureOnlyThisTabIsOpen() { - // Allows only one tab to be open on this site. - // This prevents messages from being lost. - const broadcast = new BroadcastChannel('intertab'); - broadcast.onmessage = function(event) { - if (event?.data?.message === 'TAKEOVER') window.close(); - } - broadcast.postMessage({ message: 'TAKEOVER' }); - } -} diff --git a/crates/runner-host/crates/web-server/public/board_components.js b/crates/runner-host/crates/web-server/public/board_components.js deleted file mode 100644 index f9b4abcd..00000000 --- a/crates/runner-host/crates/web-server/public/board_components.js +++ /dev/null @@ -1,140 +0,0 @@ -// Copyright 2023 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -/** - * @fileoverview Implements the available board components. - */ - -/** - * Board component abstract class. - * - * @class BoardComponent - */ -class BoardComponent { - /** - @constructor - @abstract - */ - constructor(componentId, boardDrawer, channel) { - if (new.target === BoardComponent) - throw new Error("Abstract class cannot be instantiated."); - this._drawer = boardDrawer; - this._channel = channel; - this.id = componentId; - } - - destroy() { - if (!this._element) return; - this._element.svg(""); - } - - /** - @abstract - */ - reset() { - throw new Error("Abstract method called."); - } -} - -/** - * A clickable button. - * - * @class ButtonComponent - * @extends {BoardComponent} - */ -class ButtonComponent extends BoardComponent { - constructor(componentId, boardDrawer, channel) { - super(componentId, boardDrawer, channel); - this._status = false; - } - - async place() { - this._element = await this._drawer.placeComponent({type: "button"}); - this._element.on("mousedown", () => this.onPressed()); - this._element.on("mouseup", () => this.onReleased()); - } - - onPressed() { - this._channel.send({ - componentId: this.id, - componentType: "button", - state: "pressed", - }); - this.set(true); - } - onReleased() { - this._channel.send({ - componentId: this.id, - componentType: "button", - state: "released", - }); - this.set(false); - } - - reset() { - this.set(false); - } - - get() { - return this._status; - } - - set(value) { - this._status = value; - const pressed = this._element.findOne("#pressed"); - const notPressed = this._element.findOne("#not_pressed"); - if (value) { - pressed.show(); - notPressed.hide(); - } else { - notPressed.show(); - pressed.hide(); - } - } -} - -/** - * A blinky led. - * - * @class ButtonComponent - * @extends {BoardComponent} - */ -class MonochromeLedComponent extends BoardComponent { - constructor(componentId, boardDrawer, channel) { - super(componentId, boardDrawer, channel); - this._status = false; - } - - async place() { - this._element = await this._drawer.placeComponent({type: "monochrome_led"}); - } - - reset() { - this.set(false); - } - - get() { - return this._status; - } - - set(value) { - this._status = value; - const light = this._element.findOne("#on"); - if (value) { - light.show(); - } else { - light.hide(); - } - } -} diff --git a/crates/runner-host/crates/web-server/public/index.html b/crates/runner-host/crates/web-server/public/index.html deleted file mode 100644 index 26e3de97..00000000 --- a/crates/runner-host/crates/web-server/public/index.html +++ /dev/null @@ -1,37 +0,0 @@ - - - Wasefire - - - - - - - - - - - - -
- - - - - diff --git a/crates/runner-host/crates/web-server/public/service-worker.js b/crates/runner-host/crates/web-server/public/service-worker.js deleted file mode 100644 index a07c81fe..00000000 --- a/crates/runner-host/crates/web-server/public/service-worker.js +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright 2023 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -const CACHE_KEY = "wasefire v0.1"; - -const CACHE = [ - "/", - "index.html", - "style.css", - "title.svg", - "board_components.js", - "board.js", - "manifest.json", - "favicon.png", - "components/button.svg", - "components/monochrome_led.svg", -]; - -async function install() { - const cache = await self.caches.open(CACHE_KEY); - await cache.addAll(CACHE); - await self.skipWaiting(); -} - -async function deleteOldCaches() { - for (const key of await self.caches.keys()) { - if (key !== CACHE_KEY) self.caches.delete(key); - } -} - -async function fetchFromNetworkOrCache(request) { - const url = new URL(request.url); - const cache = await self.caches.open(CACHE_KEY); - let liveResponse = null; - try { - liveResponse = await fetch(request); - } catch (err) { - // Backend is offline. - } - // Update the cache. - if (liveResponse && liveResponse.ok) { - cache.put(request, liveResponse.clone()); - return liveResponse; - } - const cached_response = await cache.match(request); - return cached_response; -} - -self.addEventListener("install", (event) => event.waitUntil(install())); - -self.addEventListener("activate", (event) => - event.waitUntil(deleteOldCaches().then(self.clients.claim())), -); - -self.addEventListener("fetch", (event) => { - const url = new URL(event.request.url); - if (self.location.hostname != url.hostname) return; - if (url.pathname.endsWith('board')) return; - event.respondWith(fetchFromNetworkOrCache(event.request)); -}); diff --git a/crates/runner-host/crates/web-server/public/style.css b/crates/runner-host/crates/web-server/public/style.css deleted file mode 100644 index 67ed3384..00000000 --- a/crates/runner-host/crates/web-server/public/style.css +++ /dev/null @@ -1,50 +0,0 @@ -/* Scrollbar */ -* { - scrollbar-width: thin; - scrollbar-color: rgb(12,74,19) rgb(10,10,10); -} -*::-webkit-scrollbar { - width: 12px; -} -*::-webkit-scrollbar-track { - background: rgba(10,10,10,.9); -} -*::-webkit-scrollbar-thumb { - background-color: blue; - border-radius: 20px; - border: 3px solid rgb(12, 74, 19); -} - - -/* Loading animation */ -.loading-title { - animation: loading-title 1s cubic-bezier(0.2, 0.6, 0.4, 1.000) both; -} -@keyframes loading-title { - 0% { - letter-spacing: -0.4em; - opacity: 0; - } - 50% { - opacity: 0.5; - } - 100% { - opacity: 1; - } -} - -.loading-message { - animation: loading-message 1.0s linear both; -} -@keyframes loading-message { - 0% { - opacity: 0; - } - 20% { - opacity: 0; - } - 100% { - opacity: 1; - } -} - diff --git a/crates/runner-host/crates/web-server/src/lib.rs b/crates/runner-host/crates/web-server/src/lib.rs index a39e6cd4..774ed89f 100644 --- a/crates/runner-host/crates/web-server/src/lib.rs +++ b/crates/runner-host/crates/web-server/src/lib.rs @@ -42,7 +42,7 @@ impl Client { let (sender, mut receiver) = oneshot::channel(); let client = Arc::new(Mutex::new(Some((sender, events)))); - let static_files = warp::fs::dir("crates/web-server/public"); + let static_files = warp::fs::dir("crates/web-client/dist"); let ws = warp::path("board") .and(warp::ws()) .and(warp::any().map(move || client.clone())) diff --git a/crates/runner-host/src/main.rs b/crates/runner-host/src/main.rs index b5b0b0f8..9c8d065e 100644 --- a/crates/runner-host/src/main.rs +++ b/crates/runner-host/src/main.rs @@ -79,6 +79,9 @@ async fn main() -> Result<()> { } } }); + let mut trunk = std::process::Command::new("../../scripts/wrapper.sh"); + trunk.args(["trunk", "build", "--release", "crates/web-client/index.html"]); + wasefire_cli_tools::cmd::execute(&mut trunk)?; let url = format!("{}:{}", flags.web_options.web_host, flags.web_options.web_port); web_server::Client::new(&url, sender).await? }; diff --git a/scripts/ci-copyright.sh b/scripts/ci-copyright.sh index e6492701..8f6ea169 100755 --- a/scripts/ci-copyright.sh +++ b/scripts/ci-copyright.sh @@ -22,7 +22,7 @@ set -e for file in $(git ls-files ':(attr:textreview)'); do case "$file" in *.gitignore|.git*|LICENSE|*/LICENSE) continue ;; - *.cff|*.css|*.html|*.json|*.lock|*.md|*.svg|*.toml|*.txt|*.x|*.yml) continue ;; + *.cff|*.css|*.html|*.json|*.lock|*.md|*.scss|*.svg|*.toml|*.txt|*.x|*.yml) continue ;; crates/cli-tools/src/data/lib.rs) continue ;; esac sed -n 'N;/Copyright/q;q1' "$file" || e "No copyright notice in $file" diff --git a/scripts/wrapper.sh b/scripts/wrapper.sh index c5d1aeee..26933a29 100755 --- a/scripts/wrapper.sh +++ b/scripts/wrapper.sh @@ -38,6 +38,7 @@ ensure_cargo() { local locked=--locked { cargo install --list --root="$CARGO_ROOT" | grep -q "^$1 v$2:\$"; } && return [ "$1" = taplo-cli ] && locked= + [ "$1" = trunk ] && locked= shift 2 x cargo install $locked --root="$CARGO_ROOT" "$flags" "$@" } @@ -55,6 +56,7 @@ case "$1" in probe-rs) ensure_cargo probe-rs-tools 0.24.0 ;; rust-objcopy|rust-size) ensure_cargo cargo-binutils 0.3.6 ;; taplo) ensure_cargo taplo-cli 0.9.0 ;; + trunk) ensure_cargo trunk 0.19.3 ;; twiggy) ensure_cargo twiggy 0.7.0 ;; *) IS_CARGO=n ;; esac