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

WIP: Universal schema editor #225

Open
wants to merge 6 commits into
base: develop
Choose a base branch
from
Open
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
11 changes: 7 additions & 4 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
{
"editor.formatOnSave": true,
"typescript.tsdk": "node_modules\\typescript\\lib",
"files.eol": "\n"
}
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.organizeImports": "explicit"
},
"typescript.tsdk": "node_modules\\typescript\\lib",
"files.eol": "\n"
}
37 changes: 37 additions & 0 deletions src/app/storybook/schema-editor/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
"use client";
import SchemaEditor from "@/components/gui/schema-editor";
import { DatabaseTableSchemaChange } from "@/drivers/base-driver";
import { useState } from "react";

export default function SchemaEditorStorybook() {
const [value, setValue] = useState<DatabaseTableSchemaChange>({
name: {
old: "",
new: "",
},
columns: [],
constraints: [],
createScript: "",
});

return (
<div className="min-h-screen bg-secondary p-4">
<div className="max-w-[800px] border shadow bg-background">
<SchemaEditor
value={value}
onChange={setValue}
alwayUseTableConstraint
dataTypeSuggestion={{
type: "dropdown",
idTypeName: "INT",
textTypeName: "TEXT",
dropdownOptions: [
{ text: "INT", value: "INT" },
{ text: "TEXT", value: "TEXT" },
],
}}
/>
</div>
</div>
);
}
60 changes: 60 additions & 0 deletions src/components/combobox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { cn } from "@/lib/utils";
import { Check, ChevronsUpDown } from "lucide-react";
import { useState } from "react";
import { Button } from "./ui/button";
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
} from "./ui/command";
import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover";

interface ComboboxProps {
value?: string;
onChange: (value: string) => void;
items?: { value: string; text: string }[];
}

