janela 0.14.1 → 0.14.2
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 +10 -2
- package/api/index.d.ts +11 -1
- package/bin/janela.mjs +18 -2
- package/package.json +1 -1
- package/templates/index.html +224 -53
- package/templates/janela.conf.json +2 -2
- package/templates/react/files/index.html +1 -0
- package/templates/react/files/janela.conf.json +2 -2
- package/templates/react/files/src/App.tsx +154 -37
- package/templates/react/files/src/main.tsx +1 -0
- package/templates/react/files/src/styles.css +137 -0
- package/templates/react/files/src-host/main.ts +82 -0
- package/templates/solid/files/index.html +1 -0
- package/templates/solid/files/janela.conf.json +2 -2
- package/templates/solid/files/src/App.tsx +145 -35
- package/templates/solid/files/src/main.tsx +1 -0
- package/templates/solid/files/src/styles.css +137 -0
- package/templates/solid/files/src-host/main.ts +82 -0
- package/templates/svelte/files/index.html +1 -0
- package/templates/svelte/files/janela.conf.json +2 -2
- package/templates/svelte/files/src/App.svelte +116 -27
- package/templates/svelte/files/src/main.ts +1 -0
- package/templates/svelte/files/src/styles.css +137 -0
- package/templates/svelte/files/src-host/main.ts +82 -0
- package/templates/vue/files/index.html +1 -0
- package/templates/vue/files/janela.conf.json +2 -2
- package/templates/vue/files/src/App.vue +99 -20
- package/templates/vue/files/src/main.ts +1 -0
- package/templates/vue/files/src/styles.css +137 -0
- package/templates/vue/files/src-host/main.ts +82 -0
- package/templates/react/files/src/App.css +0 -3
- package/templates/solid/files/src/App.css +0 -3
package/README.md
CHANGED
|
@@ -6,9 +6,11 @@ Desktop and mobile apps in pure TypeScript, compiled to native. No Rust, no
|
|
|
6
6
|
Node, no Electron. The backend is TypeScript compiled to a native binary by
|
|
7
7
|
[scriptc](https://scriptc.dev); the window is the OS webview via
|
|
8
8
|
[webview/webview](https://github.com/webview/webview). A desktop binary comes
|
|
9
|
-
out around
|
|
9
|
+
out around 225–420 KB — 225 KB for the smallest template — with no bundled
|
|
10
10
|
browser and no bundled runtime; iOS and Android bundles land around
|
|
11
|
-
|
|
11
|
+
228–421 KB. Those are the *starter's* figures, and about 35 KB of each is the
|
|
12
|
+
native file dialog and worker-thread reader it demonstrates: delete the "Files
|
|
13
|
+
and the window" card and its two commands and the smallest binary is 212 KB. Per-template figures are in
|
|
12
14
|
[docs/frontend.md](../../docs/frontend.md).
|
|
13
15
|
|
|
14
16
|
Five targets, one runtime — the same `main.ts`, the same typed contract and the
|
|
@@ -30,6 +32,7 @@ desktop-only for now; on mobile they report clearly when called.
|
|
|
30
32
|
|
|
31
33
|
```bash
|
|
32
34
|
npm install -g janela # or: npx janela init my-app
|
|
35
|
+
janela --version # which janela you actually have
|
|
33
36
|
janela init my-app # (or `jn init my-app`)
|
|
34
37
|
cd my-app
|
|
35
38
|
janela dev # build + run with logs in the terminal
|
|
@@ -44,6 +47,11 @@ cd my-app && npm install
|
|
|
44
47
|
janela dev # Vite dev server + HMR, in a native window
|
|
45
48
|
```
|
|
46
49
|
|
|
50
|
+
A `--template` a globally installed CLI has never heard of is now an error
|
|
51
|
+
rather than a silent fallback, and `janela --version` says which one you are
|
|
52
|
+
running — worth checking first if a scaffolded project comes out looking
|
|
53
|
+
nothing like the screenshots.
|
|
54
|
+
|
|
47
55
|
`vanilla` is the default and needs no frontend toolchain at all. With a
|
|
48
56
|
framework, `janela dev` runs your Vite dev server and points the window at it,
|
|
49
57
|
and `janela build` flattens the production bundle into the binary.
|
package/api/index.d.ts
CHANGED
|
@@ -94,10 +94,20 @@ export interface JanelaClient<A> {
|
|
|
94
94
|
/**
|
|
95
95
|
* Call a declared command. Unknown names and wrong argument shapes are
|
|
96
96
|
* compile errors, and the result type comes from the contract.
|
|
97
|
+
*
|
|
98
|
+
* A command declared to take nothing — `quit: () => void` — normalises to
|
|
99
|
+
* `args: null`, and for those the argument is omitted rather than passed as
|
|
100
|
+
* an explicit `null`. That is what the rest-tuple is for: it makes the
|
|
101
|
+
* parameter optional for exactly those commands, and required with the
|
|
102
|
+
* declared shape for every other one. The bracketed `[X] extends [null]`
|
|
103
|
+
* form is deliberate — a bare `null extends X` would also match a command
|
|
104
|
+
* whose arguments are legitimately nullable.
|
|
97
105
|
*/
|
|
98
106
|
invoke<K extends keyof CommandsOf<A> & string>(
|
|
99
107
|
name: K,
|
|
100
|
-
args: CommandsOf<A>[K]["args"]
|
|
108
|
+
...args: [CommandsOf<A>[K]["args"]] extends [null]
|
|
109
|
+
? [args?: null]
|
|
110
|
+
: [args: CommandsOf<A>[K]["args"]]
|
|
101
111
|
): Promise<CommandsOf<A>[K]["result"]>;
|
|
102
112
|
/**
|
|
103
113
|
* Subscribe to a declared event; the payload type is inferred. Returns a
|
package/bin/janela.mjs
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
// janela init <name> scaffold a new project
|
|
5
5
|
// janela build compile the project to a native binary (+ .app on macOS)
|
|
6
6
|
// janela dev build, then run the binary with logs in the terminal
|
|
7
|
+
// janela --version which janela this is
|
|
7
8
|
//
|
|
8
9
|
// A project is: index.html (frontend), src-host/main.ts (commands),
|
|
9
10
|
// janela.conf.json (window + bundle config). Everything else — the C shim over
|
|
@@ -26,6 +27,9 @@ import {
|
|
|
26
27
|
|
|
27
28
|
const KIT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
28
29
|
const require = createRequire(join(KIT, "package.json"));
|
|
30
|
+
// createRequire is rooted AT the package.json, so "./package.json" is this
|
|
31
|
+
// package's own — not the bin/ directory's and not the workspace's.
|
|
32
|
+
const VERSION = require("./package.json").version;
|
|
29
33
|
|
|
30
34
|
function fail(msg) {
|
|
31
35
|
console.error(`janela: ${msg}`);
|
|
@@ -1277,6 +1281,16 @@ async function dev(root) {
|
|
|
1277
1281
|
const argv = process.argv.slice(2);
|
|
1278
1282
|
const cmd = argv[0];
|
|
1279
1283
|
|
|
1284
|
+
// Answered before anything else, because the question it settles is "is the
|
|
1285
|
+
// janela running this command the one I think it is?". A globally installed
|
|
1286
|
+
// CLI several minor versions behind accepts `init` and quietly ignores flags it
|
|
1287
|
+
// has never heard of — `--template` did not exist before 0.3.0 — so the only
|
|
1288
|
+
// symptom is a project that came out looking wrong.
|
|
1289
|
+
if (cmd === "--version" || cmd === "-v") {
|
|
1290
|
+
console.log(VERSION);
|
|
1291
|
+
process.exit(0);
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1280
1294
|
function flag(name, fallback) {
|
|
1281
1295
|
const eq = argv.find((a) => a.startsWith(`--${name}=`));
|
|
1282
1296
|
if (eq) return eq.slice(name.length + 3);
|
|
@@ -1356,9 +1370,11 @@ switch (cmd) {
|
|
|
1356
1370
|
break;
|
|
1357
1371
|
default:
|
|
1358
1372
|
console.log(
|
|
1359
|
-
|
|
1373
|
+
`janela ${VERSION}\n\n` +
|
|
1374
|
+
"usage: janela init <name> [--template vanilla|vue|react|svelte|solid]\n" +
|
|
1360
1375
|
" janela build [--target desktop|ios|android]\n" +
|
|
1361
|
-
" janela dev [--target desktop|ios|android]"
|
|
1376
|
+
" janela dev [--target desktop|ios|android]\n" +
|
|
1377
|
+
" janela --version",
|
|
1362
1378
|
);
|
|
1363
1379
|
process.exit(cmd ? 1 : 0);
|
|
1364
1380
|
}
|
package/package.json
CHANGED
package/templates/index.html
CHANGED
|
@@ -1,33 +1,204 @@
|
|
|
1
1
|
<!doctype html>
|
|
2
|
-
<html>
|
|
2
|
+
<html lang="en">
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<title>__NAME__</title>
|
|
5
7
|
<style>
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
8
|
+
/* Everything here is yours to replace. It is deliberately one file with
|
|
9
|
+
no dependencies: this template has no bundler, so what you see is what
|
|
10
|
+
runs. The mark below is drawn in CSS rather than shipped as an image —
|
|
11
|
+
a janela frontend is embedded in the binary, so every asset has to be
|
|
12
|
+
inlined anyway, and a window needs no artwork. */
|
|
13
|
+
:root {
|
|
14
|
+
color-scheme: light dark;
|
|
15
|
+
--bg: #f6f6f7;
|
|
16
|
+
--card: #ffffff;
|
|
17
|
+
--text: #18181b;
|
|
18
|
+
--muted: #71717a;
|
|
19
|
+
--line: #e4e4e7;
|
|
20
|
+
--accent: #2563eb;
|
|
21
|
+
--accent-soft: #eff4ff;
|
|
22
|
+
--shadow: 0 1px 2px rgb(0 0 0 / 0.04), 0 8px 24px -12px rgb(0 0 0 / 0.12);
|
|
23
|
+
}
|
|
24
|
+
@media (prefers-color-scheme: dark) {
|
|
25
|
+
:root {
|
|
26
|
+
--bg: #17171a;
|
|
27
|
+
--card: #202024;
|
|
28
|
+
--text: #fafafa;
|
|
29
|
+
--muted: #a1a1aa;
|
|
30
|
+
--line: #2e2e34;
|
|
31
|
+
--accent: #60a5fa;
|
|
32
|
+
--accent-soft: #1d2433;
|
|
33
|
+
--shadow: 0 1px 2px rgb(0 0 0 / 0.3), 0 8px 24px -12px rgb(0 0 0 / 0.5);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
* { box-sizing: border-box; }
|
|
38
|
+
body {
|
|
39
|
+
margin: 0;
|
|
40
|
+
min-height: 100vh;
|
|
41
|
+
display: flex;
|
|
42
|
+
justify-content: center;
|
|
43
|
+
background: var(--bg);
|
|
44
|
+
color: var(--text);
|
|
45
|
+
font: 15px/1.55 -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
|
|
46
|
+
-webkit-font-smoothing: antialiased;
|
|
47
|
+
}
|
|
48
|
+
main { width: 100%; max-width: 760px; padding: 26px 22px 32px; }
|
|
49
|
+
|
|
50
|
+
/* --- header ------------------------------------------------------- */
|
|
51
|
+
header { text-align: center; margin-bottom: 20px; }
|
|
52
|
+
.mark {
|
|
53
|
+
width: 56px; height: 44px; margin: 0 auto 14px;
|
|
54
|
+
border: 2px solid var(--text); border-radius: 8px;
|
|
55
|
+
display: flex; flex-direction: column; overflow: hidden;
|
|
56
|
+
}
|
|
57
|
+
.mark .bar {
|
|
58
|
+
height: 13px; flex: none;
|
|
59
|
+
border-bottom: 2px solid var(--text);
|
|
60
|
+
display: flex; align-items: center; gap: 3px; padding: 0 4px;
|
|
61
|
+
}
|
|
62
|
+
.mark .bar i { width: 3px; height: 3px; border-radius: 50%; background: var(--text); }
|
|
63
|
+
.mark .pane { flex: 1; background: linear-gradient(135deg, var(--accent), transparent 70%); opacity: 0.85; }
|
|
64
|
+
h1 { margin: 0; font-size: 26px; letter-spacing: -0.02em; font-weight: 650; }
|
|
65
|
+
.sub { margin: 5px 0 0; color: var(--muted); font-size: 13.5px; }
|
|
66
|
+
|
|
67
|
+
/* The one line that proves the whole stack: this string was produced by
|
|
68
|
+
TypeScript compiled to machine code, and handed to the page. */
|
|
69
|
+
.greeting {
|
|
70
|
+
margin: 0 0 20px;
|
|
71
|
+
padding: 11px 16px;
|
|
72
|
+
border: 1px solid var(--line);
|
|
73
|
+
border-radius: 10px;
|
|
74
|
+
background: var(--accent-soft);
|
|
75
|
+
font: 13px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
76
|
+
text-align: center;
|
|
77
|
+
overflow-wrap: anywhere;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/* --- cards -------------------------------------------------------- */
|
|
81
|
+
/* auto-fit rather than a fixed column count: the same page is the iOS and
|
|
82
|
+
Android frontend, where it must read as a single column. */
|
|
83
|
+
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); gap: 13px; }
|
|
84
|
+
.card {
|
|
85
|
+
background: var(--card);
|
|
86
|
+
border: 1px solid var(--line);
|
|
87
|
+
border-radius: 12px;
|
|
88
|
+
padding: 15px 17px;
|
|
89
|
+
box-shadow: var(--shadow);
|
|
90
|
+
}
|
|
91
|
+
.card h2 {
|
|
92
|
+
margin: 0 0 2px;
|
|
93
|
+
font-size: 13px; font-weight: 600;
|
|
94
|
+
letter-spacing: 0.04em; text-transform: uppercase;
|
|
95
|
+
color: var(--muted);
|
|
96
|
+
}
|
|
97
|
+
.card.wide { grid-column: 1 / -1; }
|
|
98
|
+
.card p.why { margin: 0 0 11px; color: var(--muted); font-size: 12.5px; line-height: 1.45; }
|
|
99
|
+
.card h3 {
|
|
100
|
+
margin: 16px 0 6px; font-size: 11px; font-weight: 600;
|
|
101
|
+
letter-spacing: 0.04em; text-transform: uppercase; color: var(--muted);
|
|
102
|
+
}
|
|
103
|
+
.row { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
|
|
104
|
+
|
|
105
|
+
input, button {
|
|
106
|
+
font: inherit;
|
|
107
|
+
border-radius: 8px;
|
|
108
|
+
border: 1px solid var(--line);
|
|
109
|
+
padding: 8px 12px;
|
|
110
|
+
background: var(--card);
|
|
111
|
+
color: var(--text);
|
|
112
|
+
transition: border-color 0.15s, background 0.15s;
|
|
113
|
+
}
|
|
114
|
+
input { min-width: 0; }
|
|
115
|
+
input[type="number"] { width: 5.5em; text-align: center; }
|
|
116
|
+
input.grow { flex: 1 1 12em; }
|
|
117
|
+
input:focus-visible, button:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
|
|
118
|
+
button { cursor: pointer; font-weight: 500; }
|
|
119
|
+
button:hover:not(:disabled) { border-color: var(--accent); color: var(--accent); }
|
|
120
|
+
button:disabled { opacity: 0.5; cursor: default; }
|
|
121
|
+
button.primary { background: var(--accent); border-color: var(--accent); color: #fff; }
|
|
122
|
+
button.primary:hover:not(:disabled) { filter: brightness(1.08); color: #fff; }
|
|
123
|
+
|
|
124
|
+
.result { margin: 12px 0 0; font: 13px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace; color: var(--muted); }
|
|
125
|
+
.result.filled { color: var(--text); }
|
|
126
|
+
pre.result { white-space: pre-wrap; overflow-wrap: anywhere; max-height: 11em; overflow: auto; margin-top: 12px; }
|
|
127
|
+
|
|
128
|
+
/* A dot that keeps moving while a 2s command is pending. If it stalls,
|
|
129
|
+
the host has blocked the UI thread — which is the bug this proves is
|
|
130
|
+
absent. */
|
|
131
|
+
.pulse { width: 8px; height: 8px; border-radius: 50%; background: var(--accent); animation: pulse 1s ease-in-out infinite; }
|
|
132
|
+
@keyframes pulse { 0%, 100% { opacity: 0.25; transform: scale(0.8); } 50% { opacity: 1; transform: scale(1.15); } }
|
|
133
|
+
@media (prefers-reduced-motion: reduce) { .pulse { animation: none; opacity: 1; } }
|
|
134
|
+
|
|
135
|
+
ul.events { list-style: none; margin: 0; padding: 0; font: 13px/1.6 ui-monospace, SFMono-Regular, Menlo, monospace; }
|
|
136
|
+
ul.events li { padding: 3px 0; border-bottom: 1px dashed var(--line); }
|
|
137
|
+
ul.events li:last-child { border-bottom: 0; }
|
|
138
|
+
ul.events:empty::after { content: "nothing yet — press add"; color: var(--muted); font-family: inherit; }
|
|
139
|
+
|
|
140
|
+
footer { margin-top: 18px; display: flex; flex-wrap: wrap; gap: 10px; align-items: center; justify-content: center; color: var(--muted); font-size: 13px; }
|
|
141
|
+
footer code { background: var(--card); border: 1px solid var(--line); border-radius: 5px; padding: 1px 5px; font-size: 12px; }
|
|
142
|
+
|
|
9
143
|
</style>
|
|
10
144
|
</head>
|
|
11
145
|
<body>
|
|
12
|
-
<
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
<
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
146
|
+
<main>
|
|
147
|
+
<header>
|
|
148
|
+
<div class="mark" aria-hidden="true">
|
|
149
|
+
<div class="bar"><i></i><i></i><i></i></div>
|
|
150
|
+
<div class="pane"></div>
|
|
151
|
+
</div>
|
|
152
|
+
<h1>__NAME__</h1>
|
|
153
|
+
<p class="sub">TypeScript, compiled to a native binary. No Rust, no Node, no Electron.</p>
|
|
154
|
+
</header>
|
|
155
|
+
|
|
156
|
+
<p class="greeting" id="out">booting…</p>
|
|
157
|
+
|
|
158
|
+
<div class="grid">
|
|
159
|
+
<section class="card">
|
|
160
|
+
<h2>Typed commands</h2>
|
|
161
|
+
<p class="why">The name, the arguments and the result are all checked at compile time — no codegen, because both sides are TypeScript.</p>
|
|
162
|
+
<div class="row">
|
|
163
|
+
<input id="a" type="number" value="2" aria-label="first number" />
|
|
164
|
+
<span aria-hidden="true">+</span>
|
|
165
|
+
<input id="b" type="number" value="40" aria-label="second number" />
|
|
166
|
+
<button id="add" class="primary">add</button>
|
|
167
|
+
</div>
|
|
168
|
+
<p class="result" id="sum">—</p>
|
|
169
|
+
<h3>Events from the host</h3>
|
|
170
|
+
<ul class="events" id="events"></ul>
|
|
171
|
+
</section>
|
|
172
|
+
|
|
173
|
+
<section class="card">
|
|
174
|
+
<h2>Async without blocking</h2>
|
|
175
|
+
<p class="why">The window keeps answering while this is pending: the dot keeps moving and <strong>add</strong> still works.</p>
|
|
176
|
+
<div class="row">
|
|
177
|
+
<button id="wait">wait 2s</button>
|
|
178
|
+
<span id="spinner" hidden><span class="pulse"></span></span>
|
|
179
|
+
</div>
|
|
180
|
+
<p class="result" id="waited">—</p>
|
|
181
|
+
</section>
|
|
182
|
+
|
|
183
|
+
<section class="card wide">
|
|
184
|
+
<h2>Files and the window</h2>
|
|
185
|
+
<p class="why">Reads run on a worker thread, so a large file never freezes the window. The dialog is the real native one.</p>
|
|
186
|
+
<div class="row">
|
|
187
|
+
<input id="path" class="grow" type="text" value="janela.conf.json" aria-label="file to read" />
|
|
188
|
+
<button id="read">read</button>
|
|
189
|
+
<button id="open">pick a file…</button>
|
|
190
|
+
<input id="title" class="grow" type="text" value="__NAME__ — renamed" aria-label="window title" />
|
|
191
|
+
<button id="retitle">set title</button>
|
|
192
|
+
</div>
|
|
193
|
+
<pre class="result" id="file">—</pre>
|
|
194
|
+
</section>
|
|
195
|
+
</div>
|
|
196
|
+
|
|
197
|
+
<footer>
|
|
198
|
+
<span>Edit <code>index.html</code> for the page and <code>src-host/main.ts</code> for the commands.</span>
|
|
199
|
+
<button id="quit">Quit</button>
|
|
200
|
+
</footer>
|
|
201
|
+
</main>
|
|
31
202
|
|
|
32
203
|
<script>
|
|
33
204
|
// This template has no bundler, so it uses the `janela` global that the
|
|
@@ -35,56 +206,56 @@
|
|
|
35
206
|
// `import { invoke, listen } from "janela/api"` instead — same functions,
|
|
36
207
|
// but typed and resolvable by the bundler.
|
|
37
208
|
window.onload = async () => {
|
|
38
|
-
const
|
|
39
|
-
const
|
|
209
|
+
const $ = (id) => document.getElementById(id);
|
|
210
|
+
const show = (el, text) => {
|
|
211
|
+
el.textContent = text;
|
|
212
|
+
el.classList.add("filled");
|
|
213
|
+
};
|
|
40
214
|
|
|
41
215
|
janela.listen("added", (sum) => {
|
|
42
216
|
const li = document.createElement("li");
|
|
43
|
-
li.textContent = "
|
|
44
|
-
events.prepend(li);
|
|
217
|
+
li.textContent = "host emitted 'added': " + sum;
|
|
218
|
+
$("events").prepend(li);
|
|
45
219
|
});
|
|
46
220
|
|
|
47
|
-
|
|
221
|
+
// The greeting is a round trip: the string is built in compiled
|
|
222
|
+
// TypeScript and returned to the page.
|
|
223
|
+
$("out").textContent = await janela.invoke("greet", { name: "__NAME__" });
|
|
48
224
|
await janela.invoke("log", "page loaded");
|
|
49
225
|
|
|
50
|
-
|
|
51
|
-
const a = Number(
|
|
52
|
-
const b = Number(
|
|
53
|
-
|
|
54
|
-
out.textContent = `add(${a}, ${b}) -> ${sum}`;
|
|
226
|
+
$("add").onclick = async () => {
|
|
227
|
+
const a = Number($("a").value);
|
|
228
|
+
const b = Number($("b").value);
|
|
229
|
+
show($("sum"), `add(${a}, ${b}) → ${await janela.invoke("add", { a, b })}`);
|
|
55
230
|
};
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
document.getElementById("wait").onclick = async (e) => {
|
|
231
|
+
|
|
232
|
+
$("wait").onclick = async (e) => {
|
|
59
233
|
e.target.disabled = true;
|
|
60
|
-
|
|
61
|
-
|
|
234
|
+
$("spinner").hidden = false;
|
|
235
|
+
show($("waited"), "waiting… try add, it still answers");
|
|
236
|
+
const answer = await janela.invoke("wait", { ms: 2000 });
|
|
237
|
+
show($("waited"), answer);
|
|
238
|
+
$("spinner").hidden = true;
|
|
62
239
|
e.target.disabled = false;
|
|
63
240
|
};
|
|
64
241
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
document.getElementById("read").onclick = async () => {
|
|
68
|
-
const path = document.getElementById("path").value;
|
|
242
|
+
$("read").onclick = async () => {
|
|
243
|
+
const path = $("path").value;
|
|
69
244
|
const r = await janela.invoke("readFile", { path });
|
|
70
|
-
|
|
71
|
-
? `${path}: ${r.length} chars\n\n` + r.text.slice(0, 400)
|
|
72
|
-
: "error: " + r.error;
|
|
245
|
+
show($("file"), r.ok ? `${path} — ${r.length} chars\n\n${r.text.slice(0, 400)}` : "error: " + r.error);
|
|
73
246
|
};
|
|
74
247
|
|
|
75
248
|
// The native open dialog. The window keeps serving other commands
|
|
76
249
|
// while the user is deciding.
|
|
77
|
-
|
|
250
|
+
$("open").onclick = async () => {
|
|
78
251
|
const r = await janela.invoke("openFile", {});
|
|
79
|
-
if (!r.ok)
|
|
80
|
-
else if (r.cancelled)
|
|
81
|
-
else
|
|
252
|
+
if (!r.ok) show($("file"), "error: " + r.error);
|
|
253
|
+
else if (r.cancelled) show($("file"), "cancelled");
|
|
254
|
+
else show($("file"), `${r.path} — ${r.length} chars\n\n${r.text.slice(0, 400)}`);
|
|
82
255
|
};
|
|
83
256
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
document.getElementById("quit").onclick = () => janela.invoke("quit");
|
|
257
|
+
$("retitle").onclick = () => janela.invoke("setTitle", { title: $("title").value });
|
|
258
|
+
$("quit").onclick = () => janela.invoke("quit");
|
|
88
259
|
};
|
|
89
260
|
</script>
|
|
90
261
|
</body>
|