Macro to generate examples & fix CI (#9)

This commit is contained in:
Cecile Tonglet 2020-09-23 18:33:41 +02:00 committed by GitHub
parent a4178c58f9
commit ead6db471d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 545 additions and 257 deletions

View file

@ -16,6 +16,5 @@ before_script:
script:
- 'if [ -n "$TRAVIS_PULL_REQUEST" ]; then rustfmt --check **/*.rs; fi'
- 'if [ -n "$TRAVIS_PULL_REQUEST" ]; then cargo clippy --all-targets --all-features -- -D warnings; fi'
- cargo build
- cargo test
- cargo test --all-features
- cargo doc

View file

@ -1,4 +1,5 @@
[![Build Status](https://travis-ci.org/cecton/yewprint.svg?branch=main)](https://travis-ci.org/cecton/yewprint)
[![Netlify Status](https://api.netlify.com/api/v1/badges/17f076ed-49e5-4185-921e-5c5759de2fdb/deploy-status)](https://app.netlify.com/sites/epic-poincare-f8adaa/deploys)
![Demo](https://github.com/cecton/blueprint-rs/blob/main/demo.mp4?raw=true)

View file

@ -1,8 +1,8 @@
use crate::buttons::doc::*;
use crate::collapse::doc::*;
use crate::forms::controls::doc::SwitchDoc;
use crate::icon::doc::*;
use crate::menu::*;
use crate::switch::doc::SwitchDoc;
use crate::tree::doc::*;
use yew::prelude::*;
@ -24,7 +24,7 @@ impl Component for App {
fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self {
App {
dark_theme: true,
doc_menu: DocMenu::Tree,
doc_menu: DocMenu::Button,
link,
}
}

View file

@ -1,90 +0,0 @@
use yew::prelude::*;
pub struct Button {
props: Props,
}
#[derive(Clone, PartialEq, Properties)]
pub struct Props {
#[prop_or_default]
pub title: String,
#[prop_or_default]
pub onclick: Callback<MouseEvent>,
pub children: html::Children,
}
impl Component for Button {
type Message = ();
type Properties = Props;
fn create(props: Self::Properties, _link: ComponentLink<Self>) -> Self {
Button { props }
}
fn update(&mut self, _msg: Self::Message) -> ShouldRender {
true
}
fn change(&mut self, props: Self::Properties) -> ShouldRender {
if self.props != props {
self.props = props;
true
} else {
false
}
}
fn view(&self) -> Html {
html! {
<button class="bp3-button" onclick={self.props.onclick.clone()}>
{self.props.children.clone()}
</button>
}
}
}
#[cfg(feature = "doc")]
pub mod doc {
use super::*;
pub struct ButtonDoc {
link: ComponentLink<Self>,
counter: i64,
}
pub enum Msg {
AddOne,
}
impl Component for ButtonDoc {
type Message = Msg;
type Properties = ();
fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self {
ButtonDoc { counter: 0, link }
}
fn update(&mut self, msg: Self::Message) -> ShouldRender {
match msg {
Msg::AddOne => self.counter += 1,
}
true
}
fn change(&mut self, _props: Self::Properties) -> ShouldRender {
true
}
fn view(&self) -> Html {
html! {
<div>
<h1>{"Button"}</h1>
<p> {"Counter: "} { self.counter }</p>
<div>
<Button onclick=self.link.callback(|_| Msg::AddOne)>{ "Add 1" }</Button>
</div>
</div>
}
}
}
}

42
src/buttons/example.rs Normal file
View file

@ -0,0 +1,42 @@
use yew::prelude::*;
use yewprint::Button;
pub struct Example {
link: ComponentLink<Self>,
counter: i64,
}
pub enum Msg {
AddOne,
}
impl Component for Example {
type Message = Msg;
type Properties = ();
fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self {
Example { counter: 0, link }
}
fn update(&mut self, msg: Self::Message) -> ShouldRender {
match msg {
Msg::AddOne => self.counter += 1,
}
true
}
fn change(&mut self, _props: Self::Properties) -> ShouldRender {
true
}
fn view(&self) -> Html {
html! {
<div>
<p> {"Counter: "} { self.counter }</p>
<div>
<Button onclick=self.link.callback(|_| Msg::AddOne)>{ "Add 1" }</Button>
</div>
</div>
}
}
}

108
src/buttons/mod.rs Normal file
View file

@ -0,0 +1,108 @@
use crate::{Icon, IconName, Intent};
use yew::prelude::*;
pub struct Button {
props: Props,
}
#[derive(Clone, PartialEq, Properties)]
pub struct Props {
#[prop_or_default]
pub fill: bool,
#[prop_or_default]
pub minimal: bool,
#[prop_or_default]
pub icon: Option<IconName>,
#[prop_or_default]
pub intent: Option<Intent>,
#[prop_or_default]
pub title: String,
#[prop_or_default]
pub onclick: Callback<MouseEvent>,
pub children: html::Children,
}
impl Component for Button {
type Message = ();
type Properties = Props;
fn create(props: Self::Properties, _link: ComponentLink<Self>) -> Self {
Button { props }
}
fn update(&mut self, _msg: Self::Message) -> ShouldRender {
true
}
fn change(&mut self, props: Self::Properties) -> ShouldRender {
if self.props != props {
self.props = props;
true
} else {
false
}
}
fn view(&self) -> Html {
let mut class = Classes::from("bp3-button");
if self.props.fill {
class.push("bp3-fill");
}
if self.props.minimal {
class.push("bp3-minimal");
}
class = class.extend(&self.props.intent);
html! {
<button class=class onclick={self.props.onclick.clone()}>
{
if let Some(icon) = self.props.icon {
html! {
<Icon icon=icon />
}
} else {
html!()
}
}
<span class="bp3-button-text">
{self.props.children.clone()}
</span>
</button>
}
}
}
#[cfg(feature = "doc")]
pub mod doc {
use yew::prelude::*;
pub struct ButtonDoc;
impl Component for ButtonDoc {
type Message = ();
type Properties = ();
fn create(_: Self::Properties, _link: ComponentLink<Self>) -> Self {
ButtonDoc
}
fn update(&mut self, _msg: Self::Message) -> ShouldRender {
true
}
fn change(&mut self, _props: Self::Properties) -> ShouldRender {
true
}
fn view(&self) -> Html {
let source = crate::include_example!("example.rs");
html! {
<div>
<h1>{"Button"}</h1>
<div>{source}</div>
</div>
}
}
}
}

66
src/collapse/example.rs Normal file
View file

@ -0,0 +1,66 @@
use yew::prelude::*;
use yewprint::{Button,Collapse};
pub struct Example {
link: ComponentLink<Self>,
collapsed: bool,
}
pub enum Msg {
ToggleCollapse,
}
impl Component for Example {
type Message = Msg;
type Properties = ();
fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self {
Example {
collapsed: true,
link,
}
}
fn update(&mut self, msg: Self::Message) -> ShouldRender {
match msg {
Msg::ToggleCollapse => self.collapsed ^= true,
}
true
}
fn change(&mut self, _props: Self::Properties) -> ShouldRender {
true
}
fn view(&self) -> Html {
html! {
<div>
<Button onclick=self.link.callback(|_| Msg::ToggleCollapse)>
{"Toggle collapse"}
</Button>
<Collapse
is_open=!self.collapsed
keep_children_mounted=true
>
<pre class="bp3-code-block">
<div>{"[INFO]: Installing wasm-bindgen..."}</div>
<div>{"[INFO]: Optional fields missing from Cargo.toml: 'description', 'repository', and 'license'. These are not necessary, but recommended"}</div>
<div>{"[INFO]: :-) Done in 0.69s"}</div>
<div>{"[INFO]: :-) Your wasm pkg is ready to publish at /home/cecile/repos/blueprint-rs/./static."}</div>
<div>{" Index: enabled, Upload: disabled, Cache: disabled, Cors: enabled, Range: enabled, Sort: enabled, Threads: 3"}</div>
<div>{" Auth: disabled, Compression: disabled"}</div>
<div>{" https: disabled, Cert: , Cert-Password: "}</div>
<div>{" Root: /home/cecile/repos/blueprint-rs,"}</div>
<div>{" TryFile404: "}</div>
<div>{" Address: http://0.0.0.0:8000"}</div>
<div>{" ======== [2020-09-07 20:39:46] ========"}</div>
<div>{"[2020-09-07 20:39:46] - 127.0.0.1 - 200 - GET /"}</div>
<div>{"[2020-09-07 20:39:46] - 127.0.0.1 - 200 - GET /static/blueprint.css"}</div>
<div>{"[2020-09-07 20:39:46] - 127.0.0.1 - 200 - GET /static/wasm.js"}</div>
<div>{"[2020-09-07 20:39:46] - 127.0.0.1 - 200 - GET /static/wasm_bg.wasm"}</div>
</pre>
</Collapse>
</div>
}
}
}

View file

@ -199,33 +199,19 @@ impl Component for Collapse {
#[cfg(feature = "doc")]
pub mod doc {
use super::*;
use crate::buttons::Button;
use yew::prelude::*;
pub struct CollapseDoc {
link: ComponentLink<Self>,
collapsed: bool,
}
pub enum Msg {
ToggleCollapse,
}
pub struct CollapseDoc;
impl Component for CollapseDoc {
type Message = Msg;
type Message = ();
type Properties = ();
fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self {
CollapseDoc {
collapsed: true,
link,
}
fn create(_: Self::Properties, _link: ComponentLink<Self>) -> Self {
CollapseDoc
}
fn update(&mut self, msg: Self::Message) -> ShouldRender {
match msg {
Msg::ToggleCollapse => self.collapsed ^= true,
}
fn update(&mut self, _msg: Self::Message) -> ShouldRender {
true
}
@ -234,34 +220,12 @@ pub mod doc {
}
fn view(&self) -> Html {
let source = crate::include_example!("example.rs");
html! {
<div>
<h1>{"Collapse"}</h1>
<Button onclick=self.link.callback(|_| Msg::ToggleCollapse)>
{"Toggle collapse"}
</Button>
<Collapse
is_open=!self.collapsed
keep_children_mounted=true
>
<pre class="bp3-code-block">
<div>{"[INFO]: Installing wasm-bindgen..."}</div>
<div>{"[INFO]: Optional fields missing from Cargo.toml: 'description', 'repository', and 'license'. These are not necessary, but recommended"}</div>
<div>{"[INFO]: :-) Done in 0.69s"}</div>
<div>{"[INFO]: :-) Your wasm pkg is ready to publish at /home/cecile/repos/blueprint-rs/./static."}</div>
<div>{" Index: enabled, Upload: disabled, Cache: disabled, Cors: enabled, Range: enabled, Sort: enabled, Threads: 3"}</div>
<div>{" Auth: disabled, Compression: disabled"}</div>
<div>{" https: disabled, Cert: , Cert-Password: "}</div>
<div>{" Root: /home/cecile/repos/blueprint-rs,"}</div>
<div>{" TryFile404: "}</div>
<div>{" Address: http://0.0.0.0:8000"}</div>
<div>{" ======== [2020-09-07 20:39:46] ========"}</div>
<div>{"[2020-09-07 20:39:46] - 127.0.0.1 - 200 - GET /"}</div>
<div>{"[2020-09-07 20:39:46] - 127.0.0.1 - 200 - GET /static/blueprint.css"}</div>
<div>{"[2020-09-07 20:39:46] - 127.0.0.1 - 200 - GET /static/wasm.js"}</div>
<div>{"[2020-09-07 20:39:46] - 127.0.0.1 - 200 - GET /static/wasm_bg.wasm"}</div>
</pre>
</Collapse>
<h1>{"Tree"}</h1>
<div>{source}</div>
</div>
}
}

91
src/example.rs Normal file
View file

@ -0,0 +1,91 @@
use crate::{Button, Collapse, IconName, Intent};
use yew::prelude::*;
pub struct ExampleContainer {
collapsed: bool,
props: Props,
link: ComponentLink<Self>,
}
pub enum Msg {
ToggleSource,
}
#[derive(Clone, PartialEq, Properties)]
pub struct Props {
pub source: &'static str,
pub children: html::Children,
}
impl Component for ExampleContainer {
type Message = Msg;
type Properties = Props;
fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self {
ExampleContainer {
collapsed: true,
props,
link,
}
}
fn update(&mut self, msg: Self::Message) -> ShouldRender {
match msg {
Msg::ToggleSource => self.collapsed ^= true,
}
true
}
fn change(&mut self, _props: Self::Properties) -> ShouldRender {
// TODO: never re-render this component? How to optimize this
false
}
fn view(&self) -> Html {
html! {
<div class="docs-example-wrapper">
<div class="docs-example">
{self.props.children.clone()}
</div>
<div class="docs-source">
<Button
icon=IconName::Code
fill={true}
intent={Intent::Primary}
minimal={true}
onclick=self.link.callback(|_| Msg::ToggleSource)
>
{"View source"}
</Button>
<Collapse
is_open=!self.collapsed
keep_children_mounted=true
>
<pre class="bp3-code-block">{self.props.source}</pre>
</Collapse>
</div>
</div>
}
}
}
#[macro_export]
macro_rules! include_example {
($file:expr) => {{
use crate::ExampleContainer;
let source = include_str!($file);
mod source {
// TODO: example.rs files are not formatted because of this include
include!($file);
}
use source::Example;
html! {
<ExampleContainer source={source}>
<Example />
</ExampleContainer>
}
}};
}

View file

@ -1 +0,0 @@
pub mod controls;

26
src/icon/example.rs Normal file
View file

@ -0,0 +1,26 @@
use yew::prelude::*;
use yewprint::{Icon, IconName};
pub struct Example {}
impl Component for Example {
type Message = ();
type Properties = ();
fn create(_: Self::Properties, _link: ComponentLink<Self>) -> Self {
Example {}
}
fn update(&mut self, _msg: Self::Message) -> ShouldRender {
true
}
fn change(&mut self, _props: Self::Properties) -> ShouldRender {
true
}
fn view(&self) -> Html {
html! {
<div>
<Icon icon=IconName::Print />
</div>
}
}
}

View file

@ -96,28 +96,33 @@ impl Component for Icon {
#[cfg(feature = "doc")]
pub mod doc {
use super::*;
use yew::prelude::*;
pub struct IconDoc {}
pub struct IconDoc;
impl Component for IconDoc {
type Message = ();
type Properties = ();
fn create(_: Self::Properties, _link: ComponentLink<Self>) -> Self {
IconDoc {}
IconDoc
}
fn update(&mut self, _msg: Self::Message) -> ShouldRender {
true
}
fn change(&mut self, _props: Self::Properties) -> ShouldRender {
true
}
fn view(&self) -> Html {
let source = crate::include_example!("example.rs");
html! {
<div>
<h1>{"Icon"}</h1>
<Icon icon=IconName::Print />
<div>{source}</div>
</div>
}
}

View file

@ -1,13 +1,30 @@
#![recursion_limit = "512"]
#[cfg(feature = "doc")]
extern crate self as yewprint;
#[cfg(feature = "doc")]
mod app;
pub mod buttons;
pub mod collapse;
pub mod forms;
pub mod icon;
pub mod menu;
pub mod tree;
mod buttons;
mod collapse;
#[cfg(feature = "doc")]
mod example;
mod icon;
mod menu;
mod switch;
mod tree;
#[cfg(feature = "doc")]
pub use app::*;
pub use buttons::*;
pub use collapse::*;
#[cfg(feature = "doc")]
pub use example::*;
pub use icon::*;
pub use id_tree;
pub use menu::*;
pub use switch::*;
pub use tree::*;
use yew::virtual_dom::Classes;

126
src/tree/example.rs Normal file
View file

@ -0,0 +1,126 @@
use yew::prelude::*;
use yewprint::{Tree, Icon, NodeData, IconName, Intent, TreeData};
use yewprint::id_tree::{InsertBehavior, Node, TreeBuilder, NodeId};
pub struct Example {
tree: TreeData<i32>,
callback_expand_node: Callback<(NodeId, MouseEvent)>,
callback_select_node: Callback<(NodeId, MouseEvent)>,
}
pub enum Msg {
ExpandNode(NodeId),
SelectNode(NodeId),
}
impl Component for Example {
type Message = Msg;
type Properties = ();
fn create(_props: Self::Properties, link: ComponentLink<Self>) -> Self {
let mut tree = TreeBuilder::new().build();
let root_id = tree
.insert(
Node::new(NodeData {
data: 0,
..Default::default()
}),
InsertBehavior::AsRoot,
)
.unwrap();
let dir1 = tree
.insert(
Node::new(NodeData {
icon: Some(IconName::FolderClose),
label: "Big directory".into(),
has_caret: true,
data: 1,
..Default::default()
}),
InsertBehavior::UnderNode(&root_id),
)
.unwrap();
for i in 0..10 {
let dir2 = tree
.insert(
Node::new(NodeData {
icon: Some(IconName::FolderClose),
label: format!("Directory {}", i + 1).into(),
has_caret: true,
data: 1,
..Default::default()
}),
InsertBehavior::UnderNode(&dir1),
)
.unwrap();
for i in 0..10 {
tree.insert(
Node::new(NodeData {
icon: Some(IconName::Document),
label: format!("File {}", i + 1).into(),
data: i,
..Default::default()
}),
InsertBehavior::UnderNode(&dir2),
)
.unwrap();
}
}
tree.insert(
Node::new(NodeData {
icon: Some(IconName::Tag),
icon_intent: Some(Intent::Primary),
label: "Outer file".into(),
secondary_label: Some(html!(<Icon icon=IconName::EyeOpen />)),
data: 3,
..Default::default()
}),
InsertBehavior::UnderNode(&root_id),
)
.unwrap();
Self {
tree: tree.into(),
callback_expand_node: link.callback(|(node_id, _)| Msg::ExpandNode(node_id)),
callback_select_node: link.callback(|(node_id, _)| Msg::SelectNode(node_id)),
}
}
fn update(&mut self, msg: Self::Message) -> ShouldRender {
match msg {
Msg::ExpandNode(node_id) => {
let mut tree = self.tree.borrow_mut();
let node = tree.get_mut(&node_id).unwrap();
let data = node.data_mut();
data.is_expanded ^= true;
data.icon = Some(if data.is_expanded {
IconName::FolderOpen
} else {
IconName::FolderClose
})
}
Msg::SelectNode(node_id) => {
let mut tree = self.tree.borrow_mut();
let node = tree.get_mut(&node_id).unwrap();
node.data_mut().is_selected ^= true;
}
}
true
}
fn change(&mut self, _props: Self::Properties) -> ShouldRender {
true
}
fn view(&self) -> Html {
html! {
<Tree<i32>
tree=self.tree.clone()
on_collapse=Some(self.callback_expand_node.clone())
on_expand=Some(self.callback_expand_node.clone())
onclick=Some(self.callback_select_node.clone())
/>
}
}
}

View file

@ -1,7 +1,7 @@
use crate::collapse::Collapse;
use crate::icon::{Icon, IconName};
use crate::Intent;
pub use id_tree::*;
use id_tree::*;
use std::cell::{Ref, RefCell, RefMut};
use std::collections::hash_map::DefaultHasher;
use std::collections::HashMap;
@ -365,112 +365,19 @@ impl Component for TreeNode {
#[cfg(feature = "doc")]
pub mod doc {
use super::*;
use yew::prelude::*;
pub struct TreeDoc {
tree: TreeData<i32>,
callback_expand_node: Callback<(NodeId, MouseEvent)>,
callback_select_node: Callback<(NodeId, MouseEvent)>,
}
pub enum Msg {
ExpandNode(NodeId),
SelectNode(NodeId),
}
pub struct TreeDoc;
impl Component for TreeDoc {
type Message = Msg;
type Message = ();
type Properties = ();
fn create(_props: Self::Properties, link: ComponentLink<Self>) -> Self {
let mut tree = TreeBuilder::new().build();
let root_id = tree
.insert(
Node::new(NodeData {
data: 0,
..Default::default()
}),
InsertBehavior::AsRoot,
)
.unwrap();
let dir1 = tree
.insert(
Node::new(NodeData {
icon: Some(IconName::FolderClose),
label: "Big directory".into(),
has_caret: true,
data: 1,
..Default::default()
}),
InsertBehavior::UnderNode(&root_id),
)
.unwrap();
for i in 0..10 {
let dir2 = tree
.insert(
Node::new(NodeData {
icon: Some(IconName::FolderClose),
label: format!("Directory {}", i + 1).into(),
has_caret: true,
data: 1,
..Default::default()
}),
InsertBehavior::UnderNode(&dir1),
)
.unwrap();
for i in 0..10 {
tree.insert(
Node::new(NodeData {
icon: Some(IconName::Document),
label: format!("File {}", i + 1).into(),
data: i,
..Default::default()
}),
InsertBehavior::UnderNode(&dir2),
)
.unwrap();
}
}
tree.insert(
Node::new(NodeData {
icon: Some(IconName::Tag),
icon_intent: Some(Intent::Primary),
label: "Outer file".into(),
secondary_label: Some(html!(<Icon icon=IconName::EyeOpen />)),
data: 3,
..Default::default()
}),
InsertBehavior::UnderNode(&root_id),
)
.unwrap();
Self {
tree: tree.into(),
callback_expand_node: link.callback(|(node_id, _)| Msg::ExpandNode(node_id)),
callback_select_node: link.callback(|(node_id, _)| Msg::SelectNode(node_id)),
}
}
fn update(&mut self, msg: Self::Message) -> ShouldRender {
match msg {
Msg::ExpandNode(node_id) => {
let mut tree = self.tree.borrow_mut();
let node = tree.get_mut(&node_id).unwrap();
let data = node.data_mut();
data.is_expanded ^= true;
data.icon = Some(if data.is_expanded {
IconName::FolderOpen
} else {
IconName::FolderClose
})
}
Msg::SelectNode(node_id) => {
let mut tree = self.tree.borrow_mut();
let node = tree.get_mut(&node_id).unwrap();
node.data_mut().is_selected ^= true;
}
fn create(_: Self::Properties, _link: ComponentLink<Self>) -> Self {
TreeDoc
}
fn update(&mut self, _msg: Self::Message) -> ShouldRender {
true
}
@ -479,15 +386,12 @@ pub mod doc {
}
fn view(&self) -> Html {
let source = crate::include_example!("example.rs");
html! {
<div>
<h1>{"Tree"}</h1>
<Tree<i32>
tree=self.tree.clone()
on_collapse=Some(self.callback_expand_node.clone())
on_expand=Some(self.callback_expand_node.clone())
onclick=Some(self.callback_select_node.clone())
/>
<div>{source}</div>
</div>
}
}

View file

@ -72,6 +72,36 @@
line-height: 40px;
font-size: 36px;
}
.docs-example-wrapper .bp3-code-block {
/*
display: inline-block;
clear: both;
*/
}
.docs-example-wrapper .docs-example {
border-radius: 6px;
background-color: #fff;
padding: 20px;
}
.bp3-dark .docs-example-wrapper .docs-example {
background-color: #293742;
}
.docs-example-wrapper > .docs-source > .bp3-button {
background-color: #ebf1f5;
}
.bp3-dark .docs-example-wrapper > .docs-source > .bp3-button {
background-color: #394b59;
}
.docs-example-wrapper > .docs-source {
margin-bottom: 40px;
margin-top: 10px;
}
</style>
</head>
<body></body>