@perspective-dev/viewer 4.0.0 → 4.1.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 (184) 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/dracula.css +1 -1
  4. package/dist/css/gruvbox-dark.css +1 -1
  5. package/dist/css/gruvbox.css +1 -1
  6. package/dist/css/icons.css +1 -1
  7. package/dist/css/monokai.css +1 -1
  8. package/dist/css/pro-dark.css +1 -1
  9. package/dist/css/pro.css +1 -1
  10. package/dist/css/solarized-dark.css +1 -1
  11. package/dist/css/solarized.css +1 -1
  12. package/dist/css/themes.css +1 -1
  13. package/dist/css/vaporwave.css +1 -1
  14. package/dist/esm/extensions.d.ts +23 -2
  15. package/dist/esm/perspective-viewer.d.ts +2 -7
  16. package/dist/esm/perspective-viewer.inline.js +2 -2
  17. package/dist/esm/perspective-viewer.inline.js.map +4 -4
  18. package/dist/esm/perspective-viewer.js +2 -2
  19. package/dist/esm/perspective-viewer.js.map +4 -4
  20. package/dist/esm/plugin.d.ts +1 -1
  21. package/dist/esm/ts-rs/ViewerConfigUpdate.d.ts +1 -0
  22. package/dist/wasm/perspective-viewer.d.ts +218 -46
  23. package/dist/wasm/perspective-viewer.js +1251 -762
  24. package/dist/wasm/perspective-viewer.wasm +0 -0
  25. package/dist/wasm/perspective-viewer.wasm.d.ts +38 -19
  26. package/package.json +1 -1
  27. package/src/less/containers/scroll-panel.less +0 -1
  28. package/src/less/plugin-selector.less +15 -5
  29. package/src/less/status-bar.less +75 -27
  30. package/src/less/viewer.less +140 -58
  31. package/src/rust/components/column_dropdown.rs +21 -21
  32. package/src/rust/components/column_selector/active_column.rs +131 -120
  33. package/src/rust/components/column_selector/add_expression_button.rs +5 -0
  34. package/src/rust/components/column_selector/aggregate_selector.rs +8 -4
  35. package/src/rust/components/column_selector/config_selector.rs +170 -161
  36. package/src/rust/components/column_selector/empty_column.rs +16 -11
  37. package/src/rust/components/column_selector/{expression_toolbar.rs → expr_edit_button.rs} +7 -0
  38. package/src/rust/components/column_selector/filter_column.rs +195 -194
  39. package/src/rust/components/column_selector/inactive_column.rs +82 -67
  40. package/src/rust/components/column_selector/pivot_column.rs +16 -11
  41. package/src/rust/components/column_selector/sort_column.rs +9 -7
  42. package/src/rust/components/column_selector.rs +42 -37
  43. package/src/rust/components/column_settings_sidebar/save_settings.rs +3 -1
  44. package/src/rust/components/column_settings_sidebar/style_tab/agg_depth_selector.rs +58 -0
  45. package/src/rust/components/column_settings_sidebar/style_tab/symbol/row_selector.rs +6 -6
  46. package/src/rust/components/column_settings_sidebar/style_tab/symbol/symbol_pairs.rs +2 -94
  47. package/src/rust/components/column_settings_sidebar/style_tab/symbol/symbol_pairs_item.rs +111 -0
  48. package/src/rust/components/column_settings_sidebar/style_tab/symbol.rs +3 -3
  49. package/src/rust/components/column_settings_sidebar/style_tab.rs +23 -83
  50. package/src/rust/components/{column_settings_sidebar/sidebar.rs → column_settings_sidebar.rs} +198 -171
  51. package/src/rust/components/containers/dragdrop_list.rs +20 -20
  52. package/src/rust/components/containers/dropdown_menu.rs +4 -6
  53. package/src/rust/components/containers/mod.rs +1 -4
  54. package/src/rust/components/containers/scroll_panel.rs +80 -80
  55. package/src/rust/components/containers/scroll_panel_item.rs +36 -36
  56. package/src/rust/components/containers/select.rs +46 -44
  57. package/src/rust/components/containers/sidebar.rs +3 -19
  58. package/src/rust/components/{column_settings_sidebar/style_tab/symbol/symbol_config.rs → containers/sidebar_close_button.rs} +15 -9
  59. package/src/rust/components/containers/split_panel.rs +212 -200
  60. package/src/rust/components/containers/tab_list.rs +11 -11
  61. package/src/rust/components/copy_dropdown.rs +22 -25
  62. package/src/rust/components/datetime_column_style/custom.rs +19 -19
  63. package/src/rust/components/datetime_column_style/simple.rs +13 -14
  64. package/src/rust/components/datetime_column_style.rs +75 -76
  65. package/src/rust/components/editable_header.rs +18 -14
  66. package/src/rust/components/empty_row.rs +5 -5
  67. package/src/rust/components/export_dropdown.rs +42 -42
  68. package/src/rust/components/expression_editor.rs +25 -19
  69. package/src/rust/components/filter_dropdown.rs +22 -22
  70. package/src/rust/components/font_loader.rs +11 -9
  71. package/src/rust/components/form/code_editor.rs +106 -105
  72. package/src/rust/components/form/color_range_selector.rs +14 -12
  73. package/src/rust/components/form/color_selector.rs +3 -1
  74. package/src/rust/components/form/debug.rs +95 -94
  75. package/src/rust/components/form/highlight.rs +5 -3
  76. package/src/rust/components/form/mod.rs +3 -2
  77. package/src/rust/components/form/optional_field.rs +2 -2
  78. package/src/rust/components/form/{select_field.rs → select_enum_field.rs} +1 -46
  79. package/src/rust/components/form/select_value_field.rs +64 -0
  80. package/src/rust/components/function_dropdown.rs +21 -21
  81. package/src/rust/components/main_panel.rs +219 -0
  82. package/src/rust/components/mod.rs +6 -6
  83. package/src/rust/components/modal.rs +42 -42
  84. package/src/rust/components/number_column_style.rs +34 -88
  85. package/src/rust/components/plugin_selector.rs +22 -25
  86. package/src/rust/components/render_warning.rs +9 -6
  87. package/src/rust/components/settings_panel.rs +82 -0
  88. package/src/rust/components/status_bar.rs +250 -146
  89. package/src/rust/components/status_bar_counter.rs +26 -119
  90. package/src/rust/components/status_indicator.rs +95 -79
  91. package/src/rust/components/string_column_style.rs +45 -45
  92. package/src/rust/components/style/style_provider.rs +1 -15
  93. package/src/rust/components/style_controls/number_string_format/digits_section.rs +1 -1
  94. package/src/rust/components/style_controls/number_string_format/misc_section.rs +1 -1
  95. package/src/rust/components/style_controls/number_string_format/style_section.rs +1 -1
  96. package/src/rust/components/style_controls/number_string_format.rs +45 -46
  97. package/src/rust/components/type_icon.rs +14 -11
  98. package/src/rust/components/viewer.rs +241 -384
  99. package/src/rust/config/columns_config.rs +2 -2
  100. package/src/rust/config/datetime_column_style.rs +1 -6
  101. package/src/rust/config/mod.rs +1 -0
  102. package/src/rust/config/number_column_style.rs +0 -6
  103. package/src/rust/config/number_string_format.rs +27 -4
  104. package/src/rust/config/viewer_config.rs +27 -167
  105. package/src/rust/custom_elements/copy_dropdown.rs +14 -6
  106. package/src/rust/custom_elements/export_dropdown.rs +15 -7
  107. package/src/rust/custom_elements/filter_dropdown.rs +4 -4
  108. package/src/rust/custom_elements/mod.rs +3 -0
  109. package/src/rust/custom_elements/viewer.rs +353 -161
  110. package/src/rust/custom_events.rs +55 -32
  111. package/src/rust/dragdrop.rs +4 -24
  112. package/src/rust/exprtk/cursor.rs +10 -1
  113. package/src/rust/exprtk/mod.rs +2 -0
  114. package/src/rust/exprtk/tokenize.rs +20 -3
  115. package/src/rust/js/clipboard.rs +2 -2
  116. package/src/rust/js/mimetype.rs +2 -7
  117. package/src/rust/js/mod.rs +0 -1
  118. package/src/rust/js/plugin.rs +7 -0
  119. package/src/rust/lib.rs +18 -5
  120. package/src/rust/model/column_locator.rs +82 -0
  121. package/src/rust/model/columns_iter_set.rs +1 -0
  122. package/src/rust/model/copy_export.rs +50 -14
  123. package/src/rust/model/edit_expression.rs +2 -5
  124. package/src/rust/model/eject.rs +41 -0
  125. package/src/rust/model/export_app.rs +3 -2
  126. package/src/rust/model/get_viewer_config.rs +4 -28
  127. package/src/rust/model/intersection_observer.rs +20 -8
  128. package/src/rust/model/mod.rs +11 -4
  129. package/src/rust/model/plugin_column_styles.rs +0 -31
  130. package/src/rust/model/reset_all.rs +38 -0
  131. package/src/rust/model/resize_observer.rs +34 -7
  132. package/src/rust/model/restore_and_render.rs +12 -7
  133. package/src/rust/{utils/scope.rs → model/send_plugin_config.rs} +32 -35
  134. package/src/rust/model/structural.rs +194 -23
  135. package/src/rust/model/update_and_render.rs +14 -4
  136. package/src/rust/{model/create_col.rs → presentation/column_locator.rs} +73 -42
  137. package/src/rust/{utils/wasm_abi.rs → presentation/sheets.rs} +54 -40
  138. package/src/rust/presentation.rs +60 -119
  139. package/src/rust/renderer/activate.rs +20 -5
  140. package/src/rust/renderer/limits.rs +0 -149
  141. package/src/rust/renderer/render_timer.rs +1 -1
  142. package/src/rust/renderer.rs +34 -18
  143. package/src/rust/root.rs +50 -0
  144. package/src/rust/session/column_defaults_update.rs +4 -4
  145. package/src/rust/session/drag_drop_update.rs +1 -1
  146. package/src/rust/session/metadata.rs +3 -17
  147. package/src/rust/session/replace_expression_update.rs +1 -2
  148. package/src/rust/session.rs +162 -82
  149. package/src/rust/utils/browser/blob.rs +16 -2
  150. package/src/rust/utils/browser/download.rs +1 -0
  151. package/src/rust/{components/column_settings_sidebar/mod.rs → utils/browser/dragdrop.rs} +14 -5
  152. package/src/rust/utils/browser/mod.rs +8 -4
  153. package/src/rust/utils/browser/selection.rs +5 -0
  154. package/src/rust/utils/custom_element.rs +28 -13
  155. package/src/rust/utils/datetime.rs +5 -0
  156. package/src/rust/utils/debounce.rs +7 -1
  157. package/src/rust/utils/hooks/use_async_callback.rs +7 -17
  158. package/src/rust/utils/mod.rs +28 -40
  159. package/src/rust/utils/number_format.rs +6 -5
  160. package/src/rust/utils/pubsub.rs +15 -10
  161. package/src/rust/utils/weak_scope.rs +11 -1
  162. package/src/svg/bookmark-icon.svg +4 -0
  163. package/src/svg/drag-handle copy.svg +10 -0
  164. package/src/svg/drawer-tab-hover.svg +5 -7
  165. package/src/svg/drawer-tab-invert-hover.svg +4 -8
  166. package/src/svg/drawer-tab-invert.svg +4 -7
  167. package/src/svg/drawer-tab.svg +4 -6
  168. package/src/svg/status_ok.svg +24 -24
  169. package/src/ts/extensions.ts +51 -3
  170. package/src/ts/perspective-viewer.ts +2 -14
  171. package/src/ts/plugin.ts +1 -1
  172. package/src/ts/ts-rs/ViewerConfigUpdate.ts +1 -1
  173. package/src/rust/components/column_settings_sidebar/style_tab/column_style.rs +0 -177
  174. package/src/rust/components/containers/tests/mod.rs +0 -11
  175. package/src/rust/components/containers/tests/split_panel.rs +0 -91
  176. package/src/rust/js/testing.rs +0 -149
  177. package/src/rust/utils/tee.rs +0 -88
  178. /package/dist/wasm/snippets/{perspective-viewer-c69283f6f62a5f14 → perspective-viewer-0d326a25c1022412}/inline0.js +0 -0
  179. /package/dist/wasm/snippets/{perspective-viewer-c69283f6f62a5f14 → perspective-viewer-0d326a25c1022412}/inline1.js +0 -0
  180. /package/dist/wasm/snippets/{perspective-viewer-c69283f6f62a5f14 → perspective-viewer-0d326a25c1022412}/inline2.js +0 -0
  181. /package/dist/wasm/snippets/{perspective-viewer-c69283f6f62a5f14 → perspective-viewer-0d326a25c1022412}/inline3.js +0 -0
  182. /package/dist/wasm/snippets/{perspective-viewer-c69283f6f62a5f14 → perspective-viewer-0d326a25c1022412}/inline4.js +0 -0
  183. /package/src/rust/components/{style_controls.rs → style_controls/mod.rs} +0 -0
  184. /package/src/rust/{components/containers → config}/kvpair.rs +0 -0
