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