@perspective-dev/viewer 4.4.1 → 4.5.1

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.
Files changed (227) hide show
  1. package/dist/cdn/perspective-viewer.js +1 -2
  2. package/dist/cdn/perspective-viewer.js.map +4 -4
  3. package/dist/css/botanical.css +1 -1
  4. package/dist/css/dracula.css +1 -1
  5. package/dist/css/gruvbox-dark.css +1 -1
  6. package/dist/css/gruvbox.css +1 -1
  7. package/dist/css/icons.css +1 -1
  8. package/dist/css/intl/de.css +1 -1
  9. package/dist/css/intl/es.css +1 -1
  10. package/dist/css/intl/fr.css +1 -1
  11. package/dist/css/intl/ja.css +1 -1
  12. package/dist/css/intl/pt.css +1 -1
  13. package/dist/css/intl/zh.css +1 -1
  14. package/dist/css/intl.css +1 -1
  15. package/dist/css/monokai.css +1 -1
  16. package/dist/css/phosphor.css +1 -1
  17. package/dist/css/pro-dark.css +1 -1
  18. package/dist/css/pro.css +1 -1
  19. package/dist/css/solarized-dark.css +1 -1
  20. package/dist/css/solarized.css +1 -1
  21. package/dist/css/themes.css +1 -1
  22. package/dist/css/vaporwave.css +1 -1
  23. package/dist/esm/bootstrap.d.ts +2 -1
  24. package/dist/esm/column-format.d.ts +51 -0
  25. package/dist/esm/extensions.d.ts +2 -0
  26. package/dist/esm/perspective-viewer.d.ts +3 -1
  27. package/dist/esm/perspective-viewer.inline.js +1 -2
  28. package/dist/esm/perspective-viewer.inline.js.map +4 -4
  29. package/dist/esm/perspective-viewer.js +1 -2
  30. package/dist/esm/perspective-viewer.js.map +4 -4
  31. package/dist/esm/perspective-viewer.worker.d.ts +2 -0
  32. package/dist/esm/plugin.d.ts +16 -72
  33. package/dist/esm/ts-rs/ColumnSelectMode.d.ts +1 -0
  34. package/dist/esm/ts-rs/PluginStaticConfig.d.ts +77 -0
  35. package/dist/esm/ts-rs/ViewerConfig.d.ts +6 -3
  36. package/dist/esm/ts-rs/ViewerConfigUpdate.d.ts +7 -4
  37. package/dist/wasm/perspective-viewer.d.ts +77 -18
  38. package/dist/wasm/perspective-viewer.js +302 -148
  39. package/dist/wasm/perspective-viewer.wasm +0 -0
  40. package/dist/wasm/perspective-viewer.wasm.d.ts +20 -15
  41. package/package.json +24 -2
  42. package/src/css/column-selector.css +3 -2
  43. package/src/css/column-settings-panel.css +44 -9
  44. package/src/css/column-style.css +35 -2
  45. package/src/css/containers/scroll-panel.css +2 -1
  46. package/src/css/containers/tabs.css +8 -52
  47. package/src/css/dom/checkbox.css +2 -6
  48. package/src/css/form/code-editor.css +1 -0
  49. package/src/css/form/debug.css +3 -10
  50. package/src/css/plugin-selector.css +33 -0
  51. package/src/css/plugin-settings-panel.css +99 -0
  52. package/src/css/viewer.css +143 -3
  53. package/src/rust/components/column_dropdown.rs +3 -1
  54. package/src/rust/components/column_selector/active_column.rs +16 -19
  55. package/src/rust/components/column_selector/config_selector.rs +20 -20
  56. package/src/rust/components/column_selector/filter_column.rs +14 -14
  57. package/src/rust/components/column_selector/inactive_column.rs +10 -15
  58. package/src/rust/components/column_selector/pivot_column.rs +7 -7
  59. package/src/rust/components/column_selector/sort_column.rs +7 -7
  60. package/src/rust/components/column_selector.rs +55 -37
  61. package/src/rust/components/column_settings_sidebar/style_tab/agg_depth_selector.rs +15 -7
  62. package/src/rust/components/column_settings_sidebar/style_tab/primitive_field.rs +395 -0
  63. package/src/rust/components/column_settings_sidebar/style_tab/symbol.rs +15 -6
  64. package/src/rust/components/column_settings_sidebar/style_tab.rs +267 -136
  65. package/src/rust/components/column_settings_sidebar.rs +44 -49
  66. package/src/rust/components/containers/dragdrop_list.rs +32 -5
  67. package/src/rust/components/containers/mod.rs +0 -1
  68. package/src/rust/components/containers/scroll_panel.rs +21 -7
  69. package/src/rust/components/containers/sidebar.rs +8 -6
  70. package/src/rust/components/containers/split_panel.rs +3 -3
  71. package/src/rust/components/containers/tab_list.rs +3 -9
  72. package/src/rust/components/copy_dropdown.rs +2 -3
  73. package/src/rust/components/datetime_column_style.rs +19 -81
  74. package/src/rust/components/editable_header.rs +17 -3
  75. package/src/rust/components/export_dropdown.rs +2 -3
  76. package/src/rust/components/expression_editor.rs +29 -17
  77. package/src/rust/components/filter_dropdown.rs +2 -1
  78. package/src/rust/components/form/color_range_selector.rs +14 -7
  79. package/src/rust/components/form/debug.rs +47 -37
  80. package/src/rust/components/main_panel.rs +24 -65
  81. package/src/rust/components/mod.rs +2 -1
  82. package/src/rust/components/number_series_style.rs +161 -0
  83. package/src/rust/components/plugin_tab.rs +221 -0
  84. package/src/rust/components/settings_panel.rs +181 -59
  85. package/src/rust/components/status_bar.rs +141 -174
  86. package/src/rust/components/status_indicator.rs +15 -22
  87. package/src/rust/components/string_column_style.rs +20 -82
  88. package/src/rust/components/style_controls/number_string_format.rs +14 -30
  89. package/src/rust/components/viewer.rs +169 -132
  90. package/src/rust/config/column_config_schema.rs +195 -0
  91. package/src/rust/config/columns_config.rs +4 -97
  92. package/src/rust/config/datetime_column_style.rs +0 -5
  93. package/src/rust/config/mod.rs +8 -2
  94. package/src/rust/config/number_series_style.rs +79 -0
  95. package/src/rust/config/plugin_static_config.rs +144 -0
  96. package/src/rust/config/string_column_style.rs +0 -5
  97. package/src/rust/config/viewer_config.rs +5 -6
  98. package/src/rust/custom_elements/copy_dropdown.rs +30 -18
  99. package/src/rust/custom_elements/debug_plugin.rs +1 -3
  100. package/src/rust/custom_elements/export_dropdown.rs +26 -18
  101. package/src/rust/custom_elements/viewer.rs +62 -73
  102. package/src/rust/custom_events.rs +181 -224
  103. package/src/rust/js/plugin.rs +45 -117
  104. package/src/rust/lib.rs +34 -5
  105. package/src/rust/presentation/drag_helpers.rs +206 -0
  106. package/src/rust/presentation/props.rs +8 -0
  107. package/src/rust/presentation.rs +256 -41
  108. package/src/rust/{tasks → queries}/column_locator.rs +17 -73
  109. package/src/rust/queries/column_values.rs +59 -0
  110. package/src/rust/{tasks → queries}/columns_iter_set.rs +11 -18
  111. package/src/rust/queries/exports.rs +96 -0
  112. package/src/rust/queries/fetch_column_stats.rs +94 -0
  113. package/src/rust/queries/get_viewer_config.rs +54 -0
  114. package/src/rust/queries/mod.rs +44 -0
  115. package/src/rust/queries/plugin_column_styles.rs +101 -0
  116. package/src/rust/{engines.rs → queries/validate_expression.rs} +26 -15
  117. package/src/rust/renderer/activate.rs +1 -0
  118. package/src/rust/renderer/limits.rs +9 -4
  119. package/src/rust/renderer/plugin_store.rs +12 -0
  120. package/src/rust/renderer/props.rs +28 -3
  121. package/src/rust/renderer/registry.rs +40 -15
  122. package/src/rust/renderer.rs +703 -60
  123. package/src/rust/session/column_defaults_update.rs +20 -28
  124. package/src/rust/session/drag_drop_update.rs +10 -10
  125. package/src/rust/session/metadata.rs +31 -16
  126. package/src/rust/session/props.rs +15 -6
  127. package/src/rust/session/view_subscription.rs +10 -0
  128. package/src/rust/session.rs +109 -147
  129. package/src/rust/tasks/copy_export.rs +178 -158
  130. package/src/rust/tasks/{structural.rs → dismiss_render_warning.rs} +20 -40
  131. package/src/rust/tasks/edit_expression.rs +68 -88
  132. package/src/rust/tasks/eject.rs +25 -22
  133. package/src/rust/tasks/intersection_observer.rs +8 -21
  134. package/src/rust/tasks/mod.rs +19 -21
  135. package/src/rust/tasks/reset_all.rs +98 -0
  136. package/src/rust/tasks/resize_observer.rs +11 -33
  137. package/src/rust/tasks/restore_and_render.rs +128 -90
  138. package/src/rust/tasks/{get_viewer_config.rs → send_column_config.rs} +39 -35
  139. package/src/rust/tasks/send_plugin_config.rs +33 -33
  140. package/src/rust/tasks/update_and_render.rs +75 -49
  141. package/src/rust/{components/containers/trap_door_panel.rs → tasks/update_theme.rs} +34 -33
  142. package/src/rust/tasks/validate_expression.rs +61 -0
  143. package/src/rust/utils/browser/selection.rs +4 -4
  144. package/src/rust/utils/mod.rs +0 -63
  145. package/src/svg/checkbox-checked-icon.svg +1 -1
  146. package/src/svg/checkbox-unchecked-icon.svg +1 -1
  147. package/src/svg/mega-menu-icons-density.svg +23 -0
  148. package/src/svg/mega-menu-icons-map-density.svg +24 -0
  149. package/src/svg/mega-menu-icons-map-line.svg +19 -0
  150. package/src/themes/botanical.css +27 -53
  151. package/src/themes/defaults.css +24 -36
  152. package/src/themes/dracula.css +36 -54
  153. package/src/themes/gruvbox-dark.css +39 -59
  154. package/src/themes/gruvbox.css +16 -28
  155. package/src/themes/icons.css +5 -0
  156. package/src/themes/intl/de.css +43 -6
  157. package/src/themes/intl/es.css +43 -6
  158. package/src/themes/intl/fr.css +43 -6
  159. package/src/themes/intl/ja.css +43 -6
  160. package/src/themes/intl/pt.css +43 -6
  161. package/src/themes/intl/zh.css +43 -6
  162. package/src/themes/intl.css +38 -4
  163. package/src/themes/monokai.css +45 -61
  164. package/src/themes/phosphor.css +20 -29
  165. package/src/themes/pro-dark.css +25 -34
  166. package/src/themes/solarized-dark.css +21 -36
  167. package/src/themes/solarized.css +13 -23
  168. package/src/themes/vaporwave.css +40 -74
  169. package/src/ts/bootstrap.ts +14 -3
  170. package/src/ts/column-format.ts +162 -0
  171. package/src/ts/extensions.ts +4 -0
  172. package/src/ts/perspective-viewer.ts +9 -1
  173. package/src/{rust/components/column_settings_sidebar/style_tab/stub.rs → ts/perspective-viewer.worker.ts} +2 -22
  174. package/src/ts/plugin.ts +25 -101
  175. package/src/ts/ts-rs/{FormatUnit.ts → ColumnSelectMode.ts} +1 -1
  176. package/src/ts/ts-rs/PluginStaticConfig.ts +78 -0
  177. package/src/ts/ts-rs/ViewerConfig.ts +1 -2
  178. package/src/ts/ts-rs/ViewerConfigUpdate.ts +2 -3
  179. package/dist/esm/ts-rs/ColumnConfigValues.d.ts +0 -31
  180. package/dist/esm/ts-rs/CustomDatetimeFormat.d.ts +0 -1
  181. package/dist/esm/ts-rs/CustomDatetimeStyleConfig.d.ts +0 -15
  182. package/dist/esm/ts-rs/CustomNumberFormatConfig.d.ts +0 -18
  183. package/dist/esm/ts-rs/DatetimeColorMode.d.ts +0 -1
  184. package/dist/esm/ts-rs/DatetimeFormatType.d.ts +0 -6
  185. package/dist/esm/ts-rs/FormatMode.d.ts +0 -1
  186. package/dist/esm/ts-rs/FormatUnit.d.ts +0 -1
  187. package/dist/esm/ts-rs/NumberBackgroundMode.d.ts +0 -1
  188. package/dist/esm/ts-rs/NumberForegroundMode.d.ts +0 -1
  189. package/dist/esm/ts-rs/PluginConfig.d.ts +0 -2
  190. package/dist/esm/ts-rs/RoundingMode.d.ts +0 -1
  191. package/dist/esm/ts-rs/RoundingPriority.d.ts +0 -1
  192. package/dist/esm/ts-rs/SignDisplay.d.ts +0 -1
  193. package/dist/esm/ts-rs/SimpleDatetimeFormat.d.ts +0 -1
  194. package/dist/esm/ts-rs/SimpleDatetimeStyleConfig.d.ts +0 -6
  195. package/dist/esm/ts-rs/StringColorMode.d.ts +0 -1
  196. package/dist/esm/ts-rs/TrailingZeroDisplay.d.ts +0 -1
  197. package/dist/esm/ts-rs/UseGrouping.d.ts +0 -1
  198. package/src/rust/components/number_column_style.rs +0 -491
  199. package/src/rust/config/number_column_style.rs +0 -136
  200. package/src/rust/dragdrop.rs +0 -481
  201. package/src/rust/tasks/plugin_column_styles.rs +0 -98
  202. package/src/ts/ts-rs/ColumnConfigValues.ts +0 -14
  203. package/src/ts/ts-rs/CustomDatetimeFormat.ts +0 -3
  204. package/src/ts/ts-rs/CustomDatetimeStyleConfig.ts +0 -5
  205. package/src/ts/ts-rs/CustomNumberFormatConfig.ts +0 -8
  206. package/src/ts/ts-rs/DatetimeColorMode.ts +0 -3
  207. package/src/ts/ts-rs/DatetimeFormatType.ts +0 -8
  208. package/src/ts/ts-rs/FormatMode.ts +0 -3
  209. package/src/ts/ts-rs/NumberBackgroundMode.ts +0 -3
  210. package/src/ts/ts-rs/NumberForegroundMode.ts +0 -3
  211. package/src/ts/ts-rs/PluginConfig.ts +0 -4
  212. package/src/ts/ts-rs/RoundingMode.ts +0 -3
  213. package/src/ts/ts-rs/RoundingPriority.ts +0 -3
  214. package/src/ts/ts-rs/SignDisplay.ts +0 -3
  215. package/src/ts/ts-rs/SimpleDatetimeFormat.ts +0 -3
  216. package/src/ts/ts-rs/SimpleDatetimeStyleConfig.ts +0 -4
  217. package/src/ts/ts-rs/StringColorMode.ts +0 -3
  218. package/src/ts/ts-rs/TrailingZeroDisplay.ts +0 -3
  219. package/src/ts/ts-rs/UseGrouping.ts +0 -3
  220. /package/dist/wasm/snippets/{perspective-viewer-d924246f0b4a3dce → perspective-viewer-3cd58f0374935772}/inline0.js +0 -0
  221. /package/dist/wasm/snippets/{perspective-viewer-d924246f0b4a3dce → perspective-viewer-3cd58f0374935772}/inline1.js +0 -0
  222. /package/dist/wasm/snippets/{perspective-viewer-d924246f0b4a3dce → perspective-viewer-3cd58f0374935772}/inline2.js +0 -0
  223. /package/dist/wasm/snippets/{perspective-viewer-d924246f0b4a3dce → perspective-viewer-3cd58f0374935772}/inline3.js +0 -0
  224. /package/dist/wasm/snippets/{perspective-viewer-d924246f0b4a3dce → perspective-viewer-3cd58f0374935772}/inline4.js +0 -0
  225. /package/src/rust/{tasks → config}/export_method.rs +0 -0
  226. /package/src/rust/{tasks → queries}/export_app.rs +0 -0
  227. /package/src/rust/{tasks → queries}/is_invalid_drop.rs +0 -0
