diff --git a/package-lock.json b/package-lock.json index 7fe6a99..fc27720 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { - "name": "vite-template", - "version": "0.0.0", + "name": "StackRender", + "version": "1.0.1", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "vite-template", - "version": "0.0.0", + "name": "StackRender", + "version": "1.0.1", "dependencies": { "@codemirror/lang-sql": "^6.9.0", "@dnd-kit/core": "^6.3.1", @@ -39,7 +39,6 @@ "@tabler/icons-react": "^3.34.1", "@tailwindcss/typography": "^0.5.16", "@tailwindcss/vite": "^4.1.12", - "@types/react-resizable": "^3.0.8", "@uiw/react-codemirror": "^4.23.12", "@xyflow/react": "^12.6.3", "class-variance-authority": "^0.7.1", @@ -62,7 +61,7 @@ "react-day-picker": "^9.10.0", "react-dom": "18.3.1", "react-i18next": "^15.5.1", - "react-resizable": "^3.0.5", + "react-resizable-panels": "^2.1.9", "react-router-dom": "6.23.0", "react-tag-input": "^6.10.6", "sonner": "^2.0.7", @@ -72,7 +71,8 @@ "tailwindcss": "^4.1.13", "tailwindcss-animate": "^1.0.7", "use-undo": "^1.1.1", - "uuid": "^11.1.0" + "uuid": "^11.1.0", + "vaul": "^1.1.2" }, "devDependencies": { "@types/better-sqlite3": "^7.6.13", @@ -81,7 +81,6 @@ "@types/pluralize": "^0.0.33", "@types/react": "18.3.3", "@types/react-dom": "18.3.0", - "@types/react-resizable": "^3.0.8", "@typescript-eslint/eslint-plugin": "8.11.0", "@typescript-eslint/parser": "8.11.0", "@vitejs/plugin-react": "^4.2.1", @@ -7610,16 +7609,6 @@ "@types/react": "*" } }, - "node_modules/@types/react-resizable": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@types/react-resizable/-/react-resizable-3.0.8.tgz", - "integrity": "sha512-Pcvt2eGA7KNXldt1hkhVhAgZ8hK41m0mp89mFgQi7LAAEZiaLgm4fHJ5zbJZ/4m2LVaAyYrrRRv1LHDcrGQanA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/react": "*" - } - }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.11.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.11.0.tgz", @@ -11814,6 +11803,7 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -12208,6 +12198,7 @@ "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dev": true, "license": "MIT", "dependencies": { "loose-envify": "^1.4.0", @@ -12441,31 +12432,14 @@ } } }, - "node_modules/react-resizable": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/react-resizable/-/react-resizable-3.0.5.tgz", - "integrity": "sha512-vKpeHhI5OZvYn82kXOs1bC8aOXktGU5AmKAgaZS4F5JPburCtbmDPqE7Pzp+1kN4+Wb81LlF33VpGwWwtXem+w==", + "node_modules/react-resizable-panels": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/react-resizable-panels/-/react-resizable-panels-2.1.9.tgz", + "integrity": "sha512-z77+X08YDIrgAes4jl8xhnUu1LNIRp4+E7cv4xHmLOxxUPO/ML7PSrE813b90vj7xvQ1lcf7g2uA9GeMZonjhQ==", "license": "MIT", - "dependencies": { - "prop-types": "15.x", - "react-draggable": "^4.0.3" - }, "peerDependencies": { - "react": ">= 16.3" - } - }, - "node_modules/react-resizable/node_modules/react-draggable": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/react-draggable/-/react-draggable-4.5.0.tgz", - "integrity": "sha512-VC+HBLEZ0XJxnOxVAZsdRi8rD04Iz3SiiKOoYzamjylUcju/hP9np/aZdLHf/7WOD268WMoNJMvYfB5yAK45cw==", - "license": "MIT", - "dependencies": { - "clsx": "^2.1.1", - "prop-types": "^15.8.1" - }, - "peerDependencies": { - "react": ">= 16.3.0", - "react-dom": ">= 16.3.0" + "react": "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", + "react-dom": "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "node_modules/react-router": { @@ -13736,6 +13710,19 @@ "uuid": "dist/esm/bin/uuid" } }, + "node_modules/vaul": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vaul/-/vaul-1.1.2.tgz", + "integrity": "sha512-ZFkClGpWyI2WUQjdLJ/BaGuV6AVQiJ3uELGk3OYtP+B6yCO7Cmn9vPFXVJkRaGkOJu3m8bQMgtyzNHixULceQA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-dialog": "^1.1.1" + }, + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc" + } + }, "node_modules/vite": { "version": "5.4.20", "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.20.tgz", diff --git a/package.json b/package.json index 5d8fae0..7e6c388 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,6 @@ "@tabler/icons-react": "^3.34.1", "@tailwindcss/typography": "^0.5.16", "@tailwindcss/vite": "^4.1.12", - "@types/react-resizable": "^3.0.8", "@uiw/react-codemirror": "^4.23.12", "@xyflow/react": "^12.6.3", "class-variance-authority": "^0.7.1", @@ -64,7 +63,7 @@ "react-day-picker": "^9.10.0", "react-dom": "18.3.1", "react-i18next": "^15.5.1", - "react-resizable": "^3.0.5", + "react-resizable-panels": "^2.1.9", "react-router-dom": "6.23.0", "react-tag-input": "^6.10.6", "sonner": "^2.0.7", @@ -74,7 +73,8 @@ "tailwindcss": "^4.1.13", "tailwindcss-animate": "^1.0.7", "use-undo": "^1.1.1", - "uuid": "^11.1.0" + "uuid": "^11.1.0", + "vaul": "^1.1.2" }, "devDependencies": { "@types/better-sqlite3": "^7.6.13", @@ -83,7 +83,6 @@ "@types/pluralize": "^0.0.33", "@types/react": "18.3.3", "@types/react-dom": "18.3.0", - "@types/react-resizable": "^3.0.8", "@typescript-eslint/eslint-plugin": "8.11.0", "@typescript-eslint/parser": "8.11.0", "@vitejs/plugin-react": "^4.2.1", diff --git a/src/components/combobox.tsx b/src/components/combobox.tsx index d694c8a..1e5ed05 100644 --- a/src/components/combobox.tsx +++ b/src/components/combobox.tsx @@ -64,9 +64,12 @@ export const Combobox = (props: ComboboxProps) => { aria-expanded={open} className={cn("justify-between", className)} > - {value - ? items.find((item) => item.id === value)?.[label] - : (placeholder ? placeholder : "Select items...")} + diff --git a/src/components/header.tsx b/src/components/header.tsx index b655ffa..145a617 100644 --- a/src/components/header.tsx +++ b/src/components/header.tsx @@ -14,6 +14,7 @@ import { useTranslation } from 'react-i18next' import { ThemeSwitch } from './theme-switch' import RenameDb from '@/features/database/components/rename-db' import LanguagesDropdown from './languages-dropdown' +import { useIsMobile } from '@/hooks/use-mobile' interface HeaderProps extends React.HTMLAttributes { fixed?: boolean @@ -38,19 +39,13 @@ export const Header = ({ return () => document.removeEventListener('scroll', onScroll) }, []); - - - - const { open } = useModal(); - - const { t } = useTranslation(); - + const isMobile = useIsMobile(); return (
10 && fixed ? 'shadow-sm' : 'shadow-none', className @@ -61,23 +56,38 @@ export const Header = ({
- + {!isMobile && }
- + + { + isMobile && +
+ +
+ }
- - + { + !isMobile && +
+ +
+ } +
+ {isMobile &&
+ +
+ }
) } diff --git a/src/components/layout/app-sidebar/app-sidebar.tsx b/src/components/layout/app-sidebar/app-sidebar.tsx index 8653402..dc4e0ae 100644 --- a/src/components/layout/app-sidebar/app-sidebar.tsx +++ b/src/components/layout/app-sidebar/app-sidebar.tsx @@ -10,9 +10,11 @@ import { import { NavGroup } from '../nav-group' import { useSidebarData } from './data/sidebar-data' import { Separator } from '@/components/ui/separator'; +import { useDiagramOps } from '@/providers/diagram-provider/diagram-provider'; export function AppSidebar({ ...props }: React.ComponentProps) { const sidebarData = useSidebarData(); + const { openController } = useDiagramOps(); return ( @@ -24,12 +26,12 @@ export function AppSidebar({ ...props }: React.ComponentProps) {

StackRender

- +
{sidebarData.navGroups.map((props: any, index: number) => (
- + openController(true)} /> { index != sidebarData.navGroups.length - 1 &&
@@ -50,7 +52,7 @@ export function AppSidebar({ ...props }: React.ComponentProps) {
}
- ))} + ))}
diff --git a/src/components/layout/nav-group.tsx b/src/components/layout/nav-group.tsx index 29fe56d..a5a0503 100644 --- a/src/components/layout/nav-group.tsx +++ b/src/components/layout/nav-group.tsx @@ -29,7 +29,7 @@ import { } from '../ui/dropdown-menu' import { NavCollapsible, NavItem, NavLink, NavGroup as NavGroupType } from './types.ts' -export function NavGroup({ title, items }: NavGroupType) { +export function NavGroup({ title, items , onClick }: NavGroupType) { const { state, isMobile } = useSidebar(); const location = useLocation(); const href = location.pathname @@ -41,7 +41,7 @@ export function NavGroup({ title, items }: NavGroupType) { const key = `${item.title}-${item.url}` if (!item.items) - return + return if (state === 'collapsed' && !isMobile) return ( @@ -59,7 +59,7 @@ const NavBadge = ({ children }: { children: ReactNode }) => ( {children} ) -const SidebarMenuLink = ({ item, href , target}: { item: NavLink; href: string; target?: string }) => { +const SidebarMenuLink = ({ item, href , target , onClick}: { item: NavLink; href: string; target?: string , onClick? : ()=> void }) => { const { setOpenMobile } = useSidebar() return ( @@ -67,6 +67,7 @@ const SidebarMenuLink = ({ item, href , target}: { item: NavLink; href: string; asChild isActive={checkIsActive(href, item)} tooltip={item.title} + onClick={onClick} > setOpenMobile(false)} target={target}> {item.icon && } diff --git a/src/components/layout/types.ts b/src/components/layout/types.ts index 8a2624e..8266997 100644 --- a/src/components/layout/types.ts +++ b/src/components/layout/types.ts @@ -1,5 +1,5 @@ - + interface BaseNavItem { @@ -10,8 +10,8 @@ interface BaseNavItem { type NavLink = BaseNavItem & { url: string - items?: never; - newTab? : boolean + items?: never; + newTab?: boolean } type NavCollapsible = BaseNavItem & { @@ -23,13 +23,14 @@ type NavItem = NavCollapsible | NavLink interface NavGroup { title: string - items: NavItem[] + items: NavItem[]; + onClick?: () => void; } interface SidebarData { - - footerNavGroups : NavGroup[] + + footerNavGroups: NavGroup[] navGroups: NavGroup[] } @@ -47,4 +48,4 @@ interface MenuItem { clickHandler?: () => void } -export type { SidebarData, NavGroup, NavItem, NavCollapsible, NavLink , MenuItem } +export type { SidebarData, NavGroup, NavItem, NavCollapsible, NavLink, MenuItem } diff --git a/src/components/modal.tsx b/src/components/modal.tsx index f7aa491..b57fb36 100644 --- a/src/components/modal.tsx +++ b/src/components/modal.tsx @@ -67,7 +67,7 @@ const Modal: React.FC = ({ { footer && -
+
{ diff --git a/src/components/ui/drawer.tsx b/src/components/ui/drawer.tsx new file mode 100644 index 0000000..869955f --- /dev/null +++ b/src/components/ui/drawer.tsx @@ -0,0 +1,133 @@ +import * as React from "react" +import { Drawer as DrawerPrimitive } from "vaul" + +import { cn } from "@/lib/utils" + +function Drawer({ + ...props +}: React.ComponentProps) { + return +} + +function DrawerTrigger({ + ...props +}: React.ComponentProps) { + return +} + +function DrawerPortal({ + ...props +}: React.ComponentProps) { + return +} + +function DrawerClose({ + ...props +}: React.ComponentProps) { + return +} + +function DrawerOverlay({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function DrawerContent({ + className, + children, + ...props +}: React.ComponentProps) { + return ( + + + +
+ {children} + + + ) +} + +function DrawerHeader({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function DrawerFooter({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function DrawerTitle({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function DrawerDescription({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { + Drawer, + DrawerPortal, + DrawerOverlay, + DrawerTrigger, + DrawerClose, + DrawerContent, + DrawerHeader, + DrawerFooter, + DrawerTitle, + DrawerDescription, +} diff --git a/src/components/ui/resizable.tsx b/src/components/ui/resizable.tsx new file mode 100644 index 0000000..12bbd0b --- /dev/null +++ b/src/components/ui/resizable.tsx @@ -0,0 +1,56 @@ +"use client" + +import * as React from "react" +import { GripVerticalIcon } from "lucide-react" +import * as ResizablePrimitive from "react-resizable-panels" + +import { cn } from "@/lib/utils" + +function ResizablePanelGroup({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function ResizablePanel({ + ...props +}: React.ComponentProps) { + return +} + +function ResizableHandle({ + withHandle, + className, + ...props +}: React.ComponentProps & { + withHandle?: boolean +}) { + return ( + div]:rotate-90", + className + )} + {...props} + > + {withHandle && ( +
+ +
+ )} +
+ ) +} + +export { ResizablePanelGroup, ResizablePanel, ResizableHandle } diff --git a/src/features/database/components/database-diagram.tsx b/src/features/database/components/database-diagram.tsx new file mode 100644 index 0000000..5bd504d --- /dev/null +++ b/src/features/database/components/database-diagram.tsx @@ -0,0 +1,351 @@ +// Importing necessary types and hooks from React Flow (XYFlow) +import { + Background, ColorMode, Connection, Controls, EdgeChange, + EdgeRemoveChange, MiniMap, NodeChange, NodePositionChange, + NodeRemoveChange, OnEdgesChange, OnNodesChange, + ReactFlow, useEdgesState, useNodesState, useReactFlow +} from "@xyflow/react"; + +// React built-ins +import { useCallback, useEffect, useMemo } from "react"; + +// Custom components and styles +import Table from "./table"; + +import '@xyflow/react/dist/style.css'; +import Relationship from "./relationship"; +import { TableInsertType } from "@/lib/schemas/table-schema"; + +// Custom context providers and hooks +import { useDatabase, useDatabaseOperations } from "@/providers/database-provider/database-provider"; +import { useTableToNode } from "@/hooks/use-table-to-node"; +import { useRelationshipToEdge } from "@/hooks/use-relationship-to-edge"; +import { RelationshipInsertType } from "@/lib/schemas/relationship-schema"; + +// Utils and constants +import { v4 } from "uuid"; +import { TARGET_PREFIX } from "./field"; +import CardinalityMarker from "@/features/database/components/cardinality-marker"; +import { useDiagramOps } from "@/providers/diagram-provider/diagram-provider"; + + +import { AlertTriangle, Menu } from "lucide-react"; +import { adjustTablesPositions } from "@/utils/tables"; +//import DatabaseControlButtons from "./database-control-buttons"; +import { FieldType } from "@/lib/schemas/field-schema"; +import useHighlightedEdges from "@/hooks/use-highlighted-edges"; +import { useTranslation } from "react-i18next"; +import useOverlappingTables from "@/hooks/use-overlapping-tables"; + + +import { getRelationshipSourceAndTarget } from "@/utils/relationship"; +import { Loading } from "@/components/layout/loading-modal"; + +import { CardinalityStyle } from "@/lib/database"; +import { useTheme } from "@/providers/theme-provider/theme-provider"; +import { toast } from "sonner"; +import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; +import { Button } from "@/components/ui/button"; +import DatabaseControlButtons from "./database-control-buttons"; + +import { Modals } from "@/providers/modal-provider/modal-contxet"; +import { useModal } from "@/providers/modal-provider/modal-provider"; +import { useIsMobile } from "@/hooks/use-mobile"; +import { cn } from "@/lib/utils"; + + + +const DatabaseDiagram: React.FC = () => { + const { open } = useModal(); + + + const { t } = useTranslation(); + const { theme: resolvedTheme } = useTheme(); + // Extract database state and operations + const { database, getField, isSwitchingDatabase, isLoading, databases } = useDatabase(); + + const { updateTablePositions, deleteMultiTables, deleteMultiRelationships, createRelationship } = useDatabaseOperations(); + + // Node and edge state hooks + const [nodes, setNodes, onNodesChange] = useNodesState([]); + const [edges, setEdges, onEdgesChange] = useEdgesState([]); + + // Diagram-related state (e.g. connection in progress) + const { setIsConnectionInProgress, cardinalityStyle, showController, openController } = useDiagramOps(); + + // Destructure tables and relationships from database + const { tables, relationships } = database || { tables: [], relationships: [] }; + + // Hook to allow zooming and centering the diagram + const { fitView } = useReactFlow(); + + // Define custom node and edge types + const nodeTypes = useMemo(() => ({ table: Table }), []); + const edgeTypes = useMemo(() => ({ 'relationship-edge': Relationship }), []); + + + useEffect(() => { + if (isLoading) + return; + // no database selected , open (open database) Modal + if (!database && databases?.length > 0) { + + open(Modals.OPEN_DATABASE, { + closable: false + }) + } + else if (databases.length == 0) { + // there is no databases in the first place to select from , + // we have to create a new one . + open(Modals.CREATE_DATABASE, { + closable: false + }); + } else { + fitView({ + duration: 500 + }); + } + }, [database?.id, isLoading]) + + useEffect(() => { + if (isSwitchingDatabase) { + setNodes([]); + setEdges([]); + } else { + fitView({ + duration: 500 + }); + } + + + }, [isSwitchingDatabase, database?.id]) + + // Called when a connection is made between fields + const onConnect = useCallback(async (connection: Connection) => { + + const sourceId: string | undefined = (connection.sourceHandle as string).split("_").pop(); + const targetId: string = (connection.targetHandle as string).replace(TARGET_PREFIX, ""); + + const sourceField: FieldType = getField(connection.source, sourceId as string) as FieldType; + const targetField: FieldType = getField(connection.target, targetId) as FieldType; + + const { sourceTableId, targetTableId, sourceFieldId, targetFieldId } = getRelationshipSourceAndTarget(connection.source, sourceField, connection.target, targetField); + + // Check if both fields have the same type (valid relationship) + if (sourceField?.typeId == targetField?.typeId) { + await createRelationship({ + id: v4(), + sourceTableId, + targetTableId, + sourceFieldId, + targetFieldId, + + } as RelationshipInsertType); + + } else { + + + toast(t("db_controller.invalid_relationship.title"), { + description: t("db_controller.invalid_relationship.description"), + classNames: { + description: "!text-destructive", + title: "!text-destructive" + }, + }) + } + + setIsConnectionInProgress(false); + }, [database, t]); + + // Called when nodes are updated (position changes or removed) + const handleNodesChanges: OnNodesChange = useCallback(async (changes: NodeChange[]) => { + + const nodePositionChanges: NodePositionChange[] = changes.filter((change: NodeChange) => + change.type == "position" && !change.dragging + ) as NodePositionChange[]; + + const nodeRemoveChanges: NodeRemoveChange[] = changes.filter((change: NodeChange) => change.type == "remove"); + // Save new positions to the database + if (nodePositionChanges.length > 0) + + updateTablePositions(nodePositionChanges.map((change: NodePositionChange) => ({ + id: change.id, + posX: change.position?.x, + posY: change.position?.y + } as TableInsertType))); + + + // Delete tables if removed + if (nodeRemoveChanges.length > 0 && !isSwitchingDatabase) { + deleteMultiTables(nodeRemoveChanges.map((change: NodeRemoveChange) => change.id)); + } + return onNodesChange(changes); + }, [onNodesChange, database, isSwitchingDatabase]); + + // Called when edges (relationships) change + const handleEdgeChanges: OnEdgesChange = useCallback((changes: EdgeChange[]) => { + const edgeRemoveChanges: EdgeRemoveChange[] = changes.filter((change: EdgeChange) => change.type == "remove") as EdgeRemoveChange[]; + // Delete relationships from database + if (edgeRemoveChanges.length > 0 && !isSwitchingDatabase) { + deleteMultiRelationships(edgeRemoveChanges.map((change: EdgeRemoveChange) => change.id)); + } + return onEdgesChange(changes as EdgeChange[]); + }, [onEdgesChange, isSwitchingDatabase]); + + // When user starts connecting fields + const onConnectStart = useCallback(() => { + setIsConnectionInProgress(true); + }, []); + + // When user ends connecting fields + const onConnectEnd = useCallback(() => { + setIsConnectionInProgress(false); + }, []); + + // Automatically reposition tables to avoid overlap and fit the view + const adjustPositions = useCallback(async () => { + const adjustedTables = await adjustTablesPositions(nodes, relationships); + + updateTablePositions(adjustedTables) + setTimeout(() => { + fitView({ + duration: 500 + }); + }, 300); + }, [relationships, nodes]); + + + // Convert tables and relationships into flow elements + useTableToNode(tables); + useRelationshipToEdge(relationships); + useHighlightedEdges(nodes, relationships, edges); + const { isOverlapping, puls } = useOverlappingTables(tables); + + const isMobile = useIsMobile(); + return ( + +
+ { + (isSwitchingDatabase || isLoading) && + } + + { +
+ + + + + + + + +
+ { + isOverlapping && + + + + + + + + {t("table.overlapping_tables")} + + + } +
+ { + isMobile && + + } +
+ + } + { + (cardinalityStyle != CardinalityStyle.HIDDEN) && + + + + + + + + + + + + + + + + + } +
+ + ) +} + + +export default DatabaseDiagram; + + + + + diff --git a/src/features/database/components/db-controller/index.tsx b/src/features/database/components/db-controller/index.tsx deleted file mode 100644 index 77334fe..0000000 --- a/src/features/database/components/db-controller/index.tsx +++ /dev/null @@ -1,68 +0,0 @@ - -import React, { useEffect, useState } from "react" -import { Outlet } from "react-router-dom" -import { ResizableBox as ResizableBoxRaw } from 'react-resizable'; -import 'react-resizable/css/styles.css'; - -import { useDiagramOps } from "@/providers/diagram-provider/diagram-provider"; - -const ResizableBox = ResizableBoxRaw as unknown as React.FC; - -interface Props { - -} - - -const DBController: React.FC = ({ }) => { - const [width, setWidth] = useState(512); - const { showController } = useDiagramOps() - const [show, setShow] = useState(!showController); - const [style, setStyle] = useState(undefined); - const onResize = (event: any, params: any) => { - setWidth(params.size.width); - } - - useEffect(() => { - if (!showController) { - setWidth(0); - setStyle({ - transition: 'width 0.5s', - }) - setTimeout(() => { - setShow(false); - }, 500); - - } else if (showController) { - setShow(true); - setTimeout(() => setWidth(486)); - setTimeout(() => { - setStyle(undefined); - }, 500) - } - }, [showController]) - - if (show) - return ( - -
- } - > -
- -
- - ) - else - return; -} - -export default React.memo(DBController) - - diff --git a/src/features/database/components/db-controller/relationship-controller/index.tsx b/src/features/database/components/db-controller/relationship-controller/index.tsx index f3dcf73..4b17191 100644 --- a/src/features/database/components/db-controller/relationship-controller/index.tsx +++ b/src/features/database/components/db-controller/relationship-controller/index.tsx @@ -70,7 +70,7 @@ const RelationshipController: React.FC = ({ }) => { }, []); return ( -
+
diff --git a/src/features/database/components/db-controller/tables-controller/index.tsx b/src/features/database/components/db-controller/tables-controller/index.tsx index c61b532..e6f29d9 100644 --- a/src/features/database/components/db-controller/tables-controller/index.tsx +++ b/src/features/database/components/db-controller/tables-controller/index.tsx @@ -128,7 +128,7 @@ const TablesController: React.FC = ({ }) => { }, [tables]); return ( -
+
diff --git a/src/features/database/components/db-controller/tables-controller/table-accordion-item/field/field-item.tsx b/src/features/database/components/db-controller/tables-controller/table-accordion-item/field/field-item.tsx index 8aac485..32b1b4e 100644 --- a/src/features/database/components/db-controller/tables-controller/table-accordion-item/field/field-item.tsx +++ b/src/features/database/components/db-controller/tables-controller/table-accordion-item/field/field-item.tsx @@ -105,7 +105,7 @@ const FieldItem: React.FC = ({ field }) => { placeholder={t("db_controller.type")} selectedItem={selectedType} onSelectionChange={updateFieldType} - className="flex flex-1 !bg-transparent !font-normal" + className="flex flex-1 !bg-transparent !font-normal min-w-0" />
@@ -113,9 +113,9 @@ const FieldItem: React.FC = ({ field }) => { @@ -130,8 +130,8 @@ const FieldItem: React.FC = ({ field }) => { diff --git a/src/features/database/components/rename-db.tsx b/src/features/database/components/rename-db.tsx index e9db97a..30d39a4 100644 --- a/src/features/database/components/rename-db.tsx +++ b/src/features/database/components/rename-db.tsx @@ -46,7 +46,7 @@ const RenameDB: React.FC = ({ }) => { }); setIsLoading(false); setEditMode(false); - } + } if (!database) @@ -59,7 +59,7 @@ const RenameDB: React.FC = ({ }) => { - - - {t("table.overlapping_tables")} - - - } -
-
- + isMobile ? : } - { - (cardinalityStyle != CardinalityStyle.HIDDEN) && - - - - - - - - - - - - - - - - - } -
- - ) -} - - -export default Database; - - - - + + + ); +}; +export default DatabaseLayout; diff --git a/src/features/database/mobile-layout.tsx b/src/features/database/mobile-layout.tsx new file mode 100644 index 0000000..db32a34 --- /dev/null +++ b/src/features/database/mobile-layout.tsx @@ -0,0 +1,31 @@ + +import { Outlet } from "react-router-dom"; +import DatabaseDiagram from "./components/database-diagram"; +import { useDiagramOps } from "@/providers/diagram-provider/diagram-provider"; +import { + Drawer, + DrawerContent, +} from "@/components/ui/drawer" + + + + +export const DatabaseMobileLayout: React.FC = () => { + const { showController, openController } = useDiagramOps() + + return ( +
+ + + + +
+ +
+
+
+
+ ); +}; + +export default DatabaseMobileLayout; diff --git a/src/features/database/modals/create-relationship-modal.tsx b/src/features/database/modals/create-relationship-modal.tsx index aa86f8c..560e7b2 100644 --- a/src/features/database/modals/create-relationship-modal.tsx +++ b/src/features/database/modals/create-relationship-modal.tsx @@ -88,7 +88,7 @@ const CreateRelationshipModal: React.FC = ({ onRel onOpenChange={onOpenChange} title={t("db_controller.create_relationship")} actionName={t("modals.create")} - className="min-w-[520px]" + className="md:min-w-[520px] w-full" isDisabled={!isValid} actionHandler={addRelationship} > diff --git a/src/features/database/modals/delete-database-modal.tsx b/src/features/database/modals/delete-database-modal.tsx index 500c751..ba4118b 100644 --- a/src/features/database/modals/delete-database-modal.tsx +++ b/src/features/database/modals/delete-database-modal.tsx @@ -26,7 +26,7 @@ const DeleteDatabaseModal: React.FC = ({ isOpen, onOpenChange }) => onOpenChange={onOpenChange} title={t("modals.delete_database")} actionName={t("modals.delete")} - className="min-w-[560px]" + className="md:min-w-[560px] w-full" actionHandler={onDelete} variant="danger" > diff --git a/src/features/database/modals/export-sql-modal.tsx b/src/features/database/modals/export-sql-modal.tsx index cb5d836..769bc0e 100644 --- a/src/features/database/modals/export-sql-modal.tsx +++ b/src/features/database/modals/export-sql-modal.tsx @@ -15,13 +15,13 @@ const ExportSqlModal: React.FC = ({ isOpen, onOpenChange }) => { isOpen={isOpen} onOpenChange={onOpenChange} title={t("modals.export_sql")} - className="min-w-[960px] max-w-[960px] max-h-[90vh] " + className="w-full lg:min-w-[960px]" description={t("modals.export_sql_header")} > -
+
- + ) diff --git a/src/features/database/modals/import-database.tsx b/src/features/database/modals/import-database.tsx index 22b2691..e51b736 100644 --- a/src/features/database/modals/import-database.tsx +++ b/src/features/database/modals/import-database.tsx @@ -194,12 +194,12 @@ const ImportDatabaseModal: React.FC = ({ isOpen, onOpenChange }) => onOpenChange={onOpenChange} title={t("modals.import_database.title")} actionName={t("modals.import_database.import")} - className="!min-w-[860px] !max-w-[860px] flex flex-col" + className="lg:min-w-[860px] md:min-w-[560px] w-full max-h-screen overflow-auto " actionHandler={onImport} isDisabled={!parsedDatabase} > -
-
+
+

{t("modals.import_database.import_options")}

diff --git a/src/routes/database-route.tsx b/src/routes/database-route.tsx index f3c9323..479e6e3 100644 --- a/src/routes/database-route.tsx +++ b/src/routes/database-route.tsx @@ -1,5 +1,5 @@ -import Database from '@/features/database/'; +import DatabaseLayout from '@/features/database'; import RelationshipController from '@/features/database/components/db-controller/relationship-controller'; import TablesController from '@/features/database/components/db-controller/tables-controller'; import { Navigate, Route } from 'react-router-dom'; @@ -8,7 +8,7 @@ import { Navigate, Route } from 'react-router-dom'; const useDatabaseRoutes = () => { return ( - } > + } > } /> }> }>