mirror of
https://github.com/reconurge/flowsint.git
synced 2026-04-28 18:30:43 -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 { SubNav } from "@/components/dashboard/sub-nav";
|
||||||
import Feedback from "@/components/dashboard/feedback";
|
import Feedback from "@/components/dashboard/feedback";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import { Separator } from "@/components/ui/separator";
|
||||||
|
|
||||||
const DashboardLayout = async ({
|
const DashboardLayout = async ({
|
||||||
children,
|
children,
|
||||||
@@ -35,6 +36,7 @@ const DashboardLayout = async ({
|
|||||||
<Radar className="mr-2 h-6 w-6" />
|
<Radar className="mr-2 h-6 w-6" />
|
||||||
<h2 className="text-lg font-semibold mr-6">flowsint</h2>
|
<h2 className="text-lg font-semibold mr-6">flowsint</h2>
|
||||||
</Link>
|
</Link>
|
||||||
|
{/* <Separator orientation="vertical" className="h-6" /> */}
|
||||||
<MainNav className="mx-6" />
|
<MainNav className="mx-6" />
|
||||||
<div className="ml-auto flex items-center space-x-2">
|
<div className="ml-auto flex items-center space-x-2">
|
||||||
<div className="lg:flex hidden items-center space-x-2">
|
<div className="lg:flex hidden items-center space-x-2">
|
||||||
|
|||||||
@@ -36,12 +36,12 @@ export default function RootLayout({
|
|||||||
return (
|
return (
|
||||||
<html suppressHydrationWarning lang="en">
|
<html suppressHydrationWarning lang="en">
|
||||||
<head>
|
<head>
|
||||||
{process.env.NODE_ENV === "development" && (
|
{/* {process.env.NODE_ENV === "development" && ( */}
|
||||||
<script
|
<script
|
||||||
crossOrigin="anonymous"
|
crossOrigin="anonymous"
|
||||||
src="//unpkg.com/react-scan/dist/auto.global.js"
|
src="//unpkg.com/react-scan/dist/auto.global.js"
|
||||||
/>
|
/>
|
||||||
)}
|
{/* )} */}
|
||||||
</head>
|
</head>
|
||||||
<body
|
<body
|
||||||
className={clsx(
|
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 ProjectSelector from "../investigations/project-selector"
|
||||||
import CaseSelector from "../investigations/case-selector"
|
import CaseSelector from "../investigations/case-selector"
|
||||||
import { useParams } from "next/navigation"
|
import { useParams } from "next/navigation"
|
||||||
|
import { Separator } from "../ui/separator"
|
||||||
|
|
||||||
export function MainNav({ className, ...props }: React.HTMLAttributes<HTMLElement>) {
|
export function MainNav({ className, ...props }: React.HTMLAttributes<HTMLElement>) {
|
||||||
const { investigation_id, project_id } = useParams()
|
const { investigation_id, project_id } = useParams()
|
||||||
|
|||||||
@@ -3,12 +3,11 @@
|
|||||||
import type { Investigation } from "@/types/investigation"
|
import type { Investigation } from "@/types/investigation"
|
||||||
import { useQuery } from "@tanstack/react-query"
|
import { useQuery } from "@tanstack/react-query"
|
||||||
import Link from "next/link"
|
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 { Skeleton } from "@/components/ui/skeleton"
|
||||||
import { formatDistanceToNow } from "date-fns"
|
import { formatDistanceToNow } from "date-fns"
|
||||||
import { number, string } from "zod"
|
|
||||||
|
|
||||||
const RecentSketches = () => {
|
const RecentSketches = () => {
|
||||||
const {
|
const {
|
||||||
@@ -95,15 +94,12 @@ const RecentSketches = () => {
|
|||||||
{investigations?.map((investigation: Investigation) => {
|
{investigations?.map((investigation: Investigation) => {
|
||||||
return (
|
return (
|
||||||
<Link href={`/dashboard/projects/${investigation.project_id}/investigations/${investigation.id}`} key={investigation.id} className="group">
|
<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">
|
<Card className="bg-background shadow-none h-full transition-all duration-200 hover:border-primary rounded-md">
|
||||||
<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>
|
|
||||||
<CardContent className="p-4 relative">
|
<CardContent className="p-4 relative">
|
||||||
<h3 className="font-medium line-clamp-1 group-hover:text-primary transition-colors">
|
<h3 className="font-medium line-clamp-1 group-hover:text-primary transition-colors">
|
||||||
{investigation?.project?.name}/{investigation.title}
|
{investigation?.project?.name}/{investigation.title}
|
||||||
</h3>
|
</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>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ import EmailNode from "./nodes/email"
|
|||||||
import SocialNode from "./nodes/social"
|
import SocialNode from "./nodes/social"
|
||||||
import AddressNode from "./nodes/physical_address"
|
import AddressNode from "./nodes/physical_address"
|
||||||
import {
|
import {
|
||||||
AlignCenterVertical,
|
|
||||||
MaximizeIcon,
|
MaximizeIcon,
|
||||||
ZoomInIcon,
|
ZoomInIcon,
|
||||||
ZoomOutIcon,
|
ZoomOutIcon,
|
||||||
@@ -29,6 +28,8 @@ import {
|
|||||||
PlusIcon,
|
PlusIcon,
|
||||||
GroupIcon,
|
GroupIcon,
|
||||||
WorkflowIcon,
|
WorkflowIcon,
|
||||||
|
NetworkIcon,
|
||||||
|
WaypointsIcon,
|
||||||
} from "lucide-react"
|
} from "lucide-react"
|
||||||
import { useTheme } from "next-themes"
|
import { useTheme } from "next-themes"
|
||||||
import NewActions from "../new-actions"
|
import NewActions from "../new-actions"
|
||||||
@@ -134,13 +135,27 @@ const FlowControls = memo(
|
|||||||
size="icon"
|
size="icon"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
onLayout("TB", fitView)
|
onLayout("dagre", fitView)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<AlignCenterVertical className="h-4 w-4" />
|
<NetworkIcon className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</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>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
@@ -353,10 +368,12 @@ const LayoutFlow = ({ refetch, theme }: LayoutFlowProps) => {
|
|||||||
<ResizableHandle />
|
<ResizableHandle />
|
||||||
<ResizablePanel defaultSize={20} className="h-full">
|
<ResizablePanel defaultSize={20} className="h-full">
|
||||||
<ResizablePanelGroup autoSaveId="conditional" direction="vertical">
|
<ResizablePanelGroup autoSaveId="conditional" direction="vertical">
|
||||||
{currentNode && <ResizablePanel order={1} id="top" defaultSize={50}>
|
{currentNode && <>
|
||||||
<ProfilePanel data={currentNode.data} type={currentNode.type} />
|
<ResizablePanel order={1} id="top" defaultSize={50}>
|
||||||
</ResizablePanel>}
|
<ProfilePanel data={currentNode.data} type={currentNode.type} />
|
||||||
<ResizableHandle />
|
</ResizablePanel>
|
||||||
|
<ResizableHandle />
|
||||||
|
</>}
|
||||||
<ResizablePanel order={2} id="bottom" defaultSize={50}>
|
<ResizablePanel order={2} id="bottom" defaultSize={50}>
|
||||||
<NodesPanel nodes={processedNodes} />
|
<NodesPanel nodes={processedNodes} />
|
||||||
</ResizablePanel>
|
</ResizablePanel>
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export const ButtonEdge = memo(({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<BaseEdge path={edgePath} markerEnd={markerEnd} style={style} />
|
<BaseEdge className="opacity-30" path={edgePath} markerEnd={markerEnd} style={style} />
|
||||||
<EdgeLabelRenderer>
|
<EdgeLabelRenderer>
|
||||||
<div
|
<div
|
||||||
className="nodrag nopan pointer-events-auto absolute"
|
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">
|
<div className="bg-primary/10 p-3 rounded-full">
|
||||||
<Mail className="h-8 w-8 text-primary" />
|
<Mail className="h-8 w-8 text-primary" />
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full">
|
<div className="w-full text-center">
|
||||||
<h2 className="text-xl w-full font-bold break-all">{data.email}</h2>
|
<h2 className="text-xl w-full font-bold break-all">{data.email}</h2>
|
||||||
<p className="text-sm text-muted-foreground">Email Address</p>
|
<p className="text-sm text-muted-foreground">Email Address</p>
|
||||||
</div>
|
</div>
|
||||||
<SearchEmail investigation_id={investigation_id as string} email={data.email} />
|
<SearchEmail investigation_id={investigation_id as string} email={data.email} />
|
||||||
</div>
|
</div>
|
||||||
<Separator />
|
<Separator />
|
||||||
<div className="space-y-4">
|
<div className="space-y-4 text-center">
|
||||||
<div className="space-y-1">
|
<div className="space-y-1 text-center">
|
||||||
<div className="flex items-center gap-2 text-primary">
|
<div className="flex items-center justify-center gap-2 text-primary">
|
||||||
<Shield className="h-4 w-4" />
|
<Shield className="h-4 w-4" />
|
||||||
<span className="text-sm">Security Status</span>
|
<span className="text-sm">Security Status</span>
|
||||||
</div>
|
</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">
|
<Badge variant={data.breach_found ? "destructive" : "outline"} className="px-3 py-1">
|
||||||
{data.breach_found ? "Breach Found" : "No Breach Detected"}
|
{data.breach_found ? "Breach Found" : "No Breach Detected"}
|
||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-1">
|
<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" />
|
<Info className="h-4 w-4" />
|
||||||
<span className="text-sm">Email ID</span>
|
<span className="text-sm">Email ID</span>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/* Right column with related info */}
|
{/* Right column with related info */}
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div className="space-y-1">
|
<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" />
|
<Link2 className="h-4 w-4" />
|
||||||
<span className="text-sm">Associated Individual</span>
|
<span className="text-sm">Associated Individual</span>
|
||||||
</div>
|
</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>
|
||||||
<div className="space-y-1">
|
<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" />
|
<Info className="h-4 w-4" />
|
||||||
<span className="text-sm">Investigation ID</span>
|
<span className="text-sm">Investigation ID</span>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs font-mono text-muted-foreground">{data.investigation_id}</p>
|
<p className="text-xs font-mono text-center 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>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -133,80 +133,80 @@ interface LayoutOptions {
|
|||||||
iterations?: number
|
iterations?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
// export const getLayoutedElements = (
|
export const getForceLayoutedElements = (
|
||||||
// nodes: AppNode[],
|
nodes: AppNode[],
|
||||||
// edges: Edge[],
|
edges: Edge[],
|
||||||
// options: LayoutOptions = {
|
options: LayoutOptions = {
|
||||||
// direction: "LR",
|
direction: "LR",
|
||||||
// strength: -300,
|
strength: -300,
|
||||||
// distance: 100,
|
distance: 100,
|
||||||
// iterations: 300,
|
iterations: 300,
|
||||||
// },
|
},
|
||||||
// ) => {
|
) => {
|
||||||
// // Create a map of node IDs to indices for the simulation
|
// Create a map of node IDs to indices for the simulation
|
||||||
// const nodeMap = new Map(nodes.map((node, i) => [node.id, i]))
|
const nodeMap = new Map(nodes.map((node, i) => [node.id, i]))
|
||||||
|
|
||||||
// // Create a copy of nodes with positions for the simulation
|
// Create a copy of nodes with positions for the simulation
|
||||||
// const nodesCopy = nodes.map((node) => ({
|
const nodesCopy = nodes.map((node) => ({
|
||||||
// ...node,
|
...node,
|
||||||
// x: node.position?.x || Math.random() * 500,
|
x: node.position?.x || Math.random() * 500,
|
||||||
// y: node.position?.y || Math.random() * 500,
|
y: node.position?.y || Math.random() * 500,
|
||||||
// width: node.measured?.width || 0,
|
width: node.measured?.width || 0,
|
||||||
// height: node.measured?.height || 0,
|
height: node.measured?.height || 0,
|
||||||
// }))
|
}))
|
||||||
|
|
||||||
// // Create links for the simulation using indices
|
// Create links for the simulation using indices
|
||||||
// const links = edges.map((edge) => ({
|
const links = edges.map((edge) => ({
|
||||||
// source: nodeMap.get(edge.source),
|
source: nodeMap.get(edge.source),
|
||||||
// target: nodeMap.get(edge.target),
|
target: nodeMap.get(edge.target),
|
||||||
// original: edge,
|
original: edge,
|
||||||
// }))
|
}))
|
||||||
|
|
||||||
// // Create the simulation
|
// Create the simulation
|
||||||
// const simulation = d3
|
const simulation = d3
|
||||||
// .forceSimulation(nodesCopy)
|
.forceSimulation(nodesCopy)
|
||||||
// .force(
|
.force(
|
||||||
// "link",
|
"link",
|
||||||
// d3.forceLink(links).id((d: any) => nodeMap.get(d.id)),
|
d3.forceLink(links).id((d: any) => nodeMap.get(d.id)),
|
||||||
// )
|
)
|
||||||
// .force("charge", d3.forceManyBody().strength(options.strength || -300))
|
.force("charge", d3.forceManyBody().strength(options.strength || -300))
|
||||||
// .force("center", d3.forceCenter(250, 250))
|
.force("center", d3.forceCenter(250, 250))
|
||||||
// .force(
|
.force(
|
||||||
// "collision",
|
"collision",
|
||||||
// d3.forceCollide().radius((d: any) => Math.max(d.width, d.height) / 2 + 10),
|
d3.forceCollide().radius((d: any) => Math.max(d.width, d.height) / 2 + 10),
|
||||||
// )
|
)
|
||||||
|
|
||||||
// // If direction is horizontal, adjust forces
|
// If direction is horizontal, adjust forces
|
||||||
// if (options.direction === "LR") {
|
if (options.direction === "LR") {
|
||||||
// simulation.force("x", d3.forceX(250).strength(0.1))
|
simulation.force("x", d3.forceX(250).strength(0.1))
|
||||||
// simulation.force("y", d3.forceY(250).strength(0.05))
|
simulation.force("y", d3.forceY(250).strength(0.05))
|
||||||
// } else {
|
} else {
|
||||||
// simulation.force("x", d3.forceX(250).strength(0.05))
|
simulation.force("x", d3.forceX(250).strength(0.05))
|
||||||
// simulation.force("y", d3.forceY(250).strength(0.1))
|
simulation.force("y", d3.forceY(250).strength(0.1))
|
||||||
// }
|
}
|
||||||
|
|
||||||
// // Run the simulation synchronously
|
// Run the simulation synchronously
|
||||||
// simulation.stop()
|
simulation.stop()
|
||||||
// for (let i = 0; i < (options.iterations || 300); i++) {
|
for (let i = 0; i < (options.iterations || 300); i++) {
|
||||||
// simulation.tick()
|
simulation.tick()
|
||||||
// }
|
}
|
||||||
|
|
||||||
// // Update node positions based on simulation results
|
// Update node positions based on simulation results
|
||||||
// const updatedNodes = nodesCopy.map((node) => ({
|
const updatedNodes = nodesCopy.map((node) => ({
|
||||||
// ...node,
|
...node,
|
||||||
// position: {
|
position: {
|
||||||
// x: node.x - node.width / 2,
|
x: node.x - node.width / 2,
|
||||||
// y: node.y - node.height / 2,
|
y: node.y - node.height / 2,
|
||||||
// },
|
},
|
||||||
// }))
|
}))
|
||||||
|
|
||||||
// return {
|
return {
|
||||||
// nodes: updatedNodes,
|
nodes: updatedNodes,
|
||||||
// edges,
|
edges,
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
export const getLayoutedElements = (nodes: AppNode[],
|
export const getDagreLayoutedElements = (nodes: AppNode[],
|
||||||
edges: Edge[],
|
edges: Edge[],
|
||||||
options: LayoutOptions = {
|
options: LayoutOptions = {
|
||||||
direction: "TB",
|
direction: "TB",
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
type OnNodesChange,
|
type OnNodesChange,
|
||||||
type OnEdgesChange,
|
type OnEdgesChange,
|
||||||
} from '@xyflow/react';
|
} from '@xyflow/react';
|
||||||
import { getLayoutedElements } from '@/lib/utils';
|
import { getForceLayoutedElements, getDagreLayoutedElements } from '@/lib/utils';
|
||||||
|
|
||||||
export type AppNode = Node;
|
export type AppNode = Node;
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@ export type AppState = {
|
|||||||
setNodes: (nodes: AppNode[]) => void;
|
setNodes: (nodes: AppNode[]) => void;
|
||||||
setEdges: (edges: Edge[]) => void;
|
setEdges: (edges: Edge[]) => void;
|
||||||
highlightPath: (selectedNode: Node | null) => 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>;
|
onConnect: (params: any, investigation_id?: string) => Promise<void>;
|
||||||
onNodeClick: (_: React.MouseEvent, node: Node) => void;
|
onNodeClick: (_: React.MouseEvent, node: Node) => void;
|
||||||
onPaneClick: (_: React.MouseEvent) => void,
|
onPaneClick: (_: React.MouseEvent) => void,
|
||||||
@@ -139,10 +139,16 @@ const createStore = (initialNodes: AppNode[] = [], initialEdges: Edge[] = []) =>
|
|||||||
set({ currentNode: null });
|
set({ currentNode: null });
|
||||||
// get().resetNodeStyles();
|
// get().resetNodeStyles();
|
||||||
},
|
},
|
||||||
onLayout: (direction = 'TB', fitView: () => void) => {
|
onLayout: (layout = "dagre", fitView: () => void) => {
|
||||||
const { nodes, edges } = getLayoutedElements(get().nodes, get().edges, { direction });
|
if (layout === "force") {
|
||||||
// @ts-ignore
|
const { nodes, edges } = getForceLayoutedElements(get().nodes, get().edges);
|
||||||
set({ nodes, edges }); // Fixed the edges type issue
|
// @ts-ignore
|
||||||
|
set({ nodes, edges });
|
||||||
|
} else {
|
||||||
|
const { nodes, edges } = getDagreLayoutedElements(get().nodes, get().edges);
|
||||||
|
// @ts-ignore
|
||||||
|
set({ nodes, edges });
|
||||||
|
}
|
||||||
window.requestAnimationFrame(() => {
|
window.requestAnimationFrame(() => {
|
||||||
fitView();
|
fitView();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
--card-foreground: hsl(0 0% 3.9%);
|
--card-foreground: hsl(0 0% 3.9%);
|
||||||
--popover: hsl(0 0% 100%);
|
--popover: hsl(0 0% 100%);
|
||||||
--popover-foreground: hsl(0 0% 3.9%);
|
--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%);
|
--primary-foreground: hsl(0 0% 98%);
|
||||||
--secondary: hsl(0 0% 96.1%);
|
--secondary: hsl(0 0% 96.1%);
|
||||||
--secondary-foreground: hsl(0 0% 9%);
|
--secondary-foreground: hsl(0 0% 9%);
|
||||||
@@ -53,7 +53,7 @@
|
|||||||
--card-foreground: hsl(0 0% 98%);
|
--card-foreground: hsl(0 0% 98%);
|
||||||
--popover: hsl(240 0% 9.9%);
|
--popover: hsl(240 0% 9.9%);
|
||||||
--popover-foreground: hsl(0 0% 98%);
|
--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%);
|
--primary-foreground: hsl(0, 0%, 100%);
|
||||||
--secondary: hsl(0 0% 14.9%);
|
--secondary: hsl(0 0% 14.9%);
|
||||||
--secondary-foreground: hsl(0 0% 98%);
|
--secondary-foreground: hsl(0 0% 98%);
|
||||||
|
|||||||
Reference in New Issue
Block a user