From e1357d2ab5e049dec87e4fecd8a079ba286bc6ac Mon Sep 17 00:00:00 2001 From: "Visal .In" <invisal@gmail.com> Date: Thu, 16 Jan 2025 21:29:39 +0700 Subject: [PATCH] query status indicator in notebook --- .../notebook/notebook-block-code.tsx | 86 +++++++++++++------ src/extensions/notebook/notebook-editor.tsx | 2 +- src/extensions/notebook/notebook-vm.tsx | 20 +++-- 3 files changed, 74 insertions(+), 34 deletions(-) diff --git a/src/extensions/notebook/notebook-block-code.tsx b/src/extensions/notebook/notebook-block-code.tsx index 5b97dcca..e9eaeb8f 100644 --- a/src/extensions/notebook/notebook-block-code.tsx +++ b/src/extensions/notebook/notebook-block-code.tsx @@ -3,12 +3,21 @@ import { NotebookEditorBlockValue } from "./notebook-editor"; import { NotebookVM } from "./notebook-vm"; import JavascriptEditor from "@/components/editor/javascript-editor"; import { Button } from "@/components/ui/button"; -import { LucideShieldAlert, PlayIcon, Terminal } from "lucide-react"; +import { + LucideLoader, + LucideShieldAlert, + LucideTerminal, + PlayIcon, + Terminal, +} from "lucide-react"; import { produce } from "immer"; import { cn } from "@/lib/utils"; -interface OutputFormat { - type: "log"; +export interface NotebookOutputFormat { + id?: string; + type: "log" | "error" | "query"; + sql?: string; + queryStatus?: "running" | "success" | "error"; args: unknown[]; } @@ -42,25 +51,53 @@ function OutputArgItem({ value }: { value: unknown }) { return <span className="mr-2">{content}</span>; } -function OutputItem({ value }: { value: OutputFormat }) { - const color = value.type === "log" ? "" : "text-red-400 dark:text-red-300"; +function OutputItem({ value }: { value: NotebookOutputFormat }) { + if (value.type === "query") { + let icon = <LucideLoader className="w-5 h-5 animate-spin" />; + let color = ""; - return ( - <div className={cn(color, "flex")}> - {value.type === "log" ? ( - <div className="w-7"></div> - ) : ( + if (value.queryStatus === "success") { + icon = <LucideTerminal className="w-5 h-5" />; + color = "text-green-600 dark:text-green-400"; + } else if (value.queryStatus === "error") { + icon = <LucideTerminal className="w-5 h-5" />; + color = "text-red-400 dark:text-red-300"; + } + + return ( + <div className="flex"> + <div className="w-7">{icon}</div> + <pre className={"flex-1"}> + <span className={cn(color, "mr-2")}>⬤</span> + {value.sql} + </pre> + </div> + ); + } else if (value.type === "error") { + return ( + <div className="flex text-red-400 dark:text-red-300"> <div className="w-7"> <LucideShieldAlert className="w-5 h-5" /> </div> - )} - <pre className="flex-1"> - {value.args.map((argValue, argIndex) => ( - <OutputArgItem value={argValue} key={argIndex} /> - ))} - </pre> - </div> - ); + <pre className="flex-1"> + {value.args.map((argValue, argIndex) => ( + <OutputArgItem value={argValue} key={argIndex} /> + ))} + </pre> + </div> + ); + } else { + return ( + <div className="flex"> + <div className="w-7"></div> + <pre className="flex-1"> + {value.args.map((argValue, argIndex) => ( + <OutputArgItem value={argValue} key={argIndex} /> + ))} + </pre> + </div> + ); + } } export default function NotebookBlockCode({ @@ -72,7 +109,7 @@ export default function NotebookBlockCode({ value: NotebookEditorBlockValue; onChange: (value: NotebookEditorBlockValue) => void; }) { - const [output, setOutput] = useState<OutputFormat[]>([]); + const [output, setOutput] = useState<NotebookOutputFormat[]>([]); const onRunClick = () => { setOutput([]); @@ -80,11 +117,12 @@ export default function NotebookBlockCode({ complete: () => { console.log("Complete"); }, - stdOut: (data: any) => { - setOutput((prev) => [...prev, data]); - }, - stdErr: () => { - console.log("Error"); + stdOut: (data) => { + if (data.id) { + setOutput((prev) => [...prev.filter((p) => p.id !== data.id), data]); + } else { + setOutput((prev) => [...prev, data]); + } }, }); }; diff --git a/src/extensions/notebook/notebook-editor.tsx b/src/extensions/notebook/notebook-editor.tsx index 3b824bd1..2d8f98f6 100644 --- a/src/extensions/notebook/notebook-editor.tsx +++ b/src/extensions/notebook/notebook-editor.tsx @@ -47,7 +47,7 @@ export default function NotebookEditor() { value: `for(let i = 0; i < 5; i++) { await sleep(1000); const age = Math.floor(Math.random() * 100)); - const name = "name \${i}"; + const name = \`name \${i}\`; await query(\`INSERT INTO testing(name, age) VALUES ('\${name}', \${age})\`); console.log("Inserting", name, age); }`, diff --git a/src/extensions/notebook/notebook-vm.tsx b/src/extensions/notebook/notebook-vm.tsx index 33f90485..448d5ee7 100644 --- a/src/extensions/notebook/notebook-vm.tsx +++ b/src/extensions/notebook/notebook-vm.tsx @@ -1,5 +1,6 @@ import { BaseDriver } from "@/drivers/base-driver"; import { useEffect, useMemo } from "react"; +import { NotebookOutputFormat } from "./notebook-block-code"; const workerCode = ` let scope = {}; @@ -52,16 +53,14 @@ const workerCode = ` interface RunOpions { complete?: () => void; - stdOut?: <T = any>(data: T) => void; - stdErr?: () => void; + stdOut?: (data: NotebookOutputFormat) => void; } export class NotebookVM { protected vm: Worker; protected driver: BaseDriver; protected onComplete?: () => void; - protected onStdOut?: <T = any>(data: T) => void; - protected onStdErr?: () => void; + protected onStdOut?: (data: NotebookOutputFormat) => void; constructor(vm: Worker, driver: BaseDriver) { this.vm = vm; @@ -71,21 +70,25 @@ export class NotebookVM { const { type } = e.data; if (type === "log" || type === "error") { - if (this.onStdOut) { - this.onStdOut(e.data); - } + if (this.onStdOut) this.onStdOut(e.data); } else if (type === "query") { + if (this.onStdOut) this.onStdOut({ ...e.data, queryStatus: "running" }); + this.driver .query(e.data.sql) .then((result) => { - console.log("Got it result", result); this.vm.postMessage({ type: "query_result", id: e.data.id, result: result, }); + if (this.onStdOut) + this.onStdOut({ ...e.data, queryStatus: "success" }); }) .catch((error) => { + if (this.onStdOut) + this.onStdOut({ ...e.data, queryStatus: "error" }); + if (error instanceof Error) { this.vm.postMessage({ type: "query_result", @@ -111,7 +114,6 @@ export class NotebookVM { run(code: string, options: RunOpions): void { this.onComplete = options.complete; this.onStdOut = options.stdOut; - this.onStdErr = options.stdErr; this.vm.postMessage({ type: "eval",