Skip to content

Commit

Permalink
feat: support rename tab
Browse files Browse the repository at this point in the history
  • Loading branch information
invisal committed Jul 11, 2024
1 parent d7af305 commit 6526ee2
Show file tree
Hide file tree
Showing 14 changed files with 306 additions and 145 deletions.
5 changes: 4 additions & 1 deletion src/app/client/r/page-client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import RemoteDriver from "@/drivers/remote-driver";
import { useSearchParams } from "next/navigation";
import { useMemo } from "react";
import MyStudio from "@/components/my-studio";
import RemoteSavedDocDriver from "@/drivers/saved-doc/remote-saved-doc";

export default function ClientPageBody({
token,
Expand All @@ -15,13 +16,14 @@ export default function ClientPageBody({
}>) {
const params = useSearchParams();

const { driver, collaborator } = useMemo(() => {
const { driver, collaborator, docDriver } = useMemo(() => {
const databaseId = params.get("p");
if (!databaseId) return { driver: null };

return {
driver: new RemoteDriver("remote", databaseId, token),
collaborator: new CollaborationDriver(databaseId, token),
docDriver: new RemoteSavedDocDriver(databaseId),
};
}, [params, token]);

Expand All @@ -35,6 +37,7 @@ export default function ClientPageBody({
name={config.name}
color={config.label ?? "blue"}
collabarator={collaborator}
docDriver={docDriver}
/>
);
}
24 changes: 19 additions & 5 deletions src/components/gui/database-gui.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,17 @@ import useMessageListener from "@/components/hooks/useMessageListener";
import { MessageChannelName } from "@/messages/const";
import { OpenTabsProps, receiveOpenTabMessage } from "@/messages/open-tab";
import QueryWindow from "@/components/gui/tabs/query-tab";
import { LucideCode, LucideDatabase, LucideSettings } from "lucide-react";
import {
LucideBookmark,
LucideCode,
LucideDatabase,
LucideSettings,
} from "lucide-react";
import SidebarTab, { SidebarTabItem } from "./sidebar-tab";
import SchemaView from "./schema-sidebar";
import SettingSidebar from "./sidebar/setting-sidebar";
import { useDatabaseDriver } from "@/context/driver-provider";
import SavedDocTab from "./tabs/saved-doc-tab";

export default function DatabaseGui() {
const DEFAULT_WIDTH = 300;
Expand All @@ -26,13 +32,13 @@ export default function DatabaseGui() {
setDefaultWidthPercentage((DEFAULT_WIDTH / window.innerWidth) * 100);
}, []);

const { collaborationDriver } = useDatabaseDriver();
const { collaborationDriver, docDriver } = useDatabaseDriver();
const [selectedTabIndex, setSelectedTabIndex] = useState(0);
const [tabs, setTabs] = useState<WindowTabItemProps[]>(() => [
{
title: "Query",
key: "query",
component: <QueryWindow />,
component: <QueryWindow initialName="Query" />,
icon: LucideCode,
},
]);
Expand All @@ -50,10 +56,18 @@ export default function DatabaseGui() {
return [
{
key: "database",
name: "Database",
name: "Schema",
content: <SchemaView />,
icon: LucideDatabase,
},
docDriver
? {
key: "saved",
name: "Saved",
content: <SavedDocTab />,
icon: LucideBookmark,
}
: undefined,
collaborationDriver
? {
key: "setting",
Expand All @@ -63,7 +77,7 @@ export default function DatabaseGui() {
}
: undefined,
].filter(Boolean) as SidebarTabItem[];
}, [collaborationDriver]);
}, [collaborationDriver, docDriver]);

const tabSideMenu = useMemo(() => {
return [
Expand Down
9 changes: 8 additions & 1 deletion src/components/gui/studio.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { ReactElement } from "react";
import OptimizeTableState from "@/components/gui/table-optimized/OptimizeTableState";
import { StudioContextMenuItem } from "@/messages/open-context-menu";
import { CollaborationBaseDriver } from "@/drivers/collaboration-driver-base";
import { SavedDocDriver } from "@/drivers/saved-doc/saved-doc-driver";

export interface StudioExtension {
contextMenu?: (state: OptimizeTableState) => StudioContextMenuItem[];
Expand All @@ -15,6 +16,7 @@ export interface StudioExtension {
interface StudioProps {
driver: BaseDriver;
collaboration?: CollaborationBaseDriver;
docDriver?: SavedDocDriver;
name: string;
color: string;

Expand All @@ -30,14 +32,19 @@ interface StudioProps {
export function Studio({
driver,
collaboration,
docDriver,
name,
color,
extensions,
sideBarFooterComponent,
onBack,
}: Readonly<StudioProps>) {
return (
<DriverProvider driver={driver} collaborationDriver={collaboration}>
<DriverProvider
driver={driver}
collaborationDriver={collaboration}
docDriver={docDriver}
>
<ConfigProvider
extensions={extensions}
name={name}
Expand Down
69 changes: 61 additions & 8 deletions src/components/gui/tabs/query-tab.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { useMemo, useRef, useState } from "react";
import { useCallback, useMemo, useRef, useState } from "react";
import { identify } from "sql-query-identifier";
import {
LucideGrid,
LucideMessageSquareWarning,
LucidePlay,
LucideSave,
} from "lucide-react";
import SqlEditor from "@/components/gui/sql-editor";
import {
Expand All @@ -24,22 +25,32 @@ import {
MultipleQueryResult,
multipleQuery,
} from "@/components/lib/multiple-query";
import WindowTabs from "../windows-tab";
import WindowTabs, { useTabsContext } from "../windows-tab";
import QueryResult from "../query-result";
import { useSchema } from "@/context/schema-provider";

export default function QueryWindow() {
interface QueryWindowProps {
initialCode?: string;
initialName: string;
}

export default function QueryWindow({
initialCode,
initialName,
}: QueryWindowProps) {
const { schema } = useAutoComplete();
const { databaseDriver } = useDatabaseDriver();
const { databaseDriver, docDriver } = useDatabaseDriver();
const { refresh: refreshSchema } = useSchema();
const [code, setCode] = useState("");
const [code, setCode] = useState(initialCode ?? "");
const editorRef = useRef<ReactCodeMirrorRef>(null);
const [lineNumber, setLineNumber] = useState(0);
const [columnNumber, setColumnNumber] = useState(0);

const [queryTabIndex, setQueryTabIndex] = useState(0);
const [progress, setProgress] = useState<MultipleQueryProgress>();
const [data, setData] = useState<MultipleQueryResult[]>();
const [name, setName] = useState(initialName);
const { renameCurrentTab } = useTabsContext();

const onRunClicked = (all = false) => {
const statements = identify(code, {
Expand Down Expand Up @@ -100,6 +111,15 @@ export default function QueryWindow() {
}
};

const onSaveQuery = useCallback(() => {
if (docDriver) {
docDriver.createDoc("sql", docDriver.getCurrentNamespace(), {
content: code,
name: name || "Unnamed Query",
});
}
}, [docDriver, code, name]);

const windowTab = useMemo(() => {
return (
<WindowTabs
Expand Down Expand Up @@ -140,6 +160,26 @@ export default function QueryWindow() {
<ResizablePanelGroup direction="vertical">
<ResizablePanel style={{ position: "relative" }}>
<div className="absolute left-0 right-0 top-0 bottom-0 flex flex-col">
<div className="border-b pl-2 pr-1 py-1 flex">
<div className="text-xs shrink-0 items-center flex text-secondary-foreground p-1">
Unsaved Query /
</div>
<div className="inline-block relative">
<span className="inline-block text-xs p-1 outline-none font-semibold min-w-[175px] border border-background opacity-0">
&nbsp;{name}
</span>
<input
onBlur={(e) => {
renameCurrentTab(e.currentTarget.value || "Unnamed Query");
}}
placeholder="Please name your query"
spellCheck="false"
className="absolute top-0 right-0 left-0 bottom-0 text-xs p-1 outline-none font-semibold border border-background focus:border-secondary-foreground rounded"
value={name}
onChange={(e) => setName(e.currentTarget.value)}
/>
</div>
</div>
<div className="grow overflow-hidden">
<SqlEditor
ref={editorRef}
Expand All @@ -161,23 +201,36 @@ export default function QueryWindow() {
<div className="grow-0 shrink-0">
<Separator />
<div className="flex gap-1 p-1">
<Button variant={"ghost"} onClick={() => onRunClicked()}>
<Button
variant={"ghost"}
size="sm"
onClick={() => onRunClicked()}
>
<LucidePlay className="w-4 h-4 mr-2" />
Run Current{" "}
<span className="text-xs ml-2 px-2 bg-secondary py-1 rounded">
{KEY_BINDING.run.toString()}
</span>
</Button>

<Button variant={"ghost"} onClick={() => onRunClicked(true)}>
<Button
variant={"ghost"}
size="sm"
onClick={() => onRunClicked(true)}
>
<LucidePlay className="w-4 h-4 mr-2" />
Run All
</Button>

<div className="grow justify-end items-center flex text-sm mr-2 gap-2">
<div className="grow items-center flex text-xs mr-2 gap-2 border-l pl-4">
<div>Ln {lineNumber}</div>
<div>Col {columnNumber + 1}</div>
</div>

<Button size="sm" onClick={onSaveQuery} className="mr-2">
<LucideSave className="w-4 h-4 mr-2" />
Save
</Button>
</div>
</div>
</div>
Expand Down
95 changes: 95 additions & 0 deletions src/components/gui/tabs/saved-doc-tab.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { openTab } from "@/messages/open-tab";
import { Separator } from "@/components/ui/separator";
import { useDatabaseDriver } from "@/context/driver-provider";
import {
SavedDocData,
SavedDocNamespace,
} from "@/drivers/saved-doc/saved-doc-driver";
import { ListView, ListViewItem } from "@/listview";
import { LucideALargeSmall } from "lucide-react";
import { useEffect, useState } from "react";

function mapNamespace(
data: SavedDocNamespace
): ListViewItem<SavedDocNamespace> {
return {
data,
key: data.id,
icon: LucideALargeSmall,
name: data.name,
};
}

function mapDoc(data: SavedDocData): ListViewItem<SavedDocData> {
return {
data,
key: data.id,
icon: LucideALargeSmall,
name: data.name,
};
}

function SavedDocNamespaceDocList({ namespaceId }: { namespaceId?: string }) {
const { docDriver } = useDatabaseDriver();
const [selected, setSelected] = useState<string>();
const [docList, setDocList] = useState<ListViewItem<SavedDocData>[]>([]);

useEffect(() => {
if (docDriver && namespaceId) {
docDriver?.getDocs(namespaceId).then((r) => {
setDocList(r.map(mapDoc));
});
}
}, [docDriver, namespaceId]);

return (
<ListView
items={docList}
onSelectChange={setSelected}
selectedKey={selected}
onDoubleClick={(item: ListViewItem<SavedDocData>) => {
openTab({
type: "query",
name: item.name,
saved: {
key: item.key,
sql: item.data.content,
},
});
}}
/>
);
}

export default function SavedDocTab() {
const { docDriver } = useDatabaseDriver();
const [selectedNamespace, setSelectedNamespace] = useState<string>();
const [namespaceList, setNamespaceList] = useState<
ListViewItem<SavedDocNamespace>[]
>([]);

useEffect(() => {
docDriver?.getNamespaces().then((r) => {
setNamespaceList(r.map(mapNamespace));
const firstNamespaceId = r[0].id;
setSelectedNamespace(firstNamespaceId);
docDriver.setCurrentNamespace(firstNamespaceId);
});
}, [docDriver]);

return (
<div className="grow">
<div className="p-2">
<ListView
items={namespaceList}
selectedKey={selectedNamespace}
onSelectChange={setSelectedNamespace}
/>
</div>
<Separator />
<div className="p-2">
<SavedDocNamespaceDocList namespaceId={selectedNamespace} />
</div>
</div>
);
}
20 changes: 18 additions & 2 deletions src/components/gui/windows-tab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,14 @@ interface WindowTabsProps {

const WindowTabsContext = createContext<{
replaceCurrentTab: (tab: WindowTabItemProps) => void;
renameCurrentTab: (name: string) => void;
}>({
replaceCurrentTab: () => {
throw new Error("Not implemented");
},
renameCurrentTab: () => {
throw new Error("Not implemented");
},
});

export function useTabsContext() {
Expand Down Expand Up @@ -87,9 +91,21 @@ export default function WindowTabs({
[tabs, selected, onTabsChange]
);

const renameCurrentTab = useCallback(
(name: string) => {
if (tabs[selected]) {
tabs[selected].title = name;
if (onTabsChange) {
onTabsChange([...tabs]);
}
}
},
[tabs, selected, onTabsChange]
);

const contextValue = useMemo(
() => ({ replaceCurrentTab }),
[replaceCurrentTab]
() => ({ replaceCurrentTab, renameCurrentTab }),
[replaceCurrentTab, renameCurrentTab]
);

const handleDragStart = useCallback(
Expand Down
Loading

0 comments on commit 6526ee2

Please sign in to comment.