Obsidian theme developed

This commit is contained in:
KarimTamani
2025-05-30 19:50:54 +01:00
parent a6a09b7199
commit fdb168bfdd
40 changed files with 595 additions and 208 deletions
+10 -2
View File
@@ -10,7 +10,7 @@ interface AutocompleteProps {
onSelectionChange?: (item: any) => void,
placeholder?: string
isDisabled?: boolean
selectedItem?: Key
selectedItem?: Key
}
@@ -33,7 +33,15 @@ const Autocomplete: React.FC<AutocompleteProps> = ({ items, label = "name", onSe
variant="bordered"
aria-label={placeholder}
placeholder={placeholder}
selectedKey={selectedItem as any }
selectedKey={selectedItem as any}
inputProps={{
classNames: {
inputWrapper: "border-divider group-hover:border-primary",
},
}}
>
{(item: any) => <AutocompleteItem key={item.id}>{item[label]}</AutocompleteItem>}
</HeroUiAutocomplete>
@@ -41,8 +41,8 @@ const CardinalityMarker: React.FC<CardinalityMarkerProps> = ({ selected = false,
>
<svg
fill="transparent"
className={selected ? "stroke-primary" : "stroke-slate-300 dark:stroke-default-400"}
strokeWidth="8"
className={selected ? "stroke-primary" : "stroke-default-600 dark:stroke-default-400"}
strokeWidth="4"
width="24"
height="24"
viewBox="0 0 100 100">
+3 -3
View File
@@ -28,10 +28,10 @@ const ColorPicker: React.FC<ColorPickerProps> = ({ defaultColor, onChange }) =>
}, [onChange])
return (
<Popover placement="bottom" radius="sm" isOpen={isOpen} onOpenChange={(open) => setIsOpen(open)}>
<Popover placement="bottom" radius="sm" isOpen={isOpen} onOpenChange={(open) => setIsOpen(open)} shadow="sm">
<PopoverTrigger>
<Button size="sm"
className="size-8 cursor-pointer rounded-md border-2 border-muted transition-shadow hover:shadow-md dark:border-default-100"
className="size-8 cursor-pointer rounded-md border-2 transition-shadow hover:shadow-md border-divider"
isIconOnly
style={{
backgroundColor: color ? color : undefined
@@ -45,7 +45,7 @@ const ColorPicker: React.FC<ColorPickerProps> = ({ defaultColor, onChange }) =>
{colorOptions.map(color => (
<div
key={color}
className="size-8 cursor-pointer rounded-md border-2 border-muted transition-shadow hover:shadow-md dark:border-default-100"
className="size-8 cursor-pointer rounded-md border-2 transition-shadow hover:shadow-md border-divider"
style={{
backgroundColor: color
}}
+9 -6
View File
@@ -27,12 +27,12 @@ const DropdownMenu: React.FC<MenuDropdownProps> = ({ title, children, clickHand
return children ? children?.filter((child: MenuDropdownProps) => child.isDisabled && child.title).map((child: MenuDropdownProps) => child.title as string) : []
}, [children]);
return <Dropdown radius="sm" showArrow >
return <Dropdown radius="sm" shadow="sm" showArrow >
{
title &&
<DropdownTrigger onPressEnd={clickHandler}>
<DropdownTrigger onPressEnd={clickHandler}>
<Button size="sm" variant="light" className="min-w-[42px]" >
<span className=" text-left flex justify-between text-small font-semibold">
<span className=" text-left flex justify-between text-small font-semibold text-font/90">
{title}
</span>
</Button>
@@ -48,16 +48,19 @@ const DropdownMenu: React.FC<MenuDropdownProps> = ({ title, children, clickHand
className={child.theme == "danger" ? "text-danger" : ""}
color={child.theme}
shortcut={child.shortcut}
classNames={{
shortcut : "dark:border-font/10"
}}
onPressEnd={ child.clickHandler }
>
{
!child.children ?
<div>
<div className="text-font/90">
{child.title}
{
child.divide &&
<div className="w-full h-[0.5px] absolute bottom-[-0.5px] left-0 bg-default-200"></div>
<div className="w-full h-[0.5px] absolute bottom-[-0.5px] left-0 bg-divider dark:bg-font/10"></div>
}
</div>
:
@@ -65,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-default-200"></div>
<div className="w-full h-[0.5px] absolute bottom-[-0.5px] left-0 bg-divider dark:bg-font/10"></div>
}
</div>
-1
View File
@@ -19,7 +19,6 @@ const Menu: React.FC<MenuProps> = ({ }) => {
const { setTheme } = useTheme()
const { t } = useTranslation();
const menu: MenuDropdownProps[] = useMemo(() => [
{
+2 -2
View File
@@ -11,9 +11,9 @@ const SubmenuDropdown: React.FC<MenuDropdownProps> = ({ title, children }) => {
return (
<div onMouseEnter={onOpen} onMouseLeave={onClose} >
<Popover placement="right" isOpen={isOpen} radius="sm" offset={90} >
<Popover placement="right" isOpen={isOpen} radius="sm" offset={90} shadow="sm">
<PopoverTrigger>
<Button className="w-full h-6 bg-transparent p-0" size="sm" value={"light"}>
<Button className="w-full h-6 bg-transparent p-0 text-font/90" size="sm" value={"light"}>
<span className="w-full text-left flex justify-between text-small font-normal">
{title}
<ChevronRight className="size-4 text-icon" />
+1 -2
View File
@@ -12,7 +12,7 @@ import {
import { useTranslation } from "react-i18next";
interface ModalProps {
export interface ModalProps {
isOpen?: boolean,
onOpenChange: (open: boolean) => void,
className?: string,
@@ -22,7 +22,6 @@ interface ModalProps {
actionName?: string,
actionHandler?: () => void ,
isDisabled? : boolean
}
const Modal: React.FC<ModalProps> = ({ isOpen, onOpenChange, className, backdrop = "opaque", title, children, actionName = "Action" , actionHandler , isDisabled}) => {
+4 -4
View File
@@ -83,8 +83,8 @@ const ConnectionStatus: React.FC = () => {
<div className="flex items-center ">
<Tooltip>
<TooltipTrigger asChild>
<div className="border-1 flex gap-2 p-2 px-3 rounded-lg text-xs relative overflow-hidden dark:border-default ">
<span className={cn("flex gap-2 font-semibold", syncStatus.connected ? "text-success-400" : "text-danger-400")}>
<div className="border-1 flex gap-2 p-2 px-3 rounded-lg text-xs relative overflow-hidden border-divider dark:border-font/10">
<span className={cn("flex gap-2 font-semibold", syncStatus.connected ? "text-success" : "text-danger")}>
{
syncStatus.connected ?
<Wifi className="size-4 " /> :
@@ -102,7 +102,7 @@ const ConnectionStatus: React.FC = () => {
{
downloadProgress !== undefined ?
<>
<span className="text-slate-500 dark:text-white w-[96px] truncate">
<span className="text-font/90 w-[96px] truncate">
Saving ...
</span>
@@ -121,7 +121,7 @@ const ConnectionStatus: React.FC = () => {
</>
:
<span className="text-slate-500 dark:text-white w-[96px] truncate font-semibold" >
<span className="text-font/70 truncate font-semibold" >
Saved {deltaUploadTime}
</span>
+1 -1
View File
@@ -13,7 +13,7 @@ const Navbar: React.FC<Props> = ({ }) => {
return (
<nav className="h-12 fixed z-50 bg-background w-full flex items-center p-4 border-b border-default-200">
<nav className="h-12 fixed z-50 bg-background w-full flex items-center p-4 border-b border-divider ">
<img
src={`/stackrender.png`}
width={22}
+2 -2
View File
@@ -22,7 +22,7 @@ const SidebarItem: React.FC<SidebarItemProps> = ({ title, icon, href , type = "i
return (
<Tooltip delayDuration={0} >
<TooltipTrigger asChild>
<Link to={href as string} className="p-2 rounded-md hover:bg-default-200 sidebar-item " data-active={isActive}>
<Link to={href as string} className="p-2 rounded-md hover:bg-default-100 dark:hover:bg-background-50 sidebar-item " data-active={isActive}>
{icon}
</Link>
</TooltipTrigger>
@@ -36,7 +36,7 @@ const SidebarItem: React.FC<SidebarItemProps> = ({ title, icon, href , type = "i
)
else {
return <Divider className="bg-default-200 dark:bg-default-100" />
return <Divider className="bg-default-200 dark:bg-divider" />
}
}
+2 -2
View File
@@ -8,7 +8,7 @@ interface SidebarProps {
children?: React.ReactNode
}
const sidebarItemClass: string = "text-gray-700 dark:text-default-500 size-4 data-[active=true]:text-primary";
const sidebarItemClass: string = "text-font size-4 data-[active=true]:text-primary-900";
const Sidebar: React.FC<SidebarProps> = ({ }) => {
const { t } = useTranslation();
@@ -87,7 +87,7 @@ const Sidebar: React.FC<SidebarProps> = ({ }) => {
return (
<aside className="h-full z-[20] flex flex-col items-between py-2 justify-between sticky top-0 duration-500 w-12 bg-sidebar dark:bg-background border-r pt-[56px] dark:border-default-100">
<aside className="h-full z-[20] flex flex-col items-between py-2 justify-between sticky top-0 duration-500 w-12 bg-sidebar pt-[56px] dark:bg-background">
<div className="flex flex-col items-center gap-2">
{
sidebarItems.map((item: SidebarItemProps , index : number) => (
+2 -2
View File
@@ -20,10 +20,10 @@ const ToggleButton: React.FC<ToggleProps> = ({ active = false, children, onToggl
<Tooltip>
<TooltipTrigger asChild>
<button className={
cn("p-1 px-2 transition-all hover:bg-default rounded duration-200 dark:text-default-400",
cn("p-1 px-2 transition-all hover:bg-default hover:text-font/90 rounded duration-200 ",
className ? className : "",
active ?
"text-slate-700 bg-default/80 dark:text-white dark:bg-default/50" :
"text-font/90 bg-default dark:text-font/90 white dark:bg-default/40" :
"text-icon"
)
}
+1 -1
View File
@@ -19,7 +19,7 @@ const TooltipContent = React.forwardRef<
sideOffset={sideOffset}
className={cn(
'z-50 overflow-hidden rounded-md bg-foreground dark:bg-default-50 px-3 py-1.5 text-xs text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
'z-50 shadow-lg overflow-hidden rounded-md bg-foreground px-3 py-2 text-xs text-default animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
className
)}
{...props}
+4
View File
@@ -70,7 +70,11 @@ const useHighlightedEdges = (nodes: Node[], relationships: RelationshipType[], e
(edge.animated || edge.selected) &&
(edge.source === node.id || edge.target === node.id)
);
const newHighlightedEdgeIds = newHighlightedEdges.map((edge : Edge) => edge.id) ;
const previousHighlightedEdgeIds = node.data.highlightedEdges.map((edge : Edge) => edge.id) ;
if (areArraysEqual(previousHighlightedEdgeIds , newHighlightedEdgeIds))
return node ;
// Add the highlighted edges to the node's data
return {
...node,
+27 -2
View File
@@ -3,13 +3,15 @@ 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";
export const useTableToNode = (tables: TableType[]): void => {
const { setNodes } = useReactFlow();
useEffect(() => {
const nodes = tables.map((table: TableType) => {
const tableNodes = tables.map((table: TableType) => {
return {
id: table.id,
type: "table",
@@ -29,7 +31,30 @@ export const useTableToNode = (tables: TableType[]): void => {
} as Node
})
setNodes(nodes);
setNodes((nodes) => {
return tableNodes.map((tableNode) => {
const node: Node | undefined = nodes.find((node: Node) => node.id == tableNode.id);
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;
}
})
});
}, [tables])
}
+1 -1
View File
@@ -28,7 +28,7 @@ i18n.use(LanguageDetector)
.use(initReactI18next)
.init({
resources ,
lng: 'en',
lng: 'ar',
interpolation: {
escapeValue: false,
+120
View File
@@ -1,6 +1,126 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--color-primary-opacity: rgba(131, 93, 255, 0.05);
--color-bg-gray: #f5f7f9;
--color-font-light-hover: rgba(131, 93, 255, 0.7);
--color-bg-light-hover: rgba(131, 93, 255, 0.1);
--color-dashed: #e9ebf1;
--color-bg-page: #F6F8FA;
--color-bg-right: #ffffff;
--color-bg-sidebar: #F1F3F6;
--header-bg-color: #FFFFFF;
--search-bg-color: #f2f4f7;
--table-header-bg-color: #f9fafb;
--search-big-bg-color: #f2f4f7;
--search-big-select-color: #ffffff;
--color-bg-folder-hover: #EEF1F4;
--color-deep-gray: #e4e6ea;
--select-hover-color: rgba(131, 93, 255, 0.05);
--form-hover-color: #f9fafb;
--highlight-change-color: #ffffff;
--divider-color: #eef0f3;
--color-border: #f2f4f6;
--color-header-border: #f2f4f6;
--color-border-light: rgba(238, 240, 243, 0.5);
--icon-color: #a2a4a8;
--font-title-color: #47494b;
--font-content-color: rgba(51, 54, 57, 0.9);
--font-light-color: rgba(51, 54, 57, 0.4);
--font-pale-color: rgba(51, 54, 57, 0.6);
--box-shadow-modal-color: rgba(16, 24, 40, 0.06);
--line-height: 1.4;
--border-radius: 4px;
--color-primary: #835DFF;
--icon-primary-color: #835DFF;
--selected-border-color: rgba(131, 93, 255, 0.5);
--white-color: #fff;
--popover-select-bg-color: #ffffff;
--popover-select-bg-hover-color: rgba(131, 93, 255, 0.05);
--color-bg-tree-page: #f9fafb;
--color-white: #fff;
--check-box-border-color: #e6e7e8;
--color-error: #F92814;
--color-purple: #835CFF;
--color-purple-opacity: rgba(131, 92, 255, 0.1);
--color-purple-opacity-half: rgba(121, 88, 240, 0.7);
--color-success: #26cea4;
--color-warning: #ffb400;
--color-info: #097bed;
--color-error-bg: rgba(249, 40, 20, 0.1);
--color-success-bg: rgba(12, 187, 82, 0.1);
--color-warning-bg: rgba(255, 180, 0, 0.1);
--color-info-bg: rgba(9, 123, 237, 0.1);
--scrollbar-thumb-bg-color: #eceff2;
--scrollbar-thumb-hover-bg-color: #dcdfe2;
--modal-content-bg: #ffffff;
--modal-mask-bg: rgba(145, 152, 166, 0.7);
--color-bg-tooltip: #ffffff;
}
.dark {
--color-primary-opacity: rgba(131, 93, 255, 0.1);
--color-bg-gray: #2b3138;
--color-font-light-hover: rgba(131, 93, 255, 0.7);
--color-bg-light-hover: rgba(131, 93, 255, 0.1);
--color-dashed: #33373f;
--color-bg-page: #20252c;
--color-bg-right: #1c2026;
--color-bg-folder: #282d34;
--header-bg-color: #282d34;
--search-bg-color: #191c21;
--table-header-bg-color: #21262d;
--search-big-bg-color: #171b1f;
--search-big-select-color: #252a32;
--color-bg-folder-hover: #282e37;
--color-deep-gray: #373b45;
--select-hover-color: rgba(131, 93, 255, 0.1);
--form-hover-color: #21252d;
--highlight-change-color: #373c44;
--divider-color: #171b1f;
--color-border: #272c35;
--color-header-border: #272c35;
--color-border-light: #2c2c2c;
--icon-color: #4b515a;
--font-title-color: #ededed;
--font-content-color: #cecfd2;
--font-light-color: rgba(198, 201, 210, 0.45);
--font-pale-color: rgba(198, 201, 210, 0.7);
--box-shadow-modal-color: rgba(0, 0, 0, 0.1);
--line-height: 1.4;
--border-radius: 4px;
--color-primary: #835DFF;
--icon-primary-color: #835DFF;
--selected-border-color: rgba(131, 93, 255, 0.5);
--white-color: #fff;
--popover-select-bg-color: #2d3138;
--popover-select-bg-hover-color: #353940;
--color-bg-tree-page: transparent;
--color-white: #fff;
--check-box-border-color: #373d46;
--color-error: #F92814;
--color-purple: #7958F0;
--color-purple-opacity: rgba(121, 88, 240, 0.1);
--color-purple-opacity-half: rgba(121, 88, 240, 0.7);
--color-success: #26cea4;
--color-warning: #ffb400;
--color-info: #097bed;
--color-error-bg: rgba(249, 40, 20, 0.1);
--color-success-bg: rgba(12, 187, 82, 0.1);
--color-warning-bg: rgba(255, 180, 0, 0.1);
--color-info-bg: rgba(9, 123, 237, 0.1);
--scrollbar-thumb-bg-color: #242a31;
--scrollbar-thumb-hover-bg-color: #2f3740;
--modal-content-bg: #1c2026;
--modal-mask-bg: rgba(10, 10, 10, 0.7);
--color-bg-tooltip: #363a44;
}
}
+31
View File
@@ -1,3 +1,4 @@
/*
export const colorOptions = [
"#fd7f6f", // Warm coral red
"#7eb0d5", // Soft sky blue
@@ -15,7 +16,37 @@ export const colorOptions = [
];
export const colorOptions = [
"#835dffAA",
"#fa8c16AA",
"#5fe9a0AA", // this one appears as #5f80e9 but you may have meant #5fcba0 or similar
"#61cba0AA",
"#e2578dAA",
"#ec3e3eAA",
"#5bbfd4AA",
"#b49461AA",
"#4971a6AA",
"#9b6065AA"
]
*/
export const colorOptions = [
"#835CFFAA", // Light Hot Magenta
"#26cea4AA", // Light Selective Yellow
"#ffb400AA", // Light Neon Violet
"#097bedAA", // Light Deep Sky Blue
"#F92814AA", // Light Black (Gray)
"#7958F0AA",
"#FF47A5AA"
];
export const randomColor = () => {
return colorOptions[Math.floor(Math.random() * colorOptions.length)];
};
+2
View File
@@ -4,7 +4,9 @@ import { BrowserRouter } from "react-router-dom";
import App from "./App.tsx";
import { Provider } from "./provider.tsx";
import "./styles/globals.css" ;
import "./index.css";
import "./i18/index.ts" ;
ReactDOM.createRoot(document.getElementById("root")!).render(
+14 -14
View File
@@ -52,7 +52,7 @@ const DatabaseControlButtons: React.FC<DbControlButtons> = ({ adjustPositions })
}, []);
return (
<Navbar className="flex rounded-md border-1 border-default-200 bg-background dark:bg-transparent dark:border-default-800 " isBlurred
<Navbar className="flex rounded-md border-1 border-default-200 bg-background dark:bg-transparent bg-default dark:border-divider " isBlurred
classNames={{
wrapper: "h-14 p-2 gap-1",
}}
@@ -71,14 +71,14 @@ const DatabaseControlButtons: React.FC<DbControlButtons> = ({ adjustPositions })
>
<Undo className={cn(
"size-4 dark:text-white",
!canUndo ? "text-default-400 dark:text-default-600" : ""
!canUndo ? "text-font/30" : ""
)} />
</Button>
</span>
</TooltipTrigger>
<TooltipContent className="dark:bg-default-900">
<TooltipContent>
{t("control_buttons.undo")}
<span className="ml-2 text-slate-400">
<span className="ml-2 text-default-400">
Cntl + Z
</span>
</TooltipContent>
@@ -97,12 +97,12 @@ const DatabaseControlButtons: React.FC<DbControlButtons> = ({ adjustPositions })
</Button>
</span>
</TooltipTrigger>
<TooltipContent className="dark:bg-default-900">
<TooltipContent>
{t("control_buttons.adjust_positions")}
</TooltipContent>
</Tooltip>
<Divider orientation="vertical" className="bg-default-200 dark:bg-default-800" />
<Divider orientation="vertical" className="bg-font/10" />
<Tooltip>
<TooltipTrigger asChild>
@@ -117,7 +117,7 @@ const DatabaseControlButtons: React.FC<DbControlButtons> = ({ adjustPositions })
</Button>
</span>
</TooltipTrigger>
<TooltipContent className="dark:bg-default-900">
<TooltipContent>
{t("control_buttons.zoom_out")}
</TooltipContent>
</Tooltip>
@@ -145,11 +145,11 @@ const DatabaseControlButtons: React.FC<DbControlButtons> = ({ adjustPositions })
</span>
</TooltipTrigger>
<TooltipContent className="dark:bg-default-900">
<TooltipContent>
{t("control_buttons.zoom_in")}
</TooltipContent>
</Tooltip>
<Divider orientation="vertical" className="bg-default-200 dark:bg-default-800" />
<Divider orientation="vertical" className="bg-font/10" />
<Tooltip>
<TooltipTrigger asChild>
<span>
@@ -164,10 +164,10 @@ const DatabaseControlButtons: React.FC<DbControlButtons> = ({ adjustPositions })
</Button>
</span>
</TooltipTrigger>
<TooltipContent className="dark:bg-default-900">
<TooltipContent>
{t("control_buttons.show_all")}
<span className="ml-2 text-slate-400">
<span className="ml-2 text-default-400">
Cntl + A
</span>
</TooltipContent>
@@ -185,15 +185,15 @@ const DatabaseControlButtons: React.FC<DbControlButtons> = ({ adjustPositions })
>
<Redo className={cn(
"size-4 dark:text-white",
!canRedo ? "text-default-400 dark:text-default-600" : ""
!canRedo ? "text-font/30" : ""
)} />
</Button>
</span>
</TooltipTrigger>
<TooltipContent className="dark:bg-default-900">
<TooltipContent>
{t("control_buttons.redo")}
<span className="ml-2 text-slate-400">
<span className="ml-2 text-default-400">
Ctnl + Shift + Z
</span>
</TooltipContent>
+14 -7
View File
@@ -1,6 +1,6 @@
// Importing necessary types and hooks from React Flow (XYFlow)
import {
addEdge, Background, Connection, Controls, EdgeChange,
addEdge, Background, ColorMode, Connection, Controls, EdgeChange,
EdgeRemoveChange, NodeChange, NodePositionChange,
NodeRemoveChange, OnEdgesChange, OnNodesChange,
ReactFlow, useEdgesState, useNodesState, useReactFlow
@@ -30,16 +30,18 @@ import { useDiagramOps } from "@/providers/diagram-provider/diagram-provider";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/tooltip/tooltip";
import { addToast, Button } from "@heroui/react";
import { AlertTriangle } from "lucide-react";
import { adjustTablesPositions } from "@/utils/tables";
import { adjustTablesPositions, isTablesOverlapping } 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 { useTheme } from "next-themes";
const DatabasePage: React.FC = () => {
const { t } = useTranslation();
const { resolvedTheme } = useTheme();
// Extract database state and operations
const { database, getField } = useDatabase();
const { updateTablePositions, deleteMultiTables, deleteMultiRelationships, createRelationship } = useDatabaseOperations();
@@ -100,7 +102,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) => ({
@@ -140,7 +142,9 @@ const DatabasePage: React.FC = () => {
// Automatically reposition tables to avoid overlap and fit the view
const adjustPositions = useCallback(async () => {
updateTablePositions(await adjustTablesPositions(nodes, relationships));
const adjustedTables = await adjustTablesPositions(nodes, relationships);
console.log (adjustedTables )
updateTablePositions(adjustedTables)
setTimeout(() => {
fitView({
duration: 500
@@ -153,7 +157,9 @@ const DatabasePage: React.FC = () => {
useRelationshipToEdge(relationships);
useHighlightedEdges(nodes, relationships, edges);
const { isOverlapping, puls } = useOverlappingTables(tables);
return (
<div className="w-full h-screen flex relative overflow-hidden">
@@ -162,6 +168,7 @@ const DatabasePage: React.FC = () => {
</div>
<div className="relative w-full h-full">
<ReactFlow
colorMode={resolvedTheme as ColorMode}
nodes={nodes}
edges={edges}
fitView
@@ -172,7 +179,7 @@ const DatabasePage: React.FC = () => {
defaultEdgeOptions={{
type: 'relationship-edge',
}}
onlyRenderVisibleElements
// onlyRenderVisibleElements
panOnDrag={true}
zoomOnScroll={true}
nodeTypes={nodeTypes}
@@ -195,7 +202,7 @@ const DatabasePage: React.FC = () => {
/>
</Controls >
<Background className="bg-background dark:bg-black" />
<Background className="dark:bg-background-100" />
</ReactFlow>
<div
className="absolute left-[24px] bottom-[24px] "
@@ -25,11 +25,11 @@ const DBController: React.FC<Props> = ({ }) => {
minConstraints={[512]}
axis="x"
handle={
<div className="w-[6px] h-full absolute right-0 top-0 cursor-ew-resize">
<div className="w-[6px] border-r-2 border-transparent h-full absolute right-0 top-0 cursor-ew-resize hover:border-primary-300 active:border-primary transition-colors duration-200">
</div>
}
>
<div className="min-w-[512px] w-full h-full bg-background border-r p-2 pt-[52px] border-default-100">
<div className="min-w-[512px] w-full h-full bg-background dark:bg-background-50 border-r p-2 pt-[52px] border-default-100 dark:border-divider">
<Outlet />
</div>
@@ -1,10 +1,10 @@
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/tooltip/tooltip";
import { Cardinality, RelationshipInsertType, RelationshipType } from "@/lib/schemas/relationship-schema";
import { useDatabaseOperations } from "@/providers/database-provider/database-provider";
import { useDatabaseOperations } from "@/providers/database-provider/database-provider";
import { Button, Select, SelectItem, SharedSelection } from "@heroui/react";
import { ChevronsLeftRightEllipsis, FileMinus2, FileOutput, Trash2 } from "lucide-react";
import { useEffect, useState } from "react";
import { ChevronsLeftRightEllipsis, FileMinus2, FileOutput, Trash2 } from "lucide-react";
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
@@ -51,13 +51,13 @@ const RelationshipAccordionBody: React.FC<RelationshipAccordionBodyProps> = ({ r
<div className="w-full p-2 space-y-4">
<div className="flex">
<div className="w-full space-y-1">
<label className="font-medium flex text-slate-700 flex items-center gap-1 text-sm dark:text-default-600">
<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>
<Tooltip>
<TooltipTrigger>
<span className="truncate text-left text-sm dark:text-default-500">
<span className="truncate text-left text-sm text-font/70">
{relationship.sourceTable?.name}({relationship.sourceField?.name})
</span>
</TooltipTrigger>
@@ -68,7 +68,7 @@ const RelationshipAccordionBody: React.FC<RelationshipAccordionBodyProps> = ({ r
</div>
<div className="w-full space-y-1">
<label className="font-medium flex text-slate-700 flex items-center gap-1 text-sm dark:text-default-600">
<label className="font-medium flex text-font/90 flex items-center gap-1 text-sm ">
<FileMinus2 className="size-4" />
{t(" .target_table")}
@@ -76,7 +76,7 @@ const RelationshipAccordionBody: React.FC<RelationshipAccordionBodyProps> = ({ r
<Tooltip>
<TooltipTrigger>
<span className="truncate text-left text-sm dark:text-default-500 ">
<span className="truncate text-left text-sm text-font/70 ">
{relationship.targetTable?.name}({relationship.targetField?.name})
</span>
</TooltipTrigger>
@@ -87,7 +87,7 @@ const RelationshipAccordionBody: React.FC<RelationshipAccordionBodyProps> = ({ r
</div>
</div>
<div className="space-y-2">
<label className="flex text-sm font-medium flex text-slate-700 items-center gap-1 dark:text-default-600">
<label className="flex text-sm font-medium flex text-font/90 items-center gap-1 ">
<ChevronsLeftRightEllipsis className="size-4 " />
{t('db_controller.cardinality.name')}
</label>
@@ -98,6 +98,10 @@ const RelationshipAccordionBody: React.FC<RelationshipAccordionBodyProps> = ({ r
aria-label="cardinality"
selectedKeys={cardinality}
onSelectionChange={changeCardinality}
classNames={{
trigger: "border-divider group-hover:border-primary",
}}
>
<SelectItem key={Cardinality.one_to_one}>{t("db_controller.cardinality.one_to_one")}</SelectItem>
@@ -64,11 +64,11 @@ const RelationshipAccordionHeader: React.FC<RelationshipAccordionHeaderProps> =
onValueChange={setName}
onBlur={editRelationshipName}
type="text"
className="rounded-md px-2 py-0.5 w-full border-blue-400 focus-visible:ring-0 text-sm "
className="rounded-md px-2 py-0.5 w-full border-primary-700 focus-visible:ring-0 text-sm"
/>
<Button
variant="light"
className="size-6 p-0 text-slate-500 hover:bg-primary-foreground hover:text-slate-700 "
className="size-6 p-0 text-icon hover:bg-primary-foreground hover:text-font "
size="sm"
onPress={editRelationshipName}
isIconOnly
@@ -82,7 +82,7 @@ const RelationshipAccordionHeader: React.FC<RelationshipAccordionHeaderProps> =
!editMode && <>
<label
className="w-full truncate px-2 py-1 text-sm font-semibold text-black dark:text-white"
className="w-full truncate px-2 py-1 text-sm font-semibold text-black dark:text-font"
>
{relationship.name ? relationship.name : defaultName}
</label>
@@ -94,7 +94,7 @@ const RelationshipAccordionHeader: React.FC<RelationshipAccordionHeaderProps> =
variant="light"
onPressEnd={() => focusOnRelationship(relationship.id, true)}
>
<Focus className="size-4 text-icon dark:text-white" />
<Focus className="size-4 text-icon" />
</Button>
<Button
size="sm"
@@ -102,29 +102,29 @@ const RelationshipAccordionHeader: React.FC<RelationshipAccordionHeaderProps> =
variant="light"
onPress={() => setEditMode(true)}
>
<Pencil className="size-4 text-icon dark:text-white" />
<Pencil className="size-4 text-icon" />
</Button>
</div>
<Popover placement="bottom" radius="sm" shadow="sm" showArrow isOpen={popOverOpen} onOpenChange={setPopOverOpen}>
<Popover placement="bottom" radius="sm" shadow="sm" showArrow isOpen={popOverOpen} onOpenChange={setPopOverOpen} >
<PopoverTrigger>
<Button
size="sm"
isIconOnly
variant="light"
>
<EllipsisVertical className="size-4 text-slate-500 dark:text-white" />
<EllipsisVertical className="size-4 text-icon" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-[160px]" >
<div className="w-full flex flex-col gap-2 ">
<h3 className="font-semibold text-sm text-gray p-2">
<h3 className="font-semibold text-sm text-font/90 p-2">
{t("db_controller.actions")}
</h3>
</div>
<hr className="text-default-200" />
<hr className="border-divider" />
<Listbox aria-label="Actions" className="p-0 pb-1" >
<ListboxItem
@@ -1,6 +1,6 @@
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/tooltip/tooltip"
import { Accordion, AccordionItem, Button, Input, useDisclosure } from "@heroui/react"
import { ListCollapse, Workflow } from "lucide-react"
import { ListCollapse, Workflow } from "lucide-react"
import { Ref, useCallback, useEffect, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import RelationshipAccordionHeader from "./relationship-accordion-item/relationship-accordion-header";
@@ -87,7 +87,7 @@ const RelationshipController: React.FC<Props> = ({ }) => {
<span>
<Button
variant="light"
className="size-8 p-0"
className="size-8 p-0 text-icon hover:text-font/90"
isIconOnly
onPress={() =>
//setShowDBML((value) => !value)
@@ -111,10 +111,13 @@ const RelationshipController: React.FC<Props> = ({ }) => {
size="sm"
radius="sm"
variant="bordered"
variant="faded"
placeholder={t("db_controller.filter")}
className="h-8 w-full focus-visible:ring-0"
className="h-8 w-full focus-visible:ring-0 shadow-none "
onKeyUp={searchRelationships}
classNames={{
inputWrapper: "dark:bg-default border-divider group-hover:border-primary ",
}}
/>
</div>
@@ -135,9 +138,7 @@ const RelationshipController: React.FC<Props> = ({ }) => {
<Accordion
hideIndicator
dividerProps={{
className: "bg-default-200"
}}
isCompact
selectedKeys={selectedRelationship}
onSelectionChange={setSelectedRelationship as any}
@@ -147,7 +148,9 @@ const RelationshipController: React.FC<Props> = ({ }) => {
key={relationship.id}
aria-label={relationship.id}
classNames={{
trigger: "w-full hover:bg-default transition-all duration-200 h-12 dark:bg-background dark:hover:bg-default-50",
trigger: "w-full hover:bg-default transition-all duration-200 h-12 dark:hover:bg-background",
base: "rounded-md p-0 overflow-hidden",
}}
subtitle={
@@ -93,7 +93,7 @@ const FieldItem: React.FC<Props> = ({ field }) => {
<div className="flex w-full gap-1 items-center " style={style} ref={setNodeRef} {...attributes}>
<div {...listeners}>
<GripVertical className="size-4 text-icon cursor-move dark:text-default-400 dark:hover:text-white" />
<GripVertical className="size-4 text-icon cursor-move dark:hover:text-font/90" />
</div>
<Input
variant="bordered"
@@ -102,7 +102,11 @@ const FieldItem: React.FC<Props> = ({ field }) => {
placeholder={t("db_controller.name")}
value={fieldName}
onValueChange={setFieldName}
onBlur={saveFieldName}
classNames={{
inputWrapper: "border-divider group-hover:border-primary",
}}
/>
<Autocomplete
items={data_types}
@@ -138,25 +142,26 @@ const FieldItem: React.FC<Props> = ({ field }) => {
size="sm"
isIconOnly
variant="light"
className="text-icon hover:text-font/90"
>
<EllipsisVertical className="size-4 text-slate-500 dark:text-default-600" />
<EllipsisVertical className="size-4 " />
</Button>
</PopoverTrigger>
<PopoverContent className="w-[210px]" >
<div className="w-full flex flex-col gap-2 p-2">
<h3 className="font-semibold text-sm text-gray">
<PopoverContent className="w-[210px] bg-background" >
<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="text-default-200" />
<div className="flex w-full 500 justify-between">
<span className="text-sm text-slate-500 font-medium">
<hr className="border-divider dark:border-font/10" />
<div className="flex w-full justify-between">
<span className="text-sm text-icon font-medium dark:text-font/90">
{t("db_controller.unique")}
</span>
<Switch size="sm" defaultSelected={field.unique as boolean} onValueChange={toggleUnqiue}>
</Switch>
</div>
<label className="text-sm font-medium text-slate-500">
</div>
<label className="text-sm font-medium text-icon dark:text-font/90">
{t("db_controller.note")}
</label>
<Textarea
@@ -169,22 +174,23 @@ const FieldItem: React.FC<Props> = ({ field }) => {
onValueChange={setNote}
onBlur={updateFieldNote}
classNames={{
inputWrapper: "bg-default/80",
inputWrapper: "bg-default border-divider dark:bg-background-100",
base: "max-w-xs",
input: "resize-y min-h-[60px] max-h-[180px]",
}} />
<hr className="text-default-200" />
<hr className="border-divider dark:border-font/10" />
<Button
className="bg-default"
radius="sm" variant="faded"
className="bg-default dark:bg-danger dark:border-none dark:text-white"
radius="sm"
variant="faded"
color="danger"
size="sm"
onPressEnd={removeField}>
<span className="font-medium text-sm">
<span className="font-medium text-sm ">
{t("db_controller.delete_field")}
</span>
<Trash2 className="mr-1 size-3.5 text-danger" />
<Trash2 className="mr-1 size-3.5 text-danger dark:text-white" />
</Button>
</div>
</PopoverContent>
@@ -87,9 +87,9 @@ const FieldList: React.FC<Props> = ({ tableFields , tableId}) => {
radius="sm"
startContent={
<Plus className="size-4 text-icon dark:text-white" />
<Plus className="size-4 text-font/90 dark:text-white" />
}
className="h-8 p-2 text-xs bg-transparent hover:bg-default text-gray font-semibold"
className="h-8 p-2 text-xs bg-transparent hover:bg-default text-font/90 font-semibold"
onPressEnd={addField}
>
{t("db_controller.add_field")}
@@ -26,15 +26,15 @@ const IndexItem: React.FC<Props> = ({ index, fields }) => {
const [popOverOpen, setPopOverOpen] = useState<boolean>(false);
const [fieldIndices, setFieldIndices] = useState<Set<string>>(new Set());
useEffect(() => {
useEffect(() => {
setFieldIndices(
new Set( index.fieldIndices.map((fieldIndex : FieldIndexType) => fieldIndex.fieldId))
new Set(index.fieldIndices.map((fieldIndex: FieldIndexType) => fieldIndex.fieldId))
)
} , [index.fieldIndices])
}, [index.fieldIndices])
const toggleUnique = (unique: boolean) => {
editIndex({
id: index.id,
unique
@@ -73,6 +73,10 @@ const IndexItem: React.FC<Props> = ({ index, fields }) => {
size="sm"
aria-label={t("db_controller.select_fields")}
variant="bordered"
classNames={{
trigger: "border-divider group-hover:border-primary",
}}
onSelectionChange={onInexFieldChange}
onOpenChange={openChange}
selectedKeys={fieldIndices}
@@ -97,8 +101,9 @@ const IndexItem: React.FC<Props> = ({ index, fields }) => {
size="sm"
isIconOnly
variant="light"
className="text-icon hover:text-font/90"
>
<EllipsisVertical className="size-4 text-slate-500 dark:text-default-600" />
<EllipsisVertical className="size-4" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-[210px]" >
@@ -42,9 +42,9 @@ const IndexesList: React.FC<IndexesListProps> = ({ indices, fields, tableId }) =
variant="flat"
radius="sm"
startContent={
<Plus className="size-4 text-icon" />
<Plus className="size-4 text-font/90" />
}
className="h-8 p-2 text-xs bg-transparent hover:bg-default text-gray font-semibold"
className="h-8 p-2 text-xs bg-transparent hover:bg-default text-font/90 font-semibold"
onPressEnd={addIndex}
>
{t("db_controller.add_index")}
@@ -66,7 +66,7 @@ const TableAccordionBody: React.FC<TableAccordionBodyProps> = ({ table }) => {
return (
<div className="w-full dark:bg-background">
<div className="w-full dark:bg-background-50">
<Accordion
isCompact
variant={"light"} selectionMode="multiple" selectedKeys={selectedKeys} onSelectionChange={setSelectedKeys as any}>
@@ -74,25 +74,25 @@ const TableAccordionBody: React.FC<TableAccordionBodyProps> = ({ table }) => {
indicator={({ isOpen }) => (
<div className={cn(
'tarnsition-all duration-200 ',
isOpen ? "rotate-[-90deg]" : ""
isOpen ? "rotate-[0deg]" : ""
)}>
<ChevronLeft className="size-4 text-icon dark:text-default-600" />
<ChevronLeft className="size-4 text-icon " />
</div>
)}
classNames={{
trigger: "hover:bg-default h-6 dark:hover:bg-background"
}}
subtitle={
<div className="group dark:text-default-600 flex gap-2 items-center font-medium p-1 w-full hover:underline text-slate-500 hover:text-slate-600 transition-all duration-200 dark:hover:text-white">
<div className="group text-font/90 hover:text-font flex gap-2 items-center font-medium p-1 w-full hover:underline transition-all duration-200 dark:hover:text-white">
<FileType className="size-4 " />
<label className="text-sm w-full cursor-pointer">
{t("db_controller.fields")}
</label>
<button
className="size-4 p-0 text-xs opacity-0 group-hover:opacity-100 transition-all duration-200 hover:text-slate-700 text-icon"
className="size-4 p-0 text-xs opacity-0 group-hover:opacity-100 transition-all duration-200 hover:text-font text-icon"
onClick={addField}
>
<Plus className="size-4 dark:text-default-600" />
<Plus className="size-4 dark:text-font/90" />
</button>
</div>
}
@@ -103,25 +103,25 @@ const TableAccordionBody: React.FC<TableAccordionBodyProps> = ({ table }) => {
indicator={({ isOpen }) => (
<div className={cn(
'tarnsition-all duration-200',
isOpen ? "rotate-[-90deg]" : ""
isOpen ? "rotate-[0deg]" : ""
)}>
<ChevronLeft className="size-4 text-icon dark:text-default-600" />
<ChevronLeft className="size-4 text-icon " />
</div>
)}
classNames={{
trigger: "hover:bg-default h-6 dark:hover:bg-background"
}}
subtitle={
<div className="group flex gap-2 dark:text-default-600 items-center font-medium p-1 w-full hover:underline text-slate-500 hover:text-slate-600 transition-all duration-200 dark:hover:text-white">
<div className="group flex gap-2 text-font/90 items-center font-medium p-1 w-full hover:underline hover:text-font transition-all duration-200 dark:hover:text-white">
<FileKey className="size-4 " />
<label className="text-sm w-full cursor-pointer">
{t("db_controller.indexes")}
</label>
<button
className="size-4 p-0 text-xs opacity-0 group-hover:opacity-100 transition-all duration-200 hover:text-slate-700 text-icon"
className="size-4 p-0 text-xs opacity-0 group-hover:opacity-100 transition-all duration-200 hover:text-font text-icon"
onClick={addIndex}
>
<Plus className="size-4 dark:text-default-600" />
<Plus className="size-4 dark:text-font/90" />
</button>
</div>
}>
@@ -131,16 +131,16 @@ const TableAccordionBody: React.FC<TableAccordionBodyProps> = ({ table }) => {
indicator={({ isOpen }) => (
<div className={cn(
'tarnsition-all duration-200',
isOpen ? "rotate-[-90deg]" : ""
isOpen ? "rotate-[0deg]" : ""
)}>
<ChevronLeft className="size-4 text-icon dark:text-default-600" />
<ChevronLeft className="size-4 text-icon " />
</div>
)}
classNames={{
trigger: "hover:bg-default h-6 dark:hover:bg-background"
}}
subtitle={
<div className="flex gap-2 dark:text-default-600 items-center font-medium p-1 w-full hover:underline text-slate-500 hover:text-slate-600 transition-all duration-200 dark:hover:text-white">
<div className="flex gap-2 text-font/90 items-center font-medium p-1 w-full hover:underline hover:text-font transition-all duration-200 dark:hover:text-white">
<MessageSquareQuote className="size-4" />
<label className="text-sm w-full cursor-pointer">
{t("db_controller.note")}
@@ -149,7 +149,7 @@ const TableAccordionBody: React.FC<TableAccordionBodyProps> = ({ table }) => {
}>
<Textarea variant="bordered" className="w-full " label={t("db_controller.table_note")}
classNames={{
inputWrapper: "bg-default/80 ",
inputWrapper: "bg-default border-divider dark:bg-background-100 ",
}}
value={note}
onValueChange={setNote}
@@ -172,7 +172,7 @@ const TableAccordionBody: React.FC<TableAccordionBodyProps> = ({ table }) => {
variant="ghost"
onPressEnd={addIndex}
size="sm"
className="font-semibold p-4 border-default-50"
className="font-semibold p-4 border-divider text-font/90"
startContent={
<FileKey className="size-4 " />
}
@@ -183,7 +183,7 @@ const TableAccordionBody: React.FC<TableAccordionBodyProps> = ({ table }) => {
onPressEnd={addField}
variant="ghost"
size="sm"
className="font-semibold p-4 border-default-50"
className="font-semibold p-4 border-divider text-font/90"
startContent={
<FileType className="size-4 " />
}
@@ -87,9 +87,9 @@ const TableAccordionHeader: React.FC<TableAccordionHeaderProps> = ({ table, isOp
'tarnsition-all duration-200',
isOpen ? "rotate-[90deg]" : ""
)}>
<ChevronRight className="size-4 text-icon dark:text-white" />
<ChevronRight className="size-4 text-icon " />
</div>
<div className=" w-[1px] h-full bg-default-200 dark:bg-default-100">
<div className=" w-[1px] h-full bg-divider ">
</div>
{
!editMode &&
@@ -97,7 +97,7 @@ const TableAccordionHeader: React.FC<TableAccordionHeaderProps> = ({ table, isOp
<Tooltip>
<TooltipTrigger asChild>
<label
className="w-full text-editable truncate px-2 py-1 text-sm font-semibold text-black dark:text-white"
className="w-full text-editable truncate px-2 py-1 text-sm font-semibold text-black dark:text-font"
onDoubleClick={() => setEditMode(true)}
>
{tableName}
@@ -120,16 +120,16 @@ const TableAccordionHeader: React.FC<TableAccordionHeaderProps> = ({ table, isOp
variant="bordered"
onBlur={saveTableName}
type="text"
className="rounded-md px-2 py-0.5 w-full border-primary-700 focus-visible:ring-0 text-sm "
className="rounded-md px-2 py-0.5 w-full border-primary-700 focus-visible:ring-0 text-sm"
/>
<Button
variant="light"
className="size-6 p-0 text-slate-500 hover:bg-primary-foreground hover:text-slate-700 dark:text-white"
className="size-6 p-0 text-icon hover:bg-primary-foreground hover:text-font "
size="sm"
onPressEnd={saveTableName}
isIconOnly
>
<Check className="size-4 text-icon dark:text-white" />
<Check className="size-4 text-icon " />
</Button>
</>
}
@@ -145,7 +145,7 @@ const TableAccordionHeader: React.FC<TableAccordionHeaderProps> = ({ table, isOp
variant="light"
onPress={() => setEditMode(true)}
>
<Pencil className="size-4 text-icon dark:text-white" />
<Pencil className="size-4 text-icon " />
</Button>
<Button
size="sm"
@@ -153,7 +153,7 @@ const TableAccordionHeader: React.FC<TableAccordionHeaderProps> = ({ table, isOp
variant="light"
onPressEnd={() => focusOnTable(table.id, true)}
>
<Focus className="size-4 text-icon dark:text-white" />
<Focus className="size-4 text-icon " />
</Button>
</div>
@@ -164,29 +164,32 @@ const TableAccordionHeader: React.FC<TableAccordionHeaderProps> = ({ table, isOp
isIconOnly
variant="light"
>
<EllipsisVertical className="size-4 text-slate-500 dark:text-white" />
<EllipsisVertical className="size-4 text-icon " />
</Button>
</PopoverTrigger>
<PopoverContent className="w-[160px]" >
<div className="w-full flex flex-col gap-2 ">
<h3 className="font-semibold text-sm text-gray p-2">
<h3 className="font-semibold text-sm text-font/90 p-2">
{t("db_controller.table_actions")}
</h3>
</div>
<hr className="text-default-200" />
<hr className="border-divider" />
<Listbox aria-label="Actions" className="p-0 pb-1" >
<ListboxItem
key="add_field"
onPressEnd={addField}
className="text-font/90"
endContent={
<FileType className="size-4 text-icon dark:text-white" />
<FileType className="size-4 " />
}>
{t("db_controller.add_field")}
</ListboxItem>
<ListboxItem
key="add_index"
endContent={<FileKey className="size-4 text-icon dark:text-white" />}
className="text-font/90"
endContent={<FileKey className="size-4" />}
showDivider
onPressEnd={addIndex}
>
@@ -197,8 +200,10 @@ const TableAccordionHeader: React.FC<TableAccordionHeaderProps> = ({ table, isOp
<ListboxItem
key="duplicate"
showDivider
className="text-font/90"
onPressEnd={duplicate}
endContent={<Copy className="size-4 text-icon" />}>
endContent={<Copy className="size-4" />}>
{t("db_controller.duplicate")}
</ListboxItem>
@@ -207,6 +212,7 @@ const TableAccordionHeader: React.FC<TableAccordionHeaderProps> = ({ table, isOp
className="text-danger"
color="danger"
onPressEnd={onDeleteTable}
endContent={<Trash className="size-4" />}
>
{t("db_controller.delete_table")}
@@ -80,7 +80,7 @@ const TablesController: React.FC<Props> = ({ }) => {
<span>
<Button
variant="light"
className="size-8 p-0"
className="size-8 p-0 text-icon hover:text-font/90"
isIconOnly
onPress={() =>
//setShowDBML((value) => !value)
@@ -106,11 +106,15 @@ const TablesController: React.FC<Props> = ({ }) => {
type="text"
size="sm"
autoFocus
radius="sm"
variant="bordered"
radius="sm"
variant="faded"
placeholder={t("db_controller.filter")}
className="h-8 w-full focus-visible:ring-0"
className="h-8 w-full focus-visible:ring-0 shadow-none "
classNames={{
inputWrapper : "dark:bg-default border-divider group-hover:border-primary " ,
}}
onKeyUp={searchTables}
/>
</div>
<Button
@@ -130,7 +134,7 @@ const TablesController: React.FC<Props> = ({ }) => {
<Accordion
hideIndicator
variant={"splitted"}
selectedKeys={selectedTable}
onSelectionChange={setSelectedTable as any}
isCompact
@@ -141,8 +145,9 @@ const TablesController: React.FC<Props> = ({ }) => {
key={table.id}
aria-label={table.name}
classNames={{
trigger: "w-full h-12 hover:bg-default transition-all duration-200 dark:bg-background dark:hover:bg-default-50",
base: "rounded-md shadow p-0 overflow-hidden dark:border-1 dark:border-default-100",
trigger: "w-full h-12 hover:bg-default transition-all duration-200 dark:hover:bg-background",
base: "rounded-md mb-1 mt-1 p-0 overflow-hidden dark:border-background-100",
content : "bg-transparent"
}}
subtitle={
<TableAccordionHeader
@@ -162,6 +167,8 @@ const TablesController: React.FC<Props> = ({ }) => {
export default TablesController
+8 -8
View File
@@ -50,7 +50,7 @@ const Field: React.FC<Props> = (props) => {
return (
<div className={cn(
"group relative flex h-8 items-center justify-between gap-1 border-t border-default dark:border-default/5 px-3 text-sm last:rounded-b-[6px] hover:bg-slate-100 dark:hover:bg-primary/5 transition-all duration-200 ease-in-out",
"group relative flex h-8 items-center justify-between gap-1 border-t border-divider px-3 text-sm last:rounded-b-[6px] hover:bg-slate-100 dark:hover:bg-primary/5 transition-all duration-200 ease-in-out",
highlight ? "bg-primary/5" : ""
)}>
{
@@ -58,7 +58,7 @@ const Field: React.FC<Props> = (props) => {
<>
<label
className={cn(
"truncate flex gap-1 text-xs text-slate-900 dark:text-default-200",
"truncate flex gap-1 text-xs text-font/90 ",
field.isPrimary ? "font-semibold" : ""
)}
onDoubleClick={() => setEditMode(true)}
@@ -77,8 +77,8 @@ const Field: React.FC<Props> = (props) => {
}
</label>
<span className={cn("content-center truncate flex items-center h-full gap-1 text-right text-xs text-default-600 group-hover:hidden font-semibold text-icon dark:text-default-200",
field.isPrimary ? "font-semibold text-slate-700" : ""
<span className={cn("content-center truncate flex items-center h-full gap-1 text-right text-xs group-hover:hidden font-semibold text-font/70 ",
field.isPrimary ? "font-semibold text-font/90" : ""
)}>
{field.isPrimary && <KeyRound className="size-3" />} {field.type?.name?.split(' ')[0]}{field.nullable ? "?" : ""}
</span>
@@ -111,12 +111,12 @@ const Field: React.FC<Props> = (props) => {
onClick={(e) => e.stopPropagation()}
onChange={(e) => setFieldName(e.target.value)}
className="rounded-md outline-none px-2 py-0.5 w-full border-[0.5px] border-primary-700 bg-slate-100 focus-visible:ring-0 text-sm dark:bg-transparent dark:text-white"
className="rounded-md outline-none px-2 py-0.5 w-full border-[0.5px] border-primary-700 bg-slate-100 focus-visible:ring-0 text-sm dark:bg-transparent text-font/90"
/>
<Button
variant="light"
className="size-6 p-0 text-slate-500 hover:bg-primary-foreground hover:text-slate-700 dark:text-white"
className="size-6 p-0 text-icon hover:text-font/90"
size="sm"
isIconOnly
onPress={saveFieldName}
@@ -135,12 +135,12 @@ const Field: React.FC<Props> = (props) => {
type="source"
position={Position.Left}
id={LEFT_PREFIX + field.id}
className="w-4 h-4 border-4 bg-primary dark:border-default-900"
className="w-4 h-4 border-4 bg-primary dark:border-background-50"
/>
<Handle
type="source"
position={Position.Right}
className="w-4 h-4 border-4 bg-primary dark:border-default-900"
className="w-4 h-4 border-4 bg-primary dark:border-background-50"
id={RIGHT_PREFIX + field.id}
/>
</div>
+1 -1
View File
@@ -125,7 +125,7 @@ const Relationship: React.FC<EdgeProps<RelationshipProps>> = (props) => {
fill="none"
className={cn([
`!stroke-2 ${selected ? '!stroke-primary' : 'stroke-slate-300 dark:stroke-default-500'}`,
`!stroke-1 ${selected ? '!stroke-primary' : 'stroke-default-600 dark:stroke-default-300'}`,
])}
+26 -18
View File
@@ -32,8 +32,8 @@ export type TableProps = Node<{
}>
const MAX_FIELDS = 10;
const Table: React.FC<NodeProps<TableProps>> = ({ selected, data: { table, overlapping = false, pulsing = false, highlightedEdges = [] } }) => {
const Table: React.FC<NodeProps<TableProps>> = (props) => {
const { selected, data: { table, overlapping = false, pulsing = false, highlightedEdges = [] } } = props
const [editMode, setEditMode] = useState<boolean>(false);
const [tableName, setTableName] = useState<string>(table.name);
@@ -76,16 +76,17 @@ const Table: React.FC<NodeProps<TableProps>> = ({ selected, data: { table, overl
const toggleShowMore = useCallback(() => {
setShowMore((previousShowMore) => !previousShowMore);
}, [])
}, []);
return (
<Card className={cn(
"w-full h-full bg-background rounded-lg noselect overflow-visible dark:bg-default-900",
"w-full h-full bg-background rounded-md ring-1 ring-default-600 overflow-visible dark:bg-background-50 dark:ring-divider",
selected
? 'ring-2 ring-offset-1 ring-primary dark:ring-offset-default-900'
? 'ring-2 ring-primary dark:ring-primary'
: '',
overlapping
? 'ring-2 dark:ring-offset-default-900 ring-danger ring-offset-1 scale-105 shadow-danger '
? 'ring-2 dark:ring-danger ring-danger scale-105 shadow-danger '
: '',
!pulsing && overlapping
? 'scale-105'
@@ -94,17 +95,16 @@ const Table: React.FC<NodeProps<TableProps>> = ({ selected, data: { table, overl
? 'scale-110'
: '',
)}
shadow="sm"
shadow="none"
>
<div className="px-[2px] ">
<div className="px-[1px] ">
<div
className=" border-t-[4px] rounded-t-[6px] border-primary"
style={{ borderColor: table.color as string }}
></div>
</div>
<div className="group gap-2 flex h-9 items-center justify-between bg-default/50 px-2 dark:bg-black/20">
<Table2 className="size-4 shrink-0 text-icon dark:text-white" />
<Table2 className="size-4 shrink-0 text-icon " />
{
editMode && <>
<input
@@ -114,11 +114,11 @@ const Table: React.FC<NodeProps<TableProps>> = ({ selected, data: { table, overl
value={tableName}
onBlur={saveTableName}
type="text"
className="rounded-md outline-none px-2 py-0.5 w-full border-[0.5px] border-primary-700 font-bold bg-slate-100 focus-visible:ring-0 text-sm dark:bg-transparent dark:text-white"
className="rounded-md outline-none px-2 py-0.5 w-full border-[0.5px] border-primary-700 font-bold bg-slate-100 focus-visible:ring-0 text-sm dark:bg-transparent dark:text-white"
/>
<Button
variant="light"
className="size-6 p-0 text-slate-500 hover:bg-primary-foreground hover:text-slate-700 dark:text-white"
className="size-6 p-0 text-icon hover:bg-primary-foreground hover:text-font/90"
size="sm"
onPressEnd={saveTableName}
isIconOnly
@@ -133,13 +133,13 @@ const Table: React.FC<NodeProps<TableProps>> = ({ selected, data: { table, overl
<Tooltip>
<TooltipTrigger asChild>
<label
className=" w-full text-editable truncate px-2 py-0.5 text-sm font-bold dark:text-white dark:group-hover:bg-default-900"
className=" w-full text-editable truncate px-2 py-0.5 text-sm font-bold dark:text-white "
onDoubleClick={() => setEditMode(true)}
>
{tableName}
</label>
</TooltipTrigger>
<TooltipContent className="dark:bg-default-900">
<TooltipContent >
{t("table.double_click")}
</TooltipContent>
</Tooltip>
@@ -149,11 +149,11 @@ const Table: React.FC<NodeProps<TableProps>> = ({ selected, data: { table, overl
<Button
variant="light"
size="sm"
className="size-6 p-0 text-slate-500 hover:bg-primary-foreground hover:text-slate-700 dark:hover:bg-default-800 "
className="size-6 p-0 text-icon hover:bg-primary-foreground "
isIconOnly
onPressEnd={focus}
>
<Focus className="size-4 text-icon dark:text-white" />
<Focus className="size-4 " />
</Button>
</div>
</>
@@ -188,8 +188,16 @@ const Table: React.FC<NodeProps<TableProps>> = ({ selected, data: { table, overl
)
};
export default React.memo(Table, (previousState: any, newState: any) => {
export default React.memo(Table , (previousState: any, newState: any) => {
const previousStateHash: string = hash(previousState.data);
const newStateHash: string = hash(newState.data);
return previousStateHash == newStateHash && previousState.selected == newState.selected;
})
});
/*
,
*/
@@ -0,0 +1,18 @@
import { createContext } from "react";
export enum Modals {
CREATE_RELATIONSHIP = "CREATE_RELATIONSHIP"
}
interface ModalContextType {
focusedTableId: string | undefined;
focusedRelationshipId: string | undefined;
}
export const ModalContext = createContext<ModalContextType>({} as ModalContextType);
@@ -0,0 +1,42 @@
/*
import { useCallback, useContext, useState } from "react"
import { ModalContext, Modals } from "./modal-contxet"
import Modal, { ModalProps } from "@/components/modal/modal";
import { useDisclosure } from "@heroui/react";
interface Props { children: React.ReactNode }
interface CurrentModalProps {
modal: Modals,
modalProps: ModalProps,
content?: any
}
const ModalProvider: React.FC<Props> = ({ children }) => {
const { isOpen, onOpen } = useDisclosure();
const [currentModal, setCurrentModal] = useState<CurrentModalProps | undefined>(undefined);
const onOpenChange = useCallback(() => {
onOpen();
setCurrentModal(undefined);
}, [onOpen]);
return (
<ModalContext.Provider>
{
currentModal &&
<Modal isOpen={isOpen} {...currentModal.modalProps} onOpenChange={onOpenChange} >
</Modal>
}
{children}
</ModalContext.Provider>
)
}
export const useModal = () => useContext(ModalContext);
*/
+12 -5
View File
@@ -1,3 +1,5 @@
.react-flow__attribution {
display: none;
}
@@ -5,7 +7,7 @@
.text-editable {
@apply dark:group-hover:bg-default-50 group-hover:bg-slate-100 group-hover:ring-[0.5px] group-hover:ring-primary-700 dark:group-hover:ring-white rounded-md cursor-pointer;
@apply dark:group-hover:bg-background group-hover:bg-slate-100 group-hover:ring-[0.5px] group-hover:ring-primary dark:group-hover:ring-divider rounded-sm cursor-pointer;
}
@@ -45,7 +47,7 @@ body {
}
.sidebar-item[data-active="true"] svg {
color: hsl(var(--heroui-primary-900));
color: hsl(var(--heroui-primary));
}
@@ -78,7 +80,7 @@ div[data-slot="content"] hr[role="separator"] {
/* Target the scrollbar */
::-webkit-scrollbar {
width: 4px;
width: 6px;
/* Width of the scrollbar */
}
@@ -88,7 +90,7 @@ div[data-slot="content"] hr[role="separator"] {
/* Handle (the scroll thumb) */
::-webkit-scrollbar-thumb {
background: hsl(var(--heroui-default-100));
background: hsl(var(--heroui-scrollbar));
/* Darker gray */
border-radius: 6px;
/* Rounded corners */
@@ -100,7 +102,7 @@ div[data-slot="content"] hr[role="separator"] {
/* Handle on hover */
::-webkit-scrollbar-thumb:hover {
background: hsl(var(--heroui-default-300));
background: hsl(var(--heroui-scrollbar-hover));
/* Darker on hover */
}
@@ -116,4 +118,9 @@ div[data-slot="content"] hr[role="separator"] {
.react-flow__pane {
cursor: default !important;
}
svg.text-icon:hover {
color : #333639
}
+8 -2
View File
@@ -16,7 +16,10 @@ const adjustTablesPositions = async (
): Promise<TableType[]> => {
// Extract tables (same as before)
const tables: TableType[] = nodes.map((node: Node) => node.data.table) as TableType[];
const tables: TableType[] = nodes.map((node: Node) => (
{ ...node.data.table as TableType }
)) as TableType[];
// Build the ELK graph structure
const graph = {
@@ -37,7 +40,7 @@ const adjustTablesPositions = async (
targets: [rel.targetTableId],
})),
};
console.log (graph.children ) ;
// Run ELK layout (async)
const layoutedGraph = await elk.layout(graph);
@@ -56,6 +59,9 @@ const adjustTablesPositions = async (
const isTablesOverlapping = (tableA: TableType, tableB: TableType) => {
const tableAWidth: number = 224;
const tableAHeight: number = tableA.fields.length * 32 + 36;
+106 -29
View File
@@ -10,53 +10,130 @@ export default {
],
darkMode: "class",
plugins: [heroui({
layout: {
disabledOpacity: "0.3", // opacity-[0.3]
radius: {
small: "4px", // rounded-small
medium: "8px", // rounded-medium
large: "12px", // rounded-large
},
},
themes: {
dark: {
colors: {
background: {
DEFAULT: "#282a37",
50: "#222430",
100: "#1e1f28"
},
danger: {
DEFAULT: "#F92814"
},
font: {
DEFAULT: "#cccdd7"
},
content1 : {
DEFAULT : "#282a37"
} ,
default: {
50: '#f2f3f5',
100: '#d6d7db',
200: '#b9bbc1',
300: '#9d9fa7',
400: '#80838d',
500: '#191c21', // base
600: '#15171b',
700: '#111316',
800: '#0d0f11',
900: '#090b0c',
DEFAULT: '#191c21',
},
primary: {
DEFAULT: "#9822ff",
50: '#9822ff0D', // 5% opacity
100: '#9822ff1A', // 10% opacity
200: '#9822ff33', // 20% opacity
300: '#9822ff4D', // 30% opacity
400: '#9822ff66', // 40% opacity
500: '#9822ff80', // 50% opacity
600: '#9822ff99', // 60% opacity
700: '#9822ffB3', // 70% opacity
800: '#961FFFCC', // 80% opacity
900: '#9822ffE6', // 90% opacity
DEFAULT: "#835dff",
50: '#F3F0FF',
100: '#E3DCFF',
200: '#C6B8FF',
300: '#A994FF',
400: '#8D70FF',
500: '#835DFF', // Default
600: '#6C4BD9',
700: '#563BB3',
800: '#402C8D',
900: '#2A1D66'
},
divider: {
DEFAULT: "#272c35"
},
icon: {
DEFAULT: "#4b515a"
},
success: {
DEFAULT: "#26cea4"
},
scrollbar: {
DEFAULT: "#242a31" ,
hover : "#2f3740"
},
}
},
light: {
colors: {
danger: {
DEFAULT: "#F92814"
},
font: {
DEFAULT: "#333639"
},
sidebar: {
DEFAULT: 'hsl(0 0% 98%)',
foreground: 'hsl(240 5.3% 26.1%)',
DEFAULT: "#F1F3F6"
},
divider: {
DEFAULT: "#f2f4f6"
},
success: {
DEFAULT: "#26cea4"
},
scrollbar: {
DEFAULT: "#eceff2" ,
hover : "#dcdfe2"
},
default: {
DEFAULT: 'hsl(210 40% 96.1%)',
foreground: 'hsl(222.2 47.4% 11.2%)'
50: '#f9fafb',
100: '#f4f6f8',
200: '#e9edf1',
300: '#dbe2e8',
400: '#c6d0da',
500: '#f2f4f7',
600: '#a4b2c0',
700: '#8493a4',
800: '#69798a',
900: '#55616f',
DEFAULT: '#f2f4f7',
},
icon: {
DEFAULT: "hsl(215.4 16.3% 46.9%)"
DEFAULT: "#a2a4a8"
},
primary: {
DEFAULT: "#9822ff",
50: '#9822ff0D', // 5% opacity
100: '#9822ff1A', // 10% opacity
200: '#9822ff33', // 20% opacity
300: '#9822ff4D', // 30% opacity
400: '#9822ff66', // 40% opacity
500: '#9822ff80', // 50% opacity
600: '#9822ff99', // 60% opacity
700: '#9822ffB3', // 70% opacity
800: '#961FFFCC', // 80% opacity
900: '#9822ffE6', // 90% opacity
DEFAULT: "#835dff",
50: '#F3F0FF',
100: '#E3DCFF',
200: '#C6B8FF',
300: '#A994FF',
400: '#8D70FF',
500: '#835DFF', // Default
600: '#6C4BD9',
700: '#563BB3',
800: '#402C8D',
900: '#2A1D66'
},
},
},