@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.
- package/dist/cdn/perspective-viewer.js +2 -2
- package/dist/cdn/perspective-viewer.js.map +4 -4
- package/dist/css/dracula.css +1 -1
- package/dist/css/gruvbox-dark.css +1 -1
- package/dist/css/gruvbox.css +1 -1
- package/dist/css/icons.css +1 -1
- package/dist/css/monokai.css +1 -1
- package/dist/css/pro-dark.css +1 -1
- package/dist/css/pro.css +1 -1
- package/dist/css/solarized-dark.css +1 -1
- package/dist/css/solarized.css +1 -1
- package/dist/css/themes.css +1 -1
- package/dist/css/vaporwave.css +1 -1
- package/dist/esm/extensions.d.ts +23 -2
- package/dist/esm/perspective-viewer.d.ts +2 -7
- package/dist/esm/perspective-viewer.inline.js +2 -2
- package/dist/esm/perspective-viewer.inline.js.map +4 -4
- package/dist/esm/perspective-viewer.js +2 -2
- package/dist/esm/perspective-viewer.js.map +4 -4
- package/dist/esm/plugin.d.ts +1 -1
- package/dist/esm/ts-rs/ViewerConfigUpdate.d.ts +1 -0
- package/dist/wasm/perspective-viewer.d.ts +218 -46
- package/dist/wasm/perspective-viewer.js +1251 -762
- package/dist/wasm/perspective-viewer.wasm +0 -0
- package/dist/wasm/perspective-viewer.wasm.d.ts +38 -19
- package/package.json +1 -1
- package/src/less/containers/scroll-panel.less +0 -1
- package/src/less/plugin-selector.less +15 -5
- package/src/less/status-bar.less +75 -27
- package/src/less/viewer.less +140 -58
- package/src/rust/components/column_dropdown.rs +21 -21
- package/src/rust/components/column_selector/active_column.rs +131 -120
- package/src/rust/components/column_selector/add_expression_button.rs +5 -0
- package/src/rust/components/column_selector/aggregate_selector.rs +8 -4
- package/src/rust/components/column_selector/config_selector.rs +170 -161
- package/src/rust/components/column_selector/empty_column.rs +16 -11
- package/src/rust/components/column_selector/{expression_toolbar.rs → expr_edit_button.rs} +7 -0
- package/src/rust/components/column_selector/filter_column.rs +195 -194
- package/src/rust/components/column_selector/inactive_column.rs +82 -67
- package/src/rust/components/column_selector/pivot_column.rs +16 -11
- package/src/rust/components/column_selector/sort_column.rs +9 -7
- package/src/rust/components/column_selector.rs +42 -37
- package/src/rust/components/column_settings_sidebar/save_settings.rs +3 -1
- package/src/rust/components/column_settings_sidebar/style_tab/agg_depth_selector.rs +58 -0
- package/src/rust/components/column_settings_sidebar/style_tab/symbol/row_selector.rs +6 -6
- package/src/rust/components/column_settings_sidebar/style_tab/symbol/symbol_pairs.rs +2 -94
- package/src/rust/components/column_settings_sidebar/style_tab/symbol/symbol_pairs_item.rs +111 -0
- package/src/rust/components/column_settings_sidebar/style_tab/symbol.rs +3 -3
- package/src/rust/components/column_settings_sidebar/style_tab.rs +23 -83
- package/src/rust/components/{column_settings_sidebar/sidebar.rs → column_settings_sidebar.rs} +198 -171
- package/src/rust/components/containers/dragdrop_list.rs +20 -20
- package/src/rust/components/containers/dropdown_menu.rs +4 -6
- package/src/rust/components/containers/mod.rs +1 -4
- package/src/rust/components/containers/scroll_panel.rs +80 -80
- package/src/rust/components/containers/scroll_panel_item.rs +36 -36
- package/src/rust/components/containers/select.rs +46 -44
- package/src/rust/components/containers/sidebar.rs +3 -19
- package/src/rust/components/{column_settings_sidebar/style_tab/symbol/symbol_config.rs → containers/sidebar_close_button.rs} +15 -9
- package/src/rust/components/containers/split_panel.rs +212 -200
- package/src/rust/components/containers/tab_list.rs +11 -11
- package/src/rust/components/copy_dropdown.rs +22 -25
- package/src/rust/components/datetime_column_style/custom.rs +19 -19
- package/src/rust/components/datetime_column_style/simple.rs +13 -14
- package/src/rust/components/datetime_column_style.rs +75 -76
- package/src/rust/components/editable_header.rs +18 -14
- package/src/rust/components/empty_row.rs +5 -5
- package/src/rust/components/export_dropdown.rs +42 -42
- package/src/rust/components/expression_editor.rs +25 -19
- package/src/rust/components/filter_dropdown.rs +22 -22
- package/src/rust/components/font_loader.rs +11 -9
- package/src/rust/components/form/code_editor.rs +106 -105
- package/src/rust/components/form/color_range_selector.rs +14 -12
- package/src/rust/components/form/color_selector.rs +3 -1
- package/src/rust/components/form/debug.rs +95 -94
- package/src/rust/components/form/highlight.rs +5 -3
- package/src/rust/components/form/mod.rs +3 -2
- package/src/rust/components/form/optional_field.rs +2 -2
- package/src/rust/components/form/{select_field.rs → select_enum_field.rs} +1 -46
- package/src/rust/components/form/select_value_field.rs +64 -0
- package/src/rust/components/function_dropdown.rs +21 -21
- package/src/rust/components/main_panel.rs +219 -0
- package/src/rust/components/mod.rs +6 -6
- package/src/rust/components/modal.rs +42 -42
- package/src/rust/components/number_column_style.rs +34 -88
- package/src/rust/components/plugin_selector.rs +22 -25
- package/src/rust/components/render_warning.rs +9 -6
- package/src/rust/components/settings_panel.rs +82 -0
- package/src/rust/components/status_bar.rs +250 -146
- package/src/rust/components/status_bar_counter.rs +26 -119
- package/src/rust/components/status_indicator.rs +95 -79
- package/src/rust/components/string_column_style.rs +45 -45
- package/src/rust/components/style/style_provider.rs +1 -15
- package/src/rust/components/style_controls/number_string_format/digits_section.rs +1 -1
- package/src/rust/components/style_controls/number_string_format/misc_section.rs +1 -1
- package/src/rust/components/style_controls/number_string_format/style_section.rs +1 -1
- package/src/rust/components/style_controls/number_string_format.rs +45 -46
- package/src/rust/components/type_icon.rs +14 -11
- package/src/rust/components/viewer.rs +241 -384
- package/src/rust/config/columns_config.rs +2 -2
- package/src/rust/config/datetime_column_style.rs +1 -6
- package/src/rust/config/mod.rs +1 -0
- package/src/rust/config/number_column_style.rs +0 -6
- package/src/rust/config/number_string_format.rs +27 -4
- package/src/rust/config/viewer_config.rs +27 -167
- package/src/rust/custom_elements/copy_dropdown.rs +14 -6
- package/src/rust/custom_elements/export_dropdown.rs +15 -7
- package/src/rust/custom_elements/filter_dropdown.rs +4 -4
- package/src/rust/custom_elements/mod.rs +3 -0
- package/src/rust/custom_elements/viewer.rs +353 -161
- package/src/rust/custom_events.rs +55 -32
- package/src/rust/dragdrop.rs +4 -24
- package/src/rust/exprtk/cursor.rs +10 -1
- package/src/rust/exprtk/mod.rs +2 -0
- package/src/rust/exprtk/tokenize.rs +20 -3
- package/src/rust/js/clipboard.rs +2 -2
- package/src/rust/js/mimetype.rs +2 -7
- package/src/rust/js/mod.rs +0 -1
- package/src/rust/js/plugin.rs +7 -0
- package/src/rust/lib.rs +18 -5
- package/src/rust/model/column_locator.rs +82 -0
- package/src/rust/model/columns_iter_set.rs +1 -0
- package/src/rust/model/copy_export.rs +50 -14
- package/src/rust/model/edit_expression.rs +2 -5
- package/src/rust/model/eject.rs +41 -0
- package/src/rust/model/export_app.rs +3 -2
- package/src/rust/model/get_viewer_config.rs +4 -28
- package/src/rust/model/intersection_observer.rs +20 -8
- package/src/rust/model/mod.rs +11 -4
- package/src/rust/model/plugin_column_styles.rs +0 -31
- package/src/rust/model/reset_all.rs +38 -0
- package/src/rust/model/resize_observer.rs +34 -7
- package/src/rust/model/restore_and_render.rs +12 -7
- package/src/rust/{utils/scope.rs → model/send_plugin_config.rs} +32 -35
- package/src/rust/model/structural.rs +194 -23
- package/src/rust/model/update_and_render.rs +14 -4
- package/src/rust/{model/create_col.rs → presentation/column_locator.rs} +73 -42
- package/src/rust/{utils/wasm_abi.rs → presentation/sheets.rs} +54 -40
- package/src/rust/presentation.rs +60 -119
- package/src/rust/renderer/activate.rs +20 -5
- package/src/rust/renderer/limits.rs +0 -149
- package/src/rust/renderer/render_timer.rs +1 -1
- package/src/rust/renderer.rs +34 -18
- package/src/rust/root.rs +50 -0
- package/src/rust/session/column_defaults_update.rs +4 -4
- package/src/rust/session/drag_drop_update.rs +1 -1
- package/src/rust/session/metadata.rs +3 -17
- package/src/rust/session/replace_expression_update.rs +1 -2
- package/src/rust/session.rs +162 -82
- package/src/rust/utils/browser/blob.rs +16 -2
- package/src/rust/utils/browser/download.rs +1 -0
- package/src/rust/{components/column_settings_sidebar/mod.rs → utils/browser/dragdrop.rs} +14 -5
- package/src/rust/utils/browser/mod.rs +8 -4
- package/src/rust/utils/browser/selection.rs +5 -0
- package/src/rust/utils/custom_element.rs +28 -13
- package/src/rust/utils/datetime.rs +5 -0
- package/src/rust/utils/debounce.rs +7 -1
- package/src/rust/utils/hooks/use_async_callback.rs +7 -17
- package/src/rust/utils/mod.rs +28 -40
- package/src/rust/utils/number_format.rs +6 -5
- package/src/rust/utils/pubsub.rs +15 -10
- package/src/rust/utils/weak_scope.rs +11 -1
- package/src/svg/bookmark-icon.svg +4 -0
- package/src/svg/drag-handle copy.svg +10 -0
- package/src/svg/drawer-tab-hover.svg +5 -7
- package/src/svg/drawer-tab-invert-hover.svg +4 -8
- package/src/svg/drawer-tab-invert.svg +4 -7
- package/src/svg/drawer-tab.svg +4 -6
- package/src/svg/status_ok.svg +24 -24
- package/src/ts/extensions.ts +51 -3
- package/src/ts/perspective-viewer.ts +2 -14
- package/src/ts/plugin.ts +1 -1
- package/src/ts/ts-rs/ViewerConfigUpdate.ts +1 -1
- package/src/rust/components/column_settings_sidebar/style_tab/column_style.rs +0 -177
- package/src/rust/components/containers/tests/mod.rs +0 -11
- package/src/rust/components/containers/tests/split_panel.rs +0 -91
- package/src/rust/js/testing.rs +0 -149
- package/src/rust/utils/tee.rs +0 -88
- /package/dist/wasm/snippets/{perspective-viewer-c69283f6f62a5f14 → perspective-viewer-0d326a25c1022412}/inline0.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-c69283f6f62a5f14 → perspective-viewer-0d326a25c1022412}/inline1.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-c69283f6f62a5f14 → perspective-viewer-0d326a25c1022412}/inline2.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-c69283f6f62a5f14 → perspective-viewer-0d326a25c1022412}/inline3.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-c69283f6f62a5f14 → perspective-viewer-0d326a25c1022412}/inline4.js +0 -0
- /package/src/rust/components/{style_controls.rs → style_controls/mod.rs} +0 -0
- /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
|
-
|
|
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,
|
|
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,
|
|
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()
|
|
148
|
-
let content_ref = use_node_ref()
|
|
149
|
-
let lineno_ref = use_node_ref()
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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(
|
|
218
|
-
|
|
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
|
|
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
|
|
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
|
|
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 {
|
|
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 {
|
|
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(
|
|
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
|
-
|
|
44
|
+
|
|
45
|
+
use_effect_with((expr.setter(), props.clone_state()), {
|
|
117
46
|
clone!(error, modified);
|
|
118
|
-
move |(text,
|
|
119
|
-
|
|
47
|
+
move |(text, state)| {
|
|
48
|
+
state.set_text(text.clone());
|
|
120
49
|
error.set(None);
|
|
121
|
-
let sub1 =
|
|
122
|
-
.renderer
|
|
50
|
+
let sub1 = state
|
|
51
|
+
.renderer
|
|
123
52
|
.style_changed
|
|
124
|
-
.add_listener(
|
|
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 =
|
|
59
|
+
let sub2 = state
|
|
131
60
|
.renderer()
|
|
132
61
|
.reset_changed
|
|
133
|
-
.add_listener(
|
|
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 =
|
|
68
|
+
let sub3 = state
|
|
140
69
|
.session()
|
|
141
70
|
.view_config_changed
|
|
142
|
-
.add_listener(
|
|
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.
|
|
93
|
+
let onsave = use_callback((expr.clone(), error.clone(), props.clone_state()), {
|
|
165
94
|
clone!(modified);
|
|
166
|
-
move |_, (text, error, props)| on_save(
|
|
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.
|
|
114
|
+
let onapply = use_callback((expr.clone(), error.clone(), props.clone_state()), {
|
|
186
115
|
clone!(modified);
|
|
187
|
-
move |_, (text, error, props)| on_save(
|
|
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.
|
|
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.
|
|
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(&
|
|
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
|
|