@nyaruka/temba-components 0.135.9 → 0.136.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (144) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/demo/components/webchat/example.html +4 -2
  3. package/dist/static/svg/index.svg +1 -1
  4. package/dist/temba-components.js +1351 -322
  5. package/dist/temba-components.js.map +1 -1
  6. package/out-tsc/src/Icons.js +2 -1
  7. package/out-tsc/src/Icons.js.map +1 -1
  8. package/out-tsc/src/display/FloatingTab.js +2 -6
  9. package/out-tsc/src/display/FloatingTab.js.map +1 -1
  10. package/out-tsc/src/flow/CanvasNode.js +29 -1
  11. package/out-tsc/src/flow/CanvasNode.js.map +1 -1
  12. package/out-tsc/src/flow/Editor.js +229 -5
  13. package/out-tsc/src/flow/Editor.js.map +1 -1
  14. package/out-tsc/src/flow/Plumber.js +320 -1
  15. package/out-tsc/src/flow/Plumber.js.map +1 -1
  16. package/out-tsc/src/interfaces.js +1 -0
  17. package/out-tsc/src/interfaces.js.map +1 -1
  18. package/out-tsc/src/layout/FloatingWindow.js +30 -8
  19. package/out-tsc/src/layout/FloatingWindow.js.map +1 -1
  20. package/out-tsc/src/simulator/Simulator.js +1861 -0
  21. package/out-tsc/src/simulator/Simulator.js.map +1 -0
  22. package/out-tsc/src/store/AppState.js +66 -0
  23. package/out-tsc/src/store/AppState.js.map +1 -1
  24. package/out-tsc/src/utils.js +48 -0
  25. package/out-tsc/src/utils.js.map +1 -1
  26. package/out-tsc/temba-modules.js +2 -0
  27. package/out-tsc/temba-modules.js.map +1 -1
  28. package/out-tsc/test/temba-appstate-node-sorting.test.js +430 -0
  29. package/out-tsc/test/temba-appstate-node-sorting.test.js.map +1 -0
  30. package/out-tsc/test/temba-floating-tab.test.js +0 -9
  31. package/out-tsc/test/temba-floating-tab.test.js.map +1 -1
  32. package/out-tsc/test/temba-flow-editor.test.js +262 -1
  33. package/out-tsc/test/temba-flow-editor.test.js.map +1 -1
  34. package/out-tsc/test/temba-flow-plumber-connections.test.js +3 -1
  35. package/out-tsc/test/temba-flow-plumber-connections.test.js.map +1 -1
  36. package/out-tsc/test/temba-flow-plumber.test.js +3 -1
  37. package/out-tsc/test/temba-flow-plumber.test.js.map +1 -1
  38. package/out-tsc/test/temba-simulator.test.js +642 -0
  39. package/out-tsc/test/temba-simulator.test.js.map +1 -0
  40. package/out-tsc/test/utils.test.js +1 -1
  41. package/out-tsc/test/utils.test.js.map +1 -1
  42. package/package.json +1 -1
  43. package/screenshots/truth/actions/add_contact_groups/render/descriptive-group-names.png +0 -0
  44. package/screenshots/truth/actions/add_contact_groups/render/long-group-names.png +0 -0
  45. package/screenshots/truth/actions/add_contact_groups/render/many-groups.png +0 -0
  46. package/screenshots/truth/actions/add_contact_groups/render/multiple-groups.png +0 -0
  47. package/screenshots/truth/actions/add_contact_groups/render/single-group.png +0 -0
  48. package/screenshots/truth/actions/add_contact_urn/render/expression-facebook.png +0 -0
  49. package/screenshots/truth/actions/add_contact_urn/render/expression-phone.png +0 -0
  50. package/screenshots/truth/actions/add_contact_urn/render/facebook-id.png +0 -0
  51. package/screenshots/truth/actions/add_contact_urn/render/instagram-handle.png +0 -0
  52. package/screenshots/truth/actions/add_contact_urn/render/line-id.png +0 -0
  53. package/screenshots/truth/actions/add_contact_urn/render/phone-number.png +0 -0
  54. package/screenshots/truth/actions/add_contact_urn/render/telegram-id.png +0 -0
  55. package/screenshots/truth/actions/add_contact_urn/render/viber-id.png +0 -0
  56. package/screenshots/truth/actions/add_contact_urn/render/wechat-id.png +0 -0
  57. package/screenshots/truth/actions/add_contact_urn/render/whatsapp.png +0 -0
  58. package/screenshots/truth/actions/remove_contact_groups/render/cleanup-groups.png +0 -0
  59. package/screenshots/truth/actions/remove_contact_groups/render/long-descriptive-group-names.png +0 -0
  60. package/screenshots/truth/actions/remove_contact_groups/render/many-groups.png +0 -0
  61. package/screenshots/truth/actions/remove_contact_groups/render/multiple-groups.png +0 -0
  62. package/screenshots/truth/actions/remove_contact_groups/render/remove-from-all-groups.png +0 -0
  63. package/screenshots/truth/actions/remove_contact_groups/render/single-group.png +0 -0
  64. package/screenshots/truth/actions/send_broadcast/render/contacts-only.png +0 -0
  65. package/screenshots/truth/actions/send_broadcast/render/groups-and-contacts.png +0 -0
  66. package/screenshots/truth/actions/send_broadcast/render/groups-only.png +0 -0
  67. package/screenshots/truth/actions/send_broadcast/render/many-groups.png +0 -0
  68. package/screenshots/truth/actions/send_broadcast/render/multiline-text.png +0 -0
  69. package/screenshots/truth/actions/send_email/render/complex-business-email.png +0 -0
  70. package/screenshots/truth/actions/send_email/render/empty-body.png +0 -0
  71. package/screenshots/truth/actions/send_email/render/empty-subject.png +0 -0
  72. package/screenshots/truth/actions/send_email/render/long-subject.png +0 -0
  73. package/screenshots/truth/actions/send_email/render/multiline-body.png +0 -0
  74. package/screenshots/truth/actions/send_email/render/multiple-recipients.png +0 -0
  75. package/screenshots/truth/actions/send_email/render/simple-email.png +0 -0
  76. package/screenshots/truth/actions/send_email/render/with-expressions.png +0 -0
  77. package/screenshots/truth/actions/send_msg/render/long-quick-replies.png +0 -0
  78. package/screenshots/truth/actions/send_msg/render/multiline-text-with-replies.png +0 -0
  79. package/screenshots/truth/actions/send_msg/render/simple-text.png +0 -0
  80. package/screenshots/truth/actions/send_msg/render/text-with-linebreaks.png +0 -0
  81. package/screenshots/truth/actions/send_msg/render/text-with-many-quick-replies.png +0 -0
  82. package/screenshots/truth/actions/send_msg/render/text-with-quick-replies.png +0 -0
  83. package/screenshots/truth/actions/send_msg/render/text-without-quick-replies.png +0 -0
  84. package/screenshots/truth/actions/start_session/render/contact-query.png +0 -0
  85. package/screenshots/truth/actions/start_session/render/contacts-only.png +0 -0
  86. package/screenshots/truth/actions/start_session/render/create-contact.png +0 -0
  87. package/screenshots/truth/actions/start_session/render/groups-and-contacts.png +0 -0
  88. package/screenshots/truth/actions/start_session/render/groups-only.png +0 -0
  89. package/screenshots/truth/actions/start_session/render/many-recipients.png +0 -0
  90. package/screenshots/truth/floating-tab/gray.png +0 -0
  91. package/screenshots/truth/floating-tab/green.png +0 -0
  92. package/screenshots/truth/floating-tab/purple.png +0 -0
  93. package/screenshots/truth/nodes/split_by_llm/render/information-extraction.png +0 -0
  94. package/screenshots/truth/nodes/split_by_llm/render/sentiment-analysis.png +0 -0
  95. package/screenshots/truth/nodes/split_by_llm/render/summarization.png +0 -0
  96. package/screenshots/truth/nodes/split_by_llm/render/translation-task.png +0 -0
  97. package/screenshots/truth/nodes/split_by_llm_categorize/editor/feedback-categorization.png +0 -0
  98. package/screenshots/truth/nodes/split_by_llm_categorize/render/basic-categorization.png +0 -0
  99. package/screenshots/truth/nodes/split_by_llm_categorize/render/custom-input-and-result-name.png +0 -0
  100. package/screenshots/truth/nodes/split_by_llm_categorize/render/feedback-categorization.png +0 -0
  101. package/screenshots/truth/nodes/split_by_llm_categorize/render/many-categories.png +0 -0
  102. package/screenshots/truth/nodes/split_by_llm_categorize/render/minimal-categories.png +0 -0
  103. package/screenshots/truth/nodes/split_by_random/render/ab-test-multiple-variants.png +0 -0
  104. package/screenshots/truth/nodes/split_by_random/render/sampling-split.png +0 -0
  105. package/screenshots/truth/nodes/split_by_random/render/three-way-split.png +0 -0
  106. package/screenshots/truth/nodes/split_by_random/render/two-bucket-split.png +0 -0
  107. package/screenshots/truth/nodes/wait_for_digits/render/basic-digits-wait.png +0 -0
  108. package/screenshots/truth/nodes/wait_for_digits/render/phone-number-collection.png +0 -0
  109. package/screenshots/truth/nodes/wait_for_digits/render/single-digit-with-timeout.png +0 -0
  110. package/screenshots/truth/nodes/wait_for_digits/render/verification-code.png +0 -0
  111. package/screenshots/truth/nodes/wait_for_response/render/basic-wait.png +0 -0
  112. package/screenshots/truth/nodes/wait_for_response/render/custom-result-name.png +0 -0
  113. package/screenshots/truth/nodes/wait_for_response/render/no-timeout.png +0 -0
  114. package/screenshots/truth/nodes/wait_for_response/render/short-timeout.png +0 -0
  115. package/screenshots/truth/simulator/after-message-sent.png +0 -0
  116. package/screenshots/truth/simulator/after-reset.png +0 -0
  117. package/screenshots/truth/simulator/attachment-menu.png +0 -0
  118. package/screenshots/truth/simulator/context-expanded.png +0 -0
  119. package/screenshots/truth/simulator/context-explorer-open.png +0 -0
  120. package/screenshots/truth/simulator/event-info.png +0 -0
  121. package/screenshots/truth/simulator/image-attachment.png +0 -0
  122. package/screenshots/truth/simulator/open-initial.png +0 -0
  123. package/screenshots/truth/simulator/quick-replies.png +0 -0
  124. package/src/Icons.ts +2 -1
  125. package/src/display/FloatingTab.ts +2 -7
  126. package/src/flow/CanvasNode.ts +30 -1
  127. package/src/flow/Editor.ts +246 -4
  128. package/src/flow/Plumber.ts +371 -2
  129. package/src/interfaces.ts +2 -1
  130. package/src/layout/FloatingWindow.ts +37 -12
  131. package/src/simulator/Simulator.ts +2061 -0
  132. package/src/store/AppState.ts +109 -0
  133. package/src/utils.ts +53 -0
  134. package/static/svg/index.svg +1 -1
  135. package/static/svg/work/traced/route.svg +1 -0
  136. package/static/svg/work/used/route.svg +3 -0
  137. package/temba-modules.ts +2 -0
  138. package/test/temba-appstate-node-sorting.test.ts +506 -0
  139. package/test/temba-floating-tab.test.ts +0 -11
  140. package/test/temba-flow-editor.test.ts +298 -1
  141. package/test/temba-flow-plumber-connections.test.ts +4 -1
  142. package/test/temba-flow-plumber.test.ts +4 -1
  143. package/test/temba-simulator.test.ts +866 -0
  144. package/test/utils.test.ts +1 -1
