Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create View #241

Merged
merged 6 commits into from
Jan 16, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 64 additions & 38 deletions src/components/gui/schema-sidebar-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { useDatabaseDriver } from "@/context/driver-provider";
import { Table } from "@phosphor-icons/react";
import SchemaCreateDialog from "./schema-editor/schema-create";
import { scc } from "@/core/command";
import { useConfig } from "@/context/config-provider";

interface SchemaListProps {
search: string;
Expand Down Expand Up @@ -123,6 +124,7 @@ function flattenSchemaGroup(

export default function SchemaList({ search }: Readonly<SchemaListProps>) {
const { databaseDriver } = useDatabaseDriver();
const { extensions } = useConfig();
const [selected, setSelected] = useState("");
const { refresh, schema, currentSchemaName } = useSchema();
const [editSchema, setEditSchema] = useState<string | null>(null);
Expand All @@ -141,7 +143,63 @@ export default function SchemaList({ search }: Readonly<SchemaListProps>) {
const isTable = item?.type === "table";
const isTrigger = item?.type === "trigger";

const createMenuSection = {
title: "Create",
sub: [
databaseDriver.getFlags().supportCreateUpdateTable && {
title: "Create Table",
onClick: () => {
scc.tabs.openBuiltinSchema({
schemaName: item?.schemaName ?? currentSchemaName,
});
},
},
databaseDriver.getFlags().supportCreateUpdateTrigger
? {
title: "Create Trigger",
onClick: () => {
scc.tabs.openBuiltinTrigger({
schemaName: item?.schemaName ?? currentSchemaName,
tableName: item?.tableSchema?.tableName,
});
},
}
: undefined,
...extensions.getResourceCreateMenu(),
],
};

const modificationSection = item
? [
isTable && databaseDriver.getFlags().supportCreateUpdateTable
? {
title: "Edit Table",
onClick: () => {
scc.tabs.openBuiltinSchema({
schemaName: item?.schemaName ?? currentSchemaName,
tableName: item?.name,
});
},
}
: undefined,
databaseDriver.getFlags().supportCreateUpdateTrigger && isTrigger
? {
title: "Edit Trigger",
onClick: () => {
scc.tabs.openBuiltinTrigger({
schemaName: item?.schemaName ?? currentSchemaName,
name: item.name,
tableName: item?.tableSchema?.tableName,
});
},
}
: undefined,
...extensions.getResourceContextMenu(item, "modification"),
]
: [];

return [
createMenuSection,
{
title: "Copy Name",
disabled: !selectedName,
Expand All @@ -150,47 +208,15 @@ export default function SchemaList({ search }: Readonly<SchemaListProps>) {
},
},
{ separator: true },
databaseDriver.getFlags().supportCreateUpdateTable && {
title: "Create New Table",
onClick: () => {
scc.tabs.openBuiltinSchema({
schemaName: item?.schemaName ?? currentSchemaName,
});
},
},
isTable && databaseDriver.getFlags().supportCreateUpdateTable
? {
title: "Edit Table",
onClick: () => {
scc.tabs.openBuiltinTable({
schemaName: item?.schemaName ?? currentSchemaName,
tableName: item?.name,
});
},
}
: undefined,
databaseDriver.getFlags().supportCreateUpdateTrigger
? { separator: true }
: undefined,
databaseDriver.getFlags().supportCreateUpdateTrigger
? {
title: isTrigger ? "Edit Trigger" : "Create New Trigger",
onClick: () => {
scc.tabs.openBuiltinTrigger({
schemaName: item?.schemaName ?? currentSchemaName,
name: isTrigger ? item.name : "",
tableName: item?.tableSchema?.tableName,
});
},
}
: undefined,
databaseDriver.getFlags().supportCreateUpdateTable
? { separator: true }
: undefined,

// Modification Section
...modificationSection,
modificationSection.length > 0 ? { separator: true } : undefined,

{ title: "Refresh", onClick: () => refresh() },
].filter(Boolean) as OpenContextMenuList;
},
[refresh, databaseDriver, currentSchemaName]
[refresh, databaseDriver, currentSchemaName, extensions]
);

const listViewItems = useMemo(() => {
Expand Down
25 changes: 14 additions & 11 deletions src/components/gui/schema-sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,25 @@ import {
DropdownMenuTrigger,
} from "../ui/dropdown-menu";
import { scc } from "@/core/command";
import { StudioExtensionMenuItem } from "@/core/extension-manager";
import { useConfig } from "@/context/config-provider";

export default function SchemaView() {
const [search, setSearch] = useState("");
const { databaseDriver } = useDatabaseDriver();
const { currentSchemaName } = useSchema();
const [isCreateSchema, setIsCreateSchema] = useState(false);
const { extensions } = useConfig();

const contentMenu = useMemo(() => {
const items: {
name: string;
onClick: () => void;
}[] = [];
const items: StudioExtensionMenuItem[] = [];

const flags = databaseDriver.getFlags();

if (flags.supportCreateUpdateTable) {
items.push({
name: "Create Table",
title: "Create Table",
key: "create-table",
onClick: () => {
scc.tabs.openBuiltinSchema({ schemaName: currentSchemaName });
},
Expand All @@ -40,7 +41,8 @@ export default function SchemaView() {

if (flags.supportCreateUpdateDatabase) {
items.push({
name: "Create Database/Schema",
title: "Create Database/Schema",
key: "create-schema",
onClick: () => {
setIsCreateSchema(true);
},
Expand All @@ -49,15 +51,16 @@ export default function SchemaView() {

if (flags.supportCreateUpdateTrigger) {
items.push({
name: "Create Trigger",
title: "Create Trigger",
key: "create-trigger",
onClick: () => {
scc.tabs.openBuiltinTrigger({ schemaName: currentSchemaName });
},
});
}

return items;
}, [databaseDriver, currentSchemaName]);
return [...items, ...extensions.getResourceCreateMenu()];
}, [databaseDriver, currentSchemaName, extensions]);

const activatorButton = useMemo(() => {
if (contentMenu.length === 0) return null;
Expand Down Expand Up @@ -95,8 +98,8 @@ export default function SchemaView() {
<DropdownMenuContent side="bottom" align="start">
{contentMenu.map((menu) => {
return (
<DropdownMenuItem key={menu.name} onClick={menu.onClick}>
{menu.name}
<DropdownMenuItem key={menu.title} onClick={menu.onClick}>
{menu.title}
</DropdownMenuItem>
);
})}
Expand Down
2 changes: 1 addition & 1 deletion src/components/gui/studio.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ export function Studio({
useEffect(() => {
finalExtensionManager.init();
return () => finalExtensionManager.cleanup();
});
}, [finalExtensionManager]);

const config = useMemo(() => {
return {
Expand Down
4 changes: 2 additions & 2 deletions src/core/extension-base.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { StudioExtensionManager } from "./extension-manager";
import { StudioExtensionContext } from "./extension-manager";

export abstract class IStudioExtension {
abstract extensionName: string;
abstract init(studio: StudioExtensionManager): void;
abstract init(studio: StudioExtensionContext): void;
abstract cleanup(): void;
}

Expand Down
71 changes: 58 additions & 13 deletions src/core/extension-manager.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ReactElement } from "react";
import { IStudioExtension } from "./extension-base";
import { DatabaseSchemaItem } from "@/drivers/base-driver";

interface RegisterSidebarOption {
key: string;
Expand Down Expand Up @@ -47,35 +48,79 @@ export class BeforeQueryPipeline {

type BeforeQueryHandler = (payload: BeforeQueryPipeline) => Promise<void>;
type AfterQueryHandler = () => Promise<void>;
export class StudioExtensionManager {
private sidebars: RegisterSidebarOption[] = [];
private beforeQueryHandlers: BeforeQueryHandler[] = [];
private afterQueryHandlers: AfterQueryHandler[] = [];

constructor(private extensions: IStudioExtension[]) {}
export interface StudioExtensionMenuItem {
key: string;
title: string;
icon?: ReactElement;
onClick: () => void;
}

init() {
this.extensions.forEach((ext) => ext.init(this));
type CreateResourceMenuHandler = (
resource: DatabaseSchemaItem
) => StudioExtensionMenuItem | undefined;
export class StudioExtensionContext {
protected sidebars: RegisterSidebarOption[] = [];
protected beforeQueryHandlers: BeforeQueryHandler[] = [];
protected afterQueryHandlers: AfterQueryHandler[] = [];
protected resourceCreateMenu: StudioExtensionMenuItem[] = [];
protected resourceContextMenu: Record<string, CreateResourceMenuHandler[]> =
{};

constructor(protected extensions: IStudioExtension[]) {}

registerBeforeQuery(handler: BeforeQueryHandler) {
this.beforeQueryHandlers.push(handler);
}

cleanup() {
this.extensions.forEach((ext) => ext.cleanup());
registerAfterQuery(handler: AfterQueryHandler) {
this.afterQueryHandlers.push(handler);
}

registerSidebar(option: RegisterSidebarOption) {
this.sidebars.push(option);
}

registerCreateResourceMenu(menu: StudioExtensionMenuItem) {
console.log("Register", menu);
this.resourceCreateMenu.push(menu);
}

registerResourceContextMenu(
handler: CreateResourceMenuHandler,
group: "other" | "modification" = "other"
) {
if (!this.resourceContextMenu[group]) {
this.resourceContextMenu[group] = [handler];
} else {
this.resourceContextMenu[group].push(handler);
}
}
}
export class StudioExtensionManager extends StudioExtensionContext {
init() {
this.extensions.forEach((ext) => ext.init(this));
}

cleanup() {
this.extensions.forEach((ext) => ext.cleanup());
}

getSidebars() {
return this.sidebars;
}

registerBeforeQuery(handler: BeforeQueryHandler) {
this.beforeQueryHandlers.push(handler);
getResourceCreateMenu() {
return this.resourceCreateMenu;
}

registerAfterQuery(handler: AfterQueryHandler) {
this.afterQueryHandlers.push(handler);
getResourceContextMenu(
resource: DatabaseSchemaItem,
group: "other" | "modification"
) {
return (this.resourceContextMenu[group] ?? [])
.map((handler) => handler(resource))
.filter(Boolean) as StudioExtensionMenuItem[];
}

async beforeQuery(payload: BeforeQueryPipeline) {
Expand Down
16 changes: 15 additions & 1 deletion src/core/extension-tab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,30 @@ interface TabExtensionConfig<T> {

interface TabExtensionCommand<T> {
open: (options: T) => void;
generate: (options: T) => WindowTabItemProps;
close: (options: T) => void;
}

export function createTabExtension<T>(
config: TabExtensionConfig<T>
): TabExtensionCommand<T> {
return Object.freeze({
open(options: T) {
generate: (options: T) => {
const key = [config.name, config.key(options)].filter(Boolean).join("-");
return {
...config.generate(options),
key,
identifier: key,
type: config.name,
};
},

open(options: T) {
if (window.outerbaseOpenTab) {
const key = [config.name, config.key(options)]
.filter(Boolean)
.join("-");

window.outerbaseOpenTab({
...config.generate(options),
key,
Expand Down
3 changes: 2 additions & 1 deletion src/core/standard-extension.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
*/

import QueryHistoryConsoleLogExtension from "@/extensions/query-console-log";
import ViewEditorExtension from "@/extensions/view-editor";

export function createStandardExtensions() {
return [new QueryHistoryConsoleLogExtension()];
return [new QueryHistoryConsoleLogExtension(), new ViewEditorExtension()];
}
10 changes: 10 additions & 0 deletions src/drivers/base-driver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,12 @@ export interface DatabaseTriggerSchema {
statement: string;
}

export interface DatabaseViewSchema {
name: string;
schemaName: string;
statement: string;
}

interface DatabaseTableOperationInsert {
operation: "INSERT";
values: Record<string, DatabaseValue>;
Expand Down Expand Up @@ -344,4 +350,8 @@ export abstract class BaseDriver {

abstract createTrigger(trigger: DatabaseTriggerSchema): string;
abstract dropTrigger(schemaName: string, name: string): string;
abstract createView(view: DatabaseViewSchema): string;
abstract dropView(schemaName: string, name: string): string;

abstract view(schemaName: string, name: string): Promise<DatabaseViewSchema>;
}
Loading
Loading