Init Commit

This commit is contained in:
KarimTamani
2025-05-04 19:17:04 +01:00
commit d9905351bd
61 changed files with 3015 additions and 0 deletions
+20
View File
@@ -0,0 +1,20 @@
.now/*
*.css
.changeset
dist
esm/*
public/*
tests/*
scripts/*
*.config.js
.DS_Store
node_modules
coverage
.next
build
!.commitlintrc.cjs
!.lintstagedrc.cjs
!jest.config.js
!plopfile.js
!react-shim.js
!tsup.config.ts
+92
View File
@@ -0,0 +1,92 @@
{
"$schema": "https://json.schemastore.org/eslintrc.json",
"env": {
"browser": false,
"es2021": true,
"node": true
},
"extends": [
"plugin:react/recommended",
"plugin:prettier/recommended",
"plugin:react-hooks/recommended",
"plugin:jsx-a11y/recommended"
],
"plugins": ["react", "unused-imports", "import", "@typescript-eslint", "jsx-a11y", "prettier"],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaFeatures": {
"jsx": true
},
"ecmaVersion": 12,
"sourceType": "module"
},
"settings": {
"react": {
"version": "detect"
}
},
"rules": {
"no-console": "warn",
"react/prop-types": "off",
"react/jsx-uses-react": "off",
"react/react-in-jsx-scope": "off",
"react-hooks/exhaustive-deps": "off",
"jsx-a11y/click-events-have-key-events": "warn",
"jsx-a11y/interactive-supports-focus": "warn",
"prettier/prettier": "warn",
"no-unused-vars": "off",
"unused-imports/no-unused-vars": "off",
"unused-imports/no-unused-imports": "warn",
"@typescript-eslint/no-unused-vars": [
"warn",
{
"args": "after-used",
"ignoreRestSiblings": false,
"argsIgnorePattern": "^_.*?$"
}
],
"import/order": [
"warn",
{
"groups": [
"type",
"builtin",
"object",
"external",
"internal",
"parent",
"sibling",
"index"
],
"pathGroups": [
{
"pattern": "~/**",
"group": "external",
"position": "after"
}
],
"newlines-between": "always"
}
],
"react/self-closing-comp": "warn",
"react/jsx-sort-props": [
"warn",
{
"callbacksLast": true,
"shorthandFirst": true,
"noSortAlphabetically": false,
"reservedFirst": true
}
],
"padding-line-between-statements": [
"warn",
{"blankLine": "always", "prev": "*", "next": "return"},
{"blankLine": "always", "prev": ["const", "let", "var"], "next": "*"},
{
"blankLine": "any",
"prev": ["const", "let", "var"],
"next": ["const", "let", "var"]
}
]
}
}
+30
View File
@@ -0,0 +1,30 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
pnpm-lock.yaml
yarn.lock
package-lock.json
bun.lockb
+1
View File
@@ -0,0 +1 @@
package-lock=true
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 Next UI
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+50
View File
@@ -0,0 +1,50 @@
# Vite & HeroUI Template
This is a template for creating applications using Vite and HeroUI (v2).
[Try it on CodeSandbox](https://githubbox.com/frontio-ai/vite-template)
## Technologies Used
- [Vite](https://vitejs.dev/guide/)
- [HeroUI](https://heroui.com)
- [Tailwind CSS](https://tailwindcss.com)
- [Tailwind Variants](https://tailwind-variants.org)
- [TypeScript](https://www.typescriptlang.org)
- [Framer Motion](https://www.framer.com/motion)
## How to Use
To clone the project, run the following command:
```bash
git clone https://github.com/frontio-ai/vite-template.git
```
### Install dependencies
You can use one of them `npm`, `yarn`, `pnpm`, `bun`, Example using `npm`:
```bash
npm install
```
### Run the development server
```bash
npm run dev
```
### Setup pnpm (optional)
If you are using `pnpm`, you need to add the following code to your `.npmrc` file:
```bash
public-hoist-pattern[]=*@heroui/*
```
After modifying the `.npmrc` file, you need to run `pnpm install` again to ensure that the dependencies are installed correctly.
## License
Licensed under the [MIT license](https://github.com/frontio-ai/vite-template/blob/main/LICENSE).
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

+28
View File
@@ -0,0 +1,28 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + HeroUI</title>
<meta key="title" content="Vite + HeroUI" property="og:title" />
<meta
content="Make beautiful websites regardless of your design experience."
property="og:description"
/>
<meta
content="Make beautiful websites regardless of your design experience."
name="description"
/>
<meta
key="viewport"
content="viewport-fit=cover, width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"
name="viewport"
/>
<link href="/favicon.ico" rel="icon" />
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
+54
View File
@@ -0,0 +1,54 @@
{
"name": "vite-template",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"lint": "eslint -c .eslintrc.json ./src/**/**/*.{ts,tsx} --fix",
"preview": "vite preview"
},
"dependencies": {
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0",
"@heroui/react": "^2.7.6",
"@radix-ui/react-tooltip": "^1.2.3",
"@react-aria/visually-hidden": "3.8.21",
"@react-types/shared": "3.28.0",
"@xyflow/react": "^12.6.0",
"framer-motion": "11.15.0",
"i18next-browser-languagedetector": "^8.0.5",
"lucide-react": "^0.501.0",
"react": "18.3.1",
"react-dom": "18.3.1",
"react-i18next": "^15.5.1",
"react-router-dom": "6.23.0",
"tailwind-merge": "^3.2.0",
"tailwind-variants": "0.3.0",
"tailwindcss": "3.4.16"
},
"devDependencies": {
"@types/node": "20.5.7",
"@types/react": "18.3.3",
"@types/react-dom": "18.3.0",
"@typescript-eslint/eslint-plugin": "8.11.0",
"@typescript-eslint/parser": "8.11.0",
"@vitejs/plugin-react": "^4.2.1",
"autoprefixer": "10.4.19",
"eslint": "^8.57.0",
"eslint-config-prettier": "9.1.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-jsx-a11y": "^6.4.1",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-prettier": "5.2.1",
"eslint-plugin-react": "^7.23.2",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-unused-imports": "4.1.4",
"postcss": "8.4.38",
"prettier": "3.3.3",
"typescript": "5.6.3",
"vite": "^5.2.0",
"vite-tsconfig-paths": "^4.3.2"
}
}
+6
View File
@@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};
Binary file not shown.

After

Width:  |  Height:  |  Size: 157 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 KiB

+18
View File
@@ -0,0 +1,18 @@
import { Route, Routes } from "react-router-dom";
import "@/styles/globals.css"
import { TooltipProvider } from "./components/tooltip/tooltip";
import { ReactFlowProvider } from "@xyflow/react";
import useAppRoutes from "./routes/app-route";
function App() {
const appRoutes = useAppRoutes() ;
return (
<ReactFlowProvider>
<TooltipProvider delayDuration={0}>
{appRoutes}
</TooltipProvider>
</ReactFlowProvider>
);
}
export default App;
@@ -0,0 +1,56 @@
import { colorOptions } from "@/lib/colors";
import { Button, Popover, PopoverContent, PopoverTrigger } from "@heroui/react";
import { useCallback, useState } from "react";
interface ColorPickerProps {
defaultColor?: string;
onChange?: (color: string) => void
}
const ColorPicker: React.FC<ColorPickerProps> = ({ defaultColor, onChange }) => {
const [color , setColor] = useState<string>( defaultColor as string ) ;
const [isOpen, setIsOpen] = useState(false);
const onSelect = useCallback((color: string) => {
setIsOpen(false);
onChange && onChange(color) ;
setColor ( color)
}, [onChange])
return (
<Popover placement="bottom" radius="sm" isOpen={isOpen} onOpenChange={(open) => setIsOpen(open)}>
<PopoverTrigger>
<Button size="sm"
className="size-8 cursor-pointer rounded-md border-2 border-muted transition-shadow hover:shadow-md"
isIconOnly
style={{
backgroundColor : color
}}
>
</Button>
</PopoverTrigger>
<PopoverContent>
<div className="grid grid-cols-4 gap-2">
{colorOptions.map(color => (
<div
key={color}
className="size-8 cursor-pointer rounded-md border-2 border-muted transition-shadow hover:shadow-md"
style={{
backgroundColor: color
}}
onClick={() => onSelect(color)}
>
</div>
))}
</div>
</PopoverContent>
</Popover>
)
}
export default ColorPicker;
+90
View File
@@ -0,0 +1,90 @@
import { Dropdown, DropdownMenu as HeroDropdownMenu, DropdownItem, DropdownTrigger, Button, useDisclosure, Popover, PopoverTrigger, PopoverContent, Divider, DropdownSection } from "@heroui/react";
import { useMemo, useState } from "react";
import SubmenuDropdown from "./submenu-dropdown";
export interface MenuDropdownProps {
title?: string;
children?: MenuDropdownProps[];
theme?: "default" | "danger",
isDisabled?: boolean,
divide?: boolean,
shortcut?: string,
isOpen?: boolean
}
const DropdownMenu: React.FC<MenuDropdownProps> = ({ title, children, isOpen }) => {
const disabledChilds: string[] = useMemo(() => {
return children ? children?.filter((child: MenuDropdownProps) => child.isDisabled && child.title).map((child: MenuDropdownProps) => child.title as string) : []
}, [children]);
return <Dropdown radius="sm" >
{
title &&
<DropdownTrigger >
<Button size="sm" variant="light" >
<span className=" text-left flex justify-between text-small font-semibold">
{title}
</span>
</Button>
</DropdownTrigger>
}
<HeroDropdownMenu disabledKeys={disabledChilds} >
{
children ? children?.map((child: MenuDropdownProps) => (
<DropdownItem
key={child.title as string}
className={child.theme == "danger" ? "text-danger" : ""}
color={child.theme}
shortcut={child.shortcut}
>
{
!child.children ?
<div>
{child.title}
{
child.divide &&
<div className="w-full h-[0.5px] absolute bottom-[-0.5px] left-0 bg-default-200"></div>
}
</div>
:
<div >
<SubmenuDropdown {...child} />
{
child.divide &&
<div className="w-full h-[0.5px] absolute bottom-[-0.5px] left-0 bg-default-200"></div>
}
</div>
}
</DropdownItem>
)) : []
}
</HeroDropdownMenu>
</Dropdown>
}
export default DropdownMenu;
+117
View File
@@ -0,0 +1,117 @@
import DropdownMenu, { MenuDropdownProps } from "./menu-dropdown";
const menu: MenuDropdownProps[] = [
{
title: "File",
children: [{
title: "New"
},
{
title: "Open",
shortcut: "Ctnl + O"
}, {
title: "Save",
shortcut: "Ctnl + S",
divide: true
}, {
title: "Import",
divide: true,
children: [{
title: ".json",
}, {
title: ".dbml"
}, {
title: "MySql"
}, {
title: "Postgresql"
}]
}, {
title: "Export SQL",
children: [{
title: "Generic",
}, {
title: "MySql"
}, {
title: "Postgresql"
}]
}, {
title: "Export ORM Models",
divide: true
}, {
title: "Delete Project",
theme: "danger"
}]
}, {
title: "Edit",
children: [{
title: "Undo",
isDisabled: true,
},
{
title: "Redo",
isDisabled: true,
}, {
title: "Clear",
}]
} , {
title : "View" ,
children : [{
title : "Hide Controller" ,
shortcut : "Ctnl + B" ,
divide : true
} , {
title : "Zoom on scroll" ,
divide : true ,
children : [{
title : "On"
} , {
title : "Off"
}] ,
} , {
title : "Theme" ,
children : [{
title : "Light"
} , {
title : "Dark"
}]
}]
} , {
title : "Help" ,
children : [{
title : "Show Docs" ,
} , {
title : "Join Discord"
}]
}
]
interface MenuProps {
}
const Menu: React.FC<MenuProps> = ({ }) => {
return menu.map(menuItem => (
<DropdownMenu {...menuItem} />
))
}
export default Menu;
+35
View File
@@ -0,0 +1,35 @@
import { Button, Popover, PopoverContent, PopoverTrigger, useDisclosure } from "@heroui/react";
import { MenuDropdownProps } from "./menu-dropdown";
import { ChevronRight } from "lucide-react";
import DropdownMenu from "./menu-dropdown";
const SubmenuDropdown: React.FC<MenuDropdownProps> = ({ title, children }) => {
const { isOpen, onOpen, onClose } = useDisclosure();
return (
<div onMouseEnter={onOpen} onMouseLeave={onClose} >
<Popover placement="right" isOpen={isOpen} radius="sm" offset={90} >
<PopoverTrigger>
<Button className="w-full h-6 bg-transparent p-0" size="sm" value={"light"}>
<span className="w-full text-left flex justify-between text-small font-normal">
{title}
<ChevronRight className="size-4 text-icon" />
</span>
</Button>
</PopoverTrigger>
<PopoverContent>
<DropdownMenu
children={children}
/>
</PopoverContent>
</Popover>
</div>
)
};
export default SubmenuDropdown;
+31
View File
@@ -0,0 +1,31 @@
import { NavbarBrand, NavbarContent, Navbar as NavbarContainer, Image, Dropdown, DropdownTrigger, Button, DropdownMenu, DropdownItem } from "@heroui/react";
import Menu from "../menu/menu";
interface Props {
}
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">
<img
src="/stackrender.png"
width={22}
alt="logo"
/>
<h3 className="font-semibold ml-2 text-slate-900 text-sm">StackRender</h3>
<div className="ml-4">
<Menu/>
</div>
</nav>
)
}
export default Navbar;
+44
View File
@@ -0,0 +1,44 @@
import { Link } from "react-router-dom";
import { Tooltip, TooltipContent, TooltipTrigger } from "../tooltip/tooltip";
import { Divider } from "@heroui/react";
import React from "react";
export interface SidebarItemProps {
type?: "item" | "divider"
title?: string;
icon?: React.ReactNode;
href?: string;
isActive? : boolean
}
const SidebarItem: React.FC<SidebarItemProps> = ({ title, icon, href , type = "item" , isActive}) => {
if (type == "item")
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}>
{icon}
</Link>
</TooltipTrigger>
<TooltipContent
side="left"
align="center"
>
{title}
</TooltipContent>
</Tooltip>
)
else {
return <Divider className="bg-default-200" />
}
}
export default SidebarItem
+116
View File
@@ -0,0 +1,116 @@
import { BookOpen, EarthLock, GitCommitHorizontal, Github, Settings, Sparkles, Table, TableProperties, Waypoints, Workflow, X, Zap } from "lucide-react";
import SidebarItem, { SidebarItemProps } from "./sidebar-item";
import { useMemo } from "react";
import { useLocation } from "react-router-dom";
import { useTranslation } from 'react-i18next';
interface SidebarProps {
children?: React.ReactNode
}
const sidebarItemClass : string = "text-gray-700 size-4 data-[active=true]:text-primary" ;
const Sidebar: React.FC<SidebarProps> = ({ }) => {
const { t , i18n } = useTranslation() ;
console.log ( i18n.language)
console.log ()
const location = useLocation() ;
const sidebarItems: SidebarItemProps[] = useMemo(() => [
{
title: t("sidebar.tables"),
icon: <TableProperties className={sidebarItemClass}></TableProperties>,
href: "/database/tables",
isActive : location.pathname.endsWith("/database/tables")
},
{
title: t("sidebar.relationships"),
icon: <Workflow className={sidebarItemClass}></Workflow>,
href: "/database/relationships",
isActive : location.pathname.endsWith("/database/relationships")
},
{
title: "AI",
icon: <Sparkles className={sidebarItemClass}></Sparkles>,
href: "/database/bot",
isActive : location.pathname.endsWith("/database/bot")
},
{
type: "divider"
},
{
title: "API",
icon: <Zap className={sidebarItemClass}></Zap>,
href: "/database/ai-bot",
},
{
title: "Authentication",
icon: <EarthLock className={sidebarItemClass}></EarthLock>,
href: "/database/ai-bot",
},
{
title: "Controllers",
icon: <Waypoints className={sidebarItemClass}></Waypoints>,
href: "/database/ai-bot",
},
{
type: "divider"
},
{
title: "Git",
icon: <GitCommitHorizontal className={sidebarItemClass}></GitCommitHorizontal>,
href: "/database/ai-bot",
},
{
type: "divider"
},
{
title: "Project Setting",
icon: <Settings className={sidebarItemClass}></Settings>,
href: "/database/ai-bot",
},
], [location])
const bottomSidebarItems: SidebarItemProps[] = useMemo(() => [
{
title: "Github",
icon: <Github className={sidebarItemClass}></Github>,
href: "/database/ai-bot",
},
{
title: "Docs",
icon: <BookOpen className={sidebarItemClass}></BookOpen>,
href: "/database/ai-bot",
},
], []) ;
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 border-r pt-[56px]">
<div className="flex flex-col items-center gap-2">
{
sidebarItems.map((item: SidebarItemProps) => (
<SidebarItem
{...item}
/>
))
}
</div>
<div className="flex flex-col items-center gap-2 ">
{
bottomSidebarItems.map((item: SidebarItemProps) => (
<SidebarItem
{...item}
/>
))
}
</div>
</aside>
)
}
export default Sidebar;
+30
View File
@@ -0,0 +1,30 @@
import React from 'react';
import * as TooltipPrimitive from '@radix-ui/react-tooltip';
import { cn } from '@heroui/react';
const TooltipProvider = TooltipPrimitive.Provider;
const Tooltip = TooltipPrimitive.Root;
const TooltipTrigger = TooltipPrimitive.Trigger;
const TooltipContent = React.forwardRef<
React.ElementRef<typeof TooltipPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
>(({ className, sideOffset = 4, ...props }, ref) => (
<TooltipPrimitive.Content
ref={ref}
sideOffset={sideOffset}
className={cn(
'z-50 overflow-hidden rounded-md bg-foreground 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',
className
)}
{...props}
/>
));
TooltipContent.displayName = TooltipPrimitive.Content.displayName;
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
+40
View File
@@ -0,0 +1,40 @@
import { en, enLanguage } from "./languages/en";
import { fr, frLanguage } from "./languages/fr";
import { Language } from "./types";
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import { ar, arLanguage } from "./languages/ar";
export const languages: Language[] = [
enLanguage,
frLanguage ,
arLanguage
];
const resources = {
en,
fr,
ar
};
i18n.use(LanguageDetector)
.use(initReactI18next)
.init({
resources ,
lng: 'en', // 👈 set default language to French
interpolation: {
escapeValue: false,
},
fallbackLng: enLanguage.code,
debug: false,
});
export { i18n };
+51
View File
@@ -0,0 +1,51 @@
import { Language } from "../types";
export const ar = {
translation: {
sidebar: {
tables: "الجداول",
relationships: "العلاقات"
},
db_controller: {
filter : "تصفية" ,
add_table : "إضافة جدول" ,
add_field : "إضافة حقل" ,
add_index : "إضافة فهرس" ,
add_relationship : "إضافة علاقة" ,
show_code : "عرض الكود" ,
fields : "الحقول" ,
indexes : "الفهارس" ,
note : "ملاحظة" ,
name : "الاسم" ,
type : "النوع" ,
primary_key : "المفتاح الأساسي" ,
nullable : "يسمح بالقيمة الفارغة" ,
select_fields : "اختر الحقول" ,
unique : "فريد" ,
table_note : "ملاحظة الجدول" ,
collapse : "إخفاء الكل" ,
primary_table : "الجدول الأساسي" ,
referenced_table : "الجدول المشار إليه" ,
cardinality :{
name : "العلاقة" ,
one_to_one : "واحد إلى واحد" ,
one_to_many : "واحد إلى متعدد" ,
many_to_one : "متعدد إلى واحد" ,
many_to_many : "متعدد إلى متعدد" ,
},
delete : "حذف" ,
} ,
table: {
double_click: "انقر مرتين للتعديل"
},
}
}
export const arLanguage: Language = {
name: 'Arabic',
nativeName: 'العربية',
code: 'ar',
};
+52
View File
@@ -0,0 +1,52 @@
import { Language } from "../types";
export const en = {
translation : {
sidebar : {
tables : "Tables" ,
relationships : "Relationships"
} ,
db_controller : {
filter : "Filter" ,
add_table : "Add Table" ,
add_field : "Add Field" ,
add_index : "Add Index" ,
add_relationship : "Add Relationship" ,
show_code : "Show code" ,
fields : "Fields" ,
indexes : "Indexes" ,
note : "Note" ,
name : "Name" ,
type : "Type" ,
primary_key : "Primary Key" ,
nullable : "Nullable" ,
select_fields : "Select fields" ,
unique : "Unique" ,
table_note : "Table note" ,
collapse : "Collapse All" ,
primary_table : "Primary Table" ,
referenced_table : "Referenced Table" ,
cardinality :{
name : "Cardinality" ,
one_to_one : "One to One" ,
one_to_many : "One to Many" ,
many_to_one : "Many to One" ,
many_to_many : "Many to Many" ,
} ,
delete : "Delete" ,
},
table : {
double_click : "Double click to edit"
}
}
}
export const enLanguage : Language = {
name : "English" ,
nativeName: 'English',
code: 'en',
}
+51
View File
@@ -0,0 +1,51 @@
import { Language } from "../types";
export const fr = {
translation: {
sidebar: {
tables: "Tableaux",
relationships: "Relations"
} ,
db_controller : {
filter : "Filtrer" ,
add_table : "Ajouter une table" ,
add_field : "Ajouter un champ" ,
add_index : "Ajouter un index" ,
add_relationship : "Ajouter une relation" ,
show_code : "Afficher le code" ,
fields : "Champs" ,
indexes : "Index" ,
note : "Note" ,
name : "Nom" ,
type : "Type" ,
primary_key : "Clé primaire" ,
nullable : "Nullable" ,
select_fields : "Sélectionner des champs" ,
unique : "Unique" ,
table_note : "Note de la table" ,
collapse : "Tout réduire" ,
primary_table : "Table primaire" ,
referenced_table : "Table référencée" ,
cardinality :{
name : "Cardinalité" ,
one_to_one : "Un à un" ,
one_to_many : "Un à plusieurs" ,
many_to_one : "Plusieurs à un" ,
many_to_many : "Plusieurs à plusieurs" ,
},
delete : "Supprimer" ,
} ,
table: {
double_click: "Double-cliquez pour modifier"
},
}
}
export const frLanguage: Language = {
name: "French",
nativeName: 'Français',
code: 'fr',
}
+7
View File
@@ -0,0 +1,7 @@
export type Language = {
name: string;
nativeName: string;
code: string;
};
+6
View File
@@ -0,0 +1,6 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
+21
View File
@@ -0,0 +1,21 @@
export const colorOptions = [
"#fd7f6f", // Warm coral red
"#7eb0d5", // Soft sky blue
"#b2e061", // Fresh lime green
"#bd7ebe", // Soft lavender pink
"#ffb55a", // Vibrant orange
"#ffee65", // Warm yellow
"#beb9db", // Light purple grey
"#fdcce5", // Light blush pink
"#8bd3c7", // Minty teal
"#f4a1a1", // Soft peachy red
"#9bd78c", // Fresh pastel green
"#d1a9e0" // Light lilac purple
];
export const randomColor = () => {
return colorOptions[Math.floor(Math.random() * colorOptions.length)];
};
+4
View File
@@ -0,0 +1,4 @@
export interface DataType {
id: string;
name: string;
}
+14
View File
@@ -0,0 +1,14 @@
import { DataType } from "../data/data-types";
export interface Field {
id: string;
name: string;
type: DataType;
primaryKey: boolean;
unique: boolean;
nullable: boolean;
createdAt: number;
default?: string;
}
+22
View File
@@ -0,0 +1,22 @@
export interface Relationship {
id: string;
name: string;
sourceSchema?: string;
sourceTableId: string;
targetSchema?: string;
targetTableId: string;
sourceFieldId: string;
targetFieldId: string;
sourceCardinality: Cardinality;
targetCardinality: Cardinality;
createdAt: number;
}
export type RelationshipType =
| 'one_to_one'
| 'one_to_many'
| 'many_to_one'
| 'many_to_many';
export type Cardinality = 'one' | 'many';
+14
View File
@@ -0,0 +1,14 @@
import { Field } from "./field";
export interface Table {
id: string;
name: string;
x: number;
y: number;
fields: Field[];
width?: number;
order?: number;
color: string;
createdAt: number;
comments?: string;
}
+18
View File
@@ -0,0 +1,18 @@
import React from "react";
import ReactDOM from "react-dom/client";
import { BrowserRouter } from "react-router-dom";
import App from "./App.tsx";
import { Provider } from "./provider.tsx";
import "./index.css";
import "./i18/index.ts" ;
ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode>
<BrowserRouter>
<Provider>
<App />
</Provider>
</BrowserRouter>
</React.StrictMode>,
);
+27
View File
@@ -0,0 +1,27 @@
import Sidebar from "@/components/sidebar/sidebar";
import DatabasePage from "../database/database-page";
import Navbar from "@/components/navbar/navbar";
import { Outlet } from "react-router-dom";
interface Props {
}
const DashboardPage: React.FC<Props> = ({ }) => {
return (
<div className="flex flex-col justify-between h-full h-screen">
<Navbar />
<div className="flex">
<Sidebar />
<Outlet />
</div>
</div>
)
}
export default DashboardPage;
+211
View File
@@ -0,0 +1,211 @@
import { Table as TableType } from "@/lib/interfaces/table";
import { addEdge, applyEdgeChanges, applyNodeChanges, Background, Controls, MiniMap, ReactFlow, useEdgesState, useNodesState, useReactFlow } from "@xyflow/react";
import { useCallback, useMemo } from "react";
import Table from "./table/table";
import '@xyflow/react/dist/style.css';
import { Relationship } from "./table/relationship";
import DBController from "./db-controller/db-controller";
interface DatabaseProps {
initialTables?: TableType
}
const initialNodes = [
{
id: '1', type: "table", position: { x: 500, y: 500 }, data: {
table: {
id: "1",
name: "users",
fields: [
{
id: 'e5n46eojbyxpg65pb1sesysi2',
name: 'id',
type: {
id: 'integer',
name: 'integer',
},
primaryKey: true,
unique: true,
nullable: false,
createdAt: Date.now(),
},
{
id: '7nbmg2bt6dzltr8rfouxctdlc',
name: 'reservation_id',
type: {
id: 'integer',
name: 'integer',
},
primaryKey: false,
unique: false,
nullable: true,
createdAt: Date.now(),
},
{
id: 'f9kcpflp010xe5ad3rmexla63',
name: 'rating',
type: {
id: 'integer',
name: 'integer',
},
primaryKey: false,
unique: false,
nullable: false,
createdAt: Date.now(),
},
{
id: '3znekob0pqusyvdoq14nhlvgo',
name: 'comment',
type: {
id: 'character_varying',
name: 'character varying',
},
primaryKey: false,
unique: false,
nullable: true,
createdAt: Date.now(),
},
],
}
},
style: { width: 224 }
},
{
id: '2', type: "table", position: { x: 200, y: 500 }, data: {
table: {
id: "1",
name: "products",
fields: [
{
id: 'e5n46eojbyxpg65pb1sesysi2',
name: 'id',
type: {
id: 'integer',
name: 'integer',
},
primaryKey: true,
unique: true,
nullable: false,
createdAt: Date.now(),
},
{
id: '7nbmg2bt6dzltr8rfouxctdlc',
name: 'user_id',
type: {
id: 'integer',
name: 'integer',
},
primaryKey: false,
unique: false,
nullable: true,
createdAt: Date.now(),
},
{
id: 'f9kcpflp010xe5ad3rmexla63',
name: 'rating',
type: {
id: 'integer',
name: 'integer',
},
primaryKey: false,
unique: false,
nullable: false,
createdAt: Date.now(),
},
{
id: 'f9kcpflp010xe5ad3rmexla63',
name: 'rating',
type: {
id: 'integer',
name: 'integer',
},
primaryKey: false,
unique: false,
nullable: false,
createdAt: Date.now(),
},
{
id: 'f9kcpflp010xe5ad3rmexla63',
name: 'rating',
type: {
id: 'integer',
name: 'integer',
},
primaryKey: false,
unique: false,
nullable: false,
createdAt: Date.now(),
},
{
id: '3znekob0pqusyvdoq14nhlvgo',
name: 'comment',
type: {
id: 'character_varying',
name: 'character varying',
},
primaryKey: false,
unique: false,
nullable: true,
createdAt: Date.now(),
},
],
}
},
style: { width: 224 }
},
];
const initialEdges: any[] = [];
const edgeTypes = {
'relationship-edge': Relationship,
};
const DatabasePage: React.FC<DatabaseProps> = ({ initialTables }) => {
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
const nodeTypes = useMemo(() => ({ table: Table }), []);
const onConnect = useCallback((params: any) => setEdges((eds) => addEdge(params, eds)), [setEdges]);
return (
<div className="w-full h-screen flex">
<DBController/>
<ReactFlow
nodes={nodes}
edges={edges}
fitView
className="w-full h-full"
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
defaultEdgeOptions={{
animated: false,
type: 'relationship-edge',
}}
panOnDrag={true}
zoomOnScroll={true}
nodeTypes={nodeTypes}
edgeTypes={edgeTypes}
>
<Controls />
<Background className="bg-default/10" />
</ReactFlow>
</div>
)
}
export default DatabasePage;
@@ -0,0 +1,20 @@
import { Outlet } from "react-router-dom"
interface Props {
}
const DBController : React.FC<Props> = ({ }) => {
return(
<div className="w-[640px] h-full bg-background border-r p-2 pt-[52px] ">
<Outlet/>
</div>
)
}
export default DBController
@@ -0,0 +1,105 @@
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/tooltip/tooltip";
import { Button, Select, SelectItem } from "@heroui/react";
import { ChevronsLeftRightEllipsis, FileMinus2, FileOutput, SquareArrowLeft, SquareArrowRight, Trash2 } from "lucide-react";
import { useTranslation } from "react-i18next";
interface Props {
}
export const cardinalities = [
{ key: "one2one", label: "One to One" },
{ key: "one2many", label: "One to Many" },
{ key: "many2one", label: "Many to One" },
{ key: "many2many", label: "Many 2 many" },
];
const RelationshipAccordionBody: React.FC<Props> = ({ }) => {
const {t } = useTranslation() ;
return (
<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">
<FileOutput className="size-4" />
{t("db_controller.primary_table")}
</label>
<Tooltip>
<TooltipTrigger>
<span className="truncate text-left text-sm">
users(id)
</span>
</TooltipTrigger>
<TooltipContent>
users(id)
</TooltipContent>
</Tooltip>
</div>
<div className="w-full space-y-1">
<label className="font-medium flex text-slate-700 flex items-center gap-1 text-sm">
<FileMinus2 className="size-4" />
{t("db_controller.referenced_table")}
</label>
<Tooltip>
<TooltipTrigger>
<span className="truncate text-left text-sm ">
products(user_id)
</span>
</TooltipTrigger>
<TooltipContent>
products(user_id)
</TooltipContent>
</Tooltip>
</div>
</div>
<div className="space-y-2">
<label className="flex text-sm font-medium flex text-slate-700 items-center gap-1">
<ChevronsLeftRightEllipsis className="size-4 " />
{t('db_controller.cardinality.name')}
</label>
<Select
className="w-full"
size="sm"
variant="bordered"
>
<SelectItem key={"one2one"}>{t("db_controller.cardinality.one_to_one")}</SelectItem>
<SelectItem key={"one2many"}>{t("db_controller.cardinality.one_to_many")}</SelectItem>
<SelectItem key={"many2one"}>{t("db_controller.cardinality.many_to_one")}</SelectItem>
<SelectItem key={"many2many"}>{t("db_controller.cardinality.many_to_many")}</SelectItem>
</Select>
</div>
<div className="flex justify-center">
<Button
variant="solid"
className="bg-transparent text-xs"
radius="sm"
size="sm"
>
<Trash2 className="mr-1 size-3.5 text-danger" />
<div className="text-danger font-semibold">
{t("db_controller.delete")}
</div>
</Button>
</div>
</div>
)
}
export default RelationshipAccordionBody;
@@ -0,0 +1,90 @@
import { randomColor } from "@/lib/colors";
import { Button, cn, Input } from "@heroui/react";
import { Check, ChevronRight, EllipsisVertical, Focus, Pencil } from "lucide-react";
import { useState } from "react";
interface RelationshipAccordionHeaderProps {
isOpen?: boolean;
}
const RelationshipAccordionHeader: React.FC<RelationshipAccordionHeaderProps> = ({ isOpen }) => {
const [editMode, setEditMode] = useState<boolean>(false);
return (
<div className="group w-full flex h-12 gap-1 flex p-2 items-center" >
<div className={cn(
'tarnsition-all duration-200',
isOpen ? "rotate-[90deg]" : ""
)}>
<ChevronRight className="size-4 text-icon" />
</div>
{
editMode && <>
<Input
placeholder={"Usres"}
autoFocus
size="sm"
variant="bordered"
onBlur={() => setEditMode(false)}
type="text"
className="rounded-md px-2 py-0.5 w-full border-blue-400 focus-visible:ring-0 dark:bg-slate-900 text-sm "
/>
<Button
variant="light"
className="size-6 p-0 text-slate-500 hover:bg-primary-foreground hover:text-slate-700 dark:text-slate-400 dark:hover:bg-slate-800 dark:hover:text-slate-200"
size="sm"
onPress={() => setEditMode(false)}
isIconOnly
>
<Check className="size-4 text-icon" />
</Button>
</>
}
{
!editMode && <>
<label
className="w-full truncate px-2 py-1 text-sm font-semibold text-black"
>
users one or many posts
</label>
<div className="hidden shrink-0 flex-row group-hover:flex">
<Button
size="sm"
isIconOnly
variant="light"
>
<Focus className="size-4 text-icon" />
</Button>
<Button
size="sm"
isIconOnly
variant="light"
onPress={() => setEditMode(true)}
>
<Pencil className="size-4 text-icon" />
</Button>
</div>
<Button
size="sm"
isIconOnly
variant="light"
>
<EllipsisVertical className="size-4 text-slate-500" />
</Button>
</>
}
</div>
)
}
export default RelationshipAccordionHeader;
@@ -0,0 +1,104 @@
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/tooltip/tooltip"
import { Accordion, AccordionItem, Button, Input } from "@heroui/react"
import { Code, List, ListCollapse, Table, Workflow } from "lucide-react"
import { 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";
interface Props {
}
const RelationshipController: React.FC<Props> = ({ }) => {
const [items, setItems] = useState(["Item 1", "Item 2", "Item 3", "Item 4"]);
const { t } = useTranslation();
return (
<div className="w-full h-full flex flex-col gap-2">
<div className="flex items-center justify-between gap-4 py-1">
<div>
<Tooltip>
<TooltipTrigger asChild>
<span>
<Button
variant="light"
className="size-8 p-0"
isIconOnly
onPress={() =>
//setShowDBML((value) => !value)
console.log("hello world ")
}
>
<ListCollapse className="size-4" />
</Button>
</span>
</TooltipTrigger>
<TooltipContent>
{t("db_controller.collapse")}
</TooltipContent>
</Tooltip>
</div>
<div className="flex-1">
<Input
//ref={filterInputRef}
type="text"
size="sm"
radius="sm"
variant="bordered"
placeholder={t("db_controller.filter")}
//placeholder={t('side_panel.tables_section.filter')}
className="h-8 w-full focus-visible:ring-0 "
//value={filterText}
//onChange={(e) => setFilterText(e.target.value)}
/>
</div>
<Button
variant="solid"
radius="sm"
color="primary"
startContent={
<Workflow className="h-4 w-4 " />
}
className="h-8 p-2 text-xs font-semibold "
//onClick={handleCreateTable}
>{t("db_controller.add_relationship")}
</Button>
</div>
<div className=" flex-1 overflow-auto">
<Accordion
hideIndicator
dividerProps={{
className : "bg-default-200"
}}
>
{items.map(item => (
<AccordionItem
key={item}
aria-label={item}
classNames={{
trigger: "w-full hover:bg-default transition-all duration-200",
base: "rounded-md p-0 overflow-hidden",
}}
subtitle={
<RelationshipAccordionHeader/>
}
>
<RelationshipAccordionBody/>
</AccordionItem>
))}
</Accordion>
</div>
</div>
)
}
export default RelationshipController
@@ -0,0 +1,116 @@
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/tooltip/tooltip";
import { useSortable } from "@dnd-kit/sortable";
import { Autocomplete, AutocompleteItem, Button, Input } from "@heroui/react";
import { EllipsisVertical, Grip, GripVertical, KeyRound } from "lucide-react";
import { useTranslation } from "react-i18next";
import { CSS } from "@dnd-kit/utilities";
interface Props {
id : string
}
export const animals = [
{ label: "Cat", key: "cat", description: "The second most popular pet in the world" },
{ label: "Dog", key: "dog", description: "The most popular pet in the world" },
{ label: "Elephant", key: "elephant", description: "The largest land animal" },
{ label: "Lion", key: "lion", description: "The king of the jungle" },
{ label: "Tiger", key: "tiger", description: "The largest cat species" },
{ label: "Giraffe", key: "giraffe", description: "The tallest land animal" },
{
label: "Dolphin",
key: "dolphin",
description: "A widely distributed and diverse group of aquatic mammals",
},
{ label: "Penguin", key: "penguin", description: "A group of aquatic flightless birds" },
{ label: "Zebra", key: "zebra", description: "A several species of African equids" },
{
label: "Shark",
key: "shark",
description: "A group of elasmobranch fish characterized by a cartilaginous skeleton",
},
{
label: "Whale",
key: "whale",
description: "Diverse group of fully aquatic placental marine mammals",
},
{ label: "Otter", key: "otter", description: "A carnivorous mammal in the subfamily Lutrinae" },
{ label: "Crocodile", key: "crocodile", description: "A large semiaquatic reptile" },
];
const FieldItem: React.FC<Props> = ({id }) => {
const { t } = useTranslation();
const { attributes, listeners, setNodeRef, transform, transition } = useSortable({ id });
const style = {
transform: CSS.Transform.toString(transform),
};
return (
<div className="flex w-full gap-1 items-center " style={style} ref={setNodeRef} {...attributes}>
<div {...listeners}>
<GripVertical className="size-4 text-icon" />
</div>
<Input
variant="bordered"
size="sm"
placeholder={t("db_controller.name")}
value={id}
/>
<Autocomplete
className="w-full"
defaultItems={animals}
size="sm"
variant="bordered"
placeholder={t("db_controller.type")}
>
{(item) => <AutocompleteItem key={item.key}>{item.label}</AutocompleteItem>}
</Autocomplete>
<div className="flex gap-2 ml-2">
<Tooltip>
<TooltipTrigger asChild>
<button className="p-1 px-3 transition-all hover:bg-default rounded duration-200" >
<span className="text-icon text-sm">
N
</span>
</button>
</TooltipTrigger>
<TooltipContent>
{t("db_controller.nullable")}?
</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<button className="p-1 px-2 transition-all hover:bg-default rounded duration-200" >
<span className="text-icon text-sm">
<KeyRound className="size-4" />
</span>
</button>
</TooltipTrigger>
<TooltipContent>
{t("db_controller.primary_key")}
</TooltipContent>
</Tooltip>
<Button
size="sm"
isIconOnly
variant="light"
>
<EllipsisVertical className="size-4 text-icon" />
</Button>
</div>
</div>
)
}
export default FieldItem;
@@ -0,0 +1,72 @@
import { Button } from "@heroui/react";
import { Plus } from "lucide-react";
import FieldItem from "./field-item";
import { useTranslation } from "react-i18next";
import { useState } from "react";
import { closestCenter, DndContext, PointerSensor, useSensor, useSensors } from "@dnd-kit/core";
import { arrayMove, SortableContext, verticalListSortingStrategy } from "@dnd-kit/sortable";
interface Props {
}
const FieldList: React.FC<Props> = ({ }) => {
const { t } = useTranslation();
const [fields, setFields] = useState<string[]>(["Field 1", "Field 2", "Field 3", "Field 4"])
const sensors = useSensors(
useSensor(PointerSensor)
);
function handleDragEnd(event: any) {
const { active, over } = event;
if (active.id !== over?.id) {
setFields((items) => {
const oldIndex = items.indexOf(active.id);
const newIndex = items.indexOf(over.id);
return arrayMove(items, oldIndex, newIndex);
});
}
}
return (
<div className="w-full space-y-2 no-select">
<DndContext
sensors={sensors}
collisionDetection={closestCenter}
onDragEnd={handleDragEnd}
>
<div className="space-y-2">
<SortableContext
items={fields}
strategy={verticalListSortingStrategy}
>
{
fields.map((field: string) => (
<FieldItem id={field}/>
))
}
</SortableContext>
</div>
</DndContext>
<Button
variant="flat"
radius="sm"
startContent={
<Plus className="size-4 text-icon" />
}
className="h-8 p-2 text-xs bg-transparent hover:bg-default text-gray font-semibold"
//onClick={handleCreateTable}
>
{t("db_controller.add_field")}
</Button>
</div >
)
}
export default FieldList;
@@ -0,0 +1,73 @@
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/tooltip/tooltip";
import { Button, Select, SelectItem } from "@heroui/react";
import { EllipsisVertical } from "lucide-react";
import { useTranslation } from "react-i18next";
export const animals = [
{ key: "cat", label: "Cat" },
{ key: "dog", label: "Dog" },
{ key: "elephant", label: "Elephant" },
{ key: "lion", label: "Lion" },
{ key: "tiger", label: "Tiger" },
{ key: "giraffe", label: "Giraffe" },
{ key: "dolphin", label: "Dolphin" },
{ key: "penguin", label: "Penguin" },
{ key: "zebra", label: "Zebra" },
{ key: "shark", label: "Shark" },
{ key: "whale", label: "Whale" },
{ key: "otter", label: "Otter" },
{ key: "crocodile", label: "Crocodile" },
];
interface Props {
}
const IndexItem: React.FC<Props> = ({ }) => {
const { t } = useTranslation();
return (
<div className="flex gap-2 w-full">
<Select
className="w-full"
placeholder={t("db_controller.select_fields")}
selectionMode="multiple"
size="sm"
variant="bordered"
>
{animals.map((animal) => (
<SelectItem key={animal.key}>{animal.label}</SelectItem>
))}
</Select>
<div className="flex gap-2 ml-2">
<Tooltip>
<TooltipTrigger asChild>
<button className="p-1 px-3 transition-all hover:bg-default rounded duration-200" >
<span className="text-icon text-sm">
U
</span>
</button>
</TooltipTrigger>
<TooltipContent>
{t("db_controller.unique")}?
</TooltipContent>
</Tooltip>
<Button
size="sm"
isIconOnly
variant="light"
>
<EllipsisVertical className="size-4 text-icon" />
</Button>
</div>
</div>
)
}
export default IndexItem;
@@ -0,0 +1,36 @@
import { Button, Select, SelectItem } from "@heroui/react";
import IndexItem from "./index-item";
import { Plus } from "lucide-react";
import { useTranslation } from "react-i18next";
interface Props {
}
const IndexesList: React.FC<Props> = ({ }) => {
const { t} = useTranslation() ;
return (
<div className="w-full flex-col space-y-2">
<div>
<IndexItem />
</div>
<Button
variant="flat"
radius="sm"
startContent={
<Plus className="size-4 text-icon" />
}
className="h-8 p-2 text-xs bg-transparent hover:bg-default text-gray font-semibold"
//onClick={handleCreateTable}
>
{t("db_controller.add_index")}
</Button>
</div>
)
}
export default IndexesList;
@@ -0,0 +1,137 @@
import { Table } from "@/lib/interfaces/table"
import { Accordion, AccordionItem, Button, cn, Divider, Textarea } from "@heroui/react";
import { ChevronLeft, ChevronRight, FileKey, FileText, FileType, Key, MessageSquareQuote } from "lucide-react";
import { useCallback, useState } from "react";
import { useTranslation } from "react-i18next";
import ColorPicker from "@/components/color-picker/color-picker";
import FieldList from "./field/field-list";
import IndexesList from "./index/indexes-list";
export interface TableAccordionBodyProps {
table?: Table ,
}
const TableAccordionBody: React.FC<TableAccordionBodyProps> = ({ table }) => {
const [selectedKeys, setSelectedKeys] = useState(new Set(["fields"]));
const { t } = useTranslation();
const onColorChange = useCallback((color : string) => {
console.log ( color)
} ,[])
return (
<div className="w-full">
<Accordion
variant={"light"} selectionMode="multiple" selectedKeys={selectedKeys} onSelectionChange={setSelectedKeys as any}>
<AccordionItem key="fields" aria-label="Fields"
indicator={({ isOpen }) => (
<div className={cn(
'tarnsition-all duration-200',
isOpen ? "rotate-[-90deg]" : ""
)}>
<ChevronLeft className="size-4 text-icon" />
</div>
)}
classNames={{
trigger: "hover:bg-default h-6 "
}}
subtitle={
<div className="flex gap-2 items-center font-medium p-1 w-full hover:underline text-slate-500 hover:text-slate-600 transition-all duration-200">
<FileType className="size-4 " />
<label className="text-sm w-full cursor-pointer">
{t("db_controller.fields")}
</label>
</div>
}
>
<FieldList />
</AccordionItem>
<AccordionItem key="indexes" aria-label="Indexes"
indicator={({ isOpen }) => (
<div className={cn(
'tarnsition-all duration-200',
isOpen ? "rotate-[-90deg]" : ""
)}>
<ChevronLeft className="size-4 text-icon" />
</div>
)}
classNames={{
trigger: "hover:bg-default"
}}
subtitle={
<div className="flex gap-2 items-center font-medium p-1 w-full hover:underline text-slate-500 hover:text-slate-600 transition-all duration-200">
<FileKey className="size-4 " />
<label className="text-sm w-full cursor-pointer">
{t("db_controller.indexes")}
</label>
</div>
}>
<IndexesList/>
</AccordionItem>
<AccordionItem key="note" aria-label="Note"
indicator={({ isOpen }) => (
<div className={cn(
'tarnsition-all duration-200',
isOpen ? "rotate-[-90deg]" : ""
)}>
<ChevronLeft className="size-4 text-icon" />
</div>
)}
classNames={{
trigger: "hover:bg-default"
}}
subtitle={
<div className="flex gap-2 items-center font-medium p-1 w-full hover:underline text-slate-500 hover:text-slate-600 transition-all duration-200">
<MessageSquareQuote className="size-4" />
<label className="text-sm w-full cursor-pointer">
{t("db_controller.note")}
</label>
</div>
}>
<Textarea variant="bordered" className="w-full " label={t("db_controller.table_note")} classNames={{
inputWrapper: "bg-default/80",
}} />
</AccordionItem>
</Accordion>
<hr className="bg-default-200" />
<div className="flex p-2 pb-0 items-start">
<div className="w-full">
<ColorPicker
onChange={onColorChange}
/>
</div>
<div className="flex gap-2">
<Button
variant="ghost"
size="sm"
className="font-semibold p-4"
startContent={
<FileKey className="size-4 " />
}
>
{t("db_controller.add_index")}
</Button>
<Button
variant="ghost"
size="sm"
className="font-semibold p-4"
startContent={
<FileType className="size-4 " />
}
>
{t("db_controller.add_field")}
</Button>
</div>
</div>
</div>
)
}
export default TableAccordionBody;
@@ -0,0 +1,112 @@
import { Tooltip, TooltipTrigger, TooltipContent } from "@/components/tooltip/tooltip";
import { randomColor } from "@/lib/colors";
import { Table } from "@/lib/interfaces/table"
import { useSortable } from "@dnd-kit/sortable";
import { Button, cn, Divider, Input } from "@heroui/react";
import { Check, ChevronDown, ChevronLeft, ChevronRight, ChevronUp, EllipsisVertical, Focus, Grip, GripVertical, Pencil } from "lucide-react";
import { useState } from "react";
import { CSS } from "@dnd-kit/utilities";
import { useTranslation } from "react-i18next";
export interface TableAccordionHeaderProps {
table?: Table,
isOpen?: boolean,
id: string
}
const TableAccordionHeader: React.FC<TableAccordionHeaderProps> = ({ table, isOpen, id }) => {
const style = {
borderLeft: "6px solid " + randomColor(),
};
const { t } = useTranslation();
const [editMode, setEditMode] = useState<boolean>(false);
return (
<div className="group w-full flex h-12 gap-1 border-l-4 flex p-2 items-center"
style={style}
>
<div className={cn(
'tarnsition-all duration-200',
isOpen ? "rotate-[90deg]" : ""
)}>
<ChevronRight className="size-4 text-icon" />
</div>
<div className=" w-[1px] h-full bg-default-200">
</div>
{
!editMode &&
<div className="w-full px-1">
<Tooltip>
<TooltipTrigger asChild>
<label
className="w-full text-editable truncate px-2 py-1 text-sm font-semibold text-black"
onDoubleClick={() => setEditMode(true)}
>
{id}
</label>
</TooltipTrigger>
<TooltipContent>
{t("table.double_click")}
</TooltipContent>
</Tooltip>
</div>
}
{
editMode && <>
<Input
placeholder={"Usres"}
autoFocus
size="sm"
variant="bordered"
onBlur={() => setEditMode(false)}
type="text"
className="rounded-md px-2 py-0.5 w-full border-blue-400 focus-visible:ring-0 dark:bg-slate-900 text-sm "
/>
<Button
variant="light"
className="size-6 p-0 text-slate-500 hover:bg-primary-foreground hover:text-slate-700 dark:text-slate-400 dark:hover:bg-slate-800 dark:hover:text-slate-200"
size="sm"
onClick={() => setEditMode(false)}
isIconOnly
>
<Check className="size-4 text-icon" />
</Button>
</>
}
{
!editMode && <>
<div className="hidden shrink-0 flex-row group-hover:flex">
<Button
size="sm"
isIconOnly
variant="light"
>
<Focus className="size-4 text-icon" />
</Button>
<Button
size="sm"
isIconOnly
variant="light"
onPress={() => setEditMode(true)}
>
<Pencil className="size-4 text-icon" />
</Button>
</div>
<Button
size="sm"
isIconOnly
variant="light"
>
<EllipsisVertical className="size-4 text-slate-500" />
</Button>
</>
}
</div>
)
}
export default TableAccordionHeader;
@@ -0,0 +1,103 @@
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/tooltip/tooltip"
import { Accordion, AccordionItem, Button, Input } from "@heroui/react"
import { ChevronDown, Code, EllipsisVertical, Focus, Grid, Grip, List, Pencil, Table } from "lucide-react"
import { useTranslation } from "react-i18next";
import { useState } from "react";
import TableAccordionHeader from "./table-accordion-item/table-accordion-header";
import TableAccordionBody from "./table-accordion-item/table-accordion-body";
interface Props {}
const TablesController: React.FC<Props> = ({ }) => {
const [items, setItems] = useState(["Item 1", "Item 2", "Item 3", "Item 4"]);
const { t } = useTranslation();
return (
<div className="w-full h-full flex flex-col gap-2">
<div className="flex items-center justify-between gap-4 py-1">
<div>
<Tooltip>
<TooltipTrigger asChild>
<span>
<Button
variant="light"
className="size-8 p-0"
isIconOnly
onPress={() =>
//setShowDBML((value) => !value)
console.log("hello world ")
}
>
{false ? (
<List className="size-4" />
) : (
<Code className="size-4" />
)}
</Button>
</span>
</TooltipTrigger>
<TooltipContent>
{t("db_controller.show_code")}
</TooltipContent>
</Tooltip>
</div>
<div className="flex-1">
<Input
//ref={filterInputRef}
type="text"
size="sm"
radius="sm"
variant="bordered"
placeholder={t("db_controller.filter")}
className="h-8 w-full focus-visible:ring-0"
//value={filterText}
//onChange={(e) => setFilterText(e.target.value)}
/>
</div>
<Button
variant="solid"
color="primary"
radius="sm"
startContent={
<Table className="h-4 w-4 " />
}
className="h-8 p-2 text-xs font-semibold"
//onClick={handleCreateTable}
> {t("db_controller.add_table")}
</Button>
</div>
<div className=" flex-1 overflow-auto">
<Accordion
hideIndicator
variant="splitted"
>
{items.map(item => (
<AccordionItem
key={item}
aria-label={item}
classNames={{
trigger: "w-full hover:bg-default transition-all duration-200",
base: "rounded-md shadow p-0 overflow-hidden",
}}
subtitle={
<TableAccordionHeader id={item} />
}
>
<TableAccordionBody />
</AccordionItem>
))}
</Accordion>
</div>
</div>
)
}
export default TablesController
+108
View File
@@ -0,0 +1,108 @@
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/tooltip/tooltip";
import { Field as FieldType } from "@/lib/interfaces/field";
import { Button, cn, select } from "@heroui/react";
import { Handle, Position, useConnection } from "@xyflow/react";
import { Check } from "lucide-react";
import React, { useState } from "react";
interface Props {
field: FieldType,
showHandles?: boolean
}
const Field: React.FC<Props> = ({ field, showHandles }) => {
const [editMode, setEditMode] = useState<boolean>(false);
const connection = useConnection();
return (
<div className="group relative flex h-8 items-center justify-between gap-1 border-t px-3 text-sm last:rounded-b-[6px] hover:bg-slate-100 dark:hover:bg-slate-800 transition-all duration-200 ease-in-out ">
{
!editMode &&
<>
<label
className=" truncate text-sm "
onDoubleClick={() => setEditMode(true)}
>
{field.name}
</label>
<span className="content-center truncate text-right text-xs text-muted-foreground text-default-600" >
{field.type.name.split(' ')[0]}
</span>
</>
}
{
editMode &&
<>
<input
// ref={inputRef}
onBlur={() => setEditMode(false)}
placeholder={field.name}
autoFocus
type="text"
// value={fieldName}
onClick={(e) => e.stopPropagation()}
// onChange={(e) => setFieldName(e.target.value)}
className="rounded-md px-2 py-0.5 w-full border-[0.5px] border-blue-400 bg-slate-100 focus-visible:ring-0 dark:bg-slate-900 text-sm "
/>
<Button
variant="light"
className="size-6 p-0 text-slate-500 hover:bg-primary-foreground hover:text-slate-700 dark:text-slate-400 dark:hover:bg-slate-800 dark:hover:text-slate-200"
size="sm"
isIconOnly
onPress={() => setEditMode(false)}
>
<Check className="size-4" />
</Button>
</>
}
<div className={
cn(
"absolute w-full left-0 ",
!showHandles ? "invisible" : "visible"
)} >
<Handle
type="source"
position={Position.Left}
id={"left-"+ field.id}
className="w-4 h-4 border-3 bg-primary"
/>
<Handle
type="source"
position={Position.Right}
className="w-4 h-4 border-3 bg-primary"
id={"right-"+ field.id}
/>
</div>
{
<div className={
cn(
"absolute w-full left-0 ",
!connection.inProgress ? "invisible" : "visible"
)} >
<Handle
id={`${"target"}_${field.id}`}
className={
true
? '!absolute !left-0 !top-0 !h-full !w-full !transform-none !rounded-none !border-none !opacity-0'
: `!invisible`
}
position={Position.Left}
type="target"
/>
</div>
}
</div>
)
}
export default React.memo(Field);
+140
View File
@@ -0,0 +1,140 @@
import { Relationship as RelationshipType } from "@/lib/interfaces/relationship";
import { cn } from "@heroui/react";
import { Edge, EdgeProps, getSmoothStepPath, Position, useReactFlow } from "@xyflow/react";
import { useMemo } from "react";
export type RelationshipProps = Edge<{
relationship: RelationshipType,
selected?: boolean
}, 'relationship-edge'>
export const Relationship: React.FC<EdgeProps<RelationshipProps>> = ({
id,
sourceX,
sourceY,
targetX,
targetY,
source,
target,
selected,
data,
}) => {
const { getInternalNode, getEdge } = useReactFlow();
//const { openRelationshipFromSidebar, selectSidebarSection } = useLayout();
//const { checkIfRelationshipRemoved, checkIfNewRelationship } = useDiff();
const sourceNode = getInternalNode(source);
const targetNode = getInternalNode(target);
const edge = getEdge(id);
const sourceHandle: 'left' | 'right' = edge?.sourceHandle?.startsWith?.(
"right-"
)
? 'right'
: 'left';
const sourceWidth = sourceNode?.measured.width ?? 0;
const sourceLeftX =
sourceHandle === 'left' ? sourceX + 3 : sourceX - sourceWidth - 10;
const sourceRightX =
sourceHandle === 'left' ? sourceX + sourceWidth + 9 : sourceX;
const targetWidth = targetNode?.measured.width ?? 0;
const targetLeftX = targetX - 1;
const targetRightX = targetX + targetWidth + 10;
const { sourceSide, targetSide } = useMemo(() => {
const distances = {
leftToLeft: Math.abs(sourceLeftX - targetLeftX),
leftToRight: Math.abs(sourceLeftX - targetRightX),
rightToLeft: Math.abs(sourceRightX - targetLeftX),
rightToRight: Math.abs(sourceRightX - targetRightX),
};
const minDistance = Math.min(
distances.leftToLeft,
distances.leftToRight,
distances.rightToLeft,
distances.rightToRight
);
const minDistanceKey = Object.keys(distances).find(
(key) => distances[key as keyof typeof distances] === minDistance
) as keyof typeof distances;
switch (minDistanceKey) {
case 'leftToRight':
return { sourceSide: 'left', targetSide: 'right' };
case 'rightToLeft':
return { sourceSide: 'right', targetSide: 'left' };
case 'rightToRight':
return { sourceSide: 'right', targetSide: 'right' };
default:
return { sourceSide: 'left', targetSide: 'left' };
}
}, [sourceLeftX, sourceRightX, targetLeftX, targetRightX]);
const [edgePath] = useMemo(
() =>
getSmoothStepPath({
sourceX: sourceSide === 'left' ? sourceLeftX : sourceRightX,
sourceY,
targetX: targetSide === 'left' ? targetLeftX : targetRightX,
targetY,
borderRadius: 14,
sourcePosition:
sourceSide === 'left' ? Position.Left : Position.Right,
targetPosition:
targetSide === 'left' ? Position.Left : Position.Right,
// offset: (edgeNumber + 1) * 14,
}),
[
sourceSide,
targetSide,
sourceLeftX,
sourceRightX,
targetLeftX,
targetRightX,
sourceY,
targetY,
// edgeNumber,
]
);
return (
<>
<path
id={id}
d={edgePath}
//markerStart={`url(#${sourceMarker})`}
//markerEnd={`url(#${targetMarker})`}
fill="none"
className={cn([
'react-flow__edge-path',
`!stroke-2 ${selected ? '!stroke-pink-600' : '!stroke-slate-400'}`,
])}
onClick={(e) => {
if (e.detail === 2) {
console.log("hello world");
// openRelationshipInEditor();
}
}}
/>
<path
d={edgePath}
fill="none"
strokeOpacity={0}
strokeWidth={20}
// eslint-disable-next-line tailwindcss/no-custom-classname
className="react-flow__edge-interaction"
onClick={(e) => {
if (e.detail === 2) {
// openRelationshipInEditor();
}
}}
/>
</>)
}
+167
View File
@@ -0,0 +1,167 @@
import { Table as TableType } from "@/lib/interfaces/table";
import { Handle, Node, NodeProps, NodeResizer, Position } from "@xyflow/react";
import React, { useCallback, useState } from 'react';
import { Button, Card, CardBody, CardHeader, cn, Divider, Input } from "@heroui/react";
import {
ChevronsLeftRight,
ChevronsRightLeft,
Table2,
ChevronDown,
ChevronUp,
Check,
CircleDotDashed,
SquareDot,
SquarePlus,
SquareMinus,
Divide,
} from 'lucide-react';
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/tooltip/tooltip";
import { Field } from "@/lib/interfaces/field";
import FieldComponent from "./field";
export const MAX_TABLE_SIZE = 450;
export const MID_TABLE_SIZE = 337;
export const MIN_TABLE_SIZE = 224;
export const TABLE_MINIMIZED_FIELDS = 10;
export type TableProps = Node<{
table: TableType, isOverlapping?: boolean;
highlightOverlappingTables?: boolean;
}>
const Table: React.FC<NodeProps<TableProps>> = React.memo(({
selected,
dragging,
id,
data: { table, },
}) => {
const [editMode, setEditMode] = useState<boolean>(false);
return (
<div className={cn(
"w-full h-full bg-background rounded-lg entity-card border-2 noselect ",
selected
? ' border-2 border-primary-500'
: '',
)}>
<div
className="h-2 rounded-t-[6px] "
style={{ backgroundColor: "rgb(255, 107, 138)" }}
></div>
<div className="group gap-2 flex h-9 items-center justify-between bg-default px-2 dark:bg-default">
<Table2 className="size-3.5 shrink-0 text-gray-600 dark:text-primary" />
{
editMode && <>
<input
placeholder={table.name}
autoFocus
onBlur={() => setEditMode(false)}
type="text"
className="rounded-md px-2 py-0.5 w-full border-[0.5px] border-blue-400 bg-slate-100 focus-visible:ring-0 dark:bg-slate-900 text-sm "
/>
<Button
variant="light"
className="size-6 p-0 text-slate-500 hover:bg-primary-foreground hover:text-slate-700 dark:text-slate-400 dark:hover:bg-slate-800 dark:hover:text-slate-200"
size="sm"
onClick={() => setEditMode(false)}
isIconOnly
>
<Check className="size-3" />
</Button>
</>
}
{
!editMode &&
<>
<Tooltip>
<TooltipTrigger asChild>
<label
className=" w-full text-editable truncate px-2 py-0.5 text-sm font-bold"
onDoubleClick={() => setEditMode(true)}
>
{table.name}
</label>
</TooltipTrigger>
<TooltipContent>
Double click to edit
</TooltipContent>
</Tooltip>
<div className="hidden shrink-0 flex-row group-hover:flex">
<Button
variant="light"
size="sm"
className="size-6 p-0 text-slate-500 hover:bg-primary-foreground hover:text-slate-700 dark:text-slate-400 dark:hover:bg-slate-800 dark:hover:text-slate-200"
isIconOnly
>
<CircleDotDashed className="size-4" />
</Button>
<Button
variant="light"
size="sm"
className="size-6 p-0 text-slate-500 hover:bg-primary-foreground hover:text-slate-700 dark:text-slate-400 dark:hover:bg-slate-800 dark:hover:text-slate-200"
isIconOnly
>
{table.width !== MAX_TABLE_SIZE ? (
<ChevronsLeftRight className="size-4" />
) : (
<ChevronsRightLeft className="size-4" />
)}
</Button>
</div>
</>
}
</div>
<div>
<div
className="transition-[max-height] duration-200 ease-in-out"
>
{table.fields.map((field: Field) => (
<FieldComponent
key={field.id}
field={field}
showHandles={selected}
/>
))}
</div>
</div>
</div>
)
});
Table.displayName = 'table';
export default Table;
/*
focused = { false}
tableNodeId={id}
field={field}
highlighted={selectedRelEdges.some(
(edge) =>
edge.data?.relationship
.sourceFieldId === field.id ||
edge.data?.relationship
.targetFieldId === field.id
)}
visible={visibleFields.includes(field)}
isConnectable={!table.isView}
*/
+21
View File
@@ -0,0 +1,21 @@
import type { NavigateOptions } from "react-router-dom";
import { HeroUIProvider } from "@heroui/system";
import { useHref, useNavigate } from "react-router-dom";
declare module "@react-types/shared" {
interface RouterConfig {
routerOptions: NavigateOptions;
}
}
export function Provider({ children }: { children: React.ReactNode }) {
const navigate = useNavigate();
return (
<HeroUIProvider navigate={navigate} useHref={useHref}>
{children}
</HeroUIProvider>
);
}
+13
View File
@@ -0,0 +1,13 @@
import { Navigate, Route, Routes } from 'react-router-dom';
import useDashboardRoutes from './dashboard-route';
const useAppRoutes = () => {
const dashboardRoutes = useDashboardRoutes();
return (
<Routes>
{dashboardRoutes}
</Routes>
);
};
export default useAppRoutes;
+20
View File
@@ -0,0 +1,20 @@
import DashboardPage from '@/pages/dashboard/dashboard-page';
import DatabasePage from '@/pages/database/database-page';
import { Route } from 'react-router-dom';
import useDatabaseRoutes from './database-route';
const useDashboardRoutes = () => {
const databaseRoutes = useDatabaseRoutes() ;
return (
<Route path="" element={<DashboardPage />}>
{databaseRoutes}
</Route>
);
};
export default useDashboardRoutes;
+20
View File
@@ -0,0 +1,20 @@
import DashboardPage from '@/pages/dashboard/dashboard-page';
import DatabasePage from '@/pages/database/database-page';
import RelationshipController from '@/pages/database/db-controller/relationship-controller/relationship-controller';
import TablesController from '@/pages/database/db-controller/tables-controller/tables-controller';
import { Navigate, Route, Routes } from 'react-router-dom';
const useDatabaseRoutes = () => {
return (
<Route path="/database" element={<DatabasePage />} >
<Route path='tables' element={<TablesController />}></Route>
<Route path='relationships' element={<RelationshipController />}></Route>
<Route path='bot' element={<DatabasePage />}></Route>
</Route>
);
};
export default useDatabaseRoutes;
+51
View File
@@ -0,0 +1,51 @@
.react-flow__attribution {
display: none;
}
.text-editable {
@apply dark:group-hover:bg-slate-900 group-hover:bg-slate-100 group-hover:ring-[0.5px] rounded-md cursor-pointer;
}
.react-flow__node.selected {
transition: box-shadow 0.3s ease;
/* Add transition */
/* box-shadow: 0px 0px 15px 0px hsl(var(--heroui-secondary-50)), 0px 2px 30px 0px hsl(var(--heroui-secondary-50)), 0px 0px 1px 0px hsl(var(--heroui-secondary-50));
*/
}
.noselect {
user-select: none;
-webkit-user-select: none;
/* Safari */
-moz-user-select: none;
/* Firefox */
-ms-user-select: none;
/* IE10+/Edge */
}
.sidebar-item[data-active="true"] svg {
color : hsl(var(--heroui-primary-900));
}
div[data-slot="input-wrapper"] {
outline : none ;
border-width: 1px;
}
div[data-slot="mainWrapper"] button[data-slot="trigger"] {
outline : none ;
border-width: 1px;
}
div[data-slot="content"] hr[role="separator"] {
display: none ;
}
+5
View File
@@ -0,0 +1,5 @@
import { SVGProps } from "react";
export type IconSvgProps = SVGProps<SVGSVGElement> & {
size?: number;
};
+1
View File
@@ -0,0 +1 @@
/// <reference types="vite/client" />
+69
View File
@@ -0,0 +1,69 @@
import { colors, heroui } from "@heroui/theme";
/** @type {import('tailwindcss').Config} */
export default {
content: [
"./index.html",
"./src/**/*.{js,ts,jsx,tsx}",
"./node_modules/@nextui-org/theme/dist/**/*.{js,ts,jsx,tsx}",
"./node_modules/@heroui/theme/dist/components/(button|code|dropdown|input|kbd|link|navbar|snippet|toggle|popover|ripple|spinner|menu|divider|form).js",
],
darkMode: "class",
plugins: [heroui({
themes: {
dark: {
colors: {
sidebar: {
DEFAULT: 'hsl(0 0% 98%)',
foreground: 'hsl(240 5.3% 26.1%)',
},
primary: {
DEFAULT: "#961FFE",
50: '#961FFE0D', // 5% opacity
100: '#961FFE1A', // 10% opacity
200: '#961FFE33', // 20% opacity
300: '#961FFE4D', // 30% opacity
400: '#961FFE66', // 40% opacity
500: '#961FFE80', // 50% opacity
600: '#961FFE99', // 60% opacity
700: '#961FFEB3', // 70% opacity
800: '#961FFFCC', // 80% opacity
900: '#961FFEE6', // 90% opacity
},
},
},
light: {
colors: {
sidebar: {
DEFAULT: 'hsl(0 0% 98%)',
foreground: 'hsl(240 5.3% 26.1%)',
},
default: {
DEFAULT: 'hsl(210 40% 96.1%)',
foreground: 'hsl(222.2 47.4% 11.2%)'
},
icon : {
DEFAULT : "hsl(215.4 16.3% 46.9%)"
} ,
primary: {
DEFAULT: "#961FFE",
50: '#961FFE0D', // 5% opacity
100: '#961FFE1A', // 10% opacity
200: '#961FFE33', // 20% opacity
300: '#961FFE4D', // 30% opacity
400: '#961FFE66', // 40% opacity
500: '#961FFE80', // 50% opacity
600: '#961FFE99', // 60% opacity
700: '#961FFEB3', // 70% opacity
800: '#961FFFCC', // 80% opacity
900: '#961FFEE6', // 90% opacity
},
},
},
},
}),],
};
+28
View File
@@ -0,0 +1,28 @@
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
"paths": {
"@/*": ["./src/*"]
},
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}
+11
View File
@@ -0,0 +1,11 @@
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true,
"strict": true
},
"include": ["vite.config.ts"]
}
+5
View File
@@ -0,0 +1,5 @@
{
"rewrites": [
{ "source": "/(.*)", "destination": "/" }
]
}
+11
View File
@@ -0,0 +1,11 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import tsconfigPaths from 'vite-tsconfig-paths'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react(), tsconfigPaths()],
server : {
port : 3000
}
})