@vestra-player/sdk 1.6.18 → 1.6.19

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 (61) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/dist/docs-tree.json.gz +0 -0
  3. package/dist/plugin/appearance.js +1 -1
  4. package/dist/plugin/bindings.js +1 -1
  5. package/dist/plugin/commands.js +3 -2
  6. package/dist/plugin/connection.d.ts +119 -0
  7. package/dist/plugin/connection.js +402 -0
  8. package/dist/plugin/drops.d.ts +3 -3
  9. package/dist/plugin/drops.js +1 -1
  10. package/dist/plugin/errors.d.ts +9 -0
  11. package/dist/plugin/errors.js +35 -0
  12. package/dist/plugin/filters.js +1 -1
  13. package/dist/plugin/index.d.ts +1 -1
  14. package/dist/plugin/index.js +3 -1
  15. package/dist/plugin/language.js +1 -1
  16. package/dist/plugin/latest.d.ts +12 -0
  17. package/dist/plugin/latest.js +52 -0
  18. package/dist/plugin/local.js +1 -2
  19. package/dist/plugin/markdown.d.ts +2 -2
  20. package/dist/plugin/markdown.js +1 -1
  21. package/dist/plugin/media.js +1 -1
  22. package/dist/plugin/osd.js +2 -2
  23. package/dist/plugin/packages.js +1 -1
  24. package/dist/plugin/passes.d.ts +111 -0
  25. package/dist/plugin/passes.js +341 -0
  26. package/dist/plugin/player.d.ts +2 -3
  27. package/dist/plugin/player.js +2 -7
  28. package/dist/plugin/resolvers.js +1 -1
  29. package/dist/plugin/runtime.d.ts +24 -209
  30. package/dist/plugin/runtime.js +34 -770
  31. package/dist/plugin/scope.d.ts +2 -1
  32. package/dist/plugin/scope.js +5 -2
  33. package/dist/plugin/services/dialogs.d.ts +3 -0
  34. package/dist/plugin/services/dialogs.js +62 -0
  35. package/dist/plugin/services.js +1 -1
  36. package/dist/plugin/settings.js +2 -2
  37. package/dist/plugin/shaders.js +2 -1
  38. package/dist/plugin/stream.js +1 -2
  39. package/dist/plugin/subtitles.js +2 -2
  40. package/dist/plugin/testing/transport.js +1 -1
  41. package/dist/plugin/timeline.js +2 -1
  42. package/dist/plugin/view/base.d.ts +29 -0
  43. package/dist/plugin/view/base.js +88 -0
  44. package/dist/plugin/view/binder.d.ts +22 -0
  45. package/dist/plugin/view/binder.js +57 -0
  46. package/dist/plugin/view/image.d.ts +21 -0
  47. package/dist/plugin/view/image.js +60 -0
  48. package/dist/plugin/view/layout.d.ts +47 -0
  49. package/dist/plugin/view/layout.js +171 -0
  50. package/dist/plugin/view/panel.d.ts +3 -0
  51. package/dist/plugin/view/panel.js +178 -0
  52. package/dist/plugin/view/window.d.ts +3 -0
  53. package/dist/plugin/view/window.js +99 -0
  54. package/dist/plugin/view/writer.d.ts +58 -0
  55. package/dist/plugin/view/writer.js +634 -0
  56. package/dist/plugin/view.js +2 -1
  57. package/dist/plugin/window.js +1 -1
  58. package/llms.txt +1 -1
  59. package/package.json +2 -2
  60. package/dist/plugin/ui.d.ts +0 -74
  61. package/dist/plugin/ui.js +0 -1234
