create tables

This commit is contained in:
Guy Ben-Aharon
2024-08-11 18:15:13 +03:00
parent d4654b1155
commit f496d23ba6
16 changed files with 226 additions and 27 deletions

29
package-lock.json generated
View File

@@ -25,6 +25,7 @@
"cmdk": "^1.0.0",
"eslint-config-airbnb-typescript": "^18.0.0",
"lucide-react": "^0.424.0",
"nanoid": "^5.0.7",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-resizable-panels": "^2.0.22",
@@ -6092,9 +6093,9 @@
}
},
"node_modules/nanoid": {
"version": "3.3.7",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
"integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
"version": "5.0.7",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.0.7.tgz",
"integrity": "sha512-oLxFY2gd2IqnjcYyOXD8XGCftpGtZP2AbHbOkthDkvRywH5ayNtPVy9YlOPcHckXzbLTCHpkb7FB+yuxKV13pQ==",
"funding": [
{
"type": "github",
@@ -6103,10 +6104,10 @@
],
"license": "MIT",
"bin": {
"nanoid": "bin/nanoid.cjs"
"nanoid": "bin/nanoid.js"
},
"engines": {
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
"node": "^18 || >=20"
}
},
"node_modules/natural-compare": {
@@ -6616,6 +6617,24 @@
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
"license": "MIT"
},
"node_modules/postcss/node_modules/nanoid": {
"version": "3.3.7",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
"integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"bin": {
"nanoid": "bin/nanoid.cjs"
},
"engines": {
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
"node_modules/prelude-ls": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",

View File

@@ -28,6 +28,7 @@
"cmdk": "^1.0.0",
"eslint-config-airbnb-typescript": "^18.0.0",
"lucide-react": "^0.424.0",
"nanoid": "^5.0.7",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-resizable-panels": "^2.0.22",

View File

@@ -0,0 +1,19 @@
import { createContext } from 'react';
import { DBTable } from '@/lib/domain/db-table';
import { emptyFn } from '@/lib/utils';
export interface ChartDBContext {
createTable: () => void;
addTable: (table: DBTable) => void;
removeTable: (id: string) => void;
updateTable: (id: string, table: Partial<DBTable>) => void;
tables: DBTable[];
}
export const chartDBContext = createContext<ChartDBContext>({
createTable: emptyFn,
addTable: emptyFn,
removeTable: emptyFn,
updateTable: emptyFn,
tables: [],
});

View File

@@ -0,0 +1,62 @@
import React from 'react';
import { DBTable } from '@/lib/domain/db-table';
import { randomHSLA } from '@/lib/utils';
import { nanoid } from 'nanoid';
import { chartDBContext } from './chartdb-context';
export const ChartDBProvider: React.FC<React.PropsWithChildren> = ({
children,
}) => {
const [tables, setTables] = React.useState<DBTable[]>([]);
const addTable = (table: DBTable) => {
setTables((tables) => [...tables, table]);
};
const createTable = () => {
addTable({
id: nanoid(),
name: `table_${tables.length + 1}`,
x: 0,
y: 0,
fields: [
{
id: nanoid(),
name: 'id',
type: 'bigint',
unique: true,
nullable: false,
primaryKey: true,
createdAt: Date.now(),
},
],
indexes: [],
color: randomHSLA(),
createdAt: Date.now(),
});
};
const removeTable = (id: string) => {
setTables((tables) => tables.filter((table) => table.id !== id));
};
const updateTable = (id: string, table: Partial<DBTable>) => {
setTables((tables) =>
tables.map((t) => (t.id === id ? { ...t, ...table } : t))
);
};
return (
<chartDBContext.Provider
value={{
createTable,
addTable,
removeTable,
updateTable,
tables,
}}
>
{children}
</chartDBContext.Provider>
);
};

4
src/hooks/use-chartdb.ts Normal file
View File

@@ -0,0 +1,4 @@
import { chartDBContext } from '@/context/chartdb-context/chartdb-context';
import { useContext } from 'react';
export const useChartDB = () => useContext(chartDBContext);

View File

@@ -0,0 +1,6 @@
export const postgresTypes = ['bigint', 'bigserial'] as const;
export const mysqlTypes = ['bigint', 'bigserial'] as const;
export const dataTypes = [
...new Set([...postgresTypes, ...mysqlTypes]),
] as const;

View File

@@ -0,0 +1,13 @@
import { dataTypes } from '../data/data-types';
export interface DBField {
id: string;
name: string;
type: FieldType;
primaryKey: boolean;
unique: boolean;
nullable: boolean;
createdAt: number;
}
export type FieldType = (typeof dataTypes)[number];

View File

@@ -0,0 +1,9 @@
import { DBField } from './db-field';
export interface DBIndex {
id: string;
name: string;
unique: boolean;
fieldIds: string[];
fields?: DBField[];
}

View File

@@ -0,0 +1,13 @@
import { DBIndex } from './db-index';
import { DBField } from './db-field';
export interface DBTable {
id: string;
name: string;
x: number;
y: number;
fields: DBField[];
indexes: DBIndex[];
color: string;
createdAt: number;
}

View File

@@ -1,9 +1,23 @@
import { type ClassValue, clsx } from 'clsx';
import { customAlphabet } from 'nanoid';
const nanoid = customAlphabet('1234567890', 18);
import { twMerge } from 'tailwind-merge';
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
export const convertToDecimal = (number: number) => {
const digits = number.toString().length; // Get the number of digits
return number / Math.pow(10, digits); // Divide the number by 10^digits
};
// export const randomHSLA = () =>
// `hsla(${~~(360 * Math.random())}, 70%, 72%, 0.8)`;
// creates randomHSLA using nanoid library instead of math.random
export const randomHSLA = () =>
`hsla(${~~(360 * Math.random())}, 70%, 72%, 0.8)`;
`hsla(${~~(360 * convertToDecimal(parseInt(nanoid())))}, 70%, 72%, 0.8)`;
export const emptyFn = () => {};

View File

@@ -11,14 +11,16 @@ import {
AccordionContent,
} from '@/components/accordion/accordion';
import { Separator } from '@/components/separator/separator';
import { DBTable } from '@/lib/domain/db-table';
export interface TableListItemContentProps {
tableColor: string;
table: DBTable;
}
export const TableListItemContent: React.FC<TableListItemContentProps> = ({
tableColor,
table,
}) => {
const { color } = table;
const renderField = () => {
return (
<div className="flex flex-row p-1 justify-between flex-1">
@@ -122,7 +124,7 @@ export const TableListItemContent: React.FC<TableListItemContentProps> = ({
<div
className="border-l-[6px] rounded-b-md px-1 flex flex-col gap-1"
style={{
borderColor: tableColor,
borderColor: color,
}}
>
<Accordion

View File

@@ -1,13 +1,18 @@
import React from 'react';
import { CircleDotDashed, Pencil, EllipsisVertical } from 'lucide-react';
import { ListItemHeaderButton } from '@/pages/editor-page/side-panel/list-item-header-button/relationship-list-item-header-button';
import { DBTable } from '@/lib/domain/db-table';
export interface TableListItemHeaderProps {}
export interface TableListItemHeaderProps {
table: DBTable;
}
export const TableListItemHeader: React.FC<TableListItemHeaderProps> = () => {
export const TableListItemHeader: React.FC<TableListItemHeaderProps> = ({
table,
}) => {
return (
<div className="h-11 flex items-center justify-between flex-1 group">
<div>table_1</div>
<div>{table.name}</div>
<div className="flex flex-row-reverse">
<div>
<ListItemHeaderButton>

View File

@@ -4,26 +4,28 @@ import {
AccordionItem,
AccordionTrigger,
} from '@/components/accordion/accordion';
import { randomHSLA } from '@/lib/utils';
import { TableListItemHeader } from './table-list-item-header/table-list-item-header';
import { TableListItemContent } from './table-list-item-content/table-list-item-content';
import { DBTable } from '@/lib/domain/db-table';
export interface TableListItemProps {}
export interface TableListItemProps {
table: DBTable;
}
export const TableListItem: React.FC<TableListItemProps> = () => {
const tableColor = randomHSLA();
export const TableListItem: React.FC<TableListItemProps> = ({ table }) => {
const { id, color } = table;
return (
<AccordionItem value="item-1" className="rounded-md">
<AccordionItem value={id} className="rounded-md">
<AccordionTrigger
className="hover:no-underline hover:bg-accent rounded-md px-2 border-l-[6px] py-0 data-[state=open]:rounded-b-none"
style={{
borderColor: tableColor,
borderColor: color,
}}
>
<TableListItemHeader />
<TableListItemHeader table={table} />
</AccordionTrigger>
<AccordionContent>
<TableListItemContent tableColor={tableColor} />
<TableListItemContent table={table} />
</AccordionContent>
</AccordionItem>
);

View File

@@ -1,17 +1,22 @@
import React from 'react';
import { Accordion } from '@/components/accordion/accordion';
import { TableListItem } from './table-list-item/table-list-item';
import { DBTable } from '@/lib/domain/db-table';
export interface TableListProps {}
export interface TableListProps {
tables: DBTable[];
}
export const TableList: React.FC<TableListProps> = () => {
export const TableList: React.FC<TableListProps> = ({ tables }) => {
return (
<Accordion
type="single"
collapsible
className="flex flex-col w-full gap-1"
>
<TableListItem />
{tables.map((table) => (
<TableListItem key={table.id} table={table} />
))}
</Accordion>
);
};

View File

@@ -1,13 +1,27 @@
import React from 'react';
import React, { useMemo } from 'react';
import { TableList } from './table-list/table-list';
import { Button } from '@/components/button/button';
import { Table, ListCollapse } from 'lucide-react';
import { ScrollArea } from '@/components/scroll-area/scroll-area';
import { Input } from '@/components/input/input';
import { DBTable } from '@/lib/domain/db-table';
import { useChartDB } from '@/hooks/use-chartdb';
export interface TablesSectionProps {}
export const TablesSection: React.FC<TablesSectionProps> = () => {
const { createTable, tables } = useChartDB();
const [filterText, setFilterText] = React.useState('');
const filteredTables = useMemo(() => {
const filter: (table: DBTable) => boolean = (table) =>
!filterText?.trim?.() ||
table.name.toLowerCase().includes(filterText.toLowerCase());
return tables.filter(filter);
}, [tables, filterText]);
return (
<section className="flex flex-col px-2 overflow-hidden flex-1">
<div className="flex items-center py-1 justify-between gap-4">
@@ -21,16 +35,22 @@ export const TablesSection: React.FC<TablesSectionProps> = () => {
type="text"
placeholder="Filter"
className="h-8 focus-visible:ring-0 w-full"
value={filterText}
onChange={(e) => setFilterText(e.target.value)}
/>
</div>
<Button variant="secondary" className="text-xs h-8 p-2">
<Button
variant="secondary"
className="text-xs h-8 p-2"
onClick={createTable}
>
<Table className="h-4" />
Add Table
</Button>
</div>
<div className="flex flex-col flex-1 overflow-hidden">
<ScrollArea className="h-full">
<TableList />
<TableList tables={filteredTables} />
</ScrollArea>
</div>
</section>

View File

@@ -2,11 +2,16 @@ import React from 'react';
import { RouteObject, createBrowserRouter } from 'react-router-dom';
import { NotFoundPage } from './pages/not-found-page/not-found-page';
import { EditorPage } from './pages/editor-page/editor-page';
import { ChartDBProvider } from './context/chartdb-context/chartdb-provider';
const routes: RouteObject[] = [
{
path: '/',
element: <EditorPage />,
element: (
<ChartDBProvider>
<EditorPage />
</ChartDBProvider>
),
},
{
path: '*',