vintage-frames 0.16.2 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/custom-elements.json +57 -7
- package/dist/build/app-file.d.ts +21 -0
- package/dist/build/app-file.js +41 -0
- package/dist/build/box.d.ts +57 -0
- package/dist/build/box.js +93 -0
- package/dist/build/client.d.ts +12 -0
- package/dist/build/frame-picture.d.ts +6 -0
- package/dist/build/frame-picture.js +4 -0
- package/dist/build/frame.d.ts +11 -0
- package/dist/build/frame.js +44 -0
- package/dist/build/index.d.ts +14 -0
- package/dist/build/index.js +4 -0
- package/dist/build/plugins.d.ts +41 -0
- package/dist/build/plugins.js +131 -0
- package/dist/build/png.d.ts +24 -0
- package/dist/build/png.js +116 -0
- package/dist/modal-dialog.d.ts +7 -6
- package/dist/modal-dialog.js +30 -22
- package/dist/shell/app-file.d.ts +99 -0
- package/dist/shell/app-file.js +177 -0
- package/dist/shell/index.d.ts +3 -0
- package/dist/shell/index.js +3 -1
- package/dist/shell/pure.d.ts +7 -4
- package/dist/shell/pure.js +3 -1
- package/dist/shell/shell.js +1 -1
- package/dist/shell/version.d.ts +2 -0
- package/dist/shell/version.js +4 -0
- package/dist/styles/body-strike.d.ts +12 -0
- package/dist/styles/body-strike.js +3961 -0
- package/dist/styles/display-strike.d.ts +12 -0
- package/dist/styles/display-strike.js +4343 -0
- package/dist/styles/strike.d.ts +30 -0
- package/docs/SPEC.md +1 -1
- package/editor/web-types.json +1 -1
- package/package.json +19 -2
package/README.md
CHANGED
|
@@ -39,6 +39,8 @@ createShell(document.querySelector('vf-desktop'), {
|
|
|
39
39
|
|
|
40
40
|
See [docs/SHELL.md](https://github.com/aportilla/vintage-frames/blob/main/docs/SHELL.md). Its API may change in any minor release while it is experimental.
|
|
41
41
|
|
|
42
|
+
An application on the shell can ship as one PNG, its app file. `vintage-frames/build` holds the Vite plugins that write app files and build them into a site; it runs in the build only. See [docs/APP-FILES.md](https://github.com/aportilla/vintage-frames/blob/main/docs/APP-FILES.md).
|
|
43
|
+
|
|
42
44
|
|
|
43
45
|
## Sizing
|
|
44
46
|
|
package/custom-elements.json
CHANGED
|
@@ -3908,6 +3908,20 @@
|
|
|
3908
3908
|
"module": "src/modal-dialog.ts"
|
|
3909
3909
|
}
|
|
3910
3910
|
},
|
|
3911
|
+
{
|
|
3912
|
+
"kind": "field",
|
|
3913
|
+
"name": "#opened",
|
|
3914
|
+
"privacy": "private",
|
|
3915
|
+
"type": {
|
|
3916
|
+
"text": "boolean"
|
|
3917
|
+
},
|
|
3918
|
+
"default": "false",
|
|
3919
|
+
"description": "Whether the modal has opened since its last close was funneled: true from\nan open until `#funnelClose` runs for the close that ends it, which can be\nafter the native `<dialog>` has closed, while its `close` event is queued.",
|
|
3920
|
+
"inheritedFrom": {
|
|
3921
|
+
"name": "VfModalDialog",
|
|
3922
|
+
"module": "src/modal-dialog.ts"
|
|
3923
|
+
}
|
|
3924
|
+
},
|
|
3911
3925
|
{
|
|
3912
3926
|
"kind": "method",
|
|
3913
3927
|
"name": "show",
|
|
@@ -4095,7 +4109,7 @@
|
|
|
4095
4109
|
"text": "void"
|
|
4096
4110
|
}
|
|
4097
4111
|
},
|
|
4098
|
-
"description": "Reconcile the native `<dialog>` with `open`. Opening states the origin —\nthe author's, or the centered one — onto the top-layer box; the UA's own\n`margin: auto` centering lands on a half pixel whenever viewport minus\ndialog is odd, which fringes all the 1-bit chrome inside. Closing just\ncalls `dialog.close()`,
|
|
4112
|
+
"description": "Reconcile the native `<dialog>` with `open`. Opening states the origin —\nthe author's, or the centered one — onto the top-layer box; the UA's own\n`margin: auto` centering lands on a half pixel whenever viewport minus\ndialog is odd, which fringes all the 1-bit chrome inside. Closing just\ncalls `dialog.close()`, and the native `close` event runs the close funnel,\n`#funnelClose`, the one place placement is cleared and `vf-close` is\nfired.",
|
|
4099
4113
|
"inheritedFrom": {
|
|
4100
4114
|
"name": "VfModalDialog",
|
|
4101
4115
|
"module": "src/modal-dialog.ts"
|
|
@@ -4152,7 +4166,7 @@
|
|
|
4152
4166
|
}
|
|
4153
4167
|
}
|
|
4154
4168
|
],
|
|
4155
|
-
"description": "While open, watch everything that invalidates the placement: the dialog's\nown box changing (content upgrade/growth, and `--vf-scale` moving under a\nzoom or density change, since every metric resizes with it), the viewport\nresizing, and the scale itself — which the box alone would miss on a page\nthat pins `--vf-scale`, where a zoom moves the lattice without resizing\nanything. Undone
|
|
4169
|
+
"description": "While open, watch everything that invalidates the placement: the dialog's\nown box changing (content upgrade/growth, and `--vf-scale` moving under a\nzoom or density change, since every metric resizes with it), the viewport\nresizing, and the scale itself — which the box alone would miss on a page\nthat pins `--vf-scale`, where a zoom moves the lattice without resizing\nanything. Undone by the close funnel and `disconnectedCallback`.",
|
|
4156
4170
|
"inheritedFrom": {
|
|
4157
4171
|
"name": "VfModalDialog",
|
|
4158
4172
|
"module": "src/modal-dialog.ts"
|
|
@@ -4196,7 +4210,22 @@
|
|
|
4196
4210
|
"text": "void"
|
|
4197
4211
|
}
|
|
4198
4212
|
},
|
|
4199
|
-
"description": "Native `close
|
|
4213
|
+
"description": "Native `close`, which every close path ends in: syncs `open` and runs the\nclose funnel. An open that came before the event has run the funnel for\nthis close already, and the event passes.",
|
|
4214
|
+
"inheritedFrom": {
|
|
4215
|
+
"name": "VfModalDialog",
|
|
4216
|
+
"module": "src/modal-dialog.ts"
|
|
4217
|
+
}
|
|
4218
|
+
},
|
|
4219
|
+
{
|
|
4220
|
+
"kind": "method",
|
|
4221
|
+
"name": "#funnelClose",
|
|
4222
|
+
"privacy": "private",
|
|
4223
|
+
"return": {
|
|
4224
|
+
"type": {
|
|
4225
|
+
"text": "void"
|
|
4226
|
+
}
|
|
4227
|
+
},
|
|
4228
|
+
"description": "The close funnel, the single teardown for every close path. Drops the\nwritten origin so the next open re-derives it from the box it will\nactually have (a stated `top`/`left` is the author's or the user's and\nsurvives, and is re-applied by that open), and fires `vf-close` with the\nreason and the value the close carried.",
|
|
4200
4229
|
"inheritedFrom": {
|
|
4201
4230
|
"name": "VfModalDialog",
|
|
4202
4231
|
"module": "src/modal-dialog.ts"
|
|
@@ -24316,7 +24345,7 @@
|
|
|
24316
24345
|
},
|
|
24317
24346
|
{
|
|
24318
24347
|
"kind": "class",
|
|
24319
|
-
"description": "Base class for the modal shell `vf-dialog` — and for a consumer's own\nmodal (an alert box, say) authored against the kit.\n\nOwns the native `<dialog>` lifecycle every modal shares: `open` sync, `show()` /\n`close()`, the top/left placement (stated or centered) and the\nsingle `close` funnel that drops the written origin and fires `vf-close` with\nthe reason. Because every close path — Escape, `close()`, the opt-in\nlightDismiss click outside — routes through the native `close`\nevent, an Escape-close no longer leaves a stale origin behind, so the next\nopen re-derives it.\n\nIt also owns the two keyboard rules every classic dialog followed (see\ndefaultButton and initialFocusTarget): Return or Enter\nactivates the default button from anywhere in the dialog, and the box opens\nwith the insertion point in its first text field — or, with none, focus on\nthe default button. A field marked `autoselect` takes the focus with its\ntext selected, on every open.\n\nRemoving an open modal from the DOM is a close path too. HTML's dialog\n*removing steps* take the element out of the top layer **without** running\nthe close algorithm — no `close` event, no focus restoration — which is\nexactly what the standard framework pattern of unmounting a dialog instead\nof calling `close()` does. `disconnectedCallback` routes that path through\nthe same funnel: the removed element still fires `vf-close` (heard by\nlisteners on the element itself — it has left the tree, so nothing\nbubbles), `open` and the pinned margins reconcile so a re-append mounts it\nclosed and re-centered, and focus returns to the element that was focused\nwhen the modal opened.\n\nA `<form method=\"dialog\">` inside the modal closes it with the submitting\nbutton's `value` as returnValue, as a form inside a native\n`<dialog>` does. The platform's own rule looks for a `<dialog>` around the\nform, and this one is in the shadow root, so the modal listens for the\nsubmit itself.\n\nSubclasses supply only the frame chrome: a `render()` returning\n`<dialog",
|
|
24348
|
+
"description": "Base class for the modal shell `vf-dialog` — and for a consumer's own\nmodal (an alert box, say) authored against the kit.\n\nOwns the native `<dialog>` lifecycle every modal shares: `open` sync, `show()` /\n`close()`, the top/left placement (stated or centered) and the\nsingle `close` funnel that drops the written origin and fires `vf-close` with\nthe reason. Because every close path — Escape, `close()`, the opt-in\nlightDismiss click outside — routes through the native `close`\nevent, an Escape-close no longer leaves a stale origin behind, so the next\nopen re-derives it. That event is queued: a modal opened again before it\ncomes (`close()` then `show()` in one turn) runs the funnel at the open,\ninside `show()`, so the close's `vf-close` still comes before the open's\n`vf-show`, and the late event changes nothing.\n\nIt also owns the two keyboard rules every classic dialog followed (see\ndefaultButton and initialFocusTarget): Return or Enter\nactivates the default button from anywhere in the dialog, and the box opens\nwith the insertion point in its first text field — or, with none, focus on\nthe default button. A field marked `autoselect` takes the focus with its\ntext selected, on every open.\n\nRemoving an open modal from the DOM is a close path too. HTML's dialog\n*removing steps* take the element out of the top layer **without** running\nthe close algorithm — no `close` event, no focus restoration — which is\nexactly what the standard framework pattern of unmounting a dialog instead\nof calling `close()` does. `disconnectedCallback` routes that path through\nthe same funnel: the removed element still fires `vf-close` (heard by\nlisteners on the element itself — it has left the tree, so nothing\nbubbles), `open` and the pinned margins reconcile so a re-append mounts it\nclosed and re-centered, and focus returns to the element that was focused\nwhen the modal opened.\n\nA `<form method=\"dialog\">` inside the modal closes it with the submitting\nbutton's `value` as returnValue, as a form inside a native\n`<dialog>` does. The platform's own rule looks for a `<dialog>` around the\nform, and this one is in the shadow root, so the modal listens for the\nsubmit itself.\n\nSubclasses supply only the frame chrome: a `render()` returning\n`<dialog",
|
|
24320
24349
|
"name": "VfModalDialog",
|
|
24321
24350
|
"members": [
|
|
24322
24351
|
{
|
|
@@ -24551,6 +24580,16 @@
|
|
|
24551
24580
|
"default": "null",
|
|
24552
24581
|
"description": "The element that was focused when the modal opened. The normal close path\nrestores focus natively, but the removal path (see the class doc) skips\nthe close algorithm, and by the time `disconnectedCallback` runs, focus\nhas already fallen to `<body>` — so we keep our own record."
|
|
24553
24582
|
},
|
|
24583
|
+
{
|
|
24584
|
+
"kind": "field",
|
|
24585
|
+
"name": "#opened",
|
|
24586
|
+
"privacy": "private",
|
|
24587
|
+
"type": {
|
|
24588
|
+
"text": "boolean"
|
|
24589
|
+
},
|
|
24590
|
+
"default": "false",
|
|
24591
|
+
"description": "Whether the modal has opened since its last close was funneled: true from\nan open until `#funnelClose` runs for the close that ends it, which can be\nafter the native `<dialog>` has closed, while its `close` event is queued."
|
|
24592
|
+
},
|
|
24554
24593
|
{
|
|
24555
24594
|
"kind": "method",
|
|
24556
24595
|
"name": "show",
|
|
@@ -24702,7 +24741,7 @@
|
|
|
24702
24741
|
"text": "void"
|
|
24703
24742
|
}
|
|
24704
24743
|
},
|
|
24705
|
-
"description": "Reconcile the native `<dialog>` with `open`. Opening states the origin —\nthe author's, or the centered one — onto the top-layer box; the UA's own\n`margin: auto` centering lands on a half pixel whenever viewport minus\ndialog is odd, which fringes all the 1-bit chrome inside. Closing just\ncalls `dialog.close()`,
|
|
24744
|
+
"description": "Reconcile the native `<dialog>` with `open`. Opening states the origin —\nthe author's, or the centered one — onto the top-layer box; the UA's own\n`margin: auto` centering lands on a half pixel whenever viewport minus\ndialog is odd, which fringes all the 1-bit chrome inside. Closing just\ncalls `dialog.close()`, and the native `close` event runs the close funnel,\n`#funnelClose`, the one place placement is cleared and `vf-close` is\nfired."
|
|
24706
24745
|
},
|
|
24707
24746
|
{
|
|
24708
24747
|
"kind": "field",
|
|
@@ -24743,7 +24782,7 @@
|
|
|
24743
24782
|
}
|
|
24744
24783
|
}
|
|
24745
24784
|
],
|
|
24746
|
-
"description": "While open, watch everything that invalidates the placement: the dialog's\nown box changing (content upgrade/growth, and `--vf-scale` moving under a\nzoom or density change, since every metric resizes with it), the viewport\nresizing, and the scale itself — which the box alone would miss on a page\nthat pins `--vf-scale`, where a zoom moves the lattice without resizing\nanything. Undone
|
|
24785
|
+
"description": "While open, watch everything that invalidates the placement: the dialog's\nown box changing (content upgrade/growth, and `--vf-scale` moving under a\nzoom or density change, since every metric resizes with it), the viewport\nresizing, and the scale itself — which the box alone would miss on a page\nthat pins `--vf-scale`, where a zoom moves the lattice without resizing\nanything. Undone by the close funnel and `disconnectedCallback`."
|
|
24747
24786
|
},
|
|
24748
24787
|
{
|
|
24749
24788
|
"kind": "method",
|
|
@@ -24775,7 +24814,18 @@
|
|
|
24775
24814
|
"text": "void"
|
|
24776
24815
|
}
|
|
24777
24816
|
},
|
|
24778
|
-
"description": "Native `close
|
|
24817
|
+
"description": "Native `close`, which every close path ends in: syncs `open` and runs the\nclose funnel. An open that came before the event has run the funnel for\nthis close already, and the event passes."
|
|
24818
|
+
},
|
|
24819
|
+
{
|
|
24820
|
+
"kind": "method",
|
|
24821
|
+
"name": "#funnelClose",
|
|
24822
|
+
"privacy": "private",
|
|
24823
|
+
"return": {
|
|
24824
|
+
"type": {
|
|
24825
|
+
"text": "void"
|
|
24826
|
+
}
|
|
24827
|
+
},
|
|
24828
|
+
"description": "The close funnel, the single teardown for every close path. Drops the\nwritten origin so the next open re-derives it from the box it will\nactually have (a stated `top`/`left` is the author's or the user's and\nsurvives, and is re-applied by that open), and fires `vf-close` with the\nreason and the value the close carried."
|
|
24779
24829
|
}
|
|
24780
24830
|
],
|
|
24781
24831
|
"events": [
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Writing app files. The box's own chunks are copied as they are, so the
|
|
3
|
+
* picture is never re-encoded; any app chunks already there are dropped,
|
|
4
|
+
* and the manifest's and the code's go in before IEND.
|
|
5
|
+
*/
|
|
6
|
+
import type { AppManifest } from '../shell/app-file.js';
|
|
7
|
+
/** What {@link packApp} packs. */
|
|
8
|
+
export interface PackAppInput {
|
|
9
|
+
/** The manifest's fields but its format and icon, which the packer fills in. */
|
|
10
|
+
manifest: Omit<AppManifest, 'format' | 'icon'>;
|
|
11
|
+
/** The icon's PNG, 32 × 32. */
|
|
12
|
+
icon: Uint8Array;
|
|
13
|
+
/** The application's code: one ES module whose default export is its factory. */
|
|
14
|
+
code: string;
|
|
15
|
+
/** The artwork's PNG, for the box. Without it the icon is stamped there. */
|
|
16
|
+
artwork?: Uint8Array;
|
|
17
|
+
}
|
|
18
|
+
/** `box` with `manifest` and `code` in its chunks. */
|
|
19
|
+
export declare function writeAppFile(box: Uint8Array, manifest: AppManifest, code: string): Promise<Uint8Array>;
|
|
20
|
+
/** The whole app file: the box in the kit's frame, then the manifest and the code. */
|
|
21
|
+
export declare function packApp({ manifest, icon, code, artwork }: PackAppInput): Promise<Uint8Array>;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { CODE_KEYWORD as e, MANIFEST_KEYWORD as t, PNG_SIGNATURE as n, deflate as r, itxtKeyword as i, manifestOf as a, pngChunks as o } from "../shell/app-file.js";
|
|
2
|
+
import { concat as s, pngChunk as c } from "./png.js";
|
|
3
|
+
import { composeBox as l } from "./box.js";
|
|
4
|
+
//#region src/build/app-file.ts
|
|
5
|
+
var u = new TextEncoder();
|
|
6
|
+
function d(e, t, n) {
|
|
7
|
+
let r = Uint8Array.from([...e].map((e) => e.charCodeAt(0)));
|
|
8
|
+
return c("iTXt", s([
|
|
9
|
+
r,
|
|
10
|
+
Uint8Array.of(0, +!!n, 0, 0, 0),
|
|
11
|
+
t
|
|
12
|
+
]));
|
|
13
|
+
}
|
|
14
|
+
async function f(c, l, f) {
|
|
15
|
+
let p = o(c);
|
|
16
|
+
if (!p) throw Error("vintage-frames/build: the box is not a whole PNG");
|
|
17
|
+
let m = a(l);
|
|
18
|
+
if (typeof m == "string") throw Error(`vintage-frames/build: the manifest won't read back: ${m}`);
|
|
19
|
+
let h = [d(t, u.encode(JSON.stringify(m)), !1), d(e, await r(u.encode(f)), !0)], g = [n];
|
|
20
|
+
for (let e of p) e.type === "iTXt" && ["vintage-frames.app", "vintage-frames.code"].includes(i(e.data)) || (e.type === "IEND" && g.push(...h), g.push(c.subarray(e.start, e.end)));
|
|
21
|
+
return s(g);
|
|
22
|
+
}
|
|
23
|
+
async function p({ manifest: e, icon: t, code: n, artwork: r }) {
|
|
24
|
+
let i = {
|
|
25
|
+
...e,
|
|
26
|
+
format: 1,
|
|
27
|
+
icon: `data:image/png;base64,${m(t)}`
|
|
28
|
+
};
|
|
29
|
+
return f(await l({
|
|
30
|
+
manifest: i,
|
|
31
|
+
icon: t,
|
|
32
|
+
artwork: r
|
|
33
|
+
}), i, n);
|
|
34
|
+
}
|
|
35
|
+
function m(e) {
|
|
36
|
+
let t = "";
|
|
37
|
+
for (let n = 0; n < e.length; n += 32768) t += String.fromCharCode(...e.subarray(n, n + 32768));
|
|
38
|
+
return btoa(t);
|
|
39
|
+
}
|
|
40
|
+
//#endregion
|
|
41
|
+
export { p as packApp, f as writeAppFile };
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The box: the picture an app file shows. The kit's frame, with the
|
|
3
|
+
* application's artwork in the artwork slot, or its icon stamped large on
|
|
4
|
+
* the slot's backdrop when there is none, and lines of text set from the
|
|
5
|
+
* kit's strikes with the manifest's fields in them. Artwork and the stamp
|
|
6
|
+
* are pasted by their alpha. Nothing is drawn outside the slots.
|
|
7
|
+
*/
|
|
8
|
+
import type { AppManifest } from '../shell/app-file.js';
|
|
9
|
+
import type { Strike } from '../styles/strike.js';
|
|
10
|
+
import type { Pixels } from './png.js';
|
|
11
|
+
/** A box in a frame's picture, in its px. */
|
|
12
|
+
export interface Slot {
|
|
13
|
+
left: number;
|
|
14
|
+
top: number;
|
|
15
|
+
width: number;
|
|
16
|
+
height: number;
|
|
17
|
+
}
|
|
18
|
+
/** A frame: a picture with everything fixed drawn in, and its slots. */
|
|
19
|
+
export interface BoxFrame {
|
|
20
|
+
/** The picture, a PNG. */
|
|
21
|
+
picture: Uint8Array;
|
|
22
|
+
/** One system px, in the picture's px. Each of the artwork's px is a whole number of them. */
|
|
23
|
+
scale: number;
|
|
24
|
+
/** Where the artwork goes. Without artwork the icon goes there, `stamp` times its size and centered. */
|
|
25
|
+
artwork: Slot & {
|
|
26
|
+
stamp: number;
|
|
27
|
+
};
|
|
28
|
+
/** The lines of text. */
|
|
29
|
+
text: readonly TextSlot[];
|
|
30
|
+
}
|
|
31
|
+
/** A line of text on the box. */
|
|
32
|
+
export interface TextSlot {
|
|
33
|
+
/** The top-left of its em box, and the width it fits in. */
|
|
34
|
+
left: number;
|
|
35
|
+
top: number;
|
|
36
|
+
width: number;
|
|
37
|
+
strike: Strike;
|
|
38
|
+
/** Picture px per design px. */
|
|
39
|
+
scale: number;
|
|
40
|
+
/** `#rrggbb`. */
|
|
41
|
+
ink: string;
|
|
42
|
+
align?: 'left' | 'center' | 'right';
|
|
43
|
+
/** The line, with manifest fields in braces: `{name}`, `Version {version}`, `by {author}`, or fixed text. */
|
|
44
|
+
text: string;
|
|
45
|
+
}
|
|
46
|
+
/** What a box is composed from. */
|
|
47
|
+
export interface BoxInput {
|
|
48
|
+
manifest: AppManifest;
|
|
49
|
+
/** The icon's PNG, 32 × 32. */
|
|
50
|
+
icon: Uint8Array;
|
|
51
|
+
/** The artwork's PNG: the artwork slot's size in system px, or that divided by a whole number. */
|
|
52
|
+
artwork?: Uint8Array;
|
|
53
|
+
}
|
|
54
|
+
/** The box in the kit's frame, as a PNG. */
|
|
55
|
+
export declare function composeBox(input: BoxInput): Promise<Uint8Array>;
|
|
56
|
+
/** The box in `frame`, as pixels. */
|
|
57
|
+
export declare function drawBox(frame: BoxFrame, { manifest, icon, artwork }: BoxInput): Promise<Pixels>;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { FRAME as e } from "./frame.js";
|
|
2
|
+
import { decodePng as t, encodePng as n } from "./png.js";
|
|
3
|
+
//#region src/build/box.ts
|
|
4
|
+
var r = [
|
|
5
|
+
"id",
|
|
6
|
+
"name",
|
|
7
|
+
"version",
|
|
8
|
+
"requires",
|
|
9
|
+
"author"
|
|
10
|
+
];
|
|
11
|
+
async function i(t) {
|
|
12
|
+
return n(await a(e, t));
|
|
13
|
+
}
|
|
14
|
+
async function a(e, { manifest: n, icon: r, artwork: i }) {
|
|
15
|
+
let a = await t(e.picture, "the frame"), c = await t(r, "the icon");
|
|
16
|
+
if (c.width !== 32 || c.height !== 32) throw Error(`the icon is ${c.width} × ${c.height}, and an icon is 32 × 32`);
|
|
17
|
+
let u = e.artwork;
|
|
18
|
+
if (i) {
|
|
19
|
+
let n = await t(i, "the artwork"), r = {
|
|
20
|
+
width: u.width / e.scale,
|
|
21
|
+
height: u.height / e.scale
|
|
22
|
+
}, c = r.width / n.width;
|
|
23
|
+
if (!Number.isInteger(c) || n.height * c !== r.height) throw Error(`the artwork is ${n.width} × ${n.height}: draw it at ${r.width} × ${r.height}, or that divided by a whole number (${o(r)})`);
|
|
24
|
+
s(a, n, u.left, u.top, c * e.scale, u);
|
|
25
|
+
} else {
|
|
26
|
+
let e = 32 * u.stamp;
|
|
27
|
+
s(a, c, u.left + Math.floor((u.width - e) / 2), u.top + Math.floor((u.height - e) / 2), u.stamp, u);
|
|
28
|
+
}
|
|
29
|
+
for (let t of e.text) p(a, t, l(t.text, n));
|
|
30
|
+
return a;
|
|
31
|
+
}
|
|
32
|
+
function o({ width: e, height: t }) {
|
|
33
|
+
let n = [];
|
|
34
|
+
for (let r = 2; r <= e && n.length < 3; r++) e % r === 0 && t % r === 0 && n.push(`${e / r} × ${t / r}`);
|
|
35
|
+
return [...n, "…"].join(", ");
|
|
36
|
+
}
|
|
37
|
+
function s(e, t, n, r, i, a) {
|
|
38
|
+
let o = Math.max(n, a.left, 0), s = Math.max(r, a.top, 0), l = Math.min(n + t.width * i, a.left + a.width, e.width), u = Math.min(r + t.height * i, a.top + a.height, e.height);
|
|
39
|
+
for (let a = s; a < u; a++) for (let s = o; s < l; s++) {
|
|
40
|
+
let o = (Math.floor((a - r) / i) * t.width + Math.floor((s - n) / i)) * 4;
|
|
41
|
+
c(e, s, a, t.data.subarray(o, o + 4));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function c(e, t, n, [r, i, a, o]) {
|
|
45
|
+
if (!o) return;
|
|
46
|
+
let s = e.data, c = (n * e.width + t) * 4;
|
|
47
|
+
if (o === 255) {
|
|
48
|
+
s[c] = r, s[c + 1] = i, s[c + 2] = a, s[c + 3] = 255;
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
let l = s[c + 3] * (255 - o) / 255, u = o + l;
|
|
52
|
+
s[c] = Math.round((r * o + s[c] * l) / u), s[c + 1] = Math.round((i * o + s[c + 1] * l) / u), s[c + 2] = Math.round((a * o + s[c + 2] * l) / u), s[c + 3] = Math.round(u);
|
|
53
|
+
}
|
|
54
|
+
function l(e, t) {
|
|
55
|
+
return e.replace(/\{(\w+)\}/g, (n, i) => {
|
|
56
|
+
let a = r.find((e) => e === i);
|
|
57
|
+
if (!a) throw Error(`the frame's line "${e}" names {${i}}, which a manifest doesn't have`);
|
|
58
|
+
return t[a];
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
var u = (e) => `"${e}" (U+${e.codePointAt(0).toString(16).toUpperCase().padStart(4, "0")})`, d = (e, t) => [...t].reduce((t, n) => t + e.glyphs[n].advance, 0);
|
|
62
|
+
function f(e, t, n) {
|
|
63
|
+
if (d(e, t) <= n) return t;
|
|
64
|
+
if (!e.glyphs["…"]) throw Error(`${e.family} has no ${u("…")} to shorten "${t}" with`);
|
|
65
|
+
let r = [...t];
|
|
66
|
+
for (; r.length && d(e, r.join("").trimEnd() + "…") > n;) r.pop();
|
|
67
|
+
return r.join("").trimEnd() + "…";
|
|
68
|
+
}
|
|
69
|
+
function p(e, t, n) {
|
|
70
|
+
let { strike: r, scale: i } = t;
|
|
71
|
+
for (let e of n) if (!r.glyphs[e]) throw Error(`${r.family} has no ${u(e)}, in "${n}"`);
|
|
72
|
+
let a = /^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i.exec(t.ink);
|
|
73
|
+
if (!a) throw Error(`the frame's ink "${t.ink}" is not #rrggbb`);
|
|
74
|
+
let o = Uint8Array.of(parseInt(a[1], 16), parseInt(a[2], 16), parseInt(a[3], 16), 255), s = f(r, n, t.width / i), l = d(r, s) * i, p = t.align === "center" ? Math.floor((t.width - l) / 2 / i) * i : t.align === "right" ? t.width - l : 0, m = {
|
|
75
|
+
left: t.left,
|
|
76
|
+
top: t.top,
|
|
77
|
+
width: t.width,
|
|
78
|
+
height: (r.ascent + r.descent) * i
|
|
79
|
+
}, h = t.top + r.ascent * i, g = t.left + p;
|
|
80
|
+
for (let t of s) {
|
|
81
|
+
let n = r.glyphs[t];
|
|
82
|
+
n.rows.forEach((t, r) => {
|
|
83
|
+
let a = h - (n.y0 + n.height - r) * i;
|
|
84
|
+
for (let r = 0; r < t.length; r++) {
|
|
85
|
+
if (t[r] !== "#") continue;
|
|
86
|
+
let s = g + (n.x0 + r) * i;
|
|
87
|
+
for (let t = Math.max(a, m.top, 0); t < Math.min(a + i, m.top + m.height, e.height); t++) for (let n = Math.max(s, m.left, 0); n < Math.min(s + i, m.left + m.width, e.width); n++) c(e, n, t, o);
|
|
88
|
+
}
|
|
89
|
+
}), g += n.advance * i;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
//#endregion
|
|
93
|
+
export { i as composeBox, a as drawBox };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `vintage-frames/build/client`: the types of a `*.png?app` import, which
|
|
3
|
+
* `appFiles()` serves. A site adds it to tsconfig's `types` beside
|
|
4
|
+
* `vite/client`.
|
|
5
|
+
*/
|
|
6
|
+
declare module '*.png?app' {
|
|
7
|
+
/** The application's factory: its definition, given the manifest's icon. */
|
|
8
|
+
const app: (...args: any[]) => import('../shell/index.js').AppDefinition<any>;
|
|
9
|
+
export default app;
|
|
10
|
+
/** The app file's manifest. */
|
|
11
|
+
export const manifest: import('../shell/pure.js').AppManifest;
|
|
12
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
//#region src/build/frame-picture.ts
|
|
2
|
+
var e = Uint8Array.from(atob("iVBORw0KGgoAAAANSUhEUgAAAeAAAAJnCAYAAABPiYBoAAAL3klEQVR42u3VwQkAIAwEwfRfW3rSBnwJBuFmYQpQAldVtQCAcT4BAAwwAKQNsCRJepcBliTJAEuSZIAlSZIBliTJAEuSJAMsSZIBliRJBliSJAMsSZIMsCRJBliSJAMsSZIMsCRJBliSJBlgSZIMsCRJMsCSJBlgSZJkgCVJMsCSJBlgAyxJkgGWJMkAS5IkAyxJkgGWJEkGWJIkAyxJkgywJEkGWJIkA2yAJUkywJIkGWBJkmSAJUkywJIkyQBLkmSAJUmSAZYkyQBLkmSADbAkSQZYkiQDLEmSDPDxAQCEM8AGGAADbIABMMAG+NEDuhuAQAbYAANggA0wAAbYABtgAAywAQbAABtgAwyAATbAABhgA2yAATDABhgAA2yADTAABtgAA2CADTAABtgAG2AADLABBsAAG2ADDIABNsAAGGADbIABMMAGGAADbIANMAAG2AADYIANMAAG2AAbYAAMsAEGwAAbYAMMgAE2wAAYYANsgAEwwAYYAANsgA0wAAbYAANggA0wAAbYABtgAAywAQbAABtgAwyAATbAABhgA2yAATDABhgAA2yADTAABtgAA2CADTAABtgAG2AADLABBsAAG2ADDIABNsAAGGADbIABMMAGGAADbIANMAAG2AADYIANsCMEMMAG2AADYIANMAAG2AAbYAAMsAEGwAAbYAMMgAE2wAAYYANsgAEwwAYYAANsgA0wgAE2wAYYAANsgAEwwAbYAANggA0wAAbYABtgAAywAQbAABtgAwyAATbAABhgA2yAAQywATbAABhgAwyAATbABhgAA2yAATDABtgAA2CADTAABtgAG2AADLABBsAAG2ADDGCADbABBsAAG2AADLABNsAAGGADDIABNsAGGAADbIABMMAG2AADYIANMAAG2AAbYAADbIANMAAG2AADYIANsAEGwAAbYAAMsAE2wAAYYAMMgAE2wAYYAANsgAEwwAbYAANggA0wAAbYAANggA2wAQbAABtgAAywATbAABhgAwyAATbABhgAA2yAATDABtgAA2CADTAABtgAA2CADbABBsAAG2AADLABNsAAGGADDIABNsAGGAADbIABMMAG2AADYIANMAAG2AADYIANsAEGwAAbYAAMsAE2wAAYYAMMgAE2wAYYAANsgAEwwAbYAANggA0wAAbYAANggA2wAQbAABtgAAywATbAABhgAwyAATbABhgAA2yAATDABtgAA2CADTAABtgAO0IAA2yADTAABtgAA2CADbABBsAAG2AADLABNsAAGGADDIABNsAGGAADbIABMMAG2BECGGADbIABMMAGGAADbIANMAAG2AADYIANsAEGwAAbYAAMsAE2wAAYYAMMgAE2wAYYwAAbYAMMgAE2wAAYYANsgAEwwAYYAANsgA0wAAbYAANggA2wAQbAABtgAAywATbAAAbYABtgAAywAQbAABtgAwyAATbAABhgA2yAATDABhgAA2yADTAABtgAA2CADbABBjDABtgAA2CADTAABtgAG2AADLABBsAAG2ADDIABNsAAGGADbIABMMAGGAADbIANMAAG2AADYIANMAAG2AAbYAAMsAEGwAAbYAMMgAE2wAAYYANsgAEwwAYYAANsgA0wAAbYAANggA0wAAbYABtgAAywAQbAABtgAwyAATbAABhgA2yAATDABhgAA2yADTAABtgAA2CADTAABtgAG2AADLABBsAAG2ADDIABNsAAGGADbIABMMAGGAADbIANMAAG2AADYIANMAAG2AAbYAAMsAEGwAAbYAMMgAE2wAAYYANsgAEwwAYYAANsgA0wAAbYAANggA0wAAbYABtgAAywAQbAABtgAwyAATbAABhgA2yAATDABhgAA2yADTAABtgAA2CADbAjBDDABtgAA2CADTAABtgAG2AADLABBsAAG2ADDIABNsAAGGADbIABMMAGGAADbIANMIABNsAGGAADbIABMMAG2AADYIANMAAG2AAbYAAMsAEGwAAbYAMMgAE2wAAYYANsgAEMsAE2wAAYYAMMgAE2wAYYAANsgAEwwAbYAANggA0wAAbYABtgAAywAQbAABtgAwxggA2wAQbAABtgAAywATbAABhgAwyAATbABhgAA2yAATDABtgAA2CADTAABtgAG2AADLABBsAAG2AADLABNsAAGGADDIABNsAGGAADbIABMMAG2AADYIANMAAG2AAbYAAMsAEGwAAbYAAMsAE2wAAYYAMMgAE2wAYYAANsgAEwwAbYAANggA0wAAbYABtgAAywAQbAABtgAAywATbAABhgAwyAATbABhgAA2yAATDABtgAA2CADTAABtgAG2AADLABBsAAG2AADLABNsAAGGADDIABNsAGGAADbIABMMAG2AADYIANMAAG2AAbYAAMsAEGwAAbYAAMsAE2wAAYYAMMgAE2wAYYAANsgAEwwAbYAANggA0wAAbYABtgAAywAQbAABtgRwhggA2wAQbAABtgAAywATbAABhgAwyAATbABhgAA2yAATDABtgAA2CADTAABtgAG2AAA2yADTAABtgAA2CADbABBsAAG2AADLABNsAAGGADDIABNsAGGAADbIABMMAG2AADGGADbIABMMAGGAADbIANMAAG2AADYIANsAEGwAAbYAAMsAE2wAAYYAMMgAE2wAYYwAAbYAMMgAE2wAAYYANsgAEwwAYYAANsgA0wAAbYAANggA2wAQbAABtgAAywATbAAAbYABtgAAywAQbAABtgAwyAATbAABhgA2yAATDABhgAA2yADTAABtgAA2CADbABBsAAG2AADLABBsAAG2ADDIABNsAAGGADbIABMMAGGAADbIANMAAG2AADYIANsAEGwAAbYAAMsAEGwAAbYAMMgAE2wAAYYANsgAEwwAYYAANsgA0wAAbYAANggA2wAQbAABtgAAywAQbAABtgAwyAATbAABhgA2yAATDABhgAA2yADTAABtgAA2CADbABBsAAG2AADLABBsAAG2ADDIABNsAAGGADbIABMMAGGAADbICvHwBAOANsgAEwwAYYAANsgCVJCs8AS5JkgCVJMsCSJMkAS5JkgCVJkgGWJMkAS5IkAyxJkgGWJEkGWJIkAyxJkgGWJEkGWJIkAyxJkgywJEkGWJIkGWBJkgywJEkywJIkGWBJkgywAZYkyQBLkmSAJUmSAZYkyQBLkiQDLEmSAZYkSQZYkiQDLEmSATbAkiQZYEmSDLAkSTLAkiQZYEmSZIAlSTLAkiTJAEuSZIAlSTLABliSJAMsSZIBliRJBliSJAMsSZIMsCRJBliSJBlgSZIMsCRJBtgAS5JkgCVJMsCSJMkAS5JkgCVJkgGWJMkAS5IkAyxJkgGWJMkAG2BJkgywJEkGWJIkGWBJkgywJEkywJIkGWBJkmSAJUkywJIkGWADLEmSAZYkyQBLkiQDLEmSAZYkSQZYkiQDLEmSDLAkSQZYkiQDbIAlSTLAkiQZYEmSZIAlSTLAkiTJAEuSZIAlSZIBliTJAEuSZIANsCRJBliSJAMsSZIMsCRJBliSJBlgSZIMsCRJMsCSJBlgSZJkgCVJMsCSJBlgSZJkgCVJMsCSJMkAS5JkgCVJkgGWJMkAS5IkAyxJkgGWJMkAG2BJkgywJEkGWJIkGWBJkgywJEkywJIkGWBJkmSAJUkywJIkGWADLEmSAZYkyQBLkiQDLEmSAZYkSQZYkiQDLEmSDLAkSQZYkiQDbIAlSTLAkiQZYEmSZIAlSTLAkiTJAEuSZIAlSZIBliTJAEuSZIANsCRJBliSJAMsSZIMsCRJBliSJBlgSZIMsCRJMsCSJBlgSZIMsAGWJMkAS5JkgCVJkgGWJMkAS5IkAyxJkgGWJEkGWJIkAyxJkgE2wJIkGWBJkgywJEkywJIkGWBJkmSAJUkywJIkyQBLkmSAJUkywAZYkiQDLEmSAZYkSQZYkiQDLEmSDLAkSQZYkiQZYEmSDLAkSQbYAEuSZIAlSTLAkiTJAEuSZIAlSZIBliTJAEuSJAMsSZIBliRJBliSpK8GGAAY4xMAwAADQIANOM+x56UXUawAAAAASUVORK5CYII="), (e) => e.charCodeAt(0));
|
|
3
|
+
//#endregion
|
|
4
|
+
export { e as FRAME_PNG };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The kit's frame: the picture every app file's box is drawn on, and its
|
|
3
|
+
* slots, in the picture's px. A placeholder: a white card on 160 × 205
|
|
4
|
+
* system px drawn at 3×, the artwork's slot on a gray backdrop inside a
|
|
5
|
+
* black rule, then the name, the version and the author, centered.
|
|
6
|
+
*
|
|
7
|
+
* The picture is box/frame.png (`npm run embed:frame`). A redrawn picture
|
|
8
|
+
* moves its slots here.
|
|
9
|
+
*/
|
|
10
|
+
import type { BoxFrame } from './box.js';
|
|
11
|
+
export declare const FRAME: BoxFrame;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import "../shell/app-file.js";
|
|
2
|
+
import { VF_BODY_STRIKE as e } from "../styles/body-strike.js";
|
|
3
|
+
import { VF_DISPLAY_STRIKE as t } from "../styles/display-strike.js";
|
|
4
|
+
import { FRAME_PNG as n } from "./frame-picture.js";
|
|
5
|
+
//#region src/build/frame.ts
|
|
6
|
+
var r = 3, i = {
|
|
7
|
+
left: 16 * r,
|
|
8
|
+
width: 128 * r,
|
|
9
|
+
scale: r,
|
|
10
|
+
ink: "#000000",
|
|
11
|
+
align: "center"
|
|
12
|
+
}, a = {
|
|
13
|
+
picture: n,
|
|
14
|
+
scale: r,
|
|
15
|
+
artwork: {
|
|
16
|
+
left: 16 * r,
|
|
17
|
+
top: 16 * r,
|
|
18
|
+
width: 128 * r,
|
|
19
|
+
height: 128 * r,
|
|
20
|
+
stamp: 3 * r
|
|
21
|
+
},
|
|
22
|
+
text: [
|
|
23
|
+
{
|
|
24
|
+
...i,
|
|
25
|
+
top: 152 * r,
|
|
26
|
+
strike: t,
|
|
27
|
+
text: "{name}"
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
...i,
|
|
31
|
+
top: 170 * r,
|
|
32
|
+
strike: e,
|
|
33
|
+
text: "Version {version}"
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
...i,
|
|
37
|
+
top: 182 * r,
|
|
38
|
+
strike: e,
|
|
39
|
+
text: "by {author}"
|
|
40
|
+
}
|
|
41
|
+
]
|
|
42
|
+
};
|
|
43
|
+
//#endregion
|
|
44
|
+
export { a as FRAME };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `vintage-frames/build` — app files, written: the Vite plugin that makes an
|
|
3
|
+
* application's build write its app file, the one that builds app files
|
|
4
|
+
* into a site, and the pieces under them: the packer, the writer and the box
|
|
5
|
+
* in the kit's frame. Build-only: it runs under Node, and no page imports
|
|
6
|
+
* it. Reading an app file is `vintage-frames/shell/pure`'s.
|
|
7
|
+
* docs/APP-FILES.md is the guide.
|
|
8
|
+
*/
|
|
9
|
+
export { appFile, appFiles } from './plugins.js';
|
|
10
|
+
export type { AppFileOptions } from './plugins.js';
|
|
11
|
+
export { packApp, writeAppFile } from './app-file.js';
|
|
12
|
+
export type { PackAppInput } from './app-file.js';
|
|
13
|
+
export { composeBox } from './box.js';
|
|
14
|
+
export type { BoxInput } from './box.js';
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Vite plugins. `appFile()` makes an application's build write its app
|
|
3
|
+
* file; `appFiles()` builds app files into a site.
|
|
4
|
+
*/
|
|
5
|
+
import type { Plugin } from 'vite';
|
|
6
|
+
export interface AppFileOptions {
|
|
7
|
+
/** The application's id, name, version and author, and its description if it has one: its `app.ts`. */
|
|
8
|
+
app: {
|
|
9
|
+
id: string;
|
|
10
|
+
name: string;
|
|
11
|
+
version: string;
|
|
12
|
+
author: string;
|
|
13
|
+
description?: string;
|
|
14
|
+
};
|
|
15
|
+
/** The module whose default export is the application's factory. */
|
|
16
|
+
entry: string;
|
|
17
|
+
/** Its 32 × 32 icon, a PNG. */
|
|
18
|
+
icon: string;
|
|
19
|
+
/** Its artwork for the box: a 128 × 128 PNG, or that divided by a whole number. */
|
|
20
|
+
artwork?: string;
|
|
21
|
+
/** A directory to write the app file to as well, such as a site's `apps/`. */
|
|
22
|
+
copyTo?: string;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Makes an application's build write its app file, `dist/<name>.png`. It
|
|
26
|
+
* builds the entry as a library, one ES module with the kit's entries left
|
|
27
|
+
* as imports, and fails the build when the bundle breaks a rule: CSS or any
|
|
28
|
+
* other file beside the code, more than one module, Lit or the kit bundled
|
|
29
|
+
* in, or an import of anything but the kit's three entries. `npm run dev`
|
|
30
|
+
* is untouched.
|
|
31
|
+
*/
|
|
32
|
+
export declare function appFile(options: AppFileOptions): Plugin;
|
|
33
|
+
/**
|
|
34
|
+
* Builds app files into a site. An import of `*.png?app` reads the file and
|
|
35
|
+
* refuses one that isn't an app file, or whose `requires` this kit's
|
|
36
|
+
* version doesn't meet. Its default export is the application's factory,
|
|
37
|
+
* its definition given the manifest's icon, and `manifest` is the manifest.
|
|
38
|
+
* The code imports the kit by name, so the page keeps one copy of it. A new
|
|
39
|
+
* file reloads the dev server's page.
|
|
40
|
+
*/
|
|
41
|
+
export declare function appFiles(): Plugin;
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { inspectAppFile as e, isVersion as t, satisfies as n } from "../shell/app-file.js";
|
|
2
|
+
import { VERSION as r } from "../shell/version.js";
|
|
3
|
+
import { packApp as i } from "./app-file.js";
|
|
4
|
+
import { mkdir as a, readFile as o, writeFile as s } from "node:fs/promises";
|
|
5
|
+
import { join as c, relative as l, resolve as u } from "node:path";
|
|
6
|
+
//#region src/build/plugins.ts
|
|
7
|
+
var d = [
|
|
8
|
+
"vintage-frames",
|
|
9
|
+
"vintage-frames/shell",
|
|
10
|
+
"vintage-frames/shell/pure"
|
|
11
|
+
], f = "app-code";
|
|
12
|
+
function p(e) {
|
|
13
|
+
let t = e.match(/(?<=[\\/]node_modules[\\/])(?:@[^\\/]+[\\/])?[^\\/]+/g)?.at(-1)?.replace("\\", "/");
|
|
14
|
+
return t === "vintage-frames" ? "a copy of vintage-frames" : t && /^(?:lit|lit-html|lit-element|@lit\/.+|@lit-labs\/.+)$/.test(t) ? `Lit (${t})` : null;
|
|
15
|
+
}
|
|
16
|
+
async function m(e) {
|
|
17
|
+
let n = JSON.parse(await o(c(e, "package.json"), "utf8")), i = n.devDependencies?.["vintage-frames"] ?? n.dependencies?.["vintage-frames"];
|
|
18
|
+
if (typeof i != "string") throw Error("package.json has no vintage-frames dev dependency");
|
|
19
|
+
if (!i.startsWith("^") || !t(i.slice(1))) throw Error(`package.json's vintage-frames range is "${i}", and an app file requires a caret range: ^${r}`);
|
|
20
|
+
return i;
|
|
21
|
+
}
|
|
22
|
+
function h(e) {
|
|
23
|
+
let { app: l, entry: f, icon: h, artwork: g, copyTo: _ } = e;
|
|
24
|
+
for (let e of [
|
|
25
|
+
"id",
|
|
26
|
+
"name",
|
|
27
|
+
"version",
|
|
28
|
+
"author"
|
|
29
|
+
]) if (typeof l?.[e] != "string" || !l[e]) throw Error(`vintage-frames appFile(): app.${e} is missing`);
|
|
30
|
+
if (!t(l.version)) throw Error(`vintage-frames appFile(): app.version, ${l.version}, is not a version`);
|
|
31
|
+
if (!f || !h) throw Error(`vintage-frames appFile(): ${f ? "icon" : "entry"} is missing`);
|
|
32
|
+
let v = `${l.name.replace(/[\\/:*?"<>|]/g, "-")}.png`, y = "", b = null;
|
|
33
|
+
return {
|
|
34
|
+
name: "vintage-frames:app-file",
|
|
35
|
+
apply: "build",
|
|
36
|
+
config: () => ({ build: {
|
|
37
|
+
lib: {
|
|
38
|
+
entry: f,
|
|
39
|
+
formats: ["es"],
|
|
40
|
+
fileName: "app"
|
|
41
|
+
},
|
|
42
|
+
copyPublicDir: !1,
|
|
43
|
+
rolldownOptions: {
|
|
44
|
+
external: /^vintage-frames(?:\/|$)/,
|
|
45
|
+
output: { codeSplitting: !1 }
|
|
46
|
+
}
|
|
47
|
+
} }),
|
|
48
|
+
configResolved(e) {
|
|
49
|
+
y = e.root;
|
|
50
|
+
},
|
|
51
|
+
buildStart() {
|
|
52
|
+
for (let e of [
|
|
53
|
+
h,
|
|
54
|
+
g,
|
|
55
|
+
"package.json"
|
|
56
|
+
]) e && this.addWatchFile(u(y, e));
|
|
57
|
+
},
|
|
58
|
+
generateBundle: {
|
|
59
|
+
order: "post",
|
|
60
|
+
async handler(e, t) {
|
|
61
|
+
let a = Object.values(t), s = a.filter((e) => e.type === "chunk"), c = a.filter((e) => e.type === "asset").map((e) => e.fileName);
|
|
62
|
+
c.length && this.error(`${l.name} emits ${c.join(", ")} beside its code, and an app file holds one module: no CSS import, no source map file, its art inlined.`);
|
|
63
|
+
let [f] = s;
|
|
64
|
+
(!f || s.length > 1) && this.error(`${l.name} builds to ${s.length} modules, and an app file holds one.`);
|
|
65
|
+
for (let e of Object.keys(f.modules)) {
|
|
66
|
+
let t = p(e);
|
|
67
|
+
t && this.error(`${l.name} bundles ${t}. It composes the page's kit and brings none of its own.`);
|
|
68
|
+
}
|
|
69
|
+
let _ = [...f.imports, ...f.dynamicImports].filter((e) => !d.includes(e));
|
|
70
|
+
_.length && this.error(`${l.name} imports ${_.join(", ")}. An application imports ${d.join(", ")} and its own files.`);
|
|
71
|
+
try {
|
|
72
|
+
let e = await m(y);
|
|
73
|
+
if (!n("0.18.0", e)) throw Error(`package.json requires vintage-frames ${e}, and the build is on ${r}: move the range`);
|
|
74
|
+
b = await i({
|
|
75
|
+
manifest: {
|
|
76
|
+
id: l.id,
|
|
77
|
+
name: l.name,
|
|
78
|
+
version: l.version,
|
|
79
|
+
requires: e,
|
|
80
|
+
author: l.author,
|
|
81
|
+
description: l.description
|
|
82
|
+
},
|
|
83
|
+
icon: new Uint8Array(await o(u(y, h))),
|
|
84
|
+
code: f.code,
|
|
85
|
+
artwork: g ? new Uint8Array(await o(u(y, g))) : void 0
|
|
86
|
+
});
|
|
87
|
+
} catch (e) {
|
|
88
|
+
this.error(`${l.name}: ${e.message}`);
|
|
89
|
+
}
|
|
90
|
+
delete t[f.fileName], this.emitFile({
|
|
91
|
+
type: "asset",
|
|
92
|
+
fileName: v,
|
|
93
|
+
source: b
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
async writeBundle() {
|
|
98
|
+
if (!_ || !b) return;
|
|
99
|
+
let e = u(y, _);
|
|
100
|
+
await a(e, { recursive: !0 }), await s(c(e, v), b);
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
function g() {
|
|
105
|
+
let t = "";
|
|
106
|
+
return {
|
|
107
|
+
name: "vintage-frames:app-files",
|
|
108
|
+
enforce: "pre",
|
|
109
|
+
configResolved(e) {
|
|
110
|
+
t = e.root;
|
|
111
|
+
},
|
|
112
|
+
async load(i) {
|
|
113
|
+
let a = i.indexOf("?");
|
|
114
|
+
if (a < 0) return null;
|
|
115
|
+
let s = i.slice(0, a), c = new URLSearchParams(i.slice(a + 1));
|
|
116
|
+
if (!s.endsWith(".png") || !(c.has("app") || c.has(f))) return null;
|
|
117
|
+
this.addWatchFile(s);
|
|
118
|
+
let u = l(t, s), d = await e(new Uint8Array(await o(s)));
|
|
119
|
+
typeof d == "string" && this.error(`${u} is not an app file: ${d}.`);
|
|
120
|
+
let { manifest: p, code: m } = d;
|
|
121
|
+
return c.has(f) ? m : (n("0.18.0", p.requires) || this.error(`${u}: ${p.name} ${p.version} requires vintage-frames ${p.requires}, and this site has ${r}. Build ${p.name} on ${r} and copy its app file in.`), [
|
|
122
|
+
`import factory from ${JSON.stringify(`${s}?${f}`)}`,
|
|
123
|
+
`export const manifest = ${JSON.stringify(p)}`,
|
|
124
|
+
"export default (...args) => ({ ...factory(...args), icon: manifest.icon })",
|
|
125
|
+
""
|
|
126
|
+
].join("\n"));
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
//#endregion
|
|
131
|
+
export { h as appFile, g as appFiles };
|