@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
@@ -47,35 +47,18 @@ impl PartialEq for FontLoaderProps {
47
47
  }
48
48
  }
49
49
 
50
- /// The `FontLoader` component ensures that fonts are loaded before they are
51
- /// visible.
52
- pub struct FontLoader {}
53
-
54
- impl Component for FontLoader {
55
- type Message = ();
56
- type Properties = FontLoaderProps;
57
-
58
- fn create(_ctx: &Context<Self>) -> Self {
59
- Self {}
60
- }
61
-
62
- fn update(&mut self, _ctx: &Context<Self>, _msg: ()) -> bool {
63
- false
64
- }
50
+ #[function_component(FontLoader)]
51
+ pub fn font_loader(props: &FontLoaderProps) -> Html {
52
+ if matches!(props.get_status(), FontLoaderStatus::Finished) {
53
+ html! {}
54
+ } else {
55
+ let inner = props
56
+ .get_fonts()
57
+ .iter()
58
+ .map(font_test_html)
59
+ .collect::<Html>();
65
60
 
66
- fn view(&self, ctx: &Context<Self>) -> yew::virtual_dom::VNode {
67
- if matches!(ctx.props().get_status(), FontLoaderStatus::Finished) {
68
- html! {}
69
- } else {
70
- let inner = ctx
71
- .props()
72
- .get_fonts()
73
- .iter()
74
- .map(font_test_html)
75
- .collect::<Html>();
76
-
77
- html! { <><style>{ ":host{opacity:0!important;}" }</style>{ inner }</> }
78
- }
61
+ html! { <><style>{ ":host{opacity:0!important;}" }</style>{ inner }</> }
79
62
  }
80
63
  }
81
64
 
@@ -18,9 +18,9 @@ use web_sys::*;
18
18
  use yew::prelude::*;
19
19
 
20
20
  use crate::components::form::highlight::highlight;
21
+ use crate::components::function_dropdown::{FunctionDropDownElement, FunctionDropDownPortal};
21
22
  use crate::components::style::LocalStyle;
22
23
  use crate::css;
23
- use crate::custom_elements::FunctionDropDownElement;
24
24
  use crate::exprtk::{Cursor, tokenize};
25
25
  use crate::utils::*;
26
26
 
@@ -45,6 +45,10 @@ pub struct CodeEditorProps {
45
45
 
46
46
  #[prop_or_default]
47
47
  pub error: Option<ExprValidationError>,
48
+
49
+ /// Selected theme name, threaded for PortalModal consumers.
50
+ #[prop_or_default]
51
+ pub theme: String,
48
52
  }
49
53
 
50
54
  /// A syntax-highlighted text editor component.
@@ -128,6 +132,8 @@ pub fn code_editor(props: &CodeEditorProps) -> Html {
128
132
  |deps| scroll_sync(&deps.0, &deps.1, &deps.2),
129
133
  );
130
134
 
135
+ let portal_dropdown = filter_dropdown.clone();
136
+
131
137
  // Blur if this element is not in the tree
132
138
  use_effect_with(filter_dropdown.clone(), |filter_dropdown| {
133
139
  clone!(filter_dropdown);
@@ -160,7 +166,11 @@ pub fn code_editor(props: &CodeEditorProps) -> Html {
160
166
  ))
161
167
  .collect::<Html>();
162
168
 
163
- let class = if props.wordwrap { "wordwrap" } else { "" };
169
+ let class = if props.wordwrap {
170
+ "wordwrap scrollable"
171
+ } else {
172
+ "scrollable"
173
+ };
164
174
  clone!(props.disabled);
165
175
  html! {
166
176
  <>
@@ -171,6 +181,7 @@ pub fn code_editor(props: &CodeEditorProps) -> Html {
171
181
  <textarea
172
182
  {disabled}
173
183
  id="textarea_editable"
184
+ class="scrollable"
174
185
  ref={textarea_ref}
175
186
  spellcheck="false"
176
187
  {oninput}
@@ -191,6 +202,10 @@ pub fn code_editor(props: &CodeEditorProps) -> Html {
191
202
  </pre>
192
203
  </div>
193
204
  </div>
205
+ <FunctionDropDownPortal
206
+ element={(*portal_dropdown).clone()}
207
+ theme={props.theme.clone()}
208
+ />
194
209
  </>
195
210
  }
196
211
  }
@@ -106,12 +106,25 @@ pub fn color_chooser_component(props: &ColorRangeProps) -> Html {
106
106
  }
107
107
 
108
108
  fn infer_fg(color: &str) -> &'static str {
109
- let r = i32::from_str_radix(&color[1..3], 16).unwrap_or(255) as f64;
110
- let g = i32::from_str_radix(&color[3..5], 16).unwrap_or(0) as f64;
111
- let b = i32::from_str_radix(&color[5..7], 16).unwrap_or(0) as f64;
112
- if (r * r * 0.299 + g * g * 0.587 + b * b * 0.114).sqrt() > 130.0 {
113
- "--sign--color:#161616"
109
+ if color.len() >= 7 {
110
+ let r = i32::from_str_radix(&color[1..3], 16).unwrap_or(255) as f64;
111
+ let g = i32::from_str_radix(&color[3..5], 16).unwrap_or(0) as f64;
112
+ let b = i32::from_str_radix(&color[5..7], 16).unwrap_or(0) as f64;
113
+ if (r * r * 0.299 + g * g * 0.587 + b * b * 0.114).sqrt() > 130.0 {
114
+ "--sign--color:var(--psp--color)"
115
+ } else {
116
+ "--sign--color:var(--psp--background-color)"
117
+ }
118
+ } else if color.len() == 4 {
119
+ let r = i32::from_str_radix(&color[1..2], 16).unwrap_or(15) as f64;
120
+ let g = i32::from_str_radix(&color[2..3], 16).unwrap_or(0) as f64;
121
+ let b = i32::from_str_radix(&color[3..4], 16).unwrap_or(0) as f64;
122
+ if (r * r * 0.299 + g * g * 0.587 + b * b * 0.114).sqrt() > 8.0 {
123
+ "--sign--color:var(--psp--color)"
124
+ } else {
125
+ "--sign--color:var(--psp--background-color)"
126
+ }
114
127
  } else {
115
- "--sign--color:#FFFFFF"
128
+ "--sign--color:var(--psp--color)"
116
129
  }
117
130
  }
@@ -20,21 +20,39 @@ use yew::prelude::*;
20
20
  use crate::components::containers::trap_door_panel::TrapDoorPanel;
21
21
  use crate::components::form::code_editor::CodeEditor;
22
22
  use crate::components::style::LocalStyle;
23
+ use crate::css;
23
24
  use crate::js::{MimeType, copy_to_clipboard, paste_from_clipboard};
24
- use crate::model::*;
25
25
  use crate::presentation::*;
26
26
  use crate::renderer::*;
27
27
  use crate::session::*;
28
+ use crate::tasks::*;
28
29
  use crate::utils::*;
29
- use crate::{PerspectiveProperties, css};
30
30
 
31
- #[derive(PartialEq, Properties, PerspectiveProperties!)]
31
+ #[derive(Clone, PartialEq, Properties)]
32
32
  pub struct DebugPanelProps {
33
33
  pub presentation: Presentation,
34
34
  pub renderer: Renderer,
35
35
  pub session: Session,
36
36
  }
37
37
 
38
+ impl HasPresentation for DebugPanelProps {
39
+ fn presentation(&self) -> &Presentation {
40
+ &self.presentation
41
+ }
42
+ }
43
+
44
+ impl HasRenderer for DebugPanelProps {
45
+ fn renderer(&self) -> &Renderer {
46
+ &self.renderer
47
+ }
48
+ }
49
+
50
+ impl HasSession for DebugPanelProps {
51
+ fn session(&self) -> &Session {
52
+ &self.session
53
+ }
54
+ }
55
+
38
56
  #[function_component(DebugPanel)]
39
57
  pub fn debug_panel(props: &DebugPanelProps) -> Html {
40
58
  let expr = use_state_eq(|| Rc::new("".to_string()));
@@ -42,7 +60,7 @@ pub fn debug_panel(props: &DebugPanelProps) -> Html {
42
60
  let select_all = use_memo((), |()| PubSub::default());
43
61
  let modified = use_state_eq(|| false);
44
62
 
45
- use_effect_with((expr.setter(), props.clone_state()), {
63
+ use_effect_with((expr.setter(), props.clone()), {
46
64
  clone!(error, modified);
47
65
  move |(text, state)| {
48
66
  state.set_text(text.clone());
@@ -57,7 +75,7 @@ pub fn debug_panel(props: &DebugPanelProps) -> Html {
57
75
  ));
58
76
 
59
77
  let sub2 = state
60
- .renderer()
78
+ .renderer
61
79
  .reset_changed
62
80
  .add_listener(state.reset_callback(
63
81
  text.clone(),
@@ -66,7 +84,7 @@ pub fn debug_panel(props: &DebugPanelProps) -> Html {
66
84
  ));
67
85
 
68
86
  let sub3 = state
69
- .session()
87
+ .session
70
88
  .view_config_changed
71
89
  .add_listener(state.reset_callback(
72
90
  text.clone(),
@@ -90,7 +108,7 @@ pub fn debug_panel(props: &DebugPanelProps) -> Html {
90
108
  }
91
109
  });
92
110
 
93
- let onsave = use_callback((expr.clone(), error.clone(), props.clone_state()), {
111
+ let onsave = use_callback((expr.clone(), error.clone(), props.clone()), {
94
112
  clone!(modified);
95
113
  move |_, (text, error, props)| props.on_save(text, error, &modified)
96
114
  });
@@ -111,12 +129,12 @@ pub fn debug_panel(props: &DebugPanelProps) -> Html {
111
129
  },
112
130
  );
113
131
 
114
- let onapply = use_callback((expr.clone(), error.clone(), props.clone_state()), {
132
+ let onapply = use_callback((expr.clone(), error.clone(), props.clone()), {
115
133
  clone!(modified);
116
134
  move |_, (text, error, props)| props.on_save(text, error, &modified)
117
135
  });
118
136
 
119
- let onreset = use_callback((expr.setter(), error.clone(), props.clone_state()), {
137
+ let onreset = use_callback((expr.setter(), error.clone(), props.clone()), {
120
138
  clone!(modified);
121
139
  move |_, (text, error, props)| {
122
140
  props.set_text(text.clone());
@@ -125,7 +143,7 @@ pub fn debug_panel(props: &DebugPanelProps) -> Html {
125
143
  }
126
144
  });
127
145
 
128
- let onpaste = use_callback((expr.clone(), error.clone(), props.clone_state()), {
146
+ let onpaste = use_callback((expr.clone(), error.clone(), props.clone()), {
129
147
  clone!(modified);
130
148
  move |_, (text, error, props)| {
131
149
  clone!(text, error, props, modified);
@@ -181,12 +199,11 @@ pub fn debug_panel(props: &DebugPanelProps) -> Html {
181
199
  }
182
200
  }
183
201
 
184
- impl DebugPanelPropsState {
202
+ impl DebugPanelProps {
185
203
  fn set_text(&self, setter: UseStateSetter<Rc<String>>) {
186
204
  let props = self.clone();
187
205
  ApiFuture::spawn(async move {
188
- let task = props.get_viewer_config();
189
- let config = task.await?;
206
+ let config = props.get_viewer_config().await?;
190
207
  let json = JsValue::from_serde_ext(&config)?;
191
208
  let js_string =
192
209
  js_sys::JSON::stringify_with_replacer_and_space(&json, &JsValue::NULL, &2.into())?;
@@ -10,142 +10,215 @@
10
10
  // ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11
11
  // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
12
 
13
- use perspective_client::config::CompletionItemSuggestion;
13
+ use std::cell::RefCell;
14
+ use std::rc::Rc;
15
+
16
+ use perspective_client::config::{COMPLETIONS, CompletionItemSuggestion};
17
+ use perspective_js::utils::ApiResult;
14
18
  use web_sys::*;
19
+ use yew::html::ImplicitClone;
15
20
  use yew::prelude::*;
16
21
 
17
- use super::modal::*;
18
- use crate::utils::WeakScope;
22
+ use super::portal::PortalModal;
23
+ use crate::utils::*;
19
24
 
20
25
  static CSS: &str = include_str!(concat!(env!("OUT_DIR"), "/css/function-dropdown.css"));
21
26
 
22
- #[derive(Properties, PartialEq)]
23
- pub struct FunctionDropDownProps {
24
- #[prop_or_default]
25
- pub weak_link: WeakScope<FunctionDropDown>,
27
+ #[derive(Default)]
28
+ struct FunctionDropDownState {
29
+ values: Vec<CompletionItemSuggestion>,
30
+ selected: usize,
31
+ on_select: Option<Callback<CompletionItemSuggestion>>,
32
+ target: Option<HtmlElement>,
26
33
  }
27
34
 
28
- impl ModalLink<FunctionDropDown> for FunctionDropDownProps {
29
- fn weak_link(&self) -> &'_ WeakScope<FunctionDropDown> {
30
- &self.weak_link
31
- }
35
+ #[derive(Clone, Default)]
36
+ pub struct FunctionDropDownElement {
37
+ state: Rc<RefCell<FunctionDropDownState>>,
38
+ notify: Rc<PubSub<()>>,
32
39
  }
33
40
 
34
- pub enum FunctionDropDownMsg {
35
- SetValues(Vec<CompletionItemSuggestion>),
36
- SetCallback(Callback<CompletionItemSuggestion>),
37
- ItemDown,
38
- ItemUp,
39
- ItemSelect,
41
+ impl PartialEq for FunctionDropDownElement {
42
+ fn eq(&self, other: &Self) -> bool {
43
+ Rc::ptr_eq(&self.state, &other.state)
44
+ }
40
45
  }
41
46
 
42
- pub struct FunctionDropDown {
43
- values: Option<Vec<CompletionItemSuggestion>>,
44
- selected: usize,
45
- on_select: Option<Callback<CompletionItemSuggestion>>,
46
- }
47
+ impl ImplicitClone for FunctionDropDownElement {}
47
48
 
48
- impl Component for FunctionDropDown {
49
- type Message = FunctionDropDownMsg;
50
- type Properties = FunctionDropDownProps;
49
+ impl FunctionDropDownElement {
50
+ pub fn reautocomplete(&self) {
51
+ self.notify.emit(());
52
+ }
51
53
 
52
- fn create(ctx: &Context<Self>) -> Self {
53
- ctx.set_modal_link();
54
- Self {
55
- values: Some(vec![]),
56
- selected: 0,
57
- on_select: None,
54
+ pub fn autocomplete(
55
+ &self,
56
+ input: String,
57
+ target: HtmlElement,
58
+ callback: Callback<CompletionItemSuggestion>,
59
+ ) -> ApiResult<()> {
60
+ let values = filter_values(&input);
61
+ if values.is_empty() {
62
+ self.hide()?;
63
+ } else {
64
+ let mut s = self.state.borrow_mut();
65
+ s.values = values;
66
+ s.selected = 0;
67
+ s.on_select = Some(callback);
68
+ s.target = Some(target);
69
+ drop(s);
70
+ self.notify.emit(());
58
71
  }
72
+
73
+ Ok(())
59
74
  }
60
75
 
61
- fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
62
- match msg {
63
- FunctionDropDownMsg::SetCallback(callback) => {
64
- self.on_select = Some(callback);
65
- false
66
- },
67
- FunctionDropDownMsg::SetValues(values) => {
68
- self.values = Some(values);
69
- self.selected = 0;
70
- true
71
- },
72
- FunctionDropDownMsg::ItemSelect => {
73
- if let Some(ref values) = self.values {
74
- match values.get(self.selected) {
75
- None => {
76
- console::error_1(&"Selected out-of-bounds".into());
77
- false
78
- },
79
- Some(x) => {
80
- self.on_select.as_ref().unwrap().emit(*x);
81
- false
82
- },
83
- }
84
- } else {
85
- console::error_1(&"No Values".into());
86
- false
87
- }
88
- },
89
- FunctionDropDownMsg::ItemDown => {
90
- self.selected += 1;
91
- if let Some(ref values) = self.values
92
- && self.selected >= values.len()
93
- {
94
- self.selected = 0;
95
- };
96
-
97
- true
98
- },
99
- FunctionDropDownMsg::ItemUp => {
100
- if let Some(ref values) = self.values
101
- && self.selected < 1
102
- {
103
- self.selected = values.len();
104
- }
105
-
106
- self.selected -= 1;
107
- true
108
- },
76
+ pub fn item_select(&self) {
77
+ let state = self.state.borrow();
78
+ if let Some(value) = state.values.get(state.selected)
79
+ && let Some(ref cb) = state.on_select
80
+ {
81
+ cb.emit(*value);
109
82
  }
110
83
  }
111
84
 
112
- fn changed(&mut self, _ctx: &Context<Self>, _old: &Self::Properties) -> bool {
113
- false
85
+ pub fn item_down(&self) {
86
+ let mut state = self.state.borrow_mut();
87
+ state.selected += 1;
88
+ if state.selected >= state.values.len() {
89
+ state.selected = 0;
90
+ }
91
+
92
+ drop(state);
93
+ self.notify.emit(());
114
94
  }
115
95
 
116
- fn view(&self, _ctx: &Context<Self>) -> Html {
117
- let body = html! {
118
- if let Some(ref values) = self.values {
119
- if !values.is_empty() {
120
- { for values
121
- .iter()
122
- .enumerate()
123
- .map(|(idx, value)| {
124
- let click = self.on_select.as_ref().unwrap().reform({
125
- let value = *value;
126
- move |_: MouseEvent| value
127
- });
128
-
129
- html! {
130
- if idx == self.selected {
131
- <div onmousedown={ click } class="selected">
132
- <span style="font-weight:500">{ value.label }</span>
133
- <br/>
134
- <span style="padding-left:12px">{ value.documentation }</span>
135
- </div>
136
- } else {
137
- <div onmousedown={ click }>
138
- <span style="font-weight:500">{ value.label }</span>
139
- <br/>
140
- <span style="padding-left:12px">{ value.documentation }</span>
141
- </div>
142
- }
143
- }
144
- }) }
145
- }
146
- }
96
+ pub fn item_up(&self) {
97
+ let mut state = self.state.borrow_mut();
98
+ if state.selected < 1 {
99
+ state.selected = state.values.len();
100
+ }
101
+
102
+ state.selected -= 1;
103
+ drop(state);
104
+ self.notify.emit(());
105
+ }
106
+
107
+ pub fn hide(&self) -> ApiResult<()> {
108
+ self.state.borrow_mut().target = None;
109
+ self.notify.emit(());
110
+ Ok(())
111
+ }
112
+ }
113
+
114
+ #[derive(Properties, PartialEq)]
115
+ pub struct FunctionDropDownPortalProps {
116
+ pub element: FunctionDropDownElement,
117
+ pub theme: String,
118
+ }
119
+
120
+ pub struct FunctionDropDownPortal {
121
+ _sub: Subscription,
122
+ }
123
+
124
+ impl Component for FunctionDropDownPortal {
125
+ type Message = ();
126
+ type Properties = FunctionDropDownPortalProps;
127
+
128
+ fn create(ctx: &Context<Self>) -> Self {
129
+ let link = ctx.link().clone();
130
+ let sub = ctx
131
+ .props()
132
+ .element
133
+ .notify
134
+ .add_listener(move |()| link.send_message(()));
135
+ Self { _sub: sub }
136
+ }
137
+
138
+ fn update(&mut self, _ctx: &Context<Self>, _msg: ()) -> bool {
139
+ true
140
+ }
141
+
142
+ fn view(&self, ctx: &Context<Self>) -> Html {
143
+ let state = ctx.props().element.state.borrow();
144
+ let target = state.target.clone();
145
+ let on_close = {
146
+ let element = ctx.props().element.clone();
147
+ Callback::from(move |()| {
148
+ let _ = element.hide();
149
+ })
147
150
  };
148
151
 
149
- html! { <><style>{ CSS }</style>{ body }</> }
152
+ if target.is_some() {
153
+ let values = state.values.clone();
154
+ let selected = state.selected;
155
+ let on_select = state.on_select.clone();
156
+ drop(state);
157
+
158
+ html! {
159
+ <PortalModal
160
+ tag_name="perspective-dropdown"
161
+ {target}
162
+ own_focus=false
163
+ {on_close}
164
+ theme={ctx.props().theme.clone()}
165
+ >
166
+ <FunctionDropDownView {values} {selected} {on_select} />
167
+ </PortalModal>
168
+ }
169
+ } else {
170
+ html! {}
171
+ }
150
172
  }
151
173
  }
174
+
175
+ #[derive(Properties, PartialEq)]
176
+ struct FunctionDropDownViewProps {
177
+ values: Vec<CompletionItemSuggestion>,
178
+ selected: usize,
179
+ on_select: Option<Callback<CompletionItemSuggestion>>,
180
+ }
181
+
182
+ #[function_component]
183
+ fn FunctionDropDownView(props: &FunctionDropDownViewProps) -> Html {
184
+ let body = html! {
185
+ if !props.values.is_empty() {
186
+ { for props.values
187
+ .iter()
188
+ .enumerate()
189
+ .map(|(idx, value)| {
190
+ let click = props.on_select.as_ref().unwrap().reform({
191
+ let value = *value;
192
+ move |_: MouseEvent| value
193
+ });
194
+
195
+ html! {
196
+ if idx == props.selected {
197
+ <div onmousedown={click} class="selected">
198
+ <span style="font-weight:500">{ value.label }</span>
199
+ <br/>
200
+ <span style="padding-left:12px">{ value.documentation }</span>
201
+ </div>
202
+ } else {
203
+ <div onmousedown={click}>
204
+ <span style="font-weight:500">{ value.label }</span>
205
+ <br/>
206
+ <span style="padding-left:12px">{ value.documentation }</span>
207
+ </div>
208
+ }
209
+ }
210
+ }) }
211
+ }
212
+ };
213
+
214
+ html! { <><style>{ CSS }</style>{ body }</> }
215
+ }
216
+
217
+ fn filter_values(input: &str) -> Vec<CompletionItemSuggestion> {
218
+ let input = input.to_lowercase();
219
+ COMPLETIONS
220
+ .iter()
221
+ .filter(|x| x.label.to_lowercase().starts_with(&input))
222
+ .cloned()
223
+ .collect::<Vec<_>>()
224
+ }