@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
@@ -1 +1 @@
1
- {"version":3,"file":"StickyNote.js","sourceRoot":"","sources":["../../../src/flow/StickyNote.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,GAAG,EAAE,IAAI,EAAoC,MAAM,KAAK,CAAC;AAClE,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAE/C,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,EAAY,SAAS,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAEjE,MAAM,OAAO,UAAW,SAAQ,YAAY;IAA5C;;QAQU,aAAQ,GAAG,KAAK,CAAC;QAGlB,aAAQ,GAAG,KAAK,CAAC;QAGhB,wBAAmB,GAAG,KAAK,CAAC;IA2atC,CAAC;IAtaC,MAAM,KAAK,MAAM;QACf,OAAO,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KA0OT,CAAC;IACJ,CAAC;IAES,OAAO,CACf,OAA0D;QAE1D,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QACvB,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;YAC/C,IAAI,CAAC,gBAAgB,EAAE,CAAC;QAC1B,CAAC;IACH,CAAC;IAEO,gBAAgB;QACtB,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;YACf,OAAO;QACT,CAAC;QAED,MAAM,OAAO,GAAG,IAAI,CAAC,aAAa,CAAC,cAAc,CAAC,CAAC;QACnD,IAAI,OAAO,EAAE,CAAC;YACZ,MAAM,IAAI,GAAG,OAAO,CAAC,qBAAqB,EAAE,CAAC;YAC7C,QAAQ,EAAE;iBACP,QAAQ,EAAE;iBACV,YAAY,CACX,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,GAAG,IAAI,CAAC,KAAK,EACpC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,GAAG,IAAI,CAAC,MAAM,CACrC,CAAC;QACN,CAAC;IACH,CAAC;IAEO,eAAe,CAAC,KAAiB;QACvC,MAAM,MAAM,GAAG,KAAK,CAAC,MAAqB,CAAC;QAC3C,MAAM,QAAQ,GAAG,MAAM,CAAC,WAAW,IAAI,EAAE,CAAC;QAE1C,IAAI,IAAI,CAAC,IAAI,IAAI,QAAQ,KAAK,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;YAC9C,QAAQ,EAAE;iBACP,QAAQ,EAAE;iBACV,gBAAgB,CAAC,IAAI,CAAC,IAAI,EAAE;gBAC3B,GAAG,IAAI,CAAC,IAAI;gBACZ,KAAK,EAAE,QAAQ;aAChB,CAAC,CAAC;QACP,CAAC;QACD,IAAI,CAAC,aAAa,EAAE,CAAC;IACvB,CAAC;IAEO,cAAc,CAAC,KAAiB;QACtC,MAAM,MAAM,GAAG,KAAK,CAAC,MAAqB,CAAC;QAC3C,MAAM,OAAO,GAAG,MAAM,CAAC,WAAW,IAAI,EAAE,CAAC;QAEzC,IAAI,IAAI,CAAC,IAAI,IAAI,OAAO,KAAK,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;YAC5C,QAAQ,EAAE;iBACP,QAAQ,EAAE;iBACV,gBAAgB,CAAC,IAAI,CAAC,IAAI,EAAE;gBAC3B,GAAG,IAAI,CAAC,IAAI;gBACZ,IAAI,EAAE,OAAO;aACd,CAAC,CAAC;QACP,CAAC;QACD,IAAI,CAAC,aAAa,EAAE,CAAC;IACvB,CAAC;IAEO,sBAAsB,CAAC,KAAiB;QAC9C,0DAA0D;QAC1D,kCAAkC;QAClC,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAClB,OAAO;QACT,CAAC;QACD,gDAAgD;QAChD,KAAK,CAAC,eAAe,EAAE,CAAC;IAC1B,CAAC;IAEO,aAAa,CAAC,KAAoB;QACxC,IAAI,KAAK,CAAC,GAAG,KAAK,OAAO,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC;YAC7C,KAAK,CAAC,cAAc,EAAE,CAAC;YACtB,KAAK,CAAC,MAAsB,CAAC,IAAI,EAAE,CAAC;QACvC,CAAC;QACD,IAAI,KAAK,CAAC,GAAG,KAAK,QAAQ,EAAE,CAAC;YAC1B,KAAK,CAAC,MAAsB,CAAC,IAAI,EAAE,CAAC;QACvC,CAAC;IACH,CAAC;IAEO,2BAA2B;QACjC,IAAI,CAAC,mBAAmB,GAAG,IAAI,CAAC;IAClC,CAAC;IAEO,2BAA2B;QACjC,IAAI,CAAC,mBAAmB,GAAG,KAAK,CAAC;IACnC,CAAC;IAEO,sBAAsB,CAC5B,KAAiB,EACjB,KAAoD;QAEpD,KAAK,CAAC,eAAe,EAAE,CAAC;QAExB,IAAI,IAAI,CAAC,IAAI,IAAI,KAAK,KAAK,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;YAC3C,QAAQ,EAAE;iBACP,QAAQ,EAAE;iBACV,gBAAgB,CAAC,IAAI,CAAC,IAAI,EAAE;gBAC3B,GAAG,IAAI,CAAC,IAAI;gBACZ,KAAK,EAAE,KAAK;aACb,CAAC,CAAC;QACP,CAAC;QAED,IAAI,CAAC,mBAAmB,GAAG,KAAK,CAAC;QACjC,IAAI,CAAC,aAAa,EAAE,CAAC;IACvB,CAAC;IAEM,MAAM;QACX,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;YACf,OAAO,IAAI,CAAA,wDAAwD,CAAC;QACtE,CAAC;QAED,MAAM,KAAK,GAAG,SAAS,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,YAAY,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,KAAK,CAAC;QAEtF,OAAO,IAAI,CAAA;;6BAEc,IAAI,CAAC,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,QAAQ;YACnD,CAAC,CAAC,UAAU;YACZ,CAAC,CAAC,EAAE;iBACG,KAAK;qBACD,IAAI,CAAC,IAAI;;;;;;+BAMC,CAAC,IAAI,CAAC,aAAa;qBAC7B,IAAI,CAAC,eAAe;wBACjB,IAAI,CAAC,aAAa;0BAChB,IAAI,CAAC,sBAAsB;4BACzB,IAAI,CAAC,IAAI,CAAC,KAAK;;;;;;+BAMZ,CAAC,IAAI,CAAC,aAAa;qBAC7B,IAAI,CAAC,cAAc;wBAChB,IAAI,CAAC,aAAa;0BAChB,IAAI,CAAC,sBAAsB;4BACzB,IAAI,CAAC,IAAI,CAAC,IAAI;;YAE9B,CAAC,IAAI,CAAC,aAAa;YACnB,CAAC,CAAC,IAAI,CAAA,iDAAiD;YACvD,CAAC,CAAC,EAAE;;;;;2BAKW,IAAI,CAAC,2BAA2B;2BAChC,IAAI,CAAC,2BAA2B;;;qCAGtB,IAAI,CAAC,mBAAmB;YAC7C,CAAC,CAAC,UAAU;YACZ,CAAC,CAAC,EAAE;;;;0BAIM,CAAC,CAAa,EAAE,EAAE,CAC1B,IAAI,CAAC,sBAAsB,CAAC,CAAC,EAAE,QAAQ,CAAC;;;;0BAIhC,CAAC,CAAa,EAAE,EAAE,CAC1B,IAAI,CAAC,sBAAsB,CAAC,CAAC,EAAE,MAAM,CAAC;;;;0BAI9B,CAAC,CAAa,EAAE,EAAE,CAC1B,IAAI,CAAC,sBAAsB,CAAC,CAAC,EAAE,MAAM,CAAC;;;;0BAI9B,CAAC,CAAa,EAAE,EAAE,CAC1B,IAAI,CAAC,sBAAsB,CAAC,CAAC,EAAE,OAAO,CAAC;;;;0BAI/B,CAAC,CAAa,EAAE,EAAE,CAC1B,IAAI,CAAC,sBAAsB,CAAC,CAAC,EAAE,MAAM,CAAC;;;;;;KAMnD,CAAC;IACJ,CAAC;CACF;AAvbQ;IADN,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;wCACP;AAGb;IADN,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;wCACC;AAGpB;IADP,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;4CACH;AAGlB;IADN,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;4CACJ;AAGhB;IADP,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;uDACQ;AAG5B;IADP,SAAS,CAAC,OAAO,EAAE,CAAC,KAAe,EAAE,EAAE,CAAC,KAAK,CAAC,aAAa,CAAC;iDAC7B","sourcesContent":["import { css, html, PropertyValueMap, TemplateResult } from 'lit';\nimport { property } from 'lit/decorators.js';\nimport { RapidElement } from '../RapidElement';\nimport { StickyNote as StickyNoteData } from '../store/flow-definition';\nimport { getStore } from '../store/Store';\nimport { AppState, fromStore, zustand } from '../store/AppState';\n\nexport class StickyNote extends RapidElement {\n @property({ type: String })\n public uuid: string;\n\n @property({ type: Object })\n public data: StickyNoteData;\n\n @property({ type: Boolean })\n private dragging = false;\n\n @property({ type: Boolean })\n public selected = false;\n\n @property({ type: Boolean })\n private colorPickerExpanded = false;\n\n @fromStore(zustand, (state: AppState) => state.isTranslating)\n private isTranslating!: boolean;\n\n static get styles() {\n return css`\n :host {\n --sticky-color: #fef08a;\n --sticky-border-color: #facc15;\n --sticky-text-color: #451a03;\n --curvature: 8px;\n }\n\n .sticky-note {\n width: 182px;\n background-color: var(--sticky-color);\n border: 1px solid var(--sticky-border-color);\n border-radius: var(--curvature);\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,\n sans-serif;\n font-size: 12px;\n overflow: hidden;\n transition: transform 0.1s ease, box-shadow 0.2s ease;\n color: var(--sticky-text-color);\n opacity: 0.85;\n }\n\n .sticky-note.dragging {\n opacity: 0.7;\n z-index: 1000;\n transform: rotate(0deg);\n box-shadow: 0 8px 20px rgba(0, 0, 0, 0.3);\n }\n\n .sticky-note:hover {\n transform: translateY(0px);\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);\n }\n\n /* Color themes */\n .sticky-note.yellow {\n --sticky-color: #fef08a;\n --sticky-border-color: #facc15;\n --sticky-text-color: #451a03;\n }\n .sticky-note.blue {\n --sticky-color: #bfdbfe;\n --sticky-border-color: #3b82f6;\n --sticky-text-color: #1e3a8a;\n }\n .sticky-note.pink {\n --sticky-color: #fce7f3;\n --sticky-border-color: #ec4899;\n --sticky-text-color: #831843;\n }\n .sticky-note.green {\n --sticky-color: #d1fae5;\n --sticky-border-color: #10b981;\n --sticky-text-color: #064e3b;\n }\n .sticky-note.gray {\n --sticky-color: #f3f4f6;\n --sticky-border-color: #6b7280;\n --sticky-text-color: #374151;\n }\n\n /* Title and body containers */\n .sticky-title-container {\n position: relative;\n border-bottom: 1px solid var(--sticky-border-color);\n background-color: rgba(255, 255, 255, 0.5);\n display: flex;\n align-items: center;\n }\n .sticky-body-container {\n position: relative;\n }\n\n /* Editable fields */\n [contenteditable='true'] {\n margin: 2px;\n padding: 4px 8px;\n outline: none;\n border-radius: var(--curvature);\n transition: background 0.2s;\n }\n [contenteditable='true']:focus {\n background-color: rgba(255, 255, 255, 0.8);\n border-bottom: 1px solid rgba(0, 0, 0, 0.1);\n outline-color: var(--sticky-border-color);\n }\n\n /* Title */\n .sticky-title {\n font-weight: 600;\n font-size: 13px;\n color: var(--sticky-text-color);\n min-height: 20px;\n line-height: 20px;\n border-top-left-radius: var(--curvature);\n border-top-right-radius: var(--curvature);\n flex-grow: 1;\n padding: 4px 8px !important;\n margin: 2px;\n padding-left: 8px;\n }\n .sticky-title:empty::before {\n content: 'Click to add title';\n opacity: 0.5;\n font-style: italic;\n }\n .sticky-title:focus {\n border-bottom-left-radius: 0px;\n border-bottom-right-radius: 0px;\n }\n\n /* Body */\n .sticky-body {\n padding: 8px 10px;\n color: var(--sticky-text-color);\n line-height: 1.4;\n min-height: 48px;\n word-wrap: break-word;\n white-space: pre-wrap;\n margin: 2px;\n }\n .sticky-body:empty::before {\n content: 'Click to add note';\n opacity: 0.5;\n font-style: italic;\n }\n .sticky-body:focus {\n border-top-left-radius: 0px;\n border-top-right-radius: 0px;\n }\n\n /* Drag icon */\n .sticky-title-container > .drag-handle {\n --icon-color: var(--sticky-border-color);\n cursor: move;\n max-width: 20px;\n padding-left: 8px;\n overflow: hidden;\n transition: all 0.2s ease;\n }\n\n .sticky-note:hover .drag-handle {\n }\n\n .sticky-note:focus-within .sticky-title-container > .drag-handle {\n }\n\n /* Focus/active states */\n .sticky-note:focus-within {\n box-shadow: 0 0 0 1px var(--sticky-border-color),\n 0 10px 20px rgba(0, 0, 0, 0.3);\n }\n\n .sticky-note:focus-within .drag-handle {\n max-width: 0px;\n padding-left: 0px;\n }\n\n /* Color picker */\n .color-picker {\n position: absolute;\n bottom: 4px;\n right: 4px;\n width: 8px;\n height: 8px;\n border: 1px solid rgba(0, 0, 0, 0.2);\n\n border-radius: 3px;\n background-color: var(--sticky-color);\n cursor: pointer;\n transition: transform 0.2s ease;\n }\n\n .color-picker:hover {\n transform: scale(1.1);\n }\n\n .color-options {\n position: absolute;\n bottom: 0;\n right: 0;\n display: flex;\n gap: 4px;\n background-color: rgba(255, 255, 255, 0.9);\n border: 1px solid #ccc;\n border-radius: 6px;\n padding: 3px;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);\n transform-origin: bottom right;\n transform: scale(0);\n opacity: 0;\n transition: transform 0.2s ease, opacity 0.2s ease;\n z-index: 1000;\n }\n\n .color-options.expanded {\n transform: scale(1);\n opacity: 1;\n }\n\n .color-option {\n width: 12px;\n height: 12px;\n border: 1px solid rgba(0, 0, 0, 0.2);\n border-radius: 3px;\n cursor: pointer;\n transition: transform 0.15s ease, border-color 0.15s ease;\n }\n\n .color-option:hover {\n transform: scale(1.1);\n border-color: rgba(0, 0, 0, 0.4);\n }\n\n .color-option.yellow {\n background-color: #fef08a;\n }\n\n .color-option.blue {\n background-color: #bfdbfe;\n }\n\n .color-option.pink {\n background-color: #fce7f3;\n }\n\n .color-option.green {\n background-color: #d1fae5;\n }\n\n .color-option.gray {\n background-color: #f3f4f6;\n }\n `;\n }\n\n protected updated(\n changes: PropertyValueMap<any> | Map<PropertyKey, unknown>\n ): void {\n super.updated(changes);\n if (changes.has('data') || changes.has('uuid')) {\n this.updateCanvasSize();\n }\n }\n\n private updateCanvasSize(): void {\n if (!this.data) {\n return;\n }\n\n const element = this.querySelector('.sticky-note');\n if (element) {\n const rect = element.getBoundingClientRect();\n getStore()\n .getState()\n .expandCanvas(\n this.data.position.left + rect.width,\n this.data.position.top + rect.height\n );\n }\n }\n\n private handleTitleBlur(event: FocusEvent): void {\n const target = event.target as HTMLElement;\n const newTitle = target.textContent || '';\n\n if (this.data && newTitle !== this.data.title) {\n getStore()\n .getState()\n .updateStickyNote(this.uuid, {\n ...this.data,\n title: newTitle\n });\n }\n this.requestUpdate();\n }\n\n private handleBodyBlur(event: FocusEvent): void {\n const target = event.target as HTMLElement;\n const newBody = target.textContent || '';\n\n if (this.data && newBody !== this.data.body) {\n getStore()\n .getState()\n .updateStickyNote(this.uuid, {\n ...this.data,\n body: newBody\n });\n }\n this.requestUpdate();\n }\n\n private handleContentMouseDown(event: MouseEvent): void {\n // If this sticky note is selected, don't stop propagation\n // so that group dragging can work\n if (this.selected) {\n return;\n }\n // Otherwise, stop propagation to enable editing\n event.stopPropagation();\n }\n\n private handleKeyDown(event: KeyboardEvent): void {\n if (event.key === 'Enter' && !event.shiftKey) {\n event.preventDefault();\n (event.target as HTMLElement).blur();\n }\n if (event.key === 'Escape') {\n (event.target as HTMLElement).blur();\n }\n }\n\n private handleColorPickerMouseEnter(): void {\n this.colorPickerExpanded = true;\n }\n\n private handleColorPickerMouseLeave(): void {\n this.colorPickerExpanded = false;\n }\n\n private handleColorOptionClick(\n event: MouseEvent,\n color: 'yellow' | 'blue' | 'pink' | 'green' | 'gray'\n ): void {\n event.stopPropagation();\n\n if (this.data && color !== this.data.color) {\n getStore()\n .getState()\n .updateStickyNote(this.uuid, {\n ...this.data,\n color: color\n });\n }\n\n this.colorPickerExpanded = false;\n this.requestUpdate();\n }\n\n public render(): TemplateResult {\n if (!this.data) {\n return html`<div class=\"sticky-note\" style=\"display: none;\"></div>`;\n }\n\n const style = `left: ${this.data.position.left}px; top: ${this.data.position.top}px;`;\n\n return html`\n <div\n class=\"sticky-note ${this.data.color} ${this.dragging\n ? 'dragging'\n : ''}\"\n style=\"${style}\"\n data-uuid=\"${this.uuid}\"\n >\n <div class=\"sticky-title-container\">\n <temba-icon name=\"drag\" class=\"drag-handle\"></temba-icon>\n <div\n class=\"sticky-title\"\n contenteditable=\"${!this.isTranslating}\"\n @blur=\"${this.handleTitleBlur}\"\n @keydown=\"${this.handleKeyDown}\"\n @mousedown=\"${this.handleContentMouseDown}\"\n .textContent=\"${this.data.title}\"\n ></div>\n </div>\n <div class=\"sticky-body-container\">\n <div\n class=\"sticky-body\"\n contenteditable=\"${!this.isTranslating}\"\n @blur=\"${this.handleBodyBlur}\"\n @keydown=\"${this.handleKeyDown}\"\n @mousedown=\"${this.handleContentMouseDown}\"\n .textContent=\"${this.data.body}\"\n ></div>\n ${!this.isTranslating\n ? html`<div class=\"edit-icon\" title=\"Edit note\"></div>`\n : ''}\n\n <!-- Color picker -->\n <div\n class=\"color-picker\"\n @mouseenter=\"${this.handleColorPickerMouseEnter}\"\n @mouseleave=\"${this.handleColorPickerMouseLeave}\"\n >\n <div\n class=\"color-options ${this.colorPickerExpanded\n ? 'expanded'\n : ''}\"\n >\n <div\n class=\"color-option yellow\"\n @click=\"${(e: MouseEvent) =>\n this.handleColorOptionClick(e, 'yellow')}\"\n ></div>\n <div\n class=\"color-option blue\"\n @click=\"${(e: MouseEvent) =>\n this.handleColorOptionClick(e, 'blue')}\"\n ></div>\n <div\n class=\"color-option pink\"\n @click=\"${(e: MouseEvent) =>\n this.handleColorOptionClick(e, 'pink')}\"\n ></div>\n <div\n class=\"color-option green\"\n @click=\"${(e: MouseEvent) =>\n this.handleColorOptionClick(e, 'green')}\"\n ></div>\n <div\n class=\"color-option gray\"\n @click=\"${(e: MouseEvent) =>\n this.handleColorOptionClick(e, 'gray')}\"\n ></div>\n </div>\n </div>\n </div>\n </div>\n `;\n }\n}\n"]}
1
+ {"version":3,"file":"StickyNote.js","sourceRoot":"","sources":["../../../src/flow/StickyNote.ts"],"names":[],"mappings":"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","sourcesContent":["import { css, html, PropertyValueMap, TemplateResult } from 'lit';\nimport { property } from 'lit/decorators.js';\nimport { RapidElement } from '../RapidElement';\nimport { StickyNote as StickyNoteData } from '../store/flow-definition';\nimport { getStore } from '../store/Store';\nimport { AppState, fromStore, zustand } from '../store/AppState';\n\nexport class StickyNote extends RapidElement {\n @property({ type: String })\n public uuid: string;\n\n @property({ type: Object })\n public data: StickyNoteData;\n\n @property({ type: Boolean })\n private dragging = false;\n\n @property({ type: Boolean })\n public selected = false;\n\n @property({ type: Boolean })\n private colorPickerExpanded = false;\n\n @fromStore(zustand, (state: AppState) => state.isTranslating)\n private isTranslating!: boolean;\n\n static get styles() {\n return css`\n :host {\n --sticky-color: #fef08a;\n --sticky-border-color: #facc15;\n --sticky-text-color: #451a03;\n --curvature: 8px;\n }\n\n .sticky-note {\n width: 182px;\n background-color: var(--sticky-color);\n border: 1px solid var(--sticky-border-color);\n border-radius: var(--curvature);\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,\n sans-serif;\n font-size: 12px;\n overflow: hidden;\n transition: transform 0.1s ease, box-shadow 0.2s ease;\n color: var(--sticky-text-color);\n opacity: 0.85;\n }\n\n .sticky-note.dragging {\n opacity: 0.7;\n z-index: 1000;\n transform: rotate(0deg);\n box-shadow: 0 8px 20px rgba(0, 0, 0, 0.3);\n }\n\n .sticky-note:hover {\n transform: translateY(0px);\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);\n }\n\n /* Color themes */\n .sticky-note.yellow {\n --sticky-color: #fef08a;\n --sticky-border-color: #facc15;\n --sticky-text-color: #451a03;\n }\n .sticky-note.blue {\n --sticky-color: #bfdbfe;\n --sticky-border-color: #3b82f6;\n --sticky-text-color: #1e3a8a;\n }\n .sticky-note.pink {\n --sticky-color: #fce7f3;\n --sticky-border-color: #ec4899;\n --sticky-text-color: #831843;\n }\n .sticky-note.green {\n --sticky-color: #d1fae5;\n --sticky-border-color: #10b981;\n --sticky-text-color: #064e3b;\n }\n .sticky-note.gray {\n --sticky-color: #f3f4f6;\n --sticky-border-color: #6b7280;\n --sticky-text-color: #374151;\n }\n\n /* Title and body containers */\n .sticky-title-container {\n position: relative;\n border-bottom: 1px solid var(--sticky-border-color);\n background-color: rgba(255, 255, 255, 0.5);\n display: flex;\n align-items: center;\n }\n .sticky-body-container {\n position: relative;\n }\n\n /* Editable fields */\n [contenteditable='true'] {\n margin: 2px;\n padding: 4px 8px;\n outline: none;\n border-radius: var(--curvature);\n transition: background 0.2s;\n }\n [contenteditable='true']:focus {\n background-color: rgba(255, 255, 255, 0.8);\n border-bottom: 1px solid rgba(0, 0, 0, 0.1);\n outline-color: var(--sticky-border-color);\n }\n\n /* Title */\n .sticky-title {\n font-weight: 600;\n font-size: 13px;\n color: var(--sticky-text-color);\n min-height: 20px;\n line-height: 20px;\n border-top-left-radius: var(--curvature);\n border-top-right-radius: var(--curvature);\n flex-grow: 1;\n padding: 4px 8px !important;\n margin: 2px;\n padding-left: 8px;\n }\n .sticky-title:empty::before {\n content: 'Click to add title';\n opacity: 0.5;\n font-style: italic;\n }\n .sticky-title:focus {\n border-bottom-left-radius: 0px;\n border-bottom-right-radius: 0px;\n }\n\n /* Body */\n .sticky-body {\n padding: 8px 10px;\n color: var(--sticky-text-color);\n line-height: 1.4;\n min-height: 48px;\n word-wrap: break-word;\n white-space: pre-wrap;\n margin: 2px;\n }\n .sticky-body:empty::before {\n content: 'Click to add note';\n opacity: 0.5;\n font-style: italic;\n }\n .sticky-body:focus {\n border-top-left-radius: 0px;\n border-top-right-radius: 0px;\n }\n\n /* Drag icon */\n .sticky-title-container > .drag-handle {\n --icon-color: var(--sticky-border-color);\n cursor: move;\n max-width: 20px;\n padding-left: 8px;\n overflow: hidden;\n transition: all 0.2s ease;\n }\n\n .sticky-note:hover .drag-handle {\n }\n\n .sticky-note:focus-within .sticky-title-container > .drag-handle {\n }\n\n /* Focus/active states */\n .sticky-note:focus-within {\n box-shadow: 0 0 0 1px var(--sticky-border-color),\n 0 10px 20px rgba(0, 0, 0, 0.3);\n }\n\n .sticky-note:focus-within .drag-handle {\n max-width: 0px;\n padding-left: 0px;\n }\n\n /* Color picker */\n .color-picker {\n position: absolute;\n bottom: 4px;\n right: 4px;\n width: 8px;\n height: 8px;\n border: 1px solid rgba(0, 0, 0, 0.2);\n\n border-radius: 3px;\n background-color: var(--sticky-color);\n cursor: pointer;\n transition: transform 0.2s ease;\n }\n\n .color-picker:hover {\n transform: scale(1.1);\n }\n\n .color-options {\n position: absolute;\n bottom: 0;\n right: 0;\n display: flex;\n gap: 4px;\n background-color: rgba(255, 255, 255, 0.9);\n border: 1px solid #ccc;\n border-radius: 6px;\n padding: 3px;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);\n transform-origin: bottom right;\n transform: scale(0);\n opacity: 0;\n transition: transform 0.2s ease, opacity 0.2s ease;\n z-index: 1000;\n }\n\n .color-options.expanded {\n transform: scale(1);\n opacity: 1;\n }\n\n .color-option {\n width: 12px;\n height: 12px;\n border: 1px solid rgba(0, 0, 0, 0.2);\n border-radius: 3px;\n cursor: pointer;\n transition: transform 0.15s ease, border-color 0.15s ease;\n }\n\n .color-option:hover {\n transform: scale(1.1);\n border-color: rgba(0, 0, 0, 0.4);\n }\n\n .color-option.yellow {\n background-color: #fef08a;\n }\n\n .color-option.blue {\n background-color: #bfdbfe;\n }\n\n .color-option.pink {\n background-color: #fce7f3;\n }\n\n .color-option.green {\n background-color: #d1fae5;\n }\n\n .color-option.gray {\n background-color: #f3f4f6;\n }\n `;\n }\n\n protected updated(\n changes: PropertyValueMap<any> | Map<PropertyKey, unknown>\n ): void {\n super.updated(changes);\n if (changes.has('data') || changes.has('uuid')) {\n this.updateCanvasSize();\n }\n }\n\n private updateCanvasSize(): void {\n if (!this.data) {\n return;\n }\n\n const element = this.querySelector('.sticky-note');\n if (element) {\n const rect = element.getBoundingClientRect();\n getStore()\n .getState()\n .expandCanvas(\n this.data.position.left + rect.width,\n this.data.position.top + rect.height\n );\n }\n }\n\n private handleTitleBlur(event: FocusEvent): void {\n const target = event.target as HTMLElement;\n const newTitle = target.textContent || '';\n\n if (this.data && newTitle !== this.data.title) {\n getStore()\n .getState()\n .updateStickyNote(this.uuid, {\n ...this.data,\n title: newTitle\n });\n }\n this.requestUpdate();\n }\n\n private handleBodyBlur(event: FocusEvent): void {\n const target = event.target as HTMLElement;\n const newBody = target.innerText || '';\n\n if (this.data && newBody !== this.data.body) {\n getStore()\n .getState()\n .updateStickyNote(this.uuid, {\n ...this.data,\n body: newBody\n });\n }\n this.requestUpdate();\n }\n\n private handleContentMouseDown(event: MouseEvent): void {\n // If this sticky note is selected, don't stop propagation\n // so that group dragging can work\n if (this.selected) {\n return;\n }\n // Otherwise, stop propagation to enable editing\n event.stopPropagation();\n }\n\n private handleTitleKeyDown(event: KeyboardEvent): void {\n if (event.key === 'Enter') {\n event.preventDefault();\n (event.target as HTMLElement).blur();\n }\n if (event.key === 'Escape') {\n (event.target as HTMLElement).blur();\n }\n }\n\n private handleBodyKeyDown(event: KeyboardEvent): void {\n if (event.key === 'Enter' && !event.shiftKey) {\n event.preventDefault();\n (event.target as HTMLElement).blur();\n }\n if (event.key === 'Escape') {\n (event.target as HTMLElement).blur();\n }\n }\n\n private handleColorPickerMouseEnter(): void {\n this.colorPickerExpanded = true;\n }\n\n private handleColorPickerMouseLeave(): void {\n this.colorPickerExpanded = false;\n }\n\n private handleColorOptionClick(\n event: MouseEvent,\n color: 'yellow' | 'blue' | 'pink' | 'green' | 'gray'\n ): void {\n event.stopPropagation();\n\n if (this.data && color !== this.data.color) {\n getStore()\n .getState()\n .updateStickyNote(this.uuid, {\n ...this.data,\n color: color\n });\n }\n\n this.colorPickerExpanded = false;\n this.requestUpdate();\n }\n\n public render(): TemplateResult {\n if (!this.data) {\n return html`<div class=\"sticky-note\" style=\"display: none;\"></div>`;\n }\n\n const style = `left: ${this.data.position.left}px; top: ${this.data.position.top}px;`;\n\n return html`\n <div\n class=\"sticky-note ${this.data.color} ${this.dragging\n ? 'dragging'\n : ''}\"\n style=\"${style}\"\n data-uuid=\"${this.uuid}\"\n >\n <div class=\"sticky-title-container\">\n <temba-icon name=\"drag\" class=\"drag-handle\"></temba-icon>\n <div\n class=\"sticky-title\"\n contenteditable=\"${!this.isTranslating}\"\n @blur=\"${this.handleTitleBlur}\"\n @keydown=\"${this.handleTitleKeyDown}\"\n @mousedown=\"${this.handleContentMouseDown}\"\n .textContent=\"${this.data.title}\"\n ></div>\n </div>\n <div class=\"sticky-body-container\">\n <div\n class=\"sticky-body\"\n contenteditable=\"${!this.isTranslating}\"\n @blur=\"${this.handleBodyBlur}\"\n @keydown=\"${this.handleBodyKeyDown}\"\n @mousedown=\"${this.handleContentMouseDown}\"\n .textContent=\"${this.data.body}\"\n ></div>\n ${!this.isTranslating\n ? html`<div class=\"edit-icon\" title=\"Edit note\"></div>`\n : ''}\n\n <!-- Color picker -->\n <div\n class=\"color-picker\"\n @mouseenter=\"${this.handleColorPickerMouseEnter}\"\n @mouseleave=\"${this.handleColorPickerMouseLeave}\"\n >\n <div\n class=\"color-options ${this.colorPickerExpanded\n ? 'expanded'\n : ''}\"\n >\n <div\n class=\"color-option yellow\"\n @click=\"${(e: MouseEvent) =>\n this.handleColorOptionClick(e, 'yellow')}\"\n ></div>\n <div\n class=\"color-option blue\"\n @click=\"${(e: MouseEvent) =>\n this.handleColorOptionClick(e, 'blue')}\"\n ></div>\n <div\n class=\"color-option pink\"\n @click=\"${(e: MouseEvent) =>\n this.handleColorOptionClick(e, 'pink')}\"\n ></div>\n <div\n class=\"color-option green\"\n @click=\"${(e: MouseEvent) =>\n this.handleColorOptionClick(e, 'green')}\"\n ></div>\n <div\n class=\"color-option gray\"\n @click=\"${(e: MouseEvent) =>\n this.handleColorOptionClick(e, 'gray')}\"\n ></div>\n </div>\n </div>\n </div>\n </div>\n `;\n }\n}\n"]}
@@ -46,7 +46,10 @@ export const add_contact_groups = {
46
46
  return {
47
47
  uuid: formData.uuid,
48
48
  type: 'add_contact_groups',
49
- groups: formData.groups || []
49
+ groups: (formData.groups || []).map((g) => ({
50
+ uuid: g.uuid,
51
+ name: g.name
52
+ }))
50
53
  };
51
54
  }
52
55
  };
