@perspective-dev/viewer 5.2.0 → 5.3.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 +2 -2
- package/dist/css/botanical.css +1 -1
- 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/phosphor.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/docs/perspective-docs.json +1 -1
- package/dist/esm/perspective-viewer.d.ts +1 -0
- package/dist/esm/perspective-viewer.inline.js +9 -9
- package/dist/esm/perspective-viewer.inline.js.map +2 -2
- package/dist/esm/perspective-viewer.js +4 -4
- package/dist/esm/perspective-viewer.js.map +2 -2
- package/dist/esm/plugin.d.ts +10 -0
- package/dist/esm/ts-rs/PanelViewerConfig.d.ts +1 -1
- package/dist/esm/ts-rs/SaveWorkspaceOptions.d.ts +10 -0
- package/dist/esm/ts-rs/ViewerConfig.d.ts +1 -1
- package/dist/esm/ts-rs/WorkspaceConfig.d.ts +9 -1
- package/dist/esm/ts-rs/WorkspaceConfigUpdate.d.ts +13 -3
- package/dist/wasm/perspective-viewer.d.ts +43 -36
- package/dist/wasm/perspective-viewer.js +194 -136
- package/dist/wasm/perspective-viewer.wasm +0 -0
- package/dist/wasm/perspective-viewer.wasm.d.ts +19 -17
- package/package.json +1 -1
- package/src/css/column-settings-panel.css +9 -5
- package/src/css/column-style.css +209 -31
- package/src/css/dom/select.css +2 -1
- package/src/css/form/code-editor.css +4 -4
- package/src/rust/agent/tools.rs +48 -17
- package/src/rust/components/column_selector/active_column.rs +2 -2
- package/src/rust/components/column_selector/add_expression_button.rs +3 -3
- package/src/rust/components/column_selector/aggregate_selector.rs +2 -3
- package/src/rust/components/column_selector/config_selector.rs +8 -0
- package/src/rust/components/column_selector/expr_edit_button.rs +4 -10
- package/src/rust/components/column_selector/inactive_column.rs +2 -2
- package/src/rust/components/column_selector.rs +48 -4
- package/src/rust/components/column_settings_sidebar/style_tab/primitive_field.rs +293 -126
- package/src/rust/components/column_settings_sidebar/style_tab.rs +262 -225
- package/src/rust/components/column_settings_sidebar.rs +88 -69
- package/src/rust/components/editable_header.rs +85 -144
- package/src/rust/components/expression_editor.rs +9 -16
- package/src/rust/components/form/code_editor.rs +16 -6
- package/src/rust/components/form/debug.rs +1 -0
- package/src/rust/components/form/mod.rs +3 -1
- package/src/rust/components/form/multi_stop_gradient_selector.rs +476 -0
- package/src/rust/components/form/named_value_picker.rs +86 -0
- package/src/rust/components/form/palette_selector.rs +440 -0
- package/src/rust/components/main_panel/msg.rs +2 -2
- package/src/rust/components/main_panel/reconcile.rs +4 -4
- package/src/rust/components/plugin_tab.rs +4 -28
- package/src/rust/components/settings_panel.rs +25 -20
- package/src/rust/components/viewer/msg.rs +46 -7
- package/src/rust/components/viewer/panels.rs +11 -9
- package/src/rust/components/viewer/render.rs +8 -3
- package/src/rust/components/viewer/settings.rs +163 -23
- package/src/rust/components/viewer/snapshots.rs +46 -21
- package/src/rust/components/viewer/wiring.rs +14 -25
- package/src/rust/components/viewer.rs +12 -8
- package/src/rust/config/column_config_schema.rs +100 -41
- package/src/rust/config/css_scale.rs +882 -0
- package/src/rust/config/mod.rs +2 -0
- package/src/rust/config/options.rs +10 -0
- package/src/rust/config/viewer_config.rs +49 -55
- package/src/rust/config/workspace_config.rs +132 -5
- package/src/rust/custom_elements/viewer.rs +214 -155
- package/src/rust/custom_events.rs +14 -1
- package/src/rust/js/regular_layout.rs +3 -3
- package/src/rust/lib.rs +2 -0
- package/src/rust/presentation/column_locator.rs +19 -5
- package/src/rust/presentation.rs +192 -36
- package/src/rust/queries/column_locator.rs +74 -44
- package/src/rust/queries/get_viewer_config.rs +0 -3
- package/src/rust/queries/mod.rs +2 -0
- package/src/rust/queries/palette_set.rs +82 -0
- package/src/rust/queries/plugin_column_styles.rs +6 -2
- package/src/rust/renderer/dispatch.rs +16 -4
- package/src/rust/renderer/plugin_config.rs +309 -47
- package/src/rust/renderer.rs +15 -42
- package/src/rust/session/column_defaults_update.rs +15 -8
- package/src/rust/session/metadata.rs +5 -1
- package/src/rust/session/props.rs +1 -0
- package/src/rust/session.rs +9 -6
- package/src/rust/tasks/create_panel.rs +44 -57
- package/src/rust/tasks/edit_expression.rs +19 -23
- package/src/rust/tasks/edit_window.rs +8 -10
- package/src/rust/tasks/eject.rs +4 -1
- package/src/rust/tasks/pipeline.rs +2 -2
- package/src/rust/tasks/presize_panels.rs +44 -5
- package/src/rust/tasks/reset_all.rs +4 -4
- package/src/rust/tasks/restore_and_render.rs +17 -44
- package/src/rust/tasks/restore_panel.rs +11 -13
- package/src/rust/tasks/set_edit_mode.rs +3 -1
- package/src/rust/tasks/sync_update_panels.rs +11 -1
- package/src/rust/tasks/update_theme.rs +23 -18
- package/src/rust/utils/css_vars.rs +26 -0
- package/src/rust/utils/mod.rs +2 -0
- package/src/rust/utils/spawn.rs +1 -1
- package/src/rust/workspace.rs +435 -272
- package/src/svg/lock.svg +7 -0
- package/src/themes/botanical.css +10 -0
- package/src/themes/defaults.css +12 -0
- package/src/themes/dracula.css +17 -0
- package/src/themes/gruvbox-dark.css +20 -0
- package/src/themes/gruvbox.css +10 -0
- package/src/themes/icons.css +1 -0
- package/src/themes/monokai.css +22 -0
- package/src/themes/phosphor.css +5 -0
- package/src/themes/pro-dark.css +10 -0
- package/src/themes/solarized-dark.css +7 -0
- package/src/themes/solarized.css +7 -0
- package/src/themes/vaporwave.css +20 -0
- package/src/ts/perspective-viewer.ts +1 -0
- package/src/ts/plugin.ts +14 -0
- package/src/ts/ts-rs/PanelViewerConfig.ts +1 -1
- package/src/ts/ts-rs/SaveWorkspaceOptions.ts +11 -0
- package/src/ts/ts-rs/ViewerConfig.ts +1 -1
- package/src/ts/ts-rs/WorkspaceConfig.ts +8 -2
- package/src/ts/ts-rs/WorkspaceConfigUpdate.ts +11 -3
- package/src/rust/components/form/color_range_selector.rs +0 -137
- /package/dist/wasm/snippets/{perspective-viewer-02e461f3d7062b26 → perspective-viewer-39c3c3b28e3cbd48}/inline0.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-02e461f3d7062b26 → perspective-viewer-39c3c3b28e3cbd48}/inline1.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-02e461f3d7062b26 → perspective-viewer-39c3c3b28e3cbd48}/inline2.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-02e461f3d7062b26 → perspective-viewer-39c3c3b28e3cbd48}/inline3.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-02e461f3d7062b26 → perspective-viewer-39c3c3b28e3cbd48}/inline4.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-02e461f3d7062b26 → perspective-viewer-39c3c3b28e3cbd48}/inline5.js +0 -0
|
@@ -42,6 +42,7 @@ use crate::tasks::{
|
|
|
42
42
|
delete_expr, delete_window, save_expr, save_window, update_expr, update_window,
|
|
43
43
|
};
|
|
44
44
|
use crate::utils::PtrEqRc;
|
|
45
|
+
use crate::workspace::Workspace;
|
|
45
46
|
|
|
46
47
|
#[derive(Clone, Derivative, Properties)]
|
|
47
48
|
#[derivative(Debug)]
|
|
@@ -91,12 +92,13 @@ pub struct ColumnSettingsPanelProps {
|
|
|
91
92
|
|
|
92
93
|
#[derivative(Debug = "ignore")]
|
|
93
94
|
pub session: Session,
|
|
95
|
+
|
|
96
|
+
#[derivative(Debug = "ignore")]
|
|
97
|
+
pub workspace: Workspace,
|
|
94
98
|
}
|
|
95
99
|
|
|
96
|
-
impl ColumnSettingsPanelProps {
|
|
97
|
-
|
|
98
|
-
/// invalidates the drafts that `initialize` rebuilds.
|
|
99
|
-
fn identity_eq(&self, other: &Self) -> bool {
|
|
100
|
+
impl PartialEq for ColumnSettingsPanelProps {
|
|
101
|
+
fn eq(&self, other: &Self) -> bool {
|
|
100
102
|
self.selected_column == other.selected_column
|
|
101
103
|
&& self.selected_tab == other.selected_tab
|
|
102
104
|
&& self.plugin_name == other.plugin_name
|
|
@@ -104,14 +106,46 @@ impl ColumnSettingsPanelProps {
|
|
|
104
106
|
&& self.view_config == other.view_config
|
|
105
107
|
&& self.column_stats == other.column_stats
|
|
106
108
|
&& self.selected_theme == other.selected_theme
|
|
109
|
+
&& self.auto_width == other.auto_width
|
|
110
|
+
&& self.is_pinned == other.is_pinned
|
|
107
111
|
}
|
|
108
112
|
}
|
|
109
113
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
114
|
+
#[derive(Clone, PartialEq)]
|
|
115
|
+
struct Initials {
|
|
116
|
+
column_name: String,
|
|
117
|
+
expr: Rc<String>,
|
|
118
|
+
header: Option<String>,
|
|
119
|
+
window: Option<WindowSpec>,
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
impl Initials {
|
|
123
|
+
fn of(props: &ColumnSettingsPanelProps) -> Self {
|
|
124
|
+
let column_name = props
|
|
125
|
+
.metadata
|
|
126
|
+
.locator_name_or_default(&props.selected_column);
|
|
127
|
+
|
|
128
|
+
let expr = props
|
|
129
|
+
.metadata
|
|
130
|
+
.get_expression_by_alias(&column_name)
|
|
131
|
+
.or_else(|| props.view_config.expressions.get(&column_name).cloned())
|
|
132
|
+
.unwrap_or_default();
|
|
133
|
+
|
|
134
|
+
let expr = Rc::new(expr);
|
|
135
|
+
let header = (*expr != column_name).then_some(column_name.clone());
|
|
136
|
+
|
|
137
|
+
let window = props
|
|
138
|
+
.selected_column
|
|
139
|
+
.name()
|
|
140
|
+
.and_then(|name| props.view_config.windows.get(name))
|
|
141
|
+
.cloned();
|
|
142
|
+
|
|
143
|
+
Self {
|
|
144
|
+
column_name,
|
|
145
|
+
expr,
|
|
146
|
+
header,
|
|
147
|
+
window,
|
|
148
|
+
}
|
|
115
149
|
}
|
|
116
150
|
}
|
|
117
151
|
|
|
@@ -172,22 +206,23 @@ impl Component for ColumnSettingsPanel {
|
|
|
172
206
|
initial_window_value: None,
|
|
173
207
|
window_value: None,
|
|
174
208
|
tabs: vec![],
|
|
175
|
-
on_input:
|
|
176
|
-
on_save:
|
|
177
|
-
|
|
209
|
+
on_input: ctx.link().callback(ColumnSettingsPanelMsg::SetExprValue),
|
|
210
|
+
on_save: ctx
|
|
211
|
+
.link()
|
|
212
|
+
.callback(ColumnSettingsPanelMsg::OnSaveAttributes),
|
|
213
|
+
on_validate: ctx.link().callback(ColumnSettingsPanelMsg::SetExprValid),
|
|
178
214
|
};
|
|
179
215
|
|
|
180
|
-
this.
|
|
216
|
+
this.reset_to(ctx, Initials::of(ctx.props()));
|
|
181
217
|
this
|
|
182
218
|
}
|
|
183
219
|
|
|
184
220
|
fn changed(&mut self, ctx: &yew::prelude::Context<Self>, old_props: &Self::Properties) -> bool {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
self.initialize(ctx);
|
|
221
|
+
let next = Initials::of(ctx.props());
|
|
222
|
+
if ctx.props().selected_column != old_props.selected_column || next != self.initials() {
|
|
223
|
+
self.reset_to(ctx, next);
|
|
224
|
+
} else {
|
|
225
|
+
self.refresh_derived(ctx);
|
|
191
226
|
}
|
|
192
227
|
|
|
193
228
|
true
|
|
@@ -353,9 +388,9 @@ impl Component for ColumnSettingsPanel {
|
|
|
353
388
|
};
|
|
354
389
|
|
|
355
390
|
let header_props = props!(EditableHeaderProps {
|
|
391
|
+
value: self.header_value.clone(),
|
|
356
392
|
initial_value: self.initial_header_value.clone(),
|
|
357
393
|
placeholder: header_placeholder,
|
|
358
|
-
reset_count: self.reset_count,
|
|
359
394
|
editable: (ctx.props().selected_column.is_expr()
|
|
360
395
|
&& matches!(
|
|
361
396
|
ctx.props().selected_tab,
|
|
@@ -374,7 +409,6 @@ impl Component for ColumnSettingsPanel {
|
|
|
374
409
|
]
|
|
375
410
|
}),
|
|
376
411
|
metadata: ctx.props().metadata.clone(),
|
|
377
|
-
session: &ctx.props().session
|
|
378
412
|
});
|
|
379
413
|
|
|
380
414
|
let expr_editor = props!(ExpressionEditorProps {
|
|
@@ -382,6 +416,7 @@ impl Component for ColumnSettingsPanel {
|
|
|
382
416
|
on_save: self.on_save.clone(),
|
|
383
417
|
on_validate: self.on_validate.clone(),
|
|
384
418
|
alias: ctx.props().selected_column.name().cloned(),
|
|
419
|
+
initial_expr: self.initial_expr_value.clone(),
|
|
385
420
|
disabled: !ctx.props().selected_column.is_expr(),
|
|
386
421
|
reset_count: self.reset_count,
|
|
387
422
|
metadata: ctx.props().metadata.clone(),
|
|
@@ -457,6 +492,7 @@ impl Component for ColumnSettingsPanel {
|
|
|
457
492
|
presentation: ctx.props().presentation.clone(),
|
|
458
493
|
renderer: ctx.props().renderer.clone(),
|
|
459
494
|
session: ctx.props().session.clone(),
|
|
495
|
+
workspace: ctx.props().workspace.clone(),
|
|
460
496
|
};
|
|
461
497
|
|
|
462
498
|
let tab_children = self.tabs.iter().map(|tab| match tab {
|
|
@@ -506,38 +542,22 @@ impl ColumnSettingsPanel {
|
|
|
506
542
|
self.save_enabled = changed && valid;
|
|
507
543
|
}
|
|
508
544
|
|
|
509
|
-
fn
|
|
510
|
-
|
|
511
|
-
.
|
|
512
|
-
.
|
|
513
|
-
.
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
.metadata
|
|
518
|
-
.get_expression_by_alias(&column_name)
|
|
519
|
-
.unwrap_or_default();
|
|
520
|
-
|
|
521
|
-
let initial_expr_value = Rc::new(initial_expr_value);
|
|
522
|
-
let initial_header_value =
|
|
523
|
-
(*initial_expr_value != column_name).then_some(column_name.clone());
|
|
545
|
+
fn initials(&self) -> Initials {
|
|
546
|
+
Initials {
|
|
547
|
+
column_name: self.column_name.clone(),
|
|
548
|
+
expr: self.initial_expr_value.clone(),
|
|
549
|
+
header: self.initial_header_value.clone(),
|
|
550
|
+
window: self.initial_window_value.clone(),
|
|
551
|
+
}
|
|
552
|
+
}
|
|
524
553
|
|
|
525
|
-
|
|
554
|
+
fn refresh_derived(&mut self, ctx: &yew::prelude::Context<Self>) {
|
|
555
|
+
self.maybe_ty = ctx
|
|
526
556
|
.props()
|
|
527
557
|
.metadata
|
|
528
558
|
.locator_view_type(&ctx.props().selected_column);
|
|
529
559
|
|
|
530
|
-
|
|
531
|
-
// editable header owns naming in the UI), so drafts and saved specs
|
|
532
|
-
// compare directly.
|
|
533
|
-
let initial_window_value = ctx
|
|
534
|
-
.props()
|
|
535
|
-
.selected_column
|
|
536
|
-
.name()
|
|
537
|
-
.and_then(|name| ctx.props().view_config.windows.get(name))
|
|
538
|
-
.cloned();
|
|
539
|
-
|
|
540
|
-
let tabs = {
|
|
560
|
+
self.tabs = {
|
|
541
561
|
let mut tabs = vec![];
|
|
542
562
|
let is_new_expr = ctx.props().selected_column.is_new_expr();
|
|
543
563
|
let show_styles = !is_new_expr
|
|
@@ -571,28 +591,27 @@ impl ColumnSettingsPanel {
|
|
|
571
591
|
|
|
572
592
|
tabs
|
|
573
593
|
};
|
|
594
|
+
}
|
|
574
595
|
|
|
575
|
-
|
|
576
|
-
let
|
|
577
|
-
.link()
|
|
578
|
-
.callback(ColumnSettingsPanelMsg::OnSaveAttributes);
|
|
579
|
-
|
|
580
|
-
let on_validate = ctx.link().callback(ColumnSettingsPanelMsg::SetExprValid);
|
|
581
|
-
*self = Self {
|
|
596
|
+
fn reset_to(&mut self, ctx: &yew::prelude::Context<Self>, initials: Initials) {
|
|
597
|
+
let Initials {
|
|
582
598
|
column_name,
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
599
|
+
expr,
|
|
600
|
+
header,
|
|
601
|
+
window,
|
|
602
|
+
} = initials;
|
|
603
|
+
|
|
604
|
+
self.column_name = column_name;
|
|
605
|
+
self.expr_value = expr.clone();
|
|
606
|
+
self.initial_expr_value = expr;
|
|
607
|
+
self.header_value = header.clone();
|
|
608
|
+
self.initial_header_value = header;
|
|
609
|
+
self.window_value = window.clone();
|
|
610
|
+
self.initial_window_value = window;
|
|
611
|
+
self.header_valid = true;
|
|
612
|
+
self.save_enabled = false;
|
|
613
|
+
self.reset_enabled = false;
|
|
614
|
+
self.reset_count = self.reset_count.wrapping_add(1);
|
|
615
|
+
self.refresh_derived(ctx);
|
|
597
616
|
}
|
|
598
617
|
}
|
|
@@ -14,32 +14,30 @@ use std::rc::Rc;
|
|
|
14
14
|
|
|
15
15
|
use itertools::Itertools;
|
|
16
16
|
use web_sys::{FocusEvent, HtmlInputElement, KeyboardEvent};
|
|
17
|
-
use yew::
|
|
17
|
+
use yew::prelude::*;
|
|
18
18
|
|
|
19
19
|
use super::type_icon::TypeIconType;
|
|
20
20
|
use crate::components::type_icon::TypeIcon;
|
|
21
|
-
use crate::session::
|
|
21
|
+
use crate::session::SessionMetadataRc;
|
|
22
22
|
|
|
23
23
|
#[derive(Clone, PartialEq, Properties)]
|
|
24
24
|
pub struct EditableHeaderProps {
|
|
25
25
|
pub icon_type: Option<TypeIconType>,
|
|
26
26
|
pub on_change: Callback<(Option<String>, bool)>,
|
|
27
27
|
pub editable: bool,
|
|
28
|
+
|
|
29
|
+
/// The owner's draft.
|
|
30
|
+
pub value: Option<String>,
|
|
31
|
+
|
|
32
|
+
/// The saved name the draft is measured against.
|
|
28
33
|
pub initial_value: Option<String>,
|
|
29
34
|
pub placeholder: Rc<String>,
|
|
30
35
|
|
|
31
|
-
// TODO remove this pattern
|
|
32
|
-
#[prop_or_default]
|
|
33
|
-
pub reset_count: u8,
|
|
34
|
-
|
|
35
36
|
#[prop_or_default]
|
|
36
37
|
pub update_on_input: bool,
|
|
37
38
|
|
|
38
39
|
/// Session metadata snapshot — threaded from `SessionProps`.
|
|
39
40
|
pub metadata: SessionMetadataRc,
|
|
40
|
-
|
|
41
|
-
// State
|
|
42
|
-
pub session: Session,
|
|
43
41
|
}
|
|
44
42
|
|
|
45
43
|
impl EditableHeaderProps {
|
|
@@ -55,152 +53,95 @@ impl EditableHeaderProps {
|
|
|
55
53
|
Some((idx, _)) => split[..idx].to_owned(),
|
|
56
54
|
}
|
|
57
55
|
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
pub enum EditableHeaderMsg {
|
|
61
|
-
SetNewValue(String),
|
|
62
|
-
OnClick(()),
|
|
63
|
-
}
|
|
64
56
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
valid: bool,
|
|
70
|
-
value: Option<String>,
|
|
71
|
-
placeholder: String,
|
|
72
|
-
}
|
|
57
|
+
fn is_valid(&self, value: &Option<String>, placeholder: &str) -> bool {
|
|
58
|
+
let Some(value) = value else {
|
|
59
|
+
return true;
|
|
60
|
+
};
|
|
73
61
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
type Properties = EditableHeaderProps;
|
|
77
|
-
|
|
78
|
-
fn create(ctx: &yew::prelude::Context<Self>) -> Self {
|
|
79
|
-
Self {
|
|
80
|
-
value: ctx.props().initial_value.clone(),
|
|
81
|
-
placeholder: ctx.props().split_placeholder(),
|
|
82
|
-
valid: true,
|
|
83
|
-
noderef: NodeRef::default(),
|
|
84
|
-
edited: false,
|
|
62
|
+
if value == placeholder || Some(value) == self.initial_value.as_ref() {
|
|
63
|
+
return true;
|
|
85
64
|
}
|
|
86
|
-
}
|
|
87
65
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
}
|
|
92
|
-
if ctx.props().initial_value != old_props.initial_value {
|
|
93
|
-
self.edited = false;
|
|
94
|
-
self.value.clone_from(&ctx.props().initial_value);
|
|
95
|
-
}
|
|
96
|
-
if !ctx.props().editable {
|
|
97
|
-
self.edited = false;
|
|
98
|
-
}
|
|
99
|
-
self.placeholder = ctx.props().split_placeholder();
|
|
100
|
-
ctx.props() != old_props
|
|
101
|
-
}
|
|
66
|
+
let metadata = &self.metadata;
|
|
67
|
+
let Some(table_columns) = metadata.get_table_columns() else {
|
|
68
|
+
return true;
|
|
69
|
+
};
|
|
102
70
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
self.valid = (|| -> Option<bool> {
|
|
110
|
-
if maybe_value
|
|
111
|
-
.as_ref()
|
|
112
|
-
.map(|v| v == &self.placeholder)
|
|
113
|
-
.unwrap_or(true)
|
|
114
|
-
{
|
|
115
|
-
return Some(true);
|
|
116
|
-
}
|
|
117
|
-
if !self.edited {
|
|
118
|
-
return Some(true);
|
|
119
|
-
}
|
|
120
|
-
let metadata = &ctx.props().metadata;
|
|
121
|
-
let expressions = metadata.get_expression_columns();
|
|
122
|
-
let windows = metadata.get_window_columns();
|
|
123
|
-
let found = metadata
|
|
124
|
-
.get_table_columns()?
|
|
125
|
-
.iter()
|
|
126
|
-
.chain(expressions)
|
|
127
|
-
.chain(windows)
|
|
128
|
-
.contains(&new_value);
|
|
129
|
-
Some(!found)
|
|
130
|
-
})()
|
|
131
|
-
.unwrap_or(true);
|
|
132
|
-
|
|
133
|
-
self.value.clone_from(&maybe_value);
|
|
134
|
-
ctx.props().on_change.emit((maybe_value, self.valid));
|
|
135
|
-
true
|
|
136
|
-
},
|
|
137
|
-
EditableHeaderMsg::OnClick(()) => {
|
|
138
|
-
self.noderef
|
|
139
|
-
.cast::<HtmlInputElement>()
|
|
140
|
-
.unwrap()
|
|
141
|
-
.focus()
|
|
142
|
-
.unwrap();
|
|
143
|
-
false
|
|
144
|
-
},
|
|
145
|
-
}
|
|
71
|
+
!table_columns
|
|
72
|
+
.iter()
|
|
73
|
+
.chain(metadata.get_expression_columns())
|
|
74
|
+
.chain(metadata.get_window_columns())
|
|
75
|
+
.contains(value)
|
|
146
76
|
}
|
|
77
|
+
}
|
|
147
78
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
if self.edited {
|
|
159
|
-
classes.push("edited");
|
|
160
|
-
}
|
|
79
|
+
#[function_component(EditableHeader)]
|
|
80
|
+
pub fn editable_header(props: &EditableHeaderProps) -> Html {
|
|
81
|
+
let noderef = use_node_ref();
|
|
82
|
+
let placeholder = props.split_placeholder();
|
|
83
|
+
let edited = props.value != props.initial_value;
|
|
84
|
+
let valid = props.is_valid(&props.value, &placeholder);
|
|
85
|
+
let mut classes = classes!("sidebar_header_contents");
|
|
86
|
+
if props.editable {
|
|
87
|
+
classes.push("editable");
|
|
88
|
+
}
|
|
161
89
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
});
|
|
90
|
+
if !valid {
|
|
91
|
+
classes.push("invalid");
|
|
92
|
+
}
|
|
166
93
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
});
|
|
94
|
+
if edited {
|
|
95
|
+
classes.push("edited");
|
|
96
|
+
}
|
|
171
97
|
|
|
172
|
-
|
|
173
|
-
let
|
|
98
|
+
let on_value = {
|
|
99
|
+
let props = props.clone();
|
|
100
|
+
let placeholder = placeholder.clone();
|
|
101
|
+
Callback::from(move |value: String| {
|
|
102
|
+
let value = (!value.is_empty()).then_some(value);
|
|
103
|
+
let valid = props.is_valid(&value, &placeholder);
|
|
104
|
+
props.on_change.emit((value, valid));
|
|
105
|
+
})
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
let onkeyup =
|
|
109
|
+
on_value.reform(|e: KeyboardEvent| e.target_unchecked_into::<HtmlInputElement>().value());
|
|
110
|
+
let onblur =
|
|
111
|
+
on_value.reform(|e: FocusEvent| e.target_unchecked_into::<HtmlInputElement>().value());
|
|
112
|
+
let oninput = {
|
|
113
|
+
let update_on_input = props.update_on_input;
|
|
114
|
+
let on_value = on_value.clone();
|
|
115
|
+
Callback::from(move |e: InputEvent| {
|
|
174
116
|
if update_on_input {
|
|
175
|
-
|
|
176
|
-
vec![EditableHeaderMsg::SetNewValue(value)]
|
|
177
|
-
} else {
|
|
178
|
-
vec![]
|
|
117
|
+
on_value.emit(e.target_unchecked_into::<HtmlInputElement>().value());
|
|
179
118
|
}
|
|
180
|
-
})
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
119
|
+
})
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
let onclick = {
|
|
123
|
+
let noderef = noderef.clone();
|
|
124
|
+
Callback::from(move |_: MouseEvent| {
|
|
125
|
+
if let Some(input) = noderef.cast::<HtmlInputElement>() {
|
|
126
|
+
let _ = input.focus();
|
|
127
|
+
}
|
|
128
|
+
})
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
html! {
|
|
132
|
+
<div class={classes} {onclick}>
|
|
133
|
+
if let Some(icon) = props.icon_type { <TypeIcon ty={icon} /> }
|
|
134
|
+
<input
|
|
135
|
+
ref={noderef}
|
|
136
|
+
type="search"
|
|
137
|
+
class="sidebar_header_title"
|
|
138
|
+
disabled={!props.editable}
|
|
139
|
+
{onblur}
|
|
140
|
+
{onkeyup}
|
|
141
|
+
{oninput}
|
|
142
|
+
value={props.value.clone()}
|
|
143
|
+
{placeholder}
|
|
144
|
+
/>
|
|
145
|
+
</div>
|
|
198
146
|
}
|
|
199
147
|
}
|
|
200
|
-
|
|
201
|
-
#[derive(Default, Debug, PartialEq, Copy, Clone)]
|
|
202
|
-
pub enum ValueState {
|
|
203
|
-
#[default]
|
|
204
|
-
Unedited,
|
|
205
|
-
Edited,
|
|
206
|
-
}
|
|
@@ -16,7 +16,7 @@ use perspective_client::{ExprValidationError, clone};
|
|
|
16
16
|
use yew::prelude::*;
|
|
17
17
|
|
|
18
18
|
use super::form::code_editor::*;
|
|
19
|
-
use crate::session::{Session,
|
|
19
|
+
use crate::session::{Session, SessionMetadataRc};
|
|
20
20
|
use crate::tasks::{ExprValidation, validate_expression};
|
|
21
21
|
|
|
22
22
|
#[derive(Properties, PartialEq, Clone)]
|
|
@@ -27,6 +27,9 @@ pub struct ExpressionEditorProps {
|
|
|
27
27
|
pub alias: Option<String>,
|
|
28
28
|
pub disabled: bool,
|
|
29
29
|
|
|
30
|
+
/// The saved expression text this editor edits against.
|
|
31
|
+
pub initial_expr: Rc<String>,
|
|
32
|
+
|
|
30
33
|
#[prop_or_default]
|
|
31
34
|
pub reset_count: u8,
|
|
32
35
|
|
|
@@ -69,7 +72,7 @@ impl Component for ExpressionEditor {
|
|
|
69
72
|
|
|
70
73
|
fn create(ctx: &Context<Self>) -> Self {
|
|
71
74
|
let oninput = ctx.link().callback(ExpressionEditorMsg::SetExpr);
|
|
72
|
-
let expr =
|
|
75
|
+
let expr = ctx.props().initial_expr.clone();
|
|
73
76
|
ctx.link()
|
|
74
77
|
.send_message(Self::Message::SetExpr(expr.clone()));
|
|
75
78
|
|
|
@@ -156,24 +159,14 @@ impl Component for ExpressionEditor {
|
|
|
156
159
|
fn changed(&mut self, ctx: &Context<Self>, old_props: &Self::Properties) -> bool {
|
|
157
160
|
if ctx.props().alias != old_props.alias
|
|
158
161
|
|| ctx.props().reset_count != old_props.reset_count
|
|
159
|
-
||
|
|
162
|
+
|| ctx.props().initial_expr != old_props.initial_expr
|
|
160
163
|
{
|
|
161
|
-
ctx.link()
|
|
162
|
-
.
|
|
163
|
-
|
|
164
|
-
&ctx.props().alias,
|
|
165
|
-
)));
|
|
164
|
+
ctx.link().send_message(ExpressionEditorMsg::SetExpr(
|
|
165
|
+
ctx.props().initial_expr.clone(),
|
|
166
|
+
));
|
|
166
167
|
false
|
|
167
168
|
} else {
|
|
168
169
|
true
|
|
169
170
|
}
|
|
170
171
|
}
|
|
171
172
|
}
|
|
172
|
-
|
|
173
|
-
fn initial_expr(metadata: &SessionMetadata, alias: &Option<String>) -> Rc<String> {
|
|
174
|
-
alias
|
|
175
|
-
.as_ref()
|
|
176
|
-
.and_then(|alias| metadata.get_expression_by_alias(alias))
|
|
177
|
-
.unwrap_or_default()
|
|
178
|
-
.into()
|
|
179
|
-
}
|
|
@@ -45,6 +45,9 @@ pub struct CodeEditorProps {
|
|
|
45
45
|
#[prop_or_default]
|
|
46
46
|
pub error: Option<ExprValidationError>,
|
|
47
47
|
|
|
48
|
+
#[prop_or_default]
|
|
49
|
+
pub contained: bool,
|
|
50
|
+
|
|
48
51
|
/// Selected theme name, threaded for PortalModal consumers.
|
|
49
52
|
#[prop_or_default]
|
|
50
53
|
pub theme: String,
|
|
@@ -165,20 +168,27 @@ pub fn code_editor(props: &CodeEditorProps) -> Html {
|
|
|
165
168
|
))
|
|
166
169
|
.collect::<Html>();
|
|
167
170
|
|
|
168
|
-
let
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
171
|
+
let mut outer_class = classes!("scrollable");
|
|
172
|
+
let mut inner_class = classes!("editor-inner", "scrollable");
|
|
173
|
+
|
|
174
|
+
if props.wordwrap {
|
|
175
|
+
outer_class.push("wordwrap");
|
|
176
|
+
inner_class.push("wordwrap");
|
|
172
177
|
};
|
|
178
|
+
|
|
179
|
+
if props.contained {
|
|
180
|
+
outer_class.push("contained")
|
|
181
|
+
}
|
|
182
|
+
|
|
173
183
|
clone!(props.disabled);
|
|
174
184
|
html! {
|
|
175
185
|
<>
|
|
176
|
-
<div id="editor" {
|
|
186
|
+
<div id="editor" class={outer_class}>
|
|
177
187
|
<div id="line_numbers" ref={lineno_ref}>{ line_numbers }</div>
|
|
178
188
|
<MirroredTextarea
|
|
179
189
|
id="textarea_editable"
|
|
180
190
|
mirror_id="content"
|
|
181
|
-
class={
|
|
191
|
+
class={inner_class}
|
|
182
192
|
mirror={terms}
|
|
183
193
|
{textarea_ref}
|
|
184
194
|
mirror_ref={content_ref}
|
|
@@ -17,14 +17,16 @@
|
|
|
17
17
|
//! parent module directly.
|
|
18
18
|
|
|
19
19
|
pub mod code_editor;
|
|
20
|
-
pub mod color_range_selector;
|
|
21
20
|
pub mod color_selector;
|
|
22
21
|
pub mod debug;
|
|
23
22
|
pub mod mirrored_textarea;
|
|
23
|
+
pub mod multi_stop_gradient_selector;
|
|
24
|
+
pub mod named_value_picker;
|
|
24
25
|
pub mod number_field;
|
|
25
26
|
pub mod number_input;
|
|
26
27
|
pub mod number_range_field;
|
|
27
28
|
pub mod optional_field;
|
|
29
|
+
pub mod palette_selector;
|
|
28
30
|
pub mod select_enum_field;
|
|
29
31
|
pub mod select_value_field;
|
|
30
32
|
|