diff --git a/public/mysql_logo.png b/public/mysql_logo.png new file mode 100644 index 0000000..79eb4f3 Binary files /dev/null and b/public/mysql_logo.png differ diff --git a/public/postgresql_logo.png b/public/postgresql_logo.png new file mode 100644 index 0000000..d3da834 Binary files /dev/null and b/public/postgresql_logo.png differ diff --git a/public/sqlite_logo.png b/public/sqlite_logo.png new file mode 100644 index 0000000..5743d82 Binary files /dev/null and b/public/sqlite_logo.png differ diff --git a/src/App.tsx b/src/App.tsx index ca28157..6e7138f 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -9,6 +9,7 @@ import DatabaseProvider from "./providers/database-provider/database-provider"; import DiagramProvider from "./providers/diagram-provider/diagram-provider"; import { ToastProvider } from "@heroui/react"; +import { ModalProvider } from "./providers/modal-provider/modal-provider"; @@ -17,22 +18,22 @@ function App() { const appRoutes = useAppRoutes(); return ( <> - - - - - - + + + + + + {appRoutes} + + + + + - - - - - ); } diff --git a/src/components/checkbox/database-checkbox.tsx b/src/components/checkbox/database-checkbox.tsx new file mode 100644 index 0000000..a577941 --- /dev/null +++ b/src/components/checkbox/database-checkbox.tsx @@ -0,0 +1,37 @@ +import { DatabaseType } from "@/lib/database" +import { Checkbox, Image } from "@heroui/react"; + + + + +interface DatabaseCheckboxProps { + database: DatabaseType, +} + +const DatabaseCheckbox: React.FC = ({ database }) => { + + return ( + +
+ +
+
+ ) +} + + +export default DatabaseCheckbox; diff --git a/src/components/menu/menu-dropdown.tsx b/src/components/menu/menu-dropdown.tsx index 2d31d1a..99da743 100644 --- a/src/components/menu/menu-dropdown.tsx +++ b/src/components/menu/menu-dropdown.tsx @@ -60,7 +60,7 @@ const DropdownMenu: React.FC = ({ title, children, clickHand {child.title} { child.divide && -
+
} : @@ -68,7 +68,7 @@ const DropdownMenu: React.FC = ({ title, children, clickHand { child.divide && -
+
} diff --git a/src/components/menu/menu.tsx b/src/components/menu/menu.tsx index 81d6c40..9fa486f 100644 --- a/src/components/menu/menu.tsx +++ b/src/components/menu/menu.tsx @@ -1,8 +1,10 @@ import { useTheme } from "next-themes"; import DropdownMenu, { MenuDropdownProps } from "./menu-dropdown"; -import React, { useMemo } from "react"; +import React, { useMemo } from "react"; import { useTranslation } from "react-i18next"; +import { useModal } from "@/providers/modal-provider/modal-provider"; +import { Modals } from "@/providers/modal-provider/modal-contxet"; @@ -20,12 +22,23 @@ const Menu: React.FC = ({ }) => { const { setTheme } = useTheme() const { t } = useTranslation(); + const { open } = useModal(); const menu: MenuDropdownProps[] = useMemo(() => [ { title: t("menu.file"), children: [ - { title: t("menu.new") }, - { title: t("menu.open"), shortcut: "Ctnl + O" }, + { + title: t("menu.new"), + clickHandler: () => { + open(Modals.CREATE_DATABASE) + } + + }, + { + title: t("menu.open"), shortcut: "Ctnl + O", clickHandler: () => { + open(Modals.OPEN_DATABASE) + } + }, { title: t("menu.save"), shortcut: "Ctnl + S", divide: true }, { title: t("menu.import"), diff --git a/src/components/modal/modal.tsx b/src/components/modal/modal.tsx index de226ae..d02f177 100644 --- a/src/components/modal/modal.tsx +++ b/src/components/modal/modal.tsx @@ -1,5 +1,5 @@ -import React, { ReactNode } from "react"; +import React, { ReactNode, useState } from "react"; import { Modal as HeroUiModal, ModalContent, @@ -20,20 +20,24 @@ export interface ModalProps { title: string, children: ReactNode, actionName?: string, - actionHandler?: () => void , - isDisabled? : boolean + actionHandler?: () => void, + isDisabled?: boolean, + header?: string } -const Modal: React.FC = ({ isOpen, onOpenChange, className, backdrop = "opaque", title, children, actionName = "Action" , actionHandler , isDisabled}) => { +const Modal: React.FC = ({ isOpen, onOpenChange, className, backdrop = "opaque", title, children, actionName = "Action", header, actionHandler, isDisabled }) => { const targetRef = React.useRef(null); const { moveProps } = useDraggable({ targetRef, canOverflow: true, isDisabled: !isOpen }); + const [isLoading , setIsLoading] = useState( false ) ; - const {t} = useTranslation() ; + const { t } = useTranslation(); - const handleAction = (onClose: () => void) => { - actionHandler && actionHandler() ; + const handleAction = async (onClose: () => void) => { + setIsLoading( true) + actionHandler && await actionHandler(); + setIsLoading( false) ; onClose() } return ( @@ -44,13 +48,20 @@ const Modal: React.FC = ({ isOpen, onOpenChange, className, backdrop className={className} backdrop={backdrop} radius="sm" - + > {(onClose) => ( <> - + + {title} + { + header && +

+ {header} +

+ }
{children} @@ -58,9 +69,9 @@ const Modal: React.FC = ({ isOpen, onOpenChange, className, backdrop
-
diff --git a/src/hooks/use-table-to-node.tsx b/src/hooks/use-table-to-node.tsx index 98f19e5..32c1287 100644 --- a/src/hooks/use-table-to-node.tsx +++ b/src/hooks/use-table-to-node.tsx @@ -3,10 +3,11 @@ import { TableType } from "@/lib/schemas/table-schema"; import { Node, useReactFlow } from "@xyflow/react"; import { useEffect } from "react"; import { getDefaultTableOverlapping } from "@/utils/tables"; -import hash from "object-hash"; -import { compare } from "fast-json-patch"; +import hash from "object-hash"; + + export const useTableToNode = (tables: TableType[]): void => { - const { setNodes } = useReactFlow(); + const { setNodes } = useReactFlow(); useEffect(() => { @@ -38,19 +39,10 @@ export const useTableToNode = (tables: TableType[]): void => { if (!node) return tableNode; else { - // const diff = compare(node.data.table as TableType, tableNode.data.table as TableType); - - - //console.log (diff) - + const hashNode: string = hash(node.data.table as TableType); const hashTableNode: string = hash(tableNode.data.table as TableType); - - - return hashNode == hashTableNode ? node : tableNode; - - } }) diff --git a/src/i18/index.ts b/src/i18/index.ts index 12a3e09..0e78df9 100644 --- a/src/i18/index.ts +++ b/src/i18/index.ts @@ -28,7 +28,7 @@ i18n.use(LanguageDetector) .use(initReactI18next) .init({ resources , - lng: 'en', + lng: 'fr', interpolation: { escapeValue: false, diff --git a/src/i18/languages/en.ts b/src/i18/languages/en.ts index 75fa55f..7fe057a 100644 --- a/src/i18/languages/en.ts +++ b/src/i18/languages/en.ts @@ -8,10 +8,7 @@ export const en = { tables: "Tables", relationships: "Relationships" }, - modal: { - close: "Close", - create: "Create" - }, + color_picker: { default_color: "Default color" }, @@ -117,8 +114,23 @@ export const en = { dark: "Dark", help: "Help", show_docs: "Show Docs", - join_discord: "Join Discord" + join_discord: "Join Discord", + + }, + + modals: { + close: "Close", + create: "Create" , + pick_database: "Pick your Database.", + create_database_header: "Every database offers distinct features and functionalities." , + db_name : "Database name" , + db_name_error : "Please provide a Database name" , + continue : "Continue" , + open : "Open" , + open_database : "Open Database" , + open_database_header : "Open a database by selecting one from the list." } + } } diff --git a/src/lib/database.ts b/src/lib/database.ts new file mode 100644 index 0000000..fcaed01 --- /dev/null +++ b/src/lib/database.ts @@ -0,0 +1,32 @@ + + + + +export interface DatabaseType { + name: string, + dialect: string; + logo: string +} + +export const DBTypes: DatabaseType[] = [ + { + name: "PostgreSql", + dialect: "postgres", + logo: "/postgresql_logo.png" + }, { + name: "Mysql", + dialect: "mysql", + logo: "/mysql_logo.png" + }, + { + name: "Sqlite", + dialect: "sqlite", + logo: "/sqlite_logo.png" + }, +] + + +export const getDatabaseByDialect = (dialect: string): DatabaseType => { + const dbType : DatabaseType | undefined = DBTypes.find((dbType : DatabaseType) => dbType.dialect == dialect) ; + return dbType ? dbType : DBTypes[0] ; +} \ No newline at end of file diff --git a/src/lib/schemas/database-schema.ts b/src/lib/schemas/database-schema.ts index 2d4f0f8..3ad9238 100644 --- a/src/lib/schemas/database-schema.ts +++ b/src/lib/schemas/database-schema.ts @@ -1,5 +1,5 @@ import { InferInsertModel, InferSelectModel, relations } from 'drizzle-orm'; -import { sqliteTable, text } from 'drizzle-orm/sqlite-core'; +import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core'; import { tables, TableType } from './table-schema'; import { relationships, RelationshipType } from './relationship-schema'; @@ -10,6 +10,13 @@ export const databases = sqliteTable('databases', { .notNull() .unique(), name: text('name').notNull(), + dialect: text("dialect", { + enum: ["postgres", "mysql", "sqlite"], + }).notNull().default("postgres"), + + numOfTables: integer("numOfTables") + .notNull() + .default(0), createdAt: text('createdAt'), }); @@ -20,7 +27,7 @@ export const databaseRelations = relations(databases, ({ many }) => ({ export interface DatabaseType extends InferSelectModel { - tables : TableType[] , - relationships : RelationshipType[] - }; + tables: TableType[], + relationships: RelationshipType[] +}; export interface DatabaseInsertType extends InferInsertModel { }; \ No newline at end of file diff --git a/src/pages/database/database-page.tsx b/src/pages/database/database-page.tsx index ffd6dd4..297d498 100644 --- a/src/pages/database/database-page.tsx +++ b/src/pages/database/database-page.tsx @@ -40,6 +40,7 @@ import { useTheme } from "next-themes"; const DatabasePage: React.FC = () => { + const { t } = useTranslation(); const { resolvedTheme } = useTheme(); // Extract database state and operations @@ -102,7 +103,7 @@ const DatabasePage: React.FC = () => { ) as NodePositionChange[]; const nodeRemoveChanges: NodeRemoveChange[] = changes.filter((change: NodeChange) => change.type == "remove"); - + // Save new positions to the database if (nodePositionChanges.length > 0) await updateTablePositions(nodePositionChanges.map((change: NodePositionChange) => ({ @@ -112,9 +113,10 @@ const DatabasePage: React.FC = () => { } as TableInsertType))); // Delete tables if removed - if (nodeRemoveChanges.length > 0) + if (nodeRemoveChanges.length > 0) { + deleteMultiTables(nodeRemoveChanges.map((change: NodeRemoveChange) => change.id)); - + } return onNodesChange(changes); }, [onNodesChange]); @@ -142,9 +144,9 @@ const DatabasePage: React.FC = () => { // Automatically reposition tables to avoid overlap and fit the view const adjustPositions = useCallback(async () => { - const adjustedTables = await adjustTablesPositions(nodes, relationships); - console.log (adjustedTables ) - updateTablePositions(adjustedTables) + const adjustedTables = await adjustTablesPositions(nodes, relationships); + + updateTablePositions(adjustedTables) setTimeout(() => { fitView({ duration: 500 @@ -152,86 +154,89 @@ const DatabasePage: React.FC = () => { }, 300); }, [relationships, nodes]); + // Convert tables and relationships into flow elements useTableToNode(tables); useRelationshipToEdge(relationships); useHighlightedEdges(nodes, relationships, edges); const { isOverlapping, puls } = useOverlappingTables(tables); - - - + + return (
-
- + { - + - - + - - -
- { - isOverlapping && - - - - - - - - {t("table.overlapping_tables")} - - - } + + + + + +
+ { + isOverlapping && + + + + + + + + {t("table.overlapping_tables")} + + + } +
-
+ } diff --git a/src/pages/database/db-controller/relationship-controller/relationship-controller.tsx b/src/pages/database/db-controller/relationship-controller/relationship-controller.tsx index 1edde47..ed1929e 100644 --- a/src/pages/database/db-controller/relationship-controller/relationship-controller.tsx +++ b/src/pages/database/db-controller/relationship-controller/relationship-controller.tsx @@ -5,28 +5,27 @@ import { Ref, useCallback, useEffect, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import RelationshipAccordionHeader from "./relationship-accordion-item/relationship-accordion-header"; import RelationshipAccordionBody from "./relationship-accordion-item/relationship-accordion-body"; -import Modal from "@/components/modal/modal"; -import CreateRelationshipForm from "./create-relationship-form/create-relationship-form"; + +import CreateRelationshipForm from "../../modals/create-relationship-modal"; import { RelationshipInsertType, RelationshipType } from "@/lib/schemas/relationship-schema"; import { useDatabase, useDatabaseOperations } from "@/providers/database-provider/database-provider"; -import { v4 } from "uuid"; + import { useDiagram } from "@/providers/diagram-provider/diagram-provider"; import { getDefaultRelationshipName } from "@/hooks/use-relationship-name"; +import { useModal } from "@/providers/modal-provider/modal-provider"; +import { Modals } from "@/providers/modal-provider/modal-contxet"; -interface Props { - -} + -const RelationshipController: React.FC = ({ }) => { - const [relationship, setRelationship] = useState(undefined); - const [isValid, setIsValid] = useState(false); - const { isOpen, onOpen, onOpenChange } = useDisclosure(); +const RelationshipController: React.FC = ({ }) => { + + + const { open } = useModal(); const { database } = useDatabase(); - const { createRelationship } = useDatabaseOperations(); const { relationships: allRelationships } = database; const [relationships, setRelationships] = useState(allRelationships); @@ -37,18 +36,6 @@ const RelationshipController: React.FC = ({ }) => { useEffect(() => setRelationships(allRelationships), [allRelationships]); - const addRelationship = useCallback(() => { - - const newRelationshipId: string = v4(); - - createRelationship({ - id: newRelationshipId, - ...relationship, - } as RelationshipInsertType); - - setSelectedRelationship(new Set([newRelationshipId]) as any); - }, [relationship]); - useEffect(() => { if (focusedRelationshipId) { @@ -57,6 +44,11 @@ const RelationshipController: React.FC = ({ }) => { }, [focusedRelationshipId]); + const onOpen = useCallback(() => { + open(Modals.CREATE_RELATIONSHIP, { + onRlationshipCreated: (id: string) => setSelectedRelationship(new Set([id]) as any) + }) + }, []) const searchRelationships = useCallback(() => { const keyword: string | undefined = nameRef.current?.value; @@ -164,21 +156,7 @@ const RelationshipController: React.FC = ({ }) => { ))}
- - - ) } diff --git a/src/pages/database/db-controller/tables-controller/table-accordion-item/field/field-item.tsx b/src/pages/database/db-controller/tables-controller/table-accordion-item/field/field-item.tsx index 9932344..443c693 100644 --- a/src/pages/database/db-controller/tables-controller/table-accordion-item/field/field-item.tsx +++ b/src/pages/database/db-controller/tables-controller/table-accordion-item/field/field-item.tsx @@ -147,12 +147,12 @@ const FieldItem: React.FC = ({ field }) => { - +

{t("db_controller.field_setting")}

-
+
{t("db_controller.unique")} @@ -174,11 +174,11 @@ const FieldItem: React.FC = ({ field }) => { onValueChange={setNote} onBlur={updateFieldNote} classNames={{ - inputWrapper: "bg-default border-divider dark:bg-background-100", + inputWrapper: "bg-default border-divider ", base: "max-w-xs", input: "resize-y min-h-[60px] max-h-[180px]", }} /> -
+
+ +
+ +
+ + + ) +} + + + + diff --git a/src/pages/database/db-controller/relationship-controller/create-relationship-form/create-relationship-form.tsx b/src/pages/database/modals/create-relationship-modal.tsx similarity index 66% rename from src/pages/database/db-controller/relationship-controller/create-relationship-form/create-relationship-form.tsx rename to src/pages/database/modals/create-relationship-modal.tsx index 1cb2146..0df9f6a 100644 --- a/src/pages/database/db-controller/relationship-controller/create-relationship-form/create-relationship-form.tsx +++ b/src/pages/database/modals/create-relationship-modal.tsx @@ -1,23 +1,27 @@ import Autocomplete from "@/components/auto-complete/auto-complete"; +import Modal, { ModalProps } from "@/components/modal/modal"; import { FieldType } from "@/lib/schemas/field-schema"; import { RelationshipInsertType } from "@/lib/schemas/relationship-schema"; import { TableType } from "@/lib/schemas/table-schema"; -import { useDatabase } from "@/providers/database-provider/database-provider"; -import { FileKey, FileMinus2, FileOutput, KeyRound } from "lucide-react"; -import { useEffect, useMemo, useState } from "react"; +import { useDatabase, useDatabaseOperations } from "@/providers/database-provider/database-provider"; +import { FileKey, FileMinus2, FileOutput, KeyRound } from "lucide-react"; +import { useCallback, useEffect, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; +import { v4 } from "uuid"; -interface CreateRelationshipFormProps { - onRelationshipChanges: (relationship: RelationshipInsertType) => void, - onValidationChanges?: (isValid: boolean) => void +interface CreateRelationshipModalProps extends ModalProps { + onRelationshipChanges?: (relationship: RelationshipInsertType) => void, + onRlationshipCreated?: (id: string) => void } -const CreateRelationshipForm: React.FC = ({ onRelationshipChanges, onValidationChanges }) => { +const CreateRelationshipModal: React.FC = ({ onRelationshipChanges, isOpen, onOpenChange, onRlationshipCreated }) => { + const [isValid, setIsValid] = useState(false); + const { createRelationship } = useDatabaseOperations(); const [relationship, setRelationship] = useState({ sourceTableId: "", @@ -62,15 +66,39 @@ const CreateRelationshipForm: React.FC = ({ onRelat }, [relationship, sourceFields, targetFields]); useEffect(() => { - onValidationChanges && onValidationChanges((relationship.sourceFieldId && relationship.targetFieldId && fieldTypesMatches) as boolean) - }, [relationship, fieldTypesMatches]) + setIsValid((relationship.sourceFieldId && relationship.targetFieldId && fieldTypesMatches) as boolean) + }, [relationship, fieldTypesMatches]); + + + const addRelationship = useCallback(() => { + + const id: string = v4(); + + createRelationship({ + ...relationship, + id, + } as RelationshipInsertType); + onRlationshipCreated && onRlationshipCreated(id); + + }, [relationship]); + return ( - <> + +
-
-
-
-
} - + + ) } -export default CreateRelationshipForm; \ No newline at end of file +export default CreateRelationshipModal; \ No newline at end of file diff --git a/src/pages/database/modals/open-database-modal.tsx b/src/pages/database/modals/open-database-modal.tsx new file mode 100644 index 0000000..4aa4216 --- /dev/null +++ b/src/pages/database/modals/open-database-modal.tsx @@ -0,0 +1,84 @@ +import Modal, { ModalProps } from "@/components/modal/modal"; +import { getDatabaseByDialect } from "@/lib/database"; +import { DatabaseType } from "@/lib/schemas/database-schema"; + +import { useDatabase, useDatabaseOperations } from "@/providers/database-provider/database-provider"; + +import { Image, Selection, Table, TableBody, TableCell, TableColumn, TableHeader, TableRow } from "@heroui/react"; +import { Key, useState } from "react"; +import { useTranslation } from "react-i18next"; + + + + + +const OpenDatabaseModal: React.FC = ({ isOpen, onOpenChange }) => { + + const { t } = useTranslation(); + const { databases } = useDatabase(); + const { setCurrentDatabaseId } = useDatabaseOperations(); + const [selectedDatabase, setSelectedDatabase] = useState(undefined); + const openDatabase = () => { + setCurrentDatabaseId( selectedDatabase.currentKey) + } + + + + return ( + + + + Dialect + Name + Created at + Tables + + + { + databases.map((database: DatabaseType) => ( + + + + + {database.name} + { + new Date(database.createdAt as string).toLocaleString("en-US") + } + {database.numOfTables} + + )) + } + + + +
+ +
+ ) +} + + +export default OpenDatabaseModal; \ No newline at end of file diff --git a/src/providers/database-history/database-history-provider.tsx b/src/providers/database-history/database-history-provider.tsx index 2bc2bb9..ade19b3 100644 --- a/src/providers/database-history/database-history-provider.tsx +++ b/src/providers/database-history/database-history-provider.tsx @@ -42,10 +42,13 @@ const DatabaseHistoryProvider: React.FC = ({ children }) => { }, [redoChanges, udpateDbFlag, isProcessing]); useEffect(() => { + if (!udpateDbFlag.current) { udpateDbFlag.current = true; return; } + + const normalizedDatabase = normalizeDatabase(database); const normalizedPresent = normalizeDatabase(datatbaseState.present); diff --git a/src/providers/database-provider/database-context.ts b/src/providers/database-provider/database-context.ts index e59941c..ed1fa3c 100644 --- a/src/providers/database-provider/database-context.ts +++ b/src/providers/database-provider/database-context.ts @@ -1,6 +1,6 @@ import { DataType } from "@/lib/schemas/data-type-schema"; -import { DatabaseType } from "@/lib/schemas/database-schema"; +import { DatabaseInsertType, DatabaseType } from "@/lib/schemas/database-schema"; import { FieldInsertType, FieldType } from "@/lib/schemas/field-schema"; import { IndexInsertType } from "@/lib/schemas/index-schema"; import { RelationshipInsertType } from "@/lib/schemas/relationship-schema"; @@ -15,6 +15,7 @@ import { createContext } from "react"; interface DatabaseDataContextType { database: DatabaseType, + databases : DatabaseType[] , isLoading: boolean, getField: (tableId: string, id: string) => FieldType | undefined, @@ -23,6 +24,11 @@ interface DatabaseDataContextType { interface DatabaseOperationsContextType { data_types: DataType[], + // database operations + createDatabase: (database: DatabaseInsertType) => Promise, + editDatabase: (database: DatabaseInsertType) => Promise, + deleteDatabase : ( id : string) => Promise , + setCurrentDatabaseId : ( id : string) => void , createTable: (table: TableInsertType) => Promise, editTable: (table: TableInsertType) => Promise, @@ -36,10 +42,10 @@ interface DatabaseOperationsContextType { orderTableFields: (fields: FieldType[]) => Promise, // index operations - createIndex : ( index : IndexInsertType) => Promise , - editIndex : ( index : IndexInsertType) => Promise , - deleteIndex : ( id : string) => Promise , - editFieldIndices:( indexId : string , fieldIds : string[])=> Promise , + createIndex: (index: IndexInsertType) => Promise, + editIndex: (index: IndexInsertType) => Promise, + deleteIndex: (id: string) => Promise, + editFieldIndices: (indexId: string, fieldIds: string[]) => Promise, // relationship operations createRelationship: (relationship: RelationshipInsertType) => Promise, editRelationship: (relationship: RelationshipInsertType) => Promise, diff --git a/src/providers/database-provider/database-provider.tsx b/src/providers/database-provider/database-provider.tsx index abeac83..7f9055e 100644 --- a/src/providers/database-provider/database-provider.tsx +++ b/src/providers/database-provider/database-provider.tsx @@ -10,7 +10,7 @@ import { FieldInsertType, fields, FieldType } from "@/lib/schemas/field-schema"; import { RelationshipInsertType, relationships } from "@/lib/schemas/relationship-schema"; import DatabaseHistoryProvider from "../database-history/database-history-provider"; import { DBDiffOperation } from "@/utils/database"; -import { DatabaseType } from "@/lib/schemas/database-schema"; +import { DatabaseInsertType, DatabaseType, databases as databaseModel } from "@/lib/schemas/database-schema"; import { getTimestamp } from "@/utils/utils"; import { IndexInsertType, indices } from "@/lib/schemas/index-schema"; import { field_indices } from "@/lib/schemas/field_index-schema"; @@ -23,19 +23,19 @@ const DatabaseProvider: React.FC = ({ children }) => { const [currentDatabaseId, setCurrentDatabaseId] = useState(undefined); // Fetch all databases - const { data: databases, isLoading: loadingDatabases } = useQuery(toCompilableQuery( + const { data: databases, isLoading: loadingDatabases , isFetching : fetchingDatabases} = useQuery(toCompilableQuery( db.query.databases.findMany() )); // Fetch all data types - const { data: data_types, isLoading: loadingDataTypes } = useQuery(toCompilableQuery( + const { data: data_types, isLoading: loadingDataTypes , isFetching : fetchingDataTypes} = useQuery(toCompilableQuery( db.query.data_types.findMany() )); // Fetch the current database with nested tables, fields, and relationships - let { data: database, isLoading: loadingCurrentDatabase } = useQuery( + let { data: database, isLoading: loadingCurrentDatabase , isFetching : fetchingCurrentDatabase} = useQuery( toCompilableQuery( - db.query.databases.findFirst({ + db.query.databases.findMany({ where: (databases, { eq }) => eq(databases.id, currentDatabaseId as string), with: { tables: { @@ -68,9 +68,9 @@ const DatabaseProvider: React.FC = ({ children }) => { }) ) ); - - - + + + // Normalize result to single object if (database.length == 1) database = database[0] as any; @@ -82,7 +82,22 @@ const DatabaseProvider: React.FC = ({ children }) => { } }, [currentDatabaseId, databases]) - const isLoading: boolean = loadingDataTypes || loadingDatabases || loadingCurrentDatabase; + const isLoading: boolean = loadingDataTypes || loadingDatabases || loadingCurrentDatabase ; + // CRUD for database + const createDatabase = useCallback(async (database: DatabaseInsertType): Promise => { + return await db.insert(databaseModel).values({ + ...database, + createdAt: getTimestamp() + }); + }, [db]); + + const editDatabase = useCallback(async (database: DatabaseInsertType): Promise => { + return await db.update(databaseModel).set(database).where(eq(databaseModel.id, database.id)) + }, [db]); + + const deleteDatabase = useCallback(async (id: string): Promise => { + await db.delete(databaseModel).where(eq(databaseModel.id, id)); + }, [db]); // CRUD operations for Tables const createTable = useCallback(async (table: TableInsertType): Promise => { @@ -219,6 +234,7 @@ const DatabaseProvider: React.FC = ({ children }) => { // Apply a list of diff operations to sync database const executeDbDiffOps = useCallback(async (operations: DBDiffOperation[]) => { try { + console.log ("excuted diff operations") await db.transaction(async (tx) => { for (const operation of operations) { if (operation.type == "CREATE_TABLE") { @@ -253,9 +269,9 @@ const DatabaseProvider: React.FC = ({ children }) => { } else if (operation.type == "CREATE_INDEX") { await tx.insert(indices).values(operation.index); - if (operation.index.fieldIndices && Object.values(operation.index.fieldIndices).length > 0) - await tx.insert(field_indices).values(Object.values(operation.index.fieldIndices)); - + if (operation.index.fieldIndices && Object.values(operation.index.fieldIndices).length > 0) + await tx.insert(field_indices).values(Object.values(operation.index.fieldIndices)); + } else if (operation.type == "DELETE_INDEX") { await tx.delete(indices).where(eq(indices.id, operation.indexId)); @@ -279,6 +295,11 @@ const DatabaseProvider: React.FC = ({ children }) => { }, [db]); const databaseOpsValue = useMemo(() => ({ + + createDatabase, + editDatabase, + deleteDatabase, + setCurrentDatabaseId, createTable, editTable, deleteTable, @@ -299,6 +320,9 @@ const DatabaseProvider: React.FC = ({ children }) => { editFieldIndices, data_types }), [ + createDatabase, + editDatabase, + deleteDatabase, createTable, editTable, deleteTable, @@ -324,6 +348,7 @@ const DatabaseProvider: React.FC = ({ children }) => { diff --git a/src/providers/modal-provider/modal-contxet.tsx b/src/providers/modal-provider/modal-contxet.tsx index eb6f717..7604c7f 100644 --- a/src/providers/modal-provider/modal-contxet.tsx +++ b/src/providers/modal-provider/modal-contxet.tsx @@ -1,18 +1,14 @@ -import { createContext } from "react"; +import { createContext } from "react"; export enum Modals { - CREATE_RELATIONSHIP = "CREATE_RELATIONSHIP" + CREATE_RELATIONSHIP = "CREATE_RELATIONSHIP", + CREATE_DATABASE = "CREATE_DATABASE" , + OPEN_DATABASE = "OPEN_DATABASE" } - - - - - interface ModalContextType { - focusedTableId: string | undefined; - focusedRelationshipId: string | undefined; + open: (modal: Modals, props?: any) => void } export const ModalContext = createContext({} as ModalContextType); diff --git a/src/providers/modal-provider/modal-provider.tsx b/src/providers/modal-provider/modal-provider.tsx index 4e0211c..136ea2c 100644 --- a/src/providers/modal-provider/modal-provider.tsx +++ b/src/providers/modal-provider/modal-provider.tsx @@ -1,35 +1,56 @@ -/* + import { useCallback, useContext, useState } from "react" import { ModalContext, Modals } from "./modal-contxet" -import Modal, { ModalProps } from "@/components/modal/modal"; import { useDisclosure } from "@heroui/react"; +import CreateRelationshipModal from "@/pages/database/modals/create-relationship-modal"; +import { CreateDatabaseModal } from "@/pages/database/modals/create-database-modal"; +import OpenDatabaseModal from "@/pages/database/modals/open-database-modal"; + interface Props { children: React.ReactNode } interface CurrentModalProps { modal: Modals, - modalProps: ModalProps, - content?: any + props?: any, + } -const ModalProvider: React.FC = ({ children }) => { - const { isOpen, onOpen } = useDisclosure(); +export const ModalProvider: React.FC = ({ children }) => { + const [currentModal, setCurrentModal] = useState(undefined); + const { isOpen, onOpen, onClose } = useDisclosure(); + const open = useCallback((modal: Modals, props?: any) => { - const onOpenChange = useCallback(() => { + setCurrentModal({ + modal, + props + }); onOpen(); - setCurrentModal(undefined); - }, [onOpen]); + }, []); - + const onOpenChange = (isOpen: boolean) => { + if (!isOpen) { + onClose(); + setCurrentModal(undefined); + } + } return ( - + { - currentModal && - - - + currentModal ? ( + currentModal.modal == Modals.CREATE_RELATIONSHIP && + + || + currentModal.modal == Modals.CREATE_DATABASE && + + || + currentModal.modal == Modals.OPEN_DATABASE && + + ) : undefined } {children} @@ -37,6 +58,5 @@ const ModalProvider: React.FC = ({ children }) => { } -export const useModal = () => useContext(ModalContext); +export const useModal = () => useContext(ModalContext); -*/ \ No newline at end of file diff --git a/src/styles/globals.css b/src/styles/globals.css index 0a60d5b..b4493d9 100644 --- a/src/styles/globals.css +++ b/src/styles/globals.css @@ -129,4 +129,9 @@ svg.text-icon:hover { .dark .bg-background-50 { background-color: red !important; +} + + +.database-checkbox[data-selected="true"]{ + border : 1.5px solid hsl(var(--heroui-primary-300)); } \ No newline at end of file diff --git a/src/utils/utils.ts b/src/utils/utils.ts index e062430..1840d3c 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -58,5 +58,6 @@ function excludeFields( export { areArraysEqual, getTimestamp, - excludeFields + excludeFields , + } \ No newline at end of file diff --git a/tailwind.config.js b/tailwind.config.js index c1f68b5..5df8e31 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -22,7 +22,7 @@ export default { }, themes: { - + dark: { colors: { @@ -39,8 +39,9 @@ export default { DEFAULT: "#cecfd2" }, content1: { - DEFAULT: "#282d34" + DEFAULT: "#1c2026" }, + default: { 50: '#f2f3f5', 100: '#d6d7db',