use wasm_bindgen::JsCast;
use web_sys::*;
use yew::prelude::*;
use super::style::LocalStyle;
use crate::components::containers::select::*;
use crate::components::status_bar_counter::StatusBarRowsCounter;
use crate::custom_elements::copy_dropdown::*;
use crate::custom_elements::export_dropdown::*;
use crate::presentation::Presentation;
use crate::renderer::*;
use crate::session::*;
#[cfg(test)]
use crate::utils::WeakScope;
use crate::utils::*;
use crate::*;
#[derive(Properties)]
pub struct StatusBarProps {
pub id: String,
pub on_reset: Callback<bool>,
pub session: Session,
pub renderer: Renderer,
pub presentation: Presentation,
#[cfg(test)]
#[prop_or_default]
pub weak_link: WeakScope<StatusBar>,
}
derive_model!(Renderer, Session, Presentation for StatusBarProps);
impl PartialEq for StatusBarProps {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
pub enum StatusBarMsg {
Reset(bool),
Export,
Copy,
SetThemeConfig((Vec<String>, Option<usize>)),
SetTheme(String),
TableStatsChanged,
SetIsUpdating(bool),
SetTitle(Option<String>),
}
pub struct StatusBar {
is_updating: i32,
theme: Option<String>,
themes: Vec<String>,
export_ref: NodeRef,
copy_ref: NodeRef,
_sub: [Subscription; 5],
}
impl Component for StatusBar {
type Message = StatusBarMsg;
type Properties = StatusBarProps;
fn create(ctx: &Context<Self>) -> Self {
let _sub = [
ctx.props()
.session
.stats_changed
.add_listener(ctx.link().callback(|_| StatusBarMsg::TableStatsChanged)),
ctx.props()
.session
.view_config_changed
.add_listener(ctx.link().callback(|_| StatusBarMsg::SetIsUpdating(true))),
ctx.props()
.session
.view_created
.add_listener(ctx.link().callback(|_| StatusBarMsg::SetIsUpdating(false))),
ctx.props()
.presentation
.theme_config_updated
.add_listener(ctx.link().callback(StatusBarMsg::SetThemeConfig)),
ctx.props()
.presentation
.title_changed
.add_listener(ctx.link().callback(|_| StatusBarMsg::TableStatsChanged)),
];
let presentation = ctx.props().presentation.clone();
let on_theme = ctx.link().callback(StatusBarMsg::SetThemeConfig);
ApiFuture::spawn(async move {
on_theme.emit(presentation.get_selected_theme_config().await?);
Ok(())
});
Self {
_sub,
theme: None,
themes: vec![],
copy_ref: NodeRef::default(),
export_ref: NodeRef::default(),
is_updating: 0,
}
}
fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
match msg {
StatusBarMsg::SetIsUpdating(is_updating) => {
self.is_updating = max!(0, self.is_updating + if is_updating { 1 } else { -1 });
true
},
StatusBarMsg::TableStatsChanged => true,
StatusBarMsg::Reset(all) => {
ctx.props().on_reset.emit(all);
false
},
StatusBarMsg::SetThemeConfig((themes, index)) => {
let new_theme = index.and_then(|x| themes.get(x)).cloned();
let should_render = new_theme != self.theme || self.themes != themes;
self.theme = new_theme;
self.themes = themes;
should_render
},
StatusBarMsg::SetTheme(theme_name) => {
clone!(
ctx.props().renderer,
ctx.props().session,
ctx.props().presentation
);
ApiFuture::spawn(async move {
presentation.set_theme_name(Some(&theme_name)).await?;
let view = session.get_view().into_apierror()?;
renderer.restyle_all(&view).await
});
false
},
StatusBarMsg::Export => {
let target = self.export_ref.cast::<HtmlElement>().unwrap();
ExportDropDownMenuElement::new_from_model(ctx.props()).open(target);
false
},
StatusBarMsg::Copy => {
let target = self.copy_ref.cast::<HtmlElement>().unwrap();
CopyDropDownMenuElement::new_from_model(ctx.props()).open(target);
false
},
StatusBarMsg::SetTitle(title) => {
ctx.props().presentation.set_title(title);
false
},
}
}
fn view(&self, ctx: &Context<Self>) -> Html {
let stats = ctx.props().session.get_table_stats();
let mut is_updating_class_name = classes!();
if self.is_updating > 0 {
is_updating_class_name.push("updating")
};
if ctx.props().presentation.get_title().is_some() {
is_updating_class_name.push("titled")
};
let reset = ctx
.link()
.callback(|event: MouseEvent| StatusBarMsg::Reset(event.shift_key()));
let export = ctx.link().callback(|_: MouseEvent| StatusBarMsg::Export);
let copy = ctx.link().callback(|_: MouseEvent| StatusBarMsg::Copy);
let theme_button = match &self.theme {
None => html! {},
Some(selected) => {
let ontheme = ctx.link().callback(StatusBarMsg::SetTheme);
let values = self
.themes
.iter()
.cloned()
.map(SelectItem::Option)
.collect::<Vec<_>>();
html! {
if values.len() > 1 {
<span class="hover-target">
<span id="theme" class="button">
<Select<String>
id="theme_selector"
{values}
selected={selected.to_owned()}
on_select={ontheme}
/>
</span>
</span>
}
}
},
};
let oninput = ctx.link().callback({
move |input: InputEvent| {
let title = input
.target()
.unwrap()
.unchecked_into::<HtmlInputElement>()
.value();
let title = if title.trim().is_empty() {
None
} else {
Some(title)
};
StatusBarMsg::SetTitle(title)
}
});
let is_menu = ctx.props().session.has_table()
&& (ctx.props().presentation.is_settings_open()
|| ctx.props().presentation.get_title().is_some());
html! {
<>
<LocalStyle href={css!("status-bar")} />
<div id={ctx.props().id.clone()} class={is_updating_class_name}>
<StatusIndicator {stats} is_updating={self.is_updating > 0}>
<label
class="input-sizer"
data-value={ctx.props().presentation.get_title().unwrap_or_default()}
>
<input
placeholder=" "
value={ctx.props().presentation.get_title()}
size="10"
{oninput}
/>
<span id="status-bar-placeholder" />
</label>
</StatusIndicator>
<div id="spacer" />
if is_menu {
<div id="menu-bar" class="section">
{ theme_button }
<div id="plugin-settings"><slot name="plugin-settings" /></div>
<span class="hover-target">
<span id="reset" class="button" onmousedown={reset}><span /></span>
</span>
<span class="hover-target" ref={&self.export_ref} onmousedown={export}>
<span id="export" class="button"><span /></span>
</span>
<span class="hover-target" ref={&self.copy_ref} onmousedown={copy}>
<span id="copy" class="button"><span /></span>
</span>
</div>
}
</div>
</>
}
}
}
#[derive(Clone, Properties, PartialEq)]
pub struct StatusIndicatorProps {
stats: Option<ViewStats>,
is_updating: bool,
children: Children,
}
#[function_component]
fn StatusIndicator(props: &StatusIndicatorProps) -> Html {
let class_name = match &props.stats {
Some(ViewStats {
num_table_cells: Some(_),
..
}) => {
if props.is_updating {
"updating"
} else {
"connected"
}
},
Some(ViewStats {
num_table_cells: None,
..
}) => "loading",
None => "uninitialized",
};
html! {
<>
<div class="section">
<span id="status" class={class_name} />
<span id="status_updating" class={class_name} />
</div>
{ for props.children.iter() }
<div id="rows" class="section"><StatusBarRowsCounter stats={props.stats.clone()} /></div>
</>
}
}