Upgrade to latest Seed, remove JS WebSocket, remove webpack dependency

This commit is contained in:
Adrian Wozniak 2020-05-19 11:55:55 +02:00
parent 9625cde0f6
commit 39ee462e33
34 changed files with 1135 additions and 1024 deletions

358
Cargo.lock generated
View File

@ -15,7 +15,7 @@ dependencies = [
"derive_more", "derive_more",
"futures 0.3.4", "futures 0.3.4",
"lazy_static", "lazy_static",
"log 0.4.8", "log",
"parking_lot", "parking_lot",
"pin-project", "pin-project",
"smallvec", "smallvec",
@ -35,7 +35,7 @@ dependencies = [
"bytes", "bytes",
"futures-core", "futures-core",
"futures-sink", "futures-sink",
"log 0.4.8", "log",
"tokio", "tokio",
"tokio-util", "tokio-util",
] ]
@ -54,7 +54,7 @@ dependencies = [
"either", "either",
"futures 0.3.4", "futures 0.3.4",
"http", "http",
"log 0.4.8", "log",
"trust-dns-proto", "trust-dns-proto",
"trust-dns-resolver", "trust-dns-resolver",
] ]
@ -84,10 +84,10 @@ dependencies = [
"bytes", "bytes",
"derive_more", "derive_more",
"futures 0.3.4", "futures 0.3.4",
"log 0.4.8", "log",
"mime", "mime",
"mime_guess", "mime_guess",
"percent-encoding 2.1.0", "percent-encoding",
"v_htmlescape", "v_htmlescape",
] ]
@ -124,12 +124,12 @@ dependencies = [
"indexmap", "indexmap",
"language-tags", "language-tags",
"lazy_static", "lazy_static",
"log 0.4.8", "log",
"mime", "mime",
"percent-encoding 2.1.0", "percent-encoding",
"pin-project", "pin-project",
"rand 0.7.3", "rand 0.7.3",
"regex 1.3.6", "regex",
"serde", "serde",
"serde_json", "serde_json",
"serde_urlencoded", "serde_urlencoded",
@ -161,7 +161,7 @@ dependencies = [
"derive_more", "derive_more",
"futures 0.3.4", "futures 0.3.4",
"httparse", "httparse",
"log 0.4.8", "log",
"mime", "mime",
"time 0.1.42", "time 0.1.42",
"twoway", "twoway",
@ -175,8 +175,8 @@ checksum = "9d7a10ca4d94e8c8e7a87c5173aba1b97ba9a6563ca02b0e1cd23531093d3ec8"
dependencies = [ dependencies = [
"bytestring", "bytestring",
"http", "http",
"log 0.4.8", "log",
"regex 1.3.6", "regex",
"serde", "serde",
] ]
@ -204,7 +204,7 @@ dependencies = [
"actix-service", "actix-service",
"actix-utils", "actix-utils",
"futures 0.3.4", "futures 0.3.4",
"log 0.4.8", "log",
"mio", "mio",
"mio-uds", "mio-uds",
"net2", "net2",
@ -233,7 +233,7 @@ dependencies = [
"actix-server", "actix-server",
"actix-service", "actix-service",
"futures 0.3.4", "futures 0.3.4",
"log 0.4.8", "log",
"net2", "net2",
] ]
@ -246,7 +246,7 @@ dependencies = [
"derive_more", "derive_more",
"futures-channel", "futures-channel",
"lazy_static", "lazy_static",
"log 0.4.8", "log",
"num_cpus", "num_cpus",
"parking_lot", "parking_lot",
"threadpool", "threadpool",
@ -265,7 +265,7 @@ dependencies = [
"derive_more", "derive_more",
"either", "either",
"futures 0.3.4", "futures 0.3.4",
"log 0.4.8", "log",
] ]
[[package]] [[package]]
@ -281,7 +281,7 @@ dependencies = [
"bytes", "bytes",
"either", "either",
"futures 0.3.4", "futures 0.3.4",
"log 0.4.8", "log",
"pin-project", "pin-project",
"slab", "slab",
] ]
@ -310,16 +310,16 @@ dependencies = [
"encoding_rs", "encoding_rs",
"futures 0.3.4", "futures 0.3.4",
"fxhash", "fxhash",
"log 0.4.8", "log",
"mime", "mime",
"net2", "net2",
"pin-project", "pin-project",
"regex 1.3.6", "regex",
"serde", "serde",
"serde_json", "serde_json",
"serde_urlencoded", "serde_urlencoded",
"time 0.1.42", "time 0.1.42",
"url 2.1.1", "url",
] ]
[[package]] [[package]]
@ -365,22 +365,13 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d2e7343e7fc9de883d1b0341e0b13970f764c14101234857d2ddafa1cb1cac2" 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]] [[package]]
name = "aho-corasick" name = "aho-corasick"
version = "0.7.10" version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8716408b8bc624ed7f65d223ddb9ac2d044c0547b6fa4b0d554f3a9540496ada" checksum = "8716408b8bc624ed7f65d223ddb9ac2d044c0547b6fa4b0d554f3a9540496ada"
dependencies = [ dependencies = [
"memchr 2.3.3", "memchr",
] ]
[[package]] [[package]]
@ -464,9 +455,9 @@ dependencies = [
"bytes", "bytes",
"derive_more", "derive_more",
"futures-core", "futures-core",
"log 0.4.8", "log",
"mime", "mime",
"percent-encoding 2.1.0", "percent-encoding",
"rand 0.7.3", "rand 0.7.3",
"serde", "serde",
"serde_json", "serde_json",
@ -709,7 +700,7 @@ dependencies = [
"lazy_static", "lazy_static",
"pest", "pest",
"pest_derive", "pest_derive",
"regex 1.3.6", "regex",
"twoway", "twoway",
"typed-arena", "typed-arena",
"unicode_categories", "unicode_categories",
@ -733,12 +724,12 @@ checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
[[package]] [[package]]
name = "cookie" name = "cookie"
version = "0.12.0" version = "0.13.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "888604f00b3db336d2af898ec3c1d5d0ddf5e6d462220f2ededc33a87ac4bbd5" checksum = "0c60ef6d0bbf56ad2674249b6bb74f2c6aeb98b98dd57b5d3e37cace33011d69"
dependencies = [ dependencies = [
"time 0.1.42", "percent-encoding",
"url 1.7.2", "time 0.2.15",
] ]
[[package]] [[package]]
@ -1025,16 +1016,6 @@ dependencies = [
"syn", "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]] [[package]]
name = "env_logger" name = "env_logger"
version = "0.7.1" version = "0.7.1"
@ -1043,8 +1024,8 @@ checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36"
dependencies = [ dependencies = [
"atty", "atty",
"humantime", "humantime",
"log 0.4.8", "log",
"regex 1.3.6", "regex",
"termcolor", "termcolor",
] ]
@ -1261,7 +1242,7 @@ dependencies = [
"futures-macro", "futures-macro",
"futures-sink", "futures-sink",
"futures-task", "futures-task",
"memchr 2.3.3", "memchr",
"pin-utils", "pin-utils",
"proc-macro-hack", "proc-macro-hack",
"proc-macro-nested", "proc-macro-nested",
@ -1304,6 +1285,7 @@ dependencies = [
"cfg-if", "cfg-if",
"libc", "libc",
"wasi", "wasi",
"wasm-bindgen",
] ]
[[package]] [[package]]
@ -1312,12 +1294,37 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" 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]] [[package]]
name = "gloo-timers" name = "gloo-timers"
version = "0.2.1" version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47204a46aaff920a1ea58b11d03dec6f704287d27561724a4631e450654a891f" checksum = "47204a46aaff920a1ea58b11d03dec6f704287d27561724a4631e450654a891f"
dependencies = [ dependencies = [
"futures-channel",
"futures-core",
"js-sys", "js-sys",
"wasm-bindgen", "wasm-bindgen",
"web-sys", "web-sys",
@ -1336,7 +1343,7 @@ dependencies = [
"futures-util", "futures-util",
"http", "http",
"indexmap", "indexmap",
"log 0.4.8", "log",
"slab", "slab",
"tokio", "tokio",
"tokio-util", "tokio-util",
@ -1448,7 +1455,7 @@ dependencies = [
"http-body", "http-body",
"httparse", "httparse",
"itoa", "itoa",
"log 0.4.8", "log",
"net2", "net2",
"pin-project", "pin-project",
"time 0.1.42", "time 0.1.42",
@ -1470,17 +1477,6 @@ dependencies = [
"tokio-tls", "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]] [[package]]
name = "idna" name = "idna"
version = "0.2.0" version = "0.2.0"
@ -1600,21 +1596,20 @@ dependencies = [
"chrono", "chrono",
"diesel", "diesel",
"dotenv", "dotenv",
"env_logger 0.7.1", "env_logger",
"futures 0.3.4", "futures 0.3.4",
"ipnetwork", "ipnetwork",
"jirs-data", "jirs-data",
"lettre", "lettre",
"lettre_email", "lettre_email",
"libc", "libc",
"log 0.4.8", "log",
"num-bigint", "num-bigint",
"num-integer", "num-integer",
"num-traits", "num-traits",
"percent-encoding 2.1.0", "percent-encoding",
"pq-sys", "pq-sys",
"pretty_env_logger", "pretty_env_logger",
"quickcheck",
"r2d2", "r2d2",
"rusoto_core", "rusoto_core",
"rusoto_s3", "rusoto_s3",
@ -1622,7 +1617,7 @@ dependencies = [
"serde_json", "serde_json",
"time 0.1.42", "time 0.1.42",
"toml", "toml",
"url 2.1.1", "url",
"uuid 0.8.1", "uuid 0.8.1",
] ]
@ -1647,9 +1642,9 @@ dependencies = [
[[package]] [[package]]
name = "js-sys" name = "js-sys"
version = "0.3.37" version = "0.3.39"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a27d435371a2fa5b6d2b028a74bbdb1234f308da363226a2854ca3ff8ba7055" checksum = "fa5a448de267e7358beaf4a5d849518fe9a0c13fce7afd44b06e68550e5562a7"
dependencies = [ dependencies = [
"wasm-bindgen", "wasm-bindgen",
] ]
@ -1692,7 +1687,7 @@ dependencies = [
"bufstream", "bufstream",
"fast_chemail", "fast_chemail",
"hostname 0.1.5", "hostname 0.1.5",
"log 0.4.8", "log",
"native-tls", "native-tls",
"nom", "nom",
"serde", "serde",
@ -1735,15 +1730,6 @@ dependencies = [
"scopeguard", "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]] [[package]]
name = "log" name = "log"
version = "0.4.8" version = "0.4.8"
@ -1792,15 +1778,6 @@ version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771"
[[package]]
name = "memchr"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8b629fb514376c675b98c1421e80b151d3817ac42d7c667717d282761418d20"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "memchr" name = "memchr"
version = "2.3.3" version = "2.3.3"
@ -1844,7 +1821,7 @@ dependencies = [
"iovec", "iovec",
"kernel32-sys", "kernel32-sys",
"libc", "libc",
"log 0.4.8", "log",
"miow 0.2.1", "miow 0.2.1",
"net2", "net2",
"slab", "slab",
@ -1858,7 +1835,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52403fe290012ce777c4626790c8951324a2b9e3316b3143779c72b029742f19" checksum = "52403fe290012ce777c4626790c8951324a2b9e3316b3143779c72b029742f19"
dependencies = [ dependencies = [
"lazycell", "lazycell",
"log 0.4.8", "log",
"mio", "mio",
"slab", "slab",
] ]
@ -1869,7 +1846,7 @@ version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f5e374eff525ce1c5b7687c4cef63943e7686524a387933ad27ca7ec43779cb3" checksum = "f5e374eff525ce1c5b7687c4cef63943e7686524a387933ad27ca7ec43779cb3"
dependencies = [ dependencies = [
"log 0.4.8", "log",
"mio", "mio",
"miow 0.3.3", "miow 0.3.3",
"winapi 0.3.8", "winapi 0.3.8",
@ -1916,7 +1893,7 @@ checksum = "2b0d88c06fe90d5ee94048ba40409ef1d9315d86f6f38c2efdaad4fb50c58b2d"
dependencies = [ dependencies = [
"lazy_static", "lazy_static",
"libc", "libc",
"log 0.4.8", "log",
"openssl", "openssl",
"openssl-probe", "openssl-probe",
"openssl-sys", "openssl-sys",
@ -1943,7 +1920,7 @@ version = "4.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6" checksum = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6"
dependencies = [ dependencies = [
"memchr 2.3.3", "memchr",
"version_check 0.1.5", "version_check 0.1.5",
] ]
@ -2068,12 +2045,6 @@ dependencies = [
"winapi 0.3.8", "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]] [[package]]
name = "percent-encoding" name = "percent-encoding"
version = "2.1.0" version = "2.1.0"
@ -2182,8 +2153,8 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "926d36b9553851b8b0005f1275891b392ee4d2d833852c417ed025477350fb9d" checksum = "926d36b9553851b8b0005f1275891b392ee4d2d833852c417ed025477350fb9d"
dependencies = [ dependencies = [
"env_logger 0.7.1", "env_logger",
"log 0.4.8", "log",
] ]
[[package]] [[package]]
@ -2209,13 +2180,13 @@ dependencies = [
[[package]] [[package]]
name = "pulldown-cmark" name = "pulldown-cmark"
version = "0.6.1" version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c205cc82214f3594e2d50686730314f817c67ffa80fe800cf0db78c3c2b9d9e" checksum = "3e142c3b8f49d2200605ee6ba0b1d757310e9e7a72afe78c36ee2ef67300ee00"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"getopts", "getopts",
"memchr 2.3.3", "memchr",
"unicase", "unicase",
] ]
@ -2225,17 +2196,6 @@ version = "1.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" 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]] [[package]]
name = "quote" name = "quote"
version = "1.0.3" version = "1.0.3"
@ -2251,21 +2211,11 @@ version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1497e40855348e4a8a40767d8e55174bce1e445a3ac9254ad44ad468ee0485af" checksum = "1497e40855348e4a8a40767d8e55174bce1e445a3ac9254ad44ad468ee0485af"
dependencies = [ dependencies = [
"log 0.4.8", "log",
"parking_lot", "parking_lot",
"scheduled-thread-pool", "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]] [[package]]
name = "rand" name = "rand"
version = "0.4.6" version = "0.4.6"
@ -2293,7 +2243,7 @@ dependencies = [
"rand_isaac", "rand_isaac",
"rand_jitter", "rand_jitter",
"rand_os", "rand_os",
"rand_pcg", "rand_pcg 0.1.2",
"rand_xorshift", "rand_xorshift",
"winapi 0.3.8", "winapi 0.3.8",
] ]
@ -2309,6 +2259,7 @@ dependencies = [
"rand_chacha 0.2.2", "rand_chacha 0.2.2",
"rand_core 0.5.1", "rand_core 0.5.1",
"rand_hc 0.2.0", "rand_hc 0.2.0",
"rand_pcg 0.2.1",
] ]
[[package]] [[package]]
@ -2417,6 +2368,15 @@ dependencies = [
"rand_core 0.4.2", "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]] [[package]]
name = "rand_xorshift" name = "rand_xorshift"
version = "0.1.1" version = "0.1.1"
@ -2452,37 +2412,18 @@ dependencies = [
"rust-argon2", "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]] [[package]]
name = "regex" name = "regex"
version = "1.3.6" version = "1.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f6946991529684867e47d86474e3a6d0c0ab9b82d5821e314b1ede31fa3a4b3" checksum = "7f6946991529684867e47d86474e3a6d0c0ab9b82d5821e314b1ede31fa3a4b3"
dependencies = [ dependencies = [
"aho-corasick 0.7.10", "aho-corasick",
"memchr 2.3.3", "memchr",
"regex-syntax 0.6.17", "regex-syntax",
"thread_local 1.0.1", "thread_local",
] ]
[[package]]
name = "regex-syntax"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9ec002c35e86791825ed294b50008eea9ddfc8def4420124fbc6b08db834957"
[[package]] [[package]]
name = "regex-syntax" name = "regex-syntax"
version = "0.6.17" version = "0.6.17"
@ -2523,9 +2464,9 @@ dependencies = [
"hyper", "hyper",
"hyper-tls", "hyper-tls",
"lazy_static", "lazy_static",
"log 0.4.8", "log",
"md5", "md5",
"percent-encoding 2.1.0", "percent-encoding",
"pin-project", "pin-project",
"rusoto_credential", "rusoto_credential",
"rusoto_signature", "rusoto_signature",
@ -2549,7 +2490,7 @@ dependencies = [
"futures 0.3.4", "futures 0.3.4",
"hyper", "hyper",
"pin-project", "pin-project",
"regex 1.3.6", "regex",
"serde", "serde",
"serde_json", "serde_json",
"shlex", "shlex",
@ -2583,9 +2524,9 @@ dependencies = [
"hmac", "hmac",
"http", "http",
"hyper", "hyper",
"log 0.4.8", "log",
"md5", "md5",
"percent-encoding 2.1.0", "percent-encoding",
"pin-project", "pin-project",
"rusoto_credential", "rusoto_credential",
"rustc_version", "rustc_version",
@ -2699,21 +2640,24 @@ dependencies = [
[[package]] [[package]]
name = "seed" name = "seed"
version = "0.6.0" version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd5d8fd7f12f565f639caf4b6d74a3853d5d7234d0543ec3beae81311492623e" checksum = "882f4569a394bbb2f15f2fc410e0fbcef178fe24fc2d91599607a598443c6df8"
dependencies = [ dependencies = [
"console_error_panic_hook", "console_error_panic_hook",
"cookie", "cookie",
"dbg", "dbg",
"enclose", "enclose",
"futures 0.3.4", "futures 0.3.4",
"gloo-file",
"gloo-timers", "gloo-timers",
"indexmap", "indexmap",
"js-sys", "js-sys",
"pulldown-cmark", "pulldown-cmark",
"rand 0.7.3",
"serde", "serde",
"serde_json", "serde_json",
"uuid 0.8.1",
"version_check 0.9.1", "version_check 0.9.1",
"wasm-bindgen", "wasm-bindgen",
"wasm-bindgen-futures", "wasm-bindgen-futures",
@ -2737,18 +2681,18 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.105" version = "1.0.110"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e707fbbf255b8fc8c3b99abb91e7257a622caeb20a9818cbadbeeede4e0932ff" checksum = "99e7b308464d16b56eba9964e4972a3eee817760ab60d88c3f86e1fecb08204c"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.105" version = "1.0.110"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac5d00fc561ba2724df6758a17de23df5914f20e41cb00f94d5b7ae42fffaff8" checksum = "818fbf6bfa9a42d3bfcaca148547aa00c7b915bec71d1757aa2d44ca68771984"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -2757,9 +2701,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.48" version = "1.0.53"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9371ade75d4c2d6cb154141b9752cf3781ec9c05e0e5cf35060e1e70ee7b9c25" checksum = "993948e75b189211a9b31a7528f950c6adc21f9720b6438ff80a7fa2f864cea2"
dependencies = [ dependencies = [
"itoa", "itoa",
"ryu", "ryu",
@ -2775,7 +2719,7 @@ dependencies = [
"dtoa", "dtoa",
"itoa", "itoa",
"serde", "serde",
"url 2.1.1", "url",
] ]
[[package]] [[package]]
@ -2970,25 +2914,6 @@ dependencies = [
"unicode-width", "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]] [[package]]
name = "thread_local" name = "thread_local"
version = "1.0.1" version = "1.0.1"
@ -3068,7 +2993,7 @@ dependencies = [
"iovec", "iovec",
"lazy_static", "lazy_static",
"libc", "libc",
"memchr 2.3.3", "memchr",
"mio", "mio",
"mio-named-pipes", "mio-named-pipes",
"mio-uds", "mio-uds",
@ -3109,7 +3034,7 @@ dependencies = [
"bytes", "bytes",
"futures-core", "futures-core",
"futures-sink", "futures-sink",
"log 0.4.8", "log",
"pin-project-lite", "pin-project-lite",
"tokio", "tokio",
] ]
@ -3139,14 +3064,14 @@ dependencies = [
"enum-as-inner", "enum-as-inner",
"failure", "failure",
"futures 0.3.4", "futures 0.3.4",
"idna 0.2.0", "idna",
"lazy_static", "lazy_static",
"log 0.4.8", "log",
"rand 0.7.3", "rand 0.7.3",
"smallvec", "smallvec",
"socket2", "socket2",
"tokio", "tokio",
"url 2.1.1", "url",
] ]
[[package]] [[package]]
@ -3160,7 +3085,7 @@ dependencies = [
"futures 0.3.4", "futures 0.3.4",
"ipconfig", "ipconfig",
"lazy_static", "lazy_static",
"log 0.4.8", "log",
"lru-cache", "lru-cache",
"resolv-conf", "resolv-conf",
"smallvec", "smallvec",
@ -3180,7 +3105,7 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b40075910de3a912adbd80b5d8bad6ad10a23eeb1f5bf9d4006839e899ba5bc" checksum = "6b40075910de3a912adbd80b5d8bad6ad10a23eeb1f5bf9d4006839e899ba5bc"
dependencies = [ dependencies = [
"memchr 2.3.3", "memchr",
"unchecked-index", "unchecked-index",
] ]
@ -3259,34 +3184,17 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" 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]] [[package]]
name = "url" name = "url"
version = "2.1.1" version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "829d4a8476c35c9bf0bbce5a3b23f4106f79728039b726d292bb93bc106787cb" checksum = "829d4a8476c35c9bf0bbce5a3b23f4106f79728039b726d292bb93bc106787cb"
dependencies = [ dependencies = [
"idna 0.2.0", "idna",
"matches", "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]] [[package]]
name = "uuid" name = "uuid"
version = "0.6.5" version = "0.6.5"
@ -3388,7 +3296,7 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0"
dependencies = [ dependencies = [
"log 0.4.8", "log",
"try-lock", "try-lock",
] ]
@ -3400,9 +3308,9 @@ checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
[[package]] [[package]]
name = "wasm-bindgen" name = "wasm-bindgen"
version = "0.2.60" version = "0.2.62"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2cc57ce05287f8376e998cbddfb4c8cb43b84a7ec55cf4551d7c00eef317a47f" checksum = "e3c7d40d09cdbf0f4895ae58cf57d92e1e57a9dd8ed2e8390514b54a47cc5551"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"serde", "serde",
@ -3412,13 +3320,13 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-backend" name = "wasm-bindgen-backend"
version = "0.2.60" version = "0.2.62"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d967d37bf6c16cca2973ca3af071d0a2523392e4a594548155d89a678f4237cd" checksum = "c3972e137ebf830900db522d6c8fd74d1900dcfc733462e9a12e942b00b4ac94"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"lazy_static", "lazy_static",
"log 0.4.8", "log",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn",
@ -3427,9 +3335,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-futures" name = "wasm-bindgen-futures"
version = "0.4.10" version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7add542ea1ac7fdaa9dc25e031a6af33b7d63376292bd24140c637d00d1c312a" checksum = "8a369c5e1dfb7569e14d62af4da642a3cbc2f9a3652fe586e26ac22222aa4b04"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"js-sys", "js-sys",
@ -3439,9 +3347,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-macro" name = "wasm-bindgen-macro"
version = "0.2.60" version = "0.2.62"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8bd151b63e1ea881bb742cd20e1d6127cef28399558f3b5d415289bc41eee3a4" checksum = "2cd85aa2c579e8892442954685f0d801f9129de24fa2136b2c6a539c76b65776"
dependencies = [ dependencies = [
"quote", "quote",
"wasm-bindgen-macro-support", "wasm-bindgen-macro-support",
@ -3449,9 +3357,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-macro-support" name = "wasm-bindgen-macro-support"
version = "0.2.60" version = "0.2.62"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d68a5b36eef1be7868f668632863292e37739656a80fc4b9acec7b0bd35a4931" checksum = "8eb197bd3a47553334907ffd2f16507b4f4f01bbec3ac921a7719e0decdfe72a"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -3462,9 +3370,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-shared" name = "wasm-bindgen-shared"
version = "0.2.60" version = "0.2.62"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "daf76fe7d25ac79748a37538b7daeed1c7a6867c92d3245c12c6222e4a20d639" checksum = "a91c2916119c17a8e316507afaaa2dd94b47646048014bbdf6bef098c1bb58ad"
[[package]] [[package]]
name = "wasm-bindgen-test" name = "wasm-bindgen-test"
@ -3492,9 +3400,9 @@ dependencies = [
[[package]] [[package]]
name = "web-sys" name = "web-sys"
version = "0.3.37" version = "0.3.39"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d6f51648d8c56c366144378a33290049eafdd784071077f6fe37dae64c1c4cb" checksum = "8bc359e5dd3b46cb9687a051d50a2fdd228e4ba7cf6fcf861a5365c3d671a642"
dependencies = [ dependencies = [
"js-sys", "js-sys",
"wasm-bindgen", "wasm-bindgen",

View File

@ -19,7 +19,7 @@ opt-level = 's'
[dependencies] [dependencies]
jirs-data = { path = "../jirs-data" } jirs-data = { path = "../jirs-data" }
seed = { version = "*" } seed = { version = "0.7.0" }
serde = "*" serde = "*"
serde_json = "*" serde_json = "*"
bincode = "1.2.1" bincode = "1.2.1"

21
jirs-client/jirs.nginx Normal file
View File

@ -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;
}

View File

@ -1,11 +1,12 @@
.styledInput { .styledInput {
position: relative; position: relative;
display: inline-block; display: inline-block;
height: 32px; min-height: 32px;
width: 100%; width: 100%;
} }
.styledInput > .inputElement { .styledInput > .inputElement {
min-height: 32px;
height: 100%; height: 100%;
width: 100%; width: 100%;
padding: 0 7px; padding: 0 7px;

View File

@ -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 getProtocol = () => window.location.protocol.replace(/^http/, 'ws');
const wsUrl = () => `${ getProtocol() }//${ getWsHostName() }:${ process.env.JIRS_SERVER_PORT }/ws/`; const wsUrl = () => `${ getProtocol() }//${ getWsHostName() }:${ process.env.JIRS_SERVER_PORT }/ws/`;
import("../pkg/index.js").then(module => { import("/jirs.js").then(async module => {
let queue = []; window.module = module;
let ws; console.log(module)
await module.default();
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();
const host_url = `${ location.protocol }//${ process.env.JIRS_SERVER_BIND }:${ process.env.JIRS_SERVER_PORT }`; const host_url = `${ location.protocol }//${ process.env.JIRS_SERVER_BIND }:${ process.env.JIRS_SERVER_PORT }`;
module.set_host_url(host_url); module.render(host_url, wsUrl());
module.render();
}); });

View File

@ -0,0 +1,15 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" href="/favicon.png">
<link rel="icon" href="/logo.svg">
<title>JIRS</title>
<link rel="stylesheet" type="text/css" href="/styles.css">
</head>
<body>
<main id="app"></main>
<script type="module" src="/index.js"></script>
</body>
</html>

16
jirs-client/scripts/dev.sh Executable file
View File

@ -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

View File

@ -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);
}

View File

@ -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<IssueStatusId>),
}
#[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<u8>),
WebSocketOpened,
WebSocketClosed,
SendPing,
}

121
jirs-client/src/fields.rs Normal file
View File

@ -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"),
},
}
}
}

View File

@ -1,16 +1,15 @@
use std::sync::RwLock;
use seed::{prelude::*, *}; use seed::{prelude::*, *};
use web_sys::File; use web_sys::File;
use jirs_data::*; use jirs_data::*;
use crate::api::send_ws_msg;
use crate::model::{ModalType, Model, Page}; use crate::model::{ModalType, Model, Page};
use crate::shared::styled_editor::Mode as TabMode;
use crate::shared::styled_select::StyledSelectChange; 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 invite;
mod modal; mod modal;
mod model; mod model;
@ -24,179 +23,13 @@ mod users;
pub mod validations; pub mod validations;
mod ws; mod ws;
pub type AvatarFilterActive = bool; pub use changes::*;
pub use fields::*;
pub type AppType = App<Msg, Model, Node<Msg>>; pub type AppType = App<Msg, Model, Node<Msg>>;
#[derive(Clone, Debug, PartialOrd, PartialEq, Hash)] #[derive(Debug)]
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<IssueStatusId>),
}
#[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)]
pub enum Msg { pub enum Msg {
NoOp,
GlobalKeyDown { GlobalKeyDown {
key: String, key: String,
shift: bool, shift: bool,
@ -248,35 +81,82 @@ pub enum Msg {
DeleteComment(CommentId), DeleteComment(CommentId),
// profile // profile
AvatarUpdateFetched(seed::fetch::FetchObject<String>), AvatarUpdateFetched(String),
// modals // modals
ModalOpened(Box<ModalType>), ModalOpened(Box<ModalType>),
ModalDropped, ModalDropped,
ModalChanged(FieldChange), ModalChanged(FieldChange),
WsMsg(jirs_data::WsMsg), // WebSocket
WebSocketChange(WebSocketChanged),
} }
fn update(msg: Msg, model: &mut model::Model, orders: &mut impl Orders<Msg>) { fn update(msg: Msg, model: &mut model::Model, orders: &mut impl Orders<Msg>) {
match msg { if model.ws.is_none() {
Msg::NoOp => return, 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) { if cfg!(debug_assertions) {
log!(msg); log!(msg);
} }
match &msg { match &msg {
Msg::AuthTokenStored => { Msg::AuthTokenStored => {
seed::push_route(vec!["dashboard"]); go_to_board();
orders.skip().send_msg(Msg::ChangePage(Page::Project)); orders.skip().send_msg(Msg::ChangePage(Page::Project));
authorize_or_redirect(); authorize_or_redirect(model);
return; return;
} }
Msg::AuthTokenErased => { Msg::AuthTokenErased => {
seed::push_route(vec!["login"]); go_to_login();
orders.skip().send_msg(Msg::ChangePage(Page::SignIn)); orders.skip().send_msg(Msg::ChangePage(Page::SignIn));
authorize_or_redirect(); authorize_or_redirect(model);
return; return;
} }
Msg::ChangePage(page) => { Msg::ChangePage(page) => {
@ -287,7 +167,6 @@ fn update(msg: Msg, model: &mut model::Model, orders: &mut impl Orders<Msg>) {
} }
_ => (), _ => (),
} }
crate::ws::update(&msg, model, orders);
crate::modal::update(&msg, model, orders); crate::modal::update(&msg, model, orders);
match model.page { match model.page {
Page::Project | Page::AddIssue | Page::EditIssue(..) => project::update(msg, model, orders), Page::Project | Page::AddIssue | Page::EditIssue(..) => project::update(msg, model, orders),
@ -317,116 +196,98 @@ fn view(model: &model::Model) -> Node<Msg> {
} }
fn routes(url: Url) -> Option<Msg> { fn routes(url: Url) -> Option<Msg> {
if url.path.is_empty() { match resolve_page(url) {
return Some(Msg::ChangePage(model::Page::Project)); Some(page) => Some(Msg::ChangePage(page)),
_ => None,
}
} }
match url.path[0].as_ref() { fn resolve_page(url: Url) -> Option<Page> {
"board" => Some(Msg::ChangePage(model::Page::Project)), if url.path().is_empty() {
"issues" => match url.path.get(1).as_ref().map(|s| s.parse::<i32>()) { return Some(Page::Project);
Some(Ok(id)) => Some(Msg::ChangePage(model::Page::EditIssue(id))),
_ => 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)),
} }
let page = match url.path()[0].as_ref() {
"board" => Page::Project,
"issues" => match url.path().get(1).as_ref().map(|s| s.parse::<i32>()) {
Some(Ok(id)) => Page::EditIssue(id),
_ => return None,
},
"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 HOST_URL: String = String::new();
pub static mut APP: Option<RwLock<App<Msg, Model, Node<Msg>>>> = None; pub static mut WS_URL: String = String::new();
#[wasm_bindgen] #[wasm_bindgen]
pub fn set_host_url(url: String) { pub fn render(host_url: String, ws_url: String) {
unsafe { unsafe {
HOST_URL = url; HOST_URL = host_url;
} WS_URL = ws_url;
} }
#[wasm_bindgen] // if let Some(body) = seed::html_document().body() {
pub fn handle_ws_message(value: &wasm_bindgen::JsValue) { // use wasm_bindgen::JsCast;
let a = js_sys::Uint8Array::new(value); // let body = body.dyn_ref::<web_sys::HtmlBodyElement>().unwrap().clone();
let mut v = Vec::new(); // let key_up_closure =
for idx in 0..a.length() { // wasm_bindgen::closure::Closure::wrap(Box::new(|event: web_sys::KeyboardEvent| {
v.push(a.get_index(idx)); // if let Some(Ok(app)) = unsafe { APP.as_mut().map(|app| app.write()) } {
} // let msg = Msg::GlobalKeyDown {
if let Ok(msg) = bincode::deserialize(v.as_slice()) { // key: event.key(),
ws::handle(msg); // shift: event.shift_key(),
}; // ctrl: event.ctrl_key(),
} // alt: event.alt_key(),
// };
// app.update(msg);
// }
// })
// as Box<dyn Fn(web_sys::KeyboardEvent)>);
// body.add_event_listener_with_callback("keyup", key_up_closure.as_ref().unchecked_ref())
// .unwrap();
// key_up_closure.forget();
// }
#[wasm_bindgen] let _app = seed::App::builder(update, view)
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<dyn Fn()>,
5000,
);
if let Some(body) = seed::html_document().body() {
use wasm_bindgen::JsCast;
let body = body.dyn_ref::<web_sys::HtmlBodyElement>().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<dyn Fn(web_sys::KeyboardEvent)>);
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) .routes(routes)
.after_mount(after_mount)
.build_and_start(); .build_and_start();
}
authorize_or_redirect(); fn after_mount(url: Url, orders: &mut impl Orders<Msg>) -> AfterMount<Model> {
let host_url = unsafe { HOST_URL.clone() };
let cell_app = std::sync::RwLock::new(app); let ws_url = unsafe { WS_URL.clone() };
let mut model = Model::new(host_url, ws_url);
unsafe { 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] #[inline]
fn authorize_or_redirect() { fn authorize_or_redirect(model: &Model) {
match crate::shared::read_auth_token() { match crate::shared::read_auth_token() {
Ok(token) => { Ok(token) => {
send_ws_msg(WsMsg::AuthorizeRequest(token)); send_ws_msg(WsMsg::AuthorizeRequest(token), model.ws.as_ref());
} }
Err(..) => { Err(..) => {
let pathname = seed::document().location().unwrap().pathname().unwrap(); let pathname = seed::document().location().unwrap().pathname().unwrap();
match pathname.as_str() { match pathname.as_str() {
"/login" | "/register" | "/invite" => {} "/login" | "/register" | "/invite" => {}
_ => { _ => {
seed::push_route(vec!["login"]); go_to_login();
} }
}; };
} }

View File

@ -1,9 +1,8 @@
use seed::{prelude::*, *}; use seed::{prelude::*, *};
use jirs_data::IssueFieldId; use jirs_data::{IssueFieldId, WsMsg};
use jirs_data::{IssuePriority, IssueType, ToVec}; use jirs_data::{IssuePriority, IssueType, ToVec};
use crate::api::send_ws_msg;
use crate::model::{AddIssueModal, ModalType, Model}; use crate::model::{AddIssueModal, ModalType, Model};
use crate::shared::styled_button::StyledButton; use crate::shared::styled_button::StyledButton;
use crate::shared::styled_field::StyledField; 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_select::StyledSelectChange;
use crate::shared::styled_textarea::StyledTextarea; use crate::shared::styled_textarea::StyledTextarea;
use crate::shared::{ToChild, ToNode}; 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<Msg>) { pub fn update(msg: &Msg, model: &mut crate::model::Model, orders: &mut impl Orders<Msg>) {
let modal = model.modals.iter_mut().find(|modal| match modal { 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(), user_ids: modal.user_ids.clone(),
reporter_id: modal.reporter_id.unwrap_or_else(|| user_id), 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()); model.issues.push(issue.clone());
orders.skip().send_msg(Msg::ModalDropped); orders.skip().send_msg(Msg::ModalDropped);
} }

View File

@ -2,11 +2,10 @@ use seed::prelude::*;
use jirs_data::{IssueStatusId, WsMsg}; use jirs_data::{IssueStatusId, WsMsg};
use crate::api::send_ws_msg;
use crate::model::{DeleteIssueStatusModal, ModalType, Model}; use crate::model::{DeleteIssueStatusModal, ModalType, Model};
use crate::shared::styled_confirm_modal::StyledConfirmModal; use crate::shared::styled_confirm_modal::StyledConfirmModal;
use crate::shared::ToNode; 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<Msg>) { pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
let _modal: &mut Box<DeleteIssueStatusModal> = let _modal: &mut Box<DeleteIssueStatusModal> =
@ -20,9 +19,12 @@ pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
match msg { match msg {
Msg::DeleteIssueStatus(issue_status_id) => { 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); orders.skip().send_msg(Msg::ModalDropped);
} }
_ => (), _ => (),

View File

@ -2,7 +2,6 @@ use seed::{prelude::*, *};
use jirs_data::*; use jirs_data::*;
use crate::api::send_ws_msg;
use crate::modal::time_tracking::time_tracking_field; use crate::modal::time_tracking::time_tracking_field;
use crate::model::{CommentForm, EditIssueModal, ModalType, Model}; use crate::model::{CommentForm, EditIssueModal, ModalType, Model};
use crate::shared::styled_avatar::StyledAvatar; 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::styled_textarea::StyledTextarea;
use crate::shared::tracking_widget::tracking_link; use crate::shared::tracking_widget::tracking_link;
use crate::shared::{ToChild, ToNode}; 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<Msg>) { pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
let modal: &mut EditIssueModal = match model.modals.get_mut(0) { 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<Msg>) {
modal.time_remaining_select.update(msg, orders); modal.time_remaining_select.update(msg, orders);
match msg { match msg {
Msg::WsMsg(WsMsg::IssueUpdated(issue)) => { Msg::WebSocketChange(WebSocketChanged::WsMsg(WsMsg::IssueUpdated(issue))) => {
modal.payload = issue.clone().into(); modal.payload = issue.clone().into();
} }
Msg::StyledSelectChanged( Msg::StyledSelectChanged(
@ -43,44 +43,56 @@ pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
StyledSelectChange::Changed(value), StyledSelectChange::Changed(value),
) => { ) => {
modal.payload.issue_type = (*value).into(); modal.payload.issue_type = (*value).into();
send_ws_msg(WsMsg::IssueUpdateRequest( send_ws_msg(
WsMsg::IssueUpdateRequest(
modal.id, modal.id,
IssueFieldId::Type, IssueFieldId::Type,
PayloadVariant::IssueType(modal.payload.issue_type), PayloadVariant::IssueType(modal.payload.issue_type),
)); ),
model.ws.as_ref(),
);
} }
Msg::StyledSelectChanged( Msg::StyledSelectChanged(
FieldId::EditIssueModal(EditIssueModalSection::Issue(IssueFieldId::IssueStatusId)), FieldId::EditIssueModal(EditIssueModalSection::Issue(IssueFieldId::IssueStatusId)),
StyledSelectChange::Changed(value), StyledSelectChange::Changed(value),
) => { ) => {
modal.payload.issue_status_id = *value as IssueStatusId; modal.payload.issue_status_id = *value as IssueStatusId;
send_ws_msg(WsMsg::IssueUpdateRequest( send_ws_msg(
WsMsg::IssueUpdateRequest(
modal.id, modal.id,
IssueFieldId::IssueStatusId, IssueFieldId::IssueStatusId,
PayloadVariant::I32(modal.payload.issue_status_id), PayloadVariant::I32(modal.payload.issue_status_id),
)); ),
model.ws.as_ref(),
);
} }
Msg::StyledSelectChanged( Msg::StyledSelectChanged(
FieldId::EditIssueModal(EditIssueModalSection::Issue(IssueFieldId::Reporter)), FieldId::EditIssueModal(EditIssueModalSection::Issue(IssueFieldId::Reporter)),
StyledSelectChange::Changed(value), StyledSelectChange::Changed(value),
) => { ) => {
modal.payload.reporter_id = *value as i32; modal.payload.reporter_id = *value as i32;
send_ws_msg(WsMsg::IssueUpdateRequest( send_ws_msg(
WsMsg::IssueUpdateRequest(
modal.id, modal.id,
IssueFieldId::Reporter, IssueFieldId::Reporter,
PayloadVariant::I32(modal.payload.reporter_id), PayloadVariant::I32(modal.payload.reporter_id),
)); ),
model.ws.as_ref(),
);
} }
Msg::StyledSelectChanged( Msg::StyledSelectChanged(
FieldId::EditIssueModal(EditIssueModalSection::Issue(IssueFieldId::Assignees)), FieldId::EditIssueModal(EditIssueModalSection::Issue(IssueFieldId::Assignees)),
StyledSelectChange::Changed(value), StyledSelectChange::Changed(value),
) => { ) => {
modal.payload.user_ids.push(*value as i32); modal.payload.user_ids.push(*value as i32);
send_ws_msg(WsMsg::IssueUpdateRequest( send_ws_msg(
WsMsg::IssueUpdateRequest(
modal.id, modal.id,
IssueFieldId::Assignees, IssueFieldId::Assignees,
PayloadVariant::VecI32(modal.payload.user_ids.clone()), PayloadVariant::VecI32(modal.payload.user_ids.clone()),
)); ),
model.ws.as_ref(),
);
} }
Msg::StyledSelectChanged( Msg::StyledSelectChanged(
FieldId::EditIssueModal(EditIssueModalSection::Issue(IssueFieldId::Assignees)), FieldId::EditIssueModal(EditIssueModalSection::Issue(IssueFieldId::Assignees)),
@ -94,33 +106,42 @@ pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
modal.payload.user_ids.push(id); modal.payload.user_ids.push(id);
} }
} }
send_ws_msg(WsMsg::IssueUpdateRequest( send_ws_msg(
WsMsg::IssueUpdateRequest(
modal.id, modal.id,
IssueFieldId::Assignees, IssueFieldId::Assignees,
PayloadVariant::VecI32(modal.payload.user_ids.clone()), PayloadVariant::VecI32(modal.payload.user_ids.clone()),
)); ),
model.ws.as_ref(),
);
} }
Msg::StyledSelectChanged( Msg::StyledSelectChanged(
FieldId::EditIssueModal(EditIssueModalSection::Issue(IssueFieldId::Priority)), FieldId::EditIssueModal(EditIssueModalSection::Issue(IssueFieldId::Priority)),
StyledSelectChange::Changed(value), StyledSelectChange::Changed(value),
) => { ) => {
modal.payload.priority = (*value).into(); modal.payload.priority = (*value).into();
send_ws_msg(WsMsg::IssueUpdateRequest( send_ws_msg(
WsMsg::IssueUpdateRequest(
modal.id, modal.id,
IssueFieldId::Priority, IssueFieldId::Priority,
PayloadVariant::IssuePriority(modal.payload.priority), PayloadVariant::IssuePriority(modal.payload.priority),
)); ),
model.ws.as_ref(),
);
} }
Msg::StrInputChanged( Msg::StrInputChanged(
FieldId::EditIssueModal(EditIssueModalSection::Issue(IssueFieldId::Title)), FieldId::EditIssueModal(EditIssueModalSection::Issue(IssueFieldId::Title)),
value, value,
) => { ) => {
modal.payload.title = value.clone(); modal.payload.title = value.clone();
send_ws_msg(WsMsg::IssueUpdateRequest( send_ws_msg(
WsMsg::IssueUpdateRequest(
modal.id, modal.id,
IssueFieldId::Title, IssueFieldId::Title,
PayloadVariant::String(modal.payload.title.clone()), PayloadVariant::String(modal.payload.title.clone()),
)); ),
model.ws.as_ref(),
);
} }
Msg::StrInputChanged( Msg::StrInputChanged(
FieldId::EditIssueModal(EditIssueModalSection::Issue(IssueFieldId::Description)), FieldId::EditIssueModal(EditIssueModalSection::Issue(IssueFieldId::Description)),
@ -128,7 +149,8 @@ pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
) => { ) => {
modal.payload.description = Some(value.clone()); modal.payload.description = Some(value.clone());
modal.payload.description_text = Some(value.clone()); modal.payload.description_text = Some(value.clone());
send_ws_msg(WsMsg::IssueUpdateRequest( send_ws_msg(
WsMsg::IssueUpdateRequest(
modal.id, modal.id,
IssueFieldId::Description, IssueFieldId::Description,
PayloadVariant::String( PayloadVariant::String(
@ -139,7 +161,9 @@ pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
.cloned() .cloned()
.unwrap_or_default(), .unwrap_or_default(),
), ),
)); ),
model.ws.as_ref(),
);
} }
// TimeSpent // TimeSpent
Msg::StrInputChanged( Msg::StrInputChanged(
@ -147,22 +171,28 @@ pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
.., ..,
) => { ) => {
modal.payload.time_spent = modal.time_spent.represent_f64_as_i32(); modal.payload.time_spent = modal.time_spent.represent_f64_as_i32();
send_ws_msg(WsMsg::IssueUpdateRequest( send_ws_msg(
WsMsg::IssueUpdateRequest(
modal.id, modal.id,
IssueFieldId::TimeSpent, IssueFieldId::TimeSpent,
PayloadVariant::OptionI32(modal.payload.time_spent), PayloadVariant::OptionI32(modal.payload.time_spent),
)); ),
model.ws.as_ref(),
);
} }
Msg::StyledSelectChanged( Msg::StyledSelectChanged(
FieldId::EditIssueModal(EditIssueModalSection::Issue(IssueFieldId::TimeSpent)), FieldId::EditIssueModal(EditIssueModalSection::Issue(IssueFieldId::TimeSpent)),
StyledSelectChange::Changed(..), StyledSelectChange::Changed(..),
) => { ) => {
modal.payload.time_spent = modal.time_spent_select.values.get(0).map(|n| *n as i32); modal.payload.time_spent = modal.time_spent_select.values.get(0).map(|n| *n as i32);
send_ws_msg(WsMsg::IssueUpdateRequest( send_ws_msg(
WsMsg::IssueUpdateRequest(
modal.id, modal.id,
IssueFieldId::TimeSpent, IssueFieldId::TimeSpent,
PayloadVariant::OptionI32(modal.payload.time_spent), PayloadVariant::OptionI32(modal.payload.time_spent),
)); ),
model.ws.as_ref(),
);
} }
// Time Remaining // Time Remaining
Msg::StrInputChanged( Msg::StrInputChanged(
@ -170,11 +200,14 @@ pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
.., ..,
) => { ) => {
modal.payload.time_remaining = modal.time_remaining.represent_f64_as_i32(); modal.payload.time_remaining = modal.time_remaining.represent_f64_as_i32();
send_ws_msg(WsMsg::IssueUpdateRequest( send_ws_msg(
WsMsg::IssueUpdateRequest(
modal.id, modal.id,
IssueFieldId::TimeRemaining, IssueFieldId::TimeRemaining,
PayloadVariant::OptionI32(modal.payload.time_remaining), PayloadVariant::OptionI32(modal.payload.time_remaining),
)); ),
model.ws.as_ref(),
);
} }
Msg::StyledSelectChanged( Msg::StyledSelectChanged(
FieldId::EditIssueModal(EditIssueModalSection::Issue(IssueFieldId::TimeRemaining)), FieldId::EditIssueModal(EditIssueModalSection::Issue(IssueFieldId::TimeRemaining)),
@ -182,11 +215,14 @@ pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
) => { ) => {
modal.payload.time_remaining = modal.payload.time_remaining =
modal.time_remaining_select.values.get(0).map(|n| *n as i32); modal.time_remaining_select.values.get(0).map(|n| *n as i32);
send_ws_msg(WsMsg::IssueUpdateRequest( send_ws_msg(
WsMsg::IssueUpdateRequest(
modal.id, modal.id,
IssueFieldId::TimeRemaining, IssueFieldId::TimeRemaining,
PayloadVariant::OptionI32(modal.payload.time_remaining), PayloadVariant::OptionI32(modal.payload.time_remaining),
)); ),
model.ws.as_ref(),
);
} }
// Estimate // Estimate
Msg::StrInputChanged( Msg::StrInputChanged(
@ -194,22 +230,28 @@ pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
.., ..,
) => { ) => {
modal.payload.estimate = modal.estimate.represent_f64_as_i32(); modal.payload.estimate = modal.estimate.represent_f64_as_i32();
send_ws_msg(WsMsg::IssueUpdateRequest( send_ws_msg(
WsMsg::IssueUpdateRequest(
modal.id, modal.id,
IssueFieldId::Estimate, IssueFieldId::Estimate,
PayloadVariant::OptionI32(modal.payload.estimate), PayloadVariant::OptionI32(modal.payload.estimate),
)); ),
model.ws.as_ref(),
);
} }
Msg::StyledSelectChanged( Msg::StyledSelectChanged(
FieldId::EditIssueModal(EditIssueModalSection::Issue(IssueFieldId::Estimate)), FieldId::EditIssueModal(EditIssueModalSection::Issue(IssueFieldId::Estimate)),
StyledSelectChange::Changed(..), StyledSelectChange::Changed(..),
) => { ) => {
modal.payload.estimate = modal.estimate_select.values.get(0).map(|n| *n as i32); modal.payload.estimate = modal.estimate_select.values.get(0).map(|n| *n as i32);
send_ws_msg(WsMsg::IssueUpdateRequest( send_ws_msg(
WsMsg::IssueUpdateRequest(
modal.id, modal.id,
IssueFieldId::Estimate, IssueFieldId::Estimate,
PayloadVariant::OptionI32(modal.payload.estimate), PayloadVariant::OptionI32(modal.payload.estimate),
)); ),
model.ws.as_ref(),
);
} }
Msg::ModalChanged(FieldChange::TabChanged( Msg::ModalChanged(FieldChange::TabChanged(
FieldId::EditIssueModal(EditIssueModalSection::Issue(IssueFieldId::Description)), FieldId::EditIssueModal(EditIssueModalSection::Issue(IssueFieldId::Description)),
@ -248,7 +290,7 @@ pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
issue_id: modal.id, issue_id: modal.id,
}), }),
}; };
send_ws_msg(msg); send_ws_msg(msg, model.ws.as_ref());
orders orders
.skip() .skip()
.send_msg(Msg::ModalChanged(FieldChange::ToggleCommentForm( .send_msg(Msg::ModalChanged(FieldChange::ToggleCommentForm(
@ -272,7 +314,7 @@ pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
modal.comment_form.creating = true; modal.comment_form.creating = true;
} }
Msg::DeleteComment(comment_id) => { 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); orders.skip().send_msg(Msg::ModalDropped);
} }
@ -314,15 +356,13 @@ fn top_modal_row(_model: &Model, modal: &EditIssueModal) -> Node<Msg> {
let issue_id = *id; let issue_id = *id;
let click_handler = mouse_ev(Ev::Click, move |_| { let click_handler = mouse_ev(Ev::Click, move |_| {
use wasm_bindgen::JsCast;
let link = format!("http://localhost:7000/issues/{id}", id = issue_id); let link = format!("http://localhost:7000/issues/{id}", id = issue_id);
let el = match seed::html_document().create_element("textarea") { let el = match seed::html_document().create_element("textarea") {
Ok(el) => el Ok(el) => el
.dyn_ref::<web_sys::HtmlTextAreaElement>() .dyn_ref::<web_sys::HtmlTextAreaElement>()
.unwrap() .unwrap()
.clone(), .clone(),
_ => return Msg::NoOp, _ => return None as Option<Msg>,
}; };
seed::body().append_child(&el).unwrap(); seed::body().append_child(&el).unwrap();
el.set_text_content(Some(link.as_str())); el.set_text_content(Some(link.as_str()));
@ -330,7 +370,10 @@ fn top_modal_row(_model: &Model, modal: &EditIssueModal) -> Node<Msg> {
el.set_selection_range(0, 9999).unwrap(); el.set_selection_range(0, 9999).unwrap();
seed::html_document().exec_command("copy").unwrap(); seed::html_document().exec_command("copy").unwrap();
seed::body().remove_child(&el).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 close_handler = mouse_ev(Ev::Click, |_| Msg::ModalDropped);
let delete_confirmation_handler = mouse_ev(Ev::Click, move |_| { let delete_confirmation_handler = mouse_ev(Ev::Click, move |_| {
@ -579,7 +622,7 @@ fn comment(model: &Model, modal: &EditIssueModal, comment: &Comment) -> Option<N
div![ div![
class!["content"], class!["content"],
div![class!["userName"], user.name.as_str()], div![class!["userName"], user.name.as_str()],
p![class!["body"], comment.body], p![class!["body"], comment.body.as_str()],
buttons, buttons,
] ]
}; };

View File

@ -2,12 +2,12 @@ use seed::{prelude::*, *};
use jirs_data::{TimeTracking, WsMsg}; use jirs_data::{TimeTracking, WsMsg};
use crate::api::send_ws_msg;
use crate::model::{AddIssueModal, EditIssueModal, ModalType, Model, Page}; use crate::model::{AddIssueModal, EditIssueModal, ModalType, Model, Page};
use crate::shared::styled_confirm_modal::StyledConfirmModal; use crate::shared::styled_confirm_modal::StyledConfirmModal;
use crate::shared::styled_modal::{StyledModal, Variant as ModalVariant}; use crate::shared::styled_modal::{StyledModal, Variant as ModalVariant};
use crate::shared::{find_issue, ToNode}; use crate::shared::{find_issue, go_to_board, ToNode};
use crate::{model, FieldChange, FieldId, Msg}; use crate::ws::send_ws_msg;
use crate::{model, FieldChange, FieldId, Msg, WebSocketChanged};
mod add_issue; mod add_issue;
mod confirm_delete_issue; mod confirm_delete_issue;
@ -19,7 +19,7 @@ pub fn update(msg: &Msg, model: &mut model::Model, orders: &mut impl Orders<Msg>
match msg { match msg {
Msg::ModalDropped => match model.modals.pop() { Msg::ModalDropped => match model.modals.pop() {
Some(ModalType::EditIssue(..)) | Some(ModalType::AddIssue(..)) => { Some(ModalType::EditIssue(..)) | Some(ModalType::AddIssue(..)) => {
seed::push_route(vec!["board"]); go_to_board();
orders.send_msg(Msg::ChangePage(Page::Project)); orders.send_msg(Msg::ChangePage(Page::Project));
} }
_ => (), _ => (),
@ -37,12 +37,14 @@ pub fn update(msg: &Msg, model: &mut model::Model, orders: &mut impl Orders<Msg>
model.modals.push(modal_type.as_ref().clone()); model.modals.push(modal_type.as_ref().clone());
} }
Msg::WsMsg(jirs_data::WsMsg::ProjectIssuesLoaded(_issues)) => match model.page { Msg::WebSocketChange(WebSocketChanged::WsMsg(WsMsg::ProjectIssuesLoaded(_issues))) => {
match model.page {
Page::EditIssue(issue_id) if model.modals.is_empty() => { Page::EditIssue(issue_id) if model.modals.is_empty() => {
push_edit_modal(issue_id, model) push_edit_modal(issue_id, model)
} }
_ => (), _ => (),
}, }
}
Msg::ChangePage(Page::EditIssue(issue_id)) => { Msg::ChangePage(Page::EditIssue(issue_id)) => {
push_edit_modal(*issue_id, model); 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)), 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); model.modals.push(modal);
} }

View File

@ -12,7 +12,8 @@ use crate::shared::styled_editor::Mode;
use crate::shared::styled_image_input::StyledImageInputState; use crate::shared::styled_image_input::StyledImageInputState;
use crate::shared::styled_input::StyledInputState; use crate::shared::styled_input::StyledInputState;
use crate::shared::styled_select::StyledSelectState; 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)] #[derive(Clone, Debug, PartialOrd, PartialEq)]
pub enum ModalType { pub enum ModalType {
@ -269,6 +270,7 @@ pub struct ProjectSettingsPage {
pub time_tracking: StyledCheckboxState, pub time_tracking: StyledCheckboxState,
pub column_drag: DragState, pub column_drag: DragState,
pub edit_column_id: Option<IssueStatusId>, pub edit_column_id: Option<IssueStatusId>,
pub creating_issue_status: bool,
pub name: StyledInputState, pub name: StyledInputState,
} }
@ -304,6 +306,7 @@ impl ProjectSettingsPage {
), ),
column_drag: Default::default(), column_drag: Default::default(),
edit_column_id: None, edit_column_id: None,
creating_issue_status: false,
name: StyledInputState::new( name: StyledInputState::new(
FieldId::ProjectSettings(ProjectFieldId::IssueStatusName), FieldId::ProjectSettings(ProjectFieldId::IssueStatusName),
"", "",
@ -424,7 +427,9 @@ pub enum PageContent {
#[derive(Debug)] #[derive(Debug)]
pub struct Model { pub struct Model {
pub ws: Option<WebSocket>,
pub host_url: String, pub host_url: String,
pub ws_url: String,
pub access_token: Option<Uuid>, pub access_token: Option<Uuid>,
pub about_tooltip_visible: bool, pub about_tooltip_visible: bool,
@ -451,10 +456,10 @@ pub struct Model {
pub issue_statuses: Vec<IssueStatus>, pub issue_statuses: Vec<IssueStatus>,
} }
impl Default for Model { impl Model {
fn default() -> Self { pub fn new(host_url: String, ws_url: String) -> Self {
let host_url = unsafe { HOST_URL.clone() };
Self { Self {
ws: None,
access_token: None, access_token: None,
user: None, user: None,
issue_form: None, issue_form: None,
@ -465,6 +470,7 @@ impl Default for Model {
comments_by_project_id: Default::default(), comments_by_project_id: Default::default(),
page: Page::Project, page: Page::Project,
host_url, host_url,
ws_url,
page_content: PageContent::Project(Box::new(ProjectPage::default())), page_content: PageContent::Project(Box::new(ProjectPage::default())),
modals: vec![], modals: vec![],
project: None, project: None,

View File

@ -3,7 +3,6 @@ use web_sys::FormData;
use jirs_data::*; use jirs_data::*;
use crate::api::send_ws_msg;
use crate::model::{Model, Page, PageContent, ProfilePage}; use crate::model::{Model, Page, PageContent, ProfilePage};
use crate::shared::styled_button::StyledButton; use crate::shared::styled_button::StyledButton;
use crate::shared::styled_field::StyledField; 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_image_input::StyledImageInput;
use crate::shared::styled_input::StyledInput; use crate::shared::styled_input::StyledInput;
use crate::shared::{inner_layout, ToNode}; 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<Msg>) { pub fn update(msg: Msg, model: &mut crate::model::Model, orders: &mut impl Orders<Msg>) {
let user = match model.user { 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 { match msg {
Msg::WsMsg(WsMsg::AuthorizeLoaded(..)) | Msg::ChangePage(Page::Profile) => { Msg::WebSocketChange(WebSocketChanged::WsMsg(WsMsg::AuthorizeLoaded(..)))
send_ws_msg(WsMsg::ProjectRequest); | Msg::ChangePage(Page::Profile) => {
send_ws_msg(WsMsg::ProjectRequest, model.ws.as_ref());
model.page_content = PageContent::Profile(Box::new(ProfilePage::new(user))); 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()) fd.set_with_str("token", format!("{}", token).as_str())
.unwrap(); .unwrap();
fd.set_with_blob("avatar", file).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(); 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 let Some(me) = model.user.as_mut() {
if me.id == user_id { if me.id == user_id {
profile_page.avatar.url = Some(avatar_url.clone()); 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)) => { Msg::PageChanged(PageChanged::Profile(ProfilePageChange::SubmitForm)) => {
send_ws_msg(WsMsg::ProfileUpdate( send_ws_msg(
WsMsg::ProfileUpdate(
profile_page.email.value.clone(), profile_page.email.value.clone(),
profile_page.name.value.clone(), profile_page.name.value.clone(),
)) ),
model.ws.as_ref(),
);
} }
_ => (), _ => (),
} }
@ -132,12 +139,17 @@ pub fn view(model: &Model) -> Node<Msg> {
inner_layout(model, "profile", vec![content], empty![]) inner_layout(model, "profile", vec![content], empty![])
} }
async fn update_avatar(data: FormData) -> Result<Msg, Msg> { async fn update_avatar(data: FormData, host_url: String) -> Option<Msg> {
let host_url = unsafe { HOST_URL.clone() };
let path = format!("{}/avatar/", host_url); let path = format!("{}/avatar/", host_url);
Request::new(path) let result = Request::new(path)
.method(Method::Post) .method(Method::Post)
.body(data.into()) .body(data.into())
.fetch_string(Msg::AvatarUpdateFetched) .fetch()
.await .await;
let response = match result {
Ok(r) => r,
Err(_) => return None,
};
let text = response.text().await.ok()?;
Some(Msg::AvatarUpdateFetched(text))
} }

View File

@ -3,15 +3,15 @@ use seed::{prelude::*, *};
use jirs_data::*; use jirs_data::*;
use crate::api::send_ws_msg;
use crate::model::{ModalType, Model, Page, PageContent, ProjectPage}; use crate::model::{ModalType, Model, Page, PageContent, ProjectPage};
use crate::shared::styled_avatar::StyledAvatar; use crate::shared::styled_avatar::StyledAvatar;
use crate::shared::styled_button::StyledButton; use crate::shared::styled_button::StyledButton;
use crate::shared::styled_icon::{Icon, StyledIcon}; use crate::shared::styled_icon::{Icon, StyledIcon};
use crate::shared::styled_input::StyledInput; use crate::shared::styled_input::StyledInput;
use crate::shared::styled_select::StyledSelectChange; use crate::shared::styled_select::StyledSelectChange;
use crate::shared::{drag_ev, inner_layout, ToNode}; use crate::shared::{inner_layout, ToNode};
use crate::{BoardPageChange, EditIssueModalSection, FieldId, Msg, PageChanged}; 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<Msg>) { pub fn update(msg: Msg, model: &mut crate::model::Model, orders: &mut impl Orders<Msg>) {
if model.user.is_none() { 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 { match msg {
Msg::WsMsg(WsMsg::AuthorizeLoaded(..)) Msg::WebSocketChange(WebSocketChanged::WsMsg(WsMsg::AuthorizeLoaded(..)))
| Msg::ChangePage(Page::Project) | Msg::ChangePage(Page::Project)
| Msg::ChangePage(Page::AddIssue) | Msg::ChangePage(Page::AddIssue)
| Msg::ChangePage(Page::EditIssue(..)) => { | Msg::ChangePage(Page::EditIssue(..)) => {
send_ws_msg(jirs_data::WsMsg::ProjectRequest); send_ws_msg(jirs_data::WsMsg::ProjectRequest, model.ws.as_ref());
send_ws_msg(jirs_data::WsMsg::ProjectIssuesRequest); send_ws_msg(jirs_data::WsMsg::ProjectIssuesRequest, model.ws.as_ref());
send_ws_msg(jirs_data::WsMsg::ProjectUsersRequest); send_ws_msg(jirs_data::WsMsg::ProjectUsersRequest, model.ws.as_ref());
send_ws_msg(jirs_data::WsMsg::IssueStatusesRequest); 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<Issue> = vec![]; let mut old: Vec<Issue> = vec![];
std::mem::swap(&mut old, &mut model.issues); std::mem::swap(&mut old, &mut model.issues);
for is in old { 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<Issue> = vec![]; let mut old: Vec<Issue> = vec![];
std::mem::swap(&mut old, &mut model.issues); std::mem::swap(&mut old, &mut model.issues);
for is in old { 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(); project_page.issue_drag.clear_last();
} }
Msg::DeleteIssue(issue_id) => { 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<Ms
let send_status = status.id; let send_status = status.id;
let drop_handler = drag_ev(Ev::Drop, move |ev| { let drop_handler = drag_ev(Ev::Drop, move |ev| {
ev.prevent_default(); ev.prevent_default();
Msg::PageChanged(PageChanged::Board(BoardPageChange::IssueDropZone( Some(Msg::PageChanged(PageChanged::Board(
send_status, BoardPageChange::IssueDropZone(send_status),
))) )))
}); });
let send_status = status.id; let send_status = status.id;
let drag_over_handler = drag_ev(Ev::DragOver, move |ev| { let drag_over_handler = drag_ev(Ev::DragOver, move |ev| {
ev.prevent_default(); ev.prevent_default();
Msg::PageChanged(PageChanged::Board(BoardPageChange::IssueDragOverStatus( Some(Msg::PageChanged(PageChanged::Board(
send_status, BoardPageChange::IssueDragOverStatus(send_status),
))) )))
}); });
@ -395,25 +398,27 @@ fn project_issue(model: &Model, issue: &Issue) -> Node<Msg> {
let issue_id = issue.id; let issue_id = issue.id;
let drag_started = drag_ev(Ev::DragStart, move |_| { let drag_started = drag_ev(Ev::DragStart, move |_| {
Msg::PageChanged(PageChanged::Board(BoardPageChange::IssueDragStarted( Some(Msg::PageChanged(PageChanged::Board(
issue_id, BoardPageChange::IssueDragStarted(issue_id),
))) )))
}); });
let drag_stopped = drag_ev(Ev::DragEnd, move |_| { let drag_stopped = drag_ev(Ev::DragEnd, move |_| {
Msg::PageChanged(PageChanged::Board(BoardPageChange::IssueDragStopped( Some(Msg::PageChanged(PageChanged::Board(
issue_id, BoardPageChange::IssueDragStopped(issue_id),
))) )))
}); });
let drag_over_handler = drag_ev(Ev::DragOver, move |ev| { let drag_over_handler = drag_ev(Ev::DragOver, move |ev| {
ev.prevent_default(); ev.prevent_default();
ev.stop_propagation(); ev.stop_propagation();
Msg::PageChanged(PageChanged::Board(BoardPageChange::ExchangePosition( Some(Msg::PageChanged(PageChanged::Board(
issue_id, BoardPageChange::ExchangePosition(issue_id),
))) )))
}); });
let issue_id = issue.id; let issue_id = issue.id;
let drag_out = drag_ev(Ev::DragLeave, move |_| { 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"]; let class_list = vec!["issue"];
@ -428,7 +433,7 @@ fn project_issue(model: &Model, issue: &Issue) -> Node<Msg> {
drag_stopped, drag_stopped,
drag_over_handler, drag_over_handler,
drag_out, drag_out,
p![attrs![At::Class => "title"], issue.title,], p![attrs![At::Class => "title"], issue.title.as_str()],
div![ div![
attrs![At::Class => "bottom"], attrs![At::Class => "bottom"],
div![ div![

View File

@ -5,7 +5,6 @@ use jirs_data::{
IssueStatus, IssueStatusId, ProjectCategory, TimeTracking, ToVec, UpdateProjectPayload, WsMsg, IssueStatus, IssueStatusId, ProjectCategory, TimeTracking, ToVec, UpdateProjectPayload, WsMsg,
}; };
use crate::api::send_ws_msg;
use crate::model::{ use crate::model::{
DeleteIssueStatusModal, ModalType, Model, Page, PageContent, ProjectSettingsPage, 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_input::StyledInput;
use crate::shared::styled_select::{StyledSelect, StyledSelectChange}; use crate::shared::styled_select::{StyledSelect, StyledSelectChange};
use crate::shared::styled_textarea::StyledTextarea; 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::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 theyre always being watched."; 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 theyre always being watched.";
static TIME_TRACKING_HOURLY: &'static str = "Employees may feel intimidated by demands to track their time. Or they could feel that theyre constantly being watched and evaluated. And for overly ambitious managers, employee time tracking may open the doors to excessive micromanaging."; static TIME_TRACKING_HOURLY: &'static str = "Employees may feel intimidated by demands to track their time. Or they could feel that theyre 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<Msg>)
} }
match msg { match msg {
Msg::WsMsg(WsMsg::AuthorizeLoaded(..)) => { Msg::WebSocketChange(ref change) => match change {
send_ws_msg(WsMsg::ProjectRequest); WebSocketChanged::WsMsg(WsMsg::AuthorizeLoaded(..)) => {
send_ws_msg(WsMsg::IssueStatusesRequest); send_ws_msg(WsMsg::ProjectRequest, model.ws.as_ref());
send_ws_msg(WsMsg::ProjectIssuesRequest); 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) => { Msg::ChangePage(Page::ProjectSettings) => {
build_page_content(model); build_page_content(model);
if model.user.is_some() { if model.user.is_some() {
send_ws_msg(WsMsg::ProjectRequest); send_ws_msg(WsMsg::ProjectRequest, model.ws.as_ref());
send_ws_msg(WsMsg::IssueStatusesRequest); send_ws_msg(WsMsg::IssueStatusesRequest, model.ws.as_ref());
send_ws_msg(WsMsg::ProjectIssuesRequest); 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<Msg>)
page.description_mode = mode; page.description_mode = mode;
} }
Msg::PageChanged(PageChanged::ProjectSettings(ProjectPageChange::SubmitForm)) => { Msg::PageChanged(PageChanged::ProjectSettings(ProjectPageChange::SubmitForm)) => {
send_ws_msg(WsMsg::ProjectUpdateRequest(UpdateProjectPayload { send_ws_msg(
WsMsg::ProjectUpdateRequest(UpdateProjectPayload {
id: page.payload.id, id: page.payload.id,
name: page.payload.name.clone(), name: page.payload.name.clone(),
url: page.payload.url.clone(), url: page.payload.url.clone(),
description: page.payload.description.clone(), description: page.payload.description.clone(),
category: page.payload.category.clone(), category: page.payload.category.clone(),
time_tracking: Some(page.time_tracking.value.into()), time_tracking: Some(page.time_tracking.value.into()),
})); }),
model.ws.as_ref(),
);
} }
Msg::PageChanged(PageChanged::ProjectSettings(ProjectPageChange::ColumnDragStarted( Msg::PageChanged(PageChanged::ProjectSettings(ProjectPageChange::ColumnDragStarted(
issue_status_id, issue_status_id,
@ -128,7 +136,10 @@ pub fn update(msg: Msg, model: &mut model::Model, orders: &mut impl Orders<Msg>)
.find(|is| Some(is.id) == old_id) .find(|is| Some(is.id) == old_id)
.map(|is| (is.id, is.position)) .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 page.name.value = model
@ -259,91 +270,15 @@ pub fn view(model: &model::Model) -> Node<Msg> {
let columns: Vec<Node<Msg>> = model let columns: Vec<Node<Msg>> = model
.issue_statuses .issue_statuses
.iter() .iter()
.map(|is| { .map(|is| column_preview(is, page, &per_column_issue_count, column_style.as_str()))
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,
]
}
})
.collect(); .collect();
let add_column = StyledIcon::build(Icon::Plus).build().into_node();
let columns_section = section![ let columns_section = section![
class!["columnsSection"], class!["columnsSection"],
div![ div![
class!["columns"], class!["columns"],
columns, columns,
div![ add_column(page, column_style.as_str())
class!["columnPreview"],
attrs![At::Style => column_style.as_str()],
div![class!["columnName addColumn"], add_column]
]
] ]
]; ];
let columns_field = StyledField::build() let columns_field = StyledField::build()
@ -451,7 +386,10 @@ fn sync(model: &mut Model) {
Some(is) => is, Some(is) => is,
_ => continue, _ => 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))); model.page_content = PageContent::ProjectSettings(Box::new(ProjectSettingsPage::new(project)));
} }
fn add_column(page: &ProjectSettingsPage, column_style: &str) -> Node<Msg> {
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<i32, i32>,
column_style: &str,
) -> Node<Msg> {
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<i32, i32>,
column_style: &str,
) -> Node<Msg> {
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,
]
}

View File

@ -15,7 +15,7 @@ pub fn render(model: &Model) -> Node<Msg> {
project_icon, project_icon,
div![ div![
class!["projectTexts"], class!["projectTexts"],
div![class!["projectName"], project.name], div![class!["projectName"], project.name.as_str()],
div![class!["projectCategory"], project.category.to_string()] div![class!["projectCategory"], project.category.to_string()]
], ],
], ],

View File

@ -1,5 +1,5 @@
use seed::{prelude::*, *}; use seed::{prelude::*, *};
use wasm_bindgen::JsCast; use std::str::FromStr;
use jirs_data::*; use jirs_data::*;
@ -33,6 +33,18 @@ pub trait ToChild {
fn to_child(&self) -> Self::Builder; 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> { pub fn find_issue(model: &Model, issue_id: IssueId) -> Option<&Issue> {
model.issues.iter().find(|issue| issue.id == issue_id) model.issues.iter().find(|issue| issue.id == issue_id)
} }
@ -107,13 +119,3 @@ pub fn read_auth_token() -> Result<uuid::Uuid, String> {
.parse() .parse()
.map_err(|_| "Bad token format".to_string()) .map_err(|_| "Bad token format".to_string())
} }
pub fn drag_ev<Ms>(
trigger: impl Into<Ev>,
handler: impl FnOnce(web_sys::DragEvent) -> Ms + 'static + Clone,
) -> EventHandler<Ms> {
let closure_handler = move |event: web_sys::Event| {
(handler.clone())(event.dyn_ref::<web_sys::DragEvent>().unwrap().clone())
};
EventHandler::new(trigger, closure_handler)
}

View File

@ -102,11 +102,10 @@ impl ToNode for ChildBuilder {
} = self; } = self;
let id = field_id.as_ref().map(|f| f.to_string()).unwrap_or_default(); let id = field_id.as_ref().map(|f| f.to_string()).unwrap_or_default();
let handler = if let Some(field_id) = field_id { let field_id_clone = field_id.clone();
mouse_ev(Ev::Click, move |_| Msg::U32InputChanged(field_id, value)) let handler: EventHandler<Msg> = mouse_ev(Ev::Click, move |_| {
} else { field_id_clone.map(|field_id| Msg::U32InputChanged(field_id, value))
ev(Ev::FullScreenError, move |_| Msg::NoOp) });
};
class_list.push("styledCheckboxChild".to_string()); class_list.push("styledCheckboxChild".to_string());
class_list.push(if selected { "selected" } else { "" }.to_string()); class_list.push(if selected { "selected" } else { "" }.to_string());

View File

@ -222,11 +222,11 @@ pub fn render(values: StyledInput) -> Node<Msg> {
})); }));
input_handlers.push(ev(Ev::KeyUp, move |event| { input_handlers.push(ev(Ev::KeyUp, move |event| {
event.stop_propagation(); event.stop_propagation();
Msg::NoOp None as Option<Msg>
})); }));
input_handlers.push(ev(Ev::Click, move |event| { input_handlers.push(ev(Ev::Click, move |event| {
event.stop_propagation(); event.stop_propagation();
Msg::NoOp None as Option<Msg>
})); }));
div![ div![

View File

@ -122,7 +122,7 @@ pub fn render(values: StyledModal) -> Node<Msg> {
let close_handler = mouse_ev(Ev::Click, |_| Msg::ModalDropped); let close_handler = mouse_ev(Ev::Click, |_| Msg::ModalDropped);
let body_handler = mouse_ev(Ev::Click, |ev| { let body_handler = mouse_ev(Ev::Click, |ev| {
ev.stop_propagation(); ev.stop_propagation();
Msg::NoOp None as Option<Msg>
}); });
let clickable_class = format!("clickableOverlay {}", variant.to_class_name()); let clickable_class = format!("clickableOverlay {}", variant.to_class_name());

View File

@ -343,7 +343,7 @@ pub fn render(values: StyledSelect) -> Node<Msg> {
attrs![At::Class => select_class.join(" "), At::Style => dropdown_style.as_str()], attrs![At::Class => select_class.join(" "), At::Style => dropdown_style.as_str()],
keyboard_ev(Ev::KeyUp, |ev| { keyboard_ev(Ev::KeyUp, |ev| {
ev.stop_propagation(); ev.stop_propagation();
Msg::NoOp None as Option<Msg>
}), }),
div![ div![
attrs![At::Class => format!("valueContainer {}", variant)], attrs![At::Class => format!("valueContainer {}", variant)],

View File

@ -161,7 +161,7 @@ pub fn render(values: StyledTextarea) -> Node<Msg> {
let resize_handler = ev(Ev::KeyUp, move |event| { let resize_handler = ev(Ev::KeyUp, move |event| {
event.stop_propagation(); event.stop_propagation();
if handler_disable_auto_resize { if handler_disable_auto_resize {
return Msg::NoOp; return None as Option<Msg>;
} }
let target = event.target().unwrap(); let target = event.target().unwrap();
@ -172,7 +172,7 @@ pub fn render(values: StyledTextarea) -> Node<Msg> {
textarea textarea
.style() .style()
.set_css_text(format!("height: {min_height}px", min_height = min_height).as_str()); .set_css_text(format!("height: {min_height}px", min_height = min_height).as_str());
Msg::NoOp None as Option<Msg>
}); });
let handler_disable_auto_resize = disable_auto_resize; let handler_disable_auto_resize = disable_auto_resize;

View File

@ -5,7 +5,6 @@ use uuid::Uuid;
use jirs_data::WsMsg; use jirs_data::WsMsg;
use crate::api::send_ws_msg;
use crate::model::{Page, PageContent, SignInPage}; use crate::model::{Page, PageContent, SignInPage};
use crate::shared::styled_button::StyledButton; use crate::shared::styled_button::StyledButton;
use crate::shared::styled_field::StyledField; 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::styled_link::StyledLink;
use crate::shared::{outer_layout, write_auth_token, ToNode}; use crate::shared::{outer_layout, write_auth_token, ToNode};
use crate::validations::{is_email, is_token}; 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<Msg>) { pub fn update(msg: Msg, model: &mut model::Model, orders: &mut impl Orders<Msg>) {
if model.page != Page::SignIn { if model.page != Page::SignIn {
@ -49,10 +49,10 @@ pub fn update(msg: Msg, model: &mut model::Model, orders: &mut impl Orders<Msg>)
page.token_touched = true; page.token_touched = true;
} }
Msg::SignInRequest => { Msg::SignInRequest => {
send_ws_msg(WsMsg::AuthenticateRequest( send_ws_msg(
page.email.clone(), WsMsg::AuthenticateRequest(page.email.clone(), page.username.clone()),
page.username.clone(), model.ws.as_ref(),
)); );
} }
Msg::BindClientRequest => { Msg::BindClientRequest => {
let bind_token: uuid::Uuid = match Uuid::from_str(page.token.as_str()) { let bind_token: uuid::Uuid = match Uuid::from_str(page.token.as_str()) {
@ -62,12 +62,13 @@ pub fn update(msg: Msg, model: &mut model::Model, orders: &mut impl Orders<Msg>)
return; return;
} }
}; };
send_ws_msg(WsMsg::BindTokenCheck(bind_token)); send_ws_msg(WsMsg::BindTokenCheck(bind_token), model.ws.as_ref());
} }
Msg::WsMsg(WsMsg::AuthenticateSuccess) => { Msg::WebSocketChange(change) => match change {
WebSocketChanged::WsMsg(WsMsg::AuthenticateSuccess) => {
page.login_success = true; page.login_success = true;
} }
Msg::WsMsg(WsMsg::BindTokenOk(access_token)) => { WebSocketChanged::WsMsg(WsMsg::BindTokenOk(access_token)) => {
match write_auth_token(Some(access_token)) { match write_auth_token(Some(access_token)) {
Ok(msg) => { Ok(msg) => {
orders.skip().send_msg(msg); orders.skip().send_msg(msg);
@ -78,6 +79,8 @@ pub fn update(msg: Msg, model: &mut model::Model, orders: &mut impl Orders<Msg>)
} }
} }
_ => (), _ => (),
},
_ => (),
}; };
} }

View File

@ -2,7 +2,6 @@ use seed::{prelude::*, *};
use jirs_data::{SignUpFieldId, WsMsg}; use jirs_data::{SignUpFieldId, WsMsg};
use crate::api::send_ws_msg;
use crate::model::{Page, PageContent, SignUpPage}; use crate::model::{Page, PageContent, SignUpPage};
use crate::shared::styled_button::StyledButton; use crate::shared::styled_button::StyledButton;
use crate::shared::styled_field::StyledField; 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::styled_link::StyledLink;
use crate::shared::{outer_layout, ToNode}; use crate::shared::{outer_layout, ToNode};
use crate::validations::is_email; 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<Msg>) { pub fn update(msg: Msg, model: &mut model::Model, _orders: &mut impl Orders<Msg>) {
if model.page != Page::SignUp { if model.page != Page::SignUp {
@ -42,18 +42,21 @@ pub fn update(msg: Msg, model: &mut model::Model, _orders: &mut impl Orders<Msg>
page.email_touched = true; page.email_touched = true;
} }
Msg::SignUpRequest => { Msg::SignUpRequest => {
send_ws_msg(WsMsg::SignUpRequest( send_ws_msg(
page.email.clone(), WsMsg::SignUpRequest(page.email.clone(), page.username.clone()),
page.username.clone(), model.ws.as_ref(),
)); );
} }
Msg::WsMsg(WsMsg::SignUpSuccess) => { Msg::WebSocketChange(change) => match change {
WebSocketChanged::WsMsg(WsMsg::SignUpSuccess) => {
page.sign_up_success = true; page.sign_up_success = true;
} }
Msg::WsMsg(WsMsg::SignUpPairTaken) => { WebSocketChanged::WsMsg(WsMsg::SignUpPairTaken) => {
page.error = "Pair you give is either taken or is not matching".to_string(); 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<Msg> {
let error_row = if page.error.is_empty() { let error_row = if page.error.is_empty() {
empty![] empty![]
} else { } else {
div![class!["error"], p![page.error]] div![class!["error"], p![page.error.as_str()]]
}; };
let sign_up_form = StyledForm::build() let sign_up_form = StyledForm::build()

View File

@ -2,7 +2,6 @@ use seed::{prelude::*, *};
use jirs_data::{InvitationState, ToVec, UserRole, UsersFieldId, WsMsg}; use jirs_data::{InvitationState, ToVec, UserRole, UsersFieldId, WsMsg};
use crate::api::send_ws_msg;
use crate::model::*; use crate::model::*;
use crate::shared::styled_button::StyledButton; use crate::shared::styled_button::StyledButton;
use crate::shared::styled_field::StyledField; use crate::shared::styled_field::StyledField;
@ -11,7 +10,8 @@ use crate::shared::styled_input::StyledInput;
use crate::shared::styled_select::*; use crate::shared::styled_select::*;
use crate::shared::{inner_layout, ToChild, ToNode}; use crate::shared::{inner_layout, ToChild, ToNode};
use crate::validations::is_email; 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<Msg>) { pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
if let Msg::ChangePage(Page::Users) = msg { if let Msg::ChangePage(Page::Users) = msg {
@ -27,18 +27,50 @@ pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
page.user_role_state.update(&msg, orders); page.user_role_state.update(&msg, orders);
match msg { match msg {
Msg::WsMsg(WsMsg::AuthorizeLoaded(Ok(_))) | Msg::ChangePage(Page::Users) Msg::ChangePage(Page::Users) if model.user.is_some() => {
if model.user.is_some() => send_ws_msg(WsMsg::InvitationListRequest, model.ws.as_ref());
{ send_ws_msg(WsMsg::InvitedUsersRequest, model.ws.as_ref());
send_ws_msg(WsMsg::InvitationListRequest);
send_ws_msg(WsMsg::InvitedUsersRequest);
} }
Msg::WsMsg(WsMsg::InvitedUsersLoaded(users)) => { 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; page.invited_users = users;
} }
Msg::WsMsg(WsMsg::InvitationListLoaded(invitations)) => { WebSocketChanged::WsMsg(WsMsg::InvitationListLoaded(invitations)) => {
page.invitations = 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)) => { Msg::PageChanged(PageChanged::Users(UsersPageChange::ResetForm)) => {
page.name.clear(); page.name.clear();
page.name_touched = false; page.name_touched = false;
@ -64,43 +96,22 @@ pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
} }
Msg::InviteRequest => { Msg::InviteRequest => {
page.form_state = InvitationFormState::Sent; page.form_state = InvitationFormState::Sent;
send_ws_msg(WsMsg::InvitationSendRequest { send_ws_msg(
WsMsg::InvitationSendRequest {
name: page.name.clone(), name: page.name.clone(),
email: page.email.clone(), email: page.email.clone(),
}) },
} model.ws.as_ref(),
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);
} }
Msg::InviteRevokeRequest(invitation_id) => { 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) => { Msg::InvitedUserRemove(email) => {
send_ws_msg(WsMsg::InvitedUserRemoveRequest(email)); send_ws_msg(WsMsg::InvitedUserRemoveRequest(email), model.ws.as_ref());
}
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;
} }
_ => (), _ => (),
} }
@ -210,8 +221,8 @@ pub fn view(model: &Model) -> Node<Msg> {
.into_node(); .into_node();
li![ li![
class!["user"], class!["user"],
span![user.name], span![user.name.as_str()],
span![user.email], span![user.email.as_str()],
span![format!("{}", user.user_role)], span![format!("{}", user.user_role)],
remove, remove,
] ]
@ -238,8 +249,8 @@ pub fn view(model: &Model) -> Node<Msg> {
li![ li![
class!["invitation"], class!["invitation"],
attrs![At::Class => format!("{}", invitation.state)], attrs![At::Class => format!("{}", invitation.state)],
span![invitation.name], span![invitation.name.as_str()],
span![invitation.email], span![invitation.email.as_str()],
span![format!("{}", invitation.state)], span![format!("{}", invitation.state)],
revoke, revoke,
] ]

View File

@ -2,8 +2,8 @@ use seed::*;
use jirs_data::*; use jirs_data::*;
use crate::api::send_ws_msg;
use crate::model::{Model, PageContent}; use crate::model::{Model, PageContent};
use crate::ws::send_ws_msg;
pub fn drag_started(issue_id: IssueId, model: &mut Model) { pub fn drag_started(issue_id: IssueId, model: &mut Model) {
let project_page = match &mut model.page_content { let project_page = match &mut model.page_content {
@ -90,16 +90,22 @@ pub fn sync(model: &mut Model) {
continue; continue;
} }
send_ws_msg(WsMsg::IssueUpdateRequest( send_ws_msg(
WsMsg::IssueUpdateRequest(
issue.id, issue.id,
IssueFieldId::IssueStatusId, IssueFieldId::IssueStatusId,
PayloadVariant::I32(issue.issue_status_id), PayloadVariant::I32(issue.issue_status_id),
)); ),
send_ws_msg(WsMsg::IssueUpdateRequest( model.ws.as_ref(),
);
send_ws_msg(
WsMsg::IssueUpdateRequest(
issue.id, issue.id,
IssueFieldId::ListPosition, IssueFieldId::ListPosition,
PayloadVariant::I32(issue.list_position), PayloadVariant::I32(issue.list_position),
)); ),
model.ws.as_ref(),
);
} }
project_page.issue_drag.clear(); project_page.issue_drag.clear();
} }

View File

@ -4,57 +4,75 @@ use jirs_data::WsMsg;
use crate::model::*; use crate::model::*;
use crate::shared::write_auth_token; use crate::shared::write_auth_token;
use crate::{Msg, APP}; use crate::{Msg, WebSocketChanged};
pub mod issue; pub mod issue;
pub fn handle(msg: WsMsg) { pub fn send_ws_msg(msg: WsMsg, ws: Option<&WebSocket>) {
let app = match unsafe { APP.as_mut().unwrap() }.write() { let ws = match ws {
Ok(app) => app, Some(ws) => ws,
_ => return, _ => return,
}; };
let binary = bincode::serialize(&msg).unwrap();
match msg { ws.send_bytes(binary.as_slice())
WsMsg::Ping | WsMsg::Pong => {} .expect("Failed to send ws msg");
_ => app.update(Msg::WsMsg(msg)),
}
} }
pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders<Msg>) { pub fn open_socket(model: &mut Model, orders: &mut impl Orders<Msg>) {
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<Msg>) {
match msg { match msg {
// auth // auth
Msg::WsMsg(WsMsg::AuthorizeLoaded(Ok(user))) => { WsMsg::AuthorizeLoaded(Ok(user)) => {
model.user = Some(user.clone()); model.user = Some(user.clone());
} }
Msg::WsMsg(WsMsg::AuthorizeExpired) => { WsMsg::AuthorizeExpired => {
if let Ok(msg) = write_auth_token(None) { if let Ok(msg) = write_auth_token(None) {
orders.skip().send_msg(msg); orders.skip().send_msg(msg);
} }
} }
// project // project
Msg::WsMsg(WsMsg::ProjectLoaded(project)) => { WsMsg::ProjectLoaded(project) => {
model.project = Some(project.clone()); model.project = Some(project.clone());
} }
// issues // issues
Msg::WsMsg(WsMsg::ProjectIssuesLoaded(v)) => { WsMsg::ProjectIssuesLoaded(v) => {
let mut v = v.clone(); let mut v = v.clone();
v.sort_by(|a, b| (a.list_position as i64).cmp(&(b.list_position as i64))); v.sort_by(|a, b| (a.list_position as i64).cmp(&(b.list_position as i64)));
model.issues = v; model.issues = v;
} }
// issue statuses // issue statuses
Msg::WsMsg(WsMsg::IssueStatusesResponse(v)) => { WsMsg::IssueStatusesResponse(v) => {
model.issue_statuses = v.clone(); model.issue_statuses = v.clone();
model model
.issue_statuses .issue_statuses
.sort_by(|a, b| a.position.cmp(&b.position)); .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.push(is.clone());
model model
.issue_statuses .issue_statuses
.sort_by(|a, b| a.position.cmp(&b.position)); .sort_by(|a, b| a.position.cmp(&b.position));
} }
Msg::WsMsg(WsMsg::IssueStatusUpdated(changed)) => { WsMsg::IssueStatusUpdated(changed) => {
let mut old = vec![]; let mut old = vec![];
std::mem::swap(&mut model.issue_statuses, &mut old); std::mem::swap(&mut model.issue_statuses, &mut old);
for is in old { for is in old {
@ -68,7 +86,7 @@ pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
.issue_statuses .issue_statuses
.sort_by(|a, b| a.position.cmp(&b.position)); .sort_by(|a, b| a.position.cmp(&b.position));
} }
Msg::WsMsg(WsMsg::IssueDeleted(id)) => { WsMsg::IssueDeleted(id) => {
let mut old = vec![]; let mut old = vec![];
std::mem::swap(&mut model.issue_statuses, &mut old); std::mem::swap(&mut model.issue_statuses, &mut old);
for is in old { for is in old {
@ -82,11 +100,11 @@ pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
.sort_by(|a, b| a.position.cmp(&b.position)); .sort_by(|a, b| a.position.cmp(&b.position));
} }
// users // users
Msg::WsMsg(WsMsg::ProjectUsersLoaded(v)) => { WsMsg::ProjectUsersLoaded(v) => {
model.users = v.clone(); model.users = v.clone();
} }
// comments // comments
Msg::WsMsg(WsMsg::IssueCommentsLoaded(comments)) => { WsMsg::IssueCommentsLoaded(comments) => {
let issue_id = match model.modals.get(0) { let issue_id = match model.modals.get(0) {
Some(ModalType::EditIssue(issue_id, _)) => *issue_id, Some(ModalType::EditIssue(issue_id, _)) => *issue_id,
_ => return, _ => return,
@ -98,7 +116,7 @@ pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
v.sort_by(|a, b| a.updated_at.cmp(&b.updated_at)); v.sort_by(|a, b| a.updated_at.cmp(&b.updated_at));
model.comments = v; model.comments = v;
} }
Msg::WsMsg(WsMsg::CommentDeleted(comment_id)) => { WsMsg::CommentDeleted(comment_id) => {
let mut old = vec![]; let mut old = vec![];
std::mem::swap(&mut model.comments, &mut old); std::mem::swap(&mut model.comments, &mut old);
for comment in old.into_iter() { for comment in old.into_iter() {
@ -107,7 +125,7 @@ pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
} }
} }
} }
Msg::WsMsg(WsMsg::AvatarUrlChanged(user_id, avatar_url)) => { WsMsg::AvatarUrlChanged(user_id, avatar_url) => {
for user in model.users.iter_mut() { for user in model.users.iter_mut() {
if user.id == *user_id { if user.id == *user_id {
user.avatar_url = Some(avatar_url.clone()); user.avatar_url = Some(avatar_url.clone());

View File

@ -24,7 +24,7 @@ if (process.env.NODE_ENV === "production") {
execSync('cargo build --bin jirs-css', { execSync('cargo build --bin jirs-css', {
cwd: jirDir, cwd: jirDir,
}); });
const css = spawn('./target/debug/jirs-css', [ spawn('./target/debug/jirs-css', [
'-W', '-W',
'-O', '-O',
'./jirs-client/dev/styles.css' './jirs-client/dev/styles.css'
@ -70,7 +70,7 @@ module.exports = {
devServer: { devServer: {
contentBase: path.join(__dirname, 'dev'), contentBase: path.join(__dirname, 'dev'),
historyApiFallback: true, historyApiFallback: true,
hot: true, hot: false,
port: process.env.JIRS_CLIENT_PORT || 6000, port: process.env.JIRS_CLIENT_PORT || 6000,
host: process.env.JIRS_CLIENT_BIND || '0.0.0.0', host: process.env.JIRS_CLIENT_BIND || '0.0.0.0',
allowedHosts: [ allowedHosts: [

View File

@ -50,7 +50,6 @@ byteorder = "1.0"
chrono = { version = "0.4", features = [ "serde" ] } chrono = { version = "0.4", features = [ "serde" ] }
libc = { version = "0.2.0" } libc = { version = "0.2.0" }
pq-sys = { version = ">=0.3.0, <0.5.0" } pq-sys = { version = ">=0.3.0, <0.5.0" }
quickcheck = { version = "0.4" }
serde_json = { version = ">=0.8.0, <2.0" } serde_json = { version = ">=0.8.0, <2.0" }
toml = "0.5.6" toml = "0.5.6"
bincode = "1.2.1" bincode = "1.2.1"

View File

@ -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! { table! {
use diesel::sql_types::*; use diesel::sql_types::*;
use jirs_data::sql::*; 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! { table! {
use diesel::sql_types::*; use diesel::sql_types::*;
use jirs_data::sql::*; use jirs_data::sql::*;
@ -489,8 +489,8 @@ allow_tables_to_appear_in_same_query!(
comments, comments,
invitations, invitations,
issue_assignees, issue_assignees,
issue_statuses,
issues, issues,
issue_statuses,
projects, projects,
tokens, tokens,
users, users,