mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-10 06:34:20 +00:00
Bring back mroe hooks, remove old hooks, cleanup a number of examples
This commit is contained in:
parent
b291a5c0b0
commit
74aa55f85f
33 changed files with 179 additions and 643 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -2631,6 +2631,7 @@ dependencies = [
|
|||
"dioxus",
|
||||
"dioxus-core",
|
||||
"dioxus-debug-cell",
|
||||
"dioxus-signals",
|
||||
"futures-channel",
|
||||
"futures-util",
|
||||
"slab",
|
||||
|
|
|
@ -22,7 +22,7 @@ fn app() -> Element {
|
|||
dioxus_desktop::window().new_window(
|
||||
VirtualDom::new_with_props(compose, tx.clone()),
|
||||
Default::default(),
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
rsx! {
|
||||
|
@ -39,7 +39,7 @@ fn app() -> Element {
|
|||
}
|
||||
}
|
||||
|
||||
fn compose(receiver: UnboundedSender<String>) -> Element {
|
||||
fn compose(tx: UnboundedSender<String>) -> Element {
|
||||
let user_input = use_signal(String::new);
|
||||
|
||||
rsx! {
|
||||
|
@ -48,7 +48,7 @@ fn compose(receiver: UnboundedSender<String>) -> Element {
|
|||
|
||||
button {
|
||||
onclick: move |_| {
|
||||
cx.props.app_tx.send(user_input.get().clone());
|
||||
tx.send(user_input.get().clone());
|
||||
dioxus_desktop::window().close();
|
||||
},
|
||||
"Click to send"
|
||||
|
|
|
@ -9,7 +9,7 @@ fn main() {
|
|||
|
||||
fn app() -> Element {
|
||||
let counters = use_signal(|| vec![0, 0, 0]);
|
||||
let sum = use_selector(move || counters.read().iter().copied().sum() as usize);
|
||||
let sum = use_selector(move || counters.read().iter().copied().sum::<usize>());
|
||||
|
||||
render! {
|
||||
div {
|
||||
|
|
|
@ -24,12 +24,8 @@ pub struct Client {
|
|||
pub description: String,
|
||||
}
|
||||
|
||||
type ClientContext = Vec<Client>;
|
||||
|
||||
#[component]
|
||||
fn App() -> Element {
|
||||
use_shared_state_provider::<ClientContext>(Default::default);
|
||||
|
||||
render! {
|
||||
link {
|
||||
rel: "stylesheet",
|
||||
|
@ -54,8 +50,6 @@ fn App() -> Element {
|
|||
|
||||
#[component]
|
||||
fn ClientList() -> Element {
|
||||
let clients = use_shared_state::<ClientContext>().unwrap();
|
||||
|
||||
rsx! {
|
||||
h2 { "List of Clients" }
|
||||
Link { to: Route::ClientAdd {}, class: "pure-button pure-button-primary", "Add Client" }
|
||||
|
@ -71,7 +65,6 @@ fn ClientList() -> Element {
|
|||
|
||||
#[component]
|
||||
fn ClientAdd() -> Element {
|
||||
let clients = use_shared_state::<ClientContext>().unwrap();
|
||||
let first_name = use_signal(String::new);
|
||||
let last_name = use_signal(String::new);
|
||||
let description = use_signal(String::new);
|
||||
|
|
|
@ -5,15 +5,10 @@ fn main() {
|
|||
dioxus_desktop::launch(app);
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, serde::Deserialize)]
|
||||
struct ListBreeds {
|
||||
message: HashMap<String, Vec<String>>,
|
||||
}
|
||||
|
||||
fn app() -> Element {
|
||||
let breed = use_signal(|| "deerhound".to_string());
|
||||
|
||||
let breeds = use_future(|| async move {
|
||||
let breed_list = use_future(|| async move {
|
||||
reqwest::get("https://dog.ceo/api/breeds/list/all")
|
||||
.await
|
||||
.unwrap()
|
||||
|
@ -21,13 +16,13 @@ fn app() -> Element {
|
|||
.await
|
||||
});
|
||||
|
||||
match breeds.value()? {
|
||||
Ok(breed_list) => rsx! {
|
||||
match breed_list.value().read().as_ref() {
|
||||
Some(Ok(breeds)) => rsx! {
|
||||
div { height: "500px",
|
||||
h1 { "Select a dog breed!" }
|
||||
div { display: "flex",
|
||||
ul { flex: "50%",
|
||||
for cur_breed in breed_list.message.keys().take(10) {
|
||||
for cur_breed in breeds.message.keys().take(10).cloned() {
|
||||
li { key: "{cur_breed}",
|
||||
button { onclick: move |_| breed.set(cur_breed.clone()), "{cur_breed}" }
|
||||
}
|
||||
|
@ -37,18 +32,13 @@ fn app() -> Element {
|
|||
}
|
||||
}
|
||||
},
|
||||
Err(_e) => rsx! { div { "Error fetching breeds" } },
|
||||
_ => rsx! { div { "loading breeds" } },
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize, Debug)]
|
||||
struct DogApi {
|
||||
message: String,
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn BreedPic(breed: Signal<String>) -> Element {
|
||||
let fut = use_future(|breed| async move {
|
||||
let fut = use_future(|| async move {
|
||||
reqwest::get(format!("https://dog.ceo/api/breed/{breed}/images/random"))
|
||||
.await
|
||||
.unwrap()
|
||||
|
@ -56,23 +46,23 @@ fn BreedPic(breed: Signal<String>) -> Element {
|
|||
.await
|
||||
});
|
||||
|
||||
match fut.value()? {
|
||||
Ok(resp) => render! {
|
||||
match fut.value().read().as_ref() {
|
||||
Some(Ok(resp)) => rsx! {
|
||||
div {
|
||||
button {
|
||||
onclick: move |_| {
|
||||
println!("clicked");
|
||||
fut.restart()
|
||||
},
|
||||
"Click to fetch another doggo"
|
||||
}
|
||||
img {
|
||||
src: "{resp.message}",
|
||||
max_width: "500px",
|
||||
max_height: "500px",
|
||||
}
|
||||
button { onclick: move |_| fut.restart(), "Click to fetch another doggo" }
|
||||
img { max_width: "500px", max_height: "500px", src: "{resp.message}" }
|
||||
}
|
||||
},
|
||||
Err(_) => render! { div { "loading dogs failed" } },
|
||||
_ => rsx! { div { "loading dog picture" } },
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, serde::Deserialize)]
|
||||
struct ListBreeds {
|
||||
message: HashMap<String, Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize, Debug)]
|
||||
struct DogApi {
|
||||
message: String,
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use dioxus::{core::CapturedError, prelude::*};
|
||||
|
||||
fn main() {
|
||||
dioxus_desktop::launch(App);
|
||||
dioxus_desktop::launch(app);
|
||||
}
|
||||
|
||||
fn app() -> Element {
|
||||
|
|
|
@ -5,8 +5,8 @@ fn main() {
|
|||
}
|
||||
|
||||
fn app() -> Element {
|
||||
let future = use_future(|_| async move {
|
||||
let eval = eval(
|
||||
let future = use_future(|| async move {
|
||||
let mut eval = eval(
|
||||
r#"
|
||||
dioxus.send("Hi from JS!");
|
||||
let msg = await dioxus.recv();
|
||||
|
@ -22,12 +22,8 @@ fn app() -> Element {
|
|||
res
|
||||
});
|
||||
|
||||
match future.value() {
|
||||
Some(v) => rsx!(
|
||||
p { "{v}" }
|
||||
),
|
||||
_ => rsx!(
|
||||
p { "hello" }
|
||||
),
|
||||
match future.value().read().as_ref() {
|
||||
Some(v) => rsx!( p { "{v}" } ),
|
||||
_ => rsx!( p { "waiting.." } ),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ fn main() {
|
|||
.launch(app)
|
||||
}
|
||||
|
||||
fn app(_props: ()) -> Element {
|
||||
fn app() -> Element {
|
||||
rsx!(
|
||||
div {
|
||||
h1 { "drag a file here and check your console" }
|
||||
|
|
|
@ -1,152 +0,0 @@
|
|||
#![allow(non_snake_case)]
|
||||
|
||||
use dioxus::prelude::*;
|
||||
use rand::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus_desktop::launch(app);
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
struct Label {
|
||||
key: usize,
|
||||
labels: [&'static str; 3],
|
||||
}
|
||||
|
||||
impl Label {
|
||||
fn new_list(num: usize) -> Vec<Self> {
|
||||
let mut rng = SmallRng::from_entropy();
|
||||
let mut labels = Vec::with_capacity(num);
|
||||
for x in 0..num {
|
||||
labels.push(Label {
|
||||
key: x,
|
||||
labels: [
|
||||
ADJECTIVES.choose(&mut rng).unwrap(),
|
||||
COLOURS.choose(&mut rng).unwrap(),
|
||||
NOUNS.choose(&mut rng).unwrap(),
|
||||
],
|
||||
});
|
||||
}
|
||||
labels
|
||||
}
|
||||
}
|
||||
|
||||
fn app() -> Element {
|
||||
let items = use_signal(Vec::new);
|
||||
let selected = use_signal(|| None);
|
||||
|
||||
rsx! {
|
||||
div { class: "container",
|
||||
div { class: "jumbotron",
|
||||
div { class: "row",
|
||||
div { class: "col-md-6", h1 { "Dioxus" } }
|
||||
div { class: "col-md-6",
|
||||
div { class: "row",
|
||||
ActionButton { name: "Create 1,000 rows", id: "run",
|
||||
onclick: move |_| items.set(Label::new_list(1_000)),
|
||||
}
|
||||
ActionButton { name: "Create 10,000 rows", id: "runlots",
|
||||
onclick: move |_| items.set(Label::new_list(10_000)),
|
||||
}
|
||||
ActionButton { name: "Append 1,000 rows", id: "add",
|
||||
onclick: move |_| items.write().extend(Label::new_list(1_000)),
|
||||
}
|
||||
ActionButton { name: "Update every 10th row", id: "update",
|
||||
onclick: move |_| items.write().iter_mut().step_by(10).for_each(|item| item.labels[2] = "!!!"),
|
||||
}
|
||||
ActionButton { name: "Clear", id: "clear",
|
||||
onclick: move |_| items.write().clear(),
|
||||
}
|
||||
ActionButton { name: "Swap rows", id: "swaprows",
|
||||
onclick: move |_| items.write().swap(0, 998),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
table {
|
||||
tbody {
|
||||
for (id, item) in items.read().iter().enumerate() {
|
||||
tr {
|
||||
class: if (*selected).map(|s| s == id).unwrap_or(false) { "danger" },
|
||||
td { class:"col-md-1" }
|
||||
td { class:"col-md-1", "{item.key}" }
|
||||
td { class:"col-md-1", onclick: move |_| selected.set(Some(id)),
|
||||
a { class: "lbl", "{item.labels[0]}{item.labels[1]}{item.labels[2]}" }
|
||||
}
|
||||
td { class: "col-md-1",
|
||||
a { class: "remove", onclick: move |_| { items.write().remove(id); },
|
||||
span { class: "glyphicon glyphicon-remove remove", aria_hidden: "true" }
|
||||
}
|
||||
}
|
||||
td { class: "col-md-6" }
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
span { class: "preloadicon glyphicon glyphicon-remove", aria_hidden: "true" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Props)]
|
||||
struct ActionButtonProps<'a> {
|
||||
name: &'a str,
|
||||
id: &'a str,
|
||||
onclick: EventHandler<'a>,
|
||||
}
|
||||
|
||||
fn ActionButton(props: ActionButtonProps) -> Element {
|
||||
rsx! {
|
||||
div {
|
||||
class: "col-sm-6 smallpad",
|
||||
button {
|
||||
class:"btn btn-primary btn-block",
|
||||
r#type: "button",
|
||||
id: "{cx.props.id}",
|
||||
onclick: move |_| cx.props.onclick.call(()),
|
||||
|
||||
"{cx.props.name}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static ADJECTIVES: &[&str] = &[
|
||||
"pretty",
|
||||
"large",
|
||||
"big",
|
||||
"small",
|
||||
"tall",
|
||||
"short",
|
||||
"long",
|
||||
"handsome",
|
||||
"plain",
|
||||
"quaint",
|
||||
"clean",
|
||||
"elegant",
|
||||
"easy",
|
||||
"angry",
|
||||
"crazy",
|
||||
"helpful",
|
||||
"mushy",
|
||||
"odd",
|
||||
"unsightly",
|
||||
"adorable",
|
||||
"important",
|
||||
"inexpensive",
|
||||
"cheap",
|
||||
"expensive",
|
||||
"fancy",
|
||||
];
|
||||
|
||||
static COLOURS: &[&str] = &[
|
||||
"red", "yellow", "blue", "green", "pink", "brown", "purple", "brown", "white", "black",
|
||||
"orange",
|
||||
];
|
||||
|
||||
static NOUNS: &[&str] = &[
|
||||
"table", "chair", "house", "bbq", "desk", "car", "pony", "cookie", "sandwich", "burger",
|
||||
"pizza", "mouse", "keyboard",
|
||||
];
|
|
@ -129,7 +129,7 @@ pub fn RefreshToken(cx: Scope<ClientProps>) -> Element {
|
|||
|
||||
#[component]
|
||||
pub fn LoadClient() -> Element {
|
||||
let init_client_future = use_future(|_| async move { init_oidc_client().await });
|
||||
let init_client_future = use_future(|| async move { init_oidc_client().await });
|
||||
let fermi_client: &UseAtomRef<ClientState> = use_atom_ref(&FERMI_CLIENT);
|
||||
cx.render(match init_client_future.value() {
|
||||
Some(client_props) => match client_props {
|
||||
|
|
|
@ -35,7 +35,7 @@ fn app() -> Element {
|
|||
|
||||
type SthElse<T> = Option<T>;
|
||||
|
||||
#[derive(Props, PartialEq)]
|
||||
#[derive(Props, PartialEq, Clone)]
|
||||
struct ButtonProps {
|
||||
a: String,
|
||||
|
||||
|
@ -51,14 +51,14 @@ struct ButtonProps {
|
|||
e: SthElse<String>,
|
||||
}
|
||||
|
||||
fn Button(cx: Scope<ButtonProps>) -> Element {
|
||||
fn Button(props: ButtonProps) -> Element {
|
||||
rsx! {
|
||||
button {
|
||||
"{cx.props.a} | "
|
||||
"{cx.props.b:?} | "
|
||||
"{cx.props.c:?} | "
|
||||
"{cx.props.d:?} | "
|
||||
"{cx.props.e:?}"
|
||||
"{props.a} | "
|
||||
"{props.b:?} | "
|
||||
"{props.c:?} | "
|
||||
"{props.d:?} | "
|
||||
"{props.e:?}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -120,20 +120,17 @@ fn app() -> Element {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Props)]
|
||||
struct CalculatorKeyProps {
|
||||
#[props(into)]
|
||||
name: String,
|
||||
#[component]
|
||||
fn CalculatorKey(
|
||||
#[props(into)] name: String,
|
||||
onclick: EventHandler<MouseEvent>,
|
||||
children: Element,
|
||||
}
|
||||
|
||||
fn CalculatorKey(props: CalculatorKeyProps) -> Element {
|
||||
) -> Element {
|
||||
rsx! {
|
||||
button {
|
||||
class: "calculator-key {cx.props.name}",
|
||||
onclick: move |e| cx.props.onclick.call(e),
|
||||
{&cx.props.children}
|
||||
class: "calculator-key {name}",
|
||||
onclick: move |e| onclick.call(e),
|
||||
{&children}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,13 +20,9 @@ fn app() -> Element {
|
|||
rsx!(
|
||||
div {
|
||||
h1 {"Select an option"}
|
||||
h3 { "The radio is... ", {state.is_playing()}, "!" }
|
||||
button { onclick: move |_| state.make_mut().reduce(PlayerAction::Pause),
|
||||
"Pause"
|
||||
}
|
||||
button { onclick: move |_| state.make_mut().reduce(PlayerAction::Play),
|
||||
"Play"
|
||||
}
|
||||
h3 { "The radio is... ", {state.read().is_playing()}, "!" }
|
||||
button { onclick: move |_| state.write().reduce(PlayerAction::Pause), "Pause" }
|
||||
button { onclick: move |_| state.write().reduce(PlayerAction::Play), "Play" }
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
@ -45,7 +45,7 @@ fn app() -> Element {
|
|||
height: "50%",
|
||||
background_color: "red",
|
||||
onmounted: move |cx| div_element.set(Some(cx.inner().clone())),
|
||||
"This element is {dimensions.read():?}"
|
||||
"This element is {*dimensions():?}"
|
||||
}
|
||||
|
||||
button {
|
||||
|
|
|
@ -1,73 +0,0 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus_desktop::launch(App);
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct CoolData {
|
||||
data: HashMap<usize, String>,
|
||||
}
|
||||
|
||||
impl CoolData {
|
||||
pub fn new(data: HashMap<usize, String>) -> Self {
|
||||
Self { data }
|
||||
}
|
||||
|
||||
pub fn view(&self, id: &usize) -> Option<&String> {
|
||||
self.data.get(id)
|
||||
}
|
||||
|
||||
pub fn set(&mut self, id: usize, data: String) {
|
||||
self.data.insert(id, data);
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
#[rustfmt::skip]
|
||||
pub fn App() -> Element {
|
||||
use_shared_state_provider(|| CoolData::new(HashMap::from([
|
||||
(0, "Hello, World!".to_string()),
|
||||
(1, "Dioxus is amazing!".to_string())
|
||||
])));
|
||||
|
||||
render!(
|
||||
DataEditor {
|
||||
id: 0
|
||||
}
|
||||
DataEditor {
|
||||
id: 1
|
||||
}
|
||||
DataView {
|
||||
id: 0
|
||||
}
|
||||
DataView {
|
||||
id: 1
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn DataEditor(id: usize) -> Element {
|
||||
let data = use_shared_state::<CoolData>()?;
|
||||
|
||||
render! {
|
||||
p {
|
||||
{data.read().view(id)?}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn DataView(id: usize) -> Element {
|
||||
let data = use_shared_state::<CoolData>()?;
|
||||
|
||||
render! {
|
||||
input {
|
||||
oninput: move |e: FormEvent| data.write().set(*id, e.value()),
|
||||
value: data.read().view(id)?
|
||||
}
|
||||
}
|
||||
}
|
|
@ -8,7 +8,7 @@ fn main() {
|
|||
fn app() -> Element {
|
||||
let toggled = use_signal(|| false);
|
||||
|
||||
use_global_shortcut("ctrl+s", move || toggled.toggle());
|
||||
_ = use_global_shortcut("ctrl+s", move || toggled.toggle());
|
||||
|
||||
rsx!("toggle: {toggled}")
|
||||
}
|
||||
|
|
|
@ -54,13 +54,13 @@ fn NavBar() -> Element {
|
|||
|
||||
#[component]
|
||||
fn Home() -> Element {
|
||||
log::debug!("rendering home {:?}", cx.scope_id());
|
||||
log::debug!("rendering home {:?}", current_scope_id());
|
||||
render! { h1 { "Home" } }
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn BlogList() -> Element {
|
||||
log::debug!("rendering blog list {:?}", cx.scope_id());
|
||||
log::debug!("rendering blog list {:?}", current_scope_id());
|
||||
render! { div { "Blog List" } }
|
||||
}
|
||||
|
||||
|
|
|
@ -38,5 +38,5 @@ fn Nav() -> Element {
|
|||
}
|
||||
|
||||
fn main() {
|
||||
dioxus_desktop::launch(|cx| render!(Router::<Route> {}));
|
||||
dioxus_desktop::launch(|| render!(Router::<Route> {}));
|
||||
}
|
||||
|
|
|
@ -31,6 +31,7 @@ fn Component(props: Props) -> Element {
|
|||
struct Props {
|
||||
#[props(extends = GlobalAttributes)]
|
||||
attributes: Vec<Attribute>,
|
||||
|
||||
extra_data: String,
|
||||
extra_data2: String,
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ fn main() {
|
|||
// Or we can render rsx! calls themselves
|
||||
println!(
|
||||
"{}",
|
||||
dioxus_ssr::render_lazy(rsx! {
|
||||
dioxus_ssr::render_element(rsx! {
|
||||
div {
|
||||
h1 { "Hello, world!" }
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ fn main() {
|
|||
fn app() -> Element {
|
||||
let count = use_signal(|| 10);
|
||||
|
||||
use_future(|_| async move {
|
||||
use_future(|| async move {
|
||||
let mut stream = some_stream();
|
||||
|
||||
while let Some(second) = stream.next().await {
|
||||
|
|
|
@ -48,7 +48,7 @@ fn app() -> Element {
|
|||
/// Suspense is achieved my moving the future into only the component that
|
||||
/// actually renders the data.
|
||||
fn Doggo() -> Element {
|
||||
let fut = use_future(|_| async move {
|
||||
let fut = use_future(|| async move {
|
||||
#[derive(serde::Deserialize)]
|
||||
struct DogApi {
|
||||
message: String,
|
||||
|
@ -61,7 +61,7 @@ fn Doggo() -> Element {
|
|||
.await
|
||||
});
|
||||
|
||||
match fut.value() {
|
||||
match fut.value().read().as_ref() {
|
||||
Some(Ok(resp)) => rsx! {
|
||||
button {
|
||||
onclick: move |_| fut.restart(),
|
||||
|
|
|
@ -2,7 +2,7 @@ use dioxus::prelude::*;
|
|||
|
||||
fn app() -> Element {
|
||||
let state = use_signal(|| 0);
|
||||
use_future(|_| {
|
||||
use_future(|| {
|
||||
to_owned![state];
|
||||
async move {
|
||||
loop {
|
||||
|
|
|
@ -22,7 +22,7 @@ dioxus-signals = { workspace = true, optional = true }
|
|||
dioxus-hot-reload = { workspace = true, optional = true }
|
||||
|
||||
[features]
|
||||
default = ["macro", "html", "hot-reload", "signals"]
|
||||
default = ["macro", "html", "hot-reload", "signals", "hooks"]
|
||||
signals = ["dioxus-signals"]
|
||||
macro = ["dioxus-core-macro", "dioxus-rsx"]
|
||||
html = ["dioxus-html"]
|
||||
|
|
|
@ -15,6 +15,7 @@ nightly-features = []
|
|||
|
||||
[dependencies]
|
||||
dioxus-core = { workspace = true }
|
||||
dioxus-signals = { workspace = true }
|
||||
futures-channel = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
|
|
|
@ -55,42 +55,26 @@ macro_rules! to_owned {
|
|||
};
|
||||
}
|
||||
|
||||
// pub mod computed;
|
||||
|
||||
mod use_on_destroy;
|
||||
pub use use_on_destroy::*;
|
||||
|
||||
// mod use_const;
|
||||
// pub use use_const::*;
|
||||
|
||||
// mod use_context;
|
||||
// pub use use_context::*;
|
||||
|
||||
// mod use_state;
|
||||
// pub use use_state::{use_state, UseState};
|
||||
mod use_coroutine;
|
||||
pub use use_coroutine::*;
|
||||
|
||||
// mod use_ref;
|
||||
// pub use use_ref::*;
|
||||
|
||||
// mod use_shared_state;
|
||||
// pub use use_shared_state::*;
|
||||
|
||||
// mod use_coroutine;
|
||||
// pub use use_coroutine::*;
|
||||
|
||||
// mod use_future;
|
||||
// pub use use_future::*;
|
||||
mod use_future;
|
||||
pub use use_future::*;
|
||||
|
||||
// mod use_effect;
|
||||
// pub use use_effect::*;
|
||||
|
||||
// mod use_callback;
|
||||
// pub use use_callback::*;
|
||||
|
||||
// mod use_memo;
|
||||
// pub use use_memo::*;
|
||||
|
||||
// mod use_on_create;
|
||||
// pub use use_on_create::*;
|
||||
// mod use_root_context;
|
||||
// pub use use_root_context::*;
|
||||
|
||||
mod use_root_context;
|
||||
pub use use_root_context::*;
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
use dioxus_core::{ScopeState, Task};
|
||||
use dioxus_core::prelude::{consume_context, provide_context, push_future, use_hook};
|
||||
use dioxus_core::Task;
|
||||
use dioxus_signals::{CopyValue, Signal};
|
||||
pub use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender};
|
||||
use std::future::Future;
|
||||
|
||||
|
@ -63,102 +65,76 @@ use std::future::Future;
|
|||
/// }
|
||||
/// })
|
||||
/// ```
|
||||
pub fn use_coroutine<M, G, F>(nit: G) -> &Coroutine<M>
|
||||
pub fn use_coroutine<M, G, F>(init: G) -> Coroutine<M>
|
||||
where
|
||||
M: 'static,
|
||||
G: FnOnce(UnboundedReceiver<M>) -> F,
|
||||
F: Future<Output = ()> + 'static,
|
||||
{
|
||||
cx.use_hook(|| {
|
||||
let coroutine = use_hook(|| {
|
||||
provide_context(Coroutine {
|
||||
needs_regen: Signal::new(true),
|
||||
tx: CopyValue::new(None),
|
||||
task: CopyValue::new(None),
|
||||
})
|
||||
});
|
||||
|
||||
// We do this here so we can capture data with FnOnce
|
||||
// this might not be the best API
|
||||
if *coroutine.needs_regen.read() {
|
||||
let (tx, rx) = futures_channel::mpsc::unbounded();
|
||||
let task = cx.push_future(init(rx));
|
||||
cx.provide_context(Coroutine { tx, task })
|
||||
})
|
||||
let task = push_future(init(rx)).unwrap();
|
||||
coroutine.tx.set(Some(tx));
|
||||
coroutine.task.set(Some(task));
|
||||
coroutine.needs_regen.set_untracked(false);
|
||||
}
|
||||
|
||||
coroutine
|
||||
}
|
||||
|
||||
/// Get a handle to a coroutine higher in the tree
|
||||
///
|
||||
/// See the docs for [`use_coroutine`] for more details.
|
||||
#[must_use]
|
||||
pub fn use_coroutine_handle<M: 'static>() -> Option<&Coroutine<M>> {
|
||||
cx.use_hook(|| cx.consume_context::<Coroutine<M>>())
|
||||
.as_ref()
|
||||
pub fn use_coroutine_handle<M: 'static>() -> Option<Coroutine<M>> {
|
||||
use_hook(|| consume_context::<Coroutine<M>>())
|
||||
}
|
||||
|
||||
pub struct Coroutine<T> {
|
||||
tx: UnboundedSender<T>,
|
||||
task: Task,
|
||||
}
|
||||
|
||||
// for use in futures
|
||||
impl<T> Clone for Coroutine<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
tx: self.tx.clone(),
|
||||
task: self.task,
|
||||
}
|
||||
}
|
||||
#[derive(PartialEq)]
|
||||
pub struct Coroutine<T: 'static> {
|
||||
needs_regen: Signal<bool>,
|
||||
tx: CopyValue<Option<UnboundedSender<T>>>,
|
||||
task: CopyValue<Option<Task>>,
|
||||
}
|
||||
|
||||
impl<T> Coroutine<T> {
|
||||
/// Get the ID of this coroutine
|
||||
#[must_use]
|
||||
pub fn task_id(&self) -> Task {
|
||||
self.task
|
||||
/// Get the underlying task handle
|
||||
pub fn task(&self) -> Task {
|
||||
self.task.read().clone().unwrap()
|
||||
}
|
||||
|
||||
/// Send a message to the coroutine
|
||||
pub fn send(&self, msg: T) {
|
||||
let _ = self.tx.unbounded_send(msg);
|
||||
let _ = self.tx.read().as_ref().unwrap().unbounded_send(msg);
|
||||
}
|
||||
|
||||
/// Restart this coroutine
|
||||
///
|
||||
/// Forces the component to re-render, which will re-invoke the coroutine.
|
||||
pub fn restart(&self) {
|
||||
self.needs_regen.set(true);
|
||||
self.task().stop();
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> PartialEq for Coroutine<T> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.task == other.task
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#![allow(unused)]
|
||||
|
||||
use super::*;
|
||||
use dioxus_core::prelude::*;
|
||||
use futures_channel::mpsc::unbounded;
|
||||
use futures_util::StreamExt;
|
||||
|
||||
fn app(name: String) -> Element {
|
||||
let task = use_coroutine(|mut rx: UnboundedReceiver<i32>| async move {
|
||||
while let Some(msg) = rx.next().await {
|
||||
println!("got message: {msg}");
|
||||
}
|
||||
});
|
||||
|
||||
let task2 = use_coroutine(view_task);
|
||||
|
||||
let task3 = use_coroutine(|rx| complex_task(rx, 10));
|
||||
|
||||
todo!()
|
||||
}
|
||||
|
||||
async fn view_task(mut rx: UnboundedReceiver<i32>) {
|
||||
while let Some(msg) = rx.next().await {
|
||||
println!("got message: {msg}");
|
||||
}
|
||||
}
|
||||
|
||||
enum Actions {
|
||||
CloseAll,
|
||||
OpenAll,
|
||||
}
|
||||
|
||||
async fn complex_task(mut rx: UnboundedReceiver<Actions>, name: i32) {
|
||||
while let Some(msg) = rx.next().await {
|
||||
match msg {
|
||||
Actions::CloseAll => todo!(),
|
||||
Actions::OpenAll => todo!(),
|
||||
}
|
||||
// manual impl since deriving doesn't work with generics
|
||||
impl<T> Copy for Coroutine<T> {}
|
||||
impl<T> Clone for Coroutine<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
tx: self.tx,
|
||||
task: self.task,
|
||||
needs_regen: self.needs_regen,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
#![allow(missing_docs)]
|
||||
use dioxus_core::{ScopeState, Task};
|
||||
use dioxus_signals::{use_effect, use_signal, Signal};
|
||||
use std::{any::Any, cell::Cell, future::Future, rc::Rc, sync::Arc};
|
||||
|
||||
use crate::{use_state, UseState};
|
||||
|
||||
/// A future that resolves to a value.
|
||||
///
|
||||
/// This runs the future only once - though the future may be regenerated
|
||||
|
@ -17,70 +16,29 @@ use crate::{use_state, UseState};
|
|||
/// will be canceled before the new one is started.
|
||||
///
|
||||
/// - dependencies: a tuple of references to values that are PartialEq + Clone
|
||||
pub fn use_future<T, F, D>
|
||||
dependencies: D,
|
||||
future: impl FnOnce(D::Out) -> F,
|
||||
) -> &UseFuture<T>
|
||||
pub fn use_future<T, F>(future: impl FnMut() -> F) -> UseFuture<T>
|
||||
where
|
||||
T: 'static,
|
||||
F: Future<Output = T> + 'static,
|
||||
D: UseFutureDep,
|
||||
{
|
||||
let val = use_signal(|| None);
|
||||
let task = use_signal(|| None);
|
||||
|
||||
let state = cx.use_hook(move || UseFuture {
|
||||
update: cx.schedule_update(),
|
||||
needs_regen: Rc::new(Cell::new(true)),
|
||||
state: val.clone(),
|
||||
task: Default::default(),
|
||||
use_effect(|| {
|
||||
// task.set();
|
||||
});
|
||||
//
|
||||
|
||||
let state_dependencies = cx.use_hook(Vec::new);
|
||||
|
||||
if dependencies.clone().apply(state_dependencies) || state.needs_regen.get() {
|
||||
// kill the old one, if it exists
|
||||
if let Some(task) = state.task.take() {
|
||||
cx.remove_future(task);
|
||||
}
|
||||
|
||||
// Create the new future
|
||||
let fut = future(dependencies.out());
|
||||
let val = val.clone();
|
||||
let task = state.task.clone();
|
||||
|
||||
state.task.set(Some(cx.push_future(async move {
|
||||
val.set(Some(fut.await));
|
||||
task.take();
|
||||
})));
|
||||
|
||||
// Mark that we don't need to regenerate
|
||||
state.needs_regen.set(false);
|
||||
UseFuture {
|
||||
value: todo!(),
|
||||
task,
|
||||
state: todo!(),
|
||||
}
|
||||
|
||||
// update the current value
|
||||
state.state.current_val = val.current_val.clone();
|
||||
|
||||
state
|
||||
}
|
||||
|
||||
pub enum FutureState<'a, T> {
|
||||
Pending,
|
||||
Complete(&'a T),
|
||||
Regenerating(&'a T), // the old value
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct UseFuture<T: 'static> {
|
||||
update: Arc<dyn Fn()>,
|
||||
needs_regen: Rc<Cell<bool>>,
|
||||
task: Rc<Cell<Option<Task>>>,
|
||||
state: UseState<Option<T>>,
|
||||
}
|
||||
|
||||
pub enum UseFutureState<'a, T> {
|
||||
Pending,
|
||||
Complete(&'a T),
|
||||
Reloading(&'a T),
|
||||
value: Signal<T>,
|
||||
task: Signal<Option<Task>>,
|
||||
state: Signal<UseFutureState<T>>,
|
||||
}
|
||||
|
||||
impl<T> UseFuture<T> {
|
||||
|
@ -89,198 +47,57 @@ impl<T> UseFuture<T> {
|
|||
/// Will not cancel the previous future, but will ignore any values that it
|
||||
/// generates.
|
||||
pub fn restart(&self) {
|
||||
self.needs_regen.set(true);
|
||||
(self.update)();
|
||||
// self.needs_regen.set(true);
|
||||
// (self.update)();
|
||||
}
|
||||
|
||||
/// Forcefully cancel a future
|
||||
pub fn cancel(&self, ) {
|
||||
if let Some(task) = self.task.take() {
|
||||
cx.remove_future(task);
|
||||
}
|
||||
pub fn cancel(&self) {
|
||||
// if let Some(task) = self.task.take() {
|
||||
// cx.remove_future(task);
|
||||
// }
|
||||
}
|
||||
|
||||
// Manually set the value in the future slot without starting the future over
|
||||
pub fn set(&self, new_value: T) {
|
||||
self.state.set(Some(new_value));
|
||||
// self.state.set(Some(new_value));
|
||||
}
|
||||
|
||||
/// Return any value, even old values if the future has not yet resolved.
|
||||
///
|
||||
/// If the future has never completed, the returned value will be `None`.
|
||||
pub fn value(&self) -> Option<&T> {
|
||||
self.state.current_val.as_ref().as_ref()
|
||||
pub fn value(&self) -> Signal<Option<T>> {
|
||||
todo!()
|
||||
// self.state.current_val.as_ref().as_ref()
|
||||
}
|
||||
|
||||
/// Get the ID of the future in Dioxus' internal scheduler
|
||||
pub fn task(&self) -> Option<Task> {
|
||||
self.task.get()
|
||||
todo!()
|
||||
// self.task.get()
|
||||
}
|
||||
|
||||
/// Get the current state of the future.
|
||||
pub fn state(&self) -> UseFutureState<T> {
|
||||
match (&self.task.get(), &self.value()) {
|
||||
// If we have a task and an existing value, we're reloading
|
||||
(Some(_), Some(val)) => UseFutureState::Reloading(val),
|
||||
todo!()
|
||||
// match (&self.task.get(), &self.value()) {
|
||||
// // If we have a task and an existing value, we're reloading
|
||||
// (Some(_), Some(val)) => UseFutureState::Reloading(val),
|
||||
|
||||
// no task, but value - we're done
|
||||
(None, Some(val)) => UseFutureState::Complete(val),
|
||||
// // no task, but value - we're done
|
||||
// (None, Some(val)) => UseFutureState::Complete(val),
|
||||
|
||||
// no task, no value - something's wrong? return pending
|
||||
(None, None) => UseFutureState::Pending,
|
||||
// // no task, no value - something's wrong? return pending
|
||||
// (None, None) => UseFutureState::Pending,
|
||||
|
||||
// Task, no value - we're still pending
|
||||
(Some(_), None) => UseFutureState::Pending,
|
||||
}
|
||||
// // Task, no value - we're still pending
|
||||
// (Some(_), None) => UseFutureState::Pending,
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
pub trait UseFutureDep: Sized + Clone {
|
||||
type Out;
|
||||
fn out(&self) -> Self::Out;
|
||||
fn apply(self, state: &mut Vec<Box<dyn Any>>) -> bool;
|
||||
}
|
||||
|
||||
impl UseFutureDep for () {
|
||||
type Out = ();
|
||||
fn out(&self) -> Self::Out {}
|
||||
fn apply(self, _state: &mut Vec<Box<dyn Any>>) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Dep: 'static + PartialEq + Clone {}
|
||||
impl<T> Dep for T where T: 'static + PartialEq + Clone {}
|
||||
|
||||
impl<A: Dep> UseFutureDep for &A {
|
||||
type Out = A;
|
||||
fn out(&self) -> Self::Out {
|
||||
(*self).clone()
|
||||
}
|
||||
fn apply(self, state: &mut Vec<Box<dyn Any>>) -> bool {
|
||||
match state.get_mut(0).and_then(|f| f.downcast_mut::<A>()) {
|
||||
Some(val) => {
|
||||
if *val != *self {
|
||||
*val = self.clone();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
None => {
|
||||
state.push(Box::new(self.clone()));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_dep {
|
||||
(
|
||||
$($el:ident=$name:ident,)*
|
||||
) => {
|
||||
impl< $($el),* > UseFutureDep for ($(&$el,)*)
|
||||
where
|
||||
$(
|
||||
$el: Dep
|
||||
),*
|
||||
{
|
||||
type Out = ($($el,)*);
|
||||
|
||||
fn out(&self) -> Self::Out {
|
||||
let ($($name,)*) = self;
|
||||
($((*$name).clone(),)*)
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn apply(self, state: &mut Vec<Box<dyn Any>>) -> bool {
|
||||
let ($($name,)*) = self;
|
||||
let mut idx = 0;
|
||||
let mut needs_regen = false;
|
||||
|
||||
$(
|
||||
match state.get_mut(idx).map(|f| f.downcast_mut::<$el>()).flatten() {
|
||||
Some(val) => {
|
||||
if *val != *$name {
|
||||
*val = $name.clone();
|
||||
needs_regen = true;
|
||||
}
|
||||
}
|
||||
None => {
|
||||
state.push(Box::new($name.clone()));
|
||||
needs_regen = true;
|
||||
}
|
||||
}
|
||||
idx += 1;
|
||||
)*
|
||||
|
||||
needs_regen
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_dep!(A = a,);
|
||||
impl_dep!(A = a, B = b,);
|
||||
impl_dep!(A = a, B = b, C = c,);
|
||||
impl_dep!(A = a, B = b, C = c, D = d,);
|
||||
impl_dep!(A = a, B = b, C = c, D = d, E = e,);
|
||||
impl_dep!(A = a, B = b, C = c, D = d, E = e, F = f,);
|
||||
impl_dep!(A = a, B = b, C = c, D = d, E = e, F = f, G = g,);
|
||||
impl_dep!(A = a, B = b, C = c, D = d, E = e, F = f, G = g, H = h,);
|
||||
|
||||
/// A helper macro that merges uses the closure syntax to elaborate the dependency array
|
||||
#[macro_export]
|
||||
macro_rules! use_future {
|
||||
($cx:ident, || $($rest:tt)*) => { use_future( $cx, (), |_| $($rest)* ) };
|
||||
($cx:ident, | $($args:tt),* | $($rest:tt)*) => {
|
||||
use_future(
|
||||
$cx,
|
||||
($($args),*),
|
||||
|($($args),*)| $($rest)*
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[allow(unused)]
|
||||
#[test]
|
||||
fn test_use_future() {
|
||||
use dioxus_core::prelude::*;
|
||||
|
||||
struct MyProps {
|
||||
a: String,
|
||||
b: i32,
|
||||
c: i32,
|
||||
d: i32,
|
||||
e: i32,
|
||||
}
|
||||
|
||||
async fn app(cx: Scope<'_, MyProps>) -> Element {
|
||||
// should only ever run once
|
||||
let fut = use_future(|_| async move {});
|
||||
|
||||
// runs when a is changed
|
||||
let fut = use_future((&cx.props.a,), |(a,)| async move {});
|
||||
|
||||
// runs when a or b is changed
|
||||
let fut = use_future((&cx.props.a, &cx.props.b), |(a, b)| async move { 123 });
|
||||
|
||||
let a = use_future(|| async move {
|
||||
// do the thing!
|
||||
});
|
||||
|
||||
let b = &123;
|
||||
let c = &123;
|
||||
|
||||
let a = use_future(|b, c| async move {
|
||||
let a = b + c;
|
||||
let blah = "asd";
|
||||
});
|
||||
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
pub enum UseFutureState<T: 'static> {
|
||||
Pending,
|
||||
Complete(Signal<T>),
|
||||
Regenerating(Signal<T>), // the old value
|
||||
}
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
use dioxus_core::ScopeState;
|
||||
use dioxus_core::{prelude::consume_context, prelude::provide_root_context, use_hook};
|
||||
|
||||
///
|
||||
pub fn use_root_context<T: 'static + Clone>(, new: impl FnOnce() -> T) -> &T {
|
||||
cx.use_hook(|| {
|
||||
cx.consume_context::<T>()
|
||||
.unwrap_or_else(|| cx.provide_root_context(new()))
|
||||
pub fn use_root_context<T: 'static + Clone>(new: impl FnOnce() -> T) -> T {
|
||||
use_hook(|| {
|
||||
consume_context::<T>()
|
||||
// If no context is provided, create a new one at the root
|
||||
.unwrap_or_else(|| provide_root_context(new()).expect(" A runtime to exist"))
|
||||
})
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ use dioxus::prelude::*;
|
|||
|
||||
fn app() -> Element {
|
||||
let state = use_signal(|| 0);
|
||||
use_future(|_| {
|
||||
use_future(|| {
|
||||
to_owned![state];
|
||||
async move {
|
||||
loop {
|
||||
|
|
|
@ -584,12 +584,12 @@ impl RouteEnum {
|
|||
}
|
||||
|
||||
quote! {
|
||||
impl dioxus_router::routable::Routable for #name where Self: Clone {
|
||||
const SITE_MAP: &'static [dioxus_router::routable::SiteMapSegment] = &[
|
||||
impl ::dioxus_router::routable::Routable for #name where Self: Clone {
|
||||
const SITE_MAP: &'static [::dioxus_router::routable::SiteMapSegment] = &[
|
||||
#(#site_map,)*
|
||||
];
|
||||
|
||||
fn render<'a>(&self, cx: &'a dioxus::prelude::ScopeState, level: usize) -> dioxus::prelude::Element {
|
||||
fn render(&self, level: usize) -> ::dioxus::prelude::Element {
|
||||
let myself = self.clone();
|
||||
match (level, myself) {
|
||||
#(#matches)*
|
||||
|
|
|
@ -158,7 +158,7 @@ impl<T: 'static> CopyValue<T> {
|
|||
}
|
||||
|
||||
/// Set the value. If the value has been dropped, this will panic.
|
||||
pub fn set(&mut self, value: T) {
|
||||
pub fn set(&self, value: T) {
|
||||
*self.write() = value;
|
||||
}
|
||||
|
||||
|
|
|
@ -292,6 +292,14 @@ impl<T: 'static> Signal<T> {
|
|||
*self.write() = value;
|
||||
}
|
||||
|
||||
/// Set the value of the signal without triggering an update on subscribers.
|
||||
///
|
||||
/// todo: we should make it so setting while rendering doesn't trigger an update s
|
||||
pub fn set_untracked(&self, value: T) {
|
||||
let mut inner = self.inner.write();
|
||||
inner.value = value;
|
||||
}
|
||||
|
||||
/// Run a closure with a reference to the signal's value.
|
||||
/// If the signal has been dropped, this will panic.
|
||||
#[track_caller]
|
||||
|
|
Loading…
Reference in a new issue