Skip to main content

perspective_viewer/ui/containers/
control_group.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 wasm_bindgen::JsCast;
14use web_sys::{Event, MouseEvent};
15use yew::{
16    Callback, Html, Properties, function_component, html, use_callback, use_effect_with,
17    use_mut_ref, use_node_ref,
18};
19
20use crate::ui::form::intl_label::IntlLabel;
21
22#[derive(Properties, PartialEq)]
23pub struct ControlGroupProps {
24    pub group_key: String,
25
26    pub open: bool,
27
28    pub on_toggle: Callback<(String, bool)>,
29
30    pub children: Html,
31}
32
33#[function_component]
34pub fn ControlGroup(props: &ControlGroupProps) -> Html {
35    let ontoggle = use_callback(
36        (props.group_key.clone(), props.on_toggle.clone()),
37        |event: Event, (group_key, on_toggle)| {
38            if let Some(el) = event
39                .target()
40                .and_then(|x| x.dyn_into::<web_sys::Element>().ok())
41            {
42                on_toggle.emit((group_key.clone(), el.has_attribute("open")));
43            }
44        },
45    );
46
47    let onclick = use_callback((), |event: MouseEvent, _| {
48        if !event.shift_key() {
49            return;
50        }
51
52        event.prevent_default();
53        let Some(details) = event
54            .target()
55            .and_then(|x| x.dyn_into::<web_sys::Element>().ok())
56            .and_then(|x| x.closest("details.control-group").ok().flatten())
57        else {
58            return;
59        };
60
61        let Some(scope) = details.closest(".tab-section").ok().flatten() else {
62            return;
63        };
64
65        let expand = !details.has_attribute("open");
66        let Ok(groups) = scope.query_selector_all("details.control-group") else {
67            return;
68        };
69
70        for i in 0..groups.length() {
71            let Some(el) = groups
72                .item(i)
73                .and_then(|x| x.dyn_into::<web_sys::Element>().ok())
74            else {
75                continue;
76            };
77
78            if expand {
79                let _ = el.set_attribute("open", "");
80            } else {
81                let _ = el.remove_attribute("open");
82            }
83        }
84    });
85
86    let details_ref = use_node_ref();
87    {
88        let details_ref = details_ref.clone();
89        use_effect_with(
90            (props.group_key.clone(), props.on_toggle.clone()),
91            move |(group_key, on_toggle)| {
92                let group_key = group_key.clone();
93                let on_toggle = on_toggle.clone();
94                move || {
95                    if let Some(el) = details_ref.cast::<web_sys::Element>() {
96                        on_toggle.emit((group_key, el.has_attribute("open")));
97                    }
98                }
99            },
100        );
101    }
102
103    let open = *use_mut_ref(|| props.open).borrow();
104    html! {
105        <details ref={details_ref} class="control-group" {open} {ontoggle}>
106            <summary {onclick}>
107                <span class="control-group-chevron shift-alt-icon" />
108                <IntlLabel name={props.group_key.clone()} group=true />
109            </summary>
110            { props.children.clone() }
111        </details>
112    }
113}