@vestra-player/sdk 1.5.8 → 1.5.11

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.
Files changed (43) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/README.md +4 -1
  3. package/bin/vestra-plugin.mjs +170 -7
  4. package/dist/commands.d.ts +7 -4
  5. package/dist/i18n-tools.d.ts +67 -0
  6. package/dist/i18n-tools.js +787 -0
  7. package/dist/i18n.d.ts +222 -0
  8. package/dist/i18n.js +972 -0
  9. package/dist/player-runtime.d.ts +2 -0
  10. package/dist/player-runtime.js +2 -0
  11. package/dist/plugin/appearance.js +27 -1
  12. package/dist/plugin/bindings.js +1 -0
  13. package/dist/plugin/commands.js +24 -4
  14. package/dist/plugin/index.js +9 -1
  15. package/dist/plugin/language.d.ts +8 -0
  16. package/dist/plugin/language.js +79 -0
  17. package/dist/plugin/osd.d.ts +11 -0
  18. package/dist/plugin/osd.js +64 -9
  19. package/dist/plugin/packages.js +10 -0
  20. package/dist/plugin/runtime.d.ts +3 -1
  21. package/dist/plugin/runtime.js +6 -0
  22. package/dist/plugin/scope.d.ts +1 -0
  23. package/dist/plugin/scope.js +2 -0
  24. package/dist/plugin/subtitles.js +9 -1
  25. package/dist/plugin/testing.d.ts +184 -5
  26. package/dist/plugin/testing.js +459 -67
  27. package/dist/plugin/timeline.js +3 -1
  28. package/dist/plugin/types.d.ts +523 -54
  29. package/dist/plugin/ui.js +29 -10
  30. package/dist/plugin/values.d.ts +55 -0
  31. package/dist/plugin/values.js +117 -0
  32. package/dist/plugin/window.d.ts +2 -0
  33. package/dist/plugin/window.js +4 -0
  34. package/dist/source-resolvers.d.ts +0 -1
  35. package/dist/source-resolvers.js +9 -1
  36. package/dist/subtitles.d.ts +3 -1
  37. package/dist/wire.d.ts +38 -6
  38. package/dist/wire.js +13 -2
  39. package/llms-full.txt +646 -58
  40. package/llms.txt +44 -40
  41. package/package.json +1 -1
  42. package/slint/vestra/markdown.slint +5 -5
  43. package/slint/vestra/widgets.slint +4 -4
@@ -3,16 +3,52 @@ 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
5
  import { COMMAND_LIMITS, MENU_IDS } from "../commands.ts";
6
+ import { UI_VALUE_LIMITS } from "./values.ts";
6
7
  import type { CommandDeclaration } from "../commands.ts";
7
8
  import type { SourceState } from "../source.ts";
8
9
  import type { SubtitleState } from "../subtitles.ts";
9
10
  import type { RenderSettings, ViewSettings } from "./types.ts";
10
11
  import type { MediaJob } from "../media.ts";
11
- import type { Plugin, ThemeId } from "./types.ts";
12
+ import type { LanguagePreference, Locale, Plugin, TemplateArgs, TextValue, ThemeId } from "./types.ts";
13
+ import { LOCALES, LIST_SEPARATORS, TEXT_LIMITS } from "../i18n.ts";
12
14
  /** Throw one of these from `host.handle(...)` to simulate a definite refusal. */
13
15
  export { HostError };
14
16
  /** The player's command caps, which the test host enforces as well. */
15
17
  export { COMMAND_LIMITS, MENU_IDS };
18
+ /** The player's property value limits, which the test host enforces as well. */
19
+ export { UI_VALUE_LIMITS };
20
+ /**
21
+ * The player's limits on one text value ({@link Text}, {@link TextList},
22
+ * with everything nested in it), which the test host enforces as well:
23
+ * nesting depth 8, 1024 texts, lists and arguments in all, 16 arguments per
24
+ * text, 256 items per list, 4096 bytes per source text or string argument.
25
+ *
26
+ * @since 1.5.11
27
+ */
28
+ export { TEXT_LIMITS };
29
+ /**
30
+ * The separator of each {@link ListStyle} per interface language, as the
31
+ * player joins a {@link TextList}.
32
+ *
33
+ * @since 1.5.11
34
+ */
35
+ export { LIST_SEPARATORS };
36
+ /**
37
+ * The player's interface languages, English (the source) first, as the
38
+ * player's own table: `["en", "zh-Hans", "ja", "ko", "de", "fr", "es"]`.
39
+ *
40
+ * @example
41
+ * ```ts
42
+ * import { LOCALES } from "@vestra-player/sdk/testing";
43
+ *
44
+ * for (const locale of LOCALES.slice(1)) {
45
+ * const { host, stop } = await startPlugin(activate, { directory, locale });
46
+ * assert.notEqual(host.command("open").display.title, "Open…");
47
+ * await stop();
48
+ * }
49
+ * ```
50
+ */
51
+ export { LOCALES };
16
52
  type Handler = (params: Record<string, Json>) => unknown;
