@nyaruka/temba-components 0.139.0 → 0.141.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (385) hide show
  1. package/.github/workflows/cla.yml +1 -1
  2. package/.github/workflows/copilot-setup-steps.yml +6 -1
  3. package/.lintstagedrc.js +10 -0
  4. package/CHANGELOG.md +32 -0
  5. package/demo/data/flows/sample-flow.json +24 -0
  6. package/dist/locales/es.js +5 -5
  7. package/dist/locales/es.js.map +1 -1
  8. package/dist/locales/fr.js +5 -5
  9. package/dist/locales/fr.js.map +1 -1
  10. package/dist/locales/locale-codes.js +11 -2
  11. package/dist/locales/locale-codes.js.map +1 -1
  12. package/dist/locales/pt.js +5 -5
  13. package/dist/locales/pt.js.map +1 -1
  14. package/dist/temba-components.js +702 -338
  15. package/dist/temba-components.js.map +1 -1
  16. package/out-tsc/src/display/Chat.js +10 -7
  17. package/out-tsc/src/display/Chat.js.map +1 -1
  18. package/out-tsc/src/display/Dropdown.js +3 -1
  19. package/out-tsc/src/display/Dropdown.js.map +1 -1
  20. package/out-tsc/src/display/FloatingTab.js +4 -4
  21. package/out-tsc/src/display/FloatingTab.js.map +1 -1
  22. package/out-tsc/src/display/Thumbnail.js +163 -5
  23. package/out-tsc/src/display/Thumbnail.js.map +1 -1
  24. package/out-tsc/src/flow/CanvasNode.js +65 -23
  25. package/out-tsc/src/flow/CanvasNode.js.map +1 -1
  26. package/out-tsc/src/flow/Editor.js +369 -49
  27. package/out-tsc/src/flow/Editor.js.map +1 -1
  28. package/out-tsc/src/flow/NodeEditor.js +118 -10
  29. package/out-tsc/src/flow/NodeEditor.js.map +1 -1
  30. package/out-tsc/src/flow/Plumber.js +61 -14
  31. package/out-tsc/src/flow/Plumber.js.map +1 -1
  32. package/out-tsc/src/flow/StickyNote.js +13 -4
  33. package/out-tsc/src/flow/StickyNote.js.map +1 -1
  34. package/out-tsc/src/flow/actions/add_contact_groups.js +4 -1
  35. package/out-tsc/src/flow/actions/add_contact_groups.js.map +1 -1
  36. package/out-tsc/src/flow/actions/add_input_labels.js +4 -1
  37. package/out-tsc/src/flow/actions/add_input_labels.js.map +1 -1
  38. package/out-tsc/src/flow/actions/audio-player.js +112 -0
  39. package/out-tsc/src/flow/actions/audio-player.js.map +1 -0
  40. package/out-tsc/src/flow/actions/enter_flow.js +43 -0
  41. package/out-tsc/src/flow/actions/enter_flow.js.map +1 -0
  42. package/out-tsc/src/flow/actions/play_audio.js +57 -4
  43. package/out-tsc/src/flow/actions/play_audio.js.map +1 -1
  44. package/out-tsc/src/flow/actions/remove_contact_groups.js +6 -1
  45. package/out-tsc/src/flow/actions/remove_contact_groups.js.map +1 -1
  46. package/out-tsc/src/flow/actions/say_msg.js +86 -3
  47. package/out-tsc/src/flow/actions/say_msg.js.map +1 -1
  48. package/out-tsc/src/flow/actions/send_broadcast.js +6 -2
  49. package/out-tsc/src/flow/actions/send_broadcast.js.map +1 -1
  50. package/out-tsc/src/flow/actions/set_contact_channel.js +13 -0
  51. package/out-tsc/src/flow/actions/set_contact_channel.js.map +1 -1
  52. package/out-tsc/src/flow/actions/set_contact_status.js +7 -5
  53. package/out-tsc/src/flow/actions/set_contact_status.js.map +1 -1
  54. package/out-tsc/src/flow/actions/start_session.js +10 -3
  55. package/out-tsc/src/flow/actions/start_session.js.map +1 -1
  56. package/out-tsc/src/flow/config.js +11 -3
  57. package/out-tsc/src/flow/config.js.map +1 -1
  58. package/out-tsc/src/flow/nodes/shared-rules.js +1 -1
  59. package/out-tsc/src/flow/nodes/shared-rules.js.map +1 -1
  60. package/out-tsc/src/flow/nodes/split_by_contact_field.js +18 -5
  61. package/out-tsc/src/flow/nodes/split_by_contact_field.js.map +1 -1
  62. package/out-tsc/src/flow/nodes/split_by_expression.js +1 -1
  63. package/out-tsc/src/flow/nodes/split_by_expression.js.map +1 -1
  64. package/out-tsc/src/flow/nodes/split_by_llm_categorize.js +0 -1
  65. package/out-tsc/src/flow/nodes/split_by_llm_categorize.js.map +1 -1
  66. package/out-tsc/src/flow/nodes/split_by_random.js +0 -1
  67. package/out-tsc/src/flow/nodes/split_by_random.js.map +1 -1
  68. package/out-tsc/src/flow/nodes/split_by_run_result.js +10 -4
  69. package/out-tsc/src/flow/nodes/split_by_run_result.js.map +1 -1
  70. package/out-tsc/src/flow/nodes/terminal.js +7 -0
  71. package/out-tsc/src/flow/nodes/terminal.js.map +1 -0
  72. package/out-tsc/src/flow/nodes/wait_for_audio.js +77 -0
  73. package/out-tsc/src/flow/nodes/wait_for_audio.js.map +1 -0
  74. package/out-tsc/src/flow/nodes/wait_for_dial.js +151 -0
  75. package/out-tsc/src/flow/nodes/wait_for_dial.js.map +1 -0
  76. package/out-tsc/src/flow/nodes/wait_for_digits.js +61 -1
  77. package/out-tsc/src/flow/nodes/wait_for_digits.js.map +1 -1
  78. package/out-tsc/src/flow/nodes/wait_for_menu.js +173 -2
  79. package/out-tsc/src/flow/nodes/wait_for_menu.js.map +1 -1
  80. package/out-tsc/src/flow/nodes/wait_for_response.js +1 -1
  81. package/out-tsc/src/flow/nodes/wait_for_response.js.map +1 -1
  82. package/out-tsc/src/flow/operators.js +21 -5
  83. package/out-tsc/src/flow/operators.js.map +1 -1
  84. package/out-tsc/src/flow/types.js.map +1 -1
  85. package/out-tsc/src/flow/utils.js +79 -3
  86. package/out-tsc/src/flow/utils.js.map +1 -1
  87. package/out-tsc/src/form/ArrayEditor.js +4 -2
  88. package/out-tsc/src/form/ArrayEditor.js.map +1 -1
  89. package/out-tsc/src/form/FieldRenderer.js +56 -0
  90. package/out-tsc/src/form/FieldRenderer.js.map +1 -1
  91. package/out-tsc/src/interfaces.js +1 -0
  92. package/out-tsc/src/interfaces.js.map +1 -1
  93. package/out-tsc/src/layout/Dialog.js +51 -7
  94. package/out-tsc/src/layout/Dialog.js.map +1 -1
  95. package/out-tsc/src/layout/Modax.js +20 -2
  96. package/out-tsc/src/layout/Modax.js.map +1 -1
  97. package/out-tsc/src/list/ContentMenu.js +14 -1
  98. package/out-tsc/src/list/ContentMenu.js.map +1 -1
  99. package/out-tsc/src/locales/es.js +5 -5
  100. package/out-tsc/src/locales/es.js.map +1 -1
  101. package/out-tsc/src/locales/fr.js +5 -5
  102. package/out-tsc/src/locales/fr.js.map +1 -1
  103. package/out-tsc/src/locales/locale-codes.js +11 -2
  104. package/out-tsc/src/locales/locale-codes.js.map +1 -1
  105. package/out-tsc/src/locales/pt.js +5 -5
  106. package/out-tsc/src/locales/pt.js.map +1 -1
  107. package/out-tsc/src/simulator/Simulator.js +21 -4
  108. package/out-tsc/src/simulator/Simulator.js.map +1 -1
  109. package/out-tsc/src/store/AppState.js +102 -3
  110. package/out-tsc/src/store/AppState.js.map +1 -1
  111. package/out-tsc/test/actions/add_contact_groups.test.js +35 -0
  112. package/out-tsc/test/actions/add_contact_groups.test.js.map +1 -1
  113. package/out-tsc/test/actions/add_input_labels.test.js +53 -0
  114. package/out-tsc/test/actions/add_input_labels.test.js.map +1 -0
  115. package/out-tsc/test/actions/enter_flow.test.js +71 -0
  116. package/out-tsc/test/actions/enter_flow.test.js.map +1 -0
  117. package/out-tsc/test/actions/play_audio.test.js +118 -0
  118. package/out-tsc/test/actions/play_audio.test.js.map +1 -0
  119. package/out-tsc/test/actions/remove_contact_groups.test.js +24 -0
  120. package/out-tsc/test/actions/remove_contact_groups.test.js.map +1 -1
  121. package/out-tsc/test/actions/say_msg.test.js +158 -0
  122. package/out-tsc/test/actions/say_msg.test.js.map +1 -0
  123. package/out-tsc/test/actions/send_broadcast.test.js +41 -0
  124. package/out-tsc/test/actions/send_broadcast.test.js.map +1 -1
  125. package/out-tsc/test/actions/set_contact_channel.test.js +67 -0
  126. package/out-tsc/test/actions/set_contact_channel.test.js.map +1 -0
  127. package/out-tsc/test/actions/set_contact_field.test.js +52 -0
  128. package/out-tsc/test/actions/set_contact_field.test.js.map +1 -0
  129. package/out-tsc/test/actions/set_contact_language.test.js +39 -0
  130. package/out-tsc/test/actions/set_contact_language.test.js.map +1 -0
  131. package/out-tsc/test/actions/set_contact_name.test.js +28 -0
  132. package/out-tsc/test/actions/set_contact_name.test.js.map +1 -0
  133. package/out-tsc/test/actions/set_contact_status.test.js +44 -0
  134. package/out-tsc/test/actions/set_contact_status.test.js.map +1 -0
  135. package/out-tsc/test/actions/set_run_result.test.js +47 -0
  136. package/out-tsc/test/actions/set_run_result.test.js.map +1 -0
  137. package/out-tsc/test/actions/start_session.test.js +76 -0
  138. package/out-tsc/test/actions/start_session.test.js.map +1 -1
  139. package/out-tsc/test/nodes/split_by_contact_field.test.js +50 -0
  140. package/out-tsc/test/nodes/split_by_contact_field.test.js.map +1 -1
  141. package/out-tsc/test/nodes/split_by_run_result.test.js +82 -0
  142. package/out-tsc/test/nodes/split_by_run_result.test.js.map +1 -1
  143. package/out-tsc/test/nodes/split_by_ticket.test.js +139 -0
  144. package/out-tsc/test/nodes/split_by_ticket.test.js.map +1 -0
  145. package/out-tsc/test/nodes/split_by_webhook.test.js +111 -0
  146. package/out-tsc/test/nodes/split_by_webhook.test.js.map +1 -0
  147. package/out-tsc/test/nodes/wait_for_audio.test.js +156 -0
  148. package/out-tsc/test/nodes/wait_for_audio.test.js.map +1 -0
  149. package/out-tsc/test/nodes/wait_for_dial.test.js +336 -0
  150. package/out-tsc/test/nodes/wait_for_dial.test.js.map +1 -0
  151. package/out-tsc/test/nodes/wait_for_digits.test.js +198 -84
  152. package/out-tsc/test/nodes/wait_for_digits.test.js.map +1 -1
  153. package/out-tsc/test/nodes/wait_for_menu.test.js +340 -0
  154. package/out-tsc/test/nodes/wait_for_menu.test.js.map +1 -0
  155. package/out-tsc/test/temba-flow-collision.test.js +261 -6
  156. package/out-tsc/test/temba-flow-collision.test.js.map +1 -1
  157. package/out-tsc/test/temba-flow-editor.test.js +187 -0
  158. package/out-tsc/test/temba-flow-editor.test.js.map +1 -1
  159. package/out-tsc/test/temba-flow-plumber.test.js +19 -0
  160. package/out-tsc/test/temba-flow-plumber.test.js.map +1 -1
  161. package/out-tsc/test/temba-node-type-selector.test.js +6 -6
  162. package/out-tsc/test/temba-node-type-selector.test.js.map +1 -1
  163. package/out-tsc/test/temba-select.test.js +4 -1
  164. package/out-tsc/test/temba-select.test.js.map +1 -1
  165. package/out-tsc/test/utils.test.js +4 -2
  166. package/out-tsc/test/utils.test.js.map +1 -1
  167. package/package.json +3 -9
  168. package/screenshots/truth/actions/add_contact_groups/render/descriptive-group-names.png +0 -0
  169. package/screenshots/truth/actions/add_contact_groups/render/long-group-names.png +0 -0
  170. package/screenshots/truth/actions/add_contact_groups/render/many-groups.png +0 -0
  171. package/screenshots/truth/actions/add_contact_groups/render/multiple-groups.png +0 -0
  172. package/screenshots/truth/actions/add_contact_groups/render/single-group.png +0 -0
  173. package/screenshots/truth/actions/add_contact_urn/render/expression-facebook.png +0 -0
  174. package/screenshots/truth/actions/add_contact_urn/render/expression-phone.png +0 -0
  175. package/screenshots/truth/actions/add_contact_urn/render/facebook-id.png +0 -0
  176. package/screenshots/truth/actions/add_contact_urn/render/instagram-handle.png +0 -0
  177. package/screenshots/truth/actions/add_contact_urn/render/line-id.png +0 -0
  178. package/screenshots/truth/actions/add_contact_urn/render/phone-number.png +0 -0
  179. package/screenshots/truth/actions/add_contact_urn/render/telegram-id.png +0 -0
  180. package/screenshots/truth/actions/add_contact_urn/render/viber-id.png +0 -0
  181. package/screenshots/truth/actions/add_contact_urn/render/wechat-id.png +0 -0
  182. package/screenshots/truth/actions/add_contact_urn/render/whatsapp.png +0 -0
  183. package/screenshots/truth/actions/add_input_labels/editor/multiple-labels.png +0 -0
  184. package/screenshots/truth/actions/add_input_labels/editor/single-label.png +0 -0
  185. package/screenshots/truth/actions/add_input_labels/render/multiple-labels.png +0 -0
  186. package/screenshots/truth/actions/add_input_labels/render/single-label.png +0 -0
  187. package/screenshots/truth/actions/enter_flow/editor/basic-flow.png +0 -0
  188. package/screenshots/truth/actions/enter_flow/editor/long-flow-name.png +0 -0
  189. package/screenshots/truth/actions/enter_flow/render/basic-flow.png +0 -0
  190. package/screenshots/truth/actions/enter_flow/render/long-flow-name.png +0 -0
  191. package/screenshots/truth/actions/play_audio/editor/expression-url.png +0 -0
  192. package/screenshots/truth/actions/play_audio/editor/static-url.png +0 -0
  193. package/screenshots/truth/actions/play_audio/render/expression-url.png +0 -0
  194. package/screenshots/truth/actions/play_audio/render/static-url.png +0 -0
  195. package/screenshots/truth/actions/remove_contact_groups/render/cleanup-groups.png +0 -0
  196. package/screenshots/truth/actions/remove_contact_groups/render/long-descriptive-group-names.png +0 -0
  197. package/screenshots/truth/actions/remove_contact_groups/render/many-groups.png +0 -0
  198. package/screenshots/truth/actions/remove_contact_groups/render/multiple-groups.png +0 -0
  199. package/screenshots/truth/actions/remove_contact_groups/render/remove-from-all-groups.png +0 -0
  200. package/screenshots/truth/actions/remove_contact_groups/render/single-group.png +0 -0
  201. package/screenshots/truth/actions/say_msg/editor/multiline-text.png +0 -0
  202. package/screenshots/truth/actions/say_msg/editor/simple-text.png +0 -0
  203. package/screenshots/truth/actions/say_msg/editor/text-with-audio-url.png +0 -0
  204. package/screenshots/truth/actions/say_msg/render/multiline-text.png +0 -0
  205. package/screenshots/truth/actions/say_msg/render/simple-text.png +0 -0
  206. package/screenshots/truth/actions/say_msg/render/text-with-audio-url.png +0 -0
  207. package/screenshots/truth/actions/send_broadcast/render/contacts-only.png +0 -0
  208. package/screenshots/truth/actions/send_broadcast/render/groups-and-contacts.png +0 -0
  209. package/screenshots/truth/actions/send_broadcast/render/groups-only.png +0 -0
  210. package/screenshots/truth/actions/send_broadcast/render/many-groups.png +0 -0
  211. package/screenshots/truth/actions/send_broadcast/render/multiline-text.png +0 -0
  212. package/screenshots/truth/actions/send_email/render/complex-business-email.png +0 -0
  213. package/screenshots/truth/actions/send_email/render/empty-body.png +0 -0
  214. package/screenshots/truth/actions/send_email/render/empty-subject.png +0 -0
  215. package/screenshots/truth/actions/send_email/render/long-subject.png +0 -0
  216. package/screenshots/truth/actions/send_email/render/multiline-body.png +0 -0
  217. package/screenshots/truth/actions/send_email/render/multiple-recipients.png +0 -0
  218. package/screenshots/truth/actions/send_email/render/simple-email.png +0 -0
  219. package/screenshots/truth/actions/send_email/render/with-expressions.png +0 -0
  220. package/screenshots/truth/actions/send_msg/render/long-quick-replies.png +0 -0
  221. package/screenshots/truth/actions/send_msg/render/multiline-text-with-replies.png +0 -0
  222. package/screenshots/truth/actions/send_msg/render/simple-text.png +0 -0
  223. package/screenshots/truth/actions/send_msg/render/text-with-linebreaks.png +0 -0
  224. package/screenshots/truth/actions/send_msg/render/text-with-many-quick-replies.png +0 -0
  225. package/screenshots/truth/actions/send_msg/render/text-with-quick-replies.png +0 -0
  226. package/screenshots/truth/actions/send_msg/render/text-without-quick-replies.png +0 -0
  227. package/screenshots/truth/actions/set_contact_channel/editor/sms-channel.png +0 -0
  228. package/screenshots/truth/actions/set_contact_channel/editor/whatsapp-channel.png +0 -0
  229. package/screenshots/truth/actions/set_contact_channel/render/sms-channel.png +0 -0
  230. package/screenshots/truth/actions/set_contact_channel/render/whatsapp-channel.png +0 -0
  231. package/screenshots/truth/actions/set_contact_field/editor/clear-value.png +0 -0
  232. package/screenshots/truth/actions/set_contact_field/editor/set-value.png +0 -0
  233. package/screenshots/truth/actions/set_contact_field/render/clear-value.png +0 -0
  234. package/screenshots/truth/actions/set_contact_field/render/set-value.png +0 -0
  235. package/screenshots/truth/actions/set_contact_language/editor/english.png +0 -0
  236. package/screenshots/truth/actions/set_contact_language/editor/french.png +0 -0
  237. package/screenshots/truth/actions/set_contact_language/render/english.png +0 -0
  238. package/screenshots/truth/actions/set_contact_language/render/french.png +0 -0
  239. package/screenshots/truth/actions/set_contact_name/editor/expression-name.png +0 -0
  240. package/screenshots/truth/actions/set_contact_name/editor/static-name.png +0 -0
  241. package/screenshots/truth/actions/set_contact_name/render/expression-name.png +0 -0
  242. package/screenshots/truth/actions/set_contact_name/render/static-name.png +0 -0
  243. package/screenshots/truth/actions/set_contact_status/editor/active.png +0 -0
  244. package/screenshots/truth/actions/set_contact_status/editor/archived.png +0 -0
  245. package/screenshots/truth/actions/set_contact_status/editor/blocked.png +0 -0
  246. package/screenshots/truth/actions/set_contact_status/render/active.png +0 -0
  247. package/screenshots/truth/actions/set_contact_status/render/archived.png +0 -0
  248. package/screenshots/truth/actions/set_contact_status/render/blocked.png +0 -0
  249. package/screenshots/truth/actions/set_run_result/editor/expression-value.png +0 -0
  250. package/screenshots/truth/actions/set_run_result/editor/with-category.png +0 -0
  251. package/screenshots/truth/actions/set_run_result/render/expression-value.png +0 -0
  252. package/screenshots/truth/actions/set_run_result/render/with-category.png +0 -0
  253. package/screenshots/truth/actions/start_session/render/contact-query.png +0 -0
  254. package/screenshots/truth/actions/start_session/render/contacts-only.png +0 -0
  255. package/screenshots/truth/actions/start_session/render/create-contact.png +0 -0
  256. package/screenshots/truth/actions/start_session/render/groups-and-contacts.png +0 -0
  257. package/screenshots/truth/actions/start_session/render/groups-only.png +0 -0
  258. package/screenshots/truth/actions/start_session/render/many-recipients.png +0 -0
  259. package/screenshots/truth/editor/router.png +0 -0
  260. package/screenshots/truth/editor/wait.png +0 -0
  261. package/screenshots/truth/nodes/split_by_llm/render/information-extraction.png +0 -0
  262. package/screenshots/truth/nodes/split_by_llm/render/sentiment-analysis.png +0 -0
  263. package/screenshots/truth/nodes/split_by_llm/render/summarization.png +0 -0
  264. package/screenshots/truth/nodes/split_by_llm/render/translation-task.png +0 -0
  265. package/screenshots/truth/nodes/split_by_llm_categorize/editor/basic-categorization.png +0 -0
  266. package/screenshots/truth/nodes/split_by_llm_categorize/editor/custom-input-and-result-name.png +0 -0
  267. package/screenshots/truth/nodes/split_by_llm_categorize/editor/feedback-categorization.png +0 -0
  268. package/screenshots/truth/nodes/split_by_llm_categorize/editor/many-categories.png +0 -0
  269. package/screenshots/truth/nodes/split_by_llm_categorize/editor/minimal-categories.png +0 -0
  270. package/screenshots/truth/nodes/split_by_llm_categorize/render/basic-categorization.png +0 -0
  271. package/screenshots/truth/nodes/split_by_llm_categorize/render/custom-input-and-result-name.png +0 -0
  272. package/screenshots/truth/nodes/split_by_llm_categorize/render/feedback-categorization.png +0 -0
  273. package/screenshots/truth/nodes/split_by_llm_categorize/render/many-categories.png +0 -0
  274. package/screenshots/truth/nodes/split_by_llm_categorize/render/minimal-categories.png +0 -0
  275. package/screenshots/truth/nodes/split_by_random/editor/ab-test-multiple-variants.png +0 -0
  276. package/screenshots/truth/nodes/split_by_random/editor/sampling-split.png +0 -0
  277. package/screenshots/truth/nodes/split_by_random/editor/three-way-split.png +0 -0
  278. package/screenshots/truth/nodes/split_by_random/editor/two-bucket-split.png +0 -0
  279. package/screenshots/truth/nodes/split_by_random/render/ab-test-multiple-variants.png +0 -0
  280. package/screenshots/truth/nodes/split_by_random/render/sampling-split.png +0 -0
  281. package/screenshots/truth/nodes/split_by_random/render/three-way-split.png +0 -0
  282. package/screenshots/truth/nodes/split_by_random/render/two-bucket-split.png +0 -0
  283. package/screenshots/truth/nodes/wait_for_audio/editor/basic-audio-wait.png +0 -0
  284. package/screenshots/truth/nodes/wait_for_audio/render/basic-audio-wait.png +0 -0
  285. package/screenshots/truth/nodes/wait_for_dial/editor/basic-dial.png +0 -0
  286. package/screenshots/truth/nodes/wait_for_dial/editor/dial-with-limits.png +0 -0
  287. package/screenshots/truth/nodes/wait_for_dial/render/basic-dial.png +0 -0
  288. package/screenshots/truth/nodes/wait_for_dial/render/dial-with-limits.png +0 -0
  289. package/screenshots/truth/nodes/wait_for_digits/editor/basic-digits-wait.png +0 -0
  290. package/screenshots/truth/nodes/wait_for_digits/editor/digits-with-rules.png +0 -0
  291. package/screenshots/truth/nodes/wait_for_digits/render/basic-digits-wait.png +0 -0
  292. package/screenshots/truth/nodes/wait_for_digits/render/digits-with-rules.png +0 -0
  293. package/screenshots/truth/nodes/wait_for_menu/editor/menu-with-digits.png +0 -0
  294. package/screenshots/truth/nodes/wait_for_menu/render/menu-with-digits.png +0 -0
  295. package/screenshots/truth/nodes/wait_for_response/editor/basic-wait.png +0 -0
  296. package/screenshots/truth/nodes/wait_for_response/editor/custom-result-name.png +0 -0
  297. package/screenshots/truth/nodes/wait_for_response/editor/no-timeout.png +0 -0
  298. package/screenshots/truth/nodes/wait_for_response/editor/short-timeout.png +0 -0
  299. package/screenshots/truth/nodes/wait_for_response/render/basic-wait.png +0 -0
  300. package/screenshots/truth/nodes/wait_for_response/render/custom-result-name.png +0 -0
  301. package/screenshots/truth/nodes/wait_for_response/render/no-timeout.png +0 -0
  302. package/screenshots/truth/nodes/wait_for_response/render/short-timeout.png +0 -0
  303. package/src/display/Chat.ts +13 -7
  304. package/src/display/Dropdown.ts +3 -1
  305. package/src/display/FloatingTab.ts +4 -4
  306. package/src/display/Thumbnail.ts +162 -2
  307. package/src/flow/CanvasNode.ts +70 -24
  308. package/src/flow/Editor.ts +440 -99
  309. package/src/flow/NodeEditor.ts +137 -9
  310. package/src/flow/Plumber.ts +89 -14
  311. package/src/flow/StickyNote.ts +14 -4
  312. package/src/flow/actions/add_contact_groups.ts +4 -1
  313. package/src/flow/actions/add_input_labels.ts +4 -1
  314. package/src/flow/actions/audio-player.ts +127 -0
  315. package/src/flow/actions/enter_flow.ts +44 -0
  316. package/src/flow/actions/play_audio.ts +64 -5
  317. package/src/flow/actions/remove_contact_groups.ts +6 -1
  318. package/src/flow/actions/say_msg.ts +94 -4
  319. package/src/flow/actions/send_broadcast.ts +6 -2
  320. package/src/flow/actions/set_contact_channel.ts +13 -1
  321. package/src/flow/actions/set_contact_status.ts +7 -5
  322. package/src/flow/actions/start_session.ts +10 -3
  323. package/src/flow/config.ts +11 -3
  324. package/src/flow/nodes/shared-rules.ts +1 -1
  325. package/src/flow/nodes/split_by_contact_field.ts +16 -5
  326. package/src/flow/nodes/split_by_expression.ts +1 -1
  327. package/src/flow/nodes/split_by_llm_categorize.ts +0 -1
  328. package/src/flow/nodes/split_by_random.ts +0 -1
  329. package/src/flow/nodes/split_by_run_result.ts +10 -4
  330. package/src/flow/nodes/terminal.ts +9 -0
  331. package/src/flow/nodes/wait_for_audio.ts +88 -0
  332. package/src/flow/nodes/wait_for_dial.ts +176 -0
  333. package/src/flow/nodes/wait_for_digits.ts +87 -2
  334. package/src/flow/nodes/wait_for_menu.ts +209 -3
  335. package/src/flow/nodes/wait_for_response.ts +1 -1
  336. package/src/flow/operators.ts +23 -5
  337. package/src/flow/types.ts +23 -1
  338. package/src/flow/utils.ts +82 -3
  339. package/src/form/ArrayEditor.ts +4 -2
  340. package/src/form/FieldRenderer.ts +71 -1
  341. package/src/interfaces.ts +2 -1
  342. package/src/layout/Dialog.ts +52 -7
  343. package/src/layout/Modax.ts +19 -2
  344. package/src/list/ContentMenu.ts +15 -1
  345. package/src/locales/es.ts +18 -13
  346. package/src/locales/fr.ts +18 -13
  347. package/src/locales/locale-codes.ts +11 -2
  348. package/src/locales/pt.ts +18 -13
  349. package/src/simulator/Simulator.ts +25 -4
  350. package/src/store/AppState.ts +120 -1
  351. package/src/store/flow-definition.d.ts +2 -0
  352. package/test/actions/add_contact_groups.test.ts +38 -0
  353. package/test/actions/add_input_labels.test.ts +67 -0
  354. package/test/actions/enter_flow.test.ts +88 -0
  355. package/test/actions/play_audio.test.ts +155 -0
  356. package/test/actions/remove_contact_groups.test.ts +29 -0
  357. package/test/actions/say_msg.test.ts +196 -0
  358. package/test/actions/send_broadcast.test.ts +44 -0
  359. package/test/actions/set_contact_channel.test.ts +88 -0
  360. package/test/actions/set_contact_field.test.ts +68 -0
  361. package/test/actions/set_contact_language.test.ts +55 -0
  362. package/test/actions/set_contact_name.test.ts +39 -0
  363. package/test/actions/set_contact_status.test.ts +64 -0
  364. package/test/actions/set_run_result.test.ts +61 -0
  365. package/test/actions/start_session.test.ts +82 -0
  366. package/test/nodes/split_by_contact_field.test.ts +59 -0
  367. package/test/nodes/split_by_run_result.test.ts +100 -0
  368. package/test/nodes/split_by_ticket.test.ts +157 -0
  369. package/test/nodes/split_by_webhook.test.ts +131 -0
  370. package/test/nodes/wait_for_audio.test.ts +182 -0
  371. package/test/nodes/wait_for_dial.test.ts +382 -0
  372. package/test/nodes/wait_for_digits.test.ts +233 -109
  373. package/test/nodes/wait_for_menu.test.ts +383 -0
  374. package/test/temba-flow-collision.test.ts +286 -6
  375. package/test/temba-flow-editor.test.ts +240 -0
  376. package/test/temba-flow-plumber.test.ts +62 -0
  377. package/test/temba-node-type-selector.test.ts +6 -6
  378. package/test/temba-select.test.ts +6 -1
  379. package/test/utils.test.ts +4 -2
  380. package/screenshots/truth/nodes/wait_for_digits/editor/phone-number-collection.png +0 -0
  381. package/screenshots/truth/nodes/wait_for_digits/editor/single-digit-with-timeout.png +0 -0
  382. package/screenshots/truth/nodes/wait_for_digits/editor/verification-code.png +0 -0
  383. package/screenshots/truth/nodes/wait_for_digits/render/phone-number-collection.png +0 -0
  384. package/screenshots/truth/nodes/wait_for_digits/render/single-digit-with-timeout.png +0 -0
  385. package/screenshots/truth/nodes/wait_for_digits/render/verification-code.png +0 -0
