toiljs 0.0.7 → 0.0.9
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/build/backend/.tsbuildinfo +1 -1
- package/build/cli/.tsbuildinfo +1 -1
- package/build/cli/configure.d.ts +1 -0
- package/build/cli/configure.js +85 -20
- package/build/cli/create.d.ts +1 -0
- package/build/cli/create.js +18 -7
- package/build/cli/features.d.ts +2 -0
- package/build/cli/features.js +22 -0
- package/build/cli/index.js +8 -0
- package/build/client/.tsbuildinfo +1 -1
- package/build/client/components/Form.d.ts +12 -0
- package/build/client/components/Form.js +23 -0
- package/build/client/components/Image.d.ts +13 -0
- package/build/client/components/Image.js +22 -0
- package/build/client/components/Script.d.ts +13 -0
- package/build/client/components/Script.js +68 -0
- package/build/client/components/Slot.d.ts +6 -0
- package/build/client/components/Slot.js +6 -0
- package/build/client/dev/error-overlay.d.ts +20 -0
- package/build/client/dev/error-overlay.js +123 -0
- package/build/client/head/head.d.ts +2 -0
- package/build/client/head/head.js +17 -2
- package/build/client/head/metadata.d.ts +29 -0
- package/build/client/head/metadata.js +38 -0
- package/build/client/index.d.ts +15 -3
- package/build/client/index.js +8 -2
- package/build/client/navigation/navigation.d.ts +3 -0
- package/build/client/navigation/navigation.js +42 -1
- package/build/client/routing/Router.d.ts +1 -0
- package/build/client/routing/Router.js +56 -34
- package/build/client/routing/action.d.ts +17 -0
- package/build/client/routing/action.js +55 -0
- package/build/client/routing/hooks.d.ts +1 -0
- package/build/client/routing/hooks.js +6 -7
- package/build/client/routing/loader.d.ts +10 -2
- package/build/client/routing/loader.js +83 -24
- package/build/client/routing/mount.d.ts +1 -1
- package/build/client/routing/mount.js +12 -4
- package/build/client/routing/slot-context.d.ts +2 -0
- package/build/client/routing/slot-context.js +2 -0
- package/build/client/types.d.ts +1 -0
- package/build/compiler/.tsbuildinfo +1 -1
- package/build/compiler/config.d.ts +10 -0
- package/build/compiler/config.js +5 -1
- package/build/compiler/docs.js +26 -26
- package/build/compiler/fonts.d.ts +4 -0
- package/build/compiler/fonts.js +64 -0
- package/build/compiler/generate.js +67 -32
- package/build/compiler/image-report.d.ts +2 -0
- package/build/compiler/image-report.js +62 -0
- package/build/compiler/plugin.js +1 -1
- package/build/compiler/prerender.d.ts +7 -0
- package/build/compiler/prerender.js +111 -0
- package/build/compiler/routes.d.ts +3 -0
- package/build/compiler/routes.js +50 -5
- package/build/compiler/seo.d.ts +70 -0
- package/build/compiler/seo.js +221 -0
- package/build/compiler/vite.js +13 -1
- package/build/io/.tsbuildinfo +1 -1
- package/build/shared/.tsbuildinfo +1 -1
- package/examples/basic/client/404.tsx +1 -1
- package/examples/basic/client/components/Header.tsx +38 -0
- package/examples/basic/client/components/HoneycombBackground.tsx +86 -18
- package/examples/basic/client/global-error.tsx +3 -3
- package/examples/basic/client/layout.tsx +2 -33
- package/examples/basic/client/public/images/test_image.webp +0 -0
- package/examples/basic/client/routes/about.tsx +8 -0
- package/examples/basic/client/routes/get-started.tsx +1 -1
- package/examples/basic/client/routes/index.tsx +8 -1
- package/examples/basic/client/routes/io.tsx +1 -1
- package/examples/basic/client/routes/loader-demo/index.tsx +29 -1
- package/examples/basic/client/routes/test.tsx +8 -0
- package/examples/basic/client/styles/main.css +48 -1
- package/package.json +8 -6
- package/presets/eslint.js +7 -4
- package/presets/tsconfig.json +1 -1
- package/src/backend/index.ts +1 -1
- package/src/cli/configure.ts +102 -21
- package/src/cli/create.ts +25 -9
- package/src/cli/features.ts +33 -1
- package/src/cli/index.ts +10 -1
- package/src/cli/ui.ts +1 -1
- package/src/cli/validate.ts +1 -1
- package/src/client/components/Form.tsx +65 -0
- package/src/client/components/Image.tsx +89 -0
- package/src/client/components/Script.tsx +113 -0
- package/src/client/components/Slot.tsx +21 -0
- package/src/client/dev/error-overlay.tsx +197 -0
- package/src/client/head/head.ts +28 -3
- package/src/client/head/metadata.ts +92 -0
- package/src/client/index.ts +20 -3
- package/src/client/navigation/Link.tsx +1 -1
- package/src/client/navigation/navigation.ts +74 -4
- package/src/client/navigation/prefetch.ts +2 -2
- package/src/client/routing/Router.tsx +128 -62
- package/src/client/routing/action.ts +122 -0
- package/src/client/routing/error-boundary.tsx +1 -1
- package/src/client/routing/hooks.ts +17 -23
- package/src/client/routing/loader.ts +158 -35
- package/src/client/routing/mount.tsx +25 -3
- package/src/client/routing/slot-context.ts +7 -0
- package/src/client/types.ts +6 -4
- package/src/compiler/config.ts +40 -3
- package/src/compiler/docs.ts +26 -26
- package/src/compiler/fonts.ts +87 -0
- package/src/compiler/generate.ts +69 -31
- package/src/compiler/image-report.ts +85 -0
- package/src/compiler/plugin.ts +2 -2
- package/src/compiler/prerender.ts +130 -0
- package/src/compiler/routes.ts +62 -7
- package/src/compiler/seo.ts +356 -0
- package/src/compiler/vite.ts +21 -4
- package/src/io/FastSet.ts +1 -1
- package/src/io/index.ts +1 -1
- package/src/io/types.ts +1 -1
- package/src/server/index.ts +1 -1
- package/src/server/main.ts +1 -1
- package/src/shared/index.ts +1 -1
- package/test/dom/Image.test.tsx +46 -0
- package/test/dom/Script.test.tsx +45 -0
- package/test/dom/action.test.tsx +129 -0
- package/test/dom/error-overlay.test.tsx +44 -0
- package/test/dom/loader.test.tsx +121 -0
- package/test/dom/revalidate.test.tsx +38 -0
- package/test/dom/route-head.test.tsx +34 -0
- package/test/dom/router-loading.test.tsx +44 -0
- package/test/dom/slot.test.tsx +109 -0
- package/test/dom/view-transitions.test.tsx +51 -0
- package/test/features.test.ts +31 -0
- package/test/fonts.test.ts +26 -0
- package/test/metadata.test.ts +41 -0
- package/test/prerender.test.ts +46 -0
- package/test/routes.test.ts +20 -1
- package/test/seo.test.ts +142 -0
- package/examples/basic/client/template.tsx +0 -7
|
@@ -4,6 +4,7 @@ const HEX_R = 34;
|
|
|
4
4
|
const GAP = 3;
|
|
5
5
|
const DRAW_R = HEX_R - GAP;
|
|
6
6
|
const GLOW_DIST = 140;
|
|
7
|
+
const LOGO_SRC = '/images/logo.svg';
|
|
7
8
|
|
|
8
9
|
function tracePath(ctx: CanvasRenderingContext2D, cx: number, cy: number, r: number) {
|
|
9
10
|
ctx.beginPath();
|
|
@@ -43,6 +44,41 @@ function buildGrid(w: number, h: number): Array<{ x: number; y: number }> {
|
|
|
43
44
|
return hexes;
|
|
44
45
|
}
|
|
45
46
|
|
|
47
|
+
/** Samples logo colours per hex centre for use in border glow. */
|
|
48
|
+
function buildLogoColors(
|
|
49
|
+
img: HTMLImageElement,
|
|
50
|
+
hexes: Array<{ x: number; y: number }>,
|
|
51
|
+
w: number,
|
|
52
|
+
h: number,
|
|
53
|
+
): Array<[number, number, number]> | null {
|
|
54
|
+
const lc = document.createElement('canvas');
|
|
55
|
+
|
|
56
|
+
lc.width = w;
|
|
57
|
+
lc.height = h;
|
|
58
|
+
|
|
59
|
+
const lctx = lc.getContext('2d');
|
|
60
|
+
|
|
61
|
+
if (!lctx) return null;
|
|
62
|
+
|
|
63
|
+
// Draw logo large + blurred, roughly where the hero logo sits in the viewport
|
|
64
|
+
const size = 700;
|
|
65
|
+
const cx = w / 2;
|
|
66
|
+
const cy = h * 0.42;
|
|
67
|
+
|
|
68
|
+
lctx.filter = 'blur(90px)';
|
|
69
|
+
lctx.drawImage(img, cx - size / 2, cy - size / 2, size, size);
|
|
70
|
+
lctx.filter = 'none';
|
|
71
|
+
|
|
72
|
+
// Sample one pixel per hex centre so we can use logo colours for border glow
|
|
73
|
+
return hexes.map(({ x, y }) => {
|
|
74
|
+
const px = Math.round(Math.max(0, Math.min(w - 1, x)));
|
|
75
|
+
const py = Math.round(Math.max(0, Math.min(h - 1, y)));
|
|
76
|
+
const d = lctx.getImageData(px, py, 1, 1).data;
|
|
77
|
+
|
|
78
|
+
return [d[0], d[1], d[2]] as [number, number, number];
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
46
82
|
export default function HoneycombBackground() {
|
|
47
83
|
const canvasRef = useRef<HTMLCanvasElement>(null);
|
|
48
84
|
const mouse = useRef({ x: -9999, y: -9999 });
|
|
@@ -58,19 +94,42 @@ export default function HoneycombBackground() {
|
|
|
58
94
|
|
|
59
95
|
const dpr = window.devicePixelRatio || 1;
|
|
60
96
|
let hexes: Array<{ x: number; y: number }> = [];
|
|
97
|
+
let hexColors: Array<[number, number, number]> = [];
|
|
61
98
|
let raf: number;
|
|
62
99
|
|
|
100
|
+
const img = new Image();
|
|
101
|
+
|
|
102
|
+
img.onload = () => {
|
|
103
|
+
const colors = buildLogoColors(img, hexes, window.innerWidth, window.innerHeight);
|
|
104
|
+
|
|
105
|
+
if (colors) {
|
|
106
|
+
hexColors = colors;
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
img.src = LOGO_SRC;
|
|
111
|
+
|
|
63
112
|
function resize() {
|
|
64
113
|
if (!canvas || !ctx) return;
|
|
65
114
|
|
|
66
115
|
const w = window.innerWidth;
|
|
67
116
|
const h = window.innerHeight;
|
|
117
|
+
|
|
68
118
|
canvas.width = w * dpr;
|
|
69
119
|
canvas.height = h * dpr;
|
|
70
120
|
canvas.style.width = `${w}px`;
|
|
71
121
|
canvas.style.height = `${h}px`;
|
|
72
122
|
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
73
123
|
hexes = buildGrid(w, h);
|
|
124
|
+
|
|
125
|
+
// Rebuild logo colours if image is already loaded
|
|
126
|
+
if (img.complete && img.naturalWidth > 0) {
|
|
127
|
+
const colors = buildLogoColors(img, hexes, w, h);
|
|
128
|
+
|
|
129
|
+
if (colors) {
|
|
130
|
+
hexColors = colors;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
74
133
|
}
|
|
75
134
|
|
|
76
135
|
function draw() {
|
|
@@ -78,36 +137,53 @@ export default function HoneycombBackground() {
|
|
|
78
137
|
|
|
79
138
|
const w = window.innerWidth;
|
|
80
139
|
const h = window.innerHeight;
|
|
140
|
+
|
|
81
141
|
ctx.clearRect(0, 0, w, h);
|
|
82
142
|
|
|
83
143
|
const mx = mouse.current.x;
|
|
84
144
|
const my = mouse.current.y;
|
|
85
145
|
|
|
86
|
-
for (
|
|
146
|
+
for (let i = 0; i < hexes.length; i++) {
|
|
147
|
+
const hex = hexes[i];
|
|
148
|
+
|
|
149
|
+
if (!hex) continue;
|
|
150
|
+
|
|
151
|
+
const { x, y } = hex;
|
|
87
152
|
const dist = Math.hypot(x - mx, y - my);
|
|
88
153
|
const t = Math.max(0, 1 - dist / GLOW_DIST);
|
|
89
154
|
const ease = t * t * (3 - 2 * t);
|
|
90
155
|
|
|
156
|
+
// Base fill
|
|
91
157
|
tracePath(ctx, x, y, DRAW_R);
|
|
92
|
-
|
|
93
158
|
ctx.fillStyle = 'rgba(255,255,255,0.018)';
|
|
94
159
|
ctx.fill();
|
|
95
160
|
|
|
96
|
-
if (ease > 0) {
|
|
97
|
-
ctx.fillStyle = `rgba(72,148,255,${ease * 0.025})`;
|
|
98
|
-
ctx.fill();
|
|
99
|
-
}
|
|
100
161
|
|
|
162
|
+
|
|
163
|
+
// Base border
|
|
164
|
+
tracePath(ctx, x, y, DRAW_R);
|
|
101
165
|
ctx.strokeStyle = 'rgba(255,255,255,0.055)';
|
|
102
166
|
ctx.lineWidth = 1;
|
|
103
167
|
ctx.stroke();
|
|
104
168
|
|
|
169
|
+
// Glow border using logo-sampled colour
|
|
105
170
|
if (ease > 0) {
|
|
171
|
+
const col = hexColors[i];
|
|
172
|
+
const r = col ? col[0] : 120;
|
|
173
|
+
const g = col ? col[1] : 180;
|
|
174
|
+
const b = col ? col[2] : 255;
|
|
175
|
+
|
|
176
|
+
// If the logo has colour here, use it; otherwise fall back to a soft white
|
|
177
|
+
const bright = r + g + b;
|
|
178
|
+
const fr = bright > 30 ? r : 120;
|
|
179
|
+
const fg = bright > 30 ? g : 180;
|
|
180
|
+
const fb = bright > 30 ? b : 255;
|
|
181
|
+
|
|
106
182
|
ctx.save();
|
|
107
|
-
ctx.shadowColor = `rgba(
|
|
183
|
+
ctx.shadowColor = `rgba(${fr},${fg},${fb},${ease * 0.25})`;
|
|
108
184
|
ctx.shadowBlur = 8 * ease;
|
|
109
|
-
ctx.strokeStyle = `rgba(
|
|
110
|
-
ctx.lineWidth = 1 + ease * 0.
|
|
185
|
+
ctx.strokeStyle = `rgba(${fr},${fg},${fb},${ease * 0.18})`;
|
|
186
|
+
ctx.lineWidth = 1 + ease * 0.5;
|
|
111
187
|
ctx.stroke();
|
|
112
188
|
ctx.restore();
|
|
113
189
|
}
|
|
@@ -148,15 +224,7 @@ export default function HoneycombBackground() {
|
|
|
148
224
|
return (
|
|
149
225
|
<canvas
|
|
150
226
|
ref={canvasRef}
|
|
151
|
-
|
|
152
|
-
position: 'fixed',
|
|
153
|
-
inset: 0,
|
|
154
|
-
width: '100%',
|
|
155
|
-
height: '100%',
|
|
156
|
-
pointerEvents: 'none',
|
|
157
|
-
zIndex: 0,
|
|
158
|
-
}}
|
|
227
|
+
className="honeycomb-canvas"
|
|
159
228
|
/>
|
|
160
229
|
);
|
|
161
230
|
}
|
|
162
|
-
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
// Root error boundary. Unlike a route's `error.tsx`, this sits *outside* the root layout, so it
|
|
2
|
-
// also catches errors thrown while rendering the layout itself
|
|
2
|
+
// also catches errors thrown while rendering the layout itself, the last line of defense.
|
|
3
3
|
export default function GlobalError({ error, reset }: Toil.RouteErrorProps) {
|
|
4
4
|
return (
|
|
5
|
-
<main
|
|
5
|
+
<main className="global-error">
|
|
6
6
|
<h1>Something went wrong</h1>
|
|
7
|
-
<p
|
|
7
|
+
<p className="global-error-message">{error.message}</p>
|
|
8
8
|
<button type="button" onClick={reset}>
|
|
9
9
|
Try again
|
|
10
10
|
</button>
|
|
@@ -1,13 +1,8 @@
|
|
|
1
1
|
import { type ReactNode } from 'react';
|
|
2
2
|
import Footer from './components/Footer';
|
|
3
|
+
import Header from './components/Header';
|
|
3
4
|
import HoneycombBackground from './components/HoneycombBackground';
|
|
4
5
|
|
|
5
|
-
const GitHubIcon = () => (
|
|
6
|
-
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
|
7
|
-
<path d="M12 2C6.477 2 2 6.477 2 12c0 4.418 2.865 8.166 6.839 9.489.5.092.682-.217.682-.482 0-.237-.009-.868-.013-1.703-2.782.604-3.369-1.341-3.369-1.341-.454-1.154-1.11-1.462-1.11-1.462-.908-.62.069-.608.069-.608 1.003.07 1.531 1.03 1.531 1.03.892 1.529 2.341 1.087 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.11-4.555-4.943 0-1.091.39-1.984 1.029-2.683-.103-.253-.446-1.27.098-2.647 0 0 .84-.269 2.75 1.025A9.578 9.578 0 0 1 12 6.836a9.59 9.59 0 0 1 2.504.337c1.909-1.294 2.747-1.025 2.747-1.025.546 1.377.202 2.394.1 2.647.64.699 1.028 1.592 1.028 2.683 0 3.842-2.339 4.687-4.566 4.935.359.309.678.919.678 1.852 0 1.336-.012 2.415-.012 2.743 0 .267.18.579.688.481C19.138 20.163 22 16.418 22 12c0-5.523-4.477-10-10-10z" />
|
|
8
|
-
</svg>
|
|
9
|
-
);
|
|
10
|
-
|
|
11
6
|
export default function Layout({ children }: { children?: ReactNode }) {
|
|
12
7
|
return (
|
|
13
8
|
<div className="app">
|
|
@@ -17,33 +12,7 @@ export default function Layout({ children }: { children?: ReactNode }) {
|
|
|
17
12
|
title="ToilJS"
|
|
18
13
|
meta={[{ name: 'description', content: 'The most performant React framework.' }]}
|
|
19
14
|
/>
|
|
20
|
-
<
|
|
21
|
-
<Toil.Link href="/" className="nav-logo">
|
|
22
|
-
<img src="images/logo.svg" alt="ToilJS" width={28} height={28} />
|
|
23
|
-
<span>ToilJS</span>
|
|
24
|
-
</Toil.Link>
|
|
25
|
-
|
|
26
|
-
<nav className="nav-center">
|
|
27
|
-
<Toil.NavLink href="/" end className="nav-center-link">
|
|
28
|
-
Home
|
|
29
|
-
</Toil.NavLink>
|
|
30
|
-
<Toil.NavLink href="/get-started" className="nav-center-link">
|
|
31
|
-
Get Started
|
|
32
|
-
</Toil.NavLink>
|
|
33
|
-
</nav>
|
|
34
|
-
|
|
35
|
-
<nav className="nav-links">
|
|
36
|
-
<Toil.Link href="https://toil.org/docs">Docs</Toil.Link>
|
|
37
|
-
<a
|
|
38
|
-
href="https://github.com/btc-vision/toiljs"
|
|
39
|
-
target="_blank"
|
|
40
|
-
rel="noopener noreferrer"
|
|
41
|
-
className="nav-github">
|
|
42
|
-
<GitHubIcon />
|
|
43
|
-
GitHub
|
|
44
|
-
</a>
|
|
45
|
-
</nav>
|
|
46
|
-
</header>
|
|
15
|
+
<Header />
|
|
47
16
|
|
|
48
17
|
<main className="content">{children}</main>
|
|
49
18
|
|
|
Binary file
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
// Declarative per-route SEO, resolved by the router into <title> + <meta>/<link> tags. The root
|
|
2
|
+
// layout's titleTemplate (if any) still applies; component-level useHead/<Head> can override.
|
|
3
|
+
export const metadata: Toil.Metadata = {
|
|
4
|
+
title: 'About',
|
|
5
|
+
description: 'About the ToilJS example app.',
|
|
6
|
+
openGraph: { title: 'About · ToilJS', type: 'website' },
|
|
7
|
+
};
|
|
8
|
+
|
|
1
9
|
export default function About() {
|
|
2
10
|
return (
|
|
3
11
|
<main>
|
|
@@ -49,7 +49,7 @@ about.tsx → /about
|
|
|
49
49
|
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/></svg>
|
|
50
50
|
</div>
|
|
51
51
|
<h3>Entry Point</h3>
|
|
52
|
-
<p><code>client/toil.tsx</code> is the app entry. Import global CSS and call <code>Toil.mount()</code
|
|
52
|
+
<p><code>client/toil.tsx</code> is the app entry. Import global CSS and call <code>Toil.mount()</code>, runs once on startup.</p>
|
|
53
53
|
</div>
|
|
54
54
|
|
|
55
55
|
</div>
|
|
@@ -43,7 +43,14 @@ export default function Home() {
|
|
|
43
43
|
<section className="hero">
|
|
44
44
|
<div className="hero-logo">
|
|
45
45
|
<img src="images/logo.svg" className="hero-logo-glow" alt="" aria-hidden="true" width={96} height={96} />
|
|
46
|
-
<
|
|
46
|
+
<Toil.Image
|
|
47
|
+
src="images/logo.svg"
|
|
48
|
+
className="hero-logo-img"
|
|
49
|
+
alt="ToilJS"
|
|
50
|
+
width={96}
|
|
51
|
+
height={96}
|
|
52
|
+
priority
|
|
53
|
+
/>
|
|
47
54
|
</div>
|
|
48
55
|
|
|
49
56
|
<h1 className="hero-title">ToilJS</h1>
|
|
@@ -15,7 +15,7 @@ export default function IoDemo() {
|
|
|
15
15
|
<main>
|
|
16
16
|
<h1>Native IO</h1>
|
|
17
17
|
<p>
|
|
18
|
-
<code>new BinaryWriter()</code> with no import
|
|
18
|
+
<code>new BinaryWriter()</code> with no import, round-tripped {n} and "{s}" through{' '}
|
|
19
19
|
{bytes.length} bytes; FastSet size {seen.size}.
|
|
20
20
|
</p>
|
|
21
21
|
<Toil.Link href="/">Back home</Toil.Link>
|
|
@@ -7,8 +7,19 @@ export const loader = async ({ searchParams }: Toil.LoaderArgs) => {
|
|
|
7
7
|
return { loadedAt: new Date().toISOString(), q: searchParams.get('q') };
|
|
8
8
|
};
|
|
9
9
|
|
|
10
|
+
// Cache this route's data for 10s: revisiting within 10s is instant (no 2s wait); after that it
|
|
11
|
+
// refetches on navigation. Use `false` to cache forever, or omit for the default (refetch every nav).
|
|
12
|
+
export const revalidate: Toil.Revalidate = 10;
|
|
13
|
+
|
|
14
|
+
// Dynamic metadata derived from the loader's data (vs the static `metadata` export on /about).
|
|
15
|
+
export const generateMetadata: Toil.GenerateMetadata<Awaited<ReturnType<typeof loader>>> = ({
|
|
16
|
+
data,
|
|
17
|
+
}) => ({ title: `Loader demo, loaded ${data.loadedAt}` });
|
|
18
|
+
|
|
10
19
|
export default function LoaderDemo() {
|
|
11
|
-
|
|
20
|
+
// Pass the loader to infer the data type from its return, no generics, no restating the shape.
|
|
21
|
+
const data = Toil.useLoaderData(loader);
|
|
22
|
+
const router = Toil.useRouter();
|
|
12
23
|
return (
|
|
13
24
|
<main>
|
|
14
25
|
<h1>Loader demo</h1>
|
|
@@ -16,6 +27,23 @@ export default function LoaderDemo() {
|
|
|
16
27
|
Data loaded before render (no <code>useEffect</code>): <code>{data.loadedAt}</code>
|
|
17
28
|
{data.q !== null ? ` · q=${data.q}` : ''}
|
|
18
29
|
</p>
|
|
30
|
+
<p>
|
|
31
|
+
<button type="button" onClick={() => { router.revalidate(); }}>
|
|
32
|
+
Revalidate (refetch)
|
|
33
|
+
</button>
|
|
34
|
+
</p>
|
|
35
|
+
{/* The write half: an action runs on submit, then revalidates this route's loader so
|
|
36
|
+
`loadedAt` above updates, read → write → revalidate, no manual refetch. */}
|
|
37
|
+
<Toil.Form action={async (form) => { await wait(500); console.log('saved', form.get('note')); }}>
|
|
38
|
+
{({ pending }) => (
|
|
39
|
+
<>
|
|
40
|
+
<input name="note" placeholder="Leave a note" disabled={pending} />
|
|
41
|
+
<button type="submit" disabled={pending}>
|
|
42
|
+
{pending ? 'Saving…' : 'Save & revalidate'}
|
|
43
|
+
</button>
|
|
44
|
+
</>
|
|
45
|
+
)}
|
|
46
|
+
</Toil.Form>
|
|
19
47
|
<Toil.Link href="/">Back home</Toil.Link>
|
|
20
48
|
</main>
|
|
21
49
|
);
|
|
@@ -291,13 +291,26 @@ a:hover { color: var(--accent3); }
|
|
|
291
291
|
background: #131d2e;
|
|
292
292
|
}
|
|
293
293
|
|
|
294
|
+
/* ── Global Error ── */
|
|
295
|
+
.global-error {
|
|
296
|
+
padding: 3rem;
|
|
297
|
+
font-family: system-ui;
|
|
298
|
+
text-align: center;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
.global-error-message {
|
|
302
|
+
opacity: 0.7;
|
|
303
|
+
}
|
|
304
|
+
|
|
294
305
|
/* ── Footer ── */
|
|
295
306
|
.footer {
|
|
296
|
-
text-align: center;
|
|
297
307
|
padding: 1.25rem;
|
|
298
308
|
font-size: 0.82rem;
|
|
299
309
|
color: var(--muted);
|
|
300
310
|
border-top: 1px solid var(--border);
|
|
311
|
+
background: var(--bg);
|
|
312
|
+
position: relative;
|
|
313
|
+
z-index: 1;
|
|
301
314
|
}
|
|
302
315
|
|
|
303
316
|
/* ── Misc ── */
|
|
@@ -503,3 +516,37 @@ code {
|
|
|
503
516
|
.spinner { animation-duration: 0s; }
|
|
504
517
|
}
|
|
505
518
|
|
|
519
|
+
/* ── Global Error ── */
|
|
520
|
+
.global-error {
|
|
521
|
+
padding: 3rem;
|
|
522
|
+
font-family: system-ui;
|
|
523
|
+
text-align: center;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
.global-error-message {
|
|
527
|
+
opacity: 0.7;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
/* ── Test page ── */
|
|
531
|
+
.test-page {
|
|
532
|
+
display: flex;
|
|
533
|
+
justify-content: center;
|
|
534
|
+
align-items: center;
|
|
535
|
+
min-height: 100%;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
.test-page-image {
|
|
539
|
+
max-width: 100%;
|
|
540
|
+
max-height: 80vh;
|
|
541
|
+
border-radius: 8px;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
/* ── Honeycomb background ── */
|
|
545
|
+
.honeycomb-canvas {
|
|
546
|
+
position: fixed;
|
|
547
|
+
inset: 0;
|
|
548
|
+
width: 100%;
|
|
549
|
+
height: 100%;
|
|
550
|
+
pointer-events: none;
|
|
551
|
+
z-index: 0;
|
|
552
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "toiljs",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.9",
|
|
5
5
|
"author": "Dacely",
|
|
6
6
|
"description": "todo",
|
|
7
7
|
"engines": {
|
|
@@ -95,16 +95,18 @@
|
|
|
95
95
|
"@btc-vision/as-loader": "^0.0.0",
|
|
96
96
|
"@btc-vision/hyper-express": "^6.17.4",
|
|
97
97
|
"@clack/prompts": "^1.5.0",
|
|
98
|
-
"@eslint-react/eslint-plugin": "^
|
|
98
|
+
"@eslint-react/eslint-plugin": "^5.8.8",
|
|
99
99
|
"@eslint/js": "^10.0.1",
|
|
100
100
|
"@typescript-eslint/utils": "^8.60.0",
|
|
101
101
|
"@vitejs/plugin-react": "^6.0.2",
|
|
102
|
-
"eslint-plugin-react-hooks": "^7.1.
|
|
102
|
+
"eslint-plugin-react-hooks": "^7.1.1",
|
|
103
103
|
"eslint-plugin-react-refresh": "^0.5.2",
|
|
104
104
|
"picocolors": "^1.1.1",
|
|
105
|
+
"sharp": "^0.34.5",
|
|
105
106
|
"toilscript": "^0.1.4",
|
|
106
107
|
"typescript-eslint": "^8.60.0",
|
|
107
108
|
"vite": "^8.0.14",
|
|
109
|
+
"vite-imagetools": "^10.0.0",
|
|
108
110
|
"vite-plugin-node-polyfills": "^0.28.0"
|
|
109
111
|
},
|
|
110
112
|
"peerDependencies": {
|
|
@@ -131,10 +133,10 @@
|
|
|
131
133
|
"@types/react-dom": "^19.2.3",
|
|
132
134
|
"@vitest/coverage-v8": "^4.1.7",
|
|
133
135
|
"@vitest/ui": "^4.1.7",
|
|
134
|
-
"eslint": "^10.
|
|
135
|
-
"jsdom": "^
|
|
136
|
+
"eslint": "^10.4.1",
|
|
137
|
+
"jsdom": "^29.1.1",
|
|
136
138
|
"micromatch": "^4.0.8",
|
|
137
|
-
"prettier": "^3.8.
|
|
139
|
+
"prettier": "^3.8.3",
|
|
138
140
|
"react": "^19.2.6",
|
|
139
141
|
"react-dom": "^19.2.6",
|
|
140
142
|
"typedoc": "^0.28.19",
|
package/presets/eslint.js
CHANGED
|
@@ -30,12 +30,15 @@ export default tseslint.config(
|
|
|
30
30
|
},
|
|
31
31
|
rules: {
|
|
32
32
|
...reactHooks.configs.recommended.rules,
|
|
33
|
-
// Route files conventionally export
|
|
34
|
-
//
|
|
35
|
-
// doesn't flag the pattern.
|
|
33
|
+
// Route files conventionally export `loader` / `revalidate` / `metadata` /
|
|
34
|
+
// `generateMetadata` alongside the default component; the toil compiler consumes them at
|
|
35
|
+
// runtime. Allow them (plus primitive constants) so Fast Refresh doesn't flag the pattern.
|
|
36
36
|
'react-refresh/only-export-components': [
|
|
37
37
|
'warn',
|
|
38
|
-
{
|
|
38
|
+
{
|
|
39
|
+
allowConstantExport: true,
|
|
40
|
+
allowExportNames: ['loader', 'revalidate', 'metadata', 'generateMetadata'],
|
|
41
|
+
},
|
|
39
42
|
],
|
|
40
43
|
'no-undef': 'off',
|
|
41
44
|
'@typescript-eslint/no-unused-vars': 'off',
|
package/presets/tsconfig.json
CHANGED
package/src/backend/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* toiljs backend
|
|
2
|
+
* toiljs backend, the self-host / dev server, built on @btc-vision/hyper-express (uWebSockets.js)
|
|
3
3
|
* for very high throughput. It serves the built client (static assets + SPA fallback) and exposes
|
|
4
4
|
* a WebSocket channel for realtime / live updates.
|
|
5
5
|
*
|