@nyaruka/temba-components 0.131.1 → 0.131.3
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 +75 -1
- package/demo/components/floating-tabs/example.html +400 -0
- package/demo/components/flow/index.html +1 -1
- package/demo/data/flows/food-order.json +2 -2
- package/demo/data/flows/sample-flow.json +113 -125
- package/demo/data/flows/voicemail.json +613 -0
- package/demo/index.html +6 -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/static/svg/index.svg +1 -1
- package/dist/temba-components.js +1773 -662
- 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/display/FloatingTab.js +167 -0
- package/out-tsc/src/display/FloatingTab.js.map +1 -0
- package/out-tsc/src/display/ProgressBar.js +22 -2
- package/out-tsc/src/display/ProgressBar.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 +489 -47
- package/out-tsc/src/flow/CanvasNode.js.map +1 -1
- package/out-tsc/src/flow/Editor.js +1417 -67
- package/out-tsc/src/flow/Editor.js.map +1 -1
- package/out-tsc/src/flow/NodeEditor.js +479 -112
- package/out-tsc/src/flow/NodeEditor.js.map +1 -1
- package/out-tsc/src/flow/NodeTypeSelector.js +540 -0
- package/out-tsc/src/flow/NodeTypeSelector.js.map +1 -0
- package/out-tsc/src/flow/StickyNote.js +12 -3
- package/out-tsc/src/flow/StickyNote.js.map +1 -1
- package/out-tsc/src/flow/actions/add_contact_groups.js +4 -3
- package/out-tsc/src/flow/actions/add_contact_groups.js.map +1 -1
- package/out-tsc/src/flow/actions/add_contact_urn.js +63 -4
- package/out-tsc/src/flow/actions/add_contact_urn.js.map +1 -1
- package/out-tsc/src/flow/actions/add_input_labels.js +4 -3
- package/out-tsc/src/flow/actions/add_input_labels.js.map +1 -1
- package/out-tsc/src/flow/actions/play_audio.js +3 -2
- package/out-tsc/src/flow/actions/play_audio.js.map +1 -1
- package/out-tsc/src/flow/actions/remove_contact_groups.js +7 -5
- package/out-tsc/src/flow/actions/remove_contact_groups.js.map +1 -1
- package/out-tsc/src/flow/actions/request_optin.js +3 -2
- package/out-tsc/src/flow/actions/request_optin.js.map +1 -1
- package/out-tsc/src/flow/actions/say_msg.js +3 -2
- package/out-tsc/src/flow/actions/say_msg.js.map +1 -1
- package/out-tsc/src/flow/actions/send_broadcast.js +77 -23
- package/out-tsc/src/flow/actions/send_broadcast.js.map +1 -1
- package/out-tsc/src/flow/actions/send_email.js +5 -5
- package/out-tsc/src/flow/actions/send_email.js.map +1 -1
- package/out-tsc/src/flow/actions/send_msg.js +101 -21
- package/out-tsc/src/flow/actions/send_msg.js.map +1 -1
- package/out-tsc/src/flow/actions/set_contact_channel.js +6 -9
- package/out-tsc/src/flow/actions/set_contact_channel.js.map +1 -1
- package/out-tsc/src/flow/actions/set_contact_field.js +20 -20
- package/out-tsc/src/flow/actions/set_contact_field.js.map +1 -1
- package/out-tsc/src/flow/actions/set_contact_language.js +3 -2
- package/out-tsc/src/flow/actions/set_contact_language.js.map +1 -1
- package/out-tsc/src/flow/actions/set_contact_name.js +3 -12
- package/out-tsc/src/flow/actions/set_contact_name.js.map +1 -1
- package/out-tsc/src/flow/actions/set_contact_status.js +3 -2
- package/out-tsc/src/flow/actions/set_contact_status.js.map +1 -1
- package/out-tsc/src/flow/actions/set_run_result.js +4 -3
- package/out-tsc/src/flow/actions/set_run_result.js.map +1 -1
- package/out-tsc/src/flow/actions/start_session.js +181 -6
- package/out-tsc/src/flow/actions/start_session.js.map +1 -1
- package/out-tsc/src/flow/config.js +11 -23
- 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 +71 -0
- package/out-tsc/src/flow/nodes/shared.js.map +1 -0
- package/out-tsc/src/flow/nodes/split_by_airtime.js +211 -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 +152 -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 +73 -2
- package/out-tsc/src/flow/nodes/split_by_expression.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_groups.js +18 -10
- package/out-tsc/src/flow/nodes/split_by_groups.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_intent.js +8 -0
- package/out-tsc/src/flow/nodes/split_by_intent.js.map +1 -0
- package/out-tsc/src/flow/nodes/split_by_llm.js +11 -3
- package/out-tsc/src/flow/nodes/split_by_llm.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_llm_categorize.js +10 -3
- package/out-tsc/src/flow/nodes/split_by_llm_categorize.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_random.js +10 -4
- package/out-tsc/src/flow/nodes/split_by_random.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_resthook.js +113 -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 +211 -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 +158 -2
- package/out-tsc/src/flow/nodes/split_by_scheme.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_subflow.js +13 -5
- package/out-tsc/src/flow/nodes/split_by_subflow.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_ticket.js +10 -3
- package/out-tsc/src/flow/nodes/split_by_ticket.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_webhook.js +10 -3
- package/out-tsc/src/flow/nodes/split_by_webhook.js.map +1 -1
- package/out-tsc/src/flow/nodes/wait_for_digits.js +3 -2
- package/out-tsc/src/flow/nodes/wait_for_digits.js.map +1 -1
- package/out-tsc/src/flow/nodes/wait_for_menu.js +3 -2
- package/out-tsc/src/flow/nodes/wait_for_menu.js.map +1 -1
- package/out-tsc/src/flow/nodes/wait_for_response.js +38 -568
- package/out-tsc/src/flow/nodes/wait_for_response.js.map +1 -1
- package/out-tsc/src/flow/types.js +86 -12
- package/out-tsc/src/flow/types.js.map +1 -1
- package/out-tsc/src/flow/utils.js +101 -14
- package/out-tsc/src/flow/utils.js.map +1 -1
- package/out-tsc/src/form/FieldRenderer.js +2 -4
- package/out-tsc/src/form/FieldRenderer.js.map +1 -1
- package/out-tsc/src/interfaces.js +3 -0
- package/out-tsc/src/interfaces.js.map +1 -1
- package/out-tsc/src/layout/FloatingWindow.js +346 -0
- package/out-tsc/src/layout/FloatingWindow.js.map +1 -0
- 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 +6 -25
- package/out-tsc/src/live/ContactChat.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/store/AppState.js +120 -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 +8 -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-floating-tab.test.js +91 -0
- package/out-tsc/test/temba-floating-tab.test.js.map +1 -0
- package/out-tsc/test/temba-floating-window.test.js +301 -0
- package/out-tsc/test/temba-floating-window.test.js.map +1 -0
- package/out-tsc/test/temba-flow-editor-node.test.js +202 -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-localization.test.js +471 -0
- package/out-tsc/test/temba-localization.test.js.map +1 -0
- 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 +265 -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 +20 -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/floating-tab/default.png +0 -0
- package/screenshots/truth/floating-tab/gray.png +0 -0
- package/screenshots/truth/floating-tab/green.png +0 -0
- package/screenshots/truth/floating-tab/hidden.png +0 -0
- package/screenshots/truth/floating-tab/hover.png +0 -0
- package/screenshots/truth/floating-tab/purple.png +0 -0
- package/screenshots/truth/floating-window/chromeless.png +0 -0
- package/screenshots/truth/floating-window/custom-size.png +0 -0
- package/screenshots/truth/floating-window/default.png +0 -0
- package/screenshots/truth/floating-window/with-header.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/display/FloatingTab.ts +174 -0
- package/src/display/ProgressBar.ts +22 -2
- package/src/events.ts +2 -8
- package/src/flow/CanvasMenu.ts +217 -0
- package/src/flow/CanvasNode.ts +596 -40
- package/src/flow/Editor.ts +1721 -45
- package/src/flow/NodeEditor.ts +621 -144
- package/src/flow/NodeTypeSelector.ts +636 -0
- package/src/flow/StickyNote.ts +12 -3
- package/src/flow/actions/add_contact_groups.ts +5 -4
- package/src/flow/actions/add_contact_urn.ts +78 -4
- package/src/flow/actions/add_input_labels.ts +5 -4
- package/src/flow/actions/play_audio.ts +3 -2
- package/src/flow/actions/remove_contact_groups.ts +16 -6
- package/src/flow/actions/request_optin.ts +3 -2
- package/src/flow/actions/say_msg.ts +3 -2
- package/src/flow/actions/send_broadcast.ts +86 -23
- package/src/flow/actions/send_email.ts +12 -6
- package/src/flow/actions/send_msg.ts +155 -34
- package/src/flow/actions/set_contact_channel.ts +6 -11
- package/src/flow/actions/set_contact_field.ts +21 -25
- package/src/flow/actions/set_contact_language.ts +11 -4
- package/src/flow/actions/set_contact_name.ts +4 -15
- package/src/flow/actions/set_contact_status.ts +4 -3
- package/src/flow/actions/set_run_result.ts +5 -4
- package/src/flow/actions/start_session.ts +210 -6
- package/src/flow/config.ts +11 -23
- package/src/flow/currencies.ts +51 -0
- package/src/flow/nodes/shared-rules.ts +301 -0
- package/src/flow/nodes/shared.ts +87 -0
- package/src/flow/nodes/split_by_airtime.ts +255 -5
- package/src/flow/nodes/split_by_contact_field.ts +195 -3
- package/src/flow/nodes/split_by_expression.ts +104 -2
- package/src/flow/nodes/split_by_groups.ts +26 -11
- package/src/flow/nodes/split_by_intent.ts +8 -0
- package/src/flow/nodes/split_by_llm.ts +22 -4
- package/src/flow/nodes/split_by_llm_categorize.ts +22 -5
- package/src/flow/nodes/split_by_random.ts +16 -6
- package/src/flow/nodes/split_by_resthook.ts +140 -0
- package/src/flow/nodes/split_by_run_result.ts +259 -3
- package/src/flow/nodes/split_by_scheme.ts +202 -2
- package/src/flow/nodes/split_by_subflow.ts +17 -5
- package/src/flow/nodes/split_by_ticket.ts +15 -4
- package/src/flow/nodes/split_by_webhook.ts +17 -6
- package/src/flow/nodes/wait_for_digits.ts +3 -2
- package/src/flow/nodes/wait_for_menu.ts +3 -2
- package/src/flow/nodes/wait_for_response.ts +59 -680
- package/src/flow/types.ts +156 -23
- package/src/flow/utils.ts +108 -14
- package/src/form/FieldRenderer.ts +2 -4
- package/src/interfaces.ts +3 -0
- package/src/layout/FloatingWindow.ts +386 -0
- package/src/list/SortableList.ts +109 -34
- package/src/live/ContactChat.ts +7 -25
- 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/store/AppState.ts +173 -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/llms.json +18 -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 +8 -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-floating-tab.test.ts +110 -0
- package/test/temba-floating-window.test.ts +477 -0
- package/test/temba-flow-editor-node.test.ts +246 -2
- package/test/temba-flow-editor.test.ts +7 -8
- package/test/temba-localization.test.ts +611 -0
- package/test/temba-node-editor.test.ts +3 -1
- package/test/temba-node-type-selector.test.ts +355 -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 +22 -0
- package/test-assets/contacts/history.json +14 -21
- package/test-assets/select/llms.json +2 -2
- package/web-dev-server.config.mjs +49 -1
- package/web-test-runner.config.mjs +0 -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/out-tsc/src/flow/nodes/wait_for_audio.js +0 -7
- package/out-tsc/src/flow/nodes/wait_for_audio.js.map +0 -1
- package/out-tsc/src/flow/nodes/wait_for_image.js +0 -7
- package/out-tsc/src/flow/nodes/wait_for_image.js.map +0 -1
- package/out-tsc/src/flow/nodes/wait_for_location.js +0 -7
- package/out-tsc/src/flow/nodes/wait_for_location.js.map +0 -1
- package/out-tsc/src/flow/nodes/wait_for_video.js +0 -7
- package/out-tsc/src/flow/nodes/wait_for_video.js.map +0 -1
- package/src/flow/actions/call_classifier.ts +0 -12
- package/src/flow/actions/call_resthook.ts +0 -12
- package/src/flow/actions/split_by_expression_example.ts +0 -88
- package/src/flow/actions/transfer_airtime.ts +0 -12
- package/src/flow/nodes/wait_for_audio.ts +0 -7
- package/src/flow/nodes/wait_for_image.ts +0 -7
- package/src/flow/nodes/wait_for_location.ts +0 -7
- package/src/flow/nodes/wait_for_video.ts +0 -7
|
@@ -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, postJSON } 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,11 @@ export function findNodeForExit(definition, exitUuid) {
|
|
|
24
27
|
}
|
|
25
28
|
const SAVE_QUIET_TIME = 500;
|
|
26
29
|
const DRAG_THRESHOLD = 5;
|
|
30
|
+
const AUTO_TRANSLATE_MODELS_ENDPOINT = '/api/internal/llms.json';
|
|
31
|
+
// Offset for positioning dropped action node relative to mouse cursor
|
|
32
|
+
// Keep small to make drop location close to cursor position
|
|
33
|
+
const DROP_PREVIEW_OFFSET_X = 20;
|
|
34
|
+
const DROP_PREVIEW_OFFSET_Y = 20;
|
|
27
35
|
export class Editor extends RapidElement {
|
|
28
36
|
// unfortunately, jsplumb requires that we be in light DOM
|
|
29
37
|
createRenderRoot() {
|
|
@@ -33,6 +41,18 @@ export class Editor extends RapidElement {
|
|
|
33
41
|
get dragging() {
|
|
34
42
|
return this.isDragging;
|
|
35
43
|
}
|
|
44
|
+
getAvailableLanguages() {
|
|
45
|
+
var _b, _c;
|
|
46
|
+
// Use languages from flow definition if available, otherwise use defaults
|
|
47
|
+
if (((_c = (_b = this.definition) === null || _b === void 0 ? void 0 : _b._ui) === null || _c === void 0 ? void 0 : _c.languages) &&
|
|
48
|
+
this.definition._ui.languages.length > 0) {
|
|
49
|
+
return this.definition._ui.languages.map((lang) => ({
|
|
50
|
+
code: typeof lang === 'string' ? lang : lang.iso || lang.code,
|
|
51
|
+
name: typeof lang === 'string' ? lang : lang.name
|
|
52
|
+
}));
|
|
53
|
+
}
|
|
54
|
+
return this.DEFAULT_LANGUAGES;
|
|
55
|
+
}
|
|
36
56
|
static get styles() {
|
|
37
57
|
return css `
|
|
38
58
|
#editor {
|
|
@@ -176,12 +196,193 @@ export class Editor extends RapidElement {
|
|
|
176
196
|
.jtk-floating-endpoint {
|
|
177
197
|
pointer-events: none;
|
|
178
198
|
}
|
|
199
|
+
|
|
200
|
+
.localization-window-content {
|
|
201
|
+
display: flex;
|
|
202
|
+
flex-direction: column;
|
|
203
|
+
gap: 16px;
|
|
204
|
+
height: 100%;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
.localization-header {
|
|
208
|
+
font-size: 13px;
|
|
209
|
+
color: #4b5563;
|
|
210
|
+
line-height: 1.4;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
.localization-language-select {
|
|
214
|
+
--color-widget-border: #d1d5db;
|
|
215
|
+
--color-widget-background: #fff;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
.localization-language-row {
|
|
219
|
+
display: flex;
|
|
220
|
+
align-items: flex-end;
|
|
221
|
+
gap: 12px;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
.localization-language-row temba-select {
|
|
225
|
+
flex: 1;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
.localization-progress {
|
|
229
|
+
margin-top: auto;
|
|
230
|
+
display: flex;
|
|
231
|
+
flex-direction: column;
|
|
232
|
+
gap: 8px;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
.localization-progress-bar-row {
|
|
236
|
+
display: flex;
|
|
237
|
+
align-items: center;
|
|
238
|
+
gap: 8px;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
.localization-progress-trigger {
|
|
242
|
+
flex: 1;
|
|
243
|
+
border-radius: 6px;
|
|
244
|
+
cursor: pointer;
|
|
245
|
+
display: flex;
|
|
246
|
+
align-items: center;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
.localization-progress-trigger:focus-visible {
|
|
250
|
+
outline: 2px solid #94a3b8;
|
|
251
|
+
outline-offset: 2px;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
.localization-progress-trigger temba-progress {
|
|
255
|
+
flex: 1;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
.localization-progress h5 {
|
|
259
|
+
margin: 0;
|
|
260
|
+
font-size: 13px;
|
|
261
|
+
font-weight: 600;
|
|
262
|
+
color: #374151;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
.localization-progress-summary {
|
|
266
|
+
font-size: 12px;
|
|
267
|
+
color: #6b7280;
|
|
268
|
+
display: flex;
|
|
269
|
+
align-items: center;
|
|
270
|
+
gap: 6px;
|
|
271
|
+
min-height: 20px;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
.translation-settings-toggle {
|
|
275
|
+
display: inline-flex;
|
|
276
|
+
align-items: center;
|
|
277
|
+
gap: 6px;
|
|
278
|
+
background: transparent;
|
|
279
|
+
border: none;
|
|
280
|
+
color: #6b7280;
|
|
281
|
+
font-size: 12px;
|
|
282
|
+
font-weight: 600;
|
|
283
|
+
cursor: pointer;
|
|
284
|
+
padding: 4px;
|
|
285
|
+
border-radius: 4px;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
.translation-settings-label {
|
|
289
|
+
font-size: 12px;
|
|
290
|
+
color: #6b7280;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
.translation-settings-toggle:focus-visible {
|
|
294
|
+
outline: 2px solid #94a3b8;
|
|
295
|
+
outline-offset: 2px;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
.translation-settings-arrow {
|
|
299
|
+
width: 8px;
|
|
300
|
+
height: 8px;
|
|
301
|
+
border-right: 2px solid currentColor;
|
|
302
|
+
border-bottom: 2px solid currentColor;
|
|
303
|
+
transform: rotate(-45deg);
|
|
304
|
+
transition: transform 0.2s ease;
|
|
305
|
+
margin-left: 2px;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
.translation-settings-arrow.expanded {
|
|
309
|
+
transform: rotate(45deg);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
.translation-settings {
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
.translation-settings-row {
|
|
316
|
+
display: flex;
|
|
317
|
+
align-items: center;
|
|
318
|
+
justify-content: space-between;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
.translation-settings-row temba-checkbox {
|
|
322
|
+
width: 100%;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
.auto-translate-button {
|
|
326
|
+
background: var(--color-primary-dark);
|
|
327
|
+
border: none;
|
|
328
|
+
color: #fff;
|
|
329
|
+
padding: 10px 12px;
|
|
330
|
+
border-radius: var(--curvature);
|
|
331
|
+
font-size: 12px;
|
|
332
|
+
font-weight: 600;
|
|
333
|
+
cursor: pointer;
|
|
334
|
+
transition: opacity 0.2s ease;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
.auto-translate-button[disabled] {
|
|
338
|
+
opacity: 0.5;
|
|
339
|
+
cursor: not-allowed;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
.auto-translate-error {
|
|
343
|
+
font-size: 12px;
|
|
344
|
+
color: #b91c1c;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
.auto-translate-dialog-content {
|
|
348
|
+
padding: 20px;
|
|
349
|
+
display: flex;
|
|
350
|
+
flex-direction: column;
|
|
351
|
+
gap: 12px;
|
|
352
|
+
font-size: 14px;
|
|
353
|
+
color: #374151;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
.auto-translate-dialog-content p {
|
|
357
|
+
margin: 0;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
.auto-translate-loading {
|
|
361
|
+
display: flex;
|
|
362
|
+
align-items: center;
|
|
363
|
+
gap: 8px;
|
|
364
|
+
font-size: 13px;
|
|
365
|
+
color: #6b7280;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
.auto-translate-empty {
|
|
369
|
+
font-size: 13px;
|
|
370
|
+
color: #6b7280;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
.localization-empty {
|
|
374
|
+
font-size: 13px;
|
|
375
|
+
color: #9ca3af;
|
|
376
|
+
white-space: nowrap;
|
|
377
|
+
}
|
|
179
378
|
`;
|
|
180
379
|
}
|
|
181
380
|
constructor() {
|
|
182
381
|
super();
|
|
183
382
|
// timer for debounced saving
|
|
184
383
|
this.saveTimer = null;
|
|
384
|
+
this.flowType = 'message';
|
|
385
|
+
this.features = [];
|
|
185
386
|
// Drag state
|
|
186
387
|
this.isDragging = false;
|
|
187
388
|
this.isMouseDown = false;
|
|
@@ -196,17 +397,42 @@ export class Editor extends RapidElement {
|
|
|
196
397
|
this.sourceId = null;
|
|
197
398
|
this.dragFromNodeId = null;
|
|
198
399
|
this.isValidTarget = true;
|
|
400
|
+
this.localizationWindowHidden = true;
|
|
401
|
+
this.translationFilters = {
|
|
402
|
+
categories: false
|
|
403
|
+
};
|
|
404
|
+
this.translationSettingsExpanded = false;
|
|
405
|
+
this.autoTranslateDialogOpen = false;
|
|
406
|
+
this.autoTranslating = false;
|
|
407
|
+
this.autoTranslateModel = null;
|
|
408
|
+
this.autoTranslateError = null;
|
|
409
|
+
this.translationCache = new Map();
|
|
199
410
|
// NodeEditor state - handles both node and action editing
|
|
200
411
|
this.editingNode = null;
|
|
201
412
|
this.editingNodeUI = null;
|
|
202
413
|
this.editingAction = null;
|
|
414
|
+
this.isCreatingNewNode = false;
|
|
415
|
+
this.pendingNodePosition = null;
|
|
416
|
+
// Canvas drop state for dragging actions to canvas
|
|
417
|
+
this.canvasDropPreview = null;
|
|
418
|
+
this.addActionToNodeUuid = null;
|
|
419
|
+
// Track target node for action drag
|
|
420
|
+
this.actionDragTargetNodeUuid = null;
|
|
421
|
+
// Track previous target node to clear placeholder when moving between nodes
|
|
422
|
+
this.previousActionDragTargetNodeUuid = null;
|
|
203
423
|
this.canvasMouseDown = false;
|
|
424
|
+
// Default languages if not specified in flow definition
|
|
425
|
+
this.DEFAULT_LANGUAGES = [
|
|
426
|
+
{ code: 'eng', name: 'English' },
|
|
427
|
+
{ code: 'fra', name: 'French' },
|
|
428
|
+
{ code: 'esp', name: 'Spanish' }
|
|
429
|
+
];
|
|
204
430
|
// Bound event handlers to maintain proper 'this' context
|
|
205
431
|
this.boundMouseMove = this.handleMouseMove.bind(this);
|
|
206
432
|
this.boundMouseUp = this.handleMouseUp.bind(this);
|
|
207
433
|
this.boundGlobalMouseDown = this.handleGlobalMouseDown.bind(this);
|
|
208
434
|
this.boundKeyDown = this.handleKeyDown.bind(this);
|
|
209
|
-
this.
|
|
435
|
+
this.boundCanvasContextMenu = this.handleCanvasContextMenu.bind(this);
|
|
210
436
|
}
|
|
211
437
|
firstUpdated(changes) {
|
|
212
438
|
super.firstUpdated(changes);
|
|
@@ -248,18 +474,54 @@ export class Editor extends RapidElement {
|
|
|
248
474
|
this.isValidTarget = true;
|
|
249
475
|
}
|
|
250
476
|
updated(changes) {
|
|
477
|
+
var _b, _c, _d;
|
|
251
478
|
super.updated(changes);
|
|
252
479
|
if (changes.has('canvasSize')) {
|
|
253
480
|
// console.log('Setting canvas size', this.canvasSize);
|
|
254
481
|
}
|
|
255
482
|
if (changes.has('definition')) {
|
|
256
483
|
this.updateCanvasSize();
|
|
484
|
+
// Set flowType from the loaded definition
|
|
485
|
+
if ((_b = this.definition) === null || _b === void 0 ? void 0 : _b.type) {
|
|
486
|
+
this.flowType = this.getFlowTypeFromDefinition(this.definition.type);
|
|
487
|
+
}
|
|
488
|
+
const filters = ((_d = (_c = this.definition) === null || _c === void 0 ? void 0 : _c._ui) === null || _d === void 0 ? void 0 : _d.translation_filters) || {
|
|
489
|
+
categories: false
|
|
490
|
+
};
|
|
491
|
+
const normalizedFilters = {
|
|
492
|
+
categories: !!filters.categories
|
|
493
|
+
};
|
|
494
|
+
if (this.translationFilters.categories !== normalizedFilters.categories) {
|
|
495
|
+
this.translationFilters = normalizedFilters;
|
|
496
|
+
}
|
|
497
|
+
this.translationCache.clear();
|
|
257
498
|
}
|
|
258
499
|
if (changes.has('dirtyDate')) {
|
|
259
500
|
if (this.dirtyDate) {
|
|
260
501
|
this.debouncedSave();
|
|
261
502
|
}
|
|
262
503
|
}
|
|
504
|
+
if (changes.has('languageCode')) {
|
|
505
|
+
this.translationCache.clear();
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
/**
|
|
509
|
+
* Map FlowDefinition type to Editor flowType
|
|
510
|
+
* FlowDefinition uses: 'messaging', 'messaging_background', 'messaging_offline', 'voice'
|
|
511
|
+
* Editor uses: 'message', 'voice', 'background'
|
|
512
|
+
*/
|
|
513
|
+
getFlowTypeFromDefinition(definitionType) {
|
|
514
|
+
if (definitionType === 'voice') {
|
|
515
|
+
return 'voice';
|
|
516
|
+
}
|
|
517
|
+
else if (definitionType === 'messaging_background' ||
|
|
518
|
+
definitionType === 'messaging_offline') {
|
|
519
|
+
return 'background';
|
|
520
|
+
}
|
|
521
|
+
else {
|
|
522
|
+
// 'messaging' or any other messaging type defaults to 'message'
|
|
523
|
+
return 'message';
|
|
524
|
+
}
|
|
263
525
|
}
|
|
264
526
|
debouncedSave() {
|
|
265
527
|
// Clear any existing timer
|
|
@@ -293,6 +555,15 @@ export class Editor extends RapidElement {
|
|
|
293
555
|
});
|
|
294
556
|
getStore().getState().setDirtyDate(null);
|
|
295
557
|
}
|
|
558
|
+
handleLanguageChange(languageCode) {
|
|
559
|
+
zustand.getState().setLanguageCode(languageCode);
|
|
560
|
+
// Repaint connections after language change since node sizes can change
|
|
561
|
+
if (this.plumber) {
|
|
562
|
+
requestAnimationFrame(() => {
|
|
563
|
+
this.plumber.repaintEverything();
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
}
|
|
296
567
|
disconnectedCallback() {
|
|
297
568
|
super.disconnectedCallback();
|
|
298
569
|
if (this.saveTimer !== null) {
|
|
@@ -305,7 +576,7 @@ export class Editor extends RapidElement {
|
|
|
305
576
|
document.removeEventListener('keydown', this.boundKeyDown);
|
|
306
577
|
const canvas = this.querySelector('#canvas');
|
|
307
578
|
if (canvas) {
|
|
308
|
-
canvas.removeEventListener('
|
|
579
|
+
canvas.removeEventListener('contextmenu', this.boundCanvasContextMenu);
|
|
309
580
|
}
|
|
310
581
|
}
|
|
311
582
|
setupGlobalEventListeners() {
|
|
@@ -315,20 +586,40 @@ export class Editor extends RapidElement {
|
|
|
315
586
|
document.addEventListener('keydown', this.boundKeyDown);
|
|
316
587
|
const canvas = this.querySelector('#canvas');
|
|
317
588
|
if (canvas) {
|
|
318
|
-
canvas.addEventListener('
|
|
589
|
+
canvas.addEventListener('contextmenu', this.boundCanvasContextMenu);
|
|
319
590
|
}
|
|
320
591
|
// Listen for action edit requests from flow nodes
|
|
321
592
|
this.addEventListener(CustomEventType.ActionEditRequested, this.handleActionEditRequested.bind(this));
|
|
593
|
+
// Listen for add action requests from flow nodes
|
|
594
|
+
this.addEventListener(CustomEventType.AddActionRequested, this.handleAddActionRequested.bind(this));
|
|
322
595
|
// Listen for node edit requests from flow nodes
|
|
323
596
|
this.addEventListener(CustomEventType.NodeEditRequested, this.handleNodeEditRequested.bind(this));
|
|
597
|
+
// Listen for canvas menu selections
|
|
598
|
+
this.addEventListener(CustomEventType.Selection, (event) => {
|
|
599
|
+
const target = event.target;
|
|
600
|
+
if (target.tagName === 'TEMBA-CANVAS-MENU') {
|
|
601
|
+
this.handleCanvasMenuSelection(event);
|
|
602
|
+
}
|
|
603
|
+
else if (target.tagName === 'TEMBA-NODE-TYPE-SELECTOR') {
|
|
604
|
+
this.handleNodeTypeSelection(event);
|
|
605
|
+
}
|
|
606
|
+
});
|
|
607
|
+
// Listen for action drag events from nodes
|
|
608
|
+
this.addEventListener(CustomEventType.DragExternal, this.handleActionDragExternal.bind(this));
|
|
609
|
+
this.addEventListener(CustomEventType.DragInternal, this.handleActionDragInternal.bind(this));
|
|
610
|
+
this.addEventListener(CustomEventType.DragStop, (event) => {
|
|
611
|
+
if (event.detail.isExternal) {
|
|
612
|
+
this.handleActionDropExternal(event);
|
|
613
|
+
}
|
|
614
|
+
});
|
|
324
615
|
}
|
|
325
616
|
getPosition(uuid, type) {
|
|
326
|
-
var
|
|
617
|
+
var _b, _c, _d;
|
|
327
618
|
if (type === 'node') {
|
|
328
|
-
return (
|
|
619
|
+
return (_b = this.definition._ui.nodes[uuid]) === null || _b === void 0 ? void 0 : _b.position;
|
|
329
620
|
}
|
|
330
621
|
else {
|
|
331
|
-
return (
|
|
622
|
+
return (_d = (_c = this.definition._ui.stickies) === null || _c === void 0 ? void 0 : _c[uuid]) === null || _d === void 0 ? void 0 : _d.position;
|
|
332
623
|
}
|
|
333
624
|
}
|
|
334
625
|
handleMouseDown(event) {
|
|
@@ -370,12 +661,12 @@ export class Editor extends RapidElement {
|
|
|
370
661
|
event.stopPropagation();
|
|
371
662
|
}
|
|
372
663
|
handleGlobalMouseDown(event) {
|
|
373
|
-
var
|
|
664
|
+
var _b;
|
|
374
665
|
// ignore right clicks
|
|
375
666
|
if (event.button !== 0)
|
|
376
667
|
return;
|
|
377
668
|
// Check if the click is within our canvas
|
|
378
|
-
const canvasRect = (
|
|
669
|
+
const canvasRect = (_b = this.querySelector('#grid')) === null || _b === void 0 ? void 0 : _b.getBoundingClientRect();
|
|
379
670
|
if (!canvasRect)
|
|
380
671
|
return;
|
|
381
672
|
const isWithinCanvas = event.clientX >= canvasRect.left &&
|
|
@@ -395,14 +686,14 @@ export class Editor extends RapidElement {
|
|
|
395
686
|
this.handleCanvasMouseDown(event);
|
|
396
687
|
}
|
|
397
688
|
handleCanvasMouseDown(event) {
|
|
398
|
-
var
|
|
689
|
+
var _b;
|
|
399
690
|
const target = event.target;
|
|
400
691
|
if (target.id === 'canvas' || target.id === 'grid') {
|
|
401
692
|
// Ignore clicks on exits
|
|
402
693
|
// Start selection box
|
|
403
694
|
this.canvasMouseDown = true;
|
|
404
695
|
this.dragStartPos = { x: event.clientX, y: event.clientY };
|
|
405
|
-
const canvasRect = (
|
|
696
|
+
const canvasRect = (_b = this.querySelector('#canvas')) === null || _b === void 0 ? void 0 : _b.getBoundingClientRect();
|
|
406
697
|
if (canvasRect) {
|
|
407
698
|
// Clear current selection
|
|
408
699
|
this.selectedItems.clear();
|
|
@@ -466,16 +757,16 @@ export class Editor extends RapidElement {
|
|
|
466
757
|
deleteSelectedItems() {
|
|
467
758
|
const nodes = Array.from(this.selectedItems).filter((uuid) => this.definition.nodes.some((node) => node.uuid === uuid));
|
|
468
759
|
this.deleteNodes(Array.from(nodes));
|
|
469
|
-
const stickies = Array.from(this.selectedItems).filter((uuid) => { var
|
|
760
|
+
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
761
|
getStore().getState().removeStickyNotes(stickies);
|
|
471
762
|
// Clear selection
|
|
472
763
|
this.selectedItems.clear();
|
|
473
764
|
}
|
|
474
765
|
updateSelectionBox(event) {
|
|
475
|
-
var
|
|
766
|
+
var _b;
|
|
476
767
|
if (!this.selectionBox || !this.canvasMouseDown)
|
|
477
768
|
return;
|
|
478
|
-
const canvasRect = (
|
|
769
|
+
const canvasRect = (_b = this.querySelector('#canvas')) === null || _b === void 0 ? void 0 : _b.getBoundingClientRect();
|
|
479
770
|
if (!canvasRect)
|
|
480
771
|
return;
|
|
481
772
|
const relativeX = event.clientX - canvasRect.left;
|
|
@@ -489,7 +780,7 @@ export class Editor extends RapidElement {
|
|
|
489
780
|
this.updateSelectedItemsFromBox();
|
|
490
781
|
}
|
|
491
782
|
updateSelectedItemsFromBox() {
|
|
492
|
-
var
|
|
783
|
+
var _b, _c, _d;
|
|
493
784
|
if (!this.selectionBox)
|
|
494
785
|
return;
|
|
495
786
|
const newSelection = new Set();
|
|
@@ -498,14 +789,14 @@ export class Editor extends RapidElement {
|
|
|
498
789
|
const boxRight = Math.max(this.selectionBox.startX, this.selectionBox.endX);
|
|
499
790
|
const boxBottom = Math.max(this.selectionBox.startY, this.selectionBox.endY);
|
|
500
791
|
// Check nodes
|
|
501
|
-
(
|
|
502
|
-
var
|
|
792
|
+
(_b = this.definition) === null || _b === void 0 ? void 0 : _b.nodes.forEach((node) => {
|
|
793
|
+
var _b, _c, _d;
|
|
503
794
|
const nodeElement = this.querySelector(`[id="${node.uuid}"]`);
|
|
504
795
|
if (nodeElement) {
|
|
505
|
-
const position = (
|
|
796
|
+
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
797
|
if (position) {
|
|
507
798
|
const rect = nodeElement.getBoundingClientRect();
|
|
508
|
-
const canvasRect = (
|
|
799
|
+
const canvasRect = (_d = this.querySelector('#canvas')) === null || _d === void 0 ? void 0 : _d.getBoundingClientRect();
|
|
509
800
|
if (canvasRect) {
|
|
510
801
|
const nodeLeft = position.left;
|
|
511
802
|
const nodeTop = position.top;
|
|
@@ -523,7 +814,7 @@ export class Editor extends RapidElement {
|
|
|
523
814
|
}
|
|
524
815
|
});
|
|
525
816
|
// Check sticky notes
|
|
526
|
-
const stickies = ((
|
|
817
|
+
const stickies = ((_d = (_c = this.definition) === null || _c === void 0 ? void 0 : _c._ui) === null || _d === void 0 ? void 0 : _d.stickies) || {};
|
|
527
818
|
Object.entries(stickies).forEach(([uuid, sticky]) => {
|
|
528
819
|
if (sticky.position) {
|
|
529
820
|
const stickyElement = this.querySelector(`temba-sticky-note[uuid="${uuid}"]`);
|
|
@@ -561,6 +852,49 @@ export class Editor extends RapidElement {
|
|
|
561
852
|
style="left: ${left}px; top: ${top}px; width: ${width}px; height: ${height}px;"
|
|
562
853
|
></div>`;
|
|
563
854
|
}
|
|
855
|
+
renderCanvasDropPreview() {
|
|
856
|
+
var _b;
|
|
857
|
+
if (!this.canvasDropPreview)
|
|
858
|
+
return '';
|
|
859
|
+
const { action, position } = this.canvasDropPreview;
|
|
860
|
+
const actionConfig = ACTION_CONFIG[action.type];
|
|
861
|
+
if (!actionConfig)
|
|
862
|
+
return '';
|
|
863
|
+
return html `<div
|
|
864
|
+
class="canvas-drop-preview"
|
|
865
|
+
style="position: absolute; left: ${position.left}px; top: ${position.top}px; opacity: 0.6; pointer-events: none; z-index: 10000;"
|
|
866
|
+
>
|
|
867
|
+
<div
|
|
868
|
+
class="node execute-actions"
|
|
869
|
+
style="outline: 3px dashed var(--color-primary, #3b82f6); outline-offset: 2px; border-radius: var(--curvature);"
|
|
870
|
+
>
|
|
871
|
+
<div class="action sortable ${action.type}">
|
|
872
|
+
<div class="action-content">
|
|
873
|
+
<div
|
|
874
|
+
class="cn-title"
|
|
875
|
+
style="background: ${actionConfig.group
|
|
876
|
+
? (_b = ACTION_GROUP_METADATA[actionConfig.group]) === null || _b === void 0 ? void 0 : _b.color
|
|
877
|
+
: '#aaaaaa'}"
|
|
878
|
+
>
|
|
879
|
+
<div class="title-spacer"></div>
|
|
880
|
+
<div class="name">${actionConfig.name}</div>
|
|
881
|
+
<div class="title-spacer"></div>
|
|
882
|
+
</div>
|
|
883
|
+
<div class="body">
|
|
884
|
+
${actionConfig.render
|
|
885
|
+
? actionConfig.render({ actions: [action] }, action)
|
|
886
|
+
: html `<pre>${action.type}</pre>`}
|
|
887
|
+
</div>
|
|
888
|
+
</div>
|
|
889
|
+
</div>
|
|
890
|
+
<div class="action-exits">
|
|
891
|
+
<div class="exit-wrapper">
|
|
892
|
+
<div class="exit"></div>
|
|
893
|
+
</div>
|
|
894
|
+
</div>
|
|
895
|
+
</div>
|
|
896
|
+
</div>`;
|
|
897
|
+
}
|
|
564
898
|
handleMouseMove(event) {
|
|
565
899
|
// Handle selection box drawing
|
|
566
900
|
if (this.canvasMouseDown && !this.isMouseDown) {
|
|
@@ -693,7 +1027,7 @@ export class Editor extends RapidElement {
|
|
|
693
1027
|
this.canvasMouseDown = false;
|
|
694
1028
|
}
|
|
695
1029
|
updateCanvasSize() {
|
|
696
|
-
var
|
|
1030
|
+
var _b;
|
|
697
1031
|
if (!this.definition)
|
|
698
1032
|
return;
|
|
699
1033
|
const store = getStore();
|
|
@@ -715,7 +1049,7 @@ export class Editor extends RapidElement {
|
|
|
715
1049
|
}
|
|
716
1050
|
});
|
|
717
1051
|
// Check sticky note positions
|
|
718
|
-
const stickies = ((
|
|
1052
|
+
const stickies = ((_b = this.definition._ui) === null || _b === void 0 ? void 0 : _b.stickies) || {};
|
|
719
1053
|
Object.entries(stickies).forEach(([uuid, sticky]) => {
|
|
720
1054
|
if (sticky.position) {
|
|
721
1055
|
const stickyElement = this.querySelector(`temba-sticky-note[uuid="${uuid}"]`);
|
|
@@ -738,12 +1072,15 @@ export class Editor extends RapidElement {
|
|
|
738
1072
|
// Update canvas size in store
|
|
739
1073
|
store.getState().expandCanvas(maxWidth, maxHeight);
|
|
740
1074
|
}
|
|
741
|
-
|
|
742
|
-
// Check if we
|
|
1075
|
+
handleCanvasContextMenu(event) {
|
|
1076
|
+
// Check if we right-clicked on empty canvas space
|
|
743
1077
|
const target = event.target;
|
|
744
1078
|
if (target.id !== 'canvas') {
|
|
745
1079
|
return;
|
|
746
1080
|
}
|
|
1081
|
+
// Prevent the default browser context menu
|
|
1082
|
+
event.preventDefault();
|
|
1083
|
+
event.stopPropagation();
|
|
747
1084
|
// Get canvas position
|
|
748
1085
|
const canvas = this.querySelector('#canvas');
|
|
749
1086
|
if (!canvas) {
|
|
@@ -755,14 +1092,119 @@ export class Editor extends RapidElement {
|
|
|
755
1092
|
// Snap position to grid
|
|
756
1093
|
const snappedLeft = snapToGrid(relativeX);
|
|
757
1094
|
const snappedTop = snapToGrid(relativeY);
|
|
758
|
-
//
|
|
1095
|
+
// Show the canvas menu at the mouse position (use viewport coordinates)
|
|
1096
|
+
const canvasMenu = this.querySelector('temba-canvas-menu');
|
|
1097
|
+
if (canvasMenu) {
|
|
1098
|
+
canvasMenu.show(event.clientX, event.clientY, {
|
|
1099
|
+
x: snappedLeft,
|
|
1100
|
+
y: snappedTop
|
|
1101
|
+
});
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
handleCanvasMenuSelection(event) {
|
|
1105
|
+
const selection = event.detail;
|
|
759
1106
|
const store = getStore();
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
1107
|
+
if (selection.action === 'sticky') {
|
|
1108
|
+
// Create new sticky note
|
|
1109
|
+
store.getState().createStickyNote({
|
|
1110
|
+
left: selection.position.x,
|
|
1111
|
+
top: selection.position.y
|
|
1112
|
+
});
|
|
1113
|
+
}
|
|
1114
|
+
else {
|
|
1115
|
+
// Show node type selector
|
|
1116
|
+
const selector = this.querySelector('temba-node-type-selector');
|
|
1117
|
+
if (selector) {
|
|
1118
|
+
selector.show(selection.action, selection.position);
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
handleNodeTypeSelection(event) {
|
|
1123
|
+
const selection = event.detail;
|
|
1124
|
+
// Check if we're adding an action to an existing node
|
|
1125
|
+
if (this.addActionToNodeUuid) {
|
|
1126
|
+
// Find the existing node
|
|
1127
|
+
const node = this.definition.nodes.find((n) => n.uuid === this.addActionToNodeUuid);
|
|
1128
|
+
const nodeUI = this.definition._ui.nodes[this.addActionToNodeUuid];
|
|
1129
|
+
if (node && nodeUI) {
|
|
1130
|
+
// Create a new action to add to the existing node
|
|
1131
|
+
const actionUuid = generateUUID();
|
|
1132
|
+
this.editingAction = {
|
|
1133
|
+
uuid: actionUuid,
|
|
1134
|
+
type: selection.nodeType
|
|
1135
|
+
};
|
|
1136
|
+
// Set the editing node to the existing node (not creating new)
|
|
1137
|
+
this.editingNode = node;
|
|
1138
|
+
this.editingNodeUI = nodeUI;
|
|
1139
|
+
this.isCreatingNewNode = false;
|
|
1140
|
+
// Clear the addActionToNodeUuid flag
|
|
1141
|
+
this.addActionToNodeUuid = null;
|
|
1142
|
+
return;
|
|
1143
|
+
}
|
|
1144
|
+
// If we couldn't find the node, clear the flag and continue with normal flow
|
|
1145
|
+
this.addActionToNodeUuid = null;
|
|
1146
|
+
}
|
|
1147
|
+
// Create a temporary node structure for editing (not added to store yet)
|
|
1148
|
+
const nodeUuid = generateUUID();
|
|
1149
|
+
// Determine if this is an action type or a node type
|
|
1150
|
+
// Actions need to be wrapped in an execute_actions node
|
|
1151
|
+
const isActionType = selection.nodeType in ACTION_CONFIG;
|
|
1152
|
+
const nodeType = isActionType ? 'execute_actions' : selection.nodeType;
|
|
1153
|
+
// For nodes with routers, initialize an empty router to ensure fromFormData works correctly
|
|
1154
|
+
const nodeConfig = NODE_CONFIG[nodeType];
|
|
1155
|
+
const hasRouter = (nodeConfig === null || nodeConfig === void 0 ? void 0 : nodeConfig.form) &&
|
|
1156
|
+
Object.keys(nodeConfig.form).some((key) => {
|
|
1157
|
+
var _b;
|
|
1158
|
+
return ['rules', 'categories', 'cases'].includes(key) ||
|
|
1159
|
+
((_b = nodeConfig.form[key]) === null || _b === void 0 ? void 0 : _b.type) === 'array';
|
|
1160
|
+
});
|
|
1161
|
+
const tempNode = {
|
|
1162
|
+
uuid: nodeUuid,
|
|
1163
|
+
actions: [],
|
|
1164
|
+
exits: hasRouter
|
|
1165
|
+
? [] // Router-based nodes will generate their own exits
|
|
1166
|
+
: [
|
|
1167
|
+
{
|
|
1168
|
+
uuid: generateUUID(),
|
|
1169
|
+
destination_uuid: null
|
|
1170
|
+
}
|
|
1171
|
+
]
|
|
1172
|
+
};
|
|
1173
|
+
if (hasRouter) {
|
|
1174
|
+
// This node uses a router - initialize it with empty structure
|
|
1175
|
+
tempNode.router = {
|
|
1176
|
+
type: 'switch',
|
|
1177
|
+
categories: [],
|
|
1178
|
+
cases: [],
|
|
1179
|
+
operand: '@input.text',
|
|
1180
|
+
default_category_uuid: undefined
|
|
1181
|
+
};
|
|
1182
|
+
}
|
|
1183
|
+
const tempNodeUI = {
|
|
1184
|
+
position: {
|
|
1185
|
+
left: selection.position.x,
|
|
1186
|
+
top: selection.position.y
|
|
1187
|
+
},
|
|
1188
|
+
type: nodeType,
|
|
1189
|
+
config: {}
|
|
1190
|
+
};
|
|
1191
|
+
// Mark that we're creating a new node and store the position
|
|
1192
|
+
this.isCreatingNewNode = true;
|
|
1193
|
+
this.pendingNodePosition = {
|
|
1194
|
+
left: selection.position.x,
|
|
1195
|
+
top: selection.position.y
|
|
1196
|
+
};
|
|
1197
|
+
// Open the node editor with the temporary node
|
|
1198
|
+
this.editingNode = tempNode;
|
|
1199
|
+
this.editingNodeUI = tempNodeUI;
|
|
1200
|
+
// If this is an action type, we also need to set up an editing action
|
|
1201
|
+
if (isActionType) {
|
|
1202
|
+
const actionUuid = generateUUID();
|
|
1203
|
+
this.editingAction = {
|
|
1204
|
+
uuid: actionUuid,
|
|
1205
|
+
type: selection.nodeType
|
|
1206
|
+
};
|
|
1207
|
+
}
|
|
766
1208
|
}
|
|
767
1209
|
handleActionEditRequested(event) {
|
|
768
1210
|
// For action editing, we set the action and find the corresponding node
|
|
@@ -775,24 +1217,80 @@ export class Editor extends RapidElement {
|
|
|
775
1217
|
this.editingNodeUI = this.definition._ui.nodes[nodeUuid];
|
|
776
1218
|
}
|
|
777
1219
|
}
|
|
1220
|
+
handleAddActionRequested(event) {
|
|
1221
|
+
// Get the node where we want to add the action
|
|
1222
|
+
const nodeUuid = event.detail.nodeUuid;
|
|
1223
|
+
const node = this.definition.nodes.find((n) => n.uuid === nodeUuid);
|
|
1224
|
+
if (!node) {
|
|
1225
|
+
return;
|
|
1226
|
+
}
|
|
1227
|
+
// Get the node's position to place the selector near it
|
|
1228
|
+
const nodeUI = this.definition._ui.nodes[nodeUuid];
|
|
1229
|
+
if (!nodeUI) {
|
|
1230
|
+
return;
|
|
1231
|
+
}
|
|
1232
|
+
// Show the node type selector in action mode, excluding branching actions
|
|
1233
|
+
const selector = this.querySelector('temba-node-type-selector');
|
|
1234
|
+
if (selector) {
|
|
1235
|
+
// Show the selector near the node, using a mode that excludes branching actions
|
|
1236
|
+
selector.show('action-no-branching', {
|
|
1237
|
+
x: nodeUI.position.left,
|
|
1238
|
+
y: nodeUI.position.top
|
|
1239
|
+
});
|
|
1240
|
+
// Store the node UUID so we know which node to add the action to
|
|
1241
|
+
this.addActionToNodeUuid = nodeUuid;
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
778
1244
|
handleNodeEditRequested(event) {
|
|
779
1245
|
this.editingNode = event.detail.node;
|
|
780
1246
|
this.editingNodeUI = event.detail.nodeUI;
|
|
781
1247
|
}
|
|
782
1248
|
handleActionSaved(updatedAction) {
|
|
783
|
-
var
|
|
1249
|
+
var _b, _c;
|
|
784
1250
|
if (this.editingNode && this.editingAction) {
|
|
785
|
-
|
|
786
|
-
|
|
1251
|
+
let updatedActions;
|
|
1252
|
+
// Check if this action already exists in the node
|
|
1253
|
+
const existingActionIndex = this.editingNode.actions.findIndex((action) => action.uuid === this.editingAction.uuid);
|
|
1254
|
+
if (existingActionIndex >= 0) {
|
|
1255
|
+
// Update existing action
|
|
1256
|
+
updatedActions = this.editingNode.actions.map((action) => action.uuid === this.editingAction.uuid ? updatedAction : action);
|
|
1257
|
+
}
|
|
1258
|
+
else {
|
|
1259
|
+
// Add new action
|
|
1260
|
+
updatedActions = [...this.editingNode.actions, updatedAction];
|
|
1261
|
+
}
|
|
787
1262
|
const updatedNode = { ...this.editingNode, actions: updatedActions };
|
|
788
|
-
//
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
this.
|
|
795
|
-
|
|
1263
|
+
// Check if we're creating a new node or updating an existing one
|
|
1264
|
+
if (this.isCreatingNewNode) {
|
|
1265
|
+
// This is a new node with a new action - add it to the store
|
|
1266
|
+
const store = getStore();
|
|
1267
|
+
const nodeUI = {
|
|
1268
|
+
position: this.pendingNodePosition || { left: 0, top: 0 },
|
|
1269
|
+
type: (_b = this.editingNodeUI) === null || _b === void 0 ? void 0 : _b.type,
|
|
1270
|
+
config: {}
|
|
1271
|
+
};
|
|
1272
|
+
// Add the node to the store
|
|
1273
|
+
store.getState().addNode(updatedNode, nodeUI);
|
|
1274
|
+
// Reset the creation flags
|
|
1275
|
+
this.isCreatingNewNode = false;
|
|
1276
|
+
this.pendingNodePosition = null;
|
|
1277
|
+
// Repaint jsplumb connections
|
|
1278
|
+
if (this.plumber) {
|
|
1279
|
+
requestAnimationFrame(() => {
|
|
1280
|
+
this.plumber.repaintEverything();
|
|
1281
|
+
});
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
else {
|
|
1285
|
+
// Update existing node in the store
|
|
1286
|
+
(_c = getStore()) === null || _c === void 0 ? void 0 : _c.getState().updateNode(this.editingNode.uuid, updatedNode);
|
|
1287
|
+
// Repaint jsplumb connections in case node size changed
|
|
1288
|
+
if (this.plumber) {
|
|
1289
|
+
// Use requestAnimationFrame to ensure DOM has been updated first
|
|
1290
|
+
requestAnimationFrame(() => {
|
|
1291
|
+
this.plumber.repaintEverything();
|
|
1292
|
+
});
|
|
1293
|
+
}
|
|
796
1294
|
}
|
|
797
1295
|
}
|
|
798
1296
|
this.closeNodeEditor();
|
|
@@ -803,25 +1301,51 @@ export class Editor extends RapidElement {
|
|
|
803
1301
|
this.editingAction = null;
|
|
804
1302
|
}
|
|
805
1303
|
handleActionEditCanceled() {
|
|
1304
|
+
// If we were creating a new node, just discard it
|
|
1305
|
+
if (this.isCreatingNewNode) {
|
|
1306
|
+
this.isCreatingNewNode = false;
|
|
1307
|
+
this.pendingNodePosition = null;
|
|
1308
|
+
}
|
|
806
1309
|
this.closeNodeEditor();
|
|
807
1310
|
}
|
|
808
|
-
handleNodeSaved(updatedNode) {
|
|
809
|
-
var
|
|
1311
|
+
handleNodeSaved(updatedNode, uiConfig) {
|
|
1312
|
+
var _b, _c, _d;
|
|
810
1313
|
if (this.editingNode) {
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
const
|
|
814
|
-
const
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
1314
|
+
if (this.isCreatingNewNode) {
|
|
1315
|
+
// This is a new node - add it to the store for the first time
|
|
1316
|
+
const store = getStore();
|
|
1317
|
+
const nodeUI = {
|
|
1318
|
+
position: this.pendingNodePosition || { left: 0, top: 0 },
|
|
1319
|
+
type: (_b = this.editingNodeUI) === null || _b === void 0 ? void 0 : _b.type,
|
|
1320
|
+
config: uiConfig || {}
|
|
1321
|
+
};
|
|
1322
|
+
// Add the node to the store
|
|
1323
|
+
store.getState().addNode(updatedNode, nodeUI);
|
|
1324
|
+
// Reset the creation flags
|
|
1325
|
+
this.isCreatingNewNode = false;
|
|
1326
|
+
this.pendingNodePosition = null;
|
|
1327
|
+
}
|
|
1328
|
+
else {
|
|
1329
|
+
// This is an existing node - update it
|
|
1330
|
+
// Clean up jsPlumb connections for removed exits before updating the node
|
|
1331
|
+
if (this.plumber) {
|
|
1332
|
+
const oldExits = this.editingNode.exits || [];
|
|
1333
|
+
const newExits = updatedNode.exits || [];
|
|
1334
|
+
// Find exits that were removed
|
|
1335
|
+
const removedExits = oldExits.filter((oldExit) => !newExits.find((newExit) => newExit.uuid === oldExit.uuid));
|
|
1336
|
+
// Remove jsPlumb connections for removed exits
|
|
1337
|
+
removedExits.forEach((exit) => {
|
|
1338
|
+
this.plumber.removeExitConnection(exit.uuid);
|
|
1339
|
+
});
|
|
1340
|
+
}
|
|
1341
|
+
this.plumber.revalidate([updatedNode.uuid]);
|
|
1342
|
+
// Update the node in the store
|
|
1343
|
+
(_c = getStore()) === null || _c === void 0 ? void 0 : _c.getState().updateNode(this.editingNode.uuid, updatedNode);
|
|
1344
|
+
// Update the UI config if provided
|
|
1345
|
+
if (uiConfig) {
|
|
1346
|
+
(_d = getStore()) === null || _d === void 0 ? void 0 : _d.getState().updateNodeUIConfig(updatedNode.uuid, uiConfig);
|
|
1347
|
+
}
|
|
821
1348
|
}
|
|
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
1349
|
// Repaint jsplumb connections in case node size changed
|
|
826
1350
|
if (this.plumber) {
|
|
827
1351
|
// Use requestAnimationFrame to ensure DOM has been updated first
|
|
@@ -833,17 +1357,787 @@ export class Editor extends RapidElement {
|
|
|
833
1357
|
this.closeNodeEditor();
|
|
834
1358
|
}
|
|
835
1359
|
handleNodeEditCanceled() {
|
|
1360
|
+
// If we were creating a new node, just discard it
|
|
1361
|
+
if (this.isCreatingNewNode) {
|
|
1362
|
+
this.isCreatingNewNode = false;
|
|
1363
|
+
this.pendingNodePosition = null;
|
|
1364
|
+
}
|
|
836
1365
|
this.closeNodeEditor();
|
|
837
1366
|
}
|
|
1367
|
+
getNodeAtPosition(mouseX, mouseY) {
|
|
1368
|
+
// Get all node elements
|
|
1369
|
+
const nodeElements = this.querySelectorAll('temba-flow-node');
|
|
1370
|
+
for (const nodeElement of Array.from(nodeElements)) {
|
|
1371
|
+
const rect = nodeElement.getBoundingClientRect();
|
|
1372
|
+
if (mouseX >= rect.left &&
|
|
1373
|
+
mouseX <= rect.right &&
|
|
1374
|
+
mouseY >= rect.top &&
|
|
1375
|
+
mouseY <= rect.bottom) {
|
|
1376
|
+
return nodeElement.getAttribute('data-node-uuid');
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
return null;
|
|
1380
|
+
}
|
|
1381
|
+
calculateCanvasDropPosition(mouseX, mouseY, applyGridSnapping = true) {
|
|
1382
|
+
// calculate the position on the canvas
|
|
1383
|
+
const canvas = this.querySelector('#canvas');
|
|
1384
|
+
if (!canvas)
|
|
1385
|
+
return { left: 0, top: 0 };
|
|
1386
|
+
const canvasRect = canvas.getBoundingClientRect();
|
|
1387
|
+
// calculate position relative to canvas
|
|
1388
|
+
// canvasRect gives us the canvas position in the viewport, which already accounts for scroll
|
|
1389
|
+
// so we just need mouseX/Y - canvasRect.left/top to get position within canvas
|
|
1390
|
+
const left = mouseX - canvasRect.left - DROP_PREVIEW_OFFSET_X;
|
|
1391
|
+
const top = mouseY - canvasRect.top - DROP_PREVIEW_OFFSET_Y;
|
|
1392
|
+
// Apply grid snapping only if requested (for final drop position)
|
|
1393
|
+
if (applyGridSnapping) {
|
|
1394
|
+
return {
|
|
1395
|
+
left: snapToGrid(left),
|
|
1396
|
+
top: snapToGrid(top)
|
|
1397
|
+
};
|
|
1398
|
+
}
|
|
1399
|
+
return { left, top };
|
|
1400
|
+
}
|
|
1401
|
+
handleActionDragExternal(event) {
|
|
1402
|
+
const { action, nodeUuid, actionIndex, mouseX, mouseY, actionHeight = 60 } = event.detail;
|
|
1403
|
+
// Check if mouse is over another execute_actions node
|
|
1404
|
+
const targetNode = this.getNodeAtPosition(mouseX, mouseY);
|
|
1405
|
+
if (targetNode && targetNode !== nodeUuid) {
|
|
1406
|
+
const targetNodeUI = this.definition._ui.nodes[targetNode];
|
|
1407
|
+
const targetNodeDef = this.definition.nodes.find((n) => n.uuid === targetNode);
|
|
1408
|
+
// Only allow dropping on execute_actions nodes, and not the source node
|
|
1409
|
+
if ((targetNodeUI === null || targetNodeUI === void 0 ? void 0 : targetNodeUI.type) === 'execute_actions' && targetNodeDef) {
|
|
1410
|
+
// If we moved to a different target node, clear the previous one's placeholder
|
|
1411
|
+
if (this.previousActionDragTargetNodeUuid &&
|
|
1412
|
+
this.previousActionDragTargetNodeUuid !== targetNode) {
|
|
1413
|
+
const previousElement = this.querySelector(`temba-flow-node[data-node-uuid="${this.previousActionDragTargetNodeUuid}"]`);
|
|
1414
|
+
if (previousElement) {
|
|
1415
|
+
previousElement.dispatchEvent(new CustomEvent('action-drag-leave', {
|
|
1416
|
+
detail: {},
|
|
1417
|
+
bubbles: false
|
|
1418
|
+
}));
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
// Update target node for drop handling
|
|
1422
|
+
this.actionDragTargetNodeUuid = targetNode;
|
|
1423
|
+
this.previousActionDragTargetNodeUuid = targetNode;
|
|
1424
|
+
// Hide canvas preview when over a valid target
|
|
1425
|
+
this.canvasDropPreview = null;
|
|
1426
|
+
// Tell source node to show ghost (we're over a valid target)
|
|
1427
|
+
const sourceElement = this.querySelector(`temba-flow-node[data-node-uuid="${nodeUuid}"]`);
|
|
1428
|
+
if (sourceElement) {
|
|
1429
|
+
sourceElement.dispatchEvent(new CustomEvent('action-show-ghost', {
|
|
1430
|
+
detail: {},
|
|
1431
|
+
bubbles: false
|
|
1432
|
+
}));
|
|
1433
|
+
}
|
|
1434
|
+
// Notify the target node about the drag
|
|
1435
|
+
const targetElement = this.querySelector(`temba-flow-node[data-node-uuid="${targetNode}"]`);
|
|
1436
|
+
if (targetElement) {
|
|
1437
|
+
targetElement.dispatchEvent(new CustomEvent('action-drag-over', {
|
|
1438
|
+
detail: {
|
|
1439
|
+
action,
|
|
1440
|
+
sourceNodeUuid: nodeUuid,
|
|
1441
|
+
actionIndex,
|
|
1442
|
+
mouseX,
|
|
1443
|
+
mouseY,
|
|
1444
|
+
actionHeight
|
|
1445
|
+
},
|
|
1446
|
+
bubbles: false
|
|
1447
|
+
}));
|
|
1448
|
+
}
|
|
1449
|
+
this.requestUpdate();
|
|
1450
|
+
return;
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
// Not over a valid target node, clear any previous target's placeholder
|
|
1454
|
+
if (this.previousActionDragTargetNodeUuid) {
|
|
1455
|
+
const previousElement = this.querySelector(`temba-flow-node[data-node-uuid="${this.previousActionDragTargetNodeUuid}"]`);
|
|
1456
|
+
if (previousElement) {
|
|
1457
|
+
previousElement.dispatchEvent(new CustomEvent('action-drag-leave', {
|
|
1458
|
+
detail: {},
|
|
1459
|
+
bubbles: false
|
|
1460
|
+
}));
|
|
1461
|
+
}
|
|
1462
|
+
this.previousActionDragTargetNodeUuid = null;
|
|
1463
|
+
}
|
|
1464
|
+
this.actionDragTargetNodeUuid = null;
|
|
1465
|
+
// Tell source node to hide ghost (we're not over a valid target)
|
|
1466
|
+
const sourceElement = this.querySelector(`temba-flow-node[data-node-uuid="${nodeUuid}"]`);
|
|
1467
|
+
if (sourceElement) {
|
|
1468
|
+
sourceElement.dispatchEvent(new CustomEvent('action-hide-ghost', {
|
|
1469
|
+
detail: {},
|
|
1470
|
+
bubbles: false
|
|
1471
|
+
}));
|
|
1472
|
+
}
|
|
1473
|
+
// Don't snap to grid for preview - let it follow cursor smoothly
|
|
1474
|
+
const position = this.calculateCanvasDropPosition(mouseX, mouseY, false);
|
|
1475
|
+
this.canvasDropPreview = {
|
|
1476
|
+
action,
|
|
1477
|
+
nodeUuid,
|
|
1478
|
+
actionIndex,
|
|
1479
|
+
position,
|
|
1480
|
+
actionHeight
|
|
1481
|
+
};
|
|
1482
|
+
// Force re-render to update preview position
|
|
1483
|
+
this.requestUpdate();
|
|
1484
|
+
}
|
|
1485
|
+
handleActionDragInternal(_event) {
|
|
1486
|
+
// Clear any previous target's placeholder when returning to internal drag
|
|
1487
|
+
if (this.previousActionDragTargetNodeUuid) {
|
|
1488
|
+
const previousElement = this.querySelector(`temba-flow-node[data-node-uuid="${this.previousActionDragTargetNodeUuid}"]`);
|
|
1489
|
+
if (previousElement) {
|
|
1490
|
+
previousElement.dispatchEvent(new CustomEvent('action-drag-leave', {
|
|
1491
|
+
detail: {},
|
|
1492
|
+
bubbles: false
|
|
1493
|
+
}));
|
|
1494
|
+
}
|
|
1495
|
+
this.previousActionDragTargetNodeUuid = null;
|
|
1496
|
+
}
|
|
1497
|
+
this.canvasDropPreview = null;
|
|
1498
|
+
this.actionDragTargetNodeUuid = null;
|
|
1499
|
+
}
|
|
1500
|
+
handleActionDropExternal(event) {
|
|
1501
|
+
var _b, _c, _d;
|
|
1502
|
+
const { action, nodeUuid, actionIndex, mouseX, mouseY } = event.detail;
|
|
1503
|
+
// Check if we're dropping on an existing execute_actions node
|
|
1504
|
+
const targetNodeUuid = this.actionDragTargetNodeUuid;
|
|
1505
|
+
if (targetNodeUuid && targetNodeUuid !== nodeUuid) {
|
|
1506
|
+
// Dropping on another node - notify the target node to handle the drop
|
|
1507
|
+
const targetElement = this.querySelector(`temba-flow-node[data-node-uuid="${targetNodeUuid}"]`);
|
|
1508
|
+
if (targetElement) {
|
|
1509
|
+
targetElement.dispatchEvent(new CustomEvent('action-drop', {
|
|
1510
|
+
detail: {
|
|
1511
|
+
action,
|
|
1512
|
+
sourceNodeUuid: nodeUuid,
|
|
1513
|
+
actionIndex,
|
|
1514
|
+
mouseX,
|
|
1515
|
+
mouseY
|
|
1516
|
+
},
|
|
1517
|
+
bubbles: false
|
|
1518
|
+
}));
|
|
1519
|
+
}
|
|
1520
|
+
// Clear state
|
|
1521
|
+
this.canvasDropPreview = null;
|
|
1522
|
+
this.actionDragTargetNodeUuid = null;
|
|
1523
|
+
return;
|
|
1524
|
+
}
|
|
1525
|
+
// Not dropping on another node, create a new one on canvas
|
|
1526
|
+
// Snap to grid for the final drop position
|
|
1527
|
+
const position = this.calculateCanvasDropPosition(mouseX, mouseY, true);
|
|
1528
|
+
// remove the action from the original node
|
|
1529
|
+
const originalNode = this.definition.nodes.find((n) => n.uuid === nodeUuid);
|
|
1530
|
+
if (!originalNode)
|
|
1531
|
+
return;
|
|
1532
|
+
const updatedActions = originalNode.actions.filter((_a, idx) => idx !== actionIndex);
|
|
1533
|
+
// if no actions remain, delete the node
|
|
1534
|
+
if (updatedActions.length === 0) {
|
|
1535
|
+
(_b = getStore()) === null || _b === void 0 ? void 0 : _b.getState().removeNodes([nodeUuid]);
|
|
1536
|
+
}
|
|
1537
|
+
else {
|
|
1538
|
+
// update the node
|
|
1539
|
+
const updatedNode = { ...originalNode, actions: updatedActions };
|
|
1540
|
+
(_c = getStore()) === null || _c === void 0 ? void 0 : _c.getState().updateNode(nodeUuid, updatedNode);
|
|
1541
|
+
}
|
|
1542
|
+
// create a new execute_actions node with the dropped action
|
|
1543
|
+
const newNode = {
|
|
1544
|
+
uuid: generateUUID(),
|
|
1545
|
+
actions: [action],
|
|
1546
|
+
exits: [
|
|
1547
|
+
{
|
|
1548
|
+
uuid: generateUUID(),
|
|
1549
|
+
destination_uuid: null
|
|
1550
|
+
}
|
|
1551
|
+
]
|
|
1552
|
+
};
|
|
1553
|
+
const newNodeUI = {
|
|
1554
|
+
position,
|
|
1555
|
+
type: 'execute_actions',
|
|
1556
|
+
config: {}
|
|
1557
|
+
};
|
|
1558
|
+
// add the new node
|
|
1559
|
+
(_d = getStore()) === null || _d === void 0 ? void 0 : _d.getState().addNode(newNode, newNodeUI);
|
|
1560
|
+
// clear the preview
|
|
1561
|
+
this.canvasDropPreview = null;
|
|
1562
|
+
this.actionDragTargetNodeUuid = null;
|
|
1563
|
+
// repaint connections
|
|
1564
|
+
if (this.plumber) {
|
|
1565
|
+
requestAnimationFrame(() => {
|
|
1566
|
+
this.plumber.repaintEverything();
|
|
1567
|
+
});
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1570
|
+
getLocalizationLanguages() {
|
|
1571
|
+
if (!this.definition) {
|
|
1572
|
+
return [];
|
|
1573
|
+
}
|
|
1574
|
+
const baseLanguage = this.definition.language;
|
|
1575
|
+
return this.getAvailableLanguages().filter((lang) => lang.code !== baseLanguage);
|
|
1576
|
+
}
|
|
1577
|
+
getLocalizationProgress(languageCode) {
|
|
1578
|
+
if (!this.definition ||
|
|
1579
|
+
!languageCode ||
|
|
1580
|
+
languageCode === this.definition.language) {
|
|
1581
|
+
return { total: 0, localized: 0 };
|
|
1582
|
+
}
|
|
1583
|
+
const bundles = this.buildTranslationBundles(this.translationFilters.categories, languageCode);
|
|
1584
|
+
return this.getTranslationCounts(bundles);
|
|
1585
|
+
}
|
|
1586
|
+
getLanguageLocalization(languageCode) {
|
|
1587
|
+
var _b;
|
|
1588
|
+
if (!((_b = this.definition) === null || _b === void 0 ? void 0 : _b.localization)) {
|
|
1589
|
+
return {};
|
|
1590
|
+
}
|
|
1591
|
+
return this.definition.localization[languageCode] || {};
|
|
1592
|
+
}
|
|
1593
|
+
buildTranslationBundles(includeCategories, languageCode = this.languageCode) {
|
|
1594
|
+
if (!this.definition ||
|
|
1595
|
+
!languageCode ||
|
|
1596
|
+
languageCode === this.definition.language) {
|
|
1597
|
+
return [];
|
|
1598
|
+
}
|
|
1599
|
+
const languageLocalization = this.getLanguageLocalization(languageCode);
|
|
1600
|
+
const bundles = [];
|
|
1601
|
+
this.definition.nodes.forEach((node) => {
|
|
1602
|
+
var _b, _c, _d, _e;
|
|
1603
|
+
node.actions.forEach((action) => {
|
|
1604
|
+
const config = ACTION_CONFIG[action.type];
|
|
1605
|
+
if (!(config === null || config === void 0 ? void 0 : config.localizable) || config.localizable.length === 0) {
|
|
1606
|
+
return;
|
|
1607
|
+
}
|
|
1608
|
+
// For send_msg actions, only count 'text' for progress tracking
|
|
1609
|
+
// (quick_replies and attachments are still localizable but don't count toward progress)
|
|
1610
|
+
const localizableKeys = action.type === 'send_msg'
|
|
1611
|
+
? config.localizable.filter((key) => key === 'text')
|
|
1612
|
+
: config.localizable;
|
|
1613
|
+
const translations = this.findTranslations('property', action.uuid, localizableKeys, action, languageLocalization);
|
|
1614
|
+
if (translations.length > 0) {
|
|
1615
|
+
bundles.push({
|
|
1616
|
+
nodeUuid: node.uuid,
|
|
1617
|
+
actionUuid: action.uuid,
|
|
1618
|
+
translations
|
|
1619
|
+
});
|
|
1620
|
+
}
|
|
1621
|
+
});
|
|
1622
|
+
if (!includeCategories) {
|
|
1623
|
+
return;
|
|
1624
|
+
}
|
|
1625
|
+
const nodeUI = (_c = (_b = this.definition._ui) === null || _b === void 0 ? void 0 : _b.nodes) === null || _c === void 0 ? void 0 : _c[node.uuid];
|
|
1626
|
+
const nodeType = nodeUI === null || nodeUI === void 0 ? void 0 : nodeUI.type;
|
|
1627
|
+
if (!nodeType) {
|
|
1628
|
+
return;
|
|
1629
|
+
}
|
|
1630
|
+
const nodeConfig = NODE_CONFIG[nodeType];
|
|
1631
|
+
if ((nodeConfig === null || nodeConfig === void 0 ? void 0 : nodeConfig.localizable) === 'categories' &&
|
|
1632
|
+
((_e = (_d = node.router) === null || _d === void 0 ? void 0 : _d.categories) === null || _e === void 0 ? void 0 : _e.length)) {
|
|
1633
|
+
const categoryTranslations = node.router.categories.flatMap((category) => this.findTranslations('category', category.uuid, ['name'], category, languageLocalization));
|
|
1634
|
+
if (categoryTranslations.length > 0) {
|
|
1635
|
+
bundles.push({
|
|
1636
|
+
nodeUuid: node.uuid,
|
|
1637
|
+
translations: categoryTranslations
|
|
1638
|
+
});
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
});
|
|
1642
|
+
return bundles;
|
|
1643
|
+
}
|
|
1644
|
+
findTranslations(type, uuid, localizeableKeys, source, localization) {
|
|
1645
|
+
const translations = [];
|
|
1646
|
+
localizeableKeys.forEach((attribute) => {
|
|
1647
|
+
if (attribute === 'quick_replies') {
|
|
1648
|
+
return;
|
|
1649
|
+
}
|
|
1650
|
+
const pathSegments = attribute.split('.');
|
|
1651
|
+
let from = source;
|
|
1652
|
+
let to = [];
|
|
1653
|
+
while (pathSegments.length > 0 && from) {
|
|
1654
|
+
if (from.uuid) {
|
|
1655
|
+
to = localization[from.uuid];
|
|
1656
|
+
}
|
|
1657
|
+
const path = pathSegments.shift();
|
|
1658
|
+
if (!path) {
|
|
1659
|
+
break;
|
|
1660
|
+
}
|
|
1661
|
+
if (to) {
|
|
1662
|
+
to = to[path];
|
|
1663
|
+
}
|
|
1664
|
+
from = from[path];
|
|
1665
|
+
}
|
|
1666
|
+
if (!from) {
|
|
1667
|
+
return;
|
|
1668
|
+
}
|
|
1669
|
+
const fromValue = this.formatTranslationValue(from);
|
|
1670
|
+
if (!fromValue) {
|
|
1671
|
+
return;
|
|
1672
|
+
}
|
|
1673
|
+
const toValue = to ? this.formatTranslationValue(to) : null;
|
|
1674
|
+
translations.push({
|
|
1675
|
+
uuid,
|
|
1676
|
+
type,
|
|
1677
|
+
attribute,
|
|
1678
|
+
from: fromValue,
|
|
1679
|
+
to: toValue
|
|
1680
|
+
});
|
|
1681
|
+
});
|
|
1682
|
+
return translations;
|
|
1683
|
+
}
|
|
1684
|
+
formatTranslationValue(value) {
|
|
1685
|
+
if (value === null || value === undefined) {
|
|
1686
|
+
return null;
|
|
1687
|
+
}
|
|
1688
|
+
if (Array.isArray(value)) {
|
|
1689
|
+
const normalized = value
|
|
1690
|
+
.map((entry) => this.formatTranslationValue(entry))
|
|
1691
|
+
.filter((entry) => !!entry);
|
|
1692
|
+
return normalized.length > 0 ? normalized.join(', ') : null;
|
|
1693
|
+
}
|
|
1694
|
+
if (typeof value === 'object') {
|
|
1695
|
+
if ('name' in value && value.name) {
|
|
1696
|
+
return String(value.name);
|
|
1697
|
+
}
|
|
1698
|
+
if ('arguments' in value && Array.isArray(value.arguments)) {
|
|
1699
|
+
return value.arguments.join(' ');
|
|
1700
|
+
}
|
|
1701
|
+
return null;
|
|
1702
|
+
}
|
|
1703
|
+
if (typeof value === 'number') {
|
|
1704
|
+
return value.toString();
|
|
1705
|
+
}
|
|
1706
|
+
if (typeof value === 'string') {
|
|
1707
|
+
const trimmed = value.trim();
|
|
1708
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
1709
|
+
}
|
|
1710
|
+
return null;
|
|
1711
|
+
}
|
|
1712
|
+
getTranslationCounts(bundles) {
|
|
1713
|
+
return bundles.reduce((counts, bundle) => {
|
|
1714
|
+
bundle.translations.forEach((translation) => {
|
|
1715
|
+
counts.total += 1;
|
|
1716
|
+
if (translation.to && translation.to.trim().length > 0) {
|
|
1717
|
+
counts.localized += 1;
|
|
1718
|
+
}
|
|
1719
|
+
});
|
|
1720
|
+
return counts;
|
|
1721
|
+
}, { total: 0, localized: 0 });
|
|
1722
|
+
}
|
|
1723
|
+
handleLocalizationTabClick() {
|
|
1724
|
+
const languages = this.getLocalizationLanguages();
|
|
1725
|
+
if (!languages.length) {
|
|
1726
|
+
return;
|
|
1727
|
+
}
|
|
1728
|
+
this.localizationWindowHidden = false;
|
|
1729
|
+
const alreadySelected = languages.some((lang) => lang.code === this.languageCode);
|
|
1730
|
+
if (!alreadySelected) {
|
|
1731
|
+
this.handleLanguageChange(languages[0].code);
|
|
1732
|
+
}
|
|
1733
|
+
}
|
|
1734
|
+
handleLocalizationLanguageSelect(languageCode) {
|
|
1735
|
+
if (languageCode === this.languageCode) {
|
|
1736
|
+
return;
|
|
1737
|
+
}
|
|
1738
|
+
this.handleLanguageChange(languageCode);
|
|
1739
|
+
}
|
|
1740
|
+
handleLocalizationLanguageSelectChange(event) {
|
|
1741
|
+
var _b, _c;
|
|
1742
|
+
const select = event.target;
|
|
1743
|
+
const nextValue = (_c = (_b = select === null || select === void 0 ? void 0 : select.values) === null || _b === void 0 ? void 0 : _b[0]) === null || _c === void 0 ? void 0 : _c.value;
|
|
1744
|
+
if (nextValue) {
|
|
1745
|
+
this.handleLocalizationLanguageSelect(nextValue);
|
|
1746
|
+
}
|
|
1747
|
+
}
|
|
1748
|
+
handleLocalizationWindowClosed() {
|
|
1749
|
+
var _b;
|
|
1750
|
+
this.localizationWindowHidden = true;
|
|
1751
|
+
const baseLanguage = (_b = this.definition) === null || _b === void 0 ? void 0 : _b.language;
|
|
1752
|
+
if (baseLanguage && this.languageCode !== baseLanguage) {
|
|
1753
|
+
this.handleLanguageChange(baseLanguage);
|
|
1754
|
+
}
|
|
1755
|
+
}
|
|
1756
|
+
toggleTranslationSettings() {
|
|
1757
|
+
this.translationSettingsExpanded = !this.translationSettingsExpanded;
|
|
1758
|
+
}
|
|
1759
|
+
handleLocalizationProgressToggleClick(event) {
|
|
1760
|
+
const target = event.target;
|
|
1761
|
+
if (target.closest('.translation-settings-toggle')) {
|
|
1762
|
+
return;
|
|
1763
|
+
}
|
|
1764
|
+
this.toggleTranslationSettings();
|
|
1765
|
+
}
|
|
1766
|
+
handleLocalizationProgressToggleKeydown(event) {
|
|
1767
|
+
if (event.key === 'Enter' || event.key === ' ') {
|
|
1768
|
+
event.preventDefault();
|
|
1769
|
+
this.toggleTranslationSettings();
|
|
1770
|
+
}
|
|
1771
|
+
}
|
|
1772
|
+
handleIncludeCategoriesChange(event) {
|
|
1773
|
+
var _b, _c;
|
|
1774
|
+
const checkbox = event.target;
|
|
1775
|
+
const categories = (_b = checkbox === null || checkbox === void 0 ? void 0 : checkbox.checked) !== null && _b !== void 0 ? _b : false;
|
|
1776
|
+
this.translationFilters = { categories };
|
|
1777
|
+
(_c = getStore()) === null || _c === void 0 ? void 0 : _c.getState().setTranslationFilters({ categories });
|
|
1778
|
+
this.requestUpdate();
|
|
1779
|
+
}
|
|
1780
|
+
async handleAutoTranslateClick(event) {
|
|
1781
|
+
event.preventDefault();
|
|
1782
|
+
event.stopPropagation();
|
|
1783
|
+
if (this.autoTranslating) {
|
|
1784
|
+
this.autoTranslating = false;
|
|
1785
|
+
return;
|
|
1786
|
+
}
|
|
1787
|
+
this.autoTranslateDialogOpen = true;
|
|
1788
|
+
}
|
|
1789
|
+
handleAutoTranslateDialogButton(event) {
|
|
1790
|
+
var _b;
|
|
1791
|
+
const button = (_b = event.detail) === null || _b === void 0 ? void 0 : _b.button;
|
|
1792
|
+
if (!button) {
|
|
1793
|
+
return;
|
|
1794
|
+
}
|
|
1795
|
+
if (button.name === 'Translate') {
|
|
1796
|
+
if (!this.autoTranslateModel) {
|
|
1797
|
+
return;
|
|
1798
|
+
}
|
|
1799
|
+
this.autoTranslateDialogOpen = false;
|
|
1800
|
+
this.autoTranslateError = null;
|
|
1801
|
+
this.autoTranslating = true;
|
|
1802
|
+
this.runAutoTranslation().catch((error) => {
|
|
1803
|
+
console.error('Auto translation failed', error);
|
|
1804
|
+
this.autoTranslateError = 'Auto translation failed. Please try again.';
|
|
1805
|
+
this.autoTranslating = false;
|
|
1806
|
+
});
|
|
1807
|
+
}
|
|
1808
|
+
else if (button.name === 'Cancel' || button.name === 'Close') {
|
|
1809
|
+
this.autoTranslateDialogOpen = false;
|
|
1810
|
+
}
|
|
1811
|
+
}
|
|
1812
|
+
handleAutoTranslateModelChange(event) {
|
|
1813
|
+
var _b;
|
|
1814
|
+
const select = event.target;
|
|
1815
|
+
const nextModel = ((_b = select === null || select === void 0 ? void 0 : select.values) === null || _b === void 0 ? void 0 : _b[0]) || null;
|
|
1816
|
+
this.autoTranslateModel = nextModel;
|
|
1817
|
+
}
|
|
1818
|
+
shouldTranslateValue(text) {
|
|
1819
|
+
if (!text) {
|
|
1820
|
+
return false;
|
|
1821
|
+
}
|
|
1822
|
+
const trimmed = text.trim();
|
|
1823
|
+
if (trimmed.length <= 1) {
|
|
1824
|
+
return false;
|
|
1825
|
+
}
|
|
1826
|
+
if (/^\d+$/.test(trimmed)) {
|
|
1827
|
+
return false;
|
|
1828
|
+
}
|
|
1829
|
+
return true;
|
|
1830
|
+
}
|
|
1831
|
+
async requestAutoTranslation(text) {
|
|
1832
|
+
var _b, _c;
|
|
1833
|
+
if (!this.autoTranslateModel || !this.definition) {
|
|
1834
|
+
return null;
|
|
1835
|
+
}
|
|
1836
|
+
const payload = {
|
|
1837
|
+
text,
|
|
1838
|
+
lang: {
|
|
1839
|
+
from: this.definition.language,
|
|
1840
|
+
to: this.languageCode
|
|
1841
|
+
}
|
|
1842
|
+
};
|
|
1843
|
+
const response = await postJSON(`/llm/translate/${this.autoTranslateModel.uuid}/`, payload);
|
|
1844
|
+
if ((response === null || response === void 0 ? void 0 : response.status) === 200) {
|
|
1845
|
+
const result = ((_b = response.json) === null || _b === void 0 ? void 0 : _b.result) || ((_c = response.json) === null || _c === void 0 ? void 0 : _c.text);
|
|
1846
|
+
return result ? String(result) : null;
|
|
1847
|
+
}
|
|
1848
|
+
throw new Error('Auto translation request failed');
|
|
1849
|
+
}
|
|
1850
|
+
applyLocalizationUpdates(updates, autoTranslated = false) {
|
|
1851
|
+
if (!updates.length || !this.definition) {
|
|
1852
|
+
return;
|
|
1853
|
+
}
|
|
1854
|
+
const store = getStore();
|
|
1855
|
+
if (!store) {
|
|
1856
|
+
return;
|
|
1857
|
+
}
|
|
1858
|
+
updates.forEach(({ uuid, translations }) => {
|
|
1859
|
+
var _b, _c;
|
|
1860
|
+
const normalized = Object.entries(translations).reduce((acc, [key, value]) => {
|
|
1861
|
+
if (!value) {
|
|
1862
|
+
return acc;
|
|
1863
|
+
}
|
|
1864
|
+
acc[key] = Array.isArray(value) ? value : [value];
|
|
1865
|
+
return acc;
|
|
1866
|
+
}, {});
|
|
1867
|
+
const existing = ((_c = (_b = this.definition.localization) === null || _b === void 0 ? void 0 : _b[this.languageCode]) === null || _c === void 0 ? void 0 : _c[uuid]) || {};
|
|
1868
|
+
const merged = { ...existing, ...normalized };
|
|
1869
|
+
store.getState().updateLocalization(this.languageCode, uuid, merged);
|
|
1870
|
+
if (autoTranslated) {
|
|
1871
|
+
zustand
|
|
1872
|
+
.getState()
|
|
1873
|
+
.markAutoTranslated(this.languageCode, uuid, Object.keys(translations));
|
|
1874
|
+
}
|
|
1875
|
+
});
|
|
1876
|
+
}
|
|
1877
|
+
async runAutoTranslation() {
|
|
1878
|
+
if (!this.definition ||
|
|
1879
|
+
this.languageCode === this.definition.language ||
|
|
1880
|
+
!this.autoTranslateModel) {
|
|
1881
|
+
this.autoTranslating = false;
|
|
1882
|
+
return;
|
|
1883
|
+
}
|
|
1884
|
+
const bundles = this.buildTranslationBundles(this.translationFilters.categories);
|
|
1885
|
+
for (const bundle of bundles) {
|
|
1886
|
+
if (!this.autoTranslating) {
|
|
1887
|
+
break;
|
|
1888
|
+
}
|
|
1889
|
+
const untranslated = bundle.translations.filter((translation) => !translation.to || translation.to.trim().length === 0);
|
|
1890
|
+
if (untranslated.length === 0) {
|
|
1891
|
+
continue;
|
|
1892
|
+
}
|
|
1893
|
+
const updates = [];
|
|
1894
|
+
for (const translation of untranslated) {
|
|
1895
|
+
if (!this.autoTranslating) {
|
|
1896
|
+
break;
|
|
1897
|
+
}
|
|
1898
|
+
if (!this.shouldTranslateValue(translation.from)) {
|
|
1899
|
+
continue;
|
|
1900
|
+
}
|
|
1901
|
+
const cached = this.translationCache.get(translation.from);
|
|
1902
|
+
if (cached) {
|
|
1903
|
+
updates.push({
|
|
1904
|
+
uuid: translation.uuid,
|
|
1905
|
+
translations: { [translation.attribute]: cached }
|
|
1906
|
+
});
|
|
1907
|
+
continue;
|
|
1908
|
+
}
|
|
1909
|
+
try {
|
|
1910
|
+
const result = await this.requestAutoTranslation(translation.from);
|
|
1911
|
+
if (result) {
|
|
1912
|
+
this.translationCache.set(translation.from, result);
|
|
1913
|
+
updates.push({
|
|
1914
|
+
uuid: translation.uuid,
|
|
1915
|
+
translations: { [translation.attribute]: result }
|
|
1916
|
+
});
|
|
1917
|
+
}
|
|
1918
|
+
}
|
|
1919
|
+
catch (error) {
|
|
1920
|
+
console.error('Auto translation request failed', error);
|
|
1921
|
+
this.autoTranslateError =
|
|
1922
|
+
'Auto translation failed. Please try again.';
|
|
1923
|
+
this.autoTranslating = false;
|
|
1924
|
+
break;
|
|
1925
|
+
}
|
|
1926
|
+
}
|
|
1927
|
+
if (updates.length > 0) {
|
|
1928
|
+
this.applyLocalizationUpdates(updates, true);
|
|
1929
|
+
}
|
|
1930
|
+
if (!this.autoTranslating) {
|
|
1931
|
+
break;
|
|
1932
|
+
}
|
|
1933
|
+
}
|
|
1934
|
+
this.autoTranslating = false;
|
|
1935
|
+
}
|
|
1936
|
+
renderLocalizationWindow() {
|
|
1937
|
+
var _b, _c, _d;
|
|
1938
|
+
const languages = this.getLocalizationLanguages();
|
|
1939
|
+
if (!languages.length) {
|
|
1940
|
+
return html ``;
|
|
1941
|
+
}
|
|
1942
|
+
const baseLanguage = (_b = this.definition) === null || _b === void 0 ? void 0 : _b.language;
|
|
1943
|
+
const availableLanguages = this.getAvailableLanguages();
|
|
1944
|
+
const baseName = ((_c = availableLanguages.find((lang) => lang.code === baseLanguage)) === null || _c === void 0 ? void 0 : _c.name) ||
|
|
1945
|
+
'Base Language';
|
|
1946
|
+
const activeLanguageCode = languages.some((lang) => lang.code === this.languageCode)
|
|
1947
|
+
? this.languageCode
|
|
1948
|
+
: (_d = languages[0]) === null || _d === void 0 ? void 0 : _d.code;
|
|
1949
|
+
const activeLanguage = activeLanguageCode
|
|
1950
|
+
? languages.find((lang) => lang.code === activeLanguageCode)
|
|
1951
|
+
: null;
|
|
1952
|
+
const progress = this.getLocalizationProgress(activeLanguageCode || '');
|
|
1953
|
+
const includeCategories = this.translationFilters.categories;
|
|
1954
|
+
const settingsPanelId = 'translation-settings-panel';
|
|
1955
|
+
const remainingTranslations = Math.max(progress.total - progress.localized, 0);
|
|
1956
|
+
const hasTranslations = progress.total > 0;
|
|
1957
|
+
const hasPendingTranslations = remainingTranslations > 0;
|
|
1958
|
+
const autoTranslateButtonLabel = this.autoTranslating
|
|
1959
|
+
? 'Stop Auto Translate'
|
|
1960
|
+
: 'Auto Translate';
|
|
1961
|
+
const autoTranslateButtonDisabled = !this.autoTranslating && !hasTranslations;
|
|
1962
|
+
return html `
|
|
1963
|
+
<temba-floating-window
|
|
1964
|
+
id="localization-window"
|
|
1965
|
+
header="Translations"
|
|
1966
|
+
.width=${360}
|
|
1967
|
+
.maxHeight=${600}
|
|
1968
|
+
.top=${20}
|
|
1969
|
+
color="#6b7280"
|
|
1970
|
+
.hidden=${this.localizationWindowHidden}
|
|
1971
|
+
@temba-dialog-hidden=${this.handleLocalizationWindowClosed}
|
|
1972
|
+
>
|
|
1973
|
+
<div class="localization-window-content">
|
|
1974
|
+
<div class="localization-header">
|
|
1975
|
+
Translate from <strong>${baseName}</strong> to the languages below.
|
|
1976
|
+
Closing this window returns you to editing in ${baseName}.
|
|
1977
|
+
</div>
|
|
1978
|
+
<div class="localization-language-row">
|
|
1979
|
+
<temba-select
|
|
1980
|
+
flavor="small"
|
|
1981
|
+
class="localization-language-select"
|
|
1982
|
+
.values=${activeLanguage
|
|
1983
|
+
? [{ name: activeLanguage.name, value: activeLanguage.code }]
|
|
1984
|
+
: []}
|
|
1985
|
+
@change=${this.handleLocalizationLanguageSelectChange}
|
|
1986
|
+
>
|
|
1987
|
+
${languages.map((lang) => html `<temba-option
|
|
1988
|
+
value="${lang.code}"
|
|
1989
|
+
name="${lang.name}"
|
|
1990
|
+
></temba-option>`)}
|
|
1991
|
+
</temba-select>
|
|
1992
|
+
<button
|
|
1993
|
+
class="auto-translate-button"
|
|
1994
|
+
type="button"
|
|
1995
|
+
?disabled=${autoTranslateButtonDisabled}
|
|
1996
|
+
@click=${this.handleAutoTranslateClick}
|
|
1997
|
+
>
|
|
1998
|
+
${autoTranslateButtonLabel}
|
|
1999
|
+
</button>
|
|
2000
|
+
</div>
|
|
2001
|
+
<div class="localization-progress">
|
|
2002
|
+
<div class="localization-progress-summary">
|
|
2003
|
+
${this.autoTranslating
|
|
2004
|
+
? html `<temba-loading units="3" size="8"></temba-loading>
|
|
2005
|
+
<span>Auto translating remaining text…</span>`
|
|
2006
|
+
: !hasTranslations
|
|
2007
|
+
? html `<span>
|
|
2008
|
+
Add content or enable more options to start translating.
|
|
2009
|
+
</span>`
|
|
2010
|
+
: hasPendingTranslations
|
|
2011
|
+
? html `<span>
|
|
2012
|
+
${progress.localized} of ${progress.total} items translated
|
|
2013
|
+
</span>`
|
|
2014
|
+
: html `<span>All items are translated.</span>`}
|
|
2015
|
+
</div>
|
|
2016
|
+
${this.autoTranslateError
|
|
2017
|
+
? html `<div class="auto-translate-error">
|
|
2018
|
+
${this.autoTranslateError}
|
|
2019
|
+
</div>`
|
|
2020
|
+
: ''}
|
|
2021
|
+
<div class="localization-progress-bar-row">
|
|
2022
|
+
<div
|
|
2023
|
+
class="localization-progress-trigger"
|
|
2024
|
+
role="button"
|
|
2025
|
+
tabindex="0"
|
|
2026
|
+
aria-expanded="${this.translationSettingsExpanded}"
|
|
2027
|
+
aria-controls="${settingsPanelId}"
|
|
2028
|
+
@click=${this.handleLocalizationProgressToggleClick}
|
|
2029
|
+
@keydown=${this.handleLocalizationProgressToggleKeydown}
|
|
2030
|
+
>
|
|
2031
|
+
<temba-progress
|
|
2032
|
+
.current=${progress.localized}
|
|
2033
|
+
.total=${Math.max(progress.total, 1)}
|
|
2034
|
+
.animated=${false}
|
|
2035
|
+
></temba-progress>
|
|
2036
|
+
</div>
|
|
2037
|
+
<button
|
|
2038
|
+
class="translation-settings-toggle"
|
|
2039
|
+
type="button"
|
|
2040
|
+
@click=${this.toggleTranslationSettings}
|
|
2041
|
+
aria-expanded="${this.translationSettingsExpanded}"
|
|
2042
|
+
aria-controls="${settingsPanelId}"
|
|
2043
|
+
>
|
|
2044
|
+
<span
|
|
2045
|
+
class="translation-settings-arrow ${this
|
|
2046
|
+
.translationSettingsExpanded
|
|
2047
|
+
? 'expanded'
|
|
2048
|
+
: ''}"
|
|
2049
|
+
></span>
|
|
2050
|
+
</button>
|
|
2051
|
+
</div>
|
|
2052
|
+
${this.translationSettingsExpanded
|
|
2053
|
+
? html `<div id="${settingsPanelId}" class="translation-settings">
|
|
2054
|
+
<div class="translation-settings-row">
|
|
2055
|
+
<temba-checkbox
|
|
2056
|
+
name="include-categories"
|
|
2057
|
+
label="Include categories"
|
|
2058
|
+
?checked=${includeCategories}
|
|
2059
|
+
style="--checkbox-padding:5px; border-radius:var(--curvature);"
|
|
2060
|
+
@change=${this.handleIncludeCategoriesChange}
|
|
2061
|
+
></temba-checkbox>
|
|
2062
|
+
</div>
|
|
2063
|
+
</div>`
|
|
2064
|
+
: ''}
|
|
2065
|
+
</div>
|
|
2066
|
+
</div>
|
|
2067
|
+
</temba-floating-window>
|
|
2068
|
+
`;
|
|
2069
|
+
}
|
|
2070
|
+
renderAutoTranslateDialog() {
|
|
2071
|
+
if (!this.autoTranslateDialogOpen) {
|
|
2072
|
+
return html ``;
|
|
2073
|
+
}
|
|
2074
|
+
const selectedModel = this.autoTranslateModel
|
|
2075
|
+
? [this.autoTranslateModel]
|
|
2076
|
+
: [];
|
|
2077
|
+
const disableTranslate = !this.autoTranslateModel;
|
|
2078
|
+
return html `
|
|
2079
|
+
<temba-dialog
|
|
2080
|
+
header="Auto translate"
|
|
2081
|
+
.open=${this.autoTranslateDialogOpen}
|
|
2082
|
+
primaryButtonName="Translate"
|
|
2083
|
+
cancelButtonName="Cancel"
|
|
2084
|
+
size="small"
|
|
2085
|
+
.disabled=${disableTranslate}
|
|
2086
|
+
@temba-button-clicked=${this.handleAutoTranslateDialogButton}
|
|
2087
|
+
>
|
|
2088
|
+
<div class="auto-translate-dialog-content">
|
|
2089
|
+
<p>
|
|
2090
|
+
We'll send any untranslated text to the selected AI model and save
|
|
2091
|
+
the responses automatically.
|
|
2092
|
+
</p>
|
|
2093
|
+
<div class="auto-translate-models">
|
|
2094
|
+
<temba-select
|
|
2095
|
+
class="auto-translate-model-select"
|
|
2096
|
+
endpoint="${AUTO_TRANSLATE_MODELS_ENDPOINT}"
|
|
2097
|
+
.valueKey=${'uuid'}
|
|
2098
|
+
.values=${selectedModel}
|
|
2099
|
+
?searchable=${true}
|
|
2100
|
+
?clearable=${true}
|
|
2101
|
+
placeholder="Select an AI model"
|
|
2102
|
+
@change=${this.handleAutoTranslateModelChange}
|
|
2103
|
+
></temba-select>
|
|
2104
|
+
</div>
|
|
2105
|
+
<p>Only text without translations will be sent.</p>
|
|
2106
|
+
${this.autoTranslateError
|
|
2107
|
+
? html `<div class="auto-translate-error">
|
|
2108
|
+
${this.autoTranslateError}
|
|
2109
|
+
</div>`
|
|
2110
|
+
: ''}
|
|
2111
|
+
</div>
|
|
2112
|
+
</temba-dialog>
|
|
2113
|
+
`;
|
|
2114
|
+
}
|
|
2115
|
+
renderLocalizationTab() {
|
|
2116
|
+
const languages = this.getLocalizationLanguages();
|
|
2117
|
+
if (!languages.length) {
|
|
2118
|
+
return html ``;
|
|
2119
|
+
}
|
|
2120
|
+
return html `
|
|
2121
|
+
<temba-floating-tab
|
|
2122
|
+
id="localization-tab"
|
|
2123
|
+
icon="language"
|
|
2124
|
+
label="Translate Flow"
|
|
2125
|
+
color="#6b7280"
|
|
2126
|
+
.hidden=${!this.localizationWindowHidden}
|
|
2127
|
+
@temba-button-clicked=${this.handleLocalizationTabClick}
|
|
2128
|
+
></temba-floating-tab>
|
|
2129
|
+
`;
|
|
2130
|
+
}
|
|
838
2131
|
render() {
|
|
839
|
-
var
|
|
2132
|
+
var _b, _c;
|
|
840
2133
|
// we have to embed our own style since we are in light DOM
|
|
841
2134
|
const style = html `<style>
|
|
842
2135
|
${unsafeCSS(Editor.styles.cssText)}
|
|
843
2136
|
${unsafeCSS(CanvasNode.styles.cssText)}
|
|
844
2137
|
</style>`;
|
|
845
|
-
const stickies = ((
|
|
846
|
-
return html `${style}
|
|
2138
|
+
const stickies = ((_c = (_b = this.definition) === null || _b === void 0 ? void 0 : _b._ui) === null || _c === void 0 ? void 0 : _c.stickies) || {};
|
|
2139
|
+
return html `${style} ${this.renderLocalizationWindow()}
|
|
2140
|
+
${this.renderAutoTranslateDialog()}
|
|
847
2141
|
<div id="editor">
|
|
848
2142
|
<div
|
|
849
2143
|
id="grid"
|
|
@@ -853,13 +2147,13 @@ export class Editor extends RapidElement {
|
|
|
853
2147
|
<div id="canvas">
|
|
854
2148
|
${this.definition
|
|
855
2149
|
? repeat(this.definition.nodes, (node) => node.uuid, (node) => {
|
|
856
|
-
var
|
|
857
|
-
const position = ((
|
|
2150
|
+
var _b, _c, _d;
|
|
2151
|
+
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
2152
|
left: 0,
|
|
859
2153
|
top: 0
|
|
860
2154
|
};
|
|
861
2155
|
const dragging = this.isDragging &&
|
|
862
|
-
((
|
|
2156
|
+
((_d = this.currentDragItem) === null || _d === void 0 ? void 0 : _d.uuid) === node.uuid;
|
|
863
2157
|
const selected = this.selectedItems.has(node.uuid);
|
|
864
2158
|
return html `<temba-flow-node
|
|
865
2159
|
class="draggable ${dragging ? 'dragging' : ''} ${selected
|
|
@@ -867,6 +2161,7 @@ export class Editor extends RapidElement {
|
|
|
867
2161
|
: ''}"
|
|
868
2162
|
@mousedown=${this.handleMouseDown.bind(this)}
|
|
869
2163
|
uuid=${node.uuid}
|
|
2164
|
+
data-node-uuid=${node.uuid}
|
|
870
2165
|
style="left:${position.left}px; top:${position.top}px"
|
|
871
2166
|
.plumber=${this.plumber}
|
|
872
2167
|
.node=${node}
|
|
@@ -878,9 +2173,9 @@ export class Editor extends RapidElement {
|
|
|
878
2173
|
})
|
|
879
2174
|
: html `<temba-loading></temba-loading>`}
|
|
880
2175
|
${repeat(Object.entries(stickies), ([uuid]) => uuid, ([uuid, sticky]) => {
|
|
881
|
-
var
|
|
2176
|
+
var _b;
|
|
882
2177
|
const position = sticky.position || { left: 0, top: 0 };
|
|
883
|
-
const dragging = this.isDragging && ((
|
|
2178
|
+
const dragging = this.isDragging && ((_b = this.currentDragItem) === null || _b === void 0 ? void 0 : _b.uuid) === uuid;
|
|
884
2179
|
const selected = this.selectedItems.has(uuid);
|
|
885
2180
|
return html `<temba-sticky-note
|
|
886
2181
|
class="draggable ${dragging ? 'dragging' : ''} ${selected
|
|
@@ -895,7 +2190,7 @@ export class Editor extends RapidElement {
|
|
|
895
2190
|
.selected=${selected}
|
|
896
2191
|
></temba-sticky-note>`;
|
|
897
2192
|
})}
|
|
898
|
-
${this.renderSelectionBox()}
|
|
2193
|
+
${this.renderSelectionBox()} ${this.renderCanvasDropPreview()}
|
|
899
2194
|
</div>
|
|
900
2195
|
</div>
|
|
901
2196
|
</div>
|
|
@@ -905,11 +2200,18 @@ export class Editor extends RapidElement {
|
|
|
905
2200
|
.node=${this.editingNode}
|
|
906
2201
|
.nodeUI=${this.editingNodeUI}
|
|
907
2202
|
.action=${this.editingAction}
|
|
908
|
-
@temba-node-saved=${(e) => this.handleNodeSaved(e.detail.node)}
|
|
2203
|
+
@temba-node-saved=${(e) => this.handleNodeSaved(e.detail.node, e.detail.uiConfig)}
|
|
909
2204
|
@temba-action-saved=${(e) => this.handleActionSaved(e.detail.action)}
|
|
910
2205
|
@temba-node-edit-cancelled=${this.handleNodeEditCanceled}
|
|
911
2206
|
></temba-node-editor>`
|
|
912
|
-
: ''}
|
|
2207
|
+
: ''}
|
|
2208
|
+
|
|
2209
|
+
<temba-canvas-menu></temba-canvas-menu>
|
|
2210
|
+
<temba-node-type-selector
|
|
2211
|
+
.flowType=${this.flowType}
|
|
2212
|
+
.features=${this.features}
|
|
2213
|
+
></temba-node-type-selector>
|
|
2214
|
+
${this.renderLocalizationTab()} `;
|
|
913
2215
|
}
|
|
914
2216
|
}
|
|
915
2217
|
__decorate([
|
|
@@ -918,6 +2220,12 @@ __decorate([
|
|
|
918
2220
|
__decorate([
|
|
919
2221
|
property({ type: String })
|
|
920
2222
|
], Editor.prototype, "version", void 0);
|
|
2223
|
+
__decorate([
|
|
2224
|
+
property({ type: String })
|
|
2225
|
+
], Editor.prototype, "flowType", void 0);
|
|
2226
|
+
__decorate([
|
|
2227
|
+
property({ type: Array })
|
|
2228
|
+
], Editor.prototype, "features", void 0);
|
|
921
2229
|
__decorate([
|
|
922
2230
|
fromStore(zustand, (state) => state.flowDefinition)
|
|
923
2231
|
], Editor.prototype, "definition", void 0);
|
|
@@ -927,6 +2235,12 @@ __decorate([
|
|
|
927
2235
|
__decorate([
|
|
928
2236
|
fromStore(zustand, (state) => state.dirtyDate)
|
|
929
2237
|
], Editor.prototype, "dirtyDate", void 0);
|
|
2238
|
+
__decorate([
|
|
2239
|
+
fromStore(zustand, (state) => state.languageCode)
|
|
2240
|
+
], Editor.prototype, "languageCode", void 0);
|
|
2241
|
+
__decorate([
|
|
2242
|
+
fromStore(zustand, (state) => state.isTranslating)
|
|
2243
|
+
], Editor.prototype, "isTranslating", void 0);
|
|
930
2244
|
__decorate([
|
|
931
2245
|
state()
|
|
932
2246
|
], Editor.prototype, "isDragging", void 0);
|
|
@@ -954,6 +2268,27 @@ __decorate([
|
|
|
954
2268
|
__decorate([
|
|
955
2269
|
state()
|
|
956
2270
|
], Editor.prototype, "isValidTarget", void 0);
|
|
2271
|
+
__decorate([
|
|
2272
|
+
state()
|
|
2273
|
+
], Editor.prototype, "localizationWindowHidden", void 0);
|
|
2274
|
+
__decorate([
|
|
2275
|
+
state()
|
|
2276
|
+
], Editor.prototype, "translationFilters", void 0);
|
|
2277
|
+
__decorate([
|
|
2278
|
+
state()
|
|
2279
|
+
], Editor.prototype, "translationSettingsExpanded", void 0);
|
|
2280
|
+
__decorate([
|
|
2281
|
+
state()
|
|
2282
|
+
], Editor.prototype, "autoTranslateDialogOpen", void 0);
|
|
2283
|
+
__decorate([
|
|
2284
|
+
state()
|
|
2285
|
+
], Editor.prototype, "autoTranslating", void 0);
|
|
2286
|
+
__decorate([
|
|
2287
|
+
state()
|
|
2288
|
+
], Editor.prototype, "autoTranslateModel", void 0);
|
|
2289
|
+
__decorate([
|
|
2290
|
+
state()
|
|
2291
|
+
], Editor.prototype, "autoTranslateError", void 0);
|
|
957
2292
|
__decorate([
|
|
958
2293
|
state()
|
|
959
2294
|
], Editor.prototype, "editingNode", void 0);
|
|
@@ -963,4 +2298,19 @@ __decorate([
|
|
|
963
2298
|
__decorate([
|
|
964
2299
|
state()
|
|
965
2300
|
], Editor.prototype, "editingAction", void 0);
|
|
2301
|
+
__decorate([
|
|
2302
|
+
state()
|
|
2303
|
+
], Editor.prototype, "isCreatingNewNode", void 0);
|
|
2304
|
+
__decorate([
|
|
2305
|
+
state()
|
|
2306
|
+
], Editor.prototype, "pendingNodePosition", void 0);
|
|
2307
|
+
__decorate([
|
|
2308
|
+
state()
|
|
2309
|
+
], Editor.prototype, "canvasDropPreview", void 0);
|
|
2310
|
+
__decorate([
|
|
2311
|
+
state()
|
|
2312
|
+
], Editor.prototype, "addActionToNodeUuid", void 0);
|
|
2313
|
+
__decorate([
|
|
2314
|
+
state()
|
|
2315
|
+
], Editor.prototype, "actionDragTargetNodeUuid", void 0);
|
|
966
2316
|
//# sourceMappingURL=Editor.js.map
|