janela 0.5.0 → 0.6.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 +49 -9
- package/bin/janela.mjs +9 -1
- package/package.json +2 -2
- package/runtime/janela.ts +387 -266
- package/runtime/types.ts +0 -119
- package/templates/react/files/src-host/main.ts +20 -21
- package/templates/solid/files/src-host/main.ts +20 -21
- package/templates/svelte/files/src-host/main.ts +20 -21
- package/templates/vue/files/src-host/main.ts +20 -21
package/runtime/types.ts
CHANGED
|
@@ -73,64 +73,6 @@ export interface WindowConfig {
|
|
|
73
73
|
height: number;
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
export interface JanelaApp {
|
|
77
|
-
handle: number;
|
|
78
|
-
names: string[];
|
|
79
|
-
handlers: CommandHandler[];
|
|
80
|
-
/** Register a named command, callable from the page as janela.invoke(name, args). */
|
|
81
|
-
command: (name: string, h: CommandHandler) => void;
|
|
82
|
-
/** Register a command that answers later; see AsyncCommandHandler. */
|
|
83
|
-
commandAsync: (name: string, h: AsyncCommandHandler) => void;
|
|
84
|
-
/** Run fn on the next turn of the host loop — the way to slice long work. */
|
|
85
|
-
defer: (fn: () => void) => void;
|
|
86
|
-
/** Run fn after at least ms. The host loop's timer; scriptc's setTimeout
|
|
87
|
-
* cannot fire while the window is open (its loop is parked inside run()). */
|
|
88
|
-
sleep: (ms: number, fn: () => void) => void;
|
|
89
|
-
/**
|
|
90
|
-
* Read a file without blocking the window. The syscall runs on a shim
|
|
91
|
-
* worker thread; the callback lands on the UI thread on a later turn.
|
|
92
|
-
* Prefer this over node:fs readFileSync inside a command — that one blocks
|
|
93
|
-
* the loop, and with it the whole window.
|
|
94
|
-
*/
|
|
95
|
-
readFileAsync: (path: string, cb: FsCallback) => void;
|
|
96
|
-
/** Write a file without blocking the window; cb(null) on success. */
|
|
97
|
-
writeFileAsync: (
|
|
98
|
-
path: string,
|
|
99
|
-
data: string,
|
|
100
|
-
cb: (err: string | null) => void,
|
|
101
|
-
) => void;
|
|
102
|
-
/**
|
|
103
|
-
* Show the native "open" dialog. `cb` gets the chosen paths, or null if the
|
|
104
|
-
* user cancelled. The modal runs on a later turn of the UI thread, so
|
|
105
|
-
* calling this from inside a command does not block that command's reply —
|
|
106
|
-
* pair it with commandAsync when the page is waiting for the result.
|
|
107
|
-
*/
|
|
108
|
-
openFileDialog: (
|
|
109
|
-
options: OpenDialogOptions,
|
|
110
|
-
cb: (paths: string[] | null, err?: string) => void,
|
|
111
|
-
) => void;
|
|
112
|
-
/** Show the native "save" dialog; cb gets the path, or null on cancel. */
|
|
113
|
-
saveFileDialog: (
|
|
114
|
-
options: SaveDialogOptions,
|
|
115
|
-
cb: (path: string | null, err?: string) => void,
|
|
116
|
-
) => void;
|
|
117
|
-
/** Change the window title at any time, not just at startup. */
|
|
118
|
-
setTitle: (title: string) => void;
|
|
119
|
-
/**
|
|
120
|
-
* Resize the window. `hint` is webview's sizing hint: 0 none, 1 minimum,
|
|
121
|
-
* 2 maximum, 3 fixed.
|
|
122
|
-
*/
|
|
123
|
-
setSize: (width: number, height: number, hint?: number) => void;
|
|
124
|
-
/** Enter or leave fullscreen. */
|
|
125
|
-
setFullscreen: (on: boolean) => void;
|
|
126
|
-
/** Fire an event into the page; the payload is delivered as a value. */
|
|
127
|
-
emit: (event: string, payload: unknown) => void;
|
|
128
|
-
/** Close the window and make run() return. */
|
|
129
|
-
quit: () => void;
|
|
130
|
-
/** Show the page and block until the window closes. Returns the run status. */
|
|
131
|
-
run: (html: string) => number;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
76
|
// ---------------------------------------------------------------------------
|
|
135
77
|
// Typed IPC contract
|
|
136
78
|
// ---------------------------------------------------------------------------
|
|
@@ -188,64 +130,3 @@ export function defineCommands<M extends CommandShapes>(): Commands<M> {
|
|
|
188
130
|
export function defineEvents<E>(): Events<E> {
|
|
189
131
|
return {};
|
|
190
132
|
}
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* Register a command against the contract. `args` is inferred from the
|
|
194
|
-
* contract, and the return type is checked against it, so the handler is
|
|
195
|
-
* written once with no casts.
|
|
196
|
-
*
|
|
197
|
-
* ```ts
|
|
198
|
-
* on(app, commands, "add", (args) => args.a + args.b);
|
|
199
|
-
* ```
|
|
200
|
-
*/
|
|
201
|
-
export function on<M extends CommandShapes, K extends keyof M & string>(
|
|
202
|
-
app: JanelaApp,
|
|
203
|
-
_commands: Commands<M>,
|
|
204
|
-
name: K,
|
|
205
|
-
handler: (args: M[K]["args"]) => M[K]["result"],
|
|
206
|
-
): void {
|
|
207
|
-
app.command(name, (args: unknown) => handler(args as M[K]["args"]));
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
/**
|
|
211
|
-
* Register a command that answers on a later turn. `resolve` takes the
|
|
212
|
-
* contract's result type; see AsyncCommandHandler for the timing rules.
|
|
213
|
-
*/
|
|
214
|
-
export function onAsync<M extends CommandShapes, K extends keyof M & string>(
|
|
215
|
-
app: JanelaApp,
|
|
216
|
-
_commands: Commands<M>,
|
|
217
|
-
name: K,
|
|
218
|
-
handler: (
|
|
219
|
-
args: M[K]["args"],
|
|
220
|
-
resolve: (value: M[K]["result"]) => void,
|
|
221
|
-
reject: (reason: unknown) => void,
|
|
222
|
-
) => void,
|
|
223
|
-
): void {
|
|
224
|
-
app.commandAsync(
|
|
225
|
-
name,
|
|
226
|
-
(args: unknown, resolve: (value: unknown) => void, reject: (reason: unknown) => void) => {
|
|
227
|
-
handler(
|
|
228
|
-
args as M[K]["args"],
|
|
229
|
-
(value: M[K]["result"]) => resolve(value),
|
|
230
|
-
reject,
|
|
231
|
-
);
|
|
232
|
-
},
|
|
233
|
-
);
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
/**
|
|
237
|
-
* Emit a declared event. The name must exist in the contract and the payload
|
|
238
|
-
* must match its type.
|
|
239
|
-
*
|
|
240
|
-
* ```ts
|
|
241
|
-
* emit(app, events, "added", 42);
|
|
242
|
-
* ```
|
|
243
|
-
*/
|
|
244
|
-
export function emit<E, K extends keyof E & string>(
|
|
245
|
-
app: JanelaApp,
|
|
246
|
-
_events: Events<E>,
|
|
247
|
-
name: K,
|
|
248
|
-
payload: E[K],
|
|
249
|
-
): void {
|
|
250
|
-
app.emit(name, payload);
|
|
251
|
-
}
|
|
@@ -12,56 +12,55 @@
|
|
|
12
12
|
// an `undefined` argument lowers to a zero-parameter function and fails
|
|
13
13
|
// to compile.
|
|
14
14
|
|
|
15
|
-
import {
|
|
16
|
-
defineCommands,
|
|
17
|
-
defineEvents,
|
|
18
|
-
emit,
|
|
19
|
-
on,
|
|
20
|
-
onAsync,
|
|
21
|
-
type JanelaApp,
|
|
22
|
-
} from "janela/host";
|
|
15
|
+
import { defineCommands, defineEvents, type JanelaApp } from "janela/host";
|
|
23
16
|
|
|
24
|
-
|
|
17
|
+
/** Every command this app answers. Declared once; the page checks against it. */
|
|
18
|
+
export type AppCommands = {
|
|
25
19
|
add: { args: { a: number; b: number }; result: number };
|
|
26
20
|
greet: { args: { name: string }; result: string };
|
|
27
21
|
log: { args: string; result: null };
|
|
28
22
|
wait: { args: { ms: number }; result: string };
|
|
29
23
|
quit: { args: null; result: null };
|
|
30
|
-
}
|
|
24
|
+
};
|
|
31
25
|
|
|
32
|
-
|
|
26
|
+
/** Every event this app emits, and what each one carries. */
|
|
27
|
+
export type AppEvents = {
|
|
33
28
|
added: number;
|
|
34
|
-
}
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export const commands = defineCommands<AppCommands>();
|
|
32
|
+
export const events = defineEvents<AppEvents>();
|
|
35
33
|
|
|
36
34
|
/** The contract the page imports with `import type { App } from "../src-host/main"`. */
|
|
37
35
|
export type App = { commands: typeof commands; events: typeof events };
|
|
38
36
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
37
|
+
// Typing the app with the contract is what makes `app.command` checked: the
|
|
38
|
+
// name must be one of the declared ones, `args` is inferred from it, and the
|
|
39
|
+
// return value has to match. Same for `app.emit`.
|
|
40
|
+
export function setup(app: JanelaApp<AppCommands, AppEvents>): void {
|
|
41
|
+
app.command("add", (args) => {
|
|
43
42
|
const sum = args.a + args.b;
|
|
44
|
-
emit(
|
|
43
|
+
app.emit("added", sum);
|
|
45
44
|
return sum;
|
|
46
45
|
});
|
|
47
46
|
|
|
48
|
-
|
|
47
|
+
app.command("greet", (args) => {
|
|
49
48
|
return "Hello, " + args.name + " — from the native TS binary";
|
|
50
49
|
});
|
|
51
50
|
|
|
52
|
-
|
|
51
|
+
app.command("log", (args) => {
|
|
53
52
|
console.log("[host] page says:", args);
|
|
54
53
|
return null;
|
|
55
54
|
});
|
|
56
55
|
|
|
57
56
|
// An async command: answers later, without freezing the window.
|
|
58
|
-
|
|
57
|
+
app.commandAsync("wait", (args, resolve) => {
|
|
59
58
|
app.sleep(args.ms, () => {
|
|
60
59
|
resolve("waited " + args.ms + "ms without blocking the UI");
|
|
61
60
|
});
|
|
62
61
|
});
|
|
63
62
|
|
|
64
|
-
|
|
63
|
+
app.command("quit", (_args) => {
|
|
65
64
|
app.quit();
|
|
66
65
|
return null;
|
|
67
66
|
});
|
|
@@ -12,56 +12,55 @@
|
|
|
12
12
|
// an `undefined` argument lowers to a zero-parameter function and fails
|
|
13
13
|
// to compile.
|
|
14
14
|
|
|
15
|
-
import {
|
|
16
|
-
defineCommands,
|
|
17
|
-
defineEvents,
|
|
18
|
-
emit,
|
|
19
|
-
on,
|
|
20
|
-
onAsync,
|
|
21
|
-
type JanelaApp,
|
|
22
|
-
} from "janela/host";
|
|
15
|
+
import { defineCommands, defineEvents, type JanelaApp } from "janela/host";
|
|
23
16
|
|
|
24
|
-
|
|
17
|
+
/** Every command this app answers. Declared once; the page checks against it. */
|
|
18
|
+
export type AppCommands = {
|
|
25
19
|
add: { args: { a: number; b: number }; result: number };
|
|
26
20
|
greet: { args: { name: string }; result: string };
|
|
27
21
|
log: { args: string; result: null };
|
|
28
22
|
wait: { args: { ms: number }; result: string };
|
|
29
23
|
quit: { args: null; result: null };
|
|
30
|
-
}
|
|
24
|
+
};
|
|
31
25
|
|
|
32
|
-
|
|
26
|
+
/** Every event this app emits, and what each one carries. */
|
|
27
|
+
export type AppEvents = {
|
|
33
28
|
added: number;
|
|
34
|
-
}
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export const commands = defineCommands<AppCommands>();
|
|
32
|
+
export const events = defineEvents<AppEvents>();
|
|
35
33
|
|
|
36
34
|
/** The contract the page imports with `import type { App } from "../src-host/main"`. */
|
|
37
35
|
export type App = { commands: typeof commands; events: typeof events };
|
|
38
36
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
37
|
+
// Typing the app with the contract is what makes `app.command` checked: the
|
|
38
|
+
// name must be one of the declared ones, `args` is inferred from it, and the
|
|
39
|
+
// return value has to match. Same for `app.emit`.
|
|
40
|
+
export function setup(app: JanelaApp<AppCommands, AppEvents>): void {
|
|
41
|
+
app.command("add", (args) => {
|
|
43
42
|
const sum = args.a + args.b;
|
|
44
|
-
emit(
|
|
43
|
+
app.emit("added", sum);
|
|
45
44
|
return sum;
|
|
46
45
|
});
|
|
47
46
|
|
|
48
|
-
|
|
47
|
+
app.command("greet", (args) => {
|
|
49
48
|
return "Hello, " + args.name + " — from the native TS binary";
|
|
50
49
|
});
|
|
51
50
|
|
|
52
|
-
|
|
51
|
+
app.command("log", (args) => {
|
|
53
52
|
console.log("[host] page says:", args);
|
|
54
53
|
return null;
|
|
55
54
|
});
|
|
56
55
|
|
|
57
56
|
// An async command: answers later, without freezing the window.
|
|
58
|
-
|
|
57
|
+
app.commandAsync("wait", (args, resolve) => {
|
|
59
58
|
app.sleep(args.ms, () => {
|
|
60
59
|
resolve("waited " + args.ms + "ms without blocking the UI");
|
|
61
60
|
});
|
|
62
61
|
});
|
|
63
62
|
|
|
64
|
-
|
|
63
|
+
app.command("quit", (_args) => {
|
|
65
64
|
app.quit();
|
|
66
65
|
return null;
|
|
67
66
|
});
|
|
@@ -12,56 +12,55 @@
|
|
|
12
12
|
// an `undefined` argument lowers to a zero-parameter function and fails
|
|
13
13
|
// to compile.
|
|
14
14
|
|
|
15
|
-
import {
|
|
16
|
-
defineCommands,
|
|
17
|
-
defineEvents,
|
|
18
|
-
emit,
|
|
19
|
-
on,
|
|
20
|
-
onAsync,
|
|
21
|
-
type JanelaApp,
|
|
22
|
-
} from "janela/host";
|
|
15
|
+
import { defineCommands, defineEvents, type JanelaApp } from "janela/host";
|
|
23
16
|
|
|
24
|
-
|
|
17
|
+
/** Every command this app answers. Declared once; the page checks against it. */
|
|
18
|
+
export type AppCommands = {
|
|
25
19
|
add: { args: { a: number; b: number }; result: number };
|
|
26
20
|
greet: { args: { name: string }; result: string };
|
|
27
21
|
log: { args: string; result: null };
|
|
28
22
|
wait: { args: { ms: number }; result: string };
|
|
29
23
|
quit: { args: null; result: null };
|
|
30
|
-
}
|
|
24
|
+
};
|
|
31
25
|
|
|
32
|
-
|
|
26
|
+
/** Every event this app emits, and what each one carries. */
|
|
27
|
+
export type AppEvents = {
|
|
33
28
|
added: number;
|
|
34
|
-
}
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export const commands = defineCommands<AppCommands>();
|
|
32
|
+
export const events = defineEvents<AppEvents>();
|
|
35
33
|
|
|
36
34
|
/** The contract the page imports with `import type { App } from "../src-host/main"`. */
|
|
37
35
|
export type App = { commands: typeof commands; events: typeof events };
|
|
38
36
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
37
|
+
// Typing the app with the contract is what makes `app.command` checked: the
|
|
38
|
+
// name must be one of the declared ones, `args` is inferred from it, and the
|
|
39
|
+
// return value has to match. Same for `app.emit`.
|
|
40
|
+
export function setup(app: JanelaApp<AppCommands, AppEvents>): void {
|
|
41
|
+
app.command("add", (args) => {
|
|
43
42
|
const sum = args.a + args.b;
|
|
44
|
-
emit(
|
|
43
|
+
app.emit("added", sum);
|
|
45
44
|
return sum;
|
|
46
45
|
});
|
|
47
46
|
|
|
48
|
-
|
|
47
|
+
app.command("greet", (args) => {
|
|
49
48
|
return "Hello, " + args.name + " — from the native TS binary";
|
|
50
49
|
});
|
|
51
50
|
|
|
52
|
-
|
|
51
|
+
app.command("log", (args) => {
|
|
53
52
|
console.log("[host] page says:", args);
|
|
54
53
|
return null;
|
|
55
54
|
});
|
|
56
55
|
|
|
57
56
|
// An async command: answers later, without freezing the window.
|
|
58
|
-
|
|
57
|
+
app.commandAsync("wait", (args, resolve) => {
|
|
59
58
|
app.sleep(args.ms, () => {
|
|
60
59
|
resolve("waited " + args.ms + "ms without blocking the UI");
|
|
61
60
|
});
|
|
62
61
|
});
|
|
63
62
|
|
|
64
|
-
|
|
63
|
+
app.command("quit", (_args) => {
|
|
65
64
|
app.quit();
|
|
66
65
|
return null;
|
|
67
66
|
});
|
|
@@ -12,56 +12,55 @@
|
|
|
12
12
|
// an `undefined` argument lowers to a zero-parameter function and fails
|
|
13
13
|
// to compile.
|
|
14
14
|
|
|
15
|
-
import {
|
|
16
|
-
defineCommands,
|
|
17
|
-
defineEvents,
|
|
18
|
-
emit,
|
|
19
|
-
on,
|
|
20
|
-
onAsync,
|
|
21
|
-
type JanelaApp,
|
|
22
|
-
} from "janela/host";
|
|
15
|
+
import { defineCommands, defineEvents, type JanelaApp } from "janela/host";
|
|
23
16
|
|
|
24
|
-
|
|
17
|
+
/** Every command this app answers. Declared once; the page checks against it. */
|
|
18
|
+
export type AppCommands = {
|
|
25
19
|
add: { args: { a: number; b: number }; result: number };
|
|
26
20
|
greet: { args: { name: string }; result: string };
|
|
27
21
|
log: { args: string; result: null };
|
|
28
22
|
wait: { args: { ms: number }; result: string };
|
|
29
23
|
quit: { args: null; result: null };
|
|
30
|
-
}
|
|
24
|
+
};
|
|
31
25
|
|
|
32
|
-
|
|
26
|
+
/** Every event this app emits, and what each one carries. */
|
|
27
|
+
export type AppEvents = {
|
|
33
28
|
added: number;
|
|
34
|
-
}
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export const commands = defineCommands<AppCommands>();
|
|
32
|
+
export const events = defineEvents<AppEvents>();
|
|
35
33
|
|
|
36
34
|
/** The contract the page imports with `import type { App } from "../src-host/main"`. */
|
|
37
35
|
export type App = { commands: typeof commands; events: typeof events };
|
|
38
36
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
37
|
+
// Typing the app with the contract is what makes `app.command` checked: the
|
|
38
|
+
// name must be one of the declared ones, `args` is inferred from it, and the
|
|
39
|
+
// return value has to match. Same for `app.emit`.
|
|
40
|
+
export function setup(app: JanelaApp<AppCommands, AppEvents>): void {
|
|
41
|
+
app.command("add", (args) => {
|
|
43
42
|
const sum = args.a + args.b;
|
|
44
|
-
emit(
|
|
43
|
+
app.emit("added", sum);
|
|
45
44
|
return sum;
|
|
46
45
|
});
|
|
47
46
|
|
|
48
|
-
|
|
47
|
+
app.command("greet", (args) => {
|
|
49
48
|
return "Hello, " + args.name + " — from the native TS binary";
|
|
50
49
|
});
|
|
51
50
|
|
|
52
|
-
|
|
51
|
+
app.command("log", (args) => {
|
|
53
52
|
console.log("[host] page says:", args);
|
|
54
53
|
return null;
|
|
55
54
|
});
|
|
56
55
|
|
|
57
56
|
// An async command: answers later, without freezing the window.
|
|
58
|
-
|
|
57
|
+
app.commandAsync("wait", (args, resolve) => {
|
|
59
58
|
app.sleep(args.ms, () => {
|
|
60
59
|
resolve("waited " + args.ms + "ms without blocking the UI");
|
|
61
60
|
});
|
|
62
61
|
});
|
|
63
62
|
|
|
64
|
-
|
|
63
|
+
app.command("quit", (_args) => {
|
|
65
64
|
app.quit();
|
|
66
65
|
return null;
|
|
67
66
|
});
|