@nyaruka/temba-components 0.129.3 → 0.129.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.js +1 -0
- package/.github/workflows/build.yml +135 -3
- package/CHANGELOG.md +19 -0
- package/demo/data/flows/sample-flow.json +110 -87
- package/demo/field-config-demo.html +135 -0
- package/dist/temba-components.js +1257 -675
- package/dist/temba-components.js.map +1 -1
- package/docs/ActionEditor-Migration.md +118 -0
- package/out-tsc/src/events.js.map +1 -1
- package/out-tsc/src/flow/{EditorNode.js → CanvasNode.js} +345 -42
- package/out-tsc/src/flow/CanvasNode.js.map +1 -0
- package/out-tsc/src/flow/Editor.js +107 -3
- package/out-tsc/src/flow/Editor.js.map +1 -1
- package/out-tsc/src/flow/NodeEditor.js +1211 -0
- package/out-tsc/src/flow/NodeEditor.js.map +1 -0
- package/out-tsc/src/flow/Plumber.js +0 -6
- package/out-tsc/src/flow/Plumber.js.map +1 -1
- package/out-tsc/src/flow/actions/add_contact_groups.js +40 -0
- package/out-tsc/src/flow/actions/add_contact_groups.js.map +1 -0
- package/out-tsc/src/flow/actions/add_contact_urn.js +16 -0
- package/out-tsc/src/flow/actions/add_contact_urn.js.map +1 -0
- package/out-tsc/src/flow/actions/add_input_labels.js +11 -0
- package/out-tsc/src/flow/actions/add_input_labels.js.map +1 -0
- package/out-tsc/src/flow/actions/call_classifier.js +11 -0
- package/out-tsc/src/flow/actions/call_classifier.js.map +1 -0
- package/out-tsc/src/flow/actions/call_llm.js +11 -0
- package/out-tsc/src/flow/actions/call_llm.js.map +1 -0
- package/out-tsc/src/flow/actions/call_resthook.js +11 -0
- package/out-tsc/src/flow/actions/call_resthook.js.map +1 -0
- package/out-tsc/src/flow/actions/call_webhook.js +122 -0
- package/out-tsc/src/flow/actions/call_webhook.js.map +1 -0
- package/out-tsc/src/flow/actions/enter_flow.js +14 -0
- package/out-tsc/src/flow/actions/enter_flow.js.map +1 -0
- package/out-tsc/src/flow/actions/open_ticket.js +11 -0
- package/out-tsc/src/flow/actions/open_ticket.js.map +1 -0
- package/out-tsc/src/flow/actions/play_audio.js +11 -0
- package/out-tsc/src/flow/actions/play_audio.js.map +1 -0
- package/out-tsc/src/flow/actions/remove_contact_groups.js +62 -0
- package/out-tsc/src/flow/actions/remove_contact_groups.js.map +1 -0
- package/out-tsc/src/flow/actions/request_optin.js +11 -0
- package/out-tsc/src/flow/actions/request_optin.js.map +1 -0
- package/out-tsc/src/flow/actions/say_msg.js +11 -0
- package/out-tsc/src/flow/actions/say_msg.js.map +1 -0
- package/out-tsc/src/flow/actions/send_broadcast.js +33 -0
- package/out-tsc/src/flow/actions/send_broadcast.js.map +1 -0
- package/out-tsc/src/flow/actions/send_email.js +56 -0
- package/out-tsc/src/flow/actions/send_email.js.map +1 -0
- package/out-tsc/src/flow/actions/send_msg.js +55 -0
- package/out-tsc/src/flow/actions/send_msg.js.map +1 -0
- package/out-tsc/src/flow/actions/set_contact_channel.js +12 -0
- package/out-tsc/src/flow/actions/set_contact_channel.js.map +1 -0
- package/out-tsc/src/flow/actions/set_contact_field.js +12 -0
- package/out-tsc/src/flow/actions/set_contact_field.js.map +1 -0
- package/out-tsc/src/flow/actions/set_contact_language.js +10 -0
- package/out-tsc/src/flow/actions/set_contact_language.js.map +1 -0
- package/out-tsc/src/flow/actions/set_contact_name.js +10 -0
- package/out-tsc/src/flow/actions/set_contact_name.js.map +1 -0
- package/out-tsc/src/flow/actions/set_contact_status.js +10 -0
- package/out-tsc/src/flow/actions/set_contact_status.js.map +1 -0
- package/out-tsc/src/flow/actions/set_run_result.js +10 -0
- package/out-tsc/src/flow/actions/set_run_result.js.map +1 -0
- package/out-tsc/src/flow/actions/split_by_expression_example.js +77 -0
- package/out-tsc/src/flow/actions/split_by_expression_example.js.map +1 -0
- package/out-tsc/src/flow/actions/start_session.js +11 -0
- package/out-tsc/src/flow/actions/start_session.js.map +1 -0
- package/out-tsc/src/flow/actions/transfer_airtime.js +11 -0
- package/out-tsc/src/flow/actions/transfer_airtime.js.map +1 -0
- package/out-tsc/src/flow/config.js +88 -193
- package/out-tsc/src/flow/config.js.map +1 -1
- package/out-tsc/src/flow/nodes/execute_actions.js +4 -0
- package/out-tsc/src/flow/nodes/execute_actions.js.map +1 -0
- package/out-tsc/src/flow/nodes/split_by_airtime.js +9 -0
- package/out-tsc/src/flow/nodes/split_by_airtime.js.map +1 -0
- package/out-tsc/src/flow/nodes/split_by_contact_field.js +7 -0
- package/out-tsc/src/flow/nodes/split_by_contact_field.js.map +1 -0
- package/out-tsc/src/flow/nodes/split_by_expression.js +7 -0
- package/out-tsc/src/flow/nodes/split_by_expression.js.map +1 -0
- package/out-tsc/src/flow/nodes/split_by_groups.js +7 -0
- package/out-tsc/src/flow/nodes/split_by_groups.js.map +1 -0
- package/out-tsc/src/flow/nodes/split_by_random.js +10 -0
- package/out-tsc/src/flow/nodes/split_by_random.js.map +1 -0
- package/out-tsc/src/flow/nodes/split_by_run_result.js +7 -0
- package/out-tsc/src/flow/nodes/split_by_run_result.js.map +1 -0
- package/out-tsc/src/flow/nodes/split_by_scheme.js +7 -0
- package/out-tsc/src/flow/nodes/split_by_scheme.js.map +1 -0
- package/out-tsc/src/flow/nodes/split_by_subflow.js +9 -0
- package/out-tsc/src/flow/nodes/split_by_subflow.js.map +1 -0
- package/out-tsc/src/flow/nodes/split_by_webhook.js +18 -0
- package/out-tsc/src/flow/nodes/split_by_webhook.js.map +1 -0
- package/out-tsc/src/flow/nodes/wait_for_audio.js +7 -0
- package/out-tsc/src/flow/nodes/wait_for_audio.js.map +1 -0
- package/out-tsc/src/flow/nodes/wait_for_digits.js +7 -0
- package/out-tsc/src/flow/nodes/wait_for_digits.js.map +1 -0
- package/out-tsc/src/flow/nodes/wait_for_image.js +7 -0
- package/out-tsc/src/flow/nodes/wait_for_image.js.map +1 -0
- package/out-tsc/src/flow/nodes/wait_for_location.js +7 -0
- package/out-tsc/src/flow/nodes/wait_for_location.js.map +1 -0
- package/out-tsc/src/flow/nodes/wait_for_menu.js +7 -0
- package/out-tsc/src/flow/nodes/wait_for_menu.js.map +1 -0
- package/out-tsc/src/flow/nodes/wait_for_response.js +7 -0
- package/out-tsc/src/flow/nodes/wait_for_response.js.map +1 -0
- package/out-tsc/src/flow/nodes/wait_for_video.js +7 -0
- package/out-tsc/src/flow/nodes/wait_for_video.js.map +1 -0
- package/out-tsc/src/flow/types.js +79 -0
- package/out-tsc/src/flow/types.js.map +1 -0
- package/out-tsc/src/flow/utils.js +65 -0
- package/out-tsc/src/flow/utils.js.map +1 -0
- package/out-tsc/src/form/ArrayEditor.js +199 -0
- package/out-tsc/src/form/ArrayEditor.js.map +1 -0
- package/out-tsc/src/form/BaseListEditor.js +128 -0
- package/out-tsc/src/form/BaseListEditor.js.map +1 -0
- package/out-tsc/src/form/Checkbox.js +17 -2
- package/out-tsc/src/form/Checkbox.js.map +1 -1
- package/out-tsc/src/form/Completion.js +6 -0
- package/out-tsc/src/form/Completion.js.map +1 -1
- package/out-tsc/src/form/FormField.js +110 -11
- package/out-tsc/src/form/FormField.js.map +1 -1
- package/out-tsc/src/form/KeyValueEditor.js +223 -0
- package/out-tsc/src/form/KeyValueEditor.js.map +1 -0
- package/out-tsc/src/form/select/Select.js +92 -32
- package/out-tsc/src/form/select/Select.js.map +1 -1
- package/out-tsc/src/interfaces.js +6 -0
- package/out-tsc/src/interfaces.js.map +1 -1
- package/out-tsc/src/live/ContactChat.js +2 -76
- package/out-tsc/src/live/ContactChat.js.map +1 -1
- package/out-tsc/temba-modules.js +9 -2
- package/out-tsc/temba-modules.js.map +1 -1
- package/out-tsc/test/ActionHelper.js +116 -0
- package/out-tsc/test/ActionHelper.js.map +1 -0
- package/out-tsc/test/actions/add_contact_groups.test.js +66 -0
- package/out-tsc/test/actions/add_contact_groups.test.js.map +1 -0
- package/out-tsc/test/actions/remove_contact_groups.test.js +226 -0
- package/out-tsc/test/actions/remove_contact_groups.test.js.map +1 -0
- package/out-tsc/test/actions/send_email.test.js +160 -0
- package/out-tsc/test/actions/send_email.test.js.map +1 -0
- package/out-tsc/test/actions/send_msg.test.js +95 -0
- package/out-tsc/test/actions/send_msg.test.js.map +1 -0
- package/out-tsc/test/temba-action-editing-integration.test.js +183 -0
- package/out-tsc/test/temba-action-editing-integration.test.js.map +1 -0
- package/out-tsc/test/temba-checkbox.test.js +1 -1
- package/out-tsc/test/temba-checkbox.test.js.map +1 -1
- package/out-tsc/test/temba-field-config.test.js +133 -0
- package/out-tsc/test/temba-field-config.test.js.map +1 -0
- package/out-tsc/test/temba-flow-editor-node.test.js +14 -14
- package/out-tsc/test/temba-flow-editor-node.test.js.map +1 -1
- package/out-tsc/test/temba-node-editor.test.js +283 -0
- package/out-tsc/test/temba-node-editor.test.js.map +1 -0
- package/out-tsc/test/temba-select.test.js +158 -0
- package/out-tsc/test/temba-select.test.js.map +1 -1
- package/package.json +1 -1
- package/screenshots/truth/actions/add_contact_groups/editor/descriptive-group-names.png +0 -0
- package/screenshots/truth/actions/add_contact_groups/editor/long-group-names.png +0 -0
- package/screenshots/truth/actions/add_contact_groups/editor/many-groups.png +0 -0
- package/screenshots/truth/actions/add_contact_groups/editor/multiple-groups.png +0 -0
- package/screenshots/truth/actions/add_contact_groups/editor/single-group.png +0 -0
- package/screenshots/truth/actions/add_contact_groups/render/descriptive-group-names.png +0 -0
- package/screenshots/truth/actions/add_contact_groups/render/long-group-names.png +0 -0
- package/screenshots/truth/actions/add_contact_groups/render/many-groups.png +0 -0
- package/screenshots/truth/actions/add_contact_groups/render/multiple-groups.png +0 -0
- package/screenshots/truth/actions/add_contact_groups/render/single-group.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/editor/cleanup-groups.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/editor/long-descriptive-group-names.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/editor/many-groups.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/editor/multiple-groups.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/editor/remove-from-all-groups.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/editor/single-group.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/render/cleanup-groups.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/render/long-descriptive-group-names.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/render/many-groups.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/render/multiple-groups.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/render/remove-from-all-groups.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/render/single-group.png +0 -0
- package/screenshots/truth/actions/send_email/editor/complex-business-email.png +0 -0
- package/screenshots/truth/actions/send_email/editor/empty-body.png +0 -0
- package/screenshots/truth/actions/send_email/editor/empty-subject.png +0 -0
- package/screenshots/truth/actions/send_email/editor/long-subject.png +0 -0
- package/screenshots/truth/actions/send_email/editor/multiline-body.png +0 -0
- package/screenshots/truth/actions/send_email/editor/multiple-recipients.png +0 -0
- package/screenshots/truth/actions/send_email/editor/simple-email.png +0 -0
- package/screenshots/truth/actions/send_email/editor/with-expressions.png +0 -0
- package/screenshots/truth/actions/send_email/render/complex-business-email.png +0 -0
- package/screenshots/truth/actions/send_email/render/empty-body.png +0 -0
- package/screenshots/truth/actions/send_email/render/empty-subject.png +0 -0
- package/screenshots/truth/actions/send_email/render/long-subject.png +0 -0
- package/screenshots/truth/actions/send_email/render/multiline-body.png +0 -0
- package/screenshots/truth/actions/send_email/render/multiple-recipients.png +0 -0
- package/screenshots/truth/actions/send_email/render/simple-email.png +0 -0
- package/screenshots/truth/actions/send_email/render/with-expressions.png +0 -0
- package/screenshots/truth/actions/send_msg/editor/long-quick-replies.png +0 -0
- package/screenshots/truth/actions/send_msg/editor/multiline-text-with-replies.png +0 -0
- package/screenshots/truth/actions/send_msg/editor/simple-text.png +0 -0
- package/screenshots/truth/actions/send_msg/editor/text-with-linebreaks.png +0 -0
- package/screenshots/truth/actions/send_msg/editor/text-with-many-quick-replies.png +0 -0
- package/screenshots/truth/actions/send_msg/editor/text-with-quick-replies.png +0 -0
- package/screenshots/truth/actions/send_msg/editor/text-without-quick-replies.png +0 -0
- package/screenshots/truth/actions/send_msg/render/long-quick-replies.png +0 -0
- package/screenshots/truth/actions/send_msg/render/multiline-text-with-replies.png +0 -0
- package/screenshots/truth/actions/send_msg/render/simple-text.png +0 -0
- package/screenshots/truth/actions/send_msg/render/text-with-linebreaks.png +0 -0
- package/screenshots/truth/actions/send_msg/render/text-with-many-quick-replies.png +0 -0
- package/screenshots/truth/actions/send_msg/render/text-with-quick-replies.png +0 -0
- package/screenshots/truth/actions/send_msg/render/text-without-quick-replies.png +0 -0
- package/screenshots/truth/editor/router.png +0 -0
- package/screenshots/truth/editor/send_msg.png +0 -0
- package/screenshots/truth/editor/set_contact_language.png +0 -0
- package/screenshots/truth/editor/set_contact_name.png +0 -0
- package/screenshots/truth/editor/set_run_result.png +0 -0
- package/screenshots/truth/editor/wait.png +0 -0
- package/screenshots/truth/formfield/markdown-errors.png +0 -0
- package/screenshots/truth/formfield/plain-text-errors.png +0 -0
- package/screenshots/truth/formfield/widget-only-markdown-errors.png +0 -0
- package/screenshots/truth/integration/checkbox-markdown-errors.png +0 -0
- package/src/events.ts +1 -40
- package/src/flow/{EditorNode.ts → CanvasNode.ts} +424 -48
- package/src/flow/Editor.ts +140 -4
- package/src/flow/NodeEditor.ts +1454 -0
- package/src/flow/Plumber.ts +0 -9
- package/src/flow/actions/add_contact_groups.ts +42 -0
- package/src/flow/actions/add_contact_urn.ts +17 -0
- package/src/flow/actions/add_input_labels.ts +12 -0
- package/src/flow/actions/call_classifier.ts +12 -0
- package/src/flow/actions/call_llm.ts +12 -0
- package/src/flow/actions/call_resthook.ts +12 -0
- package/src/flow/actions/call_webhook.ts +133 -0
- package/src/flow/actions/enter_flow.ts +15 -0
- package/src/flow/actions/open_ticket.ts +12 -0
- package/src/flow/actions/play_audio.ts +12 -0
- package/src/flow/actions/remove_contact_groups.ts +66 -0
- package/src/flow/actions/request_optin.ts +12 -0
- package/src/flow/actions/say_msg.ts +12 -0
- package/src/flow/actions/send_broadcast.ts +35 -0
- package/src/flow/actions/send_email.ts +60 -0
- package/src/flow/actions/send_msg.ts +58 -0
- package/src/flow/actions/set_contact_channel.ts +13 -0
- package/src/flow/actions/set_contact_field.ts +13 -0
- package/src/flow/actions/set_contact_language.ts +11 -0
- package/src/flow/actions/set_contact_name.ts +11 -0
- package/src/flow/actions/set_contact_status.ts +11 -0
- package/src/flow/actions/set_run_result.ts +11 -0
- package/src/flow/actions/split_by_expression_example.ts +88 -0
- package/src/flow/actions/start_session.ts +12 -0
- package/src/flow/actions/transfer_airtime.ts +12 -0
- package/src/flow/config.ts +93 -232
- package/src/flow/nodes/execute_actions.ts +5 -0
- package/src/flow/nodes/split_by_airtime.ts +9 -0
- package/src/flow/nodes/split_by_contact_field.ts +7 -0
- package/src/flow/nodes/split_by_expression.ts +7 -0
- package/src/flow/nodes/split_by_groups.ts +7 -0
- package/src/flow/nodes/split_by_random.ts +10 -0
- package/src/flow/nodes/split_by_run_result.ts +7 -0
- package/src/flow/nodes/split_by_scheme.ts +7 -0
- package/src/flow/nodes/split_by_subflow.ts +9 -0
- package/src/flow/nodes/split_by_webhook.ts +19 -0
- package/src/flow/nodes/wait_for_audio.ts +7 -0
- package/src/flow/nodes/wait_for_digits.ts +7 -0
- package/src/flow/nodes/wait_for_image.ts +7 -0
- package/src/flow/nodes/wait_for_location.ts +7 -0
- package/src/flow/nodes/wait_for_menu.ts +7 -0
- package/src/flow/nodes/wait_for_response.ts +7 -0
- package/src/flow/nodes/wait_for_video.ts +7 -0
- package/src/flow/types.ts +352 -0
- package/src/flow/utils.ts +76 -0
- package/src/form/ArrayEditor.ts +240 -0
- package/src/form/BaseListEditor.ts +177 -0
- package/src/form/Checkbox.ts +22 -3
- package/src/form/Completion.ts +6 -0
- package/src/form/FormField.ts +115 -11
- package/src/form/KeyValueEditor.ts +251 -0
- package/src/form/select/Select.ts +105 -32
- package/src/interfaces.ts +7 -2
- package/src/live/ContactChat.ts +3 -97
- package/src/store/flow-definition.d.ts +6 -1
- package/static/api/contacts.json +30 -0
- package/static/api/groups.json +4 -426
- package/static/api/locations.json +24 -0
- package/static/api/media.json +5 -0
- package/static/api/optins.json +16 -0
- package/static/api/orgs.json +13 -0
- package/static/api/topics.json +21 -0
- package/static/api/users.json +26 -0
- package/static/css/temba-components.css +3 -6
- package/temba-modules.ts +9 -2
- package/test/ActionHelper.ts +142 -0
- package/test/actions/add_contact_groups.test.ts +89 -0
- package/test/actions/remove_contact_groups.test.ts +265 -0
- package/test/actions/send_email.test.ts +214 -0
- package/test/actions/send_msg.test.ts +130 -0
- package/test/temba-action-editing-integration.test.ts +240 -0
- package/test/temba-checkbox.test.ts +1 -1
- package/test/temba-field-config.test.ts +152 -0
- package/test/temba-flow-editor-node.test.ts +18 -18
- package/test/temba-node-editor.test.ts +353 -0
- package/test/temba-select.test.ts +234 -0
- package/test-assets/contacts/history.json +11 -33
- package/web-dev-server.config.mjs +34 -0
- package/.github/workflows/coverage.yml +0 -80
- package/demo/sticky-note-demo.html +0 -155
- package/out-tsc/src/flow/EditorNode.js.map +0 -1
- package/out-tsc/src/flow/render.js +0 -358
- package/out-tsc/src/flow/render.js.map +0 -1
- package/out-tsc/test/temba-flow-render.test.js +0 -794
- package/out-tsc/test/temba-flow-render.test.js.map +0 -1
- package/src/flow/render.ts +0 -443
- package/test/temba-flow-render.test.ts +0 -1003
|
@@ -0,0 +1,1211 @@
|
|
|
1
|
+
import { __decorate } from "tslib";
|
|
2
|
+
import { html, css } from 'lit';
|
|
3
|
+
import { property, state } from 'lit/decorators.js';
|
|
4
|
+
import { RapidElement } from '../RapidElement';
|
|
5
|
+
import { NODE_CONFIG, ACTION_CONFIG } from './config';
|
|
6
|
+
import { CustomEventType } from '../interfaces';
|
|
7
|
+
import { generateUUID } from '../utils';
|
|
8
|
+
export class NodeEditor extends RapidElement {
|
|
9
|
+
constructor() {
|
|
10
|
+
super(...arguments);
|
|
11
|
+
this.isOpen = false;
|
|
12
|
+
this.formData = {};
|
|
13
|
+
this.originalFormData = {};
|
|
14
|
+
this.errors = {};
|
|
15
|
+
this.groupCollapseState = {};
|
|
16
|
+
}
|
|
17
|
+
static get styles() {
|
|
18
|
+
return css `
|
|
19
|
+
.node-editor-form {
|
|
20
|
+
padding: 20px;
|
|
21
|
+
display: flex;
|
|
22
|
+
flex-direction: column;
|
|
23
|
+
gap: 15px;
|
|
24
|
+
min-width: 400px;
|
|
25
|
+
padding-bottom: 40px;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.form-field {
|
|
29
|
+
display: flex;
|
|
30
|
+
flex-direction: column;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.form-field label {
|
|
34
|
+
font-weight: 500;
|
|
35
|
+
margin-bottom: 6px;
|
|
36
|
+
color: #333;
|
|
37
|
+
font-size: 14px;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.field-errors {
|
|
41
|
+
color: var(--color-error, tomato);
|
|
42
|
+
font-size: 12px;
|
|
43
|
+
margin-left: 5px;
|
|
44
|
+
margin-top: 15px;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.form-actions {
|
|
48
|
+
display: flex;
|
|
49
|
+
gap: 10px;
|
|
50
|
+
justify-content: flex-end;
|
|
51
|
+
margin-top: 20px;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.action-section {
|
|
55
|
+
border: 1px solid #e0e0e0;
|
|
56
|
+
border-radius: 4px;
|
|
57
|
+
padding: 15px;
|
|
58
|
+
margin: 10px 0;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.action-section h3 {
|
|
62
|
+
margin: 0 0 10px 0;
|
|
63
|
+
color: #333;
|
|
64
|
+
font-size: 14px;
|
|
65
|
+
font-weight: 600;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.router-section {
|
|
69
|
+
border: 1px solid #e0e0e0;
|
|
70
|
+
border-radius: 4px;
|
|
71
|
+
padding: 15px;
|
|
72
|
+
margin: 10px 0;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.router-section h3 {
|
|
76
|
+
margin: 0 0 10px 0;
|
|
77
|
+
color: #333;
|
|
78
|
+
font-size: 14px;
|
|
79
|
+
font-weight: 600;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.form-row {
|
|
83
|
+
display: grid;
|
|
84
|
+
gap: 1rem;
|
|
85
|
+
align-items: end;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.form-group {
|
|
89
|
+
border: 1px solid #e0e0e0;
|
|
90
|
+
border-radius: 6px;
|
|
91
|
+
overflow: hidden;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.form-group.has-errors {
|
|
95
|
+
border-color: var(--color-error, tomato);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.form-group-header {
|
|
99
|
+
background: #f8f9fa;
|
|
100
|
+
padding: 12px 15px;
|
|
101
|
+
border-bottom: 1px solid #e0e0e0;
|
|
102
|
+
display: flex;
|
|
103
|
+
align-items: center;
|
|
104
|
+
justify-content: space-between;
|
|
105
|
+
cursor: pointer;
|
|
106
|
+
user-select: none;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.form-group-header.collapsible:hover {
|
|
110
|
+
background: #f1f3f4;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.form-group-info {
|
|
114
|
+
flex: 1;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.form-group-title {
|
|
118
|
+
font-weight: 500;
|
|
119
|
+
color: #333;
|
|
120
|
+
font-size: 14px;
|
|
121
|
+
display: flex;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.form-group-help {
|
|
125
|
+
font-size: 12px;
|
|
126
|
+
color: #666;
|
|
127
|
+
margin-top: 2px;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.form-group-toggle {
|
|
131
|
+
color: #666;
|
|
132
|
+
transition: transform 0.3s ease;
|
|
133
|
+
display: flex;
|
|
134
|
+
align-items: center;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.form-group-toggle.collapsed {
|
|
138
|
+
transform: rotate(-90deg);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.form-group-content {
|
|
142
|
+
padding: 15px;
|
|
143
|
+
display: flex;
|
|
144
|
+
flex-direction: column;
|
|
145
|
+
gap: 15px;
|
|
146
|
+
overflow: hidden;
|
|
147
|
+
transition: all 0.3s ease;
|
|
148
|
+
max-height: 1000px; /* Large enough to accommodate most content */
|
|
149
|
+
opacity: 1;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.form-group-content.collapsed {
|
|
153
|
+
max-height: 0;
|
|
154
|
+
padding-top: 0;
|
|
155
|
+
padding-bottom: 0;
|
|
156
|
+
opacity: 0;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.group-toggle-icon {
|
|
160
|
+
color: #666;
|
|
161
|
+
transition: transform 0.3s ease;
|
|
162
|
+
cursor: pointer;
|
|
163
|
+
transform: rotate(0deg);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
.group-toggle-icon.expanded {
|
|
167
|
+
transform: rotate(90deg);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.group-toggle-icon.collapsed {
|
|
171
|
+
transform: rotate(0deg);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.group-toggle-icon:hover {
|
|
175
|
+
color: #333;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
.group-error-icon {
|
|
179
|
+
color: var(--color-error, tomato);
|
|
180
|
+
margin-right: 8px;
|
|
181
|
+
}
|
|
182
|
+
`;
|
|
183
|
+
}
|
|
184
|
+
connectedCallback() {
|
|
185
|
+
super.connectedCallback();
|
|
186
|
+
this.initializeFormData();
|
|
187
|
+
}
|
|
188
|
+
updated(changedProperties) {
|
|
189
|
+
super.updated(changedProperties);
|
|
190
|
+
if (changedProperties.has('node') || changedProperties.has('action')) {
|
|
191
|
+
if (this.node || this.action) {
|
|
192
|
+
this.openDialog();
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
this.isOpen = false;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
openDialog() {
|
|
200
|
+
this.initializeFormData();
|
|
201
|
+
this.errors = {};
|
|
202
|
+
this.isOpen = true;
|
|
203
|
+
}
|
|
204
|
+
closeDialog() {
|
|
205
|
+
this.isOpen = false;
|
|
206
|
+
this.formData = {};
|
|
207
|
+
this.errors = {};
|
|
208
|
+
this.groupCollapseState = {};
|
|
209
|
+
}
|
|
210
|
+
initializeFormData() {
|
|
211
|
+
if (this.action) {
|
|
212
|
+
// Action editing mode - use action config
|
|
213
|
+
const actionConfig = ACTION_CONFIG[this.action.type];
|
|
214
|
+
if (actionConfig === null || actionConfig === void 0 ? void 0 : actionConfig.toFormData) {
|
|
215
|
+
this.formData = actionConfig.toFormData(this.action);
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
this.formData = { ...this.action };
|
|
219
|
+
// Apply smart transformations for select fields that expect {name, value} format
|
|
220
|
+
this.applySmartSelectTransformations(actionConfig);
|
|
221
|
+
}
|
|
222
|
+
// Convert Record objects to array format for key-value editors
|
|
223
|
+
this.processFormDataForEditing();
|
|
224
|
+
// Store a copy of the original form data for computed field comparisons
|
|
225
|
+
this.originalFormData = JSON.parse(JSON.stringify(this.formData));
|
|
226
|
+
}
|
|
227
|
+
else if (this.node) {
|
|
228
|
+
// Node editing mode - use node config
|
|
229
|
+
const nodeConfig = this.getNodeConfig();
|
|
230
|
+
if (nodeConfig === null || nodeConfig === void 0 ? void 0 : nodeConfig.toFormData) {
|
|
231
|
+
this.formData = nodeConfig.toFormData(this.node);
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
this.formData = { ...this.node };
|
|
235
|
+
}
|
|
236
|
+
// Convert Record objects to array format for key-value editors
|
|
237
|
+
this.processFormDataForEditing();
|
|
238
|
+
// Store a copy of the original form data for computed field comparisons
|
|
239
|
+
this.originalFormData = JSON.parse(JSON.stringify(this.formData));
|
|
240
|
+
}
|
|
241
|
+
// enforce immutability of formData
|
|
242
|
+
Object.keys(this.formData).forEach((key) => {
|
|
243
|
+
const value = this.formData[key];
|
|
244
|
+
if (Array.isArray(value)) {
|
|
245
|
+
this.formData[key] = [...value];
|
|
246
|
+
}
|
|
247
|
+
else if (value && typeof value === 'object') {
|
|
248
|
+
// If it's an object, ensure we don't mutate the original
|
|
249
|
+
this.formData[key] = { ...value };
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
processFormDataForEditing() {
|
|
254
|
+
const processed = { ...this.formData };
|
|
255
|
+
// Convert Record objects to key-value arrays for key-value editors
|
|
256
|
+
Object.keys(processed).forEach((key) => {
|
|
257
|
+
const value = processed[key];
|
|
258
|
+
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
259
|
+
// Check if this field should be a key-value editor
|
|
260
|
+
const isKeyValueField = this.isKeyValueField(key);
|
|
261
|
+
if (isKeyValueField) {
|
|
262
|
+
// Convert Record to array format
|
|
263
|
+
processed[key] = Object.entries(value).map(([k, v]) => ({
|
|
264
|
+
key: k,
|
|
265
|
+
value: v
|
|
266
|
+
}));
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
this.formData = processed;
|
|
271
|
+
}
|
|
272
|
+
applySmartSelectTransformations(actionConfig) {
|
|
273
|
+
if (!actionConfig)
|
|
274
|
+
return;
|
|
275
|
+
const fields = actionConfig.form;
|
|
276
|
+
if (!fields)
|
|
277
|
+
return;
|
|
278
|
+
Object.entries(fields).forEach(([fieldName, fieldConfig]) => {
|
|
279
|
+
if (this.shouldApplySmartSelectTransformation(fieldName, fieldConfig)) {
|
|
280
|
+
const value = this.formData[fieldName];
|
|
281
|
+
if (Array.isArray(value) &&
|
|
282
|
+
value.length > 0 &&
|
|
283
|
+
typeof value[0] === 'string') {
|
|
284
|
+
// Transform string array to select options format
|
|
285
|
+
this.formData[fieldName] = value.map((item) => ({
|
|
286
|
+
name: item,
|
|
287
|
+
value: item
|
|
288
|
+
}));
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
shouldApplySmartSelectTransformation(fieldName, fieldConfig) {
|
|
294
|
+
var _a;
|
|
295
|
+
const selectConfig = fieldConfig;
|
|
296
|
+
return ((fieldConfig.type === 'select' &&
|
|
297
|
+
(selectConfig.multi || selectConfig.tags) &&
|
|
298
|
+
// Don't transform if already has explicit transformations
|
|
299
|
+
!this.action) ||
|
|
300
|
+
!((_a = ACTION_CONFIG[this.action.type]) === null || _a === void 0 ? void 0 : _a.toFormData));
|
|
301
|
+
}
|
|
302
|
+
isKeyValueField(fieldName) {
|
|
303
|
+
var _a;
|
|
304
|
+
// Check if this field is configured as a key-value type
|
|
305
|
+
if (this.action) {
|
|
306
|
+
const actionConfig = ACTION_CONFIG[this.action.type];
|
|
307
|
+
const fields = actionConfig === null || actionConfig === void 0 ? void 0 : actionConfig.form;
|
|
308
|
+
return ((_a = fields === null || fields === void 0 ? void 0 : fields[fieldName]) === null || _a === void 0 ? void 0 : _a.type) === 'key-value';
|
|
309
|
+
}
|
|
310
|
+
return false;
|
|
311
|
+
}
|
|
312
|
+
getNodeConfig() {
|
|
313
|
+
if (!this.nodeUI)
|
|
314
|
+
return null;
|
|
315
|
+
// Get node config based on the nodeUI's type
|
|
316
|
+
return this.nodeUI.type ? NODE_CONFIG[this.nodeUI.type] : null;
|
|
317
|
+
}
|
|
318
|
+
getHeaderColor() {
|
|
319
|
+
if (this.action) {
|
|
320
|
+
// Action editing mode
|
|
321
|
+
const actionConfig = ACTION_CONFIG[this.action.type];
|
|
322
|
+
return (actionConfig === null || actionConfig === void 0 ? void 0 : actionConfig.color) || '#666666';
|
|
323
|
+
}
|
|
324
|
+
else if (this.node) {
|
|
325
|
+
// Node editing mode
|
|
326
|
+
const nodeConfig = this.getNodeConfig();
|
|
327
|
+
return (nodeConfig === null || nodeConfig === void 0 ? void 0 : nodeConfig.color) || '#666666';
|
|
328
|
+
}
|
|
329
|
+
return '#666666';
|
|
330
|
+
}
|
|
331
|
+
handleDialogButtonClick(event) {
|
|
332
|
+
const button = event.detail.button;
|
|
333
|
+
if (button.name === 'Save') {
|
|
334
|
+
this.handleSave();
|
|
335
|
+
}
|
|
336
|
+
else if (button.name === 'Cancel') {
|
|
337
|
+
this.handleCancel();
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
handleSave() {
|
|
341
|
+
// Validate the form
|
|
342
|
+
const validation = this.validateForm();
|
|
343
|
+
if (!validation.valid) {
|
|
344
|
+
this.errors = validation.errors;
|
|
345
|
+
// Expand any groups that contain validation errors
|
|
346
|
+
this.expandGroupsWithErrors(validation.errors);
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
// Process form data to convert key-value arrays to Records before saving
|
|
350
|
+
const processedFormData = this.processFormDataForSave();
|
|
351
|
+
// Determine whether to use node or action saving based on context
|
|
352
|
+
// If we have a node with a router, always use node saving (even if action is set)
|
|
353
|
+
// because router configuration is handled at the node level
|
|
354
|
+
if (this.node && this.node.router) {
|
|
355
|
+
// Node editing mode with router - use formDataToNode
|
|
356
|
+
const updatedNode = this.formDataToNode(processedFormData);
|
|
357
|
+
this.fireCustomEvent(CustomEventType.NodeSaved, {
|
|
358
|
+
node: updatedNode
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
else if (this.action) {
|
|
362
|
+
// Pure action editing mode (no router)
|
|
363
|
+
const updatedAction = this.formDataToAction(processedFormData);
|
|
364
|
+
this.fireCustomEvent(CustomEventType.ActionSaved, {
|
|
365
|
+
action: updatedAction
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
else if (this.node) {
|
|
369
|
+
// Node editing mode without router
|
|
370
|
+
const updatedNode = this.formDataToNode(processedFormData);
|
|
371
|
+
this.fireCustomEvent(CustomEventType.NodeSaved, {
|
|
372
|
+
node: updatedNode
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
processFormDataForSave() {
|
|
377
|
+
const processed = { ...this.formData };
|
|
378
|
+
// Convert key-value arrays to Records
|
|
379
|
+
Object.keys(processed).forEach((key) => {
|
|
380
|
+
const value = processed[key];
|
|
381
|
+
if (Array.isArray(value) &&
|
|
382
|
+
value.length > 0 &&
|
|
383
|
+
typeof value[0] === 'object' &&
|
|
384
|
+
'key' in value[0] &&
|
|
385
|
+
'value' in value[0]) {
|
|
386
|
+
// This is a key-value array, convert to Record
|
|
387
|
+
const record = {};
|
|
388
|
+
value.forEach(({ key: k, value: v }) => {
|
|
389
|
+
if (k.trim() !== '' || v.trim() !== '') {
|
|
390
|
+
record[k] = v;
|
|
391
|
+
}
|
|
392
|
+
});
|
|
393
|
+
processed[key] = record;
|
|
394
|
+
}
|
|
395
|
+
else if (Array.isArray(value) && value.length === 0) {
|
|
396
|
+
// Empty key-value array should become empty object
|
|
397
|
+
const isKeyValueField = this.isKeyValueField(key);
|
|
398
|
+
if (isKeyValueField) {
|
|
399
|
+
processed[key] = {};
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
});
|
|
403
|
+
return processed;
|
|
404
|
+
}
|
|
405
|
+
handleCancel() {
|
|
406
|
+
this.fireCustomEvent(CustomEventType.NodeEditCancelled, {});
|
|
407
|
+
}
|
|
408
|
+
validateForm() {
|
|
409
|
+
const errors = {};
|
|
410
|
+
if (this.action) {
|
|
411
|
+
// Action validation using fields configuration
|
|
412
|
+
const actionConfig = ACTION_CONFIG[this.action.type];
|
|
413
|
+
// Check if new field configuration system is available
|
|
414
|
+
if (actionConfig === null || actionConfig === void 0 ? void 0 : actionConfig.form) {
|
|
415
|
+
Object.entries(actionConfig === null || actionConfig === void 0 ? void 0 : actionConfig.form).forEach(([fieldName, fieldConfig]) => {
|
|
416
|
+
const value = this.formData[fieldName];
|
|
417
|
+
// Check required fields
|
|
418
|
+
if (fieldConfig.required &&
|
|
419
|
+
(!value || (Array.isArray(value) && value.length === 0))) {
|
|
420
|
+
errors[fieldName] = `${fieldConfig.label || fieldName} is required`;
|
|
421
|
+
}
|
|
422
|
+
// Check minLength for text fields
|
|
423
|
+
if (typeof value === 'string' &&
|
|
424
|
+
fieldConfig.minLength &&
|
|
425
|
+
value.length < fieldConfig.minLength) {
|
|
426
|
+
errors[fieldName] = `${fieldConfig.label || fieldName} must be at least ${fieldConfig.minLength} characters`;
|
|
427
|
+
}
|
|
428
|
+
// Check maxLength for text fields
|
|
429
|
+
if (typeof value === 'string' &&
|
|
430
|
+
fieldConfig.maxLength &&
|
|
431
|
+
value.length > fieldConfig.maxLength) {
|
|
432
|
+
errors[fieldName] = `${fieldConfig.label || fieldName} must be no more than ${fieldConfig.maxLength} characters`;
|
|
433
|
+
}
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
// Run custom validation if available
|
|
437
|
+
if (actionConfig === null || actionConfig === void 0 ? void 0 : actionConfig.validate) {
|
|
438
|
+
// Convert form data back to action for validation
|
|
439
|
+
let actionForValidation;
|
|
440
|
+
if (actionConfig.fromFormData) {
|
|
441
|
+
actionForValidation = actionConfig.fromFormData(this.formData);
|
|
442
|
+
}
|
|
443
|
+
else {
|
|
444
|
+
actionForValidation = { ...this.action, ...this.formData };
|
|
445
|
+
}
|
|
446
|
+
const customValidation = actionConfig.validate(actionForValidation);
|
|
447
|
+
Object.assign(errors, customValidation.errors);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
else if (this.node) {
|
|
451
|
+
// Node validation
|
|
452
|
+
const nodeConfig = this.getNodeConfig();
|
|
453
|
+
// Check required fields from node properties
|
|
454
|
+
if (nodeConfig === null || nodeConfig === void 0 ? void 0 : nodeConfig.properties) {
|
|
455
|
+
Object.entries(nodeConfig.properties).forEach(([fieldName, fieldConfig]) => {
|
|
456
|
+
const value = this.formData[fieldName];
|
|
457
|
+
// Check required fields
|
|
458
|
+
if (fieldConfig.required &&
|
|
459
|
+
(!value || (Array.isArray(value) && value.length === 0))) {
|
|
460
|
+
errors[fieldName] = `${fieldConfig.label || fieldName} is required`;
|
|
461
|
+
}
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
// Validate key-value fields for unique keys
|
|
466
|
+
this.validateKeyValueUniqueness(errors);
|
|
467
|
+
return {
|
|
468
|
+
valid: Object.keys(errors).length === 0,
|
|
469
|
+
errors
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
validateKeyValueUniqueness(errors) {
|
|
473
|
+
// The individual key-value editors will show validation errors on duplicate keys and empty keys with values
|
|
474
|
+
// We just need to prevent form submission when there are validation issues
|
|
475
|
+
Object.entries(this.formData).forEach(([fieldName, value]) => {
|
|
476
|
+
if (Array.isArray(value) &&
|
|
477
|
+
value.length > 0 &&
|
|
478
|
+
typeof value[0] === 'object' &&
|
|
479
|
+
'key' in value[0] &&
|
|
480
|
+
'value' in value[0]) {
|
|
481
|
+
// This is a key-value array
|
|
482
|
+
let hasValidationErrors = false;
|
|
483
|
+
// Check for empty keys with values
|
|
484
|
+
value.forEach(({ key, value: itemValue }) => {
|
|
485
|
+
if (key.trim() === '' && itemValue.trim() !== '') {
|
|
486
|
+
hasValidationErrors = true;
|
|
487
|
+
}
|
|
488
|
+
});
|
|
489
|
+
// Check for duplicate keys (only non-empty ones)
|
|
490
|
+
const keys = value
|
|
491
|
+
.filter(({ key }) => key.trim() !== '') // Only check non-empty keys
|
|
492
|
+
.map(({ key }) => key.trim());
|
|
493
|
+
const uniqueKeys = new Set(keys);
|
|
494
|
+
if (keys.length !== uniqueKeys.size) {
|
|
495
|
+
hasValidationErrors = true;
|
|
496
|
+
}
|
|
497
|
+
if (hasValidationErrors) {
|
|
498
|
+
errors[fieldName] = `Please resolve validation errors to continue`;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
formDataToNode(formData = this.formData) {
|
|
504
|
+
var _a;
|
|
505
|
+
if (!this.node)
|
|
506
|
+
throw new Error('No node to update');
|
|
507
|
+
let updatedNode = { ...this.node };
|
|
508
|
+
// Handle actions using action config transformations if available
|
|
509
|
+
if (this.node.actions && this.node.actions.length > 0) {
|
|
510
|
+
updatedNode.actions = this.node.actions.map((action) => {
|
|
511
|
+
// If we're editing a specific action, only transform that one
|
|
512
|
+
if (this.action && action.uuid === this.action.uuid) {
|
|
513
|
+
const actionConfig = ACTION_CONFIG[action.type];
|
|
514
|
+
if (actionConfig === null || actionConfig === void 0 ? void 0 : actionConfig.fromFormData) {
|
|
515
|
+
// Use action-specific form data transformation
|
|
516
|
+
return actionConfig.fromFormData(formData);
|
|
517
|
+
}
|
|
518
|
+
else {
|
|
519
|
+
// Default transformation - merge form data with original action
|
|
520
|
+
return { ...action, ...formData };
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
else {
|
|
524
|
+
// Keep other actions unchanged
|
|
525
|
+
return action;
|
|
526
|
+
}
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
// Handle router configuration using node config
|
|
530
|
+
if (this.node.router) {
|
|
531
|
+
const nodeConfig = this.getNodeConfig();
|
|
532
|
+
if (nodeConfig === null || nodeConfig === void 0 ? void 0 : nodeConfig.fromFormData) {
|
|
533
|
+
// Use node-specific form data transformation
|
|
534
|
+
updatedNode = nodeConfig.fromFormData(formData, updatedNode);
|
|
535
|
+
}
|
|
536
|
+
else {
|
|
537
|
+
// Default router handling
|
|
538
|
+
updatedNode.router = { ...this.node.router };
|
|
539
|
+
// Apply form data to router fields if they exist
|
|
540
|
+
if (formData.result_name !== undefined) {
|
|
541
|
+
updatedNode.router.result_name = formData.result_name;
|
|
542
|
+
}
|
|
543
|
+
// Handle preconfigured rules from node config
|
|
544
|
+
if ((_a = nodeConfig === null || nodeConfig === void 0 ? void 0 : nodeConfig.router) === null || _a === void 0 ? void 0 : _a.rules) {
|
|
545
|
+
// Build a complete new set of categories and exits based on node config
|
|
546
|
+
const existingCategories = updatedNode.router.categories || [];
|
|
547
|
+
const existingExits = updatedNode.exits || [];
|
|
548
|
+
const newCategories = [];
|
|
549
|
+
const newExits = [];
|
|
550
|
+
// Group rules by category name to handle multiple rules pointing to the same category
|
|
551
|
+
const categoryNameToRules = new Map();
|
|
552
|
+
nodeConfig.router.rules.forEach((rule) => {
|
|
553
|
+
if (!categoryNameToRules.has(rule.categoryName)) {
|
|
554
|
+
categoryNameToRules.set(rule.categoryName, []);
|
|
555
|
+
}
|
|
556
|
+
categoryNameToRules.get(rule.categoryName).push(rule);
|
|
557
|
+
});
|
|
558
|
+
// Create categories for all unique category names
|
|
559
|
+
categoryNameToRules.forEach((rules, categoryName) => {
|
|
560
|
+
// Check if category already exists to preserve its UUID and exit_uuid
|
|
561
|
+
const existingCategory = existingCategories.find((cat) => cat.name === categoryName);
|
|
562
|
+
if (existingCategory) {
|
|
563
|
+
// Preserve existing category and its associated exit
|
|
564
|
+
newCategories.push(existingCategory);
|
|
565
|
+
const associatedExit = existingExits.find((exit) => exit.uuid === existingCategory.exit_uuid);
|
|
566
|
+
if (associatedExit) {
|
|
567
|
+
newExits.push(associatedExit);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
else {
|
|
571
|
+
// Create new category and exit
|
|
572
|
+
const categoryUuid = generateUUID();
|
|
573
|
+
const exitUuid = generateUUID();
|
|
574
|
+
newCategories.push({
|
|
575
|
+
uuid: categoryUuid,
|
|
576
|
+
name: categoryName,
|
|
577
|
+
exit_uuid: exitUuid
|
|
578
|
+
});
|
|
579
|
+
newExits.push({
|
|
580
|
+
uuid: exitUuid,
|
|
581
|
+
destination_uuid: null
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
});
|
|
585
|
+
// Add default category if specified
|
|
586
|
+
if (nodeConfig.router.defaultCategory) {
|
|
587
|
+
// Check if default category already exists in our new list
|
|
588
|
+
const existingDefault = newCategories.find((cat) => cat.name === nodeConfig.router.defaultCategory);
|
|
589
|
+
if (!existingDefault) {
|
|
590
|
+
// Check if it exists in the original categories
|
|
591
|
+
const originalDefault = existingCategories.find((cat) => cat.name === nodeConfig.router.defaultCategory);
|
|
592
|
+
if (originalDefault) {
|
|
593
|
+
// Preserve existing default category and its exit
|
|
594
|
+
newCategories.push(originalDefault);
|
|
595
|
+
const associatedExit = existingExits.find((exit) => exit.uuid === originalDefault.exit_uuid);
|
|
596
|
+
if (associatedExit) {
|
|
597
|
+
newExits.push(associatedExit);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
else {
|
|
601
|
+
// Create new default category and exit
|
|
602
|
+
const categoryUuid = generateUUID();
|
|
603
|
+
const exitUuid = generateUUID();
|
|
604
|
+
newCategories.push({
|
|
605
|
+
uuid: categoryUuid,
|
|
606
|
+
name: nodeConfig.router.defaultCategory,
|
|
607
|
+
exit_uuid: exitUuid
|
|
608
|
+
});
|
|
609
|
+
newExits.push({
|
|
610
|
+
uuid: exitUuid,
|
|
611
|
+
destination_uuid: null
|
|
612
|
+
});
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
// Replace the entire categories and exits lists with our complete new sets
|
|
617
|
+
updatedNode.router.categories = newCategories;
|
|
618
|
+
updatedNode.exits = newExits;
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
else {
|
|
623
|
+
// If no router, just apply form data to node properties
|
|
624
|
+
Object.keys(formData).forEach((key) => {
|
|
625
|
+
if (key !== 'uuid' &&
|
|
626
|
+
key !== 'actions' &&
|
|
627
|
+
key !== 'exits' &&
|
|
628
|
+
key !== 'router') {
|
|
629
|
+
updatedNode[key] = formData[key];
|
|
630
|
+
}
|
|
631
|
+
});
|
|
632
|
+
}
|
|
633
|
+
return updatedNode;
|
|
634
|
+
}
|
|
635
|
+
formDataToAction(formData = this.formData) {
|
|
636
|
+
if (!this.action)
|
|
637
|
+
throw new Error('No action to update');
|
|
638
|
+
// Use action config transformation if available
|
|
639
|
+
const actionConfig = ACTION_CONFIG[this.action.type];
|
|
640
|
+
if (actionConfig === null || actionConfig === void 0 ? void 0 : actionConfig.fromFormData) {
|
|
641
|
+
return actionConfig.fromFormData(formData);
|
|
642
|
+
}
|
|
643
|
+
else {
|
|
644
|
+
// Apply smart select transformations in reverse and provide default 1:1 mapping
|
|
645
|
+
const processedFormData = this.reverseSmartSelectTransformations(formData, actionConfig);
|
|
646
|
+
return { ...this.action, ...processedFormData };
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
reverseSmartSelectTransformations(formData, actionConfig) {
|
|
650
|
+
if (!actionConfig || !actionConfig.form)
|
|
651
|
+
return formData;
|
|
652
|
+
const processed = { ...formData };
|
|
653
|
+
Object.entries(actionConfig.form).forEach(([fieldName, fieldConfig]) => {
|
|
654
|
+
if (this.shouldApplySmartSelectTransformation(fieldName, fieldConfig)) {
|
|
655
|
+
const value = processed[fieldName];
|
|
656
|
+
if (Array.isArray(value) &&
|
|
657
|
+
value.length > 0 &&
|
|
658
|
+
typeof value[0] === 'object' &&
|
|
659
|
+
'value' in value[0]) {
|
|
660
|
+
// Transform select options format back to string array
|
|
661
|
+
processed[fieldName] = value.map((item) => item.value || item.name || item);
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
});
|
|
665
|
+
return processed;
|
|
666
|
+
}
|
|
667
|
+
handleFormFieldChange(propertyName, event) {
|
|
668
|
+
const target = event.target;
|
|
669
|
+
let value;
|
|
670
|
+
// Handle different component types like ActionEditor does
|
|
671
|
+
if (target.tagName === 'TEMBA-CHECKBOX') {
|
|
672
|
+
value = target.checked;
|
|
673
|
+
}
|
|
674
|
+
else if (target.tagName === 'TEMBA-SELECT' &&
|
|
675
|
+
(target.multi || target.emails || target.tags)) {
|
|
676
|
+
value = target.values || [];
|
|
677
|
+
}
|
|
678
|
+
else if (target.values !== undefined) {
|
|
679
|
+
value = target.values;
|
|
680
|
+
}
|
|
681
|
+
else {
|
|
682
|
+
value = target.value;
|
|
683
|
+
}
|
|
684
|
+
this.formData = {
|
|
685
|
+
...this.formData,
|
|
686
|
+
[propertyName]: value
|
|
687
|
+
};
|
|
688
|
+
// Clear any existing error for this field
|
|
689
|
+
if (this.errors[propertyName]) {
|
|
690
|
+
const newErrors = { ...this.errors };
|
|
691
|
+
delete newErrors[propertyName];
|
|
692
|
+
this.errors = newErrors;
|
|
693
|
+
}
|
|
694
|
+
// Check for computed values in dependent fields
|
|
695
|
+
this.updateComputedFields(propertyName);
|
|
696
|
+
// Trigger re-render to handle conditional field visibility
|
|
697
|
+
this.requestUpdate();
|
|
698
|
+
}
|
|
699
|
+
updateComputedFields(changedFieldName) {
|
|
700
|
+
if (!this.action)
|
|
701
|
+
return;
|
|
702
|
+
const config = ACTION_CONFIG[this.action.type];
|
|
703
|
+
if (!(config === null || config === void 0 ? void 0 : config.form))
|
|
704
|
+
return;
|
|
705
|
+
// Check all fields to see if any depend on the changed field
|
|
706
|
+
Object.entries(config.form).forEach(([fieldName, fieldConfig]) => {
|
|
707
|
+
var _a;
|
|
708
|
+
if ((_a = fieldConfig.dependsOn) === null || _a === void 0 ? void 0 : _a.includes(changedFieldName)) {
|
|
709
|
+
if (fieldConfig.computeValue) {
|
|
710
|
+
const currentValue = this.formData[fieldName];
|
|
711
|
+
const computedValue = fieldConfig.computeValue(this.formData, currentValue, this.originalFormData);
|
|
712
|
+
// Update the form data with the computed value
|
|
713
|
+
this.formData = {
|
|
714
|
+
...this.formData,
|
|
715
|
+
[fieldName]: computedValue
|
|
716
|
+
};
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
});
|
|
720
|
+
}
|
|
721
|
+
renderNewField(fieldName, config, value) {
|
|
722
|
+
var _a;
|
|
723
|
+
// Check visibility condition
|
|
724
|
+
if ((_a = config.conditions) === null || _a === void 0 ? void 0 : _a.visible) {
|
|
725
|
+
try {
|
|
726
|
+
const isVisible = config.conditions.visible(this.formData);
|
|
727
|
+
if (!isVisible) {
|
|
728
|
+
return html ``;
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
catch (error) {
|
|
732
|
+
console.error(`Error checking visibility for ${fieldName}:`, error);
|
|
733
|
+
// If there's an error, show the field by default
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
const errors = this.errors[fieldName] ? [this.errors[fieldName]] : [];
|
|
737
|
+
// Build container style with maxWidth if specified
|
|
738
|
+
const containerStyle = config.maxWidth
|
|
739
|
+
? `max-width: ${config.maxWidth};`
|
|
740
|
+
: '';
|
|
741
|
+
const fieldContent = this.renderFieldContent(fieldName, config, value, errors);
|
|
742
|
+
// Wrap in container with style if maxWidth is specified
|
|
743
|
+
if (containerStyle) {
|
|
744
|
+
return html `<div style="${containerStyle}">${fieldContent}</div>`;
|
|
745
|
+
}
|
|
746
|
+
return fieldContent;
|
|
747
|
+
}
|
|
748
|
+
renderFieldContent(fieldName, config, value, errors) {
|
|
749
|
+
var _a;
|
|
750
|
+
switch (config.type) {
|
|
751
|
+
case 'text':
|
|
752
|
+
return html `<temba-textinput
|
|
753
|
+
name="${fieldName}"
|
|
754
|
+
label="${config.label}"
|
|
755
|
+
?required="${config.required}"
|
|
756
|
+
.errors="${errors}"
|
|
757
|
+
.value="${value || ''}"
|
|
758
|
+
placeholder="${config.placeholder || ''}"
|
|
759
|
+
.helpText="${config.helpText || ''}"
|
|
760
|
+
@input="${(e) => this.handleFormFieldChange(fieldName, e)}"
|
|
761
|
+
></temba-textinput>`;
|
|
762
|
+
case 'textarea': {
|
|
763
|
+
const textareaConfig = config;
|
|
764
|
+
const minHeightStyle = textareaConfig.minHeight
|
|
765
|
+
? `--textarea-min-height: ${textareaConfig.minHeight}px;`
|
|
766
|
+
: '';
|
|
767
|
+
if (config.evaluated) {
|
|
768
|
+
return html `<temba-completion
|
|
769
|
+
name="${fieldName}"
|
|
770
|
+
label="${config.label}"
|
|
771
|
+
?required="${config.required}"
|
|
772
|
+
.errors="${errors}"
|
|
773
|
+
.value="${value || ''}"
|
|
774
|
+
placeholder="${config.placeholder || ''}"
|
|
775
|
+
textarea
|
|
776
|
+
expressions="session"
|
|
777
|
+
style="${minHeightStyle}"
|
|
778
|
+
.helpText="${config.helpText || ''}"
|
|
779
|
+
@input="${(e) => this.handleFormFieldChange(fieldName, e)}"
|
|
780
|
+
></temba-completion>`;
|
|
781
|
+
}
|
|
782
|
+
else {
|
|
783
|
+
return html `<temba-textinput
|
|
784
|
+
name="${fieldName}"
|
|
785
|
+
label="${config.label}"
|
|
786
|
+
?required="${config.required}"
|
|
787
|
+
.errors="${errors}"
|
|
788
|
+
.value="${value || ''}"
|
|
789
|
+
placeholder="${config.placeholder || ''}"
|
|
790
|
+
textarea
|
|
791
|
+
.rows="${textareaConfig.rows || 3}"
|
|
792
|
+
style="${minHeightStyle}"
|
|
793
|
+
.helpText="${config.helpText || ''}"
|
|
794
|
+
@input="${(e) => this.handleFormFieldChange(fieldName, e)}"
|
|
795
|
+
></temba-textinput>`;
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
case 'select': {
|
|
799
|
+
const selectConfig = config;
|
|
800
|
+
return html `<temba-select
|
|
801
|
+
name="${fieldName}"
|
|
802
|
+
label="${config.label}"
|
|
803
|
+
?required="${config.required}"
|
|
804
|
+
.errors="${errors}"
|
|
805
|
+
.values="${value || (selectConfig.multi ? [] : '')}"
|
|
806
|
+
?multi="${selectConfig.multi}"
|
|
807
|
+
?searchable="${selectConfig.searchable}"
|
|
808
|
+
?tags="${selectConfig.tags}"
|
|
809
|
+
?emails="${selectConfig.emails}"
|
|
810
|
+
placeholder="${selectConfig.placeholder || ''}"
|
|
811
|
+
maxItems="${selectConfig.maxItems || 0}"
|
|
812
|
+
valueKey="${selectConfig.valueKey || 'value'}"
|
|
813
|
+
nameKey="${selectConfig.nameKey || 'name'}"
|
|
814
|
+
endpoint="${selectConfig.endpoint || ''}"
|
|
815
|
+
.helpText="${config.helpText || ''}"
|
|
816
|
+
@change="${(e) => this.handleFormFieldChange(fieldName, e)}"
|
|
817
|
+
>
|
|
818
|
+
${(_a = selectConfig.options) === null || _a === void 0 ? void 0 : _a.map((option) => {
|
|
819
|
+
if (typeof option === 'string') {
|
|
820
|
+
return html `<temba-option
|
|
821
|
+
name="${option}"
|
|
822
|
+
value="${option}"
|
|
823
|
+
></temba-option>`;
|
|
824
|
+
}
|
|
825
|
+
else {
|
|
826
|
+
return html `<temba-option
|
|
827
|
+
name="${option.label || option.name}"
|
|
828
|
+
value="${option.value}"
|
|
829
|
+
></temba-option>`;
|
|
830
|
+
}
|
|
831
|
+
})}
|
|
832
|
+
</temba-select>`;
|
|
833
|
+
}
|
|
834
|
+
case 'key-value':
|
|
835
|
+
return html `<div class="form-field">
|
|
836
|
+
<label>${config.label}${config.required ? ' *' : ''}</label>
|
|
837
|
+
<temba-key-value-editor
|
|
838
|
+
name="${fieldName}"
|
|
839
|
+
.value="${value || []}"
|
|
840
|
+
.sortable="${config.sortable}"
|
|
841
|
+
.keyPlaceholder="${config.keyPlaceholder || 'Key'}"
|
|
842
|
+
.valuePlaceholder="${config.valuePlaceholder || 'Value'}"
|
|
843
|
+
.minRows="${config.minRows || 0}"
|
|
844
|
+
@change="${(e) => {
|
|
845
|
+
if (e.detail) {
|
|
846
|
+
this.handleNewFieldChange(fieldName, e.detail.value);
|
|
847
|
+
}
|
|
848
|
+
}}"
|
|
849
|
+
></temba-key-value-editor>
|
|
850
|
+
${errors.length
|
|
851
|
+
? html `<div class="field-errors">${errors.join(', ')}</div>`
|
|
852
|
+
: ''}
|
|
853
|
+
</div>`;
|
|
854
|
+
case 'array':
|
|
855
|
+
return html `<div class="form-field">
|
|
856
|
+
<label>${config.label}${config.required ? ' *' : ''}</label>
|
|
857
|
+
<temba-array-editor
|
|
858
|
+
.value="${value || []}"
|
|
859
|
+
.itemConfig="${config.itemConfig}"
|
|
860
|
+
.sortable="${config.sortable}"
|
|
861
|
+
.itemLabel="${config.itemLabel || 'Item'}"
|
|
862
|
+
.minItems="${config.minItems || 0}"
|
|
863
|
+
.onItemChange="${config.onItemChange}"
|
|
864
|
+
@change="${(e) => this.handleNewFieldChange(fieldName, e.detail.value)}"
|
|
865
|
+
></temba-array-editor>
|
|
866
|
+
${errors.length
|
|
867
|
+
? html `<div class="field-errors">${errors.join(', ')}</div>`
|
|
868
|
+
: ''}
|
|
869
|
+
</div>`;
|
|
870
|
+
case 'checkbox': {
|
|
871
|
+
const checkboxConfig = config;
|
|
872
|
+
return html `<div class="form-field">
|
|
873
|
+
<temba-checkbox
|
|
874
|
+
name="${fieldName}"
|
|
875
|
+
label="${config.label}"
|
|
876
|
+
.helpText="${config.helpText || ''}"
|
|
877
|
+
?required="${config.required}"
|
|
878
|
+
.errors="${errors}"
|
|
879
|
+
?checked="${value || false}"
|
|
880
|
+
size="${checkboxConfig.size || 1.2}"
|
|
881
|
+
animateChange="${checkboxConfig.animateChange || 'pulse'}"
|
|
882
|
+
@change="${(e) => this.handleFormFieldChange(fieldName, e)}"
|
|
883
|
+
></temba-checkbox>
|
|
884
|
+
${errors.length
|
|
885
|
+
? html `<div class="field-errors">${errors.join(', ')}</div>`
|
|
886
|
+
: ''}
|
|
887
|
+
</div>`;
|
|
888
|
+
}
|
|
889
|
+
default:
|
|
890
|
+
return html `<div>Unsupported field type: ${config.type}</div>`;
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
handleGroupToggle(groupLabel) {
|
|
894
|
+
this.groupCollapseState = {
|
|
895
|
+
...this.groupCollapseState,
|
|
896
|
+
[groupLabel]: !this.groupCollapseState[groupLabel]
|
|
897
|
+
};
|
|
898
|
+
}
|
|
899
|
+
expandGroupsWithErrors(errors) {
|
|
900
|
+
if (!this.action)
|
|
901
|
+
return;
|
|
902
|
+
const config = ACTION_CONFIG[this.action.type];
|
|
903
|
+
if (!(config === null || config === void 0 ? void 0 : config.layout))
|
|
904
|
+
return;
|
|
905
|
+
const errorFields = new Set(Object.keys(errors));
|
|
906
|
+
this.expandGroupsWithErrorsRecursive(config.layout, errorFields);
|
|
907
|
+
}
|
|
908
|
+
expandGroupsWithErrorsRecursive(items, errorFields) {
|
|
909
|
+
items.forEach((item) => {
|
|
910
|
+
if (typeof item === 'object' && item.type === 'group') {
|
|
911
|
+
const fieldsInGroup = this.collectFieldsFromItems(item.items);
|
|
912
|
+
const groupHasErrors = fieldsInGroup.some((fieldName) => errorFields.has(fieldName));
|
|
913
|
+
if (groupHasErrors) {
|
|
914
|
+
// Expand this group
|
|
915
|
+
this.groupCollapseState = {
|
|
916
|
+
...this.groupCollapseState,
|
|
917
|
+
[item.label]: false
|
|
918
|
+
};
|
|
919
|
+
}
|
|
920
|
+
// Recursively check nested items
|
|
921
|
+
this.expandGroupsWithErrorsRecursive(item.items, errorFields);
|
|
922
|
+
}
|
|
923
|
+
else if (typeof item === 'object' && item.type === 'row') {
|
|
924
|
+
// Recursively check items in rows
|
|
925
|
+
this.expandGroupsWithErrorsRecursive(item.items, errorFields);
|
|
926
|
+
}
|
|
927
|
+
});
|
|
928
|
+
}
|
|
929
|
+
renderLayoutItem(item, config, renderedFields) {
|
|
930
|
+
if (typeof item === 'string') {
|
|
931
|
+
// String shorthand for field
|
|
932
|
+
return this.renderLayoutItem({ type: 'field', field: item }, config, renderedFields);
|
|
933
|
+
}
|
|
934
|
+
switch (item.type) {
|
|
935
|
+
case 'field':
|
|
936
|
+
if (config.form[item.field] && !renderedFields.has(item.field)) {
|
|
937
|
+
renderedFields.add(item.field);
|
|
938
|
+
return this.renderNewField(item.field, config.form[item.field], this.formData[item.field]);
|
|
939
|
+
}
|
|
940
|
+
return html ``;
|
|
941
|
+
case 'row':
|
|
942
|
+
return this.renderRow(item, config, renderedFields);
|
|
943
|
+
case 'group':
|
|
944
|
+
return this.renderGroup(item, config, renderedFields);
|
|
945
|
+
default:
|
|
946
|
+
return html ``;
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
renderRow(rowConfig, config, renderedFields) {
|
|
950
|
+
const { items, gap = '1rem' } = rowConfig;
|
|
951
|
+
// Collect all fields from this row for width calculations
|
|
952
|
+
const fieldsInRow = this.collectFieldsFromItems(items);
|
|
953
|
+
const validFields = fieldsInRow.filter((fieldName) => { var _a; return (_a = config.form) === null || _a === void 0 ? void 0 : _a[fieldName]; });
|
|
954
|
+
if (validFields.length === 0) {
|
|
955
|
+
return html ``;
|
|
956
|
+
}
|
|
957
|
+
// Calculate grid template columns based on field maxWidth constraints
|
|
958
|
+
const columns = validFields.map((fieldName) => {
|
|
959
|
+
const fieldConfig = config.form[fieldName];
|
|
960
|
+
return fieldConfig.maxWidth || '1fr';
|
|
961
|
+
});
|
|
962
|
+
return html `
|
|
963
|
+
<div
|
|
964
|
+
class="form-row"
|
|
965
|
+
style="display: grid; grid-template-columns: ${columns.join(' ')}; gap: ${gap};"
|
|
966
|
+
>
|
|
967
|
+
${items.map((item) => this.renderLayoutItem(item, config, renderedFields))}
|
|
968
|
+
</div>
|
|
969
|
+
`;
|
|
970
|
+
}
|
|
971
|
+
renderGroup(groupConfig, config, renderedFields) {
|
|
972
|
+
var _a;
|
|
973
|
+
const { label, items, collapsible = false, collapsed = false, helpText } = groupConfig;
|
|
974
|
+
// Initialize collapse state if not set
|
|
975
|
+
if (collapsible && !(label in this.groupCollapseState)) {
|
|
976
|
+
this.groupCollapseState = {
|
|
977
|
+
...this.groupCollapseState,
|
|
978
|
+
[label]: collapsed
|
|
979
|
+
};
|
|
980
|
+
}
|
|
981
|
+
const isCollapsed = collapsible
|
|
982
|
+
? (_a = this.groupCollapseState[label]) !== null && _a !== void 0 ? _a : collapsed
|
|
983
|
+
: false;
|
|
984
|
+
// Check if any field in this group has errors
|
|
985
|
+
const fieldsInGroup = this.collectFieldsFromItems(items);
|
|
986
|
+
const groupHasErrors = fieldsInGroup.some((fieldName) => this.errors[fieldName]);
|
|
987
|
+
return html `
|
|
988
|
+
<div
|
|
989
|
+
class="form-group ${collapsible ? 'collapsible' : ''} ${groupHasErrors
|
|
990
|
+
? 'has-errors'
|
|
991
|
+
: ''}"
|
|
992
|
+
>
|
|
993
|
+
<div
|
|
994
|
+
class="form-group-header ${collapsible ? 'clickable' : ''}"
|
|
995
|
+
@click=${collapsible
|
|
996
|
+
? () => this.handleGroupToggle(label)
|
|
997
|
+
: undefined}
|
|
998
|
+
>
|
|
999
|
+
<div class="form-group-info">
|
|
1000
|
+
<div class="form-group-title">${label}</div>
|
|
1001
|
+
${helpText
|
|
1002
|
+
? html `<div class="form-group-help">${helpText}</div>`
|
|
1003
|
+
: ''}
|
|
1004
|
+
</div>
|
|
1005
|
+
${groupHasErrors
|
|
1006
|
+
? html `<temba-icon
|
|
1007
|
+
name="alert_warning"
|
|
1008
|
+
class="group-error-icon"
|
|
1009
|
+
size="1.5"
|
|
1010
|
+
></temba-icon>`
|
|
1011
|
+
: ''}
|
|
1012
|
+
${collapsible && !groupHasErrors
|
|
1013
|
+
? html `<temba-icon
|
|
1014
|
+
name="arrow_right"
|
|
1015
|
+
size="1.5"
|
|
1016
|
+
class="group-toggle-icon ${isCollapsed
|
|
1017
|
+
? 'collapsed'
|
|
1018
|
+
: 'expanded'}"
|
|
1019
|
+
></temba-icon>`
|
|
1020
|
+
: ''}
|
|
1021
|
+
</div>
|
|
1022
|
+
<div
|
|
1023
|
+
class="form-group-content ${isCollapsed ? 'collapsed' : 'expanded'}"
|
|
1024
|
+
>
|
|
1025
|
+
${items.map((item) => this.renderLayoutItem(item, config, renderedFields))}
|
|
1026
|
+
</div>
|
|
1027
|
+
</div>
|
|
1028
|
+
`;
|
|
1029
|
+
}
|
|
1030
|
+
collectFieldsFromItems(items) {
|
|
1031
|
+
const fields = [];
|
|
1032
|
+
items.forEach((item) => {
|
|
1033
|
+
if (typeof item === 'string') {
|
|
1034
|
+
fields.push(item);
|
|
1035
|
+
}
|
|
1036
|
+
else if (item.type === 'field') {
|
|
1037
|
+
fields.push(item.field);
|
|
1038
|
+
}
|
|
1039
|
+
else if (item.type === 'row') {
|
|
1040
|
+
fields.push(...this.collectFieldsFromItems(item.items));
|
|
1041
|
+
}
|
|
1042
|
+
else if (item.type === 'group') {
|
|
1043
|
+
fields.push(...this.collectFieldsFromItems(item.items));
|
|
1044
|
+
}
|
|
1045
|
+
});
|
|
1046
|
+
return fields;
|
|
1047
|
+
}
|
|
1048
|
+
renderFieldRow(rowConfig, config) {
|
|
1049
|
+
// This method is deprecated - use renderRow instead
|
|
1050
|
+
return this.renderRow(rowConfig, config, new Set());
|
|
1051
|
+
}
|
|
1052
|
+
renderFieldGroup(groupConfig, config) {
|
|
1053
|
+
// This method is deprecated - use renderGroup instead
|
|
1054
|
+
return this.renderGroup(groupConfig, config, new Set());
|
|
1055
|
+
}
|
|
1056
|
+
handleNewFieldChange(fieldName, value) {
|
|
1057
|
+
this.formData = {
|
|
1058
|
+
...this.formData,
|
|
1059
|
+
[fieldName]: value
|
|
1060
|
+
};
|
|
1061
|
+
// Clear any existing error for this field
|
|
1062
|
+
if (this.errors[fieldName]) {
|
|
1063
|
+
const newErrors = { ...this.errors };
|
|
1064
|
+
delete newErrors[fieldName];
|
|
1065
|
+
this.errors = newErrors;
|
|
1066
|
+
}
|
|
1067
|
+
// Trigger re-render
|
|
1068
|
+
this.requestUpdate();
|
|
1069
|
+
}
|
|
1070
|
+
renderFields() {
|
|
1071
|
+
if (!this.action) {
|
|
1072
|
+
return html ` <div>No action selected</div> `;
|
|
1073
|
+
}
|
|
1074
|
+
const config = ACTION_CONFIG[this.action.type];
|
|
1075
|
+
if (!config) {
|
|
1076
|
+
return html ` <div>No configuration available for this action</div> `;
|
|
1077
|
+
}
|
|
1078
|
+
// Use the new fields configuration system
|
|
1079
|
+
if (config.form) {
|
|
1080
|
+
// If layout is specified, use it
|
|
1081
|
+
if (config.layout) {
|
|
1082
|
+
const renderedFields = new Set();
|
|
1083
|
+
return html `
|
|
1084
|
+
${config.layout.map((item) => this.renderLayoutItem(item, config, renderedFields))}
|
|
1085
|
+
${
|
|
1086
|
+
/* Render any fields not explicitly placed in layout */
|
|
1087
|
+
Object.entries(config.form).map(([fieldName, fieldConfig]) => {
|
|
1088
|
+
if (!renderedFields.has(fieldName)) {
|
|
1089
|
+
return this.renderNewField(fieldName, fieldConfig, this.formData[fieldName]);
|
|
1090
|
+
}
|
|
1091
|
+
return html ``;
|
|
1092
|
+
})}
|
|
1093
|
+
`;
|
|
1094
|
+
}
|
|
1095
|
+
else {
|
|
1096
|
+
// Default rendering without layout
|
|
1097
|
+
return html `
|
|
1098
|
+
${Object.entries(config.form).map(([fieldName, fieldConfig]) => this.renderNewField(fieldName, fieldConfig, this.formData[fieldName]))}
|
|
1099
|
+
`;
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
return html ` <div>No form configuration available</div> `;
|
|
1103
|
+
}
|
|
1104
|
+
renderActionSection() {
|
|
1105
|
+
if (!this.node || this.node.actions.length === 0) {
|
|
1106
|
+
return html ``;
|
|
1107
|
+
}
|
|
1108
|
+
const nodeConfig = this.getNodeConfig();
|
|
1109
|
+
// If node has an action config, defer to ActionEditor
|
|
1110
|
+
if (nodeConfig === null || nodeConfig === void 0 ? void 0 : nodeConfig.action) {
|
|
1111
|
+
const action = this.node.actions[0]; // Assume single action for now
|
|
1112
|
+
return html `
|
|
1113
|
+
<div class="action-section">
|
|
1114
|
+
<h3>Action Configuration</h3>
|
|
1115
|
+
<div class="action-preview">
|
|
1116
|
+
<p><strong>Type:</strong> ${action.type}</p>
|
|
1117
|
+
<p><em>Action details will be editable here</em></p>
|
|
1118
|
+
</div>
|
|
1119
|
+
</div>
|
|
1120
|
+
`;
|
|
1121
|
+
}
|
|
1122
|
+
return html ``;
|
|
1123
|
+
}
|
|
1124
|
+
renderRouterSection() {
|
|
1125
|
+
var _a;
|
|
1126
|
+
if (!((_a = this.node) === null || _a === void 0 ? void 0 : _a.router)) {
|
|
1127
|
+
return html ``;
|
|
1128
|
+
}
|
|
1129
|
+
const nodeConfig = this.getNodeConfig();
|
|
1130
|
+
return html `
|
|
1131
|
+
<div class="router-section">
|
|
1132
|
+
<h3>Router Configuration</h3>
|
|
1133
|
+
${(nodeConfig === null || nodeConfig === void 0 ? void 0 : nodeConfig.router)
|
|
1134
|
+
? this.renderRouterConfig()
|
|
1135
|
+
: html `<p>Basic router (no advanced configuration)</p>`}
|
|
1136
|
+
</div>
|
|
1137
|
+
`;
|
|
1138
|
+
}
|
|
1139
|
+
renderRouterConfig() {
|
|
1140
|
+
const nodeConfig = this.getNodeConfig();
|
|
1141
|
+
if (!(nodeConfig === null || nodeConfig === void 0 ? void 0 : nodeConfig.router))
|
|
1142
|
+
return html ``;
|
|
1143
|
+
// Render router configuration based on node config
|
|
1144
|
+
// This is where you'd render rule and category editors
|
|
1145
|
+
return html `
|
|
1146
|
+
<div class="router-config">
|
|
1147
|
+
<p><strong>Type:</strong> ${nodeConfig.router.type}</p>
|
|
1148
|
+
${nodeConfig.router.rules
|
|
1149
|
+
? html `
|
|
1150
|
+
<div class="rules-section">
|
|
1151
|
+
<h4>Rules</h4>
|
|
1152
|
+
<!-- Future: Render rule editor based on nodeConfig.router.rules -->
|
|
1153
|
+
<p><em>Rule editing will be implemented here</em></p>
|
|
1154
|
+
</div>
|
|
1155
|
+
`
|
|
1156
|
+
: ''}
|
|
1157
|
+
</div>
|
|
1158
|
+
`;
|
|
1159
|
+
}
|
|
1160
|
+
render() {
|
|
1161
|
+
var _a, _b;
|
|
1162
|
+
if (!this.isOpen) {
|
|
1163
|
+
return html ``;
|
|
1164
|
+
}
|
|
1165
|
+
const headerColor = this.getHeaderColor();
|
|
1166
|
+
const nodeConfig = this.getNodeConfig();
|
|
1167
|
+
const actionConfig = ACTION_CONFIG[(_a = this.action) === null || _a === void 0 ? void 0 : _a.type];
|
|
1168
|
+
return html `
|
|
1169
|
+
<temba-dialog
|
|
1170
|
+
header="${(actionConfig === null || actionConfig === void 0 ? void 0 : actionConfig.name) || (nodeConfig === null || nodeConfig === void 0 ? void 0 : nodeConfig.name) || 'Edit'}"
|
|
1171
|
+
.open="${this.isOpen}"
|
|
1172
|
+
@temba-button-clicked=${this.handleDialogButtonClick}
|
|
1173
|
+
primaryButtonName="Save"
|
|
1174
|
+
cancelButtonName="Cancel"
|
|
1175
|
+
style="--header-bg: ${headerColor}"
|
|
1176
|
+
>
|
|
1177
|
+
<div class="node-editor-form">
|
|
1178
|
+
${this.renderFields()}
|
|
1179
|
+
${((_b = nodeConfig === null || nodeConfig === void 0 ? void 0 : nodeConfig.router) === null || _b === void 0 ? void 0 : _b.configurable)
|
|
1180
|
+
? this.renderRouterSection()
|
|
1181
|
+
: null}
|
|
1182
|
+
</div>
|
|
1183
|
+
</temba-dialog>
|
|
1184
|
+
`;
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
__decorate([
|
|
1188
|
+
property({ type: Object })
|
|
1189
|
+
], NodeEditor.prototype, "action", void 0);
|
|
1190
|
+
__decorate([
|
|
1191
|
+
property({ type: Object })
|
|
1192
|
+
], NodeEditor.prototype, "node", void 0);
|
|
1193
|
+
__decorate([
|
|
1194
|
+
property({ type: Object })
|
|
1195
|
+
], NodeEditor.prototype, "nodeUI", void 0);
|
|
1196
|
+
__decorate([
|
|
1197
|
+
property({ type: Boolean })
|
|
1198
|
+
], NodeEditor.prototype, "isOpen", void 0);
|
|
1199
|
+
__decorate([
|
|
1200
|
+
state()
|
|
1201
|
+
], NodeEditor.prototype, "formData", void 0);
|
|
1202
|
+
__decorate([
|
|
1203
|
+
state()
|
|
1204
|
+
], NodeEditor.prototype, "originalFormData", void 0);
|
|
1205
|
+
__decorate([
|
|
1206
|
+
state()
|
|
1207
|
+
], NodeEditor.prototype, "errors", void 0);
|
|
1208
|
+
__decorate([
|
|
1209
|
+
state()
|
|
1210
|
+
], NodeEditor.prototype, "groupCollapseState", void 0);
|
|
1211
|
+
//# sourceMappingURL=NodeEditor.js.map
|