├── .circleci └── config.yml ├── .gitignore ├── .rustfmt.toml ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── Makefile ├── README.md ├── examples ├── history_state.rs └── minimal.rs └── src ├── lib.rs └── router.rs /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | --- 2 | version: 2.1 3 | 4 | executors: 5 | container: 6 | docker: 7 | - image: &image saschagrunert/build-rust 8 | 9 | workflows: 10 | version: 2 11 | pipeline: 12 | jobs: 13 | - build 14 | - doc 15 | - doc-publish: 16 | requires: 17 | - doc 18 | filters: 19 | branches: 20 | only: master 21 | - rustfmt 22 | - clippy 23 | - test 24 | jobs: 25 | build: 26 | executor: container 27 | steps: 28 | - checkout 29 | - run: 30 | name: Version information 31 | command: | 32 | rustc --version 33 | rustup --version 34 | cargo --version 35 | - restore_cache: 36 | keys: 37 | - build-cache-{{ arch }}-{{ checksum "Cargo.lock" }} 38 | - run: 39 | name: Build all targets 40 | command: make 41 | - save_cache: 42 | key: build-cache-{{ arch }}-{{ checksum "Cargo.lock" }} 43 | paths: 44 | - /root/.cargo/registry 45 | - target 46 | doc: 47 | executor: container 48 | steps: 49 | - checkout 50 | - restore_cache: 51 | keys: 52 | - doc-cache-{{ arch }}-{{ checksum "Cargo.lock" }} 53 | - run: 54 | name: Build documentation 55 | command: make build-doc 56 | - save_cache: 57 | key: doc-cache-{{ arch }}-{{ checksum "Cargo.lock" }} 58 | paths: 59 | - /root/.cargo/registry 60 | - target 61 | - persist_to_workspace: 62 | root: . 63 | paths: 64 | - target/doc 65 | doc-publish: 66 | executor: container 67 | steps: 68 | - add_ssh_keys: 69 | fingerprints: 70 | - cf:5b:c0:da:af:51:ee:3f:50:5a:ee:a9:a0:03:2b:37 71 | - checkout 72 | - run: 73 | name: Setup git 74 | command: | 75 | git config --global user.email mail@saschagrunert.de 76 | git config --global user.name "CircleCI" 77 | - attach_workspace: 78 | at: . 79 | - run: 80 | name: Deploy documentation 81 | command: | 82 | git fetch origin gh-pages 83 | git checkout -f gh-pages 84 | rm -rf doc 85 | mv target/doc . 86 | git add . 87 | git diff-index --quiet HEAD || git commit -m 'Update documentation' 88 | git push -f origin gh-pages 89 | rustfmt: 90 | executor: container 91 | steps: 92 | - checkout 93 | - run: 94 | name: Rust-Format 95 | command: make lint-rustfmt 96 | clippy: 97 | executor: container 98 | steps: 99 | - checkout 100 | - restore_cache: 101 | keys: 102 | - clippy-cache-{{ arch }}-{{ checksum "Cargo.lock" }} 103 | - run: 104 | name: Clippy 105 | command: make lint-clippy 106 | - save_cache: 107 | key: clippy-cache-{{ arch }}-{{ checksum "Cargo.lock" }} 108 | paths: 109 | - /root/.cargo/registry 110 | - target 111 | test: 112 | executor: container 113 | steps: 114 | - checkout 115 | - restore_cache: 116 | keys: 117 | - test-cache-{{ arch }}-{{ checksum "Cargo.lock" }} 118 | - run: 119 | name: Test if the example builds 120 | command: cargo web build --example minimal 121 | - save_cache: 122 | key: test-cache-{{ arch }}-{{ checksum "Cargo.lock" }} 123 | paths: 124 | - /root/.cargo/registry 125 | - target 126 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 100 2 | hard_tabs = false 3 | tab_spaces = 4 4 | newline_style = "Auto" 5 | use_small_heuristics = "Default" 6 | indent_style = "Block" 7 | wrap_comments = true 8 | format_code_in_doc_comments = true 9 | comment_width = 80 10 | normalize_comments = true 11 | normalize_doc_attributes = true 12 | license_template_path = "" 13 | format_strings = true 14 | format_macro_matchers = true 15 | format_macro_bodies = true 16 | empty_item_single_line = true 17 | struct_lit_single_line = true 18 | fn_single_line = true 19 | where_single_line = true 20 | imports_indent = "Block" 21 | imports_layout = "Mixed" 22 | merge_imports = false 23 | reorder_imports = true 24 | reorder_modules = true 25 | reorder_impl_items = true 26 | type_punctuation_density = "Wide" 27 | space_before_colon = false 28 | space_after_colon = true 29 | spaces_around_ranges = false 30 | binop_separator = "Front" 31 | remove_nested_parens = true 32 | combine_control_expr = true 33 | overflow_delimited_expr = false 34 | struct_field_align_threshold = 0 35 | enum_discrim_align_threshold = 0 36 | match_arm_blocks = true 37 | force_multiline_blocks = false 38 | fn_args_layout = "Tall" 39 | brace_style = "SameLineWhere" 40 | control_brace_style = "AlwaysSameLine" 41 | trailing_semicolon = true 42 | trailing_comma = "Vertical" 43 | match_block_trailing_comma = false 44 | blank_lines_upper_bound = 1 45 | blank_lines_lower_bound = 0 46 | edition = "2018" 47 | version = "One" 48 | inline_attribute_width = 0 49 | merge_derives = true 50 | use_try_shorthand = true 51 | use_field_init_shorthand = true 52 | force_explicit_abi = true 53 | condense_wildcard_suffixes = false 54 | color = "Auto" 55 | required_version = "1.4.4" 56 | unstable_features = true 57 | disable_all_formatting = false 58 | skip_children = false 59 | hide_parse_errors = false 60 | error_on_line_overflow = false 61 | error_on_unformatted = false 62 | report_todo = "Never" 63 | report_fixme = "Never" 64 | ignore = [] 65 | emit_mode = "Files" 66 | make_backup = false 67 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "anymap" 5 | version = "0.12.1" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | 8 | [[package]] 9 | name = "autocfg" 10 | version = "0.1.6" 11 | source = "registry+https://github.com/rust-lang/crates.io-index" 12 | 13 | [[package]] 14 | name = "backtrace" 15 | version = "0.3.38" 16 | source = "registry+https://github.com/rust-lang/crates.io-index" 17 | dependencies = [ 18 | "backtrace-sys 0.1.31 (registry+https://github.com/rust-lang/crates.io-index)", 19 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 20 | "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", 21 | "rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", 22 | ] 23 | 24 | [[package]] 25 | name = "backtrace-sys" 26 | version = "0.1.31" 27 | source = "registry+https://github.com/rust-lang/crates.io-index" 28 | dependencies = [ 29 | "cc 1.0.45 (registry+https://github.com/rust-lang/crates.io-index)", 30 | "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", 31 | ] 32 | 33 | [[package]] 34 | name = "base-x" 35 | version = "0.2.5" 36 | source = "registry+https://github.com/rust-lang/crates.io-index" 37 | 38 | [[package]] 39 | name = "bincode" 40 | version = "1.0.1" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | dependencies = [ 43 | "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", 44 | "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", 45 | ] 46 | 47 | [[package]] 48 | name = "boolinator" 49 | version = "2.4.0" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | 52 | [[package]] 53 | name = "bumpalo" 54 | version = "2.6.0" 55 | source = "registry+https://github.com/rust-lang/crates.io-index" 56 | 57 | [[package]] 58 | name = "byteorder" 59 | version = "1.3.2" 60 | source = "registry+https://github.com/rust-lang/crates.io-index" 61 | 62 | [[package]] 63 | name = "bytes" 64 | version = "0.4.12" 65 | source = "registry+https://github.com/rust-lang/crates.io-index" 66 | dependencies = [ 67 | "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", 68 | "iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 69 | ] 70 | 71 | [[package]] 72 | name = "cc" 73 | version = "1.0.45" 74 | source = "registry+https://github.com/rust-lang/crates.io-index" 75 | 76 | [[package]] 77 | name = "cfg-if" 78 | version = "0.1.10" 79 | source = "registry+https://github.com/rust-lang/crates.io-index" 80 | 81 | [[package]] 82 | name = "discard" 83 | version = "1.0.4" 84 | source = "registry+https://github.com/rust-lang/crates.io-index" 85 | 86 | [[package]] 87 | name = "failure" 88 | version = "0.1.5" 89 | source = "registry+https://github.com/rust-lang/crates.io-index" 90 | dependencies = [ 91 | "backtrace 0.3.38 (registry+https://github.com/rust-lang/crates.io-index)", 92 | "failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 93 | ] 94 | 95 | [[package]] 96 | name = "failure_derive" 97 | version = "0.1.5" 98 | source = "registry+https://github.com/rust-lang/crates.io-index" 99 | dependencies = [ 100 | "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", 101 | "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", 102 | "syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)", 103 | "synstructure 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)", 104 | ] 105 | 106 | [[package]] 107 | name = "fnv" 108 | version = "1.0.6" 109 | source = "registry+https://github.com/rust-lang/crates.io-index" 110 | 111 | [[package]] 112 | name = "http" 113 | version = "0.1.18" 114 | source = "registry+https://github.com/rust-lang/crates.io-index" 115 | dependencies = [ 116 | "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", 117 | "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", 118 | "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", 119 | ] 120 | 121 | [[package]] 122 | name = "indexmap" 123 | version = "1.2.0" 124 | source = "registry+https://github.com/rust-lang/crates.io-index" 125 | 126 | [[package]] 127 | name = "iovec" 128 | version = "0.1.4" 129 | source = "registry+https://github.com/rust-lang/crates.io-index" 130 | dependencies = [ 131 | "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", 132 | ] 133 | 134 | [[package]] 135 | name = "itoa" 136 | version = "0.4.4" 137 | source = "registry+https://github.com/rust-lang/crates.io-index" 138 | 139 | [[package]] 140 | name = "lazy_static" 141 | version = "1.4.0" 142 | source = "registry+https://github.com/rust-lang/crates.io-index" 143 | 144 | [[package]] 145 | name = "libc" 146 | version = "0.2.62" 147 | source = "registry+https://github.com/rust-lang/crates.io-index" 148 | 149 | [[package]] 150 | name = "log" 151 | version = "0.4.8" 152 | source = "registry+https://github.com/rust-lang/crates.io-index" 153 | dependencies = [ 154 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 155 | ] 156 | 157 | [[package]] 158 | name = "proc-macro-hack" 159 | version = "0.5.10" 160 | source = "registry+https://github.com/rust-lang/crates.io-index" 161 | dependencies = [ 162 | "proc-macro2 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", 163 | "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 164 | "syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", 165 | ] 166 | 167 | [[package]] 168 | name = "proc-macro-nested" 169 | version = "0.1.3" 170 | source = "registry+https://github.com/rust-lang/crates.io-index" 171 | 172 | [[package]] 173 | name = "proc-macro2" 174 | version = "0.4.30" 175 | source = "registry+https://github.com/rust-lang/crates.io-index" 176 | dependencies = [ 177 | "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 178 | ] 179 | 180 | [[package]] 181 | name = "proc-macro2" 182 | version = "1.0.4" 183 | source = "registry+https://github.com/rust-lang/crates.io-index" 184 | dependencies = [ 185 | "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 186 | ] 187 | 188 | [[package]] 189 | name = "quote" 190 | version = "0.6.13" 191 | source = "registry+https://github.com/rust-lang/crates.io-index" 192 | dependencies = [ 193 | "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", 194 | ] 195 | 196 | [[package]] 197 | name = "quote" 198 | version = "1.0.2" 199 | source = "registry+https://github.com/rust-lang/crates.io-index" 200 | dependencies = [ 201 | "proc-macro2 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", 202 | ] 203 | 204 | [[package]] 205 | name = "rustc-demangle" 206 | version = "0.1.16" 207 | source = "registry+https://github.com/rust-lang/crates.io-index" 208 | 209 | [[package]] 210 | name = "rustc_version" 211 | version = "0.2.3" 212 | source = "registry+https://github.com/rust-lang/crates.io-index" 213 | dependencies = [ 214 | "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", 215 | ] 216 | 217 | [[package]] 218 | name = "ryu" 219 | version = "1.0.0" 220 | source = "registry+https://github.com/rust-lang/crates.io-index" 221 | 222 | [[package]] 223 | name = "semver" 224 | version = "0.9.0" 225 | source = "registry+https://github.com/rust-lang/crates.io-index" 226 | dependencies = [ 227 | "semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", 228 | ] 229 | 230 | [[package]] 231 | name = "semver-parser" 232 | version = "0.7.0" 233 | source = "registry+https://github.com/rust-lang/crates.io-index" 234 | 235 | [[package]] 236 | name = "serde" 237 | version = "1.0.101" 238 | source = "registry+https://github.com/rust-lang/crates.io-index" 239 | dependencies = [ 240 | "serde_derive 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", 241 | ] 242 | 243 | [[package]] 244 | name = "serde_derive" 245 | version = "1.0.101" 246 | source = "registry+https://github.com/rust-lang/crates.io-index" 247 | dependencies = [ 248 | "proc-macro2 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", 249 | "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 250 | "syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", 251 | ] 252 | 253 | [[package]] 254 | name = "serde_json" 255 | version = "1.0.41" 256 | source = "registry+https://github.com/rust-lang/crates.io-index" 257 | dependencies = [ 258 | "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", 259 | "ryu 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 260 | "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", 261 | ] 262 | 263 | [[package]] 264 | name = "sha1" 265 | version = "0.6.0" 266 | source = "registry+https://github.com/rust-lang/crates.io-index" 267 | 268 | [[package]] 269 | name = "slab" 270 | version = "0.4.2" 271 | source = "registry+https://github.com/rust-lang/crates.io-index" 272 | 273 | [[package]] 274 | name = "smart-default" 275 | version = "0.5.2" 276 | source = "registry+https://github.com/rust-lang/crates.io-index" 277 | dependencies = [ 278 | "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", 279 | "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", 280 | "syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)", 281 | ] 282 | 283 | [[package]] 284 | name = "stdweb" 285 | version = "0.4.19" 286 | source = "registry+https://github.com/rust-lang/crates.io-index" 287 | dependencies = [ 288 | "discard 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", 289 | "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", 290 | "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", 291 | "serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)", 292 | "stdweb-derive 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", 293 | "stdweb-internal-macros 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 294 | "stdweb-internal-runtime 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 295 | "wasm-bindgen 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", 296 | ] 297 | 298 | [[package]] 299 | name = "stdweb-derive" 300 | version = "0.5.2" 301 | source = "registry+https://github.com/rust-lang/crates.io-index" 302 | dependencies = [ 303 | "proc-macro2 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", 304 | "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 305 | "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", 306 | "serde_derive 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", 307 | "syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", 308 | ] 309 | 310 | [[package]] 311 | name = "stdweb-internal-macros" 312 | version = "0.2.8" 313 | source = "registry+https://github.com/rust-lang/crates.io-index" 314 | dependencies = [ 315 | "base-x 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", 316 | "proc-macro2 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", 317 | "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 318 | "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", 319 | "serde_derive 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", 320 | "serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)", 321 | "sha1 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", 322 | "syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", 323 | ] 324 | 325 | [[package]] 326 | name = "stdweb-internal-runtime" 327 | version = "0.1.5" 328 | source = "registry+https://github.com/rust-lang/crates.io-index" 329 | 330 | [[package]] 331 | name = "syn" 332 | version = "0.15.44" 333 | source = "registry+https://github.com/rust-lang/crates.io-index" 334 | dependencies = [ 335 | "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", 336 | "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", 337 | "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 338 | ] 339 | 340 | [[package]] 341 | name = "syn" 342 | version = "1.0.5" 343 | source = "registry+https://github.com/rust-lang/crates.io-index" 344 | dependencies = [ 345 | "proc-macro2 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", 346 | "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 347 | "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 348 | ] 349 | 350 | [[package]] 351 | name = "synstructure" 352 | version = "0.10.2" 353 | source = "registry+https://github.com/rust-lang/crates.io-index" 354 | dependencies = [ 355 | "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", 356 | "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", 357 | "syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)", 358 | "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 359 | ] 360 | 361 | [[package]] 362 | name = "unicode-xid" 363 | version = "0.1.0" 364 | source = "registry+https://github.com/rust-lang/crates.io-index" 365 | 366 | [[package]] 367 | name = "unicode-xid" 368 | version = "0.2.0" 369 | source = "registry+https://github.com/rust-lang/crates.io-index" 370 | 371 | [[package]] 372 | name = "wasm-bindgen" 373 | version = "0.2.42" 374 | source = "registry+https://github.com/rust-lang/crates.io-index" 375 | dependencies = [ 376 | "wasm-bindgen-macro 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", 377 | ] 378 | 379 | [[package]] 380 | name = "wasm-bindgen-backend" 381 | version = "0.2.42" 382 | source = "registry+https://github.com/rust-lang/crates.io-index" 383 | dependencies = [ 384 | "bumpalo 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)", 385 | "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 386 | "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", 387 | "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", 388 | "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", 389 | "syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)", 390 | "wasm-bindgen-shared 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", 391 | ] 392 | 393 | [[package]] 394 | name = "wasm-bindgen-macro" 395 | version = "0.2.42" 396 | source = "registry+https://github.com/rust-lang/crates.io-index" 397 | dependencies = [ 398 | "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", 399 | "wasm-bindgen-macro-support 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", 400 | ] 401 | 402 | [[package]] 403 | name = "wasm-bindgen-macro-support" 404 | version = "0.2.42" 405 | source = "registry+https://github.com/rust-lang/crates.io-index" 406 | dependencies = [ 407 | "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", 408 | "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", 409 | "syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)", 410 | "wasm-bindgen-backend 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", 411 | "wasm-bindgen-shared 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", 412 | ] 413 | 414 | [[package]] 415 | name = "wasm-bindgen-shared" 416 | version = "0.2.42" 417 | source = "registry+https://github.com/rust-lang/crates.io-index" 418 | 419 | [[package]] 420 | name = "web_logger" 421 | version = "0.2.0" 422 | source = "registry+https://github.com/rust-lang/crates.io-index" 423 | dependencies = [ 424 | "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", 425 | "stdweb 0.4.19 (registry+https://github.com/rust-lang/crates.io-index)", 426 | "wasm-bindgen 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", 427 | ] 428 | 429 | [[package]] 430 | name = "yew" 431 | version = "0.9.0" 432 | source = "registry+https://github.com/rust-lang/crates.io-index" 433 | dependencies = [ 434 | "anymap 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)", 435 | "bincode 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 436 | "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 437 | "http 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)", 438 | "indexmap 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 439 | "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", 440 | "proc-macro-hack 0.5.10 (registry+https://github.com/rust-lang/crates.io-index)", 441 | "proc-macro-nested 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", 442 | "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", 443 | "serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)", 444 | "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 445 | "stdweb 0.4.19 (registry+https://github.com/rust-lang/crates.io-index)", 446 | "wasm-bindgen 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", 447 | "yew-macro 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", 448 | ] 449 | 450 | [[package]] 451 | name = "yew-macro" 452 | version = "0.9.0" 453 | source = "registry+https://github.com/rust-lang/crates.io-index" 454 | dependencies = [ 455 | "autocfg 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", 456 | "boolinator 2.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 457 | "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 458 | "proc-macro-hack 0.5.10 (registry+https://github.com/rust-lang/crates.io-index)", 459 | "proc-macro2 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", 460 | "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 461 | "syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", 462 | ] 463 | 464 | [[package]] 465 | name = "yew-router" 466 | version = "0.5.0" 467 | dependencies = [ 468 | "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 469 | "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", 470 | "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", 471 | "smart-default 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", 472 | "stdweb 0.4.19 (registry+https://github.com/rust-lang/crates.io-index)", 473 | "web_logger 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 474 | "yew 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", 475 | ] 476 | 477 | [metadata] 478 | "checksum anymap 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)" = "33954243bd79057c2de7338850b85983a44588021f8a5fee574a8888c6de4344" 479 | "checksum autocfg 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "b671c8fb71b457dd4ae18c4ba1e59aa81793daacc361d82fcd410cef0d491875" 480 | "checksum backtrace 0.3.38 (registry+https://github.com/rust-lang/crates.io-index)" = "690a62be8920ccf773ee00ef0968649b0e724cda8bd5b12286302b4ae955fdf5" 481 | "checksum backtrace-sys 0.1.31 (registry+https://github.com/rust-lang/crates.io-index)" = "82a830b4ef2d1124a711c71d263c5abdc710ef8e907bd508c88be475cebc422b" 482 | "checksum base-x 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "76f4eae81729e69bb1819a26c6caac956cc429238388091f98cb6cd858f16443" 483 | "checksum bincode 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9f2fb9e29e72fd6bc12071533d5dc7664cb01480c59406f656d7ac25c7bd8ff7" 484 | "checksum boolinator 2.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cfa8873f51c92e232f9bac4065cddef41b714152812bfc5f7672ba16d6ef8cd9" 485 | "checksum bumpalo 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ad807f2fc2bf185eeb98ff3a901bd46dc5ad58163d0fa4577ba0d25674d71708" 486 | "checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5" 487 | "checksum bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)" = "206fdffcfa2df7cbe15601ef46c813fce0965eb3286db6b56c583b814b51c81c" 488 | "checksum cc 1.0.45 (registry+https://github.com/rust-lang/crates.io-index)" = "4fc9a35e1f4290eb9e5fc54ba6cf40671ed2a2514c3eeb2b2a908dda2ea5a1be" 489 | "checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 490 | "checksum discard 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0" 491 | "checksum failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "795bd83d3abeb9220f257e597aa0080a508b27533824adf336529648f6abf7e2" 492 | "checksum failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ea1063915fd7ef4309e222a5a07cf9c319fb9c7836b1f89b85458672dbb127e1" 493 | "checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" 494 | "checksum http 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)" = "372bcb56f939e449117fb0869c2e8fd8753a8223d92a172c6e808cf123a5b6e4" 495 | "checksum indexmap 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a61202fbe46c4a951e9404a720a0180bcf3212c750d735cb5c4ba4dc551299f3" 496 | "checksum iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" 497 | "checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f" 498 | "checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 499 | "checksum libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)" = "34fcd2c08d2f832f376f4173a231990fa5aef4e99fb569867318a227ef4c06ba" 500 | "checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" 501 | "checksum proc-macro-hack 0.5.10 (registry+https://github.com/rust-lang/crates.io-index)" = "114cdf1f426eb7f550f01af5f53a33c0946156f6814aec939b3bd77e844f9a9d" 502 | "checksum proc-macro-nested 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "369a6ed065f249a159e06c45752c780bda2fb53c995718f9e484d08daa9eb42e" 503 | "checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" 504 | "checksum proc-macro2 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "afdc77cc74ec70ed262262942ebb7dac3d479e9e5cfa2da1841c0806f6cdabcc" 505 | "checksum quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1" 506 | "checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe" 507 | "checksum rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" 508 | "checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" 509 | "checksum ryu 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c92464b447c0ee8c4fb3824ecc8383b81717b9f1e74ba2e72540aef7b9f82997" 510 | "checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" 511 | "checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" 512 | "checksum serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)" = "9796c9b7ba2ffe7a9ce53c2287dfc48080f4b2b362fcc245a259b3a7201119dd" 513 | "checksum serde_derive 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)" = "4b133a43a1ecd55d4086bd5b4dc6c1751c68b1bfbeba7a5040442022c7e7c02e" 514 | "checksum serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)" = "2f72eb2a68a7dc3f9a691bfda9305a1c017a6215e5a4545c258500d2099a37c2" 515 | "checksum sha1 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d" 516 | "checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" 517 | "checksum smart-default 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5a9dbd5f03d04e80355cbbe3ce5cf1f65c421eac575402e3d4d6e95d5a44edaa" 518 | "checksum stdweb 0.4.19 (registry+https://github.com/rust-lang/crates.io-index)" = "7ef051599eb0f56b288f95bbae663bbee8b52c434d7645c2a6385d7fd1d1ef02" 519 | "checksum stdweb-derive 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dbea6324dfeea460eb1ca4a4030148b5f45c05025b288b295aca5774ef091b1b" 520 | "checksum stdweb-internal-macros 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "62b8ac7e7e96cac43a9b61993c17ac5320ebdae9da208b9141181cbe30e53ee9" 521 | "checksum stdweb-internal-runtime 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0" 522 | "checksum syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)" = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5" 523 | "checksum syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "66850e97125af79138385e9b88339cbcd037e3f28ceab8c5ad98e64f0f1f80bf" 524 | "checksum synstructure 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "02353edf96d6e4dc81aea2d8490a7e9db177bf8acb0e951c24940bf866cb313f" 525 | "checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" 526 | "checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" 527 | "checksum wasm-bindgen 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)" = "ffde3534e5fa6fd936e3260cd62cd644b8656320e369388f9303c955895e35d4" 528 | "checksum wasm-bindgen-backend 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)" = "40c0543374a7ae881cdc5d32d19de28d1d1929e92263ffa7e31712cc2d53f9f1" 529 | "checksum wasm-bindgen-macro 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)" = "f914c94c2c5f4c9364510ca2429e59c92157ec89429243bcc245e983db990a71" 530 | "checksum wasm-bindgen-macro-support 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)" = "9168c413491e4233db7b6884f09a43beb00c14d11d947ffd165242daa48a2385" 531 | "checksum wasm-bindgen-shared 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)" = "326c32126e1a157b6ced7400061a84ac5b11182b2cda6edad7314eb3ae9ac9fe" 532 | "checksum web_logger 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f50afec20c2e728ca33ffb77d1ed049c8559f1d2e59e0fea36f6586d5bc74683" 533 | "checksum yew 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8557d79f121d1656cea897ab706b762a02634a3bb7ccecefe4b75b1733345ac4" 534 | "checksum yew-macro 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2873ece0371425a2357c4f718a3d009972721d720ce342f7225e3baa5268d289" 535 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "yew-router" 3 | version = "0.5.0" 4 | authors = ["Sascha Grunert "] 5 | categories = ["web-programming"] 6 | description = "Routing extension to yew" 7 | documentation = "https://docs.rs/yew-router" 8 | homepage = "https://github.com/saschagrunert/yew-router" 9 | keywords = ["web", "yew", "router", "routing", "webassembly"] 10 | license = "MIT" 11 | readme = "README.md" 12 | repository = "https://github.com/saschagrunert/yew-router" 13 | edition = "2018" 14 | 15 | [badges] 16 | circle-ci = { repository = "saschagrunert/yew-router", branch = "master" } 17 | codecov = { repository = "saschagrunert/yew-router", branch = "master", service = "github" } 18 | maintenance = { status = "actively-developed" } 19 | 20 | [dependencies] 21 | failure = "0.1.5" 22 | serde = { version = "1.0.101", features = ["derive"] } 23 | stdweb = "0.4.19" 24 | yew = "0.9.0" 25 | 26 | [dev-dependencies] 27 | smart-default = "0.5.2" 28 | log = "0.4.8" 29 | web_logger = "0.2.0" 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Sascha Grunert 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Compiler configuration 2 | GENERAL_ARGS = --release 3 | FRONTEND_ARGS = $(GENERAL_ARGS) --target wasm32-unknown-unknown 4 | 5 | .PHONY: \ 6 | build \ 7 | build-doc \ 8 | lint-rustfmt \ 9 | lint-clippy 10 | 11 | ifndef VERBOSE 12 | .SILENT: 13 | else 14 | GENERAL_ARGS += -v 15 | endif 16 | 17 | all: build 18 | 19 | build: 20 | cargo web build $(FRONTEND_ARGS) 21 | 22 | build-doc: 23 | cargo doc --all --no-deps 24 | 25 | lint-clippy: 26 | cargo clippy -- -D warnings 27 | 28 | lint-rustfmt: 29 | cargo fmt 30 | git diff --exit-code 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # yew-router 2 | 3 | [![CircleCI](https://circleci.com/gh/saschagrunert/yew-router.svg?style=shield)](https://circleci.com/gh/saschagrunert/yew-router) 4 | [![Doc](https://img.shields.io/badge/doc-yew%20router-orange.svg)](https://saschagrunert.github.io/yew-router/doc/yew_router/index.html) 5 | [![License MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/saschagrunert/yew-router/blob/master/LICENSE) 6 | 7 | ## Routing extension to [yew](https://github.com/DenisKolodin/yew) 8 | 9 | **This project has been deprecated in favor of https://github.com/yewstack/yew_router** 10 | -------------------------------------------------------------------------------- /examples/history_state.rs: -------------------------------------------------------------------------------- 1 | #![recursion_limit = "256"] 2 | #![feature(slice_patterns)] 3 | 4 | use std::{convert::Into, fmt, str::FromStr}; 5 | 6 | use log::info; 7 | use serde::{Deserialize, Serialize}; 8 | use smart_default::SmartDefault; 9 | use stdweb::{__js_serializable_boilerplate, js_deserializable, js_serializable}; 10 | use web_logger; 11 | use yew::{html, Bridge, Bridged, Component, ComponentLink, Html, Renderable, ShouldRender}; 12 | use yew_router::{Route, RouterAgent}; 13 | 14 | #[derive(SmartDefault, Debug, Clone, PartialEq, Serialize, Deserialize)] 15 | pub enum RouterTarget { 16 | #[default] 17 | Home, 18 | Feed, 19 | Profile { 20 | user_id: i64, 21 | }, 22 | Foo { 23 | name: String, 24 | id: i64, 25 | }, 26 | Post(i64), 27 | Bar(String, i64), 28 | Settings(SettingsRoute), 29 | } 30 | 31 | #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] 32 | pub enum SettingsRoute { 33 | Notifications, 34 | Privacy, 35 | Foobar(i64), 36 | } 37 | 38 | impl fmt::Display for RouterTarget { 39 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 40 | write!( 41 | f, 42 | "{}", 43 | match self { 44 | RouterTarget::Home => "home".into(), 45 | RouterTarget::Feed => "feed".into(), 46 | RouterTarget::Profile { user_id } => format!("profile/{}", user_id), 47 | RouterTarget::Foo { name, id } => format!("foo/{}/{}", name, id), 48 | RouterTarget::Post(i) => format!("post/{}", i), 49 | RouterTarget::Bar(s, i) => format!("bar/{}/{}", s, i), 50 | RouterTarget::Settings(sub_route) => format!("settings/{}", sub_route), 51 | }, 52 | ) 53 | } 54 | } 55 | 56 | impl fmt::Display for SettingsRoute { 57 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 58 | write!( 59 | f, 60 | "{}", 61 | match self { 62 | SettingsRoute::Notifications => "notifications".into(), 63 | SettingsRoute::Privacy => "privacy".into(), 64 | SettingsRoute::Foobar(i) => format!("foobar/{}", i), 65 | }, 66 | ) 67 | } 68 | } 69 | 70 | impl FromStr for RouterTarget { 71 | type Err = (); 72 | 73 | fn from_str(path: &str) -> Result { 74 | Ok(match &path_segments(path)[..] { 75 | ["home"] => RouterTarget::Home, 76 | ["feed"] => RouterTarget::Feed, 77 | ["profile", user_id] => RouterTarget::Profile { 78 | user_id: parse(user_id)?, 79 | }, 80 | ["foo", name, id] => RouterTarget::Foo { 81 | name: name.to_string(), 82 | id: parse(id)?, 83 | }, 84 | ["post", id] => RouterTarget::Post(parse(id)?), 85 | ["bar", name, id] => RouterTarget::Bar(name.to_string(), parse(id)?), 86 | ["settings", sub_route] => RouterTarget::Settings(parse(&sub_route.join("/"))?), 87 | _ => Err(())?, 88 | }) 89 | } 90 | } 91 | 92 | impl FromStr for SettingsRoute { 93 | type Err = (); 94 | 95 | fn from_str(path: &str) -> Result { 96 | Ok(match &path_segments(path)[..] { 97 | ["notifications"] => SettingsRoute::Notifications, 98 | ["privacy"] => SettingsRoute::Privacy, 99 | ["foobar", id] => SettingsRoute::Foobar(parse(id)?), 100 | _ => Err(())?, 101 | }) 102 | } 103 | } 104 | 105 | fn path_segments(path: &str) -> Vec<&str> { 106 | path.split('/').filter(|s| !s.is_empty()).collect() 107 | } 108 | 109 | fn parse(s: &str) -> Result { 110 | s.parse().map_err(|_| ()) 111 | } 112 | 113 | js_serializable!(RouterTarget); 114 | js_deserializable!(RouterTarget); 115 | 116 | type RouteState = RouterTarget; 117 | 118 | const HASH_BASED_URL: bool = true; 119 | 120 | /// Convert a RouterTarget into a Route 121 | impl Into> for RouterTarget { 122 | fn into(self) -> Route { 123 | let route = self.to_string(); 124 | if HASH_BASED_URL { 125 | Route { 126 | fragment: Some(format!("/{}", route)), 127 | state: self, 128 | ..Default::default() 129 | } 130 | } else { 131 | Route { 132 | path_segments: vec![route], 133 | state: self, 134 | ..Default::default() 135 | } 136 | } 137 | } 138 | } 139 | 140 | /// Convert a Route into a RouterTarget 141 | impl Into for Route { 142 | fn into(self) -> RouterTarget { 143 | let path = if HASH_BASED_URL { 144 | self.fragment.unwrap_or_default() 145 | } else { 146 | self.path_segments.join("/") 147 | }; 148 | path.parse().unwrap_or_default() 149 | } 150 | } 151 | 152 | impl Into for RouterTarget { 153 | fn into(self) -> PageActions { 154 | PageActions::RoutePage(self.into()) 155 | } 156 | } 157 | 158 | // Define a root component that stays consistently mounted even with different routes 159 | pub struct RootComponent { 160 | // The RouterTarget truct gets created from the routes! macro 161 | // -> The currently rendered "child_component" is defined as on of RouterTarget::Error or RouterTarget::Login 162 | child_component: RouterTarget, 163 | 164 | // The RouterAgent is a brige to the Router service created by the yew_router module. See Yew's Agent Model for more details 165 | router_agent: Box>>, 166 | } 167 | 168 | // Define some basic actions that can be executed by elements on our page 169 | pub enum PageActions { 170 | // The route option contains a route struct 171 | Route(Route), 172 | 173 | // A RoutePage option for user-generated route changes 174 | RoutePage(Route), 175 | } 176 | 177 | impl Component for RootComponent { 178 | type Message = PageActions; 179 | type Properties = (); 180 | 181 | fn create(_: Self::Properties, mut link: ComponentLink) -> Self { 182 | // Connect to the router agent using Yew's bridge method for workers 183 | // Send back the method we will be using to route the user 184 | let mut router_agent = RouterAgent::bridge(link.send_back(PageActions::Route)); 185 | router_agent.send(yew_router::Request::GetCurrentRoute); 186 | 187 | RootComponent { 188 | child_component: RouterTarget::Feed, 189 | router_agent, 190 | } 191 | } 192 | 193 | fn update(&mut self, interaction: Self::Message) -> ShouldRender { 194 | // Match against the user interactions 195 | // This can be any interaction defined in the PageActions enum 196 | match interaction { 197 | // Handle all page routing -- this is separated because some routing should not be exposed to the user 198 | PageActions::RoutePage(pageroute) => self 199 | .router_agent 200 | .send(yew_router::Request::ChangeRoute(pageroute)), 201 | 202 | // The Routing event bound to the RouterAgent 203 | PageActions::Route(route) => { 204 | info!("{:?}", route); 205 | self.child_component = route.into(); 206 | } 207 | } 208 | true 209 | } 210 | } 211 | 212 | impl Renderable for RootComponent { 213 | fn view(&self) -> Html { 214 | html! { 215 | // Let's place a set of html that sticks with the page even when we update the child componenet 216 |
217 |
218 | 219 |
220 | { 221 | for (0..4).map(|i| html! { 222 | 225 | }) 226 | } 227 |
228 |
229 | { 230 | for (0..4).map(|i| html! { 231 | 234 | }) 235 | } 236 |
237 |
238 | // Render out the child componenet 239 | { self.child_component.view() } 240 |
241 | } 242 | } 243 | } 244 | 245 | impl Renderable for RouterTarget { 246 | fn view(&self) -> Html { 247 | html! { 248 |
{format!("Current page: {}", self)}
249 | } 250 | } 251 | } 252 | 253 | fn main() { 254 | web_logger::init(); 255 | info!("starting up"); 256 | yew::start_app::(); 257 | } 258 | -------------------------------------------------------------------------------- /examples/minimal.rs: -------------------------------------------------------------------------------- 1 | #![recursion_limit = "256"] 2 | 3 | use yew::{html, Bridge, Bridged, Component, ComponentLink, Html, Renderable, ShouldRender}; 4 | use yew_router::{routes, Route, RouterAgent}; 5 | 6 | // Define application routes via the macro 7 | // This creates the `RouterTarget` struct 8 | // Error is a required route according to the router macro 9 | routes! { 10 | Error => "/error", 11 | Login => "/login", 12 | } 13 | 14 | // Define a root component that stays consistently mounted even with different routes 15 | pub struct RootComponent { 16 | // The RouterTarget truct gets created from the routes! macro 17 | // -> The currently rendered "child_component" is defined as on of RouterTarget::Error or RouterTarget::Login 18 | child_component: RouterTarget, 19 | 20 | // The RouterAgent is a brige to the Router service created by the yew_router module. See Yew's Agent Model for more details 21 | router_agent: Box>>, 22 | } 23 | 24 | // Define some basic actions that can be executed by elements on our page 25 | pub enum PageActions { 26 | // The route option contains a route struct 27 | Route(Route<()>), 28 | 29 | // A RoutePage option for user-generated route changes 30 | RoutePage(Route<()>), 31 | } 32 | 33 | impl Component for RootComponent { 34 | type Message = PageActions; 35 | type Properties = (); 36 | 37 | fn create(_: Self::Properties, mut link: ComponentLink) -> Self { 38 | // Connect to the router agent using Yew's bridge method for workers 39 | // Send back the method we will be using to route the user 40 | let router_agent = RouterAgent::bridge(link.send_back(PageActions::Route)); 41 | 42 | RootComponent { 43 | child_component: RouterTarget::Login, 44 | router_agent, 45 | } 46 | } 47 | 48 | fn update(&mut self, interaction: Self::Message) -> ShouldRender { 49 | // Match against the user interactions 50 | // This can be any interaction defined in the PageActions enum 51 | match interaction { 52 | // Handle all page routing -- this is separated because some routing should not be exposed to the user 53 | PageActions::RoutePage(pageroute) => self 54 | .router_agent 55 | .send(yew_router::Request::ChangeRoute(pageroute)), 56 | 57 | // The Routing event bound to the RouterAgent 58 | PageActions::Route(route) => self.child_component = route.into(), 59 | } 60 | true 61 | } 62 | } 63 | 64 | impl Renderable for RootComponent { 65 | fn view(&self) -> Html { 66 | html! { 67 | // Let's place a set of html that sticks with the page even when we update the child componenet 68 |
69 |
70 | 71 | 72 |
73 | // Render out the child componenet 74 | { self.child_component.view() } 75 |
76 | } 77 | } 78 | } 79 | 80 | impl Renderable for RouterTarget { 81 | fn view(&self) -> Html { 82 | match *self { 83 | // Html to render based on the child componenet enum view 84 | RouterTarget::Login => { 85 | html! { 86 |
{"Login page activated"}
87 | } 88 | } 89 | RouterTarget::Error => { 90 | html! { 91 |
{"Error page activated"}
92 | } 93 | } 94 | } 95 | } 96 | } 97 | 98 | fn main() { 99 | yew::start_app::(); 100 | } 101 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Yew routing extension 2 | 3 | #![deny(missing_docs)] 4 | 5 | mod router; 6 | pub use crate::router::{Request, Route, RouterAgent}; 7 | 8 | #[macro_export] 9 | /// Convinience macro for route creation 10 | macro_rules! routes { 11 | ($($x:tt => $y:expr,)*) => ( 12 | #[derive(Debug, PartialEq, Clone, Copy)] 13 | /// Possible child components 14 | pub enum RouterTarget { 15 | $($x,)* 16 | } 17 | 18 | /// Convert a RouterTarget into a Route 19 | impl ::std::convert::Into<::yew_router::Route> for RouterTarget where T: Default { 20 | fn into(self) -> ::yew_router::Route { 21 | ::yew_router::Route { 22 | fragment: Some( 23 | match self { 24 | $(RouterTarget::$x => $y,)* 25 | }.into(), 26 | ), 27 | ..Default::default() 28 | } 29 | } 30 | } 31 | 32 | /// Convert a Route into a RouterTarget 33 | impl ::std::convert::Into for ::yew_router::Route<()> { 34 | fn into(self) -> RouterTarget { 35 | match self.fragment { 36 | Some(f) => match f.as_str() { 37 | $($y => RouterTarget::$x,)* 38 | _ => RouterTarget::Error, 39 | }, 40 | _ => RouterTarget::Error, 41 | } 42 | } 43 | } 44 | ) 45 | } 46 | -------------------------------------------------------------------------------- /src/router.rs: -------------------------------------------------------------------------------- 1 | use failure::Fallible; 2 | use serde::{Deserialize, Serialize}; 3 | use std::{collections::HashSet, fmt::Debug, marker::PhantomData}; 4 | use stdweb::{ 5 | js, 6 | unstable::TryFrom, 7 | web::{event::PopStateEvent, window, EventListenerHandle, History, IEventTarget, Location}, 8 | JsSerialize, Value, 9 | }; 10 | use yew::{callback::Callback, prelude::worker::*}; 11 | 12 | /// A service that facilitates manipulation of the browser's URL bar and 13 | /// responding to browser 'forward' and 'back' events. 14 | /// 15 | /// The `T` determines what route state can be stored in the route service. 16 | pub struct RouterService { 17 | history: History, 18 | location: Location, 19 | event_listener: Option, 20 | phantom_data: PhantomData, 21 | } 22 | 23 | impl RouterService 24 | where 25 | T: JsSerialize + Clone + TryFrom + 'static, 26 | { 27 | /// Creates the route service. 28 | pub fn new() -> RouterService { 29 | let location = window() 30 | .location() 31 | .expect("browser does not support location API"); 32 | Self { 33 | history: window().history(), 34 | location, 35 | event_listener: None, 36 | phantom_data: PhantomData, 37 | } 38 | } 39 | 40 | /// Registers a callback to the route service. Callbacks will be called 41 | /// when the History API experiences a change such as popping a state off 42 | /// of its stack when the forward or back buttons are pressed. 43 | pub fn register_callback(&mut self, callback: Callback<()>) { 44 | self.event_listener = 45 | Some(window().add_event_listener(move |_event: PopStateEvent| callback.emit(()))); 46 | } 47 | 48 | /// Sets the browser's url bar to contain the provided route, and creates a 49 | /// history entry that can be navigated via the forward and back buttons. 50 | /// The route should be a relative path that starts with a '/'. A state 51 | /// object be stored with the url. 52 | pub fn set_route(&mut self, route: &str, state: T) { 53 | self.history.push_state(state, "", Some(route)); 54 | } 55 | 56 | fn get_route_from_location(location: &Location) -> String { 57 | let path = location.pathname().unwrap_or_else(|_| "".to_owned()); 58 | let query = location.search().unwrap_or_else(|_| "".to_owned()); 59 | let fragment = location.hash().unwrap_or_else(|_| "".to_owned()); 60 | format!( 61 | "{path}{query}{fragment}", 62 | path = path, 63 | query = query, 64 | fragment = fragment 65 | ) 66 | } 67 | 68 | /// Gets the concatenated path, query, and fragment strings 69 | pub fn get_route(&self) -> String { 70 | Self::get_route_from_location(&self.location) 71 | } 72 | 73 | /// Gets the path name of the current url. 74 | pub fn get_path(&self) -> Fallible { 75 | Ok(self.location.pathname()?) 76 | } 77 | 78 | /// Gets the query string of the current url. 79 | pub fn get_query(&self) -> Fallible { 80 | Ok(self.location.search()?) 81 | } 82 | 83 | /// Gets the fragment of the current url. 84 | pub fn get_fragment(&self) -> Fallible { 85 | Ok(self.location.hash()?) 86 | } 87 | 88 | /// Gets the history's current state. 89 | pub fn get_state(&self) -> T 90 | where 91 | T: Default, 92 | { 93 | T::try_from(js! { return history.state; }).unwrap_or_default() 94 | } 95 | } 96 | 97 | impl Default for RouterService 98 | where 99 | T: JsSerialize + Clone + TryFrom + 'static, 100 | { 101 | fn default() -> Self { 102 | RouterService::new() 103 | } 104 | } 105 | 106 | #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] 107 | /// A single route abstraction 108 | pub struct Route { 109 | /// The URL path segments 110 | pub path_segments: Vec, 111 | 112 | /// The URL query part 113 | pub query: Option, 114 | 115 | /// The URL fragment part 116 | pub fragment: Option, 117 | 118 | /// The routes state 119 | pub state: T, 120 | } 121 | 122 | impl Route 123 | where 124 | T: JsSerialize + Clone + TryFrom + Default + 'static, 125 | { 126 | /// Convert to a String 127 | pub fn to_route_string(&self) -> String { 128 | let path = self.path_segments.join("/"); 129 | let mut path = format!("/{}", path); 130 | if let Some(ref query) = self.query { 131 | path = format!("{}?{}", path, query); 132 | } 133 | if let Some(ref fragment) = self.fragment { 134 | path = format!("{}#{}", path, fragment) 135 | } 136 | path 137 | } 138 | 139 | /// Retrieve the current route 140 | pub fn current_route(route_service: &RouterService) -> Fallible { 141 | // guaranteed to always start with a '/' 142 | let path = route_service.get_path()?; 143 | let mut path_segments: Vec = path.split('/').map(String::from).collect(); 144 | // remove empty string that is split from the first '/' 145 | path_segments.remove(0); 146 | 147 | let mut query: String = route_service.get_query()?; // The first character will be a '?' 148 | let query: Option = if query.len() > 1 { 149 | query.remove(0); 150 | Some(query) 151 | } else { 152 | None 153 | }; 154 | 155 | let mut fragment: String = route_service.get_fragment()?; // The first character will be a '#' 156 | let fragment: Option = if fragment.len() > 1 { 157 | fragment.remove(0); 158 | Some(fragment) 159 | } else { 160 | None 161 | }; 162 | 163 | Ok(Route { 164 | path_segments, 165 | query, 166 | fragment, 167 | state: route_service.get_state(), 168 | }) 169 | } 170 | } 171 | 172 | /// Messages of the RouterAgent 173 | pub enum Message { 174 | /// The browser URL has changed 175 | BrowserNavigationRouteChanged(()), 176 | } 177 | 178 | impl Transferable for Route where for<'de> T: Serialize + Deserialize<'de> {} 179 | 180 | #[derive(Serialize, Deserialize, Debug)] 181 | /// The request to the RouterAgent 182 | pub enum Request { 183 | /// Changes the route using a RouteInfo struct and alerts connected 184 | /// components to the route change. 185 | ChangeRoute(Route), 186 | 187 | /// Retrieve the current route request 188 | GetCurrentRoute, 189 | } 190 | 191 | impl Transferable for Request where for<'de> T: Serialize + Deserialize<'de> {} 192 | 193 | /// The RouterAgent worker holds on to the RouterService singleton and mediates 194 | /// access to it. 195 | pub struct RouterAgent 196 | where 197 | for<'de> T: JsSerialize 198 | + Clone 199 | + Debug 200 | + TryFrom 201 | + Default 202 | + Serialize 203 | + Deserialize<'de> 204 | + 'static, 205 | { 206 | link: AgentLink>, 207 | route_service: RouterService, 208 | /// A list of all entities connected to the RouterAgent. When a route 209 | /// changes, either initiated by the browser or by the app, the route 210 | /// change will be broadcast to all listening entities. 211 | subscribers: HashSet, 212 | } 213 | 214 | impl Agent for RouterAgent 215 | where 216 | for<'de> T: JsSerialize 217 | + Clone 218 | + Debug 219 | + TryFrom 220 | + Default 221 | + Serialize 222 | + Deserialize<'de> 223 | + 'static, 224 | { 225 | type Input = Request; 226 | type Message = Message; 227 | type Output = Route; 228 | type Reach = Context; 229 | 230 | fn create(link: AgentLink) -> Self { 231 | let callback = link.send_back(Message::BrowserNavigationRouteChanged); 232 | let mut route_service = RouterService::default(); 233 | route_service.register_callback(callback); 234 | 235 | Self { 236 | link, 237 | route_service, 238 | subscribers: HashSet::new(), 239 | } 240 | } 241 | 242 | fn update(&mut self, msg: Self::Message) { 243 | match msg { 244 | Message::BrowserNavigationRouteChanged(()) => { 245 | if let Ok(route) = Route::current_route(&self.route_service) { 246 | for sub in &self.subscribers { 247 | self.link.response(*sub, route.clone()); 248 | } 249 | } 250 | } 251 | } 252 | } 253 | 254 | fn handle(&mut self, msg: Self::Input, who: HandlerId) { 255 | match msg { 256 | Request::ChangeRoute(route) => { 257 | let route_string: String = route.to_route_string(); 258 | // set the route 259 | self.route_service.set_route(&route_string, route.state); 260 | // get the new route. This will contain a default state 261 | if let Ok(route) = Route::current_route(&self.route_service) { 262 | // broadcast it to all listening components 263 | for sub in &self.subscribers { 264 | self.link.response(*sub, route.clone()); 265 | } 266 | } 267 | } 268 | Request::GetCurrentRoute => { 269 | if let Ok(route) = Route::current_route(&self.route_service) { 270 | self.link.response(who, route.clone()); 271 | } 272 | } 273 | } 274 | } 275 | 276 | /// Add a client to the pool of connections of this agent 277 | fn connected(&mut self, id: HandlerId) { 278 | self.subscribers.insert(id); 279 | } 280 | 281 | /// Remove a client from the pool of connections of this agent 282 | fn disconnected(&mut self, id: HandlerId) { 283 | self.subscribers.remove(&id); 284 | } 285 | } 286 | --------------------------------------------------------------------------------