@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.
Files changed (142) hide show
  1. package/dist/cdn/perspective-viewer.js +4 -4
  2. package/dist/cdn/perspective-viewer.js.map +2 -2
  3. package/dist/css/blueprint.css +1 -1
  4. package/dist/css/botanical.css +1 -1
  5. package/dist/css/dracula.css +1 -1
  6. package/dist/css/eggplant.css +1 -1
  7. package/dist/css/gruvbox-dark.css +1 -1
  8. package/dist/css/gruvbox.css +1 -1
  9. package/dist/css/icons.css +1 -1
  10. package/dist/css/intl/ar.css +1 -1
  11. package/dist/css/intl/de.css +1 -1
  12. package/dist/css/intl/es.css +1 -1
  13. package/dist/css/intl/fr.css +1 -1
  14. package/dist/css/intl/ja.css +1 -1
  15. package/dist/css/intl/pt.css +1 -1
  16. package/dist/css/intl/zh.css +1 -1
  17. package/dist/css/intl.css +1 -1
  18. package/dist/css/ledger.css +1 -1
  19. package/dist/css/monokai.css +1 -1
  20. package/dist/css/nord.css +1 -1
  21. package/dist/css/phosphor.css +1 -1
  22. package/dist/css/pro-dark.css +1 -1
  23. package/dist/css/pro.css +1 -1
  24. package/dist/css/solarized-dark.css +1 -1
  25. package/dist/css/solarized.css +1 -1
  26. package/dist/css/themes.css +1 -1
  27. package/dist/css/vaporwave.css +1 -1
  28. package/dist/css/velvet.css +1 -1
  29. package/dist/docs/perspective-docs.json +1 -1
  30. package/dist/esm/perspective-viewer.d.ts +2 -0
  31. package/dist/esm/perspective-viewer.inline.js +9 -9
  32. package/dist/esm/perspective-viewer.inline.js.map +3 -3
  33. package/dist/esm/perspective-viewer.js +4 -4
  34. package/dist/esm/perspective-viewer.js.map +2 -2
  35. package/dist/esm/plugin.d.ts +9 -0
  36. package/dist/esm/ts-rs/AddPanelOptions.d.ts +9 -0
  37. package/dist/esm/ts-rs/ExportMethod.d.ts +1 -1
  38. package/dist/esm/ts-rs/GetViewMode.d.ts +4 -0
  39. package/dist/esm/ts-rs/GetViewOptions.d.ts +8 -0
  40. package/dist/esm/ts-rs/RestoreOptions.d.ts +6 -0
  41. package/dist/esm/ts-rs/RestoreWorkspaceOptions.d.ts +9 -0
  42. package/dist/esm/ts-rs/WorkspaceConfigUpdate.d.ts +30 -22
  43. package/dist/wasm/perspective-viewer.d.ts +102 -47
  44. package/dist/wasm/perspective-viewer.js +233 -142
  45. package/dist/wasm/perspective-viewer.wasm +0 -0
  46. package/dist/wasm/perspective-viewer.wasm.d.ts +18 -18
  47. package/package.json +7 -7
  48. package/src/css/column-selector.css +11 -3
  49. package/src/css/column-style.css +162 -0
  50. package/src/css/config-selector.css +1 -1
  51. package/src/css/form/debug.css +9 -0
  52. package/src/css/status-bar.css +11 -0
  53. package/src/css/ui/containers/tabs.css +2 -1
  54. package/src/rust/agent/tools.rs +80 -7
  55. package/src/rust/components/column_selector/active_column.rs +9 -0
  56. package/src/rust/components/column_selector.rs +11 -0
  57. package/src/rust/components/column_settings_sidebar/style_tab/alignment_field.rs +90 -0
  58. package/src/rust/components/column_settings_sidebar/style_tab/font_field.rs +267 -0
  59. package/src/rust/components/column_settings_sidebar/style_tab/primitive_field.rs +1 -1
  60. package/src/rust/components/column_settings_sidebar/style_tab.rs +68 -9
  61. package/src/rust/components/debug_panel.rs +107 -50
  62. package/src/rust/components/export_dropdown.rs +6 -0
  63. package/src/rust/components/main_panel/msg.rs +4 -0
  64. package/src/rust/components/main_panel.rs +14 -0
  65. package/src/rust/components/mod.rs +0 -1
  66. package/src/rust/components/plugin_tab.rs +57 -3
  67. package/src/rust/components/settings_panel.rs +6 -0
  68. package/src/rust/components/status_bar.rs +12 -3
  69. package/src/rust/components/status_indicator.rs +4 -0
  70. package/src/rust/components/viewer/filters.rs +2 -2
  71. package/src/rust/components/viewer/msg.rs +4 -0
  72. package/src/rust/components/viewer/panels.rs +6 -3
  73. package/src/rust/components/viewer/render.rs +1 -0
  74. package/src/rust/components/viewer/wiring.rs +14 -2
  75. package/src/rust/components/viewer.rs +2 -2
  76. package/src/rust/config/column_config_schema.rs +312 -8
  77. package/src/rust/config/datetime_column_style.rs +0 -11
  78. package/src/rust/config/export_method.rs +24 -0
  79. package/src/rust/config/mod.rs +0 -2
  80. package/src/rust/config/options.rs +50 -0
  81. package/src/rust/config/workspace_config.rs +69 -24
  82. package/src/rust/custom_elements/viewer.rs +214 -113
  83. package/src/rust/js/plugin.rs +3 -1
  84. package/src/rust/lib.rs +7 -3
  85. package/src/rust/queries/plugin_column_styles.rs +6 -5
  86. package/src/rust/renderer/plugin_config.rs +200 -41
  87. package/src/rust/renderer/plugin_store.rs +2 -1
  88. package/src/rust/renderer/props.rs +5 -0
  89. package/src/rust/renderer.rs +9 -0
  90. package/src/rust/session/metadata.rs +5 -10
  91. package/src/rust/session/props.rs +4 -0
  92. package/src/rust/session.rs +273 -195
  93. package/src/rust/tasks/copy_export.rs +67 -0
  94. package/src/rust/tasks/create_panel.rs +16 -13
  95. package/src/rust/tasks/pipeline.rs +26 -8
  96. package/src/rust/tasks/restore_panel.rs +129 -30
  97. package/src/rust/tasks/send_column_config.rs +3 -0
  98. package/src/rust/tasks/sync_update_panels.rs +148 -68
  99. package/src/rust/tasks/table_lifecycle.rs +7 -1
  100. package/src/rust/ui/containers/tab_list.rs +1 -1
  101. package/src/rust/ui/form/intl_label.rs +4 -23
  102. package/src/rust/ui/portal.rs +1 -0
  103. package/src/rust/utils/css_vars.rs +13 -0
  104. package/src/rust/{config/datetime_column_style/color_mode.rs → utils/in_flight.rs} +47 -35
  105. package/src/rust/utils/local_fonts.rs +395 -0
  106. package/src/rust/utils/mod.rs +4 -0
  107. package/src/rust/utils/pending_effects.rs +6 -30
  108. package/src/rust/workspace.rs +12 -0
  109. package/src/svg/status_pending.svg +3 -0
  110. package/src/themes/icons.css +1 -0
  111. package/src/themes/intl/ar.css +20 -7
  112. package/src/themes/intl/de.css +20 -7
  113. package/src/themes/intl/es.css +20 -7
  114. package/src/themes/intl/fr.css +20 -7
  115. package/src/themes/intl/ja.css +20 -7
  116. package/src/themes/intl/pt.css +20 -7
  117. package/src/themes/intl/zh.css +20 -7
  118. package/src/themes/intl.css +20 -7
  119. package/src/themes/monokai.css +1 -1
  120. package/src/themes/vaporwave.css +1 -1
  121. package/src/ts/perspective-viewer.ts +2 -0
  122. package/src/ts/plugin.ts +9 -0
  123. package/src/ts/ts-rs/AddPanelOptions.ts +10 -0
  124. package/src/ts/ts-rs/ExportMethod.ts +1 -1
  125. package/src/ts/ts-rs/{DatetimeColorMode.ts → GetViewMode.ts} +4 -1
  126. package/src/ts/ts-rs/GetViewOptions.ts +7 -0
  127. package/src/ts/ts-rs/RestoreOptions.ts +7 -1
  128. package/src/ts/ts-rs/RestoreWorkspaceOptions.ts +10 -0
  129. package/src/ts/ts-rs/WorkspaceConfigUpdate.ts +31 -20
  130. package/dist/esm/ts-rs/DatetimeColorMode.d.ts +0 -1
  131. package/dist/esm/ts-rs/FormatMode.d.ts +0 -1
  132. package/dist/esm/ts-rs/StringColorMode.d.ts +0 -1
  133. package/src/rust/components/string_column_style.rs +0 -120
  134. package/src/rust/config/string_column_style.rs +0 -135
  135. package/src/ts/ts-rs/FormatMode.ts +0 -3
  136. package/src/ts/ts-rs/StringColorMode.ts +0 -3
  137. /package/dist/wasm/snippets/{perspective-viewer-adc6f3f79141112b → perspective-viewer-6813b568219c6763}/inline0.js +0 -0
  138. /package/dist/wasm/snippets/{perspective-viewer-adc6f3f79141112b → perspective-viewer-6813b568219c6763}/inline1.js +0 -0
  139. /package/dist/wasm/snippets/{perspective-viewer-adc6f3f79141112b → perspective-viewer-6813b568219c6763}/inline2.js +0 -0
  140. /package/dist/wasm/snippets/{perspective-viewer-adc6f3f79141112b → perspective-viewer-6813b568219c6763}/inline3.js +0 -0
  141. /package/dist/wasm/snippets/{perspective-viewer-adc6f3f79141112b → perspective-viewer-6813b568219c6763}/inline4.js +0 -0
  142. /package/dist/wasm/snippets/{perspective-viewer-adc6f3f79141112b → perspective-viewer-6813b568219c6763}/inline5.js +0 -0
