Skip to main content

perspective_viewer/components/
new_panel_menu.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 std::rc::Rc;
14
15use yew::prelude::*;
16
17/// A pick in the "New" menu: a fresh panel on a hosted `Table`, or a copy of
18/// an existing panel (the same operation as the context menu's "Duplicate").
19#[derive(Clone, PartialEq)]
20pub enum NewPanelPick {
21    FromTable { client: String, table: String },
22    FromPanel(String),
23}
24
25/// `(client name, its hosted table names)` per loaded client — the resolved
26/// output of `queries::fetch_hosted_tables`.
27pub type HostedTables = Rc<Vec<(String, Vec<String>)>>;
28
29/// `(panel id, title)` per panel, in layout-insertion order.
30pub type PanelLabels = Rc<Vec<(String, Option<String>)>>;
31
32#[derive(Properties, PartialEq)]
33pub struct NewPanelMenuProps {
34    /// `None` while the hosted-table fetch is in flight.
35    pub tables: Option<HostedTables>,
36    pub panels: PanelLabels,
37    pub callback: Callback<NewPanelPick>,
38}
39
40/// The "New" menu body shared by the status bar dropdown and the context
41/// menu's "New" flyout.
42#[function_component]
43pub fn NewPanelMenu(props: &NewPanelMenuProps) -> Html {
44    let pick = |value: NewPanelPick| props.callback.reform(move |_: MouseEvent| value.clone());
45
46    let tables = match &props.tables {
47        None => html! { <span class="no-results">{ "Loading..." }</span> },
48        Some(clients) if clients.iter().all(|(_, tables)| tables.is_empty()) => {
49            html! { <span class="no-results">{ "No tables" }</span> }
50        },
51        Some(clients) => {
52            let multi = clients.len() > 1;
53            clients
54                .iter()
55                .map(|(client, tables)| {
56                    let rows = tables.iter().map(|table| {
57                        let onmousedown = pick(NewPanelPick::FromTable {
58                            client: client.clone(),
59                            table: table.clone(),
60                        });
61                        html! { <span class="dropdown-menu-item" {onmousedown}>{ table }</span> }
62                    });
63
64                    html! {
65                        <>
66                            if multi { <span class="dropdown-group-sublabel">{ client }</span> }
67                            { for rows }
68                        </>
69                    }
70                })
71                .collect::<Html>()
72        },
73    };
74
75    let panels = if props.panels.is_empty() {
76        html! { <span class="no-results">{ "No panels" }</span> }
77    } else {
78        props
79            .panels
80            .iter()
81            .map(|(id, title)| {
82                let onmousedown = pick(NewPanelPick::FromPanel(id.clone()));
83                let label = title.clone().unwrap_or_else(|| id.clone());
84                html! { <span class="dropdown-menu-item" {onmousedown}>{ label }</span> }
85            })
86            .collect::<Html>()
87    };
88
89    html! {
90        <div class="new-panel-menu">
91            <span class="dropdown-group-label" data-label="new-from-table" />
92            <div class="dropdown-group-container">{ tables }</div>
93            <span class="dropdown-group-label" data-label="new-from-panel" />
94            <div class="dropdown-group-container">{ panels }</div>
95        </div>
96    }
97}