From f9a783c2caa867c7243d3f043b63ae375414d320 Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Sat, 25 Apr 2020 17:01:50 +0800 Subject: [PATCH] Upstream yewtil crate (#1119) * Upstream yewtil crate * cargo fmt * cargo clippy * cargo fmt --- Cargo.toml | 13 + yewtil-macro/Cargo.toml | 15 + yewtil-macro/src/function_component.rs | 194 ++++ yewtil-macro/src/lib.rs | 10 + yewtil/CHANGELOG.md | 20 + yewtil/Cargo.toml | 68 ++ yewtil/README.md | 96 ++ yewtil/examples/dsl/Cargo.toml | 12 + yewtil/examples/dsl/src/main.rs | 49 + yewtil/examples/effect/Cargo.toml | 11 + yewtil/examples/effect/src/main.rs | 49 + yewtil/examples/fetch/.cargo/config | 3 + yewtil/examples/fetch/Cargo.toml | 16 + yewtil/examples/fetch/Readme.md | 9 + yewtil/examples/fetch/index.html | 10 + yewtil/examples/fetch/main.js | 6 + yewtil/examples/fetch/src/lib.rs | 122 ++ yewtil/examples/function_component/Cargo.toml | 14 + .../examples/function_component/src/button.rs | 13 + .../examples/function_component/src/main.rs | 45 + yewtil/examples/futures/Cargo.toml | 28 + yewtil/examples/futures/README.md | 14 + yewtil/examples/futures/index.html | 10 + yewtil/examples/futures/main.js | 6 + yewtil/examples/futures/src/lib.rs | 125 ++ yewtil/examples/history/Cargo.toml | 12 + yewtil/examples/history/src/main.rs | 64 ++ yewtil/examples/lrc/Cargo.toml | 12 + yewtil/examples/lrc/src/child.rs | 61 + yewtil/examples/lrc/src/main.rs | 59 + yewtil/examples/mrc_irc/Cargo.toml | 12 + yewtil/examples/mrc_irc/src/child.rs | 47 + yewtil/examples/mrc_irc/src/main.rs | 61 + yewtil/examples/pure_component/Cargo.toml | 12 + yewtil/examples/pure_component/src/button.rs | 21 + yewtil/examples/pure_component/src/main.rs | 45 + yewtil/src/dsl.rs | 59 + yewtil/src/dsl/vcomp.rs | 19 + yewtil/src/dsl/vlist.rs | 35 + yewtil/src/dsl/vtag.rs | 75 ++ yewtil/src/dsl/vtext.rs | 17 + yewtil/src/effect.rs | 64 ++ yewtil/src/fetch.rs | 279 +++++ yewtil/src/fetch/action.rs | 72 ++ yewtil/src/fetch/error.rs | 54 + yewtil/src/fetch/request.rs | 220 ++++ yewtil/src/fetch/state.rs | 60 + yewtil/src/future.rs | 78 ++ yewtil/src/history.rs | 200 ++++ yewtil/src/lib.rs | 50 + yewtil/src/not_equal_assign.rs | 59 + yewtil/src/ptr/irc.rs | 192 ++++ yewtil/src/ptr/lrc.rs | 1024 +++++++++++++++++ yewtil/src/ptr/mod.rs | 21 + yewtil/src/ptr/mrc.rs | 331 ++++++ yewtil/src/ptr/rc_box.rs | 117 ++ yewtil/src/ptr/takeable.rs | 39 + yewtil/src/pure.rs | 63 + 58 files changed, 4492 insertions(+) create mode 100644 yewtil-macro/Cargo.toml create mode 100644 yewtil-macro/src/function_component.rs create mode 100644 yewtil-macro/src/lib.rs create mode 100644 yewtil/CHANGELOG.md create mode 100644 yewtil/Cargo.toml create mode 100644 yewtil/README.md create mode 100644 yewtil/examples/dsl/Cargo.toml create mode 100644 yewtil/examples/dsl/src/main.rs create mode 100644 yewtil/examples/effect/Cargo.toml create mode 100644 yewtil/examples/effect/src/main.rs create mode 100644 yewtil/examples/fetch/.cargo/config create mode 100644 yewtil/examples/fetch/Cargo.toml create mode 100644 yewtil/examples/fetch/Readme.md create mode 100644 yewtil/examples/fetch/index.html create mode 100644 yewtil/examples/fetch/main.js create mode 100644 yewtil/examples/fetch/src/lib.rs create mode 100644 yewtil/examples/function_component/Cargo.toml create mode 100644 yewtil/examples/function_component/src/button.rs create mode 100644 yewtil/examples/function_component/src/main.rs create mode 100644 yewtil/examples/futures/Cargo.toml create mode 100644 yewtil/examples/futures/README.md create mode 100644 yewtil/examples/futures/index.html create mode 100644 yewtil/examples/futures/main.js create mode 100644 yewtil/examples/futures/src/lib.rs create mode 100644 yewtil/examples/history/Cargo.toml create mode 100644 yewtil/examples/history/src/main.rs create mode 100644 yewtil/examples/lrc/Cargo.toml create mode 100644 yewtil/examples/lrc/src/child.rs create mode 100644 yewtil/examples/lrc/src/main.rs create mode 100644 yewtil/examples/mrc_irc/Cargo.toml create mode 100644 yewtil/examples/mrc_irc/src/child.rs create mode 100644 yewtil/examples/mrc_irc/src/main.rs create mode 100644 yewtil/examples/pure_component/Cargo.toml create mode 100644 yewtil/examples/pure_component/src/button.rs create mode 100644 yewtil/examples/pure_component/src/main.rs create mode 100644 yewtil/src/dsl.rs create mode 100644 yewtil/src/dsl/vcomp.rs create mode 100644 yewtil/src/dsl/vlist.rs create mode 100644 yewtil/src/dsl/vtag.rs create mode 100644 yewtil/src/dsl/vtext.rs create mode 100644 yewtil/src/effect.rs create mode 100644 yewtil/src/fetch.rs create mode 100644 yewtil/src/fetch/action.rs create mode 100644 yewtil/src/fetch/error.rs create mode 100644 yewtil/src/fetch/request.rs create mode 100644 yewtil/src/fetch/state.rs create mode 100644 yewtil/src/future.rs create mode 100644 yewtil/src/history.rs create mode 100644 yewtil/src/lib.rs create mode 100644 yewtil/src/not_equal_assign.rs create mode 100644 yewtil/src/ptr/irc.rs create mode 100644 yewtil/src/ptr/lrc.rs create mode 100644 yewtil/src/ptr/mod.rs create mode 100644 yewtil/src/ptr/mrc.rs create mode 100644 yewtil/src/ptr/rc_box.rs create mode 100644 yewtil/src/ptr/takeable.rs create mode 100644 yewtil/src/pure.rs diff --git a/Cargo.toml b/Cargo.toml index bf1f03a01..7aa3f0b60 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,19 @@ members = [ "yew-router/examples/router_component", "yew-router/examples/switch", + # Utilities + "yewtil", + "yewtil-macro", + "yewtil/examples/pure_component", + # "yewtil/examples/dsl", + "yewtil/examples/lrc", + "yewtil/examples/history", + "yewtil/examples/mrc_irc", + "yewtil/examples/effect", + "yewtil/examples/fetch", + "yewtil/examples/futures", + "yewtil/examples/function_component", + # Examples "examples/counter", "examples/crm", diff --git a/yewtil-macro/Cargo.toml b/yewtil-macro/Cargo.toml new file mode 100644 index 000000000..bc7bccebc --- /dev/null +++ b/yewtil-macro/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "yewtil-macro" +version = "0.1.0" +authors = ["Henry Zimmerman "] +edition = "2018" +license = "MIT/Apache-2.0" +description = "Macros to be re-exported from the yewtil crate" + +[lib] +proc-macro = true + +[dependencies] +proc-macro2 = "1.0.6" +quote = "1.0.2" +syn = { version = "1.0.11", features = ["full", "extra-traits"] } diff --git a/yewtil-macro/src/function_component.rs b/yewtil-macro/src/function_component.rs new file mode 100644 index 000000000..3b401fdaa --- /dev/null +++ b/yewtil-macro/src/function_component.rs @@ -0,0 +1,194 @@ +use proc_macro::TokenStream as TokenStream1; +use proc_macro2::{Ident, Span, TokenStream}; +use quote::quote; +use syn::export::ToTokens; +use syn::parse::{Parse, ParseBuffer}; +use syn::parse_macro_input; +use syn::punctuated::Punctuated; +use syn::token; +use syn::Token; +use syn::{braced, parenthesized}; +use syn::{Block, Error, Field, Stmt, Type, VisPublic, Visibility}; + +pub fn function_component_handler(attr: TokenStream, item: TokenStream1) -> TokenStream1 { + let component_name = attr.to_string(); + assert!( + !component_name.is_empty(), + "you must provide a component name. eg: function_component(MyComponent)" + ); + let component_name = Ident::new(&component_name, Span::call_site()); + let function = parse_macro_input!(item as Function); + TokenStream1::from( + FunctionComponentInfo { + component_name, + function, + } + .to_token_stream(), + ) +} + +pub struct FunctionComponentInfo { + component_name: Ident, + function: Function, +} + +// TODO, support type parameters + +pub struct Function { + pub vis: Visibility, + pub fn_token: Token![fn], + pub name: Ident, + pub paren_token: token::Paren, + pub fields: Punctuated, + pub returns_token: Token![->], + pub return_ty: Ident, + pub brace_token: token::Brace, + pub body: Vec, +} + +impl Parse for Function { + fn parse(input: &ParseBuffer) -> Result { + let vis = input.parse()?; + let fn_token = input.parse()?; + let name = input.parse()?; + let content; + let paren_token = parenthesized!(content in input); + let returns_token = input.parse()?; + let return_ty = input.parse()?; + let content2; + let brace_token = braced!(content2 in input); + Ok(Function { + vis, + fn_token, + name, + paren_token, + fields: content.parse_terminated(Field::parse_named)?, + returns_token, + return_ty, + brace_token, + body: content2.call(Block::parse_within)?, + }) + } +} + +impl ToTokens for Function { + fn to_tokens(&self, tokens: &mut TokenStream) { + let Function { + vis, + fn_token, + name, + fields, + returns_token, + return_ty, + body, + .. + } = self; + let fields = fields + .iter() + .map(|field: &Field| { + let mut new_field: Field = field.clone(); + new_field.attrs = vec![]; + new_field + }) + .collect::>(); + + tokens.extend(quote! { + #vis #fn_token #name(#fields) #returns_token #return_ty { + #(#body)* + } + }) + } +} + +impl ToTokens for FunctionComponentInfo { + fn to_tokens(&self, tokens: &mut TokenStream) { + let FunctionComponentInfo { + component_name, + function, + } = self; + // The function tokens must be re-generated in order to strip the attributes that are not allowed. + let function_token_stream = function.to_token_stream(); + let Function { + vis, name, fields, .. + } = function; + + let impl_name = format!("FuncComp{}", component_name.to_string()); + let impl_name = Ident::new(&impl_name, Span::call_site()); + + let alias = quote! { + #vis type #component_name = ::yewtil::Pure<#impl_name>; + }; + + // Set the fields to be public and strips references as necessary. + // This will preserve attributes like #[props(required)], which will appear in the generated struct below. + let new_fields = fields + .iter() + .map(|field: &Field| { + let mut new_field: Field = field.clone(); + let visibility = Visibility::Public(VisPublic { + pub_token: syn::token::Pub { + span: Span::call_site(), + }, + }); + // Strip references so the component can have a static lifetime. + // TODO Handle 'static lifetimes gracefully here - allowing &'static strings instead of erroneously converting them to plain strs. + let ty = match &field.ty { + Type::Reference(x) => { + let elem = x.elem.clone(); + Type::Verbatim(quote! { + #elem + }) + } + x => x.clone(), + }; + new_field.vis = visibility; + new_field.ty = ty; + new_field + }) + .collect::>(); + + let component_struct = quote! { + #[derive(::std::clone::Clone, ::std::cmp::PartialEq, ::yew::Properties)] + #vis struct #impl_name { + #new_fields + } + }; + + let arguments = fields + .iter() + .zip(new_fields.iter()) + .map(|(field, new_field): (&Field, &Field)| { + let field_name = field.ident.as_ref().expect("Field must have name"); + + // If the fields differ, then a reference was removed from the function's field's type + // to make it static. + // Otherwise it is assumed that the type is not a reference on the function and it + // implements clone, and that when calling the function, the type should be cloned again. + if field.ty != new_field.ty { + quote! { + &self.#field_name + } + } else { + quote! { + self.#field_name.clone() + } + } + }) + .collect::>(); + + let pure_component_impl = quote! { + impl ::yewtil::PureComponent for #impl_name { + fn render(&self) -> ::yew::Html { + #name(#arguments) + } + } + }; + + tokens.extend(quote! { + #function_token_stream + #alias + #component_struct + #pure_component_impl + }) + } +} diff --git a/yewtil-macro/src/lib.rs b/yewtil-macro/src/lib.rs new file mode 100644 index 000000000..49d28e443 --- /dev/null +++ b/yewtil-macro/src/lib.rs @@ -0,0 +1,10 @@ +extern crate proc_macro; +use proc_macro::TokenStream; + +use crate::function_component::function_component_handler; + +mod function_component; +#[proc_macro_attribute] +pub fn function_component(attr: TokenStream, item: TokenStream) -> TokenStream { + function_component_handler(attr.into(), item) +} diff --git a/yewtil/CHANGELOG.md b/yewtil/CHANGELOG.md new file mode 100644 index 000000000..5cf818579 --- /dev/null +++ b/yewtil/CHANGELOG.md @@ -0,0 +1,20 @@ +# Changelog + + + +## ✨ **v0.2.0** *11/18/19* +- #### ⚡️ Features + - Add new `FetchRequest` trait, `fetch_resource()` function, and `FetchState` enum + to simplify making fetch requests using futures. + - Add `Default` implementations to `Irc` and `Mrc`. diff --git a/yewtil/Cargo.toml b/yewtil/Cargo.toml new file mode 100644 index 000000000..65dac24d4 --- /dev/null +++ b/yewtil/Cargo.toml @@ -0,0 +1,68 @@ +[package] +name = "yewtil" +version = "0.2.0" +authors = ["Henry Zimmerman "] +edition = "2018" +description = "Utility crate for Yew" +license = "MIT/Apache-2.0" +repository = "https://github.com/yewstack/yewtil" +readme = "Readme.md" + +[features] +default = ["stable"] # Only stable is included by default. +all = ["stable", "experimental"] + +# Broad features +## All features MUST be stable or experimental +stable = ["neq", "pure", "history", "mrc_irc", "effect", "future"] +experimental = ["dsl", "lrc", "with_callback", "fetch" ] + +# Some pointers are stable, some experimental. +# This makes sure you get all the pointers +ptr = ["lrc", "mrc_irc"] + +# Misc features +neq = [] +pure = ["neq", "yewtil-macro"] +with_callback = [] +history = [] +dsl = [] +effect = [] +fetch = ["serde", "serde_json", "neq", "future"] +future = ["wasm-bindgen-futures", "wasm-bindgen", "stdweb", "futures", "web-sys"] + +# Ptr features +lrc = [] +mrc_irc = [] + +[dependencies] +futures = {version = "0.3.1", optional = true} +log = "0.4.8" +serde = {version= "1.0.102", optional = true} +serde_json = { version = "1.0.41", optional = true } +wasm-bindgen = {version = "0.2.51", features=["serde-serialize"], optional = true} +wasm-bindgen-futures = {version = "0.4.3", optional = true} +yew = { path = "../yew" } +yewtil-macro = { path = "../yewtil-macro", optional = true } + +[dependencies.stdweb] +version = "0.4.20" +optional = true +features = [ + "futures-support", + "experimental_features_which_may_break_on_minor_version_bumps", +] + +[dependencies.web-sys] +version = "0.3.31" +optional = true +features = [ + 'Headers', + 'Request', + 'RequestInit', + 'RequestMode', + 'Response', + 'Window', + 'Location', + 'Storage', +] \ No newline at end of file diff --git a/yewtil/README.md b/yewtil/README.md new file mode 100644 index 000000000..7169446b2 --- /dev/null +++ b/yewtil/README.md @@ -0,0 +1,96 @@ +# Yewtil +Utility crate for the [Yew](https://github.com/yewstack/yew) frontend web framework. + +## Purpose +Provide a place for commonly used utilities for Yew to reside without them having to be included in the Yew crate itself. +As a consequence of this, the Yew crate is free to make changes that may cause breakages in this crate. + +## Features +Currently, this crate supports these features in a stable capacity: +* `NeqAssign` - makes assigning props and returning a relevant ShouldRender value easier. +* Pure Components - implement pure components using the `PureComponent` trait and the `Pure` Component adaptor. +This should make it much easier to define simple components that don't hold state. + * Function components - a macro that takes a function that returns `Html` and converts it to a pure component. +* `Mrc`/`Irc` smart pointers - Rc-like pointers that are more ergonomic to use within Yew. +* `History` - A wrapper that holds the history of values that have been assigned to it. +* `Effect` - A way to update component state by defining what to change inside of `html!` callbacks + instead of handling messages in `Component::update()`. + +This crate also has an experimental feature flag that enables the following features: +* `Lrc` smart pointer - Rc-like pointer implemented on top of a linked list. Allows for novel state update mechanics +and traversal over linked shared pointers. (This needs to be fuzz tested to make sure it doesn't leak.) +* DSL for `Html` - A function-based domain-specific-language for Yew that can be used in a limited capacity instead of the `html!` macro. (Broken by recent changes in yew. Will be rewritten from scratch eventually.) + +These experimental features are either not sufficiently vetted, may change significantly, or may be removed. + +## Example Projects +Examples for every stable feature exist [here](https://github.com/yewstack/yew/tree/master/yewtil/examples). + +Check out the [Pure Components example](https://github.com/yewstack/yew/tree/master/yewtil/examples/demo) to see how Pure Components work. + +## Example +#### neq_assign: +```rust +fn change(&mut self, props: Self::Properties) -> ShouldRender { + self.props.neq_assign(props) +} +``` + +------------- + +#### Pure Component: +```rust +pub type Button = Pure; + +#[derive(PartialEq, Clone, Properties)] +pub struct PureButton { + pub callback: Callback, + #[prop_or_default] + pub text: String, +} + +impl PureComponent for PureButton { + fn render(&self) -> VNode { + html! { + + } + } +} +``` + +-------------- + +#### History +```rust +pub struct Model { + text: History, +} + +// ... +fn update(&mut self, msg: Self::Message) -> ShouldRender { + match msg { + Msg::SetText(text) => self.text.neq_set(text), + Msg::Reset => self.text.reset(), + Msg::Forget => { + self.text.forget(); + false + } + } +} +``` + +## Update Schedule +This crate will target stable Yew. + +As new idioms are introduced to Yew, this crate may see updates, but given the rarity of those, this crate may sit unaltered for some time. + +## Scoping +This crate aims to be more permissive in what is allowed in than Yew, so if you have a function, type, or trait you would like to share, please open a PR or Issue. + +Components are welcome as well, but they must not have external dependencies, should solve some problem encountered my many users of Yew, and should allow for theming if possible, like an auto-scrolling wrapper, a RecyclerView/Infinite-scrolling component, or possibly a comprehensive Input component. + +Common UI elements like modals or dropdowns should probably best be left to CSS-framework component libraries, as they should often be coupled to the external CSS used to display them. + +### Stability +Since this crate aims to present a variety of helper types, traits, and functions, where the utility of each may be unknown at the time the feature is added, newer additions may be not be included in the default feature-set, and may be locked behind an `experimental` flag. +While in early development, features marked as `experimental` may be changed more frequently or even entirely removed, while those marked as `stable` will not be removed and can be depended on to not change significantly. diff --git a/yewtil/examples/dsl/Cargo.toml b/yewtil/examples/dsl/Cargo.toml new file mode 100644 index 000000000..451be56ac --- /dev/null +++ b/yewtil/examples/dsl/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "dsl" +version = "0.1.0" +authors = ["Henry Zimmerman "] +edition = "2018" +license = "MIT/Apache-2.0" + +[dependencies] +log = "0.4.8" +web_logger = "0.2.0" +yew = { path = "../../../yew" } +yewtil = { path = "../..", features = ["dsl"] } diff --git a/yewtil/examples/dsl/src/main.rs b/yewtil/examples/dsl/src/main.rs new file mode 100644 index 000000000..cf6ba2e87 --- /dev/null +++ b/yewtil/examples/dsl/src/main.rs @@ -0,0 +1,49 @@ +use yew::{Component, ComponentLink, Html, ShouldRender}; + +use yewtil::dsl::{list, populated_list, tag, text, BoxedVNodeProducer}; + +pub struct Model {} + +pub enum Msg { + DoIt, +} + +impl Component for Model { + type Message = Msg; + type Properties = (); + + fn create(_: Self::Properties, _: ComponentLink) -> Self { + Model {} + } + + fn update(&mut self, msg: Self::Message) -> ShouldRender { + match msg { + Msg::DoIt => { + log::info!("got message"); + true + } + } + } + + fn change(&mut self, _props: ()) -> ShouldRender { + false + } + + fn view(&self) -> Html { + BoxedVNodeProducer::from( + list() + .child(text("Hello there")) + .child(tag("p").child(text("Paragraph content"))) + .child(populated_list(vec![ + tag("b").child(text("Bolded")).into(), + text("Normal text").into(), + ])), + ) + .build() + } +} + +fn main() { + web_logger::init(); + yew::start_app::(); +} diff --git a/yewtil/examples/effect/Cargo.toml b/yewtil/examples/effect/Cargo.toml new file mode 100644 index 000000000..f67ab96ec --- /dev/null +++ b/yewtil/examples/effect/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "effect" +version = "0.1.0" +authors = ["Henry Zimmerman "] +edition = "2018" + +[dependencies] +log = "0.4.8" +web_logger = "0.2.0" +yew = { path = "../../../yew" } +yewtil = { path = "../..", features = ["effect"] } diff --git a/yewtil/examples/effect/src/main.rs b/yewtil/examples/effect/src/main.rs new file mode 100644 index 000000000..e55d51c3f --- /dev/null +++ b/yewtil/examples/effect/src/main.rs @@ -0,0 +1,49 @@ +use yew::{html, Component, ComponentLink, Html, ShouldRender}; +use yewtil::{effect, Effect}; + +pub struct Model { + value: bool, + link: ComponentLink, +} + +impl Component for Model { + type Message = Effect; + type Properties = (); + + fn create(_: Self::Properties, link: ComponentLink) -> Self { + Model { value: false, link } + } + + fn update(&mut self, msg: Self::Message) -> ShouldRender { + msg.call(self) + } + + fn change(&mut self, _props: ()) -> ShouldRender { + false + } + + fn view(&self) -> Html { + html! { + <> +
+ {self.value} +
+
+ +
+ + } + } +} + +fn main() { + web_logger::init(); + yew::start_app::(); +} diff --git a/yewtil/examples/fetch/.cargo/config b/yewtil/examples/fetch/.cargo/config new file mode 100644 index 000000000..595cadf28 --- /dev/null +++ b/yewtil/examples/fetch/.cargo/config @@ -0,0 +1,3 @@ +# Allow cargo-check to work as expected. +[build] +target = "wasm32-unknown-unknown" diff --git a/yewtil/examples/fetch/Cargo.toml b/yewtil/examples/fetch/Cargo.toml new file mode 100644 index 000000000..930b28b09 --- /dev/null +++ b/yewtil/examples/fetch/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "fetch" +version = "0.1.0" +authors = ["Henry Zimmerman "] +edition = "2018" + +[lib] +crate-type = ["cdylib", "rlib"] + +[dependencies] +log = "0.4.8" +serde = "1.0.102" +wasm-bindgen = "0.2.51" +web_logger = "0.2.0" +yew = { path = "../../../yew" } +yewtil = { path = "../..", features = ["fetch"] } diff --git a/yewtil/examples/fetch/Readme.md b/yewtil/examples/fetch/Readme.md new file mode 100644 index 000000000..143e37f6e --- /dev/null +++ b/yewtil/examples/fetch/Readme.md @@ -0,0 +1,9 @@ +Shows off ergonomic JSON deserialization fetch abstraction. + +Run with: + +```shell script +wasm-pack build --target web && rollup ./main.js --format iife --file ./pkg/bundle.js && python -m SimpleHTTPServer 8080 +``` + +It is expected that you have a setup with wasm-pack, rollup, and python installed. diff --git a/yewtil/examples/fetch/index.html b/yewtil/examples/fetch/index.html new file mode 100644 index 000000000..c8017c100 --- /dev/null +++ b/yewtil/examples/fetch/index.html @@ -0,0 +1,10 @@ + + + + + Yewtil • Fetch + + + + + \ No newline at end of file diff --git a/yewtil/examples/fetch/main.js b/yewtil/examples/fetch/main.js new file mode 100644 index 000000000..a64560d4c --- /dev/null +++ b/yewtil/examples/fetch/main.js @@ -0,0 +1,6 @@ +import init, { run_app } from './pkg/fetch.js'; +async function main() { + await init('./pkg/fetch_bg.wasm'); + run_app(); +} +main() \ No newline at end of file diff --git a/yewtil/examples/fetch/src/lib.rs b/yewtil/examples/fetch/src/lib.rs new file mode 100644 index 000000000..6abb917bb --- /dev/null +++ b/yewtil/examples/fetch/src/lib.rs @@ -0,0 +1,122 @@ +use crate::Msg::SetMarkdownFetchState; +use serde::{Deserialize, Serialize}; +use wasm_bindgen::prelude::*; +use yew::{html, Component, ComponentLink, Html, ShouldRender}; +use yewtil::fetch::{Fetch, FetchAction, FetchRequest, FetchState, Json, MethodBody}; +use yewtil::future::LinkFuture; + +#[wasm_bindgen] +pub fn run_app() { + yew::start_app::(); +} + +struct Model { + markdown: Fetch>, + link: ComponentLink, +} + +#[derive(Default, Debug, Clone)] +pub struct Request; +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct Employee { + id: String, + employee_name: String, + employee_salary: String, + employee_age: String, + profile_image: String, +} + +impl FetchRequest for Request { + type RequestBody = (); + type ResponseBody = Vec; + type Format = Json; + + fn url(&self) -> String { + // Given that this is an external resource, this may fail sometime in the future. + // Please report any regressions related to this. + "http://dummy.restapiexample.com/api/v1/employees".to_string() + } + + fn method(&self) -> MethodBody { + MethodBody::Get + } + + fn headers(&self) -> Vec<(String, String)> { + vec![] + } + + fn use_cors(&self) -> bool { + true + } +} + +enum Msg { + SetMarkdownFetchState(FetchAction>), + GetMarkdown, +} + +impl Component for Model { + // Some details omitted. Explore the examples to see more. + + type Message = Msg; + type Properties = (); + + fn create(_: Self::Properties, link: ComponentLink) -> Self { + Model { + markdown: Default::default(), + link, + } + } + + fn update(&mut self, msg: Self::Message) -> ShouldRender { + match msg { + Msg::SetMarkdownFetchState(fetch_state) => { + self.markdown.apply(fetch_state); + true + } + Msg::GetMarkdown => { + self.link + .send_future(self.markdown.fetch(Msg::SetMarkdownFetchState)); + self.link + .send_message(SetMarkdownFetchState(FetchAction::Fetching)); + false + } + } + } + + fn change(&mut self, _props: Self::Properties) -> ShouldRender { + false + } + + fn view(&self) -> Html { + match self.markdown.as_ref().state() { + FetchState::NotFetching(_) => { + html! {} + } + FetchState::Fetching(_) => html! {"Fetching"}, + FetchState::Fetched(data) => data.iter().map(render_employee).collect(), + FetchState::Failed(_, err) => html! {&err}, + } + } +} + +fn render_employee(e: &Employee) -> Html { + html! { +
+
+ {"Name: "} + {&e.employee_name} +
+
+ {"Salary: "} + {&e.employee_salary} +
+ +
+ {"Age: "} + {&e.employee_age} +
+
+
+ } +} diff --git a/yewtil/examples/function_component/Cargo.toml b/yewtil/examples/function_component/Cargo.toml new file mode 100644 index 000000000..1bd7be467 --- /dev/null +++ b/yewtil/examples/function_component/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "function_component" +version = "0.1.0" +authors = ["Henry Zimmerman "] +edition = "2018" + +[dependencies] +log = "0.4.8" +web_logger = "0.2.0" +yew = { path = "../../../yew" } +yewtil = { path = "../.." } + +[target.'cfg(all(target_arch = "wasm32", not(cargo_web)))'.dependencies] +wasm-bindgen = "0.2.51" \ No newline at end of file diff --git a/yewtil/examples/function_component/src/button.rs b/yewtil/examples/function_component/src/button.rs new file mode 100644 index 000000000..4d72b2b57 --- /dev/null +++ b/yewtil/examples/function_component/src/button.rs @@ -0,0 +1,13 @@ +use yew::{html, Callback, Html, MouseEvent}; +use yewtil::function_component; + +#[function_component(Button)] +pub fn button( + callback: &Callback, + #[prop_or_default] text: String, + #[prop_or_default] _num: usize, +) -> Html { + html! { + + } +} diff --git a/yewtil/examples/function_component/src/main.rs b/yewtil/examples/function_component/src/main.rs new file mode 100644 index 000000000..ea71806cb --- /dev/null +++ b/yewtil/examples/function_component/src/main.rs @@ -0,0 +1,45 @@ +use yew::{html, Component, ComponentLink, Html, ShouldRender}; + +mod button; +use crate::button::Button; + +pub struct Model { + link: ComponentLink, +} + +pub enum Msg { + DoIt, +} + +impl Component for Model { + type Message = Msg; + type Properties = (); + + fn create(_: Self::Properties, link: ComponentLink) -> Self { + Model { link } + } + + fn update(&mut self, msg: Self::Message) -> ShouldRender { + match msg { + Msg::DoIt => { + log::info!("got message"); + true + } + } + } + + fn change(&mut self, _props: ()) -> ShouldRender { + false + } + + fn view(&self) -> Html { + html! { + + }, + FetchState::Fetching => html! {"Fetching"}, + FetchState::Success(data) => html! {&data}, + FetchState::Failed(err) => html! {&err}, + } + } +} + +#[wasm_bindgen] +pub fn run_app() { + yew::start_app::(); +} diff --git a/yewtil/examples/history/Cargo.toml b/yewtil/examples/history/Cargo.toml new file mode 100644 index 000000000..5d1d5e977 --- /dev/null +++ b/yewtil/examples/history/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "history" +version = "0.1.0" +authors = ["Henry Zimmerman "] +edition = "2018" +license = "MIT/Apache-2.0" + +[dependencies] +log = "0.4.8" +web_logger = "0.2.0" +yew = { path = "../../../yew" } +yewtil = { path = "../.." } diff --git a/yewtil/examples/history/src/main.rs b/yewtil/examples/history/src/main.rs new file mode 100644 index 000000000..734b63608 --- /dev/null +++ b/yewtil/examples/history/src/main.rs @@ -0,0 +1,64 @@ +use yew::{html, Component, ComponentLink, Html, InputData, ShouldRender}; +use yewtil::History; + +pub struct Model { + text: History, + link: ComponentLink, +} + +pub enum Msg { + SetText(String), + Reset, + Forget, +} + +impl Component for Model { + type Message = Msg; + type Properties = (); + + fn create(_: Self::Properties, link: ComponentLink) -> Self { + Model { + text: History::new("".to_string()), + link, + } + } + + fn update(&mut self, msg: Self::Message) -> ShouldRender { + match msg { + Msg::SetText(text) => self.text.neq_set(text), + Msg::Reset => self.text.reset(), + Msg::Forget => { + self.text.forget(); + false + } + } + } + + fn change(&mut self, _props: ()) -> ShouldRender { + false + } + + fn view(&self) -> Html { + html! { + <> +
+ {&*self.text} +
+
+ + + +
+ + } + } +} + +fn main() { + web_logger::init(); + yew::start_app::(); +} diff --git a/yewtil/examples/lrc/Cargo.toml b/yewtil/examples/lrc/Cargo.toml new file mode 100644 index 000000000..fb0f54dc7 --- /dev/null +++ b/yewtil/examples/lrc/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "lrc" +version = "0.1.0" +authors = ["Henry Zimmerman "] +edition = "2018" +license = "MIT/Apache-2.0" + +[dependencies] +log = "0.4.8" +web_logger = "0.2.0" +yew = { path = "../../../yew" } +yewtil = { path = "../..", features = ["lrc"] } diff --git a/yewtil/examples/lrc/src/child.rs b/yewtil/examples/lrc/src/child.rs new file mode 100644 index 000000000..e819d9c9b --- /dev/null +++ b/yewtil/examples/lrc/src/child.rs @@ -0,0 +1,61 @@ +use yew::{ + events::InputData, html, Callback, Component, ComponentLink, Html, Properties, ShouldRender, +}; +use yewtil::ptr::Lrc; +use yewtil::NeqAssign; + +#[derive(PartialEq, Clone, Properties)] +pub struct Props { + pub text: Lrc, + pub callback: Callback<()>, +} + +pub struct Child { + props: Props, + on_input: Callback, +} + +pub enum Msg { + UpdateText(InputData), +} + +impl Component for Child { + type Message = Msg; + type Properties = Props; + + fn create(props: Self::Properties, link: ComponentLink) -> Self { + Child { + props, + on_input: link.callback(Msg::UpdateText), + } + } + + fn update(&mut self, msg: Self::Message) -> ShouldRender { + match msg { + Msg::UpdateText(input) => { + // Only update the Lrc if the new value is different. + self.props.text.neq_set(input.value); + true + } + } + } + + fn change(&mut self, props: Self::Properties) -> ShouldRender { + self.props.neq_assign(props) + } + + fn view(&self) -> Html { + html! { + <> + + + + } + } +} diff --git a/yewtil/examples/lrc/src/main.rs b/yewtil/examples/lrc/src/main.rs new file mode 100644 index 000000000..e73dcc30b --- /dev/null +++ b/yewtil/examples/lrc/src/main.rs @@ -0,0 +1,59 @@ +use yew::{html, Callback, Component, ComponentLink, Html, ShouldRender}; +use yewtil::ptr::Lrc; + +mod child; +use crate::child::Child; + +pub struct Model { + text: Lrc, + update_text: Callback<()>, +} + +pub enum Msg { + UpdateTextAtADistance, +} + +impl Component for Model { + type Message = Msg; + type Properties = (); + + fn create(_: Self::Properties, link: ComponentLink) -> Self { + Model { + text: Lrc::new("".to_string()), + update_text: link.callback(|_| Msg::UpdateTextAtADistance), + } + } + + fn update(&mut self, msg: Self::Message) -> ShouldRender { + match msg { + Msg::UpdateTextAtADistance => self.text.update(), + } + } + + fn change(&mut self, _props: ()) -> ShouldRender { + false + } + + fn view(&self) -> Html { + html! { + <> +
+ {&*self.text} +
+ // Either of the children's update buttons will cause this component's text + // to update to the most recently edited text. +
+ +
+
+ +
+ + } + } +} + +fn main() { + web_logger::init(); + yew::start_app::(); +} diff --git a/yewtil/examples/mrc_irc/Cargo.toml b/yewtil/examples/mrc_irc/Cargo.toml new file mode 100644 index 000000000..cf7a3e99f --- /dev/null +++ b/yewtil/examples/mrc_irc/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "mrc_irc" +version = "0.1.0" +authors = ["Henry Zimmerman "] +edition = "2018" +license = "MIT/Apache-2.0" + +[dependencies] +log = "0.4.8" +web_logger = "0.2.0" +yew = { path = "../../../yew" } +yewtil = { path = "../.." } diff --git a/yewtil/examples/mrc_irc/src/child.rs b/yewtil/examples/mrc_irc/src/child.rs new file mode 100644 index 000000000..0dc503ae6 --- /dev/null +++ b/yewtil/examples/mrc_irc/src/child.rs @@ -0,0 +1,47 @@ +use yew::{ + events::InputData, html, Callback, Component, ComponentLink, Html, Properties, ShouldRender, +}; +use yewtil::ptr::Irc; +use yewtil::NeqAssign; + +#[derive(Clone, PartialEq, Properties)] +pub struct Props { + /// This value can't be altered. + pub text: Irc, + /// This heavily implies the only way to update the text field is to send a message back + /// to the parent to have the parent component update it. + pub callback: Callback, +} + +pub struct Child { + props: Props, +} + +impl Component for Child { + type Message = (); + type Properties = Props; + + fn create(props: Self::Properties, _: ComponentLink) -> Self { + Child { props } + } + + fn update(&mut self, _msg: Self::Message) -> ShouldRender { + false + } + + fn change(&mut self, props: Self::Properties) -> ShouldRender { + self.props.neq_assign(props) + } + + fn view(&self) -> Html { + html! { + <> + + + } + } +} diff --git a/yewtil/examples/mrc_irc/src/main.rs b/yewtil/examples/mrc_irc/src/main.rs new file mode 100644 index 000000000..526c0afb1 --- /dev/null +++ b/yewtil/examples/mrc_irc/src/main.rs @@ -0,0 +1,61 @@ +use yew::{html, Callback, Component, ComponentLink, Html, ShouldRender}; +use yewtil::ptr::Mrc; + +mod child; +use crate::child::Child; +use yewtil::NeqAssign; + +pub struct Model { + text: Mrc, + update_text: Callback, +} + +pub enum Msg { + UpdateText(String), +} + +impl Component for Model { + type Message = Msg; + type Properties = (); + + fn create(_: Self::Properties, link: ComponentLink) -> Self { + Model { + text: Mrc::new("".to_string()), + update_text: link.callback(Msg::UpdateText), + } + } + + fn update(&mut self, msg: Self::Message) -> ShouldRender { + match msg { + Msg::UpdateText(text) => { + // Because Mrc implements BorrowMut, neq assign can be used here. + self.text.neq_assign(text) + } + } + } + + fn change(&mut self, _: ()) -> ShouldRender { + false + } + + fn view(&self) -> Html { + html! { + <> +
+ {&*self.text} +
+
+ // By passing an `Irc`, we strongly imply that the value should not be updated + // by the child. An effort to modify the value downstream is easily identified + // as subverting the contract implied by using `Irc`s. + +
+ + } + } +} + +fn main() { + web_logger::init(); + yew::start_app::(); +} diff --git a/yewtil/examples/pure_component/Cargo.toml b/yewtil/examples/pure_component/Cargo.toml new file mode 100644 index 000000000..99323da18 --- /dev/null +++ b/yewtil/examples/pure_component/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "pure_component" +version = "0.1.0" +authors = ["Henry Zimmerman "] +edition = "2018" +license = "MIT/Apache-2.0" + +[dependencies] +log = "0.4.8" +web_logger = "0.2.0" +yew = { path = "../../../yew" } +yewtil = { path = "../.." } diff --git a/yewtil/examples/pure_component/src/button.rs b/yewtil/examples/pure_component/src/button.rs new file mode 100644 index 000000000..00aac3e01 --- /dev/null +++ b/yewtil/examples/pure_component/src/button.rs @@ -0,0 +1,21 @@ +use yew::virtual_dom::VNode; +use yew::{html, Callback, MouseEvent, Properties}; +use yewtil::{Pure, PureComponent}; + +/// Alias to make usability better. +pub type Button = Pure; + +#[derive(Clone, PartialEq, Properties)] +pub struct PureButton { + pub callback: Callback, + #[prop_or_default] + pub text: String, +} + +impl PureComponent for PureButton { + fn render(&self) -> VNode { + html! { + + } + } +} diff --git a/yewtil/examples/pure_component/src/main.rs b/yewtil/examples/pure_component/src/main.rs new file mode 100644 index 000000000..ea71806cb --- /dev/null +++ b/yewtil/examples/pure_component/src/main.rs @@ -0,0 +1,45 @@ +use yew::{html, Component, ComponentLink, Html, ShouldRender}; + +mod button; +use crate::button::Button; + +pub struct Model { + link: ComponentLink, +} + +pub enum Msg { + DoIt, +} + +impl Component for Model { + type Message = Msg; + type Properties = (); + + fn create(_: Self::Properties, link: ComponentLink) -> Self { + Model { link } + } + + fn update(&mut self, msg: Self::Message) -> ShouldRender { + match msg { + Msg::DoIt => { + log::info!("got message"); + true + } + } + } + + fn change(&mut self, _props: ()) -> ShouldRender { + false + } + + fn view(&self) -> Html { + html! { +