export default function Combobox({ value, items, onChange }: ComboboxProps) {
const [open, setOpen] = useState(false);

return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button variant="outline" className="flex justify-between w-full">
{value || "No column selected"}
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-[250px] p-0" side="bottom" align="start">
<Command>
<CommandInput placeholder="Search column name..." />

<CommandEmpty>No column found.</CommandEmpty>
<CommandGroup className="max-h-[250px] overflow-y-auto">
{(items ?? []).map((item) => (
<CommandItem
key={item.value}
value={item.value}
onSelect={() => {
onChange(item.value);
setOpen(false);
}}
>
<Check
className={cn(
"mr-2 h-4 w-4",
value === item.value ? "opacity-100" : "opacity-0"
)}
/>
{item.text}
</CommandItem>
))}
</CommandGroup>
</Command>
</PopoverContent>
</Popover>
);
}
15 changes: 10 additions & 5 deletions src/components/common-dialog/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"use client";
import { Icon } from "@phosphor-icons/react";
import { noop } from "lodash";
import { Loader } from "lucide-react";
import {
PropsWithChildren,
ReactElement,
Expand All @@ -8,6 +10,8 @@ import {
useContext,
useState,
} from "react";
import CodePreview from "../gui/code-preview";
import { Button } from "../ui/button";
import {
Dialog,
DialogContent,
Expand All @@ -16,10 +20,6 @@ import {
DialogHeader,
DialogTitle,
} from "../ui/dialog";
import { Button } from "../ui/button";
import { Icon } from "@phosphor-icons/react";
import { Loader } from "lucide-react";
import CodePreview from "../gui/code-preview";

interface ShowDialogProps {
title: string;
Expand Down Expand Up @@ -55,8 +55,13 @@ export function CommonDialogProvider({ children }: PropsWithChildren) {
setDialogOption(null);
}, []);

const showDialog = useCallback((option: ShowDialogProps) => {
setDialogOption(option);
setErrorMessage("");
}, []);

return (
<CommonDialogContext.Provider value={{ showDialog: setDialogOption }}>
<CommonDialogContext.Provider value={{ showDialog }}>
{children}
{dialogOption && (
<Dialog
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ import { Checkbox } from "@/components/ui/checkbox";
import { Label } from "@/components/ui/label";
import {
Popover,
PopoverTrigger,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { DatabaseTableColumnConstraint } from "@/drivers/base-driver";
import { ChevronsUpDown } from "lucide-react";
import { ChangeEvent, useCallback, useMemo } from "react";
import { Input } from "../../ui/input";
import { DatabaseTableColumnConstraint } from "@/drivers/base-driver";

export default function ColumnDefaultValueInput({
constraint,
Expand Down Expand Up @@ -116,7 +116,7 @@ export default function ColumnDefaultValueInput({
return (
<Popover>
<PopoverTrigger className="h-full flex w-full">
<div className="flex text-left px-2 py-2 text-sm h-full bg-background">
<div className="flex text-left px-2 py-2 text-sm h-full bg-inherit">
<div className="grow w-[150px] overflow-hidden mr-2">
{display || "EMPTY STRING"}
</div>
Expand Down
132 changes: 58 additions & 74 deletions src/components/gui/schema-editor/column-pk-popup.tsx
Original file line number Diff line number Diff line change
@@ -1,85 +1,69 @@
import { DatabaseTableColumnConstraint, SqlOrder } from "@/drivers/base-driver";
import { LucideKeyRound } from "lucide-react";
import { Button } from "../../ui/button";
import ConflictClauseOptions from "./column-conflict-clause";
import { ColumnChangeEvent } from "./schema-editor-column-list";
import { Popover, PopoverContent, PopoverTrigger } from "../../ui/popover";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "../../ui/select";
DatabaseTableColumnChange,
DatabaseTableSchemaChange,
} from "@/drivers/base-driver";
import { produce } from "immer";
import { LucideKeyRound } from "lucide-react";
import { Dispatch, SetStateAction, useCallback } from "react";

export default function ColumnPrimaryKeyPopup({
constraint,
schema,
column,
disabled,
onChange,
}: Readonly<{
constraint: DatabaseTableColumnConstraint;
schema: DatabaseTableSchemaChange;
column: DatabaseTableColumnChange;
disabled: boolean;
onChange: ColumnChangeEvent;
onChange: Dispatch<SetStateAction<DatabaseTableSchemaChange>>;
}>) {
const columnName = column.new?.name;

// Check if the column is primary key
const isPrimaryKey = schema.constraints.some((constraint) =>
(
constraint.new?.primaryColumns ??
constraint.old?.primaryColumns ??
[]
).includes(column.new?.name ?? column.old?.name ?? "")
);

const removePrimaryKey = useCallback(() => {
onChange((prev) => {
return produce(prev, (draft) => {
// Finding the primary key constraint
draft.constraints.forEach((constraint) => {
if (constraint.new?.primaryColumns) {
// Remove the column from the primary key constraint
constraint.new.primaryColumns =
constraint.new.primaryColumns.filter(
(column) => column !== columnName
);
}
});

// Remove empty primary constraint
draft.constraints = draft.constraints.filter((constraint) => {
if (constraint.new?.primaryKey && constraint.new?.primaryColumns) {
return constraint.new.primaryColumns.length > 0;
}
return true;
});
});
});
}, [onChange, columnName]);

if (!isPrimaryKey) {
return null;
}

return (
<Popover>
<PopoverTrigger>
<span className="p-1 shadow border rounded block bg-green-200 dark:bg-green-600">
<LucideKeyRound className="w-4 h-4" />
</span>
</PopoverTrigger>
<PopoverContent>
<div className="flex flex-col gap-2">
<div className="font-semibold text-sm">Primary Key</div>
<Select
value={constraint.primaryKeyOrder}
disabled={disabled}
onValueChange={(v) => {
onChange({
constraint: {
primaryKeyOrder: v as SqlOrder,
},
});
}}
>
<SelectTrigger className="bg-background">
<SelectValue placeholder="Order" />
</SelectTrigger>
<SelectContent>
<SelectItem value="ASC">ASC</SelectItem>
<SelectItem value="DESC">DESC</SelectItem>
</SelectContent>
</Select>
<ConflictClauseOptions
value={constraint.primaryKeyConflict}
disabled={disabled}
onChange={(v) => {
onChange({
constraint: {
primaryKeyConflict: v,
},
});
}}
/>
<Button
size="sm"
className="mt-4"
variant={"destructive"}
disabled={disabled}
onClick={() => {
onChange({
constraint: {
primaryKey: undefined,
primaryKeyConflict: undefined,
primaryKeyOrder: undefined,
},
});
}}
>
Remove Constraint
</Button>
</div>
</PopoverContent>
</Popover>
<button
className="p-1 shadow border rounded block bg-green-200 dark:bg-green-600"
disabled={disabled}
onClick={removePrimaryKey}
>
<LucideKeyRound className="w-4 h-4" />
</button>
);
}
21 changes: 0 additions & 21 deletions src/components/gui/schema-editor/column-provider.tsx

This file was deleted.

Loading
Loading