mirror of
https://github.com/stackrender/stackrender.git
synced 2026-09-10 19:25:44 +00:00
Improve performance and accessibility; add documentation
This commit is contained in:
@@ -10,6 +10,7 @@ import {
|
||||
import type { SidebarData } from '../../types';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useMemo } from 'react';
|
||||
import { BookIcon } from 'lucide-react';
|
||||
|
||||
export const useSidebarData = () => {
|
||||
|
||||
@@ -37,13 +38,7 @@ export const useSidebarData = () => {
|
||||
],
|
||||
footerNavGroups:[ {
|
||||
title: '',
|
||||
items: [
|
||||
{
|
||||
title: "X",
|
||||
url: "https://x.com/Iam_The_Dev",
|
||||
icon: IconBrandX,
|
||||
newTab : true
|
||||
},
|
||||
items: [
|
||||
{
|
||||
title: "Discord",
|
||||
url: "https://discord.gg/4dv26jR4Pj",
|
||||
@@ -56,6 +51,12 @@ export const useSidebarData = () => {
|
||||
icon: IconBrandGithub,
|
||||
newTab : true
|
||||
},
|
||||
{
|
||||
title : t("sidebar.documentation") ,
|
||||
url : "https://www.stackrender.io/docs" ,
|
||||
icon : BookIcon,
|
||||
newTab : true
|
||||
}
|
||||
|
||||
],
|
||||
}]
|
||||
|
||||
@@ -124,11 +124,9 @@ export const useMenuData = () => {
|
||||
openController(!showController)
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
id: "menu.theme",
|
||||
title: t("menu.theme"),
|
||||
|
||||
children: [
|
||||
{
|
||||
id: "light",
|
||||
@@ -150,6 +148,12 @@ export const useMenuData = () => {
|
||||
id: "menu.help",
|
||||
title: t("menu.help"),
|
||||
children: [
|
||||
{
|
||||
id: "menu.documentation", title: t("sidebar.documentation"),
|
||||
clickHandler: () => {
|
||||
window.open("https://www.stackrender.io/docs", "_blank")
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "menu.join_discord", title: t("menu.join_discord"),
|
||||
clickHandler: () => {
|
||||
|
||||
@@ -14,6 +14,7 @@ import { IconFilter2Up, IconPlus } from "@tabler/icons-react";
|
||||
import { Accordion } from "@/components/ui/accordion";
|
||||
import RelationshipAccordionItem from "./relationship-accordion-item/relationship-accordion-item";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
|
||||
const RelationshipController: React.FC = ({ }) => {
|
||||
|
||||
@@ -22,32 +23,28 @@ const RelationshipController: React.FC = ({ }) => {
|
||||
const { database } = useDatabase();
|
||||
const { relationships: allRelationships } = database || { relationships: [] };
|
||||
const [relationships, setRelationships] = useState<RelationshipType[]>(allRelationships);
|
||||
|
||||
const nameRef: Ref<HTMLInputElement> = useRef<HTMLInputElement>(null);
|
||||
const { t } = useTranslation();
|
||||
const [selectedRelationship, setSelectedRelationship] = useState<string | undefined>();
|
||||
const { focusedRelationshipId } = useDiagram();
|
||||
const { t } = useTranslation();
|
||||
const { focusedRelationshipId , setFocusedRelationshipId } = useDiagram();
|
||||
|
||||
useEffect(() => searchRelationships(), [allRelationships]);
|
||||
|
||||
useEffect(() => {
|
||||
if (focusedRelationshipId) {
|
||||
setSelectedRelationship(focusedRelationshipId);
|
||||
if (focusedRelationshipId) {
|
||||
const accordionItem = document.getElementById(focusedRelationshipId)
|
||||
if (accordionItem)
|
||||
accordionItem?.scrollIntoView({
|
||||
behavior: 'smooth', block: 'center'
|
||||
behavior: 'smooth', block: "nearest"
|
||||
})
|
||||
}
|
||||
}, [focusedRelationshipId]);
|
||||
|
||||
const onOpen = useCallback(() => {
|
||||
open(Modals.CREATE_RELATIONSHIP, {
|
||||
onRlationshipCreated: (id: string) => setSelectedRelationship(id)
|
||||
onRlationshipCreated: (id: string) => setFocusedRelationshipId(id)
|
||||
})
|
||||
}, []);
|
||||
|
||||
|
||||
const searchRelationships = useCallback(() => {
|
||||
const keyword: string | undefined = nameRef.current?.value;
|
||||
if (keyword !== undefined) {
|
||||
@@ -66,13 +63,12 @@ const RelationshipController: React.FC = ({ }) => {
|
||||
}, [nameRef, allRelationships]);
|
||||
|
||||
const collapseAll = useCallback(() => {
|
||||
setSelectedRelationship("");
|
||||
setFocusedRelationshipId(undefined);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="w-full h-full flex flex-col pl-3 min-h-0">
|
||||
|
||||
<div className="flex items-center pb-2 gap-2 pt-3 pr-3 ">
|
||||
<div className="w-full h-full flex flex-col min-h-0">
|
||||
<div className="flex items-center gap-2 p-3">
|
||||
<Tooltip >
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
@@ -116,16 +112,19 @@ const RelationshipController: React.FC = ({ }) => {
|
||||
|
||||
{
|
||||
allRelationships.length > 0 &&
|
||||
<ScrollArea className=" flex-1 overflow-auto pr-3">
|
||||
<ScrollArea className="flex-1 px-3 overflow-hidden">
|
||||
<Accordion
|
||||
type="single"
|
||||
collapsible
|
||||
className="w-full "
|
||||
value={selectedRelationship}
|
||||
onValueChange={setSelectedRelationship}
|
||||
value={focusedRelationshipId}
|
||||
onValueChange={setFocusedRelationshipId}
|
||||
>
|
||||
{relationships.map((relationship: RelationshipType) => (
|
||||
<RelationshipAccordionItem relationship={relationship} key={relationship.id} />
|
||||
<>
|
||||
<RelationshipAccordionItem relationship={relationship} key={relationship.id} />
|
||||
<Separator className="my-1" />
|
||||
</>
|
||||
))}
|
||||
</Accordion>
|
||||
</ScrollArea>
|
||||
@@ -133,7 +132,7 @@ const RelationshipController: React.FC = ({ }) => {
|
||||
|
||||
{
|
||||
(allRelationships.length == 0) &&
|
||||
<div className="pr-2 h-full">
|
||||
<div className="px-3 h-full">
|
||||
<EmptyList
|
||||
title={t("db_controller.empty_list.no_relationships")}
|
||||
description={t("db_controller.empty_list.no_relationships_description")}
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@ interface RelationshipAccordionItemProps {
|
||||
const RelationshipAccordionItem: React.FC<RelationshipAccordionItemProps> = ({ relationship }) => {
|
||||
|
||||
return (
|
||||
<AccordionItem value={relationship.id} className="my-2" id={relationship.id}>
|
||||
<AccordionItem value={relationship.id} className="border-none" id={relationship.id}>
|
||||
<RelationshipAccordionTrigger relationship={relationship} />
|
||||
<AccordionContent className="flex flex-col gap-4 text-balance">
|
||||
<RelationshipAccordionContent
|
||||
|
||||
+11
-4
@@ -66,13 +66,13 @@ const RelationshipAccordionTrigger: React.FC<RelationshipAccordionTriggerProps>
|
||||
</label>
|
||||
</div>
|
||||
<div className="hidden shrink-0 flex-row group-hover:flex gap-2">
|
||||
<Button variant="outline" size="icon" className="size-7 shrink-0 " onClick={(event: any) => {
|
||||
<Button variant="outline" size="icon" className="size-8 shrink-0 " onClick={(event: any) => {
|
||||
event.stopPropagation();
|
||||
setEditMode(true)
|
||||
}}>
|
||||
<IconPencil className="size-4 text-muted-foreground " />
|
||||
</Button>
|
||||
<Button variant="outline" size="icon" className="size-7 shrink-0 " onClick={(event: any) => {
|
||||
<Button variant="outline" size="icon" className="size-8 shrink-0 " onClick={(event: any) => {
|
||||
event.stopPropagation();
|
||||
focusOnRelationship(relationship.id, true)
|
||||
}}>
|
||||
@@ -92,16 +92,23 @@ const RelationshipAccordionTrigger: React.FC<RelationshipAccordionTriggerProps>
|
||||
type="text"
|
||||
onClick={(event) => event.stopPropagation()}
|
||||
className="h-8"
|
||||
onKeyDown={(e: any) => {
|
||||
if (e.key === "Enter") {
|
||||
e.preventDefault();
|
||||
editRelationshipName();
|
||||
e.target.blur();
|
||||
}
|
||||
}}
|
||||
|
||||
/>
|
||||
<Button variant="ghost" size="icon" className="size-7 shrink-0 rounded-sm" onClick={editRelationshipName}>
|
||||
<Button variant="ghost" size="icon" className="size-8 shrink-0 rounded-sm" onClick={editRelationshipName}>
|
||||
<IconCheck className="size-4 text-muted-foreground " />
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" size="icon" className="size-7 shrink-0 rounded-sm" >
|
||||
<Button variant="ghost" size="icon" className="size-8 shrink-0 rounded-sm" >
|
||||
<IconDotsVertical className="size-4 text-muted-foreground " />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
|
||||
@@ -9,8 +9,7 @@ import { v4 } from "uuid";
|
||||
import { useDiagram } from "@/providers/diagram-provider/diagram-provider";
|
||||
import { useReactFlow } from "@xyflow/react";
|
||||
import SqlPreview from "../sql-preview";
|
||||
import EmptyList from "@/components/empty-list";
|
||||
import { useModal } from "@/providers/modal-provider/modal-provider";
|
||||
import EmptyList from "@/components/empty-list";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { IconListDetails, IconPlus } from "@tabler/icons-react";
|
||||
@@ -28,6 +27,7 @@ import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import { closestCenter, DndContext, PointerSensor, useSensor, useSensors } from "@dnd-kit/core";
|
||||
import { arrayMove, SortableContext, verticalListSortingStrategy } from "@dnd-kit/sortable";
|
||||
import { getTableNextSequence } from "@/utils/tables";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
|
||||
|
||||
const TablesController: React.FC = ({ }) => {
|
||||
@@ -38,9 +38,9 @@ const TablesController: React.FC = ({ }) => {
|
||||
const { tables: allTables } = database || { tables: [] };
|
||||
const [tables, setTables] = useState<TableType[]>(allTables);
|
||||
const { t } = useTranslation();
|
||||
const [selectedTable, setSelectedTable] = useState<string | undefined>(undefined);
|
||||
|
||||
const [showSqlPreview, setShowSqlPreview] = useState<boolean>(false);
|
||||
const { focusedTableId } = useDiagram();
|
||||
const { focusedTableId , setFocusedTableId} = useDiagram();
|
||||
const nameRef: Ref<HTMLInputElement> = useRef<HTMLInputElement>(null);
|
||||
|
||||
const sensors = useSensors(
|
||||
@@ -59,7 +59,7 @@ const TablesController: React.FC = ({ }) => {
|
||||
const newIndex = items.findIndex((item: TableType) => item.id == over.id);
|
||||
|
||||
const tables = arrayMove(items, oldIndex, newIndex);
|
||||
|
||||
|
||||
orderTables(tables)
|
||||
|
||||
return tables;
|
||||
@@ -72,7 +72,7 @@ const TablesController: React.FC = ({ }) => {
|
||||
useEffect(() => searchTables(), [allTables]);
|
||||
|
||||
const addNewTable = useCallback(async () => {
|
||||
|
||||
|
||||
const newTableId: string = v4();
|
||||
const viewport = getViewport();
|
||||
const { x, y, zoom } = viewport;
|
||||
@@ -96,17 +96,15 @@ const TablesController: React.FC = ({ }) => {
|
||||
}]
|
||||
} as TableInsertType);
|
||||
|
||||
setSelectedTable(newTableId);
|
||||
setFocusedTableId(newTableId);
|
||||
}, [database, tables, getViewport, getInteger]);
|
||||
|
||||
useEffect(() => {
|
||||
if (focusedTableId) {
|
||||
setSelectedTable(focusedTableId);
|
||||
setShowSqlPreview(false);
|
||||
if (focusedTableId) {
|
||||
const accordionItem = document.getElementById(focusedTableId)
|
||||
if (accordionItem)
|
||||
accordionItem?.scrollIntoView({
|
||||
behavior: 'smooth', block: 'center'
|
||||
behavior: 'smooth', block: "nearest"
|
||||
})
|
||||
}
|
||||
}, [focusedTableId]);
|
||||
@@ -128,8 +126,8 @@ const TablesController: React.FC = ({ }) => {
|
||||
}, [tables]);
|
||||
|
||||
return (
|
||||
<div className="w-full h-full flex flex-col pl-3 min-h-0">
|
||||
<div className="flex items-center pb-2 gap-2 pt-3 pr-3 ">
|
||||
<div className="w-full h-full flex flex-col min-h-0">
|
||||
<div className="flex items-center gap-2 p-3">
|
||||
<Tooltip >
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
@@ -174,13 +172,13 @@ const TablesController: React.FC = ({ }) => {
|
||||
</div>
|
||||
{
|
||||
allTables.length > 0 && !showSqlPreview &&
|
||||
<ScrollArea className="overflow-y-auto pr-3 h-full w-full ">
|
||||
<ScrollArea className="px-3 h-full w-full overflow-hidden">
|
||||
<Accordion
|
||||
type="single"
|
||||
collapsible
|
||||
className="w-full"
|
||||
value={selectedTable}
|
||||
onValueChange={setSelectedTable}
|
||||
value={focusedTableId}
|
||||
onValueChange={setFocusedTableId}
|
||||
>
|
||||
<DndContext
|
||||
sensors={sensors}
|
||||
@@ -194,7 +192,10 @@ const TablesController: React.FC = ({ }) => {
|
||||
>
|
||||
|
||||
{tables.map((table: TableType) => (
|
||||
<TableAccordionItem table={table} key={table.id} />
|
||||
<>
|
||||
<TableAccordionItem table={table} key={table.id} />
|
||||
<Separator className="my-1" />
|
||||
</>
|
||||
))}
|
||||
</SortableContext>
|
||||
|
||||
@@ -213,7 +214,7 @@ const TablesController: React.FC = ({ }) => {
|
||||
}
|
||||
{
|
||||
(allTables.length == 0) &&
|
||||
<div className="pr-3 h-full">
|
||||
<div className="px-3 h-full">
|
||||
<EmptyList
|
||||
title={t("db_controller.empty_list.no_tables")}
|
||||
description={t("db_controller.empty_list.no_tables_description")}
|
||||
|
||||
+15
-6
@@ -4,7 +4,7 @@ import { useSortable } from "@dnd-kit/sortable";
|
||||
import { Settings2 } from "lucide-react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { CSS } from "@dnd-kit/utilities";
|
||||
import { FieldType } from "@/lib/schemas/field-schema";
|
||||
import { FieldType } from "@/lib/schemas/field-schema";
|
||||
import { Key, useEffect, useState } from "react";
|
||||
import { useDatabaseOperations } from "@/providers/database-provider/database-provider";
|
||||
import FieldSetting from "./field-setting";
|
||||
@@ -61,6 +61,8 @@ const FieldItem: React.FC<Props> = ({ field }) => {
|
||||
if (!dataType)
|
||||
return;
|
||||
|
||||
if (dataType.id == field.typeId)
|
||||
return;
|
||||
if (key != null) {
|
||||
editField({
|
||||
id: field.id,
|
||||
@@ -98,6 +100,13 @@ const FieldItem: React.FC<Props> = ({ field }) => {
|
||||
onChange={(event: any) => setFieldName(event.target.value)}
|
||||
onBlur={saveFieldName}
|
||||
className=" flex flex-1 !bg-transparent"
|
||||
onKeyDown={(e : any) => {
|
||||
if (e.key === "Enter") {
|
||||
e.preventDefault();
|
||||
saveFieldName() ;
|
||||
e.target.blur() ;
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Combobox
|
||||
items={data_types}
|
||||
@@ -113,9 +122,9 @@ const FieldItem: React.FC<Props> = ({ field }) => {
|
||||
<TooltipTrigger asChild>
|
||||
<span>
|
||||
<Toggle size={"sm"}
|
||||
|
||||
className="size-9 text-muted-foreground "
|
||||
pressed={!field.nullable as boolean}
|
||||
|
||||
className="size-9 text-muted-foreground "
|
||||
pressed={!field.nullable as boolean}
|
||||
onPressedChange={toggleNullable}
|
||||
>
|
||||
<IconKeyframe className="size-4" />
|
||||
@@ -130,8 +139,8 @@ const FieldItem: React.FC<Props> = ({ field }) => {
|
||||
<TooltipTrigger asChild>
|
||||
<span>
|
||||
<Toggle size={"sm"}
|
||||
className="size-9 text-muted-foreground "
|
||||
pressed={field.isPrimary as boolean}
|
||||
className="size-9 text-muted-foreground "
|
||||
pressed={field.isPrimary as boolean}
|
||||
onPressedChange={togglePrimaryKey}
|
||||
>
|
||||
<IconKey className="size-4" />
|
||||
|
||||
+4
-4
@@ -91,7 +91,7 @@ const TableAccordionContent: React.FC<TableAccordionBodyProps> = ({ table, keys
|
||||
>
|
||||
|
||||
<AccordionItem value="fields" className="border-none">
|
||||
<AccordionTrigger className="py-1 rounded-none text-muted-foreground " position="right" >
|
||||
<AccordionTrigger className="py-1 rounded-none text-muted-foreground pr-1.5" position="right" >
|
||||
<div className="w-full flex gap-1 items-center">
|
||||
<IconFolder className="size-4" />
|
||||
{t("db_controller.fields")}
|
||||
@@ -103,7 +103,7 @@ const TableAccordionContent: React.FC<TableAccordionBodyProps> = ({ table, keys
|
||||
</AccordionItem>
|
||||
|
||||
<AccordionItem value="indexes" className="border-none">
|
||||
<AccordionTrigger className=" py-1 rounded-none text-muted-foreground " position="right">
|
||||
<AccordionTrigger className=" py-1 rounded-none text-muted-foreground pr-1.5" position="right">
|
||||
<div className="w-full flex gap-1 items-center">
|
||||
<IconBolt className="size-4" />
|
||||
{t("db_controller.indexes")}
|
||||
@@ -115,7 +115,7 @@ const TableAccordionContent: React.FC<TableAccordionBodyProps> = ({ table, keys
|
||||
</AccordionItem>
|
||||
|
||||
<AccordionItem value="note" className="border-none">
|
||||
<AccordionTrigger className=" py-1 rounded-none text-muted-foreground " position="right">
|
||||
<AccordionTrigger className=" py-1 rounded-none text-muted-foreground pr-1.5" position="right">
|
||||
<div className="w-full flex gap-1 items-center">
|
||||
<IconMessageCircle className="size-4 " />
|
||||
{t("db_controller.note")}
|
||||
@@ -133,7 +133,7 @@ const TableAccordionContent: React.FC<TableAccordionBodyProps> = ({ table, keys
|
||||
</AccordionItem>
|
||||
|
||||
</Accordion>
|
||||
<div className="flex pt-2 justify-between items-center ">
|
||||
<div className="flex pt-2 justify-between pr-1.5 items-center ">
|
||||
<div className="h-full flex items-center">
|
||||
<ColorPicker
|
||||
defaultColor={table.color as string}
|
||||
|
||||
+2
-2
@@ -28,12 +28,12 @@ const TableAccordionItem = forwardRef<HTMLDivElement, TableAccordionItemProps>(
|
||||
|
||||
|
||||
return (
|
||||
<AccordionItem value={table.id} className="my-2 border-none" id={table.id} ref={ref}>
|
||||
<AccordionItem value={table.id} className=" border-none" id={table.id} ref={ref}>
|
||||
<div
|
||||
ref={setNodeRef}
|
||||
style={style}
|
||||
{...attributes}
|
||||
className="w-full border-b "
|
||||
className="w-full "
|
||||
>
|
||||
<TableAccordionTrigger table={table} />
|
||||
<AccordionContent className="flex flex-col gap-4 text-balance">
|
||||
|
||||
+11
-4
@@ -126,14 +126,14 @@ const TableAccordionHeader: React.FC<TableAccordionHeaderProps> = ({ table, isOp
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div className="hidden !shrink-0 flex-row group-hover:flex gap-2 ">
|
||||
<Button variant="outline" size="icon" className="size-7 shrink-0" onClick={(event: any) => {
|
||||
<Button variant="outline" size="icon" className="size-8 shrink-0" onClick={(event: any) => {
|
||||
event.stopPropagation();
|
||||
setEditMode(true)
|
||||
|
||||
}}>
|
||||
<IconPencil className="size-4 text-muted-foreground " />
|
||||
</Button>
|
||||
<Button variant="outline" size="icon" className="size-7 shrink-0 " onClick={(event: any) => {
|
||||
<Button variant="outline" size="icon" className="size-8 shrink-0 " onClick={(event: any) => {
|
||||
event.stopPropagation();
|
||||
focusOnTable(table.id, true)
|
||||
}}>
|
||||
@@ -154,9 +154,16 @@ const TableAccordionHeader: React.FC<TableAccordionHeaderProps> = ({ table, isOp
|
||||
type="text"
|
||||
onClick={(event) => event.stopPropagation()}
|
||||
className="h-8"
|
||||
onKeyDown={(e: any) => {
|
||||
if (e.key === "Enter") {
|
||||
e.preventDefault();
|
||||
saveTableName();
|
||||
e.target.blur();
|
||||
}
|
||||
}}
|
||||
|
||||
/>
|
||||
<Button variant="ghost" size="icon" className="size-7 shrink-0 rounded-sm" onClick={saveTableName} >
|
||||
<Button variant="ghost" size="icon" className="size-8 shrink-0 rounded-sm" onClick={saveTableName} >
|
||||
<IconCheck className="size-4 text-muted-foreground " />
|
||||
</Button>
|
||||
</>
|
||||
@@ -164,7 +171,7 @@ const TableAccordionHeader: React.FC<TableAccordionHeaderProps> = ({ table, isOp
|
||||
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" size="icon" className="size-7 shrink-0 flex rounded-sm" >
|
||||
<Button variant="ghost" size="icon" className="size-8 shrink-0 flex rounded-sm" >
|
||||
<IconDotsVertical className="size-4 text-muted-foreground " />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
|
||||
@@ -98,6 +98,13 @@ 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) => {
|
||||
if (e.key === "Enter") {
|
||||
e.preventDefault();
|
||||
saveFieldName();
|
||||
e.target.blur();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Edge, Node, NodeProps } from "@xyflow/react";
|
||||
import hash from 'object-hash';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import {
|
||||
Check,
|
||||
Check,
|
||||
ChevronUp,
|
||||
ChevronDown,
|
||||
} from 'lucide-react';
|
||||
@@ -15,14 +15,12 @@ import { useDatabaseOperations } from "@/providers/database-provider/database-pr
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { RelationshipType } from "@/lib/schemas/relationship-schema";
|
||||
import { useDiagramOps } from "@/providers/diagram-provider/diagram-provider";
|
||||
import { Card, CardContent , CardHeader } from "@/components/ui/card";
|
||||
import { Card, CardContent, CardHeader } from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { IconFocus2, IconPencil, IconTableFilled } from "@tabler/icons-react";
|
||||
import { IconFocus2, IconPencil, IconTableFilled } from "@tabler/icons-react";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
|
||||
|
||||
export type TableProps = Node<{
|
||||
table: TableType,
|
||||
overlapping?: boolean,
|
||||
@@ -102,6 +100,8 @@ const Table: React.FC<NodeProps<TableProps>> = (props) => {
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
onDoubleClick={focus}
|
||||
|
||||
|
||||
>
|
||||
<CardHeader className="group rounded-t rounded-t-md p-1.5 flex items-center mb-0 bg-primary/10 "
|
||||
@@ -142,6 +142,13 @@ const Table: React.FC<NodeProps<TableProps>> = (props) => {
|
||||
value={tableName}
|
||||
onBlur={saveTableName}
|
||||
style={{ color: table.color as string }}
|
||||
onKeyDown={(e: any) => {
|
||||
if (e.key === "Enter") {
|
||||
e.preventDefault();
|
||||
saveTableName();
|
||||
e.target.blur();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<div className="flex gap-1 shrink-0 flex-row transition-opacity duration-200">
|
||||
<Button variant="outline" size="icon" className="size-6 shrink-0 shadow-sm rounded-sm" onClick={() => setEditMode(true)}>
|
||||
|
||||
@@ -7,10 +7,10 @@ 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 } from "lucide-react";
|
||||
import { AlertCircleIcon, Code } from "lucide-react";
|
||||
import { SqlToDatabase } from "@/utils/render/parsers/sql_to_database";
|
||||
import Clipboard from "@/components/clipboard";
|
||||
import { Trans } from 'react-i18next';
|
||||
import { Trans } from 'react-i18next';
|
||||
import { Node, useReactFlow } from "@xyflow/react";
|
||||
import { adjustTablesPositions } from "@/utils/tables";
|
||||
import { TableInsertType } from "@/lib/schemas/table-schema";
|
||||
@@ -181,20 +181,16 @@ const ImportDatabaseModal: React.FC<ModalProps> = ({ isOpen, onOpenChange }) =>
|
||||
}, [parsedDatabase]);
|
||||
|
||||
|
||||
|
||||
|
||||
if (isLoading || isSwitchingDatabase)
|
||||
return;
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
onOpenChange={onOpenChange}
|
||||
title={t("modals.import_database.title")}
|
||||
actionName={t("modals.import_database.import")}
|
||||
className="lg:min-w-[860px] md:min-w-[560px] w-full max-h-screen overflow-auto "
|
||||
className="lg:min-w-[860px] md:min-w-[560px] w-full max-h-screen overflow-auto "
|
||||
actionHandler={onImport}
|
||||
isDisabled={!parsedDatabase}
|
||||
>
|
||||
|
||||
@@ -6,6 +6,7 @@ export const ar = {
|
||||
tables: "الجداول",
|
||||
relationships: "العلاقات",
|
||||
database: "قاعدة البيانات",
|
||||
documentation : "الوثائق"
|
||||
},
|
||||
color_picker: {
|
||||
default_color: "اللون الافتراضي"
|
||||
@@ -195,6 +196,7 @@ export const ar = {
|
||||
import_database: {
|
||||
title: "استيراد قاعدة البيانات",
|
||||
import: "استيراد",
|
||||
view_docs : "عرض الوثائق" ,
|
||||
import_options: "هل ترغب في الاستيراد باستخدام:",
|
||||
import_error: "خطأ في تحليل SQL",
|
||||
import_error_description: "تعذر استيراد SQL الخاص بك بسبب وجود صيغة غير صحيحة.",
|
||||
|
||||
@@ -6,7 +6,7 @@ export const de = {
|
||||
tables: "Tabellen",
|
||||
relationships: "Beziehungen",
|
||||
database: "Datenbank",
|
||||
|
||||
documentation : "Dokumentation"
|
||||
},
|
||||
color_picker: {
|
||||
default_color: "Standardfarbe"
|
||||
@@ -196,6 +196,7 @@ export const de = {
|
||||
import_database: {
|
||||
title: "Datenbank importieren",
|
||||
import: "Importieren",
|
||||
view_docs : "Dokumentation anzeigen" ,
|
||||
import_options: "Möchtest du importieren mit:",
|
||||
import_error: "SQL-Parsing-Fehler",
|
||||
import_error_description: "Wir konnten dein SQL nicht importieren, da es ungültige Syntax enthält.",
|
||||
|
||||
+11
-9
@@ -6,8 +6,9 @@ export const en = {
|
||||
translation: {
|
||||
sidebar: {
|
||||
tables: "Tables",
|
||||
relationships: "Relationships",
|
||||
database : "Database"
|
||||
relationships: "Relationships",
|
||||
database: "Database",
|
||||
documentation : "Docs"
|
||||
},
|
||||
|
||||
color_picker: {
|
||||
@@ -15,9 +16,9 @@ export const en = {
|
||||
},
|
||||
navbar: {
|
||||
rename_db: "Rename database",
|
||||
search: "Search" ,
|
||||
|
||||
command : "Type a command or search..." ,
|
||||
search: "Search",
|
||||
|
||||
command: "Type a command or search...",
|
||||
},
|
||||
db_controller: {
|
||||
filter: "Filter",
|
||||
@@ -43,7 +44,7 @@ export const en = {
|
||||
|
||||
source_table: "Source Table",
|
||||
referenced_table: "Referenced Table",
|
||||
|
||||
|
||||
required: "Required",
|
||||
select_table: "Select table",
|
||||
select_field: "Select field",
|
||||
@@ -182,8 +183,8 @@ export const en = {
|
||||
theme: "Theme",
|
||||
light: "Light",
|
||||
dark: "Dark",
|
||||
|
||||
system : "System" ,
|
||||
|
||||
system: "System",
|
||||
help: "Help",
|
||||
show_docs: "Show Docs",
|
||||
join_discord: "Join Discord",
|
||||
@@ -204,10 +205,11 @@ export const en = {
|
||||
delete_database_content: "This action is irreversible and will permanently remove the diagram.",
|
||||
delete: "Delete",
|
||||
empty_diagram: "Empty diagram",
|
||||
create_and_import : "Create and import" ,
|
||||
create_and_import: "Create and import",
|
||||
import_database: {
|
||||
title: "Import your Database",
|
||||
import: "Import",
|
||||
view_docs : "View Docs" ,
|
||||
import_options: "Would you like to import using :",
|
||||
import_error: "SQL Parsing Error",
|
||||
import_error_description: "We couldn't import your SQL because it contains invalid syntax.",
|
||||
|
||||
@@ -5,7 +5,8 @@ export const es = {
|
||||
translation: {
|
||||
sidebar: {
|
||||
tables: "Tablas",
|
||||
relationships: "Relaciones"
|
||||
relationships: "Relaciones",
|
||||
documentation : "Documentación"
|
||||
},
|
||||
|
||||
color_picker: {
|
||||
@@ -204,6 +205,7 @@ export const es = {
|
||||
import_database: {
|
||||
title: "Importar tu base de datos",
|
||||
import: "Importar",
|
||||
view_docs : "Ver la documentación" ,
|
||||
import_options: "¿Te gustaría importar usando :",
|
||||
import_error: "Error al analizar SQL",
|
||||
import_error_description: "No pudimos importar tu SQL porque contiene sintaxis inválida.",
|
||||
|
||||
@@ -7,6 +7,7 @@ export const fr = {
|
||||
tables: "Tables",
|
||||
relationships: "Relations",
|
||||
database: "Base de données",
|
||||
documentation : "Docs"
|
||||
|
||||
},
|
||||
color_picker: {
|
||||
@@ -198,6 +199,7 @@ export const fr = {
|
||||
import_database: {
|
||||
title: "Importer votre base de données",
|
||||
import: "Importer",
|
||||
view_docs : "Voir la documentation" ,
|
||||
import_options: "Souhaitez-vous importer en utilisant :",
|
||||
import_error: "Erreur d’analyse SQL",
|
||||
import_error_description: "Nous n'avons pas pu importer votre SQL car il contient une syntaxe invalide.",
|
||||
|
||||
@@ -5,9 +5,8 @@ export const hi = {
|
||||
sidebar: {
|
||||
tables: "तालिकाएँ",
|
||||
relationships: "रिलेशनशिप्स",
|
||||
database: "डेटाबेस",
|
||||
|
||||
|
||||
database: "डेटाबेस",
|
||||
documentation : "दस्तावेज़"
|
||||
},
|
||||
|
||||
color_picker: {
|
||||
@@ -179,7 +178,7 @@ export const hi = {
|
||||
theme: "थीम",
|
||||
light: "हल्का",
|
||||
dark: "गहरा",
|
||||
system: "सिस्टम" ,
|
||||
system: "सिस्टम",
|
||||
help: "मदद",
|
||||
show_docs: "दस्तावेज़ दिखाएं",
|
||||
join_discord: "डिस्कॉर्ड से जुड़ें"
|
||||
@@ -200,11 +199,12 @@ export const hi = {
|
||||
delete_database_content: "यह क्रिया अपरिवर्तनीय है और आरेख को स्थायी रूप से हटा देगी।",
|
||||
delete: "हटाएँ",
|
||||
empty_diagram: "खाली आरेख",
|
||||
create_and_import : "बनाएँ और आयात करें" ,
|
||||
create_and_import: "बनाएँ और आयात करें",
|
||||
|
||||
import_database: {
|
||||
title: "अपना डेटाबेस आयात करें",
|
||||
import: "आयात करें",
|
||||
view_docs : "दस्तावेज़ देखें" ,
|
||||
import_options: "क्या आप निम्न के माध्यम से आयात करना चाहेंगे:",
|
||||
import_error: "SQL पार्सिंग त्रुटि",
|
||||
import_error_description: "हम आपका SQL आयात नहीं कर सके क्योंकि इसमें अमान्य सिंटैक्स है।",
|
||||
@@ -216,7 +216,7 @@ export const hi = {
|
||||
name: "नाम",
|
||||
created_at: "बनाया गया",
|
||||
tables: "टेबल्स"
|
||||
} ,
|
||||
},
|
||||
export_sql: "SQL निर्यात करें",
|
||||
export_sql_header: "अपने डेटाबेस आरेख को SQL कोड में निर्यात करें"
|
||||
},
|
||||
|
||||
@@ -6,6 +6,7 @@ export const pt = {
|
||||
tables: "Tabelas",
|
||||
relationships: "Relacionamentos",
|
||||
database: "Banco de dados",
|
||||
documentation : "Documentação"
|
||||
},
|
||||
|
||||
color_picker: {
|
||||
@@ -204,6 +205,7 @@ export const pt = {
|
||||
import_database: {
|
||||
title: "Importar seu Banco de Dados",
|
||||
import: "Importar",
|
||||
view_docs : "Ver documentação" ,
|
||||
import_options: "Gostaria de importar usando:",
|
||||
import_error: "Erro de Análise SQL",
|
||||
import_error_description: "Não foi possível importar seu SQL porque contém sintaxe inválida.",
|
||||
|
||||
@@ -6,6 +6,7 @@ export const ru = {
|
||||
tables: "Таблицы",
|
||||
relationships: "Связи",
|
||||
database: "База данных",
|
||||
documentation : "Документация"
|
||||
},
|
||||
color_picker: {
|
||||
default_color: "Цвет по умолчанию"
|
||||
@@ -13,7 +14,6 @@ export const ru = {
|
||||
navbar: {
|
||||
rename_db: "Переименовать базу данных",
|
||||
search: "Поиск",
|
||||
|
||||
command: "Введите команду или выполните поиск...",
|
||||
},
|
||||
db_controller: {
|
||||
@@ -194,6 +194,7 @@ export const ru = {
|
||||
import_database: {
|
||||
title: "Импортируйте вашу базу данных",
|
||||
import: "Импорт",
|
||||
view_docs : "Просмотреть документацию" ,
|
||||
import_options: "Вы хотите импортировать с помощью:",
|
||||
import_error: "Ошибка разбора SQL",
|
||||
import_error_description: "Не удалось импортировать SQL, так как он содержит недопустимый синтаксис.",
|
||||
|
||||
@@ -6,8 +6,7 @@ export const zh = {
|
||||
tables: "数据表",
|
||||
relationships: "关系",
|
||||
database: "数据库",
|
||||
|
||||
|
||||
documentation: "文档"
|
||||
},
|
||||
|
||||
color_picker: {
|
||||
@@ -16,7 +15,6 @@ export const zh = {
|
||||
navbar: {
|
||||
rename_db: "重命名数据库",
|
||||
search: "搜索",
|
||||
|
||||
command: "输入命令或搜索...",
|
||||
},
|
||||
db_controller: {
|
||||
@@ -212,6 +210,7 @@ export const zh = {
|
||||
import_database: {
|
||||
title: "导入数据库",
|
||||
import: "导入",
|
||||
view_docs : "查看文档" ,
|
||||
import_options: "请选择导入方式:",
|
||||
import_error: "SQL 解析错误",
|
||||
import_error_description: "我们无法导入您的 SQL,因为它包含无效语法。",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { DatabaseDataContext, DatabaseOperationsContext } from "./database-context";
|
||||
import { useCallback, useContext, useMemo, useState } from "react";
|
||||
import { useCallback, useContext, useEffect, useMemo, useState } from "react";
|
||||
import { db } from "../sync-provider/sync-provider";
|
||||
import { TableInsertType, tables, TableType } from "@/lib/schemas/table-schema";
|
||||
import { useQuery } from "@powersync/react";
|
||||
@@ -88,8 +88,11 @@ const DatabaseProvider: React.FC<Props> = ({ children }) => {
|
||||
else
|
||||
database = undefined as any;
|
||||
|
||||
useEffect(() => {
|
||||
console.log(database);
|
||||
}, [database])
|
||||
|
||||
|
||||
|
||||
|
||||
// Fetch all data types
|
||||
let { data: data_types, isLoading: loadingDataTypes, isFetching: fetchingDatatypes } = useQuery(toCompilableQuery(
|
||||
@@ -107,12 +110,12 @@ const DatabaseProvider: React.FC<Props> = ({ children }) => {
|
||||
})
|
||||
|
||||
|
||||
|
||||
|
||||
const isLoading: boolean = loadingDataTypes || loadingDatabases || loadingCurrentDatabase;
|
||||
|
||||
const isSwitchingDatabase: boolean = useMemo(() => {
|
||||
return currentDatabaseId != (database as any)?.id
|
||||
}, [currentDatabaseId, database ]);
|
||||
}, [currentDatabaseId, database]);
|
||||
|
||||
// CRUD for database
|
||||
const createDatabase = useCallback(async (database: DatabaseInsertType): Promise<QueryResult> => {
|
||||
@@ -226,7 +229,7 @@ const DatabaseProvider: React.FC<Props> = ({ children }) => {
|
||||
})
|
||||
}, [db]);
|
||||
|
||||
// Reorder fields in a table
|
||||
// Reorder fields in a table
|
||||
const orderTables = useCallback(async (tablesList: TableType[]): Promise<void> => {
|
||||
return await db.transaction(async (tx) => {
|
||||
for (let index = 0; index < tablesList.length; index++) {
|
||||
|
||||
@@ -5,6 +5,9 @@ import { createContext, Dispatch } from "react";
|
||||
interface DiagramDataContextType {
|
||||
focusedTableId: string | undefined;
|
||||
focusedRelationshipId: string | undefined;
|
||||
|
||||
setFocusedTableId : ( focused : string | undefined) => void ,
|
||||
setFocusedRelationshipId : ( focused : string | undefined) => void
|
||||
}
|
||||
|
||||
interface DiagramOpsContextType {
|
||||
|
||||
@@ -19,16 +19,16 @@ const DiagramProvider: React.FC<Props> = ({ children }) => {
|
||||
const [focusedTableId, setFocusedTableId] = useState<string | undefined>(undefined)
|
||||
const [focusedRelationshipId, setFocusedRelationshipId] = useState<string | undefined>(undefined)
|
||||
const [isConnectionInProgress, setIsConnectionInProgress] = useState<boolean>(false);
|
||||
|
||||
|
||||
const [showController, setShowController] = useState<boolean>(() => {
|
||||
const value: string | null = localStorage.getItem(CONTROLLER_VISIBILITY_KEY) ;
|
||||
const value: string | null = localStorage.getItem(CONTROLLER_VISIBILITY_KEY);
|
||||
if (value)
|
||||
return JSON.parse(value);
|
||||
return true;
|
||||
});
|
||||
const [cardinalityStyle, setCardinalityStyle] = useState<CardinalityStyle>(() => {
|
||||
const value: string | null = localStorage.getItem(CARDINALITY_STYLE_KEY);
|
||||
|
||||
|
||||
if (value)
|
||||
return value as CardinalityStyle;
|
||||
return CardinalityStyle.SYMBOLIC;
|
||||
@@ -101,19 +101,21 @@ const DiagramProvider: React.FC<Props> = ({ children }) => {
|
||||
}, [setFocusedRelationshipId]);
|
||||
|
||||
|
||||
const changeCardinalityStyle = useCallback((style : CardinalityStyle) => {
|
||||
localStorage.setItem(CARDINALITY_STYLE_KEY , style) ;
|
||||
setCardinalityStyle( style) ;
|
||||
} , []) ;
|
||||
const changeCardinalityStyle = useCallback((style: CardinalityStyle) => {
|
||||
localStorage.setItem(CARDINALITY_STYLE_KEY, style);
|
||||
setCardinalityStyle(style);
|
||||
}, []);
|
||||
|
||||
const openController = useCallback((isOpen : boolean) => {
|
||||
localStorage.setItem( CONTROLLER_VISIBILITY_KEY , JSON.stringify(isOpen) ) ;
|
||||
setShowController(isOpen) ;
|
||||
} , [])
|
||||
const openController = useCallback((isOpen: boolean) => {
|
||||
localStorage.setItem(CONTROLLER_VISIBILITY_KEY, JSON.stringify(isOpen));
|
||||
setShowController(isOpen);
|
||||
}, [])
|
||||
|
||||
const contextDatatValue = useMemo(() => ({
|
||||
focusedTableId,
|
||||
focusedRelationshipId,
|
||||
setFocusedTableId,
|
||||
setFocusedRelationshipId
|
||||
|
||||
}), [focusedTableId, focusedRelationshipId,]);
|
||||
|
||||
@@ -124,7 +126,7 @@ const DiagramProvider: React.FC<Props> = ({ children }) => {
|
||||
isConnectionInProgress,
|
||||
showController,
|
||||
cardinalityStyle,
|
||||
changeCardinalityStyle ,
|
||||
changeCardinalityStyle,
|
||||
openController
|
||||
|
||||
}), [focusOnTable, focusOnRelationship, setIsConnectionInProgress, isConnectionInProgress, showController, setShowController, cardinalityStyle,
|
||||
|
||||
Reference in New Issue
Block a user