mirror of
https://github.com/chartdb/chartdb.git
synced 2025-11-03 05:23:26 +00:00
field halfway
This commit is contained in:
61
package-lock.json
generated
61
package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
25
src/components/toggle/toggle-variants.tsx
Normal file
25
src/components/toggle/toggle-variants.tsx
Normal 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 };
|
||||
22
src/components/toggle/toggle.tsx
Normal file
22
src/components/toggle/toggle.tsx
Normal 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 };
|
||||
28
src/components/tooltip/tooltip.tsx
Normal file
28
src/components/tooltip/tooltip.tsx
Normal 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 };
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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;
|
||||
|
||||
5
src/lib/domain/database-type.ts
Normal file
5
src/lib/domain/database-type.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export enum DatabaseType {
|
||||
GENERIC = 'generic',
|
||||
POSTGRES = 'postgres',
|
||||
MYSQL = 'mysql',
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user