@perspective-dev/viewer 5.3.1 → 5.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cdn/perspective-viewer.js +6 -6
- package/dist/cdn/perspective-viewer.js.map +2 -2
- package/dist/css/blueprint.css +1 -1
- package/dist/css/botanical.css +1 -1
- package/dist/css/dracula.css +1 -1
- package/dist/css/eggplant.css +1 -1
- package/dist/css/gruvbox-dark.css +1 -1
- package/dist/css/gruvbox.css +1 -1
- package/dist/css/icons.css +1 -1
- package/dist/css/intl/ar.css +1 -0
- package/dist/css/intl/de.css +1 -1
- package/dist/css/intl/es.css +1 -1
- package/dist/css/intl/fr.css +1 -1
- package/dist/css/intl/ja.css +1 -1
- package/dist/css/intl/pt.css +1 -1
- package/dist/css/intl/zh.css +1 -1
- package/dist/css/intl.css +1 -1
- package/dist/css/ledger.css +1 -1
- package/dist/css/monokai.css +1 -1
- package/dist/css/nord.css +1 -1
- package/dist/css/phosphor.css +1 -1
- package/dist/css/pro-dark.css +1 -1
- package/dist/css/pro.css +1 -1
- package/dist/css/solarized-dark.css +1 -1
- package/dist/css/solarized.css +1 -1
- package/dist/css/themes.css +1 -1
- package/dist/css/vaporwave.css +1 -1
- package/dist/css/velvet.css +1 -1
- package/dist/docs/perspective-docs.json +1 -1
- package/dist/esm/extensions.d.ts +0 -5
- package/dist/esm/perspective-viewer.d.ts +2 -0
- package/dist/esm/perspective-viewer.inline.js +8 -8
- package/dist/esm/perspective-viewer.inline.js.map +2 -2
- package/dist/esm/perspective-viewer.js +5 -5
- package/dist/esm/perspective-viewer.js.map +2 -2
- package/dist/esm/plugin.d.ts +9 -0
- package/dist/esm/ts-rs/AddPanelOptions.d.ts +9 -0
- package/dist/esm/ts-rs/GetViewMode.d.ts +4 -0
- package/dist/esm/ts-rs/GetViewOptions.d.ts +8 -0
- package/dist/esm/ts-rs/RestoreOptions.d.ts +6 -0
- package/dist/esm/ts-rs/RestoreWorkspaceOptions.d.ts +9 -0
- package/dist/esm/ts-rs/ViewOnUpdateResp.d.ts +1 -1
- package/dist/esm/ts-rs/WorkspaceConfigUpdate.d.ts +30 -22
- package/dist/wasm/perspective-viewer.d.ts +137 -104
- package/dist/wasm/perspective-viewer.js +337 -269
- package/dist/wasm/perspective-viewer.wasm +0 -0
- package/dist/wasm/perspective-viewer.wasm.d.ts +20 -30
- package/package.json +7 -7
- package/src/css/_context_menu.css +1 -1
- package/src/css/_dropdown_menu.css +1 -1
- package/src/css/_viewer_bundle.css +7 -8
- package/src/css/column-dropdown.css +0 -3
- package/src/css/column-selector.css +18 -7
- package/src/css/column-settings-panel.css +0 -225
- package/src/css/column-style.css +175 -0
- package/src/css/config-selector.css +19 -11
- package/src/css/filter-dropdown.css +5 -5
- package/src/css/form/debug.css +37 -4
- package/src/css/function-dropdown.css +5 -5
- package/src/css/panel-tab.css +23 -0
- package/src/css/plugin-selector.css +5 -5
- package/src/css/render-warning.css +1 -1
- package/src/css/status-bar.css +63 -21
- package/src/css/type-icon.css +1 -3
- package/src/css/{containers → ui/containers}/context-menu.css +22 -16
- package/src/css/ui/containers/dropdown-menu-items.css +66 -0
- package/src/css/{containers → ui/containers}/dropdown-menu.css +8 -53
- package/src/css/{containers → ui/containers}/tabs.css +2 -1
- package/src/css/viewer.css +32 -26
- package/src/rust/agent/tools.rs +80 -7
- package/src/rust/components/chat_panel.rs +1 -1
- package/src/rust/components/{form/code_editor.rs → code_editor.rs} +4 -2
- package/src/rust/components/column_dropdown.rs +2 -2
- package/src/rust/components/column_selector/active_column.rs +24 -9
- package/src/rust/components/column_selector/aggregate_selector.rs +1 -1
- package/src/rust/components/column_selector/config_selector.rs +2 -2
- package/src/rust/components/column_selector/filter_column.rs +8 -5
- package/src/rust/components/column_selector/inactive_column.rs +4 -2
- package/src/rust/components/column_selector/pivot_column.rs +4 -3
- package/src/rust/components/column_selector/sort_column.rs +7 -4
- package/src/rust/components/column_selector.rs +14 -3
- package/src/rust/components/column_settings_sidebar/style_tab/agg_depth_selector.rs +1 -1
- package/src/rust/components/column_settings_sidebar/style_tab/alignment_field.rs +90 -0
- package/src/rust/components/column_settings_sidebar/style_tab/font_field.rs +267 -0
- package/src/rust/components/column_settings_sidebar/style_tab/primitive_field.rs +16 -18
- package/src/rust/components/column_settings_sidebar/style_tab/symbol/symbol_selector.rs +1 -1
- package/src/rust/components/column_settings_sidebar/style_tab.rs +70 -12
- package/src/rust/components/column_settings_sidebar.rs +3 -4
- package/src/rust/components/copy_dropdown.rs +1 -1
- package/src/rust/components/datetime_column_style/custom.rs +1 -3
- package/src/rust/components/datetime_column_style/simple.rs +1 -2
- package/src/rust/components/datetime_column_style.rs +1 -2
- package/src/rust/components/{form/debug.rs → debug_panel.rs} +112 -55
- package/src/rust/components/{containers/dragdrop_list.rs → dragdrop_list.rs} +2 -10
- package/src/rust/components/export_dropdown.rs +1 -1
- package/src/rust/components/expression_editor.rs +1 -1
- package/src/rust/components/filter_dropdown.rs +2 -2
- package/src/rust/components/function_dropdown.rs +2 -2
- package/src/rust/components/main_panel/frame_theme.rs +3 -59
- package/src/rust/components/main_panel/msg.rs +4 -0
- package/src/rust/components/main_panel/reconcile.rs +9 -4
- package/src/rust/components/main_panel/render.rs +62 -11
- package/src/rust/components/main_panel/update.rs +39 -10
- package/src/rust/components/main_panel.rs +40 -2
- package/src/rust/components/mod.rs +5 -8
- package/src/rust/components/new_panel_menu.rs +97 -0
- package/src/rust/components/number_series_style.rs +2 -3
- package/src/rust/components/panel_menu.rs +61 -87
- package/src/rust/components/panel_tab.rs +100 -43
- package/src/rust/components/plugin_tab.rs +58 -4
- package/src/rust/components/render_warning.rs +2 -2
- package/src/rust/components/{status_bar_counter.rs → rows_counter.rs} +10 -10
- package/src/rust/components/settings_panel.rs +8 -2
- package/src/rust/components/status_bar.rs +113 -19
- package/src/rust/components/status_indicator.rs +9 -14
- package/src/rust/components/{style/mod.rs → style.rs} +32 -15
- package/src/rust/components/style_controls/number_string_format/digits_section.rs +3 -5
- package/src/rust/components/style_controls/number_string_format/misc_section.rs +1 -1
- package/src/rust/components/style_controls/number_string_format/style_section.rs +1 -1
- package/src/rust/components/viewer/msg.rs +43 -30
- package/src/rust/components/viewer/panels.rs +53 -19
- package/src/rust/components/viewer/render.rs +16 -13
- package/src/rust/components/viewer/settings.rs +137 -68
- package/src/rust/components/viewer/snapshots.rs +5 -10
- package/src/rust/components/viewer/wiring.rs +11 -22
- package/src/rust/components/viewer.rs +15 -10
- package/src/rust/components/window_editor.rs +8 -9
- package/src/rust/config/column_config_schema.rs +312 -33
- package/src/rust/config/datetime_column_style.rs +0 -11
- package/src/rust/config/mod.rs +0 -4
- package/src/rust/config/options.rs +50 -0
- package/src/rust/config/workspace_config.rs +70 -24
- package/src/rust/custom_elements/debug_plugin.rs +1 -10
- package/src/rust/custom_elements/mod.rs +0 -3
- package/src/rust/custom_elements/viewer.rs +194 -115
- package/src/rust/js/plugin.rs +3 -87
- package/src/rust/lib.rs +19 -18
- package/src/rust/presentation/column_locator.rs +3 -7
- package/src/rust/presentation.rs +68 -38
- package/src/rust/queries/hosted_tables.rs +38 -0
- package/src/rust/queries/mod.rs +2 -0
- package/src/rust/queries/palette_set.rs +1 -1
- package/src/rust/queries/plugin_column_styles.rs +6 -5
- package/src/rust/renderer/activate.rs +1 -3
- package/src/rust/renderer/dispatch.rs +83 -22
- package/src/rust/renderer/limits.rs +24 -5
- package/src/rust/renderer/plugin_config.rs +201 -41
- package/src/rust/renderer/plugin_store.rs +2 -1
- package/src/rust/renderer/props.rs +7 -1
- package/src/rust/renderer.rs +23 -27
- package/src/rust/session/props.rs +8 -3
- package/src/rust/session/view_subscription.rs +19 -6
- package/src/rust/session.rs +189 -150
- package/src/rust/tasks/create_panel.rs +5 -2
- package/src/rust/tasks/dismiss_render_warning.rs +1 -1
- package/src/rust/tasks/eject.rs +1 -0
- package/src/rust/tasks/pipeline.rs +18 -67
- package/src/rust/tasks/presize_panels.rs +24 -1
- package/src/rust/tasks/restore_and_render.rs +0 -17
- package/src/rust/tasks/restore_panel.rs +129 -30
- package/src/rust/tasks/send_column_config.rs +4 -1
- package/src/rust/tasks/send_plugin_config.rs +1 -1
- package/src/rust/tasks/set_edit_mode.rs +1 -1
- package/src/rust/tasks/sync_update_panels.rs +147 -67
- package/src/rust/tasks/table_lifecycle.rs +7 -1
- package/src/rust/{components → ui}/containers/control_group.rs +25 -3
- package/src/rust/{components → ui}/containers/dropdown_menu.rs +5 -2
- package/src/rust/{components → ui}/containers/mod.rs +0 -4
- package/src/rust/{components → ui}/containers/scroll_panel.rs +0 -22
- package/src/rust/{components → ui}/containers/scroll_panel_item.rs +0 -13
- package/src/rust/{components → ui}/containers/select.rs +0 -6
- package/src/rust/{components → ui}/containers/sidebar.rs +5 -32
- package/src/rust/{components → ui}/containers/split_panel.rs +8 -80
- package/src/rust/{components → ui}/containers/tab_list.rs +15 -9
- package/src/rust/{components → ui}/context_menu.rs +8 -13
- package/src/rust/{presentation/drag_helpers.rs → ui/dragdrop.rs} +26 -31
- package/src/rust/{components → ui}/font_loader.rs +0 -26
- package/src/rust/{components → ui}/form/color_selector.rs +4 -2
- package/src/rust/{config/datetime_column_style/color_mode.rs → ui/form/intl_label.rs} +42 -42
- package/src/rust/{components → ui}/form/mirrored_textarea.rs +0 -53
- package/src/rust/{components → ui}/form/mod.rs +1 -10
- package/src/rust/{components → ui}/form/multi_stop_gradient_selector.rs +4 -5
- package/src/rust/{components → ui}/form/named_value_picker.rs +1 -6
- package/src/rust/{components → ui}/form/number_field.rs +1 -1
- package/src/rust/{components → ui}/form/number_range_field.rs +1 -1
- package/src/rust/{components → ui}/form/optional_field.rs +3 -1
- package/src/rust/{components → ui}/form/palette_selector.rs +4 -5
- package/src/rust/{components → ui}/form/select_enum_field.rs +2 -2
- package/src/rust/{components → ui}/form/select_value_field.rs +2 -2
- package/src/rust/ui/mod.rs +56 -0
- package/src/rust/ui/modal.rs +61 -0
- package/src/rust/{components → ui}/portal.rs +175 -79
- package/src/rust/{components/style/style_provider.rs → ui/style.rs} +4 -34
- package/src/rust/utils/completion.rs +0 -6
- package/src/rust/{config/css_scale.rs → utils/css.rs} +38 -35
- package/src/rust/utils/css_vars.rs +13 -0
- package/src/rust/utils/local_fonts.rs +395 -0
- package/src/rust/utils/mod.rs +4 -0
- package/src/rust/workspace.rs +47 -0
- package/src/svg/boolean-type.svg +3 -2
- package/src/svg/checkbox-checked-icon.svg +4 -7
- package/src/svg/checkbox-hover.svg +3 -3
- package/src/svg/checkbox-off.svg +2 -3
- package/src/svg/checkbox-on.svg +3 -3
- package/src/svg/checkbox-unchecked-icon.svg +3 -3
- package/src/svg/datagrid-select-column.svg +8 -15
- package/src/svg/datagrid-select-region.svg +8 -15
- package/src/svg/datagrid-select-row-tree.svg +7 -12
- package/src/svg/datagrid-select-row.svg +8 -13
- package/src/svg/date-type.svg +4 -23
- package/src/svg/drawer-tab-hover.svg +4 -6
- package/src/svg/drawer-tab-invert-hover.svg +5 -5
- package/src/svg/drawer-tab-invert.svg +3 -5
- package/src/svg/drawer-tab.svg +5 -5
- package/src/svg/mega-menu-icons-candlestick.svg +5 -37
- package/src/svg/mega-menu-icons-ohlc.svg +4 -39
- package/src/svg/mega-menu-icons-sunburst.svg +4 -38
- package/src/svg/mega-menu-icons-treemap.svg +3 -43
- package/src/svg/mega-menu-icons-y-scatter.svg +12 -72
- package/src/svg/new-panel-icon.svg +4 -0
- package/src/svg/number-type.svg +3 -3
- package/src/svg/radio-hover.svg +3 -3
- package/src/svg/radio-off.svg +3 -3
- package/src/svg/radio-on.svg +3 -3
- package/src/svg/status_ok.svg +3 -26
- package/src/svg/status_pending.svg +3 -0
- package/src/svg/string-type.svg +4 -8
- package/src/svg/theme-icon.svg +5 -5
- package/src/themes/blueprint.css +0 -57
- package/src/themes/botanical.css +0 -36
- package/src/themes/defaults.css +1 -24
- package/src/themes/dracula.css +0 -58
- package/src/themes/eggplant.css +0 -58
- package/src/themes/gruvbox-dark.css +0 -65
- package/src/themes/gruvbox.css +1 -39
- package/src/themes/icons.css +2 -8
- package/src/themes/intl/ar.css +198 -0
- package/src/themes/intl/de.css +63 -40
- package/src/themes/intl/es.css +58 -35
- package/src/themes/intl/fr.css +57 -34
- package/src/themes/intl/ja.css +59 -36
- package/src/themes/intl/pt.css +54 -31
- package/src/themes/intl/zh.css +65 -42
- package/src/themes/intl.css +34 -8
- package/src/themes/ledger.css +1 -59
- package/src/themes/monokai.css +0 -51
- package/src/themes/nord.css +0 -55
- package/src/themes/phosphor.css +0 -48
- package/src/themes/pro-dark.css +0 -37
- package/src/themes/pro.css +3 -23
- package/src/themes/solarized-dark.css +0 -31
- package/src/themes/solarized.css +1 -24
- package/src/themes/vaporwave.css +0 -44
- package/src/themes/velvet.css +0 -60
- package/src/ts/extensions.ts +0 -13
- package/src/ts/perspective-viewer.ts +2 -0
- package/src/ts/plugin.ts +9 -0
- package/src/ts/ts-rs/AddPanelOptions.ts +10 -0
- package/src/ts/ts-rs/{DatetimeColorMode.ts → GetViewMode.ts} +4 -1
- package/src/ts/ts-rs/GetViewOptions.ts +7 -0
- package/src/ts/ts-rs/RestoreOptions.ts +7 -1
- package/src/ts/ts-rs/RestoreWorkspaceOptions.ts +10 -0
- package/src/ts/ts-rs/ViewOnUpdateResp.ts +1 -1
- package/src/ts/ts-rs/WorkspaceConfigUpdate.ts +31 -20
- package/dist/esm/ts-rs/DatetimeColorMode.d.ts +0 -1
- package/dist/esm/ts-rs/FormatMode.d.ts +0 -1
- package/dist/esm/ts-rs/StringColorMode.d.ts +0 -1
- package/src/css/plugin-settings-panel.css +0 -171
- package/src/rust/components/modal.rs +0 -177
- package/src/rust/components/string_column_style.rs +0 -121
- package/src/rust/config/string_column_style.rs +0 -135
- package/src/rust/custom_elements/copy_dropdown.rs +0 -219
- package/src/rust/custom_elements/export_dropdown.rs +0 -216
- package/src/rust/custom_elements/modal.rs +0 -299
- package/src/ts/ts-rs/FormatMode.ts +0 -3
- package/src/ts/ts-rs/StringColorMode.ts +0 -3
- /package/dist/wasm/snippets/{perspective-viewer-87bd21b215d0631a → perspective-viewer-ea71a3990cad35d7}/inline0.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-87bd21b215d0631a → perspective-viewer-ea71a3990cad35d7}/inline1.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-87bd21b215d0631a → perspective-viewer-ea71a3990cad35d7}/inline2.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-87bd21b215d0631a → perspective-viewer-ea71a3990cad35d7}/inline3.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-87bd21b215d0631a → perspective-viewer-ea71a3990cad35d7}/inline4.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-87bd21b215d0631a → perspective-viewer-ea71a3990cad35d7}/inline5.js +0 -0
- /package/src/css/{containers → ui/containers}/scroll-panel.css +0 -0
- /package/src/css/{containers → ui/containers}/split-panel.css +0 -0
- /package/src/css/{dom → ui/dom}/checkbox.css +0 -0
- /package/src/css/{dom → ui/dom}/scrollbar.css +0 -0
- /package/src/css/{dom → ui/dom}/select.css +0 -0
- /package/src/css/{form → ui/form}/mirrored-textarea.css +0 -0
- /package/src/rust/components/{form → code_editor}/highlight.rs +0 -0
- /package/src/rust/{components → ui}/containers/sidebar_close_button.rs +0 -0
- /package/src/rust/{components → ui}/form/number_input.rs +0 -0
|
@@ -19,6 +19,7 @@ use std::future::Future;
|
|
|
19
19
|
use std::rc::Rc;
|
|
20
20
|
|
|
21
21
|
use perspective_client::View;
|
|
22
|
+
use perspective_client::proto::ViewDimensionsResp;
|
|
22
23
|
use perspective_js::utils::{ApiResult, ResultTApiErrorExt};
|
|
23
24
|
use wasm_bindgen::prelude::*;
|
|
24
25
|
use web_sys::*;
|
|
@@ -44,25 +45,27 @@ impl Renderer {
|
|
|
44
45
|
let _ = el.class_list().toggle_with_force("multi", !is_solo);
|
|
45
46
|
}
|
|
46
47
|
|
|
47
|
-
/// Stamp this panel's effective `theme` attribute.
|
|
48
48
|
pub fn stamp_theme(&self, plugin: Option<&JsPerspectiveViewerPlugin>) {
|
|
49
|
+
let theme = self.theme();
|
|
49
50
|
let active_plugin = self.active_plugin();
|
|
50
51
|
if let Some(plugin) = plugin.or(active_plugin.as_ref()) {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
let _ = theme_elem.set_attribute("theme", &theme);
|
|
57
|
-
},
|
|
58
|
-
Some(_) => {},
|
|
59
|
-
None => {
|
|
60
|
-
let _ = theme_elem.remove_attribute("theme");
|
|
61
|
-
},
|
|
62
|
-
}
|
|
52
|
+
stamp_theme_attr(plugin.unchecked_ref::<Element>(), theme.as_deref());
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if let Some(tab) = self.tab_host() {
|
|
56
|
+
stamp_theme_attr(&tab, theme.as_deref());
|
|
63
57
|
}
|
|
64
58
|
}
|
|
65
59
|
|
|
60
|
+
fn tab_host(&self) -> Option<Element> {
|
|
61
|
+
let slot = format!("tab-{}", self.slot_name()?);
|
|
62
|
+
let viewer = self.plugin_data.borrow().viewer_elem.clone();
|
|
63
|
+
let children = viewer.children();
|
|
64
|
+
(0..children.length())
|
|
65
|
+
.filter_map(|i| children.item(i))
|
|
66
|
+
.find(|el| el.get_attribute("slot").as_deref() == Some(slot.as_str()))
|
|
67
|
+
}
|
|
68
|
+
|
|
66
69
|
/// Restyle the active plugin and re-draw.
|
|
67
70
|
pub async fn restyle_all(&self) -> ApiResult<JsValue> {
|
|
68
71
|
self.render_task(|guard| async move {
|
|
@@ -78,8 +81,14 @@ impl Renderer {
|
|
|
78
81
|
self.stamp_active(&plugin);
|
|
79
82
|
plugin.restyle();
|
|
80
83
|
*self.0.captured_theme.borrow_mut() = Some(stamped_theme);
|
|
81
|
-
let
|
|
82
|
-
|
|
84
|
+
let dimensions = ctx.view.dimensions().await?;
|
|
85
|
+
let mut limits = get_row_and_col_limits(
|
|
86
|
+
&dimensions,
|
|
87
|
+
&ctx.view,
|
|
88
|
+
&meta,
|
|
89
|
+
self.is_render_warning_enabled(),
|
|
90
|
+
)
|
|
91
|
+
.await?;
|
|
83
92
|
|
|
84
93
|
limits.is_update = true;
|
|
85
94
|
plugin
|
|
@@ -215,6 +224,7 @@ impl Renderer {
|
|
|
215
224
|
};
|
|
216
225
|
|
|
217
226
|
self.stamp_active(&jsplugin);
|
|
227
|
+
self.0.presized_box.set(None);
|
|
218
228
|
jsplugin.resize().await?;
|
|
219
229
|
Ok(None)
|
|
220
230
|
},
|
|
@@ -241,7 +251,33 @@ impl Renderer {
|
|
|
241
251
|
self.stamp_active(&plugin);
|
|
242
252
|
self.clear_presize()?;
|
|
243
253
|
if plugin.capabilities().presize {
|
|
244
|
-
|
|
254
|
+
let Some(present) = plugin.presize(width, height).await? else {
|
|
255
|
+
return Ok(None);
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
self.0.presized_box.set(Some((width, height)));
|
|
259
|
+
let inline = js_sys::Reflect::get(&present, &"inline".into())
|
|
260
|
+
.ok()
|
|
261
|
+
.and_then(|v| v.as_bool())
|
|
262
|
+
.unwrap_or(false);
|
|
263
|
+
|
|
264
|
+
if inline {
|
|
265
|
+
self.0.presized_box.set(None);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
let renderer = self.clone();
|
|
269
|
+
let wrapped = Closure::once_into_js(move || {
|
|
270
|
+
let filled = present
|
|
271
|
+
.call0(&JsValue::NULL)
|
|
272
|
+
.map(|v| v.as_bool() != Some(false))
|
|
273
|
+
.unwrap_or(false);
|
|
274
|
+
|
|
275
|
+
if !filled {
|
|
276
|
+
renderer.0.presized_box.set(None);
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
return Ok(Some(wrapped.unchecked_into::<js_sys::Function>()));
|
|
245
281
|
}
|
|
246
282
|
|
|
247
283
|
let main_panel: &web_sys::HtmlElement = plugin.unchecked_ref();
|
|
@@ -278,6 +314,7 @@ impl Renderer {
|
|
|
278
314
|
}
|
|
279
315
|
|
|
280
316
|
result?;
|
|
317
|
+
self.0.presized_box.set(Some((width, height)));
|
|
281
318
|
}
|
|
282
319
|
|
|
283
320
|
Ok(None)
|
|
@@ -307,14 +344,16 @@ impl Renderer {
|
|
|
307
344
|
pub async fn draw_fresh(&self, guard: &RenderGuard, view: FreshView) -> ApiResult<()> {
|
|
308
345
|
let timer = self.render_timer();
|
|
309
346
|
timer
|
|
310
|
-
.capture_time(self.draw_view(guard, view.view(), false))
|
|
347
|
+
.capture_time(self.draw_view(guard, view.view(), false, None))
|
|
311
348
|
.await
|
|
312
349
|
}
|
|
313
350
|
|
|
314
351
|
/// Repaint the ALREADY-BOUND `view` on the active plugin.
|
|
315
352
|
pub async fn update_bound(&self, guard: &RenderGuard, view: &View) -> ApiResult<()> {
|
|
316
353
|
let timer = self.render_timer();
|
|
317
|
-
timer
|
|
354
|
+
timer
|
|
355
|
+
.capture_time(self.draw_view(guard, view, true, None))
|
|
356
|
+
.await
|
|
318
357
|
}
|
|
319
358
|
|
|
320
359
|
/// Mint the [`FreshView`] full-draw witness for a plugin that has never
|
|
@@ -363,7 +402,7 @@ impl Renderer {
|
|
|
363
402
|
/// Redraw an already-bound view, debounced.
|
|
364
403
|
pub async fn update_lazy(
|
|
365
404
|
&self,
|
|
366
|
-
view: impl Future<Output = ApiResult<Option<View>>>,
|
|
405
|
+
view: impl Future<Output = ApiResult<Option<(View, Option<ViewDimensionsResp>)>>>,
|
|
367
406
|
) -> ApiResult<()> {
|
|
368
407
|
let timer = self.render_timer();
|
|
369
408
|
self.draw_lock()
|
|
@@ -382,14 +421,14 @@ impl Renderer {
|
|
|
382
421
|
}
|
|
383
422
|
|
|
384
423
|
self.mount_active_plugin()?;
|
|
385
|
-
if let Some(view) = view.await? {
|
|
424
|
+
if let Some((view, dimensions)) = view.await? {
|
|
386
425
|
let _pin = self
|
|
387
426
|
.cached_context()
|
|
388
427
|
.map(|ctx| self.pin_context(&guard, ctx));
|
|
389
428
|
|
|
390
429
|
let timer = self.render_timer();
|
|
391
430
|
timer
|
|
392
|
-
.capture_time(self.draw_view(&guard, &view, true))
|
|
431
|
+
.capture_time(self.draw_view(&guard, &view, true, dimensions))
|
|
393
432
|
.await
|
|
394
433
|
} else {
|
|
395
434
|
tracing::debug!("Render skipped, no `View` attached");
|
|
@@ -399,11 +438,14 @@ impl Renderer {
|
|
|
399
438
|
.await
|
|
400
439
|
}
|
|
401
440
|
|
|
441
|
+
/// `dimensions` is the caller's already-fetched view dimensions, or
|
|
442
|
+
/// `None` to fetch here.
|
|
402
443
|
async fn draw_view(
|
|
403
444
|
&self,
|
|
404
445
|
_guard: &RenderGuard,
|
|
405
446
|
view: &perspective_client::View,
|
|
406
447
|
is_update: bool,
|
|
448
|
+
dimensions: Option<ViewDimensionsResp>,
|
|
407
449
|
) -> ApiResult<()> {
|
|
408
450
|
debug_assert!(
|
|
409
451
|
!is_update || self.cached_context().is_none() || self.render_context().is_some(),
|
|
@@ -420,8 +462,14 @@ impl Renderer {
|
|
|
420
462
|
};
|
|
421
463
|
|
|
422
464
|
let meta = self.metadata();
|
|
465
|
+
let dimensions = match dimensions {
|
|
466
|
+
Some(dimensions) => dimensions,
|
|
467
|
+
None => view.dimensions().await?,
|
|
468
|
+
};
|
|
469
|
+
|
|
423
470
|
let mut limits =
|
|
424
|
-
get_row_and_col_limits(view, &meta, self.is_render_warning_enabled())
|
|
471
|
+
get_row_and_col_limits(&dimensions, view, &meta, self.is_render_warning_enabled())
|
|
472
|
+
.await?;
|
|
425
473
|
|
|
426
474
|
limits.is_update = is_update;
|
|
427
475
|
if let Some(cb) = self.0.on_render_limits_changed.borrow().as_ref() {
|
|
@@ -440,6 +488,7 @@ impl Renderer {
|
|
|
440
488
|
}
|
|
441
489
|
|
|
442
490
|
self.0.data_stale.set(false);
|
|
491
|
+
self.0.presized_box.set(None);
|
|
443
492
|
let result = if is_update {
|
|
444
493
|
let task = plugin.update(view.clone().into(), limits.max_cols, limits.max_rows, false);
|
|
445
494
|
activate_plugin(_guard, &viewer_elem, &plugin, slot.as_deref(), task).await
|
|
@@ -499,3 +548,15 @@ impl Drop for PresizePendingGuard {
|
|
|
499
548
|
cell.set(cell.get() - 1);
|
|
500
549
|
}
|
|
501
550
|
}
|
|
551
|
+
|
|
552
|
+
fn stamp_theme_attr(el: &Element, theme: Option<&str>) {
|
|
553
|
+
match theme {
|
|
554
|
+
Some(theme) if el.get_attribute("theme").as_deref() != Some(theme) => {
|
|
555
|
+
let _ = el.set_attribute("theme", theme);
|
|
556
|
+
},
|
|
557
|
+
Some(_) => {},
|
|
558
|
+
None => {
|
|
559
|
+
let _ = el.remove_attribute("theme");
|
|
560
|
+
},
|
|
561
|
+
}
|
|
562
|
+
}
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
|
11
11
|
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
12
12
|
|
|
13
|
+
use perspective_client::proto::ViewDimensionsResp;
|
|
13
14
|
use perspective_js::utils::ApiError;
|
|
14
15
|
|
|
15
16
|
use crate::config::PluginStaticConfig;
|
|
@@ -31,16 +32,34 @@ pub struct RenderLimits {
|
|
|
31
32
|
pub max_rows: Option<usize>,
|
|
32
33
|
}
|
|
33
34
|
|
|
34
|
-
|
|
35
|
-
///
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
impl RenderLimits {
|
|
36
|
+
/// The column cap is in effect (fewer columns render than the view has).
|
|
37
|
+
pub fn is_col_capped(&self) -> bool {
|
|
38
|
+
self.max_cols.is_some_and(|x| x < self.num_cols)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/// The row cap is in effect (fewer rows render than the view has).
|
|
42
|
+
pub fn is_row_capped(&self) -> bool {
|
|
43
|
+
self.max_rows.is_some_and(|x| x < self.num_rows)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/// Either cap is in effect — exactly when the render warning shows.
|
|
47
|
+
pub fn is_capped(&self) -> bool {
|
|
48
|
+
self.is_col_capped() || self.is_row_capped()
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/// Compute the row/column caps for `view` from its already-fetched
|
|
53
|
+
/// `dimensions` against the plugin's static limits. `render_warning` is the
|
|
54
|
+
/// renderer-state flag controlling whether the warning is currently armed;
|
|
55
|
+
/// when `false`, an oversized view renders uncapped (the user has dismissed
|
|
56
|
+
/// the warning).
|
|
38
57
|
pub async fn get_row_and_col_limits(
|
|
58
|
+
dimensions: &ViewDimensionsResp,
|
|
39
59
|
view: &perspective_client::View,
|
|
40
60
|
config: &PluginStaticConfig,
|
|
41
61
|
render_warning: bool,
|
|
42
62
|
) -> Result<RenderLimits, ApiError> {
|
|
43
|
-
let dimensions = view.dimensions().await?;
|
|
44
63
|
let num_cols = dimensions.num_view_columns as usize;
|
|
45
64
|
let num_rows = dimensions.num_view_rows as usize;
|
|
46
65
|
match (config.max_columns, render_warning) {
|
|
@@ -26,6 +26,7 @@ use super::Renderer;
|
|
|
26
26
|
use crate::config::*;
|
|
27
27
|
use crate::queries::resolve_abs_max;
|
|
28
28
|
use crate::session::Session;
|
|
29
|
+
use crate::utils::{CssKind, CssLiteralUse, parse_var_ref, resolve_css_refs};
|
|
29
30
|
|
|
30
31
|
/// A per-column config map. Each inner [`serde_json::Map`] is a flat collection
|
|
31
32
|
/// of plugin-defined JSON keys whose shape is dictated by the active plugin's
|
|
@@ -246,10 +247,16 @@ impl Renderer {
|
|
|
246
247
|
{
|
|
247
248
|
let active = schema.active_keys();
|
|
248
249
|
cfg.retain(|k, _| active.contains(k));
|
|
249
|
-
let errors =
|
|
250
|
+
let errors = normalize_values(&schema, &mut cfg);
|
|
250
251
|
if let Some((key, error)) = errors.first() {
|
|
252
|
+
let ty = session
|
|
253
|
+
.metadata()
|
|
254
|
+
.get_column_view_type(&col)
|
|
255
|
+
.map(|t| format!(" for a {t} column"))
|
|
256
|
+
.unwrap_or_default();
|
|
257
|
+
|
|
251
258
|
return Err(ApiError::from(JsValue::from_str(&format!(
|
|
252
|
-
"Invalid `columns_config[\"{col}\"].{key}
|
|
259
|
+
"Invalid `columns_config[\"{col}\"].{key}`{ty}: {error}"
|
|
253
260
|
))));
|
|
254
261
|
}
|
|
255
262
|
|
|
@@ -304,7 +311,7 @@ impl Renderer {
|
|
|
304
311
|
&column_name,
|
|
305
312
|
current_value.as_ref(),
|
|
306
313
|
) {
|
|
307
|
-
for (key, error) in
|
|
314
|
+
for (key, error) in normalize_values(&schema, &mut update.value) {
|
|
308
315
|
tracing::error!("Dropping `{column_name}`.`{key}`: {error}");
|
|
309
316
|
}
|
|
310
317
|
|
|
@@ -328,16 +335,25 @@ impl Renderer {
|
|
|
328
335
|
entry.clone()
|
|
329
336
|
};
|
|
330
337
|
|
|
331
|
-
|
|
338
|
+
// The schema AFTER the update decides which keys survive and, for a
|
|
339
|
+
// key whose control kind follows another key (`fg_color` reads as a
|
|
340
|
+
// color, gradient or palette depending on `fg_mode`), whether the
|
|
341
|
+
// value still parses under its new kind. A value that no longer
|
|
342
|
+
// does is dropped here, so `save()` never emits a config that
|
|
343
|
+
// `restore()` would reject.
|
|
344
|
+
let after = self
|
|
332
345
|
.query_column_config_schema(view_config, session, &column_name, Some(&next))
|
|
333
|
-
.ok()
|
|
334
|
-
.map(|schema| schema.active_keys());
|
|
346
|
+
.ok();
|
|
335
347
|
|
|
336
348
|
let mut st = self.borrow_mut();
|
|
337
349
|
let bucket = st.plugin_states.entry(n).or_default();
|
|
338
350
|
if let Some(entry) = bucket.columns.get_mut(&column_name) {
|
|
339
|
-
if let Some(
|
|
351
|
+
if let Some(schema) = &after {
|
|
352
|
+
let active = schema.active_keys();
|
|
340
353
|
entry.retain(|k, _| active.contains(k));
|
|
354
|
+
for (key, error) in normalize_values(schema, entry) {
|
|
355
|
+
tracing::warn!("Dropping `{column_name}`.`{key}` after a mode change: {error}");
|
|
356
|
+
}
|
|
341
357
|
}
|
|
342
358
|
|
|
343
359
|
if entry.is_empty() {
|
|
@@ -370,15 +386,18 @@ impl Renderer {
|
|
|
370
386
|
}
|
|
371
387
|
}
|
|
372
388
|
|
|
373
|
-
/// Synchronously query the active plugin's
|
|
374
|
-
///
|
|
389
|
+
/// Synchronously query the active plugin's [`ColumnConfigSchema`] as it
|
|
390
|
+
/// applies to `current_value`, the plugin-config state the schema should
|
|
391
|
+
/// describe.
|
|
375
392
|
fn query_plugin_config_schema(
|
|
376
393
|
&self,
|
|
377
394
|
view_config: &ViewConfig,
|
|
395
|
+
current_value: Option<&serde_json::Map<String, serde_json::Value>>,
|
|
378
396
|
) -> ApiResult<ColumnConfigSchema> {
|
|
379
397
|
let plugin = self.ensure_plugin_selected()?;
|
|
380
398
|
let view_config_js = JsValue::from_serde_ext(view_config).unwrap_or(JsValue::NULL);
|
|
381
|
-
let
|
|
399
|
+
let current_js = JsValue::from_serde_ext(¤t_value).unwrap_or(JsValue::NULL);
|
|
400
|
+
let raw = plugin._plugin_config_schema(&view_config_js, ¤t_js)?;
|
|
382
401
|
serde_wasm_bindgen::from_value::<ColumnConfigSchema>(raw)
|
|
383
402
|
.map(|schema| schema.canonicalize())
|
|
384
403
|
.map_err(|e| e.into())
|
|
@@ -433,12 +452,9 @@ impl Renderer {
|
|
|
433
452
|
.map_err(|e| e.into())
|
|
434
453
|
}
|
|
435
454
|
|
|
436
|
-
///
|
|
437
|
-
///
|
|
438
|
-
///
|
|
439
|
-
/// is cleared rather than the default being stored literally.
|
|
440
|
-
/// Keys absent from the incoming map are left alone (merge
|
|
441
|
-
/// semantics for the non-default subset).
|
|
455
|
+
/// Merge an update into the active plugin's plugin-level config map,
|
|
456
|
+
/// clearing entries equal to the schema default and dropping keys the
|
|
457
|
+
/// resulting schema no longer advertises.
|
|
442
458
|
pub fn update_plugin_config(
|
|
443
459
|
&self,
|
|
444
460
|
view_config: &ViewConfig,
|
|
@@ -448,28 +464,42 @@ impl Renderer {
|
|
|
448
464
|
return Ok(false);
|
|
449
465
|
};
|
|
450
466
|
|
|
451
|
-
let schema = self.query_plugin_config_schema(view_config).ok();
|
|
452
|
-
let mut st = self.borrow_mut();
|
|
453
|
-
let bucket = st.plugin_states.entry(n).or_default();
|
|
454
467
|
match update {
|
|
455
468
|
OptionalUpdate::SetDefault => {
|
|
469
|
+
let mut st = self.borrow_mut();
|
|
470
|
+
let bucket = st.plugin_states.entry(n).or_default();
|
|
456
471
|
let changed = !bucket.plugin.is_empty();
|
|
457
472
|
bucket.plugin.clear();
|
|
458
473
|
Ok(changed)
|
|
459
474
|
},
|
|
460
475
|
OptionalUpdate::Missing => Ok(false),
|
|
461
476
|
OptionalUpdate::Update(mut map) => {
|
|
462
|
-
let mut
|
|
477
|
+
let mut merged = self.get_plugin_config();
|
|
478
|
+
for (k, v) in &map {
|
|
479
|
+
merged.insert(k.clone(), v.clone());
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
let schema = self
|
|
483
|
+
.query_plugin_config_schema(view_config, Some(&merged))
|
|
484
|
+
.ok();
|
|
485
|
+
|
|
486
|
+
let mut active = None;
|
|
463
487
|
if let Some(s) = &schema {
|
|
464
|
-
let
|
|
465
|
-
map.retain(|k, _|
|
|
466
|
-
|
|
488
|
+
let keys = s.active_keys();
|
|
489
|
+
map.retain(|k, _| keys.contains(k));
|
|
490
|
+
active = Some(keys);
|
|
491
|
+
let errors = normalize_values(s, &mut map);
|
|
467
492
|
if let Some((key, error)) = errors.first() {
|
|
468
493
|
return Err(ApiError::from(JsValue::from_str(&format!(
|
|
469
494
|
"Invalid `plugin_config.{key}`: {error}"
|
|
470
495
|
))));
|
|
471
496
|
}
|
|
497
|
+
}
|
|
472
498
|
|
|
499
|
+
let mut st = self.borrow_mut();
|
|
500
|
+
let bucket = st.plugin_states.entry(n).or_default();
|
|
501
|
+
let mut changed = false;
|
|
502
|
+
if let Some(s) = &schema {
|
|
473
503
|
let leaves = s.leaf_fields();
|
|
474
504
|
map.retain(|key, value| {
|
|
475
505
|
let is_default = leaves
|
|
@@ -493,12 +523,19 @@ impl Renderer {
|
|
|
493
523
|
}
|
|
494
524
|
}
|
|
495
525
|
|
|
526
|
+
if let Some(active) = active {
|
|
527
|
+
let before = bucket.plugin.len();
|
|
528
|
+
bucket.plugin.retain(|k, _| active.contains(k));
|
|
529
|
+
changed |= bucket.plugin.len() != before;
|
|
530
|
+
}
|
|
531
|
+
|
|
496
532
|
Ok(changed)
|
|
497
533
|
},
|
|
498
534
|
}
|
|
499
535
|
}
|
|
500
536
|
|
|
501
|
-
/// Apply a single schema-field update from the plugin-settings UI
|
|
537
|
+
/// Apply a single schema-field update from the plugin-settings UI,
|
|
538
|
+
/// dropping keys the resulting schema no longer advertises.
|
|
502
539
|
pub fn update_plugin_config_field(
|
|
503
540
|
&self,
|
|
504
541
|
view_config: &ViewConfig,
|
|
@@ -508,12 +545,28 @@ impl Renderer {
|
|
|
508
545
|
return false;
|
|
509
546
|
};
|
|
510
547
|
|
|
511
|
-
|
|
512
|
-
|
|
548
|
+
let mut next = self.get_plugin_config();
|
|
549
|
+
for k in &update.keys {
|
|
550
|
+
match update.value.get(k) {
|
|
551
|
+
Some(v) => {
|
|
552
|
+
next.insert(k.clone(), v.clone());
|
|
553
|
+
},
|
|
554
|
+
None => {
|
|
555
|
+
next.remove(k);
|
|
556
|
+
},
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
let schema = self
|
|
561
|
+
.query_plugin_config_schema(view_config, Some(&next))
|
|
562
|
+
.ok();
|
|
563
|
+
|
|
564
|
+
if let Some(schema) = &schema {
|
|
565
|
+
for (key, error) in normalize_values(schema, &mut update.value) {
|
|
513
566
|
tracing::error!("Dropping `plugin_config`.`{key}`: {error}");
|
|
514
567
|
}
|
|
515
568
|
|
|
516
|
-
strip_default_values(
|
|
569
|
+
strip_default_values(schema, &mut update.value);
|
|
517
570
|
}
|
|
518
571
|
|
|
519
572
|
let mut st = self.borrow_mut();
|
|
@@ -531,28 +584,48 @@ impl Renderer {
|
|
|
531
584
|
}
|
|
532
585
|
}
|
|
533
586
|
|
|
587
|
+
if let Some(schema) = &schema {
|
|
588
|
+
let active = schema.active_keys();
|
|
589
|
+
let before = bucket.plugin.len();
|
|
590
|
+
bucket.plugin.retain(|k, _| active.contains(k));
|
|
591
|
+
changed |= bucket.plugin.len() != before;
|
|
592
|
+
}
|
|
593
|
+
|
|
534
594
|
changed
|
|
535
595
|
}
|
|
536
596
|
}
|
|
537
597
|
|
|
538
|
-
|
|
598
|
+
/// Canonicalize every CSS-valued key of `map` and check every `Enum`-valued
|
|
599
|
+
/// key against its variants, removing and reporting the keys that fail.
|
|
600
|
+
/// The schema is the one queried for `map`'s own values, so an `Enum`'s
|
|
601
|
+
/// variants are exactly the values the column's type accepts.
|
|
602
|
+
fn normalize_values(
|
|
539
603
|
schema: &ColumnConfigSchema,
|
|
540
604
|
map: &mut serde_json::Map<String, serde_json::Value>,
|
|
541
605
|
) -> Vec<(String, String)> {
|
|
542
606
|
let mut errors = vec![];
|
|
543
607
|
for (key, value) in map.iter_mut() {
|
|
544
|
-
let Some(kind) = schema.css_kind_of(key)
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
Some(src) => kind.canonicalize(src),
|
|
550
|
-
None => Err(format!("expected a CSS string, got `{value}`")),
|
|
551
|
-
};
|
|
608
|
+
if let Some(kind) = schema.css_kind_of(key) {
|
|
609
|
+
let canonical = match value.as_str() {
|
|
610
|
+
Some(src) => kind.canonicalize(src),
|
|
611
|
+
None => Err(format!("expected a CSS string, got `{value}`")),
|
|
612
|
+
};
|
|
552
613
|
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
614
|
+
match canonical {
|
|
615
|
+
Ok(canonical) => *value = Value::String(canonical),
|
|
616
|
+
Err(error) => errors.push((key.clone(), error)),
|
|
617
|
+
}
|
|
618
|
+
} else if let Some(variants) = schema.enum_variants_of(key) {
|
|
619
|
+
let accepted = variants.iter().any(|v| value.as_str() == Some(&v.value));
|
|
620
|
+
if !accepted {
|
|
621
|
+
let expected = variants
|
|
622
|
+
.iter()
|
|
623
|
+
.map(|v| format!("`{}`", v.value))
|
|
624
|
+
.collect::<Vec<_>>()
|
|
625
|
+
.join(", ");
|
|
626
|
+
|
|
627
|
+
errors.push((key.clone(), format!("{value} is not one of {expected}")));
|
|
628
|
+
}
|
|
556
629
|
}
|
|
557
630
|
}
|
|
558
631
|
|
|
@@ -579,6 +652,21 @@ fn matches_declared_default(spec: &ControlSpec, key: &str, value: &Value) -> boo
|
|
|
579
652
|
match spec {
|
|
580
653
|
ControlSpec::Enum {
|
|
581
654
|
key: k, default, ..
|
|
655
|
+
}
|
|
656
|
+
| ControlSpec::Font {
|
|
657
|
+
key: k, default, ..
|
|
658
|
+
} if k == key => value.as_str() == Some(default.as_str()),
|
|
659
|
+
ControlSpec::Font {
|
|
660
|
+
size: Some(size), ..
|
|
661
|
+
} if size.key == key => value.as_f64() == Some(size.default),
|
|
662
|
+
ControlSpec::Font { bold, italic, .. } => [bold, italic]
|
|
663
|
+
.into_iter()
|
|
664
|
+
.flatten()
|
|
665
|
+
.any(|toggle| toggle.key == key && value.as_bool() == Some(toggle.default)),
|
|
666
|
+
ControlSpec::Alignment {
|
|
667
|
+
key: k,
|
|
668
|
+
default: Some(default),
|
|
669
|
+
..
|
|
582
670
|
} if k == key => value.as_str() == Some(default.as_str()),
|
|
583
671
|
ControlSpec::Bool {
|
|
584
672
|
key: k, default, ..
|
|
@@ -702,6 +790,35 @@ mod tests {
|
|
|
702
790
|
));
|
|
703
791
|
}
|
|
704
792
|
|
|
793
|
+
#[test]
|
|
794
|
+
fn alignment_strips_only_a_declared_default() {
|
|
795
|
+
let schema = ColumnConfigSchema {
|
|
796
|
+
fields: vec![
|
|
797
|
+
ControlSpec::Alignment {
|
|
798
|
+
key: "align".to_owned(),
|
|
799
|
+
default: None,
|
|
800
|
+
corners: false,
|
|
801
|
+
},
|
|
802
|
+
ControlSpec::Alignment {
|
|
803
|
+
key: "legend_anchor".to_owned(),
|
|
804
|
+
default: Some(Alignment::TopRight),
|
|
805
|
+
corners: true,
|
|
806
|
+
},
|
|
807
|
+
],
|
|
808
|
+
};
|
|
809
|
+
|
|
810
|
+
let mut map = json!({ "align": "center", "legend_anchor": "top-right" })
|
|
811
|
+
.as_object()
|
|
812
|
+
.unwrap()
|
|
813
|
+
.clone();
|
|
814
|
+
|
|
815
|
+
strip_default_values(&schema, &mut map);
|
|
816
|
+
assert_eq!(
|
|
817
|
+
map,
|
|
818
|
+
json!({ "align": "center" }).as_object().unwrap().clone()
|
|
819
|
+
);
|
|
820
|
+
}
|
|
821
|
+
|
|
705
822
|
#[test]
|
|
706
823
|
fn strip_default_values_sees_through_groups() {
|
|
707
824
|
let leaves = vec![
|
|
@@ -750,7 +867,50 @@ mod tests {
|
|
|
750
867
|
}
|
|
751
868
|
|
|
752
869
|
#[test]
|
|
753
|
-
fn
|
|
870
|
+
fn normalize_values_rejects_enum_values_outside_the_variants() {
|
|
871
|
+
let schema = ColumnConfigSchema {
|
|
872
|
+
fields: vec![ControlSpec::Group {
|
|
873
|
+
key: "color".to_owned(),
|
|
874
|
+
fields: vec![ControlSpec::Enum {
|
|
875
|
+
key: "fg_mode".to_owned(),
|
|
876
|
+
default: "color".to_owned(),
|
|
877
|
+
variants: vec![
|
|
878
|
+
EnumVariant {
|
|
879
|
+
value: "disabled".to_owned(),
|
|
880
|
+
label: None,
|
|
881
|
+
},
|
|
882
|
+
EnumVariant {
|
|
883
|
+
value: "color".to_owned(),
|
|
884
|
+
label: None,
|
|
885
|
+
},
|
|
886
|
+
],
|
|
887
|
+
}],
|
|
888
|
+
}],
|
|
889
|
+
};
|
|
890
|
+
|
|
891
|
+
let mut map = json!({ "fg_mode": "color", "other": "series" })
|
|
892
|
+
.as_object()
|
|
893
|
+
.unwrap()
|
|
894
|
+
.clone();
|
|
895
|
+
|
|
896
|
+
assert!(normalize_values(&schema, &mut map).is_empty());
|
|
897
|
+
assert_eq!(map.len(), 2);
|
|
898
|
+
|
|
899
|
+
let mut map = json!({ "fg_mode": "series" }).as_object().unwrap().clone();
|
|
900
|
+
let errors = normalize_values(&schema, &mut map);
|
|
901
|
+
assert_eq!(errors.len(), 1);
|
|
902
|
+
assert_eq!(errors[0].0, "fg_mode");
|
|
903
|
+
assert_eq!(errors[0].1, "\"series\" is not one of `disabled`, `color`");
|
|
904
|
+
assert!(map.is_empty());
|
|
905
|
+
|
|
906
|
+
let mut map = json!({ "fg_mode": 3 }).as_object().unwrap().clone();
|
|
907
|
+
let errors = normalize_values(&schema, &mut map);
|
|
908
|
+
assert_eq!(errors.len(), 1);
|
|
909
|
+
assert!(map.is_empty());
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
#[test]
|
|
913
|
+
fn normalize_values_canonicalizes_and_reports() {
|
|
754
914
|
let schema = ColumnConfigSchema {
|
|
755
915
|
fields: vec![
|
|
756
916
|
ControlSpec::Color {
|
|
@@ -785,7 +945,7 @@ mod tests {
|
|
|
785
945
|
.unwrap()
|
|
786
946
|
.clone();
|
|
787
947
|
|
|
788
|
-
let errors =
|
|
948
|
+
let errors = normalize_values(&schema, &mut map);
|
|
789
949
|
assert_eq!(errors.len(), 1);
|
|
790
950
|
assert_eq!(errors[0].0, "gradient");
|
|
791
951
|
assert_eq!(
|
|
@@ -26,7 +26,8 @@ pub struct PluginStore {
|
|
|
26
26
|
|
|
27
27
|
impl PluginStore {
|
|
28
28
|
fn init_lazy(&mut self) {
|
|
29
|
-
|
|
29
|
+
let plugins = PLUGIN_REGISTRY.create_plugins();
|
|
30
|
+
self.plugins = Some(plugins);
|
|
30
31
|
self.plugin_configs = Some(PLUGIN_REGISTRY.plugin_configs());
|
|
31
32
|
self.plugin_records = Some(PLUGIN_REGISTRY.available_plugin_names_by_category());
|
|
32
33
|
}
|
|
@@ -14,6 +14,7 @@ use std::rc::Rc;
|
|
|
14
14
|
|
|
15
15
|
use crate::config::PluginStaticConfig;
|
|
16
16
|
use crate::renderer::limits::RenderLimits;
|
|
17
|
+
use crate::renderer::plugin_config::ColumnConfigMap;
|
|
17
18
|
use crate::utils::PtrEqRc;
|
|
18
19
|
|
|
19
20
|
/// Value-semantic snapshot of the renderer state read by components.
|
|
@@ -30,7 +31,8 @@ pub struct RendererProps {
|
|
|
30
31
|
/// internal `metadata` — `Rc::ptr_eq` works.
|
|
31
32
|
pub config: Rc<PluginStaticConfig>,
|
|
32
33
|
|
|
33
|
-
///
|
|
34
|
+
/// The most recently emitted render limits, only while a cap is in
|
|
35
|
+
/// effect.
|
|
34
36
|
pub render_limits: Option<RenderLimits>,
|
|
35
37
|
|
|
36
38
|
/// Names of all registered plugins, in registration order.
|
|
@@ -49,6 +51,10 @@ pub struct RendererProps {
|
|
|
49
51
|
/// the interior-mutable handle, no manual PubSub subscription, no
|
|
50
52
|
/// race window between `plugins_idx` swap and the schema query.
|
|
51
53
|
pub plugin_config: PtrEqRc<serde_json::Map<String, serde_json::Value>>,
|
|
54
|
+
|
|
55
|
+
/// Snapshot of the active plugin's `columns_config` bucket, re-read by
|
|
56
|
+
/// `Renderer::to_props` on `columns_config_changed` and `plugin_changed`.
|
|
57
|
+
pub columns_config: PtrEqRc<ColumnConfigMap>,
|
|
52
58
|
}
|
|
53
59
|
|
|
54
60
|
impl RendererProps {
|