truecanvas 0.5.0 → 0.6.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 +6 -0
- package/dist/chunk-D565QDII.js +814 -0
- package/dist/chunk-LKFOLM3L.js +54 -0
- package/dist/chunk-NAK4DQWO.js +1005 -0
- package/dist/{chunk-DV76O5SZ.js → chunk-OPKROZI2.js} +261 -4
- package/dist/chunk-OZOV4Y6C.js +45 -0
- package/dist/{chunk-A4QIAVTG.js → chunk-QJ5M3G4E.js} +88 -852
- package/dist/{chunk-TSHD2S36.js → chunk-VWKLYIY6.js} +1 -1
- package/dist/cli.js +702 -936
- package/dist/core/index.js +14 -11
- package/dist/editor/assets/{Menu-BJ3OmqzH.css → Menu-CACFFH7S.css} +1 -1
- package/dist/editor/assets/hub-B_TP_af8.js +2 -0
- package/dist/editor/assets/main-Dj60SnCH.js +26 -0
- package/dist/editor/hub.html +3 -3
- package/dist/editor/index.html +3 -3
- package/dist/hub.d.ts +88 -0
- package/dist/hub.js +15 -0
- package/dist/share/index.d.ts +73 -1
- package/dist/share/index.js +13 -1
- package/dist/vite/index.js +3 -2
- package/package.json +5 -1
- package/dist/chunk-PBKX3SBC.js +0 -97
- package/dist/editor/assets/hub-B5hqZCM2.js +0 -2
- package/dist/editor/assets/main-woTyWmya.js +0 -26
- /package/dist/editor/assets/{Menu-C0Ti-6T2.js → Menu-CZr87VA3.js} +0 -0
package/dist/hub.d.ts
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/** A client's comment that just reached one of the projects (through share-link sync). */
|
|
2
|
+
interface HubNotice {
|
|
3
|
+
path: string;
|
|
4
|
+
project: string;
|
|
5
|
+
canvas: string;
|
|
6
|
+
name: string;
|
|
7
|
+
text: string;
|
|
8
|
+
}
|
|
9
|
+
/** A project's live session, as its Truecanvas reports it (share/session.ts). */
|
|
10
|
+
interface HubSession {
|
|
11
|
+
canvas: string;
|
|
12
|
+
status: "connecting" | "live" | "reconnecting" | "stopped";
|
|
13
|
+
url: string;
|
|
14
|
+
error: string | null;
|
|
15
|
+
people: {
|
|
16
|
+
id: string;
|
|
17
|
+
name: string;
|
|
18
|
+
kind: "client" | "studio";
|
|
19
|
+
color: string;
|
|
20
|
+
}[];
|
|
21
|
+
}
|
|
22
|
+
interface HubOptions {
|
|
23
|
+
port: number;
|
|
24
|
+
/** the truecanvas CLI that starts projects without their own copy */
|
|
25
|
+
cli: string;
|
|
26
|
+
/** stop a project's app after this many minutes unused (not the active tab, no agent calls); 0 never. Default 30, or TRUECANVAS_IDLE_MINUTES. */
|
|
27
|
+
idleMinutes?: number;
|
|
28
|
+
/** a problem to show at the top of the window (the desktop app: Node isn't installed) */
|
|
29
|
+
notice?: string | null;
|
|
30
|
+
/** a client commented (the desktop app shows a notification) */
|
|
31
|
+
onNotice?: (n: HubNotice) => void;
|
|
32
|
+
/** Quit from the window: the host decides (the desktop app quits itself). Default: stop every project and exit. */
|
|
33
|
+
onQuit?: () => void;
|
|
34
|
+
/** handle SIGINT and SIGTERM (default true; a host with its own lifecycle passes false) */
|
|
35
|
+
signals?: boolean;
|
|
36
|
+
}
|
|
37
|
+
declare function startHub(opts: HubOptions): Promise<{
|
|
38
|
+
origin: string;
|
|
39
|
+
shutdown: () => Promise<void>;
|
|
40
|
+
state: () => Promise<{
|
|
41
|
+
origin: string;
|
|
42
|
+
active: string | null;
|
|
43
|
+
focus: {
|
|
44
|
+
path: string;
|
|
45
|
+
at: number;
|
|
46
|
+
canvas?: string;
|
|
47
|
+
} | null;
|
|
48
|
+
notice: string | null;
|
|
49
|
+
liveSession: {
|
|
50
|
+
available: boolean;
|
|
51
|
+
sessions: Record<string, HubSession[]>;
|
|
52
|
+
};
|
|
53
|
+
projects: {
|
|
54
|
+
running: {
|
|
55
|
+
status: "ready" | "starting" | "failed";
|
|
56
|
+
editor: string;
|
|
57
|
+
app: string;
|
|
58
|
+
error: string | undefined;
|
|
59
|
+
memory: number;
|
|
60
|
+
} | null;
|
|
61
|
+
next: boolean;
|
|
62
|
+
ready: boolean;
|
|
63
|
+
canvases: number;
|
|
64
|
+
exists: boolean;
|
|
65
|
+
path: string;
|
|
66
|
+
name: string;
|
|
67
|
+
lastOpened: number;
|
|
68
|
+
}[];
|
|
69
|
+
}>;
|
|
70
|
+
/** shows a project's tab in the window (and a canvas, when given) */
|
|
71
|
+
focus(dir: string, canvas?: string): void;
|
|
72
|
+
/** live sessions: clients follow a project's open canvas live on its share link */
|
|
73
|
+
liveSession: {
|
|
74
|
+
available: boolean;
|
|
75
|
+
/** the project's running sessions (the active project by default) */
|
|
76
|
+
sessions: (dir?: string | null) => Promise<HubSession[]>;
|
|
77
|
+
/** stops the project's session if one runs, else starts one on the canvas open in its editor */
|
|
78
|
+
toggle(dir?: string | null, canvas?: string): Promise<HubSession[]>;
|
|
79
|
+
};
|
|
80
|
+
}>;
|
|
81
|
+
|
|
82
|
+
declare const HUB_PORT = 4800;
|
|
83
|
+
/** The port of the running hub: the one it wrote down while its process is alive, else 4800. */
|
|
84
|
+
declare function hubPort(): number;
|
|
85
|
+
|
|
86
|
+
declare function useNodeRuntime(node: string | null, env: NodeJS.ProcessEnv): void;
|
|
87
|
+
|
|
88
|
+
export { HUB_PORT, type HubNotice, type HubOptions, hubPort, startHub, useNodeRuntime };
|
package/dist/hub.js
ADDED
package/dist/share/index.d.ts
CHANGED
|
@@ -36,4 +36,76 @@ interface ShareManifest {
|
|
|
36
36
|
external: string[];
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
type Pairs = [string, string][];
|
|
40
|
+
type TunnelHeader = {
|
|
41
|
+
t: "req";
|
|
42
|
+
id: string;
|
|
43
|
+
method: string;
|
|
44
|
+
path: string;
|
|
45
|
+
headers: Pairs;
|
|
46
|
+
body: boolean;
|
|
47
|
+
} | {
|
|
48
|
+
t: "req-body";
|
|
49
|
+
id: string;
|
|
50
|
+
} | {
|
|
51
|
+
t: "req-end";
|
|
52
|
+
id: string;
|
|
53
|
+
} | {
|
|
54
|
+
t: "req-abort";
|
|
55
|
+
id: string;
|
|
56
|
+
} | {
|
|
57
|
+
t: "res";
|
|
58
|
+
id: string;
|
|
59
|
+
status: number;
|
|
60
|
+
headers: Pairs;
|
|
61
|
+
} | {
|
|
62
|
+
t: "res-body";
|
|
63
|
+
id: string;
|
|
64
|
+
} | {
|
|
65
|
+
t: "res-end";
|
|
66
|
+
id: string;
|
|
67
|
+
} | {
|
|
68
|
+
t: "res-error";
|
|
69
|
+
id: string;
|
|
70
|
+
message: string;
|
|
71
|
+
} | {
|
|
72
|
+
t: "ws-open";
|
|
73
|
+
id: string;
|
|
74
|
+
path: string;
|
|
75
|
+
headers: Pairs;
|
|
76
|
+
protocols: string[];
|
|
77
|
+
} | {
|
|
78
|
+
t: "ws-ready";
|
|
79
|
+
id: string;
|
|
80
|
+
protocol: string;
|
|
81
|
+
} | {
|
|
82
|
+
t: "ws-msg";
|
|
83
|
+
id: string;
|
|
84
|
+
text: boolean;
|
|
85
|
+
} | {
|
|
86
|
+
t: "ws-close";
|
|
87
|
+
id: string;
|
|
88
|
+
code: number;
|
|
89
|
+
reason: string;
|
|
90
|
+
};
|
|
91
|
+
/** Body chunks: well under the 1 MiB WebSocket message limit of Workers. */
|
|
92
|
+
declare const CHUNK: number;
|
|
93
|
+
declare function encodeFrame(header: TunnelHeader, payload?: Uint8Array): Uint8Array<ArrayBuffer>;
|
|
94
|
+
declare function decodeFrame(data: ArrayBuffer | Uint8Array): {
|
|
95
|
+
header: TunnelHeader;
|
|
96
|
+
payload: Uint8Array;
|
|
97
|
+
};
|
|
98
|
+
/**
|
|
99
|
+
* A path the tunnel may ask the app for: absolute-path only ("/x?y"), never a
|
|
100
|
+
* full URL, a protocol-relative "//host", a backslash or control characters,
|
|
101
|
+
* so a request can't be pointed at another host or port. Null when refused.
|
|
102
|
+
*/
|
|
103
|
+
declare function safePath(path: string): string | null;
|
|
104
|
+
/**
|
|
105
|
+
* What a viewer's request carries to the app. The review site's cookies
|
|
106
|
+
* (tc_*) and its proxy headers stay behind; the app's own cookies go along.
|
|
107
|
+
*/
|
|
108
|
+
declare function requestHeaders(headers: Iterable<[string, string]>): Pairs;
|
|
109
|
+
declare function responseHeaders(headers: Iterable<[string, string]>): Pairs;
|
|
110
|
+
|
|
111
|
+
export { CHUNK, type Pairs, type ShareFrame, type ShareManifest, type TunnelHeader, decodeFrame, encodeFrame, requestHeaders, responseHeaders, safePath, viewerHtml };
|
package/dist/share/index.js
CHANGED
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
import {
|
|
2
|
+
CHUNK,
|
|
3
|
+
decodeFrame,
|
|
4
|
+
encodeFrame,
|
|
5
|
+
requestHeaders,
|
|
6
|
+
responseHeaders,
|
|
7
|
+
safePath,
|
|
2
8
|
viewerHtml
|
|
3
|
-
} from "../chunk-
|
|
9
|
+
} from "../chunk-OPKROZI2.js";
|
|
4
10
|
export {
|
|
11
|
+
CHUNK,
|
|
12
|
+
decodeFrame,
|
|
13
|
+
encodeFrame,
|
|
14
|
+
requestHeaders,
|
|
15
|
+
responseHeaders,
|
|
16
|
+
safePath,
|
|
5
17
|
viewerHtml
|
|
6
18
|
};
|
package/dist/vite/index.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
injectIds
|
|
3
|
-
} from "../chunk-
|
|
3
|
+
} from "../chunk-VWKLYIY6.js";
|
|
4
4
|
import {
|
|
5
5
|
loadConfig
|
|
6
|
-
} from "../chunk-
|
|
6
|
+
} from "../chunk-OZOV4Y6C.js";
|
|
7
|
+
import "../chunk-LKFOLM3L.js";
|
|
7
8
|
|
|
8
9
|
// src/vite/index.ts
|
|
9
10
|
import fs from "fs";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "truecanvas",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
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",
|
|
@@ -24,6 +24,10 @@
|
|
|
24
24
|
"types": "./dist/share/index.d.ts",
|
|
25
25
|
"default": "./dist/share/index.js"
|
|
26
26
|
},
|
|
27
|
+
"./hub": {
|
|
28
|
+
"types": "./dist/hub.d.ts",
|
|
29
|
+
"default": "./dist/hub.js"
|
|
30
|
+
},
|
|
27
31
|
"./core": {
|
|
28
32
|
"types": "./dist/core/index.d.ts",
|
|
29
33
|
"default": "./dist/core/index.js"
|
package/dist/chunk-PBKX3SBC.js
DELETED
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
// src/core/config.ts
|
|
2
|
-
import fs from "fs";
|
|
3
|
-
import path from "path";
|
|
4
|
-
function loadConfig(root, overrides = {}) {
|
|
5
|
-
let file = {};
|
|
6
|
-
const configPath = path.join(root, "truecanvas.config.json");
|
|
7
|
-
if (fs.existsSync(configPath)) file = JSON.parse(fs.readFileSync(configPath, "utf8"));
|
|
8
|
-
let deps = {};
|
|
9
|
-
try {
|
|
10
|
-
const pkg = JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf8"));
|
|
11
|
-
deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
12
|
-
} catch {
|
|
13
|
-
deps = {};
|
|
14
|
-
}
|
|
15
|
-
const framework = file.framework ?? detectFramework(deps);
|
|
16
|
-
const vite = framework === "vite";
|
|
17
|
-
const appDir = file.appDir ?? (vite ? fs.existsSync(path.join(root, "src")) ? "src" : "." : fs.existsSync(path.join(root, "src/app")) ? "src/app" : "app");
|
|
18
|
-
const srcPrefix = appDir === "src" || appDir.startsWith("src/") ? "src/" : "";
|
|
19
|
-
const knownLibraries = ["@paper-design/shaders-react"].filter((l) => l in deps);
|
|
20
|
-
return {
|
|
21
|
-
root,
|
|
22
|
-
framework,
|
|
23
|
-
appUrl: vite ? "http://localhost:5173" : "http://localhost:3000",
|
|
24
|
-
port: 4800,
|
|
25
|
-
canvasDir: `${srcPrefix}canvas`,
|
|
26
|
-
appDir,
|
|
27
|
-
routeDir: vite ? ".truecanvas" : `${appDir}/truecanvas`,
|
|
28
|
-
components: [`${srcPrefix}components/**/*.tsx`],
|
|
29
|
-
darkMode: { strategy: "class", value: "dark" },
|
|
30
|
-
...file,
|
|
31
|
-
libraries: [.../* @__PURE__ */ new Set([...knownLibraries, ...file.libraries ?? []])],
|
|
32
|
-
...stripUndefined(overrides)
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
function stripUndefined(o) {
|
|
36
|
-
return Object.fromEntries(Object.entries(o).filter(([, v]) => v !== void 0));
|
|
37
|
-
}
|
|
38
|
-
function detectFramework(deps) {
|
|
39
|
-
return !("next" in deps) && "vite" in deps ? "vite" : "next";
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// src/core/ast.ts
|
|
43
|
-
import { parse } from "@babel/parser";
|
|
44
|
-
function parseModule(source) {
|
|
45
|
-
return parse(source, {
|
|
46
|
-
sourceType: "module",
|
|
47
|
-
plugins: ["typescript", "jsx"],
|
|
48
|
-
errorRecovery: false
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
function idOf(node) {
|
|
52
|
-
const loc = node.loc.start;
|
|
53
|
-
return `${loc.line}:${loc.column}`;
|
|
54
|
-
}
|
|
55
|
-
function jsxName(name) {
|
|
56
|
-
if (name.type === "JSXIdentifier") return name.name;
|
|
57
|
-
if (name.type === "JSXNamespacedName") return `${name.namespace.name}:${name.name.name}`;
|
|
58
|
-
return `${jsxName(name.object)}.${name.property.name}`;
|
|
59
|
-
}
|
|
60
|
-
function walk(node, visit, parent = null) {
|
|
61
|
-
if (!node || typeof node !== "object") return;
|
|
62
|
-
if (Array.isArray(node)) {
|
|
63
|
-
for (const child of node) walk(child, visit, parent);
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
const n = node;
|
|
67
|
-
if (typeof n.type !== "string") return;
|
|
68
|
-
if (visit(n, parent) === false) return;
|
|
69
|
-
for (const key of Object.keys(n)) {
|
|
70
|
-
if (key === "loc" || key === "leadingComments" || key === "trailingComments" || key === "innerComments" || key === "extra") continue;
|
|
71
|
-
const value = n[key];
|
|
72
|
-
if (value && typeof value === "object") walk(value, visit, n);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
function isComponentName(name) {
|
|
76
|
-
return /^[A-Z]/.test(name) || name.includes(".");
|
|
77
|
-
}
|
|
78
|
-
function rewriteImportSource(statement, from, to) {
|
|
79
|
-
if (from === to) return statement;
|
|
80
|
-
for (const q of ['"', "'"]) {
|
|
81
|
-
const needle = `${q}${from}${q}`;
|
|
82
|
-
const i = statement.indexOf(needle);
|
|
83
|
-
if (i >= 0) return `${statement.slice(0, i)}${q}${to}${q}${statement.slice(i + needle.length)}`;
|
|
84
|
-
}
|
|
85
|
-
return statement;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
export {
|
|
89
|
-
loadConfig,
|
|
90
|
-
detectFramework,
|
|
91
|
-
parseModule,
|
|
92
|
-
idOf,
|
|
93
|
-
jsxName,
|
|
94
|
-
walk,
|
|
95
|
-
isComponentName,
|
|
96
|
-
rewriteImportSource
|
|
97
|
-
};
|
|
@@ -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-C0Ti-6T2.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 or Vite 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 or Vite 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 or Vite 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}/..`),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:`App`})]},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,{}));
|