field halfway

This commit is contained in:
Guy Ben-Aharon
2024-08-12 15:17:28 +03:00
parent 8b864595f3
commit 46597a9644
12 changed files with 286 additions and 44 deletions

61
package-lock.json generated
View File

@@ -20,6 +20,8 @@
"@radix-ui/react-select": "^2.1.1",
"@radix-ui/react-separator": "^1.1.0",
"@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-toggle": "^1.1.0",
"@radix-ui/react-tooltip": "^1.1.2",
"@xyflow/react": "^12.0.4",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
@@ -1855,6 +1857,65 @@
}
}
},
"node_modules/@radix-ui/react-toggle": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.1.0.tgz",
"integrity": "sha512-gwoxaKZ0oJ4vIgzsfESBuSgJNdc0rv12VhHgcqN0TEJmmZixXG/2XpsLK8kzNWYcnaoRIEEQc0bEi3dIvdUpjw==",
"license": "MIT",
"dependencies": {
"@radix-ui/primitive": "1.1.0",
"@radix-ui/react-primitive": "2.0.0",
"@radix-ui/react-use-controllable-state": "1.1.0"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-tooltip": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.1.2.tgz",
"integrity": "sha512-9XRsLwe6Yb9B/tlnYCPVUd/TFS4J7HuOZW345DCeC6vKIxQGMZdx21RK4VoZauPD5frgkXTYVS5y90L+3YBn4w==",
"license": "MIT",
"dependencies": {
"@radix-ui/primitive": "1.1.0",
"@radix-ui/react-compose-refs": "1.1.0",
"@radix-ui/react-context": "1.1.0",
"@radix-ui/react-dismissable-layer": "1.1.0",
"@radix-ui/react-id": "1.1.0",
"@radix-ui/react-popper": "1.2.0",
"@radix-ui/react-portal": "1.1.1",
"@radix-ui/react-presence": "1.1.0",
"@radix-ui/react-primitive": "2.0.0",
"@radix-ui/react-slot": "1.1.0",
"@radix-ui/react-use-controllable-state": "1.1.0",
"@radix-ui/react-visually-hidden": "1.1.0"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-use-callback-ref": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz",

View File

@@ -23,6 +23,8 @@
"@radix-ui/react-select": "^2.1.1",
"@radix-ui/react-separator": "^1.1.0",
"@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-toggle": "^1.1.0",
"@radix-ui/react-tooltip": "^1.1.2",
"@xyflow/react": "^12.0.4",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",

View File

@@ -1,7 +1,12 @@
import React from 'react';
import { RouterProvider } from 'react-router-dom';
import { router } from './router';
import { TooltipProvider } from './components/tooltip/tooltip';
export const App = () => {
return <RouterProvider router={router} />;
return (
<TooltipProvider>
<RouterProvider router={router} />
</TooltipProvider>
);
};

View File

@@ -90,7 +90,7 @@ export function Combobox({
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-48 max-w-sm p-0">
<PopoverContent className="w-40 max-w-sm p-0">
<Command
filter={(value, search) => {
if (value.includes(search)) return 1;

View File

@@ -0,0 +1,25 @@
import { cva } from 'class-variance-authority';
const toggleVariants = cva(
'inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground',
{
variants: {
variant: {
default: 'bg-transparent',
outline:
'border border-input bg-transparent shadow-sm hover:bg-accent hover:text-accent-foreground',
},
size: {
default: 'h-9 px-3',
sm: 'h-8 px-2',
lg: 'h-10 px-3',
},
},
defaultVariants: {
variant: 'default',
size: 'default',
},
}
);
export { toggleVariants };

View File

@@ -0,0 +1,22 @@
import React from 'react';
import * as TogglePrimitive from '@radix-ui/react-toggle';
import { type VariantProps } from 'class-variance-authority';
import { cn } from '@/lib/utils';
import { toggleVariants } from './toggle-variants';
const Toggle = React.forwardRef<
React.ElementRef<typeof TogglePrimitive.Root>,
React.ComponentPropsWithoutRef<typeof TogglePrimitive.Root> &
VariantProps<typeof toggleVariants>
>(({ className, variant, size, ...props }, ref) => (
<TogglePrimitive.Root
ref={ref}
className={cn(toggleVariants({ variant, size, className }))}
{...props}
/>
));
Toggle.displayName = TogglePrimitive.Root.displayName;
export { Toggle };

View File

@@ -0,0 +1,28 @@
import React from 'react';
import * as TooltipPrimitive from '@radix-ui/react-tooltip';
import { cn } from '@/lib/utils';
const TooltipProvider = TooltipPrimitive.Provider;
const Tooltip = TooltipPrimitive.Root;
const TooltipTrigger = TooltipPrimitive.Trigger;
const TooltipContent = React.forwardRef<
React.ElementRef<typeof TooltipPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
>(({ className, sideOffset = 4, ...props }, ref) => (
<TooltipPrimitive.Content
ref={ref}
sideOffset={sideOffset}
className={cn(
'z-50 overflow-hidden rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
className
)}
{...props}
/>
));
TooltipContent.displayName = TooltipPrimitive.Content.displayName;
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };

View File

@@ -1,19 +1,43 @@
import { createContext } from 'react';
import { DBTable } from '@/lib/domain/db-table';
import { emptyFn } from '@/lib/utils';
import { DatabaseType } from '@/lib/domain/database-type';
import { DBField } from '@/lib/domain/db-field';
export interface ChartDBContext {
databaseType: DatabaseType;
tables: DBTable[];
// Database type operations
setDatabaseType: (databaseType: DatabaseType) => void;
// Table operations
createTable: () => void;
addTable: (table: DBTable) => void;
removeTable: (id: string) => void;
updateTable: (id: string, table: Partial<DBTable>) => void;
tables: DBTable[];
// Field operations
updateField: (
tableId: string,
fieldId: string,
field: Partial<DBField>
) => void;
}
export const chartDBContext = createContext<ChartDBContext>({
databaseType: DatabaseType.GENERIC,
tables: [],
// Database type operations
setDatabaseType: emptyFn,
// Table operations
createTable: emptyFn,
addTable: emptyFn,
removeTable: emptyFn,
updateTable: emptyFn,
tables: [],
// Field operations
updateField: emptyFn,
});

View File

@@ -3,10 +3,15 @@ import { DBTable } from '@/lib/domain/db-table';
import { randomHSLA } from '@/lib/utils';
import { nanoid } from 'nanoid';
import { chartDBContext } from './chartdb-context';
import { DatabaseType } from '@/lib/domain/database-type';
import { DBField } from '@/lib/domain/db-field';
export const ChartDBProvider: React.FC<React.PropsWithChildren> = ({
children,
}) => {
const [databaseType, setDatabaseType] = React.useState<DatabaseType>(
DatabaseType.GENERIC
);
const [tables, setTables] = React.useState<DBTable[]>([]);
const addTable = (table: DBTable) => {
@@ -46,14 +51,36 @@ export const ChartDBProvider: React.FC<React.PropsWithChildren> = ({
);
};
const updateField = (
tableId: string,
fieldId: string,
field: Partial<DBField>
) => {
setTables((tables) =>
tables.map((table) =>
table.id === tableId
? {
...table,
fields: table.fields.map((f) =>
f.id === fieldId ? { ...f, ...field } : f
),
}
: table
)
);
};
return (
<chartDBContext.Provider
value={{
databaseType,
tables,
setDatabaseType,
createTable,
addTable,
removeTable,
updateTable,
tables,
updateField,
}}
>
{children}

View File

@@ -1,6 +1,18 @@
import { DatabaseType } from '../domain/database-type';
export const genericDataTypes = ['bigint', 'bfile', 'clob', 'cursor'] as const;
export const postgresDataTypes = ['bigint', 'bigserial'] as const;
export const mysqlDataTypes = ['bigint', 'bigserial'] as const;
export const dataTypes = [
...new Set([...postgresDataTypes, ...mysqlDataTypes]),
...new Set([...postgresDataTypes, ...mysqlDataTypes, ...genericDataTypes]),
] as const;
export const dataTypeMap: Record<
DatabaseType,
readonly (typeof dataTypes)[number][]
> = {
[DatabaseType.GENERIC]: genericDataTypes,
[DatabaseType.POSTGRES]: postgresDataTypes,
[DatabaseType.MYSQL]: mysqlDataTypes,
} as const;

View File

@@ -0,0 +1,5 @@
export enum DatabaseType {
GENERIC = 'generic',
POSTGRES = 'postgres',
MYSQL = 'mysql',
}

View File

@@ -12,6 +12,15 @@ import {
} from '@/components/accordion/accordion';
import { Separator } from '@/components/separator/separator';
import { DBTable } from '@/lib/domain/db-table';
import { DBField, FieldType } from '@/lib/domain/db-field';
import { useChartDB } from '@/hooks/use-chartdb';
import { dataTypeMap } from '@/lib/data/data-types';
import { Toggle } from '@/components/toggle/toggle';
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from '@/components/tooltip/tooltip';
export interface TableListItemContentProps {
table: DBTable;
@@ -20,56 +29,78 @@ export interface TableListItemContentProps {
export const TableListItemContent: React.FC<TableListItemContentProps> = ({
table,
}) => {
const { databaseType, updateField } = useChartDB();
const { color } = table;
const renderField = () => {
const dataFieldOptions = dataTypeMap[databaseType].map((type) => ({
label: type,
value: type,
}));
const RenderField = ({ field }: { field: DBField }) => {
return (
<div className="flex flex-row p-1 justify-between flex-1">
<div className="flex basis-8/12 gap-1">
<Input
className="h-8 focus-visible:ring-0 basis-8/12"
type="text"
placeholder="Name"
className="h-8 focus-visible:ring-0 basis-8/12"
value={field.name}
onChange={(e) =>
updateField(table.id, field.id, {
name: e.target.value,
})
}
/>
<Combobox
className="flex h-8 basis-4/12"
mode="single"
options={[
{
label: 'small_int',
value: 'smallint',
},
{
label: 'json',
value: 'json',
},
{
label: 'jsonb',
value: 'jsonb',
},
{
label: 'varchar',
value: 'varchar',
},
]}
options={dataFieldOptions}
placeholder="Type"
selected={''}
onChange={(value) => console.log(value)}
selected={field.type}
onChange={(value) =>
updateField(table.id, field.id, {
type: value as FieldType,
})
}
emptyText="No types found."
/>
</div>
<div className="flex">
<Button
variant="ghost"
className="hover:bg-primary-foreground p-2 w-[32px] text-slate-500 hover:text-slate-700 text-xs h-8"
>
N
</Button>
<Button
variant="ghost"
className="hover:bg-primary-foreground p-2 w-[32px] text-slate-500 hover:text-slate-700 h-8"
>
<KeyRound className="h-3.5" />
</Button>
<div className="flex gap-1">
<Tooltip>
<TooltipTrigger>
<Toggle
variant="default"
className="hover:bg-primary-foreground p-2 w-[32px] text-slate-500 hover:text-slate-700 text-xs h-8"
pressed={field.nullable}
onPressedChange={(value) =>
updateField(table.id, field.id, {
nullable: value,
})
}
>
N
</Toggle>
</TooltipTrigger>
<TooltipContent>Nullable?</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger>
<Toggle
variant="default"
className="hover:bg-primary-foreground p-2 w-[32px] text-slate-500 hover:text-slate-700 h-8"
pressed={field.primaryKey}
onPressedChange={(value) =>
updateField(table.id, field.id, {
primaryKey: value,
})
}
>
<KeyRound className="h-3.5" />
</Toggle>
</TooltipTrigger>
<TooltipContent>Primary Key</TooltipContent>
</Tooltip>
<Button
variant="ghost"
className="hover:bg-primary-foreground p-2 w-[32px] text-slate-500 hover:text-slate-700 h-8"
@@ -155,9 +186,9 @@ export const TableListItemContent: React.FC<TableListItemContentProps> = ({
</div>
</AccordionTrigger>
<AccordionContent className="pb-0 pt-1">
{renderField()}
{renderField()}
{renderField()}
{table.fields.map((field) => (
<RenderField field={field} key={field.id} />
))}
</AccordionContent>
</AccordionItem>