From 244bbdd3cc77076b6e61211dcaa38fe8c8da0e14 Mon Sep 17 00:00:00 2001 From: Cecile Tonglet Date: Sat, 12 Sep 2020 11:08:17 +0200 Subject: [PATCH] WIP: Tree and Collapse --- Cargo.lock | 1 + Cargo.toml | 1 + src/app.rs | 47 +++++++++++- src/collapse.rs | 199 ++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 7 +- src/tree.rs | 80 +++++++++++++++++++ 6 files changed, 330 insertions(+), 5 deletions(-) create mode 100644 src/collapse.rs create mode 100644 src/tree.rs diff --git a/Cargo.lock b/Cargo.lock index 7040016..0cec14a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -33,6 +33,7 @@ name = "blueprint-rs" version = "0.1.0" dependencies = [ "wasm-bindgen", + "web-sys", "yew", ] diff --git a/Cargo.toml b/Cargo.toml index 33d8d9a..65c3af9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,3 +12,4 @@ crate-type = ["cdylib"] [dependencies] wasm-bindgen = "^0.2" yew = "0.17" +web-sys = "0.3" diff --git a/src/app.rs b/src/app.rs index e64be71..5bd80b9 100644 --- a/src/app.rs +++ b/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, 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! {
-

{"Counter: "} { self.counter }

- - +

{"Counter: "} { self.counter }

+
+ +
+
+ +
+
+ + +
+                            
{"[INFO]: Installing wasm-bindgen..."}
+
{"[INFO]: Optional fields missing from Cargo.toml: 'description', 'repository', and 'license'. These are not necessary, but recommended"}
+
{"[INFO]: :-) Done in 0.69s"}
+
{"[INFO]: :-) Your wasm pkg is ready to publish at /home/cecile/repos/blueprint-rs/./static."}
+
{" Index: enabled, Upload: disabled, Cache: disabled, Cors: enabled, Range: enabled, Sort: enabled, Threads: 3"}
+
{" Auth: disabled, Compression: disabled"}
+
{" https: disabled, Cert: , Cert-Password: "}
+
{" Root: /home/cecile/repos/blueprint-rs,"}
+
{" TryFile404: "}
+
{" Address: http://0.0.0.0:8000"}
+
{" ======== [2020-09-07 20:39:46] ========"}
+
{"[2020-09-07 20:39:46] - 127.0.0.1 - 200 - GET /"}
+
{"[2020-09-07 20:39:46] - 127.0.0.1 - 200 - GET /static/blueprint.css"}
+
{"[2020-09-07 20:39:46] - 127.0.0.1 - 200 - GET /static/wasm.js"}
+
{"[2020-09-07 20:39:46] - 127.0.0.1 - 200 - GET /static/wasm_bg.wasm"}
+
+
+
} } diff --git a/src/collapse.rs b/src/collapse.rs new file mode 100644 index 0000000..e5204e4 --- /dev/null +++ b/src/collapse.rs @@ -0,0 +1,199 @@ +use std::time::Duration; +use web_sys::Element; +use yew::prelude::*; +use yew::services::timeout::*; + +pub struct Collapse { + height: Option, + height_when_open: Option, + animation_state: AnimationState, + contents_ref: NodeRef, + callback_delayed_state_change: Callback<()>, + handle_delayed_state_change: Option, + props: Props, + link: ComponentLink, +} + +#[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 { + 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::().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! { +
+
+ { + if should_render_children { + self.props.children.clone() + } else { + Default::default() + } + } +
+
+ } + } +} diff --git a/src/lib.rs b/src/lib.rs index 2efe1c0..453ca34 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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()); }}; } diff --git a/src/tree.rs b/src/tree.rs new file mode 100644 index 0000000..250d0cc --- /dev/null +++ b/src/tree.rs @@ -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 { + 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! { +
+
    + +
+
+ } + } +} + +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 { + 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! { +
  • +
    + {"content"} +
    + // missing +
  • + } + } +}