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, SessionMetadata, SessionMetadataRc};
21use crate::tasks::{ExprValidation, validate_expression};
22use crate::*;
23
24#[derive(Properties, PartialEq, 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 metadata: SessionMetadataRc,
37
38 #[prop_or_default]
40 pub selected_theme: Option<String>,
41
42 pub session: Session,
44}
45
46#[derive(Debug)]
47pub enum ExpressionEditorMsg {
48 SetExpr(Rc<String>),
49 ValidateComplete(ExprValidation),
50}
51
52pub struct ExpressionEditor {
54 expr: Rc<String>,
55 error: Option<ExprValidationError>,
56 oninput: Callback<Rc<String>>,
57 validation_req_id: u64,
61 last_dispatched_req_id: u64,
64}
65
66impl Component for ExpressionEditor {
67 type Message = ExpressionEditorMsg;
68 type Properties = ExpressionEditorProps;
69
70 fn create(ctx: &Context<Self>) -> Self {
71 let oninput = ctx.link().callback(ExpressionEditorMsg::SetExpr);
72 let expr = initial_expr(&ctx.props().metadata, &ctx.props().alias);
73 ctx.link()
74 .send_message(Self::Message::SetExpr(expr.clone()));
75
76 Self {
77 error: None,
78 expr,
79 oninput,
80 validation_req_id: 0,
81 last_dispatched_req_id: 0,
82 }
83 }
84
85 fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
86 match msg {
87 ExpressionEditorMsg::SetExpr(val) => {
88 ctx.props().on_input.emit(val.clone());
89 self.expr = val.clone();
90 self.validation_req_id += 1;
91 self.last_dispatched_req_id = self.validation_req_id;
92 let cb = ctx.link().callback(ExpressionEditorMsg::ValidateComplete);
93 validate_expression(
94 &ctx.props().session,
95 cb,
96 self.validation_req_id,
97 (*val).clone(),
98 );
99 true
100 },
101 ExpressionEditorMsg::ValidateComplete(result) => {
102 if result.req_id != self.last_dispatched_req_id {
103 return false;
105 }
106 self.error = result.error;
107 if self.error.is_none() {
108 let _: Option<bool> = try {
109 let alias = ctx.props().alias.as_ref()?;
110 let session = &ctx.props().session;
111 let old = ctx.props().metadata.get_expression_by_alias(alias)?;
112 let is_edited = *self.expr != old;
113 session
114 .metadata_mut()
115 .set_edit_by_alias(alias, self.expr.to_string());
116
117 is_edited
118 };
119
120 ctx.props().on_validate.emit(true);
121 } else {
122 ctx.props().on_validate.emit(false);
123 }
124 true
125 },
126 }
127 }
128
129 fn view(&self, ctx: &Context<Self>) -> Html {
130 let disabled_class = ctx.props().disabled.then_some("disabled");
131 clone!(ctx.props().disabled);
132 html! {
133 <>
134 <LocalStyle href={css!("expression-editor")} />
135 <label class="item_title">{ "Expression" }</label>
136 <div id="editor-container" class={disabled_class}>
137 <CodeEditor
138 autofocus=true
139 expr={&self.expr}
140 autosuggest=true
141 error={self.error.clone().map(|x| x.into())}
142 {disabled}
143 oninput={self.oninput.clone()}
144 onsave={ctx.props().on_save.clone()}
145 theme={ctx.props().selected_theme.clone().unwrap_or_default()}
146 />
147 <div id="psp-expression-editor-meta">
148 <div class="error">
149 { &self.error.clone().map(|e| e.error_message).unwrap_or_default() }
150 </div>
151 </div>
152 </div>
153 </>
154 }
155 }
156
157 fn changed(&mut self, ctx: &Context<Self>, old_props: &Self::Properties) -> bool {
158 if ctx.props().alias != old_props.alias
159 || ctx.props().reset_count != old_props.reset_count
160 || (ctx.props().alias.is_some() && ctx.props().metadata != old_props.metadata)
161 {
162 ctx.link()
163 .send_message(ExpressionEditorMsg::SetExpr(initial_expr(
164 &ctx.props().metadata,
165 &ctx.props().alias,
166 )));
167 false
168 } else {
169 true
170 }
171 }
172}
173
174fn initial_expr(metadata: &SessionMetadata, alias: &Option<String>) -> Rc<String> {
175 alias
176 .as_ref()
177 .and_then(|alias| metadata.get_expression_by_alias(alias))
178 .unwrap_or_default()
179 .into()
180}