@@ -18,6 +18,32 @@ import { produce } from 'immer';
18
18
  export const FLOW_SPEC_VERSION = '14.3';
19
19
  const CANVAS_PADDING = 800;
20
20
 
21
+ /**
22
+ * Sorts nodes by their position - first by y (top), then by x (left)
23
+ */
24
+ function sortNodesByPosition(
25
+ nodes: Node[],
26
+ nodePositions: Record<string, NodeUI>
27
+ ): void {
28
+ nodes.sort((a, b) => {
29
+ const posA = nodePositions[a.uuid]?.position;
30
+ const posB = nodePositions[b.uuid]?.position;
31
+
32
+ // if either position is missing, maintain current order
33
+ if (!posA || !posB) {
34
+ return 0;
35
+ }
36
+
37
+ // sort by y (top) first
38
+ if (posA.top !== posB.top) {
39
+ return posA.top - posB.top;
40
+ }
41
+
42
+ // if y is same, sort by x (left)
43
+ return posA.left - posB.left;
44
+ });
45
+ }
46
+
21
47
  export interface InfoResult {
22
48
  key: string;
23
49
  name: string;
@@ -66,6 +92,11 @@ export interface CanvasPositions {
66
92
  [uuid: string]: FlowPosition;
67
93
  }
68
94
 
95
+ export interface Activity {
96
+ segments: { [exitToDestinationKey: string]: number };
97
+ nodes: { [nodeUuid: string]: number };
98
+ }
99
+
69
100
  export interface AppState {
70
101
  flowDefinition: FlowDefinition;
71
102
  flowInfo: FlowInfo;
@@ -78,10 +109,20 @@ export interface AppState {
78
109
  dirtyDate: Date | null;
79
110
 
80
111
  canvasSize: { width: number; height: number };
112
+ activity: Activity | null;
113
+ simulatorActivity: Activity | null;
114
+ activityEndpoint: string | null;
115
+ simulatorActive: boolean;
81
116
 
117
+ getCurrentActivity: () => Activity | null;
82
118
  fetchRevision: (endpoint: string, id?: string) => void;
83
119
  fetchWorkspace: (endpoint: string) => Promise<void>;
84
120
  fetchAllLanguages: (endpoint: string) => Promise<void>;
121
+ fetchActivity: (endpoint: string) => Promise<void>;
122
+ setActivityEndpoint: (endpoint: string) => void;
123
+ updateActivity: (activity: Activity) => void;
124
+ updateSimulatorActivity: (activity: Activity) => void;
125
+ setSimulatorActive: (active: boolean) => void;
85
126
 
86
127
  getFlowResults: () => InfoResult[];
87
128
  getResultByKey(id: any): InfoResult;
@@ -134,6 +175,10 @@ export const zustand = createStore<AppState>()(
134
175
  flowInfo: null,
135
176
  isTranslating: false,
136
177
  dirtyDate: null,
178
+ activity: null,
179
+ simulatorActivity: null,
180
+ activityEndpoint: null,
181
+ simulatorActive: false,
137
182
 
138
183
  setDirtyDate: (date: Date) => {
139
184
  set((state: AppState) => {
@@ -181,6 +226,40 @@ export const zustand = createStore<AppState>()(
181
226
  set({ languageNames: allLanguages });
182
227
  },
183
228
 
229
+ setActivityEndpoint: (endpoint: string) => {
230
+ set({ activityEndpoint: endpoint });
231
+ },
232
+
233
+ fetchActivity: async (endpoint: string) => {
234
+ try {
235
+ const response = await fetch(endpoint);
236
+ if (!response.ok) {
237
+ return;
238
+ }
239
+ const data = await response.json();
240
+ set({ activity: data });
241
+ } catch (error) {
242
+ console.error('Failed to fetch activity:', error);
243
+ }
244
+ },
245
+
246
+ updateActivity: (activity: Activity) => {
247
+ set({ activity });
248
+ },
249
+
250
+ updateSimulatorActivity: (activity: Activity) => {
251
+ set({ simulatorActivity: activity });
252
+ },
253
+
254
+ setSimulatorActive: (active: boolean) => {
255
+ set({ simulatorActive: active });
256
+ },
257
+
258
+ getCurrentActivity: () => {
259
+ const state = get();
260
+ return state.simulatorActive ? state.simulatorActivity : state.activity;
261
+ },
262
+
184
263
  getFlowResults: () => {
185
264
  const state = get();
186
265
  return state.flowInfo.results;
@@ -208,6 +287,14 @@ export const zustand = createStore<AppState>()(
208
287
  // Reset to the flow's default language when loading a new flow
209
288
  state.languageCode = flowLang;
210
289
  state.isTranslating = false;
290
+
291
+ // Sort nodes by position when loading flow
292
+ if (state.flowDefinition?.nodes && state.flowDefinition?._ui?.nodes) {
293
+ sortNodesByPosition(
294
+ state.flowDefinition.nodes,
295
+ state.flowDefinition._ui.nodes
296
+ );
297
+ }
211
298
  });
212
299
  },
213
300
 
@@ -260,6 +347,13 @@ export const zustand = createStore<AppState>()(
260
347
  positions[uuid];
261
348
  }
262
349
  }
350
+
351
+ // Sort nodes by position since positions may have changed
352
+ sortNodesByPosition(
353
+ state.flowDefinition.nodes,
354
+ state.flowDefinition._ui.nodes
355
+ );
356
+
263
357
  state.dirtyDate = new Date();
264
358
  });
265
359
  },
@@ -318,6 +412,9 @@ export const zustand = createStore<AppState>()(
318
412
  }
319
413
  });
