@wildwinter/app-shell 0.57.0 → 0.58.1
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/README.md +195 -0
- package/dist/chunk-4JQWNC6Z.js +319 -0
- package/dist/chunk-4JQWNC6Z.js.map +1 -0
- package/dist/chunk-ICYPXUNA.js +8 -0
- package/dist/chunk-ICYPXUNA.js.map +1 -0
- package/dist/{chunk-XJM733E6.js → chunk-OO6O4LEL.js} +6 -2
- package/dist/chunk-OO6O4LEL.js.map +1 -0
- package/dist/{chunk-CPNTROWD.js → chunk-OZQ2BYXH.js} +13 -4
- package/dist/chunk-OZQ2BYXH.js.map +1 -0
- package/dist/{chunk-5BKGDTYD.js → chunk-RMQNBWLZ.js} +3 -3
- package/dist/{chunk-5BKGDTYD.js.map → chunk-RMQNBWLZ.js.map} +1 -1
- package/dist/index.cjs +334 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/property-list.cjs.map +1 -1
- package/dist/property-list.js +2 -2
- package/dist/tool-window.cjs +11 -2
- package/dist/tool-window.cjs.map +1 -1
- package/dist/tool-window.d.cts +10 -4
- package/dist/tool-window.d.ts +10 -4
- package/dist/tool-window.js +1 -1
- package/dist/updater.cjs.map +1 -1
- package/dist/updater.js +1 -1
- package/dist/value-control.cjs +4 -0
- package/dist/value-control.cjs.map +1 -1
- package/dist/value-control.d.cts +4 -4
- package/dist/value-control.d.ts +4 -4
- package/dist/value-control.js +1 -1
- package/dist/value-rows.cjs +8 -1
- package/dist/value-rows.cjs.map +1 -1
- package/dist/value-rows.js +5 -2
- package/dist/value-rows.js.map +1 -1
- package/dist/view-client.cjs +172 -0
- package/dist/view-client.cjs.map +1 -0
- package/dist/view-client.d.cts +46 -0
- package/dist/view-client.d.ts +46 -0
- package/dist/view-client.js +165 -0
- package/dist/view-client.js.map +1 -0
- package/dist/view-host-web.cjs +327 -0
- package/dist/view-host-web.cjs.map +1 -0
- package/dist/view-host-web.d.cts +61 -0
- package/dist/view-host-web.d.ts +61 -0
- package/dist/view-host-web.js +4 -0
- package/dist/view-host-web.js.map +1 -0
- package/dist/view-host.cjs +532 -0
- package/dist/view-host.cjs.map +1 -0
- package/dist/view-host.d.cts +178 -0
- package/dist/view-host.d.ts +178 -0
- package/dist/view-host.js +426 -0
- package/dist/view-host.js.map +1 -0
- package/dist/view-preload.cjs +16 -0
- package/dist/view-preload.cjs.map +1 -0
- package/dist/view-preload.js +8 -0
- package/dist/view-preload.js.map +1 -0
- package/dist/view-protocol.cjs +12 -0
- package/dist/view-protocol.cjs.map +1 -0
- package/dist/view-protocol.d.cts +257 -0
- package/dist/view-protocol.d.ts +257 -0
- package/dist/view-protocol.js +3 -0
- package/dist/view-protocol.js.map +1 -0
- package/package.json +25 -4
- package/dist/chunk-CPNTROWD.js.map +0 -1
- package/dist/chunk-XJM733E6.js.map +0 -1
package/README.md
CHANGED
|
@@ -347,6 +347,201 @@ extension's per-character performance, say), too big for an inspector's column:
|
|
|
347
347
|
window that shows whatever the editor has selected. The other direction from
|
|
348
348
|
`followButton` ("Follow in the editor"), so it has its own words.
|
|
349
349
|
|
|
350
|
+
## v0.58 (tier 2 view extensions)
|
|
351
|
+
|
|
352
|
+
**A view** is a web component an extension's installed package supplies (a
|
|
353
|
+
performance sheet, say), which an app opens in a floating tool window of its
|
|
354
|
+
own. The window is sandboxed, on an in-memory session partition of its own,
|
|
355
|
+
with a preload that does one thing: it hands the view a `MessagePort` straight
|
|
356
|
+
to the editor's renderer. Main hands the pair over and then stays out of the
|
|
357
|
+
path; the editor's renderer answers the view from the app's own model, unsaved
|
|
358
|
+
edits included. Needs toolkit 0.2.6 (`views` and `reads` in the manifest).
|
|
359
|
+
|
|
360
|
+
| Subpath | Where | What |
|
|
361
|
+
| --- | --- | --- |
|
|
362
|
+
| `./view-protocol` | both ends | The messages, `VIEW_API`, the port's channel and message names. Types and constants only. |
|
|
363
|
+
| `./view-client` | the view | `connect()`, typed requests, events, the theme, and keys. For view authors. |
|
|
364
|
+
| `./view-host` | main | Packages (read, install, list, choose a version), the scheme, and `openViewWindow`. Imports electron. |
|
|
365
|
+
| `./view-host-web` | the editor's renderer | `hostViews` (every open view), `hostView`, `onViewPort`, `readTheme`. Also in the root entry. |
|
|
366
|
+
| `./view-preload.cjs` | the view window | The view preload, which `openViewWindow` uses by default. |
|
|
367
|
+
|
|
368
|
+
### Packages
|
|
369
|
+
|
|
370
|
+
An extension's local package is a folder or an `npm pack` tarball with one
|
|
371
|
+
`<id>.extension.json` at its top level and its view files. `readExtensionPackage(path)`
|
|
372
|
+
reads either (no node_modules, no dotfiles) and says why not in a sentence.
|
|
373
|
+
`installExtensionPackage(pkg, dir)` puts it in `<dir>/<id>/<version>/`
|
|
374
|
+
(`dir` is `userData/view-extensions`), replacing the same version whole.
|
|
375
|
+
`installedExtensions(dir)` lists what's there, and `resolveInstalled(dir, id, pinned)`
|
|
376
|
+
picks the version for a project: the pinned one, else the highest with the same
|
|
377
|
+
major version and a warning to show ("This project expects Pattershot 0.3.0;
|
|
378
|
+
0.4.1 is installed."), else none and why ("Pattershot isn't installed on this
|
|
379
|
+
machine.").
|
|
380
|
+
|
|
381
|
+
### Wiring an app, in order
|
|
382
|
+
|
|
383
|
+
1. **Register the scheme**, in the app's one `protocol.registerSchemesAsPrivileged`
|
|
384
|
+
call (Electron keeps only the last call, so list the app's other schemes there too):
|
|
385
|
+
|
|
386
|
+
```ts
|
|
387
|
+
import { viewSchemePrivileges } from "@wildwinter/app-shell/view-host";
|
|
388
|
+
protocol.registerSchemesAsPrivileged([assetScheme, viewSchemePrivileges("storylet-view")]);
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
2. **The scheme is served per view session** by `openViewWindow` (it calls
|
|
392
|
+
`serveViews` and `guardViewSession` on the view's partition the first time).
|
|
393
|
+
`<scheme>://<id>/?view=<view>` is a generated page that loads the view's
|
|
394
|
+
`entry` as a module and fills the window with its `element`; any other path
|
|
395
|
+
is that package's own file, confined to its folder; everything else is a 404.
|
|
396
|
+
Every response carries `VIEW_CSP` as a header. The session cancels every
|
|
397
|
+
request off the scheme and refuses every permission.
|
|
398
|
+
|
|
399
|
+
3. **The editor's preload forwards its port.** Apps allow no preload import but
|
|
400
|
+
electron, so it's a snippet rather than an import:
|
|
401
|
+
|
|
402
|
+
```js
|
|
403
|
+
ipcRenderer.on("wildwinter-view:port", (event, meta) => {
|
|
404
|
+
window.postMessage({ type: "wildwinter-view:port", meta }, "*", event.ports);
|
|
405
|
+
});
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
4. **Open the view** from main:
|
|
409
|
+
|
|
410
|
+
```ts
|
|
411
|
+
import { openViewWindow, resolveInstalled } from "@wildwinter/app-shell/view-host";
|
|
412
|
+
|
|
413
|
+
const choice = await resolveInstalled(viewsDir, "pattershot", project.pin("pattershot"));
|
|
414
|
+
if (!choice.installed) return showInstallPrompt(choice.why);
|
|
415
|
+
const view = openViewWindow({
|
|
416
|
+
installed: choice.installed, view: "sheet", host: "patter", scheme: "patter-view",
|
|
417
|
+
editor: mainWindow, pinnedVersion: project.pin("pattershot"),
|
|
418
|
+
title: choice.warning ? `Performance sheet (${choice.warning})` : undefined,
|
|
419
|
+
rect: savedWindowRect(store.sheet.bounds, DEF, MIN), pinTo: () => mainWindow, pinned: true,
|
|
420
|
+
});
|
|
421
|
+
// on a project change: view.close()
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
The window closes with the editor. Each time the view's page loads, and each
|
|
425
|
+
time the editor's does while the view is open, both ends get a fresh pair.
|
|
426
|
+
When the view window closes, the editor gets its meta again with `closed: true`
|
|
427
|
+
and no port, through the same snippet.
|
|
428
|
+
An app that bundles its main process passes `preload` (the path of
|
|
429
|
+
`@wildwinter/app-shell/view-preload.cjs`), since the default is found beside
|
|
430
|
+
the shell's own built module.
|
|
431
|
+
|
|
432
|
+
5. **Host them in the editor's renderer** with an adapter over the app's model:
|
|
433
|
+
|
|
434
|
+
```ts
|
|
435
|
+
import { hostViews } from "@wildwinter/app-shell";
|
|
436
|
+
|
|
437
|
+
const views = hostViews((meta) => {
|
|
438
|
+
const manifest = manifestOf(meta.extension);
|
|
439
|
+
if (!manifest) return null; // refused: its port is closed
|
|
440
|
+
return { adapter, reads: manifest.reads ?? [], names: { extension: manifest.name, view: viewName(meta) } };
|
|
441
|
+
});
|
|
442
|
+
// then, as things change, once for every open view: views.changed(ids), .selection(), .theme(), .writable()
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
`hostViews` keeps one `hostView` per view window: a new port for a window
|
|
446
|
+
replaces its handle (one end reloaded), and the window's `closed` drops it.
|
|
447
|
+
`onViewPort` and `hostView` are there for an app that wants them apart.
|
|
448
|
+
|
|
449
|
+
`hostView` answers nothing before a hello for `VIEW_API` (another API is
|
|
450
|
+
refused with a sentence, and the port closed); checks every request's shape
|
|
451
|
+
(`bad-request`); keeps each node's `ext` to the view's own extension and its
|
|
452
|
+
manifest's `reads`, children included; refuses a write `read-only` with the
|
|
453
|
+
adapter's why; refuses `media` as `unsupported` when the adapter has none;
|
|
454
|
+
answers a throw as `failed`; and debounces `changed` (100 ms, node lists merged,
|
|
455
|
+
"all" wins). The theme it sends is `readTheme()`: the palette tokens (`--bg`,
|
|
456
|
+
`--surface`, `--card`, `--ink`, `--muted`, `--line`, `--line-soft`, `--accent`,
|
|
457
|
+
`--accent-soft`, `--on-accent`, `--danger`, `--warn`, `--ok`, the three fonts,
|
|
458
|
+
and `--char-0` to `--char-11`) and the shell's grammar (radii, shadows, motion,
|
|
459
|
+
scrim, scrollbar), with the scheme from the root's `color-scheme`.
|
|
460
|
+
|
|
461
|
+
### The node envelope
|
|
462
|
+
|
|
463
|
+
Every node a view reads is `{ id, kind, parent, label, host, ext, children? }`
|
|
464
|
+
in both hosts: `kind` the manifest's node kind, `host` that host's own fields
|
|
465
|
+
for the kind (below), and `ext` extension data by extension id, the view's own
|
|
466
|
+
and its `reads`, nothing else. A write is a list of `{ node, field, value }`
|
|
467
|
+
edits to the view's own extension, applied as one undo step with its label or
|
|
468
|
+
refused whole. `field` is one of the extension's top-level fields, never a path
|
|
469
|
+
inside one: to change one key of a map, write the whole map. An absent `value`
|
|
470
|
+
clears the field. A write's edits are all on the project node or none are, since
|
|
471
|
+
the project's data and a scene's undo separately in Patterpad; `hostView` refuses
|
|
472
|
+
a write that mixes them. A node whose data for the view's extension somebody else
|
|
473
|
+
holds carries `held`, the sentence saying who, so the view can show it read-only;
|
|
474
|
+
a write touching it is refused `held`.
|
|
475
|
+
|
|
476
|
+
### Writing a view
|
|
477
|
+
|
|
478
|
+
```ts
|
|
479
|
+
import { connect } from "@wildwinter/app-shell/view-client";
|
|
480
|
+
|
|
481
|
+
const app = await connect(); // as the module loads: the port arrives once
|
|
482
|
+
const sel = await app.selection();
|
|
483
|
+
const node = sel.path[0] && await app.node(sel.path[0].id);
|
|
484
|
+
app.on("changed", () => redraw());
|
|
485
|
+
await app.write("Set shot", [{ node: node.id, field: "shot", value: "wide" }]);
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
The client sets the theme's tokens on the page's root (`var(--ink)` works) and
|
|
489
|
+
its `color-scheme`, and keeps them current. A key combination with Cmd or Ctrl
|
|
490
|
+
that the view didn't handle (didn't `preventDefault`) goes to the app in the
|
|
491
|
+
portable spelling, so Mod+Z is the editor's undo; Mod+Z, Mod+Shift+Z and Mod+Y
|
|
492
|
+
never run the page's own undo. Plain typing never leaves the view. A refused
|
|
493
|
+
request rejects with a `ViewRefusalError` carrying the `refusal`.
|
|
494
|
+
|
|
495
|
+
**The probe view** (`test/fixtures/probe-view/`) is the shell's test view, there
|
|
496
|
+
for hosts' tests to borrow: a `probe` extension targeting both hosts with one
|
|
497
|
+
`note` field, and a plain-JS view (`inspect`, `<probe-view>`) that speaks the
|
|
498
|
+
protocol itself, shows the selection's innermost node and its note, and writes
|
|
499
|
+
the note.
|
|
500
|
+
|
|
501
|
+
### Host fields
|
|
502
|
+
|
|
503
|
+
What each host puts in a node's `host`, by node kind.
|
|
504
|
+
|
|
505
|
+
**Patterpad** (host `patter`). `gameData` is always an object, `{}` when empty.
|
|
506
|
+
`padAfterDefault` is the pause a node sets for what's inside it, present only
|
|
507
|
+
when it sets one.
|
|
508
|
+
|
|
509
|
+
| Kind | `host` fields |
|
|
510
|
+
| --- | --- |
|
|
511
|
+
| `project` | `name`, `cast` (`[{ token, name }]`, the shown name in the source language), `qualifiers` (`[{ gameId, name }]`), `padAfterDefault` (resolved), `localiseDialogueText` |
|
|
512
|
+
| `scene` | `title`, `address`, `gameData`, `padAfterDefault` |
|
|
513
|
+
| `block` | `name`, `address`, `gameData`, `padAfterDefault` |
|
|
514
|
+
| `group` | `role` (`choice`, `branch`, `sequence`, or `group`), `gameData`, `padAfterDefault`. Not a manifest kind, so it carries no extension data. A choice's children are its options. |
|
|
515
|
+
| `option` | `text` (the choice text), `gameData`, `padAfterDefault`. Its children are its prompt beat, then its run. |
|
|
516
|
+
| `snippet` | `gameData`, `padAfterDefault`, `padAfterEffective` |
|
|
517
|
+
| `line` | `character`, `qualifier` (or null), `direction` (or null), `text` (source language), `padAfter` (effective, as Patterplay resolves it), `padInherited` (where it came from: `snippet`, `group`, `option`, `block`, `scene`, `project`, or `default`; absent when the line sets its own), `gameData` |
|
|
518
|
+
| `text` | `text`, `action`, `padAfter`, `padInherited`, `gameData` |
|
|
519
|
+
| `gameEvent` | `gameData` |
|
|
520
|
+
|
|
521
|
+
Patterpad answers `media` (a take's measured length, else the estimate Play
|
|
522
|
+
uses) and changes only the scene open in the editor and the project node; a
|
|
523
|
+
write to another scene is refused `read-only`, saying so.
|
|
524
|
+
|
|
525
|
+
**Storyletter** (host `storylets`). An absent optional string is `null`; `label`
|
|
526
|
+
is the title, else the gameId.
|
|
527
|
+
|
|
528
|
+
| Kind | `host` fields |
|
|
529
|
+
| --- | --- |
|
|
530
|
+
| `project` | `name` |
|
|
531
|
+
| `box` | `gameId`, `title`, `purpose` |
|
|
532
|
+
| `deck` | `gameId`, `title`, `purpose`, `gate`, `box` |
|
|
533
|
+
| `card` | `gameId`, `title`, `purpose`, `condition`, `priority`, `redraw`, `copies`, `tags` (`{ group: [tag gameIds] }`), `fields` (`{ name: value }`), `deck`, `box` |
|
|
534
|
+
| `outcome` | `gameId`, `title`, `purpose`, `gate`, `fields`, `card`, `deck`, `box` |
|
|
535
|
+
| `tagGroup` | `gameId`, `tags` (gameIds), `spatial`, `box` (null for the map's group) |
|
|
536
|
+
| `tag` | `gameId`, `group` |
|
|
537
|
+
| `handTemplate` | `gameId`, `title`, `purpose`, `slots`, `bindings`, `box` |
|
|
538
|
+
| `hand` | `gameId`, `title`, `template`, `slots`, `tags` (`{ group: tag }`), `box` |
|
|
539
|
+
|
|
540
|
+
Children: the project's boxes, then the map's tag group; a box's decks, tag
|
|
541
|
+
groups, hand templates, and hands; a deck's cards; a card's outcomes; a tag
|
|
542
|
+
group's tags. The selection has no outcome or tag, since Storyletter has no focus
|
|
543
|
+
for either. Storyletter has no `media`.
|
|
544
|
+
|
|
350
545
|
## Roadmap
|
|
351
546
|
|
|
352
547
|
Later slices lift the rest of the common shell from Patterpad (canonical),
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
import { VIEW_PORT_MESSAGE, VIEW_API } from './chunk-ICYPXUNA.js';
|
|
2
|
+
|
|
3
|
+
// src/view-host-web.ts
|
|
4
|
+
var PALETTE_TOKENS = [
|
|
5
|
+
"--bg",
|
|
6
|
+
"--surface",
|
|
7
|
+
"--card",
|
|
8
|
+
"--ink",
|
|
9
|
+
"--muted",
|
|
10
|
+
"--line",
|
|
11
|
+
"--line-soft",
|
|
12
|
+
"--accent",
|
|
13
|
+
"--accent-soft",
|
|
14
|
+
"--on-accent",
|
|
15
|
+
"--danger",
|
|
16
|
+
"--warn",
|
|
17
|
+
"--ok",
|
|
18
|
+
"--font-ui",
|
|
19
|
+
"--font-read",
|
|
20
|
+
"--font-mono",
|
|
21
|
+
...Array.from({ length: 12 }, (_, i) => `--char-${i}`)
|
|
22
|
+
];
|
|
23
|
+
var GRAMMAR_TOKENS = [
|
|
24
|
+
"--color-scheme",
|
|
25
|
+
"--chrome-size",
|
|
26
|
+
"--scrim",
|
|
27
|
+
"--shadow-1",
|
|
28
|
+
"--shadow-2",
|
|
29
|
+
"--shadow-3",
|
|
30
|
+
"--ease-standard",
|
|
31
|
+
"--dur-quick",
|
|
32
|
+
"--dur-base",
|
|
33
|
+
"--dur-settle",
|
|
34
|
+
"--dur-slow",
|
|
35
|
+
"--dur-panel",
|
|
36
|
+
"--dur-exit",
|
|
37
|
+
"--radius-sm",
|
|
38
|
+
"--radius-md",
|
|
39
|
+
"--radius-lg",
|
|
40
|
+
"--radius-pill",
|
|
41
|
+
"--scroll-thumb",
|
|
42
|
+
"--scroll-thumb-hover"
|
|
43
|
+
];
|
|
44
|
+
var VIEW_THEME_TOKENS = [...PALETTE_TOKENS, ...GRAMMAR_TOKENS];
|
|
45
|
+
function readTheme(root = document.documentElement, tokens = VIEW_THEME_TOKENS) {
|
|
46
|
+
const style = getComputedStyle(root);
|
|
47
|
+
const out = {};
|
|
48
|
+
for (const name of tokens) {
|
|
49
|
+
const value = style.getPropertyValue(name).trim();
|
|
50
|
+
if (value !== "") out[name] = value;
|
|
51
|
+
}
|
|
52
|
+
const declared = (style.getPropertyValue("color-scheme") || style.colorScheme || "").trim();
|
|
53
|
+
const words = declared.split(/\s+/);
|
|
54
|
+
let scheme = "light";
|
|
55
|
+
if (declared === "dark") scheme = "dark";
|
|
56
|
+
else if (words.includes("light") && words.includes("dark")) {
|
|
57
|
+
scheme = typeof matchMedia === "function" && matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
|
58
|
+
}
|
|
59
|
+
return { scheme, tokens: out };
|
|
60
|
+
}
|
|
61
|
+
function onViewPort(fn, target = window, closed) {
|
|
62
|
+
const listener = (e) => {
|
|
63
|
+
if (e.source !== target) return;
|
|
64
|
+
const data = e.data;
|
|
65
|
+
if (!data || typeof data !== "object" || data.type !== VIEW_PORT_MESSAGE) return;
|
|
66
|
+
const port = e.ports?.[0];
|
|
67
|
+
const meta = data.meta;
|
|
68
|
+
if (!meta || typeof meta.window !== "number" || typeof meta.extension !== "string" || typeof meta.view !== "string") return;
|
|
69
|
+
if (meta.closed === true) {
|
|
70
|
+
closed?.(meta);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (!port) return;
|
|
74
|
+
fn(port, meta);
|
|
75
|
+
};
|
|
76
|
+
target.addEventListener("message", listener);
|
|
77
|
+
return () => target.removeEventListener("message", listener);
|
|
78
|
+
}
|
|
79
|
+
var NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
80
|
+
var isObj = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
81
|
+
var isId = (v) => typeof v === "string" && v !== "";
|
|
82
|
+
function isJson(v, depth = 0) {
|
|
83
|
+
if (depth > 64) return false;
|
|
84
|
+
if (v === null || typeof v === "string" || typeof v === "boolean") return true;
|
|
85
|
+
if (typeof v === "number") return Number.isFinite(v);
|
|
86
|
+
if (Array.isArray(v)) return v.every((x) => isJson(x, depth + 1));
|
|
87
|
+
if (isObj(v)) return Object.getPrototypeOf(v) === Object.prototype && Object.values(v).every((x) => isJson(x, depth + 1));
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
var bad = (message) => ({ code: "bad-request", message });
|
|
91
|
+
function argsProblem(op, args) {
|
|
92
|
+
switch (op) {
|
|
93
|
+
case "project":
|
|
94
|
+
case "selection":
|
|
95
|
+
return null;
|
|
96
|
+
case "node":
|
|
97
|
+
if (!isId(args["id"])) return "A node request names the node by its id.";
|
|
98
|
+
if (args["children"] !== void 0 && typeof args["children"] !== "boolean") return "A node request's children is true or false.";
|
|
99
|
+
return null;
|
|
100
|
+
case "media":
|
|
101
|
+
if (!Array.isArray(args["ids"]) || !args["ids"].every(isId)) return "A media request lists the lines' ids.";
|
|
102
|
+
return null;
|
|
103
|
+
case "select":
|
|
104
|
+
return isId(args["id"]) ? null : "A select request names the node by its id.";
|
|
105
|
+
case "write": {
|
|
106
|
+
if (typeof args["label"] !== "string" || args["label"].trim() === "") return "A write has a label, for the undo step.";
|
|
107
|
+
const edits = args["edits"];
|
|
108
|
+
if (!Array.isArray(edits)) return "A write lists its edits.";
|
|
109
|
+
for (const e of edits) {
|
|
110
|
+
if (!isObj(e) || !isId(e["node"])) return "Each edit names its node by id.";
|
|
111
|
+
if (typeof e["field"] !== "string" || !NAME.test(e["field"])) return "Each edit names one of the extension's fields, never a path inside one.";
|
|
112
|
+
if ("value" in e && e["value"] !== void 0 && !isJson(e["value"])) return `The value for ${e["field"]} isn't plain JSON.`;
|
|
113
|
+
}
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
var OPS = ["project", "node", "selection", "media", "write", "select"];
|
|
119
|
+
function hostView(port, adapter, opts) {
|
|
120
|
+
const { meta } = opts;
|
|
121
|
+
const ext = meta.extension;
|
|
122
|
+
const allowed = /* @__PURE__ */ new Set([ext, ...opts.reads]);
|
|
123
|
+
const theme = opts.theme ?? (() => readTheme());
|
|
124
|
+
const debounceMs = opts.debounceMs ?? 100;
|
|
125
|
+
let welcomed = false;
|
|
126
|
+
let closed = false;
|
|
127
|
+
let pending = null;
|
|
128
|
+
let timer;
|
|
129
|
+
const post = (msg) => {
|
|
130
|
+
if (!closed) port.postMessage(msg);
|
|
131
|
+
};
|
|
132
|
+
const emit = (name, value) => {
|
|
133
|
+
if (welcomed) post({ t: "event", name, value });
|
|
134
|
+
};
|
|
135
|
+
const filterNode = (node) => {
|
|
136
|
+
const keptExt = {};
|
|
137
|
+
for (const [id, data] of Object.entries(node.ext ?? {})) if (allowed.has(id)) keptExt[id] = data;
|
|
138
|
+
const base = { id: node.id, kind: node.kind, parent: node.parent, label: node.label, host: node.host ?? {}, ext: keptExt };
|
|
139
|
+
const out = typeof node.held === "string" ? { ...base, held: node.held } : base;
|
|
140
|
+
return node.children ? { ...out, children: node.children.map(filterNode) } : out;
|
|
141
|
+
};
|
|
142
|
+
const answer = async (op, args) => {
|
|
143
|
+
switch (op) {
|
|
144
|
+
case "project":
|
|
145
|
+
return { value: filterNode(await adapter.project()) };
|
|
146
|
+
case "node": {
|
|
147
|
+
const node = await adapter.node(args["id"], args["children"] === true);
|
|
148
|
+
return { value: node ? filterNode(node) : null };
|
|
149
|
+
}
|
|
150
|
+
case "selection":
|
|
151
|
+
return { value: await adapter.selection() };
|
|
152
|
+
case "media":
|
|
153
|
+
if (!adapter.media) return { refusal: { code: "unsupported", message: "This app has no recordings or timings for lines." } };
|
|
154
|
+
return { value: await adapter.media(args["ids"]) };
|
|
155
|
+
case "select":
|
|
156
|
+
return { value: await adapter.select(args["id"]) };
|
|
157
|
+
case "write": {
|
|
158
|
+
const writable = adapter.writable(ext);
|
|
159
|
+
if (writable.readOnly) return { refusal: { code: "read-only", message: writable.why ?? "This project is read-only just now." } };
|
|
160
|
+
const edits = args["edits"].map((e) => "value" in e && e.value !== void 0 ? { node: e.node, field: e.field, value: e.value } : { node: e.node, field: e.field });
|
|
161
|
+
if (edits.length === 0) return { value: { written: 0 } };
|
|
162
|
+
if (edits.length > 1) {
|
|
163
|
+
const projectId = (await adapter.project()).id;
|
|
164
|
+
const onProject = edits.filter((e) => e.node === projectId).length;
|
|
165
|
+
if (onProject > 0 && onProject < edits.length) {
|
|
166
|
+
return { refusal: bad("A write changes the project or other nodes, not both: they undo separately. Make two writes.") };
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
const refused = await adapter.write(ext, args["label"], edits);
|
|
170
|
+
if (refused) return { refusal: refused };
|
|
171
|
+
return { value: { written: edits.length } };
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
const request = async (msg) => {
|
|
176
|
+
const id = msg["id"];
|
|
177
|
+
if (typeof id !== "number" || !Number.isInteger(id)) return;
|
|
178
|
+
const reply = (r) => post("refusal" in r ? { t: "reply", id, ok: false, refusal: r.refusal } : { t: "reply", id, ok: true, value: r.value });
|
|
179
|
+
const op = msg["op"];
|
|
180
|
+
if (typeof op !== "string" || !OPS.includes(op)) {
|
|
181
|
+
reply({ refusal: bad(`There's no request called ${JSON.stringify(op)}.`) });
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
const args = msg["args"] ?? {};
|
|
185
|
+
if (!isObj(args)) {
|
|
186
|
+
reply({ refusal: bad("A request's args are an object.") });
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
const problem = argsProblem(op, args);
|
|
190
|
+
if (problem) {
|
|
191
|
+
reply({ refusal: bad(problem) });
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
try {
|
|
195
|
+
reply(await answer(op, args));
|
|
196
|
+
} catch (e) {
|
|
197
|
+
reply({ refusal: { code: "failed", message: `The app couldn't do that: ${e instanceof Error ? e.message : String(e)}` } });
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
const hello = (msg) => {
|
|
201
|
+
const api = msg["api"];
|
|
202
|
+
if (api !== VIEW_API) {
|
|
203
|
+
const who = `${opts.names?.extension ?? ext}'s ${opts.names?.view ?? meta.view}`;
|
|
204
|
+
const message = typeof api === "number" ? `${who} was built for view API ${api}; this app serves ${VIEW_API}.` : `${who} didn't say which view API it was built for; this app serves ${VIEW_API}.`;
|
|
205
|
+
post({ t: "refused", message });
|
|
206
|
+
close(false);
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
const welcome = {
|
|
210
|
+
t: "welcome",
|
|
211
|
+
api: VIEW_API,
|
|
212
|
+
host: adapter.host,
|
|
213
|
+
extension: { id: ext, version: meta.version, ...meta.pinned !== void 0 ? { pinned: meta.pinned } : {} },
|
|
214
|
+
view: meta.view,
|
|
215
|
+
settings: adapter.settings(ext),
|
|
216
|
+
theme: theme(),
|
|
217
|
+
writable: adapter.writable(ext)
|
|
218
|
+
};
|
|
219
|
+
welcomed = true;
|
|
220
|
+
post(welcome);
|
|
221
|
+
};
|
|
222
|
+
const onMessage = (e) => {
|
|
223
|
+
const msg = e.data;
|
|
224
|
+
if (closed || !isObj(msg)) return;
|
|
225
|
+
if (!welcomed) {
|
|
226
|
+
if (msg["t"] === "hello") hello(msg);
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
if (msg["t"] === "request") void request(msg);
|
|
230
|
+
else if (msg["t"] === "key" && typeof msg["combo"] === "string") adapter.key?.(msg["combo"]);
|
|
231
|
+
};
|
|
232
|
+
port.addEventListener("message", onMessage);
|
|
233
|
+
port.start();
|
|
234
|
+
const flush = () => {
|
|
235
|
+
timer = void 0;
|
|
236
|
+
const nodes = pending;
|
|
237
|
+
pending = null;
|
|
238
|
+
if (nodes === null) return;
|
|
239
|
+
emit("changed", { nodes: nodes === "all" ? "all" : [...nodes] });
|
|
240
|
+
};
|
|
241
|
+
function close(sayClosing = true) {
|
|
242
|
+
if (closed) return;
|
|
243
|
+
if (sayClosing) emit("closing", {});
|
|
244
|
+
clearTimeout(timer);
|
|
245
|
+
closed = true;
|
|
246
|
+
port.removeEventListener("message", onMessage);
|
|
247
|
+
port.close();
|
|
248
|
+
}
|
|
249
|
+
return {
|
|
250
|
+
changed(nodes) {
|
|
251
|
+
if (closed || !welcomed) return;
|
|
252
|
+
if (nodes === "all" || pending === "all") pending = "all";
|
|
253
|
+
else {
|
|
254
|
+
pending ??= /* @__PURE__ */ new Set();
|
|
255
|
+
for (const n of nodes) pending.add(n);
|
|
256
|
+
}
|
|
257
|
+
if (timer === void 0) timer = setTimeout(flush, debounceMs);
|
|
258
|
+
},
|
|
259
|
+
selection() {
|
|
260
|
+
if (closed || !welcomed) return;
|
|
261
|
+
void Promise.resolve().then(() => adapter.selection()).then((s) => emit("selection", s), () => {
|
|
262
|
+
});
|
|
263
|
+
},
|
|
264
|
+
theme() {
|
|
265
|
+
if (!closed) emit("theme", theme());
|
|
266
|
+
},
|
|
267
|
+
writable() {
|
|
268
|
+
if (!closed) emit("writable", adapter.writable(ext));
|
|
269
|
+
},
|
|
270
|
+
close: () => close(true)
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
function hostViews(setup, opts = {}) {
|
|
274
|
+
const handles = /* @__PURE__ */ new Map();
|
|
275
|
+
const drop = (meta) => {
|
|
276
|
+
handles.get(meta.window)?.close();
|
|
277
|
+
handles.delete(meta.window);
|
|
278
|
+
};
|
|
279
|
+
const stop = onViewPort((port, meta) => {
|
|
280
|
+
drop(meta);
|
|
281
|
+
const s = setup(meta);
|
|
282
|
+
if (!s) {
|
|
283
|
+
port.close();
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
handles.set(meta.window, hostView(port, s.adapter, {
|
|
287
|
+
meta,
|
|
288
|
+
reads: s.reads,
|
|
289
|
+
...s.names !== void 0 ? { names: s.names } : {},
|
|
290
|
+
...opts.theme !== void 0 ? { theme: opts.theme } : {},
|
|
291
|
+
...opts.debounceMs !== void 0 ? { debounceMs: opts.debounceMs } : {}
|
|
292
|
+
}));
|
|
293
|
+
}, opts.target ?? window, drop);
|
|
294
|
+
const each = (f) => {
|
|
295
|
+
for (const h of handles.values()) f(h);
|
|
296
|
+
};
|
|
297
|
+
return {
|
|
298
|
+
get size() {
|
|
299
|
+
return handles.size;
|
|
300
|
+
},
|
|
301
|
+
changed: (nodes) => each((h) => h.changed(nodes)),
|
|
302
|
+
selection: () => each((h) => h.selection()),
|
|
303
|
+
theme: () => each((h) => h.theme()),
|
|
304
|
+
writable: () => each((h) => h.writable()),
|
|
305
|
+
close: () => {
|
|
306
|
+
each((h) => h.close());
|
|
307
|
+
handles.clear();
|
|
308
|
+
},
|
|
309
|
+
dispose: () => {
|
|
310
|
+
stop();
|
|
311
|
+
each((h) => h.close());
|
|
312
|
+
handles.clear();
|
|
313
|
+
}
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
export { VIEW_THEME_TOKENS, hostView, hostViews, onViewPort, readTheme };
|
|
318
|
+
//# sourceMappingURL=chunk-4JQWNC6Z.js.map
|
|
319
|
+
//# sourceMappingURL=chunk-4JQWNC6Z.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/view-host-web.ts"],"names":[],"mappings":"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","file":"chunk-4JQWNC6Z.js","sourcesContent":["// ---------------------------------------------------------------------------\n// The view host, RENDERER half (tier 2 view extensions): `hostView` answers a\n// view over its MessagePort from an adapter over the app's own model, in the\n// editor's renderer, which holds what the writer sees. `onViewPort` takes the\n// ports main hands the editor (through the editor preload's snippet, README\n// v0.58), and `readTheme` reads the palette a view draws in.\n//\n// What hostView guarantees, whatever the adapter does: nothing answered\n// before a hello for this API; every request's shape checked; `ext` kept to\n// the view's own extension and its manifest's `reads`; writes refused whole\n// when not writable; a throw answered as `failed`; `changed` debounced.\n// ---------------------------------------------------------------------------\n\nimport { VIEW_API, VIEW_PORT_MESSAGE } from \"./view-protocol.js\";\nimport type {\n HostToView, ViewAdapter, ViewEdit, ViewEventName, ViewEvents, ViewHandle, ViewJson, ViewNode, ViewOp, ViewPortMeta,\n ViewRefusal, ViewTheme, ViewWelcome,\n} from \"./view-protocol.js\";\n\n/** The palette names the family's apps define, read for a view's theme. */\nconst PALETTE_TOKENS = [\n \"--bg\", \"--surface\", \"--card\", \"--ink\", \"--muted\", \"--line\", \"--line-soft\", \"--accent\", \"--accent-soft\", \"--on-accent\",\n \"--danger\", \"--warn\", \"--ok\", \"--font-ui\", \"--font-read\", \"--font-mono\",\n ...Array.from({ length: 12 }, (_, i) => `--char-${i}`),\n];\n/** The shell's grammar tokens (tokens.css) a view can use too. The animations are left out: their keyframes\n * live in the app's stylesheets, not the view's. */\nconst GRAMMAR_TOKENS = [\n \"--color-scheme\", \"--chrome-size\", \"--scrim\", \"--shadow-1\", \"--shadow-2\", \"--shadow-3\",\n \"--ease-standard\", \"--dur-quick\", \"--dur-base\", \"--dur-settle\", \"--dur-slow\", \"--dur-panel\", \"--dur-exit\",\n \"--radius-sm\", \"--radius-md\", \"--radius-lg\", \"--radius-pill\", \"--scroll-thumb\", \"--scroll-thumb-hover\",\n];\n/** Every token `readTheme` reads, in order. */\nexport const VIEW_THEME_TOKENS: readonly string[] = [...PALETTE_TOKENS, ...GRAMMAR_TOKENS];\n\n/**\n * The app's theme as a view needs it: each of `VIEW_THEME_TOKENS` the root sets, computed, and the scheme from\n * the root's computed `color-scheme` (dark when it's exactly \"dark\"; with both, the OS's preference; else light).\n */\nexport function readTheme(root: Element = document.documentElement, tokens: readonly string[] = VIEW_THEME_TOKENS): ViewTheme {\n const style = getComputedStyle(root);\n const out: Record<string, string> = {};\n for (const name of tokens) {\n const value = style.getPropertyValue(name).trim();\n if (value !== \"\") out[name] = value;\n }\n const declared = (style.getPropertyValue(\"color-scheme\") || (style as unknown as { colorScheme?: string }).colorScheme || \"\").trim();\n const words = declared.split(/\\s+/);\n let scheme: \"light\" | \"dark\" = \"light\";\n if (declared === \"dark\") scheme = \"dark\";\n else if (words.includes(\"light\") && words.includes(\"dark\")) {\n scheme = typeof matchMedia === \"function\" && matchMedia(\"(prefers-color-scheme: dark)\").matches ? \"dark\" : \"light\";\n }\n return { scheme, tokens: out };\n}\n\n/**\n * Take the ports main hands this window for views: the editor preload posts each with its meta (README v0.58).\n * A second port for the same `meta.window` means one end reloaded: close the old `hostView` handle and host the\n * new port. Returns the unsubscribe.\n */\nexport function onViewPort(\n fn: (port: MessagePort, meta: ViewPortMeta) => void, target: Window = window, closed?: (meta: ViewPortMeta) => void,\n): () => void {\n const listener = (e: MessageEvent): void => {\n if (e.source !== target) return;\n const data = e.data as { type?: unknown; meta?: unknown } | null;\n if (!data || typeof data !== \"object\" || data.type !== VIEW_PORT_MESSAGE) return;\n const port = e.ports?.[0];\n const meta = data.meta as ViewPortMeta | undefined;\n if (!meta || typeof meta.window !== \"number\" || typeof meta.extension !== \"string\" || typeof meta.view !== \"string\") return;\n if (meta.closed === true) { closed?.(meta); return; }\n if (!port) return;\n fn(port, meta);\n };\n target.addEventListener(\"message\", listener);\n return () => target.removeEventListener(\"message\", listener);\n}\n\nexport interface HostViewOptions {\n /** What main said about this view with its port. */\n meta: ViewPortMeta;\n /** The manifest's `reads`: the other extensions whose data the view sees. */\n reads: readonly string[];\n /** The theme to send; `readTheme()` by default. */\n theme?: () => ViewTheme;\n /** The extension's and the view's names, for the refusal sentence; the ids by default. */\n names?: { extension?: string; view?: string };\n /** How long `changed` waits to gather nodes before it's sent, in ms (default 100). */\n debounceMs?: number;\n}\n\nconst NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;\nconst isObj = (v: unknown): v is Record<string, unknown> => typeof v === \"object\" && v !== null && !Array.isArray(v);\nconst isId = (v: unknown): v is string => typeof v === \"string\" && v !== \"\";\n\nfunction isJson(v: unknown, depth = 0): v is ViewJson {\n if (depth > 64) return false;\n if (v === null || typeof v === \"string\" || typeof v === \"boolean\") return true;\n if (typeof v === \"number\") return Number.isFinite(v);\n if (Array.isArray(v)) return v.every((x) => isJson(x, depth + 1));\n if (isObj(v)) return Object.getPrototypeOf(v) === Object.prototype && Object.values(v).every((x) => isJson(x, depth + 1));\n return false;\n}\n\nconst bad = (message: string): ViewRefusal => ({ code: \"bad-request\", message });\n\n/** The args' shape for each op, or why not. */\nfunction argsProblem(op: ViewOp, args: Record<string, unknown>): string | null {\n switch (op) {\n case \"project\": case \"selection\": return null;\n case \"node\":\n if (!isId(args[\"id\"])) return \"A node request names the node by its id.\";\n if (args[\"children\"] !== undefined && typeof args[\"children\"] !== \"boolean\") return \"A node request's children is true or false.\";\n return null;\n case \"media\":\n if (!Array.isArray(args[\"ids\"]) || !args[\"ids\"].every(isId)) return \"A media request lists the lines' ids.\";\n return null;\n case \"select\":\n return isId(args[\"id\"]) ? null : \"A select request names the node by its id.\";\n case \"write\": {\n if (typeof args[\"label\"] !== \"string\" || args[\"label\"].trim() === \"\") return \"A write has a label, for the undo step.\";\n const edits = args[\"edits\"];\n if (!Array.isArray(edits)) return \"A write lists its edits.\";\n for (const e of edits) {\n if (!isObj(e) || !isId(e[\"node\"])) return \"Each edit names its node by id.\";\n if (typeof e[\"field\"] !== \"string\" || !NAME.test(e[\"field\"])) return \"Each edit names one of the extension's fields, never a path inside one.\";\n if (\"value\" in e && e[\"value\"] !== undefined && !isJson(e[\"value\"])) return `The value for ${e[\"field\"]} isn't plain JSON.`;\n }\n return null;\n }\n }\n}\n\nconst OPS: readonly ViewOp[] = [\"project\", \"node\", \"selection\", \"media\", \"write\", \"select\"];\n\n/**\n * Answer a view over its port from the app's adapter. Returns the handle the app calls as its model, selection,\n * theme, or locks change, and `close()` when the view goes (or a new port for it arrives).\n */\nexport function hostView(port: MessagePort, adapter: ViewAdapter, opts: HostViewOptions): ViewHandle {\n const { meta } = opts;\n const ext = meta.extension;\n const allowed = new Set([ext, ...opts.reads]);\n const theme = opts.theme ?? (() => readTheme());\n const debounceMs = opts.debounceMs ?? 100;\n let welcomed = false;\n let closed = false;\n let pending: Set<string> | \"all\" | null = null;\n let timer: ReturnType<typeof setTimeout> | undefined;\n\n const post = (msg: HostToView): void => { if (!closed) port.postMessage(msg); };\n const emit = <K extends ViewEventName>(name: K, value: ViewEvents[K]): void => {\n if (welcomed) post({ t: \"event\", name, value });\n };\n\n const filterNode = (node: ViewNode): ViewNode => {\n const keptExt: Record<string, ViewNode[\"ext\"][string]> = {};\n for (const [id, data] of Object.entries(node.ext ?? {})) if (allowed.has(id)) keptExt[id] = data;\n const base: ViewNode = { id: node.id, kind: node.kind, parent: node.parent, label: node.label, host: node.host ?? {}, ext: keptExt };\n const out: ViewNode = typeof node.held === \"string\" ? { ...base, held: node.held } : base;\n return node.children ? { ...out, children: node.children.map(filterNode) } : out;\n };\n\n const answer = async (op: ViewOp, args: Record<string, unknown>): Promise<{ value: unknown } | { refusal: ViewRefusal }> => {\n switch (op) {\n case \"project\": return { value: filterNode(await adapter.project()) };\n case \"node\": {\n const node = await adapter.node(args[\"id\"] as string, args[\"children\"] === true);\n return { value: node ? filterNode(node) : null };\n }\n case \"selection\": return { value: await adapter.selection() };\n case \"media\":\n if (!adapter.media) return { refusal: { code: \"unsupported\", message: \"This app has no recordings or timings for lines.\" } };\n return { value: await adapter.media(args[\"ids\"] as string[]) };\n case \"select\": return { value: await adapter.select(args[\"id\"] as string) };\n case \"write\": {\n const writable = adapter.writable(ext);\n if (writable.readOnly) return { refusal: { code: \"read-only\", message: writable.why ?? \"This project is read-only just now.\" } };\n const edits = (args[\"edits\"] as ViewEdit[]).map((e) => (\"value\" in e && e.value !== undefined\n ? { node: e.node, field: e.field, value: e.value } : { node: e.node, field: e.field }));\n if (edits.length === 0) return { value: { written: 0 } };\n if (edits.length > 1) {\n const projectId = (await adapter.project()).id;\n const onProject = edits.filter((e) => e.node === projectId).length;\n if (onProject > 0 && onProject < edits.length) {\n return { refusal: bad(\"A write changes the project or other nodes, not both: they undo separately. Make two writes.\") };\n }\n }\n const refused = await adapter.write(ext, args[\"label\"] as string, edits);\n if (refused) return { refusal: refused };\n return { value: { written: edits.length } };\n }\n }\n };\n\n const request = async (msg: Record<string, unknown>): Promise<void> => {\n const id = msg[\"id\"];\n if (typeof id !== \"number\" || !Number.isInteger(id)) return; // nothing to answer to\n const reply = (r: { value: unknown } | { refusal: ViewRefusal }): void =>\n post(\"refusal\" in r ? { t: \"reply\", id, ok: false, refusal: r.refusal } : { t: \"reply\", id, ok: true, value: r.value });\n const op = msg[\"op\"];\n if (typeof op !== \"string\" || !OPS.includes(op as ViewOp)) {\n reply({ refusal: bad(`There's no request called ${JSON.stringify(op)}.`) });\n return;\n }\n const args = msg[\"args\"] ?? {};\n if (!isObj(args)) { reply({ refusal: bad(\"A request's args are an object.\") }); return; }\n const problem = argsProblem(op as ViewOp, args);\n if (problem) { reply({ refusal: bad(problem) }); return; }\n try {\n reply(await answer(op as ViewOp, args));\n } catch (e) {\n reply({ refusal: { code: \"failed\", message: `The app couldn't do that: ${e instanceof Error ? e.message : String(e)}` } });\n }\n };\n\n const hello = (msg: Record<string, unknown>): void => {\n const api = msg[\"api\"];\n if (api !== VIEW_API) {\n const who = `${opts.names?.extension ?? ext}'s ${opts.names?.view ?? meta.view}`;\n const message = typeof api === \"number\"\n ? `${who} was built for view API ${api}; this app serves ${VIEW_API}.`\n : `${who} didn't say which view API it was built for; this app serves ${VIEW_API}.`;\n post({ t: \"refused\", message });\n close(false);\n return;\n }\n const welcome: ViewWelcome = {\n t: \"welcome\", api: VIEW_API, host: adapter.host,\n extension: { id: ext, version: meta.version, ...(meta.pinned !== undefined ? { pinned: meta.pinned } : {}) },\n view: meta.view,\n settings: adapter.settings(ext),\n theme: theme(),\n writable: adapter.writable(ext),\n };\n welcomed = true;\n post(welcome);\n };\n\n const onMessage = (e: MessageEvent): void => {\n const msg = e.data as unknown;\n if (closed || !isObj(msg)) return;\n if (!welcomed) {\n // Nothing is answered before the hello.\n if (msg[\"t\"] === \"hello\") hello(msg);\n return;\n }\n if (msg[\"t\"] === \"request\") void request(msg);\n else if (msg[\"t\"] === \"key\" && typeof msg[\"combo\"] === \"string\") adapter.key?.(msg[\"combo\"]);\n };\n port.addEventListener(\"message\", onMessage as EventListener);\n port.start();\n\n const flush = (): void => {\n timer = undefined;\n const nodes = pending;\n pending = null;\n if (nodes === null) return;\n emit(\"changed\", { nodes: nodes === \"all\" ? \"all\" : [...nodes] });\n };\n\n function close(sayClosing = true): void {\n if (closed) return;\n if (sayClosing) emit(\"closing\", {});\n clearTimeout(timer);\n closed = true;\n port.removeEventListener(\"message\", onMessage as EventListener);\n port.close();\n }\n\n return {\n changed(nodes) {\n if (closed || !welcomed) return;\n if (nodes === \"all\" || pending === \"all\") pending = \"all\";\n else {\n pending ??= new Set();\n for (const n of nodes) pending.add(n);\n }\n if (timer === undefined) timer = setTimeout(flush, debounceMs);\n },\n selection() {\n if (closed || !welcomed) return;\n void Promise.resolve().then(() => adapter.selection()).then((s) => emit(\"selection\", s), () => {});\n },\n theme() { if (!closed) emit(\"theme\", theme()); },\n writable() { if (!closed) emit(\"writable\", adapter.writable(ext)); },\n close: () => close(true),\n };\n}\n\nexport interface HostViewsSetup {\n adapter: ViewAdapter;\n /** The manifest's `reads`. */\n reads: readonly string[];\n names?: HostViewOptions[\"names\"];\n}\n\n/** Every view open on this editor, hosted together: what `hostViews` returns. */\nexport interface ViewHosts extends ViewHandle {\n /** How many views are open. */\n readonly size: number;\n /** Stop taking ports and close every view's handle. */\n dispose(): void;\n}\n\n/**\n * Host every view main hands this editor: `onViewPort` and `hostView` together, one handle per view window. A\n * new port for a window replaces its old handle (one end reloaded), and a window's `closed` drops it. `setup`\n * gives the adapter for a view, or null to refuse it (its port is closed). The returned handle's calls reach\n * every open view, so the app calls them once on each change.\n */\nexport function hostViews(setup: (meta: ViewPortMeta) => HostViewsSetup | null, opts: { target?: Window; theme?: () => ViewTheme; debounceMs?: number } = {}): ViewHosts {\n const handles = new Map<number, ViewHandle>();\n const drop = (meta: ViewPortMeta): void => { handles.get(meta.window)?.close(); handles.delete(meta.window); };\n const stop = onViewPort((port, meta) => {\n drop(meta);\n const s = setup(meta);\n if (!s) { port.close(); return; }\n handles.set(meta.window, hostView(port, s.adapter, {\n meta, reads: s.reads,\n ...(s.names !== undefined ? { names: s.names } : {}),\n ...(opts.theme !== undefined ? { theme: opts.theme } : {}),\n ...(opts.debounceMs !== undefined ? { debounceMs: opts.debounceMs } : {}),\n }));\n }, opts.target ?? window, drop);\n const each = (f: (h: ViewHandle) => void): void => { for (const h of handles.values()) f(h); };\n return {\n get size() { return handles.size; },\n changed: (nodes) => each((h) => h.changed(nodes)),\n selection: () => each((h) => h.selection()),\n theme: () => each((h) => h.theme()),\n writable: () => each((h) => h.writable()),\n close: () => { each((h) => h.close()); handles.clear(); },\n dispose: () => { stop(); each((h) => h.close()); handles.clear(); },\n };\n}\n"]}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// src/view-protocol.ts
|
|
2
|
+
var VIEW_API = 1;
|
|
3
|
+
var VIEW_PORT_CHANNEL = "wildwinter-view:port";
|
|
4
|
+
var VIEW_PORT_MESSAGE = "wildwinter-view:port";
|
|
5
|
+
|
|
6
|
+
export { VIEW_API, VIEW_PORT_CHANNEL, VIEW_PORT_MESSAGE };
|
|
7
|
+
//# sourceMappingURL=chunk-ICYPXUNA.js.map
|
|
8
|
+
//# sourceMappingURL=chunk-ICYPXUNA.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/view-protocol.ts"],"names":[],"mappings":";AAgBO,IAAM,QAAA,GAAW;AAwKjB,IAAM,iBAAA,GAAoB;AAG1B,IAAM,iBAAA,GAAoB","file":"chunk-ICYPXUNA.js","sourcesContent":["// ---------------------------------------------------------------------------\n// The view host API (tier 2 view extensions): the messages between a view, a\n// web component an extension's installed package supplies, and the app that\n// hosts it in a sandboxed floating window (patterkit design/view-extensions.md).\n//\n// Both ends import this module: the app through `hostView`, a view through\n// `@wildwinter/app-shell/view-client`. It is types and constants only, so a\n// view's bundle takes nothing else from the shell.\n//\n// The transport is a MessagePort straight from the view to the editor's\n// renderer, which holds what the writer sees in both apps. Nothing is\n// answered before the view's hello names the API it was built for; a host\n// that can't serve that API refuses it with a sentence and closes the port.\n// ---------------------------------------------------------------------------\n\n/** The API this shell's hosts and clients speak. A host serves exactly this version. */\nexport const VIEW_API = 1;\n\n/** A host: the app a view runs in. */\nexport type ViewHostName = \"patter\" | \"storylets\";\n\n/** A JSON value, as extension data and host fields are. */\nexport type ViewJson = string | number | boolean | null | ViewJson[] | { [key: string]: ViewJson };\n\n// --- What a host hands a view ---------------------------------------------\n\n/**\n * A node of the host's project, in one envelope for both hosts.\n *\n * `kind` is the manifest's node kind (`line`, `card`, `project`, ...). `host`\n * holds the host's own fields for that kind, documented per host in the\n * shell's README; `ext` holds extension data by extension id, the view's own\n * and those its manifest `reads`, nothing else.\n */\nexport interface ViewNode {\n readonly id: string;\n readonly kind: string;\n /** The containing node's id; null for the project. */\n readonly parent: string | null;\n /** A short label for the node, in the source language, as the editor names it. */\n readonly label: string;\n readonly host: { readonly [field: string]: ViewJson };\n readonly ext: { readonly [extension: string]: { readonly [field: string]: ViewJson } };\n /** The node's children in order, when they were asked for. */\n readonly children?: readonly ViewNode[];\n /** Set when somebody else holds the view's own extension data on this node: the sentence saying who. A write\n * touching the node is refused `held`; a view can show the node read-only meanwhile. */\n readonly held?: string;\n}\n\n/** The selection, innermost first (a line, then its snippet, block, and scene). Empty when nothing is selected. */\nexport interface ViewSelection {\n readonly path: readonly { readonly id: string; readonly kind: string }[];\n}\n\n/** A line's length: a recording's when there is one (`measured`), else the host's estimate from its text. */\nexport interface ViewMediaLength {\n readonly seconds: number;\n readonly measured: boolean;\n}\n\n/** The app's theme, for the view to draw in: its CSS custom properties and its colour scheme. */\nexport interface ViewTheme {\n readonly scheme: \"light\" | \"dark\";\n /** Custom property names, with their leading `--`, and their computed values. */\n readonly tokens: { readonly [name: string]: string };\n}\n\n/** Whether the view may write, and why not when it may not, as a sentence for the view to show. */\nexport interface ViewWritable {\n readonly readOnly: boolean;\n readonly why?: string;\n}\n\n/**\n * One write to the view's own extension data: a field on a node, set to a value, or cleared when `value` is absent.\n *\n * `field` is a top-level field the manifest declares for that node's kind, never a path into one: changing one\n * entry of a map or record writes the whole field's new value. Read the node again just before writing, so the\n * write builds on any edit made in the editor meanwhile; requests are answered in order, so nothing lands between.\n *\n * One write is one undo step in one history: its edits are all on the project node, or none of them are (the\n * project's data and a scene's sit in different histories in Patterpad). `hostView` refuses a write that mixes them.\n */\nexport interface ViewEdit {\n readonly node: string;\n readonly field: string;\n readonly value?: ViewJson;\n}\n\n/** Why a request was refused: a stable code for the view, and a sentence for the writer. */\nexport interface ViewRefusal {\n readonly code:\n | \"bad-request\" // the request's shape is wrong\n | \"unsupported\" // the host has no such thing (media in Storyletter)\n | \"gone\" // a node the request names doesn't exist\n | \"held\" // somebody else holds the data the write would change\n | \"read-only\" // the project or the view is read-only\n | \"failed\"; // the host tried and couldn't\n readonly message: string;\n}\n\n// --- Requests --------------------------------------------------------------\n\n/** Each request a view can make: what it sends, and what a granted reply carries. */\nexport interface ViewRequests {\n /** The project node: the host's project facts (Patter: cast, qualifiers, pad default; Storylets: boxes, tag groups). */\n project: { args: Record<string, never>; value: ViewNode };\n /** One node by id, and its children in order when `children` is true; null when there is no such node. */\n node: { args: { id: string; children?: boolean }; value: ViewNode | null };\n selection: { args: Record<string, never>; value: ViewSelection };\n /** Lines' lengths by id. Refused `unsupported` by a host with no media. */\n media: { args: { ids: string[] }; value: { [id: string]: ViewMediaLength } };\n /** Writes to the view's own extension, applied as one undo step with the label, or refused whole. */\n write: { args: { label: string; edits: ViewEdit[] }; value: { written: number } };\n /** Reveal and select a node in the editor. */\n select: { args: { id: string }; value: boolean };\n}\n\nexport type ViewOp = keyof ViewRequests;\n\n// --- Messages ----------------------------------------------------------------\n\n/** A view's first message. */\nexport interface ViewHello { readonly t: \"hello\"; readonly api: number }\n\nexport interface ViewRequest<K extends ViewOp = ViewOp> {\n readonly t: \"request\";\n readonly id: number;\n readonly op: K;\n readonly args: ViewRequests[K][\"args\"];\n}\n\n/** A key combination the view didn't handle, in the shell's portable spelling (\"Mod+Z\", \"Mod+Shift+Z\"), for the host to run. */\nexport interface ViewKey { readonly t: \"key\"; readonly combo: string }\n\nexport type ViewToHost = ViewHello | ViewRequest | ViewKey;\n\n/** The host's answer to a hello it can serve. */\nexport interface ViewWelcome {\n readonly t: \"welcome\";\n readonly api: number;\n readonly host: ViewHostName;\n /** The view's extension: its id, the installed version, and the version the project pins, if any. */\n readonly extension: { readonly id: string; readonly version: string; readonly pinned?: string };\n /** The view's own id within its manifest. */\n readonly view: string;\n /** The extension's project settings, by name. */\n readonly settings: { readonly [name: string]: string };\n readonly theme: ViewTheme;\n readonly writable: ViewWritable;\n}\n\n/** The host's answer to a hello it can't serve, before it closes the port. */\nexport interface ViewRefused { readonly t: \"refused\"; readonly message: string }\n\nexport type ViewReply =\n | { readonly t: \"reply\"; readonly id: number; readonly ok: true; readonly value: unknown }\n | { readonly t: \"reply\"; readonly id: number; readonly ok: false; readonly refusal: ViewRefusal };\n\n/** What the host tells a view unasked. */\nexport interface ViewEvents {\n /** The project changed: these nodes, or anything (\"all\"). Debounced; read again what you show. */\n changed: { nodes: readonly string[] | \"all\" };\n selection: ViewSelection;\n theme: ViewTheme;\n writable: ViewWritable;\n /** The window is closing; the port goes next. */\n closing: Record<string, never>;\n}\n\nexport type ViewEventName = keyof ViewEvents;\n\nexport interface ViewEvent<K extends ViewEventName = ViewEventName> {\n readonly t: \"event\";\n readonly name: K;\n readonly value: ViewEvents[K];\n}\n\nexport type HostToView = ViewWelcome | ViewRefused | ViewReply | ViewEvent;\n\n// --- The port's arrival --------------------------------------------------------\n\n/** The IPC channel main sends each end's port on. */\nexport const VIEW_PORT_CHANNEL = \"wildwinter-view:port\";\n\n/** The window message a preload posts a port with, so page code (which can't see ipcRenderer) can take it. */\nexport const VIEW_PORT_MESSAGE = \"wildwinter-view:port\";\n\n/** What main tells the editor about a view whose port it is handing over. */\nexport interface ViewPortMeta {\n /** The view window's id, unique while it's open. */\n readonly window: number;\n readonly extension: string;\n readonly view: string;\n readonly version: string;\n readonly pinned?: string;\n /** Set, with no port, when the view window has closed: the editor drops its handle for `window`. */\n readonly closed?: true;\n}\n\n// --- The host's side: what an app implements, and what hostView gives back ----\n\n/**\n * What an app supplies `hostView` for one open view: its own model, as the\n * writer sees it (unsaved edits included). `hostView` answers the protocol\n * from it, checks every request's shape, keeps `ext` to the view's own\n * extension and its manifest's `reads`, and refuses writes when not writable.\n * Any method may return a promise.\n */\nexport interface ViewAdapter {\n readonly host: ViewHostName;\n /** The project node. */\n project(): ViewNode | Promise<ViewNode>;\n node(id: string, children: boolean): ViewNode | null | Promise<ViewNode | null>;\n selection(): ViewSelection | Promise<ViewSelection>;\n /** Absent in a host with no media; `hostView` then refuses `media` as unsupported. */\n media?(ids: readonly string[]): { [id: string]: ViewMediaLength } | Promise<{ [id: string]: ViewMediaLength }>;\n /** Apply the edits to `extension`'s data as one undo step with the label, or refuse them all. */\n write(extension: string, label: string, edits: readonly ViewEdit[]): void | ViewRefusal | Promise<void | ViewRefusal>;\n select(id: string): boolean | Promise<boolean>;\n writable(extension: string): ViewWritable;\n /** The extension's project settings, by name. */\n settings(extension: string): { [name: string]: string };\n /** Run a key combination the view passed on (undo, redo, the app's shortcuts). */\n key?(combo: string): void;\n}\n\n/** What `hostView` returns, and `hostViews` for every open view: the app calls these as its model, selection,\n * theme, or locks change. */\nexport interface ViewHandle {\n changed(nodes: readonly string[] | \"all\"): void;\n selection(): void;\n theme(): void;\n writable(): void;\n close(): void;\n}\n"]}
|
|
@@ -93,6 +93,10 @@ function valueHelpText(decl, value) {
|
|
|
93
93
|
return h === void 0 ? [] : [`${v}: ${h}`];
|
|
94
94
|
}).join("\n");
|
|
95
95
|
}
|
|
96
|
+
if (decl.type === "pick") {
|
|
97
|
+
const held = typeof value === "string" && value !== "" ? value : typeof decl.default === "string" ? decl.default : "";
|
|
98
|
+
return held === "" ? "" : said(held) ?? "";
|
|
99
|
+
}
|
|
96
100
|
return "";
|
|
97
101
|
}
|
|
98
102
|
var fallbackFlags = (decl) => Array.isArray(decl.default) ? decl.default.filter((v) => typeof v === "string") : [];
|
|
@@ -452,5 +456,5 @@ function fieldRows() {
|
|
|
452
456
|
}
|
|
453
457
|
|
|
454
458
|
export { defaultLabel, fallbackText, fieldRow, fieldRowFocus, fieldRows, fieldValueProblem, rungStrip, stageGateNote, valueHelpText };
|
|
455
|
-
//# sourceMappingURL=chunk-
|
|
456
|
-
//# sourceMappingURL=chunk-
|
|
459
|
+
//# sourceMappingURL=chunk-OO6O4LEL.js.map
|
|
460
|
+
//# sourceMappingURL=chunk-OO6O4LEL.js.map
|