@perspective-dev/viewer 4.0.1 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cdn/perspective-viewer.js +2 -2
- package/dist/cdn/perspective-viewer.js.map +4 -4
- package/dist/css/dracula.css +1 -1
- package/dist/css/gruvbox-dark.css +1 -1
- package/dist/css/gruvbox.css +1 -1
- package/dist/css/icons.css +1 -1
- package/dist/css/monokai.css +1 -1
- package/dist/css/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/esm/extensions.d.ts +23 -2
- package/dist/esm/perspective-viewer.d.ts +2 -7
- package/dist/esm/perspective-viewer.inline.js +2 -2
- package/dist/esm/perspective-viewer.inline.js.map +4 -4
- package/dist/esm/perspective-viewer.js +2 -2
- package/dist/esm/perspective-viewer.js.map +4 -4
- package/dist/esm/plugin.d.ts +1 -1
- package/dist/esm/ts-rs/ViewerConfigUpdate.d.ts +1 -0
- package/dist/wasm/perspective-viewer.d.ts +218 -46
- package/dist/wasm/perspective-viewer.js +1250 -761
- package/dist/wasm/perspective-viewer.wasm +0 -0
- package/dist/wasm/perspective-viewer.wasm.d.ts +38 -19
- package/package.json +1 -1
- package/src/less/containers/scroll-panel.less +0 -1
- package/src/less/plugin-selector.less +15 -5
- package/src/less/status-bar.less +75 -27
- package/src/less/viewer.less +140 -58
- package/src/rust/components/column_dropdown.rs +21 -21
- package/src/rust/components/column_selector/active_column.rs +131 -120
- package/src/rust/components/column_selector/add_expression_button.rs +5 -0
- package/src/rust/components/column_selector/aggregate_selector.rs +8 -4
- package/src/rust/components/column_selector/config_selector.rs +170 -161
- package/src/rust/components/column_selector/empty_column.rs +16 -11
- package/src/rust/components/column_selector/{expression_toolbar.rs → expr_edit_button.rs} +7 -0
- package/src/rust/components/column_selector/filter_column.rs +195 -194
- package/src/rust/components/column_selector/inactive_column.rs +82 -67
- package/src/rust/components/column_selector/pivot_column.rs +16 -11
- package/src/rust/components/column_selector/sort_column.rs +9 -7
- package/src/rust/components/column_selector.rs +42 -37
- package/src/rust/components/column_settings_sidebar/save_settings.rs +3 -1
- package/src/rust/components/column_settings_sidebar/style_tab/agg_depth_selector.rs +58 -0
- package/src/rust/components/column_settings_sidebar/style_tab/symbol/row_selector.rs +6 -6
- package/src/rust/components/column_settings_sidebar/style_tab/symbol/symbol_pairs.rs +2 -94
- package/src/rust/components/column_settings_sidebar/style_tab/symbol/symbol_pairs_item.rs +111 -0
- package/src/rust/components/column_settings_sidebar/style_tab/symbol.rs +3 -3
- package/src/rust/components/column_settings_sidebar/style_tab.rs +23 -83
- package/src/rust/components/{column_settings_sidebar/sidebar.rs → column_settings_sidebar.rs} +198 -171
- package/src/rust/components/containers/dragdrop_list.rs +20 -20
- package/src/rust/components/containers/dropdown_menu.rs +4 -6
- package/src/rust/components/containers/mod.rs +1 -4
- package/src/rust/components/containers/scroll_panel.rs +80 -80
- package/src/rust/components/containers/scroll_panel_item.rs +36 -36
- package/src/rust/components/containers/select.rs +46 -44
- package/src/rust/components/containers/sidebar.rs +3 -19
- package/src/rust/components/{column_settings_sidebar/style_tab/symbol/symbol_config.rs → containers/sidebar_close_button.rs} +15 -9
- package/src/rust/components/containers/split_panel.rs +212 -200
- package/src/rust/components/containers/tab_list.rs +11 -11
- package/src/rust/components/copy_dropdown.rs +22 -25
- package/src/rust/components/datetime_column_style/custom.rs +19 -19
- package/src/rust/components/datetime_column_style/simple.rs +13 -14
- package/src/rust/components/datetime_column_style.rs +75 -76
- package/src/rust/components/editable_header.rs +18 -14
- package/src/rust/components/empty_row.rs +5 -5
- package/src/rust/components/export_dropdown.rs +42 -42
- package/src/rust/components/expression_editor.rs +25 -19
- package/src/rust/components/filter_dropdown.rs +22 -22
- package/src/rust/components/font_loader.rs +11 -9
- package/src/rust/components/form/code_editor.rs +106 -105
- package/src/rust/components/form/color_range_selector.rs +14 -12
- package/src/rust/components/form/color_selector.rs +3 -1
- package/src/rust/components/form/debug.rs +95 -94
- package/src/rust/components/form/highlight.rs +5 -3
- package/src/rust/components/form/mod.rs +3 -2
- package/src/rust/components/form/optional_field.rs +2 -2
- package/src/rust/components/form/{select_field.rs → select_enum_field.rs} +1 -46
- package/src/rust/components/form/select_value_field.rs +64 -0
- package/src/rust/components/function_dropdown.rs +21 -21
- package/src/rust/components/main_panel.rs +219 -0
- package/src/rust/components/mod.rs +6 -6
- package/src/rust/components/modal.rs +42 -42
- package/src/rust/components/number_column_style.rs +34 -88
- package/src/rust/components/plugin_selector.rs +22 -25
- package/src/rust/components/render_warning.rs +9 -6
- package/src/rust/components/settings_panel.rs +82 -0
- package/src/rust/components/status_bar.rs +250 -146
- package/src/rust/components/status_bar_counter.rs +26 -119
- package/src/rust/components/status_indicator.rs +95 -79
- package/src/rust/components/string_column_style.rs +45 -45
- package/src/rust/components/style/style_provider.rs +1 -15
- package/src/rust/components/style_controls/number_string_format/digits_section.rs +1 -1
- package/src/rust/components/style_controls/number_string_format/misc_section.rs +1 -1
- package/src/rust/components/style_controls/number_string_format/style_section.rs +1 -1
- package/src/rust/components/style_controls/number_string_format.rs +45 -46
- package/src/rust/components/type_icon.rs +14 -11
- package/src/rust/components/viewer.rs +241 -384
- package/src/rust/config/columns_config.rs +2 -2
- package/src/rust/config/datetime_column_style.rs +1 -6
- package/src/rust/config/mod.rs +1 -0
- package/src/rust/config/number_column_style.rs +0 -6
- package/src/rust/config/number_string_format.rs +27 -4
- package/src/rust/config/viewer_config.rs +27 -167
- package/src/rust/custom_elements/copy_dropdown.rs +14 -6
- package/src/rust/custom_elements/export_dropdown.rs +15 -7
- package/src/rust/custom_elements/filter_dropdown.rs +4 -4
- package/src/rust/custom_elements/mod.rs +3 -0
- package/src/rust/custom_elements/viewer.rs +353 -161
- package/src/rust/custom_events.rs +55 -32
- package/src/rust/dragdrop.rs +4 -24
- package/src/rust/exprtk/cursor.rs +10 -1
- package/src/rust/exprtk/mod.rs +2 -0
- package/src/rust/exprtk/tokenize.rs +20 -3
- package/src/rust/js/clipboard.rs +2 -2
- package/src/rust/js/mimetype.rs +2 -7
- package/src/rust/js/mod.rs +0 -1
- package/src/rust/js/plugin.rs +7 -0
- package/src/rust/lib.rs +18 -5
- package/src/rust/model/column_locator.rs +82 -0
- package/src/rust/model/columns_iter_set.rs +1 -0
- package/src/rust/model/copy_export.rs +50 -14
- package/src/rust/model/edit_expression.rs +2 -5
- package/src/rust/model/eject.rs +41 -0
- package/src/rust/model/get_viewer_config.rs +4 -28
- package/src/rust/model/intersection_observer.rs +20 -8
- package/src/rust/model/mod.rs +11 -4
- package/src/rust/model/plugin_column_styles.rs +0 -31
- package/src/rust/model/reset_all.rs +38 -0
- package/src/rust/model/resize_observer.rs +34 -7
- package/src/rust/model/restore_and_render.rs +12 -7
- package/src/rust/{utils/scope.rs → model/send_plugin_config.rs} +32 -35
- package/src/rust/model/structural.rs +194 -23
- package/src/rust/model/update_and_render.rs +14 -4
- package/src/rust/{model/create_col.rs → presentation/column_locator.rs} +73 -42
- package/src/rust/{utils/wasm_abi.rs → presentation/sheets.rs} +54 -40
- package/src/rust/presentation.rs +60 -119
- package/src/rust/renderer/activate.rs +20 -5
- package/src/rust/renderer/limits.rs +0 -149
- package/src/rust/renderer/render_timer.rs +1 -1
- package/src/rust/renderer.rs +34 -18
- package/src/rust/root.rs +50 -0
- package/src/rust/session/column_defaults_update.rs +4 -4
- package/src/rust/session/drag_drop_update.rs +1 -1
- package/src/rust/session/metadata.rs +3 -17
- package/src/rust/session/replace_expression_update.rs +1 -2
- package/src/rust/session.rs +162 -82
- package/src/rust/utils/browser/blob.rs +16 -2
- package/src/rust/utils/browser/download.rs +1 -0
- package/src/rust/{components/column_settings_sidebar/mod.rs → utils/browser/dragdrop.rs} +14 -5
- package/src/rust/utils/browser/mod.rs +8 -4
- package/src/rust/utils/browser/selection.rs +5 -0
- package/src/rust/utils/custom_element.rs +28 -13
- package/src/rust/utils/datetime.rs +5 -0
- package/src/rust/utils/debounce.rs +7 -1
- package/src/rust/utils/hooks/use_async_callback.rs +7 -17
- package/src/rust/utils/mod.rs +28 -40
- package/src/rust/utils/number_format.rs +6 -5
- package/src/rust/utils/pubsub.rs +15 -10
- package/src/rust/utils/weak_scope.rs +11 -1
- package/src/svg/bookmark-icon.svg +4 -0
- package/src/svg/drag-handle copy.svg +10 -0
- package/src/svg/drawer-tab-hover.svg +5 -7
- package/src/svg/drawer-tab-invert-hover.svg +4 -8
- package/src/svg/drawer-tab-invert.svg +4 -7
- package/src/svg/drawer-tab.svg +4 -6
- package/src/svg/status_ok.svg +24 -24
- package/src/ts/extensions.ts +51 -3
- package/src/ts/perspective-viewer.ts +2 -14
- package/src/ts/plugin.ts +1 -1
- package/src/ts/ts-rs/ViewerConfigUpdate.ts +1 -1
- package/src/rust/components/column_settings_sidebar/style_tab/column_style.rs +0 -177
- package/src/rust/components/containers/tests/mod.rs +0 -11
- package/src/rust/components/containers/tests/split_panel.rs +0 -91
- package/src/rust/js/testing.rs +0 -149
- package/src/rust/utils/tee.rs +0 -88
- /package/dist/wasm/snippets/{perspective-viewer-9a89352df1552d2b → perspective-viewer-0d326a25c1022412}/inline0.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-9a89352df1552d2b → perspective-viewer-0d326a25c1022412}/inline1.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-9a89352df1552d2b → perspective-viewer-0d326a25c1022412}/inline2.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-9a89352df1552d2b → perspective-viewer-0d326a25c1022412}/inline3.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-9a89352df1552d2b → perspective-viewer-0d326a25c1022412}/inline4.js +0 -0
- /package/src/rust/components/{style_controls.rs → style_controls/mod.rs} +0 -0
- /package/src/rust/{components/containers → config}/kvpair.rs +0 -0
|
@@ -17,7 +17,6 @@ use std::ops::{Deref, DerefMut};
|
|
|
17
17
|
use perspective_client::config::*;
|
|
18
18
|
use perspective_js::apierror;
|
|
19
19
|
|
|
20
|
-
use crate::components::viewer::ColumnLocator;
|
|
21
20
|
use crate::*;
|
|
22
21
|
|
|
23
22
|
struct SessionViewExpressionMetadata {
|
|
@@ -46,6 +45,9 @@ impl DerefMut for SessionMetadata {
|
|
|
46
45
|
}
|
|
47
46
|
}
|
|
48
47
|
|
|
48
|
+
pub type MetadataRef<'a> = std::cell::Ref<'a, SessionMetadata>;
|
|
49
|
+
pub type MetadataMutRef<'a> = std::cell::RefMut<'a, SessionMetadata>;
|
|
50
|
+
|
|
49
51
|
/// TODO the multiple `Option` types could probably be merged since they are
|
|
50
52
|
/// populated within an async lock
|
|
51
53
|
#[derive(Default)]
|
|
@@ -193,22 +195,6 @@ impl SessionMetadata {
|
|
|
193
195
|
is_expr.unwrap_or_default()
|
|
194
196
|
}
|
|
195
197
|
|
|
196
|
-
/// This function will find a currently existing column. If you want to
|
|
197
|
-
/// create a new expression column, use ColumnLocator::Expr(None)
|
|
198
|
-
pub fn get_column_locator(&self, name: Option<String>) -> Option<ColumnLocator> {
|
|
199
|
-
name.and_then(|name| {
|
|
200
|
-
self.as_ref().and_then(|meta| {
|
|
201
|
-
if self.is_column_expression(&name) {
|
|
202
|
-
Some(ColumnLocator::Expression(name))
|
|
203
|
-
} else {
|
|
204
|
-
meta.column_names
|
|
205
|
-
.iter()
|
|
206
|
-
.find_map(|n| (n == &name).then_some(ColumnLocator::Table(name.clone())))
|
|
207
|
-
}
|
|
208
|
-
})
|
|
209
|
-
})
|
|
210
|
-
}
|
|
211
|
-
|
|
212
198
|
/// Creates a new column name by appending a numeral corresponding to the
|
|
213
199
|
/// number of columns with that name.
|
|
214
200
|
pub fn make_new_column_name(&self, col: Option<&str>) -> String {
|
package/src/rust/session.rs
CHANGED
|
@@ -21,47 +21,49 @@ use std::collections::HashSet;
|
|
|
21
21
|
use std::future::Future;
|
|
22
22
|
use std::ops::Deref;
|
|
23
23
|
use std::rc::Rc;
|
|
24
|
-
use std::sync::Arc;
|
|
25
24
|
|
|
26
25
|
use perspective_client::config::*;
|
|
27
|
-
use perspective_client::{ClientError, ReconnectCallback, View, ViewWindow};
|
|
26
|
+
use perspective_client::{Client, ClientError, ReconnectCallback, View, ViewWindow};
|
|
28
27
|
use perspective_js::apierror;
|
|
29
28
|
use perspective_js::utils::*;
|
|
30
29
|
use wasm_bindgen::prelude::*;
|
|
31
30
|
use yew::html::ImplicitClone;
|
|
32
31
|
use yew::prelude::*;
|
|
33
32
|
|
|
33
|
+
pub use self::metadata::MetadataRef;
|
|
34
34
|
use self::metadata::*;
|
|
35
35
|
use self::replace_expression_update::*;
|
|
36
36
|
pub use self::view_subscription::ViewStats;
|
|
37
37
|
use self::view_subscription::*;
|
|
38
|
-
use crate::dragdrop::*;
|
|
39
38
|
use crate::js::plugin::*;
|
|
40
39
|
use crate::utils::*;
|
|
41
40
|
|
|
42
|
-
/// The `Session` struct is the principal interface to the Perspective engine,
|
|
43
|
-
/// the `Table` and `View` objects for this viewer, and all associated state
|
|
44
|
-
/// including the `ViewConfig`.
|
|
45
|
-
#[derive(Clone, Default)]
|
|
46
|
-
pub struct Session(Arc<SessionHandle>);
|
|
47
|
-
|
|
48
|
-
impl ImplicitClone for Session {}
|
|
49
|
-
|
|
50
41
|
/// Immutable state for `Session`.
|
|
51
42
|
#[derive(Default)]
|
|
52
43
|
pub struct SessionHandle {
|
|
53
44
|
session_data: RefCell<SessionData>,
|
|
54
45
|
pub table_updated: PubSub<()>,
|
|
55
46
|
pub table_loaded: PubSub<()>,
|
|
47
|
+
pub table_errored: PubSub<ApiError>,
|
|
48
|
+
pub table_unloaded: PubSub<bool>,
|
|
56
49
|
pub view_created: PubSub<()>,
|
|
57
50
|
pub view_config_changed: PubSub<()>,
|
|
58
51
|
pub stats_changed: PubSub<Option<ViewStats>>,
|
|
59
|
-
pub
|
|
52
|
+
pub title_changed: PubSub<Option<String>>,
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
impl Deref for SessionHandle {
|
|
56
|
+
type Target = RefCell<SessionData>;
|
|
57
|
+
|
|
58
|
+
fn deref(&self) -> &Self::Target {
|
|
59
|
+
&self.session_data
|
|
60
|
+
}
|
|
60
61
|
}
|
|
61
62
|
|
|
62
63
|
/// Mutable state for `Session`.
|
|
63
64
|
#[derive(Default)]
|
|
64
65
|
pub struct SessionData {
|
|
66
|
+
client: Option<perspective_client::Client>,
|
|
65
67
|
table: Option<perspective_client::Table>,
|
|
66
68
|
metadata: SessionMetadata,
|
|
67
69
|
old_config: Option<ViewConfig>,
|
|
@@ -71,11 +73,36 @@ pub struct SessionData {
|
|
|
71
73
|
is_clean: bool,
|
|
72
74
|
is_paused: bool,
|
|
73
75
|
error: Option<TableErrorState>,
|
|
76
|
+
title: Option<String>,
|
|
74
77
|
}
|
|
75
78
|
|
|
76
79
|
#[derive(Clone)]
|
|
77
80
|
pub struct TableErrorState(ApiError, Option<ReconnectCallback>);
|
|
78
81
|
|
|
82
|
+
/// Options for [`Session::reset`]
|
|
83
|
+
#[derive(Default)]
|
|
84
|
+
pub struct ResetOptions {
|
|
85
|
+
/// Reset user defined expressions
|
|
86
|
+
pub expressions: bool,
|
|
87
|
+
|
|
88
|
+
/// Reset the [`Table`]
|
|
89
|
+
pub table: bool,
|
|
90
|
+
|
|
91
|
+
/// Reset the [`ViewConfig`]
|
|
92
|
+
pub config: bool,
|
|
93
|
+
|
|
94
|
+
/// Manually reset the [`ViewStats`]
|
|
95
|
+
pub stats: bool,
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/// The `Session` struct is the principal interface to the Perspective engine,
|
|
99
|
+
/// the `Table` and `View` objects for this viewer, and all associated state
|
|
100
|
+
/// including the `ViewConfig`.
|
|
101
|
+
#[derive(Clone)]
|
|
102
|
+
pub struct Session(Rc<SessionHandle>);
|
|
103
|
+
|
|
104
|
+
impl ImplicitClone for Session {}
|
|
105
|
+
|
|
79
106
|
impl Deref for Session {
|
|
80
107
|
type Target = SessionHandle;
|
|
81
108
|
|
|
@@ -86,22 +113,18 @@ impl Deref for Session {
|
|
|
86
113
|
|
|
87
114
|
impl PartialEq for Session {
|
|
88
115
|
fn eq(&self, other: &Self) -> bool {
|
|
89
|
-
|
|
116
|
+
Rc::ptr_eq(&self.0, &other.0)
|
|
90
117
|
}
|
|
91
118
|
}
|
|
92
119
|
|
|
93
|
-
impl
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
120
|
+
impl Session {
|
|
121
|
+
/// Uses [`Self::new`] instead of [`Default`] to prevent accidental
|
|
122
|
+
/// instantiation in props/etc.
|
|
123
|
+
#[allow(clippy::new_without_default)]
|
|
124
|
+
pub fn new() -> Self {
|
|
125
|
+
Self(Rc::default())
|
|
98
126
|
}
|
|
99
|
-
}
|
|
100
127
|
|
|
101
|
-
pub type MetadataRef<'a> = std::cell::Ref<'a, SessionMetadata>;
|
|
102
|
-
pub type MetadataMutRef<'a> = std::cell::RefMut<'a, SessionMetadata>;
|
|
103
|
-
|
|
104
|
-
impl Session {
|
|
105
128
|
pub fn metadata(&self) -> MetadataRef<'_> {
|
|
106
129
|
std::cell::Ref::map(self.borrow(), |x| &x.metadata)
|
|
107
130
|
}
|
|
@@ -110,40 +133,48 @@ impl Session {
|
|
|
110
133
|
std::cell::RefMut::map(self.borrow_mut(), |x| &mut x.metadata)
|
|
111
134
|
}
|
|
112
135
|
|
|
113
|
-
pub fn
|
|
114
|
-
self.
|
|
115
|
-
self.borrow_mut().is_clean = false;
|
|
116
|
-
self.borrow_mut().view_sub = None;
|
|
136
|
+
pub fn get_title(&self) -> Option<String> {
|
|
137
|
+
self.borrow().title.clone()
|
|
117
138
|
}
|
|
118
139
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
pub fn reset(&self, reset_expressions: bool) -> impl Future<Output = ApiResult<()>> + use<> {
|
|
124
|
-
self.borrow_mut().is_clean = false;
|
|
125
|
-
let view = self.0.borrow_mut().view_sub.take();
|
|
126
|
-
self.borrow_mut().view_sub = None;
|
|
127
|
-
self.borrow_mut().config.reset(reset_expressions);
|
|
128
|
-
let err = self.get_error();
|
|
129
|
-
async move {
|
|
130
|
-
let res = view.delete().await;
|
|
131
|
-
if let Some(err) = err { Err(err) } else { res }
|
|
132
|
-
}
|
|
140
|
+
pub fn set_title(&self, title: Option<String>) {
|
|
141
|
+
let new_title = title.filter(|x| !x.is_empty());
|
|
142
|
+
self.borrow_mut().title.clone_from(&new_title);
|
|
143
|
+
self.title_changed.emit(new_title);
|
|
133
144
|
}
|
|
134
145
|
|
|
135
146
|
/// Reset this (presumably shared) `Session` to its initial state, returning
|
|
136
147
|
/// a bool indicating whether this `Session` had a table which was
|
|
137
148
|
/// deleted. TODO Table should be an immutable constructor parameter to
|
|
138
149
|
/// `Session`.
|
|
139
|
-
pub
|
|
150
|
+
pub fn reset(&self, options: ResetOptions) -> impl Future<Output = ApiResult<()>> + use<> {
|
|
140
151
|
self.borrow_mut().is_clean = false;
|
|
141
|
-
self.borrow_mut().
|
|
142
|
-
|
|
143
|
-
self.borrow_mut().
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
152
|
+
let view = self.0.borrow_mut().view_sub.take();
|
|
153
|
+
let err = self.get_error();
|
|
154
|
+
self.borrow_mut().error = None;
|
|
155
|
+
if options.stats {
|
|
156
|
+
self.update_stats(ViewStats::default());
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if options.config {
|
|
160
|
+
self.borrow_mut().config.reset(options.expressions);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if options.table {
|
|
164
|
+
self.borrow_mut().table = None;
|
|
165
|
+
self.borrow_mut().metadata = SessionMetadata::default();
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
let table_unloaded = self.table_unloaded.callback();
|
|
169
|
+
self.borrow_mut().is_clean = false;
|
|
170
|
+
async move {
|
|
171
|
+
let res = view.delete().await;
|
|
172
|
+
if options.table {
|
|
173
|
+
table_unloaded.emit(true)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if let Some(err) = err { Err(err) } else { res }
|
|
177
|
+
}
|
|
147
178
|
}
|
|
148
179
|
|
|
149
180
|
pub fn has_table(&self) -> bool {
|
|
@@ -154,10 +185,40 @@ impl Session {
|
|
|
154
185
|
self.borrow().table.clone()
|
|
155
186
|
}
|
|
156
187
|
|
|
188
|
+
pub fn set_client(&self, client: Client) -> bool {
|
|
189
|
+
if Some(&client) != self.borrow().client.as_ref() {
|
|
190
|
+
self.borrow_mut().client = Some(client);
|
|
191
|
+
self.borrow_mut().table = None;
|
|
192
|
+
true
|
|
193
|
+
} else {
|
|
194
|
+
false
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
pub fn get_client(&self) -> Option<Client> {
|
|
199
|
+
self.borrow().client.clone()
|
|
200
|
+
}
|
|
201
|
+
|
|
157
202
|
/// Reset this `Session`'s state with a new `Table`. Implicitly clears the
|
|
158
203
|
/// `ViewSubscription`, which will need to be re-initialized later via
|
|
159
204
|
/// `create_view()`.
|
|
160
|
-
|
|
205
|
+
///
|
|
206
|
+
/// # Arguments
|
|
207
|
+
///
|
|
208
|
+
/// - `table_name` The name of the `Table` to load, which must exist on the
|
|
209
|
+
/// loaded `Client`.
|
|
210
|
+
///
|
|
211
|
+
/// # Returns
|
|
212
|
+
///
|
|
213
|
+
/// `table_name` is unique per `Client`, so if this value has not changed,
|
|
214
|
+
/// `Session::set_table` does nothing and returns `Ok(false)`.
|
|
215
|
+
pub async fn set_table(&self, table_name: String) -> ApiResult<bool> {
|
|
216
|
+
if Some(table_name.as_str()) == self.0.borrow().table.as_ref().map(|x| x.get_name()) {
|
|
217
|
+
return Ok(false);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
let client = self.0.borrow().client.clone().into_apierror()?;
|
|
221
|
+
let table = client.open_table(table_name.clone()).await?;
|
|
161
222
|
match SessionMetadata::from_table(&table).await {
|
|
162
223
|
Ok(metadata) => {
|
|
163
224
|
let client = table.get_client();
|
|
@@ -186,19 +247,31 @@ impl Session {
|
|
|
186
247
|
let sub = self.borrow_mut().view_sub.take();
|
|
187
248
|
self.borrow_mut().metadata = metadata;
|
|
188
249
|
self.borrow_mut().table = Some(table);
|
|
250
|
+
self.borrow_mut().is_clean = false;
|
|
189
251
|
sub.delete().await?;
|
|
190
252
|
self.table_loaded.emit(());
|
|
191
|
-
Ok(
|
|
253
|
+
Ok(true)
|
|
192
254
|
},
|
|
193
|
-
Err(err) => self.set_error(false, err).await.map(|_|
|
|
255
|
+
Err(err) => self.set_error(false, err).await.map(|_| false),
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
pub fn update_column_defaults(&self, requirements: &ViewConfigRequirements) {
|
|
260
|
+
if self.borrow().config.columns.is_empty() {
|
|
261
|
+
let mut update = ViewConfigUpdate::default();
|
|
262
|
+
self.set_update_column_defaults(&mut update, requirements);
|
|
263
|
+
self.borrow_mut().config.apply_update(update);
|
|
194
264
|
}
|
|
195
265
|
}
|
|
196
266
|
|
|
197
267
|
pub async fn set_error(&self, reset_table: bool, err: ApiError) -> ApiResult<()> {
|
|
198
268
|
let session = self.clone();
|
|
199
269
|
let poll_loop = LocalPollLoop::new(move |()| {
|
|
200
|
-
session.
|
|
201
|
-
|
|
270
|
+
ApiFuture::spawn(session.reset(ResetOptions {
|
|
271
|
+
config: true,
|
|
272
|
+
expressions: true,
|
|
273
|
+
..ResetOptions::default()
|
|
274
|
+
}));
|
|
202
275
|
Ok(JsValue::UNDEFINED)
|
|
203
276
|
});
|
|
204
277
|
|
|
@@ -240,7 +313,7 @@ impl Session {
|
|
|
240
313
|
|
|
241
314
|
pub async fn await_table(&self) -> ApiResult<()> {
|
|
242
315
|
if self.js_get_table().is_none() {
|
|
243
|
-
self.table_loaded.
|
|
316
|
+
self.table_loaded.read_next().await?;
|
|
244
317
|
let _ = self.js_get_table().ok_or("No table set")?;
|
|
245
318
|
}
|
|
246
319
|
|
|
@@ -279,6 +352,7 @@ impl Session {
|
|
|
279
352
|
self.borrow_mut().error = None;
|
|
280
353
|
self.borrow_mut().is_clean = false;
|
|
281
354
|
self.borrow_mut().view_sub = None;
|
|
355
|
+
self.table_loaded.emit(());
|
|
282
356
|
}
|
|
283
357
|
|
|
284
358
|
Ok(())
|
|
@@ -457,7 +531,7 @@ impl Session {
|
|
|
457
531
|
self.borrow().stats.clone()
|
|
458
532
|
}
|
|
459
533
|
|
|
460
|
-
pub fn get_view_config(&self) -> Ref<ViewConfig> {
|
|
534
|
+
pub fn get_view_config(&'_ self) -> Ref<'_, ViewConfig> {
|
|
461
535
|
Ref::map(self.borrow(), |x| &x.config)
|
|
462
536
|
}
|
|
463
537
|
|
|
@@ -511,7 +585,7 @@ impl Session {
|
|
|
511
585
|
use self::column_defaults_update::*;
|
|
512
586
|
config_update.set_update_column_defaults(
|
|
513
587
|
&self.metadata(),
|
|
514
|
-
&self.
|
|
588
|
+
&self.all_columns().into_iter().map(Some).collect::<Vec<_>>(),
|
|
515
589
|
requirements,
|
|
516
590
|
)
|
|
517
591
|
}
|
|
@@ -534,15 +608,6 @@ impl Session {
|
|
|
534
608
|
Ok(())
|
|
535
609
|
}
|
|
536
610
|
|
|
537
|
-
pub fn reset_stats(&self) {
|
|
538
|
-
self.update_stats(ViewStats::default());
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
#[cfg(test)]
|
|
542
|
-
pub fn set_stats(&self, stats: ViewStats) {
|
|
543
|
-
self.update_stats(stats)
|
|
544
|
-
}
|
|
545
|
-
|
|
546
611
|
/// In order to create a new view in this session, the session must first be
|
|
547
612
|
/// validated to create a `ValidSession<'_>` guard.
|
|
548
613
|
pub async fn validate(&self) -> Result<ValidSession<'_>, ApiError> {
|
|
@@ -555,8 +620,11 @@ impl Session {
|
|
|
555
620
|
if let Err(err) = self.validate_view_config().await {
|
|
556
621
|
let session = self.clone();
|
|
557
622
|
let poll_loop = LocalPollLoop::new(move |()| {
|
|
558
|
-
session.
|
|
559
|
-
|
|
623
|
+
ApiFuture::spawn(session.reset(ResetOptions {
|
|
624
|
+
config: true,
|
|
625
|
+
expressions: true,
|
|
626
|
+
..ResetOptions::default()
|
|
627
|
+
}));
|
|
560
628
|
Ok(JsValue::UNDEFINED)
|
|
561
629
|
});
|
|
562
630
|
|
|
@@ -574,7 +642,12 @@ impl Session {
|
|
|
574
642
|
if let Some(config) = old {
|
|
575
643
|
self.borrow_mut().config = config;
|
|
576
644
|
} else {
|
|
577
|
-
self.reset(
|
|
645
|
+
self.reset(ResetOptions {
|
|
646
|
+
config: true,
|
|
647
|
+
expressions: true,
|
|
648
|
+
..ResetOptions::default()
|
|
649
|
+
})
|
|
650
|
+
.await?;
|
|
578
651
|
}
|
|
579
652
|
|
|
580
653
|
return Err(err);
|
|
@@ -604,16 +677,18 @@ impl Session {
|
|
|
604
677
|
self.stats_changed.emit(Some(stats));
|
|
605
678
|
}
|
|
606
679
|
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
let table_columns = self
|
|
610
|
-
.metadata()
|
|
680
|
+
fn all_columns(&self) -> Vec<String> {
|
|
681
|
+
self.metadata()
|
|
611
682
|
.get_table_columns()
|
|
612
683
|
.into_iter()
|
|
613
684
|
.flatten()
|
|
614
685
|
.cloned()
|
|
615
|
-
.collect
|
|
686
|
+
.collect()
|
|
687
|
+
}
|
|
616
688
|
|
|
689
|
+
async fn validate_view_config(&self) -> ApiResult<()> {
|
|
690
|
+
let mut config = self.borrow().config.clone();
|
|
691
|
+
let table_columns = self.all_columns();
|
|
617
692
|
let all_columns: HashSet<String> = table_columns.iter().cloned().collect();
|
|
618
693
|
let mut view_columns: HashSet<&str> = HashSet::new();
|
|
619
694
|
let table = self
|
|
@@ -711,17 +786,17 @@ impl Session {
|
|
|
711
786
|
/// A newtype wrapper which only provides `create_view()`
|
|
712
787
|
pub struct ValidSession<'a>(&'a Session, bool);
|
|
713
788
|
|
|
714
|
-
impl
|
|
789
|
+
impl ValidSession<'_> {
|
|
715
790
|
/// Set a new `View` (derived from this `Session`'s `Table`), and create the
|
|
716
791
|
/// `update()` subscription, consuming this `ValidSession<'_>` and returning
|
|
717
792
|
/// the original `&Session`.
|
|
718
|
-
pub async fn create_view(&self) -> Result
|
|
793
|
+
pub async fn create_view(&self) -> Result<Option<View>, ApiError> {
|
|
719
794
|
if !self.0.reset_clean() && !self.0.borrow().is_paused {
|
|
720
795
|
if !self.1 {
|
|
721
796
|
let config = self.0.borrow().config.clone();
|
|
722
797
|
if let Some(sub) = &mut self.0.borrow_mut().view_sub.as_mut() {
|
|
723
798
|
sub.update_view_config(Rc::new(config));
|
|
724
|
-
return Ok(
|
|
799
|
+
return Ok(Some(sub.get_view().clone()));
|
|
725
800
|
}
|
|
726
801
|
}
|
|
727
802
|
|
|
@@ -747,11 +822,16 @@ impl<'a> ValidSession<'a> {
|
|
|
747
822
|
.0
|
|
748
823
|
.metadata()
|
|
749
824
|
.get_column_aggregates(col.as_str())
|
|
750
|
-
.
|
|
751
|
-
.
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
825
|
+
.and_then(|mut aggs| aggs.next())
|
|
826
|
+
.into_apierror();
|
|
827
|
+
|
|
828
|
+
match agg {
|
|
829
|
+
Err(_) => tracing::warn!(
|
|
830
|
+
"No default aggregate for column '{}' found, skipping",
|
|
831
|
+
col
|
|
832
|
+
),
|
|
833
|
+
Ok(agg) => _ = view_config.aggregates.insert(col.to_string(), agg),
|
|
834
|
+
};
|
|
755
835
|
}
|
|
756
836
|
}
|
|
757
837
|
|
|
@@ -781,7 +861,7 @@ impl<'a> ValidSession<'a> {
|
|
|
781
861
|
self.0.borrow_mut().view_sub = Some(sub);
|
|
782
862
|
}
|
|
783
863
|
|
|
784
|
-
Ok(self.0)
|
|
864
|
+
Ok(self.0.get_view())
|
|
785
865
|
}
|
|
786
866
|
}
|
|
787
867
|
|
|
@@ -11,10 +11,13 @@
|
|
|
11
11
|
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
12
12
|
|
|
13
13
|
use perspective_js::utils::ApiResult;
|
|
14
|
+
use wasm_bindgen::JsValue;
|
|
14
15
|
|
|
16
|
+
/// An extension method for converting various [`js_sys`] and [`web_sys`] types
|
|
17
|
+
/// to [`web_sys::Blob`].
|
|
15
18
|
pub trait AsBlob {
|
|
16
|
-
/// Standardized conversions from common `wasm_bindgen` types to
|
|
17
|
-
/// `
|
|
19
|
+
/// Standardized conversions from common [`wasm_bindgen`] types to
|
|
20
|
+
/// [`web_sys::Blob`], which is commonly necessary to provide data to a
|
|
18
21
|
/// download or clipboard action.
|
|
19
22
|
fn as_blob(&self) -> ApiResult<web_sys::Blob>;
|
|
20
23
|
}
|
|
@@ -26,6 +29,17 @@ impl AsBlob for js_sys::ArrayBuffer {
|
|
|
26
29
|
}
|
|
27
30
|
}
|
|
28
31
|
|
|
32
|
+
impl AsBlob for JsValue {
|
|
33
|
+
fn as_blob(&self) -> ApiResult<web_sys::Blob> {
|
|
34
|
+
let array = std::iter::once(self).collect::<js_sys::Array>();
|
|
35
|
+
let options = web_sys::BlobPropertyBag::new();
|
|
36
|
+
options.set_type("text/plain");
|
|
37
|
+
Ok(web_sys::Blob::new_with_str_sequence_and_options(
|
|
38
|
+
&array, &options,
|
|
39
|
+
)?)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
29
43
|
impl AsBlob for js_sys::JsString {
|
|
30
44
|
fn as_blob(&self) -> ApiResult<web_sys::Blob> {
|
|
31
45
|
let array = std::iter::once(self).collect::<js_sys::Array>();
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
use perspective_js::utils::{global, *};
|
|
14
14
|
use wasm_bindgen::JsCast;
|
|
15
15
|
|
|
16
|
+
/// Given a blob, invoke the Browser's download popup.
|
|
16
17
|
pub fn download(name: &str, value: &web_sys::Blob) -> ApiResult<()> {
|
|
17
18
|
let element: web_sys::HtmlElement = global::document().create_element("a")?.unchecked_into();
|
|
18
19
|
let blob_url = web_sys::Url::create_object_url_with_blob(value)?;
|
|
@@ -9,9 +9,18 @@
|
|
|
9
9
|
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
|
10
10
|
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
|
11
11
|
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
12
|
-
mod attributes_tab;
|
|
13
|
-
mod save_settings;
|
|
14
|
-
mod sidebar;
|
|
15
|
-
mod style_tab;
|
|
16
12
|
|
|
17
|
-
|
|
13
|
+
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
|
14
|
+
pub enum DragTarget {
|
|
15
|
+
Active,
|
|
16
|
+
GroupBy,
|
|
17
|
+
SplitBy,
|
|
18
|
+
Sort,
|
|
19
|
+
Filter,
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
|
23
|
+
pub enum DragEffect {
|
|
24
|
+
Copy,
|
|
25
|
+
Move(DragTarget),
|
|
26
|
+
}
|
|
@@ -10,15 +10,19 @@
|
|
|
10
10
|
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
|
11
11
|
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
12
12
|
|
|
13
|
+
//! Utilities specific to Browser-only JavaScript extensions.
|
|
14
|
+
|
|
13
15
|
mod blob;
|
|
14
16
|
mod download;
|
|
17
|
+
mod dragdrop;
|
|
15
18
|
mod request_animation_frame;
|
|
16
19
|
mod selection;
|
|
17
20
|
|
|
18
21
|
#[cfg(test)]
|
|
19
22
|
mod tests;
|
|
20
23
|
|
|
21
|
-
pub use blob::*;
|
|
22
|
-
pub use download::*;
|
|
23
|
-
pub use
|
|
24
|
-
pub use
|
|
24
|
+
pub use self::blob::*;
|
|
25
|
+
pub use self::download::*;
|
|
26
|
+
pub use self::dragdrop::*;
|
|
27
|
+
pub use self::request_animation_frame::*;
|
|
28
|
+
pub use self::selection::*;
|
|
@@ -19,8 +19,13 @@ use crate::*;
|
|
|
19
19
|
/// but `Deref` makes them fall through, so it is important that this method
|
|
20
20
|
/// be called on the correct struct type!
|
|
21
21
|
pub trait CaretPosition {
|
|
22
|
+
/// Select the entired clip
|
|
22
23
|
fn select_all(&self) -> ApiResult<()>;
|
|
24
|
+
|
|
25
|
+
/// Set the caret to a specific offset.
|
|
23
26
|
fn set_caret_position(&self, offset: usize) -> ApiResult<()>;
|
|
27
|
+
|
|
28
|
+
/// Get the caret's offset (if it has focus)
|
|
24
29
|
fn get_caret_position(&self) -> Option<u32>;
|
|
25
30
|
}
|
|
26
31
|
|
|
@@ -10,20 +10,9 @@
|
|
|
10
10
|
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
|
11
11
|
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
pub trait CustomElementMetadata {
|
|
16
|
-
const CUSTOM_ELEMENT_NAME: &'static str;
|
|
17
|
-
const STATICS: &'static [&'static str] = [].as_slice();
|
|
18
|
-
const TYPE_NAME: &'static str = std::any::type_name::<Self>();
|
|
13
|
+
//! Utilities for building JavaScript Custom Elements with Rust.
|
|
19
14
|
|
|
20
|
-
|
|
21
|
-
match &Self::TYPE_NAME.rfind(':') {
|
|
22
|
-
Some(pos) => &Self::TYPE_NAME[pos + 1..],
|
|
23
|
-
None => Self::TYPE_NAME,
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
}
|
|
15
|
+
use wasm_bindgen::prelude::*;
|
|
27
16
|
|
|
28
17
|
#[wasm_bindgen(inline_js = r#"
|
|
29
18
|
export function bootstrap(psp, name, clsname, statics) {
|
|
@@ -75,6 +64,32 @@ extern "C" {
|
|
|
75
64
|
fn js_bootstrap(psp: &JsValue, name: &str, cls: &str, statics: js_sys::Array) -> JsValue;
|
|
76
65
|
}
|
|
77
66
|
|
|
67
|
+
/// A trait which allows the [`define_web_component`] method to create a
|
|
68
|
+
/// Custom Element (which must by definition inherit from `HTMLElement`) from
|
|
69
|
+
/// a `[wasm_bindgen]` annotated Rust struct (for which this trait is
|
|
70
|
+
/// implemented).
|
|
71
|
+
pub trait CustomElementMetadata {
|
|
72
|
+
/// The name of the element to register.
|
|
73
|
+
const CUSTOM_ELEMENT_NAME: &'static str;
|
|
74
|
+
|
|
75
|
+
/// [optional] The names of the methods which should be static on the
|
|
76
|
+
/// element.
|
|
77
|
+
const STATICS: &'static [&'static str] = [].as_slice();
|
|
78
|
+
|
|
79
|
+
/// [optional] The name of the Rust struct.
|
|
80
|
+
const TYPE_NAME: &'static str = std::any::type_name::<Self>();
|
|
81
|
+
|
|
82
|
+
/// [optional] A custom implementation of struct name to class name.
|
|
83
|
+
#[must_use]
|
|
84
|
+
fn struct_name() -> &'static str {
|
|
85
|
+
match &Self::TYPE_NAME.rfind(':') {
|
|
86
|
+
Some(pos) => &Self::TYPE_NAME[pos + 1..],
|
|
87
|
+
None => Self::TYPE_NAME,
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/// Register the Custom Element globally.
|
|
78
93
|
pub fn define_web_component<T: CustomElementMetadata>(module: &JsValue) {
|
|
79
94
|
js_bootstrap(
|
|
80
95
|
module,
|
|
@@ -10,6 +10,9 @@
|
|
|
10
10
|
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
|
11
11
|
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
12
12
|
|
|
13
|
+
//! Utility functions for dealing with datetime types.
|
|
14
|
+
//! TODO These methods should use JavaSript `Intl`.
|
|
15
|
+
|
|
13
16
|
use chrono::{DateTime, FixedOffset, NaiveDateTime, TimeZone, Utc};
|
|
14
17
|
use perspective_js::utils::*;
|
|
15
18
|
use wasm_bindgen::prelude::*;
|
|
@@ -27,6 +30,7 @@ fn get_local_tz() -> FixedOffset {
|
|
|
27
30
|
FixedOffset::west_opt(js_sys::Date::new(&0.into()).get_timezone_offset() as i32 * 60).unwrap()
|
|
28
31
|
}
|
|
29
32
|
|
|
33
|
+
/// Convert POSIX timestamp to a formatted string.
|
|
30
34
|
pub fn posix_to_utc_str(x: f64) -> ApiResult<String> {
|
|
31
35
|
let tz = get_local_tz();
|
|
32
36
|
if x > 0_f64 {
|
|
@@ -42,6 +46,7 @@ pub fn posix_to_utc_str(x: f64) -> ApiResult<String> {
|
|
|
42
46
|
}
|
|
43
47
|
}
|
|
44
48
|
|
|
49
|
+
/// Convert a `String` datetime representation to a POSIX timestamp.
|
|
45
50
|
pub fn str_to_utc_posix(val: &str) -> Result<f64, ApiError> {
|
|
46
51
|
let tz = get_local_tz();
|
|
47
52
|
let posix = NaiveDateTime::parse_from_str(val, input_value_format(val)?)?;
|