the universal sandbox runtime for agents and humans. pocketenv.io
sandbox openclaw agent claude-code vercel-sandbox deno-sandbox cloudflare-sandbox atproto sprites daytona
at main 160 lines 4.8 kB view raw
1import { useEffect, useState } from "react"; 2import { createPortal } from "react-dom"; 3import Terminal from "../../../../components/terminal"; 4 5export type TerminalModalProps = { 6 isOpen: boolean; 7 onClose: () => void; 8 sandboxId: string; 9 worker: string; 10 title?: string; 11 isCloudflare?: boolean; 12 isTty?: boolean; 13 pty?: boolean; 14}; 15 16function TerminalModal({ 17 isOpen, 18 onClose, 19 title, 20 worker, 21 sandboxId, 22 isCloudflare, 23 isTty, 24 pty, 25}: TerminalModalProps) { 26 const [isFullscreen, setIsFullscreen] = useState(false); 27 28 useEffect(() => { 29 const handleEscapeKey = (event: KeyboardEvent) => { 30 if (event.key === "Escape" && isOpen) { 31 if (isFullscreen) { 32 setIsFullscreen(false); 33 (document.activeElement as HTMLElement)?.blur(); 34 } else { 35 onClose(); 36 } 37 } 38 }; 39 40 document.addEventListener("keydown", handleEscapeKey); 41 return () => { 42 document.removeEventListener("keydown", handleEscapeKey); 43 }; 44 }, [isOpen, isFullscreen, onClose]); 45 46 // Reset fullscreen when modal closes 47 useEffect(() => { 48 if (!isOpen) { 49 // eslint-disable-next-line react-hooks/set-state-in-effect 50 setIsFullscreen(false); 51 } 52 }, [isOpen]); 53 54 const handleBackdropClick = (e: React.MouseEvent<HTMLDivElement>) => { 55 e.stopPropagation(); 56 if (e.target === e.currentTarget) { 57 onClose(); 58 } 59 }; 60 61 const handleContentClick = (e: React.MouseEvent<HTMLDivElement>) => { 62 e.stopPropagation(); 63 }; 64 65 const handleCloseButton = (e: React.MouseEvent<HTMLButtonElement>) => { 66 e.stopPropagation(); 67 onClose(); 68 }; 69 70 const handleFullscreenToggle = (e: React.MouseEvent<HTMLButtonElement>) => { 71 e.stopPropagation(); 72 setIsFullscreen((prev) => !prev); 73 setTimeout(() => window.dispatchEvent(new Event("resize")), 50); 74 }; 75 76 if (!isOpen) return null; 77 78 return createPortal( 79 <> 80 <div 81 className="overlay modal modal-middle overlay-open:opacity-100 overlay-open:duration-300 open opened" 82 role="dialog" 83 style={{ outline: "none", zIndex: 80 }} 84 onClick={handleBackdropClick} 85 onMouseDown={handleBackdropClick} 86 > 87 <div 88 className={`overlay-animation-target modal-dialog overlay-open:duration-300 transition-all ease-out ${ 89 isFullscreen 90 ? "fixed inset-0 m-0! max-w-none! w-screen! h-screen! rounded-none!" 91 : "modal-dialog-xl overlay-open:mt-4 mt-12" 92 }`} 93 onClick={handleContentClick} 94 onMouseDown={handleContentClick} 95 style={isFullscreen ? { maxHeight: "100vh" } : undefined} 96 > 97 <div 98 className={`modal-content ${isFullscreen ? "rounded-none! h-full!" : ""}`} 99 > 100 <div className="modal-header"> 101 <div className="flex-1 text-center">{title}</div> 102 <button 103 type="button" 104 className="btn btn-text btn-circle btn-sm absolute end-10 top-3" 105 aria-label={isFullscreen ? "Exit fullscreen" : "Fullscreen"} 106 onClick={handleFullscreenToggle} 107 onMouseDown={(e) => e.stopPropagation()} 108 > 109 <span 110 className={ 111 isFullscreen 112 ? "icon-[mingcute--fullscreen-exit-2-line] size-4" 113 : "icon-[mingcute--fullscreen-2-line] size-4" 114 } 115 ></span> 116 </button> 117 <button 118 type="button" 119 className="btn btn-text btn-circle btn-sm absolute end-3 top-3" 120 aria-label="Close" 121 onClick={handleCloseButton} 122 onMouseDown={(e) => e.stopPropagation()} 123 > 124 <span className="icon-[tabler--x] size-4"></span> 125 </button> 126 </div> 127 <div 128 className="modal-body p-0 pl-2 overflow-y-hidden" 129 style={ 130 isFullscreen 131 ? { height: "100vh", paddingBottom: 20 } 132 : { height: "60vh" } 133 } 134 > 135 <Terminal 136 sandboxId={sandboxId} 137 onClose={onClose} 138 isCloudflare={isCloudflare} 139 worker={worker} 140 isTty={isTty} 141 pty={pty} 142 /> 143 </div> 144 </div> 145 </div> 146 </div> 147 148 <div 149 data-overlay-backdrop-template="" 150 style={{ zIndex: 79 }} 151 className="overlay-backdrop transition duration-300 fixed inset-0 bg-base-300/60 overflow-y-auto opacity-75" 152 onClick={handleBackdropClick} 153 onMouseDown={(e) => e.stopPropagation()} 154 ></div> 155 </>, 156 document.body, 157 ); 158} 159 160export default TerminalModal;