yapin.js-client 0.1.0 → 0.1.4
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 +1 -1
- package/docs/README.md +1 -0
- package/docs/about-page.md +19 -2
- package/docs/configuration.md +36 -2
- package/docs/getting-started.md +1 -1
- package/docs/installer.md +39 -12
- package/docs/ui.md +20 -3
- package/docs/welcome.md +63 -0
- package/package.json +1 -1
- package/src/build/build.js +34 -22
- package/src/build/nsis.js +763 -0
- package/src/config/defaults.js +30 -3
- package/src/config/load.js +9 -1
- package/src/desktop/preload.cjs +3 -1
- package/src/desktop/shim.cjs +60 -11
- package/src/installer/main.js +5 -0
- package/src/installer/nsis.js +75 -0
- package/src/installer/operations.js +23 -18
- package/src/installer/page.js +29 -0
- package/src/installer/processes.js +74 -19
- package/src/runtime/about.js +126 -36
- package/src/runtime/builtin-pages.js +2 -2
- package/src/runtime/client.js +21 -1
- package/src/runtime/onboarding.js +196 -0
- package/src/runtime/ui.js +52 -9
- package/src/shared/constants.js +1 -1
- package/src/shared/icons.js +52 -0
- package/types/index.d.ts +38 -1
- package/types/runtime.d.ts +26 -2
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ It's a framework, not a finished client. It provides the plumbing, so your clien
|
|
|
10
10
|
- **An About page.** Version, build, git commit and environment, out of the box. Every row can be edited, hidden or removed, and so can the whole page.
|
|
11
11
|
- **Badges.** Next to names on profile cards and messages, from a JSON file, your API, or code. Every badge is sanitised.
|
|
12
12
|
- **API access.** Named services with retries, caching and secrets stored in the operating system's key store. On desktop, requests go through the main process, checked against an allowlist that's fixed at build time.
|
|
13
|
-
- **A branded installer.** A
|
|
13
|
+
- **A branded installer.** A setup wizard on Windows, built with NSIS: one signable `setup.exe` with your logo, icon, name and version, a zip for macOS and Linux. Configured from a JSON file with three presets. It runs on the Node built into the desktop app, so the people installing need nothing else. It handles install, update, repair after desktop updates, and uninstall.
|
|
14
14
|
- **Zero dependencies.** It includes its own bundler, asar reader and writer, zip writer and test suite.
|
|
15
15
|
|
|
16
16
|
> Client apps modify Yapin’. Check Yapin’s terms before you publish one, and make clear that yours is unofficial (the installer says so by default).
|
package/docs/README.md
CHANGED
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
| [Themes](themes.md) | Theme files, variables, base styles, custom CSS |
|
|
20
20
|
| [Pages](pages.md) | Adding pages to Yapin’s settings menu |
|
|
21
21
|
| [The About page](about-page.md) | Editing, extending and removing it |
|
|
22
|
+
| [Welcome and what's new](welcome.md) | The first-run and update screens: editing, disabling, replaying |
|
|
22
23
|
| [Badges](badges.md) | Badge providers, the JSON format, where badges appear |
|
|
23
24
|
| [API access](api.md) | Services, the allowlist, secrets |
|
|
24
25
|
| [UI kit](ui.md) | Native-looking controls, toasts and dialogs |
|
package/docs/about-page.md
CHANGED
|
@@ -10,7 +10,20 @@ Every client app gets an About page in settings, filled in by the build:
|
|
|
10
10
|
| `environment` (Environment) | `environment.platform`, `environment.desktop`, `environment.electron`, `environment.chrome`, `environment.node`, `environment.os` | Where it's running. |
|
|
11
11
|
| `links` (Links) | `links.<key>` | One per entry in your config's `links`. |
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
## How it's laid out
|
|
14
|
+
|
|
15
|
+
- **A header card** for the `app` section: a logo tile, the name with a version tag (click the copy icon to copy it), and the description. Any other `app.*` field shows small underneath.
|
|
16
|
+
- **A row of buttons**: **Copy debug info** (`copy`), **Welcome screen** (`welcome`) and **What's new** (`whatsNew`) when those are on, and on desktop **Data folder** (`folder`) and **Safe mode** (`safeMode`).
|
|
17
|
+
- **A grid of compact cards**, one per other section, with labels and values side by side. Long values are cut short with the full text on hover, and copyable values get a copy icon.
|
|
18
|
+
- **Links** as small chips that open in the browser.
|
|
19
|
+
|
|
20
|
+
Each section's look is its `layout`, so any of them can be changed:
|
|
21
|
+
|
|
22
|
+
| Layout | |
|
|
23
|
+
| --- | --- |
|
|
24
|
+
| `rows` | Label and value side by side. The default for new sections. |
|
|
25
|
+
| `chips` | Compact pills: links show their label, other fields their value. The `links` section uses it. |
|
|
26
|
+
| `header` | The card at the top. Its `name`, `version` and `description` fields become the title, the version tag and the line under it. The `app` section uses it. Only the first `header` section is drawn as the header; give it to another section to change what's at the top. |
|
|
14
27
|
|
|
15
28
|
**Copy debug info** copies every row as text, plus enabled plugins and themes (with versions and sources) and the last 20 warnings and errors. It's what you want in bug reports.
|
|
16
29
|
|
|
@@ -19,10 +32,12 @@ Under the rows are buttons: **Copy debug info** (`copy`), and on desktop **Open
|
|
|
19
32
|
```js
|
|
20
33
|
about: {
|
|
21
34
|
title: 'About Nebula',
|
|
35
|
+
logo: 'sparkles', // the header tile: an icon name, SVG, or image URL (default: the first letter)
|
|
22
36
|
intro: 'Thanks for using Nebula!',
|
|
23
37
|
sections: {
|
|
24
38
|
environment: false, // hide a whole section
|
|
25
39
|
support: { title: 'Support', order: 35 }, // add one (built-in orders: 0, 10, 20, 30, 40)
|
|
40
|
+
links: { layout: 'rows' }, // show links as rows instead of chips
|
|
26
41
|
},
|
|
27
42
|
fields: {
|
|
28
43
|
'git.remote': false, // hide a row
|
|
@@ -46,8 +61,10 @@ client.about.set('app.version', { value: `${client.version} (beta)` });
|
|
|
46
61
|
client.about.set('stats.plugins', { label: 'Plugins running', value: () => String(client.plugins.list().filter((p) => p.status === 'running').length) });
|
|
47
62
|
client.about.remove('git.dirty');
|
|
48
63
|
client.about.section('stats', { title: 'Stats', order: 25 });
|
|
64
|
+
client.about.section('community', { title: 'Community', layout: 'chips' });
|
|
65
|
+
client.about.sections(); // [{ id, title, order, layout }], in order
|
|
49
66
|
client.about.removeSection('environment');
|
|
50
|
-
client.about.action('report', { label: 'Report a bug', run: (client) => client.openExternal('https://github.com/ada/nebula/issues/new') });
|
|
67
|
+
client.about.action('report', { label: 'Report a bug', icon: 'alertCircle', run: (client) => client.openExternal('https://github.com/ada/nebula/issues/new') });
|
|
51
68
|
client.about.removeAction('folder');
|
|
52
69
|
client.about.debugInfo(); // the text Copy debug info copies
|
|
53
70
|
```
|
package/docs/configuration.md
CHANGED
|
@@ -113,13 +113,47 @@ settings: {
|
|
|
113
113
|
| --- | --- | --- |
|
|
114
114
|
| `title` | `About` | |
|
|
115
115
|
| `order` | `0` | |
|
|
116
|
-
| `
|
|
117
|
-
| `
|
|
116
|
+
| `logo` | the name's first letter | The tile in the header card: an icon name, SVG, or https/data image URL. |
|
|
117
|
+
| `intro` | none | A line under the header. |
|
|
118
|
+
| `sections` | | `{ git: false }` hides a section; `{ support: { title: 'Support', order: 35 } }` adds one; `{ links: { layout: 'rows' } }` changes how one looks (`rows`, `chips` or `header`). |
|
|
118
119
|
| `fields` | | `{ 'git.remote': false }` hides a row; `{ 'support.email': { label: 'Email', value: 'help@example.com' } }` adds one. A string is a value. |
|
|
119
120
|
| `actions` | | `{ safeMode: false }` removes a button (`copy`, `folder`, `safeMode`). |
|
|
120
121
|
|
|
121
122
|
See [The About page](about-page.md) for every field id.
|
|
122
123
|
|
|
124
|
+
## `welcome` and `whatsNew`
|
|
125
|
+
|
|
126
|
+
A rounded, animated screen shown the first time the client app runs (`welcome`), and once after an update when there's something to say about it (`whatsNew`). Both are off with `enabled: false` or `welcome: false` / `whatsNew: false`, and both can be replayed at any time with `client.welcome.show()` / `client.whatsNew.show()`.
|
|
127
|
+
|
|
128
|
+
| Option | Default | |
|
|
129
|
+
| --- | --- | --- |
|
|
130
|
+
| `enabled` | `true` | |
|
|
131
|
+
| `icon` | `sparkles` / `rocket` | A built-in name, SVG markup, or an https/data image URL. See [UI kit](ui.md#icons). |
|
|
132
|
+
| `title` | `Welcome to {name}` / `What's new in {name} {version}` | `{name}` and `{version}` are replaced. |
|
|
133
|
+
| `subtitle` | none | A line under the title. |
|
|
134
|
+
| `steps` (welcome) / `entries` (whatsNew) | `[]` | `{ icon, title, description }` rows. `whatsNew` only shows once it has at least one entry. |
|
|
135
|
+
| `actionLabel` | `Get started` / `Got it` | The primary button. |
|
|
136
|
+
| `dismissible` | `true` | `false` removes the Escape/backdrop/Skip ways out, leaving only the primary button. |
|
|
137
|
+
| `dismissLabel` | `Skip` / none | The secondary button, when dismissible. |
|
|
138
|
+
|
|
139
|
+
```js
|
|
140
|
+
welcome: {
|
|
141
|
+
title: 'Welcome to {name}',
|
|
142
|
+
subtitle: "Here's what you get out of the box.",
|
|
143
|
+
steps: [
|
|
144
|
+
{ icon: 'puzzle', title: 'Plugins', description: 'Turn features on and off from Settings.' },
|
|
145
|
+
{ icon: 'palette', title: 'Themes', description: 'Apply a theme or write your own CSS.' },
|
|
146
|
+
],
|
|
147
|
+
},
|
|
148
|
+
whatsNew: {
|
|
149
|
+
entries: [
|
|
150
|
+
{ icon: 'sparkles', title: 'New in 1.2.0', description: 'A redesigned Settings page.' },
|
|
151
|
+
],
|
|
152
|
+
},
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
See [Welcome and what's new](welcome.md).
|
|
156
|
+
|
|
123
157
|
## `badges`
|
|
124
158
|
|
|
125
159
|
| Option | Default | |
|
package/docs/getting-started.md
CHANGED
|
@@ -110,7 +110,7 @@ npm run build
|
|
|
110
110
|
|
|
111
111
|
`dist/` now holds:
|
|
112
112
|
|
|
113
|
-
- `nebula-1.0.0-setup.exe`: share this with Windows users. They double-click it and
|
|
113
|
+
- `nebula-1.0.0-setup.exe`: share this with Windows users. They double-click it and a setup wizard walks them through it ([NSIS](installer.md#building-it) must be installed where you build).
|
|
114
114
|
- `nebula-1.0.0-installer.zip`: for macOS (`Install Nebula.command`) and Linux (`./install.sh`).
|
|
115
115
|
- `nebula.user.js`: the web version, for userscript managers ([The web version](web.md)).
|
|
116
116
|
- `payload/`: what gets installed (used by `yapin-client install`).
|
package/docs/installer.md
CHANGED
|
@@ -23,13 +23,41 @@ Every launcher runs `installer.mjs` with the Node that's built into the Yapin’
|
|
|
23
23
|
|
|
24
24
|
## The Windows .exe
|
|
25
25
|
|
|
26
|
-
`<id>-<version>-setup.exe` is a
|
|
26
|
+
`<id>-<version>-setup.exe` is a setup wizard, the kind people know from other Windows programs. It's built with [NSIS](https://nsis.sourceforge.io) (the Nullsoft Scriptable Install System) and has its pages:
|
|
27
27
|
|
|
28
|
-
1.
|
|
29
|
-
2.
|
|
30
|
-
3.
|
|
28
|
+
1. **Welcome**, with your logo down the side, your description and the "unofficial modification" notice.
|
|
29
|
+
2. **License**, when `marketing.terms` has text. With `requireAccept`, Next stays disabled until the box is ticked.
|
|
30
|
+
3. **Choose an option**: the desktop install it found (a list, when there are several) and what's there, then Install, Update, Reinstall or Repair, and Uninstall (with "Also delete my installed plugins, themes and settings"). Next is labelled with what it will do. If Yapin’ is open it says so, and asks before closing it (see `installer.closeApp`).
|
|
31
|
+
4. **Installing**, with a progress bar and **Show details** for each step. If something goes wrong, the error is shown and nothing is left half-done.
|
|
32
|
+
5. **Finish**, with **Open Yapin’** (ticked unless `installer.launchAfter` is false).
|
|
31
33
|
|
|
32
|
-
It runs as the current user (no administrator prompt), like the per-user desktop app it installs into.
|
|
34
|
+
The wizard does the work with the same installer as everywhere else: it unpacks the installer folder to a temporary folder, runs it with the desktop app's Node (the places in [Targets](#targets), plus any you add) with no console window, and removes the folder when it closes. It runs as the current user (no administrator prompt), like the per-user desktop app it installs into.
|
|
35
|
+
|
|
36
|
+
| Run as | Does |
|
|
37
|
+
| --- | --- |
|
|
38
|
+
| `nebula-setup.exe` | Opens the wizard. |
|
|
39
|
+
| `nebula-setup.exe /S` | Silent: does what Next would (install, update, or repair) with no window. Exits with 0 when it worked. |
|
|
40
|
+
| `nebula-setup.exe /S /UNINSTALL [/PURGE]` | Silent uninstall; `/PURGE` also deletes installed plugins, themes and settings. |
|
|
41
|
+
|
|
42
|
+
The texts are in `marketing.strings` (the keys starting with `setup`, plus `install`, `launch`, `purge`, `accept` and the messages shared with the page), so they can be changed or translated.
|
|
43
|
+
|
|
44
|
+
### Pictures, name, version and icon
|
|
45
|
+
|
|
46
|
+
The side picture (164×314) is your `accent` colour with your logo on it, and the top-right picture (150×57) is your logo on white. The logo is `marketing.logo`, or the config's `icon`, as a PNG. To use your own pictures instead, set `installer.windows.sidebar` and `installer.windows.header` to `.bmp` files of those sizes.
|
|
47
|
+
|
|
48
|
+
The .exe shows your name, description, version and authors in its file properties, and your icon (`installer.windows.icon`, or the config's `icon`: a PNG of at most 256×256 pixels, or an `.ico`) in Explorer and the taskbar.
|
|
49
|
+
|
|
50
|
+
### Building it
|
|
51
|
+
|
|
52
|
+
Building the wizard needs NSIS 3 on the machine that runs `yapin-client build`. It's found in the usual install folders, on the `PATH`, in the `MAKENSIS` environment variable, or at `installer.windows.nsis` (a path to `makensis`).
|
|
53
|
+
|
|
54
|
+
| | |
|
|
55
|
+
| --- | --- |
|
|
56
|
+
| Windows | The installer from [nsis.sourceforge.io](https://nsis.sourceforge.io/Download), or `winget install NSIS.NSIS` |
|
|
57
|
+
| macOS | `brew install makensis` |
|
|
58
|
+
| Linux | `sudo apt install nsis` (or your distribution's `nsis` package) |
|
|
59
|
+
|
|
60
|
+
Without NSIS, the build still makes a `setup.exe`, the simpler kind: a small launcher (written in C#) that opens the [installer page](#what-it-shows) in the browser. The build warns when that happens. Set `installer.windows.nsis` to `false` to always make that kind. It takes commands too:
|
|
33
61
|
|
|
34
62
|
| Run as | Does |
|
|
35
63
|
| --- | --- |
|
|
@@ -37,11 +65,7 @@ It runs as the current user (no administrator prompt), like the per-user desktop
|
|
|
37
65
|
| `nebula-setup.exe install --yes` (or any command) | Runs that installer command with no window ([options](#in-the-terminal)). |
|
|
38
66
|
| `nebula-setup.exe --extract [--to <dir>]` | Unpacks the installer folder (next to the .exe by default), for running `installer.mjs --cli` yourself. |
|
|
39
67
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
Built on Windows, the launcher is compiled for your client app with `csc.exe`, the C# compiler that comes with the .NET Framework on every Windows 10 and 11 machine. The .exe then shows your name, description, version and authors in its file properties, and your icon in Explorer and the taskbar. The icon is `installer.windows.icon`, or the config's `icon`: a PNG of at most 256×256 pixels, or an `.ico`.
|
|
43
|
-
|
|
44
|
-
Built on macOS or Linux, the .exe uses a prebuilt generic launcher instead (same behaviour, generic icon and details), and the build says so.
|
|
68
|
+
Built on Windows, that launcher is compiled for your client app with `csc.exe`, the C# compiler that comes with the .NET Framework on every Windows 10 and 11 machine, so it has your name and icon. Built on macOS or Linux, it's a prebuilt generic launcher (same behaviour, generic icon and details).
|
|
45
69
|
|
|
46
70
|
### Signing
|
|
47
71
|
|
|
@@ -55,21 +79,24 @@ installer: {
|
|
|
55
79
|
},
|
|
56
80
|
```
|
|
57
81
|
|
|
58
|
-
`{file}` is replaced with the .exe's path. Any signing tool works
|
|
82
|
+
`{file}` is replaced with the .exe's path. Any signing tool works, for either kind of .exe.
|
|
59
83
|
|
|
60
84
|
### Options
|
|
61
85
|
|
|
62
86
|
| `installer.windows` | Default | |
|
|
63
87
|
| --- | --- | --- |
|
|
64
88
|
| `exe` | `true` | Build the .exe (it's skipped when `installer.gui` is false). |
|
|
89
|
+
| `nsis` | `true` | Build the wizard when NSIS is found. `false` for the browser launcher, or the path to `makensis`. |
|
|
65
90
|
| `fileName` | `<id>-<version>-setup.exe` | |
|
|
66
91
|
| `icon` | the config's `icon` | PNG (at most 256 px) or `.ico`. |
|
|
92
|
+
| `sidebar` | generated | A 164×314 `.bmp` for the Welcome and Finish pages. |
|
|
93
|
+
| `header` | generated | A 150×57 `.bmp` for the top of the other pages. |
|
|
67
94
|
| `company` | your authors | Shown in the file's properties. |
|
|
68
95
|
| `sign` | none | A signing command containing `{file}`. |
|
|
69
96
|
|
|
70
97
|
## What it shows
|
|
71
98
|
|
|
72
|
-
|
|
99
|
+
On macOS and Linux (and from the Windows browser launcher), the installer opens a page in the browser, served from the person's own computer, built from your marketing config. It lists the desktop installs it found, with each one's state:
|
|
73
100
|
|
|
74
101
|
| State | Shown as | Buttons |
|
|
75
102
|
| --- | --- | --- |
|
package/docs/ui.md
CHANGED
|
@@ -39,6 +39,23 @@ ui.keyValue([{ label: 'Version', value: '1.0.0' }, { label: 'Link', value: ui.li
|
|
|
39
39
|
ui.icon(svgMarkupOrImageURL)
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
+
## Icons
|
|
43
|
+
|
|
44
|
+
`ui.icon()`, and anywhere else an `icon` option is accepted (page icons, About fields, `ui.card`, `ui.button`, toasts, dialogs, the [welcome and what's new screens](welcome.md)), takes:
|
|
45
|
+
|
|
46
|
+
- A built-in name: `'sparkles'`, `'rocket'`, `'puzzle'`, `'palette'`, `'sliders'`, `'info'`, `'check'`, `'x'`, `'alertTriangle'`, `'alertCircle'`, `'chevronRight'`, `'megaphone'`, `'bell'`, `'external'`, `'heart'`, `'copy'`, `'folder'`, `'shield'`.
|
|
47
|
+
- SVG markup, sanitized (no scripts, event handlers, `foreignObject` or external links).
|
|
48
|
+
- An https or `data:image/…` URL.
|
|
49
|
+
- An element.
|
|
50
|
+
|
|
51
|
+
```js
|
|
52
|
+
ui.icon('rocket')
|
|
53
|
+
ui.button({ label: 'Install', icon: 'puzzle' })
|
|
54
|
+
client.pages.add({ id: 'tools', title: 'Tools', icon: 'sliders', render })
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Every icon is a single colour that follows `currentColor`, so it matches whatever text colour it sits in, in any theme.
|
|
58
|
+
|
|
42
59
|
## Controls
|
|
43
60
|
|
|
44
61
|
```js
|
|
@@ -72,10 +89,10 @@ One control per setting, saved as the person edits, with errors shown under the
|
|
|
72
89
|
## Toasts and dialogs
|
|
73
90
|
|
|
74
91
|
```js
|
|
75
|
-
ui.toast('Saved', { type: 'success' }); // info | success | warning | error
|
|
76
|
-
ui.toast('Update available', { timeout: 0, action: { label: 'Restart', onClick: restart } });
|
|
92
|
+
ui.toast('Saved', { type: 'success' }); // info | success | warning | error — each type gets a matching icon
|
|
93
|
+
ui.toast('Update available', { timeout: 0, action: { label: 'Restart', onClick: restart }, icon: 'rocket' });
|
|
77
94
|
|
|
78
|
-
if (await ui.confirm({ title: 'Delete everything?', message: 'This can’t be undone.', confirmLabel: 'Delete', danger: true })) { … }
|
|
95
|
+
if (await ui.confirm({ title: 'Delete everything?', message: 'This can’t be undone.', confirmLabel: 'Delete', danger: true })) { … } // danger confirms get a warning icon automatically
|
|
79
96
|
|
|
80
97
|
const name = await ui.prompt({ title: 'Rename', value: current, validate: (v) => v.trim() !== '' || 'Can’t be empty' });
|
|
81
98
|
|
package/docs/welcome.md
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Welcome and what's new
|
|
2
|
+
|
|
3
|
+
Two rounded, animated screens the framework can show on top of the app:
|
|
4
|
+
|
|
5
|
+
- **Welcome**, the first time the client app runs with nothing saved yet — a rounded-corner hero icon, a title and subtitle, and a short list of steps with their own icons.
|
|
6
|
+
- **What's new**, once after an update, when `whatsNew.entries` has something for the new version.
|
|
7
|
+
|
|
8
|
+
Both fade and scale in, with the hero icon settling into place and each row sliding in in sequence; everything respects `prefers-reduced-motion`. Both are entirely optional and entirely yours to edit.
|
|
9
|
+
|
|
10
|
+
## Turning them off
|
|
11
|
+
|
|
12
|
+
```js
|
|
13
|
+
welcome: false, // no welcome screen, ever
|
|
14
|
+
whatsNew: false, // no what's new screen, ever
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Or just for one: `welcome: { enabled: false }`. `whatsNew` also stays quiet on its own whenever `entries` is empty — ship a release with nothing to say and nothing shows.
|
|
18
|
+
|
|
19
|
+
## Editing them
|
|
20
|
+
|
|
21
|
+
```js
|
|
22
|
+
welcome: {
|
|
23
|
+
icon: 'sparkles', // a built-in icon, SVG, or an image URL — see UI kit → Icons
|
|
24
|
+
title: 'Welcome to {name}', // {name} and {version} are replaced
|
|
25
|
+
subtitle: "Here's what you get out of the box.",
|
|
26
|
+
steps: [
|
|
27
|
+
{ icon: 'puzzle', title: 'Plugins', description: 'Turn features on and off from Settings.' },
|
|
28
|
+
{ icon: 'palette', title: 'Themes', description: 'Apply a theme, or write your own CSS.' },
|
|
29
|
+
],
|
|
30
|
+
actionLabel: 'Get started',
|
|
31
|
+
dismissible: true, // false removes Escape, the backdrop click, and the Skip button
|
|
32
|
+
dismissLabel: 'Skip',
|
|
33
|
+
},
|
|
34
|
+
whatsNew: {
|
|
35
|
+
icon: 'rocket',
|
|
36
|
+
title: "What's new in {name} {version}",
|
|
37
|
+
entries: [
|
|
38
|
+
{ icon: 'sparkles', title: 'A redesigned Settings page', description: 'Find things faster.' },
|
|
39
|
+
{ icon: 'puzzle', title: 'Three new plugins', description: 'Enable them from the Plugins page.' },
|
|
40
|
+
],
|
|
41
|
+
actionLabel: 'Got it',
|
|
42
|
+
},
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
A client app that only wants the hero and a message can leave `steps` / `entries` out (welcome still shows; what's new needs at least one entry to show at all).
|
|
46
|
+
|
|
47
|
+
## Replaying them from code
|
|
48
|
+
|
|
49
|
+
```js
|
|
50
|
+
client.welcome.show(); // right now, regardless of whether it's been seen
|
|
51
|
+
client.whatsNew.show();
|
|
52
|
+
|
|
53
|
+
client.welcome.reset(); // clears the "seen" flag, so it shows again next start
|
|
54
|
+
client.welcome.markSeen(); // marks it seen without showing it
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
A **Show welcome screen** / **Show what's new** button is added to the [About page](about-page.md) automatically whenever the screen is enabled (remove with `about: { actions: { welcome: false, whatsNew: false } }`).
|
|
58
|
+
|
|
59
|
+
`client.welcome` and `client.whatsNew` are `null` when turned off with `welcome: false` / `whatsNew: false`.
|
|
60
|
+
|
|
61
|
+
## How "seen" is tracked
|
|
62
|
+
|
|
63
|
+
Each screen stores one value — the version it was last seen at — in the client app's own storage, under `onboarding`. Welcome only needs to know whether it's ever run; what's new compares the stored version against `client.version`, so it shows again after every update that has entries (and only once per version).
|
package/package.json
CHANGED
package/src/build/build.js
CHANGED
|
@@ -7,6 +7,7 @@ import { pack } from './pack.js';
|
|
|
7
7
|
import { readGitInfo } from './git.js';
|
|
8
8
|
import { zipDirectory } from './zip.js';
|
|
9
9
|
import { compileLauncher, createSetupExe, findCsc, PREBUILT_LAUNCHER, signFile } from './windows.js';
|
|
10
|
+
import { buildNsisExe, findMakensis } from './nsis.js';
|
|
10
11
|
import { defaultTarget } from '../installer/paths.js';
|
|
11
12
|
import { deepMerge } from '../runtime/util.js';
|
|
12
13
|
import { apiVersion, version as frameworkVersion } from '../shared/constants.js';
|
|
@@ -65,6 +66,8 @@ export function runtimeConfig(config) {
|
|
|
65
66
|
themes,
|
|
66
67
|
pages: config.pages,
|
|
67
68
|
about: config.about,
|
|
69
|
+
welcome: config.welcome,
|
|
70
|
+
whatsNew: config.whatsNew,
|
|
68
71
|
badges: config.badges,
|
|
69
72
|
api: config.api,
|
|
70
73
|
host: config.host,
|
|
@@ -121,32 +124,38 @@ exit 1
|
|
|
121
124
|
}
|
|
122
125
|
|
|
123
126
|
/**
|
|
124
|
-
* The Windows installer: one .exe with the installer folder inside.
|
|
125
|
-
*
|
|
127
|
+
* The Windows installer: one .exe with the installer folder inside. With NSIS available it's a setup
|
|
128
|
+
* wizard (see nsis.js). Otherwise it's the small launcher that opens the installer page in the browser,
|
|
129
|
+
* compiled for this client app on Windows (its name, version and icon), or the prebuilt generic one.
|
|
126
130
|
*/
|
|
127
|
-
function buildSetupExe(config, { installerDir, outDir }) {
|
|
131
|
+
function buildSetupExe(config, { settings, installerDir, outDir }) {
|
|
128
132
|
const windows = config.installer.windows ?? {};
|
|
129
133
|
const file = join(outDir, windows.fileName ?? `${config.id}-${config.version}-setup.exe`);
|
|
134
|
+
const icon = windows.icon ?? config.icon ?? null;
|
|
135
|
+
const exeIcon = icon && /\.(png|ico)$/i.test(icon) ? icon : null;
|
|
136
|
+
const company = windows.company ?? config.authors?.map((a) => a.name).join(', ') ?? '';
|
|
137
|
+
const target = deepMerge(structuredClone(defaultTarget), config.installer.target ?? {});
|
|
138
|
+
const exeName = target.executable?.win32 ?? 'Yapper.exe';
|
|
139
|
+
const exe = (target.locations?.win32 ?? []).map((location) => `${location.replace(/[\\/]+$/, '')}\\${exeName}`);
|
|
140
|
+
|
|
141
|
+
const makensis = windows.nsis === false ? null : findMakensis({ configured: windows.nsis });
|
|
142
|
+
if (makensis) {
|
|
143
|
+
buildNsisExe({ makensis, installer: settings, source: installerDir, out: file, icon: exeIcon, sidebar: windows.sidebar, header: windows.header, company, description: config.description, exeLocations: exe });
|
|
144
|
+
if (windows.sign) signFile(windows.sign, file);
|
|
145
|
+
return { file, kind: 'nsis', warning: null };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const warnings = [];
|
|
149
|
+
if (typeof windows.nsis === 'string') warnings.push(`installer.windows.nsis: ${windows.nsis} didn't run, so the Windows .exe opens the installer in the browser instead of the setup wizard.`);
|
|
150
|
+
else if (windows.nsis !== false) warnings.push("NSIS isn't installed, so the Windows .exe opens the installer in the browser instead of the setup wizard. Install NSIS (https://nsis.sourceforge.io, apt install nsis, or brew install makensis) for the wizard.");
|
|
130
151
|
let launcher = PREBUILT_LAUNCHER;
|
|
131
|
-
let warning = null;
|
|
132
152
|
const work = mkdtempSync(join(tmpdir(), 'yc-setup-'));
|
|
133
153
|
try {
|
|
134
154
|
if (findCsc()) {
|
|
135
|
-
|
|
136
|
-
launcher = compileLauncher({
|
|
137
|
-
out: join(work, 'launcher.exe'),
|
|
138
|
-
name: config.name,
|
|
139
|
-
version: config.version,
|
|
140
|
-
description: config.description,
|
|
141
|
-
company: windows.company ?? config.authors?.map((a) => a.name).join(', ') ?? '',
|
|
142
|
-
icon: icon && /\.(png|ico)$/i.test(icon) ? icon : null,
|
|
143
|
-
});
|
|
155
|
+
launcher = compileLauncher({ out: join(work, 'launcher.exe'), name: config.name, version: config.version, description: config.description, company, icon: exeIcon });
|
|
144
156
|
} else {
|
|
145
|
-
|
|
157
|
+
warnings.push('Built the Windows .exe with the generic launcher (no csc.exe here), so it has no custom icon or file details. Build on Windows for those.');
|
|
146
158
|
}
|
|
147
|
-
const target = deepMerge(structuredClone(defaultTarget), config.installer.target ?? {});
|
|
148
|
-
const exeName = target.executable?.win32 ?? 'Yapper.exe';
|
|
149
|
-
const exe = (target.locations?.win32 ?? []).map((location) => `${location.replace(/[\\/]+$/, '')}\\${exeName}`);
|
|
150
159
|
createSetupExe({
|
|
151
160
|
launcher,
|
|
152
161
|
installerDir,
|
|
@@ -158,7 +167,7 @@ function buildSetupExe(config, { installerDir, outDir }) {
|
|
|
158
167
|
rmSync(work, { recursive: true, force: true });
|
|
159
168
|
}
|
|
160
169
|
if (windows.sign) signFile(windows.sign, file);
|
|
161
|
-
return { file, warning };
|
|
170
|
+
return { file, kind: 'launcher', warning: warnings.join(' ') || null };
|
|
162
171
|
}
|
|
163
172
|
|
|
164
173
|
function userscript(config, manifest, runtime) {
|
|
@@ -286,13 +295,15 @@ export default (args) => boot(args, { entry, plugins, themes });
|
|
|
286
295
|
let installerDir = null;
|
|
287
296
|
let zipFile = null;
|
|
288
297
|
let exeFile = null;
|
|
298
|
+
let exeKind = null;
|
|
289
299
|
if (installer) {
|
|
290
300
|
onStep('Building the installer');
|
|
291
301
|
installerDir = join(outDir, 'installer', config.id);
|
|
292
302
|
mkdirSync(installerDir, { recursive: true });
|
|
293
303
|
const installerBundle = pack({ entry: join(SRC, 'installer', 'main.js'), format: 'module', root: SRC });
|
|
294
304
|
writeFileSync(join(installerDir, 'installer.mjs'), installerBundle.code);
|
|
295
|
-
|
|
305
|
+
const settings = installerConfig(config, manifest);
|
|
306
|
+
writeFileSync(join(installerDir, 'installer.json'), JSON.stringify(settings, null, 2));
|
|
296
307
|
cpSync(payloadDir, join(installerDir, 'payload'), { recursive: true });
|
|
297
308
|
const sh = launcherScript();
|
|
298
309
|
const safeName = config.name.replace(/[^\w .-]+/g, '').trim() || config.id;
|
|
@@ -307,7 +318,7 @@ export default (args) => boot(args, { entry, plugins, themes });
|
|
|
307
318
|
}
|
|
308
319
|
writeFileSync(
|
|
309
320
|
join(installerDir, 'README.txt'),
|
|
310
|
-
`${config.name} ${config.version}\r\n\r\nWindows: run ${config.id}-${config.version}-setup.exe instead of this folder.\r\nmacOS: double-click "Install ${safeName}.command".\r\nLinux: run ./install.sh\r\nAnywhere with Node: node installer.mjs (add --cli for the terminal version).\r\n\r\
|
|
321
|
+
`${config.name} ${config.version}\r\n\r\nWindows: run ${config.id}-${config.version}-setup.exe instead of this folder.\r\nmacOS: double-click "Install ${safeName}.command".\r\nLinux: run ./install.sh\r\nAnywhere with Node: node installer.mjs (add --cli for the terminal version).\r\n\r\nOn macOS and Linux the installer opens in your browser. Close the Yapin' desktop app first, or the installer will offer to.\r\nTo remove ${config.name}, run the installer again and choose Uninstall.\r\n`,
|
|
311
322
|
);
|
|
312
323
|
if (config.installer.zip !== false) {
|
|
313
324
|
zipFile = join(outDir, `${config.id}-${config.version}-installer.zip`);
|
|
@@ -316,14 +327,15 @@ export default (args) => boot(args, { entry, plugins, themes });
|
|
|
316
327
|
const windows = config.installer.windows ?? {};
|
|
317
328
|
if (windows.exe !== false && config.installer.gui !== false) {
|
|
318
329
|
onStep('Building the Windows setup .exe');
|
|
319
|
-
const built = buildSetupExe(config, { installerDir, outDir });
|
|
330
|
+
const built = buildSetupExe(config, { settings, installerDir, outDir });
|
|
320
331
|
exeFile = built.file;
|
|
332
|
+
exeKind = built.kind;
|
|
321
333
|
if (built.warning) warnings.push(built.warning);
|
|
322
334
|
}
|
|
323
335
|
}
|
|
324
336
|
|
|
325
337
|
writeFileSync(join(outDir, 'build.json'), JSON.stringify({ id: config.id, version: config.version, mode, time: manifest.build.time, plugins: pluginFiles.map((f) => toPosix(relative(root, f))), themes: themeFiles.map((f) => toPosix(relative(root, f))), runtimeBytes: runtime.length, hash: bundled.hash }, null, 2));
|
|
326
|
-
return { outDir, payloadDir, installerDir, zipFile, exeFile, userscriptFile, manifest, plugins: pluginFiles, themes: themeFiles, files: bundled.files, warnings, ms: Date.now() - started };
|
|
338
|
+
return { outDir, payloadDir, installerDir, zipFile, exeFile, exeKind, userscriptFile, manifest, plugins: pluginFiles, themes: themeFiles, files: bundled.files, warnings, ms: Date.now() - started };
|
|
327
339
|
}
|
|
328
340
|
|
|
329
341
|
function camel(id) {
|