2024-04-06 19:03:04 -04:00
|
|
|
"use client"
|
|
|
|
|
2024-05-03 00:52:01 -07:00
|
|
|
import { useEffect, useRef, useState } from "react"
|
2024-05-03 13:53:21 -07:00
|
|
|
import monaco from "monaco-editor"
|
|
|
|
import Editor, { BeforeMount, OnMount } from "@monaco-editor/react"
|
|
|
|
import { io } from "socket.io-client"
|
|
|
|
import { toast } from "sonner"
|
|
|
|
import { useClerk } from "@clerk/nextjs"
|
|
|
|
|
|
|
|
import * as Y from "yjs"
|
|
|
|
import LiveblocksProvider from "@liveblocks/yjs"
|
|
|
|
import { MonacoBinding } from "y-monaco"
|
|
|
|
import { Awareness } from "y-protocols/awareness"
|
2024-05-03 14:27:45 -07:00
|
|
|
import {
|
|
|
|
TypedLiveblocksProvider,
|
|
|
|
useRoom,
|
|
|
|
AwarenessList,
|
|
|
|
useSelf,
|
|
|
|
} from "@/liveblocks.config"
|
2024-04-06 19:03:04 -04:00
|
|
|
|
|
|
|
import {
|
|
|
|
ResizableHandle,
|
|
|
|
ResizablePanel,
|
|
|
|
ResizablePanelGroup,
|
|
|
|
} from "@/components/ui/resizable"
|
2024-04-08 23:40:42 -04:00
|
|
|
import {
|
|
|
|
ChevronLeft,
|
|
|
|
ChevronRight,
|
2024-04-27 11:08:19 -04:00
|
|
|
FileJson,
|
2024-04-30 02:00:50 -04:00
|
|
|
Plus,
|
2024-04-08 23:40:42 -04:00
|
|
|
RotateCw,
|
2024-04-30 02:00:50 -04:00
|
|
|
Shell,
|
|
|
|
SquareTerminal,
|
2024-04-08 23:40:42 -04:00
|
|
|
TerminalSquare,
|
|
|
|
} from "lucide-react"
|
|
|
|
import Tab from "../ui/tab"
|
2024-04-09 00:50:48 -04:00
|
|
|
import Sidebar from "./sidebar"
|
2024-04-28 20:06:47 -04:00
|
|
|
import EditorTerminal from "./terminal"
|
2024-04-30 02:00:50 -04:00
|
|
|
import { Button } from "../ui/button"
|
2024-05-02 17:38:37 -07:00
|
|
|
import GenerateInput from "./generate"
|
2024-05-03 13:53:21 -07:00
|
|
|
import { TFile, TFileData, TFolder, TTab } from "./sidebar/types"
|
2024-05-05 00:06:10 -07:00
|
|
|
import { Sandbox, User } from "@/lib/types"
|
2024-05-03 13:53:21 -07:00
|
|
|
import { processFileType, validateName } from "@/lib/utils"
|
2024-05-03 14:27:45 -07:00
|
|
|
import { Cursors } from "./live/cursors"
|
2024-04-28 20:06:47 -04:00
|
|
|
|
2024-04-26 22:34:56 -04:00
|
|
|
export default function CodeEditor({
|
2024-05-01 01:53:49 -04:00
|
|
|
userData,
|
2024-05-05 00:06:10 -07:00
|
|
|
sandboxData,
|
2024-04-26 22:34:56 -04:00
|
|
|
}: {
|
2024-05-01 01:53:49 -04:00
|
|
|
userData: User
|
2024-05-05 00:06:10 -07:00
|
|
|
sandboxData: Sandbox
|
2024-04-26 22:34:56 -04:00
|
|
|
}) {
|
2024-05-02 00:00:35 -07:00
|
|
|
const [files, setFiles] = useState<(TFolder | TFile)[]>([])
|
2024-05-04 01:18:06 -07:00
|
|
|
const [tabs, setTabs] = useState<TTab[]>([])
|
2024-05-02 00:00:35 -07:00
|
|
|
const [editorLanguage, setEditorLanguage] = useState("plaintext")
|
2024-05-04 01:18:06 -07:00
|
|
|
const [activeId, setActiveId] = useState<string>("")
|
2024-05-02 00:00:35 -07:00
|
|
|
const [activeFile, setActiveFile] = useState<string | null>(null)
|
|
|
|
const [cursorLine, setCursorLine] = useState(0)
|
2024-05-02 17:38:37 -07:00
|
|
|
const [generate, setGenerate] = useState<{
|
|
|
|
show: boolean
|
|
|
|
id: string
|
2024-05-02 18:05:18 -07:00
|
|
|
line: number
|
2024-05-02 17:38:37 -07:00
|
|
|
widget: monaco.editor.IContentWidget | undefined
|
|
|
|
pref: monaco.editor.ContentWidgetPositionPreference[]
|
|
|
|
width: number
|
2024-05-02 18:05:18 -07:00
|
|
|
}>({ show: false, line: 0, id: "", widget: undefined, pref: [], width: 0 })
|
2024-05-02 15:25:24 -07:00
|
|
|
const [decorations, setDecorations] = useState<{
|
|
|
|
options: monaco.editor.IModelDeltaDecoration[]
|
|
|
|
instance: monaco.editor.IEditorDecorationsCollection | undefined
|
|
|
|
}>({ options: [], instance: undefined })
|
|
|
|
const [terminals, setTerminals] = useState<string[]>([])
|
2024-05-03 14:27:45 -07:00
|
|
|
const [provider, setProvider] = useState<TypedLiveblocksProvider>()
|
2024-05-04 01:50:33 -07:00
|
|
|
const [ai, setAi] = useState(false)
|
2024-05-02 00:00:35 -07:00
|
|
|
|
2024-05-05 00:06:10 -07:00
|
|
|
const isOwner = sandboxData.userId === userData.id
|
2024-04-28 20:06:47 -04:00
|
|
|
const clerk = useClerk()
|
2024-05-03 13:53:21 -07:00
|
|
|
const room = useRoom()
|
2024-04-28 20:06:47 -04:00
|
|
|
|
2024-05-04 20:50:00 -07:00
|
|
|
// const editorRef = useRef<monaco.editor.IStandaloneCodeEditor | null>(null)
|
|
|
|
const [editorRef, setEditorRef] =
|
|
|
|
useState<monaco.editor.IStandaloneCodeEditor>()
|
2024-05-02 17:38:37 -07:00
|
|
|
const editorContainerRef = useRef<HTMLDivElement>(null)
|
2024-05-02 00:00:35 -07:00
|
|
|
const monacoRef = useRef<typeof monaco | null>(null)
|
|
|
|
const generateRef = useRef<HTMLDivElement>(null)
|
2024-05-02 17:38:37 -07:00
|
|
|
const generateWidgetRef = useRef<HTMLDivElement>(null)
|
2024-05-02 00:00:35 -07:00
|
|
|
|
|
|
|
const handleEditorWillMount: BeforeMount = (monaco) => {
|
|
|
|
monaco.editor.addKeybindingRules([
|
|
|
|
{
|
|
|
|
keybinding: monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyG,
|
|
|
|
command: "null",
|
|
|
|
// when: "textInputFocus",
|
|
|
|
},
|
|
|
|
])
|
|
|
|
}
|
2024-04-06 19:03:04 -04:00
|
|
|
|
2024-04-27 00:20:17 -04:00
|
|
|
const handleEditorMount: OnMount = (editor, monaco) => {
|
2024-05-04 20:50:00 -07:00
|
|
|
setEditorRef(editor)
|
2024-05-02 00:00:35 -07:00
|
|
|
monacoRef.current = monaco
|
|
|
|
|
|
|
|
editor.onDidChangeCursorPosition((e) => {
|
2024-05-02 15:25:24 -07:00
|
|
|
const { column, lineNumber } = e.position
|
|
|
|
if (lineNumber === cursorLine) return
|
|
|
|
setCursorLine(lineNumber)
|
|
|
|
|
|
|
|
const model = editor.getModel()
|
|
|
|
const endColumn = model?.getLineContent(lineNumber).length || 0
|
|
|
|
|
|
|
|
setDecorations((prev) => {
|
|
|
|
return {
|
|
|
|
...prev,
|
|
|
|
options: [
|
|
|
|
{
|
|
|
|
range: new monaco.Range(
|
|
|
|
lineNumber,
|
|
|
|
column,
|
|
|
|
lineNumber,
|
|
|
|
endColumn
|
|
|
|
),
|
|
|
|
options: {
|
|
|
|
afterContentClassName: "inline-decoration",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
],
|
|
|
|
}
|
|
|
|
})
|
2024-05-02 00:00:35 -07:00
|
|
|
})
|
|
|
|
|
2024-05-02 17:38:37 -07:00
|
|
|
editor.onDidBlurEditorText((e) => {
|
|
|
|
setDecorations((prev) => {
|
|
|
|
return {
|
|
|
|
...prev,
|
|
|
|
options: [],
|
|
|
|
}
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
2024-05-02 00:00:35 -07:00
|
|
|
editor.addAction({
|
|
|
|
id: "generate",
|
|
|
|
label: "Generate",
|
|
|
|
keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyG],
|
|
|
|
precondition:
|
|
|
|
"editorTextFocus && !suggestWidgetVisible && !renameInputVisible && !inSnippetMode && !quickFixWidgetVisible",
|
2024-05-02 15:25:24 -07:00
|
|
|
run: () => {
|
|
|
|
setGenerate((prev) => {
|
2024-05-02 17:38:37 -07:00
|
|
|
return {
|
|
|
|
...prev,
|
|
|
|
show: !prev.show,
|
|
|
|
pref: [monaco.editor.ContentWidgetPositionPreference.BELOW],
|
|
|
|
}
|
2024-05-02 15:25:24 -07:00
|
|
|
})
|
|
|
|
},
|
2024-05-02 00:00:35 -07:00
|
|
|
})
|
2024-04-27 00:20:17 -04:00
|
|
|
}
|
2024-04-06 19:03:04 -04:00
|
|
|
|
2024-05-02 00:00:35 -07:00
|
|
|
useEffect(() => {
|
2024-05-04 01:50:33 -07:00
|
|
|
if (!ai) {
|
|
|
|
setGenerate((prev) => {
|
|
|
|
return {
|
|
|
|
...prev,
|
|
|
|
show: false,
|
|
|
|
}
|
|
|
|
})
|
|
|
|
return
|
|
|
|
}
|
2024-05-02 15:25:24 -07:00
|
|
|
if (generate.show) {
|
2024-05-04 20:50:00 -07:00
|
|
|
editorRef?.changeViewZones(function (changeAccessor) {
|
2024-05-02 00:00:35 -07:00
|
|
|
if (!generateRef.current) return
|
|
|
|
const id = changeAccessor.addZone({
|
|
|
|
afterLineNumber: cursorLine,
|
|
|
|
heightInLines: 3,
|
|
|
|
domNode: generateRef.current,
|
|
|
|
})
|
2024-05-02 15:25:24 -07:00
|
|
|
setGenerate((prev) => {
|
2024-05-02 18:05:18 -07:00
|
|
|
return { ...prev, id, line: cursorLine }
|
2024-05-02 15:25:24 -07:00
|
|
|
})
|
2024-05-02 00:00:35 -07:00
|
|
|
})
|
2024-05-02 17:38:37 -07:00
|
|
|
|
|
|
|
if (!generateWidgetRef.current) return
|
|
|
|
const widgetElement = generateWidgetRef.current
|
|
|
|
|
|
|
|
const contentWidget = {
|
|
|
|
getDomNode: () => {
|
|
|
|
return widgetElement
|
|
|
|
},
|
|
|
|
getId: () => {
|
|
|
|
return "generate.widget"
|
|
|
|
},
|
|
|
|
getPosition: () => {
|
|
|
|
return {
|
|
|
|
position: {
|
|
|
|
lineNumber: cursorLine,
|
|
|
|
column: 1,
|
|
|
|
},
|
|
|
|
preference: generate.pref,
|
|
|
|
}
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
setGenerate((prev) => {
|
|
|
|
return { ...prev, widget: contentWidget }
|
|
|
|
})
|
2024-05-04 20:50:00 -07:00
|
|
|
editorRef?.addContentWidget(contentWidget)
|
2024-05-02 17:38:37 -07:00
|
|
|
|
|
|
|
if (generateRef.current && generateWidgetRef.current) {
|
2024-05-04 20:50:00 -07:00
|
|
|
editorRef?.applyFontInfo(generateRef.current)
|
|
|
|
editorRef?.applyFontInfo(generateWidgetRef.current)
|
2024-05-02 17:38:37 -07:00
|
|
|
}
|
2024-05-02 00:00:35 -07:00
|
|
|
} else {
|
2024-05-04 20:50:00 -07:00
|
|
|
editorRef?.changeViewZones(function (changeAccessor) {
|
2024-05-02 15:25:24 -07:00
|
|
|
changeAccessor.removeZone(generate.id)
|
|
|
|
setGenerate((prev) => {
|
|
|
|
return { ...prev, id: "" }
|
|
|
|
})
|
|
|
|
})
|
2024-05-02 17:38:37 -07:00
|
|
|
|
|
|
|
if (!generate.widget) return
|
2024-05-04 20:50:00 -07:00
|
|
|
editorRef?.removeContentWidget(generate.widget)
|
2024-05-02 17:38:37 -07:00
|
|
|
setGenerate((prev) => {
|
|
|
|
return {
|
|
|
|
...prev,
|
|
|
|
widget: undefined,
|
|
|
|
}
|
|
|
|
})
|
2024-05-02 15:25:24 -07:00
|
|
|
}
|
|
|
|
}, [generate.show])
|
|
|
|
|
|
|
|
useEffect(() => {
|
2024-05-02 17:38:37 -07:00
|
|
|
if (decorations.options.length === 0) {
|
|
|
|
decorations.instance?.clear()
|
|
|
|
}
|
2024-05-02 15:25:24 -07:00
|
|
|
|
2024-05-04 01:50:33 -07:00
|
|
|
if (!ai) return
|
|
|
|
|
2024-05-02 15:25:24 -07:00
|
|
|
if (decorations.instance) {
|
|
|
|
decorations.instance.set(decorations.options)
|
|
|
|
} else {
|
2024-05-04 20:50:00 -07:00
|
|
|
const instance = editorRef?.createDecorationsCollection()
|
2024-05-02 15:25:24 -07:00
|
|
|
instance?.set(decorations.options)
|
|
|
|
|
|
|
|
setDecorations((prev) => {
|
|
|
|
return {
|
|
|
|
...prev,
|
|
|
|
instance,
|
|
|
|
}
|
2024-05-02 00:00:35 -07:00
|
|
|
})
|
|
|
|
}
|
2024-05-02 15:25:24 -07:00
|
|
|
}, [decorations.options])
|
2024-04-26 22:34:56 -04:00
|
|
|
|
|
|
|
const socket = io(
|
2024-05-05 00:06:10 -07:00
|
|
|
`http://localhost:4000?userId=${userData.id}&sandboxId=${sandboxData.id}`
|
2024-04-26 22:34:56 -04:00
|
|
|
)
|
|
|
|
|
2024-04-27 16:41:25 -04:00
|
|
|
useEffect(() => {
|
|
|
|
const down = (e: KeyboardEvent) => {
|
|
|
|
if (e.key === "s" && (e.metaKey || e.ctrlKey)) {
|
|
|
|
e.preventDefault()
|
|
|
|
|
2024-05-03 13:53:21 -07:00
|
|
|
// const activeTab = tabs.find((t) => t.id === activeId)
|
2024-05-04 20:50:00 -07:00
|
|
|
// console.log("saving:", activeTab?.name, editorRef?.getValue())
|
2024-04-27 16:41:25 -04:00
|
|
|
|
|
|
|
setTabs((prev) =>
|
|
|
|
prev.map((tab) =>
|
|
|
|
tab.id === activeId ? { ...tab, saved: true } : tab
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
2024-05-04 20:50:00 -07:00
|
|
|
socket.emit("saveFile", activeId, editorRef?.getValue())
|
2024-04-27 16:41:25 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
document.addEventListener("keydown", down)
|
|
|
|
|
|
|
|
return () => {
|
|
|
|
document.removeEventListener("keydown", down)
|
|
|
|
}
|
|
|
|
}, [tabs, activeId])
|
|
|
|
|
2024-05-02 17:38:37 -07:00
|
|
|
const resizeObserver = new ResizeObserver((entries) => {
|
|
|
|
for (const entry of entries) {
|
|
|
|
const { width } = entry.contentRect
|
|
|
|
setGenerate((prev) => {
|
|
|
|
return { ...prev, width }
|
|
|
|
})
|
|
|
|
}
|
|
|
|
})
|
2024-04-27 16:41:25 -04:00
|
|
|
|
2024-05-04 20:50:00 -07:00
|
|
|
useEffect(() => {
|
|
|
|
const tab = tabs.find((t) => t.id === activeId)
|
|
|
|
const model = editorRef?.getModel()
|
|
|
|
|
|
|
|
if (!editorRef || !tab || !model) return
|
|
|
|
|
|
|
|
const yDoc = new Y.Doc()
|
|
|
|
const yText = yDoc.getText(tab.id)
|
|
|
|
const yProvider: any = new LiveblocksProvider(room, yDoc)
|
|
|
|
|
|
|
|
const onSync = (isSynced: boolean) => {
|
|
|
|
if (isSynced) {
|
|
|
|
const text = yText.toString()
|
|
|
|
if (text === "") {
|
|
|
|
if (activeFile) {
|
|
|
|
yText.insert(0, activeFile)
|
|
|
|
} else {
|
|
|
|
setTimeout(() => {
|
|
|
|
yText.insert(0, editorRef.getValue())
|
|
|
|
}, 0)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Yjs content is not synchronized
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
yProvider.on("sync", onSync)
|
|
|
|
|
|
|
|
setProvider(yProvider)
|
|
|
|
|
|
|
|
const binding = new MonacoBinding(
|
|
|
|
yText,
|
|
|
|
model,
|
|
|
|
new Set([editorRef]),
|
|
|
|
yProvider.awareness as Awareness
|
|
|
|
)
|
|
|
|
|
|
|
|
return () => {
|
|
|
|
yDoc.destroy()
|
|
|
|
yProvider.destroy()
|
|
|
|
binding.destroy()
|
|
|
|
yProvider.off("sync", onSync)
|
|
|
|
}
|
|
|
|
}, [editorRef, room, activeFile])
|
2024-05-03 13:53:21 -07:00
|
|
|
|
2024-05-02 17:38:37 -07:00
|
|
|
// connection/disconnection effect + resizeobserver
|
2024-04-26 22:34:56 -04:00
|
|
|
useEffect(() => {
|
|
|
|
socket.connect()
|
|
|
|
|
2024-05-02 17:38:37 -07:00
|
|
|
if (editorContainerRef.current) {
|
|
|
|
resizeObserver.observe(editorContainerRef.current)
|
|
|
|
}
|
|
|
|
|
2024-04-26 22:34:56 -04:00
|
|
|
return () => {
|
|
|
|
socket.disconnect()
|
2024-05-02 17:38:37 -07:00
|
|
|
|
|
|
|
resizeObserver.disconnect()
|
2024-04-26 22:34:56 -04:00
|
|
|
}
|
|
|
|
}, [])
|
|
|
|
|
|
|
|
// event listener effect
|
|
|
|
useEffect(() => {
|
2024-04-28 20:06:47 -04:00
|
|
|
const onConnect = () => {}
|
|
|
|
|
|
|
|
const onDisconnect = () => {}
|
|
|
|
|
|
|
|
const onLoadedEvent = (files: (TFolder | TFile)[]) => {
|
2024-04-26 22:34:56 -04:00
|
|
|
setFiles(files)
|
|
|
|
}
|
|
|
|
|
2024-04-28 20:06:47 -04:00
|
|
|
socket.on("connect", onConnect)
|
|
|
|
|
|
|
|
socket.on("disconnect", onDisconnect)
|
2024-04-26 22:34:56 -04:00
|
|
|
socket.on("loaded", onLoadedEvent)
|
|
|
|
|
|
|
|
return () => {
|
2024-04-28 20:06:47 -04:00
|
|
|
socket.off("connect", onConnect)
|
|
|
|
socket.off("disconnect", onDisconnect)
|
2024-04-26 22:34:56 -04:00
|
|
|
socket.off("loaded", onLoadedEvent)
|
|
|
|
}
|
|
|
|
}, [])
|
|
|
|
|
2024-04-28 20:06:47 -04:00
|
|
|
// Helper functions:
|
2024-04-06 19:03:04 -04:00
|
|
|
|
2024-04-27 11:08:19 -04:00
|
|
|
const selectFile = (tab: TTab) => {
|
2024-05-04 01:18:06 -07:00
|
|
|
if (tab.id === activeId) return
|
|
|
|
const exists = tabs.find((t) => t.id === tab.id)
|
2024-05-05 12:55:34 -07:00
|
|
|
|
2024-04-26 00:10:53 -04:00
|
|
|
setTabs((prev) => {
|
|
|
|
if (exists) {
|
|
|
|
setActiveId(exists.id)
|
|
|
|
return prev
|
|
|
|
}
|
|
|
|
return [...prev, tab]
|
|
|
|
})
|
2024-05-04 01:18:06 -07:00
|
|
|
|
2024-04-27 00:20:17 -04:00
|
|
|
socket.emit("getFile", tab.id, (response: string) => {
|
|
|
|
setActiveFile(response)
|
|
|
|
})
|
2024-04-27 00:28:00 -04:00
|
|
|
setEditorLanguage(processFileType(tab.name))
|
2024-04-26 00:10:53 -04:00
|
|
|
setActiveId(tab.id)
|
|
|
|
}
|
|
|
|
|
|
|
|
const closeTab = (tab: TFile) => {
|
|
|
|
const numTabs = tabs.length
|
|
|
|
const index = tabs.findIndex((t) => t.id === tab.id)
|
2024-04-30 01:56:43 -04:00
|
|
|
|
|
|
|
if (index === -1) return
|
|
|
|
|
2024-04-27 00:20:17 -04:00
|
|
|
const nextId =
|
|
|
|
activeId === tab.id
|
|
|
|
? numTabs === 1
|
|
|
|
? null
|
|
|
|
: index < numTabs - 1
|
|
|
|
? tabs[index + 1].id
|
|
|
|
: tabs[index - 1].id
|
|
|
|
: activeId
|
|
|
|
|
2024-04-26 00:10:53 -04:00
|
|
|
setTabs((prev) => prev.filter((t) => t.id !== tab.id))
|
2024-05-04 01:18:06 -07:00
|
|
|
|
|
|
|
if (!nextId) {
|
|
|
|
setActiveId("")
|
|
|
|
} else {
|
|
|
|
const nextTab = tabs.find((t) => t.id === nextId)
|
|
|
|
if (nextTab) {
|
|
|
|
selectFile(nextTab)
|
|
|
|
}
|
|
|
|
}
|
2024-04-26 00:10:53 -04:00
|
|
|
}
|
|
|
|
|
2024-04-27 14:23:09 -04:00
|
|
|
const handleRename = (
|
|
|
|
id: string,
|
|
|
|
newName: string,
|
|
|
|
oldName: string,
|
|
|
|
type: "file" | "folder"
|
|
|
|
) => {
|
2024-05-05 12:55:34 -07:00
|
|
|
const valid = validateName(newName, oldName, type)
|
|
|
|
if (!valid.status) {
|
|
|
|
if (valid.message) toast.error("Invalid file name.")
|
2024-04-30 01:56:43 -04:00
|
|
|
return false
|
|
|
|
}
|
2024-04-27 14:23:09 -04:00
|
|
|
|
2024-04-27 11:08:19 -04:00
|
|
|
socket.emit("renameFile", id, newName)
|
|
|
|
setTabs((prev) =>
|
|
|
|
prev.map((tab) => (tab.id === id ? { ...tab, name: newName } : tab))
|
|
|
|
)
|
2024-04-27 14:23:09 -04:00
|
|
|
|
|
|
|
return true
|
2024-04-27 11:08:19 -04:00
|
|
|
}
|
|
|
|
|
2024-04-30 01:56:43 -04:00
|
|
|
const handleDeleteFile = (file: TFile) => {
|
|
|
|
socket.emit("deleteFile", file.id, (response: (TFolder | TFile)[]) => {
|
|
|
|
setFiles(response)
|
|
|
|
})
|
|
|
|
closeTab(file)
|
|
|
|
}
|
|
|
|
|
|
|
|
const handleDeleteFolder = (folder: TFolder) => {
|
|
|
|
// socket.emit("deleteFolder", folder.id, (response: (TFolder | TFile)[]) => {
|
|
|
|
// setFiles(response)
|
|
|
|
// })
|
|
|
|
}
|
|
|
|
|
2024-04-06 19:03:04 -04:00
|
|
|
return (
|
|
|
|
<>
|
2024-05-02 17:38:37 -07:00
|
|
|
<div ref={generateRef} />
|
|
|
|
<div className="z-50 p-1" ref={generateWidgetRef}>
|
2024-05-04 01:50:33 -07:00
|
|
|
{generate.show && ai ? (
|
2024-05-02 17:38:37 -07:00
|
|
|
<GenerateInput
|
2024-05-03 00:52:01 -07:00
|
|
|
socket={socket}
|
2024-05-02 17:38:37 -07:00
|
|
|
width={generate.width - 90}
|
2024-05-03 00:52:01 -07:00
|
|
|
data={{
|
|
|
|
fileName: tabs.find((t) => t.id === activeId)?.name ?? "",
|
2024-05-04 20:50:00 -07:00
|
|
|
code: editorRef?.getValue() ?? "",
|
2024-05-03 00:52:01 -07:00
|
|
|
line: generate.line,
|
|
|
|
}}
|
|
|
|
editor={{
|
|
|
|
language: editorLanguage,
|
|
|
|
}}
|
2024-05-02 17:38:37 -07:00
|
|
|
onExpand={() => {
|
2024-05-04 20:50:00 -07:00
|
|
|
editorRef?.changeViewZones(function (changeAccessor) {
|
2024-05-02 17:38:37 -07:00
|
|
|
changeAccessor.removeZone(generate.id)
|
|
|
|
|
|
|
|
if (!generateRef.current) return
|
|
|
|
const id = changeAccessor.addZone({
|
|
|
|
afterLineNumber: cursorLine,
|
|
|
|
heightInLines: 12,
|
|
|
|
domNode: generateRef.current,
|
|
|
|
})
|
|
|
|
setGenerate((prev) => {
|
|
|
|
return { ...prev, id }
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}}
|
2024-05-02 18:05:18 -07:00
|
|
|
onAccept={(code: string) => {
|
2024-05-03 00:52:01 -07:00
|
|
|
const line = generate.line
|
2024-05-02 18:05:18 -07:00
|
|
|
setGenerate((prev) => {
|
|
|
|
return {
|
|
|
|
...prev,
|
|
|
|
show: !prev.show,
|
|
|
|
}
|
|
|
|
})
|
2024-05-04 20:50:00 -07:00
|
|
|
const file = editorRef?.getValue()
|
2024-05-03 00:52:01 -07:00
|
|
|
|
|
|
|
const lines = file?.split("\n") || []
|
|
|
|
lines.splice(line - 1, 0, code)
|
|
|
|
const updatedFile = lines.join("\n")
|
2024-05-04 20:50:00 -07:00
|
|
|
editorRef?.setValue(updatedFile)
|
2024-05-02 18:05:18 -07:00
|
|
|
}}
|
2024-05-02 17:38:37 -07:00
|
|
|
/>
|
|
|
|
) : null}
|
2024-05-02 00:00:35 -07:00
|
|
|
</div>
|
2024-05-02 17:38:37 -07:00
|
|
|
|
2024-04-27 14:23:09 -04:00
|
|
|
<Sidebar
|
|
|
|
files={files}
|
|
|
|
selectFile={selectFile}
|
|
|
|
handleRename={handleRename}
|
2024-04-30 01:56:43 -04:00
|
|
|
handleDeleteFile={handleDeleteFile}
|
|
|
|
handleDeleteFolder={handleDeleteFolder}
|
2024-04-29 00:50:25 -04:00
|
|
|
socket={socket}
|
|
|
|
addNew={(name, type) => {
|
|
|
|
if (type === "file") {
|
|
|
|
setFiles((prev) => [
|
|
|
|
...prev,
|
2024-05-05 00:06:10 -07:00
|
|
|
{ id: `projects/${sandboxData.id}/${name}`, name, type: "file" },
|
2024-04-29 00:50:25 -04:00
|
|
|
])
|
|
|
|
} else {
|
|
|
|
console.log("adding folder")
|
|
|
|
// setFiles(prev => [...prev, { id, name, type: "folder", children: [] }])
|
|
|
|
}
|
|
|
|
}}
|
2024-05-04 01:50:33 -07:00
|
|
|
ai={ai}
|
|
|
|
setAi={setAi}
|
2024-04-27 14:23:09 -04:00
|
|
|
/>
|
2024-04-06 19:03:04 -04:00
|
|
|
<ResizablePanelGroup direction="horizontal">
|
|
|
|
<ResizablePanel
|
|
|
|
className="p-2 flex flex-col"
|
|
|
|
maxSize={75}
|
|
|
|
minSize={30}
|
|
|
|
defaultSize={60}
|
|
|
|
>
|
|
|
|
<div className="h-10 w-full flex gap-2">
|
2024-04-26 00:10:53 -04:00
|
|
|
{tabs.map((tab) => (
|
|
|
|
<Tab
|
|
|
|
key={tab.id}
|
2024-04-27 11:08:19 -04:00
|
|
|
saved={tab.saved}
|
2024-04-26 00:10:53 -04:00
|
|
|
selected={activeId === tab.id}
|
2024-05-04 01:18:06 -07:00
|
|
|
onClick={(e) => {
|
2024-04-27 00:20:17 -04:00
|
|
|
selectFile(tab)
|
|
|
|
}}
|
2024-04-26 00:10:53 -04:00
|
|
|
onClose={() => closeTab(tab)}
|
|
|
|
>
|
|
|
|
{tab.name}
|
|
|
|
</Tab>
|
|
|
|
))}
|
2024-04-06 19:03:04 -04:00
|
|
|
</div>
|
2024-05-02 17:38:37 -07:00
|
|
|
<div
|
|
|
|
ref={editorContainerRef}
|
2024-05-03 14:27:45 -07:00
|
|
|
className="grow w-full overflow-hidden rounded-md relative"
|
2024-05-02 17:38:37 -07:00
|
|
|
>
|
2024-05-04 01:18:06 -07:00
|
|
|
{!activeId ? (
|
2024-04-26 00:10:53 -04:00
|
|
|
<>
|
|
|
|
<div className="w-full h-full flex items-center justify-center text-xl font-medium text-secondary select-none">
|
2024-04-27 11:08:19 -04:00
|
|
|
<FileJson className="w-6 h-6 mr-3" />
|
2024-04-26 00:10:53 -04:00
|
|
|
No file selected.
|
|
|
|
</div>
|
|
|
|
</>
|
|
|
|
) : clerk.loaded ? (
|
2024-05-03 14:27:45 -07:00
|
|
|
<>
|
|
|
|
{provider ? <Cursors yProvider={provider} /> : null}
|
|
|
|
<Editor
|
|
|
|
height="100%"
|
|
|
|
language={editorLanguage}
|
|
|
|
beforeMount={handleEditorWillMount}
|
|
|
|
onMount={handleEditorMount}
|
|
|
|
onChange={(value) => {
|
2024-05-04 20:50:00 -07:00
|
|
|
if (value === activeFile) {
|
|
|
|
setTabs((prev) =>
|
|
|
|
prev.map((tab) =>
|
|
|
|
tab.id === activeId ? { ...tab, saved: true } : tab
|
|
|
|
)
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
setTabs((prev) =>
|
|
|
|
prev.map((tab) =>
|
|
|
|
tab.id === activeId ? { ...tab, saved: false } : tab
|
|
|
|
)
|
2024-05-03 14:27:45 -07:00
|
|
|
)
|
2024-05-04 20:50:00 -07:00
|
|
|
}
|
2024-05-03 14:27:45 -07:00
|
|
|
}}
|
|
|
|
options={{
|
|
|
|
minimap: {
|
|
|
|
enabled: false,
|
|
|
|
},
|
|
|
|
padding: {
|
|
|
|
bottom: 4,
|
|
|
|
top: 4,
|
|
|
|
},
|
|
|
|
scrollBeyondLastLine: false,
|
|
|
|
fixedOverflowWidgets: true,
|
|
|
|
fontFamily: "var(--font-geist-mono)",
|
|
|
|
}}
|
|
|
|
theme="vs-dark"
|
|
|
|
value={activeFile ?? ""}
|
|
|
|
/>
|
|
|
|
</>
|
2024-04-18 15:32:27 -04:00
|
|
|
) : null}
|
2024-04-06 19:03:04 -04:00
|
|
|
</div>
|
|
|
|
</ResizablePanel>
|
|
|
|
<ResizableHandle />
|
|
|
|
<ResizablePanel defaultSize={40}>
|
|
|
|
<ResizablePanelGroup direction="vertical">
|
|
|
|
<ResizablePanel
|
|
|
|
defaultSize={50}
|
|
|
|
minSize={20}
|
|
|
|
className="p-2 flex flex-col"
|
|
|
|
>
|
2024-04-08 23:40:42 -04:00
|
|
|
<div className="h-10 select-none w-full flex gap-2">
|
|
|
|
<div className="h-8 rounded-md px-3 text-xs bg-secondary flex items-center w-full justify-between">
|
|
|
|
Preview
|
|
|
|
<div className="flex space-x-1 translate-x-1">
|
2024-04-09 00:50:48 -04:00
|
|
|
<div className="p-0.5 h-5 w-5 ml-0.5 flex items-center justify-center transition-colors bg-transparent hover:bg-muted-foreground/25 cursor-pointer rounded-sm">
|
2024-04-08 23:40:42 -04:00
|
|
|
<TerminalSquare className="w-4 h-4" />
|
|
|
|
</div>
|
2024-04-09 00:50:48 -04:00
|
|
|
<div className="p-0.5 h-5 w-5 ml-0.5 flex items-center justify-center transition-colors bg-transparent hover:bg-muted-foreground/25 cursor-pointer rounded-sm">
|
2024-04-08 23:40:42 -04:00
|
|
|
<ChevronLeft className="w-4 h-4" />
|
|
|
|
</div>
|
2024-04-09 00:50:48 -04:00
|
|
|
<div className="p-0.5 h-5 w-5 ml-0.5 flex items-center justify-center transition-colors bg-transparent hover:bg-muted-foreground/25 cursor-pointer rounded-sm">
|
2024-04-08 23:40:42 -04:00
|
|
|
<ChevronRight className="w-4 h-4" />
|
|
|
|
</div>
|
2024-04-09 00:50:48 -04:00
|
|
|
<div className="p-0.5 h-5 w-5 ml-0.5 flex items-center justify-center transition-colors bg-transparent hover:bg-muted-foreground/25 cursor-pointer rounded-sm">
|
2024-04-08 23:40:42 -04:00
|
|
|
<RotateCw className="w-3 h-3" />
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
2024-04-06 19:03:04 -04:00
|
|
|
</div>
|
2024-04-08 23:40:42 -04:00
|
|
|
<div className="w-full grow rounded-md bg-foreground"></div>
|
2024-04-06 19:03:04 -04:00
|
|
|
</ResizablePanel>
|
|
|
|
<ResizableHandle />
|
|
|
|
<ResizablePanel
|
|
|
|
defaultSize={50}
|
|
|
|
minSize={20}
|
|
|
|
className="p-2 flex flex-col"
|
|
|
|
>
|
2024-04-29 21:36:33 -04:00
|
|
|
<div className="h-10 w-full flex gap-2 shrink-0">
|
2024-04-30 02:00:50 -04:00
|
|
|
<Tab selected>
|
|
|
|
<SquareTerminal className="w-4 h-4 mr-2" />
|
|
|
|
Shell
|
|
|
|
</Tab>
|
|
|
|
<Button
|
2024-05-05 12:55:34 -07:00
|
|
|
onClick={() => {
|
|
|
|
if (terminals.length >= 4) {
|
|
|
|
toast.error("You reached the maximum # of terminals.")
|
|
|
|
}
|
|
|
|
}}
|
2024-04-30 02:00:50 -04:00
|
|
|
size="smIcon"
|
|
|
|
variant={"secondary"}
|
|
|
|
className={`font-normal select-none text-muted-foreground`}
|
|
|
|
>
|
|
|
|
<Plus className="w-4 h-4" />
|
|
|
|
</Button>
|
2024-04-06 19:03:04 -04:00
|
|
|
</div>
|
2024-04-29 21:36:33 -04:00
|
|
|
<div className="w-full relative grow h-full overflow-hidden rounded-md bg-secondary">
|
2024-04-28 20:06:47 -04:00
|
|
|
{socket ? <EditorTerminal socket={socket} /> : null}
|
|
|
|
</div>
|
2024-04-06 19:03:04 -04:00
|
|
|
</ResizablePanel>
|
|
|
|
</ResizablePanelGroup>
|
|
|
|
</ResizablePanel>
|
|
|
|
</ResizablePanelGroup>
|
|
|
|
</>
|
|
|
|
)
|
|
|
|
}
|