├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── example.mp4 ├── front ├── .gitignore ├── index.html ├── package.json ├── src │ ├── App.scss │ ├── App.tsx │ ├── Todo.ts │ ├── Websocket.ts │ ├── favicon.svg │ ├── index.css │ ├── main.tsx │ └── vite-env.d.ts ├── tsconfig.json ├── vite.config.ts └── yarn.lock └── src ├── main.rs ├── todo.rs └── websocket.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "async-trait" 7 | version = "0.1.63" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "eff18d764974428cf3a9328e23fc5c986f5fbed46e6cd4cdf42544df5d297ec1" 10 | dependencies = [ 11 | "proc-macro2", 12 | "quote", 13 | "syn", 14 | ] 15 | 16 | [[package]] 17 | name = "autocfg" 18 | version = "1.1.0" 19 | source = "registry+https://github.com/rust-lang/crates.io-index" 20 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 21 | 22 | [[package]] 23 | name = "axum" 24 | version = "0.6.4" 25 | source = "registry+https://github.com/rust-lang/crates.io-index" 26 | checksum = "e5694b64066a2459918d8074c2ce0d5a88f409431994c2356617c8ae0c4721fc" 27 | dependencies = [ 28 | "async-trait", 29 | "axum-core", 30 | "base64 0.20.0", 31 | "bitflags", 32 | "bytes", 33 | "futures-util", 34 | "http", 35 | "http-body", 36 | "hyper", 37 | "itoa", 38 | "matchit", 39 | "memchr", 40 | "mime", 41 | "percent-encoding", 42 | "pin-project-lite", 43 | "rustversion", 44 | "serde", 45 | "serde_json", 46 | "serde_path_to_error", 47 | "serde_urlencoded", 48 | "sha1", 49 | "sync_wrapper", 50 | "tokio", 51 | "tokio-tungstenite", 52 | "tower", 53 | "tower-http", 54 | "tower-layer", 55 | "tower-service", 56 | ] 57 | 58 | [[package]] 59 | name = "axum-core" 60 | version = "0.3.2" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | checksum = "1cae3e661676ffbacb30f1a824089a8c9150e71017f7e1e38f2aa32009188d34" 63 | dependencies = [ 64 | "async-trait", 65 | "bytes", 66 | "futures-util", 67 | "http", 68 | "http-body", 69 | "mime", 70 | "rustversion", 71 | "tower-layer", 72 | "tower-service", 73 | ] 74 | 75 | [[package]] 76 | name = "base64" 77 | version = "0.13.1" 78 | source = "registry+https://github.com/rust-lang/crates.io-index" 79 | checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" 80 | 81 | [[package]] 82 | name = "base64" 83 | version = "0.20.0" 84 | source = "registry+https://github.com/rust-lang/crates.io-index" 85 | checksum = "0ea22880d78093b0cbe17c89f64a7d457941e65759157ec6cb31a31d652b05e5" 86 | 87 | [[package]] 88 | name = "bitflags" 89 | version = "1.3.2" 90 | source = "registry+https://github.com/rust-lang/crates.io-index" 91 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 92 | 93 | [[package]] 94 | name = "block-buffer" 95 | version = "0.10.3" 96 | source = "registry+https://github.com/rust-lang/crates.io-index" 97 | checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" 98 | dependencies = [ 99 | "generic-array", 100 | ] 101 | 102 | [[package]] 103 | name = "byteorder" 104 | version = "1.4.3" 105 | source = "registry+https://github.com/rust-lang/crates.io-index" 106 | checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" 107 | 108 | [[package]] 109 | name = "bytes" 110 | version = "1.3.0" 111 | source = "registry+https://github.com/rust-lang/crates.io-index" 112 | checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c" 113 | 114 | [[package]] 115 | name = "cfg-if" 116 | version = "1.0.0" 117 | source = "registry+https://github.com/rust-lang/crates.io-index" 118 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 119 | 120 | [[package]] 121 | name = "cpufeatures" 122 | version = "0.2.5" 123 | source = "registry+https://github.com/rust-lang/crates.io-index" 124 | checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" 125 | dependencies = [ 126 | "libc", 127 | ] 128 | 129 | [[package]] 130 | name = "crypto-common" 131 | version = "0.1.6" 132 | source = "registry+https://github.com/rust-lang/crates.io-index" 133 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 134 | dependencies = [ 135 | "generic-array", 136 | "typenum", 137 | ] 138 | 139 | [[package]] 140 | name = "digest" 141 | version = "0.10.6" 142 | source = "registry+https://github.com/rust-lang/crates.io-index" 143 | checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" 144 | dependencies = [ 145 | "block-buffer", 146 | "crypto-common", 147 | ] 148 | 149 | [[package]] 150 | name = "fnv" 151 | version = "1.0.7" 152 | source = "registry+https://github.com/rust-lang/crates.io-index" 153 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 154 | 155 | [[package]] 156 | name = "form_urlencoded" 157 | version = "1.1.0" 158 | source = "registry+https://github.com/rust-lang/crates.io-index" 159 | checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" 160 | dependencies = [ 161 | "percent-encoding", 162 | ] 163 | 164 | [[package]] 165 | name = "futures" 166 | version = "0.3.25" 167 | source = "registry+https://github.com/rust-lang/crates.io-index" 168 | checksum = "38390104763dc37a5145a53c29c63c1290b5d316d6086ec32c293f6736051bb0" 169 | dependencies = [ 170 | "futures-channel", 171 | "futures-core", 172 | "futures-executor", 173 | "futures-io", 174 | "futures-sink", 175 | "futures-task", 176 | "futures-util", 177 | ] 178 | 179 | [[package]] 180 | name = "futures-channel" 181 | version = "0.3.25" 182 | source = "registry+https://github.com/rust-lang/crates.io-index" 183 | checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" 184 | dependencies = [ 185 | "futures-core", 186 | "futures-sink", 187 | ] 188 | 189 | [[package]] 190 | name = "futures-core" 191 | version = "0.3.25" 192 | source = "registry+https://github.com/rust-lang/crates.io-index" 193 | checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" 194 | 195 | [[package]] 196 | name = "futures-executor" 197 | version = "0.3.25" 198 | source = "registry+https://github.com/rust-lang/crates.io-index" 199 | checksum = "7acc85df6714c176ab5edf386123fafe217be88c0840ec11f199441134a074e2" 200 | dependencies = [ 201 | "futures-core", 202 | "futures-task", 203 | "futures-util", 204 | ] 205 | 206 | [[package]] 207 | name = "futures-io" 208 | version = "0.3.25" 209 | source = "registry+https://github.com/rust-lang/crates.io-index" 210 | checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" 211 | 212 | [[package]] 213 | name = "futures-macro" 214 | version = "0.3.25" 215 | source = "registry+https://github.com/rust-lang/crates.io-index" 216 | checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d" 217 | dependencies = [ 218 | "proc-macro2", 219 | "quote", 220 | "syn", 221 | ] 222 | 223 | [[package]] 224 | name = "futures-sink" 225 | version = "0.3.25" 226 | source = "registry+https://github.com/rust-lang/crates.io-index" 227 | checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9" 228 | 229 | [[package]] 230 | name = "futures-task" 231 | version = "0.3.25" 232 | source = "registry+https://github.com/rust-lang/crates.io-index" 233 | checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" 234 | 235 | [[package]] 236 | name = "futures-util" 237 | version = "0.3.25" 238 | source = "registry+https://github.com/rust-lang/crates.io-index" 239 | checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" 240 | dependencies = [ 241 | "futures-channel", 242 | "futures-core", 243 | "futures-io", 244 | "futures-macro", 245 | "futures-sink", 246 | "futures-task", 247 | "memchr", 248 | "pin-project-lite", 249 | "pin-utils", 250 | "slab", 251 | ] 252 | 253 | [[package]] 254 | name = "generic-array" 255 | version = "0.14.6" 256 | source = "registry+https://github.com/rust-lang/crates.io-index" 257 | checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" 258 | dependencies = [ 259 | "typenum", 260 | "version_check", 261 | ] 262 | 263 | [[package]] 264 | name = "getrandom" 265 | version = "0.2.8" 266 | source = "registry+https://github.com/rust-lang/crates.io-index" 267 | checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" 268 | dependencies = [ 269 | "cfg-if", 270 | "libc", 271 | "wasi", 272 | ] 273 | 274 | [[package]] 275 | name = "hermit-abi" 276 | version = "0.2.6" 277 | source = "registry+https://github.com/rust-lang/crates.io-index" 278 | checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" 279 | dependencies = [ 280 | "libc", 281 | ] 282 | 283 | [[package]] 284 | name = "http" 285 | version = "0.2.8" 286 | source = "registry+https://github.com/rust-lang/crates.io-index" 287 | checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" 288 | dependencies = [ 289 | "bytes", 290 | "fnv", 291 | "itoa", 292 | ] 293 | 294 | [[package]] 295 | name = "http-body" 296 | version = "0.4.5" 297 | source = "registry+https://github.com/rust-lang/crates.io-index" 298 | checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" 299 | dependencies = [ 300 | "bytes", 301 | "http", 302 | "pin-project-lite", 303 | ] 304 | 305 | [[package]] 306 | name = "http-range-header" 307 | version = "0.3.0" 308 | source = "registry+https://github.com/rust-lang/crates.io-index" 309 | checksum = "0bfe8eed0a9285ef776bb792479ea3834e8b94e13d615c2f66d03dd50a435a29" 310 | 311 | [[package]] 312 | name = "httparse" 313 | version = "1.8.0" 314 | source = "registry+https://github.com/rust-lang/crates.io-index" 315 | checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" 316 | 317 | [[package]] 318 | name = "httpdate" 319 | version = "1.0.2" 320 | source = "registry+https://github.com/rust-lang/crates.io-index" 321 | checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" 322 | 323 | [[package]] 324 | name = "hyper" 325 | version = "0.14.23" 326 | source = "registry+https://github.com/rust-lang/crates.io-index" 327 | checksum = "034711faac9d2166cb1baf1a2fb0b60b1f277f8492fd72176c17f3515e1abd3c" 328 | dependencies = [ 329 | "bytes", 330 | "futures-channel", 331 | "futures-core", 332 | "futures-util", 333 | "http", 334 | "http-body", 335 | "httparse", 336 | "httpdate", 337 | "itoa", 338 | "pin-project-lite", 339 | "socket2", 340 | "tokio", 341 | "tower-service", 342 | "tracing", 343 | "want", 344 | ] 345 | 346 | [[package]] 347 | name = "idna" 348 | version = "0.3.0" 349 | source = "registry+https://github.com/rust-lang/crates.io-index" 350 | checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" 351 | dependencies = [ 352 | "unicode-bidi", 353 | "unicode-normalization", 354 | ] 355 | 356 | [[package]] 357 | name = "itoa" 358 | version = "1.0.5" 359 | source = "registry+https://github.com/rust-lang/crates.io-index" 360 | checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" 361 | 362 | [[package]] 363 | name = "json-patch" 364 | version = "0.3.0" 365 | source = "registry+https://github.com/rust-lang/crates.io-index" 366 | checksum = "e712e62827c382a77b87f590532febb1f8b2fdbc3eefa1ee37fe7281687075ef" 367 | dependencies = [ 368 | "serde", 369 | "serde_json", 370 | "thiserror", 371 | "treediff", 372 | ] 373 | 374 | [[package]] 375 | name = "lazy_static" 376 | version = "1.4.0" 377 | source = "registry+https://github.com/rust-lang/crates.io-index" 378 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 379 | 380 | [[package]] 381 | name = "libc" 382 | version = "0.2.139" 383 | source = "registry+https://github.com/rust-lang/crates.io-index" 384 | checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" 385 | 386 | [[package]] 387 | name = "lock_api" 388 | version = "0.4.9" 389 | source = "registry+https://github.com/rust-lang/crates.io-index" 390 | checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" 391 | dependencies = [ 392 | "autocfg", 393 | "scopeguard", 394 | ] 395 | 396 | [[package]] 397 | name = "log" 398 | version = "0.4.17" 399 | source = "registry+https://github.com/rust-lang/crates.io-index" 400 | checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" 401 | dependencies = [ 402 | "cfg-if", 403 | ] 404 | 405 | [[package]] 406 | name = "matchit" 407 | version = "0.7.0" 408 | source = "registry+https://github.com/rust-lang/crates.io-index" 409 | checksum = "b87248edafb776e59e6ee64a79086f65890d3510f2c656c000bf2a7e8a0aea40" 410 | 411 | [[package]] 412 | name = "memchr" 413 | version = "2.5.0" 414 | source = "registry+https://github.com/rust-lang/crates.io-index" 415 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 416 | 417 | [[package]] 418 | name = "mime" 419 | version = "0.3.16" 420 | source = "registry+https://github.com/rust-lang/crates.io-index" 421 | checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" 422 | 423 | [[package]] 424 | name = "mio" 425 | version = "0.8.5" 426 | source = "registry+https://github.com/rust-lang/crates.io-index" 427 | checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" 428 | dependencies = [ 429 | "libc", 430 | "log", 431 | "wasi", 432 | "windows-sys", 433 | ] 434 | 435 | [[package]] 436 | name = "nu-ansi-term" 437 | version = "0.46.0" 438 | source = "registry+https://github.com/rust-lang/crates.io-index" 439 | checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" 440 | dependencies = [ 441 | "overload", 442 | "winapi", 443 | ] 444 | 445 | [[package]] 446 | name = "num_cpus" 447 | version = "1.15.0" 448 | source = "registry+https://github.com/rust-lang/crates.io-index" 449 | checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" 450 | dependencies = [ 451 | "hermit-abi", 452 | "libc", 453 | ] 454 | 455 | [[package]] 456 | name = "once_cell" 457 | version = "1.17.0" 458 | source = "registry+https://github.com/rust-lang/crates.io-index" 459 | checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" 460 | 461 | [[package]] 462 | name = "overload" 463 | version = "0.1.1" 464 | source = "registry+https://github.com/rust-lang/crates.io-index" 465 | checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" 466 | 467 | [[package]] 468 | name = "parking_lot" 469 | version = "0.12.1" 470 | source = "registry+https://github.com/rust-lang/crates.io-index" 471 | checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" 472 | dependencies = [ 473 | "lock_api", 474 | "parking_lot_core", 475 | ] 476 | 477 | [[package]] 478 | name = "parking_lot_core" 479 | version = "0.9.6" 480 | source = "registry+https://github.com/rust-lang/crates.io-index" 481 | checksum = "ba1ef8814b5c993410bb3adfad7a5ed269563e4a2f90c41f5d85be7fb47133bf" 482 | dependencies = [ 483 | "cfg-if", 484 | "libc", 485 | "redox_syscall", 486 | "smallvec", 487 | "windows-sys", 488 | ] 489 | 490 | [[package]] 491 | name = "percent-encoding" 492 | version = "2.2.0" 493 | source = "registry+https://github.com/rust-lang/crates.io-index" 494 | checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" 495 | 496 | [[package]] 497 | name = "pin-project" 498 | version = "1.0.12" 499 | source = "registry+https://github.com/rust-lang/crates.io-index" 500 | checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" 501 | dependencies = [ 502 | "pin-project-internal", 503 | ] 504 | 505 | [[package]] 506 | name = "pin-project-internal" 507 | version = "1.0.12" 508 | source = "registry+https://github.com/rust-lang/crates.io-index" 509 | checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" 510 | dependencies = [ 511 | "proc-macro2", 512 | "quote", 513 | "syn", 514 | ] 515 | 516 | [[package]] 517 | name = "pin-project-lite" 518 | version = "0.2.9" 519 | source = "registry+https://github.com/rust-lang/crates.io-index" 520 | checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" 521 | 522 | [[package]] 523 | name = "pin-utils" 524 | version = "0.1.0" 525 | source = "registry+https://github.com/rust-lang/crates.io-index" 526 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 527 | 528 | [[package]] 529 | name = "ppv-lite86" 530 | version = "0.2.17" 531 | source = "registry+https://github.com/rust-lang/crates.io-index" 532 | checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" 533 | 534 | [[package]] 535 | name = "proc-macro2" 536 | version = "1.0.50" 537 | source = "registry+https://github.com/rust-lang/crates.io-index" 538 | checksum = "6ef7d57beacfaf2d8aee5937dab7b7f28de3cb8b1828479bb5de2a7106f2bae2" 539 | dependencies = [ 540 | "unicode-ident", 541 | ] 542 | 543 | [[package]] 544 | name = "quote" 545 | version = "1.0.23" 546 | source = "registry+https://github.com/rust-lang/crates.io-index" 547 | checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" 548 | dependencies = [ 549 | "proc-macro2", 550 | ] 551 | 552 | [[package]] 553 | name = "rand" 554 | version = "0.8.5" 555 | source = "registry+https://github.com/rust-lang/crates.io-index" 556 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 557 | dependencies = [ 558 | "libc", 559 | "rand_chacha", 560 | "rand_core", 561 | ] 562 | 563 | [[package]] 564 | name = "rand_chacha" 565 | version = "0.3.1" 566 | source = "registry+https://github.com/rust-lang/crates.io-index" 567 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 568 | dependencies = [ 569 | "ppv-lite86", 570 | "rand_core", 571 | ] 572 | 573 | [[package]] 574 | name = "rand_core" 575 | version = "0.6.4" 576 | source = "registry+https://github.com/rust-lang/crates.io-index" 577 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 578 | dependencies = [ 579 | "getrandom", 580 | ] 581 | 582 | [[package]] 583 | name = "redox_syscall" 584 | version = "0.2.16" 585 | source = "registry+https://github.com/rust-lang/crates.io-index" 586 | checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" 587 | dependencies = [ 588 | "bitflags", 589 | ] 590 | 591 | [[package]] 592 | name = "rustversion" 593 | version = "1.0.11" 594 | source = "registry+https://github.com/rust-lang/crates.io-index" 595 | checksum = "5583e89e108996506031660fe09baa5011b9dd0341b89029313006d1fb508d70" 596 | 597 | [[package]] 598 | name = "ryu" 599 | version = "1.0.12" 600 | source = "registry+https://github.com/rust-lang/crates.io-index" 601 | checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" 602 | 603 | [[package]] 604 | name = "scopeguard" 605 | version = "1.1.0" 606 | source = "registry+https://github.com/rust-lang/crates.io-index" 607 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 608 | 609 | [[package]] 610 | name = "serde" 611 | version = "1.0.152" 612 | source = "registry+https://github.com/rust-lang/crates.io-index" 613 | checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" 614 | dependencies = [ 615 | "serde_derive", 616 | ] 617 | 618 | [[package]] 619 | name = "serde_derive" 620 | version = "1.0.152" 621 | source = "registry+https://github.com/rust-lang/crates.io-index" 622 | checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" 623 | dependencies = [ 624 | "proc-macro2", 625 | "quote", 626 | "syn", 627 | ] 628 | 629 | [[package]] 630 | name = "serde_json" 631 | version = "1.0.91" 632 | source = "registry+https://github.com/rust-lang/crates.io-index" 633 | checksum = "877c235533714907a8c2464236f5c4b2a17262ef1bd71f38f35ea592c8da6883" 634 | dependencies = [ 635 | "itoa", 636 | "ryu", 637 | "serde", 638 | ] 639 | 640 | [[package]] 641 | name = "serde_path_to_error" 642 | version = "0.1.9" 643 | source = "registry+https://github.com/rust-lang/crates.io-index" 644 | checksum = "26b04f22b563c91331a10074bda3dd5492e3cc39d56bd557e91c0af42b6c7341" 645 | dependencies = [ 646 | "serde", 647 | ] 648 | 649 | [[package]] 650 | name = "serde_urlencoded" 651 | version = "0.7.1" 652 | source = "registry+https://github.com/rust-lang/crates.io-index" 653 | checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" 654 | dependencies = [ 655 | "form_urlencoded", 656 | "itoa", 657 | "ryu", 658 | "serde", 659 | ] 660 | 661 | [[package]] 662 | name = "sha1" 663 | version = "0.10.5" 664 | source = "registry+https://github.com/rust-lang/crates.io-index" 665 | checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" 666 | dependencies = [ 667 | "cfg-if", 668 | "cpufeatures", 669 | "digest", 670 | ] 671 | 672 | [[package]] 673 | name = "sharded-slab" 674 | version = "0.1.4" 675 | source = "registry+https://github.com/rust-lang/crates.io-index" 676 | checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" 677 | dependencies = [ 678 | "lazy_static", 679 | ] 680 | 681 | [[package]] 682 | name = "signal-hook-registry" 683 | version = "1.4.0" 684 | source = "registry+https://github.com/rust-lang/crates.io-index" 685 | checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" 686 | dependencies = [ 687 | "libc", 688 | ] 689 | 690 | [[package]] 691 | name = "slab" 692 | version = "0.4.7" 693 | source = "registry+https://github.com/rust-lang/crates.io-index" 694 | checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" 695 | dependencies = [ 696 | "autocfg", 697 | ] 698 | 699 | [[package]] 700 | name = "smallvec" 701 | version = "1.10.0" 702 | source = "registry+https://github.com/rust-lang/crates.io-index" 703 | checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" 704 | 705 | [[package]] 706 | name = "socket2" 707 | version = "0.4.7" 708 | source = "registry+https://github.com/rust-lang/crates.io-index" 709 | checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" 710 | dependencies = [ 711 | "libc", 712 | "winapi", 713 | ] 714 | 715 | [[package]] 716 | name = "syn" 717 | version = "1.0.107" 718 | source = "registry+https://github.com/rust-lang/crates.io-index" 719 | checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" 720 | dependencies = [ 721 | "proc-macro2", 722 | "quote", 723 | "unicode-ident", 724 | ] 725 | 726 | [[package]] 727 | name = "sync_wrapper" 728 | version = "0.1.1" 729 | source = "registry+https://github.com/rust-lang/crates.io-index" 730 | checksum = "20518fe4a4c9acf048008599e464deb21beeae3d3578418951a189c235a7a9a8" 731 | 732 | [[package]] 733 | name = "thiserror" 734 | version = "1.0.38" 735 | source = "registry+https://github.com/rust-lang/crates.io-index" 736 | checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" 737 | dependencies = [ 738 | "thiserror-impl", 739 | ] 740 | 741 | [[package]] 742 | name = "thiserror-impl" 743 | version = "1.0.38" 744 | source = "registry+https://github.com/rust-lang/crates.io-index" 745 | checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" 746 | dependencies = [ 747 | "proc-macro2", 748 | "quote", 749 | "syn", 750 | ] 751 | 752 | [[package]] 753 | name = "thread_local" 754 | version = "1.1.4" 755 | source = "registry+https://github.com/rust-lang/crates.io-index" 756 | checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" 757 | dependencies = [ 758 | "once_cell", 759 | ] 760 | 761 | [[package]] 762 | name = "tinyvec" 763 | version = "1.6.0" 764 | source = "registry+https://github.com/rust-lang/crates.io-index" 765 | checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" 766 | dependencies = [ 767 | "tinyvec_macros", 768 | ] 769 | 770 | [[package]] 771 | name = "tinyvec_macros" 772 | version = "0.1.0" 773 | source = "registry+https://github.com/rust-lang/crates.io-index" 774 | checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" 775 | 776 | [[package]] 777 | name = "tokio" 778 | version = "1.24.2" 779 | source = "registry+https://github.com/rust-lang/crates.io-index" 780 | checksum = "597a12a59981d9e3c38d216785b0c37399f6e415e8d0712047620f189371b0bb" 781 | dependencies = [ 782 | "autocfg", 783 | "bytes", 784 | "libc", 785 | "memchr", 786 | "mio", 787 | "num_cpus", 788 | "parking_lot", 789 | "pin-project-lite", 790 | "signal-hook-registry", 791 | "socket2", 792 | "tokio-macros", 793 | "windows-sys", 794 | ] 795 | 796 | [[package]] 797 | name = "tokio-macros" 798 | version = "1.8.2" 799 | source = "registry+https://github.com/rust-lang/crates.io-index" 800 | checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8" 801 | dependencies = [ 802 | "proc-macro2", 803 | "quote", 804 | "syn", 805 | ] 806 | 807 | [[package]] 808 | name = "tokio-tungstenite" 809 | version = "0.18.0" 810 | source = "registry+https://github.com/rust-lang/crates.io-index" 811 | checksum = "54319c93411147bced34cb5609a80e0a8e44c5999c93903a81cd866630ec0bfd" 812 | dependencies = [ 813 | "futures-util", 814 | "log", 815 | "tokio", 816 | "tungstenite", 817 | ] 818 | 819 | [[package]] 820 | name = "tower" 821 | version = "0.4.13" 822 | source = "registry+https://github.com/rust-lang/crates.io-index" 823 | checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" 824 | dependencies = [ 825 | "futures-core", 826 | "futures-util", 827 | "pin-project", 828 | "pin-project-lite", 829 | "tokio", 830 | "tower-layer", 831 | "tower-service", 832 | "tracing", 833 | ] 834 | 835 | [[package]] 836 | name = "tower-http" 837 | version = "0.3.5" 838 | source = "registry+https://github.com/rust-lang/crates.io-index" 839 | checksum = "f873044bf02dd1e8239e9c1293ea39dad76dc594ec16185d0a1bf31d8dc8d858" 840 | dependencies = [ 841 | "bitflags", 842 | "bytes", 843 | "futures-core", 844 | "futures-util", 845 | "http", 846 | "http-body", 847 | "http-range-header", 848 | "pin-project-lite", 849 | "tower", 850 | "tower-layer", 851 | "tower-service", 852 | ] 853 | 854 | [[package]] 855 | name = "tower-layer" 856 | version = "0.3.2" 857 | source = "registry+https://github.com/rust-lang/crates.io-index" 858 | checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" 859 | 860 | [[package]] 861 | name = "tower-service" 862 | version = "0.3.2" 863 | source = "registry+https://github.com/rust-lang/crates.io-index" 864 | checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" 865 | 866 | [[package]] 867 | name = "tracing" 868 | version = "0.1.37" 869 | source = "registry+https://github.com/rust-lang/crates.io-index" 870 | checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" 871 | dependencies = [ 872 | "cfg-if", 873 | "log", 874 | "pin-project-lite", 875 | "tracing-attributes", 876 | "tracing-core", 877 | ] 878 | 879 | [[package]] 880 | name = "tracing-attributes" 881 | version = "0.1.23" 882 | source = "registry+https://github.com/rust-lang/crates.io-index" 883 | checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" 884 | dependencies = [ 885 | "proc-macro2", 886 | "quote", 887 | "syn", 888 | ] 889 | 890 | [[package]] 891 | name = "tracing-core" 892 | version = "0.1.30" 893 | source = "registry+https://github.com/rust-lang/crates.io-index" 894 | checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" 895 | dependencies = [ 896 | "once_cell", 897 | "valuable", 898 | ] 899 | 900 | [[package]] 901 | name = "tracing-log" 902 | version = "0.1.3" 903 | source = "registry+https://github.com/rust-lang/crates.io-index" 904 | checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" 905 | dependencies = [ 906 | "lazy_static", 907 | "log", 908 | "tracing-core", 909 | ] 910 | 911 | [[package]] 912 | name = "tracing-subscriber" 913 | version = "0.3.16" 914 | source = "registry+https://github.com/rust-lang/crates.io-index" 915 | checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70" 916 | dependencies = [ 917 | "nu-ansi-term", 918 | "sharded-slab", 919 | "smallvec", 920 | "thread_local", 921 | "tracing-core", 922 | "tracing-log", 923 | ] 924 | 925 | [[package]] 926 | name = "treediff" 927 | version = "4.0.2" 928 | source = "registry+https://github.com/rust-lang/crates.io-index" 929 | checksum = "52984d277bdf2a751072b5df30ec0377febdb02f7696d64c2d7d54630bac4303" 930 | dependencies = [ 931 | "serde_json", 932 | ] 933 | 934 | [[package]] 935 | name = "try-lock" 936 | version = "0.2.4" 937 | source = "registry+https://github.com/rust-lang/crates.io-index" 938 | checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" 939 | 940 | [[package]] 941 | name = "tungstenite" 942 | version = "0.18.0" 943 | source = "registry+https://github.com/rust-lang/crates.io-index" 944 | checksum = "30ee6ab729cd4cf0fd55218530c4522ed30b7b6081752839b68fcec8d0960788" 945 | dependencies = [ 946 | "base64 0.13.1", 947 | "byteorder", 948 | "bytes", 949 | "http", 950 | "httparse", 951 | "log", 952 | "rand", 953 | "sha1", 954 | "thiserror", 955 | "url", 956 | "utf-8", 957 | ] 958 | 959 | [[package]] 960 | name = "typenum" 961 | version = "1.16.0" 962 | source = "registry+https://github.com/rust-lang/crates.io-index" 963 | checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" 964 | 965 | [[package]] 966 | name = "unicode-bidi" 967 | version = "0.3.10" 968 | source = "registry+https://github.com/rust-lang/crates.io-index" 969 | checksum = "d54675592c1dbefd78cbd98db9bacd89886e1ca50692a0692baefffdeb92dd58" 970 | 971 | [[package]] 972 | name = "unicode-ident" 973 | version = "1.0.6" 974 | source = "registry+https://github.com/rust-lang/crates.io-index" 975 | checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" 976 | 977 | [[package]] 978 | name = "unicode-normalization" 979 | version = "0.1.22" 980 | source = "registry+https://github.com/rust-lang/crates.io-index" 981 | checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" 982 | dependencies = [ 983 | "tinyvec", 984 | ] 985 | 986 | [[package]] 987 | name = "url" 988 | version = "2.3.1" 989 | source = "registry+https://github.com/rust-lang/crates.io-index" 990 | checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" 991 | dependencies = [ 992 | "form_urlencoded", 993 | "idna", 994 | "percent-encoding", 995 | ] 996 | 997 | [[package]] 998 | name = "utf-8" 999 | version = "0.7.6" 1000 | source = "registry+https://github.com/rust-lang/crates.io-index" 1001 | checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" 1002 | 1003 | [[package]] 1004 | name = "valuable" 1005 | version = "0.1.0" 1006 | source = "registry+https://github.com/rust-lang/crates.io-index" 1007 | checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" 1008 | 1009 | [[package]] 1010 | name = "version_check" 1011 | version = "0.9.4" 1012 | source = "registry+https://github.com/rust-lang/crates.io-index" 1013 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 1014 | 1015 | [[package]] 1016 | name = "want" 1017 | version = "0.3.0" 1018 | source = "registry+https://github.com/rust-lang/crates.io-index" 1019 | checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" 1020 | dependencies = [ 1021 | "log", 1022 | "try-lock", 1023 | ] 1024 | 1025 | [[package]] 1026 | name = "wasi" 1027 | version = "0.11.0+wasi-snapshot-preview1" 1028 | source = "registry+https://github.com/rust-lang/crates.io-index" 1029 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1030 | 1031 | [[package]] 1032 | name = "websocket_jsonpatch" 1033 | version = "0.1.0" 1034 | dependencies = [ 1035 | "axum", 1036 | "futures", 1037 | "json-patch", 1038 | "serde", 1039 | "serde_json", 1040 | "tokio", 1041 | "tracing", 1042 | "tracing-subscriber", 1043 | ] 1044 | 1045 | [[package]] 1046 | name = "winapi" 1047 | version = "0.3.9" 1048 | source = "registry+https://github.com/rust-lang/crates.io-index" 1049 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1050 | dependencies = [ 1051 | "winapi-i686-pc-windows-gnu", 1052 | "winapi-x86_64-pc-windows-gnu", 1053 | ] 1054 | 1055 | [[package]] 1056 | name = "winapi-i686-pc-windows-gnu" 1057 | version = "0.4.0" 1058 | source = "registry+https://github.com/rust-lang/crates.io-index" 1059 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1060 | 1061 | [[package]] 1062 | name = "winapi-x86_64-pc-windows-gnu" 1063 | version = "0.4.0" 1064 | source = "registry+https://github.com/rust-lang/crates.io-index" 1065 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1066 | 1067 | [[package]] 1068 | name = "windows-sys" 1069 | version = "0.42.0" 1070 | source = "registry+https://github.com/rust-lang/crates.io-index" 1071 | checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" 1072 | dependencies = [ 1073 | "windows_aarch64_gnullvm", 1074 | "windows_aarch64_msvc", 1075 | "windows_i686_gnu", 1076 | "windows_i686_msvc", 1077 | "windows_x86_64_gnu", 1078 | "windows_x86_64_gnullvm", 1079 | "windows_x86_64_msvc", 1080 | ] 1081 | 1082 | [[package]] 1083 | name = "windows_aarch64_gnullvm" 1084 | version = "0.42.1" 1085 | source = "registry+https://github.com/rust-lang/crates.io-index" 1086 | checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" 1087 | 1088 | [[package]] 1089 | name = "windows_aarch64_msvc" 1090 | version = "0.42.1" 1091 | source = "registry+https://github.com/rust-lang/crates.io-index" 1092 | checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" 1093 | 1094 | [[package]] 1095 | name = "windows_i686_gnu" 1096 | version = "0.42.1" 1097 | source = "registry+https://github.com/rust-lang/crates.io-index" 1098 | checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" 1099 | 1100 | [[package]] 1101 | name = "windows_i686_msvc" 1102 | version = "0.42.1" 1103 | source = "registry+https://github.com/rust-lang/crates.io-index" 1104 | checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" 1105 | 1106 | [[package]] 1107 | name = "windows_x86_64_gnu" 1108 | version = "0.42.1" 1109 | source = "registry+https://github.com/rust-lang/crates.io-index" 1110 | checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" 1111 | 1112 | [[package]] 1113 | name = "windows_x86_64_gnullvm" 1114 | version = "0.42.1" 1115 | source = "registry+https://github.com/rust-lang/crates.io-index" 1116 | checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" 1117 | 1118 | [[package]] 1119 | name = "windows_x86_64_msvc" 1120 | version = "0.42.1" 1121 | source = "registry+https://github.com/rust-lang/crates.io-index" 1122 | checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" 1123 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "websocket_jsonpatch" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | axum = { version = "0.6.4", features = ["ws"]} 8 | tokio = { version = "1", features = ["full"] } 9 | 10 | tracing = "0.1.37" 11 | tracing-subscriber = "0.3.16" 12 | 13 | serde = { version = "1.0", features = ["derive"] } 14 | serde_json = "1.0" 15 | futures = "0.3" 16 | json-patch = "0.3.0" 17 | -------------------------------------------------------------------------------- /example.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cetra3/websocket_jsonpatch/3153c0bc0533c681d4492f57ac811b076249600b/example.mp4 -------------------------------------------------------------------------------- /front/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | dist-ssr 5 | *.local 6 | -------------------------------------------------------------------------------- /front/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite App 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /front/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "websocket_jsonpatch", 3 | "version": "0.1.0", 4 | "license": "GPL-3.0-or-later", 5 | "scripts": { 6 | "dev": "vite", 7 | "build": "tsc && vite build", 8 | "serve": "vite preview", 9 | "format": "prettier --write \"**/*.{js,jsx,ts,tsx,json,md,css}\" " 10 | }, 11 | "dependencies": { 12 | "fast-json-patch": "^3.1.0", 13 | "react": "^17.0.0", 14 | "react-dom": "^17.0.0", 15 | "spectre.css": "^0.5.9" 16 | }, 17 | "devDependencies": { 18 | "@types/react": "^17.0.0", 19 | "@types/react-dom": "^17.0.0", 20 | "@vitejs/plugin-react": "^1.0.0", 21 | "prettier": "^2.4.1", 22 | "sass": "=1.32.8", 23 | "typescript": "^4.3.2", 24 | "vite": "^2.6.4" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /front/src/App.scss: -------------------------------------------------------------------------------- 1 | 2 | @import "spectre.css/src/spectre"; 3 | @import "spectre.css/src/spectre-icons"; 4 | 5 | 6 | .todo-title { 7 | margin-top: 2rem; 8 | } 9 | 10 | .todo-row { 11 | margin-top: 0.5rem; 12 | } 13 | 14 | .todo-buttons { 15 | margin-top: 0.5rem; 16 | display: flex; 17 | flex-direction: row-reverse; 18 | } 19 | 20 | .completed { 21 | text-decoration: line-through; 22 | } -------------------------------------------------------------------------------- /front/src/App.tsx: -------------------------------------------------------------------------------- 1 | import "./App.scss"; 2 | 3 | import { Todo, TodoRow } from "./Todo"; 4 | import { sendAction, useWebsocket } from "./Websocket"; 5 | 6 | function App() { 7 | const todo = useWebsocket(); 8 | 9 | return ( 10 |
11 |
12 |
13 |

