@nyaruka/temba-components 0.131.2 → 0.131.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (223) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/demo/components/floating-tabs/example.html +400 -0
  3. package/demo/components/flow/index.html +1 -1
  4. package/demo/data/flows/sample-flow.json +41 -2
  5. package/demo/data/flows/voicemail.json +613 -0
  6. package/demo/index.html +6 -0
  7. package/dist/locales/es.js +5 -5
  8. package/dist/locales/es.js.map +1 -1
  9. package/dist/locales/fr.js +5 -5
  10. package/dist/locales/fr.js.map +1 -1
  11. package/dist/locales/locale-codes.js +11 -2
  12. package/dist/locales/locale-codes.js.map +1 -1
  13. package/dist/locales/pt.js +5 -5
  14. package/dist/locales/pt.js.map +1 -1
  15. package/dist/temba-components.js +1109 -535
  16. package/dist/temba-components.js.map +1 -1
  17. package/out-tsc/src/display/FloatingTab.js +167 -0
  18. package/out-tsc/src/display/FloatingTab.js.map +1 -0
  19. package/out-tsc/src/display/ProgressBar.js +22 -2
  20. package/out-tsc/src/display/ProgressBar.js.map +1 -1
  21. package/out-tsc/src/events.js.map +1 -1
  22. package/out-tsc/src/flow/CanvasNode.js +165 -31
  23. package/out-tsc/src/flow/CanvasNode.js.map +1 -1
  24. package/out-tsc/src/flow/Editor.js +857 -3
  25. package/out-tsc/src/flow/Editor.js.map +1 -1
  26. package/out-tsc/src/flow/NodeEditor.js +239 -19
  27. package/out-tsc/src/flow/NodeEditor.js.map +1 -1
  28. package/out-tsc/src/flow/NodeTypeSelector.js +44 -3
  29. package/out-tsc/src/flow/NodeTypeSelector.js.map +1 -1
  30. package/out-tsc/src/flow/StickyNote.js +12 -3
  31. package/out-tsc/src/flow/StickyNote.js.map +1 -1
  32. package/out-tsc/src/flow/actions/add_contact_groups.js +2 -1
  33. package/out-tsc/src/flow/actions/add_contact_groups.js.map +1 -1
  34. package/out-tsc/src/flow/actions/add_contact_urn.js +2 -1
  35. package/out-tsc/src/flow/actions/add_contact_urn.js.map +1 -1
  36. package/out-tsc/src/flow/actions/add_input_labels.js +2 -1
  37. package/out-tsc/src/flow/actions/add_input_labels.js.map +1 -1
  38. package/out-tsc/src/flow/actions/play_audio.js +2 -1
  39. package/out-tsc/src/flow/actions/play_audio.js.map +1 -1
  40. package/out-tsc/src/flow/actions/remove_contact_groups.js +2 -1
  41. package/out-tsc/src/flow/actions/remove_contact_groups.js.map +1 -1
  42. package/out-tsc/src/flow/actions/request_optin.js +1 -0
  43. package/out-tsc/src/flow/actions/request_optin.js.map +1 -1
  44. package/out-tsc/src/flow/actions/say_msg.js +2 -1
  45. package/out-tsc/src/flow/actions/say_msg.js.map +1 -1
  46. package/out-tsc/src/flow/actions/send_broadcast.js +2 -1
  47. package/out-tsc/src/flow/actions/send_broadcast.js.map +1 -1
  48. package/out-tsc/src/flow/actions/send_email.js +2 -1
  49. package/out-tsc/src/flow/actions/send_email.js.map +1 -1
  50. package/out-tsc/src/flow/actions/send_msg.js +93 -3
  51. package/out-tsc/src/flow/actions/send_msg.js.map +1 -1
  52. package/out-tsc/src/flow/actions/set_contact_channel.js +2 -1
  53. package/out-tsc/src/flow/actions/set_contact_channel.js.map +1 -1
  54. package/out-tsc/src/flow/actions/set_contact_field.js +2 -1
  55. package/out-tsc/src/flow/actions/set_contact_field.js.map +1 -1
  56. package/out-tsc/src/flow/actions/set_contact_language.js +2 -1
  57. package/out-tsc/src/flow/actions/set_contact_language.js.map +1 -1
  58. package/out-tsc/src/flow/actions/set_contact_name.js +2 -1
  59. package/out-tsc/src/flow/actions/set_contact_name.js.map +1 -1
  60. package/out-tsc/src/flow/actions/set_contact_status.js +2 -1
  61. package/out-tsc/src/flow/actions/set_contact_status.js.map +1 -1
  62. package/out-tsc/src/flow/actions/set_run_result.js +2 -1
  63. package/out-tsc/src/flow/actions/set_run_result.js.map +1 -1
  64. package/out-tsc/src/flow/actions/start_session.js +2 -1
  65. package/out-tsc/src/flow/actions/start_session.js.map +1 -1
  66. package/out-tsc/src/flow/config.js +2 -10
  67. package/out-tsc/src/flow/config.js.map +1 -1
  68. package/out-tsc/src/flow/nodes/shared.js +54 -0
  69. package/out-tsc/src/flow/nodes/shared.js.map +1 -1
  70. package/out-tsc/src/flow/nodes/split_by_airtime.js +9 -3
  71. package/out-tsc/src/flow/nodes/split_by_airtime.js.map +1 -1
  72. package/out-tsc/src/flow/nodes/split_by_contact_field.js +8 -3
  73. package/out-tsc/src/flow/nodes/split_by_contact_field.js.map +1 -1
  74. package/out-tsc/src/flow/nodes/split_by_expression.js +8 -3
  75. package/out-tsc/src/flow/nodes/split_by_expression.js.map +1 -1
  76. package/out-tsc/src/flow/nodes/split_by_groups.js +8 -3
  77. package/out-tsc/src/flow/nodes/split_by_groups.js.map +1 -1
  78. package/out-tsc/src/flow/nodes/split_by_intent.js +3 -2
  79. package/out-tsc/src/flow/nodes/split_by_intent.js.map +1 -1
  80. package/out-tsc/src/flow/nodes/split_by_llm.js +9 -2
  81. package/out-tsc/src/flow/nodes/split_by_llm.js.map +1 -1
  82. package/out-tsc/src/flow/nodes/split_by_llm_categorize.js +9 -2
  83. package/out-tsc/src/flow/nodes/split_by_llm_categorize.js.map +1 -1
  84. package/out-tsc/src/flow/nodes/split_by_random.js +8 -2
  85. package/out-tsc/src/flow/nodes/split_by_random.js.map +1 -1
  86. package/out-tsc/src/flow/nodes/split_by_resthook.js +8 -3
  87. package/out-tsc/src/flow/nodes/split_by_resthook.js.map +1 -1
  88. package/out-tsc/src/flow/nodes/split_by_run_result.js +8 -3
  89. package/out-tsc/src/flow/nodes/split_by_run_result.js.map +1 -1
  90. package/out-tsc/src/flow/nodes/split_by_scheme.js +8 -3
  91. package/out-tsc/src/flow/nodes/split_by_scheme.js.map +1 -1
  92. package/out-tsc/src/flow/nodes/split_by_subflow.js +8 -2
  93. package/out-tsc/src/flow/nodes/split_by_subflow.js.map +1 -1
  94. package/out-tsc/src/flow/nodes/split_by_ticket.js +8 -2
  95. package/out-tsc/src/flow/nodes/split_by_ticket.js.map +1 -1
  96. package/out-tsc/src/flow/nodes/split_by_webhook.js +8 -2
  97. package/out-tsc/src/flow/nodes/split_by_webhook.js.map +1 -1
  98. package/out-tsc/src/flow/nodes/wait_for_digits.js +3 -2
  99. package/out-tsc/src/flow/nodes/wait_for_digits.js.map +1 -1
  100. package/out-tsc/src/flow/nodes/wait_for_menu.js +3 -2
  101. package/out-tsc/src/flow/nodes/wait_for_menu.js.map +1 -1
  102. package/out-tsc/src/flow/nodes/wait_for_response.js +8 -3
  103. package/out-tsc/src/flow/nodes/wait_for_response.js.map +1 -1
  104. package/out-tsc/src/flow/types.js +15 -0
  105. package/out-tsc/src/flow/types.js.map +1 -1
  106. package/out-tsc/src/layout/FloatingWindow.js +346 -0
  107. package/out-tsc/src/layout/FloatingWindow.js.map +1 -0
  108. package/out-tsc/src/live/ContactChat.js +3 -19
  109. package/out-tsc/src/live/ContactChat.js.map +1 -1
  110. package/out-tsc/src/locales/es.js +5 -5
  111. package/out-tsc/src/locales/es.js.map +1 -1
  112. package/out-tsc/src/locales/fr.js +5 -5
  113. package/out-tsc/src/locales/fr.js.map +1 -1
  114. package/out-tsc/src/locales/locale-codes.js +11 -2
  115. package/out-tsc/src/locales/locale-codes.js.map +1 -1
  116. package/out-tsc/src/locales/pt.js +5 -5
  117. package/out-tsc/src/locales/pt.js.map +1 -1
  118. package/out-tsc/src/store/AppState.js +67 -0
  119. package/out-tsc/src/store/AppState.js.map +1 -1
  120. package/out-tsc/temba-modules.js +4 -0
  121. package/out-tsc/temba-modules.js.map +1 -1
  122. package/out-tsc/test/temba-floating-tab.test.js +91 -0
  123. package/out-tsc/test/temba-floating-tab.test.js.map +1 -0
  124. package/out-tsc/test/temba-floating-window.test.js +301 -0
  125. package/out-tsc/test/temba-floating-window.test.js.map +1 -0
  126. package/out-tsc/test/temba-flow-editor-node.test.js +117 -0
  127. package/out-tsc/test/temba-flow-editor-node.test.js.map +1 -1
  128. package/out-tsc/test/temba-localization.test.js +471 -0
  129. package/out-tsc/test/temba-localization.test.js.map +1 -0
  130. package/out-tsc/test/temba-node-type-selector.test.js +150 -0
  131. package/out-tsc/test/temba-node-type-selector.test.js.map +1 -1
  132. package/out-tsc/test/utils.test.js +18 -0
  133. package/out-tsc/test/utils.test.js.map +1 -1
  134. package/package.json +1 -1
  135. package/screenshots/truth/floating-tab/default.png +0 -0
  136. package/screenshots/truth/floating-tab/gray.png +0 -0
  137. package/screenshots/truth/floating-tab/green.png +0 -0
  138. package/screenshots/truth/floating-tab/hidden.png +0 -0
  139. package/screenshots/truth/floating-tab/hover.png +0 -0
  140. package/screenshots/truth/floating-tab/purple.png +0 -0
  141. package/screenshots/truth/floating-window/chromeless.png +0 -0
  142. package/screenshots/truth/floating-window/custom-size.png +0 -0
  143. package/screenshots/truth/floating-window/default.png +0 -0
  144. package/screenshots/truth/floating-window/with-header.png +0 -0
  145. package/screenshots/truth/node-type-selector/action-mode.png +0 -0
  146. package/screenshots/truth/node-type-selector/split-mode.png +0 -0
  147. package/screenshots/truth/nodes/split_by_llm_categorize/editor/feedback-categorization.png +0 -0
  148. package/src/display/FloatingTab.ts +174 -0
  149. package/src/display/ProgressBar.ts +22 -2
  150. package/src/events.ts +2 -4
  151. package/src/flow/CanvasNode.ts +190 -32
  152. package/src/flow/Editor.ts +1040 -3
  153. package/src/flow/NodeEditor.ts +317 -19
  154. package/src/flow/NodeTypeSelector.ts +47 -3
  155. package/src/flow/StickyNote.ts +12 -3
  156. package/src/flow/actions/add_contact_groups.ts +2 -1
  157. package/src/flow/actions/add_contact_urn.ts +3 -1
  158. package/src/flow/actions/add_input_labels.ts +2 -1
  159. package/src/flow/actions/play_audio.ts +2 -1
  160. package/src/flow/actions/remove_contact_groups.ts +3 -1
  161. package/src/flow/actions/request_optin.ts +1 -0
  162. package/src/flow/actions/say_msg.ts +2 -1
  163. package/src/flow/actions/send_broadcast.ts +2 -1
  164. package/src/flow/actions/send_email.ts +3 -1
  165. package/src/flow/actions/send_msg.ts +134 -3
  166. package/src/flow/actions/set_contact_channel.ts +2 -1
  167. package/src/flow/actions/set_contact_field.ts +2 -1
  168. package/src/flow/actions/set_contact_language.ts +3 -1
  169. package/src/flow/actions/set_contact_name.ts +2 -1
  170. package/src/flow/actions/set_contact_status.ts +2 -1
  171. package/src/flow/actions/set_run_result.ts +2 -1
  172. package/src/flow/actions/start_session.ts +3 -1
  173. package/src/flow/config.ts +2 -12
  174. package/src/flow/nodes/shared.ts +70 -1
  175. package/src/flow/nodes/split_by_airtime.ts +20 -3
  176. package/src/flow/nodes/split_by_contact_field.ts +13 -3
  177. package/src/flow/nodes/split_by_expression.ts +13 -3
  178. package/src/flow/nodes/split_by_groups.ts +13 -3
  179. package/src/flow/nodes/split_by_intent.ts +3 -2
  180. package/src/flow/nodes/split_by_llm.ts +19 -2
  181. package/src/flow/nodes/split_by_llm_categorize.ts +19 -2
  182. package/src/flow/nodes/split_by_random.ts +12 -2
  183. package/src/flow/nodes/split_by_resthook.ts +13 -3
  184. package/src/flow/nodes/split_by_run_result.ts +13 -3
  185. package/src/flow/nodes/split_by_scheme.ts +13 -3
  186. package/src/flow/nodes/split_by_subflow.ts +12 -2
  187. package/src/flow/nodes/split_by_ticket.ts +12 -2
  188. package/src/flow/nodes/split_by_webhook.ts +12 -2
  189. package/src/flow/nodes/wait_for_digits.ts +3 -2
  190. package/src/flow/nodes/wait_for_menu.ts +3 -2
  191. package/src/flow/nodes/wait_for_response.ts +13 -3
  192. package/src/flow/types.ts +47 -0
  193. package/src/layout/FloatingWindow.ts +386 -0
  194. package/src/live/ContactChat.ts +4 -19
  195. package/src/locales/es.ts +18 -13
  196. package/src/locales/fr.ts +18 -13
  197. package/src/locales/locale-codes.ts +11 -2
  198. package/src/locales/pt.ts +18 -13
  199. package/src/store/AppState.ts +104 -0
  200. package/static/api/llms.json +18 -0
  201. package/temba-modules.ts +4 -0
  202. package/test/temba-floating-tab.test.ts +110 -0
  203. package/test/temba-floating-window.test.ts +477 -0
  204. package/test/temba-flow-editor-node.test.ts +144 -0
  205. package/test/temba-localization.test.ts +611 -0
  206. package/test/temba-node-type-selector.test.ts +203 -0
  207. package/test/utils.test.ts +20 -0
  208. package/test-assets/contacts/history.json +5 -6
  209. package/test-assets/select/llms.json +2 -2
  210. package/web-dev-server.config.mjs +47 -1
  211. package/web-test-runner.config.mjs +0 -1
  212. package/out-tsc/src/flow/nodes/wait_for_audio.js +0 -7
  213. package/out-tsc/src/flow/nodes/wait_for_audio.js.map +0 -1
  214. package/out-tsc/src/flow/nodes/wait_for_image.js +0 -7
  215. package/out-tsc/src/flow/nodes/wait_for_image.js.map +0 -1
  216. package/out-tsc/src/flow/nodes/wait_for_location.js +0 -7
  217. package/out-tsc/src/flow/nodes/wait_for_location.js.map +0 -1
  218. package/out-tsc/src/flow/nodes/wait_for_video.js +0 -7
  219. package/out-tsc/src/flow/nodes/wait_for_video.js.map +0 -1
  220. package/src/flow/nodes/wait_for_audio.ts +0 -7
  221. package/src/flow/nodes/wait_for_image.ts +0 -7
  222. package/src/flow/nodes/wait_for_location.ts +0 -7
  223. package/src/flow/nodes/wait_for_video.ts +0 -7
