open database feature is done

This commit is contained in:
KarimTamani
2025-06-02 20:09:42 +01:00
parent 5d821a9865
commit 90b24a7fc7
9 changed files with 149 additions and 63 deletions
+3 -3
View File
@@ -4,12 +4,12 @@ import { Node, useReactFlow } from "@xyflow/react";
import { useEffect } from "react";
import { getDefaultTableOverlapping } from "@/utils/tables";
import hash from "object-hash";
import { useDatabase } from "@/providers/database-provider/database-provider";
export const useTableToNode = (tables: TableType[]): void => {
const { setNodes } = useReactFlow();
useEffect(() => {
const tableNodes = tables.map((table: TableType) => {
@@ -33,7 +33,7 @@ export const useTableToNode = (tables: TableType[]): void => {
})
setNodes((nodes) => {
return tableNodes.map((tableNode) => {
const node: Node | undefined = nodes.find((node: Node) => node.id == tableNode.id);
if (!node)
+1 -9
View File
@@ -1,34 +1,26 @@
import { en, enLanguage } from "./languages/en";
import { fr, frLanguage } from "./languages/fr";
import { Language } from "./types";
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import { ar, arLanguage } from "./languages/ar";
export const languages: Language[] = [
enLanguage,
frLanguage ,
arLanguage
];
const resources = {
en,
fr,
ar
};
i18n.use(LanguageDetector)
.use(initReactI18next)
.init({
resources ,
lng: 'fr',
lng: 'en',
interpolation: {
escapeValue: false,
+5 -6
View File
@@ -7,7 +7,7 @@ import {
} from "@xyflow/react";
// React built-ins
import { useCallback, useMemo } from "react";
import { useCallback, useEffect, useMemo } from "react";
// Custom components and styles
import Table from "./table/table";
@@ -55,7 +55,7 @@ const DatabasePage: React.FC = () => {
const { setIsConnectionInProgress } = useDiagramOps();
// Destructure tables and relationships from database
const { tables, relationships } = database;
const { tables, relationships } = database;
// Hook to allow zooming and centering the diagram
const { fitView } = useReactFlow();
@@ -63,7 +63,7 @@ const DatabasePage: React.FC = () => {
// Define custom node and edge types
const nodeTypes = useMemo(() => ({ table: Table }), []);
const edgeTypes = useMemo(() => ({ 'relationship-edge': Relationship }), []);
// Called when a connection is made between fields
const onConnect = useCallback((connection: Connection) => {
const sourceFieldId: string | undefined = (connection.sourceHandle as string).split("_").pop();
@@ -113,12 +113,11 @@ 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]);
}, [onNodesChange ]);
// Called when edges (relationships) change
const handleEdgeChanges: OnEdgesChange<any> = useCallback((changes: EdgeChange<any>[]) => {
@@ -10,6 +10,7 @@ import { TableInsertType, TableType } from "@/lib/schemas/table-schema";
import { v4 } from "uuid";
import { useDiagram } from "@/providers/diagram-provider/diagram-provider";
import { useReactFlow } from "@xyflow/react";
import { randomColor } from "@/lib/colors";
interface Props { }
@@ -45,6 +46,7 @@ const TablesController: React.FC<Props> = ({ }) => {
name: `table_${tables.length + 1}`,
posX,
posY,
fields: [{
id: v4(),
name: "id",
@@ -14,7 +14,7 @@ export const CreateDatabaseModal: React.FC<ModalProps> = ({ isOpen, onOpenChange
const { t } = useTranslation();
const [selectedDbType, setSelectedDbType] = useState<string[]>([DBTypes[0].dialect]);
const [dbName, setDbName] = useState<string>("db_example");
const { createDatabase, setCurrentDatabaseId } = useDatabaseOperations();
const { createDatabase, switchDatabase } = useDatabaseOperations();
const onDatabaseTypeChange = (types: string[]) => {
const selectedType: string | undefined = types.pop();
@@ -31,7 +31,7 @@ export const CreateDatabaseModal: React.FC<ModalProps> = ({ isOpen, onOpenChange
name: dbName,
dialect: selectedDbType[0] as any
});
setCurrentDatabaseId(databaseId);
switchDatabase(databaseId);
res(databaseId)
})
@@ -15,15 +15,16 @@ import { useTranslation } from "react-i18next";
const OpenDatabaseModal: React.FC<ModalProps> = ({ isOpen, onOpenChange }) => {
const { t } = useTranslation();
const { databases } = useDatabase();
const { setCurrentDatabaseId } = useDatabaseOperations();
const [selectedDatabase, setSelectedDatabase] = useState<any | undefined>(undefined);
const { databases , currentDatabaseId} = useDatabase();
const { switchDatabase } = useDatabaseOperations();
const [selectedDatabase, setSelectedDatabase] = useState<any | undefined>(
(() => currentDatabaseId ? new Set([currentDatabaseId]) : undefined)
);
const openDatabase = () => {
setCurrentDatabaseId( selectedDatabase.currentKey)
switchDatabase( selectedDatabase.currentKey)
}
return (
<Modal
isOpen={isOpen}
@@ -44,11 +45,13 @@ const OpenDatabaseModal: React.FC<ModalProps> = ({ isOpen, onOpenChange }) => {
selectedKeys={selectedDatabase}
onSelectionChange={setSelectedDatabase}
classNames={{
wrapper: "min-h-[360px] shadow-none border-1 border-divider border-sm"
wrapper: "min-h-[360px] shadow-none border-1 border-divider rounded-sm" ,
th : "dark:bg-background" ,
tr : "hover:bg-default-100 dark:hover:bg-background rounded-lg cursor-pointer text-font/90 transition-colors duration-200 "
}}
>
<TableHeader>
<TableColumn>Dialect</TableColumn>
<TableHeader className="rounded-sm">
<TableColumn >Dialect</TableColumn>
<TableColumn>Name</TableColumn>
<TableColumn>Created at</TableColumn>
<TableColumn>Tables</TableColumn>
@@ -63,7 +66,7 @@ const OpenDatabaseModal: React.FC<ModalProps> = ({ isOpen, onOpenChange }) => {
width={24}
/>
</TableCell>
<TableCell>{database.name}</TableCell>
<TableCell className="font-semibold">{database.name}</TableCell>
<TableCell>{
new Date(database.createdAt as string).toLocaleString("en-US")
}</TableCell>
@@ -15,8 +15,10 @@ import { createContext } from "react";
interface DatabaseDataContextType {
database: DatabaseType,
databases : DatabaseType[] ,
currentDatabaseId: string | undefined,
databases: DatabaseType[],
isLoading: boolean,
isSwitchingDatabase: boolean,
getField: (tableId: string, id: string) => FieldType | undefined,
}
@@ -27,14 +29,14 @@ interface DatabaseOperationsContextType {
// database operations
createDatabase: (database: DatabaseInsertType) => Promise<QueryResult>,
editDatabase: (database: DatabaseInsertType) => Promise<QueryResult>,
deleteDatabase : ( id : string) => Promise<void> ,
setCurrentDatabaseId : ( id : string) => void ,
deleteDatabase: (id: string) => Promise<void>,
switchDatabase: (databaseId: string) => void,
createTable: (table: TableInsertType) => Promise<void>,
editTable: (table: TableInsertType) => Promise<QueryResult>,
deleteTable: (id: string) => Promise<void>,
updateTablePositions: (tables: TableInsertType[]) => Promise<void>,
deleteMultiTables: (ids: string[]) => Promise<QueryResult>
deleteMultiTables: (ids: string[]) => Promise<void>
// field operations
createField: (field: FieldInsertType) => Promise<QueryResult>,
editField: (field: FieldInsertType) => Promise<QueryResult>,
@@ -4,8 +4,8 @@ import { db } from "../sync-provider/sync-provider";
import { TableInsertType, tables, TableType } from "@/lib/schemas/table-schema";
import { useQuery } from "@powersync/react";
import { toCompilableQuery } from "@powersync/drizzle-driver";
import { asc, desc, eq, inArray, or } from "drizzle-orm";
import { QueryResult } from "@powersync/web";
import { asc, count, desc, eq, inArray, or } from "drizzle-orm";
import { QueryResult, Transaction } from "@powersync/web";
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";
@@ -15,6 +15,7 @@ import { getTimestamp } from "@/utils/utils";
import { IndexInsertType, indices } from "@/lib/schemas/index-schema";
import { field_indices } from "@/lib/schemas/field_index-schema";
import { v4 } from "uuid";
import { SQLiteTransaction } from "drizzle-orm/sqlite-core";
interface Props { children: React.ReactNode }
@@ -23,17 +24,17 @@ const DatabaseProvider: React.FC<Props> = ({ children }) => {
const [currentDatabaseId, setCurrentDatabaseId] = useState<string | undefined>(undefined);
// Fetch all databases
const { data: databases, isLoading: loadingDatabases , isFetching : fetchingDatabases} = useQuery(toCompilableQuery(
const { data: databases, isLoading: loadingDatabases } = useQuery(toCompilableQuery(
db.query.databases.findMany()
));
// Fetch all data types
const { data: data_types, isLoading: loadingDataTypes , isFetching : fetchingDataTypes} = useQuery(toCompilableQuery(
const { data: data_types, isLoading: loadingDataTypes } = useQuery(toCompilableQuery(
db.query.data_types.findMany()
));
// Fetch the current database with nested tables, fields, and relationships
let { data: database, isLoading: loadingCurrentDatabase , isFetching : fetchingCurrentDatabase} = useQuery(
let { data: database, isLoading: loadingCurrentDatabase, isFetching } = useQuery(
toCompilableQuery(
db.query.databases.findMany({
where: (databases, { eq }) => eq(databases.id, currentDatabaseId as string),
@@ -68,9 +69,13 @@ const DatabaseProvider: React.FC<Props> = ({ children }) => {
})
)
);
const switchDatabase = useCallback((databaseId: string) => {
setCurrentDatabaseId(databaseId);
localStorage.setItem("database_id", databaseId);
}, [])
// Normalize result to single object
if (database.length == 1)
database = database[0] as any;
@@ -78,11 +83,16 @@ const DatabaseProvider: React.FC<Props> = ({ children }) => {
// Auto-select first database if none is selected
useEffect(() => {
if (databases.length > 0 && !currentDatabaseId) {
setCurrentDatabaseId(databases[0].id);
switchDatabase(databases[0].id);
}
}, [currentDatabaseId, databases])
}, [currentDatabaseId, databases]);
const isLoading: boolean = loadingDataTypes || loadingDatabases || loadingCurrentDatabase;
const isSwitchingDatabase: boolean = useMemo(() => {
return isFetching && currentDatabaseId != (database as any)?.id
}, [currentDatabaseId, database, isFetching]);
const isLoading: boolean = loadingDataTypes || loadingDatabases || loadingCurrentDatabase ;
// CRUD for database
const createDatabase = useCallback(async (database: DatabaseInsertType): Promise<QueryResult> => {
return await db.insert(databaseModel).values({
@@ -99,6 +109,20 @@ const DatabaseProvider: React.FC<Props> = ({ children }) => {
await db.delete(databaseModel).where(eq(databaseModel.id, id));
}, [db]);
const updateDbNumTables = useCallback(async (databaseId: string, tx: any) => {
const [{ count : numOfTables }] = await tx
.select({ count: count() })
.from(tables)
.where(eq(tables.databaseId, databaseId))
console.log (numOfTables)
await tx.update(databaseModel).set({
numOfTables
}).where(eq(databaseModel.id, databaseId))
}, []);
// CRUD operations for Tables
const createTable = useCallback(async (table: TableInsertType): Promise<void> => {
if (currentDatabaseId) {
@@ -108,6 +132,10 @@ const DatabaseProvider: React.FC<Props> = ({ children }) => {
databaseId: currentDatabaseId,
createdAt: table.createdAt || getTimestamp()
});
await updateDbNumTables(currentDatabaseId, tx);
if (table.fields) {
await tx.insert(fields).values(
table.fields.map((field: FieldInsertType) => ({ ...field, tableId: table.id }))
@@ -124,12 +152,28 @@ const DatabaseProvider: React.FC<Props> = ({ children }) => {
}, [db]);
const deleteTable = useCallback(async (id: string): Promise<void> => {
await db.delete(tables).where(eq(tables.id, id));
}, [db]);
if (currentDatabaseId) {
await db.transaction(async (tx) => {
await tx.delete(tables).where(eq(tables.id, id));
await updateDbNumTables(currentDatabaseId, tx)
})
} else {
throw Error("No Database selected");
}
const deleteMultiTables = useCallback(async (ids: string[]): Promise<QueryResult> => {
return await db.delete(tables).where(inArray(tables.id, ids));
}, [db]);
}, [db, currentDatabaseId]);
const deleteMultiTables = useCallback(async (ids: string[]): Promise<void> => {
if (currentDatabaseId) {
await db.transaction(async (tx) => {
await tx.delete(tables).where(inArray(tables.id, ids));;
await updateDbNumTables(currentDatabaseId, tx)
})
} else {
throw Error("No Database selected");
}
}, [db, currentDatabaseId]);
// CRUD operations for Fields
const createField = useCallback(async (field: FieldInsertType): Promise<QueryResult> => {
@@ -191,7 +235,6 @@ const DatabaseProvider: React.FC<Props> = ({ children }) => {
id: v4(),
fieldId,
indexId,
})))
})
}, [db])
@@ -234,11 +277,11 @@ const DatabaseProvider: React.FC<Props> = ({ 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") {
await tx.insert(tables).values(operation.table);
currentDatabaseId && await updateDbNumTables(currentDatabaseId, tx);
if (operation.table.fields && Object.values(operation.table.fields).length > 0)
await tx.insert(fields).values(Object.values(operation.table.fields));
@@ -246,7 +289,9 @@ const DatabaseProvider: React.FC<Props> = ({ children }) => {
await tx.update(tables).set(operation.changes).where(eq(tables.id, operation.tableId));
} else if (operation.type === "DELETE_TABLE") {
await tx.delete(tables).where(eq(tables.id, operation.tableId));
currentDatabaseId && await updateDbNumTables(currentDatabaseId, tx);
} else if (operation.type === "CREATE_FIELD") {
await tx.insert(fields).values(operation.field);
@@ -292,14 +337,14 @@ const DatabaseProvider: React.FC<Props> = ({ children }) => {
} catch (error) {
throw error
}
}, [db]);
}, [db, currentDatabaseId]);
const databaseOpsValue = useMemo(() => ({
createDatabase,
editDatabase,
deleteDatabase,
setCurrentDatabaseId,
switchDatabase,
createTable,
editTable,
deleteTable,
@@ -348,13 +393,17 @@ const DatabaseProvider: React.FC<Props> = ({ children }) => {
<DatabaseDataContext.Provider value={{
database: database as unknown as DatabaseType,
currentDatabaseId ,
databases: databases as DatabaseType[],
isLoading,
isSwitchingDatabase,
getField,
}}>
<DatabaseOperationsContext.Provider value={databaseOpsValue}>
{
!isLoading && database &&
database && !isLoading && !isSwitchingDatabase &&
<DatabaseHistoryProvider>
{children}
</DatabaseHistoryProvider>
+47 -8
View File
@@ -1,5 +1,3 @@
.react-flow__attribution {
display: none;
}
@@ -121,17 +119,58 @@ div[data-slot="content"] hr[role="separator"] {
}
svg.text-icon:hover {
color : #333639
color: #333639
}
.dark .bg-background-50 {
background-color: red !important;
.dark .bg-background-50 {
background-color: red !important;
}
.database-checkbox[data-selected="true"]{
border : 1.5px solid hsl(var(--heroui-primary-300));
.database-checkbox[data-selected="true"] {
border: 1.5px solid hsl(var(--heroui-primary-300));
}
thead[role="rowgroup"] th {
border-radius: 0px !important;
}
thead[role="rowgroup"] th:last-child {
border-radius: 0px 4px 4px 0px !important;
}
thead[role="rowgroup"] th:first-child {
border-radius: 4px 0px 0px 4px !important;
}
tbody[role="rowgroup"] tr td::before {
display: none;
}
tbody[role="rowgroup"] tr td {
border-radius: 0px;
}
tbody[role="rowgroup"] tr td:last-child {
border-radius: 0px 4px 4px 0px !important;
}
tbody[role="rowgroup"] tr td:first-child {
border-radius: 4px 0px 0px 4px !important;
}