From 0adc1efe88a41d7f32cc19f649ccc79cfbdb588f Mon Sep 17 00:00:00 2001 From: Anchel123 <110421452+Anchel123@users.noreply.github.com> Date: Tue, 16 Apr 2024 14:21:12 +0300 Subject: [PATCH 1/3] add multiple query sections --- app/components/combobox.tsx | 35 +---- app/graph/GraphList.tsx | 72 ---------- app/graph/GraphView.tsx | 4 +- app/graph/QuerySection.tsx | 77 +++++++++++ app/graph/page.tsx | 156 ++++++++++++---------- app/graph/query.tsx | 256 +++++++++++++++++++++++------------- 6 files changed, 332 insertions(+), 268 deletions(-) delete mode 100644 app/graph/GraphList.tsx create mode 100644 app/graph/QuerySection.tsx diff --git a/app/components/combobox.tsx b/app/components/combobox.tsx index dc882684..ec583c99 100644 --- a/app/components/combobox.tsx +++ b/app/components/combobox.tsx @@ -44,14 +44,12 @@ interface ComboboxProps { type?: string, options: string[], addOption?: Dispatch | null, - deleteOption?: (graphName :string) => void, selectedValue: string, setSelectedValue: Dispatch } -export default function Combobox({ className = '', type = '', options, addOption = null, deleteOption, selectedValue, setSelectedValue }: ComboboxProps) { +export default function Combobox({ className = '', type = 'Graph', options, addOption = null, selectedValue, setSelectedValue }: ComboboxProps) { const [open, setOpen] = useState(false) - const [deleteGraph, setDeleteGraph] = useState("") const inputRef = createRef() // read the text in the create input box and add it to the list of options @@ -65,13 +63,6 @@ export default function Combobox({ className = '', type = '', options, addOption } } - const onDeleteOption = (graphName: string) => { - setOpen(false) - if (deleteOption) { - deleteOption(graphName) - } - } - const handleKeyDown = (event: React.KeyboardEvent) => { if (event.key === "Enter") { onAddOption(); @@ -80,7 +71,6 @@ export default function Combobox({ className = '', type = '', options, addOption const entityType = type ?? "" return ( - } diff --git a/app/graph/QuerySection.tsx b/app/graph/QuerySection.tsx new file mode 100644 index 00000000..1599bf4c --- /dev/null +++ b/app/graph/QuerySection.tsx @@ -0,0 +1,77 @@ +import { Query } from "./query" +import { QueryState } from "./page" +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" +import MetaDataView from "./metadataview" +import GraphView, { GraphViewRef } from "./GraphView" +import { TableView } from "./tableview" +import { useEffect, useRef, useState } from "react" +import { Graph } from "./model" +import { useTheme } from "next-themes" + +export const QuerySection = ({ onSubmit, onDelete, queryState }: { + onSubmit: (e: React.FormEvent, queryState: QueryState) => Promise, + onDelete: (graphName: string) => void, + queryState: QueryState, +}) => { + + const [graph, setGraph] = useState(Graph.create(queryState.graphName, queryState.data)) + const [value, setValue] = useState() + const [metadata, setMetadata] = useState(queryState.data.metadata) + const graphView = useRef(null) + const showGraph = graph.Elements && graph.Elements.length > 0 + const showTable = graph.Data && graph.Data.length > 0 + const { theme, systemTheme } = useTheme() + const darkmode = theme === "dark" || (theme === "system" && systemTheme === "dark") + useEffect(() => { + if (showGraph) { + setValue("graph") + } else if (showTable) { + setValue("table") + } + }, [showTable, showGraph]) + + const handelSubmit = async (e: React.FormEvent, state: QueryState) => { + const data = await onSubmit(e, state) + setGraph(Graph.create(state.graphName, data)) + setMetadata(data.metadata) + graphView.current?.expand(graph.Elements) + } + + return ( +
+ + { + graph.Id && + <> +
+ { + (showTable || showGraph) && + +
+ + {showGraph && setValue("graph")}>Graph} + {showTable && setValue("table")}>Table} + +
+ + + + + + +
+ } +
+
+ +
+ + } +
+ ) +} \ No newline at end of file diff --git a/app/graph/page.tsx b/app/graph/page.tsx index dec6bbf6..226cce32 100644 --- a/app/graph/page.tsx +++ b/app/graph/page.tsx @@ -1,17 +1,27 @@ 'use client' import { toast } from "@/components/ui/use-toast"; -import { useEffect, useRef, useState } from "react"; -import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import React, { useState } from "react"; import { signOut } from "next-auth/react"; -import { useTheme } from "next-themes"; -import { Query, QueryState } from "./query"; -import { TableView } from "./tableview"; -import MetaDataView from "./metadataview"; -import { Graph } from "./model"; -import GraphView, { GraphViewRef } from "./GraphView"; +import { Query } from "./query"; +import { QuerySection } from "./QuerySection"; +import { Maximize2, X } from "lucide-react"; +import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog"; +export class QueryState { + public static count: number = 0; + + public id: number; + + constructor( + public graphName: string, + public query: string | undefined, + public data?: any, + ) { + this.id = QueryState.count++; + } +} // Validate the graph selection is not empty and show an error message if it is function validateGraphSelection(graphName: string): boolean { @@ -26,45 +36,32 @@ function validateGraphSelection(graphName: string): boolean { } export default function Page() { - const [graph, setGraph] = useState(Graph.empty()); - const [value, setValue] = useState(""); - const [metaData, setMetaData] = useState([]); - const [showGraph, setShowGraph] = useState(true); - const [showData, setShowData] = useState(true); - - useEffect(() => { - if (showGraph) { - setValue("graph") - } else if (showData) { - setValue("data") - } - }, [showData, showGraph]) - const graphView = useRef(null) - - // A reference to the query state to allow running the user query - const queryState = useRef(null) - - const { theme, systemTheme } = useTheme() - const darkmode = theme === "dark" || (theme === "system" && systemTheme === "dark") + const [queryStates, setQueryStates] = useState([]) + const [mainQueryState, setMainQueryState] = useState(null); + const iconSize = 15 function prepareArg(arg: string): string { return encodeURIComponent(arg.trim()) } - const runQuery = async (event: React.FormEvent) => { - event.preventDefault(); - const state = queryState.current; - if (!state) { - return false - } + const runMainQuery = async (event: React.FormEvent) => { + event.preventDefault() + if (!mainQueryState) return + const data = await runQuery(event, mainQueryState) + if (!data) return + const q = mainQueryState.query?.trim() || "MATCH (n) OPTIONAL MATCH (n)-[e]-(m) RETURN n,e,m limit 100"; + setQueryStates(prev => [new QueryState(mainQueryState.graphName, q, data), ...prev]) + } + const runQuery = async (event: React.FormEvent, queryState: QueryState) => { + event.preventDefault() // Proposed abstraction for improved modularity - if (!validateGraphSelection(state.graphName)) return false; + if (!validateGraphSelection(queryState.graphName)) return - const q = state.query?.trim() || "MATCH (n) OPTIONAL MATCH (n)-[e]-(m) RETURN n,e,m limit 100"; + const q = queryState.query?.trim() || "MATCH (n) OPTIONAL MATCH (n)-[e]-(m) RETURN n,e,m limit 100"; - const result = await fetch(`/api/graph?graph=${prepareArg(state.graphName)}&query=${prepareArg(q)}`, { + const result = await fetch(`/api/graph?graph=${prepareArg(queryState.graphName)}&query=${prepareArg(q)}`, { method: 'GET', headers: { 'Content-Type': 'application/json' @@ -78,54 +75,67 @@ export default function Page() { if (result.status >= 400 && result.status < 500) { signOut({ callbackUrl: '/login' }) } - return false + return } const json = await result.json() - setMetaData(json.result.metadata) - const newGraph = Graph.create(state.graphName, json.result) - setGraph(newGraph) - setShowGraph(!!newGraph.Categories && newGraph.Categories.length > 0) - setShowData(!!newGraph.Data && newGraph.Data.length > 0) - graphView.current?.expand(newGraph.Elements) - return true + return json.result } + const onDelete = (graphName: string) => { + setQueryStates((prev: QueryState[]) => prev.filter(state => state.graphName !== graphName)) + } + + const closeState = (id: number) => { + setQueryStates((prev: QueryState[]) => prev.filter(state => state.id !== id)) + } return (
{ queryState.current = state }} - onDelete={() => setGraph(Graph.empty())} /> -
+
    { - graph.Id && - <> - -
    - { - (showData || showGraph) && - - {showGraph && setValue("graph")}>Graph} - {showData && setValue("table")}>Table} - - } -
    - - - - - - -
    -
    - -
    - + queryStates.length > 0 && + queryStates.map((state) => { + return ( +
  • +
    +
    + + + + + + + + +
    + +
    +
    + +
    +
  • + ) + }) } -
