use std::rc::Rc;
use perspective_client::ExprValidationError;
use wasm_bindgen::prelude::*;
use yew::prelude::*;
use crate::components::containers::trap_door_panel::TrapDoorPanel;
use crate::components::form::code_editor::CodeEditor;
use crate::components::style::LocalStyle;
use crate::js::{copy_to_clipboard, paste_from_clipboard, MimeType};
use crate::model::*;
use crate::presentation::*;
use crate::renderer::*;
use crate::session::*;
use crate::utils::*;
use crate::*;
#[derive(Properties, Clone, PartialEq)]
pub struct DebugPanelProps {
pub session: Session,
pub renderer: Renderer,
pub presentation: Presentation,
}
derive_model!(Presentation, Renderer, Session for DebugPanelProps);
impl DebugPanelProps {
fn set_text(&self, setter: UseStateSetter<Rc<String>>) {
let props = self.clone();
ApiFuture::spawn(async move {
let task = props.get_viewer_config();
let config = task.await?;
let json = JsValue::from_serde_ext(&config)?;
let js_string =
js_sys::JSON::stringify_with_replacer_and_space(&json, &JsValue::NULL, &2.into())?;
setter.set(Rc::new(js_string.as_string().unwrap()));
Ok(())
});
}
fn reset_callback(
&self,
text: UseStateSetter<Rc<String>>,
error: UseStateSetter<Option<ExprValidationError>>,
modified: UseStateSetter<bool>,
) -> impl Fn(()) {
let props = self.clone();
move |_| {
error.set(None);
props.set_text(text.clone());
modified.set(false);
}
}
}
fn on_save(
props: &DebugPanelProps,
text: &Rc<String>,
error: &UseStateHandle<Option<ExprValidationError>>,
modified: &UseStateHandle<bool>,
) {
clone!(props, text, error, modified);
ApiFuture::spawn(async move {
match serde_json::from_str(&text) {
Ok(config) => {
match props.restore_and_render(config, async { Ok(()) }).await {
Ok(_) => {
modified.set(false);
},
Err(e) => {
modified.set(true);
error.set(Some(ExprValidationError {
error_message: JsValue::from(e)
.as_string()
.unwrap_or_else(|| "Failed to validate viewer config".to_owned()),
line: 0_u32,
column: 0,
}));
},
}
Ok(())
},
Err(err) => {
modified.set(true);
error.set(Some(ExprValidationError {
error_message: err.to_string(),
line: err.line() as u32 - 1,
column: err.column() as u32 - 1,
}));
Ok(())
},
}
});
}
#[function_component(DebugPanel)]
pub fn debug_panel(props: &DebugPanelProps) -> Html {
let expr = use_state_eq(|| Rc::new("".to_string()));
let error = use_state_eq(|| Option::<ExprValidationError>::None);
let select_all = use_memo((), |()| PubSub::default());
let modified = use_state_eq(|| false);
use_effect_with((expr.setter(), props.clone()), {
clone!(error, modified);
move |(text, props)| {
props.set_text(text.clone());
error.set(None);
let sub1 = props.renderer().style_changed.add_listener({
props.reset_callback(text.clone(), error.setter(), modified.setter())
});
let sub2 = props.renderer().reset_changed.add_listener({
props.reset_callback(text.clone(), error.setter(), modified.setter())
});
let sub3 = props.session().view_config_changed.add_listener({
props.reset_callback(text.clone(), error.setter(), modified.setter())
});
|| {
drop(sub1);
drop(sub2);
drop(sub3);
}
}
});
let oninput = use_callback(expr.setter(), {
clone!(modified);
move |x, expr| {
modified.set(true);
expr.set(x)
}
});
let onsave = use_callback((expr.clone(), error.clone(), props.clone()), {
clone!(modified);
move |_, (text, error, props)| on_save(props, text, error, &modified)
});
let oncopy = use_callback(
(expr.clone(), select_all.callback()),
move |_, (text, select_all)| {
select_all.emit(());
let mut options = web_sys::BlobPropertyBag::new();
options.type_("text/plain");
let blob_txt = (JsValue::from((***text).clone())).clone();
let blob_parts = js_sys::Array::from_iter([blob_txt].iter());
let blob = web_sys::Blob::new_with_str_sequence_and_options(&blob_parts, &options);
ApiFuture::spawn(copy_to_clipboard(
async move { Ok(blob?) },
MimeType::TextPlain,
));
},
);
let onapply = use_callback((expr.clone(), error.clone(), props.clone()), {
clone!(modified);
move |_, (text, error, props)| on_save(props, text, error, &modified)
});
let onreset = use_callback((expr.setter(), error.clone(), props.clone()), {
clone!(modified);
move |_, (text, error, props)| {
props.set_text(text.clone());
error.set(None);
modified.set(false);
}
});
let onpaste = use_callback((expr.clone(), error.clone(), props.clone()), {
clone!(modified);
move |_, (text, error, props)| {
clone!(text, error, props, modified);
ApiFuture::spawn(async move {
if let Some(x) = paste_from_clipboard().await {
let x = Rc::new(x);
modified.set(true);
error.set(None);
text.set(x.clone());
on_save(&props, &x, &error, &modified);
}
Ok(())
});
}
});
html! {
<>
<LocalStyle href={css!("containers/tabs")} />
<LocalStyle href={css!("form/debug")} />
<div id="debug-panel-overflow">
<TrapDoorPanel id="debug-panel" class="sidebar_column">
<div class="tab-gutter">
<span class="tab selected">
<div id="Debug" class="tab-title" />
<div class="tab-border" />
</span>
<span class="tab tab-padding">
<div class="tab-title" />
<div class="tab-border" />
</span>
</div>
<div id="debug-panel-editor">
<CodeEditor
expr={&*expr}
disabled=false
{oninput}
{onsave}
select_all={select_all.subscriber()}
error={(*error).clone()}
/>
</div>
<div id="debug-panel-controls">
<button disabled={!*modified} onclick={onapply}>{ "Apply" }</button>
<button disabled={!*modified} onclick={onreset}>{ "Reset" }</button>
<button onclick={oncopy}>{ "Copy" }</button>
<button onclick={onpaste}>{ "Paste" }</button>
</div>
</TrapDoorPanel>
</div>
</>
}
}