3 Commits

Author SHA1 Message Date
KarimTamani eb6c80417c 1.4.0 2026-06-10 22:12:23 +01:00
KarimTamani 5cce8cc03d 1.3.3 2026-06-10 22:12:05 +01:00
KarimTamani caa277577a Improved UI/UX + Lazy Loading 2026-06-10 21:43:46 +01:00
25 changed files with 1325 additions and 848 deletions
+857 -483
View File
File diff suppressed because it is too large Load Diff
+3 -3
View File
@@ -1,7 +1,7 @@
{
"name": "StackRender",
"private": true,
"version": "1.0.2",
"version": "1.4.0",
"type": "module",
"scripts": {
"dev": "vite",
@@ -86,7 +86,7 @@
"@types/react-dom": "18.3.0",
"@typescript-eslint/eslint-plugin": "8.11.0",
"@typescript-eslint/parser": "8.11.0",
"@vitejs/plugin-react": "^6.0.2",
"@vitejs/plugin-react": "^4.2.1",
"autoprefixer": "10.4.19",
"esbuild": "^0.28.0",
"eslint": "^8.57.0",
@@ -102,7 +102,7 @@
"rollup": "^4.61.1",
"tw-animate-css": "^1.3.6",
"typescript": "5.6.3",
"vite": "^8.0.3",
"vite": "^5.2.0",
"vite-plugin-top-level-await": "^1.5.0",
"vite-tsconfig-paths": "^4.3.2"
}
+2 -3
View File
@@ -41,14 +41,13 @@ const Clipboard: React.FC<ClipboardProps> = ({ text }) => {
}, [text])
return (
<Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<span>
<Button
size="icon"
variant="outline"
className="size-8"
className="w-7 h-7 text-muted-foreground bg-card/50 backdrop-blur-xs dark:backdrop-blur-md shadow-lg"
onClick={copyToClipboard}
>
{
+130
View File
@@ -0,0 +1,130 @@
import React, { useEffect, useState } from "react";
import { useTheme } from "@/providers/theme-provider/theme-provider";
import { cn } from "@/lib/utils";
import { Spinner } from "./ui/shadcn-io/spinner";
interface CodeEditorProps {
defaultValue?: string;
value?: string;
className?: string | undefined,
readOnly?: boolean,
onChange?: (sql: string) => void
}
const CodeEditor: React.FC<CodeEditorProps> = ({
defaultValue,
value,
className,
readOnly = false,
onChange
}) => {
const [editor, setEditor] = useState<any>(null);
useEffect(() => {
let mounted = true;
Promise.all([
import("@uiw/react-codemirror"),
import("@codemirror/lang-sql"),
import("@codemirror/view"),
]).then(([cm, sqlLang, view]) => {
if (!mounted) return;
const overrideDarkTheme = view.EditorView.theme({
'.cm-content': {
backgroundColor: "#1c2025",
},
".cm-gutter": {
backgroundColor: "#1c2025",
},
".cm-gutterElement": {
color: "#4b515a"
},
".ͼp": {
color: "#A994FF"
},
".cm-line .ͼq": {
color: "#ff6363"
},
".ͼu": {
color: "#B6E672"
},
".ͼv": {
color: "#6cdcc4"
}
}, { dark: true });
const overrideLightTheme = view.EditorView.theme({
".ͼb": {
color: "#2A1D66"
},
".cm-gutterElement": {
color: "#62748e"
},
".cm-gutter": {
backgroundColor: "white",
},
".cm-gutters": {
borderColor: "#f2f4f6"
},
".cm-line": {
color: "#0f172b"
},
})
setEditor({
CodeMirror: cm.default,
oneDark: cm.oneDark,
sql: sqlLang.sql,
overrideDarkTheme,
overrideLightTheme,
});
});
return () => {
mounted = false;
};
}, []);
const { theme } = useTheme();
if (!editor) {
return (
<div
className={cn(
"flex flex-1 w-full min-h-9 rounded-sm border border-border bg-card items-center justify-center",
className
)}
>
<Spinner className="text-primary" />
</div>
);
}
const { CodeMirror, oneDark, sql, overrideDarkTheme, overrideLightTheme } = editor;
return (
<CodeMirror
defaultValue={defaultValue}
value={value}
className={cn("flex flex-1 w-full min-h-9 rounded-sm h-full bg-card border-1 border-border !min-w-0 overflow-hidden", className)}
extensions={[sql()]}
readOnly={readOnly}
theme={theme != "dark" ? overrideLightTheme : [oneDark, overrideDarkTheme]}
onChange={onChange}
/>
)
}
export default React.memo(CodeEditor);
@@ -16,11 +16,11 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
const sidebarData = useSidebarData();
const { openController } = useDiagramOps();
return (
<Sidebar collapsible='icon' variant='floating' {...props} className='bg-card border-r' >
<Sidebar collapsible='icon' {...props} >
<SidebarHeader>
<div className='flex items-center'>
<div className='flex items-center gap-2'>
<img
className='w-8 h-8 p-[6px] rounded-md '
className='w-8 p-[4px] rounded-md '
src='/stackrender.png'
/>
<h3 className='data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground truncate font-semibold text-sm '>
+16 -21
View File
@@ -1,7 +1,6 @@
import { Outlet } from "react-router-dom";
import { SidebarProvider } from "@/components/ui/sidebar";
import { cn } from "@/lib/utils";
import { SidebarInset, SidebarProvider } from "@/components/ui/sidebar";
import { AppSidebar } from "@/components/layout/app-sidebar/app-sidebar";
import { Header } from "@/components/header";
@@ -14,24 +13,20 @@ interface Props {
const Dashboard: React.FC<Props> = ({ children }) => {
return (
<SidebarProvider defaultOpen={false}>
<AppSidebar />
<div
id='content'
className={cn(
'ml-auto w-full max-w-full',
'peer-data-[state=collapsed]:w-[calc(100%-var(--sidebar-width-icon)-1rem)]',
'peer-data-[state=expanded]:w-[calc(100%-var(--sidebar-width))]',
'sm:transition-[width] sm:duration-200 sm:ease-linear',
'flex h-svh flex-col',
'group-data-[scroll-locked=1]/body:h-full',
'has-[main.fixed-main]:group-data-[scroll-locked=1]/body:h-svh'
)}
>
<Header className="bg-card border-b pl-3"/>
{
children ? children : <Outlet />
}
</div>
<AppSidebar variant="inset" />
{/* dark:p-0 dark:border-l-1 */}
<SidebarInset className=" overflow-hidden max-h-screen flex flex-col flex-1 !m-0 !rounded-none bg-sidebar p-1 ">
{/*dark:border-none dark:rounded-none */}
<div className="border-4 border-sidebar-border ring-1 ring-border/30 dark:!border-background dark:ring-sidebar-border flex flex-col min-h-0 !overflow-hidden rounded-2xl h-screen">
<div className="overflow-hidden h-full flex flex-col flex-1 bg-white dark:bg-background dark:!border-1 dark:border-sidebar-border dark:rounded-[14px] !overflow-hidden" >
<Header className="bg-card border-b pl-3" />
{
children ? children : <Outlet />
}
</div>
</div>
</SidebarInset>
</SidebarProvider>
)
@@ -48,8 +48,8 @@ const CardinalityMarker: React.FC<CardinalityMarkerProps> = ({ selected = false,
>
<svg
fill="transparent"
className={selected ? "stroke-ring dark:!stroke-primary-foreground" : "stroke-ring/60 dark:!stroke-muted-foreground/60"}
strokeWidth="4"
className={selected ? ' !stroke-[oklch(0.60_0.028_263.3984)] dark:!stroke-primary-foreground' : 'stroke-[oklch(0.72_0.022_263.3984)] dark:!stroke-muted-foreground'}
strokeWidth="6"
width="24"
height="24"
viewBox="0 0 100 100">
@@ -74,24 +74,24 @@ const CardinalityMarker: React.FC<CardinalityMarkerProps> = ({ selected = false,
<circle
cx="12"
cy="12"
r="8"
r="6"
strokeWidth="1"
className={
cn(" fill-background",
selected ? " stroke-ring fill-background dark:!stroke-primary-foreground" :
" stroke-ring/60 dark:!stroke-muted-foreground "
cn(" fill-background ",
selected ? " stroke-[oklch(0.60_0.028_263.3984)] fill-background dark:!stroke-primary-foreground" :
" stroke-[oklch(0.72_0.022_263.3984)] dark:!stroke-muted-foreground "
)
}
/>
<text
x="12"
y="13"
y="12.5"
textAnchor="middle"
dominantBaseline="middle"
fontSize="8"
fontSize="6"
className={
cn("fill-ring/60 font-semibold dark:!fill-muted-foreground" ,
selected ? "fill-ring dark:!fill-primary-foreground" : "" ,
cn("fill-[oklch(0.72_0.022_263.3984)] font-semibold dark:!fill-muted-foreground" ,
selected ? "fill-[oklch(0.60_0.028_263.3984)] dark:!fill-primary-foreground" : "" ,
)
}
>
@@ -9,7 +9,7 @@ import { useDatabaseHistory } from "@/providers/database-history/database-histor
import { useOnViewportChange, useReactFlow } from "@xyflow/react";
import { LayoutGrid, Redo, Scan, Undo, ZoomIn, ZoomOut } from "lucide-react";
import { useCallback, useState } from "react";
import { useCallback, useState } from "react";
import { useTranslation } from "react-i18next";
interface DbControlButtons {
@@ -58,8 +58,8 @@ const DatabaseControlButtons: React.FC<DbControlButtons> = ({ adjustPositions })
}, []);
return (
<div className="flex !rounded-md p-2 border-1 !overflow-hidden !bg-background/20 dark:!bg-card/20 text-muted-foreground shadow-md backdrop-blur-sm"
>
<div className="flex !rounded-md p-2 border-1 !overflow-hidden !bg-input/50 dark:!bg-card/50 text-muted-foreground shadow-md backdrop-blur-xs dark:backdrop-blur-md">
<Tooltip>
<TooltipTrigger asChild>
<span>
@@ -52,6 +52,7 @@ import { Modals } from "@/providers/modal-provider/modal-contxet";
import { useModal } from "@/providers/modal-provider/modal-provider";
import { useIsMobile } from "@/hooks/use-mobile";
import { cn } from "@/lib/utils";
import useToast from "@/hooks/use-toast";
@@ -72,7 +73,7 @@ const DatabaseDiagram: React.FC = () => {
// Diagram-related state (e.g. connection in progress)
const { setIsConnectionInProgress, cardinalityStyle, showController, openController } = useDiagramOps();
const raise = useToast() ;
// Destructure tables and relationships from database
const { tables, relationships } = database || { tables: [], relationships: [] };
@@ -145,13 +146,11 @@ const DatabaseDiagram: React.FC = () => {
} else {
toast(t("db_controller.invalid_relationship.title"), {
description: t("db_controller.invalid_relationship.description"),
classNames: {
description: "!text-destructive",
title: "!text-destructive"
},
})
raise(
t("db_controller.invalid_relationship.title"),
t("db_controller.invalid_relationship.description"),
"ERROR"
)
}
setIsConnectionInProgress(false);
@@ -266,17 +265,18 @@ const DatabaseDiagram: React.FC = () => {
</Controls >
<MiniMap
nodeStrokeWidth={4}
className="!bg-background border-1 rounded-lg overflow-hidden "
maskStrokeColor={resolvedTheme == "dark" ? "#FFFFFF1A" : "#e2e8f0"}
maskColor={resolvedTheme == "dark" ? "#21262d77" : "#62748e05"}
className="!bg-input/50 ring-1 ring-border shadow-sm rounded-md overflow-hidden shadow-md backdrop-blur-xs dark:!bg-background/50 "
maskStrokeColor={resolvedTheme == "dark" ? "#FFFFFF1A" : "#cad5e2"}
maskColor={resolvedTheme == "dark" ? "#21262d88" : "#f1f5f9cc"}
maskStrokeWidth={1}
nodeClassName={"!fill-muted-foreground/20 "}
nodeClassName={"!fill-foreground/20 "}
style={{
width: 164,
width: 148,
height: 128
}}
/>
<Background color="#62748e" className="dark:!bg-background " />
<Background color="#62748e" className="!bg-background dark:!bg-background" />
</ReactFlow>
<div
@@ -52,7 +52,7 @@ const CircularDependencyAlert: React.FC<CircularDependencyAlertProps> = ({ error
deleteRelationship(id)
}, [])
return (
<div className="flex flex-col gap-2 w-full h-full items-center pt-12 ">
<div className="flex flex-col gap-2 w-full h-full items-center pt-12 ">
<AlertTriangle
className="size-12 text-destructive"
/>
@@ -63,14 +63,14 @@ const CircularDependencyAlert: React.FC<CircularDependencyAlertProps> = ({ error
{t("db_controller.circular_dependency.description")} , <span className="font-medium text-foreground"> {t("db_controller.circular_dependency.suggestion")} </span>
</p>
<ul aria-label="Relationships" className="w-[80%] max-w-[360px]"
<ul aria-label="Relationships" className="w-[80%] max-w-[360px] mt-4"
>
{
circularRelationships.map((relationship: RelationshipType) => (
<li
onClick={() => focus(relationship.id)}
key={relationship.id}>
<div className="hover:bg-secondary flex items-center justify-between h-10 mb-2 px-3 rounded-md cursor-pointer">
<div className="hover:bg-secondary flex items-center justify-between h-10 mb-2 px-3 rounded-md cursor-pointer bg-card border-1">
<span className="text-sm">
{relationship.sourceTable.name} -&gt; {relationship.targetTable.name}
</span>
@@ -1,34 +1,33 @@
import { useDatabase, useDatabaseOperations } from "@/providers/database-provider/database-provider";
import React, { useEffect, useMemo, useState } from "react";
import CodeMirror from '@uiw/react-codemirror';
import { sql } from '@codemirror/lang-sql';
import { oneDark } from '@codemirror/theme-one-dark';
import { overrideDarkTheme, overrideLightTheme } from "@/lib/colors";
import { DatabaseType } from "@/lib/schemas/database-schema";
import CircularDependencyAlert from "./circular-dependecy-alert";
import { useTranslation } from "react-i18next";
import Clipboard from "@/components/clipboard";
import { TableType } from "@/lib/schemas/table-schema";
import { RelationshipType } from "@/lib/schemas/relationship-schema";
import { useTheme } from "@/providers/theme-provider/theme-provider";
import { toast } from "sonner";
import BaseDatabaseRenderer from "@/utils/render/database/base-database-renderer";
import { CircularDependencyError, getRenderer } from "@/utils/render/render-uttils";
import { areArraysEqual } from "@/utils/utils";
import CodeEditor from "@/components/code-editor";
import useToast from "@/hooks/use-toast";
interface SqlPreviewProps {
tableFilterIds?: string[]
tableFilterIds?: string[] ,
className? : string ;
}
const SqlPreview: React.FC<SqlPreviewProps> = ({ tableFilterIds }) => {
const SqlPreview: React.FC<SqlPreviewProps> = ({ tableFilterIds , className}) => {
const { database: currentDatabase } = useDatabase();
const { data_types } = useDatabaseOperations();
const [sqlCode, setSqlCode] = useState<string>("");
const [sqlCode, setSqlCode] = useState<string>("");
const raise = useToast();
const [circularDependency, setCircularDependency] = useState<CircularDependencyError | undefined>(undefined);
const database = useMemo(() => {
@@ -42,19 +41,18 @@ const SqlPreview: React.FC<SqlPreviewProps> = ({ tableFilterIds }) => {
),
} as DatabaseType;
}, [currentDatabase, tableFilterIds])
const { theme } = useTheme();
const { t } = useTranslation();
useEffect(() => {
(async () => {
if (database?.dialect && data_types.length > 0) {
try {
const renderer: BaseDatabaseRenderer = getRenderer(database.dialect, data_types);
const sql: string = await renderer.renderDDL(database)
setSqlCode(sql);
setCircularDependency(undefined);
@@ -76,35 +74,34 @@ const SqlPreview: React.FC<SqlPreviewProps> = ({ tableFilterIds }) => {
}, [database, data_types]);
useEffect(() => {
if (circularDependency)
toast(t("db_controller.circular_dependency.title"), {
description: t("db_controller.circular_dependency.description"),
classNames: {
description: "!text-destructive",
title: "!text-destructive"
},
});
raise(
t("db_controller.circular_dependency.title"),
t("db_controller.circular_dependency.description"),
"ERROR"
);
}, [circularDependency]);
if (circularDependency)
return <CircularDependencyAlert error={circularDependency} />
else
return (
<div className="flex w-full h-full relative ">
<div className="absolute right-[12px] top-[4px] z-[1] ">
<div className="flex w-full h-full relative min-w-0 !min-h-0 ">
<div className="absolute right-2 top-2 z-1">
<Clipboard
text={sqlCode}
/>
</div>
<CodeMirror
<CodeEditor
defaultValue={sqlCode}
value={sqlCode}
className="flex flex-1 w-full "
extensions={[sql()]}
readOnly
theme={theme != "dark" ? overrideLightTheme : [oneDark, overrideDarkTheme]}
className={ className}
/>
</div>
)
@@ -206,9 +206,10 @@ const TablesController: React.FC = ({ }) => {
}
{
allTables.length > 0 && showSqlPreview &&
<div className=" flex-1 overflow-auto -ml-3">
<div className=" flex-1 overflow-auto ">
<SqlPreview
tableFilterIds={tableFilterIds}
className="rounded-none border-0 border-t-1"
/>
</div>
}
+16 -10
View File
@@ -17,7 +17,8 @@ interface Props {
field: FieldType,
showHandles?: boolean,
highlight?: boolean,
color?: string
color?: string,
className?: string
}
@@ -28,7 +29,7 @@ export const TARGET_PREFIX = "target_";
const Field: React.FC<Props> = (props) => {
const { field, showHandles, highlight, color } = props;
const { field, showHandles, highlight, color, className } = props;
const [editMode, setEditMode] = useState<boolean>(false);
const { deleteField, editField } = useDatabaseOperations();
const [fieldName, setFieldName] = useState<string>(field.name);
@@ -39,14 +40,17 @@ const Field: React.FC<Props> = (props) => {
}, [field.name]);
const removeField = useCallback(() => {
deleteField(field.id)
}, [])
}, [field.id])
const saveFieldName = useCallback(() => {
editField({
id: field.id,
name: fieldName
} as FieldType);
setEditMode(false);
}, [fieldName]);
@@ -54,16 +58,17 @@ const Field: React.FC<Props> = (props) => {
return (
<div className={cn(
"group relative flex h-8 items-center justify-between gap-1 px-1.5 text-sm hover:bg-secondary transition-all duration-200 ease-in-out ",
highlight ? "bg-secondary" : ""
highlight ? "bg-secondary" : "",
className
)}>
<div className="text-muted-foreground flex items-center truncate gap-1.5">
<div className="text-muted-foreground flex items-center truncate w-full gap-1.5 min-w-0 ">
{
field.isPrimary &&
<IconKey className="size-3" />
}
{
field.nullable && !field.isPrimary &&
<IconKeyframe className="size-3" />
<IconKeyframe className="size-3 stroke-3" />
}
{
!field.nullable && !field.isPrimary &&
@@ -72,7 +77,7 @@ const Field: React.FC<Props> = (props) => {
{
!editMode ?
<label
className={"truncate flex gap-1 text-xs text-foreground "}
className={"truncate flex gap-1 text-xs text-foreground font-medium "}
onDoubleClick={() => setEditMode(true)}
>
{fieldName}
@@ -98,7 +103,7 @@ const Field: React.FC<Props> = (props) => {
onClick={(e) => e.stopPropagation()}
onChange={(e) => setFieldName(e.target.value)}
className="h-6 font-bold pb-1.5 rounded-sm px-1"
onKeyDown={(e: any) => {
onKeyDown={(e: any) => {
if (e.key === "Enter") {
e.preventDefault();
saveFieldName();
@@ -106,15 +111,15 @@ const Field: React.FC<Props> = (props) => {
}
}}
/>
}
</div>
{
!editMode ?
<div className="text-xs text-muted-foreground flex shrink-0 ">
<div className="group-hover:opacity-0">
<div className={cn(" !text-muted-foreground font-medium group-hover:opacity-0")}>
{field.type?.name?.split(' ')[0]}
</div>
<div className="flex gap-1 opacity-0 shrink-0 flex-row group-hover:opacity-100 transition-opacity duration-200 absolute right-1 top-1 ">
<Button variant="outline" size="icon" className="size-6 shrink-0 shadow-sm rounded-sm" onClick={() => setEditMode(true)}>
<IconPencil className="size-3 text-muted-foreground " />
@@ -123,6 +128,7 @@ const Field: React.FC<Props> = (props) => {
<IconTrash className="size-3 text-destructive " />
</Button>
</div>
</div>
:
<div className="text-xs text-muted-foreground flex ml-1">
@@ -80,7 +80,7 @@ const Relationship: React.FC<EdgeProps<RelationshipProps>> = (props) => {
sourceY,
targetX: targetSide === 'left' ? targetLeftX : targetRightX,
targetY,
borderRadius: 6,
borderRadius: 8,
sourcePosition:
sourceSide === 'left' ? Position.Left : Position.Right,
targetPosition:
@@ -125,7 +125,7 @@ const Relationship: React.FC<EdgeProps<RelationshipProps>> = (props) => {
markerEnd={`url(#${endMarker})`}
fill="none"
className={cn([
`!stroke-1 ${selected ? '!stroke-ring dark:!stroke-primary-foreground' : 'stroke-ring/60 dark:!stroke-muted-foreground'}`,
`!stroke-[1.5px] ${selected ? ' !stroke-[oklch(0.60_0.028_263.3984)] dark:!stroke-primary-foreground' : 'stroke-[oklch(0.72_0.022_263.3984)] dark:!stroke-muted-foreground'}`,
])}
onClick={(e) => {
if (e.detail === 2) {
@@ -143,7 +143,6 @@ const Relationship: React.FC<EdgeProps<RelationshipProps>> = (props) => {
fill="none"
strokeOpacity={0}
strokeWidth={16}
className="react-flow__edge-interaction"
onClick={(e) => {
if (e.detail === 2) {
+18 -15
View File
@@ -56,7 +56,7 @@ const Table: React.FC<NodeProps<TableProps>> = (props) => {
const fields: React.ReactNode[] = useMemo(() => {
return table.fields.map((field: FieldType) => {
return table.fields.map((field: FieldType , index : number) => {
const highlight: boolean = highlightedEdges.find((edge: any) =>
(edge.data?.relationship as RelationshipType).sourceFieldId == field.id ||
@@ -68,6 +68,7 @@ const Table: React.FC<NodeProps<TableProps>> = (props) => {
showHandles={selected}
highlight={highlight}
color={table.color as string}
className={ index == table.fields.length - 1 ? "!rounded-b-md" : undefined }
/>)
})
}, [table.fields, selected, highlightedEdges]);
@@ -78,11 +79,11 @@ const Table: React.FC<NodeProps<TableProps>> = (props) => {
}, []);
return (
<Card
<Card
className={cn(
"rounded-[12px] p-0.5 gap-0 transition-all duration-200",
"rounded-lg p-0.5 gap-0 transition-all duration-200 border-none ring-1 ring-slate-300 shadow-xs dark:ring-border ",
overlapping
? 'ring-1 ring-destructive scale-105 shadow-danger '
? 'ring-1 !ring-destructive scale-105 shadow-danger '
: '',
!pulsing && overlapping
? 'scale-105'
@@ -91,23 +92,23 @@ const Table: React.FC<NodeProps<TableProps>> = (props) => {
? 'scale-110'
: '',
selected && !overlapping ? "ring-1 ring-primary" : ""
selected && !overlapping ? "ring-[1.5px] !ring-primary" : ""
)}
style={
(selected && table.color && !overlapping) ?
{
boxShadow: "0 0 0 1px " + table.color,
boxShadow: "0 0 0 1.5px " + table.color,
}
: undefined
}
onDoubleClick={focus}
>
<CardHeader className="group rounded-t rounded-t-md p-1.5 flex items-center mb-0 bg-primary/10 "
<CardHeader className="group rounded-t rounded-t-md p-1.5 border-1 border-primary/20 flex items-center mb-0 bg-primary/10 "
style={
table.color ? {
backgroundColor: table.color + "20" as string
backgroundColor: table.color + "20" as string,
border: " 1px solid " + table.color + "60"
} : undefined
}
>
@@ -115,15 +116,17 @@ const Table: React.FC<NodeProps<TableProps>> = (props) => {
{!editMode ? <>
<label
className=" w-full text-editable truncate py-0.5 text-sm font-bold text-primary"
onDoubleClick={() => setEditMode(true)}
onDoubleClick={ () => setEditMode(true) }
style={{ color: table.color as string }}
>
{tableName}
</label>
<div className="flex gap-1 hidden shrink-0 flex-row group-hover:flex ">
<Button variant="outline" size="icon" className="size-6 shrink-0 shadow-sm rounded-sm" onClick={() => setEditMode(true)}>
<IconPencil className="size-3 text-muted-foreground " />
</Button>
<Button variant="outline" size="icon" className="size-6 shrink-0 shadow-sm rounded-sm" onClick={() => setEditMode(true)}>
<IconPencil className="size-3 text-muted-foreground " />
</Button>
<Button variant="outline" size="icon" className="size-6 shrink-0 shadow-sm rounded-sm" onClick={focus}>
<IconFocus2 className="size-3 text-muted-foreground " />
</Button>
@@ -159,14 +162,14 @@ const Table: React.FC<NodeProps<TableProps>> = (props) => {
}
</CardHeader>
<CardContent className="p-0 mt-0">
<CardContent className="p-0 mt-0 rounded-b-md ">
{
!showMore ? fields.slice(0, MAX_FIELDS) : fields
}
{fields.length > MAX_FIELDS && (
<div
className="flex h-8 cursor-pointer items-center gap-1 justify-center text-xs transition-colors duration-200 "
className="flex h-8 cursor-pointer items-center gap-1 justify-center text-xs transition-colors duration-200 "
onClick={toggleShowMore}
>
{showMore ? (
@@ -5,25 +5,28 @@ import Modal, { ModalProps } from "@/components/modal";
import React from "react";
import { useTranslation } from "react-i18next";
import SqlPreview from "../components/db-controller/sql-preview";
import { Separator } from "@/components/ui/separator";
const ExportSqlModal: React.FC<ModalProps> = ({ isOpen, onOpenChange }) => {
const { t } = useTranslation();
return (
<Modal
<Modal
isOpen={isOpen}
onOpenChange={onOpenChange}
title={t("modals.export_sql")}
className="w-full lg:min-w-[960px]"
className="w-full lg:min-w-[960px] "
description={t("modals.export_sql_header")}
>
<div className="h-full max-h-[72vh] min-w-0">
<Separator />
<div className="h-full min-h-[65vh] max-h-[65vh] min-w-0 !bg-background p-1 border-1 rounded-md shadow-xs" >
<SqlPreview />
</div>
<Separator />
</Modal>
)
};
@@ -1,9 +1,6 @@
import Modal, { ModalProps } from "@/components/modal"
import ReactCodeMirror, { oneDark } from "@uiw/react-codemirror";
import { useCallback, useEffect, useMemo, useState } from "react"
import { useTranslation } from "react-i18next"
import { overrideDarkTheme, overrideLightTheme } from "@/lib/colors";
import { sql } from '@codemirror/lang-sql';
import { DatabaseDialect, ImportDatabaseMethod, ImportDatabaseOption, ImportMethodType, MARIADB_DUMP_EXAMPLE, MARIADB_DUMP_INSTRUCTIONS, MYSQL_DUMP_EXAMPLE, MYSQL_DUMP_INSTRUCTIONS, PG_DUMP_EXAMPLE, PG_DUMP_INSTRUCTIONS, SQLITE_DUMP_EXAMPLE, SQLITE_DUMP_INSRUCTION } from "@/lib/database";
import { useDatabase, useDatabaseOperations } from "@/providers/database-provider/database-provider";
import { AlertCircleIcon, Code, HelpCircle } from "lucide-react";
@@ -20,6 +17,7 @@ import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { buttonVariants } from "@/components/ui/button";
import { BaseSqlImporter } from "@/utils/import/base-sql-importer";
import { getImporter } from "@/utils/import/import-utils";
import CodeEditor from "@/components/code-editor";
const options: ImportDatabaseOption[] = [{
@@ -180,8 +178,7 @@ const ImportDatabaseModal: React.FC<ModalProps> = ({ isOpen, onOpenChange }) =>
setParsedDatabase(parsedDatabase);
setError(false);
} catch (error) {
} catch (error) {
setError(true);
setParsedDatabase(undefined)
}
@@ -235,7 +232,7 @@ const ImportDatabaseModal: React.FC<ModalProps> = ({ isOpen, onOpenChange }) =>
return;
return (
<Modal
<Modal
isOpen={isOpen}
onOpenChange={onOpenChange}
title={t("modals.import_database.title")}
@@ -245,7 +242,7 @@ const ImportDatabaseModal: React.FC<ModalProps> = ({ isOpen, onOpenChange }) =>
isDisabled={!parsedDatabase}
>
<div className="flex flex-col gap-4 !min-w-0 ">
<div className="w-full space-y-2 !min-w-0 ">
<div className="w-full space-y-2 min-w-0 ">
<p className="text-sm text-muted-foreground">
{t("modals.import_database.import_options")}
</p>
@@ -298,6 +295,7 @@ const ImportDatabaseModal: React.FC<ModalProps> = ({ isOpen, onOpenChange }) =>
</RadioGroup>
}
</div>
{
selectedImportMethod?.type == ImportMethodType.DUMP &&
@@ -316,7 +314,7 @@ const ImportDatabaseModal: React.FC<ModalProps> = ({ isOpen, onOpenChange }) =>
<div className=" font-normal w-full">
<span className="border-1 border-border p-1 px-2 rounded-md w-full flex items-center justify-between">
{selectedImportMethod.instruction}
<Clipboard text={selectedImportMethod.example} />
<Clipboard text={selectedImportMethod.instruction} />
</span>
</div>
@@ -362,18 +360,17 @@ const ImportDatabaseModal: React.FC<ModalProps> = ({ isOpen, onOpenChange }) =>
}
</div>
<div className="flex flex-col ">
<ReactCodeMirror
<div className="flex flex-1 flex-col ">
<CodeEditor
className={
cn("flex w-full border-1 rounded-md border-divider overflow-hidden ",
cn("flex w-full border-1 rounded-md border-divider overflow-hidden ",
(error || parsedDatabase?.errors?.length > 0) ? "min-h-[360px] max-h-[360px]" : "min-h-[424px] max-h-[424px]"
)
}
extensions={[sql()]}
value={sqlCode}
onChange={validateSql}
theme={resolvedTheme == "light" ? overrideLightTheme : [oneDark, overrideDarkTheme]}
/>
<div className="mt-4">
{
error &&
@@ -394,13 +391,13 @@ const ImportDatabaseModal: React.FC<ModalProps> = ({ isOpen, onOpenChange }) =>
(parsedDatabase?.errors && parsedDatabase?.errors?.length > 0) &&
<Alert
variant={"default"}
className="text-chart-5 dark:text-chart-3"
className="text-chart-4"
>
<AlertTitle>
{t("modals.import_database.import_warning")}
</AlertTitle>
<AlertDescription className="text-chart-5 dark:text-chart-3">
<AlertDescription className="text-chart-4 ">
{t("modals.import_database.import_warning_description")}
</AlertDescription>
+75
View File
@@ -0,0 +1,75 @@
import { IconCircleCheck } from "@tabler/icons-react";
import { CircleAlert, OctagonX } from "lucide-react";
import { useCallback } from "react";
import { toast } from "sonner";
export type ToastVariant = "INFO" | "ERROR" | "SUCCESS";
export interface ToastAction {
title: string,
actionHandler: () => void;
}
const useToast = () => {
const raise = useCallback((title: string, description: React.ReactNode, variant: ToastVariant = "INFO", action?: ToastAction) => {
toast(title, {
description,
...toastStyles[variant],
action: action ? {
label: action.title,
onClick: action.actionHandler,
} : undefined,
});
}, []);
return raise;
}
const toastStyles = {
SUCCESS: {
classNames: {
description: "!text-chart-2"
},
style: {
'--normal-bg':
'color-mix(in oklab, light-dark(var(--color-chart-2), var(--color-chart-2)) 10%, var(--background))',
'--normal-text': 'light-dark(var(--color-chart-2), var(--color-chart-2))',
'--normal-border': 'light-dark(var(--color-chart-2), var(--color-chart-2))'
} as React.CSSProperties,
icon: <IconCircleCheck className="size-5" />
},
INFO: {
classNames: {
description: "!text-primary"
},
style: {
'--normal-bg':
'color-mix(in oklab, light-dark(var(--color-primary), var(--primary)) 10%, var(--background))',
'--normal-text': 'light-dark(var(--color-primary), var(--primary))',
'--normal-border': 'light-dark(var(--color-primary), var(--primary))'
} as React.CSSProperties,
icon: <CircleAlert className="size-4" />
},
ERROR: {
classNames: {
description: "!text-destructive"
},
style: {
'--normal-bg': 'color-mix(in oklab, var(--destructive) 10%, var(--background))',
'--normal-text': 'var(--destructive)',
'--normal-border': 'var(--destructive)'
} as React.CSSProperties ,
icon : <OctagonX className="size-4" />
}
}
export default useToast;
+118 -165
View File
@@ -1,72 +1,87 @@
@import 'tailwindcss';
@import "tailwindcss";
@import 'tw-animate-css';
@custom-variant dark (&:is(.dark *));
:root {
--radius: 0.625rem;
--background: oklch(1 0 0);
--foreground: oklch(0.129 0.042 264.695);
--card: oklch(1 0 0);
--card-foreground: oklch(0.129 0.042 264.695);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.129 0.042 264.695);
--primary: oklch(0.606 0.25 292.717);
--primary-foreground: oklch(0.969 0.016 293.756);
--secondary: oklch(0.968 0.007 247.896);
--secondary-foreground: oklch(0.208 0.042 265.755);
--muted: oklch(0.968 0.007 247.896);
--muted-foreground: oklch(0.554 0.046 257.417);
--accent: oklch(0.968 0.007 247.896);
--accent-foreground: oklch(0.208 0.042 265.755);
--destructive: oklch(0.577 0.245 27.325);
--border: oklch(0.929 0.013 255.508);
--input: oklch(0.929 0.013 255.508);
--ring: oklch(0.704 0.04 256.788);
--chart-1: oklch(0.646 0.222 41.116);
--chart-2: oklch(0.6 0.118 184.704);
--chart-3: oklch(0.398 0.07 227.392);
--chart-4: oklch(0.828 0.189 84.429);
--chart-5: oklch(0.769 0.188 70.08);
--background: oklch(0.9848 0 0);
--foreground: oklch(0.2496 0.0417 263.3984);
--card: oklch(1.0000 0 0);
--card-foreground: oklch(0.2496 0.0417 263.3984);
--popover: oklch(1.0000 0 0);
--popover-foreground: oklch(0.2496 0.0417 263.3984);
--primary: oklch(0.5424 0.2454 293.0160);
--primary-foreground: oklch(0.9843 0.0017 247.8393);
--secondary: oklch(0.9684 0.0068 247.8951);
--secondary-foreground: oklch(0.2079 0.0399 265.7275);
--muted: oklch(0.9684 0.0068 247.8951);
--muted-foreground: oklch(0.5547 0.0407 257.4404);
--accent: oklch(0.9544 0.0226 302.5680);
--accent-foreground: oklch(0.5424 0.2454 293.0160);
--destructive: oklch(0.6368 0.2078 25.3259);
--destructive-foreground: oklch(0.9843 0.0017 247.8393);
--border: oklch(0.9290 0.0126 255.5317);
--input: oklch(0.9290 0.0126 255.5317);
--ring: oklch(0.5424 0.2454 293.0160);
--chart-1: oklch(0.5424 0.2454 293.0160);
--chart-2: oklch(0.6309 0.1013 183.4907);
--chart-3: oklch(0.3787 0.0440 225.5393);
--chart-4: oklch(0.8336 0.1186 88.1463);
--chart-5: oklch(0.7834 0.1261 58.7491);
--sidebar: oklch(0.2064 0.0388 265.5472);
--sidebar-foreground: oklch(0.9838 0.0035 247.8583);
--sidebar-primary: oklch(0.5424 0.2454 293.0160);
--sidebar-primary-foreground: oklch(1.0000 0 0);
--sidebar-accent: oklch(0.2709 0.0592 265.1803);
--sidebar-accent-foreground: oklch(0.9838 0.0035 247.8583);
--sidebar-border: oklch(0.2887 0.0646 265.1126);
--sidebar-ring: oklch(0.5424 0.2454 293.0160);
--font-sans: Inter, -apple-system, sans-serif;
--font-serif: Georgia, serif;
--font-mono: JetBrains Mono, monospace;
--radius: 0.75rem;
--shadow-x: 0px;
--shadow-y: 4px;
--shadow-blur: 15px;
--shadow-spread: 0px;
--shadow-opacity: 0.05;
--shadow-color: hsl(220, 40%, 15%);
--shadow-2xs: 0px 4px 15px 0px hsl(220 40% 15% / 0.03);
--shadow-xs: 0px 4px 15px 0px hsl(220 40% 15% / 0.03);
--shadow-sm: 0px 4px 15px 0px hsl(220 40% 15% / 0.05), 0px 1px 2px -1px hsl(220 40% 15% / 0.05);
--shadow: 0px 4px 15px 0px hsl(220 40% 15% / 0.05), 0px 1px 2px -1px hsl(220 40% 15% / 0.05);
--shadow-md: 0px 4px 15px 0px hsl(220 40% 15% / 0.05), 0px 2px 4px -1px hsl(220 40% 15% / 0.05);
--shadow-lg: 0px 4px 15px 0px hsl(220 40% 15% / 0.05), 0px 4px 6px -1px hsl(220 40% 15% / 0.05);
--shadow-xl: 0px 4px 15px 0px hsl(220 40% 15% / 0.05), 0px 8px 10px -1px hsl(220 40% 15% / 0.05);
--shadow-2xl: 0px 4px 15px 0px hsl(220 40% 15% / 0.13);
--tracking-normal: -0.015em;
--spacing: 0.25rem;
--sidebar: var(--background);
--sidebar-foreground: var(--foreground);
--sidebar-primary: var(--primary);
--sidebar-primary-foreground: var(--primary-foreground);
--sidebar-accent: var(--accent);
--sidebar-accent-foreground: var(--accent-foreground);
--sidebar-border: var(--border);
--sidebar-ring: var(--ring);
}
.dark {
--background: oklch(0.240 0.011 256.8);
--foreground: oklch(0.985 0 0);
--background: oklch(0.240 0.011 256.8);
--foreground: oklch(0.9838 0.0035 247.8583);
--card: oklch(0.266 0.015 257.5);
--card-foreground: oklch(0.985 0 0);
--card-foreground: oklch(0.9838 0.0035 247.8583);
--popover: oklch(0.266 0.015 257.5);
--popover-foreground: oklch(0.985 0 0);
--primary: oklch(0.541 0.281 293.009);
--popover-foreground: oklch(0.9838 0.0035 247.8583);
--primary: oklch(0.4865 0.2423 291.8661);
--primary-foreground: oklch(0.969 0.016 293.756);
--secondary: oklch(0.295 0.015 257.7);
--secondary-foreground: oklch(0.985 0 0);
--secondary-foreground: oklch(0.9838 0.0035 247.8583);
--muted: oklch(0.295 0.015 257.7);
--muted-foreground: oklch(0.705 0.015 286.067);
--accent: oklch(0.295 0.015 257.7);
--accent-foreground: oklch(0.985 0 0);
--accent-foreground: oklch(0.5424 0.2454 293.0160);
--destructive: oklch(0.704 0.191 22.216);
--border: oklch(1 0 0 / 10%);
--input: oklch(1 0 0 / 10%);
--ring: oklch(0.541 0.281 293.009);
--chart-1: oklch(0.488 0.243 264.376);
--chart-2: oklch(0.696 0.17 162.48);
--chart-3: oklch(0.769 0.188 70.08);
--chart-4: oklch(0.627 0.265 303.9);
--chart-5: oklch(0.645 0.246 16.439);
--ring: oklch(0.4865 0.2423 291.8661);
--sidebar: oklch(0.266 0.015 257.5);
--sidebar: oklch(0.2639 0.0161 264.25);
--sidebar-foreground: oklch(0.985 0 0);
--sidebar-primary: oklch(0.541 0.281 293.009);
--sidebar-primary-foreground: oklch(0.969 0.016 293.756);
@@ -74,59 +89,21 @@
--sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: oklch(1 0 0 / 10%);
--sidebar-ring: oklch(0.541 0.281 293.009);
--chart-1: oklch(0.5424 0.2454 293.0160);
--chart-2: oklch(0.6309 0.1013 183.4907);
--chart-3: oklch(0.3787 0.0440 225.5393);
--chart-4: oklch(0.8336 0.1186 88.1463);
--chart-5: oklch(0.7834 0.1261 58.7491);
--font-sans: Inter, -apple-system, sans-serif;
--font-serif: Georgia, serif;
--font-mono: JetBrains Mono, monospace;
--radius: 0.75rem;
}
/*
.dark {
--background: oklch(0.240 0.011 256.8);
--foreground: oklch(0.985 0 0);
--card: oklch(0.266 0.015 257.5);
--card-foreground: oklch(0.985 0 0);
--popover: oklch(0.266 0.015 257.5);
--popover-foreground: oklch(0.985 0 0);
--primary: oklch(0.541 0.281 293.009);
--primary-foreground: oklch(0.969 0.016 293.756);
--secondary: oklch(0.274 0.006 286.033);
--secondary-foreground: oklch(0.985 0 0);
--muted: oklch(0.274 0.006 286.033);
--muted-foreground: oklch(0.705 0.015 286.067);
--accent: oklch(0.274 0.006 286.033);
--accent-foreground: oklch(0.985 0 0);
--destructive: oklch(0.704 0.191 22.216);
--border: oklch(1 0 0 / 10%);
--input: oklch(1 0 0 / 15%);
--ring: oklch(0.541 0.281 293.009);
--chart-1: oklch(0.488 0.243 264.376);
--chart-2: oklch(0.696 0.17 162.48);
--chart-3: oklch(0.769 0.188 70.08);
--chart-4: oklch(0.627 0.265 303.9);
--chart-5: oklch(0.645 0.246 16.439);
--sidebar: oklch(0.295 0.015 256.8);
--sidebar-foreground: oklch(0.985 0 0);
--sidebar-primary: oklch(0.541 0.281 293.009);
--sidebar-primary-foreground: oklch(0.969 0.016 293.756);
--sidebar-accent: oklch(0.274 0.006 286.033);
--sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: oklch(1 0 0 / 10%);
--sidebar-ring: oklch(0.541 0.281 293.009);
}
*/
@theme inline {
--font-inter: 'Inter', 'sans-serif';
--font-manrope: 'Manrope', 'sans-serif';
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-card: var(--card);
@@ -142,6 +119,7 @@
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-destructive-foreground: var(--destructive-foreground);
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);
@@ -158,19 +136,41 @@
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-ring: var(--sidebar-ring);
--font-sans: var(--font-sans);
--font-mono: var(--font-mono);
--font-serif: var(--font-serif);
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
--shadow-2xs: var(--shadow-2xs);
--shadow-xs: var(--shadow-xs);
--shadow-sm: var(--shadow-sm);
--shadow: var(--shadow);
--shadow-md: var(--shadow-md);
--shadow-lg: var(--shadow-lg);
--shadow-xl: var(--shadow-xl);
--shadow-2xl: var(--shadow-2xl);
--tracking-tighter: calc(var(--tracking-normal) - 0.05em);
--tracking-tight: calc(var(--tracking-normal) - 0.025em);
--tracking-normal: var(--tracking-normal);
--tracking-wide: calc(var(--tracking-normal) + 0.025em);
--tracking-wider: calc(var(--tracking-normal) + 0.05em);
--tracking-widest: calc(var(--tracking-normal) + 0.1em);
}
@layer base {
* {
@apply border-border outline-ring/50;
scrollbar-width: thin;
scrollbar-color: var(--border) transparent;
}
html {
@apply overflow-x-hidden;
}
body {
@apply bg-background text-foreground min-h-svh w-full;
@apply bg-background text-foreground;
letter-spacing: var(--tracking-normal);
}
button:not(:disabled),
@@ -180,74 +180,27 @@
/* Prevent focus zoom on mobile devices */
@media screen and (max-width: 767px) {
input,
select,
textarea {
font-size: 16px !important;
}
}
}
@utility container {
margin-inline: auto;
padding-inline: 2rem;
}
@utility no-scrollbar {
/* Hide scrollbar for Chrome, Safari and Opera */
&::-webkit-scrollbar {
display: none;
}
/* Hide scrollbar for IE, Edge and Firefox */
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
}
@utility faded-bottom {
@apply after:pointer-events-none after:absolute after:bottom-0 after:left-0 after:hidden after:h-32 after:w-full after:bg-[linear-gradient(180deg,_transparent_10%,_var(--background)_70%)] md:after:block;
}
/* styles.css */
.CollapsibleContent {
overflow: hidden;
}
.CollapsibleContent[data-state='open'] {
animation: slideDown 300ms ease-out;
}
.CollapsibleContent[data-state='closed'] {
animation: slideUp 300ms ease-out;
}
@keyframes slideDown {
from {
height: 0;
}
to {
height: var(--radix-collapsible-content-height);
}
}
@keyframes slideUp {
from {
height: var(--radix-collapsible-content-height);
}
to {
height: 0;
}
}
@layer components {
.prose :where(h1, h2, h3, h4, h5, h6) {
@apply font-bold text-foreground my-4;
}
.prose :where(p) {
@apply text-muted-foreground block mb-2;
/* Target the scrollbar */
::-webkit-scrollbar {
/* Width of the scrollbar */
width: 5px;
height: 5px;
position: absolute !important;
}
.prose :where(strong) {
@apply text-foreground font-semibold mb-2;
::-webkit-scrollbar-thumb {
background: var(--border);
border-radius: 46px !important;
cursor: pointer;
}
}
}
+2 -53
View File
@@ -1,6 +1,5 @@
import { EditorView } from '@uiw/react-codemirror';
export const colorOptions = [
@@ -25,54 +24,4 @@ export const randomColor = () => {
export const overrideDarkTheme = EditorView.theme({
'.cm-content': {
backgroundColor: "#20252c" ,
},
".cm-gutter": {
backgroundColor: "#20252c",
},
".cm-gutterElement": {
color: "#4b515a"
},
".ͼp": {
color: "#A994FF"
},
".cm-line .ͼq": {
color: "#ff6363"
},
".ͼu": {
color: "#B6E672"
},
".ͼv": {
color: "#6cdcc4"
}
}, { dark: true });
export const overrideLightTheme = EditorView.theme({
".ͼb": {
color: "#2A1D66"
},
".cm-gutterElement": {
color: "#a2a4a8"
},
".cm-gutter": {
backgroundColor: "white",
},
".cm-gutters": {
borderColor: "#f2f4f6"
},
".cm-line": {
color: "#333639"
},
});
-3
View File
@@ -1,12 +1,9 @@
import ReactDOM from "react-dom/client";
import { BrowserRouter } from "react-router-dom";
import App from "./App.tsx";
import { UIProviders } from "./provider.tsx";
import "./styles/globals.css";
import "./index.css";
import "./i18/index.ts";
ReactDOM.createRoot(document.getElementById("root")!).render(
+1 -1
View File
@@ -12,7 +12,7 @@ export function UIProviders({ children }: { children: React.ReactNode }) {
return (
<FontProvider>
<ThemeProvider defaultTheme ={"dark"}>
<ThemeProvider defaultTheme ={"light"}>
<TooltipProvider delayDuration={0}>
<Toaster />
<SearchProvider>
@@ -88,10 +88,7 @@ const DatabaseProvider: React.FC<Props> = ({ children }) => {
database = database[0] as any;
else
database = undefined as any;
useEffect(() => {
console.log(database);
}, [database])
+1 -3
View File
@@ -145,8 +145,7 @@ export class BaseSqlImporter {
} catch (error) {
errors.push(error as Error);
}
}
}
if (tables.length == 0)
throw Error("Can't parse sql")
@@ -307,7 +306,6 @@ export class BaseSqlImporter {
const dataType: DataType | undefined = this.processDataType(typeName as string);
if (!dataType) {
console.log (typeName , this.data_types)
throw Error("Data type not found");
}
// get data type modifiers
+4
View File
@@ -18,5 +18,9 @@ export default defineConfig({
worker: {
format: 'es',
plugins: () => [ topLevelAwait()]
} ,
build: {
target: "esnext"
}
})