truecanvas 0.2.0 → 0.3.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 +54 -183
- package/dist/catalog-worker.js +1 -1
- package/dist/chunk-PBKX3SBC.js +97 -0
- package/dist/chunk-TSHD2S36.js +28 -0
- package/dist/{chunk-T2TCBSTG.js → chunk-WU7NBTJH.js} +25 -18
- package/dist/{chunk-ZPARY6OD.js → chunk-XWQ5MS24.js} +362 -335
- package/dist/cli.js +105 -59
- package/dist/core/index.d.ts +42 -9
- package/dist/core/index.js +16 -25
- package/dist/editor/assets/hub-UhU8yp5F.js +2 -0
- package/dist/editor/assets/{main-DlADROMw.js → main-UtSaCfnK.js} +2 -2
- package/dist/editor/hub.html +1 -1
- package/dist/editor/index.html +1 -1
- package/dist/next/index.js +3 -2
- package/dist/vite/index.d.ts +33 -0
- package/dist/vite/index.js +81 -0
- package/package.json +15 -6
- package/dist/editor/assets/hub-BnbEAnt6.js +0 -2
package/dist/editor/hub.html
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>Truecanvas</title>
|
|
7
7
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
8
|
-
<script type="module" crossorigin src="/assets/hub-
|
|
8
|
+
<script type="module" crossorigin src="/assets/hub-UhU8yp5F.js"></script>
|
|
9
9
|
<link rel="modulepreload" crossorigin href="/assets/react-DB-4Zxce.js">
|
|
10
10
|
<link rel="modulepreload" crossorigin href="/assets/Menu-BFifVcgz.js">
|
|
11
11
|
<link rel="stylesheet" crossorigin href="/assets/Menu-DXPm0EXo.css">
|
package/dist/editor/index.html
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>Truecanvas</title>
|
|
7
7
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
8
|
-
<script type="module" crossorigin src="/assets/main-
|
|
8
|
+
<script type="module" crossorigin src="/assets/main-UtSaCfnK.js"></script>
|
|
9
9
|
<link rel="modulepreload" crossorigin href="/assets/react-DB-4Zxce.js">
|
|
10
10
|
<link rel="modulepreload" crossorigin href="/assets/Menu-BFifVcgz.js">
|
|
11
11
|
<link rel="stylesheet" crossorigin href="/assets/Menu-DXPm0EXo.css">
|
package/dist/next/index.js
CHANGED
|
@@ -17,8 +17,9 @@ function withTruecanvas(config = {}) {
|
|
|
17
17
|
}
|
|
18
18
|
function withTruecanvasObject(config) {
|
|
19
19
|
if (productionCommand()) return config;
|
|
20
|
-
const
|
|
21
|
-
warnIfLinkedOutside(
|
|
20
|
+
const loaderFile = fileURLToPath(new URL("./loader.cjs", import.meta.url));
|
|
21
|
+
warnIfLinkedOutside(loaderFile);
|
|
22
|
+
const loader = process.platform === "win32" ? "truecanvas/loader" : loaderFile;
|
|
22
23
|
const userWebpack = config.webpack;
|
|
23
24
|
const root = process.cwd();
|
|
24
25
|
const appDir = existsSync(join(root, "src/app")) ? "src/app" : "app";
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { IncomingMessage, ServerResponse } from 'node:http';
|
|
2
|
+
|
|
3
|
+
/** The bits of Vite's API this plugin uses, so Vite stays an optional peer. */
|
|
4
|
+
interface ViteDevServer {
|
|
5
|
+
config: {
|
|
6
|
+
root: string;
|
|
7
|
+
};
|
|
8
|
+
middlewares: {
|
|
9
|
+
use(fn: (req: IncomingMessage, res: ServerResponse, next: () => void) => void): void;
|
|
10
|
+
};
|
|
11
|
+
transformIndexHtml(url: string, html: string): Promise<string>;
|
|
12
|
+
}
|
|
13
|
+
interface TruecanvasVitePlugin {
|
|
14
|
+
name: string;
|
|
15
|
+
apply: "serve";
|
|
16
|
+
enforce: "pre";
|
|
17
|
+
config(): {
|
|
18
|
+
optimizeDeps: {
|
|
19
|
+
include: string[];
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
configResolved(config: {
|
|
23
|
+
root: string;
|
|
24
|
+
}): void;
|
|
25
|
+
transform(code: string, id: string): {
|
|
26
|
+
code: string;
|
|
27
|
+
map: null;
|
|
28
|
+
} | null;
|
|
29
|
+
configureServer(server: ViteDevServer): void;
|
|
30
|
+
}
|
|
31
|
+
declare function truecanvas(): TruecanvasVitePlugin;
|
|
32
|
+
|
|
33
|
+
export { type TruecanvasVitePlugin, truecanvas as default, truecanvas };
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import {
|
|
2
|
+
injectIds
|
|
3
|
+
} from "../chunk-TSHD2S36.js";
|
|
4
|
+
import {
|
|
5
|
+
loadConfig
|
|
6
|
+
} from "../chunk-PBKX3SBC.js";
|
|
7
|
+
|
|
8
|
+
// src/vite/index.ts
|
|
9
|
+
import fs from "fs";
|
|
10
|
+
import path from "path";
|
|
11
|
+
function truecanvas() {
|
|
12
|
+
let root = process.cwd();
|
|
13
|
+
let components = [];
|
|
14
|
+
let routeDir = ".truecanvas";
|
|
15
|
+
return {
|
|
16
|
+
name: "truecanvas",
|
|
17
|
+
apply: "serve",
|
|
18
|
+
enforce: "pre",
|
|
19
|
+
config() {
|
|
20
|
+
return { optimizeDeps: { include: ["truecanvas", "truecanvas/host", "react-dom/client"] } };
|
|
21
|
+
},
|
|
22
|
+
configResolved(resolved) {
|
|
23
|
+
root = resolved.root;
|
|
24
|
+
const config = loadConfig(root);
|
|
25
|
+
routeDir = config.routeDir;
|
|
26
|
+
components = componentRoots(config.components);
|
|
27
|
+
},
|
|
28
|
+
transform(code, id) {
|
|
29
|
+
const file = id.split("?")[0].replace(/\\/g, "/");
|
|
30
|
+
if (!/\.[jt]sx$/.test(file) || file.includes("/node_modules/")) return null;
|
|
31
|
+
if (file.endsWith(".canvas.tsx")) return { code: injectIds(code), map: null };
|
|
32
|
+
const rel = path.relative(root, file).split(path.sep).join("/");
|
|
33
|
+
if (rel.startsWith("..") || rel.startsWith(`${routeDir}/`)) return null;
|
|
34
|
+
if (components.some((dir) => rel.startsWith(`${dir}/`))) return { code: injectIds(code, rel), map: null };
|
|
35
|
+
return null;
|
|
36
|
+
},
|
|
37
|
+
configureServer(server) {
|
|
38
|
+
server.middlewares.use((req, res, next) => {
|
|
39
|
+
const url = req.url ?? "";
|
|
40
|
+
if (!/^\/truecanvas\/[^/?#]+\/?(?:[?#]|$)/.test(url)) return next();
|
|
41
|
+
server.transformIndexHtml(url, frameHtml(server.config.root, routeDir)).then((html) => {
|
|
42
|
+
res.statusCode = 200;
|
|
43
|
+
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
44
|
+
res.setHeader("Cache-Control", "no-store");
|
|
45
|
+
res.end(html);
|
|
46
|
+
}).catch(() => next());
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
var vite_default = truecanvas;
|
|
52
|
+
function frameHtml(root, routeDir) {
|
|
53
|
+
let head = "";
|
|
54
|
+
try {
|
|
55
|
+
const html = fs.readFileSync(path.join(root, "index.html"), "utf8");
|
|
56
|
+
const inner = /<head[^>]*>([\s\S]*?)<\/head>/i.exec(html)?.[1] ?? "";
|
|
57
|
+
head = [...inner.matchAll(/<link\b[^>]*>|<style\b[\s\S]*?<\/style>/gi)].map((m) => m[0]).filter((tag) => !/rel=["']?modulepreload/i.test(tag)).join("\n ");
|
|
58
|
+
} catch {
|
|
59
|
+
}
|
|
60
|
+
return `<!doctype html>
|
|
61
|
+
<html lang="en">
|
|
62
|
+
<head>
|
|
63
|
+
<meta charset="UTF-8" />
|
|
64
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
65
|
+
<title>Truecanvas frame</title>
|
|
66
|
+
${head}
|
|
67
|
+
</head>
|
|
68
|
+
<body>
|
|
69
|
+
<div id="tc-root"></div>
|
|
70
|
+
<script type="module" src="/${routeDir}/entry.tsx"></script>
|
|
71
|
+
</body>
|
|
72
|
+
</html>
|
|
73
|
+
`;
|
|
74
|
+
}
|
|
75
|
+
function componentRoots(globs) {
|
|
76
|
+
return [...new Set(globs.map((g) => g.split("/").filter((p) => !p.includes("*")).join("/")).filter(Boolean))];
|
|
77
|
+
}
|
|
78
|
+
export {
|
|
79
|
+
vite_default as default,
|
|
80
|
+
truecanvas
|
|
81
|
+
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "truecanvas",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Design with your real React components: a Figma-like canvas for Next.js where layers are your .tsx and agents edit through MCP.",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "Design with your real React components: a Figma-like canvas for Next.js and Vite where layers are your .tsx and agents edit through MCP.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
@@ -16,6 +16,10 @@
|
|
|
16
16
|
"types": "./dist/next/index.d.ts",
|
|
17
17
|
"default": "./dist/next/index.js"
|
|
18
18
|
},
|
|
19
|
+
"./vite": {
|
|
20
|
+
"types": "./dist/vite/index.d.ts",
|
|
21
|
+
"default": "./dist/vite/index.js"
|
|
22
|
+
},
|
|
19
23
|
"./core": {
|
|
20
24
|
"types": "./dist/core/index.d.ts",
|
|
21
25
|
"default": "./dist/core/index.js"
|
|
@@ -33,22 +37,26 @@
|
|
|
33
37
|
"LICENSE"
|
|
34
38
|
],
|
|
35
39
|
"scripts": {
|
|
36
|
-
"build": "
|
|
40
|
+
"build": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\" && tsup && vite build --config editor/vite.config.ts",
|
|
37
41
|
"build:lib": "tsup",
|
|
38
42
|
"build:editor": "vite build --config editor/vite.config.ts",
|
|
39
43
|
"dev:editor": "vite --config editor/vite.config.ts",
|
|
40
44
|
"typecheck": "tsc --noEmit -p tsconfig.json && tsc --noEmit -p editor/tsconfig.json",
|
|
41
|
-
"test": "node --test
|
|
45
|
+
"test": "node --test",
|
|
42
46
|
"prepublishOnly": "pnpm build && pnpm test",
|
|
43
47
|
"prepack": "node ../../scripts/sync-readme.mjs"
|
|
44
48
|
},
|
|
45
49
|
"peerDependencies": {
|
|
46
50
|
"next": ">=15.0",
|
|
47
|
-
"react": ">=19"
|
|
51
|
+
"react": ">=19",
|
|
52
|
+
"vite": ">=5"
|
|
48
53
|
},
|
|
49
54
|
"peerDependenciesMeta": {
|
|
50
55
|
"next": {
|
|
51
56
|
"optional": true
|
|
57
|
+
},
|
|
58
|
+
"vite": {
|
|
59
|
+
"optional": true
|
|
52
60
|
}
|
|
53
61
|
},
|
|
54
62
|
"dependencies": {
|
|
@@ -82,6 +90,7 @@
|
|
|
82
90
|
"figma",
|
|
83
91
|
"react",
|
|
84
92
|
"nextjs",
|
|
93
|
+
"vite",
|
|
85
94
|
"tailwind",
|
|
86
95
|
"mcp",
|
|
87
96
|
"agents",
|
|
@@ -91,7 +100,7 @@
|
|
|
91
100
|
"components"
|
|
92
101
|
],
|
|
93
102
|
"engines": {
|
|
94
|
-
"node": ">=
|
|
103
|
+
"node": ">=22"
|
|
95
104
|
},
|
|
96
105
|
"homepage": "https://truecanvas.dev",
|
|
97
106
|
"repository": {
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{i as e,t}from"./react-DB-4Zxce.js";import{A as n,C as r,D as i,S as a,T as o,_ as s,b as c,g as l,h as u,i as d,k as f,n as p,t as m,v as h,w as g,x as _,y as v}from"./Menu-BFifVcgz.js";var y={name:`cloud-download`,size:24,node:[[`path`,{d:`M12 13v8l-4-4`,key:`1f5nwf`}],[`path`,{d:`m12 21 4-4`,key:`1lfcce`}],[`path`,{d:`M4.393 15.269A7 7 0 1 1 15.71 8h1.79a4.5 4.5 0 0 1 2.436 8.284`,key:`ui1hmy`}]],aliases:[`download-cloud`]};y.node;var b=f(y),x={name:`cpu`,size:24,node:[[`path`,{d:`M12 20v2`,key:`1lh1kg`}],[`path`,{d:`M12 2v2`,key:`tus03m`}],[`path`,{d:`M17 20v2`,key:`1rnc9c`}],[`path`,{d:`M17 2v2`,key:`11trls`}],[`path`,{d:`M2 12h2`,key:`1t8f8n`}],[`path`,{d:`M2 17h2`,key:`7oei6x`}],[`path`,{d:`M2 7h2`,key:`asdhe0`}],[`path`,{d:`M20 12h2`,key:`1q8mjw`}],[`path`,{d:`M20 17h2`,key:`1fpfkl`}],[`path`,{d:`M20 7h2`,key:`1o8tra`}],[`path`,{d:`M7 20v2`,key:`4gnj0m`}],[`path`,{d:`M7 2v2`,key:`1i4yhu`}],[`rect`,{x:`4`,y:`4`,width:`16`,height:`16`,rx:`2`,key:`1vbyd7`}],[`rect`,{x:`8`,y:`8`,width:`8`,height:`8`,rx:`1`,key:`z9xiuo`}]]};x.node;var S=f(x),C={name:`folder-open`,size:24,node:[[`path`,{d:`m6 14 1.5-2.9A2 2 0 0 1 9.24 10H20a2 2 0 0 1 1.94 2.5l-1.54 6a2 2 0 0 1-1.95 1.5H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H18a2 2 0 0 1 2 2v2`,key:`usdka0`}]]};C.node;var w=f(C),T={name:`folder-plus`,size:24,node:[[`path`,{d:`M12 10v6`,key:`1bos4e`}],[`path`,{d:`M9 13h6`,key:`1uhe8q`}],[`path`,{d:`M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z`,key:`1kt360`}]]};T.node;var E=f(T),D={name:`folder`,size:24,node:[[`path`,{d:`M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z`,key:`1kt360`}]]};D.node;var O=f(D),k={name:`power`,size:24,node:[[`path`,{d:`M12 2v10`,key:`mnfbl`}],[`path`,{d:`M18.4 6.6a9 9 0 1 1-12.77.04`,key:`obofu9`}]]};k.node;var A=f(k),j={name:`triangle-alert`,size:24,node:[[`path`,{d:`m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3`,key:`wmoenq`}],[`path`,{d:`M12 9v4`,key:`juzpu7`}],[`path`,{d:`M12 17h.01`,key:`p32p05`}]],aliases:[`alert-triangle`]};j.node;var M=f(j),N={name:`wand-sparkles`,size:24,node:[[`path`,{d:`m21.64 3.64-1.28-1.28a1.21 1.21 0 0 0-1.72 0L2.36 18.64a1.21 1.21 0 0 0 0 1.72l1.28 1.28a1.2 1.2 0 0 0 1.72 0L21.64 5.36a1.2 1.2 0 0 0 0-1.72`,key:`ul74o6`}],[`path`,{d:`m14 7 3 3`,key:`1r5n42`}],[`path`,{d:`M5 6v4`,key:`ilb8ba`}],[`path`,{d:`M19 14v4`,key:`blhpug`}],[`path`,{d:`M10 2v2`,key:`7u0qdc`}],[`path`,{d:`M7 8H3`,key:`zfb6yr`}],[`path`,{d:`M21 16h-4`,key:`1cnmox`}],[`path`,{d:`M11 3H9`,key:`1obp7u`}]],aliases:[`wand-2`]};N.node;var P=f(N),F=n(),I=e(t(),1),L=l();async function R(e,t){let n=await fetch(e,t===void 0?void 0:{method:`POST`,headers:{"content-type":`application/json`},body:JSON.stringify(t)}),r=await n.json();if(!n.ok)throw Error(r.error??`Request failed (${n.status})`);return r}var z=e=>e.replace(/^\/home\/[^/]+/,`~`);function B(){let e=(()=>{try{return JSON.parse(localStorage.getItem(`tc:uiTheme`)??`"system"`)}catch{return`system`}})(),t=e===`dark`||e===`system`&&window.matchMedia(`(prefers-color-scheme: dark)`).matches;document.documentElement.dataset.ui=t?`dark`:`light`}function V(){let[e,t]=(0,I.useState)(null),[n,i]=(0,I.useState)(()=>localStorage.getItem(`tc:hubTab`)??`dashboard`),[o,c]=(0,I.useState)(null),[l,d]=(0,I.useState)(!0),f=(0,I.useRef)(0),p=(0,I.useCallback)(()=>{let e=++f.current;R(`/api/hub/state`).then(n=>e===f.current&&t(n)).catch(()=>{})},[]);(0,I.useEffect)(()=>{B();let e=window.matchMedia(`(prefers-color-scheme: dark)`);e.addEventListener(`change`,B),p();let t=new EventSource(`/api/hub/events`);t.onmessage=()=>p(),t.onopen=()=>{d(!0),p()},t.onerror=()=>d(!1);let n=setInterval(p,5e3);return()=>{e.removeEventListener(`change`,B),t.close(),clearInterval(n)}},[p]);let m=(0,I.useMemo)(()=>(e?.projects??[]).filter(e=>e.running),[e]),h=(0,I.useRef)(null);(0,I.useEffect)(()=>{e&&(m.some(e=>e.path===h.current)&&(h.current=null),n!==`dashboard`&&n!==h.current&&!m.some(e=>e.path===n)&&i(`dashboard`))},[e,m,n]),(0,I.useEffect)(()=>{localStorage.setItem(`tc:hubTab`,n),R(`/api/hub/active`,{path:n===`dashboard`?``:n}).catch(()=>{})},[n]);let g=(0,I.useRef)(Date.now());(0,I.useEffect)(()=>{let t=e?.focus;!t||t.at<=g.current||!m.some(e=>e.path===t.path)||(g.current=t.at,i(t.path))},[e,m]);let _=m.find(e=>e.path===n)?.name;(0,I.useEffect)(()=>{document.title=_?`${_} · Truecanvas`:`Truecanvas`},[_]);let v=async e=>{h.current=e.path,i(e.path);try{await R(`/api/hub/open`,{path:e.path})}catch(e){h.current=null,c(e.message)}p()},y=async e=>{let t=m.findIndex(t=>t.path===e.path);n===e.path&&i(m[t+1]?.path??m[t-1]?.path??`dashboard`);try{await R(`/api/hub/close`,{path:e.path})}catch(e){c(e.message)}p()};(0,I.useEffect)(()=>{let e=e=>{if(!e.ctrlKey)return;let t=[`dashboard`,...m.map(e=>e.path)];if(e.key===`Tab`){e.preventDefault();let r=t.indexOf(n);i(t[(r+(e.shiftKey?-1:1)+t.length)%t.length])}};return window.addEventListener(`keydown`,e),()=>window.removeEventListener(`keydown`,e)},[m,n]);let b=m.reduce((e,t)=>e+(t.running?.memory??0),0),[x,C]=(0,I.useState)(!1),[w,T]=(0,I.useState)(!1),E=async()=>{C(!1),await fetch(`/api/hub/quit`,{method:`POST`}).catch(()=>{}),T(!0),setTimeout(()=>window.close(),300)};return(0,I.useEffect)(()=>{let e=e=>{e.ctrlKey&&(e.key===`q`||e.key===`Q`)&&(e.preventDefault(),m.length?C(!0):E())};return window.addEventListener(`keydown`,e),()=>window.removeEventListener(`keydown`,e)}),w?(0,L.jsxs)(`div`,{className:`hub-center`,style:{height:`100%`},children:[(0,L.jsx)(`img`,{src:`/favicon.svg`,width:44,height:44,alt:``}),(0,L.jsx)(`div`,{style:{fontWeight:600,marginTop:6},children:`Truecanvas has quit`}),(0,L.jsx)(`div`,{className:`muted`,children:"You can close this window. Open it again from your app launcher or with `truecanvas`."})]}):(0,L.jsxs)(`div`,{className:`hub`,children:[(0,L.jsxs)(`header`,{className:`tabbar`,role:`tablist`,children:[(0,L.jsxs)(`button`,{role:`tab`,"aria-selected":n===`dashboard`,className:`hub-tab home-tab${n===`dashboard`?` on`:``}`,onClick:()=>i(`dashboard`),children:[(0,L.jsx)(r,{size:14}),(0,L.jsx)(`span`,{children:`Dashboard`})]}),(0,L.jsx)(`span`,{className:`tab-sep`}),(0,L.jsx)(`div`,{className:`tab-strip`,children:m.map(e=>(0,L.jsxs)(`div`,{role:`tab`,tabIndex:0,"aria-selected":n===e.path,className:`hub-tab${n===e.path?` on`:``}`,onClick:()=>i(e.path),onAuxClick:t=>t.button===1&&void y(e),title:z(e.path),children:[e.running?.status===`starting`?(0,L.jsx)(a,{size:14,className:`spin-working`}):e.running?.status===`failed`?(0,L.jsx)(M,{size:14,className:`warn`}):(0,L.jsx)(H,{path:e.path}),(0,L.jsx)(`span`,{className:`label`,children:e.name}),(0,L.jsx)(`button`,{className:`tab-close`,"aria-label":`Close ${e.name}`,onClick:t=>{t.stopPropagation(),y(e)},children:(0,L.jsx)(s,{size:12})})]},e.path))}),(0,L.jsxs)(`div`,{className:`tabbar-right`,children:[b>0&&(0,L.jsx)(u,{label:`Memory used by open projects (app + Truecanvas)`,side:`bottom`,children:(0,L.jsxs)(`span`,{className:`mem-chip`,children:[(0,L.jsx)(S,{size:12}),` `,b>=1024?`${(b/1024).toFixed(1)} GB`:`${b} MB`]})}),(0,L.jsx)(U,{onQuit:()=>C(!0)})]})]}),x&&(0,L.jsx)(W,{running:m.map(e=>e.name),onCancel:()=>C(!1),onQuit:()=>void E()}),(0,L.jsxs)(`main`,{className:`hub-body`,children:[n===`dashboard`&&e&&(0,L.jsx)(K,{state:e,onOpen:v,refresh:p}),m.map(e=>(0,L.jsx)(G,{project:e,visible:n===e.path,onRetry:()=>void v(e)},e.path)),!l&&(0,L.jsxs)(`div`,{className:`banner`,role:`status`,style:{top:12},children:[(0,L.jsx)(a,{size:14,className:`spin-working`}),(0,L.jsx)(`div`,{className:`muted`,children:`Lost connection to Truecanvas. Reconnecting…`})]}),o&&(0,L.jsxs)(`div`,{className:`banner`,role:`alert`,style:{top:12},children:[(0,L.jsx)(`span`,{className:`dot`}),(0,L.jsx)(`div`,{children:o}),(0,L.jsx)(`button`,{className:`icon-btn`,"aria-label":`Dismiss`,onClick:()=>c(null),children:(0,L.jsx)(s,{size:14})})]})]})]})}function H({path:e,size:t=14}){let[n,r]=(0,I.useState)(!1);return n?(0,L.jsx)(O,{size:t,className:`folder-icon`}):(0,L.jsx)(`img`,{className:`project-favicon`,src:`/api/hub/icon?path=${encodeURIComponent(e)}`,width:t,height:t,alt:``,onError:()=>r(!0)})}function U({onQuit:e}){let[t,n]=(0,I.useState)(null);return(0,L.jsxs)(L.Fragment,{children:[(0,L.jsx)(u,{label:`Truecanvas`,side:`bottom`,children:(0,L.jsx)(`button`,{className:`icon-btn app-menu-btn`,"aria-label":`Truecanvas menu`,onClick:e=>{let t=e.currentTarget.getBoundingClientRect();n({x:t.right-220,y:t.bottom+6})},children:(0,L.jsx)(o,{size:16})})}),t&&(0,L.jsx)(m,{x:t.x,y:t.y,onClose:()=>n(null),items:[{label:`Reload window`,icon:(0,L.jsx)(v,{size:14}),kbd:`Ctrl+R`,onSelect:()=>location.reload()},`sep`,{label:`Quit Truecanvas`,icon:(0,L.jsx)(A,{size:14}),kbd:`Ctrl+Q`,danger:!0,onSelect:e}]})]})}function W({running:e,onCancel:t,onQuit:n}){return(0,L.jsxs)(d,{title:`Quit Truecanvas?`,role:`alertdialog`,width:380,onClose:t,children:[(0,L.jsx)(`p`,{className:`muted`,children:e.length?`This also stops ${e.length===1?`the app`:`the apps`} it started: ${e.join(`, `)}. Your files are saved as you edit.`:`Your files are saved as you edit.`}),(0,L.jsxs)(`div`,{className:`modal-actions`,children:[(0,L.jsx)(`button`,{className:`btn outline`,onClick:t,children:`Cancel`}),(0,L.jsx)(`button`,{className:`btn danger-solid`,autoFocus:!0,onClick:n,children:`Quit`})]})]})}function G({project:e,visible:t,onRetry:n}){let r=e.running;return(0,L.jsxs)(`div`,{className:`project-view`,style:{display:t?`block`:`none`},children:[r.status===`ready`&&(0,L.jsx)(`iframe`,{title:e.name,src:`${r.editor}/`,allow:`clipboard-read; clipboard-write`}),r.status===`starting`&&(0,L.jsxs)(`div`,{className:`hub-center`,children:[(0,L.jsx)(a,{size:22,className:`spin-working`}),(0,L.jsxs)(`div`,{className:`muted`,children:[`Starting `,e.name,`…`]}),(0,L.jsx)(`div`,{className:`faint`,children:`Your app and its Truecanvas editor are booting.`})]}),r.status===`failed`&&(0,L.jsxs)(`div`,{className:`hub-center`,children:[(0,L.jsx)(M,{size:22,className:`warn`}),(0,L.jsxs)(`div`,{style:{fontWeight:600},children:[e.name,` didn’t start`]}),(0,L.jsx)(`pre`,{className:`job-log`,style:{maxWidth:640},children:r.error}),(0,L.jsxs)(`button`,{className:`btn primary`,onClick:n,children:[(0,L.jsx)(v,{size:13}),` Try again`]})]})]})}function K({state:e,onOpen:t,refresh:n}){let[r,a]=(0,I.useState)(null),[l,u]=(0,I.useState)(null),[d,f]=(0,I.useState)(null),[_,v]=(0,I.useState)(null),[y,x]=(0,I.useState)(``),[S,C]=(0,I.useState)(null);(0,I.useEffect)(()=>{R(`/api/hub/discover`).then(e=>a(e.found)).catch(()=>a([]))},[e.projects.length]);let T=async(e,t)=>{let n=await R(e,t);v(n)},D=e.projects.filter(e=>!y||`${e.name} ${e.path}`.toLowerCase().includes(y.toLowerCase()));return(0,L.jsxs)(`div`,{className:`dashboard scroll`,children:[(0,L.jsxs)(`div`,{className:`dash-inner`,children:[(0,L.jsxs)(`div`,{className:`dash-head`,children:[(0,L.jsxs)(`div`,{children:[(0,L.jsx)(`h1`,{children:`Projects`}),(0,L.jsx)(`p`,{className:`muted`,children:`Each project is a Next.js app. Its canvases are .tsx files in the repo.`})]}),(0,L.jsxs)(`div`,{className:`dash-actions`,children:[(0,L.jsxs)(`div`,{className:`field`,style:{width:200},children:[(0,L.jsx)(`span`,{className:`prefix`,children:(0,L.jsx)(h,{size:13})}),(0,L.jsx)(`input`,{value:y,placeholder:`Search projects`,onChange:e=>x(e.target.value)})]}),(0,L.jsxs)(`button`,{className:`btn outline`,onClick:()=>u(`open`),children:[(0,L.jsx)(w,{size:14}),` Open folder`]}),(0,L.jsxs)(`button`,{className:`btn outline`,onClick:()=>u(`clone`),children:[(0,L.jsx)(b,{size:14}),` Clone from GitHub`]}),(0,L.jsxs)(`button`,{className:`btn primary`,onClick:()=>u(`new`),children:[(0,L.jsx)(c,{size:14}),` New project`]})]})]}),D.length===0&&!y&&(0,L.jsxs)(`div`,{className:`dash-empty`,children:[(0,L.jsx)(E,{size:28}),(0,L.jsx)(`div`,{style:{fontWeight:600,marginTop:10},children:`No projects yet`}),(0,L.jsx)(`div`,{className:`muted`,children:`Open a Next.js app from your computer, clone one from GitHub, or create a new one.`})]}),(0,L.jsx)(`div`,{className:`project-grid`,children:D.map(e=>(0,L.jsxs)(`div`,{className:`project-card${e.running?` live`:``}${e.exists?``:` missing`}`,role:`button`,tabIndex:0,onClick:()=>e.ready&&e.exists&&t(e),onKeyDown:n=>n.key===`Enter`&&e.ready&&t(e),onContextMenu:t=>{t.preventDefault(),C({x:t.clientX,y:t.clientY,p:e})},children:[(0,L.jsxs)(`div`,{className:`card-top`,children:[(0,L.jsx)(`span`,{className:`card-icon`,children:(0,L.jsx)(H,{path:e.path,size:22})}),(0,L.jsx)(`button`,{className:`icon-btn sm`,"aria-label":`Options for ${e.name}`,onClick:t=>{t.stopPropagation();let n=t.currentTarget.getBoundingClientRect();C({x:n.left,y:n.bottom+4,p:e})},children:(0,L.jsx)(o,{size:14})})]}),(0,L.jsx)(`div`,{className:`card-name`,children:e.name}),(0,L.jsx)(`div`,{className:`card-path faint`,children:z(e.path)}),(0,L.jsx)(`div`,{className:`card-meta`,children:e.exists?e.ready?(0,L.jsxs)(L.Fragment,{children:[(0,L.jsxs)(`span`,{className:`faint`,children:[e.canvases,` `,e.canvases===1?`page`:`pages`,` · `,p(e.lastOpened,`long`)]}),e.running&&(0,L.jsxs)(`span`,{className:`live-tag`,children:[(0,L.jsx)(`span`,{className:`live-dot`}),` `,e.running.memory?`${e.running.memory} MB`:`open`]})]}):(0,L.jsxs)(`button`,{className:`btn primary sm-btn`,onClick:t=>{t.stopPropagation(),T(`/api/hub/setup`,{path:e.path})},children:[(0,L.jsx)(P,{size:13}),` Set up Truecanvas`]}):(0,L.jsx)(`span`,{className:`chip`,children:`Folder missing`})})]},e.path))}),r&&r.length>0&&(0,L.jsxs)(L.Fragment,{children:[(0,L.jsx)(`h2`,{children:`Found on this computer`}),(0,L.jsx)(`div`,{className:`found-list`,children:r.map(e=>(0,L.jsxs)(`div`,{className:`found-row`,children:[(0,L.jsx)(H,{path:e.path,size:16}),(0,L.jsx)(`span`,{className:`name`,children:e.name}),(0,L.jsx)(`span`,{className:`faint path`,children:z(e.path)}),(0,L.jsx)(`button`,{className:`btn outline`,onClick:async()=>{await R(`/api/hub/add`,{path:e.path}),n()},children:`Add`})]},e.path))})]})]}),S&&(0,L.jsx)(m,{x:S.x,y:S.y,onClose:()=>C(null),items:[...S.p.ready?[{label:`Open`,icon:(0,L.jsx)(i,{size:14}),onSelect:()=>t(S.p)}]:[],...S.p.ready?[]:[{label:`Set up Truecanvas`,icon:(0,L.jsx)(P,{size:14}),onSelect:()=>void T(`/api/hub/setup`,{path:S.p.path})}],...S.p.ready?[{label:`Pull requests…`,icon:(0,L.jsx)(g,{size:14}),onSelect:()=>f(S.p)}]:[],...S.p.running?[{label:`Stop app`,icon:(0,L.jsx)(A,{size:14}),onSelect:()=>void R(`/api/hub/close`,{path:S.p.path}).then(n)}]:[],`sep`,{label:`Remove from list`,icon:(0,L.jsx)(s,{size:14}),danger:!0,onSelect:()=>void R(`/api/hub/forget`,{path:S.p.path}).then(n)}]}),l===`open`&&(0,L.jsx)(J,{onClose:()=>u(null),onPick:async e=>{let r=await R(`/api/hub/add`,{path:e});u(null),n(),r.ready&&t({path:r.path})}}),l===`clone`&&(0,L.jsx)(Y,{onClose:()=>u(null),onClone:async(e,t)=>{u(null),await T(`/api/hub/clone`,{repo:e,parent:t})}}),d&&(0,L.jsx)(X,{project:d,onClose:()=>f(null),onOpened:()=>{let e=d;f(null),t(e)}}),l===`new`&&(0,L.jsx)(Z,{onClose:()=>u(null),onCreate:async(e,t)=>{u(null),await T(`/api/hub/create`,{name:e,parent:t})}}),_&&(0,L.jsx)(Q,{job:_,onClose:e=>{v(null),n(),e&&t({path:e})}})]})}function q({title:e,children:t,onClose:n,width:r=460}){return(0,L.jsx)(d,{title:e,width:r,onClose:n,children:t})}function J({onClose:e,onPick:t}){let[n,r]=(0,I.useState)(`~/Github`),[i,a]=(0,I.useState)(null),[o,s]=(0,I.useState)(null),c=e=>void t(e).catch(e=>s(e.message)),[l,u]=(0,I.useState)(null);return(0,I.useEffect)(()=>{let e=!1,t=setTimeout(()=>{R(`/api/hub/ls?path=${encodeURIComponent(n)}`).then(t=>{e||(a(t),u(n),s(null))}).catch(t=>!e&&s(t.message))},150);return()=>{e=!0,clearTimeout(t)}},[n]),(0,L.jsxs)(q,{title:`Open a project folder`,onClose:e,width:520,children:[(0,L.jsx)(`p`,{className:`muted`,children:`Pick the root of a Next.js app (the folder with its package.json).`}),(0,L.jsxs)(`div`,{className:`field`,children:[(0,L.jsx)(`span`,{className:`prefix`,children:(0,L.jsx)(O,{size:13})}),(0,L.jsx)(`input`,{autoFocus:!0,value:n,onChange:e=>r(e.target.value),onKeyDown:e=>e.key===`Enter`&&c(i&&l===n?i.path:n),spellCheck:!1})]}),(0,L.jsxs)(`div`,{className:`dir-list`,children:[i&&(0,L.jsxs)(`button`,{className:`dir-row`,onClick:()=>r(i.path.split(`/`).slice(0,-1).join(`/`)||`/`),children:[(0,L.jsx)(O,{size:14,className:`faint`}),` ..`]}),i?.dirs.map(e=>(0,L.jsxs)(`button`,{className:`dir-row`,onClick:()=>r(`${i.path}/${e.name}`),onDoubleClick:()=>e.next&&c(`${i.path}/${e.name}`),children:[(0,L.jsx)(O,{size:14,className:e.next?``:`faint`}),(0,L.jsx)(`span`,{children:e.name}),e.next&&(0,L.jsx)(`span`,{className:`chip accent`,children:`Next.js`})]},e.name)),o&&(0,L.jsx)(`div`,{className:`faint`,style:{padding:8},children:o})]}),(0,L.jsxs)(`div`,{className:`modal-actions`,children:[(0,L.jsx)(`button`,{className:`btn outline`,onClick:e,children:`Cancel`}),(0,L.jsxs)(`button`,{className:`btn primary`,onClick:()=>c(i?.path??n),children:[`Open `,i?z(i.path).split(`/`).pop():``]})]})]})}function Y({onClose:e,onClone:t}){let[n,r]=(0,I.useState)(null),[i,a]=(0,I.useState)(``),[o,s]=(0,I.useState)(`~/Github`);(0,I.useEffect)(()=>{R(`/api/hub/github/repos`).then(r).catch(e=>r({gh:!1,error:e.message,repos:[]}))},[]);let c=/^(https?:|git@|ssh:|file:)/.test(i.trim())||/^[\w.-]+\/[\w.-]+$/.test(i.trim()),l=(n?.repos??[]).filter(e=>!i||`${e.nameWithOwner} ${e.description??``}`.toLowerCase().includes(i.toLowerCase()));return(0,L.jsxs)(q,{title:`Clone from GitHub`,onClose:e,width:560,children:[(0,L.jsx)(`p`,{className:`muted`,children:`Clones the repo, installs its dependencies and sets up Truecanvas if it isn't there yet. Then it's one click to open.`}),(0,L.jsxs)(`div`,{className:`field`,children:[(0,L.jsx)(`span`,{className:`prefix`,children:(0,L.jsx)(h,{size:13})}),(0,L.jsx)(`input`,{autoFocus:!0,value:i,placeholder:`Search your repositories, or paste owner/name or a git URL`,onChange:e=>a(e.target.value),onKeyDown:e=>e.key===`Enter`&&c&&t(i.trim(),o),spellCheck:!1})]}),(0,L.jsxs)(`div`,{className:`dir-list`,style:{height:260},children:[n===null&&(0,L.jsx)(`div`,{className:`faint`,style:{padding:10},children:`Loading your repositories…`}),n&&!n.gh&&(0,L.jsxs)(`div`,{className:`faint`,style:{padding:10,lineHeight:1.5},children:[`The GitHub CLI isn't signed in, so your repositories can't be listed. Paste a git URL above, or run `,(0,L.jsx)(`span`,{className:`mono`,children:`gh auth login`}),` in a terminal.`]}),c&&!l.some(e=>e.nameWithOwner===i.trim())&&(0,L.jsxs)(`button`,{className:`dir-row`,onClick:()=>t(i.trim(),o),children:[(0,L.jsx)(b,{size:14}),(0,L.jsxs)(`span`,{children:[`Clone `,(0,L.jsx)(`strong`,{children:i.trim()})]})]}),l.map(e=>(0,L.jsxs)(`button`,{className:`dir-row repo-row`,onClick:()=>t(e.nameWithOwner,o),title:e.description??``,children:[e.isPrivate?(0,L.jsx)(_,{size:13,className:`faint`}):(0,L.jsx)(b,{size:13,className:`faint`}),(0,L.jsx)(`span`,{style:{fontWeight:500},children:e.nameWithOwner}),(0,L.jsx)(`span`,{className:`faint`,style:{marginLeft:`auto`,whiteSpace:`nowrap`},children:p(Date.parse(e.updatedAt),`long`)})]},e.nameWithOwner))]}),(0,L.jsxs)(`div`,{className:`field`,style:{marginTop:10},children:[(0,L.jsx)(`span`,{className:`prefix`,children:(0,L.jsx)(O,{size:13})}),(0,L.jsx)(`input`,{value:o,onChange:e=>s(e.target.value),"aria-label":`Clone into`,spellCheck:!1})]}),(0,L.jsx)(`div`,{className:`modal-actions`,style:{marginTop:12},children:(0,L.jsx)(`button`,{className:`btn outline`,onClick:e,children:`Cancel`})})]})}function X({project:e,onClose:t,onOpened:n}){let[r,i]=(0,I.useState)(null),[o,s]=(0,I.useState)(null),[c,l]=(0,I.useState)(null);(0,I.useEffect)(()=>{R(`/api/hub/prs?path=${encodeURIComponent(e.path)}`).then(i).catch(e=>i({error:e.message,prs:[]}))},[e.path]);let u=async t=>{s(t),l(null);try{await R(`/api/hub/pr-checkout`,{path:e.path,number:String(t)}),n()}catch(e){l(e.message)}finally{s(null)}};return(0,L.jsxs)(q,{title:`Pull requests · ${e.name}`,onClose:t,width:560,children:[(0,L.jsx)(`p`,{className:`muted`,children:`Open a pull request to see its design in Truecanvas: its branch is checked out and the canvases show that version.`}),(0,L.jsxs)(`div`,{className:`dir-list`,style:{height:280},children:[r===null&&(0,L.jsx)(`div`,{className:`faint`,style:{padding:10},children:`Loading…`}),r?.error&&(0,L.jsx)(`div`,{className:`faint`,style:{padding:10},children:r.error}),r&&!r.error&&r.prs.length===0&&(0,L.jsx)(`div`,{className:`faint`,style:{padding:10},children:`No open pull requests.`}),r?.prs.map(e=>(0,L.jsxs)(`button`,{className:`dir-row repo-row`,disabled:o!==null,onClick:()=>void u(e.number),children:[o===e.number?(0,L.jsx)(a,{size:14,className:`spin-working`}):(0,L.jsx)(g,{size:14,className:e.isDraft?`faint`:`pr-open`}),(0,L.jsxs)(`span`,{style:{fontWeight:500,overflow:`hidden`,textOverflow:`ellipsis`,whiteSpace:`nowrap`},children:[`#`,e.number,` `,e.title]}),(0,L.jsxs)(`span`,{className:`faint`,style:{marginLeft:`auto`,whiteSpace:`nowrap`},children:[e.author?.login,` · `,p(Date.parse(e.updatedAt),`long`)]})]},e.number))]}),c&&(0,L.jsx)(`div`,{className:`hub-error`,children:c}),(0,L.jsx)(`div`,{className:`modal-actions`,style:{marginTop:12},children:(0,L.jsx)(`button`,{className:`btn outline`,onClick:t,children:`Close`})})]})}function Z({onClose:e,onCreate:t}){let[n,r]=(0,I.useState)(``),[i,a]=(0,I.useState)(`~/Github`),o=n.trim().toLowerCase().replace(/[^a-z0-9-_]+/g,`-`).replace(/^-+|-+$/g,``);return(0,L.jsxs)(q,{title:`New project`,onClose:e,children:[(0,L.jsx)(`p`,{className:`muted`,children:`Creates a Next.js app with TypeScript and Tailwind, then sets up Truecanvas in it.`}),(0,L.jsxs)(`div`,{style:{display:`grid`,gap:8},children:[(0,L.jsxs)(`div`,{className:`field`,children:[(0,L.jsx)(`span`,{className:`prefix`,children:`Name`}),(0,L.jsx)(`input`,{autoFocus:!0,value:n,placeholder:`my-app`,onChange:e=>r(e.target.value),onKeyDown:e=>e.key===`Enter`&&o&&t(o,i)})]}),(0,L.jsxs)(`div`,{className:`field`,children:[(0,L.jsx)(`span`,{className:`prefix`,children:`In`}),(0,L.jsx)(`input`,{value:i,onChange:e=>a(e.target.value),spellCheck:!1})]}),o&&(0,L.jsx)(`div`,{className:`faint mono`,children:`${i.replace(/\/$/,``)}/${o}`})]}),(0,L.jsxs)(`div`,{className:`modal-actions`,style:{marginTop:16},children:[(0,L.jsx)(`button`,{className:`btn outline`,onClick:e,children:`Cancel`}),(0,L.jsx)(`button`,{className:`btn primary`,disabled:!o,onClick:()=>t(o,i),children:`Create project`})]})]})}function Q({job:e,onClose:t}){let[n,r]=(0,I.useState)(e),i=(0,I.useRef)(null);return(0,I.useEffect)(()=>{if(n.status!==`running`)return;let e=setInterval(()=>void R(`/api/hub/jobs/${n.id}`).then(r),700);return()=>clearInterval(e)},[n.id,n.status]),(0,I.useEffect)(()=>{i.current?.scrollTo({top:i.current.scrollHeight})},[n.log.length]),(0,I.useEffect)(()=>{if(n.status!==`done`||!n.result)return;let e=setTimeout(()=>t(n.result),700);return()=>clearTimeout(e)},[n.status,n.result]),(0,L.jsxs)(q,{title:n.title,onClose:()=>n.status!==`running`&&t(),width:600,children:[(0,L.jsxs)(`div`,{className:`job-status`,children:[n.status===`running`?(0,L.jsx)(a,{size:16,className:`spin-working`}):n.status===`done`?(0,L.jsx)(`span`,{className:`live-dot`}):(0,L.jsx)(M,{size:16,className:`warn`}),(0,L.jsx)(`span`,{children:n.status===`running`?`Working… this can take a minute.`:n.status===`done`?`Done.`:`Something went wrong.`})]}),(0,L.jsx)(`pre`,{className:`job-log`,ref:i,children:n.log.slice(-120).join(`
|
|
2
|
-
`)}),(0,L.jsx)(`div`,{className:`modal-actions`,children:n.status===`done`&&n.result?(0,L.jsx)(`button`,{className:`btn primary`,onClick:()=>t(n.result),children:`Opening…`}):(0,L.jsx)(`button`,{className:`btn outline`,disabled:n.status===`running`,onClick:()=>t(),children:`Close`})})]})}(0,F.createRoot)(document.getElementById(`root`)).render((0,L.jsx)(V,{}));
|