@perspective-dev/viewer 5.4.0 → 5.5.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 +4 -4
- package/dist/cdn/perspective-viewer.js.map +2 -2
- package/dist/css/blueprint.css +1 -1
- package/dist/css/botanical.css +1 -1
- package/dist/css/dracula.css +1 -1
- package/dist/css/eggplant.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/intl/ar.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 -1
- package/dist/css/monokai.css +1 -1
- package/dist/css/nord.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/css/velvet.css +1 -1
- package/dist/docs/perspective-docs.json +1 -1
- package/dist/esm/perspective-viewer.d.ts +2 -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 +4 -4
- package/dist/esm/perspective-viewer.js.map +2 -2
- package/dist/esm/plugin.d.ts +9 -0
- package/dist/esm/ts-rs/AddPanelOptions.d.ts +9 -0
- package/dist/esm/ts-rs/ExportMethod.d.ts +1 -1
- package/dist/esm/ts-rs/GetViewMode.d.ts +4 -0
- package/dist/esm/ts-rs/GetViewOptions.d.ts +8 -0
- package/dist/esm/ts-rs/RestoreOptions.d.ts +6 -0
- package/dist/esm/ts-rs/RestoreWorkspaceOptions.d.ts +9 -0
- package/dist/esm/ts-rs/WorkspaceConfigUpdate.d.ts +30 -22
- package/dist/wasm/perspective-viewer.d.ts +102 -47
- package/dist/wasm/perspective-viewer.js +233 -142
- package/dist/wasm/perspective-viewer.wasm +0 -0
- package/dist/wasm/perspective-viewer.wasm.d.ts +18 -18
- package/package.json +7 -7
- package/src/css/column-selector.css +11 -3
- package/src/css/column-style.css +162 -0
- package/src/css/config-selector.css +1 -1
- package/src/css/form/debug.css +9 -0
- package/src/css/status-bar.css +11 -0
- package/src/css/ui/containers/tabs.css +2 -1
- package/src/rust/agent/tools.rs +80 -7
- package/src/rust/components/column_selector/active_column.rs +9 -0
- package/src/rust/components/column_selector.rs +11 -0
- package/src/rust/components/column_settings_sidebar/style_tab/alignment_field.rs +90 -0
- package/src/rust/components/column_settings_sidebar/style_tab/font_field.rs +267 -0
- package/src/rust/components/column_settings_sidebar/style_tab/primitive_field.rs +1 -1
- package/src/rust/components/column_settings_sidebar/style_tab.rs +68 -9
- package/src/rust/components/debug_panel.rs +107 -50
- package/src/rust/components/export_dropdown.rs +6 -0
- package/src/rust/components/main_panel/msg.rs +4 -0
- package/src/rust/components/main_panel.rs +14 -0
- package/src/rust/components/mod.rs +0 -1
- package/src/rust/components/plugin_tab.rs +57 -3
- package/src/rust/components/settings_panel.rs +6 -0
- package/src/rust/components/status_bar.rs +12 -3
- package/src/rust/components/status_indicator.rs +4 -0
- package/src/rust/components/viewer/filters.rs +2 -2
- package/src/rust/components/viewer/msg.rs +4 -0
- package/src/rust/components/viewer/panels.rs +6 -3
- package/src/rust/components/viewer/render.rs +1 -0
- package/src/rust/components/viewer/wiring.rs +14 -2
- package/src/rust/components/viewer.rs +2 -2
- package/src/rust/config/column_config_schema.rs +312 -8
- package/src/rust/config/datetime_column_style.rs +0 -11
- package/src/rust/config/export_method.rs +24 -0
- package/src/rust/config/mod.rs +0 -2
- package/src/rust/config/options.rs +50 -0
- package/src/rust/config/workspace_config.rs +69 -24
- package/src/rust/custom_elements/viewer.rs +214 -113
- package/src/rust/js/plugin.rs +3 -1
- package/src/rust/lib.rs +7 -3
- package/src/rust/queries/plugin_column_styles.rs +6 -5
- package/src/rust/renderer/plugin_config.rs +200 -41
- package/src/rust/renderer/plugin_store.rs +2 -1
- package/src/rust/renderer/props.rs +5 -0
- package/src/rust/renderer.rs +9 -0
- package/src/rust/session/metadata.rs +5 -10
- package/src/rust/session/props.rs +4 -0
- package/src/rust/session.rs +273 -195
- package/src/rust/tasks/copy_export.rs +67 -0
- package/src/rust/tasks/create_panel.rs +16 -13
- package/src/rust/tasks/pipeline.rs +26 -8
- package/src/rust/tasks/restore_panel.rs +129 -30
- package/src/rust/tasks/send_column_config.rs +3 -0
- package/src/rust/tasks/sync_update_panels.rs +148 -68
- package/src/rust/tasks/table_lifecycle.rs +7 -1
- package/src/rust/ui/containers/tab_list.rs +1 -1
- package/src/rust/ui/form/intl_label.rs +4 -23
- package/src/rust/ui/portal.rs +1 -0
- package/src/rust/utils/css_vars.rs +13 -0
- package/src/rust/{config/datetime_column_style/color_mode.rs → utils/in_flight.rs} +47 -35
- package/src/rust/utils/local_fonts.rs +395 -0
- package/src/rust/utils/mod.rs +4 -0
- package/src/rust/utils/pending_effects.rs +6 -30
- package/src/rust/workspace.rs +12 -0
- package/src/svg/status_pending.svg +3 -0
- package/src/themes/icons.css +1 -0
- package/src/themes/intl/ar.css +20 -7
- package/src/themes/intl/de.css +20 -7
- package/src/themes/intl/es.css +20 -7
- package/src/themes/intl/fr.css +20 -7
- package/src/themes/intl/ja.css +20 -7
- package/src/themes/intl/pt.css +20 -7
- package/src/themes/intl/zh.css +20 -7
- package/src/themes/intl.css +20 -7
- package/src/themes/monokai.css +1 -1
- package/src/themes/vaporwave.css +1 -1
- package/src/ts/perspective-viewer.ts +2 -0
- package/src/ts/plugin.ts +9 -0
- package/src/ts/ts-rs/AddPanelOptions.ts +10 -0
- package/src/ts/ts-rs/ExportMethod.ts +1 -1
- package/src/ts/ts-rs/{DatetimeColorMode.ts → GetViewMode.ts} +4 -1
- package/src/ts/ts-rs/GetViewOptions.ts +7 -0
- package/src/ts/ts-rs/RestoreOptions.ts +7 -1
- package/src/ts/ts-rs/RestoreWorkspaceOptions.ts +10 -0
- package/src/ts/ts-rs/WorkspaceConfigUpdate.ts +31 -20
- package/dist/esm/ts-rs/DatetimeColorMode.d.ts +0 -1
- package/dist/esm/ts-rs/FormatMode.d.ts +0 -1
- package/dist/esm/ts-rs/StringColorMode.d.ts +0 -1
- package/src/rust/components/string_column_style.rs +0 -120
- package/src/rust/config/string_column_style.rs +0 -135
- package/src/ts/ts-rs/FormatMode.ts +0 -3
- package/src/ts/ts-rs/StringColorMode.ts +0 -3
- /package/dist/wasm/snippets/{perspective-viewer-adc6f3f79141112b → perspective-viewer-6813b568219c6763}/inline0.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-adc6f3f79141112b → perspective-viewer-6813b568219c6763}/inline1.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-adc6f3f79141112b → perspective-viewer-6813b568219c6763}/inline2.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-adc6f3f79141112b → perspective-viewer-6813b568219c6763}/inline3.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-adc6f3f79141112b → perspective-viewer-6813b568219c6763}/inline4.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-adc6f3f79141112b → perspective-viewer-6813b568219c6763}/inline5.js +0 -0
|
@@ -17,7 +17,7 @@ use wasm_bindgen::prelude::*;
|
|
|
17
17
|
use crate::components::viewer::PerspectiveViewerMsg;
|
|
18
18
|
use crate::config::*;
|
|
19
19
|
use crate::renderer::*;
|
|
20
|
-
use crate::session::Session;
|
|
20
|
+
use crate::session::{Disposal, Session};
|
|
21
21
|
use crate::tasks::*;
|
|
22
22
|
use crate::workspace::PanelId;
|
|
23
23
|
use crate::*;
|
|
@@ -26,6 +26,9 @@ use crate::*;
|
|
|
26
26
|
extern "C" {
|
|
27
27
|
#[wasm_bindgen(typescript_type = "ViewerConfigUpdate")]
|
|
28
28
|
pub type JsViewerConfigUpdate;
|
|
29
|
+
|
|
30
|
+
#[wasm_bindgen(typescript_type = "WorkspaceConfigUpdate")]
|
|
31
|
+
pub type JsWorkspaceConfigUpdate;
|
|
29
32
|
}
|
|
30
33
|
|
|
31
34
|
pub type SyncUpdatePanelsResult = (
|
|
@@ -33,12 +36,12 @@ pub type SyncUpdatePanelsResult = (
|
|
|
33
36
|
std::vec::Vec<perspective_js::utils::ApiFuture<()>>,
|
|
34
37
|
);
|
|
35
38
|
|
|
36
|
-
///
|
|
37
|
-
///
|
|
38
|
-
///
|
|
39
|
+
/// Synchronously apply a `WorkspaceConfigUpdate` to the element's models
|
|
40
|
+
/// field-wise, replacing the panel set only when `panels` is present, and
|
|
41
|
+
/// return the fresh panels to restore plus the eject tasks.
|
|
39
42
|
pub fn sync_update_panels(
|
|
40
43
|
this: &PerspectiveViewerElement,
|
|
41
|
-
update:
|
|
44
|
+
update: JsWorkspaceConfigUpdate,
|
|
42
45
|
) -> ApiResult<SyncUpdatePanelsResult> {
|
|
43
46
|
let WorkspaceConfigUpdate {
|
|
44
47
|
active,
|
|
@@ -49,98 +52,175 @@ pub fn sync_update_panels(
|
|
|
49
52
|
palette,
|
|
50
53
|
} = update.into_serde_ext()?;
|
|
51
54
|
|
|
52
|
-
let palette =
|
|
53
|
-
|
|
55
|
+
let palette = match palette {
|
|
56
|
+
OptionalUpdate::Missing => None,
|
|
57
|
+
OptionalUpdate::SetDefault => Some(Default::default()),
|
|
58
|
+
OptionalUpdate::Update(palette) => {
|
|
59
|
+
Some(validate_palette(palette).map_err(|e| ApiError::from(JsValue::from_str(&e)))?)
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
|
|
54
63
|
let old_ids = this.workspace.panel_ids();
|
|
64
|
+
let replace = panels.is_some();
|
|
55
65
|
|
|
56
66
|
// Phase 1 — models only, NO renders and NO draws
|
|
57
67
|
let mut id_map: HashMap<String, String> = Default::default();
|
|
58
|
-
let mut fallback_fresh: Option<String> = None;
|
|
59
|
-
let mut active_fresh: Option<String> = None;
|
|
60
68
|
let mut contents = Vec::new();
|
|
61
|
-
// `panels` entries are `ViewerConfigInitial`s — `table` required by
|
|
62
|
-
// TYPE (a placed panel without a binding would be permanently blank),
|
|
63
|
-
// and no per-panel `settings` exists (element-level; carried by the
|
|
64
|
-
// top-level `active` field). An EMPTY `panels` map restores to the
|
|
65
|
-
// zero-panel empty stage — the former table-less fallback panel was
|
|
66
|
-
// exactly the blank-panel state this type exists to preclude.
|
|
67
|
-
for (saved_id, config) in panels {
|
|
68
|
-
let (fresh, session, renderer, config) = create_panel_model(
|
|
69
|
-
&this.elem,
|
|
70
|
-
&this.presentation,
|
|
71
|
-
&this.workspace,
|
|
72
|
-
None,
|
|
73
|
-
config.into(),
|
|
74
|
-
None,
|
|
75
|
-
Placement::Placed,
|
|
76
|
-
);
|
|
77
|
-
|
|
78
|
-
if active.as_deref() == Some(saved_id.as_str()) {
|
|
79
|
-
active_fresh = Some(fresh.as_str().to_owned());
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
fallback_fresh.get_or_insert_with(|| fresh.as_str().to_owned());
|
|
83
|
-
id_map.insert(saved_id, fresh.as_str().to_owned());
|
|
84
|
-
contents.push((fresh, session, renderer, config));
|
|
85
|
-
}
|
|
86
|
-
|
|
87
69
|
let mut eject_tasks = Vec::new();
|
|
88
|
-
|
|
89
|
-
|
|
70
|
+
match panels {
|
|
71
|
+
// `panels` entries are `ViewerConfigInitial`s — `table` required by
|
|
72
|
+
// TYPE (a placed panel without a binding would be permanently blank),
|
|
73
|
+
// and no per-panel `settings` exists (element-level; carried by the
|
|
74
|
+
// top-level `active` field). An EMPTY `panels` map restores to the
|
|
75
|
+
// zero-panel empty stage — the former table-less fallback panel was
|
|
76
|
+
// exactly the blank-panel state this type exists to preclude.
|
|
77
|
+
Some(panels) => {
|
|
78
|
+
for (saved_id, config) in panels {
|
|
79
|
+
let (fresh, session, renderer, config) = create_panel_model(
|
|
80
|
+
&this.elem,
|
|
81
|
+
&this.presentation,
|
|
82
|
+
&this.workspace,
|
|
83
|
+
None,
|
|
84
|
+
config.into(),
|
|
85
|
+
None,
|
|
86
|
+
Placement::Placed,
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
id_map.insert(saved_id, fresh.as_str().to_owned());
|
|
90
|
+
contents.push((fresh, session, renderer, config));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if let Some(panel) = this.workspace.take_reserved() {
|
|
94
|
+
eject_tasks.push(eject_panel(panel, Disposal::Reject));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
for old in &old_ids {
|
|
98
|
+
if let Some(panel) = this.workspace.remove_panel(old) {
|
|
99
|
+
eject_tasks.push(eject_panel(panel, Disposal::Reject));
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
None => {
|
|
104
|
+
for id in &old_ids {
|
|
105
|
+
id_map.insert(id.as_str().to_owned(), id.as_str().to_owned());
|
|
106
|
+
}
|
|
107
|
+
},
|
|
90
108
|
}
|
|
91
109
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
eject_tasks.push(eject_panel(panel));
|
|
95
|
-
}
|
|
110
|
+
if let Some(palette) = palette {
|
|
111
|
+
this.presentation.set_palette(palette)?;
|
|
96
112
|
}
|
|
97
113
|
|
|
98
|
-
this.presentation.set_palette(palette)?;
|
|
99
|
-
|
|
100
114
|
// Phase 2 — stage the remapped layout tree on the Workspace
|
|
115
|
+
let layout_staged = layout.is_some();
|
|
101
116
|
if let Some(layout) = layout {
|
|
102
117
|
this.workspace
|
|
103
118
|
.set_pending_layout(layout.remap(&|name| id_map.get(name).cloned()));
|
|
119
|
+
if !replace {
|
|
120
|
+
this.workspace.layout_staged().emit(());
|
|
121
|
+
}
|
|
104
122
|
}
|
|
105
123
|
|
|
106
124
|
// Phase 3 — the single visible commit
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
125
|
+
let active_target = match &active {
|
|
126
|
+
OptionalUpdate::Update(saved) => {
|
|
127
|
+
let target = id_map.get(saved).cloned();
|
|
128
|
+
if target.is_none() {
|
|
129
|
+
tracing::warn!("`active` names unknown panel \"{saved}\"");
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
target
|
|
133
|
+
},
|
|
134
|
+
_ => None,
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
let commit_target = if replace {
|
|
138
|
+
active_target
|
|
139
|
+
.clone()
|
|
140
|
+
.or_else(|| contents.first().map(|(id, ..)| id.as_str().to_owned()))
|
|
141
|
+
} else if layout_staged || active_target.is_some() {
|
|
142
|
+
active_target
|
|
143
|
+
.clone()
|
|
144
|
+
.or_else(|| this.workspace.active_id().map(|id| id.as_str().to_owned()))
|
|
145
|
+
} else {
|
|
146
|
+
None
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
if let Some(target) = commit_target
|
|
115
150
|
&& let Some(app) = this.root.borrow().as_ref()
|
|
116
151
|
{
|
|
117
152
|
app.send_message(PerspectiveViewerMsg::CommitWorkspaceRestore(target));
|
|
118
153
|
}
|
|
119
154
|
|
|
120
|
-
|
|
155
|
+
let settings = if replace {
|
|
156
|
+
Some(active_target.is_some())
|
|
157
|
+
} else {
|
|
158
|
+
match active {
|
|
159
|
+
OptionalUpdate::Update(_) => active_target.as_ref().map(|_| true),
|
|
160
|
+
OptionalUpdate::SetDefault => Some(false),
|
|
161
|
+
OptionalUpdate::Missing => None,
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
if let Some(open) = settings
|
|
166
|
+
&& let Some(app) = this.root.borrow().as_ref()
|
|
167
|
+
{
|
|
121
168
|
// Silent (`announce: false`): each restored panel's own view-config
|
|
122
169
|
// commit dispatch announces the config, settings field included.
|
|
123
170
|
app.send_message(PerspectiveViewerMsg::ToggleSettingsInit(
|
|
124
|
-
Some(SettingsUpdate::Update(
|
|
171
|
+
Some(SettingsUpdate::Update(open)),
|
|
125
172
|
false,
|
|
126
173
|
None,
|
|
127
174
|
));
|
|
128
175
|
}
|
|
129
176
|
|
|
130
|
-
let masters = masters
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
177
|
+
let masters = match masters {
|
|
178
|
+
OptionalUpdate::Missing => None,
|
|
179
|
+
OptionalUpdate::SetDefault => Some(Vec::new()),
|
|
180
|
+
OptionalUpdate::Update(masters) => Some(
|
|
181
|
+
masters
|
|
182
|
+
.into_iter()
|
|
183
|
+
.filter_map(|saved| match id_map.get(&saved) {
|
|
184
|
+
Some(fresh) => Some(PanelId::from(fresh.as_str())),
|
|
185
|
+
None => {
|
|
186
|
+
tracing::warn!("`masters` names unknown panel \"{saved}\"");
|
|
187
|
+
None
|
|
188
|
+
},
|
|
189
|
+
})
|
|
190
|
+
.collect::<Vec<_>>(),
|
|
191
|
+
),
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
if let Some(masters) = masters {
|
|
195
|
+
let before = this.workspace.masters();
|
|
196
|
+
this.workspace.set_masters(masters);
|
|
197
|
+
if !replace {
|
|
198
|
+
let after = this.workspace.masters();
|
|
199
|
+
for id in &old_ids {
|
|
200
|
+
let is_master = after.contains(id);
|
|
201
|
+
if before.contains(id) != is_master
|
|
202
|
+
&& let Some(panel) = this.workspace.panel(id)
|
|
203
|
+
{
|
|
204
|
+
let mode = if is_master {
|
|
205
|
+
"SELECT_ROW_TREE"
|
|
206
|
+
} else {
|
|
207
|
+
"READ_ONLY"
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
set_edit_mode(&panel.session, &panel.renderer, mode);
|
|
211
|
+
if !is_master {
|
|
212
|
+
this.workspace.clear_contribution(id);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
match global_filters {
|
|
220
|
+
OptionalUpdate::Missing => {},
|
|
221
|
+
OptionalUpdate::SetDefault => this.workspace.set_global_filters(Vec::new()),
|
|
222
|
+
OptionalUpdate::Update(filters) => this.workspace.set_global_filters(filters),
|
|
223
|
+
}
|
|
224
|
+
|
|
145
225
|
Ok((contents, eject_tasks))
|
|
146
226
|
}
|
|
@@ -150,7 +150,13 @@ async fn bind_pending(
|
|
|
150
150
|
return Ok(());
|
|
151
151
|
};
|
|
152
152
|
|
|
153
|
-
super::restore_panel::bind_table_task(
|
|
153
|
+
super::restore_panel::bind_table_task(
|
|
154
|
+
&session,
|
|
155
|
+
&workspace,
|
|
156
|
+
name,
|
|
157
|
+
crate::session::MissingTable::Pend,
|
|
158
|
+
)
|
|
159
|
+
.await
|
|
154
160
|
}
|
|
155
161
|
},
|
|
156
162
|
)
|
|
@@ -80,7 +80,7 @@ impl<T: TabItem> Component for TabList<T> {
|
|
|
80
80
|
|
|
81
81
|
let onclick = ctx.link().callback(move |_| TabListMsg::SetSelected(idx));
|
|
82
82
|
let title = tab.to_string();
|
|
83
|
-
let style = intl_content_style(&format!("{}-tab", intl_slug(&title))
|
|
83
|
+
let style = intl_content_style(&format!("{}-tab", intl_slug(&title)));
|
|
84
84
|
html! {
|
|
85
85
|
<span {class} {onclick}>
|
|
86
86
|
<div class="tab-title" id={title} {style} />
|
|
@@ -26,29 +26,10 @@ pub fn intl_slug(key: &str) -> String {
|
|
|
26
26
|
.collect()
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
fn humanize(key: &str) -> String {
|
|
30
|
-
let mut out = String::with_capacity(key.len());
|
|
31
|
-
for x in key.chars() {
|
|
32
|
-
if x.is_alphanumeric() {
|
|
33
|
-
if out.is_empty() {
|
|
34
|
-
out.extend(x.to_uppercase());
|
|
35
|
-
} else {
|
|
36
|
-
out.push(x);
|
|
37
|
-
}
|
|
38
|
-
} else if matches!(x, '_' | '-') || x.is_whitespace() {
|
|
39
|
-
out.push(' ');
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
out
|
|
44
|
-
}
|
|
45
|
-
|
|
46
29
|
/// The inline `style` that routes an element's `:before` text through the intl
|
|
47
|
-
/// indirection
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
pub fn intl_content_style(var: &str, fallback: &str) -> String {
|
|
51
|
-
format!("--psp-label--content: var(--psp-label--{var}--content, \"{fallback}\")")
|
|
30
|
+
/// indirection.
|
|
31
|
+
pub fn intl_content_style(var: &str) -> String {
|
|
32
|
+
format!("--psp-label--content: var(--psp-label--{var}--content)")
|
|
52
33
|
}
|
|
53
34
|
|
|
54
35
|
#[derive(Properties, PartialEq)]
|
|
@@ -75,7 +56,7 @@ pub fn intl_label(props: &IntlLabelProps) -> Html {
|
|
|
75
56
|
(format!("{}-label", props.name), slug)
|
|
76
57
|
};
|
|
77
58
|
|
|
78
|
-
let style = intl_content_style(&var
|
|
59
|
+
let style = intl_content_style(&var);
|
|
79
60
|
let class = classes!("intl-label", props.class.clone());
|
|
80
61
|
html! { <label {class} {id} {style} /> }
|
|
81
62
|
}
|
package/src/rust/ui/portal.rs
CHANGED
|
@@ -24,3 +24,16 @@ pub fn read_custom_property(elem: &Element, name: &str) -> Option<String> {
|
|
|
24
24
|
let value = value.trim();
|
|
25
25
|
(!value.is_empty()).then(|| value.to_owned())
|
|
26
26
|
}
|
|
27
|
+
|
|
28
|
+
/// The intl string `--psp-label--{slug}--content` as inherited by `elem`,
|
|
29
|
+
/// unquoted, or `None` when no theme defines it.
|
|
30
|
+
pub fn read_intl_string(elem: &Element, slug: &str) -> Option<String> {
|
|
31
|
+
let raw = read_custom_property(elem, &format!("--psp-label--{slug}--content"))?;
|
|
32
|
+
let unquoted = raw
|
|
33
|
+
.strip_prefix('"')
|
|
34
|
+
.and_then(|x| x.strip_suffix('"'))
|
|
35
|
+
.or_else(|| raw.strip_prefix('\'').and_then(|x| x.strip_suffix('\'')))
|
|
36
|
+
.unwrap_or(&raw);
|
|
37
|
+
|
|
38
|
+
Some(unquoted.replace("\\\"", "\""))
|
|
39
|
+
}
|
|
@@ -10,53 +10,65 @@
|
|
|
10
10
|
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
|
11
11
|
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
12
12
|
|
|
13
|
-
use std::
|
|
14
|
-
use std::
|
|
13
|
+
use std::cell::Cell;
|
|
14
|
+
use std::rc::Rc;
|
|
15
15
|
|
|
16
|
-
use
|
|
17
|
-
use strum::EnumIter;
|
|
18
|
-
use ts_rs::TS;
|
|
16
|
+
use super::pubsub::PubSub;
|
|
19
17
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
None,
|
|
18
|
+
/// A refcounted account of concurrent in-flight work, moved only by
|
|
19
|
+
/// [`InFlightGuard`]'s `Drop` so no exit path can strand the count.
|
|
20
|
+
#[derive(Clone, Default)]
|
|
21
|
+
pub struct InFlight(Rc<InFlightData>);
|
|
25
22
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
Background,
|
|
23
|
+
#[derive(Default)]
|
|
24
|
+
struct InFlightData {
|
|
25
|
+
count: Cell<u32>,
|
|
26
|
+
changed: PubSub<u32>,
|
|
31
27
|
}
|
|
32
28
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
Self::None => "none",
|
|
37
|
-
Self::Foreground => "foreground",
|
|
38
|
-
Self::Background => "background",
|
|
39
|
-
};
|
|
29
|
+
/// One unit of in-flight work, moved INTO the future it accounts for.
|
|
30
|
+
#[must_use]
|
|
31
|
+
pub struct InFlightGuard(InFlight);
|
|
40
32
|
|
|
41
|
-
|
|
33
|
+
impl InFlight {
|
|
34
|
+
pub fn guard(&self) -> InFlightGuard {
|
|
35
|
+
let count = self.0.count.get() + 1;
|
|
36
|
+
self.0.count.set(count);
|
|
37
|
+
self.0.changed.emit(count);
|
|
38
|
+
InFlightGuard(self.clone())
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
pub fn count(&self) -> u32 {
|
|
42
|
+
self.0.count.get()
|
|
42
43
|
}
|
|
43
|
-
}
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
pub fn is_empty(&self) -> bool {
|
|
46
|
+
self.0.count.get() == 0
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/// Fires with the ABSOLUTE [`Self::count`] on both edges, so subscribers
|
|
50
|
+
/// assign rather than accumulate.
|
|
51
|
+
pub fn changed(&self) -> &PubSub<u32> {
|
|
52
|
+
&self.0.changed
|
|
53
|
+
}
|
|
47
54
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
55
|
+
/// Resolve once every unit in flight has settled.
|
|
56
|
+
pub async fn settle(&self) {
|
|
57
|
+
while self.0.count.get() > 0 {
|
|
58
|
+
if self.0.changed.read_next().await.is_err() {
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
54
61
|
}
|
|
55
62
|
}
|
|
56
63
|
}
|
|
57
64
|
|
|
58
|
-
impl
|
|
59
|
-
|
|
60
|
-
|
|
65
|
+
impl Drop for InFlightGuard {
|
|
66
|
+
fn drop(&mut self) {
|
|
67
|
+
let data = &self.0.0;
|
|
68
|
+
let count = data.count.get();
|
|
69
|
+
debug_assert!(count > 0, "InFlight underflow");
|
|
70
|
+
let count = count.saturating_sub(1);
|
|
71
|
+
data.count.set(count);
|
|
72
|
+
data.changed.emit(count);
|
|
61
73
|
}
|
|
62
74
|
}
|