webcontainer-sandbox-react
Version:
A React component library for WebContainer-based code sandboxes
206 lines (185 loc) • 33.8 kB
JavaScript
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const n=require("react/jsx-runtime"),r=require("react"),$e=require("@webcontainer/api"),O=require("@ant-design/icons"),b=require("antd"),De=require("xterm");require("xterm/css/xterm.css");const Ie=require("@monaco-editor/react"),fe=r.createContext({webContainer:null,iframeSrc:""}),J=()=>r.useContext(fe),Le=({children:l})=>{const[t,e]=r.useState(null),[s,a]=r.useState("");return r.useEffect(()=>{(async()=>{if(t)return;const i=await $e.WebContainer.boot({workdirName:"project"});i.on("error",p=>{console.error("WebContainer error:",p)}),i.on("port",(p,f,g)=>{f==="close"?(a(""),console.log("port closed")):f==="open"&&(console.log(`Server running on ${g}`),a(g))}),e(i)})()},[]),n.jsx(fe.Provider,{value:{webContainer:t,iframeSrc:s},children:l})},pe=r.createContext({loading:!1,loadingContent:"",startProcess:null,writer:null,output:null,processOutput:null,sessionErrors:[],clearErrors:null,files:{},setFile:null,spawn:null}),K=()=>r.useContext(pe),Ae=()=>`task_${Date.now()}_${Math.random().toString(36).substring(2,9)}`;function We(l={}){const{cancelPending:t=!0,cleanupBeforeNew:e=!0,debug:s=!1}=l,a=r.useRef([]),c=r.useRef(null),i=r.useRef(null),p=r.useRef(!1),f=r.useRef(!1),g=r.useCallback((...w)=>{s&&console.log("[AsyncQueue]",...w)},[s]),h=r.useCallback(async()=>{if(!p.current){for(p.current=!0;a.current.length>0&&!f.current;){let w=null;if(t){for(let y=0;y<a.current.length-1;y++){const j=a.current[y];j!==c.current&&(j.cancelled=!0,g(`取消等待任务: ${j.id}`))}for(let y=a.current.length-1;y>=0;y--){const j=a.current[y];if(!j.cancelled){w=j;break}}}else w=a.current.find(y=>!y.cancelled)||null;if(a.current=a.current.filter(y=>!y.cancelled),!w)break;if(e&&c.current&&c.current!==w){if(g(`检测到新任务,开始清理当前任务: ${c.current.id}`),c.current.cleanup)try{await c.current.cleanup(),g(`清理完成: ${c.current.id}`)}catch(y){g(`清理失败: ${c.current.id}`,y)}c.current=null,i.current=null}else i.current&&i.current.cleanup&&(await i.current.cleanup(),g(`清理完成: ${i.current.id}`),i.current=null);if(!w.cancelled){c.current=w;try{g(`开始执行任务: ${w.id}`),await w.execute(),w.cancelled?g(`任务被取消: ${w.id}`):(g(`任务执行完成: ${w.id}`),w.cleanup&&(i.current=w))}catch(y){const j=y;g(`任务执行失败: ${w.id}`,j)}a.current=a.current.filter(y=>y!==w)}}c.current=null,p.current=!1,g("队列处理完成")}},[t,e,g]),x=r.useCallback(async(w,y)=>new Promise((j,k)=>{const W=Ae(),P={id:W,cancelled:!1,timestamp:Date.now(),execute:async()=>{try{const M=await w();return P.cancelled||j(M),M}catch(M){throw P.cancelled||k(M),M}},cleanup:y};a.current.push(P),g(`任务入队: ${W}, 队列长度: ${a.current.length}`),h()}),[h,g]);r.useEffect(()=>()=>{x(()=>new Promise(w=>{setTimeout(w,1e3)}))},[]);const T=r.useCallback(()=>{a.current.forEach(w=>{w.cancelled=!0}),a.current=[],g("所有任务已取消")},[g]);return{enqueue:x,cancelAll:T}}const Ne={cleanupBeforeNew:!0,debug:!0,cancelPending:!0};function ze(l,t){const{enqueue:e,cancelAll:s}=We(Ne),a=r.useCallback(async()=>{try{return await e(l,t)}catch(i){throw console.log("Error:",i),i}},[e,l,t]),c=r.useCallback(()=>{s()},[s]);return{execute:a,reset:c}}const Be=`
(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);
})();
`,qe=l=>l.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g,"");class _e 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:s}=await t.read();if(e)break;(s.startsWith("Error:")||s.indexOf("[22m \x1B[31m\x1B[1m[vite]")>-1)&&this.onErrorStream(qe(s)),this.buffer.push(s),this.buffer.length>this.maxBufferSize&&this.buffer.shift(),this.dispatchEvent(new CustomEvent("data",{detail:s}))}}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:s=>{if(this.abortController.signal.aborted){s.close();return}t.forEach(f=>{e&&!this.abortController.signal.aborted&&s.enqueue(f)});const a=f=>{const g=f;if(e&&!this.abortController.signal.aborted)try{s.enqueue(g.detail)}catch(h){console.error("Failed to enqueue data:",h)}},c=()=>{if(e)try{s.close()}catch(f){console.error("Failed to close controller:",f)}},i=f=>{const g=f;if(e)try{s.error(g.detail)}catch(h){console.error("Failed to error controller:",h)}},p=()=>{e=!1;try{s.close()}catch(f){console.error("Failed to close controller on abort:",f)}};return this.addEventListener("data",a,{signal:this.abortController.signal}),this.addEventListener("end",c,{signal:this.abortController.signal}),this.addEventListener("error",i,{signal:this.abortController.signal}),this.abortController.signal.addEventListener("abort",p),()=>{e=!1}},cancel(){e=!1}})}cleanup(){this.buffer=[],this.abortController.abort()}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 Ve=l=>{const t={};return l.forEach(({name:e,content:s})=>{const a=e.split("/");let c=t;for(let i=0;i<a.length;i++){const p=a[i];i===a.length-1?c[p]={file:{contents:s}}:(c[p]||(c[p]={directory:{}}),c=c[p].directory)}}),t},Ke=({sessionKey:l,initFiles:t,buildCommand:e="yarn",startCommand:s="yarn dev",children:a,fileObservers:c,onError:i})=>{const[p,f]=r.useState(""),[g,h]=r.useState(!0),{webContainer:x,iframeSrc:T}=J(),w=r.useRef(null),[y,j]=r.useState(null),[k,W]=r.useState(null),[P,M]=r.useState(null),[q,Y]=r.useState(null),[re,_]=r.useState([]),U=r.useRef([]),G=r.useCallback(()=>{_([])},[]);r.useEffect(()=>{x&&(G(),x.mount(Ve(t)))},[x,t]),r.useEffect(()=>{g&&T&&h(!1)},[T,g]);const V=r.useCallback(v=>{_(E=>E.find(I=>I.message===v.message&&I.from===v.from&&I.type===v.type)?E:[...E,v]),i&&i(v)},[i]),Z=r.useCallback(async()=>{if(!x||!t||!t.length||!l)return;f("(1/4)初始化容器...");const v={...c,"package.json":async()=>{e&&await(await x.spawn(e)).exit}},E={};for(const S of t){const{name:R,content:A}=S;if(f(`(2/4)写入文件${R}...`),console.log(`Writing file ${R}...`),R.indexOf("/")>-1){const z=R.substring(0,R.lastIndexOf("/"));await x.fs.mkdir(z,{recursive:!0})}if(v&&v[R]&&(E[R]=A),R==="index.html"){let z=A;A.indexOf("</body>")>-1?z=A.replace("</body>",'<script src="./error-monitor.js"><\/script></body>'):z=A+'<script src="./error-monitor.js"><\/script>',await x.fs.writeFile(R,z)}else await x.fs.writeFile(R,A)}if(await x.fs.writeFile("error-monitor.js",Be),console.log("Writing files done"),f("(3/4)构建中..."),e&&await(await x.spawn(e)).exit,v)for(const S in v){U.current.push(x.fs.watch(S,async()=>{const A=await x.fs.readFile(S,"utf-8");E[S]!==A&&(v[S](A,E[S]),E[S]=A)}));const R=await x.fs.readFile(S,"utf-8");E[S]!==R&&(v[S](R,E[S]),E[S]=R)}console.log("Build done");const I=await x.spawn("/bin/jsh",["--osc"],{terminal:{cols:60,rows:24}});w.current=I,j(I);const L=I.input.getWriter();W(L);const{output:N}=I;M(N),Y(new _e(N,S=>V({type:"terminal",message:S}))),f("(4/4)启动中..."),s&&await L.write(`${s}
`),console.log("Start done")},[l,x]),se=r.useCallback(async()=>{x&&(U.current.forEach(v=>{v.close()}),U.current=[],w.current&&(console.log("kill process"),await w.current.kill(),w.current=null),await Promise.all([...t.map(v=>v.name).map(v=>x.fs.rm(v)),x.fs.rm("yarn.lock")]),console.log("cleanup done"),G())},[x]),{execute:H}=ze(Z,se);r.useEffect(()=>{x&&(h(!0),H())},[x,H]);const oe=r.useMemo(()=>t.reduce((v,E)=>(v[E.name]=E.content,v),{}),[t]),ae=r.useCallback(async(v,E)=>{if(x){if(console.log(`Writing file ${v}...`),v.indexOf("/")>-1){const I=v.substring(0,v.lastIndexOf("/"));await x.fs.mkdir(I,{recursive:!0})}await x.fs.writeFile(v,E)}},[x]),X=r.useCallback(async v=>{x&&await(await x.spawn(v)).exit},[x,k]);return n.jsx(pe.Provider,{value:{loading:g,loadingContent:p,startProcess:y,writer:k,output:P,processOutput:q,onError:V,sessionErrors:re,clearErrors:G,setFile:ae,spawn:X,files:oe},children:a})},Ue=({router:l="",style:t,className:e,width:s="100%",height:a="100%"})=>{const{loading:c,onError:i}=K(),{iframeSrc:p}=J(),f=r.useRef(null);return r.useEffect(()=>{if(!f.current)return;const g=({data:{type:h},data:x})=>{if(h==="webcontainer-error"){console.error("WebContainer error:",x);const{error:{message:T,filename:w,lineno:y,colno:j}}=x;if(i&&T){const k=w?`${w.indexOf("webcontainer-api.io/")>-1?w.substring(w.indexOf("webcontainer-api.io/")+20):w}:${y}:${j}`:"";i({type:"browser",message:T,from:k})}}};return window.addEventListener("message",g),()=>{window.removeEventListener("message",g)}},[p,i,c]),c||!p?null:n.jsx("iframe",{ref:f,src:l?`${p}/${l}`:p,className:e,style:{border:"none",width:s,height:a,...t}})},de=({active:l,onTerminalReady:t})=>{const e=r.useRef(null);return r.useEffect(()=>{if(!e.current||!t)return;const s=new De.Terminal({cursorBlink:!0,rows:24,cols:60,theme:{background:"#1e1e1e",foreground:"#f0f0f0"},fontSize:14,fontFamily:'Menlo, Monaco, "Courier New", monospace'});s.open(e.current),t(s)},[t]),n.jsx("div",{ref:e,style:{display:l?"block":"none"}})},He=()=>{const[l,t]=r.useState(!1),[e,s]=r.useState(0),[a,c]=r.useState(0),{startProcess:i,writer:p,processOutput:f}=K(),{webContainer:g}=J(),h=r.useRef(null),x=r.useCallback((y,j)=>{const k=y.input.getWriter(),{output:W}=y;W.pipeTo(new WritableStream({write(P){j.write(P)}})),j.onData(P=>{k.write(P)})},[]),T=r.useCallback(async y=>{if(!g)return;const j=await g.spawn("/bin/jsh",["--osc"],{terminal:{cols:60,rows:24}});x(j,y)},[g,x]),w=r.useCallback(async y=>{if(!i||!p||!f)return;const j=f.createStream();h.current=j,j.pipeTo(new WritableStream({write(k){y.write(k)}})),y.onData(k=>{p.write(k)})},[i,p]);return n.jsxs(n.Fragment,{children:[n.jsx(b.Button,{type:"primary",shape:"circle",size:"large",icon:n.jsx(O.CodeOutlined,{}),onClick:()=>t(!l),style:{boxShadow:"0 2px 8px rgba(0, 0, 0, 0.15)"}}),n.jsxs("div",{style:{position:"fixed",bottom:"80px",right:"20px",width:"600px",height:"400px",backgroundColor:"#1e1e1e",color:"#f0f0f0",borderRadius:"5px",zIndex:1e3,display:l?"flex":"none",boxShadow:"0 4px 12px rgba(0, 0, 0, 0.15)",overflow:"hidden"},children:[n.jsxs(b.Space,{direction:"vertical",style:{padding:"8px 12px",backgroundColor:"#333",display:"flex",justifyContent:"flex-start",alignItems:"center"},children:[Array.from({length:e+1},(y,j)=>n.jsx(b.Button,{type:j===a?"primary":"text",size:"small",onClick:()=>c(j),style:{color:"#f0f0f0"},children:j===0?"Main":`Terminal ${j+1}`},j)),n.jsx(b.Button,{type:"text",size:"small",onClick:()=>s(y=>y+1),style:{color:"#f0f0f0"},children:"+"})]}),i&&n.jsx(de,{active:a===0,onTerminalReady:w}),Array.from({length:e},(y,j)=>{const k=j+1===a;return n.jsx(de,{active:k,onTerminalReady:T},j)})]})]})},Qe=({onFix:l,hanging:t=!1})=>{const[e,s]=r.useState([]),[a,c]=r.useState(!1),[i,p]=r.useState(!0),{sessionErrors:f,clearErrors:g}=K(),h=r.useCallback(async()=>{c(!0),await l(f),c(!1),g&&g()},[f,g]);return!f||f.length===0?null:n.jsxs(n.Fragment,{children:[n.jsx(b.Button,{type:"primary",shape:"circle",size:"large",icon:n.jsx(O.WarningOutlined,{}),onClick:()=>p(!i),style:{boxShadow:"0 2px 8px rgba(0, 0, 0, 0.15)"}}),n.jsxs(b.Card,{title:n.jsxs(b.Space,{children:[n.jsx(b.Typography.Text,{type:"danger",children:"潜在问题已检测到"}),n.jsxs(b.Typography.Text,{type:"secondary",children:[f.length," 个错误"]})]}),extra:n.jsx(b.Button,{type:"text",icon:n.jsx(O.CloseOutlined,{}),onClick:()=>p(!1)}),style:{width:500,position:"fixed",right:20,bottom:20,zIndex:1e3,boxShadow:"0 3px 6px rgba(0,0,0,0.16)",display:i?"block":"none"},children:[n.jsx(b.Collapse,{activeKey:e,onChange:s,items:f.map((x,T)=>({key:T,label:n.jsxs(b.Space,{children:[n.jsx(b.Typography.Text,{type:"danger",children:x.type==="terminal"?"终端错误":"浏览器错误"}),x.from&&n.jsx(b.Typography.Text,{ellipsis:!0,style:{maxWidth:300},children:x.from})]}),children:n.jsx("div",{children:n.jsx(b.Alert,{type:"error",message:x.message})})}))}),n.jsx("div",{style:{marginTop:16,textAlign:"right"},children:n.jsxs(b.Space,{children:[n.jsx(b.Button,{disabled:t,onClick:h,loading:a,type:"primary",icon:n.jsx(O.ToolOutlined,{}),children:"尝试修复"}),n.jsx(b.Button,{onClick:()=>p(!1),children:"忽略"})]})})]})]})},Ge=({routers:l,style:t,className:e,width:s=1440,height:a=1024,scale:c=.5})=>{const{loading:i}=K(),{iframeSrc:p}=J(),f={width:s*c,height:a*c},g={width:s,height:a};return i||!p?null:n.jsx("div",{className:e,style:{position:"relative",...t},children:n.jsx(b.Space,{wrap:!0,size:"large",children:l.map(h=>n.jsxs("div",{style:{border:"1px solid #ccc",padding:"10px",borderRadius:"5px",margin:"10px"},children:[h,n.jsx("div",{style:{...f},children:n.jsx("iframe",{src:`${p}/${h}`,style:{...g,transform:`scale(${c})`,transformOrigin:"0 0"}})})]},h))})})},Xe=({value:l,onChange:t})=>n.jsx(b.Button,{type:"primary",shape:"circle",size:"large",icon:l==="mobile"?n.jsx(O.DesktopOutlined,{}):n.jsx(O.MobileOutlined,{}),onClick:()=>t&&t(l==="mobile"?"pc":"mobile"),style:{boxShadow:"0 2px 8px rgba(0, 0, 0, 0.15)"}}),Je=({style:l,className:t,children:e})=>{const{loading:s,loadingContent:a}=K();return n.jsxs("div",{className:t,style:{position:"relative",width:"100%",height:"100%",...l},children:[s&&n.jsxs(n.Fragment,{children:[n.jsx("div",{style:{display:"flex",justifyContent:"center",alignItems:"center",height:"100%"},children:n.jsx(b.Spin,{size:"large"})}),n.jsxs("div",{style:{position:"absolute",left:"10px",bottom:"10px",display:"flex",alignItems:"center",color:"#999",zIndex:1e3},children:[n.jsx(O.LoadingOutlined,{style:{color:"#999",fontSize:"12px"}}),a]})]}),e]})},Ye=({style:l,className:t,children:e})=>n.jsx("div",{className:t,style:{position:"fixed",bottom:"20px",right:"20px",zIndex:1e3,display:"flex",justifyItems:"flex-end",gap:"10px",...l},children:e}),Ze={Layout:Je,Footer:Ye},et=({fileSystem:l,onFileClick:t,onFolderToggle:e,onContextMenu:s,activeFileId:a,openFiles:c})=>{const[i,p]=r.useState(null),f=(h,x=0)=>{var W,P;if(h.id==="root")return n.jsx("div",{children:Array.from(((W=h.children)==null?void 0:W.values())||[]).map(M=>f(M,x))},h.id);const T=i===h.id,w=a===h.id,y=h.type==="file"&&((P=c.get(h.id))==null?void 0:P.modified),j=M=>{M.stopPropagation(),p(h.id),h.type==="file"?t(h.id):e(h.id)},k=M=>{M.stopPropagation(),p(h.id),s(M,h.id)};return n.jsxs("div",{children:[n.jsxs("div",{className:`file-tree-item ${h.type} ${T?"selected":""} ${w?"active":""} ${y?"modified":""}`,style:{paddingLeft:`${x*16+8}px`},onClick:j,onContextMenu:k,children:[n.jsx("span",{className:"icon",children:h.type==="folder"?h.expanded?n.jsx(O.FolderOpenOutlined,{}):n.jsx(O.FolderOutlined,{}):n.jsx(O.FileTextOutlined,{})}),n.jsx("span",{className:"label",children:h.name}),y&&n.jsx("span",{className:"modified-indicator",children:"●"})]}),h.type==="folder"&&h.expanded&&h.children&&n.jsx("div",{children:Array.from(h.children.values()).map(M=>f(M,x+1))})]},h.id)},g=l.get("root");return g?n.jsx("div",{className:"file-tree",children:f(g)}):null},tt=({openFiles:l,fileSystem:t,activeFileId:e,onTabClick:s,onTabClose:a})=>n.jsx("div",{className:"tab-bar",children:Array.from(l.entries()).map(([c,i])=>{const p=t.get(c);if(!p)return null;const f=e===c,g=i.modified;return n.jsxs("div",{className:`tab ${f?"active":""} ${g?"modified":""}`,onClick:()=>s(c),children:[n.jsx("span",{className:"tab-label",children:p.name}),n.jsx("button",{className:"tab-close",onClick:h=>{h.stopPropagation(),a(c)},children:"×"})]},c)})}),ge=l=>{var s;const t=((s=l.split(".").pop())==null?void 0:s.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"},nt=(l,t,e)=>{const s=l.get(t);if(!(s!=null&&s.children))return null;for(const[a,c]of s.children)if(c.name===e)return c;return null},Q=(l,t,e,s="")=>{const a=l.get(t);if(!a||a.type!=="folder")return null;const c=`${a.fullPath}${e}`,i=c,p={id:i,name:e,fullPath:c,type:"file",content:s,parent:t,modified:!1,language:ge(e)};return l.set(i,p),a.children.set(i,p),p},ne=(l,t,e)=>{const s=l.get(t);if(!s||s.type!=="folder")return null;const a=`${s.fullPath}${e}/`,c=a,i={id:c,name:e,fullPath:a,type:"folder",children:new Map,parent:t,expanded:!1};return l.set(c,i),s.children.set(c,i),i},rt=(l,t,e)=>{const s=t.split("/").filter(Boolean);let a="root";for(let i=0;i<s.length-1;i++){const p=s[i],f=nt(l,a,p);if(f)a=f.id;else{const g=ne(l,a,p);g&&(a=g.id)}}const c=s[s.length-1];Q(l,a,c,e)},he=l=>{const t={id:l.id,name:l.name,fullPath:l.fullPath,type:l.type,content:l.content,children:new Map};if(l.type==="folder"&&l.children)for(const[e,s]of l.children){const a=he(s);t.children.set(a.id,a)}return t},me=(l,t,e)=>{const s=l.get(e);if(!(s!=null&&s.children))return!1;for(const[a,c]of s.children)if(c.name===t)return!0;return!1},st=(l,t,e)=>{const s=l.get(e);if(!(s!=null&&s.children))return t;let a=1,c=t;for(;me(l,c,e);){const i=t.includes(".")?t.substring(t.lastIndexOf(".")):"";c=`${t.includes(".")?t.substring(0,t.lastIndexOf(".")):t}_copy${a>1?a:""}${i}`,a++}return c},xe=(l,t,e)=>{const s=st(l,t.name,e);if(t.type==="file")Q(l,e,s,t.content||"");else if(t.children){const a=ne(l,e,s);if(a)for(const[c,i]of t.children)xe(l,i,a.id)}},we=(l,t)=>{const e=l.get(t);if(e){if(e.type==="folder"&&e.children)for(const[s]of e.children)we(l,s);l.delete(t)}},ot=()=>{const{files:l,setFile:t}=K(),[e,s]=r.useState(new Map),[a,c]=r.useState(new Map),[i,p]=r.useState(null),[f,g]=r.useState({x:0,y:0,targetId:"",show:!1}),[h,x]=r.useState(null),[T,w]=r.useState(300),[y,j]=r.useState(!1),[k,W]=r.useState("file"),[P,M]=r.useState("root"),[q,Y]=r.useState(""),[re,_]=r.useState(!1),[U,G]=r.useState(""),[V,Z]=r.useState(""),[se,H]=r.useState(!1),[oe,ae]=r.useState(""),[X,v]=r.useState("file"),[E,I]=r.useState(""),L=r.useRef(null),N=r.useRef(null),S=r.useRef(!1);r.useEffect(()=>{const o={id:"root",name:"root",fullPath:"/",type:"folder",children:new Map,expanded:!0},d=new Map;if(d.set("root",o),Object.entries(l).forEach(([u,m])=>{rt(d,u,m)}),Object.keys(l).length===0){Q(d,"root","index.js",'console.log("Hello World!");');const u=ne(d,"root","src");u&&(Q(d,u.id,"app.js",`export default function App() {
return "Hello from App";
}`),Q(d,u.id,"utils.js",`export const utils = {
formatDate: (date) => {
return date.toLocaleDateString();
}
};`))}s(d)},[]);const R=r.useCallback(o=>{s(d=>{const u=new Map(d),m=u.get(o);if(!m||o==="root")return d;if(m.type==="folder"&&m.children)for(const[F]of m.children)we(u,F);m.type==="file"&&(c(F=>{var B;const $=new Map(F),D=$.get(o);return D&&((B=D.model)==null||B.dispose(),$.delete(o)),$}),i===o&&p(null));const C=u.get(m.parent);return C!=null&&C.children&&C.children.delete(o),u.delete(o),u})},[i]),A=r.useCallback((o,d)=>{if(!d.trim()||o==="root")return!1;let u=!0;return s(m=>{const C=new Map(m),F=C.get(o);if(!F)return m;const $=C.get(F.parent);if($!=null&&$.children){for(const[D,B]of $.children)if(B.id!==o&&B.name===d)return b.message.error("该名称已存在"),u=!1,m}if(F.name=d,F.type==="file"){F.language=ge(d);const D=a.get(o);D&&N.current&&N.current.editor.setModelLanguage(D.model,F.language)}return C}),u},[a]),z=r.useCallback(o=>{const d=e.get(o);d&&o!=="root"&&x(he(d))},[e]),le=r.useCallback(o=>{h&&s(d=>{const u=new Map(d),m=u.get(o);return!m||m.type!=="folder"?d:(xe(u,h,o),u)})},[h]),ce=r.useCallback(o=>{const d=e.get(o);if(!(!d||d.type!=="file")){if(a.has(o)){p(o);return}c(u=>{const m=new Map(u),C={id:o,model:null,modified:!1,viewState:null};return m.set(o,C),m}),p(o)}},[e,a]),ye=r.useCallback(o=>{const d=a.get(o);if(d){if(d.modified){const u=e.get(o);if(!window.confirm(`"${u==null?void 0:u.name}" 有未保存的更改,确定要关闭吗?`))return}if(c(u=>{const m=new Map(u),C=m.get(o);return C!=null&&C.model&&C.model.dispose(),m.delete(o),m}),i===o){const u=Array.from(a.keys()).filter(m=>m!==o);p(u.length>0?u[u.length-1]:null)}}},[a,e,i]),be=r.useCallback((o,d)=>{s(u=>{var F;const m=new Map(u),C=m.get(o);return C&&(C.content=d),t&&t(((F=m.get(o))==null?void 0:F.fullPath)||"",d),m}),c(u=>{const m=new Map(u),C=m.get(o);return C&&(C.modified=!1),m})},[t]),ee=r.useCallback(o=>{if(!L.current||!N.current)return;const d=e.get(o);if(!d||d.type!=="file")return;let u=a.get(o);if(!u)return;if(!(u!=null&&u.model)){const C=N.current.editor.createModel(d.content||"",d.language,N.current.Uri.file(d.fullPath));C._fileId=o,c(F=>{const $=new Map(F),D=$.get(o);return D?(D.model=C,$.set(o,D),$):F}),u={...u,model:C}}if(L.current.getModel()&&i&&i!==o){const C=a.get(i);C&&(C.viewState=L.current.saveViewState())}L.current.setModel(u.model),u.viewState&&L.current.restoreViewState(u.viewState),L.current.focus()},[e,a,i]),je=(o,d)=>{L.current=o,N.current=d,o.addCommand(d.KeyMod.CtrlCmd|d.KeyCode.KeyS,()=>{const u=o.getValue(),m=o.getModel()._fileId;be(m,u)}),o.onDidChangeModelContent(()=>{var m;const u=o.getModel()._fileId;if(u){const C=o.getValue(),F=((m=e.get(u))==null?void 0:m.content)||"";c($=>{const D=new Map($),B=D.get(u);return B&&(B.modified=C!==F),D})}}),i&&ee(i)};r.useEffect(()=>{i?ee(i):L.current&&(L.current=null)},[i,ee]);const ve=r.useCallback(o=>{s(d=>{const u=new Map(d),m=u.get(o);return m&&m.type==="folder"&&(m.expanded=!m.expanded),u})},[]),te=(o,d)=>{W(o),M(d),Y(""),j(!0)},Ce=()=>{if(!q.trim()){b.message.error("名称不能为空");return}if(me(e,q,P)){b.message.error("该名称已存在");return}s(o=>{const d=new Map(o);if(k==="file"){const m=Q(d,P,q);m&&setTimeout(()=>ce(m.id),0)}else ne(d,P,q);const u=d.get(P);return u&&u.type==="folder"&&(u.expanded=!0),d}),j(!1),b.message.success(`${k==="file"?"文件":"文件夹"}创建成功`)},ke=o=>{const d=e.get(o);d&&(G(o),Z(d.name),_(!0))},Se=()=>{if(!V.trim()){b.message.error("名称不能为空");return}const o=e.get(U);if(!o)return;if(V===o.name){_(!1);return}A(U,V)&&(_(!1),b.message.success("重命名成功"))},Me=o=>{const d=e.get(o);d&&(ae(o),v(d.type),I(d.name),H(!0))},Ee=()=>{R(oe),H(!1),b.message.success(`${X==="file"?"文件":"文件夹"}已删除`)},Fe=r.useCallback((o,d)=>{const u=d||"root";switch(o){case"newFile":te("file",u);break;case"newFolder":te("folder",u);break;case"rename":u!=="root"&&ke(u);break;case"copy":u!=="root"&&(z(u),b.message.success("已复制到剪贴板"));break;case"paste":le(u),b.message.success("粘贴成功");break;case"delete":u!=="root"&&Me(u);break}g(m=>({...m,show:!1}))},[z,le]),Oe=r.useCallback((o,d)=>{o.preventDefault(),g({x:o.clientX,y:o.clientY,targetId:d,show:!0})},[]),Pe=r.useCallback(()=>{g(o=>({...o,show:!1}))},[]),Re=()=>{S.current=!0;const o=u=>{if(!S.current)return;const m=Math.max(200,Math.min(u.clientX,window.innerWidth-200));w(m)},d=()=>{S.current=!1,i&&ee(i),document.removeEventListener("mousemove",o),document.removeEventListener("mouseup",d)};document.addEventListener("mousemove",o),document.addEventListener("mouseup",d)},ie=i?e.get(i):null,ue=a.size>0,Te=[{key:"newFile",label:"新建文件",icon:n.jsx(O.FileAddOutlined,{})},{key:"newFolder",label:"新建文件夹",icon:n.jsx(O.FolderAddOutlined,{})},{type:"divider"},{key:"rename",label:"重命名",icon:n.jsx(O.EditOutlined,{}),disabled:f.targetId==="root"},{key:"copy",label:"复制",icon:n.jsx(O.CopyOutlined,{}),disabled:f.targetId==="root"},{key:"paste",label:"粘贴",icon:n.jsx(O.ScissorOutlined,{}),disabled:!h},{type:"divider"},{key:"delete",label:"删除",icon:n.jsx(O.DeleteOutlined,{}),danger:!0,disabled:f.targetId==="root"}];return n.jsxs("div",{className:"code-editor",onClick:Pe,children:[n.jsxs("div",{className:"editor-container",children:[n.jsxs("div",{className:"file-panel",style:{width:T},children:[n.jsxs("div",{className:"file-panel-header",children:[n.jsx("h3",{children:"文件资源管理器"}),n.jsx("div",{className:"toolbar",children:n.jsxs(b.Space,{size:"small",children:[n.jsx(b.Tooltip,{title:"新建文件",children:n.jsx(O.FileAddOutlined,{onClick:()=>te("file","root")})}),n.jsx(b.Tooltip,{title:"新建文件夹",children:n.jsx(O.FolderAddOutlined,{onClick:()=>te("folder","root")})})]})})]}),n.jsx(et,{fileSystem:e,onFileClick:ce,onFolderToggle:ve,onContextMenu:Oe,activeFileId:i,openFiles:a})]}),n.jsx("div",{className:"resizer",onMouseDown:Re}),n.jsxs("div",{className:"editor-panel",children:[ue&&n.jsx(tt,{openFiles:a,fileSystem:e,activeFileId:i,onTabClick:p,onTabClose:ye}),n.jsx("div",{className:"monaco-container",children:ue?n.jsx(Ie,{height:"100%",theme:"vs-dark",language:(ie==null?void 0:ie.language)||"javascript",onMount:je,options:{automaticLayout:!0,fontSize:14,wordWrap:"on",minimap:{enabled:!0},scrollBeyondLastLine:!1,renderLineHighlight:"all",selectOnLineNumbers:!0,tabSize:2,insertSpaces:!0}}):n.jsxs("div",{className:"welcome-screen",children:[n.jsx("h2",{children:"简陋的代码编辑器"}),n.jsx("p",{children:"选择一个文件开始编辑,或创建一个新文件"})]})})]})]}),f.show&&n.jsx(b.Dropdown,{open:f.show,menu:{items:Te,onClick:({key:o})=>Fe(o,f.targetId)},trigger:["contextMenu"],overlayStyle:{position:"fixed",left:`${f.x}px`,top:`${f.y}px`},children:n.jsx("div",{style:{position:"fixed",left:f.x,top:f.y,width:0,height:0}})}),n.jsx(b.Modal,{title:`新建${k==="file"?"文件":"文件夹"}`,open:y,onOk:Ce,onCancel:()=>j(!1),okText:"确定",cancelText:"取消",children:n.jsx(b.Input,{placeholder:`请输入${k==="file"?"文件":"文件夹"}名称`,value:q,onChange:o=>Y(o.target.value),autoFocus:!0})}),n.jsx(b.Modal,{title:"重命名",open:re,onOk:Se,onCancel:()=>_(!1),okText:"确定",cancelText:"取消",children:n.jsx(b.Input,{placeholder:"请输入新名称",value:V,onChange:o=>Z(o.target.value),autoFocus:!0})}),n.jsx(b.Modal,{title:"确认删除",open:se,onOk:Ee,onCancel:()=>H(!1),okText:"确定",cancelText:"取消",children:n.jsxs("p",{children:["确定要删除",X==="folder"?"文件夹":"文件",' "',E,'"',X==="folder"?"及其所有内容":"","吗?"]})})]})};exports.CodeEditor=ot;exports.EndSwitch=Xe;exports.ErrorFix=Qe;exports.Gallery=Ge;exports.Preview=Ue;exports.SessionLayout=Ze;exports.SessionProvider=Ke;exports.TerminalPanel=He;exports.WebContainerProvider=Le;exports.useSession=K;exports.useWebContainer=J;
//# sourceMappingURL=index.js.map