@vestra-player/sdk 1.5.43 → 1.5.45
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 +21 -1
- package/dist/player-runtime.d.ts +1 -0
- package/dist/player-runtime.js +1 -0
- package/dist/plugin/index.js +4 -1
- package/dist/plugin/patch.d.ts +101 -0
- package/dist/plugin/patch.js +337 -0
- package/dist/plugin/player.d.ts +11 -0
- package/dist/plugin/player.js +80 -7
- package/dist/plugin/runtime.d.ts +64 -1
- package/dist/plugin/runtime.js +227 -16
- package/dist/plugin/scope.d.ts +9 -1
- package/dist/plugin/scope.js +24 -0
- package/dist/plugin/settings.js +2 -2
- package/dist/plugin/testing.d.ts +7 -0
- package/dist/plugin/testing.js +139 -14
- package/dist/plugin/types.d.ts +24 -15
- package/dist/plugin/ui.d.ts +7 -3
- package/dist/plugin/ui.js +236 -22
- package/dist/wire.d.ts +42 -3
- package/dist/wire.js +3 -3
- package/llms-full.txt +148 -26
- package/llms.txt +23 -23
- package/package.json +1 -1
- package/slint/vestra/markdown.slint +17 -11
- package/slint/vestra/widgets.slint +770 -1
package/dist/plugin/runtime.d.ts
CHANGED
|
@@ -23,6 +23,20 @@ export declare const failure: (code: string, message?: string) => PluginError;
|
|
|
23
23
|
export declare const uncertain: (error: unknown) => boolean;
|
|
24
24
|
/** Mark a promise as observed so an unused `ready` never crashes the runner. */
|
|
25
25
|
export declare function quiet<T>(promise: Promise<T>): Promise<T>;
|
|
26
|
+
/**
|
|
27
|
+
* The fields of the SDK's state copies that a binding or listener read, by
|
|
28
|
+
* state (`player`: `plugin.player.state`). An empty set: the state was
|
|
29
|
+
* taken whole (no field read was seen), so any change of it counts.
|
|
30
|
+
*/
|
|
31
|
+
export type Deps = Map<string, Set<string>>;
|
|
32
|
+
/** A change of the SDK's state copies: the fields that changed, by state. */
|
|
33
|
+
export type Change = ReadonlyMap<string, ReadonlySet<string>>;
|
|
34
|
+
/**
|
|
35
|
+
* The longest a binding waits for a full pass while precise passes skip
|
|
36
|
+
* it: state a plugin changes outside code the SDK runs (its own timer,
|
|
37
|
+
* without `update()`) still shows within about this long.
|
|
38
|
+
*/
|
|
39
|
+
export declare const SAFETY_PASS_MS = 1000;
|
|
26
40
|
export declare const same: (a: Json | undefined, b: Json | undefined) => boolean;
|
|
27
41
|
/** One value in flight, only the newest waiting. Each push learns its fate. */
|
|
28
42
|
export declare class Latest<T> {
|
|
@@ -54,6 +68,12 @@ export declare class Runtime {
|
|
|
54
68
|
readonly app: App;
|
|
55
69
|
/** `plugin.i18n.locale`, from the start-up handshake; English when unknown. */
|
|
56
70
|
readonly locale: Locale;
|
|
71
|
+
/**
|
|
72
|
+
* Protocol additions the player advertises at start-up (`features` in the
|
|
73
|
+
* handshake's app data, player 0.6.0): `ui.patch` (row patches of
|
|
74
|
+
* property writes). Not part of the plugin API; empty for older players.
|
|
75
|
+
*/
|
|
76
|
+
readonly features: ReadonlySet<string>;
|
|
57
77
|
constructor(transport: Transport, manifest: PluginManifest, directory: string, dataDirectory?: string, app?: unknown);
|
|
58
78
|
can(capability: string): boolean;
|
|
59
79
|
/** Lazily created singletons shared by every scope of this plugin. */
|
|
@@ -70,6 +90,36 @@ export declare class Runtime {
|
|
|
70
90
|
* on it, so the event counts as listened to while the binding reads it.
|
|
71
91
|
*/
|
|
72
92
|
reading(event: string): void;
|
|
93
|
+
/**
|
|
94
|
+
* A field of the SDK's copy of a state (`key`) is read; without `field`,
|
|
95
|
+
* the state is taken (`plugin.player.state`). What is being evaluated
|
|
96
|
+
* then depends on it: a precise pass after a change of that field runs
|
|
97
|
+
* it again.
|
|
98
|
+
*/
|
|
99
|
+
depends(key: string, field?: string): void;
|
|
100
|
+
/**
|
|
101
|
+
* Evaluate one part of the binding being evaluated (one `view.bind`
|
|
102
|
+
* function), identified by `token`. A precise pass skips it when nothing
|
|
103
|
+
* it read last time changed, and returns `undefined`; what it read then
|
|
104
|
+
* still counts for the binding.
|
|
105
|
+
*/
|
|
106
|
+
part<T>(token: object, read: () => T): {
|
|
107
|
+
value: T;
|
|
108
|
+
} | undefined;
|
|
109
|
+
/**
|
|
110
|
+
* Run plugin code (a state listener) and return the state copy fields it
|
|
111
|
+
* read; they are not counted as a binding's.
|
|
112
|
+
*/
|
|
113
|
+
observe<T>(run: () => T): {
|
|
114
|
+
value: T;
|
|
115
|
+
deps: Deps;
|
|
116
|
+
};
|
|
117
|
+
/**
|
|
118
|
+
* Whether code that read `deps` while `change` was delivered needs a full
|
|
119
|
+
* pass after it: it read a field that changed, or no field of that state
|
|
120
|
+
* at all (so the SDK cannot tell what it depends on).
|
|
121
|
+
*/
|
|
122
|
+
needsFull(deps: Deps, change: Change): boolean;
|
|
73
123
|
/**
|
|
74
124
|
* Evaluate `read` as `label`, added at `where`: the state it reads is
|
|
75
125
|
* reported as read by it. `field`: a command's `enabled` or `checked`,
|
|
@@ -82,8 +132,21 @@ export declare class Runtime {
|
|
|
82
132
|
* line (`main.ts:353`). Captured now, formatted when asked.
|
|
83
133
|
*/
|
|
84
134
|
callSite(): () => string | undefined;
|
|
85
|
-
/**
|
|
135
|
+
/**
|
|
136
|
+
* A full pass: every binding is evaluated. Requested after plugin code
|
|
137
|
+
* the SDK ran, by `plugin.update()`, and on any change other than a state
|
|
138
|
+
* copy's fields. Coalesced: one pass per microtask turn, however many
|
|
139
|
+
* requests.
|
|
140
|
+
*/
|
|
86
141
|
invalidate(): void;
|
|
142
|
+
/**
|
|
143
|
+
* Fields of the SDK's copy of a state (`key`) changed: a precise pass
|
|
144
|
+
* evaluates only the bindings (and view bind functions) that read one of
|
|
145
|
+
* them, unless a full pass is due anyway.
|
|
146
|
+
*/
|
|
147
|
+
changed(key: string, fields: Iterable<string>): void;
|
|
148
|
+
/** The plugin was disposed: cancel the pending safety pass, arm no more. */
|
|
149
|
+
stop(): void;
|
|
87
150
|
/**
|
|
88
151
|
* Host events with one `events.subscribe` per name, however many listeners.
|
|
89
152
|
* Without the `events` capability the listener simply never fires.
|
package/dist/plugin/runtime.js
CHANGED
|
@@ -53,6 +53,39 @@ export function quiet(promise) {
|
|
|
53
53
|
promise.catch(() => { });
|
|
54
54
|
return promise;
|
|
55
55
|
}
|
|
56
|
+
/** Whether something that read `deps` may see a different result after `change`. */
|
|
57
|
+
function affected(deps, change) {
|
|
58
|
+
for (const [key, fields] of change) {
|
|
59
|
+
const read = deps.get(key);
|
|
60
|
+
if (!read)
|
|
61
|
+
continue;
|
|
62
|
+
if (read.size === 0)
|
|
63
|
+
return true;
|
|
64
|
+
for (const field of fields)
|
|
65
|
+
if (read.has(field))
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
/** Add `from` to `into` (an empty set, the whole state, wins). */
|
|
71
|
+
function merge(into, from) {
|
|
72
|
+
for (const [key, fields] of from) {
|
|
73
|
+
const set = into.get(key);
|
|
74
|
+
if (!set)
|
|
75
|
+
into.set(key, new Set(fields));
|
|
76
|
+
else if (set.size && fields.size)
|
|
77
|
+
for (const field of fields)
|
|
78
|
+
set.add(field);
|
|
79
|
+
else
|
|
80
|
+
set.clear();
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* The longest a binding waits for a full pass while precise passes skip
|
|
85
|
+
* it: state a plugin changes outside code the SDK runs (its own timer,
|
|
86
|
+
* without `update()`) still shows within about this long.
|
|
87
|
+
*/
|
|
88
|
+
export const SAFETY_PASS_MS = 1000;
|
|
56
89
|
export const same = (a, b) => JSON.stringify(a) === JSON.stringify(b);
|
|
57
90
|
/** One value in flight, only the newest waiting. Each push learns its fate. */
|
|
58
91
|
export class Latest {
|
|
@@ -124,6 +157,22 @@ export class Runtime {
|
|
|
124
157
|
/** Who reads now (a command's field and where it was added). */
|
|
125
158
|
#reader;
|
|
126
159
|
#scheduled = false;
|
|
160
|
+
/** The next pass evaluates every binding (not only a state copy changed). */
|
|
161
|
+
#full = false;
|
|
162
|
+
/** State copy changes since the last pass (a precise pass, unless `#full`). */
|
|
163
|
+
#pending = new Map();
|
|
164
|
+
/** The change the running precise pass is for; `undefined` in a full pass. */
|
|
165
|
+
#pass;
|
|
166
|
+
/** The state copy fields read by what is being evaluated now. */
|
|
167
|
+
#deps;
|
|
168
|
+
/** Each part's reads and fields last time (`part`). */
|
|
169
|
+
#parts = new WeakMap();
|
|
170
|
+
/** A precise pass skipped something: a full pass follows (`SAFETY_PASS_MS`). */
|
|
171
|
+
#safety;
|
|
172
|
+
/** The running pass skipped a binding or a part. */
|
|
173
|
+
#skipped = false;
|
|
174
|
+
/** The plugin was disposed: no safety pass is armed any more. */
|
|
175
|
+
#stopped = false;
|
|
127
176
|
#events = new Map();
|
|
128
177
|
views = new Map();
|
|
129
178
|
commands = new Map();
|
|
@@ -132,6 +181,12 @@ export class Runtime {
|
|
|
132
181
|
app;
|
|
133
182
|
/** `plugin.i18n.locale`, from the start-up handshake; English when unknown. */
|
|
134
183
|
locale;
|
|
184
|
+
/**
|
|
185
|
+
* Protocol additions the player advertises at start-up (`features` in the
|
|
186
|
+
* handshake's app data, player 0.6.0): `ui.patch` (row patches of
|
|
187
|
+
* property writes). Not part of the plugin API; empty for older players.
|
|
188
|
+
*/
|
|
189
|
+
features;
|
|
135
190
|
constructor(transport, manifest, directory, dataDirectory, app) {
|
|
136
191
|
this.#transport = transport;
|
|
137
192
|
this.dataDirectory = dataDirectory;
|
|
@@ -139,6 +194,11 @@ export class Runtime {
|
|
|
139
194
|
this.host = createWire(transport);
|
|
140
195
|
const host = this.host;
|
|
141
196
|
this.app = appInfo(app, () => host.app.system());
|
|
197
|
+
const features = app
|
|
198
|
+
?.features;
|
|
199
|
+
this.features = new Set(Array.isArray(features)
|
|
200
|
+
? features.filter((name) => typeof name === "string")
|
|
201
|
+
: []);
|
|
142
202
|
this.locale = LOCALES.includes(transport.locale ?? "")
|
|
143
203
|
? transport.locale
|
|
144
204
|
: "en";
|
|
@@ -185,11 +245,12 @@ export class Runtime {
|
|
|
185
245
|
}
|
|
186
246
|
bind(evaluate) {
|
|
187
247
|
const held = new Map();
|
|
188
|
-
|
|
248
|
+
const binding = { held, deps: new Map() };
|
|
249
|
+
this.#bindings.set(evaluate, binding);
|
|
189
250
|
this.invalidate();
|
|
190
251
|
return {
|
|
191
252
|
dispose: async () => {
|
|
192
|
-
if (this.#bindings.get(evaluate) !==
|
|
253
|
+
if (this.#bindings.get(evaluate) !== binding)
|
|
193
254
|
return;
|
|
194
255
|
this.#bindings.delete(evaluate);
|
|
195
256
|
for (const hold of held.values())
|
|
@@ -220,6 +281,92 @@ export class Runtime {
|
|
|
220
281
|
return `${reader.label} is a function that reads ${read}${at}${hint}`;
|
|
221
282
|
});
|
|
222
283
|
}
|
|
284
|
+
/**
|
|
285
|
+
* A field of the SDK's copy of a state (`key`) is read; without `field`,
|
|
286
|
+
* the state is taken (`plugin.player.state`). What is being evaluated
|
|
287
|
+
* then depends on it: a precise pass after a change of that field runs
|
|
288
|
+
* it again.
|
|
289
|
+
*/
|
|
290
|
+
depends(key, field) {
|
|
291
|
+
const deps = this.#deps;
|
|
292
|
+
if (!deps)
|
|
293
|
+
return;
|
|
294
|
+
let fields = deps.get(key);
|
|
295
|
+
if (!fields)
|
|
296
|
+
deps.set(key, (fields = new Set()));
|
|
297
|
+
if (field !== undefined)
|
|
298
|
+
fields.add(field);
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Evaluate one part of the binding being evaluated (one `view.bind`
|
|
302
|
+
* function), identified by `token`. A precise pass skips it when nothing
|
|
303
|
+
* it read last time changed, and returns `undefined`; what it read then
|
|
304
|
+
* still counts for the binding.
|
|
305
|
+
*/
|
|
306
|
+
part(token, read) {
|
|
307
|
+
const last = this.#parts.get(token);
|
|
308
|
+
const outerReads = this.#reads;
|
|
309
|
+
const outerDeps = this.#deps;
|
|
310
|
+
if (last && this.#pass && !affected(last.deps, this.#pass)) {
|
|
311
|
+
this.#skipped = true;
|
|
312
|
+
if (outerReads)
|
|
313
|
+
for (const [event, why] of last.reads)
|
|
314
|
+
if (!outerReads.has(event))
|
|
315
|
+
outerReads.set(event, why);
|
|
316
|
+
if (outerDeps)
|
|
317
|
+
merge(outerDeps, last.deps);
|
|
318
|
+
return undefined;
|
|
319
|
+
}
|
|
320
|
+
const reads = new Map();
|
|
321
|
+
const deps = new Map();
|
|
322
|
+
this.#reads = outerReads && reads;
|
|
323
|
+
this.#deps = deps;
|
|
324
|
+
try {
|
|
325
|
+
return { value: read() };
|
|
326
|
+
}
|
|
327
|
+
finally {
|
|
328
|
+
this.#reads = outerReads;
|
|
329
|
+
this.#deps = outerDeps;
|
|
330
|
+
this.#parts.set(token, { reads, deps });
|
|
331
|
+
if (outerReads)
|
|
332
|
+
for (const [event, why] of reads)
|
|
333
|
+
if (!outerReads.has(event))
|
|
334
|
+
outerReads.set(event, why);
|
|
335
|
+
if (outerDeps)
|
|
336
|
+
merge(outerDeps, deps);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Run plugin code (a state listener) and return the state copy fields it
|
|
341
|
+
* read; they are not counted as a binding's.
|
|
342
|
+
*/
|
|
343
|
+
observe(run) {
|
|
344
|
+
const outerReads = this.#reads;
|
|
345
|
+
const outerDeps = this.#deps;
|
|
346
|
+
const deps = new Map();
|
|
347
|
+
this.#reads = undefined;
|
|
348
|
+
this.#deps = deps;
|
|
349
|
+
try {
|
|
350
|
+
return { value: run(), deps };
|
|
351
|
+
}
|
|
352
|
+
finally {
|
|
353
|
+
this.#reads = outerReads;
|
|
354
|
+
this.#deps = outerDeps;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Whether code that read `deps` while `change` was delivered needs a full
|
|
359
|
+
* pass after it: it read a field that changed, or no field of that state
|
|
360
|
+
* at all (so the SDK cannot tell what it depends on).
|
|
361
|
+
*/
|
|
362
|
+
needsFull(deps, change) {
|
|
363
|
+
for (const key of change.keys()) {
|
|
364
|
+
const read = deps.get(key);
|
|
365
|
+
if (!read || read.size === 0)
|
|
366
|
+
return true;
|
|
367
|
+
}
|
|
368
|
+
return affected(deps, change);
|
|
369
|
+
}
|
|
223
370
|
/**
|
|
224
371
|
* Evaluate `read` as `label`, added at `where`: the state it reads is
|
|
225
372
|
* reported as read by it. `field`: a command's `enabled` or `checked`,
|
|
@@ -275,18 +422,24 @@ export class Runtime {
|
|
|
275
422
|
return undefined;
|
|
276
423
|
}
|
|
277
424
|
/** Evaluate one binding, holding the events whose state it read. */
|
|
278
|
-
#evaluate(evaluate,
|
|
425
|
+
#evaluate(evaluate, binding) {
|
|
426
|
+
const held = binding.held;
|
|
279
427
|
const reads = new Map();
|
|
428
|
+
const deps = new Map();
|
|
280
429
|
const outer = this.#reads;
|
|
430
|
+
const outerDeps = this.#deps;
|
|
281
431
|
const reader = this.#reader;
|
|
282
432
|
this.#reads = reads;
|
|
433
|
+
this.#deps = deps;
|
|
283
434
|
this.#reader = undefined;
|
|
284
435
|
try {
|
|
285
436
|
evaluate();
|
|
286
437
|
}
|
|
287
438
|
finally {
|
|
288
439
|
this.#reads = outer;
|
|
440
|
+
this.#deps = outerDeps;
|
|
289
441
|
this.#reader = reader;
|
|
442
|
+
binding.deps = deps;
|
|
290
443
|
for (const [event, hold] of held)
|
|
291
444
|
if (!reads.has(event)) {
|
|
292
445
|
held.delete(event);
|
|
@@ -297,24 +450,82 @@ export class Runtime {
|
|
|
297
450
|
held.set(event, this.hold(event, why));
|
|
298
451
|
}
|
|
299
452
|
}
|
|
300
|
-
/**
|
|
453
|
+
/**
|
|
454
|
+
* A full pass: every binding is evaluated. Requested after plugin code
|
|
455
|
+
* the SDK ran, by `plugin.update()`, and on any change other than a state
|
|
456
|
+
* copy's fields. Coalesced: one pass per microtask turn, however many
|
|
457
|
+
* requests.
|
|
458
|
+
*/
|
|
301
459
|
invalidate() {
|
|
460
|
+
this.#full = true;
|
|
461
|
+
this.#schedule();
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* Fields of the SDK's copy of a state (`key`) changed: a precise pass
|
|
465
|
+
* evaluates only the bindings (and view bind functions) that read one of
|
|
466
|
+
* them, unless a full pass is due anyway.
|
|
467
|
+
*/
|
|
468
|
+
changed(key, fields) {
|
|
469
|
+
let set = this.#pending.get(key);
|
|
470
|
+
if (!set)
|
|
471
|
+
this.#pending.set(key, (set = new Set()));
|
|
472
|
+
for (const field of fields)
|
|
473
|
+
set.add(field);
|
|
474
|
+
this.#schedule();
|
|
475
|
+
}
|
|
476
|
+
#schedule() {
|
|
302
477
|
if (this.#scheduled)
|
|
303
478
|
return;
|
|
304
479
|
this.#scheduled = true;
|
|
305
|
-
queueMicrotask(() =>
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
480
|
+
queueMicrotask(() => this.#run());
|
|
481
|
+
}
|
|
482
|
+
/**
|
|
483
|
+
* One pass. A precise pass that skips anything arms a full pass
|
|
484
|
+
* `SAFETY_PASS_MS` later (unless a full pass comes first), for plugin
|
|
485
|
+
* state changed outside code the SDK ran.
|
|
486
|
+
*/
|
|
487
|
+
#run() {
|
|
488
|
+
this.#scheduled = false;
|
|
489
|
+
const full = this.#full;
|
|
490
|
+
const change = this.#pending;
|
|
491
|
+
this.#full = false;
|
|
492
|
+
this.#pending = new Map();
|
|
493
|
+
if (full) {
|
|
494
|
+
clearTimeout(this.#safety);
|
|
495
|
+
this.#safety = undefined;
|
|
496
|
+
}
|
|
497
|
+
this.#skipped = false;
|
|
498
|
+
for (const [evaluate, binding] of [...this.#bindings]) {
|
|
499
|
+
if (this.#bindings.get(evaluate) !== binding)
|
|
500
|
+
continue;
|
|
501
|
+
if (!full && !affected(binding.deps, change)) {
|
|
502
|
+
this.#skipped = true;
|
|
503
|
+
continue;
|
|
316
504
|
}
|
|
317
|
-
|
|
505
|
+
this.#pass = full ? undefined : change;
|
|
506
|
+
try {
|
|
507
|
+
this.#evaluate(evaluate, binding);
|
|
508
|
+
}
|
|
509
|
+
catch (error) {
|
|
510
|
+
console.error(`[${this.manifest.id}] binding failed:`, error);
|
|
511
|
+
}
|
|
512
|
+
finally {
|
|
513
|
+
this.#pass = undefined;
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
if (this.#skipped && !this.#safety && !this.#stopped) {
|
|
517
|
+
this.#safety = setTimeout(() => {
|
|
518
|
+
this.#safety = undefined;
|
|
519
|
+
this.invalidate();
|
|
520
|
+
}, SAFETY_PASS_MS);
|
|
521
|
+
this.#safety.unref?.();
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
/** The plugin was disposed: cancel the pending safety pass, arm no more. */
|
|
525
|
+
stop() {
|
|
526
|
+
this.#stopped = true;
|
|
527
|
+
clearTimeout(this.#safety);
|
|
528
|
+
this.#safety = undefined;
|
|
318
529
|
}
|
|
319
530
|
/**
|
|
320
531
|
* Host events with one `events.subscribe` per name, however many listeners.
|
package/dist/plugin/scope.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Runtime } from "./runtime.ts";
|
|
1
|
+
import type { Change, Runtime } from "./runtime.ts";
|
|
2
2
|
import type { Disposable, Plugin, PluginError, Scope } from "./types.ts";
|
|
3
3
|
export declare class ScopeImpl implements Plugin {
|
|
4
4
|
#private;
|
|
@@ -54,6 +54,14 @@ export declare class ScopeImpl implements Plugin {
|
|
|
54
54
|
report(error: unknown): void;
|
|
55
55
|
/** Invoke plugin code on its behalf: report failures, then refresh bindings. */
|
|
56
56
|
run(handler: (...args: never[]) => unknown, ...args: unknown[]): void;
|
|
57
|
+
/**
|
|
58
|
+
* Run a listener of a state change (`player.onChange`) like `run`, except
|
|
59
|
+
* that the full pass after it is left out when it returned synchronously
|
|
60
|
+
* and read only fields of the state that `change` left alone: its result
|
|
61
|
+
* then depends on nothing new. The precise pass for the change runs
|
|
62
|
+
* anyway, and a full pass follows within `SAFETY_PASS_MS`.
|
|
63
|
+
*/
|
|
64
|
+
runOn(change: Change, handler: (...args: never[]) => unknown, ...args: unknown[]): void;
|
|
57
65
|
update(): void;
|
|
58
66
|
dispose(): Promise<void>;
|
|
59
67
|
}
|
package/dist/plugin/scope.js
CHANGED
|
@@ -145,6 +145,30 @@ export class ScopeImpl {
|
|
|
145
145
|
}
|
|
146
146
|
this.update();
|
|
147
147
|
}
|
|
148
|
+
/**
|
|
149
|
+
* Run a listener of a state change (`player.onChange`) like `run`, except
|
|
150
|
+
* that the full pass after it is left out when it returned synchronously
|
|
151
|
+
* and read only fields of the state that `change` left alone: its result
|
|
152
|
+
* then depends on nothing new. The precise pass for the change runs
|
|
153
|
+
* anyway, and a full pass follows within `SAFETY_PASS_MS`.
|
|
154
|
+
*/
|
|
155
|
+
runOn(change, handler, ...args) {
|
|
156
|
+
if (this.disposed)
|
|
157
|
+
return;
|
|
158
|
+
let full = true;
|
|
159
|
+
try {
|
|
160
|
+
const { value, deps } = this.runtime.observe(() => handler(...args));
|
|
161
|
+
if (value instanceof Promise)
|
|
162
|
+
value.catch((error) => this.report(error)).finally(() => this.update());
|
|
163
|
+
else
|
|
164
|
+
full = this.runtime.needsFull(deps, change);
|
|
165
|
+
}
|
|
166
|
+
catch (error) {
|
|
167
|
+
this.report(error);
|
|
168
|
+
}
|
|
169
|
+
if (full)
|
|
170
|
+
this.update();
|
|
171
|
+
}
|
|
148
172
|
update() {
|
|
149
173
|
this.runtime.invalidate();
|
|
150
174
|
}
|
package/dist/plugin/settings.js
CHANGED
|
@@ -61,8 +61,8 @@ class Page extends ViewBase {
|
|
|
61
61
|
live() {
|
|
62
62
|
return this.#registered && !this.closed;
|
|
63
63
|
}
|
|
64
|
-
write(property,
|
|
65
|
-
return this.scope.runtime.host.settings.set(this.#id, property,
|
|
64
|
+
write(property, write) {
|
|
65
|
+
return this.scope.runtime.host.settings.set(this.#id, property, write);
|
|
66
66
|
}
|
|
67
67
|
async dispose() {
|
|
68
68
|
if (this.closed)
|
package/dist/plugin/testing.d.ts
CHANGED
|
@@ -2621,6 +2621,13 @@ export declare class TestHost implements Transport {
|
|
|
2621
2621
|
* for: on a loaded machine it can finish after `settle()` returns. Poll
|
|
2622
2622
|
* the result you assert on instead (second example).
|
|
2623
2623
|
*
|
|
2624
|
+
* `settle()` does not wait for the SDK's once-a-second full update pass
|
|
2625
|
+
* (SDK 1.5.45). After `setSource` (or another player state change) only
|
|
2626
|
+
* the bindings that read a changed field of `plugin.player.state` run
|
|
2627
|
+
* again; state the plugin changed outside a handler without
|
|
2628
|
+
* `plugin.update()` shows only in the next full pass. Have the plugin
|
|
2629
|
+
* call `plugin.update()` after such a change, or wait `settle(1000)`.
|
|
2630
|
+
*
|
|
2624
2631
|
* @param ms - Extra time to wait, for debounces and timers (default 0).
|
|
2625
2632
|
*
|
|
2626
2633
|
* @example Wait past a debounced save
|