@@ -1 +1 @@
1
- {"version":3,"file":"add_contact_groups.js","sourceRoot":"","sources":["../../../../src/flow/actions/add_contact_groups.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AAChC,OAAO,EAAgB,aAAa,EAAY,SAAS,EAAE,MAAM,UAAU,CAAC;AAE5E,OAAO,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAE9C,MAAM,CAAC,MAAM,kBAAkB,GAAiB;IAC9C,IAAI,EAAE,cAAc;IACpB,KAAK,EAAE,aAAa,CAAC,QAAQ;IAC7B,SAAS,EAAE,CAAC,SAAS,CAAC,KAAK,EAAE,SAAS,CAAC,OAAO,EAAE,SAAS,CAAC,UAAU,CAAC;IACrE,MAAM,EAAE,CAAC,KAAW,EAAE,MAAkB,EAAE,EAAE;QAC1C,OAAO,IAAI,CAAA,QAAQ,kBAAkB,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,QAAQ,CAAC;IACxE,CAAC;IAED,iEAAiE;IACjE,UAAU,EAAE,CAAC,MAAkB,EAAE,EAAE;QACjC,OAAO;YACL,MAAM,EAAE,MAAM,CAAC,MAAM,IAAI,IAAI;YAC7B,IAAI,EAAE,MAAM,CAAC,IAAI;SAClB,CAAC;IACJ,CAAC;IACD,IAAI,EAAE;QACJ,MAAM,EAAE;YACN,IAAI,EAAE,QAAQ;YACd,KAAK,EAAE,QAAQ;YACf,QAAQ,EAAE,yCAAyC;YACnD,QAAQ,EAAE,IAAI;YACd,OAAO,EAAE,EAAE;YACX,KAAK,EAAE,IAAI;YACX,UAAU,EAAE,IAAI;YAChB,QAAQ,EAAE,qBAAqB;YAC/B,QAAQ,EAAE,MAAM;YAChB,OAAO,EAAE,MAAM;YACf,WAAW,EAAE,sBAAsB;YACnC,WAAW,EAAE,IAAI;YACjB,qBAAqB,EAAE,CAAC,KAAa,EAAE,OAAc,EAAE,EAAE;gBACvD,iDAAiD;gBACjD,MAAM,QAAQ,GAAG,OAAO,CAAC,IAAI,CAC3B,CAAC,MAAM,EAAE,EAAE,CACT,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,KAAK,KAAK,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,CAClE,CAAC;gBACF,IAAI,CAAC,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC;oBAC9B,OAAO;wBACL,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE;wBAClB,SAAS,EAAE,IAAI;qBAChB,CAAC;gBACJ,CAAC;gBACD,OAAO,IAAI,CAAC;YACd,CAAC;SACF;KACF;IACD,YAAY,EAAE,CAAC,QAAkB,EAAc,EAAE;QAC/C,OAAO;YACL,IAAI,EAAE,QAAQ,CAAC,IAAI;YACnB,IAAI,EAAE,oBAAoB;YAC1B,MAAM,EAAE,QAAQ,CAAC,MAAM,IAAI,EAAE;SAC9B,CAAC;IACJ,CAAC;CACF,CAAC","sourcesContent":["import { html } from 'lit-html';\nimport { ActionConfig, ACTION_GROUPS, FormData, FlowTypes } from '../types';\nimport { Node, AddToGroup } from '../../store/flow-definition';\nimport { renderNamedObjects } from '../utils';\n\nexport const add_contact_groups: ActionConfig = {\n name: 'Add to Group',\n group: ACTION_GROUPS.contacts,\n flowTypes: [FlowTypes.VOICE, FlowTypes.MESSAGE, FlowTypes.BACKGROUND],\n render: (_node: Node, action: AddToGroup) => {\n return html`<div>${renderNamedObjects(action.groups, 'group')}</div>`;\n },\n\n // Form-level transformations - default 1:1 mapping for this case\n toFormData: (action: AddToGroup) => {\n return {\n groups: action.groups || null,\n uuid: action.uuid\n };\n },\n form: {\n groups: {\n type: 'select',\n label: 'Groups',\n helpText: 'Select the groups to add the contact to',\n required: true,\n options: [],\n multi: true,\n searchable: true,\n endpoint: '/api/v2/groups.json',\n valueKey: 'uuid',\n nameKey: 'name',\n placeholder: 'Search for groups...',\n allowCreate: true,\n createArbitraryOption: (input: string, options: any[]) => {\n // Check if a label with this name already exists\n const existing = options.find(\n (option) =>\n option.name.toLowerCase().trim() === input.toLowerCase().trim()\n );\n if (!existing && input.trim()) {\n return {\n name: input.trim(),\n arbitrary: true\n };\n }\n return null;\n }\n }\n },\n fromFormData: (formData: FormData): AddToGroup => {\n return {\n uuid: formData.uuid,\n type: 'add_contact_groups',\n groups: formData.groups || []\n };\n }\n};\n"]}
1
+ {"version":3,"file":"add_contact_groups.js","sourceRoot":"","sources":["../../../../src/flow/actions/add_contact_groups.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AAChC,OAAO,EAAgB,aAAa,EAAY,SAAS,EAAE,MAAM,UAAU,CAAC;AAE5E,OAAO,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAE9C,MAAM,CAAC,MAAM,kBAAkB,GAAiB;IAC9C,IAAI,EAAE,cAAc;IACpB,KAAK,EAAE,aAAa,CAAC,QAAQ;IAC7B,SAAS,EAAE,CAAC,SAAS,CAAC,KAAK,EAAE,SAAS,CAAC,OAAO,EAAE,SAAS,CAAC,UAAU,CAAC;IACrE,MAAM,EAAE,CAAC,KAAW,EAAE,MAAkB,EAAE,EAAE;QAC1C,OAAO,IAAI,CAAA,QAAQ,kBAAkB,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,QAAQ,CAAC;IACxE,CAAC;IAED,iEAAiE;IACjE,UAAU,EAAE,CAAC,MAAkB,EAAE,EAAE;QACjC,OAAO;YACL,MAAM,EAAE,MAAM,CAAC,MAAM,IAAI,IAAI;YAC7B,IAAI,EAAE,MAAM,CAAC,IAAI;SAClB,CAAC;IACJ,CAAC;IACD,IAAI,EAAE;QACJ,MAAM,EAAE;YACN,IAAI,EAAE,QAAQ;YACd,KAAK,EAAE,QAAQ;YACf,QAAQ,EAAE,yCAAyC;YACnD,QAAQ,EAAE,IAAI;YACd,OAAO,EAAE,EAAE;YACX,KAAK,EAAE,IAAI;YACX,UAAU,EAAE,IAAI;YAChB,QAAQ,EAAE,qBAAqB;YAC/B,QAAQ,EAAE,MAAM;YAChB,OAAO,EAAE,MAAM;YACf,WAAW,EAAE,sBAAsB;YACnC,WAAW,EAAE,IAAI;YACjB,qBAAqB,EAAE,CAAC,KAAa,EAAE,OAAc,EAAE,EAAE;gBACvD,iDAAiD;gBACjD,MAAM,QAAQ,GAAG,OAAO,CAAC,IAAI,CAC3B,CAAC,MAAM,EAAE,EAAE,CACT,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,KAAK,KAAK,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,CAClE,CAAC;gBACF,IAAI,CAAC,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC;oBAC9B,OAAO;wBACL,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE;wBAClB,SAAS,EAAE,IAAI;qBAChB,CAAC;gBACJ,CAAC;gBACD,OAAO,IAAI,CAAC;YACd,CAAC;SACF;KACF;IACD,YAAY,EAAE,CAAC,QAAkB,EAAc,EAAE;QAC/C,OAAO;YACL,IAAI,EAAE,QAAQ,CAAC,IAAI;YACnB,IAAI,EAAE,oBAAoB;YAC1B,MAAM,EAAE,CAAC,QAAQ,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC;gBAC/C,IAAI,EAAE,CAAC,CAAC,IAAI;gBACZ,IAAI,EAAE,CAAC,CAAC,IAAI;aACb,CAAC,CAAC;SACJ,CAAC;IACJ,CAAC;CACF,CAAC","sourcesContent":["import { html } from 'lit-html';\nimport { ActionConfig, ACTION_GROUPS, FormData, FlowTypes } from '../types';\nimport { Node, AddToGroup } from '../../store/flow-definition';\nimport { renderNamedObjects } from '../utils';\n\nexport const add_contact_groups: ActionConfig = {\n name: 'Add to Group',\n group: ACTION_GROUPS.contacts,\n flowTypes: [FlowTypes.VOICE, FlowTypes.MESSAGE, FlowTypes.BACKGROUND],\n render: (_node: Node, action: AddToGroup) => {\n return html`<div>${renderNamedObjects(action.groups, 'group')}</div>`;\n },\n\n // Form-level transformations - default 1:1 mapping for this case\n toFormData: (action: AddToGroup) => {\n return {\n groups: action.groups || null,\n uuid: action.uuid\n };\n },\n form: {\n groups: {\n type: 'select',\n label: 'Groups',\n helpText: 'Select the groups to add the contact to',\n required: true,\n options: [],\n multi: true,\n searchable: true,\n endpoint: '/api/v2/groups.json',\n valueKey: 'uuid',\n nameKey: 'name',\n placeholder: 'Search for groups...',\n allowCreate: true,\n createArbitraryOption: (input: string, options: any[]) => {\n // Check if a label with this name already exists\n const existing = options.find(\n (option) =>\n option.name.toLowerCase().trim() === input.toLowerCase().trim()\n );\n if (!existing && input.trim()) {\n return {\n name: input.trim(),\n arbitrary: true\n };\n }\n return null;\n }\n }\n },\n fromFormData: (formData: FormData): AddToGroup => {\n return {\n uuid: formData.uuid,\n type: 'add_contact_groups',\n groups: (formData.groups || []).map((g: any) => ({\n uuid: g.uuid,\n name: g.name\n }))\n };\n }\n};\n"]}
@@ -46,7 +46,10 @@ export const add_input_labels = {
46
46
  return {
47
47
  uuid: formData.uuid,
48
48
  type: 'add_input_labels',
49
- labels: formData.labels || []
49
+ labels: (formData.labels || []).map((l) => ({
50
+ uuid: l.uuid,
51
+ name: l.name
52
+ }))
50
53
  };
51
54
  }
52
55
  };
@@ -1 +1 @@
1
- {"version":3,"file":"add_input_labels.js","sourceRoot":"","sources":["../../../../src/flow/actions/add_input_labels.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AAChC,OAAO,EAAgB,aAAa,EAAY,SAAS,EAAE,MAAM,UAAU,CAAC;AAE5E,OAAO,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAE9C,MAAM,CAAC,MAAM,gBAAgB,GAAiB;IAC5C,IAAI,EAAE,kBAAkB;IACxB,KAAK,EAAE,aAAa,CAAC,IAAI;IACzB,SAAS,EAAE,CAAC,SAAS,CAAC,KAAK,EAAE,SAAS,CAAC,OAAO,EAAE,SAAS,CAAC,UAAU,CAAC;IACrE,MAAM,EAAE,CAAC,KAAW,EAAE,MAAsB,EAAE,EAAE;QAC9C,OAAO,IAAI,CAAA,QAAQ,kBAAkB,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,QAAQ,CAAC;IACxE,CAAC;IAED,6BAA6B;IAC7B,UAAU,EAAE,CAAC,MAAsB,EAAE,EAAE;QACrC,OAAO;YACL,MAAM,EAAE,MAAM,CAAC,MAAM,IAAI,IAAI;YAC7B,IAAI,EAAE,MAAM,CAAC,IAAI;SAClB,CAAC;IACJ,CAAC;IACD,IAAI,EAAE;QACJ,MAAM,EAAE;YACN,IAAI,EAAE,QAAQ;YACd,KAAK,EAAE,QAAQ;YACf,QAAQ,EACN,wEAAwE;YAC1E,QAAQ,EAAE,IAAI;YACd,OAAO,EAAE,EAAE;YACX,KAAK,EAAE,IAAI;YACX,UAAU,EAAE,IAAI;YAChB,QAAQ,EAAE,qBAAqB;YAC/B,QAAQ,EAAE,MAAM;YAChB,OAAO,EAAE,MAAM;YACf,WAAW,EAAE,iDAAiD;YAC9D,WAAW,EAAE,IAAI;YACjB,qBAAqB,EAAE,CAAC,KAAa,EAAE,OAAc,EAAE,EAAE;gBACvD,iDAAiD;gBACjD,MAAM,QAAQ,GAAG,OAAO,CAAC,IAAI,CAC3B,CAAC,MAAM,EAAE,EAAE,CACT,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,KAAK,KAAK,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,CAClE,CAAC;gBACF,IAAI,CAAC,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC;oBAC9B,OAAO;wBACL,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE;wBAClB,SAAS,EAAE,IAAI;qBAChB,CAAC;gBACJ,CAAC;gBACD,OAAO,IAAI,CAAC;YACd,CAAC;SACF;KACF;IACD,YAAY,EAAE,CAAC,QAAkB,EAAkB,EAAE;QACnD,OAAO;YACL,IAAI,EAAE,QAAQ,CAAC,IAAI;YACnB,IAAI,EAAE,kBAAkB;YACxB,MAAM,EAAE,QAAQ,CAAC,MAAM,IAAI,EAAE;SAC9B,CAAC;IACJ,CAAC;CACF,CAAC","sourcesContent":["import { html } from 'lit-html';\nimport { ActionConfig, ACTION_GROUPS, FormData, FlowTypes } from '../types';\nimport { Node, AddInputLabels } from '../../store/flow-definition';\nimport { renderNamedObjects } from '../utils';\n\nexport const add_input_labels: ActionConfig = {\n name: 'Add Input Labels',\n group: ACTION_GROUPS.save,\n flowTypes: [FlowTypes.VOICE, FlowTypes.MESSAGE, FlowTypes.BACKGROUND],\n render: (_node: Node, action: AddInputLabels) => {\n return html`<div>${renderNamedObjects(action.labels, 'label')}</div>`;\n },\n\n // Form-level transformations\n toFormData: (action: AddInputLabels) => {\n return {\n labels: action.labels || null,\n uuid: action.uuid\n };\n },\n form: {\n labels: {\n type: 'select',\n label: 'Labels',\n helpText:\n 'Select labels to add to the input. Type a new label name to create it.',\n required: true,\n options: [],\n multi: true,\n searchable: true,\n endpoint: '/api/v2/labels.json',\n valueKey: 'uuid',\n nameKey: 'name',\n placeholder: 'Search for labels or type to create new ones...',\n allowCreate: true,\n createArbitraryOption: (input: string, options: any[]) => {\n // Check if a label with this name already exists\n const existing = options.find(\n (option) =>\n option.name.toLowerCase().trim() === input.toLowerCase().trim()\n );\n if (!existing && input.trim()) {\n return {\n name: input.trim(),\n arbitrary: true\n };\n }\n return null;\n }\n }\n },\n fromFormData: (formData: FormData): AddInputLabels => {\n return {\n uuid: formData.uuid,\n type: 'add_input_labels',\n labels: formData.labels || []\n };\n }\n};\n"]}
1
+ {"version":3,"file":"add_input_labels.js","sourceRoot":"","sources":["../../../../src/flow/actions/add_input_labels.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AAChC,OAAO,EAAgB,aAAa,EAAY,SAAS,EAAE,MAAM,UAAU,CAAC;AAE5E,OAAO,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAE9C,MAAM,CAAC,MAAM,gBAAgB,GAAiB;IAC5C,IAAI,EAAE,kBAAkB;IACxB,KAAK,EAAE,aAAa,CAAC,IAAI;IACzB,SAAS,EAAE,CAAC,SAAS,CAAC,KAAK,EAAE,SAAS,CAAC,OAAO,EAAE,SAAS,CAAC,UAAU,CAAC;IACrE,MAAM,EAAE,CAAC,KAAW,EAAE,MAAsB,EAAE,EAAE;QAC9C,OAAO,IAAI,CAAA,QAAQ,kBAAkB,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,QAAQ,CAAC;IACxE,CAAC;IAED,6BAA6B;IAC7B,UAAU,EAAE,CAAC,MAAsB,EAAE,EAAE;QACrC,OAAO;YACL,MAAM,EAAE,MAAM,CAAC,MAAM,IAAI,IAAI;YAC7B,IAAI,EAAE,MAAM,CAAC,IAAI;SAClB,CAAC;IACJ,CAAC;IACD,IAAI,EAAE;QACJ,MAAM,EAAE;YACN,IAAI,EAAE,QAAQ;YACd,KAAK,EAAE,QAAQ;YACf,QAAQ,EACN,wEAAwE;YAC1E,QAAQ,EAAE,IAAI;YACd,OAAO,EAAE,EAAE;YACX,KAAK,EAAE,IAAI;YACX,UAAU,EAAE,IAAI;YAChB,QAAQ,EAAE,qBAAqB;YAC/B,QAAQ,EAAE,MAAM;YAChB,OAAO,EAAE,MAAM;YACf,WAAW,EAAE,iDAAiD;YAC9D,WAAW,EAAE,IAAI;YACjB,qBAAqB,EAAE,CAAC,KAAa,EAAE,OAAc,EAAE,EAAE;gBACvD,iDAAiD;gBACjD,MAAM,QAAQ,GAAG,OAAO,CAAC,IAAI,CAC3B,CAAC,MAAM,EAAE,EAAE,CACT,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,KAAK,KAAK,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,CAClE,CAAC;gBACF,IAAI,CAAC,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC;oBAC9B,OAAO;wBACL,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE;wBAClB,SAAS,EAAE,IAAI;qBAChB,CAAC;gBACJ,CAAC;gBACD,OAAO,IAAI,CAAC;YACd,CAAC;SACF;KACF;IACD,YAAY,EAAE,CAAC,QAAkB,EAAkB,EAAE;QACnD,OAAO;YACL,IAAI,EAAE,QAAQ,CAAC,IAAI;YACnB,IAAI,EAAE,kBAAkB;YACxB,MAAM,EAAE,CAAC,QAAQ,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC;gBAC/C,IAAI,EAAE,CAAC,CAAC,IAAI;gBACZ,IAAI,EAAE,CAAC,CAAC,IAAI;aACb,CAAC,CAAC;SACJ,CAAC;IACJ,CAAC;CACF,CAAC","sourcesContent":["import { html } from 'lit-html';\nimport { ActionConfig, ACTION_GROUPS, FormData, FlowTypes } from '../types';\nimport { Node, AddInputLabels } from '../../store/flow-definition';\nimport { renderNamedObjects } from '../utils';\n\nexport const add_input_labels: ActionConfig = {\n name: 'Add Input Labels',\n group: ACTION_GROUPS.save,\n flowTypes: [FlowTypes.VOICE, FlowTypes.MESSAGE, FlowTypes.BACKGROUND],\n render: (_node: Node, action: AddInputLabels) => {\n return html`<div>${renderNamedObjects(action.labels, 'label')}</div>`;\n },\n\n // Form-level transformations\n toFormData: (action: AddInputLabels) => {\n return {\n labels: action.labels || null,\n uuid: action.uuid\n };\n },\n form: {\n labels: {\n type: 'select',\n label: 'Labels',\n helpText:\n 'Select labels to add to the input. Type a new label name to create it.',\n required: true,\n options: [],\n multi: true,\n searchable: true,\n endpoint: '/api/v2/labels.json',\n valueKey: 'uuid',\n nameKey: 'name',\n placeholder: 'Search for labels or type to create new ones...',\n allowCreate: true,\n createArbitraryOption: (input: string, options: any[]) => {\n // Check if a label with this name already exists\n const existing = options.find(\n (option) =>\n option.name.toLowerCase().trim() === input.toLowerCase().trim()\n );\n if (!existing && input.trim()) {\n return {\n name: input.trim(),\n arbitrary: true\n };\n }\n return null;\n }\n }\n },\n fromFormData: (formData: FormData): AddInputLabels => {\n return {\n uuid: formData.uuid,\n type: 'add_input_labels',\n labels: (formData.labels || []).map((l: any) => ({\n uuid: l.uuid,\n name: l.name\n }))\n };\n }\n};\n"]}
@@ -0,0 +1,112 @@
1
+ import { html } from 'lit-html';
2
+ // SVG paths for play and pause icons
3
+ const PLAY_SVG = html `<svg
4
+ viewBox="0 0 24 24"
5
+ width="16"
6
+ height="16"
7
+ fill="currentColor"
8
+ >
9
+ <polygon points="6,3 20,12 6,21" />
10
+ </svg>`;
11
+ // Track active audio so only one plays at a time
12
+ let activeAudio = null;
13
+ let activeContainer = null;
14
+ function stopActive() {
15
+ if (activeAudio) {
16
+ activeAudio.pause();
17
+ activeAudio.currentTime = 0;
18
+ if (activeContainer) {
19
+ resetPlayer(activeContainer);
20
+ }
21
+ activeAudio = null;
22
+ activeContainer = null;
23
+ }
24
+ }
25
+ function resetPlayer(container) {
26
+ const btn = container.querySelector('.audio-play-btn');
27
+ const progress = container.querySelector('.audio-progress');
28
+ if (btn)
29
+ btn.innerHTML =
30
+ '<svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor"><polygon points="6,3 20,12 6,21"/></svg>';
31
+ if (progress)
32
+ progress.style.width = '0%';
33
+ }
34
+ function handlePlayClick(e) {
35
+ e.stopPropagation();
36
+ e.preventDefault();
37
+ const container = e.currentTarget.closest('.audio-player');
38
+ if (!container)
39
+ return;
40
+ const url = container.dataset.url;
41
+ if (!url)
42
+ return;
43
+ const btn = container.querySelector('.audio-play-btn');
44
+ const progress = container.querySelector('.audio-progress');
45
+ // If this is already playing, pause it
46
+ if (activeAudio && activeContainer === container && !activeAudio.paused) {
47
+ activeAudio.pause();
48
+ btn.innerHTML =
49
+ '<svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor"><polygon points="6,3 20,12 6,21"/></svg>';
50
+ return;
51
+ }
52
+ // Stop any other playing audio
53
+ stopActive();
54
+ const audio = new Audio(url);
55
+ activeAudio = audio;
56
+ activeContainer = container;
57
+ btn.innerHTML =
58
+ '<svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor"><rect x="5" y="3" width="4" height="18"/><rect x="15" y="3" width="4" height="18"/></svg>';
59
+ audio.addEventListener('timeupdate', () => {
60
+ if (audio.duration && progress) {
61
+ const pct = (audio.currentTime / audio.duration) * 100;
62
+ progress.style.width = `${pct}%`;
63
+ }
64
+ });
65
+ audio.addEventListener('ended', () => {
66
+ resetPlayer(container);
67
+ activeAudio = null;
68
+ activeContainer = null;
69
+ });
70
+ audio.addEventListener('error', () => {
71
+ resetPlayer(container);
72
+ activeAudio = null;
73
+ activeContainer = null;
74
+ });
75
+ audio.play().catch(() => {
76
+ resetPlayer(container);
77
+ activeAudio = null;
78
+ activeContainer = null;
79
+ });
80
+ }
81
+ /**
82
+ * Renders an inline audio player with play/pause button and progress bar.
83
+ * Used on canvas nodes for play_audio and say_msg actions.
84
+ */
85
+ export function renderAudioPlayer(audioUrl) {
86
+ return html `
87
+ <div
88
+ class="audio-player"
89
+ data-url="${audioUrl}"
90
+ style="display: flex; align-items: center; gap: 0.4em; cursor: default;"
91
+ @mousedown=${(e) => e.stopPropagation()}
92
+ @mouseup=${(e) => e.stopPropagation()}
93
+ >
94
+ <div
95
+ class="audio-play-btn"
96
+ @click=${handlePlayClick}
97
+ style="cursor: pointer; color: #666; display: flex; align-items: center; flex-shrink: 0;"
98
+ >
99
+ ${PLAY_SVG}
100
+ </div>
101
+ <div
102
+ style="flex: 1; height: 4px; background: #e0e0e0; border-radius: 2px; overflow: hidden; min-width: 40px;"
103
+ >
104
+ <div
105
+ class="audio-progress"
106
+ style="width: 0%; height: 100%; background: var(--color-primary, #2387ca); border-radius: 2px; transition: width 0.2s linear;"
107
+ ></div>
108
+ </div>
109
+ </div>
110
+ `;
111
+ }
112
+ //# sourceMappingURL=audio-player.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audio-player.js","sourceRoot":"","sources":["../../../../src/flow/actions/audio-player.ts"],"names":[],"mappings":"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","sourcesContent":["import { html, TemplateResult } from 'lit-html';\n\n// SVG paths for play and pause icons\nconst PLAY_SVG = html`<svg\n viewBox=\"0 0 24 24\"\n width=\"16\"\n height=\"16\"\n fill=\"currentColor\"\n>\n <polygon points=\"6,3 20,12 6,21\" />\n</svg>`;\n\n// Track active audio so only one plays at a time\nlet activeAudio: HTMLAudioElement | null = null;\nlet activeContainer: HTMLElement | null = null;\n\nfunction stopActive() {\n if (activeAudio) {\n activeAudio.pause();\n activeAudio.currentTime = 0;\n if (activeContainer) {\n resetPlayer(activeContainer);\n }\n activeAudio = null;\n activeContainer = null;\n }\n}\n\nfunction resetPlayer(container: HTMLElement) {\n const btn = container.querySelector('.audio-play-btn') as HTMLElement;\n const progress = container.querySelector('.audio-progress') as HTMLElement;\n if (btn)\n btn.innerHTML =\n '<svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"currentColor\"><polygon points=\"6,3 20,12 6,21\"/></svg>';\n if (progress) progress.style.width = '0%';\n}\n\nfunction handlePlayClick(e: MouseEvent) {\n e.stopPropagation();\n e.preventDefault();\n\n const container = (e.currentTarget as HTMLElement).closest(\n '.audio-player'\n ) as HTMLElement;\n if (!container) return;\n\n const url = container.dataset.url;\n if (!url) return;\n\n const btn = container.querySelector('.audio-play-btn') as HTMLElement;\n const progress = container.querySelector('.audio-progress') as HTMLElement;\n\n // If this is already playing, pause it\n if (activeAudio && activeContainer === container && !activeAudio.paused) {\n activeAudio.pause();\n btn.innerHTML =\n '<svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"currentColor\"><polygon points=\"6,3 20,12 6,21\"/></svg>';\n return;\n }\n\n // Stop any other playing audio\n stopActive();\n\n const audio = new Audio(url);\n activeAudio = audio;\n activeContainer = container;\n\n btn.innerHTML =\n '<svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"currentColor\"><rect x=\"5\" y=\"3\" width=\"4\" height=\"18\"/><rect x=\"15\" y=\"3\" width=\"4\" height=\"18\"/></svg>';\n\n audio.addEventListener('timeupdate', () => {\n if (audio.duration && progress) {\n const pct = (audio.currentTime / audio.duration) * 100;\n progress.style.width = `${pct}%`;\n }\n });\n\n audio.addEventListener('ended', () => {\n resetPlayer(container);\n activeAudio = null;\n activeContainer = null;\n });\n\n audio.addEventListener('error', () => {\n resetPlayer(container);\n activeAudio = null;\n activeContainer = null;\n });\n\n audio.play().catch(() => {\n resetPlayer(container);\n activeAudio = null;\n activeContainer = null;\n });\n}\n\n/**\n * Renders an inline audio player with play/pause button and progress bar.\n * Used on canvas nodes for play_audio and say_msg actions.\n */\nexport function renderAudioPlayer(audioUrl: string): TemplateResult {\n return html`\n <div\n class=\"audio-player\"\n data-url=\"${audioUrl}\"\n style=\"display: flex; align-items: center; gap: 0.4em; cursor: default;\"\n @mousedown=${(e: MouseEvent) => e.stopPropagation()}\n @mouseup=${(e: MouseEvent) => e.stopPropagation()}\n >\n <div\n class=\"audio-play-btn\"\n @click=${handlePlayClick}\n style=\"cursor: pointer; color: #666; display: flex; align-items: center; flex-shrink: 0;\"\n >\n ${PLAY_SVG}\n </div>\n <div\n style=\"flex: 1; height: 4px; background: #e0e0e0; border-radius: 2px; overflow: hidden; min-width: 40px;\"\n >\n <div\n class=\"audio-progress\"\n style=\"width: 0%; height: 100%; background: var(--color-primary, #2387ca); border-radius: 2px; transition: width 0.2s linear;\"\n ></div>\n </div>\n </div>\n `;\n}\n"]}
@@ -0,0 +1,43 @@
1
+ import { html } from 'lit-html';
2
+ import { ACTION_GROUPS, FlowTypes } from '../types';
3
+ import { renderNamedObjects } from '../utils';
4
+ export const enter_flow = {
5
+ name: 'Enter a Flow',
6
+ group: ACTION_GROUPS.trigger,
7
+ hideFromActions: true,
8
+ flowTypes: [FlowTypes.VOICE, FlowTypes.MESSAGE, FlowTypes.BACKGROUND],
9
+ render: (_node, action) => {
10
+ return html `${renderNamedObjects([action.flow], 'flow')}`;
11
+ },
12
+ toFormData: (action) => {
13
+ return {
14
+ uuid: action.uuid,
15
+ flow: action.flow ? [action.flow] : []
16
+ };
17
+ },
18
+ form: {
19
+ flow: {
20
+ type: 'select',
21
+ required: true,
22
+ placeholder: 'Select a flow...',
23
+ helpText: 'The contact will enter this flow and not return',
24
+ endpoint: '/api/v2/flows.json',
25
+ valueKey: 'uuid',
26
+ nameKey: 'name'
27
+ }
28
+ },
29
+ layout: ['flow'],
30
+ fromFormData: (formData) => {
31
+ const selected = formData.flow[0];
32
+ return {
33
+ uuid: formData.uuid,
34
+ type: 'enter_flow',
35
+ terminal: true,
36
+ flow: {
37
+ uuid: selected.uuid || selected.value,
38
+ name: selected.name
39
+ }
40
+ };
41
+ }
42
+ };
43
+ //# sourceMappingURL=enter_flow.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"enter_flow.js","sourceRoot":"","sources":["../../../../src/flow/actions/enter_flow.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AAChC,OAAO,EAAgB,aAAa,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAElE,OAAO,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAE9C,MAAM,CAAC,MAAM,UAAU,GAAiB;IACtC,IAAI,EAAE,cAAc;IACpB,KAAK,EAAE,aAAa,CAAC,OAAO;IAC5B,eAAe,EAAE,IAAI;IACrB,SAAS,EAAE,CAAC,SAAS,CAAC,KAAK,EAAE,SAAS,CAAC,OAAO,EAAE,SAAS,CAAC,UAAU,CAAC;IACrE,MAAM,EAAE,CAAC,KAAW,EAAE,MAAiB,EAAE,EAAE;QACzC,OAAO,IAAI,CAAA,GAAG,kBAAkB,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,EAAE,CAAC;IAC5D,CAAC;IACD,UAAU,EAAE,CAAC,MAAiB,EAAE,EAAE;QAChC,OAAO;YACL,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE;SACvC,CAAC;IACJ,CAAC;IACD,IAAI,EAAE;QACJ,IAAI,EAAE;YACJ,IAAI,EAAE,QAAQ;YACd,QAAQ,EAAE,IAAI;YACd,WAAW,EAAE,kBAAkB;YAC/B,QAAQ,EAAE,iDAAiD;YAC3D,QAAQ,EAAE,oBAAoB;YAC9B,QAAQ,EAAE,MAAM;YAChB,OAAO,EAAE,MAAM;SAChB;KACF;IACD,MAAM,EAAE,CAAC,MAAM,CAAC;IAChB,YAAY,EAAE,CAAC,QAAa,EAAa,EAAE;QACzC,MAAM,QAAQ,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClC,OAAO;YACL,IAAI,EAAE,QAAQ,CAAC,IAAI;YACnB,IAAI,EAAE,YAAY;YAClB,QAAQ,EAAE,IAAI;YACd,IAAI,EAAE;gBACJ,IAAI,EAAE,QAAQ,CAAC,IAAI,IAAI,QAAQ,CAAC,KAAK;gBACrC,IAAI,EAAE,QAAQ,CAAC,IAAI;aACpB;SACF,CAAC;IACJ,CAAC;CACF,CAAC","sourcesContent":["import { html } from 'lit-html';\nimport { ActionConfig, ACTION_GROUPS, FlowTypes } from '../types';\nimport { Node, EnterFlow } from '../../store/flow-definition';\nimport { renderNamedObjects } from '../utils';\n\nexport const enter_flow: ActionConfig = {\n name: 'Enter a Flow',\n group: ACTION_GROUPS.trigger,\n hideFromActions: true,\n flowTypes: [FlowTypes.VOICE, FlowTypes.MESSAGE, FlowTypes.BACKGROUND],\n render: (_node: Node, action: EnterFlow) => {\n return html`${renderNamedObjects([action.flow], 'flow')}`;\n },\n toFormData: (action: EnterFlow) => {\n return {\n uuid: action.uuid,\n flow: action.flow ? [action.flow] : []\n };\n },\n form: {\n flow: {\n type: 'select',\n required: true,\n placeholder: 'Select a flow...',\n helpText: 'The contact will enter this flow and not return',\n endpoint: '/api/v2/flows.json',\n valueKey: 'uuid',\n nameKey: 'name'\n }\n },\n layout: ['flow'],\n fromFormData: (formData: any): EnterFlow => {\n const selected = formData.flow[0];\n return {\n uuid: formData.uuid,\n type: 'enter_flow',\n terminal: true,\n flow: {\n uuid: selected.uuid || selected.value,\n name: selected.name\n }\n };\n }\n};\n"]}
@@ -1,12 +1,65 @@
1
1
  import { html } from 'lit-html';
2
2
  import { ACTION_GROUPS, FlowTypes } from '../types';
3
3
  export const play_audio = {
4
- name: 'Play Audio',
4
+ name: 'Play Recording',
5
5
  group: ACTION_GROUPS.send,
6
6
  flowTypes: [FlowTypes.VOICE],
7
- render: (_node, _action) => {
8
- // This will need to be implemented based on the actual render logic
9
- return html `<div>Play Audio</div>`;
7
+ render: (_node, action) => {
8
+ return html `
9
+ <div style="display: flex; align-items: center; gap: 0.3em;">
10
+ <temba-icon name="recording" size="1"></temba-icon>
11
+ <div
12
+ style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0;"
13
+ title="${action.audio_url || ''}"
14
+ >
15
+ ${action.audio_url || ''}
16
+ </div>
17
+ </div>
18
+ `;
19
+ },
20
+ form: {
21
+ audio_url: {
22
+ type: 'text',
23
+ label: 'Recording URL',
24
+ required: true,
25
+ evaluated: true
26
+ }
27
+ },
28
+ layout: ['audio_url'],
29
+ toFormData: (action) => {
30
+ return {
31
+ uuid: action.uuid,
32
+ audio_url: action.audio_url || ''
33
+ };
34
+ },
35
+ fromFormData: (data) => {
36
+ return {
37
+ uuid: data.uuid,
38
+ type: 'play_audio',
39
+ audio_url: (data.audio_url || '').trim()
40
+ };
41
+ },
42
+ localizable: ['audio_url'],
43
+ toLocalizationFormData: (action, localization) => {
44
+ const formData = {
45
+ uuid: action.uuid
46
+ };
47
+ if (localization.audio_url && Array.isArray(localization.audio_url)) {
48
+ formData.audio_url = localization.audio_url[0] || '';
49
+ }
50
+ else {
51
+ formData.audio_url = '';
52
+ }
53
+ return formData;
54
+ },
55
+ fromLocalizationFormData: (formData, action) => {
56
+ const localization = {};
57
+ if (formData.audio_url && formData.audio_url.trim() !== '') {
58
+ if (formData.audio_url !== action.audio_url) {
59
+ localization.audio_url = [formData.audio_url];
60
+ }
61
+ }
62
+ return localization;
10
63
  }
11
64
  };
12
65
  //# sourceMappingURL=play_audio.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"play_audio.js","sourceRoot":"","sources":["../../../../src/flow/actions/play_audio.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AAChC,OAAO,EAAgB,aAAa,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAGlE,MAAM,CAAC,MAAM,UAAU,GAAiB;IACtC,IAAI,EAAE,YAAY;IAClB,KAAK,EAAE,aAAa,CAAC,IAAI;IACzB,SAAS,EAAE,CAAC,SAAS,CAAC,KAAK,CAAC;IAC5B,MAAM,EAAE,CAAC,KAAW,EAAE,OAAkB,EAAE,EAAE;QAC1C,oEAAoE;QACpE,OAAO,IAAI,CAAA,uBAAuB,CAAC;IACrC,CAAC;CACF,CAAC","sourcesContent":["import { html } from 'lit-html';\nimport { ActionConfig, ACTION_GROUPS, FlowTypes } from '../types';\nimport { Node, PlayAudio } from '../../store/flow-definition';\n\nexport const play_audio: ActionConfig = {\n name: 'Play Audio',\n group: ACTION_GROUPS.send,\n flowTypes: [FlowTypes.VOICE],\n render: (_node: Node, _action: PlayAudio) => {\n // This will need to be implemented based on the actual render logic\n return html`<div>Play Audio</div>`;\n }\n};\n"]}
1
+ {"version":3,"file":"play_audio.js","sourceRoot":"","sources":["../../../../src/flow/actions/play_audio.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AAChC,OAAO,EAAgB,aAAa,EAAY,SAAS,EAAE,MAAM,UAAU,CAAC;AAG5E,MAAM,CAAC,MAAM,UAAU,GAAiB;IACtC,IAAI,EAAE,gBAAgB;IACtB,KAAK,EAAE,aAAa,CAAC,IAAI;IACzB,SAAS,EAAE,CAAC,SAAS,CAAC,KAAK,CAAC;IAC5B,MAAM,EAAE,CAAC,KAAW,EAAE,MAAiB,EAAE,EAAE;QACzC,OAAO,IAAI,CAAA;;;;;mBAKI,MAAM,CAAC,SAAS,IAAI,EAAE;;YAE7B,MAAM,CAAC,SAAS,IAAI,EAAE;;;KAG7B,CAAC;IACJ,CAAC;IACD,IAAI,EAAE;QACJ,SAAS,EAAE;YACT,IAAI,EAAE,MAAM;YACZ,KAAK,EAAE,eAAe;YACtB,QAAQ,EAAE,IAAI;YACd,SAAS,EAAE,IAAI;SAChB;KACF;IACD,MAAM,EAAE,CAAC,WAAW,CAAC;IACrB,UAAU,EAAE,CAAC,MAAiB,EAAE,EAAE;QAChC,OAAO;YACL,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,SAAS,EAAE,MAAM,CAAC,SAAS,IAAI,EAAE;SAClC,CAAC;IACJ,CAAC;IACD,YAAY,EAAE,CAAC,IAAc,EAAE,EAAE;QAC/B,OAAO;YACL,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,IAAI,EAAE,YAAY;YAClB,SAAS,EAAE,CAAC,IAAI,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE;SAC5B,CAAC;IACjB,CAAC;IACD,WAAW,EAAE,CAAC,WAAW,CAAC;IAC1B,sBAAsB,EAAE,CACtB,MAAiB,EACjB,YAAiC,EACjC,EAAE;QACF,MAAM,QAAQ,GAAa;YACzB,IAAI,EAAE,MAAM,CAAC,IAAI;SAClB,CAAC;QAEF,IAAI,YAAY,CAAC,SAAS,IAAI,KAAK,CAAC,OAAO,CAAC,YAAY,CAAC,SAAS,CAAC,EAAE,CAAC;YACpE,QAAQ,CAAC,SAAS,GAAG,YAAY,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QACvD,CAAC;aAAM,CAAC;YACN,QAAQ,CAAC,SAAS,GAAG,EAAE,CAAC;QAC1B,CAAC;QAED,OAAO,QAAQ,CAAC;IAClB,CAAC;IACD,wBAAwB,EAAE,CAAC,QAAkB,EAAE,MAAiB,EAAE,EAAE;QAClE,MAAM,YAAY,GAAwB,EAAE,CAAC;QAE7C,IAAI,QAAQ,CAAC,SAAS,IAAI,QAAQ,CAAC,SAAS,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;YAC3D,IAAI,QAAQ,CAAC,SAAS,KAAK,MAAM,CAAC,SAAS,EAAE,CAAC;gBAC5C,YAAY,CAAC,SAAS,GAAG,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;YAChD,CAAC;QACH,CAAC;QAED,OAAO,YAAY,CAAC;IACtB,CAAC;CACF,CAAC","sourcesContent":["import { html } from 'lit-html';\nimport { ActionConfig, ACTION_GROUPS, FormData, FlowTypes } from '../types';\nimport { Node, PlayAudio } from '../../store/flow-definition';\n\nexport const play_audio: ActionConfig = {\n name: 'Play Recording',\n group: ACTION_GROUPS.send,\n flowTypes: [FlowTypes.VOICE],\n render: (_node: Node, action: PlayAudio) => {\n return html`\n <div style=\"display: flex; align-items: center; gap: 0.3em;\">\n <temba-icon name=\"recording\" size=\"1\"></temba-icon>\n <div\n style=\"overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0;\"\n title=\"${action.audio_url || ''}\"\n >\n ${action.audio_url || ''}\n </div>\n </div>\n `;\n },\n form: {\n audio_url: {\n type: 'text',\n label: 'Recording URL',\n required: true,\n evaluated: true\n }\n },\n layout: ['audio_url'],\n toFormData: (action: PlayAudio) => {\n return {\n uuid: action.uuid,\n audio_url: action.audio_url || ''\n };\n },\n fromFormData: (data: FormData) => {\n return {\n uuid: data.uuid,\n type: 'play_audio',\n audio_url: (data.audio_url || '').trim()\n } as PlayAudio;\n },\n localizable: ['audio_url'],\n toLocalizationFormData: (\n action: PlayAudio,\n localization: Record<string, any>\n ) => {\n const formData: FormData = {\n uuid: action.uuid\n };\n\n if (localization.audio_url && Array.isArray(localization.audio_url)) {\n formData.audio_url = localization.audio_url[0] || '';\n } else {\n formData.audio_url = '';\n }\n\n return formData;\n },\n fromLocalizationFormData: (formData: FormData, action: PlayAudio) => {\n const localization: Record<string, any> = {};\n\n if (formData.audio_url && formData.audio_url.trim() !== '') {\n if (formData.audio_url !== action.audio_url) {\n localization.audio_url = [formData.audio_url];\n }\n }\n\n return localization;\n }\n};\n"]}
@@ -56,7 +56,12 @@ export const remove_contact_groups = {
56
56
  return {
57
57
  uuid: formData.uuid,
58
58
  type: 'remove_contact_groups',
59
- groups: formData.all_groups ? [] : formData.groups || [],
59
+ groups: formData.all_groups
60
+ ? []
61
+ : (formData.groups || []).map((g) => ({
62
+ uuid: g.uuid,
63
+ name: g.name
64
+ })),
60
65
  all_groups: formData.all_groups || false
61
66
  };
62
67
  }
@@ -1 +1 @@
1
- {"version":3,"file":"remove_contact_groups.js","sourceRoot":"","sources":["../../../../src/flow/actions/remove_contact_groups.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AAChC,OAAO,EAEL,aAAa,EAGb,SAAS,EACV,MAAM,UAAU,CAAC;AAElB,OAAO,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAE9C,MAAM,CAAC,MAAM,qBAAqB,GAAiB;IACjD,IAAI,EAAE,mBAAmB;IACzB,KAAK,EAAE,aAAa,CAAC,QAAQ;IAC7B,SAAS,EAAE,CAAC,SAAS,CAAC,KAAK,EAAE,SAAS,CAAC,OAAO,EAAE,SAAS,CAAC,UAAU,CAAC;IACrE,MAAM,EAAE,CAAC,KAAW,EAAE,MAAuB,EAAE,EAAE;QAC/C,IAAI,MAAM,CAAC,UAAU,EAAE,CAAC;YACtB,OAAO,IAAI,CAAA,mCAAmC,CAAC;QACjD,CAAC;QACD,OAAO,IAAI,CAAA,QAAQ,kBAAkB,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,QAAQ,CAAC;IACxE,CAAC;IACD,UAAU,EAAE,CAAC,MAAuB,EAAE,EAAE;QACtC,OAAO;YACL,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,UAAU,EAAE,MAAM,CAAC,UAAU,IAAI,KAAK;YACtC,MAAM,EAAE,MAAM,CAAC,MAAM,IAAI,IAAI;SAC9B,CAAC;IACJ,CAAC;IACD,IAAI,EAAE;QACJ,MAAM,EAAE;YACN,IAAI,EAAE,QAAQ;YACd,KAAK,EAAE,QAAQ;YACf,QAAQ,EAAE,8CAA8C;YACxD,OAAO,EAAE,EAAE;YACX,KAAK,EAAE,IAAI;YACX,UAAU,EAAE,IAAI;YAChB,QAAQ,EAAE,qBAAqB;YAC/B,QAAQ,EAAE,MAAM;YAChB,OAAO,EAAE,MAAM;YACf,WAAW,EAAE,sBAAsB;YACnC,UAAU,EAAE;gBACV,OAAO,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,UAAU;aAC5C;SACF;QACD,UAAU,EAAE;YACV,IAAI,EAAE,UAAU;YAChB,KAAK,EAAE,wBAAwB;YAC/B,QAAQ,EACN,2EAA2E;SAC9E;KACF;IACD,QAAQ,EAAE,CAAC,QAAkB,EAAoB,EAAE;QACjD,MAAM,MAAM,GAA8B,EAAE,CAAC;QAE7C,IACE,CAAC,QAAQ,CAAC,UAAU;YACpB,CAAC,CAAC,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC,EAClD,CAAC;YACD,MAAM,CAAC,MAAM;gBACX,uEAAuE,CAAC;QAC5E,CAAC;QAED,OAAO;YACL,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,KAAK,CAAC;YACvC,MAAM;SACP,CAAC;IACJ,CAAC;IACD,YAAY,EAAE,CAAC,QAAkB,EAAmB,EAAE;QACpD,OAAO;YACL,IAAI,EAAE,QAAQ,CAAC,IAAI;YACnB,IAAI,EAAE,uBAAuB;YAC7B,MAAM,EAAE,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,IAAI,EAAE;YACxD,UAAU,EAAE,QAAQ,CAAC,UAAU,IAAI,KAAK;SACzC,CAAC;IACJ,CAAC;CACF,CAAC","sourcesContent":["import { html } from 'lit-html';\nimport {\n ActionConfig,\n ACTION_GROUPS,\n FormData,\n ValidationResult,\n FlowTypes\n} from '../types';\nimport { Node, RemoveFromGroup } from '../../store/flow-definition';\nimport { renderNamedObjects } from '../utils';\n\nexport const remove_contact_groups: ActionConfig = {\n name: 'Remove from Group',\n group: ACTION_GROUPS.contacts,\n flowTypes: [FlowTypes.VOICE, FlowTypes.MESSAGE, FlowTypes.BACKGROUND],\n render: (_node: Node, action: RemoveFromGroup) => {\n if (action.all_groups) {\n return html`<div>Remove from all groups</div>`;\n }\n return html`<div>${renderNamedObjects(action.groups, 'group')}</div>`;\n },\n toFormData: (action: RemoveFromGroup) => {\n return {\n uuid: action.uuid,\n all_groups: action.all_groups || false,\n groups: action.groups || null\n };\n },\n form: {\n groups: {\n type: 'select',\n label: 'Groups',\n helpText: 'Select the groups to remove the contact from',\n options: [],\n multi: true,\n searchable: true,\n endpoint: '/api/v2/groups.json',\n valueKey: 'uuid',\n nameKey: 'name',\n placeholder: 'Search for groups...',\n conditions: {\n visible: (formData) => !formData.all_groups\n }\n },\n all_groups: {\n type: 'checkbox',\n label: 'Remove from All Groups',\n helpText:\n 'Check this to remove the contact from all groups instead of specific ones'\n }\n },\n validate: (formData: FormData): ValidationResult => {\n const errors: { [key: string]: string } = {};\n\n if (\n !formData.all_groups &&\n (!formData.groups || formData.groups.length === 0)\n ) {\n errors.groups =\n 'At least one group must be selected or check \"Remove from All Groups\"';\n }\n\n return {\n valid: Object.keys(errors).length === 0,\n errors\n };\n },\n fromFormData: (formData: FormData): RemoveFromGroup => {\n return {\n uuid: formData.uuid,\n type: 'remove_contact_groups',\n groups: formData.all_groups ? [] : formData.groups || [],\n all_groups: formData.all_groups || false\n };\n }\n};\n"]}
1
+ {"version":3,"file":"remove_contact_groups.js","sourceRoot":"","sources":["../../../../src/flow/actions/remove_contact_groups.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AAChC,OAAO,EAEL,aAAa,EAGb,SAAS,EACV,MAAM,UAAU,CAAC;AAElB,OAAO,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAE9C,MAAM,CAAC,MAAM,qBAAqB,GAAiB;IACjD,IAAI,EAAE,mBAAmB;IACzB,KAAK,EAAE,aAAa,CAAC,QAAQ;IAC7B,SAAS,EAAE,CAAC,SAAS,CAAC,KAAK,EAAE,SAAS,CAAC,OAAO,EAAE,SAAS,CAAC,UAAU,CAAC;IACrE,MAAM,EAAE,CAAC,KAAW,EAAE,MAAuB,EAAE,EAAE;QAC/C,IAAI,MAAM,CAAC,UAAU,EAAE,CAAC;YACtB,OAAO,IAAI,CAAA,mCAAmC,CAAC;QACjD,CAAC;QACD,OAAO,IAAI,CAAA,QAAQ,kBAAkB,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,QAAQ,CAAC;IACxE,CAAC;IACD,UAAU,EAAE,CAAC,MAAuB,EAAE,EAAE;QACtC,OAAO;YACL,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,UAAU,EAAE,MAAM,CAAC,UAAU,IAAI,KAAK;YACtC,MAAM,EAAE,MAAM,CAAC,MAAM,IAAI,IAAI;SAC9B,CAAC;IACJ,CAAC;IACD,IAAI,EAAE;QACJ,MAAM,EAAE;YACN,IAAI,EAAE,QAAQ;YACd,KAAK,EAAE,QAAQ;YACf,QAAQ,EAAE,8CAA8C;YACxD,OAAO,EAAE,EAAE;YACX,KAAK,EAAE,IAAI;YACX,UAAU,EAAE,IAAI;YAChB,QAAQ,EAAE,qBAAqB;YAC/B,QAAQ,EAAE,MAAM;YAChB,OAAO,EAAE,MAAM;YACf,WAAW,EAAE,sBAAsB;YACnC,UAAU,EAAE;gBACV,OAAO,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,UAAU;aAC5C;SACF;QACD,UAAU,EAAE;YACV,IAAI,EAAE,UAAU;YAChB,KAAK,EAAE,wBAAwB;YAC/B,QAAQ,EACN,2EAA2E;SAC9E;KACF;IACD,QAAQ,EAAE,CAAC,QAAkB,EAAoB,EAAE;QACjD,MAAM,MAAM,GAA8B,EAAE,CAAC;QAE7C,IACE,CAAC,QAAQ,CAAC,UAAU;YACpB,CAAC,CAAC,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC,EAClD,CAAC;YACD,MAAM,CAAC,MAAM;gBACX,uEAAuE,CAAC;QAC5E,CAAC;QAED,OAAO;YACL,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,KAAK,CAAC;YACvC,MAAM;SACP,CAAC;IACJ,CAAC;IACD,YAAY,EAAE,CAAC,QAAkB,EAAmB,EAAE;QACpD,OAAO;YACL,IAAI,EAAE,QAAQ,CAAC,IAAI;YACnB,IAAI,EAAE,uBAAuB;YAC7B,MAAM,EAAE,QAAQ,CAAC,UAAU;gBACzB,CAAC,CAAC,EAAE;gBACJ,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC;oBACvC,IAAI,EAAE,CAAC,CAAC,IAAI;oBACZ,IAAI,EAAE,CAAC,CAAC,IAAI;iBACb,CAAC,CAAC;YACP,UAAU,EAAE,QAAQ,CAAC,UAAU,IAAI,KAAK;SACzC,CAAC;IACJ,CAAC;CACF,CAAC","sourcesContent":["import { html } from 'lit-html';\nimport {\n ActionConfig,\n ACTION_GROUPS,\n FormData,\n ValidationResult,\n FlowTypes\n} from '../types';\nimport { Node, RemoveFromGroup } from '../../store/flow-definition';\nimport { renderNamedObjects } from '../utils';\n\nexport const remove_contact_groups: ActionConfig = {\n name: 'Remove from Group',\n group: ACTION_GROUPS.contacts,\n flowTypes: [FlowTypes.VOICE, FlowTypes.MESSAGE, FlowTypes.BACKGROUND],\n render: (_node: Node, action: RemoveFromGroup) => {\n if (action.all_groups) {\n return html`<div>Remove from all groups</div>`;\n }\n return html`<div>${renderNamedObjects(action.groups, 'group')}</div>`;\n },\n toFormData: (action: RemoveFromGroup) => {\n return {\n uuid: action.uuid,\n all_groups: action.all_groups || false,\n groups: action.groups || null\n };\n },\n form: {\n groups: {\n type: 'select',\n label: 'Groups',\n helpText: 'Select the groups to remove the contact from',\n options: [],\n multi: true,\n searchable: true,\n endpoint: '/api/v2/groups.json',\n valueKey: 'uuid',\n nameKey: 'name',\n placeholder: 'Search for groups...',\n conditions: {\n visible: (formData) => !formData.all_groups\n }\n },\n all_groups: {\n type: 'checkbox',\n label: 'Remove from All Groups',\n helpText:\n 'Check this to remove the contact from all groups instead of specific ones'\n }\n },\n validate: (formData: FormData): ValidationResult => {\n const errors: { [key: string]: string } = {};\n\n if (\n !formData.all_groups &&\n (!formData.groups || formData.groups.length === 0)\n ) {\n errors.groups =\n 'At least one group must be selected or check \"Remove from All Groups\"';\n }\n\n return {\n valid: Object.keys(errors).length === 0,\n errors\n };\n },\n fromFormData: (formData: FormData): RemoveFromGroup => {\n return {\n uuid: formData.uuid,\n type: 'remove_contact_groups',\n groups: formData.all_groups\n ? []\n : (formData.groups || []).map((g: any) => ({\n uuid: g.uuid,\n name: g.name\n })),\n all_groups: formData.all_groups || false\n };\n }\n};\n"]}
@@ -1,12 +1,95 @@
1
1
  import { html } from 'lit-html';
2
+ import { unsafeHTML } from 'lit-html/directives/unsafe-html.js';
2
3
  import { ACTION_GROUPS, FlowTypes } from '../types';
4
+ import { renderAudioPlayer } from './audio-player';
3
5
  export const say_msg = {
4
6
  name: 'Say Message',
5
7
  group: ACTION_GROUPS.send,
6
8
  flowTypes: [FlowTypes.VOICE],
7
- render: (_node, _action) => {
8
- // This will need to be implemented based on the actual render logic
9
- return html `<div>Say Message</div>`;
9
+ render: (_node, action) => {
10
+ const text = (action.text || '').replace(/\n/g, '<br>');
11
+ return html `
12
+ ${unsafeHTML(text)}
13
+ ${action.audio_url
14
+ ? html `<div style="margin-top: 0.5em;">
15
+ ${renderAudioPlayer(action.audio_url)}
16
+ </div>`
17
+ : null}
18
+ `;
19
+ },
20
+ form: {
21
+ text: {
22
+ type: 'textarea',
23
+ label: 'Message',
24
+ required: true,
25
+ evaluated: true,
26
+ placeholder: 'Enter message to speak...',
27
+ minHeight: 80
28
+ },
29
+ audio_url: {
30
+ type: 'media',
31
+ label: 'Recording',
32
+ required: false,
33
+ accept: 'audio/*',
34
+ optionalLink: 'Add a recording'
35
+ }
36
+ },
37
+ layout: ['text', 'audio_url'],
38
+ toFormData: (action) => {
39
+ return {
40
+ uuid: action.uuid,
41
+ text: action.text || '',
42
+ audio_url: action.audio_url || ''
43
+ };
44
+ },
45
+ fromFormData: (data) => {
46
+ const result = {
47
+ uuid: data.uuid,
48
+ type: 'say_msg',
49
+ text: data.text || ''
50
+ };
51
+ if (data.audio_url && data.audio_url.trim() !== '') {
52
+ result.audio_url = data.audio_url.trim();
53
+ }
54
+ return result;
55
+ },
56
+ sanitize: (formData) => {
57
+ if (formData.text && typeof formData.text === 'string') {
58
+ formData.text = formData.text.trim();
59
+ }
60
+ },
61
+ localizable: ['text', 'audio_url'],
62
+ toLocalizationFormData: (action, localization) => {
63
+ const formData = {
64
+ uuid: action.uuid
65
+ };
66
+ if (localization.text && Array.isArray(localization.text)) {
67
+ formData.text = localization.text[0] || '';
68
+ }
69
+ else {
70
+ formData.text = '';
71
+ }
72
+ if (localization.audio_url && Array.isArray(localization.audio_url)) {
73
+ formData.audio_url = localization.audio_url[0] || '';
74
+ }
75
+ else {
76
+ formData.audio_url = '';
77
+ }
78
+ return formData;
79
+ },
80
+ fromLocalizationFormData: (formData, action) => {
81
+ const localization = {};
82
+ if (formData.text && formData.text.trim() !== '') {
83
+ if (formData.text !== action.text) {
84
+ localization.text = [formData.text];
85
+ }
86
+ }
87
+ if (formData.audio_url && formData.audio_url.trim() !== '') {
88
+ if (formData.audio_url !== action.audio_url) {
89
+ localization.audio_url = [formData.audio_url];
90
+ }
91
+ }
92
+ return localization;
10
93
  }
11
94
  };
12
95
  //# sourceMappingURL=say_msg.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"say_msg.js","sourceRoot":"","sources":["../../../../src/flow/actions/say_msg.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AAChC,OAAO,EAAgB,aAAa,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAGlE,MAAM,CAAC,MAAM,OAAO,GAAiB;IACnC,IAAI,EAAE,aAAa;IACnB,KAAK,EAAE,aAAa,CAAC,IAAI;IACzB,SAAS,EAAE,CAAC,SAAS,CAAC,KAAK,CAAC;IAC5B,MAAM,EAAE,CAAC,KAAW,EAAE,OAAe,EAAE,EAAE;QACvC,oEAAoE;QACpE,OAAO,IAAI,CAAA,wBAAwB,CAAC;IACtC,CAAC;CACF,CAAC","sourcesContent":["import { html } from 'lit-html';\nimport { ActionConfig, ACTION_GROUPS, FlowTypes } from '../types';\nimport { Node, SayMsg } from '../../store/flow-definition';\n\nexport const say_msg: ActionConfig = {\n name: 'Say Message',\n group: ACTION_GROUPS.send,\n flowTypes: [FlowTypes.VOICE],\n render: (_node: Node, _action: SayMsg) => {\n // This will need to be implemented based on the actual render logic\n return html`<div>Say Message</div>`;\n }\n};\n"]}
1
+ {"version":3,"file":"say_msg.js","sourceRoot":"","sources":["../../../../src/flow/actions/say_msg.ts"],"names":[],"mappings":"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","sourcesContent":["import { html } from 'lit-html';\nimport { unsafeHTML } from 'lit-html/directives/unsafe-html.js';\nimport { ActionConfig, ACTION_GROUPS, FormData, FlowTypes } from '../types';\nimport { Node, SayMsg } from '../../store/flow-definition';\nimport { renderAudioPlayer } from './audio-player';\n\nexport const say_msg: ActionConfig = {\n name: 'Say Message',\n group: ACTION_GROUPS.send,\n flowTypes: [FlowTypes.VOICE],\n render: (_node: Node, action: SayMsg) => {\n const text = (action.text || '').replace(/\\n/g, '<br>');\n return html`\n ${unsafeHTML(text)}\n ${action.audio_url\n ? html`<div style=\"margin-top: 0.5em;\">\n ${renderAudioPlayer(action.audio_url)}\n </div>`\n : null}\n `;\n },\n form: {\n text: {\n type: 'textarea',\n label: 'Message',\n required: true,\n evaluated: true,\n placeholder: 'Enter message to speak...',\n minHeight: 80\n },\n audio_url: {\n type: 'media',\n label: 'Recording',\n required: false,\n accept: 'audio/*',\n optionalLink: 'Add a recording'\n }\n },\n layout: ['text', 'audio_url'],\n toFormData: (action: SayMsg) => {\n return {\n uuid: action.uuid,\n text: action.text || '',\n audio_url: action.audio_url || ''\n };\n },\n fromFormData: (data: FormData) => {\n const result: any = {\n uuid: data.uuid,\n type: 'say_msg',\n text: data.text || ''\n };\n if (data.audio_url && data.audio_url.trim() !== '') {\n result.audio_url = data.audio_url.trim();\n }\n return result as SayMsg;\n },\n sanitize: (formData: FormData): void => {\n if (formData.text && typeof formData.text === 'string') {\n formData.text = formData.text.trim();\n }\n },\n localizable: ['text', 'audio_url'],\n toLocalizationFormData: (\n action: SayMsg,\n localization: Record<string, any>\n ) => {\n const formData: FormData = {\n uuid: action.uuid\n };\n\n if (localization.text && Array.isArray(localization.text)) {\n formData.text = localization.text[0] || '';\n } else {\n formData.text = '';\n }\n\n if (localization.audio_url && Array.isArray(localization.audio_url)) {\n formData.audio_url = localization.audio_url[0] || '';\n } else {\n formData.audio_url = '';\n }\n\n return formData;\n },\n fromLocalizationFormData: (formData: FormData, action: SayMsg) => {\n const localization: Record<string, any> = {};\n\n if (formData.text && formData.text.trim() !== '') {\n if (formData.text !== action.text) {\n localization.text = [formData.text];\n }\n }\n\n if (formData.audio_url && formData.audio_url.trim() !== '') {\n if (formData.audio_url !== action.audio_url) {\n localization.audio_url = [formData.audio_url];\n }\n }\n\n return localization;\n }\n};\n"]}
@@ -62,8 +62,12 @@ export const send_broadcast = {
62
62
  },
63
63
  fromFormData: (formData) => {
64
64
  const recipients = formData.recipients || [];
65
- const contacts = recipients.filter((r) => !r.group);
66
- const groups = recipients.filter((r) => r.group);
65
+ const contacts = recipients
66
+ .filter((r) => !r.group)
67
+ .map((c) => ({ uuid: c.uuid, name: c.name }));
68
+ const groups = recipients
69
+ .filter((r) => r.group)
70
+ .map((g) => ({ uuid: g.uuid, name: g.name }));
67
71
  const result = {
68
72
  uuid: formData.uuid,
69
73
  type: 'send_broadcast',