From dfc40a8d6c3db976bb546706e7bdcda50dcaa63a Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Sun, 21 Jul 2024 16:33:48 +0300 Subject: [PATCH 1/7] mid progress --- app/api/graph/[graph]/route.ts | 6 +- app/api/schema/[schema]/route.ts | 26 ----- app/components/Header.tsx | 24 +++-- app/components/graph/CreateElement.tsx | 123 ++++++++++++++++++++++++ app/components/graph/DeleteGraph.tsx | 10 +- app/components/ui/combobox.tsx | 18 ++-- app/create/page.tsx | 72 +------------- app/graph/DataPanel.tsx | 5 +- app/graph/Selector.tsx | 31 +++--- app/graph/toolbar.tsx | 4 +- app/schema/SchemaView.tsx | 127 +++++++++++++++++++++---- app/schema/page.tsx | 6 +- lib/utils.ts | 3 + 13 files changed, 293 insertions(+), 162 deletions(-) delete mode 100644 app/api/schema/[schema]/route.ts create mode 100644 app/components/graph/CreateElement.tsx diff --git a/app/api/graph/[graph]/route.ts b/app/api/graph/[graph]/route.ts index 003ac49..c232552 100644 --- a/app/api/graph/[graph]/route.ts +++ b/app/api/graph/[graph]/route.ts @@ -131,9 +131,13 @@ export async function GET(request: NextRequest, { params }: { params: { graph: s } const query = request.nextUrl.searchParams.get("query") + const create = request.nextUrl.searchParams.get("create") if (!query) throw new Error("Missing parameter 'query'") - + if (create === "false") { + const type = await client.connection.type(graphId) + if (type === "none") return NextResponse.json({}, { status: 200 }) + } const graph = client.selectGraph(graphId) const result = await graph.query(query) diff --git a/app/api/schema/[schema]/route.ts b/app/api/schema/[schema]/route.ts deleted file mode 100644 index 0b785a4..0000000 --- a/app/api/schema/[schema]/route.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { NextRequest, NextResponse } from "next/server"; -import { getClient } from "../../auth/[...nextauth]/options"; - -// eslint-disable-next-line import/prefer-default-export -export async function GET(_request: NextRequest, { params }: { params: {schema: string} }) { - const client = await getClient() - if (client instanceof NextResponse) { - return client - } - - const schemaId = params.schema; - - try { - if (!schemaId) throw new Error("Missing SchemaID") - - const graph = client.selectGraph(schemaId); - - const query = `MATCh (n)-[e]-(m) return n,e,m` - - const result = await graph.query(query) - - return NextResponse.json({ result }) - } catch (err: unknown) { - return NextResponse.json({ message: (err as Error).message }, { status: 400 }) - } -} \ No newline at end of file diff --git a/app/components/Header.tsx b/app/components/Header.tsx index 1c61246..55a3637 100644 --- a/app/components/Header.tsx +++ b/app/components/Header.tsx @@ -27,19 +27,18 @@ export default function Header({ inCreate = false, onSetGraphName }: Props) { const pathname = usePathname() const [userStatus, setUserStatus] = useState() const [graphName, setGraphName] = useState("") - - // const createGraph = async () => { - // const result = await securedFetch(`api/graph/${newName}`) - // } + const type = pathname.includes("/schema") ? "Schema" : "Graph" const handelCreateGraph = async (e: FormEvent) => { - if (!onSetGraphName) return + e.preventDefault() + const name = `${graphName}${type === "Schema" ? "_schema" : ""}` + const q = `RETURN 1` - const result = await securedFetch(`api/graph/${graphName}/?query=${prepareArg(q)}`, { + const result = await securedFetch(`api/graph/${name}/?query=${prepareArg(q)}`, { method: "GET" }) @@ -49,7 +48,6 @@ export default function Header({ inCreate = false, onSetGraphName }: Props) { setCreateOpen(false) setGraphName("") } - } return ( @@ -68,14 +66,14 @@ export default function Header({ inCreate = false, onSetGraphName }: Props) {
@@ -86,11 +84,11 @@ export default function Header({ inCreate = false, onSetGraphName }: Props) {
+ ) +} \ No newline at end of file diff --git a/app/components/graph/DeleteGraph.tsx b/app/components/graph/DeleteGraph.tsx index 7d9cd7e..c8a3860 100644 --- a/app/components/graph/DeleteGraph.tsx +++ b/app/components/graph/DeleteGraph.tsx @@ -1,20 +1,24 @@ import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from "@/components/ui/alert-dialog"; import { Toast, securedFetch } from "@/lib/utils"; -export default function DeleteGraph({ graphName, isOpen, onOpen, onDeleteGraph }: { +export default function DeleteGraph({ graphName, isOpen, onOpen, onDeleteGraph, isSchema }: { graphName: string isOpen: boolean onOpen: (open: boolean) => void onDeleteGraph: () => void + isSchema: boolean }) { + const type = isSchema ? "Schema" : "Graph" + const deleteGraph = async () => { - const result = await securedFetch(`/api/graph/${graphName}`, { + const name = `${graphName}${isSchema ? "_schema" : ""}` + const result = await securedFetch(`/api/graph/${name}`, { method: "DELETE", }); if (result.ok) { - Toast(`Graph ${graphName} deleted`, "Success") + Toast(`${type} ${graphName} deleted`, "Success") onDeleteGraph() } } diff --git a/app/components/ui/combobox.tsx b/app/components/ui/combobox.tsx index 5a8443f..4277aa4 100644 --- a/app/components/ui/combobox.tsx +++ b/app/components/ui/combobox.tsx @@ -22,9 +22,10 @@ interface ComboboxProps { setOptions?: Dispatch>, selectedValue?: string, setSelectedValue: (value: string) => void, + isSchema?: boolean } -export default function Combobox({ isSelectGraph, disabled = false, inTable, type, options, setOptions, selectedValue = "", setSelectedValue }: ComboboxProps) { +export default function Combobox({ isSelectGraph, disabled = false, inTable, type, options, setOptions, selectedValue = "", setSelectedValue, isSchema = false}: ComboboxProps) { const [open, setOpen] = useState(false) const [optionName, setOptionName] = useState("") @@ -53,6 +54,13 @@ export default function Combobox({ isSelectGraph, disabled = false, inTable, typ } } + const handelDelete = (option: string) => { + if (!setOptions) return + setOptions(prev => prev.filter(name => name !== option)) + if (selectedValue !== option) return + setSelectedValue("") + } + const handelSetOption = async (e: React.KeyboardEvent, option: string) => { if (!setOptions) return if (e.key !== "Enter") return @@ -196,12 +204,8 @@ export default function Combobox({ isSelectGraph, disabled = false, inTable, typ graphName={option} isOpen={isDeleteOpen} onOpen={setIsDeleteOpen} - onDeleteGraph={() => { - if (!setOptions) return - setOptions(prev => prev.filter(name => name !== option)) - if (selectedValue !== option) return - setSelectedValue("") - }} + onDeleteGraph={() => handelDelete(option)} + isSchema={isSchema} /> diff --git a/app/create/page.tsx b/app/create/page.tsx index 2250135..b0a4e6a 100644 --- a/app/create/page.tsx +++ b/app/create/page.tsx @@ -5,7 +5,6 @@ import { FormEvent, useEffect, useState } from "react"; import { useRouter } from "next/navigation"; import { Progress } from "@/components/ui/progress"; import useSWR from "swr"; -import { EdgeDataDefinition, NodeDataDefinition } from "cytoscape"; import { Toast, prepareArg, securedFetch } from "@/lib/utils"; import Header from "../components/Header"; import { Graph } from "../api/graph/model"; @@ -16,8 +15,6 @@ import Dropzone from "../components/ui/Dropzone"; type CurrentTab = "loadSchema" | "schema" | "graph" -type ElementDataDefinition = NodeDataDefinition | EdgeDataDefinition - export default function Create() { const [currentTab, setCurrentTab] = useState() @@ -154,28 +151,6 @@ export default function Create() { router.push("/graph") } - const onDelete = async (selectedValue: ElementDataDefinition) => { - const { id } = selectedValue - const q = `MATCH (n) WHERE ID(n) = ${id} delete n` - const result = await securedFetch(`api/graph/${prepareArg(graphName)}_schema/?query=${prepareArg(q)}`, { - method: "GET" - }) - - if (!result.ok) { - Toast("Faild to delete") - return - } - schema.Elements = schema.Elements.filter(e => e.data.id !== id) - } - - const onAddEntity = () => { - schema.Elements = [...schema.Elements, { data: { id: "number" } }] - } - - const onAddRelation = () => { - schema.Elements = [...schema.Elements, { data: { id: "number" } }] - } - // const setLabel = async (selectedElement: ElementDataDefinition, label: string) => { // const { id } = selectedElement @@ -205,51 +180,6 @@ export default function Create() { // return ok // } - const setProperty = async (selectedElement: ElementDataDefinition, key: string, newVal: string[]) => { - const { id } = selectedElement - const q = `MATCH (n) WHERE ID(n) = ${id} SET n.${key} = "${newVal}"` - const { ok } = await securedFetch(`api/graph/${prepareArg(graphName)}_schema/?query=${prepareArg(q)}`, { - method: "GET" - }) - - if (!ok) { - Toast("Failed to set property") - return ok - } - - schema.Elements = schema.Elements.map(e => { - if (e.data.id === id) { - const updatedElement = e - updatedElement.data[key] = newVal - return updatedElement - } - return e - }) - - return ok - } - - const removeProperty = async (selectedElement: ElementDataDefinition, key: string) => { - const { id } = selectedElement - const q = `MATCH (n) WHERE ID(n) = ${id} SET n.${key} = null` - const result = await securedFetch(`api/graph/${prepareArg(graphName)}_schema/?query=${prepareArg(q)}`, { - method: "GET" - }) - - if (!result.ok) return result.ok - - schema.Elements = schema.Elements.map(e => { - if (e.data.id === id) { - const updatedElement = e - delete updatedElement.data[key] - return updatedElement - } - return e - }) - - return result.ok - } - const getCurrentTab = () => { switch (currentTab) { case "loadSchema": @@ -264,7 +194,7 @@ export default function Create() { case "schema": return (
- +
}
diff --git a/app/graph/toolbar.tsx b/app/graph/toolbar.tsx index a098561..7e18f4f 100644 --- a/app/graph/toolbar.tsx +++ b/app/graph/toolbar.tsx @@ -33,14 +33,14 @@ export default function Toolbar({disabled, chartRef, onDeleteElement, onAddEntit
+
+ } +
+
+ ) } \ No newline at end of file diff --git a/app/components/ui/combobox.tsx b/app/components/ui/combobox.tsx index 4277aa4..7138a44 100644 --- a/app/components/ui/combobox.tsx +++ b/app/components/ui/combobox.tsx @@ -23,11 +23,13 @@ interface ComboboxProps { selectedValue?: string, setSelectedValue: (value: string) => void, isSchema?: boolean + defaultOpen?: boolean + onOpenChange?: (open:boolean) => void } -export default function Combobox({ isSelectGraph, disabled = false, inTable, type, options, setOptions, selectedValue = "", setSelectedValue, isSchema = false}: ComboboxProps) { +export default function Combobox({ isSelectGraph, disabled = false, inTable, type, options, setOptions, selectedValue = "", setSelectedValue, isSchema = false, defaultOpen = false, onOpenChange }: ComboboxProps) { - const [open, setOpen] = useState(false) + const [open, setOpen] = useState(defaultOpen) const [optionName, setOptionName] = useState("") const [editable, setEditable] = useState("") const [isUploadOpen, setIsUploadOpen] = useState(false) @@ -87,12 +89,15 @@ export default function Combobox({ isSelectGraph, disabled = false, inTable, typ return ( - + { + setOpen(o) + if (onOpenChange) onOpenChange(o) + }}> diff --git a/app/graph/page.tsx b/app/graph/page.tsx index 7ce3b4e..cdb9ddf 100644 --- a/app/graph/page.tsx +++ b/app/graph/page.tsx @@ -56,7 +56,7 @@ export default function Page() {
- +
diff --git a/app/schema/SchemaView.tsx b/app/schema/SchemaView.tsx index ff56c56..56788fe 100644 --- a/app/schema/SchemaView.tsx +++ b/app/schema/SchemaView.tsx @@ -3,7 +3,7 @@ import { ResizablePanel, ResizablePanelGroup, ResizableHandle } from "@/components/ui/resizable" import CytoscapeComponent from "react-cytoscapejs" import { ChevronLeft } from "lucide-react" -import cytoscape, { EdgeSingular, ElementDefinition, EventObject, NodeDataDefinition } from "cytoscape" +import cytoscape, { EdgeSingular, EventObject, NodeDataDefinition } from "cytoscape" import { ImperativePanelHandle } from "react-resizable-panels" import { Dispatch, SetStateAction, useEffect, useRef, useState } from "react" import fcose from "cytoscape-fcose"; @@ -74,20 +74,14 @@ function getStyle() { "overlay-opacity": 0, // hide gray box around active node }, }, - { - selector: "node:selected", - style: { - "border-width": 0.7, - } - }, { selector: "edge", style: { width: 1, - "line-color": "black", + "line-color": "white", "line-opacity": 0.7, "arrow-scale": 0.7, - "target-arrow-color": "black", + "target-arrow-color": "white", "target-arrow-shape": "triangle", 'curve-style': 'straight', }, @@ -97,15 +91,7 @@ function getStyle() { style: { "overlay-opacity": 0, }, - }, - { - selector: "edge:selected", - style: { - width: 2, - "line-opacity": 1, - "arrow-scale": 1, - } - }, + } ] return style } @@ -118,7 +104,6 @@ const getCreateQuery = (type: string, selectedNodes: NodeDataDefinition[], label } export default function SchemaView({ schema, setSchema }: Props) { - const [elements, setElements] = useState([]); const [selectedElement, setSelectedElement] = useState(); const [selectedNodes, setSelectedNodes] = useState([]); const [isCollapsed, setIsCollapsed] = useState(false); @@ -136,12 +121,8 @@ export default function SchemaView({ schema, setSchema }: Props) { }, [isAddRelation]) useEffect(() => { - setElements(schema.Elements) - }, [schema.Elements]) - - useEffect(() => { - chartRef?.current?.elements().layout(LAYOUT).run(); - }, [elements]); + chartRef?.current?.elements().layout(LAYOUT).run(); + }, [schema.Elements.length]); const onCategoryClick = (category: Category) => { const chart = chartRef.current @@ -336,10 +317,10 @@ export default function SchemaView({ schema, setSchema }: Props) { const json = await result.json() if (type === "node") { - setElements(schema.extendNode(json.result.data[0].n, [...schema.Elements])) + chartRef?.current?.add({ data: schema.extendNode(json.result.data[0].n) }) setIsAddEntity(false) } else { - setElements(schema.extendEdge(json.result.data[0].e, [...schema.Elements])) + chartRef?.current?.add({ data: schema.extendEdge(json.result.data[0].e) }) setIsAddRelation(false) } onExpand() @@ -391,7 +372,7 @@ export default function SchemaView({ schema, setSchema }: Props) { className="Canvas" layout={LAYOUT} stylesheet={getStyle()} - elements={elements} + elements={schema.Elements} cy={(cy) => { chartRef.current = cy diff --git a/app/schema/page.tsx b/app/schema/page.tsx index c84fa8b..41ce312 100644 --- a/app/schema/page.tsx +++ b/app/schema/page.tsx @@ -1,7 +1,7 @@ 'use client' import { useEffect, useState } from "react"; -import { Toast, prepareArg, securedFetch } from "@/lib/utils"; +import { Toast, defaultQuery, prepareArg, securedFetch } from "@/lib/utils"; import Header from "../components/Header"; import Selector from "../graph/Selector"; import SchemaView from "./SchemaView"; @@ -15,8 +15,7 @@ export default function Page() { useEffect(() => { if (!schemaName) return const run = async () => { - const q = "MATCH (n)-[e]-(m) RETURN n, e, m" - const result = await securedFetch(`/api/graph/${prepareArg(schemaName)}_schema/?query=${prepareArg(q)}`, { + const result = await securedFetch(`/api/graph/${prepareArg(schemaName)}_schema/?query=${defaultQuery()}`, { method: "GET" }) if (!result.ok) { @@ -33,7 +32,7 @@ export default function Page() {
- +
diff --git a/lib/utils.ts b/lib/utils.ts index b4a413d..422f10c 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -38,4 +38,4 @@ export function prepareArg(arg: string) { return encodeURIComponent(arg.trim()) } -export const defaultQuery = (q: string) => q || "MATCH (n) OPTIONAL MATCH (n)-[e]-(m) return n,e,m LIMIT 100" \ No newline at end of file +export const defaultQuery = (q?: string) => q || "MATCH (n) OPTIONAL MATCH (n)-[e]-(m) return n,e,m LIMIT 100" \ No newline at end of file From a7b4ff24cf5739f7b73780d36d9069769da72cdd Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Wed, 31 Jul 2024 12:06:08 +0300 Subject: [PATCH 4/7] fix add element --- app/api/graph/model.ts | 34 +- .../{DataPanel.tsx => GraphDataPanel.tsx} | 2 +- app/graph/GraphView.tsx | 2 +- app/graph/Selector.tsx | 39 +- .../SchemaCreateElement.tsx} | 127 +++--- app/schema/SchemaDataPanel.tsx | 411 ++++++++++++++++++ app/schema/SchemaView.tsx | 140 +++--- app/schema/page.tsx | 34 +- 8 files changed, 629 insertions(+), 160 deletions(-) rename app/graph/{DataPanel.tsx => GraphDataPanel.tsx} (99%) rename app/{components/graph/CreateElement.tsx => schema/SchemaCreateElement.tsx} (78%) create mode 100644 app/schema/SchemaDataPanel.tsx diff --git a/app/api/graph/model.ts b/app/api/graph/model.ts index 96e5688..8fd9ac9 100644 --- a/app/api/graph/model.ts +++ b/app/api/graph/model.ts @@ -120,10 +120,26 @@ export class Graph { return this.categories; } + set Categories(categories: Category[]) { + this.categories = categories; + } + + get CategoriesMap(): Map { + return this.categoriesMap; + } + get Labels(): Category[] { return this.labels; } + set Labels(labels: Category[]) { + this.labels = labels; + } + + get LabelsMap(): Map { + return this.labelsMap; + } + get Elements(): ElementDefinition[] { return this.elements; } @@ -210,7 +226,7 @@ export class Graph { const sourceId = cell.sourceId.toString(); const destinationId = cell.destinationId.toString() const edge: EdgeDataDefinition = { - _id: cell.id, + id: `_${cell.id}`, source: sourceId, target: destinationId, label: cell.relationshipType, @@ -284,7 +300,21 @@ export class Graph { } }) }) - + return newElements } + + public updateCategories(category: string, type: string) { + if (type === "node" && !this.elements.find(e => e.data.source ? e.data.label === category : e.data.category === category)) { + const i = this.categories.findIndex(({ name }) => name === category) + this.categories.splice(i, 1) + this.categoriesMap.delete(category) + } + + if (type === "edge" && !this.elements.find(e => e.data.source ? e.data.label === category : e.data.category === category)) { + const i = this.labels.findIndex(({ name }) => name === category) + this.labels.splice(i, 1) + this.labelsMap.delete(category) + } + } } \ No newline at end of file diff --git a/app/graph/DataPanel.tsx b/app/graph/GraphDataPanel.tsx similarity index 99% rename from app/graph/DataPanel.tsx rename to app/graph/GraphDataPanel.tsx index 355fd77..52f3aa9 100644 --- a/app/graph/DataPanel.tsx +++ b/app/graph/GraphDataPanel.tsx @@ -28,7 +28,7 @@ const excludedProperties = new Set([ "source", ]); -export default function DataPanel({ inSchema, obj, onExpand, setProperty, setPropertySchema, removeProperty, onDeleteElement }: Props) { +export default function GraphDataPanel({ inSchema, obj, onExpand, setProperty, setPropertySchema, removeProperty, onDeleteElement }: Props) { const [isAddValue, setIsAddValue] = useState(false) const [hover, setHover] = useState("") diff --git a/app/graph/GraphView.tsx b/app/graph/GraphView.tsx index ba44414..1402f74 100644 --- a/app/graph/GraphView.tsx +++ b/app/graph/GraphView.tsx @@ -12,7 +12,7 @@ import { ChevronLeft, Maximize2 } from "lucide-react" import { cn, prepareArg, securedFetch } from "@/lib/utils"; import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog"; import { Category, Graph } from "../api/graph/model"; -import DataPanel from "./DataPanel"; +import DataPanel from "./GraphDataPanel"; import Labels from "./labels"; import Toolbar from "./toolbar"; import Button from "../components/ui/Button"; diff --git a/app/graph/Selector.tsx b/app/graph/Selector.tsx index eeb34df..bdb505e 100644 --- a/app/graph/Selector.tsx +++ b/app/graph/Selector.tsx @@ -1,11 +1,11 @@ 'use client' -import { useEffect, useRef, useState } from "react"; +import { Dispatch, SetStateAction, useEffect, useRef, useState } from "react"; import { Dialog, DialogTitle, DialogTrigger } from "@/components/ui/dialog"; import { Editor } from "@monaco-editor/react"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"; import { editor } from "monaco-editor"; -import { Toast, cn, prepareArg, securedFetch } from "@/lib/utils"; +import { Toast, cn, securedFetch } from "@/lib/utils"; import Combobox from "../components/ui/combobox"; import { Graph, Query } from "../api/graph/model"; import UploadGraph from "../components/graph/UploadGraph"; @@ -14,13 +14,17 @@ import Button from "../components/ui/Button"; import Duplicate from "./Duplicate"; import SchemaView from "../schema/SchemaView"; -export default function Selector({ onChange, graph, queries, runQuery, isSchema }: { +export default function Selector({ onChange, graph, queries, runQuery, isSchema, edgesCount, nodesCount, setEdgesCount, setNodesCount }: { /* eslint-disable react/require-default-props */ onChange: (selectedGraphName: string) => void graph: Graph runQuery?: (query: string, setQueriesOpen: (open: boolean) => void) => Promise queries?: Query[] isSchema?: boolean + edgesCount: number + nodesCount: number + setEdgesCount: Dispatch> + setNodesCount: Dispatch> }) { const [options, setOptions] = useState([]); @@ -29,8 +33,6 @@ export default function Selector({ onChange, graph, queries, runQuery, isSchema const [duplicateOpen, setDuplicateOpen] = useState(false); const [dropOpen, setDropOpen] = useState(false); const [queriesOpen, setQueriesOpen] = useState(false); - const [edgesCount, setEdgesCount] = useState(0); - const [nodesCount, setNodesCount] = useState(0); const [query, setQuery] = useState(); const editorRef = useRef(null) @@ -56,31 +58,6 @@ export default function Selector({ onChange, graph, queries, runQuery, isSchema setSelectedValue(graph.Id) }, [graph.Id]) - useEffect(() => { - if (!selectedValue) return - const name = `${selectedValue}${isSchema ? "_schema" : ""}` - const run = async () => { - const q = [ - "MATCH (n) RETURN COUNT(n) as nodes", - "MATCH ()-[e]->() RETURN COUNT(e) as edges" - ] - - const nodes = await (await securedFetch(`api/graph/${prepareArg(name)}/?query=${q[0]}`, { - method: "GET" - })).json() - - const edges = await (await securedFetch(`api/graph/${prepareArg(name)}/?query=${q[1]}`, { - method: "GET" - })).json() - - if (!edges || !nodes) return - - setEdgesCount(edges.result?.data[0].edges) - setNodesCount(nodes.result?.data[0].nodes) - } - run() - }, [isSchema, selectedValue, graph.Elements.length]) - const handleEditorDidMount = (e: editor.IStandaloneCodeEditor) => { editorRef.current = e } @@ -282,7 +259,7 @@ export default function Selector({ onChange, graph, queries, runQuery, isSchema /> - + diff --git a/app/components/graph/CreateElement.tsx b/app/schema/SchemaCreateElement.tsx similarity index 78% rename from app/components/graph/CreateElement.tsx rename to app/schema/SchemaCreateElement.tsx index eab6450..9852a3a 100644 --- a/app/components/graph/CreateElement.tsx +++ b/app/schema/SchemaCreateElement.tsx @@ -7,36 +7,33 @@ import { ArrowRight, ArrowRightLeft, ChevronRight, Trash2 } from "lucide-react"; import { Checkbox } from "@/components/ui/checkbox"; import { NodeDataDefinition } from "cytoscape"; import { getCategoryColorNameFromValue } from "@/app/api/graph/model"; -import Input from "../ui/Input"; -import Button from "../ui/Button"; -import Combobox from "../ui/combobox"; - -const OPTIONS = ["String", "Integer", "Float", "Geospatial", "Boolean"] - -type Type = "String" | "Integer" | "Float" | "Geospatial" | "Boolean" | undefined -export type Attribute = [string, Type, string, boolean, boolean] +import Input from "../components/ui/Input"; +import Button from "../components/ui/Button"; +import Combobox from "../components/ui/combobox"; +import { Attribute, OPTIONS, Type } from "./SchemaDataPanel"; interface Props { - onCreate: (element: Attribute[], label?: string) => Promise + onCreate: (element: [string, Attribute][], label?: string) => Promise onExpand: () => void selectedNodes: NodeDataDefinition[] setSelectedNodes: Dispatch> type: "node" | "edge" } -const emptyAttribute = (): Attribute => ["", undefined, "", false, false] +const emptyAttribute = (): Attribute => [undefined, "", false, false] -export default function CreateElement({ onCreate, onExpand, selectedNodes, setSelectedNodes, type }: Props) { +export default function SchemaCreateElement({ onCreate, onExpand, selectedNodes, setSelectedNodes, type }: Props) { - const [attributes, setAttributes] = useState([]) + const [attributes, setAttributes] = useState<[string, Attribute][]>([]) const [attribute, setAttribute] = useState(emptyAttribute()) - const [value, setValue] = useState() + const [newKey, setNewKey] = useState() + const [newValue, setNewValue] = useState() const [label, setLabel] = useState() const [labelEditable, setLabelEditable] = useState(false) const [editable, setEditable] = useState("") const [hover, setHover] = useState("") - const onAddAttribute = (e: React.KeyboardEvent) => { + const handelAddAttribute = (e: React.KeyboardEvent) => { if (e.code === "Escape") { e.preventDefault() setAttribute(emptyAttribute()) @@ -46,19 +43,19 @@ export default function CreateElement({ onCreate, onExpand, selectedNodes, setSe if (e.key !== 'Enter') return e.preventDefault() - if (!attribute[0] || !attribute[1] || !attribute[2]) { + if (!newKey || !attribute[0] || !attribute[1]) { Toast('Please fill all the fields') return } - setAttributes(prev => [...prev, attribute]) + setAttributes(prev => [...prev, [newKey ,attribute]]) setAttribute(emptyAttribute()) } - const onSetAttribute = (e: React.KeyboardEvent) => { + const handelSetAttribute = (e: React.KeyboardEvent) => { if (e.code === "Escape") { e.preventDefault() - setValue(undefined) + setNewValue(undefined) setEditable("") return } @@ -67,7 +64,7 @@ export default function CreateElement({ onCreate, onExpand, selectedNodes, setSe e.preventDefault() - if (!value) { + if (!newValue) { Toast("Please fill the field") return } @@ -75,10 +72,10 @@ export default function CreateElement({ onCreate, onExpand, selectedNodes, setSe setAttributes(prev => { const p = prev const [index, i] = editable.split("-").map((v) => parseInt(v, 10)) - p[index][i] = value + p[index][i] = newValue return p }) - setValue(undefined) + setNewValue(undefined) } const handelOnCreate = async () => { @@ -99,7 +96,7 @@ export default function CreateElement({ onCreate, onExpand, selectedNodes, setSe } const handelCancel = () => { - setValue("") + setNewValue("") setEditable("") } @@ -135,7 +132,7 @@ export default function CreateElement({ onCreate, onExpand, selectedNodes, setSe onKeyDown={onSetLabel} /> :