Skip to main content

perspective_viewer/ui/containers/
sidebar.rs

1// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
2// ┃ ██████ ██████ ██████       █      █      █      █      █ █▄  ▀███ █       ┃
3// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█  ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄  ▀█ █ ▀▀▀▀▀ ┃
4// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄   █ ▄▄▄▄▄ ┃
5// ┃ █      ██████ █  ▀█▄       █ ██████      █      ███▌▐███ ███████▄ █       ┃
6// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
7// ┃ Copyright (c) 2017, the Perspective Authors.                              ┃
8// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
9// ┃ This file is part of the Perspective library, distributed under the terms ┃
10// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
13use perspective_client::clone;
14use wasm_bindgen::JsCast;
15use wasm_bindgen::prelude::Closure;
16use web_sys::HtmlElement;
17use yew::{
18    Callback, Children, Html, Properties, function_component, html, use_effect_with, use_mut_ref,
19    use_node_ref,
20};
21
22use crate::js::{ResizeObserver, ResizeObserverEntry};
23use crate::ui::containers::sidebar_close_button::SidebarCloseButton;
24
25#[derive(PartialEq, Clone, Properties)]
26pub struct SidebarProps {
27    pub children: Children,
28
29    pub on_close: Callback<()>,
30    pub id_prefix: String,
31    pub width_override: Option<i32>,
32    pub selected_tab: Option<usize>,
33    pub header: Html,
34
35    #[prop_or_default]
36    pub auto_width: f64,
37
38    #[prop_or_default]
39    pub on_auto_width: Callback<f64>,
40
41    #[prop_or_default]
42    pub is_pinned: bool,
43
44    #[prop_or_default]
45    pub on_toggle_pin: Option<Callback<()>>,
46}
47
48/// Sidebars are designed to live in a `SplitPanel`.
49#[function_component]
50pub fn Sidebar(p: &SidebarProps) -> Html {
51    let id = &p.id_prefix;
52    let noderef = use_node_ref();
53
54    let live_props = use_mut_ref(|| (Callback::<f64>::default(), None::<i32>));
55    *live_props.borrow_mut() = (p.on_auto_width.clone(), p.width_override);
56    use_effect_with((), {
57        clone!(noderef, live_props);
58        move |_| {
59            let closure =
60                Closure::<dyn FnMut(js_sys::Array)>::new(move |entries: js_sys::Array| {
61                    let (on_auto_width, width_override) = live_props.borrow().clone();
62                    if width_override.is_none() {
63                        for entry in entries.iter() {
64                            let entry: ResizeObserverEntry = entry.unchecked_into();
65                            on_auto_width.emit(entry.content_rect().width());
66                        }
67                    }
68                });
69
70            let observer = ResizeObserver::new(closure.as_ref().unchecked_ref());
71            let elem = noderef.cast::<HtmlElement>();
72            if let Some(elem) = &elem {
73                observer.observe(elem);
74            }
75
76            move || {
77                if let Some(elem) = &elem {
78                    observer.unobserve(elem);
79                }
80
81                drop(closure);
82            }
83        }
84    });
85
86    let auto_width = if p.width_override.is_none() {
87        p.auto_width
88    } else {
89        0.0
90    };
91
92    let width_style = format!("min-width: 200px; width: {}px", auto_width);
93    let pin_button = p.on_toggle_pin.as_ref().map(|cb| {
94        let onclick = {
95            let cb = cb.clone();
96            Callback::from(move |_: web_sys::MouseEvent| cb.emit(()))
97        };
98
99        let mut class = yew::classes!("sidebar_pin_button");
100        if p.is_pinned {
101            class.push("is-pinned");
102        }
103
104        html! {
105            <span
106                id={format!("{id}_pin_button")}
107                {class}
108                title={if p.is_pinned { "Unpin" } else { "Pin" }}
109                {onclick}
110            />
111        }
112    });
113
114    html! {
115        <>
116            <SidebarCloseButton id={format!("{id}_close_button")} on_close_sidebar={&p.on_close} />
117            <div class="sidebar_column" id={format!("{id}_sidebar")} ref={noderef}>
118                <div class="sidebar_header">{ p.header.clone() }{ pin_button }</div>
119                <div class="sidebar_border" id={format!("{id}_border")} />
120                { p.children.iter().collect::<Html>() }
121                <div class="sidebar-auto-width" style={width_style} />
122            </div>
123        </>
124    }
125}