Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix broken examples (#1289) - batch #1 #1340

Merged
merged 15 commits into from
Jun 26, 2020
Merged
2 changes: 1 addition & 1 deletion examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Have a look at Yew's [starter templates](https://yew.rs/docs/getting-started/sta
git clone https://github.com/yewstack/yew.git
cd yew/examples
./build.sh minimal # example subfolder
python3 -m http.server --directory static # open localhost:8000 in browser
cd static && python3 -m http.server # open localhost:8000 in browser
jstarry marked this conversation as resolved.
Show resolved Hide resolved
```


Expand Down
21 changes: 15 additions & 6 deletions examples/crm/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#![recursion_limit = "128"]
#![recursion_limit = "256"]

#[macro_use]
extern crate serde_derive;
Expand Down Expand Up @@ -154,6 +154,7 @@ impl Component for Model {
match self.scene {
Scene::ClientsList => html! {
<div class="crm">
<h1>{"List of clients"}</h1>
<div class="clients">
{ for self.database.clients.iter().map(Renderable::render) }
</div>
Expand All @@ -163,10 +164,17 @@ impl Component for Model {
},
Scene::NewClientForm(ref client) => html! {
<div class="crm">
<h1>{"Add new client"}</h1>
jstarry marked this conversation as resolved.
Show resolved Hide resolved
<div class="names">
{ client.view_first_name_input(&self.link) }
{ client.view_last_name_input(&self.link) }
{ client.view_description_textarea(&self.link) }
<div>
{ client.view_first_name_input(&self.link) }
</div>
<div>
{ client.view_last_name_input(&self.link) }
</div>
<div>
{ client.view_description_textarea(&self.link) }
</div>
</div>
<button disabled=client.first_name.is_empty() || client.last_name.is_empty()
onclick=self.link.callback(|_| Msg::AddNew)>{ "Add New" }</button>
Expand All @@ -175,6 +183,7 @@ impl Component for Model {
},
Scene::Settings => html! {
<div>
<h1>{"Settings"}</h1>
<button onclick=self.link.callback(|_| Msg::Clear)>{ "Clear Database" }</button>
<button onclick=self.link.callback(|_| Msg::SwitchTo(Scene::ClientsList))>{ "Go Back" }</button>
</div>
Expand All @@ -186,7 +195,7 @@ impl Component for Model {
impl Renderable for Client {
fn render(&self) -> Html {
html! {
<div class="client">
<div class="client" style="margin-bottom: 50px">
<p>{ format!("First Name: {}", self.first_name) }</p>
<p>{ format!("Last Name: {}", self.last_name) }</p>
<p>{ "Description:" }</p>
Expand Down Expand Up @@ -217,7 +226,7 @@ impl Client {
fn view_description_textarea(&self, link: &ComponentLink<Model>) -> Html {
html! {
<textarea class=("new-client", "description")
placeholder="Description"
placeholder="Description (can use Markdown)"
value=&self.description
oninput=link.callback(|e: InputData| Msg::UpdateDescription(e.value)) />
}
Expand Down
1 change: 1 addition & 0 deletions examples/file_upload/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ impl Component for Model {
html! {
<div>
<div>
<p>{"Choose a file to upload to see the uploaded bytes"}</p>
<input type="file" multiple=true onchange=self.link.callback(move |value| {
let mut result = Vec::new();
if let ChangeData::Files(files) = value {
Expand Down
6 changes: 3 additions & 3 deletions examples/fragments/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ impl Component for Model {
<nav class="menu">{ self.view_menu() }</nav>
<table>
<tr>
// Important! All columns have contain the same elements
// Important! All columns have to contain the same elements
{ self.view_cols() }
<td>{ "- - - >" }</td>
{ self.view_cols() }
Expand All @@ -59,13 +59,13 @@ impl Component for Model {

impl Model {
fn view_cols(&self) -> Html {
let render = |idx| {
let render_func = |idx| {
html! {
<td>{ idx }</td>
}
};
html! { // We use a fragment directly
{ for (0..self.counter).map(render) }
{ for (0..self.counter).map(render_func) }
}
}

Expand Down
1 change: 1 addition & 0 deletions examples/futures_wp/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ crate-type = ["cdylib", "rlib"]
yew = { path = "../../yew" }
wasm-bindgen = "0.2.60"
wasm-bindgen-futures = "0.4.3"
pulldown-cmark = { version = "0.7.0", default-features = false }

[dependencies.web-sys]
version = "0.3.35"
Expand Down
42 changes: 30 additions & 12 deletions examples/futures_wp/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ use wasm_bindgen_futures::JsFuture;
use web_sys::{Request, RequestInit, RequestMode, Response, Window};
use yew::{html, Component, ComponentLink, Html, ShouldRender};

mod markdown;

/// This method processes a Future that returns a message and sends it back to the component's
/// loop.
///
Expand Down Expand Up @@ -52,15 +54,12 @@ pub enum FetchState<T> {
///
/// Consult the following for an example of the fetch api by the team behind web_sys:
/// https://rustwasm.github.io/wasm-bindgen/examples/fetch.html
async fn fetch_markdown() -> Result<String, FetchError> {
async fn fetch_markdown(url: &'static str) -> Result<String, FetchError> {
let mut opts = RequestInit::new();
opts.method("GET");
opts.mode(RequestMode::Cors);

let request = Request::new_with_str_and_init(
"https://raw.githubusercontent.com/yewstack/yew/master/README.md",
&opts,
)?;
let request = Request::new_with_str_and_init(url, &opts)?;

let window: Window = web_sys::window().unwrap();
let resp_value = JsFuture::from(window.fetch_with_request(&request)).await?;
Expand All @@ -80,11 +79,13 @@ struct Model {
enum Msg {
SetMarkdownFetchState(FetchState<String>),
GetMarkdown,
GetError,
}

impl Component for Model {
// Some details omitted. Explore the examples to see more.
const MARKDOWN_URL: &str = "https://raw.githubusercontent.com/yewstack/yew/master/README.md";
const INCORRECT_URL: &str = "https://raw.githubusercontent.com/yewstack/yew/master/README.md.404";

impl Component for Model {
type Message = Msg;
type Properties = ();

Expand All @@ -107,7 +108,19 @@ impl Component for Model {
}
Msg::GetMarkdown => {
let future = async {
match fetch_markdown().await {
match fetch_markdown(MARKDOWN_URL).await {
Ok(md) => Msg::SetMarkdownFetchState(FetchState::Success(md)),
Err(err) => Msg::SetMarkdownFetchState(FetchState::Failed(err)),
}
};
send_future(self.link.clone(), future);
self.link
.send_message(SetMarkdownFetchState(FetchState::Fetching));
false
}
Msg::GetError => {
let future = async {
match fetch_markdown(INCORRECT_URL).await {
Ok(md) => Msg::SetMarkdownFetchState(FetchState::Success(md)),
Err(err) => Msg::SetMarkdownFetchState(FetchState::Failed(err)),
}
Expand All @@ -123,12 +136,17 @@ impl Component for Model {
fn view(&self) -> Html {
match &self.markdown {
FetchState::NotFetching => html! {
<button onclick=self.link.callback(|_| Msg::GetMarkdown)>
{"Get Markdown"}
</button>
<>
<button onclick=self.link.callback(|_| Msg::GetMarkdown)>
{"Get Markdown"}
</button>
<button onclick=self.link.callback(|_| Msg::GetError)>
{"Get using incorrect URL"}
</button>
</>
},
FetchState::Fetching => html! {"Fetching"},
FetchState::Success(data) => html! {&data},
FetchState::Success(data) => html! { markdown::render_markdown(&data) },
FetchState::Failed(err) => html! {&err},
}
}
Expand Down
185 changes: 185 additions & 0 deletions examples/futures_wp/src/markdown.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
/// Original author of this code is [Nathan Ringo](https://github.com/remexre)
/// Source: https://github.com/acmumn/mentoring/blob/master/web-client/src/view/markdown.rs
jstarry marked this conversation as resolved.
Show resolved Hide resolved
use pulldown_cmark::{Alignment, CodeBlockKind, Event, Options, Parser, Tag};
use yew::virtual_dom::{Classes, VNode, VTag, VText};
use yew::{html, Html};

/// Adds a class to the VTag.
/// You can also provide multiple classes separated by ascii whitespaces.
///
/// Note that this has a complexity of O(n),
/// where n is the number of classes already in VTag plus
/// the number of classes to be added.
fn add_class(vtag: &mut VTag, class: &str) {
let mut classes: Classes = vtag
.attributes
.get("class")
.map(AsRef::as_ref)
.unwrap_or("")
.into();
classes.push(class);
vtag.add_attribute("class", &classes);
}

/// Renders a string of Markdown to HTML with the default options (footnotes
/// disabled, tables enabled).
pub fn render_markdown(src: &str) -> Html {
let mut elems = vec![];
let mut spine = vec![];

macro_rules! add_child {
($child:expr) => {{
let l = spine.len();
assert_ne!(l, 0);
spine[l - 1].add_child($child);
}};
}

let mut options = Options::empty();
options.insert(Options::ENABLE_TABLES);

for ev in Parser::new_ext(src, options) {
match ev {
Event::Start(tag) => {
spine.push(make_tag(tag));
}
Event::End(tag) => {
// TODO Verify stack end.
let l = spine.len();
assert!(l >= 1);
let mut top = spine.pop().unwrap();
if let Tag::CodeBlock(_) = tag {
let mut pre = VTag::new("pre");
pre.add_child(top.into());
top = pre;
} else if let Tag::Table(aligns) = tag {
for r in top.children.iter_mut() {
if let VNode::VTag(ref mut vtag) = r {
for (i, c) in vtag.children.iter_mut().enumerate() {
if let VNode::VTag(ref mut vtag) = c {
match aligns[i] {
Alignment::None => {}
Alignment::Left => add_class(vtag, "text-left"),
Alignment::Center => add_class(vtag, "text-center"),
Alignment::Right => add_class(vtag, "text-right"),
}
}
}
}
}
} else if let Tag::TableHead = tag {
for c in top.children.iter_mut() {
if let VNode::VTag(ref mut vtag) = c {
// TODO
// vtag.tag = "th".into();
vtag.add_attribute("scope", &"col");
}
}
}
if l == 1 {
elems.push(top);
} else {
spine[l - 2].add_child(top.into());
}
}
Event::Text(text) => add_child!(VText::new(text.to_string()).into()),
Event::Rule => add_child!(VTag::new("hr").into()),
Event::SoftBreak => add_child!(VText::new("\n".to_string()).into()),
Event::HardBreak => add_child!(VTag::new("br").into()),
_ => println!("Unknown event: {:#?}", ev),
}
}

if elems.len() == 1 {
VNode::VTag(Box::new(elems.pop().unwrap()))
} else {
html! {
<div>{ for elems.into_iter() }</div>
}
}
}

fn make_tag(t: Tag) -> VTag {
match t {
Tag::Paragraph => VTag::new("p"),
Tag::Heading(n) => {
assert!(n > 0);
assert!(n < 7);
VTag::new(format!("h{}", n))
}
Tag::BlockQuote => {
let mut el = VTag::new("blockquote");
el.add_attribute("class", &"blockquote");
el
}
Tag::CodeBlock(code_block_kind) => {
let mut el = VTag::new("code");

if let CodeBlockKind::Fenced(lang) = code_block_kind {
// Different color schemes may be used for different code blocks,
// but a different library (likely js based at the moment) would be necessary to actually provide the
// highlighting support by locating the language classes and applying dom transforms
// on their contents.
match lang.as_ref() {
"html" => el.add_attribute("class", &"html-language"),
"rust" => el.add_attribute("class", &"rust-language"),
"java" => el.add_attribute("class", &"java-language"),
"c" => el.add_attribute("class", &"c-language"),
_ => {} // Add your own language highlighting support
};
}

el
}
Tag::List(None) => VTag::new("ul"),
Tag::List(Some(1)) => VTag::new("ol"),
Tag::List(Some(ref start)) => {
let mut el = VTag::new("ol");
el.add_attribute("start", start);
el
}
Tag::Item => VTag::new("li"),
Tag::Table(_) => {
let mut el = VTag::new("table");
el.add_attribute("class", &"table");
el
}
Tag::TableHead => VTag::new("th"),
Tag::TableRow => VTag::new("tr"),
Tag::TableCell => VTag::new("td"),
Tag::Emphasis => {
let mut el = VTag::new("span");
el.add_attribute("class", &"font-italic");
el
}
Tag::Strong => {
let mut el = VTag::new("span");
el.add_attribute("class", &"font-weight-bold");
el
}
Tag::Link(_link_type, ref href, ref title) => {
let mut el = VTag::new("a");
el.add_attribute("href", href);
let title = title.clone().into_string();
if title != "" {
el.add_attribute("title", &title);
}
el
}
Tag::Image(_link_type, ref src, ref title) => {
let mut el = VTag::new("img");
el.add_attribute("src", src);
let title = title.clone().into_string();
if title != "" {
el.add_attribute("title", &title);
}
el
}
Tag::FootnoteDefinition(ref _footnote_id) => VTag::new("span"), // Footnotes are not rendered as anything special
Tag::Strikethrough => {
let mut el = VTag::new("span");
el.add_attribute("class", &"text-decoration-strikethrough");
el
}
}
}
Loading