@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
|
@@ -29,7 +29,7 @@ use std::rc::Rc;
|
|
|
29
29
|
pub use column_selector_column_row::*;
|
|
30
30
|
pub use empty_column::*;
|
|
31
31
|
pub use invalid_column::*;
|
|
32
|
-
use perspective_client::config::ViewConfig;
|
|
32
|
+
use perspective_client::config::{ViewConfig, ViewConfigUpdate};
|
|
33
33
|
pub use pivot_column::*;
|
|
34
34
|
use web_sys::*;
|
|
35
35
|
use yew::prelude::*;
|
|
@@ -43,8 +43,10 @@ use super::containers::split_panel::{Orientation, SplitPanel};
|
|
|
43
43
|
use crate::components::column_dropdown::{ColumnDropDownElement, ColumnDropDownPortal};
|
|
44
44
|
use crate::components::containers::scroll_panel_item::ScrollPanelItem;
|
|
45
45
|
use crate::config::PluginStaticConfig;
|
|
46
|
-
use crate::presentation::{ColumnLocator, DragDropContainer, Presentation};
|
|
47
|
-
use crate::queries::{
|
|
46
|
+
use crate::presentation::{ColumnLocator, ColumnSettingsTarget, DragDropContainer, Presentation};
|
|
47
|
+
use crate::queries::{
|
|
48
|
+
ActiveColumnState, ActiveColumnStateData, ColumnsIteratorSet, get_current_column_locator,
|
|
49
|
+
};
|
|
48
50
|
use crate::renderer::*;
|
|
49
51
|
use crate::session::drag_drop_update::*;
|
|
50
52
|
use crate::session::*;
|
|
@@ -54,7 +56,7 @@ use crate::utils::*;
|
|
|
54
56
|
#[derive(Properties)]
|
|
55
57
|
pub struct ColumnSelectorProps {
|
|
56
58
|
/// Fires when the expression/config column is open.
|
|
57
|
-
pub on_open_expr_panel: Callback<
|
|
59
|
+
pub on_open_expr_panel: Callback<ColumnSettingsTarget>,
|
|
58
60
|
|
|
59
61
|
/// This is passed to the add_expression_button for styling.
|
|
60
62
|
pub selected_column: Option<ColumnLocator>,
|
|
@@ -137,6 +139,32 @@ pub struct ColumnSelector {
|
|
|
137
139
|
on_reset: Rc<PubSub<()>>,
|
|
138
140
|
}
|
|
139
141
|
|
|
142
|
+
fn close_column_settings_if_displaced(
|
|
143
|
+
presentation: &Presentation,
|
|
144
|
+
renderer: &Renderer,
|
|
145
|
+
metadata: &SessionMetadata,
|
|
146
|
+
view_config: &ViewConfig,
|
|
147
|
+
update: &ViewConfigUpdate,
|
|
148
|
+
) {
|
|
149
|
+
let Some(columns) = &update.columns else {
|
|
150
|
+
return;
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
let ocs = presentation.get_open_column_settings();
|
|
154
|
+
if get_current_column_locator(&ocs, renderer, view_config, metadata).is_none() {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
let next_config = ViewConfig {
|
|
159
|
+
columns: columns.clone(),
|
|
160
|
+
..view_config.clone()
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
if get_current_column_locator(&ocs, renderer, &next_config, metadata).is_none() {
|
|
164
|
+
presentation.set_open_column_settings(None);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
140
168
|
impl Component for ColumnSelector {
|
|
141
169
|
type Message = ColumnSelectorMsg;
|
|
142
170
|
type Properties = ColumnSelectorProps;
|
|
@@ -224,6 +252,14 @@ impl Component for ColumnSelector {
|
|
|
224
252
|
ctx.props().metadata.get_features().unwrap(),
|
|
225
253
|
);
|
|
226
254
|
|
|
255
|
+
close_column_settings_if_displaced(
|
|
256
|
+
&ctx.props().presentation,
|
|
257
|
+
&ctx.props().renderer,
|
|
258
|
+
&ctx.props().metadata,
|
|
259
|
+
&ctx.props().view_config,
|
|
260
|
+
&update,
|
|
261
|
+
);
|
|
262
|
+
|
|
227
263
|
let session = ctx.props().session.clone();
|
|
228
264
|
let renderer = ctx.props().renderer.clone();
|
|
229
265
|
if let Ok(task) = apply_and_render(&session, &renderer, update) {
|
|
@@ -249,6 +285,14 @@ impl Component for ColumnSelector {
|
|
|
249
285
|
ctx.props().metadata.get_features().unwrap(),
|
|
250
286
|
);
|
|
251
287
|
|
|
288
|
+
close_column_settings_if_displaced(
|
|
289
|
+
&ctx.props().presentation,
|
|
290
|
+
&ctx.props().renderer,
|
|
291
|
+
&ctx.props().metadata,
|
|
292
|
+
&ctx.props().view_config,
|
|
293
|
+
&update,
|
|
294
|
+
);
|
|
295
|
+
|
|
252
296
|
let session = ctx.props().session.clone();
|
|
253
297
|
let renderer = ctx.props().renderer.clone();
|
|
254
298
|
if let Ok(task) = apply_and_render(&session, &renderer, update) {
|
|
@@ -17,20 +17,27 @@
|
|
|
17
17
|
//! [`ColorSelector`]) so that they visually match the rich Yew widgets in
|
|
18
18
|
//! the same sidebar.
|
|
19
19
|
|
|
20
|
+
use std::collections::BTreeMap;
|
|
20
21
|
use std::rc::Rc;
|
|
21
22
|
|
|
22
23
|
use itertools::Itertools;
|
|
23
24
|
use serde_json::Value;
|
|
24
25
|
use wasm_bindgen::JsCast;
|
|
25
26
|
use web_sys::{HtmlInputElement, MouseEvent};
|
|
26
|
-
use yew::{Callback, Html, Properties,
|
|
27
|
+
use yew::{Callback, Html, Properties, function_component, html, use_callback};
|
|
27
28
|
|
|
28
29
|
use crate::components::containers::select::{Select, SelectItem};
|
|
29
|
-
use crate::components::form::color_range_selector::ColorRangeSelector;
|
|
30
30
|
use crate::components::form::color_selector::ColorSelector;
|
|
31
|
+
use crate::components::form::multi_stop_gradient_selector::MultiStopGradientSelector;
|
|
32
|
+
use crate::components::form::named_value_picker::NamedValuePicker;
|
|
31
33
|
use crate::components::form::number_field::NumberField;
|
|
32
34
|
use crate::components::form::optional_field::OptionalField;
|
|
33
|
-
use crate::
|
|
35
|
+
use crate::components::form::palette_selector::PaletteSelector;
|
|
36
|
+
use crate::config::{
|
|
37
|
+
ColumnConfigFieldUpdate, CssColor, CssGradient, CssKind, CssPalette, EnumVariant,
|
|
38
|
+
GradientStopSpec, NamedValue, canonicalize_css_color, discrete_pair, gradient_to_css,
|
|
39
|
+
palette_name_for,
|
|
40
|
+
};
|
|
34
41
|
|
|
35
42
|
fn emit(on_change: &Callback<ColumnConfigFieldUpdate>, key: &str, value: Option<Value>) {
|
|
36
43
|
let mut map = serde_json::Map::new();
|
|
@@ -44,28 +51,6 @@ fn emit(on_change: &Callback<ColumnConfigFieldUpdate>, key: &str, value: Option<
|
|
|
44
51
|
});
|
|
45
52
|
}
|
|
46
53
|
|
|
47
|
-
fn emit_color_range(
|
|
48
|
-
on_change: &Callback<ColumnConfigFieldUpdate>,
|
|
49
|
-
key_pos: &str,
|
|
50
|
-
key_neg: &str,
|
|
51
|
-
default_pos: &str,
|
|
52
|
-
default_neg: &str,
|
|
53
|
-
new_pos: &str,
|
|
54
|
-
new_neg: &str,
|
|
55
|
-
) {
|
|
56
|
-
let mut value = serde_json::Map::new();
|
|
57
|
-
if new_pos != default_pos {
|
|
58
|
-
value.insert(key_pos.to_owned(), Value::String(new_pos.to_owned()));
|
|
59
|
-
}
|
|
60
|
-
if new_neg != default_neg {
|
|
61
|
-
value.insert(key_neg.to_owned(), Value::String(new_neg.to_owned()));
|
|
62
|
-
}
|
|
63
|
-
on_change.emit(ColumnConfigFieldUpdate {
|
|
64
|
-
keys: vec![key_pos.to_owned(), key_neg.to_owned()],
|
|
65
|
-
value,
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
|
|
69
54
|
#[derive(Properties, PartialEq)]
|
|
70
55
|
pub struct EnumFieldProps {
|
|
71
56
|
pub field_key: String,
|
|
@@ -239,106 +224,242 @@ pub fn NumberFieldPrimitive(props: &NumberFieldPrimitiveProps) -> Html {
|
|
|
239
224
|
}
|
|
240
225
|
}
|
|
241
226
|
|
|
227
|
+
#[allow(clippy::too_many_arguments)]
|
|
228
|
+
fn css_field_controls(
|
|
229
|
+
kind: CssKind,
|
|
230
|
+
named: &Rc<Vec<NamedValue>>,
|
|
231
|
+
restored: &BTreeMap<String, String>,
|
|
232
|
+
current: &str,
|
|
233
|
+
is_modified: bool,
|
|
234
|
+
on_select: Callback<String>,
|
|
235
|
+
on_pin: Callback<()>,
|
|
236
|
+
) -> Html {
|
|
237
|
+
let can_pin = is_modified && palette_name_for(restored, kind, current).is_none();
|
|
238
|
+
if named.is_empty() && !can_pin {
|
|
239
|
+
return html! {};
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
html! { <NamedValuePicker {kind} entries={named.clone()} {on_select} {can_pin} {on_pin} /> }
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
fn named_literal(named: &[NamedValue], name: &str) -> Option<String> {
|
|
246
|
+
named
|
|
247
|
+
.iter()
|
|
248
|
+
.find(|entry| entry.name == name)
|
|
249
|
+
.map(|entry| entry.value.clone())
|
|
250
|
+
}
|
|
251
|
+
|
|
242
252
|
#[derive(Properties, PartialEq)]
|
|
243
|
-
pub struct
|
|
244
|
-
pub
|
|
245
|
-
|
|
246
|
-
pub
|
|
247
|
-
pub
|
|
248
|
-
|
|
249
|
-
pub
|
|
250
|
-
pub is_gradient: bool,
|
|
253
|
+
pub struct PaletteFieldProps {
|
|
254
|
+
pub field_key: String,
|
|
255
|
+
/// Canonical palette string.
|
|
256
|
+
pub default: String,
|
|
257
|
+
pub max: Option<usize>,
|
|
258
|
+
/// Canonical palette string.
|
|
259
|
+
pub current: Option<String>,
|
|
251
260
|
pub on_change: Callback<ColumnConfigFieldUpdate>,
|
|
261
|
+
|
|
262
|
+
/// Named palettes (workspace set ∪ theme), for the loader.
|
|
263
|
+
#[prop_or_default]
|
|
264
|
+
pub named: Rc<Vec<NamedValue>>,
|
|
265
|
+
|
|
266
|
+
/// The restored palette — the set Pin adds to.
|
|
267
|
+
#[prop_or_default]
|
|
268
|
+
pub restored: Rc<BTreeMap<String, String>>,
|
|
269
|
+
|
|
270
|
+
/// Pin the field's `(kind, literal)` into the restored palette.
|
|
271
|
+
#[prop_or_default]
|
|
272
|
+
pub on_pin: Callback<(CssKind, String)>,
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
fn palette_colors(src: &str, default: &str) -> Vec<String> {
|
|
276
|
+
let literal = |css: &str| match CssPalette::parse(css) {
|
|
277
|
+
Ok(CssPalette::Literal(colors)) => Some(colors),
|
|
278
|
+
_ => None,
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
literal(src)
|
|
282
|
+
.or_else(|| literal(default))
|
|
283
|
+
.unwrap_or_default()
|
|
252
284
|
}
|
|
253
285
|
|
|
254
286
|
#[function_component]
|
|
255
|
-
pub fn
|
|
256
|
-
let
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
.
|
|
262
|
-
.clone()
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
let
|
|
273
|
-
(
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
)
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
&new_pos,
|
|
289
|
-
neg,
|
|
290
|
-
);
|
|
291
|
-
},
|
|
292
|
-
);
|
|
287
|
+
pub fn PaletteField(props: &PaletteFieldProps) -> Html {
|
|
288
|
+
let current = props.current.as_deref().unwrap_or(&props.default);
|
|
289
|
+
let values = palette_colors(current, &props.default);
|
|
290
|
+
let is_modified = props.current.is_some() && props.current.as_ref() != Some(&props.default);
|
|
291
|
+
let emit_css = {
|
|
292
|
+
let key = props.field_key.clone();
|
|
293
|
+
let default = props.default.clone();
|
|
294
|
+
let on_change = props.on_change.clone();
|
|
295
|
+
move |css: String| {
|
|
296
|
+
if css == default {
|
|
297
|
+
emit(&on_change, &key, None);
|
|
298
|
+
} else {
|
|
299
|
+
emit(&on_change, &key, Some(Value::String(css)));
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
let on_change_palette = {
|
|
305
|
+
let emit_css = emit_css.clone();
|
|
306
|
+
Callback::from(move |values: Vec<String>| {
|
|
307
|
+
emit_css(CssPalette::Literal(values).to_css());
|
|
308
|
+
})
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
let on_select = {
|
|
312
|
+
let emit_css = emit_css.clone();
|
|
313
|
+
let named = props.named.clone();
|
|
314
|
+
Callback::from(move |name: String| {
|
|
315
|
+
if let Some(literal) = named_literal(&named, &name) {
|
|
316
|
+
emit_css(literal);
|
|
317
|
+
}
|
|
318
|
+
})
|
|
319
|
+
};
|
|
293
320
|
|
|
294
|
-
let
|
|
295
|
-
(
|
|
296
|
-
|
|
297
|
-
props.field_key_neg.clone(),
|
|
298
|
-
props.default_pos.clone(),
|
|
299
|
-
props.default_neg.clone(),
|
|
300
|
-
props.on_change.clone(),
|
|
301
|
-
pos.clone(),
|
|
302
|
-
),
|
|
303
|
-
|new_neg: String, (key_pos, key_neg, default_pos, default_neg, on_change, pos)| {
|
|
304
|
-
emit_color_range(
|
|
305
|
-
on_change,
|
|
306
|
-
key_pos,
|
|
307
|
-
key_neg,
|
|
308
|
-
default_pos,
|
|
309
|
-
default_neg,
|
|
310
|
-
pos,
|
|
311
|
-
&new_neg,
|
|
312
|
-
);
|
|
313
|
-
},
|
|
321
|
+
let on_reset = use_callback(
|
|
322
|
+
(props.field_key.clone(), props.on_change.clone()),
|
|
323
|
+
|_: (), (key, on_change)| emit(on_change, key, None),
|
|
314
324
|
);
|
|
315
325
|
|
|
326
|
+
let on_pin = {
|
|
327
|
+
let on_pin = props.on_pin.clone();
|
|
328
|
+
let literal = current.to_owned();
|
|
329
|
+
Callback::from(move |()| on_pin.emit((CssKind::Palette, literal.clone())))
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
html! {
|
|
333
|
+
<div class="row">
|
|
334
|
+
{ css_field_controls(
|
|
335
|
+
CssKind::Palette,
|
|
336
|
+
&props.named,
|
|
337
|
+
&props.restored,
|
|
338
|
+
current,
|
|
339
|
+
is_modified,
|
|
340
|
+
on_select,
|
|
341
|
+
on_pin,
|
|
342
|
+
) }
|
|
343
|
+
<PaletteSelector
|
|
344
|
+
{values}
|
|
345
|
+
max={props.max}
|
|
346
|
+
on_change={on_change_palette}
|
|
347
|
+
{on_reset}
|
|
348
|
+
{is_modified}
|
|
349
|
+
title={Some(format!("{}-label", props.field_key))}
|
|
350
|
+
/>
|
|
351
|
+
</div>
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
#[derive(Properties, PartialEq)]
|
|
356
|
+
pub struct GradientStopsFieldProps {
|
|
357
|
+
pub field_key: String,
|
|
358
|
+
/// Canonical gradient string.
|
|
359
|
+
pub default: String,
|
|
360
|
+
pub discrete: bool,
|
|
361
|
+
/// Canonical gradient string.
|
|
362
|
+
pub current: Option<String>,
|
|
363
|
+
pub on_change: Callback<ColumnConfigFieldUpdate>,
|
|
364
|
+
|
|
365
|
+
/// Named gradients (workspace set ∪ theme), for the loader.
|
|
366
|
+
#[prop_or_default]
|
|
367
|
+
pub named: Rc<Vec<NamedValue>>,
|
|
368
|
+
|
|
369
|
+
/// The restored palette — the set Pin adds to.
|
|
370
|
+
#[prop_or_default]
|
|
371
|
+
pub restored: Rc<BTreeMap<String, String>>,
|
|
372
|
+
|
|
373
|
+
/// Pin the field's `(kind, literal)` into the restored palette.
|
|
374
|
+
#[prop_or_default]
|
|
375
|
+
pub on_pin: Callback<(CssKind, String)>,
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
fn gradient_stops(src: &str, default: &str) -> Vec<GradientStopSpec> {
|
|
379
|
+
let literal = |css: &str| match CssGradient::parse(css) {
|
|
380
|
+
Ok(CssGradient::Literal(stops)) => Some(stops),
|
|
381
|
+
_ => None,
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
literal(src)
|
|
385
|
+
.or_else(|| literal(default))
|
|
386
|
+
.unwrap_or_default()
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
#[function_component]
|
|
390
|
+
pub fn GradientStopsField(props: &GradientStopsFieldProps) -> Html {
|
|
391
|
+
let current = props.current.as_deref().unwrap_or(&props.default);
|
|
392
|
+
let stops = gradient_stops(current, &props.default);
|
|
393
|
+
let is_modified = props.current.is_some() && props.current.as_ref() != Some(&props.default);
|
|
394
|
+
let emit_css = {
|
|
395
|
+
let key = props.field_key.clone();
|
|
396
|
+
let default = props.default.clone();
|
|
397
|
+
let on_change = props.on_change.clone();
|
|
398
|
+
move |css: String| {
|
|
399
|
+
if css == default {
|
|
400
|
+
emit(&on_change, &key, None);
|
|
401
|
+
} else {
|
|
402
|
+
emit(&on_change, &key, Some(Value::String(css)));
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
let on_change_stops = {
|
|
408
|
+
let emit_css = emit_css.clone();
|
|
409
|
+
Callback::from(move |stops: Vec<GradientStopSpec>| {
|
|
410
|
+
emit_css(gradient_to_css(&stops));
|
|
411
|
+
})
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
let on_select = {
|
|
415
|
+
let emit_css = emit_css.clone();
|
|
416
|
+
let named = props.named.clone();
|
|
417
|
+
let discrete = props.discrete;
|
|
418
|
+
Callback::from(move |name: String| {
|
|
419
|
+
let Some(literal) = named_literal(&named, &name) else {
|
|
420
|
+
return;
|
|
421
|
+
};
|
|
422
|
+
|
|
423
|
+
let literal = match CssGradient::parse(&literal) {
|
|
424
|
+
Ok(CssGradient::Literal(stops)) if discrete && stops.len() > 2 => {
|
|
425
|
+
gradient_to_css(&discrete_pair(stops))
|
|
426
|
+
},
|
|
427
|
+
_ => literal,
|
|
428
|
+
};
|
|
429
|
+
|
|
430
|
+
emit_css(literal);
|
|
431
|
+
})
|
|
432
|
+
};
|
|
433
|
+
|
|
316
434
|
let on_reset = use_callback(
|
|
317
|
-
(
|
|
318
|
-
|
|
319
|
-
props.field_key_neg.clone(),
|
|
320
|
-
props.on_change.clone(),
|
|
321
|
-
),
|
|
322
|
-
|_: (), (key_pos, key_neg, on_change)| {
|
|
323
|
-
on_change.emit(ColumnConfigFieldUpdate {
|
|
324
|
-
keys: vec![key_pos.clone(), key_neg.clone()],
|
|
325
|
-
value: serde_json::Map::new(),
|
|
326
|
-
});
|
|
327
|
-
},
|
|
435
|
+
(props.field_key.clone(), props.on_change.clone()),
|
|
436
|
+
|_: (), (key, on_change)| emit(on_change, key, None),
|
|
328
437
|
);
|
|
329
438
|
|
|
439
|
+
let on_pin = {
|
|
440
|
+
let on_pin = props.on_pin.clone();
|
|
441
|
+
let literal = current.to_owned();
|
|
442
|
+
Callback::from(move |()| on_pin.emit((CssKind::Gradient, literal.clone())))
|
|
443
|
+
};
|
|
444
|
+
|
|
330
445
|
html! {
|
|
331
446
|
<div class="row">
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
447
|
+
{ css_field_controls(
|
|
448
|
+
CssKind::Gradient,
|
|
449
|
+
&props.named,
|
|
450
|
+
&props.restored,
|
|
451
|
+
current,
|
|
452
|
+
is_modified,
|
|
453
|
+
on_select,
|
|
454
|
+
on_pin,
|
|
455
|
+
) }
|
|
456
|
+
<MultiStopGradientSelector
|
|
457
|
+
{stops}
|
|
458
|
+
discrete={props.discrete}
|
|
459
|
+
on_change={on_change_stops}
|
|
340
460
|
{on_reset}
|
|
341
461
|
{is_modified}
|
|
462
|
+
title={Some(format!("{}-label", props.field_key))}
|
|
342
463
|
/>
|
|
343
464
|
</div>
|
|
344
465
|
}
|
|
@@ -347,42 +468,88 @@ pub fn ColorRangeField(props: &ColorRangeFieldProps) -> Html {
|
|
|
347
468
|
#[derive(Properties, PartialEq)]
|
|
348
469
|
pub struct ColorFieldProps {
|
|
349
470
|
pub field_key: String,
|
|
471
|
+
/// Canonical `#rrggbb`.
|
|
350
472
|
pub default: String,
|
|
473
|
+
/// Canonical `#rrggbb`.
|
|
351
474
|
pub current: Option<String>,
|
|
352
475
|
pub on_change: Callback<ColumnConfigFieldUpdate>,
|
|
476
|
+
|
|
477
|
+
/// Named colors (workspace set ∪ theme), for the loader.
|
|
478
|
+
#[prop_or_default]
|
|
479
|
+
pub named: Rc<Vec<NamedValue>>,
|
|
480
|
+
|
|
481
|
+
/// The restored palette — the set Pin adds to.
|
|
482
|
+
#[prop_or_default]
|
|
483
|
+
pub restored: Rc<BTreeMap<String, String>>,
|
|
484
|
+
|
|
485
|
+
/// Pin the field's `(kind, literal)` into the restored palette.
|
|
486
|
+
#[prop_or_default]
|
|
487
|
+
pub on_pin: Callback<(CssKind, String)>,
|
|
353
488
|
}
|
|
354
489
|
|
|
355
490
|
#[function_component]
|
|
356
491
|
pub fn ColorField(props: &ColorFieldProps) -> Html {
|
|
357
|
-
let
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
492
|
+
let current = props.current.as_deref().unwrap_or(&props.default);
|
|
493
|
+
let color = match CssColor::parse(current) {
|
|
494
|
+
Ok(CssColor::Literal(color)) => color,
|
|
495
|
+
_ => props.default.clone(),
|
|
496
|
+
};
|
|
497
|
+
|
|
361
498
|
let is_modified =
|
|
362
499
|
props.current.as_deref() != Some(props.default.as_str()) && props.current.is_some();
|
|
363
500
|
|
|
364
|
-
let
|
|
365
|
-
(
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
if value == *default {
|
|
372
|
-
emit(on_change, key, None);
|
|
501
|
+
let emit_css = {
|
|
502
|
+
let key = props.field_key.clone();
|
|
503
|
+
let default = props.default.clone();
|
|
504
|
+
let on_change = props.on_change.clone();
|
|
505
|
+
move |css: String| {
|
|
506
|
+
if css == default {
|
|
507
|
+
emit(&on_change, &key, None);
|
|
373
508
|
} else {
|
|
374
|
-
emit(on_change, key, Some(Value::String(
|
|
509
|
+
emit(&on_change, &key, Some(Value::String(css)));
|
|
375
510
|
}
|
|
376
|
-
}
|
|
377
|
-
|
|
511
|
+
}
|
|
512
|
+
};
|
|
513
|
+
|
|
514
|
+
let on_color = {
|
|
515
|
+
let emit_css = emit_css.clone();
|
|
516
|
+
Callback::from(move |value: String| {
|
|
517
|
+
emit_css(canonicalize_css_color(&value).unwrap_or(value));
|
|
518
|
+
})
|
|
519
|
+
};
|
|
520
|
+
|
|
521
|
+
let on_select = {
|
|
522
|
+
let emit_css = emit_css.clone();
|
|
523
|
+
let named = props.named.clone();
|
|
524
|
+
Callback::from(move |name: String| {
|
|
525
|
+
if let Some(literal) = named_literal(&named, &name) {
|
|
526
|
+
emit_css(literal);
|
|
527
|
+
}
|
|
528
|
+
})
|
|
529
|
+
};
|
|
378
530
|
|
|
379
531
|
let on_reset = use_callback(
|
|
380
532
|
(props.field_key.clone(), props.on_change.clone()),
|
|
381
533
|
|_: (), (key, on_change)| emit(on_change, key, None),
|
|
382
534
|
);
|
|
383
535
|
|
|
536
|
+
let on_pin = {
|
|
537
|
+
let on_pin = props.on_pin.clone();
|
|
538
|
+
let literal = current.to_owned();
|
|
539
|
+
Callback::from(move |()| on_pin.emit((CssKind::Color, literal.clone())))
|
|
540
|
+
};
|
|
541
|
+
|
|
384
542
|
html! {
|
|
385
543
|
<div class="row">
|
|
544
|
+
{ css_field_controls(
|
|
545
|
+
CssKind::Color,
|
|
546
|
+
&props.named,
|
|
547
|
+
&props.restored,
|
|
548
|
+
current,
|
|
549
|
+
is_modified,
|
|
550
|
+
on_select,
|
|
551
|
+
on_pin,
|
|
552
|
+
) }
|
|
386
553
|
<ColorSelector
|
|
387
554
|
{color}
|
|
388
555
|
{on_color}
|