@perspective-dev/viewer 4.3.0 → 4.4.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.
Files changed (227) 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/pro-dark.css +1 -1
  17. package/dist/css/pro.css +1 -1
  18. package/dist/css/solarized-dark.css +1 -1
  19. package/dist/css/solarized.css +1 -1
  20. package/dist/css/themes.css +1 -1
  21. package/dist/css/vaporwave.css +1 -1
  22. package/dist/esm/perspective-viewer.inline.js +2 -2
  23. package/dist/esm/perspective-viewer.inline.js.map +4 -4
  24. package/dist/esm/perspective-viewer.js +2 -2
  25. package/dist/esm/perspective-viewer.js.map +4 -4
  26. package/dist/wasm/perspective-viewer.d.ts +57 -53
  27. package/dist/wasm/perspective-viewer.js +190 -165
  28. package/dist/wasm/perspective-viewer.wasm +0 -0
  29. package/dist/wasm/perspective-viewer.wasm.d.ts +17 -18
  30. package/package.json +7 -5
  31. package/src/{less/aggregate-selector.less → css/aggregate-selector.css} +23 -20
  32. package/src/css/column-dropdown.css +109 -0
  33. package/src/{less/column-selector.less → css/column-selector.css} +160 -158
  34. package/src/{less/column-settings-panel.less → css/column-settings-panel.css} +69 -59
  35. package/src/{less/column-style.less → css/column-style.css} +52 -66
  36. package/src/{less/column-symbol-attributes.less → css/column-symbol-attributes.css} +15 -14
  37. package/src/{less/config-selector.less → css/config-selector.css} +151 -135
  38. package/src/{less/containers/dropdown-menu.less → css/containers/dropdown-menu.css} +20 -19
  39. package/src/{less/containers/pairs-list.less → css/containers/pairs-list.css} +13 -12
  40. package/src/{themes/variables.less → css/containers/scroll-panel.css} +25 -22
  41. package/src/{less/containers/split-panel.less → css/containers/split-panel.css} +15 -14
  42. package/src/{less/containers/tabs.less → css/containers/tabs.css} +17 -19
  43. package/src/css/dom/checkbox.css +102 -0
  44. package/src/css/dom/scrollbar.css +35 -0
  45. package/src/{less/dom/select.less → css/dom/select.css} +17 -18
  46. package/src/{less/empty-column.less → css/empty-column.css} +19 -18
  47. package/src/{less/expression-editor.less → css/expression-editor.css} +19 -18
  48. package/src/{less/filter-dropdown.less → css/filter-dropdown.css} +12 -11
  49. package/src/{less/filter-item.less → css/filter-item.css} +16 -15
  50. package/src/{less/form/code-editor.less → css/form/code-editor.css} +26 -30
  51. package/src/{less/form/debug.less → css/form/debug.css} +19 -18
  52. package/src/{less/function-dropdown.less → css/function-dropdown.css} +12 -11
  53. package/src/css/plugin-selector.css +261 -0
  54. package/src/{less/render-warning.less → css/render-warning.css} +18 -17
  55. package/src/{less/status-bar.less → css/status-bar.css} +156 -144
  56. package/src/css/type-icon.css +116 -0
  57. package/src/{less/viewer.less → css/viewer.css} +112 -146
  58. package/src/rust/components/column_dropdown.rs +229 -119
  59. package/src/rust/components/column_selector/active_column.rs +81 -62
  60. package/src/rust/components/column_selector/add_expression_button.rs +1 -0
  61. package/src/rust/components/column_selector/aggregate_selector.rs +25 -15
  62. package/src/rust/components/column_selector/config_selector.rs +315 -199
  63. package/src/rust/components/column_selector/empty_column.rs +2 -2
  64. package/src/rust/components/column_selector/expr_edit_button.rs +8 -2
  65. package/src/rust/components/column_selector/filter_column.rs +37 -26
  66. package/src/rust/components/column_selector/inactive_column.rs +41 -29
  67. package/src/rust/components/column_selector/invalid_column.rs +7 -18
  68. package/src/rust/components/column_selector/pivot_column.rs +11 -5
  69. package/src/rust/components/column_selector/sort_column.rs +23 -13
  70. package/src/rust/components/column_selector.rs +163 -84
  71. package/src/rust/components/column_settings_sidebar/style_tab/symbol/row_selector.rs +1 -1
  72. package/src/rust/components/column_settings_sidebar/style_tab/symbol/symbol_pairs.rs +3 -2
  73. package/src/rust/components/column_settings_sidebar/style_tab/symbol/symbol_pairs_item.rs +3 -2
  74. package/src/rust/components/column_settings_sidebar/style_tab/symbol/symbol_selector.rs +2 -3
  75. package/src/rust/components/column_settings_sidebar/style_tab/symbol.rs +7 -1
  76. package/src/rust/components/column_settings_sidebar/style_tab.rs +153 -112
  77. package/src/rust/components/column_settings_sidebar.rs +91 -53
  78. package/src/rust/components/containers/dragdrop_list.rs +2 -1
  79. package/src/rust/components/containers/sidebar_close_button.rs +1 -1
  80. package/src/rust/components/containers/split_panel.rs +1 -0
  81. package/src/rust/components/containers/tab_list.rs +1 -1
  82. package/src/rust/components/copy_dropdown.rs +7 -28
  83. package/src/rust/components/datetime_column_style/custom.rs +2 -2
  84. package/src/rust/components/datetime_column_style/simple.rs +2 -2
  85. package/src/rust/components/datetime_column_style.rs +4 -2
  86. package/src/rust/components/editable_header.rs +7 -4
  87. package/src/rust/components/empty_row.rs +1 -1
  88. package/src/rust/components/export_dropdown.rs +4 -30
  89. package/src/rust/components/expression_editor.rs +19 -10
  90. package/src/rust/components/filter_dropdown.rs +246 -102
  91. package/src/rust/components/font_loader.rs +11 -28
  92. package/src/rust/components/form/code_editor.rs +17 -2
  93. package/src/rust/components/form/color_range_selector.rs +19 -6
  94. package/src/rust/components/form/debug.rs +30 -13
  95. package/src/rust/components/function_dropdown.rs +186 -113
  96. package/src/rust/components/main_panel.rs +71 -89
  97. package/src/rust/components/mod.rs +1 -1
  98. package/src/rust/components/modal.rs +7 -1
  99. package/src/rust/components/number_column_style.rs +22 -7
  100. package/src/rust/components/plugin_selector.rs +34 -102
  101. package/src/rust/components/portal.rs +274 -0
  102. package/src/rust/components/render_warning.rs +72 -123
  103. package/src/rust/components/settings_panel.rs +115 -11
  104. package/src/rust/components/status_bar.rs +222 -98
  105. package/src/rust/components/status_bar_counter.rs +8 -20
  106. package/src/rust/components/status_indicator.rs +64 -114
  107. package/src/rust/components/string_column_style.rs +2 -2
  108. package/src/rust/components/style/style_cache.rs +5 -1
  109. package/src/rust/components/viewer.rs +391 -39
  110. package/src/rust/custom_elements/copy_dropdown.rs +102 -21
  111. package/src/rust/custom_elements/export_dropdown.rs +102 -20
  112. package/src/rust/custom_elements/mod.rs +0 -7
  113. package/src/rust/custom_elements/modal.rs +7 -103
  114. package/src/rust/custom_elements/viewer.rs +99 -35
  115. package/src/rust/custom_events.rs +23 -2
  116. package/src/rust/dragdrop.rs +149 -10
  117. package/src/{less/containers/scroll-panel.less → rust/engines.rs} +15 -13
  118. package/src/rust/js/plugin.rs +1 -1
  119. package/src/rust/lib.rs +5 -4
  120. package/src/rust/presentation/props.rs +39 -0
  121. package/src/rust/presentation/sheets.rs +3 -3
  122. package/src/rust/presentation.rs +44 -8
  123. package/src/rust/renderer/limits.rs +32 -3
  124. package/src/{less/dom/scrollbar.less → rust/renderer/props.rs} +18 -19
  125. package/src/rust/renderer.rs +83 -9
  126. package/src/rust/session/column_defaults_update.rs +1 -1
  127. package/src/rust/session/metadata.rs +23 -2
  128. package/src/rust/session/props.rs +178 -0
  129. package/src/rust/session.rs +124 -117
  130. package/src/rust/tasks/column_locator.rs +133 -0
  131. package/src/rust/{model → tasks}/columns_iter_set.rs +14 -23
  132. package/src/rust/{model → tasks}/edit_expression.rs +34 -10
  133. package/src/rust/{model → tasks}/eject.rs +2 -2
  134. package/src/rust/{model → tasks}/get_viewer_config.rs +0 -11
  135. package/src/rust/{model → tasks}/intersection_observer.rs +19 -3
  136. package/src/{less/containers/radio-list.less → rust/tasks/is_invalid_drop.rs} +21 -14
  137. package/src/rust/tasks/mod.rs +52 -0
  138. package/src/rust/{model → tasks}/plugin_column_styles.rs +69 -46
  139. package/src/rust/{model → tasks}/resize_observer.rs +39 -6
  140. package/src/rust/{model → tasks}/send_plugin_config.rs +1 -1
  141. package/src/rust/tasks/structural.rs +53 -0
  142. package/src/rust/utils/mod.rs +4 -0
  143. package/src/rust/utils/modal_position.rs +110 -0
  144. package/src/rust/utils/ptr_eq_rc.rs +74 -0
  145. package/src/rust/utils/pubsub.rs +11 -1
  146. package/src/svg/bg-pattern.png +0 -0
  147. package/src/svg/close-icon.svg +1 -1
  148. package/src/svg/expression.svg +1 -1
  149. package/src/svg/mega-menu-icons-candlestick.svg +1 -1
  150. package/src/svg/mega-menu-icons-datagrid.svg +1 -2
  151. package/src/svg/mega-menu-icons-heatmap.svg +1 -1
  152. package/src/svg/mega-menu-icons-map-scatter.svg +1 -1
  153. package/src/svg/mega-menu-icons-ohlc.svg +1 -1
  154. package/src/svg/mega-menu-icons-sunburst.svg +1 -1
  155. package/src/svg/mega-menu-icons-treemap.svg +1 -1
  156. package/src/svg/mega-menu-icons-x-bar.svg +1 -1
  157. package/src/svg/mega-menu-icons-x-y-line.svg +1 -1
  158. package/src/svg/mega-menu-icons-x-y-scatter.svg +1 -1
  159. package/src/svg/mega-menu-icons-y-area.svg +1 -1
  160. package/src/svg/mega-menu-icons-y-bar.svg +1 -1
  161. package/src/svg/mega-menu-icons-y-line.svg +1 -1
  162. package/src/svg/mega-menu-icons-y-scatter.svg +1 -1
  163. package/src/svg/radio-hover.svg +1 -1
  164. package/src/svg/radio-off.svg +1 -1
  165. package/src/svg/radio-on.svg +1 -1
  166. package/src/themes/botanical.css +157 -0
  167. package/src/themes/defaults.css +139 -0
  168. package/src/themes/dracula.css +233 -0
  169. package/src/themes/gruvbox-dark.css +255 -0
  170. package/src/themes/gruvbox.css +134 -0
  171. package/src/themes/icons.css +124 -0
  172. package/src/themes/intl/de.css +102 -0
  173. package/src/themes/intl/es.css +102 -0
  174. package/src/themes/intl/fr.css +102 -0
  175. package/src/themes/intl/ja.css +102 -0
  176. package/src/themes/intl/pt.css +102 -0
  177. package/src/themes/intl/zh.css +102 -0
  178. package/src/themes/intl.css +102 -0
  179. package/src/themes/monokai.css +233 -0
  180. package/src/themes/pro-dark.css +158 -0
  181. package/src/themes/{themes.less → pro.css} +17 -21
  182. package/src/themes/solarized-dark.css +135 -0
  183. package/src/themes/solarized.css +95 -0
  184. package/src/themes/themes.css +22 -0
  185. package/src/themes/vaporwave.css +256 -0
  186. package/dist/css/variables.css +0 -0
  187. package/src/less/column-dropdown.less +0 -95
  188. package/src/less/dom/checkbox.less +0 -100
  189. package/src/less/plugin-selector.less +0 -183
  190. package/src/less/type-icon.less +0 -68
  191. package/src/rust/components/error_message.rs +0 -56
  192. package/src/rust/custom_elements/column_dropdown.rs +0 -123
  193. package/src/rust/custom_elements/filter_dropdown.rs +0 -179
  194. package/src/rust/custom_elements/function_dropdown.rs +0 -115
  195. package/src/rust/model/column_locator.rs +0 -82
  196. package/src/rust/model/is_invalid_drop.rs +0 -36
  197. package/src/rust/model/mod.rs +0 -100
  198. package/src/rust/model/reset_all.rs +0 -38
  199. package/src/rust/model/structural.rs +0 -244
  200. package/src/themes/botanical.less +0 -142
  201. package/src/themes/dracula.less +0 -101
  202. package/src/themes/gruvbox-dark.less +0 -116
  203. package/src/themes/gruvbox.less +0 -152
  204. package/src/themes/icons.less +0 -130
  205. package/src/themes/intl/de.less +0 -102
  206. package/src/themes/intl/es.less +0 -102
  207. package/src/themes/intl/fr.less +0 -102
  208. package/src/themes/intl/ja.less +0 -102
  209. package/src/themes/intl/pt.less +0 -102
  210. package/src/themes/intl/zh.less +0 -102
  211. package/src/themes/intl.less +0 -102
  212. package/src/themes/monokai.less +0 -107
  213. package/src/themes/pro-dark.less +0 -147
  214. package/src/themes/pro.less +0 -186
  215. package/src/themes/solarized-dark.less +0 -78
  216. package/src/themes/solarized.less +0 -102
  217. package/src/themes/vaporwave.less +0 -145
  218. /package/dist/wasm/snippets/{perspective-viewer-d729f682ba5c19df → perspective-viewer-68fef752754ffbc6}/inline0.js +0 -0
  219. /package/dist/wasm/snippets/{perspective-viewer-d729f682ba5c19df → perspective-viewer-68fef752754ffbc6}/inline1.js +0 -0
  220. /package/dist/wasm/snippets/{perspective-viewer-d729f682ba5c19df → perspective-viewer-68fef752754ffbc6}/inline2.js +0 -0
  221. /package/dist/wasm/snippets/{perspective-viewer-d729f682ba5c19df → perspective-viewer-68fef752754ffbc6}/inline3.js +0 -0
  222. /package/dist/wasm/snippets/{perspective-viewer-d729f682ba5c19df → perspective-viewer-68fef752754ffbc6}/inline4.js +0 -0
  223. /package/src/rust/{model → tasks}/copy_export.rs +0 -0
  224. /package/src/rust/{model → tasks}/export_app.rs +0 -0
  225. /package/src/rust/{model → tasks}/export_method.rs +0 -0
  226. /package/src/rust/{model → tasks}/restore_and_render.rs +0 -0
  227. /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
  }