UNPKG

webcontainer-sandbox-react

Version:

A React component library for WebContainer-based code sandboxes

1,541 lines (1,520 loc) 48 kB
import { jsx as l, jsxs as k, Fragment as me } from "react/jsx-runtime"; import { createContext as ke, useContext as Ee, useState as v, useEffect as _, useRef as L, useCallback as M, useMemo as He } from "react"; import { WebContainer as Qe } from "@webcontainer/api"; import { CodeOutlined as Xe, WarningOutlined as Ge, ToolOutlined as Je, CloseOutlined as Ye, DesktopOutlined as Ze, MobileOutlined as et, LoadingOutlined as tt, FolderOpenOutlined as nt, FolderOutlined as rt, FileTextOutlined as ot, FileAddOutlined as be, FolderAddOutlined as ve, EditOutlined as st, CopyOutlined as it, ScissorOutlined as lt, DeleteOutlined as at } from "@ant-design/icons"; import { Button as K, Space as Y, Card as ct, Collapse as dt, Alert as ut, Typography as ie, Spin as ft, message as B, Tooltip as xe, Dropdown as pt, Modal as he, Input as Ce } from "antd"; import { Terminal as ht } from "xterm"; import "xterm/css/xterm.css"; import mt from "@monaco-editor/react"; const Fe = ke({ webContainer: null, iframeSrc: "" }), ae = () => Ee(Fe), Nt = ({ children: i }) => { const [t, e] = v(null), [n, o] = v(""); return _(() => { (async () => { if (t) return; const s = await Qe.boot({ workdirName: "project" }); s.on("error", (f) => { console.error("WebContainer error:", f); }), s.on("port", (f, u, p) => { u === "close" ? (o(""), console.log("port closed")) : u === "open" && (console.log(`Server running on ${p}`), o(p)); }), e(s); })(); }, []), /* @__PURE__ */ l(Fe.Provider, { value: { webContainer: t, iframeSrc: n }, children: i }); }, Se = ke({ loading: !1, loadingContent: "", startProcess: null, writer: null, output: null, processOutput: null, sessionErrors: [], clearErrors: null, files: {}, setFile: null, spawn: null }), Z = () => Ee(Se), gt = () => `task_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`; function wt(i = {}) { const { cancelPending: t = !0, cleanupBeforeNew: e = !0, debug: n = !1 } = i, o = L([]), a = L(null), s = L(null), f = L(!1), u = L(!1), p = M( (...w) => { n && console.log("[AsyncQueue]", ...w); }, [n] ), h = M(async () => { if (!f.current) { for (f.current = !0; o.current.length > 0 && !u.current; ) { let w = null; if (t) { for (let y = 0; y < o.current.length - 1; y++) { const b = o.current[y]; b !== a.current && (b.cancelled = !0, p(`取消等待任务: ${b.id}`)); } for (let y = o.current.length - 1; y >= 0; y--) { const b = o.current[y]; if (!b.cancelled) { w = b; break; } } } else w = o.current.find((y) => !y.cancelled) || null; if (o.current = o.current.filter((y) => !y.cancelled), !w) break; if (e && a.current && a.current !== w) { if (p(`检测到新任务,开始清理当前任务: ${a.current.id}`), a.current.cleanup) try { await a.current.cleanup(), p(`清理完成: ${a.current.id}`); } catch (y) { p(`清理失败: ${a.current.id}`, y); } a.current = null, s.current = null; } else s.current && s.current.cleanup && (await s.current.cleanup(), p(`清理完成: ${s.current.id}`), s.current = null); if (!w.cancelled) { a.current = w; try { p(`开始执行任务: ${w.id}`), await w.execute(), w.cancelled ? p(`任务被取消: ${w.id}`) : (p(`任务执行完成: ${w.id}`), w.cleanup && (s.current = w)); } catch (y) { const b = y; p(`任务执行失败: ${w.id}`, b); } o.current = o.current.filter((y) => y !== w); } } a.current = null, f.current = !1, p("队列处理完成"); } }, [t, e, p]), g = M( async (w, y) => new Promise((b, E) => { const N = gt(), P = { id: N, cancelled: !1, timestamp: Date.now(), execute: async () => { try { const S = await w(); return P.cancelled || b(S), S; } catch (S) { throw P.cancelled || E(S), S; } }, cleanup: y }; o.current.push(P), p(`任务入队: ${N}, 队列长度: ${o.current.length}`), h(); }), [h, p] ); _(() => () => { g(() => new Promise((w) => { setTimeout(w, 1e3); })); }, []); const T = M(() => { o.current.forEach((w) => { w.cancelled = !0; }), o.current = [], p("所有任务已取消"); }, [p]); return { enqueue: g, cancelAll: T }; } const yt = { cleanupBeforeNew: !0, debug: !0, cancelPending: !0 }; function bt(i, t) { const { enqueue: e, cancelAll: n } = wt(yt), o = M(async () => { try { return await e(i, t); } catch (s) { throw console.log("Error:", s), s; } }, [e, i, t]), a = M(() => { n(); }, [n]); return { execute: o, reset: a }; } const vt = ` (function() { "use strict"; // 数据清理函数 - 移除不可序列化的对象 function sanitizeData(obj, depth = 0) { if (depth > 10) return "[深度限制]"; if (obj === null || obj === undefined) { return obj; } if (typeof obj === "string" || typeof obj === "number" || typeof obj === "boolean") { return obj; } if (obj instanceof Error) { return { name: obj.name, message: obj.message, stack: obj.stack }; } if (obj instanceof Request || obj instanceof Response || obj instanceof ReadableStream) { return "[不可序列化对象: " + obj.constructor.name + "]"; } if (obj instanceof Event) { return { type: obj.type, target: obj.target ? obj.target.tagName || obj.target.constructor.name : null, timeStamp: obj.timeStamp }; } if (Array.isArray(obj)) { return obj.map(item => sanitizeData(item, depth + 1)); } if (typeof obj === "object") { const result = {}; for (const key in obj) { try { if (obj.hasOwnProperty(key)) { const value = obj[key]; if (typeof value === "function") { result[key] = "[函数]"; } else if (value && typeof value === "object") { // 检查是否是不可序列化的对象 if (value instanceof Request || value instanceof Response || value instanceof ReadableStream || value instanceof AbortController || value instanceof FormData || value instanceof File) { result[key] = "[不可序列化对象: " + value.constructor.name + "]"; } else { result[key] = sanitizeData(value, depth + 1); } } else { result[key] = value; } } } catch (e) { result[key] = "[访问错误: " + e.message + "]"; } } return result; } return String(obj); } function reportError(errorData) { console.log("准备报告错误:", errorData); try { // 清理错误数据 const cleanErrorData = sanitizeData(errorData); if (window.parent && window.parent !== window) { window.parent.postMessage({ type: "webcontainer-error", error: cleanErrorData, source: "webcontainer", timestamp: Date.now() }, "*"); console.log("错误已发送到父页面"); }else if (window.top && window.top !== window) { window.top.postMessage({ type: "webcontainer-error", error: cleanErrorData, source: "webcontainer", timestamp: Date.now() }, "*"); } } catch (e) { console.warn("无法发送错误报告:", e); // 尝试发送简化版本的错误信息 try { const simplifiedError = { type: errorData.type || "unknown-error", message: String(errorData.message || "未知错误"), timestamp: Date.now() }; if (window.parent && window.parent !== window) { window.parent.postMessage({ type: "webcontainer-error", error: simplifiedError, source: "webcontainer" }, "*"); } } catch (fallbackError) { console.error("连简化错误都无法发送:", fallbackError); } } } // 全局错误捕获 window.addEventListener("error", function(event) { reportError({ type: "javascript-error", message: event.message, filename: event.filename, lineno: event.lineno, colno: event.colno, stack: event.error ? event.error.stack : null, timestamp: Date.now() }); }, true); // Promise rejection window.addEventListener("unhandledrejection", function(event) { console.log("捕获到Promise rejection:", event); reportError({ type: "unhandled-rejection", message: event.reason ? String(event.reason) : "Unknown rejection", stack: event.reason && event.reason.stack ? event.reason.stack : null, reason: event.reason, timestamp: Date.now() }); }); // // React错误补充 // const originalConsoleError = console.error; // console.error = function(...args) { // const errorMessage = args.map(arg => { // if (typeof arg === "object") { // try { // return JSON.stringify(arg, null, 2); // } catch { // return String(arg); // } // } // return String(arg); // }).join(" "); // if (errorMessage.includes("React") || // errorMessage.includes("component") || // errorMessage.includes("render")) { // reportError({ // type: "react-error", // message: errorMessage, // timestamp: Date.now() // }); // } // originalConsoleError.apply(console, args); // }; // // 网络错误监听 // const originalFetch = window.fetch; // window.fetch = function(...args) { // return originalFetch.apply(this, args).catch(error => { // reportError({ // type: "network-error", // message: "Fetch failed: " + error.message, // url: args[0], // timestamp: Date.now() // }); // throw error; // }); // }; // // 资源加载错误 // window.addEventListener("error", function(event) { // if (event.target !== window) { // reportError({ // type: "resource-error", // message: "Failed to load " + event.target.tagName + ": " + (event.target.src || event.target.href), // element: event.target.tagName, // source: event.target.src || event.target.href, // timestamp: Date.now() // }); // } // }, true); })(); `, xt = (i) => i.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, ""); class Ct extends EventTarget { constructor(t, e) { super(), this.buffer = [], this.isReading = !1, this.maxBufferSize = 1e3, this.sourceStream = t, this.abortController = new AbortController(), this.startReading(), this.onErrorStream = e; } async startReading() { if (this.isReading) return; this.isReading = !0; const t = this.sourceStream.getReader(); try { for (; !this.abortController.signal.aborted; ) { const { done: e, value: n } = await t.read(); if (e) break; (n.startsWith("Error:") || n.indexOf("[22m \x1B[31m\x1B[1m[vite]") > -1) && this.onErrorStream(xt(n)), this.buffer.push(n), this.buffer.length > this.maxBufferSize && this.buffer.shift(), this.dispatchEvent(new CustomEvent("data", { detail: n })); } } catch (e) { this.abortController.signal.aborted || this.dispatchEvent(new CustomEvent("error", { detail: e })); } finally { t.releaseLock(), this.abortController.signal.aborted || this.dispatchEvent(new CustomEvent("end")), this.isReading = !1; } } getBuffer() { return [...this.buffer]; } createStream() { const t = this.getBuffer(); let e = !0; return new ReadableStream({ start: (n) => { if (this.abortController.signal.aborted) { n.close(); return; } t.forEach((u) => { e && !this.abortController.signal.aborted && n.enqueue(u); }); const o = (u) => { const p = u; if (e && !this.abortController.signal.aborted) try { n.enqueue(p.detail); } catch (h) { console.error("Failed to enqueue data:", h); } }, a = () => { if (e) try { n.close(); } catch (u) { console.error("Failed to close controller:", u); } }, s = (u) => { const p = u; if (e) try { n.error(p.detail); } catch (h) { console.error("Failed to error controller:", h); } }, f = () => { e = !1; try { n.close(); } catch (u) { console.error("Failed to close controller on abort:", u); } }; return this.addEventListener("data", o, { signal: this.abortController.signal }), this.addEventListener("end", a, { signal: this.abortController.signal }), this.addEventListener("error", s, { signal: this.abortController.signal }), this.abortController.signal.addEventListener("abort", f), () => { e = !1; }; }, cancel() { e = !1; } }); } cleanup() { this.buffer = [], this.abortController.abort(); } // 提供类型安全的方法,但不重写 addEventListener onData(t, e) { this.addEventListener("data", t, e); } onEnd(t, e) { this.addEventListener("end", t, e); } onError(t, e) { this.addEventListener("error", t, e); } offData(t, e) { this.removeEventListener("data", t, e); } offEnd(t, e) { this.removeEventListener("end", t, e); } offError(t, e) { this.removeEventListener("error", t, e); } } const Mt = (i) => { const t = {}; return i.forEach(({ name: e, content: n }) => { const o = e.split("/"); let a = t; for (let s = 0; s < o.length; s++) { const f = o[s]; s === o.length - 1 ? a[f] = { file: { contents: n } } : (a[f] || (a[f] = { directory: {} }), a = a[f].directory); } }), t; }, zt = ({ sessionKey: i, initFiles: t, buildCommand: e = "yarn", startCommand: n = "yarn dev", children: o, fileObservers: a, onError: s }) => { const [f, u] = v(""), [p, h] = v(!0), { webContainer: g, iframeSrc: T } = ae(), w = L(null), [y, b] = v(null), [E, N] = v(null), [P, S] = v(null), [U, ne] = v(null), [ce, H] = v([]), X = L([]), ee = M(() => { H([]); }, []); _(() => { g && (ee(), g.mount(Mt(t))); }, [g, t]), _(() => { p && T && h(!1); }, [T, p]); const Q = M( (x) => { H((j) => j.find((R) => R.message === x.message && R.from === x.from && R.type === x.type) ? j : [...j, x]), s && s(x); }, [s] ), re = M(async () => { if (!g || !t || !t.length || !i) return; u("(1/4)初始化容器..."); const x = { ...a, "package.json": async () => { e && await (await g.spawn(e)).exit; } }, j = {}; for (const F of t) { const { name: $, content: W } = F; if (u(`(2/4)写入文件${$}...`), console.log(`Writing file ${$}...`), $.indexOf("/") > -1) { const V = $.substring(0, $.lastIndexOf("/")); await g.fs.mkdir(V, { recursive: !0 }); } if (x && x[$] && (j[$] = W), $ === "index.html") { let V = W; W.indexOf("</body>") > -1 ? V = W.replace("</body>", '<script src="./error-monitor.js"><\/script></body>') : V = W + '<script src="./error-monitor.js"><\/script>', await g.fs.writeFile($, V); } else await g.fs.writeFile($, W); } if (await g.fs.writeFile("error-monitor.js", vt), console.log("Writing files done"), u("(3/4)构建中..."), e && await (await g.spawn(e)).exit, x) for (const F in x) { X.current.push( g.fs.watch(F, async () => { const W = await g.fs.readFile(F, "utf-8"); j[F] !== W && (x[F](W, j[F]), j[F] = W); }) ); const $ = await g.fs.readFile(F, "utf-8"); j[F] !== $ && (x[F]($, j[F]), j[F] = $); } console.log("Build done"); const R = await g.spawn("/bin/jsh", ["--osc"], { terminal: { cols: 60, rows: 24 } }); w.current = R, b(R); const A = R.input.getWriter(); N(A); const { output: z } = R; S(z), ne(new Ct(z, (F) => Q({ type: "terminal", message: F }))), u("(4/4)启动中..."), n && await A.write(`${n} `), console.log("Start done"); }, [i, g]), de = M(async () => { g && (X.current.forEach((x) => { x.close(); }), X.current = [], w.current && (console.log("kill process"), await w.current.kill(), w.current = null), await Promise.all([ ...t.map((x) => x.name).map((x) => g.fs.rm(x)), g.fs.rm("yarn.lock") ]), console.log("cleanup done"), ee()); }, [g]), { execute: G } = bt(re, de); _(() => { g && (h(!0), G()); }, [g, G]); const ue = He(() => t.reduce((x, j) => (x[j.name] = j.content, x), {}), [t]), fe = M( async (x, j) => { if (g) { if (console.log(`Writing file ${x}...`), x.indexOf("/") > -1) { const R = x.substring(0, x.lastIndexOf("/")); await g.fs.mkdir(R, { recursive: !0 }); } await g.fs.writeFile(x, j); } }, [g] ), te = M( async (x) => { g && await (await g.spawn(x)).exit; }, [g, E] ); return /* @__PURE__ */ l( Se.Provider, { value: { loading: p, loadingContent: f, startProcess: y, writer: E, output: P, processOutput: U, onError: Q, sessionErrors: ce, clearErrors: ee, setFile: fe, spawn: te, files: ue }, children: o } ); }, Bt = ({ router: i = "", style: t, className: e, width: n = "100%", height: o = "100%" }) => { const { loading: a, onError: s } = Z(), { iframeSrc: f } = ae(), u = L(null); return _(() => { if (!u.current) return; const p = ({ data: { type: h }, data: g }) => { if (h === "webcontainer-error") { console.error("WebContainer error:", g); const { error: { message: T, filename: w, lineno: y, colno: b } } = g; if (s && T) { const E = w ? `${w.indexOf("webcontainer-api.io/") > -1 ? w.substring(w.indexOf("webcontainer-api.io/") + 20) : w}:${y}:${b}` : ""; s({ type: "browser", message: T, from: E }); } } }; return window.addEventListener("message", p), () => { window.removeEventListener("message", p); }; }, [f, s, a]), a || !f ? null : /* @__PURE__ */ l( "iframe", { ref: u, src: i ? `${f}/${i}` : f, className: e, style: { border: "none", width: n, height: o, ...t } } ); }, Me = ({ active: i, onTerminalReady: t }) => { const e = L(null); return _(() => { if (!e.current || !t) return; const n = new ht({ cursorBlink: !0, rows: 24, cols: 60, theme: { background: "#1e1e1e", foreground: "#f0f0f0" }, fontSize: 14, fontFamily: 'Menlo, Monaco, "Courier New", monospace' }); n.open(e.current), t(n); }, [t]), /* @__PURE__ */ l( "div", { ref: e, style: { display: i ? "block" : "none" } } ); }, _t = () => { const [i, t] = v(!1), [e, n] = v(0), [o, a] = v(0), { startProcess: s, writer: f, processOutput: u } = Z(), { webContainer: p } = ae(), h = L(null), g = M((y, b) => { const E = y.input.getWriter(), { output: N } = y; N.pipeTo( new WritableStream({ write(P) { b.write(P); } }) ), b.onData((P) => { E.write(P); }); }, []), T = M( async (y) => { if (!p) return; const b = await p.spawn("/bin/jsh", ["--osc"], { terminal: { cols: 60, rows: 24 } }); g(b, y); }, [p, g] ), w = M( async (y) => { if (!s || !f || !u) return; const b = u.createStream(); h.current = b, b.pipeTo( new WritableStream({ write(E) { y.write(E); } }) ), y.onData((E) => { f.write(E); }); }, [s, f] ); return /* @__PURE__ */ k(me, { children: [ /* @__PURE__ */ l( K, { type: "primary", shape: "circle", size: "large", icon: /* @__PURE__ */ l(Xe, {}), onClick: () => t(!i), style: { boxShadow: "0 2px 8px rgba(0, 0, 0, 0.15)" } } ), /* @__PURE__ */ k( "div", { style: { position: "fixed", bottom: "80px", right: "20px", width: "600px", height: "400px", backgroundColor: "#1e1e1e", color: "#f0f0f0", borderRadius: "5px", zIndex: 1e3, display: i ? "flex" : "none", boxShadow: "0 4px 12px rgba(0, 0, 0, 0.15)", overflow: "hidden" }, children: [ /* @__PURE__ */ k( Y, { direction: "vertical", style: { padding: "8px 12px", backgroundColor: "#333", display: "flex", justifyContent: "flex-start", alignItems: "center" }, children: [ Array.from({ length: e + 1 }, (y, b) => /* @__PURE__ */ l( K, { type: b === o ? "primary" : "text", size: "small", onClick: () => a(b), style: { color: "#f0f0f0" }, children: b === 0 ? "Main" : `Terminal ${b + 1}` }, b )), /* @__PURE__ */ l( K, { type: "text", size: "small", onClick: () => n((y) => y + 1), style: { color: "#f0f0f0" }, children: "+" } ) ] } ), s && /* @__PURE__ */ l(Me, { active: o === 0, onTerminalReady: w }), Array.from({ length: e }, (y, b) => { const E = b + 1 === o; return /* @__PURE__ */ l(Me, { active: E, onTerminalReady: T }, b); }) ] } ) ] }); }, Vt = ({ onFix: i, hanging: t = !1 }) => { const [e, n] = v([]), [o, a] = v(!1), [s, f] = v(!0), { sessionErrors: u, clearErrors: p } = Z(), h = M(async () => { a(!0), await i(u), a(!1), p && p(); }, [u, p]); return !u || u.length === 0 ? null : /* @__PURE__ */ k(me, { children: [ /* @__PURE__ */ l( K, { type: "primary", shape: "circle", size: "large", icon: /* @__PURE__ */ l(Ge, {}), onClick: () => f(!s), style: { boxShadow: "0 2px 8px rgba(0, 0, 0, 0.15)" } } ), /* @__PURE__ */ k( ct, { title: /* @__PURE__ */ k(Y, { children: [ /* @__PURE__ */ l(ie.Text, { type: "danger", children: "潜在问题已检测到" }), /* @__PURE__ */ k(ie.Text, { type: "secondary", children: [ u.length, " 个错误" ] }) ] }), extra: /* @__PURE__ */ l(K, { type: "text", icon: /* @__PURE__ */ l(Ye, {}), onClick: () => f(!1) }), style: { width: 500, position: "fixed", right: 20, bottom: 20, zIndex: 1e3, boxShadow: "0 3px 6px rgba(0,0,0,0.16)", display: s ? "block" : "none" }, children: [ /* @__PURE__ */ l( dt, { activeKey: e, onChange: n, items: u.map((g, T) => ({ key: T, label: /* @__PURE__ */ k(Y, { children: [ /* @__PURE__ */ l(ie.Text, { type: "danger", children: g.type === "terminal" ? "终端错误" : "浏览器错误" }), g.from && /* @__PURE__ */ l(ie.Text, { ellipsis: !0, style: { maxWidth: 300 }, children: g.from }) ] }), children: /* @__PURE__ */ l("div", { children: /* @__PURE__ */ l(ut, { type: "error", message: g.message }) }) })) } ), /* @__PURE__ */ l("div", { style: { marginTop: 16, textAlign: "right" }, children: /* @__PURE__ */ k(Y, { children: [ /* @__PURE__ */ l(K, { disabled: t, onClick: h, loading: o, type: "primary", icon: /* @__PURE__ */ l(Je, {}), children: "尝试修复" }), /* @__PURE__ */ l(K, { onClick: () => f(!1), children: "忽略" }) ] }) }) ] } ) ] }); }, qt = ({ routers: i, style: t, className: e, width: n = 1440, height: o = 1024, scale: a = 0.5 }) => { const { loading: s } = Z(), { iframeSrc: f } = ae(), u = { width: n * a, height: o * a }, p = { width: n, height: o }; return s || !f ? null : /* @__PURE__ */ l("div", { className: e, style: { position: "relative", ...t }, children: /* @__PURE__ */ l(Y, { wrap: !0, size: "large", children: i.map((h) => /* @__PURE__ */ k("div", { style: { border: "1px solid #ccc", padding: "10px", borderRadius: "5px", margin: "10px" }, children: [ h, /* @__PURE__ */ l("div", { style: { ...u }, children: /* @__PURE__ */ l( "iframe", { src: `${f}/${h}`, style: { ...p, transform: `scale(${a})`, transformOrigin: "0 0" } } ) }) ] }, h)) }) }); }, Kt = ({ value: i, onChange: t }) => /* @__PURE__ */ l( K, { type: "primary", shape: "circle", size: "large", icon: i === "mobile" ? /* @__PURE__ */ l(Ze, {}) : /* @__PURE__ */ l(et, {}), onClick: () => t && t(i === "mobile" ? "pc" : "mobile"), style: { boxShadow: "0 2px 8px rgba(0, 0, 0, 0.15)" } } ), kt = ({ style: i, className: t, children: e }) => { const { loading: n, loadingContent: o } = Z(); return /* @__PURE__ */ k("div", { className: t, style: { position: "relative", width: "100%", height: "100%", ...i }, children: [ n && /* @__PURE__ */ k(me, { children: [ /* @__PURE__ */ l("div", { style: { display: "flex", justifyContent: "center", alignItems: "center", height: "100%" }, children: /* @__PURE__ */ l(ft, { size: "large" }) }), /* @__PURE__ */ k( "div", { style: { position: "absolute", left: "10px", bottom: "10px", display: "flex", alignItems: "center", color: "#999", zIndex: 1e3 }, children: [ /* @__PURE__ */ l( tt, { style: { color: "#999", fontSize: "12px" } } ), o ] } ) ] }), e ] }); }, Et = ({ style: i, className: t, children: e }) => /* @__PURE__ */ l( "div", { className: t, style: { position: "fixed", bottom: "20px", right: "20px", zIndex: 1e3, display: "flex", justifyItems: "flex-end", gap: "10px", ...i }, children: e } ), Ut = { Layout: kt, Footer: Et }, Ft = ({ fileSystem: i, onFileClick: t, onFolderToggle: e, onContextMenu: n, activeFileId: o, openFiles: a }) => { const [s, f] = v(null), u = (h, g = 0) => { var N, P; if (h.id === "root") return /* @__PURE__ */ l("div", { children: Array.from(((N = h.children) == null ? void 0 : N.values()) || []).map((S) => u(S, g)) }, h.id); const T = s === h.id, w = o === h.id, y = h.type === "file" && ((P = a.get(h.id)) == null ? void 0 : P.modified), b = (S) => { S.stopPropagation(), f(h.id), h.type === "file" ? t(h.id) : e(h.id); }, E = (S) => { S.stopPropagation(), f(h.id), n(S, h.id); }; return /* @__PURE__ */ k("div", { children: [ /* @__PURE__ */ k( "div", { className: `file-tree-item ${h.type} ${T ? "selected" : ""} ${w ? "active" : ""} ${y ? "modified" : ""}`, style: { paddingLeft: `${g * 16 + 8}px` }, onClick: b, onContextMenu: E, children: [ /* @__PURE__ */ l("span", { className: "icon", children: h.type === "folder" ? h.expanded ? /* @__PURE__ */ l(nt, {}) : /* @__PURE__ */ l(rt, {}) : /* @__PURE__ */ l(ot, {}) }), /* @__PURE__ */ l("span", { className: "label", children: h.name }), y && /* @__PURE__ */ l("span", { className: "modified-indicator", children: "●" }) ] } ), h.type === "folder" && h.expanded && h.children && /* @__PURE__ */ l("div", { children: Array.from(h.children.values()).map((S) => u(S, g + 1)) }) ] }, h.id); }, p = i.get("root"); return p ? /* @__PURE__ */ l("div", { className: "file-tree", children: u(p) }) : null; }, St = ({ openFiles: i, fileSystem: t, activeFileId: e, onTabClick: n, onTabClose: o }) => /* @__PURE__ */ l("div", { className: "tab-bar", children: Array.from(i.entries()).map(([a, s]) => { const f = t.get(a); if (!f) return null; const u = e === a, p = s.modified; return /* @__PURE__ */ k( "div", { className: `tab ${u ? "active" : ""} ${p ? "modified" : ""}`, onClick: () => n(a), children: [ /* @__PURE__ */ l("span", { className: "tab-label", children: f.name }), /* @__PURE__ */ l( "button", { className: "tab-close", onClick: (h) => { h.stopPropagation(), o(a); }, children: "×" } ) ] }, a ); }) }), je = (i) => { var n; const t = ((n = i.split(".").pop()) == null ? void 0 : n.toLowerCase()) || ""; return { js: "javascript", ts: "typescript", jsx: "javascript", tsx: "typescript", html: "html", css: "css", scss: "scss", json: "json", md: "markdown", py: "python", java: "java", c: "c", cpp: "cpp", cs: "csharp", php: "php", go: "go", rs: "rust", sql: "sql", yaml: "yaml", yml: "yaml" }[t] || "plaintext"; }, jt = (i, t, e) => { const n = i.get(t); if (!(n != null && n.children)) return null; for (const [o, a] of n.children) if (a.name === e) return a; return null; }, J = (i, t, e, n = "") => { const o = i.get(t); if (!o || o.type !== "folder") return null; const a = `${o.fullPath}${e}`, s = a, f = { id: s, name: e, fullPath: a, type: "file", content: n, parent: t, modified: !1, language: je(e) }; return i.set(s, f), o.children.set(s, f), f; }, le = (i, t, e) => { const n = i.get(t); if (!n || n.type !== "folder") return null; const o = `${n.fullPath}${e}/`, a = o, s = { id: a, name: e, fullPath: o, type: "folder", children: /* @__PURE__ */ new Map(), parent: t, expanded: !1 }; return i.set(a, s), n.children.set(a, s), s; }, Ot = (i, t, e) => { const n = t.split("/").filter(Boolean); let o = "root"; for (let s = 0; s < n.length - 1; s++) { const f = n[s], u = jt(i, o, f); if (u) o = u.id; else { const p = le(i, o, f); p && (o = p.id); } } const a = n[n.length - 1]; J(i, o, a, e); }, Oe = (i) => { const t = { id: i.id, name: i.name, fullPath: i.fullPath, type: i.type, content: i.content, children: /* @__PURE__ */ new Map() }; if (i.type === "folder" && i.children) for (const [e, n] of i.children) { const o = Oe(n); t.children.set(o.id, o); } return t; }, Pe = (i, t, e) => { const n = i.get(e); if (!(n != null && n.children)) return !1; for (const [o, a] of n.children) if (a.name === t) return !0; return !1; }, Pt = (i, t, e) => { const n = i.get(e); if (!(n != null && n.children)) return t; let o = 1, a = t; for (; Pe(i, a, e); ) { const s = t.includes(".") ? t.substring(t.lastIndexOf(".")) : ""; a = `${t.includes(".") ? t.substring(0, t.lastIndexOf(".")) : t}_copy${o > 1 ? o : ""}${s}`, o++; } return a; }, $e = (i, t, e) => { const n = Pt(i, t.name, e); if (t.type === "file") J(i, e, n, t.content || ""); else if (t.children) { const o = le(i, e, n); if (o) for (const [a, s] of t.children) $e(i, s, o.id); } }, Te = (i, t) => { const e = i.get(t); if (e) { if (e.type === "folder" && e.children) for (const [n] of e.children) Te(i, n); i.delete(t); } }, Ht = () => { const { files: i, setFile: t } = Z(), [e, n] = v(/* @__PURE__ */ new Map()), [o, a] = v(/* @__PURE__ */ new Map()), [s, f] = v(null), [u, p] = v({ x: 0, y: 0, targetId: "", show: !1 }), [h, g] = v(null), [T, w] = v(300), [y, b] = v(!1), [E, N] = v("file"), [P, S] = v("root"), [U, ne] = v(""), [ce, H] = v(!1), [X, ee] = v(""), [Q, re] = v(""), [de, G] = v(!1), [ue, fe] = v(""), [te, x] = v("file"), [j, R] = v(""), A = L(null), z = L(null), F = L(!1); _(() => { const r = { id: "root", name: "root", fullPath: "/", type: "folder", children: /* @__PURE__ */ new Map(), expanded: !0 }, d = /* @__PURE__ */ new Map(); if (d.set("root", r), Object.entries(i).forEach(([c, m]) => { Ot(d, c, m); }), Object.keys(i).length === 0) { J(d, "root", "index.js", 'console.log("Hello World!");'); const c = le(d, "root", "src"); c && (J( d, c.id, "app.js", `export default function App() { return "Hello from App"; }` ), J( d, c.id, "utils.js", `export const utils = { formatDate: (date) => { return date.toLocaleDateString(); } };` )); } n(d); }, []); const $ = M( (r) => { n((d) => { const c = new Map(d), m = c.get(r); if (!m || r === "root") return d; if (m.type === "folder" && m.children) for (const [O] of m.children) Te(c, O); m.type === "file" && (a((O) => { var q; const D = new Map(O), I = D.get(r); return I && ((q = I.model) == null || q.dispose(), D.delete(r)), D; }), s === r && f(null)); const C = c.get(m.parent); return C != null && C.children && C.children.delete(r), c.delete(r), c; }); }, [s] ), W = M( (r, d) => { if (!d.trim() || r === "root") return !1; let c = !0; return n((m) => { const C = new Map(m), O = C.get(r); if (!O) return m; const D = C.get(O.parent); if (D != null && D.children) { for (const [I, q] of D.children) if (q.id !== r && q.name === d) return B.error("该名称已存在"), c = !1, m; } if (O.name = d, O.type === "file") { O.language = je(d); const I = o.get(r); I && z.current && z.current.editor.setModelLanguage(I.model, O.language); } return C; }), c; }, [o] ), V = M( (r) => { const d = e.get(r); d && r !== "root" && g(Oe(d)); }, [e] ), ge = M( (r) => { h && n((d) => { const c = new Map(d), m = c.get(r); return !m || m.type !== "folder" ? d : ($e(c, h, r), c); }); }, [h] ), we = M( (r) => { const d = e.get(r); if (!(!d || d.type !== "file")) { if (o.has(r)) { f(r); return; } a((c) => { const m = new Map(c), C = { id: r, model: null, modified: !1, viewState: null }; return m.set(r, C), m; }), f(r); } }, [e, o] ), De = M( (r) => { const d = o.get(r); if (d) { if (d.modified) { const c = e.get(r); if (!window.confirm(`"${c == null ? void 0 : c.name}" 有未保存的更改,确定要关闭吗?`)) return; } if (a((c) => { const m = new Map(c), C = m.get(r); return C != null && C.model && C.model.dispose(), m.delete(r), m; }), s === r) { const c = Array.from(o.keys()).filter((m) => m !== r); f(c.length > 0 ? c[c.length - 1] : null); } } }, [o, e, s] ), Ie = M( (r, d) => { n((c) => { var O; const m = new Map(c), C = m.get(r); return C && (C.content = d), t && t(((O = m.get(r)) == null ? void 0 : O.fullPath) || "", d), m; }), a((c) => { const m = new Map(c), C = m.get(r); return C && (C.modified = !1), m; }); }, [t] ), oe = M( (r) => { if (!A.current || !z.current) return; const d = e.get(r); if (!d || d.type !== "file") return; let c = o.get(r); if (!c) return; if (!(c != null && c.model)) { const C = z.current.editor.createModel( d.content || "", d.language, z.current.Uri.file(d.fullPath) ); C._fileId = r, a((O) => { const D = new Map(O), I = D.get(r); return I ? (I.model = C, D.set(r, I), D) : O; }), c = { ...c, model: C }; } if (A.current.getModel() && s && s !== r) { const C = o.get(s); C && (C.viewState = A.current.saveViewState()); } A.current.setModel(c.model), c.viewState && A.current.restoreViewState(c.viewState), A.current.focus(); }, [e, o, s] ), Le = (r, d) => { A.current = r, z.current = d, r.addCommand(d.KeyMod.CtrlCmd | d.KeyCode.KeyS, () => { const c = r.getValue(), m = r.getModel()._fileId; Ie(m, c); }), r.onDidChangeModelContent(() => { var m; const c = r.getModel()._fileId; if (c) { const C = r.getValue(), O = ((m = e.get(c)) == null ? void 0 : m.content) || ""; a((D) => { const I = new Map(D), q = I.get(c); return q && (q.modified = C !== O), I; }); } }), s && oe(s); }; _(() => { s ? oe(s) : A.current && (A.current = null); }, [s, oe]); const Re = M((r) => { n((d) => { const c = new Map(d), m = c.get(r); return m && m.type === "folder" && (m.expanded = !m.expanded), c; }); }, []), se = (r, d) => { N(r), S(d), ne(""), b(!0); }, Ae = () => { if (!U.trim()) { B.error("名称不能为空"); return; } if (Pe(e, U, P)) { B.error("该名称已存在"); return; } n((r) => { const d = new Map(r); if (E === "file") { const m = J(d, P, U); m && setTimeout(() => we(m.id), 0); } else le(d, P, U); const c = d.get(P); return c && c.type === "folder" && (c.expanded = !0), d; }), b(!1), B.success(`${E === "file" ? "文件" : "文件夹"}创建成功`); }, We = (r) => { const d = e.get(r); d && (ee(r), re(d.name), H(!0)); }, Ne = () => { if (!Q.trim()) { B.error("名称不能为空"); return; } const r = e.get(X); if (!r) return; if (Q === r.name) { H(!1); return; } W(X, Q) && (H(!1), B.success("重命名成功")); }, ze = (r) => { const d = e.get(r); d && (fe(r), x(d.type), R(d.name), G(!0)); }, Be = () => { $(ue), G(!1), B.success(`${te === "file" ? "文件" : "文件夹"}已删除`); }, _e = M( (r, d) => { const c = d || "root"; switch (r) { case "newFile": se("file", c); break; case "newFolder": se("folder", c); break; case "rename": c !== "root" && We(c); break; case "copy": c !== "root" && (V(c), B.success("已复制到剪贴板")); break; case "paste": ge(c), B.success("粘贴成功"); break; case "delete": c !== "root" && ze(c); break; } p((m) => ({ ...m, show: !1 })); }, [V, ge] ), Ve = M((r, d) => { r.preventDefault(), p({ x: r.clientX, y: r.clientY, targetId: d, show: !0 }); }, []), qe = M(() => { p((r) => ({ ...r, show: !1 })); }, []), Ke = () => { F.current = !0; const r = (c) => { if (!F.current) return; const m = Math.max(200, Math.min(c.clientX, window.innerWidth - 200)); w(m); }, d = () => { F.current = !1, s && oe(s), document.removeEventListener("mousemove", r), document.removeEventListener("mouseup", d); }; document.addEventListener("mousemove", r), document.addEventListener("mouseup", d); }, pe = s ? e.get(s) : null, ye = o.size > 0, Ue = [ { key: "newFile", label: "新建文件", icon: /* @__PURE__ */ l(be, {}) }, { key: "newFolder", label: "新建文件夹", icon: /* @__PURE__ */ l(ve, {}) }, { type: "divider" }, { key: "rename", label: "重命名", icon: /* @__PURE__ */ l(st, {}), disabled: u.targetId === "root" }, { key: "copy", label: "复制", icon: /* @__PURE__ */ l(it, {}), disabled: u.targetId === "root" }, { key: "paste", label: "粘贴", icon: /* @__PURE__ */ l(lt, {}), disabled: !h }, { type: "divider" }, { key: "delete", label: "删除", icon: /* @__PURE__ */ l(at, {}), danger: !0, disabled: u.targetId === "root" } ]; return /* @__PURE__ */ k("div", { className: "code-editor", onClick: qe, children: [ /* @__PURE__ */ k("div", { className: "editor-container", children: [ /* @__PURE__ */ k("div", { className: "file-panel", style: { width: T }, children: [ /* @__PURE__ */ k("div", { className: "file-panel-header", children: [ /* @__PURE__ */ l("h3", { children: "文件资源管理器" }), /* @__PURE__ */ l("div", { className: "toolbar", children: /* @__PURE__ */ k(Y, { size: "small", children: [ /* @__PURE__ */ l(xe, { title: "新建文件", children: /* @__PURE__ */ l(be, { onClick: () => se("file", "root") }) }), /* @__PURE__ */ l(xe, { title: "新建文件夹", children: /* @__PURE__ */ l(ve, { onClick: () => se("folder", "root") }) }) ] }) }) ] }), /* @__PURE__ */ l( Ft, { fileSystem: e, onFileClick: we, onFolderToggle: Re, onContextMenu: Ve, activeFileId: s, openFiles: o } ) ] }), /* @__PURE__ */ l("div", { className: "resizer", onMouseDown: Ke }), /* @__PURE__ */ k("div", { className: "editor-panel", children: [ ye && /* @__PURE__ */ l( St, { openFiles: o, fileSystem: e, activeFileId: s, onTabClick: f, onTabClose: De } ), /* @__PURE__ */ l("div", { className: "monaco-container", children: ye ? /* @__PURE__ */ l( mt, { height: "100%", theme: "vs-dark", language: (pe == null ? void 0 : pe.language) || "javascript", onMount: Le, options: { automaticLayout: !0, fontSize: 14, wordWrap: "on", minimap: { enabled: !0 }, scrollBeyondLastLine: !1, renderLineHighlight: "all", selectOnLineNumbers: !0, tabSize: 2, insertSpaces: !0 } } ) : /* @__PURE__ */ k("div", { className: "welcome-screen", children: [ /* @__PURE__ */ l("h2", { children: "简陋的代码编辑器" }), /* @__PURE__ */ l("p", { children: "选择一个文件开始编辑,或创建一个新文件" }) ] }) }) ] }) ] }), u.show && /* @__PURE__ */ l( pt, { open: u.show, menu: { items: Ue, onClick: ({ key: r }) => _e(r, u.targetId) }, trigger: ["contextMenu"], overlayStyle: { position: "fixed", left: `${u.x}px`, top: `${u.y}px` }, children: /* @__PURE__ */ l("div", { style: { position: "fixed", left: u.x, top: u.y, width: 0, height: 0 } }) } ), /* @__PURE__ */ l( he, { title: `新建${E === "file" ? "文件" : "文件夹"}`, open: y, onOk: Ae, onCancel: () => b(!1), okText: "确定", cancelText: "取消", children: /* @__PURE__ */ l( Ce, { placeholder: `请输入${E === "file" ? "文件" : "文件夹"}名称`, value: U, onChange: (r) => ne(r.target.value), autoFocus: !0 } ) } ), /* @__PURE__ */ l( he, { title: "重命名", open: ce, onOk: Ne, onCancel: () => H(!1), okText: "确定", cancelText: "取消", children: /* @__PURE__ */ l( Ce, { placeholder: "请输入新名称", value: Q, onChange: (r) => re(r.target.value), autoFocus: !0 } ) } ), /* @__PURE__ */ l( he, { title: "确认删除", open: de, onOk: Be, onCancel: () => G(!1), okText: "确定", cancelText: "取消", children: /* @__PURE__ */ k("p", { children: [ "确定要删除", te === "folder" ? "文件夹" : "文件", ' "', j, '"', te === "folder" ? "及其所有内容" : "", "吗?" ] }) } ) ] }); }; export { Ht as CodeEditor, Kt as EndSwitch, Vt as ErrorFix, qt as Gallery, Bt as Preview, Ut as SessionLayout, zt as SessionProvider, _t as TerminalPanel, Nt as WebContainerProvider, Z as useSession, ae as useWebContainer }; //# sourceMappingURL=index.esm.js.map