mirror of
https://github.com/yewprint/yewprint
synced 2024-11-22 11:33:04 +00:00
WIP: Tree and Collapse
This commit is contained in:
parent
b267c5f1ba
commit
244bbdd3cc
6 changed files with 330 additions and 5 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -33,6 +33,7 @@ name = "blueprint-rs"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
"yew",
|
||||
]
|
||||
|
||||
|
|
|
@ -12,3 +12,4 @@ crate-type = ["cdylib"]
|
|||
[dependencies]
|
||||
wasm-bindgen = "^0.2"
|
||||
yew = "0.17"
|
||||
web-sys = "0.3"
|
||||
|
|
47
src/app.rs
47
src/app.rs
|
@ -1,4 +1,5 @@
|
|||
use crate::buttons::*;
|
||||
use crate::collapse::*;
|
||||
use crate::forms::controls::*;
|
||||
use yew::prelude::*;
|
||||
|
||||
|
@ -11,11 +12,13 @@ pub struct App {
|
|||
link: ComponentLink<Self>,
|
||||
counter: i64,
|
||||
dark_theme: bool,
|
||||
collapsed: bool,
|
||||
}
|
||||
|
||||
pub enum Msg {
|
||||
AddOne,
|
||||
ToggleLight,
|
||||
ToggleCollapse,
|
||||
}
|
||||
|
||||
impl Component for App {
|
||||
|
@ -26,7 +29,8 @@ impl Component for App {
|
|||
App {
|
||||
link,
|
||||
counter: 0,
|
||||
dark_theme: false,
|
||||
dark_theme: true,
|
||||
collapsed: true,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -34,6 +38,7 @@ impl Component for App {
|
|||
match msg {
|
||||
Msg::AddOne => self.counter += 1,
|
||||
Msg::ToggleLight => self.dark_theme = !self.dark_theme,
|
||||
Msg::ToggleCollapse => self.collapsed ^= true,
|
||||
}
|
||||
true
|
||||
}
|
||||
|
@ -56,9 +61,43 @@ impl Component for App {
|
|||
|
||||
html! {
|
||||
<div class={class} style={style}>
|
||||
<p> {"Counter: "} { self.counter }</p>
|
||||
<Button onclick=self.link.callback(|_| Msg::AddOne)>{ "Add 1" }</Button>
|
||||
<Switch onclick=self.link.callback(|_| Msg::ToggleLight) checked={self.dark_theme} label="Dark theme"/>
|
||||
<p> {"Counter: "} { self.counter }</p>
|
||||
<div>
|
||||
<Button onclick=self.link.callback(|_| Msg::AddOne)>{ "Add 1" }</Button>
|
||||
</div>
|
||||
<div>
|
||||
<Switch
|
||||
onclick=self.link.callback(|_| Msg::ToggleLight)
|
||||
checked=self.dark_theme
|
||||
label="Dark theme"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Button onclick=self.link.callback(|_| Msg::ToggleCollapse)>
|
||||
{"Toggle collapse"}
|
||||
</Button>
|
||||
<Collapse
|
||||
is_open=!self.collapsed
|
||||
>
|
||||
<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>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
|
199
src/collapse.rs
Normal file
199
src/collapse.rs
Normal file
|
@ -0,0 +1,199 @@
|
|||
use std::time::Duration;
|
||||
use web_sys::Element;
|
||||
use yew::prelude::*;
|
||||
use yew::services::timeout::*;
|
||||
|
||||
pub struct Collapse {
|
||||
height: Option<String>,
|
||||
height_when_open: Option<i32>,
|
||||
animation_state: AnimationState,
|
||||
contents_ref: NodeRef,
|
||||
callback_delayed_state_change: Callback<()>,
|
||||
handle_delayed_state_change: Option<TimeoutTask>,
|
||||
props: Props,
|
||||
link: ComponentLink<Self>,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Properties)]
|
||||
pub struct Props {
|
||||
#[prop_or_default]
|
||||
pub is_open: bool,
|
||||
#[prop_or_default]
|
||||
pub children: html::Children,
|
||||
#[prop_or_default]
|
||||
pub keep_children_mounted: bool,
|
||||
#[prop_or_else(|| Duration::from_millis(200))]
|
||||
pub transition_duration: Duration,
|
||||
}
|
||||
|
||||
enum AnimationState {
|
||||
OpenStart,
|
||||
Opening,
|
||||
Open,
|
||||
ClosingStart,
|
||||
Closing,
|
||||
Closed,
|
||||
}
|
||||
|
||||
pub enum Message {
|
||||
DelayedStateChange,
|
||||
}
|
||||
|
||||
impl Component for Collapse {
|
||||
type Message = Message;
|
||||
type Properties = Props;
|
||||
|
||||
fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self {
|
||||
Collapse {
|
||||
height: None,
|
||||
height_when_open: None,
|
||||
animation_state: if props.is_open {
|
||||
AnimationState::Open
|
||||
} else {
|
||||
AnimationState::Closed
|
||||
},
|
||||
contents_ref: NodeRef::default(),
|
||||
callback_delayed_state_change: link.callback(|_| Message::DelayedStateChange),
|
||||
handle_delayed_state_change: None,
|
||||
props,
|
||||
link,
|
||||
}
|
||||
}
|
||||
|
||||
fn update(&mut self, msg: Self::Message) -> ShouldRender {
|
||||
match msg {
|
||||
Message::DelayedStateChange => match self.animation_state {
|
||||
AnimationState::Opening => {
|
||||
crate::log!("open");
|
||||
self.animation_state = AnimationState::Open;
|
||||
self.height = Some("auto".to_string());
|
||||
true
|
||||
}
|
||||
AnimationState::Closing => {
|
||||
crate::log!("closed");
|
||||
self.animation_state = AnimationState::Closed;
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn change(&mut self, props: Self::Properties) -> ShouldRender {
|
||||
if self.props != props {
|
||||
if props.is_open {
|
||||
match self.animation_state {
|
||||
AnimationState::Open | AnimationState::Opening => {}
|
||||
_ => {
|
||||
crate::log!("openstart");
|
||||
self.animation_state = AnimationState::OpenStart;
|
||||
self.height = self.height_when_open.as_ref().map(|x| format!("{}px", x));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
match self.animation_state {
|
||||
AnimationState::Closed | AnimationState::Closing => {}
|
||||
_ => {
|
||||
crate::log!("closingstart");
|
||||
self.animation_state = AnimationState::ClosingStart;
|
||||
self.height = self.height_when_open.as_ref().map(|x| format!("{}px", x));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.props = props;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn rendered(&mut self, _first_render: bool) {
|
||||
let client_height = self.contents_ref.cast::<Element>().unwrap().client_height();
|
||||
|
||||
if client_height > 0 {
|
||||
self.height_when_open = Some(client_height);
|
||||
}
|
||||
|
||||
match self.animation_state {
|
||||
AnimationState::OpenStart => {
|
||||
crate::log!("openstart -> opening {}", client_height);
|
||||
self.animation_state = AnimationState::Opening;
|
||||
//self.height = Some(format!("{}px", client_height));
|
||||
self.height_when_open = Some(client_height);
|
||||
self.handle_delayed_state_change =
|
||||
Some(yew::services::timeout::TimeoutService::spawn(
|
||||
self.props.transition_duration,
|
||||
self.callback_delayed_state_change.clone(),
|
||||
));
|
||||
}
|
||||
AnimationState::ClosingStart => {
|
||||
crate::log!("closingstart -> closing {}", client_height);
|
||||
self.animation_state = AnimationState::Closing;
|
||||
self.height = Some("0px".to_string());
|
||||
self.height_when_open = Some(client_height);
|
||||
self.handle_delayed_state_change =
|
||||
Some(yew::services::timeout::TimeoutService::spawn(
|
||||
self.props.transition_duration,
|
||||
self.callback_delayed_state_change.clone(),
|
||||
));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn view(&self) -> Html {
|
||||
let is_content_visible = !matches!(self.animation_state, AnimationState::Closed);
|
||||
crate::log!("is_content_visible: {}", is_content_visible);
|
||||
let should_render_children = is_content_visible || self.props.keep_children_mounted;
|
||||
let display_with_transform = is_content_visible
|
||||
&& !matches!(
|
||||
self.animation_state,
|
||||
AnimationState::Closing | AnimationState::ClosingStart
|
||||
);
|
||||
let is_auto_height = matches!(self.height.as_deref(), Some("auto"));
|
||||
|
||||
let mut container_style = String::new();
|
||||
if is_content_visible {
|
||||
if let Some(ref height) = self.height {
|
||||
container_style.push_str("height: ");
|
||||
container_style.push_str(height);
|
||||
container_style.push_str("; ");
|
||||
}
|
||||
}
|
||||
if is_auto_height {
|
||||
container_style.push_str("overflow-y: visible; ");
|
||||
container_style.push_str("transition: none 0s ease 0s; ");
|
||||
}
|
||||
container_style.push_str("border: red 2px solid;");
|
||||
|
||||
let mut content_style = String::new();
|
||||
if display_with_transform {
|
||||
content_style.push_str("transform: translateY(0px); ");
|
||||
} else if let Some(ref height_when_open) = self.height_when_open {
|
||||
content_style.push_str(&format!("transform: translateY(-{}px); ", height_when_open));
|
||||
}
|
||||
if is_auto_height {
|
||||
content_style.push_str("transition: none 0s ease 0s; ");
|
||||
}
|
||||
|
||||
html! {
|
||||
<div class="bp3-collapse" style={container_style}>
|
||||
<div
|
||||
class="bp3-collapse-body"
|
||||
style={content_style}
|
||||
aria-hidden={!is_content_visible && self.props.keep_children_mounted}
|
||||
ref={self.contents_ref.clone()}
|
||||
>
|
||||
{
|
||||
if should_render_children {
|
||||
self.props.children.clone()
|
||||
} else {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,12 +1,17 @@
|
|||
#![recursion_limit = "512"]
|
||||
|
||||
mod app;
|
||||
mod buttons;
|
||||
mod collapse;
|
||||
mod forms;
|
||||
mod tree;
|
||||
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! log {
|
||||
($s:expr $(,$args:expr)*) => {{
|
||||
ConsoleService::log(format!($s $(,$args)*).as_str());
|
||||
yew::services::ConsoleService::log(format!($s $(,$args)*).as_str());
|
||||
}};
|
||||
}
|
||||
|
||||
|
|
80
src/tree.rs
Normal file
80
src/tree.rs
Normal file
|
@ -0,0 +1,80 @@
|
|||
use yew::prelude::*;
|
||||
|
||||
pub struct Tree {
|
||||
props: Props,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Properties)]
|
||||
pub struct Props {}
|
||||
|
||||
impl Component for Tree {
|
||||
type Message = ();
|
||||
type Properties = Props;
|
||||
|
||||
fn create(props: Self::Properties, _link: ComponentLink<Self>) -> Self {
|
||||
Tree { 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! {
|
||||
<div class="bp3-tree">
|
||||
<ul class="bp3-tree-node-list">
|
||||
<TreeNode/>
|
||||
</ul>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TreeNode {
|
||||
props: TreeNodeProps,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Properties)]
|
||||
pub struct TreeNodeProps {}
|
||||
|
||||
impl Component for TreeNode {
|
||||
type Message = ();
|
||||
type Properties = TreeNodeProps;
|
||||
|
||||
fn create(props: Self::Properties, _link: ComponentLink<Self>) -> Self {
|
||||
TreeNode { 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! {
|
||||
<li class="bp3-tree-node">
|
||||
<div class="bp3-tree-node-content">
|
||||
{"content"}
|
||||
</div>
|
||||
// missing <Collapse/>
|
||||
</li>
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue