@vestra-player/sdk 1.5.17 → 1.5.20
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 +26 -0
- package/dist/capabilities.js +1 -0
- package/dist/commands.js +0 -1
- package/dist/i18n-tools.d.ts +2 -2
- package/dist/i18n-tools.js +19 -1
- package/dist/player-runtime.d.ts +1 -1
- package/dist/player-runtime.js +1 -1
- package/dist/plugin/local.d.ts +7 -0
- package/dist/plugin/local.js +12 -0
- package/dist/plugin/markdown.d.ts +7 -0
- package/dist/plugin/markdown.js +62 -22
- package/dist/plugin/packages.js +5 -0
- package/dist/plugin/player.d.ts +2 -0
- package/dist/plugin/player.js +11 -6
- package/dist/plugin/services.js +10 -3
- package/dist/plugin/testing.d.ts +50 -10
- package/dist/plugin/testing.js +124 -23
- package/dist/plugin/types.d.ts +179 -24
- package/dist/plugin/ui.d.ts +4 -2
- package/dist/plugin/ui.js +31 -13
- package/dist/plugin/values.d.ts +15 -0
- package/dist/plugin/values.js +18 -0
- package/dist/wire.d.ts +3 -2
- package/dist/wire.js +3 -2
- package/llms-full.txt +171 -31
- package/llms.txt +23 -23
- package/package.json +1 -1
package/dist/plugin/testing.js
CHANGED
|
@@ -65,13 +65,15 @@ import { tmpdir } from "node:os";
|
|
|
65
65
|
import { extname, isAbsolute, join } from "node:path";
|
|
66
66
|
import { fileURLToPath } from "node:url";
|
|
67
67
|
import { HostError } from "../wire.js";
|
|
68
|
+
import { localSocketPath } from "./local.js";
|
|
68
69
|
import { BUILTIN_COMMANDS, BUILTIN_SUBMENUS, COMMAND_LIMITS, MENU_IDS, } from "../commands.js";
|
|
69
70
|
import { CAPABILITY_INFO, MESSAGE_LIMIT } from "../capabilities.js";
|
|
70
71
|
import { OSD_LIMITS } from "./osd.js";
|
|
71
72
|
import { SECRET_LIMITS } from "./services.js";
|
|
72
73
|
import { BINDING_INPUTS } from "./bindings.js";
|
|
73
74
|
import { THEME_PALETTES } from "../palettes.js";
|
|
74
|
-
import { UI_VALUE_LIMITS, valueLimitProblem } from "./values.js";
|
|
75
|
+
import { IMAGE_LIMITS, UI_VALUE_LIMITS, valueLimitProblem } from "./values.js";
|
|
76
|
+
import { validStep } from "./player.js";
|
|
75
77
|
import { nodeProblem } from "../processing.js";
|
|
76
78
|
import { RENDER_DEFAULTS, renderProblem, VIEW_DEFAULTS, viewProblem, } from "./view.js";
|
|
77
79
|
import { createPlugin } from "./index.js";
|
|
@@ -96,7 +98,7 @@ export { BINDING_INPUTS };
|
|
|
96
98
|
/** The colours of the player's palettes, by `Theme` token. */
|
|
97
99
|
export { THEME_PALETTES };
|
|
98
100
|
/** The player's property value limits, which the test host enforces as well. */
|
|
99
|
-
export { UI_VALUE_LIMITS };
|
|
101
|
+
export { IMAGE_LIMITS, UI_VALUE_LIMITS };
|
|
100
102
|
/**
|
|
101
103
|
* The player's limits on one text value ({@link Text}, {@link TextList},
|
|
102
104
|
* with everything nested in it), which the test host enforces as well:
|
|
@@ -624,8 +626,11 @@ export class TestHost {
|
|
|
624
626
|
this.emit("timeline.changed", { marks: this.timeline.marks });
|
|
625
627
|
}
|
|
626
628
|
/**
|
|
627
|
-
* Runtime images written to views (`plugin.ui.image
|
|
628
|
-
*
|
|
629
|
+
* Runtime images written to views (`plugin.ui.image`,
|
|
630
|
+
* `plugin.ui.remoteImage`), whole values and (SDK 1.5.19) images nested
|
|
631
|
+
* in rows, newest last. The SDK refuses images of released scopes before
|
|
632
|
+
* anything is sent. The test host does not read the files, so it does not
|
|
633
|
+
* check the player's per-view budget (`IMAGE_LIMITS.viewBytes`).
|
|
629
634
|
*/
|
|
630
635
|
images = [];
|
|
631
636
|
/**
|
|
@@ -699,14 +704,22 @@ export class TestHost {
|
|
|
699
704
|
return kept;
|
|
700
705
|
}
|
|
701
706
|
#seeImages(values) {
|
|
702
|
-
|
|
707
|
+
const see = (value) => {
|
|
703
708
|
const path = this.imagePath(value);
|
|
704
|
-
if (path !== null)
|
|
709
|
+
if (path !== null) {
|
|
710
|
+
const { ref, maxSide } = value;
|
|
705
711
|
this.images.push({
|
|
706
712
|
path,
|
|
707
|
-
ref: Number(
|
|
713
|
+
ref: Number(ref),
|
|
714
|
+
...(maxSide === undefined ? {} : { maxSide }),
|
|
708
715
|
});
|
|
709
|
-
|
|
716
|
+
}
|
|
717
|
+
else if (value !== null && typeof value === "object")
|
|
718
|
+
for (const item of Object.values(value))
|
|
719
|
+
see(item);
|
|
720
|
+
};
|
|
721
|
+
for (const value of Object.values(values))
|
|
722
|
+
see(value);
|
|
710
723
|
}
|
|
711
724
|
// ------------------------------------------------------------ round 3b batch 2
|
|
712
725
|
/** `drops.provide` registrations of this plugin, in order (`id` is the SDK's). */
|
|
@@ -1013,8 +1026,9 @@ export class TestHost {
|
|
|
1013
1026
|
*/
|
|
1014
1027
|
jobs = new Map();
|
|
1015
1028
|
/**
|
|
1016
|
-
* `openStream`'s transport: a
|
|
1017
|
-
*
|
|
1029
|
+
* `openStream`'s transport: a local stream (a Unix socket on macOS and
|
|
1030
|
+
* Linux, a named pipe on Windows) or, with `"tcp"`, loopback TCP where the
|
|
1031
|
+
* plugin must send the token first, as the player on Windows answers.
|
|
1018
1032
|
* Set it before the plugin calls `openStream`.
|
|
1019
1033
|
*/
|
|
1020
1034
|
producerTransport = "unix";
|
|
@@ -1239,6 +1253,30 @@ export class TestHost {
|
|
|
1239
1253
|
// ------------------------------------------------------------ core actions
|
|
1240
1254
|
/** The `player.setStepSeek` value in effect (default 5). */
|
|
1241
1255
|
stepSeek = 5;
|
|
1256
|
+
/** The `player.setStepSeek` `keyframes` in effect (default `false`, SDK 1.5.19). */
|
|
1257
|
+
stepSeekKeyframes = false;
|
|
1258
|
+
/**
|
|
1259
|
+
* Test data: the keyframe times (seconds, ascending) of the current
|
|
1260
|
+
* source, for keyframe step seeks. `null` (the default): none modelled,
|
|
1261
|
+
* and step seeks are exact even with `stepSeekKeyframes`.
|
|
1262
|
+
*
|
|
1263
|
+
* @example
|
|
1264
|
+
* ```ts
|
|
1265
|
+
* host.setSource({ phase: "ready", duration: 45, position: 12 });
|
|
1266
|
+
* host.keyframes = [0, 10, 20, 30, 40];
|
|
1267
|
+
* await plugin.player.setStepSeek({ seconds: 5, keyframes: true });
|
|
1268
|
+
* await plugin.host.run("seek-forward");
|
|
1269
|
+
* assert.equal(host.source.position, 20);
|
|
1270
|
+
* assert.equal(host.osd.at(-1), "Forward 5 s: 00:20 / 00:45");
|
|
1271
|
+
* ```
|
|
1272
|
+
*/
|
|
1273
|
+
keyframes = null;
|
|
1274
|
+
/**
|
|
1275
|
+
* Every step seek the core ran (`seek-backward` / `seek-forward`, from a
|
|
1276
|
+
* key, the menu or `host.run`), newest last: the step, whether it went to
|
|
1277
|
+
* a keyframe, and where it landed.
|
|
1278
|
+
*/
|
|
1279
|
+
coreSeeks = [];
|
|
1242
1280
|
/** Every `player.step`, newest last. */
|
|
1243
1281
|
steps = [];
|
|
1244
1282
|
/** Every core action run (`host.run`, a core command, a pressed core binding), newest last. */
|
|
@@ -1468,7 +1506,7 @@ export class TestHost {
|
|
|
1468
1506
|
this.coreCommands.set(`host/${id}`, command);
|
|
1469
1507
|
}
|
|
1470
1508
|
}
|
|
1471
|
-
#runCore(action) {
|
|
1509
|
+
#runCore(action, seconds) {
|
|
1472
1510
|
const state = this.#coreState(action);
|
|
1473
1511
|
if (!state.enabled)
|
|
1474
1512
|
throw new HostError("unavailable", state.reason ?? "unavailable");
|
|
@@ -1510,13 +1548,36 @@ export class TestHost {
|
|
|
1510
1548
|
return;
|
|
1511
1549
|
case "seek-backward":
|
|
1512
1550
|
case "seek-forward": {
|
|
1513
|
-
const
|
|
1551
|
+
const step = seconds ?? this.stepSeek;
|
|
1552
|
+
const delta = action === "seek-forward" ? step : -step;
|
|
1514
1553
|
const base = s.position ?? 0;
|
|
1515
|
-
|
|
1554
|
+
let target = Math.max(0, Math.min(s.duration ?? Infinity, base + delta));
|
|
1555
|
+
// Keyframe mode, as the player: forward on the first keyframe at or
|
|
1556
|
+
// after the target (none: the exact target), backward on the last at
|
|
1557
|
+
// or before it; only sources with moving video that can seek.
|
|
1558
|
+
const keyframes = this.stepSeekKeyframes &&
|
|
1559
|
+
this.keyframes !== null &&
|
|
1560
|
+
s.hasVideo !== false &&
|
|
1561
|
+
s.capabilities.seek;
|
|
1562
|
+
if (keyframes && target !== base) {
|
|
1563
|
+
const keys = this.keyframes;
|
|
1564
|
+
target =
|
|
1565
|
+
delta > 0
|
|
1566
|
+
? (keys.find((k) => k >= target) ?? target)
|
|
1567
|
+
: keys
|
|
1568
|
+
.filter((k) => k <= target)
|
|
1569
|
+
.reduce((a, k) => Math.max(a, k), 0);
|
|
1570
|
+
}
|
|
1516
1571
|
this.#intent({
|
|
1517
1572
|
kind: "control",
|
|
1518
1573
|
control: { kind: "seek", seconds: target },
|
|
1519
1574
|
});
|
|
1575
|
+
this.coreSeeks.push({
|
|
1576
|
+
action,
|
|
1577
|
+
seconds: step,
|
|
1578
|
+
keyframes,
|
|
1579
|
+
landed: target,
|
|
1580
|
+
});
|
|
1520
1581
|
this.osd.push(this.#hostText(delta < 0 ? "Back {} s: {} / {}" : "Forward {} s: {} / {}", [Math.abs(delta), time(target), time(s.duration)]));
|
|
1521
1582
|
return;
|
|
1522
1583
|
}
|
|
@@ -1884,9 +1945,10 @@ export class TestHost {
|
|
|
1884
1945
|
*/
|
|
1885
1946
|
candidates = new Map();
|
|
1886
1947
|
/**
|
|
1887
|
-
* Remote
|
|
1888
|
-
* JPEG file served for it, or `null` for a failing
|
|
1889
|
-
* fetched from the network in tests; unknown
|
|
1948
|
+
* Remote images (`plugin.ui.markdown`, `plugin.ui.remoteImage`): address
|
|
1949
|
+
* → a local PNG or JPEG file served for it, or `null` for a failing
|
|
1950
|
+
* download. Nothing is fetched from the network in tests; unknown
|
|
1951
|
+
* addresses fail.
|
|
1890
1952
|
*/
|
|
1891
1953
|
remoteImages = new Map();
|
|
1892
1954
|
/**
|
|
@@ -2676,14 +2738,27 @@ export class TestHost {
|
|
|
2676
2738
|
];
|
|
2677
2739
|
if (!actions.includes(String(p.action)))
|
|
2678
2740
|
throw new HostError("invalid", `unknown host action ${p.action}`);
|
|
2679
|
-
|
|
2741
|
+
// SDK 1.5.19: one step seek of another step.
|
|
2742
|
+
let seconds;
|
|
2743
|
+
if (p.seconds !== undefined && p.seconds !== null) {
|
|
2744
|
+
if (p.action !== "seek-backward" && p.action !== "seek-forward")
|
|
2745
|
+
throw new HostError("invalid", `seconds applies only to seek-backward and seek-forward, not ${p.action}`);
|
|
2746
|
+
if (!validStep(p.seconds))
|
|
2747
|
+
throw new HostError("invalid", "seconds must be 0.1..3600 in tenths");
|
|
2748
|
+
seconds = p.seconds;
|
|
2749
|
+
}
|
|
2750
|
+
this.#runCore(String(p.action), seconds);
|
|
2680
2751
|
},
|
|
2681
2752
|
"player.setStepSeek": (p) => {
|
|
2682
|
-
|
|
2683
|
-
if (!(seconds >= 0.1 && seconds <= 3600) ||
|
|
2684
|
-
Math.abs(Math.round(seconds * 10) - seconds * 10) > 1e-9)
|
|
2753
|
+
if (!validStep(p.seconds))
|
|
2685
2754
|
throw new HostError("invalid", "seconds must be 0.1..3600 in tenths");
|
|
2686
|
-
|
|
2755
|
+
if (p.keyframes !== undefined &&
|
|
2756
|
+
p.keyframes !== null &&
|
|
2757
|
+
typeof p.keyframes !== "boolean")
|
|
2758
|
+
throw new HostError("invalid", "keyframes must be a boolean");
|
|
2759
|
+
// Written as a pair, as the player does.
|
|
2760
|
+
this.stepSeek = p.seconds;
|
|
2761
|
+
this.stepSeekKeyframes = p.keyframes === true;
|
|
2687
2762
|
},
|
|
2688
2763
|
"player.step": (p) => {
|
|
2689
2764
|
this.#audioCondition(p.expected);
|
|
@@ -3161,6 +3236,9 @@ export class TestHost {
|
|
|
3161
3236
|
const entry = this.packages.find((each) => each.id === id);
|
|
3162
3237
|
if (!entry)
|
|
3163
3238
|
throw new HostError("missing", `no plugin ${id}`);
|
|
3239
|
+
// As the player (with SDK 1.5.18 on): users manage third-party plugins only.
|
|
3240
|
+
if (entry.builtin && p.enabled === false)
|
|
3241
|
+
throw new HostError("unsupported", `${id} is a built-in plugin: built-in plugins cannot be disabled`);
|
|
3164
3242
|
this.packageCalls.push({
|
|
3165
3243
|
method: "setEnabled",
|
|
3166
3244
|
id,
|
|
@@ -3169,6 +3247,28 @@ export class TestHost {
|
|
|
3169
3247
|
entry.state = p.enabled === true ? "running" : "disabled";
|
|
3170
3248
|
this.setPackages(this.packages);
|
|
3171
3249
|
},
|
|
3250
|
+
// As the player (SDK 1.5.19): stop and start again, built-ins included;
|
|
3251
|
+
// `packages.changed` shows `starting`, then `running`.
|
|
3252
|
+
"packages.reload": (p) => {
|
|
3253
|
+
const id = String(p.id);
|
|
3254
|
+
if (id === this.manifestId)
|
|
3255
|
+
throw new HostError("invalid", "a plugin cannot reload itself");
|
|
3256
|
+
const entry = this.packages.find((each) => each.id === id);
|
|
3257
|
+
if (!entry)
|
|
3258
|
+
throw new HostError("missing", `no plugin ${id}`);
|
|
3259
|
+
if (entry.state === "starting")
|
|
3260
|
+
throw new HostError("busy", "the package is being installed or reloaded");
|
|
3261
|
+
if (entry.state === "disabled")
|
|
3262
|
+
throw new HostError("invalid", `${id} is disabled: enable it instead of reloading it`);
|
|
3263
|
+
this.packageCalls.push({ method: "reload", id });
|
|
3264
|
+
entry.state = "starting";
|
|
3265
|
+
entry.error = null;
|
|
3266
|
+
this.setPackages(this.packages);
|
|
3267
|
+
setTimeout(() => {
|
|
3268
|
+
entry.state = "running";
|
|
3269
|
+
this.setPackages(this.packages);
|
|
3270
|
+
}, 0);
|
|
3271
|
+
},
|
|
3172
3272
|
"packages.validate": (p) => this.#validate(String(p.directory)),
|
|
3173
3273
|
"packages.validateArchive": (p) => this.#validate(String(p.file)),
|
|
3174
3274
|
"packages.install": (p) => {
|
|
@@ -3221,7 +3321,7 @@ export class TestHost {
|
|
|
3221
3321
|
if (!entry)
|
|
3222
3322
|
throw new HostError("missing", `no plugin ${id}`);
|
|
3223
3323
|
if (entry.builtin)
|
|
3224
|
-
throw new HostError("unsupported", "built-in plugins
|
|
3324
|
+
throw new HostError("unsupported", "built-in plugins cannot be uninstalled");
|
|
3225
3325
|
if (entry.origin !== "installed")
|
|
3226
3326
|
throw new HostError("unsupported", "only installed plugins can be uninstalled");
|
|
3227
3327
|
this.packageCalls.push({
|
|
@@ -3436,7 +3536,8 @@ export class TestHost {
|
|
|
3436
3536
|
const id = ++this.#next.producer;
|
|
3437
3537
|
const tcp = this.producerTransport === "tcp";
|
|
3438
3538
|
const token = tcp ? randomBytes(32) : null;
|
|
3439
|
-
|
|
3539
|
+
// A Unix socket, or a named pipe on Windows (`localSocketPath`).
|
|
3540
|
+
const path = localSocketPath(this.#scratch, `producer-${id}`);
|
|
3440
3541
|
const producer = {
|
|
3441
3542
|
id,
|
|
3442
3543
|
receivedFrames: 0,
|
package/dist/plugin/types.d.ts
CHANGED
|
@@ -578,11 +578,12 @@ export interface Commands {
|
|
|
578
578
|
* An image file for a Slint `image` property, made by {@link Ui.image}.
|
|
579
579
|
*
|
|
580
580
|
* @remarks
|
|
581
|
-
* Opaque: pass it
|
|
582
|
-
* {@link View.
|
|
583
|
-
*
|
|
584
|
-
*
|
|
585
|
-
*
|
|
581
|
+
* Opaque: pass it where the Slint type is `image`, with {@link View.set} or
|
|
582
|
+
* {@link View.bind}: the whole value of an `image` property or, from SDK
|
|
583
|
+
* 1.5.19 (player 0.5.1), an `image` field at any depth, such as the logo of
|
|
584
|
+
* each row of a list. Making the reference does not read the file: the
|
|
585
|
+
* player checks and decodes it each time a view uses it ({@link Ui.image}
|
|
586
|
+
* has the rules and limits).
|
|
586
587
|
*
|
|
587
588
|
* A reference belongs to the {@link Scope} that created it. Once that scope
|
|
588
589
|
* is disposed, writing the reference fails with `invalid` (reported to
|
|
@@ -596,6 +597,22 @@ export interface Commands {
|
|
|
596
597
|
* await cover.show();
|
|
597
598
|
* ```
|
|
598
599
|
*
|
|
600
|
+
* @example A logo in each row
|
|
601
|
+
* ```slint
|
|
602
|
+
* export struct Row { key: string, name: string, icon: image }
|
|
603
|
+
* ```
|
|
604
|
+
* ```ts
|
|
605
|
+
* const items = [{ id: "a", name: "A", iconPath: `${plugin.dataDirectory}/a.png` }];
|
|
606
|
+
* const list = plugin.windows.create("ui/list.slint");
|
|
607
|
+
* list.bind(() => ({
|
|
608
|
+
* rows: items.map((item) => ({
|
|
609
|
+
* key: item.id,
|
|
610
|
+
* name: item.name,
|
|
611
|
+
* icon: plugin.ui.image(item.iconPath, { maxSide: 64 }),
|
|
612
|
+
* })),
|
|
613
|
+
* }));
|
|
614
|
+
* ```
|
|
615
|
+
*
|
|
599
616
|
* @see {@link Ui.image}, [Windows, panels, settings pages, dialogs and the OSD](../../docs/concepts/ui.md)
|
|
600
617
|
* @since 1.0.0
|
|
601
618
|
* @category UI
|
|
@@ -606,6 +623,24 @@ export interface ImageRef {
|
|
|
606
623
|
/** Brand that marks the value as an image reference; never construct one yourself. */
|
|
607
624
|
readonly __vestraImage: true;
|
|
608
625
|
}
|
|
626
|
+
/**
|
|
627
|
+
* Options of {@link Ui.image} and {@link Ui.remoteImage}.
|
|
628
|
+
*
|
|
629
|
+
* @since 1.5.19
|
|
630
|
+
* @category UI
|
|
631
|
+
*/
|
|
632
|
+
export interface ImageOptions {
|
|
633
|
+
/**
|
|
634
|
+
* Decode the image scaled down, aspect kept, so that neither side is over
|
|
635
|
+
* this many pixels (an integer 1–4096); smaller images stay as they are.
|
|
636
|
+
* Use about twice the drawn size (64 for a 32 px logo) so that it stays
|
|
637
|
+
* sharp on high-density screens. It keeps the decoded memory small: a
|
|
638
|
+
* 1024×1024 icon is 4 MB decoded, 16 KB at `maxSide: 64`.
|
|
639
|
+
*
|
|
640
|
+
* @player 0.5.1; older players refuse the reference (`invalid`).
|
|
641
|
+
*/
|
|
642
|
+
maxSide?: number;
|
|
643
|
+
}
|
|
609
644
|
/**
|
|
610
645
|
* A Markdown document for `MarkdownView` or `MarkdownBlocks`, made by
|
|
611
646
|
* {@link Ui.markdown}.
|
|
@@ -665,9 +700,8 @@ export interface MarkdownOptions {
|
|
|
665
700
|
remoteImages?: boolean;
|
|
666
701
|
}
|
|
667
702
|
/**
|
|
668
|
-
* The value of one Slint property: JSON with Markdown documents
|
|
669
|
-
* depth ({@link PropData})
|
|
670
|
-
* property.
|
|
703
|
+
* The value of one Slint property: JSON with Markdown documents and images
|
|
704
|
+
* at any depth ({@link PropData}).
|
|
671
705
|
*
|
|
672
706
|
* @remarks
|
|
673
707
|
* JSON becomes Slint values as you would expect: booleans for `bool`,
|
|
@@ -684,13 +718,20 @@ export interface MarkdownOptions {
|
|
|
684
718
|
*
|
|
685
719
|
* A {@link MarkdownRef} goes where the Slint type is `[MarkdownBlock]`: the
|
|
686
720
|
* whole value, or a field at any depth (SDK 1.5.4; older players refuse a
|
|
687
|
-
* nested one). An {@link ImageRef}
|
|
688
|
-
*
|
|
721
|
+
* nested one). An {@link ImageRef} goes where the Slint type is `image`: the
|
|
722
|
+
* whole value, or a field at any depth (SDK 1.5.19, player 0.5.1; older
|
|
723
|
+
* players refuse a nested one with `invalid`). The images nested in the
|
|
724
|
+
* values of one view (a window, panel or settings page) may show at most
|
|
725
|
+
* 32 MB of decoded pixels together (`IMAGE_LIMITS.viewBytes` in
|
|
726
|
+
* `@vestra-player/sdk/runtime`): a write
|
|
727
|
+
* that would pass it is refused with `limit`; use
|
|
728
|
+
* {@link ImageOptions.maxSide} for small pictures such as logos and
|
|
729
|
+
* thumbnails.
|
|
689
730
|
*
|
|
690
731
|
* Limits per value (`UI_VALUE_LIMITS` in `@vestra-player/sdk/testing`,
|
|
691
732
|
* player 0.4.3; older players: 4096 values and 1024 rows): nesting depth 8,
|
|
692
733
|
* 16384 values in total (every array, object, string, number, boolean and
|
|
693
|
-
* null counts, the value itself included; a document is one), 4096 rows
|
|
734
|
+
* null counts, the value itself included; a document or an image is one), 4096 rows
|
|
694
735
|
* per array, 64 KiB per string or field name and 1 MiB of text in total,
|
|
695
736
|
* field names and the text of its Markdown documents included (256 KB per
|
|
696
737
|
* document). A list of rows with 8 fields is 9 values per row, so about
|
|
@@ -708,8 +749,8 @@ export interface MarkdownOptions {
|
|
|
708
749
|
*/
|
|
709
750
|
export type PropValue = PropData | ImageRef;
|
|
710
751
|
/**
|
|
711
|
-
* JSON whose values may also be Markdown documents
|
|
712
|
-
* of {@link PropValue} that nests.
|
|
752
|
+
* JSON whose values may also be Markdown documents and (SDK 1.5.19) images,
|
|
753
|
+
* at any depth: the part of {@link PropValue} that nests.
|
|
713
754
|
*
|
|
714
755
|
* @example A message list whose rows carry a document
|
|
715
756
|
* ```ts
|
|
@@ -721,7 +762,7 @@ export type PropValue = PropData | ImageRef;
|
|
|
721
762
|
* @since 1.5.4
|
|
722
763
|
* @category UI
|
|
723
764
|
*/
|
|
724
|
-
export type PropData = null | boolean | number | string | MarkdownRef | Text | TextList | PropData[] | {
|
|
765
|
+
export type PropData = null | boolean | number | string | MarkdownRef | ImageRef | Text | TextList | PropData[] | {
|
|
725
766
|
[key: string]: PropData;
|
|
726
767
|
};
|
|
727
768
|
/**
|
|
@@ -778,10 +819,13 @@ export interface Ui {
|
|
|
778
819
|
* the main window being rebuilt): it must be inside this plugin's package
|
|
779
820
|
* directory or {@link Plugin.dataDirectory} after following links, at most
|
|
780
821
|
* 16 MB and 4096×4096 pixels, and PNG or JPEG by content (the extension
|
|
781
|
-
* does not matter). A failed check
|
|
782
|
-
* for a file that does not exist, `invalid`
|
|
783
|
-
* {@link Scope.onError} and the property keeps its
|
|
784
|
-
* initial properties of a tool window it makes
|
|
822
|
+
* does not matter). A failed check of a whole-value image is a failed
|
|
823
|
+
* property write: `missing` for a file that does not exist, `invalid`
|
|
824
|
+
* otherwise. It goes to {@link Scope.onError} and the property keeps its
|
|
825
|
+
* previous image; in the initial properties of a tool window it makes
|
|
826
|
+
* {@link Window.show} reject. A nested image (a field of a row, SDK
|
|
827
|
+
* 1.5.19) that fails its check is drawn empty instead, and the rest of
|
|
828
|
+
* the value is shown, as a Markdown image shows its alt text.
|
|
785
829
|
*
|
|
786
830
|
* The player caches decoded images per plugin (up to 64 MB, least recently
|
|
787
831
|
* used first) and does not watch files. Every call returns a new value, so
|
|
@@ -792,9 +836,12 @@ export interface Ui {
|
|
|
792
836
|
*
|
|
793
837
|
* @param path - Absolute path of the image file, inside the package
|
|
794
838
|
* directory or the data directory.
|
|
839
|
+
* @param options - `maxSide` (SDK 1.5.19, player 0.5.1): decode the image
|
|
840
|
+
* scaled down, aspect kept, so that neither side is over it.
|
|
795
841
|
* @returns A new {@link ImageRef}, owned by this scope.
|
|
796
842
|
*
|
|
797
|
-
* @throws `invalid` (throws) `path` is not a non-empty string
|
|
843
|
+
* @throws `invalid` (throws) `path` is not a non-empty string, or
|
|
844
|
+
* `maxSide` is not an integer 1–4096.
|
|
798
845
|
*
|
|
799
846
|
* @example Show a thumbnail the plugin rendered
|
|
800
847
|
* ```ts
|
|
@@ -807,7 +854,51 @@ export interface Ui {
|
|
|
807
854
|
*
|
|
808
855
|
* @since 1.0.0
|
|
809
856
|
*/
|
|
810
|
-
image(path: string): ImageRef;
|
|
857
|
+
image(path: string, options?: ImageOptions): ImageRef;
|
|
858
|
+
/**
|
|
859
|
+
* Download an `https:` PNG or JPEG image (a thumbnail, a repository's
|
|
860
|
+
* plugin icon) and reference the local copy, as {@link Ui.image} does.
|
|
861
|
+
*
|
|
862
|
+
* @remarks
|
|
863
|
+
* Downloads like {@link Ui.markdown}'s remote images: Node's `fetch` in
|
|
864
|
+
* this plugin's process, at most 4 MB and 10 s per image, 4 downloads at a
|
|
865
|
+
* time per plugin, into `<dataDirectory>/.vestra-cache/images/`; the cache
|
|
866
|
+
* keeps 32 MB and fetches an address again after 24 hours, so a list that
|
|
867
|
+
* shows the same thumbnails again reads them from disk.
|
|
868
|
+
*
|
|
869
|
+
* @capability None (the plugin process has network access).
|
|
870
|
+
* @player Any (the reference is an ordinary {@link ImageRef}); nested in a
|
|
871
|
+
* value, player 0.5.1.
|
|
872
|
+
*
|
|
873
|
+
* @param url - The image's `https:` address.
|
|
874
|
+
* @param options - As for {@link Ui.image}.
|
|
875
|
+
* @returns The reference, owned by this scope, once the file is in the
|
|
876
|
+
* cache.
|
|
877
|
+
*
|
|
878
|
+
* @throws `invalid` (rejects) `url` is not an `https:` address, or
|
|
879
|
+
* `maxSide` is not an integer 1–4096.
|
|
880
|
+
* @throws `unavailable` (rejects) the download failed, timed out, was too
|
|
881
|
+
* large, or is not a PNG or JPEG image; or the plugin has no data
|
|
882
|
+
* directory.
|
|
883
|
+
*
|
|
884
|
+
* @example Search results with thumbnails
|
|
885
|
+
* ```ts
|
|
886
|
+
* const results = [{ title: "A", thumbnailUrl: "https://example.com/a.jpg" }];
|
|
887
|
+
* const placeholder = plugin.ui.image(`${plugin.dataDirectory}/blank.png`);
|
|
888
|
+
* const rows = await Promise.all(
|
|
889
|
+
* results.map(async (r) => ({
|
|
890
|
+
* title: r.title,
|
|
891
|
+
* thumb: await plugin.ui
|
|
892
|
+
* .remoteImage(r.thumbnailUrl, { maxSide: 320 })
|
|
893
|
+
* .catch(() => placeholder),
|
|
894
|
+
* })),
|
|
895
|
+
* );
|
|
896
|
+
* plugin.windows.create("ui/results.slint").set({ rows });
|
|
897
|
+
* ```
|
|
898
|
+
*
|
|
899
|
+
* @since 1.5.19
|
|
900
|
+
*/
|
|
901
|
+
remoteImage(url: string, options?: ImageOptions): Promise<ImageRef>;
|
|
811
902
|
/**
|
|
812
903
|
* Prepare a Markdown document for `MarkdownView` (`@vestra/markdown.slint`).
|
|
813
904
|
*
|
|
@@ -3729,9 +3820,25 @@ export interface Player {
|
|
|
3729
3820
|
* The core's first-tier relative seek (`seek-backward` / `seek-forward`,
|
|
3730
3821
|
* ← / →): 0.1–3600 seconds in tenths, default 5. The newest call of any
|
|
3731
3822
|
* plugin wins; it reverts when that plugin stops.
|
|
3823
|
+
*
|
|
3824
|
+
* `keyframes: true` (player 0.5.1, SDK 1.5.19): the step seeks
|
|
3825
|
+
* (`seek-backward` / `seek-forward`, including {@link Host.run} with
|
|
3826
|
+
* `seconds` and the picture-in-picture skips) land on a keyframe instead
|
|
3827
|
+
* of the exact time: forward on the first keyframe at or after position +
|
|
3828
|
+
* step, backward on the last at or before position − step, and they
|
|
3829
|
+
* always move. Presses during a seek add up and continue from where it
|
|
3830
|
+
* landed; the on-screen text shows the landed time. Sources without moving
|
|
3831
|
+
* video seek exactly. The seek bar, {@link Player.seek}, scrubs, chapters,
|
|
3832
|
+
* loops and {@link Player.step} are unaffected. Omitted or `false`: exact
|
|
3833
|
+
* seeks, as before. `seconds` and `keyframes` are written as a pair: the
|
|
3834
|
+
* newest call sets both.
|
|
3835
|
+
*
|
|
3836
|
+
* Older players ignore `keyframes`: set `"minPlayerVersion": "0.5.1"` in
|
|
3837
|
+
* the manifest when you rely on it.
|
|
3732
3838
|
*/
|
|
3733
3839
|
setStepSeek(options: {
|
|
3734
3840
|
seconds: number;
|
|
3841
|
+
keyframes?: boolean;
|
|
3735
3842
|
}): Promise<void>;
|
|
3736
3843
|
/**
|
|
3737
3844
|
* The chapters of the current media (empty without media or chapters),
|
|
@@ -3778,7 +3885,7 @@ export interface Player {
|
|
|
3778
3885
|
* | `play-pause` | Space | pause / resume, replay after the end |
|
|
3779
3886
|
* | `previous` / `next` | PageUp, Mouse:X1 / PageDown, Mouse:X2 | ask the sequence provider |
|
|
3780
3887
|
* | `frame-back` / `frame-forward` | D / F | `player.step("frame", …)` |
|
|
3781
|
-
* | `seek-backward` / `seek-forward` | ← / → | relative seek by `setStepSeek` seconds |
|
|
3888
|
+
* | `seek-backward` / `seek-forward` | ← / → | relative seek by `setStepSeek` seconds (or `host.run`'s `seconds`), exact or to keyframes as `setStepSeek` says |
|
|
3782
3889
|
* | `rate-normal`, `rate-down`, `rate-up` | Z, X, C | 1×, ±0.1 within 0.25–4 |
|
|
3783
3890
|
* | `volume-up` / `volume-down` | ↑ / ↓, wheel | ±5 (`volume-up` stops at 100) |
|
|
3784
3891
|
* | `mute` | M | toggle mute |
|
|
@@ -3802,6 +3909,31 @@ export interface Host {
|
|
|
3802
3909
|
* ```
|
|
3803
3910
|
*/
|
|
3804
3911
|
run(action: HostAction): Promise<void>;
|
|
3912
|
+
/**
|
|
3913
|
+
* One step seek of `seconds` (0.1–3600 in tenths) instead of the
|
|
3914
|
+
* {@link Player.setStepSeek} step: the same checks, mode (exact or
|
|
3915
|
+
* keyframes), accumulation and on-screen text as ← / →. Use it for more
|
|
3916
|
+
* seek steps of your own (Ctrl+→ and the like) so that they add up with
|
|
3917
|
+
* the core's and land the same way.
|
|
3918
|
+
*
|
|
3919
|
+
* In keyframe mode it resolves once the seek has landed (so
|
|
3920
|
+
* `player.state.position` is current after the `await`); presses that add
|
|
3921
|
+
* up resolve together, the last with the text. In exact mode it resolves
|
|
3922
|
+
* once the seek is submitted, as before. `invalid` for `seconds` out of
|
|
3923
|
+
* range.
|
|
3924
|
+
*
|
|
3925
|
+
* @player 0.5.1 (set `"minPlayerVersion": "0.5.1"`: older players ignore
|
|
3926
|
+
* `seconds` and seek by the `setStepSeek` step)
|
|
3927
|
+
* @since 1.5.19
|
|
3928
|
+
*
|
|
3929
|
+
* @example
|
|
3930
|
+
* ```ts
|
|
3931
|
+
* bar.on("forward30", () => plugin.host.run("seek-forward", { seconds: 30 }));
|
|
3932
|
+
* ```
|
|
3933
|
+
*/
|
|
3934
|
+
run(action: "seek-backward" | "seek-forward", options: {
|
|
3935
|
+
seconds: number;
|
|
3936
|
+
}): Promise<void>;
|
|
3805
3937
|
}
|
|
3806
3938
|
/**
|
|
3807
3939
|
* This plugin's information block on the OSD: multi-line text over the
|
|
@@ -4733,8 +4865,9 @@ export interface PluginInfo {
|
|
|
4733
4865
|
*/
|
|
4734
4866
|
directory: string;
|
|
4735
4867
|
/**
|
|
4736
|
-
* Shipped with the player: it
|
|
4737
|
-
* replaced
|
|
4868
|
+
* Shipped with the player: it always runs; it cannot be disabled,
|
|
4869
|
+
* uninstalled or replaced (older players, before SDK 1.5.18, let it be disabled).
|
|
4870
|
+
* `false` from players before 0.2.0.
|
|
4738
4871
|
*/
|
|
4739
4872
|
builtin: boolean;
|
|
4740
4873
|
/** Where an installed plugin came from; `null` for development loads. */
|
|
@@ -4838,9 +4971,31 @@ export interface Packages {
|
|
|
4838
4971
|
* Stop or start a plugin (`plugins.manage`). An installed plugin stays
|
|
4839
4972
|
* disabled across launches; a development load only for this run.
|
|
4840
4973
|
* `invalid` to disable oneself, `missing` unknown.
|
|
4841
|
-
* `busy` while it is being installed.
|
|
4974
|
+
* `busy` while it is being installed. `unsupported` to disable a built-in
|
|
4975
|
+
* ({@link PluginInfo.builtin}): users manage third-party plugins only
|
|
4976
|
+
* (players released with SDK 1.5.18 on; older ones disabled built-ins too).
|
|
4842
4977
|
*/
|
|
4843
4978
|
setEnabled(id: string, enabled: boolean): Promise<void>;
|
|
4979
|
+
/**
|
|
4980
|
+
* Restart a plugin (`plugins.manage`): stop it and start it again from the
|
|
4981
|
+
* same package with the same data directory, for example to recover one
|
|
4982
|
+
* that misbehaves. Built-ins included. A stopped or failed plugin is just
|
|
4983
|
+
* started. The disabled flag is never written, so a reload never leaves
|
|
4984
|
+
* a plugin disabled.
|
|
4985
|
+
*
|
|
4986
|
+
* Resolves once the plugin is stopped and its start is scheduled; the
|
|
4987
|
+
* player starts it as soon as the old process is gone (at most 20 s).
|
|
4988
|
+
* Follow it with {@link Packages.onChange}: `starting`, then `running`,
|
|
4989
|
+
* or `failed` with the reason.
|
|
4990
|
+
*
|
|
4991
|
+
* `invalid` for oneself or a disabled plugin (enable it instead),
|
|
4992
|
+
* `missing` unknown, `busy` while it is being installed or reloaded.
|
|
4993
|
+
*
|
|
4994
|
+
* @player 0.5.1; older players reject it as an unknown request
|
|
4995
|
+
* (`unsupported`).
|
|
4996
|
+
* @since 1.5.19
|
|
4997
|
+
*/
|
|
4998
|
+
reload(id: string): Promise<void>;
|
|
4844
4999
|
/**
|
|
4845
5000
|
* Remove an installed plugin (`plugins.manage`); `removeData` also deletes
|
|
4846
5001
|
* its data directory, otherwise reinstalling it from the same source
|
package/dist/plugin/ui.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Json } from "../wire.ts";
|
|
2
2
|
import type { ScopeImpl } from "./scope.ts";
|
|
3
|
-
import type { Dialogs, Disposable, ImageRef, Panels, PropValue, Props, Ui, View, Windows } from "./types.ts";
|
|
3
|
+
import type { Dialogs, Disposable, ImageOptions, ImageRef, Panels, PropValue, Props, Ui, View, Windows } from "./types.ts";
|
|
4
4
|
/** Slint names are kebab-case; plugin code may use camelCase. */
|
|
5
5
|
export declare const kebab: (name: string) => string;
|
|
6
6
|
/**
|
|
@@ -13,11 +13,13 @@ export declare class ImageHandle implements ImageRef {
|
|
|
13
13
|
#private;
|
|
14
14
|
readonly path: string;
|
|
15
15
|
readonly __vestraImage: true;
|
|
16
|
-
constructor(scope: ScopeImpl, path: string);
|
|
16
|
+
constructor(scope: ScopeImpl, path: string, maxSide?: number);
|
|
17
17
|
/** Released with its scope: writing it is `invalid`. */
|
|
18
18
|
get released(): boolean;
|
|
19
19
|
toJSON(): Json;
|
|
20
20
|
}
|
|
21
|
+
/** `ImageOptions.maxSide`, checked: an integer 1–4096 or absent. */
|
|
22
|
+
export declare function imageSide(options: ImageOptions | undefined): number | undefined;
|
|
21
23
|
export declare const ui: (scope: ScopeImpl) => Ui;
|
|
22
24
|
/** Property and callback plumbing shared by windows, panels and settings pages. */
|
|
23
25
|
export declare abstract class ViewBase implements View {
|