Initial Commit
This commit is contained in:
609
frontend/Cargo.lock
generated
Normal file
609
frontend/Cargo.lock
generated
Normal file
@ -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",
|
||||
]
|
||||
31
frontend/Cargo.toml
Normal file
31
frontend/Cargo.toml
Normal file
@ -0,0 +1,31 @@
|
||||
[package]
|
||||
name = "hemma_web"
|
||||
version = "0.1.0"
|
||||
authors = ["Joakim Hulthe <joakim@hulthe.net"]
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
seed = "0.9.1"
|
||||
wasm-bindgen = "=0.2.80" # 0.2.81 has a breaking change
|
||||
serde = { version = "1", features = ['derive'] }
|
||||
serde_json = "1"
|
||||
anyhow = "*"
|
||||
ron = "0.7.1"
|
||||
|
||||
[dependencies.css_typegen]
|
||||
git = "https://github.com/hulthe/css_typegen.git"
|
||||
branch = "master"
|
||||
|
||||
[dependencies.common]
|
||||
path = "../common"
|
||||
|
||||
[dependencies.lighter_lib]
|
||||
git = "https://git.nubo.sh/hulthe/lighter.git"
|
||||
#path = "../../lighter/lib"
|
||||
|
||||
[dependencies.seed_router]
|
||||
git = "https://git.nubo.sh/hulthe/seed_router.git"
|
||||
#path = "../../seed_router/seed_router"
|
||||
18
frontend/Trunk.toml
Normal file
18
frontend/Trunk.toml
Normal file
@ -0,0 +1,18 @@
|
||||
[serve]
|
||||
address = "0.0.0.0"
|
||||
|
||||
|
||||
|
||||
[[proxy]]
|
||||
# This WebSocket proxy example has a backend and ws field. This example will listen for
|
||||
# WebSocket connections at `/api/ws` and proxy them to `ws://localhost:9000/api/ws`.
|
||||
backend = "ws://localhost:8000/api/ws"
|
||||
ws = true
|
||||
|
||||
#[[proxy]]
|
||||
# This proxy example has a backend and a rewrite field. Requests received on `rewrite` will be
|
||||
# proxied to the backend after rewriting the `rewrite` prefix to the `backend`'s URI prefix.
|
||||
# E.G., `/api/v1/resource/x/y/z` -> `/resource/x/y/z`
|
||||
#rewrite = "/api/"
|
||||
#backend = "http://localhost:8000/api/"
|
||||
|
||||
38
frontend/index.html
Executable file
38
frontend/index.html
Executable file
@ -0,0 +1,38 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<!-- stylesheets -->
|
||||
<link data-trunk data-inline rel="scss" href="/static/styles/common.scss">
|
||||
<link data-trunk data-inline rel="scss" href="/static/styles/penguin.scss">
|
||||
|
||||
<!-- fonts -->
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Ubuntu|Ubuntu+Mono&display=swap">
|
||||
|
||||
<!-- pwa manifest -->
|
||||
<!--
|
||||
<link data-trunk rel="copy-file" href="/static/manifest.json">
|
||||
<link rel="manifest" href="/static/manifest.json">
|
||||
-->
|
||||
|
||||
<!-- copy image directory -->
|
||||
<link data-trunk rel="copy-dir" href="/static/images">
|
||||
|
||||
<!-- image preloading -->
|
||||
<link rel="preload" href="/images/penguin1.svg" as="image">
|
||||
<link rel="preload" href="/images/penguin2.svg" as="image">
|
||||
<link rel="preload" href="/images/penguin3.svg" as="image">
|
||||
|
||||
<!-- icon -->
|
||||
<link rel="icon" type="image/png" href="/images/icon.png">
|
||||
|
||||
<title>hemma</title>
|
||||
<meta name="description" content="Home automation and information">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app", style="width: 100%;"></div>
|
||||
</body>
|
||||
</html>
|
||||
151
frontend/src/app.rs
Normal file
151
frontend/src/app.rs
Normal file
@ -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<Msg, Model, Vec<Node<Msg>>>;
|
||||
|
||||
/// 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<ClientMessage>,
|
||||
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<Msg>) -> 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<Msg>) -> 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<Msg>) -> 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<Node<Msg>> {
|
||||
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![],
|
||||
//}
|
||||
}
|
||||
225
frontend/src/components/color_picker.rs
Normal file
225
frontend/src/components/color_picker.rs
Normal file
@ -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<ColorPickerAttr>,
|
||||
}
|
||||
|
||||
#[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<ColorPickerAttr>, MouseEvent),
|
||||
|
||||
SetColor(BulbColor),
|
||||
}
|
||||
|
||||
impl ColorPicker {
|
||||
pub fn update(&mut self, msg: ColorPickerMsg, orders: &mut impl Orders<ColorPickerMsg>) {
|
||||
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<ColorPickerMsg> {
|
||||
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))
|
||||
}
|
||||
1
frontend/src/components/mod.rs
Normal file
1
frontend/src/components/mod.rs
Normal file
@ -0,0 +1 @@
|
||||
pub mod color_picker;
|
||||
7
frontend/src/css.rs
Normal file
7
frontend/src/css.rs
Normal file
@ -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");
|
||||
12
frontend/src/lib.rs
Normal file
12
frontend/src/lib.rs
Normal file
@ -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);
|
||||
}
|
||||
39
frontend/src/page/info.rs
Normal file
39
frontend/src/page/info.rs
Normal file
@ -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::Msg>) -> Self {
|
||||
orders.subscribe(Msg::ServerMessage);
|
||||
|
||||
Model {
|
||||
content: r#"<div class="penguin"></div>"#.into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn update(&mut self, msg: Self::Msg, _orders: &mut impl Orders<Self::Msg>) {
|
||||
match msg {
|
||||
Msg::ServerMessage(ServerMessage::InfoPage { html }) => {
|
||||
self.content = html;
|
||||
}
|
||||
Msg::ServerMessage(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn view(&self) -> Node<Self::Msg> {
|
||||
div![raw![&self.content]]
|
||||
}
|
||||
}
|
||||
240
frontend/src/page/lights.rs
Normal file
240
frontend/src/page/lights.rs
Normal file
@ -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<BulbId, BulbMode>,
|
||||
|
||||
bulb_map: BulbMap,
|
||||
|
||||
/// The currently selected bulb map groups
|
||||
selected_groups: HashSet<usize>,
|
||||
|
||||
/// 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::Msg>) -> Self {
|
||||
orders.subscribe(Msg::ServerMessage);
|
||||
orders.notify(ClientMessage::GetBulbs);
|
||||
|
||||
Model::default()
|
||||
}
|
||||
|
||||
fn update(&mut self, msg: Self::Msg, orders: &mut impl Orders<Self::Msg>) {
|
||||
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<Self::Msg> {
|
||||
//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));
|
||||
}
|
||||
}
|
||||
3
frontend/src/page/mod.rs
Normal file
3
frontend/src/page/mod.rs
Normal file
@ -0,0 +1,3 @@
|
||||
pub mod info;
|
||||
pub mod lights;
|
||||
pub mod not_found;
|
||||
25
frontend/src/page/not_found.rs
Normal file
25
frontend/src/page/not_found.rs
Normal file
@ -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::Msg>) -> Self {
|
||||
Model
|
||||
}
|
||||
|
||||
fn update(&mut self, msg: Self::Msg, _orders: &mut impl Orders<Self::Msg>) {
|
||||
match msg {}
|
||||
}
|
||||
|
||||
fn view(&self) -> Node<Self::Msg> {
|
||||
h1!["404: Not found"]
|
||||
}
|
||||
}
|
||||
BIN
frontend/static/images/blueprint_bg.png
Normal file
BIN
frontend/static/images/blueprint_bg.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.0 KiB |
BIN
frontend/static/images/hsb.png
Normal file
BIN
frontend/static/images/hsb.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 139 KiB |
84
frontend/static/images/penguin1.svg
Normal file
84
frontend/static/images/penguin1.svg
Normal file
@ -0,0 +1,84 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="120"
|
||||
height="120"
|
||||
viewBox="0 0 31.75 31.75"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
sodipodi:docname="penguin1.svg"
|
||||
inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20, custom)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<sodipodi:namedview
|
||||
id="namedview14"
|
||||
pagecolor="#505050"
|
||||
bordercolor="#ffffff"
|
||||
borderopacity="1"
|
||||
inkscape:pageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="1"
|
||||
showgrid="false"
|
||||
inkscape:zoom="6.9083333"
|
||||
inkscape:cx="60"
|
||||
inkscape:cy="68.54041"
|
||||
inkscape:window-width="1702"
|
||||
inkscape:window-height="930"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg8" />
|
||||
<defs
|
||||
id="defs2" />
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
id="layer1"
|
||||
style="display:none">
|
||||
<path
|
||||
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 7.5967544,-6.2400058 c 0,0 -0.041418,0.1031026 -0.02862,0.2093299 0.012798,0.1062273 0.084435,0.1741091 0.084435,0.1741091 l -0.083147,0.088222 c 0,0 -0.1053512,-0.138166 -0.073877,-0.2774823 0.031474,-0.1393166 0.1012092,-0.1941783 0.101209,-0.1941787 z"
|
||||
id="path1039" />
|
||||
</g>
|
||||
<g
|
||||
id="layer2"
|
||||
style="display:inline;fill:#ffffff">
|
||||
<g
|
||||
id="g1085"
|
||||
transform="matrix(4.3607823,0,0,4.3607823,-25.60314,0)"
|
||||
style="fill:#ffffff">
|
||||
<path
|
||||
style="fill:#ffffff;stroke-width:0.0678637"
|
||||
d="m 8.9055733,7.2413582 c -0.565186,-0.09685 -1.176774,-0.415363 -1.632858,-0.850389 -1.503214,-1.43381 -1.115157,-3.86618 0.767032,-4.807792 0.257986,-0.129065 0.613842,-0.340937 0.790791,-0.470828 0.427006,-0.31344801 0.957267,-0.88614801 0.957267,-1.03388001 0,-0.09418 0.01716,-0.101117 0.08424,-0.03403 0.04633,0.04633 0.09978,0.25562 0.118763,0.465073 l 0.03452,0.380824 0.213389,-0.180047 c 0.117363,-0.09903 0.228326,-0.180047 0.246583,-0.180047 0.01825,0 0.05453,0.199156 0.08059,0.442568 0.04564,0.42610801 0.0569,0.44741901 0.303068,0.57300001 0.140621,0.07174 0.394132,0.23612 0.563357,0.365292 l 0.307682,0.234855 0.07434,-0.163155 c 0.159263,-0.34954 0.752058,-0.0992 0.869072,0.367026 0.06159,0.245394 -0.05541,0.517892 -0.23331,0.543385 -0.08323,0.01193 -0.150934,0.0272 -0.150453,0.03393 4.74e-4,0.0067 0.06156,0.205833 0.135728,0.442438 0.385927,1.231145 -0.08926,2.609103 -1.154563,3.348037 -0.643116,0.446087 -1.5987099,0.656795 -2.3752269,0.523734 z m 1.2351777,-0.136144 c 0.558283,-0.117422 1.025941,-0.369806 1.37326,-0.741116 0.473889,-0.506621 0.628567,-0.967713 0.420215,-1.252649 -0.07888,-0.107873 -0.07888,-0.161324 0,-0.350108 0.121083,-0.289793 0.117727,-0.594607 -0.01134,-1.029201 -0.101025,-0.340191 -0.181472,-0.425109 -0.268827,-0.28377 -0.08904,0.144072 -0.389985,0.0647 -0.617562,-0.162872 -0.17695,-0.176948 -0.230737,-0.287688 -0.230737,-0.475043 0,-0.241031 -0.0037,-0.244309 -0.280028,-0.244309 -0.176302,0 -0.427095,0.07473 -0.6770717,0.201763 l -0.397045,0.201762 -0.337336,-0.209698 c -0.815418,-0.506889 -1.602182,-0.242198 -2.027616,0.682148 -0.191776,0.416675 -0.236961,1.14766 -0.08788,1.421699 0.06294,0.115695 0.06651,0.180504 0.01288,0.234129 -0.116061,0.11606 -0.08718,0.501906 0.059,0.78846 0.482693,0.946148 1.827708,1.480112 3.0700767,1.218805 z M 8.8477543,5.2533082 c -0.424112,-0.10747 -0.654904,-0.301236 -0.592603,-0.497529 0.06213,-0.195726 0.665023,-0.497533 1.076087,-0.538679 0.611047,-0.06116 1.5000967,0.382898 1.3821197,0.69034 -0.119917,0.312492 -1.2041087,0.513493 -1.8656037,0.345868 z m 1.2589217,-0.1425 c 0.456161,-0.135464 0.539706,-0.23998 0.34103,-0.426626 -0.419028,-0.393651 -1.1014207,-0.461363 -1.6826577,-0.166964 -0.455585,0.230757 -0.494803,0.363836 -0.150648,0.511195 0.486065,0.208121 0.97753,0.235258 1.4922757,0.0824 z M 8.6341183,4.7696162 c 0,-0.08863 0.121319,-0.101086 0.82579,-0.08483 0.5862597,0.01353 0.8339577,0.04366 0.8539537,0.103883 0.02154,0.06486 -0.172846,0.08483 -0.8257887,0.08483 -0.72146,0 -0.853955,-0.01612 -0.853955,-0.103884 z m 2.9852977,-1.422177 c 0.116451,-0.140314 0.03796,-0.315999 -0.115476,-0.258467 -0.109958,0.04122 -0.111839,0.03384 -0.02006,-0.07879 0.09429,-0.115732 0.08966,-0.120606 -0.06733,-0.07078 -0.151,0.04792 -0.158931,0.04187 -0.07599,-0.05808 0.08138,-0.09805 0.07308,-0.112539 -0.0684,-0.119456 -0.115019,-0.0056 -0.127085,-0.01674 -0.04224,-0.03891 0.145624,-0.03806 0.154107,-0.128726 0.01697,-0.181351 -0.432561,-0.165989 -0.470623,0.571787 -0.04153,0.804994 0.228877,0.12439 0.311386,0.124558 0.414065,8.42e-4 z m 0.337056,-0.43736 0.16668,-0.128093 -0.131214,-0.243646 c -0.07217,-0.134006 -0.148013,-0.243647 -0.168546,-0.243647 -0.02982,0 -0.274289,0.150867 -0.397626,0.245381 -0.03646,0.02793 0.26552,0.501375 0.318664,0.499607 0.02495,-8.28e-4 0.120369,-0.05915 0.212042,-0.129602 z m 0.629143,-0.248805 c 0.176025,-0.386328 -0.430301,-0.9977 -0.667609,-0.673165 -0.104081,0.142338 -0.09554,0.249613 0.02302,0.289132 0.04119,0.01373 0.09163,-0.03017 0.112083,-0.09756 0.02046,-0.06738 0.02721,-0.03826 0.015,0.0647 -0.01797,0.151566 -0.0046,0.1726 0.0703,0.110451 0.111754,-0.09275 0.124608,0.01632 0.01486,0.126073 -0.05828,0.05827 -0.03712,0.06896 0.08483,0.04283 0.137963,-0.02955 0.144981,-0.02359 0.04653,0.03952 -0.06377,0.04088 -0.09868,0.119327 -0.07757,0.174329 0.06225,0.162216 0.290873,0.116122 0.378559,-0.07633 z"
|
||||
id="path913-7" />
|
||||
<path
|
||||
style="fill:#ffffff;stroke-width:0.0691565"
|
||||
d="M 7.8768005,4.236173 C 7.6543017,4.153323 7.6524223,3.710674 7.8742605,3.628625 8.0875869,3.549705 8.2761935,3.613255 8.3404236,3.785709 8.4567516,4.098027 8.1933217,4.354002 7.8768404,4.236173 Z M 8.1361383,3.927654 c 0,-0.117352 -0.1782109,-0.133788 -0.2468281,-0.02276 -0.052137,0.08436 0.1031823,0.211193 0.1916582,0.156514 0.030349,-0.01875 0.055173,-0.07894 0.055173,-0.13375 z m 2.6417907,0.232298 c -0.219104,-0.219102 0.03747,-0.636462 0.342282,-0.556756 0.196786,0.05146 0.30135,0.306717 0.201823,0.492685 -0.08796,0.164344 -0.406338,0.201838 -0.544105,0.06407 z m 0.370255,-0.130506 c 0.07127,-0.115313 -0.08903,-0.247547 -0.184421,-0.152145 -0.0798,0.0798 -0.02986,0.227327 0.07696,0.227327 0.03354,0 0.0819,-0.03383 0.107459,-0.07518 z"
|
||||
id="path913-6-5" />
|
||||
<path
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 10.825894,3.3605122 c 0,0 -0.04142,0.1031026 -0.02862,0.2093299 0.0128,0.1062273 0.08444,0.1741091 0.08444,0.1741091 l -0.08315,0.088222 c 0,0 -0.105351,-0.138166 -0.07388,-0.2774823 0.03147,-0.1393166 0.101209,-0.1941783 0.10121,-0.1941787 z"
|
||||
id="path1039-6" />
|
||||
<path
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 7.7793686,3.3458678 c 0,0 -0.04142,0.1031026 -0.02862,0.2093299 0.0128,0.1062273 0.08444,0.1741091 0.08444,0.1741091 l -0.08315,0.088222 c 0,0 -0.105351,-0.138166 -0.07388,-0.2774823 0.03147,-0.1393166 0.101209,-0.1941783 0.10121,-0.1941787 z"
|
||||
id="path1039-6-2" />
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
id="layer3"
|
||||
style="display:inline" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 7.8 KiB |
66
frontend/static/images/penguin2.svg
Normal file
66
frontend/static/images/penguin2.svg
Normal file
@ -0,0 +1,66 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="120"
|
||||
height="120"
|
||||
viewBox="0 0 31.75 31.75"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
sodipodi:docname="penguin2.svg"
|
||||
inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20, custom)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<sodipodi:namedview
|
||||
id="namedview839"
|
||||
pagecolor="#505050"
|
||||
bordercolor="#ffffff"
|
||||
borderopacity="1"
|
||||
inkscape:pageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="1"
|
||||
showgrid="false"
|
||||
inkscape:zoom="6.2083333"
|
||||
inkscape:cx="6.8456376"
|
||||
inkscape:cy="60"
|
||||
inkscape:window-width="1702"
|
||||
inkscape:window-height="930"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg8" />
|
||||
<defs
|
||||
id="defs2" />
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
id="layer1"
|
||||
style="display:inline;fill:#ffffff">
|
||||
<path
|
||||
style="fill:#ffffff;stroke-width:0.177246"
|
||||
d="m 13.134179,31.651166 c -1.367874,-0.18043 -3.4872278,-0.85238 -4.780367,-1.51563 -1.871747,-0.96002 -4.179215,-3.22966 -5.0074297,-4.92534 -1.5914865,-3.25839 -1.6801158,-7.72336 -0.2278948,-11.48085 1.0247424,-2.65144 2.7098602,-4.5706201 5.4768881,-6.2376401 4.4640374,-2.6894 6.9208324,-4.59636 8.5218534,-6.61464 0.435944,-0.54955 0.851411,-0.9404 0.923263,-0.86855 0.34663,0.34663 0.709996,2.45556 0.596276,3.46071 l -0.118698,1.04918 0.543199,-0.3868 c 0.298763,-0.21273 0.754695,-0.66407 1.013185,-1.00297 0.258491,-0.3389 0.531184,-0.55268 0.605988,-0.47507 0.277608,0.28804 0.872923,2.3693 0.872923,3.05181 0,0.78189 -0.07309,0.70941 2.140938,2.12277 3.360595,2.14527 5.28622,5.2741001 5.845846,9.4985401 0.238089,1.79727 0.119928,4.12317 -0.289284,5.69424 -0.438918,1.68512 -1.295306,3.20011 -2.596637,4.59356 -2.715779,2.90803 -6.119638,4.1954 -10.928887,4.13338 -1.132683,-0.0146 -2.298705,-0.0581 -2.591161,-0.0967 z m 5.683631,-1.1265 c 5.074026,-0.89873 8.492767,-3.70496 9.49005,-7.7898 0.358508,-1.46844 0.317005,-5.28368 -0.07569,-6.95754 -0.594746,-2.53514 -2.005266,-4.78931 -3.64922,-5.8318801 -1.428352,-0.90583 -1.561881,-0.82384 -2.840659,1.7442101 -0.615739,1.23654 -1.777129,3.21756 -2.580868,4.40229 l -1.461342,2.15404 0.927607,0.3153 c 1.188141,0.40386 2.476979,1.20634 2.848748,1.77373 0.272049,0.4152 0.269021,0.4806 -0.04735,1.02231 -0.778356,1.33273 -2.272203,1.79512 -5.849412,1.81056 l -2.340153,0.0101 -2.489801,2.46697 c -2.6276133,2.60352 -2.8788341,3.03045 -2.0880964,3.54856 1.8126654,1.18771 7.0732674,1.8772 10.1561944,1.33115 z m 3.183004,-13.17071 c -0.205126,-0.14999 -0.329285,-0.4602 -0.329285,-0.8227 0,-0.96498 1.087198,-1.43355 1.715448,-0.73934 0.872429,0.96402 -0.338235,2.3283 -1.386163,1.56204 z m -3.186586,4.78402 c 1.133944,-0.30198 1.619925,-0.56299 1.85054,-0.9939 0.246861,-0.46126 -0.269147,-0.94676 -1.770094,-1.66543 -1.062457,-0.50872 -1.296906,-0.55176 -3.013183,-0.55311 -1.794444,-0.001 -1.910137,0.0225 -3.231141,0.66815 -1.860639,0.90936 -2.118197,1.48499 -0.948717,2.12031 1.182601,0.64245 5.357705,0.89133 7.112595,0.42398 z m -5.26747,-0.96936 c -1.113796,-0.13246 -1.303724,-0.23853 -1.149022,-0.64168 0.08921,-0.23246 0.582443,-0.27351 3.286452,-0.27351 1.749822,0 3.307658,0.0484 3.461858,0.10758 0.350902,0.13466 0.365077,0.53097 0.02367,0.66198 -0.432127,0.16582 -4.555272,0.2726 -5.622968,0.14563 z m -3.7845902,-3.81466 c 0.7343882,-0.537 0.2478022,-1.88618 -0.6802563,-1.88618 -0.5030742,0 -0.9992445,0.51278 -0.9992445,1.03268 0,0.90987 0.9453857,1.39029 1.6795008,0.8535 z"
|
||||
id="path865" />
|
||||
<path
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 7.5967544,-6.2400058 c 0,0 -0.041418,0.1031026 -0.02862,0.2093299 0.012798,0.1062273 0.084435,0.1741091 0.084435,0.1741091 l -0.083147,0.088222 c 0,0 -0.1053512,-0.138166 -0.073877,-0.2774823 0.031474,-0.1393166 0.1012092,-0.1941783 0.101209,-0.1941787 z"
|
||||
id="path1039" />
|
||||
</g>
|
||||
<g
|
||||
id="layer2"
|
||||
style="display:none" />
|
||||
<g
|
||||
id="layer3"
|
||||
style="display:none" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.6 KiB |
67
frontend/static/images/penguin3.svg
Normal file
67
frontend/static/images/penguin3.svg
Normal file
@ -0,0 +1,67 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="120"
|
||||
height="120"
|
||||
viewBox="0 0 31.75 31.75"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
sodipodi:docname="penguin3.svg"
|
||||
inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20, custom)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<sodipodi:namedview
|
||||
id="namedview906"
|
||||
pagecolor="#505050"
|
||||
bordercolor="#ffffff"
|
||||
borderopacity="1"
|
||||
inkscape:pageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="1"
|
||||
showgrid="false"
|
||||
inkscape:zoom="6.2083333"
|
||||
inkscape:cx="6.8456376"
|
||||
inkscape:cy="60"
|
||||
inkscape:window-width="1702"
|
||||
inkscape:window-height="930"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg8" />
|
||||
<defs
|
||||
id="defs2" />
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
id="layer1"
|
||||
style="display:none">
|
||||
<path
|
||||
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 7.5967544,-6.2400058 c 0,0 -0.041418,0.1031026 -0.02862,0.2093299 0.012798,0.1062273 0.084435,0.1741091 0.084435,0.1741091 l -0.083147,0.088222 c 0,0 -0.1053512,-0.138166 -0.073877,-0.2774823 0.031474,-0.1393166 0.1012092,-0.1941783 0.101209,-0.1941787 z"
|
||||
id="path1039" />
|
||||
</g>
|
||||
<g
|
||||
id="layer2"
|
||||
style="display:none" />
|
||||
<g
|
||||
id="layer3"
|
||||
style="display:inline;fill:#ffffff">
|
||||
<path
|
||||
style="display:inline;fill:#ffffff;stroke-width:0.294273"
|
||||
d="M 13.219872,31.561152 C 7.7639295,30.619019 3.1854282,26.04262 2.2530273,20.599337 1.3266857,15.191452 4.3731265,9.445337 9.4587119,7.0081512 12.617064,5.4945709 15.836366,2.8084407 16.825842,0.86116517 L 17.263414,0 17.622933,0.67182296 c 0.19778,0.36951884 0.313308,1.25361504 0.256757,1.96469194 -0.05655,0.7110726 -0.05991,1.2928605 -0.0077,1.2928605 0.05242,0 0.52936,-0.3310521 1.059852,-0.7356803 0.530492,-0.4046239 1.05135,-0.7356802 1.157461,-0.7356802 0.106124,0 0.192929,0.845204 0.192929,1.8782354 v 1.8782312 l 1.580587,0.7733087 c 7.26375,3.5537888 9.530087,12.9645358 4.6616,19.3568408 -3.030552,3.979101 -8.363039,6.069856 -13.304766,5.216521 z m 5.356014,-0.592886 c 3.720242,-0.783427 7.101118,-3.48828 7.892345,-6.314256 0.336074,-1.200345 0.329308,-1.506279 -0.04795,-2.163583 -0.383774,-0.668636 -0.386199,-0.89544 -0.01957,-1.791532 0.938375,-2.291723 -0.417007,-6.522811 -2.67179,-8.34061 -0.91567,-0.738208 -1.4406,-0.918725 -2.942743,-1.011981 -1.593611,-0.09893 -2.045846,0.0015 -3.523445,0.781916 l -1.694978,0.895411 -1.487934,-0.924941 c -3.376446,-2.0989003 -6.6719039,-1.163244 -8.4524067,2.399829 -1.1527548,2.306846 -1.4375744,4.436294 -0.8292982,6.200159 0.2922024,0.847387 0.3536045,1.421626 0.1714833,1.603781 -0.4403631,0.440338 -0.3350945,2.176004 0.2005035,3.304594 0.7011922,1.477632 2.814632,3.472702 4.6084279,4.350322 2.6015972,1.272827 5.8052792,1.640958 8.7973002,1.010891 z m -5.355571,-7.922727 c -1.664073,-0.348285 -2.79605,-1.176171 -2.79605,-2.044978 0,-0.89302 2.504528,-2.316003 4.490206,-2.551169 1.788767,-0.211822 3.947996,0.437993 5.353112,1.610989 0.846392,0.706597 0.936274,0.903347 0.653807,1.431145 -0.704264,1.315915 -4.845636,2.151609 -7.701075,1.554013 z m 5.207815,-0.725102 c 2.110112,-0.626633 2.388166,-1.069391 1.265061,-2.014417 -1.037329,-0.87285 -2.579593,-1.366029 -4.266265,-1.364263 -1.351595,0.0015 -4.190311,1.218923 -4.45594,1.911124 -0.557627,1.453161 4.291873,2.407531 7.457144,1.467556 z m -6.385357,-1.479496 c 0,-0.384327 0.526071,-0.438355 3.580814,-0.367817 2.54216,0.05868 3.616241,0.189313 3.702949,0.450461 0.0934,0.281224 -0.749501,0.367859 -3.580813,0.367859 -3.128417,0 -3.70295,-0.06991 -3.70295,-0.450469 z M 8.5850578,18.025628 c -0.9467707,-0.352541 -0.9547704,-2.236086 -0.010638,-2.585219 0.907738,-0.335818 1.7102922,-0.0654 1.9836022,0.668419 0.494995,1.328966 -0.625944,2.418182 -1.9726282,1.9168 z m 1.1035225,-1.3128 c 0,-0.499353 -0.7583177,-0.569291 -1.0502946,-0.09685 -0.2218645,0.358966 0.439061,0.898662 0.815537,0.665994 0.1291443,-0.07979 0.2347576,-0.335903 0.2347576,-0.569129 z m 11.2412737,0.988467 c -0.932325,-0.932316 0.159441,-2.708253 1.456467,-2.36909 0.837358,0.218971 1.282295,1.305132 0.858791,2.096457 -0.374285,0.699311 -1.729037,0.858855 -2.315258,0.272633 z m 1.575497,-0.555325 c 0.303266,-0.490676 -0.378838,-1.053354 -0.784743,-0.647403 -0.339562,0.339563 -0.127059,0.967315 0.327478,0.967315 0.142719,0 0.348498,-0.143952 0.457256,-0.319903 z"
|
||||
id="path913-6" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.0 KiB |
267
frontend/static/styles/common.scss
Normal file
267
frontend/static/styles/common.scss
Normal file
@ -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;
|
||||
}
|
||||
59
frontend/static/styles/penguin.scss
Normal file
59
frontend/static/styles/penguin.scss
Normal file
@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user