@perspective-dev/viewer 4.3.0 → 4.4.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 (243) hide show
  1. package/dist/cdn/perspective-viewer.js +2 -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 -0
  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/extensions.d.ts +4 -0
  24. package/dist/esm/perspective-viewer.d.ts +1 -0
  25. package/dist/esm/perspective-viewer.inline.js +2 -2
  26. package/dist/esm/perspective-viewer.inline.js.map +4 -4
  27. package/dist/esm/perspective-viewer.js +2 -2
  28. package/dist/esm/perspective-viewer.js.map +4 -4
  29. package/dist/esm/plugin.d.ts +9 -9
  30. package/dist/esm/ts-rs/NumberForegroundMode.d.ts +1 -1
  31. package/dist/esm/ts-rs/ViewerConfig.d.ts +36 -0
  32. package/dist/wasm/perspective-viewer.d.ts +68 -59
  33. package/dist/wasm/perspective-viewer.js +216 -171
  34. package/dist/wasm/perspective-viewer.wasm +0 -0
  35. package/dist/wasm/perspective-viewer.wasm.d.ts +19 -20
  36. package/package.json +7 -5
  37. package/src/{less/aggregate-selector.less → css/aggregate-selector.css} +23 -20
  38. package/src/css/column-dropdown.css +109 -0
  39. package/src/{less/column-selector.less → css/column-selector.css} +160 -158
  40. package/src/{less/column-settings-panel.less → css/column-settings-panel.css} +70 -59
  41. package/src/{less/column-style.less → css/column-style.css} +52 -66
  42. package/src/{less/column-symbol-attributes.less → css/column-symbol-attributes.css} +15 -14
  43. package/src/{less/config-selector.less → css/config-selector.css} +151 -135
  44. package/src/{less/containers/dropdown-menu.less → css/containers/dropdown-menu.css} +20 -19
  45. package/src/{less/containers/pairs-list.less → css/containers/pairs-list.css} +13 -12
  46. package/src/{themes/variables.less → css/containers/scroll-panel.css} +25 -22
  47. package/src/{less/containers/split-panel.less → css/containers/split-panel.css} +15 -14
  48. package/src/{less/containers/tabs.less → css/containers/tabs.css} +17 -19
  49. package/src/css/dom/checkbox.css +102 -0
  50. package/src/css/dom/scrollbar.css +35 -0
  51. package/src/{less/dom/select.less → css/dom/select.css} +17 -18
  52. package/src/{less/empty-column.less → css/empty-column.css} +19 -18
  53. package/src/{less/expression-editor.less → css/expression-editor.css} +19 -18
  54. package/src/{less/filter-dropdown.less → css/filter-dropdown.css} +12 -11
  55. package/src/{less/filter-item.less → css/filter-item.css} +16 -15
  56. package/src/{less/form/code-editor.less → css/form/code-editor.css} +26 -30
  57. package/src/{less/form/debug.less → css/form/debug.css} +19 -18
  58. package/src/{less/function-dropdown.less → css/function-dropdown.css} +12 -11
  59. package/src/css/plugin-selector.css +261 -0
  60. package/src/{less/render-warning.less → css/render-warning.css} +18 -17
  61. package/src/{less/status-bar.less → css/status-bar.css} +157 -145
  62. package/src/css/type-icon.css +116 -0
  63. package/src/{less/viewer.less → css/viewer.css} +112 -146
  64. package/src/rust/components/column_dropdown.rs +229 -119
  65. package/src/rust/components/column_selector/active_column.rs +81 -62
  66. package/src/rust/components/column_selector/add_expression_button.rs +1 -0
  67. package/src/rust/components/column_selector/aggregate_selector.rs +25 -15
  68. package/src/rust/components/column_selector/config_selector.rs +315 -199
  69. package/src/rust/components/column_selector/empty_column.rs +2 -2
  70. package/src/rust/components/column_selector/expr_edit_button.rs +8 -2
  71. package/src/rust/components/column_selector/filter_column.rs +37 -26
  72. package/src/rust/components/column_selector/inactive_column.rs +41 -29
  73. package/src/rust/components/column_selector/invalid_column.rs +7 -18
  74. package/src/rust/components/column_selector/pivot_column.rs +11 -5
  75. package/src/rust/components/column_selector/sort_column.rs +23 -13
  76. package/src/rust/components/column_selector.rs +163 -84
  77. package/src/rust/components/column_settings_sidebar/style_tab/symbol/row_selector.rs +1 -1
  78. package/src/rust/components/column_settings_sidebar/style_tab/symbol/symbol_pairs.rs +3 -2
  79. package/src/rust/components/column_settings_sidebar/style_tab/symbol/symbol_pairs_item.rs +3 -2
  80. package/src/rust/components/column_settings_sidebar/style_tab/symbol/symbol_selector.rs +2 -3
  81. package/src/rust/components/column_settings_sidebar/style_tab/symbol.rs +7 -1
  82. package/src/rust/components/column_settings_sidebar/style_tab.rs +153 -112
  83. package/src/rust/components/column_settings_sidebar.rs +91 -53
  84. package/src/rust/components/containers/dragdrop_list.rs +2 -1
  85. package/src/rust/components/containers/sidebar_close_button.rs +1 -1
  86. package/src/rust/components/containers/split_panel.rs +1 -0
  87. package/src/rust/components/containers/tab_list.rs +1 -1
  88. package/src/rust/components/copy_dropdown.rs +7 -28
  89. package/src/rust/components/datetime_column_style/custom.rs +2 -2
  90. package/src/rust/components/datetime_column_style/simple.rs +2 -2
  91. package/src/rust/components/datetime_column_style.rs +4 -2
  92. package/src/rust/components/editable_header.rs +7 -4
  93. package/src/rust/components/empty_row.rs +1 -1
  94. package/src/rust/components/export_dropdown.rs +4 -30
  95. package/src/rust/components/expression_editor.rs +19 -10
  96. package/src/rust/components/filter_dropdown.rs +246 -102
  97. package/src/rust/components/font_loader.rs +11 -28
  98. package/src/rust/components/form/code_editor.rs +17 -2
  99. package/src/rust/components/form/color_range_selector.rs +19 -6
  100. package/src/rust/components/form/debug.rs +30 -13
  101. package/src/rust/components/function_dropdown.rs +186 -113
  102. package/src/rust/components/main_panel.rs +71 -89
  103. package/src/rust/components/mod.rs +1 -1
  104. package/src/rust/components/modal.rs +7 -1
  105. package/src/rust/components/number_column_style.rs +30 -7
  106. package/src/rust/components/plugin_selector.rs +34 -102
  107. package/src/rust/components/portal.rs +274 -0
  108. package/src/rust/components/render_warning.rs +72 -123
  109. package/src/rust/components/settings_panel.rs +115 -11
  110. package/src/rust/components/status_bar.rs +222 -98
  111. package/src/rust/components/status_bar_counter.rs +8 -20
  112. package/src/rust/components/status_indicator.rs +64 -114
  113. package/src/rust/components/string_column_style.rs +2 -2
  114. package/src/rust/components/style/style_cache.rs +5 -1
  115. package/src/rust/components/viewer.rs +391 -40
  116. package/src/rust/config/number_column_style.rs +5 -1
  117. package/src/rust/config/viewer_config.rs +2 -2
  118. package/src/rust/custom_elements/copy_dropdown.rs +102 -21
  119. package/src/rust/custom_elements/debug_plugin.rs +4 -4
  120. package/src/rust/custom_elements/export_dropdown.rs +102 -20
  121. package/src/rust/custom_elements/mod.rs +0 -7
  122. package/src/rust/custom_elements/modal.rs +7 -103
  123. package/src/rust/custom_elements/viewer.rs +114 -39
  124. package/src/rust/custom_events.rs +23 -2
  125. package/src/rust/dragdrop.rs +149 -10
  126. package/src/{less/containers/scroll-panel.less → rust/engines.rs} +15 -13
  127. package/src/rust/js/plugin.rs +1 -1
  128. package/src/rust/lib.rs +10 -4
  129. package/src/rust/presentation/props.rs +39 -0
  130. package/src/rust/presentation/sheets.rs +3 -3
  131. package/src/rust/presentation.rs +44 -8
  132. package/src/rust/renderer/limits.rs +32 -3
  133. package/src/{less/dom/scrollbar.less → rust/renderer/props.rs} +18 -19
  134. package/src/rust/renderer.rs +93 -14
  135. package/src/rust/session/column_defaults_update.rs +1 -1
  136. package/src/rust/session/metadata.rs +23 -2
  137. package/src/rust/session/props.rs +178 -0
  138. package/src/rust/session.rs +124 -117
  139. package/src/rust/tasks/column_locator.rs +133 -0
  140. package/src/rust/{model → tasks}/columns_iter_set.rs +14 -23
  141. package/src/rust/{model → tasks}/edit_expression.rs +34 -10
  142. package/src/rust/{model → tasks}/eject.rs +2 -2
  143. package/src/rust/{model → tasks}/get_viewer_config.rs +0 -11
  144. package/src/rust/{model → tasks}/intersection_observer.rs +19 -3
  145. package/src/{less/containers/radio-list.less → rust/tasks/is_invalid_drop.rs} +21 -14
  146. package/src/rust/tasks/mod.rs +52 -0
  147. package/src/rust/{model → tasks}/plugin_column_styles.rs +69 -46
  148. package/src/rust/{model → tasks}/resize_observer.rs +39 -6
  149. package/src/rust/{model → tasks}/restore_and_render.rs +4 -3
  150. package/src/rust/{model → tasks}/send_plugin_config.rs +1 -1
  151. package/src/rust/tasks/structural.rs +53 -0
  152. package/src/rust/utils/mod.rs +4 -0
  153. package/src/rust/utils/modal_position.rs +110 -0
  154. package/src/rust/utils/ptr_eq_rc.rs +74 -0
  155. package/src/rust/utils/pubsub.rs +11 -1
  156. package/src/svg/bg-pattern.png +0 -0
  157. package/src/svg/close-icon.svg +1 -1
  158. package/src/svg/datagrid-select-row-tree.svg +13 -0
  159. package/src/svg/expression.svg +1 -1
  160. package/src/svg/mega-menu-icons-candlestick.svg +1 -1
  161. package/src/svg/mega-menu-icons-datagrid.svg +1 -2
  162. package/src/svg/mega-menu-icons-heatmap.svg +1 -1
  163. package/src/svg/mega-menu-icons-map-scatter.svg +1 -1
  164. package/src/svg/mega-menu-icons-ohlc.svg +1 -1
  165. package/src/svg/mega-menu-icons-sunburst.svg +1 -1
  166. package/src/svg/mega-menu-icons-treemap.svg +1 -1
  167. package/src/svg/mega-menu-icons-x-bar.svg +1 -1
  168. package/src/svg/mega-menu-icons-x-y-line.svg +1 -1
  169. package/src/svg/mega-menu-icons-x-y-scatter.svg +1 -1
  170. package/src/svg/mega-menu-icons-y-area.svg +1 -1
  171. package/src/svg/mega-menu-icons-y-bar.svg +1 -1
  172. package/src/svg/mega-menu-icons-y-line.svg +1 -1
  173. package/src/svg/mega-menu-icons-y-scatter.svg +1 -1
  174. package/src/svg/radio-hover.svg +1 -1
  175. package/src/svg/radio-off.svg +1 -1
  176. package/src/svg/radio-on.svg +1 -1
  177. package/src/themes/botanical.css +157 -0
  178. package/src/themes/defaults.css +157 -0
  179. package/src/themes/dracula.css +233 -0
  180. package/src/themes/gruvbox-dark.css +255 -0
  181. package/src/themes/gruvbox.css +134 -0
  182. package/src/themes/icons.css +107 -0
  183. package/src/themes/intl/de.css +102 -0
  184. package/src/themes/intl/es.css +102 -0
  185. package/src/themes/intl/fr.css +102 -0
  186. package/src/themes/intl/ja.css +102 -0
  187. package/src/themes/intl/pt.css +102 -0
  188. package/src/themes/intl/zh.css +102 -0
  189. package/src/themes/intl.css +102 -0
  190. package/src/themes/monokai.css +233 -0
  191. package/src/themes/phosphor.css +184 -0
  192. package/src/themes/pro-dark.css +158 -0
  193. package/src/themes/{themes.less → pro.css} +17 -21
  194. package/src/themes/solarized-dark.css +135 -0
  195. package/src/themes/solarized.css +95 -0
  196. package/src/themes/themes.css +23 -0
  197. package/src/themes/vaporwave.css +256 -0
  198. package/src/ts/extensions.ts +8 -1
  199. package/src/ts/perspective-viewer.ts +1 -0
  200. package/src/ts/plugin.ts +9 -9
  201. package/src/ts/ts-rs/NumberForegroundMode.ts +1 -1
  202. package/src/ts/ts-rs/ViewerConfig.ts +15 -0
  203. package/dist/css/variables.css +0 -0
  204. package/src/less/column-dropdown.less +0 -95
  205. package/src/less/dom/checkbox.less +0 -100
  206. package/src/less/plugin-selector.less +0 -183
  207. package/src/less/type-icon.less +0 -68
  208. package/src/rust/components/error_message.rs +0 -56
  209. package/src/rust/custom_elements/column_dropdown.rs +0 -123
  210. package/src/rust/custom_elements/filter_dropdown.rs +0 -179
  211. package/src/rust/custom_elements/function_dropdown.rs +0 -115
  212. package/src/rust/model/column_locator.rs +0 -82
  213. package/src/rust/model/is_invalid_drop.rs +0 -36
  214. package/src/rust/model/mod.rs +0 -100
  215. package/src/rust/model/reset_all.rs +0 -38
  216. package/src/rust/model/structural.rs +0 -244
  217. package/src/themes/botanical.less +0 -142
  218. package/src/themes/dracula.less +0 -101
  219. package/src/themes/gruvbox-dark.less +0 -116
  220. package/src/themes/gruvbox.less +0 -152
  221. package/src/themes/icons.less +0 -130
  222. package/src/themes/intl/de.less +0 -102
  223. package/src/themes/intl/es.less +0 -102
  224. package/src/themes/intl/fr.less +0 -102
  225. package/src/themes/intl/ja.less +0 -102
  226. package/src/themes/intl/pt.less +0 -102
  227. package/src/themes/intl/zh.less +0 -102
  228. package/src/themes/intl.less +0 -102
  229. package/src/themes/monokai.less +0 -107
  230. package/src/themes/pro-dark.less +0 -147
  231. package/src/themes/pro.less +0 -186
  232. package/src/themes/solarized-dark.less +0 -78
  233. package/src/themes/solarized.less +0 -102
  234. package/src/themes/vaporwave.less +0 -145
  235. /package/dist/wasm/snippets/{perspective-viewer-d729f682ba5c19df → perspective-viewer-d924246f0b4a3dce}/inline0.js +0 -0
  236. /package/dist/wasm/snippets/{perspective-viewer-d729f682ba5c19df → perspective-viewer-d924246f0b4a3dce}/inline1.js +0 -0
  237. /package/dist/wasm/snippets/{perspective-viewer-d729f682ba5c19df → perspective-viewer-d924246f0b4a3dce}/inline2.js +0 -0
  238. /package/dist/wasm/snippets/{perspective-viewer-d729f682ba5c19df → perspective-viewer-d924246f0b4a3dce}/inline3.js +0 -0
  239. /package/dist/wasm/snippets/{perspective-viewer-d729f682ba5c19df → perspective-viewer-d924246f0b4a3dce}/inline4.js +0 -0
  240. /package/src/rust/{model → tasks}/copy_export.rs +0 -0
  241. /package/src/rust/{model → tasks}/export_app.rs +0 -0
  242. /package/src/rust/{model → tasks}/export_method.rs +0 -0
  243. /package/src/rust/{model → tasks}/update_and_render.rs +0 -0