320
414
  });
415
+
416
+ // Sort nodes by position
417
+ sortNodesByPosition(draft.nodes, draft._ui.nodes);
321
418
  });
322
419
 
323
420
  state.dirtyDate = new Date();
@@ -452,6 +549,12 @@ export const zustand = createStore<AppState>()(
452
549
  config: {}
453
550
  };
454
551
 
552
+ // Sort nodes by position
553
+ sortNodesByPosition(
554
+ state.flowDefinition.nodes,
555
+ state.flowDefinition._ui.nodes
556
+ );
557
+
455
558
  state.dirtyDate = new Date();
456
559
  });
457
560
 
@@ -470,6 +573,12 @@ export const zustand = createStore<AppState>()(
470
573
 
471
574
  state.flowDefinition._ui.nodes[node.uuid] = nodeUI;
472
575
 
576
+ // Sort nodes by position
577
+ sortNodesByPosition(
578
+ state.flowDefinition.nodes,
579
+ state.flowDefinition._ui.nodes
580
+ );
581
+
473
582
  state.dirtyDate = new Date();
474
583
  });
475
584
  },
package/src/utils.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  /* eslint-disable @typescript-eslint/no-this-alias */
2
2
  import { html, TemplateResult } from 'lit-html';
3
+ import { property } from 'lit/decorators.js';
3
4
  import { Button } from './display/Button';
