@perspective-dev/viewer 5.5.1 → 5.6.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/README.md +82 -3
- package/dist/cdn/perspective-viewer.js +9 -9
- package/dist/cdn/perspective-viewer.js.map +3 -3
- 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/column-format.js +2 -0
- package/dist/esm/column-format.js.map +7 -0
- package/dist/esm/extensions.d.ts +3 -13
- package/dist/esm/perspective-viewer.d.ts +1 -1
- package/dist/esm/perspective-viewer.js +9 -9
- package/dist/esm/perspective-viewer.js.map +3 -3
- package/dist/esm/plugin.d.ts +4 -1
- package/dist/esm/select-detail.d.ts +15 -0
- package/dist/esm/select-detail.js +2 -0
- package/dist/esm/select-detail.js.map +7 -0
- package/dist/esm/ts-rs/DerivedTableConfig.d.ts +35 -0
- package/dist/esm/ts-rs/PanelViewerConfig.d.ts +7 -6
- package/dist/esm/ts-rs/RestoreOptions.d.ts +3 -7
- package/dist/esm/ts-rs/TableRef.d.ts +9 -0
- package/dist/esm/ts-rs/ViewerConfig.d.ts +7 -6
- package/dist/esm/ts-rs/ViewerConfigUpdate.d.ts +7 -5
- package/dist/esm/ts-rs/WorkspaceConfig.d.ts +9 -1
- package/dist/esm/ts-rs/WorkspaceConfigUpdate.d.ts +9 -0
- package/dist/wasm/perspective-viewer.d.ts +115 -35
- package/dist/wasm/perspective-viewer.js +314 -190
- package/dist/wasm/perspective-viewer.wasm +0 -0
- package/dist/wasm/perspective-viewer.wasm.d.ts +21 -17
- package/package.json +31 -18
- package/src/css/column-selector.css +205 -50
- package/src/css/ui/containers/context-menu.css +12 -1
- package/src/css/ui/containers/dropdown-menu-items.css +29 -0
- package/src/css/ui/containers/scroll-panel.css +4 -1
- package/src/rust/agent/client/protocol.rs +0 -89
- package/src/rust/agent/markdown/mod.rs +0 -238
- package/src/rust/agent/preamble.md +1 -0
- package/src/rust/agent/tools.rs +2 -16
- package/src/rust/components/column_selector/active_column.rs +15 -5
- package/src/rust/components/column_selector/add_expression_button.rs +9 -26
- package/src/rust/components/column_selector/config_selector.rs +34 -6
- package/src/rust/components/column_selector/derived_control.rs +229 -0
- package/src/rust/components/column_selector/expr_edit_button.rs +3 -2
- package/src/rust/components/column_selector/filter_column.rs +0 -2
- package/src/rust/components/column_selector/inactive_column.rs +4 -5
- package/src/rust/components/column_selector/pivot_column.rs +21 -1
- package/src/rust/components/column_selector/sort_column.rs +4 -3
- package/src/rust/components/column_selector.rs +21 -1
- package/src/rust/components/column_settings_sidebar/save_settings.rs +5 -3
- package/src/rust/components/column_settings_sidebar/style_tab.rs +1 -0
- package/src/rust/components/column_settings_sidebar.rs +25 -19
- package/src/rust/components/debug_panel.rs +38 -29
- package/src/rust/components/dragdrop_list.rs +8 -9
- package/src/rust/components/main_panel/msg.rs +6 -4
- package/src/rust/components/main_panel/reconcile.rs +6 -1
- package/src/rust/components/main_panel/render.rs +70 -51
- package/src/rust/components/main_panel/update.rs +39 -1
- package/src/rust/components/main_panel.rs +97 -59
- package/src/rust/components/new_panel_menu.rs +69 -1
- package/src/rust/components/panel_menu.rs +63 -12
- package/src/rust/components/plugin_tab.rs +3 -2
- package/src/rust/components/render_warning.rs +2 -1
- package/src/rust/components/settings_panel.rs +4 -4
- package/src/rust/components/status_bar.rs +74 -24
- package/src/rust/components/status_indicator.rs +8 -1
- package/src/rust/components/viewer/filters.rs +13 -1
- package/src/rust/components/viewer/msg.rs +4 -0
- package/src/rust/components/viewer/panels.rs +41 -1
- package/src/rust/components/viewer/render.rs +1 -0
- package/src/rust/components/viewer/settings.rs +0 -22
- package/src/rust/components/viewer/snapshots.rs +2 -2
- package/src/rust/components/viewer/wiring.rs +14 -0
- package/src/rust/components/viewer.rs +45 -25
- package/src/rust/components/window_editor.rs +5 -4
- package/src/rust/config/column_config_schema.rs +38 -377
- package/src/rust/config/number_string_format.rs +0 -94
- package/src/rust/config/options.rs +3 -7
- package/src/rust/config/plugin_static_config.rs +0 -42
- package/src/rust/config/viewer_config.rs +47 -17
- package/src/rust/config/workspace_config.rs +158 -128
- package/src/rust/custom_elements/viewer.rs +187 -131
- package/src/rust/custom_events.rs +33 -18
- package/src/rust/derived.rs +266 -0
- package/src/rust/exprtk/tokenize.rs +0 -54
- package/src/rust/js/plugin.rs +1 -1
- package/src/rust/lib.rs +3 -0
- package/src/rust/presentation.rs +23 -4
- package/src/rust/queries/column_locator.rs +55 -48
- package/src/rust/queries/get_viewer_config.rs +1 -1
- package/src/rust/queries/plugin_column_styles.rs +22 -23
- package/src/rust/renderer/dispatch.rs +2 -0
- package/src/rust/renderer/plugin_config.rs +652 -487
- package/src/rust/renderer/props.rs +4 -0
- package/src/rust/renderer/registry.rs +2 -6
- package/src/rust/renderer/state.rs +205 -0
- package/src/rust/renderer.rs +201 -59
- package/src/rust/session/metadata.rs +30 -9
- package/src/rust/session/op_queue.rs +430 -0
- package/src/rust/session/panel_state.rs +327 -0
- package/src/rust/session/view_subscription.rs +42 -3
- package/src/rust/session.rs +900 -395
- package/src/rust/tasks/apply_global_filters.rs +49 -35
- package/src/rust/tasks/auto_pause.rs +22 -12
- package/src/rust/tasks/copy_export.rs +5 -0
- package/src/rust/tasks/create_panel.rs +34 -9
- package/src/rust/tasks/derive.rs +312 -0
- package/src/rust/tasks/mod.rs +6 -2
- package/src/rust/tasks/pipeline.rs +145 -89
- package/src/rust/tasks/reset_all.rs +31 -20
- package/src/rust/tasks/restore_derived.rs +200 -0
- package/src/rust/tasks/restore_panel.rs +153 -141
- package/src/rust/tasks/send_column_config.rs +56 -29
- package/src/rust/tasks/send_plugin_config.rs +52 -23
- package/src/rust/tasks/set_edit_mode.rs +33 -25
- package/src/rust/tasks/sync_update_panels.rs +39 -12
- package/src/rust/tasks/table_lifecycle.rs +70 -34
- package/src/rust/tasks/transactional_restore.rs +387 -0
- package/src/rust/tasks/update_theme.rs +45 -6
- package/src/rust/ui/containers/scroll_panel.rs +17 -10
- package/src/rust/ui/containers/split_panel.rs +0 -41
- package/src/rust/ui/context_menu.rs +157 -18
- package/src/rust/ui/font_loader.rs +23 -35
- package/src/rust/ui/form/mirrored_textarea.rs +0 -12
- package/src/rust/ui/form/multi_stop_gradient_selector.rs +0 -89
- package/src/rust/ui/form/palette_selector.rs +0 -51
- package/src/rust/ui/portal.rs +4 -10
- package/src/rust/utils/browser/mod.rs +2 -3
- package/src/{ts/perspective-viewer.inline.ts → rust/utils/browser/mouse.rs} +11 -10
- package/src/rust/utils/browser/request_animation_frame.rs +27 -8
- package/src/rust/utils/css.rs +0 -360
- package/src/rust/utils/local_fonts.rs +0 -36
- package/src/rust/utils/mod.rs +2 -3
- package/src/rust/utils/modal_position.rs +15 -21
- package/src/rust/utils/{browser/tests/mod.rs → plugin_host_state.rs} +31 -1
- package/src/rust/utils/pubsub.rs +0 -1
- package/src/rust/workspace.rs +187 -140
- package/src/svg/derived.svg +6 -0
- package/src/themes/icons.css +1 -0
- package/src/themes/intl/ar.css +10 -0
- package/src/themes/intl/de.css +10 -0
- package/src/themes/intl/es.css +10 -0
- package/src/themes/intl/fr.css +10 -0
- package/src/themes/intl/ja.css +10 -0
- package/src/themes/intl/pt.css +10 -0
- package/src/themes/intl/zh.css +10 -0
- package/src/themes/intl.css +10 -0
- package/src/ts/extensions.ts +3 -36
- package/src/ts/perspective-viewer.ts +1 -1
- package/src/ts/plugin.ts +4 -1
- package/src/{rust/utils/tests/request_animation_frame.rs → ts/select-detail.ts} +32 -26
- package/src/ts/ts-rs/DerivedTableConfig.ts +22 -0
- package/src/ts/ts-rs/PanelViewerConfig.ts +7 -6
- package/src/ts/ts-rs/RestoreOptions.ts +3 -7
- package/src/ts/ts-rs/TableRef.ts +9 -0
- package/src/ts/ts-rs/ViewerConfig.ts +7 -6
- package/src/ts/ts-rs/ViewerConfigUpdate.ts +7 -5
- package/src/ts/ts-rs/WorkspaceConfig.ts +7 -1
- package/src/ts/ts-rs/WorkspaceConfigUpdate.ts +7 -0
- package/dist/esm/perspective-viewer.inline.d.ts +0 -3
- package/dist/esm/perspective-viewer.inline.js +0 -21
- package/dist/esm/perspective-viewer.inline.js.map +0 -7
- package/src/rust/tasks/restore_and_render.rs +0 -107
- package/src/rust/utils/browser/tests/debounce.rs +0 -163
- package/src/rust/utils/tests/mod.rs +0 -14
- package/src/rust/utils/tests/pubsub.rs +0 -95
- /package/dist/wasm/snippets/{perspective-viewer-6813b568219c6763 → perspective-viewer-d0d525977a1acba2}/inline0.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-6813b568219c6763 → perspective-viewer-d0d525977a1acba2}/inline1.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-6813b568219c6763 → perspective-viewer-d0d525977a1acba2}/inline2.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-6813b568219c6763 → perspective-viewer-d0d525977a1acba2}/inline3.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-6813b568219c6763 → perspective-viewer-d0d525977a1acba2}/inline4.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-6813b568219c6763 → perspective-viewer-d0d525977a1acba2}/inline5.js +0 -0
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
use std::collections::{HashMap, HashSet};
|
|
14
14
|
use std::iter::IntoIterator;
|
|
15
15
|
use std::ops::{Deref, DerefMut};
|
|
16
|
+
use std::rc::Rc;
|
|
16
17
|
|
|
17
18
|
use perspective_client::config::*;
|
|
18
19
|
use perspective_js::apierror;
|
|
@@ -56,7 +57,18 @@ impl DerefMut for SessionMetadata {
|
|
|
56
57
|
}
|
|
57
58
|
}
|
|
58
59
|
|
|
59
|
-
|
|
60
|
+
/// An OWNED read guard over a panel's [`SessionMetadata`]: a snapshot that
|
|
61
|
+
/// borrows nothing, so holding one can never make a later state write panic.
|
|
62
|
+
pub struct MetadataRef(pub(super) Rc<SessionMetadata>);
|
|
63
|
+
|
|
64
|
+
impl Deref for MetadataRef {
|
|
65
|
+
type Target = SessionMetadata;
|
|
66
|
+
|
|
67
|
+
fn deref(&self) -> &Self::Target {
|
|
68
|
+
&self.0
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
60
72
|
pub type MetadataMutRef<'a> = std::cell::RefMut<'a, SessionMetadata>;
|
|
61
73
|
|
|
62
74
|
/// TODO the multiple `Option` types could probably be merged since they are
|
|
@@ -75,15 +87,18 @@ pub struct SessionMetadataState {
|
|
|
75
87
|
impl SessionMetadata {
|
|
76
88
|
/// Creates a new `SessionMetadata` from a `JsPerspectiveTable`.
|
|
77
89
|
pub(super) async fn from_table(table: &perspective_client::Table) -> ApiResult<Self> {
|
|
78
|
-
let features =
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
90
|
+
let (features, column_names, table_schema, edit_port) = futures::join!(
|
|
91
|
+
table.get_features(),
|
|
92
|
+
table.columns(),
|
|
93
|
+
table.schema(),
|
|
94
|
+
table.make_port()
|
|
95
|
+
);
|
|
96
|
+
|
|
82
97
|
Ok(Self(Some(SessionMetadataState {
|
|
83
|
-
features
|
|
84
|
-
column_names
|
|
85
|
-
table_schema
|
|
86
|
-
edit_port,
|
|
98
|
+
features: features?,
|
|
99
|
+
column_names: column_names?,
|
|
100
|
+
table_schema: table_schema?,
|
|
101
|
+
edit_port: edit_port? as f64,
|
|
87
102
|
..SessionMetadataState::default()
|
|
88
103
|
})))
|
|
89
104
|
}
|
|
@@ -290,6 +305,12 @@ impl SessionMetadata {
|
|
|
290
305
|
self.as_ref().map(|meta| meta.edit_port)
|
|
291
306
|
}
|
|
292
307
|
|
|
308
|
+
/// The type of a column of the `Table` itself — expression and window
|
|
309
|
+
/// columns, which belong to a config, excluded.
|
|
310
|
+
pub fn get_table_schema_type(&self, name: &str) -> Option<ColumnType> {
|
|
311
|
+
self.as_ref()?.table_schema.get(name).copied()
|
|
312
|
+
}
|
|
313
|
+
|
|
293
314
|
/// Returns the type of a column name relative to the `Table`. Despite the
|
|
294
315
|
/// name, `get_column_table_type()` also returns the `Table` type for
|
|
295
316
|
/// Expressions, which despite living on the `View` still have a `table`
|
|
@@ -0,0 +1,430 @@
|
|
|
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
|
+
//! The per-panel op queue: every writer of a panel's committed state is an
|
|
14
|
+
//! [`OpKind`] submitted here, and the queue's single drain runs them one at a
|
|
15
|
+
//! time in SUBMIT order.
|
|
16
|
+
|
|
17
|
+
use std::cell::{Cell, RefCell};
|
|
18
|
+
use std::collections::VecDeque;
|
|
19
|
+
use std::rc::Rc;
|
|
20
|
+
|
|
21
|
+
use futures::channel::oneshot;
|
|
22
|
+
use futures::future::LocalBoxFuture;
|
|
23
|
+
use perspective_client::config::ViewConfigUpdate;
|
|
24
|
+
use perspective_js::utils::*;
|
|
25
|
+
|
|
26
|
+
use crate::config::{OptionalUpdate, ViewerConfigUpdate};
|
|
27
|
+
use crate::utils::InFlightGuard;
|
|
28
|
+
|
|
29
|
+
/// The render an op's ticket waits on after its write is finished.
|
|
30
|
+
pub type RenderFuture = LocalBoxFuture<'static, ApiResult<()>>;
|
|
31
|
+
|
|
32
|
+
/// An op's write, which yields the render its ticket resolves with, if any.
|
|
33
|
+
pub type StepFuture = LocalBoxFuture<'static, ApiResult<Option<RenderFuture>>>;
|
|
34
|
+
type Step = Box<dyn FnOnce(OpCtx) -> StepFuture>;
|
|
35
|
+
|
|
36
|
+
/// What a UI edit changes — enough to PROJECT it onto the committed state
|
|
37
|
+
/// before the drain commits it.
|
|
38
|
+
#[derive(Clone)]
|
|
39
|
+
pub enum EditDelta {
|
|
40
|
+
View(Box<ViewConfigUpdate>),
|
|
41
|
+
Title(Option<String>),
|
|
42
|
+
|
|
43
|
+
/// An edit of the renderer's own state, which the renderer projects.
|
|
44
|
+
Renderer,
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
pub enum OpKind {
|
|
48
|
+
/// A UI edit, which a later op may supersede unless it swaps the plugin.
|
|
49
|
+
Edit {
|
|
50
|
+
delta: EditDelta,
|
|
51
|
+
swaps_plugin: bool,
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
/// A restore-family op, which a later op may supersede when it carries
|
|
55
|
+
/// its `update`.
|
|
56
|
+
Restore {
|
|
57
|
+
update: Option<Rc<ViewerConfigUpdate>>,
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
/// The element's global filter, broadcast to this panel.
|
|
61
|
+
Overlay,
|
|
62
|
+
|
|
63
|
+
/// A `load()`.
|
|
64
|
+
Load { table_known: bool },
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
impl OpKind {
|
|
68
|
+
fn writes(&self) -> Option<Writes<'_>> {
|
|
69
|
+
match self {
|
|
70
|
+
OpKind::Edit {
|
|
71
|
+
delta: EditDelta::View(update),
|
|
72
|
+
swaps_plugin: false,
|
|
73
|
+
} => Some((update, None)),
|
|
74
|
+
OpKind::Restore {
|
|
75
|
+
update: Some(update),
|
|
76
|
+
} => Some((&update.view_config, Some(update))),
|
|
77
|
+
OpKind::Edit { .. }
|
|
78
|
+
| OpKind::Restore { .. }
|
|
79
|
+
| OpKind::Load { .. }
|
|
80
|
+
| OpKind::Overlay => None,
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/// Whether this LATER op makes the `earlier` one pointless: everything
|
|
85
|
+
/// `earlier` would write, this op overwrites.
|
|
86
|
+
fn covers(&self, earlier: &OpKind) -> bool {
|
|
87
|
+
match (self, earlier) {
|
|
88
|
+
(OpKind::Overlay, OpKind::Overlay) => true,
|
|
89
|
+
(OpKind::Overlay, _) | (_, OpKind::Overlay) => false,
|
|
90
|
+
(OpKind::Load { .. }, OpKind::Load { .. }) => true,
|
|
91
|
+
(OpKind::Load { table_known }, _) => *table_known,
|
|
92
|
+
(_, OpKind::Load { .. }) => false,
|
|
93
|
+
(later, earlier) => match (later.writes(), earlier.writes()) {
|
|
94
|
+
(Some(later), Some(earlier)) => overwrites(later, earlier),
|
|
95
|
+
_ => false,
|
|
96
|
+
},
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/// A submitted op's handle: resolves exactly once, when the op (or, for a
|
|
102
|
+
/// superseded op, the op that superseded it) has finished AND rendered.
|
|
103
|
+
pub struct Ticket(oneshot::Receiver<ApiResult<()>>);
|
|
104
|
+
|
|
105
|
+
impl Ticket {
|
|
106
|
+
/// A ticket for an op that will never run (the panel is disposed).
|
|
107
|
+
pub fn settled(result: ApiResult<()>) -> Self {
|
|
108
|
+
let (sender, receiver) = oneshot::channel();
|
|
109
|
+
let _ = sender.send(result);
|
|
110
|
+
Ticket(receiver)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
pub async fn settle(self) -> ApiResult<()> {
|
|
114
|
+
match self.0.await {
|
|
115
|
+
Ok(result) => result,
|
|
116
|
+
Err(_) => Err(ApiError::new("Cancelled")),
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/// What a running step can ask of the queue.
|
|
122
|
+
#[derive(Clone)]
|
|
123
|
+
pub struct OpCtx {
|
|
124
|
+
queue: std::rc::Rc<OpQueue>,
|
|
125
|
+
kind_is_load: bool,
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
impl OpCtx {
|
|
129
|
+
/// Whether a `load()` submitted since this op began makes its table binding
|
|
130
|
+
/// pointless: a `load()` abandons for ANY later `load()`, a restore's
|
|
131
|
+
/// rebind only for one already known to carry a `Table`.
|
|
132
|
+
pub fn is_superseded(&self) -> bool {
|
|
133
|
+
self.queue
|
|
134
|
+
.entries
|
|
135
|
+
.borrow()
|
|
136
|
+
.iter()
|
|
137
|
+
.any(|later| match later.kind {
|
|
138
|
+
OpKind::Load { table_known } => self.kind_is_load || table_known,
|
|
139
|
+
_ => false,
|
|
140
|
+
})
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
struct Entry {
|
|
145
|
+
kind: OpKind,
|
|
146
|
+
step: Step,
|
|
147
|
+
replies: Vec<oneshot::Sender<ApiResult<()>>>,
|
|
148
|
+
guards: Vec<Pending>,
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
impl Entry {
|
|
152
|
+
fn resolve(self, result: ApiResult<()>) {
|
|
153
|
+
{
|
|
154
|
+
let replies = self.replies;
|
|
155
|
+
for reply in replies {
|
|
156
|
+
let _ = reply.send(result.clone());
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/// One unsettled op's accounts, released together when its ticket resolves — on
|
|
163
|
+
/// every path (rendered, rejected, superseded, disposed): the busy indicator,
|
|
164
|
+
/// and the queue's own count of ops still in flight.
|
|
165
|
+
struct Pending {
|
|
166
|
+
_busy: InFlightGuard,
|
|
167
|
+
queue: std::rc::Weak<OpQueue>,
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
impl Drop for Pending {
|
|
171
|
+
fn drop(&mut self) {
|
|
172
|
+
if let Some(queue) = self.queue.upgrade() {
|
|
173
|
+
queue.in_flight.set(queue.in_flight.get() - 1);
|
|
174
|
+
if queue.in_flight.get() == 0 {
|
|
175
|
+
for waiter in queue.idle_waiters.take() {
|
|
176
|
+
let _ = waiter.send(());
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/// What happened to an entry the drain took off the queue without running.
|
|
184
|
+
pub enum Exit {
|
|
185
|
+
Superseded,
|
|
186
|
+
Rejected,
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
#[derive(Default)]
|
|
190
|
+
pub struct OpQueue {
|
|
191
|
+
entries: RefCell<VecDeque<Entry>>,
|
|
192
|
+
draining: Cell<bool>,
|
|
193
|
+
load_running: Cell<bool>,
|
|
194
|
+
|
|
195
|
+
/// Ops whose tickets have not resolved — queued, running, or committed with
|
|
196
|
+
/// a detached render still to land.
|
|
197
|
+
in_flight: Cell<usize>,
|
|
198
|
+
idle_waiters: RefCell<Vec<oneshot::Sender<()>>>,
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
impl OpQueue {
|
|
202
|
+
/// Append an op.
|
|
203
|
+
pub fn push(
|
|
204
|
+
self: &std::rc::Rc<Self>,
|
|
205
|
+
kind: OpKind,
|
|
206
|
+
guard: InFlightGuard,
|
|
207
|
+
step: impl FnOnce(OpCtx) -> StepFuture + 'static,
|
|
208
|
+
) -> (Ticket, bool) {
|
|
209
|
+
let (sender, receiver) = oneshot::channel();
|
|
210
|
+
self.in_flight.set(self.in_flight.get() + 1);
|
|
211
|
+
self.entries.borrow_mut().push_back(Entry {
|
|
212
|
+
kind,
|
|
213
|
+
step: Box::new(step),
|
|
214
|
+
replies: vec![sender],
|
|
215
|
+
guards: vec![Pending {
|
|
216
|
+
_busy: guard,
|
|
217
|
+
queue: std::rc::Rc::downgrade(self),
|
|
218
|
+
}],
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
(Ticket(receiver), !self.draining.replace(true))
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/// The deltas of every pending [`OpKind::Edit`], in submit order.
|
|
225
|
+
pub fn pending_edits(&self) -> Vec<EditDelta> {
|
|
226
|
+
self.entries
|
|
227
|
+
.borrow()
|
|
228
|
+
.iter()
|
|
229
|
+
.filter_map(|entry| match &entry.kind {
|
|
230
|
+
OpKind::Edit { delta, .. } => Some(delta.clone()),
|
|
231
|
+
_ => None,
|
|
232
|
+
})
|
|
233
|
+
.collect()
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/// Resolves once every submitted op's ticket has — nothing queued, nothing
|
|
237
|
+
/// running, no committed render still to land.
|
|
238
|
+
pub async fn idle(&self) {
|
|
239
|
+
while self.in_flight.get() > 0 {
|
|
240
|
+
let (sender, receiver) = oneshot::channel();
|
|
241
|
+
self.idle_waiters.borrow_mut().push(sender);
|
|
242
|
+
let _ = receiver.await;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/// Whether a `load()` is queued or running.
|
|
247
|
+
pub fn has_load(&self) -> bool {
|
|
248
|
+
self.load_running.get() || self.has_queued_load()
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/// Whether a `load()` is queued behind the running op.
|
|
252
|
+
pub fn has_queued_load(&self) -> bool {
|
|
253
|
+
self.entries
|
|
254
|
+
.borrow()
|
|
255
|
+
.iter()
|
|
256
|
+
.any(|entry| matches!(entry.kind, OpKind::Load { .. }))
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/// Run every queued op, one at a time, in submit order.
|
|
260
|
+
pub async fn drain(self: std::rc::Rc<Self>, on_exit: impl Fn(Exit)) {
|
|
261
|
+
loop {
|
|
262
|
+
let Some(mut entry) = self.entries.borrow_mut().pop_front() else {
|
|
263
|
+
break;
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
let is_edit = matches!(entry.kind, OpKind::Edit {
|
|
267
|
+
delta: EditDelta::View(_),
|
|
268
|
+
..
|
|
269
|
+
});
|
|
270
|
+
let superseded = {
|
|
271
|
+
let mut entries = self.entries.borrow_mut();
|
|
272
|
+
match entries
|
|
273
|
+
.iter_mut()
|
|
274
|
+
.find(|later| later.kind.covers(&entry.kind))
|
|
275
|
+
{
|
|
276
|
+
Some(later) => {
|
|
277
|
+
later.replies.append(&mut entry.replies);
|
|
278
|
+
later.guards.append(&mut entry.guards);
|
|
279
|
+
true
|
|
280
|
+
},
|
|
281
|
+
None => false,
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
if superseded {
|
|
286
|
+
if is_edit {
|
|
287
|
+
on_exit(Exit::Superseded);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
let Entry {
|
|
294
|
+
kind,
|
|
295
|
+
step,
|
|
296
|
+
replies,
|
|
297
|
+
guards,
|
|
298
|
+
} = entry;
|
|
299
|
+
|
|
300
|
+
let kind_is_load = matches!(kind, OpKind::Load { .. });
|
|
301
|
+
self.load_running.set(kind_is_load);
|
|
302
|
+
let outcome = step(OpCtx {
|
|
303
|
+
queue: self.clone(),
|
|
304
|
+
kind_is_load,
|
|
305
|
+
})
|
|
306
|
+
.await;
|
|
307
|
+
|
|
308
|
+
self.load_running.set(false);
|
|
309
|
+
match outcome {
|
|
310
|
+
Ok(None) => {
|
|
311
|
+
let result = Ok(());
|
|
312
|
+
for reply in replies {
|
|
313
|
+
let _ = reply.send(result.clone());
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
drop(guards);
|
|
317
|
+
},
|
|
318
|
+
Ok(Some(render)) => ApiFuture::spawn(async move {
|
|
319
|
+
{
|
|
320
|
+
let result = render.await;
|
|
321
|
+
for reply in replies {
|
|
322
|
+
let _ = reply.send(result.clone());
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
drop(guards);
|
|
326
|
+
};
|
|
327
|
+
Ok(())
|
|
328
|
+
}),
|
|
329
|
+
Err(error) => {
|
|
330
|
+
if is_edit {
|
|
331
|
+
tracing::warn!("Edit rejected: {}", error);
|
|
332
|
+
on_exit(Exit::Rejected);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
{
|
|
336
|
+
let result = Err(error);
|
|
337
|
+
for reply in replies {
|
|
338
|
+
let _ = reply.send(result.clone());
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
drop(guards);
|
|
342
|
+
};
|
|
343
|
+
},
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
self.draining.set(false);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/// Reject everything still queued — the panel is gone.
|
|
351
|
+
pub fn reject_all(&self, error: ApiResult<()>) {
|
|
352
|
+
let entries = std::mem::take(&mut *self.entries.borrow_mut());
|
|
353
|
+
for entry in entries {
|
|
354
|
+
entry.resolve(error.clone());
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
type Writes<'a> = (&'a ViewConfigUpdate, Option<&'a ViewerConfigUpdate>);
|
|
360
|
+
|
|
361
|
+
/// Whether `later` overwrites every top-level key `earlier` writes, which a
|
|
362
|
+
/// merging `plugin_config` / `columns_config` update never does.
|
|
363
|
+
fn overwrites((later_view, later): Writes, (earlier_view, earlier): Writes) -> bool {
|
|
364
|
+
let empty = ViewerConfigUpdate::default();
|
|
365
|
+
let later = later.unwrap_or(&empty);
|
|
366
|
+
let ViewerConfigUpdate {
|
|
367
|
+
version,
|
|
368
|
+
plugin,
|
|
369
|
+
plugin_config,
|
|
370
|
+
columns_config,
|
|
371
|
+
settings,
|
|
372
|
+
theme,
|
|
373
|
+
title,
|
|
374
|
+
table,
|
|
375
|
+
view_config,
|
|
376
|
+
} = earlier.unwrap_or(&empty);
|
|
377
|
+
|
|
378
|
+
let _ = (version, view_config);
|
|
379
|
+
|
|
380
|
+
let ViewConfigUpdate {
|
|
381
|
+
group_by,
|
|
382
|
+
split_by,
|
|
383
|
+
columns,
|
|
384
|
+
filter,
|
|
385
|
+
sort,
|
|
386
|
+
expressions,
|
|
387
|
+
windows,
|
|
388
|
+
aggregates,
|
|
389
|
+
group_by_depth,
|
|
390
|
+
filter_op,
|
|
391
|
+
group_rollup_mode,
|
|
392
|
+
split_rollup_mode,
|
|
393
|
+
} = earlier_view;
|
|
394
|
+
|
|
395
|
+
fn merges<T: Clone>(update: &OptionalUpdate<T>) -> bool {
|
|
396
|
+
matches!(update, OptionalUpdate::Update(_))
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
fn kept<T: Clone>(earlier: &OptionalUpdate<T>, later: &OptionalUpdate<T>) -> bool {
|
|
400
|
+
!matches!(earlier, OptionalUpdate::Missing) && matches!(later, OptionalUpdate::Missing)
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
let merging = merges(plugin_config)
|
|
404
|
+
|| merges(columns_config)
|
|
405
|
+
|| merges(&later.plugin_config)
|
|
406
|
+
|| merges(&later.columns_config);
|
|
407
|
+
|
|
408
|
+
let groups = |x: &ViewConfigUpdate| x.group_by.is_some() || x.group_rollup_mode.is_some();
|
|
409
|
+
let kept_view = (group_by.is_some() || group_rollup_mode.is_some()) && !groups(later_view)
|
|
410
|
+
|| split_by.is_some() && later_view.split_by.is_none()
|
|
411
|
+
|| columns.is_some() && later_view.columns.is_none()
|
|
412
|
+
|| filter.is_some() && later_view.filter.is_none()
|
|
413
|
+
|| sort.is_some() && later_view.sort.is_none()
|
|
414
|
+
|| expressions.is_some() && later_view.expressions.is_none()
|
|
415
|
+
|| windows.is_some() && later_view.windows.is_none()
|
|
416
|
+
|| aggregates.is_some() && later_view.aggregates.is_none()
|
|
417
|
+
|| group_by_depth.is_some() && later_view.group_by_depth.is_none()
|
|
418
|
+
|| filter_op.is_some() && later_view.filter_op.is_none()
|
|
419
|
+
|| split_rollup_mode.is_some() && later_view.split_rollup_mode.is_none();
|
|
420
|
+
|
|
421
|
+
!merging
|
|
422
|
+
&& !kept_view
|
|
423
|
+
&& !kept(plugin, &later.plugin)
|
|
424
|
+
&& !kept(plugin_config, &later.plugin_config)
|
|
425
|
+
&& !kept(columns_config, &later.columns_config)
|
|
426
|
+
&& !kept(settings, &later.settings)
|
|
427
|
+
&& !kept(theme, &later.theme)
|
|
428
|
+
&& !kept(title, &later.title)
|
|
429
|
+
&& !kept(table, &later.table)
|
|
430
|
+
}
|