Skip to content

Commit

Permalink
Merge pull request #177 from FalkorDB/small-styling-changes
Browse files Browse the repository at this point in the history
fix #176  re-style
  • Loading branch information
AviAvni authored Apr 21, 2024
2 parents a1aba7a + 1d50241 commit d5525df
Show file tree
Hide file tree
Showing 9 changed files with 348 additions and 338 deletions.
177 changes: 112 additions & 65 deletions app/components/combobox.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
"use client"

import { useState, Dispatch, createRef } from "react"
import { Check, ChevronsUpDown } from "lucide-react"
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog"

import { Check, ChevronsUpDown, Trash2 } from "lucide-react"
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger
} from "@/components/ui/dialog"
import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
import {
Expand All @@ -20,20 +26,32 @@ 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 {
className?: string,
type?: string,
options: string[],
addOption?: Dispatch<string>|null,
addOption?: Dispatch<string> | null,
deleteOption?: (graphName :string) => void,
selectedValue: string,
setSelectedValue: Dispatch<string>
}

export default function Combobox({ className='', type='', options, addOption=null, selectedValue, setSelectedValue }: ComboboxProps) {
export default function Combobox({ className = '', type = '', options, addOption = null, deleteOption, selectedValue, setSelectedValue }: ComboboxProps) {
const [open, setOpen] = useState(false)
const [deleteGraph, setDeleteGraph] = useState<string>("")
const inputRef = createRef<HTMLInputElement>()

// read the text in the create input box and add it to the list of options
Expand All @@ -47,6 +65,13 @@ export default function Combobox({ className='', type='', options, addOption=nul
}
}

const onDeleteOption = (graphName: string) => {
setOpen(false)
if (deleteOption) {
deleteOption(graphName)
}
}

const handleKeyDown = (event: React.KeyboardEvent) => {
if (event.key === "Enter") {
onAddOption();
Expand All @@ -55,65 +80,87 @@ export default function Combobox({ className='', type='', options, addOption=nul

const entityType = type ?? ""
return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
aria-expanded={open}
className={`w-[200px] justify-between ${className} `}
>
{selectedValue
? options.find((option) => option === selectedValue)
: `Select ${entityType}...`}
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-[200px] p-0">
<Command>
<CommandInput placeholder="Search framework..." />
<CommandEmpty>No framework found.</CommandEmpty>
<CommandGroup>
{options.map((option) => (
<CommandItem
key={option}
onSelect={(currentValue) => {
if (currentValue !== selectedValue) {
setSelectedValue(option)
<AlertDialog>
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
aria-expanded={open}
className={`w-[200px] justify-between ${className} `}
>
{selectedValue
? options.find((option) => option === selectedValue)
: `Select ${entityType}...`}
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-[200px] p-0">
<Command>
<CommandInput placeholder="Search framework..." />
<CommandEmpty>No framework found.</CommandEmpty>
<CommandGroup>
{options.map((option) => (
<CommandItem
className="w-full flex flex-row justify-between px-6"
key={option}
onSelect={(currentValue) => {
if (currentValue !== selectedValue) {
setSelectedValue(option)
}
setOpen(false)
}}
>
<div className="flex flex-row">
<Check
className={cn(
"mr-2 h-4 w-4",
selectedValue === option ? "opacity-100" : "opacity-0"
)}
/>
{option}
</div>
{
deleteOption &&
<AlertDialogTrigger onClick={() => setDeleteGraph(option)}>
<Trash2 />
</AlertDialogTrigger>
}
setOpen(false)
}}
>
<Check
className={cn(
"mr-2 h-4 w-4",
selectedValue === option ? "opacity-100" : "opacity-0"
)}
/>
{option}
</CommandItem>
))}
<Separator orientation="horizontal" />

{addOption &&
<Dialog>
<DialogTrigger>
<CommandItem>Create new {entityType}...</CommandItem>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Create a new {entityType}?</DialogTitle>
<DialogDescription>
<Input type="text" ref={inputRef} id="create" name="create" onKeyDown={handleKeyDown} placeholder={`${entityType} name ...`} />
</DialogDescription>
</DialogHeader>
<Button className="p-4" type="submit" onClick={onAddOption}>Create</Button>
</DialogContent>
</Dialog>
}
</CommandGroup>
</Command>
</PopoverContent>
</Popover>
</CommandItem>
))}
<Separator orientation="horizontal" />
{addOption &&
<Dialog>
<DialogTrigger>
<CommandItem>Create new {entityType}...</CommandItem>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Create a new {entityType}?</DialogTitle>
<DialogDescription>
<Input type="text" ref={inputRef} id="create" name="create" onKeyDown={handleKeyDown} placeholder={`${entityType} name ...`} />
</DialogDescription>
</DialogHeader>
<Button className="p-4" type="submit" onClick={onAddOption}>Create</Button>
</DialogContent>
</Dialog>
}
</CommandGroup>
</Command>
</PopoverContent>
</Popover>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
<AlertDialogDescription>
Are you sure you want to delete {deleteGraph}?
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction onClick={() => onDeleteOption(deleteGraph)}>Delete</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
)
}
9 changes: 5 additions & 4 deletions app/graph/DataPanel.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
import { Table, TableBody, TableCaption, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";

interface Props {
Expand All @@ -17,9 +17,10 @@ const excludedProperties = new Set([
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export default function DataPanel({ object }: Props) {
const rowClass = "dark:hover:bg-slate-700 hover:bg-gray-400 border-y-[1px] border-y-gray-700"

const type = object.source ? "edge" : "node"
return (
<Table>
<TableCaption>{type} properties</TableCaption>
<TableHeader>
<TableRow className={rowClass}>
<TableHead>Field</TableHead>
Expand All @@ -38,10 +39,10 @@ export default function DataPanel({ object }: Props) {
const text = cellIndex === 1 ? JSON.parse(strCell) : strCell
return (
// eslint-disable-next-line react/no-array-index-key
<TableCell key={cellIndex}>
<TableCell className="max-w-10" key={cellIndex}>
<TooltipProvider>
<Tooltip>
<TooltipTrigger className="max-w-96 truncate">
<TooltipTrigger className="w-full truncate" >
{text}
</TooltipTrigger>
<TooltipContent>
Expand Down
34 changes: 26 additions & 8 deletions app/graph/GraphList.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { useState, useEffect, Dispatch, SetStateAction } from 'react';
import { useToast } from "@/components/ui/use-toast"
import { useToast } from "@/components/ui/use-toast"
import Combobox from '../components/combobox';

interface Props {
onSelectedGraph: Dispatch<SetStateAction<string>>,
onDelete: boolean,
onDelete: () => void,
}
// A component that renders an input box for Cypher queries
export default function GraphsList({onSelectedGraph, onDelete}: Props) {
export default function GraphsList({ onSelectedGraph, onDelete }: Props) {

const [graphs, setGraphs] = useState<string[]>([]);
const [selectedGraph, setSelectedGraph] = useState("");
Expand All @@ -33,10 +33,21 @@ export default function GraphsList({onSelectedGraph, onDelete}: Props) {
})
}, [toast])

useEffect(() => {
setGraphs((prevGraphs: string[]) => [...prevGraphs.filter((graph) => graph !== selectedGraph)])
setSelectedGraph('')
}, [onDelete])
const handelDelete = (graphName: string) => {
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 setSelectedValue = (graph: string) => {
setSelectedGraph(graph)
Expand All @@ -48,7 +59,14 @@ export default function GraphsList({onSelectedGraph, onDelete}: Props) {
setSelectedValue(newGraph)
}

const deleteOption = (graphName: string) => {
setGraphs((prevGraphs: string[]) => [...prevGraphs.filter(graph => graph !== graphName)]);
setSelectedValue("")
handelDelete(graphName)
onDelete()
}

return (
<Combobox type="Graph" options={graphs} addOption={addOption} selectedValue={selectedGraph} setSelectedValue={setSelectedValue} />
<Combobox type="Graph" options={graphs} addOption={addOption} deleteOption={deleteOption} selectedValue={selectedGraph} setSelectedValue={setSelectedValue} />
)
}
45 changes: 28 additions & 17 deletions app/graph/GraphView.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import CytoscapeComponent from "react-cytoscapejs";
import { toast } from "@/components/ui/use-toast";
import cytoscape, { ElementDefinition, EventObject, NodeDataDefinition } from "cytoscape";
import { useRef, useState, useImperativeHandle, forwardRef, useEffect } from "react";
import { useRef, useState, useImperativeHandle, forwardRef } from "react";
import { signOut } from "next-auth/react";
import fcose from 'cytoscape-fcose';
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@/components/ui/resizable";
Expand Down Expand Up @@ -98,18 +98,12 @@ const GraphView = forwardRef(({ graph, darkmode }: GraphViewProps, ref) => {

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const [selectedObject, setSelectedObject] = useState<any | null>(null);
const [isOpen, setIsOpen] = useState<boolean>(false);
const [isCollapsed, setIsCollapsed] = useState<boolean>(true);

// A reference to the chart container to allowing zooming and editing
const chartRef = useRef<cytoscape.Core | null>(null)
const dataPanel = useRef<ImperativePanelHandle>(null)

useEffect(() => {
if (isOpen) {
dataPanel.current?.expand()
} else dataPanel.current?.collapse()
}, [isOpen])

useImperativeHandle(ref, () => ({
expand: (elements: ElementDefinition[]) => {
const chart = chartRef.current
Expand All @@ -121,6 +115,16 @@ const GraphView = forwardRef(({ graph, darkmode }: GraphViewProps, ref) => {
}
}))

const onExpand = () => {
if (dataPanel.current) {
if (dataPanel.current.isCollapsed()) {
dataPanel.current.expand()
} else {
dataPanel.current.collapse()
}
}
}

// Send the user query to the server to expand a node
async function onFetchNode(node: NodeDataDefinition) {
const result = await fetch(`/api/graph/${graph.Id}/${node.id}`, {
Expand Down Expand Up @@ -177,7 +181,7 @@ const GraphView = forwardRef(({ graph, darkmode }: GraphViewProps, ref) => {
const handleTap = (evt: EventObject) => {
const object = evt.target.json().data;
setSelectedObject(object);
setIsOpen(true);
dataPanel.current?.expand()
}

return (
Expand Down Expand Up @@ -212,16 +216,23 @@ const GraphView = forwardRef(({ graph, darkmode }: GraphViewProps, ref) => {
<ResizableHandle />
{
selectedObject &&
<button type="button" onClick={() => setIsOpen(prev => !prev)} className="fixed right-5 top-[50%]">
{isOpen ? <ChevronRight /> : <ChevronLeft />}
<button title={isCollapsed ? "open" : "close"} type="button" onClick={() => onExpand()} className="fixed right-5 top-[50%]">
{!isCollapsed ? <ChevronRight /> : <ChevronLeft />}
</button>
}
{
isOpen &&
<ResizablePanel id="panel" ref={dataPanel} maxSize={50} minSize={20} collapsible defaultSize={selectedObject ? 20 : 0} className="bg-gray-100 dark:bg-gray-800">
{selectedObject && <DataPanel object={selectedObject} />}
</ResizablePanel>
}
<ResizablePanel
id="panel"
ref={dataPanel}
maxSize={50}
minSize={20}
onCollapse={() => { setIsCollapsed(true) }}
onExpand={() => { setIsCollapsed(false) }}
collapsible
defaultSize={selectedObject ? 20 : 0}
className="bg-gray-100 dark:bg-gray-800"
>
{selectedObject && <DataPanel object={selectedObject} />}
</ResizablePanel>
</ResizablePanelGroup>
)
});
Expand Down
Loading

0 comments on commit d5525df

Please sign in to comment.