Switch over to WebAssembly, Rust and Yew #35

Merged
BlakeRain merged 87 commits from yew-static into main 2023-08-30 18:01:40 +00:00
16 changed files with 719 additions and 327 deletions
Showing only changes of commit c3dd1dc3fe - Show all commits

424
Cargo.lock generated
View File

@ -64,6 +64,12 @@ dependencies = [
"rustc-demangle", "rustc-demangle",
] ]
[[package]]
name = "base64"
version = "0.21.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d"
[[package]] [[package]]
name = "base64ct" name = "base64ct"
version = "1.6.0" version = "1.6.0"
@ -134,6 +140,22 @@ dependencies = [
"wasm-bindgen", "wasm-bindgen",
] ]
[[package]]
name = "core-foundation"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa"
[[package]] [[package]]
name = "deranged" name = "deranged"
version = "0.3.8" version = "0.3.8"
@ -143,6 +165,15 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "encoding_rs"
version = "0.8.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1"
dependencies = [
"cfg-if",
]
[[package]] [[package]]
name = "env_logger" name = "env_logger"
version = "0.10.0" version = "0.10.0"
@ -183,12 +214,33 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "fastrand"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764"
[[package]] [[package]]
name = "fnv" name = "fnv"
version = "1.0.7" version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "foreign-types"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
dependencies = [
"foreign-types-shared",
]
[[package]]
name = "foreign-types-shared"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
[[package]] [[package]]
name = "form_urlencoded" name = "form_urlencoded"
version = "1.2.0" version = "1.2.0"
@ -461,6 +513,25 @@ dependencies = [
"yaml-rust", "yaml-rust",
] ]
[[package]]
name = "h2"
version = "0.3.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91fc23aa11be92976ef4729127f1a74adf36d8436f7816b185d18df956790833"
dependencies = [
"bytes",
"fnv",
"futures-core",
"futures-sink",
"futures-util",
"http",
"indexmap 1.9.3",
"slab",
"tokio",
"tokio-util",
"tracing",
]
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.12.3" version = "0.12.3"
@ -499,12 +570,82 @@ dependencies = [
"itoa", "itoa",
] ]
[[package]]
name = "http-body"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1"
dependencies = [
"bytes",
"http",
"pin-project-lite",
]
[[package]]
name = "httparse"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904"
[[package]]
name = "httpdate"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
[[package]] [[package]]
name = "humantime" name = "humantime"
version = "2.1.0" version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]]
name = "hyper"
version = "0.14.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468"
dependencies = [
"bytes",
"futures-channel",
"futures-core",
"futures-util",
"h2",
"http",
"http-body",
"httparse",
"httpdate",
"itoa",
"pin-project-lite",
"socket2 0.4.9",
"tokio",
"tower-service",
"tracing",
"want",
]
[[package]]
name = "hyper-tls"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
dependencies = [
"bytes",
"hyper",
"native-tls",
"tokio",
"tokio-native-tls",
]
[[package]]
name = "idna"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c"
dependencies = [
"unicode-bidi",
"unicode-normalization",
]
[[package]] [[package]]
name = "implicit-clone" name = "implicit-clone"
version = "0.3.6" version = "0.3.6"
@ -553,6 +694,12 @@ dependencies = [
"hashbrown 0.14.0", "hashbrown 0.14.0",
] ]
[[package]]
name = "ipnet"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6"
[[package]] [[package]]
name = "is-terminal" name = "is-terminal"
version = "0.4.9" version = "0.4.9"
@ -579,6 +726,12 @@ dependencies = [
"wasm-bindgen", "wasm-bindgen",
] ]
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.147" version = "0.2.147"
@ -619,6 +772,12 @@ version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "mime"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]] [[package]]
name = "miniz_oxide" name = "miniz_oxide"
version = "0.7.1" version = "0.7.1"
@ -639,6 +798,24 @@ dependencies = [
"windows-sys", "windows-sys",
] ]
[[package]]
name = "native-tls"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e"
dependencies = [
"lazy_static",
"libc",
"log",
"openssl",
"openssl-probe",
"openssl-sys",
"schannel",
"security-framework",
"security-framework-sys",
"tempfile",
]
[[package]] [[package]]
name = "num_cpus" name = "num_cpus"
version = "1.16.0" version = "1.16.0"
@ -673,6 +850,50 @@ version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
[[package]]
name = "openssl"
version = "0.10.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "729b745ad4a5575dd06a3e1af1414bd330ee561c01b3899eb584baeaa8def17e"
dependencies = [
"bitflags 1.3.2",
"cfg-if",
"foreign-types",
"libc",
"once_cell",
"openssl-macros",
"openssl-sys",
]
[[package]]
name = "openssl-macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.29",
]
[[package]]
name = "openssl-probe"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
[[package]]
name = "openssl-sys"
version = "0.9.91"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "866b5f16f90776b9bb8dc1e1802ac6f0513de3a7a7465867bfbc563dc737faac"
dependencies = [
"cc",
"libc",
"pkg-config",
"vcpkg",
]
[[package]] [[package]]
name = "parking_lot" name = "parking_lot"
version = "0.12.1" version = "0.12.1"
@ -745,6 +966,12 @@ dependencies = [
"thiserror", "thiserror",
] ]
[[package]]
name = "pkg-config"
version = "0.3.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964"
[[package]] [[package]]
name = "prettyplease" name = "prettyplease"
version = "0.1.25" version = "0.1.25"
@ -852,6 +1079,43 @@ version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2"
[[package]]
name = "reqwest"
version = "0.11.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e9ad3fe7488d7e34558a2033d45a0c90b72d97b4f80705666fea71472e2e6a1"
dependencies = [
"base64",
"bytes",
"encoding_rs",
"futures-core",
"futures-util",
"h2",
"http",
"http-body",
"hyper",
"hyper-tls",
"ipnet",
"js-sys",
"log",
"mime",
"native-tls",
"once_cell",
"percent-encoding",
"pin-project-lite",
"serde",
"serde_json",
"serde_urlencoded",
"tokio",
"tokio-native-tls",
"tower-service",
"url",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
"winreg",
]
[[package]] [[package]]
name = "route-recognizer" name = "route-recognizer"
version = "0.3.1" version = "0.3.1"
@ -889,12 +1153,44 @@ version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
[[package]]
name = "schannel"
version = "0.1.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88"
dependencies = [
"windows-sys",
]
[[package]] [[package]]
name = "scopeguard" name = "scopeguard"
version = "1.2.0" version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "security-framework"
version = "2.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de"
dependencies = [
"bitflags 1.3.2",
"core-foundation",
"core-foundation-sys",
"libc",
"security-framework-sys",
]
[[package]]
name = "security-framework-sys"
version = "2.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.171" version = "1.0.171"
@ -980,6 +1276,7 @@ dependencies = [
"gray_matter", "gray_matter",
"include_dir", "include_dir",
"log", "log",
"reqwest",
"serde", "serde",
"serde_json", "serde_json",
"serde_yaml", "serde_yaml",
@ -1010,6 +1307,16 @@ version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9"
[[package]]
name = "socket2"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662"
dependencies = [
"libc",
"winapi",
]
[[package]] [[package]]
name = "socket2" name = "socket2"
version = "0.5.3" version = "0.5.3"
@ -1042,6 +1349,19 @@ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]]
name = "tempfile"
version = "3.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef"
dependencies = [
"cfg-if",
"fastrand",
"redox_syscall",
"rustix",
"windows-sys",
]
[[package]] [[package]]
name = "termcolor" name = "termcolor"
version = "1.2.0" version = "1.2.0"
@ -1102,6 +1422,21 @@ dependencies = [
"time-core", "time-core",
] ]
[[package]]
name = "tinyvec"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
dependencies = [
"tinyvec_macros",
]
[[package]]
name = "tinyvec_macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.32.0" version = "1.32.0"
@ -1116,7 +1451,7 @@ dependencies = [
"parking_lot", "parking_lot",
"pin-project-lite", "pin-project-lite",
"signal-hook-registry", "signal-hook-registry",
"socket2", "socket2 0.5.3",
"tokio-macros", "tokio-macros",
"windows-sys", "windows-sys",
] ]
@ -1132,6 +1467,16 @@ dependencies = [
"syn 2.0.29", "syn 2.0.29",
] ]
[[package]]
name = "tokio-native-tls"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
dependencies = [
"native-tls",
"tokio",
]
[[package]] [[package]]
name = "tokio-stream" name = "tokio-stream"
version = "0.1.14" version = "0.1.14"
@ -1143,6 +1488,26 @@ dependencies = [
"tokio", "tokio",
] ]
[[package]]
name = "tokio-util"
version = "0.7.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d"
dependencies = [
"bytes",
"futures-core",
"futures-sink",
"pin-project-lite",
"tokio",
"tracing",
]
[[package]]
name = "tower-service"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52"
[[package]] [[package]]
name = "tracing" name = "tracing"
version = "0.1.37" version = "0.1.37"
@ -1175,6 +1540,18 @@ dependencies = [
"once_cell", "once_cell",
] ]
[[package]]
name = "try-lock"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed"
[[package]]
name = "unicode-bidi"
version = "0.3.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460"
[[package]] [[package]]
name = "unicode-blocks" name = "unicode-blocks"
version = "0.1.8" version = "0.1.8"
@ -1187,24 +1564,59 @@ version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c"
[[package]]
name = "unicode-normalization"
version = "0.1.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921"
dependencies = [
"tinyvec",
]
[[package]] [[package]]
name = "unsafe-libyaml" name = "unsafe-libyaml"
version = "0.2.9" version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f28467d3e1d3c6586d8f25fa243f544f5800fec42d97032474e17222c2b75cfa" checksum = "f28467d3e1d3c6586d8f25fa243f544f5800fec42d97032474e17222c2b75cfa"
[[package]]
name = "url"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb"
dependencies = [
"form_urlencoded",
"idna",
"percent-encoding",
]
[[package]] [[package]]
name = "utf8-width" name = "utf8-width"
version = "0.1.6" version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5190c9442dcdaf0ddd50f37420417d219ae5261bbf5db120d0f9bab996c9cba1" checksum = "5190c9442dcdaf0ddd50f37420417d219ae5261bbf5db120d0f9bab996c9cba1"
[[package]]
name = "vcpkg"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]] [[package]]
name = "version_check" name = "version_check"
version = "0.9.4" version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "want"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
dependencies = [
"try-lock",
]
[[package]] [[package]]
name = "wasi" name = "wasi"
version = "0.11.0+wasi-snapshot-preview1" version = "0.11.0+wasi-snapshot-preview1"
@ -1395,6 +1807,16 @@ version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
[[package]]
name = "winreg"
version = "0.50.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1"
dependencies = [
"cfg-if",
"windows-sys",
]
[[package]] [[package]]
name = "words-count" name = "words-count"
version = "0.1.5" version = "0.1.5"

