Compare commits

..

13 Commits

Author SHA1 Message Date
James Murdza
bd6284df8f docs: add information about E2B 2024-06-19 21:57:40 -04:00
James Murdza
c262fb2a31 fix: add error handling to the backend 2024-06-19 21:57:40 -04:00
James Murdza
ed709210e3 fix: remove hardcoded reference to localhost 2024-06-19 21:57:40 -04:00
Akhilesh Rangani
97c8598717 fix: count only the current user's sandboxes towards the limit 2024-06-19 21:57:40 -04:00
James Murdza
9ec59bc781 fix: use the container URL for the preview panel 2024-06-19 21:57:40 -04:00
James Murdza
687416e6e9 fix: set project file permissions so that they belong to the terminal user 2024-06-19 21:57:40 -04:00
James Murdza
006c5cea66 fix: sync files to container instead of local file system 2024-06-19 21:57:40 -04:00
James Murdza
869ae6c148 fix: ensure container remains open until all owner connections are closed 2024-06-19 21:57:40 -04:00
James Murdza
7353e88567 fix: wait until terminals are killed to close the container 2024-06-19 21:57:40 -04:00
Akhilesh Rangani
a0fb905a04 fix: move socket connection to useRef 2024-06-19 21:56:18 -04:00
Akhilesh Rangani
0df074924f added debounced function in the editor 2024-06-14 12:10:01 -04:00
James Murdza
e5b320d1c5 feat: replace node-pty with E2B sandboxes 2024-06-14 12:02:20 -04:00
James Murdza
b561f1e962 chore: rename utils.ts to fileoperations.ts 2024-06-14 11:57:32 -04:00
3 changed files with 39 additions and 72 deletions

View File

@@ -192,8 +192,6 @@ io.on("connection", async (socket) => {
// todo: send diffs + debounce for efficiency
socket.on("saveFile", async (fileId: string, body: string) => {
if (!fileId) return; // handles saving when no file is open
try {
if (Buffer.byteLength(body, "utf-8") > MAX_BODY_SIZE) {
socket.emit(

View File

@@ -63,6 +63,14 @@ const CodeEditor = dynamic(() => import("@/components/editor"), {
loading: () => <Loading />,
})
function getReactDefinitionFile() {
const reactDefinitionFile = fs.readFileSync(
"node_modules/@types/react/index.d.ts",
"utf8"
)
return reactDefinitionFile
}
export default async function CodePage({ params }: { params: { id: string } }) {
const user = await currentUser()
const sandboxId = params.id
@@ -86,6 +94,8 @@ export default async function CodePage({ params }: { params: { id: string } }) {
return notFound()
}
const reactDefinitionFile = getReactDefinitionFile()
return (
<div className="overflow-hidden overscroll-none w-screen flex flex-col h-screen bg-background">
<Room id={sandboxId}>
@@ -94,6 +104,7 @@ export default async function CodePage({ params }: { params: { id: string } }) {
<CodeEditor
userData={userData}
sandboxData={sandboxData}
reactDefinitionFile={reactDefinitionFile}
/>
</div>
</Room>

View File

@@ -35,9 +35,11 @@ import { ImperativePanelHandle } from "react-resizable-panels"
export default function CodeEditor({
userData,
sandboxData,
reactDefinitionFile,
}: {
userData: User
sandboxData: Sandbox
reactDefinitionFile: string
}) {
const socketRef = useRef<Socket | null>(null);
@@ -102,16 +104,6 @@ export default function CodeEditor({
const room = useRoom()
const [provider, setProvider] = useState<TypedLiveblocksProvider>()
const userInfo = useSelf((me) => me.info)
// Liveblocks providers map to prevent reinitializing providers
type ProviderData = {
provider: LiveblocksProvider<never, never, never, never>;
yDoc: Y.Doc;
yText: Y.Text;
binding?: MonacoBinding;
onSync: (isSynced: boolean) => void;
};
const providersMap = useRef(new Map<string, ProviderData>());
// Refs for libraries / features
const editorContainerRef = useRef<HTMLDivElement>(null)
@@ -340,77 +332,43 @@ export default function CodeEditor({
if (!editorRef || !tab || !model) return
let providerData: ProviderData;
// When a file is opened for the first time, create a new provider and store in providersMap.
if (!providersMap.current.has(tab.id)) {
const yDoc = new Y.Doc();
const yText = yDoc.getText(tab.id);
const yProvider = new LiveblocksProvider(room, yDoc);
const yDoc = new Y.Doc()
const yText = yDoc.getText(tab.id)
const yProvider: any = new LiveblocksProvider(room, yDoc)
// Inserts the file content into the editor once when the tab is changed.
const onSync = (isSynced: boolean) => {
if (isSynced) {
const text = yText.toString()
if (text === "") {
if (activeFileContent) {
yText.insert(0, activeFileContent)
} else {
setTimeout(() => {
yText.insert(0, editorRef.getValue())
}, 0)
}
const onSync = (isSynced: boolean) => {
if (isSynced) {
const text = yText.toString()
if (text === "") {
if (activeFileContent) {
yText.insert(0, activeFileContent)
} else {
setTimeout(() => {
yText.insert(0, editorRef.getValue())
}, 0)
}
}
}
yProvider.on("sync", onSync)
// Save the provider to the map.
providerData = { provider: yProvider, yDoc, yText, onSync };
providersMap.current.set(tab.id, providerData);
} else {
// When a tab is opened that has been open before, reuse the existing provider.
providerData = providersMap.current.get(tab.id)!;
}
yProvider.on("sync", onSync)
setProvider(yProvider)
const binding = new MonacoBinding(
providerData.yText,
yText,
model,
new Set([editorRef]),
providerData.provider.awareness as unknown as Awareness
);
providerData.binding = binding;
setProvider(providerData.provider);
yProvider.awareness as Awareness
)
return () => {
// Cleanup logic
if (binding) {
binding.destroy();
}
if (providerData.binding) {
providerData.binding = undefined;
}
};
}, [room, activeFileContent]);
// Added this effect to clean up when the component unmounts
useEffect(() => {
return () => {
// Clean up all providers when the component unmounts
providersMap.current.forEach((data) => {
if (data.binding) {
data.binding.destroy();
}
data.provider.disconnect();
data.yDoc.destroy();
});
providersMap.current.clear();
};
}, []);
yDoc.destroy()
yProvider.destroy()
binding.destroy()
yProvider.off("sync", onSync)
}
}, [editorRef, room, activeFileContent])
// Connection/disconnection effect
useEffect(() => {