@nyaruka/temba-components 0.140.0 → 0.141.1

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 (297) hide show
  1. package/.lintstagedrc.js +10 -0
  2. package/CHANGELOG.md +22 -0
  3. package/dist/locales/es.js +5 -5
  4. package/dist/locales/es.js.map +1 -1
  5. package/dist/locales/fr.js +5 -5
  6. package/dist/locales/fr.js.map +1 -1
  7. package/dist/locales/locale-codes.js +11 -2
  8. package/dist/locales/locale-codes.js.map +1 -1
  9. package/dist/locales/pt.js +5 -5
  10. package/dist/locales/pt.js.map +1 -1
  11. package/dist/temba-components.js +263 -156
  12. package/dist/temba-components.js.map +1 -1
  13. package/out-tsc/src/display/FloatingTab.js +1 -1
  14. package/out-tsc/src/display/FloatingTab.js.map +1 -1
  15. package/out-tsc/src/flow/CanvasNode.js +1 -1
  16. package/out-tsc/src/flow/CanvasNode.js.map +1 -1
  17. package/out-tsc/src/flow/Editor.js +239 -43
  18. package/out-tsc/src/flow/Editor.js.map +1 -1
  19. package/out-tsc/src/flow/Plumber.js +61 -14
  20. package/out-tsc/src/flow/Plumber.js.map +1 -1
  21. package/out-tsc/src/flow/actions/add_contact_groups.js +4 -1
  22. package/out-tsc/src/flow/actions/add_contact_groups.js.map +1 -1
  23. package/out-tsc/src/flow/actions/add_input_labels.js +4 -1
  24. package/out-tsc/src/flow/actions/add_input_labels.js.map +1 -1
  25. package/out-tsc/src/flow/actions/remove_contact_groups.js +6 -1
  26. package/out-tsc/src/flow/actions/remove_contact_groups.js.map +1 -1
  27. package/out-tsc/src/flow/actions/send_broadcast.js +6 -2
  28. package/out-tsc/src/flow/actions/send_broadcast.js.map +1 -1
  29. package/out-tsc/src/flow/actions/set_contact_channel.js +13 -0
  30. package/out-tsc/src/flow/actions/set_contact_channel.js.map +1 -1
  31. package/out-tsc/src/flow/actions/set_contact_status.js +7 -5
  32. package/out-tsc/src/flow/actions/set_contact_status.js.map +1 -1
  33. package/out-tsc/src/flow/actions/start_session.js +10 -3
  34. package/out-tsc/src/flow/actions/start_session.js.map +1 -1
  35. package/out-tsc/src/flow/nodes/split_by_contact_field.js +18 -5
  36. package/out-tsc/src/flow/nodes/split_by_contact_field.js.map +1 -1
  37. package/out-tsc/src/flow/nodes/split_by_expression.js +1 -1
  38. package/out-tsc/src/flow/nodes/split_by_expression.js.map +1 -1
  39. package/out-tsc/src/flow/nodes/split_by_llm_categorize.js +0 -1
  40. package/out-tsc/src/flow/nodes/split_by_llm_categorize.js.map +1 -1
  41. package/out-tsc/src/flow/nodes/split_by_random.js +0 -1
  42. package/out-tsc/src/flow/nodes/split_by_random.js.map +1 -1
  43. package/out-tsc/src/flow/nodes/split_by_run_result.js +10 -4
  44. package/out-tsc/src/flow/nodes/split_by_run_result.js.map +1 -1
  45. package/out-tsc/src/flow/nodes/wait_for_digits.js +1 -1
  46. package/out-tsc/src/flow/nodes/wait_for_digits.js.map +1 -1
  47. package/out-tsc/src/flow/nodes/wait_for_response.js +1 -1
  48. package/out-tsc/src/flow/nodes/wait_for_response.js.map +1 -1
  49. package/out-tsc/src/form/FieldRenderer.js +7 -0
  50. package/out-tsc/src/form/FieldRenderer.js.map +1 -1
  51. package/out-tsc/src/layout/Dialog.js +0 -1
  52. package/out-tsc/src/layout/Dialog.js.map +1 -1
  53. package/out-tsc/src/layout/Modax.js +20 -2
  54. package/out-tsc/src/layout/Modax.js.map +1 -1
  55. package/out-tsc/src/list/ContentMenu.js +14 -1
  56. package/out-tsc/src/list/ContentMenu.js.map +1 -1
  57. package/out-tsc/src/live/ContactChat.js +10 -1
  58. package/out-tsc/src/live/ContactChat.js.map +1 -1
  59. package/out-tsc/src/live/TembaChart.js.map +1 -1
  60. package/out-tsc/src/locales/es.js +5 -5
  61. package/out-tsc/src/locales/es.js.map +1 -1
  62. package/out-tsc/src/locales/fr.js +5 -5
  63. package/out-tsc/src/locales/fr.js.map +1 -1
  64. package/out-tsc/src/locales/locale-codes.js +11 -2
  65. package/out-tsc/src/locales/locale-codes.js.map +1 -1
  66. package/out-tsc/src/locales/pt.js +5 -5
  67. package/out-tsc/src/locales/pt.js.map +1 -1
  68. package/out-tsc/src/simulator/Simulator.js +11 -0
  69. package/out-tsc/src/simulator/Simulator.js.map +1 -1
  70. package/out-tsc/src/store/AppState.js +13 -0
  71. package/out-tsc/src/store/AppState.js.map +1 -1
  72. package/out-tsc/src/version.js +9 -0
  73. package/out-tsc/src/version.js.map +1 -0
  74. package/out-tsc/test/actions/add_contact_groups.test.js +35 -0
  75. package/out-tsc/test/actions/add_contact_groups.test.js.map +1 -1
  76. package/out-tsc/test/actions/add_input_labels.test.js +53 -0
  77. package/out-tsc/test/actions/add_input_labels.test.js.map +1 -0
  78. package/out-tsc/test/actions/enter_flow.test.js +71 -0
  79. package/out-tsc/test/actions/enter_flow.test.js.map +1 -0
  80. package/out-tsc/test/actions/remove_contact_groups.test.js +24 -0
  81. package/out-tsc/test/actions/remove_contact_groups.test.js.map +1 -1
  82. package/out-tsc/test/actions/send_broadcast.test.js +41 -0
  83. package/out-tsc/test/actions/send_broadcast.test.js.map +1 -1
  84. package/out-tsc/test/actions/set_contact_channel.test.js +67 -0
  85. package/out-tsc/test/actions/set_contact_channel.test.js.map +1 -0
  86. package/out-tsc/test/actions/set_contact_field.test.js +52 -0
  87. package/out-tsc/test/actions/set_contact_field.test.js.map +1 -0
  88. package/out-tsc/test/actions/set_contact_language.test.js +39 -0
  89. package/out-tsc/test/actions/set_contact_language.test.js.map +1 -0
  90. package/out-tsc/test/actions/set_contact_name.test.js +28 -0
  91. package/out-tsc/test/actions/set_contact_name.test.js.map +1 -0
  92. package/out-tsc/test/actions/set_contact_status.test.js +44 -0
  93. package/out-tsc/test/actions/set_contact_status.test.js.map +1 -0
  94. package/out-tsc/test/actions/set_run_result.test.js +47 -0
  95. package/out-tsc/test/actions/set_run_result.test.js.map +1 -0
  96. package/out-tsc/test/actions/start_session.test.js +76 -0
  97. package/out-tsc/test/actions/start_session.test.js.map +1 -1
  98. package/out-tsc/test/nodes/split_by_contact_field.test.js +50 -0
  99. package/out-tsc/test/nodes/split_by_contact_field.test.js.map +1 -1
  100. package/out-tsc/test/nodes/split_by_run_result.test.js +82 -0
  101. package/out-tsc/test/nodes/split_by_run_result.test.js.map +1 -1
  102. package/out-tsc/test/nodes/split_by_ticket.test.js +139 -0
  103. package/out-tsc/test/nodes/split_by_ticket.test.js.map +1 -0
  104. package/out-tsc/test/nodes/split_by_webhook.test.js +111 -0
  105. package/out-tsc/test/nodes/split_by_webhook.test.js.map +1 -0
  106. package/out-tsc/test/temba-contact-chat.test.js +12 -0
  107. package/out-tsc/test/temba-contact-chat.test.js.map +1 -1
  108. package/out-tsc/test/temba-flow-editor.test.js +206 -0
  109. package/out-tsc/test/temba-flow-editor.test.js.map +1 -1
  110. package/out-tsc/test/temba-flow-plumber.test.js +19 -0
  111. package/out-tsc/test/temba-flow-plumber.test.js.map +1 -1
  112. package/out-tsc/test/temba-select.test.js +4 -1
  113. package/out-tsc/test/temba-select.test.js.map +1 -1
  114. package/out-tsc/test/utils.test.js +4 -2
  115. package/out-tsc/test/utils.test.js.map +1 -1
  116. package/package.json +3 -9
  117. package/rollup.components.mjs +7 -1
  118. package/screenshots/truth/actions/add_contact_groups/render/descriptive-group-names.png +0 -0
  119. package/screenshots/truth/actions/add_contact_groups/render/long-group-names.png +0 -0
  120. package/screenshots/truth/actions/add_contact_groups/render/many-groups.png +0 -0
  121. package/screenshots/truth/actions/add_contact_groups/render/multiple-groups.png +0 -0
  122. package/screenshots/truth/actions/add_contact_groups/render/single-group.png +0 -0
  123. package/screenshots/truth/actions/add_contact_urn/render/expression-facebook.png +0 -0
  124. package/screenshots/truth/actions/add_contact_urn/render/expression-phone.png +0 -0
  125. package/screenshots/truth/actions/add_contact_urn/render/facebook-id.png +0 -0
  126. package/screenshots/truth/actions/add_contact_urn/render/instagram-handle.png +0 -0
  127. package/screenshots/truth/actions/add_contact_urn/render/line-id.png +0 -0
  128. package/screenshots/truth/actions/add_contact_urn/render/phone-number.png +0 -0
  129. package/screenshots/truth/actions/add_contact_urn/render/telegram-id.png +0 -0
  130. package/screenshots/truth/actions/add_contact_urn/render/viber-id.png +0 -0
  131. package/screenshots/truth/actions/add_contact_urn/render/wechat-id.png +0 -0
  132. package/screenshots/truth/actions/add_contact_urn/render/whatsapp.png +0 -0
  133. package/screenshots/truth/actions/add_input_labels/editor/multiple-labels.png +0 -0
  134. package/screenshots/truth/actions/add_input_labels/editor/single-label.png +0 -0
  135. package/screenshots/truth/actions/add_input_labels/render/multiple-labels.png +0 -0
  136. package/screenshots/truth/actions/add_input_labels/render/single-label.png +0 -0
  137. package/screenshots/truth/actions/enter_flow/editor/basic-flow.png +0 -0
  138. package/screenshots/truth/actions/enter_flow/editor/long-flow-name.png +0 -0
  139. package/screenshots/truth/actions/enter_flow/render/basic-flow.png +0 -0
  140. package/screenshots/truth/actions/enter_flow/render/long-flow-name.png +0 -0
  141. package/screenshots/truth/actions/play_audio/render/expression-url.png +0 -0
  142. package/screenshots/truth/actions/play_audio/render/static-url.png +0 -0
  143. package/screenshots/truth/actions/remove_contact_groups/render/cleanup-groups.png +0 -0
  144. package/screenshots/truth/actions/remove_contact_groups/render/long-descriptive-group-names.png +0 -0
  145. package/screenshots/truth/actions/remove_contact_groups/render/many-groups.png +0 -0
  146. package/screenshots/truth/actions/remove_contact_groups/render/multiple-groups.png +0 -0
  147. package/screenshots/truth/actions/remove_contact_groups/render/remove-from-all-groups.png +0 -0
  148. package/screenshots/truth/actions/remove_contact_groups/render/single-group.png +0 -0
  149. package/screenshots/truth/actions/say_msg/render/multiline-text.png +0 -0
  150. package/screenshots/truth/actions/say_msg/render/simple-text.png +0 -0
  151. package/screenshots/truth/actions/say_msg/render/text-with-audio-url.png +0 -0
  152. package/screenshots/truth/actions/send_broadcast/render/contacts-only.png +0 -0
  153. package/screenshots/truth/actions/send_broadcast/render/groups-and-contacts.png +0 -0
  154. package/screenshots/truth/actions/send_broadcast/render/groups-only.png +0 -0
  155. package/screenshots/truth/actions/send_broadcast/render/many-groups.png +0 -0
  156. package/screenshots/truth/actions/send_broadcast/render/multiline-text.png +0 -0
  157. package/screenshots/truth/actions/send_email/render/complex-business-email.png +0 -0
  158. package/screenshots/truth/actions/send_email/render/empty-body.png +0 -0
  159. package/screenshots/truth/actions/send_email/render/empty-subject.png +0 -0
  160. package/screenshots/truth/actions/send_email/render/long-subject.png +0 -0
  161. package/screenshots/truth/actions/send_email/render/multiline-body.png +0 -0
  162. package/screenshots/truth/actions/send_email/render/multiple-recipients.png +0 -0
  163. package/screenshots/truth/actions/send_email/render/simple-email.png +0 -0
  164. package/screenshots/truth/actions/send_email/render/with-expressions.png +0 -0
  165. package/screenshots/truth/actions/send_msg/render/long-quick-replies.png +0 -0
  166. package/screenshots/truth/actions/send_msg/render/multiline-text-with-replies.png +0 -0
  167. package/screenshots/truth/actions/send_msg/render/simple-text.png +0 -0
  168. package/screenshots/truth/actions/send_msg/render/text-with-linebreaks.png +0 -0
  169. package/screenshots/truth/actions/send_msg/render/text-with-many-quick-replies.png +0 -0
  170. package/screenshots/truth/actions/send_msg/render/text-with-quick-replies.png +0 -0
  171. package/screenshots/truth/actions/send_msg/render/text-without-quick-replies.png +0 -0
  172. package/screenshots/truth/actions/set_contact_channel/editor/sms-channel.png +0 -0
  173. package/screenshots/truth/actions/set_contact_channel/editor/whatsapp-channel.png +0 -0
  174. package/screenshots/truth/actions/set_contact_channel/render/sms-channel.png +0 -0
  175. package/screenshots/truth/actions/set_contact_channel/render/whatsapp-channel.png +0 -0
  176. package/screenshots/truth/actions/set_contact_field/editor/clear-value.png +0 -0
  177. package/screenshots/truth/actions/set_contact_field/editor/set-value.png +0 -0
  178. package/screenshots/truth/actions/set_contact_field/render/clear-value.png +0 -0
  179. package/screenshots/truth/actions/set_contact_field/render/set-value.png +0 -0
  180. package/screenshots/truth/actions/set_contact_language/editor/english.png +0 -0
  181. package/screenshots/truth/actions/set_contact_language/editor/french.png +0 -0
  182. package/screenshots/truth/actions/set_contact_language/render/english.png +0 -0
  183. package/screenshots/truth/actions/set_contact_language/render/french.png +0 -0
  184. package/screenshots/truth/actions/set_contact_name/editor/expression-name.png +0 -0
  185. package/screenshots/truth/actions/set_contact_name/editor/static-name.png +0 -0
  186. package/screenshots/truth/actions/set_contact_name/render/expression-name.png +0 -0
  187. package/screenshots/truth/actions/set_contact_name/render/static-name.png +0 -0
  188. package/screenshots/truth/actions/set_contact_status/editor/active.png +0 -0
  189. package/screenshots/truth/actions/set_contact_status/editor/archived.png +0 -0
  190. package/screenshots/truth/actions/set_contact_status/editor/blocked.png +0 -0
  191. package/screenshots/truth/actions/set_contact_status/render/active.png +0 -0
  192. package/screenshots/truth/actions/set_contact_status/render/archived.png +0 -0
  193. package/screenshots/truth/actions/set_contact_status/render/blocked.png +0 -0
  194. package/screenshots/truth/actions/set_run_result/editor/expression-value.png +0 -0
  195. package/screenshots/truth/actions/set_run_result/editor/with-category.png +0 -0
  196. package/screenshots/truth/actions/set_run_result/render/expression-value.png +0 -0
  197. package/screenshots/truth/actions/set_run_result/render/with-category.png +0 -0
  198. package/screenshots/truth/actions/start_session/render/contact-query.png +0 -0
  199. package/screenshots/truth/actions/start_session/render/contacts-only.png +0 -0
  200. package/screenshots/truth/actions/start_session/render/create-contact.png +0 -0
  201. package/screenshots/truth/actions/start_session/render/groups-and-contacts.png +0 -0
  202. package/screenshots/truth/actions/start_session/render/groups-only.png +0 -0
  203. package/screenshots/truth/actions/start_session/render/many-recipients.png +0 -0
  204. package/screenshots/truth/editor/wait.png +0 -0
  205. package/screenshots/truth/nodes/split_by_llm/render/information-extraction.png +0 -0
  206. package/screenshots/truth/nodes/split_by_llm/render/sentiment-analysis.png +0 -0
  207. package/screenshots/truth/nodes/split_by_llm/render/summarization.png +0 -0
  208. package/screenshots/truth/nodes/split_by_llm/render/translation-task.png +0 -0
  209. package/screenshots/truth/nodes/split_by_llm_categorize/editor/basic-categorization.png +0 -0
  210. package/screenshots/truth/nodes/split_by_llm_categorize/editor/custom-input-and-result-name.png +0 -0
  211. package/screenshots/truth/nodes/split_by_llm_categorize/editor/feedback-categorization.png +0 -0
  212. package/screenshots/truth/nodes/split_by_llm_categorize/editor/many-categories.png +0 -0
  213. package/screenshots/truth/nodes/split_by_llm_categorize/editor/minimal-categories.png +0 -0
  214. package/screenshots/truth/nodes/split_by_llm_categorize/render/basic-categorization.png +0 -0
  215. package/screenshots/truth/nodes/split_by_llm_categorize/render/custom-input-and-result-name.png +0 -0
  216. package/screenshots/truth/nodes/split_by_llm_categorize/render/feedback-categorization.png +0 -0
  217. package/screenshots/truth/nodes/split_by_llm_categorize/render/many-categories.png +0 -0
  218. package/screenshots/truth/nodes/split_by_llm_categorize/render/minimal-categories.png +0 -0
  219. package/screenshots/truth/nodes/split_by_random/editor/ab-test-multiple-variants.png +0 -0
  220. package/screenshots/truth/nodes/split_by_random/editor/sampling-split.png +0 -0
  221. package/screenshots/truth/nodes/split_by_random/editor/three-way-split.png +0 -0
  222. package/screenshots/truth/nodes/split_by_random/editor/two-bucket-split.png +0 -0
  223. package/screenshots/truth/nodes/split_by_random/render/ab-test-multiple-variants.png +0 -0
  224. package/screenshots/truth/nodes/split_by_random/render/sampling-split.png +0 -0
  225. package/screenshots/truth/nodes/split_by_random/render/three-way-split.png +0 -0
  226. package/screenshots/truth/nodes/split_by_random/render/two-bucket-split.png +0 -0
  227. package/screenshots/truth/nodes/wait_for_audio/render/basic-audio-wait.png +0 -0
  228. package/screenshots/truth/nodes/wait_for_dial/render/basic-dial.png +0 -0
  229. package/screenshots/truth/nodes/wait_for_dial/render/dial-with-limits.png +0 -0
  230. package/screenshots/truth/nodes/wait_for_digits/editor/basic-digits-wait.png +0 -0
  231. package/screenshots/truth/nodes/wait_for_digits/editor/digits-with-rules.png +0 -0
  232. package/screenshots/truth/nodes/wait_for_digits/render/basic-digits-wait.png +0 -0
  233. package/screenshots/truth/nodes/wait_for_digits/render/digits-with-rules.png +0 -0
  234. package/screenshots/truth/nodes/wait_for_menu/render/menu-with-digits.png +0 -0
  235. package/screenshots/truth/nodes/wait_for_response/editor/basic-wait.png +0 -0
  236. package/screenshots/truth/nodes/wait_for_response/editor/custom-result-name.png +0 -0
  237. package/screenshots/truth/nodes/wait_for_response/editor/no-timeout.png +0 -0
  238. package/screenshots/truth/nodes/wait_for_response/editor/short-timeout.png +0 -0
  239. package/screenshots/truth/nodes/wait_for_response/render/basic-wait.png +0 -0
  240. package/screenshots/truth/nodes/wait_for_response/render/custom-result-name.png +0 -0
  241. package/screenshots/truth/nodes/wait_for_response/render/no-timeout.png +0 -0
  242. package/screenshots/truth/nodes/wait_for_response/render/short-timeout.png +0 -0
  243. package/src/display/FloatingTab.ts +1 -1
  244. package/src/flow/CanvasNode.ts +1 -1
  245. package/src/flow/Editor.ts +299 -88
  246. package/src/flow/Plumber.ts +89 -14
  247. package/src/flow/actions/add_contact_groups.ts +4 -1
  248. package/src/flow/actions/add_input_labels.ts +4 -1
  249. package/src/flow/actions/remove_contact_groups.ts +6 -1
  250. package/src/flow/actions/send_broadcast.ts +6 -2
  251. package/src/flow/actions/set_contact_channel.ts +13 -1
  252. package/src/flow/actions/set_contact_status.ts +7 -5
  253. package/src/flow/actions/start_session.ts +10 -3
  254. package/src/flow/nodes/split_by_contact_field.ts +16 -5
  255. package/src/flow/nodes/split_by_expression.ts +1 -1
  256. package/src/flow/nodes/split_by_llm_categorize.ts +0 -1
  257. package/src/flow/nodes/split_by_random.ts +0 -1
  258. package/src/flow/nodes/split_by_run_result.ts +10 -4
  259. package/src/flow/nodes/wait_for_digits.ts +2 -1
  260. package/src/flow/nodes/wait_for_response.ts +1 -1
  261. package/src/form/FieldRenderer.ts +7 -0
  262. package/src/layout/Dialog.ts +0 -1
  263. package/src/layout/Modax.ts +19 -2
  264. package/src/list/ContentMenu.ts +15 -1
  265. package/src/live/ContactChat.ts +10 -1
  266. package/src/live/TembaChart.ts +1 -1
  267. package/src/locales/es.ts +18 -13
  268. package/src/locales/fr.ts +18 -13
  269. package/src/locales/locale-codes.ts +11 -2
  270. package/src/locales/pt.ts +18 -13
  271. package/src/simulator/Simulator.ts +12 -0
  272. package/src/store/AppState.ts +15 -0
  273. package/src/store/flow-definition.d.ts +1 -0
  274. package/src/version.ts +10 -0
  275. package/test/actions/add_contact_groups.test.ts +38 -0
  276. package/test/actions/add_input_labels.test.ts +67 -0
  277. package/test/actions/enter_flow.test.ts +88 -0
  278. package/test/actions/remove_contact_groups.test.ts +29 -0
  279. package/test/actions/send_broadcast.test.ts +44 -0
  280. package/test/actions/set_contact_channel.test.ts +88 -0
  281. package/test/actions/set_contact_field.test.ts +68 -0
  282. package/test/actions/set_contact_language.test.ts +55 -0
  283. package/test/actions/set_contact_name.test.ts +39 -0
  284. package/test/actions/set_contact_status.test.ts +64 -0
  285. package/test/actions/set_run_result.test.ts +61 -0
  286. package/test/actions/start_session.test.ts +82 -0
  287. package/test/nodes/split_by_contact_field.test.ts +59 -0
  288. package/test/nodes/split_by_run_result.test.ts +100 -0
  289. package/test/nodes/split_by_ticket.test.ts +157 -0
  290. package/test/nodes/split_by_webhook.test.ts +131 -0
  291. package/test/temba-contact-chat.test.ts +17 -0
  292. package/test/temba-flow-editor.test.ts +264 -0
  293. package/test/temba-flow-plumber.test.ts +62 -0
  294. package/test/temba-select.test.ts +6 -1
  295. package/test/utils.test.ts +4 -2
  296. package/web-dev-server.config.mjs +5 -1
  297. package/web-test-runner.config.mjs +4 -1
