diff --git a/package-lock.json b/package-lock.json index 91408151..ee9bb12e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index 99aa3547..f1150389 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/context/chartdb-context/chartdb-context.tsx b/src/context/chartdb-context/chartdb-context.tsx new file mode 100644 index 00000000..8715ae36 --- /dev/null +++ b/src/context/chartdb-context/chartdb-context.tsx @@ -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) => void; + tables: DBTable[]; +} + +export const chartDBContext = createContext({ + createTable: emptyFn, + addTable: emptyFn, + removeTable: emptyFn, + updateTable: emptyFn, + tables: [], +}); diff --git a/src/context/chartdb-context/chartdb-provider.tsx b/src/context/chartdb-context/chartdb-provider.tsx new file mode 100644 index 00000000..25dae812 --- /dev/null +++ b/src/context/chartdb-context/chartdb-provider.tsx @@ -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 = ({ + children, +}) => { + const [tables, setTables] = React.useState([]); + + 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) => { + setTables((tables) => + tables.map((t) => (t.id === id ? { ...t, ...table } : t)) + ); + }; + + return ( + + {children} + + ); +}; diff --git a/src/hooks/use-chartdb.ts b/src/hooks/use-chartdb.ts new file mode 100644 index 00000000..65f7572a --- /dev/null +++ b/src/hooks/use-chartdb.ts @@ -0,0 +1,4 @@ +import { chartDBContext } from '@/context/chartdb-context/chartdb-context'; +import { useContext } from 'react'; + +export const useChartDB = () => useContext(chartDBContext); diff --git a/src/lib/data/data-types.ts b/src/lib/data/data-types.ts new file mode 100644 index 00000000..25105f7d --- /dev/null +++ b/src/lib/data/data-types.ts @@ -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; diff --git a/src/lib/domain/db-field.ts b/src/lib/domain/db-field.ts new file mode 100644 index 00000000..996d3a97 --- /dev/null +++ b/src/lib/domain/db-field.ts @@ -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]; diff --git a/src/lib/domain/db-index.ts b/src/lib/domain/db-index.ts new file mode 100644 index 00000000..023eb2a0 --- /dev/null +++ b/src/lib/domain/db-index.ts @@ -0,0 +1,9 @@ +import { DBField } from './db-field'; + +export interface DBIndex { + id: string; + name: string; + unique: boolean; + fieldIds: string[]; + fields?: DBField[]; +} diff --git a/src/lib/domain/db-table.ts b/src/lib/domain/db-table.ts new file mode 100644 index 00000000..9340eff9 --- /dev/null +++ b/src/lib/domain/db-table.ts @@ -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; +} diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 0e85dc04..094dd1aa 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -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 = () => {}; diff --git a/src/pages/editor-page/side-panel/tables-section/table-list/table-list-item/table-list-item-content/table-list-item-content.tsx b/src/pages/editor-page/side-panel/tables-section/table-list/table-list-item/table-list-item-content/table-list-item-content.tsx index 36f9b13b..8ffcf369 100644 --- a/src/pages/editor-page/side-panel/tables-section/table-list/table-list-item/table-list-item-content/table-list-item-content.tsx +++ b/src/pages/editor-page/side-panel/tables-section/table-list/table-list-item/table-list-item-content/table-list-item-content.tsx @@ -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 = ({ - tableColor, + table, }) => { + const { color } = table; const renderField = () => { return (
@@ -122,7 +124,7 @@ export const TableListItemContent: React.FC = ({
= () => { +export const TableListItemHeader: React.FC = ({ + table, +}) => { return (
-
table_1
+
{table.name}
diff --git a/src/pages/editor-page/side-panel/tables-section/table-list/table-list-item/table-list-item.tsx b/src/pages/editor-page/side-panel/tables-section/table-list/table-list-item/table-list-item.tsx index b5c754bf..7b21db81 100644 --- a/src/pages/editor-page/side-panel/tables-section/table-list/table-list-item/table-list-item.tsx +++ b/src/pages/editor-page/side-panel/tables-section/table-list/table-list-item/table-list-item.tsx @@ -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 = () => { - const tableColor = randomHSLA(); +export const TableListItem: React.FC = ({ table }) => { + const { id, color } = table; return ( - + - + - + ); diff --git a/src/pages/editor-page/side-panel/tables-section/table-list/table-list.tsx b/src/pages/editor-page/side-panel/tables-section/table-list/table-list.tsx index 66e520c6..b58fb321 100644 --- a/src/pages/editor-page/side-panel/tables-section/table-list/table-list.tsx +++ b/src/pages/editor-page/side-panel/tables-section/table-list/table-list.tsx @@ -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 = () => { +export const TableList: React.FC = ({ tables }) => { return ( - + {tables.map((table) => ( + + ))} ); }; diff --git a/src/pages/editor-page/side-panel/tables-section/tables-section.tsx b/src/pages/editor-page/side-panel/tables-section/tables-section.tsx index 12fd08ab..a7515ecb 100644 --- a/src/pages/editor-page/side-panel/tables-section/tables-section.tsx +++ b/src/pages/editor-page/side-panel/tables-section/tables-section.tsx @@ -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 = () => { + 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 (
@@ -21,16 +35,22 @@ export const TablesSection: React.FC = () => { type="text" placeholder="Filter" className="h-8 focus-visible:ring-0 w-full" + value={filterText} + onChange={(e) => setFilterText(e.target.value)} />
-