415 lines
11 KiB
JavaScript
415 lines
11 KiB
JavaScript
import React from 'react';
|
||
import {Motion, spring} from 'react-motion';
|
||
import dagre from 'dagre';
|
||
// import prettyFormat from 'pretty-format';
|
||
// import reactElement from 'pretty-format/plugins/ReactElement';
|
||
|
||
function getFiberColor(fibers, id) {
|
||
if (fibers.currentIDs.indexOf(id) > -1) {
|
||
return 'lightgreen';
|
||
}
|
||
if (id === fibers.workInProgressID) {
|
||
return 'yellow';
|
||
}
|
||
return 'lightyellow';
|
||
}
|
||
|
||
function Graph(props) {
|
||
const {rankdir, trackActive} = props.settings;
|
||
var g = new dagre.graphlib.Graph();
|
||
g.setGraph({
|
||
width: 1000,
|
||
height: 1000,
|
||
nodesep: 50,
|
||
edgesep: 150,
|
||
ranksep: 100,
|
||
marginx: 100,
|
||
marginy: 100,
|
||
rankdir,
|
||
});
|
||
|
||
var edgeLabels = {};
|
||
React.Children.forEach(props.children, function(child) {
|
||
if (!child) {
|
||
return;
|
||
}
|
||
if (child.type.isVertex) {
|
||
g.setNode(child.key, {
|
||
label: child,
|
||
width: child.props.width,
|
||
height: child.props.height,
|
||
});
|
||
} else if (child.type.isEdge) {
|
||
const relationshipKey = child.props.source + ':' + child.props.target;
|
||
if (!edgeLabels[relationshipKey]) {
|
||
edgeLabels[relationshipKey] = [];
|
||
}
|
||
edgeLabels[relationshipKey].push(child);
|
||
}
|
||
});
|
||
|
||
Object.keys(edgeLabels).forEach(key => {
|
||
const children = edgeLabels[key];
|
||
const child = children[0];
|
||
g.setEdge(child.props.source, child.props.target, {
|
||
label: child,
|
||
allChildren: children.map(c => c.props.children),
|
||
weight: child.props.weight,
|
||
});
|
||
});
|
||
|
||
dagre.layout(g);
|
||
|
||
var activeNode = g
|
||
.nodes()
|
||
.map(v => g.node(v))
|
||
.find(node => node.label.props.isActive);
|
||
const [winX, winY] = [window.innerWidth / 2, window.innerHeight / 2];
|
||
var focusDx = trackActive && activeNode ? winX - activeNode.x : 0;
|
||
var focusDy = trackActive && activeNode ? winY - activeNode.y : 0;
|
||
|
||
var nodes = g.nodes().map(v => {
|
||
var node = g.node(v);
|
||
return (
|
||
<Motion
|
||
style={{
|
||
x: props.isDragging ? node.x + focusDx : spring(node.x + focusDx),
|
||
y: props.isDragging ? node.y + focusDy : spring(node.y + focusDy),
|
||
}}
|
||
key={node.label.key}>
|
||
{interpolatingStyle =>
|
||
React.cloneElement(node.label, {
|
||
x: interpolatingStyle.x + props.dx,
|
||
y: interpolatingStyle.y + props.dy,
|
||
vanillaX: node.x,
|
||
vanillaY: node.y,
|
||
})
|
||
}
|
||
</Motion>
|
||
);
|
||
});
|
||
|
||
var edges = g.edges().map(e => {
|
||
var edge = g.edge(e);
|
||
let idx = 0;
|
||
return (
|
||
<Motion
|
||
style={edge.points.reduce((bag, point) => {
|
||
bag[idx + ':x'] = props.isDragging
|
||
? point.x + focusDx
|
||
: spring(point.x + focusDx);
|
||
bag[idx + ':y'] = props.isDragging
|
||
? point.y + focusDy
|
||
: spring(point.y + focusDy);
|
||
idx++;
|
||
return bag;
|
||
}, {})}
|
||
key={edge.label.key}>
|
||
{interpolatedStyle => {
|
||
let points = [];
|
||
Object.keys(interpolatedStyle).forEach(key => {
|
||
const [idx, prop] = key.split(':');
|
||
if (!points[idx]) {
|
||
points[idx] = {x: props.dx, y: props.dy};
|
||
}
|
||
points[idx][prop] += interpolatedStyle[key];
|
||
});
|
||
return React.cloneElement(edge.label, {
|
||
points,
|
||
id: edge.label.key,
|
||
children: edge.allChildren.join(', '),
|
||
});
|
||
}}
|
||
</Motion>
|
||
);
|
||
});
|
||
|
||
return (
|
||
<div
|
||
style={{
|
||
position: 'relative',
|
||
height: '100%',
|
||
}}>
|
||
{edges}
|
||
{nodes}
|
||
</div>
|
||
);
|
||
}
|
||
|
||
function Vertex(props) {
|
||
if (Number.isNaN(props.x) || Number.isNaN(props.y)) {
|
||
return null;
|
||
}
|
||
|
||
return (
|
||
<div
|
||
style={{
|
||
position: 'absolute',
|
||
border: '1px solid black',
|
||
left: props.x - props.width / 2,
|
||
top: props.y - props.height / 2,
|
||
width: props.width,
|
||
height: props.height,
|
||
overflow: 'hidden',
|
||
padding: '4px',
|
||
wordWrap: 'break-word',
|
||
}}>
|
||
{props.children}
|
||
</div>
|
||
);
|
||
}
|
||
Vertex.isVertex = true;
|
||
|
||
const strokes = {
|
||
alt: 'blue',
|
||
child: 'green',
|
||
sibling: 'darkgreen',
|
||
return: 'red',
|
||
fx: 'purple',
|
||
};
|
||
|
||
function Edge(props) {
|
||
var points = props.points;
|
||
var path = 'M' + points[0].x + ' ' + points[0].y + ' ';
|
||
|
||
if (!points[0].x || !points[0].y) {
|
||
return null;
|
||
}
|
||
|
||
for (var i = 1; i < points.length; i++) {
|
||
path += 'L' + points[i].x + ' ' + points[i].y + ' ';
|
||
if (!points[i].x || !points[i].y) {
|
||
return null;
|
||
}
|
||
}
|
||
|
||
var lineID = props.id;
|
||
|
||
return (
|
||
<svg
|
||
width="100%"
|
||
height="100%"
|
||
style={{
|
||
position: 'absolute',
|
||
left: 0,
|
||
right: 0,
|
||
top: 0,
|
||
bottom: 0,
|
||
}}>
|
||
<defs>
|
||
<path d={path} id={lineID} />
|
||
<marker
|
||
id="markerCircle"
|
||
markerWidth="8"
|
||
markerHeight="8"
|
||
refX="5"
|
||
refY="5">
|
||
<circle cx="5" cy="5" r="3" style={{stroke: 'none', fill: 'black'}} />
|
||
</marker>
|
||
<marker
|
||
id="markerArrow"
|
||
markerWidth="13"
|
||
markerHeight="13"
|
||
refX="2"
|
||
refY="6"
|
||
orient="auto">
|
||
<path d="M2,2 L2,11 L10,6 L2,2" style={{fill: 'black'}} />
|
||
</marker>
|
||
</defs>
|
||
|
||
<use
|
||
xlinkHref={`#${lineID}`}
|
||
fill="none"
|
||
stroke={strokes[props.kind]}
|
||
style={{
|
||
markerStart: 'url(#markerCircle)',
|
||
markerEnd: 'url(#markerArrow)',
|
||
}}
|
||
/>
|
||
<text>
|
||
<textPath xlinkHref={`#${lineID}`}>
|
||
{' '}
|
||
{props.children}
|
||
</textPath>
|
||
</text>
|
||
</svg>
|
||
);
|
||
}
|
||
Edge.isEdge = true;
|
||
|
||
function formatPriority(priority) {
|
||
switch (priority) {
|
||
case 1:
|
||
return 'synchronous';
|
||
case 2:
|
||
return 'task';
|
||
case 3:
|
||
return 'hi-pri work';
|
||
case 4:
|
||
return 'lo-pri work';
|
||
case 5:
|
||
return 'offscreen work';
|
||
default:
|
||
throw new Error('Unknown priority.');
|
||
}
|
||
}
|
||
|
||
export default function Fibers({fibers, show, graphSettings, ...rest}) {
|
||
const items = Object.keys(fibers.descriptions).map(
|
||
id => fibers.descriptions[id]
|
||
);
|
||
|
||
const isDragging = rest.className.indexOf('dragging') > -1;
|
||
const [_, sdx, sdy] =
|
||
rest.style.transform.match(/translate\((-?\d+)px,(-?\d+)px\)/) || [];
|
||
const dx = Number(sdx);
|
||
const dy = Number(sdy);
|
||
|
||
return (
|
||
<div
|
||
{...rest}
|
||
style={{
|
||
width: '100%',
|
||
height: '100%',
|
||
position: 'absolute',
|
||
top: 0,
|
||
left: 0,
|
||
...rest.style,
|
||
transform: null,
|
||
}}>
|
||
<Graph
|
||
className="graph"
|
||
dx={dx}
|
||
dy={dy}
|
||
isDragging={isDragging}
|
||
settings={graphSettings}>
|
||
{items.map(fiber => [
|
||
<Vertex
|
||
key={fiber.id}
|
||
width={150}
|
||
height={100}
|
||
isActive={fiber.id === fibers.workInProgressID}>
|
||
<div
|
||
style={{
|
||
width: '100%',
|
||
height: '100%',
|
||
backgroundColor: getFiberColor(fibers, fiber.id),
|
||
}}
|
||
title={
|
||
/*prettyFormat(fiber, { plugins: [reactElement ]})*/
|
||
'todo: this was hanging last time I tried to pretty print'
|
||
}>
|
||
<small>
|
||
{fiber.tag} #{fiber.id}
|
||
</small>
|
||
<br />
|
||
{fiber.type}
|
||
<br />
|
||
{fibers.currentIDs.indexOf(fiber.id) === -1 ? (
|
||
<small>
|
||
{fiber.pendingWorkPriority !== 0 && [
|
||
<span key="span">
|
||
Needs: {formatPriority(fiber.pendingWorkPriority)}
|
||
</span>,
|
||
<br key="br" />,
|
||
]}
|
||
{fiber.memoizedProps !== null &&
|
||
fiber.pendingProps !== null && [
|
||
fiber.memoizedProps === fiber.pendingProps
|
||
? 'Can reuse memoized.'
|
||
: 'Cannot reuse memoized.',
|
||
<br key="br" />,
|
||
]}
|
||
</small>
|
||
) : (
|
||
<small>Committed</small>
|
||
)}
|
||
{fiber.effectTag && [
|
||
<br key="br" />,
|
||
<small key="small">Effect: {fiber.effectTag}</small>,
|
||
]}
|
||
</div>
|
||
</Vertex>,
|
||
fiber.child &&
|
||
show.child && (
|
||
<Edge
|
||
source={fiber.id}
|
||
target={fiber.child}
|
||
kind="child"
|
||
weight={1000}
|
||
key={`${fiber.id}-${fiber.child}-child`}>
|
||
child
|
||
</Edge>
|
||
),
|
||
fiber.sibling &&
|
||
show.sibling && (
|
||
<Edge
|
||
source={fiber.id}
|
||
target={fiber.sibling}
|
||
kind="sibling"
|
||
weight={2000}
|
||
key={`${fiber.id}-${fiber.sibling}-sibling`}>
|
||
sibling
|
||
</Edge>
|
||
),
|
||
fiber.return &&
|
||
show.return && (
|
||
<Edge
|
||
source={fiber.id}
|
||
target={fiber.return}
|
||
kind="return"
|
||
weight={1000}
|
||
key={`${fiber.id}-${fiber.return}-return`}>
|
||
return
|
||
</Edge>
|
||
),
|
||
fiber.nextEffect &&
|
||
show.fx && (
|
||
<Edge
|
||
source={fiber.id}
|
||
target={fiber.nextEffect}
|
||
kind="fx"
|
||
weight={100}
|
||
key={`${fiber.id}-${fiber.nextEffect}-nextEffect`}>
|
||
nextFx
|
||
</Edge>
|
||
),
|
||
fiber.firstEffect &&
|
||
show.fx && (
|
||
<Edge
|
||
source={fiber.id}
|
||
target={fiber.firstEffect}
|
||
kind="fx"
|
||
weight={100}
|
||
key={`${fiber.id}-${fiber.firstEffect}-firstEffect`}>
|
||
firstFx
|
||
</Edge>
|
||
),
|
||
fiber.lastEffect &&
|
||
show.fx && (
|
||
<Edge
|
||
source={fiber.id}
|
||
target={fiber.lastEffect}
|
||
kind="fx"
|
||
weight={100}
|
||
key={`${fiber.id}-${fiber.lastEffect}-lastEffect`}>
|
||
lastFx
|
||
</Edge>
|
||
),
|
||
fiber.alternate &&
|
||
show.alt && (
|
||
<Edge
|
||
source={fiber.id}
|
||
target={fiber.alternate}
|
||
kind="alt"
|
||
weight={10}
|
||
key={`${fiber.id}-${fiber.alternate}-alt`}>
|
||
alt
|
||
</Edge>
|
||
),
|
||
])}
|
||
</Graph>
|
||
</div>
|
||
);
|
||
}
|