develop (#43)
* 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:
@@ -7,7 +7,7 @@ const nodeStyles: CSSProperties = {
|
||||
background: '#ff6060',
|
||||
padding: 10,
|
||||
borderRadius: 5,
|
||||
width: 150
|
||||
width: 150,
|
||||
};
|
||||
|
||||
export default ({ data, style }: NodeProps) => (
|
||||
|
||||
@@ -7,7 +7,7 @@ const nodeStyles: CSSProperties = {
|
||||
background: '#9999ff',
|
||||
padding: 10,
|
||||
borderRadius: 5,
|
||||
width: 150
|
||||
width: 150,
|
||||
};
|
||||
|
||||
export default ({ data, style }: NodeProps) => (
|
||||
|
||||
@@ -7,7 +7,7 @@ const nodeStyles: CSSProperties = {
|
||||
background: '#55dd99',
|
||||
padding: 10,
|
||||
borderRadius: 5,
|
||||
width: 150
|
||||
width: 150,
|
||||
};
|
||||
|
||||
export default ({ data, style }: NodeProps) => (
|
||||
|
||||
+208
-106
@@ -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';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user