generate ui almost done
This commit is contained in:
parent
dc6e5e4cbe
commit
3bdb5be4c4
93
frontend/components/editor/generate.tsx
Normal file
93
frontend/components/editor/generate.tsx
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import { useEffect, useRef, useState } from "react"
|
||||||
|
import { Input } from "../ui/input"
|
||||||
|
import { Button } from "../ui/button"
|
||||||
|
import { Check, Loader2, RotateCw, Sparkles, X } from "lucide-react"
|
||||||
|
|
||||||
|
export default function GenerateInput({
|
||||||
|
cancel,
|
||||||
|
submit,
|
||||||
|
width,
|
||||||
|
onExpand,
|
||||||
|
}: {
|
||||||
|
cancel: () => void
|
||||||
|
submit: (input: string) => void
|
||||||
|
width: number
|
||||||
|
onExpand: () => void
|
||||||
|
}) {
|
||||||
|
const inputRef = useRef<HTMLInputElement>(null)
|
||||||
|
|
||||||
|
const [code, setCode] = useState(`function add(a: number, b: number): number {
|
||||||
|
return a + b;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = add(2, 3);
|
||||||
|
console.log(result);`)
|
||||||
|
const [expanded, setExpanded] = useState(false)
|
||||||
|
const [loading, setLoading] = useState(false)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
inputRef.current?.focus()
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const handleGenerate = async () => {
|
||||||
|
setLoading(true)
|
||||||
|
// const res = await generateCode()
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 1000))
|
||||||
|
|
||||||
|
setExpanded(true)
|
||||||
|
onExpand()
|
||||||
|
setLoading(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="w-full pr-4 space-y-2">
|
||||||
|
<div className="flex items-center font-sans space-x-2">
|
||||||
|
<input
|
||||||
|
ref={inputRef}
|
||||||
|
style={{
|
||||||
|
width: width + "px",
|
||||||
|
}}
|
||||||
|
placeholder="✨ Generate code with a prompt"
|
||||||
|
className="h-8 w-full rounded-md border border-muted-foreground bg-transparent px-3 py-1 text-sm shadow-sm transition-all file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Button size="sm" disabled={loading} onClick={handleGenerate}>
|
||||||
|
{loading ? (
|
||||||
|
<>
|
||||||
|
<Loader2 className="animate-spin h-3 w-3 mr-2" />
|
||||||
|
Generating...
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Sparkles className="h-3 w-3 mr-2" />
|
||||||
|
Generate Code
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
{expanded ? (
|
||||||
|
<>
|
||||||
|
<div className="rounded-md border border-muted-foreground w-full h-28 overflow-y-scroll p-2">
|
||||||
|
<pre>{code}</pre>
|
||||||
|
</div>
|
||||||
|
<div className="flex space-x-2">
|
||||||
|
<Button size="sm">
|
||||||
|
<Check className="h-3 w-3 mr-2" />
|
||||||
|
Accept
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
className="bg-transparent border-muted-foreground"
|
||||||
|
>
|
||||||
|
<RotateCw className="h-3 w-3 mr-2" />
|
||||||
|
Re-Generate
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
@ -31,6 +31,8 @@ import { toast } from "sonner"
|
|||||||
import EditorTerminal from "./terminal"
|
import EditorTerminal from "./terminal"
|
||||||
import { Button } from "../ui/button"
|
import { Button } from "../ui/button"
|
||||||
import { User } from "@/lib/types"
|
import { User } from "@/lib/types"
|
||||||
|
import { Input } from "../ui/input"
|
||||||
|
import GenerateInput from "./generate"
|
||||||
|
|
||||||
export default function CodeEditor({
|
export default function CodeEditor({
|
||||||
userData,
|
userData,
|
||||||
@ -45,7 +47,13 @@ export default function CodeEditor({
|
|||||||
const [tabs, setTabs] = useState<TTab[]>([])
|
const [tabs, setTabs] = useState<TTab[]>([])
|
||||||
const [activeId, setActiveId] = useState<string | null>(null)
|
const [activeId, setActiveId] = useState<string | null>(null)
|
||||||
const [cursorLine, setCursorLine] = useState(0)
|
const [cursorLine, setCursorLine] = useState(0)
|
||||||
const [generate, setGenerate] = useState({ show: false, id: "" })
|
const [generate, setGenerate] = useState<{
|
||||||
|
show: boolean
|
||||||
|
id: string
|
||||||
|
widget: monaco.editor.IContentWidget | undefined
|
||||||
|
pref: monaco.editor.ContentWidgetPositionPreference[]
|
||||||
|
width: number
|
||||||
|
}>({ show: false, id: "", widget: undefined, pref: [], width: 0 })
|
||||||
const [decorations, setDecorations] = useState<{
|
const [decorations, setDecorations] = useState<{
|
||||||
options: monaco.editor.IModelDeltaDecoration[]
|
options: monaco.editor.IModelDeltaDecoration[]
|
||||||
instance: monaco.editor.IEditorDecorationsCollection | undefined
|
instance: monaco.editor.IEditorDecorationsCollection | undefined
|
||||||
@ -55,8 +63,10 @@ export default function CodeEditor({
|
|||||||
const clerk = useClerk()
|
const clerk = useClerk()
|
||||||
|
|
||||||
const editorRef = useRef<monaco.editor.IStandaloneCodeEditor | null>(null)
|
const editorRef = useRef<monaco.editor.IStandaloneCodeEditor | null>(null)
|
||||||
|
const editorContainerRef = useRef<HTMLDivElement>(null)
|
||||||
const monacoRef = useRef<typeof monaco | null>(null)
|
const monacoRef = useRef<typeof monaco | null>(null)
|
||||||
const generateRef = useRef<HTMLDivElement>(null)
|
const generateRef = useRef<HTMLDivElement>(null)
|
||||||
|
const generateWidgetRef = useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
const handleEditorWillMount: BeforeMount = (monaco) => {
|
const handleEditorWillMount: BeforeMount = (monaco) => {
|
||||||
monaco.editor.addKeybindingRules([
|
monaco.editor.addKeybindingRules([
|
||||||
@ -100,6 +110,15 @@ export default function CodeEditor({
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
editor.onDidBlurEditorText((e) => {
|
||||||
|
setDecorations((prev) => {
|
||||||
|
return {
|
||||||
|
...prev,
|
||||||
|
options: [],
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
editor.addAction({
|
editor.addAction({
|
||||||
id: "generate",
|
id: "generate",
|
||||||
label: "Generate",
|
label: "Generate",
|
||||||
@ -108,7 +127,11 @@ export default function CodeEditor({
|
|||||||
"editorTextFocus && !suggestWidgetVisible && !renameInputVisible && !inSnippetMode && !quickFixWidgetVisible",
|
"editorTextFocus && !suggestWidgetVisible && !renameInputVisible && !inSnippetMode && !quickFixWidgetVisible",
|
||||||
run: () => {
|
run: () => {
|
||||||
setGenerate((prev) => {
|
setGenerate((prev) => {
|
||||||
return { ...prev, show: !prev.show }
|
return {
|
||||||
|
...prev,
|
||||||
|
show: !prev.show,
|
||||||
|
pref: [monaco.editor.ContentWidgetPositionPreference.BELOW],
|
||||||
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@ -127,26 +150,64 @@ export default function CodeEditor({
|
|||||||
return { ...prev, id }
|
return { ...prev, id }
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
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 }
|
||||||
|
})
|
||||||
|
editorRef.current?.addContentWidget(contentWidget)
|
||||||
|
|
||||||
|
if (generateRef.current && generateWidgetRef.current) {
|
||||||
|
editorRef.current?.applyFontInfo(generateRef.current)
|
||||||
|
editorRef.current?.applyFontInfo(generateWidgetRef.current)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
editorRef.current?.changeViewZones(function (changeAccessor) {
|
editorRef.current?.changeViewZones(function (changeAccessor) {
|
||||||
if (!generateRef.current) return
|
|
||||||
changeAccessor.removeZone(generate.id)
|
changeAccessor.removeZone(generate.id)
|
||||||
setGenerate((prev) => {
|
setGenerate((prev) => {
|
||||||
return { ...prev, id: "" }
|
return { ...prev, id: "" }
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (!generate.widget) return
|
||||||
|
editorRef.current?.removeContentWidget(generate.widget)
|
||||||
|
setGenerate((prev) => {
|
||||||
|
return {
|
||||||
|
...prev,
|
||||||
|
widget: undefined,
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}, [generate.show])
|
}, [generate.show])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (decorations.options.length === 0) return
|
if (decorations.options.length === 0) {
|
||||||
|
decorations.instance?.clear()
|
||||||
|
}
|
||||||
|
|
||||||
if (decorations.instance) {
|
if (decorations.instance) {
|
||||||
console.log("setting decorations")
|
|
||||||
// decorations.instance.clear()
|
|
||||||
decorations.instance.set(decorations.options)
|
decorations.instance.set(decorations.options)
|
||||||
} else {
|
} else {
|
||||||
console.log("creating decorations")
|
|
||||||
const instance = editorRef.current?.createDecorationsCollection()
|
const instance = editorRef.current?.createDecorationsCollection()
|
||||||
instance?.set(decorations.options)
|
instance?.set(decorations.options)
|
||||||
|
|
||||||
@ -169,7 +230,7 @@ export default function CodeEditor({
|
|||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
|
||||||
const activeTab = tabs.find((t) => t.id === activeId)
|
const activeTab = tabs.find((t) => t.id === activeId)
|
||||||
console.log("saving:", activeTab?.name, editorRef.current?.getValue())
|
// console.log("saving:", activeTab?.name, editorRef.current?.getValue())
|
||||||
|
|
||||||
setTabs((prev) =>
|
setTabs((prev) =>
|
||||||
prev.map((tab) =>
|
prev.map((tab) =>
|
||||||
@ -187,14 +248,27 @@ export default function CodeEditor({
|
|||||||
}
|
}
|
||||||
}, [tabs, activeId])
|
}, [tabs, activeId])
|
||||||
|
|
||||||
// WS event handlers:
|
const resizeObserver = new ResizeObserver((entries) => {
|
||||||
|
for (const entry of entries) {
|
||||||
|
const { width } = entry.contentRect
|
||||||
|
setGenerate((prev) => {
|
||||||
|
return { ...prev, width }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
// connection/disconnection effect
|
// connection/disconnection effect + resizeobserver
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
socket.connect()
|
socket.connect()
|
||||||
|
|
||||||
|
if (editorContainerRef.current) {
|
||||||
|
resizeObserver.observe(editorContainerRef.current)
|
||||||
|
}
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
socket.disconnect()
|
socket.disconnect()
|
||||||
|
|
||||||
|
resizeObserver.disconnect()
|
||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
@ -205,7 +279,6 @@ export default function CodeEditor({
|
|||||||
const onDisconnect = () => {}
|
const onDisconnect = () => {}
|
||||||
|
|
||||||
const onLoadedEvent = (files: (TFolder | TFile)[]) => {
|
const onLoadedEvent = (files: (TFolder | TFile)[]) => {
|
||||||
console.log("onLoadedEvent")
|
|
||||||
setFiles(files)
|
setFiles(files)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -227,7 +300,6 @@ export default function CodeEditor({
|
|||||||
setTabs((prev) => {
|
setTabs((prev) => {
|
||||||
const exists = prev.find((t) => t.id === tab.id)
|
const exists = prev.find((t) => t.id === tab.id)
|
||||||
if (exists) {
|
if (exists) {
|
||||||
// console.log("exists")
|
|
||||||
setActiveId(exists.id)
|
setActiveId(exists.id)
|
||||||
return prev
|
return prev
|
||||||
}
|
}
|
||||||
@ -269,7 +341,6 @@ export default function CodeEditor({
|
|||||||
) => {
|
) => {
|
||||||
if (!validateName(newName, oldName, type)) {
|
if (!validateName(newName, oldName, type)) {
|
||||||
toast.error("Invalid file name.")
|
toast.error("Invalid file name.")
|
||||||
console.log("invalid name")
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -296,9 +367,32 @@ export default function CodeEditor({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="bg-blue-500" ref={generateRef}>
|
<div ref={generateRef} />
|
||||||
{generate.show ? "HELLO" : null}
|
<div className="z-50 p-1" ref={generateWidgetRef}>
|
||||||
|
{generate.show ? (
|
||||||
|
<GenerateInput
|
||||||
|
cancel={() => {}}
|
||||||
|
submit={(str: string) => {}}
|
||||||
|
width={generate.width - 90}
|
||||||
|
onExpand={() => {
|
||||||
|
editorRef.current?.changeViewZones(function (changeAccessor) {
|
||||||
|
changeAccessor.removeZone(generate.id)
|
||||||
|
|
||||||
|
if (!generateRef.current) return
|
||||||
|
const id = changeAccessor.addZone({
|
||||||
|
afterLineNumber: cursorLine,
|
||||||
|
heightInLines: 12,
|
||||||
|
domNode: generateRef.current,
|
||||||
|
})
|
||||||
|
setGenerate((prev) => {
|
||||||
|
return { ...prev, id }
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Sidebar
|
<Sidebar
|
||||||
files={files}
|
files={files}
|
||||||
selectFile={selectFile}
|
selectFile={selectFile}
|
||||||
@ -308,7 +402,6 @@ export default function CodeEditor({
|
|||||||
socket={socket}
|
socket={socket}
|
||||||
addNew={(name, type) => {
|
addNew={(name, type) => {
|
||||||
if (type === "file") {
|
if (type === "file") {
|
||||||
console.log("adding file")
|
|
||||||
setFiles((prev) => [
|
setFiles((prev) => [
|
||||||
...prev,
|
...prev,
|
||||||
{ id: `projects/${sandboxId}/${name}`, name, type: "file" },
|
{ id: `projects/${sandboxId}/${name}`, name, type: "file" },
|
||||||
@ -341,7 +434,10 @@ export default function CodeEditor({
|
|||||||
</Tab>
|
</Tab>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div className="grow w-full overflow-hidden rounded-md">
|
<div
|
||||||
|
ref={editorContainerRef}
|
||||||
|
className="grow w-full overflow-hidden rounded-md"
|
||||||
|
>
|
||||||
{activeId === null ? (
|
{activeId === null ? (
|
||||||
<>
|
<>
|
||||||
<div className="w-full h-full flex items-center justify-center text-xl font-medium text-secondary select-none">
|
<div className="w-full h-full flex items-center justify-center text-xl font-medium text-secondary select-none">
|
||||||
|
Loading…
x
Reference in New Issue
Block a user