View File

@ -34,6 +34,7 @@ yew = { version = "0.20" }
yew-hooks = { version = "0.2" } yew-hooks = { version = "0.2" }
yew-router = { version = "0.17" } yew-router = { version = "0.17" }
log = { version = "0.4" } log = { version = "0.4" }
reqwest = { version = "0.11", features = ["json"] }
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = { version = "1.0" } serde_json = { version = "1.0" }
serde_yaml = { version = "0.9" } serde_yaml = { version = "0.9" }

View File

@ -10,24 +10,16 @@ use yew_router::{
#[cfg(not(feature = "static"))] #[cfg(not(feature = "static"))]
use yew_router::BrowserRouter; use yew_router::BrowserRouter;
use crate::{ use crate::{components::layout::Layout, pages::Route};
components::layout::Layout,
model::{source::ModelProvider, tags::TagsProvider},
pages::Route,
};
#[function_component(AppContent)] #[function_component(AppContent)]
fn app_content() -> Html { fn app_content() -> Html {
html! { html! {
<ModelProvider>
<TagsProvider>
<Layout> <Layout>
<main> <main>
<Switch<Route> render={Route::switch} /> <Switch<Route> render={Route::switch} />
</main> </main>
</Layout> </Layout>
</TagsProvider>
</ModelProvider>
} }
} }
@ -43,6 +35,7 @@ pub struct AppProps {
pub fn app(props: &AppProps) -> Html { pub fn app(props: &AppProps) -> Html {
#[cfg(feature = "static")] #[cfg(feature = "static")]
{ {
log::info!("Application is running in static mode");
let history = AnyHistory::from(MemoryHistory::default()); let history = AnyHistory::from(MemoryHistory::default());
history.push(&props.url); history.push(&props.url);
html! { html! {

View File

@ -80,7 +80,18 @@ async fn copy_resources(
dist_dir: impl AsRef<Path>, dist_dir: impl AsRef<Path>,
out_dir: impl AsRef<Path>, out_dir: impl AsRef<Path>,
) -> std::io::Result<()> { ) -> std::io::Result<()> {
let mut resources = tokio::fs::read_dir(dist_dir).await?; let mut stack: Vec<(PathBuf, PathBuf)> = vec![(
dist_dir.as_ref().to_path_buf(),
out_dir.as_ref().to_path_buf(),
)];
while let Some((dist_dir, out_dir)) = stack.pop() {
if !out_dir.exists() {
log::info!("Creaing output directory: {out_dir:?}");
tokio::fs::create_dir(&out_dir).await?;
}
let mut resources = tokio::fs::read_dir(&dist_dir).await?;
while let Some(entry) = resources.next_entry().await? { while let Some(entry) = resources.next_entry().await? {
let Ok(file_type) = entry.file_type().await else { let Ok(file_type) = entry.file_type().await else {
log::error!("Could not get file type for: {:?}", entry.path()); log::error!("Could not get file type for: {:?}", entry.path());
@ -94,9 +105,37 @@ async fn copy_resources(
let path = entry.path(); let path = entry.path();
log::info!("Copying resource: {:?}", path); log::info!("Copying resource: {:?}", path);
tokio::fs::copy(path, out_dir.as_ref().join(entry.file_name())).await?; tokio::fs::copy(path, out_dir.join(entry.file_name())).await?;
}
if file_type.is_dir() {
let name = entry.file_name();
let out_dir = out_dir.join(name.clone());
let dist_dir = dist_dir.join(name.clone());
stack.push((dist_dir, out_dir));
} }
} }
}
Ok(())
}
async fn write_data(out_dir: impl AsRef<Path>) -> std::io::Result<()> {
log::info!("Writing tags.json ...");
let tags = site::model::source::get_tags();
tokio::fs::write(
out_dir.as_ref().join("tags.json"),
serde_json::to_string(&tags)?,
)
.await?;
log::info!("Writing posts.json ...");
let posts = site::model::source::get_posts();
tokio::fs::write(
out_dir.as_ref().join("posts.json"),
serde_json::to_string(&posts)?,
)
.await?;
Ok(()) Ok(())
} }
@ -137,5 +176,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
tokio::fs::write(path, html).await?; tokio::fs::write(path, html).await?;
} }
log::info!("Writing data");
write_data(&out_dir).await?;
Ok(()) Ok(())
} }

View File

@ -5,7 +5,7 @@ use yew_router::prelude::Link;
use crate::{ use crate::{
components::layout::intersperse::Intersperse, components::layout::intersperse::Intersperse,
model::{tags::TagsContext, PostInfo}, model::{source::TagsContext, PostInfo},
pages::Route, pages::Route,
}; };
@ -40,11 +40,13 @@ fn post_card_details(info: &PostInfo, tags: &TagsContext) -> Html {
let tags = Intersperse::from_iter( let tags = Intersperse::from_iter(
html! { <Icon class="text-gray-500" icon_id={IconId::BootstrapDot} /> }, html! { <Icon class="text-gray-500" icon_id={IconId::BootstrapDot} /> },
info.tags.iter().map(|tag| { info.tags.iter().map(|tag| {
if let Some(tag) = tags.get_tag(tag) { if let Some(tag) = tags.get(tag) {
html! { html! {
<Link<Route> <Link<Route>
classes="text-sky-500 hover:text-sky-600" classes="text-sky-500 hover:text-sky-600"
to={Route::Tag { slug: tag.slug }}>{tag.name}</Link<Route>> to={Route::Tag { slug: tag.slug.clone() }}>
{tag.name.clone()}
</Link<Route>>
} }
} else { } else {
html! { html! {

View File

@ -1,17 +1,14 @@
use yew::{function_component, html, Html, Properties}; use yew::{function_component, html, use_context, Html, Properties};
use crate::{components::blog::post_card::PostCard, model::PostInfo}; use crate::{components::blog::post_card::PostCard, model::source::PostsContext};
#[derive(Properties, PartialEq)]
pub struct PostCardListProps {
pub posts: Vec<PostInfo>,
}
#[function_component(PostCardList)] #[function_component(PostCardList)]
pub fn post_card_list(props: &PostCardListProps) -> Html { pub fn post_card_list() -> Html {
let posts = use_context::<PostsContext>().expect("PostsContext to be provided");
html! { html! {
<div class="grid grid-cols-3 gap-x-10 gap-y-20 my-10"> <div class="grid grid-cols-3 gap-x-10 gap-y-20 my-10">
{for props.posts.iter().enumerate().map(|(index, post)| { {for posts.iter().enumerate().map(|(index, post)| {
html! { html! {
<PostCard post={post.clone()} first={index == 0} /> <PostCard post={post.clone()} first={index == 0} />
} }

View File

@ -1,13 +1,12 @@
pub mod frontmatter; pub mod frontmatter;
pub mod source; pub mod source;
pub mod tags;
use serde::Deserialize; use serde::{Deserialize, Serialize};
use time::OffsetDateTime; use time::OffsetDateTime;
use self::frontmatter::FrontMatter; use self::frontmatter::FrontMatter;
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct DocInfo { pub struct DocInfo {
/// The slug used to form the URL for this document. /// The slug used to form the URL for this document.
pub slug: String, pub slug: String,
@ -19,7 +18,7 @@ pub struct DocInfo {
pub published: Option<OffsetDateTime>, pub published: Option<OffsetDateTime>,
} }
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct PostInfo { pub struct PostInfo {
/// Document information. /// Document information.
pub doc_info: DocInfo, pub doc_info: DocInfo,
@ -31,7 +30,7 @@ pub struct PostInfo {
pub cover_image: Option<String>, pub cover_image: Option<String>,
} }
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Post { pub struct Post {
/// Information about the post /// Information about the post
pub info: PostInfo, pub info: PostInfo,
@ -66,7 +65,7 @@ impl PostInfo {
} }
} }
#[derive(Debug, Clone, PartialEq, Deserialize)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Tag { pub struct Tag {
/// The slug of the tag /// The slug of the tag
pub slug: String, pub slug: String,
@ -78,7 +77,7 @@ pub struct Tag {
pub description: Option<String>, pub description: Option<String>,
} }
#[derive(Debug, Clone, PartialEq, Deserialize)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum TagVisibility { pub enum TagVisibility {
#[serde(rename = "public")] #[serde(rename = "public")]
Public, Public,

View File

@ -2,8 +2,6 @@ use gray_matter::{engine::YAML, Matter, ParsedEntity};
use serde::Deserialize; use serde::Deserialize;
use time::OffsetDateTime; use time::OffsetDateTime;
use super::source::SourceError;
#[derive(Debug, Clone, PartialEq, Deserialize)] #[derive(Debug, Clone, PartialEq, Deserialize)]
pub struct FrontMatter { pub struct FrontMatter {
pub title: String, pub title: String,
@ -16,20 +14,21 @@ pub struct FrontMatter {
pub cover: Option<String>, pub cover: Option<String>,
} }
pub fn parse_front_matter( pub fn parse_front_matter(content: &[u8]) -> (Option<FrontMatter>, ParsedEntity) {
content: &[u8],
) -> Result<(Option<FrontMatter>, ParsedEntity), SourceError> {
let content = unsafe { std::str::from_utf8_unchecked(content) }; let content = unsafe { std::str::from_utf8_unchecked(content) };
let matter = Matter::<YAML>::new().parse(content); let matter = Matter::<YAML>::new().parse(content);
let info: Option<FrontMatter> = if let Some(data) = &matter.data { let info: Option<FrontMatter> = if let Some(data) = &matter.data {
Some(data.deserialize().map_err(|err| { match data.deserialize() {
Ok(info) => Some(info),
Err(err) => {
log::error!("Failed to parse front matter: {err:?}"); log::error!("Failed to parse front matter: {err:?}");
SourceError::InvalidFrontMatter(err.to_string()) None
})?) }
}
} else { } else {
None None
}; };
Ok((info, matter)) (info, matter)
} }

View File

@ -1,86 +1,18 @@
use std::{collections::HashMap, sync::Arc}; #[cfg(feature = "hydration")]
mod hydrating;
use async_trait::async_trait; #[cfg(feature = "hydration")]
use thiserror::Error; pub use hydrating::*;
use yew::{function_component, html, Children, ContextProvider, Html, Properties};
#[cfg(not(feature = "hydration"))]
mod filesystem;
use std::collections::HashMap;
#[cfg(not(feature = "hydration"))]
pub use filesystem::*;
use super::{PostInfo, Tag}; use super::{PostInfo, Tag};
#[cfg(feature = "hydration")] pub type TagsContext = HashMap<String, Tag>;
mod web; pub type PostsContext = Vec<PostInfo>;
#[cfg(not(feature = "hydration"))]
mod fs;
#[derive(Debug, Clone, Error)]
pub enum SourceError {
#[error("Invalid front matter: {0}")]
InvalidFrontMatter(String),
#[error("Invalid tags format")]
InvalidTags,
}
#[async_trait]
pub trait ModelSource: Send + Sync {
async fn get_posts(&self) -> Result<Vec<PostInfo>, SourceError>;
async fn get_tags(&self) -> Result<HashMap<String, Tag>, SourceError>;
}
pub fn get_source() -> Box<dyn ModelSource> {
// If we're compiled to use hydration, we want to use the web source.
#[cfg(feature = "hydration")]
return Box::new(self::web::Source::new());
// If we're NOT compiled to use hydration, we want to use the file-system source.
#[cfg(not(feature = "hydration"))]
return Box::new(fs::Source::new());
}
pub struct ModelSourceWrapper {
inner: Arc<Box<dyn ModelSource>>,
}
impl Clone for ModelSourceWrapper {
fn clone(&self) -> Self {
Self {
inner: Arc::clone(&self.inner),
}
}
}
impl PartialEq for ModelSourceWrapper {
fn eq(&self, _: &Self) -> bool {
true
}
}
#[async_trait]
impl ModelSource for ModelSourceWrapper {
async fn get_posts(&self) -> Result<Vec<PostInfo>, SourceError> {
self.inner.get_posts().await
}
async fn get_tags(&self) -> Result<HashMap<String, Tag>, SourceError> {
self.inner.get_tags().await
}
}
#[derive(Properties, PartialEq)]
pub struct ModelProviderProps {
#[prop_or_default]
pub children: Children,
}
#[function_component(ModelProvider)]
pub fn model_provider(props: &ModelProviderProps) -> Html {
let source = get_source();
let source = ModelSourceWrapper {
inner: Arc::new(source),
};
html! {
<ContextProvider<ModelSourceWrapper> context={source}>
{props.children.clone()}
</ContextProvider<ModelSourceWrapper>>
}
}

View File

@ -0,0 +1,87 @@
use include_dir::{include_dir, Dir, File};
use yew::{function_component, html, Children, ContextProvider, Html, Properties};
use crate::model::{
frontmatter::parse_front_matter,
source::{PostsContext, TagsContext},
PostInfo, Tag,
};
#[derive(Properties, PartialEq)]
pub struct ProvideTagsProps {
#[prop_or_default]
pub children: Children,
}
#[function_component(ProvideTags)]
pub fn provide_tags(props: &ProvideTagsProps) -> Html {
let tags = get_tags();
html! {
<ContextProvider<TagsContext> context={tags}>
{props.children.clone()}
</ContextProvider<TagsContext>>
}
}
pub fn get_tags() -> TagsContext {
let file = CONTENT_DIR.get_file("tags.yaml").expect("tags.yaml");
match serde_yaml::from_slice::<Vec<Tag>>(file.contents()) {
Ok(tags) => tags
.into_iter()
.map(|tag| (tag.slug.clone(), tag))
.collect::<TagsContext>(),
Err(err) => {
panic!("Failed to parse tags.yaml: {err}");
}
}
}
#[derive(Properties, PartialEq)]
pub struct ProvidePostsProps {
#[prop_or_default]
pub children: Children,
}
#[function_component(ProvidePosts)]
pub fn provide_posts(props: &ProvidePostsProps) -> Html {
let posts = get_posts();
html! {
<ContextProvider<PostsContext> context={posts}>
{props.children.clone()}
</ContextProvider<PostsContext>>
}
}
pub fn get_posts() -> PostsContext {
let files = CONTENT_DIR.get_dir("posts").expect("posts dir").files();
let mut posts = files.filter_map(load_post_info).collect::<Vec<_>>();
posts.sort_by(|a, b| b.doc_info.published.cmp(&a.doc_info.published));
posts
}
fn load_post_info(file: &File) -> Option<PostInfo> {
let (Some(front_matter), matter) = parse_front_matter(file.contents()) else {
return None;
};
let slug = file
.path()
.file_stem()
.expect("filename")
.to_str()
.expect("valid file name")
.to_string();
let reading_time = words_count::count(&matter.content).words / 200;
Some(PostInfo::from_front_matter(
slug,
Some(reading_time),
matter.excerpt,
front_matter,
))
}
static CONTENT_DIR: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/content");

View File

@ -1,76 +0,0 @@
use std::collections::HashMap;
use async_trait::async_trait;
use include_dir::{include_dir, Dir, File};
use crate::model::{frontmatter::parse_front_matter, PostInfo, Tag};
use super::{ModelSource, SourceError};
pub struct Source {}
impl Source {
pub fn new() -> Self {
Self {}
}
}
#[async_trait]
impl ModelSource for Source {
async fn get_posts(&self) -> Result<Vec<PostInfo>, SourceError> {
let files = get_post_files().map(|file| -> Result<Option<PostInfo>, SourceError> {
let (Some(front_matter), matter) = parse_front_matter(file.contents())? else {
return Ok(None);
};
let slug = file
.path()
.file_stem()
.expect("filename")
.to_str()
.expect("valid file name")
.to_string();
let reading_time = words_count::count(&matter.content).words / 200;
Ok(Some(PostInfo::from_front_matter(
slug,
Some(reading_time),
matter.excerpt,
front_matter,
)))
});
let mut posts = Vec::new();
for file in files {
let file = file?;
if let Some(file) = file {
posts.push(file);
}
}
posts.sort_by(|a, b| b.doc_info.published.cmp(&a.doc_info.published));
Ok(posts)
}
async fn get_tags(&self) -> Result<HashMap<String, Tag>, SourceError> {
let file = CONTENT_DIR.get_file("tags.yaml").expect("tags.yaml");
match serde_yaml::from_slice::<Vec<Tag>>(file.contents()) {
Ok(tags) => Ok(tags
.into_iter()
.map(|tag| (tag.slug.clone(), tag))
.collect()),
Err(err) => {
log::error!("Failed to parse tags.yaml: {err}");
Err(SourceError::InvalidTags)
}
}
}
}
fn get_post_files<'a>() -> impl Iterator<Item = &'a File<'a>> {
CONTENT_DIR.get_dir("posts").expect("posts dir").files()
}
static CONTENT_DIR: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/content");

View File

@ -0,0 +1,94 @@
use std::collections::HashMap;
use thiserror::Error;
use yew::{function_component, html, use_state, Children, ContextProvider, Html, Properties};
use yew_hooks::{use_async_with_options, UseAsyncOptions};
use crate::model::source::{PostsContext, TagsContext};
#[derive(Debug, Error, Clone, PartialEq)]
pub enum ApiError {
#[error("Unable to complete request")]
RequestError,
#[error("Unable to deserialize response")]
DeserializeError,
}
#[derive(Properties, PartialEq)]
pub struct ProvideTagsProps {
#[prop_or_default]
pub children: Children,
}
#[function_component(ProvideTags)]
pub fn provide_tags(props: &ProvideTagsProps) -> Html {
let tags = use_state(HashMap::new);
{
let tags = tags.clone();
use_async_with_options::<_, (), ApiError>(
async move {
let res = reqwest::get("/tags.json")
.await
.map_err(|err| {
log::error!("Failed to get '/tags.json': {err:?}");
ApiError::RequestError
})?
.json()
.await
.map_err(|err| {
log::error!("Failed to deserialize '/tags.json': {err:?}");
ApiError::DeserializeError
})?;
tags.set(res);
Ok(())
},
UseAsyncOptions::enable_auto(),
);
}
html! {
<ContextProvider<TagsContext> context={(*tags).clone()}>
{props.children.clone()}
</ContextProvider<TagsContext>>
}
}
#[derive(Properties, PartialEq)]
pub struct ProvidePostsProps {
#[prop_or_default]
pub children: Children,
}
#[function_component(ProvidePosts)]
pub fn provide_posts(props: &ProvidePostsProps) -> Html {
let posts = use_state(Vec::new);
{
let posts = posts.clone();
use_async_with_options::<_, (), ApiError>(
async move {
let res = reqwest::get("/posts.json")
.await
.map_err(|err| {
log::error!("Failed to get '/posts.json': {err:?}");
ApiError::RequestError
})?
.json()
.await
.map_err(|err| {
log::error!("Failed to deserialize '/posts.json': {err:?}");
ApiError::DeserializeError
})?;
posts.set(res);
Ok(())
},
UseAsyncOptions::enable_auto(),
);
}
html! {
<ContextProvider<PostsContext> context={(*posts).clone()}>
{props.children.clone()}
</ContextProvider<PostsContext>>
}
}

View File

@ -1,20 +0,0 @@
use async_trait::async_trait;
use crate::model::PostInfo;
use super::{ModelProvider, SourceError};
pub struct Source {}
impl Source {
pub fn new() -> Self {
Self {}
}
}
#[async_trait]
impl ModelProvider for Source {
async fn get_posts(&self) -> Result<Vec<PostInfo>, SourceError> {
todo!()
}
}

View File

@ -1,54 +0,0 @@
use std::collections::HashMap;
use yew::{
function_component, html, use_context, use_state, Children, ContextProvider, Html, Properties,
};
use yew_hooks::{use_async_with_options, UseAsyncOptions};
use crate::model::source::{ModelSource, SourceError};
use super::{source::ModelSourceWrapper, Tag};
#[derive(Properties, PartialEq)]
pub struct TagsProviderProps {
#[prop_or_default]
pub children: Children,
}
#[derive(Clone, Default, PartialEq)]
pub struct TagsContext {
tags: HashMap<String, Tag>,
}
impl TagsContext {
fn new(tags: HashMap<String, Tag>) -> Self {
Self { tags }
}
pub fn get_tag<S: AsRef<str>>(&self, slug: S) -> Option<Tag> {
self.tags.get(slug.as_ref()).cloned()
}
}
#[function_component(TagsProvider)]
pub fn tags_provider(props: &TagsProviderProps) -> Html {
let source = use_context::<ModelSourceWrapper>().expect("ModelSource to be provided");
let tags = use_state(TagsContext::default);
{
let tags = tags.clone();
use_async_with_options::<_, (), SourceError>(
async move {
tags.set(TagsContext::new(source.get_tags().await?));
Ok(())
},
UseAsyncOptions::enable_auto(),
);
}
html! {
<ContextProvider<TagsContext> context={(*tags).clone()}>
{props.children.clone()}
</ContextProvider<TagsContext>>
}
}

View File

@ -1,30 +1,17 @@
use yew::{function_component, html, use_context, use_state, Html}; use yew::{function_component, html, Html};
use yew_hooks::{use_async_with_options, UseAsyncOptions};
use crate::{ use crate::{
components::blog::post_card_list::PostCardList, components::blog::post_card_list::PostCardList,
model::source::{ModelSource, ModelSourceWrapper, SourceError}, model::source::{ProvidePosts, ProvideTags},
}; };
#[function_component(Page)] #[function_component(Page)]
pub fn page() -> Html { pub fn page() -> Html {
let source = use_context::<ModelSourceWrapper>().expect("ModelSource to be provided");
let posts = use_state(Vec::new);
{
let posts = posts.clone();
use_async_with_options::<_, (), SourceError>(
async move {
posts.set(source.get_posts().await?);
Ok(())
},
UseAsyncOptions::enable_auto(),
);
}
html! { html! {
<> <ProvideTags>
<PostCardList posts={(*posts).clone()} /> <ProvidePosts>
</> <PostCardList />
</ProvidePosts>
</ProvideTags>
} }
} }

View File

@ -1,30 +1,17 @@
use yew::{function_component, html, use_context, use_state, Html}; use yew::{function_component, html, Html};
use yew_hooks::{use_async_with_options, UseAsyncOptions};
use crate::{ use crate::{
components::blog::post_card_list::PostCardList, components::blog::post_card_list::PostCardList,
model::source::{ModelSource, ModelSourceWrapper, SourceError}, model::source::{ProvidePosts, ProvideTags},
}; };
#[function_component(Page)] #[function_component(Page)]
pub fn page() -> Html { pub fn page() -> Html {
let source = use_context::<ModelSourceWrapper>().expect("ModelSource to be provided");
let posts = use_state(Vec::new);
{
let posts = posts.clone();
use_async_with_options::<_, (), SourceError>(
async move {
posts.set(source.get_posts().await?);
Ok(())
},
UseAsyncOptions::enable_auto(),
);
}
html! { html! {
<> <ProvideTags>
<PostCardList posts={(*posts).clone()} /> <ProvidePosts>
</> <PostCardList />
</ProvidePosts>
</ProvideTags>
} }
} }