@@ -8,11 +8,11 @@ import { RapidElement } from '../RapidElement';
8
8
  import { repeat } from 'lit-html/directives/repeat.js';
9
9
  import { CustomEventType } from '../interfaces';
10
10
  import { generateUUID, postJSON, fetchResults, getClasses } from '../utils';
11
+ import { formatIssueMessage, getNodeBounds, calculateReflowPositions, snapToGrid } from './utils';
11
12
  import { ACTION_CONFIG, NODE_CONFIG } from './config';
12
13
  import { ACTION_GROUP_METADATA } from './types';
13
14
  import { Plumber, calculateFlowchartPath, ARROW_LENGTH, ARROW_HALF_WIDTH, CURSOR_GAP } from './Plumber';
14
15
  import { CanvasNode } from './CanvasNode';
15
- import { getNodeBounds, calculateReflowPositions, snapToGrid } from './utils';
16
16
  export function findNodeForExit(definition, exitUuid) {
17
17
  for (const node of definition.nodes) {
18
18
  const exit = node.exits.find((e) => e.uuid === exitUuid);
@@ -22,7 +22,7 @@ export function findNodeForExit(definition, exitUuid) {
22
22
  }
23
23
  return null;
24
24
  }
25
- const SAVE_QUIET_TIME = 500;
25
+ const SAVE_QUIET_TIME = 2000;
26
26
  const DRAG_THRESHOLD = 5;
27
27
  const AUTO_TRANSLATE_MODELS_ENDPOINT = '/api/internal/llms.json';
28
28
  // Offset for positioning dropped action node relative to mouse cursor
@@ -68,12 +68,23 @@ export class Editor extends RapidElement {
68
68
  }
69
69
  static get styles() {
70
70
  return css `
71
+ #editor-container {
72
+ position: relative;
73
+ flex: 1;
74
+ display: flex;
75
+ min-height: 0;
76
+ }
77
+
71
78
  #editor {
72
79
  overflow: scroll;
73
80
  flex: 1;
74
81
  -webkit-font-smoothing: antialiased;
75
82
  }
76
83
 
84
+ temba-floating-tab {
85
+ --floating-tab-right: 15px;
86
+ }
87
+
77
88
  #grid {
78
89
  position: relative;
79
90
  background-color: #f9f9f9;
@@ -207,7 +218,7 @@ export class Editor extends RapidElement {
207
218
  font-weight: 600;
208
219
  line-height: 0.9;
209
220
  cursor: pointer;
210
- z-index: 500;
221
+ z-index: 10;
211
222
  pointer-events: auto;
212
223
  white-space: nowrap;
213
224
  user-select: none;
@@ -522,6 +533,91 @@ export class Editor extends RapidElement {
522
533
  color: #9ca3af;
523
534
  white-space: nowrap;
524
535
  }
536
+
537
+ .issue-list-item {
538
+ display: flex;
539
+ align-items: center;
540
+ gap: 8px;
541
+ padding: 8px;
542
+ border-radius: 4px;
543
+ cursor: pointer;
544
+ font-size: 13px;
545
+ color: #333;
546
+ }
547
+
548
+ .issue-list-item:hover {
549
+ background: #fff5f5;
550
+ }
551
+
552
+ .issue-list-item temba-icon {
553
+ color: tomato;
554
+ flex-shrink: 0;
555
+ }
556
+
557
+ .empty-flow {
558
+ position: sticky;
559
+ top: 80px;
560
+ left: 0;
561
+ right: 0;
562
+ height: 0;
563
+ display: flex;
564
+ justify-content: center;
565
+ pointer-events: none;
566
+ z-index: 50;
567
+ }
568
+
569
+ .empty-flow-content {
570
+ display: flex;
571
+ flex-direction: column;
572
+ align-items: center;
573
+ gap: 16px;
574
+ text-align: center;
575
+ pointer-events: auto;
576
+ }
577
+
578
+ .empty-flow-title {
579
+ font-size: 18px;
580
+ font-weight: 600;
581
+ color: #374151;
582
+ }
583
+
584
+ .empty-flow-description {
585
+ font-size: 14px;
586
+ color: #6b7280;
587
+ max-width: 320px;
588
+ line-height: 1.5;
589
+ }
590
+
591
+ .empty-flow-button {
592
+ background: var(--color-primary-dark);
593
+ border: none;
594
+ color: #fff;
595
+ padding: 10px 20px;
596
+ border-radius: var(--curvature);
597
+ font-size: 14px;
598
+ font-weight: 600;
599
+ cursor: pointer;
600
+ transition: opacity 0.2s ease;
601
+ }
602
+
603
+ .empty-flow-button:hover {
604
+ opacity: 0.9;
605
+ }
606
+
607
+ .save-indicator {
608
+ position: absolute;
609
+ top: 8px;
610
+ right: 16px;
611
+ padding: 6px 10px;
612
+ z-index: 10000;
613
+ pointer-events: none;
614
+ opacity: 0;
615
+ transition: opacity 0.15s ease-in-out;
616
+ }
617
+
618
+ .save-indicator.visible {
619
+ opacity: 1;
620
+ }
525
621
  `;
526
622
  }
527
623
  constructor() {
@@ -550,6 +646,7 @@ export class Editor extends RapidElement {
550
646
  // Canvas-relative source exit position (set at drag start)
551
647
  this.connectionSourceX = null;
552
648
  this.connectionSourceY = null;
649
+ this.issuesWindowHidden = true;
553
650
  this.localizationWindowHidden = true;
554
651
  this.translationFilters = {
555
652
  categories: false
@@ -563,12 +660,15 @@ export class Editor extends RapidElement {
563
660
  this.revisions = [];
564
661
  this.viewingRevision = null;
565
662
  this.isLoadingRevisions = false;
663
+ this.isSaving = false;
664
+ this.saveError = null;
566
665
  this.preRevertState = null;
567
666
  this.translationCache = new Map();
568
667
  // NodeEditor state - handles both node and action editing
569
668
  this.editingNode = null;
570
669
  this.editingNodeUI = null;
571
670
  this.editingAction = null;
671
+ this.dialogOrigin = null;
572
672
  this.isCreatingNewNode = false;
573
673
  this.pendingNodePosition = null;
574
674
  // Canvas drop state for dragging actions to canvas
@@ -596,6 +696,7 @@ export class Editor extends RapidElement {
596
696
  this.setupGlobalEventListeners();
597
697
  if (changes.has('flow')) {
598
698
  getStore().getState().fetchRevision(`/flow/revisions/${this.flow}`);
699
+ this.fetchRevisions();
599
700
  }
600
701
  this.plumber.on('connection:drag', (connection) => {
601
702
  this.dragFromNodeId = connection.data.nodeId;
@@ -723,9 +824,14 @@ export class Editor extends RapidElement {
723
824
  }
724
825
  if (changes.has('dirtyDate')) {
725
826
  if (this.dirtyDate) {
827
+ this.isSaving = true;
726
828
  this.debouncedSave();
727
829
  }
728
830
  }
831
+ if (changes.has('saveError') && this.saveError) {
832
+ this.showSaveErrorDialog(this.saveError);
833
+ this.saveError = null;
834
+ }
729
835
  if (changes.has('languageCode')) {
730
836
  this.translationCache.clear();
731
837
  }
@@ -767,11 +873,15 @@ export class Editor extends RapidElement {
767
873
  }
768
874
  saveChanges(definitionOverride) {
769
875
  const definition = definitionOverride || this.definition;
770
- // post the flow definition to the server
876
+ this.isSaving = true;
771
877
  return getStore()
772
878
  .postJSON(`/flow/revisions/${this.flow}/`, definition)
773
879
  .then((response) => {
774
880
  var _b;
881
+ if (response.status < 200 || response.status >= 300) {
882
+ this.saveError = this.extractErrorMessage(response);
883
+ return;
884
+ }
775
885
  // Update flow info and revision with the response data
776
886
  if (response.json) {
777
887
  const state = getStore().getState();
@@ -781,16 +891,53 @@ export class Editor extends RapidElement {
781
891
  if (((_b = response.json.revision) === null || _b === void 0 ? void 0 : _b.revision) !== undefined) {
782
892
  state.setRevision(response.json.revision.revision);
783
893
  }
784
- // if the revisions window is open, refresh the list
785
- if (!this.revisionsWindowHidden) {
786
- this.fetchRevisions();
787
- }
894
+ // Refresh revisions list so the tab visibility stays up to date
895
+ this.fetchRevisions();
788
896
  }
897
+ getStore().getState().setDirtyDate(null);
789
898
  })
790
899
  .catch((error) => {
791
900
  console.error('Failed to save flow:', error);
901
+ if (error instanceof Response) {
902
+ this.saveError = `Server error (${error.status}). Your changes have not been saved.`;
903
+ }
904
+ else {
905
+ this.saveError =
906
+ 'Unable to reach the server. Please check your connection and try again.';
907
+ }
908
+ })
909
+ .finally(() => {
910
+ this.isSaving = false;
911
+ });
912
+ }
913
+ extractErrorMessage(response) {
914
+ if (response.json) {
915
+ if (typeof response.json.detail === 'string') {
916
+ return response.json.detail;
917
+ }
918
+ if (typeof response.json.error === 'string') {
919
+ return response.json.error;
920
+ }
921
+ if (typeof response.json.description === 'string') {
922
+ return response.json.description;
923
+ }
924
+ }
925
+ return `Save failed with status ${response.status}.`;
926
+ }
927
+ showSaveErrorDialog(message) {
928
+ const dialog = document.createElement('temba-dialog');
929
+ dialog.header = 'Save Failed';
930
+ dialog.primaryButtonName = '';
931
+ dialog.cancelButtonName = 'Dismiss';
932
+ const content = document.createElement('div');
933
+ content.style.cssText = 'padding: 20px; font-size: 14px; line-height: 1.5;';
934
+ content.textContent = message;
935
+ dialog.appendChild(content);
936
+ document.body.appendChild(dialog);
937
+ dialog.open = true;
938
+ dialog.addEventListener('temba-dialog-hidden', () => {
939
+ document.body.removeChild(dialog);
792
940
  });
793
- getStore().getState().setDirtyDate(null);
794
941
  }
795
942
  startActivityFetching() {
796
943
  // Don't start if simulator is active
@@ -822,6 +969,10 @@ export class Editor extends RapidElement {
822
969
  }
823
970
  const state = store.getState();
824
971
  state.fetchActivity(activityEndpoint).then(() => {
972
+ // Guard against responses arriving after the editor is disconnected
973
+ if (!this.isConnected) {
974
+ return;
975
+ }
825
976
  // Schedule next fetch with exponential backoff (max 5 minutes)
826
977
  this.activityInterval = Math.min(60000 * 5, this.activityInterval + 100);
827
978
  if (this.activityTimer !== null) {
@@ -853,6 +1004,9 @@ export class Editor extends RapidElement {
853
1004
  if (canvas) {
854
1005
  canvas.removeEventListener('contextmenu', this.boundCanvasContextMenu);
855
1006
  }
1007
+ // Clear all flow-specific data from the store so stale data
1008
+ // isn't briefly visible when a different flow is opened.
1009
+ zustand.getState().clearFlowData();
856
1010
  }
857
1011
  setupGlobalEventListeners() {
858
1012
  document.addEventListener('mousemove', this.boundMouseMove);
@@ -915,6 +1069,7 @@ export class Editor extends RapidElement {
915
1069
  return;
916
1070
  if (this.isReadOnly())
917
1071
  return;
1072
+ this.blurActiveContentEditable();
918
1073
  const element = event.currentTarget;
919
1074
  // Only start dragging if clicking on the element itself, not on exits or other interactive elements
920
1075
  const target = event.target;
@@ -974,10 +1129,22 @@ export class Editor extends RapidElement {
974
1129
  // We clicked on empty canvas space, start selection
975
1130
  this.handleCanvasMouseDown(event);
976
1131
  }
1132
+ blurActiveContentEditable() {
1133
+ var _b;
1134
+ let active = document.activeElement;
1135
+ while ((_b = active === null || active === void 0 ? void 0 : active.shadowRoot) === null || _b === void 0 ? void 0 : _b.activeElement) {
1136
+ active = active.shadowRoot.activeElement;
1137
+ }
1138
+ if (active instanceof HTMLElement &&
1139
+ active.getAttribute('contenteditable') === 'true') {
1140
+ active.blur();
1141
+ }
1142
+ }
977
1143
  handleCanvasMouseDown(event) {
978
1144
  var _b;
979
1145
  if (this.isReadOnly())
980
1146
  return;
1147
+ this.blurActiveContentEditable();
981
1148
  const target = event.target;
982
1149
  if (target.id === 'canvas' || target.id === 'grid') {
983
1150
  // Ignore clicks on exits
@@ -1538,6 +1705,25 @@ export class Editor extends RapidElement {
1538
1705
  });
1539
1706
  }
1540
1707
  }
1708
+ handleEmptyFlowClick(event) {
1709
+ const editor = this.querySelector('#editor');
1710
+ if (!editor)
1711
+ return;
1712
+ // Scroll to top-left
1713
+ editor.scrollTo({ left: 0, top: 0, behavior: 'smooth' });
1714
+ // Place node at top-left of the canvas
1715
+ const nodeLeft = 0;
1716
+ const nodeTop = 0;
1717
+ const canvasMenu = this.querySelector('temba-canvas-menu');
1718
+ if (canvasMenu) {
1719
+ const button = event.currentTarget;
1720
+ const rect = button.getBoundingClientRect();
1721
+ const menuWidth = 265;
1722
+ const menuX = rect.left + rect.width / 2 - menuWidth / 2;
1723
+ const menuY = rect.bottom + 8;
1724
+ canvasMenu.show(menuX, menuY, { x: nodeLeft, y: nodeTop }, false);
1725
+ }
1726
+ }
1541
1727
  handleCanvasMenuSelection(event) {
1542
1728
  const selection = event.detail;
1543
1729
  const store = getStore();
@@ -1692,6 +1878,10 @@ export class Editor extends RapidElement {
1692
1878
  handleActionEditRequested(event) {
1693
1879
  // For action editing, we set the action and find the corresponding node
1694
1880
  this.editingAction = event.detail.action;
1881
+ this.dialogOrigin =
1882
+ event.detail.originX != null
1883
+ ? { x: event.detail.originX, y: event.detail.originY }
1884
+ : null;
1695
1885
  // Find the node that contains this action
1696
1886
  const nodeUuid = event.detail.nodeUuid;
1697
1887
  const node = this.definition.nodes.find((n) => n.uuid === nodeUuid);
@@ -1727,6 +1917,10 @@ export class Editor extends RapidElement {
1727
1917
  handleNodeEditRequested(event) {
1728
1918
  this.editingNode = event.detail.node;
1729
1919
  this.editingNodeUI = event.detail.nodeUI;
1920
+ this.dialogOrigin =
1921
+ event.detail.originX != null
1922
+ ? { x: event.detail.originX, y: event.detail.originY }
1923
+ : null;
1730
1924
  }
1731
1925
  handleNodeDeleted(event) {
1732
1926
  const nodeUuid = event.detail.uuid;
@@ -1797,6 +1991,7 @@ export class Editor extends RapidElement {
1797
1991
  this.editingNode = null;
1798
1992
  this.editingNodeUI = null;
1799
1993
  this.editingAction = null;
1994
+ this.dialogOrigin = null;
1800
1995
  }
1801
1996
  handleActionEditCanceled() {
1802
1997
  // If we were creating a new node, just discard it
@@ -2253,6 +2448,7 @@ export class Editor extends RapidElement {
2253
2448
  }
2254
2449
  this.localizationWindowHidden = false;
2255
2450
  this.revisionsWindowHidden = true;
2451
+ this.issuesWindowHidden = true;
2256
2452
  const alreadySelected = languages.some((lang) => lang.code === this.languageCode);
2257
2453
  if (!alreadySelected) {
2258
2454
  this.handleLanguageChange(languages[0].code);
@@ -2460,11 +2656,42 @@ export class Editor extends RapidElement {
2460
2656
  }
2461
2657
  this.autoTranslating = false;
2462
2658
  }
2659
+ handleIssuesTabClick() {
2660
+ this.issuesWindowHidden = false;
2661
+ this.revisionsWindowHidden = true;
2662
+ this.localizationWindowHidden = true;
2663
+ }
2664
+ handleIssuesWindowClosed() {
2665
+ this.issuesWindowHidden = true;
2666
+ }
2667
+ handleIssueItemClick(issue) {
2668
+ var _b;
2669
+ const issuesWindow = document.getElementById('issues-window');
2670
+ issuesWindow === null || issuesWindow === void 0 ? void 0 : issuesWindow.handleClose();
2671
+ this.issuesWindowHidden = true;
2672
+ this.focusNode(issue.node_uuid);
2673
+ const node = this.definition.nodes.find((n) => n.uuid === issue.node_uuid);
2674
+ if (!node)
2675
+ return;
2676
+ if (issue.action_uuid) {
2677
+ const action = (_b = node.actions) === null || _b === void 0 ? void 0 : _b.find((a) => a.uuid === issue.action_uuid);
2678
+ if (action) {
2679
+ this.editingAction = action;
2680
+ this.editingNode = node;
2681
+ this.editingNodeUI = this.definition._ui.nodes[issue.node_uuid];
2682
+ }
2683
+ }
2684
+ else {
2685
+ this.editingNode = node;
2686
+ this.editingNodeUI = this.definition._ui.nodes[issue.node_uuid];
2687
+ }
2688
+ }
2463
2689
  handleRevisionsTabClick() {
2464
2690
  if (this.revisionsWindowHidden) {
2465
2691
  this.fetchRevisions();
2466
2692
  this.revisionsWindowHidden = false;
2467
- this.localizationWindowHidden = true; // Close other window
2693
+ this.issuesWindowHidden = true;
2694
+ this.localizationWindowHidden = true;
2468
2695
  }
2469
2696
  }
2470
2697
  handleRevisionsWindowClosed() {
@@ -2565,14 +2792,61 @@ export class Editor extends RapidElement {
2565
2792
  // Fetch the latest version of the flow to ensure the store is up to date
2566
2793
  getStore().getState().fetchRevision(`/flow/revisions/${this.flow}`);
2567
2794
  }
2795
+ renderIssuesTab() {
2796
+ var _b;
2797
+ if (!((_b = this.flowIssues) === null || _b === void 0 ? void 0 : _b.length) || !this.revisionsWindowHidden)
2798
+ return '';
2799
+ return html `
2800
+ <temba-floating-tab
2801
+ id="issues-tab"
2802
+ icon="alert_warning"
2803
+ label="Flow Issues"
2804
+ color="tomato"
2805
+ order="1"
2806
+ .hidden=${!this.issuesWindowHidden}
2807
+ @temba-button-clicked=${this.handleIssuesTabClick}
2808
+ ></temba-floating-tab>
2809
+ `;
2810
+ }
2811
+ renderIssuesWindow() {
2812
+ var _b;
2813
+ if (!((_b = this.flowIssues) === null || _b === void 0 ? void 0 : _b.length))
2814
+ return '';
2815
+ return html `
2816
+ <temba-floating-window
2817
+ id="issues-window"
2818
+ header="Flow Issues"
2819
+ .width=${360}
2820
+ .maxHeight=${600}
2821
+ .top=${75}
2822
+ color="tomato"
2823
+ .hidden=${this.issuesWindowHidden}
2824
+ @temba-dialog-hidden=${this.handleIssuesWindowClosed}
2825
+ >
2826
+ <div style="display:flex; flex-direction:column; gap:2px;">
2827
+ ${this.flowIssues.map((issue) => html `
2828
+ <div
2829
+ class="issue-list-item"
2830
+ @click=${() => this.handleIssueItemClick(issue)}
2831
+ >
2832
+ <temba-icon name="alert_warning" size="1.2"></temba-icon>
2833
+ <span>${formatIssueMessage(issue)}</span>
2834
+ </div>
2835
+ `)}
2836
+ </div>
2837
+ </temba-floating-window>
2838
+ `;
2839
+ }
2568
2840
  renderRevisionsTab() {
2841
+ if (this.revisions.length <= 1)
2842
+ return '';
2569
2843
  return html `
2570
2844
  <temba-floating-tab
2571
2845
  id="revisions-tab"
2572
2846
  icon="revisions"
2573
2847
  label="Revisions"
2574
2848
  color="rgb(142, 94, 167)"
2575
- order="1"
2849
+ order="2"
2576
2850
  .hidden=${!this.revisionsWindowHidden && this.localizationWindowHidden}
2577
2851
  @temba-button-clicked=${this.handleRevisionsTabClick}
2578
2852
  ></temba-floating-tab>
@@ -2833,6 +3107,11 @@ export class Editor extends RapidElement {
2833
3107
  `;
2834
3108
  }
2835
3109
  renderLocalizationTab() {
3110
+ var _b;
3111
+ if (!this.revisionsWindowHidden)
3112
+ return '';
3113
+ if (((_b = this.definition) === null || _b === void 0 ? void 0 : _b.nodes.length) === 0)
3114
+ return '';
2836
3115
  const languages = this.getLocalizationLanguages();
2837
3116
  if (!languages.length) {
2838
3117
  return html ``;
@@ -2843,7 +3122,7 @@ export class Editor extends RapidElement {
2843
3122
  icon="language"
2844
3123
  label="Translate Flow"
2845
3124
  color="#6b7280"
2846
- order="2"
3125
+ order="3"
2847
3126
  .hidden=${!this.localizationWindowHidden}
2848
3127
  @temba-button-clicked=${this.handleLocalizationTabClick}
2849
3128
  ></temba-floating-tab>
@@ -2890,23 +3169,44 @@ export class Editor extends RapidElement {
2890
3169
  ${unsafeCSS(CanvasNode.styles.cssText)}
2891
3170
  </style>`;
2892
3171
  const stickies = ((_c = (_b = this.definition) === null || _b === void 0 ? void 0 : _b._ui) === null || _c === void 0 ? void 0 : _c.stickies) || {};
2893
- return html `${style} ${this.renderRevisionsWindow()}
2894
- ${this.renderLocalizationWindow()} ${this.renderAutoTranslateDialog()}
2895
- <div id="editor">
2896
- <div
2897
- id="grid"
2898
- class="${this.viewingRevision ? 'viewing-revision' : ''}"
2899
- style="min-width:100%;width:${this.canvasSize.width}px; height:${this
2900
- .canvasSize.height}px"
2901
- >
3172
+ return html `${style} ${this.renderIssuesWindow()}
3173
+ ${this.renderRevisionsWindow()} ${this.renderLocalizationWindow()}
3174
+ ${this.renderAutoTranslateDialog()}
3175
+ <div id="editor-container">
3176
+ <div id="editor">
3177
+ ${this.definition &&
3178
+ this.definition.nodes.length === 0 &&
3179
+ !this.isReadOnly()
3180
+ ? html `<div class="empty-flow">
3181
+ <div class="empty-flow-content">
3182
+ <div class="empty-flow-title">This flow is empty</div>
3183
+ <div class="empty-flow-description">
3184
+ Get started by adding your first action or split to define
3185
+ how this flow will work.
3186
+ </div>
3187
+ <button
3188
+ class="empty-flow-button"
3189
+ @click=${this.handleEmptyFlowClick}
3190
+ >
3191
+ Add first step
3192
+ </button>
3193
+ </div>
3194
+ </div>`
3195
+ : ''}
2902
3196
  <div
2903
- id="canvas"
2904
- class="${getClasses({
3197
+ id="grid"
3198
+ class="${this.viewingRevision ? 'viewing-revision' : ''}"
3199
+ style="min-width:100%;width:${this.canvasSize
3200
+ .width}px; height:${this.canvasSize.height}px"
3201
+ >
3202
+ <div
3203
+ id="canvas"
3204
+ class="${getClasses({
2905
3205
  'viewing-revision': !!this.viewingRevision,
2906
3206
  'read-only-connections': !!this.viewingRevision || this.isTranslating
2907
3207
  })}"
2908
- >
2909
- ${this.definition
3208
+ >
3209
+ ${this.definition
2910
3210
  ? repeat([...this.definition.nodes].sort((a, b) => a.uuid.localeCompare(b.uuid)), (node) => node.uuid, (node) => {
2911
3211
  var _b, _c, _d;
2912
3212
  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) || {
@@ -2920,43 +3220,49 @@ export class Editor extends RapidElement {
2920
3220
  const isFlowStart = this.definition.nodes.length > 0 &&
2921
3221
  this.definition.nodes[0].uuid === node.uuid;
2922
3222
  return html `<temba-flow-node
2923
- class="draggable ${dragging ? 'dragging' : ''} ${selected
2924
- ? 'selected'
2925
- : ''} ${isFlowStart ? 'flow-start' : ''}"
2926
- @mousedown=${this.handleMouseDown.bind(this)}
2927
- uuid=${node.uuid}
2928
- data-node-uuid=${node.uuid}
2929
- style="left:${position.left}px; top:${position.top}px;transition: all 0.2s ease-in-out;"
2930
- .plumber=${this.plumber}
2931
- .node=${node}
2932
- .ui=${this.definition._ui.nodes[node.uuid]}
2933
- @temba-node-deleted=${(event) => {
3223
+ class="draggable ${dragging
3224
+ ? 'dragging'
3225
+ : ''} ${selected ? 'selected' : ''} ${isFlowStart
3226
+ ? 'flow-start'
3227
+ : ''}"
3228
+ @mousedown=${this.handleMouseDown.bind(this)}
3229
+ uuid=${node.uuid}
3230
+ data-node-uuid=${node.uuid}
3231
+ style="left:${position.left}px; top:${position.top}px;transition: all 0.2s ease-in-out;"
3232
+ .plumber=${this.plumber}
3233
+ .node=${node}
3234
+ .ui=${this.definition._ui.nodes[node.uuid]}
3235
+ @temba-node-deleted=${(event) => {
2934
3236
  this.deleteNodes([event.detail.uuid]);
2935
3237
  }}
2936
- ></temba-flow-node>`;
3238
+ ></temba-flow-node>`;
2937
3239
  })
2938
3240
  : html `<temba-loading></temba-loading>`}
2939
- ${repeat(Object.entries(stickies), ([uuid]) => uuid, ([uuid, sticky]) => {
3241
+ ${repeat(Object.entries(stickies), ([uuid]) => uuid, ([uuid, sticky]) => {
2940
3242
  var _b;
2941
3243
  const position = sticky.position || { left: 0, top: 0 };
2942
3244
  const dragging = this.isDragging && ((_b = this.currentDragItem) === null || _b === void 0 ? void 0 : _b.uuid) === uuid;
2943
3245
  const selected = this.selectedItems.has(uuid);
2944
3246
  return html `<temba-sticky-note
2945
- class="draggable ${dragging ? 'dragging' : ''} ${selected
3247
+ class="draggable ${dragging ? 'dragging' : ''} ${selected
2946
3248
  ? 'selected'
2947
3249
  : ''}"
2948
- @mousedown=${this.handleMouseDown.bind(this)}
2949
- style="left:${position.left}px; top:${position.top}px;"
2950
- uuid=${uuid}
2951
- .data=${sticky}
2952
- .dragging=${dragging}
2953
- .selected=${selected}
2954
- ></temba-sticky-note>`;
3250
+ @mousedown=${this.handleMouseDown.bind(this)}
3251
+ style="left:${position.left}px; top:${position.top}px;"
3252
+ uuid=${uuid}
3253
+ .data=${sticky}
3254
+ .dragging=${dragging}
3255
+ .selected=${selected}
3256
+ ></temba-sticky-note>`;
2955
3257
  })}
2956
- ${this.renderSelectionBox()} ${this.renderCanvasDropPreview()}
2957
- ${this.renderConnectionPlaceholder()}
3258
+ ${this.renderSelectionBox()} ${this.renderCanvasDropPreview()}
3259
+ ${this.renderConnectionPlaceholder()}
3260
+ </div>
2958
3261
  </div>
2959
3262
  </div>
3263
+ <div class="save-indicator ${this.isSaving ? 'visible' : ''}">
3264
+ <temba-loading units="3" size="8"></temba-loading>
3265
+ </div>
2960
3266
  </div>
2961
3267
 
2962
3268
  ${this.editingNode || this.editingAction
@@ -2964,6 +3270,7 @@ export class Editor extends RapidElement {
2964
3270
  .node=${this.editingNode}
2965
3271
  .nodeUI=${this.editingNodeUI}
2966
3272
  .action=${this.editingAction}
3273
+ .dialogOrigin=${this.dialogOrigin}
2967
3274
  @temba-node-saved=${(e) => this.handleNodeSaved(e.detail.node, e.detail.uiConfig)}
2968
3275
  @temba-action-saved=${(e) => this.handleActionSaved(e.detail.action)}
2969
3276
  @temba-node-edit-cancelled=${this.handleNodeEditCanceled}
@@ -2977,7 +3284,8 @@ export class Editor extends RapidElement {
2977
3284
  .features=${this.features}
2978
3285
  ></temba-node-type-selector>`
2979
3286
  : ''}
2980
- ${this.renderRevisionsTab()} ${this.renderLocalizationTab()} `;
3287
+ ${this.renderIssuesTab()} ${this.renderRevisionsTab()}
3288
+ ${this.renderLocalizationTab()} `;
2981
3289
  }
2982
3290
  }
2983
3291
  __decorate([
@@ -3016,6 +3324,9 @@ __decorate([
3016
3324
  __decorate([
3017
3325
  fromStore(zustand, (state) => state.getCurrentActivity())
3018
3326
  ], Editor.prototype, "activityData", void 0);
3327
+ __decorate([
3328
+ fromStore(zustand, (state) => { var _b; return ((_b = state.flowInfo) === null || _b === void 0 ? void 0 : _b.issues) || []; })
3329
+ ], Editor.prototype, "flowIssues", void 0);
3019
3330
  __decorate([
3020
3331
  state()
3021
3332
  ], Editor.prototype, "isDragging", void 0);
@@ -3046,6 +3357,9 @@ __decorate([
3046
3357
  __decorate([
3047
3358
  state()
3048
3359
  ], Editor.prototype, "isValidTarget", void 0);
3360
+ __decorate([
3361
+ state()
3362
+ ], Editor.prototype, "issuesWindowHidden", void 0);
3049
3363
  __decorate([
3050
3364
  state()
3051
3365
  ], Editor.prototype, "localizationWindowHidden", void 0);
@@ -3079,6 +3393,12 @@ __decorate([
3079
3393
  __decorate([
3080
3394
  state()
3081
3395
  ], Editor.prototype, "isLoadingRevisions", void 0);
3396
+ __decorate([
3397
+ state()
3398
+ ], Editor.prototype, "isSaving", void 0);
3399
+ __decorate([
3400
+ state()
3401
+ ], Editor.prototype, "saveError", void 0);
3082
3402
  __decorate([
3083
3403
  state()
3084
3404
  ], Editor.prototype, "editingNode", void 0);