@player-ui/async-node-plugin 0.7.4-next.4 → 0.7.5--canary.432.14776
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/dist/AsyncNodePlugin.native.js +7706 -0
- package/dist/AsyncNodePlugin.native.js.map +1 -0
- package/dist/cjs/index.cjs +166 -0
- package/dist/cjs/index.cjs.map +1 -0
- package/dist/index.legacy-esm.js +130 -0
- package/dist/index.mjs +130 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +24 -60
- package/src/index.test.ts +453 -0
- package/src/index.ts +155 -77
- package/src/types.ts +2 -2
- package/types/index.d.ts +38 -0
- package/types/types.d.ts +3 -0
- package/dist/async-node-plugin.dev.js +0 -10687
- package/dist/async-node-plugin.prod.js +0 -2
- package/dist/index.cjs.js +0 -102
- package/dist/index.d.ts +0 -20
- package/dist/index.esm.js +0 -94
|
@@ -0,0 +1,453 @@
|
|
|
1
|
+
import { expect, test } from "vitest";
|
|
2
|
+
import { Node, InProgressState, ViewInstance } from "@player-ui/player";
|
|
3
|
+
import { Player } from "@player-ui/player";
|
|
4
|
+
import { waitFor } from "@testing-library/react";
|
|
5
|
+
import { AsyncNodePlugin, AsyncNodePluginPlugin } from "./index";
|
|
6
|
+
|
|
7
|
+
const basicFRFWithActions = {
|
|
8
|
+
id: "test-flow",
|
|
9
|
+
views: [
|
|
10
|
+
{
|
|
11
|
+
id: "my-view",
|
|
12
|
+
actions: [
|
|
13
|
+
{
|
|
14
|
+
asset: {
|
|
15
|
+
id: "action-0",
|
|
16
|
+
type: "action",
|
|
17
|
+
value: "{{foo.bar}}",
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
id: "nodeId",
|
|
22
|
+
async: "true",
|
|
23
|
+
},
|
|
24
|
+
],
|
|
25
|
+
},
|
|
26
|
+
],
|
|
27
|
+
navigation: {
|
|
28
|
+
BEGIN: "FLOW_1",
|
|
29
|
+
FLOW_1: {
|
|
30
|
+
startState: "VIEW_1",
|
|
31
|
+
VIEW_1: {
|
|
32
|
+
state_type: "VIEW",
|
|
33
|
+
ref: "my-view",
|
|
34
|
+
transitions: {},
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const asyncNodeTest = async (resolvedValue: any) => {
|
|
41
|
+
const plugin = new AsyncNodePlugin({
|
|
42
|
+
plugins: [new AsyncNodePluginPlugin()],
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
let deferredResolve: ((value: any) => void) | undefined;
|
|
46
|
+
|
|
47
|
+
plugin.hooks.onAsyncNode.tap("test", async (node: Node.Async) => {
|
|
48
|
+
return new Promise((resolve) => {
|
|
49
|
+
deferredResolve = resolve; // Promise would be resolved only once
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
let updateNumber = 0;
|
|
54
|
+
|
|
55
|
+
const player = new Player({ plugins: [plugin] });
|
|
56
|
+
|
|
57
|
+
let viewInstance: ViewInstance | undefined;
|
|
58
|
+
|
|
59
|
+
player.hooks.viewController.tap("async-node-test", (vc) => {
|
|
60
|
+
vc.hooks.view.tap("async-node-test", (view) => {
|
|
61
|
+
viewInstance = view;
|
|
62
|
+
view.hooks.onUpdate.tap("async-node-test", () => {
|
|
63
|
+
updateNumber++;
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
player.start(basicFRFWithActions as any);
|
|
69
|
+
|
|
70
|
+
let view = (player.getState() as InProgressState).controllers.view.currentView
|
|
71
|
+
?.lastUpdate;
|
|
72
|
+
|
|
73
|
+
expect(view).toBeDefined();
|
|
74
|
+
expect(view?.actions[0].asset.type).toBe("action");
|
|
75
|
+
expect(view?.actions[1]).toBeUndefined();
|
|
76
|
+
|
|
77
|
+
await waitFor(() => {
|
|
78
|
+
expect(deferredResolve).toBeDefined();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// Consumer responds with null/undefined
|
|
82
|
+
if (deferredResolve) {
|
|
83
|
+
deferredResolve(resolvedValue);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
await waitFor(() => {
|
|
87
|
+
expect(updateNumber).toBe(2);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
view = (player.getState() as InProgressState).controllers.view.currentView
|
|
91
|
+
?.lastUpdate;
|
|
92
|
+
|
|
93
|
+
expect(view?.actions[0].asset.type).toBe("action");
|
|
94
|
+
expect(view?.actions.length).toBe(1);
|
|
95
|
+
|
|
96
|
+
viewInstance?.update();
|
|
97
|
+
|
|
98
|
+
await waitFor(() => {
|
|
99
|
+
expect(updateNumber).toBe(3);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
view = (player.getState() as InProgressState).controllers.view.currentView
|
|
103
|
+
?.lastUpdate;
|
|
104
|
+
|
|
105
|
+
expect(view?.actions[0].asset.type).toBe("action");
|
|
106
|
+
expect(view?.actions.length).toBe(1);
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
test("should return current node view when the resolved node is null", async () => {
|
|
110
|
+
await asyncNodeTest(null);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test("should return current node view when the resolved node is undefined", async () => {
|
|
114
|
+
await asyncNodeTest(undefined);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test("replaces async nodes with provided node", async () => {
|
|
118
|
+
const plugin = new AsyncNodePlugin({
|
|
119
|
+
plugins: [new AsyncNodePluginPlugin()],
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
let deferredResolve: ((value: any) => void) | undefined;
|
|
123
|
+
|
|
124
|
+
plugin.hooks.onAsyncNode.tap("test", async (node: Node.Async) => {
|
|
125
|
+
return new Promise((resolve) => {
|
|
126
|
+
deferredResolve = resolve;
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
let updateNumber = 0;
|
|
130
|
+
|
|
131
|
+
const player = new Player({ plugins: [plugin] });
|
|
132
|
+
|
|
133
|
+
player.hooks.viewController.tap("async-node-test", (vc) => {
|
|
134
|
+
vc.hooks.view.tap("async-node-test", (view) => {
|
|
135
|
+
view.hooks.onUpdate.tap("async-node-test", (update) => {
|
|
136
|
+
updateNumber++;
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
player.start(basicFRFWithActions as any);
|
|
142
|
+
|
|
143
|
+
let view = (player.getState() as InProgressState).controllers.view.currentView
|
|
144
|
+
?.lastUpdate;
|
|
145
|
+
|
|
146
|
+
expect(view).toBeDefined();
|
|
147
|
+
expect(view?.actions[0].asset.type).toBe("action");
|
|
148
|
+
expect(view?.actions[1]).toBeUndefined();
|
|
149
|
+
expect(updateNumber).toBe(1);
|
|
150
|
+
|
|
151
|
+
await waitFor(() => {
|
|
152
|
+
expect(deferredResolve).toBeDefined();
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
if (deferredResolve) {
|
|
156
|
+
deferredResolve({
|
|
157
|
+
asset: {
|
|
158
|
+
id: "next-label-action",
|
|
159
|
+
type: "action",
|
|
160
|
+
value: "dummy value",
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
await waitFor(() => {
|
|
166
|
+
expect(updateNumber).toBe(2);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
view = (player.getState() as InProgressState).controllers.view.currentView
|
|
170
|
+
?.lastUpdate;
|
|
171
|
+
|
|
172
|
+
expect(view?.actions[0].asset.type).toBe("action");
|
|
173
|
+
expect(view?.actions[1].asset.type).toBe("action");
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
test("replaces async nodes with multi node", async () => {
|
|
177
|
+
const plugin = new AsyncNodePlugin({
|
|
178
|
+
plugins: [new AsyncNodePluginPlugin()],
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
let deferredResolve: ((value: any) => void) | undefined;
|
|
182
|
+
|
|
183
|
+
plugin.hooks.onAsyncNode.tap("test", async (node) => {
|
|
184
|
+
return new Promise((resolve) => {
|
|
185
|
+
deferredResolve = resolve;
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
let updateNumber = 0;
|
|
190
|
+
|
|
191
|
+
const player = new Player({ plugins: [plugin] });
|
|
192
|
+
|
|
193
|
+
player.hooks.viewController.tap("async-node-test", (vc) => {
|
|
194
|
+
vc.hooks.view.tap("async-node-test", (view) => {
|
|
195
|
+
view.hooks.onUpdate.tap("async-node-test", (update) => {
|
|
196
|
+
updateNumber++;
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
player.start(basicFRFWithActions as any);
|
|
202
|
+
|
|
203
|
+
let view = (player.getState() as InProgressState).controllers.view.currentView
|
|
204
|
+
?.lastUpdate;
|
|
205
|
+
|
|
206
|
+
expect(view).toBeDefined();
|
|
207
|
+
expect(view?.actions[1]).toBeUndefined();
|
|
208
|
+
expect(updateNumber).toBe(1);
|
|
209
|
+
|
|
210
|
+
await waitFor(() => {
|
|
211
|
+
expect(deferredResolve).toBeDefined();
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
if (deferredResolve) {
|
|
215
|
+
deferredResolve([
|
|
216
|
+
{
|
|
217
|
+
asset: {
|
|
218
|
+
id: "value-1",
|
|
219
|
+
type: "text",
|
|
220
|
+
value: "1st value in the multinode",
|
|
221
|
+
},
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
asset: {
|
|
225
|
+
id: "value-2",
|
|
226
|
+
type: "text",
|
|
227
|
+
value: "2nd value in the multinode",
|
|
228
|
+
},
|
|
229
|
+
},
|
|
230
|
+
]);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
await waitFor(() => {
|
|
234
|
+
expect(updateNumber).toBe(2);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
view = (player.getState() as InProgressState).controllers.view.currentView
|
|
238
|
+
?.lastUpdate;
|
|
239
|
+
|
|
240
|
+
expect(view?.actions[0].asset.type).toBe("action");
|
|
241
|
+
expect(view?.actions[1].asset.type).toBe("text");
|
|
242
|
+
expect(view?.actions[2].asset.type).toBe("text");
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
test("replaces async nodes with chained multiNodes", async () => {
|
|
246
|
+
const plugin = new AsyncNodePlugin({
|
|
247
|
+
plugins: [new AsyncNodePluginPlugin()],
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
let deferredResolve: ((value: any) => void) | undefined;
|
|
251
|
+
|
|
252
|
+
plugin.hooks.onAsyncNode.tap("test", async (node: Node.Async) => {
|
|
253
|
+
return new Promise((resolve) => {
|
|
254
|
+
deferredResolve = resolve;
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
let updateNumber = 0;
|
|
258
|
+
|
|
259
|
+
const player = new Player({ plugins: [plugin] });
|
|
260
|
+
|
|
261
|
+
player.hooks.viewController.tap("async-node-test", (vc) => {
|
|
262
|
+
vc.hooks.view.tap("async-node-test", (view) => {
|
|
263
|
+
view.hooks.onUpdate.tap("async-node-test", (update) => {
|
|
264
|
+
updateNumber++;
|
|
265
|
+
});
|
|
266
|
+
});
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
player.start(basicFRFWithActions as any);
|
|
270
|
+
|
|
271
|
+
let view = (player.getState() as InProgressState).controllers.view.currentView
|
|
272
|
+
?.lastUpdate;
|
|
273
|
+
|
|
274
|
+
expect(view).toBeDefined();
|
|
275
|
+
expect(view?.actions[1]).toBeUndefined();
|
|
276
|
+
|
|
277
|
+
await waitFor(() => {
|
|
278
|
+
expect(updateNumber).toBe(1);
|
|
279
|
+
expect(deferredResolve).toBeDefined();
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
if (deferredResolve) {
|
|
283
|
+
deferredResolve([
|
|
284
|
+
{
|
|
285
|
+
asset: {
|
|
286
|
+
id: "value-1",
|
|
287
|
+
type: "text",
|
|
288
|
+
value: "1st value in the multinode",
|
|
289
|
+
},
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
id: "another-async",
|
|
293
|
+
async: true,
|
|
294
|
+
},
|
|
295
|
+
]);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
await waitFor(() => {
|
|
299
|
+
expect(updateNumber).toBe(2);
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
view = (player.getState() as InProgressState).controllers.view.currentView
|
|
303
|
+
?.lastUpdate;
|
|
304
|
+
|
|
305
|
+
expect(view?.actions[0].asset.type).toBe("action");
|
|
306
|
+
expect(view?.actions[1].asset.type).toBe("text");
|
|
307
|
+
expect(view?.actions[2]).toBeUndefined();
|
|
308
|
+
expect(updateNumber).toBe(2);
|
|
309
|
+
|
|
310
|
+
if (deferredResolve) {
|
|
311
|
+
deferredResolve([
|
|
312
|
+
{
|
|
313
|
+
asset: {
|
|
314
|
+
id: "value-2",
|
|
315
|
+
type: "text",
|
|
316
|
+
value: "2nd value in the multinode",
|
|
317
|
+
},
|
|
318
|
+
},
|
|
319
|
+
{
|
|
320
|
+
asset: {
|
|
321
|
+
id: "value-3",
|
|
322
|
+
type: "text",
|
|
323
|
+
value: "3rd value in the multinode",
|
|
324
|
+
},
|
|
325
|
+
},
|
|
326
|
+
]);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
await waitFor(() => {
|
|
330
|
+
expect(updateNumber).toBe(3);
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
view = (player.getState() as InProgressState).controllers.view.currentView
|
|
334
|
+
?.lastUpdate;
|
|
335
|
+
|
|
336
|
+
expect(view?.actions[0].asset.type).toBe("action");
|
|
337
|
+
expect(view?.actions[1].asset.type).toBe("text");
|
|
338
|
+
expect(view?.actions[2].asset.type).toBe("text");
|
|
339
|
+
expect(view?.actions[3].asset.type).toBe("text");
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
test("replaces async nodes with chained multiNodes singular", async () => {
|
|
343
|
+
const plugin = new AsyncNodePlugin({
|
|
344
|
+
plugins: [new AsyncNodePluginPlugin()],
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
let deferredResolve: ((value: any) => void) | undefined;
|
|
348
|
+
|
|
349
|
+
plugin.hooks.onAsyncNode.tap("test", async (node: Node.Async) => {
|
|
350
|
+
return new Promise((resolve) => {
|
|
351
|
+
deferredResolve = resolve;
|
|
352
|
+
});
|
|
353
|
+
});
|
|
354
|
+
let updateNumber = 0;
|
|
355
|
+
|
|
356
|
+
const player = new Player({ plugins: [plugin] });
|
|
357
|
+
|
|
358
|
+
player.hooks.viewController.tap("async-node-test", (vc) => {
|
|
359
|
+
vc.hooks.view.tap("async-node-test", (view) => {
|
|
360
|
+
view.hooks.onUpdate.tap("async-node-test", (update) => {
|
|
361
|
+
updateNumber++;
|
|
362
|
+
});
|
|
363
|
+
});
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
player.start(basicFRFWithActions as any);
|
|
367
|
+
|
|
368
|
+
let view = (player.getState() as InProgressState).controllers.view.currentView
|
|
369
|
+
?.lastUpdate;
|
|
370
|
+
|
|
371
|
+
expect(view).toBeDefined();
|
|
372
|
+
expect(view?.actions[1]).toBeUndefined();
|
|
373
|
+
|
|
374
|
+
await waitFor(() => {
|
|
375
|
+
expect(updateNumber).toBe(1);
|
|
376
|
+
expect(deferredResolve).toBeDefined();
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
if (deferredResolve) {
|
|
380
|
+
deferredResolve([
|
|
381
|
+
{
|
|
382
|
+
asset: {
|
|
383
|
+
id: "value-1",
|
|
384
|
+
type: "text",
|
|
385
|
+
value: "1st value in the multinode",
|
|
386
|
+
},
|
|
387
|
+
},
|
|
388
|
+
{
|
|
389
|
+
id: "another-async",
|
|
390
|
+
async: true,
|
|
391
|
+
},
|
|
392
|
+
]);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
await waitFor(() => {
|
|
396
|
+
expect(updateNumber).toBe(2);
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
view = (player.getState() as InProgressState).controllers.view.currentView
|
|
400
|
+
?.lastUpdate;
|
|
401
|
+
|
|
402
|
+
expect(view?.actions[0].asset.type).toBe("action");
|
|
403
|
+
expect(view?.actions[1].asset.type).toBe("text");
|
|
404
|
+
expect(view?.actions[2]).toBeUndefined();
|
|
405
|
+
|
|
406
|
+
if (deferredResolve) {
|
|
407
|
+
deferredResolve({
|
|
408
|
+
asset: {
|
|
409
|
+
id: "value-2",
|
|
410
|
+
type: "text",
|
|
411
|
+
value: "2nd value in the multinode",
|
|
412
|
+
},
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
await waitFor(() => {
|
|
417
|
+
expect(updateNumber).toBe(3);
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
view = (player.getState() as InProgressState).controllers.view.currentView
|
|
421
|
+
?.lastUpdate;
|
|
422
|
+
|
|
423
|
+
expect(view?.actions[0].asset.type).toBe("action");
|
|
424
|
+
expect(view?.actions[1].asset.type).toBe("text");
|
|
425
|
+
expect(view?.actions[2].asset.type).toBe("text");
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
test("should call onAsyncNode hook when async node is encountered", async () => {
|
|
429
|
+
const plugin = new AsyncNodePlugin({
|
|
430
|
+
plugins: [new AsyncNodePluginPlugin()],
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
let localNode: Node.Async;
|
|
434
|
+
plugin.hooks.onAsyncNode.tap("test", async (node: Node.Async) => {
|
|
435
|
+
if (node !== null) {
|
|
436
|
+
// assigns node value to a local variable
|
|
437
|
+
localNode = node;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
return new Promise((resolve) => {
|
|
441
|
+
resolve("Promise resolved");
|
|
442
|
+
});
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
const player = new Player({ plugins: [plugin] });
|
|
446
|
+
|
|
447
|
+
player.start(basicFRFWithActions as any);
|
|
448
|
+
|
|
449
|
+
await waitFor(() => {
|
|
450
|
+
expect(localNode.id).toStrictEqual("nodeId");
|
|
451
|
+
expect(localNode.type).toStrictEqual("async");
|
|
452
|
+
});
|
|
453
|
+
});
|
package/src/index.ts
CHANGED
|
@@ -1,107 +1,185 @@
|
|
|
1
|
-
import { NodeType, getNodeID } from
|
|
1
|
+
import { NodeType, getNodeID } from "@player-ui/player";
|
|
2
2
|
import type {
|
|
3
3
|
Player,
|
|
4
4
|
PlayerPlugin,
|
|
5
5
|
Node,
|
|
6
6
|
ParseObjectOptions,
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
ViewInstance,
|
|
8
|
+
Parser,
|
|
9
|
+
ViewPlugin,
|
|
10
|
+
Resolver,
|
|
11
|
+
} from "@player-ui/player";
|
|
12
|
+
import { AsyncParallelBailHook } from "tapable-ts";
|
|
13
|
+
import queueMicrotask from "queue-microtask";
|
|
14
|
+
import { omit } from "timm";
|
|
11
15
|
|
|
12
|
-
export * from
|
|
16
|
+
export * from "./types";
|
|
17
|
+
|
|
18
|
+
export interface AsyncNodePluginOptions {
|
|
19
|
+
/** A set of plugins to load */
|
|
20
|
+
plugins?: AsyncNodeViewPlugin[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface AsyncNodeViewPlugin extends ViewPlugin {
|
|
24
|
+
/** Use this to tap into the async node plugin hooks */
|
|
25
|
+
applyPlugin: (asyncNodePlugin: AsyncNodePlugin) => void;
|
|
26
|
+
|
|
27
|
+
asyncNode: AsyncParallelBailHook<[Node.Async], any>;
|
|
28
|
+
}
|
|
13
29
|
|
|
14
30
|
/**
|
|
15
31
|
* Async node plugin used to resolve async nodes in the content
|
|
16
32
|
* If an async node is present, allow users to provide a replacement node to be rendered when ready
|
|
17
33
|
*/
|
|
18
34
|
export class AsyncNodePlugin implements PlayerPlugin {
|
|
35
|
+
private plugins: AsyncNodeViewPlugin[] | undefined;
|
|
36
|
+
|
|
37
|
+
constructor(options: AsyncNodePluginOptions) {
|
|
38
|
+
if (options?.plugins) {
|
|
39
|
+
this.plugins = options.plugins;
|
|
40
|
+
options.plugins.forEach((plugin) => {
|
|
41
|
+
plugin.applyPlugin(this);
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
19
46
|
public readonly hooks = {
|
|
20
47
|
onAsyncNode: new AsyncParallelBailHook<[Node.Async], any>(),
|
|
21
48
|
};
|
|
22
49
|
|
|
23
|
-
name =
|
|
50
|
+
name = "AsyncNode";
|
|
51
|
+
|
|
52
|
+
apply(player: Player) {
|
|
53
|
+
player.hooks.viewController.tap(this.name, (viewController) => {
|
|
54
|
+
viewController.hooks.view.tap(this.name, (view) => {
|
|
55
|
+
this.plugins?.forEach((plugin) => {
|
|
56
|
+
plugin.apply(view);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export class AsyncNodePluginPlugin implements AsyncNodeViewPlugin {
|
|
64
|
+
public asyncNode = new AsyncParallelBailHook<[Node.Async], any>();
|
|
65
|
+
private basePlugin: AsyncNodePlugin | undefined;
|
|
66
|
+
|
|
67
|
+
name = "AsyncNode";
|
|
68
|
+
|
|
69
|
+
private resolvedMapping = new Map<string, any>();
|
|
24
70
|
|
|
25
|
-
private
|
|
71
|
+
private currentView: ViewInstance | undefined;
|
|
26
72
|
|
|
27
73
|
private isAsync(node: Node.Node | null): node is Node.Async {
|
|
28
74
|
return node?.type === NodeType.Async;
|
|
29
75
|
}
|
|
30
76
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
)
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
omit(obj, 'async'),
|
|
51
|
-
nodeType,
|
|
52
|
-
options
|
|
53
|
-
);
|
|
54
|
-
const parsedNodeId = getNodeID(parsedAsync);
|
|
55
|
-
if (parsedAsync !== null && parsedNodeId) {
|
|
56
|
-
return parser.createASTNode(
|
|
57
|
-
{
|
|
58
|
-
id: parsedNodeId,
|
|
59
|
-
type: NodeType.Async,
|
|
60
|
-
value: parsedAsync,
|
|
61
|
-
},
|
|
62
|
-
obj
|
|
63
|
-
);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
return null;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
77
|
+
applyParser(parser: Parser) {
|
|
78
|
+
parser.hooks.determineNodeType.tap(this.name, (obj) => {
|
|
79
|
+
if (Object.prototype.hasOwnProperty.call(obj, "async")) {
|
|
80
|
+
return NodeType.Async;
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
parser.hooks.parseNode.tap(
|
|
84
|
+
this.name,
|
|
85
|
+
(
|
|
86
|
+
obj: any,
|
|
87
|
+
nodeType: Node.ChildrenTypes,
|
|
88
|
+
options: ParseObjectOptions,
|
|
89
|
+
determinedNodeType: null | NodeType,
|
|
90
|
+
) => {
|
|
91
|
+
if (determinedNodeType === NodeType.Async) {
|
|
92
|
+
const parsedAsync = parser.parseObject(
|
|
93
|
+
omit(obj, "async"),
|
|
94
|
+
nodeType,
|
|
95
|
+
options,
|
|
69
96
|
);
|
|
97
|
+
const parsedNodeId = getNodeID(parsedAsync);
|
|
98
|
+
if (parsedAsync !== null && parsedNodeId) {
|
|
99
|
+
return parser.createASTNode(
|
|
100
|
+
{
|
|
101
|
+
id: parsedNodeId,
|
|
102
|
+
type: NodeType.Async,
|
|
103
|
+
value: parsedAsync,
|
|
104
|
+
},
|
|
105
|
+
obj,
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
applyResolverHooks(resolver: Resolver) {
|
|
116
|
+
resolver.hooks.beforeResolve.tap(this.name, (node, options) => {
|
|
117
|
+
let resolvedNode;
|
|
118
|
+
if (this.isAsync(node)) {
|
|
119
|
+
const mappedValue = this.resolvedMapping.get(node.id);
|
|
120
|
+
if (mappedValue) {
|
|
121
|
+
resolvedNode = mappedValue;
|
|
122
|
+
}
|
|
123
|
+
} else {
|
|
124
|
+
resolvedNode = null;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const newNode = resolvedNode || node;
|
|
128
|
+
if (!resolvedNode && node?.type === NodeType.Async) {
|
|
129
|
+
queueMicrotask(async () => {
|
|
130
|
+
const result = await this.basePlugin?.hooks.onAsyncNode.call(node);
|
|
131
|
+
const parsedNode =
|
|
132
|
+
options.parseNode && result ? options.parseNode(result) : undefined;
|
|
133
|
+
|
|
134
|
+
if (parsedNode) {
|
|
135
|
+
this.resolvedMapping.set(node.id, parsedNode);
|
|
136
|
+
this.currentView?.updateAsync();
|
|
137
|
+
}
|
|
70
138
|
});
|
|
71
139
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
140
|
+
return node;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return newNode;
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
apply(view: ViewInstance): void {
|
|
148
|
+
view.hooks.parser.tap("template", this.applyParser.bind(this));
|
|
149
|
+
view.hooks.resolver.tap("template", (resolver) => {
|
|
150
|
+
resolver.hooks.beforeResolve.tap(this.name, (node, options) => {
|
|
151
|
+
let resolvedNode;
|
|
152
|
+
if (this.isAsync(node)) {
|
|
153
|
+
const mappedValue = this.resolvedMapping.get(node.id);
|
|
154
|
+
if (mappedValue) {
|
|
155
|
+
resolvedNode = mappedValue;
|
|
156
|
+
}
|
|
157
|
+
} else {
|
|
158
|
+
resolvedNode = null;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const newNode = resolvedNode || node;
|
|
162
|
+
if (!resolvedNode && node?.type === NodeType.Async) {
|
|
163
|
+
queueMicrotask(async () => {
|
|
164
|
+
const result = await this.basePlugin?.hooks.onAsyncNode.call(node);
|
|
165
|
+
const parsedNode =
|
|
166
|
+
options.parseNode && result
|
|
167
|
+
? options.parseNode(result)
|
|
168
|
+
: undefined;
|
|
169
|
+
|
|
170
|
+
this.resolvedMapping.set(node.id, parsedNode ? parsedNode : node);
|
|
171
|
+
view.updateAsync();
|
|
102
172
|
});
|
|
103
|
-
|
|
173
|
+
|
|
174
|
+
return node;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return newNode;
|
|
104
178
|
});
|
|
105
179
|
});
|
|
106
180
|
}
|
|
181
|
+
|
|
182
|
+
applyPlugin(asyncNodePlugin: AsyncNodePlugin): void {
|
|
183
|
+
this.basePlugin = asyncNodePlugin;
|
|
184
|
+
}
|
|
107
185
|
}
|