Todo App Example

14 |
15 |
16 | {todo === undefined &&
} 17 | {todo && } 18 |
19 | ); 20 | } 21 | 22 | function TodoComponent({ todo }: { todo: Todo }) { 23 | return ( 24 | <> 25 |
26 |
27 |
28 | 31 |
32 |
33 | { 40 | sendAction({ 41 | type: "ChangeName", 42 | name: ev.currentTarget.value, 43 | }); 44 | }} 45 | /> 46 |
47 |
48 |
49 | {Object.entries(todo.todos).map(([index, row]) => ( 50 | 51 | ))} 52 |
53 |
54 |
55 | {Object.entries(todo.todos).some(([, val]) => val.completed) && ( 56 | 66 | )} 67 | 81 |
82 |
83 |
84 | 85 | ); 86 | } 87 | 88 | function TodoRowComponent({ row, index }: { row: TodoRow; index: number }) { 89 | return ( 90 |
91 |
92 |
93 | 110 | 116 | sendAction({ 117 | type: "Update", 118 | row: { 119 | ...row, 120 | name: ev.currentTarget.value, 121 | }, 122 | index, 123 | }) 124 | } 125 | /> 126 | 127 | 138 |
139 |
140 |
141 | ); 142 | } 143 | 144 | export default App; 145 | -------------------------------------------------------------------------------- /front/src/Todo.ts: -------------------------------------------------------------------------------- 1 | export interface Todo { 2 | name: string; 3 | todos: { [index: number]: TodoRow }; 4 | } 5 | 6 | export interface TodoRow { 7 | name: string; 8 | completed: boolean; 9 | } 10 | 11 | export interface Add { 12 | type: "Add"; 13 | row: TodoRow; 14 | } 15 | 16 | export interface ChangeName { 17 | type: "ChangeName"; 18 | name: string; 19 | } 20 | 21 | export interface Update { 22 | type: "Update"; 23 | row: TodoRow; 24 | index: number; 25 | } 26 | 27 | export interface Remove { 28 | type: "Remove"; 29 | index: number; 30 | } 31 | 32 | export interface RemoveCompleted { 33 | type: "RemoveCompleted"; 34 | } 35 | 36 | export type TodoAction = Add | ChangeName | Update | Remove | RemoveCompleted; 37 | -------------------------------------------------------------------------------- /front/src/Websocket.ts: -------------------------------------------------------------------------------- 1 | import { applyPatch, Operation } from "fast-json-patch"; 2 | import { useEffect, useState } from "react"; 3 | import { Todo, TodoAction } from "./Todo"; 4 | 5 | interface Patch { 6 | type: "Patch"; 7 | ops: Operation[]; 8 | } 9 | 10 | interface Full { 11 | type: "Full"; 12 | todo: Todo; 13 | } 14 | 15 | type ServerMessage = Patch | Full; 16 | 17 | let websocket: WebSocket | undefined; 18 | let todo: Todo | undefined; 19 | 20 | const setupWebsocket = (onTodoUpdate: (todo: Todo) => void) => { 21 | const loc = window.location; 22 | const uri = `${loc.protocol === "https:" ? "wss:" : "ws:"}//${loc.host}/ws`; 23 | console.log(`Connecting websocket: ${uri}`); 24 | 25 | const connection = new WebSocket(uri); 26 | 27 | connection.onopen = () => { 28 | console.log("Websocket Connected"); 29 | websocket = connection; 30 | }; 31 | 32 | // If we receive a close event the backend has gone away, we try reconnecting in a bit of time 33 | connection.onclose = (reason) => { 34 | websocket = undefined; 35 | 36 | // https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent 37 | if (reason.code !== 1000 && reason.code !== 1001) { 38 | console.error("Websocket connection closed", reason); 39 | 40 | setTimeout(() => { 41 | setupWebsocket(onTodoUpdate); 42 | }, 500); 43 | } 44 | }; 45 | 46 | connection.onerror = (error) => { 47 | console.error("Error with websocket", error); 48 | connection.close(); 49 | }; 50 | 51 | connection.onmessage = (message) => { 52 | const msg = JSON.parse(message.data) as ServerMessage; 53 | 54 | switch (msg.type) { 55 | case "Patch": { 56 | if (todo !== undefined) { 57 | let { newDocument: newTodo } = applyPatch( 58 | todo, 59 | msg.ops, 60 | false, 61 | false 62 | ); 63 | 64 | onTodoUpdate(newTodo); 65 | todo = newTodo; 66 | } 67 | break; 68 | } 69 | case "Full": { 70 | onTodoUpdate(msg.todo); 71 | todo = msg.todo; 72 | break; 73 | } 74 | } 75 | }; 76 | }; 77 | 78 | export const useWebsocket = () => { 79 | // Keep our local state of the todo app to trigger a render on change 80 | let [todo, updateTodo] = useState(); 81 | 82 | useEffect(() => { 83 | // Update our app state when changes are received 84 | setupWebsocket((msg) => { 85 | updateTodo(msg); 86 | }); 87 | // If the destructor runs, clean up the websocket 88 | return () => { 89 | if (websocket) { 90 | websocket.close(1000); 91 | } 92 | }; 93 | // The empty `[]` dependency list makes this `useEffect` callback execute only once on construction 94 | }, []); 95 | 96 | return todo; 97 | }; 98 | 99 | export const sendAction = (action: TodoAction): void => { 100 | if (websocket) { 101 | websocket.send(JSON.stringify(action)); 102 | } 103 | }; 104 | -------------------------------------------------------------------------------- /front/src/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /front/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", 4 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /front/src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import "./index.css"; 4 | import App from "./App"; 5 | 6 | ReactDOM.render( 7 | 8 | 9 | , 10 | document.getElementById("root") 11 | ); 12 | -------------------------------------------------------------------------------- /front/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /front/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "lib": ["DOM", "DOM.Iterable", "ESNext"], 6 | "allowJs": false, 7 | "skipLibCheck": false, 8 | "esModuleInterop": false, 9 | "allowSyntheticDefaultImports": true, 10 | "strict": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "module": "ESNext", 13 | "moduleResolution": "Node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noEmit": true, 17 | "jsx": "react-jsx" 18 | }, 19 | "include": ["./src"] 20 | } 21 | -------------------------------------------------------------------------------- /front/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import react from "@vitejs/plugin-react"; 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | server: { 8 | proxy: { 9 | "/ws": { 10 | target: "http://127.0.0.1:3333", 11 | ws: true, 12 | }, 13 | }, 14 | }, 15 | }); 16 | -------------------------------------------------------------------------------- /front/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@babel/code-frame@^7.14.5", "@babel/code-frame@^7.15.8": 6 | version "7.15.8" 7 | resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.15.8.tgz#45990c47adadb00c03677baa89221f7cc23d2503" 8 | integrity sha512-2IAnmn8zbvC/jKYhq5Ki9I+DwjlrtMPUCH/CpHvqI4dNnlwHwsxoIhlc8WcYY5LSYknXQtAlFYuHfqAFCvQ4Wg== 9 | dependencies: 10 | "@babel/highlight" "^7.14.5" 11 | 12 | "@babel/compat-data@^7.15.0": 13 | version "7.15.0" 14 | resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.15.0.tgz#2dbaf8b85334796cafbb0f5793a90a2fc010b176" 15 | integrity sha512-0NqAC1IJE0S0+lL1SWFMxMkz1pKCNCjI4tr2Zx4LJSXxCLAdr6KyArnY+sno5m3yH9g737ygOyPABDsnXkpxiA== 16 | 17 | "@babel/core@^7.15.5": 18 | version "7.15.8" 19 | resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.15.8.tgz#195b9f2bffe995d2c6c159e72fe525b4114e8c10" 20 | integrity sha512-3UG9dsxvYBMYwRv+gS41WKHno4K60/9GPy1CJaH6xy3Elq8CTtvtjT5R5jmNhXfCYLX2mTw+7/aq5ak/gOE0og== 21 | dependencies: 22 | "@babel/code-frame" "^7.15.8" 23 | "@babel/generator" "^7.15.8" 24 | "@babel/helper-compilation-targets" "^7.15.4" 25 | "@babel/helper-module-transforms" "^7.15.8" 26 | "@babel/helpers" "^7.15.4" 27 | "@babel/parser" "^7.15.8" 28 | "@babel/template" "^7.15.4" 29 | "@babel/traverse" "^7.15.4" 30 | "@babel/types" "^7.15.6" 31 | convert-source-map "^1.7.0" 32 | debug "^4.1.0" 33 | gensync "^1.0.0-beta.2" 34 | json5 "^2.1.2" 35 | semver "^6.3.0" 36 | source-map "^0.5.0" 37 | 38 | "@babel/generator@^7.15.4", "@babel/generator@^7.15.8": 39 | version "7.15.8" 40 | resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.15.8.tgz#fa56be6b596952ceb231048cf84ee499a19c0cd1" 41 | integrity sha512-ECmAKstXbp1cvpTTZciZCgfOt6iN64lR0d+euv3UZisU5awfRawOvg07Utn/qBGuH4bRIEZKrA/4LzZyXhZr8g== 42 | dependencies: 43 | "@babel/types" "^7.15.6" 44 | jsesc "^2.5.1" 45 | source-map "^0.5.0" 46 | 47 | "@babel/helper-annotate-as-pure@^7.14.5": 48 | version "7.15.4" 49 | resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.15.4.tgz#3d0e43b00c5e49fdb6c57e421601a7a658d5f835" 50 | integrity sha512-QwrtdNvUNsPCj2lfNQacsGSQvGX8ee1ttrBrcozUP2Sv/jylewBP/8QFe6ZkBsC8T/GYWonNAWJV4aRR9AL2DA== 51 | dependencies: 52 | "@babel/types" "^7.15.4" 53 | 54 | "@babel/helper-compilation-targets@^7.15.4": 55 | version "7.15.4" 56 | resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.15.4.tgz#cf6d94f30fbefc139123e27dd6b02f65aeedb7b9" 57 | integrity sha512-rMWPCirulnPSe4d+gwdWXLfAXTTBj8M3guAf5xFQJ0nvFY7tfNAFnWdqaHegHlgDZOCT4qvhF3BYlSJag8yhqQ== 58 | dependencies: 59 | "@babel/compat-data" "^7.15.0" 60 | "@babel/helper-validator-option" "^7.14.5" 61 | browserslist "^4.16.6" 62 | semver "^6.3.0" 63 | 64 | "@babel/helper-function-name@^7.15.4": 65 | version "7.15.4" 66 | resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.15.4.tgz#845744dafc4381a4a5fb6afa6c3d36f98a787ebc" 67 | integrity sha512-Z91cOMM4DseLIGOnog+Z8OI6YseR9bua+HpvLAQ2XayUGU+neTtX+97caALaLdyu53I/fjhbeCnWnRH1O3jFOw== 68 | dependencies: 69 | "@babel/helper-get-function-arity" "^7.15.4" 70 | "@babel/template" "^7.15.4" 71 | "@babel/types" "^7.15.4" 72 | 73 | "@babel/helper-get-function-arity@^7.15.4": 74 | version "7.15.4" 75 | resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.15.4.tgz#098818934a137fce78b536a3e015864be1e2879b" 76 | integrity sha512-1/AlxSF92CmGZzHnC515hm4SirTxtpDnLEJ0UyEMgTMZN+6bxXKg04dKhiRx5Enel+SUA1G1t5Ed/yQia0efrA== 77 | dependencies: 78 | "@babel/types" "^7.15.4" 79 | 80 | "@babel/helper-hoist-variables@^7.15.4": 81 | version "7.15.4" 82 | resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.15.4.tgz#09993a3259c0e918f99d104261dfdfc033f178df" 83 | integrity sha512-VTy085egb3jUGVK9ycIxQiPbquesq0HUQ+tPO0uv5mPEBZipk+5FkRKiWq5apuyTE9FUrjENB0rCf8y+n+UuhA== 84 | dependencies: 85 | "@babel/types" "^7.15.4" 86 | 87 | "@babel/helper-member-expression-to-functions@^7.15.4": 88 | version "7.15.4" 89 | resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.15.4.tgz#bfd34dc9bba9824a4658b0317ec2fd571a51e6ef" 90 | integrity sha512-cokOMkxC/BTyNP1AlY25HuBWM32iCEsLPI4BHDpJCHHm1FU2E7dKWWIXJgQgSFiu4lp8q3bL1BIKwqkSUviqtA== 91 | dependencies: 92 | "@babel/types" "^7.15.4" 93 | 94 | "@babel/helper-module-imports@^7.14.5", "@babel/helper-module-imports@^7.15.4": 95 | version "7.15.4" 96 | resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.15.4.tgz#e18007d230632dea19b47853b984476e7b4e103f" 97 | integrity sha512-jeAHZbzUwdW/xHgHQ3QmWR4Jg6j15q4w/gCfwZvtqOxoo5DKtLHk8Bsf4c5RZRC7NmLEs+ohkdq8jFefuvIxAA== 98 | dependencies: 99 | "@babel/types" "^7.15.4" 100 | 101 | "@babel/helper-module-transforms@^7.15.8": 102 | version "7.15.8" 103 | resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.15.8.tgz#d8c0e75a87a52e374a8f25f855174786a09498b2" 104 | integrity sha512-DfAfA6PfpG8t4S6npwzLvTUpp0sS7JrcuaMiy1Y5645laRJIp/LiLGIBbQKaXSInK8tiGNI7FL7L8UvB8gdUZg== 105 | dependencies: 106 | "@babel/helper-module-imports" "^7.15.4" 107 | "@babel/helper-replace-supers" "^7.15.4" 108 | "@babel/helper-simple-access" "^7.15.4" 109 | "@babel/helper-split-export-declaration" "^7.15.4" 110 | "@babel/helper-validator-identifier" "^7.15.7" 111 | "@babel/template" "^7.15.4" 112 | "@babel/traverse" "^7.15.4" 113 | "@babel/types" "^7.15.6" 114 | 115 | "@babel/helper-optimise-call-expression@^7.15.4": 116 | version "7.15.4" 117 | resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.15.4.tgz#f310a5121a3b9cc52d9ab19122bd729822dee171" 118 | integrity sha512-E/z9rfbAOt1vDW1DR7k4SzhzotVV5+qMciWV6LaG1g4jeFrkDlJedjtV4h0i4Q/ITnUu+Pk08M7fczsB9GXBDw== 119 | dependencies: 120 | "@babel/types" "^7.15.4" 121 | 122 | "@babel/helper-plugin-utils@^7.14.5": 123 | version "7.14.5" 124 | resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz#5ac822ce97eec46741ab70a517971e443a70c5a9" 125 | integrity sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ== 126 | 127 | "@babel/helper-replace-supers@^7.15.4": 128 | version "7.15.4" 129 | resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.15.4.tgz#52a8ab26ba918c7f6dee28628b07071ac7b7347a" 130 | integrity sha512-/ztT6khaXF37MS47fufrKvIsiQkx1LBRvSJNzRqmbyeZnTwU9qBxXYLaaT/6KaxfKhjs2Wy8kG8ZdsFUuWBjzw== 131 | dependencies: 132 | "@babel/helper-member-expression-to-functions" "^7.15.4" 133 | "@babel/helper-optimise-call-expression" "^7.15.4" 134 | "@babel/traverse" "^7.15.4" 135 | "@babel/types" "^7.15.4" 136 | 137 | "@babel/helper-simple-access@^7.15.4": 138 | version "7.15.4" 139 | resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.15.4.tgz#ac368905abf1de8e9781434b635d8f8674bcc13b" 140 | integrity sha512-UzazrDoIVOZZcTeHHEPYrr1MvTR/K+wgLg6MY6e1CJyaRhbibftF6fR2KU2sFRtI/nERUZR9fBd6aKgBlIBaPg== 141 | dependencies: 142 | "@babel/types" "^7.15.4" 143 | 144 | "@babel/helper-split-export-declaration@^7.15.4": 145 | version "7.15.4" 146 | resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.15.4.tgz#aecab92dcdbef6a10aa3b62ab204b085f776e257" 147 | integrity sha512-HsFqhLDZ08DxCpBdEVtKmywj6PQbwnF6HHybur0MAnkAKnlS6uHkwnmRIkElB2Owpfb4xL4NwDmDLFubueDXsw== 148 | dependencies: 149 | "@babel/types" "^7.15.4" 150 | 151 | "@babel/helper-validator-identifier@^7.14.5", "@babel/helper-validator-identifier@^7.14.9", "@babel/helper-validator-identifier@^7.15.7": 152 | version "7.15.7" 153 | resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz#220df993bfe904a4a6b02ab4f3385a5ebf6e2389" 154 | integrity sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w== 155 | 156 | "@babel/helper-validator-option@^7.14.5": 157 | version "7.14.5" 158 | resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz#6e72a1fff18d5dfcb878e1e62f1a021c4b72d5a3" 159 | integrity sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow== 160 | 161 | "@babel/helpers@^7.15.4": 162 | version "7.15.4" 163 | resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.15.4.tgz#5f40f02050a3027121a3cf48d497c05c555eaf43" 164 | integrity sha512-V45u6dqEJ3w2rlryYYXf6i9rQ5YMNu4FLS6ngs8ikblhu2VdR1AqAd6aJjBzmf2Qzh6KOLqKHxEN9+TFbAkAVQ== 165 | dependencies: 166 | "@babel/template" "^7.15.4" 167 | "@babel/traverse" "^7.15.4" 168 | "@babel/types" "^7.15.4" 169 | 170 | "@babel/highlight@^7.14.5": 171 | version "7.14.5" 172 | resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.14.5.tgz#6861a52f03966405001f6aa534a01a24d99e8cd9" 173 | integrity sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg== 174 | dependencies: 175 | "@babel/helper-validator-identifier" "^7.14.5" 176 | chalk "^2.0.0" 177 | js-tokens "^4.0.0" 178 | 179 | "@babel/parser@^7.15.4", "@babel/parser@^7.15.8": 180 | version "7.15.8" 181 | resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.15.8.tgz#7bacdcbe71bdc3ff936d510c15dcea7cf0b99016" 182 | integrity sha512-BRYa3wcQnjS/nqI8Ac94pYYpJfojHVvVXJ97+IDCImX4Jc8W8Xv1+47enbruk+q1etOpsQNwnfFcNGw+gtPGxA== 183 | 184 | "@babel/plugin-syntax-jsx@^7.14.5": 185 | version "7.14.5" 186 | resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.14.5.tgz#000e2e25d8673cce49300517a3eda44c263e4201" 187 | integrity sha512-ohuFIsOMXJnbOMRfX7/w7LocdR6R7whhuRD4ax8IipLcLPlZGJKkBxgHp++U4N/vKyU16/YDQr2f5seajD3jIw== 188 | dependencies: 189 | "@babel/helper-plugin-utils" "^7.14.5" 190 | 191 | "@babel/plugin-transform-react-jsx-development@^7.14.5": 192 | version "7.14.5" 193 | resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.14.5.tgz#1a6c73e2f7ed2c42eebc3d2ad60b0c7494fcb9af" 194 | integrity sha512-rdwG/9jC6QybWxVe2UVOa7q6cnTpw8JRRHOxntG/h6g/guAOe6AhtQHJuJh5FwmnXIT1bdm5vC2/5huV8ZOorQ== 195 | dependencies: 196 | "@babel/plugin-transform-react-jsx" "^7.14.5" 197 | 198 | "@babel/plugin-transform-react-jsx-self@^7.14.9": 199 | version "7.14.9" 200 | resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.14.9.tgz#33041e665453391eb6ee54a2ecf3ba1d46bd30f4" 201 | integrity sha512-Fqqu0f8zv9W+RyOnx29BX/RlEsBRANbOf5xs5oxb2aHP4FKbLXxIaVPUiCti56LAR1IixMH4EyaixhUsKqoBHw== 202 | dependencies: 203 | "@babel/helper-plugin-utils" "^7.14.5" 204 | 205 | "@babel/plugin-transform-react-jsx-source@^7.14.5": 206 | version "7.14.5" 207 | resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.14.5.tgz#79f728e60e6dbd31a2b860b0bf6c9765918acf1d" 208 | integrity sha512-1TpSDnD9XR/rQ2tzunBVPThF5poaYT9GqP+of8fAtguYuI/dm2RkrMBDemsxtY0XBzvW7nXjYM0hRyKX9QYj7Q== 209 | dependencies: 210 | "@babel/helper-plugin-utils" "^7.14.5" 211 | 212 | "@babel/plugin-transform-react-jsx@^7.14.5", "@babel/plugin-transform-react-jsx@^7.14.9": 213 | version "7.14.9" 214 | resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.14.9.tgz#3314b2163033abac5200a869c4de242cd50a914c" 215 | integrity sha512-30PeETvS+AeD1f58i1OVyoDlVYQhap/K20ZrMjLmmzmC2AYR/G43D4sdJAaDAqCD3MYpSWbmrz3kES158QSLjw== 216 | dependencies: 217 | "@babel/helper-annotate-as-pure" "^7.14.5" 218 | "@babel/helper-module-imports" "^7.14.5" 219 | "@babel/helper-plugin-utils" "^7.14.5" 220 | "@babel/plugin-syntax-jsx" "^7.14.5" 221 | "@babel/types" "^7.14.9" 222 | 223 | "@babel/template@^7.15.4": 224 | version "7.15.4" 225 | resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.15.4.tgz#51898d35dcf3faa670c4ee6afcfd517ee139f194" 226 | integrity sha512-UgBAfEa1oGuYgDIPM2G+aHa4Nlo9Lh6mGD2bDBGMTbYnc38vulXPuC1MGjYILIEmlwl6Rd+BPR9ee3gm20CBtg== 227 | dependencies: 228 | "@babel/code-frame" "^7.14.5" 229 | "@babel/parser" "^7.15.4" 230 | "@babel/types" "^7.15.4" 231 | 232 | "@babel/traverse@^7.15.4": 233 | version "7.15.4" 234 | resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.15.4.tgz#ff8510367a144bfbff552d9e18e28f3e2889c22d" 235 | integrity sha512-W6lQD8l4rUbQR/vYgSuCAE75ADyyQvOpFVsvPPdkhf6lATXAsQIG9YdtOcu8BB1dZ0LKu+Zo3c1wEcbKeuhdlA== 236 | dependencies: 237 | "@babel/code-frame" "^7.14.5" 238 | "@babel/generator" "^7.15.4" 239 | "@babel/helper-function-name" "^7.15.4" 240 | "@babel/helper-hoist-variables" "^7.15.4" 241 | "@babel/helper-split-export-declaration" "^7.15.4" 242 | "@babel/parser" "^7.15.4" 243 | "@babel/types" "^7.15.4" 244 | debug "^4.1.0" 245 | globals "^11.1.0" 246 | 247 | "@babel/types@^7.14.9", "@babel/types@^7.15.4", "@babel/types@^7.15.6": 248 | version "7.15.6" 249 | resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.15.6.tgz#99abdc48218b2881c058dd0a7ab05b99c9be758f" 250 | integrity sha512-BPU+7QhqNjmWyDO0/vitH/CuhpV8ZmK1wpKva8nuyNF5MJfuRNWMc+hc14+u9xT93kvykMdncrJT19h74uB1Ig== 251 | dependencies: 252 | "@babel/helper-validator-identifier" "^7.14.9" 253 | to-fast-properties "^2.0.0" 254 | 255 | "@rollup/pluginutils@^4.1.1": 256 | version "4.1.1" 257 | resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-4.1.1.tgz#1d4da86dd4eded15656a57d933fda2b9a08d47ec" 258 | integrity sha512-clDjivHqWGXi7u+0d2r2sBi4Ie6VLEAzWMIkvJLnDmxoOhBYOTfzGbOQBA32THHm11/LiJbd01tJUpJsbshSWQ== 259 | dependencies: 260 | estree-walker "^2.0.1" 261 | picomatch "^2.2.2" 262 | 263 | "@types/prop-types@*": 264 | version "15.7.4" 265 | resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.4.tgz#fcf7205c25dff795ee79af1e30da2c9790808f11" 266 | integrity sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ== 267 | 268 | "@types/react-dom@^17.0.0": 269 | version "17.0.9" 270 | resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-17.0.9.tgz#441a981da9d7be117042e1a6fd3dac4b30f55add" 271 | integrity sha512-wIvGxLfgpVDSAMH5utdL9Ngm5Owu0VsGmldro3ORLXV8CShrL8awVj06NuEXFQ5xyaYfdca7Sgbk/50Ri1GdPg== 272 | dependencies: 273 | "@types/react" "*" 274 | 275 | "@types/react@*", "@types/react@^17.0.0": 276 | version "17.0.30" 277 | resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.30.tgz#2f8e6f5ab6415c091cc5e571942ee9064b17609e" 278 | integrity sha512-3Dt/A8gd3TCXi2aRe84y7cK1K8G+N9CZRDG8kDGguOKa0kf/ZkSwTmVIDPsm/KbQOVMaDJXwhBtuOXxqwdpWVg== 279 | dependencies: 280 | "@types/prop-types" "*" 281 | "@types/scheduler" "*" 282 | csstype "^3.0.2" 283 | 284 | "@types/scheduler@*": 285 | version "0.16.2" 286 | resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.2.tgz#1a62f89525723dde24ba1b01b092bf5df8ad4d39" 287 | integrity sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew== 288 | 289 | "@vitejs/plugin-react@^1.0.0": 290 | version "1.0.4" 291 | resolved "https://registry.yarnpkg.com/@vitejs/plugin-react/-/plugin-react-1.0.4.tgz#b4e934915205f2b05998019549c853bb1bb1f6c1" 292 | integrity sha512-38/w1q2FON4e/es8WnAW0ZOa/RIOoOrpeWNUkgY6+u+M1eQZjyWWI0piLRM6fbDnm8Lm8Qtged8A7OZ/YnkNtw== 293 | dependencies: 294 | "@babel/core" "^7.15.5" 295 | "@babel/plugin-transform-react-jsx" "^7.14.9" 296 | "@babel/plugin-transform-react-jsx-development" "^7.14.5" 297 | "@babel/plugin-transform-react-jsx-self" "^7.14.9" 298 | "@babel/plugin-transform-react-jsx-source" "^7.14.5" 299 | "@rollup/pluginutils" "^4.1.1" 300 | react-refresh "^0.10.0" 301 | resolve "^1.20.0" 302 | 303 | ansi-styles@^3.2.1: 304 | version "3.2.1" 305 | resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" 306 | integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== 307 | dependencies: 308 | color-convert "^1.9.0" 309 | 310 | anymatch@~3.1.2: 311 | version "3.1.2" 312 | resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" 313 | integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== 314 | dependencies: 315 | normalize-path "^3.0.0" 316 | picomatch "^2.0.4" 317 | 318 | binary-extensions@^2.0.0: 319 | version "2.2.0" 320 | resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" 321 | integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== 322 | 323 | braces@~3.0.2: 324 | version "3.0.2" 325 | resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" 326 | integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== 327 | dependencies: 328 | fill-range "^7.0.1" 329 | 330 | browserslist@^4.16.6: 331 | version "4.17.4" 332 | resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.17.4.tgz#72e2508af2a403aec0a49847ef31bd823c57ead4" 333 | integrity sha512-Zg7RpbZpIJRW3am9Lyckue7PLytvVxxhJj1CaJVlCWENsGEAOlnlt8X0ZxGRPp7Bt9o8tIRM5SEXy4BCPMJjLQ== 334 | dependencies: 335 | caniuse-lite "^1.0.30001265" 336 | electron-to-chromium "^1.3.867" 337 | escalade "^3.1.1" 338 | node-releases "^2.0.0" 339 | picocolors "^1.0.0" 340 | 341 | caniuse-lite@^1.0.30001265: 342 | version "1.0.30001269" 343 | resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001269.tgz#3a71bee03df627364418f9fd31adfc7aa1cc2d56" 344 | integrity sha512-UOy8okEVs48MyHYgV+RdW1Oiudl1H6KolybD6ZquD0VcrPSgj25omXO1S7rDydjpqaISCwA8Pyx+jUQKZwWO5w== 345 | 346 | chalk@^2.0.0: 347 | version "2.4.2" 348 | resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" 349 | integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== 350 | dependencies: 351 | ansi-styles "^3.2.1" 352 | escape-string-regexp "^1.0.5" 353 | supports-color "^5.3.0" 354 | 355 | "chokidar@>=2.0.0 <4.0.0": 356 | version "3.5.2" 357 | resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.2.tgz#dba3976fcadb016f66fd365021d91600d01c1e75" 358 | integrity sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ== 359 | dependencies: 360 | anymatch "~3.1.2" 361 | braces "~3.0.2" 362 | glob-parent "~5.1.2" 363 | is-binary-path "~2.1.0" 364 | is-glob "~4.0.1" 365 | normalize-path "~3.0.0" 366 | readdirp "~3.6.0" 367 | optionalDependencies: 368 | fsevents "~2.3.2" 369 | 370 | color-convert@^1.9.0: 371 | version "1.9.3" 372 | resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" 373 | integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== 374 | dependencies: 375 | color-name "1.1.3" 376 | 377 | color-name@1.1.3: 378 | version "1.1.3" 379 | resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" 380 | integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= 381 | 382 | convert-source-map@^1.7.0: 383 | version "1.8.0" 384 | resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.8.0.tgz#f3373c32d21b4d780dd8004514684fb791ca4369" 385 | integrity sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA== 386 | dependencies: 387 | safe-buffer "~5.1.1" 388 | 389 | csstype@^3.0.2: 390 | version "3.0.9" 391 | resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.9.tgz#6410af31b26bd0520933d02cbc64fce9ce3fbf0b" 392 | integrity sha512-rpw6JPxK6Rfg1zLOYCSwle2GFOOsnjmDYDaBwEcwoOg4qlsIVCN789VkBZDJAGi4T07gI4YSutR43t9Zz4Lzuw== 393 | 394 | debug@^4.1.0: 395 | version "4.3.2" 396 | resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b" 397 | integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw== 398 | dependencies: 399 | ms "2.1.2" 400 | 401 | electron-to-chromium@^1.3.867: 402 | version "1.3.871" 403 | resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.871.tgz#6e87365fd72037a6c898fb46050ad4be3ac9ef62" 404 | integrity sha512-qcLvDUPf8DSIMWarHT2ptgcqrYg62n3vPA7vhrOF24d8UNzbUBaHu2CySiENR3nEDzYgaN60071t0F6KLYMQ7Q== 405 | 406 | esbuild-android-arm64@0.13.8: 407 | version "0.13.8" 408 | resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.13.8.tgz#c20e875c3c98164b1ffba9b28637bdf96f5e9e7c" 409 | integrity sha512-AilbChndywpk7CdKkNSZ9klxl+9MboLctXd9LwLo3b0dawmOF/i/t2U5d8LM6SbT1Xw36F8yngSUPrd8yPs2RA== 410 | 411 | esbuild-darwin-64@0.13.8: 412 | version "0.13.8" 413 | resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.13.8.tgz#f46e6b471ddbf62265234808a6a1aa91df18a417" 414 | integrity sha512-b6sdiT84zV5LVaoF+UoMVGJzR/iE2vNUfUDfFQGrm4LBwM/PWXweKpuu6RD9mcyCq18cLxkP6w/LD/w9DtX3ng== 415 | 416 | esbuild-darwin-arm64@0.13.8: 417 | version "0.13.8" 418 | resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.13.8.tgz#a991157a6013facd4f2e14159b7da52626c90154" 419 | integrity sha512-R8YuPiiJayuJJRUBG4H0VwkEKo6AvhJs2m7Tl0JaIer3u1FHHXwGhMxjJDmK+kXwTFPriSysPvcobXC/UrrZCQ== 420 | 421 | esbuild-freebsd-64@0.13.8: 422 | version "0.13.8" 423 | resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.13.8.tgz#301601d2e443ad458960e359b402a17d9500be9d" 424 | integrity sha512-zBn6urrn8FnKC+YSgDxdof9jhPCeU8kR/qaamlV4gI8R3KUaUK162WYM7UyFVAlj9N0MyD3AtB+hltzu4cysTw== 425 | 426 | esbuild-freebsd-arm64@0.13.8: 427 | version "0.13.8" 428 | resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.13.8.tgz#039a63acc12ec0892006c147ea221e55f9125a9f" 429 | integrity sha512-pWW2slN7lGlkx0MOEBoUGwRX5UgSCLq3dy2c8RIOpiHtA87xAUpDBvZK10MykbT+aMfXc0NI2lu1X+6kI34xng== 430 | 431 | esbuild-linux-32@0.13.8: 432 | version "0.13.8" 433 | resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.13.8.tgz#c537b67d7e694b60bfa2786581412838c6ba0284" 434 | integrity sha512-T0I0ueeKVO/Is0CAeSEOG9s2jeNNb8jrrMwG9QBIm3UU18MRB60ERgkS2uV3fZ1vP2F8i3Z2e3Zju4lg9dhVmw== 435 | 436 | esbuild-linux-64@0.13.8: 437 | version "0.13.8" 438 | resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.13.8.tgz#0092fc8a064001a777bfa0e3b425bb8be8f96e6a" 439 | integrity sha512-Bm8SYmFtvfDCIu9sjKppFXzRXn2BVpuCinU1ChTuMtdKI/7aPpXIrkqBNOgPTOQO9AylJJc1Zw6EvtKORhn64w== 440 | 441 | esbuild-linux-arm64@0.13.8: 442 | version "0.13.8" 443 | resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.13.8.tgz#5cd3f2bb924212971482e8dbc25c4afd09b28110" 444 | integrity sha512-X4pWZ+SL+FJ09chWFgRNO3F+YtvAQRcWh0uxKqZSWKiWodAB20flsW/OWFYLXBKiVCTeoGMvENZS/GeVac7+tQ== 445 | 446 | esbuild-linux-arm@0.13.8: 447 | version "0.13.8" 448 | resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.13.8.tgz#ad634f96bf2975536907aeb9fdb75a3194f4ddce" 449 | integrity sha512-4/HfcC40LJ4GPyboHA+db0jpFarTB628D1ifU+/5bunIgY+t6mHkJWyxWxAAE8wl/ZIuRYB9RJFdYpu1AXGPdg== 450 | 451 | esbuild-linux-mips64le@0.13.8: 452 | version "0.13.8" 453 | resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.13.8.tgz#57857edfebf9bf65766dc8be1637f2179c990572" 454 | integrity sha512-o7e0D+sqHKT31v+mwFircJFjwSKVd2nbkHEn4l9xQ1hLR+Bv8rnt3HqlblY3+sBdlrOTGSwz0ReROlKUMJyldA== 455 | 456 | esbuild-linux-ppc64le@0.13.8: 457 | version "0.13.8" 458 | resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.13.8.tgz#fdb82a059a5b86bb10fb42091b4ebcf488b9cd46" 459 | integrity sha512-eZSQ0ERsWkukJp2px/UWJHVNuy0lMoz/HZcRWAbB6reoaBw7S9vMzYNUnflfL3XA6WDs+dZn3ekHE4Y2uWLGig== 460 | 461 | esbuild-netbsd-64@0.13.8: 462 | version "0.13.8" 463 | resolved "https://registry.yarnpkg.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.13.8.tgz#d7879e7123d3b2c04754ece8bd061aa6866deeff" 464 | integrity sha512-gZX4kP7gVvOrvX0ZwgHmbuHczQUwqYppxqtoyC7VNd80t5nBHOFXVhWo2Ad/Lms0E8b+wwgI/WjZFTCpUHOg9Q== 465 | 466 | esbuild-openbsd-64@0.13.8: 467 | version "0.13.8" 468 | resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.13.8.tgz#88b280b6cb0a3f6adb60abf27fc506c506a35cf0" 469 | integrity sha512-afzza308X4WmcebexbTzAgfEWt9MUkdTvwIa8xOu4CM2qGbl2LanqEl8/LUs8jh6Gqw6WsicEK52GPrS9wvkcw== 470 | 471 | esbuild-sunos-64@0.13.8: 472 | version "0.13.8" 473 | resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.13.8.tgz#229ae7c7703196a58acd0f0291ad9bebda815d63" 474 | integrity sha512-mWPZibmBbuMKD+LDN23LGcOZ2EawMYBONMXXHmbuxeT0XxCNwadbCVwUQ/2p5Dp5Kvf6mhrlIffcnWOiCBpiVw== 475 | 476 | esbuild-windows-32@0.13.8: 477 | version "0.13.8" 478 | resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.13.8.tgz#892d093e32a21c0c9135e5a0ffdc380aeb70e763" 479 | integrity sha512-QsZ1HnWIcnIEApETZWw8HlOhDSWqdZX2SylU7IzGxOYyVcX7QI06ety/aDcn437mwyO7Ph4RrbhB+2ntM8kX8A== 480 | 481 | esbuild-windows-64@0.13.8: 482 | version "0.13.8" 483 | resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.13.8.tgz#7defd8d79ae3bb7e6f53b65a7190be7daf901686" 484 | integrity sha512-76Fb57B9eE/JmJi1QmUW0tRLQZfGo0it+JeYoCDTSlbTn7LV44ecOHIMJSSgZADUtRMWT9z0Kz186bnaB3amSg== 485 | 486 | esbuild-windows-arm64@0.13.8: 487 | version "0.13.8" 488 | resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.13.8.tgz#e59ae004496fd8a5ab67bfc7945a2e47480d6fb9" 489 | integrity sha512-HW6Mtq5eTudllxY2YgT62MrVcn7oq2o8TAoAvDUhyiEmRmDY8tPwAhb1vxw5/cdkbukM3KdMYtksnUhF/ekWeg== 490 | 491 | esbuild@^0.13.2: 492 | version "0.13.8" 493 | resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.13.8.tgz#bd7cc51b881ab067789f88e17baca74724c1ec4f" 494 | integrity sha512-A4af7G7YZLfG5OnARJRMtlpEsCkq/zHZQXewgPA864l9D6VjjbH1SuFYK/OSV6BtHwDGkdwyRrX0qQFLnMfUcw== 495 | optionalDependencies: 496 | esbuild-android-arm64 "0.13.8" 497 | esbuild-darwin-64 "0.13.8" 498 | esbuild-darwin-arm64 "0.13.8" 499 | esbuild-freebsd-64 "0.13.8" 500 | esbuild-freebsd-arm64 "0.13.8" 501 | esbuild-linux-32 "0.13.8" 502 | esbuild-linux-64 "0.13.8" 503 | esbuild-linux-arm "0.13.8" 504 | esbuild-linux-arm64 "0.13.8" 505 | esbuild-linux-mips64le "0.13.8" 506 | esbuild-linux-ppc64le "0.13.8" 507 | esbuild-netbsd-64 "0.13.8" 508 | esbuild-openbsd-64 "0.13.8" 509 | esbuild-sunos-64 "0.13.8" 510 | esbuild-windows-32 "0.13.8" 511 | esbuild-windows-64 "0.13.8" 512 | esbuild-windows-arm64 "0.13.8" 513 | 514 | escalade@^3.1.1: 515 | version "3.1.1" 516 | resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" 517 | integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== 518 | 519 | escape-string-regexp@^1.0.5: 520 | version "1.0.5" 521 | resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" 522 | integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= 523 | 524 | estree-walker@^2.0.1: 525 | version "2.0.2" 526 | resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac" 527 | integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w== 528 | 529 | fast-json-patch@^3.1.0: 530 | version "3.1.0" 531 | resolved "https://registry.yarnpkg.com/fast-json-patch/-/fast-json-patch-3.1.0.tgz#ec8cd9b9c4c564250ec8b9140ef7a55f70acaee6" 532 | integrity sha512-IhpytlsVTRndz0hU5t0/MGzS/etxLlfrpG5V5M9mVbuj9TrJLWaMfsox9REM5rkuGX0T+5qjpe8XA1o0gZ42nA== 533 | 534 | fill-range@^7.0.1: 535 | version "7.0.1" 536 | resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" 537 | integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== 538 | dependencies: 539 | to-regex-range "^5.0.1" 540 | 541 | fsevents@~2.3.2: 542 | version "2.3.2" 543 | resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" 544 | integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== 545 | 546 | function-bind@^1.1.1: 547 | version "1.1.1" 548 | resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" 549 | integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== 550 | 551 | gensync@^1.0.0-beta.2: 552 | version "1.0.0-beta.2" 553 | resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" 554 | integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== 555 | 556 | glob-parent@~5.1.2: 557 | version "5.1.2" 558 | resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" 559 | integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== 560 | dependencies: 561 | is-glob "^4.0.1" 562 | 563 | globals@^11.1.0: 564 | version "11.12.0" 565 | resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" 566 | integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== 567 | 568 | has-flag@^3.0.0: 569 | version "3.0.0" 570 | resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" 571 | integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= 572 | 573 | has@^1.0.3: 574 | version "1.0.3" 575 | resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" 576 | integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== 577 | dependencies: 578 | function-bind "^1.1.1" 579 | 580 | is-binary-path@~2.1.0: 581 | version "2.1.0" 582 | resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" 583 | integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== 584 | dependencies: 585 | binary-extensions "^2.0.0" 586 | 587 | is-core-module@^2.2.0: 588 | version "2.8.0" 589 | resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.8.0.tgz#0321336c3d0925e497fd97f5d95cb114a5ccd548" 590 | integrity sha512-vd15qHsaqrRL7dtH6QNuy0ndJmRDrS9HAM1CAiSifNUFv4x1a0CCVsj18hJ1mShxIG6T2i1sO78MkP56r0nYRw== 591 | dependencies: 592 | has "^1.0.3" 593 | 594 | is-extglob@^2.1.1: 595 | version "2.1.1" 596 | resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" 597 | integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= 598 | 599 | is-glob@^4.0.1, is-glob@~4.0.1: 600 | version "4.0.3" 601 | resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" 602 | integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== 603 | dependencies: 604 | is-extglob "^2.1.1" 605 | 606 | is-number@^7.0.0: 607 | version "7.0.0" 608 | resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" 609 | integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== 610 | 611 | "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: 612 | version "4.0.0" 613 | resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" 614 | integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== 615 | 616 | jsesc@^2.5.1: 617 | version "2.5.2" 618 | resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" 619 | integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== 620 | 621 | json5@^2.1.2: 622 | version "2.2.0" 623 | resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.0.tgz#2dfefe720c6ba525d9ebd909950f0515316c89a3" 624 | integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA== 625 | dependencies: 626 | minimist "^1.2.5" 627 | 628 | loose-envify@^1.1.0: 629 | version "1.4.0" 630 | resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" 631 | integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== 632 | dependencies: 633 | js-tokens "^3.0.0 || ^4.0.0" 634 | 635 | minimist@^1.2.5: 636 | version "1.2.5" 637 | resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" 638 | integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== 639 | 640 | ms@2.1.2: 641 | version "2.1.2" 642 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" 643 | integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== 644 | 645 | nanoid@^3.1.28: 646 | version "3.1.30" 647 | resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.30.tgz#63f93cc548d2a113dc5dfbc63bfa09e2b9b64362" 648 | integrity sha512-zJpuPDwOv8D2zq2WRoMe1HsfZthVewpel9CAvTfc/2mBD1uUT/agc5f7GHGWXlYkFvi1mVxe4IjvP2HNrop7nQ== 649 | 650 | node-releases@^2.0.0: 651 | version "2.0.0" 652 | resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.0.tgz#67dc74903100a7deb044037b8a2e5f453bb05400" 653 | integrity sha512-aA87l0flFYMzCHpTM3DERFSYxc6lv/BltdbRTOMZuxZ0cwZCD3mejE5n9vLhSJCN++/eOqr77G1IO5uXxlQYWA== 654 | 655 | normalize-path@^3.0.0, normalize-path@~3.0.0: 656 | version "3.0.0" 657 | resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" 658 | integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== 659 | 660 | object-assign@^4.1.1: 661 | version "4.1.1" 662 | resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" 663 | integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= 664 | 665 | path-parse@^1.0.6: 666 | version "1.0.7" 667 | resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" 668 | integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== 669 | 670 | picocolors@^0.2.1: 671 | version "0.2.1" 672 | resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-0.2.1.tgz#570670f793646851d1ba135996962abad587859f" 673 | integrity sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA== 674 | 675 | picocolors@^1.0.0: 676 | version "1.0.0" 677 | resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" 678 | integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== 679 | 680 | picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.2: 681 | version "2.3.0" 682 | resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972" 683 | integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw== 684 | 685 | postcss@^8.3.8: 686 | version "8.3.9" 687 | resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.3.9.tgz#98754caa06c4ee9eb59cc48bd073bb6bd3437c31" 688 | integrity sha512-f/ZFyAKh9Dnqytx5X62jgjhhzttjZS7hMsohcI7HEI5tjELX/HxCy3EFhsRxyzGvrzFF+82XPvCS8T9TFleVJw== 689 | dependencies: 690 | nanoid "^3.1.28" 691 | picocolors "^0.2.1" 692 | source-map-js "^0.6.2" 693 | 694 | prettier@^2.4.1: 695 | version "2.4.1" 696 | resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.4.1.tgz#671e11c89c14a4cfc876ce564106c4a6726c9f5c" 697 | integrity sha512-9fbDAXSBcc6Bs1mZrDYb3XKzDLm4EXXL9sC1LqKP5rZkT6KRr/rf9amVUcODVXgguK/isJz0d0hP72WeaKWsvA== 698 | 699 | react-dom@^17.0.0: 700 | version "17.0.2" 701 | resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23" 702 | integrity sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA== 703 | dependencies: 704 | loose-envify "^1.1.0" 705 | object-assign "^4.1.1" 706 | scheduler "^0.20.2" 707 | 708 | react-refresh@^0.10.0: 709 | version "0.10.0" 710 | resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.10.0.tgz#2f536c9660c0b9b1d500684d9e52a65e7404f7e3" 711 | integrity sha512-PgidR3wST3dDYKr6b4pJoqQFpPGNKDSCDx4cZoshjXipw3LzO7mG1My2pwEzz2JVkF+inx3xRpDeQLFQGH/hsQ== 712 | 713 | react@^17.0.0: 714 | version "17.0.2" 715 | resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037" 716 | integrity sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA== 717 | dependencies: 718 | loose-envify "^1.1.0" 719 | object-assign "^4.1.1" 720 | 721 | readdirp@~3.6.0: 722 | version "3.6.0" 723 | resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" 724 | integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== 725 | dependencies: 726 | picomatch "^2.2.1" 727 | 728 | resolve@^1.20.0: 729 | version "1.20.0" 730 | resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" 731 | integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A== 732 | dependencies: 733 | is-core-module "^2.2.0" 734 | path-parse "^1.0.6" 735 | 736 | rollup@^2.57.0: 737 | version "2.58.0" 738 | resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.58.0.tgz#a643983365e7bf7f5b7c62a8331b983b7c4c67fb" 739 | integrity sha512-NOXpusKnaRpbS7ZVSzcEXqxcLDOagN6iFS8p45RkoiMqPHDLwJm758UF05KlMoCRbLBTZsPOIa887gZJ1AiXvw== 740 | optionalDependencies: 741 | fsevents "~2.3.2" 742 | 743 | safe-buffer@~5.1.1: 744 | version "5.1.2" 745 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" 746 | integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== 747 | 748 | sass@=1.32.8: 749 | version "1.32.8" 750 | resolved "https://registry.yarnpkg.com/sass/-/sass-1.32.8.tgz#f16a9abd8dc530add8834e506878a2808c037bdc" 751 | integrity sha512-Sl6mIeGpzjIUZqvKnKETfMf0iDAswD9TNlv13A7aAF3XZlRPMq4VvJWBC2N2DXbp94MQVdNSFG6LfF/iOXrPHQ== 752 | dependencies: 753 | chokidar ">=2.0.0 <4.0.0" 754 | 755 | scheduler@^0.20.2: 756 | version "0.20.2" 757 | resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.20.2.tgz#4baee39436e34aa93b4874bddcbf0fe8b8b50e91" 758 | integrity sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ== 759 | dependencies: 760 | loose-envify "^1.1.0" 761 | object-assign "^4.1.1" 762 | 763 | semver@^6.3.0: 764 | version "6.3.0" 765 | resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" 766 | integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== 767 | 768 | source-map-js@^0.6.2: 769 | version "0.6.2" 770 | resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-0.6.2.tgz#0bb5de631b41cfbda6cfba8bd05a80efdfd2385e" 771 | integrity sha512-/3GptzWzu0+0MBQFrDKzw/DvvMTUORvgY6k6jd/VS6iCR4RDTKWH6v6WPwQoUO8667uQEf9Oe38DxAYWY5F/Ug== 772 | 773 | source-map@^0.5.0: 774 | version "0.5.7" 775 | resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" 776 | integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= 777 | 778 | spectre.css@^0.5.9: 779 | version "0.5.9" 780 | resolved "https://registry.yarnpkg.com/spectre.css/-/spectre.css-0.5.9.tgz#86c732d093036d9fdc0a2ba570f005e4023ae6ca" 781 | integrity sha512-9jUqwZmCnvflrxFGcK+ize43TvjwDjqMwZPVubEtSIHzvinH0TBUESm1LcOJx3Ur7bdPaeOHQIjOqBl1Y5kLFw== 782 | 783 | supports-color@^5.3.0: 784 | version "5.5.0" 785 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" 786 | integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== 787 | dependencies: 788 | has-flag "^3.0.0" 789 | 790 | to-fast-properties@^2.0.0: 791 | version "2.0.0" 792 | resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" 793 | integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= 794 | 795 | to-regex-range@^5.0.1: 796 | version "5.0.1" 797 | resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" 798 | integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== 799 | dependencies: 800 | is-number "^7.0.0" 801 | 802 | typescript@^4.3.2: 803 | version "4.4.4" 804 | resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.4.4.tgz#2cd01a1a1f160704d3101fd5a58ff0f9fcb8030c" 805 | integrity sha512-DqGhF5IKoBl8WNf8C1gu8q0xZSInh9j1kJJMqT3a94w1JzVaBU4EXOSMrz9yDqMT0xt3selp83fuFMQ0uzv6qA== 806 | 807 | vite@^2.6.4: 808 | version "2.6.7" 809 | resolved "https://registry.yarnpkg.com/vite/-/vite-2.6.7.tgz#e15c1d8327950720b5d7c4ec3fb36a5a58ccf7cb" 810 | integrity sha512-ewk//jve9k6vlU8PfJmWUHN8k0YYdw4VaKOMvoQ3nT2Pb6k5OSMKQi4jPOzVH/TlUqMsCrq7IJ80xcuDDVyigg== 811 | dependencies: 812 | esbuild "^0.13.2" 813 | postcss "^8.3.8" 814 | resolve "^1.20.0" 815 | rollup "^2.57.0" 816 | optionalDependencies: 817 | fsevents "~2.3.2" 818 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use std::{net::SocketAddr, sync::Arc}; 2 | 3 | use axum::{ 4 | extract::{ws::WebSocketUpgrade, Extension}, 5 | response::IntoResponse, 6 | routing::get, 7 | Router, 8 | }; 9 | use tracing::*; 10 | 11 | mod todo; 12 | mod websocket; 13 | 14 | use websocket::{handle_socket, WsState}; 15 | 16 | #[tokio::main] 17 | async fn main() { 18 | // Set a sensible default for logging to ensure we see something 19 | if std::env::var_os("RUST_LOG").is_none() { 20 | std::env::set_var("RUST_LOG", "websocket_jsonpatch=debug") 21 | } 22 | 23 | // Initialise the `fmt` subscriber which will print logs to stderr 24 | tracing_subscriber::fmt::init(); 25 | 26 | // Add in our application with the websocket handler 27 | let app = Router::new() 28 | .route("/ws", get(ws_handler)) 29 | // Add in a shared websocket state struct `WsState` 30 | .layer(Extension(Arc::new(WsState::new()))); 31 | 32 | // Listen on port 3333 for connections 33 | let addr: SocketAddr = "127.0.0.1:3333".parse().unwrap(); 34 | 35 | debug!("Listening for requests on {}", addr); 36 | 37 | // Start up the server 38 | axum::Server::bind(&addr) 39 | .serve(app.into_make_service()) 40 | .await 41 | .unwrap(); 42 | } 43 | 44 | async fn ws_handler( 45 | ws: WebSocketUpgrade, 46 | Extension(state): Extension>, 47 | ) -> impl IntoResponse { 48 | debug!("New Websocket Connection"); 49 | ws.on_upgrade(|socket| handle_socket(socket, state)) 50 | } 51 | -------------------------------------------------------------------------------- /src/todo.rs: -------------------------------------------------------------------------------- 1 | use std::collections::BTreeMap; 2 | 3 | use serde::{Deserialize, Serialize}; 4 | 5 | #[derive(Serialize, Deserialize, Default, Debug, Clone)] 6 | pub struct Todo { 7 | // The main name of the todo list 8 | name: String, 9 | // The list of todos 10 | todos: BTreeMap, 11 | } 12 | 13 | #[derive(Serialize, Deserialize, Default, Debug, Clone)] 14 | pub struct TodoRow { 15 | // The name of the specific todo 16 | name: String, 17 | // Whether this todo is completed or not 18 | completed: bool, 19 | } 20 | 21 | #[derive(Deserialize)] 22 | #[serde(tag = "type")] 23 | pub enum TodoAction { 24 | Add { row: TodoRow }, 25 | ChangeName { name: String }, 26 | Update { index: u32, row: TodoRow }, 27 | Remove { index: u32 }, 28 | RemoveCompleted, 29 | } 30 | 31 | impl Todo { 32 | pub fn apply(&mut self, action: TodoAction) { 33 | match action { 34 | TodoAction::Add { row } => { 35 | // Find the next available index 36 | let index = self.todos.keys().max().copied().unwrap_or_default() + 1; 37 | 38 | // Insert this into our map 39 | self.todos.insert(index, row); 40 | } 41 | TodoAction::Update { row, index } => { 42 | self.todos.insert(index, row); 43 | } 44 | // Change the name of the todo list 45 | TodoAction::ChangeName { name } => self.name = name, 46 | TodoAction::Remove { index } => { 47 | self.todos.remove(&index); 48 | } 49 | // Filter and remove all completed todo rows 50 | TodoAction::RemoveCompleted => self.todos.retain(|_, val| !val.completed), 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/websocket.rs: -------------------------------------------------------------------------------- 1 | use std::mem; 2 | use std::sync::Arc; 3 | 4 | use axum::extract::ws::{Message, WebSocket}; 5 | use futures::{stream::SplitSink, SinkExt, StreamExt}; 6 | use json_patch::{diff, PatchOperation}; 7 | use serde::Serialize; 8 | use serde_json::Error; 9 | use tokio::sync::Mutex; 10 | use tracing::*; 11 | 12 | use crate::todo::{Todo, TodoAction}; 13 | 14 | pub struct WsState { 15 | // Our main state, behind a tokio Mutex 16 | todo: Mutex, 17 | // A list of sessions we will send changes to 18 | txs: Mutex>>, 19 | } 20 | 21 | impl WsState { 22 | pub fn new() -> Self { 23 | WsState { 24 | todo: Mutex::new(Todo::default()), 25 | txs: Mutex::new(Vec::default()), 26 | } 27 | } 28 | 29 | async fn add_session(&self, mut tx: SplitSink) { 30 | let mut txs = self.txs.lock().await; 31 | 32 | if let Err(err) = tx 33 | .send(Message::Text( 34 | // This method will not fail in "normal" operations so an `expect()` is OK here 35 | serde_json::to_string(&ServerMessage::Full { 36 | todo: &*self.todo.lock().await, 37 | }) 38 | .expect("Serialize Error"), 39 | )) 40 | .await 41 | { 42 | warn!("Could not send initial state update: {}", err); 43 | return; 44 | } 45 | 46 | // Add session to our list of sessions 47 | txs.push(tx); 48 | } 49 | 50 | async fn apply(&self, action: TodoAction) -> Result<(), Error> { 51 | // Grab a mutable reference 52 | let mut state = self.todo.lock().await; 53 | 54 | // Serialize out the existing JSON for diffing later on 55 | let existing_json = serde_json::to_value(&*state)?; 56 | 57 | // Apply the action to our todo list. This mutates it in place 58 | state.apply(action); 59 | 60 | // Serialize out the new JSON for diffing 61 | let new_json = serde_json::to_value(&*state)?; 62 | 63 | // Get the changes using the `diff` method from `json_patch` 64 | let ops = diff(&existing_json, &new_json).0; 65 | 66 | debug!("New Patches:{:?}", ops); 67 | 68 | // If there are no changes, don't bother broadcasting 69 | if !ops.is_empty() { 70 | let message = serde_json::to_string(&ServerMessage::Patch { ops })?; 71 | 72 | let mut txs = self.txs.lock().await; 73 | 74 | // We take all the txs to iterate, and replace with an empty `Vec` 75 | for mut tx in mem::take(&mut *txs) { 76 | // If there is an issue sending a message we will warn about it 77 | if let Err(err) = tx.send(Message::Text(message.clone())).await { 78 | warn!("Client disconnected: {}", err); 79 | // If there is no issue sending, then we add it back to our `Vec` 80 | } else { 81 | txs.push(tx) 82 | } 83 | } 84 | } 85 | 86 | Ok(()) 87 | } 88 | } 89 | 90 | #[derive(Serialize, Debug)] 91 | #[serde(tag = "type")] 92 | enum ServerMessage<'a> { 93 | Patch { ops: Vec }, 94 | Full { todo: &'a Todo }, 95 | } 96 | 97 | pub async fn handle_socket(socket: WebSocket, state: Arc) { 98 | let (tx, mut rx) = socket.split(); 99 | 100 | // Add tx to our list of sessions for broadcasting later 101 | state.add_session(tx).await; 102 | 103 | // Loop until there are no messages or an error 104 | while let Some(Ok(msg)) = rx.next().await { 105 | if let Message::Text(text) = msg { 106 | // Decode our message and warn if it's something we don't know about 107 | if let Ok(action) = serde_json::from_str::(&text) { 108 | // Apply the state, which will broadcast out changes as a JSON patch 109 | if let Err(err) = state.apply(action).await { 110 | warn!("Error applying state:{}", err); 111 | } 112 | } else { 113 | warn!("Unknown action received:{}", text); 114 | } 115 | } 116 | } 117 | } 118 | --------------------------------------------------------------------------------