janela 0.2.0 → 0.3.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 +66 -12
- package/bin/janela.mjs +351 -39
- package/package.json +1 -1
- package/runtime/janela.ts +170 -25
- package/shim/wvshim.cc +539 -63
- package/templates/index.html +17 -0
- package/templates/main.ts +32 -0
- package/templates/react/deps.json +4 -0
- package/templates/react/files/index.html +11 -0
- package/templates/react/files/janela.conf.json +10 -0
- package/templates/react/files/src/App.css +3 -0
- package/templates/react/files/src/App.jsx +38 -0
- package/templates/react/files/src/main.jsx +9 -0
- package/templates/react/files/src-host/main.ts +42 -0
- package/templates/react/files/vite.config.js +6 -0
- package/templates/solid/deps.json +4 -0
- package/templates/solid/files/index.html +11 -0
- package/templates/solid/files/janela.conf.json +10 -0
- package/templates/solid/files/src/App.css +3 -0
- package/templates/solid/files/src/App.jsx +35 -0
- package/templates/solid/files/src/main.jsx +4 -0
- package/templates/solid/files/src-host/main.ts +42 -0
- package/templates/solid/files/vite.config.js +6 -0
- package/templates/svelte/deps.json +7 -0
- package/templates/svelte/files/index.html +11 -0
- package/templates/svelte/files/janela.conf.json +10 -0
- package/templates/svelte/files/src/App.svelte +33 -0
- package/templates/svelte/files/src/main.js +4 -0
- package/templates/svelte/files/src-host/main.ts +42 -0
- package/templates/svelte/files/svelte.config.js +3 -0
- package/templates/svelte/files/vite.config.js +6 -0
- package/templates/vue/deps.json +4 -0
- package/templates/vue/files/index.html +11 -0
- package/templates/vue/files/janela.conf.json +10 -0
- package/templates/vue/files/src/App.vue +39 -0
- package/templates/vue/files/src/main.js +4 -0
- package/templates/vue/files/src-host/main.ts +42 -0
- package/templates/vue/files/vite.config.js +6 -0
package/templates/index.html
CHANGED
|
@@ -20,6 +20,11 @@
|
|
|
20
20
|
<p>
|
|
21
21
|
<input id="path" type="text" value="janela.conf.json" size="32" />
|
|
22
22
|
<button id="read">read file (async)</button>
|
|
23
|
+
<button id="open">pick a file…</button>
|
|
24
|
+
</p>
|
|
25
|
+
<p>
|
|
26
|
+
<input id="title" type="text" value="__NAME__ — renamed" size="24" />
|
|
27
|
+
<button id="retitle">set window title</button>
|
|
23
28
|
</p>
|
|
24
29
|
<pre id="out">booting…</pre>
|
|
25
30
|
<ul id="events"></ul>
|
|
@@ -65,6 +70,18 @@
|
|
|
65
70
|
: "error: " + r.error;
|
|
66
71
|
};
|
|
67
72
|
|
|
73
|
+
// The native open dialog. The window keeps serving other commands
|
|
74
|
+
// while the user is deciding.
|
|
75
|
+
document.getElementById("open").onclick = async () => {
|
|
76
|
+
const r = await janela.invoke("openFile", {});
|
|
77
|
+
if (!r.ok) out.textContent = "error: " + r.error;
|
|
78
|
+
else if (r.cancelled) out.textContent = "cancelled";
|
|
79
|
+
else out.textContent = `${r.path}: ${r.length} chars\n\n` + r.text.slice(0, 400);
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
document.getElementById("retitle").onclick = () =>
|
|
83
|
+
janela.invoke("setTitle", { title: document.getElementById("title").value });
|
|
84
|
+
|
|
68
85
|
document.getElementById("quit").onclick = () => janela.invoke("quit");
|
|
69
86
|
};
|
|
70
87
|
</script>
|
package/templates/main.ts
CHANGED
|
@@ -51,6 +51,38 @@ export function setup(app: JanelaApp): void {
|
|
|
51
51
|
});
|
|
52
52
|
});
|
|
53
53
|
|
|
54
|
+
// The native "open" dialog, paired with the reader above — picking a file is
|
|
55
|
+
// what makes readFileAsync useful. commandAsync is the right shape here: the
|
|
56
|
+
// page's promise stays parked while the user takes as long as they like, and
|
|
57
|
+
// the window carries on serving other calls meanwhile.
|
|
58
|
+
app.commandAsync("openFile", (_args, resolve) => {
|
|
59
|
+
app.openFileDialog(
|
|
60
|
+
{ title: "Pick a file", filters: [{ name: "Text", extensions: ["txt", "md"] }] },
|
|
61
|
+
(paths, err) => {
|
|
62
|
+
if (err !== undefined) {
|
|
63
|
+
resolve({ ok: false, error: err });
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
if (paths === null) {
|
|
67
|
+
resolve({ ok: true, cancelled: true });
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
app.readFileAsync(paths[0], (rerr, text) => {
|
|
71
|
+
resolve(
|
|
72
|
+
rerr !== null
|
|
73
|
+
? { ok: false, error: rerr }
|
|
74
|
+
: { ok: true, path: paths[0], length: text.length, text: text },
|
|
75
|
+
);
|
|
76
|
+
});
|
|
77
|
+
},
|
|
78
|
+
);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// The window is yours to change at runtime, not just at startup.
|
|
82
|
+
app.command("setTitle", (args) => {
|
|
83
|
+
app.setTitle((args as { title: string }).title);
|
|
84
|
+
});
|
|
85
|
+
|
|
54
86
|
app.command("quit", (_args) => {
|
|
55
87
|
app.quit();
|
|
56
88
|
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
import "./App.css";
|
|
3
|
+
|
|
4
|
+
export default function App() {
|
|
5
|
+
const [greeting, setGreeting] = useState("…");
|
|
6
|
+
const [a, setA] = useState(2);
|
|
7
|
+
const [b, setB] = useState(40);
|
|
8
|
+
const [sum, setSum] = useState(null);
|
|
9
|
+
const [events, setEvents] = useState([]);
|
|
10
|
+
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
// Backend→frontend events. The payload arrives as a value.
|
|
13
|
+
janela.listen("added", (value) =>
|
|
14
|
+
setEvents((prev) => [`host emitted: ${value}`, ...prev]),
|
|
15
|
+
);
|
|
16
|
+
janela.invoke("greet", { name: "__NAME__" }).then(setGreeting);
|
|
17
|
+
}, []);
|
|
18
|
+
|
|
19
|
+
const add = async () =>
|
|
20
|
+
setSum(await janela.invoke("add", { a: Number(a), b: Number(b) }));
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<>
|
|
24
|
+
<h1>{greeting}</h1>
|
|
25
|
+
<p>
|
|
26
|
+
<input type="number" value={a} onChange={(e) => setA(e.target.value)} /> +
|
|
27
|
+
<input type="number" value={b} onChange={(e) => setB(e.target.value)} />
|
|
28
|
+
<button onClick={add}>add</button>
|
|
29
|
+
{sum !== null && <span> = {sum}</span>}
|
|
30
|
+
</p>
|
|
31
|
+
<ul>
|
|
32
|
+
{events.map((e, i) => (
|
|
33
|
+
<li key={i}>{e}</li>
|
|
34
|
+
))}
|
|
35
|
+
</ul>
|
|
36
|
+
</>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// src-host/main.ts — your app's backend, compiled to native code by scriptc.
|
|
2
|
+
//
|
|
3
|
+
// Register commands here; the page calls them with `await janela.invoke(name, args)`.
|
|
4
|
+
// Handlers take the arguments as a value and return a value — the runtime owns
|
|
5
|
+
// JSON at the boundary, so there is no parsing or stringifying to do here.
|
|
6
|
+
//
|
|
7
|
+
// Gotcha inherited from scriptc: never use a bare FFI-backed call as a
|
|
8
|
+
// complete variable initializer — wrap it in any expression (`+ 0`). Plain
|
|
9
|
+
// TypeScript like everything in this file is unaffected.
|
|
10
|
+
|
|
11
|
+
import type { JanelaApp } from "./janela";
|
|
12
|
+
|
|
13
|
+
export function setup(app: JanelaApp): void {
|
|
14
|
+
app.command("add", (args) => {
|
|
15
|
+
const a = args as { a: number; b: number };
|
|
16
|
+
const sum = a.a + a.b;
|
|
17
|
+
// Backend→frontend event: the page listens with janela.listen("added", …).
|
|
18
|
+
app.emit("added", sum);
|
|
19
|
+
return sum;
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
app.command("greet", (args) => {
|
|
23
|
+
const a = args as { name: string };
|
|
24
|
+
return "Hello, " + a.name + " — from the native TS binary";
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
app.command("log", (args) => {
|
|
28
|
+
console.log("[host] page says:", args as string);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// An async command: answers later, without freezing the window.
|
|
32
|
+
app.commandAsync("wait", (args, resolve) => {
|
|
33
|
+
const a = args as { ms: number };
|
|
34
|
+
app.sleep(a.ms, () => {
|
|
35
|
+
resolve("waited " + a.ms + "ms without blocking the UI");
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
app.command("quit", () => {
|
|
40
|
+
app.quit();
|
|
41
|
+
});
|
|
42
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { defineConfig } from "vite";
|
|
2
|
+
import react from "@vitejs/plugin-react";
|
|
3
|
+
|
|
4
|
+
// janela flattens this build into one HTML document at `janela build` time,
|
|
5
|
+
// so there is no server and no base path to configure.
|
|
6
|
+
export default defineConfig({ plugins: [react()] });
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { createSignal, onMount, For, Show } from "solid-js";
|
|
2
|
+
import "./App.css";
|
|
3
|
+
|
|
4
|
+
export default function App() {
|
|
5
|
+
const [greeting, setGreeting] = createSignal("…");
|
|
6
|
+
const [a, setA] = createSignal(2);
|
|
7
|
+
const [b, setB] = createSignal(40);
|
|
8
|
+
const [sum, setSum] = createSignal(null);
|
|
9
|
+
const [events, setEvents] = createSignal([]);
|
|
10
|
+
|
|
11
|
+
// Backend→frontend events. The payload arrives as a value.
|
|
12
|
+
janela.listen("added", (value) =>
|
|
13
|
+
setEvents((prev) => [`host emitted: ${value}`, ...prev]),
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
onMount(async () => setGreeting(await janela.invoke("greet", { name: "__NAME__" })));
|
|
17
|
+
|
|
18
|
+
const add = async () =>
|
|
19
|
+
setSum(await janela.invoke("add", { a: Number(a()), b: Number(b()) }));
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<>
|
|
23
|
+
<h1>{greeting()}</h1>
|
|
24
|
+
<p>
|
|
25
|
+
<input type="number" value={a()} onInput={(e) => setA(e.currentTarget.value)} /> +
|
|
26
|
+
<input type="number" value={b()} onInput={(e) => setB(e.currentTarget.value)} />
|
|
27
|
+
<button onClick={add}>add</button>
|
|
28
|
+
<Show when={sum() !== null}><span> = {sum()}</span></Show>
|
|
29
|
+
</p>
|
|
30
|
+
<ul>
|
|
31
|
+
<For each={events()}>{(e) => <li>{e}</li>}</For>
|
|
32
|
+
</ul>
|
|
33
|
+
</>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// src-host/main.ts — your app's backend, compiled to native code by scriptc.
|
|
2
|
+
//
|
|
3
|
+
// Register commands here; the page calls them with `await janela.invoke(name, args)`.
|
|
4
|
+
// Handlers take the arguments as a value and return a value — the runtime owns
|
|
5
|
+
// JSON at the boundary, so there is no parsing or stringifying to do here.
|
|
6
|
+
//
|
|
7
|
+
// Gotcha inherited from scriptc: never use a bare FFI-backed call as a
|
|
8
|
+
// complete variable initializer — wrap it in any expression (`+ 0`). Plain
|
|
9
|
+
// TypeScript like everything in this file is unaffected.
|
|
10
|
+
|
|
11
|
+
import type { JanelaApp } from "./janela";
|
|
12
|
+
|
|
13
|
+
export function setup(app: JanelaApp): void {
|
|
14
|
+
app.command("add", (args) => {
|
|
15
|
+
const a = args as { a: number; b: number };
|
|
16
|
+
const sum = a.a + a.b;
|
|
17
|
+
// Backend→frontend event: the page listens with janela.listen("added", …).
|
|
18
|
+
app.emit("added", sum);
|
|
19
|
+
return sum;
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
app.command("greet", (args) => {
|
|
23
|
+
const a = args as { name: string };
|
|
24
|
+
return "Hello, " + a.name + " — from the native TS binary";
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
app.command("log", (args) => {
|
|
28
|
+
console.log("[host] page says:", args as string);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// An async command: answers later, without freezing the window.
|
|
32
|
+
app.commandAsync("wait", (args, resolve) => {
|
|
33
|
+
const a = args as { ms: number };
|
|
34
|
+
app.sleep(a.ms, () => {
|
|
35
|
+
resolve("waited " + a.ms + "ms without blocking the UI");
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
app.command("quit", () => {
|
|
40
|
+
app.quit();
|
|
41
|
+
});
|
|
42
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { defineConfig } from "vite";
|
|
2
|
+
import solid from "vite-plugin-solid";
|
|
3
|
+
|
|
4
|
+
// janela flattens this build into one HTML document at `janela build` time,
|
|
5
|
+
// so there is no server and no base path to configure.
|
|
6
|
+
export default defineConfig({ plugins: [solid()] });
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
let greeting = $state("…");
|
|
3
|
+
let a = $state(2);
|
|
4
|
+
let b = $state(40);
|
|
5
|
+
let sum = $state(null);
|
|
6
|
+
let events = $state([]);
|
|
7
|
+
|
|
8
|
+
// Backend→frontend events. The payload arrives as a value.
|
|
9
|
+
janela.listen("added", (value) => (events = [`host emitted: ${value}`, ...events]));
|
|
10
|
+
|
|
11
|
+
janela.invoke("greet", { name: "__NAME__" }).then((g) => (greeting = g));
|
|
12
|
+
|
|
13
|
+
async function add() {
|
|
14
|
+
sum = await janela.invoke("add", { a: Number(a), b: Number(b) });
|
|
15
|
+
}
|
|
16
|
+
</script>
|
|
17
|
+
|
|
18
|
+
<h1>{greeting}</h1>
|
|
19
|
+
<p>
|
|
20
|
+
<input type="number" bind:value={a} /> +
|
|
21
|
+
<input type="number" bind:value={b} />
|
|
22
|
+
<button onclick={add}>add</button>
|
|
23
|
+
{#if sum !== null}<span> = {sum}</span>{/if}
|
|
24
|
+
</p>
|
|
25
|
+
<ul>
|
|
26
|
+
{#each events as e}<li>{e}</li>{/each}
|
|
27
|
+
</ul>
|
|
28
|
+
|
|
29
|
+
<style>
|
|
30
|
+
:global(body) { font: 16px -apple-system, system-ui, sans-serif; padding: 2rem; }
|
|
31
|
+
input { width: 5em; }
|
|
32
|
+
ul { color: #666; font-size: 13px; }
|
|
33
|
+
</style>
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// src-host/main.ts — your app's backend, compiled to native code by scriptc.
|
|
2
|
+
//
|
|
3
|
+
// Register commands here; the page calls them with `await janela.invoke(name, args)`.
|
|
4
|
+
// Handlers take the arguments as a value and return a value — the runtime owns
|
|
5
|
+
// JSON at the boundary, so there is no parsing or stringifying to do here.
|
|
6
|
+
//
|
|
7
|
+
// Gotcha inherited from scriptc: never use a bare FFI-backed call as a
|
|
8
|
+
// complete variable initializer — wrap it in any expression (`+ 0`). Plain
|
|
9
|
+
// TypeScript like everything in this file is unaffected.
|
|
10
|
+
|
|
11
|
+
import type { JanelaApp } from "./janela";
|
|
12
|
+
|
|
13
|
+
export function setup(app: JanelaApp): void {
|
|
14
|
+
app.command("add", (args) => {
|
|
15
|
+
const a = args as { a: number; b: number };
|
|
16
|
+
const sum = a.a + a.b;
|
|
17
|
+
// Backend→frontend event: the page listens with janela.listen("added", …).
|
|
18
|
+
app.emit("added", sum);
|
|
19
|
+
return sum;
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
app.command("greet", (args) => {
|
|
23
|
+
const a = args as { name: string };
|
|
24
|
+
return "Hello, " + a.name + " — from the native TS binary";
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
app.command("log", (args) => {
|
|
28
|
+
console.log("[host] page says:", args as string);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// An async command: answers later, without freezing the window.
|
|
32
|
+
app.commandAsync("wait", (args, resolve) => {
|
|
33
|
+
const a = args as { ms: number };
|
|
34
|
+
app.sleep(a.ms, () => {
|
|
35
|
+
resolve("waited " + a.ms + "ms without blocking the UI");
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
app.command("quit", () => {
|
|
40
|
+
app.quit();
|
|
41
|
+
});
|
|
42
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { defineConfig } from "vite";
|
|
2
|
+
import { svelte } from "@sveltejs/vite-plugin-svelte";
|
|
3
|
+
|
|
4
|
+
// janela flattens this build into one HTML document at `janela build` time,
|
|
5
|
+
// so there is no server and no base path to configure.
|
|
6
|
+
export default defineConfig({ plugins: [svelte()] });
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { onMounted, ref } from "vue";
|
|
3
|
+
|
|
4
|
+
const greeting = ref("…");
|
|
5
|
+
const a = ref(2);
|
|
6
|
+
const b = ref(40);
|
|
7
|
+
const sum = ref(null);
|
|
8
|
+
const events = ref([]);
|
|
9
|
+
|
|
10
|
+
// Backend→frontend events. The payload arrives as a value, not a JSON string.
|
|
11
|
+
janela.listen("added", (value) => events.value.unshift(`host emitted: ${value}`));
|
|
12
|
+
|
|
13
|
+
onMounted(async () => {
|
|
14
|
+
greeting.value = await janela.invoke("greet", { name: "__NAME__" });
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
async function add() {
|
|
18
|
+
sum.value = await janela.invoke("add", { a: Number(a.value), b: Number(b.value) });
|
|
19
|
+
}
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
<template>
|
|
23
|
+
<h1>{{ greeting }}</h1>
|
|
24
|
+
<p>
|
|
25
|
+
<input v-model="a" type="number" /> +
|
|
26
|
+
<input v-model="b" type="number" />
|
|
27
|
+
<button @click="add">add</button>
|
|
28
|
+
<span v-if="sum !== null"> = {{ sum }}</span>
|
|
29
|
+
</p>
|
|
30
|
+
<ul>
|
|
31
|
+
<li v-for="(e, i) in events" :key="i">{{ e }}</li>
|
|
32
|
+
</ul>
|
|
33
|
+
</template>
|
|
34
|
+
|
|
35
|
+
<style>
|
|
36
|
+
body { font: 16px -apple-system, system-ui, sans-serif; padding: 2rem; }
|
|
37
|
+
input { width: 5em; }
|
|
38
|
+
ul { color: #666; font-size: 13px; }
|
|
39
|
+
</style>
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// src-host/main.ts — your app's backend, compiled to native code by scriptc.
|
|
2
|
+
//
|
|
3
|
+
// Register commands here; the page calls them with `await janela.invoke(name, args)`.
|
|
4
|
+
// Handlers take the arguments as a value and return a value — the runtime owns
|
|
5
|
+
// JSON at the boundary, so there is no parsing or stringifying to do here.
|
|
6
|
+
//
|
|
7
|
+
// Gotcha inherited from scriptc: never use a bare FFI-backed call as a
|
|
8
|
+
// complete variable initializer — wrap it in any expression (`+ 0`). Plain
|
|
9
|
+
// TypeScript like everything in this file is unaffected.
|
|
10
|
+
|
|
11
|
+
import type { JanelaApp } from "./janela";
|
|
12
|
+
|
|
13
|
+
export function setup(app: JanelaApp): void {
|
|
14
|
+
app.command("add", (args) => {
|
|
15
|
+
const a = args as { a: number; b: number };
|
|
16
|
+
const sum = a.a + a.b;
|
|
17
|
+
// Backend→frontend event: the page listens with janela.listen("added", …).
|
|
18
|
+
app.emit("added", sum);
|
|
19
|
+
return sum;
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
app.command("greet", (args) => {
|
|
23
|
+
const a = args as { name: string };
|
|
24
|
+
return "Hello, " + a.name + " — from the native TS binary";
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
app.command("log", (args) => {
|
|
28
|
+
console.log("[host] page says:", args as string);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// An async command: answers later, without freezing the window.
|
|
32
|
+
app.commandAsync("wait", (args, resolve) => {
|
|
33
|
+
const a = args as { ms: number };
|
|
34
|
+
app.sleep(a.ms, () => {
|
|
35
|
+
resolve("waited " + a.ms + "ms without blocking the UI");
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
app.command("quit", () => {
|
|
40
|
+
app.quit();
|
|
41
|
+
});
|
|
42
|
+
}
|