use std::rc::Rc;
use perspective_client::config::ViewConfigUpdate;
use perspective_js::utils::*;
use super::pipeline::{Committed, RunCommit, RunOrigin, RunSpec, locked_run, run_locked};
use crate::config::{OptionalUpdate, SettingsUpdate, ThemeUpdate, ViewerConfigUpdate};
use crate::presentation::Presentation;
use crate::renderer::{
PluginRef, PreparedRenderer, Renderer, ValidatedColumnsConfig, ValidatedPluginConfig,
};
use crate::session::{
BindPlan, BindingEffects, OverlayClause, PreparedView, Session, ViewDefaults,
};
pub(crate) struct Prepared {
state: PreparedState,
effects: Effects,
}
struct PreparedState {
view: PreparedView,
plugin: Option<PluginRef>,
target_name: String,
plugin_config: ValidatedPluginConfig,
columns_config: ValidatedColumnsConfig,
theme: Option<Option<String>>,
title: Option<Option<String>>,
}
pub(crate) struct Effects {
settings: SettingsUpdate,
host_theme: ThemeUpdate,
}
pub(crate) async fn prepare(
session: &Session,
renderer: &Renderer,
presentation: &Presentation,
active: bool,
plan: BindPlan,
overlay: Option<Rc<Vec<OverlayClause>>>,
update: ViewerConfigUpdate,
) -> ApiResult<Prepared> {
let ViewerConfigUpdate {
plugin,
plugin_config,
columns_config,
settings,
theme,
title,
view_config,
..
} = update;
renderer.check_plugin_update(&plugin)?;
let named = renderer.resolve_plugin_update(&plugin);
let (target, swap_idx) = renderer.resolve_target(&plugin)?;
let rebinds = !matches!(plan, BindPlan::Keep);
let defaults = match &named {
Some((_, metadata)) => ViewDefaults::Swap(metadata),
None if rebinds && matches!(plugin, OptionalUpdate::Update(_)) => {
ViewDefaults::Swap(&target.static_config)
},
None => ViewDefaults::Rollup(&target.static_config),
};
let view = session
.prepare_view(plan, view_config, defaults, overlay)
.await?;
let (plugin_config, columns_config) = renderer.prepare_buckets(
&target,
session,
view.config(),
view.view_schema(),
rebinds,
plugin_config,
columns_config,
)?;
let panel_theme = match &theme {
OptionalUpdate::Update(theme) => Some(Some(theme.clone())),
OptionalUpdate::SetDefault => Some(presentation.get_default_theme_name().await),
OptionalUpdate::Missing if renderer.committed_theme().is_none() => {
Some(presentation.get_default_theme_name().await).filter(|x| x.is_some())
},
OptionalUpdate::Missing => None,
};
let title = match title {
OptionalUpdate::Update(title) => Some(Some(title).filter(|x| !x.is_empty())),
OptionalUpdate::SetDefault => Some(None),
OptionalUpdate::Missing => None,
};
Ok(Prepared {
state: PreparedState {
view,
plugin: swap_idx.map(|idx| renderer.plugin_ref(idx)).transpose()?,
target_name: target.static_config.name.clone(),
plugin_config,
columns_config,
theme: panel_theme,
title,
},
effects: Effects {
settings,
host_theme: if active {
theme
} else {
OptionalUpdate::Missing
},
},
})
}
pub(crate) async fn prepare_edit(
session: &Session,
renderer: &Renderer,
update: ViewConfigUpdate,
plugin_idx: Option<usize>,
) -> ApiResult<Prepared> {
let selected = renderer.committed_plugin_idx();
let (target, swap_idx) = match plugin_idx.filter(|idx| Some(*idx) != selected) {
Some(idx) => (renderer.target_at(idx)?, Some(idx)),
None => renderer.resolve_target(&OptionalUpdate::Missing)?,
};
let view = session
.prepare_view(BindPlan::Keep, update, ViewDefaults::AsGiven, None)
.await?
.projected();
edit_of(renderer, view, &target.static_config.name, swap_idx)
}
pub(crate) async fn prepare_overlay(
session: &Session,
renderer: &Renderer,
overlay: Rc<Vec<OverlayClause>>,
) -> ApiResult<Prepared> {
let (target, swap_idx) = renderer.resolve_target(&OptionalUpdate::Missing)?;
let view = session
.prepare_view(
BindPlan::Keep,
ViewConfigUpdate::default(),
ViewDefaults::AsGiven,
Some(overlay),
)
.await?;
edit_of(renderer, view, &target.static_config.name, swap_idx)
}
fn edit_of(
renderer: &Renderer,
view: PreparedView,
target_name: &str,
swap_idx: Option<usize>,
) -> ApiResult<Prepared> {
Ok(Prepared {
state: PreparedState {
view,
plugin: swap_idx.map(|idx| renderer.plugin_ref(idx)).transpose()?,
target_name: target_name.to_owned(),
plugin_config: ValidatedPluginConfig::Missing,
columns_config: ValidatedColumnsConfig::Missing,
theme: None,
title: None,
},
effects: Effects {
settings: OptionalUpdate::Missing,
host_theme: OptionalUpdate::Missing,
},
})
}
pub(crate) fn commit_edit(session: &Session, renderer: &Renderer, prepared: Prepared) -> Committed {
let (committed, binding) = commit(session, renderer, prepared.state);
debug_assert!(!binding.rebound());
binding.forget();
committed
}
pub(crate) async fn commit_and_render_locked(
guard: crate::utils::RenderGuard,
session: &Session,
renderer: &Renderer,
Prepared { state, .. }: Prepared,
) -> ApiResult<()> {
let commit = {
let (session, renderer) = (session.clone(), renderer.clone());
Box::new(move || commit(&session, &renderer, state))
};
run_locked(guard, session.clone(), renderer.clone(), RunSpec {
origin: RunOrigin::Public,
commit: RunCommit::Deferred(commit),
presentation: None,
})
.await
}
fn commit(
session: &Session,
renderer: &Renderer,
prepared: PreparedState,
) -> (Committed, BindingEffects) {
let PreparedState {
view,
plugin,
target_name,
plugin_config,
columns_config,
theme,
title,
} = prepared;
let had_plugin = renderer.active_plugin().is_some();
let activate = plugin.is_some();
let binding = session.commit_view(view, title);
let changed = renderer.commit(PreparedRenderer {
plugin,
target_name,
plugin_config,
columns_config,
theme,
});
let committed = Committed {
activate,
plugin_swapped: activate && had_plugin,
plugin_config_changed: changed.plugin_config,
columns_config_changed: changed.columns_config,
};
(committed, binding)
}
pub(crate) async fn restore_in_place(
session: &Session,
renderer: &Renderer,
presentation: &Presentation,
origin: RunOrigin,
update: ViewerConfigUpdate,
) -> ApiResult<()> {
let prepared = prepare(
session,
renderer,
presentation,
false,
BindPlan::Keep,
None,
update,
)
.await?;
commit_and_render(session, renderer, presentation, origin, prepared)
.await
.result
}
pub(crate) struct Outcome {
pub committed: bool,
pub result: ApiResult<()>,
}
pub(crate) async fn commit_and_render(
session: &Session,
renderer: &Renderer,
presentation: &Presentation,
origin: RunOrigin,
Prepared { state, effects }: Prepared,
) -> Outcome {
let Effects {
settings,
host_theme,
} = effects;
let did_commit = std::rc::Rc::new(std::cell::Cell::new(false));
let result = render(
session,
renderer,
presentation,
origin,
settings,
host_theme,
{
let (session, renderer, did_commit) =
(session.clone(), renderer.clone(), did_commit.clone());
Box::new(move || {
did_commit.set(true);
commit(&session, &renderer, state)
})
},
)
.await;
Outcome {
committed: did_commit.get(),
result,
}
}
#[allow(clippy::too_many_arguments)]
async fn render(
session: &Session,
renderer: &Renderer,
presentation: &Presentation,
origin: RunOrigin,
settings: SettingsUpdate,
host_theme: ThemeUpdate,
commit: Box<dyn FnOnce() -> (Committed, BindingEffects)>,
) -> ApiResult<()> {
if let OptionalUpdate::Update(x) = settings {
presentation.set_settings_attribute(x);
presentation.set_settings_before_open(x);
}
match host_theme {
OptionalUpdate::SetDefault => {
if presentation.get_selected_theme_name().await.is_some() {
presentation.set_theme_name(None).await?;
}
},
OptionalUpdate::Update(x) => {
presentation.set_theme_name(Some(&x)).await?;
},
OptionalUpdate::Missing => {},
}
locked_run(session, renderer, RunSpec {
origin,
commit: RunCommit::Deferred(commit),
presentation: Some(presentation.clone()),
})
.await?;
if renderer.needs_restyle() {
renderer.restyle_all().await?;
}
presentation.publish_theme_config().await?;
Ok(())
}