@nyaruka/temba-components 0.131.1 → 0.131.2
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/.github/workflows/publish.yml +4 -1
- package/CHANGELOG.md +61 -1
- package/demo/data/flows/food-order.json +2 -2
- package/demo/data/flows/sample-flow.json +74 -125
- package/dist/static/svg/index.svg +1 -1
- package/dist/temba-components.js +1155 -618
- package/dist/temba-components.js.map +1 -1
- package/out-tsc/src/Icons.js +4 -1
- package/out-tsc/src/Icons.js.map +1 -1
- package/out-tsc/src/events.js.map +1 -1
- package/out-tsc/src/flow/CanvasMenu.js +200 -0
- package/out-tsc/src/flow/CanvasMenu.js.map +1 -0
- package/out-tsc/src/flow/CanvasNode.js +327 -19
- package/out-tsc/src/flow/CanvasNode.js.map +1 -1
- package/out-tsc/src/flow/Editor.js +562 -66
- package/out-tsc/src/flow/Editor.js.map +1 -1
- package/out-tsc/src/flow/NodeEditor.js +240 -93
- package/out-tsc/src/flow/NodeEditor.js.map +1 -1
- package/out-tsc/src/flow/NodeTypeSelector.js +499 -0
- package/out-tsc/src/flow/NodeTypeSelector.js.map +1 -0
- package/out-tsc/src/flow/actions/add_contact_groups.js +3 -3
- package/out-tsc/src/flow/actions/add_contact_groups.js.map +1 -1
- package/out-tsc/src/flow/actions/add_contact_urn.js +62 -4
- package/out-tsc/src/flow/actions/add_contact_urn.js.map +1 -1
- package/out-tsc/src/flow/actions/add_input_labels.js +3 -3
- package/out-tsc/src/flow/actions/add_input_labels.js.map +1 -1
- package/out-tsc/src/flow/actions/play_audio.js +2 -2
- package/out-tsc/src/flow/actions/play_audio.js.map +1 -1
- package/out-tsc/src/flow/actions/remove_contact_groups.js +6 -5
- package/out-tsc/src/flow/actions/remove_contact_groups.js.map +1 -1
- package/out-tsc/src/flow/actions/request_optin.js +2 -2
- package/out-tsc/src/flow/actions/request_optin.js.map +1 -1
- package/out-tsc/src/flow/actions/say_msg.js +2 -2
- package/out-tsc/src/flow/actions/say_msg.js.map +1 -1
- package/out-tsc/src/flow/actions/send_broadcast.js +76 -23
- package/out-tsc/src/flow/actions/send_broadcast.js.map +1 -1
- package/out-tsc/src/flow/actions/send_email.js +4 -5
- package/out-tsc/src/flow/actions/send_email.js.map +1 -1
- package/out-tsc/src/flow/actions/send_msg.js +9 -19
- package/out-tsc/src/flow/actions/send_msg.js.map +1 -1
- package/out-tsc/src/flow/actions/set_contact_channel.js +5 -9
- package/out-tsc/src/flow/actions/set_contact_channel.js.map +1 -1
- package/out-tsc/src/flow/actions/set_contact_field.js +19 -20
- package/out-tsc/src/flow/actions/set_contact_field.js.map +1 -1
- package/out-tsc/src/flow/actions/set_contact_language.js +2 -2
- package/out-tsc/src/flow/actions/set_contact_language.js.map +1 -1
- package/out-tsc/src/flow/actions/set_contact_name.js +2 -12
- package/out-tsc/src/flow/actions/set_contact_name.js.map +1 -1
- package/out-tsc/src/flow/actions/set_contact_status.js +2 -2
- package/out-tsc/src/flow/actions/set_contact_status.js.map +1 -1
- package/out-tsc/src/flow/actions/set_run_result.js +3 -3
- package/out-tsc/src/flow/actions/set_run_result.js.map +1 -1
- package/out-tsc/src/flow/actions/start_session.js +180 -6
- package/out-tsc/src/flow/actions/start_session.js.map +1 -1
- package/out-tsc/src/flow/config.js +11 -15
- package/out-tsc/src/flow/config.js.map +1 -1
- package/out-tsc/src/flow/currencies.js +45 -0
- package/out-tsc/src/flow/currencies.js.map +1 -0
- package/out-tsc/src/flow/nodes/shared-rules.js +257 -0
- package/out-tsc/src/flow/nodes/shared-rules.js.map +1 -0
- package/out-tsc/src/flow/nodes/shared.js +17 -0
- package/out-tsc/src/flow/nodes/shared.js.map +1 -0
- package/out-tsc/src/flow/nodes/split_by_airtime.js +205 -5
- package/out-tsc/src/flow/nodes/split_by_airtime.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_contact_field.js +147 -3
- package/out-tsc/src/flow/nodes/split_by_contact_field.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_expression.js +68 -2
- package/out-tsc/src/flow/nodes/split_by_expression.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_groups.js +12 -9
- package/out-tsc/src/flow/nodes/split_by_groups.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_intent.js +7 -0
- package/out-tsc/src/flow/nodes/split_by_intent.js.map +1 -0
- package/out-tsc/src/flow/nodes/split_by_llm.js +3 -2
- package/out-tsc/src/flow/nodes/split_by_llm.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_llm_categorize.js +2 -2
- package/out-tsc/src/flow/nodes/split_by_llm_categorize.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_random.js +3 -3
- package/out-tsc/src/flow/nodes/split_by_random.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_resthook.js +108 -0
- package/out-tsc/src/flow/nodes/split_by_resthook.js.map +1 -0
- package/out-tsc/src/flow/nodes/split_by_run_result.js +206 -3
- package/out-tsc/src/flow/nodes/split_by_run_result.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_scheme.js +153 -2
- package/out-tsc/src/flow/nodes/split_by_scheme.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_subflow.js +6 -4
- package/out-tsc/src/flow/nodes/split_by_subflow.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_ticket.js +3 -2
- package/out-tsc/src/flow/nodes/split_by_ticket.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_webhook.js +3 -2
- package/out-tsc/src/flow/nodes/split_by_webhook.js.map +1 -1
- package/out-tsc/src/flow/nodes/wait_for_audio.js +2 -2
- package/out-tsc/src/flow/nodes/wait_for_audio.js.map +1 -1
- package/out-tsc/src/flow/nodes/wait_for_digits.js +2 -2
- package/out-tsc/src/flow/nodes/wait_for_digits.js.map +1 -1
- package/out-tsc/src/flow/nodes/wait_for_image.js +2 -2
- package/out-tsc/src/flow/nodes/wait_for_image.js.map +1 -1
- package/out-tsc/src/flow/nodes/wait_for_location.js +2 -2
- package/out-tsc/src/flow/nodes/wait_for_location.js.map +1 -1
- package/out-tsc/src/flow/nodes/wait_for_menu.js +2 -2
- package/out-tsc/src/flow/nodes/wait_for_menu.js.map +1 -1
- package/out-tsc/src/flow/nodes/wait_for_response.js +32 -567
- package/out-tsc/src/flow/nodes/wait_for_response.js.map +1 -1
- package/out-tsc/src/flow/nodes/wait_for_video.js +2 -2
- package/out-tsc/src/flow/nodes/wait_for_video.js.map +1 -1
- package/out-tsc/src/flow/types.js +71 -12
- package/out-tsc/src/flow/types.js.map +1 -1
- package/out-tsc/src/flow/utils.js +101 -14
- package/out-tsc/src/flow/utils.js.map +1 -1
- package/out-tsc/src/form/FieldRenderer.js +2 -4
- package/out-tsc/src/form/FieldRenderer.js.map +1 -1
- package/out-tsc/src/interfaces.js +3 -0
- package/out-tsc/src/interfaces.js.map +1 -1
- package/out-tsc/src/list/SortableList.js +98 -33
- package/out-tsc/src/list/SortableList.js.map +1 -1
- package/out-tsc/src/live/ContactChat.js +15 -18
- package/out-tsc/src/live/ContactChat.js.map +1 -1
- package/out-tsc/src/store/AppState.js +53 -0
- package/out-tsc/src/store/AppState.js.map +1 -1
- package/out-tsc/src/utils.js +254 -13
- package/out-tsc/src/utils.js.map +1 -1
- package/out-tsc/temba-modules.js +4 -0
- package/out-tsc/temba-modules.js.map +1 -1
- package/out-tsc/test/ActionHelper.js +3 -3
- package/out-tsc/test/ActionHelper.js.map +1 -1
- package/out-tsc/test/NodeHelper.js +6 -3
- package/out-tsc/test/NodeHelper.js.map +1 -1
- package/out-tsc/test/actions/add_contact_urn.test.js +202 -0
- package/out-tsc/test/actions/add_contact_urn.test.js.map +1 -0
- package/out-tsc/test/actions/send_broadcast.test.js +148 -0
- package/out-tsc/test/actions/send_broadcast.test.js.map +1 -0
- package/out-tsc/test/actions/send_email.test.js +17 -23
- package/out-tsc/test/actions/send_email.test.js.map +1 -1
- package/out-tsc/test/actions/send_msg.test.js +33 -15
- package/out-tsc/test/actions/send_msg.test.js.map +1 -1
- package/out-tsc/test/actions/start_session.test.js +116 -0
- package/out-tsc/test/actions/start_session.test.js.map +1 -0
- package/out-tsc/test/nodes/split_by_airtime.test.js +604 -0
- package/out-tsc/test/nodes/split_by_airtime.test.js.map +1 -0
- package/out-tsc/test/nodes/split_by_contact_field.test.js +387 -0
- package/out-tsc/test/nodes/split_by_contact_field.test.js.map +1 -0
- package/out-tsc/test/nodes/split_by_expression.test.js +614 -0
- package/out-tsc/test/nodes/split_by_expression.test.js.map +1 -0
- package/out-tsc/test/nodes/split_by_random.test.js +3 -3
- package/out-tsc/test/nodes/split_by_random.test.js.map +1 -1
- package/out-tsc/test/nodes/split_by_resthook.test.js +337 -0
- package/out-tsc/test/nodes/split_by_resthook.test.js.map +1 -0
- package/out-tsc/test/nodes/split_by_run_result.test.js +920 -0
- package/out-tsc/test/nodes/split_by_run_result.test.js.map +1 -0
- package/out-tsc/test/nodes/split_by_scheme.test.js +399 -0
- package/out-tsc/test/nodes/split_by_scheme.test.js.map +1 -0
- package/out-tsc/test/nodes/split_by_subflow.test.js +333 -0
- package/out-tsc/test/nodes/split_by_subflow.test.js.map +1 -0
- package/out-tsc/test/nodes/wait_for_digits.test.js +2 -2
- package/out-tsc/test/nodes/wait_for_digits.test.js.map +1 -1
- package/out-tsc/test/nodes/wait_for_response.test.js +2 -1
- package/out-tsc/test/nodes/wait_for_response.test.js.map +1 -1
- package/out-tsc/test/temba-action-drag-between-nodes.test.js +252 -0
- package/out-tsc/test/temba-action-drag-between-nodes.test.js.map +1 -0
- package/out-tsc/test/temba-canvas-menu.test.js +122 -0
- package/out-tsc/test/temba-canvas-menu.test.js.map +1 -0
- package/out-tsc/test/temba-flow-editor-node.test.js +85 -2
- package/out-tsc/test/temba-flow-editor-node.test.js.map +1 -1
- package/out-tsc/test/temba-flow-editor.test.js +7 -8
- package/out-tsc/test/temba-flow-editor.test.js.map +1 -1
- package/out-tsc/test/temba-node-editor.test.js +3 -1
- package/out-tsc/test/temba-node-editor.test.js.map +1 -1
- package/out-tsc/test/temba-node-type-selector.test.js +115 -0
- package/out-tsc/test/temba-node-type-selector.test.js.map +1 -0
- package/out-tsc/test/temba-omnibox.test.js +2 -1
- package/out-tsc/test/temba-omnibox.test.js.map +1 -1
- package/out-tsc/test/temba-sortable-list.test.js +51 -0
- package/out-tsc/test/temba-sortable-list.test.js.map +1 -1
- package/out-tsc/test/temba-utils-index.test.js +1 -27
- package/out-tsc/test/temba-utils-index.test.js.map +1 -1
- package/out-tsc/test/utils.test.js +2 -0
- package/out-tsc/test/utils.test.js.map +1 -1
- package/package.json +2 -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/add_contact_urn/editor/expression-facebook.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/editor/expression-phone.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/editor/facebook-id.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/editor/instagram-handle.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/editor/line-id.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/editor/phone-number.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/editor/telegram-id.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/editor/viber-id.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/editor/wechat-id.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/editor/whatsapp.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/expression-facebook.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/expression-phone.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/facebook-id.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/instagram-handle.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/line-id.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/phone-number.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/telegram-id.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/viber-id.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/wechat-id.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/whatsapp.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_broadcast/editor/contacts-only.png +0 -0
- package/screenshots/truth/actions/send_broadcast/editor/groups-and-contacts.png +0 -0
- package/screenshots/truth/actions/send_broadcast/editor/groups-only.png +0 -0
- package/screenshots/truth/actions/send_broadcast/editor/many-groups.png +0 -0
- package/screenshots/truth/actions/send_broadcast/editor/multiline-text.png +0 -0
- package/screenshots/truth/actions/send_broadcast/editor/with-attachments.png +0 -0
- package/screenshots/truth/actions/send_broadcast/render/contacts-only.png +0 -0
- package/screenshots/truth/actions/send_broadcast/render/groups-and-contacts.png +0 -0
- package/screenshots/truth/actions/send_broadcast/render/groups-only.png +0 -0
- package/screenshots/truth/actions/send_broadcast/render/many-groups.png +0 -0
- package/screenshots/truth/actions/send_broadcast/render/multiline-text.png +0 -0
- package/screenshots/truth/actions/send_broadcast/render/with-attachments.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/actions/start_session/editor/contact-query.png +0 -0
- package/screenshots/truth/actions/start_session/editor/contacts-only.png +0 -0
- package/screenshots/truth/actions/start_session/editor/create-contact.png +0 -0
- package/screenshots/truth/actions/start_session/editor/groups-and-contacts.png +0 -0
- package/screenshots/truth/actions/start_session/editor/groups-only.png +0 -0
- package/screenshots/truth/actions/start_session/editor/many-recipients.png +0 -0
- package/screenshots/truth/actions/start_session/render/contact-query.png +0 -0
- package/screenshots/truth/actions/start_session/render/contacts-only.png +0 -0
- package/screenshots/truth/actions/start_session/render/create-contact.png +0 -0
- package/screenshots/truth/actions/start_session/render/groups-and-contacts.png +0 -0
- package/screenshots/truth/actions/start_session/render/groups-only.png +0 -0
- package/screenshots/truth/actions/start_session/render/many-recipients.png +0 -0
- package/screenshots/truth/canvas-menu/open.png +0 -0
- package/screenshots/truth/editor/router.png +0 -0
- package/screenshots/truth/editor/wait.png +0 -0
- package/screenshots/truth/list/fields-dragging.png +0 -0
- package/screenshots/truth/list/sortable-dragging.png +0 -0
- package/screenshots/truth/node-type-selector/action-mode.png +0 -0
- package/screenshots/truth/node-type-selector/split-mode.png +0 -0
- package/screenshots/truth/nodes/split_by_llm/editor/information-extraction.png +0 -0
- package/screenshots/truth/nodes/split_by_llm/editor/sentiment-analysis.png +0 -0
- package/screenshots/truth/nodes/split_by_llm/editor/summarization.png +0 -0
- package/screenshots/truth/nodes/split_by_llm/editor/translation-task.png +0 -0
- package/screenshots/truth/nodes/split_by_llm/render/information-extraction.png +0 -0
- package/screenshots/truth/nodes/split_by_llm/render/sentiment-analysis.png +0 -0
- package/screenshots/truth/nodes/split_by_llm/render/summarization.png +0 -0
- package/screenshots/truth/nodes/split_by_llm/render/translation-task.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/editor/basic-categorization.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/editor/custom-input-and-result-name.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/editor/feedback-categorization.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/editor/many-categories.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/editor/minimal-categories.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/render/basic-categorization.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/render/custom-input-and-result-name.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/render/feedback-categorization.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/render/many-categories.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/render/minimal-categories.png +0 -0
- package/screenshots/truth/nodes/split_by_random/editor/ab-test-multiple-variants.png +0 -0
- package/screenshots/truth/nodes/split_by_random/editor/sampling-split.png +0 -0
- package/screenshots/truth/nodes/split_by_random/editor/three-way-split.png +0 -0
- package/screenshots/truth/nodes/split_by_random/editor/two-bucket-split.png +0 -0
- package/screenshots/truth/nodes/split_by_random/render/ab-test-multiple-variants.png +0 -0
- package/screenshots/truth/nodes/split_by_random/render/sampling-split.png +0 -0
- package/screenshots/truth/nodes/split_by_random/render/three-way-split.png +0 -0
- package/screenshots/truth/nodes/split_by_random/render/two-bucket-split.png +0 -0
- package/screenshots/truth/nodes/wait_for_digits/editor/basic-digits-wait.png +0 -0
- package/screenshots/truth/nodes/wait_for_digits/editor/phone-number-collection.png +0 -0
- package/screenshots/truth/nodes/wait_for_digits/editor/single-digit-with-timeout.png +0 -0
- package/screenshots/truth/nodes/wait_for_digits/editor/verification-code.png +0 -0
- package/screenshots/truth/nodes/wait_for_digits/render/basic-digits-wait.png +0 -0
- package/screenshots/truth/nodes/wait_for_digits/render/phone-number-collection.png +0 -0
- package/screenshots/truth/nodes/wait_for_digits/render/single-digit-with-timeout.png +0 -0
- package/screenshots/truth/nodes/wait_for_digits/render/verification-code.png +0 -0
- package/screenshots/truth/nodes/wait_for_response/editor/basic-wait.png +0 -0
- package/screenshots/truth/nodes/wait_for_response/editor/custom-result-name.png +0 -0
- package/screenshots/truth/nodes/wait_for_response/editor/no-timeout.png +0 -0
- package/screenshots/truth/nodes/wait_for_response/editor/short-timeout.png +0 -0
- package/screenshots/truth/nodes/wait_for_response/render/basic-wait.png +0 -0
- package/screenshots/truth/nodes/wait_for_response/render/custom-result-name.png +0 -0
- package/screenshots/truth/nodes/wait_for_response/render/no-timeout.png +0 -0
- package/screenshots/truth/nodes/wait_for_response/render/short-timeout.png +0 -0
- package/src/Icons.ts +4 -1
- package/src/events.ts +2 -6
- package/src/flow/CanvasMenu.ts +217 -0
- package/src/flow/CanvasNode.ts +408 -10
- package/src/flow/Editor.ts +683 -44
- package/src/flow/NodeEditor.ts +304 -125
- package/src/flow/NodeTypeSelector.ts +592 -0
- package/src/flow/actions/add_contact_groups.ts +4 -4
- package/src/flow/actions/add_contact_urn.ts +76 -4
- package/src/flow/actions/add_input_labels.ts +4 -4
- package/src/flow/actions/play_audio.ts +2 -2
- package/src/flow/actions/remove_contact_groups.ts +14 -6
- package/src/flow/actions/request_optin.ts +2 -2
- package/src/flow/actions/say_msg.ts +2 -2
- package/src/flow/actions/send_broadcast.ts +85 -23
- package/src/flow/actions/send_email.ts +10 -6
- package/src/flow/actions/send_msg.ts +22 -32
- package/src/flow/actions/set_contact_channel.ts +5 -11
- package/src/flow/actions/set_contact_field.ts +20 -25
- package/src/flow/actions/set_contact_language.ts +9 -4
- package/src/flow/actions/set_contact_name.ts +3 -15
- package/src/flow/actions/set_contact_status.ts +3 -3
- package/src/flow/actions/set_run_result.ts +4 -4
- package/src/flow/actions/start_session.ts +208 -6
- package/src/flow/config.ts +13 -15
- package/src/flow/currencies.ts +51 -0
- package/src/flow/nodes/shared-rules.ts +301 -0
- package/src/flow/nodes/shared.ts +18 -0
- package/src/flow/nodes/split_by_airtime.ts +238 -5
- package/src/flow/nodes/split_by_contact_field.ts +185 -3
- package/src/flow/nodes/split_by_expression.ts +94 -2
- package/src/flow/nodes/split_by_groups.ts +15 -10
- package/src/flow/nodes/split_by_intent.ts +7 -0
- package/src/flow/nodes/split_by_llm.ts +4 -3
- package/src/flow/nodes/split_by_llm_categorize.ts +4 -4
- package/src/flow/nodes/split_by_random.ts +5 -5
- package/src/flow/nodes/split_by_resthook.ts +130 -0
- package/src/flow/nodes/split_by_run_result.ts +249 -3
- package/src/flow/nodes/split_by_scheme.ts +192 -2
- package/src/flow/nodes/split_by_subflow.ts +6 -4
- package/src/flow/nodes/split_by_ticket.ts +4 -3
- package/src/flow/nodes/split_by_webhook.ts +6 -5
- package/src/flow/nodes/wait_for_audio.ts +2 -2
- package/src/flow/nodes/wait_for_digits.ts +2 -2
- package/src/flow/nodes/wait_for_image.ts +2 -2
- package/src/flow/nodes/wait_for_location.ts +2 -2
- package/src/flow/nodes/wait_for_menu.ts +2 -2
- package/src/flow/nodes/wait_for_response.ts +48 -679
- package/src/flow/nodes/wait_for_video.ts +2 -2
- package/src/flow/types.ts +109 -23
- package/src/flow/utils.ts +108 -14
- package/src/form/FieldRenderer.ts +2 -4
- package/src/interfaces.ts +3 -0
- package/src/list/SortableList.ts +109 -34
- package/src/live/ContactChat.ts +15 -18
- package/src/store/AppState.ts +69 -0
- package/src/store/flow-definition.d.ts +2 -5
- package/src/utils.ts +332 -12
- package/static/api/channels.json +46 -0
- package/static/api/resthooks.json +31 -0
- package/static/svg/index.svg +1 -1
- package/static/svg/work/traced/lightning-02.svg +1 -0
- package/static/svg/work/used/lightning-02.svg +3 -0
- package/temba-modules.ts +4 -0
- package/test/ActionHelper.ts +3 -3
- package/test/NodeHelper.ts +6 -3
- package/test/actions/add_contact_urn.test.ts +287 -0
- package/test/actions/send_broadcast.test.ts +190 -0
- package/test/actions/send_email.test.ts +17 -23
- package/test/actions/send_msg.test.ts +39 -15
- package/test/actions/start_session.test.ts +151 -0
- package/test/nodes/split_by_airtime.test.ts +673 -0
- package/test/nodes/split_by_contact_field.test.ts +451 -0
- package/test/nodes/split_by_expression.test.ts +751 -0
- package/test/nodes/split_by_random.test.ts +3 -3
- package/test/nodes/split_by_resthook.test.ts +398 -0
- package/test/nodes/split_by_run_result.test.ts +1109 -0
- package/test/nodes/split_by_scheme.test.ts +486 -0
- package/test/nodes/split_by_subflow.test.ts +381 -0
- package/test/nodes/wait_for_digits.test.ts +2 -2
- package/test/nodes/wait_for_response.test.ts +2 -1
- package/test/temba-action-drag-between-nodes.test.ts +301 -0
- package/test/temba-canvas-menu.test.ts +156 -0
- package/test/temba-flow-editor-node.test.ts +102 -2
- package/test/temba-flow-editor.test.ts +7 -8
- package/test/temba-node-editor.test.ts +3 -1
- package/test/temba-node-type-selector.test.ts +152 -0
- package/test/temba-omnibox.test.ts +2 -1
- package/test/temba-sortable-list.test.ts +69 -0
- package/test/temba-utils-index.test.ts +0 -35
- package/test/utils.test.ts +2 -0
- package/test-assets/contacts/history.json +14 -20
- package/web-dev-server.config.mjs +3 -1
- package/out-tsc/src/flow/actions/call_classifier.js +0 -11
- package/out-tsc/src/flow/actions/call_classifier.js.map +0 -1
- package/out-tsc/src/flow/actions/call_resthook.js +0 -11
- package/out-tsc/src/flow/actions/call_resthook.js.map +0 -1
- package/out-tsc/src/flow/actions/split_by_expression_example.js +0 -77
- package/out-tsc/src/flow/actions/split_by_expression_example.js.map +0 -1
- package/out-tsc/src/flow/actions/transfer_airtime.js +0 -11
- package/out-tsc/src/flow/actions/transfer_airtime.js.map +0 -1
- package/src/flow/actions/call_classifier.ts +0 -12
- package/src/flow/actions/call_resthook.ts +0 -12
- package/src/flow/actions/split_by_expression_example.ts +0 -88
- package/src/flow/actions/transfer_airtime.ts +0 -12
package/src/flow/Editor.ts
CHANGED
|
@@ -13,11 +13,16 @@ import { AppState, fromStore, zustand } from '../store/AppState';
|
|
|
13
13
|
import { RapidElement } from '../RapidElement';
|
|
14
14
|
import { repeat } from 'lit-html/directives/repeat.js';
|
|
15
15
|
import { CustomEventType } from '../interfaces';
|
|
16
|
+
import { generateUUID } from '../utils';
|
|
17
|
+
import { ACTION_CONFIG, NODE_CONFIG } from './config';
|
|
18
|
+
import { ACTION_GROUP_METADATA } from './types';
|
|
16
19
|
|
|
17
20
|
import { Plumber } from './Plumber';
|
|
18
21
|
import { CanvasNode } from './CanvasNode';
|
|
19
22
|
import { Dialog } from '../layout/Dialog';
|
|
20
23
|
import { Connection } from '@jsplumb/browser-ui';
|
|
24
|
+
import { CanvasMenu, CanvasMenuSelection } from './CanvasMenu';
|
|
25
|
+
import { NodeTypeSelector, NodeTypeSelection } from './NodeTypeSelector';
|
|
21
26
|
|
|
22
27
|
export function snapToGrid(value: number): number {
|
|
23
28
|
const snapped = Math.round(value / 20) * 20;
|
|
@@ -55,6 +60,11 @@ export interface SelectionBox {
|
|
|
55
60
|
|
|
56
61
|
const DRAG_THRESHOLD = 5;
|
|
57
62
|
|
|
63
|
+
// Offset for positioning dropped action node relative to mouse cursor
|
|
64
|
+
// Keep small to make drop location close to cursor position
|
|
65
|
+
const DROP_PREVIEW_OFFSET_X = 20;
|
|
66
|
+
const DROP_PREVIEW_OFFSET_Y = 20;
|
|
67
|
+
|
|
58
68
|
export class Editor extends RapidElement {
|
|
59
69
|
// unfortunately, jsplumb requires that we be in light DOM
|
|
60
70
|
createRenderRoot() {
|
|
@@ -129,6 +139,31 @@ export class Editor extends RapidElement {
|
|
|
129
139
|
@state()
|
|
130
140
|
private editingAction: Action | null = null;
|
|
131
141
|
|
|
142
|
+
@state()
|
|
143
|
+
private isCreatingNewNode = false;
|
|
144
|
+
|
|
145
|
+
@state()
|
|
146
|
+
private pendingNodePosition: FlowPosition | null = null;
|
|
147
|
+
|
|
148
|
+
// Canvas drop state for dragging actions to canvas
|
|
149
|
+
@state()
|
|
150
|
+
private canvasDropPreview: {
|
|
151
|
+
action: Action;
|
|
152
|
+
nodeUuid: string;
|
|
153
|
+
actionIndex: number;
|
|
154
|
+
position: FlowPosition;
|
|
155
|
+
actionHeight: number;
|
|
156
|
+
} | null = null;
|
|
157
|
+
@state()
|
|
158
|
+
private addActionToNodeUuid: string | null = null;
|
|
159
|
+
|
|
160
|
+
// Track target node for action drag
|
|
161
|
+
@state()
|
|
162
|
+
private actionDragTargetNodeUuid: string | null = null;
|
|
163
|
+
|
|
164
|
+
// Track previous target node to clear placeholder when moving between nodes
|
|
165
|
+
private previousActionDragTargetNodeUuid: string | null = null;
|
|
166
|
+
|
|
132
167
|
private canvasMouseDown = false;
|
|
133
168
|
|
|
134
169
|
// Bound event handlers to maintain proper 'this' context
|
|
@@ -136,7 +171,7 @@ export class Editor extends RapidElement {
|
|
|
136
171
|
private boundMouseUp = this.handleMouseUp.bind(this);
|
|
137
172
|
private boundGlobalMouseDown = this.handleGlobalMouseDown.bind(this);
|
|
138
173
|
private boundKeyDown = this.handleKeyDown.bind(this);
|
|
139
|
-
private
|
|
174
|
+
private boundCanvasContextMenu = this.handleCanvasContextMenu.bind(this);
|
|
140
175
|
|
|
141
176
|
static get styles() {
|
|
142
177
|
return css`
|
|
@@ -412,7 +447,7 @@ export class Editor extends RapidElement {
|
|
|
412
447
|
|
|
413
448
|
const canvas = this.querySelector('#canvas');
|
|
414
449
|
if (canvas) {
|
|
415
|
-
canvas.removeEventListener('
|
|
450
|
+
canvas.removeEventListener('contextmenu', this.boundCanvasContextMenu);
|
|
416
451
|
}
|
|
417
452
|
}
|
|
418
453
|
|
|
@@ -424,7 +459,7 @@ export class Editor extends RapidElement {
|
|
|
424
459
|
|
|
425
460
|
const canvas = this.querySelector('#canvas');
|
|
426
461
|
if (canvas) {
|
|
427
|
-
canvas.addEventListener('
|
|
462
|
+
canvas.addEventListener('contextmenu', this.boundCanvasContextMenu);
|
|
428
463
|
}
|
|
429
464
|
|
|
430
465
|
// Listen for action edit requests from flow nodes
|
|
@@ -433,11 +468,44 @@ export class Editor extends RapidElement {
|
|
|
433
468
|
this.handleActionEditRequested.bind(this)
|
|
434
469
|
);
|
|
435
470
|
|
|
471
|
+
// Listen for add action requests from flow nodes
|
|
472
|
+
this.addEventListener(
|
|
473
|
+
CustomEventType.AddActionRequested,
|
|
474
|
+
this.handleAddActionRequested.bind(this)
|
|
475
|
+
);
|
|
476
|
+
|
|
436
477
|
// Listen for node edit requests from flow nodes
|
|
437
478
|
this.addEventListener(
|
|
438
479
|
CustomEventType.NodeEditRequested,
|
|
439
480
|
this.handleNodeEditRequested.bind(this)
|
|
440
481
|
);
|
|
482
|
+
|
|
483
|
+
// Listen for canvas menu selections
|
|
484
|
+
this.addEventListener(CustomEventType.Selection, (event: CustomEvent) => {
|
|
485
|
+
const target = event.target as HTMLElement;
|
|
486
|
+
if (target.tagName === 'TEMBA-CANVAS-MENU') {
|
|
487
|
+
this.handleCanvasMenuSelection(event);
|
|
488
|
+
} else if (target.tagName === 'TEMBA-NODE-TYPE-SELECTOR') {
|
|
489
|
+
this.handleNodeTypeSelection(event);
|
|
490
|
+
}
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
// Listen for action drag events from nodes
|
|
494
|
+
this.addEventListener(
|
|
495
|
+
CustomEventType.DragExternal,
|
|
496
|
+
this.handleActionDragExternal.bind(this)
|
|
497
|
+
);
|
|
498
|
+
|
|
499
|
+
this.addEventListener(
|
|
500
|
+
CustomEventType.DragInternal,
|
|
501
|
+
this.handleActionDragInternal.bind(this)
|
|
502
|
+
);
|
|
503
|
+
|
|
504
|
+
this.addEventListener(CustomEventType.DragStop, (event: CustomEvent) => {
|
|
505
|
+
if (event.detail.isExternal) {
|
|
506
|
+
this.handleActionDropExternal(event);
|
|
507
|
+
}
|
|
508
|
+
});
|
|
441
509
|
}
|
|
442
510
|
|
|
443
511
|
private getPosition(uuid: string, type: 'node' | 'sticky'): FlowPosition {
|
|
@@ -730,6 +798,50 @@ export class Editor extends RapidElement {
|
|
|
730
798
|
></div>`;
|
|
731
799
|
}
|
|
732
800
|
|
|
801
|
+
private renderCanvasDropPreview(): TemplateResult | string {
|
|
802
|
+
if (!this.canvasDropPreview) return '';
|
|
803
|
+
|
|
804
|
+
const { action, position } = this.canvasDropPreview;
|
|
805
|
+
const actionConfig = ACTION_CONFIG[action.type];
|
|
806
|
+
|
|
807
|
+
if (!actionConfig) return '';
|
|
808
|
+
|
|
809
|
+
return html`<div
|
|
810
|
+
class="canvas-drop-preview"
|
|
811
|
+
style="position: absolute; left: ${position.left}px; top: ${position.top}px; opacity: 0.6; pointer-events: none; z-index: 10000;"
|
|
812
|
+
>
|
|
813
|
+
<div
|
|
814
|
+
class="node execute-actions"
|
|
815
|
+
style="outline: 3px dashed var(--color-primary, #3b82f6); outline-offset: 2px; border-radius: var(--curvature);"
|
|
816
|
+
>
|
|
817
|
+
<div class="action sortable ${action.type}">
|
|
818
|
+
<div class="action-content">
|
|
819
|
+
<div
|
|
820
|
+
class="cn-title"
|
|
821
|
+
style="background: ${actionConfig.group
|
|
822
|
+
? ACTION_GROUP_METADATA[actionConfig.group]?.color
|
|
823
|
+
: '#aaaaaa'}"
|
|
824
|
+
>
|
|
825
|
+
<div class="title-spacer"></div>
|
|
826
|
+
<div class="name">${actionConfig.name}</div>
|
|
827
|
+
<div class="title-spacer"></div>
|
|
828
|
+
</div>
|
|
829
|
+
<div class="body">
|
|
830
|
+
${actionConfig.render
|
|
831
|
+
? actionConfig.render({ actions: [action] } as any, action)
|
|
832
|
+
: html`<pre>${action.type}</pre>`}
|
|
833
|
+
</div>
|
|
834
|
+
</div>
|
|
835
|
+
</div>
|
|
836
|
+
<div class="action-exits">
|
|
837
|
+
<div class="exit-wrapper">
|
|
838
|
+
<div class="exit"></div>
|
|
839
|
+
</div>
|
|
840
|
+
</div>
|
|
841
|
+
</div>
|
|
842
|
+
</div>`;
|
|
843
|
+
}
|
|
844
|
+
|
|
733
845
|
private handleMouseMove(event: MouseEvent): void {
|
|
734
846
|
// Handle selection box drawing
|
|
735
847
|
if (this.canvasMouseDown && !this.isMouseDown) {
|
|
@@ -943,13 +1055,17 @@ export class Editor extends RapidElement {
|
|
|
943
1055
|
store.getState().expandCanvas(maxWidth, maxHeight);
|
|
944
1056
|
}
|
|
945
1057
|
|
|
946
|
-
private
|
|
947
|
-
// Check if we
|
|
1058
|
+
private handleCanvasContextMenu(event: MouseEvent): void {
|
|
1059
|
+
// Check if we right-clicked on empty canvas space
|
|
948
1060
|
const target = event.target as HTMLElement;
|
|
949
1061
|
if (target.id !== 'canvas') {
|
|
950
1062
|
return;
|
|
951
1063
|
}
|
|
952
1064
|
|
|
1065
|
+
// Prevent the default browser context menu
|
|
1066
|
+
event.preventDefault();
|
|
1067
|
+
event.stopPropagation();
|
|
1068
|
+
|
|
953
1069
|
// Get canvas position
|
|
954
1070
|
const canvas = this.querySelector('#canvas');
|
|
955
1071
|
if (!canvas) {
|
|
@@ -964,15 +1080,141 @@ export class Editor extends RapidElement {
|
|
|
964
1080
|
const snappedLeft = snapToGrid(relativeX);
|
|
965
1081
|
const snappedTop = snapToGrid(relativeY);
|
|
966
1082
|
|
|
967
|
-
//
|
|
1083
|
+
// Show the canvas menu at the mouse position (use viewport coordinates)
|
|
1084
|
+
const canvasMenu = this.querySelector('temba-canvas-menu') as CanvasMenu;
|
|
1085
|
+
if (canvasMenu) {
|
|
1086
|
+
canvasMenu.show(event.clientX, event.clientY, {
|
|
1087
|
+
x: snappedLeft,
|
|
1088
|
+
y: snappedTop
|
|
1089
|
+
});
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
private handleCanvasMenuSelection(event: CustomEvent): void {
|
|
1094
|
+
const selection = event.detail as CanvasMenuSelection;
|
|
968
1095
|
const store = getStore();
|
|
969
|
-
store.getState().createStickyNote({
|
|
970
|
-
left: snappedLeft,
|
|
971
|
-
top: snappedTop
|
|
972
|
-
});
|
|
973
1096
|
|
|
974
|
-
|
|
975
|
-
|
|
1097
|
+
if (selection.action === 'sticky') {
|
|
1098
|
+
// Create new sticky note
|
|
1099
|
+
store.getState().createStickyNote({
|
|
1100
|
+
left: selection.position.x,
|
|
1101
|
+
top: selection.position.y
|
|
1102
|
+
});
|
|
1103
|
+
} else {
|
|
1104
|
+
// Show node type selector
|
|
1105
|
+
const selector = this.querySelector(
|
|
1106
|
+
'temba-node-type-selector'
|
|
1107
|
+
) as NodeTypeSelector;
|
|
1108
|
+
if (selector) {
|
|
1109
|
+
selector.show(selection.action, selection.position);
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
private handleNodeTypeSelection(event: CustomEvent): void {
|
|
1115
|
+
const selection = event.detail as NodeTypeSelection;
|
|
1116
|
+
|
|
1117
|
+
// Check if we're adding an action to an existing node
|
|
1118
|
+
if (this.addActionToNodeUuid) {
|
|
1119
|
+
// Find the existing node
|
|
1120
|
+
const node = this.definition.nodes.find(
|
|
1121
|
+
(n) => n.uuid === this.addActionToNodeUuid
|
|
1122
|
+
);
|
|
1123
|
+
const nodeUI = this.definition._ui.nodes[this.addActionToNodeUuid];
|
|
1124
|
+
|
|
1125
|
+
if (node && nodeUI) {
|
|
1126
|
+
// Create a new action to add to the existing node
|
|
1127
|
+
const actionUuid = generateUUID();
|
|
1128
|
+
this.editingAction = {
|
|
1129
|
+
uuid: actionUuid,
|
|
1130
|
+
type: selection.nodeType as any
|
|
1131
|
+
} as Action;
|
|
1132
|
+
|
|
1133
|
+
// Set the editing node to the existing node (not creating new)
|
|
1134
|
+
this.editingNode = node;
|
|
1135
|
+
this.editingNodeUI = nodeUI;
|
|
1136
|
+
this.isCreatingNewNode = false;
|
|
1137
|
+
|
|
1138
|
+
// Clear the addActionToNodeUuid flag
|
|
1139
|
+
this.addActionToNodeUuid = null;
|
|
1140
|
+
|
|
1141
|
+
return;
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
// If we couldn't find the node, clear the flag and continue with normal flow
|
|
1145
|
+
this.addActionToNodeUuid = null;
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
// Create a temporary node structure for editing (not added to store yet)
|
|
1149
|
+
const nodeUuid = generateUUID();
|
|
1150
|
+
|
|
1151
|
+
// Determine if this is an action type or a node type
|
|
1152
|
+
// Actions need to be wrapped in an execute_actions node
|
|
1153
|
+
const isActionType = selection.nodeType in ACTION_CONFIG;
|
|
1154
|
+
const nodeType = isActionType ? 'execute_actions' : selection.nodeType;
|
|
1155
|
+
|
|
1156
|
+
// For nodes with routers, initialize an empty router to ensure fromFormData works correctly
|
|
1157
|
+
const nodeConfig = NODE_CONFIG[nodeType];
|
|
1158
|
+
const hasRouter =
|
|
1159
|
+
nodeConfig?.form &&
|
|
1160
|
+
Object.keys(nodeConfig.form).some(
|
|
1161
|
+
(key) =>
|
|
1162
|
+
['rules', 'categories', 'cases'].includes(key) ||
|
|
1163
|
+
nodeConfig.form[key]?.type === 'array'
|
|
1164
|
+
);
|
|
1165
|
+
|
|
1166
|
+
const tempNode: Node = {
|
|
1167
|
+
uuid: nodeUuid,
|
|
1168
|
+
actions: [],
|
|
1169
|
+
exits: hasRouter
|
|
1170
|
+
? [] // Router-based nodes will generate their own exits
|
|
1171
|
+
: [
|
|
1172
|
+
{
|
|
1173
|
+
uuid: generateUUID(),
|
|
1174
|
+
destination_uuid: null
|
|
1175
|
+
}
|
|
1176
|
+
]
|
|
1177
|
+
};
|
|
1178
|
+
|
|
1179
|
+
if (hasRouter) {
|
|
1180
|
+
// This node uses a router - initialize it with empty structure
|
|
1181
|
+
tempNode.router = {
|
|
1182
|
+
type: 'switch',
|
|
1183
|
+
categories: [],
|
|
1184
|
+
cases: [],
|
|
1185
|
+
operand: '@input.text',
|
|
1186
|
+
default_category_uuid: undefined
|
|
1187
|
+
};
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
const tempNodeUI: NodeUI = {
|
|
1191
|
+
position: {
|
|
1192
|
+
left: selection.position.x,
|
|
1193
|
+
top: selection.position.y
|
|
1194
|
+
},
|
|
1195
|
+
type: nodeType as any,
|
|
1196
|
+
config: {}
|
|
1197
|
+
};
|
|
1198
|
+
|
|
1199
|
+
// Mark that we're creating a new node and store the position
|
|
1200
|
+
this.isCreatingNewNode = true;
|
|
1201
|
+
this.pendingNodePosition = {
|
|
1202
|
+
left: selection.position.x,
|
|
1203
|
+
top: selection.position.y
|
|
1204
|
+
};
|
|
1205
|
+
|
|
1206
|
+
// Open the node editor with the temporary node
|
|
1207
|
+
this.editingNode = tempNode;
|
|
1208
|
+
this.editingNodeUI = tempNodeUI;
|
|
1209
|
+
|
|
1210
|
+
// If this is an action type, we also need to set up an editing action
|
|
1211
|
+
if (isActionType) {
|
|
1212
|
+
const actionUuid = generateUUID();
|
|
1213
|
+
this.editingAction = {
|
|
1214
|
+
uuid: actionUuid,
|
|
1215
|
+
type: selection.nodeType as any
|
|
1216
|
+
} as Action;
|
|
1217
|
+
}
|
|
976
1218
|
}
|
|
977
1219
|
|
|
978
1220
|
private handleActionEditRequested(event: CustomEvent): void {
|
|
@@ -988,6 +1230,38 @@ export class Editor extends RapidElement {
|
|
|
988
1230
|
this.editingNodeUI = this.definition._ui.nodes[nodeUuid];
|
|
989
1231
|
}
|
|
990
1232
|
}
|
|
1233
|
+
|
|
1234
|
+
private handleAddActionRequested(event: CustomEvent): void {
|
|
1235
|
+
// Get the node where we want to add the action
|
|
1236
|
+
const nodeUuid = event.detail.nodeUuid;
|
|
1237
|
+
const node = this.definition.nodes.find((n) => n.uuid === nodeUuid);
|
|
1238
|
+
|
|
1239
|
+
if (!node) {
|
|
1240
|
+
return;
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
// Get the node's position to place the selector near it
|
|
1244
|
+
const nodeUI = this.definition._ui.nodes[nodeUuid];
|
|
1245
|
+
if (!nodeUI) {
|
|
1246
|
+
return;
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
// Show the node type selector in action mode, excluding branching actions
|
|
1250
|
+
const selector = this.querySelector(
|
|
1251
|
+
'temba-node-type-selector'
|
|
1252
|
+
) as NodeTypeSelector;
|
|
1253
|
+
if (selector) {
|
|
1254
|
+
// Show the selector near the node, using a mode that excludes branching actions
|
|
1255
|
+
selector.show('action-no-branching', {
|
|
1256
|
+
x: nodeUI.position.left,
|
|
1257
|
+
y: nodeUI.position.top
|
|
1258
|
+
});
|
|
1259
|
+
|
|
1260
|
+
// Store the node UUID so we know which node to add the action to
|
|
1261
|
+
this.addActionToNodeUuid = nodeUuid;
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
|
|
991
1265
|
private handleNodeEditRequested(event: CustomEvent): void {
|
|
992
1266
|
this.editingNode = event.detail.node;
|
|
993
1267
|
this.editingNodeUI = event.detail.nodeUI;
|
|
@@ -995,21 +1269,60 @@ export class Editor extends RapidElement {
|
|
|
995
1269
|
|
|
996
1270
|
private handleActionSaved(updatedAction: Action): void {
|
|
997
1271
|
if (this.editingNode && this.editingAction) {
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1272
|
+
let updatedActions: Action[];
|
|
1273
|
+
|
|
1274
|
+
// Check if this action already exists in the node
|
|
1275
|
+
const existingActionIndex = this.editingNode.actions.findIndex(
|
|
1276
|
+
(action) => action.uuid === this.editingAction.uuid
|
|
1001
1277
|
);
|
|
1278
|
+
|
|
1279
|
+
if (existingActionIndex >= 0) {
|
|
1280
|
+
// Update existing action
|
|
1281
|
+
updatedActions = this.editingNode.actions.map((action) =>
|
|
1282
|
+
action.uuid === this.editingAction.uuid ? updatedAction : action
|
|
1283
|
+
);
|
|
1284
|
+
} else {
|
|
1285
|
+
// Add new action
|
|
1286
|
+
updatedActions = [...this.editingNode.actions, updatedAction];
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1002
1289
|
const updatedNode = { ...this.editingNode, actions: updatedActions };
|
|
1003
1290
|
|
|
1004
|
-
//
|
|
1005
|
-
|
|
1291
|
+
// Check if we're creating a new node or updating an existing one
|
|
1292
|
+
if (this.isCreatingNewNode) {
|
|
1293
|
+
// This is a new node with a new action - add it to the store
|
|
1294
|
+
const store = getStore();
|
|
1006
1295
|
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1296
|
+
const nodeUI: NodeUI = {
|
|
1297
|
+
position: this.pendingNodePosition || { left: 0, top: 0 },
|
|
1298
|
+
type: this.editingNodeUI?.type,
|
|
1299
|
+
config: {}
|
|
1300
|
+
};
|
|
1301
|
+
|
|
1302
|
+
// Add the node to the store
|
|
1303
|
+
store.getState().addNode(updatedNode, nodeUI);
|
|
1304
|
+
|
|
1305
|
+
// Reset the creation flags
|
|
1306
|
+
this.isCreatingNewNode = false;
|
|
1307
|
+
this.pendingNodePosition = null;
|
|
1308
|
+
|
|
1309
|
+
// Repaint jsplumb connections
|
|
1310
|
+
if (this.plumber) {
|
|
1311
|
+
requestAnimationFrame(() => {
|
|
1312
|
+
this.plumber.repaintEverything();
|
|
1313
|
+
});
|
|
1314
|
+
}
|
|
1315
|
+
} else {
|
|
1316
|
+
// Update existing node in the store
|
|
1317
|
+
getStore()?.getState().updateNode(this.editingNode.uuid, updatedNode);
|
|
1318
|
+
|
|
1319
|
+
// Repaint jsplumb connections in case node size changed
|
|
1320
|
+
if (this.plumber) {
|
|
1321
|
+
// Use requestAnimationFrame to ensure DOM has been updated first
|
|
1322
|
+
requestAnimationFrame(() => {
|
|
1323
|
+
this.plumber.repaintEverything();
|
|
1324
|
+
});
|
|
1325
|
+
}
|
|
1013
1326
|
}
|
|
1014
1327
|
}
|
|
1015
1328
|
this.closeNodeEditor();
|
|
@@ -1022,32 +1335,64 @@ export class Editor extends RapidElement {
|
|
|
1022
1335
|
}
|
|
1023
1336
|
|
|
1024
1337
|
private handleActionEditCanceled(): void {
|
|
1338
|
+
// If we were creating a new node, just discard it
|
|
1339
|
+
if (this.isCreatingNewNode) {
|
|
1340
|
+
this.isCreatingNewNode = false;
|
|
1341
|
+
this.pendingNodePosition = null;
|
|
1342
|
+
}
|
|
1025
1343
|
this.closeNodeEditor();
|
|
1026
1344
|
}
|
|
1027
1345
|
|
|
1028
|
-
private handleNodeSaved(
|
|
1346
|
+
private handleNodeSaved(
|
|
1347
|
+
updatedNode: Node,
|
|
1348
|
+
uiConfig?: Record<string, any>
|
|
1349
|
+
): void {
|
|
1029
1350
|
if (this.editingNode) {
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
const
|
|
1033
|
-
|
|
1351
|
+
if (this.isCreatingNewNode) {
|
|
1352
|
+
// This is a new node - add it to the store for the first time
|
|
1353
|
+
const store = getStore();
|
|
1354
|
+
|
|
1355
|
+
const nodeUI: NodeUI = {
|
|
1356
|
+
position: this.pendingNodePosition || { left: 0, top: 0 },
|
|
1357
|
+
type: this.editingNodeUI?.type,
|
|
1358
|
+
config: uiConfig || {}
|
|
1359
|
+
};
|
|
1034
1360
|
|
|
1035
|
-
//
|
|
1036
|
-
|
|
1037
|
-
(oldExit) =>
|
|
1038
|
-
!newExits.find((newExit) => newExit.uuid === oldExit.uuid)
|
|
1039
|
-
);
|
|
1361
|
+
// Add the node to the store
|
|
1362
|
+
store.getState().addNode(updatedNode, nodeUI);
|
|
1040
1363
|
|
|
1041
|
-
//
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1364
|
+
// Reset the creation flags
|
|
1365
|
+
this.isCreatingNewNode = false;
|
|
1366
|
+
this.pendingNodePosition = null;
|
|
1367
|
+
} else {
|
|
1368
|
+
// This is an existing node - update it
|
|
1369
|
+
// Clean up jsPlumb connections for removed exits before updating the node
|
|
1370
|
+
if (this.plumber) {
|
|
1371
|
+
const oldExits = this.editingNode.exits || [];
|
|
1372
|
+
const newExits = updatedNode.exits || [];
|
|
1373
|
+
|
|
1374
|
+
// Find exits that were removed
|
|
1375
|
+
const removedExits = oldExits.filter(
|
|
1376
|
+
(oldExit) =>
|
|
1377
|
+
!newExits.find((newExit) => newExit.uuid === oldExit.uuid)
|
|
1378
|
+
);
|
|
1379
|
+
|
|
1380
|
+
// Remove jsPlumb connections for removed exits
|
|
1381
|
+
removedExits.forEach((exit) => {
|
|
1382
|
+
this.plumber.removeExitConnection(exit.uuid);
|
|
1383
|
+
});
|
|
1384
|
+
}
|
|
1046
1385
|
|
|
1047
|
-
|
|
1386
|
+
this.plumber.revalidate([updatedNode.uuid]);
|
|
1048
1387
|
|
|
1049
|
-
|
|
1050
|
-
|
|
1388
|
+
// Update the node in the store
|
|
1389
|
+
getStore()?.getState().updateNode(this.editingNode.uuid, updatedNode);
|
|
1390
|
+
|
|
1391
|
+
// Update the UI config if provided
|
|
1392
|
+
if (uiConfig) {
|
|
1393
|
+
getStore()?.getState().updateNodeUIConfig(updatedNode.uuid, uiConfig);
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1051
1396
|
|
|
1052
1397
|
// Repaint jsplumb connections in case node size changed
|
|
1053
1398
|
if (this.plumber) {
|
|
@@ -1061,9 +1406,299 @@ export class Editor extends RapidElement {
|
|
|
1061
1406
|
}
|
|
1062
1407
|
|
|
1063
1408
|
private handleNodeEditCanceled(): void {
|
|
1409
|
+
// If we were creating a new node, just discard it
|
|
1410
|
+
if (this.isCreatingNewNode) {
|
|
1411
|
+
this.isCreatingNewNode = false;
|
|
1412
|
+
this.pendingNodePosition = null;
|
|
1413
|
+
}
|
|
1064
1414
|
this.closeNodeEditor();
|
|
1065
1415
|
}
|
|
1066
1416
|
|
|
1417
|
+
private getNodeAtPosition(mouseX: number, mouseY: number): string | null {
|
|
1418
|
+
// Get all node elements
|
|
1419
|
+
const nodeElements = this.querySelectorAll('temba-flow-node');
|
|
1420
|
+
|
|
1421
|
+
for (const nodeElement of Array.from(nodeElements)) {
|
|
1422
|
+
const rect = nodeElement.getBoundingClientRect();
|
|
1423
|
+
|
|
1424
|
+
if (
|
|
1425
|
+
mouseX >= rect.left &&
|
|
1426
|
+
mouseX <= rect.right &&
|
|
1427
|
+
mouseY >= rect.top &&
|
|
1428
|
+
mouseY <= rect.bottom
|
|
1429
|
+
) {
|
|
1430
|
+
return nodeElement.getAttribute('data-node-uuid');
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1434
|
+
return null;
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
private calculateCanvasDropPosition(
|
|
1438
|
+
mouseX: number,
|
|
1439
|
+
mouseY: number,
|
|
1440
|
+
applyGridSnapping: boolean = true
|
|
1441
|
+
): FlowPosition {
|
|
1442
|
+
// calculate the position on the canvas
|
|
1443
|
+
const canvas = this.querySelector('#canvas');
|
|
1444
|
+
if (!canvas) return { left: 0, top: 0 };
|
|
1445
|
+
|
|
1446
|
+
const canvasRect = canvas.getBoundingClientRect();
|
|
1447
|
+
|
|
1448
|
+
// calculate position relative to canvas
|
|
1449
|
+
// canvasRect gives us the canvas position in the viewport, which already accounts for scroll
|
|
1450
|
+
// so we just need mouseX/Y - canvasRect.left/top to get position within canvas
|
|
1451
|
+
const left = mouseX - canvasRect.left - DROP_PREVIEW_OFFSET_X;
|
|
1452
|
+
const top = mouseY - canvasRect.top - DROP_PREVIEW_OFFSET_Y;
|
|
1453
|
+
|
|
1454
|
+
// Apply grid snapping only if requested (for final drop position)
|
|
1455
|
+
if (applyGridSnapping) {
|
|
1456
|
+
return {
|
|
1457
|
+
left: snapToGrid(left),
|
|
1458
|
+
top: snapToGrid(top)
|
|
1459
|
+
};
|
|
1460
|
+
}
|
|
1461
|
+
|
|
1462
|
+
return { left, top };
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1465
|
+
private handleActionDragExternal(event: CustomEvent): void {
|
|
1466
|
+
const {
|
|
1467
|
+
action,
|
|
1468
|
+
nodeUuid,
|
|
1469
|
+
actionIndex,
|
|
1470
|
+
mouseX,
|
|
1471
|
+
mouseY,
|
|
1472
|
+
actionHeight = 60
|
|
1473
|
+
} = event.detail;
|
|
1474
|
+
|
|
1475
|
+
// Check if mouse is over another execute_actions node
|
|
1476
|
+
const targetNode = this.getNodeAtPosition(mouseX, mouseY);
|
|
1477
|
+
|
|
1478
|
+
if (targetNode && targetNode !== nodeUuid) {
|
|
1479
|
+
const targetNodeUI = this.definition._ui.nodes[targetNode];
|
|
1480
|
+
const targetNodeDef = this.definition.nodes.find(
|
|
1481
|
+
(n) => n.uuid === targetNode
|
|
1482
|
+
);
|
|
1483
|
+
|
|
1484
|
+
// Only allow dropping on execute_actions nodes, and not the source node
|
|
1485
|
+
if (targetNodeUI?.type === 'execute_actions' && targetNodeDef) {
|
|
1486
|
+
// If we moved to a different target node, clear the previous one's placeholder
|
|
1487
|
+
if (
|
|
1488
|
+
this.previousActionDragTargetNodeUuid &&
|
|
1489
|
+
this.previousActionDragTargetNodeUuid !== targetNode
|
|
1490
|
+
) {
|
|
1491
|
+
const previousElement = this.querySelector(
|
|
1492
|
+
`temba-flow-node[data-node-uuid="${this.previousActionDragTargetNodeUuid}"]`
|
|
1493
|
+
);
|
|
1494
|
+
if (previousElement) {
|
|
1495
|
+
previousElement.dispatchEvent(
|
|
1496
|
+
new CustomEvent('action-drag-leave', {
|
|
1497
|
+
detail: {},
|
|
1498
|
+
bubbles: false
|
|
1499
|
+
})
|
|
1500
|
+
);
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
// Update target node for drop handling
|
|
1505
|
+
this.actionDragTargetNodeUuid = targetNode;
|
|
1506
|
+
this.previousActionDragTargetNodeUuid = targetNode;
|
|
1507
|
+
|
|
1508
|
+
// Hide canvas preview when over a valid target
|
|
1509
|
+
this.canvasDropPreview = null;
|
|
1510
|
+
|
|
1511
|
+
// Tell source node to show ghost (we're over a valid target)
|
|
1512
|
+
const sourceElement = this.querySelector(
|
|
1513
|
+
`temba-flow-node[data-node-uuid="${nodeUuid}"]`
|
|
1514
|
+
);
|
|
1515
|
+
if (sourceElement) {
|
|
1516
|
+
sourceElement.dispatchEvent(
|
|
1517
|
+
new CustomEvent('action-show-ghost', {
|
|
1518
|
+
detail: {},
|
|
1519
|
+
bubbles: false
|
|
1520
|
+
})
|
|
1521
|
+
);
|
|
1522
|
+
}
|
|
1523
|
+
|
|
1524
|
+
// Notify the target node about the drag
|
|
1525
|
+
const targetElement = this.querySelector(
|
|
1526
|
+
`temba-flow-node[data-node-uuid="${targetNode}"]`
|
|
1527
|
+
);
|
|
1528
|
+
if (targetElement) {
|
|
1529
|
+
targetElement.dispatchEvent(
|
|
1530
|
+
new CustomEvent('action-drag-over', {
|
|
1531
|
+
detail: {
|
|
1532
|
+
action,
|
|
1533
|
+
sourceNodeUuid: nodeUuid,
|
|
1534
|
+
actionIndex,
|
|
1535
|
+
mouseX,
|
|
1536
|
+
mouseY,
|
|
1537
|
+
actionHeight
|
|
1538
|
+
},
|
|
1539
|
+
bubbles: false
|
|
1540
|
+
})
|
|
1541
|
+
);
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
this.requestUpdate();
|
|
1545
|
+
return;
|
|
1546
|
+
}
|
|
1547
|
+
}
|
|
1548
|
+
|
|
1549
|
+
// Not over a valid target node, clear any previous target's placeholder
|
|
1550
|
+
if (this.previousActionDragTargetNodeUuid) {
|
|
1551
|
+
const previousElement = this.querySelector(
|
|
1552
|
+
`temba-flow-node[data-node-uuid="${this.previousActionDragTargetNodeUuid}"]`
|
|
1553
|
+
);
|
|
1554
|
+
if (previousElement) {
|
|
1555
|
+
previousElement.dispatchEvent(
|
|
1556
|
+
new CustomEvent('action-drag-leave', {
|
|
1557
|
+
detail: {},
|
|
1558
|
+
bubbles: false
|
|
1559
|
+
})
|
|
1560
|
+
);
|
|
1561
|
+
}
|
|
1562
|
+
this.previousActionDragTargetNodeUuid = null;
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
this.actionDragTargetNodeUuid = null;
|
|
1566
|
+
|
|
1567
|
+
// Tell source node to hide ghost (we're not over a valid target)
|
|
1568
|
+
const sourceElement = this.querySelector(
|
|
1569
|
+
`temba-flow-node[data-node-uuid="${nodeUuid}"]`
|
|
1570
|
+
);
|
|
1571
|
+
if (sourceElement) {
|
|
1572
|
+
sourceElement.dispatchEvent(
|
|
1573
|
+
new CustomEvent('action-hide-ghost', {
|
|
1574
|
+
detail: {},
|
|
1575
|
+
bubbles: false
|
|
1576
|
+
})
|
|
1577
|
+
);
|
|
1578
|
+
}
|
|
1579
|
+
|
|
1580
|
+
// Don't snap to grid for preview - let it follow cursor smoothly
|
|
1581
|
+
const position = this.calculateCanvasDropPosition(mouseX, mouseY, false);
|
|
1582
|
+
|
|
1583
|
+
this.canvasDropPreview = {
|
|
1584
|
+
action,
|
|
1585
|
+
nodeUuid,
|
|
1586
|
+
actionIndex,
|
|
1587
|
+
position,
|
|
1588
|
+
actionHeight
|
|
1589
|
+
};
|
|
1590
|
+
|
|
1591
|
+
// Force re-render to update preview position
|
|
1592
|
+
this.requestUpdate();
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1595
|
+
private handleActionDragInternal(_event: CustomEvent): void {
|
|
1596
|
+
// Clear any previous target's placeholder when returning to internal drag
|
|
1597
|
+
if (this.previousActionDragTargetNodeUuid) {
|
|
1598
|
+
const previousElement = this.querySelector(
|
|
1599
|
+
`temba-flow-node[data-node-uuid="${this.previousActionDragTargetNodeUuid}"]`
|
|
1600
|
+
);
|
|
1601
|
+
if (previousElement) {
|
|
1602
|
+
previousElement.dispatchEvent(
|
|
1603
|
+
new CustomEvent('action-drag-leave', {
|
|
1604
|
+
detail: {},
|
|
1605
|
+
bubbles: false
|
|
1606
|
+
})
|
|
1607
|
+
);
|
|
1608
|
+
}
|
|
1609
|
+
this.previousActionDragTargetNodeUuid = null;
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
this.canvasDropPreview = null;
|
|
1613
|
+
this.actionDragTargetNodeUuid = null;
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
private handleActionDropExternal(event: CustomEvent): void {
|
|
1617
|
+
const { action, nodeUuid, actionIndex, mouseX, mouseY } = event.detail;
|
|
1618
|
+
|
|
1619
|
+
// Check if we're dropping on an existing execute_actions node
|
|
1620
|
+
const targetNodeUuid = this.actionDragTargetNodeUuid;
|
|
1621
|
+
|
|
1622
|
+
if (targetNodeUuid && targetNodeUuid !== nodeUuid) {
|
|
1623
|
+
// Dropping on another node - notify the target node to handle the drop
|
|
1624
|
+
const targetElement = this.querySelector(
|
|
1625
|
+
`temba-flow-node[data-node-uuid="${targetNodeUuid}"]`
|
|
1626
|
+
);
|
|
1627
|
+
if (targetElement) {
|
|
1628
|
+
targetElement.dispatchEvent(
|
|
1629
|
+
new CustomEvent('action-drop', {
|
|
1630
|
+
detail: {
|
|
1631
|
+
action,
|
|
1632
|
+
sourceNodeUuid: nodeUuid,
|
|
1633
|
+
actionIndex,
|
|
1634
|
+
mouseX,
|
|
1635
|
+
mouseY
|
|
1636
|
+
},
|
|
1637
|
+
bubbles: false
|
|
1638
|
+
})
|
|
1639
|
+
);
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1642
|
+
// Clear state
|
|
1643
|
+
this.canvasDropPreview = null;
|
|
1644
|
+
this.actionDragTargetNodeUuid = null;
|
|
1645
|
+
return;
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1648
|
+
// Not dropping on another node, create a new one on canvas
|
|
1649
|
+
// Snap to grid for the final drop position
|
|
1650
|
+
const position = this.calculateCanvasDropPosition(mouseX, mouseY, true);
|
|
1651
|
+
|
|
1652
|
+
// remove the action from the original node
|
|
1653
|
+
const originalNode = this.definition.nodes.find((n) => n.uuid === nodeUuid);
|
|
1654
|
+
if (!originalNode) return;
|
|
1655
|
+
|
|
1656
|
+
const updatedActions = originalNode.actions.filter(
|
|
1657
|
+
(_a, idx) => idx !== actionIndex
|
|
1658
|
+
);
|
|
1659
|
+
|
|
1660
|
+
// if no actions remain, delete the node
|
|
1661
|
+
if (updatedActions.length === 0) {
|
|
1662
|
+
getStore()?.getState().removeNodes([nodeUuid]);
|
|
1663
|
+
} else {
|
|
1664
|
+
// update the node
|
|
1665
|
+
const updatedNode = { ...originalNode, actions: updatedActions };
|
|
1666
|
+
getStore()?.getState().updateNode(nodeUuid, updatedNode);
|
|
1667
|
+
}
|
|
1668
|
+
|
|
1669
|
+
// create a new execute_actions node with the dropped action
|
|
1670
|
+
const newNode: Node = {
|
|
1671
|
+
uuid: generateUUID(),
|
|
1672
|
+
actions: [action],
|
|
1673
|
+
exits: [
|
|
1674
|
+
{
|
|
1675
|
+
uuid: generateUUID(),
|
|
1676
|
+
destination_uuid: null
|
|
1677
|
+
}
|
|
1678
|
+
]
|
|
1679
|
+
};
|
|
1680
|
+
|
|
1681
|
+
const newNodeUI: NodeUI = {
|
|
1682
|
+
position,
|
|
1683
|
+
type: 'execute_actions',
|
|
1684
|
+
config: {}
|
|
1685
|
+
};
|
|
1686
|
+
|
|
1687
|
+
// add the new node
|
|
1688
|
+
getStore()?.getState().addNode(newNode, newNodeUI);
|
|
1689
|
+
|
|
1690
|
+
// clear the preview
|
|
1691
|
+
this.canvasDropPreview = null;
|
|
1692
|
+
this.actionDragTargetNodeUuid = null;
|
|
1693
|
+
|
|
1694
|
+
// repaint connections
|
|
1695
|
+
if (this.plumber) {
|
|
1696
|
+
requestAnimationFrame(() => {
|
|
1697
|
+
this.plumber.repaintEverything();
|
|
1698
|
+
});
|
|
1699
|
+
}
|
|
1700
|
+
}
|
|
1701
|
+
|
|
1067
1702
|
public render(): TemplateResult {
|
|
1068
1703
|
// we have to embed our own style since we are in light DOM
|
|
1069
1704
|
const style = html`<style>
|
|
@@ -1104,6 +1739,7 @@ export class Editor extends RapidElement {
|
|
|
1104
1739
|
: ''}"
|
|
1105
1740
|
@mousedown=${this.handleMouseDown.bind(this)}
|
|
1106
1741
|
uuid=${node.uuid}
|
|
1742
|
+
data-node-uuid=${node.uuid}
|
|
1107
1743
|
style="left:${position.left}px; top:${position.top}px"
|
|
1108
1744
|
.plumber=${this.plumber}
|
|
1109
1745
|
.node=${node}
|
|
@@ -1137,7 +1773,7 @@ export class Editor extends RapidElement {
|
|
|
1137
1773
|
></temba-sticky-note>`;
|
|
1138
1774
|
}
|
|
1139
1775
|
)}
|
|
1140
|
-
${this.renderSelectionBox()}
|
|
1776
|
+
${this.renderSelectionBox()} ${this.renderCanvasDropPreview()}
|
|
1141
1777
|
</div>
|
|
1142
1778
|
</div>
|
|
1143
1779
|
</div>
|
|
@@ -1148,11 +1784,14 @@ export class Editor extends RapidElement {
|
|
|
1148
1784
|
.nodeUI=${this.editingNodeUI}
|
|
1149
1785
|
.action=${this.editingAction}
|
|
1150
1786
|
@temba-node-saved=${(e: CustomEvent) =>
|
|
1151
|
-
this.handleNodeSaved(e.detail.node)}
|
|
1787
|
+
this.handleNodeSaved(e.detail.node, e.detail.uiConfig)}
|
|
1152
1788
|
@temba-action-saved=${(e: CustomEvent) =>
|
|
1153
1789
|
this.handleActionSaved(e.detail.action)}
|
|
1154
1790
|
@temba-node-edit-cancelled=${this.handleNodeEditCanceled}
|
|
1155
1791
|
></temba-node-editor>`
|
|
1156
|
-
: ''}
|
|
1792
|
+
: ''}
|
|
1793
|
+
|
|
1794
|
+
<temba-canvas-menu></temba-canvas-menu>
|
|
1795
|
+
<temba-node-type-selector></temba-node-type-selector> `;
|
|
1157
1796
|
}
|
|
1158
1797
|
}
|