17
53
  type Call = {
18
54
  method: string;
@@ -59,6 +95,19 @@ export interface FakeWindow {
59
95
  */
60
96
  userClose(): void;
61
97
  }
98
+ /**
99
+ * A file dialog as the player shows it ({@link TestHost.fileDialogs}).
100
+ *
101
+ * @since 1.5.11
102
+ */
103
+ export interface FileDialogText {
104
+ /** Which dialog. */
105
+ kind: "openFile" | "openFiles" | "openDirectory" | "saveFile";
106
+ /** Its title as shown, if the plugin gave one. */
107
+ title?: string;
108
+ /** The proposed file name as shown, if the plugin gave one. */
109
+ fileName?: string;
110
+ }
62
111
  /**
63
112
  * A command the plugin registered with `plugin.commands.add`, as the test
64
113
  * host keeps it. Get one with {@link TestHost.command} (the plugin's own) or
@@ -80,6 +129,28 @@ export interface FakeCommand {
80
129
  * versions.
81
130
  */
82
131
  declaration: CommandDeclaration;
132
+ /**
133
+ * What the menu shows, as the player shows it in the test host's
134
+ * {@link TestHost.locale}: the title and each path segment translated with
135
+ * the plugin's catalog (the player's own menus in the player's words), the
136
+ * keys normalized (`@video` is "Video").
137
+ *
138
+ * @since 1.5.9
139
+ */
140
+ readonly display: {
141
+ /** The menu text of the item. */
142
+ title: string;
143
+ /** Each segment of the path as shown; empty without a path. */
144
+ path: string[];
145
+ /**
146
+ * The disabled or busy reason as shown, `null` without one (a plugin
147
+ * command's; not enumerable, so comparing `display` with
148
+ * `{ title, path }` still works).
149
+ *
150
+ * @since 1.5.11
151
+ */
152
+ reason?: string | null;
153
+ };
83
154
  /** The command state the player last received (after the update pass). */
84
155
  state: {
85
156
  /** `false` when the plugin disabled the command. */
@@ -88,8 +159,8 @@ export interface FakeCommand {
88
159
  checked: boolean | null;
89
160
  /** `true` while the plugin reports the command as busy. */
90
161
  busy: boolean;
91
- /** The reason shown when the command is disabled or busy. */
92
- reason: string | null;
162
+ /** The reason the plugin sent (source text or a text value; see `display.reason`). */
163
+ reason: TextValue | null;
93
164
  };
94
165
  /**
95
166
  * The state revision the player holds; increases with every state update
@@ -257,10 +328,110 @@ export declare class TestHost implements Transport {
257
328
  */
258
329
  sidebar: LayoutSlot | null;
259
330
  };
260
- /** Every `osd.show` text, newest last. */
331
+ /**
332
+ * Every `osd.show` text as the player shows it (translated with the
333
+ * plugin's catalog and formatted with its args), newest last. The
334
+ * template and args the plugin sent are in `host.sent("osd.show")`.
335
+ */
261
336
  readonly osd: string[];
262
- /** The text of this plugin's osd panel, `null` when hidden. */
337
+ /** The text of this plugin's osd panel as shown, `null` when hidden. */
263
338
  osdPanel: string | null;
339
+ /**
340
+ * The interface language the test host plays (`plugin.i18n.locale`), `"en"`
341
+ * by default. Set it before {@link startPlugin} (or pass its `locale`
342
+ * option): the plugin's catalog for it is read from the package's
343
+ * `locales/` then, and the menus ({@link FakeCommand.display}), OSD
344
+ * messages, dialog texts, settings page and panel titles show its
345
+ * translations, the way the player shows them.
346
+ *
347
+ * The player's own texts (core actions' messages and menus, its
348
+ * refusals) are English, or the player's Simplified Chinese for
349
+ * `"zh-Hans"`. While `locale` was never set they stay in Chinese, as
350
+ * players up to 0.4.2 showed them, so tests written for SDK 1.5.8 keep
351
+ * passing; set `locale` to test what a user sees now.
352
+ *
353
+ * @example
354
+ * ```ts
355
+ * const { host, stop } = await startPlugin(activate, { directory, locale: "zh-Hans" });
356
+ * assert.deepEqual(host.command("blur").display.path, ["视频", "模糊…"]);
357
+ * await stop();
358
+ * ```
359
+ *
360
+ * @since 1.5.9
361
+ */
362
+ get locale(): Locale;
363
+ set locale(locale: Locale);
364
+ /**
365
+ * Read the plugin's translations for {@link TestHost.locale} from a
366
+ * package directory, as the player would (`locales/<locale>.json` of a
367
+ * built package, else the source `locales/<locale>.po`); `startPlugin`
368
+ * does this. None for English; a missing file means none; a broken `.po`
369
+ * throws.
370
+ *
371
+ * @param directory - The package directory (holding `locales/`).
372
+ * @since 1.5.9
373
+ */
374
+ loadCatalog(directory: string): void;
375
+ /**
376
+ * A text of the plugin as the player shows it: translated with the
377
+ * plugin's catalog ({@link TestHost.loadCatalog}), then formatted with
378
+ * `args` when given.
379
+ *
380
+ * @param text - English source text or template.
381
+ * @param args - Values for its placeholders.
382
+ * @returns The shown text.
383
+ *
384
+ * @example
385
+ * ```ts
386
+ * assert.equal(host.translate("Speed {}×", [2]), "倍速 2×");
387
+ * ```
388
+ *
389
+ * @since 1.5.9
390
+ */
391
+ translate(text: TextValue, args?: TemplateArgs): string;
392
+ /**
393
+ * The saved interface language (`plugin.appearance.language`), changed by
394
+ * `appearance.setLanguage` or {@link TestHost.setLanguage}.
395
+ *
396
+ * @since 1.5.9
397
+ */
398
+ language: LanguagePreference;
399
+ /**
400
+ * Simulate another plugin (or the user) saving the interface language:
401
+ * pushes `appearance.changed`.
402
+ *
403
+ * @param language - `"system"` or a locale.
404
+ * @since 1.5.9
405
+ */
406
+ setLanguage(language: LanguagePreference): void;
407
+ /**
408
+ * Every `dialogs.confirm` as the player shows it: its texts translated
409
+ * (message and detail formatted with `args`), newest last. The options
410
+ * the plugin sent are in {@link TestHost.confirms}.
411
+ *
412
+ * @since 1.5.9
413
+ */
414
+ readonly confirmTexts: {
415
+ /** The question. */
416
+ message: string;
417
+ /** The text under it, if any. */
418
+ detail?: string;
419
+ /** The confirm button. */
420
+ confirm: string;
421
+ /** The cancel button (the player's "Cancel" by default). */
422
+ cancel: string;
423
+ /** The checkbox, if any. */
424
+ checkbox?: string;
425
+ }[];
426
+ /**
427
+ * Every file dialog (`openFile`, `openFiles`, `openDirectory`,
428
+ * `saveFile`) as the player shows it: the title translated, a text file
429
+ * name translated, newest last. The options the plugin sent are in
430
+ * `host.sent("dialogs.open")`.
431
+ *
432
+ * @since 1.5.11
433
+ */
434
+ readonly fileDialogs: FileDialogText[];
264
435
  /** The sequence provider's flags, `null` without a provider. */
265
436
  sequence: {
266
437
  /** The provider's `canNext`, as last evaluated. */
@@ -1341,6 +1512,14 @@ export declare function startPlugin(activate: (plugin: Plugin) => unknown, optio
1341
1512
  host?: TestHost;
1342
1513
  /** Use an existing directory as `plugin.dataDirectory` (to test a restart). */
1343
1514
  dataDirectory?: string;
1515
+ /**
1516
+ * The interface language to play ({@link TestHost.locale}): the plugin's
1517
+ * `locales/<locale>.po` (or `.json`) in `directory` is loaded and its
1518
+ * menus, messages and titles show translated. Default: the host's.
1519
+ *
1520
+ * @since 1.5.9
1521
+ */
1522
+ locale?: Locale;
1344
1523
  }): Promise<{
1345
1524
  /** The test host the plugin talks to. */
1346
1525
  host: TestHost;