mirror of
https://github.com/stackrender/stackrender.git
synced 2026-09-10 11:15:42 +00:00
Create new Database
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 38 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 21 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
+12
-11
@@ -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 (
|
||||
<>
|
||||
<ToastProvider placement="bottom-right"/>
|
||||
|
||||
<SyncProvider>
|
||||
<ReactFlowProvider>
|
||||
<DatabaseProvider>
|
||||
<DiagramProvider>
|
||||
<ToastProvider placement="bottom-right" />
|
||||
|
||||
<SyncProvider>
|
||||
<ReactFlowProvider>
|
||||
<DatabaseProvider>
|
||||
<DiagramProvider>
|
||||
<ModalProvider>
|
||||
<TooltipProvider delayDuration={0}>
|
||||
{appRoutes}
|
||||
</TooltipProvider>
|
||||
</ModalProvider>
|
||||
</DiagramProvider>
|
||||
</DatabaseProvider>
|
||||
</ReactFlowProvider>
|
||||
</SyncProvider>
|
||||
|
||||
</DiagramProvider>
|
||||
</DatabaseProvider>
|
||||
</ReactFlowProvider>
|
||||
</SyncProvider>
|
||||
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import { DatabaseType } from "@/lib/database"
|
||||
import { Checkbox, Image } from "@heroui/react";
|
||||
|
||||
|
||||
|
||||
|
||||
interface DatabaseCheckboxProps {
|
||||
database: DatabaseType,
|
||||
}
|
||||
|
||||
const DatabaseCheckbox: React.FC<DatabaseCheckboxProps> = ({ database }) => {
|
||||
|
||||
return (
|
||||
<Checkbox
|
||||
aria-label={database.name}
|
||||
value={database.dialect}
|
||||
className="database-checkbox"
|
||||
classNames={{
|
||||
base: "flex min-w-[128px] min-h-[128px] max-w-[128px] max-h-[128px] hover:bg-default rounded-md relative",
|
||||
|
||||
wrapper: "absolute top-2 left-2 " ,
|
||||
label : "flex items-center justify-center w-full h-full p-2 "
|
||||
}}
|
||||
>
|
||||
<div className="min-w-full h-full flex items-center justify-center ">
|
||||
<Image
|
||||
src={database.logo}
|
||||
|
||||
className="w-full"
|
||||
/>
|
||||
</div>
|
||||
</Checkbox>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
export default DatabaseCheckbox;
|
||||
@@ -60,7 +60,7 @@ const DropdownMenu: React.FC<MenuDropdownProps> = ({ title, children, clickHand
|
||||
{child.title}
|
||||
{
|
||||
child.divide &&
|
||||
<div className="w-full h-[0.5px] absolute bottom-[-0.5px] left-0 bg-divider dark:bg-font/10"></div>
|
||||
<div className="w-full h-[0.5px] absolute bottom-[-0.5px] left-0 bg-divider "></div>
|
||||
}
|
||||
</div>
|
||||
:
|
||||
@@ -68,7 +68,7 @@ const DropdownMenu: React.FC<MenuDropdownProps> = ({ title, children, clickHand
|
||||
<SubmenuDropdown {...child} />
|
||||
{
|
||||
child.divide &&
|
||||
<div className="w-full h-[0.5px] absolute bottom-[-0.5px] left-0 bg-divider dark:bg-font/10"></div>
|
||||
<div className="w-full h-[0.5px] absolute bottom-[-0.5px] left-0 bg-divider "></div>
|
||||
}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -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<MenuProps> = ({ }) => {
|
||||
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"),
|
||||
|
||||
@@ -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<ModalProps> = ({ isOpen, onOpenChange, className, backdrop = "opaque", title, children, actionName = "Action" , actionHandler , isDisabled}) => {
|
||||
const Modal: React.FC<ModalProps> = ({ 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<boolean>( 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<ModalProps> = ({ isOpen, onOpenChange, className, backdrop
|
||||
className={className}
|
||||
backdrop={backdrop}
|
||||
radius="sm"
|
||||
|
||||
|
||||
>
|
||||
<ModalContent>
|
||||
{(onClose) => (
|
||||
<>
|
||||
<ModalHeader {...moveProps} className="flex flex-row gap-1" >
|
||||
<ModalHeader {...moveProps} className=" flex flex-col gap-1" >
|
||||
|
||||
{title}
|
||||
{
|
||||
header &&
|
||||
<p className="text-sm text-font/70 block">
|
||||
{header}
|
||||
</p>
|
||||
}
|
||||
</ModalHeader>
|
||||
<ModalBody>
|
||||
{children}
|
||||
@@ -58,9 +69,9 @@ const Modal: React.FC<ModalProps> = ({ isOpen, onOpenChange, className, backdrop
|
||||
<ModalFooter>
|
||||
<div className="flex w-full justify-between">
|
||||
<Button color="danger" variant="light" onPress={onClose} size="sm">
|
||||
{t("modal.close")}
|
||||
{t("modals.close")}
|
||||
</Button>
|
||||
<Button color="primary" onPress={() => handleAction(onClose)} size="sm" isDisabled={isDisabled}>
|
||||
<Button color="primary" onPress={() => handleAction(onClose)} size="sm" isDisabled={isDisabled || isLoading} isLoading={isLoading}>
|
||||
{actionName}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
+1
-1
@@ -28,7 +28,7 @@ i18n.use(LanguageDetector)
|
||||
.use(initReactI18next)
|
||||
.init({
|
||||
resources ,
|
||||
lng: 'en',
|
||||
lng: 'fr',
|
||||
|
||||
interpolation: {
|
||||
escapeValue: false,
|
||||
|
||||
+17
-5
@@ -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."
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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] ;
|
||||
}
|
||||
@@ -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<typeof databases> {
|
||||
tables : TableType[] ,
|
||||
relationships : RelationshipType[]
|
||||
};
|
||||
tables: TableType[],
|
||||
relationships: RelationshipType[]
|
||||
};
|
||||
export interface DatabaseInsertType extends InferInsertModel<typeof databases> { };
|
||||
@@ -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 (
|
||||
|
||||
<div className="w-full h-screen flex relative overflow-hidden">
|
||||
<div className="flex max-w-full">
|
||||
<DBController />
|
||||
</div>
|
||||
<div className="relative w-full h-full ">
|
||||
<ReactFlow
|
||||
colorMode={resolvedTheme as ColorMode}
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
fitView
|
||||
className="w-full h-full cursor-default "
|
||||
onNodesChange={handleNodesChanges}
|
||||
onEdgesChange={handleEdgeChanges}
|
||||
onConnect={onConnect}
|
||||
defaultEdgeOptions={{
|
||||
type: 'relationship-edge',
|
||||
}}
|
||||
// onlyRenderVisibleElements
|
||||
panOnDrag={true}
|
||||
zoomOnScroll={true}
|
||||
nodeTypes={nodeTypes}
|
||||
edgeTypes={edgeTypes}
|
||||
snapGrid={[20, 20]}
|
||||
onConnectStart={onConnectStart}
|
||||
onConnectEnd={onConnectEnd}
|
||||
>
|
||||
{
|
||||
|
||||
<Controls
|
||||
position="bottom-center"
|
||||
showFitView={false}
|
||||
showZoom={false}
|
||||
showInteractive={false}
|
||||
className="shadow-none "
|
||||
<div className="relative w-full h-full ">
|
||||
<ReactFlow
|
||||
colorMode={resolvedTheme as ColorMode}
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
fitView
|
||||
className="w-full h-full cursor-default "
|
||||
onNodesChange={handleNodesChanges}
|
||||
onEdgesChange={handleEdgeChanges}
|
||||
onConnect={onConnect}
|
||||
defaultEdgeOptions={{
|
||||
type: 'relationship-edge',
|
||||
}}
|
||||
// onlyRenderVisibleElements
|
||||
panOnDrag={true}
|
||||
zoomOnScroll={true}
|
||||
nodeTypes={nodeTypes}
|
||||
edgeTypes={edgeTypes}
|
||||
snapGrid={[20, 20]}
|
||||
onConnectStart={onConnectStart}
|
||||
onConnectEnd={onConnectEnd}
|
||||
>
|
||||
|
||||
<DatabaseControlButtons
|
||||
adjustPositions={adjustPositions}
|
||||
/>
|
||||
</Controls >
|
||||
<Controls
|
||||
position="bottom-center"
|
||||
showFitView={false}
|
||||
showZoom={false}
|
||||
showInteractive={false}
|
||||
className="shadow-none "
|
||||
>
|
||||
|
||||
<Background className=" dark:bg-background-100" />
|
||||
</ReactFlow>
|
||||
<div
|
||||
className="absolute left-[24px] bottom-[24px] "
|
||||
>
|
||||
{
|
||||
isOverlapping &&
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span>
|
||||
<Button
|
||||
variant="shadow"
|
||||
size="sm"
|
||||
isIconOnly
|
||||
color="danger"
|
||||
className="size-8 p-1 "
|
||||
onPressEnd={puls}
|
||||
>
|
||||
<AlertTriangle className="size-4 text-white" />
|
||||
</Button>
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
{t("table.overlapping_tables")}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
}
|
||||
<DatabaseControlButtons
|
||||
adjustPositions={adjustPositions}
|
||||
/>
|
||||
</Controls >
|
||||
|
||||
<Background className=" dark:bg-background-100" />
|
||||
</ReactFlow>
|
||||
<div
|
||||
className="absolute left-[24px] bottom-[24px] "
|
||||
>
|
||||
{
|
||||
isOverlapping &&
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span>
|
||||
<Button
|
||||
variant="shadow"
|
||||
size="sm"
|
||||
isIconOnly
|
||||
color="danger"
|
||||
className="size-8 p-1 "
|
||||
onPressEnd={puls}
|
||||
>
|
||||
<AlertTriangle className="size-4 text-white" />
|
||||
</Button>
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
{t("table.overlapping_tables")}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
}
|
||||
<svg style={{ position: 'absolute', width: 0, height: 0 }}>
|
||||
<defs>
|
||||
<CardinalityMarker type="one" direction="start" />
|
||||
|
||||
+15
-37
@@ -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<Props> = ({ }) => {
|
||||
const [relationship, setRelationship] = useState<RelationshipInsertType | undefined>(undefined);
|
||||
const [isValid, setIsValid] = useState<boolean>(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<RelationshipType[]>(allRelationships);
|
||||
|
||||
@@ -37,18 +36,6 @@ const RelationshipController: React.FC<Props> = ({ }) => {
|
||||
|
||||
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<Props> = ({ }) => {
|
||||
|
||||
}, [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<Props> = ({ }) => {
|
||||
))}
|
||||
</Accordion>
|
||||
</div>
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
onOpenChange={onOpenChange}
|
||||
title={t("db_controller.create_relationship")}
|
||||
actionName={t("modal.create")}
|
||||
className="min-w-[520px]"
|
||||
isDisabled={!isValid}
|
||||
actionHandler={addRelationship}
|
||||
|
||||
>
|
||||
<CreateRelationshipForm
|
||||
onRelationshipChanges={setRelationship}
|
||||
onValidationChanges={setIsValid}
|
||||
/>
|
||||
</Modal>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
+4
-4
@@ -147,12 +147,12 @@ const FieldItem: React.FC<Props> = ({ field }) => {
|
||||
<EllipsisVertical className="size-4 " />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-[210px] bg-background" >
|
||||
<PopoverContent className="w-[210px] " >
|
||||
<div className="w-full flex flex-col gap-2 p-2 ">
|
||||
<h3 className="font-semibold text-sm text-font/90">
|
||||
{t("db_controller.field_setting")}
|
||||
</h3>
|
||||
<hr className="border-divider dark:border-font/10" />
|
||||
<hr className="border-divider" />
|
||||
<div className="flex w-full justify-between">
|
||||
<span className="text-sm text-icon font-medium dark:text-font/90">
|
||||
{t("db_controller.unique")}
|
||||
@@ -174,11 +174,11 @@ const FieldItem: React.FC<Props> = ({ 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]",
|
||||
}} />
|
||||
<hr className="border-divider dark:border-font/10" />
|
||||
<hr className="border-divider" />
|
||||
|
||||
<Button
|
||||
className="bg-default dark:bg-danger dark:border-none dark:text-white"
|
||||
|
||||
+3
-3
@@ -111,7 +111,7 @@ const IndexItem: React.FC<Props> = ({ index, fields }) => {
|
||||
<h3 className="font-semibold text-sm text-font/90">
|
||||
{t("db_controller.index_setting")}
|
||||
</h3>
|
||||
<hr className="border-divider dark:border-font/10" />
|
||||
<hr className="border-divider " />
|
||||
|
||||
<label className="text-sm font-medium text-icon dark:text-font/90">
|
||||
{t("db_controller.name")}
|
||||
@@ -123,13 +123,13 @@ const IndexItem: React.FC<Props> = ({ index, fields }) => {
|
||||
onBlur={editIndexName}
|
||||
size="sm"
|
||||
classNames={{
|
||||
inputWrapper: "border-divider group-hover:border-primary dark:border-font/10",
|
||||
inputWrapper: "border-divider group-hover:border-primary ",
|
||||
}}
|
||||
defaultValue={index.name}
|
||||
/>
|
||||
|
||||
|
||||
<hr className="border-divider dark:border-font/10" />
|
||||
<hr className="border-divider" />
|
||||
|
||||
<Button
|
||||
className="bg-default dark:bg-danger dark:border-none dark:text-white"
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
import DatabaseCheckbox from "@/components/checkbox/database-checkbox";
|
||||
import Modal, { ModalProps } from "@/components/modal/modal"
|
||||
import { DatabaseType, DBTypes } from "@/lib/database";
|
||||
import { useDatabaseOperations } from "@/providers/database-provider/database-provider";
|
||||
import { Button, CheckboxGroup, Input } from "@heroui/react";
|
||||
import { Database, SquareMenu } from "lucide-react";
|
||||
import { useCallback, useEffect, useState } from "react"
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { v4 } from "uuid";
|
||||
|
||||
|
||||
export const CreateDatabaseModal: React.FC<ModalProps> = ({ isOpen, onOpenChange }) => {
|
||||
const [isValid, setIsValid] = useState<boolean>(false);
|
||||
const { t } = useTranslation();
|
||||
const [selectedDbType, setSelectedDbType] = useState<string[]>([DBTypes[0].dialect]);
|
||||
const [dbName, setDbName] = useState<string>("db_example");
|
||||
const { createDatabase, setCurrentDatabaseId } = useDatabaseOperations();
|
||||
|
||||
const onDatabaseTypeChange = (types: string[]) => {
|
||||
const selectedType: string | undefined = types.pop();
|
||||
if (selectedType && selectedType != selectedDbType?.[0])
|
||||
setSelectedDbType([selectedType]);
|
||||
}
|
||||
|
||||
const createNewDatabase = useCallback(async () => {
|
||||
const databaseId: string = v4();
|
||||
|
||||
return new Promise(async (res, rej) => {
|
||||
await createDatabase({
|
||||
id: databaseId,
|
||||
name: dbName,
|
||||
dialect: selectedDbType[0] as any
|
||||
});
|
||||
setCurrentDatabaseId(databaseId);
|
||||
res(databaseId)
|
||||
})
|
||||
|
||||
}, [selectedDbType, dbName])
|
||||
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
setIsValid((selectedDbType.length > 0 && dbName.trim().length > 0) as boolean)
|
||||
}, [selectedDbType, dbName])
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
onOpenChange={onOpenChange}
|
||||
title={t("modals.pick_database")}
|
||||
actionName={t("modals.continue")}
|
||||
className="min-w-[720px]"
|
||||
isDisabled={!isValid}
|
||||
actionHandler={createNewDatabase}
|
||||
header={t("modals.create_database_header")}
|
||||
>
|
||||
<div className="w-full justify-center flex ">
|
||||
<div className="flex flex-col gap-1 w-[70%]">
|
||||
<div className="p-8 py-2 pb-4 space-y-2">
|
||||
<label className="text-sm text-font/90 font-semibold">
|
||||
{t("modals.db_name")}
|
||||
</label>
|
||||
<Input
|
||||
errorMessage={t("modals.db_name_error")}
|
||||
isInvalid={dbName.trim().length == 0}
|
||||
type="text"
|
||||
value={dbName}
|
||||
onValueChange={setDbName}
|
||||
size="sm"
|
||||
radius="sm"
|
||||
variant="faded"
|
||||
placeholder={t("modals.db_name")}
|
||||
className="h-8 w-full focus-visible:ring-0 shadow-none "
|
||||
startContent={
|
||||
<Database className="text-icon size-4 " />
|
||||
}
|
||||
classNames={{
|
||||
inputWrapper: "dark:bg-default border-divider group-hover:border-primary ",
|
||||
}}
|
||||
|
||||
/>
|
||||
</div>
|
||||
<CheckboxGroup
|
||||
classNames={{
|
||||
base: "w-full p-0 ",
|
||||
wrapper: "flex-row p-4 gap-8 px-0 items-center justify-center"
|
||||
}}
|
||||
aria-label="Select Database"
|
||||
value={selectedDbType}
|
||||
onChange={onDatabaseTypeChange}
|
||||
>
|
||||
{
|
||||
DBTypes.map((db: DatabaseType) => (
|
||||
<DatabaseCheckbox
|
||||
database={db}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</CheckboxGroup>
|
||||
<div className="p-8 py-4 space-y-2">
|
||||
<Button
|
||||
variant="bordered"
|
||||
size="sm"
|
||||
className="w-full text-font border-divider"
|
||||
>
|
||||
<SquareMenu className="size-4" /> Check examples
|
||||
|
||||
</Button>
|
||||
<Button
|
||||
variant="bordered"
|
||||
size="sm"
|
||||
className="w-full text-font border-divider"
|
||||
|
||||
>
|
||||
<span className="underline">
|
||||
Empty Diagram
|
||||
</span>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
+49
-20
@@ -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<CreateRelationshipFormProps> = ({ onRelationshipChanges, onValidationChanges }) => {
|
||||
const CreateRelationshipModal: React.FC<CreateRelationshipModalProps> = ({ onRelationshipChanges, isOpen, onOpenChange, onRlationshipCreated }) => {
|
||||
const [isValid, setIsValid] = useState<boolean>(false);
|
||||
const { createRelationship } = useDatabaseOperations();
|
||||
|
||||
const [relationship, setRelationship] = useState<RelationshipInsertType>({
|
||||
sourceTableId: "",
|
||||
@@ -62,15 +66,39 @@ const CreateRelationshipForm: React.FC<CreateRelationshipFormProps> = ({ 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 (
|
||||
<>
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
onOpenChange={onOpenChange}
|
||||
title={t("db_controller.create_relationship")}
|
||||
actionName={t("modals.create")}
|
||||
className="min-w-[520px]"
|
||||
isDisabled={!isValid}
|
||||
actionHandler={addRelationship}
|
||||
|
||||
>
|
||||
|
||||
<div className="grid grid-cols-2 grid-rows-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<label className="font-medium flex text-slate-700 flex items-center gap-1 text-sm">
|
||||
<FileOutput className="size-4 text-icon" />
|
||||
<label className="font-medium flex text-font/90 flex items-center gap-1 text-sm">
|
||||
<FileOutput className="size-4" />
|
||||
{t("db_controller.source_table")}
|
||||
</label>
|
||||
<Autocomplete
|
||||
@@ -80,8 +108,8 @@ const CreateRelationshipForm: React.FC<CreateRelationshipFormProps> = ({ onRelat
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label className="font-medium flex text-slate-700 flex items-center gap-1 text-sm">
|
||||
<FileMinus2 className="size-4 text-icon" />
|
||||
<label className="font-medium flex text-font/90 flex items-center gap-1 text-sm">
|
||||
<FileMinus2 className="size-4" />
|
||||
{t("db_controller.target_table")}
|
||||
</label>
|
||||
|
||||
@@ -92,8 +120,8 @@ const CreateRelationshipForm: React.FC<CreateRelationshipFormProps> = ({ onRelat
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label className="font-medium flex text-slate-700 flex items-center gap-1 text-sm">
|
||||
<KeyRound className="size-4 text-icon" />
|
||||
<label className="font-medium flex text-font/90 flex items-center gap-1 text-sm">
|
||||
<KeyRound className="size-4 " />
|
||||
{t("db_controller.primary_key")}
|
||||
|
||||
</label>
|
||||
@@ -106,8 +134,8 @@ const CreateRelationshipForm: React.FC<CreateRelationshipFormProps> = ({ onRelat
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label className="font-medium flex text-slate-700 flex items-center gap-1 text-sm">
|
||||
<FileKey className="size-4 text-icon" />
|
||||
<label className="font-medium flex text-font/90 flex items-center gap-1 text-sm">
|
||||
<FileKey className="size-4 " />
|
||||
{t("db_controller.foreign_key")}
|
||||
</label>
|
||||
<Autocomplete
|
||||
@@ -127,9 +155,10 @@ const CreateRelationshipForm: React.FC<CreateRelationshipFormProps> = ({ onRelat
|
||||
|
||||
</div>
|
||||
}
|
||||
</>
|
||||
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
export default CreateRelationshipForm;
|
||||
export default CreateRelationshipModal;
|
||||
@@ -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<ModalProps> = ({ isOpen, onOpenChange }) => {
|
||||
|
||||
const { t } = useTranslation();
|
||||
const { databases } = useDatabase();
|
||||
const { setCurrentDatabaseId } = useDatabaseOperations();
|
||||
const [selectedDatabase, setSelectedDatabase] = useState<any | undefined>(undefined);
|
||||
const openDatabase = () => {
|
||||
setCurrentDatabaseId( selectedDatabase.currentKey)
|
||||
}
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
onOpenChange={onOpenChange}
|
||||
title={t("modals.open_database")}
|
||||
actionName={t("modals.open")}
|
||||
className="min-w-[860px]"
|
||||
actionHandler={openDatabase}
|
||||
header={t("modals.open_database_header")}
|
||||
isDisabled={!selectedDatabase?.size}
|
||||
>
|
||||
<Table
|
||||
aria-label="Example static collection table"
|
||||
color={"primary"}
|
||||
|
||||
|
||||
selectionMode="single"
|
||||
selectedKeys={selectedDatabase}
|
||||
onSelectionChange={setSelectedDatabase}
|
||||
classNames={{
|
||||
wrapper: "min-h-[360px] shadow-none border-1 border-divider border-sm"
|
||||
}}
|
||||
>
|
||||
<TableHeader>
|
||||
<TableColumn>Dialect</TableColumn>
|
||||
<TableColumn>Name</TableColumn>
|
||||
<TableColumn>Created at</TableColumn>
|
||||
<TableColumn>Tables</TableColumn>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{
|
||||
databases.map((database: DatabaseType) => (
|
||||
<TableRow key={database.id}>
|
||||
<TableCell>
|
||||
<Image
|
||||
src={getDatabaseByDialect(database.dialect).logo}
|
||||
width={24}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>{database.name}</TableCell>
|
||||
<TableCell>{
|
||||
new Date(database.createdAt as string).toLocaleString("en-US")
|
||||
}</TableCell>
|
||||
<TableCell>{database.numOfTables}</TableCell>
|
||||
</TableRow>
|
||||
))
|
||||
}
|
||||
|
||||
|
||||
</TableBody>
|
||||
</Table>
|
||||
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
export default OpenDatabaseModal;
|
||||
@@ -42,10 +42,13 @@ const DatabaseHistoryProvider: React.FC<Props> = ({ children }) => {
|
||||
}, [redoChanges, udpateDbFlag, isProcessing]);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
if (!udpateDbFlag.current) {
|
||||
udpateDbFlag.current = true;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
const normalizedDatabase = normalizeDatabase(database);
|
||||
|
||||
const normalizedPresent = normalizeDatabase(datatbaseState.present);
|
||||
|
||||
@@ -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<QueryResult>,
|
||||
editDatabase: (database: DatabaseInsertType) => Promise<QueryResult>,
|
||||
deleteDatabase : ( id : string) => Promise<void> ,
|
||||
setCurrentDatabaseId : ( id : string) => void ,
|
||||
|
||||
createTable: (table: TableInsertType) => Promise<void>,
|
||||
editTable: (table: TableInsertType) => Promise<QueryResult>,
|
||||
@@ -36,10 +42,10 @@ interface DatabaseOperationsContextType {
|
||||
orderTableFields: (fields: FieldType[]) => Promise<void>,
|
||||
|
||||
// index operations
|
||||
createIndex : ( index : IndexInsertType) => Promise<QueryResult> ,
|
||||
editIndex : ( index : IndexInsertType) => Promise<QueryResult> ,
|
||||
deleteIndex : ( id : string) => Promise<QueryResult> ,
|
||||
editFieldIndices:( indexId : string , fieldIds : string[])=> Promise<void> ,
|
||||
createIndex: (index: IndexInsertType) => Promise<QueryResult>,
|
||||
editIndex: (index: IndexInsertType) => Promise<QueryResult>,
|
||||
deleteIndex: (id: string) => Promise<QueryResult>,
|
||||
editFieldIndices: (indexId: string, fieldIds: string[]) => Promise<void>,
|
||||
// relationship operations
|
||||
createRelationship: (relationship: RelationshipInsertType) => Promise<QueryResult>,
|
||||
editRelationship: (relationship: RelationshipInsertType) => Promise<QueryResult>,
|
||||
|
||||
@@ -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<Props> = ({ children }) => {
|
||||
const [currentDatabaseId, setCurrentDatabaseId] = useState<string | undefined>(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<Props> = ({ children }) => {
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// Normalize result to single object
|
||||
if (database.length == 1)
|
||||
database = database[0] as any;
|
||||
@@ -82,7 +82,22 @@ const DatabaseProvider: React.FC<Props> = ({ 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<QueryResult> => {
|
||||
return await db.insert(databaseModel).values({
|
||||
...database,
|
||||
createdAt: getTimestamp()
|
||||
});
|
||||
}, [db]);
|
||||
|
||||
const editDatabase = useCallback(async (database: DatabaseInsertType): Promise<QueryResult> => {
|
||||
return await db.update(databaseModel).set(database).where(eq(databaseModel.id, database.id))
|
||||
}, [db]);
|
||||
|
||||
const deleteDatabase = useCallback(async (id: string): Promise<void> => {
|
||||
await db.delete(databaseModel).where(eq(databaseModel.id, id));
|
||||
}, [db]);
|
||||
|
||||
// CRUD operations for Tables
|
||||
const createTable = useCallback(async (table: TableInsertType): Promise<void> => {
|
||||
@@ -219,6 +234,7 @@ 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") {
|
||||
@@ -253,9 +269,9 @@ const DatabaseProvider: React.FC<Props> = ({ 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<Props> = ({ children }) => {
|
||||
}, [db]);
|
||||
|
||||
const databaseOpsValue = useMemo(() => ({
|
||||
|
||||
createDatabase,
|
||||
editDatabase,
|
||||
deleteDatabase,
|
||||
setCurrentDatabaseId,
|
||||
createTable,
|
||||
editTable,
|
||||
deleteTable,
|
||||
@@ -299,6 +320,9 @@ const DatabaseProvider: React.FC<Props> = ({ children }) => {
|
||||
editFieldIndices,
|
||||
data_types
|
||||
}), [
|
||||
createDatabase,
|
||||
editDatabase,
|
||||
deleteDatabase,
|
||||
createTable,
|
||||
editTable,
|
||||
deleteTable,
|
||||
@@ -324,6 +348,7 @@ const DatabaseProvider: React.FC<Props> = ({ children }) => {
|
||||
<DatabaseDataContext.Provider value={{
|
||||
|
||||
database: database as unknown as DatabaseType,
|
||||
databases: databases as DatabaseType[],
|
||||
isLoading,
|
||||
getField,
|
||||
}}>
|
||||
|
||||
@@ -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<ModalContextType>({} as ModalContextType);
|
||||
|
||||
@@ -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<Props> = ({ children }) => {
|
||||
const { isOpen, onOpen } = useDisclosure();
|
||||
export const ModalProvider: React.FC<Props> = ({ children }) => {
|
||||
|
||||
const [currentModal, setCurrentModal] = useState<CurrentModalProps | undefined>(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 (
|
||||
<ModalContext.Provider>
|
||||
<ModalContext.Provider value={{
|
||||
open
|
||||
}}
|
||||
>
|
||||
{
|
||||
currentModal &&
|
||||
<Modal isOpen={isOpen} {...currentModal.modalProps} onOpenChange={onOpenChange} >
|
||||
|
||||
</Modal>
|
||||
currentModal ? (
|
||||
currentModal.modal == Modals.CREATE_RELATIONSHIP &&
|
||||
<CreateRelationshipModal {...currentModal.props} onOpenChange={onOpenChange} isOpen={isOpen} />
|
||||
||
|
||||
currentModal.modal == Modals.CREATE_DATABASE &&
|
||||
<CreateDatabaseModal {...currentModal.props} onOpenChange={onOpenChange} isOpen={isOpen} />
|
||||
||
|
||||
currentModal.modal == Modals.OPEN_DATABASE &&
|
||||
<OpenDatabaseModal {...currentModal.props} onOpenChange={onOpenChange} isOpen={isOpen} />
|
||||
) : undefined
|
||||
}
|
||||
{children}
|
||||
</ModalContext.Provider>
|
||||
@@ -37,6 +58,5 @@ const ModalProvider: React.FC<Props> = ({ children }) => {
|
||||
}
|
||||
|
||||
|
||||
export const useModal = () => useContext(ModalContext);
|
||||
export const useModal = () => useContext(ModalContext);
|
||||
|
||||
*/
|
||||
@@ -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));
|
||||
}
|
||||
+2
-1
@@ -58,5 +58,6 @@ function excludeFields(
|
||||
export {
|
||||
areArraysEqual,
|
||||
getTimestamp,
|
||||
excludeFields
|
||||
excludeFields ,
|
||||
|
||||
}
|
||||
+3
-2
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user