4
5
  import { Dialog } from './layout/Dialog';
5
6
  import { Attachment, ContactField, Shortcut, Ticket, User } from './interfaces';
@@ -700,6 +701,58 @@ export const getCookieBoolean = (name: string) => {
700
701
  return (getCookie(name) || '') === 'true';
701
702
  };
702
703
 
704
+ /**
705
+ * Custom Lit property decorator that binds a property to a cookie.
706
+ * The property value is loaded from the cookie on component connection
707
+ * and automatically saved to the cookie whenever it changes.
708
+ *
709
+ * @param cookieName - The name of the cookie to use for storage.
710
+ * @param defaultValue - Optional default value if cookie doesn't exist.
711
+ */
712
+ export function fromCookie<T = unknown>(
713
+ cookieName: string,
714
+ defaultValue?: T
715
+ ): PropertyDecorator {
716
+ return (proto: any, propertyName: string | symbol) => {
717
+ // register as a reactive property
718
+ property()(proto, propertyName as string);
719
+
720
+ const connectedKey = 'connectedCallback';
721
+ const updatedKey = 'updated';
722
+
723
+ const userConnected = proto[connectedKey];
724
+ const userUpdated = proto[updatedKey];
725
+
726
+ // on connect, load value from cookie
727
+ proto[connectedKey] = function () {
728
+ const cookieValue = getCookie(cookieName);
729
+ if (cookieValue !== null) {
730
+ // parse boolean values
731
+ if (cookieValue === 'true') {
732
+ this[propertyName] = true;
733
+ } else if (cookieValue === 'false') {
734
+ this[propertyName] = false;
735
+ } else {
736
+ this[propertyName] = cookieValue;
737
+ }
738
+ } else if (defaultValue !== undefined) {
739
+ this[propertyName] = defaultValue;
740
+ }
741
+
742
+ if (userConnected) userConnected.call(this);
743
+ };
744
+
745
+ // on property change, save to cookie
746
+ proto[updatedKey] = function (changedProperties: Map<string, any>) {
747
+ if (changedProperties.has(propertyName as string)) {
748
+ setCookie(cookieName, this[propertyName]);
749
+ }
750
+
751
+ if (userUpdated) userUpdated.call(this, changedProperties);
752
+ };
753
+ };
754
+ }
755
+
703
756
  export enum COOKIE_KEYS {
704
757
  SETTINGS = 'settings',
705
758
  MENU_COLLAPSED = 'menu-collapsed',