@vestra-player/sdk 1.6.10 → 1.6.12

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 CHANGED
@@ -1,5 +1,23 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.6.12
4
+
5
+ **Who must change: nobody.** A behaviour fix of `toggle` in fullscreen, an example fix and a development warning; no new API, `apiVersion` stays `vestra/1`.
6
+
7
+ - **`Panel.toggle` and `PanelSlot.toggle` slide a collapsed column out.** In fullscreen a sidebar or adjust column the user wants shown is collapsed at the right edge (`reason: "fullscreen"`). Toggling it then turned it off; it now shows it, which player 0.7.6 answers by sliding the column out, as its own Adjust button and Alt+1…4 do. Toggling a column that is on screen still turns it off. With players before 0.7.6 a show of a collapsed column changes nothing on screen, as before.
8
+ - **[adjust-panel](docs/examples/adjust-panel.md)** declares `events`: without it `plugin.player.state` changes only after the plugin's own calls, so a panel activated before a file was opened stayed disabled at 1×. A new test opens the file after activation. Its presets are four (0.5×, 1×, 1.5×, 2×), which fit the adjust column at its least width. No other example reads `player.state` without `events`.
9
+ - **A development warning for state followed without `events`.** A plugin that registers an `onChange` or a `bind` whose function reads changing state (`player.state`, a panel position, and the like) without the `events` capability gets one line in its log, once per plugin: changes made elsewhere never reach it. Only a warning; behaviour is unchanged. `TestHost` already behaved like the player here (it delivers `source.state` and the other state events only to a plugin that subscribed, which needs `events`): a new test pins that down. The example's old tests passed because they opened the file before starting the plugin or did not check `enabled`. No SDK example or test relied on the opposite, so nobody must change.
10
+ ## 1.6.11
11
+
12
+ **Who must change: nobody.** Additions only: a mount slot, a panel position, a button icon and `TestHost` support; `apiVersion` stays `vestra/1`, no new capability. A plugin that mounts `"adjust"` sets `minPlayerVersion` to `0.7.6`: older players refuse the slot (`invalid`), which fails the activation like any refused mount.
13
+
14
+ - **The adjust slot** (player 0.7.6, menu redesign step 3b): `plugin.panels.mount("adjust", file, { title })` puts a panel in the adjust column, for controls that change what is playing (picture, sound, subtitles, speed). Any number of plugins mount there, one tab each. The slot is a contract about purpose; the player decides how it looks and may change that. The rule, in [Slots](docs/explanation/ui.md#slots-what-each-one-is-for) and on `Panels.mount`: if the user still looks at the panel after closing it, it is `"sidebar"`; otherwise it is `"adjust"`. Today the player draws a second column between the video and the sidebar, with text tabs and the opening key under each title (the key of the command the panel's commands name as their `home`), no close or detach button, floating over the video in a narrow window and sliding out from the right edge in fullscreen; it starts closed and is compiled the first time it opens.
15
+ - **`plugin.panels.adjust`**: the position, the same shape as `panels.sidebar` without `setDetached` (`PanelSlot`: `state`, `panels`, `onChange`, `setVisible`, `toggle`, `activate`). `Panel.show` and `Panel.toggle` work on an adjust panel as on a sidebar tab. Players before 0.7.6 build the `plugin` object with their own SDK, which has no `adjust`: there `plugin.panels.adjust` is `undefined` and reading its `state` throws a `TypeError` (as for `commands.keys`), so declare `minPlayerVersion` 0.7.6 or test it first.
16
+ - **Wire**: `ui.mount` and `layout.set` take `slot: "adjust"`; `layout.state` reports `adjust` (a `LayoutSlot`, never `detached`); `LayoutSlot` names the adjust column.
17
+ - **`BAR_BUTTON_ICONS`** gains `"sliders"` (the player's Adjust button), and `BUILTIN_COMMANDS` the player's `host/adjust`. `@vestra/bar-buttons.slint` (synced): `BarButtonsHost.adjust`, counted in `fixed`; `@vestra/icons.slint`: `sliders`.
18
+ - **`TestHost`**: `layout.adjust` (starts hidden), `mountOther("adjust", …)`, `selectTab` for adjust tabs, `layout.set` refuses detaching the adjust column, `keepsRunning` names an open adjust column's current tab; an unknown mount slot is refused with `invalid`, as by the player.
19
+ - **Example** [adjust-panel](docs/examples/adjust-panel.md): a speed slider in the adjust column, opened and closed by its own key.
20
+
3
21
  ## 1.6.10
4
22
 
5
23
  **Who must change: nobody.** Additions only: a clip format, optional export fields, two optional `JobState` fields, an error code and a `TestHost` field. `apiVersion` stays `vestra/1`, no new capability (`media.control` covers them). A plugin that does not use them sends exactly what it sent before; one that uses them needs player 0.7.6: declare `"minPlayerVersion": "0.7.6"`, or compare `plugin.app.version` with 0.7.6 to offer them only where they exist (do not probe by trying: on an older player `plugin` comes from that player's own, older SDK, which rejects `format: "copy"` with `invalid` but builds a re-encode request without the `encoding` it does not know, so the clip is encoded with the fixed settings and no error), and leave `progress`, `start` and `end` out.
@@ -136,7 +136,9 @@ export declare const MENU_IDS: readonly ["open", "playback", "subtitles", "video
136
136
  * The icons a bottom-bar button may name (`CommandOptions.button.icon`):
137
137
  * every icon of the player's V-Line set, `@vestra/icons.slint` (draw one
138
138
  * in your own Slint with `Icon { name: "store"; }`). The player's tests check
139
- * that this list is its icon set.
139
+ * that this list is its icon set. `"sliders"` (three sliders, the player's
140
+ * Adjust button) is new in player 0.7.6 (SDK 1.6.11); older players refuse
141
+ * it as a button icon with `invalid`.
140
142
  *
141
143
  * @example Check in a test that the button's icon exists
142
144
  * The test fails before release if the plugin's bar button names an icon
@@ -154,7 +156,7 @@ export declare const MENU_IDS: readonly ["open", "playback", "subtitles", "video
154
156
  *
155
157
  * @category Testing
156
158
  */
157
- export declare const BAR_BUTTON_ICONS: readonly ["play", "pause", "stop", "back", "forward", "open", "volume", "mute", "playlist", "detach", "attach", "close", "failed", "move-top", "move-up", "move-down", "move-bottom", "search", "chapter", "bookmark", "check-task", "check-box", "menu-check", "submenu", "scroll-up", "scroll-down", "caret-down", "sort-asc", "sort-desc", "tag-remove", "preview-play", "add", "remove", "sort", "fullscreen", "exit-fullscreen", "store", "ai"];
159
+ export declare const BAR_BUTTON_ICONS: readonly ["play", "pause", "stop", "back", "forward", "open", "volume", "mute", "playlist", "detach", "attach", "close", "failed", "move-top", "move-up", "move-down", "move-bottom", "search", "chapter", "bookmark", "check-task", "check-box", "menu-check", "submenu", "scroll-up", "scroll-down", "caret-down", "sort-asc", "sort-desc", "tag-remove", "preview-play", "add", "remove", "sort", "fullscreen", "exit-fullscreen", "store", "ai", "sliders"];
158
160
  /**
159
161
  * The player's limits on bottom-bar buttons (`CommandOptions.button`), the
160
162
  * same values as the player's (its tests check that they agree). A button
package/dist/commands.js CHANGED
@@ -141,7 +141,9 @@ export const MENU_IDS = [
141
141
  * The icons a bottom-bar button may name (`CommandOptions.button.icon`):
142
142
  * every icon of the player's V-Line set, `@vestra/icons.slint` (draw one
143
143
  * in your own Slint with `Icon { name: "store"; }`). The player's tests check
144
- * that this list is its icon set.
144
+ * that this list is its icon set. `"sliders"` (three sliders, the player's
145
+ * Adjust button) is new in player 0.7.6 (SDK 1.6.11); older players refuse
146
+ * it as a button icon with `invalid`.
145
147
  *
146
148
  * @example Check in a test that the button's icon exists
147
149
  * The test fails before release if the plugin's bar button names an icon
@@ -198,6 +200,7 @@ export const BAR_BUTTON_ICONS = [
198
200
  "exit-fullscreen",
199
201
  "store",
200
202
  "ai",
203
+ "sliders",
201
204
  ];
202
205
  /**
203
206
  * The player's limits on bottom-bar buttons (`CommandOptions.button`), the
@@ -601,6 +604,7 @@ host | enter-fullscreen | (Enter Fullscreen)
601
604
  host | exit-fullscreen | (Leave Fullscreen)
602
605
  host | sidebar | (Toggle Sidebar)
603
606
  host | palette | (Command Palette) | Ctrl+Shift+P
607
+ host | adjust | (Adjust)
604
608
  vestra.info | info.brief | (Brief Info) | Shift+Tab
605
609
  vestra.playlist | playlist.play | (Play) | Enter, P | panel
606
610
  vestra.playlist | playlist.list-add | (Add Files…) | Ctrl+I | panel
Binary file
package/dist/i18n.js CHANGED
@@ -942,6 +942,7 @@ export const HOST_ZH_HANS = {
942
942
  "bottom bar": "底栏",
943
943
  sidebar: "侧栏",
944
944
  "video backdrop": "视频背景",
945
+ "adjust column": "调节栏",
945
946
  "The {} is used by {}": "{}已被 {} 使用",
946
947
  "{} is resolved by {}": "{} 已由 {} 解析",
947
948
  "File type .{}": "文件类型 .{}",
@@ -208,6 +208,8 @@ export class Runtime {
208
208
  /** The plugin was disposed: no safety pass is armed any more. */
209
209
  #stopped = false;
210
210
  #events = new Map();
211
+ /** The missing `events` warning was given (once per plugin). */
212
+ #warnedEvents = false;
211
213
  views = new Map();
212
214
  commands = new Map();
213
215
  services = new Map();
@@ -617,6 +619,10 @@ export class Runtime {
617
619
  group.active++;
618
620
  group.whys.set(token, listenerWhy(this.callSite()));
619
621
  }
622
+ if (created && needs === "events" && !this.can(needs))
623
+ group.unheard = true;
624
+ if (!mirror && group.unheard)
625
+ this.#warnUnheard(event);
620
626
  if (created && (needs === null || this.can(needs))) {
621
627
  group.told = group.active > 0;
622
628
  const whys = group.told ? allWhys(group) : undefined;
@@ -658,6 +664,8 @@ export class Runtime {
658
664
  const group = this.#events.get(event);
659
665
  if (!group)
660
666
  return { dispose: async () => { } };
667
+ if (group.unheard)
668
+ this.#warnUnheard(event);
661
669
  group.active++;
662
670
  const token = {};
663
671
  group.whys.set(token, why ?? listenerWhy(this.callSite()));
@@ -675,6 +683,19 @@ export class Runtime {
675
683
  },
676
684
  };
677
685
  }
686
+ /**
687
+ * A development warning, once per plugin, no behaviour change (SDK
688
+ * 1.6.12): the plugin follows `event` (an `onChange`, a `bind` that reads
689
+ * the state) without the `events` capability, so changes made elsewhere
690
+ * (the user, the player, another plugin) never reach it; the state moves
691
+ * only after its own calls.
692
+ */
693
+ #warnUnheard(event) {
694
+ if (this.#warnedEvents)
695
+ return;
696
+ this.#warnedEvents = true;
697
+ console.warn(`[${this.manifest.id}] follows ${event} (onChange, or a bind() that reads the state) without the "events" capability: changes made elsewhere will not reach it, only its own calls refresh the state. Declare "events" in manifest.json.`);
698
+ }
678
699
  /**
679
700
  * Tell the player when `group` changes between mirror and listener, and
680
701
  * why it listens when that changed.
@@ -601,7 +601,7 @@ export declare class TestHost implements Transport {
601
601
  * See {@link TestHost.mounted}.
602
602
  */
603
603
  panel: (Omit<FakeWindow, "callbacks" | "userClose"> & {
604
- /** `"bottom"`, `"sidebar"` or `"backdrop"`. */
604
+ /** `"bottom"`, `"sidebar"`, `"adjust"` or `"backdrop"`. */
605
605
  slot: string;
606
606
  /** The exported component the SDK found in the file. */
607
607
  component: string;
@@ -643,6 +643,12 @@ export declare class TestHost implements Transport {
643
643
  * the current tab's (the plugin under test is owner 1).
644
644
  */
645
645
  sidebar: LayoutSlot | null;
646
+ /**
647
+ * The adjust column (SDK 1.6.11, player 0.7.6), like the sidebar but
648
+ * never `detached`. It starts hidden (`preferred: false`, `reason:
649
+ * "userHidden"`), as the player's does until the user opens it.
650
+ */
651
+ adjust: LayoutSlot | null;
646
652
  };
647
653
  /**
648
654
  * Every `osd.show` text as the player shows it (translated with the
@@ -1331,7 +1337,7 @@ export declare class TestHost implements Transport {
1331
1337
  *
1332
1338
  * @capability None.
1333
1339
  */
1334
- mountOther(slot: "bottom" | "sidebar" | "backdrop", plugin: string, options?: {
1340
+ mountOther(slot: "bottom" | "sidebar" | "adjust" | "backdrop", plugin: string, options?: {
1335
1341
  title?: string;
1336
1342
  }): void;
1337
1343
  /**
@@ -2829,7 +2835,7 @@ export declare class TestHost implements Transport {
2829
2835
  * @since 1.0.0
2830
2836
  */
2831
2837
  mounted(): Omit<FakeWindow, "callbacks" | "userClose"> & {
2832
- /** `"bottom"`, `"sidebar"` or `"backdrop"`. */
2838
+ /** `"bottom"`, `"sidebar"`, `"adjust"` or `"backdrop"`. */
2833
2839
  slot: string;
2834
2840
  /** The exported component the SDK found in the file. */
2835
2841
  component: string;
@@ -328,6 +328,7 @@ const SLOT_LABELS = {
328
328
  bottom: "bottom bar",
329
329
  sidebar: "sidebar",
330
330
  backdrop: "video backdrop",
331
+ adjust: "adjust column",
331
332
  };
332
333
  /** Texts only the test host uses, with their Chinese of SDK 1.5.8. */
333
334
  const TEST_ZH_HANS = {
@@ -550,6 +551,7 @@ export class TestHost {
550
551
  layout = {
551
552
  bottom: null,
552
553
  sidebar: null,
554
+ adjust: null,
553
555
  };
554
556
  /**
555
557
  * Every `osd.show` text as the player shows it (translated with the
@@ -1528,10 +1530,10 @@ export class TestHost {
1528
1530
  * ```
1529
1531
  */
1530
1532
  selectTab(plugin) {
1531
- const slot = this.layout.sidebar;
1533
+ const slot = [this.layout.sidebar, this.layout.adjust].find((each) => each?.panels?.some((entry) => entry.plugin === plugin));
1532
1534
  const panel = slot?.panels?.find((entry) => entry.plugin === plugin);
1533
1535
  if (!slot || !panel)
1534
- throw new Error(`${plugin} has no sidebar tab`);
1536
+ throw new Error(`${plugin} has no tab`);
1535
1537
  slot.owner = panel.owner;
1536
1538
  this.emit("window.state", this.#windowFacts());
1537
1539
  }
@@ -1539,15 +1541,18 @@ export class TestHost {
1539
1541
  const state = this.layout[slot];
1540
1542
  if (state)
1541
1543
  state.panels = [...(state.panels ?? []), panel];
1542
- else
1544
+ else {
1545
+ // The adjust column starts hidden, as the player's.
1546
+ const shown = slot !== "adjust";
1543
1547
  this.layout[slot] = {
1544
1548
  owner: panel.owner,
1545
- preferred: true,
1546
- effective: true,
1547
- reason: null,
1549
+ preferred: shown,
1550
+ effective: shown,
1551
+ reason: shown ? null : "userHidden",
1548
1552
  detached: false,
1549
1553
  panels: [panel],
1550
1554
  };
1555
+ }
1551
1556
  }
1552
1557
  #removePanel(slot, owner) {
1553
1558
  const state = this.layout[slot];
@@ -4247,11 +4252,16 @@ export class TestHost {
4247
4252
  for (const why of this.listeningWhy.get(event) ?? ["a listener"])
4248
4253
  reasons.push(`listens to ${event}: ${why}`);
4249
4254
  // A sidebar panel is never live (player 0.7.3): the current tab keeps
4250
- // the plugin running as busy work, a background tab not at all.
4255
+ // the plugin running as busy work, a background tab not at all. The
4256
+ // adjust column's current tab only while the column is open (0.7.6).
4251
4257
  if (this.panel?.slot === "sidebar") {
4252
4258
  if (this.layout.sidebar?.owner === 1)
4253
4259
  reasons.push("its sidebar tab is the current tab");
4254
4260
  }
4261
+ else if (this.panel?.slot === "adjust") {
4262
+ if (this.layout.adjust?.owner === 1 && this.layout.adjust.preferred)
4263
+ reasons.push("its adjust tab is the current tab");
4264
+ }
4255
4265
  else if (this.panel)
4256
4266
  reasons.push("a mounted panel");
4257
4267
  for (const [id, window] of this.windows)
@@ -4487,8 +4497,11 @@ export class TestHost {
4487
4497
  "ui.mount": (p) => {
4488
4498
  if (this.panel)
4489
4499
  throw new HostError("conflict", "owner already mounted");
4500
+ if (!(String(p.slot) in SLOT_LABELS))
4501
+ throw new HostError("invalid", "unknown mount slot");
4490
4502
  // Like the player: one panel in the bottom bar and the backdrop, any
4491
- // number of tabs in the sidebar (player 0.7.3; 0.4.x to 0.7.2 took 8).
4503
+ // number of tabs in the sidebar (player 0.7.3; 0.4.x to 0.7.2 took 8)
4504
+ // and in the adjust column (player 0.7.6).
4492
4505
  const holder = p.slot === "backdrop"
4493
4506
  ? this.backdrop?.owner
4494
4507
  : p.slot === "bottom"
@@ -4542,7 +4555,10 @@ export class TestHost {
4542
4555
  "ui.menu": (p) => {
4543
4556
  if (!this.panel)
4544
4557
  throw new HostError("unavailable", "panel not shown");
4545
- if (this.panel.slot === "sidebar" && this.layout.sidebar?.owner !== 1)
4558
+ const tabs = this.panel.slot === "sidebar" || this.panel.slot === "adjust"
4559
+ ? this.layout[this.panel.slot]
4560
+ : null;
4561
+ if (tabs && tabs.owner !== 1)
4546
4562
  throw new HostError("unavailable", "the panel's tab is not the current one");
4547
4563
  const items = p.items;
4548
4564
  for (const id of items)
@@ -4559,11 +4575,15 @@ export class TestHost {
4559
4575
  }),
4560
4576
  "layout.set": (p) => {
4561
4577
  const slot = String(p.slot);
4578
+ if (!["bottom", "sidebar", "adjust"].includes(slot))
4579
+ throw new HostError("invalid", "unknown slot");
4562
4580
  const state = this.layout[slot];
4563
4581
  if (!state)
4564
4582
  throw new HostError("missing", "no panel at this position");
4565
4583
  if (p.detached !== undefined && slot === "bottom")
4566
4584
  throw new HostError("invalid", "the bottom bar cannot be detached");
4585
+ if (p.detached === true && slot === "adjust")
4586
+ throw new HostError("invalid", "the adjust column cannot be detached");
4567
4587
  if (p.active !== undefined && p.active !== null) {
4568
4588
  const panel = state.panels?.find((entry) => entry.plugin === p.active);
4569
4589
  if (!panel)
@@ -2578,12 +2578,13 @@ export interface PanelState {
2578
2578
  * else the position's reason ({@link SlotState.reason}).
2579
2579
  */
2580
2580
  reason: SlotState["reason"] | "otherTab";
2581
- /** The sidebar floats in its own window (with all its tabs). */
2581
+ /** The sidebar floats in its own window (with all its tabs); always `false` in the adjust column. */
2582
2582
  detached: boolean;
2583
2583
  }
2584
2584
  /**
2585
- * The bottom bar or the sidebar position (`plugin.panels.bottom`,
2586
- * `plugin.panels.sidebar`), independent of who mounted the panel there.
2585
+ * The bottom bar, the sidebar or the adjust column position
2586
+ * (`plugin.panels.bottom`, `plugin.panels.sidebar`, `plugin.panels.adjust`),
2587
+ * independent of who mounted the panel there.
2587
2588
  *
2588
2589
  * @remarks
2589
2590
  * The position does not belong to the plugin that mounted the panel: any
@@ -2704,7 +2705,9 @@ export interface PanelSlot {
2704
2705
  *
2705
2706
  * @remarks
2706
2707
  * Reads the state from the player first, so it is right even without
2707
- * `events`.
2708
+ * `events`. A sidebar or adjust column wanted shown but collapsed at the
2709
+ * right edge of fullscreen (`reason: "fullscreen"`) slides out instead of
2710
+ * being turned off (SDK 1.6.12; player 0.7.6 pops it out).
2708
2711
  *
2709
2712
  * @capability ui.mount
2710
2713
  *
@@ -3009,7 +3012,10 @@ export interface Panel extends View, Disposable {
3009
3012
  * Reads the state from the player first. When the position is shown
3010
3013
  * (`preferred`) and this panel's tab is the current one, it hides the
3011
3014
  * position; otherwise it does {@link Panel.show}: a panel in a background
3012
- * tab comes to the front instead of the sidebar closing. A "Show list"
3015
+ * tab comes to the front instead of the sidebar closing, and a column
3016
+ * collapsed at the right edge of fullscreen (`reason: "fullscreen"`) slides
3017
+ * out instead of being turned off (SDK 1.6.12; player 0.7.6 pops it out),
3018
+ * like the player's own Alt+1…4 and Adjust button. A "Show list"
3013
3019
  * command checked by `state.preferred && state.active` matches it. Waits
3014
3020
  * for {@link Panel.ready} first.
3015
3021
  *
@@ -4140,6 +4146,38 @@ export interface Panels {
4140
4146
  * takes no pointer or keyboard input. While it is mounted, the player does
4141
4147
  * not show the cover art of audio files itself (from the next file
4142
4148
  * opened on), so the plugin can draw it.
4149
+ * - `"adjust"` (player 0.7.6, SDK 1.6.11): **controls that change what is
4150
+ * playing**: the picture, the sound, the subtitles, the speed (the
4151
+ * player's own Picture, Audio, Subtitles and Playback panels, a colour
4152
+ * grading tool). Several plugins may mount here, one tab each, labelled
4153
+ * `title` (else the plugin's name). Declare `minPlayerVersion: "0.7.6"`:
4154
+ * older players refuse the slot (`invalid`, "unknown mount slot"), and a
4155
+ * mount refused during `activate` fails the activation.
4156
+ *
4157
+ * **Choosing between `"sidebar"` and `"adjust"`.** The slot says what the
4158
+ * panel is for; the player decides how it looks and may change that at any
4159
+ * time. Ask: after the user closes the panel, do they still look at what it
4160
+ * showed? If yes (a list, chapters, search results, another video), it is
4161
+ * content: `"sidebar"`. If it only changes what is playing, it is
4162
+ * `"adjust"`.
4163
+ *
4164
+ * **What the player shows today (may change; do not depend on it).** The
4165
+ * adjust column is a second column between the video and the sidebar
4166
+ * (`[video][adjust][sidebar]`; both can be open, the sidebar always
4167
+ * rightmost), 317 wide by default, 280 to half the window, with text tabs
4168
+ * and the key that opens each tab under its title (the key of the command
4169
+ * your other commands name as their {@link CommandOptions.home | home}). It
4170
+ * has no close or detach button: the bottom bar's Adjust button, your
4171
+ * panel's own command ({@link Panel.toggle}) and the menus open and close
4172
+ * it. It starts closed. In a window too narrow to dock it beside a video at
4173
+ * least 480 wide, it floats over the right of the video, 300 wide; in
4174
+ * fullscreen it slides out from the right edge, left of the sidebar. Its
4175
+ * width, open state, current tab and tab order are remembered across
4176
+ * launches. Its strip is always drawn, so panel coordinates (drops) start
4177
+ * below it, and the bridge global's `tabbed` property, if declared, is
4178
+ * `true`. As in the sidebar, only the current tab's plugin is kept
4179
+ * running while the column is open, and the player compiles a panel the
4180
+ * first time it is shown, not at start-up.
4143
4181
  *
4144
4182
  * @capability ui.mount
4145
4183
  *
@@ -4176,12 +4214,12 @@ export interface Panels {
4176
4214
  *
4177
4215
  * @since 1.0.0
4178
4216
  */
4179
- mount(slot: "bottom" | "sidebar" | "backdrop", file: string, options?: {
4217
+ mount(slot: "bottom" | "sidebar" | "adjust" | "backdrop", file: string, options?: {
4180
4218
  /** The exported component to mount, if the file exports several. */
4181
4219
  component?: string;
4182
4220
  /** The exported global to use as the bridge, if the file exports several. */
4183
4221
  global?: string;
4184
- /** The floating sidebar window's title (at most 128 bytes). */
4222
+ /** The tab label and the floating sidebar window's title (at most 128 bytes). */
4185
4223
  title?: string;
4186
4224
  }): Panel;
4187
4225
  /**
@@ -4193,6 +4231,51 @@ export interface Panels {
4193
4231
  readonly bottom: PanelSlot;
4194
4232
  /** The sidebar position, including its floating state; any plugin may read and toggle it. */
4195
4233
  readonly sidebar: SidebarSlot;
4234
+ /**
4235
+ * The adjust column (player 0.7.6, SDK 1.6.11), whoever mounted there;
4236
+ * any plugin may read and toggle it. The same shape as
4237
+ * {@link Panels.sidebar} without floating: the adjust column never
4238
+ * detaches.
4239
+ *
4240
+ * @remarks
4241
+ * `state` is `null` while no plugin has a panel there. Players before
4242
+ * 0.7.6 build the `plugin` object with their own SDK, which has no
4243
+ * `adjust`: there `plugin.panels.adjust` is `undefined` and reading its
4244
+ * `state` throws a `TypeError`, so declare `"minPlayerVersion": "0.7.6"`
4245
+ * (or test `plugin.panels.adjust` first). The column starts hidden (`preferred: false`, `reason:
4246
+ * "userHidden"`) until the user opens it. To open the column at your own
4247
+ * panel, call {@link Panel.show} (or {@link Panel.toggle}, which closes it
4248
+ * again when your tab is already the current one, the behaviour of the
4249
+ * player's own `Alt+1…4`).
4250
+ *
4251
+ * @example Open the adjust column at this plugin's tab with a key
4252
+ * One command opens the column at the plugin's tab, and pressing it again
4253
+ * while that tab is shown closes the column. The panel's other commands
4254
+ * name it as their `home`, so the player shows its key under the tab and
4255
+ * the command palette knows where they live.
4256
+ * ```ts
4257
+ * const panel = plugin.panels.mount("adjust", "ui/wheels.slint", { title: "Wheels" });
4258
+ * plugin.commands.add({
4259
+ * id: "wheels",
4260
+ * title: "Colour Wheels",
4261
+ * bindings: ["Alt+5"],
4262
+ * // Opens the column at this tab; closes it when this tab is shown.
4263
+ * run: () => panel.toggle(),
4264
+ * });
4265
+ * plugin.commands.add({
4266
+ * id: "wheels-reset",
4267
+ * title: "Reset Colour Wheels",
4268
+ * // Lives in the panel `wheels` opens: its key shows under the tab.
4269
+ * home: "wheels",
4270
+ * run: () => panel.set({ lift: 0 }),
4271
+ * });
4272
+ * ```
4273
+ *
4274
+ * @player 0.7.6 (declare `"minPlayerVersion": "0.7.6"`; older players
4275
+ * have no `adjust`: reading its `state` throws a `TypeError`).
4276
+ * @since 1.6.11
4277
+ */
4278
+ readonly adjust: PanelSlot;
4196
4279
  }
4197
4280
  /**
4198
4281
  * What a timeline mark stands for; readers choose colours by it.
@@ -9417,7 +9500,7 @@ export interface PluginWarning {
9417
9500
  * 0.4.1); a newer player may add codes.
9418
9501
  */
9419
9502
  code: string;
9420
- /** The panel position (`"bottom"`, `"sidebar"`, `"backdrop"`) for `slot-occupied`. */
9503
+ /** The panel position (`"bottom"`, `"backdrop"`; the sidebar and the adjust column hold any number of tabs) for `slot-occupied`. */
9421
9504
  slot?: string;
9422
9505
  /**
9423
9506
  * What was taken, for `resolver-taken`: 「文件类型 .nes」 or the
package/dist/plugin/ui.js CHANGED
@@ -917,7 +917,9 @@ class PanelHandle extends ViewBase {
917
917
  throw failure("invalid", "the player shows the backdrop by itself");
918
918
  await layoutOf(this.scope.runtime).read();
919
919
  const state = this.state;
920
- if (state?.preferred && state.active)
920
+ // Collapsed at the edge of fullscreen (wanted shown, not on screen):
921
+ // slide it out rather than turn it off (player 0.7.6 pops it out).
922
+ if (state?.preferred && state.active && state.reason !== "fullscreen")
921
923
  await layoutOf(this.scope.runtime).set(this.#slot, { visible: false });
922
924
  else
923
925
  await this.show();
@@ -991,7 +993,8 @@ class LayoutService {
991
993
  state = {
992
994
  bottom: null,
993
995
  sidebar: null,
994
- tabs: { bottom: null, sidebar: null },
996
+ adjust: null,
997
+ tabs: { bottom: null, sidebar: null, adjust: null },
995
998
  backdrop: null,
996
999
  };
997
1000
  #listeners = new Set();
@@ -1051,10 +1054,16 @@ class LayoutService {
1051
1054
  active: panel.owner === slot.owner,
1052
1055
  })))
1053
1056
  : null;
1057
+ const adjust = raw.adjust ?? null;
1054
1058
  const next = {
1055
1059
  bottom: view(raw.bottom),
1056
1060
  sidebar: view(raw.sidebar),
1057
- tabs: { bottom: tabs(raw.bottom), sidebar: tabs(raw.sidebar) },
1061
+ adjust: view(adjust),
1062
+ tabs: {
1063
+ bottom: tabs(raw.bottom),
1064
+ sidebar: tabs(raw.sidebar),
1065
+ adjust: tabs(adjust),
1066
+ },
1058
1067
  backdrop: raw.backdrop
1059
1068
  ? Object.freeze({ effective: raw.backdrop.effective === true })
1060
1069
  : null,
@@ -1110,7 +1119,11 @@ function slot(scope, name) {
1110
1119
  async toggle() {
1111
1120
  await service().read();
1112
1121
  const current = service().state[name];
1113
- await service().set(name, { visible: !(current?.preferred ?? true) });
1122
+ // Collapsed at the edge of fullscreen: slide it out (see Panel.toggle).
1123
+ const collapsed = current?.reason === "fullscreen";
1124
+ await service().set(name, {
1125
+ visible: collapsed || !(current?.preferred ?? true),
1126
+ });
1114
1127
  },
1115
1128
  setDetached: (detached) => service().set(name, { detached }),
1116
1129
  activate: (plugin) => service().set(name, { active: plugin }),
@@ -1145,6 +1158,7 @@ export const panels = (scope) => ({
1145
1158
  mount: (position, file, options = {}) => new PanelHandle(scope, position, file, options),
1146
1159
  bottom: slot(scope, "bottom"),
1147
1160
  sidebar: slot(scope, "sidebar"),
1161
+ adjust: slot(scope, "adjust"),
1148
1162
  backdrop: backdrop(scope),
1149
1163
  });
1150
1164
  export const dialogs = (scope) => {
package/dist/wire.d.ts CHANGED
@@ -288,7 +288,10 @@ export interface LayoutPanel {
288
288
  /** The tab label: the mount's `title`, else the plugin's name. */
289
289
  title: string;
290
290
  }
291
- /** One position (bottom bar or sidebar) as `layout.state` reports it. */
291
+ /**
292
+ * One position (the bottom bar, the sidebar or, from player 0.7.6, the
293
+ * adjust column) as `layout.state` reports it.
294
+ */
292
295
  export interface LayoutSlot {
293
296
  /** The current tab's panel (the only panel of the bottom bar). */
294
297
  owner: number;
@@ -696,10 +699,13 @@ export declare function createWire(transport: Transport): {
696
699
  state: () => Promise<{
697
700
  bottom: LayoutSlot | null;
698
701
  sidebar: LayoutSlot | null;
702
+ /** Player 0.7.6 (absent from older players). */
703
+ adjust?: LayoutSlot | null;
699
704
  backdrop?: LayoutBackdrop | null;
700
705
  }>;
701
706
  set: (options: {
702
- slot: "bottom" | "sidebar";
707
+ /** `"adjust"`: player 0.7.6 (older players refuse it with `invalid`). */
708
+ slot: "bottom" | "sidebar" | "adjust";
703
709
  visible?: boolean;
704
710
  detached?: boolean;
705
711
  /** The plugin id of the panel whose tab becomes the current one. */
@@ -727,7 +733,8 @@ export declare function createWire(transport: Transport): {
727
733
  images: string[];
728
734
  }>;
729
735
  mount: (options: {
730
- slot: "bottom" | "sidebar" | "backdrop";
736
+ /** `"adjust"`: player 0.7.6 (older players refuse it with `invalid`). */
737
+ slot: "bottom" | "sidebar" | "backdrop" | "adjust";
731
738
  file: string;
732
739
  component: string;
733
740
  global: string;
package/llms.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  # Vestra Player SDK
2
2
 
3
- > @vestra-player/sdk 1.6.10: write plugins for Vestra Player in TypeScript (plugin API `vestra/1`). A plugin is a folder with `manifest.json` and an entry module whose default export `activate(plugin)` the player calls. The documentation is a tree of short pages read by path, like `go doc`: the same pages and API as https://vestrasys.github.io/vestra-player-sdk/.
3
+ > @vestra-player/sdk 1.6.12: write plugins for Vestra Player in TypeScript (plugin API `vestra/1`). A plugin is a folder with `manifest.json` and an entry module whose default export `activate(plugin)` the player calls. The documentation is a tree of short pages read by path, like `go doc`: the same pages and API as https://vestrasys.github.io/vestra-player-sdk/.
4
4
 
5
5
  ## Read the documentation
6
6
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vestra-player/sdk",
3
- "version": "1.6.10",
3
+ "version": "1.6.12",
4
4
  "description": "Plugin API types, test host and plugin CLI for Vestra Player (apiVersion vestra/1)",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -3,8 +3,8 @@
3
3
  // player's own. Since SDK 1.6.8 (menu redesign step 3a) the player draws
4
4
  // the strip itself, at the right end of the bottom slot, its 40px cells on
5
5
  // the slot's bottom edge (a bar's 40px button row), and also when no plugin
6
- // mounts a bottom bar: `[plugin buttons…][»][Command Palette][Toggle
7
- // Sidebar]`. A bottom bar places nothing: it lays its controls out left of
6
+ // mounts a bottom bar: `[plugin buttons…][»][Command Palette][Adjust]
7
+ // [Toggle Sidebar]`. A bottom bar places nothing: it lays its controls out left of
8
8
  // the strip and says how wide they must stay:
9
9
  // import { BarButtonsHost } from "@vestra/bar-buttons.slint";
10
10
  // export component MyBar inherits Rectangle {
@@ -65,12 +65,16 @@ export global BarButtonsHost {
65
65
  in property <BarButtonItem> toggle;
66
66
  // The command palette (`host/palette`; `token` "" while there is none).
67
67
  in property <BarButtonItem> palette;
68
+ // The adjust column's toggle (`host/adjust`, player 0.7.6; `token` ""
69
+ // while there is none).
70
+ in property <BarButtonItem> adjust;
68
71
  // Set when a (deprecated) `BarButtons` exists in this window.
69
72
  in-out property <bool> placed;
70
73
  // The width the player gives its strip (the player sets it).
71
74
  in-out property <length> room;
72
- // The player's own cells, always shown (step 3b adds Adjust).
73
- private property <int> own: (root.palette.token != "" ? 1 : 0) + (root.toggle.token != "" ? 1 : 0);
75
+ // The player's own cells, always shown: Command Palette, Adjust,
76
+ // Toggle Sidebar.
77
+ private property <int> own: (root.palette.token != "" ? 1 : 0) + (root.adjust.token != "" ? 1 : 0) + (root.toggle.token != "" ? 1 : 0);
74
78
  // The cells a bar must always leave free, 41px each: the player's own,
75
79
  // and the `»` cell whenever any plugin button exists (drawn only on
76
80
  // overflow, but always reserved, so `»` never covers the bar). Never
@@ -52,6 +52,9 @@ export global Icons {
52
52
  // small cross (AI), the default 14 × 12 box; bar buttons use them.
53
53
  if name == "store" { return "M 2.5 4 H 11.5 L 10.9 9.6 Q 10.8 10.5 9.9 10.5 H 4.1 Q 3.2 10.5 3.1 9.6 Z M 5.2 4 V 3.3 A 1.8 1.8 0 0 1 8.8 3.3 V 4"; }
54
54
  if name == "ai" { return "M 6 1.5 Q 6.6 5.4 10.5 6 Q 6.6 6.6 6 10.5 Q 5.4 6.6 1.5 6 Q 5.4 5.4 6 1.5 Z M 11.5 1.2 V 3.8 M 10.2 2.5 H 12.8"; }
55
+ // Three sliders with their knobs (the adjust column, `host/adjust`),
56
+ // the default 14 × 12 box.
57
+ if name == "sliders" { return "M 1.5 2.5 H 7.6 M 10.4 2.5 H 12.5 M 10.4 2.5 A 1.4 1.4 0 1 1 7.6 2.5 A 1.4 1.4 0 1 1 10.4 2.5 Z M 1.5 6 H 3.1 M 5.9 6 H 12.5 M 5.9 6 A 1.4 1.4 0 1 1 3.1 6 A 1.4 1.4 0 1 1 5.9 6 Z M 1.5 9.5 H 6.6 M 9.4 9.5 H 12.5 M 9.4 9.5 A 1.4 1.4 0 1 1 6.6 9.5 A 1.4 1.4 0 1 1 9.4 9.5 Z"; }
55
58
  return "";
56
59
  }
57
60
  // Line width.