* fix(ts): use strict mode strictNullChecks etc

* chore: Use extended React.HTMLAttributes<> (#41)

* refactor(code-format): add prettier closes #42

* feat(renderer): add snap to grid option closes #20

* chore(dependabot): use develop as target branch
This commit is contained in:
Moritz
2019-10-21 20:58:28 +02:00
committed by GitHub
parent ce210eb5dc
commit a793f8c2ff
80 changed files with 2634 additions and 1247 deletions
+1 -1
View File
@@ -7,7 +7,7 @@ const nodeStyles: CSSProperties = {
background: '#ff6060',
padding: 10,
borderRadius: 5,
width: 150
width: 150,
};
export default ({ data, style }: NodeProps) => (
+1 -1
View File
@@ -7,7 +7,7 @@ const nodeStyles: CSSProperties = {
background: '#9999ff',
padding: 10,
borderRadius: 5,
width: 150
width: 150,
};
export default ({ data, style }: NodeProps) => (
+1 -1
View File
@@ -7,7 +7,7 @@ const nodeStyles: CSSProperties = {
background: '#55dd99',
padding: 10,
borderRadius: 5,
width: 150
width: 150,
};
export default ({ data, style }: NodeProps) => (
+208 -106
View File
@@ -1,4 +1,11 @@
import React, { useEffect, useRef, useState, memo, ComponentType } from 'react';
import React, {
useEffect,
useRef,
useState,
memo,
ComponentType,
CSSProperties,
} from 'react';
import { DraggableCore, DraggableEvent } from 'react-draggable';
import cx from 'classnames';
import { ResizeObserver } from 'resize-observer';
@@ -6,7 +13,28 @@ import { ResizeObserver } from 'resize-observer';
import { getDimensions, isInputDOMNode } from '../../utils';
import { Provider } from '../../contexts/NodeIdContext';
import store from '../../store';
import { NodeComponentProps, Node, XYPosition, HandleElement, Position, Transform, ElementId } from '../../types';
import {
Node,
XYPosition,
HandleElement,
Position,
Transform,
ElementId,
NodeComponentProps,
} from '../../types';
interface WrapNodeProps {
id: ElementId;
type: string;
data: any;
selected: boolean;
transform: Transform;
xPos: number;
yPos: number;
onClick: (node: Node) => void | undefined;
onNodeDragStop: (node: Node) => void;
style?: CSSProperties;
}
const isHandle = (evt: MouseEvent | DraggableEvent) => {
const target = evt.target as HTMLElement;
@@ -14,165 +42,239 @@ const isHandle = (evt: MouseEvent | DraggableEvent) => {
return (
target.className &&
target.className.includes &&
(target.className.includes('source') || target.className.includes('target'))
(target.className.includes('source') || target.className.includes('target'))
);
};
const getHandleBounds = (
selector: string, nodeElement: HTMLDivElement, parentBounds: ClientRect | DOMRect, k: number
): HandleElement => {
selector: string,
nodeElement: HTMLDivElement,
parentBounds: ClientRect | DOMRect,
k: number
): HandleElement[] | null => {
const handles = nodeElement.querySelectorAll(selector);
if (!handles || !handles.length) {
return null;
}
return [].map.call(handles, (handle: HTMLDivElement): HandleElement => {
const bounds = handle.getBoundingClientRect();
const dimensions = getDimensions(handle);
const nodeIdAttr = handle.getAttribute('data-nodeid');
const handlePosition = handle.getAttribute('data-handlepos') as unknown as Position;
const nodeIdSplitted = nodeIdAttr.split('__');
const handlesArray = Array.from(handles) as HTMLDivElement[];
let handleId = null;
return handlesArray.map(
(handle): HandleElement => {
const bounds = handle.getBoundingClientRect();
const dimensions = getDimensions(handle);
const nodeIdAttr = handle.getAttribute('data-nodeid');
const handlePosition = (handle.getAttribute(
'data-handlepos'
) as unknown) as Position;
const nodeIdSplitted = nodeIdAttr ? nodeIdAttr.split('__') : null;
if (nodeIdSplitted) {
handleId = (nodeIdSplitted.length ? nodeIdSplitted[1] : nodeIdSplitted) as string;
let handleId = null;
if (nodeIdSplitted) {
handleId = (nodeIdSplitted.length
? nodeIdSplitted[1]
: nodeIdSplitted) as string;
}
return {
id: handleId,
position: handlePosition,
x: (bounds.left - parentBounds.left) * (1 / k),
y: (bounds.top - parentBounds.top) * (1 / k),
...dimensions,
};
}
return {
id: handleId,
position: handlePosition,
x: (bounds.left - parentBounds.left) * (1 / k),
y: (bounds.top - parentBounds.top) * (1 / k),
...dimensions
};
});
);
};
const onStart = (
evt: MouseEvent, onClick: (node: Node) => void, id: ElementId, type: string,
data: any, setOffset: (pos: XYPosition) => void, transform: Transform, position: XYPosition
): false | void => {
evt: MouseEvent,
onClick: (node: Node) => void,
id: ElementId,
type: string,
data: any,
setOffset: (pos: XYPosition) => void,
transform: Transform,
position: XYPosition
): false | void => {
if (isInputDOMNode(evt) || isHandle(evt)) {
return false;
}
const scaledClient: XYPosition = {
x: evt.clientX * (1 / transform[2]),
y: evt.clientY * (1 / transform[2])
y: evt.clientY * (1 / transform[2]),
};
const offsetX = scaledClient.x - position.x - transform[0];
const offsetY = scaledClient.y - position.y - transform[1];
const node = { id, type, position, data };
const node = { id, type, position, data };
store.dispatch.setSelectedElements({ id, type });
store.dispatch.setSelectedElements({ id, type } as Node);
setOffset({ x: offsetX, y: offsetY });
onClick(node);
};
const onDrag = (
evt: MouseEvent, setDragging: (isDragging: boolean) => void, id: ElementId, offset: XYPosition,
evt: MouseEvent,
setDragging: (isDragging: boolean) => void,
id: ElementId,
offset: XYPosition,
transform: Transform
): void => {
const scaledClient = {
x: evt.clientX * (1 / transform[2]),
y: evt.clientY * (1 / transform[2])
y: evt.clientY * (1 / transform[2]),
};
setDragging(true);
store.dispatch.updateNodePos({ id, pos: {
x: scaledClient.x - transform[0] - offset.x,
y: scaledClient.y - transform[1] - offset.y
}});
store.dispatch.updateNodePos({
id,
pos: {
x: scaledClient.x - transform[0] - offset.x,
y: scaledClient.y - transform[1] - offset.y,
},
});
};
const onStop = (
onNodeDragStop: (params: Node) => void, isDragging: boolean, setDragging: (isDragging: boolean) => void, id: ElementId,
type: string, position: XYPosition, data: any
onNodeDragStop: (params: Node) => void,
isDragging: boolean,
setDragging: (isDragging: boolean) => void,
id: ElementId,
type: string,
position: XYPosition,
data: any
): void => {
if (isDragging) {
setDragging(false);
onNodeDragStop({
id, type, position, data
});
id,
type,
position,
data,
} as Node);
}
};
export default (NodeComponent: ComponentType<NodeComponentProps>) => {
const NodeWrapper = memo(({
id, type, data, transform,
xPos, yPos, selected, onClick,
onNodeDragStop, style
}: NodeComponentProps) => {
const nodeElement = useRef<HTMLDivElement>(null);
const [offset, setOffset] = useState({ x: 0, y: 0 });
const [isDragging, setDragging] = useState(false);
const NodeWrapper = memo(
({
id,
type,
data,
transform,
xPos,
yPos,
selected,
onClick,
onNodeDragStop,
style,
}: WrapNodeProps) => {
const nodeElement = useRef<HTMLDivElement>(null);
const [offset, setOffset] = useState({ x: 0, y: 0 });
const [isDragging, setDragging] = useState(false);
const position = { x: xPos, y: yPos };
const nodeClasses = cx('react-flow__node', { selected });
const nodeStyle = { zIndex: selected ? 10 : 3, transform: `translate(${xPos}px,${yPos}px)` };
const updateNode = () => {
if (!nodeElement.current) {
return false;
}
const storeState = store.getState()
const bounds = nodeElement.current.getBoundingClientRect();
const dimensions = getDimensions(nodeElement.current);
const handleBounds = {
source: getHandleBounds('.source', nodeElement.current, bounds, storeState.transform[2]),
target: getHandleBounds('.target', nodeElement.current, bounds, storeState.transform[2])
const position = { x: xPos, y: yPos };
const nodeClasses = cx('react-flow__node', { selected });
const nodeStyle = {
zIndex: selected ? 10 : 3,
transform: `translate(${xPos}px,${yPos}px)`,
};
store.dispatch.updateNodeData({ id, ...dimensions, handleBounds });
};
useEffect(() => {
if (nodeElement.current) {
updateNode();
const resizeObserver = new ResizeObserver(entries => {
for (let _ of entries) {
updateNode();
}
});
resizeObserver.observe(nodeElement.current);
return () => {
if (resizeObserver && nodeElement.current) {
resizeObserver.unobserve(nodeElement.current);
}
const updateNode = (): void => {
if (!nodeElement.current) {
return;
}
}
}, [nodeElement.current]);
return (
<DraggableCore
onStart={evt => onStart(evt as MouseEvent, onClick, id, type, data, setOffset, transform, position)}
onDrag={evt => onDrag(evt as MouseEvent, setDragging, id, offset, transform)}
onStop={() => onStop(onNodeDragStop, isDragging, setDragging, id, type, position, data)}
scale={transform[2]}
>
<div
className={nodeClasses}
ref={nodeElement}
style={nodeStyle}
const storeState = store.getState();
const bounds = nodeElement.current.getBoundingClientRect();
const dimensions = getDimensions(nodeElement.current);
const handleBounds = {
source: getHandleBounds(
'.source',
nodeElement.current,
bounds,
storeState.transform[2]
),
target: getHandleBounds(
'.target',
nodeElement.current,
bounds,
storeState.transform[2]
),
};
store.dispatch.updateNodeData({ id, ...dimensions, handleBounds });
};
useEffect(() => {
if (nodeElement.current) {
updateNode();
const resizeObserver = new ResizeObserver(entries => {
for (let _ of entries) {
updateNode();
}
});
resizeObserver.observe(nodeElement.current);
return () => {
if (resizeObserver && nodeElement.current) {
resizeObserver.unobserve(nodeElement.current);
}
};
}
return;
}, [nodeElement.current]);
return (
<DraggableCore
onStart={evt =>
onStart(
evt as MouseEvent,
onClick,
id,
type,
data,
setOffset,
transform,
position
)
}
onDrag={evt =>
onDrag(evt as MouseEvent, setDragging, id, offset, transform)
}
onStop={() =>
onStop(
onNodeDragStop,
isDragging,
setDragging,
id,
type,
position,
data
)
}
scale={transform[2]}
>
<Provider value={id}>
<NodeComponent
id={id}
data={data}
type={type}
style={style}
selected={selected}
/>
</Provider>
</div>
</DraggableCore>
);
});
<div className={nodeClasses} ref={nodeElement} style={nodeStyle}>
<Provider value={id}>
<NodeComponent
id={id}
data={data}
type={type}
style={style}
selected={selected}
/>
</Provider>
</div>
</DraggableCore>
);
}
);
NodeWrapper.displayName = 'NodeWrapper';