@@ -8,6 +8,7 @@ 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 { TEMBA_COMPONENTS_VERSION } from '../version';
11
12
  import { formatIssueMessage, getNodeBounds, calculateReflowPositions, snapToGrid } from './utils';
12
13
  import { ACTION_CONFIG, NODE_CONFIG } from './config';
13
14
  import { ACTION_GROUP_METADATA } from './types';
@@ -22,7 +23,7 @@ export function findNodeForExit(definition, exitUuid) {
22
23
  }
23
24
  return null;
24
25
  }
25
- const SAVE_QUIET_TIME = 500;
26
+ const SAVE_QUIET_TIME = 2000;
26
27
  const DRAG_THRESHOLD = 5;
27
28
  const AUTO_TRANSLATE_MODELS_ENDPOINT = '/api/internal/llms.json';
28
29
  // Offset for positioning dropped action node relative to mouse cursor
@@ -68,6 +69,13 @@ export class Editor extends RapidElement {
68
69
  }
69
70
  static get styles() {
70
71
  return css `
72
+ #editor-container {
73
+ position: relative;
74
+ flex: 1;
75
+ display: flex;
76
+ min-height: 0;
77
+ }
78
+
71
79
  #editor {
72
80
  overflow: scroll;
73
81
  flex: 1;
@@ -546,6 +554,71 @@ export class Editor extends RapidElement {
546
554
  color: tomato;
547
555
  flex-shrink: 0;
548
556
  }
557
+
558
+ .empty-flow {
559
+ position: sticky;
560
+ top: 80px;
561
+ left: 0;
562
+ right: 0;
563
+ height: 0;
564
+ display: flex;
565
+ justify-content: center;
566
+ pointer-events: none;
567
+ z-index: 50;
568
+ }
569
+
570
+ .empty-flow-content {
571
+ display: flex;
572
+ flex-direction: column;
573
+ align-items: center;
574
+ gap: 16px;
575
+ text-align: center;
576
+ pointer-events: auto;
577
+ }
578
+
579
+ .empty-flow-title {
580
+ font-size: 18px;
581
+ font-weight: 600;
582
+ color: #374151;
583
+ }
584
+
585
+ .empty-flow-description {
586
+ font-size: 14px;
587
+ color: #6b7280;
588
+ max-width: 320px;
589
+ line-height: 1.5;
590
+ }
591
+
592
+ .empty-flow-button {
593
+ background: var(--color-primary-dark);
594
+ border: none;
595
+ color: #fff;
596
+ padding: 10px 20px;
597
+ border-radius: var(--curvature);
598
+ font-size: 14px;
599
+ font-weight: 600;
600
+ cursor: pointer;
601
+ transition: opacity 0.2s ease;
602
+ }
603
+
604
+ .empty-flow-button:hover {
605
+ opacity: 0.9;
606
+ }
607
+
608
+ .save-indicator {
609
+ position: absolute;
610
+ top: 8px;
611
+ right: 16px;
612
+ padding: 6px 10px;
613
+ z-index: 10000;
614
+ pointer-events: none;
615
+ opacity: 0;
616
+ transition: opacity 0.15s ease-in-out;
617
+ }
618
+
619
+ .save-indicator.visible {
620
+ opacity: 1;
621
+ }
549
622
  `;
550
623
  }
