pf2e-spellbook 1.0.0 → 1.2.1
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 +15 -0
- package/bin/cli.mjs +86 -0
- package/dist/icon.svg +3 -3
- package/dist/index.html +568 -269
- package/dist/manifest.webmanifest +2 -2
- package/dist/sw.js +1 -1
- package/package.json +22 -4
- package/src/classes.js +74 -35
- package/src/engine.js +229 -67
- package/src/icon.svg +3 -3
- package/src/manifest.webmanifest +2 -2
- package/src/styles.css +229 -151
- package/src/template.html +36 -16
- package/tools/test.mjs +102 -5
package/README.md
CHANGED
|
@@ -76,6 +76,21 @@ curse, companions/familiars).
|
|
|
76
76
|
|
|
77
77
|
## Using it
|
|
78
78
|
|
|
79
|
+
### Quick start (npm)
|
|
80
|
+
|
|
81
|
+
With [Node.js](https://nodejs.org) installed:
|
|
82
|
+
|
|
83
|
+
```sh
|
|
84
|
+
npx pf2e-spellbook
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
That downloads the latest release, serves it at `http://localhost:8724`, and opens
|
|
88
|
+
your browser. Characters are saved in that browser (per origin), so they survive
|
|
89
|
+
package updates. `--port N` picks another port (a different port is a different
|
|
90
|
+
origin, i.e. a separate set of characters); `--no-open` skips the browser launch.
|
|
91
|
+
|
|
92
|
+
### The HTML file itself
|
|
93
|
+
|
|
79
94
|
Open `dist/index.html` in any browser — no server, no install, works offline. State
|
|
80
95
|
is saved in the browser's localStorage per device.
|
|
81
96
|
|
package/bin/cli.mjs
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Serves the built spellbook from dist/ on localhost and opens the browser.
|
|
3
|
+
// The port is fixed by default: localStorage (where all characters live) is
|
|
4
|
+
// keyed by origin, so a stable http://localhost:8724 keeps characters across
|
|
5
|
+
// package updates and install locations. localhost is a secure context, so
|
|
6
|
+
// the service worker and the "Install app" PWA button work too.
|
|
7
|
+
import http from 'node:http';
|
|
8
|
+
import { readFile } from 'node:fs/promises';
|
|
9
|
+
import { fileURLToPath } from 'node:url';
|
|
10
|
+
import { spawn } from 'node:child_process';
|
|
11
|
+
|
|
12
|
+
const DEFAULT_PORT = 8724;
|
|
13
|
+
|
|
14
|
+
const args = process.argv.slice(2);
|
|
15
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
16
|
+
console.log(`Usage: pf2e-spellbook [--port N] [--no-open]
|
|
17
|
+
|
|
18
|
+
Serves the PF2e Spellbook at http://localhost:${DEFAULT_PORT} and opens your browser.
|
|
19
|
+
|
|
20
|
+
--port N listen on port N instead of ${DEFAULT_PORT} (note: characters are
|
|
21
|
+
stored per-origin, so a different port is a different spellbook)
|
|
22
|
+
--no-open don't open the browser, just serve`);
|
|
23
|
+
process.exit(0);
|
|
24
|
+
}
|
|
25
|
+
const portIdx = args.indexOf('--port');
|
|
26
|
+
const port = portIdx !== -1 ? Number(args[portIdx + 1]) : DEFAULT_PORT;
|
|
27
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
28
|
+
console.error(`pf2e-spellbook: invalid --port ${args[portIdx + 1]}`);
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
const noOpen = args.includes('--no-open');
|
|
32
|
+
|
|
33
|
+
const distDir = new URL('../dist/', import.meta.url);
|
|
34
|
+
const FILES = {
|
|
35
|
+
'/': ['index.html', 'text/html; charset=utf-8'],
|
|
36
|
+
'/index.html': ['index.html', 'text/html; charset=utf-8'],
|
|
37
|
+
'/sw.js': ['sw.js', 'text/javascript; charset=utf-8'],
|
|
38
|
+
'/manifest.webmanifest': ['manifest.webmanifest', 'application/manifest+json'],
|
|
39
|
+
'/icon.svg': ['icon.svg', 'image/svg+xml'],
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const server = http.createServer(async (req, res) => {
|
|
43
|
+
const entry = FILES[new URL(req.url, 'http://localhost').pathname];
|
|
44
|
+
if (!entry || (req.method !== 'GET' && req.method !== 'HEAD')) {
|
|
45
|
+
res.writeHead(404, { 'Content-Type': 'text/plain' }).end('Not found');
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const [file, type] = entry;
|
|
49
|
+
try {
|
|
50
|
+
const body = await readFile(new URL(file, distDir));
|
|
51
|
+
// no-cache so package updates (new sw.js/index.html) are picked up on reload
|
|
52
|
+
res.writeHead(200, {
|
|
53
|
+
'Content-Type': type,
|
|
54
|
+
'Content-Length': body.byteLength,
|
|
55
|
+
'Cache-Control': 'no-cache',
|
|
56
|
+
});
|
|
57
|
+
res.end(req.method === 'HEAD' ? undefined : body);
|
|
58
|
+
} catch {
|
|
59
|
+
res.writeHead(500, { 'Content-Type': 'text/plain' }).end('Read error');
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
function openBrowser(url) {
|
|
64
|
+
const [cmd, cmdArgs] =
|
|
65
|
+
process.platform === 'darwin' ? ['open', [url]]
|
|
66
|
+
: process.platform === 'win32' ? ['cmd', ['/c', 'start', '', url]]
|
|
67
|
+
: ['xdg-open', [url]];
|
|
68
|
+
spawn(cmd, cmdArgs, { stdio: 'ignore', detached: true }).on('error', () => {
|
|
69
|
+
console.error(`Couldn't open a browser — go to ${url}`);
|
|
70
|
+
}).unref();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const url = `http://localhost:${port}/`;
|
|
74
|
+
server.on('error', (err) => {
|
|
75
|
+
if (err.code === 'EADDRINUSE') {
|
|
76
|
+
console.log(`Port ${port} is in use — assuming the spellbook is already running at ${url}`);
|
|
77
|
+
if (!noOpen) openBrowser(url);
|
|
78
|
+
process.exit(0);
|
|
79
|
+
}
|
|
80
|
+
console.error(`pf2e-spellbook: ${err.message}`);
|
|
81
|
+
process.exit(1);
|
|
82
|
+
});
|
|
83
|
+
server.listen(port, '127.0.0.1', () => {
|
|
84
|
+
console.log(`PF2e Spellbook running at ${url} (Ctrl+C to stop)`);
|
|
85
|
+
if (!noOpen) openBrowser(url);
|
|
86
|
+
});
|
package/dist/icon.svg
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" role="img" aria-label="Spellbook">
|
|
2
|
-
<rect width="512" height="512" rx="104" fill="#
|
|
3
|
-
<g fill="none" stroke="#
|
|
2
|
+
<rect width="512" height="512" rx="104" fill="#1d2027"/>
|
|
3
|
+
<g fill="none" stroke="#7b6cf0" stroke-width="22" stroke-linejoin="round" stroke-linecap="round">
|
|
4
4
|
<path d="M256 152 C220 128 168 128 130 146 L130 372 C168 354 220 354 256 380 C292 354 344 354 382 372 L382 146 C344 128 292 128 256 152 Z"/>
|
|
5
5
|
<line x1="256" y1="152" x2="256" y2="380"/>
|
|
6
6
|
</g>
|
|
7
|
-
<path d="M366 96 l12 30 30 12 -30 12 -12 30 -12 -30 -30 -12 30 -12 z" fill="#
|
|
7
|
+
<path d="M366 96 l12 30 30 12 -30 12 -12 30 -12 -30 -30 -12 30 -12 z" fill="#e8eaf0"/>
|
|
8
8
|
</svg>
|