@vestra-player/sdk 1.5.11 → 1.5.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 +9 -0
- package/README.md +1 -1
- package/bin/vestra-plugin.mjs +4 -2
- package/dist/i18n-tools.js +61 -9
- package/dist/plugin/testing.d.ts +15 -6
- package/dist/plugin/testing.js +35 -9
- package/llms-full.txt +4 -3
- package/llms.txt +12 -12
- package/package.json +1 -1
- package/slint/vestra/widgets.slint +6 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.5.12
|
|
4
|
+
|
|
5
|
+
**Keys of disabled commands pass on; `i18n check` fails on stale catalogs; translator notes in tables; the Button follows its text.** A fix release; `apiVersion` stays `vestra/1`.
|
|
6
|
+
|
|
7
|
+
- **`TestHost.press` matches the player (0.4.3) for disabled commands.** A disabled command no longer swallows its key: the key goes to the next command bound to it (the one it shadowed in a conflict, then the global scope after a panel binding). With only disabled commands left, the first one with a `reason` shows the reason on the OSD and eats the key; without a reason the key reaches the focused control. `press` now resolves with what happened, `"ran"`, `"reason"` (see `host.osd`) or `"passed"`, instead of rejecting for a disabled command; it still rejects when nothing is bound and when the command that runs is busy. A test that expected `press` to reject for a disabled command now gets `"passed"` or `"reason"`.
|
|
8
|
+
- **`vestra-plugin i18n check` exits with 1** when `locales/messages.pot` is out of date, missing while `.po` files exist, or a `.po` lacks some of the template's messages, so CI catches a forgotten `extract`. Before, it printed these and exited 0. The other findings stay warnings, and `vestra-plugin check` still only prints them.
|
|
9
|
+
- **`TRANSLATORS:` notes on `as const` table entries.** A comment before a property or element of an object or array literal under `as const` (at any depth) now reaches the translators like one at a call site; before, texts extracted through a table lost their notes.
|
|
10
|
+
- **`slint/vestra/widgets.slint`** (the copy of the player's library): `Button`'s preferred width follows its label (the minimum width is unchanged), so a longer translated label widens the button instead of overflowing it.
|
|
11
|
+
|
|
3
12
|
## 1.5.11
|
|
4
13
|
|
|
5
14
|
**Text values: words mixed with data, translated by the player** (player 0.4.3). Additive; `apiVersion` stays `vestra/1`, and plugins that worked need no change. 1.5.9 and 1.5.10 were not published to npm; this release includes them. Round-2 i18n requests R2-audio-1, R2-video-1, R2-subtitles-1/2, R2-playlist-1/2, R2-store-1, R2-workshop-1, R2-nes-1, capture #1, P-2, the info and hotkeys export texts, timeline and subtitle titles: one mechanism. Guide: [Translate a plugin ▸ Text that mixes words and data](docs/how-to/i18n.md#text-that-mixes-words-and-data).
|
package/README.md
CHANGED
|
@@ -83,7 +83,7 @@ Complete, tested plugins in [`examples/`](https://github.com/vestrasys/vestra-pl
|
|
|
83
83
|
| `vestra-plugin build [dir...] [--version x.y.z]` | Writes `dist/`: types stripped, files copied, manifest versioned. |
|
|
84
84
|
| `vestra-plugin pack [dir...] [--version x.y.z] [--out dir]` | Builds, then packs `dist/` as `<id>-<version>.vxp`. |
|
|
85
85
|
| `vestra-plugin i18n extract [dir...] [--locale lang]` | Collects the texts to translate into `locales/`, keeping translations. |
|
|
86
|
-
| `vestra-plugin i18n check [dir...]` | Translation coverage and untranslatable text
|
|
86
|
+
| `vestra-plugin i18n check [dir...]` | Translation coverage and untranslatable text; fails on stale catalogs. |
|
|
87
87
|
|
|
88
88
|
## Compatibility
|
|
89
89
|
|
package/bin/vestra-plugin.mjs
CHANGED
|
@@ -29,7 +29,8 @@
|
|
|
29
29
|
* i18n check coverage per language, placeholder and plural problems, a
|
|
30
30
|
* stale messages.pot, and texts that cannot be translated
|
|
31
31
|
* (composed in TypeScript, not literals, not English, Slint
|
|
32
|
-
* text without @tr)
|
|
32
|
+
* text without @tr) as warnings; fails (exit 1) on a stale
|
|
33
|
+
* messages.pot, a .po missing messages, an unreadable file
|
|
33
34
|
*
|
|
34
35
|
* `check` prints the same i18n warnings when the plugin has locales/;
|
|
35
36
|
* `build` compiles locales/<lang>.po to dist/locales/<lang>.json (the .po
|
|
@@ -430,7 +431,8 @@ async function i18nCheck(target) {
|
|
|
430
431
|
dependencies(target);
|
|
431
432
|
const { warnings, errors } = await i18nReport(target);
|
|
432
433
|
for (const line of warnings) console.log(`${target.name}: i18n: ${line}`);
|
|
433
|
-
|
|
434
|
+
// A stale messages.pot, a .po missing messages, an unreadable file.
|
|
435
|
+
if (errors.length) fail(`${target.name}: i18n: ${errors.join("; ")}`);
|
|
434
436
|
if (!warnings.length) console.log(`${target.name}: i18n: no problems`);
|
|
435
437
|
}
|
|
436
438
|
|
package/dist/i18n-tools.js
CHANGED
|
@@ -100,6 +100,34 @@ function ignored(node, text) {
|
|
|
100
100
|
.slice(node.getFullStart(), node.getStart())
|
|
101
101
|
.includes("i18n-ignore");
|
|
102
102
|
}
|
|
103
|
+
/**
|
|
104
|
+
* A string that is an entry (a property value or an element) of an object
|
|
105
|
+
* or array literal inside `… as const`, at any depth.
|
|
106
|
+
*/
|
|
107
|
+
function inConstTable(node) {
|
|
108
|
+
let current;
|
|
109
|
+
if (ts.isPropertyAssignment(node.parent) && node.parent.initializer === node)
|
|
110
|
+
current = node.parent.parent;
|
|
111
|
+
else if (ts.isArrayLiteralExpression(node.parent))
|
|
112
|
+
current = node.parent;
|
|
113
|
+
else
|
|
114
|
+
return false;
|
|
115
|
+
for (;;) {
|
|
116
|
+
const parent = current.parent;
|
|
117
|
+
if (ts.isAsExpression(parent) &&
|
|
118
|
+
ts.isTypeReferenceNode(parent.type) &&
|
|
119
|
+
ts.isIdentifier(parent.type.typeName) &&
|
|
120
|
+
parent.type.typeName.text === "const")
|
|
121
|
+
return true;
|
|
122
|
+
if (ts.isPropertyAssignment(parent))
|
|
123
|
+
current = parent.parent;
|
|
124
|
+
else if (ts.isArrayLiteralExpression(parent) ||
|
|
125
|
+
ts.isParenthesizedExpression(parent))
|
|
126
|
+
current = parent;
|
|
127
|
+
else
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
103
131
|
/** The statement (or class member) a node belongs to. */
|
|
104
132
|
function statementOf(node) {
|
|
105
133
|
let current = node;
|
|
@@ -111,6 +139,11 @@ function statementOf(node) {
|
|
|
111
139
|
}
|
|
112
140
|
class Extractor {
|
|
113
141
|
found = [];
|
|
142
|
+
/**
|
|
143
|
+
* `TRANSLATORS:` notes on entries of `as const` tables, by text: a table
|
|
144
|
+
* entry is extracted through its literal type, which has no comments.
|
|
145
|
+
*/
|
|
146
|
+
tableNotes = new Map();
|
|
114
147
|
findings = [];
|
|
115
148
|
#checker;
|
|
116
149
|
constructor(checker) {
|
|
@@ -302,6 +335,20 @@ class Extractor {
|
|
|
302
335
|
const visit = (node) => {
|
|
303
336
|
if (ts.isCallExpression(node))
|
|
304
337
|
this.#call(node, file, source, text);
|
|
338
|
+
if ((ts.isStringLiteral(node) ||
|
|
339
|
+
ts.isNoSubstitutionTemplateLiteral(node)) &&
|
|
340
|
+
inConstTable(node)) {
|
|
341
|
+
// The comment sits before the entry: the property, or the element.
|
|
342
|
+
const entry = ts.isPropertyAssignment(node.parent) ? node.parent : node;
|
|
343
|
+
const notes = notesOf(entry, text);
|
|
344
|
+
if (notes.length) {
|
|
345
|
+
const known = this.tableNotes.get(node.text) ?? [];
|
|
346
|
+
for (const note of notes)
|
|
347
|
+
if (!known.includes(note))
|
|
348
|
+
known.push(note);
|
|
349
|
+
this.tableNotes.set(node.text, known);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
305
352
|
// A text value written as data: `{ $vestraText: "…" }`.
|
|
306
353
|
if (ts.isPropertyAssignment(node) &&
|
|
307
354
|
(ts.isIdentifier(node.name) || ts.isStringLiteral(node.name)) &&
|
|
@@ -589,7 +636,10 @@ export function extract(options) {
|
|
|
589
636
|
}
|
|
590
637
|
if (occurrence.plural !== undefined && message.plural === undefined)
|
|
591
638
|
message.plural = occurrence.plural;
|
|
592
|
-
|
|
639
|
+
const tableNotes = occurrence.context === undefined
|
|
640
|
+
? (extractor.tableNotes.get(occurrence.id) ?? [])
|
|
641
|
+
: [];
|
|
642
|
+
for (const note of [...occurrence.notes, ...tableNotes])
|
|
593
643
|
if (!message.notes.includes(note))
|
|
594
644
|
message.notes.push(note);
|
|
595
645
|
if (!message.references.includes(reference))
|
|
@@ -683,24 +733,26 @@ export function checkCatalogs(directory, messages, findings) {
|
|
|
683
733
|
const pot = join(locales, "messages.pot");
|
|
684
734
|
const keys = (entries) => new Set(entries.map((e) => `${keyOf(e.context, e.id)}\u0000${e.plural ?? ""}`));
|
|
685
735
|
const wanted = keys(messages);
|
|
736
|
+
const files = existsSync(locales)
|
|
737
|
+
? readdirSync(locales)
|
|
738
|
+
.filter((name) => name.endsWith(".po"))
|
|
739
|
+
.sort()
|
|
740
|
+
: [];
|
|
741
|
+
// A stale template or a .po without some of its messages fails
|
|
742
|
+
// `i18n check` (an error); `check` prints it like the warnings.
|
|
686
743
|
if (!existsSync(pot))
|
|
687
|
-
warnings.push("locales/messages.pot is missing: run vestra-plugin i18n extract");
|
|
744
|
+
(files.length ? errors : warnings).push("locales/messages.pot is missing: run vestra-plugin i18n extract");
|
|
688
745
|
else {
|
|
689
746
|
try {
|
|
690
747
|
const have = keys(parsePo(readFileSync(pot, "utf8")).entries);
|
|
691
748
|
const same = have.size === wanted.size && [...wanted].every((key) => have.has(key));
|
|
692
749
|
if (!same)
|
|
693
|
-
|
|
750
|
+
errors.push("locales/messages.pot is out of date: run vestra-plugin i18n extract");
|
|
694
751
|
}
|
|
695
752
|
catch (error) {
|
|
696
753
|
errors.push(`locales/messages.pot: ${error.message}`);
|
|
697
754
|
}
|
|
698
755
|
}
|
|
699
|
-
const files = existsSync(locales)
|
|
700
|
-
? readdirSync(locales)
|
|
701
|
-
.filter((name) => name.endsWith(".po"))
|
|
702
|
-
.sort()
|
|
703
|
-
: [];
|
|
704
756
|
for (const name of files) {
|
|
705
757
|
const language = name.slice(0, -3);
|
|
706
758
|
if (language === "en") {
|
|
@@ -729,7 +781,7 @@ export function checkCatalogs(directory, messages, findings) {
|
|
|
729
781
|
const inFile = keys(live);
|
|
730
782
|
const missing = [...wanted].filter((key) => !inFile.has(key)).length;
|
|
731
783
|
if (missing)
|
|
732
|
-
|
|
784
|
+
errors.push(`locales/${name}: ${missing} ${missing === 1 ? "message is" : "messages are"} not in this file: run vestra-plugin i18n extract`);
|
|
733
785
|
for (const entry of live) {
|
|
734
786
|
entry.strings.forEach((text, form) => {
|
|
735
787
|
if (text === "")
|
package/dist/plugin/testing.d.ts
CHANGED
|
@@ -1242,11 +1242,20 @@ export declare class TestHost implements Transport {
|
|
|
1242
1242
|
failStream(producerId: number, message: string): void;
|
|
1243
1243
|
/**
|
|
1244
1244
|
* Press a binding like the host's table would: panel-focus bindings first
|
|
1245
|
-
* when `panel` is true, then global ones;
|
|
1246
|
-
*
|
|
1247
|
-
*
|
|
1248
|
-
* (
|
|
1249
|
-
*
|
|
1245
|
+
* when `panel` is true, then global ones; within a scope the earliest
|
|
1246
|
+
* registration first (the player's own commands before the plugin's), as
|
|
1247
|
+
* the conflict rule orders them. A disabled command lets the key pass to
|
|
1248
|
+
* the next command bound to it (SDK 1.5.12, as player 0.4.3): the one it
|
|
1249
|
+
* shadowed, then the global scope. The first enabled one runs; `press`
|
|
1250
|
+
* resolves after it ran (`"ran"`), or rejects when it is busy. When only
|
|
1251
|
+
* disabled commands hold the key, the first one with a `reason` shows the
|
|
1252
|
+
* reason on the OSD and eats the key (`"reason"`, see `host.osd`);
|
|
1253
|
+
* without a reason the key goes on to the focused control (`"passed"`,
|
|
1254
|
+
* nothing runs). Rejects when nothing is bound. Bindings use the
|
|
1255
|
+
* reference syntax (`"Space"`, `"Ctrl+O"`, `"Mouse:WheelUp"`), and user
|
|
1256
|
+
* overrides in `bindings.overlay` apply.
|
|
1257
|
+
*
|
|
1258
|
+
* @returns What the key did: `"ran"`, `"reason"` or `"passed"`.
|
|
1250
1259
|
*
|
|
1251
1260
|
* @example
|
|
1252
1261
|
* ```ts
|
|
@@ -1256,7 +1265,7 @@ export declare class TestHost implements Transport {
|
|
|
1256
1265
|
*/
|
|
1257
1266
|
press(binding: string, options?: {
|
|
1258
1267
|
panel?: boolean;
|
|
1259
|
-
}): Promise<
|
|
1268
|
+
}): Promise<"ran" | "reason" | "passed">;
|
|
1260
1269
|
/**
|
|
1261
1270
|
* Bindings declared by more than one command, each as
|
|
1262
1271
|
* `[binding, winner, ...ignored]` (command ids; core actions as
|
package/dist/plugin/testing.js
CHANGED
|
@@ -1749,11 +1749,20 @@ export class TestHost {
|
|
|
1749
1749
|
}
|
|
1750
1750
|
/**
|
|
1751
1751
|
* Press a binding like the host's table would: panel-focus bindings first
|
|
1752
|
-
* when `panel` is true, then global ones;
|
|
1753
|
-
*
|
|
1754
|
-
*
|
|
1755
|
-
* (
|
|
1756
|
-
*
|
|
1752
|
+
* when `panel` is true, then global ones; within a scope the earliest
|
|
1753
|
+
* registration first (the player's own commands before the plugin's), as
|
|
1754
|
+
* the conflict rule orders them. A disabled command lets the key pass to
|
|
1755
|
+
* the next command bound to it (SDK 1.5.12, as player 0.4.3): the one it
|
|
1756
|
+
* shadowed, then the global scope. The first enabled one runs; `press`
|
|
1757
|
+
* resolves after it ran (`"ran"`), or rejects when it is busy. When only
|
|
1758
|
+
* disabled commands hold the key, the first one with a `reason` shows the
|
|
1759
|
+
* reason on the OSD and eats the key (`"reason"`, see `host.osd`);
|
|
1760
|
+
* without a reason the key goes on to the focused control (`"passed"`,
|
|
1761
|
+
* nothing runs). Rejects when nothing is bound. Bindings use the
|
|
1762
|
+
* reference syntax (`"Space"`, `"Ctrl+O"`, `"Mouse:WheelUp"`), and user
|
|
1763
|
+
* overrides in `bindings.overlay` apply.
|
|
1764
|
+
*
|
|
1765
|
+
* @returns What the key did: `"ran"`, `"reason"` or `"passed"`.
|
|
1757
1766
|
*
|
|
1758
1767
|
* @example
|
|
1759
1768
|
* ```ts
|
|
@@ -1763,11 +1772,28 @@ export class TestHost {
|
|
|
1763
1772
|
*/
|
|
1764
1773
|
async press(binding, options = {}) {
|
|
1765
1774
|
const claims = this.#claims();
|
|
1766
|
-
const
|
|
1767
|
-
claims.get(`
|
|
1768
|
-
|
|
1775
|
+
const ids = [
|
|
1776
|
+
...(options.panel ? (claims.get(`panel ${binding}`) ?? []) : []),
|
|
1777
|
+
...(claims.get(`global ${binding}`) ?? []),
|
|
1778
|
+
];
|
|
1779
|
+
if (!ids.length)
|
|
1769
1780
|
throw new Error(`test host: nothing is bound to ${binding}`);
|
|
1770
|
-
|
|
1781
|
+
const command = (id) => this.coreCommands.get(id) ?? this.command(id);
|
|
1782
|
+
const enabled = ids.find((id) => command(id).state.enabled);
|
|
1783
|
+
if (enabled !== undefined) {
|
|
1784
|
+
await command(enabled).run();
|
|
1785
|
+
return "ran";
|
|
1786
|
+
}
|
|
1787
|
+
for (const id of ids) {
|
|
1788
|
+
const reason = this.commands.has(id)
|
|
1789
|
+
? command(id).display.reason
|
|
1790
|
+
: command(id).state.reason;
|
|
1791
|
+
if (typeof reason === "string" && reason !== "") {
|
|
1792
|
+
this.osd.push(reason);
|
|
1793
|
+
return "reason";
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1796
|
+
return "passed";
|
|
1771
1797
|
}
|
|
1772
1798
|
/**
|
|
1773
1799
|
* Bindings declared by more than one command, each as
|
package/llms-full.txt
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# @vestra-player/sdk 1.5.
|
|
1
|
+
# @vestra-player/sdk 1.5.12: full reference
|
|
2
2
|
|
|
3
3
|
> Everything needed to write a Vestra Player plugin (plugin API `vestra/1`): the guides, every public declaration of `@vestra-player/sdk` and `@vestra-player/sdk/testing` with its documentation, and the complete source of the example plugins. Generated from the SDK's sources by scripts/llms.mjs; the short index is llms.txt.
|
|
4
4
|
|
|
@@ -5230,11 +5230,12 @@ class TestHost implements Transport {
|
|
|
5230
5230
|
*/
|
|
5231
5231
|
failStream(producerId: number, message: string): void;
|
|
5232
5232
|
/**
|
|
5233
|
-
* Press a binding like the host's table would: panel-focus bindings first when `panel` is true, then global ones; the earliest registration
|
|
5233
|
+
* Press a binding like the host's table would: panel-focus bindings first when `panel` is true, then global ones; within a scope the earliest registration first (the player's own commands before the plugin's), as the conflict rule orders them. A disabled command lets the key pass to the next command bound to it (SDK 1.5.12, as player 0.4.3): the one it shadowed, then the global scope. The first enabled one runs; `press` resolves after it ran (`"ran"`), or rejects when it is busy. When only disabled commands hold the key, the first one with a `reason` shows the reason on the OSD and eats the key (`"reason"`, see `host.osd`); without a reason the key goes on to the focused control (`"passed"`, nothing runs). Rejects when nothing is bound. Bindings use the reference syntax (`"Space"`, `"Ctrl+O"`, `"Mouse:WheelUp"`), and user overrides in `bindings.overlay` apply.
|
|
5234
|
+
* @returns What the key did: `"ran"`, `"reason"` or `"passed"`.
|
|
5234
5235
|
*/
|
|
5235
5236
|
press(binding: string, options?: {
|
|
5236
5237
|
panel?: boolean;
|
|
5237
|
-
}): Promise<
|
|
5238
|
+
}): Promise<"ran" | "reason" | "passed">;
|
|
5238
5239
|
/** Bindings declared by more than one command, each as `[binding, winner, ...ignored]` (command ids; core actions as `host/<id>`). Empty when there are none. */
|
|
5239
5240
|
get conflicts(): string[][];
|
|
5240
5241
|
/** The binding overlay `plugin.bindings.set` writes (`"<plugin>/<command>"` to inputs; preset it to simulate saved overrides). `press` honours it. */
|
package/llms.txt
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Vestra Player SDK
|
|
2
2
|
|
|
3
|
-
> @vestra-player/sdk 1.5.
|
|
3
|
+
> @vestra-player/sdk 1.5.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; it runs in its own Node.js process. llms-full.txt, next to this file, has every declaration, the guides and the examples; L<n> below are its line numbers.
|
|
4
4
|
|
|
5
5
|
**How plugins run**
|
|
6
6
|
|
|
@@ -121,7 +121,7 @@ test("shows the position", async () => {
|
|
|
121
121
|
- `vestra-plugin build [dir...] [--version x.y.z]`: Writes `dist/`: types stripped, files copied, manifest versioned.
|
|
122
122
|
- `vestra-plugin pack [dir...] [--version x.y.z] [--out dir]`: Builds, then packs `dist/` as `<id>-<version>.vxp`.
|
|
123
123
|
- `vestra-plugin i18n extract [dir...] [--locale lang]`: Collects the texts to translate into `locales/`, keeping translations.
|
|
124
|
-
- `vestra-plugin i18n check [dir...]`: Translation coverage and untranslatable text
|
|
124
|
+
- `vestra-plugin i18n check [dir...]`: Translation coverage and untranslatable text; fails on stale catalogs.
|
|
125
125
|
- `vestra-player --plugin <dir>` runs a plugin from source, no build needed.
|
|
126
126
|
|
|
127
127
|
**Error codes** (`error.code`; branch on it, never on `message`)
|
|
@@ -153,7 +153,7 @@ test("shows the position", async () => {
|
|
|
153
153
|
- [How-to: Keep a slider responsive while dragging](https://vestrasys.github.io/vestra-player-sdk/documents/Keep_a_slider_responsive_while_dragging.html): L627–801
|
|
154
154
|
- [How-to: Remember a window's state between runs](https://vestrasys.github.io/vestra-player-sdk/documents/Remember_a_window_s_state_between_runs.html): L802–931
|
|
155
155
|
- [API: @vestra-player/sdk](https://vestrasys.github.io/vestra-player-sdk/modules/_vestra-player_sdk.html): L932–4427
|
|
156
|
-
- [API: @vestra-player/sdk/testing](https://vestrasys.github.io/vestra-player-sdk/modules/_vestra-player_sdk_testing.html) (TestHost, startPlugin): L4428–
|
|
156
|
+
- [API: @vestra-player/sdk/testing](https://vestrasys.github.io/vestra-player-sdk/modules/_vestra-player_sdk_testing.html) (TestHost, startPlugin): L4428–5538
|
|
157
157
|
|
|
158
158
|
## API
|
|
159
159
|
|
|
@@ -193,15 +193,15 @@ Where each module of `plugin` is declared:
|
|
|
193
193
|
|
|
194
194
|
Complete plugins with tests, in [examples/](https://github.com/vestrasys/vestra-player-sdk/tree/main/examples) and in llms-full.txt:
|
|
195
195
|
|
|
196
|
-
- hello: The README Quick start: one menu command and an on-screen message.
|
|
197
|
-
- blur: A tool window with one slider that blurs the playing video.
|
|
198
|
-
- signal-generator: Generates its own video and audio: colour bars and a 1 kHz tone.
|
|
199
|
-
- bookmark: A tool window bound to the playback state: remember the position and jump back to it.
|
|
200
|
-
- panel-menu: A sidebar panel with its own right-click menu.
|
|
201
|
-
- menus: Radio menu items, separator groups and a menu item whose title changes.
|
|
202
|
-
- theme-widgets: A tool window drawn with the player's theme and widgets.
|
|
203
|
-
- markdown: A window that shows a Markdown document.
|
|
204
|
-
- i18n: A plugin in the player's language: English source text, a Chinese translation.
|
|
196
|
+
- hello: The README Quick start: one menu command and an on-screen message. L5955–6044
|
|
197
|
+
- blur: A tool window with one slider that blurs the playing video. L6045–6306
|
|
198
|
+
- signal-generator: Generates its own video and audio: colour bars and a 1 kHz tone. L6307–6639
|
|
199
|
+
- bookmark: A tool window bound to the playback state: remember the position and jump back to it. L6640–6824
|
|
200
|
+
- panel-menu: A sidebar panel with its own right-click menu. L6825–7030
|
|
201
|
+
- menus: Radio menu items, separator groups and a menu item whose title changes. L7031–7190
|
|
202
|
+
- theme-widgets: A tool window drawn with the player's theme and widgets. L7191–7354
|
|
203
|
+
- markdown: A window that shows a Markdown document. L7355–7548
|
|
204
|
+
- i18n: A plugin in the player's language: English source text, a Chinese translation. L7549–7716
|
|
205
205
|
|
|
206
206
|
## Optional
|
|
207
207
|
|
package/package.json
CHANGED
|
@@ -81,13 +81,17 @@ export component Button {
|
|
|
81
81
|
min-height: root.icon-only ? root.icon-height : root.small ? Theme.button-small-height : Theme.button-height;
|
|
82
82
|
min-width: root.icon-only ? root.icon-width : root.small ? Theme.button-small-min-width : Theme.button-min-width;
|
|
83
83
|
preferred-height: self.min-height;
|
|
84
|
+
// A longer (translated) label widens the button instead of overflowing it.
|
|
85
|
+
preferred-width: root.icon-only ? self.min-width
|
|
86
|
+
: max(self.min-width, label.preferred-width + 2 * Theme.button-padding);
|
|
84
87
|
accessible-role: button;
|
|
85
88
|
accessible-label: root.text;
|
|
86
89
|
accessible-enabled: root.enabled;
|
|
87
90
|
accessible-checkable: root.icon-only;
|
|
88
91
|
accessible-checked: root.checked;
|
|
89
92
|
accessible-action-default => { if root.enabled { root.clicked(); } }
|
|
90
|
-
|
|
93
|
+
Rectangle {
|
|
94
|
+
visible: !root.icon-only;
|
|
91
95
|
border-radius: Theme.radius-control;
|
|
92
96
|
background: !root.enabled ? Theme.button-disabled
|
|
93
97
|
: touch.pressed ? Theme.button-press
|
|
@@ -96,7 +100,7 @@ export component Button {
|
|
|
96
100
|
padding-left: Theme.button-padding;
|
|
97
101
|
padding-right: Theme.button-padding;
|
|
98
102
|
alignment: center;
|
|
99
|
-
Text {
|
|
103
|
+
label := Text {
|
|
100
104
|
text: root.text;
|
|
101
105
|
color: root.enabled ? Theme.text-list : Theme.text-dim;
|
|
102
106
|
font-size: Theme.font-body;
|