use std::cell::RefCell;
use std::rc::Rc;
use perspective_client::utils::PerspectiveResultExt;
use perspective_client::{Client, clone};
use perspective_js::utils::{ApiFuture, ApiResult, LocalPollLoop};
use wasm_bindgen::JsValue;
use super::pipeline::RunOrigin;
use super::restore_panel::{Rebind, RestoreErrors, RestoreMode, restore_panel_step};
use crate::config::{OptionalUpdate, ViewerConfigUpdate};
use crate::presentation::Presentation;
use crate::utils::{AddListener, Subscription};
use crate::workspace::{Panel, Workspace};
pub(crate) type HostedTableSubs = Rc<RefCell<Vec<(Client, u32)>>>;
pub(crate) fn wire_table_lifecycle(
workspace: &Workspace,
presentation: &Presentation,
) -> (Subscription, HostedTableSubs) {
let subs: HostedTableSubs = Rc::default();
let sub = workspace.client_registered().add_listener({
clone!(workspace, presentation, subs);
move |client: Client| {
clone!(workspace, presentation, subs);
ApiFuture::spawn(async move {
let poll_loop = LocalPollLoop::new({
clone!(workspace, presentation);
move |()| {
clone!(workspace, presentation);
ApiFuture::spawn(async move {
sweep_table_bindings(&workspace, &presentation).await
});
Ok(JsValue::UNDEFINED)
}
});
let id = client
.on_hosted_tables_update(move || {
let poll_loop = poll_loop.clone();
async move {
poll_loop.poll(()).await;
}
})
.await?;
subs.borrow_mut().push((client, id));
sweep_table_bindings(&workspace, &presentation).await
})
}
});
(sub, subs)
}
pub(crate) async fn sweep_table_bindings(
workspace: &Workspace,
presentation: &Presentation,
) -> ApiResult<()> {
if let Err(err) = super::resume_derived(workspace).await {
tracing::warn!("Failed to resume derived tables: {err}");
}
for panel in workspace.panels() {
if let Some(name) = panel.session.get_table().map(|t| t.get_name().to_owned())
&& let Some(client) = panel.session.get_client()
&& let Ok(hosted) = client.get_hosted_table_names().await
&& !hosted.iter().any(|n| n == &name)
{
suspend_panel(&panel).await.unwrap_or_log();
}
}
for panel in workspace.panels() {
if panel.session.pending_table().is_some() {
bind_pending(&panel, workspace, presentation)
.await
.unwrap_or_log();
}
}
workspace.hosted_tables_changed().emit(());
Ok(())
}
async fn suspend_panel(panel: &Panel) -> ApiResult<()> {
clone!(panel.session, panel.renderer);
let ticket = panel.session.submit(
crate::session::OpKind::Restore { update: None },
move |_ctx| {
Box::pin(async move {
renderer
.clone()
.render_task(|_guard| async move {
if let Some(reset) = session.suspend_table() {
reset.await?;
if let Some(plugin) = renderer.active_plugin() {
plugin.clear().await?;
}
}
Ok(())
})
.await?;
Ok(None)
})
},
);
ticket.settle().await
}
async fn bind_pending(
panel: &Panel,
workspace: &Workspace,
presentation: &Presentation,
) -> ApiResult<()> {
clone!(panel.session, panel.renderer, workspace, presentation);
let ticket = panel.session.submit(
crate::session::OpKind::Restore { update: None },
move |ctx| {
Box::pin(async move {
let Some(name) = session.pending_table() else {
return Ok(None);
};
let update = ViewerConfigUpdate {
table: OptionalUpdate::Update(name.into()),
plugin_config: OptionalUpdate::Update(renderer.committed_plugin_config()),
columns_config: OptionalUpdate::Update(renderer.committed_columns_configs()),
..ViewerConfigUpdate::default()
};
let result = restore_panel_step(
&ctx,
&session,
&renderer,
&presentation,
&workspace,
RestoreMode::Existing { active: false },
Rebind::Complete,
RunOrigin::Internal,
update,
RestoreErrors::Publish,
crate::session::MissingTable::Pend,
)
.await;
if let Err(e) = &result
&& session.pending_table().is_some()
{
let _ = renderer.fail(e.clone());
}
result?;
Ok(None)
})
},
);
ticket.settle().await
}