@@ -40,8 +40,8 @@ impl ModalLink<DatetimeStyleCustom> for DatetimeStyleCustomProps {
40
40
  }
41
41
 
42
42
  impl PartialEq for DatetimeStyleCustomProps {
43
- fn eq(&self, _other: &Self) -> bool {
44
- false
43
+ fn eq(&self, other: &Self) -> bool {
44
+ self.enable_time_config == other.enable_time_config && self.config == other.config
45
45
  }
46
46
  }
47
47
 
@@ -36,8 +36,8 @@ impl ModalLink<DatetimeStyleSimple> for DatetimeStyleSimpleProps {
36
36
  }
37
37
 
38
38
  impl PartialEq for DatetimeStyleSimpleProps {
39
- fn eq(&self, _other: &Self) -> bool {
40
- false
39
+ fn eq(&self, other: &Self) -> bool {
40
+ self.enable_time_config == other.enable_time_config && self.config == other.config
41
41
  }
42
42
  }
43
43
 
@@ -56,8 +56,10 @@ impl ModalLink<DatetimeColumnStyle> for DatetimeColumnStyleProps {
56
56
  }
57
57
 
58
58
  impl PartialEq for DatetimeColumnStyleProps {
59
- fn eq(&self, _other: &Self) -> bool {
60
- false
59
+ fn eq(&self, other: &Self) -> bool {
60
+ self.enable_time_config == other.enable_time_config
61
+ && self.config == other.config
62
+ && self.default_config == other.default_config
61
63
  }
62
64
  }
63
65
 
@@ -18,10 +18,10 @@ use yew::{Callback, Component, Html, NodeRef, Properties, TargetCast, classes, h
18
18
 
19
19
  use super::type_icon::TypeIconType;
20
20
  use crate::components::type_icon::TypeIcon;
21
- use crate::session::Session;
22
- use crate::*;
21
+ use crate::maybe;
22
+ use crate::session::{Session, SessionMetadataRc};
23
23
 
24
- #[derive(Clone, PartialEq, Properties, PerspectiveProperties!)]
24
+ #[derive(Clone, PartialEq, Properties)]
25
25
  pub struct EditableHeaderProps {
26
26
  pub icon_type: Option<TypeIconType>,
27
27
  pub on_change: Callback<(Option<String>, bool)>,
@@ -32,6 +32,9 @@ pub struct EditableHeaderProps {
32
32
  #[prop_or_default]
33
33
  pub reset_count: u8,
34
34
 
35
+ /// Session metadata snapshot — threaded from `SessionProps`.
36
+ pub metadata: SessionMetadataRc,
37
+
35
38
  // State
36
39
  pub session: Session,
37
40
  }
@@ -111,7 +114,7 @@ impl Component for EditableHeader {
111
114
  if !self.edited {
112
115
  return Some(true);
113
116
  }
114
- let metadata = ctx.props().session.metadata();
117
+ let metadata = &ctx.props().metadata;
115
118
  let expressions = metadata.get_expression_columns();
116
119
  let found = metadata
117
120
  .get_table_columns()?
@@ -19,9 +19,9 @@ use wasm_bindgen::JsCast;
19
19
  use web_sys::*;
20
20
  use yew::prelude::*;
21
21
 
22
+ use crate::components::filter_dropdown::FilterDropDownElement;
22
23
  use crate::components::style::LocalStyle;
23
24
  use crate::css;
24
- use crate::custom_elements::FilterDropDownElement;
25
25
 
26
26
  #[derive(Properties, Derivative)]
27
27
  #[derivative(Debug)]
@@ -12,16 +12,12 @@
12
12
 
13
13
  use std::rc::Rc;
14
14
 
15
- use session::Session;
16
15
  use yew::prelude::*;
17
16
 
18
17
  use super::containers::dropdown_menu::*;
19
- use super::modal::{ModalLink, SetModalLink};
20
- use super::style::StyleProvider;
21
- use crate::model::*;
22
18
  use crate::renderer::*;
23
- use crate::utils::*;
24
- use crate::*;
19
+ use crate::session::Session;
20
+ use crate::tasks::*;
25
21
 
26
22
  pub type ExportDropDownMenuItem = DropDownMenuItem<ExportFile>;
27
23
 
@@ -30,16 +26,6 @@ pub struct ExportDropDownMenuProps {
30
26
  pub renderer: Renderer,
31
27
  pub session: Session,
32
28
  pub callback: Callback<ExportFile>,
33
- pub root: web_sys::HtmlElement,
34
-
35
- #[prop_or_default]
36
- weak_link: WeakScope<ExportDropDownMenu>,
37
- }
38
-
39
- impl ModalLink<ExportDropDownMenu> for ExportDropDownMenuProps {
40
- fn weak_link(&self) -> &'_ utils::WeakScope<ExportDropDownMenu> {
41
- &self.weak_link
42
- }
43
29
  }
44
30
 
45
31
  pub enum ExportDropDownMenuMsg {
@@ -49,7 +35,6 @@ pub enum ExportDropDownMenuMsg {
49
35
  #[derive(Default)]
50
36
  pub struct ExportDropDownMenu {
51
37
  title: String,
52
- _sub: Option<Subscription>,
53
38
  input_ref: NodeRef,
54
39
  invalid: bool,
55
40
  }
@@ -61,11 +46,9 @@ impl Component for ExportDropDownMenu {
61
46
  fn view(&self, ctx: &Context<Self>) -> yew::virtual_dom::VNode {
62
47
  let callback = ctx.link().callback(|_| ExportDropDownMenuMsg::TitleChange);
63
48
  let plugin = ctx.props().renderer.get_active_plugin().unwrap();
64
- // let has_render = js_sys::Reflect::has(&plugin,
65
- // js_intern::js_intern!("render")).unwrap();
66
49
  let is_chart = plugin.name().as_str() != "Datagrid";
67
50
  html! {
68
- <StyleProvider root={ctx.props().root.clone()}>
51
+ <>
69
52
  <span class="dropdown-group-label">{ "Save as" }</span>
70
53
  <input
71
54
  class={if self.invalid { "invalid" } else { "" }}
@@ -77,7 +60,7 @@ impl Component for ExportDropDownMenu {
77
60
  values={Rc::new(get_menu_items(&self.title, is_chart))}
78
61
  callback={&ctx.props().callback}
79
62
  />
80
- </StyleProvider>
63
+ </>
81
64
  }
82
65
  }
83
66
 
@@ -97,21 +80,12 @@ impl Component for ExportDropDownMenu {
97
80
  }
98
81
 
99
82
  fn create(ctx: &Context<Self>) -> Self {
100
- ctx.set_modal_link();
101
- let _sub = Some(
102
- ctx.props()
103
- .renderer
104
- .plugin_changed
105
- .add_listener(ctx.link().callback(|_| ExportDropDownMenuMsg::TitleChange)),
106
- );
107
-
108
83
  Self {
109
84
  title: ctx
110
85
  .props()
111
86
  .session
112
87
  .get_title()
113
88
  .unwrap_or_else(|| "untitled".to_owned()),
114
- _sub,
115
89
  ..Default::default()
116
90
  }
117
91
  }
@@ -17,11 +17,10 @@ use yew::prelude::*;
17
17
 
18
18
  use super::form::code_editor::*;
19
19
  use super::style::LocalStyle;
20
- use crate::model::*;
21
- use crate::session::Session;
20
+ use crate::session::{Session, SessionMetadata, SessionMetadataRc};
22
21
  use crate::*;
23
22
 
24
- #[derive(Properties, PartialEq, PerspectiveProperties!, Clone)]
23
+ #[derive(Properties, PartialEq, Clone)]
25
24
  pub struct ExpressionEditorProps {
26
25
  pub on_save: Callback<()>,
27
26
  pub on_validate: Callback<bool>,
@@ -32,6 +31,13 @@ pub struct ExpressionEditorProps {
32
31
  #[prop_or_default]
33
32
  pub reset_count: u8,
34
33
 
34
+ /// Session metadata snapshot — threaded from `SessionProps`.
35
+ pub metadata: SessionMetadataRc,
36
+
37
+ /// Selected theme name, threaded for PortalModal consumers.
38
+ #[prop_or_default]
39
+ pub selected_theme: Option<String>,
40
+
35
41
  // State
36
42
  pub session: Session,
37
43
  }
@@ -55,7 +61,7 @@ impl Component for ExpressionEditor {
55
61
 
56
62
  fn create(ctx: &Context<Self>) -> Self {
57
63
  let oninput = ctx.link().callback(ExpressionEditorMsg::SetExpr);
58
- let expr = initial_expr(&ctx.props().session, &ctx.props().alias);
64
+ let expr = initial_expr(&ctx.props().metadata, &ctx.props().alias);
59
65
  ctx.link()
60
66
  .send_message(Self::Message::SetExpr(expr.clone()));
61
67
 
@@ -89,8 +95,8 @@ impl Component for ExpressionEditor {
89
95
  if self.error.is_none() {
90
96
  maybe!({
91
97
  let alias = ctx.props().alias.as_ref()?;
92
- let session = ctx.props().session();
93
- let old = session.metadata().get_expression_by_alias(alias)?;
98
+ let session = &ctx.props().session;
99
+ let old = ctx.props().metadata.get_expression_by_alias(alias)?;
94
100
  let is_edited = *self.expr != old;
95
101
  session
96
102
  .metadata_mut()
@@ -124,6 +130,7 @@ impl Component for ExpressionEditor {
124
130
  {disabled}
125
131
  oninput={self.oninput.clone()}
126
132
  onsave={ctx.props().on_save.clone()}
133
+ theme={ctx.props().selected_theme.clone().unwrap_or_default()}
127
134
  />
128
135
  <div id="psp-expression-editor-meta">
129
136
  <div class="error">
@@ -136,11 +143,13 @@ impl Component for ExpressionEditor {
136
143
  }
137
144
 
138
145
  fn changed(&mut self, ctx: &Context<Self>, old_props: &Self::Properties) -> bool {
139
- if ctx.props().alias != old_props.alias || ctx.props().reset_count != old_props.reset_count
146
+ if ctx.props().alias != old_props.alias
147
+ || ctx.props().reset_count != old_props.reset_count
148
+ || (ctx.props().alias.is_some() && ctx.props().metadata != old_props.metadata)
140
149
  {
141
150
  ctx.link()
142
151
  .send_message(ExpressionEditorMsg::SetExpr(initial_expr(
143
- &ctx.props().session,
152
+ &ctx.props().metadata,
144
153
  &ctx.props().alias,
145
154
  )));
146
155
  false
@@ -150,10 +159,10 @@ impl Component for ExpressionEditor {
150
159
  }
151
160
  }
152
161
 
153
- fn initial_expr(session: &Session, alias: &Option<String>) -> Rc<String> {
162
+ fn initial_expr(metadata: &SessionMetadata, alias: &Option<String>) -> Rc<String> {
154
163
  alias
155
164
  .as_ref()
156
- .and_then(|alias| session.metadata().get_expression_by_alias(alias))
165
+ .and_then(|alias| metadata.get_expression_by_alias(alias))
157
166
  .unwrap_or_default()
158
167
  .into()
159
168
  }
@@ -10,135 +10,279 @@
10
10
  // ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11
11
  // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
12
 
13
+ use std::cell::RefCell;
14
+ use std::collections::HashSet;
15
+ use std::rc::Rc;
16
+
17
+ use perspective_client::clone;
13
18
  use web_sys::*;
19
+ use yew::html::ImplicitClone;
14
20
  use yew::prelude::*;
15
21
 
16
- use super::modal::*;
17
- use crate::utils::WeakScope;
22
+ use super::portal::PortalModal;
23
+ use crate::session::Session;
24
+ use crate::utils::*;
25
+ use crate::*;
18
26
 
19
27
  static CSS: &str = include_str!(concat!(env!("OUT_DIR"), "/css/filter-dropdown.css"));
20
28
 
21
- #[derive(Properties, PartialEq)]
22
- pub struct FilterDropDownProps {
23
- #[prop_or_default]
24
- pub weak_link: WeakScope<FilterDropDown>,
25
- }
26
-
27
- impl ModalLink<FilterDropDown> for FilterDropDownProps {
28
- fn weak_link(&self) -> &'_ WeakScope<FilterDropDown> {
29
- &self.weak_link
30
- }
29
+ #[derive(Default)]
30
+ struct FilterDropDownState {
31
+ values: Vec<String>,
32
+ selected: usize,
33
+ on_select: Option<Callback<String>>,
34
+ target: Option<HtmlElement>,
31
35
  }
32
36
 
33
- pub enum FilterDropDownMsg {
34
- SetValues(Vec<String>),
35
- SetCallback(Callback<String>),
36
- ItemDown,
37
- ItemUp,
38
- ItemSelect,
37
+ #[derive(Clone)]
38
+ pub struct FilterDropDownElement {
39
+ state: Rc<RefCell<FilterDropDownState>>,
40
+ session: Session,
41
+ column: Rc<RefCell<Option<(usize, String)>>>,
42
+ all_values: Rc<RefCell<Option<Vec<String>>>>,
43
+ notify: Rc<PubSub<()>>,
39
44
  }
40
45
 
41
- pub struct FilterDropDown {
42
- values: Option<Vec<String>>,
43
- selected: usize,
44
- on_select: Option<Callback<String>>,
46
+ impl PartialEq for FilterDropDownElement {
47
+ fn eq(&self, other: &Self) -> bool {
48
+ Rc::ptr_eq(&self.state, &other.state)
49
+ }
45
50
  }
46
51
 
47
- impl Component for FilterDropDown {
48
- type Message = FilterDropDownMsg;
49
- type Properties = FilterDropDownProps;
52
+ impl ImplicitClone for FilterDropDownElement {}
50
53
 
51
- fn create(ctx: &Context<Self>) -> Self {
52
- ctx.set_modal_link();
54
+ impl FilterDropDownElement {
55
+ pub fn new(session: Session) -> Self {
53
56
  Self {
54
- values: Some(vec![]),
55
- selected: 0,
56
- on_select: None,
57
+ state: Default::default(),
58
+ session,
59
+ column: Default::default(),
60
+ all_values: Default::default(),
61
+ notify: Rc::default(),
57
62
  }
58
63
  }
59
64
 
60
- fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
61
- match msg {
62
- FilterDropDownMsg::SetCallback(callback) => {
63
- self.on_select = Some(callback);
64
- false
65
- },
66
- FilterDropDownMsg::SetValues(values) => {
67
- self.values = Some(values);
68
- self.selected = 0;
69
- true
70
- },
71
- FilterDropDownMsg::ItemSelect => {
72
- if let Some(ref values) = self.values {
73
- match values.get(self.selected) {
74
- None => {
75
- console::error_1(&"Selected out-of-bounds".into());
76
- false
77
- },
78
- Some(x) => {
79
- self.on_select.as_ref().unwrap().emit(x.clone());
80
- false
81
- },
82
- }
65
+ pub fn reautocomplete(&self) {
66
+ // Re-open portal with current target
67
+ self.notify.emit(());
68
+ }
69
+
70
+ pub fn autocomplete(
71
+ &self,
72
+ column: (usize, String),
73
+ input: String,
74
+ exclude: HashSet<String>,
75
+ target: HtmlElement,
76
+ callback: Callback<String>,
77
+ ) {
78
+ let current_column = self.column.borrow().clone();
79
+ match current_column {
80
+ Some(filter_col) if filter_col == column => {
81
+ let values = filter_values(&input, &self.all_values, &exclude);
82
+ if values.len() == 1 && values[0] == input {
83
+ let _ = self.hide();
83
84
  } else {
84
- console::error_1(&"No Values".into());
85
- false
85
+ let mut s = self.state.borrow_mut();
86
+ s.values = values;
87
+ s.selected = 0;
88
+ s.on_select = Some(callback);
89
+ if s.target.is_none() {
90
+ s.target = Some(target);
91
+ }
92
+
93
+ drop(s);
94
+ self.notify.emit(());
86
95
  }
87
96
  },
88
- FilterDropDownMsg::ItemDown => {
89
- self.selected += 1;
90
- if let Some(ref values) = self.values
91
- && self.selected >= values.len()
92
- {
93
- self.selected = 0;
94
- };
95
-
96
- true
97
- },
98
- FilterDropDownMsg::ItemUp => {
99
- if let Some(ref values) = self.values
100
- && self.selected < 1
101
- {
102
- self.selected = values.len();
103
- };
104
-
105
- self.selected -= 1;
106
- true
97
+ _ => {
98
+ clone!(
99
+ self.state,
100
+ self.session,
101
+ self.all_values,
102
+ self.notify,
103
+ old_column = self.column
104
+ );
105
+ ApiFuture::spawn(async move {
106
+ let fetched = session.get_column_values(column.1.clone()).await?;
107
+ *all_values.borrow_mut() = Some(fetched);
108
+ let values = filter_values(&input, &all_values, &exclude);
109
+ let should_hide = values.len() == 1 && values[0] == input;
110
+
111
+ *old_column.borrow_mut() = Some(column);
112
+ {
113
+ let mut s = state.borrow_mut();
114
+ s.on_select = Some(callback);
115
+ if should_hide {
116
+ let fv = self::filter_values("", &all_values, &exclude);
117
+ s.values = fv;
118
+ s.target = Some(target);
119
+ } else {
120
+ s.values = values;
121
+ s.target = Some(target);
122
+ }
123
+ s.selected = 0;
124
+ }
125
+ if should_hide {
126
+ state.borrow_mut().target = None;
127
+ }
128
+
129
+ notify.emit(());
130
+ Ok(())
131
+ });
107
132
  },
108
133
  }
109
134
  }
110
135
 
111
- fn changed(&mut self, _ctx: &Context<Self>, _old: &Self::Properties) -> bool {
112
- false
136
+ pub fn item_select(&self) {
137
+ let state = self.state.borrow();
138
+ if let Some(value) = state.values.get(state.selected)
139
+ && let Some(ref cb) = state.on_select
140
+ {
141
+ cb.emit(value.clone());
142
+ }
113
143
  }
114
144
 
115
- fn view(&self, _ctx: &Context<Self>) -> Html {
116
- let body = html! {
117
- if let Some(ref values) = self.values {
118
- if !values.is_empty() {
119
- { for values
120
- .iter()
121
- .enumerate()
122
- .map(|(idx, value)| {
123
- let click = self.on_select.as_ref().unwrap().reform({
124
- let value = value.clone();
125
- move |_: MouseEvent| value.clone()
126
- });
127
-
128
- html! {
129
- if idx == self.selected {
130
- <span onmousedown={ click } class="selected">{ value }</span>
131
- } else {
132
- <span onmousedown={ click }>{ value }</span>
133
- }
134
- }
135
- }) }
136
- } else {
137
- <span class="no-results">{ "No Completions" }</span>
138
- }
139
- }
145
+ pub fn item_down(&self) {
146
+ let mut state = self.state.borrow_mut();
147
+ state.selected += 1;
148
+ if state.selected >= state.values.len() {
149
+ state.selected = 0;
150
+ }
151
+
152
+ drop(state);
153
+ self.notify.emit(());
154
+ }
155
+
156
+ pub fn item_up(&self) {
157
+ let mut state = self.state.borrow_mut();
158
+ if state.selected < 1 {
159
+ state.selected = state.values.len();
160
+ }
161
+
162
+ state.selected -= 1;
163
+ drop(state);
164
+ self.notify.emit(());
165
+ }
166
+
167
+ pub fn hide(&self) -> ApiResult<()> {
168
+ self.state.borrow_mut().target = None;
169
+ self.column.borrow_mut().take();
170
+ self.notify.emit(());
171
+ Ok(())
172
+ }
173
+ }
174
+
175
+ #[derive(Properties, PartialEq)]
176
+ pub struct FilterDropDownPortalProps {
177
+ pub element: FilterDropDownElement,
178
+ pub theme: String,
179
+ }
180
+
181
+ pub struct FilterDropDownPortal {
182
+ _sub: Subscription,
183
+ }
184
+
185
+ impl Component for FilterDropDownPortal {
186
+ type Message = ();
187
+ type Properties = FilterDropDownPortalProps;
188
+
189
+ fn create(ctx: &Context<Self>) -> Self {
190
+ let link = ctx.link().clone();
191
+ let sub = ctx
192
+ .props()
193
+ .element
194
+ .notify
195
+ .add_listener(move |()| link.send_message(()));
196
+ Self { _sub: sub }
197
+ }
198
+
199
+ fn update(&mut self, _ctx: &Context<Self>, _msg: ()) -> bool {
200
+ true
201
+ }
202
+
203
+ fn view(&self, ctx: &Context<Self>) -> Html {
204
+ let state = ctx.props().element.state.borrow();
205
+ let target = state.target.clone();
206
+ let on_close = {
207
+ let element = ctx.props().element.clone();
208
+ Callback::from(move |()| {
209
+ let _ = element.hide();
210
+ })
140
211
  };
141
212
 
142
- html! { <><style>{ CSS }</style>{ body }</> }
213
+ if target.is_some() {
214
+ let values = state.values.clone();
215
+ let selected = state.selected;
216
+ let on_select = state.on_select.clone();
217
+ drop(state);
218
+
219
+ html! {
220
+ <PortalModal
221
+ tag_name="perspective-dropdown"
222
+ {target}
223
+ own_focus=false
224
+ {on_close}
225
+ theme={ctx.props().theme.clone()}
226
+ >
227
+ <FilterDropDownView {values} {selected} {on_select} />
228
+ </PortalModal>
229
+ }
230
+ } else {
231
+ html! {}
232
+ }
233
+ }
234
+ }
235
+
236
+ #[derive(Properties, PartialEq)]
237
+ struct FilterDropDownViewProps {
238
+ values: Vec<String>,
239
+ selected: usize,
240
+ on_select: Option<Callback<String>>,
241
+ }
242
+
243
+ #[function_component]
244
+ fn FilterDropDownView(props: &FilterDropDownViewProps) -> Html {
245
+ let body = html! {
246
+ if !props.values.is_empty() {
247
+ { for props.values
248
+ .iter()
249
+ .enumerate()
250
+ .map(|(idx, value)| {
251
+ let click = props.on_select.as_ref().unwrap().reform({
252
+ let value = value.clone();
253
+ move |_: MouseEvent| value.clone()
254
+ });
255
+
256
+ html! {
257
+ if idx == props.selected {
258
+ <span onmousedown={click} class="selected">{ value }</span>
259
+ } else {
260
+ <span onmousedown={click}>{ value }</span>
261
+ }
262
+ }
263
+ }) }
264
+ } else {
265
+ <span class="no-results">{ "No Completions" }</span>
266
+ }
267
+ };
268
+
269
+ html! { <><style>{ CSS }</style>{ body }</> }
270
+ }
271
+
272
+ fn filter_values(
273
+ input: &str,
274
+ values: &Rc<RefCell<Option<Vec<String>>>>,
275
+ exclude: &HashSet<String>,
276
+ ) -> Vec<String> {
277
+ let input = input.to_lowercase();
278
+ if let Some(values) = &*values.borrow() {
279
+ values
280
+ .iter()
281
+ .filter(|x| x.to_lowercase().contains(&input) && !exclude.contains(x.as_str()))
282
+ .take(10)
283
+ .cloned()
284
+ .collect::<Vec<String>>()
285
+ } else {
286
+ vec![]
143
287
  }
144
288
  }