mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-21 19:53:04 +00:00
20d146d9bd
* improve documentation for the fullstack server context * Add a section about axum integration to the crate root docs * make serve_dioxus_application accept the cfg builder directly * remove unused server_fn module * improve fullstack config docs * improve documentation for the server function macro * fix axum router extension link * Fix doc tests * Fix launch builder * Simplify the launch builder * don't re-export launch in the prelude * refactor fullstack launch * Fix fullstack launch builder * Update static generation with the new builder api * fix some formatting/overly broad launch replacements * fix custom menu example * fix fullstack/static generation examples * Fix static generation launch * A few small formatting fixes * Fix a few doc tests * implement LaunchConfig for serve configs * fix fullstack launch with separate web and server launch methods * fix check with all features * dont expose inner core module * clippy and check * fix readme --------- Co-authored-by: Jonathan Kelley <jkelleyrtp@gmail.com>
130 lines
3.6 KiB
Rust
130 lines
3.6 KiB
Rust
//! This example shows how to use the hash segment to store state in the url.
|
|
//!
|
|
//! You can set up two way data binding between the url hash and signals.
|
|
//!
|
|
//! Run this example on desktop with
|
|
//! ```sh
|
|
//! dx serve --example hash_fragment_state --features=ciborium,base64
|
|
//! ```
|
|
//! Or on web with
|
|
//! ```sh
|
|
//! dx serve --platform web --features web --example hash_fragment_state --features=ciborium,base64 -- --no-default-features
|
|
//! ```
|
|
|
|
use std::{fmt::Display, str::FromStr};
|
|
|
|
use base64::engine::general_purpose::STANDARD;
|
|
use base64::Engine;
|
|
use dioxus::prelude::*;
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
fn main() {
|
|
dioxus::launch(|| {
|
|
rsx! {
|
|
Router::<Route> {}
|
|
}
|
|
});
|
|
}
|
|
|
|
#[derive(Routable, Clone, Debug, PartialEq)]
|
|
#[rustfmt::skip]
|
|
enum Route {
|
|
#[route("/#:url_hash")]
|
|
Home {
|
|
url_hash: State,
|
|
},
|
|
}
|
|
|
|
// You can use a custom type with the hash segment as long as it implements Display, FromStr and Default
|
|
#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq)]
|
|
struct State {
|
|
counters: Vec<usize>,
|
|
}
|
|
|
|
// Display the state in a way that can be parsed by FromStr
|
|
impl Display for State {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
let mut serialized = Vec::new();
|
|
if ciborium::into_writer(self, &mut serialized).is_ok() {
|
|
write!(f, "{}", STANDARD.encode(serialized))?;
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
enum StateParseError {
|
|
DecodeError(base64::DecodeError),
|
|
CiboriumError(ciborium::de::Error<std::io::Error>),
|
|
}
|
|
|
|
impl std::fmt::Display for StateParseError {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
match self {
|
|
Self::DecodeError(err) => write!(f, "Failed to decode base64: {}", err),
|
|
Self::CiboriumError(err) => write!(f, "Failed to deserialize: {}", err),
|
|
}
|
|
}
|
|
}
|
|
|
|
// Parse the state from a string that was created by Display
|
|
impl FromStr for State {
|
|
type Err = StateParseError;
|
|
|
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
let decompressed = STANDARD
|
|
.decode(s.as_bytes())
|
|
.map_err(StateParseError::DecodeError)?;
|
|
let parsed = ciborium::from_reader(std::io::Cursor::new(decompressed))
|
|
.map_err(StateParseError::CiboriumError)?;
|
|
Ok(parsed)
|
|
}
|
|
}
|
|
|
|
#[component]
|
|
fn Home(url_hash: ReadOnlySignal<State>) -> Element {
|
|
// The initial state of the state comes from the url hash
|
|
let mut state = use_signal(&*url_hash);
|
|
|
|
// Change the state signal when the url hash changes
|
|
use_memo(move || {
|
|
if *state.peek() != *url_hash.read() {
|
|
state.set(url_hash());
|
|
}
|
|
});
|
|
|
|
// Change the url hash when the state changes
|
|
use_memo(move || {
|
|
if *state.read() != *url_hash.peek() {
|
|
navigator().replace(Route::Home { url_hash: state() });
|
|
}
|
|
});
|
|
|
|
rsx! {
|
|
button {
|
|
onclick: move |_| state.write().counters.clear(),
|
|
"Reset"
|
|
}
|
|
button {
|
|
onclick: move |_| {
|
|
state.write().counters.push(0);
|
|
},
|
|
"Add Counter"
|
|
}
|
|
for counter in 0..state.read().counters.len() {
|
|
div {
|
|
button {
|
|
onclick: move |_| {
|
|
state.write().counters.remove(counter);
|
|
},
|
|
"Remove"
|
|
}
|
|
button {
|
|
onclick: move |_| {
|
|
state.write().counters[counter] += 1;
|
|
},
|
|
"Count: {state.read().counters[counter]}"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|