1681-engine 0.5.9 → 0.5.10
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/CHANGELOG.md +42 -0
- package/README.md +3 -0
- package/dist/1681-engine.js +501 -438
- package/dist/ambient/life/Birds.js +73 -0
- package/dist/ambient/life/Butterflies.js +43 -0
- package/dist/ambient/life/ChimneySmoke.js +50 -0
- package/dist/ambient/life/Drift.js +75 -0
- package/dist/ambient/life/Fireflies.js +52 -0
- package/dist/ambient/life/lifeMotion.js +49 -0
- package/dist/assets/AssetLoader.js +115 -70
- package/dist/assets/Prepainter.js +61 -0
- package/dist/assets/workerPainter.js +39 -0
- package/dist/cjs/electron/electronLike.d.ts +55 -0
- package/dist/cjs/electron/electronLike.js +7 -0
- package/dist/cjs/electron/index.d.ts +14 -0
- package/dist/cjs/electron/index.js +20 -0
- package/dist/cjs/electron/lockdown.d.ts +33 -0
- package/dist/cjs/electron/lockdown.js +69 -0
- package/dist/cjs/electron/lockdownRules.d.ts +28 -0
- package/dist/cjs/electron/lockdownRules.js +61 -0
- package/dist/cjs/electron/sessionStore.d.ts +40 -0
- package/dist/cjs/electron/sessionStore.js +77 -0
- package/dist/cjs/electron/shellId.d.ts +22 -0
- package/dist/cjs/electron/shellId.js +31 -0
- package/dist/cjs/electron/webFiles.d.ts +13 -0
- package/dist/cjs/electron/webFiles.js +52 -0
- package/dist/cjs/electron/webPatcher.d.ts +80 -0
- package/dist/cjs/electron/webPatcher.js +216 -0
- package/dist/cjs/electron/webPointers.d.ts +19 -0
- package/dist/cjs/electron/webPointers.js +35 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/updates/index.d.ts +8 -0
- package/dist/cjs/updates/index.js +21 -0
- package/dist/cjs/updates/manifest.d.ts +73 -0
- package/dist/cjs/updates/manifest.js +67 -0
- package/dist/cjs/updates/pickUpdate.d.ts +42 -0
- package/dist/cjs/updates/pickUpdate.js +52 -0
- package/dist/cjs/updates/webPlan.d.ts +20 -0
- package/dist/cjs/updates/webPlan.js +34 -0
- package/dist/electron/lockdown.js +31 -0
- package/dist/electron/lockdownRules.js +42 -0
- package/dist/electron/sessionStore.js +48 -0
- package/dist/electron/shellId.js +18 -0
- package/dist/electron/webFiles.js +36 -0
- package/dist/electron/webPatcher.js +147 -0
- package/dist/electron/webPointers.js +25 -0
- package/dist/electron.js +19 -0
- package/dist/input/haptics.js +50 -0
- package/dist/net/ApiClient.js +96 -0
- package/dist/pixel/lifeArt.js +83 -0
- package/dist/pixel.js +63 -58
- package/dist/react/ConfirmButton.js +24 -0
- package/dist/react/baseCss.js +1 -0
- package/dist/react/labels.js +7 -6
- package/dist/react/useJsonSelector.js +13 -0
- package/dist/react.js +73 -68
- package/dist/tilemap/TilemapView.js +57 -32
- package/dist/tilemap/autotile.js +103 -0
- package/dist/tilemap/collision.js +29 -18
- package/dist/tilemap/ldtk.js +21 -0
- package/dist/tilemap/ldtkParse.js +191 -0
- package/dist/types/ambient/life/Birds.d.ts +50 -0
- package/dist/types/ambient/life/Birds.d.ts.map +1 -0
- package/dist/types/ambient/life/Butterflies.d.ts +32 -0
- package/dist/types/ambient/life/Butterflies.d.ts.map +1 -0
- package/dist/types/ambient/life/ChimneySmoke.d.ts +37 -0
- package/dist/types/ambient/life/ChimneySmoke.d.ts.map +1 -0
- package/dist/types/ambient/life/Drift.d.ts +58 -0
- package/dist/types/ambient/life/Drift.d.ts.map +1 -0
- package/dist/types/ambient/life/Fireflies.d.ts +43 -0
- package/dist/types/ambient/life/Fireflies.d.ts.map +1 -0
- package/dist/types/ambient/life/lifeMotion.d.ts +63 -0
- package/dist/types/ambient/life/lifeMotion.d.ts.map +1 -0
- package/dist/types/assets/AssetLoader.d.ts +20 -0
- package/dist/types/assets/AssetLoader.d.ts.map +1 -1
- package/dist/types/assets/Prepainter.d.ts +64 -0
- package/dist/types/assets/Prepainter.d.ts.map +1 -0
- package/dist/types/assets/workerPainter.d.ts +21 -0
- package/dist/types/assets/workerPainter.d.ts.map +1 -0
- package/dist/types/electron/electronLike.d.ts +56 -0
- package/dist/types/electron/electronLike.d.ts.map +1 -0
- package/dist/types/electron/index.d.ts +15 -0
- package/dist/types/electron/index.d.ts.map +1 -0
- package/dist/types/electron/lockdown.d.ts +34 -0
- package/dist/types/electron/lockdown.d.ts.map +1 -0
- package/dist/types/electron/lockdownRules.d.ts +29 -0
- package/dist/types/electron/lockdownRules.d.ts.map +1 -0
- package/dist/types/electron/sessionStore.d.ts +41 -0
- package/dist/types/electron/sessionStore.d.ts.map +1 -0
- package/dist/types/electron/shellId.d.ts +23 -0
- package/dist/types/electron/shellId.d.ts.map +1 -0
- package/dist/types/electron/webFiles.d.ts +14 -0
- package/dist/types/electron/webFiles.d.ts.map +1 -0
- package/dist/types/electron/webPatcher.d.ts +81 -0
- package/dist/types/electron/webPatcher.d.ts.map +1 -0
- package/dist/types/electron/webPointers.d.ts +20 -0
- package/dist/types/electron/webPointers.d.ts.map +1 -0
- package/dist/types/index.d.ts +15 -2
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/input/haptics.d.ts +42 -0
- package/dist/types/input/haptics.d.ts.map +1 -0
- package/dist/types/net/ApiClient.d.ts +74 -0
- package/dist/types/net/ApiClient.d.ts.map +1 -0
- package/dist/types/pixel/index.d.ts +1 -0
- package/dist/types/pixel/index.d.ts.map +1 -1
- package/dist/types/pixel/lifeArt.d.ts +63 -0
- package/dist/types/pixel/lifeArt.d.ts.map +1 -0
- package/dist/types/react/ConfirmButton.d.ts +26 -0
- package/dist/types/react/ConfirmButton.d.ts.map +1 -0
- package/dist/types/react/baseCss.d.ts +1 -1
- package/dist/types/react/baseCss.d.ts.map +1 -1
- package/dist/types/react/index.d.ts +2 -0
- package/dist/types/react/index.d.ts.map +1 -1
- package/dist/types/react/labels.d.ts +2 -0
- package/dist/types/react/labels.d.ts.map +1 -1
- package/dist/types/react/useJsonSelector.d.ts +13 -0
- package/dist/types/react/useJsonSelector.d.ts.map +1 -0
- package/dist/types/tilemap/TilemapView.d.ts +17 -0
- package/dist/types/tilemap/TilemapView.d.ts.map +1 -1
- package/dist/types/tilemap/autotile.d.ts +95 -0
- package/dist/types/tilemap/autotile.d.ts.map +1 -0
- package/dist/types/tilemap/collision.d.ts +8 -0
- package/dist/types/tilemap/collision.d.ts.map +1 -1
- package/dist/types/tilemap/ldtk.d.ts +97 -0
- package/dist/types/tilemap/ldtk.d.ts.map +1 -0
- package/dist/types/tilemap/ldtkParse.d.ts +26 -0
- package/dist/types/tilemap/ldtkParse.d.ts.map +1 -0
- package/dist/types/updates/index.d.ts +9 -0
- package/dist/types/updates/index.d.ts.map +1 -0
- package/dist/types/updates/manifest.d.ts +74 -0
- package/dist/types/updates/manifest.d.ts.map +1 -0
- package/dist/types/updates/pickUpdate.d.ts +43 -0
- package/dist/types/updates/pickUpdate.d.ts.map +1 -0
- package/dist/types/updates/webPlan.d.ts +21 -0
- package/dist/types/updates/webPlan.d.ts.map +1 -0
- package/dist/updates/manifest.js +35 -0
- package/dist/updates/pickUpdate.js +27 -0
- package/dist/updates/webPlan.js +14 -0
- package/dist/updates.js +15 -0
- package/package.json +31 -3
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.planWeb = planWeb;
|
|
4
|
+
/**
|
|
5
|
+
* What to copy and what to download for `target`, given the web held now (`have`: path → sha256): a file whose path
|
|
6
|
+
* holds the same content is copied from there, one whose content lies under another path (a bundler renamed it) is
|
|
7
|
+
* copied from that path, and only content found nowhere is downloaded — once, however many paths share it. This is
|
|
8
|
+
* what makes a patch cost only what changed, the way a game launcher patches.
|
|
9
|
+
*/
|
|
10
|
+
function planWeb(target, have) {
|
|
11
|
+
const byHash = new Map();
|
|
12
|
+
for (const [path, sha] of have)
|
|
13
|
+
if (!byHash.has(sha))
|
|
14
|
+
byHash.set(sha, path);
|
|
15
|
+
const copy = [];
|
|
16
|
+
const download = [];
|
|
17
|
+
const fetched = new Set();
|
|
18
|
+
let bytes = 0;
|
|
19
|
+
for (const f of target.files) {
|
|
20
|
+
const there = byHash.get(f.sha256);
|
|
21
|
+
if (have.get(f.path) === f.sha256)
|
|
22
|
+
copy.push({ path: f.path, from: f.path });
|
|
23
|
+
else if (there !== undefined)
|
|
24
|
+
copy.push({ path: f.path, from: there });
|
|
25
|
+
else {
|
|
26
|
+
download.push(f);
|
|
27
|
+
if (!fetched.has(f.sha256)) {
|
|
28
|
+
fetched.add(f.sha256);
|
|
29
|
+
bytes += f.size;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return { copy, download, bytes };
|
|
34
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { isAllowedNavigation as a, isDevToolsKey as d, debuggerRequested as f, isAllowedExternal as u } from "./lockdownRules.js";
|
|
2
|
+
function g(r, n) {
|
|
3
|
+
const i = n.log ?? ((...e) => console.log(...e)), t = r.webContents, s = (e, o) => {
|
|
4
|
+
u(e, n.external) ? n.shell.openExternal(e).catch((c) => i("could not open", e, c)) : i(`refused ${o}:`, e);
|
|
5
|
+
};
|
|
6
|
+
t.setWindowOpenHandler(({ url: e }) => (s(e, "external link"), { action: "deny" })), t.on("will-navigate", (e, o) => {
|
|
7
|
+
a(o, n.devServerUrl) || (e.preventDefault(), s(o, "navigation"));
|
|
8
|
+
}), t.on("will-redirect", (e, o) => {
|
|
9
|
+
a(o, n.devServerUrl) || e.preventDefault();
|
|
10
|
+
});
|
|
11
|
+
const l = n.devToolsKey === void 0 ? "F12" : n.devToolsKey;
|
|
12
|
+
t.on("before-input-event", (e, o) => {
|
|
13
|
+
if (n.packaged) {
|
|
14
|
+
d(o) && e.preventDefault();
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
l && o.type === "keyDown" && o.key === l && (t.toggleDevTools(), e.preventDefault());
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
function w(r, n = []) {
|
|
21
|
+
const i = new Set(n);
|
|
22
|
+
r.setPermissionRequestHandler((t, s, l) => l(i.has(s))), r.setPermissionCheckHandler((t, s) => i.has(s));
|
|
23
|
+
}
|
|
24
|
+
function p(r, n = process.argv, i = (t) => process.exit(t)) {
|
|
25
|
+
return !r.isPackaged || !f(n, (t) => r.commandLine.hasSwitch(t)) ? !1 : (console.error("[main] refusing to run with a debugger attached"), i(1), !0);
|
|
26
|
+
}
|
|
27
|
+
export {
|
|
28
|
+
w as denyPermissions,
|
|
29
|
+
g as lockDownWindow,
|
|
30
|
+
p as refuseDebugLaunch
|
|
31
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
function i(e) {
|
|
2
|
+
try {
|
|
3
|
+
return new URL(e);
|
|
4
|
+
} catch {
|
|
5
|
+
return null;
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
function g(e, r) {
|
|
9
|
+
const t = i(e);
|
|
10
|
+
if (!t || t.protocol !== "https:" || t.username || t.password) return !1;
|
|
11
|
+
const o = t.hostname.toLowerCase(), s = (r.paths ?? []).filter((n) => n.host.toLowerCase() === o);
|
|
12
|
+
return s.length ? s.some((n) => t.pathname.toLowerCase().startsWith(n.prefix.toLowerCase())) : r.hosts.some((n) => {
|
|
13
|
+
const a = n.toLowerCase();
|
|
14
|
+
return o === a || o.endsWith(`.${a}`);
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
function f(e, r) {
|
|
18
|
+
const t = i(e);
|
|
19
|
+
if (!t) return !1;
|
|
20
|
+
if (r) {
|
|
21
|
+
const o = i(r);
|
|
22
|
+
return !!o && t.origin === o.origin;
|
|
23
|
+
}
|
|
24
|
+
return t.protocol === "file:";
|
|
25
|
+
}
|
|
26
|
+
function l(e) {
|
|
27
|
+
if (e.type !== "keyDown") return !1;
|
|
28
|
+
if (e.key === "F12") return !0;
|
|
29
|
+
const r = e.key.toLowerCase();
|
|
30
|
+
return (e.control || e.meta) && (e.shift || e.alt) && (r === "i" || r === "j" || r === "c");
|
|
31
|
+
}
|
|
32
|
+
const c = ["--inspect", "--remote-debugging-port", "--remote-debugging-pipe", "--remote-debugging-address"], u = ["remote-debugging-port", "remote-debugging-pipe", "inspect", "inspect-brk"];
|
|
33
|
+
function d(e, r = () => !1) {
|
|
34
|
+
return e.some((o) => c.some((s) => o === s || o.startsWith(`${s}=`) || o.startsWith(`${s}-`))) || u.some((o) => r(o));
|
|
35
|
+
}
|
|
36
|
+
export {
|
|
37
|
+
c as DEBUG_SWITCHES,
|
|
38
|
+
d as debuggerRequested,
|
|
39
|
+
g as isAllowedExternal,
|
|
40
|
+
f as isAllowedNavigation,
|
|
41
|
+
l as isDevToolsKey
|
|
42
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { promises as r } from "node:fs";
|
|
2
|
+
import { join as n } from "node:path";
|
|
3
|
+
class l {
|
|
4
|
+
constructor(t) {
|
|
5
|
+
this.o = t, this.file = n(t.dir, t.fileName ?? "session.bin"), this.maxLength = t.maxLength ?? 4096, this.platform = t.platform ?? process.platform;
|
|
6
|
+
}
|
|
7
|
+
o;
|
|
8
|
+
file;
|
|
9
|
+
maxLength;
|
|
10
|
+
platform;
|
|
11
|
+
/** Whether the OS can encrypt for real here. */
|
|
12
|
+
canEncrypt() {
|
|
13
|
+
const t = this.o.safeStorage;
|
|
14
|
+
return t.isEncryptionAvailable() ? this.platform !== "linux" || t.getSelectedStorageBackend?.() !== "basic_text" : !1;
|
|
15
|
+
}
|
|
16
|
+
/** The saved value, or null when there is none, it cannot be decrypted (another OS user, a reinstall) or nothing may be. */
|
|
17
|
+
async get() {
|
|
18
|
+
if (!this.canEncrypt()) return null;
|
|
19
|
+
try {
|
|
20
|
+
return this.o.safeStorage.decryptString(await r.readFile(this.file));
|
|
21
|
+
} catch {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/** Saves `value`; false when it is not a string, too long, or the OS cannot encrypt it. */
|
|
26
|
+
async set(t) {
|
|
27
|
+
if (typeof t != "string" || t.length > this.maxLength || !this.canEncrypt()) return !1;
|
|
28
|
+
const e = `${this.file}.${process.pid}.${Date.now()}.tmp`;
|
|
29
|
+
try {
|
|
30
|
+
await r.mkdir(this.o.dir, { recursive: !0 }), await r.writeFile(e, this.o.safeStorage.encryptString(t)), await r.rename(e, this.file);
|
|
31
|
+
} catch (a) {
|
|
32
|
+
throw await r.rm(e, { force: !0 }), a;
|
|
33
|
+
}
|
|
34
|
+
return !0;
|
|
35
|
+
}
|
|
36
|
+
async clear() {
|
|
37
|
+
await r.rm(this.file, { force: !0 });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function h(s, t, e = "session") {
|
|
41
|
+
return s.handle(`${e}:get`, () => t.get()), s.handle(`${e}:set`, (a, i) => t.set(i)), s.handle(`${e}:clear`, () => t.clear()), () => {
|
|
42
|
+
for (const a of ["get", "set", "clear"]) s.removeHandler?.(`${e}:${a}`);
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
export {
|
|
46
|
+
l as SessionStore,
|
|
47
|
+
h as registerSessionStore
|
|
48
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { createHash as a } from "node:crypto";
|
|
2
|
+
import { readdirSync as f, readFileSync as d } from "node:fs";
|
|
3
|
+
import { join as i } from "node:path";
|
|
4
|
+
function p(n, r) {
|
|
5
|
+
const o = typeof r == "string" ? { dirs: [r] } : r, s = o.match ?? ((t) => t.endsWith(".cjs")), e = a("sha256");
|
|
6
|
+
e.update(`electron ${n}
|
|
7
|
+
`);
|
|
8
|
+
for (const t of o.dirs)
|
|
9
|
+
for (const c of f(t).filter(s).sort())
|
|
10
|
+
e.update(`${c}
|
|
11
|
+
`), e.update(d(i(t, c)));
|
|
12
|
+
for (const t of o.extra ?? []) e.update(`extra ${t}
|
|
13
|
+
`);
|
|
14
|
+
return e.digest("hex").slice(0, o.length ?? 16);
|
|
15
|
+
}
|
|
16
|
+
export {
|
|
17
|
+
p as shellId
|
|
18
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { createHash as s } from "node:crypto";
|
|
2
|
+
import { readFile as c, mkdir as l, writeFile as u, readdir as f } from "node:fs/promises";
|
|
3
|
+
import { join as r, dirname as h } from "node:path";
|
|
4
|
+
const m = (t) => s("sha256").update(t).digest("hex");
|
|
5
|
+
async function n(t, e = "") {
|
|
6
|
+
const i = [];
|
|
7
|
+
for (const a of await f(r(t, e), { withFileTypes: !0 })) {
|
|
8
|
+
const o = e ? `${e}/${a.name}` : a.name;
|
|
9
|
+
a.isDirectory() ? i.push(...await n(t, o)) : i.push(o);
|
|
10
|
+
}
|
|
11
|
+
return i;
|
|
12
|
+
}
|
|
13
|
+
async function d(t) {
|
|
14
|
+
const e = /* @__PURE__ */ new Map();
|
|
15
|
+
for (const i of await n(t)) e.set(i, m(await c(r(t, ...i.split("/")))));
|
|
16
|
+
return e;
|
|
17
|
+
}
|
|
18
|
+
async function F(t, e, i) {
|
|
19
|
+
const a = r(t, ...e.split("/"));
|
|
20
|
+
await l(h(a), { recursive: !0 }), await u(a, i);
|
|
21
|
+
}
|
|
22
|
+
async function T(t, e, i) {
|
|
23
|
+
const a = new AbortController(), o = setTimeout(() => a.abort(), i);
|
|
24
|
+
try {
|
|
25
|
+
return await t(e, { signal: a.signal, cache: "no-store" });
|
|
26
|
+
} finally {
|
|
27
|
+
clearTimeout(o);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export {
|
|
31
|
+
T as fetchWithTimeout,
|
|
32
|
+
d as hashWeb,
|
|
33
|
+
n as listFiles,
|
|
34
|
+
F as putFile,
|
|
35
|
+
m as sha256
|
|
36
|
+
};
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { existsSync as b } from "node:fs";
|
|
2
|
+
import { rm as d, mkdir as M, readFile as V, rename as T, readdir as x } from "node:fs/promises";
|
|
3
|
+
import { join as s } from "node:path";
|
|
4
|
+
import { isNewer as y, validManifest as $, fileUrl as U } from "../updates/manifest.js";
|
|
5
|
+
import { planWeb as D } from "../updates/webPlan.js";
|
|
6
|
+
import { fetchWithTimeout as u, hashWeb as E, putFile as g, sha256 as P } from "./webFiles.js";
|
|
7
|
+
import { WebPointers as W } from "./webPointers.js";
|
|
8
|
+
class C {
|
|
9
|
+
constructor(e) {
|
|
10
|
+
this.options = e, this.o = {
|
|
11
|
+
root: e.root,
|
|
12
|
+
builtinDir: e.builtinDir,
|
|
13
|
+
appVersion: e.appVersion,
|
|
14
|
+
channelUrl: e.channelUrl,
|
|
15
|
+
packaged: e.packaged ?? !0,
|
|
16
|
+
maxTries: e.maxTries ?? 2,
|
|
17
|
+
keepPrevious: e.keepPrevious ?? !0,
|
|
18
|
+
entry: e.entry ?? "index.html",
|
|
19
|
+
parallel: e.parallel ?? 6,
|
|
20
|
+
fetchTimeoutMs: e.fetchTimeoutMs ?? 15e3,
|
|
21
|
+
fetch: e.fetch ?? ((t, i) => globalThis.fetch(t, i)),
|
|
22
|
+
log: e.log ?? ((...t) => console.error(...t))
|
|
23
|
+
}, this.pointers = new W(e.root);
|
|
24
|
+
}
|
|
25
|
+
options;
|
|
26
|
+
o;
|
|
27
|
+
pointers;
|
|
28
|
+
shellValue = null;
|
|
29
|
+
/** The web this run loads, and its version (the installed build's own when none is patched in). */
|
|
30
|
+
loaded = null;
|
|
31
|
+
/** The tidy a start began: a build waits for it, or it could delete the `.part` folder being filled. */
|
|
32
|
+
tidying = Promise.resolve();
|
|
33
|
+
/** This shell's identity. */
|
|
34
|
+
get shell() {
|
|
35
|
+
const e = this.options.shell;
|
|
36
|
+
return this.shellValue ??= typeof e == "string" ? e : e(), this.shellValue;
|
|
37
|
+
}
|
|
38
|
+
/** Where the window loads the page from: a patched web when there is a good one, else the built-in copy. */
|
|
39
|
+
startEntry() {
|
|
40
|
+
if (!this.o.packaged)
|
|
41
|
+
return this.loaded = { dir: this.o.builtinDir, version: this.o.appVersion }, s(this.o.builtinDir, this.o.entry);
|
|
42
|
+
try {
|
|
43
|
+
const e = this.pointers.read("pending");
|
|
44
|
+
this.usable(e) && this.promote(e, 0), this.pointers.write("pending", null);
|
|
45
|
+
const t = this.pointers.read("active");
|
|
46
|
+
if (this.usable(t) && (t.booted || t.tries < this.o.maxTries))
|
|
47
|
+
t.booted || this.pointers.write("active", { ...t, tries: t.tries + 1 }), this.loaded = { dir: s(this.o.root, t.version), version: t.version };
|
|
48
|
+
else {
|
|
49
|
+
t && this.pointers.write("active", null);
|
|
50
|
+
const i = this.pointers.read("previous");
|
|
51
|
+
this.pointers.write("previous", null), this.o.keepPrevious && i?.booted && this.usable(i) && (this.pointers.write("active", i), this.loaded = { dir: s(this.o.root, i.version), version: i.version });
|
|
52
|
+
}
|
|
53
|
+
} catch (e) {
|
|
54
|
+
this.o.log("[web] could not read the patched web, using the built-in one", e);
|
|
55
|
+
}
|
|
56
|
+
return this.loaded ??= { dir: this.o.builtinDir, version: this.o.appVersion }, this.tidying = this.tidy(), s(this.loaded.dir, this.o.entry);
|
|
57
|
+
}
|
|
58
|
+
/** The version of the web running now. */
|
|
59
|
+
webVersion() {
|
|
60
|
+
return this.loaded?.version ?? this.o.appVersion;
|
|
61
|
+
}
|
|
62
|
+
/** The page came up: the web it was loaded from is kept from now on. Call it from the page's own "booted" message. */
|
|
63
|
+
markBooted() {
|
|
64
|
+
const e = this.pointers.read("active");
|
|
65
|
+
e && this.loaded && e.version === this.loaded.version && !e.booted && this.pointers.write("active", { ...e, booted: !0 });
|
|
66
|
+
}
|
|
67
|
+
/** The channel's newest build, or null when the store cannot be read. */
|
|
68
|
+
async latest() {
|
|
69
|
+
try {
|
|
70
|
+
const e = await u(this.o.fetch, `${this.o.channelUrl}/latest.json`, this.o.fetchTimeoutMs);
|
|
71
|
+
if (!e.ok) return null;
|
|
72
|
+
const t = await e.json();
|
|
73
|
+
return typeof t?.version == "string" ? t : null;
|
|
74
|
+
} catch {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/** Whether `l`'s web can be patched in here rather than installed: newer than the web running, for this shell. */
|
|
79
|
+
patchable(e) {
|
|
80
|
+
return !!e.web && e.web.shell === this.shell && y(e.version, this.webVersion());
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Builds `l`'s web beside the one running: the files held are copied, the rest downloaded and checked, into
|
|
84
|
+
* `<version>.part/`, which becomes `<version>/` and the pending web once whole — a crash mid-way never leaves a
|
|
85
|
+
* folder that looks finished. `onProgress` gets 0–100. Resolves with the version built; throws on any failure.
|
|
86
|
+
*/
|
|
87
|
+
async prepare(e, t = () => {
|
|
88
|
+
}) {
|
|
89
|
+
if (!e.web) throw new Error("no web in this release");
|
|
90
|
+
const i = await u(this.o.fetch, `${this.o.channelUrl}/${e.web.manifest}`, this.o.fetchTimeoutMs);
|
|
91
|
+
if (!i.ok) throw new Error(`web manifest ${i.status}`);
|
|
92
|
+
const o = await i.json();
|
|
93
|
+
if (!$(o) || o.version !== e.version || o.shell !== this.shell) throw new Error("bad web manifest");
|
|
94
|
+
const l = s(this.o.root, o.version), p = { version: o.version, shell: o.shell, booted: !1, tries: 0 };
|
|
95
|
+
if (b(s(l, this.o.entry)) && this.pointers.read("active")?.version !== o.version)
|
|
96
|
+
return this.pointers.write("pending", p), t(100), o.version;
|
|
97
|
+
await this.tidying;
|
|
98
|
+
const f = this.loaded?.dir ?? this.o.builtinDir, h = D(o, await E(f)), n = s(this.o.root, `${o.version}.part`);
|
|
99
|
+
await d(n, { recursive: !0, force: !0 }), await M(n, { recursive: !0 });
|
|
100
|
+
for (const r of h.copy) await g(n, r.path, await V(s(f, ...r.from.split("/"))));
|
|
101
|
+
let v = 0;
|
|
102
|
+
const w = /* @__PURE__ */ new Map(), m = [...h.download];
|
|
103
|
+
t(h.bytes ? 0 : 100);
|
|
104
|
+
const k = async () => {
|
|
105
|
+
for (let r = m.shift(); r; r = m.shift()) {
|
|
106
|
+
let a = w.get(r.sha256);
|
|
107
|
+
if (!a) {
|
|
108
|
+
const c = await u(this.o.fetch, U(this.o.channelUrl, r.sha256), this.o.fetchTimeoutMs);
|
|
109
|
+
if (!c.ok) throw new Error(`${r.path}: ${c.status}`);
|
|
110
|
+
if (a = new Uint8Array(await c.arrayBuffer()), P(a) !== r.sha256) throw new Error(`${r.path}: checksum`);
|
|
111
|
+
w.set(r.sha256, a), v += r.size, t(Math.min(99, Math.round(v / Math.max(1, h.bytes) * 100)));
|
|
112
|
+
}
|
|
113
|
+
await g(n, r.path, a);
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
return await Promise.all(Array.from({ length: Math.max(1, this.o.parallel) }, k)), await d(l, { recursive: !0, force: !0 }), await T(n, l), this.pointers.write("pending", p), t(100), o.version;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Puts the pending web in use now and returns the page to load (reload every window from it), or null when there is
|
|
120
|
+
* none this shell may load. The switch counts as its first start, so a web that never boots is still dropped.
|
|
121
|
+
*/
|
|
122
|
+
applyPending() {
|
|
123
|
+
const e = this.pointers.read("pending");
|
|
124
|
+
return this.usable(e) ? (this.promote(e, 1), this.pointers.write("pending", null), this.loaded = { dir: s(this.o.root, e.version), version: e.version }, s(this.loaded.dir, this.o.entry)) : null;
|
|
125
|
+
}
|
|
126
|
+
/** Makes `p` the active web, keeping the one it replaces as the fallback when that one ever booted. */
|
|
127
|
+
promote(e, t) {
|
|
128
|
+
const i = this.pointers.read("active");
|
|
129
|
+
this.o.keepPrevious && i?.booted && i.version !== e.version && this.pointers.write("previous", i), this.pointers.write("active", { ...e, booted: !1, tries: t });
|
|
130
|
+
}
|
|
131
|
+
/** A pointer this shell may load: its own shell, newer than the installed build, its folder whole. */
|
|
132
|
+
usable(e) {
|
|
133
|
+
return !!e && e.shell === this.shell && y(e.version, this.o.appVersion) && b(s(this.o.root, e.version, this.o.entry));
|
|
134
|
+
}
|
|
135
|
+
/** Removes every patched web but the active, pending and previous one, and any half-made folder. */
|
|
136
|
+
async tidy() {
|
|
137
|
+
try {
|
|
138
|
+
const e = ["active", "pending", "previous"], t = new Set([...e.map((i) => this.pointers.read(i)?.version), this.loaded?.version].filter((i) => !!i));
|
|
139
|
+
for (const i of await x(this.o.root))
|
|
140
|
+
i.endsWith(".json") || t.has(i) || await d(s(this.o.root, i), { recursive: !0, force: !0 });
|
|
141
|
+
} catch {
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
export {
|
|
146
|
+
C as WebPatcher
|
|
147
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { readFileSync as t, mkdirSync as i, writeFileSync as o, rmSync as s } from "node:fs";
|
|
2
|
+
import { join as n } from "node:path";
|
|
3
|
+
class c {
|
|
4
|
+
constructor(e) {
|
|
5
|
+
this.root = e;
|
|
6
|
+
}
|
|
7
|
+
root;
|
|
8
|
+
read(e) {
|
|
9
|
+
try {
|
|
10
|
+
const r = JSON.parse(t(this.file(e), "utf8"));
|
|
11
|
+
return typeof r?.version != "string" || typeof r.shell != "string" ? null : { version: r.version, shell: r.shell, booted: r.booted === !0, tries: typeof r.tries == "number" ? r.tries : 0 };
|
|
12
|
+
} catch {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
write(e, r) {
|
|
17
|
+
r ? (i(this.root, { recursive: !0 }), o(this.file(e), JSON.stringify(r))) : s(this.file(e), { force: !0 });
|
|
18
|
+
}
|
|
19
|
+
file(e) {
|
|
20
|
+
return n(this.root, `${e}.json`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export {
|
|
24
|
+
c as WebPointers
|
|
25
|
+
};
|
package/dist/electron.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { shellId as r } from "./electron/shellId.js";
|
|
2
|
+
import { WebPatcher as t } from "./electron/webPatcher.js";
|
|
3
|
+
import { SessionStore as l, registerSessionStore as n } from "./electron/sessionStore.js";
|
|
4
|
+
import { DEBUG_SWITCHES as f, debuggerRequested as m, isAllowedExternal as x, isAllowedNavigation as S, isDevToolsKey as a } from "./electron/lockdownRules.js";
|
|
5
|
+
import { denyPermissions as p, lockDownWindow as u, refuseDebugLaunch as w } from "./electron/lockdown.js";
|
|
6
|
+
export {
|
|
7
|
+
f as DEBUG_SWITCHES,
|
|
8
|
+
l as SessionStore,
|
|
9
|
+
t as WebPatcher,
|
|
10
|
+
m as debuggerRequested,
|
|
11
|
+
p as denyPermissions,
|
|
12
|
+
x as isAllowedExternal,
|
|
13
|
+
S as isAllowedNavigation,
|
|
14
|
+
a as isDevToolsKey,
|
|
15
|
+
u as lockDownWindow,
|
|
16
|
+
w as refuseDebugLaunch,
|
|
17
|
+
n as registerSessionStore,
|
|
18
|
+
r as shellId
|
|
19
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
class n {
|
|
2
|
+
constructor(t = {}) {
|
|
3
|
+
this.gaps = t;
|
|
4
|
+
}
|
|
5
|
+
gaps;
|
|
6
|
+
last = /* @__PURE__ */ new Map();
|
|
7
|
+
/** Whether a buzz of `kind` at `now` (ms) may go; one that may is counted. */
|
|
8
|
+
allow(t, e) {
|
|
9
|
+
const r = this.last.get(t), s = this.gaps[t] ?? 0;
|
|
10
|
+
return r !== void 0 && e - r < s ? !1 : (this.last.set(t, e), !0);
|
|
11
|
+
}
|
|
12
|
+
/** Forgets every kind's last buzz. */
|
|
13
|
+
reset() {
|
|
14
|
+
this.last.clear();
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function c(a) {
|
|
18
|
+
const t = typeof navigator > "u" ? void 0 : navigator;
|
|
19
|
+
if (typeof t?.vibrate != "function") return !1;
|
|
20
|
+
try {
|
|
21
|
+
return t.vibrate(typeof a == "number" ? a : [...a]);
|
|
22
|
+
} catch {
|
|
23
|
+
return !1;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
class i {
|
|
27
|
+
gate;
|
|
28
|
+
play;
|
|
29
|
+
enabled;
|
|
30
|
+
now;
|
|
31
|
+
constructor(t = {}) {
|
|
32
|
+
this.gate = new n(t.gaps), this.play = t.play ?? ((e) => c(t.patterns?.[e] ?? 15)), this.enabled = t.enabled ?? (() => !0), this.now = t.now ?? (() => performance.now());
|
|
33
|
+
}
|
|
34
|
+
/** Buzzes for `kind`; returns whether it was let through (not whether the device did buzz). */
|
|
35
|
+
buzz(t) {
|
|
36
|
+
if (!this.enabled() || !this.gate.allow(t, this.now())) return !1;
|
|
37
|
+
try {
|
|
38
|
+
const e = this.play(t);
|
|
39
|
+
e && typeof e.catch == "function" && e.catch(() => {
|
|
40
|
+
});
|
|
41
|
+
} catch {
|
|
42
|
+
}
|
|
43
|
+
return !0;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
export {
|
|
47
|
+
n as HapticGate,
|
|
48
|
+
i as Haptics,
|
|
49
|
+
c as vibrate
|
|
50
|
+
};
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { timeoutSignal as p } from "../platform/timeoutSignal.js";
|
|
2
|
+
class g extends Error {
|
|
3
|
+
constructor(t, e, r, n, u) {
|
|
4
|
+
super(r), this.status = t, this.code = e, this.details = n, this.body = u, this.name = "ApiError";
|
|
5
|
+
}
|
|
6
|
+
status;
|
|
7
|
+
code;
|
|
8
|
+
details;
|
|
9
|
+
body;
|
|
10
|
+
}
|
|
11
|
+
function y(i) {
|
|
12
|
+
if (!i || typeof i != "object") return {};
|
|
13
|
+
const t = i;
|
|
14
|
+
return {
|
|
15
|
+
code: typeof t.code == "string" ? t.code : void 0,
|
|
16
|
+
message: typeof t.message == "string" ? t.message : void 0,
|
|
17
|
+
details: t.details && typeof t.details == "object" ? t.details : void 0
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
function v(i) {
|
|
21
|
+
const t = AbortSignal.any;
|
|
22
|
+
if (typeof t == "function") return t.call(AbortSignal, [...i]);
|
|
23
|
+
const e = new AbortController();
|
|
24
|
+
for (const r of i) {
|
|
25
|
+
if (r.aborted) {
|
|
26
|
+
e.abort(r.reason);
|
|
27
|
+
break;
|
|
28
|
+
}
|
|
29
|
+
r.addEventListener("abort", () => e.abort(r.reason), { once: !0 });
|
|
30
|
+
}
|
|
31
|
+
return e.signal;
|
|
32
|
+
}
|
|
33
|
+
class E {
|
|
34
|
+
constructor(t) {
|
|
35
|
+
this.options = t;
|
|
36
|
+
}
|
|
37
|
+
options;
|
|
38
|
+
token = null;
|
|
39
|
+
/** A new access token for a 401 (`RefreshResult`); unset, a 401 is simply thrown. */
|
|
40
|
+
refresher = null;
|
|
41
|
+
/** Called when a 401 ends the session: no refresher, a refused refresh, or a retry refused again. */
|
|
42
|
+
onUnauthorized = null;
|
|
43
|
+
get accessToken() {
|
|
44
|
+
return this.token;
|
|
45
|
+
}
|
|
46
|
+
setToken(t) {
|
|
47
|
+
this.token = t;
|
|
48
|
+
}
|
|
49
|
+
/** Sends a request and returns its parsed JSON body (undefined for an empty or unreadable one). */
|
|
50
|
+
async request(t, e, r, n = {}, u = !1) {
|
|
51
|
+
const o = this.options, l = { Accept: "application/json", ...o.headers?.(), ...n.headers };
|
|
52
|
+
r !== void 0 && (l["Content-Type"] = "application/json");
|
|
53
|
+
const f = this.token;
|
|
54
|
+
f && (l.Authorization = `Bearer ${f}`);
|
|
55
|
+
const w = p(n.timeoutMs ?? o.timeoutMs ?? 1e4), b = n.signal ? v([w, n.signal]) : w;
|
|
56
|
+
let s, h;
|
|
57
|
+
try {
|
|
58
|
+
const a = typeof o.baseUrl == "function" ? o.baseUrl(e) : o.baseUrl;
|
|
59
|
+
s = await (o.fetch ?? fetch)(a + e, {
|
|
60
|
+
method: t,
|
|
61
|
+
headers: l,
|
|
62
|
+
body: r !== void 0 ? JSON.stringify(r) : void 0,
|
|
63
|
+
signal: b
|
|
64
|
+
}), h = s.status === 204 ? "" : await s.text();
|
|
65
|
+
} catch (a) {
|
|
66
|
+
throw n.signal?.aborted ? new g(0, "ABORTED", "Aborted.") : (console.warn("[api] network error", t, e, a), this.networkError());
|
|
67
|
+
}
|
|
68
|
+
let c;
|
|
69
|
+
if (h)
|
|
70
|
+
try {
|
|
71
|
+
c = JSON.parse(h);
|
|
72
|
+
} catch {
|
|
73
|
+
c = void 0;
|
|
74
|
+
}
|
|
75
|
+
if (s.ok) return c;
|
|
76
|
+
const d = (o.describe ?? y)(c, s.status), k = new g(s.status, d.code ?? `HTTP_${s.status}`, d.message ?? `HTTP ${s.status}`, d.details, c);
|
|
77
|
+
if (o.onError?.(k), o.onStatus?.[s.status]?.(k), s.status === 401 && this.token && !u && !o.noRefresh?.(e)) {
|
|
78
|
+
if (f !== this.token) return this.request(t, e, r, n, !0);
|
|
79
|
+
if (this.refresher) {
|
|
80
|
+
const a = await this.refresher().catch(() => null);
|
|
81
|
+
if (a !== null && a !== "refused")
|
|
82
|
+
return this.token = a, this.request(t, e, r, n, !0);
|
|
83
|
+
if (a === null) throw this.networkError();
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
throw s.status === 401 && this.token && this.onUnauthorized?.(), k;
|
|
87
|
+
}
|
|
88
|
+
networkError() {
|
|
89
|
+
const t = this.options.networkMessage;
|
|
90
|
+
return new g(0, "NETWORK", typeof t == "function" ? t() : t ?? "Cannot reach the server.");
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
export {
|
|
94
|
+
E as ApiClient,
|
|
95
|
+
g as ApiError
|
|
96
|
+
};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
const F = {
|
|
2
|
+
leaves: [
|
|
3
|
+
["#7ab04e", "#3d7a36"],
|
|
4
|
+
["#d8a040", "#9a5a24"]
|
|
5
|
+
],
|
|
6
|
+
seed: ["#f6f2e4", "#c8c2b0"],
|
|
7
|
+
ember: ["#ffd070", "#ff8a30", "#a8481c"],
|
|
8
|
+
mote: ["rgba(206,226,240,0.85)", "rgba(150,180,206,0.7)"],
|
|
9
|
+
ash: [
|
|
10
|
+
["#8a827c", "#5c5652"],
|
|
11
|
+
["#6e6864", "#3e3a38"]
|
|
12
|
+
],
|
|
13
|
+
butterflies: [
|
|
14
|
+
["#f0d050", "#9a7420"],
|
|
15
|
+
["#f4f0e8", "#a8a098"],
|
|
16
|
+
["#e8883a", "#8a4418"],
|
|
17
|
+
["#9ab8f0", "#465e9c"]
|
|
18
|
+
],
|
|
19
|
+
butterflyBody: "#3a2a20",
|
|
20
|
+
bird: "#2c2830",
|
|
21
|
+
birdShadow: ["rgba(0,0,0,0.3)", "rgba(0,0,0,0.22)"],
|
|
22
|
+
firefly: ["#f0ff9a", "#a8c850"],
|
|
23
|
+
smoke: ["#b8b0b4", "#9a9296", "#7c7478"]
|
|
24
|
+
}, v = 6;
|
|
25
|
+
function L(o, e) {
|
|
26
|
+
const l = Number.parseInt(o.replace("#", ""), 16);
|
|
27
|
+
return `rgba(${l >> 16 & 255},${l >> 8 & 255},${l & 255},${e})`;
|
|
28
|
+
}
|
|
29
|
+
function M(o, e = F.smoke) {
|
|
30
|
+
const l = [[2, 2], [3, 3], [4, 3], [5, 4], [6, 5], [7, 5]], [s, n] = l[Math.max(0, Math.min(v - 1, o))], [b, _, p] = e, h = [], y = (s - 1) / 2, u = (n - 1) / 2, S = o < 3 ? 0.9 : o < 5 ? 0.78 : 0.62;
|
|
31
|
+
for (let a = 0; a < n; a++)
|
|
32
|
+
for (let r = 0; r < s; r++) {
|
|
33
|
+
const $ = (r - y) / (s / 2), m = (a - u) / (n / 2), g = (r * 7 + a * 13 + o * 5) % 5 / 10;
|
|
34
|
+
if ($ * $ + m * m > 0.85 + g || o >= v - 2 && (r + a + o) % 2) continue;
|
|
35
|
+
const w = $ + m, d = w < -0.35 ? b : w > 0.45 ? p : _;
|
|
36
|
+
h.push([r, a, d.startsWith("#") ? L(d, S) : d]);
|
|
37
|
+
}
|
|
38
|
+
return { w: s, h: n, px: h };
|
|
39
|
+
}
|
|
40
|
+
function f(o, e, l, s, n) {
|
|
41
|
+
if (o.exists(e)) return e;
|
|
42
|
+
const b = document.createElement("canvas");
|
|
43
|
+
b.width = l, b.height = s;
|
|
44
|
+
const _ = b.getContext("2d");
|
|
45
|
+
for (const [p, h, y] of n)
|
|
46
|
+
_.fillStyle = y, _.fillRect(p, h, 1, 1);
|
|
47
|
+
return o.addCanvas(e, b), e;
|
|
48
|
+
}
|
|
49
|
+
function R(o, e = {}, l = "life_") {
|
|
50
|
+
const s = { ...F, ...e }, n = (c) => l + c, b = s.leaves.map(([c, t], i) => [
|
|
51
|
+
f(o, n(`leaf_${i}_0`), 2, 1, [[0, 0, c], [1, 0, t]]),
|
|
52
|
+
f(o, n(`leaf_${i}_1`), 2, 2, [[0, 0, c], [1, 1, t]]),
|
|
53
|
+
f(o, n(`leaf_${i}_2`), 1, 2, [[0, 0, c], [0, 1, t]])
|
|
54
|
+
]), _ = [
|
|
55
|
+
[f(o, n("seed_0"), 1, 1, [[0, 0, s.seed[0]]])],
|
|
56
|
+
[f(o, n("seed_1"), 2, 1, [[0, 0, s.seed[0]], [1, 0, s.seed[1]]])]
|
|
57
|
+
], p = [s.ember.map((c, t) => f(o, n(`ember_${t}`), 1, 1, [[0, 0, c]]))], h = s.mote.map((c, t) => [f(o, n(`mote_${t}`), 1, 1, [[0, 0, c]])]), y = s.ash.map(
|
|
58
|
+
([c, t], i) => (
|
|
59
|
+
// the first flake flat, the next tumbling on its corner
|
|
60
|
+
[f(o, n(`ash_${i}`), 2, i % 2 ? 2 : 1, i % 2 ? [[0, 0, c], [1, 1, t]] : [[0, 0, c], [1, 0, t]])]
|
|
61
|
+
)
|
|
62
|
+
), u = s.butterflyBody, S = s.butterflies.map(([c, t], i) => [
|
|
63
|
+
f(o, n(`fly_${i}_o`), 3, 2, [[0, 0, c], [2, 0, c], [1, 0, u], [0, 1, t], [1, 1, u], [2, 1, t]]),
|
|
64
|
+
f(o, n(`fly_${i}_c`), 3, 2, [[1, 0, c], [1, 1, t]])
|
|
65
|
+
]), a = s.bird, r = [
|
|
66
|
+
f(o, n("bird_0"), 5, 2, [[0, 0, a], [4, 0, a], [1, 1, a], [2, 1, a], [3, 1, a]]),
|
|
67
|
+
f(o, n("bird_1"), 5, 2, [[0, 1, a], [1, 0, a], [2, 0, a], [3, 0, a], [4, 1, a]])
|
|
68
|
+
], [$, m] = s.birdShadow, g = f(o, n("bird_shadow"), 3, 1, [[0, 0, m], [1, 0, $], [2, 0, m]]), [w, d] = s.firefly, A = [
|
|
69
|
+
f(o, n("firefly_dim"), 3, 3, [[1, 1, d]]),
|
|
70
|
+
f(o, n("firefly_lit"), 3, 3, [[1, 1, w], [0, 1, d], [2, 1, d], [1, 0, d], [1, 2, d]])
|
|
71
|
+
], C = [];
|
|
72
|
+
for (let c = 0; c < v; c++) {
|
|
73
|
+
const { w: t, h: i, px: E } = M(c, s.smoke);
|
|
74
|
+
C.push(f(o, n(`puff_${c}`), t, i, E));
|
|
75
|
+
}
|
|
76
|
+
return { leaves: b, seeds: _, embers: p, motes: h, ash: y, butterflies: S, bird: r, birdShadow: g, firefly: A, puffs: C };
|
|
77
|
+
}
|
|
78
|
+
export {
|
|
79
|
+
F as LIFE_COLORS,
|
|
80
|
+
v as PUFF_FRAMES,
|
|
81
|
+
R as paintLifeArt,
|
|
82
|
+
M as puffPixels
|
|
83
|
+
};
|