use perspective_client::config::ViewConfig;
use perspective_js::utils::ApiFuture;
use yew::prelude::*;
use crate::presentation::Presentation;
use crate::renderer::Renderer;
use crate::session::{Session, SessionMetadataRc};
use crate::tasks::release_derived;
use crate::utils::{AddListener, PtrEqRc, Subscription, left_click, read_intl_string};
use crate::workspace::Workspace;
#[derive(Clone, Properties)]
pub struct DerivedControlProps {
pub session: Session,
pub renderer: Renderer,
pub presentation: Presentation,
pub workspace: Workspace,
pub metadata: SessionMetadataRc,
pub view_config: PtrEqRc<ViewConfig>,
pub table: Option<String>,
}
impl PartialEq for DerivedControlProps {
fn eq(&self, other: &Self) -> bool {
self.table == other.table
&& self.metadata == other.metadata
&& self.view_config == other.view_config
}
}
pub enum DerivedControlMsg {
Release(bool),
Refresh,
}
pub struct DerivedControl {
_bindings_sub: Subscription,
}
impl Component for DerivedControl {
type Message = DerivedControlMsg;
type Properties = DerivedControlProps;
fn create(ctx: &Context<Self>) -> Self {
let link = ctx.link().clone();
let _bindings_sub = ctx
.props()
.workspace
.bindings_changed()
.add_listener(move |_| link.send_message(DerivedControlMsg::Refresh));
Self { _bindings_sub }
}
fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
match msg {
DerivedControlMsg::Release(delete) => {
let props = ctx.props().clone();
let effect = props.workspace.effects().guard();
ApiFuture::spawn(async move {
let _effect = effect;
release_derived(
&props.session,
&props.renderer,
&props.presentation,
&props.workspace,
delete,
)
.await
});
false
},
DerivedControlMsg::Refresh => true,
}
}
fn view(&self, ctx: &Context<Self>) -> Html {
let props = ctx.props();
let workspace = &props.workspace;
let bound = props.table.clone().unwrap_or_default();
let is_derived = workspace.is_derived(&bound);
if !is_derived {
return html! {};
}
let is_snapshot = !props.metadata.get_features().is_some_and(|x| x.on_update);
let mut chain = workspace.derived_ancestors(&bound);
chain.reverse();
if is_derived {
chain.push(bound.clone());
chain.remove(0);
}
let sole_consumer = workspace.derived_consumers(&bound) == 1;
let release = left_click(
ctx.link()
.callback(|event: MouseEvent| DerivedControlMsg::Release(event.shift_key())),
);
let viewer = props.presentation.viewer_elem();
let intl = |slug: &str, fallback: &str| {
read_intl_string(viewer, slug).unwrap_or_else(|| fallback.to_owned())
};
let snapshot_title = intl(
"derived-snapshot-title",
"This data source does not stream - this table's data is fixed at the point it was \
derived.",
);
let release_title = if sole_consumer {
intl(
"derived-release-delete-title",
"Back to the parent table - shift-click to delete this one",
)
} else {
intl("derived-release-title", "Back to the parent table")
};
let last = chain.len().saturating_sub(1);
let derived_tables = chain.iter().enumerate().map(|(idx, name)| {
let is_own = is_derived && idx == last;
let mut close_class = classes!("derived_release");
if sole_consumer {
close_class.push("shift-alt-icon");
}
let mut level_class = classes!("derived_level");
if is_own {
level_class.push("is-own");
}
html! {
<div class={level_class}>
<div class="derived_label_wrapper">
<span class="derived_icon" />
<span class="derived_label">{ name }</span>
</div>
if is_snapshot {
<span class="derived_snapshot" title={snapshot_title.clone()} />
}
if is_own {
<span
class={close_class}
title={release_title.clone()}
onmousedown={release.clone()}
/>
} else {
<span class="derived_release disabled" />
}
</div>
}
});
html! {
<div id="derived_stack">
{ for derived_tables }
</div>
}
}
}