@@ -77,6 +77,15 @@ export class ProgressBar extends RapidElement {
77
77
  display: none;
78
78
  }
79
79
 
80
+ .meter.static > span:after {
81
+ display: none;
82
+ animation: none;
83
+ }
84
+
85
+ .meter.static > span {
86
+ background-image: none;
87
+ }
88
+
80
89
  @keyframes move {
81
90
  0% {
82
91
  background-position: 0 0;
@@ -154,6 +163,9 @@ export class ProgressBar extends RapidElement {
154
163
  @property({ type: String })
155
164
  message: string;
156
165
 
166
+ @property({ type: Boolean })
167
+ animated = true;
168
+
157
169
  public updated(
158
170
  changes: PropertyValueMap<any> | Map<PropertyKey, unknown>
159
171
  ): void {
@@ -162,7 +174,7 @@ export class ProgressBar extends RapidElement {
162
174
  this.showEstimatedCompletion = this.estimatedCompletionDate > new Date();
163
175
  }
164
176
 
165
- if (changes.has('current')) {
177
+ if (changes.has('current') || changes.has('total')) {
166
178
  const pct = Math.floor(Math.min((this.current / this.total) * 100, 100));
167
179
  if (Number.isNaN(pct)) {
168
180
  this.showPercentage = false;
@@ -176,8 +188,16 @@ export class ProgressBar extends RapidElement {
176
188
  }
177
189
 
178
190
  public render(): TemplateResult {
191
+ const meterClasses = [
192
+ 'meter',
193
+ this.done ? 'done' : '',
194
+ this.animated ? '' : 'static'
195
+ ]
196
+ .filter(Boolean)
197
+ .join(' ');
198
+
179
199
  return html`<div class="wrapper ${this.done ? 'complete' : ''}">
180
- <div class="meter ${this.done ? 'done' : ''}">
200
+ <div class="${meterClasses}">
181
201
  ${this.message
182
202
  ? html`<div class="message">${this.message}</div>`
183
203
  : null}
package/src/events.ts CHANGED
@@ -57,8 +57,7 @@ export interface ChatStartedEvent extends ContactEvent {
57
57
  export interface MsgEvent extends ContactEvent {
58
58
  msg: Msg;
59
59
  optin?: ObjectReference;
60
- _status?: string | { status: string; changed_on: string; reason: string };
61
- _failed_reason?: string; // deprecated
60
+ _status?: { created_on: string; status: string; reason: string };
62
61
  _logs_url?: string;
63
62
  }
64
63
 
@@ -107,10 +106,9 @@ export interface AirtimeTransferredEvent extends ContactEvent {
107
106
  export type CallStartedEvent = ContactEvent;
108
107
 
109
108
  export interface ContactHistoryPage {
109
+ events: ContactEvent[];
110
110
  has_older: boolean;
111
111
  recent_only: boolean;
112
112
  next_before: number;
113
113
  next_after: number;
114
- start_date: Date;
115
- events: ContactEvent[];
116
114
  }
@@ -9,6 +9,7 @@ import { getClasses } from '../utils';
9
9
  import { Plumber } from './Plumber';
10
10
  import { getStore } from '../store/Store';
11
11
  import { CustomEventType } from '../interfaces';
12
+ import { AppState, fromStore, zustand } from '../store/AppState';
12
13
 
13
14
  const DRAG_THRESHOLD = 5;
14
15
 
@@ -26,6 +27,22 @@ export class CanvasNode extends RapidElement {
26
27
  @property({ type: Object })
27
28
  private ui: NodeUI;
28
29
 
30
+ @fromStore(zustand, (state: AppState) => state.isTranslating)
31
+ private isTranslating!: boolean;
32
+
33
+ @fromStore(zustand, (state: AppState) => state.languageCode)
34
+ private languageCode!: string;
35
+
36
+ @fromStore(zustand, (state: AppState) => state.flowDefinition)
37
+ private flowDefinition!: any;
38
+
39
+ @fromStore(
40
+ zustand,
41
+ (state: AppState) =>
42
+ state.flowDefinition?._ui?.translation_filters?.categories || false
43
+ )
44
+ private includeCategoriesInTranslation!: boolean;
45
+
29
46
  // Track exits that are in "removing" state
30
47
  private exitRemovalTimeouts: Map<string, number> = new Map();
31
48
 
@@ -160,6 +177,20 @@ export class CanvasNode extends RapidElement {
160
177
 
161
178
  .node.execute-actions temba-sortable-list .action:last-child .body {
162
179
  padding-bottom: 1.5em;
180
+ }
181
+
182
+ /* Localization indicators */
183
+ .action.localizable:not(.has-localization) .action-content {
184
+ background: #fff8dc !important; /* Light yellow background for localizable but not yet localized */
185
+ }
186
+
187
+ .non-localizable {
188
+ opacity: 0.25;
189
+ pointer-events: none;
190
+ }
191
+
192
+ .action.non-localizable .action-content {
193
+ cursor: not-allowed;
163
194
  }
164
195
 
165
196
  .action .drag-handle {
@@ -228,6 +259,10 @@ export class CanvasNode extends RapidElement {
228
259
  margin: 0.2em;
229
260
  }
230
261
 
262
+ .router-section {
263
+ /* Container for router and categories */
264
+ }
265
+
231
266
  .categories {
232
267
  display: flex;
233
268
  flex-direction: row;
@@ -244,6 +279,11 @@ export class CanvasNode extends RapidElement {
244
279
  flex-direction: column;
245
280
  }
246
281
 
282
+ /* Localizable category - yellow background */
283
+ .category.localizable {
284
+ background-color: #fff8dc;
285
+ }
286
+
247
287
  .action-exits {
248
288
  padding-bottom: 0.7em;
249
289
  margin-top: -0.7em;
@@ -1153,19 +1193,21 @@ export class CanvasNode extends RapidElement {
1153
1193
  ? ACTION_GROUP_METADATA[config.group]?.color
1154
1194
  : '#aaaaaa';
1155
1195
  return html`<div class="cn-title" style="background:${color}">
1156
- ${this.node?.actions?.length > 1
1196
+ ${!this.isTranslating && this.node?.actions?.length > 1
1157
1197
  ? html`<temba-icon class="drag-handle" name="sort"></temba-icon>`
1158
1198
  : html`<div class="title-spacer"></div>`}
1159
1199
 
1160
1200
  <div class="name">${isRemoving ? 'Remove?' : config.name}</div>
1161
- <div
1162
- class="remove-button"
1163
- @click=${(e: MouseEvent) =>
1164
- this.handleActionRemoveClick(e, action, index)}
1165
- title="Remove action"
1166
- >
1167
-
1168
- </div>
1201
+ ${!this.isTranslating
1202
+ ? html`<div
1203
+ class="remove-button"
1204
+ @click=${(e: MouseEvent) =>
1205
+ this.handleActionRemoveClick(e, action, index)}
1206
+ title="Remove action"
1207
+ >
1208
+
1209
+ </div>`
1210
+ : html`<div class="title-spacer"></div>`}
1169
1211
  </div>`;
1170
1212
  }
1171
1213
 
@@ -1192,13 +1234,15 @@ export class CanvasNode extends RapidElement {
1192
1234
  ? config.renderTitle(node, ui)
1193
1235
  : html`${config.name}`}
1194
1236
  </div>
1195
- <div
1196
- class="remove-button"
1197
- @click=${(e: MouseEvent) => this.handleNodeRemoveClick(e)}
1198
- title="Remove node"
1199
- >
1200
-
1201
- </div>
1237
+ ${!this.isTranslating
1238
+ ? html`<div
1239
+ class="remove-button"
1240
+ @click=${(e: MouseEvent) => this.handleNodeRemoveClick(e)}
1241
+ title="Remove node"
1242
+ >
1243
+
1244
+ </div>`
1245
+ : html`<div class="title-spacer"></div>`}
1202
1246
  </div>`;
1203
1247
  }
1204
1248
 
@@ -1210,25 +1254,93 @@ export class CanvasNode extends RapidElement {
1210
1254
  ></div>`;
1211
1255
  }
1212
1256
 
1257
+ /**
1258
+ * Get the localized version of an action if translating, otherwise return the original action.
1259
+ * Falls back to base language values if no localization exists for a field.
1260
+ */
1261
+ private getLocalizedAction(action: Action): Action {
1262
+ // If not translating or no flow definition, return original action
1263
+ if (
1264
+ !this.isTranslating ||
1265
+ !this.flowDefinition ||
1266
+ !this.languageCode ||
1267
+ this.languageCode === this.flowDefinition.language
1268
+ ) {
1269
+ return action;
1270
+ }
1271
+
1272
+ // Check if there's localization for this action
1273
+ const localization =
1274
+ this.flowDefinition?.localization?.[this.languageCode]?.[action.uuid];
1275
+
1276
+ if (!localization) {
1277
+ // No localization available, return original action
1278
+ return action;
1279
+ }
1280
+
1281
+ // Create a new action with localized values, falling back to base language
1282
+ const localizedAction = { ...action };
1283
+
1284
+ // Apply localized values for each field
1285
+ Object.keys(localization).forEach((field) => {
1286
+ const localizedValue = localization[field];
1287
+ if (Array.isArray(localizedValue)) {
1288
+ // Localized values are stored as arrays
1289
+ if (localizedValue.length > 0) {
1290
+ // For single-value fields like 'text', take the first element
1291
+ // For array fields like 'quick_replies', use the whole array
1292
+ if (Array.isArray(action[field])) {
1293
+ localizedAction[field] = localizedValue;
1294
+ } else {
1295
+ localizedAction[field] = localizedValue[0];
1296
+ }
1297
+ }
1298
+ }
1299
+ });
1300
+
1301
+ return localizedAction;
1302
+ }
1303
+
1213
1304
  private renderAction(node: Node, action: Action, index: number) {
1214
1305
  const config = ACTION_CONFIG[action.type];
1215
1306
  const isRemoving = this.actionRemovingState.has(action.uuid);
1307
+ const isLocalizable = config?.localizable && config.localizable.length > 0;
1308
+ const isDisabled = this.isTranslating && !isLocalizable;
1309
+
1310
+ // Check if this action has localization data
1311
+ const hasLocalization =
1312
+ this.isTranslating &&
1313
+ this.flowDefinition?.localization?.[this.languageCode]?.[action.uuid];
1314
+
1315
+ // Get the localized action if translating
1316
+ const displayAction = this.getLocalizedAction(action);
1216
1317
 
1217
1318
  if (config) {
1218
- return html`<div
1219
- class="action sortable ${action.type} ${isRemoving ? 'removing' : ''}"
1220
- id="action-${index}"
1221
- >
1319
+ const classes = [
1320
+ 'action',
1321
+ 'sortable',
1322
+ action.type,
1323
+ isRemoving ? 'removing' : '',
1324
+ isLocalizable && this.isTranslating ? 'localizable' : '',
1325
+ hasLocalization ? 'has-localization' : '',
1326
+ isDisabled ? 'non-localizable' : ''
1327
+ ]
1328
+ .filter(Boolean)
1329
+ .join(' ');
1330
+
1331
+ return html`<div class="${classes}" id="action-${index}">
1222
1332
  <div
1223
1333
  class="action-content"
1224
- @mousedown=${(e: MouseEvent) => this.handleActionMouseDown(e, action)}
1225
- @mouseup=${(e: MouseEvent) => this.handleActionMouseUp(e, action)}
1226
- style="cursor: pointer; background: #fff"
1334
+ @mousedown=${(e: MouseEvent) =>
1335
+ !isDisabled && this.handleActionMouseDown(e, action)}
1336
+ @mouseup=${(e: MouseEvent) =>
1337
+ !isDisabled && this.handleActionMouseUp(e, action)}
1338
+ style="cursor: ${isDisabled ? 'not-allowed' : 'pointer'}"
1227
1339
  >
1228
1340
  ${this.renderTitle(config, action, index, isRemoving)}
1229
1341
  <div class="body">
1230
1342
  ${config.render
1231
- ? config.render(node, action)
1343
+ ? config.render(node, displayAction)
1232
1344
  : html`<pre>${action.type}</pre>`}
1233
1345
  </div>
1234
1346
  </div>
@@ -1299,6 +1411,10 @@ export class CanvasNode extends RapidElement {
1299
1411
  return null;
1300
1412
  }
1301
1413
 
1414
+ // Check if this node type supports category localization
1415
+ const nodeConfig = NODE_CONFIG[this.ui?.type];
1416
+ const supportsLocalization = nodeConfig?.localizable === 'categories';
1417
+
1302
1418
  return html`<div class="categories">
1303
1419
  ${repeat(
1304
1420
  node.router.categories,
@@ -1308,13 +1424,44 @@ export class CanvasNode extends RapidElement {
1308
1424
  (exit: Exit) => exit.uuid == category.exit_uuid
1309
1425
  );
1310
1426
 
1427
+ // Get localized category name if translating
1428
+ let displayName = category.name;
1429
+ let isLocalized = false;
1430
+
1431
+ if (
1432
+ this.isTranslating &&
1433
+ this.languageCode !== 'eng' &&
1434
+ supportsLocalization
1435
+ ) {
1436
+ const localization =
1437
+ this.flowDefinition?.localization?.[this.languageCode];
1438
+ if (localization && localization[category.uuid]) {
1439
+ const categoryLocalization = localization[category.uuid];
1440
+ if (categoryLocalization.name && categoryLocalization.name[0]) {
1441
+ displayName = categoryLocalization.name[0];
1442
+ isLocalized = true;
1443
+ }
1444
+ }
1445
+ }
1446
+
1447
+ // Category is localizable if: translating, supports localization, categories enabled, and not base language
1448
+ const isLocalizable =
1449
+ this.isTranslating &&
1450
+ this.languageCode !== 'eng' &&
1451
+ supportsLocalization &&
1452
+ this.includeCategoriesInTranslation &&
1453
+ !isLocalized;
1454
+
1311
1455
  return html`<div
1312
- class="category"
1456
+ class=${getClasses({
1457
+ category: true,
1458
+ localizable: isLocalizable
1459
+ })}
1313
1460
  @mousedown=${(e: MouseEvent) => this.handleNodeMouseDown(e)}
1314
1461
  @mouseup=${(e: MouseEvent) => this.handleNodeMouseUp(e)}
1315
1462
  style="cursor: pointer;"
1316
1463
  >
1317
- <div class="cn-title">${category.name}</div>
1464
+ <div class="cn-title">${displayName}</div>
1318
1465
  ${this.renderExit(exit)}
1319
1466
  </div>`;
1320
1467
  }
@@ -1343,12 +1490,21 @@ export class CanvasNode extends RapidElement {
1343
1490
 
1344
1491
  const nodeConfig = NODE_CONFIG[this.ui.type];
1345
1492
 
1493
+ // Check if this node should be disabled (grayed out)
1494
+ const supportsLocalization = nodeConfig?.localizable === 'categories';
1495
+ const isNodeDisabled =
1496
+ this.isTranslating &&
1497
+ supportsLocalization &&
1498
+ !this.includeCategoriesInTranslation;
1499
+
1346
1500
  return html`
1347
1501
  <div
1348
1502
  id="${this.node.uuid}"
1349
- class="node ${this.ui.type === 'execute_actions'
1350
- ? 'execute-actions'
1351
- : ''}"
1503
+ class=${getClasses({
1504
+ node: true,
1505
+ 'execute-actions': this.ui.type === 'execute_actions',
1506
+ 'non-localizable': isNodeDisabled
1507
+ })}
1352
1508
  style="left:${this.ui.position.left}px;top:${this.ui.position.top}px"
1353
1509
  >
1354
1510
  ${nodeConfig && nodeConfig.type !== 'execute_actions'
@@ -1387,8 +1543,10 @@ export class CanvasNode extends RapidElement {
1387
1543
  )}`
1388
1544
  : ''}
1389
1545
  ${this.node.router
1390
- ? html` ${this.renderRouter(this.node.router, this.ui)}
1391
- ${this.renderCategories(this.node)}`
1546
+ ? html`<div class="router-section">
1547
+ ${this.renderRouter(this.node.router, this.ui)}
1548
+ ${this.renderCategories(this.node)}
1549
+ </div>`
1392
1550
  : html`<div class="action-exits">
1393
1551
  ${repeat(
1394
1552
  this.node.exits,
@@ -1396,7 +1554,7 @@ export class CanvasNode extends RapidElement {
1396
1554
  (exit) => this.renderExit(exit)
1397
1555
  )}
1398
1556
  </div>`}
1399
- ${this.ui.type === 'execute_actions'
1557
+ ${this.ui.type === 'execute_actions' && !this.isTranslating
1400
1558
  ? html`<div
1401
1559
  class="add-action-button"
1402
1560
  @click=${(e: MouseEvent) => this.handleAddActionClick(e)}