refactor(app): folder structure, components cleanup

This commit is contained in:
moklick
2019-10-06 19:01:02 +02:00
parent 845bae284f
commit cc2775dd78
25 changed files with 822 additions and 981 deletions
+187
View File
@@ -0,0 +1,187 @@
import React, { memo } from 'react';
import { useStoreState } from 'easy-peasy';
import ConnectionLine from '../../components/ConnectionLine';
import { isEdge } from '../../graph-utils';
function getHandlePosition(position, node, handle = null) {
if (!handle) {
switch (position) {
case 'top': return {
x: node.__rg.width / 2,
y: 0
};
case 'right': return {
x: node.__rg.width,
y: node.__rg.height / 2
};
case 'bottom': return {
x: node.__rg.width / 2,
y: node.__rg.height
};
case 'left': return {
x: 0,
y: node.__rg.height / 2
};
}
}
switch (position) {
case 'top': return {
x: handle.x + (handle.width / 2),
y: handle.y
};
case 'right': return {
x: handle.x + handle.width,
y: handle.y + (handle.height / 2)
};
case 'bottom': return {
x: handle.x + (handle.width / 2),
y: handle.y + handle.height
};
case 'left': return {
x: handle.x,
y: handle.y + (handle.height / 2)
};
}
}
function getHandle(bounds, handleId) {
let handle = null;
if (!bounds) {
return null;
}
// there is no handleId when there are no multiple handles/ handles with ids
// so we just pick the first one
if (bounds.length === 1 || !handleId) {
handle = bounds[0];
} else if (handleId) {
handle = bounds.find(d => d.id === handleId);
}
return handle;
}
function getEdgePositions({ sourceNode, sourceHandle, sourcePosition, targetNode, targetHandle, targetPosition }) {
const sourceHandlePos = getHandlePosition(sourcePosition, sourceNode, sourceHandle)
const sourceX = sourceNode.__rg.position.x + sourceHandlePos.x;
const sourceY = sourceNode.__rg.position.y + sourceHandlePos.y;
const targetHandlePos = getHandlePosition(targetPosition, targetNode, targetHandle);
const targetX = targetNode.__rg.position.x + targetHandlePos.x;
const targetY = targetNode.__rg.position.y + targetHandlePos.y;
return {
sourceX, sourceY, targetX, targetY
};
}
function renderEdge(e, props, state) {
const edgeType = e.type || 'default';
const hasSourceHandleId = e.source.includes('__');
const hasTargetHandleId = e.target.includes('__');
const sourceId = hasSourceHandleId ? e.source.split('__')[0] : e.source;
const targetId = hasTargetHandleId ? e.target.split('__')[0] : e.target;
const sourceHandleId = hasSourceHandleId ? e.source.split('__')[1] : null;
const targetHandleId = hasTargetHandleId ? e.target.split('__')[1] : null;
const sourceNode = state.nodes.find(n => n.id === sourceId);
const targetNode = state.nodes.find(n => n.id === targetId);
if (!sourceNode) {
throw new Error(`couldn't create edge for source id: ${sourceId}`);
}
if (!targetNode) {
throw new Error(`couldn't create edge for target id: ${targetId}`);
}
const EdgeComponent = props.edgeTypes[edgeType] || props.edgeTypes.default;
const sourceHandle = getHandle(sourceNode.__rg.handleBounds.source, sourceHandleId);
const targetHandle = getHandle(targetNode.__rg.handleBounds.target, targetHandleId);
const sourcePosition = sourceHandle ? sourceHandle.position : 'bottom';
const targetPosition = targetHandle ? targetHandle.position : 'top';
const { sourceX, sourceY, targetX, targetY } = getEdgePositions({
sourceNode, sourceHandle, sourcePosition,
targetNode, targetHandle, targetPosition
});
const selected = state.selectedElements
.filter(isEdge)
.find(elm => elm.source === sourceId && elm.target === targetId);
return (
<EdgeComponent
key={e.id}
id={e.id}
type={e.type}
onClick={props.onElementClick}
selected={selected}
animated={e.animated}
style={e.style}
source={sourceId}
target={targetId}
sourceHandleId={sourceHandleId}
targetHandleId={targetHandleId}
sourceX={sourceX}
sourceY={sourceY}
targetX={targetX}
targetY={targetY}
sourcePosition={sourcePosition}
targetPosition={targetPosition}
/>
);
}
const EdgeRenderer = memo((props) => {
const state = useStoreState(s => ({
nodes: s.nodes,
edges: s.edges,
transform: s.transform,
selectedElements: s.selectedElements,
connectionSourceId: s.connectionSourceId,
position: s.connectionPosition
}));
const {
width, height, connectionLineStyle, connectionLineType
} = props;
if (!width) {
return null;
}
const { transform, edges, nodes, connectionSourceId, position } = state;
const transformStyle = `translate(${transform[0]},${transform[1]}) scale(${transform[2]})`;
return (
<svg
width={width}
height={height}
className="react-graph__edges"
>
<g transform={transformStyle}>
{edges.map(e => renderEdge(e, props, state))}
{connectionSourceId && (
<ConnectionLine
nodes={nodes}
connectionSourceId={connectionSourceId}
connectionPositionX={position.x}
connectionPositionY={position.y}
transform={transform}
connectionLineStyle={connectionLineStyle}
connectionLineType={connectionLineType}
/>
)}
</g>
</svg>
);
});
EdgeRenderer.displayName = 'EdgeRenderer';
export default EdgeRenderer;
+24
View File
@@ -0,0 +1,24 @@
import StraightEdge from '../../components/Edges/StraightEdge';
import BezierEdge from '../../components/Edges/BezierEdge';
import wrapEdge from '../../components/Edges/wrapEdge';
export function createEdgeTypes(edgeTypes) {
const standardTypes = {
default: wrapEdge(edgeTypes.default || BezierEdge),
straight: wrapEdge(edgeTypes.bezier || StraightEdge)
};
const specialTypes = Object
.keys(edgeTypes)
.filter(k => !['default', 'bezier'].includes(k))
.reduce((res, key) => {
res[key] = wrapEdge(edgeTypes[key] ||BezierEdge);
return res;
}, {});
return {
...standardTypes,
...specialTypes
};
}
+96
View File
@@ -0,0 +1,96 @@
import React, { useEffect, useRef, memo } from 'react';
import { useStoreState, useStoreActions } from 'easy-peasy';
import NodeRenderer from '../NodeRenderer';
import EdgeRenderer from '../EdgeRenderer';
import UserSelection from '../../components/UserSelection';
import NodesSelection from '../../components/NodesSelection';
import useKeyPress from '../../hooks/useKeyPress';
import useD3Zoom from '../../hooks/useD3Zoom';
import useGlobalKeyHandler from '../../hooks/useGlobalKeyHandler';
import useElementUpdater from '../../hooks/useElementUpdater'
import { getDimensions } from '../../utils';
import { fitView, zoomIn, zoomOut } from '../../graph-utils';
const GraphView = memo(({
nodeTypes, edgeTypes, onMove, onLoad,
onElementClick, onNodeDragStop, connectionLineType, connectionLineStyle,
selectionKeyCode, onElementsRemove, deleteKeyCode, elements,
onConnect
}) => {
const zoomPane = useRef();
const rendererNode = useRef();
const state = useStoreState(s => ({
width: s.width,
height: s.height,
nodes: s.nodes,
edges: s.edges,
d3Initialised: s.d3Initialised,
nodesSelectionActive: s.nodesSelectionActive
}));
const updateSize = useStoreActions(actions => actions.updateSize);
const setNodesSelection = useStoreActions(actions => actions.setNodesSelection);
const setOnConnect = useStoreActions(a => a.setOnConnect);
const selectionKeyPressed = useKeyPress(selectionKeyCode);
const onZoomPaneClick = () => setNodesSelection({ isActive: false });
const updateDimensions = () => {
const size = getDimensions(rendererNode.current);
updateSize(size);
};
useEffect(() => {
updateDimensions();
setOnConnect(onConnect);
window.onresize = updateDimensions;
return () => {
window.onresize = null;
};
}, []);
useD3Zoom(zoomPane, onMove, selectionKeyPressed);
useEffect(() => {
if (state.d3Initialised) {
onLoad({
fitView,
zoomIn,
zoomOut
});
}
}, [state.d3Initialised]);
useGlobalKeyHandler({ onElementsRemove, deleteKeyCode });
useElementUpdater({ elements });
return (
<div className="react-graph__renderer" ref={rendererNode}>
<NodeRenderer
nodeTypes={nodeTypes}
onElementClick={onElementClick}
onNodeDragStop={onNodeDragStop}
/>
<EdgeRenderer
width={state.width}
height={state.height}
edgeTypes={edgeTypes}
onElementClick={onElementClick}
connectionLineType={connectionLineType}
connectionLineStyle={connectionLineStyle}
/>
{selectionKeyPressed && <UserSelection />}
{state.nodesSelectionActive && <NodesSelection />}
<div
className="react-graph__zoompane"
onClick={onZoomPaneClick}
ref={zoomPane}
/>
</div>
);
});
GraphView.displayName = 'GraphView';
export default GraphView;
+59
View File
@@ -0,0 +1,59 @@
import React, { memo } from 'react';
import { useStoreState } from 'easy-peasy';
import { isNode } from '../../graph-utils';
function renderNode(d, props, state) {
const nodeType = d.type || 'default';
if (!props.nodeTypes[nodeType]) {
console.warn(`No node type found for type "${nodeType}". Using fallback type "default".`);
}
const NodeComponent = props.nodeTypes[nodeType] || props.nodeTypes.default;
const selected = state.selectedElements
.filter(isNode)
.map(e => e.id)
.includes(d.id);
return (
<NodeComponent
key={d.id}
id={d.id}
type={d.type}
data={d.data}
xPos={d.__rg.position.x}
yPos={d.__rg.position.y}
onClick={props.onElementClick}
onNodeDragStop={props.onNodeDragStop}
transform={state.transform}
selected={selected}
style={d.style}
/>
);
}
const NodeRenderer = memo((props) => {
const state = useStoreState(s => ({
nodes: s.nodes,
transform: s.transform,
selectedElements: s.selectedElements
}));
const { transform, nodes } = state;
const transformStyle = { transform : `translate(${transform[0]}px,${transform[1]}px) scale(${transform[2]})` };
return (
<div
className="react-graph__nodes"
style={transformStyle}
>
{nodes.map(d => renderNode(d, props, state))}
</div>
);
});
NodeRenderer.displayName = 'NodeRenderer';
NodeRenderer.whyDidYouRender = false;
export default NodeRenderer;
+26
View File
@@ -0,0 +1,26 @@
import DefaultNode from '../../components/Nodes/DefaultNode';
import InputNode from '../../components/Nodes/InputNode';
import OutputNode from '../../components/Nodes/OutputNode';
import wrapNode from '../../components/Nodes/wrapNode';
export function createNodeTypes(nodeTypes) {
const standardTypes = {
input: wrapNode(nodeTypes.input || InputNode),
default: wrapNode(nodeTypes.default || DefaultNode),
output: wrapNode(nodeTypes.output || OutputNode)
};
const specialTypes = Object
.keys(nodeTypes)
.filter(k => !['input', 'default', 'output'].includes(k))
.reduce((res, key) => {
res[key] = wrapNode(nodeTypes[key] || DefaultNode);
return res;
}, {});
return {
...standardTypes,
...specialTypes
};
}