@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
|
@@ -0,0 +1,395 @@
|
|
|
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
|
+
//! Page-global Local Font Access state shared by every `Font`
|
|
14
|
+
//! control on the page: the `local-fonts` permission, the enumerated
|
|
15
|
+
//! family list once granted, and a measured web-safe fallback list
|
|
16
|
+
//! otherwise.
|
|
17
|
+
|
|
18
|
+
use std::cell::{Cell, RefCell};
|
|
19
|
+
use std::rc::Rc;
|
|
20
|
+
|
|
21
|
+
use perspective_js::utils::global;
|
|
22
|
+
use wasm_bindgen::prelude::*;
|
|
23
|
+
use wasm_bindgen_futures::JsFuture;
|
|
24
|
+
use yew::Callback;
|
|
25
|
+
|
|
26
|
+
use super::{PubSub, Subscription};
|
|
27
|
+
|
|
28
|
+
/// CSS generic families always offered first, in this order.
|
|
29
|
+
pub const GENERIC_FONT_FAMILIES: [&str; 5] =
|
|
30
|
+
["inherit", "monospace", "sans-serif", "serif", "system-ui"];
|
|
31
|
+
|
|
32
|
+
const FALLBACK_FONT_FAMILIES: [&str; 28] = [
|
|
33
|
+
"Arial",
|
|
34
|
+
"Arial Black",
|
|
35
|
+
"Calibri",
|
|
36
|
+
"Cambria",
|
|
37
|
+
"Comic Sans MS",
|
|
38
|
+
"Consolas",
|
|
39
|
+
"Courier New",
|
|
40
|
+
"DejaVu Sans",
|
|
41
|
+
"DejaVu Serif",
|
|
42
|
+
"Georgia",
|
|
43
|
+
"Helvetica",
|
|
44
|
+
"Helvetica Neue",
|
|
45
|
+
"Impact",
|
|
46
|
+
"Liberation Sans",
|
|
47
|
+
"Liberation Serif",
|
|
48
|
+
"Lucida Console",
|
|
49
|
+
"Menlo",
|
|
50
|
+
"Monaco",
|
|
51
|
+
"Noto Sans",
|
|
52
|
+
"Noto Serif",
|
|
53
|
+
"Roboto",
|
|
54
|
+
"SF Mono",
|
|
55
|
+
"Segoe UI",
|
|
56
|
+
"Tahoma",
|
|
57
|
+
"Times New Roman",
|
|
58
|
+
"Trebuchet MS",
|
|
59
|
+
"Ubuntu",
|
|
60
|
+
"Verdana",
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
const FONT_TEST_SAMPLE: &str = "mmmmmmmmmmlli ABCDΔ 0123";
|
|
64
|
+
const FONT_TEST_BASELINES: [&str; 3] = ["monospace", "sans-serif", "serif"];
|
|
65
|
+
|
|
66
|
+
/// The `local-fonts` permission as last observed.
|
|
67
|
+
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
|
68
|
+
pub enum LocalFontsAccess {
|
|
69
|
+
/// No `window.queryLocalFonts` (non-Chromium, or insecure context).
|
|
70
|
+
Unsupported,
|
|
71
|
+
Prompt,
|
|
72
|
+
Granted,
|
|
73
|
+
Denied,
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
#[derive(Default)]
|
|
77
|
+
struct LocalFontsState {
|
|
78
|
+
access: Cell<Option<LocalFontsAccess>>,
|
|
79
|
+
probing: Cell<bool>,
|
|
80
|
+
families: RefCell<Option<Rc<Vec<String>>>>,
|
|
81
|
+
fallback: RefCell<Option<Rc<Vec<String>>>>,
|
|
82
|
+
on_status_change: RefCell<Option<Closure<dyn Fn()>>>,
|
|
83
|
+
changed: PubSub<()>,
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
thread_local! {
|
|
87
|
+
static STATE: LocalFontsState = LocalFontsState::default();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/// The permission state, or `None` before [`probe_local_fonts_access`]
|
|
91
|
+
/// has resolved.
|
|
92
|
+
pub fn local_fonts_access() -> Option<LocalFontsAccess> {
|
|
93
|
+
STATE.with(|s| s.access.get())
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/// Subscribe to any change in access state or family list.
|
|
97
|
+
pub fn on_local_fonts_changed(cb: Callback<()>) -> Subscription {
|
|
98
|
+
STATE.with(|s| s.changed.add_notify_listener(&cb))
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/// The options a `Font` control offers for `current`: the generic
|
|
102
|
+
/// keywords, then the enumerated families when granted (and non-empty) or
|
|
103
|
+
/// the measured fallback otherwise, then `current` itself if absent.
|
|
104
|
+
pub fn font_family_options(current: &str) -> Vec<String> {
|
|
105
|
+
let installed = STATE.with(|s| {
|
|
106
|
+
let enumerated = match s.access.get() {
|
|
107
|
+
Some(LocalFontsAccess::Granted) => s.families.borrow().clone(),
|
|
108
|
+
_ => None,
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
match enumerated {
|
|
112
|
+
Some(families) if !families.is_empty() => families,
|
|
113
|
+
_ => fallback_families(s),
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
compose_font_family_options(current, &installed)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
fn compose_font_family_options(current: &str, installed: &[String]) -> Vec<String> {
|
|
121
|
+
let mut out: Vec<String> = GENERIC_FONT_FAMILIES
|
|
122
|
+
.iter()
|
|
123
|
+
.map(|x| (*x).to_owned())
|
|
124
|
+
.collect();
|
|
125
|
+
|
|
126
|
+
out.extend(installed.iter().cloned());
|
|
127
|
+
if !out.iter().any(|x| x == current) {
|
|
128
|
+
out.push(current.to_owned());
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
out
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/// Resolve the permission state once, without ever showing a prompt.
|
|
135
|
+
pub fn probe_local_fonts_access() {
|
|
136
|
+
let already = STATE.with(|s| s.access.get().is_some() || s.probing.replace(true));
|
|
137
|
+
if already {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if query_local_fonts_fn().is_none() {
|
|
142
|
+
set_access(LocalFontsAccess::Unsupported);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
wasm_bindgen_futures::spawn_local(async move {
|
|
147
|
+
match query_permission_status().await {
|
|
148
|
+
Some(status) => {
|
|
149
|
+
install_status_listener(&status);
|
|
150
|
+
let access = access_of_status(&status);
|
|
151
|
+
set_access(access);
|
|
152
|
+
if access == LocalFontsAccess::Granted {
|
|
153
|
+
load_families().await;
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
None => match call_query_local_fonts() {
|
|
157
|
+
Some(promise) => match families_from(promise).await {
|
|
158
|
+
Some(families) => set_granted(families),
|
|
159
|
+
None => set_access(LocalFontsAccess::Prompt),
|
|
160
|
+
},
|
|
161
|
+
None => set_access(LocalFontsAccess::Unsupported),
|
|
162
|
+
},
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/// Ask the browser for local fonts, synchronously from a user-gesture handler
|
|
168
|
+
/// so `queryLocalFonts()` may prompt.
|
|
169
|
+
pub fn request_local_fonts_access() {
|
|
170
|
+
let Some(promise) = call_query_local_fonts() else {
|
|
171
|
+
set_access(LocalFontsAccess::Unsupported);
|
|
172
|
+
return;
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
wasm_bindgen_futures::spawn_local(async move {
|
|
176
|
+
match families_from(promise).await {
|
|
177
|
+
Some(families) => set_granted(families),
|
|
178
|
+
None => {
|
|
179
|
+
let access = match query_permission_status().await {
|
|
180
|
+
Some(status) => access_of_status(&status),
|
|
181
|
+
None => LocalFontsAccess::Prompt,
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
set_access(access);
|
|
185
|
+
},
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
fn set_access(access: LocalFontsAccess) {
|
|
191
|
+
STATE.with(|s| {
|
|
192
|
+
s.probing.set(false);
|
|
193
|
+
let prev = s.access.replace(Some(access));
|
|
194
|
+
if access != LocalFontsAccess::Granted {
|
|
195
|
+
s.families.borrow_mut().take();
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if prev != Some(access) {
|
|
199
|
+
s.changed.emit(());
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
fn set_granted(families: Vec<String>) {
|
|
205
|
+
STATE.with(|s| {
|
|
206
|
+
s.probing.set(false);
|
|
207
|
+
s.access.set(Some(LocalFontsAccess::Granted));
|
|
208
|
+
*s.families.borrow_mut() = Some(Rc::new(families));
|
|
209
|
+
s.changed.emit(());
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
async fn load_families() {
|
|
214
|
+
if let Some(promise) = call_query_local_fonts()
|
|
215
|
+
&& let Some(families) = families_from(promise).await
|
|
216
|
+
{
|
|
217
|
+
set_granted(families);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
fn query_local_fonts_fn() -> Option<js_sys::Function> {
|
|
222
|
+
js_sys::Reflect::get(&global::window(), &JsValue::from_str("queryLocalFonts"))
|
|
223
|
+
.ok()?
|
|
224
|
+
.dyn_into::<js_sys::Function>()
|
|
225
|
+
.ok()
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
fn call_query_local_fonts() -> Option<js_sys::Promise> {
|
|
229
|
+
query_local_fonts_fn()?
|
|
230
|
+
.call0(&global::window())
|
|
231
|
+
.ok()?
|
|
232
|
+
.dyn_into::<js_sys::Promise>()
|
|
233
|
+
.ok()
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/// The enumerated families, or `None` when the call yielded none, which
|
|
237
|
+
/// Chromium resolves in place of rejecting a dismissed or blocked prompt.
|
|
238
|
+
async fn families_from(promise: js_sys::Promise) -> Option<Vec<String>> {
|
|
239
|
+
let fonts = JsFuture::from(promise).await.ok()?;
|
|
240
|
+
let family_key = JsValue::from_str("family");
|
|
241
|
+
let mut families: Vec<String> = js_sys::Array::from(&fonts)
|
|
242
|
+
.iter()
|
|
243
|
+
.filter_map(|font| js_sys::Reflect::get(&font, &family_key).ok()?.as_string())
|
|
244
|
+
.collect();
|
|
245
|
+
|
|
246
|
+
families.sort_unstable();
|
|
247
|
+
families.dedup();
|
|
248
|
+
(!families.is_empty()).then_some(families)
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
async fn query_permission_status() -> Option<JsValue> {
|
|
252
|
+
let navigator: JsValue = global::window().navigator().into();
|
|
253
|
+
let permissions = js_sys::Reflect::get(&navigator, &JsValue::from_str("permissions")).ok()?;
|
|
254
|
+
let query = js_sys::Reflect::get(&permissions, &JsValue::from_str("query"))
|
|
255
|
+
.ok()?
|
|
256
|
+
.dyn_into::<js_sys::Function>()
|
|
257
|
+
.ok()?;
|
|
258
|
+
|
|
259
|
+
let descriptor = js_sys::Object::new();
|
|
260
|
+
js_sys::Reflect::set(
|
|
261
|
+
&descriptor,
|
|
262
|
+
&JsValue::from_str("name"),
|
|
263
|
+
&JsValue::from_str("local-fonts"),
|
|
264
|
+
)
|
|
265
|
+
.ok()?;
|
|
266
|
+
|
|
267
|
+
let promise = query
|
|
268
|
+
.call1(&permissions, &descriptor)
|
|
269
|
+
.ok()?
|
|
270
|
+
.dyn_into::<js_sys::Promise>()
|
|
271
|
+
.ok()?;
|
|
272
|
+
|
|
273
|
+
let status = JsFuture::from(promise).await.ok()?;
|
|
274
|
+
(!status.is_undefined() && !status.is_null()).then_some(status)
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
fn access_of_status(status: &JsValue) -> LocalFontsAccess {
|
|
278
|
+
let state = js_sys::Reflect::get(status, &JsValue::from_str("state"))
|
|
279
|
+
.ok()
|
|
280
|
+
.and_then(|x| x.as_string());
|
|
281
|
+
|
|
282
|
+
match state.as_deref() {
|
|
283
|
+
Some("granted") => LocalFontsAccess::Granted,
|
|
284
|
+
Some("denied") => LocalFontsAccess::Denied,
|
|
285
|
+
_ => LocalFontsAccess::Prompt,
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
fn install_status_listener(status: &JsValue) {
|
|
290
|
+
let target = status.clone();
|
|
291
|
+
let closure = Closure::<dyn Fn()>::new(move || {
|
|
292
|
+
let access = access_of_status(&target);
|
|
293
|
+
set_access(access);
|
|
294
|
+
if access == LocalFontsAccess::Granted {
|
|
295
|
+
wasm_bindgen_futures::spawn_local(load_families());
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
let _ = js_sys::Reflect::set(
|
|
300
|
+
status,
|
|
301
|
+
&JsValue::from_str("onchange"),
|
|
302
|
+
closure.as_ref().unchecked_ref(),
|
|
303
|
+
);
|
|
304
|
+
|
|
305
|
+
STATE.with(|s| *s.on_status_change.borrow_mut() = Some(closure));
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
fn fallback_families(state: &LocalFontsState) -> Rc<Vec<String>> {
|
|
309
|
+
state
|
|
310
|
+
.fallback
|
|
311
|
+
.borrow_mut()
|
|
312
|
+
.get_or_insert_with(|| Rc::new(detect_fallback_families()))
|
|
313
|
+
.clone()
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/// The subset of [`FALLBACK_FONT_FAMILIES`] the browser can render,
|
|
317
|
+
/// detected by comparing rendered widths against the generic baselines.
|
|
318
|
+
fn detect_fallback_families() -> Vec<String> {
|
|
319
|
+
let Some(ctx) = canvas_context() else {
|
|
320
|
+
return vec![];
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
let baselines: Vec<f64> = FONT_TEST_BASELINES
|
|
324
|
+
.iter()
|
|
325
|
+
.map(|x| measure_width(&ctx, x))
|
|
326
|
+
.collect();
|
|
327
|
+
|
|
328
|
+
FALLBACK_FONT_FAMILIES
|
|
329
|
+
.iter()
|
|
330
|
+
.filter(|family| {
|
|
331
|
+
FONT_TEST_BASELINES
|
|
332
|
+
.iter()
|
|
333
|
+
.zip(&baselines)
|
|
334
|
+
.any(|(baseline, width)| {
|
|
335
|
+
measure_width(&ctx, &format!("\"{family}\", {baseline}")) != *width
|
|
336
|
+
})
|
|
337
|
+
})
|
|
338
|
+
.map(|x| (*x).to_owned())
|
|
339
|
+
.collect()
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
fn canvas_context() -> Option<web_sys::CanvasRenderingContext2d> {
|
|
343
|
+
global::document()
|
|
344
|
+
.create_element("canvas")
|
|
345
|
+
.ok()?
|
|
346
|
+
.dyn_into::<web_sys::HtmlCanvasElement>()
|
|
347
|
+
.ok()?
|
|
348
|
+
.get_context("2d")
|
|
349
|
+
.ok()??
|
|
350
|
+
.dyn_into::<web_sys::CanvasRenderingContext2d>()
|
|
351
|
+
.ok()
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
fn measure_width(ctx: &web_sys::CanvasRenderingContext2d, family: &str) -> f64 {
|
|
355
|
+
ctx.set_font(&format!("72px {family}"));
|
|
356
|
+
ctx.measure_text(FONT_TEST_SAMPLE)
|
|
357
|
+
.map(|x| x.width())
|
|
358
|
+
.unwrap_or_default()
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
#[cfg(test)]
|
|
362
|
+
mod tests {
|
|
363
|
+
use super::*;
|
|
364
|
+
|
|
365
|
+
fn owned(xs: &[&str]) -> Vec<String> {
|
|
366
|
+
xs.iter().map(|x| (*x).to_owned()).collect()
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
#[test]
|
|
370
|
+
fn generic_families_lead_and_current_appends_when_missing() {
|
|
371
|
+
let options = compose_font_family_options("Zapf Chancery", &owned(&["Arial", "Menlo"]));
|
|
372
|
+
assert_eq!(
|
|
373
|
+
options,
|
|
374
|
+
owned(&[
|
|
375
|
+
"inherit",
|
|
376
|
+
"monospace",
|
|
377
|
+
"sans-serif",
|
|
378
|
+
"serif",
|
|
379
|
+
"system-ui",
|
|
380
|
+
"Arial",
|
|
381
|
+
"Menlo",
|
|
382
|
+
"Zapf Chancery"
|
|
383
|
+
])
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
#[test]
|
|
388
|
+
fn current_is_not_duplicated() {
|
|
389
|
+
let options = compose_font_family_options("Arial", &owned(&["Arial"]));
|
|
390
|
+
assert_eq!(options.iter().filter(|x| *x == "Arial").count(), 1);
|
|
391
|
+
|
|
392
|
+
let options = compose_font_family_options("inherit", &[]);
|
|
393
|
+
assert_eq!(options, owned(&GENERIC_FONT_FAMILIES));
|
|
394
|
+
}
|
|
395
|
+
}
|
package/src/rust/utils/mod.rs
CHANGED
|
@@ -24,6 +24,8 @@ mod custom_element;
|
|
|
24
24
|
mod datetime;
|
|
25
25
|
mod debounce;
|
|
26
26
|
mod hooks;
|
|
27
|
+
mod in_flight;
|
|
28
|
+
mod local_fonts;
|
|
27
29
|
mod modal_position;
|
|
28
30
|
mod number_format;
|
|
29
31
|
mod pending_effects;
|
|
@@ -43,6 +45,8 @@ pub use custom_element::*;
|
|
|
43
45
|
pub use datetime::*;
|
|
44
46
|
pub use debounce::*;
|
|
45
47
|
pub use hooks::*;
|
|
48
|
+
pub use in_flight::*;
|
|
49
|
+
pub use local_fonts::*;
|
|
46
50
|
pub use modal_position::*;
|
|
47
51
|
pub use number_format::*;
|
|
48
52
|
pub use pending_effects::*;
|
|
@@ -10,10 +10,7 @@
|
|
|
10
10
|
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
|
11
11
|
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
12
12
|
|
|
13
|
-
use
|
|
14
|
-
use std::rc::Rc;
|
|
15
|
-
|
|
16
|
-
use super::pubsub::PubSub;
|
|
13
|
+
use super::in_flight::{InFlight, InFlightGuard};
|
|
17
14
|
|
|
18
15
|
/// The element-scoped ledger of in-flight EFFECTS: every effectful public
|
|
19
16
|
/// method (and the internal flows they schedule, e.g. the `before-resize`
|
|
@@ -22,41 +19,20 @@ use super::pubsub::PubSub;
|
|
|
22
19
|
/// after the ledger drains, so "called before `flush`" implies "applied
|
|
23
20
|
/// before `flush` resolves".
|
|
24
21
|
#[derive(Clone, Default)]
|
|
25
|
-
pub struct EffectLedger(
|
|
26
|
-
|
|
27
|
-
#[derive(Default)]
|
|
28
|
-
struct EffectLedgerData {
|
|
29
|
-
count: Cell<u32>,
|
|
30
|
-
on_drain: PubSub<()>,
|
|
31
|
-
}
|
|
22
|
+
pub struct EffectLedger(InFlight);
|
|
32
23
|
|
|
33
|
-
pub
|
|
24
|
+
pub type EffectGuard = InFlightGuard;
|
|
34
25
|
|
|
35
26
|
impl EffectLedger {
|
|
36
27
|
pub fn guard(&self) -> EffectGuard {
|
|
37
|
-
self.0.
|
|
38
|
-
EffectGuard(self.clone())
|
|
28
|
+
self.0.guard()
|
|
39
29
|
}
|
|
40
30
|
|
|
41
31
|
pub fn is_empty(&self) -> bool {
|
|
42
|
-
self.0.
|
|
32
|
+
self.0.is_empty()
|
|
43
33
|
}
|
|
44
34
|
|
|
45
35
|
pub async fn settle(&self) {
|
|
46
|
-
|
|
47
|
-
if self.0.on_drain.read_next().await.is_err() {
|
|
48
|
-
break;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
impl Drop for EffectGuard {
|
|
55
|
-
fn drop(&mut self) {
|
|
56
|
-
let data = &self.0.0;
|
|
57
|
-
data.count.set(data.count.get() - 1);
|
|
58
|
-
if data.count.get() == 0 {
|
|
59
|
-
data.on_drain.emit(());
|
|
60
|
-
}
|
|
36
|
+
self.0.settle().await
|
|
61
37
|
}
|
|
62
38
|
}
|
package/src/rust/workspace.rs
CHANGED
|
@@ -327,6 +327,11 @@ struct WorkspaceData {
|
|
|
327
327
|
/// Fires on every [`PanelPhase`] transition.
|
|
328
328
|
staged_changed: Rc<PubSub<()>>,
|
|
329
329
|
|
|
330
|
+
/// Fires when a layout tree is staged for a RETAINED panel set (a
|
|
331
|
+
/// `panels`-less `restoreWorkspace`), whose props-driven render would
|
|
332
|
+
/// otherwise never consume it.
|
|
333
|
+
layout_staged: Rc<PubSub<()>>,
|
|
334
|
+
|
|
330
335
|
/// Panels removed from `panels` whose layout cell has not yet been
|
|
331
336
|
/// reclaimed, so their engines and plugin pixels stay alive until the
|
|
332
337
|
/// commit.
|
|
@@ -357,6 +362,7 @@ impl Workspace {
|
|
|
357
362
|
pending_layout: None,
|
|
358
363
|
effects: EffectLedger::default(),
|
|
359
364
|
staged_changed: Rc::new(PubSub::default()),
|
|
365
|
+
layout_staged: Rc::new(PubSub::default()),
|
|
360
366
|
closing: Vec::new(),
|
|
361
367
|
emitter: LayoutEmitter::default(),
|
|
362
368
|
})))
|
|
@@ -420,6 +426,12 @@ impl Workspace {
|
|
|
420
426
|
self.0.borrow().staged_changed.clone()
|
|
421
427
|
}
|
|
422
428
|
|
|
429
|
+
/// A handle to the `layout_staged` PubSub (see
|
|
430
|
+
/// [`WorkspaceData::layout_staged`]).
|
|
431
|
+
pub fn layout_staged(&self) -> Rc<PubSub<()>> {
|
|
432
|
+
self.0.borrow().layout_staged.clone()
|
|
433
|
+
}
|
|
434
|
+
|
|
423
435
|
/// Promote a staged panel toward layout insertion, returning whether it
|
|
424
436
|
/// was still staged — restore completion and the staging deadline RACE
|
|
425
437
|
/// to promote, and only the winner proceeds. Emits `staged_changed` —
|
package/src/themes/icons.css
CHANGED
|
@@ -56,6 +56,7 @@ perspective-viewer-tab {
|
|
|
56
56
|
--psp-icon--updating--mask-image: url("../svg/updating.gif");
|
|
57
57
|
--psp-icon--downloading--mask-image: url("../svg/downloading.gif");
|
|
58
58
|
--psp-icon--status-ok--mask-image: url(../svg/status_ok.svg);
|
|
59
|
+
--psp-icon--status-pending--mask-image: url(../svg/status_pending.svg);
|
|
59
60
|
--psp-icon--export--mask-image: url("../svg/export-icon.svg");
|
|
60
61
|
--psp-icon--free-scroll--mask-image: url("../svg/free-scroll-icon.svg");
|
|
61
62
|
--psp-icon--lock--mask-image: url("../svg/lock.svg");
|
package/src/themes/intl/ar.css
CHANGED
|
@@ -69,16 +69,13 @@ perspective-viewer {
|
|
|
69
69
|
--psp-label--scroll-lock-alt-toggle--content: "تمرير محاذى";
|
|
70
70
|
|
|
71
71
|
--psp-label--color--content: "اللون";
|
|
72
|
-
--psp-label--format--content: "التنسيق";
|
|
73
72
|
--psp-label--timezone--content: "المنطقة الزمنية";
|
|
74
73
|
--psp-label--date-style--content: "نمط التاريخ";
|
|
75
74
|
--psp-label--time-style--content: "نمط الوقت";
|
|
76
|
-
--psp-label--
|
|
77
|
-
--psp-label--
|
|
78
|
-
--psp-label--fg-
|
|
79
|
-
--psp-label--bg-
|
|
80
|
-
--psp-label--datetime-color-mode--content: "اللون";
|
|
81
|
-
--psp-label--string-color-mode--content: "اللون";
|
|
75
|
+
--psp-label--fg-mode--content: "المقدمة";
|
|
76
|
+
--psp-label--bg-mode--content: "الخلفية";
|
|
77
|
+
--psp-label--fg-color--content: "المقدمة";
|
|
78
|
+
--psp-label--bg-color--content: "الخلفية";
|
|
82
79
|
--psp-label--series--content: "السلسلة";
|
|
83
80
|
--psp-label--color-range--content: "نطاق الألوان";
|
|
84
81
|
--psp-label--style--content: "النمط";
|
|
@@ -117,6 +114,7 @@ perspective-viewer {
|
|
|
117
114
|
--psp-label--group-legend--content: "مفتاح الرسم";
|
|
118
115
|
--psp-label--group-tooltip--content: "التلميحات";
|
|
119
116
|
--psp-label--group-color--content: "اللون";
|
|
117
|
+
--psp-label--group-column--content: "العمود";
|
|
120
118
|
--psp-label--group-format--content: "التنسيق";
|
|
121
119
|
--psp-label--column-size-override--content: "عرض العمود";
|
|
122
120
|
|
|
@@ -150,6 +148,21 @@ perspective-viewer {
|
|
|
150
148
|
|
|
151
149
|
--psp-label--edit-mode--content: "وضع التحرير";
|
|
152
150
|
--psp-label--scroll-lock--content: "قفل التمرير";
|
|
151
|
+
--psp-label--column-menus--content: "قوائم الأعمدة";
|
|
152
|
+
--psp-label--group-font--content: "الخط";
|
|
153
|
+
--psp-label--group-rows--content: "الصفوف";
|
|
154
|
+
--psp-label--link--content: "رابط";
|
|
155
|
+
--psp-label--font-family--content: "عائلة الخط";
|
|
156
|
+
--psp-label--font-size--content: "حجم الخط";
|
|
157
|
+
--psp-label--request-local-fonts--content: "الخطوط المحلية";
|
|
158
|
+
--psp-label--local-fonts-blocked--content: "الخطوط المحلية محظورة";
|
|
159
|
+
--psp-label--request-local-fonts-title--content: "السماح بالوصول إلى الخطوط المثبتة على هذا الجهاز";
|
|
160
|
+
--psp-label--local-fonts-blocked-title--content: "الوصول إلى الخطوط المحلية محظور لهذا الموقع. اسمح به في إعدادات الموقع بالمتصفح لعرضها هنا.";
|
|
161
|
+
--psp-label--word-wrap--content: "التفاف النص";
|
|
162
|
+
--psp-label--align--content: "المحاذاة";
|
|
163
|
+
--psp-label--row-height--content: "ارتفاع الصف";
|
|
164
|
+
--psp-label--zebra-rows--content: "صفوف متناوبة";
|
|
165
|
+
--psp-label--zebra-color--content: "لون التناوب";
|
|
153
166
|
--psp-label--chart-type--content: "نوع الشكل";
|
|
154
167
|
|
|
155
168
|
--psp-label--auto-alt-y-axis--content: "محور Y تلقائي";
|
package/src/themes/intl/de.css
CHANGED
|
@@ -74,16 +74,13 @@ perspective-viewer {
|
|
|
74
74
|
|
|
75
75
|
/* Column Settings */
|
|
76
76
|
--psp-label--color--content: "Farbe";
|
|
77
|
-
--psp-label--format--content: "Format";
|
|
78
77
|
--psp-label--timezone--content: "Zeitzone";
|
|
79
78
|
--psp-label--date-style--content: "Datumsstil";
|
|
80
79
|
--psp-label--time-style--content: "Zeitstil";
|
|
81
|
-
--psp-label--
|
|
82
|
-
--psp-label--
|
|
83
|
-
--psp-label--fg-
|
|
84
|
-
--psp-label--bg-
|
|
85
|
-
--psp-label--datetime-color-mode--content: "Farbe";
|
|
86
|
-
--psp-label--string-color-mode--content: "Farbe";
|
|
80
|
+
--psp-label--fg-mode--content: "Vordergrund";
|
|
81
|
+
--psp-label--bg-mode--content: "Hintergrund";
|
|
82
|
+
--psp-label--fg-color--content: "Vordergrund";
|
|
83
|
+
--psp-label--bg-color--content: "Hintergrund";
|
|
87
84
|
--psp-label--series--content: "Serie";
|
|
88
85
|
--psp-label--color-range--content: "Farbspektrum";
|
|
89
86
|
--psp-label--style--content: "Stil";
|
|
@@ -148,6 +145,21 @@ perspective-viewer {
|
|
|
148
145
|
/* Plugin settings */
|
|
149
146
|
--psp-label--edit-mode--content: "Bearbeitungsmodus";
|
|
150
147
|
--psp-label--scroll-lock--content: "Bildlaufsperre";
|
|
148
|
+
--psp-label--column-menus--content: "Spaltenmenüs";
|
|
149
|
+
--psp-label--group-font--content: "Schrift";
|
|
150
|
+
--psp-label--group-rows--content: "Zeilen";
|
|
151
|
+
--psp-label--link--content: "Link";
|
|
152
|
+
--psp-label--font-family--content: "Schriftfamilie";
|
|
153
|
+
--psp-label--font-size--content: "Schriftgröße";
|
|
154
|
+
--psp-label--request-local-fonts--content: "Lokale Schriften";
|
|
155
|
+
--psp-label--local-fonts-blocked--content: "Lokale Schriften blockiert";
|
|
156
|
+
--psp-label--request-local-fonts-title--content: "Zugriff auf die auf diesem Gerät installierten Schriften erlauben";
|
|
157
|
+
--psp-label--local-fonts-blocked-title--content: "Der Zugriff auf lokale Schriften ist für diese Website blockiert. Erlauben Sie ihn in den Website-Einstellungen des Browsers, um sie hier aufzulisten.";
|
|
158
|
+
--psp-label--word-wrap--content: "Zeilenumbruch";
|
|
159
|
+
--psp-label--align--content: "Ausrichtung";
|
|
160
|
+
--psp-label--row-height--content: "Zeilenhöhe";
|
|
161
|
+
--psp-label--zebra-rows--content: "Zebra-Zeilen";
|
|
162
|
+
--psp-label--zebra-color--content: "Zebra-Farbe";
|
|
151
163
|
--psp-label--chart-type--content: "Glyphentyp";
|
|
152
164
|
|
|
153
165
|
/* Plugin settings — viewer-charts */
|
|
@@ -189,6 +201,7 @@ perspective-viewer {
|
|
|
189
201
|
--psp-label--group-legend--content: "Legende";
|
|
190
202
|
--psp-label--group-tooltip--content: "Tooltips";
|
|
191
203
|
--psp-label--group-color--content: "Farbe";
|
|
204
|
+
--psp-label--group-column--content: "Spalte";
|
|
192
205
|
--psp-label--group-format--content: "Format";
|
|
193
206
|
--psp-label--column-size-override--content: "Spaltenbreite";
|
|
194
207
|
}
|
package/src/themes/intl/es.css
CHANGED
|
@@ -74,16 +74,13 @@ perspective-viewer {
|
|
|
74
74
|
|
|
75
75
|
/* Column Settings */
|
|
76
76
|
--psp-label--color--content: "Color";
|
|
77
|
-
--psp-label--format--content: "Formato";
|
|
78
77
|
--psp-label--timezone--content: "Zona horaria";
|
|
79
78
|
--psp-label--date-style--content: "Estilo de fecha";
|
|
80
79
|
--psp-label--time-style--content: "Estilo de hora";
|
|
81
|
-
--psp-label--
|
|
82
|
-
--psp-label--
|
|
83
|
-
--psp-label--fg-
|
|
84
|
-
--psp-label--bg-
|
|
85
|
-
--psp-label--datetime-color-mode--content: "Color";
|
|
86
|
-
--psp-label--string-color-mode--content: "Color";
|
|
80
|
+
--psp-label--fg-mode--content: "Primer plano";
|
|
81
|
+
--psp-label--bg-mode--content: "Fondo";
|
|
82
|
+
--psp-label--fg-color--content: "Primer plano";
|
|
83
|
+
--psp-label--bg-color--content: "Fondo";
|
|
87
84
|
--psp-label--series--content: "Serie";
|
|
88
85
|
--psp-label--color-range--content: "Gama de colores";
|
|
89
86
|
--psp-label--style--content: "Estilo";
|
|
@@ -148,6 +145,21 @@ perspective-viewer {
|
|
|
148
145
|
/* Plugin settings */
|
|
149
146
|
--psp-label--edit-mode--content: "Modo de edición";
|
|
150
147
|
--psp-label--scroll-lock--content: "Bloqueo de desplazamiento";
|
|
148
|
+
--psp-label--column-menus--content: "Menús de columna";
|
|
149
|
+
--psp-label--group-font--content: "Fuente";
|
|
150
|
+
--psp-label--group-rows--content: "Filas";
|
|
151
|
+
--psp-label--link--content: "Enlace";
|
|
152
|
+
--psp-label--font-family--content: "Familia de fuente";
|
|
153
|
+
--psp-label--font-size--content: "Tamaño de fuente";
|
|
154
|
+
--psp-label--request-local-fonts--content: "Fuentes locales";
|
|
155
|
+
--psp-label--local-fonts-blocked--content: "Fuentes locales bloqueadas";
|
|
156
|
+
--psp-label--request-local-fonts-title--content: "Permitir el acceso a las fuentes instaladas en este dispositivo";
|
|
157
|
+
--psp-label--local-fonts-blocked-title--content: "El acceso a las fuentes locales está bloqueado para este sitio. Permítalo en la configuración del sitio del navegador para mostrarlas aquí.";
|
|
158
|
+
--psp-label--word-wrap--content: "Ajuste de línea";
|
|
159
|
+
--psp-label--align--content: "Alineación";
|
|
160
|
+
--psp-label--row-height--content: "Altura de fila";
|
|
161
|
+
--psp-label--zebra-rows--content: "Filas cebra";
|
|
162
|
+
--psp-label--zebra-color--content: "Color cebra";
|
|
151
163
|
--psp-label--chart-type--content: "Tipo de glifo";
|
|
152
164
|
|
|
153
165
|
/* Plugin settings — viewer-charts */
|
|
@@ -189,6 +201,7 @@ perspective-viewer {
|
|
|
189
201
|
--psp-label--group-legend--content: "Leyenda";
|
|
190
202
|
--psp-label--group-tooltip--content: "Descripciones emergentes";
|
|
191
203
|
--psp-label--group-color--content: "Color";
|
|
204
|
+
--psp-label--group-column--content: "Columna";
|
|
192
205
|
--psp-label--group-format--content: "Formato";
|
|
193
206
|
--psp-label--column-size-override--content: "Ancho de columna";
|
|
194
207
|
}
|