Merge pull request #48 from DioxusLabs/jk/release

feat: more API updates
This commit is contained in:
Jonathan Kelley 2021-12-28 23:20:31 -05:00 committed by GitHub
commit f24aae8787
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 227 additions and 189 deletions

View file

@ -49,7 +49,7 @@
Dioxus is a portable, performant, and ergonomic framework for building cross-platform user experiences in Rust.
```rust
fn app(cx: Scope<()>) -> Element {
fn app(cx: Scope) -> Element {
let mut count = use_state(&cx, || 0);
cx.render(rsx!(
@ -85,18 +85,6 @@ If you know React, then you already know Dioxus.
<tr>
</table>
Available cargo features:
- default: core (macro, hooks, html)
- macro: support for `Props` and `rsx` macros
- hooks: foundational hooks like `use_state`, `use_ref`, etc.
- html: the entire namespace of `html` elements, their listeners, and attributes
- router: a cross-platform (web and desktop) solution for global app routing
- liveview: a threadpool to spawn new VirtualDoms and their handles in
- ssr: render the virtualdom to a string
- web: render the your app on the web
- desktop: render your app locally rendered with webview
- mobile: render your app on your device rendered with webview
## Examples:
| File Navigator (Desktop) | Bluetooth scanner (Desktop) | TodoMVC (All platforms) | Tailwind (Liveview) |

View file

@ -4,19 +4,19 @@ fn main() {}
type Element<'a> = ();
pub struct Scope<'a, T> {
pub struct Scope<'a, T = ()> {
props: &'a T,
}
#[inline_props]
pub fn component(
cx: Scope,
// #[inline_props]
pub fn component<'a>(
cx: Scope<'a>,
chkk: String,
chkk2: String,
r: u32,
cat: &'a str,
drd: String,
e: String,
) -> Element {
) -> Element<'a> {
let r = chkk.len();
}

View file

@ -92,13 +92,19 @@ impl ToTokens for InlinePropsBody {
quote! { #[derive(Props)] }
};
let lifetime = if generics.params.is_empty() {
quote! {}
} else {
quote! { 'a, }
};
out_tokens.append_all(quote! {
#modifiers
#vis struct #struct_name #generics {
#(#fields),*
}
#vis fn #ident #generics (#cx_token: Scope<'a, #struct_name #generics>) #output {
#vis fn #ident #generics (#cx_token: Scope<#lifetime #struct_name #generics>) #output {
let #struct_name { #(#field_names),* } = &cx.props;
#block
}

View file

@ -226,7 +226,7 @@ pub fn routable_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStrea
/// # Example
/// ```
/// #[inline_props]
/// fn app(cx: Scope<{ bob: String }>) -> Element {
/// fn app(cx: Scope, bob: String) -> Element {
/// cx.render(rsx!("hello, {bob}"))
/// }
///

View file

