Canvas: Improve layout
Change-Id: Ife4f14d23eefc0ef0cb6b189446590fc42b8d797
diff --git a/apps/canvas/front/src/App.tsx b/apps/canvas/front/src/App.tsx
index afacdb1..f37370a 100644
--- a/apps/canvas/front/src/App.tsx
+++ b/apps/canvas/front/src/App.tsx
@@ -5,38 +5,42 @@
import { Config } from "./Config";
import { Integrations } from "./Integrations";
import { Toaster } from "./components/ui/toaster";
-import { Header } from "./Header";
+import { ProjectSelect } from "./ProjectSelect";
import { Logs } from "./components/logs";
export default function App() {
return (
<ReactFlowProvider>
- <Header />
- <AppImpl />
- <Toaster />
+ <div className="h-screen flex flex-col">
+ <AppImpl />
+ <Toaster />
+ </div>
</ReactFlowProvider>
);
}
function AppImpl() {
return (
- <Tabs defaultValue="canvas">
- <TabsList>
- <TabsTrigger value="canvas">Canvas</TabsTrigger>
- <TabsTrigger value="config">Config</TabsTrigger>
- <TabsTrigger value="integrations">Integrations</TabsTrigger>
- <TabsTrigger value="logs">Logs</TabsTrigger>
- </TabsList>
- <TabsContent value="canvas">
+ <Tabs defaultValue="canvas" className="flex-1 flex flex-col min-h-0">
+ <div className="flex items-center justify-between px-4 border-b">
+ <TabsList>
+ <TabsTrigger value="canvas">Canvas</TabsTrigger>
+ <TabsTrigger value="logs">Logs</TabsTrigger>
+ <TabsTrigger value="config">Config</TabsTrigger>
+ <TabsTrigger value="integrations">Integrations</TabsTrigger>
+ </TabsList>
+ <ProjectSelect />
+ </div>
+ <TabsContent value="canvas" className="flex-1 min-h-0">
<CanvasBuilder />
</TabsContent>
- <TabsContent value="config">
+ <TabsContent value="config" className="flex-1 min-h-0">
<Config />
</TabsContent>
- <TabsContent value="integrations">
+ <TabsContent value="integrations" className="flex-1 min-h-0">
<Integrations />
</TabsContent>
- <TabsContent value="logs">
+ <TabsContent value="logs" className="flex-1 min-h-0">
<Logs />
</TabsContent>
</Tabs>
diff --git a/apps/canvas/front/src/Config.tsx b/apps/canvas/front/src/Config.tsx
index df03746..7a93634 100644
--- a/apps/canvas/front/src/Config.tsx
+++ b/apps/canvas/front/src/Config.tsx
@@ -2,6 +2,8 @@
import { AppNode, useEnv, useProjectId } from "./lib/state";
import { generateDodoConfig } from "./lib/config";
import { useEffect, useMemo, useState } from "react";
+import { Card, CardContent } from "./components/ui/card";
+import JSONView from "@microlink/react-json-view";
export function Config() {
const env = useEnv();
@@ -14,11 +16,21 @@
setNodes(n);
}
}, [n, setNodes]);
- const config = useMemo(() => generateDodoConfig(projectId, nodes, env), [projectId, nodes, env]);
- const configS = useMemo(() => JSON.stringify(config, undefined, 4), [config]);
+ const config = useMemo(() => generateDodoConfig(projectId, nodes, env) || {}, [projectId, nodes, env]);
return (
- <div className="px-5">
- <pre>{configS}</pre>
- </div>
+ <Card className="h-full flex flex-col">
+ <CardContent className="flex-1 min-h-0 p-4">
+ <div className="h-full p-4 bg-muted rounded-lg overflow-auto">
+ <JSONView
+ src={config as object}
+ theme="rjv-default"
+ name={false}
+ displayDataTypes={false}
+ enableClipboard={true}
+ style={{ fontFamily: "JetBrains Mono" }}
+ />
+ </div>
+ </CardContent>
+ </Card>
);
}
diff --git a/apps/canvas/front/src/Header.tsx b/apps/canvas/front/src/ProjectSelect.tsx
similarity index 78%
rename from apps/canvas/front/src/Header.tsx
rename to apps/canvas/front/src/ProjectSelect.tsx
index 979d85c..5178b5e 100644
--- a/apps/canvas/front/src/Header.tsx
+++ b/apps/canvas/front/src/ProjectSelect.tsx
@@ -7,7 +7,7 @@
import { Dialog, DialogContent, DialogTrigger } from "./components/ui/dialog";
import { useToast } from "@/hooks/use-toast";
-export function Header() {
+export function ProjectSelect() {
const { toast } = useToast();
const store = useStateStore();
const [projects, setProjects] = useState<Project[]>([]);
@@ -96,26 +96,26 @@
});
}, [name, setCreateNewOpen, toast, store, refreshProjects]);
return (
- <div className="flex flex-row h-9">
- <Select onValueChange={onSelect} value={project}>
- <SelectTrigger>
- <SelectValue placeholder="Choose Project" defaultValue={project} />
- </SelectTrigger>
- <SelectContent>
- {projects.map((p) => (
- <SelectItem value={p.id}>{p.name}</SelectItem>
- ))}
- <SelectItem value={"create-new"}>
- <Dialog open={createNewOpen} onOpenChange={setCreateNewOpen}>
- <DialogTrigger>Create New</DialogTrigger>
- <DialogContent>
- <Input type="text" placeholder="Name" onChange={updateName} />
- <Button onClick={createNew}>Create New</Button>
- </DialogContent>
- </Dialog>
+ <Select onValueChange={onSelect} value={project}>
+ <SelectTrigger className="w-[200px]">
+ <SelectValue placeholder="Choose Project" defaultValue={project} />
+ </SelectTrigger>
+ <SelectContent>
+ <SelectItem value={"create-new"}>
+ <Dialog open={createNewOpen} onOpenChange={setCreateNewOpen}>
+ <DialogTrigger>Create New</DialogTrigger>
+ <DialogContent>
+ <Input type="text" placeholder="Name" onChange={updateName} />
+ <Button onClick={createNew}>Create New</Button>
+ </DialogContent>
+ </Dialog>
+ </SelectItem>
+ {projects.map((p) => (
+ <SelectItem key={p.id} value={p.id}>
+ {p.name}
</SelectItem>
- </SelectContent>
- </Select>
- </div>
+ ))}
+ </SelectContent>
+ </Select>
);
}
diff --git a/apps/canvas/front/src/components/details.tsx b/apps/canvas/front/src/components/details.tsx
index 252952d..cb7d7fe 100644
--- a/apps/canvas/front/src/components/details.tsx
+++ b/apps/canvas/front/src/components/details.tsx
@@ -5,6 +5,7 @@
import { AccordionItem } from "@radix-ui/react-accordion";
import { useMemo, useState } from "react";
import { Icon } from "./icon";
+import { Separator } from "./ui/separator";
function unique<T>(v: T, i: number, a: T[]) {
return a.indexOf(v) === i;
@@ -42,18 +43,21 @@
const all = useMemo(() => open.concat(selected).filter(unique), [open, selected]);
return (
<Accordion type="multiple" value={all} onValueChange={(v) => setOpen(v)}>
- {sorted.map((n) => (
- <AccordionItem key={n.id} value={n.id} className="px-3">
- <AccordionTrigger>
- <div className="flex flex-row space-x-2">
- {Icon(n.type)}
- <span>{nodeLabel(n)}</span>
- </div>
- </AccordionTrigger>
- <AccordionContent>
- <NodeDetails {...n} />
- </AccordionContent>
- </AccordionItem>
+ {sorted.map((n, index) => (
+ <>
+ {index > 0 && <Separator className="my-2" />}
+ <AccordionItem key={n.id} value={n.id} className="px-3">
+ <AccordionTrigger>
+ <div className="flex flex-row space-x-2">
+ {Icon(n.type)}
+ <span>{nodeLabel(n)}</span>
+ </div>
+ </AccordionTrigger>
+ <AccordionContent>
+ <NodeDetails {...n} />
+ </AccordionContent>
+ </AccordionItem>
+ </>
))}
</Accordion>
);
diff --git a/apps/canvas/front/src/components/logs.tsx b/apps/canvas/front/src/components/logs.tsx
index 5918cf7..a2bf1ba 100644
--- a/apps/canvas/front/src/components/logs.tsx
+++ b/apps/canvas/front/src/components/logs.tsx
@@ -5,6 +5,7 @@
import { useToast } from "@/hooks/use-toast";
// ANSI escape sequence regex
+// eslint-disable-next-line no-control-regex
const ANSI_ESCAPE_REGEX = /\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g;
function cleanAnsiEscapeSequences(text: string): string {
@@ -98,7 +99,7 @@
}, [sortedServices, selectedService]);
return (
- <Card>
+ <Card className="h-full flex flex-col">
<CardHeader>
<Select value={selectedService} onValueChange={setSelectedService}>
<SelectTrigger>
@@ -113,11 +114,11 @@
</SelectContent>
</Select>
</CardHeader>
- <CardContent className="h-full">
+ <CardContent className="flex-1 min-h-0">
{selectedService && (
<pre
ref={preRef}
- className="p-4 bg-muted rounded-lg overflow-auto max-h-[500px] font-['JetBrains_Mono'] whitespace-pre-wrap break-all"
+ className="h-full p-4 bg-muted rounded-lg overflow-auto font-['JetBrains_Mono'] whitespace-pre-wrap break-all"
>
{cleanAnsiEscapeSequences(logs) || "No logs available"}
</pre>