Skip to main content

perspective_viewer/components/
settings_panel.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 perspective_client::config::{ViewConfig, ViewConfigUpdate};
16use perspective_js::utils::ApiFuture;
17use yew::prelude::*;
18
19use super::column_selector::ColumnSelector;
20use super::plugin_selector::PluginSelector;
21use crate::components::containers::sidebar_close_button::SidebarCloseButton;
22use crate::config::PluginUpdate;
23use crate::dragdrop::*;
24use crate::presentation::{ColumnLocator, OpenColumnSettings, Presentation};
25use crate::renderer::*;
26use crate::session::column_defaults_update::*;
27use crate::session::*;
28use crate::tasks::can_render_column_styles;
29use crate::utils::*;
30
31#[derive(Clone, Properties)]
32pub struct SettingsPanelProps {
33    pub on_close: Callback<()>,
34    pub on_resize: Rc<PubSub<()>>,
35    pub on_select_column: Callback<ColumnLocator>,
36    pub on_debug: Callback<()>,
37    pub is_debug: bool,
38
39    /// Value props threaded from the root's `RendererProps` / `SessionProps`.
40    pub plugin_name: Option<String>,
41    pub available_plugins: PtrEqRc<Vec<String>>,
42    pub has_table: Option<TableLoadState>,
43    pub named_column_count: usize,
44    pub view_config: PtrEqRc<ViewConfig>,
45    /// Column currently being dragged (if any) — threaded to show drag
46    /// highlights without per-component `DragDrop` PubSub subscriptions.
47    pub drag_column: Option<String>,
48    /// Cloned session metadata snapshot — threaded from `SessionProps`
49    /// so that metadata changes trigger re-renders via prop diffing.
50    pub metadata: SessionMetadataRc,
51    /// Snapshot of the column-settings sidebar state — threaded from
52    /// `PresentationProps` so that open/close triggers re-renders.
53    pub open_column_settings: OpenColumnSettings,
54
55    /// Selected theme name, threaded for PortalModal consumers.
56    pub selected_theme: Option<String>,
57
58    /// State
59    pub dragdrop: DragDrop,
60    pub session: Session,
61    pub renderer: Renderer,
62    pub presentation: Presentation,
63}
64
65impl PartialEq for SettingsPanelProps {
66    fn eq(&self, rhs: &Self) -> bool {
67        self.is_debug == rhs.is_debug
68            && self.plugin_name == rhs.plugin_name
69            && self.available_plugins == rhs.available_plugins
70            && self.has_table == rhs.has_table
71            && self.named_column_count == rhs.named_column_count
72            && self.view_config == rhs.view_config
73            && self.drag_column == rhs.drag_column
74            && self.metadata == rhs.metadata
75            && self.open_column_settings == rhs.open_column_settings
76            && self.selected_theme == rhs.selected_theme
77    }
78}
79
80#[function_component]
81pub fn SettingsPanel(props: &SettingsPanelProps) -> Html {
82    let SettingsPanelProps {
83        dragdrop,
84        presentation,
85        renderer,
86        session,
87        ..
88    } = &props;
89
90    let selected_column = {
91        let locator = props.open_column_settings.locator.clone();
92        let config = &props.view_config;
93        locator.filter(|locator| match locator {
94            ColumnLocator::Table(name) => {
95                locator
96                    .name()
97                    .map(|n| {
98                        config.columns.iter().any(|maybe_col| {
99                            maybe_col.as_ref().map(|col| col == n).unwrap_or_default()
100                        }) || config.group_by.iter().any(|col| col == n)
101                            || config.split_by.iter().any(|col| col == n)
102                            || config.filter.iter().any(|col| col.column() == n)
103                            || config.sort.iter().any(|col| &col.0 == n)
104                    })
105                    .unwrap_or_default()
106                    && can_render_column_styles(&props.renderer, config, &props.metadata, name)
107                        .unwrap_or_default()
108            },
109            _ => true,
110        })
111    };
112
113    let plugin_name = props.plugin_name.clone();
114    let available_plugins = props.available_plugins.clone();
115
116    // Dispatch callback: captures engine handles, constructs config update, renders
117    let on_select_plugin = {
118        clone!(renderer, session, presentation);
119        let session_metadata = props.metadata.clone();
120        Callback::from(move |plugin_name: String| {
121            if !session.is_errored() {
122                let metadata =
123                    renderer.get_next_plugin_metadata(&PluginUpdate::Update(plugin_name));
124
125                let prev_metadata = renderer.metadata();
126                let requirements = metadata.as_ref().unwrap_or(&*prev_metadata);
127                let rollup_features = session_metadata
128                    .get_features()
129                    .map(|x| x.get_group_rollup_modes())
130                    .unwrap();
131
132                let group_rollups = requirements.get_group_rollups(&rollup_features);
133                let mut update = ViewConfigUpdate {
134                    group_rollup_mode: group_rollups.first().cloned(),
135                    ..ViewConfigUpdate::default()
136                };
137
138                update.set_update_column_defaults(
139                    &session_metadata,
140                    &session.get_view_config().columns,
141                    requirements,
142                );
143
144                if session.update_view_config(update).is_ok() {
145                    clone!(renderer, session);
146                    ApiFuture::spawn(async move {
147                        renderer.apply_pending_plugin()?;
148                        renderer.draw(session.validate().await?.create_view()).await
149                    });
150                }
151
152                presentation.set_open_column_settings(None);
153            }
154        })
155    };
156
157    html! {
158        <div id="settings_panel" class="sidebar_column noselect split-panel orient-vertical">
159            if selected_column.is_none() {
160                <SidebarCloseButton
161                    id="settings_close_button"
162                    on_close_sidebar={&props.on_close.clone()}
163                />
164            }
165            <SidebarCloseButton
166                id={if props.is_debug {"debug_close_button"} else {"debug_open_button"}}
167                on_close_sidebar={&props.on_debug}
168            />
169            <PluginSelector {plugin_name} {available_plugins} {on_select_plugin} />
170            <ColumnSelector
171                on_resize={&props.on_resize}
172                on_open_expr_panel={&props.on_select_column}
173                {selected_column}
174                has_table={props.has_table.clone()}
175                named_column_count={props.named_column_count}
176                view_config={props.view_config.clone()}
177                drag_column={props.drag_column.clone()}
178                metadata={props.metadata.clone()}
179                selected_theme={props.selected_theme.clone()}
180                {dragdrop}
181                renderer={renderer.clone()}
182                session={session.clone()}
183            />
184        </div>
185    }
186}