+
) } diff --git a/app/graph/query.tsx b/app/graph/query.tsx index 07246ae1..0ce9586e 100644 --- a/app/graph/query.tsx +++ b/app/graph/query.tsx @@ -1,31 +1,29 @@ import { cn } from "@/lib/utils"; -import { useEffect, useState } from "react"; -import { Maximize, Play } from "lucide-react"; -import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; +import { FormEvent, useEffect, useState } from "react"; +import { Maximize, Menu, Play, Trash2 } from "lucide-react"; import Editor from "@monaco-editor/react"; import { useTheme } from "next-themes"; -import { editor } from "monaco-editor"; import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog"; -import GraphsList from "./GraphList"; +import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogTitle, AlertDialogTrigger, AlertDialogHeader, AlertDialogFooter } from "@/components/ui/alert-dialog"; +import { useToast } from "@/components/ui/use-toast"; +import { QueryState } from "./page"; +import { DropdownMenu, DropdownMenuContent, DropdownMenuTrigger, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuItem } from "@/components/ui/dropdown-menu"; +import Combobox from "../components/combobox"; -export class QueryState { - constructor( - public query: string | undefined, - public graphName: string, - ) { } -} - -export function Query({ onSubmit, onQueryUpdate, onDelete, className = "" }: { - onSubmit: (event: React.FormEvent) => Promise, - onQueryUpdate: (state: QueryState) => void, - onDelete: () => void, - className: string +export function Query({ onSubmit, onMainSubmit, setMainQueryState, queryState, onDelete, className = "" }: { + onSubmit?: (e: FormEvent, queryState: QueryState) => Promise, + onMainSubmit?: (e: FormEvent, queryState: QueryState) => Promise, + setMainQueryState?: (queryState: QueryState) => void, + queryState?: QueryState, + onDelete: (graphName: string) => void, + className: string, }) { const lineHeight = 40 - const [query, setQuery] = useState(); - const [graphName, setGraphName] = useState(''); + const [query, setQuery] = useState(queryState?.query); + const [graphName, setGraphName] = useState(queryState?.graphName || ''); const { theme, systemTheme } = useTheme() - const [monacoEditor, setMonacoEditor] = useState(null) + const [graphs, setGraphs] = useState([]); + const { toast } = useToast() const darkmode = theme === "dark" || (theme === "system" && systemTheme === "dark") const getHeight = () => { @@ -39,78 +37,160 @@ export function Query({ onSubmit, onQueryUpdate, onDelete, className = "" }: { } const height = getHeight(); + useEffect(() => { + fetch('/api/graph', { + method: 'GET', + headers: { + 'Content-Type': 'application/json' + } + }) + .then((result) => { + if (result.status < 300) { + return result.json() + } + toast({ + title: "Error", + description: result.text(), + }) + return { result: [] } + }).then((result) => { + setGraphs(result.result.graphs ?? []) + }) + }, [toast]) useEffect(() => { - if (monacoEditor) { - const scrollLine = query?.split("\n").length || lineHeight - monacoEditor.setScrollPosition({ scrollTop: scrollLine }); - } - }, [height]) + setMainQueryState && setMainQueryState({ id: 0, graphName, query }) + }, [query, graphName]) - onQueryUpdate(new QueryState(query, graphName)) + const handelDelete = (graphName: string) => { + setGraphName('') + onDelete(graphName) + setGraphs((prevGraphs: string[]) => [...prevGraphs.filter(graph => graph !== graphName)]); + fetch(`/api/graph/${encodeURIComponent(graphName)}`, { + method: 'DELETE', + }).then(() => + toast({ + title: 'Graph Deleted', + description: `Graph ${graphName} deleted`, + }) + ).catch((error) => { + toast({ + title: "Error", + description: error.message, + }) + }) + } + + const addOption = (newGraph: string) => { + setGraphs((prevGraphs: string[]) => [...prevGraphs, newGraph]); + setGraphName(newGraph) + } return ( - -
- -
- -
-
- - - - - - - - - - - - -

