From 3bdb5be4c4e49f5b788f17e38b216d98e6b2a0cb Mon Sep 17 00:00:00 2001 From: Ishaan Dey Date: Thu, 2 May 2024 17:38:37 -0700 Subject: [PATCH] generate ui almost done --- frontend/components/editor/generate.tsx | 93 +++++++++++++++++ frontend/components/editor/index.tsx | 130 ++++++++++++++++++++---- 2 files changed, 206 insertions(+), 17 deletions(-) create mode 100644 frontend/components/editor/generate.tsx diff --git a/frontend/components/editor/generate.tsx b/frontend/components/editor/generate.tsx new file mode 100644 index 0000000..b37cb6e --- /dev/null +++ b/frontend/components/editor/generate.tsx @@ -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(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 ( +
+
+ + + +
+ {expanded ? ( + <> +
+
{code}
+
+
+ + +
+ + ) : null} +
+ ) +} diff --git a/frontend/components/editor/index.tsx b/frontend/components/editor/index.tsx index 35e3750..8ad43f1 100644 --- a/frontend/components/editor/index.tsx +++ b/frontend/components/editor/index.tsx @@ -31,6 +31,8 @@ import { toast } from "sonner" import EditorTerminal from "./terminal" import { Button } from "../ui/button" import { User } from "@/lib/types" +import { Input } from "../ui/input" +import GenerateInput from "./generate" export default function CodeEditor({ userData, @@ -45,7 +47,13 @@ export default function CodeEditor({ const [tabs, setTabs] = useState([]) const [activeId, setActiveId] = useState(null) 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<{ options: monaco.editor.IModelDeltaDecoration[] instance: monaco.editor.IEditorDecorationsCollection | undefined @@ -55,8 +63,10 @@ export default function CodeEditor({ const clerk = useClerk() const editorRef = useRef(null) + const editorContainerRef = useRef(null) const monacoRef = useRef(null) const generateRef = useRef(null) + const generateWidgetRef = useRef(null) const handleEditorWillMount: BeforeMount = (monaco) => { monaco.editor.addKeybindingRules([ @@ -100,6 +110,15 @@ export default function CodeEditor({ }) }) + editor.onDidBlurEditorText((e) => { + setDecorations((prev) => { + return { + ...prev, + options: [], + } + }) + }) + editor.addAction({ id: "generate", label: "Generate", @@ -108,7 +127,11 @@ export default function CodeEditor({ "editorTextFocus && !suggestWidgetVisible && !renameInputVisible && !inSnippetMode && !quickFixWidgetVisible", run: () => { 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 } }) }) + + 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 { editorRef.current?.changeViewZones(function (changeAccessor) { - if (!generateRef.current) return changeAccessor.removeZone(generate.id) setGenerate((prev) => { return { ...prev, id: "" } }) }) + + if (!generate.widget) return + editorRef.current?.removeContentWidget(generate.widget) + setGenerate((prev) => { + return { + ...prev, + widget: undefined, + } + }) } }, [generate.show]) useEffect(() => { - if (decorations.options.length === 0) return + if (decorations.options.length === 0) { + decorations.instance?.clear() + } if (decorations.instance) { - console.log("setting decorations") - // decorations.instance.clear() decorations.instance.set(decorations.options) } else { - console.log("creating decorations") const instance = editorRef.current?.createDecorationsCollection() instance?.set(decorations.options) @@ -169,7 +230,7 @@ export default function CodeEditor({ e.preventDefault() 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) => prev.map((tab) => @@ -187,14 +248,27 @@ export default function CodeEditor({ } }, [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(() => { socket.connect() + if (editorContainerRef.current) { + resizeObserver.observe(editorContainerRef.current) + } + return () => { socket.disconnect() + + resizeObserver.disconnect() } }, []) @@ -205,7 +279,6 @@ export default function CodeEditor({ const onDisconnect = () => {} const onLoadedEvent = (files: (TFolder | TFile)[]) => { - console.log("onLoadedEvent") setFiles(files) } @@ -227,7 +300,6 @@ export default function CodeEditor({ setTabs((prev) => { const exists = prev.find((t) => t.id === tab.id) if (exists) { - // console.log("exists") setActiveId(exists.id) return prev } @@ -269,7 +341,6 @@ export default function CodeEditor({ ) => { if (!validateName(newName, oldName, type)) { toast.error("Invalid file name.") - console.log("invalid name") return false } @@ -296,9 +367,32 @@ export default function CodeEditor({ return ( <> -
- {generate.show ? "HELLO" : null} +
+
+ {generate.show ? ( + {}} + 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}
+ { if (type === "file") { - console.log("adding file") setFiles((prev) => [ ...prev, { id: `projects/${sandboxId}/${name}`, name, type: "file" }, @@ -341,7 +434,10 @@ export default function CodeEditor({ ))}
-
+
{activeId === null ? ( <>