Compare commits

..

2 Commits

10 changed files with 841 additions and 300 deletions

3
.dockerignore Normal file
View File

@ -0,0 +1,3 @@
frontend/node_modules
backend/server/.env
backend/server/node_modules

View File

@ -1,197 +1,168 @@
{ {
"version": "5", "version": "5",
"dialect": "sqlite", "dialect": "sqlite",
"id": "70e3d022-aed8-4464-9063-c92113b4ebe2", "id": "6570ba20-a672-400c-8147-7ba533784918",
"prevId": "00000000-0000-0000-0000-000000000000", "prevId": "00000000-0000-0000-0000-000000000000",
"tables": { "tables": {
"sandbox": { "sandbox": {
"name": "sandbox", "name": "sandbox",
"columns": { "columns": {
"id": { "id": {
"name": "id", "name": "id",
"type": "text", "type": "text",
"primaryKey": true, "primaryKey": true,
"notNull": true, "notNull": true,
"autoincrement": false "autoincrement": false
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"type": {
"name": "type",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"visibility": {
"name": "visibility",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"createdAt": {
"name": "createdAt",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"user_id": {
"name": "user_id",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
}, },
"indexes": { "name": {
"sandbox_id_unique": { "name": "name",
"name": "sandbox_id_unique", "type": "text",
"columns": [ "primaryKey": false,
"id" "notNull": true,
], "autoincrement": false
"isUnique": true
}
}, },
"foreignKeys": { "type": {
"sandbox_user_id_user_id_fk": { "name": "type",
"name": "sandbox_user_id_user_id_fk", "type": "text",
"tableFrom": "sandbox", "primaryKey": false,
"tableTo": "user", "notNull": true,
"columnsFrom": [ "autoincrement": false
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
}, },
"compositePrimaryKeys": {}, "visibility": {
"uniqueConstraints": {} "name": "visibility",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"user_id": {
"name": "user_id",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
}, },
"user": { "indexes": {
"name": "user", "sandbox_id_unique": {
"columns": { "name": "sandbox_id_unique",
"id": { "columns": [
"name": "id", "id"
"type": "text", ],
"primaryKey": true, "isUnique": true
"notNull": true, }
"autoincrement": false
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"email": {
"name": "email",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"image": {
"name": "image",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"generations": {
"name": "generations",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false,
"default": 0
}
},
"indexes": {
"user_id_unique": {
"name": "user_id_unique",
"columns": [
"id"
],
"isUnique": true
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
}, },
"users_to_sandboxes": { "foreignKeys": {
"name": "users_to_sandboxes", "sandbox_user_id_user_id_fk": {
"columns": { "name": "sandbox_user_id_user_id_fk",
"userId": { "tableFrom": "sandbox",
"name": "userId", "tableTo": "user",
"type": "text", "columnsFrom": [
"primaryKey": false, "user_id"
"notNull": true, ],
"autoincrement": false "columnsTo": [
}, "id"
"sandboxId": { ],
"name": "sandboxId", "onDelete": "no action",
"type": "text", "onUpdate": "no action"
"primaryKey": false, }
"notNull": true, },
"autoincrement": false "compositePrimaryKeys": {},
}, "uniqueConstraints": {}
"sharedOn": {
"name": "sharedOn",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {
"users_to_sandboxes_userId_user_id_fk": {
"name": "users_to_sandboxes_userId_user_id_fk",
"tableFrom": "users_to_sandboxes",
"tableTo": "user",
"columnsFrom": [
"userId"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
},
"users_to_sandboxes_sandboxId_sandbox_id_fk": {
"name": "users_to_sandboxes_sandboxId_sandbox_id_fk",
"tableFrom": "users_to_sandboxes",
"tableTo": "sandbox",
"columnsFrom": [
"sandboxId"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
}
}, },
"enums": {}, "user": {
"_meta": { "name": "user",
"schemas": {}, "columns": {
"tables": {}, "id": {
"columns": {} "name": "id",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"email": {
"name": "email",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {
"user_id_unique": {
"name": "user_id_unique",
"columns": [
"id"
],
"isUnique": true
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"users_to_sandboxes": {
"name": "users_to_sandboxes",
"columns": {
"userId": {
"name": "userId",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"sandboxId": {
"name": "sandboxId",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {
"users_to_sandboxes_userId_user_id_fk": {
"name": "users_to_sandboxes_userId_user_id_fk",
"tableFrom": "users_to_sandboxes",
"tableTo": "user",
"columnsFrom": [
"userId"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
},
"users_to_sandboxes_sandboxId_sandbox_id_fk": {
"name": "users_to_sandboxes_sandboxId_sandbox_id_fk",
"tableFrom": "users_to_sandboxes",
"tableTo": "sandbox",
"columnsFrom": [
"sandboxId"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
} }
} },
"enums": {},
"_meta": {
"schemas": {},
"tables": {},
"columns": {}
}
}

View File

@ -0,0 +1,175 @@
{
"version": "5",
"dialect": "sqlite",
"id": "9f64104a-4954-40c0-8155-17755ea0a243",
"prevId": "6570ba20-a672-400c-8147-7ba533784918",
"tables": {
"sandbox": {
"name": "sandbox",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"type": {
"name": "type",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"visibility": {
"name": "visibility",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"user_id": {
"name": "user_id",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {
"sandbox_id_unique": {
"name": "sandbox_id_unique",
"columns": [
"id"
],
"isUnique": true
}
},
"foreignKeys": {
"sandbox_user_id_user_id_fk": {
"name": "sandbox_user_id_user_id_fk",
"tableFrom": "sandbox",
"tableTo": "user",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"user": {
"name": "user",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"email": {
"name": "email",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"image": {
"name": "image",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
}
},
"indexes": {
"user_id_unique": {
"name": "user_id_unique",
"columns": [
"id"
],
"isUnique": true
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"users_to_sandboxes": {
"name": "users_to_sandboxes",
"columns": {
"userId": {
"name": "userId",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"sandboxId": {
"name": "sandboxId",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {
"users_to_sandboxes_userId_user_id_fk": {
"name": "users_to_sandboxes_userId_user_id_fk",
"tableFrom": "users_to_sandboxes",
"tableTo": "user",
"columnsFrom": [
"userId"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
},
"users_to_sandboxes_sandboxId_sandbox_id_fk": {
"name": "users_to_sandboxes_sandboxId_sandbox_id_fk",
"tableFrom": "users_to_sandboxes",
"tableTo": "sandbox",
"columnsFrom": [
"sandboxId"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
}
},
"enums": {},
"_meta": {
"schemas": {},
"tables": {},
"columns": {}
}
}

View File

@ -0,0 +1,168 @@
{
"version": "5",
"dialect": "sqlite",
"id": "5baf10d6-7697-42ba-a11a-ee4c7bd7e91e",
"prevId": "9f64104a-4954-40c0-8155-17755ea0a243",
"tables": {
"sandbox": {
"name": "sandbox",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"type": {
"name": "type",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"visibility": {
"name": "visibility",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"user_id": {
"name": "user_id",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {
"sandbox_id_unique": {
"name": "sandbox_id_unique",
"columns": [
"id"
],
"isUnique": true
}
},
"foreignKeys": {
"sandbox_user_id_user_id_fk": {
"name": "sandbox_user_id_user_id_fk",
"tableFrom": "sandbox",
"tableTo": "user",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"user": {
"name": "user",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"email": {
"name": "email",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {
"user_id_unique": {
"name": "user_id_unique",
"columns": [
"id"
],
"isUnique": true
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"users_to_sandboxes": {
"name": "users_to_sandboxes",
"columns": {
"userId": {
"name": "userId",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"sandboxId": {
"name": "sandboxId",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {
"users_to_sandboxes_userId_user_id_fk": {
"name": "users_to_sandboxes_userId_user_id_fk",
"tableFrom": "users_to_sandboxes",
"tableTo": "user",
"columnsFrom": [
"userId"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
},
"users_to_sandboxes_sandboxId_sandbox_id_fk": {
"name": "users_to_sandboxes_sandboxId_sandbox_id_fk",
"tableFrom": "users_to_sandboxes",
"tableTo": "sandbox",
"columnsFrom": [
"sandboxId"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
}
},
"enums": {},
"_meta": {
"schemas": {},
"tables": {},
"columns": {}
}
}

View File

@ -0,0 +1,175 @@
{
"version": "5",
"dialect": "sqlite",
"id": "37e38b82-1494-4818-8c26-b9024cce3fa9",
"prevId": "5baf10d6-7697-42ba-a11a-ee4c7bd7e91e",
"tables": {
"sandbox": {
"name": "sandbox",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"type": {
"name": "type",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"visibility": {
"name": "visibility",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"user_id": {
"name": "user_id",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {
"sandbox_id_unique": {
"name": "sandbox_id_unique",
"columns": [
"id"
],
"isUnique": true
}
},
"foreignKeys": {
"sandbox_user_id_user_id_fk": {
"name": "sandbox_user_id_user_id_fk",
"tableFrom": "sandbox",
"tableTo": "user",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"user": {
"name": "user",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"email": {
"name": "email",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"image": {
"name": "image",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
}
},
"indexes": {
"user_id_unique": {
"name": "user_id_unique",
"columns": [
"id"
],
"isUnique": true
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"users_to_sandboxes": {
"name": "users_to_sandboxes",
"columns": {
"userId": {
"name": "userId",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"sandboxId": {
"name": "sandboxId",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {
"users_to_sandboxes_userId_user_id_fk": {
"name": "users_to_sandboxes_userId_user_id_fk",
"tableFrom": "users_to_sandboxes",
"tableTo": "user",
"columnsFrom": [
"userId"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
},
"users_to_sandboxes_sandboxId_sandbox_id_fk": {
"name": "users_to_sandboxes_sandboxId_sandbox_id_fk",
"tableFrom": "users_to_sandboxes",
"tableTo": "sandbox",
"columnsFrom": [
"sandboxId"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
}
},
"enums": {},
"_meta": {
"schemas": {},
"tables": {},
"columns": {}
}
}

View File

@ -1,13 +1,55 @@
{ {
"version": "5", "version": "5",
"dialect": "sqlite", "dialect": "sqlite",
"entries": [ "entries": [
{ {
"idx": 0, "idx": 0,
"version": "5", "version": "5",
"when": 1718032216030, "when": 1714540200800,
"tag": "0000_melodic_the_captain", "tag": "0000_big_rogue",
"breakpoints": true "breakpoints": true
} },
] {
} "idx": 1,
"version": "5",
"when": 1714541190588,
"tag": "0001_empty_black_knight",
"breakpoints": true
},
{
"idx": 2,
"version": "5",
"when": 1714541209173,
"tag": "0002_sour_ego",
"breakpoints": true
},
{
"idx": 3,
"version": "5",
"when": 1714541233589,
"tag": "0003_pale_overlord",
"breakpoints": true
},
{
"idx": 4,
"version": "5",
"when": 1714565073180,
"tag": "0004_cuddly_wolf_cub",
"breakpoints": true
},
{
"idx": 5,
"version": "5",
"when": 1714950365718,
"tag": "0005_last_the_twelve",
"breakpoints": true
},
{
"idx": 6,
"version": "5",
"when": 1716432225404,
"tag": "0006_lively_mattie_franklin",
"breakpoints": true
}
]
}

View File

@ -31,7 +31,7 @@ export const sandbox = sqliteTable("sandbox", {
createdAt: integer("createdAt", { mode: "timestamp_ms" }), createdAt: integer("createdAt", { mode: "timestamp_ms" }),
userId: text("user_id") userId: text("user_id")
.notNull() .notNull()
.references(() => user.id, { onDelete: "cascade" }), .references(() => user.id),
}); });
export type Sandbox = typeof sandbox.$inferSelect; export type Sandbox = typeof sandbox.$inferSelect;

41
dockerfile Normal file
View File

@ -0,0 +1,41 @@
# docker build -t myimage .
# docker run --env-file backend/server/.env -p 3000:3000 -p 4000:4000 myimage
FROM node:20
# Security: Drop all capabilities
USER root
RUN apt-get update && apt-get install -y libcap2-bin
RUN setcap cap_net_bind_service=+ep /usr/local/bin/node
# Build backend
WORKDIR /backend
COPY backend/server/package*.json ./
RUN npm install
COPY backend/server/ .
RUN npm run build
# Build frontend
WORKDIR /frontend
COPY frontend/package*.json ./
RUN npm install
COPY frontend .
RUN npm run build
# Set working directory to the root directory
WORKDIR /
# Security: Create non-root user and assign ownership
RUN useradd -m appuser
RUN mkdir -p /backend/projects && chown -R appuser:appuser /backend/projects
USER appuser
# Start both backend and frontend
ENV BACKEND_PORT=4000
ENV FRONTEND_PORT=3000
EXPOSE 5173
EXPOSE $BACKEND_PORT
EXPOSE $FRONTEND_PORT
CMD ["sh", "-c", "cd /backend && PORT=$BACKEND_PORT npm run start & cd /frontend && PORT=$FRONTEND_PORT npm run start"]

View File

@ -1,9 +1,9 @@
"use client" "use client"
import { SetStateAction, useCallback, useEffect, useRef, useState } from "react" import { useEffect, useRef, useState } from "react"
import monaco from "monaco-editor" import monaco from "monaco-editor"
import Editor, { BeforeMount, OnMount } from "@monaco-editor/react" import Editor, { BeforeMount, OnMount } from "@monaco-editor/react"
import { Socket, io } from "socket.io-client" import { io } from "socket.io-client"
import { toast } from "sonner" import { toast } from "sonner"
import { useClerk } from "@clerk/nextjs" import { useClerk } from "@clerk/nextjs"
@ -23,7 +23,7 @@ import Tab from "../ui/tab"
import Sidebar from "./sidebar" import Sidebar from "./sidebar"
import GenerateInput from "./generate" import GenerateInput from "./generate"
import { Sandbox, User, TFile, TFolder, TTab } from "@/lib/types" import { Sandbox, User, TFile, TFolder, TTab } from "@/lib/types"
import { addNew, processFileType, validateName, debounce } from "@/lib/utils" import { addNew, processFileType, validateName } from "@/lib/utils"
import { Cursors } from "./live/cursors" import { Cursors } from "./live/cursors"
import { Terminal } from "@xterm/xterm" import { Terminal } from "@xterm/xterm"
import DisableAccessModal from "./live/disableModal" import DisableAccessModal from "./live/disableModal"
@ -41,16 +41,12 @@ export default function CodeEditor({
sandboxData: Sandbox sandboxData: Sandbox
reactDefinitionFile: string reactDefinitionFile: string
}) { }) {
const socketRef = useRef<Socket | null>(null); const socket = io(
`http://localhost:${process.env.NEXT_PUBLIC_SERVER_PORT}?userId=${userData.id}&sandboxId=${sandboxData.id}`,
// Initialize socket connection if it doesn't exist {
if (!socketRef.current) { timeout: 2000,
socketRef.current = io( }
`http://localhost:${process.env.NEXT_PUBLIC_SERVER_PORT}?userId=${userData.id}&sandboxId=${sandboxData.id}`, )
{
timeout: 2000,
}
);}
const [isPreviewCollapsed, setIsPreviewCollapsed] = useState(true) const [isPreviewCollapsed, setIsPreviewCollapsed] = useState(true)
const [disableAccess, setDisableAccess] = useState({ const [disableAccess, setDisableAccess] = useState({
@ -294,33 +290,26 @@ export default function CodeEditor({
}, [decorations.options]) }, [decorations.options])
// Save file keybinding logic effect // Save file keybinding logic effect
const debouncedSaveData = useCallback(
debounce((value: string | undefined, activeFileId: string | undefined) => {
setTabs((prev) =>
prev.map((tab) =>
tab.id === activeFileId ? { ...tab, saved: true } : tab
)
);
console.log(`Saving file...${activeFileId}`);
console.log(`Saving file...${value}`);
socketRef.current?.emit("saveFile", activeFileId, value);
}, Number(process.env.FILE_SAVE_DEBOUNCE_DELAY)||1000),
[socketRef]
);
useEffect(() => { useEffect(() => {
const down = (e: KeyboardEvent) => { const down = (e: KeyboardEvent) => {
if (e.key === "s" && (e.metaKey || e.ctrlKey)) { if (e.key === "s" && (e.metaKey || e.ctrlKey)) {
e.preventDefault(); e.preventDefault()
debouncedSaveData(editorRef?.getValue(), activeFileId);
setTabs((prev) =>
prev.map((tab) =>
tab.id === activeFileId ? { ...tab, saved: true } : tab
)
)
socket.emit("saveFile", activeFileId, editorRef?.getValue())
} }
}; }
document.addEventListener("keydown", down); document.addEventListener("keydown", down)
return () => { return () => {
document.removeEventListener("keydown", down); document.removeEventListener("keydown", down)
}; }
}, [activeFileId, tabs, debouncedSaveData]); }, [tabs, activeFileId])
// Liveblocks live collaboration setup effect // Liveblocks live collaboration setup effect
useEffect(() => { useEffect(() => {
@ -369,10 +358,10 @@ export default function CodeEditor({
// Connection/disconnection effect // Connection/disconnection effect
useEffect(() => { useEffect(() => {
socketRef.current?.connect() socket.connect()
return () => { return () => {
socketRef.current?.disconnect() socket.disconnect()
} }
}, []) }, [])
@ -407,20 +396,20 @@ export default function CodeEditor({
}) })
} }
socketRef.current?.on("connect", onConnect) socket.on("connect", onConnect)
socketRef.current?.on("disconnect", onDisconnect) socket.on("disconnect", onDisconnect)
socketRef.current?.on("loaded", onLoadedEvent) socket.on("loaded", onLoadedEvent)
socketRef.current?.on("rateLimit", onRateLimit) socket.on("rateLimit", onRateLimit)
socketRef.current?.on("terminalResponse", onTerminalResponse) socket.on("terminalResponse", onTerminalResponse)
socketRef.current?.on("disableAccess", onDisableAccess) socket.on("disableAccess", onDisableAccess)
return () => { return () => {
socketRef.current?.off("connect", onConnect) socket.off("connect", onConnect)
socketRef.current?.off("disconnect", onDisconnect) socket.off("disconnect", onDisconnect)
socketRef.current?.off("loaded", onLoadedEvent) socket.off("loaded", onLoadedEvent)
socketRef.current?.off("rateLimit", onRateLimit) socket.off("rateLimit", onRateLimit)
socketRef.current?.off("terminalResponse", onTerminalResponse) socket.off("terminalResponse", onTerminalResponse)
socketRef.current?.off("disableAccess", onDisableAccess) socket.off("disableAccess", onDisableAccess)
} }
// }, []); // }, []);
}, [terminals]) }, [terminals])
@ -428,43 +417,31 @@ export default function CodeEditor({
// Helper functions for tabs: // Helper functions for tabs:
// Select file and load content // Select file and load content
const selectFile = (tab: TTab) => {
if (tab.id === activeFileId) return
// Initialize debounced function once setGenerate((prev) => {
const fileCache = useRef(new Map()); return {
...prev,
show: false,
}
})
const exists = tabs.find((t) => t.id === tab.id)
// Debounced function to get file content
const debouncedGetFile = useCallback(
debounce((tabId, callback) => {
socketRef.current?.emit('getFile', tabId, callback);
}, 300), // 300ms debounce delay, adjust as needed
[]
);
const selectFile = useCallback((tab: TTab) => {
if (tab.id === activeFileId) return;
setGenerate((prev) => ({ ...prev, show: false }));
const exists = tabs.find((t) => t.id === tab.id);
setTabs((prev) => { setTabs((prev) => {
if (exists) { if (exists) {
setActiveFileId(exists.id); setActiveFileId(exists.id)
return prev; return prev
} }
return [...prev, tab]; return [...prev, tab]
}); })
if (fileCache.current.has(tab.id)) { socket.emit("getFile", tab.id, (response: string) => {
setActiveFileContent(fileCache.current.get(tab.id)); setActiveFileContent(response)
} else { })
debouncedGetFile(tab.id, (response: SetStateAction<string>) => { setEditorLanguage(processFileType(tab.name))
fileCache.current.set(tab.id, response); setActiveFileId(tab.id)
setActiveFileContent(response); }
});
}
setEditorLanguage(processFileType(tab.name));
setActiveFileId(tab.id);
}, [activeFileId, tabs, debouncedGetFile]);
// Close tab and remove from tabs // Close tab and remove from tabs
const closeTab = (id: string) => { const closeTab = (id: string) => {
@ -538,7 +515,7 @@ export default function CodeEditor({
return false return false
} }
socketRef.current?.emit("renameFile", id, newName) socket.emit("renameFile", id, newName)
setTabs((prev) => setTabs((prev) =>
prev.map((tab) => (tab.id === id ? { ...tab, name: newName } : tab)) prev.map((tab) => (tab.id === id ? { ...tab, name: newName } : tab))
) )
@ -547,7 +524,7 @@ export default function CodeEditor({
} }
const handleDeleteFile = (file: TFile) => { const handleDeleteFile = (file: TFile) => {
socketRef.current?.emit("deleteFile", file.id, (response: (TFolder | TFile)[]) => { socket.emit("deleteFile", file.id, (response: (TFolder | TFile)[]) => {
setFiles(response) setFiles(response)
}) })
closeTab(file.id) closeTab(file.id)
@ -557,11 +534,11 @@ export default function CodeEditor({
setDeletingFolderId(folder.id) setDeletingFolderId(folder.id)
console.log("deleting folder", folder.id) console.log("deleting folder", folder.id)
socketRef.current?.emit("getFolder", folder.id, (response: string[]) => socket.emit("getFolder", folder.id, (response: string[]) =>
closeTabs(response) closeTabs(response)
) )
socketRef.current?.emit("deleteFolder", folder.id, (response: (TFolder | TFile)[]) => { socket.emit("deleteFolder", folder.id, (response: (TFolder | TFile)[]) => {
setFiles(response) setFiles(response)
setDeletingFolderId("") setDeletingFolderId("")
}) })
@ -588,7 +565,7 @@ export default function CodeEditor({
{generate.show && ai ? ( {generate.show && ai ? (
<GenerateInput <GenerateInput
user={userData} user={userData}
socket={socketRef.current} socket={socket}
width={generate.width - 90} width={generate.width - 90}
data={{ data={{
fileName: tabs.find((t) => t.id === activeFileId)?.name ?? "", fileName: tabs.find((t) => t.id === activeFileId)?.name ?? "",
@ -648,7 +625,7 @@ export default function CodeEditor({
handleRename={handleRename} handleRename={handleRename}
handleDeleteFile={handleDeleteFile} handleDeleteFile={handleDeleteFile}
handleDeleteFolder={handleDeleteFolder} handleDeleteFolder={handleDeleteFolder}
socket={socketRef.current} socket={socket}
setFiles={setFiles} setFiles={setFiles}
addNew={(name, type) => addNew(name, type, setFiles, sandboxData)} addNew={(name, type) => addNew(name, type, setFiles, sandboxData)}
deletingFolderId={deletingFolderId} deletingFolderId={deletingFolderId}
@ -780,7 +757,7 @@ export default function CodeEditor({
<Terminals <Terminals
terminals={terminals} terminals={terminals}
setTerminals={setTerminals} setTerminals={setTerminals}
socket={socketRef.current} socket={socket}
/> />
) : ( ) : (
<div className="w-full h-full flex items-center justify-center text-lg font-medium text-muted-foreground/50 select-none"> <div className="w-full h-full flex items-center justify-center text-lg font-medium text-muted-foreground/50 select-none">
@ -795,4 +772,3 @@ export default function CodeEditor({
</> </>
) )
} }

View File

@ -61,13 +61,3 @@ export function addNew(
]) ])
} }
} }
export function debounce<T extends (...args: any[]) => void>(func: T, wait: number): T {
let timeout: NodeJS.Timeout | null = null;
return function (...args: Parameters<T>) {
if (timeout) {
clearTimeout(timeout);
}
timeout = setTimeout(() => func(...args), wait);
} as T;
}