use std::cell::Cell;
use std::rc::Rc;
use perspective_client::config::*;
use perspective_client::{OnUpdateOptions, View};
use wasm_bindgen::prelude::*;
use yew::prelude::*;
use crate::utils::*;
use crate::*;
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct ViewStats {
pub is_group_by: bool,
pub is_split_by: bool,
pub is_filtered: bool,
pub num_table_cells: Option<(u32, u32)>,
pub num_view_cells: Option<(u32, u32)>,
}
#[derive(Clone)]
struct ViewSubscriptionData {
view: View,
config: Rc<ViewConfig>,
callback_id: Rc<Cell<u32>>,
on_stats: Callback<ViewStats>,
on_update: Option<Callback<()>>,
is_deleted: Rc<Cell<bool>>,
}
pub struct ViewSubscription {
data: ViewSubscriptionData,
}
impl ViewSubscriptionData {
async fn on_view_update(self) -> ApiResult<JsValue> {
if let Some(on_update) = &self.on_update {
on_update.emit(());
};
self.clone().update_view_stats().await?;
Ok(JsValue::UNDEFINED)
}
async fn update_view_stats(self) -> ApiResult<JsValue> {
let dimensions = self.view.dimensions().await?;
let num_rows = dimensions.num_table_rows as u32;
let num_cols = dimensions.num_table_columns as u32;
let virtual_rows = dimensions.num_view_rows as u32;
let virtual_cols = dimensions.num_view_columns as u32;
let stats = ViewStats {
num_table_cells: Some((num_rows, num_cols)),
num_view_cells: Some((virtual_rows, virtual_cols)),
is_filtered: virtual_rows != num_rows,
is_group_by: !self.config.group_by.is_empty(),
is_split_by: !self.config.split_by.is_empty(),
};
self.on_stats.emit(stats);
Ok(JsValue::UNDEFINED)
}
async fn internal_delete(&self) -> ApiResult<()> {
let view = &self.view;
if self.on_update.is_some() {
view.remove_update(self.callback_id.get()).await?;
}
view.delete().await?;
self.is_deleted.set(true);
Ok(())
}
}
impl ViewSubscription {
pub async fn new(
view: perspective_client::View,
config: ViewConfig,
on_stats: Callback<ViewStats>,
on_update: Option<Callback<()>>,
) -> Result<Self, ApiError> {
let data = ViewSubscriptionData {
view,
config: config.into(),
on_stats,
callback_id: Rc::default(),
on_update,
is_deleted: Rc::default(),
};
if data.on_update.is_some() {
let emit = perspective_js::utils::LocalPollLoop::new({
clone!(data);
move |_| {
ApiFuture::spawn(data.clone().on_view_update());
Ok(JsValue::UNDEFINED)
}
});
clone!(data.view, data.callback_id);
let result = view
.on_update(
Box::new(move |msg| emit.poll(msg)),
OnUpdateOptions::default(),
)
.await?;
callback_id.set(result);
}
ApiFuture::spawn(data.clone().update_view_stats());
Ok(Self { data })
}
pub fn update_view_config(&mut self, config: Rc<ViewConfig>) {
self.data.config = config
}
pub const fn get_view(&self) -> &View {
&self.data.view
}
pub async fn delete(self) -> ApiResult<()> {
self.data.internal_delete().await
}
pub fn dismiss(self) {
self.data.is_deleted.set(true);
}
}
#[extend::ext]
pub impl Option<ViewSubscription> {
async fn delete(self) -> ApiResult<()> {
if let Some(x) = self {
x.delete().await?;
}
Ok(())
}
}
impl Drop for ViewSubscription {
fn drop(&mut self) {
if !self.data.is_deleted.get() {
tracing::debug!("View dropped without calling `delete()`");
let view = self.data.clone();
ApiFuture::spawn(async move { view.internal_delete().await })
}
}
}