@@ -15,9 +15,9 @@ use std::rc::Rc;
15
15
  use yew::prelude::*;
16
16
 
17
17
  use super::containers::dropdown_menu::*;
18
+ use crate::config::*;
18
19
  use crate::renderer::*;
19
20
  use crate::session::Session;
20
- use crate::tasks::*;
21
21
 
22
22
  pub type ExportDropDownMenuItem = DropDownMenuItem<ExportFile>;
23
23
 
@@ -45,8 +45,7 @@ impl Component for ExportDropDownMenu {
45
45
 
46
46
  fn view(&self, ctx: &Context<Self>) -> yew::virtual_dom::VNode {
47
47
  let callback = ctx.link().callback(|_| ExportDropDownMenuMsg::TitleChange);
48
- let plugin = ctx.props().renderer.get_active_plugin().unwrap();
49
- let is_chart = plugin.name().as_str() != "Datagrid";
48
+ let is_chart = ctx.props().renderer.is_chart();
50
49
  html! {
51
50
  <>
52
51
  <span class="dropdown-group-label">{ "Save as" }</span>
@@ -18,6 +18,7 @@ use yew::prelude::*;
18
18
  use super::form::code_editor::*;
19
19
  use super::style::LocalStyle;
20
20
  use crate::session::{Session, SessionMetadata, SessionMetadataRc};
21
+ use crate::tasks::{ExprValidation, validate_expression};
21
22
  use crate::*;
22
23
 
23
24
  #[derive(Properties, PartialEq, Clone)]
@@ -45,7 +46,7 @@ pub struct ExpressionEditorProps {
45
46
  #[derive(Debug)]
46
47
  pub enum ExpressionEditorMsg {
47
48
  SetExpr(Rc<String>),
48
- ValidateComplete(Option<ExprValidationError>),
49
+ ValidateComplete(ExprValidation),
49
50
  }
50
51
 
51
52
  /// Expression editor component `CodeEditor` and a button toolbar.
@@ -53,6 +54,13 @@ pub struct ExpressionEditor {
53
54
  expr: Rc<String>,
54
55
  error: Option<ExprValidationError>,
55
56
  oninput: Callback<Rc<String>>,
57
+ /// Monotonically increasing request id used to drop stale
58
+ /// validation results when the user types faster than the engine
59
+ /// can validate.
60
+ validation_req_id: u64,
61
+ /// The id of the most recently dispatched validation; the result
62
+ /// is only applied when its echoed id matches.
63
+ last_dispatched_req_id: u64,
56
64
  }
57
65
 
58
66
  impl Component for ExpressionEditor {
@@ -69,6 +77,8 @@ impl Component for ExpressionEditor {
69
77
  error: None,
70
78
  expr,
71
79
  oninput,
80
+ validation_req_id: 0,
81
+ last_dispatched_req_id: 0,
72
82
  }
73
83
  }
74
84
 
@@ -77,23 +87,25 @@ impl Component for ExpressionEditor {
77
87
  ExpressionEditorMsg::SetExpr(val) => {
78
88
  ctx.props().on_input.emit(val.clone());
79
89
  self.expr = val.clone();
80
- clone!(ctx.props().session);
81
- ctx.link().send_future(async move {
82
- match session.validate_expr(&val).await {
83
- Ok(x) => ExpressionEditorMsg::ValidateComplete(x),
84
- Err(err) => {
85
- web_sys::console::error_1(&format!("{err:?}").into());
86
- ExpressionEditorMsg::ValidateComplete(None)
87
- },
88
- }
89
- });
90
-
90
+ self.validation_req_id += 1;
91
+ self.last_dispatched_req_id = self.validation_req_id;
92
+ let cb = ctx.link().callback(ExpressionEditorMsg::ValidateComplete);
93
+ validate_expression(
94
+ &ctx.props().session,
95
+ cb,
96
+ self.validation_req_id,
97
+ (*val).clone(),
98
+ );
91
99
  true
92
100
  },
93
- ExpressionEditorMsg::ValidateComplete(err) => {
94
- self.error = err;
101
+ ExpressionEditorMsg::ValidateComplete(result) => {
102
+ if result.req_id != self.last_dispatched_req_id {
103
+ // Stale result from a superseded request — ignore.
104
+ return false;
105
+ }
106
+ self.error = result.error;
95
107
  if self.error.is_none() {
96
- maybe!({
108
+ let _: Option<bool> = try {
97
109
  let alias = ctx.props().alias.as_ref()?;
98
110
  let session = &ctx.props().session;
99
111
  let old = ctx.props().metadata.get_expression_by_alias(alias)?;
@@ -102,8 +114,8 @@ impl Component for ExpressionEditor {
102
114
  .metadata_mut()
103
115
  .set_edit_by_alias(alias, self.expr.to_string());
104
116
 
105
- Some(is_edited)
106
- });
117
+ is_edited
118
+ };
107
119
 
108
120
  ctx.props().on_validate.emit(true);
109
121
  } else {
@@ -103,7 +103,8 @@ impl FilterDropDownElement {
103
103
  old_column = self.column
104
104
  );
105
105
  ApiFuture::spawn(async move {
106
- let fetched = session.get_column_values(column.1.clone()).await?;
106
+ let fetched =
107
+ crate::queries::get_column_values(&session, column.1.clone()).await?;
107
108
  *all_values.borrow_mut() = Some(fetched);
108
109
  let values = filter_values(&input, &all_values, &exclude);
109
110
  let should_hide = values.len() == 1 && values[0] == input;
@@ -16,7 +16,13 @@ use yew::prelude::*;
16
16
 
17
17
  #[derive(Properties, PartialEq, Clone)]
18
18
  pub struct ColorRangeProps {
19
- pub id: String,
19
+ /// Extra class appended to the positive input's `class` attribute,
20
+ /// so callers can target the pos / neg sides independently from CSS.
21
+ /// Paired with `neg_class`; both default to empty.
22
+ #[prop_or_default]
23
+ pub pos_class: Classes,
24
+ #[prop_or_default]
25
+ pub neg_class: Classes,
20
26
  pub pos_color: String,
21
27
  pub neg_color: String,
22
28
  pub is_gradient: bool,
@@ -68,32 +74,33 @@ pub fn color_chooser_component(props: &ColorRangeProps) -> Html {
68
74
 
69
75
  let on_reset = use_callback(props.clone(), |_: MouseEvent, deps| deps.on_reset.emit(()));
70
76
 
77
+ let pos_class = classes!("parameter", "pos-color-param", props.pos_class.clone());
78
+ let neg_class = classes!("parameter", "neg-color-param", props.neg_class.clone());
79
+
71
80
  html! {
72
81
  <>
73
82
  <label id="color-range-label" />
74
83
  <div class="color-gradient-container">
75
84
  <div style={fg_pos} class="color-selector">
76
85
  <input
77
- id={format!("{}-pos", props.id)}
78
- class="parameter pos-color-param"
86
+ class={pos_class}
79
87
  type="color"
80
88
  value={props.pos_color.to_owned()}
81
89
  data-value={props.pos_color.to_owned()}
82
90
  oninput={on_pos_color}
83
91
  />
84
- <label for={format!("{}-pos", props.id)} class="color-label">{ "+" }</label>
92
+ <label class="color-label">{ "+" }</label>
85
93
  </div>
86
94
  <div class="color-thermometer" {style} />
87
95
  <div style={fg_neg} class="color-selector">
88
96
  <input
89
- id={format!("{}-neg", props.id)}
90
- class="parameter neg-color-param"
97
+ class={neg_class}
91
98
  type="color"
92
99
  value={props.neg_color.to_owned()}
93
100
  data-value={props.neg_color.to_owned()}
94
101
  oninput={on_neg_color}
95
102
  />
96
- <label for={format!("{}-neg", props.id)} class="color-label">{ "-" }</label>
103
+ <label class="color-label">{ "-" }</label>
97
104
  </div>
98
105
  if props.is_modified {
99
106
  <span class="reset-default-style" onclick={on_reset} />
@@ -17,7 +17,6 @@ use perspective_js::utils::{ApiFuture, JsValueSerdeExt};
17
17
  use wasm_bindgen::prelude::*;
18
18
  use yew::prelude::*;
19
19
 
20
- use crate::components::containers::trap_door_panel::TrapDoorPanel;
21
20
  use crate::components::form::code_editor::CodeEditor;
22
21
  use crate::components::style::LocalStyle;
23
22
  use crate::css;
@@ -25,7 +24,6 @@ use crate::js::{MimeType, copy_to_clipboard, paste_from_clipboard};
25
24
  use crate::presentation::*;
26
25
  use crate::renderer::*;
27
26
  use crate::session::*;
28
- use crate::tasks::*;
29
27
  use crate::utils::*;
30
28
 
31
29
  #[derive(Clone, PartialEq, Properties)]
@@ -33,24 +31,17 @@ pub struct DebugPanelProps {
33
31
  pub presentation: Presentation,
34
32
  pub renderer: Renderer,
35
33
  pub session: Session,
36
- }
37
-
38
- impl HasPresentation for DebugPanelProps {
39
- fn presentation(&self) -> &Presentation {
40
- &self.presentation
41
- }
42
- }
43
34
 
44
- impl HasRenderer for DebugPanelProps {
45
- fn renderer(&self) -> &Renderer {
46
- &self.renderer
47
- }
48
- }
35
+ /// Trap-door width pinned by the parent `SettingsPanel` so switching
36
+ /// tabs doesn't shrink the panel. Threaded into the hidden sizer
37
+ /// `<div class="scroll-panel-auto-width">`.
38
+ #[prop_or_default]
39
+ pub initial_width: f64,
49
40
 
50
- impl HasSession for DebugPanelProps {
51
- fn session(&self) -> &Session {
52
- &self.session
53
- }
41
+ /// Fires once on mount with this panel's measured natural width.
42
+ /// Routed up to `SettingsPanel` which keeps the running max.
43
+ #[prop_or_default]
44
+ pub on_auto_width: Callback<f64>,
54
45
  }
55
46
 
56
47
  #[function_component(DebugPanel)]
@@ -60,6 +51,18 @@ pub fn debug_panel(props: &DebugPanelProps) -> Html {
60
51
  let select_all = use_memo((), |()| PubSub::default());
61
52
  let modified = use_state_eq(|| false);
62
53
 
54
+ // Measure natural width on mount and route up to `SettingsPanel`.
55
+ let sizer = use_node_ref();
56
+ use_effect_with(expr.clone(), {
57
+ let sizer = sizer.clone();
58
+ let on_auto_width = props.on_auto_width.clone();
59
+ move |_| {
60
+ if let Some(elem) = sizer.cast::<web_sys::HtmlElement>() {
61
+ on_auto_width.emit(elem.get_bounding_client_rect().width());
62
+ }
63
+ }
64
+ });
65
+
63
66
  use_effect_with((expr.setter(), props.clone()), {
64
67
  clone!(error, modified);
65
68
  move |(text, state)| {
@@ -166,16 +169,12 @@ pub fn debug_panel(props: &DebugPanelProps) -> Html {
166
169
  <LocalStyle href={css!("containers/tabs")} />
167
170
  <LocalStyle href={css!("form/debug")} />
168
171
  <div id="debug-panel-overflow">
169
- <TrapDoorPanel id="debug-panel" class="sidebar_column">
170
- <div class="tab-gutter">
171
- <span class="tab selected">
172
- <div id="Debug" class="tab-title" />
173
- <div class="tab-border" />
174
- </span>
175
- <span class="tab tab-padding">
176
- <div class="tab-title" />
177
- <div class="tab-border" />
178
- </span>
172
+ <div id="debug-panel" class="sidebar_column" ref={sizer}>
173
+ <div id="debug-panel-controls">
174
+ <button disabled={!*modified} onclick={onapply}>{ "Apply" }</button>
175
+ <button disabled={!*modified} onclick={onreset}>{ "Reset" }</button>
176
+ <button onclick={oncopy}>{ "Copy" }</button>
177
+ <button onclick={onpaste}>{ "Paste" }</button>
179
178
  </div>
180
179
  <div id="debug-panel-editor">
181
180
  <CodeEditor
@@ -187,13 +186,11 @@ pub fn debug_panel(props: &DebugPanelProps) -> Html {
187
186
  error={(*error).clone()}
188
187
  />
189
188
  </div>
190
- <div id="debug-panel-controls">
191
- <button disabled={!*modified} onclick={onapply}>{ "Apply" }</button>
192
- <button disabled={!*modified} onclick={onreset}>{ "Reset" }</button>
193
- <button onclick={oncopy}>{ "Copy" }</button>
194
- <button onclick={onpaste}>{ "Paste" }</button>
195
- </div>
196
- </TrapDoorPanel>
189
+ <div
190
+ class="scroll-panel-auto-width"
191
+ style={format!("width:{}px", props.initial_width)}
192
+ />
193
+ </div>
197
194
  </div>
198
195
  </>
199
196
  }
@@ -203,7 +200,12 @@ impl DebugPanelProps {
203
200
  fn set_text(&self, setter: UseStateSetter<Rc<String>>) {
204
201
  let props = self.clone();
205
202
  ApiFuture::spawn(async move {
206
- let config = props.get_viewer_config().await?;
203
+ let config = crate::queries::get_viewer_config(
204
+ &props.session,
205
+ &props.renderer,
206
+ &props.presentation,
207
+ )
208
+ .await?;
207
209
  let json = JsValue::from_serde_ext(&config)?;
208
210
  let js_string =
209
211
  js_sys::JSON::stringify_with_replacer_and_space(&json, &JsValue::NULL, &2.into())?;
@@ -238,7 +240,15 @@ impl DebugPanelProps {
238
240
  ApiFuture::spawn(async move {
239
241
  match serde_json::from_str(&text) {
240
242
  Ok(config) => {
241
- match props.restore_and_render(config, async { Ok(()) }).await {
243
+ match crate::tasks::restore_and_render(
244
+ &props.session,
245
+ &props.renderer,
246
+ &props.presentation,
247
+ config,
248
+ async { Ok(()) },
249
+ )
250
+ .await
251
+ {
242
252
  Ok(_) => {
243
253
  modified.set(false);
244
254
  },
@@ -10,18 +10,15 @@
10
10
  // ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11
11
  // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
12
 
13
- use perspective_js::utils::*;
14
13
  use wasm_bindgen::prelude::*;
15
14
  use yew::prelude::*;
16
15
 
17
16
  use super::render_warning::RenderWarning;
18
17
  use super::status_bar::StatusBar;
19
- use crate::custom_events::CustomEvents;
20
- use crate::presentation::Presentation;
21
- use crate::renderer::limits::RenderLimits;
18
+ use crate::presentation::{Presentation, PresentationProps};
22
19
  use crate::renderer::*;
23
- use crate::session::{Session, TableErrorState, TableLoadState, ViewStats};
24
- use crate::utils::*;
20
+ use crate::session::{Session, SessionProps};
21
+ use crate::tasks::dismiss_render_warning_callback;
25
22
 
26
23
  #[derive(Clone, Properties)]
27
24
  pub struct MainPanelProps {
@@ -32,28 +29,19 @@ pub struct MainPanelProps {
32
29
  /// + column configs), `false` for config-only.
33
30
  pub on_reset: Callback<bool>,
34
31
 
35
- /// Render-limit dimensions forwarded from the root's `RendererProps`.
36
- /// `Some` when the active plugin is capping the rendered row/column count;
37
- /// `None` when no limits are active (e.g. after a plugin change).
38
- pub render_limits: Option<RenderLimits>,
32
+ /// Snapshots threaded from root. Read for `has_table`, `title` here in
33
+ /// the panel itself; threaded wholesale to `StatusBar`/`StatusIndicator`.
34
+ pub session_props: SessionProps,
35
+ pub renderer_props: RendererProps,
36
+ pub presentation_props: PresentationProps,
39
37
 
40
- /// Value props from root's `SessionProps`, threaded to `StatusBar` /
41
- /// `StatusIndicator`.
42
- pub has_table: Option<TableLoadState>,
43
- pub is_errored: bool,
44
- pub stats: Option<ViewStats>,
45
- pub update_count: u32,
46
- pub error: Option<TableErrorState>,
47
- pub title: Option<String>,
48
-
49
- /// Value props from root's `PresentationProps`, threaded to `StatusBar`.
38
+ /// Derived from root: `settings_open && has_table_loaded`.
50
39
  pub is_settings_open: bool,
51
- pub selected_theme: Option<String>,
52
- pub available_themes: PtrEqRc<Vec<String>>,
53
- pub is_workspace: bool,
40
+
41
+ /// Root-managed in-flight render counter (not engine state).
42
+ pub update_count: u32,
54
43
 
55
44
  /// State
56
- pub custom_events: CustomEvents,
57
45
  pub session: Session,
58
46
  pub renderer: Renderer,
59
47
  pub presentation: Presentation,
@@ -61,23 +49,17 @@ pub struct MainPanelProps {
61
49
 
62
50
  impl PartialEq for MainPanelProps {
63
51
  fn eq(&self, rhs: &Self) -> bool {
64
- self.has_table == rhs.has_table
65
- && self.is_errored == rhs.is_errored
66
- && self.stats == rhs.stats
67
- && self.update_count == rhs.update_count
68
- && self.error == rhs.error
69
- && self.title == rhs.title
52
+ self.session_props == rhs.session_props
53
+ && self.renderer_props == rhs.renderer_props
54
+ && self.presentation_props == rhs.presentation_props
70
55
  && self.is_settings_open == rhs.is_settings_open
71
- && self.selected_theme == rhs.selected_theme
72
- && self.available_themes == rhs.available_themes
73
- && self.is_workspace == rhs.is_workspace
74
- && self.render_limits == rhs.render_limits
56
+ && self.update_count == rhs.update_count
75
57
  }
76
58
  }
77
59
 
78
60
  impl MainPanelProps {
79
61
  fn is_title(&self) -> bool {
80
- self.title.is_some()
62
+ self.session_props.title.is_some()
81
63
  }
82
64
  }
83
65
 
@@ -109,10 +91,7 @@ impl Component for MainPanel {
109
91
  .cast::<web_sys::HtmlElement>()
110
92
  .map(JsValue::from)
111
93
  {
112
- ctx.props()
113
- .custom_events
114
- .dispatch_event(format!("statusbar-{}", event.type_()).as_str(), &event)
115
- .unwrap();
94
+ ctx.props().presentation.statusbar_pointer_event.emit(event);
116
95
  }
117
96
 
118
97
  false
@@ -126,16 +105,13 @@ impl Component for MainPanel {
126
105
 
127
106
  fn view(&self, ctx: &Context<Self>) -> Html {
128
107
  let Self::Properties {
129
- custom_events,
130
108
  presentation,
131
109
  renderer,
132
110
  session,
133
111
  ..
134
112
  } = ctx.props();
135
113
 
136
- let is_settings_open = ctx.props().is_settings_open
137
- && matches!(ctx.props().has_table, Some(TableLoadState::Loaded));
138
-
114
+ let is_settings_open = ctx.props().is_settings_open;
139
115
  let on_settings = (!is_settings_open).then(|| ctx.props().on_settings.clone());
140
116
 
141
117
  let mut class = classes!();
@@ -148,17 +124,7 @@ impl Component for MainPanel {
148
124
  }
149
125
 
150
126
  let pointerdown = ctx.link().callback(MainPanelMsg::PointerEvent);
151
- let on_dismiss_warning = {
152
- clone!(renderer, session);
153
- Callback::from(move |_: ()| {
154
- clone!(renderer, session);
155
- ApiFuture::spawn(async move {
156
- renderer.disable_active_plugin_render_warning();
157
- let view_task = session.get_view();
158
- renderer.update(view_task).await
159
- });
160
- })
161
- };
127
+ let on_dismiss_warning = dismiss_render_warning_callback(session, renderer);
162
128
 
163
129
  html! {
164
130
  <div id="main_column">
@@ -166,17 +132,10 @@ impl Component for MainPanel {
166
132
  id="status_bar"
167
133
  {on_settings}
168
134
  on_reset={ctx.props().on_reset.clone()}
169
- has_table={ctx.props().has_table.clone()}
170
- is_errored={ctx.props().is_errored}
171
- stats={ctx.props().stats.clone()}
172
- update_count={ctx.props().update_count}
173
- error={ctx.props().error.clone()}
174
- title={ctx.props().title.clone()}
135
+ session_props={ctx.props().session_props.clone()}
136
+ presentation_props={ctx.props().presentation_props.clone()}
175
137
  is_settings_open={ctx.props().is_settings_open}
176
- selected_theme={ctx.props().selected_theme.clone()}
177
- available_themes={ctx.props().available_themes.clone()}
178
- is_workspace={ctx.props().is_workspace}
179
- {custom_events}
138
+ update_count={ctx.props().update_count}
180
139
  {presentation}
181
140
  {renderer}
182
141
  {session}
@@ -189,7 +148,7 @@ impl Component for MainPanel {
189
148
  >
190
149
  <RenderWarning
191
150
  on_dismiss={on_dismiss_warning}
192
- dimensions={ctx.props().render_limits}
151
+ dimensions={ctx.props().renderer_props.render_limits}
193
152
  />
194
153
  <slot />
195
154
  </div>
@@ -30,8 +30,9 @@ pub mod form;
30
30
  pub mod function_dropdown;
31
31
  pub mod main_panel;
32
32
  pub mod modal;
33
- pub mod number_column_style;
33
+ pub mod number_series_style;
34
34
  pub mod plugin_selector;
35
+ pub mod plugin_tab;
35
36
  pub mod portal;
36
37
  pub mod render_warning;
37
38
  pub mod settings_panel;
@@ -0,0 +1,161 @@
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
+
13
+ use web_sys::{HtmlInputElement, InputEvent};
14
+ use yew::prelude::*;
15
+
16
+ use super::modal::{ModalLink, SetModalLink};
17
+ use super::style::LocalStyle;
18
+ use crate::components::form::select_enum_field::SelectEnumField;
19
+ use crate::config::*;
20
+ use crate::css;
21
+ use crate::utils::WeakScope;
22
+
23
+ #[derive(Properties)]
24
+ pub struct NumberSeriesStyleProps {
25
+ pub config: Option<NumberSeriesStyleConfig>,
26
+ pub default_config: NumberSeriesStyleDefaultConfig,
27
+
28
+ #[prop_or_default]
29
+ pub on_change: Callback<ColumnConfigFieldUpdate>,
30
+
31
+ #[prop_or_default]
32
+ pub keys: Vec<String>,
33
+
34
+ #[prop_or_default]
35
+ weak_link: WeakScope<NumberSeriesStyle>,
36
+ }
37
+
38
+ impl ModalLink<NumberSeriesStyle> for NumberSeriesStyleProps {
39
+ fn weak_link(&self) -> &'_ WeakScope<NumberSeriesStyle> {
40
+ &self.weak_link
41
+ }
42
+ }
43
+
44
+ impl PartialEq for NumberSeriesStyleProps {
45
+ fn eq(&self, other: &Self) -> bool {
46
+ self.config == other.config && self.default_config == other.default_config
47
+ }
48
+ }
49
+
50
+ pub enum NumberSeriesStyleMsg {
51
+ ChartTypeChanged(Option<ChartType>),
52
+ StackChanged(Option<bool>),
53
+ }
54
+
55
+ /// Form control for the per-column `chart_type` + `stack` picker. Rendered
56
+ /// inside the column-settings sidebar when the active plugin returns a
57
+ /// `ControlSpec::NumberSeriesStyle` from its `column_config_schema` hook.
58
+ pub struct NumberSeriesStyle {
59
+ config: NumberSeriesStyleConfig,
60
+ }
61
+
62
+ impl Component for NumberSeriesStyle {
63
+ type Message = NumberSeriesStyleMsg;
64
+ type Properties = NumberSeriesStyleProps;
65
+
66
+ fn create(ctx: &Context<Self>) -> Self {
67
+ ctx.set_modal_link();
68
+ Self {
69
+ config: ctx.props().config.clone().unwrap_or_default(),
70
+ }
71
+ }
72
+
73
+ fn changed(&mut self, ctx: &Context<Self>, _old: &Self::Properties) -> bool {
74
+ let new_config = ctx.props().config.clone().unwrap_or_default();
75
+ if self.config != new_config {
76
+ self.config = new_config;
77
+ true
78
+ } else {
79
+ false
80
+ }
81
+ }
82
+
83
+ fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
84
+ match msg {
85
+ NumberSeriesStyleMsg::ChartTypeChanged(val) => {
86
+ self.config.chart_type = val.unwrap_or_default();
87
+ // Hiding the stack checkbox on Line/Scatter also clears any
88
+ // lingering override so the JSON stays empty by default.
89
+ if !self.config.chart_type.supports_stack() {
90
+ self.config.stack = None;
91
+ }
92
+ self.dispatch_config(ctx);
93
+ true
94
+ },
95
+ NumberSeriesStyleMsg::StackChanged(val) => {
96
+ self.config.stack = val;
97
+ self.dispatch_config(ctx);
98
+ true
99
+ },
100
+ }
101
+ }
102
+
103
+ fn view(&self, ctx: &Context<Self>) -> Html {
104
+ let chart_type_changed = ctx.link().callback(NumberSeriesStyleMsg::ChartTypeChanged);
105
+
106
+ let stack_controls = if self.config.chart_type.supports_stack() {
107
+ // Default: bar/area stack. `None` == inherit the default.
108
+ let checked = self.config.stack.unwrap_or(true);
109
+ let oninput = ctx.link().callback(move |e: InputEvent| {
110
+ let input: HtmlInputElement = e.target_unchecked_into();
111
+ let next = input.checked();
112
+ // Persist explicit `false` overrides; the "stacked" default
113
+ // round-trips as `None` to keep JSON empty.
114
+ NumberSeriesStyleMsg::StackChanged(if next { None } else { Some(false) })
115
+ });
116
+ html! {
117
+ <div class="row">
118
+ <label id="stack-label" />
119
+ <input type="checkbox" id="stack-checkbox" {checked} {oninput} />
120
+ </div>
121
+ }
122
+ } else {
123
+ html! {}
124
+ };
125
+
126
+ html! {
127
+ <>
128
+ <LocalStyle href={css!("column-style")} />
129
+ <div id="column-style-container" class="number-series-style-container">
130
+ <SelectEnumField<ChartType>
131
+ label="chart-type"
132
+ on_change={chart_type_changed}
133
+ current_value={self.config.chart_type}
134
+ />
135
+ { stack_controls }
136
+ </div>
137
+ </>
138
+ }
139
+ }
140
+ }
141
+
142
+ impl NumberSeriesStyle {
143
+ /// Dispatch the current config as an update. The default (Bar + no
144
+ /// stack override) round-trips as an empty JSON object via
145
+ /// `skip_serializing_if`, which means a field-level reset for this
146
+ /// schema field.
147
+ fn dispatch_config(&self, ctx: &Context<Self>) {
148
+ let value = if self.config == NumberSeriesStyleConfig::default() {
149
+ serde_json::Map::new()
150
+ } else {
151
+ match serde_json::to_value(&self.config) {
152
+ Ok(serde_json::Value::Object(m)) => m,
153
+ _ => serde_json::Map::new(),
154
+ }
155
+ };
156
+ ctx.props().on_change.emit(ColumnConfigFieldUpdate {
157
+ keys: ctx.props().keys.clone(),
158
+ value,
159
+ });
160
+ }
161
+ }