Skip to content

Commit 4d6c998

Browse files
authored
Merge pull request #229 from outerbase/add-dolt-support
WIP: Add Dolt database version control support
2 parents c898a0b + 693aa7b commit 4d6c998

File tree

9 files changed

+706
-25
lines changed

9 files changed

+706
-25
lines changed
+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
"use client";
2+
import { Studio } from "@/components/gui/studio";
3+
import { IframeDoltDriver } from "@/drivers/iframe-driver";
4+
import { useSearchParams } from "next/navigation";
5+
import { useEffect, useMemo } from "react";
6+
7+
export default function EmbedPageClient() {
8+
const searchParams = useSearchParams();
9+
const driver = useMemo(() => new IframeDoltDriver(), []);
10+
11+
useEffect(() => {
12+
return driver.listen();
13+
}, [driver]);
14+
15+
return (
16+
<Studio
17+
driver={driver}
18+
name={searchParams.get("name") || "Unnamed Connection"}
19+
color={searchParams.get("color") || "gray"}
20+
/>
21+
);
22+
}

src/app/(theme)/embed/dolt/page.tsx

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import ThemeLayout from "../../theme_layout";
2+
import EmbedPageClient from "./page-client";
3+
4+
export default async function EmbedPage(props: {
5+
searchParams: {
6+
theme?: string;
7+
disableThemeToggle?: string;
8+
[key: string]: any;
9+
};
10+
}) {
11+
let overrideTheme: "dark" | "light" | undefined = undefined;
12+
const disableToggle = props.searchParams.disableThemeToggle === "1";
13+
14+
if (props.searchParams.theme) {
15+
overrideTheme = props.searchParams.theme === "dark" ? "dark" : "light";
16+
}
17+
18+
const overrideThemeVariables: Record<string, string> = {};
19+
20+
for (const key in props.searchParams) {
21+
if (!key.startsWith("themeVariables[")) {
22+
continue;
23+
}
24+
25+
overrideThemeVariables[key.slice(15, -1)] = props.searchParams[key];
26+
}
27+
28+
return (
29+
<ThemeLayout
30+
overrideTheme={overrideTheme}
31+
disableToggle={disableToggle}
32+
overrideThemeVariables={overrideThemeVariables}
33+
>
34+
<EmbedPageClient />
35+
</ThemeLayout>
36+
);
37+
}

src/components/common-dialog/index.tsx

+15-12
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
DialogDescription,
1515
DialogFooter,
1616
DialogHeader,
17+
DialogTitle,
1718
} from "../ui/dialog";
1819
import { Button } from "../ui/button";
1920
import { Icon } from "@phosphor-icons/react";
@@ -70,20 +71,22 @@ export function CommonDialogProvider({ children }: PropsWithChildren) {
7071
<DialogHeader
7172
className={dialogOption?.destructive ? "text-red-500" : ""}
7273
>
73-
{dialogOption.title}
74+
<DialogTitle>{dialogOption.title}</DialogTitle>
7475
</DialogHeader>
75-
<DialogDescription className="flex flex-col gap-2">
76-
{errorMessage && (
77-
<div className="text-sm text-red-500 font-mono flex gap-4 items-end">
78-
<p>{errorMessage}</p>
79-
</div>
80-
)}
8176

82-
<div>{dialogOption.content}</div>
83-
{dialogOption.previewCode && (
84-
<CodePreview code={dialogOption.previewCode} />
85-
)}
86-
</DialogDescription>
77+
{errorMessage && (
78+
<div className="text-sm text-red-500 font-mono flex gap-4 items-end">
79+
<p>{errorMessage}</p>
80+
</div>
81+
)}
82+
83+
<div>{dialogOption.content}</div>
84+
{dialogOption.previewCode && (
85+
<CodePreview code={dialogOption.previewCode} />
86+
)}
87+
88+
<DialogDescription className="flex flex-col gap-2"></DialogDescription>
89+
8790
<DialogFooter>
8891
{dialogOption.actions?.map((action) => (
8992
<Button

src/components/gui/database-gui.tsx

+23-11
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ import { useDatabaseDriver } from "@/context/driver-provider";
1919
import SavedDocTab from "./sidebar/saved-doc-tab";
2020
import { useSchema } from "@/context/schema-provider";
2121
import { Binoculars, GearSix, Table } from "@phosphor-icons/react";
22+
import DoltSidebar from "./database-specified/dolt/dolt-sidebar";
23+
import { DoltIcon } from "../icons/outerbase-icon";
2224

2325
export default function DatabaseGui() {
2426
const DEFAULT_WIDTH = 300;
@@ -30,6 +32,8 @@ export default function DatabaseGui() {
3032
}, []);
3133

3234
const { databaseDriver, docDriver } = useDatabaseDriver();
35+
const databaseDriverDialect = databaseDriver.getFlags().dialect;
36+
3337
const [selectedTabIndex, setSelectedTabIndex] = useState(0);
3438
const { currentSchemaName } = useSchema();
3539
const [tabs, setTabs] = useState<WindowTabItemProps[]>(() => [
@@ -91,20 +95,28 @@ export default function DatabaseGui() {
9195
},
9296
docDriver
9397
? {
94-
key: "saved",
95-
name: "Queries",
96-
content: <SavedDocTab />,
97-
icon: <Binoculars weight="light" size={24} />,
98-
}
98+
key: "saved",
99+
name: "Queries",
100+
content: <SavedDocTab />,
101+
icon: <Binoculars weight="light" size={24} />,
102+
}
99103
: undefined,
100104
{
101105
key: "tools",
102106
name: "Tools",
103107
content: <ToolSidebar />,
104108
icon: <GearSix weight="light" size={24} />,
105109
},
110+
databaseDriverDialect
111+
? {
112+
key: "dolt",
113+
name: "Dolt",
114+
content: <DoltSidebar />,
115+
icon: <DoltIcon className="w-7 h-7 text-green-500" />,
116+
}
117+
: undefined,
106118
].filter(Boolean) as SidebarTabItem[];
107-
}, [docDriver]);
119+
}, [docDriver, databaseDriverDialect]);
108120