@@ -0,0 +1,178 @@
1
+ /** Docked panels (`plugin.panels`): one Slint component per position. */
2
+ import { Latest } from "../latest.js";
3
+ import { failure, quiet } from "../errors.js";
4
+ import { dropsOf } from "../drops.js";
5
+ import { exported, ViewBase } from "./base.js";
6
+ import { backdrop, layoutOf, slot } from "./layout.js";
7
+ class PanelHandle extends ViewBase {
8
+ ready;
9
+ #slot;
10
+ #mounted = false;
11
+ #locks = { focused: false, dragging: false, popupOpen: false };
12
+ #interaction;
13
+ constructor(scope, slot, file, options) {
14
+ // One panel per slot (SDK 1.6.15, player 0.7.6; a player before 0.7.6
15
+ // refuses a second panel with `conflict`).
16
+ if (scope.runtime.views.has(`panel:${slot}`))
17
+ throw new Error(`this plugin already has a panel in the ${slot}`);
18
+ super(scope);
19
+ this.#slot = slot;
20
+ const runtime = scope.runtime;
21
+ runtime.views.set(`panel:${slot}`, (callback, args) => this.dispatch(callback, args));
22
+ this.#interaction = new Latest((locks) => runtime.host.ui.interaction(locks, this.#named()));
23
+ const component = options.component ?? exported(runtime.directory, file, "component");
24
+ const global = options.global ?? exported(runtime.directory, file, "global");
25
+ scope.add(this);
26
+ this.ready = runtime
27
+ .starting((async () => {
28
+ // Let handlers and bindings declared right after mount() join in.
29
+ await Promise.resolve();
30
+ const properties = this.wired(this.snapshot());
31
+ await runtime.host.ui.mount({
32
+ slot,
33
+ file,
34
+ component,
35
+ global,
36
+ ...(options.title === undefined ? {} : { title: options.title }),
37
+ properties,
38
+ });
39
+ this.#mounted = true;
40
+ this.shown(properties);
41
+ this.flush();
42
+ // The position's state changed: refresh it if this plugin reads it.
43
+ await layoutOf(runtime).refresh();
44
+ })(), (error) => scope.report(error))
45
+ .then(() => { });
46
+ quiet(this.ready);
47
+ }
48
+ live() {
49
+ return this.#mounted && !this.closed;
50
+ }
51
+ /**
52
+ * The slot to name in a request about this panel: only while the plugin
53
+ * has panels in more than one slot (the player then needs it); a plugin
54
+ * with one panel sends what it always sent.
55
+ */
56
+ #named() {
57
+ const panels = [...this.scope.runtime.views.keys()].filter((key) => key.startsWith("panel:"));
58
+ return panels.length > 1 ? this.#slot : undefined;
59
+ }
60
+ write(property, write) {
61
+ return this.scope.runtime.host.ui.setMount(property, write, this.#named());
62
+ }
63
+ get state() {
64
+ const service = layoutOf(this.scope.runtime);
65
+ service.start();
66
+ return this.live()
67
+ ? panelState(service.state, this.#slot, this.scope.runtime.manifest.id)
68
+ : null;
69
+ }
70
+ onChange(listener) {
71
+ const scope = this.scope;
72
+ let last = JSON.stringify(this.state);
73
+ const watch = layoutOf(scope.runtime).watch(() => {
74
+ const state = this.state;
75
+ const next = JSON.stringify(state);
76
+ if (next === last)
77
+ return;
78
+ last = next;
79
+ scope.run(listener, state);
80
+ });
81
+ return scope.add({
82
+ dispose: async () => {
83
+ scope.forget(watch);
84
+ await watch.dispose();
85
+ },
86
+ });
87
+ }
88
+ async show() {
89
+ await this.ready;
90
+ if (this.#slot === "backdrop")
91
+ throw failure("invalid", "the player shows the backdrop by itself");
92
+ await layoutOf(this.scope.runtime).set(this.#slot, {
93
+ active: this.scope.runtime.manifest.id,
94
+ visible: true,
95
+ });
96
+ }
97
+ async toggle() {
98
+ await this.ready;
99
+ if (this.#slot === "backdrop")
100
+ throw failure("invalid", "the player shows the backdrop by itself");
101
+ await layoutOf(this.scope.runtime).read();
102
+ const state = this.state;
103
+ // A switch, the same in every mode (user ruling 2026-11-08): off when
104
+ // on and current, else on at this tab. `reveal: false`: in fullscreen
105
+ // nothing pops out, the right edge reveals it (player 0.7.6; older
106
+ // players ignore it and pop it out).
107
+ if (state?.preferred && state.active)
108
+ await layoutOf(this.scope.runtime).set(this.#slot, { visible: false });
109
+ else
110
+ await layoutOf(this.scope.runtime).set(this.#slot, {
111
+ active: this.scope.runtime.manifest.id,
112
+ visible: true,
113
+ reveal: false,
114
+ });
115
+ }
116
+ async showMenu(items, at) {
117
+ await this.ready;
118
+ await this.scope.runtime.host.ui.menu(items.map((item) => item?.id ?? null), at.x, at.y, this.#named());
119
+ }
120
+ onDrop(listener) {
121
+ const watch = dropsOf(this.scope.runtime).listen(this.scope, (event) => this.scope.run(listener, event));
122
+ return this.scope.add({ dispose: () => watch.dispose() });
123
+ }
124
+ setInteraction(locks) {
125
+ Object.assign(this.#locks, locks);
126
+ this.#interaction
127
+ .push({ ...this.#locks })
128
+ .catch((error) => this.scope.report(error));
129
+ }
130
+ async dispose() {
131
+ if (this.closed)
132
+ return;
133
+ this.scope.forget(this);
134
+ this.#interaction.stop();
135
+ if (this.#mounted)
136
+ await this.scope.runtime.host.ui.unmount(this.#named()).catch(() => { });
137
+ this.#mounted = false;
138
+ await this.release();
139
+ this.scope.runtime.views.delete(`panel:${this.#slot}`);
140
+ // Listeners of `onChange` hear `null`.
141
+ this.scope.runtime.invalidate();
142
+ await layoutOf(this.scope.runtime).refresh();
143
+ }
144
+ }
145
+ /** One panel's view of its position (`Panel.state`). */
146
+ function panelState(layout, slot, plugin) {
147
+ if (slot === "backdrop")
148
+ return (layout.backdrop && {
149
+ active: true,
150
+ preferred: true,
151
+ effective: layout.backdrop.effective,
152
+ reason: null,
153
+ detached: false,
154
+ });
155
+ const state = layout[slot];
156
+ if (!state)
157
+ return null;
158
+ // Players without tabs (no `panels`): the position holds this panel only.
159
+ const panels = layout.tabs[slot];
160
+ const own = panels?.find((panel) => panel.plugin === plugin);
161
+ if (panels && !own)
162
+ return null;
163
+ const active = own?.active ?? true;
164
+ return {
165
+ active,
166
+ preferred: state.preferred,
167
+ effective: state.effective && active,
168
+ reason: active ? state.reason : "otherTab",
169
+ detached: state.detached,
170
+ };
171
+ }
172
+ export const panels = (scope) => ({
173
+ mount: (position, file, options = {}) => new PanelHandle(scope, position, file, options),
174
+ bottom: slot(scope, "bottom"),
175
+ sidebar: slot(scope, "sidebar"),
176
+ adjust: slot(scope, "adjust"),
177
+ backdrop: backdrop(scope),
178
+ });
@@ -0,0 +1,3 @@
1
+ import type { ScopeImpl } from "../scope.ts";
2
+ import type { Windows } from "../types.ts";
3
+ export declare const windows: (scope: ScopeImpl) => Windows;
@@ -0,0 +1,99 @@
1
+ /** Tool windows (`plugin.windows`): one Slint window per handle. */
2
+ import { basename, extname } from "node:path";
3
+ import { failure } from "../errors.js";
4
+ import { ViewBase } from "./base.js";
5
+ import { kebab } from "./writer.js";
6
+ class WindowHandle extends ViewBase {
7
+ #id;
8
+ #file;
9
+ #opened = false;
10
+ #queue = Promise.resolve();
11
+ constructor(scope, file, id) {
12
+ if (scope.runtime.views.has(id))
13
+ throw failure("conflict", `window ${id} already exists; pass a distinct id`);
14
+ super(scope);
15
+ this.#file = file;
16
+ this.#id = id;
17
+ scope.runtime.views.set(id, (callback, args) => this.dispatch(callback, args));
18
+ // The user closed it with the title bar: the host already dropped it.
19
+ this.#closedByUser = scope.runtime.raw("ui.closed", (data) => {
20
+ if (data.view !== this.#id || !this.#opened)
21
+ return;
22
+ this.#opened = false;
23
+ for (const listener of [...this.#closeListeners])
24
+ this.scope.run(listener);
25
+ this.scope.update();
26
+ });
27
+ scope.add(this);
28
+ }
29
+ #closeListeners = new Set();
30
+ #closedByUser;
31
+ onClose(listener) {
32
+ this.#closeListeners.add(listener);
33
+ return this.scope.onDispose(() => {
34
+ this.#closeListeners.delete(listener);
35
+ });
36
+ }
37
+ get opened() {
38
+ return this.#opened;
39
+ }
40
+ live() {
41
+ return this.#opened && !this.closed;
42
+ }
43
+ write(property, write) {
44
+ return this.scope.runtime.host.ui.set(this.#id, property, write);
45
+ }
46
+ on(callback, handler) {
47
+ // The host forwards only callbacks named when the window opens.
48
+ if (this.#opened && !this.handlers.has(kebab(callback)))
49
+ throw new Error(`bind window handler ${callback} before the first show()`);
50
+ return super.on(callback, handler);
51
+ }
52
+ /** Window RPCs run strictly in order; a failure does not block later calls. */
53
+ #serial(task) {
54
+ const next = this.#queue.then(task, task);
55
+ this.#queue = next.catch(() => { });
56
+ return next;
57
+ }
58
+ show() {
59
+ return this.#serial(async () => {
60
+ if (this.closed)
61
+ return;
62
+ const ui = this.scope.runtime.host.ui;
63
+ if (this.#opened)
64
+ return ui.show(this.#id, true);
65
+ const initial = this.wired(this.snapshot(), true);
66
+ await ui.open(this.#id, this.#file, initial, [...this.handlers.keys()]);
67
+ this.#opened = true;
68
+ this.shown(initial);
69
+ this.flush();
70
+ });
71
+ }
72
+ hide() {
73
+ return this.#serial(async () => {
74
+ if (this.#opened)
75
+ await this.scope.runtime.host.ui.show(this.#id, false);
76
+ });
77
+ }
78
+ close() {
79
+ return this.#serial(async () => {
80
+ if (!this.#opened)
81
+ return;
82
+ this.#opened = false;
83
+ await this.scope.runtime.host.ui.close(this.#id);
84
+ });
85
+ }
86
+ async dispose() {
87
+ if (this.closed)
88
+ return;
89
+ this.scope.forget(this);
90
+ await this.close().catch(() => { });
91
+ await this.release();
92
+ await this.#closedByUser.dispose();
93
+ this.#closeListeners.clear();
94
+ this.scope.runtime.views.delete(this.#id);
95
+ }
96
+ }
97
+ export const windows = (scope) => ({
98
+ create: (file, options) => new WindowHandle(scope, file, options?.id ?? basename(file, extname(file))),
99
+ });
@@ -0,0 +1,58 @@
1
+ import type { Json, PropertyWrite } from "../../wire.ts";
2
+ import type { PropValue, Props } from "../types.ts";
3
+ /** Slint names are kebab-case; plugin code may use camelCase. */
4
+ export declare const kebab: (name: string) => string;
5
+ export declare const encode: (props: Props) => {
6
+ [k: string]: PropValue;
7
+ };
8
+ /** Where a {@link ViewWriter}'s writes go, and what it asks of its view. */
9
+ export interface WriterTarget {
10
+ /** The view shows its properties: writes may be sent. */
11
+ live(): boolean;
12
+ /** Send one property write to the player. */
13
+ write(property: string, write: PropertyWrite): Promise<void>;
14
+ /** Report a failure (the plugin's error handler and the player's log). */
15
+ report(error: unknown): void;
16
+ /** The player takes row patches (and `seq` on whole writes). */
17
+ patches(): boolean;
18
+ }
19
+ /**
20
+ * One view's properties: their values now, what the player shows as far as
21
+ * this side knows, and the writes that bring the one to the other.
22
+ */
23
+ export declare class ViewWriter {
24
+ #private;
25
+ constructor(target: WriterTarget);
26
+ /**
27
+ * Initial properties in wire form. A released image is left out (the
28
+ * property keeps its default) and reported, like a failed write. With a
29
+ * player that takes row patches, the arrays of one request carry at most
30
+ * {@link CHUNK_BYTES} of rows together (SDK 1.5.46): an array over that
31
+ * goes with its first rows, and the first flush sends the rest as patches.
32
+ * An array over the value limits is refused here, as the player would
33
+ * refuse it whole: left out and reported (a panel, a settings page), or,
34
+ * with `refuse` (a window's `show`), thrown.
35
+ */
36
+ wired(values: Record<string, PropValue>, refuse?: boolean): Record<string, Json>;
37
+ /** `View.set`: each value is sent once even if equal to what was sent. */
38
+ set(props: Props): void;
39
+ /** A bound value (Slint name): sent by the next flush if it changed. */
40
+ assign(key: string, value: PropValue): void;
41
+ /** Every property's value now, by Slint name. */
42
+ values(): Record<string, PropValue>;
43
+ /** The host already shows these values (e.g. sent as initial properties). */
44
+ shown(values: Record<string, Json>): void;
45
+ /**
46
+ * Send what changed. A bound value is sent when it differs from the last one
47
+ * sent; a `set()` value is always sent once. Nothing is resent merely
48
+ * because a callback arrived, so text the user typed into an in-out
49
+ * property survives other callbacks (call `set()` to pull a control back).
50
+ * A failed bound value is not repeated until it changes; an explicit `set()`
51
+ * is, after a back-off (0.25 s doubling to 30 s). Only the first failure of
52
+ * a failing streak is reported, so an error handler that writes a status to
53
+ * a broken view cannot spin.
54
+ */
55
+ flush(): void;
56
+ /** The view closed: no retry after a back-off. */
57
+ stop(): void;
58
+ }