@perspective-dev/viewer 5.2.0 → 5.3.1
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 +8 -8
- package/dist/cdn/perspective-viewer.js.map +3 -3
- package/dist/css/blueprint.css +1 -0
- package/dist/css/botanical.css +1 -1
- package/dist/css/dracula.css +1 -1
- package/dist/css/eggplant.css +1 -0
- 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/intl/de.css +1 -1
- package/dist/css/intl/es.css +1 -1
- package/dist/css/intl/fr.css +1 -1
- package/dist/css/intl/ja.css +1 -1
- package/dist/css/intl/pt.css +1 -1
- package/dist/css/intl/zh.css +1 -1
- package/dist/css/intl.css +1 -1
- package/dist/css/ledger.css +1 -0
- package/dist/css/monokai.css +1 -1
- package/dist/css/nord.css +1 -0
- 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/css/velvet.css +1 -0
- package/dist/docs/perspective-docs.json +1 -1
- package/dist/esm/column-format.d.ts +16 -3
- 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 +3 -3
- package/dist/esm/perspective-viewer.js +8 -8
- package/dist/esm/perspective-viewer.js.map +3 -3
- 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 +198 -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 +28 -5
- package/src/css/column-style.css +291 -31
- package/src/css/containers/tabs.css +13 -3
- package/src/css/dom/select.css +21 -9
- package/src/css/form/code-editor.css +4 -4
- package/src/css/plugin-selector.css +1 -0
- package/src/css/plugin-settings-panel.css +72 -0
- package/src/css/viewer.css +0 -5
- package/src/rust/agent/tools.rs +53 -19
- 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 +330 -234
- package/src/rust/components/column_settings_sidebar.rs +88 -69
- package/src/rust/components/containers/control_group.rs +91 -0
- package/src/rust/components/containers/mod.rs +1 -0
- package/src/rust/components/containers/select.rs +8 -2
- package/src/rust/components/datetime_column_style/simple.rs +15 -4
- package/src/rust/components/datetime_column_style.rs +50 -10
- 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 +126 -112
- package/src/rust/components/settings_panel.rs +26 -20
- package/src/rust/components/status_bar.rs +0 -1
- package/src/rust/components/style_controls/number_string_format/digits_section.rs +22 -10
- package/src/rust/components/style_controls/number_string_format/misc_section.rs +6 -2
- package/src/rust/components/style_controls/number_string_format/style_section.rs +1 -0
- package/src/rust/components/style_controls/number_string_format/types.rs +24 -0
- package/src/rust/components/style_controls/number_string_format.rs +37 -14
- 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 +435 -45
- package/src/rust/config/css_scale.rs +882 -0
- package/src/rust/config/datetime_column_style.rs +4 -21
- package/src/rust/config/mod.rs +2 -0
- package/src/rust/config/number_string_format.rs +211 -18
- 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 +209 -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 +50 -12
- package/src/rust/renderer/plugin_config.rs +362 -53
- 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/blueprint.css +190 -0
- package/src/themes/botanical.css +14 -17
- package/src/themes/defaults.css +4 -110
- package/src/themes/dracula.css +19 -14
- package/src/themes/eggplant.css +189 -0
- package/src/themes/gruvbox-dark.css +23 -15
- package/src/themes/gruvbox.css +11 -1
- package/src/themes/icons.css +2 -2
- package/src/themes/intl/de.css +23 -1
- package/src/themes/intl/es.css +23 -1
- package/src/themes/intl/fr.css +23 -1
- package/src/themes/intl/ja.css +23 -1
- package/src/themes/intl/pt.css +23 -1
- package/src/themes/intl/zh.css +23 -1
- package/src/themes/intl.css +26 -4
- package/src/themes/ledger.css +195 -0
- package/src/themes/monokai.css +24 -14
- package/src/themes/nord.css +186 -0
- package/src/themes/phosphor.css +7 -14
- package/src/themes/pro-dark.css +12 -16
- package/src/themes/pro.css +118 -0
- package/src/themes/solarized-dark.css +9 -16
- package/src/themes/solarized.css +7 -0
- package/src/themes/themes.css +10 -5
- package/src/themes/vaporwave.css +22 -14
- package/src/themes/velvet.css +192 -0
- package/src/ts/column-format.ts +52 -2
- 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/src/svg/dropdown-selector-light.svg +0 -3
- /package/dist/wasm/snippets/{perspective-viewer-02e461f3d7062b26 → perspective-viewer-87bd21b215d0631a}/inline0.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-02e461f3d7062b26 → perspective-viewer-87bd21b215d0631a}/inline1.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-02e461f3d7062b26 → perspective-viewer-87bd21b215d0631a}/inline2.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-02e461f3d7062b26 → perspective-viewer-87bd21b215d0631a}/inline3.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-02e461f3d7062b26 → perspective-viewer-87bd21b215d0631a}/inline4.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-02e461f3d7062b26 → perspective-viewer-87bd21b215d0631a}/inline5.js +0 -0
|
@@ -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}
|