1use crate::{
2 EditBool, EditBoolMessage, EditExtended, EditExtendedMessage, EditIcon, EditIconMessage,
3 EditNumber, EditNumberMessage, EditRequirement, EditRequirementMessage, EditString,
4 EditStringArray, EditStringArrayMessage, EditStringMessage, IconEvent, SharedWorkflow,
5};
6use css_in_rust_next::Style;
7use mcai_models::{ParameterType, Workflow};
8use serde_json::{Map, Value};
9use std::ops::DerefMut;
10use yew::{html, Callback, Component, Context, Html, Properties};
11use yew_feather::{check_square::CheckSquare, edit::Edit, slash::Slash, square::Square};
12
13pub enum McaiFieldMessage {
14 ModalDisplay,
15 EditArrayString(EditStringArrayMessage),
16 EditBool(EditBoolMessage),
17 EditExtended(EditExtendedMessage),
18 EditNumber(EditNumberMessage),
19 EditRequirement(EditRequirementMessage),
20 EditString(EditStringMessage),
21 IconListEvent(EditIconMessage),
22}
23
24#[derive(PartialEq, Properties)]
25pub struct McaiFieldProperties {
26 pub kind: ParameterType,
27 pub step_id: Option<u32>,
28 pub field_name: String,
29 #[prop_or_default]
30 pub is_icon: bool,
31 pub workflow: SharedWorkflow,
32 pub event: Callback<()>,
33}
34
35pub struct McaiField {
36 modal_display: bool,
37 is_editable: bool,
38 style: Style,
39}
40
41impl Component for McaiField {
42 type Message = McaiFieldMessage;
43 type Properties = McaiFieldProperties;
44
45 fn create(ctx: &Context<Self>) -> Self {
46 let is_editable = ctx.props().workflow.lock().unwrap().is_definition();
47 let style = Style::create("Component", include_str!("mcai_field.css")).unwrap();
48
49 McaiField {
50 modal_display: false,
51 is_editable,
52 style,
53 }
54 }
55
56 fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
57 match msg {
58 McaiFieldMessage::ModalDisplay => {
59 self.modal_display = true;
60 true
61 }
62 McaiFieldMessage::EditString(message) => {
63 if let EditStringMessage::Submit(value) = message {
64 if let Workflow::Definition(definition) = ctx.props().workflow.lock().unwrap().deref_mut()
65 {
66 if let Some(step_id) = ctx.props().step_id {
67 let is_parameter = ctx.props().field_name != "name";
68 definition.set_parameter_on_step(
69 step_id,
70 &ctx.props().field_name,
71 ParameterType::String {
72 value,
73 default: None,
74 required: true,
75 },
76 is_parameter,
77 );
78 } else {
79 definition.set_workflow_property(
80 &ctx.props().field_name,
81 ParameterType::String {
82 value,
83 default: None,
84 required: true,
85 },
86 );
87 }
88 }
89 }
90
91 ctx.props().event.emit(());
92 self.modal_display = false;
93 true
94 }
95 McaiFieldMessage::EditExtended(message) => {
96 if let EditExtendedMessage::Submit(value) = message {
97 if let Workflow::Definition(definition) = ctx.props().workflow.lock().unwrap().deref_mut()
98 {
99 if let Some(step_id) = ctx.props().step_id {
100 let is_parameter = ctx.props().field_name != "name";
101 definition.set_parameter_on_step(
102 step_id,
103 &ctx.props().field_name,
104 ParameterType::Extended {
105 value,
106 default: Value::Object(Map::default()),
107 required: true,
108 },
109 is_parameter,
110 );
111 } else {
112 definition.set_workflow_property(
113 &ctx.props().field_name,
114 ParameterType::Extended {
115 value,
116 default: Value::Object(Map::default()),
117 required: true,
118 },
119 );
120 }
121 }
122 }
123
124 ctx.props().event.emit(());
125 self.modal_display = false;
126 true
127 }
128 McaiFieldMessage::IconListEvent(message) => {
129 if let EditIconMessage::Selected(value) = message {
130 if let Workflow::Definition(definition) = ctx.props().workflow.lock().unwrap().deref_mut()
131 {
132 let IconEvent::SelectedIcon(icon_name) = value;
133 if let Some(step_id) = ctx.props().step_id {
134 definition.set_parameter_on_step(
135 step_id,
136 &ctx.props().field_name,
137 ParameterType::String {
138 value: Some(icon_name),
139 default: None,
140 required: true,
141 },
142 false,
143 );
144 } else {
145 definition.set_workflow_property(
146 &ctx.props().field_name,
147 ParameterType::String {
148 value: Some(icon_name),
149 default: None,
150 required: true,
151 },
152 );
153 }
154 }
155
156 ctx.props().event.emit(());
157 }
158 self.modal_display = false;
159 true
160 }
161 McaiFieldMessage::EditArrayString(message) => {
162 if let EditStringArrayMessage::Submit(value) = message {
163 if let Workflow::Definition(definition) = ctx.props().workflow.lock().unwrap().deref_mut()
164 {
165 if let Some(step_id) = ctx.props().step_id {
166 let is_parameter = ctx.props().field_name != "name";
167 definition.set_parameter_on_step(
168 step_id,
169 &ctx.props().field_name,
170 ParameterType::ArrayOfStrings {
171 value,
172 default: vec![],
173 required: true,
174 },
175 is_parameter,
176 );
177 } else {
178 definition.set_workflow_property(
179 &ctx.props().field_name,
180 ParameterType::ArrayOfStrings {
181 value,
182 default: vec![],
183 required: true,
184 },
185 );
186 }
187 }
188 }
189
190 ctx.props().event.emit(());
191 self.modal_display = false;
192 true
193 }
194 McaiFieldMessage::EditNumber(message) => {
195 if let EditNumberMessage::Submit(value) = message {
196 let new_value = match ctx.props().kind {
197 ParameterType::Number { .. } => ParameterType::Number {
198 default: None,
199 value,
200 required: true,
201 },
202 ParameterType::Integer { .. } => ParameterType::Integer {
203 default: None,
204 value: value.map(|v| v as u32),
205 required: true,
206 },
207 _ => unreachable!(),
208 };
209
210 if let Workflow::Definition(definition) = ctx.props().workflow.lock().unwrap().deref_mut()
211 {
212 if let Some(step_id) = ctx.props().step_id {
213 let is_parameter = ctx.props().field_name != "name";
214 definition.set_parameter_on_step(
215 step_id,
216 &ctx.props().field_name,
217 new_value,
218 is_parameter,
219 );
220 } else {
221 definition.set_workflow_property(&ctx.props().field_name, new_value);
222 }
223 }
224 }
225
226 ctx.props().event.emit(());
227 self.modal_display = false;
228 true
229 }
230 McaiFieldMessage::EditBool(message) => {
231 if let EditBoolMessage::Submit(value) = message {
232 if let Workflow::Definition(definition) = ctx.props().workflow.lock().unwrap().deref_mut()
233 {
234 if let Some(step_id) = ctx.props().step_id {
235 let is_parameter = ctx.props().field_name != "name";
236 definition.set_parameter_on_step(
237 step_id,
238 &ctx.props().field_name,
239 ParameterType::Boolean {
240 default: None,
241 value,
242 required: true,
243 },
244 is_parameter,
245 );
246 } else {
247 definition.set_workflow_property(
248 &ctx.props().field_name,
249 ParameterType::Boolean {
250 default: None,
251 value,
252 required: true,
253 },
254 );
255 }
256 }
257 }
258
259 ctx.props().event.emit(());
260 self.modal_display = false;
261 true
262 }
263 McaiFieldMessage::EditRequirement(message) => {
264 if let EditRequirementMessage::Submit(value) = message {
265 if let Workflow::Definition(definition) = ctx.props().workflow.lock().unwrap().deref_mut()
266 {
267 if let Some(step_id) = ctx.props().step_id {
268 definition.set_parameter_on_step(
269 step_id,
270 &ctx.props().field_name,
271 ParameterType::Requirements {
272 default: None,
273 value,
274 required: true,
275 },
276 true,
277 );
278 }
279 }
280 }
281 ctx.props().event.emit(());
282 self.modal_display = false;
283 true
284 }
285 }
286 }
287
288 fn view(&self, ctx: &Context<Self>) -> Html {
289 let modal = self
290 .modal_display
291 .then(|| {
292 let title = format!("Edit {}", ctx.props().field_name);
293
294 match &ctx.props().kind {
295 ParameterType::ArrayOfStrings { value, .. }
296 | ParameterType::ArrayOfTemplates { value, .. } => html!(
297 <EditStringArray
298 field_name={ctx.props().field_name.clone()}
299 event={ctx.link().callback(McaiFieldMessage::EditArrayString)}
300 title={title}
301 value={value.clone()}
302 />
303 ),
304 ParameterType::Boolean {
305 value, required, ..
306 } => html!(
307 <EditBool
308 field_name={ctx.props().field_name.clone()}
309 event={ctx.link().callback(McaiFieldMessage::EditBool)}
310 title={title}
311 value={*value}
312 required={*required}
313 />
314 ),
315 ParameterType::Integer {
316 value, required, ..
317 } => html!(
318 <EditNumber
319 field_name={ctx.props().field_name.clone()}
320 event={ctx.link().callback(McaiFieldMessage::EditNumber)}
321 title={title}
322 value={value.map(|v| v as f64)}
323 number_step={1.0}
324 required={*required}
325 />
326 ),
327 ParameterType::Number {
328 value, required, ..
329 } => html!(
330 <EditNumber
331 field_name={ctx.props().field_name.clone()}
332 event={ctx.link().callback(McaiFieldMessage::EditNumber)}
333 title={title}
334 value={*value}
335 number_step={0.00000000001}
336 required={*required}
337 />
338 ),
339 ParameterType::Requirements { value, .. } => html!(
340 <EditRequirement
341 field_name={ctx.props().field_name.clone()}
342 event={ctx.link().callback(McaiFieldMessage::EditRequirement)}
343 title={title}
344 requirement={value.clone()}
345 />
346 ),
347 ParameterType::String {
348 value, required, ..
349 }
350 | ParameterType::Template {
351 value, required, ..
352 } => {
353 if ctx.props().is_icon {
354 html!(
355 <EditIcon
356 event={ctx.link().callback(McaiFieldMessage::IconListEvent)}
357 title={title}
358 />
359 )
360 } else {
361 html!(
362 <EditString
363 event={ctx.link().callback(McaiFieldMessage::EditString)}
364 field_name={ctx.props().field_name.clone()}
365 title={title}
366 value={value.clone()}
367 required={*required}
368 />
369 )
370 }
371 }
372 ParameterType::Extended {
373 value, required, ..
374 } => {
375 html!(
376 <EditExtended
377 event={ctx.link().callback(McaiFieldMessage::EditExtended)}
378 field_name={ctx.props().field_name.clone()}
379 title={title}
380 value={value.clone()}
381 required={*required}
382 />
383 )
384 }
385 _ => html!(),
386 }
387 })
388 .unwrap_or_default();
389
390 let display_value = {
391 match &ctx.props().kind {
392 ParameterType::ArrayOfStrings { value, .. }
393 | ParameterType::ArrayOfTemplates { value, .. } => value
394 .iter()
395 .map(|value| {
396 html!(
397 <div>
398 {value}
399 </div>
400 )
401 })
402 .collect::<Html>(),
403 ParameterType::Boolean {
404 value, required, ..
405 } => {
406 let inner = match if *required {
407 Some(value.unwrap_or_default())
408 } else {
409 value.map(|value| value)
410 } {
411 Some(true) => html!(<CheckSquare />),
412 Some(false) => html!(<Square />),
413 None => html!(<Slash color={"#888"} />),
414 };
415
416 html!(<div>{inner}</div>)
417 }
418 ParameterType::Integer {
419 value, required, ..
420 } => value
421 .map(|value| html!(value.to_string()))
422 .unwrap_or_else(|| {
423 if *required {
424 html!(0)
425 } else {
426 html!(<Slash color={"#888"} />)
427 }
428 }),
429 ParameterType::Number {
430 value, required, ..
431 } => value
432 .map(|value| html!(value.to_string()))
433 .unwrap_or_else(|| {
434 if *required {
435 html!(0f64)
436 } else {
437 html!(<Slash color={"#888"} />)
438 }
439 }),
440 ParameterType::Requirements {
441 value, required, ..
442 } => value
443 .as_ref()
444 .map(|value| html!(<>{value.paths.len()}{" path(s)"}</>))
445 .unwrap_or_else(|| {
446 if *required {
447 html!("")
448 } else {
449 html!(<Slash color={"#888"} />)
450 }
451 }),
452 ParameterType::String {
453 value, required, ..
454 }
455 | ParameterType::Template {
456 value, required, ..
457 } => {
458 if ctx.props().is_icon {
459 html!(<i class="material-icons">{value.clone().unwrap_or_default()}</i>)
460 } else {
461 value.as_ref().map(|value| html!(value)).unwrap_or_else(|| {
462 (!required)
463 .then(|| html!(<Slash color={"#888"} />))
464 .unwrap_or_default()
465 })
466 }
467 }
468 ParameterType::Extended {
469 value, required, ..
470 } => {
471 if matches!(value, Value::Null) {
472 if *required {
473 html!("")
474 } else {
475 html!(<Slash color={"#888"} />)
476 }
477 } else {
478 html!(value)
479 }
480 }
481 _ => html!(),
482 }
483 };
484
485 if self.is_editable {
486 html!(
487 <span class={self.style.clone()} onclick={ctx.link().callback(|_|McaiFieldMessage::ModalDisplay)}>
488 <span class="value">
489 {display_value}
490 </span>
491 <Edit class="fieldIcon" />
492 {modal}
493 </span>
494 )
495 } else {
496 html!(
497 <span class={self.style.clone()}>
498 <span class="value">
499 {display_value}
500 </span>
501 </span>
502 )
503 }
504 }
505}