@@ -100,6 +100,8 @@ fn get_menu_items(name: &str, is_chart: bool) -> Vec<ExportDropDownMenuItem> {
100
100
  ExportMethod::Json.new_file(name, is_chart),
101
101
  ExportMethod::Ndjson.new_file(name, is_chart),
102
102
  ExportMethod::Arrow.new_file(name, is_chart),
103
+ ExportMethod::ArrowLz4.new_file(name, is_chart),
104
+ ExportMethod::ArrowZstd.new_file(name, is_chart),
103
105
  ExportMethod::Html.new_file(name, is_chart),
104
106
  ExportMethod::Plugin.new_file(name, is_chart),
105
107
  ]
@@ -109,6 +111,8 @@ fn get_menu_items(name: &str, is_chart: bool) -> Vec<ExportDropDownMenuItem> {
109
111
  ExportMethod::Json.new_file(name, is_chart),
110
112
  ExportMethod::Ndjson.new_file(name, is_chart),
111
113
  ExportMethod::Arrow.new_file(name, is_chart),
114
+ ExportMethod::ArrowLz4.new_file(name, is_chart),
115
+ ExportMethod::ArrowZstd.new_file(name, is_chart),
112
116
  ExportMethod::Html.new_file(name, is_chart),
113
117
  ]
