@nyaruka/temba-components 0.170.1 → 0.172.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.
- package/CHANGELOG.md +24 -0
- package/dist/temba-components.js +763 -703
- package/dist/temba-components.js.map +1 -1
- package/package.json +1 -1
- package/src/RapidElement.ts +83 -41
- package/src/events/eventRenderers.ts +4 -0
- package/src/flow/Editor.ts +202 -42
- package/src/flow/FlowSearch.ts +12 -392
- package/src/flow/actions/send_msg.ts +2 -1
- package/src/flow/actions/set_contact_field.ts +1 -0
- package/src/flow/actions/set_contact_name.ts +1 -0
- package/src/flow/dependencies.ts +115 -0
- package/src/flow/nodes/split_by_ticket.ts +1 -0
- package/src/flow/nodes/wait_for_dial.ts +1 -0
- package/src/form/RangePicker.ts +137 -97
- package/src/layout/SearchModal.ts +564 -0
- package/src/list/ContentList.ts +7 -1
- package/src/list/FlowList.ts +82 -0
- package/src/list/TembaMenu.ts +71 -13
- package/src/live/ContactChat.ts +247 -70
- package/src/live/ContactWatch.ts +166 -63
- package/src/live/Realtime.ts +155 -4
- package/src/live/SocketService.ts +115 -10
- package/src/live/TicketSearch.ts +290 -0
- package/src/live/Watchers.ts +93 -0
- package/src/simulator/Simulator.ts +59 -36
- package/src/store/AppState.ts +97 -10
- package/src/store/Store.ts +478 -6
- package/src/store/identity.ts +28 -0
- package/src/utils.ts +10 -8
- package/temba-modules.ts +2 -0
package/package.json
CHANGED
package/src/RapidElement.ts
CHANGED
|
@@ -40,6 +40,34 @@ export interface EventHandler {
|
|
|
40
40
|
isWindow?: boolean;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Inline handlers compiled from an element's -<event-type> attribute, keyed
|
|
45
|
+
* by their source. Page authors write a finite set of these, so compiling
|
|
46
|
+
* each one once keeps dispatch off the Function constructor entirely.
|
|
47
|
+
*/
|
|
48
|
+
const inlineHandlers = new Map<string, (event: Event) => any>();
|
|
49
|
+
|
|
50
|
+
const compileInlineHandler = (source: string): ((event: Event) => any) => {
|
|
51
|
+
let compiled = inlineHandlers.get(source);
|
|
52
|
+
if (!compiled) {
|
|
53
|
+
compiled = new Function(
|
|
54
|
+
'event',
|
|
55
|
+
`
|
|
56
|
+
with(document) {
|
|
57
|
+
with(this) {
|
|
58
|
+
let handler = ${source};
|
|
59
|
+
if(typeof handler === 'function') {
|
|
60
|
+
handler(event);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
`
|
|
65
|
+
) as (event: Event) => any;
|
|
66
|
+
inlineHandlers.set(source, compiled);
|
|
67
|
+
}
|
|
68
|
+
return compiled;
|
|
69
|
+
};
|
|
70
|
+
|
|
43
71
|
export class RapidElement extends LitElement {
|
|
44
72
|
DEBUG = false;
|
|
45
73
|
DEBUG_UPDATES = false;
|
|
@@ -49,34 +77,50 @@ export class RapidElement extends LitElement {
|
|
|
49
77
|
service: string;
|
|
50
78
|
|
|
51
79
|
private eles: { [selector: string]: HTMLDivElement } = {};
|
|
80
|
+
|
|
81
|
+
// teardowns for the listeners we installed, so disconnecting removes the
|
|
82
|
+
// exact functions we added
|
|
83
|
+
private listenerTeardowns: (() => void)[] = [];
|
|
84
|
+
|
|
52
85
|
public getEventHandlers(): EventHandler[] {
|
|
53
86
|
return [];
|
|
54
87
|
}
|
|
55
88
|
|
|
89
|
+
/**
|
|
90
|
+
* Adds a listener this element owns the teardown for - it is bound to this
|
|
91
|
+
* element and removed automatically when we disconnect. Use this instead of
|
|
92
|
+
* addEventListener for anything on document or window, where a listener
|
|
93
|
+
* that outlives its element keeps the whole element alive.
|
|
94
|
+
*/
|
|
95
|
+
public listenTo(
|
|
96
|
+
target: EventTarget,
|
|
97
|
+
event: string,
|
|
98
|
+
method: EventListener,
|
|
99
|
+
options?: AddEventListenerOptions
|
|
100
|
+
): void {
|
|
101
|
+
const bound = method.bind(this);
|
|
102
|
+
target.addEventListener(event, bound, options);
|
|
103
|
+
this.listenerTeardowns.push(() =>
|
|
104
|
+
target.removeEventListener(event, bound, options)
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
56
108
|
connectedCallback() {
|
|
57
109
|
super.connectedCallback();
|
|
58
110
|
|
|
59
111
|
for (const handler of this.getEventHandlers()) {
|
|
60
|
-
|
|
61
|
-
document
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}
|
|
112
|
+
const target = handler.isDocument
|
|
113
|
+
? document
|
|
114
|
+
: handler.isWindow
|
|
115
|
+
? window
|
|
116
|
+
: this;
|
|
117
|
+
this.listenTo(target, handler.event, handler.method);
|
|
67
118
|
}
|
|
68
119
|
}
|
|
69
120
|
|
|
70
121
|
disconnectedCallback() {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
document.removeEventListener(handler.event, handler.method);
|
|
74
|
-
} else if (handler.isWindow) {
|
|
75
|
-
window.removeEventListener(handler.event, handler.method);
|
|
76
|
-
} else {
|
|
77
|
-
this.removeEventListener(handler.event, handler.method);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
122
|
+
this.listenerTeardowns.forEach((teardown) => teardown());
|
|
123
|
+
this.listenerTeardowns = [];
|
|
80
124
|
super.disconnectedCallback();
|
|
81
125
|
}
|
|
82
126
|
|
|
@@ -121,9 +165,7 @@ export class RapidElement extends LitElement {
|
|
|
121
165
|
}
|
|
122
166
|
|
|
123
167
|
public fireCustomEvent(type: CustomEventType, detail: any = {}): any {
|
|
124
|
-
|
|
125
|
-
showEvent(this, type, detail);
|
|
126
|
-
}
|
|
168
|
+
showEvent(this, type, detail);
|
|
127
169
|
|
|
128
170
|
const event = new CustomEvent(type, {
|
|
129
171
|
detail,
|
|
@@ -135,30 +177,30 @@ export class RapidElement extends LitElement {
|
|
|
135
177
|
}
|
|
136
178
|
|
|
137
179
|
public dispatchEvent(event: any): any {
|
|
138
|
-
super.dispatchEvent(event);
|
|
180
|
+
const dispatched = super.dispatchEvent(event);
|
|
181
|
+
|
|
182
|
+
// the page can hang a handler off the target for any of our events, as a
|
|
183
|
+
// -<event-type> property or an attribute of the same name
|
|
139
184
|
const ele = event.target;
|
|
140
|
-
if (ele) {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
'event',
|
|
148
|
-
`
|
|
149
|
-
with(document) {
|
|
150
|
-
with(this) {
|
|
151
|
-
let handler = ${ele.getAttribute('-' + event.type)};
|
|
152
|
-
if(typeof handler === 'function') {
|
|
153
|
-
handler(event);
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
`
|
|
158
|
-
);
|
|
159
|
-
return func.call(ele, event);
|
|
160
|
-
}
|
|
185
|
+
if (!ele) {
|
|
186
|
+
return dispatched;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const eventFire = (ele as any)['-' + event.type];
|
|
190
|
+
if (eventFire) {
|
|
191
|
+
return eventFire(event);
|
|
161
192
|
}
|
|
193
|
+
|
|
194
|
+
const inline = ele.getAttribute ? ele.getAttribute('-' + event.type) : null;
|
|
195
|
+
if (!inline) {
|
|
196
|
+
return dispatched;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// the compiled handler has no return value of its own, so handing its
|
|
200
|
+
// undefined back would read as a cancelled event - what the caller wants
|
|
201
|
+
// to know is whether anything called preventDefault
|
|
202
|
+
compileInlineHandler(inline).call(ele, event);
|
|
203
|
+
return dispatched;
|
|
162
204
|
}
|
|
163
205
|
|
|
164
206
|
public closestElement(selector: string, base: Element = this) {
|
|
@@ -59,6 +59,10 @@ export enum Events {
|
|
|
59
59
|
TICKET_OPENED = 'ticket_opened',
|
|
60
60
|
TICKET_REOPENED = 'ticket_reopened',
|
|
61
61
|
TICKET_TOPIC_CHANGED = 'ticket_topic_changed',
|
|
62
|
+
// ephemeral, drives the chat's typing indicator instead of recording
|
|
63
|
+
// history - published by agents as they compose and never persisted
|
|
64
|
+
TYPING_STARTED = 'typing_started',
|
|
65
|
+
TYPING_STOPPED = 'typing_stopped',
|
|
62
66
|
WARNING = 'warning',
|
|
63
67
|
WEBHOOK_CALLED = 'webhook_called'
|
|
64
68
|
}
|
package/src/flow/Editor.ts
CHANGED
|
@@ -58,6 +58,8 @@ import { Dialog } from '../layout/Dialog';
|
|
|
58
58
|
import { CanvasMenu, CanvasMenuSelection } from './CanvasMenu';
|
|
59
59
|
import { NodeTypeSelector, NodeTypeSelection } from './NodeTypeSelector';
|
|
60
60
|
import { FlowSearch, SearchResult } from './FlowSearch';
|
|
61
|
+
import { RealtimeSubscription, subscribeToFlow } from '../live/Realtime';
|
|
62
|
+
import { FlowDependency } from './dependencies';
|
|
61
63
|
|
|
62
64
|
export function findNodeForExit(
|
|
63
65
|
definition: FlowDefinition,
|
|
@@ -105,6 +107,10 @@ const EMPTY_FLOW_ISSUES: FlowIssue[] = [];
|
|
|
105
107
|
// Used in both the CSS animation and the JS setTimeout.
|
|
106
108
|
const PENDING_SAVE_DELAY = 5000;
|
|
107
109
|
|
|
110
|
+
// Shortest gap (in ms) between activity reads, however many changes the flow
|
|
111
|
+
// socket announces in that window.
|
|
112
|
+
const ACTIVITY_FETCH_INTERVAL = 3000;
|
|
113
|
+
|
|
108
114
|
/**
|
|
109
115
|
* Manages a timed "pending changes" card that lets users discard or auto-save
|
|
110
116
|
* after a delay. Used for both auto-layout and shift+drag copy.
|
|
@@ -200,8 +206,17 @@ export class Editor extends RapidElement {
|
|
|
200
206
|
@property({ type: Array })
|
|
201
207
|
public features: string[] = [];
|
|
202
208
|
|
|
203
|
-
private
|
|
204
|
-
private
|
|
209
|
+
private activityWatch: RealtimeSubscription = null;
|
|
210
|
+
private watchedActivityFlow: string = null;
|
|
211
|
+
private activityFetchTimer: number | null = null;
|
|
212
|
+
private activityFetchInFlight = false;
|
|
213
|
+
private activityFetchQueued = false;
|
|
214
|
+
private activityFetchToken = 0;
|
|
215
|
+
private assetWatch: RealtimeSubscription = null;
|
|
216
|
+
private watchedAssetStore: Store = null;
|
|
217
|
+
private flowInfoWatch: () => void = null;
|
|
218
|
+
private assetResolveVersion = 0;
|
|
219
|
+
private watchedAssetSignature = '';
|
|
205
220
|
|
|
206
221
|
@fromStore(zustand, (state: AppState) => state.flowDefinition)
|
|
207
222
|
public definition!: FlowDefinition;
|
|
@@ -1198,10 +1213,17 @@ export class Editor extends RapidElement {
|
|
|
1198
1213
|
this.zoomManager = new ZoomManager(this);
|
|
1199
1214
|
}
|
|
1200
1215
|
|
|
1216
|
+
connectedCallback(): void {
|
|
1217
|
+
super.connectedCallback();
|
|
1218
|
+
this.syncAssetWatch(zustand.getState().flowInfo);
|
|
1219
|
+
this.syncActivityWatch();
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1201
1222
|
protected firstUpdated(
|
|
1202
1223
|
changes: PropertyValueMap<any> | Map<PropertyKey, unknown>
|
|
1203
1224
|
): void {
|
|
1204
1225
|
super.firstUpdated(changes);
|
|
1226
|
+
this.syncAssetWatch(zustand.getState().flowInfo);
|
|
1205
1227
|
this.plumber = new Plumber(this.querySelector('#canvas'), this);
|
|
1206
1228
|
this.plumber.zoom = this.zoom;
|
|
1207
1229
|
this.setupGlobalEventListeners();
|
|
@@ -1422,23 +1444,15 @@ export class Editor extends RapidElement {
|
|
|
1422
1444
|
// defer to avoid triggering a reactive canvasSize update during this cycle
|
|
1423
1445
|
setTimeout(() => this.updateCanvasSize(), 0);
|
|
1424
1446
|
|
|
1425
|
-
|
|
1426
|
-
if (this.definition?.uuid) {
|
|
1427
|
-
this.startActivityFetching();
|
|
1428
|
-
}
|
|
1447
|
+
this.syncActivityWatch();
|
|
1429
1448
|
}
|
|
1430
1449
|
|
|
1431
1450
|
if (changes.has('simulatorActive')) {
|
|
1432
1451
|
if (this.simulatorActive) {
|
|
1433
1452
|
// Close any open floating windows when simulator opens
|
|
1434
1453
|
this.closeFloatingWindows();
|
|
1435
|
-
// Stop polling when simulator becomes active
|
|
1436
|
-
this.stopActivityFetching();
|
|
1437
|
-
} else {
|
|
1438
|
-
// Resume polling and refresh activity when simulator closes
|
|
1439
|
-
this.activityInterval = 100; // Reset to fast initial interval
|
|
1440
|
-
this.startActivityFetching();
|
|
1441
1454
|
}
|
|
1455
|
+
this.syncActivityWatch();
|
|
1442
1456
|
}
|
|
1443
1457
|
|
|
1444
1458
|
if (changes.has('activityData')) {
|
|
@@ -1653,55 +1667,188 @@ export class Editor extends RapidElement {
|
|
|
1653
1667
|
});
|
|
1654
1668
|
}
|
|
1655
1669
|
|
|
1656
|
-
private
|
|
1657
|
-
|
|
1658
|
-
if (this.
|
|
1670
|
+
private syncActivityWatch(): void {
|
|
1671
|
+
const flow = this.simulatorActive ? null : this.definition?.uuid;
|
|
1672
|
+
if (this.activityWatch && flow === this.watchedActivityFlow) {
|
|
1673
|
+
return;
|
|
1674
|
+
}
|
|
1675
|
+
|
|
1676
|
+
if (this.activityWatch) {
|
|
1677
|
+
this.activityWatch.unsubscribe();
|
|
1678
|
+
this.activityWatch = null;
|
|
1679
|
+
}
|
|
1680
|
+
this.clearActivityFetch();
|
|
1681
|
+
this.watchedActivityFlow = flow;
|
|
1682
|
+
if (!flow) {
|
|
1659
1683
|
return;
|
|
1660
1684
|
}
|
|
1661
|
-
|
|
1662
|
-
this.
|
|
1685
|
+
|
|
1686
|
+
this.activityWatch = subscribeToFlow(
|
|
1687
|
+
flow,
|
|
1688
|
+
(event) => {
|
|
1689
|
+
if (event?.type === 'activity') {
|
|
1690
|
+
this.scheduleActivityFetch(flow);
|
|
1691
|
+
}
|
|
1692
|
+
},
|
|
1693
|
+
// fires on every (re)subscribe, so catch up on anything published while
|
|
1694
|
+
// we were disconnected
|
|
1695
|
+
() => this.scheduleActivityFetch(flow)
|
|
1696
|
+
);
|
|
1697
|
+
|
|
1698
|
+
// the socket only tells us about *changes*, and it may never connect at
|
|
1699
|
+
// all, so the first read doesn't wait on it
|
|
1700
|
+
this.fetchActivityData(flow);
|
|
1663
1701
|
}
|
|
1664
1702
|
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1703
|
+
/**
|
|
1704
|
+
* Abandons any scheduled or in-flight read. The token makes the in-flight
|
|
1705
|
+
* one a no-op when it lands, so a read for the flow we've stopped watching
|
|
1706
|
+
* can neither hold up nor swallow the next flow's first read.
|
|
1707
|
+
*/
|
|
1708
|
+
private clearActivityFetch(): void {
|
|
1709
|
+
if (this.activityFetchTimer !== null) {
|
|
1710
|
+
clearTimeout(this.activityFetchTimer);
|
|
1711
|
+
this.activityFetchTimer = null;
|
|
1669
1712
|
}
|
|
1713
|
+
this.activityFetchQueued = false;
|
|
1714
|
+
this.activityFetchInFlight = false;
|
|
1715
|
+
this.activityFetchToken++;
|
|
1670
1716
|
}
|
|
1671
1717
|
|
|
1672
|
-
|
|
1673
|
-
|
|
1718
|
+
/**
|
|
1719
|
+
* Coalesces activity publications into at most one request per window.
|
|
1720
|
+
* Mailroom publishes once per committed sprint batch, so a flow running
|
|
1721
|
+
* against a large group can otherwise announce many changes a second.
|
|
1722
|
+
*/
|
|
1723
|
+
private scheduleActivityFetch(flow: string): void {
|
|
1724
|
+
if (flow !== this.watchedActivityFlow || this.activityFetchTimer !== null) {
|
|
1674
1725
|
return;
|
|
1675
1726
|
}
|
|
1727
|
+
this.activityFetchTimer = window.setTimeout(() => {
|
|
1728
|
+
this.activityFetchTimer = null;
|
|
1729
|
+
this.fetchActivityData(flow);
|
|
1730
|
+
}, ACTIVITY_FETCH_INTERVAL);
|
|
1731
|
+
}
|
|
1676
1732
|
|
|
1677
|
-
|
|
1678
|
-
if (
|
|
1733
|
+
private fetchActivityData(flow: string): void {
|
|
1734
|
+
if (
|
|
1735
|
+
!this.isConnected ||
|
|
1736
|
+
this.simulatorActive ||
|
|
1737
|
+
flow !== this.watchedActivityFlow
|
|
1738
|
+
) {
|
|
1679
1739
|
return;
|
|
1680
1740
|
}
|
|
1681
1741
|
|
|
1682
|
-
const activityEndpoint = `/flow/activity/${this.definition.uuid}/`;
|
|
1683
1742
|
const store = getStore();
|
|
1684
1743
|
if (!store) {
|
|
1685
1744
|
return;
|
|
1686
1745
|
}
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1746
|
+
|
|
1747
|
+
// one request at a time, so responses can't land out of order and a burst
|
|
1748
|
+
// of publications collapses into a single follow-up read
|
|
1749
|
+
if (this.activityFetchInFlight) {
|
|
1750
|
+
this.activityFetchQueued = true;
|
|
1751
|
+
return;
|
|
1752
|
+
}
|
|
1753
|
+
this.activityFetchInFlight = true;
|
|
1754
|
+
const token = ++this.activityFetchToken;
|
|
1755
|
+
const done = () => {
|
|
1756
|
+
// a flow change (or teardown) since this read started has already
|
|
1757
|
+
// reset the bookkeeping for the flow we now care about
|
|
1758
|
+
if (token !== this.activityFetchToken) {
|
|
1691
1759
|
return;
|
|
1692
1760
|
}
|
|
1761
|
+
this.activityFetchInFlight = false;
|
|
1762
|
+
if (this.activityFetchQueued) {
|
|
1763
|
+
this.activityFetchQueued = false;
|
|
1764
|
+
this.scheduleActivityFetch(flow);
|
|
1765
|
+
}
|
|
1766
|
+
};
|
|
1767
|
+
Promise.resolve(
|
|
1768
|
+
store.getState().fetchActivity(`/flow/activity/${flow}/`)
|
|
1769
|
+
).then(done, done);
|
|
1770
|
+
}
|
|
1771
|
+
|
|
1772
|
+
private syncAssetNames(): void {
|
|
1773
|
+
const store = getStore();
|
|
1774
|
+
const state = zustand.getState();
|
|
1775
|
+
if (!store || !state.flowDefinition || !state.flowInfo) {
|
|
1776
|
+
return;
|
|
1777
|
+
}
|
|
1693
1778
|
|
|
1694
|
-
|
|
1695
|
-
|
|
1779
|
+
const canonical: FlowDependency[] = [];
|
|
1780
|
+
for (const dependency of state.flowInfo.dependencies || []) {
|
|
1781
|
+
const identity = dependency.uuid || dependency.key;
|
|
1782
|
+
if (!identity) {
|
|
1783
|
+
continue;
|
|
1784
|
+
}
|
|
1785
|
+
const asset = store.getAsset(dependency.type, identity);
|
|
1786
|
+
if (asset) {
|
|
1787
|
+
canonical.push({ ...dependency, name: asset.name });
|
|
1788
|
+
}
|
|
1789
|
+
}
|
|
1790
|
+
state.updateDependencyNames(canonical);
|
|
1791
|
+
}
|
|
1696
1792
|
|
|
1697
|
-
|
|
1698
|
-
|
|
1793
|
+
private async resolveAssetNames(info: AppState['flowInfo']): Promise<void> {
|
|
1794
|
+
const store = getStore();
|
|
1795
|
+
if (!store || !info) {
|
|
1796
|
+
return;
|
|
1797
|
+
}
|
|
1798
|
+
const version = ++this.assetResolveVersion;
|
|
1799
|
+
try {
|
|
1800
|
+
const canonical = await store.resolveAssets(info.dependencies || []);
|
|
1801
|
+
if (version === this.assetResolveVersion && this.isConnected) {
|
|
1802
|
+
zustand.getState().updateDependencyNames(canonical);
|
|
1699
1803
|
}
|
|
1804
|
+
} catch (error) {
|
|
1805
|
+
console.error('failed to resolve flow dependency assets', error);
|
|
1806
|
+
}
|
|
1807
|
+
}
|
|
1700
1808
|
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
}
|
|
1809
|
+
private syncAssetWatch(info: AppState['flowInfo']): void {
|
|
1810
|
+
if (!this.isConnected) {
|
|
1811
|
+
return;
|
|
1812
|
+
}
|
|
1813
|
+
const store = getStore();
|
|
1814
|
+
if (!store) {
|
|
1815
|
+
return;
|
|
1816
|
+
}
|
|
1817
|
+
const interests = info?.dependencies || [];
|
|
1818
|
+
const signature = interests
|
|
1819
|
+
.map(
|
|
1820
|
+
(dependency) =>
|
|
1821
|
+
`${dependency.type}:${dependency.uuid || dependency.key || ''}`
|
|
1822
|
+
)
|
|
1823
|
+
.sort()
|
|
1824
|
+
.join('|');
|
|
1825
|
+
// the store is part of the key: a replaced <temba-store> leaves us
|
|
1826
|
+
// watching a dead one otherwise
|
|
1827
|
+
if (
|
|
1828
|
+
this.assetWatch &&
|
|
1829
|
+
store === this.watchedAssetStore &&
|
|
1830
|
+
signature === this.watchedAssetSignature
|
|
1831
|
+
) {
|
|
1832
|
+
return;
|
|
1833
|
+
}
|
|
1834
|
+
if (this.assetWatch) {
|
|
1835
|
+
this.assetWatch.unsubscribe();
|
|
1836
|
+
}
|
|
1837
|
+
this.watchedAssetStore = store;
|
|
1838
|
+
this.watchedAssetSignature = signature;
|
|
1839
|
+
// the store caches the changed asset before publishing, so applying
|
|
1840
|
+
// everything it has covers both a single change and a reconnect refresh
|
|
1841
|
+
this.assetWatch = store.watchAssets(interests, () => this.syncAssetNames());
|
|
1842
|
+
if (!this.flowInfoWatch) {
|
|
1843
|
+
this.flowInfoWatch = zustand.subscribe(
|
|
1844
|
+
(state) => state.flowInfo,
|
|
1845
|
+
(info) => {
|
|
1846
|
+
this.syncAssetWatch(info);
|
|
1847
|
+
this.resolveAssetNames(info);
|
|
1848
|
+
}
|
|
1849
|
+
);
|
|
1850
|
+
this.resolveAssetNames(zustand.getState().flowInfo);
|
|
1851
|
+
}
|
|
1705
1852
|
}
|
|
1706
1853
|
|
|
1707
1854
|
private handleLanguageChange(languageCode: string): void {
|
|
@@ -1957,6 +2104,23 @@ export class Editor extends RapidElement {
|
|
|
1957
2104
|
|
|
1958
2105
|
disconnectedCallback(): void {
|
|
1959
2106
|
super.disconnectedCallback();
|
|
2107
|
+
if (this.assetWatch) {
|
|
2108
|
+
this.assetWatch.unsubscribe();
|
|
2109
|
+
this.assetWatch = null;
|
|
2110
|
+
}
|
|
2111
|
+
if (this.activityWatch) {
|
|
2112
|
+
this.activityWatch.unsubscribe();
|
|
2113
|
+
this.activityWatch = null;
|
|
2114
|
+
}
|
|
2115
|
+
this.clearActivityFetch();
|
|
2116
|
+
this.watchedActivityFlow = null;
|
|
2117
|
+
this.watchedAssetStore = null;
|
|
2118
|
+
this.watchedAssetSignature = '';
|
|
2119
|
+
if (this.flowInfoWatch) {
|
|
2120
|
+
this.flowInfoWatch();
|
|
2121
|
+
this.flowInfoWatch = null;
|
|
2122
|
+
}
|
|
2123
|
+
this.assetResolveVersion++;
|
|
1960
2124
|
this.zoomManager.teardownLoupe();
|
|
1961
2125
|
getStore()?.getState().setFlushSave(null);
|
|
1962
2126
|
this.dragManager.teardownListeners();
|
|
@@ -1969,10 +2133,6 @@ export class Editor extends RapidElement {
|
|
|
1969
2133
|
clearTimeout(this.saveTimer);
|
|
1970
2134
|
this.saveTimer = null;
|
|
1971
2135
|
}
|
|
1972
|
-
if (this.activityTimer !== null) {
|
|
1973
|
-
clearTimeout(this.activityTimer);
|
|
1974
|
-
this.activityTimer = null;
|
|
1975
|
-
}
|
|
1976
2136
|
this.pendingTimer.clearTimer();
|
|
1977
2137
|
window.removeEventListener('resize', this.boundWindowResize);
|
|
1978
2138
|
|