@ -609,7 +609,7 @@ impl ScopeState {
///
/// This is a "fundamental" operation and should only be called during initialization of a hook.
///
/// For a hook that provides the same functionality, use `use_provide_state` and `use_consume_state` instead.
/// For a hook that provides the same functionality, use `use_provide_context` and `use_consume_context` instead.
///
/// When the component is dropped, so is the context. Be aware of this behavior when consuming
/// the context via Rc/Weak.
@ -620,7 +620,7 @@ impl ScopeState {
/// struct SharedState(&'static str);
///
/// static App: Component<()> = |cx, props|{
/// cx.use_hook(|_| cx.provide_state(SharedState("world")), |_| {}, |_| {});
/// cx.use_hook(|_| cx.provide_context(SharedState("world")), |_| {}, |_| {});
/// rsx!(cx, Child {})
/// }
///
@ -629,7 +629,7 @@ impl ScopeState {
/// rsx!(cx, div { "hello {state.0}" })
/// }
/// ```
pub fn provide_state<T: 'static>(&self, value: T) {
pub fn provide_context<T: 'static>(&self, value: T) {
self.shared_contexts
.borrow_mut()
.insert(TypeId::of::<T>(), Rc::new(value))
@ -638,7 +638,7 @@ impl ScopeState {
}
/// Try to retrieve a SharedState with type T from the any parent Scope.
pub fn consume_state<T: 'static>(&self) -> Option<Rc<T>> {
pub fn consume_context<T: 'static>(&self) -> Option<Rc<T>> {
if let Some(shared) = self.shared_contexts.borrow().get(&TypeId::of::<T>()) {
Some(shared.clone().downcast::<T>().unwrap())
} else {

View file

@ -14,12 +14,12 @@ fn shared_state_test() {
struct MySharedState(&'static str);
static App: Component<()> = |cx| {
cx.provide_state(MySharedState("world!"));
cx.provide_context(MySharedState("world!"));
cx.render(rsx!(Child {}))
};
static Child: Component<()> = |cx| {
let shared = cx.consume_state::<MySharedState>()?;
let shared = cx.consume_context::<MySharedState>()?;
cx.render(rsx!("Hello, {shared.0}"))
};

View file

@ -1,29 +1,30 @@
//! Example: README.md showcase
//!
//! The example from the README.md.
use std::time::Duration;
use dioxus::prelude::*;
use dioxus_core as dioxus;
use dioxus_core_macro::*;
use dioxus_hooks::*;
use dioxus_html as dioxus_elements;
use std::time::Duration;
fn main() {
// simple_logger::init().unwrap();
dioxus_desktop::launch(app);
}
fn app(cx: Scope<()>) -> Element {
let mut count = use_state(&cx, || 0);
log::debug!("count is {:?}", count);
let count = use_state(&cx, || 0);
cx.push_future(|| async move {
tokio::time::sleep(Duration::from_millis(1000)).await;
println!("count is now {:?}", count);
count += 1;
});
// push the futureo on initialization
cx.use_hook(
|_| {
cx.push_future({
let count = count.for_async();
async move {
tokio::time::sleep(Duration::from_millis(1000)).await;
*count.get_mut() += 1;
}
});
},
|_| {},
);
cx.render(rsx! {
div {

View file

@ -59,11 +59,11 @@ impl<T> ProvidedStateInner<T> {
///
///
///
pub fn use_shared_state<'a, T: 'static>(cx: &'a ScopeState) -> Option<UseSharedState<'a, T>> {
pub fn use_context<'a, T: 'static>(cx: &'a ScopeState) -> Option<UseSharedState<'a, T>> {
cx.use_hook(
|_| {
let scope_id = cx.scope_id();
let root = cx.consume_state::<ProvidedState<T>>();
let root = cx.consume_context::<ProvidedState<T>>();
if let Some(root) = root.as_ref() {
root.borrow_mut().consumers.insert(scope_id);
@ -175,7 +175,7 @@ where
///
///
///
pub fn use_provide_state<'a, T: 'static>(cx: &'a ScopeState, f: impl FnOnce() -> T) {
pub fn use_context_provider<'a, T: 'static>(cx: &'a ScopeState, f: impl FnOnce() -> T) {
cx.use_hook(
|_| {
let state: ProvidedState<T> = RefCell::new(ProvidedStateInner {
@ -183,7 +183,7 @@ pub fn use_provide_state<'a, T: 'static>(cx: &'a ScopeState, f: impl FnOnce() ->
notify_any: cx.schedule_update_any(),
consumers: HashSet::new(),
});
cx.provide_state(state)
cx.provide_context(state)
},
|_inner| {},
)

View file

@ -0,0 +1 @@

View file

@ -2,6 +2,7 @@ use dioxus_core::prelude::*;
use dioxus_core_macro::*;
use dioxus_html as dioxus_elements;
use dioxus_router::*;
use serde::{Deserialize, Serialize};
fn main() {
console_error_panic_hook::set_once();
@ -10,18 +11,19 @@ fn main() {
}
static APP: Component<()> = |cx| {
#[derive(Clone, Debug, PartialEq)]
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
enum Route {
Home,
About,
NotFound,
}
impl Default for Route {
fn default() -> Self {
Route::Home
}
}
let route = use_router(&cx, |s| match s {
"/" => Route::Home,
"/about" => Route::About,
_ => Route::NotFound,
});
let route = use_router(&cx, |c| {});
cx.render(rsx! {
div {
@ -31,8 +33,8 @@ static APP: Component<()> = |cx| {
Route::NotFound => rsx!(h1 { "NotFound" }),
}}
nav {
Link { to: Route::Home, href: |_| "/".to_string() }
Link { to: Route::About, href: |_| "/about".to_string() }
Link { to: Route::Home, href: "/" }
Link { to: Route::About, href: "/about" }
}
}
})

View file

@ -1,72 +1,56 @@
//! Dioxus-Router
//!
//! A simple match-based router and router service for most routing needs.
//!
//! Dioxus-Router is not a *declarative* router. Instead it uses a simple parse-match
//! pattern which can be derived via a macro.
//!
//! ```rust
//! fn app(cx: Scope) -> Element {
//! let route = use_router(&cx, |svc, path| {
//! match path {
//! "/about" => Route::About,
//! _ => Route::Home,
//! }
//! });
//!
//! match route {
//! Route::Home => rsx!(cx, h1 { "Home" }),
//! Route::About => rsx!(cx, h1 { "About" }),
//! }
//! }
//!
//!
//!
//!
//!
//! ```
//!
//!
//!
//!
//!
//!
//!
//!
//!
mod link;
mod platform;
mod service;
mod userouter;
mod utils;
use std::{cell::RefCell, rc::Rc};
pub use link::*;
use serde::{de::DeserializeOwned, Deserialize, Serialize};
pub use service::*;
pub use userouter::*;
use dioxus::Attribute;
use dioxus_core as dioxus;
use dioxus_core::prelude::*;
use dioxus_core_macro::{format_args_f, rsx, Props};
use dioxus_html as dioxus_elements;
// use wasm_bindgen::{JsCast, JsValue};
use crate::utils::strip_slash_suffix;
/// Initialize the app's router service and provide access to `Link` components
pub fn use_router<R: 'static>(cx: &ScopeState, f: impl Fn(&str) -> R) -> &R {
let r = f("/");
cx.use_hook(
|_| {
//
r
},
|f| f,
)
pub trait Routable:
'static + Send + Clone + PartialEq + Serialize + DeserializeOwned + Default
{
}
pub trait Routable: 'static + Send + Clone + PartialEq {}
impl<T> Routable for T where T: 'static + Send + Clone + PartialEq {}
#[derive(Props)]
pub struct LinkProps<'a, R: Routable> {
to: R,
/// The url that gets pushed to the history stack
///
/// You can either put it your own inline method or just autoderive the route using `derive(Routable)`
///
/// ```rust
///
/// Link { to: Route::Home, href: |_| "home".to_string() }
///
/// // or
///
/// Link { to: Route::Home, href: Route::as_url }
///
/// ```
#[props(default, setter(strip_option))]
href: Option<&'a str>,
#[props(default, setter(strip_option))]
class: Option<&'a str>,
children: Element<'a>,
#[props(default)]
attributes: Option<&'a [Attribute<'a>]>,
}
pub fn Link<'a, R: Routable>(cx: Scope<'a, LinkProps<'a, R>>) -> Element {
// let service = todo!();
// let service: todo!() = use_router_service::<R>(&cx)?;
let class = cx.props.class.unwrap_or("");
cx.render(rsx! {
a {
href: "#",
class: "{class}",
{&cx.props.children}
// onclick: move |_| service.push_route(cx.props.to.clone()),
// href: format_args!("{}", (cx.props.href)(&cx.props.to)),
}
})
impl<T> Routable for T where
T: 'static + Send + Clone + PartialEq + Serialize + DeserializeOwned + Default
{
}

View file

@ -0,0 +1,48 @@
use crate::{Routable, RouterService};
use dioxus::Attribute;
use dioxus_core as dioxus;
use dioxus_core::prelude::*;
use dioxus_core_macro::{format_args_f, rsx, Props};
use dioxus_html as dioxus_elements;
#[derive(Props)]
pub struct LinkProps<'a, R: Routable> {
to: R,
/// The url that gets pushed to the history stack
///
/// You can either put it your own inline method or just autoderive the route using `derive(Routable)`
///
/// ```rust
///
/// Link { to: Route::Home, href: |_| "home".to_string() }
///
/// // or
///
/// Link { to: Route::Home, href: Route::as_url }
///
/// ```
#[props(default, setter(strip_option))]
href: Option<&'a str>,
#[props(default, setter(strip_option))]
class: Option<&'a str>,
children: Element<'a>,
#[props(default)]
attributes: Option<&'a [Attribute<'a>]>,
}
#[allow(non_snake_case)]
pub fn Link<'a, R: Routable>(cx: Scope<'a, LinkProps<'a, R>>) -> Element {
let service = cx.consume_context::<RouterService<R>>()?;
cx.render(rsx! {
a {
href: "#",
class: format_args!("{}", cx.props.class.unwrap_or("")),
{&cx.props.children}
onclick: move |_| service.push_route(cx.props.to.clone()),
}
})
}

View file

@ -1 +1,7 @@
pub fn parse_current_route() -> String {
todo!()
}
pub(crate) fn get_base_route() -> String {
todo!()
}

View file

@ -0,0 +1,27 @@
use crate::Routable;
use std::{cell::RefCell, rc::Rc};
pub struct RouterService<R: Routable> {
pub(crate) regen_route: Rc<dyn Fn()>,
pub(crate) pending_routes: RefCell<Vec<R>>,
}
impl<R: Routable> RouterService<R> {
pub fn current_path(&self) -> &str {
todo!()
}
pub fn push_route(&self, route: R) {
self.pending_routes.borrow_mut().push(route);
(self.regen_route)();
}
}
pub struct RouterCfg {
initial_route: String,
}
impl RouterCfg {
pub fn new(initial_route: String) -> Self {
Self { initial_route }
}
}

View file

@ -0,0 +1,36 @@
use std::{cell::RefCell, rc::Rc};
use crate::{Routable, RouterCfg, RouterService};
use dioxus_core::ScopeState;
/// Initialize the app's router service and provide access to `Link` components
pub fn use_router<'a, R: Routable>(cx: &'a ScopeState, cfg: impl FnOnce(&mut RouterCfg)) -> &'a R {
cx.use_hook(
|_| {
let svc: RouterService<R> = RouterService {
regen_route: cx.schedule_update(),
pending_routes: RefCell::new(Vec::new()),
};
let first_path = R::default();
cx.provide_context(svc);
UseRouterInner {
svc: cx.consume_context::<RouterService<R>>().unwrap(),
history: vec![first_path],
}
},
|f| {
let mut pending_routes = f.svc.pending_routes.borrow_mut();
for route in pending_routes.drain(..) {
f.history.push(route);
}
f.history.last().unwrap()
},
)
}
struct UseRouterInner<R: Routable> {
svc: Rc<RouterService<R>>,
history: Vec<R>,
}

View file

@ -144,7 +144,7 @@ static App: Component<()> = |cx| {
{items.read().iter().enumerate().map(|(id, item)| {
let [adj, col, noun] = item.labels;
let is_in_danger = if (*selected).map(|s| s == id).unwrap_or(false) {"danger"} else {""};
rsx!(tr {
rsx!(tr {
class: "{is_in_danger}",
key: "{id}",
td { class:"col-md-1" }

View file

@ -246,7 +246,6 @@ impl WebsysDom {
.unwrap(),
};
use smallstr;
use smallstr::SmallString;
use std::fmt::Write;
@ -260,7 +259,7 @@ impl WebsysDom {
self.nodes[(id as usize)] = Some(el);
}
fn new_event_listener(&mut self, event: &'static str, scope: ScopeId, real_id: u64) {
fn new_event_listener(&mut self, event: &'static str, _scope: ScopeId, _real_id: u64) {
let event = wasm_bindgen::intern(event);
// attach the correct attributes to the element
@ -271,24 +270,9 @@ impl WebsysDom {
let el = self.stack.top();
let el = el.dyn_ref::<Element>().unwrap();
// let el = el.dyn_ref::<Element>().unwrap();
// .expect(&format!("not an element: {:?}", el));
// let scope_id = scope.data().as_ffi();
// let scope_id = scope.0 as u64;
// "dioxus-event-click",
// "1.10"
// &format!("", scope_id, real_id),
// &format!("dioxus-event-{}", event),
// &format!("{}.{}", scope_id, real_id),
// &format!("dioxus-event-{}", event),
// &format!("{}.{}", scope_id, real_id),
el.set_attribute("dioxus-event", event).unwrap();
// el.set_attribute(&format!("dioxus-event"), &format!("{}", event))
// .unwrap();
// Register the callback to decode
if let Some(entry) = self.listeners.get_mut(event) {
@ -487,20 +471,17 @@ impl Stack {
}
pub struct DioxusWebsysEvent(web_sys::Event);
// safety: currently the web is not multithreaded and our VirtualDom exists on the same thread
unsafe impl Send for DioxusWebsysEvent {}
unsafe impl Sync for DioxusWebsysEvent {}
// trait MyTrait {}
// impl MyTrait for web_sys::Event {}
// todo: some of these events are being casted to the wrong event type.
// We need tests that simulate clicks/etc and make sure every event type works.
fn virtual_event_from_websys_event(event: web_sys::Event) -> Arc<dyn Any + Send + Sync> {
use dioxus_html::on::*;
use dioxus_html::KeyCode;
// event.prevent_default();
// use dioxus_core::events::on::*;
match event.type_().as_str() {
"copy" | "cut" | "paste" => Arc::new(ClipboardEvent {}),
"compositionend" | "compositionstart" | "compositionupdate" => {
@ -525,11 +506,7 @@ fn virtual_event_from_websys_event(event: web_sys::Event) -> Arc<dyn Any + Send
which: evt.which() as usize,
})
}
"focus" | "blur" => {
//
Arc::new(FocusEvent {})
}
// "change" => SyntheticEvent::GenericEvent(DioxusEvent::new((), DioxusWebsysEvent(event))),
"focus" | "blur" => Arc::new(FocusEvent {}),
// todo: these handlers might get really slow if the input box gets large and allocation pressure is heavy
// don't have a good solution with the serialized event problem
@ -623,7 +600,6 @@ fn virtual_event_from_websys_event(event: web_sys::Event) -> Arc<dyn Any + Send
})
}
"select" => Arc::new(SelectionEvent {}),
"touchcancel" | "touchend" | "touchmove" | "touchstart" => {
let evt: &web_sys::TouchEvent = event.dyn_ref().unwrap();
Arc::new(TouchEvent {
@ -633,9 +609,7 @@ fn virtual_event_from_websys_event(event: web_sys::Event) -> Arc<dyn Any + Send
shift_key: evt.shift_key(),
})
}
"scroll" => Arc::new(()),
"wheel" => {
let evt: &web_sys::WheelEvent = event.dyn_ref().unwrap();
Arc::new(WheelEvent {
@ -645,7 +619,6 @@ fn virtual_event_from_websys_event(event: web_sys::Event) -> Arc<dyn Any + Send
delta_mode: evt.delta_mode(),
})
}
"animationstart" | "animationend" | "animationiteration" => {
let evt: &web_sys::AnimationEvent = event.dyn_ref().unwrap();
Arc::new(AnimationEvent {
@ -654,7 +627,6 @@ fn virtual_event_from_websys_event(event: web_sys::Event) -> Arc<dyn Any + Send
pseudo_element: evt.pseudo_element(),
})
}
"transitionend" => {
let evt: &web_sys::TransitionEvent = event.dyn_ref().unwrap();
Arc::new(TransitionEvent {
@ -663,20 +635,11 @@ fn virtual_event_from_websys_event(event: web_sys::Event) -> Arc<dyn Any + Send
pseudo_element: evt.pseudo_element(),
})
}
"abort" | "canplay" | "canplaythrough" | "durationchange" | "emptied" | "encrypted"
| "ended" | "error" | "loadeddata" | "loadedmetadata" | "loadstart" | "pause" | "play"
| "playing" | "progress" | "ratechange" | "seeked" | "seeking" | "stalled" | "suspend"
| "timeupdate" | "volumechange" | "waiting" => {
//
Arc::new(MediaEvent {})
}
"toggle" => {
//
Arc::new(ToggleEvent {})
}
| "timeupdate" | "volumechange" | "waiting" => Arc::new(MediaEvent {}),
"toggle" => Arc::new(ToggleEvent {}),
_ => Arc::new(()),
}
}
@ -684,6 +647,8 @@ fn virtual_event_from_websys_event(event: web_sys::Event) -> Arc<dyn Any + Send
/// This function decodes a websys event and produces an EventTrigger
/// With the websys implementation, we attach a unique key to the nodes
fn decode_trigger(event: &web_sys::Event) -> anyhow::Result<UserEvent> {
use anyhow::Context;
let target = event
.target()
.expect("missing target")
@ -692,42 +657,16 @@ fn decode_trigger(event: &web_sys::Event) -> anyhow::Result<UserEvent> {
let typ = event.type_();
use anyhow::Context;
let element_id = target
.get_attribute("dioxus-id")
.context("Could not find element id on event target")?
.parse()?;
// The error handling here is not very descriptive and needs to be replaced with a zero-cost error system
let val: String = target
.get_attribute("dioxus-event")
.context(format!("wrong format - received {:#?}", typ))?;
// .get_attribute(&format!("dioxus-event-{}", typ))
// .context(format!("wrong format - received {:#?}", typ))?;
let mut fields = val.splitn(3, ".");
// let gi_id = fields
// .next()
// .and_then(|f| f.parse::<u64>().ok())
// .context("failed to parse gi id")?;
// let real_id = fields
// .next()
// .and_then(|raw_id| raw_id.parse::<u64>().ok())
// .context("failed to parse real id")?;
// let triggered_scope = gi_id;
Ok(UserEvent {
name: event_name_from_typ(&typ),
data: virtual_event_from_websys_event(event.clone()),
element: Some(ElementId(element_id)),
scope_id: None,
// scope_id: Some(ScopeId(triggered_scope as usize)),
// element: Some(ElementId(real_id as usize)),
// scope_id: Some(ScopeId(triggered_scope as usize)),
priority: dioxus_core::EventPriority::Medium,
})
}