114
118
  },
@@ -118,6 +122,8 @@ fn get_menu_items(name: &str, is_chart: bool) -> Vec<ExportDropDownMenuItem> {
118
122
  ExportMethod::JsonAll.new_file(name, is_chart),
119
123
  ExportMethod::NdjsonAll.new_file(name, is_chart),
120
124
  ExportMethod::ArrowAll.new_file(name, is_chart),
125
+ ExportMethod::ArrowLz4All.new_file(name, is_chart),
126
+ ExportMethod::ArrowZstdAll.new_file(name, is_chart),
121
127
  ]),
122
128
  ExportDropDownMenuItem::OptGroup("Config".into(), vec![
123
129
  ExportMethod::JsonConfig.new_file(name, is_chart),
@@ -25,6 +25,10 @@ pub enum MainPanelMsg {
25
25
  /// deliver in every observed context.
26
26
  StagedChanged,
27
27
 
28
+ /// A layout tree was staged for the retained panel set
29
+ /// (`layout_staged` PubSub): re-render so `reconcile` applies it.
30
+ LayoutStaged,
31
+
28
32
  /// The `<regular-layout>` tree changed (fired by its
29
33
  /// `regular-layout-update` event). Used to detect panels removed from
30
34
  /// the layout (e.g. a frame's close button) so they can be disposed.
@@ -250,6 +250,9 @@ pub struct MainPanel {
250
250
  /// `Workspace::staged_changed` → [`MainPanelMsg::StagedChanged`] on THIS
251
251
  /// component's scope (see the message doc for why not the root's).
252
252
  _staged_sub: crate::utils::Subscription,
253
+
254
+ /// `Workspace::layout_staged` → [`MainPanelMsg::LayoutStaged`].
255
+ _layout_staged_sub: crate::utils::Subscription,
253
256
  }
254
257
 
255
258
  impl Component for MainPanel {
@@ -351,6 +354,15 @@ impl Component for MainPanel {
351
354
  .add_listener(move |()| cb.emit(()))
352
355
  };
353
356
 
357
+ let layout_staged_sub = {
358
+ use crate::utils::AddListener;
359
+ let cb = ctx.link().callback(|_: ()| MainPanelMsg::LayoutStaged);
360
+ ctx.props()
361
+ .workspace
362
+ .layout_staged()
363
+ .add_listener(move |()| cb.emit(()))
364
+ };
365
+
354
366
  Self {
355
367
  main_panel_ref: NodeRef::default(),
356
368
  layout_ref: NodeRef::default(),
@@ -368,6 +380,7 @@ impl Component for MainPanel {
368
380
  theme_backgrounds: HashMap::new(),
369
381
  stamped_frame_themes: None,
370
382
  _staged_sub: staged_sub,
383
+ _layout_staged_sub: layout_staged_sub,
371
384
  }
372
385
  }
373
386
 
@@ -375,6 +388,7 @@ impl Component for MainPanel {
375
388
  match msg {
376
389
  MainPanelMsg::PointerEvent(event) => self.on_pointer_event(ctx, event),
377
390
  MainPanelMsg::StagedChanged => true,
391
+ MainPanelMsg::LayoutStaged => true,
378
392
  MainPanelMsg::LayoutUpdated => self.on_layout_updated(ctx),
379
393
  MainPanelMsg::TabSelected(name) => self.on_tab_selected(ctx, name),
380
394
  MainPanelMsg::ContextMenu(id, x, y) => self.on_context_menu(ctx, id, x, y),
@@ -43,7 +43,6 @@ pub mod rows_counter;
43
43
  pub mod settings_panel;
44
44
  pub mod status_bar;
45
45
  pub mod status_indicator;
46
- pub mod string_column_style;
47
46
  pub mod style;
48
47
  pub mod style_controls;
49
48
  pub mod type_icon;
@@ -19,10 +19,12 @@ use itertools::Itertools;
19
19
  use perspective_client::config::ViewConfig;
20
20
  use yew::prelude::*;
21
21
 
22
+ use crate::components::column_settings_sidebar::style_tab::alignment_field::AlignmentField;
23
+ use crate::components::column_settings_sidebar::style_tab::font_field::FontField;
22
24
  use crate::components::column_settings_sidebar::style_tab::primitive_field::{
23
25
  BoolField, ColorField, EnumField, NumberFieldPrimitive,
24
26
  };
25
- use crate::config::ControlSpec;
27
+ use crate::config::{Alignment, ControlSpec, FontToggle};
26
28
  use crate::presentation::Presentation;
27
29
  use crate::queries::get_plugin_config_schema;
28
30
  use crate::renderer::Renderer;
@@ -72,9 +74,10 @@ pub fn PluginTab(props: &PluginTabProps) -> Html {
72
74
  let schema = {
73
75
  let renderer = props.renderer.clone();
74
76
  let view_config = props.view_config.clone();
77
+ let plugin_config = props.plugin_config.clone();
75
78
  use_memo(
76
79
  (props.plugin_config.clone(), props.view_config.clone()),
77
- move |_| match get_plugin_config_schema(&renderer, &view_config) {
80
+ move |_| match get_plugin_config_schema(&renderer, &view_config, Some(&plugin_config)) {
78
81
  Ok(schema) => schema.fields,
79
82
  Err(error) => {
80
83
  tracing::error!("{}", error);
@@ -180,6 +183,58 @@ fn render_leaf(
180
183
  />
181
184
  })
182
185
  },
186
+ ControlSpec::Font {
187
+ key,
188
+ default,
189
+ size,
190
+ bold,
191
+ italic,
192
+ } => {
193
+ let current = raw_config
194
+ .get(&key)
195
+ .and_then(|v| v.as_str().map(|s| s.to_string()));
196
+ let toggle = |t: &FontToggle| raw_config.get(&t.key).and_then(|v| v.as_bool());
197
+ let size_current = size
198
+ .as_ref()
199
+ .and_then(|s| raw_config.get(&s.key))
200
+ .and_then(|v| v.as_f64());
201
+ let bold_current = bold.as_ref().and_then(toggle);
202
+ let italic_current = italic.as_ref().and_then(toggle);
203
+ Some(html! {
204
+ <FontField
205
+ field_key={key}
206
+ {default}
207
+ {current}
208
+ {size}
209
+ {size_current}
210
+ {bold}
211
+ {bold_current}
212
+ {italic}
213
+ {italic_current}
214
+ on_change={on_change.clone()}
215
+ />
216
+ })
217
+ },
218
+ ControlSpec::Alignment {
219
+ key,
220
+ default,
221
+ corners,
222
+ } => {
223
+ let current = raw_config
224
+ .get(&key)
225
+ .and_then(|v| v.as_str())
226
+ .and_then(Alignment::parse);
227
+
228
+ Some(html! {
229
+ <AlignmentField
230
+ field_key={key}
231
+ {default}
232
+ {corners}
233
+ {current}
234
+ on_change={on_change.clone()}
235
+ />
236
+ })
237
+ },
183
238
  ControlSpec::Bool { key, default } => {
184
239
  let current = raw_config.get(&key).and_then(|v| v.as_bool());
185
240
  Some(html! {
@@ -220,7 +275,6 @@ fn render_leaf(
220
275
  | ControlSpec::AggregateDepth
221
276
  | ControlSpec::NumberSeriesStyle { .. }
222
277
  | ControlSpec::DatetimeFormat { .. }
223
- | ControlSpec::StringFormat
224
278
  | ControlSpec::Symbols { .. }
225
279
  | ControlSpec::NumberFormat { .. }
226
280
  | ControlSpec::String { .. }
@@ -60,6 +60,10 @@ pub struct SettingsPanelProps {
60
60
  /// prop-driven instead of reading `Renderer` directly.
61
61
  pub plugin_config: PtrEqRc<serde_json::Map<String, serde_json::Value>>,
62
62
 
63
+ /// Snapshot of the active plugin's `columns_config` bucket, threaded
64
+ /// from `RendererProps` into `ColumnSelector`.
65
+ pub columns_config: PtrEqRc<ColumnConfigMap>,
66
+
63
67
  /// Column currently being dragged (if any) — threaded to show drag
64
68
  /// highlights without per-component `DragDrop` PubSub subscriptions.
65
69
  pub drag_column: Option<String>,
@@ -114,6 +118,7 @@ impl PartialEq for SettingsPanelProps {
114
118
  && self.plugin_static_config == rhs.plugin_static_config
115
119
  && self.view_config == rhs.view_config
116
120
  && self.plugin_config == rhs.plugin_config
121
+ && self.columns_config == rhs.columns_config
117
122
  && self.drag_column == rhs.drag_column
118
123
  && self.metadata == rhs.metadata
119
124
  && self.open_column_settings == rhs.open_column_settings
@@ -356,6 +361,7 @@ pub fn SettingsPanel(props: &SettingsPanelProps) -> Html {
356
361
  named_column_count={props.named_column_count}
357
362
  plugin_static_config={props.plugin_static_config.clone()}
358
363
  view_config={props.view_config.clone()}
364
+ columns_config={props.columns_config.clone()}
359
365
  drag_column={props.drag_column.clone()}
360
366
  metadata={props.metadata.clone()}
361
367
  selected_theme={props.selected_theme.clone()}
@@ -276,10 +276,19 @@ impl Component for StatusBar {
276
276
  // TODO Memoizing these would reduce some vdom diffing later on
277
277
  let onclose = ctx.link().callback(|_| StatusBarMsg::Eject);
278
278
  let onpointerdown = ctx.link().callback(StatusBarMsg::PointerEvent);
279
- let onexport = ctx.link().callback(|_: MouseEvent| StatusBarMsg::Export);
280
- let oncopy = ctx.link().callback(|_: MouseEvent| StatusBarMsg::Copy);
279
+ let onexport = ctx.link().callback(|event: MouseEvent| {
280
+ event.prevent_default();
281
+ StatusBarMsg::Export
282
+ });
283
+ let oncopy = ctx.link().callback(|event: MouseEvent| {
284
+ event.prevent_default();
285
+ StatusBarMsg::Copy
286
+ });
281
287
  let onreset = ctx.link().callback(StatusBarMsg::Reset);
282
- let onnew = ctx.link().callback(|_: MouseEvent| StatusBarMsg::NewPanel);
288
+ let onnew = ctx.link().callback(|event: MouseEvent| {
289
+ event.prevent_default();
290
+ StatusBarMsg::NewPanel
291
+ });
283
292
  let on_new_select = ctx.link().callback(StatusBarMsg::NewPanelPick);
284
293
  let on_close_new = ctx.link().callback(|_| StatusBarMsg::CloseNewPanel);
285
294
 
@@ -51,6 +51,8 @@ pub fn StatusIndicator(props: &StatusIndicatorProps) -> Html {
51
51
  && matches!(props.session_props.has_table, Some(TableLoadState::Loading))
52
52
  {
53
53
  StatusIconState::Loading
54
+ } else if matches!(props.session_props.has_table, Some(TableLoadState::Pending)) {
55
+ StatusIconState::Pending
54
56
  } else if props.update_count > 0 {
55
57
  StatusIconState::Updating
56
58
  } else if has_table_cells {
@@ -65,6 +67,7 @@ pub fn StatusIndicator(props: &StatusIndicatorProps) -> Html {
65
67
  StatusIconState::Normal => "connected",
66
68
  StatusIconState::Updating => "updating",
67
69
  StatusIconState::Loading => "loading",
70
+ StatusIconState::Pending => "pending",
68
71
  StatusIconState::Uninitialized => "uninitialized",
69
72
  };
70
73
 
@@ -107,6 +110,7 @@ pub fn StatusIndicator(props: &StatusIndicatorProps) -> Html {
107
110
  #[derive(Clone, Debug, PartialEq)]
108
111
  enum StatusIconState {
109
112
  Loading,
113
+ Pending,
110
114
  Updating,
111
115
  Errored(String, String, &'static str, bool),
112
116
  Normal,
@@ -97,10 +97,10 @@ impl PerspectiveViewer {
97
97
  derived
98
98
  } else {
99
99
  // Nothing derivable from the event's configs — fall back
100
- // to the clicked-cell clause if the detail carried one,
101
- // else leave the current contribution untouched.
100
+ // to the clicked-cell clause if the detail carried one.
102
101
  match selection.cell_fallback {
103
102
  Some(x) => vec![x],
103
+ None if selection.clear_if_underivable => Vec::new(),
104
104
  None => return false,
105
105
  }
106
106
  }
@@ -43,6 +43,10 @@ pub struct MasterSelection {
43
43
  /// `filters` derives to nothing — e.g. a FLAT (un-grouped) datagrid
44
44
  /// master, whose clicks carry no group-by path to filter on.
45
45
  pub cell_fallback: Option<Filter>,
46
+
47
+ /// Whether an event deriving no clause and carrying no `cell_fallback`
48
+ /// clears the panel's contribution rather than leaving it untouched.
49
+ pub clear_if_underivable: bool,
46
50
  }
47
51
 
48
52
  #[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -190,7 +190,7 @@ impl PerspectiveViewer {
190
190
  pub(super) fn on_panel_closed(&mut self, ctx: &Context<Self>, id: String) -> bool {
191
191
  let id = PanelId::from(id);
192
192
  if let Some(panel) = ctx.props().workspace.take_closing(&id) {
193
- let eject = eject_panel(panel);
193
+ let eject = eject_panel(panel, Disposal::Reject);
194
194
  match self.pending_closes.remove(&id) {
195
195
  Some(completion) => completion.resolve_after(eject),
196
196
  None => spawn_owned("close-panel", eject),
@@ -201,7 +201,7 @@ impl PerspectiveViewer {
201
201
 
202
202
  match self.detach_panel(ctx, &id) {
203
203
  Some(panel) => {
204
- spawn_owned("close-panel", eject_panel(panel));
204
+ spawn_owned("close-panel", eject_panel(panel, Disposal::Reject));
205
205
  true
206
206
  },
207
207
  None => false,
@@ -235,6 +235,7 @@ impl PerspectiveViewer {
235
235
  None,
236
236
  update,
237
237
  client,
238
+ crate::session::MissingTable::Error,
238
239
  )
239
240
  .await?;
240
241
 
@@ -269,6 +270,7 @@ impl PerspectiveViewer {
269
270
  None,
270
271
  ViewerConfigInitial::new(table_name),
271
272
  client,
273
+ crate::session::MissingTable::Error,
272
274
  )
273
275
  .await?;
274
276
 
@@ -313,6 +315,7 @@ impl PerspectiveViewer {
313
315
  None,
314
316
  ViewerConfigInitial::new(table),
315
317
  Some(client),
318
+ crate::session::MissingTable::Error,
316
319
  )
317
320
  .await?;
318
321
 
@@ -334,7 +337,7 @@ impl PerspectiveViewer {
334
337
  self._active_subscriptions = create_active_subscriptions(ctx, &session, &renderer);
335
338
  self.session_props = session.to_props();
336
339
  self.renderer_props = renderer.to_props(None);
337
- self.update_count = session.in_flight_config_runs();
340
+ self.update_count = session.config_runs.count();
338
341
  self.active_session = session;
339
342
  self.active_renderer = renderer;
340
343
  let presentation = ctx.props().presentation.clone();
@@ -95,6 +95,7 @@ impl PerspectiveViewer {
95
95
  plugin_static_config={self.renderer_props.config.clone()}
96
96
  {view_config}
97
97
  plugin_config={self.renderer_props.plugin_config.clone()}
98
+ columns_config={self.renderer_props.columns_config.clone()}
98
99
  {drag_column}
99
100
  metadata={metadata.clone()}
100
101
  open_column_settings={self.presentation_props.open_column_settings.clone()}
@@ -150,6 +150,7 @@ pub(super) fn global_filter_listener(
150
150
  let selection = selected.then(|| MasterSelection {
151
151
  cell_fallback: cell_fallback(&row, &column_names),
152
152
  filters: insert_filters,
153
+ clear_if_underivable: true,
153
154
  });
154
155
 
155
156
  cb.emit((panel, selection));
@@ -210,6 +211,7 @@ pub(super) fn master_click_listener(
210
211
  let selection = MasterSelection {
211
212
  cell_fallback: cell_fallback(&row, &column_names),
212
213
  filters: config.map(|x| x.filter).unwrap_or_default(),
214
+ clear_if_underivable: false,
213
215
  };
214
216
 
215
217
  cb.emit((panel, Some(selection)));
@@ -284,7 +286,8 @@ pub(super) fn create_active_subscriptions(
284
286
  let sub4 = session.view_config_changed.add_notify_listener(&cb);
285
287
  let sub5 = session.title_changed.add_notify_listener(&cb);
286
288
  let sub6 = session
287
- .run_state_changed
289
+ .config_runs
290
+ .changed()
288
291
  .add_listener(ctx.link().callback(UpdateInFlight));
289
292
 
290
293
  let sub7 = session.column_stats_changed.add_notify_listener(&cb);
@@ -304,12 +307,21 @@ pub(super) fn create_active_subscriptions(
304
307
  }
305
308
  });
306
309
 
310
+ let cb_columns_config = ctx.link().callback({
311
+ let renderer = renderer.clone();
312
+ move |_: ColumnConfigMap| UpdateRenderer(Box::new(renderer.to_props(None)))
313
+ });
314
+
307
315
  let sub1 = renderer.plugin_changed.add_listener(cb_plugin);
308
316
  let sub2 = renderer
309
317
  .plugin_config_changed
310
318
  .add_listener(cb_plugin_config);
311
319
 
312
- vec![sub1, sub2]
320
+ let sub3 = renderer
321
+ .columns_config_changed
322
+ .add_listener(cb_columns_config);
323
+
324
+ vec![sub1, sub2, sub3]
313
325
  };
314
326
 
315
327
  let mut subscriptions = Vec::new();
@@ -124,8 +124,8 @@ pub struct PerspectiveViewer {
124
124
  dragdrop_props: crate::presentation::DragDropProps,
125
125
 
126
126
  /// The active panel's in-flight config-run count — a LEVEL-triggered
127
- /// snapshot of `Session::in_flight_config_runs` (RAII-settled;
128
- /// assigned by `UpdateInFlight`, re-read on retarget). Threaded to
127
+ /// snapshot of `Session::config_runs` (RAII-settled; assigned by
128
+ /// `UpdateInFlight`, re-read on retarget). Threaded to
129
129
  /// `StatusIndicator` as the "updating" spinner.
130
130
  update_count: u32,
131
131