@@ -17,21 +17,23 @@ use std::rc::Rc;
17
17
 
18
18
  use futures::future::{join_all, select_all};
19
19
  use perspective_js::utils::{global, *};
20
- use wasm_bindgen::JsCast;
21
20
  use wasm_bindgen::prelude::*;
21
+ use wasm_bindgen::{JsCast, intern};
22
22
  use wasm_bindgen_futures::JsFuture;
23
23
  use yew::prelude::*;
24
24
 
25
25
  use crate::utils::*;
26
26
 
27
- const FONT_DOWNLOAD_TIMEOUT_MS: i32 = 1000;
28
-
29
27
  /// This test string is injected into the DOM with the target `font-family`
30
28
  /// applied. It is important for this string to contain the correct unicode
31
29
  /// range, as otherwise the browser may download the latin-only variant of the
32
30
  /// font which will later be invalidated.
33
31
  const FONT_TEST_SAMPLE: &str = "ABCDΔ";
34
32
 
33
+ /// How long to wait for the test string to receive the font before proceeding
34
+ /// anyway (with a consoel warning).
35
+ const FONT_DOWNLOAD_TIMEOUT_MS: i32 = 1000;
36
+
35
37
  /// `state` is private to force construction of props with the `::new()` static
36
38
  /// method, which initializes the async `load_fonts_task()` method.
