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