WIP: Tree

This commit is contained in:
Cecile Tonglet 2020-09-13 18:16:01 +02:00
parent 066dc90444
commit 584f5f69b1
6 changed files with 207 additions and 34 deletions

25
Cargo.lock generated
View file

@ -33,6 +33,7 @@ name = "blueprint-rs"
version = "0.1.0"
dependencies = [
"heck",
"id_tree",
"regex",
"wasm-bindgen",
"web-sys",
@ -269,6 +270,15 @@ dependencies = [
"itoa",
]
[[package]]
name = "id_tree"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8233bef841ffcb4766af63c917b09c786430cfcfbaf41bffeae57d53284ddd00"
dependencies = [
"snowflake",
]
[[package]]
name = "indexmap"
version = "1.5.2"
@ -435,6 +445,12 @@ version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
[[package]]
name = "snowflake"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "27207bb65232eda1f588cf46db2fee75c0808d557f6b3cf19a75f5d6d7c94df1"
[[package]]
name = "syn"
version = "1.0.39"
@ -557,8 +573,7 @@ dependencies = [
[[package]]
name = "yew"
version = "0.17.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "882b275d108d2280dbc588a8ad6c9690d806fcd299d63b68d702ff4d848fdc0c"
source = "git+https://github.com/yewstack/yew.git?rev=1507c21b#1507c21b39e7941f7b9fe8ff5af792cf55be1f6f"
dependencies = [
"anyhow",
"anymap",
@ -572,8 +587,6 @@ dependencies = [
"indexmap",
"js-sys",
"log",
"proc-macro-hack",
"proc-macro-nested",
"ryu",
"serde",
"serde_json",
@ -588,12 +601,10 @@ dependencies = [
[[package]]
name = "yew-macro"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61a9a452e63b6222b28b426dafbc6b207192e0127cdb93324cc7407b8c7e1768"
source = "git+https://github.com/yewstack/yew.git?rev=1507c21b#1507c21b39e7941f7b9fe8ff5af792cf55be1f6f"
dependencies = [
"boolinator",
"lazy_static",
"proc-macro-hack",
"proc-macro2",
"quote",
"syn",

View file

@ -11,8 +11,9 @@ crate-type = ["cdylib"]
[dependencies]
wasm-bindgen = "^0.2"
yew = "0.17"
yew = { git = "https://github.com/yewstack/yew.git", rev = "1507c21b" }
web-sys = "0.3"
id_tree = "1.7"
[build-dependencies]
regex = { version = "1", default-features = false, features = ["std"] }

View file

@ -12,6 +12,10 @@
margin: 0;
padding: 0;
}
.bp3-tree {
width: 350px;
}
</style>
</head>
<body></body>

View file

@ -1,7 +1,8 @@
use crate::buttons::Button;
use crate::collapse::Collapse;
use crate::forms::controls::Switch;
use crate::tree::Tree;
use crate::icon::IconName;
use crate::tree::{NodeData, Tree};
use yew::prelude::*;
const DARK_BG_COLOR: &str = "#30404d";
@ -14,12 +15,15 @@ pub struct App {
counter: i64,
dark_theme: bool,
collapsed: bool,
tree: id_tree::Tree<NodeData<i32>>,
node_dir1_id: id_tree::NodeId,
}
pub enum Msg {
AddOne,
ToggleLight,
ToggleCollapse,
ExpandNode(id_tree::NodeId),
}
impl Component for App {
@ -27,19 +31,91 @@ impl Component for App {
type Properties = ();
fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self {
let mut tree = id_tree::TreeBuilder::new().build();
let root_id = tree
.insert(
id_tree::Node::new(NodeData {
icon: None,
label: "".into(),
is_selected: false,
is_expanded: false,
has_caret: true,
disabled: false,
on_collapse: None,
on_expand: None,
data: 0,
}),
id_tree::InsertBehavior::AsRoot,
)
.unwrap();
let dir1 = tree
.insert(
id_tree::Node::new(NodeData {
icon: Some(IconName::FolderClose),
label: "Directory 1".into(),
is_selected: false,
is_expanded: false,
has_caret: true,
disabled: false,
on_collapse: Some(link.callback(|(node_id, _)| Msg::ExpandNode(node_id))),
on_expand: Some(link.callback(|(node_id, _)| Msg::ExpandNode(node_id))),
data: 1,
}),
id_tree::InsertBehavior::UnderNode(&root_id),
)
.unwrap();
tree.insert(
id_tree::Node::new(NodeData {
icon: Some(IconName::Document),
label: "File 1".into(),
is_selected: false,
is_expanded: false,
has_caret: false,
disabled: false,
on_collapse: None,
on_expand: None,
data: 2,
}),
id_tree::InsertBehavior::UnderNode(&root_id),
)
.unwrap();
tree.insert(
id_tree::Node::new(NodeData {
icon: None,
label: "File 2".into(),
is_selected: false,
is_expanded: false,
has_caret: false,
disabled: false,
on_collapse: None,
on_expand: None,
data: 3,
}),
id_tree::InsertBehavior::UnderNode(&dir1),
)
.unwrap();
App {
link,
counter: 0,
dark_theme: true,
collapsed: true,
tree,
node_dir1_id: dir1,
}
}
fn update(&mut self, msg: Self::Message) -> ShouldRender {
match msg {
Msg::AddOne => self.counter += 1,
Msg::ToggleLight => self.dark_theme = !self.dark_theme,
Msg::ToggleLight => self.dark_theme ^= true,
Msg::ToggleCollapse => self.collapsed ^= true,
Msg::ExpandNode(node_id) => {
crate::log!("{:?}", node_id);
crate::log!("{:?}", self.node_dir1_id);
let node = self.tree.get_mut(&node_id).unwrap();
node.data_mut().is_expanded ^= true;
}
}
true
}
@ -101,7 +177,7 @@ impl Component for App {
</Collapse>
</div>
<div>
<Tree />
<Tree<i32> tree=self.tree.clone() />
</div>
</div>
}

View file

@ -2,6 +2,12 @@ use yew::prelude::*;
include!(concat!(env!("OUT_DIR"), "/icon_svg_paths.rs"));
impl Default for IconName {
fn default() -> Self {
IconName::Blank
}
}
pub const SIZE_STANDARD: i32 = 16;
pub const SIZE_LARGE: i32 = 20;
@ -21,7 +27,7 @@ pub struct Props {
#[prop_or(16)]
pub icon_size: i32,
#[prop_or_default]
pub onclick: Callback<MouseEvent>,
pub onclick: Option<Callback<MouseEvent>>,
}
impl Component for Icon {
@ -59,23 +65,18 @@ impl Component for Icon {
} else {
SIZE_STANDARD
};
let icon_string = format!("{:?}", self.props.icon);
html! {
<span class="bp3-icon" onclick={self.props.onclick.clone()}>
<span class=class onclick?={self.props.onclick.clone()}>
<svg
fill={self.props.color.clone().unwrap_or_default()}
data-icon={format!("{:?}", self.props.icon)}
fill?={self.props.color.clone()}
data-icon={icon_string.clone()}
width={self.props.icon_size}
height={self.props.icon_size}
viewBox={format!("0 0 {x} {x}", x=pixel_grid_size)}
>
{
if let Some(title) = self.props.title.clone() {
html!(<desc>{title}</desc>)
} else {
Default::default()
}
}
<desc>{self.props.title.clone().unwrap_or_else(|| icon_string)}</desc>
{
paths.iter()
.map(|x| html! {

View file

@ -2,19 +2,33 @@ use crate::collapse::Collapse;
use crate::icon::{Icon, IconName};
use yew::prelude::*;
pub struct Tree {
props: Props,
pub struct Tree<T: Clone + PartialEq> {
props: Props<T>,
}
#[derive(Clone, PartialEq, Properties)]
pub struct Props {
pub struct Props<T: Clone + PartialEq> {
#[prop_or_default]
is_expanded: bool,
pub is_expanded: bool,
pub tree: id_tree::Tree<NodeData<T>>,
}
impl Component for Tree {
#[derive(Clone, PartialEq)]
pub struct NodeData<T: Clone + PartialEq> {
pub disabled: bool,
pub has_caret: bool,
pub icon: Option<IconName>,
pub is_expanded: bool,
pub is_selected: bool,
pub label: yew::virtual_dom::VNode,
pub on_collapse: Option<Callback<(id_tree::NodeId, MouseEvent)>>,
pub on_expand: Option<Callback<(id_tree::NodeId, MouseEvent)>>,
pub data: T,
}
impl<T: Clone + PartialEq + 'static> Component for Tree<T> {
type Message = ();
type Properties = Props;
type Properties = Props<T>;
fn create(props: Self::Properties, _link: ComponentLink<Self>) -> Self {
Tree { props }
@ -34,12 +48,46 @@ impl Component for Tree {
}
fn view(&self) -> Html {
let mut nodes = Vec::new();
if let Some(root_id) = self.props.tree.root_node_id() {
for node_id in self.props.tree.children_ids(root_id).unwrap() {
let node = self.props.tree.get(node_id).unwrap();
let data = node.data();
let on_collapse = {
let node_id = node_id.clone();
data.on_collapse
.clone()
.map(move |x| x.reform(move |event| (node_id.clone(), event)))
};
let on_expand = {
let node_id = node_id.clone();
data.on_expand
.clone()
.map(move |x| x.reform(move |event| (node_id.clone(), event)))
};
nodes.push(html! {
<TreeNode
disabled=data.disabled
has_caret=data.has_caret
icon=data.icon
is_expanded=data.is_expanded
is_selected=data.is_selected
label=data.label.clone()
on_collapse=on_collapse
on_expand=on_expand
>
{"Example Stuff"}
</TreeNode>
});
}
}
html! {
<div class="bp3-tree">
<ul class="bp3-tree-node-list">
<TreeNode is_expanded=self.props.is_expanded has_caret=true>
{"Example Stuff"}
</TreeNode>
{nodes}
</ul>
</div>
}
@ -53,10 +101,22 @@ pub struct TreeNode {
#[derive(Clone, PartialEq, Properties)]
pub struct TreeNodeProps {
#[prop_or_default]
pub is_expanded: bool,
pub disabled: bool,
#[prop_or_default]
pub has_caret: bool,
#[prop_or_default]
pub icon: Option<IconName>,
#[prop_or_default]
pub is_expanded: bool,
#[prop_or_default]
pub is_selected: bool,
#[prop_or_default]
pub label: yew::virtual_dom::VNode,
#[prop_or_default]
pub on_collapse: Option<Callback<MouseEvent>>,
#[prop_or_default]
pub on_expand: Option<Callback<MouseEvent>>,
#[prop_or_default]
pub children: html::Children,
}
@ -87,8 +147,27 @@ impl Component for TreeNode {
<div class="bp3-tree-node-content">
{
if self.props.has_caret {
let mut class = "bp3-tree-node-caret ".to_string();
class.push_str(if self.props.is_expanded {
"bp3-tree-node-caret-open"
} else {
"bp3-tree-node-caret-closed"
});
html! {
<Icon class="bp3-tree-node-caret" icon=IconName::ChevronRight />
<Icon
class=class
icon=IconName::ChevronRight
onclick={
if self.props.disabled {
None
} else if self.props.is_expanded {
self.props.on_collapse.clone()
} else {
self.props.on_expand.clone()
}
}
/>
}
} else {
html! {
@ -96,7 +175,8 @@ impl Component for TreeNode {
}
}
}
{"content"}
<Icon class="bp3-tree-node-icon" icon=self.props.icon.unwrap_or_default() />
<span class="bp3-tree-node-label">{self.props.label.clone()}</span>
</div>
<Collapse is_open=self.props.is_expanded>
{self.props.children.clone()}