perspective-viewer 5.4.0

A data visualization and analytics component, especially well-suited for large and/or streaming datasets.
Documentation
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ ██████ ██████ ██████       █      █      █      █      █ █▄  ▀███ █       ┃
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█  ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄  ▀█ █ ▀▀▀▀▀ ┃
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄   █ ▄▄▄▄▄ ┃
// ┃ █      ██████ █  ▀█▄       █ ██████      █      ███▌▐███ ███████▄ █       ┃
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
// ┃ Copyright (c) 2017, the Perspective Authors.                              ┃
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
// ┃ This file is part of the Perspective library, distributed under the terms ┃
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

use std::rc::Rc;

use yew::prelude::*;

/// A pick in the "New" menu: a fresh panel on a hosted `Table`, or a copy of
/// an existing panel (the same operation as the context menu's "Duplicate").
#[derive(Clone, PartialEq)]
pub enum NewPanelPick {
    FromTable { client: String, table: String },
    FromPanel(String),
}

/// `(client name, its hosted table names)` per loaded client — the resolved
/// output of `queries::fetch_hosted_tables`.
pub type HostedTables = Rc<Vec<(String, Vec<String>)>>;

/// `(panel id, title)` per panel, in layout-insertion order.
pub type PanelLabels = Rc<Vec<(String, Option<String>)>>;

#[derive(Properties, PartialEq)]
pub struct NewPanelMenuProps {
    /// `None` while the hosted-table fetch is in flight.
    pub tables: Option<HostedTables>,
    pub panels: PanelLabels,
    pub callback: Callback<NewPanelPick>,
}

/// The "New" menu body shared by the status bar dropdown and the context
/// menu's "New" flyout.
#[function_component]
pub fn NewPanelMenu(props: &NewPanelMenuProps) -> Html {
    let pick = |value: NewPanelPick| props.callback.reform(move |_: MouseEvent| value.clone());

    let tables = match &props.tables {
        None => html! { <span class="no-results">{ "Loading..." }</span> },
        Some(clients) if clients.iter().all(|(_, tables)| tables.is_empty()) => {
            html! { <span class="no-results">{ "No tables" }</span> }
        },
        Some(clients) => {
            let multi = clients.len() > 1;
            clients
                .iter()
                .map(|(client, tables)| {
                    let rows = tables.iter().map(|table| {
                        let onmousedown = pick(NewPanelPick::FromTable {
                            client: client.clone(),
                            table: table.clone(),
                        });
                        html! { <span class="dropdown-menu-item" {onmousedown}>{ table }</span> }
                    });

                    html! {
                        <>
                            if multi { <span class="dropdown-group-sublabel">{ client }</span> }
                            { for rows }
                        </>
                    }
                })
                .collect::<Html>()
        },
    };

    let panels = if props.panels.is_empty() {
        html! { <span class="no-results">{ "No panels" }</span> }
    } else {
        props
            .panels
            .iter()
            .map(|(id, title)| {
                let onmousedown = pick(NewPanelPick::FromPanel(id.clone()));
                let label = title.clone().unwrap_or_else(|| id.clone());
                html! { <span class="dropdown-menu-item" {onmousedown}>{ label }</span> }
            })
            .collect::<Html>()
    };

    html! {
        <div class="new-panel-menu">
            <span class="dropdown-group-label" data-label="new-from-table" />
            <div class="dropdown-group-container">{ tables }</div>
            <span class="dropdown-group-label" data-label="new-from-panel" />
            <div class="dropdown-group-container">{ panels }</div>
        </div>
    }
}