some basic viewer

This commit is contained in:
Robin Appelman 2023-04-29 14:54:53 +02:00
commit 5910b2f35a
45 changed files with 1089 additions and 1436 deletions

384
Cargo.lock generated
View file

@ -33,7 +33,7 @@ version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
dependencies = [
"getrandom 0.2.9",
"getrandom",
"once_cell",
"serde",
"version_check",
@ -41,9 +41,9 @@ dependencies = [
[[package]]
name = "aho-corasick"
version = "0.7.20"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac"
checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04"
dependencies = [
"memchr",
]
@ -127,7 +127,7 @@ dependencies = [
"chrono",
"hmac 0.11.0",
"log",
"rand 0.8.5",
"rand",
"serde",
"serde_json",
"sha2 0.9.9",
@ -184,9 +184,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "axum"
version = "0.6.15"
version = "0.6.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b32c5ea3aabaf4deb5f5ced2d688ec0844c881c9e6c696a8b769a05fc691e62"
checksum = "113713495a32dd0ab52baf5c10044725aa3aec00b31beda84218e469029b72a3"
dependencies = [
"async-trait",
"axum-core",
@ -307,9 +307,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.1.0"
version = "2.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c70beb79cbb5ce9c4f8e20849978f34225931f665bb49efa6982875a4d5facb3"
checksum = "24a6904aef64d73cf10ab17ebace7befb918b82164785cb89907993be7f83813"
[[package]]
name = "blake3"
@ -394,9 +394,9 @@ dependencies = [
[[package]]
name = "bumpalo"
version = "3.12.0"
version = "3.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535"
checksum = "9b1ce199063694f33ffb7dd4e0ee620741495c32833cde5aa08f02a0bf96f0c8"
[[package]]
name = "bytecheck"
@ -566,9 +566,9 @@ checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa"
[[package]]
name = "cpufeatures"
version = "0.2.6"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "280a9f2d8b3a38871a3c8a46fb80db65e5e5ed97da80c4d08bf27fb63e35e181"
checksum = "3e4c1eaa2012c47becbbad2ab175484c2a84d1185b566fb2cc5b8707343dfe58"
dependencies = [
"libc",
]
@ -696,7 +696,7 @@ dependencies = [
"dtoa-short",
"itoa",
"matches",
"phf 0.10.1",
"phf",
"proc-macro2",
"quote",
"smallvec",
@ -801,7 +801,7 @@ version = "0.1.0"
dependencies = [
"demostf-build-bundlers",
"demostf-build-derive",
"rand 0.8.5",
"rand",
"tracing-subscriber",
]
@ -814,7 +814,7 @@ dependencies = [
"fnv",
"jsx-dom-expressions",
"lightningcss",
"rand 0.8.5",
"rand",
"swc",
"swc_atoms",
"swc_bundler",
@ -1065,7 +1065,7 @@ dependencies = [
"itertools",
"maud",
"quick-xml",
"rand 0.8.5",
"rand",
"reqwest",
"sea-query",
"sea-query-binder",
@ -1166,17 +1166,6 @@ dependencies = [
"version_check",
]
[[package]]
name = "getrandom"
version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
dependencies = [
"cfg-if 1.0.0",
"libc",
"wasi 0.9.0+wasi-snapshot-preview1",
]
[[package]]
name = "getrandom"
version = "0.2.9"
@ -1214,9 +1203,9 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
[[package]]
name = "h2"
version = "0.3.17"
version = "0.3.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "66b91535aa35fea1523ad1b86cb6b53c28e0ae566ba4a460f4457e936cad7c6f"
checksum = "17f8a914c2987b688368b5138aa05321db91f4090cf26118185672ad588bce21"
dependencies = [
"bytes",
"fnv",
@ -1583,7 +1572,7 @@ dependencies = [
[[package]]
name = "jsx-dom-expressions"
version = "0.1.0"
source = "git+https://github.com/icewind1991/swc-plugin-jsx-dom-expressions?branch=event#53bb986d0e1abb627d3726378423b55adbad5e5f"
source = "git+https://github.com/icewind1991/swc-plugin-jsx-dom-expressions?branch=mixed-svg#56de4af8df415d0bdd4f578586e47c9db312502f"
dependencies = [
"html-escape",
"serde",
@ -1674,16 +1663,17 @@ dependencies = [
[[package]]
name = "libc"
version = "0.2.141"
version = "0.2.142"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5"
checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317"
[[package]]
name = "lightningcss"
version = "1.0.0-alpha.40"
version = "1.0.0-alpha.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6338806873ab8053fa9aac727f603fdd2fe4d8bec192faab114183f8483c67f8"
checksum = "05745e54889965f22e544ccf6d8182656275eece23355e13550e76d48ad42bc1"
dependencies = [
"ahash",
"bitflags 1.3.2",
"browserslist-rs 0.7.0",
"const-str 0.3.2",
@ -1703,9 +1693,9 @@ dependencies = [
[[package]]
name = "lightningcss-derive"
version = "1.0.0-alpha.37"
version = "1.0.0-alpha.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4faebe0508afdfe088a3221013aecbb7014ecf8504b9c5a6764bb58d29f932b1"
checksum = "d9208a9dfcf23ce4d6e3cdaa945f87af18fffa04203cf849a5fb196f46d36d2e"
dependencies = [
"proc-macro2",
"quote",
@ -1729,9 +1719,9 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
[[package]]
name = "linux-raw-sys"
version = "0.3.1"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d59d8c75012853d2e872fb56bc8a2e53718e2cafe1a4c823143141c6d90c322f"
checksum = "36eb31c1778188ae1e64398743890d0877fef36d11521ac60406b42016e8c2cf"
[[package]]
name = "lock_api"
@ -2099,9 +2089,9 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
[[package]]
name = "openssl"
version = "0.10.50"
version = "0.10.51"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e30d8bc91859781f0a943411186324d580f2bbeb71b452fe91ae344806af3f1"
checksum = "97ea2d98598bf9ada7ea6ee8a30fb74f9156b63bbe495d64ec2b87c269d2dda3"
dependencies = [
"bitflags 1.3.2",
"cfg-if 1.0.0",
@ -2131,9 +2121,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
[[package]]
name = "openssl-sys"
version = "0.9.85"
version = "0.9.86"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d3d193fb1488ad46ffe3aaabc912cc931d02ee8518fe2959aea8ef52718b0c0"
checksum = "992bac49bdbab4423199c654a5515bd2a6c6a23bf03f2dd3bdb7e5ae6259bc69"
dependencies = [
"cc",
"libc",
@ -2180,15 +2170,15 @@ checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f"
[[package]]
name = "parcel_selectors"
version = "0.25.1"
version = "0.25.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a215f11d4e3b505238167f6dbe47e7d809dcf452319506328d904d62c4eb8da5"
checksum = "537fd75055e0ed78c7820d0ac8f6407dd813d760995cd2252022a5514df7aecd"
dependencies = [
"bitflags 1.3.2",
"cssparser",
"fxhash",
"log",
"phf 0.8.0",
"phf",
"phf_codegen",
"precomputed-hash",
"smallvec",
@ -2334,15 +2324,6 @@ dependencies = [
"indexmap",
]
[[package]]
name = "phf"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12"
dependencies = [
"phf_shared 0.8.0",
]
[[package]]
name = "phf"
version = "0.10.1"
@ -2350,28 +2331,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259"
dependencies = [
"phf_macros",
"phf_shared 0.10.0",
"phf_shared",
"proc-macro-hack",
]
[[package]]
name = "phf_codegen"
version = "0.8.0"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815"
checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd"
dependencies = [
"phf_generator 0.8.0",
"phf_shared 0.8.0",
]
[[package]]
name = "phf_generator"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526"
dependencies = [
"phf_shared 0.8.0",
"rand 0.7.3",
"phf_generator",
"phf_shared",
]
[[package]]
@ -2380,8 +2351,8 @@ version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6"
dependencies = [
"phf_shared 0.10.0",
"rand 0.8.5",
"phf_shared",
"rand",
]
[[package]]
@ -2390,23 +2361,14 @@ version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58fdf3184dd560f160dd73922bea2d5cd6e8f064bf4b13110abd81b03697b4e0"
dependencies = [
"phf_generator 0.10.0",
"phf_shared 0.10.0",
"phf_generator",
"phf_shared",
"proc-macro-hack",
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "phf_shared"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7"
dependencies = [
"siphasher",
]
[[package]]
name = "phf_shared"
version = "0.10.0"
@ -2479,9 +2441,9 @@ checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
[[package]]
name = "preset_env_base"
version = "0.4.1"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c963ac17c08dfc36f01b7d2c4426e759ac1cbd181c2a9ed807f3dea5200b90e1"
checksum = "b09a48d8ea8b031bde7755cdf6f87f7123a0cbefc36b0cd09cbb2de726594393"
dependencies = [
"ahash",
"anyhow",
@ -2600,20 +2562,6 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce082a9940a7ace2ad4a8b7d0b1eac6aa378895f18be598230c5f2284ac05426"
[[package]]
name = "rand"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
dependencies = [
"getrandom 0.1.16",
"libc",
"rand_chacha 0.2.2",
"rand_core 0.5.1",
"rand_hc",
"rand_pcg",
]
[[package]]
name = "rand"
version = "0.8.5"
@ -2621,18 +2569,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
"rand_chacha 0.3.1",
"rand_core 0.6.4",
]
[[package]]
name = "rand_chacha"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
dependencies = [
"ppv-lite86",
"rand_core 0.5.1",
"rand_chacha",
"rand_core",
]
[[package]]
@ -2642,16 +2580,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core 0.6.4",
]
[[package]]
name = "rand_core"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
dependencies = [
"getrandom 0.1.16",
"rand_core",
]
[[package]]
@ -2660,25 +2589,7 @@ version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom 0.2.9",
]
[[package]]
name = "rand_hc"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
dependencies = [
"rand_core 0.5.1",
]
[[package]]
name = "rand_pcg"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429"
dependencies = [
"rand_core 0.5.1",
"getrandom",
]
[[package]]
@ -2727,20 +2638,20 @@ version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b"
dependencies = [
"getrandom 0.2.9",
"getrandom",
"redox_syscall 0.2.16",
"thiserror",
]
[[package]]
name = "regex"
version = "1.7.3"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b1f693b24f6ac912f4893ef08244d70b6067480d2f1a46e950c9691e6749d1d"
checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
"regex-syntax 0.7.1",
]
[[package]]
@ -2749,7 +2660,7 @@ version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
dependencies = [
"regex-syntax",
"regex-syntax 0.6.29",
]
[[package]]
@ -2758,6 +2669,12 @@ version = "0.6.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
[[package]]
name = "regex-syntax"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c"
[[package]]
name = "relative-path"
version = "1.8.0"
@ -2873,9 +2790,9 @@ dependencies = [
[[package]]
name = "rustc-demangle"
version = "0.1.22"
version = "0.1.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4a36c42d1873f9a77c53bde094f9664d9891bc604a45b4798fd2c389ed12e5b"
checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
[[package]]
name = "rustc-hash"
@ -2894,9 +2811,9 @@ dependencies = [
[[package]]
name = "rustix"
version = "0.37.11"
version = "0.37.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85597d61f83914ddeba6a47b3b8ffe7365107221c2e557ed94426489fefb5f77"
checksum = "d9b864d3c18a5785a05953adeed93e2dca37ed30f18e69bba9f30079d51f363f"
dependencies = [
"bitflags 1.3.2",
"errno",
@ -3357,7 +3274,7 @@ dependencies = [
"once_cell",
"paste",
"percent-encoding",
"rand 0.8.5",
"rand",
"rustls",
"rustls-pemfile",
"serde",
@ -3408,9 +3325,9 @@ dependencies = [
[[package]]
name = "st-map"
version = "0.1.6"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc9c9f3a1df5f73b7392bd9773108fef41ad9126f0282412fd5904389f0c0c4f"
checksum = "f09d891835f076b0d4a58dd4478fb54d47aa3da1f7a4c6e89ad6c791357ab5ed"
dependencies = [
"arrayvec 0.7.2",
"static-map-macro",
@ -3437,9 +3354,9 @@ dependencies = [
[[package]]
name = "static-map-macro"
version = "0.2.3"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "752564de9cd8937fdbc1c55d47ac391758c352ab3755607cc391b659fe87d56b"
checksum = "b862d598fbc9f7085b017890e2e61433f501e7467f2c585323e1aa3c07ef8599"
dependencies = [
"pmutil",
"proc-macro2",
@ -3491,7 +3408,7 @@ dependencies = [
"new_debug_unreachable",
"once_cell",
"parking_lot 0.12.1",
"phf_shared 0.10.0",
"phf_shared",
"precomputed-hash",
"serde",
]
@ -3502,8 +3419,8 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6bb30289b722be4ff74a408c3cc27edeaad656e06cb1fe8fa9231fa59c728988"
dependencies = [
"phf_generator 0.10.0",
"phf_shared 0.10.0",
"phf_generator",
"phf_shared",
"proc-macro2",
"quote",
]
@ -3590,9 +3507,9 @@ dependencies = [
[[package]]
name = "swc"
version = "0.260.22"
version = "0.260.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d532de929cc1827ed29a577ff9e8f4ca35cffa26223eda26a2993d0090645689"
checksum = "9609c6488e028451160088095d86a774e549a8077d7a2bac01594d27ef920795"
dependencies = [
"ahash",
"anyhow",
@ -3653,9 +3570,9 @@ dependencies = [
[[package]]
name = "swc_bundler"
version = "0.213.16"
version = "0.213.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "898fdbc4f307a6c3dbf9b51fbcc7935be1860959f847b03671d67d4ed8635b7a"
checksum = "7bd31956ba105867d4320f47276e5b47ab6c9de991fb2333dcdc962a7d4b341c"
dependencies = [
"ahash",
"anyhow",
@ -3698,9 +3615,9 @@ dependencies = [
[[package]]
name = "swc_common"
version = "0.31.4"
version = "0.31.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b557014d62318e08070c2a3d5eb0278ff73749dd69db53c39a4de4bcd301d6a"
checksum = "6f876f826866e402da364d77aa97448fdf67cb4aeb6a5f1c0de39cacf35aa89a"
dependencies = [
"ahash",
"anyhow",
@ -3756,9 +3673,9 @@ dependencies = [
[[package]]
name = "swc_core"
version = "0.75.22"
version = "0.75.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c055b088e0c1ccb5797b1f7b135d2afecbe8386755c0f0967d079ca65d446ed2"
checksum = "c536dec38f39b9bc2c7fff9d073375c443519300f0ab45c0ff36a4a41c0baf6c"
dependencies = [
"once_cell",
"swc_atoms",
@ -3776,11 +3693,11 @@ dependencies = [
[[package]]
name = "swc_ecma_ast"
version = "0.103.4"
version = "0.103.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5206233430a6763e2759da76cfc596a64250793f70cd94cace1f82fdcc4d702c"
checksum = "1087786027e9e938588c0a66d45d1044a5e2285922b0928e023303f03a60900f"
dependencies = [
"bitflags 2.1.0",
"bitflags 2.2.1",
"is-macro",
"num-bigint 0.4.3",
"rkyv",
@ -3794,9 +3711,9 @@ dependencies = [
[[package]]
name = "swc_ecma_codegen"
version = "0.138.9"
version = "0.138.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd67009c5208689787f9fc59265deaeddad68d3e59da909e8db4615bb8c1d4a9"
checksum = "57dbd98c65fc3fb0b9a441da1ded7e02bdecafe7c7a757a872a6c4526ca26127"
dependencies = [
"memchr",
"num-bigint 0.4.3",
@ -3826,11 +3743,11 @@ dependencies = [
[[package]]
name = "swc_ecma_ext_transforms"
version = "0.102.8"
version = "0.102.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3aece9e36d47a48d49301d358d2c22a52918e8447c7679e2622c9fb366fdc6d"
checksum = "96ac924af6552610a6d66f4066c2f25e42a32f51d246e4d05467631f5c09c48e"
dependencies = [
"phf 0.10.1",
"phf",
"swc_atoms",
"swc_common",
"swc_ecma_ast",
@ -3840,9 +3757,9 @@ dependencies = [
[[package]]
name = "swc_ecma_lints"
version = "0.81.10"
version = "0.81.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44afd61c03093baca78f20eb5698f8ac4470afe8891529dcf62ae9ebf6c8c617"
checksum = "0be7d12d1ed80cf5299fa1ad828cab753e10eb55aa68420cfa7dfba241d0e580"
dependencies = [
"ahash",
"auto_impl",
@ -3861,9 +3778,9 @@ dependencies = [
[[package]]
name = "swc_ecma_loader"
version = "0.43.5"
version = "0.43.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9af852e780a8c33a7215139164472eca9edd3b2c38fb60fb9dc85500239195c8"
checksum = "1b853be4b7380384dc3bac5564697c1ba30b47eff94b81449567032fa44d3d0c"
dependencies = [
"ahash",
"anyhow",
@ -3883,9 +3800,9 @@ dependencies = [
[[package]]
name = "swc_ecma_minifier"
version = "0.180.16"
version = "0.180.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "954cd73cfd3477ddf69c67e7a797aaffe053aaa3ff0087d6696d3c942c868634"
checksum = "c70a15eb2beb998ed7abc9991f459bee8189c73c7842fd6390050ad063fd6ad1"
dependencies = [
"ahash",
"arrayvec 0.7.2",
@ -3918,9 +3835,9 @@ dependencies = [
[[package]]
name = "swc_ecma_parser"
version = "0.133.8"
version = "0.133.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5341644ae5e8d143db12c43dcd06d31138c0dbda7db9db31ce751f0a46a58575"
checksum = "61c8494e9ddb892339a1cc3e08c9db1f9d803b9c6fa92eebc775b7cd64027711"
dependencies = [
"either",
"lexical",
@ -3938,9 +3855,9 @@ dependencies = [
[[package]]
name = "swc_ecma_preset_env"
version = "0.194.14"
version = "0.194.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2328e4d269345cc638077a4a04bc630e70ad3ad34b05f89186da6c87aa71cb4"
checksum = "548a7daa429e80a292af496cc24481d3f92117749e16825763c0e07cc80168ee"
dependencies = [
"ahash",
"anyhow",
@ -3975,9 +3892,9 @@ dependencies = [
[[package]]
name = "swc_ecma_transforms"
version = "0.217.14"
version = "0.217.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c22012722f4fddf1ccc9855c04e1887a11824cecf46c27fff16de56a3daeec3b"
checksum = "d17fc31a2f18f2dc4115e42c029592a86fee937d0e20f447441edc8b7a41d076"
dependencies = [
"swc_atoms",
"swc_common",
@ -3995,15 +3912,15 @@ dependencies = [
[[package]]
name = "swc_ecma_transforms_base"
version = "0.126.10"
version = "0.126.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "907d73e5a76ddb21f33a3f5907c46c9df968ee52ba0d9f8d6ade43e8f06233a3"
checksum = "582e8ff470ffb2108c0e95d517b498bfba3ab5c2bf2e693ebb4caf746190cdc6"
dependencies = [
"better_scoped_tls",
"bitflags 2.1.0",
"bitflags 2.2.1",
"indexmap",
"once_cell",
"phf 0.10.1",
"phf",
"rustc-hash",
"serde",
"smallvec",
@ -4018,9 +3935,9 @@ dependencies = [
[[package]]
name = "swc_ecma_transforms_classes"
version = "0.115.10"
version = "0.115.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15722bc7895b17b32d114caed125abeca6d556e0afbf6e8c55b43ab6d4448afa"
checksum = "ceb3e469ab121b52223a1507c54a0ea36b00cbf8813cdbe0a6598ae7a394b544"
dependencies = [
"swc_atoms",
"swc_common",
@ -4032,9 +3949,9 @@ dependencies = [
[[package]]
name = "swc_ecma_transforms_compat"
version = "0.152.11"
version = "0.152.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5185a3ff6fc272cd4fd180265ad621b26b3c74c60b878bf10850a2d9f81eee1"
checksum = "85bb944af58d500e8cb0009654067dec2a4de53208027a07a38d82f8c43f82ce"
dependencies = [
"ahash",
"arrayvec 0.7.2",
@ -4071,14 +3988,14 @@ dependencies = [
[[package]]
name = "swc_ecma_transforms_module"
version = "0.169.13"
version = "0.169.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa808a92ea3bc645d2021c63d54c4a440b43a2686bc6f3344f87c9ce9cf888ff"
checksum = "b1d95a0c5d9bb047cdcc08367898801f9164ab41f640a3021c78077627d7de29"
dependencies = [
"Inflector",
"ahash",
"anyhow",
"bitflags 2.1.0",
"bitflags 2.2.1",
"indexmap",
"is-macro",
"path-clean",
@ -4099,9 +4016,9 @@ dependencies = [
[[package]]
name = "swc_ecma_transforms_optimization"
version = "0.186.14"
version = "0.186.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db5fb60aa2e6c5c4f401ace0a8ac9ed21fa130d8f9918e22a9cf87a7c31f456d"
checksum = "3e913627e69017574ae0f849ad06585a01410d41b089b5780f6ae07044cc58b4"
dependencies = [
"ahash",
"dashmap",
@ -4124,9 +4041,9 @@ dependencies = [
[[package]]
name = "swc_ecma_transforms_proposal"
version = "0.160.12"
version = "0.160.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eaf64211f97397a84978a0cbdd28227fddf9c804bf3479fad8983c3527ba5b39"
checksum = "885ef4e2d9b4c60034c6e48c35fed1d6ff2fb0f68aa3b7d16c87fc5b020c202d"
dependencies = [
"either",
"rustc-hash",
@ -4144,16 +4061,15 @@ dependencies = [
[[package]]
name = "swc_ecma_transforms_react"
version = "0.172.13"
version = "0.172.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "524bf7df90558cc643a3f367c79e9b476b81b92990dc5df3c31febbd7110875a"
checksum = "0dc67773019ae770f4ddad940149da3e9eb8a2829ccd0ca5990c7a469cdabf6d"
dependencies = [
"ahash",
"base64 0.13.1",
"dashmap",
"indexmap",
"once_cell",
"regex",
"serde",
"sha-1",
"string_enum",
@ -4170,9 +4086,9 @@ dependencies = [
[[package]]
name = "swc_ecma_transforms_testing"
version = "0.129.10"
version = "0.129.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02fe6a8a1290bbd4fdd9cc1554e3458ffb14e2131936f065bed0a684f89aaf0c"
checksum = "a2a51a15213491f841c82cab96a405e9b9dcb1323ebfbdd4d2c8b457c038de94"
dependencies = [
"ansi_term",
"anyhow",
@ -4196,9 +4112,9 @@ dependencies = [
[[package]]
name = "swc_ecma_transforms_typescript"
version = "0.176.13"
version = "0.176.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b9b4384e78d27819237123b161c439d6fbb75ff55da532e6df809ebc73cf48"
checksum = "da93d0f2d847a75739c7f1679fccbbe623666e1c6eff78813e374ba5f33e4558"
dependencies = [
"serde",
"swc_atoms",
@ -4212,9 +4128,9 @@ dependencies = [
[[package]]
name = "swc_ecma_usage_analyzer"
version = "0.12.8"
version = "0.12.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "784ba3ec77f8c18e251b613074ee7da678e0a30dee37856b70d9a7c7ac636d19"
checksum = "2b15e582f493c227e535bd45874809584efb01acde8fb9547ec9efb4359c9991"
dependencies = [
"ahash",
"indexmap",
@ -4230,9 +4146,9 @@ dependencies = [
[[package]]
name = "swc_ecma_utils"
version = "0.116.8"
version = "0.116.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba86ab0cf8c64043dcc8ac5cb4f438f6e3666ddac58587925ddc2af6be2ed5d1"
checksum = "7ea67b796253a2d99ec02d5751f83d174c4d894d439353e77cff50477463816f"
dependencies = [
"indexmap",
"num_cpus",
@ -4248,9 +4164,9 @@ dependencies = [
[[package]]
name = "swc_ecma_visit"
version = "0.89.4"
version = "0.89.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ecb23a4a1d77997f54e9b3a4e68d1441e5e8a25ad1a476bbb3b5a620d6562a86"
checksum = "4490a5ed042234d72986e1a0c8afb54291fcf82b42af78ea72507b52bcbe13dd"
dependencies = [
"num-bigint 0.4.3",
"swc_atoms",
@ -4274,9 +4190,9 @@ dependencies = [
[[package]]
name = "swc_error_reporters"
version = "0.15.4"
version = "0.15.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf37dae113d98ec257727dce3d746254a2731abc56e609a6f2efa7cf57806705"
checksum = "3afbf2e52ddce38da1ee204252f7b9019a12176ca73aaa0fd0c36ded1ecbec7d"
dependencies = [
"anyhow",
"miette",
@ -4287,9 +4203,9 @@ dependencies = [
[[package]]
name = "swc_fast_graph"
version = "0.19.4"
version = "0.19.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "992a92e087f7b2dc9aa626a6bee26530abbffba3572adf3894ccb55d2480f596"
checksum = "95683baee47d2cbf10e0bf8ad14d4e8f6160674d9eb96b3ab560aa39fa37ccdc"
dependencies = [
"indexmap",
"petgraph",
@ -4299,9 +4215,9 @@ dependencies = [
[[package]]
name = "swc_graph_analyzer"
version = "0.20.4"
version = "0.20.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "66f7d6f4ec40acd00a7620c1627f926947c89d8a6c0a9728120b2396ee7eaa12"
checksum = "4812745a05bf856948b7ada6f1f1f8d2c4be0afa060844532798165b4c76416e"
dependencies = [
"ahash",
"auto_impl",
@ -4324,9 +4240,9 @@ dependencies = [
[[package]]
name = "swc_node_comments"
version = "0.18.4"
version = "0.18.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e723c3796eb5c3e46e68023062facc665c9ccf71df4907d829739388b7d919c"
checksum = "f7d3be08cc983291059f7a16a62ff297bdb83c1282cfe876416fd52b3466e791"
dependencies = [
"ahash",
"dashmap",
@ -4356,9 +4272,9 @@ dependencies = [
[[package]]
name = "swc_plugin_proxy"
version = "0.32.4"
version = "0.32.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01e5a04649cde6c40bd2b746ad7ac8195b9e3316048bf7263380e315907a6fd9"
checksum = "4cd5b2880508aedc964f4f52a4debc07c4b48212b45b44522d651c701b1a4d84"
dependencies = [
"better_scoped_tls",
"rkyv",
@ -4370,9 +4286,9 @@ dependencies = [
[[package]]
name = "swc_timer"
version = "0.19.4"
version = "0.19.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd3f57fbbb68655b2e2baadc47bfd96b9b25f179d8925b25b7a866a7ec71e041"
checksum = "7d525672140610b0797da5ee7a3f5bcb0dbf13940c84d63c9f966c0239f26cb7"
dependencies = [
"tracing",
]
@ -4474,9 +4390,9 @@ dependencies = [
[[package]]
name = "testing"
version = "0.33.4"
version = "0.33.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3be7349994b23b7d91beddbfb6d63907651a9d2f6b4e4fa8d2ec65b41094968a"
checksum = "77e5fcdfc6805d181431333b850f9fde170f654148e29ba97fd8033c7814e665"
dependencies = [
"ansi_term",
"difference",
@ -4783,13 +4699,13 @@ dependencies = [
[[package]]
name = "tracing-attributes"
version = "0.1.23"
version = "0.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a"
checksum = "0f57e3ca2a01450b1a921183a9c9cbfda207fd822cef4ccb00a65402cbba7a74"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
"syn 2.0.15",
]
[[package]]
@ -4815,9 +4731,9 @@ dependencies = [
[[package]]
name = "tracing-subscriber"
version = "0.3.16"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70"
checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77"
dependencies = [
"matchers",
"nu-ansi-term",
@ -4998,12 +4914,6 @@ dependencies = [
"try-lock",
]
[[package]]
name = "wasi"
version = "0.9.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
[[package]]
name = "wasi"
version = "0.10.0+wasi-snapshot-preview1"

File diff suppressed because it is too large Load diff

View file

@ -20,7 +20,7 @@ swc_ecma_transforms_base = { version = "0.126.10" }
swc_ecma_transforms_typescript = { version = "0.176.12" }
swc_ecma_visit = { version = "0.89.4" }
anyhow = "1.0.70"
jsx-dom-expressions = { version = "0.1", git = "https://github.com/icewind1991/swc-plugin-jsx-dom-expressions", branch = "event" }
jsx-dom-expressions = { version = "0.1", git = "https://github.com/icewind1991/swc-plugin-jsx-dom-expressions", branch = "mixed-svg" }
#jsx-dom-expressions = { version = "0.1", path = "../../../../rust/swc-plugin-jsx-dom-expressions" }
rand = "0.8.5"
fnv = "1.0.7"

View file

@ -32,7 +32,13 @@ pub fn guess_mime(path: &str) -> &'static str {
return "image/png";
} else if path.ends_with("css") {
return "text/css";
} else if path.ends_with("js") {
} else if path.ends_with("wasm") {
return "application/wasm";
} else if path.ends_with("js")
|| path.ends_with("ts")
|| path.ends_with("jsx")
|| path.ends_with("tsx")
{
return "text/javascript";
}
return "text/plain";

62
package-lock.json generated
View file

@ -6,6 +6,8 @@
"": {
"dependencies": {
"@demostf/parser-worker": "^0.1.1",
"@demostf/tf-demos-viewer": "^0.1.0",
"@solid-primitives/resize-observer": "^2.0.15",
"@thisbeyond/solid-select": "^0.13.0",
"@types/throttle-debounce": "^5.0.0",
"react": "^18.2.0",
@ -22,6 +24,66 @@
"resolved": "https://registry.npmjs.org/@demostf/parser-worker/-/parser-worker-0.1.1.tgz",
"integrity": "sha512-BDCN1G5bZkD46MlIUJz8z/ALP1ZTL4SF8Ygwp739+RqlmPj5k7ptrFHagnIqAvnsrlEwwaMtuOpWRkJLANLDVA=="
},
"node_modules/@demostf/tf-demos-viewer": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/@demostf/tf-demos-viewer/-/tf-demos-viewer-0.1.1.tgz",
"integrity": "sha512-WAWtX5n76lOrDyL7ys9FC7csIMfbkYGDXlK0hYf1aTh5H+28dTlqlUpKkRFBxP3USlIe/kYjn9HqwLxVGIauSw=="
},
"node_modules/@solid-primitives/event-listener": {
"version": "2.2.10",
"resolved": "https://registry.npmjs.org/@solid-primitives/event-listener/-/event-listener-2.2.10.tgz",
"integrity": "sha512-rWBCeF1NRAmLJtVo2wpY9vF3IAQ8VAxGnFDOUqROSdYhUfiCeM7Hw3PKkGCELwNQzZK1W1z+MjzB7fctpjX4Sg==",
"dependencies": {
"@solid-primitives/utils": "^6.0.0"
},
"peerDependencies": {
"solid-js": "^1.6.12"
}
},
"node_modules/@solid-primitives/resize-observer": {
"version": "2.0.15",
"resolved": "https://registry.npmjs.org/@solid-primitives/resize-observer/-/resize-observer-2.0.15.tgz",
"integrity": "sha512-Wu2KygKhe4wVeZYCvBrD/yUr9oMs7Jtm+cCctroPlTIKOviLhtiNKzi56Jh0FR2G+adeD5662qHpAS/obnM5gQ==",
"dependencies": {
"@solid-primitives/event-listener": "^2.2.10",
"@solid-primitives/rootless": "^1.3.2",
"@solid-primitives/static-store": "^0.0.2",
"@solid-primitives/utils": "^6.0.0"
},
"peerDependencies": {
"solid-js": "^1.6.12"
}
},
"node_modules/@solid-primitives/rootless": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/@solid-primitives/rootless/-/rootless-1.3.2.tgz",
"integrity": "sha512-R1rncXOUcB/i3PyvKhSWcsocPRe1n3HsMIO717RpWFd2knUF8+b0cGgRDEoneGaV/a5kq4cqH3csa66klxuM3A==",
"dependencies": {
"@solid-primitives/utils": "^6.0.0"
},
"peerDependencies": {
"solid-js": "^1.6.12"
}
},
"node_modules/@solid-primitives/static-store": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/@solid-primitives/static-store/-/static-store-0.0.2.tgz",
"integrity": "sha512-JR51MmoZbFWE7fmzm0NnfS4RuLHpzXpPqAb7RJu3fHDGHp+q7v4KylseULcailINzDIosHQXbpiDQlj2Lx9zbQ==",
"dependencies": {
"@solid-primitives/utils": "^6.0.0"
},
"peerDependencies": {
"solid-js": "^1.6.12"
}
},
"node_modules/@solid-primitives/utils": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/@solid-primitives/utils/-/utils-6.1.0.tgz",
"integrity": "sha512-uTikKFrq33UO+MnKt2WzZr9WYbQe5YX58ytGkL+29DL6o0pZs1wrICbd4ymzSm8azqzMcQqEQOL3HLWjuv9tLw==",
"peerDependencies": {
"solid-js": "^1.6.12"
}
},
"node_modules/@thisbeyond/solid-select": {
"version": "0.13.0",
"resolved": "https://registry.npmjs.org/@thisbeyond/solid-select/-/solid-select-0.13.0.tgz",

View file

@ -1,6 +1,8 @@
{
"dependencies": {
"@demostf/parser-worker": "^0.1.1",
"@demostf/tf-demos-viewer": "^0.1.0",
"@solid-primitives/resize-observer": "^2.0.15",
"@thisbeyond/solid-select": "^0.13.0",
"@types/throttle-debounce": "^5.0.0",
"react": "^18.2.0",

View file

@ -1,103 +0,0 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (_) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
exports.__esModule = true;
exports.Api = void 0;
var Api = /** @class */ (function () {
function Api(base) {
this.base = base;
}
Api.prototype.getApiUrl = function (url) {
return this.base + url;
};
Api.prototype.request = function (url, params, json) {
if (params === void 0) { params = {}; }
if (json === void 0) { json = true; }
var queryParams = new URLSearchParams(params);
return fetch(this.getApiUrl(url) + '?' + queryParams)
.then(function (response) {
if (json) {
return response.json();
}
else {
return response.text();
}
});
};
Api.prototype.searchPlayer = function (query) {
return __awaiter(this, void 0, void 0, function () {
var players, _i, players_1, player;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
if (query.length < 2) {
return [2 /*return*/, []];
}
return [4 /*yield*/, this.request('users/search', { query: query })];
case 1:
players = _a.sent();
for (_i = 0, players_1 = players; _i < players_1.length; _i++) {
player = players_1[_i];
localStorage.setItem("player.".concat(player.steamid), JSON.stringify(player));
}
return [2 /*return*/, players];
}
});
});
};
Api.prototype.getPlayer = function (id) {
return __awaiter(this, void 0, void 0, function () {
var cached, player;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
cached = localStorage.getItem("player.".concat(id));
if (cached) {
return [2 /*return*/, JSON.parse(cached)];
}
return [4 /*yield*/, this.request("users/".concat(id), {})];
case 1:
player = _a.sent();
localStorage.setItem("player.".concat(id), JSON.stringify(player));
return [2 /*return*/, player];
}
});
});
};
return Api;
}());
exports.Api = Api;

28
script/download.ts Normal file
View file

@ -0,0 +1,28 @@
export async function download(url: string, progress: (number) => void): Promise<ArrayBuffer> {
const response = await fetch(url, {mode: 'cors'});
if (!response.body || !response.headers) {
throw new Error("invalid response");
}
const contentLength = +(response.headers.get('Content-Length') || 0);
let receivedLength = 0;
let data = new Uint8Array(contentLength);
const reader = response.body.getReader();
while(true) {
const {done, value} = await reader.read();
if (done) {
break;
}
data.set(value, receivedLength);
receivedLength += value.length;
progress((receivedLength / contentLength) * 100);
}
return data.buffer;
}

View file

@ -1,61 +0,0 @@
"use strict";
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
exports.__esModule = true;
exports.queryForFilter = exports.Reset = exports.FilterBar = void 0;
var solid_select_1 = require("@thisbeyond/solid-select");
var solid_js_1 = require("solid-js");
var FilterBar = function (_a) {
var maps = _a.maps, api = _a.api, onChange = _a.onChange, initialFilter = _a.initialFilter;
var modes = (0, solid_select_1.createOptions)(["4v4", "6v6", "Highlander"]);
var mapOptions = (0, solid_select_1.createOptions)(maps, {
createable: true
});
var playerOptions = (0, solid_select_1.createAsyncOptions)(function (search) { return api.searchPlayer(search); });
var playerFormat = function (player) { return player.name; };
var _b = (0, solid_js_1.createSignal)(initialFilter.mode, { equals: false }), initialMode = _b[0], setInitialMode = _b[1];
var _c = (0, solid_js_1.createSignal)(initialFilter.map, { equals: false }), initialMap = _c[0], setInitialMap = _c[1];
var _d = (0, solid_js_1.createSignal)(initialFilter), filterSet = _d[0], setFilterSet = _d[1];
(0, solid_js_1.createEffect)(function () { return onChange(filterSet()); });
return <div class="filter-bar">
<solid_select_1.Select class="mode" onChange={function (mode) { return setFilterSet(__assign(__assign({}, filterSet()), { mode: mode })); }} initialValue={initialMode()} placeholder="All Types" {...modes}/>
<exports.Reset reset={function () {
setInitialMode("");
onChange(__assign(__assign({}, filterSet()), { mode: "" }));
}}/>
<solid_select_1.Select class="maps" onChange={function (map) { return setFilterSet(__assign(__assign({}, filterSet()), { map: map })); }} initialValue={initialMap()} placeholder="All Maps" {...mapOptions}/>
<exports.Reset reset={function () {
setInitialMap("");
console.log(__assign(__assign({}, filterSet()), { map: "" }));
onChange(__assign(__assign({}, filterSet()), { map: "" }));
}}/>
<solid_select_1.Select class="players" initialValue={initialFilter.players} onChange={function (players) { return setFilterSet(__assign(__assign({}, filterSet()), { players: players })); }} multiple placeholder="All Players" format={playerFormat} {...playerOptions}/>
</div>;
};
exports.FilterBar = FilterBar;
var Reset = function (_a) {
var reset = _a.reset;
return <button onMouseDown={reset} class="reset">X</button>;
};
exports.Reset = Reset;
function queryForFilter(filter) {
var queryParams = new URLSearchParams({
players: filter.players.map(function (player) { return player.steamid; }).join(','),
mode: (filter.mode || "").toLowerCase(),
map: filter.map || ""
});
if (filter.uploader) {
queryParams.set("uploader", filter.uploader);
}
return queryParams;
}
exports.queryForFilter = queryForFilter;

View file

@ -58,7 +58,7 @@ export const FilterBar = ({maps, api, onChange, initialFilter}: FilterBarProps)
}
export const Reset = ({reset}) => {
return <button onMouseDown={reset} class="reset">X</button>;
return <button onClick={reset} class="reset">X</button>;
}
export interface FilterSet {

View file

@ -1,88 +0,0 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (_) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
exports.__esModule = true;
exports.parseHeader = void 0;
DataView.prototype["getString"] = function (offset, length) {
var end = typeof length == 'number' ? offset + length : this.byteLength;
var text = '';
var val = -1;
while (offset < this.byteLength && offset < end) {
val = this.getUint8(offset++);
if (val === 0)
break;
text += String.fromCharCode(val);
}
return text;
};
function parseHeader(file) {
return __awaiter(this, void 0, void 0, function () {
var data, view;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, readFile(file)];
case 1:
data = _a.sent();
view = new DataView(data);
return [2 /*return*/, {
'type': view.getString(0, 8),
'server': view.getString(16, 260),
'nick': view.getString(276, 260),
'map': view.getString(536, 260),
'game': view.getString(796, 260),
'duration': view.getFloat32(1056, true),
'ticks': view.getUint32(1060, true)
}];
}
});
});
}
exports.parseHeader = parseHeader;
function readFile(file) {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
return [2 /*return*/, new Promise(function (resolve, reject) {
var reader = new FileReader();
reader.onload = function () {
resolve(reader.result);
};
reader.onerror = reject;
reader.readAsArrayBuffer(file);
})];
});
});
}

View file

@ -26,8 +26,12 @@ export interface GetStringDataView extends DataView {
getString: (offset: number, length: number) => string;
}
export async function parseHeader(file): Promise<DemoHead> {
export async function parseHeader(file: File): Promise<DemoHead> {
const data = await readFile(file);
return parseHeaderFromBuffer(data);
}
export function parseHeaderFromBuffer(data: ArrayBuffer): DemoHead {
const view = new DataView(data) as GetStringDataView;
return {
'type': view.getString(0, 8),
@ -40,7 +44,7 @@ export async function parseHeader(file): Promise<DemoHead> {
};
}
async function readFile(file: File): Promise<ArrayBuffer> {
export async function readFile(file: File): Promise<ArrayBuffer> {
return new Promise((resolve, reject) => {
const reader = new FileReader();

View file

@ -1,12 +0,0 @@
"use strict";
exports.__esModule = true;
exports.ready = void 0;
function ready(cb) {
if (document.readyState === "complete") {
cb();
}
else {
document.addEventListener("DOMContentLoaded", cb);
}
}
exports.ready = ready;

View file

@ -1,21 +0,0 @@
"use strict";
exports.__esModule = true;
exports.formatDuration = void 0;
function formatDuration(input) {
if (!input) {
return '0:00';
}
var hours = Math.floor(input / 3600);
var minutes = Math.floor((input - (hours * 3600)) / 60);
var seconds = Math.floor(input - (hours * 3600) - (minutes * 60));
var hourString = (hours < 10) ? "0" + hours : "" + hours;
var minuteString = (minutes < 10) ? "0" + minutes : "" + minutes;
var secondString = (seconds < 10) ? "0" + seconds : "" + seconds;
if (hourString !== '00') {
return hourString + ':' + minuteString + ':' + secondString;
}
else {
return minuteString + ':' + secondString;
}
}
exports.formatDuration = formatDuration;

View file

@ -1,144 +0,0 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (_) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
exports.__esModule = true;
var ready_1 = require("./ready");
var header_1 = require("./header");
var time_1 = require("./time");
(0, ready_1.ready)(function () {
var red_name = document.querySelector(".red input");
var blue_name = document.querySelector(".blue input");
var file = document.querySelector(".dropzone input[type=\"file\"]");
var drop_text = document.querySelector(".dropzone .text");
var button = document.querySelector(".upload > button");
var map = document.querySelector(".demo-info .map");
var time = document.querySelector(".demo-info .time");
var apiBase = document.querySelector("input[name=\"api\"]").value;
var key = document.querySelector(".key").textContent;
var selectedFile = null;
console.log(key);
file.addEventListener("change", function (event) { return __awaiter(void 0, void 0, void 0, function () {
var file, header;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
file = event.target.files[0];
drop_text.textContent = file.name;
return [4 /*yield*/, (0, header_1.parseHeader)(file)];
case 1:
header = _a.sent();
if (header.type === "HL2DEMO" && header.game === "tf") {
map.textContent = header.map;
time.textContent = (0, time_1.formatDuration)(header.duration);
button.removeAttribute("disabled");
selectedFile = file;
}
else {
drop_text.textContent = "Malformed demo or not a TF2 demo";
map.textContent = "";
time.textContent = "";
button.setAttribute("disabled", "disabled");
selectedFile = null;
}
return [2 /*return*/];
}
});
}); });
button.addEventListener("click", function () { return __awaiter(void 0, void 0, void 0, function () {
var _a, e_1;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
button.setAttribute("disabled", "disabled");
if (!selectedFile) {
return [2 /*return*/];
}
drop_text.textContent = "Uploading...";
_b.label = 1;
case 1:
_b.trys.push([1, 3, , 4]);
_a = window.location;
return [4 /*yield*/, uploadDemo(apiBase, key, red_name.value || 'RED', blue_name.value || 'BLU', selectedFile.name, selectedFile)];
case 2:
_a.href = _b.sent();
return [3 /*break*/, 4];
case 3:
e_1 = _b.sent();
drop_text.textContent = "Error ".concat(e_1.message);
return [3 /*break*/, 4];
case 4: return [2 /*return*/];
}
});
}); });
});
function uploadDemo(apiBase, key, red, blue, name, demo) {
return __awaiter(this, void 0, void 0, function () {
var data, response, _a, body, matches;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
data = new FormData();
data.append('key', key);
data.append('red', red);
data.append('blu', blue);
data.append('name', name);
data.append('demo', demo, demo.name);
return [4 /*yield*/, fetch(apiBase + "upload", {
method: 'POST',
body: data
})];
case 1:
response = _b.sent();
if (!(response.status >= 400)) return [3 /*break*/, 3];
_a = Error.bind;
return [4 /*yield*/, response.text()];
case 2: throw new (_a.apply(Error, [void 0, _b.sent()]))();
case 3: return [4 /*yield*/, response.text()];
case 4:
body = _b.sent();
matches = body.match(/STV available at: https?:\/\/[^/]+\/(\d+)/);
if (matches) {
return [2 /*return*/, matches[1]];
}
else {
throw new Error(body);
}
return [2 /*return*/];
}
});
});
}

45
script/viewer.tsx Normal file
View file

@ -0,0 +1,45 @@
import {ready} from './ready';
import {parseHeaderFromBuffer, readFile} from './header';
import {download} from "./download";
import {AsyncParser} from "./viewer/Analyse/Data/AsyncParser";
import {render} from "solid-js/web";
import {Analyser} from "./viewer/Analyse/Analyser";
ready(async () => {
const fileInput: HTMLInputElement | null = document.querySelector(`.dropzone input[type="file"]`);
const urlInput: HTMLInputElement | null = document.querySelector(`.viewer-page input[name="url"]`);
const drop_text = document.querySelector(`.dropzone .text`);
const downloadProgress: HTMLProgressElement = document.querySelector(`progress.download`);
const parseProgress: HTMLProgressElement = document.querySelector(`progress.parse`);
if (fileInput) {
fileInput.addEventListener("change", async (event: InputEvent) => {
let file = (event.target as HTMLInputElement).files[0];
drop_text.textContent = file.name;
const data = await readFile(file);
const header = parseHeaderFromBuffer(data);
if (header.type === "HL2DEMO" && header.game === "tf") {
drop_text.textContent = "Parsing...";
parse(data, parseProgress, false);
} else {
drop_text.textContent = "Malformed demo or not a TF2 demo";
}
});
} else {
const url = urlInput.value;
console.log(url);
const data = await download(url, (progress) => downloadProgress.value = progress);
parse(data, parseProgress, true);
}
})
const parse = async (data: ArrayBuffer, parseProgress: HTMLProgressElement, stored: boolean) => {
const header = parseHeaderFromBuffer(data);
const parser = new AsyncParser(data, (progress) => parseProgress.value = progress);
await parser.cache();
const page = document.querySelector('.viewer-page');
render(() => <Analyser parser={parser} header={header} isStored={stored}/>, page);
}

View file

@ -4,7 +4,7 @@ import {throttle, debounce} from 'throttle-debounce';
import {Timeline} from './Render/Timeline';
import {SpecHUD} from './Render/SpecHUD';
import {AnalyseMenu} from './AnalyseMenu'
import {Header, WorldBoundaries} from "@demostf/parser-worker";
import {Header, WorldBoundaries} from "./Data/Parser";
import {AsyncParser} from "./Data/AsyncParser";
import {getMapBoundaries} from "./MapBoundries";
@ -21,10 +21,10 @@ export const Analyser = (props: AnalyseProps) => {
const parser = props.parser;
const intervalPerTick = props.header.interval_per_tick;
const [tick, setTick] = createSignal<number>();
const [scale, setScale] = createSignal<number>();
const [playing, setPlaying] = createSignal<boolean>();
const [sessionName, setSessionName] = createSignal<string>();
const [tick, setTick] = createSignal<number>(0);
const [scale, setScale] = createSignal<number>(1);
const [playing, setPlaying] = createSignal<boolean>(false);
const [sessionName, setSessionName] = createSignal<string>("");
let lastFrameTime = 0;
let playStartTick = 0;
@ -165,9 +165,9 @@ export const Analyser = (props: AnalyseProps) => {
<div class="time-control"
title={`${tickToTime(tick(), intervalPerTick)} (tick ${tick()})`}>
<input class="play-pause-button" type="button"
onClick={togglePlay}
value={playButtonText()}
disabled={disabled}
onClick={togglePlay}
/>
<Timeline parser={parser} tick={tick()}
onSetTick={throttle(50, (tick) => {

View file

@ -1,58 +0,0 @@
"use strict";
exports.__esModule = true;
exports.AsyncParser = void 0;
var parser_worker_1 = require("@demostf/parser-worker");
var AsyncParser = /** @class */ (function () {
function AsyncParser(buffer, progressCallback) {
this.buffer = buffer;
this.progressCallback = progressCallback;
}
AsyncParser.prototype.cache = function () {
var _this = this;
return new Promise(function (resolve, reject) {
var worker = new Worker(new URL('./ParseWorker.ts', import.meta.url));
worker.postMessage({
buffer: _this.buffer
}, [_this.buffer]);
worker.onmessage = function (event) {
if (event.data.error) {
reject(event.data.error);
return;
}
else if (event.data.progress) {
_this.progressCallback(event.data.progress);
return;
}
else if (event.data.demo) {
var cachedData = event.data.demo;
console.log("packed data: ".concat((cachedData.data.length / (1024 * 1024)).toFixed(1), "MB"));
_this.world = cachedData.world;
_this.demo = new parser_worker_1.ParsedDemo(cachedData.playerCount, cachedData.buildingCount, cachedData.world, cachedData.header, cachedData.data, cachedData.kills, cachedData.playerInfo, cachedData.tickCount);
resolve(_this.demo);
}
};
});
};
AsyncParser.prototype.getPlayersAtTick = function (tick) {
var players = [];
for (var i = 0; i < this.demo.playerCount; i++) {
players.push(this.demo.getPlayer(tick, i));
}
return players;
};
AsyncParser.prototype.getBuildingsAtTick = function (tick) {
var buildings = [];
for (var i = 0; i < this.demo.buildingCount; i++) {
var building = this.demo.getBuilding(tick, i);
if (building.health > 0) {
buildings.push(building);
}
}
return buildings;
};
AsyncParser.prototype.getKills = function () {
return this.demo.kills;
};
return AsyncParser;
}());
exports.AsyncParser = AsyncParser;

View file

@ -1,5 +1,9 @@
import {ParsedDemo, PlayerState, WorldBoundaries, Header, Kill, BuildingState} from "@demostf/parser-worker";
import {getMapBoundaries} from "../MapBoundries";
import {ParsedDemo, PlayerState, WorldBoundaries, Kill, BuildingState} from "./Parser";
function getCacheBuster(): string {
const url = document.querySelector('script[src*="viewer"]').attributes.src.value;
return url.substring("/viewer.js".length);
}
export class AsyncParser {
buffer: ArrayBuffer;
@ -14,7 +18,7 @@ export class AsyncParser {
cache(): Promise<ParsedDemo> {
return new Promise((resolve, reject) => {
const worker = new Worker(new URL('./ParseWorker.ts', import.meta.url));
const worker = new Worker(`/parse-worker.js${getCacheBuster()}`);
worker.postMessage({
buffer: this.buffer
}, [this.buffer]);

View file

@ -1,25 +0,0 @@
"use strict";
exports.__esModule = true;
var parser_worker_1 = require("@demostf/parser-worker");
/**
* @global postMessage
* @param event
*/
onmessage = function (event) {
var buffer = event.data.buffer;
var bytes = new Uint8Array(buffer);
(0, parser_worker_1.parseDemo)(bytes, function (progress) {
postMessage({
progress: progress
});
}).then(function (parsed) {
postMessage({
demo: parsed
}, [parsed.data.buffer]);
})["catch"](function (e) {
console.error(e);
postMessage({
error: e.message
});
});
};

View file

@ -1,4 +1,4 @@
import {parseDemo} from "@demostf/parser-worker";
import {parseDemo} from "./Parser";
declare function postMessage(message: any, transfer?: any[]): void;
@ -23,5 +23,4 @@ onmessage = (event: MessageEvent) => {
error: e.message
});
});
};

View file

@ -0,0 +1,259 @@
import {
get_assister_ids,
get_attacker_ids, get_data,
get_kill_ticks,
get_map, get_player_entity_id,
get_player_name, get_player_steam_id, get_player_user_id, get_victim_ids, get_weapon,
parse_demo,
XY
} from '@demostf/tf-demos-viewer';
import viewer from "@demostf/tf-demos-viewer";
function getCacheBuster(): string {
const url = self.location.href;
return url.substring(url.indexOf('?'));
}
export async function parseDemo(bytes: Uint8Array, progressCallback: (progress: number) => void): Promise<ParsedDemo> {
await viewer(`/tf-demo-viewer.wasm${getCacheBuster()}`);
const state = parse_demo(bytes, progressCallback);
let playerCount = state.player_count;
let buildingCount = state.building_count;
let boundaries = state.boundaries;
let interval_per_tick = state.interval_per_tick;
let tickCount = state.tick_count;
let kill_ticks = get_kill_ticks(state);
let attackers = get_attacker_ids(state);
let assisters = get_assister_ids(state);
let victims = get_victim_ids(state);
let playerInfo = [];
for (let i = 0; i < playerCount; i++) {
playerInfo.push({
name: get_player_name(state, i),
steamId: get_player_steam_id(state, i),
entityId: get_player_entity_id(state, i),
userId: get_player_user_id(state, i),
})
}
let kills = [];
for (let i = 0; i < kill_ticks.length; i++) {
kills.push({
tick: kill_ticks[i],
attacker: attackers[i],
assister: assisters[i],
victim: victims[i],
weapon: get_weapon(state, i),
})
}
let map = get_map(state);
let data = get_data(state);
return new ParsedDemo(
playerCount,
buildingCount,
{
boundary_min: {
x: boundaries.boundary_min.x,
y: boundaries.boundary_min.y,
},
boundary_max: {
x: boundaries.boundary_max.x,
y: boundaries.boundary_max.y,
}
},
{
map,
interval_per_tick
},
data,
kills,
playerInfo,
tickCount,
);
}
export interface PlayerInfo {
entityId: number,
name: string,
steamId: string,
userId: number,
}
export enum Team {
Other = 0,
Spectator = 1,
Red = 2,
Blue = 3,
}
export enum Class {
Other = 0,
Scout = 1,
Sniper = 2,
Solder = 3,
Demoman = 4,
Medic = 5,
Heavy = 6,
Pyro = 7,
Spy = 8,
Engineer = 9,
}
export enum BuildingType {
TeleporterEntrance = 0,
TeleporterExit = 1,
Dispenser = 2,
Level1Sentry = 3,
Level2Sentry = 4,
Level3Sentry = 5,
MiniSentry = 6,
Unknown = 7,
}
export interface WorldBoundaries {
boundary_min: {
x: number,
y: number
},
boundary_max: {
x: number,
y: number
}
}
export interface PlayerState {
position: {
x: number,
y: number
},
angle: number,
health: number,
team: Team,
playerClass: Class,
info: PlayerInfo,
charge: number,
}
export interface BuildingState {
position: {
x: number,
y: number
},
angle: number,
health: number,
level: number,
team: Team,
buildingType: BuildingType,
}
export interface Header {
interval_per_tick: number,
map: string
}
export interface Kill {
tick: number,
attacker: number,
assister: number,
victim: number,
weapon: string,
}
function unpack_f32(val: number, min: number, max: number): number {
const ratio = val / (Math.pow(2, 16) - 1);
return ratio * (max - min) + min;
}
function unpack_angle(val: number): number {
const ratio = val / (Math.pow(2, 8) - 1);
return ratio * 360;
}
export class ParsedDemo {
public readonly playerCount: number;
public readonly buildingCount: number;
public readonly world: WorldBoundaries;
public readonly data: Uint8Array;
public readonly header: Header;
public readonly tickCount: number;
public readonly kills: Kill[];
public readonly playerInfo: PlayerInfo[];
constructor(playerCount: number, buildingCount: number, world: WorldBoundaries, header: Header, data: Uint8Array, kills: Kill[], playerInfo: PlayerInfo[], tickCount: number) {
this.playerCount = playerCount;
this.buildingCount = buildingCount;
this.world = world;
this.header = header;
this.data = data;
this.kills = kills;
this.playerInfo = playerInfo;
this.tickCount = tickCount;
}
getPlayer(tick: number, playerIndex: number): PlayerState {
if (playerIndex >= this.playerCount) {
throw new Error("Player out of bounds");
}
const base = ((playerIndex * this.tickCount) + tick) * PLAYER_PACK_SIZE;
return unpackPlayer(this.data, base, this.world, this.playerInfo[playerIndex]);
}
getBuilding(tick: number, buildingIndex: number): BuildingState {
if (buildingIndex >= this.buildingCount) {
throw new Error("Building out of bounds");
}
const base = (this.playerCount * this.tickCount * PLAYER_PACK_SIZE) + ((buildingIndex * this.tickCount) + tick) * BUILDING_PACK_SIZE;
return unpackBuilding(this.data, base, this.world);
}
}
const PLAYER_PACK_SIZE = 8;
const BUILDING_PACK_SIZE = 7;
function unpackPlayer(bytes: Uint8Array, base: number, world: WorldBoundaries, info: PlayerInfo): PlayerState {
const x = unpack_f32(bytes[base] + (bytes[base + 1] << 8), world.boundary_min.x, world.boundary_max.x);
const y = unpack_f32(bytes[base + 2] + (bytes[base + 3] << 8), world.boundary_min.y, world.boundary_max.y);
const team_class_health = bytes[base + 4] + (bytes[base + 5] << 8);
const angle = unpack_angle(bytes[base + 6]);
const health = team_class_health & 1013;
const team = (team_class_health >> 14) as Team;
const playerClass = ((team_class_health >> 10) & 15) as Class;
const charge = bytes[base + 7];
return {
position: {x, y},
angle,
health,
team,
playerClass,
info,
charge
}
}
function unpackBuilding(bytes: Uint8Array, base: number, world: WorldBoundaries): BuildingState {
const x = unpack_f32(bytes[base] + (bytes[base + 1] << 8), world.boundary_min.x, world.boundary_max.x);
const y = unpack_f32(bytes[base + 2] + (bytes[base + 3] << 8), world.boundary_min.y, world.boundary_max.y);
const team_type_health = bytes[base + 4] + (bytes[base + 5] << 8);
const angle = unpack_angle(bytes[base + 6]);
const health = team_type_health & 1013;
const team = (((team_type_health >> 13) & 1) === 0) ? Team.Blue : Team.Red;
const level = (team_type_health >> 14);
const buildingType = ((team_type_health >> 10) & 7) as BuildingType;
return {
position: {x, y},
angle,
health,
team,
buildingType,
level,
}
}

View file

@ -1,7 +1,8 @@
import {Panner} from "../Panner/Panner";
import {createEffect, createSignal, ParentProps} from "solid-js";
import {createEffect, createSignal, ParentProps, Show} from "solid-js";
import { createElementSize } from "@solid-primitives/resize-observer";
export class MapContainerProps {
export interface MapContainerProps {
contentSize: {
width: number;
height: number;
@ -9,25 +10,31 @@ export class MapContainerProps {
onScale?: (scale: number) => any;
}
export const MapContainer = ({children, contentSize, onScale}: ParentProps<MapContainerProps>) => {
export const MapContainer = (props: ParentProps<MapContainerProps>) => {
const [container, setContainer] = createSignal<Element>();
const scale = () => Math.min(
container().clientWidth / contentSize.width,
container().clientHeight / contentSize.height
);
const size = createElementSize(container);
const scale = () => {
if (size.width && size.height) {
return Math.min(size.width / props.contentSize.width, size.height / props.contentSize.height);
} else {
return 1;
}
}
createEffect(() => {
if (isFinite(scale())) {
onScale && onScale(scale());
props.onScale && props.onScale(scale());
}
});
return (
<div class="map-container" ref={setContainer}>
<Panner width={container().clientWidth} height={container().clientHeight}
scale={scale()} contentSize={contentSize}
onScale={onScale}>
{children}
<Show when={size.width}>
<Panner width={size.width} height={size.height}
scale={scale()} contentSize={props.contentSize}
onScale={props.onScale}>
{props.children}
</Panner>
</Show>
</div>
)
}

View file

@ -1,7 +1,8 @@
import {Player as PlayerDot} from './Render/Player';
import {Building as BuildingDot} from './Render/Building';
import {findMapAlias} from './MapBoundries';
import {PlayerState, Header, WorldBoundaries, BuildingState} from "@demostf/parser-worker";
import {PlayerState, Header, WorldBoundaries, BuildingState} from "./Data/Parser";
import {splitProps} from "solid-js";
export interface MapRenderProps {
header: Header;
@ -15,37 +16,31 @@ export interface MapRenderProps {
scale: number;
}
declare const require: {
<T>(path: string): T;
(paths: string[], callback: (...modules: any[]) => void): void;
ensure: (paths: string[], callback: (require: <T>(path: string) => T) => void) => void;
};
export function MapRender({header, players, size, world, scale, buildings}: MapRenderProps) {
const mapAlias = findMapAlias(header.map);
export function MapRender(props: MapRenderProps) {
const mapAlias = findMapAlias(props.header.map);
const image = `images/leveloverview/dist/${mapAlias}.webp`;
const background = `url(${image})`;
const playerDots = players
const playerDots = () => props.players
.filter((player: PlayerState) => player.health)
.map((player: PlayerState) => {
return <PlayerDot player={player} mapBoundary={world}
targetSize={size} scale={scale} />
return <PlayerDot player={player} mapBoundary={props.world}
targetSize={props.size} scale={props.scale} />
});
const buildingDots = buildings
const buildingDots = () => props.buildings
.filter((building: PlayerState) => building.position.x)
.map((building: PlayerState) => {
return <BuildingDot building={building}
mapBoundary={world}
targetSize={size} scale={scale}/>
mapBoundary={props.world}
targetSize={props.size} scale={props.scale}/>
});
return (
<svg class="map-background" width={size.width} height={size.height}
<svg class="map-background" width={props.size.width} height={props.size.height}
style={{"background-image": background}}>
{playerDots}
{buildingDots}
{playerDots()}
{buildingDots()}
</svg>
);
}

View file

@ -1,4 +1,5 @@
import {BuildingState, WorldBoundaries, BuildingType, Team} from "@demostf/parser-worker";
import {BuildingState, WorldBoundaries, BuildingType, Team} from "../Data/Parser";
import {Show} from "solid-js";
export interface BuildingProp {
building: BuildingState;
@ -54,17 +55,15 @@ export function Building({building, mapBoundary, targetSize, scale}: BuildingPro
const alpha = building.health / maxHealth;
try {
const image = getIcon(building);
const angle = (building.angle) ?
<polygon points="-6,14 0, 16 6,14 0,24" fill="white"
transform={`rotate(${270 - building.angle})`}/> : '';
return <g transform={`translate(${scaledX} ${scaledY}) scale(${1 / scale})`}
opacity={alpha}>
{angle}
<image href={image} class={"player-icon"} height={32}
<image href={image} className={"player-icon"} height={32}
width={32}
transform={`translate(-16 -16)`}/>
<Show when={building.angle}>
<polygon points="-6,14 0, 16 6,14 0,24" fill="white"
transform={`rotate(${270 - building.angle})`}/>
</Show>
</g>
} catch (e) {
console.log(e);

View file

@ -1,4 +1,4 @@
import {Kill, PlayerState} from "@demostf/parser-worker";
import {Kill, PlayerState} from "../Data/Parser";
import {killAlias} from "./killAlias";
export interface KillFeedProps {

View file

@ -1,4 +1,4 @@
import {PlayerState, WorldBoundaries, Team} from "@demostf/parser-worker";
import {PlayerState, WorldBoundaries, Team} from "../Data/Parser";
export interface PlayerProp {
player: PlayerState;

View file

@ -1,4 +1,4 @@
import {PlayerState} from "@demostf/parser-worker";
import {PlayerState} from "../Data/Parser";
export interface PlayerSpecProps {
player: PlayerState;

View file

@ -1,7 +1,7 @@
import {PlayersSpec} from './PlayerSpec';
import {KillFeed} from './KillFeed';
import {AsyncParser} from "../Data/AsyncParser";
import {PlayerState, Kill} from "@demostf/parser-worker";
import {PlayerState, Kill} from "../Data/Parser";
export interface SpecHUDProps {
tick: number;

View file

@ -1,4 +1,4 @@
import {ParsedDemo, PlayerState, Header, WorldBoundaries, Team} from "@demostf/parser-worker";
import {ParsedDemo, PlayerState, Header, WorldBoundaries, Team} from "../Data/Parser";
export interface TimelineProps {
parser: AsyncParser;
@ -8,16 +8,13 @@ export interface TimelineProps {
}
export const Timeline = ({parser, tick, onSetTick, disabled}) => {
const background = <TimeLineBackground parser={parser}/>;
return (<div class="timeline">
{background}
<input class="timeline-progress" type="range" min={0}
max={parser.demo.tickCount} value={tick}
disabled={disabled}
return <div class="timeline">
<input max={parser.demo.tickCount} value={tick} class="timeline-progress" type="range" min={0}
onChange={(event) => {onSetTick(parseInt(event.target.value, 10))}}
disabled={disabled}
/>
</div>);
<TimeLineBackground parser={parser}/>
</div>;
}
import {AsyncParser} from "../Data/AsyncParser";

View file

@ -85,9 +85,14 @@ export const Panner = (props: ParentProps<PannerProps>) => {
y: Math.floor(panner.screen.height / 2)
});
const pannerStyle = () => {
console.log(props.width, props.height, props.scale);
return {width: `${props.width}px`, height: `${props.height}px`}
};
return (
<div class="pan-zoom-element"
style={{width: `${props.width}px`, height: `${props.height}px`}}
style={pannerStyle()}
onMouseDown={mouseDown}
onWheel={mouseWheel}>
<div class="content-container noselect"

View file

@ -21,6 +21,7 @@ use crate::pages::index::{DemoListScript, Index};
use crate::pages::profile::Profile;
use crate::pages::upload::{UploadPage, UploadScript};
use crate::pages::uploads::Uploads;
use crate::pages::viewer::{ParseWorkerScript, ParserWasm, ViewerPage, ViewerScript, ViewerStyle};
use crate::pages::{render, GlobalStyle};
use crate::session::{SessionData, COOKIE_NAME};
use async_session::{MemoryStore, Session, SessionStore};
@ -97,6 +98,13 @@ async fn main() -> Result<()> {
)
.route(UploadScript::route(), get(serve_asset::<UploadScript>))
.route(DemoListScript::route(), get(serve_asset::<DemoListScript>))
.route(ViewerScript::route(), get(serve_asset::<ViewerScript>))
.route(ViewerStyle::route(), get(serve_asset::<ViewerStyle>))
.route(
ParseWorkerScript::route(),
get(serve_asset::<ParseWorkerScript>),
)
.route(ParserWasm::route(), get(serve_asset::<ParserWasm>))
.route(LogoPng::route(), get(serve_asset::<LogoPng>))
.route(LogoSvg::route(), get(serve_asset::<LogoSvg>))
.route("/fragments/demo-list", get(demo_list))
@ -106,6 +114,8 @@ async fn main() -> Result<()> {
.route("/login", get(login))
.route("/logout", get(logout))
.route("/upload", get(upload))
.route("/viewer", get(viewer))
.route("/viewer/:id", get(viewer))
.route("/:id", get(demo))
.layer(
TraceLayer::new_for_http().make_span_with(|request: &Request<_>| {
@ -346,6 +356,24 @@ async fn profiles(
))
}
async fn viewer(
State(app): State<Arc<App>>,
id: Option<Path<String>>,
session: SessionData,
) -> Result<Markup> {
let demo = if let Some(Path(id)) = id {
let id = id.parse().map_err(|_| Error::NotFound)?;
Some(
Demo::by_id(&app.connection, id)
.await?
.ok_or(Error::NotFound)?,
)
} else {
None
};
Ok(render(ViewerPage { demo }, session))
}
async fn handler_404() -> impl IntoResponse {
Error::NotFound
}

View file

@ -6,6 +6,7 @@ mod plugin_section;
pub mod profile;
pub mod upload;
pub mod uploads;
pub mod viewer;
use crate::session::SessionData;
use demostf_build::Asset;

68
src/pages/viewer.rs Normal file
View file

@ -0,0 +1,68 @@
use crate::data::demo::Demo;
use crate::pages::Page;
use demostf_build::Asset;
use maud::{html, Markup};
use std::borrow::Cow;
pub struct ViewerPage {
pub demo: Option<Demo>,
}
#[derive(Asset)]
#[asset(source = "script/viewer.tsx", url = "/viewer.js")]
pub struct ViewerScript;
#[derive(Asset)]
#[asset(
source = "script/viewer/Analyse/Data/ParseWorker.ts",
url = "/parse-worker.js"
)]
pub struct ParseWorkerScript;
#[derive(Asset)]
#[asset(source = "style/pages/viewer.css", url = "/viewer.css")]
pub struct ViewerStyle;
#[derive(Asset)]
#[asset(
source = "node_modules/@demostf/tf-demos-viewer/tf_demos_viewer_bg.wasm",
url = "/tf-demo-viewer.wasm"
)]
pub struct ParserWasm;
impl Page for ViewerPage {
fn title(&self) -> Cow<'static, str> {
format!(
"{} - demos.tf",
self.demo
.as_ref()
.map(|demo| demo.server.as_str())
.unwrap_or("Viewer")
)
.into()
}
fn render(&self) -> Markup {
let script = ViewerScript::url();
let style_url = ViewerStyle::url();
html! {
.viewer-page {
@if let Some(demo) = self.demo.as_ref() {
input type = "hidden" name = "url" value = (demo.url) {}
progress.download min = "0" max = "100" value = "0" {}
} @else {
.dropzone role = "button" {
noscript {
"Javascript is required to view a demo."
}
span.text { "Drop files or click to view" }
input type = "file" {}
}
}
progress.parse min = "0" max = "100" value = "0" {}
}
script module src = (script) type = "text/javascript" {}
link rel="stylesheet" type="text/css" href=(style_url);
}
}
}

14
style/pages/viewer.css Normal file
View file

@ -0,0 +1,14 @@
@import 'viewer/AnalyseMenu.css';
@import 'viewer/Analyser.css';
@import 'viewer/KillFeed.css';
@import 'viewer/MapContainer.css';
@import 'viewer/MapRender.css';
@import 'viewer/Panner.css';
@import 'viewer/Player.css';
@import 'viewer/PlayerSpec.css';
@import 'viewer/Timeline.css';
progress {
width: 100%;
display: block;
}

View file

@ -9,7 +9,7 @@
opacity: 1;
}
.share-session {
& .share-session {
background: transparent;
color: var(--primary-color);
@ -19,7 +19,7 @@
margin: 10px;
border: none;
cursor: pointer;
background-image: url("../images/link_white.svg");
background-image: url("images/link_white.svg");
background-size: contain;
&:active, &:focus {
@ -27,7 +27,7 @@
}
}
.share-text {
& .share-text {
color: var(--primary-color);
background-color: var(--text-secondary);
padding: 5px;

View file

@ -14,14 +14,14 @@
height: 100px;
background-color: var(--primary-color-accent);
.timeline {
& .timeline {
position: absolute;
bottom: 0;
left: 64px;
width: calc(100% - 64px);
}
.play-pause-button {
& .play-pause-button {
position: absolute;
bottom: 0;
left: 0;
@ -50,25 +50,25 @@
background-size: contain;
background-repeat: no-repeat;
background-position: 50% 50%;
background-image: url('../images/teleporter.png');
background-image: url('images/teleporter.png');
width: 100%;
height: 500px;
margin: 50px 0;
}
}
.error {
& .error {
background-color: #FF9494;
line-height: 32px;
margin: 0 -30px;
padding: 32px;
padding-left: 74px;
background-image: url('../images/error.png');
background-image: url('images/error.png');
background-size: 32px;
background-repeat: no-repeat;
background-position: 32px 32px;
.error-hint {
& .error-hint {
margin-top: 32px;
}
}

View file

@ -5,11 +5,11 @@
padding: 15px;
user-select: none;
.kill {
.red {
& .kill {
& .red {
color: #a75d50;
}
.blue {
& .blue {
color: #5b818f;
}
@ -30,7 +30,7 @@
float: right;
clear: both;
.player {
& .player {
padding: 0 5px;
}
}

View file

@ -4,14 +4,14 @@
overflow: hidden;
cursor: default;
.content-container {
& .content-container {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
}
.noselect {
& .noselect {
user-select: none;
}
}
@ -35,7 +35,7 @@
background-color: #f8f8f8;
border-radius: 5px;
div {
& div {
transition: all 0.5s;
width: 32px;
text-align: center;
@ -47,7 +47,7 @@
}
}
div:first-child {
& div:first-child {
border-bottom: 1px solid #ddd;
margin: 0;
}

View file

@ -26,7 +26,7 @@
height: 28px;
}
.class-icon, .steam-avatar {
& .class-icon, .steam-avatar {
width: 42px;
height: 42px;
display: inline-block;
@ -44,7 +44,7 @@
}
}
.player-name {
& .player-name {
display: inline-block;
position: relative;
padding: 0 5px;
@ -54,7 +54,7 @@
text-overflow: ellipsis;
}
.health-container {
& .health-container {
display: inline-block;
position: absolute;
left: 42px;
@ -63,13 +63,13 @@
width: calc(100% - 42px);
line-height: 28px;
font-weight: bold;
.health {
& .health {
position: relative;
float: right;
padding: 0 5px;
}
.healthbar {
& .healthbar {
position: absolute;
top: 0;
left: 0;
@ -78,60 +78,60 @@
}
&.red {
.health-container {
& .health-container {
background-color: #a75d50aa;
}
.healthbar {
& .healthbar {
background-color: #a75d50;
}
.class-icon.scout {
& .class-icon.scout {
background-image: url('../../images/class_portraits/Icon_scout.jpg');
}
.class-icon.soldier {
& .class-icon.soldier {
background-image: url('../../images/class_portraits/Icon_soldier.jpg');
}
.class-icon.pyro {
& .class-icon.pyro {
background-image: url('../../images/class_portraits/Icon_pyro.jpg');
}
.class-icon.demoman {
& .class-icon.demoman {
background-image: url('../../images/class_portraits/Icon_demoman.jpg');
}
.class-icon.engineer {
& .class-icon.engineer {
background-image: url('../../images/class_portraits/Icon_engineer.jpg');
}
.class-icon.heavy {
& .class-icon.heavy {
background-image: url('../../images/class_portraits/Icon_heavy.jpg');
}
.class-icon.medic {
& .class-icon.medic {
background-image: url('../../images/class_portraits/Icon_medic.jpg');
}
.class-icon.sniper {
& .class-icon.sniper {
background-image: url('../../images/class_portraits/Icon_sniper.jpg');
}
.class-icon.spy{
& .class-icon.spy{
background-image: url('../../images/class_portraits/Icon_spy.jpg');
}
.class-icon.uber {
background-image: url('../../images/charge_red.svg');
& .class-icon.uber {
background-image: url('images/charge_red.svg');
}
.class-icon, .steam-avatar {
& .class-icon, & .steam-avatar {
right: 0;
left: auto;
}
.health-container {
& .health-container {
right: 42px;
left: auto;
}
.health {
& .health {
float: left;
}
.player-name {
& .player-name {
float: right;
direction: ltr;
text-align: right;
@ -139,52 +139,52 @@
}
&.blue {
.health-container {
& .health-container {
background-color: #5b818faa;
}
.healthbar {
& .healthbar {
background-color: #5b818f;
}
.class-icon.scout {
& .class-icon.scout {
background-image: url('../../images/class_portraits/Icon_scout_blue.jpg');
}
.class-icon.soldier {
& .class-icon.soldier {
background-image: url('../../images/class_portraits/Icon_soldier_blue.jpg');
}
.class-icon.pyro {
& .class-icon.pyro {
background-image: url('../../images/class_portraits/Icon_pyro_blue.jpg');
}
.class-icon.demoman {
& .class-icon.demoman {
background-image: url('../../images/class_portraits/Icon_demoman_blue.jpg');
}
.class-icon.engineer {
& .class-icon.engineer {
background-image: url('../../images/class_portraits/Icon_engineer_blue.jpg');
}
.class-icon.heavy {
& .class-icon.heavy {
background-image: url('../../images/class_portraits/Icon_heavy_blue.jpg');
}
.class-icon.medic {
& .class-icon.medic {
background-image: url('../../images/class_portraits/Icon_medic_blue.jpg');
}
.class-icon.sniper {
& .class-icon.sniper {
background-image: url('../../images/class_portraits/Icon_sniper_blue.jpg');
}
.class-icon.spy {
& .class-icon.spy {
background-image: url('../../images/class_portraits/Icon_spy_blue.jpg');
}
.class-icon.uber {
background-image: url('../../images/charge_blue.svg');
& .class-icon.uber {
background-image: url('images/charge_blue.svg');
}
}
&.overhealed {
.health {
& .health {
color: #79d297;
}
.health:after {
& .health:after {
position: absolute;
top: 21px;
right: 0;
@ -203,15 +203,15 @@
}
&.dead {
.healthbar, .health {
& .healthbar, & .health {
display: none;
}
.health-container {
& .health-container {
background-color: transparent;
}
.class-icon {
& .class-icon {
opacity: 0.5;
}
}

View file

@ -1,8 +1,6 @@
.timeline {
width: 100%;
height: 100px;
/*bottom: 0;*/
/*position: relative;*/
}
.timeline-progress {