Run Query

-
-
-
-
- -
+
) => { + if (onSubmit) { + await onSubmit(e, { id: 0, graphName, query }) + } else if (onMainSubmit) { + await onMainSubmit(e, { id: 0, graphName, query }) + setQuery("") + } + }} + > + { + queryState ? +

{graphName}

+ : + } +
+ + { + !queryState && + + + + + + + + + } +
+ + { + !queryState && + + + + + + + +

Actions

+
+ + { + graphName && + + + Delete + + + + + Are you absolutely sure you? + + Are you absolutely sure you want to delete {graphName}? + + + + Cancel + handelDelete(graphName)}>Delete + + + + } +
+ + + } + ) } \ No newline at end of file From c0e5f69e19d4d6784b8f080d9c167db0ceaa48ba Mon Sep 17 00:00:00 2001 From: Anchel123 <110421452+Anchel123@users.noreply.github.com> Date: Thu, 18 Apr 2024 18:26:39 +0300 Subject: [PATCH 2/3] fix lint --- app/components/combobox.tsx | 13 +- .../{QuerySection.tsx => graphSection.tsx} | 27 ++-- app/graph/{query.tsx => mainQuery.tsx} | 145 ++++++++---------- app/graph/page.tsx | 127 +++++++-------- app/graph/sectionQuery.tsx | 92 +++++++++++ 5 files changed, 225 insertions(+), 179 deletions(-) rename app/graph/{QuerySection.tsx => graphSection.tsx} (85%) rename app/graph/{query.tsx => mainQuery.tsx} (51%) create mode 100644 app/graph/sectionQuery.tsx diff --git a/app/components/combobox.tsx b/app/components/combobox.tsx index ec583c99..540ba129 100644 --- a/app/components/combobox.tsx +++ b/app/components/combobox.tsx @@ -1,7 +1,7 @@ "use client" import { useState, Dispatch, createRef } from "react" -import { Check, ChevronsUpDown, Trash2 } from "lucide-react" +import { Check, ChevronsUpDown } from "lucide-react" import { Dialog, DialogContent, @@ -26,17 +26,6 @@ import { } from "@/components/ui/popover" import { Separator } from "@/components/ui/separator" import { Input } from "@/components/ui/input" -import { - AlertDialog, - AlertDialogAction, - AlertDialogCancel, - AlertDialogContent, - AlertDialogDescription, - AlertDialogHeader, - AlertDialogTitle, - AlertDialogTrigger, - AlertDialogFooter -} from "@/components/ui/alert-dialog" /* eslint-disable react/require-default-props */ interface ComboboxProps { diff --git a/app/graph/QuerySection.tsx b/app/graph/graphSection.tsx similarity index 85% rename from app/graph/QuerySection.tsx rename to app/graph/graphSection.tsx index 1599bf4c..49aa2c79 100644 --- a/app/graph/QuerySection.tsx +++ b/app/graph/graphSection.tsx @@ -1,18 +1,17 @@ -import { Query } from "./query" -import { QueryState } from "./page" +import { useTheme } from "next-themes" +import { useEffect, useRef, useState } from "react" import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" +import SectionQuery, { GraphState } from "./sectionQuery" import MetaDataView from "./metadataview" import GraphView, { GraphViewRef } from "./GraphView" import { TableView } from "./tableview" -import { useEffect, useRef, useState } from "react" import { Graph } from "./model" -import { useTheme } from "next-themes" -export const QuerySection = ({ onSubmit, onDelete, queryState }: { - onSubmit: (e: React.FormEvent, queryState: QueryState) => Promise, - onDelete: (graphName: string) => void, - queryState: QueryState, -}) => { +export default function GraphSection({ onSubmit, queryState }: { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + onSubmit: (e: React.FormEvent, graphName: string, query: string) => Promise, + queryState: GraphState, +}) { const [graph, setGraph] = useState(Graph.create(queryState.graphName, queryState.data)) const [value, setValue] = useState() @@ -22,6 +21,7 @@ export const QuerySection = ({ onSubmit, onDelete, queryState }: { const showTable = graph.Data && graph.Data.length > 0 const { theme, systemTheme } = useTheme() const darkmode = theme === "dark" || (theme === "system" && systemTheme === "dark") + useEffect(() => { if (showGraph) { setValue("graph") @@ -30,19 +30,18 @@ export const QuerySection = ({ onSubmit, onDelete, queryState }: { } }, [showTable, showGraph]) - const handelSubmit = async (e: React.FormEvent, state: QueryState) => { - const data = await onSubmit(e, state) - setGraph(Graph.create(state.graphName, data)) + const handelSubmit = async (e: React.FormEvent, graphName: string, query: string) => { + const data = await onSubmit(e, graphName, query) + setGraph(Graph.create(graphName, data)) setMetadata(data.metadata) graphView.current?.expand(graph.Elements) } return (
- { diff --git a/app/graph/query.tsx b/app/graph/mainQuery.tsx similarity index 51% rename from app/graph/query.tsx rename to app/graph/mainQuery.tsx index 0ce9586e..18ff214f 100644 --- a/app/graph/query.tsx +++ b/app/graph/mainQuery.tsx @@ -6,25 +6,22 @@ import { useTheme } from "next-themes"; import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogTitle, AlertDialogTrigger, AlertDialogHeader, AlertDialogFooter } from "@/components/ui/alert-dialog"; import { useToast } from "@/components/ui/use-toast"; -import { QueryState } from "./page"; import { DropdownMenu, DropdownMenuContent, DropdownMenuTrigger, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuItem } from "@/components/ui/dropdown-menu"; import Combobox from "../components/combobox"; -export function Query({ onSubmit, onMainSubmit, setMainQueryState, queryState, onDelete, className = "" }: { - onSubmit?: (e: FormEvent, queryState: QueryState) => Promise, - onMainSubmit?: (e: FormEvent, queryState: QueryState) => Promise, - setMainQueryState?: (queryState: QueryState) => void, - queryState?: QueryState, +export default function MainQuery({ onSubmit, onDelete, className = "" }: { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + onSubmit: (e: FormEvent, graphName: string, query: string) => Promise, onDelete: (graphName: string) => void, className: string, }) { const lineHeight = 40 - const [query, setQuery] = useState(queryState?.query); - const [graphName, setGraphName] = useState(queryState?.graphName || ''); + const [query, setQuery] = useState(""); + const [graphName, setGraphName] = useState(""); const { theme, systemTheme } = useTheme() + const darkmode = theme === "dark" || (theme === "system" && systemTheme === "dark") const [graphs, setGraphs] = useState([]); const { toast } = useToast() - const darkmode = theme === "dark" || (theme === "system" && systemTheme === "dark") const getHeight = () => { if (!query) return lineHeight @@ -37,41 +34,37 @@ export function Query({ onSubmit, onMainSubmit, setMainQueryState, queryState, o } const height = getHeight(); + useEffect(() => { fetch('/api/graph', { method: 'GET', headers: { 'Content-Type': 'application/json' } - }) - .then((result) => { - if (result.status < 300) { - return result.json() - } - toast({ - title: "Error", - description: result.text(), - }) - return { result: [] } - }).then((result) => { - setGraphs(result.result.graphs ?? []) + }).then((result) => { + if (result.status < 300) { + return result.json() + } + toast({ + title: "Error", + description: result.text(), }) + return { result: [] } + }).then((result) => { + setGraphs(result.result.graphs ?? []) + }) }, [toast]) - useEffect(() => { - setMainQueryState && setMainQueryState({ id: 0, graphName, query }) - }, [query, graphName]) - - const handelDelete = (graphName: string) => { + const handelDelete = (name: string) => { setGraphName('') - onDelete(graphName) - setGraphs((prevGraphs: string[]) => [...prevGraphs.filter(graph => graph !== graphName)]); - fetch(`/api/graph/${encodeURIComponent(graphName)}`, { + onDelete(name) + setGraphs((prevGraphs: string[]) => [...prevGraphs.filter(graph => graph !== name)]); + fetch(`/api/graph/${encodeURIComponent(name)}`, { method: 'DELETE', }).then(() => toast({ title: 'Graph Deleted', - description: `Graph ${graphName} deleted`, + description: `Graph ${name} deleted`, }) ).catch((error) => { toast({ @@ -90,25 +83,17 @@ export function Query({ onSubmit, onMainSubmit, setMainQueryState, queryState, o
) => { - if (onSubmit) { - await onSubmit(e, { id: 0, graphName, query }) - } else if (onMainSubmit) { - await onMainSubmit(e, { id: 0, graphName, query }) - setQuery("") - } + await onSubmit(e, graphName, query) + setQuery("") }} > - { - queryState ? -

{graphName}

- : - } +
val && setQuery(val)} theme={`${darkmode ? "vs-dark" : "light"}`} language="cypher" options={{ @@ -132,30 +117,29 @@ export function Query({ onSubmit, onMainSubmit, setMainQueryState, queryState, o }, }} /> - { - !queryState && - - - - - - - - - } + + + + + + val && setQuery(val)} + theme={`${darkmode ? "vs-dark" : "light"}`} + language="cypher" + /> + +
{ - !queryState && + graphName && @@ -166,27 +150,24 @@ export function Query({ onSubmit, onMainSubmit, setMainQueryState, queryState, o

Actions

- { - graphName && - - - Delete - - - - - Are you absolutely sure you? - - Are you absolutely sure you want to delete {graphName}? - - - - Cancel - handelDelete(graphName)}>Delete - - - - } + + + Delete + + + + + Are you absolutely sure you? + + Are you absolutely sure you want to delete {graphName}? + + + + Cancel + handelDelete(graphName)}>Delete + + +
diff --git a/app/graph/page.tsx b/app/graph/page.tsx index 226cce32..19b962a5 100644 --- a/app/graph/page.tsx +++ b/app/graph/page.tsx @@ -3,25 +3,11 @@ import { toast } from "@/components/ui/use-toast"; import React, { useState } from "react"; import { signOut } from "next-auth/react"; -import { Query } from "./query"; -import { QuerySection } from "./QuerySection"; -import { Maximize2, X } from "lucide-react"; import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog"; - -export class QueryState { - - public static count: number = 0; - - public id: number; - - constructor( - public graphName: string, - public query: string | undefined, - public data?: any, - ) { - this.id = QueryState.count++; - } -} +import { Maximize2, X } from "lucide-react"; +import Query from "./mainQuery"; +import GraphSection from "./graphSection"; +import { GraphState } from "./sectionQuery"; // Validate the graph selection is not empty and show an error message if it is function validateGraphSelection(graphName: string): boolean { @@ -37,31 +23,25 @@ function validateGraphSelection(graphName: string): boolean { export default function Page() { - const [queryStates, setQueryStates] = useState([]) - const [mainQueryState, setMainQueryState] = useState(null); + const [queryStates, setQueryStates] = useState([]) const iconSize = 15 function prepareArg(arg: string): string { return encodeURIComponent(arg.trim()) } - const runMainQuery = async (event: React.FormEvent) => { - event.preventDefault() - if (!mainQueryState) return - const data = await runQuery(event, mainQueryState) - if (!data) return - const q = mainQueryState.query?.trim() || "MATCH (n) OPTIONAL MATCH (n)-[e]-(m) RETURN n,e,m limit 100"; - setQueryStates(prev => [new QueryState(mainQueryState.graphName, q, data), ...prev]) - } - - const runQuery = async (event: React.FormEvent, queryState: QueryState) => { + const defaultQuery = (q: string) => + q.trim() || "MATCH (n) OPTIONAL MATCH (n)-[e]-(m) RETURN n,e,m limit 100"; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const runQuery = async (event: React.FormEvent, graphName: string, query: string): Promise => { event.preventDefault() // Proposed abstraction for improved modularity - if (!validateGraphSelection(queryState.graphName)) return + if (!validateGraphSelection(graphName)) return null - const q = queryState.query?.trim() || "MATCH (n) OPTIONAL MATCH (n)-[e]-(m) RETURN n,e,m limit 100"; + const q = defaultQuery(query) - const result = await fetch(`/api/graph?graph=${prepareArg(queryState.graphName)}&query=${prepareArg(q)}`, { + const result = await fetch(`/api/graph?graph=${prepareArg(graphName)}&query=${prepareArg(q)}`, { method: 'GET', headers: { 'Content-Type': 'application/json' @@ -75,65 +55,70 @@ export default function Page() { if (result.status >= 400 && result.status < 500) { signOut({ callbackUrl: '/login' }) } - return + return null } const json = await result.json() return json.result } + + const runMainQuery = async (event: React.FormEvent, graphName: string, query: string) => { + event.preventDefault() + const data = await runQuery(event, graphName, query) + if (!data) return + const q = defaultQuery(query) + setQueryStates(prev => [new GraphState(graphName, q, data), ...prev]) + } const onDelete = (graphName: string) => { - setQueryStates((prev: QueryState[]) => prev.filter(state => state.graphName !== graphName)) + setQueryStates((prev: GraphState[]) => prev.filter(state => state.graphName !== graphName)) } const closeState = (id: number) => { - setQueryStates((prev: QueryState[]) => prev.filter(state => state.id !== id)) + setQueryStates((prev: GraphState[]) => prev.filter(state => state.id !== id)) } return (
    { queryStates.length > 0 && - queryStates.map((state) => { - return ( -
  • -
    -
    - - - - - - - - -
    - -
    -
    - + queryStates.map((state) => ( +
  • +
    +
    + + + + + + + +
    -
  • - ) - }) + +
+
+ +
+ + )) }
diff --git a/app/graph/sectionQuery.tsx b/app/graph/sectionQuery.tsx new file mode 100644 index 00000000..2a7e20c1 --- /dev/null +++ b/app/graph/sectionQuery.tsx @@ -0,0 +1,92 @@ +import { cn } from "@/lib/utils"; +import { FormEvent, useState } from "react"; +import { Play } from "lucide-react"; +import Editor from "@monaco-editor/react"; +import { useTheme } from "next-themes"; + +export class GraphState { + + public static count: number = 0; + + public id: number; + + constructor( + public graphName: string, + public query: string, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + public data?: any, + ) { + this.id = GraphState.count; + GraphState.count += 1; + } +} + +export default function SectionQuery({ onSubmit, queryState, className = "" }: { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + onSubmit: (e: FormEvent, graphName: string, query: string) => Promise, + queryState: GraphState, + className: string, +}) { + const lineHeight = 40 + const { graphName } = queryState + const [query, setQuery] = useState(queryState.query); + const { theme, systemTheme } = useTheme() + const darkmode = theme === "dark" || (theme === "system" && systemTheme === "dark") + + const getHeight = () => { + if (!query) return lineHeight + switch (query.split("\n").length) { + case 1: return lineHeight + case 2: return lineHeight * 2 + case 3: return lineHeight * 3 + default: return lineHeight * 4 + } + } + + const height = getHeight(); + + return ( + ) => { + await onSubmit(e, graphName, query) + }} + > + {graphName} +
+ val && setQuery(val)} + theme={`${darkmode ? "vs-dark" : "light"}`} + language="cypher" + options={{ + lineDecorationsWidth: 10, + lineNumbersMinChars: 2, + scrollBeyondLastLine: false, + suggest: { + showKeywords: true, + }, + minimap: { enabled: false }, + wordWrap: "on", + lineHeight, + fontSize: 30, + find: { + addExtraSpaceOnTop: false, + autoFindInSelection: "never", + seedSearchStringFromSelection: "never", + }, + scrollbar: { + horizontal: "hidden", + }, + }} + /> +
+ + + ) +} \ No newline at end of file From c1f0fc473733b1b07fb66704cfa8a761b6ae8540 Mon Sep 17 00:00:00 2001 From: Anchel123 <110421452+Anchel123@users.noreply.github.com> Date: Sun, 21 Apr 2024 10:40:17 +0300 Subject: [PATCH 3/3] Update route.ts --- app/api/graph/[graph]/route.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/app/api/graph/[graph]/route.ts b/app/api/graph/[graph]/route.ts index e98a8309..a741e18e 100644 --- a/app/api/graph/[graph]/route.ts +++ b/app/api/graph/[graph]/route.ts @@ -24,7 +24,6 @@ export async function DELETE(request: NextRequest, { params }: { params: { graph const graph = client.selectGraph(graphId); await graph.delete() - console.log(client.list()); return NextResponse.json({ message: `${graphId} graph deleted` }) }