@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
@@ -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
+ }