Merge branch 'master' into router-typesafe
This commit is contained in:
commit
4c3fb1a33f
42 files changed
+327
-168
No files matched your search
@@ -1,6 +1,9 @@
|
||||
use crate::error::Result;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{collections::HashMap, path::PathBuf};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct DioxusConfig {
|
||||
@@ -19,27 +22,36 @@ fn default_plugin() -> toml::Value {
|
||||
impl DioxusConfig {
|
||||
pub fn load() -> crate::error::Result<Option<DioxusConfig>> {
|
||||
let Ok(crate_dir) = crate::cargo::crate_root() else { return Ok(None); };
|
||||
let crate_dir = crate_dir.as_path();
|
||||
|
||||
// we support either `Dioxus.toml` or `Cargo.toml`
|
||||
let Some(dioxus_conf_file) = acquire_dioxus_toml(crate_dir) else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let dioxus_conf_file = dioxus_conf_file.as_path();
|
||||
toml::from_str::<DioxusConfig>(&std::fs::read_to_string(dioxus_conf_file)?)
|
||||
.map_err(|_| crate::Error::Unique("Dioxus.toml parse failed".into()))
|
||||
.map_err(|err| {
|
||||
let error_location = dioxus_conf_file
|
||||
.strip_prefix(crate_dir)
|
||||
.unwrap_or(dioxus_conf_file)
|
||||
.display();
|
||||
crate::Error::Unique(format!("{error_location} {err}"))
|
||||
})
|
||||
.map(Some)
|
||||
}
|
||||
}
|
||||
|
||||
fn acquire_dioxus_toml(dir: PathBuf) -> Option<PathBuf> {
|
||||
fn acquire_dioxus_toml(dir: &Path) -> Option<PathBuf> {
|
||||
// prefer uppercase
|
||||
if dir.join("Dioxus.toml").is_file() {
|
||||
return Some(dir.join("Dioxus.toml"));
|
||||
let uppercase_conf = dir.join("Dioxus.toml");
|
||||
if uppercase_conf.is_file() {
|
||||
return Some(uppercase_conf);
|
||||
}
|
||||
|
||||
// lowercase is fine too
|
||||
if dir.join("dioxus.toml").is_file() {
|
||||
return Some(dir.join("Dioxus.toml"));
|
||||
let lowercase_conf = dir.join("dioxus.toml");
|
||||
if lowercase_conf.is_file() {
|
||||
return Some(lowercase_conf);
|
||||
}
|
||||
|
||||
None
|
||||
|
||||
@@ -14,7 +14,7 @@ async fn main() -> anyhow::Result<()> {
|
||||
set_up_logging();
|
||||
|
||||
let _dioxus_config = DioxusConfig::load()
|
||||
.map_err(|e| anyhow!("Failed to load `Dioxus.toml` because: {e}"))?
|
||||
.map_err(|e| anyhow!("Failed to load Dioxus config because: {e}"))?
|
||||
.unwrap_or_else(|| {
|
||||
log::warn!("You appear to be creating a Dioxus project from scratch; we will use the default config");
|
||||
DioxusConfig::default()
|
||||
|
||||
+10
-10
@@ -1,4 +1,3 @@
|
||||
|
||||
<div align="center">
|
||||
<h1>Fermi ⚛</h1>
|
||||
<p>
|
||||
@@ -6,7 +5,6 @@
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
<div align="center">
|
||||
<!-- Crates version -->
|
||||
<a href="https://crates.io/crates/fermi">
|
||||
@@ -30,21 +28,21 @@
|
||||
</a>
|
||||
</div>
|
||||
|
||||
-----
|
||||
---
|
||||
|
||||
Fermi is a global state management solution for Dioxus that's as easy as `use_state`.
|
||||
|
||||
Inspired by atom-based state management solutions, all state in Fermi starts as an `atom`:
|
||||
|
||||
```rust, ignore
|
||||
static NAME: Atom<&str> = |_| "Dioxus";
|
||||
static NAME: Atom<&str> = Atom(|_| "Dioxus");
|
||||
```
|
||||
|
||||
From anywhere in our app, we can read the value of our atom:
|
||||
|
||||
```rust, ignores
|
||||
```rust, ignore
|
||||
fn NameCard(cx: Scope) -> Element {
|
||||
let name = use_read(cx, NAME);
|
||||
let name = use_read(cx, &NAME);
|
||||
cx.render(rsx!{ h1 { "Hello, {name}"} })
|
||||
}
|
||||
```
|
||||
@@ -53,7 +51,7 @@ We can also set the value of our atom, also from anywhere in our app:
|
||||
|
||||
```rust, ignore
|
||||
fn NameCard(cx: Scope) -> Element {
|
||||
let set_name = use_set(cx, NAME);
|
||||
let set_name = use_set(cx, &NAME);
|
||||
cx.render(rsx!{
|
||||
button {
|
||||
onclick: move |_| set_name("Fermi"),
|
||||
@@ -66,10 +64,10 @@ fn NameCard(cx: Scope) -> Element {
|
||||
If needed, we can update the atom's value, based on itself:
|
||||
|
||||
```rust, ignore
|
||||
static COUNT: Atom<i32> = |_| 0;
|
||||
static COUNT: Atom<i32> = Atom(|_| 0);
|
||||
|
||||
fn Counter(cx: Scope) -> Element {
|
||||
let mut count = use_atom_state(cx, COUNT);
|
||||
let mut count = use_atom_state(cx, &COUNT);
|
||||
|
||||
cx.render(rsx!{
|
||||
p {
|
||||
@@ -86,6 +84,7 @@ fn Counter(cx: Scope) -> Element {
|
||||
It's that simple!
|
||||
|
||||
## Installation
|
||||
|
||||
Fermi is currently under construction, so you have to use the `master` branch to get started.
|
||||
|
||||
```toml
|
||||
@@ -93,10 +92,10 @@ Fermi is currently under construction, so you have to use the `master` branch to
|
||||
fermi = { git = "https://github.com/dioxuslabs/dioxus" }
|
||||
```
|
||||
|
||||
|
||||
## Running examples
|
||||
|
||||
The examples here use Dioxus Desktop to showcase their functionality. To run an example, use
|
||||
|
||||
```sh
|
||||
$ cargo run --example fermi
|
||||
```
|
||||
@@ -104,6 +103,7 @@ $ cargo run --example fermi
|
||||
## Features
|
||||
|
||||
Broadly our feature set required to be released includes:
|
||||
|
||||
- [x] Support for Atoms
|
||||
- [x] Support for AtomRef (for values that aren't `Clone`)
|
||||
- [ ] Support for Atom Families
|
||||
|
||||
@@ -1,24 +1,21 @@
|
||||
use crate::{AtomId, AtomRoot, Readable, Writable};
|
||||
|
||||
pub type Atom<T> = fn(AtomBuilder) -> T;
|
||||
pub struct Atom<T>(pub fn(AtomBuilder) -> T);
|
||||
pub struct AtomBuilder;
|
||||
|
||||
impl<V: 'static> Readable<V> for Atom<V> {
|
||||
impl<V> Readable<V> for &'static Atom<V> {
|
||||
fn read(&self, _root: AtomRoot) -> Option<V> {
|
||||
todo!()
|
||||
}
|
||||
fn init(&self) -> V {
|
||||
(*self)(AtomBuilder)
|
||||
self.0(AtomBuilder)
|
||||
}
|
||||
fn unique_id(&self) -> AtomId {
|
||||
AtomId {
|
||||
ptr: *self as *const (),
|
||||
type_id: std::any::TypeId::of::<V>(),
|
||||
}
|
||||
*self as *const Atom<V> as *const ()
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: 'static> Writable<V> for Atom<V> {
|
||||
impl<V> Writable<V> for &'static Atom<V> {
|
||||
fn write(&self, _root: AtomRoot, _value: V) {
|
||||
todo!()
|
||||
}
|
||||
@@ -26,6 +23,22 @@ impl<V: 'static> Writable<V> for Atom<V> {
|
||||
|
||||
#[test]
|
||||
fn atom_compiles() {
|
||||
static TEST_ATOM: Atom<&str> = |_| "hello";
|
||||
dbg!(TEST_ATOM.init());
|
||||
static TEST_ATOM: Atom<&str> = Atom(|_| "hello");
|
||||
dbg!((&TEST_ATOM).init());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn atom_is_unique() {
|
||||
static TEST_ATOM_1: Atom<&str> = Atom(|_| "hello");
|
||||
static TEST_ATOM_2: Atom<&str> = Atom(|_| "hello");
|
||||
assert_eq!((&TEST_ATOM_1).unique_id(), (&TEST_ATOM_1).unique_id());
|
||||
assert_ne!((&TEST_ATOM_1).unique_id(), (&TEST_ATOM_2).unique_id());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn atom_is_unique_2() {
|
||||
struct S(String);
|
||||
static TEST_ATOM_1: Atom<Vec<S>> = Atom(|_| Vec::new());
|
||||
static TEST_ATOM_2: Atom<Vec<String>> = Atom(|_| Vec::new());
|
||||
assert_ne!((&TEST_ATOM_1).unique_id(), (&TEST_ATOM_2).unique_id());
|
||||
}
|
||||
@@ -2,26 +2,23 @@ use crate::{AtomId, AtomRoot, Readable, Writable};
|
||||
use im_rc::HashMap as ImMap;
|
||||
|
||||
pub struct AtomFamilyBuilder;
|
||||
pub type AtomFamily<K, V> = fn(AtomFamilyBuilder) -> ImMap<K, V>;
|
||||
pub struct AtomFamily<K, V>(pub fn(AtomFamilyBuilder) -> ImMap<K, V>);
|
||||
|
||||
impl<K, V: 'static> Readable<ImMap<K, V>> for AtomFamily<K, V> {
|
||||
impl<K, V> Readable<ImMap<K, V>> for &'static AtomFamily<K, V> {
|
||||
fn read(&self, _root: AtomRoot) -> Option<ImMap<K, V>> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn init(&self) -> ImMap<K, V> {
|
||||
(*self)(AtomFamilyBuilder)
|
||||
self.0(AtomFamilyBuilder)
|
||||
}
|
||||
|
||||
fn unique_id(&self) -> AtomId {
|
||||
AtomId {
|
||||
ptr: *self as *const (),
|
||||
type_id: std::any::TypeId::of::<V>(),
|
||||
}
|
||||
*self as *const AtomFamily<K, V> as *const ()
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, V: 'static> Writable<ImMap<K, V>> for AtomFamily<K, V> {
|
||||
impl<K, V> Writable<ImMap<K, V>> for &'static AtomFamily<K, V> {
|
||||
fn write(&self, _root: AtomRoot, _value: ImMap<K, V>) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
@@ -2,27 +2,24 @@ use crate::{AtomId, AtomRoot, Readable};
|
||||
use std::cell::RefCell;
|
||||
|
||||
pub struct AtomRefBuilder;
|
||||
pub type AtomRef<T> = fn(AtomRefBuilder) -> T;
|
||||
pub struct AtomRef<T>(pub fn(AtomRefBuilder) -> T);
|
||||
|
||||
impl<V: 'static> Readable<RefCell<V>> for AtomRef<V> {
|
||||
impl<V> Readable<RefCell<V>> for &'static AtomRef<V> {
|
||||
fn read(&self, _root: AtomRoot) -> Option<RefCell<V>> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn init(&self) -> RefCell<V> {
|
||||
RefCell::new((*self)(AtomRefBuilder))
|
||||
RefCell::new(self.0(AtomRefBuilder))
|
||||
}
|
||||
|
||||
fn unique_id(&self) -> AtomId {
|
||||
AtomId {
|
||||
ptr: *self as *const (),
|
||||
type_id: std::any::TypeId::of::<V>(),
|
||||
}
|
||||
*self as *const AtomRef<V> as *const ()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn atom_compiles() {
|
||||
static TEST_ATOM: AtomRef<Vec<String>> = |_| vec![];
|
||||
dbg!(TEST_ATOM.init());
|
||||
static TEST_ATOM: AtomRef<Vec<String>> = AtomRef(|_| vec![]);
|
||||
dbg!((&TEST_ATOM).init());
|
||||
}
|
||||
@@ -13,7 +13,10 @@ use std::{
|
||||
///
|
||||
///
|
||||
///
|
||||
pub fn use_atom_ref<T: 'static>(cx: &ScopeState, atom: AtomRef<T>) -> &UseAtomRef<T> {
|
||||
pub fn use_atom_ref<'a, T: 'static>(
|
||||
cx: &'a ScopeState,
|
||||
atom: &'static AtomRef<T>,
|
||||
) -> &'a UseAtomRef<T> {
|
||||
let root = use_atom_root(cx);
|
||||
|
||||
&cx.use_hook(|| {
|
||||
|
||||
@@ -19,7 +19,7 @@ use std::{
|
||||
/// static COUNT: Atom<u32> = |_| 0;
|
||||
///
|
||||
/// fn Example(cx: Scope) -> Element {
|
||||
/// let mut count = use_atom_state(cx, COUNT);
|
||||
/// let mut count = use_atom_state(cx, &COUNT);
|
||||
///
|
||||
/// cx.render(rsx! {
|
||||
/// div {
|
||||
|
||||
@@ -1,21 +1,11 @@
|
||||
use std::{
|
||||
any::{Any, TypeId},
|
||||
cell::RefCell,
|
||||
collections::HashMap,
|
||||
rc::Rc,
|
||||
sync::Arc,
|
||||
};
|
||||
use std::{any::Any, cell::RefCell, collections::HashMap, rc::Rc, sync::Arc};
|
||||
|
||||
use dioxus_core::ScopeId;
|
||||
use im_rc::HashSet;
|
||||
|
||||
use crate::Readable;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct AtomId {
|
||||
pub ptr: *const (),
|
||||
pub type_id: TypeId,
|
||||
}
|
||||
pub type AtomId = *const ();
|
||||
|
||||
pub struct AtomRoot {
|
||||
pub atoms: RefCell<HashMap<AtomId, Slot>>,
|
||||
@@ -54,15 +44,7 @@ impl AtomRoot {
|
||||
// initialize the value if it's not already initialized
|
||||
if let Some(slot) = atoms.get_mut(&f.unique_id()) {
|
||||
slot.subscribers.insert(scope);
|
||||
match slot.value.clone().downcast() {
|
||||
Ok(res) => res,
|
||||
Err(e) => panic!(
|
||||
"Downcasting atom failed: {:?}. Has typeid of {:?} but needs typeid of {:?}",
|
||||
f.unique_id(),
|
||||
e.type_id(),
|
||||
TypeId::of::<V>()
|
||||
),
|
||||
}
|
||||
slot.value.clone().downcast().unwrap()
|
||||
} else {
|
||||
let value = Rc::new(f.init());
|
||||
let mut subscribers = HashSet::new();
|
||||
|
||||
@@ -9,17 +9,33 @@ use crate::UseFutureDep;
|
||||
/// If a future is pending when the dependencies change, the previous future
|
||||
/// will be allowed to continue
|
||||
///
|
||||
/// - dependencies: a tuple of references to values that are PartialEq + Clone
|
||||
/// - dependencies: a tuple of references to values that are `PartialEq` + `Clone`
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```rust, ignore
|
||||
///
|
||||
/// ```rust, no_run
|
||||
/// #[inline_props]
|
||||
/// fn app(cx: Scope, name: &str) -> Element {
|
||||
/// use_effect(cx, (name,), |(name,)| async move {
|
||||
/// set_title(name);
|
||||
/// }))
|
||||
/// fn Profile(cx: Scope, id: usize) -> Element {
|
||||
/// let name = use_state(cx, || None);
|
||||
///
|
||||
/// // Only fetch the user data when the id changes.
|
||||
/// use_effect(cx, (id,), |(id,)| {
|
||||
/// to_owned![name];
|
||||
/// async move {
|
||||
/// let user = fetch_user(id).await;
|
||||
/// name.set(user.name);
|
||||
/// }
|
||||
/// });
|
||||
///
|
||||
/// let name = name.get().clone().unwrap_or("Loading...".to_string());
|
||||
///
|
||||
/// render!(
|
||||
/// p { "{name}" }
|
||||
/// )
|
||||
/// }
|
||||
///
|
||||
/// fn app(cx: Scope) -> Element {
|
||||
/// render!(Profile { id: 0 })
|
||||
/// }
|
||||
/// ```
|
||||
pub fn use_effect<T, F, D>(cx: &ScopeState, dependencies: D, future: impl FnOnce(D::Out) -> F)
|
||||
|
||||
@@ -2,21 +2,27 @@ use dioxus_core::ScopeState;
|
||||
|
||||
use crate::UseFutureDep;
|
||||
|
||||
/// A hook that provides a callback that executes after the hooks have been applied
|
||||
/// A hook that provides a callback that executes if the dependencies change.
|
||||
/// This is useful to avoid running computation-expensive calculations even when the data doesn't change.
|
||||
///
|
||||
/// Whenever the hooks dependencies change, the callback will be re-evaluated.
|
||||
///
|
||||
/// - dependencies: a tuple of references to values that are PartialEq + Clone
|
||||
/// - dependencies: a tuple of references to values that are `PartialEq` + `Clone`
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```rust, ignore
|
||||
/// ```rust, no_run
|
||||
///
|
||||
/// #[inline_props]
|
||||
/// fn app(cx: Scope, name: &str) -> Element {
|
||||
/// use_memo(cx, (name,), |(name,)| {
|
||||
/// expensive_computation(name);
|
||||
/// }))
|
||||
/// fn Calculator(cx: Scope, number: usize) -> Element {
|
||||
/// let bigger_number = use_memo(cx, (number,), |(number,)| {
|
||||
/// // This will only be calculated when `number` has changed.
|
||||
/// number * 100
|
||||
/// });
|
||||
/// render!(
|
||||
/// p { "{bigger_number}" }
|
||||
/// )
|
||||
/// }
|
||||
/// fn app(cx: Scope) -> Element {
|
||||
/// render!(Calculator { number: 0 })
|
||||
/// }
|
||||
/// ```
|
||||
pub fn use_memo<T, D>(cx: &ScopeState, dependencies: D, callback: impl FnOnce(D::Out) -> T) -> &T
|
||||
|
||||
@@ -16,7 +16,7 @@ use std::{
|
||||
/// writes through the `write` method. Whenever `write` is called, the component
|
||||
/// that initialized the hook will be marked as "dirty".
|
||||
///
|
||||
/// ```rust, ignore
|
||||
/// ```rust, no_run
|
||||
/// let val = use_ref(|| HashMap::<u32, String>::new());
|
||||
///
|
||||
/// // using `write` will give us a `RefMut` to the inner value, which we can call methods on
|
||||
@@ -26,7 +26,7 @@ use std::{
|
||||
///
|
||||
/// You can avoid this default behavior with `write_silent`
|
||||
///
|
||||
/// ```ignore
|
||||
/// ```rust, no_run
|
||||
/// // with `write_silent`, the component will not be re-rendered
|
||||
/// val.write_silent().insert(2, "goodbye".to_string());
|
||||
/// ```
|
||||
@@ -35,7 +35,7 @@ use std::{
|
||||
///
|
||||
/// To read values out of the refcell, you can use the `read` method which will retrun a `Ref`.
|
||||
///
|
||||
/// ```rust, ignore
|
||||
/// ```rust, no_run
|
||||
/// let map: Ref<_> = val.read();
|
||||
///
|
||||
/// let item = map.get(&1);
|
||||
@@ -43,7 +43,7 @@ use std::{
|
||||
///
|
||||
/// To get an &T out of the RefCell, you need to "reborrow" through the Ref:
|
||||
///
|
||||
/// ```rust, ignore
|
||||
/// ```rust, no_run
|
||||
/// let read = val.read();
|
||||
/// let map = &*read;
|
||||
/// ```
|
||||
@@ -54,10 +54,10 @@ use std::{
|
||||
/// Typically this will be a collection like a HashMap or a Vec. To create new
|
||||
/// elements from the collection, we can use `read()` directly in our rsx!.
|
||||
///
|
||||
/// ```rust, ignore
|
||||
/// ```rust, no_run
|
||||
/// rsx!{
|
||||
/// val.read().iter().map(|(k, v)| {
|
||||
/// rsx!{ key: "{k}", value: "{v}" }
|
||||
/// rsx!{ key: "{k}", "{v}" }
|
||||
/// })
|
||||
/// }
|
||||
/// ```
|
||||
@@ -66,9 +66,9 @@ use std::{
|
||||
/// "render" inside the iterator. For some cases you might need to collect into
|
||||
/// a temporary Vec.
|
||||
///
|
||||
/// ```rust, ignore
|
||||
/// ```rust, no_run
|
||||
/// let items = val.read().iter().map(|(k, v)| {
|
||||
/// cx.render(rsx!{ key: "{k}", value: "{v}" })
|
||||
/// cx.render(rsx!{ key: "{k}", "{v}" })
|
||||
/// });
|
||||
///
|
||||
/// // collect into a Vec
|
||||
@@ -80,9 +80,9 @@ use std::{
|
||||
///
|
||||
/// To access values from a `UseRef` in an async context, you need to detach it
|
||||
/// from the current scope's lifetime, making it a `'static` value. This is done
|
||||
/// by simply calling `ToOnwed` or `Clone`.
|
||||
/// by simply calling `to_owned` or `clone`.
|
||||
///
|
||||
/// ```rust, ignore
|
||||
/// ```rust, no_run
|
||||
/// let val = use_ref(|| HashMap::<u32, String>::new());
|
||||
///
|
||||
/// cx.spawn({
|
||||
@@ -95,15 +95,15 @@ use std::{
|
||||
/// ```
|
||||
///
|
||||
/// If you're working with lots of values like UseState and UseRef, you can use the
|
||||
/// `clone!` macro to make it easier to write the above code.
|
||||
/// `to_owned!` macro to make it easier to write the above code.
|
||||
///
|
||||
/// ```rust, ignore
|
||||
/// ```rust, no_run
|
||||
/// let val1 = use_ref(|| HashMap::<u32, String>::new());
|
||||
/// let val2 = use_ref(|| HashMap::<u32, String>::new());
|
||||
/// let val3 = use_ref(|| HashMap::<u32, String>::new());
|
||||
///
|
||||
/// cx.spawn({
|
||||
/// clone![val1, val2, val3];
|
||||
/// to_owned![val1, val2, val3];
|
||||
/// async move {
|
||||
/// some_work().await;
|
||||
/// val.write().insert(1, "hello".to_string());
|
||||
@@ -194,7 +194,7 @@ impl<T> UseRef<T> {
|
||||
/// Note: You can always "reborrow" the value through the RefCell.
|
||||
/// This method just does it for you automatically.
|
||||
///
|
||||
/// ```rust, ignore
|
||||
/// ```rust, no_run
|
||||
/// let val = use_ref(|| HashMap::<u32, String>::new());
|
||||
///
|
||||
///
|
||||
@@ -214,7 +214,7 @@ impl<T> UseRef<T> {
|
||||
/// Note: You can always "reborrow" the value through the RefCell.
|
||||
/// This method just does it for you automatically.
|
||||
///
|
||||
/// ```rust, ignore
|
||||
/// ```rust, no_run
|
||||
/// let val = use_ref(|| HashMap::<u32, String>::new());
|
||||
///
|
||||
///
|
||||
|
||||
Reference in new issue
Block a user