109121
const tabSideMenu = useMemo(() => {
110122
return [
@@ -116,11 +128,11 @@ export default function DatabaseGui() {
116128
},
117129
databaseDriver.getFlags().supportCreateUpdateTable
118130
? {
119-
text: "New Table",
120-
onClick: () => {
121-
openTab({ type: "schema", schemaName: currentSchemaName });
122-
},
123-
}
131+
text: "New Table",
132+
onClick: () => {
133+
openTab({ type: "schema", schemaName: currentSchemaName });
134+
},
135+
}
124136
: undefined,
125137
].filter(Boolean) as { text: string; onClick: () => void }[];
126138
}, [currentSchemaName, databaseDriver]);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import { Button } from "@/components/ui/button";
2+
import {
3+
Dialog,
4+
DialogContent,
5+
DialogDescription,
6+
DialogFooter,
7+
DialogHeader,
8+
DialogTitle,
9+
} from "@/components/ui/dialog";
10+
import { Input } from "@/components/ui/input";
11+
import { useDatabaseDriver } from "@/context/driver-provider";
12+
import { GitBranch, Loader } from "lucide-react";
13+
import { useCallback, useMemo, useState } from "react";
14+
15+
export default function useDoltCreateBranchModal(refreshStatus: () => void) {
16+
const { databaseDriver } = useDatabaseDriver();
17+
const [open, setOpen] = useState(false);
18+
const [loading, setLoading] = useState(false);
19+
const [errorMessage, setErrorMessage] = useState<string | undefined>();
20+
const [branchName, setBranchName] = useState<string>();
21+
const [commitHash, setCommitHash] = useState<string | undefined>();
22+
23+
const onSaveClicked = useCallback(() => {
24+
const command = commitHash
25+
? `CALL DOLT_CHECKOUT('-b', ${databaseDriver.escapeValue(branchName)}, ${databaseDriver.escapeValue(commitHash)});`
26+
: `CALL DOLT_CHECKOUT('-b', ${databaseDriver.escapeValue(branchName)});`;
27+
28+
setLoading(true);
29+
databaseDriver
30+
.query(command)
31+
.then(() => {
32+
refreshStatus();
33+
setOpen(false);
34+
})
35+
.catch((e) => {
36+
setLoading(false);
37+
if (e instanceof Error) {
38+
setErrorMessage(e.message);
39+
} else {
40+
setErrorMessage("An error occurred");
41+
}
42+
});
43+
}, [databaseDriver, refreshStatus, commitHash, branchName]);
44+
45+
const modal = useMemo(() => {
46+
if (!open) return null;
47+
48+
return (
49+
<Dialog open={open} onOpenChange={setOpen}>
50+
<DialogContent>
51+
<DialogHeader>
52+
<DialogTitle>Create Branch</DialogTitle>
53+
</DialogHeader>
54+
55+
<DialogDescription>
56+
{commitHash ? (
57+
<>
58+
Creating new branch from{" "}
59+
<span className="bg-muted rounded inline-flex font-mono px-2">
60+
{commitHash}
61+
</span>
62+
</>
63+
) : (
64+
"Creating new branch"
65+
)}
66+
</DialogDescription>
67+
68+
{errorMessage && (
69+
<div className="py-2 font-mono text-red-500">{errorMessage}</div>
70+
)}
71+
72+
<div>
73+
<Input
74+
value={branchName}
75+
placeholder="Branch Name"
76+
onChange={(e) => setBranchName(e.currentTarget.value)}
77+
/>
78+
</div>
79+
80+
<DialogFooter>
81+
<Button disabled={!branchName || loading} onClick={onSaveClicked}>
82+
{loading ? (
83+
<Loader className="mr-2 w-4 h-4 animate-spin" />
84+
) : (
85+
<GitBranch className="mr-2 w-4 h-4" />
86+
)}
87+
Create
88+
</Button>
89+
</DialogFooter>
90+
</DialogContent>
91+
</Dialog>
92+
);
93+
}, [open, branchName, commitHash, onSaveClicked, loading, errorMessage]);
94+
95+
const openModal = (commitHash?: string) => {
96+
setCommitHash(commitHash);
97+
setBranchName("");
98+
setLoading(false);
99+
setOpen(true);
100+
setErrorMessage(undefined);
101+
};
102+
103+
return { modal, openModal };
104+
}

0 commit comments

Comments
 (0)