perspective_viewer/components/
expression_editor.rs1use std::rc::Rc;
14
15use perspective_client::{ExprValidationError, clone};
16use yew::prelude::*;
17
18use super::form::code_editor::*;
19use super::style::LocalStyle;
20use crate::session::Session;
21use crate::*;
22
23#[derive(Debug)]
24pub enum ExpressionEditorMsg {
25 SetExpr(Rc<String>),
26 ValidateComplete(Option<ExprValidationError>),
27}
28
29#[derive(Properties, PartialEq, Clone)]
30pub struct ExpressionEditorProps {
31 pub session: Session,
32 pub on_save: Callback<()>,
33 pub on_validate: Callback<bool>,
34 pub on_input: Callback<Rc<String>>,
35 pub alias: Option<String>,
36 pub disabled: bool,
37 #[prop_or_default]
38 pub reset_count: u8,
39}
40
41impl ExpressionEditorProps {
42 fn initial_expr(&self) -> Rc<String> {
43 self.alias
44 .as_ref()
45 .and_then(|alias| self.session.metadata().get_expression_by_alias(alias))
46 .unwrap_or_default()
47 .into()
48 }
49}
50
51pub struct ExpressionEditor {
53 expr: Rc<String>,
54 error: Option<ExprValidationError>,
55 oninput: Callback<Rc<String>>,
56}
57
58impl Component for ExpressionEditor {
59 type Message = ExpressionEditorMsg;
60 type Properties = ExpressionEditorProps;
61
62 fn create(ctx: &Context<Self>) -> Self {
63 let oninput = ctx.link().callback(ExpressionEditorMsg::SetExpr);
64 let expr = ctx.props().initial_expr();
65 ctx.link()
66 .send_message(Self::Message::SetExpr(expr.clone()));
67
68 Self {
69 error: None,
70 expr,
71 oninput,
72 }
73 }
74
75 fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
76 match msg {
77 ExpressionEditorMsg::SetExpr(val) => {
78 ctx.props().on_input.emit(val.clone());
79 self.expr = val.clone();
80 clone!(ctx.props().session);
81 ctx.link().send_future(async move {
82 match session.validate_expr(&val).await {
83 Ok(x) => ExpressionEditorMsg::ValidateComplete(x),
84 Err(err) => {
85 web_sys::console::error_1(&format!("{:?}", err).into());
86 ExpressionEditorMsg::ValidateComplete(None)
87 },
88 }
89 });
90
91 true
92 },
93 ExpressionEditorMsg::ValidateComplete(err) => {
94 self.error = err;
95 if self.error.is_none() {
96 maybe!({
97 let alias = ctx.props().alias.as_ref()?;
98 let session = &ctx.props().session;
99 let old = session.metadata().get_expression_by_alias(alias)?;
100 let is_edited = *self.expr != old;
101 session
102 .metadata_mut()
103 .set_edit_by_alias(alias, self.expr.to_string());
104 Some(is_edited)
105 });
106
107 ctx.props().on_validate.emit(true);
108 } else {
109 ctx.props().on_validate.emit(false);
110 }
111 true
112 },
113 }
114 }
115
116 fn view(&self, ctx: &Context<Self>) -> Html {
117 let disabled_class = ctx.props().disabled.then_some("disabled");
118 clone!(ctx.props().disabled);
119 html! {
120 <>
121 <LocalStyle href={css!("expression-editor")} />
122 <label class="item_title">{ "Expression" }</label>
123 <div id="editor-container" class={disabled_class}>
124 <CodeEditor
125 autofocus=true
126 expr={&self.expr}
127 autosuggest=true
128 error={self.error.clone().map(|x| x.into())}
129 {disabled}
130 oninput={self.oninput.clone()}
131 onsave={ctx.props().on_save.clone()}
132 />
133 <div id="psp-expression-editor-meta">
134 <div class="error">
135 { &self.error.clone().map(|e| e.error_message).unwrap_or_default() }
136 </div>
137 </div>
138 </div>
139 </>
140 }
141 }
142
143 fn changed(&mut self, ctx: &Context<Self>, old_props: &Self::Properties) -> bool {
144 if ctx.props().alias != old_props.alias || ctx.props().reset_count != old_props.reset_count
145 {
146 ctx.link()
147 .send_message(ExpressionEditorMsg::SetExpr(ctx.props().initial_expr()));
148 false
149 } else {
150 true
151 }
152 }
153}