janela 0.3.0 → 0.4.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 +77 -11
- package/api/global.d.ts +27 -0
- package/api/index.d.ts +37 -0
- package/api/index.js +35 -0
- package/bin/janela.mjs +21 -4
- package/package.json +15 -1
- package/runtime/janela.ts +138 -134
- package/runtime/types.ts +131 -0
- package/shim/wvshim.cc +55 -8
- package/templates/index.html +4 -2
- package/templates/main.ts +1 -1
- package/templates/react/deps.json +14 -2
- package/templates/react/files/index.html +1 -1
- package/templates/react/files/src/{App.jsx → App.tsx} +19 -8
- package/templates/react/files/src/main.tsx +12 -0
- package/templates/react/files/src-host/main.ts +1 -1
- package/templates/react/files/tsconfig.json +25 -0
- package/templates/solid/deps.json +11 -2
- package/templates/solid/files/index.html +1 -1
- package/templates/solid/files/src/App.tsx +50 -0
- package/templates/solid/files/src/main.tsx +7 -0
- package/templates/solid/files/src-host/main.ts +1 -1
- package/templates/solid/files/tsconfig.json +26 -0
- package/templates/svelte/deps.json +6 -1
- package/templates/svelte/files/index.html +1 -1
- package/templates/svelte/files/src/App.svelte +10 -7
- package/templates/svelte/files/src/main.ts +7 -0
- package/templates/svelte/files/src-host/main.ts +1 -1
- package/templates/svelte/files/tsconfig.json +23 -0
- package/templates/vue/deps.json +12 -2
- package/templates/vue/files/index.html +1 -1
- package/templates/vue/files/src/App.vue +9 -7
- package/templates/vue/files/src-host/main.ts +1 -1
- package/templates/vue/files/tsconfig.json +23 -0
- package/templates/react/files/src/main.jsx +0 -9
- package/templates/solid/files/src/App.jsx +0 -35
- package/templates/solid/files/src/main.jsx +0 -4
- package/templates/svelte/files/src/main.js +0 -4
- /package/templates/vue/files/src/{main.js → main.ts} +0 -0
package/runtime/types.ts
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public host-side types for a janela app — the shapes `src-host/main.ts`
|
|
3
|
+
* works with.
|
|
4
|
+
*
|
|
5
|
+
* This file is the single definition of those types. It is both:
|
|
6
|
+
* - what `import type { JanelaApp } from "janela/host"` resolves to in an
|
|
7
|
+
* editor, via the package's exports map; and
|
|
8
|
+
* - re-exported by runtime/janela.ts, which is what the compiled build
|
|
9
|
+
* actually links against (the CLI copies both files into .janela/build/).
|
|
10
|
+
*
|
|
11
|
+
* Keep it declaration-only: no runtime code lives here.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
// Handlers take the invoked arguments as a value and return a value; the
|
|
15
|
+
// runtime owns JSON at the boundary. `args` is whatever the page passed to
|
|
16
|
+
// janela.invoke(name, args) — cast it to the shape you expect. The return
|
|
17
|
+
// value is what the page's promise resolves with.
|
|
18
|
+
//
|
|
19
|
+
// Throwing is not supported by scriptc across the FFI boundary. Use
|
|
20
|
+
// commandAsync's `reject` to fail a call, or return an error value.
|
|
21
|
+
export type CommandHandler = (args: unknown) => unknown;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* An async command: return immediately, answer later. `resolve`/`reject` take
|
|
25
|
+
* a value and settle the page's `await janela.invoke(...)` promise whenever
|
|
26
|
+
* they are called — from a later defer()/sleep() turn, or from another
|
|
27
|
+
* command. The window stays responsive for as long as the call is pending.
|
|
28
|
+
*/
|
|
29
|
+
export type AsyncCommandHandler = (
|
|
30
|
+
args: unknown,
|
|
31
|
+
resolve: (value: unknown) => void,
|
|
32
|
+
reject: (reason: unknown) => void,
|
|
33
|
+
) => void;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Completion of an async file operation. `err` is null on success; on failure
|
|
37
|
+
* it carries a Node-shaped message ("ENOENT: no such file or directory, open
|
|
38
|
+
* '/x'") and `text` is empty. Errors arrive as values, never as throws —
|
|
39
|
+
* scriptc cannot propagate an exception across the FFI boundary.
|
|
40
|
+
*/
|
|
41
|
+
export type FsCallback = (err: string | null, text: string) => void;
|
|
42
|
+
|
|
43
|
+
/** A named group of extensions offered in a dialog's file-type popup. */
|
|
44
|
+
export interface DialogFilter {
|
|
45
|
+
name: string;
|
|
46
|
+
/** Bare extensions, no dot and no glob: ["png", "jpg"]. */
|
|
47
|
+
extensions: string[];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface OpenDialogOptions {
|
|
51
|
+
title?: string;
|
|
52
|
+
/** Directory the dialog opens in. */
|
|
53
|
+
defaultPath?: string;
|
|
54
|
+
/** Allow picking more than one entry. */
|
|
55
|
+
multiple?: boolean;
|
|
56
|
+
/** Pick directories instead of files. Not supported on Windows. */
|
|
57
|
+
directory?: boolean;
|
|
58
|
+
filters?: DialogFilter[];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface SaveDialogOptions {
|
|
62
|
+
title?: string;
|
|
63
|
+
defaultPath?: string;
|
|
64
|
+
/** Filename pre-filled in the name field. */
|
|
65
|
+
defaultName?: string;
|
|
66
|
+
filters?: DialogFilter[];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface WindowConfig {
|
|
70
|
+
title: string;
|
|
71
|
+
width: number;
|
|
72
|
+
height: number;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface JanelaApp {
|
|
76
|
+
handle: number;
|
|
77
|
+
names: string[];
|
|
78
|
+
handlers: CommandHandler[];
|
|
79
|
+
/** Register a named command, callable from the page as janela.invoke(name, args). */
|
|
80
|
+
command: (name: string, h: CommandHandler) => void;
|
|
81
|
+
/** Register a command that answers later; see AsyncCommandHandler. */
|
|
82
|
+
commandAsync: (name: string, h: AsyncCommandHandler) => void;
|
|
83
|
+
/** Run fn on the next turn of the host loop — the way to slice long work. */
|
|
84
|
+
defer: (fn: () => void) => void;
|
|
85
|
+
/** Run fn after at least ms. The host loop's timer; scriptc's setTimeout
|
|
86
|
+
* cannot fire while the window is open (its loop is parked inside run()). */
|
|
87
|
+
sleep: (ms: number, fn: () => void) => void;
|
|
88
|
+
/**
|
|
89
|
+
* Read a file without blocking the window. The syscall runs on a shim
|
|
90
|
+
* worker thread; the callback lands on the UI thread on a later turn.
|
|
91
|
+
* Prefer this over node:fs readFileSync inside a command — that one blocks
|
|
92
|
+
* the loop, and with it the whole window.
|
|
93
|
+
*/
|
|
94
|
+
readFileAsync: (path: string, cb: FsCallback) => void;
|
|
95
|
+
/** Write a file without blocking the window; cb(null) on success. */
|
|
96
|
+
writeFileAsync: (
|
|
97
|
+
path: string,
|
|
98
|
+
data: string,
|
|
99
|
+
cb: (err: string | null) => void,
|
|
100
|
+
) => void;
|
|
101
|
+
/**
|
|
102
|
+
* Show the native "open" dialog. `cb` gets the chosen paths, or null if the
|
|
103
|
+
* user cancelled. The modal runs on a later turn of the UI thread, so
|
|
104
|
+
* calling this from inside a command does not block that command's reply —
|
|
105
|
+
* pair it with commandAsync when the page is waiting for the result.
|
|
106
|
+
*/
|
|
107
|
+
openFileDialog: (
|
|
108
|
+
options: OpenDialogOptions,
|
|
109
|
+
cb: (paths: string[] | null, err?: string) => void,
|
|
110
|
+
) => void;
|
|
111
|
+
/** Show the native "save" dialog; cb gets the path, or null on cancel. */
|
|
112
|
+
saveFileDialog: (
|
|
113
|
+
options: SaveDialogOptions,
|
|
114
|
+
cb: (path: string | null, err?: string) => void,
|
|
115
|
+
) => void;
|
|
116
|
+
/** Change the window title at any time, not just at startup. */
|
|
117
|
+
setTitle: (title: string) => void;
|
|
118
|
+
/**
|
|
119
|
+
* Resize the window. `hint` is webview's sizing hint: 0 none, 1 minimum,
|
|
120
|
+
* 2 maximum, 3 fixed.
|
|
121
|
+
*/
|
|
122
|
+
setSize: (width: number, height: number, hint?: number) => void;
|
|
123
|
+
/** Enter or leave fullscreen. */
|
|
124
|
+
setFullscreen: (on: boolean) => void;
|
|
125
|
+
/** Fire an event into the page; the payload is delivered as a value. */
|
|
126
|
+
emit: (event: string, payload: unknown) => void;
|
|
127
|
+
/** Close the window and make run() return. */
|
|
128
|
+
quit: () => void;
|
|
129
|
+
/** Show the page and block until the window closes. Returns the run status. */
|
|
130
|
+
run: (html: string) => number;
|
|
131
|
+
}
|
package/shim/wvshim.cc
CHANGED
|
@@ -820,18 +820,65 @@ int32_t wv_job_status(int32_t h, int32_t id) {
|
|
|
820
820
|
return j->status.load(std::memory_order_acquire);
|
|
821
821
|
}
|
|
822
822
|
|
|
823
|
-
//
|
|
824
|
-
//
|
|
825
|
-
//
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
823
|
+
// Payload size in bytes, so TS knows when it has drained the whole thing.
|
|
824
|
+
// f64 rather than i32: a payload may exceed 2 GB, and every scriptc number is
|
|
825
|
+
// a double anyway (exact for byte counts far beyond any plausible file).
|
|
826
|
+
double wv_job_size(int32_t h, int32_t id) {
|
|
827
|
+
if (!app_at(h)) return -1;
|
|
828
|
+
Job *j = job_at(id);
|
|
829
|
+
if (!j) return -1;
|
|
830
|
+
if (j->status.load(std::memory_order_acquire) == JOB_PENDING) return -1;
|
|
831
|
+
return static_cast<double>(j->data.size());
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
// Hand ONE SLICE of a finished job's payload to TS, and answer how many bytes
|
|
835
|
+
// it covered. The callback is lifetime:"call", so it runs synchronously here —
|
|
836
|
+
// on the UI thread, the only thread allowed to touch the scriptc runtime. The
|
|
837
|
+
// worker is already done by then (the caller has observed a terminal status),
|
|
838
|
+
// so `data` is stable for the whole drain.
|
|
839
|
+
//
|
|
840
|
+
// Slicing exists so the UI thread can decode a large payload across several
|
|
841
|
+
// turns instead of stalling on all of it at once; the caller advances `offset`
|
|
842
|
+
// by the returned count until it reaches wv_job_size().
|
|
843
|
+
//
|
|
844
|
+
// The slice end is pulled back to a UTF-8 sequence boundary, because scriptc
|
|
845
|
+
// decodes a `string` param as UTF-8: cutting mid-sequence would turn one
|
|
846
|
+
// character into replacement characters on both sides of the seam. Bytes that
|
|
847
|
+
// are not valid UTF-8 have no boundary to find, so after four steps the cut
|
|
848
|
+
// stands as asked and the payload is passed through unchanged.
|
|
849
|
+
double wv_job_take_at(int32_t h, int32_t id, double offset, double max_bytes,
|
|
850
|
+
void (*sink)(const uint8_t *, size_t, void *),
|
|
851
|
+
void *ctx) {
|
|
829
852
|
if (!app_at(h)) return -1;
|
|
830
853
|
Job *j = job_at(id);
|
|
831
854
|
if (!j || !sink) return -1;
|
|
832
855
|
if (j->status.load(std::memory_order_acquire) == JOB_PENDING) return -1;
|
|
833
|
-
|
|
834
|
-
|
|
856
|
+
if (offset < 0 || max_bytes < 0) return -1;
|
|
857
|
+
|
|
858
|
+
const size_t size = j->data.size();
|
|
859
|
+
const size_t off = static_cast<size_t>(offset);
|
|
860
|
+
if (off > size) return -1;
|
|
861
|
+
if (off == size) return 0;
|
|
862
|
+
|
|
863
|
+
size_t want = static_cast<size_t>(max_bytes);
|
|
864
|
+
if (want == 0) return 0;
|
|
865
|
+
size_t end = off + want;
|
|
866
|
+
if (end >= size) {
|
|
867
|
+
end = size;
|
|
868
|
+
} else {
|
|
869
|
+
const unsigned char *d =
|
|
870
|
+
reinterpret_cast<const unsigned char *>(j->data.data());
|
|
871
|
+
// d[end] is the first byte of the NEXT slice; while it is a continuation
|
|
872
|
+
// byte (10xxxxxx) the cut sits inside a character, so step back.
|
|
873
|
+
size_t e = end;
|
|
874
|
+
for (int guard = 0; guard < 4 && e > off && (d[e] & 0xc0) == 0x80; guard++) {
|
|
875
|
+
e--;
|
|
876
|
+
}
|
|
877
|
+
if (e > off) end = e;
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
sink(reinterpret_cast<const uint8_t *>(j->data.data()) + off, end - off, ctx);
|
|
881
|
+
return static_cast<double>(end - off);
|
|
835
882
|
}
|
|
836
883
|
|
|
837
884
|
// Release the slot for reuse. Refuses while the worker is still running, so a
|
package/templates/index.html
CHANGED
|
@@ -30,8 +30,10 @@
|
|
|
30
30
|
<ul id="events"></ul>
|
|
31
31
|
|
|
32
32
|
<script>
|
|
33
|
-
//
|
|
34
|
-
//
|
|
33
|
+
// This template has no bundler, so it uses the `janela` global that the
|
|
34
|
+
// host injects before the page loads. Projects with a build step should
|
|
35
|
+
// `import { invoke, listen } from "janela/api"` instead — same functions,
|
|
36
|
+
// but typed and resolvable by the bundler.
|
|
35
37
|
window.onload = async () => {
|
|
36
38
|
const out = document.getElementById("out");
|
|
37
39
|
const events = document.getElementById("events");
|
package/templates/main.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
// complete variable initializer — wrap it in any expression (`+ 0`). Plain
|
|
9
9
|
// TypeScript like everything in this file is unaffected.
|
|
10
10
|
|
|
11
|
-
import type { JanelaApp } from "
|
|
11
|
+
import type { JanelaApp } from "janela/host";
|
|
12
12
|
|
|
13
13
|
export function setup(app: JanelaApp): void {
|
|
14
14
|
app.command("add", (args) => {
|
|
@@ -1,4 +1,16 @@
|
|
|
1
1
|
{
|
|
2
|
-
"dependencies": {
|
|
3
|
-
|
|
2
|
+
"dependencies": {
|
|
3
|
+
"react": "^19.0.0",
|
|
4
|
+
"react-dom": "^19.0.0"
|
|
5
|
+
},
|
|
6
|
+
"devDependencies": {
|
|
7
|
+
"@vitejs/plugin-react": "^4.3.4",
|
|
8
|
+
"vite": "^6.0.7",
|
|
9
|
+
"@types/react": "^19.0.7",
|
|
10
|
+
"@types/react-dom": "^19.0.3",
|
|
11
|
+
"typescript": "^5.7.3"
|
|
12
|
+
},
|
|
13
|
+
"scripts": {
|
|
14
|
+
"typecheck": "tsc --noEmit"
|
|
15
|
+
}
|
|
4
16
|
}
|
|
@@ -1,30 +1,41 @@
|
|
|
1
1
|
import { useEffect, useState } from "react";
|
|
2
|
+
import { invoke, listen } from "janela/api";
|
|
2
3
|
import "./App.css";
|
|
3
4
|
|
|
4
5
|
export default function App() {
|
|
5
6
|
const [greeting, setGreeting] = useState("…");
|
|
6
7
|
const [a, setA] = useState(2);
|
|
7
8
|
const [b, setB] = useState(40);
|
|
8
|
-
const [sum, setSum] = useState(null);
|
|
9
|
-
const [events, setEvents] = useState([]);
|
|
9
|
+
const [sum, setSum] = useState<number | null>(null);
|
|
10
|
+
const [events, setEvents] = useState<string[]>([]);
|
|
10
11
|
|
|
11
12
|
useEffect(() => {
|
|
12
|
-
// Backend→frontend events. The payload arrives as a value
|
|
13
|
-
|
|
13
|
+
// Backend→frontend events. The payload arrives as a value, and the
|
|
14
|
+
// generic says which value.
|
|
15
|
+
listen<number>("added", (value) =>
|
|
14
16
|
setEvents((prev) => [`host emitted: ${value}`, ...prev]),
|
|
15
17
|
);
|
|
16
|
-
|
|
18
|
+
invoke<string>("greet", { name: "__NAME__" }).then(setGreeting);
|
|
17
19
|
}, []);
|
|
18
20
|
|
|
19
21
|
const add = async () =>
|
|
20
|
-
setSum(await
|
|
22
|
+
setSum(await invoke<number>("add", { a, b }));
|
|
21
23
|
|
|
22
24
|
return (
|
|
23
25
|
<>
|
|
24
26
|
<h1>{greeting}</h1>
|
|
25
27
|
<p>
|
|
26
|
-
<input
|
|
27
|
-
|
|
28
|
+
<input
|
|
29
|
+
type="number"
|
|
30
|
+
value={a}
|
|
31
|
+
onChange={(e) => setA(Number(e.target.value))}
|
|
32
|
+
/>{" "}
|
|
33
|
+
+
|
|
34
|
+
<input
|
|
35
|
+
type="number"
|
|
36
|
+
value={b}
|
|
37
|
+
onChange={(e) => setB(Number(e.target.value))}
|
|
38
|
+
/>
|
|
28
39
|
<button onClick={add}>add</button>
|
|
29
40
|
{sum !== null && <span> = {sum}</span>}
|
|
30
41
|
</p>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { StrictMode } from "react";
|
|
2
|
+
import { createRoot } from "react-dom/client";
|
|
3
|
+
import App from "./App.tsx";
|
|
4
|
+
|
|
5
|
+
const root = document.getElementById("root");
|
|
6
|
+
if (!root) throw new Error("index.html is missing #root");
|
|
7
|
+
|
|
8
|
+
createRoot(root).render(
|
|
9
|
+
<StrictMode>
|
|
10
|
+
<App />
|
|
11
|
+
</StrictMode>,
|
|
12
|
+
);
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
// complete variable initializer — wrap it in any expression (`+ 0`). Plain
|
|
9
9
|
// TypeScript like everything in this file is unaffected.
|
|
10
10
|
|
|
11
|
-
import type { JanelaApp } from "
|
|
11
|
+
import type { JanelaApp } from "janela/host";
|
|
12
12
|
|
|
13
13
|
export function setup(app: JanelaApp): void {
|
|
14
14
|
app.command("add", (args) => {
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"lib": [
|
|
7
|
+
"ES2022",
|
|
8
|
+
"DOM",
|
|
9
|
+
"DOM.Iterable"
|
|
10
|
+
],
|
|
11
|
+
"types": [],
|
|
12
|
+
"jsx": "react-jsx",
|
|
13
|
+
"strict": true,
|
|
14
|
+
"noEmit": true,
|
|
15
|
+
"allowImportingTsExtensions": true,
|
|
16
|
+
"skipLibCheck": true,
|
|
17
|
+
"isolatedModules": true,
|
|
18
|
+
"verbatimModuleSyntax": true
|
|
19
|
+
},
|
|
20
|
+
"include": [
|
|
21
|
+
"src/**/*.ts",
|
|
22
|
+
"src/**/*.tsx",
|
|
23
|
+
"src-host/**/*.ts"
|
|
24
|
+
]
|
|
25
|
+
}
|
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
{
|
|
2
|
-
"dependencies": {
|
|
3
|
-
|
|
2
|
+
"dependencies": {
|
|
3
|
+
"solid-js": "^1.9.4"
|
|
4
|
+
},
|
|
5
|
+
"devDependencies": {
|
|
6
|
+
"vite": "^6.0.7",
|
|
7
|
+
"vite-plugin-solid": "^2.11.0",
|
|
8
|
+
"typescript": "^5.7.3"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"typecheck": "tsc --noEmit"
|
|
12
|
+
}
|
|
4
13
|
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { createSignal, onMount, For, Show } from "solid-js";
|
|
2
|
+
import { invoke, listen } from "janela/api";
|
|
3
|
+
import "./App.css";
|
|
4
|
+
|
|
5
|
+
export default function App() {
|
|
6
|
+
const [greeting, setGreeting] = createSignal("…");
|
|
7
|
+
const [a, setA] = createSignal(2);
|
|
8
|
+
const [b, setB] = createSignal(40);
|
|
9
|
+
const [sum, setSum] = createSignal<number | null>(null);
|
|
10
|
+
const [events, setEvents] = createSignal<string[]>([]);
|
|
11
|
+
|
|
12
|
+
// Backend→frontend events. The payload arrives as a value, and the generic
|
|
13
|
+
// says which value.
|
|
14
|
+
listen<number>("added", (value) =>
|
|
15
|
+
setEvents((prev) => [`host emitted: ${value}`, ...prev]),
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
onMount(async () =>
|
|
19
|
+
setGreeting(await invoke<string>("greet", { name: "__NAME__" })),
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
const add = async () =>
|
|
23
|
+
setSum(await invoke<number>("add", { a: a(), b: b() }));
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<>
|
|
27
|
+
<h1>{greeting()}</h1>
|
|
28
|
+
<p>
|
|
29
|
+
<input
|
|
30
|
+
type="number"
|
|
31
|
+
value={a()}
|
|
32
|
+
onInput={(e) => setA(Number(e.currentTarget.value))}
|
|
33
|
+
/>{" "}
|
|
34
|
+
+
|
|
35
|
+
<input
|
|
36
|
+
type="number"
|
|
37
|
+
value={b()}
|
|
38
|
+
onInput={(e) => setB(Number(e.currentTarget.value))}
|
|
39
|
+
/>
|
|
40
|
+
<button onClick={add}>add</button>
|
|
41
|
+
<Show when={sum() !== null}>
|
|
42
|
+
<span> = {sum()}</span>
|
|
43
|
+
</Show>
|
|
44
|
+
</p>
|
|
45
|
+
<ul>
|
|
46
|
+
<For each={events()}>{(e) => <li>{e}</li>}</For>
|
|
47
|
+
</ul>
|
|
48
|
+
</>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
// complete variable initializer — wrap it in any expression (`+ 0`). Plain
|
|
9
9
|
// TypeScript like everything in this file is unaffected.
|
|
10
10
|
|
|
11
|
-
import type { JanelaApp } from "
|
|
11
|
+
import type { JanelaApp } from "janela/host";
|
|
12
12
|
|
|
13
13
|
export function setup(app: JanelaApp): void {
|
|
14
14
|
app.command("add", (args) => {
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"lib": [
|
|
7
|
+
"ES2022",
|
|
8
|
+
"DOM",
|
|
9
|
+
"DOM.Iterable"
|
|
10
|
+
],
|
|
11
|
+
"types": [],
|
|
12
|
+
"jsx": "preserve",
|
|
13
|
+
"jsxImportSource": "solid-js",
|
|
14
|
+
"strict": true,
|
|
15
|
+
"noEmit": true,
|
|
16
|
+
"allowImportingTsExtensions": true,
|
|
17
|
+
"skipLibCheck": true,
|
|
18
|
+
"isolatedModules": true,
|
|
19
|
+
"verbatimModuleSyntax": true
|
|
20
|
+
},
|
|
21
|
+
"include": [
|
|
22
|
+
"src/**/*.ts",
|
|
23
|
+
"src/**/*.tsx",
|
|
24
|
+
"src-host/**/*.ts"
|
|
25
|
+
]
|
|
26
|
+
}
|
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
"devDependencies": {
|
|
3
3
|
"@sveltejs/vite-plugin-svelte": "^5.0.3",
|
|
4
4
|
"svelte": "^5.16.0",
|
|
5
|
-
"vite": "^6.0.7"
|
|
5
|
+
"vite": "^6.0.7",
|
|
6
|
+
"svelte-check": "^4.1.4",
|
|
7
|
+
"typescript": "^5.7.3"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"typecheck": "svelte-check --tsconfig ./tsconfig.json"
|
|
6
11
|
}
|
|
7
12
|
}
|
|
@@ -1,17 +1,20 @@
|
|
|
1
|
-
<script>
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { invoke, listen } from "janela/api";
|
|
3
|
+
|
|
2
4
|
let greeting = $state("…");
|
|
3
5
|
let a = $state(2);
|
|
4
6
|
let b = $state(40);
|
|
5
|
-
let sum = $state(null);
|
|
6
|
-
let events = $state([]);
|
|
7
|
+
let sum = $state<number | null>(null);
|
|
8
|
+
let events = $state<string[]>([]);
|
|
7
9
|
|
|
8
|
-
// Backend→frontend events. The payload arrives as a value
|
|
9
|
-
|
|
10
|
+
// Backend→frontend events. The payload arrives as a value, and the generic
|
|
11
|
+
// says which value.
|
|
12
|
+
listen<number>("added", (value) => (events = [`host emitted: ${value}`, ...events]));
|
|
10
13
|
|
|
11
|
-
|
|
14
|
+
invoke<string>("greet", { name: "__NAME__" }).then((g) => (greeting = g));
|
|
12
15
|
|
|
13
16
|
async function add() {
|
|
14
|
-
sum = await
|
|
17
|
+
sum = await invoke<number>("add", { a: Number(a), b: Number(b) });
|
|
15
18
|
}
|
|
16
19
|
</script>
|
|
17
20
|
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
// complete variable initializer — wrap it in any expression (`+ 0`). Plain
|
|
9
9
|
// TypeScript like everything in this file is unaffected.
|
|
10
10
|
|
|
11
|
-
import type { JanelaApp } from "
|
|
11
|
+
import type { JanelaApp } from "janela/host";
|
|
12
12
|
|
|
13
13
|
export function setup(app: JanelaApp): void {
|
|
14
14
|
app.command("add", (args) => {
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"lib": [
|
|
7
|
+
"ES2022",
|
|
8
|
+
"DOM",
|
|
9
|
+
"DOM.Iterable"
|
|
10
|
+
],
|
|
11
|
+
"types": [],
|
|
12
|
+
"strict": true,
|
|
13
|
+
"noEmit": true,
|
|
14
|
+
"skipLibCheck": true,
|
|
15
|
+
"isolatedModules": true,
|
|
16
|
+
"verbatimModuleSyntax": true
|
|
17
|
+
},
|
|
18
|
+
"include": [
|
|
19
|
+
"src/**/*.ts",
|
|
20
|
+
"src/**/*.svelte",
|
|
21
|
+
"src-host/**/*.ts"
|
|
22
|
+
]
|
|
23
|
+
}
|
package/templates/vue/deps.json
CHANGED
|
@@ -1,4 +1,14 @@
|
|
|
1
1
|
{
|
|
2
|
-
"dependencies": {
|
|
3
|
-
|
|
2
|
+
"dependencies": {
|
|
3
|
+
"vue": "^3.5.13"
|
|
4
|
+
},
|
|
5
|
+
"devDependencies": {
|
|
6
|
+
"@vitejs/plugin-vue": "^5.2.1",
|
|
7
|
+
"vite": "^6.0.7",
|
|
8
|
+
"typescript": "^5.7.3",
|
|
9
|
+
"vue-tsc": "^2.2.0"
|
|
10
|
+
},
|
|
11
|
+
"scripts": {
|
|
12
|
+
"typecheck": "vue-tsc --noEmit"
|
|
13
|
+
}
|
|
4
14
|
}
|
|
@@ -1,21 +1,23 @@
|
|
|
1
|
-
<script setup>
|
|
1
|
+
<script setup lang="ts">
|
|
2
2
|
import { onMounted, ref } from "vue";
|
|
3
|
+
import { invoke, listen } from "janela/api";
|
|
3
4
|
|
|
4
5
|
const greeting = ref("…");
|
|
5
6
|
const a = ref(2);
|
|
6
7
|
const b = ref(40);
|
|
7
|
-
const sum = ref(null);
|
|
8
|
-
const events = ref([]);
|
|
8
|
+
const sum = ref<number | null>(null);
|
|
9
|
+
const events = ref<string[]>([]);
|
|
9
10
|
|
|
10
|
-
// Backend→frontend events. The payload arrives as a value, not a JSON string
|
|
11
|
-
|
|
11
|
+
// Backend→frontend events. The payload arrives as a value, not a JSON string,
|
|
12
|
+
// and the generic says which value.
|
|
13
|
+
listen<number>("added", (value) => events.value.unshift(`host emitted: ${value}`));
|
|
12
14
|
|
|
13
15
|
onMounted(async () => {
|
|
14
|
-
greeting.value = await
|
|
16
|
+
greeting.value = await invoke<string>("greet", { name: "__NAME__" });
|
|
15
17
|
});
|
|
16
18
|
|
|
17
19
|
async function add() {
|
|
18
|
-
sum.value = await
|
|
20
|
+
sum.value = await invoke<number>("add", { a: Number(a.value), b: Number(b.value) });
|
|
19
21
|
}
|
|
20
22
|
</script>
|
|
21
23
|
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
// complete variable initializer — wrap it in any expression (`+ 0`). Plain
|
|
9
9
|
// TypeScript like everything in this file is unaffected.
|
|
10
10
|
|
|
11
|
-
import type { JanelaApp } from "
|
|
11
|
+
import type { JanelaApp } from "janela/host";
|
|
12
12
|
|
|
13
13
|
export function setup(app: JanelaApp): void {
|
|
14
14
|
app.command("add", (args) => {
|