-
Notifications
You must be signed in to change notification settings - Fork 24
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
27 changed files
with
1,266 additions
and
401 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
'@yeger/debounce': minor | ||
--- | ||
|
||
add optional args to callback |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,6 +3,7 @@ | |
"compilerOptions": { | ||
"declaration": false, | ||
"module": "CommonJS", | ||
"noEmit": false, | ||
"sourceMap": false | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
@tailwind base; | ||
@tailwind components; | ||
@tailwind utilities; | ||
|
||
@layer base { | ||
:root { | ||
--background: 0 0% 100%; | ||
--foreground: 222.2 84% 4.9%; | ||
|
||
--card: 0 0% 100%; | ||
--card-foreground: 222.2 84% 4.9%; | ||
|
||
--popover: 0 0% 100%; | ||
--popover-foreground: 222.2 84% 4.9%; | ||
|
||
--primary: 222.2 47.4% 11.2%; | ||
--primary-foreground: 210 40% 98%; | ||
|
||
--secondary: 210 40% 96.1%; | ||
--secondary-foreground: 222.2 47.4% 11.2%; | ||
|
||
--muted: 210 40% 96.1%; | ||
--muted-foreground: 215.4 16.3% 46.9%; | ||
|
||
--accent: 210 40% 96.1%; | ||
--accent-foreground: 222.2 47.4% 11.2%; | ||
|
||
--destructive: 0 84.2% 60.2%; | ||
--destructive-foreground: 210 40% 98%; | ||
|
||
--border: 214.3 31.8% 91.4%; | ||
--input: 214.3 31.8% 91.4%; | ||
--ring: 222.2 84% 4.9%; | ||
|
||
--radius: 0.5rem; | ||
} | ||
|
||
.dark { | ||
--background: 222.2 84% 4.9%; | ||
--foreground: 210 40% 98%; | ||
|
||
--card: 222.2 84% 4.9%; | ||
--card-foreground: 210 40% 98%; | ||
|
||
--popover: 222.2 84% 4.9%; | ||
--popover-foreground: 210 40% 98%; | ||
|
||
--primary: 210 40% 98%; | ||
--primary-foreground: 222.2 47.4% 11.2%; | ||
|
||
--secondary: 217.2 32.6% 17.5%; | ||
--secondary-foreground: 210 40% 98%; | ||
|
||
--muted: 217.2 32.6% 17.5%; | ||
--muted-foreground: 215 20.2% 65.1%; | ||
|
||
--accent: 217.2 32.6% 17.5%; | ||
--accent-foreground: 210 40% 98%; | ||
|
||
--destructive: 0 62.8% 30.6%; | ||
--destructive-foreground: 210 40% 98%; | ||
|
||
--border: 217.2 32.6% 17.5%; | ||
--input: 217.2 32.6% 17.5%; | ||
--ring: hsl(212.7, 26.8%, 83.9); | ||
} | ||
} | ||
|
||
@layer base { | ||
* { | ||
@apply border-border; | ||
} | ||
body { | ||
@apply bg-background text-foreground; | ||
} | ||
} | ||
|
||
.react-flow__edge { | ||
pointer-events: none !important; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import { FilterInput, TaskInput } from '../components/GraphInputs' | ||
import './globals.css' | ||
|
||
export default function RootLayout({ | ||
// Layouts must accept a children prop. | ||
// This will be populated with nested layouts or pages | ||
children, | ||
}: { | ||
children: React.ReactNode | ||
}) { | ||
return ( | ||
<html lang="en" className="h-full w-full"> | ||
<body className="m-0 h-full w-full"> | ||
<div className="h-full w-full"> | ||
<main className="relative flex h-full w-full flex-col"> | ||
<div className="absolute inset-x-0 top-0 z-10 flex gap-2 border-gray-400 bg-none p-2 shadow-xl backdrop-blur-sm"> | ||
<TaskInput /> | ||
<FilterInput /> | ||
</div> | ||
{children} | ||
</main> | ||
</div> | ||
</body> | ||
</html> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
export default function Loading() { | ||
// You can add any UI inside Loading, including a Skeleton. | ||
return <span>Loading</span> | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import type { Metadata } from 'next' | ||
|
||
import { Graph } from '../components/Graph' | ||
|
||
export const metadata: Metadata = { | ||
title: 'Turbo Graph', | ||
// m | ||
// <meta | ||
// name="description" | ||
// content="Interactive visualization of Turborepo task graphs." | ||
// /> | ||
// <link rel="icon" href="/favicon.ico" /> | ||
// </Head> | ||
} | ||
|
||
// TODO: Add error boundary | ||
// TODO: Add loading state | ||
export default async function Home({ | ||
searchParams, | ||
}: { | ||
searchParams?: { [key: string]: string | string[] | undefined } | ||
}) { | ||
const rawTasks = searchParams?.tasks | ||
const tasks = Array.isArray(rawTasks) | ||
? rawTasks | ||
: rawTasks?.split(' ') ?? ['build'] | ||
const filter = searchParams?.filter | ||
if (filter && Array.isArray(filter)) { | ||
throw new Error(`Unsupported filter ${filter}`) | ||
} | ||
|
||
return <Graph tasks={tasks} filter={filter} /> | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
'use client' | ||
|
||
import { Stream } from '@yeger/streams' | ||
import { scaleOrdinal } from 'd3-scale' | ||
import { schemeSet3 } from 'd3-scale-chromatic' | ||
import { useEffect, useMemo } from 'react' | ||
import { | ||
Background, | ||
Controls, | ||
Handle, | ||
MiniMap, | ||
Position, | ||
ReactFlow, | ||
useReactFlow, | ||
} from 'reactflow' | ||
|
||
import 'reactflow/dist/style.css' | ||
import { | ||
type FlowNode, | ||
TASK_HEIGHT_VAR, | ||
TASK_WIDTH_VAR, | ||
convertGraph, | ||
getTaskColorVar, | ||
} from '../lib/flow' | ||
import type { TurboGraph } from '../lib/turbo' | ||
import { useGraphSettings } from '../lib/utils' | ||
|
||
export interface Props { | ||
graph: TurboGraph | ||
uniqueTasks: Set<string> | ||
} | ||
|
||
interface TaskProps { | ||
data: FlowNode | ||
} | ||
|
||
function Task({ data }: TaskProps) { | ||
const { task, workspace, isTerminal, isOrigin } = data | ||
return ( | ||
<div className="flex flex-col"> | ||
{isOrigin ? null : ( | ||
<Handle type="target" position={Position.Top} isConnectable={false} /> | ||
)} | ||
<div | ||
className="flex flex-col rounded p-2 outline outline-2 outline-neutral-700" | ||
style={{ | ||
width: `var(${TASK_WIDTH_VAR})`, | ||
height: `var(${TASK_HEIGHT_VAR})`, | ||
backgroundColor: `var(${getTaskColorVar(task)})`, | ||
}} | ||
> | ||
<div className="font-bold">{task}</div> | ||
<div className="text-right text-neutral-800">{workspace}</div> | ||
</div> | ||
{isTerminal ? null : ( | ||
<Handle | ||
type="source" | ||
position={Position.Bottom} | ||
isConnectable={false} | ||
/> | ||
)} | ||
</div> | ||
) | ||
} | ||
|
||
const nodeTypes = { | ||
task: Task, | ||
} | ||
|
||
export function FlowGraph({ graph, uniqueTasks }: Props) { | ||
const { flowGraph, taskCssVars } = useMemo(() => { | ||
const flowGraph = convertGraph(graph) | ||
const getColor = scaleOrdinal(schemeSet3).domain(uniqueTasks) | ||
const taskColors = Stream.from(uniqueTasks).toRecord( | ||
(task) => getTaskColorVar(task), | ||
(task) => getColor(task), | ||
) | ||
const taskSize: Record<string, string> = { | ||
[TASK_WIDTH_VAR]: `${flowGraph.sizeConfig.width}px`, | ||
[TASK_HEIGHT_VAR]: `${flowGraph.sizeConfig.height}px`, | ||
} | ||
const taskCssVars = { ...taskColors, ...taskSize } | ||
return { | ||
flowGraph, | ||
taskColors, | ||
taskCssVars, | ||
} | ||
}, [graph, uniqueTasks]) | ||
|
||
const { setParameter } = useGraphSettings() | ||
|
||
const onNodeClicked = (_: unknown, { data }: { data: FlowNode }) => { | ||
setParameter('filter', data.workspace) | ||
} | ||
|
||
return ( | ||
<ReactFlow | ||
nodes={flowGraph.nodes} | ||
edges={flowGraph.edges} | ||
nodeTypes={nodeTypes} | ||
minZoom={0.1} | ||
style={taskCssVars} | ||
edgesFocusable={false} | ||
edgesUpdatable={false} | ||
nodesDraggable={false} | ||
nodesConnectable={false} | ||
onNodeContextMenu={onNodeClicked} | ||
onNodeDoubleClick={onNodeClicked} | ||
zoomOnDoubleClick={false} | ||
> | ||
<Background /> | ||
<Controls showInteractive={false} /> | ||
<MiniMap | ||
nodeColor={({ data }: { data: FlowNode }) => | ||
`var(${getTaskColorVar(data.task)})` | ||
} | ||
nodeStrokeColor="#000000" | ||
/> | ||
<ViewFitter graph={graph} /> | ||
</ReactFlow> | ||
) | ||
} | ||
|
||
function ViewFitter({ graph }: { graph: TurboGraph }) { | ||
const reactFlow = useReactFlow() | ||
useEffect(() => { | ||
setTimeout(() => { | ||
reactFlow.fitView({ duration: 200, padding: 0.3 }) | ||
}, 200) | ||
}, [reactFlow, graph]) | ||
return null | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import { Stream } from '@yeger/streams' | ||
|
||
import { getGraph } from '../lib/turbo' | ||
|
||
import { FlowGraph } from './FlowGraph' | ||
|
||
export interface GraphProps { | ||
tasks: string[] | ||
filter?: string | ||
} | ||
|
||
// TODO: Add error boundary | ||
// TODO: Add loading state | ||
export async function Graph({ tasks, filter }: GraphProps) { | ||
const graphResult = await getGraph(tasks, filter) | ||
|
||
if (graphResult.isError) { | ||
// TODO: Improve error message | ||
return ( | ||
<div className="flex h-full w-full items-center justify-center p-4"> | ||
<code className="text-justify text-sm text-red-500"> | ||
{graphResult.getError().message} | ||
</code> | ||
</div> | ||
) | ||
} | ||
|
||
const graph = graphResult.get() | ||
const uniqueTasks = Stream.from(graph.nodes) | ||
.map(({ task }) => task) | ||
.toSet() | ||
|
||
return ( | ||
<div className="h-full w-full"> | ||
<FlowGraph graph={graph} uniqueTasks={uniqueTasks} /> | ||
</div> | ||
) | ||
} |
Oops, something went wrong.