551
624
  constructor() {
@@ -588,6 +661,8 @@ export class Editor extends RapidElement {
588
661
  this.revisions = [];
589
662
  this.viewingRevision = null;
590
663
  this.isLoadingRevisions = false;
664
+ this.isSaving = false;
665
+ this.saveError = null;
591
666
  this.preRevertState = null;
592
667
  this.translationCache = new Map();
593
668
  // NodeEditor state - handles both node and action editing
@@ -622,6 +697,7 @@ export class Editor extends RapidElement {
622
697
  this.setupGlobalEventListeners();
623
698
  if (changes.has('flow')) {
624
699
  getStore().getState().fetchRevision(`/flow/revisions/${this.flow}`);
700
+ this.fetchRevisions();
625
701
  }
626
702
  this.plumber.on('connection:drag', (connection) => {
627
703
  this.dragFromNodeId = connection.data.nodeId;
@@ -749,9 +825,14 @@ export class Editor extends RapidElement {
749
825
  }
750
826
  if (changes.has('dirtyDate')) {
751
827
  if (this.dirtyDate) {
828
+ this.isSaving = true;
752
829
  this.debouncedSave();
753
830
  }
754
831
  }
832
+ if (changes.has('saveError') && this.saveError) {
833
+ this.showSaveErrorDialog(this.saveError);
834
+ this.saveError = null;
835
+ }
755
836
  if (changes.has('languageCode')) {
756
837
  this.translationCache.clear();
757
838
  }
@@ -791,13 +872,26 @@ export class Editor extends RapidElement {
791
872
  }
792
873
  }, SAVE_QUIET_TIME);
793
874
  }
875
+ definitionForSave(definition) {
876
+ return {
877
+ ...definition,
878
+ _ui: {
879
+ ...definition._ui,
880
+ editor: TEMBA_COMPONENTS_VERSION
881
+ }
882
+ };
883
+ }
794
884
  saveChanges(definitionOverride) {
795
- const definition = definitionOverride || this.definition;
796
- // post the flow definition to the server
885
+ const definition = this.definitionForSave(definitionOverride || this.definition);
886
+ this.isSaving = true;
797
887
  return getStore()
798
888
  .postJSON(`/flow/revisions/${this.flow}/`, definition)
799
889
  .then((response) => {
800
890
  var _b;
891
+ if (response.status < 200 || response.status >= 300) {
892
+ this.saveError = this.extractErrorMessage(response);
893
+ return;
894
+ }
801
895
  // Update flow info and revision with the response data
802
896
  if (response.json) {
803
897
  const state = getStore().getState();
@@ -807,16 +901,53 @@ export class Editor extends RapidElement {
807
901
  if (((_b = response.json.revision) === null || _b === void 0 ? void 0 : _b.revision) !== undefined) {
808
902
  state.setRevision(response.json.revision.revision);
809
903
  }
810
- // if the revisions window is open, refresh the list
811
- if (!this.revisionsWindowHidden) {
812
- this.fetchRevisions();
813
- }
904
+ // Refresh revisions list so the tab visibility stays up to date
905
+ this.fetchRevisions();
814
906
  }
907
+ getStore().getState().setDirtyDate(null);
815
908
  })
816
909
  .catch((error) => {
817
910
  console.error('Failed to save flow:', error);
911
+ if (error instanceof Response) {
912
+ this.saveError = `Server error (${error.status}). Your changes have not been saved.`;
913
+ }
914
+ else {
915
+ this.saveError =
916
+ 'Unable to reach the server. Please check your connection and try again.';
917
+ }
918
+ })
919
+ .finally(() => {
920
+ this.isSaving = false;
921
+ });
922
+ }
923
+ extractErrorMessage(response) {
924
+ if (response.json) {
925
+ if (typeof response.json.detail === 'string') {
926
+ return response.json.detail;
927
+ }
928
+ if (typeof response.json.error === 'string') {
929
+ return response.json.error;
930
+ }
931
+ if (typeof response.json.description === 'string') {
932
+ return response.json.description;
933
+ }
934
+ }
935
+ return `Save failed with status ${response.status}.`;
936
+ }
937
+ showSaveErrorDialog(message) {
938
+ const dialog = document.createElement('temba-dialog');
939
+ dialog.header = 'Save Failed';
940
+ dialog.primaryButtonName = '';
941
+ dialog.cancelButtonName = 'Dismiss';
942
+ const content = document.createElement('div');
943
+ content.style.cssText = 'padding: 20px; font-size: 14px; line-height: 1.5;';
944
+ content.textContent = message;
945
+ dialog.appendChild(content);
946
+ document.body.appendChild(dialog);
947
+ dialog.open = true;
948
+ dialog.addEventListener('temba-dialog-hidden', () => {
949
+ document.body.removeChild(dialog);
818
950
  });
819
- getStore().getState().setDirtyDate(null);
820
951
  }
821
952
  startActivityFetching() {
822
953
  // Don't start if simulator is active
@@ -848,6 +979,10 @@ export class Editor extends RapidElement {
848
979
  }
849
980
  const state = store.getState();
850
981
  state.fetchActivity(activityEndpoint).then(() => {
982
+ // Guard against responses arriving after the editor is disconnected
983
+ if (!this.isConnected) {
984
+ return;
985
+ }
851
986
  // Schedule next fetch with exponential backoff (max 5 minutes)
852
987
  this.activityInterval = Math.min(60000 * 5, this.activityInterval + 100);
853
988
  if (this.activityTimer !== null) {
@@ -879,6 +1014,9 @@ export class Editor extends RapidElement {
879
1014
  if (canvas) {
880
1015
  canvas.removeEventListener('contextmenu', this.boundCanvasContextMenu);
881
1016
  }
1017
+ // Clear all flow-specific data from the store so stale data
1018
+ // isn't briefly visible when a different flow is opened.
1019
+ zustand.getState().clearFlowData();
882
1020
  }
883
1021
  setupGlobalEventListeners() {
884
1022
  document.addEventListener('mousemove', this.boundMouseMove);
@@ -1577,6 +1715,25 @@ export class Editor extends RapidElement {
1577
1715
  });
1578
1716
  }
1579
1717
  }
1718
+ handleEmptyFlowClick(event) {
1719
+ const editor = this.querySelector('#editor');
1720
+ if (!editor)
1721
+ return;
1722
+ // Scroll to top-left
1723
+ editor.scrollTo({ left: 0, top: 0, behavior: 'smooth' });
1724
+ // Place node at top-left of the canvas
1725
+ const nodeLeft = 0;
1726
+ const nodeTop = 0;
1727
+ const canvasMenu = this.querySelector('temba-canvas-menu');
1728
+ if (canvasMenu) {
1729
+ const button = event.currentTarget;
1730
+ const rect = button.getBoundingClientRect();
1731
+ const menuWidth = 265;
1732
+ const menuX = rect.left + rect.width / 2 - menuWidth / 2;
1733
+ const menuY = rect.bottom + 8;
1734
+ canvasMenu.show(menuX, menuY, { x: nodeLeft, y: nodeTop }, false);
1735
+ }
1736
+ }
1580
1737
  handleCanvasMenuSelection(event) {
1581
1738
  const selection = event.detail;
1582
1739
  const store = getStore();
@@ -2647,7 +2804,7 @@ export class Editor extends RapidElement {
2647
2804
  }
2648
2805
  renderIssuesTab() {
2649
2806
  var _b;
2650
- if (!((_b = this.flowIssues) === null || _b === void 0 ? void 0 : _b.length))
2807
+ if (!((_b = this.flowIssues) === null || _b === void 0 ? void 0 : _b.length) || !this.revisionsWindowHidden)
2651
2808
  return '';
2652
2809
  return html `
2653
2810
  <temba-floating-tab
@@ -2691,6 +2848,8 @@ export class Editor extends RapidElement {
2691
2848
  `;
2692
2849
  }
2693
2850
  renderRevisionsTab() {
2851
+ if (this.revisions.length <= 1)
2852
+ return '';
2694
2853
  return html `
2695
2854
  <temba-floating-tab
2696
2855
  id="revisions-tab"
@@ -2958,6 +3117,11 @@ export class Editor extends RapidElement {
2958
3117
  `;
2959
3118
  }
2960
3119
  renderLocalizationTab() {
3120
+ var _b;
3121
+ if (!this.revisionsWindowHidden)
3122
+ return '';
3123
+ if (((_b = this.definition) === null || _b === void 0 ? void 0 : _b.nodes.length) === 0)
3124
+ return '';
2961
3125
  const languages = this.getLocalizationLanguages();
2962
3126
  if (!languages.length) {
2963
3127
  return html ``;
@@ -3018,21 +3182,41 @@ export class Editor extends RapidElement {
3018
3182
  return html `${style} ${this.renderIssuesWindow()}
3019
3183
  ${this.renderRevisionsWindow()} ${this.renderLocalizationWindow()}
3020
3184
  ${this.renderAutoTranslateDialog()}
3021
- <div id="editor">
3022
- <div
3023
- id="grid"
3024
- class="${this.viewingRevision ? 'viewing-revision' : ''}"
3025
- style="min-width:100%;width:${this.canvasSize.width}px; height:${this
3026
- .canvasSize.height}px"
3027
- >
3185
+ <div id="editor-container">
3186
+ <div id="editor">
3187
+ ${this.definition &&
3188
+ this.definition.nodes.length === 0 &&
3189
+ !this.isReadOnly()
3190
+ ? html `<div class="empty-flow">
3191
+ <div class="empty-flow-content">
3192
+ <div class="empty-flow-title">This flow is empty</div>
3193
+ <div class="empty-flow-description">
3194
+ Get started by adding your first action or split to define
3195
+ how this flow will work.
3196
+ </div>
3197
+ <button
3198
+ class="empty-flow-button"
3199
+ @click=${this.handleEmptyFlowClick}
3200
+ >
3201
+ Add first step
3202
+ </button>
3203
+ </div>
3204
+ </div>`
3205
+ : ''}
3028
3206
  <div
3029
- id="canvas"
3030
- class="${getClasses({
3207
+ id="grid"
3208
+ class="${this.viewingRevision ? 'viewing-revision' : ''}"
3209
+ style="min-width:100%;width:${this.canvasSize
3210
+ .width}px; height:${this.canvasSize.height}px"
3211
+ >
3212
+ <div
3213
+ id="canvas"
3214
+ class="${getClasses({
3031
3215
  'viewing-revision': !!this.viewingRevision,
3032
3216
  'read-only-connections': !!this.viewingRevision || this.isTranslating
3033
3217
  })}"
3034
- >
3035
- ${this.definition
3218
+ >
3219
+ ${this.definition
3036
3220
  ? repeat([...this.definition.nodes].sort((a, b) => a.uuid.localeCompare(b.uuid)), (node) => node.uuid, (node) => {
3037
3221
  var _b, _c, _d;
3038
3222
  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) || {
@@ -3046,43 +3230,49 @@ export class Editor extends RapidElement {
3046
3230
  const isFlowStart = this.definition.nodes.length > 0 &&
3047
3231
  this.definition.nodes[0].uuid === node.uuid;
3048
3232
  return html `<temba-flow-node
3049
- class="draggable ${dragging ? 'dragging' : ''} ${selected
3050
- ? 'selected'
3051
- : ''} ${isFlowStart ? 'flow-start' : ''}"
3052
- @mousedown=${this.handleMouseDown.bind(this)}
3053
- uuid=${node.uuid}
3054
- data-node-uuid=${node.uuid}
3055
- style="left:${position.left}px; top:${position.top}px;transition: all 0.2s ease-in-out;"
3056
- .plumber=${this.plumber}
3057
- .node=${node}
3058
- .ui=${this.definition._ui.nodes[node.uuid]}
3059
- @temba-node-deleted=${(event) => {
3233
+ class="draggable ${dragging
3234
+ ? 'dragging'
3235
+ : ''} ${selected ? 'selected' : ''} ${isFlowStart
3236
+ ? 'flow-start'
3237
+ : ''}"
3238
+ @mousedown=${this.handleMouseDown.bind(this)}
3239
+ uuid=${node.uuid}
3240
+ data-node-uuid=${node.uuid}
3241
+ style="left:${position.left}px; top:${position.top}px;transition: all 0.2s ease-in-out;"
3242
+ .plumber=${this.plumber}
3243
+ .node=${node}
3244
+ .ui=${this.definition._ui.nodes[node.uuid]}
3245
+ @temba-node-deleted=${(event) => {
3060
3246
  this.deleteNodes([event.detail.uuid]);
3061
3247
  }}
3062
- ></temba-flow-node>`;
3248
+ ></temba-flow-node>`;
3063
3249
  })
3064
3250
  : html `<temba-loading></temba-loading>`}
3065
- ${repeat(Object.entries(stickies), ([uuid]) => uuid, ([uuid, sticky]) => {
3251
+ ${repeat(Object.entries(stickies), ([uuid]) => uuid, ([uuid, sticky]) => {
3066
3252
  var _b;
3067
3253
  const position = sticky.position || { left: 0, top: 0 };
3068
3254
  const dragging = this.isDragging && ((_b = this.currentDragItem) === null || _b === void 0 ? void 0 : _b.uuid) === uuid;
3069
3255
  const selected = this.selectedItems.has(uuid);
3070
3256
  return html `<temba-sticky-note
3071
- class="draggable ${dragging ? 'dragging' : ''} ${selected
3257
+ class="draggable ${dragging ? 'dragging' : ''} ${selected
3072
3258
  ? 'selected'
3073
3259
  : ''}"
3074
- @mousedown=${this.handleMouseDown.bind(this)}
3075
- style="left:${position.left}px; top:${position.top}px;"
3076
- uuid=${uuid}
3077
- .data=${sticky}
3078
- .dragging=${dragging}
3079
- .selected=${selected}
3080
- ></temba-sticky-note>`;
3260
+ @mousedown=${this.handleMouseDown.bind(this)}
3261
+ style="left:${position.left}px; top:${position.top}px;"
3262
+ uuid=${uuid}
3263
+ .data=${sticky}
3264
+ .dragging=${dragging}
3265
+ .selected=${selected}
3266
+ ></temba-sticky-note>`;
3081
3267
  })}
3082
- ${this.renderSelectionBox()} ${this.renderCanvasDropPreview()}
3083
- ${this.renderConnectionPlaceholder()}
3268
+ ${this.renderSelectionBox()} ${this.renderCanvasDropPreview()}
3269
+ ${this.renderConnectionPlaceholder()}
3270
+ </div>
3084
3271
  </div>
3085
3272
  </div>
3273
+ <div class="save-indicator ${this.isSaving ? 'visible' : ''}">
3274
+ <temba-loading units="3" size="8"></temba-loading>
3275
+ </div>
3086
3276
  </div>
3087
3277
 
3088
3278
  ${this.editingNode || this.editingAction
@@ -3213,6 +3403,12 @@ __decorate([
3213
3403
  __decorate([
3214
3404
  state()
3215
3405
  ], Editor.prototype, "isLoadingRevisions", void 0);
3406
+ __decorate([
3407
+ state()
3408
+ ], Editor.prototype, "isSaving", void 0);
3409
+ __decorate([
3410
+ state()
3411
+ ], Editor.prototype, "saveError", void 0);
3216
3412
  __decorate([
3217
3413
  state()
3218
3414
  ], Editor.prototype, "editingNode", void 0);