mirror of
https://github.com/reconurge/flowsint.git
synced 2026-03-12 01:44:42 -05:00
feat: new color and recent sketches
This commit is contained in:
@@ -11,6 +11,7 @@ import { NavUser } from "@/components/nav-user";
|
||||
import { SubNav } from "@/components/dashboard/sub-nav";
|
||||
import Feedback from "@/components/dashboard/feedback";
|
||||
import Link from "next/link";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
|
||||
const DashboardLayout = async ({
|
||||
children,
|
||||
@@ -35,6 +36,7 @@ const DashboardLayout = async ({
|
||||
<Radar className="mr-2 h-6 w-6" />
|
||||
<h2 className="text-lg font-semibold mr-6">flowsint</h2>
|
||||
</Link>
|
||||
{/* <Separator orientation="vertical" className="h-6" /> */}
|
||||
<MainNav className="mx-6" />
|
||||
<div className="ml-auto flex items-center space-x-2">
|
||||
<div className="lg:flex hidden items-center space-x-2">
|
||||
|
||||
@@ -36,12 +36,12 @@ export default function RootLayout({
|
||||
return (
|
||||
<html suppressHydrationWarning lang="en">
|
||||
<head>
|
||||
{process.env.NODE_ENV === "development" && (
|
||||
{/* {process.env.NODE_ENV === "development" && ( */}
|
||||
<script
|
||||
crossOrigin="anonymous"
|
||||
src="//unpkg.com/react-scan/dist/auto.global.js"
|
||||
/>
|
||||
)}
|
||||
{/* )} */}
|
||||
</head>
|
||||
<body
|
||||
className={clsx(
|
||||
|
||||
@@ -1,164 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import type * as React from "react"
|
||||
import { usePathname } from "next/navigation"
|
||||
import Link from "next/link"
|
||||
import { FolderSearch, Globe, KeyIcon, MapPin, Network, SettingsIcon, UserIcon, Users } from "lucide-react"
|
||||
|
||||
import { NavUser } from "@/components/nav-user"
|
||||
import {
|
||||
Sidebar,
|
||||
SidebarContent,
|
||||
SidebarFooter,
|
||||
SidebarGroup,
|
||||
SidebarGroupLabel,
|
||||
SidebarHeader,
|
||||
SidebarMenu,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
SidebarRail,
|
||||
SidebarSeparator,
|
||||
SidebarTrigger,
|
||||
} from "@/components/ui/sidebar"
|
||||
import { TeamSwitcher } from "./team-switcher"
|
||||
// Define navigation item type
|
||||
interface NavItem {
|
||||
title: string
|
||||
href: string
|
||||
icon: React.ElementType
|
||||
}
|
||||
|
||||
interface AppSidebarProps extends React.ComponentProps<typeof Sidebar> {
|
||||
user: any
|
||||
}
|
||||
|
||||
export function AppSidebar({ user, ...props }: AppSidebarProps) {
|
||||
const pathname = usePathname()
|
||||
|
||||
// Main navigation items
|
||||
const mainNavItems: NavItem[] = [
|
||||
{
|
||||
title: "Investigations",
|
||||
href: "/dashboard",
|
||||
icon: FolderSearch,
|
||||
},
|
||||
{
|
||||
title: "Networks",
|
||||
href: "/dashboard/networks",
|
||||
icon: Network,
|
||||
},
|
||||
{
|
||||
title: "Entities",
|
||||
href: "/dashboard/entities",
|
||||
icon: Users,
|
||||
},
|
||||
{
|
||||
title: "OSINT sources",
|
||||
href: "/dashboard/sources",
|
||||
icon: Globe,
|
||||
},
|
||||
{
|
||||
title: "Map",
|
||||
href: "/dashboard/map",
|
||||
icon: MapPin,
|
||||
},
|
||||
]
|
||||
|
||||
// Team navigation items
|
||||
const teamNavItems: NavItem[] = [
|
||||
{
|
||||
title: "My account",
|
||||
href: "/dashboard/settings/account",
|
||||
icon: UserIcon,
|
||||
},
|
||||
{
|
||||
title: "Team members",
|
||||
href: "/dashboard/settings/team",
|
||||
icon: Users,
|
||||
},
|
||||
]
|
||||
|
||||
// Preferences navigation items
|
||||
const preferencesNavItems: NavItem[] = [
|
||||
{
|
||||
title: "Settings",
|
||||
href: "/dashboard/settings",
|
||||
icon: SettingsIcon,
|
||||
},
|
||||
{
|
||||
title: "API keys",
|
||||
href: "/dashboard/keys",
|
||||
icon: KeyIcon,
|
||||
},
|
||||
]
|
||||
|
||||
// Function to check if a link is active
|
||||
const isActive = (href: string) => {
|
||||
// Exact match for dashboard
|
||||
if (href === "/dashboard" && pathname === "/dashboard") {
|
||||
return true
|
||||
}
|
||||
// For other routes, check if pathname starts with href (for nested routes)
|
||||
return href !== "/dashboard" && pathname.startsWith(href)
|
||||
}
|
||||
|
||||
return (
|
||||
<Sidebar collapsible="icon" {...props} >
|
||||
<SidebarHeader>
|
||||
<TeamSwitcher />
|
||||
</SidebarHeader>
|
||||
<SidebarContent className="p-0">
|
||||
<SidebarGroup className="group-data-[collapsible=icon]:p-auto">
|
||||
<SidebarGroupLabel>NAVIGATION</SidebarGroupLabel>
|
||||
<SidebarMenu>
|
||||
{mainNavItems.map((item) => (
|
||||
<SidebarMenuItem key={item.href}>
|
||||
<SidebarMenuButton asChild isActive={isActive(item.href)}>
|
||||
<Link href={item.href}>
|
||||
<item.icon className="h-4 w-4" />
|
||||
<span>{item.title}</span>
|
||||
</Link>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
))}
|
||||
</SidebarMenu>
|
||||
</SidebarGroup>
|
||||
<SidebarSeparator />
|
||||
<SidebarGroup className="group-data-[collapsible=icon]:p-auto">
|
||||
<SidebarGroupLabel>TEAMS</SidebarGroupLabel>
|
||||
<SidebarMenu>
|
||||
{teamNavItems.map((item) => (
|
||||
<SidebarMenuItem key={item.href}>
|
||||
<SidebarMenuButton asChild isActive={isActive(item.href)}>
|
||||
<Link href={item.href}>
|
||||
<item.icon className="h-4 w-4" />
|
||||
<span>{item.title}</span>
|
||||
</Link>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
))}
|
||||
</SidebarMenu>
|
||||
</SidebarGroup>
|
||||
<SidebarGroup className="group-data-[collapsible=icon]:p-auto">
|
||||
<SidebarGroupLabel>PREFERENCES</SidebarGroupLabel>
|
||||
<SidebarMenu>
|
||||
{preferencesNavItems.map((item) => (
|
||||
<SidebarMenuItem key={item.href}>
|
||||
<SidebarMenuButton asChild isActive={isActive(item.href)}>
|
||||
<Link href={item.href}>
|
||||
<item.icon className="h-4 w-4" />
|
||||
<span>{item.title}</span>
|
||||
</Link>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
))}
|
||||
</SidebarMenu>
|
||||
</SidebarGroup>
|
||||
</SidebarContent>
|
||||
<SidebarFooter>
|
||||
<NavUser user={user} />
|
||||
</SidebarFooter>
|
||||
<SidebarRail />
|
||||
</Sidebar>
|
||||
)
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import { cn } from "@/lib/utils"
|
||||
import ProjectSelector from "../investigations/project-selector"
|
||||
import CaseSelector from "../investigations/case-selector"
|
||||
import { useParams } from "next/navigation"
|
||||
import { Separator } from "../ui/separator"
|
||||
|
||||
export function MainNav({ className, ...props }: React.HTMLAttributes<HTMLElement>) {
|
||||
const { investigation_id, project_id } = useParams()
|
||||
|
||||
@@ -3,12 +3,11 @@
|
||||
import type { Investigation } from "@/types/investigation"
|
||||
import { useQuery } from "@tanstack/react-query"
|
||||
import Link from "next/link"
|
||||
import { FileSearch, FileText, Folder, Layers, Search, Users, Waypoints } from "lucide-react"
|
||||
import { Search } from "lucide-react"
|
||||
|
||||
import { Card, CardContent, CardFooter } from "@/components/ui/card"
|
||||
import { Card, CardContent } from "@/components/ui/card"
|
||||
import { Skeleton } from "@/components/ui/skeleton"
|
||||
import { formatDistanceToNow } from "date-fns"
|
||||
import { number, string } from "zod"
|
||||
|
||||
const RecentSketches = () => {
|
||||
const {
|
||||
@@ -95,15 +94,12 @@ const RecentSketches = () => {
|
||||
{investigations?.map((investigation: Investigation) => {
|
||||
return (
|
||||
<Link href={`/dashboard/projects/${investigation.project_id}/investigations/${investigation.id}`} key={investigation.id} className="group">
|
||||
<Card className="bg-transparent shadow-none h-full transition-all duration-200 border-none">
|
||||
<div className={`flex items-center justify-center overflow-hidden bg-foreground/5 h-40 border group-hover:border-primary/80 group-hover:border-2 rounded-md`}>
|
||||
<FlowchartDiagram />
|
||||
</div>
|
||||
<Card className="bg-background shadow-none h-full transition-all duration-200 hover:border-primary rounded-md">
|
||||
<CardContent className="p-4 relative">
|
||||
<h3 className="font-medium line-clamp-1 group-hover:text-primary transition-colors">
|
||||
{investigation?.project?.name}/{investigation.title}
|
||||
</h3>
|
||||
<span className="text-xs opacity-60">Last updated {formatDistanceToNow(investigation.last_updated_at, { addSuffix: true })}</span>
|
||||
<span className="text-xs opacity-60 group-hover:text-primary">Last updated {formatDistanceToNow(investigation.last_updated_at, { addSuffix: true })}</span>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Link>
|
||||
|
||||
@@ -21,7 +21,6 @@ import EmailNode from "./nodes/email"
|
||||
import SocialNode from "./nodes/social"
|
||||
import AddressNode from "./nodes/physical_address"
|
||||
import {
|
||||
AlignCenterVertical,
|
||||
MaximizeIcon,
|
||||
ZoomInIcon,
|
||||
ZoomOutIcon,
|
||||
@@ -29,6 +28,8 @@ import {
|
||||
PlusIcon,
|
||||
GroupIcon,
|
||||
WorkflowIcon,
|
||||
NetworkIcon,
|
||||
WaypointsIcon,
|
||||
} from "lucide-react"
|
||||
import { useTheme } from "next-themes"
|
||||
import NewActions from "../new-actions"
|
||||
@@ -134,13 +135,27 @@ const FlowControls = memo(
|
||||
size="icon"
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
onLayout("TB", fitView)
|
||||
onLayout("dagre", fitView)
|
||||
}}
|
||||
>
|
||||
<AlignCenterVertical className="h-4 w-4" />
|
||||
<NetworkIcon className="h-4 w-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Auto layout</TooltipContent>
|
||||
<TooltipContent>Dagre layout</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
onLayout("force", fitView)
|
||||
}}
|
||||
>
|
||||
<WaypointsIcon className="h-4 w-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Force layout</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
@@ -353,10 +368,12 @@ const LayoutFlow = ({ refetch, theme }: LayoutFlowProps) => {
|
||||
<ResizableHandle />
|
||||
<ResizablePanel defaultSize={20} className="h-full">
|
||||
<ResizablePanelGroup autoSaveId="conditional" direction="vertical">
|
||||
{currentNode && <ResizablePanel order={1} id="top" defaultSize={50}>
|
||||
<ProfilePanel data={currentNode.data} type={currentNode.type} />
|
||||
</ResizablePanel>}
|
||||
<ResizableHandle />
|
||||
{currentNode && <>
|
||||
<ResizablePanel order={1} id="top" defaultSize={50}>
|
||||
<ProfilePanel data={currentNode.data} type={currentNode.type} />
|
||||
</ResizablePanel>
|
||||
<ResizableHandle />
|
||||
</>}
|
||||
<ResizablePanel order={2} id="bottom" defaultSize={50}>
|
||||
<NodesPanel nodes={processedNodes} />
|
||||
</ResizablePanel>
|
||||
|
||||
@@ -27,7 +27,7 @@ export const ButtonEdge = memo(({
|
||||
|
||||
return (
|
||||
<>
|
||||
<BaseEdge path={edgePath} markerEnd={markerEnd} style={style} />
|
||||
<BaseEdge className="opacity-30" path={edgePath} markerEnd={markerEnd} style={style} />
|
||||
<EdgeLabelRenderer>
|
||||
<div
|
||||
className="nodrag nopan pointer-events-auto absolute"
|
||||
|
||||
@@ -29,54 +29,49 @@ export default function ProfilePanel({ data, type }: { data: any, type: "individ
|
||||
<div className="bg-primary/10 p-3 rounded-full">
|
||||
<Mail className="h-8 w-8 text-primary" />
|
||||
</div>
|
||||
<div className="w-full">
|
||||
<div className="w-full text-center">
|
||||
<h2 className="text-xl w-full font-bold break-all">{data.email}</h2>
|
||||
<p className="text-sm text-muted-foreground">Email Address</p>
|
||||
</div>
|
||||
<SearchEmail investigation_id={investigation_id as string} email={data.email} />
|
||||
</div>
|
||||
<Separator />
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-1">
|
||||
<div className="flex items-center gap-2 text-primary">
|
||||
<div className="space-y-4 text-center">
|
||||
<div className="space-y-1 text-center">
|
||||
<div className="flex items-center justify-center gap-2 text-primary">
|
||||
<Shield className="h-4 w-4" />
|
||||
<span className="text-sm">Security Status</span>
|
||||
</div>
|
||||
<div className="flex gap-2 mt-1">
|
||||
<div className="flex gap-2 mt-1 items-center justify-center">
|
||||
<Badge variant={data.breach_found ? "destructive" : "outline"} className="px-3 py-1">
|
||||
{data.breach_found ? "Breach Found" : "No Breach Detected"}
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<div className="flex items-center gap-2 text-primary">
|
||||
<div className="flex items-center justify-center gap-2 text-primary">
|
||||
<Info className="h-4 w-4" />
|
||||
<span className="text-sm">Email ID</span>
|
||||
</div>
|
||||
<p className="text-xs font-mono text-muted-foreground">{data.id}</p>
|
||||
<p className="text-xs font-mono text-center text-muted-foreground">{data.id}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* Right column with related info */}
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-1">
|
||||
<div className="flex items-center gap-2 text-primary">
|
||||
<div className="flex items-center justify-center gap-2 text-primary">
|
||||
<Link2 className="h-4 w-4" />
|
||||
<span className="text-sm">Associated Individual</span>
|
||||
</div>
|
||||
<p className="text-xs font-mono text-muted-foreground">{data.individual_id}</p>
|
||||
<p className="text-xs font-mono text-center text-muted-foreground">{data.individual_id}</p>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<div className="flex items-center gap-2 text-primary">
|
||||
<div className="flex items-center justify-center gap-2 text-primary">
|
||||
<Info className="h-4 w-4" />
|
||||
<span className="text-sm">Investigation ID</span>
|
||||
</div>
|
||||
<p className="text-xs font-mono text-muted-foreground">{data.investigation_id}</p>
|
||||
</div>
|
||||
<div className="mt-auto pt-4">
|
||||
<Badge variant="outline" className="px-2 py-1">
|
||||
Email Record
|
||||
</Badge>
|
||||
<p className="text-xs font-mono text-center text-muted-foreground">{data.investigation_id}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -133,80 +133,80 @@ interface LayoutOptions {
|
||||
iterations?: number
|
||||
}
|
||||
|
||||
// export const getLayoutedElements = (
|
||||
// nodes: AppNode[],
|
||||
// edges: Edge[],
|
||||
// options: LayoutOptions = {
|
||||
// direction: "LR",
|
||||
// strength: -300,
|
||||
// distance: 100,
|
||||
// iterations: 300,
|
||||
// },
|
||||
// ) => {
|
||||
// // Create a map of node IDs to indices for the simulation
|
||||
// const nodeMap = new Map(nodes.map((node, i) => [node.id, i]))
|
||||
export const getForceLayoutedElements = (
|
||||
nodes: AppNode[],
|
||||
edges: Edge[],
|
||||
options: LayoutOptions = {
|
||||
direction: "LR",
|
||||
strength: -300,
|
||||
distance: 100,
|
||||
iterations: 300,
|
||||
},
|
||||
) => {
|
||||
// Create a map of node IDs to indices for the simulation
|
||||
const nodeMap = new Map(nodes.map((node, i) => [node.id, i]))
|
||||
|
||||
// // Create a copy of nodes with positions for the simulation
|
||||
// const nodesCopy = nodes.map((node) => ({
|
||||
// ...node,
|
||||
// x: node.position?.x || Math.random() * 500,
|
||||
// y: node.position?.y || Math.random() * 500,
|
||||
// width: node.measured?.width || 0,
|
||||
// height: node.measured?.height || 0,
|
||||
// }))
|
||||
// Create a copy of nodes with positions for the simulation
|
||||
const nodesCopy = nodes.map((node) => ({
|
||||
...node,
|
||||
x: node.position?.x || Math.random() * 500,
|
||||
y: node.position?.y || Math.random() * 500,
|
||||
width: node.measured?.width || 0,
|
||||
height: node.measured?.height || 0,
|
||||
}))
|
||||
|
||||
// // Create links for the simulation using indices
|
||||
// const links = edges.map((edge) => ({
|
||||
// source: nodeMap.get(edge.source),
|
||||
// target: nodeMap.get(edge.target),
|
||||
// original: edge,
|
||||
// }))
|
||||
// Create links for the simulation using indices
|
||||
const links = edges.map((edge) => ({
|
||||
source: nodeMap.get(edge.source),
|
||||
target: nodeMap.get(edge.target),
|
||||
original: edge,
|
||||
}))
|
||||
|
||||
// // Create the simulation
|
||||
// const simulation = d3
|
||||
// .forceSimulation(nodesCopy)
|
||||
// .force(
|
||||
// "link",
|
||||
// d3.forceLink(links).id((d: any) => nodeMap.get(d.id)),
|
||||
// )
|
||||
// .force("charge", d3.forceManyBody().strength(options.strength || -300))
|
||||
// .force("center", d3.forceCenter(250, 250))
|
||||
// .force(
|
||||
// "collision",
|
||||
// d3.forceCollide().radius((d: any) => Math.max(d.width, d.height) / 2 + 10),
|
||||
// )
|
||||
// Create the simulation
|
||||
const simulation = d3
|
||||
.forceSimulation(nodesCopy)
|
||||
.force(
|
||||
"link",
|
||||
d3.forceLink(links).id((d: any) => nodeMap.get(d.id)),
|
||||
)
|
||||
.force("charge", d3.forceManyBody().strength(options.strength || -300))
|
||||
.force("center", d3.forceCenter(250, 250))
|
||||
.force(
|
||||
"collision",
|
||||
d3.forceCollide().radius((d: any) => Math.max(d.width, d.height) / 2 + 10),
|
||||
)
|
||||
|
||||
// // If direction is horizontal, adjust forces
|
||||
// if (options.direction === "LR") {
|
||||
// simulation.force("x", d3.forceX(250).strength(0.1))
|
||||
// simulation.force("y", d3.forceY(250).strength(0.05))
|
||||
// } else {
|
||||
// simulation.force("x", d3.forceX(250).strength(0.05))
|
||||
// simulation.force("y", d3.forceY(250).strength(0.1))
|
||||
// }
|
||||
// If direction is horizontal, adjust forces
|
||||
if (options.direction === "LR") {
|
||||
simulation.force("x", d3.forceX(250).strength(0.1))
|
||||
simulation.force("y", d3.forceY(250).strength(0.05))
|
||||
} else {
|
||||
simulation.force("x", d3.forceX(250).strength(0.05))
|
||||
simulation.force("y", d3.forceY(250).strength(0.1))
|
||||
}
|
||||
|
||||
// // Run the simulation synchronously
|
||||
// simulation.stop()
|
||||
// for (let i = 0; i < (options.iterations || 300); i++) {
|
||||
// simulation.tick()
|
||||
// }
|
||||
// Run the simulation synchronously
|
||||
simulation.stop()
|
||||
for (let i = 0; i < (options.iterations || 300); i++) {
|
||||
simulation.tick()
|
||||
}
|
||||
|
||||
// // Update node positions based on simulation results
|
||||
// const updatedNodes = nodesCopy.map((node) => ({
|
||||
// ...node,
|
||||
// position: {
|
||||
// x: node.x - node.width / 2,
|
||||
// y: node.y - node.height / 2,
|
||||
// },
|
||||
// }))
|
||||
// Update node positions based on simulation results
|
||||
const updatedNodes = nodesCopy.map((node) => ({
|
||||
...node,
|
||||
position: {
|
||||
x: node.x - node.width / 2,
|
||||
y: node.y - node.height / 2,
|
||||
},
|
||||
}))
|
||||
|
||||
// return {
|
||||
// nodes: updatedNodes,
|
||||
// edges,
|
||||
// }
|
||||
// }
|
||||
return {
|
||||
nodes: updatedNodes,
|
||||
edges,
|
||||
}
|
||||
}
|
||||
|
||||
export const getLayoutedElements = (nodes: AppNode[],
|
||||
export const getDagreLayoutedElements = (nodes: AppNode[],
|
||||
edges: Edge[],
|
||||
options: LayoutOptions = {
|
||||
direction: "TB",
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
type OnNodesChange,
|
||||
type OnEdgesChange,
|
||||
} from '@xyflow/react';
|
||||
import { getLayoutedElements } from '@/lib/utils';
|
||||
import { getForceLayoutedElements, getDagreLayoutedElements } from '@/lib/utils';
|
||||
|
||||
export type AppNode = Node;
|
||||
|
||||
@@ -36,7 +36,7 @@ export type AppState = {
|
||||
setNodes: (nodes: AppNode[]) => void;
|
||||
setEdges: (edges: Edge[]) => void;
|
||||
highlightPath: (selectedNode: Node | null) => void;
|
||||
onLayout: (direct: string, fitView: () => void) => void,
|
||||
onLayout: (layout: string, fitView: () => void) => void,
|
||||
onConnect: (params: any, investigation_id?: string) => Promise<void>;
|
||||
onNodeClick: (_: React.MouseEvent, node: Node) => void;
|
||||
onPaneClick: (_: React.MouseEvent) => void,
|
||||
@@ -139,10 +139,16 @@ const createStore = (initialNodes: AppNode[] = [], initialEdges: Edge[] = []) =>
|
||||
set({ currentNode: null });
|
||||
// get().resetNodeStyles();
|
||||
},
|
||||
onLayout: (direction = 'TB', fitView: () => void) => {
|
||||
const { nodes, edges } = getLayoutedElements(get().nodes, get().edges, { direction });
|
||||
// @ts-ignore
|
||||
set({ nodes, edges }); // Fixed the edges type issue
|
||||
onLayout: (layout = "dagre", fitView: () => void) => {
|
||||
if (layout === "force") {
|
||||
const { nodes, edges } = getForceLayoutedElements(get().nodes, get().edges);
|
||||
// @ts-ignore
|
||||
set({ nodes, edges });
|
||||
} else {
|
||||
const { nodes, edges } = getDagreLayoutedElements(get().nodes, get().edges);
|
||||
// @ts-ignore
|
||||
set({ nodes, edges });
|
||||
}
|
||||
window.requestAnimationFrame(() => {
|
||||
fitView();
|
||||
});
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
--card-foreground: hsl(0 0% 3.9%);
|
||||
--popover: hsl(0 0% 100%);
|
||||
--popover-foreground: hsl(0 0% 3.9%);
|
||||
--primary: oklch(0.623 0.214 259.815);;
|
||||
--primary: oklch(0.49 0.24 293);
|
||||
--primary-foreground: hsl(0 0% 98%);
|
||||
--secondary: hsl(0 0% 96.1%);
|
||||
--secondary-foreground: hsl(0 0% 9%);
|
||||
@@ -53,7 +53,7 @@
|
||||
--card-foreground: hsl(0 0% 98%);
|
||||
--popover: hsl(240 0% 9.9%);
|
||||
--popover-foreground: hsl(0 0% 98%);
|
||||
--primary: oklch(0.623 0.214 259.815);;
|
||||
--primary: oklch(0.49 0.24 293);
|
||||
--primary-foreground: hsl(0, 0%, 100%);
|
||||
--secondary: hsl(0 0% 14.9%);
|
||||
--secondary-foreground: hsl(0 0% 98%);
|
||||
|
||||
Reference in New Issue
Block a user