use std::rc::Rc;
use futures::channel::oneshot::*;
use wasm_bindgen::prelude::*;
use yew::prelude::*;
use super::column_selector::ColumnSelector;
use super::containers::split_panel::SplitPanel;
use super::font_loader::{FontLoader, FontLoaderProps, FontLoaderStatus};
use super::plugin_selector::PluginSelector;
use super::render_warning::RenderWarning;
use super::status_bar::StatusBar;
use super::style::{LocalStyle, StyleProvider};
use crate::config::*;
use crate::dragdrop::*;
use crate::model::*;
use crate::presentation::Presentation;
use crate::renderer::*;
use crate::session::*;
use crate::utils::*;
use crate::*;
#[derive(Properties)]
pub struct PerspectiveViewerProps {
pub elem: web_sys::HtmlElement,
pub session: Session,
pub renderer: Renderer,
pub presentation: Presentation,
pub dragdrop: DragDrop,
#[prop_or_default]
pub weak_link: WeakScope<PerspectiveViewer>,
}
derive_model!(Renderer, Session for PerspectiveViewerProps);
impl PartialEq for PerspectiveViewerProps {
fn eq(&self, _rhs: &Self) -> bool {
false
}
}
pub enum Msg {
Resize,
Reset(bool, Option<Sender<()>>),
ToggleSettingsInit(Option<SettingsUpdate>, Option<Sender<ApiResult<JsValue>>>),
ToggleSettingsComplete(SettingsUpdate, Sender<()>),
PreloadFontsUpdate,
RenderLimits(Option<(usize, usize, Option<usize>, Option<usize>)>),
}
pub struct PerspectiveViewer {
dimensions: Option<(usize, usize, Option<usize>, Option<usize>)>,
on_rendered: Option<Sender<()>>,
fonts: FontLoaderProps,
settings_open: bool,
on_resize: Rc<PubSub<()>>,
on_dimensions_reset: Rc<PubSub<()>>,
_subscriptions: [Subscription; 1],
}
impl Component for PerspectiveViewer {
type Message = Msg;
type Properties = PerspectiveViewerProps;
fn create(ctx: &Context<Self>) -> Self {
*ctx.props().weak_link.borrow_mut() = Some(ctx.link().clone());
let elem = ctx.props().elem.clone();
let callback = ctx.link().callback(|()| Msg::PreloadFontsUpdate);
let limit_sub = {
let callback = ctx.link().callback(|x| Msg::RenderLimits(Some(x)));
ctx.props().renderer.limits_changed.add_listener(callback)
};
Self {
dimensions: None,
on_rendered: None,
fonts: FontLoaderProps::new(&elem, callback),
settings_open: false,
on_resize: Default::default(),
on_dimensions_reset: Default::default(),
_subscriptions: [limit_sub],
}
}
fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
match msg {
Msg::PreloadFontsUpdate => true,
Msg::Resize => {
self.on_resize.emit_all(());
false
}
Msg::Reset(all, sender) => {
clone!(
ctx.props().renderer,
ctx.props().session,
ctx.props().presentation
);
ApiFuture::spawn(async move {
session.reset(all);
renderer.reset().await;
presentation.reset_available_themes(None).await;
let result = renderer.draw(session.validate().await?.create_view()).await;
if let Some(sender) = sender {
sender.send(()).unwrap();
}
result
});
false
}
Msg::ToggleSettingsInit(Some(SettingsUpdate::Missing), None) => false,
Msg::ToggleSettingsInit(Some(SettingsUpdate::Missing), Some(resolve)) => {
resolve.send(Ok(JsValue::UNDEFINED)).unwrap();
false
}
Msg::ToggleSettingsInit(Some(SettingsUpdate::SetDefault), resolve) => {
self.init_toggle_settings_task(ctx, Some(false), resolve);
false
}
Msg::ToggleSettingsInit(Some(SettingsUpdate::Update(force)), resolve) => {
self.init_toggle_settings_task(ctx, Some(force), resolve);
false
}
Msg::ToggleSettingsInit(None, resolve) => {
self.init_toggle_settings_task(ctx, None, resolve);
false
}
Msg::ToggleSettingsComplete(SettingsUpdate::SetDefault, resolve)
if self.settings_open =>
{
self.settings_open = false;
self.on_rendered = Some(resolve);
true
}
Msg::ToggleSettingsComplete(SettingsUpdate::Update(force), resolve)
if force != self.settings_open =>
{
self.settings_open = force;
self.on_rendered = Some(resolve);
true
}
Msg::ToggleSettingsComplete(_, resolve)
if matches!(self.fonts.get_status(), FontLoaderStatus::Finished) =>
{
resolve.send(()).expect("Orphan render");
false
}
Msg::ToggleSettingsComplete(_, resolve) => {
self.on_rendered = Some(resolve);
true
}
Msg::RenderLimits(dimensions) => {
if self.dimensions != dimensions {
self.dimensions = dimensions;
true
} else {
false
}
}
}
}
fn changed(&mut self, _ctx: &Context<Self>, _old: &Self::Properties) -> bool {
true
}
fn rendered(&mut self, ctx: &Context<Self>, _first_render: bool) {
ctx.props()
.presentation
.set_settings_open(Some(self.settings_open))
.unwrap();
if self.on_rendered.is_some()
&& matches!(self.fonts.get_status(), FontLoaderStatus::Finished)
{
self.on_rendered
.take()
.unwrap()
.send(())
.expect("Orphan render");
}
}
fn view(&self, ctx: &Context<Self>) -> Html {
let settings = ctx.link().callback(|_| Msg::ToggleSettingsInit(None, None));
let mut class = classes!("settings-closed");
if ctx.props().presentation.get_title().is_some() {
class.push("titled");
}
html_template! {
<StyleProvider>
<LocalStyle href={ css!("viewer") } />
if self.settings_open {
<SplitPanel
id="app_panel"
reverse=true
on_reset={ self.on_dimensions_reset.callback() }
on_resize_finished={ ctx.props().render_callback() }>
<div id="side_panel" class="column noselect split-panel orient-vertical">
<PluginSelector
session={ &ctx.props().session }
renderer={ &ctx.props().renderer }>
</PluginSelector>
<ColumnSelector
dragdrop={ &ctx.props().dragdrop }
renderer={ &ctx.props().renderer }
session={ &ctx.props().session }
on_resize={ &self.on_resize }
on_dimensions_reset={ &self.on_dimensions_reset }>
</ColumnSelector>
</div>
<div id="main_column">
<StatusBar
id="status_bar"
session={ &ctx.props().session }
renderer={ &ctx.props().renderer }
presentation={ &ctx.props().presentation }
on_reset={ ctx.link().callback(|all| Msg::Reset(all, None)) }>
</StatusBar>
<div id="main_panel_container">
<RenderWarning
dimensions={ self.dimensions }
session={ &ctx.props().session }
renderer={ &ctx.props().renderer }>
</RenderWarning>
<slot></slot>
</div>
<div
id="settings_button"
class="noselect button open"
onmousedown={ settings }>
</div>
</div>
</SplitPanel>
} else {
<RenderWarning
dimensions={ self.dimensions }
session={ &ctx.props().session }
renderer={ &ctx.props().renderer }>
</RenderWarning>
if ctx.props().presentation.get_title().is_some() {
<StatusBar
id="status_bar"
session={ &ctx.props().session }
renderer={ &ctx.props().renderer }
presentation={ &ctx.props().presentation }
on_reset={ ctx.link().callback(|all| Msg::Reset(all, None)) }>
</StatusBar>
}
<div id="main_panel_container" class={ class }>
<slot></slot>
</div>
<div
id="settings_button"
class={ if ctx.props().presentation.get_title().is_some() { "noselect button closed titled" } else { "noselect button closed" } }
onmousedown={ settings }>
</div>
}
</StyleProvider>
<FontLoader ..self.fonts.clone()></FontLoader>
}
}
fn destroy(&mut self, _ctx: &Context<Self>) {}
}
impl PerspectiveViewer {
fn init_toggle_settings_task(
&mut self,
ctx: &Context<Self>,
force: Option<bool>,
sender: Option<Sender<ApiResult<JsValue>>>,
) {
let is_open = ctx.props().presentation.is_settings_open();
match force {
Some(force) if is_open == force => {
if let Some(sender) = sender {
sender.send(Ok(JsValue::UNDEFINED)).unwrap();
}
}
Some(_) | None => {
let force = !is_open;
let callback = ctx.link().callback(move |resolve| {
let update = SettingsUpdate::Update(force);
Msg::ToggleSettingsComplete(update, resolve)
});
clone!(ctx.props().renderer, ctx.props().session);
ApiFuture::spawn(async move {
let result = if session.js_get_table().is_some() {
renderer.presize(force, callback.emit_async_safe()).await
} else {
callback.emit_async_safe().await?;
Ok(JsValue::UNDEFINED)
};
if let Some(sender) = sender {
let msg = result.clone().ignore_view_delete();
sender.send(msg).into_apierror()?;
};
result
});
}
};
}
}