@nyaruka/temba-components 0.139.0 → 0.141.0
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/cla.yml +1 -1
- package/.github/workflows/copilot-setup-steps.yml +6 -1
- package/.lintstagedrc.js +10 -0
- package/CHANGELOG.md +32 -0
- package/demo/data/flows/sample-flow.json +24 -0
- package/dist/locales/es.js +5 -5
- package/dist/locales/es.js.map +1 -1
- package/dist/locales/fr.js +5 -5
- package/dist/locales/fr.js.map +1 -1
- package/dist/locales/locale-codes.js +11 -2
- package/dist/locales/locale-codes.js.map +1 -1
- package/dist/locales/pt.js +5 -5
- package/dist/locales/pt.js.map +1 -1
- package/dist/temba-components.js +702 -338
- package/dist/temba-components.js.map +1 -1
- package/out-tsc/src/display/Chat.js +10 -7
- package/out-tsc/src/display/Chat.js.map +1 -1
- package/out-tsc/src/display/Dropdown.js +3 -1
- package/out-tsc/src/display/Dropdown.js.map +1 -1
- package/out-tsc/src/display/FloatingTab.js +4 -4
- package/out-tsc/src/display/FloatingTab.js.map +1 -1
- package/out-tsc/src/display/Thumbnail.js +163 -5
- package/out-tsc/src/display/Thumbnail.js.map +1 -1
- package/out-tsc/src/flow/CanvasNode.js +65 -23
- package/out-tsc/src/flow/CanvasNode.js.map +1 -1
- package/out-tsc/src/flow/Editor.js +369 -49
- package/out-tsc/src/flow/Editor.js.map +1 -1
- package/out-tsc/src/flow/NodeEditor.js +118 -10
- package/out-tsc/src/flow/NodeEditor.js.map +1 -1
- package/out-tsc/src/flow/Plumber.js +61 -14
- package/out-tsc/src/flow/Plumber.js.map +1 -1
- package/out-tsc/src/flow/StickyNote.js +13 -4
- package/out-tsc/src/flow/StickyNote.js.map +1 -1
- package/out-tsc/src/flow/actions/add_contact_groups.js +4 -1
- package/out-tsc/src/flow/actions/add_contact_groups.js.map +1 -1
- package/out-tsc/src/flow/actions/add_input_labels.js +4 -1
- package/out-tsc/src/flow/actions/add_input_labels.js.map +1 -1
- package/out-tsc/src/flow/actions/audio-player.js +112 -0
- package/out-tsc/src/flow/actions/audio-player.js.map +1 -0
- package/out-tsc/src/flow/actions/enter_flow.js +43 -0
- package/out-tsc/src/flow/actions/enter_flow.js.map +1 -0
- package/out-tsc/src/flow/actions/play_audio.js +57 -4
- package/out-tsc/src/flow/actions/play_audio.js.map +1 -1
- package/out-tsc/src/flow/actions/remove_contact_groups.js +6 -1
- package/out-tsc/src/flow/actions/remove_contact_groups.js.map +1 -1
- package/out-tsc/src/flow/actions/say_msg.js +86 -3
- package/out-tsc/src/flow/actions/say_msg.js.map +1 -1
- package/out-tsc/src/flow/actions/send_broadcast.js +6 -2
- package/out-tsc/src/flow/actions/send_broadcast.js.map +1 -1
- package/out-tsc/src/flow/actions/set_contact_channel.js +13 -0
- package/out-tsc/src/flow/actions/set_contact_channel.js.map +1 -1
- package/out-tsc/src/flow/actions/set_contact_status.js +7 -5
- package/out-tsc/src/flow/actions/set_contact_status.js.map +1 -1
- package/out-tsc/src/flow/actions/start_session.js +10 -3
- package/out-tsc/src/flow/actions/start_session.js.map +1 -1
- package/out-tsc/src/flow/config.js +11 -3
- package/out-tsc/src/flow/config.js.map +1 -1
- package/out-tsc/src/flow/nodes/shared-rules.js +1 -1
- package/out-tsc/src/flow/nodes/shared-rules.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_contact_field.js +18 -5
- package/out-tsc/src/flow/nodes/split_by_contact_field.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_expression.js +1 -1
- package/out-tsc/src/flow/nodes/split_by_expression.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_llm_categorize.js +0 -1
- package/out-tsc/src/flow/nodes/split_by_llm_categorize.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_random.js +0 -1
- package/out-tsc/src/flow/nodes/split_by_random.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_run_result.js +10 -4
- package/out-tsc/src/flow/nodes/split_by_run_result.js.map +1 -1
- package/out-tsc/src/flow/nodes/terminal.js +7 -0
- package/out-tsc/src/flow/nodes/terminal.js.map +1 -0
- package/out-tsc/src/flow/nodes/wait_for_audio.js +77 -0
- package/out-tsc/src/flow/nodes/wait_for_audio.js.map +1 -0
- package/out-tsc/src/flow/nodes/wait_for_dial.js +151 -0
- package/out-tsc/src/flow/nodes/wait_for_dial.js.map +1 -0
- package/out-tsc/src/flow/nodes/wait_for_digits.js +61 -1
- package/out-tsc/src/flow/nodes/wait_for_digits.js.map +1 -1
- package/out-tsc/src/flow/nodes/wait_for_menu.js +173 -2
- package/out-tsc/src/flow/nodes/wait_for_menu.js.map +1 -1
- package/out-tsc/src/flow/nodes/wait_for_response.js +1 -1
- package/out-tsc/src/flow/nodes/wait_for_response.js.map +1 -1
- package/out-tsc/src/flow/operators.js +21 -5
- package/out-tsc/src/flow/operators.js.map +1 -1
- package/out-tsc/src/flow/types.js.map +1 -1
- package/out-tsc/src/flow/utils.js +79 -3
- package/out-tsc/src/flow/utils.js.map +1 -1
- package/out-tsc/src/form/ArrayEditor.js +4 -2
- package/out-tsc/src/form/ArrayEditor.js.map +1 -1
- package/out-tsc/src/form/FieldRenderer.js +56 -0
- package/out-tsc/src/form/FieldRenderer.js.map +1 -1
- package/out-tsc/src/interfaces.js +1 -0
- package/out-tsc/src/interfaces.js.map +1 -1
- package/out-tsc/src/layout/Dialog.js +51 -7
- package/out-tsc/src/layout/Dialog.js.map +1 -1
- package/out-tsc/src/layout/Modax.js +20 -2
- package/out-tsc/src/layout/Modax.js.map +1 -1
- package/out-tsc/src/list/ContentMenu.js +14 -1
- package/out-tsc/src/list/ContentMenu.js.map +1 -1
- package/out-tsc/src/locales/es.js +5 -5
- package/out-tsc/src/locales/es.js.map +1 -1
- package/out-tsc/src/locales/fr.js +5 -5
- package/out-tsc/src/locales/fr.js.map +1 -1
- package/out-tsc/src/locales/locale-codes.js +11 -2
- package/out-tsc/src/locales/locale-codes.js.map +1 -1
- package/out-tsc/src/locales/pt.js +5 -5
- package/out-tsc/src/locales/pt.js.map +1 -1
- package/out-tsc/src/simulator/Simulator.js +21 -4
- package/out-tsc/src/simulator/Simulator.js.map +1 -1
- package/out-tsc/src/store/AppState.js +102 -3
- package/out-tsc/src/store/AppState.js.map +1 -1
- package/out-tsc/test/actions/add_contact_groups.test.js +35 -0
- package/out-tsc/test/actions/add_contact_groups.test.js.map +1 -1
- package/out-tsc/test/actions/add_input_labels.test.js +53 -0
- package/out-tsc/test/actions/add_input_labels.test.js.map +1 -0
- package/out-tsc/test/actions/enter_flow.test.js +71 -0
- package/out-tsc/test/actions/enter_flow.test.js.map +1 -0
- package/out-tsc/test/actions/play_audio.test.js +118 -0
- package/out-tsc/test/actions/play_audio.test.js.map +1 -0
- package/out-tsc/test/actions/remove_contact_groups.test.js +24 -0
- package/out-tsc/test/actions/remove_contact_groups.test.js.map +1 -1
- package/out-tsc/test/actions/say_msg.test.js +158 -0
- package/out-tsc/test/actions/say_msg.test.js.map +1 -0
- package/out-tsc/test/actions/send_broadcast.test.js +41 -0
- package/out-tsc/test/actions/send_broadcast.test.js.map +1 -1
- package/out-tsc/test/actions/set_contact_channel.test.js +67 -0
- package/out-tsc/test/actions/set_contact_channel.test.js.map +1 -0
- package/out-tsc/test/actions/set_contact_field.test.js +52 -0
- package/out-tsc/test/actions/set_contact_field.test.js.map +1 -0
- package/out-tsc/test/actions/set_contact_language.test.js +39 -0
- package/out-tsc/test/actions/set_contact_language.test.js.map +1 -0
- package/out-tsc/test/actions/set_contact_name.test.js +28 -0
- package/out-tsc/test/actions/set_contact_name.test.js.map +1 -0
- package/out-tsc/test/actions/set_contact_status.test.js +44 -0
- package/out-tsc/test/actions/set_contact_status.test.js.map +1 -0
- package/out-tsc/test/actions/set_run_result.test.js +47 -0
- package/out-tsc/test/actions/set_run_result.test.js.map +1 -0
- package/out-tsc/test/actions/start_session.test.js +76 -0
- package/out-tsc/test/actions/start_session.test.js.map +1 -1
- package/out-tsc/test/nodes/split_by_contact_field.test.js +50 -0
- package/out-tsc/test/nodes/split_by_contact_field.test.js.map +1 -1
- package/out-tsc/test/nodes/split_by_run_result.test.js +82 -0
- package/out-tsc/test/nodes/split_by_run_result.test.js.map +1 -1
- package/out-tsc/test/nodes/split_by_ticket.test.js +139 -0
- package/out-tsc/test/nodes/split_by_ticket.test.js.map +1 -0
- package/out-tsc/test/nodes/split_by_webhook.test.js +111 -0
- package/out-tsc/test/nodes/split_by_webhook.test.js.map +1 -0
- package/out-tsc/test/nodes/wait_for_audio.test.js +156 -0
- package/out-tsc/test/nodes/wait_for_audio.test.js.map +1 -0
- package/out-tsc/test/nodes/wait_for_dial.test.js +336 -0
- package/out-tsc/test/nodes/wait_for_dial.test.js.map +1 -0
- package/out-tsc/test/nodes/wait_for_digits.test.js +198 -84
- package/out-tsc/test/nodes/wait_for_digits.test.js.map +1 -1
- package/out-tsc/test/nodes/wait_for_menu.test.js +340 -0
- package/out-tsc/test/nodes/wait_for_menu.test.js.map +1 -0
- package/out-tsc/test/temba-flow-collision.test.js +261 -6
- package/out-tsc/test/temba-flow-collision.test.js.map +1 -1
- package/out-tsc/test/temba-flow-editor.test.js +187 -0
- package/out-tsc/test/temba-flow-editor.test.js.map +1 -1
- package/out-tsc/test/temba-flow-plumber.test.js +19 -0
- package/out-tsc/test/temba-flow-plumber.test.js.map +1 -1
- package/out-tsc/test/temba-node-type-selector.test.js +6 -6
- package/out-tsc/test/temba-node-type-selector.test.js.map +1 -1
- package/out-tsc/test/temba-select.test.js +4 -1
- package/out-tsc/test/temba-select.test.js.map +1 -1
- package/out-tsc/test/utils.test.js +4 -2
- package/out-tsc/test/utils.test.js.map +1 -1
- package/package.json +3 -9
- 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/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/add_input_labels/editor/multiple-labels.png +0 -0
- package/screenshots/truth/actions/add_input_labels/editor/single-label.png +0 -0
- package/screenshots/truth/actions/add_input_labels/render/multiple-labels.png +0 -0
- package/screenshots/truth/actions/add_input_labels/render/single-label.png +0 -0
- package/screenshots/truth/actions/enter_flow/editor/basic-flow.png +0 -0
- package/screenshots/truth/actions/enter_flow/editor/long-flow-name.png +0 -0
- package/screenshots/truth/actions/enter_flow/render/basic-flow.png +0 -0
- package/screenshots/truth/actions/enter_flow/render/long-flow-name.png +0 -0
- package/screenshots/truth/actions/play_audio/editor/expression-url.png +0 -0
- package/screenshots/truth/actions/play_audio/editor/static-url.png +0 -0
- package/screenshots/truth/actions/play_audio/render/expression-url.png +0 -0
- package/screenshots/truth/actions/play_audio/render/static-url.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/say_msg/editor/multiline-text.png +0 -0
- package/screenshots/truth/actions/say_msg/editor/simple-text.png +0 -0
- package/screenshots/truth/actions/say_msg/editor/text-with-audio-url.png +0 -0
- package/screenshots/truth/actions/say_msg/render/multiline-text.png +0 -0
- package/screenshots/truth/actions/say_msg/render/simple-text.png +0 -0
- package/screenshots/truth/actions/say_msg/render/text-with-audio-url.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_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/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/set_contact_channel/editor/sms-channel.png +0 -0
- package/screenshots/truth/actions/set_contact_channel/editor/whatsapp-channel.png +0 -0
- package/screenshots/truth/actions/set_contact_channel/render/sms-channel.png +0 -0
- package/screenshots/truth/actions/set_contact_channel/render/whatsapp-channel.png +0 -0
- package/screenshots/truth/actions/set_contact_field/editor/clear-value.png +0 -0
- package/screenshots/truth/actions/set_contact_field/editor/set-value.png +0 -0
- package/screenshots/truth/actions/set_contact_field/render/clear-value.png +0 -0
- package/screenshots/truth/actions/set_contact_field/render/set-value.png +0 -0
- package/screenshots/truth/actions/set_contact_language/editor/english.png +0 -0
- package/screenshots/truth/actions/set_contact_language/editor/french.png +0 -0
- package/screenshots/truth/actions/set_contact_language/render/english.png +0 -0
- package/screenshots/truth/actions/set_contact_language/render/french.png +0 -0
- package/screenshots/truth/actions/set_contact_name/editor/expression-name.png +0 -0
- package/screenshots/truth/actions/set_contact_name/editor/static-name.png +0 -0
- package/screenshots/truth/actions/set_contact_name/render/expression-name.png +0 -0
- package/screenshots/truth/actions/set_contact_name/render/static-name.png +0 -0
- package/screenshots/truth/actions/set_contact_status/editor/active.png +0 -0
- package/screenshots/truth/actions/set_contact_status/editor/archived.png +0 -0
- package/screenshots/truth/actions/set_contact_status/editor/blocked.png +0 -0
- package/screenshots/truth/actions/set_contact_status/render/active.png +0 -0
- package/screenshots/truth/actions/set_contact_status/render/archived.png +0 -0
- package/screenshots/truth/actions/set_contact_status/render/blocked.png +0 -0
- package/screenshots/truth/actions/set_run_result/editor/expression-value.png +0 -0
- package/screenshots/truth/actions/set_run_result/editor/with-category.png +0 -0
- package/screenshots/truth/actions/set_run_result/render/expression-value.png +0 -0
- package/screenshots/truth/actions/set_run_result/render/with-category.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/editor/router.png +0 -0
- package/screenshots/truth/editor/wait.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_audio/editor/basic-audio-wait.png +0 -0
- package/screenshots/truth/nodes/wait_for_audio/render/basic-audio-wait.png +0 -0
- package/screenshots/truth/nodes/wait_for_dial/editor/basic-dial.png +0 -0
- package/screenshots/truth/nodes/wait_for_dial/editor/dial-with-limits.png +0 -0
- package/screenshots/truth/nodes/wait_for_dial/render/basic-dial.png +0 -0
- package/screenshots/truth/nodes/wait_for_dial/render/dial-with-limits.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/digits-with-rules.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/digits-with-rules.png +0 -0
- package/screenshots/truth/nodes/wait_for_menu/editor/menu-with-digits.png +0 -0
- package/screenshots/truth/nodes/wait_for_menu/render/menu-with-digits.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/display/Chat.ts +13 -7
- package/src/display/Dropdown.ts +3 -1
- package/src/display/FloatingTab.ts +4 -4
- package/src/display/Thumbnail.ts +162 -2
- package/src/flow/CanvasNode.ts +70 -24
- package/src/flow/Editor.ts +440 -99
- package/src/flow/NodeEditor.ts +137 -9
- package/src/flow/Plumber.ts +89 -14
- package/src/flow/StickyNote.ts +14 -4
- package/src/flow/actions/add_contact_groups.ts +4 -1
- package/src/flow/actions/add_input_labels.ts +4 -1
- package/src/flow/actions/audio-player.ts +127 -0
- package/src/flow/actions/enter_flow.ts +44 -0
- package/src/flow/actions/play_audio.ts +64 -5
- package/src/flow/actions/remove_contact_groups.ts +6 -1
- package/src/flow/actions/say_msg.ts +94 -4
- package/src/flow/actions/send_broadcast.ts +6 -2
- package/src/flow/actions/set_contact_channel.ts +13 -1
- package/src/flow/actions/set_contact_status.ts +7 -5
- package/src/flow/actions/start_session.ts +10 -3
- package/src/flow/config.ts +11 -3
- package/src/flow/nodes/shared-rules.ts +1 -1
- package/src/flow/nodes/split_by_contact_field.ts +16 -5
- package/src/flow/nodes/split_by_expression.ts +1 -1
- package/src/flow/nodes/split_by_llm_categorize.ts +0 -1
- package/src/flow/nodes/split_by_random.ts +0 -1
- package/src/flow/nodes/split_by_run_result.ts +10 -4
- package/src/flow/nodes/terminal.ts +9 -0
- package/src/flow/nodes/wait_for_audio.ts +88 -0
- package/src/flow/nodes/wait_for_dial.ts +176 -0
- package/src/flow/nodes/wait_for_digits.ts +87 -2
- package/src/flow/nodes/wait_for_menu.ts +209 -3
- package/src/flow/nodes/wait_for_response.ts +1 -1
- package/src/flow/operators.ts +23 -5
- package/src/flow/types.ts +23 -1
- package/src/flow/utils.ts +82 -3
- package/src/form/ArrayEditor.ts +4 -2
- package/src/form/FieldRenderer.ts +71 -1
- package/src/interfaces.ts +2 -1
- package/src/layout/Dialog.ts +52 -7
- package/src/layout/Modax.ts +19 -2
- package/src/list/ContentMenu.ts +15 -1
- package/src/locales/es.ts +18 -13
- package/src/locales/fr.ts +18 -13
- package/src/locales/locale-codes.ts +11 -2
- package/src/locales/pt.ts +18 -13
- package/src/simulator/Simulator.ts +25 -4
- package/src/store/AppState.ts +120 -1
- package/src/store/flow-definition.d.ts +2 -0
- package/test/actions/add_contact_groups.test.ts +38 -0
- package/test/actions/add_input_labels.test.ts +67 -0
- package/test/actions/enter_flow.test.ts +88 -0
- package/test/actions/play_audio.test.ts +155 -0
- package/test/actions/remove_contact_groups.test.ts +29 -0
- package/test/actions/say_msg.test.ts +196 -0
- package/test/actions/send_broadcast.test.ts +44 -0
- package/test/actions/set_contact_channel.test.ts +88 -0
- package/test/actions/set_contact_field.test.ts +68 -0
- package/test/actions/set_contact_language.test.ts +55 -0
- package/test/actions/set_contact_name.test.ts +39 -0
- package/test/actions/set_contact_status.test.ts +64 -0
- package/test/actions/set_run_result.test.ts +61 -0
- package/test/actions/start_session.test.ts +82 -0
- package/test/nodes/split_by_contact_field.test.ts +59 -0
- package/test/nodes/split_by_run_result.test.ts +100 -0
- package/test/nodes/split_by_ticket.test.ts +157 -0
- package/test/nodes/split_by_webhook.test.ts +131 -0
- package/test/nodes/wait_for_audio.test.ts +182 -0
- package/test/nodes/wait_for_dial.test.ts +382 -0
- package/test/nodes/wait_for_digits.test.ts +233 -109
- package/test/nodes/wait_for_menu.test.ts +383 -0
- package/test/temba-flow-collision.test.ts +286 -6
- package/test/temba-flow-editor.test.ts +240 -0
- package/test/temba-flow-plumber.test.ts +62 -0
- package/test/temba-node-type-selector.test.ts +6 -6
- package/test/temba-select.test.ts +6 -1
- package/test/utils.test.ts +4 -2
- 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/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/src/flow/Editor.ts
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
import { getStore } from '../store/Store';
|
|
12
12
|
import {
|
|
13
13
|
AppState,
|
|
14
|
+
FlowIssue,
|
|
14
15
|
fromStore,
|
|
15
16
|
zustand,
|
|
16
17
|
FLOW_SPEC_VERSION
|
|
@@ -18,7 +19,20 @@ import {
|
|
|
18
19
|
import { RapidElement } from '../RapidElement';
|
|
19
20
|
import { repeat } from 'lit-html/directives/repeat.js';
|
|
20
21
|
import { CustomEventType, Workspace } from '../interfaces';
|
|
21
|
-
import {
|
|
22
|
+
import {
|
|
23
|
+
generateUUID,
|
|
24
|
+
postJSON,
|
|
25
|
+
fetchResults,
|
|
26
|
+
getClasses,
|
|
27
|
+
WebResponse
|
|
28
|
+
} from '../utils';
|
|
29
|
+
import {
|
|
30
|
+
formatIssueMessage,
|
|
31
|
+
getNodeBounds,
|
|
32
|
+
calculateReflowPositions,
|
|
33
|
+
NodeBounds,
|
|
34
|
+
snapToGrid
|
|
35
|
+
} from './utils';
|
|
22
36
|
import { ACTION_CONFIG, NODE_CONFIG } from './config';
|
|
23
37
|
|
|
24
38
|
interface Revision {
|
|
@@ -49,12 +63,6 @@ import { Dialog } from '../layout/Dialog';
|
|
|
49
63
|
|
|
50
64
|
import { CanvasMenu, CanvasMenuSelection } from './CanvasMenu';
|
|
51
65
|
import { NodeTypeSelector, NodeTypeSelection } from './NodeTypeSelector';
|
|
52
|
-
import {
|
|
53
|
-
getNodeBounds,
|
|
54
|
-
calculateReflowPositions,
|
|
55
|
-
NodeBounds,
|
|
56
|
-
snapToGrid
|
|
57
|
-
} from './utils';
|
|
58
66
|
import { FloatingWindow } from '../layout/FloatingWindow';
|
|
59
67
|
|
|
60
68
|
export function findNodeForExit(
|
|
@@ -70,7 +78,7 @@ export function findNodeForExit(
|
|
|
70
78
|
return null;
|
|
71
79
|
}
|
|
72
80
|
|
|
73
|
-
const SAVE_QUIET_TIME =
|
|
81
|
+
const SAVE_QUIET_TIME = 2000;
|
|
74
82
|
|
|
75
83
|
export interface DraggableItem {
|
|
76
84
|
uuid: string;
|
|
@@ -173,6 +181,9 @@ export class Editor extends RapidElement {
|
|
|
173
181
|
@fromStore(zustand, (state: AppState) => state.getCurrentActivity())
|
|
174
182
|
private activityData!: any;
|
|
175
183
|
|
|
184
|
+
@fromStore(zustand, (state: AppState) => state.flowInfo?.issues || [])
|
|
185
|
+
private flowIssues!: FlowIssue[];
|
|
186
|
+
|
|
176
187
|
// Drag state
|
|
177
188
|
@state()
|
|
178
189
|
private isDragging = false;
|
|
@@ -217,6 +228,9 @@ export class Editor extends RapidElement {
|
|
|
217
228
|
private connectionSourceX: number | null = null;
|
|
218
229
|
private connectionSourceY: number | null = null;
|
|
219
230
|
|
|
231
|
+
@state()
|
|
232
|
+
private issuesWindowHidden = true;
|
|
233
|
+
|
|
220
234
|
@state()
|
|
221
235
|
private localizationWindowHidden = true;
|
|
222
236
|
|
|
@@ -252,6 +266,12 @@ export class Editor extends RapidElement {
|
|
|
252
266
|
@state()
|
|
253
267
|
private isLoadingRevisions = false;
|
|
254
268
|
|
|
269
|
+
@state()
|
|
270
|
+
private isSaving = false;
|
|
271
|
+
|
|
272
|
+
@state()
|
|
273
|
+
private saveError: string | null = null;
|
|
274
|
+
|
|
255
275
|
private preRevertState: {
|
|
256
276
|
definition: FlowDefinition;
|
|
257
277
|
dirtyDate: Date | null;
|
|
@@ -269,6 +289,8 @@ export class Editor extends RapidElement {
|
|
|
269
289
|
@state()
|
|
270
290
|
private editingAction: Action | null = null;
|
|
271
291
|
|
|
292
|
+
private dialogOrigin: { x: number; y: number } | null = null;
|
|
293
|
+
|
|
272
294
|
@state()
|
|
273
295
|
private isCreatingNewNode = false;
|
|
274
296
|
|
|
@@ -351,12 +373,23 @@ export class Editor extends RapidElement {
|
|
|
351
373
|
|
|
352
374
|
static get styles() {
|
|
353
375
|
return css`
|
|
376
|
+
#editor-container {
|
|
377
|
+
position: relative;
|
|
378
|
+
flex: 1;
|
|
379
|
+
display: flex;
|
|
380
|
+
min-height: 0;
|
|
381
|
+
}
|
|
382
|
+
|
|
354
383
|
#editor {
|
|
355
384
|
overflow: scroll;
|
|
356
385
|
flex: 1;
|
|
357
386
|
-webkit-font-smoothing: antialiased;
|
|
358
387
|
}
|
|
359
388
|
|
|
389
|
+
temba-floating-tab {
|
|
390
|
+
--floating-tab-right: 15px;
|
|
391
|
+
}
|
|
392
|
+
|
|
360
393
|
#grid {
|
|
361
394
|
position: relative;
|
|
362
395
|
background-color: #f9f9f9;
|
|
@@ -490,7 +523,7 @@ export class Editor extends RapidElement {
|
|
|
490
523
|
font-weight: 600;
|
|
491
524
|
line-height: 0.9;
|
|
492
525
|
cursor: pointer;
|
|
493
|
-
z-index:
|
|
526
|
+
z-index: 10;
|
|
494
527
|
pointer-events: auto;
|
|
495
528
|
white-space: nowrap;
|
|
496
529
|
user-select: none;
|
|
@@ -805,6 +838,91 @@ export class Editor extends RapidElement {
|
|
|
805
838
|
color: #9ca3af;
|
|
806
839
|
white-space: nowrap;
|
|
807
840
|
}
|
|
841
|
+
|
|
842
|
+
.issue-list-item {
|
|
843
|
+
display: flex;
|
|
844
|
+
align-items: center;
|
|
845
|
+
gap: 8px;
|
|
846
|
+
padding: 8px;
|
|
847
|
+
border-radius: 4px;
|
|
848
|
+
cursor: pointer;
|
|
849
|
+
font-size: 13px;
|
|
850
|
+
color: #333;
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
.issue-list-item:hover {
|
|
854
|
+
background: #fff5f5;
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
.issue-list-item temba-icon {
|
|
858
|
+
color: tomato;
|
|
859
|
+
flex-shrink: 0;
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
.empty-flow {
|
|
863
|
+
position: sticky;
|
|
864
|
+
top: 80px;
|
|
865
|
+
left: 0;
|
|
866
|
+
right: 0;
|
|
867
|
+
height: 0;
|
|
868
|
+
display: flex;
|
|
869
|
+
justify-content: center;
|
|
870
|
+
pointer-events: none;
|
|
871
|
+
z-index: 50;
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
.empty-flow-content {
|
|
875
|
+
display: flex;
|
|
876
|
+
flex-direction: column;
|
|
877
|
+
align-items: center;
|
|
878
|
+
gap: 16px;
|
|
879
|
+
text-align: center;
|
|
880
|
+
pointer-events: auto;
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
.empty-flow-title {
|
|
884
|
+
font-size: 18px;
|
|
885
|
+
font-weight: 600;
|
|
886
|
+
color: #374151;
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
.empty-flow-description {
|
|
890
|
+
font-size: 14px;
|
|
891
|
+
color: #6b7280;
|
|
892
|
+
max-width: 320px;
|
|
893
|
+
line-height: 1.5;
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
.empty-flow-button {
|
|
897
|
+
background: var(--color-primary-dark);
|
|
898
|
+
border: none;
|
|
899
|
+
color: #fff;
|
|
900
|
+
padding: 10px 20px;
|
|
901
|
+
border-radius: var(--curvature);
|
|
902
|
+
font-size: 14px;
|
|
903
|
+
font-weight: 600;
|
|
904
|
+
cursor: pointer;
|
|
905
|
+
transition: opacity 0.2s ease;
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
.empty-flow-button:hover {
|
|
909
|
+
opacity: 0.9;
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
.save-indicator {
|
|
913
|
+
position: absolute;
|
|
914
|
+
top: 8px;
|
|
915
|
+
right: 16px;
|
|
916
|
+
padding: 6px 10px;
|
|
917
|
+
z-index: 10000;
|
|
918
|
+
pointer-events: none;
|
|
919
|
+
opacity: 0;
|
|
920
|
+
transition: opacity 0.15s ease-in-out;
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
.save-indicator.visible {
|
|
924
|
+
opacity: 1;
|
|
925
|
+
}
|
|
808
926
|
`;
|
|
809
927
|
}
|
|
810
928
|
|
|
@@ -820,6 +938,7 @@ export class Editor extends RapidElement {
|
|
|
820
938
|
this.setupGlobalEventListeners();
|
|
821
939
|
if (changes.has('flow')) {
|
|
822
940
|
getStore().getState().fetchRevision(`/flow/revisions/${this.flow}`);
|
|
941
|
+
this.fetchRevisions();
|
|
823
942
|
}
|
|
824
943
|
|
|
825
944
|
this.plumber.on('connection:drag', (connection: any) => {
|
|
@@ -984,10 +1103,16 @@ export class Editor extends RapidElement {
|
|
|
984
1103
|
|
|
985
1104
|
if (changes.has('dirtyDate')) {
|
|
986
1105
|
if (this.dirtyDate) {
|
|
1106
|
+
this.isSaving = true;
|
|
987
1107
|
this.debouncedSave();
|
|
988
1108
|
}
|
|
989
1109
|
}
|
|
990
1110
|
|
|
1111
|
+
if (changes.has('saveError') && this.saveError) {
|
|
1112
|
+
this.showSaveErrorDialog(this.saveError);
|
|
1113
|
+
this.saveError = null;
|
|
1114
|
+
}
|
|
1115
|
+
|
|
991
1116
|
if (changes.has('languageCode')) {
|
|
992
1117
|
this.translationCache.clear();
|
|
993
1118
|
}
|
|
@@ -1033,10 +1158,16 @@ export class Editor extends RapidElement {
|
|
|
1033
1158
|
|
|
1034
1159
|
private saveChanges(definitionOverride?: FlowDefinition): Promise<void> {
|
|
1035
1160
|
const definition = definitionOverride || this.definition;
|
|
1036
|
-
|
|
1161
|
+
this.isSaving = true;
|
|
1162
|
+
|
|
1037
1163
|
return getStore()
|
|
1038
1164
|
.postJSON(`/flow/revisions/${this.flow}/`, definition)
|
|
1039
1165
|
.then((response) => {
|
|
1166
|
+
if (response.status < 200 || response.status >= 300) {
|
|
1167
|
+
this.saveError = this.extractErrorMessage(response);
|
|
1168
|
+
return;
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1040
1171
|
// Update flow info and revision with the response data
|
|
1041
1172
|
if (response.json) {
|
|
1042
1173
|
const state = getStore().getState();
|
|
@@ -1049,17 +1180,58 @@ export class Editor extends RapidElement {
|
|
|
1049
1180
|
state.setRevision(response.json.revision.revision);
|
|
1050
1181
|
}
|
|
1051
1182
|
|
|
1052
|
-
//
|
|
1053
|
-
|
|
1054
|
-
this.fetchRevisions();
|
|
1055
|
-
}
|
|
1183
|
+
// Refresh revisions list so the tab visibility stays up to date
|
|
1184
|
+
this.fetchRevisions();
|
|
1056
1185
|
}
|
|
1186
|
+
|
|
1187
|
+
getStore().getState().setDirtyDate(null);
|
|
1057
1188
|
})
|
|
1058
1189
|
.catch((error) => {
|
|
1059
1190
|
console.error('Failed to save flow:', error);
|
|
1191
|
+
if (error instanceof Response) {
|
|
1192
|
+
this.saveError = `Server error (${error.status}). Your changes have not been saved.`;
|
|
1193
|
+
} else {
|
|
1194
|
+
this.saveError =
|
|
1195
|
+
'Unable to reach the server. Please check your connection and try again.';
|
|
1196
|
+
}
|
|
1197
|
+
})
|
|
1198
|
+
.finally(() => {
|
|
1199
|
+
this.isSaving = false;
|
|
1060
1200
|
});
|
|
1201
|
+
}
|
|
1061
1202
|
|
|
1062
|
-
|
|
1203
|
+
private extractErrorMessage(response: WebResponse): string {
|
|
1204
|
+
if (response.json) {
|
|
1205
|
+
if (typeof response.json.detail === 'string') {
|
|
1206
|
+
return response.json.detail;
|
|
1207
|
+
}
|
|
1208
|
+
if (typeof response.json.error === 'string') {
|
|
1209
|
+
return response.json.error;
|
|
1210
|
+
}
|
|
1211
|
+
if (typeof response.json.description === 'string') {
|
|
1212
|
+
return response.json.description;
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
return `Save failed with status ${response.status}.`;
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
private showSaveErrorDialog(message: string): void {
|
|
1219
|
+
const dialog = document.createElement('temba-dialog') as Dialog;
|
|
1220
|
+
dialog.header = 'Save Failed';
|
|
1221
|
+
dialog.primaryButtonName = '';
|
|
1222
|
+
dialog.cancelButtonName = 'Dismiss';
|
|
1223
|
+
|
|
1224
|
+
const content = document.createElement('div');
|
|
1225
|
+
content.style.cssText = 'padding: 20px; font-size: 14px; line-height: 1.5;';
|
|
1226
|
+
content.textContent = message;
|
|
1227
|
+
dialog.appendChild(content);
|
|
1228
|
+
|
|
1229
|
+
document.body.appendChild(dialog);
|
|
1230
|
+
dialog.open = true;
|
|
1231
|
+
|
|
1232
|
+
dialog.addEventListener('temba-dialog-hidden', () => {
|
|
1233
|
+
document.body.removeChild(dialog);
|
|
1234
|
+
});
|
|
1063
1235
|
}
|
|
1064
1236
|
|
|
1065
1237
|
private startActivityFetching(): void {
|
|
@@ -1095,6 +1267,11 @@ export class Editor extends RapidElement {
|
|
|
1095
1267
|
}
|
|
1096
1268
|
const state = store.getState();
|
|
1097
1269
|
state.fetchActivity(activityEndpoint).then(() => {
|
|
1270
|
+
// Guard against responses arriving after the editor is disconnected
|
|
1271
|
+
if (!this.isConnected) {
|
|
1272
|
+
return;
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1098
1275
|
// Schedule next fetch with exponential backoff (max 5 minutes)
|
|
1099
1276
|
this.activityInterval = Math.min(60000 * 5, this.activityInterval + 100);
|
|
1100
1277
|
|
|
@@ -1131,6 +1308,10 @@ export class Editor extends RapidElement {
|
|
|
1131
1308
|
if (canvas) {
|
|
1132
1309
|
canvas.removeEventListener('contextmenu', this.boundCanvasContextMenu);
|
|
1133
1310
|
}
|
|
1311
|
+
|
|
1312
|
+
// Clear all flow-specific data from the store so stale data
|
|
1313
|
+
// isn't briefly visible when a different flow is opened.
|
|
1314
|
+
zustand.getState().clearFlowData();
|
|
1134
1315
|
}
|
|
1135
1316
|
|
|
1136
1317
|
private setupGlobalEventListeners(): void {
|
|
@@ -1219,6 +1400,7 @@ export class Editor extends RapidElement {
|
|
|
1219
1400
|
if (event.button !== 0) return;
|
|
1220
1401
|
|
|
1221
1402
|
if (this.isReadOnly()) return;
|
|
1403
|
+
this.blurActiveContentEditable();
|
|
1222
1404
|
|
|
1223
1405
|
const element = event.currentTarget as HTMLElement;
|
|
1224
1406
|
// Only start dragging if clicking on the element itself, not on exits or other interactive elements
|
|
@@ -1288,8 +1470,22 @@ export class Editor extends RapidElement {
|
|
|
1288
1470
|
this.handleCanvasMouseDown(event);
|
|
1289
1471
|
}
|
|
1290
1472
|
|
|
1473
|
+
private blurActiveContentEditable(): void {
|
|
1474
|
+
let active: Element | null = document.activeElement;
|
|
1475
|
+
while (active?.shadowRoot?.activeElement) {
|
|
1476
|
+
active = active.shadowRoot.activeElement;
|
|
1477
|
+
}
|
|
1478
|
+
if (
|
|
1479
|
+
active instanceof HTMLElement &&
|
|
1480
|
+
active.getAttribute('contenteditable') === 'true'
|
|
1481
|
+
) {
|
|
1482
|
+
active.blur();
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1291
1486
|
private handleCanvasMouseDown(event: MouseEvent): void {
|
|
1292
1487
|
if (this.isReadOnly()) return;
|
|
1488
|
+
this.blurActiveContentEditable();
|
|
1293
1489
|
|
|
1294
1490
|
const target = event.target as HTMLElement;
|
|
1295
1491
|
if (target.id === 'canvas' || target.id === 'grid') {
|
|
@@ -1974,6 +2170,28 @@ export class Editor extends RapidElement {
|
|
|
1974
2170
|
}
|
|
1975
2171
|
}
|
|
1976
2172
|
|
|
2173
|
+
private handleEmptyFlowClick(event: MouseEvent): void {
|
|
2174
|
+
const editor = this.querySelector('#editor') as HTMLElement;
|
|
2175
|
+
if (!editor) return;
|
|
2176
|
+
|
|
2177
|
+
// Scroll to top-left
|
|
2178
|
+
editor.scrollTo({ left: 0, top: 0, behavior: 'smooth' });
|
|
2179
|
+
|
|
2180
|
+
// Place node at top-left of the canvas
|
|
2181
|
+
const nodeLeft = 0;
|
|
2182
|
+
const nodeTop = 0;
|
|
2183
|
+
|
|
2184
|
+
const canvasMenu = this.querySelector('temba-canvas-menu') as CanvasMenu;
|
|
2185
|
+
if (canvasMenu) {
|
|
2186
|
+
const button = event.currentTarget as HTMLElement;
|
|
2187
|
+
const rect = button.getBoundingClientRect();
|
|
2188
|
+
const menuWidth = 265;
|
|
2189
|
+
const menuX = rect.left + rect.width / 2 - menuWidth / 2;
|
|
2190
|
+
const menuY = rect.bottom + 8;
|
|
2191
|
+
canvasMenu.show(menuX, menuY, { x: nodeLeft, y: nodeTop }, false);
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
2194
|
+
|
|
1977
2195
|
private handleCanvasMenuSelection(event: CustomEvent): void {
|
|
1978
2196
|
const selection = event.detail as CanvasMenuSelection;
|
|
1979
2197
|
const store = getStore();
|
|
@@ -2160,6 +2378,10 @@ export class Editor extends RapidElement {
|
|
|
2160
2378
|
private handleActionEditRequested(event: CustomEvent): void {
|
|
2161
2379
|
// For action editing, we set the action and find the corresponding node
|
|
2162
2380
|
this.editingAction = event.detail.action;
|
|
2381
|
+
this.dialogOrigin =
|
|
2382
|
+
event.detail.originX != null
|
|
2383
|
+
? { x: event.detail.originX, y: event.detail.originY }
|
|
2384
|
+
: null;
|
|
2163
2385
|
|
|
2164
2386
|
// Find the node that contains this action
|
|
2165
2387
|
const nodeUuid = event.detail.nodeUuid;
|
|
@@ -2205,6 +2427,10 @@ export class Editor extends RapidElement {
|
|
|
2205
2427
|
private handleNodeEditRequested(event: CustomEvent): void {
|
|
2206
2428
|
this.editingNode = event.detail.node;
|
|
2207
2429
|
this.editingNodeUI = event.detail.nodeUI;
|
|
2430
|
+
this.dialogOrigin =
|
|
2431
|
+
event.detail.originX != null
|
|
2432
|
+
? { x: event.detail.originX, y: event.detail.originY }
|
|
2433
|
+
: null;
|
|
2208
2434
|
}
|
|
2209
2435
|
|
|
2210
2436
|
private handleNodeDeleted(event: CustomEvent): void {
|
|
@@ -2294,6 +2520,7 @@ export class Editor extends RapidElement {
|
|
|
2294
2520
|
this.editingNode = null;
|
|
2295
2521
|
this.editingNodeUI = null;
|
|
2296
2522
|
this.editingAction = null;
|
|
2523
|
+
this.dialogOrigin = null;
|
|
2297
2524
|
}
|
|
2298
2525
|
|
|
2299
2526
|
private handleActionEditCanceled(): void {
|
|
@@ -2948,6 +3175,7 @@ export class Editor extends RapidElement {
|
|
|
2948
3175
|
|
|
2949
3176
|
this.localizationWindowHidden = false;
|
|
2950
3177
|
this.revisionsWindowHidden = true;
|
|
3178
|
+
this.issuesWindowHidden = true;
|
|
2951
3179
|
|
|
2952
3180
|
const alreadySelected = languages.some(
|
|
2953
3181
|
(lang) => lang.code === this.languageCode
|
|
@@ -3210,11 +3438,47 @@ export class Editor extends RapidElement {
|
|
|
3210
3438
|
this.autoTranslating = false;
|
|
3211
3439
|
}
|
|
3212
3440
|
|
|
3441
|
+
private handleIssuesTabClick(): void {
|
|
3442
|
+
this.issuesWindowHidden = false;
|
|
3443
|
+
this.revisionsWindowHidden = true;
|
|
3444
|
+
this.localizationWindowHidden = true;
|
|
3445
|
+
}
|
|
3446
|
+
|
|
3447
|
+
private handleIssuesWindowClosed(): void {
|
|
3448
|
+
this.issuesWindowHidden = true;
|
|
3449
|
+
}
|
|
3450
|
+
|
|
3451
|
+
private handleIssueItemClick(issue: FlowIssue): void {
|
|
3452
|
+
const issuesWindow = document.getElementById(
|
|
3453
|
+
'issues-window'
|
|
3454
|
+
) as FloatingWindow;
|
|
3455
|
+
issuesWindow?.handleClose();
|
|
3456
|
+
this.issuesWindowHidden = true;
|
|
3457
|
+
|
|
3458
|
+
this.focusNode(issue.node_uuid);
|
|
3459
|
+
|
|
3460
|
+
const node = this.definition.nodes.find((n) => n.uuid === issue.node_uuid);
|
|
3461
|
+
if (!node) return;
|
|
3462
|
+
|
|
3463
|
+
if (issue.action_uuid) {
|
|
3464
|
+
const action = node.actions?.find((a) => a.uuid === issue.action_uuid);
|
|
3465
|
+
if (action) {
|
|
3466
|
+
this.editingAction = action;
|
|
3467
|
+
this.editingNode = node;
|
|
3468
|
+
this.editingNodeUI = this.definition._ui.nodes[issue.node_uuid];
|
|
3469
|
+
}
|
|
3470
|
+
} else {
|
|
3471
|
+
this.editingNode = node;
|
|
3472
|
+
this.editingNodeUI = this.definition._ui.nodes[issue.node_uuid];
|
|
3473
|
+
}
|
|
3474
|
+
}
|
|
3475
|
+
|
|
3213
3476
|
private handleRevisionsTabClick(): void {
|
|
3214
3477
|
if (this.revisionsWindowHidden) {
|
|
3215
3478
|
this.fetchRevisions();
|
|
3216
3479
|
this.revisionsWindowHidden = false;
|
|
3217
|
-
this.
|
|
3480
|
+
this.issuesWindowHidden = true;
|
|
3481
|
+
this.localizationWindowHidden = true;
|
|
3218
3482
|
}
|
|
3219
3483
|
}
|
|
3220
3484
|
|
|
@@ -3328,14 +3592,60 @@ export class Editor extends RapidElement {
|
|
|
3328
3592
|
getStore().getState().fetchRevision(`/flow/revisions/${this.flow}`);
|
|
3329
3593
|
}
|
|
3330
3594
|
|
|
3595
|
+
private renderIssuesTab(): TemplateResult | string {
|
|
3596
|
+
if (!this.flowIssues?.length || !this.revisionsWindowHidden) return '';
|
|
3597
|
+
return html`
|
|
3598
|
+
<temba-floating-tab
|
|
3599
|
+
id="issues-tab"
|
|
3600
|
+
icon="alert_warning"
|
|
3601
|
+
label="Flow Issues"
|
|
3602
|
+
color="tomato"
|
|
3603
|
+
order="1"
|
|
3604
|
+
.hidden=${!this.issuesWindowHidden}
|
|
3605
|
+
@temba-button-clicked=${this.handleIssuesTabClick}
|
|
3606
|
+
></temba-floating-tab>
|
|
3607
|
+
`;
|
|
3608
|
+
}
|
|
3609
|
+
|
|
3610
|
+
private renderIssuesWindow(): TemplateResult | string {
|
|
3611
|
+
if (!this.flowIssues?.length) return '';
|
|
3612
|
+
return html`
|
|
3613
|
+
<temba-floating-window
|
|
3614
|
+
id="issues-window"
|
|
3615
|
+
header="Flow Issues"
|
|
3616
|
+
.width=${360}
|
|
3617
|
+
.maxHeight=${600}
|
|
3618
|
+
.top=${75}
|
|
3619
|
+
color="tomato"
|
|
3620
|
+
.hidden=${this.issuesWindowHidden}
|
|
3621
|
+
@temba-dialog-hidden=${this.handleIssuesWindowClosed}
|
|
3622
|
+
>
|
|
3623
|
+
<div style="display:flex; flex-direction:column; gap:2px;">
|
|
3624
|
+
${this.flowIssues.map(
|
|
3625
|
+
(issue) => html`
|
|
3626
|
+
<div
|
|
3627
|
+
class="issue-list-item"
|
|
3628
|
+
@click=${() => this.handleIssueItemClick(issue)}
|
|
3629
|
+
>
|
|
3630
|
+
<temba-icon name="alert_warning" size="1.2"></temba-icon>
|
|
3631
|
+
<span>${formatIssueMessage(issue)}</span>
|
|
3632
|
+
</div>
|
|
3633
|
+
`
|
|
3634
|
+
)}
|
|
3635
|
+
</div>
|
|
3636
|
+
</temba-floating-window>
|
|
3637
|
+
`;
|
|
3638
|
+
}
|
|
3639
|
+
|
|
3331
3640
|
private renderRevisionsTab(): TemplateResult | string {
|
|
3641
|
+
if (this.revisions.length <= 1) return '';
|
|
3332
3642
|
return html`
|
|
3333
3643
|
<temba-floating-tab
|
|
3334
3644
|
id="revisions-tab"
|
|
3335
3645
|
icon="revisions"
|
|
3336
3646
|
label="Revisions"
|
|
3337
3647
|
color="rgb(142, 94, 167)"
|
|
3338
|
-
order="
|
|
3648
|
+
order="2"
|
|
3339
3649
|
.hidden=${!this.revisionsWindowHidden && this.localizationWindowHidden}
|
|
3340
3650
|
@temba-button-clicked=${this.handleRevisionsTabClick}
|
|
3341
3651
|
></temba-floating-tab>
|
|
@@ -3622,6 +3932,8 @@ export class Editor extends RapidElement {
|
|
|
3622
3932
|
}
|
|
3623
3933
|
|
|
3624
3934
|
private renderLocalizationTab(): TemplateResult | string {
|
|
3935
|
+
if (!this.revisionsWindowHidden) return '';
|
|
3936
|
+
if (this.definition?.nodes.length === 0) return '';
|
|
3625
3937
|
const languages = this.getLocalizationLanguages();
|
|
3626
3938
|
if (!languages.length) {
|
|
3627
3939
|
return html``;
|
|
@@ -3633,7 +3945,7 @@ export class Editor extends RapidElement {
|
|
|
3633
3945
|
icon="language"
|
|
3634
3946
|
label="Translate Flow"
|
|
3635
3947
|
color="#6b7280"
|
|
3636
|
-
order="
|
|
3948
|
+
order="3"
|
|
3637
3949
|
.hidden=${!this.localizationWindowHidden}
|
|
3638
3950
|
@temba-button-clicked=${this.handleLocalizationTabClick}
|
|
3639
3951
|
></temba-floating-tab>
|
|
@@ -3691,90 +4003,117 @@ export class Editor extends RapidElement {
|
|
|
3691
4003
|
|
|
3692
4004
|
const stickies = this.definition?._ui?.stickies || {};
|
|
3693
4005
|
|
|
3694
|
-
return html`${style} ${this.
|
|
3695
|
-
${this.
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
|
|
4006
|
+
return html`${style} ${this.renderIssuesWindow()}
|
|
4007
|
+
${this.renderRevisionsWindow()} ${this.renderLocalizationWindow()}
|
|
4008
|
+
${this.renderAutoTranslateDialog()}
|
|
4009
|
+
<div id="editor-container">
|
|
4010
|
+
<div id="editor">
|
|
4011
|
+
${this.definition &&
|
|
4012
|
+
this.definition.nodes.length === 0 &&
|
|
4013
|
+
!this.isReadOnly()
|
|
4014
|
+
? html`<div class="empty-flow">
|
|
4015
|
+
<div class="empty-flow-content">
|
|
4016
|
+
<div class="empty-flow-title">This flow is empty</div>
|
|
4017
|
+
<div class="empty-flow-description">
|
|
4018
|
+
Get started by adding your first action or split to define
|
|
4019
|
+
how this flow will work.
|
|
4020
|
+
</div>
|
|
4021
|
+
<button
|
|
4022
|
+
class="empty-flow-button"
|
|
4023
|
+
@click=${this.handleEmptyFlowClick}
|
|
4024
|
+
>
|
|
4025
|
+
Add first step
|
|
4026
|
+
</button>
|
|
4027
|
+
</div>
|
|
4028
|
+
</div>`
|
|
4029
|
+
: ''}
|
|
3703
4030
|
<div
|
|
3704
|
-
id="
|
|
3705
|
-
class="${
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
!!this.viewingRevision || this.isTranslating
|
|
3709
|
-
})}"
|
|
4031
|
+
id="grid"
|
|
4032
|
+
class="${this.viewingRevision ? 'viewing-revision' : ''}"
|
|
4033
|
+
style="min-width:100%;width:${this.canvasSize
|
|
4034
|
+
.width}px; height:${this.canvasSize.height}px"
|
|
3710
4035
|
>
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
this.
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
.
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
4036
|
+
<div
|
|
4037
|
+
id="canvas"
|
|
4038
|
+
class="${getClasses({
|
|
4039
|
+
'viewing-revision': !!this.viewingRevision,
|
|
4040
|
+
'read-only-connections':
|
|
4041
|
+
!!this.viewingRevision || this.isTranslating
|
|
4042
|
+
})}"
|
|
4043
|
+
>
|
|
4044
|
+
${this.definition
|
|
4045
|
+
? repeat(
|
|
4046
|
+
[...this.definition.nodes].sort((a, b) =>
|
|
4047
|
+
a.uuid.localeCompare(b.uuid)
|
|
4048
|
+
),
|
|
4049
|
+
(node) => node.uuid,
|
|
4050
|
+
(node) => {
|
|
4051
|
+
const position = this.definition._ui?.nodes[node.uuid]
|
|
4052
|
+
?.position || {
|
|
4053
|
+
left: 0,
|
|
4054
|
+
top: 0
|
|
4055
|
+
};
|
|
4056
|
+
|
|
4057
|
+
const dragging =
|
|
4058
|
+
this.isDragging &&
|
|
4059
|
+
this.currentDragItem?.uuid === node.uuid;
|
|
4060
|
+
|
|
4061
|
+
const selected = this.selectedItems.has(node.uuid);
|
|
4062
|
+
|
|
4063
|
+
// first node is the flow start (nodes are sorted by position)
|
|
4064
|
+
const isFlowStart =
|
|
4065
|
+
this.definition.nodes.length > 0 &&
|
|
4066
|
+
this.definition.nodes[0].uuid === node.uuid;
|
|
4067
|
+
|
|
4068
|
+
return html`<temba-flow-node
|
|
4069
|
+
class="draggable ${dragging
|
|
4070
|
+
? 'dragging'
|
|
4071
|
+
: ''} ${selected ? 'selected' : ''} ${isFlowStart
|
|
4072
|
+
? 'flow-start'
|
|
4073
|
+
: ''}"
|
|
4074
|
+
@mousedown=${this.handleMouseDown.bind(this)}
|
|
4075
|
+
uuid=${node.uuid}
|
|
4076
|
+
data-node-uuid=${node.uuid}
|
|
4077
|
+
style="left:${position.left}px; top:${position.top}px;transition: all 0.2s ease-in-out;"
|
|
4078
|
+
.plumber=${this.plumber}
|
|
4079
|
+
.node=${node}
|
|
4080
|
+
.ui=${this.definition._ui.nodes[node.uuid]}
|
|
4081
|
+
@temba-node-deleted=${(event) => {
|
|
4082
|
+
this.deleteNodes([event.detail.uuid]);
|
|
4083
|
+
}}
|
|
4084
|
+
></temba-flow-node>`;
|
|
4085
|
+
}
|
|
4086
|
+
)
|
|
4087
|
+
: html`<temba-loading></temba-loading>`}
|
|
4088
|
+
${repeat(
|
|
4089
|
+
Object.entries(stickies),
|
|
4090
|
+
([uuid]) => uuid,
|
|
4091
|
+
([uuid, sticky]) => {
|
|
4092
|
+
const position = sticky.position || { left: 0, top: 0 };
|
|
4093
|
+
const dragging =
|
|
4094
|
+
this.isDragging && this.currentDragItem?.uuid === uuid;
|
|
4095
|
+
const selected = this.selectedItems.has(uuid);
|
|
4096
|
+
return html`<temba-sticky-note
|
|
4097
|
+
class="draggable ${dragging ? 'dragging' : ''} ${selected
|
|
4098
|
+
? 'selected'
|
|
4099
|
+
: ''}"
|
|
4100
|
+
@mousedown=${this.handleMouseDown.bind(this)}
|
|
4101
|
+
style="left:${position.left}px; top:${position.top}px;"
|
|
4102
|
+
uuid=${uuid}
|
|
4103
|
+
.data=${sticky}
|
|
4104
|
+
.dragging=${dragging}
|
|
4105
|
+
.selected=${selected}
|
|
4106
|
+
></temba-sticky-note>`;
|
|
4107
|
+
}
|
|
4108
|
+
)}
|
|
4109
|
+
${this.renderSelectionBox()} ${this.renderCanvasDropPreview()}
|
|
4110
|
+
${this.renderConnectionPlaceholder()}
|
|
4111
|
+
</div>
|
|
3776
4112
|
</div>
|
|
3777
4113
|
</div>
|
|
4114
|
+
<div class="save-indicator ${this.isSaving ? 'visible' : ''}">
|
|
4115
|
+
<temba-loading units="3" size="8"></temba-loading>
|
|
4116
|
+
</div>
|
|
3778
4117
|
</div>
|
|
3779
4118
|
|
|
3780
4119
|
${this.editingNode || this.editingAction
|
|
@@ -3782,6 +4121,7 @@ export class Editor extends RapidElement {
|
|
|
3782
4121
|
.node=${this.editingNode}
|
|
3783
4122
|
.nodeUI=${this.editingNodeUI}
|
|
3784
4123
|
.action=${this.editingAction}
|
|
4124
|
+
.dialogOrigin=${this.dialogOrigin}
|
|
3785
4125
|
@temba-node-saved=${(e: CustomEvent) =>
|
|
3786
4126
|
this.handleNodeSaved(e.detail.node, e.detail.uiConfig)}
|
|
3787
4127
|
@temba-action-saved=${(e: CustomEvent) =>
|
|
@@ -3797,6 +4137,7 @@ export class Editor extends RapidElement {
|
|
|
3797
4137
|
.features=${this.features}
|
|
3798
4138
|
></temba-node-type-selector>`
|
|
3799
4139
|
: ''}
|
|
3800
|
-
${this.
|
|
4140
|
+
${this.renderIssuesTab()} ${this.renderRevisionsTab()}
|
|
4141
|
+
${this.renderLocalizationTab()} `;
|
|
3801
4142
|
}
|
|
3802
4143
|
}
|