diff --git a/app/GTM.tsx b/app/GTM.tsx new file mode 100644 index 00000000..889963ea --- /dev/null +++ b/app/GTM.tsx @@ -0,0 +1,15 @@ +"use client" + +import { useEffect } from "react"; +import TagManager from "react-gtm-module"; + +export default function GTM() { + useEffect(() => { + const gtmId = process.env.NEXT_PUBLIC_GTM_ID + if (gtmId) { + TagManager.initialize({ gtmId }); + } + }, []); + + return null; +} \ No newline at end of file diff --git a/app/api/utils.ts b/app/api/utils.ts index 4b759fe9..b6bb6bf6 100644 --- a/app/api/utils.ts +++ b/app/api/utils.ts @@ -1,13 +1,12 @@ export function getEnvVariables() { - const url = process.env.BACKEND_URL - const token = process.env.SECRET_TOKEN + const { SECRET_TOKEN, BACKEND_URL } = process.env - if (!url) { + if (!BACKEND_URL) { throw new Error("Environment variable BACKEND_URL must be set"); } - if (!token) { + if (!SECRET_TOKEN) { throw new Error("Environment variable SECRET_TOKEN must be set"); } - return { url, token } + return { url: BACKEND_URL, token: SECRET_TOKEN }; } \ No newline at end of file diff --git a/app/components/Input.tsx b/app/components/Input.tsx index 11f9fdaa..83ed01be 100644 --- a/app/components/Input.tsx +++ b/app/components/Input.tsx @@ -33,6 +33,8 @@ export default function Input({ value, onValueChange, handleSubmit, graph, icon, }, [open]) useEffect(() => { + if (!graph.Id) return + let isLastRequest = true const timeout = setTimeout(async () => { @@ -75,7 +77,7 @@ export default function Input({ value, onValueChange, handleSubmit, graph, icon, clearTimeout(timeout) isLastRequest = false } - }, [value]) + }, [value, graph.Id]) const handleKeyDown = (e: React.KeyboardEvent) => { const container = containerRef.current diff --git a/app/components/chat.tsx b/app/components/chat.tsx index 80d55fb3..f11f2476 100644 --- a/app/components/chat.tsx +++ b/app/components/chat.tsx @@ -1,17 +1,20 @@ import { toast } from "@/components/ui/use-toast"; -import { Dispatch, FormEvent, MutableRefObject, SetStateAction, useEffect, useRef, useState } from "react"; +import { Dispatch, FormEvent, SetStateAction, useEffect, useRef, useState } from "react"; import Image from "next/image"; import { AlignLeft, ArrowDown, ArrowRight, ChevronDown, Lightbulb, Undo2 } from "lucide-react"; import { Path } from "../page"; import Input from "./Input"; -import { Graph } from "./model"; +import { Graph, GraphData } from "./model"; import { cn } from "@/lib/utils"; -import { LAYOUT } from "./code-graph"; import { TypeAnimation } from "react-type-animation"; import { DropdownMenu, DropdownMenuContent, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"; -import cytoscape from "cytoscape"; import { prepareArg } from "../utils"; +type PathData = { + nodes: any[] + links: any[] +} + enum MessageTypes { Query, Response, @@ -68,7 +71,8 @@ const SELECTED_PATH_NODE_STYLE = { interface Message { type: MessageTypes; text?: string; - paths?: { nodes: any[], edges: any[] }[]; + paths?: { nodes: any[], links: any[] }[]; + graphName?: string; } interface Props { @@ -76,10 +80,10 @@ interface Props { path: Path | undefined setPath: Dispatch> graph: Graph - chartRef: MutableRefObject - selectedPathId: string | undefined - isPath: boolean - setIsPath: (isPathResponse: boolean) => void + selectedPathId: number | undefined + isPathResponse: boolean | undefined + setIsPathResponse: (isPathResponse: boolean | undefined) => void + setData: Dispatch> } const SUGGESTIONS = [ @@ -92,47 +96,45 @@ const SUGGESTIONS = [ const RemoveLastPath = (messages: Message[]) => { const index = messages.findIndex((m) => m.type === MessageTypes.Path) - + if (index !== -1) { messages = [...messages.slice(0, index - 2), ...messages.slice(index + 1)]; messages = RemoveLastPath(messages) } - + return messages } -export function Chat({ repo, path, setPath, graph, chartRef, selectedPathId, isPath, setIsPath }: Props) { - +export function Chat({ repo, path, setPath, graph, selectedPathId, isPathResponse, setIsPathResponse, setData }: Props) { + // Holds the messages in the chat const [messages, setMessages] = useState([]); - + // Holds the messages in the chat - const [paths, setPaths] = useState<{ nodes: any[], edges: any[] }[]>([]); - - const [selectedPath, setSelectedPath] = useState<{ nodes: any[], edges: any[] }>(); - + const [paths, setPaths] = useState([]); + + const [selectedPath, setSelectedPath] = useState(); + // Holds the user input while typing const [query, setQuery] = useState(''); - - const [isPathResponse, setIsPathResponse] = useState(false); - + const [tipOpen, setTipOpen] = useState(false); - + const [sugOpen, setSugOpen] = useState(false); - + // A reference to the chat container to allow scrolling to the bottom const containerRef: React.RefObject = useRef(null); - + const isSendMessage = messages.some(m => m.type === MessageTypes.Pending) || (messages.some(m => m.text === "Please select a starting point and the end point. Select or press relevant item on the graph") && !messages.some(m => m.type === MessageTypes.Path)) - + useEffect(() => { - const p = paths.find((path) => [...path.edges, ...path.nodes].some((e: any) => e.id === selectedPathId)) - + const p = paths.find((path) => [...path.links, ...path.nodes].some((e: any) => e.id === selectedPathId)) + if (!p) return - - handleSetSelectedPath(p) + + handelSetSelectedPath(p) }, [selectedPathId]) - + // Scroll to the bottom of the chat on new message useEffect(() => { setTimeout(() => { @@ -145,74 +147,65 @@ export function Chat({ repo, path, setPath, graph, chartRef, selectedPathId, isP }, [path]) useEffect(() => { - if (isPath) return + if (isPathResponse || isPathResponse === undefined) return setIsPathResponse(false) setSelectedPath(undefined) setPaths([]) - }, [isPath]) - - useEffect(() => { - setIsPath(isPathResponse) }, [isPathResponse]) - const updatePreviousPath = (chart: cytoscape.Core, p: { nodes: any[], edges: any[] }) => { + const handelSetSelectedPath = (p: PathData) => { setSelectedPath(prev => { if (prev) { - if (isPathResponse && paths.some((path) => [...path.nodes, ...path.edges].every((e: any) => [...prev.nodes, ...prev.edges].some((el: any) => el.id === e.id)))) { - chart.edges().forEach(e => { - const id = e.id() + if (isPathResponse && paths.some((path) => [...path.nodes, ...path.links].every((e: any) => [...prev.nodes, ...prev.links].some((e: any) => e.id === e.id)))) { + graph.getElements().forEach(link => { + const { id } = link - if (prev.edges.some(el => el.id == id) && !p.edges.some(el => el.id == id)) { - e.style(PATH_EDGE_STYLE) + if (prev.links.some(e => e.id === id) && !p.links.some(e => e.id === id)) { + link.isPathSelected = false } }) } else { - const elements = chart.elements().filter(e => [...prev.edges, ...prev.nodes].some(el => el.id == e.id() && ![...p.nodes, ...p.edges].some(ele => ele.id == e.id()))).removeStyle() - if (isPathResponse) { + const elements = graph.getElements().filter(e => [...prev.links, ...prev.nodes].some(el => el.id === e.id && ![...p.nodes, ...p.links].some(ele => ele.id === el.id))) + if (isPathResponse || isPathResponse === undefined) { elements.forEach(e => { - if (e.isNode()) { - e.style(NODE_STYLE); - } - - if (e.isEdge()) { - e.style(EDGE_STYLE); - } + e.isPath = false + e.isPathSelected = false }) } } } return p }) - } - - const handleSetSelectedPath = (p: { nodes: any[], edges: any[] }) => { - const chart = chartRef.current - - if (!chart) return - - updatePreviousPath(chart, p) - - if (isPathResponse && paths.some((path) => [...path.nodes, ...path.edges].every((e: any) => [...p.nodes, ...p.edges].some((el: any) => el.id === e.id)))) { - chart.edges().forEach(e => { - const id = e.id() - - if (p.edges.some(el => el.id == id)) { - e.style(SELECTED_PATH_EDGE_STYLE) + if (isPathResponse && paths.length > 0 && paths.some((path) => [...path.nodes, ...path.links].every((e: any) => [...p.nodes, ...p.links].some((el: any) => el.id === e.id)))) { + graph.Elements.links.forEach(e => { + if (p.links.some(el => el.id === e.id)) { + e.isPathSelected = true } }) - chart.elements().filter(el => [...p.nodes, ...p.edges].some(e => e.id == el.id())).layout(LAYOUT).run(); } else { - chart.elements().filter(el => [...p.nodes, ...p.edges].some(e => e.id == el.id())).forEach(el => { - if (el.id() == p.nodes[0].id || el.id() == p.nodes[p.nodes.length - 1].id) { - el.removeStyle().style(SELECTED_PATH_NODE_STYLE); - } else if (el.isNode()) { - el.removeStyle().style(PATH_NODE_STYLE); + const elements: PathData = { nodes: [], links: [] }; + p.nodes.forEach(node => { + let element = graph.Elements.nodes.find(n => n.id === node.id) + if (!element) { + elements.nodes.push(node) } - if (el.isEdge()) { - el.removeStyle().style(SELECTED_PATH_EDGE_STYLE); + }) + p.links.forEach(link => { + let element = graph.Elements.links.find(l => l.id === link.id) + if (!element) { + elements.links.push(link) } - }).layout(LAYOUT).run(); + }) + graph.extend(elements, true, { start: p.nodes[0], end: p.nodes[p.nodes.length - 1] }) + graph.getElements().filter(e => "source" in e ? p.links.some(l => l.id === e.id) : p.nodes.some(n => n.id === e.id)).forEach(e => { + if ((e.id === p.nodes[0].id || e.id === p.nodes[p.nodes.length - 1].id) || "source" in e) { + e.isPathSelected = true + } else { + e.isPath = true + } + }); } + setData({ ...graph.Elements }) } // A function that handles the change event of the url input box @@ -271,9 +264,7 @@ export function Chat({ repo, path, setPath, graph, chartRef, selectedPathId, isP const handleSubmit = async () => { setSelectedPath(undefined) - const chart = chartRef?.current - - if (!chart || !path?.start?.id || !path.end?.id) return + if (!path?.start?.id || !path.end?.id) return const result = await fetch(`/api/repo/${prepareArg(repo)}/${prepareArg(String(path.start.id))}/?targetId=${prepareArg(String(path.end.id))}`, { method: 'POST' @@ -298,40 +289,14 @@ export function Chat({ repo, path, setPath, graph, chartRef, selectedPathId, isP return } - const formattedPaths: { nodes: any[], edges: any[] }[] = json.result.paths.map((p: any) => ({ nodes: p.filter((node: any, i: number) => i % 2 === 0), edges: p.filter((edge: any, i: number) => i % 2 !== 0) })) - chart.add(formattedPaths.flatMap((p: any) => graph.extend(p, false, path))) - formattedPaths.forEach(p => p.edges.forEach(e => e.id = `_${e.id}`)) - graph.Elements.forEach((element: any) => { - const { id } = element.data - const e = chart.elements().filter(el => el.id() == id) - if (id == path.start?.id || id == path.end?.id) { - e.style(SELECTED_PATH_NODE_STYLE); - } else if (formattedPaths.some((p: any) => [...p.nodes, ...p.edges].some((el: any) => el.id == id))) { - if (e.isNode()) { - e.style(PATH_NODE_STYLE); - } - - if (e.isEdge()) { - e.style(PATH_EDGE_STYLE); - } - } else { - if (e.isNode()) { - e.style(NODE_STYLE); - } + const formattedPaths: PathData[] = json.result.paths.map((p: any) => ({ nodes: p.filter((n: any, i: number) => i % 2 === 0), links: p.filter((l: any, i: number) => i % 2 !== 0) })) + formattedPaths.forEach((p: any) => graph.extend(p, false, path)) - if (e.isEdge()) { - e.style(EDGE_STYLE); - } - } - }) - const elements = chart.elements().filter((element) => { - return formattedPaths.some(p => [...p.nodes, ...p.edges].some((node) => node.id == element.id())) - }); - elements.layout(LAYOUT).run() setPaths(formattedPaths) - setMessages((prev) => [...RemoveLastPath(prev), { type: MessageTypes.PathResponse, paths: formattedPaths }]); + setMessages((prev) => [...RemoveLastPath(prev), { type: MessageTypes.PathResponse, paths: formattedPaths, graphName: graph.Id }]); setPath(undefined) setIsPathResponse(true) + setData({ ...graph.Elements }) } const getTip = (disabled = false) => @@ -341,22 +306,27 @@ export function Chat({ repo, path, setPath, graph, chartRef, selectedPathId, isP className="Tip" onClick={() => { setTipOpen(false) - setPath({}) setMessages(prev => [ ...RemoveLastPath(prev), { type: MessageTypes.Query, text: "Create a path" }, ]) if (isPathResponse) { - chartRef.current?.elements().removeStyle().layout(LAYOUT).run() setIsPathResponse(false) + graph.getElements().forEach(e => { + e.isPath = false + e.isPathSelected = false + }) } setTimeout(() => setMessages(prev => [...prev, { type: MessageTypes.Response, text: "Please select a starting point and the end point. Select or press relevant item on the graph" }]), 300) - setTimeout(() => setMessages(prev => [...prev, { type: MessageTypes.Path }]), 4000) + setTimeout(() => { + setMessages(prev => [...prev, { type: MessageTypes.Path }]) + setPath({}) + }, 4000) }} > @@ -431,22 +401,40 @@ export function Chat({ repo, path, setPath, graph, chartRef, selectedPathId, isP message.paths.map((p, i: number) => ( )) @@ -498,7 +486,7 @@ export function Chat({ repo, path, setPath, graph, chartRef, selectedPathId, isP
- diff --git a/app/components/code-graph.tsx b/app/components/code-graph.tsx index 7034a6a7..83d27c88 100644 --- a/app/components/code-graph.tsx +++ b/app/components/code-graph.tsx @@ -1,124 +1,46 @@ -import CytoscapeComponent from 'react-cytoscapejs' -import { Dispatch, MutableRefObject, SetStateAction, useContext, useEffect, useRef, useState } from "react"; -import { Node } from "./model"; +import { Dispatch, RefObject, SetStateAction, useContext, useEffect, useRef, useState } from "react"; +import { GraphData, Node } from "./model"; import { GraphContext } from "./provider"; -import cytoscape, { ElementDefinition, EventObject, Position } from 'cytoscape'; -import fcose from 'cytoscape-fcose'; import { Toolbar } from "./toolbar"; import { Labels } from "./labels"; import { GitFork, Search, X } from "lucide-react"; import ElementMenu from "./elementMenu"; -import ElementTooltip from "./elementTooltip"; import Combobox from "./combobox"; import { toast } from '@/components/ui/use-toast'; import { Path, PathNode } from '../page'; import Input from './Input'; // import CommitList from './commitList'; import { Checkbox } from '@/components/ui/checkbox'; +import dynamic from 'next/dynamic'; +import { Position } from "./graphView"; import { prepareArg } from '../utils'; +const GraphView = dynamic(() => import('./graphView')); + interface Props { + data: GraphData, + setData: Dispatch>, onFetchGraph: (graphName: string) => void, - onFetchNode: (nodeIds: string[]) => Promise, + onFetchNode: (nodeIds: number[]) => Promise, options: string[] + setOptions: Dispatch> isShowPath: boolean setPath: Dispatch> - chartRef: MutableRefObject + chartRef: RefObject selectedValue: string - selectedPathId: string | undefined - setSelectedPathId: (selectedPathId: string) => void - isPathResponse: boolean - setIsPathResponse: Dispatch> -} - -// The stylesheet for the graph -const STYLESHEET: cytoscape.Stylesheet[] = [ - { - selector: "core", - style: { - 'active-bg-size': 0, // hide gray circle when panning - // All of the following styles are meaningless and are specified - // to satisfy the linter... - 'active-bg-color': 'blue', - 'active-bg-opacity': 0.3, - "selection-box-border-color": 'gray', - "selection-box-border-width": 3, - "selection-box-opacity": 0.5, - "selection-box-color": 'gray', - "outside-texture-bg-color": 'blue', - "outside-texture-bg-opacity": 1, - }, - }, - { - selector: "node", - style: { - label: "data(name)", - "color": "black", - "text-valign": "center", - "text-wrap": "ellipsis", - "text-max-width": "10rem", - shape: "ellipse", - height: "15rem", - width: "15rem", - "border-width": 0.3, - "border-color": "black", - "border-opacity": 0.5, - "background-color": "data(color)", - "font-size": "3rem", - "overlay-padding": "1rem", - }, - }, - { - selector: "node:active", - style: { - "overlay-opacity": 0, // hide gray box around active node - }, - }, - { - selector: "node:selected", - style: { - 'border-width': 0.5, - 'border-color': 'black', - 'border-opacity': 1, - }, - }, - { - selector: "edge", - style: { - width: 0.5, - "line-color": "#ccc", - "arrow-scale": 0.3, - "target-arrow-shape": "triangle", - "target-arrow-color": "#ccc", - label: "data(label)", - 'curve-style': 'straight', - "text-background-color": "#ffffff", - "text-background-opacity": 1, - "font-size": "3", - "overlay-padding": "2px", - }, - }, - { - selector: "edge:active", - style: { - "overlay-opacity": 0, // hide gray box around active node - }, - } -] - -cytoscape.use(fcose); - -export const LAYOUT = { - name: "fcose", - fit: true, - padding: 80, - avoidOverlap: true, + selectedPathId: number | undefined + setSelectedPathId: (selectedPathId: number) => void + isPathResponse: boolean | undefined + setIsPathResponse: Dispatch> } export function CodeGraph({ + data, + setData, onFetchGraph, onFetchNode, options, + setOptions, isShowPath, setPath, chartRef, @@ -134,10 +56,7 @@ export function CodeGraph({ const [url, setURL] = useState(""); const [selectedObj, setSelectedObj] = useState(); const [selectedObjects, setSelectedObjects] = useState([]); - const [isSelectedObj, setIsSelectedObj] = useState(""); - const [tooltipLabel, setTooltipLabel] = useState(); const [position, setPosition] = useState(); - const [tooltipPosition, setTooltipPosition] = useState(); const [graphName, setGraphName] = useState(""); const [searchNode, setSearchNode] = useState({}); const [commits, setCommits] = useState([]); @@ -145,19 +64,24 @@ export function CodeGraph({ const [edgesCount, setEdgesCount] = useState(0); const [commitIndex, setCommitIndex] = useState(0); const [currentCommit, setCurrentCommit] = useState(0); - const [containerWidth, setContainerWidth] = useState(0); + const [cooldownTicks, setCooldownTicks] = useState(0) + const [cooldownTime, setCooldownTime] = useState(0) const containerRef = useRef(null); useEffect(() => { - setContainerWidth(containerRef.current?.clientWidth || 0) - }, [containerRef.current?.clientWidth]) + setData({ ...graph.Elements }) + }, [graph.Id]) useEffect(() => { - const handleKeyDown = (event: KeyboardEvent) => { - if (!isSelectedObj && selectedObj && selectedObjects.length === 0) return + if (!selectedValue) return + handleSelectedValue(selectedValue) + }, [selectedValue]) + useEffect(() => { + const handleKeyDown = (event: KeyboardEvent) => { if (event.key === 'Delete') { - handleRemove(selectedObjects.length > 0 ? selectedObjects.map(obj => Number(obj.id)) : [Number(isSelectedObj || selectedObj?.id)]); + if (selectedObj && selectedObjects.length === 0) return + handelRemove([...selectedObjects.map(obj => obj.id), selectedObj?.id].filter(id => id !== undefined)); } }; @@ -166,7 +90,7 @@ export function CodeGraph({ return () => { document.removeEventListener('keydown', handleKeyDown); }; - }, [selectedObjects, selectedObj, isSelectedObj]); + }, [selectedObjects, selectedObj]); async function fetchCount() { const result = await fetch(`/api/repo/${prepareArg(graphName)}/info`, { @@ -189,11 +113,6 @@ export function CodeGraph({ setURL(json.result.info.repo_url) } - useEffect(() => { - if (!selectedValue) return - handleSelectedValue(selectedValue) - }, [selectedValue]) - useEffect(() => { if (!graphName) return @@ -233,170 +152,110 @@ export function CodeGraph({ } function onCategoryClick(name: string, show: boolean) { - let chart = chartRef.current - if (chart) { - let elements = chart.elements(`node[category = "${name}"]`) - - graph.Categories.forEach((category) => { - if (category.name === name) { - category.show = show - } - }) - - if (show) { - elements.style({ display: 'element' }) - } else { - elements.style({ display: 'none' }) - } - chart.elements().layout(LAYOUT).run(); - } - } - - const deleteNeighbors = (nodes: Node[], chart: cytoscape.Core) => { - if (nodes.length === 0) return - - const neighbors = chart.elements(nodes.map(node => `#${node.id}`).join(',')).outgoers() - - neighbors.forEach((n) => { - const id = n.id() - graph.Elements.forEach((e, i) => { - if (e.data.id === id) { - const type = "category" in e.data + graph.Categories.find(c => c.name === name)!.show = show - if (e.data.expand) { - deleteNeighbors([e.data] as Node[], chart) - } - - graph.Elements.splice(i, 1); - - if (type) { - graph.NodesMap.delete(Number(id)) - } else { - graph.EdgesMap.delete(Number(id.split('_')[1])) - } - - chart.remove(`#${id}`) - } - }); + graph.Elements.nodes.forEach(node => { + if (!(node.category === name)) return + node.visible = show }) - } - const handleDoubleTap = async (evt?: EventObject) => { + graph.visibleLinks(show) - const chart = chartRef.current - - if (!chart) return - - let node: Node - if (evt) { - const { target } = evt - target.unselect() - node = target.json().data - } else { - node = selectedObj! - } - - const graphNode = graph.Elements.find(e => e.data.id === node.id) + setData({ ...graph.Elements }) + } - if (!graphNode) return + const deleteNeighbors = (nodes: Node[]) => { + if (nodes.length === 0) return; - const expand = !graphNode.data.expand + graph.Elements = { + nodes: graph.Elements.nodes.map(node => { + const isTarget = graph.Elements.links.some(link => link.target.id === node.id && nodes.some(n => n.id === link.source.id)); - if (expand) { - const elements = await onFetchNode([node.id]) + if (!isTarget || !node.collapsed) return node - if (elements.length === 0) { - toast({ - title: `No neighbors found`, - description: `No neighbors found`, - }) - return - } + if (node.expand) { + node.expand = false + deleteNeighbors([node]) + } - chart.add(elements); - } else { - deleteNeighbors([node], chart); + graph.NodesMap.delete(Number(node.id)) + }).filter(node => node !== undefined), + links: graph.Elements.links } - graphNode.data.expand = expand - - setSelectedObj(undefined) - chart.elements().layout(LAYOUT).run(); + graph.removeLinks() } const handleExpand = async (nodes: Node[], expand: boolean) => { - const chart = chartRef.current - - if (!chart) return - if (expand) { const elements = await onFetchNode(nodes.map(n => n.id)) - if (elements.length === 0) { + if (elements.nodes.length === 0) { toast({ title: `No neighbors found`, description: `No neighbors found`, }) return } - - chart.add(elements); - chart.elements().layout(LAYOUT).run(); } else { - const deleteNodes = graph.Elements.filter(n => n.data.expand === true && nodes.some(e => e.id === n.data.id)) - + const deleteNodes = nodes.filter(n => n.expand) if (deleteNodes.length > 0) { - deleteNeighbors(deleteNodes.map(n => n.data) as Node[], chart); - chart.elements().layout(LAYOUT).run(); + deleteNeighbors(deleteNodes); } } nodes.forEach((node) => { - const graphNode = graph.Elements.find(e => e.data.id === node.id) - - if (!graphNode) return - - graphNode.data.expand = expand + node.expand = expand }) - + setSelectedObj(undefined) + setData({ ...graph.Elements }) } - const handleSearchSubmit = (node: any) => { - const chart = chartRef.current - - if (!chart) return - + const handelSearchSubmit = (node: any) => { const n = { name: node.properties.name, id: node.id } - let chartNode = chart.elements(`node[id = "${n.id}"]`) + let chartNode = graph.Elements.nodes.find(n => n.id == node.id) - if (chartNode.length === 0) { - const [newNode] = graph.extend({ nodes: [node], edges: [] }) - chartNode = chart.add(newNode) + if (!chartNode?.visible) { + if (!chartNode) { + chartNode = graph.extend({ nodes: [node], edges: [] }).nodes[0] + } else { + chartNode.visible = true + setCooldownTicks(undefined) + setCooldownTime(1000) + } + graph.visibleLinks(true, [chartNode.id]) } - chartNode.select() - chartNode.style({ display: "element" }) - setIsSelectedObj(String(n.id)) - const layout = { ...LAYOUT, padding: 250 } - chartNode.layout(layout).run() setSearchNode(n) - } + setData({ ...graph.Elements }) + + const chart = chartRef.current - const handleRemove = (ids: number[]) => { - chartRef.current?.elements(`#${ids.join(',#')}`).style({ display: 'none' }) - if (ids.some(id => Number(selectedObj?.id) === id)) { - setSelectedObj(undefined) + if (chart) { + chart.centerAt(chartNode.x, chartNode.y, 1000); } } + const handelRemove = (ids: number[]) => { + graph.Elements.nodes.forEach(node => { + if (!ids.includes(node.id)) return + node.visible = false + }) + + graph.visibleLinks(false, ids) + + setData({ ...graph.Elements }) + } + return (
@@ -413,27 +272,50 @@ export function CodeGraph({ value={searchNode.name} onValueChange={({ name }) => setSearchNode({ name })} icon={} - handleSubmit={handleSearchSubmit} + handleSubmit={handelSearchSubmit} node={searchNode} />
- { - isPathResponse && - - } +
+ { + (isPathResponse || isPathResponse === undefined) && + + } + { + (graph.Elements.nodes.some(e => !e.visible)) && + + } +
-
-
+
+

{nodesCount} Nodes

{edgesCount} Edges

@@ -460,15 +342,9 @@ export function CodeGraph({
- { - handleExpand(nodes, expand) - }} - parentWidth={containerWidth} + handelExpand={handleExpand} + parentRef={containerRef} /> - { - chartRef.current = cy - - // Make sure no previous listeners are attached - cy.removeAllListeners(); - - // Listen to the click event on nodes for expanding the node - cy.on('dbltap', 'node', handleDoubleTap); - - cy.on('mousedown', () => { - setTooltipLabel(undefined) - setSelectedObj(undefined) - setIsSelectedObj("") - }) - - cy.on('mouseout', 'node', (evt) => { - const { target } = evt - - setTooltipLabel(undefined) - - const { id } = target.json().data - - if (selectedObj?.id === id || isSelectedObj === id || selectedObjects.some(e => e.id === id)) return - - target.unselect() - }) - - cy.on('scrollzoom', () => { - setSelectedObj(undefined) - setTooltipLabel(undefined) - }); - - cy.on('mouseover', 'node', (evt) => { - const { target } = evt - - target.select() - - if (selectedObj && target.id() === selectedObj.id) return - - const position = target.renderedPosition() - - setTooltipPosition(() => ({ x: position.x, y: position.y + cy.zoom() * 8 })); - setTooltipLabel(() => target.json().data.name); - }) - - cy.on('tap', () => { - setSelectedObjects([]) - }) - - cy.on('tap', 'node', (evt) => { - const { target } = evt - - target.select() - - setIsSelectedObj(target.json().data.id) - - if (isShowPath) { - setPath(prev => { - if (!prev?.start?.name || (prev.end?.name && prev.end?.name !== "")) { - return ({ start: { id: Number(target.id()), name: target.data().name as string } }) - } else { - return ({ end: { id: Number(target.id()), name: target.data().name as string }, start: prev.start }) - } - }) - return - } - }); - - cy.on('cxttap', 'node', (evt) => { - const chart = chartRef.current - - if (!chart) return - - setTooltipLabel(undefined) - - if (isSelectedObj && isSelectedObj !== evt.target.id()) { - chart.elements(`#${isSelectedObj}`).unselect() - setIsSelectedObj("") - } - - if (selectedObj && selectedObj !== evt.target.id()) { - chart.elements(`#${selectedObj.id}`).unselect() - } - - const { target } = evt - const { x, y } = target.renderedPosition() - - setPosition(() => (x && y) ? { x, y: y + chart.zoom() * 8 } : { x: 0, y: 0 }); - setSelectedObj(target.json().data) - }); - - cy.on('tap', 'edge', (evt) => { - const { target } = evt - - if (!isPathResponse || selectedPathId === target.id()) return - - setSelectedPathId(target.id()) - }); - - cy.on('boxselect', 'node', (evt) => { - const { target } = evt - - selectedObjects.push(target.json().data) - }); - }} - stylesheet={STYLESHEET} - elements={graph.Elements} - layout={LAYOUT} - className="h-full w-full" +
:
diff --git a/app/components/combobox.tsx b/app/components/combobox.tsx index 59020d6c..354fee3f 100644 --- a/app/components/combobox.tsx +++ b/app/components/combobox.tsx @@ -1,15 +1,56 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; +import { toast } from "@/components/ui/use-toast"; +import { useEffect, useState } from "react"; interface Props { options: string[] + setOptions: (options: string[]) => void selectedValue: string onSelectedValue: (value: string) => void } -export default function Combobox({ options, selectedValue, onSelectedValue }: Props) { +export default function Combobox({ options, setOptions, selectedValue, onSelectedValue }: Props) { + + const [open, setOpen] = useState(false) + const [lastOpened, setLastOpened] = useState(); + + const fetchOptions = async () => { + const result = await fetch(`/api/repo`, { + method: 'GET', + }) + + if (!result.ok) { + toast({ + variant: "destructive", + title: "Uh oh! Something went wrong.", + description: await result.text(), + }) + return + } + + const json = await result.json() + setOptions(json.result) + } + + useEffect(() => { + fetchOptions() + }, []) + + useEffect(() => { + if (!open) return + + const now = Date.now(); + + if (lastOpened && now - lastOpened < 30000) return; + + setLastOpened(now); + + fetchOptions() + }, [open]) + return ( - diff --git a/app/components/dataPanel.tsx b/app/components/dataPanel.tsx index b38d40a5..07269cd1 100644 --- a/app/components/dataPanel.tsx +++ b/app/components/dataPanel.tsx @@ -16,7 +16,16 @@ const excludedProperties = [ "expand", "collapsed", "isPath", - "isPathStartEnd" + "isPathStartEnd", + "visible", + "index", + "__indexColor", + "x", + "y", + "vx", + "vy", + "fx", + "fy", ] export default function DataPanel({ obj, setObj, url }: Props) { @@ -36,14 +45,14 @@ export default function DataPanel({ obj, setObj, url }: Props) { const object = Object.entries(obj).filter(([k]) => !excludedProperties.includes(k)) return ( -
+

{label.toUpperCase()}

-
+
{ object.map(([key, value]) => (
@@ -74,7 +83,7 @@ export default function DataPanel({ obj, setObj, url }: Props) { @@ -90,7 +90,7 @@ export default function ElementMenu({ obj, objects, setPath, handleRemove, posit @@ -114,13 +114,13 @@ export default function ElementMenu({ obj, objects, setPath, handleRemove, posit diff --git a/app/components/elementTooltip.tsx b/app/components/elementTooltip.tsx deleted file mode 100644 index d10cb928..00000000 --- a/app/components/elementTooltip.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { Position } from "cytoscape"; -import { useState } from "react"; - -interface Props { - label: string | undefined; - position: Position | undefined; - parentWidth: number; -} - -export default function ElementTooltip({ label, position, parentWidth }: Props) { - const [containerWidth, setContainerWidth] = useState(0); - - if (!label || !position) return null - - return ( -
{ - if (!ref) return - setContainerWidth(ref.clientWidth) - }} - className="absolute z-20 bg-white rounded-lg shadow-lg p-3" - style={{ - left: Math.max(-34, Math.min(position.x - containerWidth / 2, parentWidth + 34 - containerWidth)), - top: position.y - }} - > - {label} -
- ) -} \ No newline at end of file diff --git a/app/components/graphView.tsx b/app/components/graphView.tsx new file mode 100644 index 00000000..b754fd82 --- /dev/null +++ b/app/components/graphView.tsx @@ -0,0 +1,296 @@ + +import ForceGraph2D from 'react-force-graph-2d'; +import { Graph, GraphData, Link, Node } from './model'; +import { Dispatch, RefObject, SetStateAction, useEffect, useRef } from 'react'; +import { toast } from '@/components/ui/use-toast'; +import { Path } from '../page'; + +export interface Position { + x: number, + y: number, +} + +interface Props { + data: GraphData + setData: Dispatch> + graph: Graph + chartRef: RefObject + selectedObj: Node | undefined + setSelectedObj: Dispatch> + selectedObjects: Node[] + setSelectedObjects: Dispatch> + setPosition: Dispatch> + onFetchNode: (nodeIds: number[]) => Promise + deleteNeighbors: (nodes: Node[]) => void + isShowPath: boolean + setPath: Dispatch> + isPathResponse: boolean | undefined + selectedPathId: number | undefined + setSelectedPathId: (selectedPathId: number) => void + cooldownTicks: number | undefined + setCooldownTicks: Dispatch> + cooldownTime: number | undefined + setCooldownTime: Dispatch> +} + +const PATH_COLOR = "#ffde21" +const NODE_SIZE = 6; +const PADDING = 2; + +export default function GraphView({ + data, + setData, + graph, + chartRef, + selectedObj, + setSelectedObj, + selectedObjects, + setSelectedObjects, + setPosition, + onFetchNode, + deleteNeighbors, + isShowPath, + setPath, + isPathResponse, + selectedPathId, + setSelectedPathId, + cooldownTicks, + cooldownTime, + setCooldownTicks, + setCooldownTime +}: Props) { + + const parentRef = useRef(null) + + useEffect(() => { + setCooldownTime(4000) + setCooldownTicks(undefined) + }, [graph.Id]) + + useEffect(() => { + setCooldownTime(1000) + setCooldownTicks(undefined) + }, [graph.getElements().length]) + + const unsetSelectedObjects = (evt?: MouseEvent) => { + if (evt?.ctrlKey || (!selectedObj && selectedObjects.length === 0)) return + setSelectedObj(undefined) + setSelectedObjects([]) + } + + const handelNodeClick = (node: Node, evt: MouseEvent) => { + if (isShowPath) { + setPath(prev => { + if (!prev?.start?.name || (prev.end?.name && prev.end?.name !== "")) { + return ({ start: { id: Number(node.id), name: node.name } }) + } else { + return ({ end: { id: Number(node.id), name: node.name }, start: prev.start }) + } + }) + return + } + + if (evt.ctrlKey) { + if (selectedObjects.some(obj => obj.id === node.id)) { + setSelectedObjects(selectedObjects.filter(obj => obj.id !== node.id)) + return + } else { + setSelectedObjects([...selectedObjects, node]) + } + } else { + setSelectedObjects([]) + } + + setSelectedObj(node) + setPosition({ x: evt.clientX, y: evt.clientY }) + } + + const handelLinkClick = (link: Link, evt: MouseEvent) => { + unsetSelectedObjects(evt) + if (!isPathResponse || link.id === selectedPathId) return + setSelectedPathId(link.id) + } + + const handelNodeRightClick = async (node: Node) => { + const expand = !node.expand + if (expand) { + const elements = await onFetchNode([node.id]) + + if (elements.nodes.length === 0) { + toast({ + title: `No neighbors found`, + description: `No neighbors found`, + }) + return + } + } else { + deleteNeighbors([node]); + } + + node.expand = expand + + setSelectedObj(undefined) + setData({ ...graph.Elements }) + } + + return ( +
+ (link.isPath || link.isPathSelected) ? PATH_COLOR : link.color} + linkDirectionalArrowLength={(link) => link.source.id === link.target.id ? 0 : (link.id === selectedObj?.id || link.isPathSelected) ? 3 : 2} + nodeRelSize={NODE_SIZE} + linkLineDash={(link) => (link.isPath && !link.isPathSelected) ? [5, 5] : []} + linkColor={(link) => (link.isPath || link.isPathSelected) ? PATH_COLOR : link.color} + linkWidth={(link) => (link.id === selectedObj?.id || link.isPathSelected) ? 2 : 1} + nodeCanvasObjectMode={() => 'after'} + linkCanvasObjectMode={() => 'after'} + nodeCanvasObject={(node, ctx) => { + if (!node.x || !node.y) return + + if (isPathResponse) { + if (node.isPathSelected) { + ctx.fillStyle = node.color; + ctx.strokeStyle = PATH_COLOR; + ctx.lineWidth = 1 + } else if (node.isPath) { + ctx.fillStyle = node.color; + ctx.strokeStyle = PATH_COLOR; + ctx.lineWidth = 0.5 + } else { + ctx.fillStyle = '#E5E5E5'; + ctx.strokeStyle = 'gray'; + ctx.lineWidth = 0.5 + } + } else if (isPathResponse === undefined) { + if (node.isPathSelected) { + ctx.fillStyle = node.color; + ctx.strokeStyle = PATH_COLOR; + ctx.lineWidth = 1 + } else if (node.isPath) { + ctx.fillStyle = node.color; + ctx.strokeStyle = PATH_COLOR; + ctx.lineWidth = 0.5 + } else { + ctx.fillStyle = node.color; + ctx.strokeStyle = 'black'; + ctx.lineWidth = selectedObjects.some(obj => obj.id === node.id) || selectedObj?.id === node.id ? 1 : 0.5 + } + } else { + ctx.fillStyle = node.color; + ctx.strokeStyle = 'black'; + ctx.lineWidth = selectedObjects.some(obj => obj.id === node.id) || selectedObj?.id === node.id ? 1 : 0.5 + } + + ctx.beginPath(); + ctx.arc(node.x, node.y, NODE_SIZE, 0, 2 * Math.PI, false); + ctx.stroke(); + ctx.fill(); + + ctx.fillStyle = 'black'; + ctx.textAlign = 'center'; + ctx.textBaseline = 'middle'; + ctx.font = '4px Arial'; + const textWidth = ctx.measureText(node.name).width; + const ellipsis = '...'; + const ellipsisWidth = ctx.measureText(ellipsis).width; + const nodeSize = NODE_SIZE * 2 - PADDING; + let { name } = { ...node } + + // truncate text if it's too long + if (textWidth > nodeSize) { + while (name.length > 0 && ctx.measureText(name).width + ellipsisWidth > nodeSize) { + name = name.slice(0, -1); + } + name += ellipsis; + } + + // add label + ctx.fillText(name, node.x, node.y); + }} + linkCanvasObject={(link, ctx) => { + const start = link.source; + const end = link.target; + + if (!start.x || !start.y || !end.x || !end.y) return + + const sameNodesLinks = graph.Elements.links.filter(l => (l.source.id === start.id && l.target.id === end.id) || (l.target.id === start.id && l.source.id === end.id)) + const index = sameNodesLinks.findIndex(l => l.id === link.id) || 0 + const even = index % 2 === 0 + let curve + + if (start.id === end.id) { + if (even) { + curve = Math.floor(-(index / 2)) - 3 + } else { + curve = Math.floor((index + 1) / 2) + 2 + } + + link.curve = curve * 0.1 + + const radius = NODE_SIZE * link.curve * 6.2; + const angleOffset = -Math.PI / 4; // 45 degrees offset for text alignment + const textX = start.x + radius * Math.cos(angleOffset); + const textY = start.y + radius * Math.sin(angleOffset); + + ctx.save(); + ctx.translate(textX, textY); + ctx.rotate(-angleOffset); + } else { + if (even) { + curve = Math.floor(-(index / 2)) + } else { + curve = Math.floor((index + 1) / 2) + } + + link.curve = curve * 0.1 + + const midX = (start.x + end.x) / 2 + (end.y - start.y) * (link.curve / 2); + const midY = (start.y + end.y) / 2 + (start.x - end.x) * (link.curve / 2); + + let textAngle = Math.atan2(end.y - start.y, end.x - start.x) + + // maintain label vertical orientation for legibility + if (textAngle > Math.PI / 2) textAngle = -(Math.PI - textAngle); + if (textAngle < -Math.PI / 2) textAngle = -(-Math.PI - textAngle); + + ctx.save(); + ctx.translate(midX, midY); + ctx.rotate(textAngle); + } + + // add label + ctx.globalAlpha = 1; + ctx.fillStyle = 'black'; + ctx.textAlign = 'center'; + ctx.textBaseline = 'middle'; + ctx.font = '2px Arial'; + ctx.fillText(link.label, 0, 0); + ctx.restore() + }} + onNodeClick={handelNodeClick} + onNodeDragEnd={(n, translate) => setPosition(prev => { + return prev && { x: prev.x + translate.x * chartRef.current.zoom(), y: prev.y + translate.y * chartRef.current.zoom() } + })} + onNodeRightClick={handelNodeRightClick} + onLinkClick={handelLinkClick} + onBackgroundRightClick={unsetSelectedObjects} + onBackgroundClick={unsetSelectedObjects} + onZoom={() => unsetSelectedObjects()} + onEngineStop={() => { + setCooldownTicks(0) + setCooldownTime(0) + }} + cooldownTicks={cooldownTicks} + cooldownTime={cooldownTime} + /> +
+ ) +} \ No newline at end of file diff --git a/app/components/labels.tsx b/app/components/labels.tsx index 5defa8ab..923ee3f1 100644 --- a/app/components/labels.tsx +++ b/app/components/labels.tsx @@ -8,9 +8,9 @@ export function Labels(params: { categories: Category[], className?: string, onC const [reload, setReload] = useState(false) return ( -
+
{params.categories.map((category) => -
+
{ diff --git a/app/components/model.ts b/app/components/model.ts index 18ea3f56..44fd658d 100644 --- a/app/components/model.ts +++ b/app/components/model.ts @@ -1,41 +1,55 @@ -import twcolors from 'tailwindcss/colors' +import { LinkObject, NodeObject } from 'react-force-graph-2d' import { Path } from '../page' -export interface Element { - data: Node | Edge, +export interface GraphData { + nodes: Node[], + links: Link[], } - export interface Category { name: string, index: number, show: boolean, } -export interface Node { - id: string, +export type Node = NodeObject<{ + id: number, name: string, category: string, color: string, + collapsed: boolean, + expand: boolean, + visible: boolean, + isPathSelected: boolean, + isPath: boolean, [key: string]: any, -} +}> -export interface Edge { - source: number, - target: number, +export type Link = LinkObject const COLORS_ORDER_NAME = [ - "pink", - "yellow", "blue", + "pink", + "orange", + "turquoise", ] const COLORS_ORDER = [ - "#F43F5F", - "#E9B306", - "#15B8A6", + "#7466FF", + "#FF66B3", + "#FF804D", + "#80E6E6", ] export function getCategoryColorValue(index: number): string { @@ -50,20 +64,20 @@ export class Graph { private id: string; private categories: Category[]; - private elements: any[]; + private elements: GraphData; private categoriesMap: Map; private nodesMap: Map; - private edgesMap: Map; + private linksMap: Map; - private constructor(id: string, categories: Category[], elements: any[], - categoriesMap: Map, nodesMap: Map, edgesMap: Map) { + private constructor(id: string, categories: Category[], elements: GraphData, + categoriesMap: Map, nodesMap: Map, edgesMap: Map) { this.id = id; this.categories = categories; this.elements = elements; this.categoriesMap = categoriesMap; this.nodesMap = nodesMap; - this.edgesMap = edgesMap; + this.linksMap = edgesMap; } get Id(): string { @@ -78,20 +92,28 @@ export class Graph { return this.categoriesMap; } - get Elements(): Element[] { + get Elements(): GraphData { return this.elements; } - get EdgesMap(): Map { - return this.edgesMap; + set Elements(elements: GraphData) { + this.elements = elements; + } + + get EdgesMap(): Map { + return this.linksMap; } get NodesMap(): Map { return this.nodesMap; } + public getElements(): (Node | Link)[] { + return [...this.elements.nodes, ...this.elements.links] + } + public static empty(): Graph { - return new Graph("", [], [], new Map(), new Map(), new Map()) + return new Graph("", [], { nodes: [], links: [] }, new Map(), new Map(), new Map()) } public static create(results: any, graphName: string): Graph { @@ -101,8 +123,8 @@ export class Graph { return graph } - public extend(results: any, collapsed = false, path?: Path): any[] { - let newElements: any[] = [] + public extend(results: any, collapsed = false, path?: Path): GraphData { + let newElements: GraphData = { nodes: [], links: [] } results.nodes.forEach((nodeData: any) => { let label = nodeData.labels[0]; @@ -118,57 +140,90 @@ export class Graph { let node = this.nodesMap.get(nodeData.id) if (node) { node.isPath = !!path - if (path?.start?.id == nodeData.id || path?.end?.id == nodeData.id) { - node.isPathStartEnd = true + if (path?.start?.id === nodeData.id || path?.end?.id === nodeData.id) { + node.isPathSelected = true } - node.isPath = !!path return } node = { - id: nodeData.id.toString(), + id: nodeData.id, name: nodeData.name, color: getCategoryColorValue(category.index), category: category.name, expand: false, + visible: true, collapsed, isPath: !!path, - } - if (path?.start?.id == nodeData.id || path?.end?.id == nodeData.id) { - node.isPathStartEnd = true + isPathSelected: path?.start?.id === nodeData.id || path?.end?.id === nodeData.id } Object.entries(nodeData.properties).forEach(([key, value]) => { node[key] = value }) this.nodesMap.set(nodeData.id, node) - this.elements.push({ data: node }) - newElements.push({ data: node }) + this.elements.nodes.push(node) + newElements.nodes.push(node) }) + if (!("edges" in results)) { + results.edges = results.links + } + results.edges.forEach((edgeData: any) => { - let edge = this.edgesMap.get(edgeData.id) - if (edge) { - edge.isPath = !!path + let link = this.linksMap.get(edgeData.id) + if (link) { + link.isPath = !!path return } - let sourceId = edgeData.src_node.toString(); - let destinationId = edgeData.dest_node.toString() + let sourceId = edgeData.src_node; + let destinationId = edgeData.dest_node - edge = { - id: `_${edgeData.id}`, + link = { + id: edgeData.id, source: sourceId, target: destinationId, label: edgeData.relation, + visible: true, expand: false, collapsed, + isPathSelected: false, isPath: !!path, + curve: 0 } - this.edgesMap.set(edgeData.id, edge) - this.elements.push({ data: edge }) - newElements.push({ data: edge }) + this.linksMap.set(edgeData.id, link) + this.elements.links.push(link) + newElements.links.push(link) }) return newElements } + + public removeLinks() { + this.elements = { + nodes: this.elements.nodes, + links: this.elements.links.map(link => { + if (this.elements.nodes.map(n => n.id).includes(link.source.id) && this.elements.nodes.map(n => n.id).includes(link.target.id)) { + return link + } + this.linksMap.delete(link.id) + }).filter(link => link !== undefined) + } + } + + public visibleLinks(visible: boolean, ids?: number[]) { + const elements = ids ? this.elements.links.filter(link => ids.includes(link.source.id) || ids.includes(link.target.id)) : this.elements.links + + elements.forEach(link => { + if (visible && this.elements.nodes.map(n => n.id).includes(link.source.id) && link.source.visible && this.elements.nodes.map(n => n.id).includes(link.target.id) && link.target.visible) { + // eslint-disable-next-line no-param-reassign + link.visible = true + } + + if (!visible && ((this.elements.nodes.map(n => n.id).includes(link.source.id) && !link.source.visible) || (this.elements.nodes.map(n => n.id).includes(link.target.id) && !link.target.visible))) { + // eslint-disable-next-line no-param-reassign + link.visible = false + } + }) + } } \ No newline at end of file diff --git a/app/components/toolbar.tsx b/app/components/toolbar.tsx index 00264fb8..f718d741 100644 --- a/app/components/toolbar.tsx +++ b/app/components/toolbar.tsx @@ -1,31 +1,26 @@ import { CircleDot, Minus, Plus } from "lucide-react"; import { cn } from "@/lib/utils" -import { Dispatch, RefObject, SetStateAction } from "react"; -import { Node } from "./model"; +import { RefObject } from "react"; interface Props { - chartRef: RefObject - setSelectedObj: Dispatch> + chartRef: RefObject className?: string } -export function Toolbar({ chartRef, setSelectedObj, className }: Props) { +export function Toolbar({ chartRef, className }: Props) { - function handleZoomClick(changefactor: number) { - let chart = chartRef.current + const handleZoomClick = (changefactor: number) => { + const chart = chartRef.current if (chart) { chart.zoom(chart.zoom() * changefactor) } - setSelectedObj(undefined) } - function handleCenterClick() { - let chart = chartRef.current + const handleCenterClick = () => { + const chart = chartRef.current if (chart) { - chart.fit(undefined, 80) - chart.center() + chart.zoomToFit(1000, 40) } - setSelectedObj(undefined) } return ( diff --git a/app/layout.tsx b/app/layout.tsx index 7d2475e9..f1c3b6dc 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -4,6 +4,7 @@ import './globals.css' import { Toaster } from '@/components/ui/toaster' import GoogleAnalytics from './components/GoogleAnalytics' import { cn } from '@/lib/utils' +import GTM from './GTM' const inter = Inter({ subsets: ['latin'] }) @@ -20,10 +21,7 @@ export default function RootLayout({ return ( - {process.env.NEXT_PUBLIC_GOOGLE_ANALYTICS ? ( - - ) : null} + {children} diff --git a/app/page.tsx b/app/page.tsx index 4cf6fc23..a58f1a56 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -2,7 +2,7 @@ import { useEffect, useRef, useState } from 'react'; import { Chat } from './components/chat'; -import { Graph, Node } from './components/model'; +import { Graph, GraphData } from './components/model'; import { BookOpen, Github, HomeIcon, X } from 'lucide-react'; import Link from 'next/link'; import { Panel, PanelGroup, PanelResizeHandle } from "react-resizable-panels"; @@ -11,8 +11,6 @@ import { toast } from '@/components/ui/use-toast'; import { GraphContext } from './components/provider'; import Image from 'next/image'; import { DropdownMenu, DropdownMenuContent, DropdownMenuLabel, DropdownMenuTrigger } from '@/components/ui/dropdown-menu'; -import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog'; -import { Progress } from '@/components/ui/progress'; import { prepareArg } from './utils'; export type PathNode = { @@ -53,39 +51,22 @@ const TIPS: Tip[] = [ export default function Home() { + const [data, setData] = useState({ nodes: [], links: [] }); const [graph, setGraph] = useState(Graph.empty()); const [selectedValue, setSelectedValue] = useState(""); - const [selectedPathId, setSelectedPathId] = useState(); - const [isPathResponse, setIsPathResponse] = useState(false); + const [selectedPathId, setSelectedPathId] = useState(); + const [isPathResponse, setIsPathResponse] = useState(false); const [createURL, setCreateURL] = useState("") const [createOpen, setCreateOpen] = useState(false) const [tipOpen, setTipOpen] = useState(false) const [options, setOptions] = useState([]); const [path, setPath] = useState(); const [isSubmit, setIsSubmit] = useState(false); - const chartRef = useRef(null) + const chartRef = useRef() useEffect(() => { - const run = async () => { - const result = await fetch(`/api/repo`, { - method: 'GET', - }) - - if (!result.ok) { - toast({ - variant: "destructive", - title: "Uh oh! Something went wrong.", - description: await result.text(), - }) - return - } - - const json = await result.json() - setOptions(json.result) - } - - run() - }, []) + setIsPathResponse(false) + }, [graph.Id]) async function onCreateRepo(e: React.FormEvent) { e.preventDefault() @@ -146,11 +127,14 @@ export default function Home() { } const json = await result.json() - setGraph(Graph.create(json.result.entities, graphName)) + const g = Graph.create(json.result.entities, graphName) + setGraph(g) + // @ts-ignore + window.graph = g } // Send the user query to the server to expand a node - async function onFetchNode(nodeIds: string[]) { + async function onFetchNode(nodeIds: number[]) { const result = await fetch(`/api/repo/${prepareArg(graph.Id)}/neighbors`, { method: 'POST', @@ -166,7 +150,7 @@ export default function Home() { title: "Uh oh! Something went wrong.", description: await result.text(), }) - return [] + return { nodes: [], links: [] } } const json = await result.json() @@ -182,7 +166,7 @@ export default function Home() { FalkorDB -

+

CODE GRAPH

@@ -277,8 +261,11 @@ export default function Home() { diff --git a/app/parsers/tree-sitter-java.wasm b/app/parsers/tree-sitter-java.wasm deleted file mode 100644 index 71ffc4a7..00000000 Binary files a/app/parsers/tree-sitter-java.wasm and /dev/null differ diff --git a/app/parsers/tree-sitter-python.wasm b/app/parsers/tree-sitter-python.wasm deleted file mode 100755 index 69b540b1..00000000 Binary files a/app/parsers/tree-sitter-python.wasm and /dev/null differ diff --git a/app/parsers/tree-sitter-rust.wasm b/app/parsers/tree-sitter-rust.wasm deleted file mode 100644 index 36e7806a..00000000 Binary files a/app/parsers/tree-sitter-rust.wasm and /dev/null differ diff --git a/app/parsers/tree-sitter-typescript.wasm b/app/parsers/tree-sitter-typescript.wasm deleted file mode 100644 index e46182bf..00000000 Binary files a/app/parsers/tree-sitter-typescript.wasm and /dev/null differ diff --git a/app/parsers/tree-sitter.wasm b/app/parsers/tree-sitter.wasm deleted file mode 100755 index dd8c7395..00000000 Binary files a/app/parsers/tree-sitter.wasm and /dev/null differ diff --git a/e2e/config/constants.ts b/e2e/config/constants.ts index 0c38c15a..63bcec56 100644 --- a/e2e/config/constants.ts +++ b/e2e/config/constants.ts @@ -1,7 +1,5 @@ -export const GRAPH_ID = "1"; +export const GRAPH_ID = "GraphRAG-SDK"; export const PROJECT_NAME = "GraphRAG-SDK"; export const CHAT_OPTTIONS_COUNT = 1; export const Node_Question = "how many nodes do we have?"; -export const Edge_Question = "how many edges do we have?"; -export const Node_Import_Data = "import_data"; -export const Node_Add_Edge = "add_edge"; \ No newline at end of file +export const Edge_Question = "how many edges do we have?"; \ No newline at end of file diff --git a/e2e/config/testData.ts b/e2e/config/testData.ts index 8e89a4f0..dab5ebf7 100644 --- a/e2e/config/testData.ts +++ b/e2e/config/testData.ts @@ -13,3 +13,17 @@ export const specialCharacters: { character: string; expectedRes: boolean }[] = ...categorizeCharacters(['%', '*', '(', ')', '-', '[', ']', '{', '}', ';', ':', '"', '|', '~'], false), ...categorizeCharacters(['!', '@', '$', '^', '_', '=', '+', "'", ',', '.', '<', '>', '/', '?', '\\', '`', '&', '#'], true) ]; + +export const nodesPath: { firstNode: string; secondNode: string }[] = [ + { firstNode: "import_data", secondNode: "add_edge" }, + { firstNode: "test_kg_delete", secondNode: "list_graphs" }, +]; + +export const nodes: { nodeName: string; }[] = [ + { nodeName: "import_data"}, + { nodeName: "add_edge" }, + { nodeName: "test_kg_delete"}, + { nodeName: "list_graphs"} +]; + +export const categories: string[] = ['File', 'Class', 'Function']; \ No newline at end of file diff --git a/e2e/infra/ui/browserWrapper.ts b/e2e/infra/ui/browserWrapper.ts index 706ebaef..985bf25c 100644 --- a/e2e/infra/ui/browserWrapper.ts +++ b/e2e/infra/ui/browserWrapper.ts @@ -13,9 +13,9 @@ export default class BrowserWrapper { if (!this.browser) { this.browser = await chromium.launch(); } - if (!this.context) { - this.context = await this.browser.newContext(); - } + this.context = await this.browser.newContext({ + permissions: ['clipboard-read', 'clipboard-write'], + }); if (!this.page) { this.page = await this.context.newPage(); } diff --git a/e2e/logic/POM/codeGraph.ts b/e2e/logic/POM/codeGraph.ts index 3f8e3d60..9910ded3 100644 --- a/e2e/logic/POM/codeGraph.ts +++ b/e2e/logic/POM/codeGraph.ts @@ -1,7 +1,12 @@ import { Locator, Page } from "playwright"; import BasePage from "../../infra/ui/basePage"; import { delay, waitToBeEnabled } from "../utils"; -import { analyzeCanvasWithLocator, CanvasAnalysisResult } from "../canvasAnalysis"; + +declare global { + interface Window { + graph: any; + } +} export default class CodeGraph extends BasePage { /* NavBar Locators*/ @@ -21,17 +26,33 @@ export default class CodeGraph extends BasePage { return this.page.locator("//div[@role='dialog']") } + private get tipBtn(): Locator { + return this.page.locator("//button[@title='Tip']") + } + + private get genericMenu(): Locator { + return this.page.locator("//div[contains(@role, 'menu')]") + } + + private get tipMenuCloseBtn(): Locator { + return this.page.locator("//div[@role='menu']//button[@title='Close']") + } + /* CodeGraph Locators*/ private get comboBoxbtn(): Locator { return this.page.locator("//button[@role='combobox']") } - private get selectGraphInComboBox(): (graph: string) => Locator { - return (graph: string) => this.page.locator(`//div[@role='presentation']//div[@role='option'][${graph}]`); + private get selectGraphInComboBoxByName(): (graph: string) => Locator { + return (graph: string) => this.page.locator(`//div[@role='presentation']//div//span[contains(text(), '${graph}')]`); + } + + private get selectGraphInComboBoxById(): (graph: string) => Locator { + return (graph: string) => this.page.locator(`//div[@role='presentation']//div[${graph}]`); } private get lastElementInChat(): Locator { - return this.page.locator("//main[@data-name='main-chat']/*[last()]/p"); + return this.page.locator("//main[@data-name='main-chat']/*[last()]/span"); } private get typeUrlInput(): Locator { @@ -104,7 +125,7 @@ export default class CodeGraph extends BasePage { } private get locateNodeInLastChatPath(): (node: string) => Locator { - return (node: string) => this.page.locator(`//main[@data-name='main-chat']/*[last()]//span[contains(text(), '${node}')]`); + return (node: string) => this.page.locator(`(//main[@data-name='main-chat']//span[contains(text(), '${node}')])[last()]`); } private get selectFirstPathOption(): (inputNum: string) => Locator { @@ -115,10 +136,26 @@ export default class CodeGraph extends BasePage { return this.page.locator("//div[@role='region']//ol//li"); } + private get notificationErrorCloseBtn(): Locator { + return this.page.locator("//div[@role='region']//ol//li/button"); + } + + private get questionOptionsMenu(): Locator { + return this.page.locator("//button[@data-name='questionOptionsMenu']"); + } + + private get selectQuestionInMenu(): (questionNumber: string) => Locator { + return (questionNumber: string) => this.page.locator(`//div[contains(@role, 'menu')]/button[${questionNumber}]`); + } + + private get lastQuestionInChat(): Locator { + return this.page.locator("//main[@data-name='main-chat']/*[last()-1]/p"); + } + /* Canvas Locators*/ private get canvasElement(): Locator { - return this.page.locator("//canvas[position()=3]"); + return this.page.locator("//canvas"); } private get zoomInBtn(): Locator { @@ -133,10 +170,53 @@ export default class CodeGraph extends BasePage { return this.page.locator("//button[@title='Center']"); } - private get removeNodeViaElementMenu(): Locator { - return this.page.locator("//button[@title='Remove']"); + private get codeGraphCheckbox(): (checkbox: string) => Locator { + return (checkbox: string) => this.page.locator(`(//button[@role='checkbox'])[${checkbox}]`); + } + + private get clearGraphBtn(): Locator { + return this.page.locator("//button[p[text()='Reset Graph']]"); + } + + private get unhideNodesBtn(): Locator { + return this.page.locator("//button[p[text()='Unhide Nodes']]"); + } + + private get elementMenuButton(): (buttonID: string) => Locator { + return (buttonID: string) => this.page.locator(`//button[@title='${buttonID}']`); + } + + private get nodeDetailsPanel(): Locator { + return this.page.locator("//div[@data-name='node-details-panel']"); } + private get nodedetailsPanelHeader(): Locator { + return this.page.locator("//div[@data-name='node-details-panel']/header/p"); + } + + private get nodedetailsPanelcloseBtn(): Locator { + return this.page.locator("//div[@data-name='node-details-panel']/header/button"); + } + private get canvasMetricsPanel(): (itemId: string) => Locator { + return (itemId: string) => this.page.locator(`//div[@data-name='metrics-panel']/p[${itemId}]`); + } + + private get nodedetailsPanelID(): Locator { + return this.page.locator("//div[@data-name='node-details-panel']/main/div[1]/p[2]"); + } + + private get nodedetailsPanelElements(): Locator { + return this.page.locator("//div[@data-name='node-details-panel']/main/div/p[1]"); + } + + private get canvasElementBeforeGraphSelection(): Locator { + return this.page.locator("//h1[contains(text(), 'Select a repo to show its graph here')]"); + } + + private get copyToClipboardNodePanelDetails(): Locator { + return this.page.locator(`//div[@data-name='node-details-panel']//button[@title='Copy src to clipboard']`); + } + /* NavBar functionality */ async clickOnFalkorDbLogo(): Promise { await this.page.waitForLoadState('networkidle'); @@ -164,6 +244,19 @@ export default class CodeGraph extends BasePage { return await this.createNewProjectDialog.isVisible(); } + async clickonTipBtn(): Promise { + await this.tipBtn.click(); + } + + async isTipMenuVisible(): Promise { + await delay(500); + return await this.genericMenu.isVisible(); + } + + async clickonTipMenuCloseBtn(): Promise { + await this.tipMenuCloseBtn.click(); + } + /* Chat functionality */ async clickOnshowPathBtn(): Promise { await this.showPathBtn.click(); @@ -183,8 +276,9 @@ export default class CodeGraph extends BasePage { await this.lightbulbBtn.click(); } - async getTextInLastChatElement(): Promise{ - return await this.lastElementInChat.textContent(); + async getTextInLastChatElement(): Promise{ + await delay(2500); + return (await this.lastElementInChat.textContent())!; } async getLastChatElementButtonCount(): Promise{ @@ -224,14 +318,37 @@ export default class CodeGraph extends BasePage { } async isNotificationError(): Promise { + await delay(500); return await this.notificationError.isVisible(); } + async clickOnNotificationErrorCloseBtn(): Promise { + await this.notificationErrorCloseBtn.click(); + } + + async clickOnQuestionOptionsMenu(): Promise { + await this.questionOptionsMenu.click(); + } + + async selectAndGetQuestionInOptionsMenu(questionNumber: string): Promise { + await this.selectQuestionInMenu(questionNumber).click(); + return await this.selectQuestionInMenu(questionNumber).innerHTML(); + } + + async getLastQuestionInChat(): Promise { + return await this.lastQuestionInChat.innerText(); + } + /* CodeGraph functionality */ - async selectGraph(graph: string): Promise { + async selectGraph(graph: string | number): Promise { await this.comboBoxbtn.click(); - await this.selectGraphInComboBox(graph).waitFor({ state : 'visible'}) - await this.selectGraphInComboBox(graph).click(); + if(typeof graph === 'number'){ + await this.selectGraphInComboBoxById(graph.toString()).waitFor({ state : 'visible'}) + await this.selectGraphInComboBoxById(graph.toString()).click(); + } else { + await this.selectGraphInComboBoxByName(graph).waitFor({ state : 'visible'}) + await this.selectGraphInComboBoxByName(graph).click(); + } } async createProject(url : string): Promise { @@ -251,6 +368,7 @@ export default class CodeGraph extends BasePage { } async getSearchAutoCompleteCount(): Promise { + await this.searchBarAutoCompleteOptions.first().waitFor({ state: 'visible' }); return await this.searchBarAutoCompleteOptions.count(); } @@ -259,6 +377,7 @@ export default class CodeGraph extends BasePage { } async selectSearchBarOptionBtn(buttonNum: string): Promise { + await delay(1000); await this.searchBarOptionBtn(buttonNum).click(); } @@ -266,7 +385,6 @@ export default class CodeGraph extends BasePage { return await this.searchBarInput.inputValue(); } - async scrollToBottomInSearchBarList(): Promise { await this.searchBarList.evaluate((element) => { element.scrollTop = element.scrollHeight; @@ -279,11 +397,6 @@ export default class CodeGraph extends BasePage { } /* Canvas functionality */ - - async getCanvasAnalysis(): Promise { - await delay(2000); - return await analyzeCanvasWithLocator(this.canvasElement); - } async clickZoomIn(): Promise { await this.zoomInBtn.click(); @@ -295,17 +408,139 @@ export default class CodeGraph extends BasePage { async clickCenter(): Promise { await this.centerBtn.click(); + await delay(2000); //animation delay } async clickOnRemoveNodeViaElementMenu(): Promise { - await this.removeNodeViaElementMenu.click(); + await this.elementMenuButton("Remove").click(); + } + + async nodeClick(x: number, y: number): Promise { + await this.canvasElement.hover({ position: { x, y } }); + await this.canvasElement.click({ position: { x, y } }); + } + + async selectCodeGraphCheckbox(checkbox: string): Promise { + await this.codeGraphCheckbox(checkbox).click(); + } + + async clickOnClearGraphBtn(): Promise { + await this.clearGraphBtn.click(); + } + + async clickOnUnhideNodesBtn(): Promise { + await this.unhideNodesBtn.click(); + } + + async changeNodePosition(x: number, y: number): Promise { + const box = (await this.canvasElement.boundingBox())!; + const targetX = x + 100; + const targetY = y + 50; + const absStartX = box.x + x; + const absStartY = box.y + y; + const absEndX = box.x + targetX; + const absEndY = box.y + targetY; + await this.page.mouse.move(absStartX, absStartY); + await this.page.mouse.down(); + await this.page.mouse.move(absEndX, absEndY); + await this.page.mouse.up(); + } + + async isNodeDetailsPanel(): Promise { + return this.nodeDetailsPanel.isVisible(); + } + + async clickOnViewNode(): Promise { + await this.elementMenuButton("View Node").click(); + } + + async getNodeDetailsHeader(): Promise { + await this.elementMenuButton("View Node").click(); + const text = await this.nodedetailsPanelHeader.innerHTML(); + return text; + } + + async clickOnNodeDetailsCloseBtn(): Promise{ + await this.nodedetailsPanelcloseBtn.click(); + } + + async getMetricsPanelInfo(): Promise<{nodes: string, edges: string}> { + const nodes = await this.canvasMetricsPanel("1").innerHTML(); + const edges = await this.canvasMetricsPanel("2").innerHTML(); + return { nodes, edges } + } + + async clickOnCopyToClipboardNodePanelDetails(): Promise { + await this.copyToClipboardNodePanelDetails.click(); + await delay(1000) + return await this.page.evaluate(() => navigator.clipboard.readText()); } - async rightClickOnNode(x : number, y: number): Promise { - const boundingBox = (await this.canvasElement.boundingBox())!; - const adjustedX = boundingBox.x + Math.round(x); - const adjustedY = boundingBox.y + Math.round(y); - await this.page.mouse.click(adjustedX, adjustedY, { button: 'right' }); + async clickOnCopyToClipboard(): Promise { + await this.elementMenuButton("Copy src to clipboard").click(); + await delay(1000) + return await this.page.evaluate(() => navigator.clipboard.readText()); } + + async getNodedetailsPanelID(): Promise { + return await this.nodedetailsPanelID.innerHTML(); + } + + async getNodeDetailsPanelElements(): Promise { + await this.elementMenuButton("View Node").click(); + await delay(500) + const elements = await this.nodedetailsPanelElements.all(); + return Promise.all(elements.map(element => element.innerHTML())); + } + + async getGraphDetails(): Promise { + await this.canvasElementBeforeGraphSelection.waitFor({ state: 'detached' }); + await delay(2000) + await this.page.waitForFunction(() => !!window.graph); + + const graphData = await this.page.evaluate(() => { + return window.graph; + }); + + return graphData; + } + + async transformNodeCoordinates(graphData: any): Promise { + const { canvasLeft, canvasTop, canvasWidth, canvasHeight, transform } = await this.canvasElement.evaluate((canvas: HTMLCanvasElement) => { + const rect = canvas.getBoundingClientRect(); + const ctx = canvas.getContext('2d'); + const transform = ctx?.getTransform()!; + return { + canvasLeft: rect.left, + canvasTop: rect.top, + canvasWidth: rect.width, + canvasHeight: rect.height, + transform, + }; + }); + + const screenCoordinates = graphData.elements.nodes.map((node: any) => { + const adjustedX = node.x * transform.a + transform.e; + const adjustedY = node.y * transform.d + transform.f; + const screenX = canvasLeft + adjustedX - 35; + const screenY = canvasTop + adjustedY - 190; + return {...node, screenX, screenY,}; + }); + + return screenCoordinates; + } + + async getCanvasScaling(): Promise<{ scaleX: number; scaleY: number }> { + const { scaleX, scaleY } = await this.canvasElement.evaluate((canvas: HTMLCanvasElement) => { + const ctx = canvas.getContext('2d'); + const transform = ctx?.getTransform(); + return { + scaleX: transform?.a || 1, + scaleY: transform?.d || 1, + }; + }); + return { scaleX, scaleY }; + } + } diff --git a/e2e/logic/api/apiCalls.ts b/e2e/logic/api/apiCalls.ts index cb885629..1b1248d4 100644 --- a/e2e/logic/api/apiCalls.ts +++ b/e2e/logic/api/apiCalls.ts @@ -14,8 +14,8 @@ export class ApiCalls { return await result.json(); } - async fetchLatestRepoInfo(projectName: string): Promise{ - const result = await postRequest(urls.baseUrl + "api/repo/" + projectName); + async projectInfo(projectName: string): Promise{ + const result = await getRequest(urls.baseUrl + "api/repo/" + projectName + "/info"); return await result.json(); } diff --git a/e2e/logic/api/apiResponse.ts b/e2e/logic/api/apiResponse.ts index 184311b0..08d89cbd 100644 --- a/e2e/logic/api/apiResponse.ts +++ b/e2e/logic/api/apiResponse.ts @@ -21,6 +21,7 @@ export interface getProjectResponse { ext?: string; name: string; path: string; + src: string; doc?: string; src_end?: number; src_start?: number; diff --git a/e2e/logic/canvasAnalysis.ts b/e2e/logic/canvasAnalysis.ts deleted file mode 100644 index e449f64f..00000000 --- a/e2e/logic/canvasAnalysis.ts +++ /dev/null @@ -1,158 +0,0 @@ -import { Locator } from "@playwright/test"; - -export type Pixel = { x: number; y: number }; - -export interface CanvasAnalysisResult { - red: Array<{ x: number; y: number; radius: number }>; - yellow: Array<{ x: number; y: number; radius: number }>; - green: Array<{ x: number; y: number; radius: number }>; -} - -export async function analyzeCanvasWithLocator(locator: Locator) { - const canvasHandle = await locator.evaluateHandle((canvas) => canvas as HTMLCanvasElement); - const canvasElement = await canvasHandle.asElement(); - if (!canvasElement) { - throw new Error("Failed to retrieve canvas element"); - } - - // Retrieve the original canvas width - const originalCanvasWidth = await canvasElement.evaluate((canvas) => canvas.width); - - const result = await canvasElement.evaluate( - (canvas, originalWidth) => { - const ctx = canvas?.getContext("2d"); - if (!ctx) { - throw new Error("Failed to get 2D context"); - } - - const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); - const { data, width, height } = imageData; - - const scaleFactor = canvas.width / originalWidth; - const adjustedRadius = 3 / scaleFactor; - const adjustedMergeRadius = 10 / scaleFactor; - - type Pixel = { x: number; y: number }; - - const redPixels: Pixel[] = []; - const yellowPixels: Pixel[] = []; - const greenPixels: Pixel[] = []; - - const isRedPixel = (r: number, g: number, b: number) => r > 170 && g < 120 && b < 120; - const isYellowPixel = (r: number, g: number, b: number) => r > 170 && g > 170 && b < 130; - const isGreenPixel = (r: number, g: number, b: number) => g > 120 && g > r && g > b && r < 50 && b < 160; - - for (let y = 0; y < height; y++) { - for (let x = 0; x < width; x++) { - const i = (y * width + x) * 4; - const r = data[i]; - const g = data[i + 1]; - const b = data[i + 2]; - - if (isRedPixel(r, g, b)) redPixels.push({ x, y }); - if (isYellowPixel(r, g, b)) yellowPixels.push({ x, y }); - if (isGreenPixel(r, g, b)) greenPixels.push({ x, y }); - } - } - - const clusterNodes = (pixels: Pixel[], radius: number): Pixel[][] => { - const visited = new Set(); - const clusters: Pixel[][] = []; - - pixels.forEach((pixel) => { - const key = `${pixel.x},${pixel.y}`; - if (visited.has(key)) return; - - const cluster: Pixel[] = []; - const stack: Pixel[] = [pixel]; - - while (stack.length > 0) { - const current = stack.pop()!; - const currentKey = `${current.x},${current.y}`; - if (visited.has(currentKey)) continue; - - visited.add(currentKey); - cluster.push(current); - - pixels.forEach((neighbor) => { - const dist = Math.sqrt( - (current.x - neighbor.x) ** 2 + (current.y - neighbor.y) ** 2 - ); - if (dist <= radius) stack.push(neighbor); - }); - } - - clusters.push(cluster); - }); - - return clusters; - }; - - const mergeCloseClusters = (clusters: Pixel[][], mergeRadius: number): Pixel[][] => { - const mergedClusters: Pixel[][] = []; - const used = new Set(); - - for (let i = 0; i < clusters.length; i++) { - if (used.has(i)) continue; - - let merged = [...clusters[i]]; - for (let j = i + 1; j < clusters.length; j++) { - if (used.has(j)) continue; - - const dist = Math.sqrt( - (merged[0].x - clusters[j][0].x) ** 2 + - (merged[0].y - clusters[j][0].y) ** 2 - ); - - if (dist <= mergeRadius) { - merged = merged.concat(clusters[j]); - used.add(j); - } - } - - mergedClusters.push(merged); - used.add(i); - } - - return mergedClusters; - }; - - const redClusters = clusterNodes(redPixels, adjustedRadius); - const yellowClusters = clusterNodes(yellowPixels, adjustedRadius); - const greenClusters = clusterNodes(greenPixels, adjustedRadius); - - const mergedGreenClusters = mergeCloseClusters(greenClusters, adjustedMergeRadius); - - const filteredRedClusters = redClusters.filter((cluster) => cluster.length >= 5); - const filteredYellowClusters = yellowClusters.filter((cluster) => cluster.length >= 5); - const filteredGreenClusters = mergedGreenClusters.filter((cluster) => cluster.length >= 5); - - const calculateRadius = (cluster: Pixel[], scaleFactor: number) => { - const rawRadius = Math.sqrt(cluster.length / Math.PI) / scaleFactor; - return Math.round(rawRadius * 1000) / 1000; - }; - - return { - red: filteredRedClusters.map(cluster => ({ - x: cluster.reduce((sum, p) => sum + p.x, 0) / cluster.length, - y: cluster.reduce((sum, p) => sum + p.y, 0) / cluster.length, - radius: calculateRadius(cluster, scaleFactor) - })), - yellow: filteredYellowClusters.map(cluster => ({ - x: cluster.reduce((sum, p) => sum + p.x, 0) / cluster.length, - y: cluster.reduce((sum, p) => sum + p.y, 0) / cluster.length, - radius: calculateRadius(cluster, scaleFactor) - })), - green: filteredGreenClusters.map(cluster => ({ - x: cluster.reduce((sum, p) => sum + p.x, 0) / cluster.length, - y: cluster.reduce((sum, p) => sum + p.y, 0) / cluster.length, - radius: calculateRadius(cluster, scaleFactor) - })) - }; - }, - originalCanvasWidth - ); - - await canvasHandle.dispose(); - return result; -} \ No newline at end of file diff --git a/e2e/logic/utils.ts b/e2e/logic/utils.ts index 7cca11b2..6ba3475d 100644 --- a/e2e/logic/utils.ts +++ b/e2e/logic/utils.ts @@ -15,4 +15,6 @@ export const waitToBeEnabled = async (locator: Locator, timeout: number = 5000): return false; }; - +export function findNodeByName(nodes: { name: string }[], nodeName: string): any { + return nodes.find((node) => node.name === nodeName); + } \ No newline at end of file diff --git a/e2e/tests/canvas.spec.ts b/e2e/tests/canvas.spec.ts new file mode 100644 index 00000000..3701ac86 --- /dev/null +++ b/e2e/tests/canvas.spec.ts @@ -0,0 +1,218 @@ +import { test, expect } from "@playwright/test"; +import BrowserWrapper from "../infra/ui/browserWrapper"; +import CodeGraph from "../logic/POM/codeGraph"; +import urls from "../config/urls.json"; +import { GRAPH_ID, PROJECT_NAME } from "../config/constants"; +import { findNodeByName } from "../logic/utils"; +import { nodesPath, categories, nodes } from "../config/testData"; +import { ApiCalls } from "../logic/api/apiCalls"; + +test.describe("Canvas tests", () => { + let browser: BrowserWrapper; + + test.beforeAll(async () => { + browser = new BrowserWrapper(); + }); + + test.afterAll(async () => { + await browser.closeBrowser(); + }); + + test(`Verify zoom in functionality on canvas`, async () => { + const codeGraph = await browser.createNewPage(CodeGraph, urls.baseUrl); + await codeGraph.selectGraph(GRAPH_ID); + const initialGraph = await codeGraph.getCanvasScaling(); + await codeGraph.clickZoomIn(); + await codeGraph.clickZoomIn(); + const updatedGraph = await codeGraph.getCanvasScaling(); + expect(updatedGraph.scaleX).toBeGreaterThan(initialGraph.scaleX) + expect(updatedGraph.scaleY).toBeGreaterThan(initialGraph.scaleY) + }) + + test(`Verify zoom out functionality on canvas`, async () => { + const codeGraph = await browser.createNewPage(CodeGraph, urls.baseUrl); + await codeGraph.selectGraph(GRAPH_ID); + const initialGraph = await codeGraph.getCanvasScaling(); + await codeGraph.clickZoomOut(); + await codeGraph.clickZoomOut(); + const updatedGraph = await codeGraph.getCanvasScaling(); + expect(updatedGraph.scaleX).toBeLessThan(initialGraph.scaleX) + expect(updatedGraph.scaleY).toBeLessThan(initialGraph.scaleY) + }) + + test(`Verify center graph button centers nodes in canvas`, async () => { + const codeGraph = await browser.createNewPage(CodeGraph, urls.baseUrl); + await codeGraph.selectGraph(GRAPH_ID); + const initialGraph = await codeGraph.getCanvasScaling(); + + await codeGraph.clickZoomOut(); + await codeGraph.clickZoomOut(); + await codeGraph.clickCenter(); + const updatedGraph = await codeGraph.getCanvasScaling(); + expect(Math.abs(initialGraph.scaleX - updatedGraph.scaleX)).toBeLessThanOrEqual(0.1); + expect(Math.abs(initialGraph.scaleY - updatedGraph.scaleY)).toBeLessThanOrEqual(0.1); + + }) + + nodes.slice(0,2).forEach((node) => { + test(`Validate node hide functionality via element menu in canvas for ${node.nodeName}`, async () => { + const codeGraph = await browser.createNewPage(CodeGraph, urls.baseUrl); + await codeGraph.selectGraph(GRAPH_ID); + const initialGraph = await codeGraph.getGraphDetails(); + const convertCoordinates = await codeGraph.transformNodeCoordinates(initialGraph); + const targetNode = findNodeByName(convertCoordinates, node.nodeName); + await codeGraph.nodeClick(targetNode.screenX, targetNode.screenY); + await codeGraph.clickOnRemoveNodeViaElementMenu(); + const updatedGraph = await codeGraph.getGraphDetails(); + const targetNodeForUpdateGraph = findNodeByName(updatedGraph.elements.nodes, node.nodeName); + expect(targetNodeForUpdateGraph.visible).toBe(false); + }); + }) + + nodes.slice(0,2).forEach((node) => { + test(`Validate unhide node functionality after hiding a node in canvas for ${node.nodeName}`, async () => { + const codeGraph = await browser.createNewPage(CodeGraph, urls.baseUrl); + await codeGraph.selectGraph(GRAPH_ID); + const initialGraph = await codeGraph.getGraphDetails(); + const convertCoordinates = await codeGraph.transformNodeCoordinates(initialGraph); + const targetNode = findNodeByName(convertCoordinates, node.nodeName); + await codeGraph.nodeClick(targetNode.screenX, targetNode.screenY); + await codeGraph.clickOnRemoveNodeViaElementMenu(); + await codeGraph.clickOnUnhideNodesBtn(); + const updatedGraph = await codeGraph.getGraphDetails(); + const targetNodeForUpdateGraph = findNodeByName(updatedGraph.elements.nodes, node.nodeName); + expect(targetNodeForUpdateGraph.visible).toBe(true); + }); + }) + + categories.forEach((category, index) => { + const checkboxIndex = index + 1; + test(`Verify that unchecking the ${category} checkbox hides ${category} nodes on the canvas`, async () => { + const codeGraph = await browser.createNewPage(CodeGraph, urls.baseUrl); + await codeGraph.selectGraph(GRAPH_ID); + await codeGraph.selectCodeGraphCheckbox(checkboxIndex.toString()); + const result = await codeGraph.getGraphDetails(); + const findItem = result.categories.find((item: { name: string; }) => item.name === category) + expect(findItem.show).toBe(false) + }); + }) + + nodesPath.forEach((path) => { + test(`Verify "Clear graph" button resets canvas view for path ${path.firstNode} and ${path.secondNode}`, async () => { + const codeGraph = await browser.createNewPage(CodeGraph, urls.baseUrl); + await codeGraph.selectGraph(GRAPH_ID); + await codeGraph.clickOnshowPathBtn(); + await codeGraph.insertInputForShowPath("1", path.firstNode); + await codeGraph.insertInputForShowPath("2", path.secondNode); + const initialGraph = await codeGraph.getGraphDetails(); + const firstNode = findNodeByName(initialGraph.elements.nodes, path.firstNode); + const secondNode = findNodeByName(initialGraph.elements.nodes, path.secondNode); + expect(firstNode.isPath).toBe(true); + expect(secondNode.isPath).toBe(true); + await codeGraph.clickOnClearGraphBtn(); + const updateGraph = await codeGraph.getGraphDetails(); + const firstNode1 = findNodeByName(updateGraph.elements.nodes, path.firstNode); + const secondNode1 = findNodeByName(updateGraph.elements.nodes, path.secondNode); + expect(firstNode1.isPath).toBe(false); + expect(secondNode1.isPath).toBe(false); + }); + }) + + for (let index = 0; index < 2; index++) { + const checkboxIndex = index + 1; + test(`Verify selecting different graphs displays nodes in canvas - Iteration ${index + 1}`, async () => { + const codeGraph = await browser.createNewPage(CodeGraph, urls.baseUrl); + await codeGraph.selectGraph(checkboxIndex); + const result = await codeGraph.getGraphDetails(); + expect(result.elements.nodes.length).toBeGreaterThan(1); + expect(result.elements.links.length).toBeGreaterThan(1); + }); + } + + for (let index = 0; index < 3; index++) { + const nodeIndex: number = index + 1; + test(`Validate canvas node dragging for node: ${index}`, async () => { + const codeGraph = await browser.createNewPage(CodeGraph, urls.baseUrl); + await codeGraph.selectGraph(GRAPH_ID); + const initialGraph = await codeGraph.getGraphDetails(); + const convertCoordinates = await codeGraph.transformNodeCoordinates(initialGraph); + await codeGraph.changeNodePosition(convertCoordinates[nodeIndex].screenX, convertCoordinates[nodeIndex].screenY); + const updateGraph = await codeGraph.getGraphDetails(); + expect(updateGraph.elements.nodes[nodeIndex].x).not.toBe(initialGraph.elements.nodes[nodeIndex].x); + expect(updateGraph.elements.nodes[nodeIndex].y).not.toBe(initialGraph.elements.nodes[nodeIndex].y); + }); + } + + test(`Validate node and edge counts in canvas match API data`, async () => { + const codeGraph = await browser.createNewPage(CodeGraph, urls.baseUrl); + await codeGraph.selectGraph(GRAPH_ID); + const { nodes, edges } = await codeGraph.getMetricsPanelInfo(); + const api = new ApiCalls(); + const response = await api.projectInfo(PROJECT_NAME); + expect(response.result.info.node_count).toEqual(parseInt(nodes)); + expect(response.result.info.edge_count).toEqual(parseInt(edges)); + }); + + + test(`Validate displayed nodes match API response after selecting a graph via UI`, async () => { + const codeGraph = await browser.createNewPage(CodeGraph, urls.baseUrl); + await codeGraph.selectGraph(GRAPH_ID); + const graphData = await codeGraph.getGraphDetails(); + const api = new ApiCalls(); + const response = await api.getProject(PROJECT_NAME); + const isMatching = graphData.elements.nodes.slice(0, 2).every( + (node: any, index: number) => node.name === response.result.entities.nodes[index].properties.name + ); + expect(isMatching).toBe(true) + }); + + nodes.slice(0,2).forEach((node) => { + test(`Validate copy to clipboard functionality for node and verify with api for ${node.nodeName}`, async () => { + const codeGraph = await browser.createNewPage(CodeGraph, urls.baseUrl); + await codeGraph.selectGraph(GRAPH_ID); + const graphData = await codeGraph.getGraphDetails(); + const convertCoordinates = await codeGraph.transformNodeCoordinates(graphData); + const targetNode = findNodeByName(convertCoordinates, node.nodeName); + await codeGraph.nodeClick(targetNode.screenX, targetNode.screenY); + const result = await codeGraph.clickOnCopyToClipboard(); + const api = new ApiCalls(); + const response = await api.getProject(PROJECT_NAME); + const matchingNode = response.result.entities.nodes.find(nod => nod.properties?.name === node.nodeName); + expect(matchingNode?.properties.src).toBe(result); + }); + }) + + nodesPath.forEach(({firstNode, secondNode}) => { + test(`Verify successful node path connection in canvas between ${firstNode} and ${secondNode} via UI`, async () => { + const codeGraph = await browser.createNewPage(CodeGraph, urls.baseUrl); + await codeGraph.selectGraph(GRAPH_ID); + await codeGraph.clickOnshowPathBtn(); + await codeGraph.insertInputForShowPath("1", firstNode); + await codeGraph.insertInputForShowPath("2", secondNode); + const result = await codeGraph.getGraphDetails(); + const firstNodeRes = findNodeByName(result.elements.nodes, firstNode); + const secondnodeRes = findNodeByName(result.elements.nodes, secondNode); + expect(firstNodeRes.isPath).toBe(true) + expect(secondnodeRes.isPath).toBe(true) + }) + }) + + nodesPath.forEach((path) => { + test(`Validate node path connection in canvas ui and confirm via api for path ${path.firstNode} and ${path.secondNode}`, async () => { + const codeGraph = await browser.createNewPage(CodeGraph, urls.baseUrl); + await codeGraph.selectGraph(GRAPH_ID); + await codeGraph.clickOnshowPathBtn(); + await codeGraph.insertInputForShowPath("1", path.firstNode); + await codeGraph.insertInputForShowPath("2", path.secondNode); + const result = await codeGraph.getGraphDetails(); + const firstNodeRes = findNodeByName(result.elements.nodes, path.firstNode); + const secondNodeRes = findNodeByName(result.elements.nodes, path.secondNode); + + const api = new ApiCalls(); + const response = await api.showPath(PROJECT_NAME ,firstNodeRes.id, secondNodeRes.id); + const callsRelationObject = response.result.paths[0].find(item => item.relation === "CALLS") + expect(callsRelationObject?.src_node).toBe(firstNodeRes.id); + expect(callsRelationObject?.dest_node).toBe(secondNodeRes.id); + }); + }) +}); diff --git a/e2e/tests/chat.spec.ts b/e2e/tests/chat.spec.ts index 02e20253..1f245f11 100644 --- a/e2e/tests/chat.spec.ts +++ b/e2e/tests/chat.spec.ts @@ -3,8 +3,9 @@ import BrowserWrapper from "../infra/ui/browserWrapper"; import urls from "../config/urls.json"; import { ApiCalls } from "../logic/api/apiCalls"; import CodeGraph from "../logic/POM/codeGraph"; -import { CHAT_OPTTIONS_COUNT, GRAPH_ID, Node_Add_Edge, Node_Import_Data, Node_Question, PROJECT_NAME } from "../config/constants"; +import { CHAT_OPTTIONS_COUNT, GRAPH_ID, Node_Question, PROJECT_NAME } from "../config/constants"; import { delay } from "../logic/utils"; +import { nodesPath } from "../config/testData"; test.describe("Chat tests", () => { let browser: BrowserWrapper; @@ -56,32 +57,74 @@ test.describe("Chat tests", () => { expect(await chat.isAtBottom()).toBe(true); }); - test("Verify successful node path connection between two nodes in chat", async () => { + nodesPath.forEach((path) => { + test(`Verify successful node path connection between two nodes in chat for ${path.firstNode} and ${path.secondNode}`, async () => { + const chat = await browser.createNewPage(CodeGraph, urls.baseUrl); + await chat.selectGraph(GRAPH_ID); + await chat.clickOnshowPathBtn(); + await chat.insertInputForShowPath("1", path.firstNode); + await chat.insertInputForShowPath("2", path.secondNode); + expect(await chat.isNodeVisibleInLastChatPath(path.firstNode)).toBe(true); + expect(await chat.isNodeVisibleInLastChatPath(path.secondNode)).toBe(true); + }); + }) + + nodesPath.forEach((path) => { + test(`Verify unsuccessful node path connection between two nodes in chat for ${path.firstNode} and ${path.secondNode}`, async () => { + const chat = await browser.createNewPage(CodeGraph, urls.baseUrl); + await chat.selectGraph(GRAPH_ID); + await chat.clickOnshowPathBtn(); + await chat.insertInputForShowPath("1", path.secondNode); + await chat.insertInputForShowPath("2", path.firstNode); + await delay(500); + expect(await chat.isNotificationError()).toBe(true); + }); + }) + + test("Validate error notification and its closure when sending an empty question in chat", async () => { const chat = await browser.createNewPage(CodeGraph, urls.baseUrl); await chat.selectGraph(GRAPH_ID); - await chat.clickOnshowPathBtn(); - await chat.insertInputForShowPath("1", Node_Import_Data); - await chat.insertInputForShowPath("2", Node_Add_Edge); - expect(await chat.isNodeVisibleInLastChatPath(Node_Import_Data)).toBe(true); - expect(await chat.isNodeVisibleInLastChatPath(Node_Add_Edge)).toBe(true); + await chat.clickAskquestionBtn(); + expect(await chat.isNotificationError()).toBe(true); + await chat.clickOnNotificationErrorCloseBtn(); + expect(await chat.isNotificationError()).toBe(false); }); - test("Verify unsuccessful node path connection between two nodes in chat", async () => { + for (let index = 0; index < 5; index++) { + const questionNumber = index + 1; + test(`Validate displaying question ${index} in chat after selection from options menu`, async () => { + const chat = await browser.createNewPage(CodeGraph, urls.baseUrl); + await chat.selectGraph(GRAPH_ID); + await chat.clickOnQuestionOptionsMenu(); + const selectedQuestion = await chat.selectAndGetQuestionInOptionsMenu(questionNumber.toString()); + expect(selectedQuestion).toEqual(await chat.getLastQuestionInChat()) + }); + } + + test(`Validate consistent UI responses for repeated questions in chat`, async () => { const chat = await browser.createNewPage(CodeGraph, urls.baseUrl); await chat.selectGraph(GRAPH_ID); - await chat.clickOnshowPathBtn(); - await chat.insertInputForShowPath("1", Node_Add_Edge); - await chat.insertInputForShowPath("2", Node_Import_Data); - await delay(500); - expect(await chat.isNotificationError()).toBe(true); + const responses: string[] = []; + for (let i = 0; i < 3; i++) { + await chat.sendMessage(Node_Question); + const result = await chat.getTextInLastChatElement(); + const number = result.match(/\d+/g)?.[0]!; + responses.push(number); + } + const identicalResponses = responses.every((value) => value === responses[0]); + expect(identicalResponses).toBe(true); }); - test("Validate error notification when sending an empty question in chat", async () => { + test(`Validate UI response matches API response for a given question in chat`, async () => { const chat = await browser.createNewPage(CodeGraph, urls.baseUrl); await chat.selectGraph(GRAPH_ID); - await chat.clickAskquestionBtn(); - await delay(500); - expect(await chat.isNotificationError()).toBe(true); - }); + await chat.sendMessage(Node_Question); + const uiResponse = await chat.getTextInLastChatElement(); + const number = uiResponse.match(/\d+/g)?.[0]!; + + const api = new ApiCalls(); + const apiResponse = await api.askQuestion(PROJECT_NAME, Node_Question); + expect(number).toEqual(apiResponse.result.response.match(/\d+/g)?.[0]); + }); }); diff --git a/e2e/tests/codeGraph.spec.ts b/e2e/tests/codeGraph.spec.ts deleted file mode 100644 index f2eaa60f..00000000 --- a/e2e/tests/codeGraph.spec.ts +++ /dev/null @@ -1,129 +0,0 @@ -import { test, expect } from "@playwright/test"; -import BrowserWrapper from "../infra/ui/browserWrapper"; -import CodeGraph from "../logic/POM/codeGraph"; -import urls from "../config/urls.json"; -import { GRAPH_ID } from "../config/constants"; -import { delay } from "../logic/utils"; -import { searchData, specialCharacters } from "../config/testData"; -import { CanvasAnalysisResult } from "../logic/canvasAnalysis"; - -const colors: (keyof CanvasAnalysisResult)[] = ["red", "yellow", "green"]; - -test.describe("Code graph tests", () => { - let browser: BrowserWrapper; - - test.beforeAll(async () => { - browser = new BrowserWrapper(); - }); - - test.afterAll(async () => { - await browser.closeBrowser(); - }); - - searchData.slice(0, 2).forEach(({ searchInput }) => { - test(`Verify search bar auto-complete behavior for input: ${searchInput} via UI`, async () => { - const codeGraph = await browser.createNewPage(CodeGraph, urls.baseUrl); - await codeGraph.selectGraph(GRAPH_ID); - await codeGraph.fillSearchBar(searchInput); - await delay(1000); - const textList = await codeGraph.getSearchBarElementsText(); - textList.forEach((text) => { - expect(text.toLowerCase()).toContain(searchInput); - }); - }); - }); - - searchData.slice(2, 4).forEach(({ searchInput, completedSearchInput }) => { - test(`Validate search bar updates with selected element: ${searchInput}`, async () => { - const codeGraph = await browser.createNewPage(CodeGraph, urls.baseUrl); - await codeGraph.selectGraph(GRAPH_ID); - await codeGraph.fillSearchBar(searchInput); - await delay(1000); - await codeGraph.selectSearchBarOptionBtn("1"); - expect(await codeGraph.getSearchBarInputValue()).toBe( - completedSearchInput - ); - }); - }); - - searchData.slice(0, 2).forEach(({ searchInput }) => { - test(`Verify auto-scroll scroll in search bar list for: ${searchInput}`, async () => { - const codeGraph = await browser.createNewPage(CodeGraph, urls.baseUrl); - await codeGraph.selectGraph(GRAPH_ID); - await codeGraph.fillSearchBar(searchInput); - await codeGraph.scrollToBottomInSearchBarList(); - expect(await codeGraph.isScrolledToBottomInSearchBarList()).toBe(true); - }); - }); - - specialCharacters.forEach(({ character, expectedRes }) => { - test(`Verify entering special characters behavior in search bar for: ${character}`, async () => { - const codeGraph = await browser.createNewPage(CodeGraph, urls.baseUrl); - await codeGraph.selectGraph(GRAPH_ID); - await codeGraph.fillSearchBar(character); - await delay(1000); - expect((await codeGraph.getSearchBarInputValue()).includes(character)).toBe(expectedRes); - }); - }); - - test(`Verify zoom in functionality on canvas`, async () => { - const codeGraph = await browser.createNewPage(CodeGraph, urls.baseUrl); - await codeGraph.selectGraph(GRAPH_ID); - const initialNodeAnalysis = await codeGraph.getCanvasAnalysis(); - await codeGraph.clickZoomIn(); - await codeGraph.clickZoomIn(); - const updatedNodeAnalysis = await codeGraph.getCanvasAnalysis(); - for (const color of colors) { - const initialRadius = initialNodeAnalysis[color][0].radius; - const updatedRadius = updatedNodeAnalysis[color][0].radius; - expect(initialRadius).toBeDefined(); - expect(updatedRadius).toBeDefined(); - expect(updatedRadius).toBeGreaterThan(initialRadius); - } - }) - - test(`Verify zoom out functionality on canvas`, async () => { - const codeGraph = await browser.createNewPage(CodeGraph, urls.baseUrl); - await codeGraph.selectGraph(GRAPH_ID); - const initialNodeAnalysis = await codeGraph.getCanvasAnalysis(); - for (let i = 0; i < 5; i++) { - await codeGraph.clickZoomOut(); - } - const updatedNodeAnalysis = await codeGraph.getCanvasAnalysis(); - for (const color of colors) { - const initialRadius = initialNodeAnalysis[color][0].radius; - const updatedRadius = updatedNodeAnalysis[color][0].radius; - expect(initialRadius).toBeDefined(); - expect(updatedRadius).toBeDefined(); - expect(updatedRadius).toBeLessThan(initialRadius); - } - }) - - test(`Verify center graph button centers nodes in canvas`, async () => { - const codeGraph = await browser.createNewPage(CodeGraph, urls.baseUrl); - await codeGraph.selectGraph(GRAPH_ID); - const initialNodeAnalysis = await codeGraph.getCanvasAnalysis(); - await codeGraph.clickZoomIn(); - await codeGraph.clickZoomIn(); - await codeGraph.clickCenter(); - const updatedNodeAnalysis = await codeGraph.getCanvasAnalysis(); - for (const color of colors) { - const initialRadius = Math.round(initialNodeAnalysis[color][0].radius); - const updatedRadius = Math.round(updatedNodeAnalysis[color][0].radius); - expect(initialRadius).toBeDefined(); - expect(updatedRadius).toBeDefined(); - expect(updatedRadius).toBeCloseTo(initialRadius); - } - }) - - test(`Validate node removal functionality via element menu in canvas`, async () => { - const codeGraph = await browser.createNewPage(CodeGraph, urls.baseUrl); - await codeGraph.selectGraph(GRAPH_ID); - const initialNodeAnalysis = await codeGraph.getCanvasAnalysis(); - await codeGraph.rightClickOnNode(initialNodeAnalysis.red[0].x, initialNodeAnalysis.red[0].y); - await codeGraph.clickOnRemoveNodeViaElementMenu(); - const updatedNodeAnalysis = await codeGraph.getCanvasAnalysis(); - expect(initialNodeAnalysis.red.length).toBeGreaterThan(updatedNodeAnalysis.red.length); - }); - -}); diff --git a/e2e/tests/navBar.spec.ts b/e2e/tests/navBar.spec.ts index b6706cbf..9f294a75 100644 --- a/e2e/tests/navBar.spec.ts +++ b/e2e/tests/navBar.spec.ts @@ -38,4 +38,12 @@ test.describe(' Navbar tests', () => { await navBar.clickCreateNewProjectBtn(); expect(await navBar.isCreateNewProjectDialog()).toBe(true) }) + + test("Validate Tip popup visibility and closure functionality", async () => { + const navBar = await browser.createNewPage(CodeGraph, urls.baseUrl); + await navBar.clickonTipBtn(); + expect(await navBar.isTipMenuVisible()).toBe(true); + await navBar.clickonTipMenuCloseBtn(); + expect(await navBar.isTipMenuVisible()).toBe(false); + }); }); diff --git a/e2e/tests/nodeDetailsPanel.spec.ts b/e2e/tests/nodeDetailsPanel.spec.ts new file mode 100644 index 00000000..aa59a6fe --- /dev/null +++ b/e2e/tests/nodeDetailsPanel.spec.ts @@ -0,0 +1,101 @@ +import { test, expect } from "@playwright/test"; +import BrowserWrapper from "../infra/ui/browserWrapper"; +import CodeGraph from "../logic/POM/codeGraph"; +import urls from "../config/urls.json"; +import { GRAPH_ID, PROJECT_NAME } from "../config/constants"; +import { nodes } from "../config/testData"; +import { ApiCalls } from "../logic/api/apiCalls"; +import { findNodeByName } from "../logic/utils"; + +test.describe("Node details panel tests", () => { + let browser: BrowserWrapper; + + test.beforeAll(async () => { + browser = new BrowserWrapper(); + }); + + test.afterAll(async () => { + await browser.closeBrowser(); + }); + + nodes.slice(0,2).forEach((node) => { + test(`Validate node details panel displayed on node click for ${node.nodeName}`, async () => { + const codeGraph = await browser.createNewPage(CodeGraph, urls.baseUrl); + await codeGraph.selectGraph(GRAPH_ID); + const graphData = await codeGraph.getGraphDetails(); + const convertCoordinates = await codeGraph.transformNodeCoordinates(graphData); + const targetNode = findNodeByName(convertCoordinates, node.nodeName); + await codeGraph.nodeClick(targetNode.screenX, targetNode.screenY); + await codeGraph.clickOnViewNode(); + expect(await codeGraph.isNodeDetailsPanel()).toBe(true) + }) + }) + + nodes.slice(0,2).forEach((node) => { + test(`Validate node details panel is not displayed after close interaction for ${node.nodeName}`, async () => { + const codeGraph = await browser.createNewPage(CodeGraph, urls.baseUrl); + await codeGraph.selectGraph(GRAPH_ID); + const graphData = await codeGraph.getGraphDetails(); + const convertCoordinates = await codeGraph.transformNodeCoordinates(graphData); + const node1 = findNodeByName(convertCoordinates, node.nodeName); + await codeGraph.nodeClick(node1.screenX, node1.screenY); + await codeGraph.clickOnViewNode(); + await codeGraph.clickOnNodeDetailsCloseBtn(); + expect(await codeGraph.isNodeDetailsPanel()).toBe(false) + }) + }) + + nodes.forEach((node) => { + test(`Validate node details panel header displays correct node name: ${node.nodeName}`, async () => { + const codeGraph = await browser.createNewPage(CodeGraph, urls.baseUrl); + await codeGraph.selectGraph(GRAPH_ID); + const graphData = await codeGraph.getGraphDetails(); + const convertCoordinates = await codeGraph.transformNodeCoordinates(graphData); + const node1 = findNodeByName(convertCoordinates, node.nodeName); + await codeGraph.nodeClick(node1.screenX, node1.screenY); + expect(await codeGraph.getNodeDetailsHeader()).toContain(node.nodeName.toUpperCase()) + }) + }) + + nodes.slice(0,2).forEach((node) => { + test(`Validate copy functionality for node inside node details panel and verify with api for ${node.nodeName}`, async () => { + const codeGraph = await browser.createNewPage(CodeGraph, urls.baseUrl); + await codeGraph.selectGraph(GRAPH_ID); + const graphData = await codeGraph.getGraphDetails(); + const convertCoordinates = await codeGraph.transformNodeCoordinates(graphData); + const nodeData = findNodeByName(convertCoordinates, node.nodeName); + await codeGraph.nodeClick(nodeData.screenX, nodeData.screenY); + await codeGraph.clickOnViewNode(); + const result = await codeGraph.clickOnCopyToClipboardNodePanelDetails(); + const api = new ApiCalls(); + const response = await api.getProject(PROJECT_NAME); + const foundNode = response.result.entities.nodes.find(nod => nod.properties?.name === node.nodeName); + expect(foundNode?.properties.src).toBe(result); + }); + }) + + nodes.slice(0,2).forEach((node) => { + test(`Validate view node panel keys via api for ${node.nodeName}`, async () => { + const codeGraph = await browser.createNewPage(CodeGraph, urls.baseUrl); + await codeGraph.selectGraph(GRAPH_ID); + const graphData = await codeGraph.getGraphDetails(); + const convertCoordinates = await codeGraph.transformNodeCoordinates(graphData); + const node1 = findNodeByName(convertCoordinates, node.nodeName); + const api = new ApiCalls(); + const response = await api.getProject(PROJECT_NAME); + const data: any = response.result.entities.nodes; + const findNode = data.find((nod: any) => nod.properties.name === node.nodeName); + + await codeGraph.nodeClick(node1.screenX, node1.screenY); + let elements = await codeGraph.getNodeDetailsPanelElements(); + elements.splice(2,1) + const apiFields = [...Object.keys(findNode), ...Object.keys(findNode.properties || {})]; + + const isValid = elements.every((field) => { + const cleanedField = field.replace(':', '').trim(); + return apiFields.includes(cleanedField); + }); + expect(isValid).toBe(true) + }); + }) +}); \ No newline at end of file diff --git a/e2e/tests/searchBar.spec.ts b/e2e/tests/searchBar.spec.ts new file mode 100644 index 00000000..a7427d3d --- /dev/null +++ b/e2e/tests/searchBar.spec.ts @@ -0,0 +1,77 @@ +import { test, expect } from "@playwright/test"; +import BrowserWrapper from "../infra/ui/browserWrapper"; +import CodeGraph from "../logic/POM/codeGraph"; +import urls from "../config/urls.json"; +import { GRAPH_ID, PROJECT_NAME } from "../config/constants"; +import { delay } from "../logic/utils"; +import { searchData, specialCharacters } from "../config/testData"; +import { ApiCalls } from "../logic/api/apiCalls"; + +test.describe("search bar tests", () => { + let browser: BrowserWrapper; + + test.beforeAll(async () => { + browser = new BrowserWrapper(); + }); + + test.afterAll(async () => { + await browser.closeBrowser(); + }); + + searchData.slice(0, 2).forEach(({ searchInput }) => { + test(`Verify search bar auto-complete behavior for input: ${searchInput} via UI`, async () => { + const codeGraph = await browser.createNewPage(CodeGraph, urls.baseUrl); + await codeGraph.selectGraph(GRAPH_ID); + await codeGraph.fillSearchBar(searchInput); + await delay(1000); + const textList = await codeGraph.getSearchBarElementsText(); + textList.forEach((text) => { + expect(text.toLowerCase()).toContain(searchInput); + }); + }); + }); + + searchData.slice(2, 4).forEach(({ searchInput, completedSearchInput }) => { + test(`Validate search bar updates with selected element: ${searchInput}`, async () => { + const codeGraph = await browser.createNewPage(CodeGraph, urls.baseUrl); + await codeGraph.selectGraph(GRAPH_ID); + await codeGraph.fillSearchBar(searchInput); + await codeGraph.selectSearchBarOptionBtn("1"); + expect(await codeGraph.getSearchBarInputValue()).toBe( + completedSearchInput + ); + }); + }); + + searchData.slice(0, 2).forEach(({ searchInput }) => { + test(`Verify auto-scroll scroll in search bar list for: ${searchInput}`, async () => { + const codeGraph = await browser.createNewPage(CodeGraph, urls.baseUrl); + await codeGraph.selectGraph(GRAPH_ID); + await codeGraph.fillSearchBar(searchInput); + await codeGraph.scrollToBottomInSearchBarList(); + expect(await codeGraph.isScrolledToBottomInSearchBarList()).toBe(true); + }); + }); + + specialCharacters.forEach(({ character, expectedRes }) => { + test(`Verify entering special characters behavior in search bar for: ${character}`, async () => { + const codeGraph = await browser.createNewPage(CodeGraph, urls.baseUrl); + await codeGraph.selectGraph(GRAPH_ID); + await codeGraph.fillSearchBar(character); + await delay(1000); + expect((await codeGraph.getSearchBarInputValue()).includes(character)).toBe(expectedRes); + }); + }); + + searchData.slice(0, 2).forEach(({ searchInput}) => { + test(`search bar auto complete via ui and validating via api for: ${searchInput}`, async () => { + const codeGraph = await browser.createNewPage(CodeGraph, urls.baseUrl); + await codeGraph.selectGraph(GRAPH_ID); + await codeGraph.fillSearchBar(searchInput); + const count = await codeGraph.getSearchAutoCompleteCount(); + const api = new ApiCalls(); + const response = await api.searchAutoComplete(PROJECT_NAME, searchInput); + expect(count).toBe(response.result.completions.length); + }); + }) +}) \ No newline at end of file diff --git a/lib/languages/language.ts b/lib/languages/language.ts deleted file mode 100644 index b2163444..00000000 --- a/lib/languages/language.ts +++ /dev/null @@ -1,54 +0,0 @@ -import path from 'path'; -import Parser from 'web-tree-sitter'; - -// Initialize Tree-Sitter parser -await Parser.init({ - locateFile(scriptName: string, scriptDirectory: string) { - return path.join(process.cwd(), 'app/parsers', scriptName); - }, -}); - -//----------------------------------------------------------------------------- -// Tree-Sitter queries -//----------------------------------------------------------------------------- -export default class Language { - - public language: Parser.Language; - - // class definition tree-sitter query - // responsible for matching class definition, in addition to extracting the class name - public class_definition_query: Parser.Query; - - // function definition tree-sitter query - // responsible for matching function definition, in addition to extracting the function name - public function_definition_query: Parser.Query; - - // function call tree-sitter query - // responsible for matching function calls, in addition to extracting the callee function name - public function_call_query: Parser.Query; - - // function call tree-sitter query - // responsible for matching function calls of type self.f() - // in addition to extracting the callee function name - public function_attr_call_query: Parser.Query; - - // identifier tree-sitter query - // responsible for matching Identifier nodes - public identifier_query: Parser.Query; - - constructor(language: Parser.Language, - class_definition_query: Parser.Query, - function_definition_query: Parser.Query, - function_call_query: Parser.Query, - function_attr_call_query: Parser.Query, - identifier_query: Parser.Query) { - - this.language = language; - this.class_definition_query = class_definition_query; - this.function_definition_query = function_definition_query; - this.function_call_query = function_call_query; - this.function_attr_call_query = function_attr_call_query; - this.identifier_query = identifier_query; - } - -} diff --git a/lib/languages/python.ts b/lib/languages/python.ts deleted file mode 100644 index 1053d4a6..00000000 --- a/lib/languages/python.ts +++ /dev/null @@ -1,21 +0,0 @@ -import path from 'path'; -import Parser from 'web-tree-sitter'; -import Language from './language'; - -const PYTHON_LANG = await Parser.Language.load(path.join(process.cwd(), 'app/parsers/tree-sitter-python.wasm')); - -//----------------------------------------------------------------------------- -// Tree-Sitter queries -//----------------------------------------------------------------------------- -export default class Python extends Language { - - constructor() { - super( - PYTHON_LANG, - PYTHON_LANG.query(`(class_definition name: (identifier) @class-name) @class-definition`), - PYTHON_LANG.query(`((function_definition name: (identifier) @function-name parameters: (parameters) @parameters) @function-definition)`), - PYTHON_LANG.query(`((call function: (identifier) @function-name) @function-call)`), - PYTHON_LANG.query(`((call function: (attribute object: (identifier) attribute: (identifier) @function-name )) @function-call)`), - PYTHON_LANG.query(`((identifier) @identifier)`)) - } -} diff --git a/package-lock.json b/package-lock.json index fc79f9b7..1a67026a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,34 +16,32 @@ "@radix-ui/react-select": "^2.1.2", "@radix-ui/react-slot": "^1.0.2", "@radix-ui/react-toast": "^1.2.2", - "@radix-ui/react-tooltip": "^1.0.7", + "@radix-ui/react-tooltip": "^1.1.4", + "@types/react-gtm-module": "^2.0.4", "autoprefixer": "^10.4.20", - "class-variance-authority": "^0.7.0", + "class-variance-authority": "^0.7.1", "clsx": "^2.1.0", - "cytoscape": "^3.30.4", - "cytoscape-fcose": "^2.2.0", "lucide-react": "^0.441.0", - "next": "15.0.3", + "next": "^15.1.2", + "playwright": "^1.49.1", "react": "^18", - "react-cytoscapejs": "^2.0.0", "react-dom": "^18", + "react-force-graph-2d": "^1.25.8", + "react-gtm-module": "^2.0.11", "react-resizable-panels": "^2.0.20", "react-syntax-highlighter": "^15.6.1", "react-type-animation": "^3.2.0", - "tailwind-merge": "^2.2.0", - "tailwindcss-animate": "^1.0.7", - "web-tree-sitter": "^0.22.6" + "tailwind-merge": "^2.5.5", + "tailwindcss-animate": "^1.0.7" }, "devDependencies": { - "@playwright/test": "^1.48.2", - "@types/cytoscape-fcose": "^2.2.4", - "@types/node": "^22", + "@playwright/test": "^1.49.1", + "@types/node": "^22.10.2", "@types/react": "^18", - "@types/react-cytoscapejs": "^1.2.5", "@types/react-dom": "^18", "@types/react-syntax-highlighter": "^15.5.13", "eslint": "^9", - "eslint-config-next": "15.0.3", + "eslint-config-next": "^15.1.2", "tailwindcss": "^3.4.3", "typescript": "^5" } @@ -75,7 +73,6 @@ "version": "1.3.1", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.3.1.tgz", "integrity": "sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==", - "license": "MIT", "optional": true, "dependencies": { "tslib": "^2.4.0" @@ -300,7 +297,6 @@ "cpu": [ "arm64" ], - "license": "Apache-2.0", "optional": true, "os": [ "darwin" @@ -322,7 +318,6 @@ "cpu": [ "x64" ], - "license": "Apache-2.0", "optional": true, "os": [ "darwin" @@ -344,7 +339,6 @@ "cpu": [ "arm64" ], - "license": "LGPL-3.0-or-later", "optional": true, "os": [ "darwin" @@ -360,7 +354,6 @@ "cpu": [ "x64" ], - "license": "LGPL-3.0-or-later", "optional": true, "os": [ "darwin" @@ -376,7 +369,6 @@ "cpu": [ "arm" ], - "license": "LGPL-3.0-or-later", "optional": true, "os": [ "linux" @@ -392,7 +384,6 @@ "cpu": [ "arm64" ], - "license": "LGPL-3.0-or-later", "optional": true, "os": [ "linux" @@ -408,7 +399,6 @@ "cpu": [ "s390x" ], - "license": "LGPL-3.0-or-later", "optional": true, "os": [ "linux" @@ -424,7 +414,6 @@ "cpu": [ "x64" ], - "license": "LGPL-3.0-or-later", "optional": true, "os": [ "linux" @@ -440,7 +429,6 @@ "cpu": [ "arm64" ], - "license": "LGPL-3.0-or-later", "optional": true, "os": [ "linux" @@ -456,7 +444,6 @@ "cpu": [ "x64" ], - "license": "LGPL-3.0-or-later", "optional": true, "os": [ "linux" @@ -472,7 +459,6 @@ "cpu": [ "arm" ], - "license": "Apache-2.0", "optional": true, "os": [ "linux" @@ -494,7 +480,6 @@ "cpu": [ "arm64" ], - "license": "Apache-2.0", "optional": true, "os": [ "linux" @@ -516,7 +501,6 @@ "cpu": [ "s390x" ], - "license": "Apache-2.0", "optional": true, "os": [ "linux" @@ -538,7 +522,6 @@ "cpu": [ "x64" ], - "license": "Apache-2.0", "optional": true, "os": [ "linux" @@ -560,7 +543,6 @@ "cpu": [ "arm64" ], - "license": "Apache-2.0", "optional": true, "os": [ "linux" @@ -582,7 +564,6 @@ "cpu": [ "x64" ], - "license": "Apache-2.0", "optional": true, "os": [ "linux" @@ -604,7 +585,6 @@ "cpu": [ "wasm32" ], - "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", "optional": true, "dependencies": { "@emnapi/runtime": "^1.2.0" @@ -623,7 +603,6 @@ "cpu": [ "ia32" ], - "license": "Apache-2.0 AND LGPL-3.0-or-later", "optional": true, "os": [ "win32" @@ -642,7 +621,6 @@ "cpu": [ "x64" ], - "license": "Apache-2.0 AND LGPL-3.0-or-later", "optional": true, "os": [ "win32" @@ -747,15 +725,14 @@ } }, "node_modules/@next/env": { - "version": "15.0.3", - "resolved": "https://registry.npmjs.org/@next/env/-/env-15.0.3.tgz", - "integrity": "sha512-t9Xy32pjNOvVn2AS+Utt6VmyrshbpfUMhIjFO60gI58deSo/KgLOp31XZ4O+kY/Is8WAGYwA5gR7kOb1eORDBA==", - "license": "MIT" + "version": "15.1.2", + "resolved": "https://registry.npmjs.org/@next/env/-/env-15.1.2.tgz", + "integrity": "sha512-Hm3jIGsoUl6RLB1vzY+dZeqb+/kWPZ+h34yiWxW0dV87l8Im/eMOwpOA+a0L78U0HM04syEjXuRlCozqpwuojQ==" }, "node_modules/@next/eslint-plugin-next": { - "version": "15.0.3", - "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-15.0.3.tgz", - "integrity": "sha512-3Ln/nHq2V+v8uIaxCR6YfYo7ceRgZNXfTd3yW1ukTaFbO+/I8jNakrjYWODvG9BuR2v5kgVtH/C8r0i11quOgw==", + "version": "15.1.2", + "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-15.1.2.tgz", + "integrity": "sha512-sgfw3+WdaYOGPKCvM1L+UucBmRfh8V2Ygefp7ELON0+0vY7uohQwXXnVWg3rY7mXDKharQR3o7uedpfvnU2hlQ==", "dev": true, "dependencies": { "fast-glob": "3.3.1" @@ -790,13 +767,12 @@ } }, "node_modules/@next/swc-darwin-arm64": { - "version": "15.0.3", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.0.3.tgz", - "integrity": "sha512-s3Q/NOorCsLYdCKvQlWU+a+GeAd3C8Rb3L1YnetsgwXzhc3UTWrtQpB/3eCjFOdGUj5QmXfRak12uocd1ZiiQw==", + "version": "15.1.2", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.1.2.tgz", + "integrity": "sha512-b9TN7q+j5/7+rGLhFAVZiKJGIASuo8tWvInGfAd8wsULjB1uNGRCj1z1WZwwPWzVQbIKWFYqc+9L7W09qwt52w==", "cpu": [ "arm64" ], - "license": "MIT", "optional": true, "os": [ "darwin" @@ -806,13 +782,12 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "15.0.3", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.0.3.tgz", - "integrity": "sha512-Zxl/TwyXVZPCFSf0u2BNj5sE0F2uR6iSKxWpq4Wlk/Sv9Ob6YCKByQTkV2y6BCic+fkabp9190hyrDdPA/dNrw==", + "version": "15.1.2", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.1.2.tgz", + "integrity": "sha512-caR62jNDUCU+qobStO6YJ05p9E+LR0EoXh1EEmyU69cYydsAy7drMcOlUlRtQihM6K6QfvNwJuLhsHcCzNpqtA==", "cpu": [ "x64" ], - "license": "MIT", "optional": true, "os": [ "darwin" @@ -822,13 +797,12 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "15.0.3", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.0.3.tgz", - "integrity": "sha512-T5+gg2EwpsY3OoaLxUIofmMb7ohAUlcNZW0fPQ6YAutaWJaxt1Z1h+8zdl4FRIOr5ABAAhXtBcpkZNwUcKI2fw==", + "version": "15.1.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.1.2.tgz", + "integrity": "sha512-fHHXBusURjBmN6VBUtu6/5s7cCeEkuGAb/ZZiGHBLVBXMBy4D5QpM8P33Or8JD1nlOjm/ZT9sEE5HouQ0F+hUA==", "cpu": [ "arm64" ], - "license": "MIT", "optional": true, "os": [ "linux" @@ -838,13 +812,12 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "15.0.3", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.0.3.tgz", - "integrity": "sha512-WkAk6R60mwDjH4lG/JBpb2xHl2/0Vj0ZRu1TIzWuOYfQ9tt9NFsIinI1Epma77JVgy81F32X/AeD+B2cBu/YQA==", + "version": "15.1.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.1.2.tgz", + "integrity": "sha512-9CF1Pnivij7+M3G74lxr+e9h6o2YNIe7QtExWq1KUK4hsOLTBv6FJikEwCaC3NeYTflzrm69E5UfwEAbV2U9/g==", "cpu": [ "arm64" ], - "license": "MIT", "optional": true, "os": [ "linux" @@ -854,13 +827,12 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "15.0.3", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.0.3.tgz", - "integrity": "sha512-gWL/Cta1aPVqIGgDb6nxkqy06DkwJ9gAnKORdHWX1QBbSZZB+biFYPFti8aKIQL7otCE1pjyPaXpFzGeG2OS2w==", + "version": "15.1.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.1.2.tgz", + "integrity": "sha512-tINV7WmcTUf4oM/eN3Yuu/f8jQ5C6AkueZPKeALs/qfdfX57eNv4Ij7rt0SA6iZ8+fMobVfcFVv664Op0caCCg==", "cpu": [ "x64" ], - "license": "MIT", "optional": true, "os": [ "linux" @@ -870,13 +842,12 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "15.0.3", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.0.3.tgz", - "integrity": "sha512-QQEMwFd8r7C0GxQS62Zcdy6GKx999I/rTO2ubdXEe+MlZk9ZiinsrjwoiBL5/57tfyjikgh6GOU2WRQVUej3UA==", + "version": "15.1.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.1.2.tgz", + "integrity": "sha512-jf2IseC4WRsGkzeUw/cK3wci9pxR53GlLAt30+y+B+2qAQxMw6WAC3QrANIKxkcoPU3JFh/10uFfmoMDF9JXKg==", "cpu": [ "x64" ], - "license": "MIT", "optional": true, "os": [ "linux" @@ -886,13 +857,12 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "15.0.3", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.0.3.tgz", - "integrity": "sha512-9TEp47AAd/ms9fPNgtgnT7F3M1Hf7koIYYWCMQ9neOwjbVWJsHZxrFbI3iEDJ8rf1TDGpmHbKxXf2IFpAvheIQ==", + "version": "15.1.2", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.1.2.tgz", + "integrity": "sha512-wvg7MlfnaociP7k8lxLX4s2iBJm4BrNiNFhVUY+Yur5yhAJHfkS8qPPeDEUH8rQiY0PX3u/P7Q/wcg6Mv6GSAA==", "cpu": [ "arm64" ], - "license": "MIT", "optional": true, "os": [ "win32" @@ -902,13 +872,12 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "15.0.3", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.0.3.tgz", - "integrity": "sha512-VNAz+HN4OGgvZs6MOoVfnn41kBzT+M+tB+OK4cww6DNyWS6wKaDpaAm/qLeOUbnMh0oVx1+mg0uoYARF69dJyA==", + "version": "15.1.2", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.1.2.tgz", + "integrity": "sha512-D3cNA8NoT3aWISWmo7HF5Eyko/0OdOO+VagkoJuiTk7pyX3P/b+n8XA/MYvyR+xSVcbKn68B1rY9fgqjNISqzQ==", "cpu": [ "x64" ], - "license": "MIT", "optional": true, "os": [ "win32" @@ -963,12 +932,12 @@ } }, "node_modules/@playwright/test": { - "version": "1.48.2", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.48.2.tgz", - "integrity": "sha512-54w1xCWfXuax7dz4W2M9uw0gDyh+ti/0K/MxcCUxChFh37kkdxPdfZDw5QBbuPUJHr1CiHJ1hXgSs+GgeQc5Zw==", + "version": "1.49.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.49.1.tgz", + "integrity": "sha512-Ky+BVzPz8pL6PQxHqNRW1k3mIyv933LML7HktS8uik0bUXNCdPhoS/kLihiO1tMf/egaJb4IutXd7UywvXEW+g==", "devOptional": true, "dependencies": { - "playwright": "1.48.2" + "playwright": "1.49.1" }, "bin": { "playwright": "cli.js" @@ -1271,14 +1240,13 @@ } }, "node_modules/@radix-ui/react-dismissable-layer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.0.tgz", - "integrity": "sha512-/UovfmmXGptwGcBQawLzvn2jOfM0t4z3/uKffoBlj724+n3FvBbZ7M0aaBOmkp6pqFYpO4yx8tSVJjx3Fl2jig==", - "license": "MIT", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.2.tgz", + "integrity": "sha512-kEHnlhv7wUggvhuJPkyw4qspXLJOdYoAP4dO2c8ngGuXTq1w/HZp1YeVB+NQ2KbH1iEG+pvOCGYSqh9HZOz6hg==", "dependencies": { - "@radix-ui/primitive": "1.1.0", - "@radix-ui/react-compose-refs": "1.1.0", - "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-primitive": "2.0.1", "@radix-ui/react-use-callback-ref": "1.1.0", "@radix-ui/react-use-escape-keydown": "1.1.0" }, @@ -1297,6 +1265,64 @@ } } }, + "node_modules/@radix-ui/react-dismissable-layer/node_modules/@radix-ui/primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.1.tgz", + "integrity": "sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA==" + }, + "node_modules/@radix-ui/react-dismissable-layer/node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz", + "integrity": "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dismissable-layer/node_modules/@radix-ui/react-primitive": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.1.tgz", + "integrity": "sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg==", + "dependencies": { + "@radix-ui/react-slot": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dismissable-layer/node_modules/@radix-ui/react-slot": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.1.tgz", + "integrity": "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-dropdown-menu": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.2.tgz", @@ -1670,12 +1696,11 @@ } }, "node_modules/@radix-ui/react-portal": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.1.tgz", - "integrity": "sha512-A3UtLk85UtqhzFqtoC8Q0KvR2GbXF3mtPgACSazajqq6A41mEQgo53iPzY4i6BwDxlIFqWIhiQ2G729n+2aw/g==", - "license": "MIT", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.3.tgz", + "integrity": "sha512-NciRqhXnGojhT93RPyDaMPfLH3ZSl4jjIFbZQ1b/vxvZEdHsBZ49wP9w8L3HzUQwep01LcWtkUvm0OVB5JAHTw==", "dependencies": { - "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-primitive": "2.0.1", "@radix-ui/react-use-layout-effect": "1.1.0" }, "peerDependencies": { @@ -1693,13 +1718,65 @@ } } }, + "node_modules/@radix-ui/react-portal/node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz", + "integrity": "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-portal/node_modules/@radix-ui/react-primitive": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.1.tgz", + "integrity": "sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg==", + "dependencies": { + "@radix-ui/react-slot": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-portal/node_modules/@radix-ui/react-slot": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.1.tgz", + "integrity": "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-presence": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.0.tgz", - "integrity": "sha512-Gq6wuRN/asf9H/E/VzdKoUtT8GC9PQc9z40/vEr0VCJ4u5XvvhWIrSsCB6vD2/cH7ugTdSfYq9fLJCcM00acrQ==", - "license": "MIT", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.2.tgz", + "integrity": "sha512-18TFr80t5EVgL9x1SwF/YGtfG+l0BS0PRAlCWBDoBEiDQjeKgnNZRVJp/oVBl24sr3Gbfwc/Qpj4OcWTQMsAEg==", "dependencies": { - "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-use-layout-effect": "1.1.0" }, "peerDependencies": { @@ -1717,6 +1794,20 @@ } } }, + "node_modules/@radix-ui/react-presence/node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz", + "integrity": "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-primitive": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.0.tgz", @@ -1794,29 +1885,29 @@ } }, "node_modules/@radix-ui/react-select": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.1.2.tgz", - "integrity": "sha512-rZJtWmorC7dFRi0owDmoijm6nSJH1tVw64QGiNIZ9PNLyBDtG+iAq+XGsya052At4BfarzY/Dhv9wrrUr6IMZA==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.1.3.tgz", + "integrity": "sha512-tlLwaewTfrKetiex8iW9wwME/qrYlzlH0qcgYmos7xS54MO00SiPHasLoAykg/yVrjf41GQptPPi4oXzrP+sgg==", "dependencies": { "@radix-ui/number": "1.1.0", - "@radix-ui/primitive": "1.1.0", - "@radix-ui/react-collection": "1.1.0", - "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-collection": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-direction": "1.1.0", - "@radix-ui/react-dismissable-layer": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.2", "@radix-ui/react-focus-guards": "1.1.1", - "@radix-ui/react-focus-scope": "1.1.0", + "@radix-ui/react-focus-scope": "1.1.1", "@radix-ui/react-id": "1.1.0", - "@radix-ui/react-popper": "1.2.0", - "@radix-ui/react-portal": "1.1.2", - "@radix-ui/react-primitive": "2.0.0", - "@radix-ui/react-slot": "1.1.0", + "@radix-ui/react-popper": "1.2.1", + "@radix-ui/react-portal": "1.1.3", + "@radix-ui/react-primitive": "2.0.1", + "@radix-ui/react-slot": "1.1.1", "@radix-ui/react-use-callback-ref": "1.1.0", "@radix-ui/react-use-controllable-state": "1.1.0", "@radix-ui/react-use-layout-effect": "1.1.0", "@radix-ui/react-use-previous": "1.1.0", - "@radix-ui/react-visually-hidden": "1.1.0", + "@radix-ui/react-visually-hidden": "1.1.1", "aria-hidden": "^1.1.1", "react-remove-scroll": "2.6.0" }, @@ -1835,30 +1926,17 @@ } } }, - "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-context": { + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/primitive": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.1.tgz", - "integrity": "sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.1.tgz", + "integrity": "sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA==" }, - "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-dismissable-layer": { + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-arrow": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.1.tgz", - "integrity": "sha512-QSxg29lfr/xcev6kSz7MAlmDnzbP1eI/Dwn3Tp1ip0KT5CUELsxkekFEMVBEoykI3oV39hKT4TKZzBNMbcTZYQ==", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.1.tgz", + "integrity": "sha512-NaVpZfmv8SKeZbn4ijN2V3jlHA9ngBG16VnIIm22nUR0Yk8KUALyBxT3KYEUnNuch9sTE8UTsS3whzBgKOL30w==", "dependencies": { - "@radix-ui/primitive": "1.1.0", - "@radix-ui/react-compose-refs": "1.1.0", - "@radix-ui/react-primitive": "2.0.0", - "@radix-ui/react-use-callback-ref": "1.1.0", - "@radix-ui/react-use-escape-keydown": "1.1.0" + "@radix-ui/react-primitive": "2.0.1" }, "peerDependencies": { "@types/react": "*", @@ -1875,13 +1953,15 @@ } } }, - "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-portal": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.2.tgz", - "integrity": "sha512-WeDYLGPxJb/5EGBoedyJbT0MpoULmwnIPMJMSldkuiMsBAv7N1cRdsTWZWht9vpPOiN3qyiGAtbK2is47/uMFg==", + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-collection": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.1.tgz", + "integrity": "sha512-LwT3pSho9Dljg+wY2KN2mrrh6y3qELfftINERIzBUO9e0N+t0oMTyn3k9iv+ZqgrwGkRnLpNJrsMv9BZlt2yuA==", "dependencies": { - "@radix-ui/react-primitive": "2.0.0", - "@radix-ui/react-use-layout-effect": "1.1.0" + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-primitive": "2.0.1", + "@radix-ui/react-slot": "1.1.1" }, "peerDependencies": { "@types/react": "*", @@ -1898,14 +1978,10 @@ } } }, - "node_modules/@radix-ui/react-slot": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.0.tgz", - "integrity": "sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.0" - }, + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz", + "integrity": "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==", "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" @@ -1916,40 +1992,7 @@ } } }, - "node_modules/@radix-ui/react-toast": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.2.2.tgz", - "integrity": "sha512-Z6pqSzmAP/bFJoqMAston4eSNa+ud44NSZTiZUmUen+IOZ5nBY8kzuU5WDBVyFXPtcW6yUalOHsxM/BP6Sv8ww==", - "dependencies": { - "@radix-ui/primitive": "1.1.0", - "@radix-ui/react-collection": "1.1.0", - "@radix-ui/react-compose-refs": "1.1.0", - "@radix-ui/react-context": "1.1.1", - "@radix-ui/react-dismissable-layer": "1.1.1", - "@radix-ui/react-portal": "1.1.2", - "@radix-ui/react-presence": "1.1.1", - "@radix-ui/react-primitive": "2.0.0", - "@radix-ui/react-use-callback-ref": "1.1.0", - "@radix-ui/react-use-controllable-state": "1.1.0", - "@radix-ui/react-use-layout-effect": "1.1.0", - "@radix-ui/react-visually-hidden": "1.1.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-toast/node_modules/@radix-ui/react-context": { + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-context": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.1.tgz", "integrity": "sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==", @@ -1963,16 +2006,14 @@ } } }, - "node_modules/@radix-ui/react-toast/node_modules/@radix-ui/react-dismissable-layer": { + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-focus-scope": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.1.tgz", - "integrity": "sha512-QSxg29lfr/xcev6kSz7MAlmDnzbP1eI/Dwn3Tp1ip0KT5CUELsxkekFEMVBEoykI3oV39hKT4TKZzBNMbcTZYQ==", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.1.tgz", + "integrity": "sha512-01omzJAYRxXdG2/he/+xy+c8a8gCydoQ1yOxnWNcRhrrBW5W+RQJ22EK1SaO8tb3WoUsuEw7mJjBozPzihDFjA==", "dependencies": { - "@radix-ui/primitive": "1.1.0", - "@radix-ui/react-compose-refs": "1.1.0", - "@radix-ui/react-primitive": "2.0.0", - "@radix-ui/react-use-callback-ref": "1.1.0", - "@radix-ui/react-use-escape-keydown": "1.1.0" + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-primitive": "2.0.1", + "@radix-ui/react-use-callback-ref": "1.1.0" }, "peerDependencies": { "@types/react": "*", @@ -1989,13 +2030,21 @@ } } }, - "node_modules/@radix-ui/react-toast/node_modules/@radix-ui/react-portal": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.2.tgz", - "integrity": "sha512-WeDYLGPxJb/5EGBoedyJbT0MpoULmwnIPMJMSldkuiMsBAv7N1cRdsTWZWht9vpPOiN3qyiGAtbK2is47/uMFg==", + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-popper": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.1.tgz", + "integrity": "sha512-3kn5Me69L+jv82EKRuQCXdYyf1DqHwD2U/sxoNgBGCB7K9TRc3bQamQ+5EPM9EvyPdli0W41sROd+ZU1dTCztw==", "dependencies": { - "@radix-ui/react-primitive": "2.0.0", - "@radix-ui/react-use-layout-effect": "1.1.0" + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-primitive": "2.0.1", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0", + "@radix-ui/react-use-rect": "1.1.0", + "@radix-ui/react-use-size": "1.1.0", + "@radix-ui/rect": "1.1.0" }, "peerDependencies": { "@types/react": "*", @@ -2012,13 +2061,12 @@ } } }, - "node_modules/@radix-ui/react-toast/node_modules/@radix-ui/react-presence": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.1.tgz", - "integrity": "sha512-IeFXVi4YS1K0wVZzXNrbaaUvIJ3qdY+/Ih4eHFhWA9SwGR9UDX7Ck8abvL57C4cv3wwMvUE0OG69Qc3NCcTe/A==", + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-primitive": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.1.tgz", + "integrity": "sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg==", "dependencies": { - "@radix-ui/react-compose-refs": "1.1.0", - "@radix-ui/react-use-layout-effect": "1.1.0" + "@radix-ui/react-slot": "1.1.1" }, "peerDependencies": { "@types/react": "*", @@ -2035,44 +2083,333 @@ } } }, - "node_modules/@radix-ui/react-tooltip": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.1.2.tgz", - "integrity": "sha512-9XRsLwe6Yb9B/tlnYCPVUd/TFS4J7HuOZW345DCeC6vKIxQGMZdx21RK4VoZauPD5frgkXTYVS5y90L+3YBn4w==", - "license": "MIT", + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-slot": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.1.tgz", + "integrity": "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==", "dependencies": { - "@radix-ui/primitive": "1.1.0", - "@radix-ui/react-compose-refs": "1.1.0", - "@radix-ui/react-context": "1.1.0", - "@radix-ui/react-dismissable-layer": "1.1.0", - "@radix-ui/react-id": "1.1.0", - "@radix-ui/react-popper": "1.2.0", - "@radix-ui/react-portal": "1.1.1", - "@radix-ui/react-presence": "1.1.0", - "@radix-ui/react-primitive": "2.0.0", - "@radix-ui/react-slot": "1.1.0", - "@radix-ui/react-use-controllable-state": "1.1.0", - "@radix-ui/react-visually-hidden": "1.1.0" + "@radix-ui/react-compose-refs": "1.1.1" }, "peerDependencies": { "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "peerDependenciesMeta": { "@types/react": { "optional": true - }, - "@types/react-dom": { - "optional": true } } }, - "node_modules/@radix-ui/react-use-callback-ref": { + "node_modules/@radix-ui/react-slot": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz", - "integrity": "sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.0.tgz", + "integrity": "sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toast": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.2.3.tgz", + "integrity": "sha512-oB8irs7CGAml6zWbum7MNySTH/sR7PM1ZQyLV8reO946u73sU83yZUKijrMLNbm4hTOrJY4tE8Oa/XUKrOr2Wg==", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-collection": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.2", + "@radix-ui/react-portal": "1.1.3", + "@radix-ui/react-presence": "1.1.2", + "@radix-ui/react-primitive": "2.0.1", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0", + "@radix-ui/react-visually-hidden": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toast/node_modules/@radix-ui/primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.1.tgz", + "integrity": "sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA==" + }, + "node_modules/@radix-ui/react-toast/node_modules/@radix-ui/react-collection": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.1.tgz", + "integrity": "sha512-LwT3pSho9Dljg+wY2KN2mrrh6y3qELfftINERIzBUO9e0N+t0oMTyn3k9iv+ZqgrwGkRnLpNJrsMv9BZlt2yuA==", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-primitive": "2.0.1", + "@radix-ui/react-slot": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toast/node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz", + "integrity": "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toast/node_modules/@radix-ui/react-context": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.1.tgz", + "integrity": "sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toast/node_modules/@radix-ui/react-primitive": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.1.tgz", + "integrity": "sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg==", + "dependencies": { + "@radix-ui/react-slot": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toast/node_modules/@radix-ui/react-slot": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.1.tgz", + "integrity": "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.1.5.tgz", + "integrity": "sha512-IucoQPcK5nwUuztaxBQvudvYwH58wtRcJlv1qvaMSyIbL9dEBfFN0vRf/D8xDbu6HmAJLlNGty4z8Na+vIqe9Q==", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.2", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-popper": "1.2.1", + "@radix-ui/react-portal": "1.1.3", + "@radix-ui/react-presence": "1.1.2", + "@radix-ui/react-primitive": "2.0.1", + "@radix-ui/react-slot": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.1.0", + "@radix-ui/react-visually-hidden": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.1.tgz", + "integrity": "sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA==" + }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-arrow": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.1.tgz", + "integrity": "sha512-NaVpZfmv8SKeZbn4ijN2V3jlHA9ngBG16VnIIm22nUR0Yk8KUALyBxT3KYEUnNuch9sTE8UTsS3whzBgKOL30w==", + "dependencies": { + "@radix-ui/react-primitive": "2.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz", + "integrity": "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-context": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.1.tgz", + "integrity": "sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-popper": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.1.tgz", + "integrity": "sha512-3kn5Me69L+jv82EKRuQCXdYyf1DqHwD2U/sxoNgBGCB7K9TRc3bQamQ+5EPM9EvyPdli0W41sROd+ZU1dTCztw==", + "dependencies": { + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-primitive": "2.0.1", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0", + "@radix-ui/react-use-rect": "1.1.0", + "@radix-ui/react-use-size": "1.1.0", + "@radix-ui/rect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-primitive": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.1.tgz", + "integrity": "sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg==", + "dependencies": { + "@radix-ui/react-slot": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-slot": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.1.tgz", + "integrity": "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz", + "integrity": "sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==", "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -2187,12 +2524,47 @@ } }, "node_modules/@radix-ui/react-visually-hidden": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.1.0.tgz", - "integrity": "sha512-N8MDZqtgCgG5S3aV60INAB475osJousYpZ4cTJ2cFbMpdHS5Y6loLTH8LPtkj2QN0x93J30HT/M3qJXM0+lyeQ==", - "license": "MIT", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.1.1.tgz", + "integrity": "sha512-vVfA2IZ9q/J+gEamvj761Oq1FpWgCDaNOOIfbPVp2MVPLEomUr5+Vf7kJGwQ24YxZSlQVar7Bes8kyTo5Dshpg==", "dependencies": { - "@radix-ui/react-primitive": "2.0.0" + "@radix-ui/react-primitive": "2.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-visually-hidden/node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz", + "integrity": "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-visually-hidden/node_modules/@radix-ui/react-primitive": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.1.tgz", + "integrity": "sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg==", + "dependencies": { + "@radix-ui/react-slot": "1.1.1" }, "peerDependencies": { "@types/react": "*", @@ -2209,6 +2581,23 @@ } } }, + "node_modules/@radix-ui/react-visually-hidden/node_modules/@radix-ui/react-slot": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.1.tgz", + "integrity": "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/rect": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.0.tgz", @@ -2231,34 +2620,20 @@ "node_modules/@swc/counter": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", - "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", - "license": "Apache-2.0" + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==" }, "node_modules/@swc/helpers": { - "version": "0.5.13", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.13.tgz", - "integrity": "sha512-UoKGxQ3r5kYI9dALKJapMmuK+1zWM/H17Z1+iwnNmzcJRnfFuevZs375TA5rW31pu4BS4NoSy1fRsexDXfWn5w==", - "license": "Apache-2.0", + "version": "0.5.15", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", + "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", "dependencies": { - "tslib": "^2.4.0" + "tslib": "^2.8.0" } }, - "node_modules/@types/cytoscape": { - "version": "3.21.5", - "resolved": "https://registry.npmjs.org/@types/cytoscape/-/cytoscape-3.21.5.tgz", - "integrity": "sha512-fzYT3vqY5J4gxVXDOsCgDpm0ZdU8bQq+wCv0ucS0MSTtvQdjs3lcb2VetJiUSAd4WBgouqizI+JT1f8Yc6eY7Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/cytoscape-fcose": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/@types/cytoscape-fcose/-/cytoscape-fcose-2.2.4.tgz", - "integrity": "sha512-QwWtnT8HI9h+DHhG5krGc1ZY0Ex+cn85MvX96ZNAjSxuXiZDnjIZW/ypVkvvubTjIY4rSdkJY1D/Nsn8NDpmAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/cytoscape": "*" - } + "node_modules/@tweenjs/tween.js": { + "version": "25.0.0", + "resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-25.0.0.tgz", + "integrity": "sha512-XKLA6syeBUaPzx4j3qwMqzzq+V4uo72BnlbOjmuljLrRqdsd3qnzvZZoxvMHZ23ndsRS4aufU6JOZYpCbU6T1A==" }, "node_modules/@types/estree": { "version": "1.0.6", @@ -2290,11 +2665,10 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.10.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.1.tgz", - "integrity": "sha512-qKgsUwfHZV2WCWLAnVP1JqnpE6Im6h3Y0+fYgMTasNQ7V++CBX5OT1as0g0f+OyubbFqhf6XVNIsmN4IIhEgGQ==", + "version": "22.10.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.2.tgz", + "integrity": "sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==", "dev": true, - "license": "MIT", "dependencies": { "undici-types": "~6.20.0" } @@ -2307,9 +2681,9 @@ "license": "MIT" }, "node_modules/@types/react": { - "version": "18.3.12", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.12.tgz", - "integrity": "sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw==", + "version": "18.3.3", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.3.tgz", + "integrity": "sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==", "devOptional": true, "license": "MIT", "dependencies": { @@ -2317,17 +2691,6 @@ "csstype": "^3.0.2" } }, - "node_modules/@types/react-cytoscapejs": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/@types/react-cytoscapejs/-/react-cytoscapejs-1.2.5.tgz", - "integrity": "sha512-G9VcGQOlER3njklOu5D0FDGHYfkQJ3yEL95kGbgI/MR08N5dQ7IbLSZI8WqaB4fG0zOURIg0BUtOCrbE5HRZEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/cytoscape": "*", - "@types/react": "*" - } - }, "node_modules/@types/react-dom": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.1.tgz", @@ -2337,6 +2700,11 @@ "@types/react": "*" } }, + "node_modules/@types/react-gtm-module": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/react-gtm-module/-/react-gtm-module-2.0.4.tgz", + "integrity": "sha512-5wPMWsUE5AI6O0B0K1/zbs0rFHBKu+7NWXQwDXhqvA12ooLD6W1AYiWZqR4UiOd7ixZDV1H5Ys301zEsqyIfNg==" + }, "node_modules/@types/react-syntax-highlighter": { "version": "15.5.13", "resolved": "https://registry.npmjs.org/@types/react-syntax-highlighter/-/react-syntax-highlighter-15.5.13.tgz", @@ -2545,16 +2913,14 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.13.0.tgz", "integrity": "sha512-7N/+lztJqH4Mrf0lb10R/CbI1EaAMMGyF5y0oJvFoAhafwgiRA7TXyd8TFn8FC8k5y2dTsYogg238qavRGNnlw==", "dev": true, - "dependencies": { - "@typescript-eslint/types": "8.13.0", - "eslint-visitor-keys": "^3.4.3" - }, + "license": "ISC" + }, + "node_modules/accessor-fn": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/accessor-fn/-/accessor-fn-1.5.1.tgz", + "integrity": "sha512-zZpFYBqIL1Aqg+f2qmYHJ8+yIZF7/tP6PUGx2/QM0uGPSO5UegpinmkNwDohxWtOj586BpMPVRUjce2HI6xB3A==", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "node": ">=12" } }, "node_modules/acorn": { @@ -2679,7 +3045,6 @@ "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", "dev": true, - "license": "MIT", "dependencies": { "call-bind": "^1.0.5", "is-array-buffer": "^3.0.4" @@ -2717,7 +3082,6 @@ "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", "dev": true, - "license": "MIT", "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", @@ -2797,7 +3161,6 @@ "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", "dev": true, - "license": "MIT", "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", @@ -2810,20 +3173,18 @@ } }, "node_modules/arraybuffer.prototype.slice": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", - "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", "dev": true, - "license": "MIT", "dependencies": { "array-buffer-byte-length": "^1.0.1", - "call-bind": "^1.0.5", + "call-bind": "^1.0.8", "define-properties": "^1.2.1", - "es-abstract": "^1.22.3", - "es-errors": "^1.2.1", - "get-intrinsic": "^1.2.3", - "is-array-buffer": "^3.0.4", - "is-shared-array-buffer": "^1.0.2" + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" }, "engines": { "node": ">= 0.4" @@ -2914,6 +3275,15 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "license": "MIT" }, + "node_modules/bezier-js": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/bezier-js/-/bezier-js-6.1.4.tgz", + "integrity": "sha512-PA0FW9ZpcHbojUCMu28z9Vg/fNkwTj5YhusSAjHHDfHDGLxJ6YUKrAN2vk1fP2MMOxVw4Oko16FMlRGVBGqLKg==", + "funding": { + "type": "individual", + "url": "https://github.com/Pomax/bezierjs/blob/master/FUNDING.md" + } + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -2985,24 +3355,51 @@ "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", "dependencies": { - "streamsearch": "^1.1.0" + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "dev": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz", + "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" }, "engines": { - "node": ">=10.16.0" + "node": ">= 0.4" } }, - "node_modules/call-bind": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", - "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "node_modules/call-bound": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz", + "integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==", "dev": true, - "license": "MIT", "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.1" + "call-bind-apply-helpers": "^1.0.1", + "get-intrinsic": "^1.2.6" }, "engines": { "node": ">= 0.4" @@ -3049,6 +3446,17 @@ } ] }, + "node_modules/canvas-color-tracker": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/canvas-color-tracker/-/canvas-color-tracker-1.3.1.tgz", + "integrity": "sha512-eNycxGS7oQ3IS/9QQY41f/aQjiO9Y/MtedhCgSdsbLSxC9EyUD8L3ehl/Q3Kfmvt8um79S45PBV+5Rxm5ztdSw==", + "dependencies": { + "tinycolor2": "^1.6.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -3130,31 +3538,20 @@ } }, "node_modules/class-variance-authority": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.0.tgz", - "integrity": "sha512-jFI8IQw4hczaL4ALINxqLEXQbWcNjoSkloa4IaufXCJr6QawJyw7tuRysRsrE8w2p/4gGaxKIt/hX3qz/IbD1A==", - "license": "Apache-2.0", + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", + "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", "dependencies": { - "clsx": "2.0.0" + "clsx": "^2.1.1" }, "funding": { - "url": "https://joebell.co.uk" - } - }, - "node_modules/class-variance-authority/node_modules/clsx": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz", - "integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==", - "license": "MIT", - "engines": { - "node": ">=6" + "url": "https://polar.sh/cva" } }, "node_modules/client-only": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", - "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", - "license": "MIT" + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==" }, "node_modules/clsx": { "version": "2.1.1", @@ -3169,7 +3566,6 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", - "license": "MIT", "optional": true, "dependencies": { "color-convert": "^2.0.1", @@ -3201,7 +3597,6 @@ "version": "1.9.1", "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", - "license": "MIT", "optional": true, "dependencies": { "color-name": "^1.0.0", @@ -3233,15 +3628,6 @@ "dev": true, "license": "MIT" }, - "node_modules/cose-base": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-2.2.0.tgz", - "integrity": "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==", - "license": "MIT", - "dependencies": { - "layout-base": "^2.0.0" - } - }, "node_modules/cross-spawn": { "version": "7.0.5", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.5.tgz", @@ -3275,25 +3661,201 @@ "devOptional": true, "license": "MIT" }, - "node_modules/cytoscape": { - "version": "3.30.4", - "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.30.4.tgz", - "integrity": "sha512-OxtlZwQl1WbwMmLiyPSEBuzeTIQnwZhJYYWFzZ2PhEHVFwpeaqNIkUzSiso00D98qk60l8Gwon2RP304d3BJ1A==", - "license": "MIT", + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "dependencies": { + "internmap": "1 - 2" + }, "engines": { - "node": ">=0.10" + "node": ">=12" } }, - "node_modules/cytoscape-fcose": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cytoscape-fcose/-/cytoscape-fcose-2.2.0.tgz", - "integrity": "sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==", - "license": "MIT", + "node_modules/d3-binarytree": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/d3-binarytree/-/d3-binarytree-1.0.2.tgz", + "integrity": "sha512-cElUNH+sHu95L04m92pG73t2MEJXKu+GeKUN1TJkFsu93E5W8E9Sc3kHEGJKgenGvj19m6upSn2EunvMgMD2Yw==" + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-force-3d": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/d3-force-3d/-/d3-force-3d-3.0.5.tgz", + "integrity": "sha512-tdwhAhoTYZY/a6eo9nR7HP3xSW/C6XvJTbeRpR92nlPzH6OiE+4MliN9feuSFd0tPtEUo+191qOhCTWx3NYifg==", + "dependencies": { + "d3-binarytree": "1", + "d3-dispatch": "1 - 3", + "d3-octree": "1", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-octree": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/d3-octree/-/d3-octree-1.0.2.tgz", + "integrity": "sha512-Qxg4oirJrNXauiuC94uKMbgxwnhdda9xRLl9ihq45srlJ4Ga3CSgqGcAL8iW7N5CIv4Oz8x3E734ulxyvHPvwA==" + }, + "node_modules/d3-quadtree": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", + "dependencies": { + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", "dependencies": { - "cose-base": "^2.2.0" + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" }, "peerDependencies": { - "cytoscape": "^3.2.0" + "d3-selection": "2 - 3" + } + }, + "node_modules/d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "engines": { + "node": ">=12" } }, "node_modules/damerau-levenshtein": { @@ -3421,7 +3983,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", - "license": "Apache-2.0", "optional": true, "engines": { "node": ">=8" @@ -3445,6 +4006,32 @@ "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", "license": "MIT" }, + "node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.0.tgz", + "integrity": "sha512-9+Sj30DIu+4KvHqMfLUGLFYL2PkURSYMVXJyXe92nFRvlYq5hBjLEhblKB+vkd/WVlUYMWigiY07T91Fkk0+4A==", + "dev": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -3477,58 +4064,59 @@ } }, "node_modules/es-abstract": { - "version": "1.23.3", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz", - "integrity": "sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==", + "version": "1.23.6", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.6.tgz", + "integrity": "sha512-Ifco6n3yj2tMZDWNLyloZrytt9lqqlwvS83P3HtaETR0NUOYnIULGGHpktqYGObGy+8wc1okO25p8TjemhImvA==", "dev": true, - "license": "MIT", "dependencies": { "array-buffer-byte-length": "^1.0.1", - "arraybuffer.prototype.slice": "^1.0.3", + "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", "data-view-buffer": "^1.0.1", "data-view-byte-length": "^1.0.1", "data-view-byte-offset": "^1.0.0", - "es-define-property": "^1.0.0", + "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "es-set-tostringtag": "^2.0.3", - "es-to-primitive": "^1.2.1", - "function.prototype.name": "^1.1.6", - "get-intrinsic": "^1.2.4", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.7", + "get-intrinsic": "^1.2.6", "get-symbol-description": "^1.0.2", - "globalthis": "^1.0.3", - "gopd": "^1.0.1", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", - "has-proto": "^1.0.3", - "has-symbols": "^1.0.3", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", "hasown": "^2.0.2", - "internal-slot": "^1.0.7", + "internal-slot": "^1.1.0", "is-array-buffer": "^3.0.4", "is-callable": "^1.2.7", - "is-data-view": "^1.0.1", + "is-data-view": "^1.0.2", "is-negative-zero": "^2.0.3", - "is-regex": "^1.1.4", + "is-regex": "^1.2.1", "is-shared-array-buffer": "^1.0.3", - "is-string": "^1.0.7", + "is-string": "^1.1.1", "is-typed-array": "^1.1.13", - "is-weakref": "^1.0.2", - "object-inspect": "^1.13.1", + "is-weakref": "^1.1.0", + "math-intrinsics": "^1.0.0", + "object-inspect": "^1.13.3", "object-keys": "^1.1.1", "object.assign": "^4.1.5", - "regexp.prototype.flags": "^1.5.2", - "safe-array-concat": "^1.1.2", - "safe-regex-test": "^1.0.3", - "string.prototype.trim": "^1.2.9", - "string.prototype.trimend": "^1.0.8", + "regexp.prototype.flags": "^1.5.3", + "safe-array-concat": "^1.1.3", + "safe-regex-test": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.2", "typed-array-byte-length": "^1.0.1", - "typed-array-byte-offset": "^1.0.2", - "typed-array-length": "^1.0.6", + "typed-array-byte-offset": "^1.0.3", + "typed-array-length": "^1.0.7", "unbox-primitive": "^1.0.2", - "which-typed-array": "^1.1.15" + "which-typed-array": "^1.1.16" }, "engines": { "node": ">= 0.4" @@ -3538,14 +4126,10 @@ } }, "node_modules/es-define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "dev": true, - "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.2.4" - }, "engines": { "node": ">= 0.4" } @@ -3561,11 +4145,10 @@ } }, "node_modules/es-iterator-helpers": { - "version": "1.0.19", - "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.0.19.tgz", - "integrity": "sha512-zoMwbCcH5hwUkKJkT8kDIBZSz9I6mVG//+lDCinLCGov4+r7NIy0ld8o03M0cJxl2spVf6ESYVS6/gpIfq1FFw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.0.tgz", + "integrity": "sha512-tpxqxncxnpw3c93u8n3VOzACmRFoVmWJqbWXvX/JfKbkhBw1oslgPrUfeSt2psuqyEJFD6N/9lg5i7bsKpoq+Q==", "dev": true, - "license": "MIT", "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", @@ -3574,12 +4157,13 @@ "es-set-tostringtag": "^2.0.3", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", - "globalthis": "^1.0.3", + "globalthis": "^1.0.4", + "gopd": "^1.0.1", "has-property-descriptors": "^1.0.2", "has-proto": "^1.0.3", "has-symbols": "^1.0.3", "internal-slot": "^1.0.7", - "iterator.prototype": "^1.1.2", + "iterator.prototype": "^1.1.3", "safe-array-concat": "^1.1.2" }, "engines": { @@ -3625,15 +4209,14 @@ } }, "node_modules/es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", "dev": true, - "license": "MIT", "dependencies": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" }, "engines": { "node": ">= 0.4" @@ -3724,12 +4307,12 @@ } }, "node_modules/eslint-config-next": { - "version": "15.0.3", - "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-15.0.3.tgz", - "integrity": "sha512-IGP2DdQQrgjcr4mwFPve4DrCqo7CVVez1WoYY47XwKSrYO4hC0Dlb+iJA60i0YfICOzgNADIb8r28BpQ5Zs0wg==", + "version": "15.1.2", + "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-15.1.2.tgz", + "integrity": "sha512-PrMm1/4zWSJ689wd/ypWIR5ZF1uvmp3EkgpgBV1Yu6PhEobBjXMGgT8bVNelwl17LXojO8D5ePFRiI4qXjsPRA==", "dev": true, "dependencies": { - "@next/eslint-plugin-next": "15.0.3", + "@next/eslint-plugin-next": "15.1.2", "@rushstack/eslint-patch": "^1.10.3", "@typescript-eslint/eslint-plugin": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", "@typescript-eslint/parser": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", @@ -3737,7 +4320,7 @@ "eslint-import-resolver-typescript": "^3.5.2", "eslint-plugin-import": "^2.31.0", "eslint-plugin-jsx-a11y": "^6.10.0", - "eslint-plugin-react": "^7.35.0", + "eslint-plugin-react": "^7.37.0", "eslint-plugin-react-hooks": "^5.0.0" }, "peerDependencies": { @@ -3868,19 +4451,6 @@ "ms": "^2.1.1" } }, - "node_modules/eslint-plugin-import/node_modules/doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/eslint-plugin-import/node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -3921,18 +4491,17 @@ } }, "node_modules/eslint-plugin-react": { - "version": "7.35.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.35.0.tgz", - "integrity": "sha512-v501SSMOWv8gerHkk+IIQBkcGRGrO2nfybfj5pLxuJNFTPxxA3PSryhXTK+9pNbtkggheDdsC0E9Q8CuPk6JKA==", + "version": "7.37.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.2.tgz", + "integrity": "sha512-EsTAnj9fLVr/GZleBLFbj/sSuXeWmp1eXIN60ceYnZveqEaUCyW4X+Vh4WTdUhCkW4xutXYqTXCUSyqD4rB75w==", "dev": true, - "license": "MIT", "dependencies": { "array-includes": "^3.1.8", "array.prototype.findlast": "^1.2.5", "array.prototype.flatmap": "^1.3.2", "array.prototype.tosorted": "^1.1.4", "doctrine": "^2.1.0", - "es-iterator-helpers": "^1.0.19", + "es-iterator-helpers": "^1.1.0", "estraverse": "^5.3.0", "hasown": "^2.0.2", "jsx-ast-utils": "^2.4.1 || ^3.0.0", @@ -3965,25 +4534,11 @@ "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" } }, - "node_modules/eslint-plugin-react/node_modules/doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/eslint-plugin-react/node_modules/resolve": { "version": "2.0.0-next.5", "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", "dev": true, - "license": "MIT", "dependencies": { "is-core-module": "^2.13.0", "path-parse": "^1.0.7", @@ -4001,7 +4556,6 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, - "license": "ISC", "bin": { "semver": "bin/semver.js" } @@ -4269,6 +4823,30 @@ "is-callable": "^1.1.3" } }, + "node_modules/force-graph": { + "version": "1.47.1", + "resolved": "https://registry.npmjs.org/force-graph/-/force-graph-1.47.1.tgz", + "integrity": "sha512-NF0prpR8tNGq7oCE/aFImT/6/3wSk585bcp39UAj6SNSPjvKbX6ktCH6cZnjfsnPNx/DYj1rn+cvvjH814HCFA==", + "dependencies": { + "@tweenjs/tween.js": "18 - 25", + "accessor-fn": "1", + "bezier-js": "3 - 6", + "canvas-color-tracker": "^1.3", + "d3-array": "1 - 3", + "d3-drag": "2 - 3", + "d3-force-3d": "2 - 3", + "d3-scale": "1 - 4", + "d3-scale-chromatic": "1 - 3", + "d3-selection": "2 - 3", + "d3-zoom": "2 - 3", + "index-array-by": "1", + "kapsule": "^1.16", + "lodash-es": "4" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/foreground-child": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", @@ -4306,11 +4884,10 @@ } }, "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", "hasInstallScript": true, - "license": "MIT", "optional": true, "os": [ "darwin" @@ -4329,16 +4906,16 @@ } }, "node_modules/function.prototype.name": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", - "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.7.tgz", + "integrity": "sha512-2g4x+HqTJKM9zcJqBSpjoRmdcPFtJM60J3xJisTQSXBWka5XqyBN/2tNUgma1mztTXyDuUsEtYe5qcs7xYzYQA==", "dev": true, - "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "functions-have-names": "^1.2.3" + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" }, "engines": { "node": ">= 0.4" @@ -4358,17 +4935,21 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.6.tgz", + "integrity": "sha512-qxsEs+9A+u85HhllWJJFicJfPDhRmjzoYdl64aMWW9yRIJmSyxdn8IEkuIM530/7T+lv0TIHd8L6Q/ra0tEoeA==", "dev": true, - "license": "MIT", "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "dunder-proto": "^1.0.0", + "es-define-property": "^1.0.1", "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.0.0" }, "engines": { "node": ">= 0.4" @@ -4506,13 +5087,12 @@ } }, "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "dev": true, - "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.1.3" + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -4566,11 +5146,13 @@ } }, "node_modules/has-proto": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", - "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", "dev": true, - "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, "engines": { "node": ">= 0.4" }, @@ -4579,11 +5161,10 @@ } }, "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -4691,24 +5272,39 @@ "dev": true, "license": "MIT", "engines": { - "node": ">=0.8.19" + "node": ">=0.8.19" + } + }, + "node_modules/index-array-by": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/index-array-by/-/index-array-by-1.4.2.tgz", + "integrity": "sha512-SP23P27OUKzXWEC/TOyWlwLviofQkCSCKONnc62eItjp69yCZZPqDQtr3Pw5gJDnPeUMqExmKydNZaJO0FU9pw==", + "engines": { + "node": ">=12" } }, "node_modules/internal-slot": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", - "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", "dev": true, - "license": "MIT", "dependencies": { "es-errors": "^1.3.0", - "hasown": "^2.0.0", - "side-channel": "^1.0.4" + "hasown": "^2.0.2", + "side-channel": "^1.1.0" }, "engines": { "node": ">= 0.4" } }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "engines": { + "node": ">=12" + } + }, "node_modules/invariant": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", @@ -4745,7 +5341,6 @@ "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", "dev": true, - "license": "MIT", "dependencies": { "call-bind": "^1.0.2", "get-intrinsic": "^1.2.1" @@ -4761,7 +5356,6 @@ "version": "0.3.2", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", - "license": "MIT", "optional": true }, "node_modules/is-async-function": { @@ -4769,7 +5363,6 @@ "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.0.0.tgz", "integrity": "sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==", "dev": true, - "license": "MIT", "dependencies": { "has-tostringtag": "^1.0.0" }, @@ -4781,13 +5374,15 @@ } }, "node_modules/is-bigint": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", - "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", "dev": true, - "license": "MIT", "dependencies": { - "has-bigints": "^1.0.1" + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -4806,14 +5401,13 @@ } }, "node_modules/is-boolean-object": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", - "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.1.tgz", + "integrity": "sha512-l9qO6eFlUETHtuihLcYOaLKByJ1f+N4kthcU9YjHy3N+B3hWv0y/2Nd0mu/7lTFnRQHTrSdXF50HQ3bl5fEnng==", "dev": true, - "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -4850,12 +5444,13 @@ } }, "node_modules/is-data-view": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.1.tgz", - "integrity": "sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", "dev": true, - "license": "MIT", "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", "is-typed-array": "^1.1.13" }, "engines": { @@ -4866,13 +5461,13 @@ } }, "node_modules/is-date-object": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", - "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", "dev": true, - "license": "MIT", "dependencies": { - "has-tostringtag": "^1.0.0" + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -4900,13 +5495,15 @@ } }, "node_modules/is-finalizationregistry": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.0.2.tgz", - "integrity": "sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.0.tgz", + "integrity": "sha512-qfMdqbAQEwBw78ZyReKnlA8ezmPdb9BemzIIip/JkjaZUhitfXDkkr+3QTboW0JrSXT1QWyYShpvnNHGZ4c4yA==", "dev": true, - "license": "MIT", "dependencies": { - "call-bind": "^1.0.2" + "call-bind": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -4926,7 +5523,6 @@ "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", "dev": true, - "license": "MIT", "dependencies": { "has-tostringtag": "^1.0.0" }, @@ -4963,7 +5559,6 @@ "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -4994,13 +5589,13 @@ } }, "node_modules/is-number-object": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", - "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", "dev": true, - "license": "MIT", "dependencies": { - "has-tostringtag": "^1.0.0" + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -5010,14 +5605,15 @@ } }, "node_modules/is-regex": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", - "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", "dev": true, - "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" }, "engines": { "node": ">= 0.4" @@ -5031,7 +5627,6 @@ "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -5044,7 +5639,6 @@ "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", "dev": true, - "license": "MIT", "dependencies": { "call-bind": "^1.0.7" }, @@ -5056,13 +5650,13 @@ } }, "node_modules/is-string": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", - "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", "dev": true, - "license": "MIT", "dependencies": { - "has-tostringtag": "^1.0.0" + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -5072,13 +5666,14 @@ } }, "node_modules/is-symbol": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", - "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", "dev": true, - "license": "MIT", "dependencies": { - "has-symbols": "^1.0.2" + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -5108,7 +5703,6 @@ "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -5117,13 +5711,15 @@ } }, "node_modules/is-weakref": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", - "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.0.tgz", + "integrity": "sha512-SXM8Nwyys6nT5WP6pltOwKytLV7FqQ4UiibxVmW+EIosHcmCqkkjViTb5SNssDlkCiEYRP1/pdWUKVvZBmsR2Q==", "dev": true, - "license": "MIT", "dependencies": { - "call-bind": "^1.0.2" + "call-bound": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -5134,7 +5730,6 @@ "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.3.tgz", "integrity": "sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==", "dev": true, - "license": "MIT", "dependencies": { "call-bind": "^1.0.7", "get-intrinsic": "^1.2.4" @@ -5160,17 +5755,20 @@ "license": "ISC" }, "node_modules/iterator.prototype": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.2.tgz", - "integrity": "sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.4.tgz", + "integrity": "sha512-x4WH0BWmrMmg4oHHl+duwubhrvczGlyuGAZu3nvrf0UXOfPu8IhZObFEr7DE/iv01YgVZrsOiRcqw2srkKEDIA==", "dev": true, - "license": "MIT", "dependencies": { - "define-properties": "^1.2.1", - "get-intrinsic": "^1.2.1", - "has-symbols": "^1.0.3", - "reflect.getprototypeof": "^1.0.4", - "set-function-name": "^2.0.1" + "define-data-property": "^1.1.4", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", + "reflect.getprototypeof": "^1.0.8", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" } }, "node_modules/jackspeak": { @@ -5191,6 +5789,14 @@ "@pkgjs/parseargs": "^0.11.0" } }, + "node_modules/jerrypick": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/jerrypick/-/jerrypick-1.1.1.tgz", + "integrity": "sha512-XTtedPYEyVp4t6hJrXuRKr/jHj8SC4z+4K0b396PMkov6muL+i8IIamJIvZWe3jUspgIJak0P+BaWKawMYNBLg==", + "engines": { + "node": ">=12" + } + }, "node_modules/jiti": { "version": "1.21.6", "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz", @@ -5269,6 +5875,17 @@ "node": ">=4.0" } }, + "node_modules/kapsule": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/kapsule/-/kapsule-1.16.0.tgz", + "integrity": "sha512-4f/z/Luu0cEXmagCwaFyzvfZai2HKgB4CQLwmsMUA+jlUbW94HfFSX+TWZxzWoMSO6b6aR+FD2Xd5z88VYZJTw==", + "dependencies": { + "lodash-es": "4" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -5297,12 +5914,6 @@ "node": ">=0.10" } }, - "node_modules/layout-base": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-2.0.1.tgz", - "integrity": "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==", - "license": "MIT" - }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -5348,6 +5959,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -5394,6 +6010,15 @@ "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc" } }, + "node_modules/math-intrinsics": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.0.0.tgz", + "integrity": "sha512-4MqMiKP90ybymYvsut0CH2g4XWbfLtmlCkXmtmdcDCxNB+mQcu1w/1+L/VD7vi/PSv7X2JYV7SCcR+jiPXnQtA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -5491,14 +6116,13 @@ "license": "MIT" }, "node_modules/next": { - "version": "15.0.3", - "resolved": "https://registry.npmjs.org/next/-/next-15.0.3.tgz", - "integrity": "sha512-ontCbCRKJUIoivAdGB34yCaOcPgYXr9AAkV/IwqFfWWTXEPUgLYkSkqBhIk9KK7gGmgjc64B+RdoeIDM13Irnw==", - "license": "MIT", + "version": "15.1.2", + "resolved": "https://registry.npmjs.org/next/-/next-15.1.2.tgz", + "integrity": "sha512-nLJDV7peNy+0oHlmY2JZjzMfJ8Aj0/dd3jCwSZS8ZiO5nkQfcZRqDrRN3U5rJtqVTQneIOGZzb6LCNrk7trMCQ==", "dependencies": { - "@next/env": "15.0.3", + "@next/env": "15.1.2", "@swc/counter": "0.1.3", - "@swc/helpers": "0.5.13", + "@swc/helpers": "0.5.15", "busboy": "1.6.0", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", @@ -5511,22 +6135,22 @@ "node": "^18.18.0 || ^19.8.0 || >= 20.0.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "15.0.3", - "@next/swc-darwin-x64": "15.0.3", - "@next/swc-linux-arm64-gnu": "15.0.3", - "@next/swc-linux-arm64-musl": "15.0.3", - "@next/swc-linux-x64-gnu": "15.0.3", - "@next/swc-linux-x64-musl": "15.0.3", - "@next/swc-win32-arm64-msvc": "15.0.3", - "@next/swc-win32-x64-msvc": "15.0.3", + "@next/swc-darwin-arm64": "15.1.2", + "@next/swc-darwin-x64": "15.1.2", + "@next/swc-linux-arm64-gnu": "15.1.2", + "@next/swc-linux-arm64-musl": "15.1.2", + "@next/swc-linux-x64-gnu": "15.1.2", + "@next/swc-linux-x64-musl": "15.1.2", + "@next/swc-win32-arm64-msvc": "15.1.2", + "@next/swc-win32-x64-msvc": "15.1.2", "sharp": "^0.33.5" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.41.2", "babel-plugin-react-compiler": "*", - "react": "^18.2.0 || 19.0.0-rc-66855b96-20241106", - "react-dom": "^18.2.0 || 19.0.0-rc-66855b96-20241106", + "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "peerDependenciesMeta": { @@ -5562,7 +6186,6 @@ "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", @@ -5613,11 +6236,10 @@ } }, "node_modules/object-inspect": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", - "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", + "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -5659,7 +6281,6 @@ "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.8.tgz", "integrity": "sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==", "dev": true, - "license": "MIT", "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", @@ -5869,12 +6490,11 @@ } }, "node_modules/playwright": { - "version": "1.48.2", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.48.2.tgz", - "integrity": "sha512-NjYvYgp4BPmiwfe31j4gHLa3J7bD2WiBz8Lk2RoSsmX38SVIARZ18VYjxLjAcDsAhA+F4iSEXTSGgjua0rrlgQ==", - "devOptional": true, + "version": "1.49.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.49.1.tgz", + "integrity": "sha512-VYL8zLoNTBxVOrJBbDuRgDWa3i+mfQgDTrL8Ah9QXZ7ax4Dsj0MSq5bYgytRnDVVe+njoKnfsYkH3HzqVj5UZA==", "dependencies": { - "playwright-core": "1.48.2" + "playwright-core": "1.49.1" }, "bin": { "playwright": "cli.js" @@ -5887,10 +6507,9 @@ } }, "node_modules/playwright-core": { - "version": "1.48.2", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.48.2.tgz", - "integrity": "sha512-sjjw+qrLFlriJo64du+EK0kJgZzoQPsabGF4lBvsid+3CNIZIYLgnMj9V6JY5VhM2Peh20DJWIVpVljLLnlawA==", - "devOptional": true, + "version": "1.49.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.49.1.tgz", + "integrity": "sha512-BzmpVcs4kE2CH15rWfzpjzVGhWERJfmnXmniSyKeRZUs9Ws65m+RGIi7mjJK/euCegfn3i7jvqWeWyHe9y3Vgg==", "bin": { "playwright-core": "cli.js" }, @@ -5898,20 +6517,6 @@ "node": ">=18" } }, - "node_modules/playwright/node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/possible-typed-array-names": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", @@ -6158,19 +6763,6 @@ "node": ">=0.10.0" } }, - "node_modules/react-cytoscapejs": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/react-cytoscapejs/-/react-cytoscapejs-2.0.0.tgz", - "integrity": "sha512-t3SSl1DQy7+JQjN+8QHi1anEJlM3i3aAeydHTsJwmjo/isyKK7Rs7oCvU6kZsB9NwZidzZQR21Vm2PcBLG/Tjg==", - "license": "MIT", - "dependencies": { - "prop-types": "^15.8.1" - }, - "peerDependencies": { - "cytoscape": "^3.2.19", - "react": ">=15.0.0" - } - }, "node_modules/react-dom": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", @@ -6184,12 +6776,47 @@ "react": "^18.3.1" } }, + "node_modules/react-force-graph-2d": { + "version": "1.26.1", + "resolved": "https://registry.npmjs.org/react-force-graph-2d/-/react-force-graph-2d-1.26.1.tgz", + "integrity": "sha512-7dRD0zNjMpeNghc6dwqzKrdWz45kM1/RNQ7OfR/Y4t9cK02NvHjtmA5JeKePAmzZajqmQQFCbTtwxEfhKgcsww==", + "dependencies": { + "force-graph": "^1.47", + "prop-types": "15", + "react-kapsule": "^2.5" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "react": "*" + } + }, + "node_modules/react-gtm-module": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/react-gtm-module/-/react-gtm-module-2.0.11.tgz", + "integrity": "sha512-8gyj4TTxeP7eEyc2QKawEuQoAZdjKvMY4pgWfycGmqGByhs17fR+zEBs0JUDq4US/l+vbTl+6zvUIx27iDo/Vw==" + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "license": "MIT" }, + "node_modules/react-kapsule": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/react-kapsule/-/react-kapsule-2.5.6.tgz", + "integrity": "sha512-aE4Nq7dDG8R/LdNmvOL6Azjr97I2E7ycFDJRkoHJSp9OQgTJDT3MHTJtJDrOTwzCl6sllYSqrtcndaCzizyAjQ==", + "dependencies": { + "jerrypick": "^1.1.1" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "react": ">=16.13.1" + } + }, "node_modules/react-remove-scroll": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.6.0.tgz", @@ -6326,19 +6953,19 @@ } }, "node_modules/reflect.getprototypeof": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.6.tgz", - "integrity": "sha512-fmfw4XgoDke3kdI6h4xcUz1dG8uaiv5q9gcEwLS4Pnth2kxT+GZ7YehS1JTMGBQmtV7Y4GFGbs2re2NqhdozUg==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.8.tgz", + "integrity": "sha512-B5dj6usc5dkk8uFliwjwDHM8To5/QwdKz9JcBZ8Ic4G1f0YmeeJTtE/ZTdgRFPAfxZFiUaPhZ1Jcs4qeagItGQ==", "dev": true, - "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", "define-properties": "^1.2.1", - "es-abstract": "^1.23.1", + "dunder-proto": "^1.0.0", + "es-abstract": "^1.23.5", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.4", - "globalthis": "^1.0.3", - "which-builtin-type": "^1.1.3" + "gopd": "^1.2.0", + "which-builtin-type": "^1.2.0" }, "engines": { "node": ">= 0.4" @@ -6375,16 +7002,15 @@ "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" }, "node_modules/regexp.prototype.flags": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", - "integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==", + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.3.tgz", + "integrity": "sha512-vqlC04+RQoFalODCbCumG2xIOvapzVMHwsyIGM/SIE8fRhFFsXeH8/QQ+s0T0kDAhKc4k30s73/0ydkHQz6HlQ==", "dev": true, - "license": "MIT", "dependencies": { - "call-bind": "^1.0.6", + "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-errors": "^1.3.0", - "set-function-name": "^2.0.1" + "set-function-name": "^2.0.2" }, "engines": { "node": ">= 0.4" @@ -6464,15 +7090,15 @@ } }, "node_modules/safe-array-concat": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz", - "integrity": "sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", + "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", "dev": true, - "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", - "get-intrinsic": "^1.2.4", - "has-symbols": "^1.0.3", + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", "isarray": "^2.0.5" }, "engines": { @@ -6483,15 +7109,14 @@ } }, "node_modules/safe-regex-test": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", - "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", "dev": true, - "license": "MIT", "dependencies": { - "call-bind": "^1.0.6", + "call-bound": "^1.0.2", "es-errors": "^1.3.0", - "is-regex": "^1.1.4" + "is-regex": "^1.2.1" }, "engines": { "node": ">= 0.4" @@ -6560,7 +7185,6 @@ "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz", "integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==", "hasInstallScript": true, - "license": "Apache-2.0", "optional": true, "dependencies": { "color": "^4.2.3", @@ -6617,16 +7241,69 @@ } }, "node_modules/side-channel": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", - "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", "dev": true, - "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4", - "object-inspect": "^1.13.1" + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" }, "engines": { "node": ">= 0.4" @@ -6651,7 +7328,6 @@ "version": "0.2.2", "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", - "license": "MIT", "optional": true, "dependencies": { "is-arrayish": "^0.3.1" @@ -6766,7 +7442,6 @@ "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.11.tgz", "integrity": "sha512-NUdh0aDavY2og7IbBPenWqR9exH+E26Sv8e0/eTe1tltDGZL+GtBkDAnnyBtmekfK6/Dq3MkcGtzXFEd1LQrtg==", "dev": true, - "license": "MIT", "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", @@ -6793,23 +7468,24 @@ "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", "dev": true, - "license": "MIT", "dependencies": { "define-properties": "^1.1.3", "es-abstract": "^1.17.5" } }, "node_modules/string.prototype.trim": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", - "integrity": "sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==", + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", "dev": true, - "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", "define-properties": "^1.2.1", - "es-abstract": "^1.23.0", - "es-object-atoms": "^1.0.0" + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -6819,16 +7495,19 @@ } }, "node_modules/string.prototype.trimend": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz", - "integrity": "sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", "dev": true, - "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -6903,7 +7582,6 @@ "version": "5.1.6", "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==", - "license": "MIT", "dependencies": { "client-only": "0.0.1" }, @@ -6970,10 +7648,9 @@ } }, "node_modules/tailwind-merge": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.5.0.tgz", - "integrity": "sha512-a6Q/isR5XAo9IR7Hjh80BQDkn8PG9ONJpSO/U3vGzdKyKG125lPHNXdiPfeQ5X0EOG0qKlS/0qnxdBYkLlD6tA==", - "license": "MIT", + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.5.5.tgz", + "integrity": "sha512-0LXunzzAZzo0tEPxV3I297ffKZPlKDrjj7NXphC8V5ak9yHC5zRmxnOe2m/Rd/7ivsOMJe3JZ2JVocoDdQTRBA==", "funding": { "type": "github", "url": "https://github.com/sponsors/dcastil" @@ -7055,6 +7732,11 @@ "node": ">=0.8" } }, + "node_modules/tinycolor2": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz", + "integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==" + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -7100,10 +7782,9 @@ } }, "node_modules/tslib": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", - "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", - "license": "0BSD" + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" }, "node_modules/type-check": { "version": "0.4.0", @@ -7154,18 +7835,18 @@ } }, "node_modules/typed-array-byte-offset": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz", - "integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.3.tgz", + "integrity": "sha512-GsvTyUHTriq6o/bHcTd0vM7OQ9JEdlvluu9YISaA7+KzDzPaIzEeDFNkTfhdE3MYcNhNi0vq/LlegYgIs5yPAw==", "dev": true, - "license": "MIT", "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.7", "for-each": "^0.3.3", "gopd": "^1.0.1", "has-proto": "^1.0.3", - "is-typed-array": "^1.1.13" + "is-typed-array": "^1.1.13", + "reflect.getprototypeof": "^1.0.6" }, "engines": { "node": ">= 0.4" @@ -7175,18 +7856,17 @@ } }, "node_modules/typed-array-length": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.6.tgz", - "integrity": "sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", "dev": true, - "license": "MIT", "dependencies": { "call-bind": "^1.0.7", "for-each": "^0.3.3", "gopd": "^1.0.1", - "has-proto": "^1.0.3", "is-typed-array": "^1.1.13", - "possible-typed-array-names": "^1.0.0" + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" }, "engines": { "node": ">= 0.4" @@ -7196,9 +7876,9 @@ } }, "node_modules/typescript": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz", - "integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==", + "version": "5.5.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", + "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", "dev": true, "license": "Apache-2.0", "bin": { @@ -7229,8 +7909,7 @@ "version": "6.20.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/update-browserslist-db": { "version": "1.1.1", @@ -7320,12 +7999,6 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "license": "MIT" }, - "node_modules/web-tree-sitter": { - "version": "0.22.6", - "resolved": "https://registry.npmjs.org/web-tree-sitter/-/web-tree-sitter-0.22.6.tgz", - "integrity": "sha512-hS87TH71Zd6mGAmYCvlgxeGDjqd9GTeqXNqTT+u0Gs51uIozNIaaq/kUAbV/Zf56jb2ZOyG8BxZs2GG9wbLi6Q==", - "license": "MIT" - }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -7342,41 +8015,43 @@ } }, "node_modules/which-boxed-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", - "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", "dev": true, - "license": "MIT", "dependencies": { - "is-bigint": "^1.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/which-builtin-type": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.1.4.tgz", - "integrity": "sha512-bppkmBSsHFmIMSl8BO9TbsyzsvGjVoppt8xUiGzwiu/bhDCGxnpOKCxgqj6GuyHE0mINMDecBFPlOm2hzY084w==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", "dev": true, - "license": "MIT", "dependencies": { + "call-bound": "^1.0.2", "function.prototype.name": "^1.1.6", "has-tostringtag": "^1.0.2", "is-async-function": "^2.0.0", - "is-date-object": "^1.0.5", - "is-finalizationregistry": "^1.0.2", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", "is-generator-function": "^1.0.10", - "is-regex": "^1.1.4", + "is-regex": "^1.2.1", "is-weakref": "^1.0.2", "isarray": "^2.0.5", - "which-boxed-primitive": "^1.0.2", + "which-boxed-primitive": "^1.1.0", "which-collection": "^1.0.2", - "which-typed-array": "^1.1.15" + "which-typed-array": "^1.1.16" }, "engines": { "node": ">= 0.4" @@ -7390,7 +8065,6 @@ "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", "dev": true, - "license": "MIT", "dependencies": { "is-map": "^2.0.3", "is-set": "^2.0.3", @@ -7405,11 +8079,10 @@ } }, "node_modules/which-typed-array": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz", - "integrity": "sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==", + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.16.tgz", + "integrity": "sha512-g+N+GAWiRj66DngFwHvISJd+ITsyphZvD1vChfVg6cEdnzy53GzB3oy0fUNlvhz7H7+MiqhYr26qxQShCpKTTQ==", "dev": true, - "license": "MIT", "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.7", diff --git a/package.json b/package.json index ec460c4b..b9cf75f9 100644 --- a/package.json +++ b/package.json @@ -17,34 +17,32 @@ "@radix-ui/react-select": "^2.1.2", "@radix-ui/react-slot": "^1.0.2", "@radix-ui/react-toast": "^1.2.2", - "@radix-ui/react-tooltip": "^1.0.7", + "@radix-ui/react-tooltip": "^1.1.4", + "@types/react-gtm-module": "^2.0.4", "autoprefixer": "^10.4.20", - "class-variance-authority": "^0.7.0", + "class-variance-authority": "^0.7.1", "clsx": "^2.1.0", - "cytoscape": "^3.30.4", - "cytoscape-fcose": "^2.2.0", "lucide-react": "^0.441.0", - "next": "15.0.3", + "next": "^15.1.2", + "playwright": "^1.49.1", "react": "^18", - "react-cytoscapejs": "^2.0.0", "react-dom": "^18", + "react-force-graph-2d": "^1.25.8", + "react-gtm-module": "^2.0.11", "react-resizable-panels": "^2.0.20", "react-syntax-highlighter": "^15.6.1", "react-type-animation": "^3.2.0", - "tailwind-merge": "^2.2.0", - "tailwindcss-animate": "^1.0.7", - "web-tree-sitter": "^0.22.6" + "tailwind-merge": "^2.5.5", + "tailwindcss-animate": "^1.0.7" }, "devDependencies": { - "@playwright/test": "^1.48.2", - "@types/cytoscape-fcose": "^2.2.4", - "@types/node": "^22", + "@playwright/test": "^1.49.1", + "@types/node": "^22.10.2", "@types/react": "^18", - "@types/react-cytoscapejs": "^1.2.5", "@types/react-dom": "^18", "@types/react-syntax-highlighter": "^15.5.13", "eslint": "^9", - "eslint-config-next": "15.0.3", + "eslint-config-next": "^15.1.2", "tailwindcss": "^3.4.3", "typescript": "^5" } diff --git a/playwright.config.ts b/playwright.config.ts index 02963413..7a5292d5 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -20,7 +20,7 @@ export default defineConfig({ /* Retry on CI only */ retries: process.env.CI ? 2 : 0, /* Opt out of parallel tests on CI. */ - workers: process.env.CI ? 1 : undefined, + workers: process.env.CI ? 2 : undefined, /* Reporter to use. See https://playwright.dev/docs/test-reporters */ reporter: 'html', /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ diff --git a/tailwind.config.js b/tailwind.config.js index f0c4cb6f..14d51792 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -23,9 +23,10 @@ module.exports = { }, extend: { colors: { - pink: "#F43F5F", - yellow: "#E9B306", - blue: "#15B8A6", + blue: "#7466FF", + pink: "#FF66B3", + orange: "#FF804D", + turquoise: "#80E6E6", border: "hsl(var(--border))", input: "hsl(var(--input))", ring: "hsl(var(--ring))",