@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.
- package/CHANGELOG.md +25 -0
- package/demo/components/webchat/example.html +4 -2
- package/dist/static/svg/index.svg +1 -1
- package/dist/temba-components.js +1351 -322
- package/dist/temba-components.js.map +1 -1
- package/out-tsc/src/Icons.js +2 -1
- package/out-tsc/src/Icons.js.map +1 -1
- package/out-tsc/src/display/FloatingTab.js +2 -6
- package/out-tsc/src/display/FloatingTab.js.map +1 -1
- package/out-tsc/src/flow/CanvasNode.js +29 -1
- package/out-tsc/src/flow/CanvasNode.js.map +1 -1
- package/out-tsc/src/flow/Editor.js +229 -5
- package/out-tsc/src/flow/Editor.js.map +1 -1
- package/out-tsc/src/flow/Plumber.js +320 -1
- package/out-tsc/src/flow/Plumber.js.map +1 -1
- package/out-tsc/src/interfaces.js +1 -0
- package/out-tsc/src/interfaces.js.map +1 -1
- package/out-tsc/src/layout/FloatingWindow.js +30 -8
- package/out-tsc/src/layout/FloatingWindow.js.map +1 -1
- package/out-tsc/src/simulator/Simulator.js +1861 -0
- package/out-tsc/src/simulator/Simulator.js.map +1 -0
- package/out-tsc/src/store/AppState.js +66 -0
- package/out-tsc/src/store/AppState.js.map +1 -1
- package/out-tsc/src/utils.js +48 -0
- package/out-tsc/src/utils.js.map +1 -1
- package/out-tsc/temba-modules.js +2 -0
- package/out-tsc/temba-modules.js.map +1 -1
- package/out-tsc/test/temba-appstate-node-sorting.test.js +430 -0
- package/out-tsc/test/temba-appstate-node-sorting.test.js.map +1 -0
- package/out-tsc/test/temba-floating-tab.test.js +0 -9
- package/out-tsc/test/temba-floating-tab.test.js.map +1 -1
- package/out-tsc/test/temba-flow-editor.test.js +262 -1
- package/out-tsc/test/temba-flow-editor.test.js.map +1 -1
- package/out-tsc/test/temba-flow-plumber-connections.test.js +3 -1
- package/out-tsc/test/temba-flow-plumber-connections.test.js.map +1 -1
- package/out-tsc/test/temba-flow-plumber.test.js +3 -1
- package/out-tsc/test/temba-flow-plumber.test.js.map +1 -1
- package/out-tsc/test/temba-simulator.test.js +642 -0
- package/out-tsc/test/temba-simulator.test.js.map +1 -0
- package/out-tsc/test/utils.test.js +1 -1
- package/out-tsc/test/utils.test.js.map +1 -1
- package/package.json +1 -1
- package/screenshots/truth/actions/add_contact_groups/render/descriptive-group-names.png +0 -0
- package/screenshots/truth/actions/add_contact_groups/render/long-group-names.png +0 -0
- package/screenshots/truth/actions/add_contact_groups/render/many-groups.png +0 -0
- package/screenshots/truth/actions/add_contact_groups/render/multiple-groups.png +0 -0
- package/screenshots/truth/actions/add_contact_groups/render/single-group.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/expression-facebook.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/expression-phone.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/facebook-id.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/instagram-handle.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/line-id.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/phone-number.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/telegram-id.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/viber-id.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/wechat-id.png +0 -0
- package/screenshots/truth/actions/add_contact_urn/render/whatsapp.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/render/cleanup-groups.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/render/long-descriptive-group-names.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/render/many-groups.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/render/multiple-groups.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/render/remove-from-all-groups.png +0 -0
- package/screenshots/truth/actions/remove_contact_groups/render/single-group.png +0 -0
- package/screenshots/truth/actions/send_broadcast/render/contacts-only.png +0 -0
- package/screenshots/truth/actions/send_broadcast/render/groups-and-contacts.png +0 -0
- package/screenshots/truth/actions/send_broadcast/render/groups-only.png +0 -0
- package/screenshots/truth/actions/send_broadcast/render/many-groups.png +0 -0
- package/screenshots/truth/actions/send_broadcast/render/multiline-text.png +0 -0
- package/screenshots/truth/actions/send_email/render/complex-business-email.png +0 -0
- package/screenshots/truth/actions/send_email/render/empty-body.png +0 -0
- package/screenshots/truth/actions/send_email/render/empty-subject.png +0 -0
- package/screenshots/truth/actions/send_email/render/long-subject.png +0 -0
- package/screenshots/truth/actions/send_email/render/multiline-body.png +0 -0
- package/screenshots/truth/actions/send_email/render/multiple-recipients.png +0 -0
- package/screenshots/truth/actions/send_email/render/simple-email.png +0 -0
- package/screenshots/truth/actions/send_email/render/with-expressions.png +0 -0
- package/screenshots/truth/actions/send_msg/render/long-quick-replies.png +0 -0
- package/screenshots/truth/actions/send_msg/render/multiline-text-with-replies.png +0 -0
- package/screenshots/truth/actions/send_msg/render/simple-text.png +0 -0
- package/screenshots/truth/actions/send_msg/render/text-with-linebreaks.png +0 -0
- package/screenshots/truth/actions/send_msg/render/text-with-many-quick-replies.png +0 -0
- package/screenshots/truth/actions/send_msg/render/text-with-quick-replies.png +0 -0
- package/screenshots/truth/actions/send_msg/render/text-without-quick-replies.png +0 -0
- package/screenshots/truth/actions/start_session/render/contact-query.png +0 -0
- package/screenshots/truth/actions/start_session/render/contacts-only.png +0 -0
- package/screenshots/truth/actions/start_session/render/create-contact.png +0 -0
- package/screenshots/truth/actions/start_session/render/groups-and-contacts.png +0 -0
- package/screenshots/truth/actions/start_session/render/groups-only.png +0 -0
- package/screenshots/truth/actions/start_session/render/many-recipients.png +0 -0
- package/screenshots/truth/floating-tab/gray.png +0 -0
- package/screenshots/truth/floating-tab/green.png +0 -0
- package/screenshots/truth/floating-tab/purple.png +0 -0
- package/screenshots/truth/nodes/split_by_llm/render/information-extraction.png +0 -0
- package/screenshots/truth/nodes/split_by_llm/render/sentiment-analysis.png +0 -0
- package/screenshots/truth/nodes/split_by_llm/render/summarization.png +0 -0
- package/screenshots/truth/nodes/split_by_llm/render/translation-task.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/editor/feedback-categorization.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/render/basic-categorization.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/render/custom-input-and-result-name.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/render/feedback-categorization.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/render/many-categories.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/render/minimal-categories.png +0 -0
- package/screenshots/truth/nodes/split_by_random/render/ab-test-multiple-variants.png +0 -0
- package/screenshots/truth/nodes/split_by_random/render/sampling-split.png +0 -0
- package/screenshots/truth/nodes/split_by_random/render/three-way-split.png +0 -0
- package/screenshots/truth/nodes/split_by_random/render/two-bucket-split.png +0 -0
- package/screenshots/truth/nodes/wait_for_digits/render/basic-digits-wait.png +0 -0
- package/screenshots/truth/nodes/wait_for_digits/render/phone-number-collection.png +0 -0
- package/screenshots/truth/nodes/wait_for_digits/render/single-digit-with-timeout.png +0 -0
- package/screenshots/truth/nodes/wait_for_digits/render/verification-code.png +0 -0
- package/screenshots/truth/nodes/wait_for_response/render/basic-wait.png +0 -0
- package/screenshots/truth/nodes/wait_for_response/render/custom-result-name.png +0 -0
- package/screenshots/truth/nodes/wait_for_response/render/no-timeout.png +0 -0
- package/screenshots/truth/nodes/wait_for_response/render/short-timeout.png +0 -0
- package/screenshots/truth/simulator/after-message-sent.png +0 -0
- package/screenshots/truth/simulator/after-reset.png +0 -0
- package/screenshots/truth/simulator/attachment-menu.png +0 -0
- package/screenshots/truth/simulator/context-expanded.png +0 -0
- package/screenshots/truth/simulator/context-explorer-open.png +0 -0
- package/screenshots/truth/simulator/event-info.png +0 -0
- package/screenshots/truth/simulator/image-attachment.png +0 -0
- package/screenshots/truth/simulator/open-initial.png +0 -0
- package/screenshots/truth/simulator/quick-replies.png +0 -0
- package/src/Icons.ts +2 -1
- package/src/display/FloatingTab.ts +2 -7
- package/src/flow/CanvasNode.ts +30 -1
- package/src/flow/Editor.ts +246 -4
- package/src/flow/Plumber.ts +371 -2
- package/src/interfaces.ts +2 -1
- package/src/layout/FloatingWindow.ts +37 -12
- package/src/simulator/Simulator.ts +2061 -0
- package/src/store/AppState.ts +109 -0
- package/src/utils.ts +53 -0
- package/static/svg/index.svg +1 -1
- package/static/svg/work/traced/route.svg +1 -0
- package/static/svg/work/used/route.svg +3 -0
- package/temba-modules.ts +2 -0
- package/test/temba-appstate-node-sorting.test.ts +506 -0
- package/test/temba-floating-tab.test.ts +0 -11
- package/test/temba-flow-editor.test.ts +298 -1
- package/test/temba-flow-plumber-connections.test.ts +4 -1
- package/test/temba-flow-plumber.test.ts +4 -1
- package/test/temba-simulator.test.ts +866 -0
- package/test/utils.test.ts +1 -1
package/src/store/AppState.ts
CHANGED
|
@@ -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',
|