@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
|
@@ -0,0 +1,440 @@
|
|
|
1
|
+
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
|
2
|
+
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
|
3
|
+
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
|
4
|
+
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
|
5
|
+
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
|
6
|
+
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
|
7
|
+
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
|
8
|
+
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
|
9
|
+
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
|
10
|
+
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
|
11
|
+
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
12
|
+
|
|
13
|
+
use wasm_bindgen::JsCast;
|
|
14
|
+
use web_sys::*;
|
|
15
|
+
use yew::prelude::*;
|
|
16
|
+
|
|
17
|
+
/// The widget for [`crate::config::ControlSpec::Palette`]: an ordered row
|
|
18
|
+
/// of discrete color swatches with per-swatch edit/remove, pointer-drag
|
|
19
|
+
/// reordering, and a trailing add tile.
|
|
20
|
+
#[derive(Properties, PartialEq)]
|
|
21
|
+
pub struct PaletteProps {
|
|
22
|
+
pub values: Vec<String>,
|
|
23
|
+
pub on_change: Callback<Vec<String>>,
|
|
24
|
+
pub on_reset: Callback<()>,
|
|
25
|
+
pub is_modified: bool,
|
|
26
|
+
|
|
27
|
+
#[prop_or_default]
|
|
28
|
+
pub max: Option<usize>,
|
|
29
|
+
|
|
30
|
+
#[prop_or_default]
|
|
31
|
+
pub title: Option<String>,
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const DRAG_THRESHOLD_PX: f64 = 4.0;
|
|
35
|
+
|
|
36
|
+
#[derive(Clone, Copy)]
|
|
37
|
+
struct Press {
|
|
38
|
+
pointer_id: i32,
|
|
39
|
+
x: i32,
|
|
40
|
+
y: i32,
|
|
41
|
+
from: usize,
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
fn event_index(target: &Element) -> Option<usize> {
|
|
45
|
+
target
|
|
46
|
+
.get_attribute("data-index")
|
|
47
|
+
.and_then(|x| x.parse::<usize>().ok())
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
fn insertion_index(centers: &[(f64, f64)], x: f64, y: f64) -> usize {
|
|
51
|
+
let nearest = centers
|
|
52
|
+
.iter()
|
|
53
|
+
.enumerate()
|
|
54
|
+
.min_by(|(_, a), (_, b)| {
|
|
55
|
+
let da = (a.0 - x).hypot(a.1 - y);
|
|
56
|
+
let db = (b.0 - x).hypot(b.1 - y);
|
|
57
|
+
da.partial_cmp(&db).unwrap_or(std::cmp::Ordering::Equal)
|
|
58
|
+
})
|
|
59
|
+
.map(|(index, _)| index);
|
|
60
|
+
|
|
61
|
+
match nearest {
|
|
62
|
+
Some(index) if x < centers[index].0 => index,
|
|
63
|
+
Some(index) => index + 1,
|
|
64
|
+
None => 0,
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
fn reordered<T: Clone>(values: &[T], from: usize, insert: usize) -> Option<Vec<T>> {
|
|
69
|
+
if from >= values.len() || insert > values.len() || insert == from || insert == from + 1 {
|
|
70
|
+
return None;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
let mut next = values.to_vec();
|
|
74
|
+
let color = next.remove(from);
|
|
75
|
+
let at = if insert > from { insert - 1 } else { insert };
|
|
76
|
+
next.insert(at, color);
|
|
77
|
+
Some(next)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
fn slot_from_display(slot: usize, shown_at: usize, from: usize) -> usize {
|
|
81
|
+
let shown = if slot > shown_at { slot - 1 } else { slot };
|
|
82
|
+
match shown.cmp(&from) {
|
|
83
|
+
std::cmp::Ordering::Less => shown,
|
|
84
|
+
std::cmp::Ordering::Greater => shown + 1,
|
|
85
|
+
std::cmp::Ordering::Equal => from,
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
fn swatch_centers(container: &Element) -> Vec<(f64, f64, usize)> {
|
|
90
|
+
let Ok(nodes) = container.query_selector_all(".palette-swatch") else {
|
|
91
|
+
return vec![];
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
(0..nodes.length())
|
|
95
|
+
.filter_map(|i| nodes.get(i))
|
|
96
|
+
.filter_map(|node| node.dyn_into::<Element>().ok())
|
|
97
|
+
.filter_map(|el| {
|
|
98
|
+
let index = event_index(&el)?;
|
|
99
|
+
let rect = el.get_bounding_client_rect();
|
|
100
|
+
Some((
|
|
101
|
+
rect.left() + rect.width() / 2.0,
|
|
102
|
+
rect.top() + rect.height() / 2.0,
|
|
103
|
+
index,
|
|
104
|
+
))
|
|
105
|
+
})
|
|
106
|
+
.collect()
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
#[function_component(PaletteSelector)]
|
|
110
|
+
pub fn palette_selector(props: &PaletteProps) -> Html {
|
|
111
|
+
let oninput = use_callback(
|
|
112
|
+
(props.values.clone(), props.on_change.clone()),
|
|
113
|
+
|event: InputEvent, (values, on_change)| {
|
|
114
|
+
let input = event.target().unwrap().unchecked_into::<HtmlInputElement>();
|
|
115
|
+
|
|
116
|
+
let Some(index) = event_index(&input) else {
|
|
117
|
+
return;
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
let mut next = values.clone();
|
|
121
|
+
let Some(slot) = next.get_mut(index) else {
|
|
122
|
+
return;
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
*slot = input.value();
|
|
126
|
+
on_change.emit(next);
|
|
127
|
+
},
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
let on_remove = use_callback(
|
|
131
|
+
(props.values.clone(), props.on_change.clone()),
|
|
132
|
+
|event: MouseEvent, (values, on_change)| {
|
|
133
|
+
let Some(target) = event.target().and_then(|x| x.dyn_into::<Element>().ok()) else {
|
|
134
|
+
return;
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
let Some(index) = event_index(&target) else {
|
|
138
|
+
return;
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
if values.len() > 1 && index < values.len() {
|
|
142
|
+
let mut next = values.clone();
|
|
143
|
+
next.remove(index);
|
|
144
|
+
on_change.emit(next);
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
let swatches_ref = use_node_ref();
|
|
150
|
+
let open_index = use_mut_ref(|| None::<usize>);
|
|
151
|
+
let on_add = use_callback(
|
|
152
|
+
(
|
|
153
|
+
props.values.clone(),
|
|
154
|
+
props.on_change.clone(),
|
|
155
|
+
props.max,
|
|
156
|
+
open_index.clone(),
|
|
157
|
+
),
|
|
158
|
+
|_: MouseEvent, (values, on_change, max, open_index)| {
|
|
159
|
+
if max.map(|max| values.len() < max).unwrap_or(true)
|
|
160
|
+
&& let Some(last) = values.last()
|
|
161
|
+
{
|
|
162
|
+
let mut next = values.clone();
|
|
163
|
+
next.push(last.clone());
|
|
164
|
+
*open_index.borrow_mut() = Some(values.len());
|
|
165
|
+
on_change.emit(next);
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
{
|
|
171
|
+
let swatches_ref = swatches_ref.clone();
|
|
172
|
+
let open_index = open_index.clone();
|
|
173
|
+
use_effect(move || {
|
|
174
|
+
let Some(index) = *open_index.borrow() else {
|
|
175
|
+
return;
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
let input = swatches_ref
|
|
179
|
+
.cast::<Element>()
|
|
180
|
+
.and_then(|el| {
|
|
181
|
+
el.query_selector(&format!("input[data-index=\"{index}\"]"))
|
|
182
|
+
.ok()
|
|
183
|
+
.flatten()
|
|
184
|
+
})
|
|
185
|
+
.and_then(|el| el.dyn_into::<HtmlInputElement>().ok());
|
|
186
|
+
|
|
187
|
+
if let Some(input) = input {
|
|
188
|
+
*open_index.borrow_mut() = None;
|
|
189
|
+
let _ = input.focus();
|
|
190
|
+
if input.show_picker().is_err() {
|
|
191
|
+
input.click();
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
let press = use_mut_ref(|| None::<Press>);
|
|
198
|
+
let drag = use_state(|| None::<(usize, usize)>);
|
|
199
|
+
let suppress_click = use_mut_ref(|| false);
|
|
200
|
+
|
|
201
|
+
let on_pointerdown = {
|
|
202
|
+
let press = press.clone();
|
|
203
|
+
let suppress_click = suppress_click.clone();
|
|
204
|
+
Callback::from(move |event: PointerEvent| {
|
|
205
|
+
if event.button() != 0 {
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
let Some(target) = event.target().and_then(|x| x.dyn_into::<Element>().ok()) else {
|
|
210
|
+
return;
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
if target.class_list().contains("palette-swatch-remove") {
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
let Some(swatch) = target.closest(".palette-swatch").ok().flatten() else {
|
|
218
|
+
return;
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
let Some(from) = event_index(&swatch) else {
|
|
222
|
+
return;
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
*suppress_click.borrow_mut() = false;
|
|
226
|
+
*press.borrow_mut() = Some(Press {
|
|
227
|
+
pointer_id: event.pointer_id(),
|
|
228
|
+
x: event.client_x(),
|
|
229
|
+
y: event.client_y(),
|
|
230
|
+
from,
|
|
231
|
+
});
|
|
232
|
+
})
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
let on_pointermove = {
|
|
236
|
+
let press = press.clone();
|
|
237
|
+
let drag = drag.clone();
|
|
238
|
+
let swatches_ref = swatches_ref.clone();
|
|
239
|
+
let suppress_click = suppress_click.clone();
|
|
240
|
+
Callback::from(move |event: PointerEvent| {
|
|
241
|
+
let Some(pressed) = *press.borrow() else {
|
|
242
|
+
return;
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
if pressed.pointer_id != event.pointer_id() {
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
let Some(container) = swatches_ref.cast::<Element>() else {
|
|
250
|
+
return;
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
if drag.is_none() {
|
|
254
|
+
let dx = (event.client_x() - pressed.x) as f64;
|
|
255
|
+
let dy = (event.client_y() - pressed.y) as f64;
|
|
256
|
+
if dx.hypot(dy) < DRAG_THRESHOLD_PX {
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
let _ = container.set_pointer_capture(pressed.pointer_id);
|
|
261
|
+
*suppress_click.borrow_mut() = true;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
let shown = swatch_centers(&container);
|
|
265
|
+
let Some(shown_at) = shown
|
|
266
|
+
.iter()
|
|
267
|
+
.position(|(_, _, index)| *index == pressed.from)
|
|
268
|
+
else {
|
|
269
|
+
return;
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
let centers = shown.iter().map(|(x, y, _)| (*x, *y)).collect::<Vec<_>>();
|
|
273
|
+
let slot = insertion_index(¢ers, event.client_x() as f64, event.client_y() as f64);
|
|
274
|
+
let insert = slot_from_display(slot, shown_at, pressed.from);
|
|
275
|
+
if *drag != Some((pressed.from, insert)) {
|
|
276
|
+
drag.set(Some((pressed.from, insert)));
|
|
277
|
+
}
|
|
278
|
+
})
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
let on_pointerup = {
|
|
282
|
+
let press = press.clone();
|
|
283
|
+
let drag = drag.clone();
|
|
284
|
+
let values = props.values.clone();
|
|
285
|
+
let on_change = props.on_change.clone();
|
|
286
|
+
Callback::from(move |event: PointerEvent| {
|
|
287
|
+
let pressed = press
|
|
288
|
+
.borrow()
|
|
289
|
+
.is_some_and(|p| p.pointer_id == event.pointer_id());
|
|
290
|
+
|
|
291
|
+
if !pressed {
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
*press.borrow_mut() = None;
|
|
296
|
+
if let Some((from, insert)) = *drag {
|
|
297
|
+
drag.set(None);
|
|
298
|
+
if let Some(next) = reordered(&values, from, insert) {
|
|
299
|
+
on_change.emit(next);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
})
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
let on_pointercancel = {
|
|
306
|
+
let press = press.clone();
|
|
307
|
+
let drag = drag.clone();
|
|
308
|
+
Callback::from(move |_: PointerEvent| {
|
|
309
|
+
*press.borrow_mut() = None;
|
|
310
|
+
if drag.is_some() {
|
|
311
|
+
drag.set(None);
|
|
312
|
+
}
|
|
313
|
+
})
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
let on_swatch_click = {
|
|
317
|
+
let suppress_click = suppress_click.clone();
|
|
318
|
+
Callback::from(move |event: MouseEvent| {
|
|
319
|
+
if std::mem::replace(&mut *suppress_click.borrow_mut(), false) {
|
|
320
|
+
event.prevent_default();
|
|
321
|
+
}
|
|
322
|
+
})
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
let on_reset = use_callback(props.on_reset.clone(), |_: MouseEvent, on_reset| {
|
|
326
|
+
on_reset.emit(())
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
let drag_from = drag.map(|(from, _)| from);
|
|
330
|
+
let identity = (0..props.values.len()).collect::<Vec<_>>();
|
|
331
|
+
let display = (*drag)
|
|
332
|
+
.and_then(|(from, insert)| reordered(&identity, from, insert))
|
|
333
|
+
.unwrap_or(identity);
|
|
334
|
+
|
|
335
|
+
let can_add = props
|
|
336
|
+
.max
|
|
337
|
+
.map(|max| props.values.len() < max)
|
|
338
|
+
.unwrap_or(true);
|
|
339
|
+
let can_remove = props.values.len() > 1;
|
|
340
|
+
|
|
341
|
+
html! {
|
|
342
|
+
<>
|
|
343
|
+
<label id={props.title.as_deref().unwrap_or("palette-label").to_owned()} />
|
|
344
|
+
<div class="color-gradient-container palette-selector">
|
|
345
|
+
<div
|
|
346
|
+
class={classes!("palette-swatches", drag.is_some().then_some("reordering"))}
|
|
347
|
+
ref={swatches_ref}
|
|
348
|
+
onpointerdown={on_pointerdown}
|
|
349
|
+
onpointermove={on_pointermove}
|
|
350
|
+
onpointerup={on_pointerup}
|
|
351
|
+
onpointercancel={on_pointercancel}
|
|
352
|
+
>
|
|
353
|
+
{ for display.iter().map(|&index| html! {
|
|
354
|
+
<div
|
|
355
|
+
class={classes!(
|
|
356
|
+
"palette-swatch",
|
|
357
|
+
(drag_from == Some(index)).then_some("dragging"),
|
|
358
|
+
)}
|
|
359
|
+
key={index.to_string()}
|
|
360
|
+
data-index={index.to_string()}
|
|
361
|
+
>
|
|
362
|
+
<input
|
|
363
|
+
class="parameter"
|
|
364
|
+
type="color"
|
|
365
|
+
value={props.values[index].clone()}
|
|
366
|
+
data-index={index.to_string()}
|
|
367
|
+
oninput={oninput.clone()}
|
|
368
|
+
onclick={on_swatch_click.clone()}
|
|
369
|
+
/>
|
|
370
|
+
if can_remove {
|
|
371
|
+
<span
|
|
372
|
+
class="palette-swatch-remove"
|
|
373
|
+
data-index={index.to_string()}
|
|
374
|
+
onclick={on_remove.clone()}
|
|
375
|
+
/>
|
|
376
|
+
}
|
|
377
|
+
</div>
|
|
378
|
+
}) }
|
|
379
|
+
if can_add { <span class="palette-add" onclick={on_add} /> }
|
|
380
|
+
</div>
|
|
381
|
+
if props.is_modified {
|
|
382
|
+
<span class="reset-default-style" onclick={on_reset} />
|
|
383
|
+
} else {
|
|
384
|
+
<span class="reset-default-style-disabled" />
|
|
385
|
+
}
|
|
386
|
+
</div>
|
|
387
|
+
</>
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
#[cfg(test)]
|
|
392
|
+
mod tests {
|
|
393
|
+
use super::*;
|
|
394
|
+
|
|
395
|
+
fn v(items: &[&str]) -> Vec<String> {
|
|
396
|
+
items.iter().map(|x| x.to_string()).collect()
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
#[test]
|
|
400
|
+
fn insertion_index_splits_at_centers_and_resolves_rows() {
|
|
401
|
+
let centers = [(12.0, 12.0), (40.0, 12.0), (68.0, 12.0), (12.0, 40.0)];
|
|
402
|
+
assert_eq!(insertion_index(¢ers, 0.0, 12.0), 0);
|
|
403
|
+
assert_eq!(insertion_index(¢ers, 13.0, 12.0), 1);
|
|
404
|
+
assert_eq!(insertion_index(¢ers, 39.0, 12.0), 1);
|
|
405
|
+
assert_eq!(insertion_index(¢ers, 41.0, 12.0), 2);
|
|
406
|
+
assert_eq!(insertion_index(¢ers, 90.0, 12.0), 3);
|
|
407
|
+
assert_eq!(insertion_index(¢ers, 10.0, 41.0), 3);
|
|
408
|
+
assert_eq!(insertion_index(¢ers, 30.0, 41.0), 4);
|
|
409
|
+
assert_eq!(insertion_index(&[], 5.0, 5.0), 0);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
#[test]
|
|
413
|
+
fn slot_from_display_maps_previewed_slots_back_to_original_order() {
|
|
414
|
+
assert_eq!(slot_from_display(3, 2, 0), 3);
|
|
415
|
+
assert_eq!(slot_from_display(2, 2, 0), 3);
|
|
416
|
+
assert_eq!(slot_from_display(1, 2, 0), 2);
|
|
417
|
+
assert_eq!(slot_from_display(0, 2, 0), 0);
|
|
418
|
+
assert_eq!(slot_from_display(0, 0, 2), 0);
|
|
419
|
+
assert_eq!(slot_from_display(1, 0, 2), 0);
|
|
420
|
+
assert_eq!(slot_from_display(2, 0, 2), 1);
|
|
421
|
+
assert_eq!(slot_from_display(3, 0, 2), 2);
|
|
422
|
+
assert_eq!(slot_from_display(4, 0, 2), 4);
|
|
423
|
+
assert_eq!(slot_from_display(1, 1, 1), 1);
|
|
424
|
+
assert_eq!(slot_from_display(2, 1, 1), 1);
|
|
425
|
+
assert_eq!(slot_from_display(3, 1, 1), 3);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
#[test]
|
|
429
|
+
fn reordered_moves_and_rejects_no_ops() {
|
|
430
|
+
let values = v(&["a", "b", "c", "d"]);
|
|
431
|
+
assert_eq!(reordered(&values, 0, 4), Some(v(&["b", "c", "d", "a"])));
|
|
432
|
+
assert_eq!(reordered(&values, 3, 0), Some(v(&["d", "a", "b", "c"])));
|
|
433
|
+
assert_eq!(reordered(&values, 0, 2), Some(v(&["b", "a", "c", "d"])));
|
|
434
|
+
assert_eq!(reordered(&values, 2, 1), Some(v(&["a", "c", "b", "d"])));
|
|
435
|
+
assert_eq!(reordered(&values, 1, 1), None);
|
|
436
|
+
assert_eq!(reordered(&values, 1, 2), None);
|
|
437
|
+
assert_eq!(reordered(&values, 4, 0), None);
|
|
438
|
+
assert_eq!(reordered(&values, 0, 5), None);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
@@ -16,8 +16,8 @@ use crate::components::panel_menu::PanelCommand;
|
|
|
16
16
|
pub enum MainPanelMsg {
|
|
17
17
|
PointerEvent(web_sys::PointerEvent),
|
|
18
18
|
|
|
19
|
-
///
|
|
20
|
-
///
|
|
19
|
+
/// A panel's `PanelPhase` changed (`staged_changed` PubSub — see
|
|
20
|
+
/// `Workspace::insert_panel`/`promote`): re-render so cells/tabs/wrappers
|
|
21
21
|
/// reflect it and `reconcile` inserts a promoted panel. Subscribed on
|
|
22
22
|
/// MainPanel's OWN scope — the root's `LayoutChanged` delivery proved
|
|
23
23
|
/// unreliable from element-API task contexts (promoted panels stranded
|
|
@@ -41,7 +41,7 @@ const LAYOUT_PHYSICS: LayoutPhysics = LayoutPhysics {
|
|
|
41
41
|
|
|
42
42
|
impl MainPanel {
|
|
43
43
|
/// Size each STAGED panel's hidden wrapper (`.psp-staging` — see
|
|
44
|
-
/// `MainPanel::render` and `
|
|
44
|
+
/// `MainPanel::render` and `PanelPhase::Staging`) to its PREDICTED
|
|
45
45
|
/// post-insert cell: an equal share of the layout box's width (the
|
|
46
46
|
/// reconcile insert splits the root horizontally with equal
|
|
47
47
|
/// redistribution) minus the frame-chrome fallback the presize sweep
|
|
@@ -139,7 +139,7 @@ impl MainPanel {
|
|
|
139
139
|
let layout: RegularLayout = el.unchecked_into();
|
|
140
140
|
let panel_ids = &ctx.props().panel_ids;
|
|
141
141
|
|
|
142
|
-
//
|
|
142
|
+
// `restoreWorkspace` stages its saved layout tree on the Workspace
|
|
143
143
|
// (the model; regular-layout is a slave view). Apply it here, BEFORE
|
|
144
144
|
// the insert reconcile, and seed `inserted` from its panel names — so
|
|
145
145
|
// restored panels mount directly at their saved positions in ONE
|
|
@@ -165,8 +165,8 @@ impl MainPanel {
|
|
|
165
165
|
|
|
166
166
|
// STAGED panels are withheld from the layout: their first draw
|
|
167
167
|
// is completing in the hidden staging wrapper (see
|
|
168
|
-
// `
|
|
169
|
-
//
|
|
168
|
+
// `PanelPhase::Staging`). The promote re-render flips the phase,
|
|
169
|
+
// and this loop then inserts the already-drawn panel.
|
|
170
170
|
if ctx.props().workspace.is_staged(id) {
|
|
171
171
|
continue;
|
|
172
172
|
}
|
|
@@ -20,7 +20,7 @@ use perspective_client::config::ViewConfig;
|
|
|
20
20
|
use yew::prelude::*;
|
|
21
21
|
|
|
22
22
|
use crate::components::column_settings_sidebar::style_tab::primitive_field::{
|
|
23
|
-
BoolField, ColorField,
|
|
23
|
+
BoolField, ColorField, EnumField, NumberFieldPrimitive,
|
|
24
24
|
};
|
|
25
25
|
use crate::config::ControlSpec;
|
|
26
26
|
use crate::queries::get_plugin_config_schema;
|
|
@@ -143,32 +143,6 @@ pub fn PluginTab(props: &PluginTabProps) -> Html {
|
|
|
143
143
|
/>
|
|
144
144
|
}
|
|
145
145
|
},
|
|
146
|
-
ControlSpec::ColorRange {
|
|
147
|
-
key_pos,
|
|
148
|
-
key_neg,
|
|
149
|
-
default_pos,
|
|
150
|
-
default_neg,
|
|
151
|
-
is_gradient,
|
|
152
|
-
} => {
|
|
153
|
-
let current_pos = raw_config
|
|
154
|
-
.get(&key_pos)
|
|
155
|
-
.and_then(|v| v.as_str().map(|s| s.to_string()));
|
|
156
|
-
let current_neg = raw_config
|
|
157
|
-
.get(&key_neg)
|
|
158
|
-
.and_then(|v| v.as_str().map(|s| s.to_string()));
|
|
159
|
-
html! {
|
|
160
|
-
<ColorRangeField
|
|
161
|
-
field_key_pos={key_pos}
|
|
162
|
-
field_key_neg={key_neg}
|
|
163
|
-
{default_pos}
|
|
164
|
-
{default_neg}
|
|
165
|
-
{current_pos}
|
|
166
|
-
{current_neg}
|
|
167
|
-
{is_gradient}
|
|
168
|
-
on_change={on_change.clone()}
|
|
169
|
-
/>
|
|
170
|
-
}
|
|
171
|
-
},
|
|
172
146
|
ControlSpec::Number {
|
|
173
147
|
key,
|
|
174
148
|
default,
|
|
@@ -199,7 +173,9 @@ pub fn PluginTab(props: &PluginTabProps) -> Html {
|
|
|
199
173
|
| ControlSpec::StringFormat
|
|
200
174
|
| ControlSpec::Symbols { .. }
|
|
201
175
|
| ControlSpec::NumberFormat
|
|
202
|
-
| ControlSpec::String { .. }
|
|
176
|
+
| ControlSpec::String { .. }
|
|
177
|
+
| ControlSpec::Palette { .. }
|
|
178
|
+
| ControlSpec::GradientStops { .. } => {
|
|
203
179
|
return None;
|
|
204
180
|
},
|
|
205
181
|
};
|
|
@@ -22,7 +22,8 @@ use super::plugin_tab::PluginTab;
|
|
|
22
22
|
use crate::components::containers::sidebar_close_button::SidebarCloseButton;
|
|
23
23
|
use crate::components::form::debug::DebugPanel;
|
|
24
24
|
use crate::config::{PluginStaticConfig, PluginUpdate};
|
|
25
|
-
use crate::presentation::{ColumnLocator, OpenColumnSettings, Presentation};
|
|
25
|
+
use crate::presentation::{ColumnLocator, ColumnSettingsTarget, OpenColumnSettings, Presentation};
|
|
26
|
+
use crate::queries::classify_column;
|
|
26
27
|
use crate::renderer::*;
|
|
27
28
|
use crate::session::column_defaults_update::*;
|
|
28
29
|
use crate::session::*;
|
|
@@ -34,7 +35,7 @@ use crate::workspace::Workspace;
|
|
|
34
35
|
pub struct SettingsPanelProps {
|
|
35
36
|
pub on_close: Callback<()>,
|
|
36
37
|
pub on_resize: Rc<PubSub<()>>,
|
|
37
|
-
pub on_select_column: Callback<Option<
|
|
38
|
+
pub on_select_column: Callback<Option<ColumnSettingsTarget>>,
|
|
38
39
|
pub on_debug: Callback<()>,
|
|
39
40
|
pub is_debug: bool,
|
|
40
41
|
|
|
@@ -145,25 +146,28 @@ pub fn SettingsPanel(props: &SettingsPanelProps) -> Html {
|
|
|
145
146
|
} = &props;
|
|
146
147
|
|
|
147
148
|
let selected_column = {
|
|
148
|
-
let locator = props.open_column_settings.locator.clone();
|
|
149
149
|
let config = &props.view_config;
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
.
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
150
|
+
props
|
|
151
|
+
.open_column_settings
|
|
152
|
+
.target
|
|
153
|
+
.as_ref()
|
|
154
|
+
.and_then(|target| match target {
|
|
155
|
+
ColumnSettingsTarget::NewExpression => Some(ColumnLocator::NewExpression),
|
|
156
|
+
ColumnSettingsTarget::Column(n) => {
|
|
157
|
+
let locator = classify_column(n, config, &props.metadata)?;
|
|
158
|
+
if !matches!(locator, ColumnLocator::Table(_)) {
|
|
159
|
+
return Some(locator);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
let used = config.columns.iter().any(|maybe_col| {
|
|
163
|
+
maybe_col.as_ref().map(|col| col == n).unwrap_or_default()
|
|
164
|
+
}) || config.group_by.iter().any(|col| col == n)
|
|
165
|
+
|| config.split_by.iter().any(|col| col == n)
|
|
166
|
+
|| config.filter.iter().any(|col| col.column() == n)
|
|
167
|
+
|| config.sort.iter().any(|col| &col.0 == n);
|
|
168
|
+
(used && props.renderer.can_render_column_styles()).then_some(locator)
|
|
169
|
+
},
|
|
170
|
+
})
|
|
167
171
|
};
|
|
168
172
|
|
|
169
173
|
let plugin_name = props.plugin_name.clone();
|
|
@@ -217,6 +221,7 @@ pub fn SettingsPanel(props: &SettingsPanelProps) -> Html {
|
|
|
217
221
|
|
|
218
222
|
update.set_update_column_defaults(
|
|
219
223
|
&session_metadata,
|
|
224
|
+
&view_config,
|
|
220
225
|
&view_config.columns,
|
|
221
226
|
plugin_config,
|
|
222
227
|
);
|