@vestra-player/sdk 1.5.13 → 1.5.14
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 +12 -0
- package/README.md +12 -20
- package/dist/capabilities.d.ts +73 -1
- package/dist/capabilities.js +263 -28
- package/dist/commands.d.ts +89 -0
- package/dist/commands.js +438 -0
- package/dist/palettes.d.ts +23 -0
- package/dist/palettes.js +72 -0
- package/dist/player-runtime.d.ts +6 -2
- package/dist/player-runtime.js +6 -2
- package/dist/plugin/bindings.d.ts +45 -0
- package/dist/plugin/bindings.js +91 -42
- package/dist/plugin/osd.d.ts +32 -0
- package/dist/plugin/osd.js +36 -2
- package/dist/plugin/services.d.ts +25 -0
- package/dist/plugin/services.js +29 -4
- package/dist/plugin/testing.d.ts +24 -1
- package/dist/plugin/testing.js +36 -7
- package/dist/plugin/types.d.ts +93 -25
- package/llms-full.txt +2645 -373
- package/llms.txt +72 -136
- package/package.json +2 -1
package/dist/plugin/bindings.js
CHANGED
|
@@ -7,54 +7,103 @@
|
|
|
7
7
|
* input = key | "Mouse:" mouse
|
|
8
8
|
*/
|
|
9
9
|
import { failure } from "./runtime.js";
|
|
10
|
-
|
|
10
|
+
/**
|
|
11
|
+
* The names a binding (`CommandOptions.bindings`, `Bindings.set`) is made
|
|
12
|
+
* of, as the player's binding parser accepts them. A binding is
|
|
13
|
+
* `{ modifier "+" } input`: modifiers in the order of `modifiers`, each at
|
|
14
|
+
* most once, then one input. An input is a name of `letters`, `digits`,
|
|
15
|
+
* `functionKeys`, `named` or `mouse`, or one other printable character that
|
|
16
|
+
* is not a letter, digit, space or `+` (`">"`, `"["`, `"/"`), written as
|
|
17
|
+
* typed, never with `Shift` (`">"`, not `"Shift+."`; `"!"`, not
|
|
18
|
+
* `"Shift+1"`). Anything else fails with code `invalid`. The player's tests
|
|
19
|
+
* check that these agree with the player.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```ts
|
|
23
|
+
* import { BINDING_INPUTS } from "@vestra-player/sdk/testing";
|
|
24
|
+
*
|
|
25
|
+
* assert.ok(BINDING_INPUTS.named.includes("PageUp"));
|
|
26
|
+
* assert.ok(BINDING_INPUTS.mouse.includes("Mouse:WheelUp"));
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* @since 1.5.14
|
|
30
|
+
*/
|
|
31
|
+
export const BINDING_INPUTS = Object.freeze({
|
|
32
|
+
/**
|
|
33
|
+
* Modifiers, in the order a binding writes them. `Cmd` is ⌘ on macOS and
|
|
34
|
+
* the Windows logo key on Windows; on macOS `Ctrl` also matches ⌘.
|
|
35
|
+
*/
|
|
36
|
+
modifiers: ["Ctrl", "Alt", "Shift", "Cmd"],
|
|
37
|
+
/** Letter keys, by key name whatever the case: `A` to `Z`. */
|
|
38
|
+
letters: [..."ABCDEFGHIJKLMNOPQRSTUVWXYZ"],
|
|
39
|
+
/** Digit keys of the main keyboard: `0` to `9`. */
|
|
40
|
+
digits: [..."0123456789"],
|
|
41
|
+
/** Function keys: `F1` to `F24`. */
|
|
42
|
+
functionKeys: Array.from({ length: 24 }, (_, index) => `F${index + 1}`),
|
|
43
|
+
/**
|
|
44
|
+
* Other named keys, matched by the physical key. `Plus` is the `+` key
|
|
45
|
+
* however it is typed; the `Numpad…` names are the numeric keypad only,
|
|
46
|
+
* never the main keyboard.
|
|
47
|
+
*/
|
|
48
|
+
named: [
|
|
49
|
+
"Space",
|
|
50
|
+
"Enter",
|
|
51
|
+
"Tab",
|
|
52
|
+
"Escape",
|
|
53
|
+
"Backspace",
|
|
54
|
+
"Delete",
|
|
55
|
+
"Insert",
|
|
56
|
+
"Home",
|
|
57
|
+
"End",
|
|
58
|
+
"PageUp",
|
|
59
|
+
"PageDown",
|
|
60
|
+
"ArrowLeft",
|
|
61
|
+
"ArrowRight",
|
|
62
|
+
"ArrowUp",
|
|
63
|
+
"ArrowDown",
|
|
64
|
+
"Plus",
|
|
65
|
+
...Array.from({ length: 10 }, (_, digit) => `Numpad${digit}`),
|
|
66
|
+
"NumpadPlus",
|
|
67
|
+
"NumpadMinus",
|
|
68
|
+
"NumpadMultiply",
|
|
69
|
+
"NumpadDivide",
|
|
70
|
+
"NumpadEnter",
|
|
71
|
+
"NumpadDecimal",
|
|
72
|
+
],
|
|
73
|
+
/**
|
|
74
|
+
* Mouse inputs on the video area. `:LeftHalf` and `:RightHalf` tell the
|
|
75
|
+
* halves of the video apart for a double click.
|
|
76
|
+
*/
|
|
77
|
+
mouse: [
|
|
78
|
+
"Mouse:LeftClick",
|
|
79
|
+
"Mouse:LeftDoubleClick",
|
|
80
|
+
"Mouse:LeftDoubleClick:LeftHalf",
|
|
81
|
+
"Mouse:LeftDoubleClick:RightHalf",
|
|
82
|
+
"Mouse:MiddleClick",
|
|
83
|
+
"Mouse:X1",
|
|
84
|
+
"Mouse:X2",
|
|
85
|
+
"Mouse:WheelUp",
|
|
86
|
+
"Mouse:WheelDown",
|
|
87
|
+
"Mouse:WheelLeft",
|
|
88
|
+
"Mouse:WheelRight",
|
|
89
|
+
],
|
|
90
|
+
});
|
|
91
|
+
const MODIFIERS = BINDING_INPUTS.modifiers;
|
|
11
92
|
const NAMED = new Set([
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
"Tab",
|
|
15
|
-
"Escape",
|
|
16
|
-
"Backspace",
|
|
17
|
-
"Delete",
|
|
18
|
-
"Insert",
|
|
19
|
-
"Home",
|
|
20
|
-
"End",
|
|
21
|
-
"PageUp",
|
|
22
|
-
"PageDown",
|
|
23
|
-
"ArrowLeft",
|
|
24
|
-
"ArrowRight",
|
|
25
|
-
"ArrowUp",
|
|
26
|
-
"ArrowDown",
|
|
27
|
-
"Plus",
|
|
28
|
-
// Numeric keypad: never matched by the main keyboard keys, nor they by these.
|
|
29
|
-
..."0123456789".split("").map((digit) => `Numpad${digit}`),
|
|
30
|
-
"NumpadPlus",
|
|
31
|
-
"NumpadMinus",
|
|
32
|
-
"NumpadMultiply",
|
|
33
|
-
"NumpadDivide",
|
|
34
|
-
"NumpadEnter",
|
|
35
|
-
"NumpadDecimal",
|
|
36
|
-
]);
|
|
37
|
-
const MOUSE = new Set([
|
|
38
|
-
"LeftClick",
|
|
39
|
-
"LeftDoubleClick",
|
|
40
|
-
"LeftDoubleClick:LeftHalf",
|
|
41
|
-
"LeftDoubleClick:RightHalf",
|
|
42
|
-
"MiddleClick",
|
|
43
|
-
"X1",
|
|
44
|
-
"X2",
|
|
45
|
-
"WheelUp",
|
|
46
|
-
"WheelDown",
|
|
47
|
-
"WheelLeft",
|
|
48
|
-
"WheelRight",
|
|
93
|
+
...BINDING_INPUTS.functionKeys,
|
|
94
|
+
...BINDING_INPUTS.named,
|
|
49
95
|
]);
|
|
96
|
+
const MOUSE = new Set(BINDING_INPUTS.mouse);
|
|
97
|
+
const LETTERS = new Set(BINDING_INPUTS.letters);
|
|
98
|
+
const DIGITS = new Set(BINDING_INPUTS.digits);
|
|
50
99
|
function kind(input) {
|
|
51
100
|
if (input.startsWith("Mouse:"))
|
|
52
|
-
return MOUSE.has(input
|
|
53
|
-
if (
|
|
101
|
+
return MOUSE.has(input) ? "mouse" : null;
|
|
102
|
+
if (LETTERS.has(input))
|
|
54
103
|
return "letter";
|
|
55
|
-
if (
|
|
104
|
+
if (DIGITS.has(input))
|
|
56
105
|
return "digit";
|
|
57
|
-
if (
|
|
106
|
+
if (NAMED.has(input))
|
|
58
107
|
return "named";
|
|
59
108
|
// One printable, non-alphanumeric, non-space character; "+" is written Plus.
|
|
60
109
|
if ([...input].length === 1 && /^[^\p{L}\p{N}\s+]$/u.test(input))
|
package/dist/plugin/osd.d.ts
CHANGED
|
@@ -1,6 +1,38 @@
|
|
|
1
1
|
import type { ScopeImpl } from "./scope.ts";
|
|
2
2
|
import type { Osd } from "./types.ts";
|
|
3
3
|
import type { TemplateArgsJson, TextValueJson } from "../wire.ts";
|
|
4
|
+
/**
|
|
5
|
+
* The player's limits on the on-screen display ({@link Osd}), counted on
|
|
6
|
+
* the text as shown: after the player translated it and filled in its
|
|
7
|
+
* arguments. A text over them fails with code `invalid`: `osd.show`
|
|
8
|
+
* rejects, `OsdPanel.set` reports to `Scope.onError`. The player's tests
|
|
9
|
+
* check that these agree with the player.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* import { OSD_LIMITS } from "@vestra-player/sdk/testing";
|
|
14
|
+
*
|
|
15
|
+
* const lines = rows.slice(0, OSD_LIMITS.panelLines);
|
|
16
|
+
* panel.set(lines.join("\n"), null, { maxColumns: 60 });
|
|
17
|
+
* ```
|
|
18
|
+
*
|
|
19
|
+
* @since 1.5.14
|
|
20
|
+
*/
|
|
21
|
+
export declare const OSD_LIMITS: Readonly<{
|
|
22
|
+
/**
|
|
23
|
+
* Characters of one `osd.show` message (200; at least 1 that is not
|
|
24
|
+
* white space). Line breaks are shown as spaces.
|
|
25
|
+
*/
|
|
26
|
+
readonly messageChars: 200;
|
|
27
|
+
/** Lines of the information block (`OsdPanel.set`) (32). */
|
|
28
|
+
readonly panelLines: 32;
|
|
29
|
+
/** UTF-8 bytes of the information block (4096). */
|
|
30
|
+
readonly panelBytes: 4096;
|
|
31
|
+
/** The smallest `OsdPanelOptions.maxColumns` (8). */
|
|
32
|
+
readonly minColumns: 8;
|
|
33
|
+
/** The largest `OsdPanelOptions.maxColumns` (1000). */
|
|
34
|
+
readonly maxColumns: 1000;
|
|
35
|
+
}>;
|
|
4
36
|
/**
|
|
5
37
|
* Copy and check template arguments: an array or object of at most 16
|
|
6
38
|
* strings, finite numbers, texts and lists (`TEXT_LIMITS`).
|
package/dist/plugin/osd.js
CHANGED
|
@@ -2,6 +2,38 @@
|
|
|
2
2
|
import { Latest } from "./runtime.js";
|
|
3
3
|
import { failure } from "./runtime.js";
|
|
4
4
|
import { isTextValue, textProblem } from "../i18n.js";
|
|
5
|
+
/**
|
|
6
|
+
* The player's limits on the on-screen display ({@link Osd}), counted on
|
|
7
|
+
* the text as shown: after the player translated it and filled in its
|
|
8
|
+
* arguments. A text over them fails with code `invalid`: `osd.show`
|
|
9
|
+
* rejects, `OsdPanel.set` reports to `Scope.onError`. The player's tests
|
|
10
|
+
* check that these agree with the player.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* import { OSD_LIMITS } from "@vestra-player/sdk/testing";
|
|
15
|
+
*
|
|
16
|
+
* const lines = rows.slice(0, OSD_LIMITS.panelLines);
|
|
17
|
+
* panel.set(lines.join("\n"), null, { maxColumns: 60 });
|
|
18
|
+
* ```
|
|
19
|
+
*
|
|
20
|
+
* @since 1.5.14
|
|
21
|
+
*/
|
|
22
|
+
export const OSD_LIMITS = Object.freeze({
|
|
23
|
+
/**
|
|
24
|
+
* Characters of one `osd.show` message (200; at least 1 that is not
|
|
25
|
+
* white space). Line breaks are shown as spaces.
|
|
26
|
+
*/
|
|
27
|
+
messageChars: 200,
|
|
28
|
+
/** Lines of the information block (`OsdPanel.set`) (32). */
|
|
29
|
+
panelLines: 32,
|
|
30
|
+
/** UTF-8 bytes of the information block (4096). */
|
|
31
|
+
panelBytes: 4096,
|
|
32
|
+
/** The smallest `OsdPanelOptions.maxColumns` (8). */
|
|
33
|
+
minColumns: 8,
|
|
34
|
+
/** The largest `OsdPanelOptions.maxColumns` (1000). */
|
|
35
|
+
maxColumns: 1000,
|
|
36
|
+
});
|
|
5
37
|
/**
|
|
6
38
|
* Copy and check template arguments: an array or object of at most 16
|
|
7
39
|
* strings, finite numbers, texts and lists (`TEXT_LIMITS`).
|
|
@@ -54,8 +86,10 @@ class PanelHandle {
|
|
|
54
86
|
try {
|
|
55
87
|
const max = options?.maxColumns;
|
|
56
88
|
if (max !== undefined &&
|
|
57
|
-
!(Number.isInteger(max) &&
|
|
58
|
-
|
|
89
|
+
!(Number.isInteger(max) &&
|
|
90
|
+
max >= OSD_LIMITS.minColumns &&
|
|
91
|
+
max <= OSD_LIMITS.maxColumns))
|
|
92
|
+
throw failure("invalid", `maxColumns is an integer from ${OSD_LIMITS.minColumns} to ${OSD_LIMITS.maxColumns}`);
|
|
59
93
|
value = {
|
|
60
94
|
text: text === null ? null : shownText(text, args),
|
|
61
95
|
...(text === null || args === undefined || args === null
|
|
@@ -6,4 +6,29 @@ export declare const power: (scope: ScopeImpl) => Power;
|
|
|
6
6
|
export declare function tools(scope: ScopeImpl): Tools;
|
|
7
7
|
export declare const storage: (scope: ScopeImpl) => Storage;
|
|
8
8
|
export declare function system(scope: ScopeImpl): System;
|
|
9
|
+
/**
|
|
10
|
+
* The player's limits on {@link Secrets}. A name or value outside them
|
|
11
|
+
* fails with code `invalid`; a new name beyond `names` fails with `limit`.
|
|
12
|
+
* The player's tests check that these agree with the player.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* import { SECRET_LIMITS } from "@vestra-player/sdk/testing";
|
|
17
|
+
*
|
|
18
|
+
* assert.ok(Buffer.byteLength(token) <= SECRET_LIMITS.valueBytes);
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* @since 1.5.14
|
|
22
|
+
*/
|
|
23
|
+
export declare const SECRET_LIMITS: Readonly<{
|
|
24
|
+
/**
|
|
25
|
+
* Characters of a name (64, at least 1), each one of `a-z`, `0-9`, `.`,
|
|
26
|
+
* `_` and `-`.
|
|
27
|
+
*/
|
|
28
|
+
readonly nameChars: 64;
|
|
29
|
+
/** UTF-8 bytes of a value (1024, at least 1; no NUL character). */
|
|
30
|
+
readonly valueBytes: 1024;
|
|
31
|
+
/** Different names one plugin may have stored at the same time (256). */
|
|
32
|
+
readonly names: 256;
|
|
33
|
+
}>;
|
|
9
34
|
export declare const secrets: (scope: ScopeImpl) => Secrets;
|
package/dist/plugin/services.js
CHANGED
|
@@ -292,13 +292,38 @@ export function system(scope) {
|
|
|
292
292
|
},
|
|
293
293
|
};
|
|
294
294
|
}
|
|
295
|
-
|
|
295
|
+
/**
|
|
296
|
+
* The player's limits on {@link Secrets}. A name or value outside them
|
|
297
|
+
* fails with code `invalid`; a new name beyond `names` fails with `limit`.
|
|
298
|
+
* The player's tests check that these agree with the player.
|
|
299
|
+
*
|
|
300
|
+
* @example
|
|
301
|
+
* ```ts
|
|
302
|
+
* import { SECRET_LIMITS } from "@vestra-player/sdk/testing";
|
|
303
|
+
*
|
|
304
|
+
* assert.ok(Buffer.byteLength(token) <= SECRET_LIMITS.valueBytes);
|
|
305
|
+
* ```
|
|
306
|
+
*
|
|
307
|
+
* @since 1.5.14
|
|
308
|
+
*/
|
|
309
|
+
export const SECRET_LIMITS = Object.freeze({
|
|
310
|
+
/**
|
|
311
|
+
* Characters of a name (64, at least 1), each one of `a-z`, `0-9`, `.`,
|
|
312
|
+
* `_` and `-`.
|
|
313
|
+
*/
|
|
314
|
+
nameChars: 64,
|
|
315
|
+
/** UTF-8 bytes of a value (1024, at least 1; no NUL character). */
|
|
316
|
+
valueBytes: 1024,
|
|
317
|
+
/** Different names one plugin may have stored at the same time (256). */
|
|
318
|
+
names: 256,
|
|
319
|
+
});
|
|
320
|
+
const SECRET = new RegExp(`^[a-z0-9._-]{1,${SECRET_LIMITS.nameChars}}$`);
|
|
296
321
|
export const secrets = (scope) => {
|
|
297
322
|
const named = (name) => {
|
|
298
323
|
if (!scope.runtime.can("secrets"))
|
|
299
324
|
throw failure("capability", "secrets must be declared in manifest");
|
|
300
325
|
if (typeof name !== "string" || !SECRET.test(name))
|
|
301
|
-
throw failure("invalid",
|
|
326
|
+
throw failure("invalid", `secret names are 1–${SECRET_LIMITS.nameChars} of a-z, 0-9, ., _ and -`);
|
|
302
327
|
return name;
|
|
303
328
|
};
|
|
304
329
|
return {
|
|
@@ -307,8 +332,8 @@ export const secrets = (scope) => {
|
|
|
307
332
|
if (typeof value !== "string" ||
|
|
308
333
|
!value ||
|
|
309
334
|
value.includes("\0") ||
|
|
310
|
-
Buffer.byteLength(value) >
|
|
311
|
-
throw failure("invalid",
|
|
335
|
+
Buffer.byteLength(value) > SECRET_LIMITS.valueBytes)
|
|
336
|
+
throw failure("invalid", `secret values are 1–${SECRET_LIMITS.valueBytes} bytes of text; delete() removes one`);
|
|
312
337
|
await scope.runtime.host.secrets.set(named(name), value);
|
|
313
338
|
},
|
|
314
339
|
delete: async (name) => scope.runtime.host.secrets.delete(named(name)),
|
package/dist/plugin/testing.d.ts
CHANGED
|
@@ -2,7 +2,14 @@ import type { Server } from "node:net";
|
|
|
2
2
|
import { HostError } from "../wire.ts";
|
|
3
3
|
import type { PackageManifest, PackageSource } from "./types.ts";
|
|
4
4
|
import type { Json, LayoutSlot, LocalServices, PlaybackStats, Transport, WireSystemInfo } from "../wire.ts";
|
|
5
|
-
import { COMMAND_LIMITS, MENU_IDS } from "../commands.ts";
|
|
5
|
+
import { BUILTIN_COMMANDS, BUILTIN_SUBMENUS, COMMAND_LIMITS, MENU_IDS } from "../commands.ts";
|
|
6
|
+
import type { BuiltinCommand, BuiltinSubmenu } from "../commands.ts";
|
|
7
|
+
import { CAPABILITY_INFO, MESSAGE_LIMIT } from "../capabilities.ts";
|
|
8
|
+
import type { CapabilityInfo } from "../capabilities.ts";
|
|
9
|
+
import { OSD_LIMITS } from "./osd.ts";
|
|
10
|
+
import { SECRET_LIMITS } from "./services.ts";
|
|
11
|
+
import { BINDING_INPUTS } from "./bindings.ts";
|
|
12
|
+
import { THEME_PALETTES } from "../palettes.ts";
|
|
6
13
|
import { UI_VALUE_LIMITS } from "./values.ts";
|
|
7
14
|
import type { CommandDeclaration } from "../commands.ts";
|
|
8
15
|
import type { SourceState } from "../source.ts";
|
|
@@ -15,6 +22,22 @@ import { LOCALES, LIST_SEPARATORS, TEXT_LIMITS } from "../i18n.ts";
|
|
|
15
22
|
export { HostError };
|
|
16
23
|
/** The player's command caps, which the test host enforces as well. */
|
|
17
24
|
export { COMMAND_LIMITS, MENU_IDS };
|
|
25
|
+
/** The submenus of the player's built-in plugins, by key path. */
|
|
26
|
+
export { BUILTIN_COMMANDS, BUILTIN_SUBMENUS };
|
|
27
|
+
export type { BuiltinCommand, BuiltinSubmenu };
|
|
28
|
+
/** The manifest capabilities: what each opens and how the store shows it. */
|
|
29
|
+
export { CAPABILITY_INFO };
|
|
30
|
+
export type { CapabilityInfo };
|
|
31
|
+
/** The longest message a plugin may send the player. */
|
|
32
|
+
export { MESSAGE_LIMIT };
|
|
33
|
+
/** The player's limits on the on-screen display. */
|
|
34
|
+
export { OSD_LIMITS };
|
|
35
|
+
/** The player's limits on stored secrets. */
|
|
36
|
+
export { SECRET_LIMITS };
|
|
37
|
+
/** The names bindings are made of. */
|
|
38
|
+
export { BINDING_INPUTS };
|
|
39
|
+
/** The colours of the player's palettes, by `Theme` token. */
|
|
40
|
+
export { THEME_PALETTES };
|
|
18
41
|
/** The player's property value limits, which the test host enforces as well. */
|
|
19
42
|
export { UI_VALUE_LIMITS };
|
|
20
43
|
/**
|
package/dist/plugin/testing.js
CHANGED
|
@@ -65,7 +65,12 @@ 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 { COMMAND_LIMITS, MENU_IDS } from "../commands.js";
|
|
68
|
+
import { BUILTIN_COMMANDS, BUILTIN_SUBMENUS, COMMAND_LIMITS, MENU_IDS, } from "../commands.js";
|
|
69
|
+
import { CAPABILITY_INFO, MESSAGE_LIMIT } from "../capabilities.js";
|
|
70
|
+
import { OSD_LIMITS } from "./osd.js";
|
|
71
|
+
import { SECRET_LIMITS } from "./services.js";
|
|
72
|
+
import { BINDING_INPUTS } from "./bindings.js";
|
|
73
|
+
import { THEME_PALETTES } from "../palettes.js";
|
|
69
74
|
import { UI_VALUE_LIMITS, valueLimitProblem } from "./values.js";
|
|
70
75
|
import { nodeProblem } from "../processing.js";
|
|
71
76
|
import { RENDER_DEFAULTS, renderProblem, VIEW_DEFAULTS, viewProblem, } from "./view.js";
|
|
@@ -76,6 +81,20 @@ import { Catalog, format as formatTemplate, HOST_GROUPS, HOST_ZH_HANS, LOCALES,
|
|
|
76
81
|
export { HostError };
|
|
77
82
|
/** The player's command caps, which the test host enforces as well. */
|
|
78
83
|
export { COMMAND_LIMITS, MENU_IDS };
|
|
84
|
+
/** The submenus of the player's built-in plugins, by key path. */
|
|
85
|
+
export { BUILTIN_COMMANDS, BUILTIN_SUBMENUS };
|
|
86
|
+
/** The manifest capabilities: what each opens and how the store shows it. */
|
|
87
|
+
export { CAPABILITY_INFO };
|
|
88
|
+
/** The longest message a plugin may send the player. */
|
|
89
|
+
export { MESSAGE_LIMIT };
|
|
90
|
+
/** The player's limits on the on-screen display. */
|
|
91
|
+
export { OSD_LIMITS };
|
|
92
|
+
/** The player's limits on stored secrets. */
|
|
93
|
+
export { SECRET_LIMITS };
|
|
94
|
+
/** The names bindings are made of. */
|
|
95
|
+
export { BINDING_INPUTS };
|
|
96
|
+
/** The colours of the player's palettes, by `Theme` token. */
|
|
97
|
+
export { THEME_PALETTES };
|
|
79
98
|
/** The player's property value limits, which the test host enforces as well. */
|
|
80
99
|
export { UI_VALUE_LIMITS };
|
|
81
100
|
/**
|
|
@@ -2545,8 +2564,8 @@ export class TestHost {
|
|
|
2545
2564
|
},
|
|
2546
2565
|
"osd.show": (p) => {
|
|
2547
2566
|
const text = this.#shown(p.text, p.args, "text").replace(/[\n\r]/g, " ");
|
|
2548
|
-
if (!text.trim() || [...text].length >
|
|
2549
|
-
throw new HostError("invalid",
|
|
2567
|
+
if (!text.trim() || [...text].length > OSD_LIMITS.messageChars)
|
|
2568
|
+
throw new HostError("invalid", `osd text needs 1..${OSD_LIMITS.messageChars} characters`);
|
|
2550
2569
|
this.osd.push(text);
|
|
2551
2570
|
},
|
|
2552
2571
|
"osd.panel": (p) => {
|
|
@@ -2554,10 +2573,20 @@ export class TestHost {
|
|
|
2554
2573
|
return void (this.osdPanel = null);
|
|
2555
2574
|
const max = p.maxColumns;
|
|
2556
2575
|
if (max !== undefined &&
|
|
2557
|
-
!(Number.isInteger(max) &&
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2576
|
+
!(Number.isInteger(max) &&
|
|
2577
|
+
Number(max) >= OSD_LIMITS.minColumns &&
|
|
2578
|
+
Number(max) <= OSD_LIMITS.maxColumns))
|
|
2579
|
+
throw new HostError("invalid", `maxColumns is an integer from ${OSD_LIMITS.minColumns} to ${OSD_LIMITS.maxColumns}`);
|
|
2580
|
+
const shown = this.#shown(p.text, p.args, "text");
|
|
2581
|
+
const text = max === undefined ? shown : fitColumns(shown, Number(max));
|
|
2582
|
+
// As the player: \n separates lines, a trailing one ends the last.
|
|
2583
|
+
const lines = text.split("\n");
|
|
2584
|
+
if (text.endsWith("\n"))
|
|
2585
|
+
lines.pop();
|
|
2586
|
+
if (Buffer.byteLength(text) > OSD_LIMITS.panelBytes ||
|
|
2587
|
+
lines.length > OSD_LIMITS.panelLines)
|
|
2588
|
+
throw new HostError("invalid", `osd panel allows ${OSD_LIMITS.panelLines} lines, ${OSD_LIMITS.panelBytes} bytes`);
|
|
2589
|
+
this.osdPanel = text;
|
|
2561
2590
|
},
|
|
2562
2591
|
"playback.stats": () => this.stats,
|
|
2563
2592
|
"audio.addTrack": (p) => {
|
package/dist/plugin/types.d.ts
CHANGED
|
@@ -320,16 +320,19 @@ export interface Plugin extends Scope {
|
|
|
320
320
|
/** The manifest id, e.g. `"community.bookmark"`. */
|
|
321
321
|
readonly id: string;
|
|
322
322
|
/**
|
|
323
|
-
* A writable directory for this plugin's own files (settings,
|
|
324
|
-
* folders)
|
|
325
|
-
*
|
|
326
|
-
*
|
|
327
|
-
*
|
|
328
|
-
*
|
|
329
|
-
*
|
|
330
|
-
*
|
|
331
|
-
*
|
|
332
|
-
*
|
|
323
|
+
* A writable directory for this plugin's own files (settings, caches,
|
|
324
|
+
* remembered folders), created by the player before it starts the plugin.
|
|
325
|
+
* Each installation has its own directory (player 0.2.0): it is kept across
|
|
326
|
+
* restarts, disabling, upgrades and rollbacks, and a reinstall of the same
|
|
327
|
+
* id from a different source gets a new, empty one. It is deleted only by
|
|
328
|
+
* `packages.uninstall(id, { removeData: true })`. No capability.
|
|
329
|
+
*
|
|
330
|
+
* The path's format is not part of the API: do not derive another
|
|
331
|
+
* plugin's directory from it (a plugin manager reads
|
|
332
|
+
* {@link PluginInfo.dataDirectory}). Write files atomically (to a
|
|
333
|
+
* temporary name, then rename); for JSON settings prefer
|
|
334
|
+
* {@link Scope.storage}. Throws `unavailable` when the player provided
|
|
335
|
+
* none. See [How a plugin runs](../../docs/concepts/plugins.md#the-data-directory).
|
|
333
336
|
*/
|
|
334
337
|
readonly dataDirectory: string;
|
|
335
338
|
}
|
|
@@ -388,9 +391,10 @@ export type MenuId = "open" | "playback" | "subtitles" | "video" | "audio" | "sc
|
|
|
388
391
|
* item never moves the player's menu. Directly inside a built-in menu,
|
|
389
392
|
* `order` 0 (the default) sorts after every other order: an item without
|
|
390
393
|
* `order` goes at the end, after the player's own items (which all use 100
|
|
391
|
-
* or more). Give a positive order to sit between them (the player's
|
|
392
|
-
*
|
|
393
|
-
*
|
|
394
|
+
* or more). Give a positive order to sit between them (the player's items
|
|
395
|
+
* in menu order are in
|
|
396
|
+
* [Built-in commands](../../docs/reference/commands.md#built-in-commands)),
|
|
397
|
+
* or a negative one to come first. Deeper levels (a plugin's own submenu) have no such rule.
|
|
394
398
|
* Players before 0.3.0 place every submenu at its smallest order, so there
|
|
395
399
|
* an item without `order` moves the whole menu to the top; to avoid that,
|
|
396
400
|
* give it an order in the menu's range (Video: 400–499).
|
|
@@ -471,8 +475,8 @@ export interface CommandOptions {
|
|
|
471
475
|
*
|
|
472
476
|
* Platforms: on macOS `Ctrl+X` fires for both ⌃X and ⌘X while `Cmd+X`
|
|
473
477
|
* means ⌘X only; on Windows `Cmd` is the Windows logo key, so write
|
|
474
|
-
* `Ctrl+…` for bindings that work on both. Avoid
|
|
475
|
-
*
|
|
478
|
+
* `Ctrl+…` for bindings that work on both. Avoid the
|
|
479
|
+
* [keys the system takes first](../../docs/reference/commands.md#keys-the-system-takes-first).
|
|
476
480
|
*
|
|
477
481
|
* Conflicts are not errors: the user's overrides win, then the player's
|
|
478
482
|
* own commands, then the plugin loaded first; a shadowed binding takes
|
|
@@ -812,6 +816,15 @@ export interface Ui {
|
|
|
812
816
|
* The text is at most 256 KB; very long documents are cut off after 4000
|
|
813
817
|
* blocks.
|
|
814
818
|
*
|
|
819
|
+
* `MarkdownView` scrolls one document. `MarkdownBlocks` (SDK 1.5.4, players
|
|
820
|
+
* after 0.4.0) draws one without scrolling, as tall as its content, for a
|
|
821
|
+
* layout of your own or a `ListView` row. For a list of documents, write
|
|
822
|
+
* the whole list with one document per row: the player parses only the
|
|
823
|
+
* documents that changed and updates the list in place (rows added or
|
|
824
|
+
* removed at the end, a growing document's changed blocks), so re-sending
|
|
825
|
+
* the list on every streamed chunk keeps the scroll position and costs
|
|
826
|
+
* little.
|
|
827
|
+
*
|
|
815
828
|
* @capability None.
|
|
816
829
|
* @player 0.2.0 (set `"minPlayerVersion": "0.2.0"` in the manifest)
|
|
817
830
|
*
|
|
@@ -826,6 +839,44 @@ export interface Ui {
|
|
|
826
839
|
* `base` is neither an `http(s):` address nor an absolute path.
|
|
827
840
|
* @throws `unsupported` (rejects) The player is older than 0.2.0.
|
|
828
841
|
*
|
|
842
|
+
* @example A chat: one Markdown document per row, with Copy buttons
|
|
843
|
+
* `ui/chat.slint`:
|
|
844
|
+
*
|
|
845
|
+
* ```slint
|
|
846
|
+
* import { ListView } from "std-widgets.slint";
|
|
847
|
+
* import { MarkdownBlocks, MarkdownBlock } from "@vestra/markdown.slint";
|
|
848
|
+
* export struct Message { id: string, role: string, body: [MarkdownBlock] }
|
|
849
|
+
* export component Chat inherits Window {
|
|
850
|
+
* in property <[Message]> messages;
|
|
851
|
+
* callback copy-code(string);
|
|
852
|
+
* ListView {
|
|
853
|
+
* for message in root.messages: VerticalLayout {
|
|
854
|
+
* padding: 8px;
|
|
855
|
+
* MarkdownBlocks {
|
|
856
|
+
* blocks: message.body;
|
|
857
|
+
* code-copy: true;
|
|
858
|
+
* copy-code(code) => { root.copy-code(code); }
|
|
859
|
+
* }
|
|
860
|
+
* }
|
|
861
|
+
* }
|
|
862
|
+
* }
|
|
863
|
+
* ```
|
|
864
|
+
*
|
|
865
|
+
* ```ts
|
|
866
|
+
* const chat = plugin.windows.create("ui/chat.slint");
|
|
867
|
+
* const history: { id: string; role: string; text: string }[] = [];
|
|
868
|
+
* chat.on("copyCode", (code: string) => plugin.clipboard.writeText(code));
|
|
869
|
+
* async function render() {
|
|
870
|
+
* const messages = [];
|
|
871
|
+
* for (const { id, role, text } of history)
|
|
872
|
+
* messages.push({ id, role, body: await plugin.ui.markdown(text) });
|
|
873
|
+
* chat.set({ messages });
|
|
874
|
+
* }
|
|
875
|
+
* history.push({ id: "1", role: "user", text: "Show me `ls`." });
|
|
876
|
+
* await render();
|
|
877
|
+
* await chat.show();
|
|
878
|
+
* ```
|
|
879
|
+
*
|
|
829
880
|
* @example Show a README from the web
|
|
830
881
|
* ```ts
|
|
831
882
|
* const readmeUrl = "https://example.com/plugin/README.md";
|
|
@@ -1029,8 +1080,8 @@ export interface View {
|
|
|
1029
1080
|
* {@link View.bind} or {@link View.set}.
|
|
1030
1081
|
* 3. {@link Window.show} opens the window with those properties.
|
|
1031
1082
|
* {@link Window.hide} hides it and keeps its state.
|
|
1032
|
-
* 4. {@link Window.close}, or the user (title-bar button
|
|
1033
|
-
*
|
|
1083
|
+
* 4. {@link Window.close}, or the user (title-bar button or the
|
|
1084
|
+
* [standard close keys](../../docs/reference/commands.md#keys-the-system-takes-first)), destroys the window instance; only the
|
|
1034
1085
|
* user's close calls {@link Window.onClose}. The handle stays usable: the
|
|
1035
1086
|
* next `show()` opens a fresh instance with the current properties.
|
|
1036
1087
|
* 5. `dispose()`, disposing the owning scope or the plugin stopping closes
|
|
@@ -1184,8 +1235,8 @@ export interface Window extends View, Disposable {
|
|
|
1184
1235
|
* Run code when the user closes the window.
|
|
1185
1236
|
*
|
|
1186
1237
|
* @remarks
|
|
1187
|
-
* Called for the title-bar close button and the
|
|
1188
|
-
*
|
|
1238
|
+
* Called for the title-bar close button and the
|
|
1239
|
+
* [standard close keys](../../docs/reference/commands.md#keys-the-system-takes-first). When it runs, the window
|
|
1189
1240
|
* instance is gone and {@link Window.opened} is already `false`; a later
|
|
1190
1241
|
* `show()` opens a fresh one. It is not called for the plugin's own
|
|
1191
1242
|
* {@link Window.close} or `dispose()`, nor when the plugin stops. Errors
|
|
@@ -1514,8 +1565,8 @@ export interface PanelSlot {
|
|
|
1514
1565
|
* Detached, the sidebar panel is shown in a separate window (300×408 by
|
|
1515
1566
|
* default, at least 300×250) with the panel's `title`. The plugin's
|
|
1516
1567
|
* properties and callbacks stay the same, but the window holds a new
|
|
1517
|
-
* instance of the component. Closing the floating window (its close button
|
|
1518
|
-
*
|
|
1568
|
+
* instance of the component. Closing the floating window (its close button
|
|
1569
|
+
* or the [standard close keys](../../docs/reference/commands.md#keys-the-system-takes-first)) is the same as `setVisible(false)`.
|
|
1519
1570
|
*
|
|
1520
1571
|
* @capability ui.mount
|
|
1521
1572
|
*
|
|
@@ -3714,8 +3765,8 @@ export interface Host {
|
|
|
3714
3765
|
* {@link Osd.panel}.
|
|
3715
3766
|
*
|
|
3716
3767
|
* @remarks
|
|
3717
|
-
* The block sits under the OSD message line, on a translucent
|
|
3718
|
-
*
|
|
3768
|
+
* The block sits under the OSD message line, on a translucent dark plate
|
|
3769
|
+
* (see {@link Osd} for sizes), and takes no input. When
|
|
3719
3770
|
* several plugins have one, they are stacked from top to bottom. `dispose()`,
|
|
3720
3771
|
* disposing the scope or stopping the plugin removes it.
|
|
3721
3772
|
*
|
|
@@ -3789,8 +3840,25 @@ export interface OsdPanelOptions {
|
|
|
3789
3840
|
* ({@link Osd.show}) is one line at the top left of the video that
|
|
3790
3841
|
* disappears after 2 seconds; a newer message, from any plugin, replaces it.
|
|
3791
3842
|
* The information block ({@link Osd.panel}) stays until the plugin changes
|
|
3792
|
-
* or removes it.
|
|
3793
|
-
*
|
|
3843
|
+
* or removes it.
|
|
3844
|
+
*
|
|
3845
|
+
* How the player draws them (a player before 0.4.0 drew bold white text
|
|
3846
|
+
* with a black outline instead):
|
|
3847
|
+
*
|
|
3848
|
+
* - **Message**: white text on a translucent dark plate with rounded
|
|
3849
|
+
* corners and a gradient line along its bottom edge, 10px from the
|
|
3850
|
+
* video area's left and top edges.
|
|
3851
|
+
* - **Font size** follows the video area: 3 % of its height, rounded and
|
|
3852
|
+
* clamped to 12–48px (about 18px in the default window, 32px in
|
|
3853
|
+
* fullscreen at 1080 pixels high). The plate's padding and corners scale
|
|
3854
|
+
* with it.
|
|
3855
|
+
* - **Information block**: 4px below the message plate, on the same kind of
|
|
3856
|
+
* plate; its font scales in proportion (11px when the message is 16px),
|
|
3857
|
+
* clamped to 9–28px.
|
|
3858
|
+
*
|
|
3859
|
+
* How long a text may be is in
|
|
3860
|
+
* [Limits: on-screen display](../../docs/reference/limits.md#on-screen-display)
|
|
3861
|
+
* ({@link "@vestra-player/sdk/testing"!OSD_LIMITS | OSD_LIMITS}).
|
|
3794
3862
|
*
|
|
3795
3863
|
* @capability ui.osd
|
|
3796
3864
|
*
|