@wildwinter/app-shell 0.57.0 → 0.58.0
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-CPNTROWD.js → chunk-OZQ2BYXH.js} +13 -4
- package/dist/chunk-OZQ2BYXH.js.map +1 -0
- package/dist/index.cjs +330 -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 +2 -0
- package/dist/index.js.map +1 -1
- 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/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
|
@@ -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":"view-protocol.cjs","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"]}
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
/** The API this shell's hosts and clients speak. A host serves exactly this version. */
|
|
2
|
+
declare const VIEW_API = 1;
|
|
3
|
+
/** A host: the app a view runs in. */
|
|
4
|
+
type ViewHostName = "patter" | "storylets";
|
|
5
|
+
/** A JSON value, as extension data and host fields are. */
|
|
6
|
+
type ViewJson = string | number | boolean | null | ViewJson[] | {
|
|
7
|
+
[key: string]: ViewJson;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* A node of the host's project, in one envelope for both hosts.
|
|
11
|
+
*
|
|
12
|
+
* `kind` is the manifest's node kind (`line`, `card`, `project`, ...). `host`
|
|
13
|
+
* holds the host's own fields for that kind, documented per host in the
|
|
14
|
+
* shell's README; `ext` holds extension data by extension id, the view's own
|
|
15
|
+
* and those its manifest `reads`, nothing else.
|
|
16
|
+
*/
|
|
17
|
+
interface ViewNode {
|
|
18
|
+
readonly id: string;
|
|
19
|
+
readonly kind: string;
|
|
20
|
+
/** The containing node's id; null for the project. */
|
|
21
|
+
readonly parent: string | null;
|
|
22
|
+
/** A short label for the node, in the source language, as the editor names it. */
|
|
23
|
+
readonly label: string;
|
|
24
|
+
readonly host: {
|
|
25
|
+
readonly [field: string]: ViewJson;
|
|
26
|
+
};
|
|
27
|
+
readonly ext: {
|
|
28
|
+
readonly [extension: string]: {
|
|
29
|
+
readonly [field: string]: ViewJson;
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
/** The node's children in order, when they were asked for. */
|
|
33
|
+
readonly children?: readonly ViewNode[];
|
|
34
|
+
/** Set when somebody else holds the view's own extension data on this node: the sentence saying who. A write
|
|
35
|
+
* touching the node is refused `held`; a view can show the node read-only meanwhile. */
|
|
36
|
+
readonly held?: string;
|
|
37
|
+
}
|
|
38
|
+
/** The selection, innermost first (a line, then its snippet, block, and scene). Empty when nothing is selected. */
|
|
39
|
+
interface ViewSelection {
|
|
40
|
+
readonly path: readonly {
|
|
41
|
+
readonly id: string;
|
|
42
|
+
readonly kind: string;
|
|
43
|
+
}[];
|
|
44
|
+
}
|
|
45
|
+
/** A line's length: a recording's when there is one (`measured`), else the host's estimate from its text. */
|
|
46
|
+
interface ViewMediaLength {
|
|
47
|
+
readonly seconds: number;
|
|
48
|
+
readonly measured: boolean;
|
|
49
|
+
}
|
|
50
|
+
/** The app's theme, for the view to draw in: its CSS custom properties and its colour scheme. */
|
|
51
|
+
interface ViewTheme {
|
|
52
|
+
readonly scheme: "light" | "dark";
|
|
53
|
+
/** Custom property names, with their leading `--`, and their computed values. */
|
|
54
|
+
readonly tokens: {
|
|
55
|
+
readonly [name: string]: string;
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/** Whether the view may write, and why not when it may not, as a sentence for the view to show. */
|
|
59
|
+
interface ViewWritable {
|
|
60
|
+
readonly readOnly: boolean;
|
|
61
|
+
readonly why?: string;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* One write to the view's own extension data: a field on a node, set to a value, or cleared when `value` is absent.
|
|
65
|
+
*
|
|
66
|
+
* `field` is a top-level field the manifest declares for that node's kind, never a path into one: changing one
|
|
67
|
+
* entry of a map or record writes the whole field's new value. Read the node again just before writing, so the
|
|
68
|
+
* write builds on any edit made in the editor meanwhile; requests are answered in order, so nothing lands between.
|
|
69
|
+
*
|
|
70
|
+
* One write is one undo step in one history: its edits are all on the project node, or none of them are (the
|
|
71
|
+
* project's data and a scene's sit in different histories in Patterpad). `hostView` refuses a write that mixes them.
|
|
72
|
+
*/
|
|
73
|
+
interface ViewEdit {
|
|
74
|
+
readonly node: string;
|
|
75
|
+
readonly field: string;
|
|
76
|
+
readonly value?: ViewJson;
|
|
77
|
+
}
|
|
78
|
+
/** Why a request was refused: a stable code for the view, and a sentence for the writer. */
|
|
79
|
+
interface ViewRefusal {
|
|
80
|
+
readonly code: "bad-request" | "unsupported" | "gone" | "held" | "read-only" | "failed";
|
|
81
|
+
readonly message: string;
|
|
82
|
+
}
|
|
83
|
+
/** Each request a view can make: what it sends, and what a granted reply carries. */
|
|
84
|
+
interface ViewRequests {
|
|
85
|
+
/** The project node: the host's project facts (Patter: cast, qualifiers, pad default; Storylets: boxes, tag groups). */
|
|
86
|
+
project: {
|
|
87
|
+
args: Record<string, never>;
|
|
88
|
+
value: ViewNode;
|
|
89
|
+
};
|
|
90
|
+
/** One node by id, and its children in order when `children` is true; null when there is no such node. */
|
|
91
|
+
node: {
|
|
92
|
+
args: {
|
|
93
|
+
id: string;
|
|
94
|
+
children?: boolean;
|
|
95
|
+
};
|
|
96
|
+
value: ViewNode | null;
|
|
97
|
+
};
|
|
98
|
+
selection: {
|
|
99
|
+
args: Record<string, never>;
|
|
100
|
+
value: ViewSelection;
|
|
101
|
+
};
|
|
102
|
+
/** Lines' lengths by id. Refused `unsupported` by a host with no media. */
|
|
103
|
+
media: {
|
|
104
|
+
args: {
|
|
105
|
+
ids: string[];
|
|
106
|
+
};
|
|
107
|
+
value: {
|
|
108
|
+
[id: string]: ViewMediaLength;
|
|
109
|
+
};
|
|
110
|
+
};
|
|
111
|
+
/** Writes to the view's own extension, applied as one undo step with the label, or refused whole. */
|
|
112
|
+
write: {
|
|
113
|
+
args: {
|
|
114
|
+
label: string;
|
|
115
|
+
edits: ViewEdit[];
|
|
116
|
+
};
|
|
117
|
+
value: {
|
|
118
|
+
written: number;
|
|
119
|
+
};
|
|
120
|
+
};
|
|
121
|
+
/** Reveal and select a node in the editor. */
|
|
122
|
+
select: {
|
|
123
|
+
args: {
|
|
124
|
+
id: string;
|
|
125
|
+
};
|
|
126
|
+
value: boolean;
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
type ViewOp = keyof ViewRequests;
|
|
130
|
+
/** A view's first message. */
|
|
131
|
+
interface ViewHello {
|
|
132
|
+
readonly t: "hello";
|
|
133
|
+
readonly api: number;
|
|
134
|
+
}
|
|
135
|
+
interface ViewRequest<K extends ViewOp = ViewOp> {
|
|
136
|
+
readonly t: "request";
|
|
137
|
+
readonly id: number;
|
|
138
|
+
readonly op: K;
|
|
139
|
+
readonly args: ViewRequests[K]["args"];
|
|
140
|
+
}
|
|
141
|
+
/** A key combination the view didn't handle, in the shell's portable spelling ("Mod+Z", "Mod+Shift+Z"), for the host to run. */
|
|
142
|
+
interface ViewKey {
|
|
143
|
+
readonly t: "key";
|
|
144
|
+
readonly combo: string;
|
|
145
|
+
}
|
|
146
|
+
type ViewToHost = ViewHello | ViewRequest | ViewKey;
|
|
147
|
+
/** The host's answer to a hello it can serve. */
|
|
148
|
+
interface ViewWelcome {
|
|
149
|
+
readonly t: "welcome";
|
|
150
|
+
readonly api: number;
|
|
151
|
+
readonly host: ViewHostName;
|
|
152
|
+
/** The view's extension: its id, the installed version, and the version the project pins, if any. */
|
|
153
|
+
readonly extension: {
|
|
154
|
+
readonly id: string;
|
|
155
|
+
readonly version: string;
|
|
156
|
+
readonly pinned?: string;
|
|
157
|
+
};
|
|
158
|
+
/** The view's own id within its manifest. */
|
|
159
|
+
readonly view: string;
|
|
160
|
+
/** The extension's project settings, by name. */
|
|
161
|
+
readonly settings: {
|
|
162
|
+
readonly [name: string]: string;
|
|
163
|
+
};
|
|
164
|
+
readonly theme: ViewTheme;
|
|
165
|
+
readonly writable: ViewWritable;
|
|
166
|
+
}
|
|
167
|
+
/** The host's answer to a hello it can't serve, before it closes the port. */
|
|
168
|
+
interface ViewRefused {
|
|
169
|
+
readonly t: "refused";
|
|
170
|
+
readonly message: string;
|
|
171
|
+
}
|
|
172
|
+
type ViewReply = {
|
|
173
|
+
readonly t: "reply";
|
|
174
|
+
readonly id: number;
|
|
175
|
+
readonly ok: true;
|
|
176
|
+
readonly value: unknown;
|
|
177
|
+
} | {
|
|
178
|
+
readonly t: "reply";
|
|
179
|
+
readonly id: number;
|
|
180
|
+
readonly ok: false;
|
|
181
|
+
readonly refusal: ViewRefusal;
|
|
182
|
+
};
|
|
183
|
+
/** What the host tells a view unasked. */
|
|
184
|
+
interface ViewEvents {
|
|
185
|
+
/** The project changed: these nodes, or anything ("all"). Debounced; read again what you show. */
|
|
186
|
+
changed: {
|
|
187
|
+
nodes: readonly string[] | "all";
|
|
188
|
+
};
|
|
189
|
+
selection: ViewSelection;
|
|
190
|
+
theme: ViewTheme;
|
|
191
|
+
writable: ViewWritable;
|
|
192
|
+
/** The window is closing; the port goes next. */
|
|
193
|
+
closing: Record<string, never>;
|
|
194
|
+
}
|
|
195
|
+
type ViewEventName = keyof ViewEvents;
|
|
196
|
+
interface ViewEvent<K extends ViewEventName = ViewEventName> {
|
|
197
|
+
readonly t: "event";
|
|
198
|
+
readonly name: K;
|
|
199
|
+
readonly value: ViewEvents[K];
|
|
200
|
+
}
|
|
201
|
+
type HostToView = ViewWelcome | ViewRefused | ViewReply | ViewEvent;
|
|
202
|
+
/** The IPC channel main sends each end's port on. */
|
|
203
|
+
declare const VIEW_PORT_CHANNEL = "wildwinter-view:port";
|
|
204
|
+
/** The window message a preload posts a port with, so page code (which can't see ipcRenderer) can take it. */
|
|
205
|
+
declare const VIEW_PORT_MESSAGE = "wildwinter-view:port";
|
|
206
|
+
/** What main tells the editor about a view whose port it is handing over. */
|
|
207
|
+
interface ViewPortMeta {
|
|
208
|
+
/** The view window's id, unique while it's open. */
|
|
209
|
+
readonly window: number;
|
|
210
|
+
readonly extension: string;
|
|
211
|
+
readonly view: string;
|
|
212
|
+
readonly version: string;
|
|
213
|
+
readonly pinned?: string;
|
|
214
|
+
/** Set, with no port, when the view window has closed: the editor drops its handle for `window`. */
|
|
215
|
+
readonly closed?: true;
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* What an app supplies `hostView` for one open view: its own model, as the
|
|
219
|
+
* writer sees it (unsaved edits included). `hostView` answers the protocol
|
|
220
|
+
* from it, checks every request's shape, keeps `ext` to the view's own
|
|
221
|
+
* extension and its manifest's `reads`, and refuses writes when not writable.
|
|
222
|
+
* Any method may return a promise.
|
|
223
|
+
*/
|
|
224
|
+
interface ViewAdapter {
|
|
225
|
+
readonly host: ViewHostName;
|
|
226
|
+
/** The project node. */
|
|
227
|
+
project(): ViewNode | Promise<ViewNode>;
|
|
228
|
+
node(id: string, children: boolean): ViewNode | null | Promise<ViewNode | null>;
|
|
229
|
+
selection(): ViewSelection | Promise<ViewSelection>;
|
|
230
|
+
/** Absent in a host with no media; `hostView` then refuses `media` as unsupported. */
|
|
231
|
+
media?(ids: readonly string[]): {
|
|
232
|
+
[id: string]: ViewMediaLength;
|
|
233
|
+
} | Promise<{
|
|
234
|
+
[id: string]: ViewMediaLength;
|
|
235
|
+
}>;
|
|
236
|
+
/** Apply the edits to `extension`'s data as one undo step with the label, or refuse them all. */
|
|
237
|
+
write(extension: string, label: string, edits: readonly ViewEdit[]): void | ViewRefusal | Promise<void | ViewRefusal>;
|
|
238
|
+
select(id: string): boolean | Promise<boolean>;
|
|
239
|
+
writable(extension: string): ViewWritable;
|
|
240
|
+
/** The extension's project settings, by name. */
|
|
241
|
+
settings(extension: string): {
|
|
242
|
+
[name: string]: string;
|
|
243
|
+
};
|
|
244
|
+
/** Run a key combination the view passed on (undo, redo, the app's shortcuts). */
|
|
245
|
+
key?(combo: string): void;
|
|
246
|
+
}
|
|
247
|
+
/** What `hostView` returns, and `hostViews` for every open view: the app calls these as its model, selection,
|
|
248
|
+
* theme, or locks change. */
|
|
249
|
+
interface ViewHandle {
|
|
250
|
+
changed(nodes: readonly string[] | "all"): void;
|
|
251
|
+
selection(): void;
|
|
252
|
+
theme(): void;
|
|
253
|
+
writable(): void;
|
|
254
|
+
close(): void;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export { type HostToView, VIEW_API, VIEW_PORT_CHANNEL, VIEW_PORT_MESSAGE, type ViewAdapter, type ViewEdit, type ViewEvent, type ViewEventName, type ViewEvents, type ViewHandle, type ViewHello, type ViewHostName, type ViewJson, type ViewKey, type ViewMediaLength, type ViewNode, type ViewOp, type ViewPortMeta, type ViewRefusal, type ViewRefused, type ViewReply, type ViewRequest, type ViewRequests, type ViewSelection, type ViewTheme, type ViewToHost, type ViewWelcome, type ViewWritable };
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
/** The API this shell's hosts and clients speak. A host serves exactly this version. */
|
|
2
|
+
declare const VIEW_API = 1;
|
|
3
|
+
/** A host: the app a view runs in. */
|
|
4
|
+
type ViewHostName = "patter" | "storylets";
|
|
5
|
+
/** A JSON value, as extension data and host fields are. */
|
|
6
|
+
type ViewJson = string | number | boolean | null | ViewJson[] | {
|
|
7
|
+
[key: string]: ViewJson;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* A node of the host's project, in one envelope for both hosts.
|
|
11
|
+
*
|
|
12
|
+
* `kind` is the manifest's node kind (`line`, `card`, `project`, ...). `host`
|
|
13
|
+
* holds the host's own fields for that kind, documented per host in the
|
|
14
|
+
* shell's README; `ext` holds extension data by extension id, the view's own
|
|
15
|
+
* and those its manifest `reads`, nothing else.
|
|
16
|
+
*/
|
|
17
|
+
interface ViewNode {
|
|
18
|
+
readonly id: string;
|
|
19
|
+
readonly kind: string;
|
|
20
|
+
/** The containing node's id; null for the project. */
|
|
21
|
+
readonly parent: string | null;
|
|
22
|
+
/** A short label for the node, in the source language, as the editor names it. */
|
|
23
|
+
readonly label: string;
|
|
24
|
+
readonly host: {
|
|
25
|
+
readonly [field: string]: ViewJson;
|
|
26
|
+
};
|
|
27
|
+
readonly ext: {
|
|
28
|
+
readonly [extension: string]: {
|
|
29
|
+
readonly [field: string]: ViewJson;
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
/** The node's children in order, when they were asked for. */
|
|
33
|
+
readonly children?: readonly ViewNode[];
|
|
34
|
+
/** Set when somebody else holds the view's own extension data on this node: the sentence saying who. A write
|
|
35
|
+
* touching the node is refused `held`; a view can show the node read-only meanwhile. */
|
|
36
|
+
readonly held?: string;
|
|
37
|
+
}
|
|
38
|
+
/** The selection, innermost first (a line, then its snippet, block, and scene). Empty when nothing is selected. */
|
|
39
|
+
interface ViewSelection {
|
|
40
|
+
readonly path: readonly {
|
|
41
|
+
readonly id: string;
|
|
42
|
+
readonly kind: string;
|
|
43
|
+
}[];
|
|
44
|
+
}
|
|
45
|
+
/** A line's length: a recording's when there is one (`measured`), else the host's estimate from its text. */
|
|
46
|
+
interface ViewMediaLength {
|
|
47
|
+
readonly seconds: number;
|
|
48
|
+
readonly measured: boolean;
|
|
49
|
+
}
|
|
50
|
+
/** The app's theme, for the view to draw in: its CSS custom properties and its colour scheme. */
|
|
51
|
+
interface ViewTheme {
|
|
52
|
+
readonly scheme: "light" | "dark";
|
|
53
|
+
/** Custom property names, with their leading `--`, and their computed values. */
|
|
54
|
+
readonly tokens: {
|
|
55
|
+
readonly [name: string]: string;
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/** Whether the view may write, and why not when it may not, as a sentence for the view to show. */
|
|
59
|
+
interface ViewWritable {
|
|
60
|
+
readonly readOnly: boolean;
|
|
61
|
+
readonly why?: string;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* One write to the view's own extension data: a field on a node, set to a value, or cleared when `value` is absent.
|
|
65
|
+
*
|
|
66
|
+
* `field` is a top-level field the manifest declares for that node's kind, never a path into one: changing one
|
|
67
|
+
* entry of a map or record writes the whole field's new value. Read the node again just before writing, so the
|
|
68
|
+
* write builds on any edit made in the editor meanwhile; requests are answered in order, so nothing lands between.
|
|
69
|
+
*
|
|
70
|
+
* One write is one undo step in one history: its edits are all on the project node, or none of them are (the
|
|
71
|
+
* project's data and a scene's sit in different histories in Patterpad). `hostView` refuses a write that mixes them.
|
|
72
|
+
*/
|
|
73
|
+
interface ViewEdit {
|
|
74
|
+
readonly node: string;
|
|
75
|
+
readonly field: string;
|
|
76
|
+
readonly value?: ViewJson;
|
|
77
|
+
}
|
|
78
|
+
/** Why a request was refused: a stable code for the view, and a sentence for the writer. */
|
|
79
|
+
interface ViewRefusal {
|
|
80
|
+
readonly code: "bad-request" | "unsupported" | "gone" | "held" | "read-only" | "failed";
|
|
81
|
+
readonly message: string;
|
|
82
|
+
}
|
|
83
|
+
/** Each request a view can make: what it sends, and what a granted reply carries. */
|
|
84
|
+
interface ViewRequests {
|
|
85
|
+
/** The project node: the host's project facts (Patter: cast, qualifiers, pad default; Storylets: boxes, tag groups). */
|
|
86
|
+
project: {
|
|
87
|
+
args: Record<string, never>;
|
|
88
|
+
value: ViewNode;
|
|
89
|
+
};
|
|
90
|
+
/** One node by id, and its children in order when `children` is true; null when there is no such node. */
|
|
91
|
+
node: {
|
|
92
|
+
args: {
|
|
93
|
+
id: string;
|
|
94
|
+
children?: boolean;
|
|
95
|
+
};
|
|
96
|
+
value: ViewNode | null;
|
|
97
|
+
};
|
|
98
|
+
selection: {
|
|
99
|
+
args: Record<string, never>;
|
|
100
|
+
value: ViewSelection;
|
|
101
|
+
};
|
|
102
|
+
/** Lines' lengths by id. Refused `unsupported` by a host with no media. */
|
|
103
|
+
media: {
|
|
104
|
+
args: {
|
|
105
|
+
ids: string[];
|
|
106
|
+
};
|
|
107
|
+
value: {
|
|
108
|
+
[id: string]: ViewMediaLength;
|
|
109
|
+
};
|
|
110
|
+
};
|
|
111
|
+
/** Writes to the view's own extension, applied as one undo step with the label, or refused whole. */
|
|
112
|
+
write: {
|
|
113
|
+
args: {
|
|
114
|
+
label: string;
|
|
115
|
+
edits: ViewEdit[];
|
|
116
|
+
};
|
|
117
|
+
value: {
|
|
118
|
+
written: number;
|
|
119
|
+
};
|
|
120
|
+
};
|
|
121
|
+
/** Reveal and select a node in the editor. */
|
|
122
|
+
select: {
|
|
123
|
+
args: {
|
|
124
|
+
id: string;
|
|
125
|
+
};
|
|
126
|
+
value: boolean;
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
type ViewOp = keyof ViewRequests;
|
|
130
|
+
/** A view's first message. */
|
|
131
|
+
interface ViewHello {
|
|
132
|
+
readonly t: "hello";
|
|
133
|
+
readonly api: number;
|
|
134
|
+
}
|
|
135
|
+
interface ViewRequest<K extends ViewOp = ViewOp> {
|
|
136
|
+
readonly t: "request";
|
|
137
|
+
readonly id: number;
|
|
138
|
+
readonly op: K;
|
|
139
|
+
readonly args: ViewRequests[K]["args"];
|
|
140
|
+
}
|
|
141
|
+
/** A key combination the view didn't handle, in the shell's portable spelling ("Mod+Z", "Mod+Shift+Z"), for the host to run. */
|
|
142
|
+
interface ViewKey {
|
|
143
|
+
readonly t: "key";
|
|
144
|
+
readonly combo: string;
|
|
145
|
+
}
|
|
146
|
+
type ViewToHost = ViewHello | ViewRequest | ViewKey;
|
|
147
|
+
/** The host's answer to a hello it can serve. */
|
|
148
|
+
interface ViewWelcome {
|
|
149
|
+
readonly t: "welcome";
|
|
150
|
+
readonly api: number;
|
|
151
|
+
readonly host: ViewHostName;
|
|
152
|
+
/** The view's extension: its id, the installed version, and the version the project pins, if any. */
|
|
153
|
+
readonly extension: {
|
|
154
|
+
readonly id: string;
|
|
155
|
+
readonly version: string;
|
|
156
|
+
readonly pinned?: string;
|
|
157
|
+
};
|
|
158
|
+
/** The view's own id within its manifest. */
|
|
159
|
+
readonly view: string;
|
|
160
|
+
/** The extension's project settings, by name. */
|
|
161
|
+
readonly settings: {
|
|
162
|
+
readonly [name: string]: string;
|
|
163
|
+
};
|
|
164
|
+
readonly theme: ViewTheme;
|
|
165
|
+
readonly writable: ViewWritable;
|
|
166
|
+
}
|
|
167
|
+
/** The host's answer to a hello it can't serve, before it closes the port. */
|
|
168
|
+
interface ViewRefused {
|
|
169
|
+
readonly t: "refused";
|
|
170
|
+
readonly message: string;
|
|
171
|
+
}
|
|
172
|
+
type ViewReply = {
|
|
173
|
+
readonly t: "reply";
|
|
174
|
+
readonly id: number;
|
|
175
|
+
readonly ok: true;
|
|
176
|
+
readonly value: unknown;
|
|
177
|
+
} | {
|
|
178
|
+
readonly t: "reply";
|
|
179
|
+
readonly id: number;
|
|
180
|
+
readonly ok: false;
|
|
181
|
+
readonly refusal: ViewRefusal;
|
|
182
|
+
};
|
|
183
|
+
/** What the host tells a view unasked. */
|
|
184
|
+
interface ViewEvents {
|
|
185
|
+
/** The project changed: these nodes, or anything ("all"). Debounced; read again what you show. */
|
|
186
|
+
changed: {
|
|
187
|
+
nodes: readonly string[] | "all";
|
|
188
|
+
};
|
|
189
|
+
selection: ViewSelection;
|
|
190
|
+
theme: ViewTheme;
|
|
191
|
+
writable: ViewWritable;
|
|
192
|
+
/** The window is closing; the port goes next. */
|
|
193
|
+
closing: Record<string, never>;
|
|
194
|
+
}
|
|
195
|
+
type ViewEventName = keyof ViewEvents;
|
|
196
|
+
interface ViewEvent<K extends ViewEventName = ViewEventName> {
|
|
197
|
+
readonly t: "event";
|
|
198
|
+
readonly name: K;
|
|
199
|
+
readonly value: ViewEvents[K];
|
|
200
|
+
}
|
|
201
|
+
type HostToView = ViewWelcome | ViewRefused | ViewReply | ViewEvent;
|
|
202
|
+
/** The IPC channel main sends each end's port on. */
|
|
203
|
+
declare const VIEW_PORT_CHANNEL = "wildwinter-view:port";
|
|
204
|
+
/** The window message a preload posts a port with, so page code (which can't see ipcRenderer) can take it. */
|
|
205
|
+
declare const VIEW_PORT_MESSAGE = "wildwinter-view:port";
|
|
206
|
+
/** What main tells the editor about a view whose port it is handing over. */
|
|
207
|
+
interface ViewPortMeta {
|
|
208
|
+
/** The view window's id, unique while it's open. */
|
|
209
|
+
readonly window: number;
|
|
210
|
+
readonly extension: string;
|
|
211
|
+
readonly view: string;
|
|
212
|
+
readonly version: string;
|
|
213
|
+
readonly pinned?: string;
|
|
214
|
+
/** Set, with no port, when the view window has closed: the editor drops its handle for `window`. */
|
|
215
|
+
readonly closed?: true;
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* What an app supplies `hostView` for one open view: its own model, as the
|
|
219
|
+
* writer sees it (unsaved edits included). `hostView` answers the protocol
|
|
220
|
+
* from it, checks every request's shape, keeps `ext` to the view's own
|
|
221
|
+
* extension and its manifest's `reads`, and refuses writes when not writable.
|
|
222
|
+
* Any method may return a promise.
|
|
223
|
+
*/
|
|
224
|
+
interface ViewAdapter {
|
|
225
|
+
readonly host: ViewHostName;
|
|
226
|
+
/** The project node. */
|
|
227
|
+
project(): ViewNode | Promise<ViewNode>;
|
|
228
|
+
node(id: string, children: boolean): ViewNode | null | Promise<ViewNode | null>;
|
|
229
|
+
selection(): ViewSelection | Promise<ViewSelection>;
|
|
230
|
+
/** Absent in a host with no media; `hostView` then refuses `media` as unsupported. */
|
|
231
|
+
media?(ids: readonly string[]): {
|
|
232
|
+
[id: string]: ViewMediaLength;
|
|
233
|
+
} | Promise<{
|
|
234
|
+
[id: string]: ViewMediaLength;
|
|
235
|
+
}>;
|
|
236
|
+
/** Apply the edits to `extension`'s data as one undo step with the label, or refuse them all. */
|
|
237
|
+
write(extension: string, label: string, edits: readonly ViewEdit[]): void | ViewRefusal | Promise<void | ViewRefusal>;
|
|
238
|
+
select(id: string): boolean | Promise<boolean>;
|
|
239
|
+
writable(extension: string): ViewWritable;
|
|
240
|
+
/** The extension's project settings, by name. */
|
|
241
|
+
settings(extension: string): {
|
|
242
|
+
[name: string]: string;
|
|
243
|
+
};
|
|
244
|
+
/** Run a key combination the view passed on (undo, redo, the app's shortcuts). */
|
|
245
|
+
key?(combo: string): void;
|
|
246
|
+
}
|
|
247
|
+
/** What `hostView` returns, and `hostViews` for every open view: the app calls these as its model, selection,
|
|
248
|
+
* theme, or locks change. */
|
|
249
|
+
interface ViewHandle {
|
|
250
|
+
changed(nodes: readonly string[] | "all"): void;
|
|
251
|
+
selection(): void;
|
|
252
|
+
theme(): void;
|
|
253
|
+
writable(): void;
|
|
254
|
+
close(): void;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export { type HostToView, VIEW_API, VIEW_PORT_CHANNEL, VIEW_PORT_MESSAGE, type ViewAdapter, type ViewEdit, type ViewEvent, type ViewEventName, type ViewEvents, type ViewHandle, type ViewHello, type ViewHostName, type ViewJson, type ViewKey, type ViewMediaLength, type ViewNode, type ViewOp, type ViewPortMeta, type ViewRefusal, type ViewRefused, type ViewReply, type ViewRequest, type ViewRequests, type ViewSelection, type ViewTheme, type ViewToHost, type ViewWelcome, type ViewWritable };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"view-protocol.js"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wildwinter/app-shell",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.58.0",
|
|
4
4
|
"description": "The shared shell for the editor family (Patterpad, Storylet Studio): the app-agnostic design-token grammar, the per-entity colour hash, and the vanilla-TS DOM primitive. Two palettes, one grammar - each app supplies its own palette/fonts over these tokens.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -229,7 +229,28 @@
|
|
|
229
229
|
"./link-status.css": {
|
|
230
230
|
"import": "./dist/link-status.css",
|
|
231
231
|
"require": "./dist/link-status.css"
|
|
232
|
-
}
|
|
232
|
+
},
|
|
233
|
+
"./view-protocol": {
|
|
234
|
+
"types": "./dist/view-protocol.d.ts",
|
|
235
|
+
"import": "./dist/view-protocol.js",
|
|
236
|
+
"require": "./dist/view-protocol.cjs"
|
|
237
|
+
},
|
|
238
|
+
"./view-client": {
|
|
239
|
+
"types": "./dist/view-client.d.ts",
|
|
240
|
+
"import": "./dist/view-client.js",
|
|
241
|
+
"require": "./dist/view-client.cjs"
|
|
242
|
+
},
|
|
243
|
+
"./view-host": {
|
|
244
|
+
"types": "./dist/view-host.d.ts",
|
|
245
|
+
"import": "./dist/view-host.js",
|
|
246
|
+
"require": "./dist/view-host.cjs"
|
|
247
|
+
},
|
|
248
|
+
"./view-host-web": {
|
|
249
|
+
"types": "./dist/view-host-web.d.ts",
|
|
250
|
+
"import": "./dist/view-host-web.js",
|
|
251
|
+
"require": "./dist/view-host-web.cjs"
|
|
252
|
+
},
|
|
253
|
+
"./view-preload.cjs": "./dist/view-preload.cjs"
|
|
233
254
|
},
|
|
234
255
|
"files": [
|
|
235
256
|
"dist",
|
|
@@ -254,7 +275,7 @@
|
|
|
254
275
|
},
|
|
255
276
|
"peerDependencies": {
|
|
256
277
|
"@wildwinter/simple-vc-lib": ">=0.5.0",
|
|
257
|
-
"@wildwinter/toolkit": ">=0.2.
|
|
278
|
+
"@wildwinter/toolkit": ">=0.2.6 <1.0.0",
|
|
258
279
|
"electron": ">=28",
|
|
259
280
|
"electron-updater": ">=6",
|
|
260
281
|
"ws": ">=8"
|
|
@@ -281,7 +302,7 @@
|
|
|
281
302
|
"@wildwinter/expr": "^0.6.0",
|
|
282
303
|
"@wildwinter/scoperegistry": "^0.9.0",
|
|
283
304
|
"@wildwinter/simple-vc-lib": "^0.5.2",
|
|
284
|
-
"@wildwinter/toolkit": "^0.2.
|
|
305
|
+
"@wildwinter/toolkit": "^0.2.6",
|
|
285
306
|
"electron": "42.6.0",
|
|
286
307
|
"electron-updater": "^6.8.9",
|
|
287
308
|
"jsdom": "^25.0.0",
|