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