37
39
  #[derive(Clone, Properties)]
@@ -86,15 +88,15 @@ pub enum FontLoaderStatus {
86
88
  Finished,
87
89
  }
88
90
 
89
- type PromiseSet = Vec<ApiFuture<JsValue>>;
90
-
91
- pub struct FontLoaderState {
91
+ struct FontLoaderState {
92
92
  status: Cell<FontLoaderStatus>,
93
93
  elem: web_sys::HtmlElement,
94
94
  on_update: Callback<()>,
95
95
  fonts: RefCell<Vec<(String, String)>>,
96
96
  }
97
97
 
98
+ type PromiseSet = Vec<ApiFuture<JsValue>>;
99
+
98
100
  impl FontLoaderProps {
99
101
  pub fn new(elem: &web_sys::HtmlElement, on_update: Callback<()>) -> Self {
100
102
  let inner = FontLoaderState {
@@ -116,7 +118,7 @@ impl FontLoaderProps {
116
118
  self.state.status.get()
117
119
  }
118
120
 
119
- fn get_fonts(&self) -> Ref<Vec<(String, String)>> {
121
+ fn get_fonts(&'_ self) -> Ref<'_, Vec<(String, String)>> {
120
122
  self.state.fonts.borrow()
121
123
  }
122
124
 
@@ -155,7 +157,7 @@ impl FontLoaderProps {
155
157
  let mut block_fonts: PromiseSet = vec![ApiFuture::new(task)];
156
158
 
157
159
  for entry in font_iter(global::document().fonts().values()) {
158
- let font_face = js_sys::Reflect::get(&entry, js_intern::js_intern!("value"))?
160
+ let font_face = js_sys::Reflect::get(&entry, &intern("value").into())?
159
161
  .dyn_into::<web_sys::FontFace>()?;
160
162
 
161
163
  // Safari always has to be "different".
@@ -246,7 +248,7 @@ fn font_iter(
246
248
  repeat_with(move || iter.next())
247
249
  .filter_map(|x| x.ok())
248
250
  .take_while(|entry| {
249
- !js_sys::Reflect::get(entry, js_intern::js_intern!("done"))
251
+ !js_sys::Reflect::get(entry, &intern("done").into())
250
252
  .unwrap()
251
253
  .as_bool()
252
254
  .unwrap()
@@ -19,10 +19,10 @@ use yew::prelude::*;
19
19
 
20
20
  use crate::components::form::highlight::highlight;
21
21
  use crate::components::style::LocalStyle;
22
+ use crate::css;
22
23
  use crate::custom_elements::FunctionDropDownElement;
23
24
  use crate::exprtk::{Cursor, tokenize};
24
25
  use crate::utils::*;
25
- use crate::*;
26
26
 
27
27
  #[derive(Properties, PartialEq)]
28
28
  pub struct CodeEditorProps {
@@ -47,106 +47,15 @@ pub struct CodeEditorProps {
47
47
  pub error: Option<ExprValidationError>,
48
48
  }
49
49
 
50
- /// Capture the input (for re-parse) and caret position whenever the input
51
- /// text changes.
52
- fn on_input_callback(
53
- event: InputEvent,
54
- // position: &UseStateSetter<u32>,
55
- oninput: &Callback<Rc<String>>,
56
- ) {
57
- let elem = event
58
- .target()
59
- .unwrap()
60
- .unchecked_into::<web_sys::HtmlTextAreaElement>();
61
-
62
- oninput.emit(elem.value().into());
63
- // position.set(elem.get_caret_position().unwrap_or_default());
64
- }
65
-
66
- /// Overide for special non-character commands e.g. shift+enter
67
- fn on_keydown(event: KeyboardEvent, deps: &(UseStateSetter<u32>, Callback<()>)) {
68
- let elem = event
69
- .target()
70
- .unwrap()
71
- .unchecked_into::<web_sys::HtmlTextAreaElement>();
72
-
73
- deps.0.set(elem.get_caret_position().unwrap_or_default());
74
- if event.shift_key() && event.key_code() == 13 {
75
- event.prevent_default();
76
- deps.1.emit(())
77
- }
78
-
79
- // handle the tab key press
80
- if event.key() == "Tab" {
81
- event.prevent_default();
82
-
83
- let caret_pos = elem.selection_start().unwrap().unwrap_or_default() as usize;
84
-
85
- let mut initial_text = elem.value();
86
-
87
- initial_text.insert(caret_pos, '\t');
88
-
89
- elem.set_value(&initial_text);
90
-
91
- let input_event = web_sys::InputEvent::new("input").unwrap();
92
- let _ = elem.dispatch_event(&input_event).unwrap();
93
-
94
- // place caret after inserted tab
95
- let new_caret_pos = (caret_pos + 1) as u32;
96
- let _ = elem.set_selection_range(new_caret_pos, new_caret_pos);
97
-
98
- elem.focus().unwrap();
99
- }
100
- }
101
-
102
- /// Scrolling callback
103
- fn on_scroll(scroll: &UseStateSetter<(i32, i32)>, editable: &NodeRef) {
104
- let div = editable.cast::<HtmlElement>().unwrap();
105
- scroll.set((div.scroll_top(), div.scroll_left()));
106
- }
107
-
108
- /// Scrolling sync
109
- fn scroll_sync(scroll: &UseStateHandle<(i32, i32)>, div: &NodeRef, lineno: &NodeRef) {
110
- if let Some(div) = div.cast::<HtmlElement>() {
111
- div.set_scroll_top(scroll.0);
112
- div.set_scroll_left(scroll.1);
113
- }
114
-
115
- if let Some(div) = lineno.cast::<HtmlElement>() {
116
- div.set_scroll_top(scroll.0);
117
- }
118
- }
119
-
120
- /// Autocomplete
121
- /// TODO this should use a portal
122
- fn autocomplete(
123
- filter_dropdown: &Rc<FunctionDropDownElement>,
124
- token: &Option<String>,
125
- target: &NodeRef,
126
- ) {
127
- if let Some(x) = token {
128
- let elem = target.cast::<HtmlElement>().unwrap();
129
- if elem.is_connected() {
130
- filter_dropdown
131
- .autocomplete(x.clone(), elem, Callback::from(|_| ()))
132
- .unwrap();
133
- } else {
134
- filter_dropdown.hide().unwrap();
135
- }
136
- } else {
137
- filter_dropdown.hide().unwrap();
138
- }
139
- }
140
-
141
50
  /// A syntax-highlighted text editor component.
142
51
  #[function_component(CodeEditor)]
143
52
  pub fn code_editor(props: &CodeEditorProps) -> Html {
144
53
  let select_all = use_state_eq(|| false);
145
54
  let caret_position = use_state_eq(|| 0_u32);
146
55
  let scroll_offset = use_state_eq(|| (0, 0));
147
- let textarea_ref = use_node_ref().tee::<5>();
148
- let content_ref = use_node_ref().tee::<3>();
149
- let lineno_ref = use_node_ref().tee::<2>();
56
+ let textarea_ref = use_node_ref();
57
+ let content_ref = use_node_ref();
58
+ let lineno_ref = use_node_ref();
150
59
  let filter_dropdown = use_memo((), |_| FunctionDropDownElement::default());
151
60
  let mut cursor = Cursor::new(&props.error);
152
61
  let terms = tokenize(&props.expr)
@@ -159,12 +68,12 @@ pub fn code_editor(props: &CodeEditorProps) -> Html {
159
68
  on_input_callback(event, deps)
160
69
  });
161
70
 
162
- let onscroll = use_callback((scroll_offset.setter(), textarea_ref.0), |_, deps| {
71
+ let onscroll = use_callback((scroll_offset.setter(), textarea_ref.clone()), |_, deps| {
163
72
  on_scroll(&deps.0, &deps.1)
164
73
  });
165
74
 
166
75
  let autofocus = props.autofocus;
167
- use_effect_with((props.expr.clone(), textarea_ref.1), {
76
+ use_effect_with((props.expr.clone(), textarea_ref.clone()), {
168
77
  move |(expr, textarea_ref)| {
169
78
  let elem = textarea_ref.cast::<web_sys::HtmlTextAreaElement>().unwrap();
170
79
  if autofocus {
@@ -190,7 +99,7 @@ pub fn code_editor(props: &CodeEditorProps) -> Html {
190
99
  });
191
100
 
192
101
  // select_all.set(props.select_all);
193
- use_effect_with((select_all, textarea_ref.2), {
102
+ use_effect_with((select_all, textarea_ref.clone()), {
194
103
  move |(select_all, textarea_ref)| {
195
104
  let elem = textarea_ref.cast::<web_sys::HtmlTextAreaElement>().unwrap();
196
105
  if **select_all {
@@ -204,7 +113,7 @@ pub fn code_editor(props: &CodeEditorProps) -> Html {
204
113
 
205
114
  // ????
206
115
  let autofocus = props.autofocus;
207
- use_effect_with((props.error.clone(), textarea_ref.3), {
116
+ use_effect_with((props.error.clone(), textarea_ref.clone()), {
208
117
  move |(_, textarea_ref)| {
209
118
  if autofocus {
210
119
  let elem = textarea_ref.cast::<web_sys::HtmlTextAreaElement>().unwrap();
@@ -214,9 +123,10 @@ pub fn code_editor(props: &CodeEditorProps) -> Html {
214
123
  });
215
124
 
216
125
  // Sync scrolling between textarea and formatted HTML
217
- use_effect_with((scroll_offset, content_ref.0, lineno_ref.0), |deps| {
218
- scroll_sync(&deps.0, &deps.1, &deps.2)
219
- });
126
+ use_effect_with(
127
+ (scroll_offset, content_ref.clone(), lineno_ref.clone()),
128
+ |deps| scroll_sync(&deps.0, &deps.1, &deps.2),
129
+ );
220
130
 
221
131
  // Blur if this element is not in the tree
222
132
  use_effect_with(filter_dropdown.clone(), |filter_dropdown| {
@@ -256,19 +166,19 @@ pub fn code_editor(props: &CodeEditorProps) -> Html {
256
166
  <>
257
167
  <LocalStyle href={css!("form/code-editor")} />
258
168
  <div id="editor" {class}>
259
- <div id="line_numbers" ref={lineno_ref.1}>{ line_numbers }</div>
169
+ <div id="line_numbers" ref={lineno_ref}>{ line_numbers }</div>
260
170
  <div id="editor-inner" {class}>
261
171
  <textarea
262
172
  {disabled}
263
173
  id="textarea_editable"
264
- ref={textarea_ref.4}
174
+ ref={textarea_ref}
265
175
  spellcheck="false"
266
176
  {oninput}
267
177
  {onscroll}
268
178
  {onkeydown}
269
179
  />
270
180
  <div id="editor-height-sizer" />
271
- <pre id="content" ref={content_ref.1}>
181
+ <pre id="content" ref={content_ref}>
272
182
  { terms }
273
183
  { {
274
184
  // A linebreak which pushs a textarea into scroll overflow
@@ -284,3 +194,94 @@ pub fn code_editor(props: &CodeEditorProps) -> Html {
284
194
  </>
285
195
  }
286
196
  }
197
+
198
+ /// Capture the input (for re-parse) and caret position whenever the input
199
+ /// text changes.
200
+ fn on_input_callback(
201
+ event: InputEvent,
202
+ // position: &UseStateSetter<u32>,
203
+ oninput: &Callback<Rc<String>>,
204
+ ) {
205
+ let elem = event
206
+ .target()
207
+ .unwrap()
208
+ .unchecked_into::<web_sys::HtmlTextAreaElement>();
209
+
210
+ oninput.emit(elem.value().into());
211
+ // position.set(elem.get_caret_position().unwrap_or_default());
212
+ }
213
+
214
+ /// Overide for special non-character commands e.g. shift+enter
215
+ fn on_keydown(event: KeyboardEvent, deps: &(UseStateSetter<u32>, Callback<()>)) {
216
+ let elem = event
217
+ .target()
218
+ .unwrap()
219
+ .unchecked_into::<web_sys::HtmlTextAreaElement>();
220
+
221
+ deps.0.set(elem.get_caret_position().unwrap_or_default());
222
+ if event.shift_key() && event.key_code() == 13 {
223
+ event.prevent_default();
224
+ deps.1.emit(())
225
+ }
226
+
227
+ // handle the tab key press
228
+ if event.key() == "Tab" {
229
+ event.prevent_default();
230
+
231
+ let caret_pos = elem.selection_start().unwrap().unwrap_or_default() as usize;
232
+
233
+ let mut initial_text = elem.value();
234
+
235
+ initial_text.insert(caret_pos, '\t');
236
+
237
+ elem.set_value(&initial_text);
238
+
239
+ let input_event = web_sys::InputEvent::new("input").unwrap();
240
+ let _ = elem.dispatch_event(&input_event).unwrap();
241
+
242
+ // place caret after inserted tab
243
+ let new_caret_pos = (caret_pos + 1) as u32;
244
+ let _ = elem.set_selection_range(new_caret_pos, new_caret_pos);
245
+
246
+ elem.focus().unwrap();
247
+ }
248
+ }
249
+
250
+ /// Scrolling callback
251
+ fn on_scroll(scroll: &UseStateSetter<(i32, i32)>, editable: &NodeRef) {
252
+ let div = editable.cast::<HtmlElement>().unwrap();
253
+ scroll.set((div.scroll_top(), div.scroll_left()));
254
+ }
255
+
256
+ /// Scrolling sync
257
+ fn scroll_sync(scroll: &UseStateHandle<(i32, i32)>, div: &NodeRef, lineno: &NodeRef) {
258
+ if let Some(div) = div.cast::<HtmlElement>() {
259
+ div.set_scroll_top(scroll.0);
260
+ div.set_scroll_left(scroll.1);
261
+ }
262
+
263
+ if let Some(div) = lineno.cast::<HtmlElement>() {
264
+ div.set_scroll_top(scroll.0);
265
+ }
266
+ }
267
+
268
+ /// Autocomplete
269
+ /// TODO this should use a portal
270
+ fn autocomplete(
271
+ filter_dropdown: &Rc<FunctionDropDownElement>,
272
+ token: &Option<String>,
273
+ target: &NodeRef,
274
+ ) {
275
+ if let Some(x) = token {
276
+ let elem = target.cast::<HtmlElement>().unwrap();
277
+ if elem.is_connected() {
278
+ filter_dropdown
279
+ .autocomplete(x.clone(), elem, Callback::from(|_| ()))
280
+ .unwrap();
281
+ } else {
282
+ filter_dropdown.hide().unwrap();
283
+ }
284
+ } else {
285
+ filter_dropdown.hide().unwrap();
286
+ }
287
+ }
@@ -26,17 +26,6 @@ pub struct ColorRangeProps {
26
26
  pub is_modified: bool,
27
27
  }
28
28
 
29
- fn infer_fg(color: &str) -> &'static str {
30
- let r = i32::from_str_radix(&color[1..3], 16).unwrap_or(255) as f64;
31
- let g = i32::from_str_radix(&color[3..5], 16).unwrap_or(0) as f64;
32
- let b = i32::from_str_radix(&color[5..7], 16).unwrap_or(0) as f64;
33
- if (r * r * 0.299 + g * g * 0.587 + b * b * 0.114).sqrt() > 130.0 {
34
- "--sign--color:#161616"
35
- } else {
36
- "--sign--color:#FFFFFF"
37
- }
38
- }
39
-
40
29
  #[function_component(ColorRangeSelector)]
41
30
  pub fn color_chooser_component(props: &ColorRangeProps) -> Html {
42
31
  let on_pos_color = use_callback(
@@ -106,10 +95,23 @@ pub fn color_chooser_component(props: &ColorRangeProps) -> Html {
106
95
  />
107
96
  <label for={format!("{}-neg", props.id)} class="color-label">{ "-" }</label>
108
97
  </div>
109
- if props.is_modified { <span class="reset-default-style" onclick={on_reset} /> } else {
98
+ if props.is_modified {
99
+ <span class="reset-default-style" onclick={on_reset} />
100
+ } else {
110
101
  <span class="reset-default-style-disabled" />
111
102
  }
112
103
  </div>
113
104
  </>
114
105
  }
115
106
  }
107
+
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"
114
+ } else {
115
+ "--sign--color:#FFFFFF"
116
+ }
117
+ }
@@ -45,7 +45,9 @@ pub fn color_component(props: &ColorProps) -> Html {
45
45
  <label id={props.title.as_deref().unwrap_or("color-label").to_owned()} />
46
46
  <div class="color-gradient-container">
47
47
  <input class="parameter" type="color" value={props.color.to_owned()} {oninput} />
48
- if props.is_modified { <span class="reset-default-style" onclick={on_reset} /> } else {
48
+ if props.is_modified {
49
+ <span class="reset-default-style" onclick={on_reset} />
50
+ } else {
49
51
  <span class="reset-default-style-disabled" />
50
52
  }
51
53
  </div>
@@ -13,6 +13,7 @@
13
13
  use std::rc::Rc;
14
14
 
15
15
  use perspective_client::ExprValidationError;
16
+ use perspective_js::utils::{ApiFuture, JsValueSerdeExt};
16
17
  use wasm_bindgen::prelude::*;
17
18
  use yew::prelude::*;
18
19
 
@@ -25,86 +26,13 @@ use crate::presentation::*;
25
26
  use crate::renderer::*;
26
27
  use crate::session::*;
27
28
  use crate::utils::*;
28
- use crate::*;
29
+ use crate::{PerspectiveProperties, css};
29
30
 
30
- #[derive(Properties, Clone, PartialEq)]
31
+ #[derive(PartialEq, Properties, PerspectiveProperties!)]
31
32
  pub struct DebugPanelProps {
32
- pub session: Session,
33
- pub renderer: Renderer,
34
33
  pub presentation: Presentation,
35
- }
36
-
37
- derive_model!(Presentation, Renderer, Session for DebugPanelProps);
38
-
39
- impl DebugPanelProps {
40
- fn set_text(&self, setter: UseStateSetter<Rc<String>>) {
41
- let props = self.clone();
42
- ApiFuture::spawn(async move {
43
- let task = props.get_viewer_config();
44
- let config = task.await?;
45
- let json = JsValue::from_serde_ext(&config)?;
46
- let js_string =
47
- js_sys::JSON::stringify_with_replacer_and_space(&json, &JsValue::NULL, &2.into())?;
48
-
49
- setter.set(Rc::new(js_string.as_string().unwrap()));
50
- Ok(())
51
- });
52
- }
53
-
54
- fn reset_callback(
55
- &self,
56
- text: UseStateSetter<Rc<String>>,
57
- error: UseStateSetter<Option<ExprValidationError>>,
58
- modified: UseStateSetter<bool>,
59
- ) -> impl Fn(()) + use<> {
60
- let props = self.clone();
61
- move |_| {
62
- error.set(None);
63
- props.set_text(text.clone());
64
- modified.set(false);
65
- }
66
- }
67
- }
68
-
69
- fn on_save(
70
- props: &DebugPanelProps,
71
- text: &Rc<String>,
72
- error: &UseStateHandle<Option<ExprValidationError>>,
73
- modified: &UseStateHandle<bool>,
74
- ) {
75
- clone!(props, text, error, modified);
76
- ApiFuture::spawn(async move {
77
- match serde_json::from_str(&text) {
78
- Ok(config) => {
79
- match props.restore_and_render(config, async { Ok(()) }).await {
80
- Ok(_) => {
81
- modified.set(false);
82
- },
83
- Err(e) => {
84
- modified.set(true);
85
- error.set(Some(ExprValidationError {
86
- error_message: JsValue::from(e)
87
- .as_string()
88
- .unwrap_or_else(|| "Failed to validate viewer config".to_owned()),
89
- line: 0_u32,
90
- column: 0,
91
- }));
92
- },
93
- }
94
- Ok(())
95
- },
96
- Err(err) => {
97
- modified.set(true);
98
- error.set(Some(ExprValidationError {
99
- error_message: err.to_string(),
100
- line: err.line() as u32 - 1,
101
- column: err.column() as u32 - 1,
102
- }));
103
-
104
- Ok(())
105
- },
106
- }
107
- });
34
+ pub renderer: Renderer,
35
+ pub session: Session,
108
36
  }
109
37
 
110
38
  #[function_component(DebugPanel)]
@@ -113,33 +41,34 @@ pub fn debug_panel(props: &DebugPanelProps) -> Html {
113
41
  let error = use_state_eq(|| Option::<ExprValidationError>::None);
114
42
  let select_all = use_memo((), |()| PubSub::default());
115
43
  let modified = use_state_eq(|| false);
116
- use_effect_with((expr.setter(), props.clone()), {
44
+
45
+ use_effect_with((expr.setter(), props.clone_state()), {
117
46
  clone!(error, modified);
118
- move |(text, props)| {
119
- props.set_text(text.clone());
47
+ move |(text, state)| {
48
+ state.set_text(text.clone());
120
49
  error.set(None);
121
- let sub1 = props
122
- .renderer()
50
+ let sub1 = state
51
+ .renderer
123
52
  .style_changed
124
- .add_listener(props.reset_callback(
53
+ .add_listener(state.reset_callback(
125
54
  text.clone(),
126
55
  error.setter(),
127
56
  modified.setter(),
128
57
  ));
129
58
 
130
- let sub2 = props
59
+ let sub2 = state
131
60
  .renderer()
132
61
  .reset_changed
133
- .add_listener(props.reset_callback(
62
+ .add_listener(state.reset_callback(
134
63
  text.clone(),
135
64
  error.setter(),
136
65
  modified.setter(),
137
66
  ));
138
67
 
139
- let sub3 = props
68
+ let sub3 = state
140
69
  .session()
141
70
  .view_config_changed
142
- .add_listener(props.reset_callback(
71
+ .add_listener(state.reset_callback(
143
72
  text.clone(),
144
73
  error.setter(),
145
74
  modified.setter(),
@@ -161,9 +90,9 @@ pub fn debug_panel(props: &DebugPanelProps) -> Html {
161
90
  }
162
91
  });
163
92
 
164
- let onsave = use_callback((expr.clone(), error.clone(), props.clone()), {
93
+ let onsave = use_callback((expr.clone(), error.clone(), props.clone_state()), {
165
94
  clone!(modified);
166
- move |_, (text, error, props)| on_save(props, text, error, &modified)
95
+ move |_, (text, error, props)| props.on_save(text, error, &modified)
167
96
  });
168
97
 
169
98
  let oncopy = use_callback(
@@ -182,12 +111,12 @@ pub fn debug_panel(props: &DebugPanelProps) -> Html {
182
111
  },
183
112
  );
184
113
 
185
- let onapply = use_callback((expr.clone(), error.clone(), props.clone()), {
114
+ let onapply = use_callback((expr.clone(), error.clone(), props.clone_state()), {
186
115
  clone!(modified);
187
- move |_, (text, error, props)| on_save(props, text, error, &modified)
116
+ move |_, (text, error, props)| props.on_save(text, error, &modified)
188
117
  });
189
118
 
190
- let onreset = use_callback((expr.setter(), error.clone(), props.clone()), {
119
+ let onreset = use_callback((expr.setter(), error.clone(), props.clone_state()), {
191
120
  clone!(modified);
192
121
  move |_, (text, error, props)| {
193
122
  props.set_text(text.clone());
@@ -196,7 +125,7 @@ pub fn debug_panel(props: &DebugPanelProps) -> Html {
196
125
  }
197
126
  });
198
127
 
199
- let onpaste = use_callback((expr.clone(), error.clone(), props.clone()), {
128
+ let onpaste = use_callback((expr.clone(), error.clone(), props.clone_state()), {
200
129
  clone!(modified);
201
130
  move |_, (text, error, props)| {
202
131
  clone!(text, error, props, modified);
@@ -206,7 +135,7 @@ pub fn debug_panel(props: &DebugPanelProps) -> Html {
206
135
  modified.set(true);
207
136
  error.set(None);
208
137
  text.set(x.clone());
209
- on_save(&props, &x, &error, &modified);
138
+ props.on_save(&x, &error, &modified);
210
139
  }
211
140
 
212
141
  Ok(())
@@ -251,3 +180,75 @@ pub fn debug_panel(props: &DebugPanelProps) -> Html {
251
180
  </>
252
181
  }
253
182
  }
183
+
184
+ impl DebugPanelPropsState {
185
+ fn set_text(&self, setter: UseStateSetter<Rc<String>>) {
186
+ let props = self.clone();
187
+ ApiFuture::spawn(async move {
188
+ let task = props.get_viewer_config();
189
+ let config = task.await?;
190
+ let json = JsValue::from_serde_ext(&config)?;
191
+ let js_string =
192
+ js_sys::JSON::stringify_with_replacer_and_space(&json, &JsValue::NULL, &2.into())?;
193
+
194
+ setter.set(Rc::new(js_string.as_string().unwrap()));
195
+ Ok(())
196
+ });
197
+ }
198
+
199
+ fn reset_callback(
200
+ &self,
201
+ text: UseStateSetter<Rc<String>>,
202
+ error: UseStateSetter<Option<ExprValidationError>>,
203
+ modified: UseStateSetter<bool>,
204
+ ) -> impl Fn(()) + use<> {
205
+ let props = self.clone();
206
+ move |_| {
207
+ error.set(None);
208
+ props.set_text(text.clone());
209
+ modified.set(false);
210
+ }
211
+ }
212
+
213
+ fn on_save(
214
+ &self,
215
+ text: &Rc<String>,
216
+ error: &UseStateHandle<Option<ExprValidationError>>,
217
+ modified: &UseStateHandle<bool>,
218
+ ) {
219
+ let props = self.clone();
220
+ clone!(text, error, modified);
221
+ ApiFuture::spawn(async move {
222
+ match serde_json::from_str(&text) {
223
+ Ok(config) => {
224
+ match props.restore_and_render(config, async { Ok(()) }).await {
225
+ Ok(_) => {
226
+ modified.set(false);
227
+ },
228
+ Err(e) => {
229
+ modified.set(true);
230
+ error.set(Some(ExprValidationError {
231
+ error_message: JsValue::from(e).as_string().unwrap_or_else(|| {
232
+ "Failed to validate viewer config".to_owned()
233
+ }),
234
+ line: 0_u32,
235
+ column: 0,
236
+ }));
237
+ },
238
+ }
239
+ Ok(())
240
+ },
241
+ Err(err) => {
242
+ modified.set(true);
243
+ error.set(Some(ExprValidationError {
244
+ error_message: err.to_string(),
245
+ line: err.line() as u32 - 1,
246
+ column: err.column() as u32 - 1,
247
+ }));
248
+
249
+ Ok(())
250
+ },
251
+ }
252
+ });
253
+ }
254
+ }
@@ -29,10 +29,12 @@ pub fn highlight<'a>(cursor: &mut Cursor<'a>, token: Token<'a>, position: u32) -
29
29
  let is_overlap = cursor.is_error();
30
30
  let result = match (is_auto, is_overlap, is_break) {
31
31
  (true, true, false) => html! {
32
- <span ref={cursor.noderef.clone()} class="error_highlight">{ token }</span>
32
+ <span ref={cursor.noderef.clone()} class="error_highlight">{ token.to_html() }</span>
33
+ },
34
+ (false, true, false) => html! { <span class="error_highlight">{ token.to_html() }</span> },
35
+ (true, false, false) => {
36
+ html! { <span ref={cursor.noderef.clone()}>{ token.to_html() }</span> }
33
37
  },
34
- (false, true, false) => html! { <span class="error_highlight">{ token }</span> },
35
- (true, false, false) => html! { <span ref={cursor.noderef.clone()}>{ token }</span> },
36
38
  _ => token.to_html(),
37
39
  };
38
40