@nyaruka/temba-components 0.131.0 → 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 +67 -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 +1156 -619
- 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/ContactSearch.js +1 -1
- package/out-tsc/src/form/ContactSearch.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/ContactSearch.ts +1 -1
- 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
|
@@ -7,6 +7,9 @@ import { fromStore, zustand } from '../store/AppState';
|
|
|
7
7
|
import { RapidElement } from '../RapidElement';
|
|
8
8
|
import { repeat } from 'lit-html/directives/repeat.js';
|
|
9
9
|
import { CustomEventType } from '../interfaces';
|
|
10
|
+
import { generateUUID } from '../utils';
|
|
11
|
+
import { ACTION_CONFIG, NODE_CONFIG } from './config';
|
|
12
|
+
import { ACTION_GROUP_METADATA } from './types';
|
|
10
13
|
import { Plumber } from './Plumber';
|
|
11
14
|
import { CanvasNode } from './CanvasNode';
|
|
12
15
|
export function snapToGrid(value) {
|
|
@@ -24,6 +27,10 @@ export function findNodeForExit(definition, exitUuid) {
|
|
|
24
27
|
}
|
|
25
28
|
const SAVE_QUIET_TIME = 500;
|
|
26
29
|
const DRAG_THRESHOLD = 5;
|
|
30
|
+
// Offset for positioning dropped action node relative to mouse cursor
|
|
31
|
+
// Keep small to make drop location close to cursor position
|
|
32
|
+
const DROP_PREVIEW_OFFSET_X = 20;
|
|
33
|
+
const DROP_PREVIEW_OFFSET_Y = 20;
|
|
27
34
|
export class Editor extends RapidElement {
|
|
28
35
|
// unfortunately, jsplumb requires that we be in light DOM
|
|
29
36
|
createRenderRoot() {
|
|
@@ -200,13 +207,22 @@ export class Editor extends RapidElement {
|
|
|
200
207
|
this.editingNode = null;
|
|
201
208
|
this.editingNodeUI = null;
|
|
202
209
|
this.editingAction = null;
|
|
210
|
+
this.isCreatingNewNode = false;
|
|
211
|
+
this.pendingNodePosition = null;
|
|
212
|
+
// Canvas drop state for dragging actions to canvas
|
|
213
|
+
this.canvasDropPreview = null;
|
|
214
|
+
this.addActionToNodeUuid = null;
|
|
215
|
+
// Track target node for action drag
|
|
216
|
+
this.actionDragTargetNodeUuid = null;
|
|
217
|
+
// Track previous target node to clear placeholder when moving between nodes
|
|
218
|
+
this.previousActionDragTargetNodeUuid = null;
|
|
203
219
|
this.canvasMouseDown = false;
|
|
204
220
|
// Bound event handlers to maintain proper 'this' context
|
|
205
221
|
this.boundMouseMove = this.handleMouseMove.bind(this);
|
|
206
222
|
this.boundMouseUp = this.handleMouseUp.bind(this);
|
|
207
223
|
this.boundGlobalMouseDown = this.handleGlobalMouseDown.bind(this);
|
|
208
224
|
this.boundKeyDown = this.handleKeyDown.bind(this);
|
|
209
|
-
this.
|
|
225
|
+
this.boundCanvasContextMenu = this.handleCanvasContextMenu.bind(this);
|
|
210
226
|
}
|
|
211
227
|
firstUpdated(changes) {
|
|
212
228
|
super.firstUpdated(changes);
|
|
@@ -305,7 +321,7 @@ export class Editor extends RapidElement {
|
|
|
305
321
|
document.removeEventListener('keydown', this.boundKeyDown);
|
|
306
322
|
const canvas = this.querySelector('#canvas');
|
|
307
323
|
if (canvas) {
|
|
308
|
-
canvas.removeEventListener('
|
|
324
|
+
canvas.removeEventListener('contextmenu', this.boundCanvasContextMenu);
|
|
309
325
|
}
|
|
310
326
|
}
|
|
311
327
|
setupGlobalEventListeners() {
|
|
@@ -315,20 +331,40 @@ export class Editor extends RapidElement {
|
|
|
315
331
|
document.addEventListener('keydown', this.boundKeyDown);
|
|
316
332
|
const canvas = this.querySelector('#canvas');
|
|
317
333
|
if (canvas) {
|
|
318
|
-
canvas.addEventListener('
|
|
334
|
+
canvas.addEventListener('contextmenu', this.boundCanvasContextMenu);
|
|
319
335
|
}
|
|
320
336
|
// Listen for action edit requests from flow nodes
|
|
321
337
|
this.addEventListener(CustomEventType.ActionEditRequested, this.handleActionEditRequested.bind(this));
|
|
338
|
+
// Listen for add action requests from flow nodes
|
|
339
|
+
this.addEventListener(CustomEventType.AddActionRequested, this.handleAddActionRequested.bind(this));
|
|
322
340
|
// Listen for node edit requests from flow nodes
|
|
323
341
|
this.addEventListener(CustomEventType.NodeEditRequested, this.handleNodeEditRequested.bind(this));
|
|
342
|
+
// Listen for canvas menu selections
|
|
343
|
+
this.addEventListener(CustomEventType.Selection, (event) => {
|
|
344
|
+
const target = event.target;
|
|
345
|
+
if (target.tagName === 'TEMBA-CANVAS-MENU') {
|
|
346
|
+
this.handleCanvasMenuSelection(event);
|
|
347
|
+
}
|
|
348
|
+
else if (target.tagName === 'TEMBA-NODE-TYPE-SELECTOR') {
|
|
349
|
+
this.handleNodeTypeSelection(event);
|
|
350
|
+
}
|
|
351
|
+
});
|
|
352
|
+
// Listen for action drag events from nodes
|
|
353
|
+
this.addEventListener(CustomEventType.DragExternal, this.handleActionDragExternal.bind(this));
|
|
354
|
+
this.addEventListener(CustomEventType.DragInternal, this.handleActionDragInternal.bind(this));
|
|
355
|
+
this.addEventListener(CustomEventType.DragStop, (event) => {
|
|
356
|
+
if (event.detail.isExternal) {
|
|
357
|
+
this.handleActionDropExternal(event);
|
|
358
|
+
}
|
|
359
|
+
});
|
|
324
360
|
}
|
|
325
361
|
getPosition(uuid, type) {
|
|
326
|
-
var
|
|
362
|
+
var _b, _c, _d;
|
|
327
363
|
if (type === 'node') {
|
|
328
|
-
return (
|
|
364
|
+
return (_b = this.definition._ui.nodes[uuid]) === null || _b === void 0 ? void 0 : _b.position;
|
|
329
365
|
}
|
|
330
366
|
else {
|
|
331
|
-
return (
|
|
367
|
+
return (_d = (_c = this.definition._ui.stickies) === null || _c === void 0 ? void 0 : _c[uuid]) === null || _d === void 0 ? void 0 : _d.position;
|
|
332
368
|
}
|
|
333
369
|
}
|
|
334
370
|
handleMouseDown(event) {
|
|
@@ -370,12 +406,12 @@ export class Editor extends RapidElement {
|
|
|
370
406
|
event.stopPropagation();
|
|
371
407
|
}
|
|
372
408
|
handleGlobalMouseDown(event) {
|
|
373
|
-
var
|
|
409
|
+
var _b;
|
|
374
410
|
// ignore right clicks
|
|
375
411
|
if (event.button !== 0)
|
|
376
412
|
return;
|
|
377
413
|
// Check if the click is within our canvas
|
|
378
|
-
const canvasRect = (
|
|
414
|
+
const canvasRect = (_b = this.querySelector('#grid')) === null || _b === void 0 ? void 0 : _b.getBoundingClientRect();
|
|
379
415
|
if (!canvasRect)
|
|
380
416
|
return;
|
|
381
417
|
const isWithinCanvas = event.clientX >= canvasRect.left &&
|
|
@@ -395,14 +431,14 @@ export class Editor extends RapidElement {
|
|
|
395
431
|
this.handleCanvasMouseDown(event);
|
|
396
432
|
}
|
|
397
433
|
handleCanvasMouseDown(event) {
|
|
398
|
-
var
|
|
434
|
+
var _b;
|
|
399
435
|
const target = event.target;
|
|
400
436
|
if (target.id === 'canvas' || target.id === 'grid') {
|
|
401
437
|
// Ignore clicks on exits
|
|
402
438
|
// Start selection box
|
|
403
439
|
this.canvasMouseDown = true;
|
|
404
440
|
this.dragStartPos = { x: event.clientX, y: event.clientY };
|
|
405
|
-
const canvasRect = (
|
|
441
|
+
const canvasRect = (_b = this.querySelector('#canvas')) === null || _b === void 0 ? void 0 : _b.getBoundingClientRect();
|
|
406
442
|
if (canvasRect) {
|
|
407
443
|
// Clear current selection
|
|
408
444
|
this.selectedItems.clear();
|
|
@@ -466,16 +502,16 @@ export class Editor extends RapidElement {
|
|
|
466
502
|
deleteSelectedItems() {
|
|
467
503
|
const nodes = Array.from(this.selectedItems).filter((uuid) => this.definition.nodes.some((node) => node.uuid === uuid));
|
|
468
504
|
this.deleteNodes(Array.from(nodes));
|
|
469
|
-
const stickies = Array.from(this.selectedItems).filter((uuid) => { var
|
|
505
|
+
const stickies = Array.from(this.selectedItems).filter((uuid) => { var _b, _c; return (_c = (_b = this.definition._ui) === null || _b === void 0 ? void 0 : _b.stickies) === null || _c === void 0 ? void 0 : _c[uuid]; });
|
|
470
506
|
getStore().getState().removeStickyNotes(stickies);
|
|
471
507
|
// Clear selection
|
|
472
508
|
this.selectedItems.clear();
|
|
473
509
|
}
|
|
474
510
|
updateSelectionBox(event) {
|
|
475
|
-
var
|
|
511
|
+
var _b;
|
|
476
512
|
if (!this.selectionBox || !this.canvasMouseDown)
|
|
477
513
|
return;
|
|
478
|
-
const canvasRect = (
|
|
514
|
+
const canvasRect = (_b = this.querySelector('#canvas')) === null || _b === void 0 ? void 0 : _b.getBoundingClientRect();
|
|
479
515
|
if (!canvasRect)
|
|
480
516
|
return;
|
|
481
517
|
const relativeX = event.clientX - canvasRect.left;
|
|
@@ -489,7 +525,7 @@ export class Editor extends RapidElement {
|
|
|
489
525
|
this.updateSelectedItemsFromBox();
|
|
490
526
|
}
|
|
491
527
|
updateSelectedItemsFromBox() {
|
|
492
|
-
var
|
|
528
|
+
var _b, _c, _d;
|
|
493
529
|
if (!this.selectionBox)
|
|
494
530
|
return;
|
|
495
531
|
const newSelection = new Set();
|
|
@@ -498,14 +534,14 @@ export class Editor extends RapidElement {
|
|
|
498
534
|
const boxRight = Math.max(this.selectionBox.startX, this.selectionBox.endX);
|
|
499
535
|
const boxBottom = Math.max(this.selectionBox.startY, this.selectionBox.endY);
|
|
500
536
|
// Check nodes
|
|
501
|
-
(
|
|
502
|
-
var
|
|
537
|
+
(_b = this.definition) === null || _b === void 0 ? void 0 : _b.nodes.forEach((node) => {
|
|
538
|
+
var _b, _c, _d;
|
|
503
539
|
const nodeElement = this.querySelector(`[id="${node.uuid}"]`);
|
|
504
540
|
if (nodeElement) {
|
|
505
|
-
const position = (
|
|
541
|
+
const position = (_c = (_b = this.definition._ui) === null || _b === void 0 ? void 0 : _b.nodes[node.uuid]) === null || _c === void 0 ? void 0 : _c.position;
|
|
506
542
|
if (position) {
|
|
507
543
|
const rect = nodeElement.getBoundingClientRect();
|
|
508
|
-
const canvasRect = (
|
|
544
|
+
const canvasRect = (_d = this.querySelector('#canvas')) === null || _d === void 0 ? void 0 : _d.getBoundingClientRect();
|
|
509
545
|
if (canvasRect) {
|
|
510
546
|
const nodeLeft = position.left;
|
|
511
547
|
const nodeTop = position.top;
|
|
@@ -523,7 +559,7 @@ export class Editor extends RapidElement {
|
|
|
523
559
|
}
|
|
524
560
|
});
|
|
525
561
|
// Check sticky notes
|
|
526
|
-
const stickies = ((
|
|
562
|
+
const stickies = ((_d = (_c = this.definition) === null || _c === void 0 ? void 0 : _c._ui) === null || _d === void 0 ? void 0 : _d.stickies) || {};
|
|
527
563
|
Object.entries(stickies).forEach(([uuid, sticky]) => {
|
|
528
564
|
if (sticky.position) {
|
|
529
565
|
const stickyElement = this.querySelector(`temba-sticky-note[uuid="${uuid}"]`);
|
|
@@ -561,6 +597,49 @@ export class Editor extends RapidElement {
|
|
|
561
597
|
style="left: ${left}px; top: ${top}px; width: ${width}px; height: ${height}px;"
|
|
562
598
|
></div>`;
|
|
563
599
|
}
|
|
600
|
+
renderCanvasDropPreview() {
|
|
601
|
+
var _b;
|
|
602
|
+
if (!this.canvasDropPreview)
|
|
603
|
+
return '';
|
|
604
|
+
const { action, position } = this.canvasDropPreview;
|
|
605
|
+
const actionConfig = ACTION_CONFIG[action.type];
|
|
606
|
+
if (!actionConfig)
|
|
607
|
+
return '';
|
|
608
|
+
return html `<div
|
|
609
|
+
class="canvas-drop-preview"
|
|
610
|
+
style="position: absolute; left: ${position.left}px; top: ${position.top}px; opacity: 0.6; pointer-events: none; z-index: 10000;"
|
|
611
|
+
>
|
|
612
|
+
<div
|
|
613
|
+
class="node execute-actions"
|
|
614
|
+
style="outline: 3px dashed var(--color-primary, #3b82f6); outline-offset: 2px; border-radius: var(--curvature);"
|
|
615
|
+
>
|
|
616
|
+
<div class="action sortable ${action.type}">
|
|
617
|
+
<div class="action-content">
|
|
618
|
+
<div
|
|
619
|
+
class="cn-title"
|
|
620
|
+
style="background: ${actionConfig.group
|
|
621
|
+
? (_b = ACTION_GROUP_METADATA[actionConfig.group]) === null || _b === void 0 ? void 0 : _b.color
|
|
622
|
+
: '#aaaaaa'}"
|
|
623
|
+
>
|
|
624
|
+
<div class="title-spacer"></div>
|
|
625
|
+
<div class="name">${actionConfig.name}</div>
|
|
626
|
+
<div class="title-spacer"></div>
|
|
627
|
+
</div>
|
|
628
|
+
<div class="body">
|
|
629
|
+
${actionConfig.render
|
|
630
|
+
? actionConfig.render({ actions: [action] }, action)
|
|
631
|
+
: html `<pre>${action.type}</pre>`}
|
|
632
|
+
</div>
|
|
633
|
+
</div>
|
|
634
|
+
</div>
|
|
635
|
+
<div class="action-exits">
|
|
636
|
+
<div class="exit-wrapper">
|
|
637
|
+
<div class="exit"></div>
|
|
638
|
+
</div>
|
|
639
|
+
</div>
|
|
640
|
+
</div>
|
|
641
|
+
</div>`;
|
|
642
|
+
}
|
|
564
643
|
handleMouseMove(event) {
|
|
565
644
|
// Handle selection box drawing
|
|
566
645
|
if (this.canvasMouseDown && !this.isMouseDown) {
|
|
@@ -693,7 +772,7 @@ export class Editor extends RapidElement {
|
|
|
693
772
|
this.canvasMouseDown = false;
|
|
694
773
|
}
|
|
695
774
|
updateCanvasSize() {
|
|
696
|
-
var
|
|
775
|
+
var _b;
|
|
697
776
|
if (!this.definition)
|
|
698
777
|
return;
|
|
699
778
|
const store = getStore();
|
|
@@ -715,7 +794,7 @@ export class Editor extends RapidElement {
|
|
|
715
794
|
}
|
|
716
795
|
});
|
|
717
796
|
// Check sticky note positions
|
|
718
|
-
const stickies = ((
|
|
797
|
+
const stickies = ((_b = this.definition._ui) === null || _b === void 0 ? void 0 : _b.stickies) || {};
|
|
719
798
|
Object.entries(stickies).forEach(([uuid, sticky]) => {
|
|
720
799
|
if (sticky.position) {
|
|
721
800
|
const stickyElement = this.querySelector(`temba-sticky-note[uuid="${uuid}"]`);
|
|
@@ -738,12 +817,15 @@ export class Editor extends RapidElement {
|
|
|
738
817
|
// Update canvas size in store
|
|
739
818
|
store.getState().expandCanvas(maxWidth, maxHeight);
|
|
740
819
|
}
|
|
741
|
-
|
|
742
|
-
// Check if we
|
|
820
|
+
handleCanvasContextMenu(event) {
|
|
821
|
+
// Check if we right-clicked on empty canvas space
|
|
743
822
|
const target = event.target;
|
|
744
823
|
if (target.id !== 'canvas') {
|
|
745
824
|
return;
|
|
746
825
|
}
|
|
826
|
+
// Prevent the default browser context menu
|
|
827
|
+
event.preventDefault();
|
|
828
|
+
event.stopPropagation();
|
|
747
829
|
// Get canvas position
|
|
748
830
|
const canvas = this.querySelector('#canvas');
|
|
749
831
|
if (!canvas) {
|
|
@@ -755,14 +837,119 @@ export class Editor extends RapidElement {
|
|
|
755
837
|
// Snap position to grid
|
|
756
838
|
const snappedLeft = snapToGrid(relativeX);
|
|
757
839
|
const snappedTop = snapToGrid(relativeY);
|
|
758
|
-
//
|
|
840
|
+
// Show the canvas menu at the mouse position (use viewport coordinates)
|
|
841
|
+
const canvasMenu = this.querySelector('temba-canvas-menu');
|
|
842
|
+
if (canvasMenu) {
|
|
843
|
+
canvasMenu.show(event.clientX, event.clientY, {
|
|
844
|
+
x: snappedLeft,
|
|
845
|
+
y: snappedTop
|
|
846
|
+
});
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
handleCanvasMenuSelection(event) {
|
|
850
|
+
const selection = event.detail;
|
|
759
851
|
const store = getStore();
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
852
|
+
if (selection.action === 'sticky') {
|
|
853
|
+
// Create new sticky note
|
|
854
|
+
store.getState().createStickyNote({
|
|
855
|
+
left: selection.position.x,
|
|
856
|
+
top: selection.position.y
|
|
857
|
+
});
|
|
858
|
+
}
|
|
859
|
+
else {
|
|
860
|
+
// Show node type selector
|
|
861
|
+
const selector = this.querySelector('temba-node-type-selector');
|
|
862
|
+
if (selector) {
|
|
863
|
+
selector.show(selection.action, selection.position);
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
handleNodeTypeSelection(event) {
|
|
868
|
+
const selection = event.detail;
|
|
869
|
+
// Check if we're adding an action to an existing node
|
|
870
|
+
if (this.addActionToNodeUuid) {
|
|
871
|
+
// Find the existing node
|
|
872
|
+
const node = this.definition.nodes.find((n) => n.uuid === this.addActionToNodeUuid);
|
|
873
|
+
const nodeUI = this.definition._ui.nodes[this.addActionToNodeUuid];
|
|
874
|
+
if (node && nodeUI) {
|
|
875
|
+
// Create a new action to add to the existing node
|
|
876
|
+
const actionUuid = generateUUID();
|
|
877
|
+
this.editingAction = {
|
|
878
|
+
uuid: actionUuid,
|
|
879
|
+
type: selection.nodeType
|
|
880
|
+
};
|
|
881
|
+
// Set the editing node to the existing node (not creating new)
|
|
882
|
+
this.editingNode = node;
|
|
883
|
+
this.editingNodeUI = nodeUI;
|
|
884
|
+
this.isCreatingNewNode = false;
|
|
885
|
+
// Clear the addActionToNodeUuid flag
|
|
886
|
+
this.addActionToNodeUuid = null;
|
|
887
|
+
return;
|
|
888
|
+
}
|
|
889
|
+
// If we couldn't find the node, clear the flag and continue with normal flow
|
|
890
|
+
this.addActionToNodeUuid = null;
|
|
891
|
+
}
|
|
892
|
+
// Create a temporary node structure for editing (not added to store yet)
|
|
893
|
+
const nodeUuid = generateUUID();
|
|
894
|
+
// Determine if this is an action type or a node type
|
|
895
|
+
// Actions need to be wrapped in an execute_actions node
|
|
896
|
+
const isActionType = selection.nodeType in ACTION_CONFIG;
|
|
897
|
+
const nodeType = isActionType ? 'execute_actions' : selection.nodeType;
|
|
898
|
+
// For nodes with routers, initialize an empty router to ensure fromFormData works correctly
|
|
899
|
+
const nodeConfig = NODE_CONFIG[nodeType];
|
|
900
|
+
const hasRouter = (nodeConfig === null || nodeConfig === void 0 ? void 0 : nodeConfig.form) &&
|
|
901
|
+
Object.keys(nodeConfig.form).some((key) => {
|
|
902
|
+
var _b;
|
|
903
|
+
return ['rules', 'categories', 'cases'].includes(key) ||
|
|
904
|
+
((_b = nodeConfig.form[key]) === null || _b === void 0 ? void 0 : _b.type) === 'array';
|
|
905
|
+
});
|
|
906
|
+
const tempNode = {
|
|
907
|
+
uuid: nodeUuid,
|
|
908
|
+
actions: [],
|
|
909
|
+
exits: hasRouter
|
|
910
|
+
? [] // Router-based nodes will generate their own exits
|
|
911
|
+
: [
|
|
912
|
+
{
|
|
913
|
+
uuid: generateUUID(),
|
|
914
|
+
destination_uuid: null
|
|
915
|
+
}
|
|
916
|
+
]
|
|
917
|
+
};
|
|
918
|
+
if (hasRouter) {
|
|
919
|
+
// This node uses a router - initialize it with empty structure
|
|
920
|
+
tempNode.router = {
|
|
921
|
+
type: 'switch',
|
|
922
|
+
categories: [],
|
|
923
|
+
cases: [],
|
|
924
|
+
operand: '@input.text',
|
|
925
|
+
default_category_uuid: undefined
|
|
926
|
+
};
|
|
927
|
+
}
|
|
928
|
+
const tempNodeUI = {
|
|
929
|
+
position: {
|
|
930
|
+
left: selection.position.x,
|
|
931
|
+
top: selection.position.y
|
|
932
|
+
},
|
|
933
|
+
type: nodeType,
|
|
934
|
+
config: {}
|
|
935
|
+
};
|
|
936
|
+
// Mark that we're creating a new node and store the position
|
|
937
|
+
this.isCreatingNewNode = true;
|
|
938
|
+
this.pendingNodePosition = {
|
|
939
|
+
left: selection.position.x,
|
|
940
|
+
top: selection.position.y
|
|
941
|
+
};
|
|
942
|
+
// Open the node editor with the temporary node
|
|
943
|
+
this.editingNode = tempNode;
|
|
944
|
+
this.editingNodeUI = tempNodeUI;
|
|
945
|
+
// If this is an action type, we also need to set up an editing action
|
|
946
|
+
if (isActionType) {
|
|
947
|
+
const actionUuid = generateUUID();
|
|
948
|
+
this.editingAction = {
|
|
949
|
+
uuid: actionUuid,
|
|
950
|
+
type: selection.nodeType
|
|
951
|
+
};
|
|
952
|
+
}
|
|
766
953
|
}
|
|
767
954
|
handleActionEditRequested(event) {
|
|
768
955
|
// For action editing, we set the action and find the corresponding node
|
|
@@ -775,24 +962,80 @@ export class Editor extends RapidElement {
|
|
|
775
962
|
this.editingNodeUI = this.definition._ui.nodes[nodeUuid];
|
|
776
963
|
}
|
|
777
964
|
}
|
|
965
|
+
handleAddActionRequested(event) {
|
|
966
|
+
// Get the node where we want to add the action
|
|
967
|
+
const nodeUuid = event.detail.nodeUuid;
|
|
968
|
+
const node = this.definition.nodes.find((n) => n.uuid === nodeUuid);
|
|
969
|
+
if (!node) {
|
|
970
|
+
return;
|
|
971
|
+
}
|
|
972
|
+
// Get the node's position to place the selector near it
|
|
973
|
+
const nodeUI = this.definition._ui.nodes[nodeUuid];
|
|
974
|
+
if (!nodeUI) {
|
|
975
|
+
return;
|
|
976
|
+
}
|
|
977
|
+
// Show the node type selector in action mode, excluding branching actions
|
|
978
|
+
const selector = this.querySelector('temba-node-type-selector');
|
|
979
|
+
if (selector) {
|
|
980
|
+
// Show the selector near the node, using a mode that excludes branching actions
|
|
981
|
+
selector.show('action-no-branching', {
|
|
982
|
+
x: nodeUI.position.left,
|
|
983
|
+
y: nodeUI.position.top
|
|
984
|
+
});
|
|
985
|
+
// Store the node UUID so we know which node to add the action to
|
|
986
|
+
this.addActionToNodeUuid = nodeUuid;
|
|
987
|
+
}
|
|
988
|
+
}
|
|
778
989
|
handleNodeEditRequested(event) {
|
|
779
990
|
this.editingNode = event.detail.node;
|
|
780
991
|
this.editingNodeUI = event.detail.nodeUI;
|
|
781
992
|
}
|
|
782
993
|
handleActionSaved(updatedAction) {
|
|
783
|
-
var
|
|
994
|
+
var _b, _c;
|
|
784
995
|
if (this.editingNode && this.editingAction) {
|
|
785
|
-
|
|
786
|
-
|
|
996
|
+
let updatedActions;
|
|
997
|
+
// Check if this action already exists in the node
|
|
998
|
+
const existingActionIndex = this.editingNode.actions.findIndex((action) => action.uuid === this.editingAction.uuid);
|
|
999
|
+
if (existingActionIndex >= 0) {
|
|
1000
|
+
// Update existing action
|
|
1001
|
+
updatedActions = this.editingNode.actions.map((action) => action.uuid === this.editingAction.uuid ? updatedAction : action);
|
|
1002
|
+
}
|
|
1003
|
+
else {
|
|
1004
|
+
// Add new action
|
|
1005
|
+
updatedActions = [...this.editingNode.actions, updatedAction];
|
|
1006
|
+
}
|
|
787
1007
|
const updatedNode = { ...this.editingNode, actions: updatedActions };
|
|
788
|
-
//
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
this.
|
|
795
|
-
|
|
1008
|
+
// Check if we're creating a new node or updating an existing one
|
|
1009
|
+
if (this.isCreatingNewNode) {
|
|
1010
|
+
// This is a new node with a new action - add it to the store
|
|
1011
|
+
const store = getStore();
|
|
1012
|
+
const nodeUI = {
|
|
1013
|
+
position: this.pendingNodePosition || { left: 0, top: 0 },
|
|
1014
|
+
type: (_b = this.editingNodeUI) === null || _b === void 0 ? void 0 : _b.type,
|
|
1015
|
+
config: {}
|
|
1016
|
+
};
|
|
1017
|
+
// Add the node to the store
|
|
1018
|
+
store.getState().addNode(updatedNode, nodeUI);
|
|
1019
|
+
// Reset the creation flags
|
|
1020
|
+
this.isCreatingNewNode = false;
|
|
1021
|
+
this.pendingNodePosition = null;
|
|
1022
|
+
// Repaint jsplumb connections
|
|
1023
|
+
if (this.plumber) {
|
|
1024
|
+
requestAnimationFrame(() => {
|
|
1025
|
+
this.plumber.repaintEverything();
|
|
1026
|
+
});
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
else {
|
|
1030
|
+
// Update existing node in the store
|
|
1031
|
+
(_c = getStore()) === null || _c === void 0 ? void 0 : _c.getState().updateNode(this.editingNode.uuid, updatedNode);
|
|
1032
|
+
// Repaint jsplumb connections in case node size changed
|
|
1033
|
+
if (this.plumber) {
|
|
1034
|
+
// Use requestAnimationFrame to ensure DOM has been updated first
|
|
1035
|
+
requestAnimationFrame(() => {
|
|
1036
|
+
this.plumber.repaintEverything();
|
|
1037
|
+
});
|
|
1038
|
+
}
|
|
796
1039
|
}
|
|
797
1040
|
}
|
|
798
1041
|
this.closeNodeEditor();
|
|
@@ -803,25 +1046,51 @@ export class Editor extends RapidElement {
|
|
|
803
1046
|
this.editingAction = null;
|
|
804
1047
|
}
|
|
805
1048
|
handleActionEditCanceled() {
|
|
1049
|
+
// If we were creating a new node, just discard it
|
|
1050
|
+
if (this.isCreatingNewNode) {
|
|
1051
|
+
this.isCreatingNewNode = false;
|
|
1052
|
+
this.pendingNodePosition = null;
|
|
1053
|
+
}
|
|
806
1054
|
this.closeNodeEditor();
|
|
807
1055
|
}
|
|
808
|
-
handleNodeSaved(updatedNode) {
|
|
809
|
-
var
|
|
1056
|
+
handleNodeSaved(updatedNode, uiConfig) {
|
|
1057
|
+
var _b, _c, _d;
|
|
810
1058
|
if (this.editingNode) {
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
const
|
|
814
|
-
const
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
1059
|
+
if (this.isCreatingNewNode) {
|
|
1060
|
+
// This is a new node - add it to the store for the first time
|
|
1061
|
+
const store = getStore();
|
|
1062
|
+
const nodeUI = {
|
|
1063
|
+
position: this.pendingNodePosition || { left: 0, top: 0 },
|
|
1064
|
+
type: (_b = this.editingNodeUI) === null || _b === void 0 ? void 0 : _b.type,
|
|
1065
|
+
config: uiConfig || {}
|
|
1066
|
+
};
|
|
1067
|
+
// Add the node to the store
|
|
1068
|
+
store.getState().addNode(updatedNode, nodeUI);
|
|
1069
|
+
// Reset the creation flags
|
|
1070
|
+
this.isCreatingNewNode = false;
|
|
1071
|
+
this.pendingNodePosition = null;
|
|
1072
|
+
}
|
|
1073
|
+
else {
|
|
1074
|
+
// This is an existing node - update it
|
|
1075
|
+
// Clean up jsPlumb connections for removed exits before updating the node
|
|
1076
|
+
if (this.plumber) {
|
|
1077
|
+
const oldExits = this.editingNode.exits || [];
|
|
1078
|
+
const newExits = updatedNode.exits || [];
|
|
1079
|
+
// Find exits that were removed
|
|
1080
|
+
const removedExits = oldExits.filter((oldExit) => !newExits.find((newExit) => newExit.uuid === oldExit.uuid));
|
|
1081
|
+
// Remove jsPlumb connections for removed exits
|
|
1082
|
+
removedExits.forEach((exit) => {
|
|
1083
|
+
this.plumber.removeExitConnection(exit.uuid);
|
|
1084
|
+
});
|
|
1085
|
+
}
|
|
1086
|
+
this.plumber.revalidate([updatedNode.uuid]);
|
|
1087
|
+
// Update the node in the store
|
|
1088
|
+
(_c = getStore()) === null || _c === void 0 ? void 0 : _c.getState().updateNode(this.editingNode.uuid, updatedNode);
|
|
1089
|
+
// Update the UI config if provided
|
|
1090
|
+
if (uiConfig) {
|
|
1091
|
+
(_d = getStore()) === null || _d === void 0 ? void 0 : _d.getState().updateNodeUIConfig(updatedNode.uuid, uiConfig);
|
|
1092
|
+
}
|
|
821
1093
|
}
|
|
822
|
-
this.plumber.revalidate([updatedNode.uuid]);
|
|
823
|
-
// Update the node in the store
|
|
824
|
-
(_a = getStore()) === null || _a === void 0 ? void 0 : _a.getState().updateNode(this.editingNode.uuid, updatedNode);
|
|
825
1094
|
// Repaint jsplumb connections in case node size changed
|
|
826
1095
|
if (this.plumber) {
|
|
827
1096
|
// Use requestAnimationFrame to ensure DOM has been updated first
|
|
@@ -833,16 +1102,224 @@ export class Editor extends RapidElement {
|
|
|
833
1102
|
this.closeNodeEditor();
|
|
834
1103
|
}
|
|
835
1104
|
handleNodeEditCanceled() {
|
|
1105
|
+
// If we were creating a new node, just discard it
|
|
1106
|
+
if (this.isCreatingNewNode) {
|
|
1107
|
+
this.isCreatingNewNode = false;
|
|
1108
|
+
this.pendingNodePosition = null;
|
|
1109
|
+
}
|
|
836
1110
|
this.closeNodeEditor();
|
|
837
1111
|
}
|
|
1112
|
+
getNodeAtPosition(mouseX, mouseY) {
|
|
1113
|
+
// Get all node elements
|
|
1114
|
+
const nodeElements = this.querySelectorAll('temba-flow-node');
|
|
1115
|
+
for (const nodeElement of Array.from(nodeElements)) {
|
|
1116
|
+
const rect = nodeElement.getBoundingClientRect();
|
|
1117
|
+
if (mouseX >= rect.left &&
|
|
1118
|
+
mouseX <= rect.right &&
|
|
1119
|
+
mouseY >= rect.top &&
|
|
1120
|
+
mouseY <= rect.bottom) {
|
|
1121
|
+
return nodeElement.getAttribute('data-node-uuid');
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
return null;
|
|
1125
|
+
}
|
|
1126
|
+
calculateCanvasDropPosition(mouseX, mouseY, applyGridSnapping = true) {
|
|
1127
|
+
// calculate the position on the canvas
|
|
1128
|
+
const canvas = this.querySelector('#canvas');
|
|
1129
|
+
if (!canvas)
|
|
1130
|
+
return { left: 0, top: 0 };
|
|
1131
|
+
const canvasRect = canvas.getBoundingClientRect();
|
|
1132
|
+
// calculate position relative to canvas
|
|
1133
|
+
// canvasRect gives us the canvas position in the viewport, which already accounts for scroll
|
|
1134
|
+
// so we just need mouseX/Y - canvasRect.left/top to get position within canvas
|
|
1135
|
+
const left = mouseX - canvasRect.left - DROP_PREVIEW_OFFSET_X;
|
|
1136
|
+
const top = mouseY - canvasRect.top - DROP_PREVIEW_OFFSET_Y;
|
|
1137
|
+
// Apply grid snapping only if requested (for final drop position)
|
|
1138
|
+
if (applyGridSnapping) {
|
|
1139
|
+
return {
|
|
1140
|
+
left: snapToGrid(left),
|
|
1141
|
+
top: snapToGrid(top)
|
|
1142
|
+
};
|
|
1143
|
+
}
|
|
1144
|
+
return { left, top };
|
|
1145
|
+
}
|
|
1146
|
+
handleActionDragExternal(event) {
|
|
1147
|
+
const { action, nodeUuid, actionIndex, mouseX, mouseY, actionHeight = 60 } = event.detail;
|
|
1148
|
+
// Check if mouse is over another execute_actions node
|
|
1149
|
+
const targetNode = this.getNodeAtPosition(mouseX, mouseY);
|
|
1150
|
+
if (targetNode && targetNode !== nodeUuid) {
|
|
1151
|
+
const targetNodeUI = this.definition._ui.nodes[targetNode];
|
|
1152
|
+
const targetNodeDef = this.definition.nodes.find((n) => n.uuid === targetNode);
|
|
1153
|
+
// Only allow dropping on execute_actions nodes, and not the source node
|
|
1154
|
+
if ((targetNodeUI === null || targetNodeUI === void 0 ? void 0 : targetNodeUI.type) === 'execute_actions' && targetNodeDef) {
|
|
1155
|
+
// If we moved to a different target node, clear the previous one's placeholder
|
|
1156
|
+
if (this.previousActionDragTargetNodeUuid &&
|
|
1157
|
+
this.previousActionDragTargetNodeUuid !== targetNode) {
|
|
1158
|
+
const previousElement = this.querySelector(`temba-flow-node[data-node-uuid="${this.previousActionDragTargetNodeUuid}"]`);
|
|
1159
|
+
if (previousElement) {
|
|
1160
|
+
previousElement.dispatchEvent(new CustomEvent('action-drag-leave', {
|
|
1161
|
+
detail: {},
|
|
1162
|
+
bubbles: false
|
|
1163
|
+
}));
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
// Update target node for drop handling
|
|
1167
|
+
this.actionDragTargetNodeUuid = targetNode;
|
|
1168
|
+
this.previousActionDragTargetNodeUuid = targetNode;
|
|
1169
|
+
// Hide canvas preview when over a valid target
|
|
1170
|
+
this.canvasDropPreview = null;
|
|
1171
|
+
// Tell source node to show ghost (we're over a valid target)
|
|
1172
|
+
const sourceElement = this.querySelector(`temba-flow-node[data-node-uuid="${nodeUuid}"]`);
|
|
1173
|
+
if (sourceElement) {
|
|
1174
|
+
sourceElement.dispatchEvent(new CustomEvent('action-show-ghost', {
|
|
1175
|
+
detail: {},
|
|
1176
|
+
bubbles: false
|
|
1177
|
+
}));
|
|
1178
|
+
}
|
|
1179
|
+
// Notify the target node about the drag
|
|
1180
|
+
const targetElement = this.querySelector(`temba-flow-node[data-node-uuid="${targetNode}"]`);
|
|
1181
|
+
if (targetElement) {
|
|
1182
|
+
targetElement.dispatchEvent(new CustomEvent('action-drag-over', {
|
|
1183
|
+
detail: {
|
|
1184
|
+
action,
|
|
1185
|
+
sourceNodeUuid: nodeUuid,
|
|
1186
|
+
actionIndex,
|
|
1187
|
+
mouseX,
|
|
1188
|
+
mouseY,
|
|
1189
|
+
actionHeight
|
|
1190
|
+
},
|
|
1191
|
+
bubbles: false
|
|
1192
|
+
}));
|
|
1193
|
+
}
|
|
1194
|
+
this.requestUpdate();
|
|
1195
|
+
return;
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
// Not over a valid target node, clear any previous target's placeholder
|
|
1199
|
+
if (this.previousActionDragTargetNodeUuid) {
|
|
1200
|
+
const previousElement = this.querySelector(`temba-flow-node[data-node-uuid="${this.previousActionDragTargetNodeUuid}"]`);
|
|
1201
|
+
if (previousElement) {
|
|
1202
|
+
previousElement.dispatchEvent(new CustomEvent('action-drag-leave', {
|
|
1203
|
+
detail: {},
|
|
1204
|
+
bubbles: false
|
|
1205
|
+
}));
|
|
1206
|
+
}
|
|
1207
|
+
this.previousActionDragTargetNodeUuid = null;
|
|
1208
|
+
}
|
|
1209
|
+
this.actionDragTargetNodeUuid = null;
|
|
1210
|
+
// Tell source node to hide ghost (we're not over a valid target)
|
|
1211
|
+
const sourceElement = this.querySelector(`temba-flow-node[data-node-uuid="${nodeUuid}"]`);
|
|
1212
|
+
if (sourceElement) {
|
|
1213
|
+
sourceElement.dispatchEvent(new CustomEvent('action-hide-ghost', {
|
|
1214
|
+
detail: {},
|
|
1215
|
+
bubbles: false
|
|
1216
|
+
}));
|
|
1217
|
+
}
|
|
1218
|
+
// Don't snap to grid for preview - let it follow cursor smoothly
|
|
1219
|
+
const position = this.calculateCanvasDropPosition(mouseX, mouseY, false);
|
|
1220
|
+
this.canvasDropPreview = {
|
|
1221
|
+
action,
|
|
1222
|
+
nodeUuid,
|
|
1223
|
+
actionIndex,
|
|
1224
|
+
position,
|
|
1225
|
+
actionHeight
|
|
1226
|
+
};
|
|
1227
|
+
// Force re-render to update preview position
|
|
1228
|
+
this.requestUpdate();
|
|
1229
|
+
}
|
|
1230
|
+
handleActionDragInternal(_event) {
|
|
1231
|
+
// Clear any previous target's placeholder when returning to internal drag
|
|
1232
|
+
if (this.previousActionDragTargetNodeUuid) {
|
|
1233
|
+
const previousElement = this.querySelector(`temba-flow-node[data-node-uuid="${this.previousActionDragTargetNodeUuid}"]`);
|
|
1234
|
+
if (previousElement) {
|
|
1235
|
+
previousElement.dispatchEvent(new CustomEvent('action-drag-leave', {
|
|
1236
|
+
detail: {},
|
|
1237
|
+
bubbles: false
|
|
1238
|
+
}));
|
|
1239
|
+
}
|
|
1240
|
+
this.previousActionDragTargetNodeUuid = null;
|
|
1241
|
+
}
|
|
1242
|
+
this.canvasDropPreview = null;
|
|
1243
|
+
this.actionDragTargetNodeUuid = null;
|
|
1244
|
+
}
|
|
1245
|
+
handleActionDropExternal(event) {
|
|
1246
|
+
var _b, _c, _d;
|
|
1247
|
+
const { action, nodeUuid, actionIndex, mouseX, mouseY } = event.detail;
|
|
1248
|
+
// Check if we're dropping on an existing execute_actions node
|
|
1249
|
+
const targetNodeUuid = this.actionDragTargetNodeUuid;
|
|
1250
|
+
if (targetNodeUuid && targetNodeUuid !== nodeUuid) {
|
|
1251
|
+
// Dropping on another node - notify the target node to handle the drop
|
|
1252
|
+
const targetElement = this.querySelector(`temba-flow-node[data-node-uuid="${targetNodeUuid}"]`);
|
|
1253
|
+
if (targetElement) {
|
|
1254
|
+
targetElement.dispatchEvent(new CustomEvent('action-drop', {
|
|
1255
|
+
detail: {
|
|
1256
|
+
action,
|
|
1257
|
+
sourceNodeUuid: nodeUuid,
|
|
1258
|
+
actionIndex,
|
|
1259
|
+
mouseX,
|
|
1260
|
+
mouseY
|
|
1261
|
+
},
|
|
1262
|
+
bubbles: false
|
|
1263
|
+
}));
|
|
1264
|
+
}
|
|
1265
|
+
// Clear state
|
|
1266
|
+
this.canvasDropPreview = null;
|
|
1267
|
+
this.actionDragTargetNodeUuid = null;
|
|
1268
|
+
return;
|
|
1269
|
+
}
|
|
1270
|
+
// Not dropping on another node, create a new one on canvas
|
|
1271
|
+
// Snap to grid for the final drop position
|
|
1272
|
+
const position = this.calculateCanvasDropPosition(mouseX, mouseY, true);
|
|
1273
|
+
// remove the action from the original node
|
|
1274
|
+
const originalNode = this.definition.nodes.find((n) => n.uuid === nodeUuid);
|
|
1275
|
+
if (!originalNode)
|
|
1276
|
+
return;
|
|
1277
|
+
const updatedActions = originalNode.actions.filter((_a, idx) => idx !== actionIndex);
|
|
1278
|
+
// if no actions remain, delete the node
|
|
1279
|
+
if (updatedActions.length === 0) {
|
|
1280
|
+
(_b = getStore()) === null || _b === void 0 ? void 0 : _b.getState().removeNodes([nodeUuid]);
|
|
1281
|
+
}
|
|
1282
|
+
else {
|
|
1283
|
+
// update the node
|
|
1284
|
+
const updatedNode = { ...originalNode, actions: updatedActions };
|
|
1285
|
+
(_c = getStore()) === null || _c === void 0 ? void 0 : _c.getState().updateNode(nodeUuid, updatedNode);
|
|
1286
|
+
}
|
|
1287
|
+
// create a new execute_actions node with the dropped action
|
|
1288
|
+
const newNode = {
|
|
1289
|
+
uuid: generateUUID(),
|
|
1290
|
+
actions: [action],
|
|
1291
|
+
exits: [
|
|
1292
|
+
{
|
|
1293
|
+
uuid: generateUUID(),
|
|
1294
|
+
destination_uuid: null
|
|
1295
|
+
}
|
|
1296
|
+
]
|
|
1297
|
+
};
|
|
1298
|
+
const newNodeUI = {
|
|
1299
|
+
position,
|
|
1300
|
+
type: 'execute_actions',
|
|
1301
|
+
config: {}
|
|
1302
|
+
};
|
|
1303
|
+
// add the new node
|
|
1304
|
+
(_d = getStore()) === null || _d === void 0 ? void 0 : _d.getState().addNode(newNode, newNodeUI);
|
|
1305
|
+
// clear the preview
|
|
1306
|
+
this.canvasDropPreview = null;
|
|
1307
|
+
this.actionDragTargetNodeUuid = null;
|
|
1308
|
+
// repaint connections
|
|
1309
|
+
if (this.plumber) {
|
|
1310
|
+
requestAnimationFrame(() => {
|
|
1311
|
+
this.plumber.repaintEverything();
|
|
1312
|
+
});
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
838
1315
|
render() {
|
|
839
|
-
var
|
|
1316
|
+
var _b, _c;
|
|
840
1317
|
// we have to embed our own style since we are in light DOM
|
|
841
1318
|
const style = html `<style>
|
|
842
1319
|
${unsafeCSS(Editor.styles.cssText)}
|
|
843
1320
|
${unsafeCSS(CanvasNode.styles.cssText)}
|
|
844
1321
|
</style>`;
|
|
845
|
-
const stickies = ((
|
|
1322
|
+
const stickies = ((_c = (_b = this.definition) === null || _b === void 0 ? void 0 : _b._ui) === null || _c === void 0 ? void 0 : _c.stickies) || {};
|
|
846
1323
|
return html `${style}
|
|
847
1324
|
<div id="editor">
|
|
848
1325
|
<div
|
|
@@ -853,13 +1330,13 @@ export class Editor extends RapidElement {
|
|
|
853
1330
|
<div id="canvas">
|
|
854
1331
|
${this.definition
|
|
855
1332
|
? repeat(this.definition.nodes, (node) => node.uuid, (node) => {
|
|
856
|
-
var
|
|
857
|
-
const position = ((
|
|
1333
|
+
var _b, _c, _d;
|
|
1334
|
+
const position = ((_c = (_b = this.definition._ui) === null || _b === void 0 ? void 0 : _b.nodes[node.uuid]) === null || _c === void 0 ? void 0 : _c.position) || {
|
|
858
1335
|
left: 0,
|
|
859
1336
|
top: 0
|
|
860
1337
|
};
|
|
861
1338
|
const dragging = this.isDragging &&
|
|
862
|
-
((
|
|
1339
|
+
((_d = this.currentDragItem) === null || _d === void 0 ? void 0 : _d.uuid) === node.uuid;
|
|
863
1340
|
const selected = this.selectedItems.has(node.uuid);
|
|
864
1341
|
return html `<temba-flow-node
|
|
865
1342
|
class="draggable ${dragging ? 'dragging' : ''} ${selected
|
|
@@ -867,6 +1344,7 @@ export class Editor extends RapidElement {
|
|
|
867
1344
|
: ''}"
|
|
868
1345
|
@mousedown=${this.handleMouseDown.bind(this)}
|
|
869
1346
|
uuid=${node.uuid}
|
|
1347
|
+
data-node-uuid=${node.uuid}
|
|
870
1348
|
style="left:${position.left}px; top:${position.top}px"
|
|
871
1349
|
.plumber=${this.plumber}
|
|
872
1350
|
.node=${node}
|
|
@@ -878,9 +1356,9 @@ export class Editor extends RapidElement {
|
|
|
878
1356
|
})
|
|
879
1357
|
: html `<temba-loading></temba-loading>`}
|
|
880
1358
|
${repeat(Object.entries(stickies), ([uuid]) => uuid, ([uuid, sticky]) => {
|
|
881
|
-
var
|
|
1359
|
+
var _b;
|
|
882
1360
|
const position = sticky.position || { left: 0, top: 0 };
|
|
883
|
-
const dragging = this.isDragging && ((
|
|
1361
|
+
const dragging = this.isDragging && ((_b = this.currentDragItem) === null || _b === void 0 ? void 0 : _b.uuid) === uuid;
|
|
884
1362
|
const selected = this.selectedItems.has(uuid);
|
|
885
1363
|
return html `<temba-sticky-note
|
|
886
1364
|
class="draggable ${dragging ? 'dragging' : ''} ${selected
|
|
@@ -895,7 +1373,7 @@ export class Editor extends RapidElement {
|
|
|
895
1373
|
.selected=${selected}
|
|
896
1374
|
></temba-sticky-note>`;
|
|
897
1375
|
})}
|
|
898
|
-
${this.renderSelectionBox()}
|
|
1376
|
+
${this.renderSelectionBox()} ${this.renderCanvasDropPreview()}
|
|
899
1377
|
</div>
|
|
900
1378
|
</div>
|
|
901
1379
|
</div>
|
|
@@ -905,11 +1383,14 @@ export class Editor extends RapidElement {
|
|
|
905
1383
|
.node=${this.editingNode}
|
|
906
1384
|
.nodeUI=${this.editingNodeUI}
|
|
907
1385
|
.action=${this.editingAction}
|
|
908
|
-
@temba-node-saved=${(e) => this.handleNodeSaved(e.detail.node)}
|
|
1386
|
+
@temba-node-saved=${(e) => this.handleNodeSaved(e.detail.node, e.detail.uiConfig)}
|
|
909
1387
|
@temba-action-saved=${(e) => this.handleActionSaved(e.detail.action)}
|
|
910
1388
|
@temba-node-edit-cancelled=${this.handleNodeEditCanceled}
|
|
911
1389
|
></temba-node-editor>`
|
|
912
|
-
: ''}
|
|
1390
|
+
: ''}
|
|
1391
|
+
|
|
1392
|
+
<temba-canvas-menu></temba-canvas-menu>
|
|
1393
|
+
<temba-node-type-selector></temba-node-type-selector> `;
|
|
913
1394
|
}
|
|
914
1395
|
}
|
|
915
1396
|
__decorate([
|
|
@@ -963,4 +1444,19 @@ __decorate([
|
|
|
963
1444
|
__decorate([
|
|
964
1445
|
state()
|
|
965
1446
|
], Editor.prototype, "editingAction", void 0);
|
|
1447
|
+
__decorate([
|
|
1448
|
+
state()
|
|
1449
|
+
], Editor.prototype, "isCreatingNewNode", void 0);
|
|
1450
|
+
__decorate([
|
|
1451
|
+
state()
|
|
1452
|
+
], Editor.prototype, "pendingNodePosition", void 0);
|
|
1453
|
+
__decorate([
|
|
1454
|
+
state()
|
|
1455
|
+
], Editor.prototype, "canvasDropPreview", void 0);
|
|
1456
|
+
__decorate([
|
|
1457
|
+
state()
|
|
1458
|
+
], Editor.prototype, "addActionToNodeUuid", void 0);
|
|
1459
|
+
__decorate([
|
|
1460
|
+
state()
|
|
1461
|
+
], Editor.prototype, "actionDragTargetNodeUuid", void 0);
|
|
966
1462
|
//# sourceMappingURL=Editor.js.map
|