diff --git a/Cargo.lock b/Cargo.lock index 3e3d1155..7c50a5c9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -15,7 +15,7 @@ dependencies = [ "derive_more", "futures 0.3.4", "lazy_static", - "log 0.4.8", + "log", "parking_lot", "pin-project", "smallvec", @@ -35,7 +35,7 @@ dependencies = [ "bytes", "futures-core", "futures-sink", - "log 0.4.8", + "log", "tokio", "tokio-util", ] @@ -54,7 +54,7 @@ dependencies = [ "either", "futures 0.3.4", "http", - "log 0.4.8", + "log", "trust-dns-proto", "trust-dns-resolver", ] @@ -84,10 +84,10 @@ dependencies = [ "bytes", "derive_more", "futures 0.3.4", - "log 0.4.8", + "log", "mime", "mime_guess", - "percent-encoding 2.1.0", + "percent-encoding", "v_htmlescape", ] @@ -124,12 +124,12 @@ dependencies = [ "indexmap", "language-tags", "lazy_static", - "log 0.4.8", + "log", "mime", - "percent-encoding 2.1.0", + "percent-encoding", "pin-project", "rand 0.7.3", - "regex 1.3.6", + "regex", "serde", "serde_json", "serde_urlencoded", @@ -161,7 +161,7 @@ dependencies = [ "derive_more", "futures 0.3.4", "httparse", - "log 0.4.8", + "log", "mime", "time 0.1.42", "twoway", @@ -175,8 +175,8 @@ checksum = "9d7a10ca4d94e8c8e7a87c5173aba1b97ba9a6563ca02b0e1cd23531093d3ec8" dependencies = [ "bytestring", "http", - "log 0.4.8", - "regex 1.3.6", + "log", + "regex", "serde", ] @@ -204,7 +204,7 @@ dependencies = [ "actix-service", "actix-utils", "futures 0.3.4", - "log 0.4.8", + "log", "mio", "mio-uds", "net2", @@ -233,7 +233,7 @@ dependencies = [ "actix-server", "actix-service", "futures 0.3.4", - "log 0.4.8", + "log", "net2", ] @@ -246,7 +246,7 @@ dependencies = [ "derive_more", "futures-channel", "lazy_static", - "log 0.4.8", + "log", "num_cpus", "parking_lot", "threadpool", @@ -265,7 +265,7 @@ dependencies = [ "derive_more", "either", "futures 0.3.4", - "log 0.4.8", + "log", ] [[package]] @@ -281,7 +281,7 @@ dependencies = [ "bytes", "either", "futures 0.3.4", - "log 0.4.8", + "log", "pin-project", "slab", ] @@ -310,16 +310,16 @@ dependencies = [ "encoding_rs", "futures 0.3.4", "fxhash", - "log 0.4.8", + "log", "mime", "net2", "pin-project", - "regex 1.3.6", + "regex", "serde", "serde_json", "serde_urlencoded", "time 0.1.42", - "url 2.1.1", + "url", ] [[package]] @@ -365,22 +365,13 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d2e7343e7fc9de883d1b0341e0b13970f764c14101234857d2ddafa1cb1cac2" -[[package]] -name = "aho-corasick" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca972c2ea5f742bfce5687b9aef75506a764f61d37f8f649047846a9686ddb66" -dependencies = [ - "memchr 0.1.11", -] - [[package]] name = "aho-corasick" version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8716408b8bc624ed7f65d223ddb9ac2d044c0547b6fa4b0d554f3a9540496ada" dependencies = [ - "memchr 2.3.3", + "memchr", ] [[package]] @@ -464,9 +455,9 @@ dependencies = [ "bytes", "derive_more", "futures-core", - "log 0.4.8", + "log", "mime", - "percent-encoding 2.1.0", + "percent-encoding", "rand 0.7.3", "serde", "serde_json", @@ -709,7 +700,7 @@ dependencies = [ "lazy_static", "pest", "pest_derive", - "regex 1.3.6", + "regex", "twoway", "typed-arena", "unicode_categories", @@ -733,12 +724,12 @@ checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" [[package]] name = "cookie" -version = "0.12.0" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "888604f00b3db336d2af898ec3c1d5d0ddf5e6d462220f2ededc33a87ac4bbd5" +checksum = "0c60ef6d0bbf56ad2674249b6bb74f2c6aeb98b98dd57b5d3e37cace33011d69" dependencies = [ - "time 0.1.42", - "url 1.7.2", + "percent-encoding", + "time 0.2.15", ] [[package]] @@ -1025,16 +1016,6 @@ dependencies = [ "syn", ] -[[package]] -name = "env_logger" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15abd780e45b3ea4f76b4e9a26ff4843258dd8a3eed2775a0e7368c2e7936c2f" -dependencies = [ - "log 0.3.9", - "regex 0.1.80", -] - [[package]] name = "env_logger" version = "0.7.1" @@ -1043,8 +1024,8 @@ checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" dependencies = [ "atty", "humantime", - "log 0.4.8", - "regex 1.3.6", + "log", + "regex", "termcolor", ] @@ -1261,7 +1242,7 @@ dependencies = [ "futures-macro", "futures-sink", "futures-task", - "memchr 2.3.3", + "memchr", "pin-utils", "proc-macro-hack", "proc-macro-nested", @@ -1304,6 +1285,7 @@ dependencies = [ "cfg-if", "libc", "wasi", + "wasm-bindgen", ] [[package]] @@ -1312,12 +1294,37 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" +[[package]] +name = "gloo-events" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "088514ec8ef284891c762c88a66b639b3a730134714692ee31829765c5bc814f" +dependencies = [ + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-file" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f9fecfe46b5dc3cc46f58e98ba580cc714f2c93860796d002eb3527a465ef49" +dependencies = [ + "futures-channel", + "gloo-events", + "js-sys", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "gloo-timers" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47204a46aaff920a1ea58b11d03dec6f704287d27561724a4631e450654a891f" dependencies = [ + "futures-channel", + "futures-core", "js-sys", "wasm-bindgen", "web-sys", @@ -1336,7 +1343,7 @@ dependencies = [ "futures-util", "http", "indexmap", - "log 0.4.8", + "log", "slab", "tokio", "tokio-util", @@ -1448,7 +1455,7 @@ dependencies = [ "http-body", "httparse", "itoa", - "log 0.4.8", + "log", "net2", "pin-project", "time 0.1.42", @@ -1470,17 +1477,6 @@ dependencies = [ "tokio-tls", ] -[[package]] -name = "idna" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e" -dependencies = [ - "matches", - "unicode-bidi", - "unicode-normalization", -] - [[package]] name = "idna" version = "0.2.0" @@ -1600,21 +1596,20 @@ dependencies = [ "chrono", "diesel", "dotenv", - "env_logger 0.7.1", + "env_logger", "futures 0.3.4", "ipnetwork", "jirs-data", "lettre", "lettre_email", "libc", - "log 0.4.8", + "log", "num-bigint", "num-integer", "num-traits", - "percent-encoding 2.1.0", + "percent-encoding", "pq-sys", "pretty_env_logger", - "quickcheck", "r2d2", "rusoto_core", "rusoto_s3", @@ -1622,7 +1617,7 @@ dependencies = [ "serde_json", "time 0.1.42", "toml", - "url 2.1.1", + "url", "uuid 0.8.1", ] @@ -1647,9 +1642,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.37" +version = "0.3.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a27d435371a2fa5b6d2b028a74bbdb1234f308da363226a2854ca3ff8ba7055" +checksum = "fa5a448de267e7358beaf4a5d849518fe9a0c13fce7afd44b06e68550e5562a7" dependencies = [ "wasm-bindgen", ] @@ -1692,7 +1687,7 @@ dependencies = [ "bufstream", "fast_chemail", "hostname 0.1.5", - "log 0.4.8", + "log", "native-tls", "nom", "serde", @@ -1735,15 +1730,6 @@ dependencies = [ "scopeguard", ] -[[package]] -name = "log" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" -dependencies = [ - "log 0.4.8", -] - [[package]] name = "log" version = "0.4.8" @@ -1792,15 +1778,6 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" -[[package]] -name = "memchr" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b629fb514376c675b98c1421e80b151d3817ac42d7c667717d282761418d20" -dependencies = [ - "libc", -] - [[package]] name = "memchr" version = "2.3.3" @@ -1844,7 +1821,7 @@ dependencies = [ "iovec", "kernel32-sys", "libc", - "log 0.4.8", + "log", "miow 0.2.1", "net2", "slab", @@ -1858,7 +1835,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52403fe290012ce777c4626790c8951324a2b9e3316b3143779c72b029742f19" dependencies = [ "lazycell", - "log 0.4.8", + "log", "mio", "slab", ] @@ -1869,7 +1846,7 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f5e374eff525ce1c5b7687c4cef63943e7686524a387933ad27ca7ec43779cb3" dependencies = [ - "log 0.4.8", + "log", "mio", "miow 0.3.3", "winapi 0.3.8", @@ -1916,7 +1893,7 @@ checksum = "2b0d88c06fe90d5ee94048ba40409ef1d9315d86f6f38c2efdaad4fb50c58b2d" dependencies = [ "lazy_static", "libc", - "log 0.4.8", + "log", "openssl", "openssl-probe", "openssl-sys", @@ -1943,7 +1920,7 @@ version = "4.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6" dependencies = [ - "memchr 2.3.3", + "memchr", "version_check 0.1.5", ] @@ -2068,12 +2045,6 @@ dependencies = [ "winapi 0.3.8", ] -[[package]] -name = "percent-encoding" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" - [[package]] name = "percent-encoding" version = "2.1.0" @@ -2182,8 +2153,8 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "926d36b9553851b8b0005f1275891b392ee4d2d833852c417ed025477350fb9d" dependencies = [ - "env_logger 0.7.1", - "log 0.4.8", + "env_logger", + "log", ] [[package]] @@ -2209,13 +2180,13 @@ dependencies = [ [[package]] name = "pulldown-cmark" -version = "0.6.1" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c205cc82214f3594e2d50686730314f817c67ffa80fe800cf0db78c3c2b9d9e" +checksum = "3e142c3b8f49d2200605ee6ba0b1d757310e9e7a72afe78c36ee2ef67300ee00" dependencies = [ "bitflags", "getopts", - "memchr 2.3.3", + "memchr", "unicase", ] @@ -2225,17 +2196,6 @@ version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" -[[package]] -name = "quickcheck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02c2411d418cea2364325b18a205664f9ef8252e06b2e911db97c0b0d98b1406" -dependencies = [ - "env_logger 0.3.5", - "log 0.3.9", - "rand 0.3.23", -] - [[package]] name = "quote" version = "1.0.3" @@ -2251,21 +2211,11 @@ version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1497e40855348e4a8a40767d8e55174bce1e445a3ac9254ad44ad468ee0485af" dependencies = [ - "log 0.4.8", + "log", "parking_lot", "scheduled-thread-pool", ] -[[package]] -name = "rand" -version = "0.3.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64ac302d8f83c0c1974bf758f6b041c6c8ada916fbb44a609158ca8b064cc76c" -dependencies = [ - "libc", - "rand 0.4.6", -] - [[package]] name = "rand" version = "0.4.6" @@ -2293,7 +2243,7 @@ dependencies = [ "rand_isaac", "rand_jitter", "rand_os", - "rand_pcg", + "rand_pcg 0.1.2", "rand_xorshift", "winapi 0.3.8", ] @@ -2309,6 +2259,7 @@ dependencies = [ "rand_chacha 0.2.2", "rand_core 0.5.1", "rand_hc 0.2.0", + "rand_pcg 0.2.1", ] [[package]] @@ -2417,6 +2368,15 @@ dependencies = [ "rand_core 0.4.2", ] +[[package]] +name = "rand_pcg" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" +dependencies = [ + "rand_core 0.5.1", +] + [[package]] name = "rand_xorshift" version = "0.1.1" @@ -2452,37 +2412,18 @@ dependencies = [ "rust-argon2", ] -[[package]] -name = "regex" -version = "0.1.80" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fd4ace6a8cf7860714a2c2280d6c1f7e6a413486c13298bbc86fd3da019402f" -dependencies = [ - "aho-corasick 0.5.3", - "memchr 0.1.11", - "regex-syntax 0.3.9", - "thread_local 0.2.7", - "utf8-ranges", -] - [[package]] name = "regex" version = "1.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f6946991529684867e47d86474e3a6d0c0ab9b82d5821e314b1ede31fa3a4b3" dependencies = [ - "aho-corasick 0.7.10", - "memchr 2.3.3", - "regex-syntax 0.6.17", - "thread_local 1.0.1", + "aho-corasick", + "memchr", + "regex-syntax", + "thread_local", ] -[[package]] -name = "regex-syntax" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9ec002c35e86791825ed294b50008eea9ddfc8def4420124fbc6b08db834957" - [[package]] name = "regex-syntax" version = "0.6.17" @@ -2523,9 +2464,9 @@ dependencies = [ "hyper", "hyper-tls", "lazy_static", - "log 0.4.8", + "log", "md5", - "percent-encoding 2.1.0", + "percent-encoding", "pin-project", "rusoto_credential", "rusoto_signature", @@ -2549,7 +2490,7 @@ dependencies = [ "futures 0.3.4", "hyper", "pin-project", - "regex 1.3.6", + "regex", "serde", "serde_json", "shlex", @@ -2583,9 +2524,9 @@ dependencies = [ "hmac", "http", "hyper", - "log 0.4.8", + "log", "md5", - "percent-encoding 2.1.0", + "percent-encoding", "pin-project", "rusoto_credential", "rustc_version", @@ -2699,21 +2640,24 @@ dependencies = [ [[package]] name = "seed" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd5d8fd7f12f565f639caf4b6d74a3853d5d7234d0543ec3beae81311492623e" +checksum = "882f4569a394bbb2f15f2fc410e0fbcef178fe24fc2d91599607a598443c6df8" dependencies = [ "console_error_panic_hook", "cookie", "dbg", "enclose", "futures 0.3.4", + "gloo-file", "gloo-timers", "indexmap", "js-sys", "pulldown-cmark", + "rand 0.7.3", "serde", "serde_json", + "uuid 0.8.1", "version_check 0.9.1", "wasm-bindgen", "wasm-bindgen-futures", @@ -2737,18 +2681,18 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.105" +version = "1.0.110" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e707fbbf255b8fc8c3b99abb91e7257a622caeb20a9818cbadbeeede4e0932ff" +checksum = "99e7b308464d16b56eba9964e4972a3eee817760ab60d88c3f86e1fecb08204c" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.105" +version = "1.0.110" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac5d00fc561ba2724df6758a17de23df5914f20e41cb00f94d5b7ae42fffaff8" +checksum = "818fbf6bfa9a42d3bfcaca148547aa00c7b915bec71d1757aa2d44ca68771984" dependencies = [ "proc-macro2", "quote", @@ -2757,9 +2701,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.48" +version = "1.0.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9371ade75d4c2d6cb154141b9752cf3781ec9c05e0e5cf35060e1e70ee7b9c25" +checksum = "993948e75b189211a9b31a7528f950c6adc21f9720b6438ff80a7fa2f864cea2" dependencies = [ "itoa", "ryu", @@ -2775,7 +2719,7 @@ dependencies = [ "dtoa", "itoa", "serde", - "url 2.1.1", + "url", ] [[package]] @@ -2970,25 +2914,6 @@ dependencies = [ "unicode-width", ] -[[package]] -name = "thread-id" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9539db560102d1cef46b8b78ce737ff0bb64e7e18d35b2a5688f7d097d0ff03" -dependencies = [ - "kernel32-sys", - "libc", -] - -[[package]] -name = "thread_local" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8576dbbfcaef9641452d5cf0df9b0e7eeab7694956dd33bb61515fb8f18cfdd5" -dependencies = [ - "thread-id", -] - [[package]] name = "thread_local" version = "1.0.1" @@ -3068,7 +2993,7 @@ dependencies = [ "iovec", "lazy_static", "libc", - "memchr 2.3.3", + "memchr", "mio", "mio-named-pipes", "mio-uds", @@ -3109,7 +3034,7 @@ dependencies = [ "bytes", "futures-core", "futures-sink", - "log 0.4.8", + "log", "pin-project-lite", "tokio", ] @@ -3139,14 +3064,14 @@ dependencies = [ "enum-as-inner", "failure", "futures 0.3.4", - "idna 0.2.0", + "idna", "lazy_static", - "log 0.4.8", + "log", "rand 0.7.3", "smallvec", "socket2", "tokio", - "url 2.1.1", + "url", ] [[package]] @@ -3160,7 +3085,7 @@ dependencies = [ "futures 0.3.4", "ipconfig", "lazy_static", - "log 0.4.8", + "log", "lru-cache", "resolv-conf", "smallvec", @@ -3180,7 +3105,7 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b40075910de3a912adbd80b5d8bad6ad10a23eeb1f5bf9d4006839e899ba5bc" dependencies = [ - "memchr 2.3.3", + "memchr", "unchecked-index", ] @@ -3259,34 +3184,17 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" -[[package]] -name = "url" -version = "1.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd4e7c0d531266369519a4aa4f399d748bd37043b00bde1e4ff1f60a120b355a" -dependencies = [ - "idna 0.1.5", - "matches", - "percent-encoding 1.0.1", -] - [[package]] name = "url" version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "829d4a8476c35c9bf0bbce5a3b23f4106f79728039b726d292bb93bc106787cb" dependencies = [ - "idna 0.2.0", + "idna", "matches", - "percent-encoding 2.1.0", + "percent-encoding", ] -[[package]] -name = "utf8-ranges" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1ca13c08c41c9c3e04224ed9ff80461d97e121589ff27c753a16cb10830ae0f" - [[package]] name = "uuid" version = "0.6.5" @@ -3388,7 +3296,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" dependencies = [ - "log 0.4.8", + "log", "try-lock", ] @@ -3400,9 +3308,9 @@ checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" [[package]] name = "wasm-bindgen" -version = "0.2.60" +version = "0.2.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cc57ce05287f8376e998cbddfb4c8cb43b84a7ec55cf4551d7c00eef317a47f" +checksum = "e3c7d40d09cdbf0f4895ae58cf57d92e1e57a9dd8ed2e8390514b54a47cc5551" dependencies = [ "cfg-if", "serde", @@ -3412,13 +3320,13 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.60" +version = "0.2.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d967d37bf6c16cca2973ca3af071d0a2523392e4a594548155d89a678f4237cd" +checksum = "c3972e137ebf830900db522d6c8fd74d1900dcfc733462e9a12e942b00b4ac94" dependencies = [ "bumpalo", "lazy_static", - "log 0.4.8", + "log", "proc-macro2", "quote", "syn", @@ -3427,9 +3335,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.10" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7add542ea1ac7fdaa9dc25e031a6af33b7d63376292bd24140c637d00d1c312a" +checksum = "8a369c5e1dfb7569e14d62af4da642a3cbc2f9a3652fe586e26ac22222aa4b04" dependencies = [ "cfg-if", "js-sys", @@ -3439,9 +3347,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.60" +version = "0.2.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bd151b63e1ea881bb742cd20e1d6127cef28399558f3b5d415289bc41eee3a4" +checksum = "2cd85aa2c579e8892442954685f0d801f9129de24fa2136b2c6a539c76b65776" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3449,9 +3357,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.60" +version = "0.2.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d68a5b36eef1be7868f668632863292e37739656a80fc4b9acec7b0bd35a4931" +checksum = "8eb197bd3a47553334907ffd2f16507b4f4f01bbec3ac921a7719e0decdfe72a" dependencies = [ "proc-macro2", "quote", @@ -3462,9 +3370,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.60" +version = "0.2.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daf76fe7d25ac79748a37538b7daeed1c7a6867c92d3245c12c6222e4a20d639" +checksum = "a91c2916119c17a8e316507afaaa2dd94b47646048014bbdf6bef098c1bb58ad" [[package]] name = "wasm-bindgen-test" @@ -3492,9 +3400,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.37" +version = "0.3.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d6f51648d8c56c366144378a33290049eafdd784071077f6fe37dae64c1c4cb" +checksum = "8bc359e5dd3b46cb9687a051d50a2fdd228e4ba7cf6fcf861a5365c3d671a642" dependencies = [ "js-sys", "wasm-bindgen", diff --git a/jirs-client/Cargo.toml b/jirs-client/Cargo.toml index 6d191139..169ea567 100644 --- a/jirs-client/Cargo.toml +++ b/jirs-client/Cargo.toml @@ -19,7 +19,7 @@ opt-level = 's' [dependencies] jirs-data = { path = "../jirs-data" } -seed = { version = "*" } +seed = { version = "0.7.0" } serde = "*" serde_json = "*" bincode = "1.2.1" diff --git a/jirs-client/jirs.nginx b/jirs-client/jirs.nginx new file mode 100644 index 00000000..397dca60 --- /dev/null +++ b/jirs-client/jirs.nginx @@ -0,0 +1,21 @@ +server { + listen 80; + server_name jirs.lvh.me; + + charset utf-8; + root /home/eraden/code/eraden/jirs/jirs-client/tmp; + + location ~ .wasm { + default_type application/wasm; + } + + location *.js { + default_type application/javascript; + } + + location / { + index index.html index.htm; + } + + error_page 404 =200 /index.html; +} diff --git a/jirs-client/js/css/styledInput.css b/jirs-client/js/css/styledInput.css index 675554cd..a0ddb2b2 100644 --- a/jirs-client/js/css/styledInput.css +++ b/jirs-client/js/css/styledInput.css @@ -1,11 +1,12 @@ .styledInput { position: relative; display: inline-block; - height: 32px; + min-height: 32px; width: 100%; } .styledInput > .inputElement { + min-height: 32px; height: 100%; width: 100%; padding: 0 7px; diff --git a/jirs-client/js/index.js b/jirs-client/js/index.js index 9056c8eb..43d80393 100644 --- a/jirs-client/js/index.js +++ b/jirs-client/js/index.js @@ -2,85 +2,10 @@ const getWsHostName = () => process.env.JIRS_SERVER_BIND === "0.0.0.0" ? 'localh const getProtocol = () => window.location.protocol.replace(/^http/, 'ws'); const wsUrl = () => `${ getProtocol() }//${ getWsHostName() }:${ process.env.JIRS_SERVER_PORT }/ws/`; -import("../pkg/index.js").then(module => { - let queue = []; - let ws; - - const buildWebSocket = () => { - ws = new WebSocket(wsUrl()); - ws.binaryType = 'blob'; - ws.onopen = event => { - console.log('open', event); - module.reconnected(); - }; - ws.onerror = event => { - console.error(event); - }; - ws.onmessage = async event => { - const arrayBuffer = await event.data.arrayBuffer(); - const array = new Uint8Array(arrayBuffer); - module.handle_ws_message(array); - }; - ws.onclose = () => { - setTimeout(() => buildWebSocket(), 600); - }; - }; - buildWebSocket(); - - window.send_bin_code = code => queue.push(code); - window.inspectQueue = () => queue; - - let wsCheckDelay = 100; - const flush = () => { - if (queue.length >= 1000) { - ws.close(); - throw new Error("Message queue overflow"); - } - // if (queue.length && wsCheckDelay <= 0) console.log(ws.readyState, queue); - switch (ws.readyState) { - case 1: { - const [ code, ...rest ] = queue; - queue = rest; - if (code) { - // console.log('open', code); - ws.send(Uint8Array.from(code).buffer); - } - break; - } - default: - break; - } - window.requestAnimationFrame(flush); - }; - window.flush = flush; - - const keepWsOpen = () => { - if (wsCheckDelay > 0) { - wsCheckDelay -= 1; - } else { - wsCheckDelay = 100; - switch (ws.readyState) { - case 1: { - // console.log('sending ping'); - // ws.send(Uint8Array.from([ 0, 0, 0, 0 ]).buffer); - break; - } - case 0: - case 2: - break; - case 3: - throw new Error('web socket has been closed'); - buildWebSocket(); - break; - } - } - window.requestAnimationFrame(keepWsOpen); - }; - - keepWsOpen(); - flush(); - +import("/jirs.js").then(async module => { + window.module = module; + console.log(module) + await module.default(); const host_url = `${ location.protocol }//${ process.env.JIRS_SERVER_BIND }:${ process.env.JIRS_SERVER_PORT }`; - module.set_host_url(host_url); - module.render(); + module.render(host_url, wsUrl()); }); diff --git a/jirs-client/js/template.html b/jirs-client/js/template.html new file mode 100644 index 00000000..3c594c1f --- /dev/null +++ b/jirs-client/js/template.html @@ -0,0 +1,15 @@ + + + + + + + + JIRS + + + +
+ + + diff --git a/jirs-client/scripts/dev.sh b/jirs-client/scripts/dev.sh new file mode 100755 index 00000000..5558ec46 --- /dev/null +++ b/jirs-client/scripts/dev.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +. .env + +rm -Rf tmp +mkdir tmp + +wasm-pack build --mode normal --dev --out-name jirs --out-dir ./tmp --target web +../target/debug/jirs-css -i ./js/styles.css -O ./tmp/styles.css + +cat ./js/index.js \ +| sed -e "s/process.env.JIRS_SERVER_BIND/'$JIRS_SERVER_BIND'/g" \ +| sed -e "s/process.env.JIRS_SERVER_PORT/'$JIRS_SERVER_PORT'/g" &> ./tmp/index.js +cp ./js/template.html ./tmp/index.html + +cp -r ./dev/* ./tmp diff --git a/jirs-client/src/api.rs b/jirs-client/src/api.rs deleted file mode 100644 index e53e6edf..00000000 --- a/jirs-client/src/api.rs +++ /dev/null @@ -1,10 +0,0 @@ -use jirs_data::*; - -pub fn send_ws_msg(msg: WsMsg) { - use crate::send_bin_code; - use wasm_bindgen::JsValue; - - let binary = bincode::serialize(&msg).unwrap(); - let data = JsValue::from_serde(&binary).unwrap(); - send_bin_code(data); -} diff --git a/jirs-client/src/changes.rs b/jirs-client/src/changes.rs new file mode 100644 index 00000000..cce41dd8 --- /dev/null +++ b/jirs-client/src/changes.rs @@ -0,0 +1,67 @@ +use crate::FieldId; +use jirs_data::{IssueId, IssueStatusId}; + +use crate::shared::styled_editor::Mode as TabMode; +use seed::prelude::WebSocketMessage; + +#[derive(Clone, Debug, PartialEq)] +pub enum FieldChange { + LinkCopied(FieldId, bool), + TabChanged(FieldId, TabMode), + ToggleCommentForm(FieldId, bool), + EditComment(FieldId, i32), +} + +#[derive(Clone, Debug, PartialEq)] +pub enum BoardPageChange { + // dragging + IssueDragStarted(IssueId), + IssueDragStopped(IssueId), + DragLeave(IssueId), + ExchangePosition(IssueId), + IssueDragOverStatus(IssueStatusId), + IssueDropZone(IssueStatusId), +} + +#[derive(Clone, Debug, PartialEq)] +pub enum UsersPageChange { + ResetForm, +} + +#[derive(Clone, Debug, PartialEq)] +pub enum ProjectPageChange { + ResetForm, + SubmitForm, + // dragging + ColumnDragStarted(IssueStatusId), + ColumnDragStopped(IssueStatusId), + ColumnDragLeave(IssueStatusId), + ColumnExchangePosition(IssueStatusId), + ColumnDragOverStatus(IssueStatusId), + ColumnDropZone(IssueStatusId), + // edit issue status name + EditIssueStatusName(Option), +} + +#[derive(Clone, Debug, PartialEq)] +pub enum ProfilePageChange { + SubmitForm, +} + +#[derive(Clone, Debug, PartialEq)] +pub enum PageChanged { + Users(UsersPageChange), + ProjectSettings(ProjectPageChange), + Profile(ProfilePageChange), + Board(BoardPageChange), +} + +#[derive(Debug)] +pub enum WebSocketChanged { + WsMsg(jirs_data::WsMsg), + WebSocketMessage(WebSocketMessage), + WebSocketMessageLoaded(Vec), + WebSocketOpened, + WebSocketClosed, + SendPing, +} diff --git a/jirs-client/src/fields.rs b/jirs-client/src/fields.rs new file mode 100644 index 00000000..77ad7b1b --- /dev/null +++ b/jirs-client/src/fields.rs @@ -0,0 +1,121 @@ +use jirs_data::{ + CommentFieldId, InviteFieldId, IssueFieldId, ProjectFieldId, SignInFieldId, SignUpFieldId, + UsersFieldId, +}; + +pub type AvatarFilterActive = bool; + +#[derive(Clone, Debug, PartialOrd, PartialEq, Hash)] +pub enum EditIssueModalSection { + Issue(IssueFieldId), + Comment(CommentFieldId), +} + +#[derive(Clone, Debug, PartialOrd, PartialEq, Hash)] +pub enum FieldId { + SignIn(SignInFieldId), + SignUp(SignUpFieldId), + Invite(InviteFieldId), + Users(UsersFieldId), + Profile(UsersFieldId), + // issue + AddIssueModal(IssueFieldId), + EditIssueModal(EditIssueModalSection), + // project boards + TextFilterBoard, + CopyButtonLabel, + + ProjectSettings(ProjectFieldId), +} + +impl std::fmt::Display for FieldId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + FieldId::EditIssueModal(sub) => match sub { + EditIssueModalSection::Issue(IssueFieldId::Type) => { + f.write_str("issueTypeEditModalTop") + } + EditIssueModalSection::Issue(IssueFieldId::Title) => { + f.write_str("titleIssueEditModal") + } + EditIssueModalSection::Issue(IssueFieldId::Description) => { + f.write_str("descriptionIssueEditModal") + } + EditIssueModalSection::Issue(IssueFieldId::IssueStatusId) => { + f.write_str("statusIssueEditModal") + } + EditIssueModalSection::Issue(IssueFieldId::Assignees) => { + f.write_str("assigneesIssueEditModal") + } + EditIssueModalSection::Issue(IssueFieldId::Reporter) => { + f.write_str("reporterIssueEditModal") + } + EditIssueModalSection::Issue(IssueFieldId::Priority) => { + f.write_str("priorityIssueEditModal") + } + EditIssueModalSection::Issue(IssueFieldId::Estimate) => { + f.write_str("estimateIssueEditModal") + } + EditIssueModalSection::Issue(IssueFieldId::TimeSpent) => { + f.write_str("timeSpendIssueEditModal") + } + EditIssueModalSection::Issue(IssueFieldId::TimeRemaining) => { + f.write_str("timeRemainingIssueEditModal") + } + EditIssueModalSection::Comment(CommentFieldId::Body) => { + f.write_str("editIssue-commentBody") + } + EditIssueModalSection::Issue(IssueFieldId::ListPosition) => { + f.write_str("editIssue-listPosition") + } + }, + FieldId::AddIssueModal(sub) => match sub { + IssueFieldId::Type => f.write_str("issueTypeAddIssueModal"), + IssueFieldId::Title => f.write_str("summaryAddIssueModal"), + IssueFieldId::Description => f.write_str("descriptionAddIssueModal"), + IssueFieldId::Reporter => f.write_str("reporterAddIssueModal"), + IssueFieldId::Assignees => f.write_str("assigneesAddIssueModal"), + IssueFieldId::Priority => f.write_str("issuePriorityAddIssueModal"), + IssueFieldId::IssueStatusId => f.write_str("addIssueModal-status"), + IssueFieldId::Estimate => f.write_str("addIssueModal-estimate"), + IssueFieldId::TimeSpent => f.write_str("addIssueModal-timeSpend"), + IssueFieldId::TimeRemaining => f.write_str("addIssueModal-timeRemaining"), + IssueFieldId::ListPosition => f.write_str("addIssueModal-listPosition"), + }, + FieldId::TextFilterBoard => f.write_str("textFilterBoard"), + FieldId::CopyButtonLabel => f.write_str("copyButtonLabel"), + FieldId::ProjectSettings(sub) => match sub { + ProjectFieldId::Name => f.write_str("projectSettings-name"), + ProjectFieldId::Url => f.write_str("projectSettings-url"), + ProjectFieldId::Description => f.write_str("projectSettings-description"), + ProjectFieldId::Category => f.write_str("projectSettings-category"), + ProjectFieldId::TimeTracking => f.write_str("projectSettings-timeTracking"), + ProjectFieldId::IssueStatusName => f.write_str("projectSettings-issueStatusName"), + }, + FieldId::SignIn(sub) => match sub { + SignInFieldId::Email => f.write_str("login-email"), + SignInFieldId::Username => f.write_str("login-username"), + SignInFieldId::Token => f.write_str("login-token"), + }, + FieldId::SignUp(sub) => match sub { + SignUpFieldId::Username => f.write_str("signUp-email"), + SignUpFieldId::Email => f.write_str("signUp-username"), + }, + FieldId::Invite(sub) => match sub { + InviteFieldId::Token => f.write_str("invite-token"), + }, + FieldId::Users(sub) => match sub { + UsersFieldId::Username => f.write_str("users-username"), + UsersFieldId::Email => f.write_str("users-email"), + UsersFieldId::UserRole => f.write_str("users-userRole"), + UsersFieldId::Avatar => f.write_str("users-avatar"), + }, + FieldId::Profile(sub) => match sub { + UsersFieldId::Username => f.write_str("profile-username"), + UsersFieldId::Email => f.write_str("profile-email"), + UsersFieldId::UserRole => f.write_str("profile-userRole"), + UsersFieldId::Avatar => f.write_str("profile-avatar"), + }, + } + } +} diff --git a/jirs-client/src/lib.rs b/jirs-client/src/lib.rs index 261e3eb2..63f75c26 100644 --- a/jirs-client/src/lib.rs +++ b/jirs-client/src/lib.rs @@ -1,16 +1,15 @@ -use std::sync::RwLock; - use seed::{prelude::*, *}; use web_sys::File; use jirs_data::*; -use crate::api::send_ws_msg; use crate::model::{ModalType, Model, Page}; -use crate::shared::styled_editor::Mode as TabMode; use crate::shared::styled_select::StyledSelectChange; +use crate::shared::{go_to_board, go_to_login}; +use crate::ws::{open_socket, read_incoming, send_ws_msg}; -mod api; +mod changes; +mod fields; mod invite; mod modal; mod model; @@ -24,179 +23,13 @@ mod users; pub mod validations; mod ws; -pub type AvatarFilterActive = bool; +pub use changes::*; +pub use fields::*; + pub type AppType = App>; -#[derive(Clone, Debug, PartialOrd, PartialEq, Hash)] -pub enum EditIssueModalSection { - Issue(IssueFieldId), - Comment(CommentFieldId), -} - -#[derive(Clone, Debug, PartialOrd, PartialEq, Hash)] -pub enum FieldId { - SignIn(SignInFieldId), - SignUp(SignUpFieldId), - Invite(InviteFieldId), - Users(UsersFieldId), - Profile(UsersFieldId), - // issue - AddIssueModal(IssueFieldId), - EditIssueModal(EditIssueModalSection), - // project boards - TextFilterBoard, - CopyButtonLabel, - - ProjectSettings(ProjectFieldId), -} - -impl std::fmt::Display for FieldId { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - FieldId::EditIssueModal(sub) => match sub { - EditIssueModalSection::Issue(IssueFieldId::Type) => { - f.write_str("issueTypeEditModalTop") - } - EditIssueModalSection::Issue(IssueFieldId::Title) => { - f.write_str("titleIssueEditModal") - } - EditIssueModalSection::Issue(IssueFieldId::Description) => { - f.write_str("descriptionIssueEditModal") - } - EditIssueModalSection::Issue(IssueFieldId::IssueStatusId) => { - f.write_str("statusIssueEditModal") - } - EditIssueModalSection::Issue(IssueFieldId::Assignees) => { - f.write_str("assigneesIssueEditModal") - } - EditIssueModalSection::Issue(IssueFieldId::Reporter) => { - f.write_str("reporterIssueEditModal") - } - EditIssueModalSection::Issue(IssueFieldId::Priority) => { - f.write_str("priorityIssueEditModal") - } - EditIssueModalSection::Issue(IssueFieldId::Estimate) => { - f.write_str("estimateIssueEditModal") - } - EditIssueModalSection::Issue(IssueFieldId::TimeSpent) => { - f.write_str("timeSpendIssueEditModal") - } - EditIssueModalSection::Issue(IssueFieldId::TimeRemaining) => { - f.write_str("timeRemainingIssueEditModal") - } - EditIssueModalSection::Comment(CommentFieldId::Body) => { - f.write_str("editIssue-commentBody") - } - EditIssueModalSection::Issue(IssueFieldId::ListPosition) => { - f.write_str("editIssue-listPosition") - } - }, - FieldId::AddIssueModal(sub) => match sub { - IssueFieldId::Type => f.write_str("issueTypeAddIssueModal"), - IssueFieldId::Title => f.write_str("summaryAddIssueModal"), - IssueFieldId::Description => f.write_str("descriptionAddIssueModal"), - IssueFieldId::Reporter => f.write_str("reporterAddIssueModal"), - IssueFieldId::Assignees => f.write_str("assigneesAddIssueModal"), - IssueFieldId::Priority => f.write_str("issuePriorityAddIssueModal"), - IssueFieldId::IssueStatusId => f.write_str("addIssueModal-status"), - IssueFieldId::Estimate => f.write_str("addIssueModal-estimate"), - IssueFieldId::TimeSpent => f.write_str("addIssueModal-timeSpend"), - IssueFieldId::TimeRemaining => f.write_str("addIssueModal-timeRemaining"), - IssueFieldId::ListPosition => f.write_str("addIssueModal-listPosition"), - }, - FieldId::TextFilterBoard => f.write_str("textFilterBoard"), - FieldId::CopyButtonLabel => f.write_str("copyButtonLabel"), - FieldId::ProjectSettings(sub) => match sub { - ProjectFieldId::Name => f.write_str("projectSettings-name"), - ProjectFieldId::Url => f.write_str("projectSettings-url"), - ProjectFieldId::Description => f.write_str("projectSettings-description"), - ProjectFieldId::Category => f.write_str("projectSettings-category"), - ProjectFieldId::TimeTracking => f.write_str("projectSettings-timeTracking"), - ProjectFieldId::IssueStatusName => f.write_str("projectSettings-issueStatusName"), - }, - FieldId::SignIn(sub) => match sub { - SignInFieldId::Email => f.write_str("login-email"), - SignInFieldId::Username => f.write_str("login-username"), - SignInFieldId::Token => f.write_str("login-token"), - }, - FieldId::SignUp(sub) => match sub { - SignUpFieldId::Username => f.write_str("signUp-email"), - SignUpFieldId::Email => f.write_str("signUp-username"), - }, - FieldId::Invite(sub) => match sub { - InviteFieldId::Token => f.write_str("invite-token"), - }, - FieldId::Users(sub) => match sub { - UsersFieldId::Username => f.write_str("users-username"), - UsersFieldId::Email => f.write_str("users-email"), - UsersFieldId::UserRole => f.write_str("users-userRole"), - UsersFieldId::Avatar => f.write_str("users-avatar"), - }, - FieldId::Profile(sub) => match sub { - UsersFieldId::Username => f.write_str("profile-username"), - UsersFieldId::Email => f.write_str("profile-email"), - UsersFieldId::UserRole => f.write_str("profile-userRole"), - UsersFieldId::Avatar => f.write_str("profile-avatar"), - }, - } - } -} - -#[derive(Clone, Debug, PartialEq)] -pub enum FieldChange { - LinkCopied(FieldId, bool), - TabChanged(FieldId, TabMode), - ToggleCommentForm(FieldId, bool), - EditComment(FieldId, i32), -} - -#[derive(Clone, Debug, PartialEq)] -pub enum BoardPageChange { - // dragging - IssueDragStarted(IssueId), - IssueDragStopped(IssueId), - DragLeave(IssueId), - ExchangePosition(IssueId), - IssueDragOverStatus(IssueStatusId), - IssueDropZone(IssueStatusId), -} - -#[derive(Clone, Debug, PartialEq)] -pub enum UsersPageChange { - ResetForm, -} - -#[derive(Clone, Debug, PartialEq)] -pub enum ProjectPageChange { - ResetForm, - SubmitForm, - // dragging - ColumnDragStarted(IssueStatusId), - ColumnDragStopped(IssueStatusId), - ColumnDragLeave(IssueStatusId), - ColumnExchangePosition(IssueStatusId), - ColumnDragOverStatus(IssueStatusId), - ColumnDropZone(IssueStatusId), - // edit issue status name - EditIssueStatusName(Option), -} - -#[derive(Clone, Debug, PartialEq)] -pub enum ProfilePageChange { - SubmitForm, -} - -#[derive(Clone, Debug, PartialEq)] -pub enum PageChanged { - Users(UsersPageChange), - ProjectSettings(ProjectPageChange), - Profile(ProfilePageChange), - Board(BoardPageChange), -} - -#[derive(Clone, Debug)] +#[derive(Debug)] pub enum Msg { - NoOp, GlobalKeyDown { key: String, shift: bool, @@ -248,35 +81,82 @@ pub enum Msg { DeleteComment(CommentId), // profile - AvatarUpdateFetched(seed::fetch::FetchObject), + AvatarUpdateFetched(String), // modals ModalOpened(Box), ModalDropped, ModalChanged(FieldChange), - WsMsg(jirs_data::WsMsg), + // WebSocket + WebSocketChange(WebSocketChanged), } fn update(msg: Msg, model: &mut model::Model, orders: &mut impl Orders) { - match msg { - Msg::NoOp => return, - _ => (), + if model.ws.is_none() { + open_socket(model, orders); + } + + let msg = match msg { + Msg::WebSocketChange(change) => { + match change { + WebSocketChanged::WebSocketOpened => { + authorize_or_redirect(model); + send_ws_msg(WsMsg::Ping, model.ws.as_ref()); + return; + } + WebSocketChanged::SendPing => { + send_ws_msg(WsMsg::Ping, model.ws.as_ref()); + return; + } + WebSocketChanged::WebSocketMessage(incoming) => { + orders.perform_cmd(read_incoming(incoming)); + return; + } + WebSocketChanged::WsMsg(ref ws_msg) => { + ws::update(ws_msg, model, orders); + } + WebSocketChanged::WebSocketMessageLoaded(v) => { + if let Ok(m) = bincode::deserialize(v.clone().as_slice()) { + match m { + WsMsg::Ping | WsMsg::Pong => { + orders.perform_cmd(cmds::timeout(1000, || { + Msg::WebSocketChange(WebSocketChanged::SendPing) + })); + } + _ => { + orders + .skip() + .send_msg(Msg::WebSocketChange(WebSocketChanged::WsMsg(m))); + } + } + }; + return; + } + WebSocketChanged::WebSocketClosed => { + open_socket(model, orders); + } + }; + Msg::WebSocketChange(change) + } + _ => msg, }; + if cfg!(debug_assertions) { log!(msg); } + match &msg { Msg::AuthTokenStored => { - seed::push_route(vec!["dashboard"]); + go_to_board(); orders.skip().send_msg(Msg::ChangePage(Page::Project)); - authorize_or_redirect(); + authorize_or_redirect(model); return; } Msg::AuthTokenErased => { - seed::push_route(vec!["login"]); + go_to_login(); orders.skip().send_msg(Msg::ChangePage(Page::SignIn)); - authorize_or_redirect(); + authorize_or_redirect(model); return; } Msg::ChangePage(page) => { @@ -287,7 +167,6 @@ fn update(msg: Msg, model: &mut model::Model, orders: &mut impl Orders) { } _ => (), } - crate::ws::update(&msg, model, orders); crate::modal::update(&msg, model, orders); match model.page { Page::Project | Page::AddIssue | Page::EditIssue(..) => project::update(msg, model, orders), @@ -317,116 +196,98 @@ fn view(model: &model::Model) -> Node { } fn routes(url: Url) -> Option { - if url.path.is_empty() { - return Some(Msg::ChangePage(model::Page::Project)); + match resolve_page(url) { + Some(page) => Some(Msg::ChangePage(page)), + _ => None, + } +} + +fn resolve_page(url: Url) -> Option { + if url.path().is_empty() { + return Some(Page::Project); } - match url.path[0].as_ref() { - "board" => Some(Msg::ChangePage(model::Page::Project)), - "issues" => match url.path.get(1).as_ref().map(|s| s.parse::()) { - Some(Ok(id)) => Some(Msg::ChangePage(model::Page::EditIssue(id))), - _ => None, + let page = match url.path()[0].as_ref() { + "board" => Page::Project, + "issues" => match url.path().get(1).as_ref().map(|s| s.parse::()) { + Some(Ok(id)) => Page::EditIssue(id), + _ => return None, }, - "profile" => Some(Msg::ChangePage(Page::Profile)), - "add-issue" => Some(Msg::ChangePage(Page::AddIssue)), - "project-settings" => Some(Msg::ChangePage(model::Page::ProjectSettings)), - "login" => Some(Msg::ChangePage(model::Page::SignIn)), - "register" => Some(Msg::ChangePage(model::Page::SignUp)), - "invite" => Some(Msg::ChangePage(model::Page::Invite)), - "users" => Some(Msg::ChangePage(model::Page::Users)), - _ => Some(Msg::ChangePage(model::Page::Project)), - } + "profile" => Page::Profile, + "add-issue" => Page::AddIssue, + "project-settings" => Page::ProjectSettings, + "login" => Page::SignIn, + "register" => Page::SignUp, + "invite" => Page::Invite, + "users" => Page::Users, + _ => Page::Project, + }; + Some(page) } pub static mut HOST_URL: String = String::new(); -pub static mut APP: Option>>> = None; +pub static mut WS_URL: String = String::new(); #[wasm_bindgen] -pub fn set_host_url(url: String) { +pub fn render(host_url: String, ws_url: String) { unsafe { - HOST_URL = url; - } -} - -#[wasm_bindgen] -pub fn handle_ws_message(value: &wasm_bindgen::JsValue) { - let a = js_sys::Uint8Array::new(value); - let mut v = Vec::new(); - for idx in 0..a.length() { - v.push(a.get_index(idx)); - } - if let Ok(msg) = bincode::deserialize(v.as_slice()) { - ws::handle(msg); - }; -} - -#[wasm_bindgen] -pub fn reconnected() { - authorize_or_redirect(); -} - -#[wasm_bindgen] -extern "C" { - pub fn send_bin_code(data: wasm_bindgen::JsValue); -} - -#[wasm_bindgen] -pub fn render() { - seed::set_interval( - Box::new(|| { - let binary = bincode::serialize(&jirs_data::WsMsg::Ping).unwrap(); - let data = JsValue::from_serde(&binary).unwrap(); - send_bin_code(data); - }) as Box, - 5000, - ); - - if let Some(body) = seed::html_document().body() { - use wasm_bindgen::JsCast; - - let body = body.dyn_ref::().unwrap().clone(); - let key_up_closure = - wasm_bindgen::closure::Closure::wrap(Box::new(|event: web_sys::KeyboardEvent| { - if let Some(Ok(app)) = unsafe { APP.as_mut().map(|app| app.write()) } { - let msg = Msg::GlobalKeyDown { - key: event.key(), - shift: event.shift_key(), - ctrl: event.ctrl_key(), - alt: event.alt_key(), - }; - app.update(msg); - } - }) - as Box); - body.add_event_listener_with_callback("keyup", key_up_closure.as_ref().unchecked_ref()) - .unwrap(); - key_up_closure.forget(); + HOST_URL = host_url; + WS_URL = ws_url; } - let app = seed::App::builder(update, view) + // if let Some(body) = seed::html_document().body() { + // use wasm_bindgen::JsCast; + // let body = body.dyn_ref::().unwrap().clone(); + // let key_up_closure = + // wasm_bindgen::closure::Closure::wrap(Box::new(|event: web_sys::KeyboardEvent| { + // if let Some(Ok(app)) = unsafe { APP.as_mut().map(|app| app.write()) } { + // let msg = Msg::GlobalKeyDown { + // key: event.key(), + // shift: event.shift_key(), + // ctrl: event.ctrl_key(), + // alt: event.alt_key(), + // }; + // app.update(msg); + // } + // }) + // as Box); + // body.add_event_listener_with_callback("keyup", key_up_closure.as_ref().unchecked_ref()) + // .unwrap(); + // key_up_closure.forget(); + // } + + let _app = seed::App::builder(update, view) .routes(routes) + .after_mount(after_mount) .build_and_start(); +} - authorize_or_redirect(); - - let cell_app = std::sync::RwLock::new(app); +fn after_mount(url: Url, orders: &mut impl Orders) -> AfterMount { + let host_url = unsafe { HOST_URL.clone() }; + let ws_url = unsafe { WS_URL.clone() }; + let mut model = Model::new(host_url, ws_url); unsafe { - APP = Some(cell_app); - }; + HOST_URL = "".to_string(); + WS_URL = "".to_string(); + } + model.page = resolve_page(url).unwrap_or_else(|| Page::Project); + log!(model); + open_socket(&mut model, orders); + AfterMount::new(model).url_handling(UrlHandling::PassToRoutes) } #[inline] -fn authorize_or_redirect() { +fn authorize_or_redirect(model: &Model) { match crate::shared::read_auth_token() { Ok(token) => { - send_ws_msg(WsMsg::AuthorizeRequest(token)); + send_ws_msg(WsMsg::AuthorizeRequest(token), model.ws.as_ref()); } Err(..) => { let pathname = seed::document().location().unwrap().pathname().unwrap(); match pathname.as_str() { "/login" | "/register" | "/invite" => {} _ => { - seed::push_route(vec!["login"]); + go_to_login(); } }; } diff --git a/jirs-client/src/modal/add_issue.rs b/jirs-client/src/modal/add_issue.rs index f65f19c2..66d11047 100644 --- a/jirs-client/src/modal/add_issue.rs +++ b/jirs-client/src/modal/add_issue.rs @@ -1,9 +1,8 @@ use seed::{prelude::*, *}; -use jirs_data::IssueFieldId; +use jirs_data::{IssueFieldId, WsMsg}; use jirs_data::{IssuePriority, IssueType, ToVec}; -use crate::api::send_ws_msg; use crate::model::{AddIssueModal, ModalType, Model}; use crate::shared::styled_button::StyledButton; use crate::shared::styled_field::StyledField; @@ -14,7 +13,8 @@ use crate::shared::styled_select::StyledSelect; use crate::shared::styled_select::StyledSelectChange; use crate::shared::styled_textarea::StyledTextarea; use crate::shared::{ToChild, ToNode}; -use crate::{FieldId, Msg}; +use crate::ws::send_ws_msg; +use crate::{FieldId, Msg, WebSocketChanged}; pub fn update(msg: &Msg, model: &mut crate::model::Model, orders: &mut impl Orders) { let modal = model.modals.iter_mut().find(|modal| match modal { @@ -50,9 +50,12 @@ pub fn update(msg: &Msg, model: &mut crate::model::Model, orders: &mut impl Orde user_ids: modal.user_ids.clone(), reporter_id: modal.reporter_id.unwrap_or_else(|| user_id), }; - send_ws_msg(jirs_data::WsMsg::IssueCreateRequest(payload)); + send_ws_msg( + jirs_data::WsMsg::IssueCreateRequest(payload), + model.ws.as_ref(), + ); } - Msg::WsMsg(jirs_data::WsMsg::IssueCreated(issue)) => { + Msg::WebSocketChange(WebSocketChanged::WsMsg(WsMsg::IssueCreated(issue))) => { model.issues.push(issue.clone()); orders.skip().send_msg(Msg::ModalDropped); } diff --git a/jirs-client/src/modal/delete_issue_status.rs b/jirs-client/src/modal/delete_issue_status.rs index 20665150..4bd5933f 100644 --- a/jirs-client/src/modal/delete_issue_status.rs +++ b/jirs-client/src/modal/delete_issue_status.rs @@ -2,11 +2,10 @@ use seed::prelude::*; use jirs_data::{IssueStatusId, WsMsg}; -use crate::api::send_ws_msg; use crate::model::{DeleteIssueStatusModal, ModalType, Model}; use crate::shared::styled_confirm_modal::StyledConfirmModal; use crate::shared::ToNode; -use crate::{model, Msg}; +use crate::{model, Msg, WebSocketChanged}; pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders) { let _modal: &mut Box = @@ -20,9 +19,12 @@ pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders) { match msg { Msg::DeleteIssueStatus(issue_status_id) => { - send_ws_msg(WsMsg::IssueStatusDelete(*issue_status_id)); + crate::ws::send_ws_msg( + WsMsg::IssueStatusDelete(*issue_status_id), + model.ws.as_ref(), + ); } - Msg::WsMsg(WsMsg::IssueStatusDelete(_)) => { + Msg::WebSocketChange(WebSocketChanged::WsMsg(WsMsg::IssueStatusDelete(_))) => { orders.skip().send_msg(Msg::ModalDropped); } _ => (), diff --git a/jirs-client/src/modal/issue_details.rs b/jirs-client/src/modal/issue_details.rs index 349d9625..70b979e1 100644 --- a/jirs-client/src/modal/issue_details.rs +++ b/jirs-client/src/modal/issue_details.rs @@ -2,7 +2,6 @@ use seed::{prelude::*, *}; use jirs_data::*; -use crate::api::send_ws_msg; use crate::modal::time_tracking::time_tracking_field; use crate::model::{CommentForm, EditIssueModal, ModalType, Model}; use crate::shared::styled_avatar::StyledAvatar; @@ -15,7 +14,8 @@ use crate::shared::styled_select::{StyledSelect, StyledSelectChange}; use crate::shared::styled_textarea::StyledTextarea; use crate::shared::tracking_widget::tracking_link; use crate::shared::{ToChild, ToNode}; -use crate::{EditIssueModalSection, FieldChange, FieldId, Msg}; +use crate::ws::send_ws_msg; +use crate::{EditIssueModalSection, FieldChange, FieldId, Msg, WebSocketChanged}; pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders) { let modal: &mut EditIssueModal = match model.modals.get_mut(0) { @@ -35,7 +35,7 @@ pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders) { modal.time_remaining_select.update(msg, orders); match msg { - Msg::WsMsg(WsMsg::IssueUpdated(issue)) => { + Msg::WebSocketChange(WebSocketChanged::WsMsg(WsMsg::IssueUpdated(issue))) => { modal.payload = issue.clone().into(); } Msg::StyledSelectChanged( @@ -43,44 +43,56 @@ pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders) { StyledSelectChange::Changed(value), ) => { modal.payload.issue_type = (*value).into(); - send_ws_msg(WsMsg::IssueUpdateRequest( - modal.id, - IssueFieldId::Type, - PayloadVariant::IssueType(modal.payload.issue_type), - )); + send_ws_msg( + WsMsg::IssueUpdateRequest( + modal.id, + IssueFieldId::Type, + PayloadVariant::IssueType(modal.payload.issue_type), + ), + model.ws.as_ref(), + ); } Msg::StyledSelectChanged( FieldId::EditIssueModal(EditIssueModalSection::Issue(IssueFieldId::IssueStatusId)), StyledSelectChange::Changed(value), ) => { modal.payload.issue_status_id = *value as IssueStatusId; - send_ws_msg(WsMsg::IssueUpdateRequest( - modal.id, - IssueFieldId::IssueStatusId, - PayloadVariant::I32(modal.payload.issue_status_id), - )); + send_ws_msg( + WsMsg::IssueUpdateRequest( + modal.id, + IssueFieldId::IssueStatusId, + PayloadVariant::I32(modal.payload.issue_status_id), + ), + model.ws.as_ref(), + ); } Msg::StyledSelectChanged( FieldId::EditIssueModal(EditIssueModalSection::Issue(IssueFieldId::Reporter)), StyledSelectChange::Changed(value), ) => { modal.payload.reporter_id = *value as i32; - send_ws_msg(WsMsg::IssueUpdateRequest( - modal.id, - IssueFieldId::Reporter, - PayloadVariant::I32(modal.payload.reporter_id), - )); + send_ws_msg( + WsMsg::IssueUpdateRequest( + modal.id, + IssueFieldId::Reporter, + PayloadVariant::I32(modal.payload.reporter_id), + ), + model.ws.as_ref(), + ); } Msg::StyledSelectChanged( FieldId::EditIssueModal(EditIssueModalSection::Issue(IssueFieldId::Assignees)), StyledSelectChange::Changed(value), ) => { modal.payload.user_ids.push(*value as i32); - send_ws_msg(WsMsg::IssueUpdateRequest( - modal.id, - IssueFieldId::Assignees, - PayloadVariant::VecI32(modal.payload.user_ids.clone()), - )); + send_ws_msg( + WsMsg::IssueUpdateRequest( + modal.id, + IssueFieldId::Assignees, + PayloadVariant::VecI32(modal.payload.user_ids.clone()), + ), + model.ws.as_ref(), + ); } Msg::StyledSelectChanged( FieldId::EditIssueModal(EditIssueModalSection::Issue(IssueFieldId::Assignees)), @@ -94,33 +106,42 @@ pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders) { modal.payload.user_ids.push(id); } } - send_ws_msg(WsMsg::IssueUpdateRequest( - modal.id, - IssueFieldId::Assignees, - PayloadVariant::VecI32(modal.payload.user_ids.clone()), - )); + send_ws_msg( + WsMsg::IssueUpdateRequest( + modal.id, + IssueFieldId::Assignees, + PayloadVariant::VecI32(modal.payload.user_ids.clone()), + ), + model.ws.as_ref(), + ); } Msg::StyledSelectChanged( FieldId::EditIssueModal(EditIssueModalSection::Issue(IssueFieldId::Priority)), StyledSelectChange::Changed(value), ) => { modal.payload.priority = (*value).into(); - send_ws_msg(WsMsg::IssueUpdateRequest( - modal.id, - IssueFieldId::Priority, - PayloadVariant::IssuePriority(modal.payload.priority), - )); + send_ws_msg( + WsMsg::IssueUpdateRequest( + modal.id, + IssueFieldId::Priority, + PayloadVariant::IssuePriority(modal.payload.priority), + ), + model.ws.as_ref(), + ); } Msg::StrInputChanged( FieldId::EditIssueModal(EditIssueModalSection::Issue(IssueFieldId::Title)), value, ) => { modal.payload.title = value.clone(); - send_ws_msg(WsMsg::IssueUpdateRequest( - modal.id, - IssueFieldId::Title, - PayloadVariant::String(modal.payload.title.clone()), - )); + send_ws_msg( + WsMsg::IssueUpdateRequest( + modal.id, + IssueFieldId::Title, + PayloadVariant::String(modal.payload.title.clone()), + ), + model.ws.as_ref(), + ); } Msg::StrInputChanged( FieldId::EditIssueModal(EditIssueModalSection::Issue(IssueFieldId::Description)), @@ -128,18 +149,21 @@ pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders) { ) => { modal.payload.description = Some(value.clone()); modal.payload.description_text = Some(value.clone()); - send_ws_msg(WsMsg::IssueUpdateRequest( - modal.id, - IssueFieldId::Description, - PayloadVariant::String( - modal - .payload - .description - .as_ref() - .cloned() - .unwrap_or_default(), + send_ws_msg( + WsMsg::IssueUpdateRequest( + modal.id, + IssueFieldId::Description, + PayloadVariant::String( + modal + .payload + .description + .as_ref() + .cloned() + .unwrap_or_default(), + ), ), - )); + model.ws.as_ref(), + ); } // TimeSpent Msg::StrInputChanged( @@ -147,22 +171,28 @@ pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders) { .., ) => { modal.payload.time_spent = modal.time_spent.represent_f64_as_i32(); - send_ws_msg(WsMsg::IssueUpdateRequest( - modal.id, - IssueFieldId::TimeSpent, - PayloadVariant::OptionI32(modal.payload.time_spent), - )); + send_ws_msg( + WsMsg::IssueUpdateRequest( + modal.id, + IssueFieldId::TimeSpent, + PayloadVariant::OptionI32(modal.payload.time_spent), + ), + model.ws.as_ref(), + ); } Msg::StyledSelectChanged( FieldId::EditIssueModal(EditIssueModalSection::Issue(IssueFieldId::TimeSpent)), StyledSelectChange::Changed(..), ) => { modal.payload.time_spent = modal.time_spent_select.values.get(0).map(|n| *n as i32); - send_ws_msg(WsMsg::IssueUpdateRequest( - modal.id, - IssueFieldId::TimeSpent, - PayloadVariant::OptionI32(modal.payload.time_spent), - )); + send_ws_msg( + WsMsg::IssueUpdateRequest( + modal.id, + IssueFieldId::TimeSpent, + PayloadVariant::OptionI32(modal.payload.time_spent), + ), + model.ws.as_ref(), + ); } // Time Remaining Msg::StrInputChanged( @@ -170,11 +200,14 @@ pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders) { .., ) => { modal.payload.time_remaining = modal.time_remaining.represent_f64_as_i32(); - send_ws_msg(WsMsg::IssueUpdateRequest( - modal.id, - IssueFieldId::TimeRemaining, - PayloadVariant::OptionI32(modal.payload.time_remaining), - )); + send_ws_msg( + WsMsg::IssueUpdateRequest( + modal.id, + IssueFieldId::TimeRemaining, + PayloadVariant::OptionI32(modal.payload.time_remaining), + ), + model.ws.as_ref(), + ); } Msg::StyledSelectChanged( FieldId::EditIssueModal(EditIssueModalSection::Issue(IssueFieldId::TimeRemaining)), @@ -182,11 +215,14 @@ pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders) { ) => { modal.payload.time_remaining = modal.time_remaining_select.values.get(0).map(|n| *n as i32); - send_ws_msg(WsMsg::IssueUpdateRequest( - modal.id, - IssueFieldId::TimeRemaining, - PayloadVariant::OptionI32(modal.payload.time_remaining), - )); + send_ws_msg( + WsMsg::IssueUpdateRequest( + modal.id, + IssueFieldId::TimeRemaining, + PayloadVariant::OptionI32(modal.payload.time_remaining), + ), + model.ws.as_ref(), + ); } // Estimate Msg::StrInputChanged( @@ -194,22 +230,28 @@ pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders) { .., ) => { modal.payload.estimate = modal.estimate.represent_f64_as_i32(); - send_ws_msg(WsMsg::IssueUpdateRequest( - modal.id, - IssueFieldId::Estimate, - PayloadVariant::OptionI32(modal.payload.estimate), - )); + send_ws_msg( + WsMsg::IssueUpdateRequest( + modal.id, + IssueFieldId::Estimate, + PayloadVariant::OptionI32(modal.payload.estimate), + ), + model.ws.as_ref(), + ); } Msg::StyledSelectChanged( FieldId::EditIssueModal(EditIssueModalSection::Issue(IssueFieldId::Estimate)), StyledSelectChange::Changed(..), ) => { modal.payload.estimate = modal.estimate_select.values.get(0).map(|n| *n as i32); - send_ws_msg(WsMsg::IssueUpdateRequest( - modal.id, - IssueFieldId::Estimate, - PayloadVariant::OptionI32(modal.payload.estimate), - )); + send_ws_msg( + WsMsg::IssueUpdateRequest( + modal.id, + IssueFieldId::Estimate, + PayloadVariant::OptionI32(modal.payload.estimate), + ), + model.ws.as_ref(), + ); } Msg::ModalChanged(FieldChange::TabChanged( FieldId::EditIssueModal(EditIssueModalSection::Issue(IssueFieldId::Description)), @@ -248,7 +290,7 @@ pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders) { issue_id: modal.id, }), }; - send_ws_msg(msg); + send_ws_msg(msg, model.ws.as_ref()); orders .skip() .send_msg(Msg::ModalChanged(FieldChange::ToggleCommentForm( @@ -272,7 +314,7 @@ pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders) { modal.comment_form.creating = true; } Msg::DeleteComment(comment_id) => { - send_ws_msg(WsMsg::CommentDeleteRequest(*comment_id)); + send_ws_msg(WsMsg::CommentDeleteRequest(*comment_id), model.ws.as_ref()); orders.skip().send_msg(Msg::ModalDropped); } @@ -314,15 +356,13 @@ fn top_modal_row(_model: &Model, modal: &EditIssueModal) -> Node { let issue_id = *id; let click_handler = mouse_ev(Ev::Click, move |_| { - use wasm_bindgen::JsCast; - let link = format!("http://localhost:7000/issues/{id}", id = issue_id); let el = match seed::html_document().create_element("textarea") { Ok(el) => el .dyn_ref::() .unwrap() .clone(), - _ => return Msg::NoOp, + _ => return None as Option, }; seed::body().append_child(&el).unwrap(); el.set_text_content(Some(link.as_str())); @@ -330,7 +370,10 @@ fn top_modal_row(_model: &Model, modal: &EditIssueModal) -> Node { el.set_selection_range(0, 9999).unwrap(); seed::html_document().exec_command("copy").unwrap(); seed::body().remove_child(&el).unwrap(); - Msg::ModalChanged(FieldChange::LinkCopied(FieldId::CopyButtonLabel, true)) + Some(Msg::ModalChanged(FieldChange::LinkCopied( + FieldId::CopyButtonLabel, + true, + ))) }); let close_handler = mouse_ev(Ev::Click, |_| Msg::ModalDropped); let delete_confirmation_handler = mouse_ev(Ev::Click, move |_| { @@ -579,7 +622,7 @@ fn comment(model: &Model, modal: &EditIssueModal, comment: &Comment) -> Option match msg { Msg::ModalDropped => match model.modals.pop() { Some(ModalType::EditIssue(..)) | Some(ModalType::AddIssue(..)) => { - seed::push_route(vec!["board"]); + go_to_board(); orders.send_msg(Msg::ChangePage(Page::Project)); } _ => (), @@ -37,12 +37,14 @@ pub fn update(msg: &Msg, model: &mut model::Model, orders: &mut impl Orders model.modals.push(modal_type.as_ref().clone()); } - Msg::WsMsg(jirs_data::WsMsg::ProjectIssuesLoaded(_issues)) => match model.page { - Page::EditIssue(issue_id) if model.modals.is_empty() => { - push_edit_modal(issue_id, model) + Msg::WebSocketChange(WebSocketChanged::WsMsg(WsMsg::ProjectIssuesLoaded(_issues))) => { + match model.page { + Page::EditIssue(issue_id) if model.modals.is_empty() => { + push_edit_modal(issue_id, model) + } + _ => (), } - _ => (), - }, + } Msg::ChangePage(Page::EditIssue(issue_id)) => { push_edit_modal(*issue_id, model); @@ -116,6 +118,6 @@ fn push_edit_modal(issue_id: i32, model: &mut Model) { Box::new(EditIssueModal::new(issue, time_tracking_type)), ) }; - send_ws_msg(WsMsg::IssueCommentsRequest(issue_id)); + send_ws_msg(WsMsg::IssueCommentsRequest(issue_id), model.ws.as_ref()); model.modals.push(modal); } diff --git a/jirs-client/src/model.rs b/jirs-client/src/model.rs index 483cf388..9e70a837 100644 --- a/jirs-client/src/model.rs +++ b/jirs-client/src/model.rs @@ -12,7 +12,8 @@ use crate::shared::styled_editor::Mode; use crate::shared::styled_image_input::StyledImageInputState; use crate::shared::styled_input::StyledInputState; use crate::shared::styled_select::StyledSelectState; -use crate::{EditIssueModalSection, FieldId, ProjectFieldId, HOST_URL}; +use crate::{EditIssueModalSection, FieldId, ProjectFieldId /*HOST_URL*/}; +use seed::browser::web_socket::WebSocket; #[derive(Clone, Debug, PartialOrd, PartialEq)] pub enum ModalType { @@ -269,6 +270,7 @@ pub struct ProjectSettingsPage { pub time_tracking: StyledCheckboxState, pub column_drag: DragState, pub edit_column_id: Option, + pub creating_issue_status: bool, pub name: StyledInputState, } @@ -304,6 +306,7 @@ impl ProjectSettingsPage { ), column_drag: Default::default(), edit_column_id: None, + creating_issue_status: false, name: StyledInputState::new( FieldId::ProjectSettings(ProjectFieldId::IssueStatusName), "", @@ -424,7 +427,9 @@ pub enum PageContent { #[derive(Debug)] pub struct Model { + pub ws: Option, pub host_url: String, + pub ws_url: String, pub access_token: Option, pub about_tooltip_visible: bool, @@ -451,10 +456,10 @@ pub struct Model { pub issue_statuses: Vec, } -impl Default for Model { - fn default() -> Self { - let host_url = unsafe { HOST_URL.clone() }; +impl Model { + pub fn new(host_url: String, ws_url: String) -> Self { Self { + ws: None, access_token: None, user: None, issue_form: None, @@ -465,6 +470,7 @@ impl Default for Model { comments_by_project_id: Default::default(), page: Page::Project, host_url, + ws_url, page_content: PageContent::Project(Box::new(ProjectPage::default())), modals: vec![], project: None, diff --git a/jirs-client/src/profile.rs b/jirs-client/src/profile.rs index 6e266cbd..085920e1 100644 --- a/jirs-client/src/profile.rs +++ b/jirs-client/src/profile.rs @@ -3,7 +3,6 @@ use web_sys::FormData; use jirs_data::*; -use crate::api::send_ws_msg; use crate::model::{Model, Page, PageContent, ProfilePage}; use crate::shared::styled_button::StyledButton; use crate::shared::styled_field::StyledField; @@ -11,7 +10,8 @@ use crate::shared::styled_form::StyledForm; use crate::shared::styled_image_input::StyledImageInput; use crate::shared::styled_input::StyledInput; use crate::shared::{inner_layout, ToNode}; -use crate::{FieldId, Msg, PageChanged, ProfilePageChange, HOST_URL}; +use crate::ws::send_ws_msg; +use crate::{FieldId, Msg, PageChanged, ProfilePageChange, WebSocketChanged}; pub fn update(msg: Msg, model: &mut crate::model::Model, orders: &mut impl Orders) { let user = match model.user { @@ -20,8 +20,9 @@ pub fn update(msg: Msg, model: &mut crate::model::Model, orders: &mut impl Order }; match msg { - Msg::WsMsg(WsMsg::AuthorizeLoaded(..)) | Msg::ChangePage(Page::Profile) => { - send_ws_msg(WsMsg::ProjectRequest); + Msg::WebSocketChange(WebSocketChanged::WsMsg(WsMsg::AuthorizeLoaded(..))) + | Msg::ChangePage(Page::Profile) => { + send_ws_msg(WsMsg::ProjectRequest, model.ws.as_ref()); model.page_content = PageContent::Profile(Box::new(ProfilePage::new(user))); } _ => (), @@ -50,10 +51,13 @@ pub fn update(msg: Msg, model: &mut crate::model::Model, orders: &mut impl Order fd.set_with_str("token", format!("{}", token).as_str()) .unwrap(); fd.set_with_blob("avatar", file).unwrap(); - orders.perform_cmd(update_avatar(fd)); + orders.perform_cmd(update_avatar(fd, model.host_url.clone())); orders.skip(); } - Msg::WsMsg(WsMsg::AvatarUrlChanged(user_id, avatar_url)) => { + Msg::WebSocketChange(WebSocketChanged::WsMsg(WsMsg::AvatarUrlChanged( + user_id, + avatar_url, + ))) => { if let Some(me) = model.user.as_mut() { if me.id == user_id { profile_page.avatar.url = Some(avatar_url.clone()); @@ -61,10 +65,13 @@ pub fn update(msg: Msg, model: &mut crate::model::Model, orders: &mut impl Order } } Msg::PageChanged(PageChanged::Profile(ProfilePageChange::SubmitForm)) => { - send_ws_msg(WsMsg::ProfileUpdate( - profile_page.email.value.clone(), - profile_page.name.value.clone(), - )) + send_ws_msg( + WsMsg::ProfileUpdate( + profile_page.email.value.clone(), + profile_page.name.value.clone(), + ), + model.ws.as_ref(), + ); } _ => (), } @@ -132,12 +139,17 @@ pub fn view(model: &Model) -> Node { inner_layout(model, "profile", vec![content], empty![]) } -async fn update_avatar(data: FormData) -> Result { - let host_url = unsafe { HOST_URL.clone() }; +async fn update_avatar(data: FormData, host_url: String) -> Option { let path = format!("{}/avatar/", host_url); - Request::new(path) + let result = Request::new(path) .method(Method::Post) .body(data.into()) - .fetch_string(Msg::AvatarUpdateFetched) - .await + .fetch() + .await; + let response = match result { + Ok(r) => r, + Err(_) => return None, + }; + let text = response.text().await.ok()?; + Some(Msg::AvatarUpdateFetched(text)) } diff --git a/jirs-client/src/project.rs b/jirs-client/src/project.rs index 272eda92..058d552f 100644 --- a/jirs-client/src/project.rs +++ b/jirs-client/src/project.rs @@ -3,15 +3,15 @@ use seed::{prelude::*, *}; use jirs_data::*; -use crate::api::send_ws_msg; use crate::model::{ModalType, Model, Page, PageContent, ProjectPage}; use crate::shared::styled_avatar::StyledAvatar; use crate::shared::styled_button::StyledButton; use crate::shared::styled_icon::{Icon, StyledIcon}; use crate::shared::styled_input::StyledInput; use crate::shared::styled_select::StyledSelectChange; -use crate::shared::{drag_ev, inner_layout, ToNode}; -use crate::{BoardPageChange, EditIssueModalSection, FieldId, Msg, PageChanged}; +use crate::shared::{inner_layout, ToNode}; +use crate::ws::send_ws_msg; +use crate::{BoardPageChange, EditIssueModalSection, FieldId, Msg, PageChanged, WebSocketChanged}; pub fn update(msg: Msg, model: &mut crate::model::Model, orders: &mut impl Orders) { if model.user.is_none() { @@ -33,16 +33,16 @@ pub fn update(msg: Msg, model: &mut crate::model::Model, orders: &mut impl Order }; match msg { - Msg::WsMsg(WsMsg::AuthorizeLoaded(..)) + Msg::WebSocketChange(WebSocketChanged::WsMsg(WsMsg::AuthorizeLoaded(..))) | Msg::ChangePage(Page::Project) | Msg::ChangePage(Page::AddIssue) | Msg::ChangePage(Page::EditIssue(..)) => { - send_ws_msg(jirs_data::WsMsg::ProjectRequest); - send_ws_msg(jirs_data::WsMsg::ProjectIssuesRequest); - send_ws_msg(jirs_data::WsMsg::ProjectUsersRequest); - send_ws_msg(jirs_data::WsMsg::IssueStatusesRequest); + send_ws_msg(jirs_data::WsMsg::ProjectRequest, model.ws.as_ref()); + send_ws_msg(jirs_data::WsMsg::ProjectIssuesRequest, model.ws.as_ref()); + send_ws_msg(jirs_data::WsMsg::ProjectUsersRequest, model.ws.as_ref()); + send_ws_msg(jirs_data::WsMsg::IssueStatusesRequest, model.ws.as_ref()); } - Msg::WsMsg(WsMsg::IssueUpdated(issue)) => { + Msg::WebSocketChange(WebSocketChanged::WsMsg(WsMsg::IssueUpdated(issue))) => { let mut old: Vec = vec![]; std::mem::swap(&mut old, &mut model.issues); for is in old { @@ -53,7 +53,7 @@ pub fn update(msg: Msg, model: &mut crate::model::Model, orders: &mut impl Order } } } - Msg::WsMsg(WsMsg::IssueDeleted(id)) => { + Msg::WebSocketChange(WebSocketChanged::WsMsg(WsMsg::IssueDeleted(id))) => { let mut old: Vec = vec![]; std::mem::swap(&mut old, &mut model.issues); for is in old { @@ -123,7 +123,10 @@ pub fn update(msg: Msg, model: &mut crate::model::Model, orders: &mut impl Order project_page.issue_drag.clear_last(); } Msg::DeleteIssue(issue_id) => { - send_ws_msg(jirs_data::WsMsg::IssueDeleteRequest(issue_id)); + send_ws_msg( + jirs_data::WsMsg::IssueDeleteRequest(issue_id), + model.ws.as_ref(), + ); } _ => (), } @@ -306,16 +309,16 @@ fn project_issue_list(model: &Model, status: &jirs_data::IssueStatus) -> Node Node { let issue_id = issue.id; let drag_started = drag_ev(Ev::DragStart, move |_| { - Msg::PageChanged(PageChanged::Board(BoardPageChange::IssueDragStarted( - issue_id, + Some(Msg::PageChanged(PageChanged::Board( + BoardPageChange::IssueDragStarted(issue_id), ))) }); let drag_stopped = drag_ev(Ev::DragEnd, move |_| { - Msg::PageChanged(PageChanged::Board(BoardPageChange::IssueDragStopped( - issue_id, + Some(Msg::PageChanged(PageChanged::Board( + BoardPageChange::IssueDragStopped(issue_id), ))) }); let drag_over_handler = drag_ev(Ev::DragOver, move |ev| { ev.prevent_default(); ev.stop_propagation(); - Msg::PageChanged(PageChanged::Board(BoardPageChange::ExchangePosition( - issue_id, + Some(Msg::PageChanged(PageChanged::Board( + BoardPageChange::ExchangePosition(issue_id), ))) }); let issue_id = issue.id; let drag_out = drag_ev(Ev::DragLeave, move |_| { - Msg::PageChanged(PageChanged::Board(BoardPageChange::DragLeave(issue_id))) + Some(Msg::PageChanged(PageChanged::Board( + BoardPageChange::DragLeave(issue_id), + ))) }); let class_list = vec!["issue"]; @@ -428,7 +433,7 @@ fn project_issue(model: &Model, issue: &Issue) -> Node { drag_stopped, drag_over_handler, drag_out, - p![attrs![At::Class => "title"], issue.title,], + p![attrs![At::Class => "title"], issue.title.as_str()], div![ attrs![At::Class => "bottom"], div![ diff --git a/jirs-client/src/project_settings.rs b/jirs-client/src/project_settings.rs index 70e45fff..5b926c71 100644 --- a/jirs-client/src/project_settings.rs +++ b/jirs-client/src/project_settings.rs @@ -5,7 +5,6 @@ use jirs_data::{ IssueStatus, IssueStatusId, ProjectCategory, TimeTracking, ToVec, UpdateProjectPayload, WsMsg, }; -use crate::api::send_ws_msg; use crate::model::{ DeleteIssueStatusModal, ModalType, Model, Page, PageContent, ProjectSettingsPage, }; @@ -18,9 +17,12 @@ use crate::shared::styled_icon::{Icon, StyledIcon}; use crate::shared::styled_input::StyledInput; use crate::shared::styled_select::{StyledSelect, StyledSelectChange}; use crate::shared::styled_textarea::StyledTextarea; -use crate::shared::{drag_ev, inner_layout, ToChild, ToNode}; +use crate::shared::{inner_layout, ToChild, ToNode}; +use crate::ws::send_ws_msg; use crate::FieldChange::TabChanged; -use crate::{model, FieldId, Msg, PageChanged, ProjectFieldId, ProjectPageChange}; +use crate::{ + model, FieldId, Msg, PageChanged, ProjectFieldId, ProjectPageChange, WebSocketChanged, +}; static TIME_TRACKING_FIBONACCI: &'static str = "Tracking employees’ time carries the risk of having them feel like they are being spied on. This is one of the most common fears that employees have when a time tracking system is implemented. No one likes to feel like they’re always being watched."; static TIME_TRACKING_HOURLY: &'static str = "Employees may feel intimidated by demands to track their time. Or they could feel that they’re constantly being watched and evaluated. And for overly ambitious managers, employee time tracking may open the doors to excessive micromanaging."; @@ -31,22 +33,25 @@ pub fn update(msg: Msg, model: &mut model::Model, orders: &mut impl Orders) } match msg { - Msg::WsMsg(WsMsg::AuthorizeLoaded(..)) => { - send_ws_msg(WsMsg::ProjectRequest); - send_ws_msg(WsMsg::IssueStatusesRequest); - send_ws_msg(WsMsg::ProjectIssuesRequest); - } + Msg::WebSocketChange(ref change) => match change { + WebSocketChanged::WsMsg(WsMsg::AuthorizeLoaded(..)) => { + send_ws_msg(WsMsg::ProjectRequest, model.ws.as_ref()); + send_ws_msg(WsMsg::IssueStatusesRequest, model.ws.as_ref()); + send_ws_msg(WsMsg::ProjectIssuesRequest, model.ws.as_ref()); + } + WebSocketChanged::WsMsg(WsMsg::ProjectLoaded(..)) => { + build_page_content(model); + } + _ => (), + }, Msg::ChangePage(Page::ProjectSettings) => { build_page_content(model); if model.user.is_some() { - send_ws_msg(WsMsg::ProjectRequest); - send_ws_msg(WsMsg::IssueStatusesRequest); - send_ws_msg(WsMsg::ProjectIssuesRequest); + send_ws_msg(WsMsg::ProjectRequest, model.ws.as_ref()); + send_ws_msg(WsMsg::IssueStatusesRequest, model.ws.as_ref()); + send_ws_msg(WsMsg::ProjectIssuesRequest, model.ws.as_ref()); } } - Msg::WsMsg(WsMsg::ProjectLoaded(..)) => { - build_page_content(model); - } _ => (), } @@ -86,14 +91,17 @@ pub fn update(msg: Msg, model: &mut model::Model, orders: &mut impl Orders) page.description_mode = mode; } Msg::PageChanged(PageChanged::ProjectSettings(ProjectPageChange::SubmitForm)) => { - send_ws_msg(WsMsg::ProjectUpdateRequest(UpdateProjectPayload { - id: page.payload.id, - name: page.payload.name.clone(), - url: page.payload.url.clone(), - description: page.payload.description.clone(), - category: page.payload.category.clone(), - time_tracking: Some(page.time_tracking.value.into()), - })); + send_ws_msg( + WsMsg::ProjectUpdateRequest(UpdateProjectPayload { + id: page.payload.id, + name: page.payload.name.clone(), + url: page.payload.url.clone(), + description: page.payload.description.clone(), + category: page.payload.category.clone(), + time_tracking: Some(page.time_tracking.value.into()), + }), + model.ws.as_ref(), + ); } Msg::PageChanged(PageChanged::ProjectSettings(ProjectPageChange::ColumnDragStarted( issue_status_id, @@ -128,7 +136,10 @@ pub fn update(msg: Msg, model: &mut model::Model, orders: &mut impl Orders) .find(|is| Some(is.id) == old_id) .map(|is| (is.id, is.position)) { - send_ws_msg(WsMsg::IssueStatusUpdate(id, name.to_string(), pos)) + send_ws_msg( + WsMsg::IssueStatusUpdate(id, name.to_string(), pos), + model.ws.as_ref(), + ); } } page.name.value = model @@ -259,91 +270,15 @@ pub fn view(model: &model::Model) -> Node { let columns: Vec> = model .issue_statuses .iter() - .map(|is| { - let id = is.id; - let drag_started = drag_ev(Ev::DragStart, move |_| { - Msg::PageChanged(PageChanged::ProjectSettings(ProjectPageChange::ColumnDragStarted( - id, - ))) - }); - let drag_stopped = drag_ev(Ev::DragEnd, move |_| { - Msg::PageChanged(PageChanged::ProjectSettings(ProjectPageChange::ColumnDragStopped( - id, - ))) - }); - let drag_over_handler = drag_ev(Ev::DragOver, move |ev| { - ev.prevent_default(); - ev.stop_propagation(); - Msg::PageChanged(PageChanged::ProjectSettings(ProjectPageChange::ColumnExchangePosition( - id, - ))) - }); - let drag_out = drag_ev(Ev::DragLeave, move |_| { - Msg::PageChanged(PageChanged::ProjectSettings(ProjectPageChange::ColumnDragLeave(id))) - }); - - if page.edit_column_id == Some(id) { - let blur = ev("focusout", |_| { - Msg::PageChanged(PageChanged::ProjectSettings(ProjectPageChange::EditIssueStatusName(None))) - }); - let input = StyledInput::build(FieldId::ProjectSettings(ProjectFieldId::IssueStatusName)) - .state(&page.name) - .primary() - .auto_focus() - .on_input_ev(blur) - .build() - .into_node(); - - div![ - class!["columnPreview"], - div![class!["columnName"], input] - ] - } else { - let on_edit = mouse_ev(Ev::Click, move |_| { - Msg::PageChanged(PageChanged::ProjectSettings(ProjectPageChange::EditIssueStatusName(Some(id)))) - }); - let issue_count_in_column = per_column_issue_count.get(&id).cloned().unwrap_or_default(); - let delete_row = if issue_count_in_column == 0 { - let on_delete = mouse_ev(Ev::Click, move |ev| { - ev.prevent_default(); - ev.stop_propagation(); - Msg::ModalOpened(Box::new(ModalType::DeleteIssueStatusModal(Box::new(DeleteIssueStatusModal::new(id))))) - }); - let delete = StyledButton::build() - .primary() - .add_class("removeColumn") - .icon(Icon::Trash) - .on_click(on_delete) - .build() - .into_node(); - div![class!["removeColumn"], delete] - } else { - div![class!["issueCount"], format!("Issues in column: {}", issue_count_in_column)] - }; - - div![ - class!["columnPreview"], - attrs![At::Style => column_style.as_str(), At::Draggable => "true", At::DropZone => "true"], - div![class!["columnName"], span![is.name], on_edit, delete_row], - drag_started, - drag_stopped, - drag_over_handler, - drag_out, - ] - } - }) + .map(|is| column_preview(is, page, &per_column_issue_count, column_style.as_str())) .collect(); - let add_column = StyledIcon::build(Icon::Plus).build().into_node(); + let columns_section = section![ class!["columnsSection"], div![ class!["columns"], columns, - div![ - class!["columnPreview"], - attrs![At::Style => column_style.as_str()], - div![class!["columnName addColumn"], add_column] - ] + add_column(page, column_style.as_str()) ] ]; let columns_field = StyledField::build() @@ -451,7 +386,10 @@ fn sync(model: &mut Model) { Some(is) => is, _ => continue, }; - send_ws_msg(WsMsg::IssueStatusUpdate(*id, name.clone(), *position)) + send_ws_msg( + WsMsg::IssueStatusUpdate(*id, name.clone(), *position), + model.ws.as_ref(), + ); } } @@ -462,3 +400,137 @@ fn build_page_content(model: &mut Model) { }; model.page_content = PageContent::ProjectSettings(Box::new(ProjectSettingsPage::new(project))); } + +fn add_column(page: &ProjectSettingsPage, column_style: &str) -> Node { + let on_click = mouse_ev(Ev::Click, move |_| { + Msg::PageChanged(PageChanged::ProjectSettings( + ProjectPageChange::EditIssueStatusName(Some(0)), + )) + }); + + if page.edit_column_id == Some(0) { + let blur = ev("focusout", |_| { + Msg::PageChanged(PageChanged::ProjectSettings( + ProjectPageChange::EditIssueStatusName(None), + )) + }); + + let input = StyledInput::build(FieldId::ProjectSettings(ProjectFieldId::IssueStatusName)) + .state(&page.name) + .primary() + .auto_focus() + .on_input_ev(blur) + .build() + .into_node(); + + div![class!["columnPreview"], div![class!["columnName"], input]] + } else { + let add_column = StyledIcon::build(Icon::Plus).build().into_node(); + div![ + class!["columnPreview"], + attrs![At::Style => column_style], + div![class!["columnName addColumn"], add_column], + on_click, + ] + } +} + +fn column_preview( + is: &IssueStatus, + page: &ProjectSettingsPage, + per_column_issue_count: &HashMap, + column_style: &str, +) -> Node { + if page.edit_column_id == Some(is.id) { + let blur = ev("focusout", |_| { + Msg::PageChanged(PageChanged::ProjectSettings( + ProjectPageChange::EditIssueStatusName(None), + )) + }); + let input = StyledInput::build(FieldId::ProjectSettings(ProjectFieldId::IssueStatusName)) + .state(&page.name) + .primary() + .auto_focus() + .on_input_ev(blur) + .build() + .into_node(); + + div![class!["columnPreview"], div![class!["columnName"], input]] + } else { + show_column_preview(is, per_column_issue_count, column_style) + } +} + +fn show_column_preview( + is: &IssueStatus, + per_column_issue_count: &HashMap, + column_style: &str, +) -> Node { + let id = is.id; + let drag_started = drag_ev(Ev::DragStart, move |_| { + Some(Msg::PageChanged(PageChanged::ProjectSettings( + ProjectPageChange::ColumnDragStarted(id), + ))) + }); + let drag_stopped = drag_ev(Ev::DragEnd, move |_| { + Some(Msg::PageChanged(PageChanged::ProjectSettings( + ProjectPageChange::ColumnDragStopped(id), + ))) + }); + let drag_over_handler = drag_ev(Ev::DragOver, move |ev| { + ev.prevent_default(); + ev.stop_propagation(); + Some(Msg::PageChanged(PageChanged::ProjectSettings( + ProjectPageChange::ColumnExchangePosition(id), + ))) + }); + let drag_out = drag_ev(Ev::DragLeave, move |_| { + Some(Msg::PageChanged(PageChanged::ProjectSettings( + ProjectPageChange::ColumnDragLeave(id), + ))) + }); + + let on_edit = mouse_ev(Ev::Click, move |_| { + Msg::PageChanged(PageChanged::ProjectSettings( + ProjectPageChange::EditIssueStatusName(Some(id)), + )) + }); + let issue_count_in_column = per_column_issue_count.get(&id).cloned().unwrap_or_default(); + let delete_row = if issue_count_in_column == 0 { + let on_delete = mouse_ev(Ev::Click, move |ev| { + ev.prevent_default(); + ev.stop_propagation(); + Msg::ModalOpened(Box::new(ModalType::DeleteIssueStatusModal(Box::new( + DeleteIssueStatusModal::new(id), + )))) + }); + let delete = StyledButton::build() + .primary() + .add_class("removeColumn") + .icon(Icon::Trash) + .on_click(on_delete) + .build() + .into_node(); + div![class!["removeColumn"], delete] + } else { + div![ + class!["issueCount"], + format!("Issues in column: {}", issue_count_in_column) + ] + }; + + div![ + class!["columnPreview"], + attrs![At::Style => column_style, At::Draggable => "true", At::DropZone => "true"], + div![ + class!["columnName"], + span![is.name.as_str()], + on_edit, + delete_row + ], + drag_started, + drag_stopped, + drag_over_handler, + drag_out, + ] +} diff --git a/jirs-client/src/shared/aside.rs b/jirs-client/src/shared/aside.rs index bd671ff8..aa453279 100644 --- a/jirs-client/src/shared/aside.rs +++ b/jirs-client/src/shared/aside.rs @@ -15,7 +15,7 @@ pub fn render(model: &Model) -> Node { project_icon, div![ class!["projectTexts"], - div![class!["projectName"], project.name], + div![class!["projectName"], project.name.as_str()], div![class!["projectCategory"], project.category.to_string()] ], ], diff --git a/jirs-client/src/shared/mod.rs b/jirs-client/src/shared/mod.rs index 8d70f795..4e7d622c 100644 --- a/jirs-client/src/shared/mod.rs +++ b/jirs-client/src/shared/mod.rs @@ -1,5 +1,5 @@ use seed::{prelude::*, *}; -use wasm_bindgen::JsCast; +use std::str::FromStr; use jirs_data::*; @@ -33,6 +33,18 @@ pub trait ToChild { fn to_child(&self) -> Self::Builder; } +pub fn go_to_board() { + go_to("/board"); +} + +pub fn go_to_login() { + go_to("/login"); +} + +pub fn go_to(url: &str) { + seed::push_route(Url::from_str(url).unwrap()); +} + pub fn find_issue(model: &Model, issue_id: IssueId) -> Option<&Issue> { model.issues.iter().find(|issue| issue.id == issue_id) } @@ -107,13 +119,3 @@ pub fn read_auth_token() -> Result { .parse() .map_err(|_| "Bad token format".to_string()) } - -pub fn drag_ev( - trigger: impl Into, - handler: impl FnOnce(web_sys::DragEvent) -> Ms + 'static + Clone, -) -> EventHandler { - let closure_handler = move |event: web_sys::Event| { - (handler.clone())(event.dyn_ref::().unwrap().clone()) - }; - EventHandler::new(trigger, closure_handler) -} diff --git a/jirs-client/src/shared/styled_checkbox.rs b/jirs-client/src/shared/styled_checkbox.rs index 057c945d..aef52908 100644 --- a/jirs-client/src/shared/styled_checkbox.rs +++ b/jirs-client/src/shared/styled_checkbox.rs @@ -102,11 +102,10 @@ impl ToNode for ChildBuilder { } = self; let id = field_id.as_ref().map(|f| f.to_string()).unwrap_or_default(); - let handler = if let Some(field_id) = field_id { - mouse_ev(Ev::Click, move |_| Msg::U32InputChanged(field_id, value)) - } else { - ev(Ev::FullScreenError, move |_| Msg::NoOp) - }; + let field_id_clone = field_id.clone(); + let handler: EventHandler = mouse_ev(Ev::Click, move |_| { + field_id_clone.map(|field_id| Msg::U32InputChanged(field_id, value)) + }); class_list.push("styledCheckboxChild".to_string()); class_list.push(if selected { "selected" } else { "" }.to_string()); diff --git a/jirs-client/src/shared/styled_input.rs b/jirs-client/src/shared/styled_input.rs index 348415d2..ee84616d 100644 --- a/jirs-client/src/shared/styled_input.rs +++ b/jirs-client/src/shared/styled_input.rs @@ -222,11 +222,11 @@ pub fn render(values: StyledInput) -> Node { })); input_handlers.push(ev(Ev::KeyUp, move |event| { event.stop_propagation(); - Msg::NoOp + None as Option })); input_handlers.push(ev(Ev::Click, move |event| { event.stop_propagation(); - Msg::NoOp + None as Option })); div![ diff --git a/jirs-client/src/shared/styled_modal.rs b/jirs-client/src/shared/styled_modal.rs index 6f430506..68c0ddc4 100644 --- a/jirs-client/src/shared/styled_modal.rs +++ b/jirs-client/src/shared/styled_modal.rs @@ -122,7 +122,7 @@ pub fn render(values: StyledModal) -> Node { let close_handler = mouse_ev(Ev::Click, |_| Msg::ModalDropped); let body_handler = mouse_ev(Ev::Click, |ev| { ev.stop_propagation(); - Msg::NoOp + None as Option }); let clickable_class = format!("clickableOverlay {}", variant.to_class_name()); diff --git a/jirs-client/src/shared/styled_select.rs b/jirs-client/src/shared/styled_select.rs index 4e11fb74..1110b500 100644 --- a/jirs-client/src/shared/styled_select.rs +++ b/jirs-client/src/shared/styled_select.rs @@ -343,7 +343,7 @@ pub fn render(values: StyledSelect) -> Node { attrs![At::Class => select_class.join(" "), At::Style => dropdown_style.as_str()], keyboard_ev(Ev::KeyUp, |ev| { ev.stop_propagation(); - Msg::NoOp + None as Option }), div![ attrs![At::Class => format!("valueContainer {}", variant)], diff --git a/jirs-client/src/shared/styled_textarea.rs b/jirs-client/src/shared/styled_textarea.rs index 2b496978..fe0536a8 100644 --- a/jirs-client/src/shared/styled_textarea.rs +++ b/jirs-client/src/shared/styled_textarea.rs @@ -161,7 +161,7 @@ pub fn render(values: StyledTextarea) -> Node { let resize_handler = ev(Ev::KeyUp, move |event| { event.stop_propagation(); if handler_disable_auto_resize { - return Msg::NoOp; + return None as Option; } let target = event.target().unwrap(); @@ -172,7 +172,7 @@ pub fn render(values: StyledTextarea) -> Node { textarea .style() .set_css_text(format!("height: {min_height}px", min_height = min_height).as_str()); - Msg::NoOp + None as Option }); let handler_disable_auto_resize = disable_auto_resize; diff --git a/jirs-client/src/sign_in.rs b/jirs-client/src/sign_in.rs index 2ce6b209..9b917b07 100644 --- a/jirs-client/src/sign_in.rs +++ b/jirs-client/src/sign_in.rs @@ -5,7 +5,6 @@ use uuid::Uuid; use jirs_data::WsMsg; -use crate::api::send_ws_msg; use crate::model::{Page, PageContent, SignInPage}; use crate::shared::styled_button::StyledButton; use crate::shared::styled_field::StyledField; @@ -15,7 +14,8 @@ use crate::shared::styled_input::StyledInput; use crate::shared::styled_link::StyledLink; use crate::shared::{outer_layout, write_auth_token, ToNode}; use crate::validations::{is_email, is_token}; -use crate::{model, FieldId, Msg, SignInFieldId}; +use crate::ws::send_ws_msg; +use crate::{model, FieldId, Msg, SignInFieldId, WebSocketChanged}; pub fn update(msg: Msg, model: &mut model::Model, orders: &mut impl Orders) { if model.page != Page::SignIn { @@ -49,10 +49,10 @@ pub fn update(msg: Msg, model: &mut model::Model, orders: &mut impl Orders) page.token_touched = true; } Msg::SignInRequest => { - send_ws_msg(WsMsg::AuthenticateRequest( - page.email.clone(), - page.username.clone(), - )); + send_ws_msg( + WsMsg::AuthenticateRequest(page.email.clone(), page.username.clone()), + model.ws.as_ref(), + ); } Msg::BindClientRequest => { let bind_token: uuid::Uuid = match Uuid::from_str(page.token.as_str()) { @@ -62,21 +62,24 @@ pub fn update(msg: Msg, model: &mut model::Model, orders: &mut impl Orders) return; } }; - send_ws_msg(WsMsg::BindTokenCheck(bind_token)); + send_ws_msg(WsMsg::BindTokenCheck(bind_token), model.ws.as_ref()); } - Msg::WsMsg(WsMsg::AuthenticateSuccess) => { - page.login_success = true; - } - Msg::WsMsg(WsMsg::BindTokenOk(access_token)) => { - match write_auth_token(Some(access_token)) { - Ok(msg) => { - orders.skip().send_msg(msg); - } - Err(e) => { - error!(e); + Msg::WebSocketChange(change) => match change { + WebSocketChanged::WsMsg(WsMsg::AuthenticateSuccess) => { + page.login_success = true; + } + WebSocketChanged::WsMsg(WsMsg::BindTokenOk(access_token)) => { + match write_auth_token(Some(access_token)) { + Ok(msg) => { + orders.skip().send_msg(msg); + } + Err(e) => { + error!(e); + } } } - } + _ => (), + }, _ => (), }; } diff --git a/jirs-client/src/sign_up.rs b/jirs-client/src/sign_up.rs index d3c06067..4c0ea813 100644 --- a/jirs-client/src/sign_up.rs +++ b/jirs-client/src/sign_up.rs @@ -2,7 +2,6 @@ use seed::{prelude::*, *}; use jirs_data::{SignUpFieldId, WsMsg}; -use crate::api::send_ws_msg; use crate::model::{Page, PageContent, SignUpPage}; use crate::shared::styled_button::StyledButton; use crate::shared::styled_field::StyledField; @@ -12,7 +11,8 @@ use crate::shared::styled_input::StyledInput; use crate::shared::styled_link::StyledLink; use crate::shared::{outer_layout, ToNode}; use crate::validations::is_email; -use crate::{model, FieldId, Msg}; +use crate::ws::send_ws_msg; +use crate::{model, FieldId, Msg, WebSocketChanged}; pub fn update(msg: Msg, model: &mut model::Model, _orders: &mut impl Orders) { if model.page != Page::SignUp { @@ -42,17 +42,20 @@ pub fn update(msg: Msg, model: &mut model::Model, _orders: &mut impl Orders page.email_touched = true; } Msg::SignUpRequest => { - send_ws_msg(WsMsg::SignUpRequest( - page.email.clone(), - page.username.clone(), - )); - } - Msg::WsMsg(WsMsg::SignUpSuccess) => { - page.sign_up_success = true; - } - Msg::WsMsg(WsMsg::SignUpPairTaken) => { - page.error = "Pair you give is either taken or is not matching".to_string(); + send_ws_msg( + WsMsg::SignUpRequest(page.email.clone(), page.username.clone()), + model.ws.as_ref(), + ); } + Msg::WebSocketChange(change) => match change { + WebSocketChanged::WsMsg(WsMsg::SignUpSuccess) => { + page.sign_up_success = true; + } + WebSocketChanged::WsMsg(WsMsg::SignUpPairTaken) => { + page.error = "Pair you give is either taken or is not matching".to_string(); + } + _ => (), + }, _ => (), } } @@ -126,7 +129,7 @@ pub fn view(model: &model::Model) -> Node { let error_row = if page.error.is_empty() { empty![] } else { - div![class!["error"], p![page.error]] + div![class!["error"], p![page.error.as_str()]] }; let sign_up_form = StyledForm::build() diff --git a/jirs-client/src/users.rs b/jirs-client/src/users.rs index 298bf641..f8c6c393 100644 --- a/jirs-client/src/users.rs +++ b/jirs-client/src/users.rs @@ -2,7 +2,6 @@ use seed::{prelude::*, *}; use jirs_data::{InvitationState, ToVec, UserRole, UsersFieldId, WsMsg}; -use crate::api::send_ws_msg; use crate::model::*; use crate::shared::styled_button::StyledButton; use crate::shared::styled_field::StyledField; @@ -11,7 +10,8 @@ use crate::shared::styled_input::StyledInput; use crate::shared::styled_select::*; use crate::shared::{inner_layout, ToChild, ToNode}; use crate::validations::is_email; -use crate::{FieldId, Msg, PageChanged, UsersPageChange}; +use crate::ws::send_ws_msg; +use crate::{FieldId, Msg, PageChanged, UsersPageChange, WebSocketChanged}; pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders) { if let Msg::ChangePage(Page::Users) = msg { @@ -27,18 +27,50 @@ pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders) { page.user_role_state.update(&msg, orders); match msg { - Msg::WsMsg(WsMsg::AuthorizeLoaded(Ok(_))) | Msg::ChangePage(Page::Users) - if model.user.is_some() => - { - send_ws_msg(WsMsg::InvitationListRequest); - send_ws_msg(WsMsg::InvitedUsersRequest); - } - Msg::WsMsg(WsMsg::InvitedUsersLoaded(users)) => { - page.invited_users = users; - } - Msg::WsMsg(WsMsg::InvitationListLoaded(invitations)) => { - page.invitations = invitations; + Msg::ChangePage(Page::Users) if model.user.is_some() => { + send_ws_msg(WsMsg::InvitationListRequest, model.ws.as_ref()); + send_ws_msg(WsMsg::InvitedUsersRequest, model.ws.as_ref()); } + Msg::WebSocketChange(change) => match change { + WebSocketChanged::WsMsg(WsMsg::AuthorizeLoaded(Ok(_))) if model.user.is_some() => { + send_ws_msg(WsMsg::InvitationListRequest, model.ws.as_ref()); + send_ws_msg(WsMsg::InvitedUsersRequest, model.ws.as_ref()); + } + WebSocketChanged::WsMsg(WsMsg::InvitedUsersLoaded(users)) => { + page.invited_users = users; + } + WebSocketChanged::WsMsg(WsMsg::InvitationListLoaded(invitations)) => { + page.invitations = invitations; + } + WebSocketChanged::WsMsg(WsMsg::InvitationRevokeSuccess(id)) => { + let mut old = vec![]; + std::mem::swap(&mut page.invitations, &mut old); + for mut invitation in old { + if id == invitation.id { + invitation.state = InvitationState::Revoked; + } + page.invitations.push(invitation); + } + send_ws_msg(WsMsg::InvitationListRequest, model.ws.as_ref()); + } + WebSocketChanged::WsMsg(WsMsg::InvitedUserRemoveSuccess(email)) => { + let mut old = vec![]; + std::mem::swap(&mut page.invited_users, &mut old); + for user in old { + if user.email != email { + page.invited_users.push(user); + } + } + } + WebSocketChanged::WsMsg(WsMsg::InvitationSendSuccess) => { + send_ws_msg(WsMsg::InvitationListRequest, model.ws.as_ref()); + page.form_state = InvitationFormState::Succeed; + } + WebSocketChanged::WsMsg(WsMsg::InvitationSendFailure) => { + page.form_state = InvitationFormState::Failed; + } + _ => (), + }, Msg::PageChanged(PageChanged::Users(UsersPageChange::ResetForm)) => { page.name.clear(); page.name_touched = false; @@ -64,43 +96,22 @@ pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders) { } Msg::InviteRequest => { page.form_state = InvitationFormState::Sent; - send_ws_msg(WsMsg::InvitationSendRequest { - name: page.name.clone(), - email: page.email.clone(), - }) - } - Msg::WsMsg(WsMsg::InvitationRevokeSuccess(id)) => { - let mut old = vec![]; - std::mem::swap(&mut page.invitations, &mut old); - for mut invitation in old { - if id == invitation.id { - invitation.state = InvitationState::Revoked; - } - page.invitations.push(invitation); - } - send_ws_msg(WsMsg::InvitationListRequest); + send_ws_msg( + WsMsg::InvitationSendRequest { + name: page.name.clone(), + email: page.email.clone(), + }, + model.ws.as_ref(), + ); } Msg::InviteRevokeRequest(invitation_id) => { - send_ws_msg(WsMsg::InvitationRevokeRequest(invitation_id)); + send_ws_msg( + WsMsg::InvitationRevokeRequest(invitation_id), + model.ws.as_ref(), + ); } Msg::InvitedUserRemove(email) => { - send_ws_msg(WsMsg::InvitedUserRemoveRequest(email)); - } - Msg::WsMsg(WsMsg::InvitedUserRemoveSuccess(email)) => { - let mut old = vec![]; - std::mem::swap(&mut page.invited_users, &mut old); - for user in old { - if user.email != email { - page.invited_users.push(user); - } - } - } - Msg::WsMsg(WsMsg::InvitationSendSuccess) => { - send_ws_msg(WsMsg::InvitationListRequest); - page.form_state = InvitationFormState::Succeed; - } - Msg::WsMsg(WsMsg::InvitationSendFailure) => { - page.form_state = InvitationFormState::Failed; + send_ws_msg(WsMsg::InvitedUserRemoveRequest(email), model.ws.as_ref()); } _ => (), } @@ -210,8 +221,8 @@ pub fn view(model: &Model) -> Node { .into_node(); li![ class!["user"], - span![user.name], - span![user.email], + span![user.name.as_str()], + span![user.email.as_str()], span![format!("{}", user.user_role)], remove, ] @@ -238,8 +249,8 @@ pub fn view(model: &Model) -> Node { li![ class!["invitation"], attrs![At::Class => format!("{}", invitation.state)], - span![invitation.name], - span![invitation.email], + span![invitation.name.as_str()], + span![invitation.email.as_str()], span![format!("{}", invitation.state)], revoke, ] diff --git a/jirs-client/src/ws/issue.rs b/jirs-client/src/ws/issue.rs index 2b7f1437..8be1a89c 100644 --- a/jirs-client/src/ws/issue.rs +++ b/jirs-client/src/ws/issue.rs @@ -2,8 +2,8 @@ use seed::*; use jirs_data::*; -use crate::api::send_ws_msg; use crate::model::{Model, PageContent}; +use crate::ws::send_ws_msg; pub fn drag_started(issue_id: IssueId, model: &mut Model) { let project_page = match &mut model.page_content { @@ -90,16 +90,22 @@ pub fn sync(model: &mut Model) { continue; } - send_ws_msg(WsMsg::IssueUpdateRequest( - issue.id, - IssueFieldId::IssueStatusId, - PayloadVariant::I32(issue.issue_status_id), - )); - send_ws_msg(WsMsg::IssueUpdateRequest( - issue.id, - IssueFieldId::ListPosition, - PayloadVariant::I32(issue.list_position), - )); + send_ws_msg( + WsMsg::IssueUpdateRequest( + issue.id, + IssueFieldId::IssueStatusId, + PayloadVariant::I32(issue.issue_status_id), + ), + model.ws.as_ref(), + ); + send_ws_msg( + WsMsg::IssueUpdateRequest( + issue.id, + IssueFieldId::ListPosition, + PayloadVariant::I32(issue.list_position), + ), + model.ws.as_ref(), + ); } project_page.issue_drag.clear(); } diff --git a/jirs-client/src/ws/mod.rs b/jirs-client/src/ws/mod.rs index 58e89798..30960656 100644 --- a/jirs-client/src/ws/mod.rs +++ b/jirs-client/src/ws/mod.rs @@ -4,57 +4,75 @@ use jirs_data::WsMsg; use crate::model::*; use crate::shared::write_auth_token; -use crate::{Msg, APP}; +use crate::{Msg, WebSocketChanged}; pub mod issue; -pub fn handle(msg: WsMsg) { - let app = match unsafe { APP.as_mut().unwrap() }.write() { - Ok(app) => app, +pub fn send_ws_msg(msg: WsMsg, ws: Option<&WebSocket>) { + let ws = match ws { + Some(ws) => ws, _ => return, }; - - match msg { - WsMsg::Ping | WsMsg::Pong => {} - _ => app.update(Msg::WsMsg(msg)), - } + let binary = bincode::serialize(&msg).unwrap(); + ws.send_bytes(binary.as_slice()) + .expect("Failed to send ws msg"); } -pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders) { +pub fn open_socket(model: &mut Model, orders: &mut impl Orders) { + if model.host_url.is_empty() { + return; + } + let url = model.ws_url.as_str(); + + model.ws = WebSocket::builder(url, orders) + .on_message(|msg| Msg::WebSocketChange(WebSocketChanged::WebSocketMessage(msg))) + .on_open(|| Msg::WebSocketChange(WebSocketChanged::WebSocketOpened)) + .on_close(|_| Msg::WebSocketChange(WebSocketChanged::WebSocketClosed)) + .on_error(|| {}) + .build_and_open() + .ok(); +} + +pub async fn read_incoming(msg: WebSocketMessage) -> Msg { + let bytes = msg.bytes().await.unwrap_or_default(); + Msg::WebSocketChange(WebSocketChanged::WebSocketMessageLoaded(bytes)) +} + +pub fn update(msg: &WsMsg, model: &mut Model, orders: &mut impl Orders) { match msg { // auth - Msg::WsMsg(WsMsg::AuthorizeLoaded(Ok(user))) => { + WsMsg::AuthorizeLoaded(Ok(user)) => { model.user = Some(user.clone()); } - Msg::WsMsg(WsMsg::AuthorizeExpired) => { + WsMsg::AuthorizeExpired => { if let Ok(msg) = write_auth_token(None) { orders.skip().send_msg(msg); } } // project - Msg::WsMsg(WsMsg::ProjectLoaded(project)) => { + WsMsg::ProjectLoaded(project) => { model.project = Some(project.clone()); } // issues - Msg::WsMsg(WsMsg::ProjectIssuesLoaded(v)) => { + WsMsg::ProjectIssuesLoaded(v) => { let mut v = v.clone(); v.sort_by(|a, b| (a.list_position as i64).cmp(&(b.list_position as i64))); model.issues = v; } // issue statuses - Msg::WsMsg(WsMsg::IssueStatusesResponse(v)) => { + WsMsg::IssueStatusesResponse(v) => { model.issue_statuses = v.clone(); model .issue_statuses .sort_by(|a, b| a.position.cmp(&b.position)); } - Msg::WsMsg(WsMsg::IssueStatusCreated(is)) => { + WsMsg::IssueStatusCreated(is) => { model.issue_statuses.push(is.clone()); model .issue_statuses .sort_by(|a, b| a.position.cmp(&b.position)); } - Msg::WsMsg(WsMsg::IssueStatusUpdated(changed)) => { + WsMsg::IssueStatusUpdated(changed) => { let mut old = vec![]; std::mem::swap(&mut model.issue_statuses, &mut old); for is in old { @@ -68,7 +86,7 @@ pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders) { .issue_statuses .sort_by(|a, b| a.position.cmp(&b.position)); } - Msg::WsMsg(WsMsg::IssueDeleted(id)) => { + WsMsg::IssueDeleted(id) => { let mut old = vec![]; std::mem::swap(&mut model.issue_statuses, &mut old); for is in old { @@ -82,11 +100,11 @@ pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders) { .sort_by(|a, b| a.position.cmp(&b.position)); } // users - Msg::WsMsg(WsMsg::ProjectUsersLoaded(v)) => { + WsMsg::ProjectUsersLoaded(v) => { model.users = v.clone(); } // comments - Msg::WsMsg(WsMsg::IssueCommentsLoaded(comments)) => { + WsMsg::IssueCommentsLoaded(comments) => { let issue_id = match model.modals.get(0) { Some(ModalType::EditIssue(issue_id, _)) => *issue_id, _ => return, @@ -98,7 +116,7 @@ pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders) { v.sort_by(|a, b| a.updated_at.cmp(&b.updated_at)); model.comments = v; } - Msg::WsMsg(WsMsg::CommentDeleted(comment_id)) => { + WsMsg::CommentDeleted(comment_id) => { let mut old = vec![]; std::mem::swap(&mut model.comments, &mut old); for comment in old.into_iter() { @@ -107,7 +125,7 @@ pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders) { } } } - Msg::WsMsg(WsMsg::AvatarUrlChanged(user_id, avatar_url)) => { + WsMsg::AvatarUrlChanged(user_id, avatar_url) => { for user in model.users.iter_mut() { if user.id == *user_id { user.avatar_url = Some(avatar_url.clone()); diff --git a/jirs-client/webpack.config.js b/jirs-client/webpack.config.js index 8e302aeb..93512ad7 100644 --- a/jirs-client/webpack.config.js +++ b/jirs-client/webpack.config.js @@ -24,7 +24,7 @@ if (process.env.NODE_ENV === "production") { execSync('cargo build --bin jirs-css', { cwd: jirDir, }); - const css = spawn('./target/debug/jirs-css', [ + spawn('./target/debug/jirs-css', [ '-W', '-O', './jirs-client/dev/styles.css' @@ -70,7 +70,7 @@ module.exports = { devServer: { contentBase: path.join(__dirname, 'dev'), historyApiFallback: true, - hot: true, + hot: false, port: process.env.JIRS_CLIENT_PORT || 6000, host: process.env.JIRS_CLIENT_BIND || '0.0.0.0', allowedHosts: [ diff --git a/jirs-server/Cargo.toml b/jirs-server/Cargo.toml index 58963b78..a7f2237b 100644 --- a/jirs-server/Cargo.toml +++ b/jirs-server/Cargo.toml @@ -50,7 +50,6 @@ byteorder = "1.0" chrono = { version = "0.4", features = [ "serde" ] } libc = { version = "0.2.0" } pq-sys = { version = ">=0.3.0, <0.5.0" } -quickcheck = { version = "0.4" } serde_json = { version = ">=0.8.0, <2.0" } toml = "0.5.6" bincode = "1.2.1" diff --git a/jirs-server/src/schema.rs b/jirs-server/src/schema.rs index 5bb21f5d..6a16806e 100644 --- a/jirs-server/src/schema.rs +++ b/jirs-server/src/schema.rs @@ -153,53 +153,6 @@ table! { } } -table! { - use diesel::sql_types::*; - use jirs_data::sql::*; - - /// Representation of the `issue_statuses` table. - /// - /// (Automatically generated by Diesel.) - issue_statuses (id) { - /// The `id` column of the `issue_statuses` table. - /// - /// Its SQL type is `Int4`. - /// - /// (Automatically generated by Diesel.) - id -> Int4, - /// The `name` column of the `issue_statuses` table. - /// - /// Its SQL type is `Varchar`. - /// - /// (Automatically generated by Diesel.) - name -> Varchar, - /// The `position` column of the `issue_statuses` table. - /// - /// Its SQL type is `Int4`. - /// - /// (Automatically generated by Diesel.) - position -> Int4, - /// The `project_id` column of the `issue_statuses` table. - /// - /// Its SQL type is `Int4`. - /// - /// (Automatically generated by Diesel.) - project_id -> Int4, - /// The `created_at` column of the `issue_statuses` table. - /// - /// Its SQL type is `Timestamp`. - /// - /// (Automatically generated by Diesel.) - created_at -> Timestamp, - /// The `updated_at` column of the `issue_statuses` table. - /// - /// Its SQL type is `Timestamp`. - /// - /// (Automatically generated by Diesel.) - updated_at -> Timestamp, - } -} - table! { use diesel::sql_types::*; use jirs_data::sql::*; @@ -301,6 +254,53 @@ table! { } } +table! { + use diesel::sql_types::*; + use jirs_data::sql::*; + + /// Representation of the `issue_statuses` table. + /// + /// (Automatically generated by Diesel.) + issue_statuses (id) { + /// The `id` column of the `issue_statuses` table. + /// + /// Its SQL type is `Int4`. + /// + /// (Automatically generated by Diesel.) + id -> Int4, + /// The `name` column of the `issue_statuses` table. + /// + /// Its SQL type is `Varchar`. + /// + /// (Automatically generated by Diesel.) + name -> Varchar, + /// The `position` column of the `issue_statuses` table. + /// + /// Its SQL type is `Int4`. + /// + /// (Automatically generated by Diesel.) + position -> Int4, + /// The `project_id` column of the `issue_statuses` table. + /// + /// Its SQL type is `Int4`. + /// + /// (Automatically generated by Diesel.) + project_id -> Int4, + /// The `created_at` column of the `issue_statuses` table. + /// + /// Its SQL type is `Timestamp`. + /// + /// (Automatically generated by Diesel.) + created_at -> Timestamp, + /// The `updated_at` column of the `issue_statuses` table. + /// + /// Its SQL type is `Timestamp`. + /// + /// (Automatically generated by Diesel.) + updated_at -> Timestamp, + } +} + table! { use diesel::sql_types::*; use jirs_data::sql::*; @@ -489,8 +489,8 @@ allow_tables_to_appear_in_same_query!( comments, invitations, issue_assignees, - issue_statuses, issues, + issue_statuses, projects, tokens, users,