mirror of
https://github.com/chartdb/chartdb.git
synced 2025-11-03 05:23:26 +00:00
create tables
This commit is contained in:
29
package-lock.json
generated
29
package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
19
src/context/chartdb-context/chartdb-context.tsx
Normal file
19
src/context/chartdb-context/chartdb-context.tsx
Normal 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: [],
|
||||
});
|
||||
62
src/context/chartdb-context/chartdb-provider.tsx
Normal file
62
src/context/chartdb-context/chartdb-provider.tsx
Normal 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
4
src/hooks/use-chartdb.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import { chartDBContext } from '@/context/chartdb-context/chartdb-context';
|
||||
import { useContext } from 'react';
|
||||
|
||||
export const useChartDB = () => useContext(chartDBContext);
|
||||
6
src/lib/data/data-types.ts
Normal file
6
src/lib/data/data-types.ts
Normal 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;
|
||||
13
src/lib/domain/db-field.ts
Normal file
13
src/lib/domain/db-field.ts
Normal 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];
|
||||
9
src/lib/domain/db-index.ts
Normal file
9
src/lib/domain/db-index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { DBField } from './db-field';
|
||||
|
||||
export interface DBIndex {
|
||||
id: string;
|
||||
name: string;
|
||||
unique: boolean;
|
||||
fieldIds: string[];
|
||||
fields?: DBField[];
|
||||
}
|
||||
13
src/lib/domain/db-table.ts
Normal file
13
src/lib/domain/db-table.ts
Normal 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;
|
||||
}
|
||||
@@ -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 = () => {};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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: '*',
|
||||
|
||||
Reference in New Issue
Block a user