Closed CamboimGabriel closed 3 years ago
I believe you need to wrap your ReactFlow
with a ReactFlowProvider
: https://reactflow.dev/examples/provider/
But I'm already with provider
import React, { useState, useRef, useCallback, useEffect } from "react";
import ReactFlow, {
Controls,
addEdge,
removeElements,
ReactFlowProvider,
Background,
isNode,
useStoreState,
useZoomPanHelper,
MiniMap,
} from "react-flow-renderer";
import { Sidebar } from "../playground/TestComponents";
import "../styles/styles.css";
import ConditionalNode from "../playground/ConditionalNode";
import { v4 as uuid } from "uuid";
import ConditionalEndNode from "../playground/ConditionalEndNode";
import ParallelNode from "../playground/ParallelNode";
import ParallelEndNode from "../playground/ParallelEndNode";
import TaskNode from "../playground/TaskNode";
import EventStartNode from "../playground/EventStartNode";
import EventEndNode from "../playground/EventEndNode";
import dagre from "dagre";
import { useHistory } from "react-router-dom";
import { connect, useDispatch, useSelector } from "react-redux";
import {
addHandleClick,
addLayoutElements,
connectElement,
dropElement,
editFlowTitle,
handleAddModelFlow,
handleEditModelFlow,
removeElement,
updateElement,
} from "../actions/flows";
import CustomEdge from "../playground/CustomEdge";
import Popover from "@material-ui/core/Popover";
import { findFlow } from "../selectors/flows";
import { fetchFlows } from "../reducers/flows";
const dagreGraph = new dagre.graphlib.Graph();
dagreGraph.setDefaultEdgeLabel(() => ({}));
const getLayoutedElements = (
elements,
direction = elements[0].sourcePosition === "bottom" ? "TB" : "LR"
) => {
const clone = JSON.parse(JSON.stringify(elements));
const isHorizontal = direction === "LR";
dagreGraph.setGraph({ rankdir: direction });
clone.forEach((el) => {
if (isNode(el)) {
dagreGraph.setNode(el.id, { width: 150, height: 50 });
} else {
dagreGraph.setEdge(el.source, el.target);
}
});
dagre.layout(dagreGraph);
return clone.map((el) => {
if (isNode(el)) {
const nodeWithPosition = dagreGraph.node(el.id);
el.targetPosition = isHorizontal ? "left" : "top";
el.sourcePosition = isHorizontal ? "right" : "bottom";
el.position = {
x: nodeWithPosition.x + Math.random() / 1000,
y: nodeWithPosition.y,
};
}
return el;
});
};
const FlowEditionPage = ({ location }) => {
const params = new URLSearchParams(location.search);
const flow = useSelector((state) =>
findFlow(state.flows.entities, params.get("flowId"))
);
const dispatch = useDispatch();
const [error, setError] = useState("");
const [edgeId, setEdgeId] = useState("");
const [nodeId, setNodeId] = useState("");
const history = useHistory();
const [text, setText] = useState("Padrão");
const [anchorEl, setAnchorEl] = React.useState(null);
const open = Boolean(anchorEl);
const id = open ? "simple-popover" : undefined;
const elements = flow && flow.elements;
const flowId = flow && flow._id;
const reactFlowWrapper = useRef(null);
const [reactFlowInstance, setReactFlowInstance] = useState(null);
const flowStatus = useSelector((state) => state.flows.status);
const handleClick = (event, id, edges, nodes) => {
const test =
nodes.find((e) => e.id === id) !== undefined
? nodes.find((e) => e.id === id)
: edges.find((e) => e.id === id);
if (isNode(test, elements)) {
setText(test.data.label);
setNodeId(id);
setEdgeId(null);
} else {
setText(test.data.text);
setEdgeId(id);
setNodeId(null);
}
setAnchorEl(event.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};
useEffect(() => {
dispatch(updateElement({ nodeId, edgeId, text, _id: flowId }));
}, [dispatch, edgeId, flowId, nodeId, text]);
const onConnect = (params) => {
if (elements.find((e) => e.id === params.source).type === "conditional") {
if (params.sourceHandle === "a")
dispatch(
connectElement({
_id: flowId,
params: {
...params,
type: "customEdge",
data: { handleClick, text: "Etiqueta 1" },
},
})
);
else
dispatch(
connectElement({
_id: flowId,
params: {
...params,
type: "customEdge",
data: { handleClick, text: "Etiqueta 2" },
},
})
);
} else
dispatch(
connectElement({
_id: flowId,
params,
})
);
};
const onElementsRemove = (elementsToRemove) =>
dispatch(removeElement({ elementsToRemove, _id: flowId }));
const onLoad = (_reactFlowInstance) => {
_reactFlowInstance.fitView();
setReactFlowInstance(_reactFlowInstance);
};
const onDragOver = (event) => {
event.preventDefault();
event.dataTransfer.dropEffect = "move";
};
const onDrop = (event) => {
event.preventDefault();
const reactFlowBounds = reactFlowWrapper.current.getBoundingClientRect();
const type = event.dataTransfer.getData("application/reactflow");
const position = reactFlowInstance.project({
x: event.clientX - reactFlowBounds.left,
y: event.clientY - reactFlowBounds.top,
});
const newNode = {
id: uuid(),
type,
position,
data: {
label:
type === "conditional"
? "Início da condicional"
: type === "eventEnd"
? "Evento de fim"
: type === "eventStart"
? "Evento de início"
: type === "task"
? "Tarefa"
: type === "conditionalEnd"
? "Fim da condicional"
: type === "parallel"
? "Paralelo"
: type === "parallelEnd"
? "Fim do paralelo"
: null,
status: "model",
handleClick,
expiration: {
number: 5,
time: "days",
},
subtasks: [],
},
};
dispatch(dropElement({ _id: flowId, newNode }));
};
const onLayout = useCallback(
(direction) => {
const layoutedElements = getLayoutedElements(elements, direction);
dispatch(
addLayoutElements({ layoutedElements, _id: params.get("flowId") })
);
},
[dispatch, elements, params]
);
const nodeTypes = {
conditional: ConditionalNode,
conditionalEnd: ConditionalEndNode,
parallel: ParallelNode,
parallelEnd: ParallelEndNode,
task: TaskNode,
eventStart: EventStartNode,
eventEnd: EventEndNode,
};
const edgeTypes = {
customEdge: CustomEdge,
};
useEffect(() => {
dispatch(fetchFlows());
dispatch(addHandleClick({ handleClick, _id: params.get("flowId") }));
}, [dispatch]);
console.log(flowStatus);
return flowStatus === "succeeded" ? (
<div className="dndflow">
<ReactFlowProvider>
<div className="reactflow-wrapper" ref={reactFlowWrapper}>
<ReactFlow
elements={elements}
onConnect={onConnect}
onElementsRemove={onElementsRemove}
onLoad={onLoad}
onDrop={onDrop}
onDragOver={onDragOver}
nodeTypes={nodeTypes}
edgeTypes={edgeTypes}
>
<Controls />
<Background
style={{ backgroundColor: "#fff4eb" }}
size={1}
variant="lines"
/>
<MiniMap />
</ReactFlow>
</div>
<Popover
id={id}
open={open}
anchorEl={anchorEl}
onClose={handleClose}
anchorOrigin={{
vertical: "bottom",
horizontal: "center",
}}
transformOrigin={{
vertical: "top",
horizontal: "center",
}}
>
<input
value={text}
onChange={(text) => setText(text.target.value)}
></input>
</Popover>
<Sidebar />
<div className="column">
<button
onClick={() => {
onLayout("TB");
reactFlowInstance.fitView();
}}
>
vertical layout
</button>
<button
onClick={() => {
onLayout("LR");
reactFlowInstance.fitView();
}}
>
horizontal layout
</button>
<h3>ALTERAR TITULO DO FLUXO</h3>
<input
value={flow.title}
onChange={(text) =>
dispatch(
editFlowTitle({
title: text.target.value,
flowId: params.get("flowId"),
})
)
}
></input>
<p style={{ color: "red" }}>{error === "" ? null : error}</p>
<button
className="roww"
onClick={() => {
if (flow.title === "") {
return setError("Necessário preencher o título");
} else
return dispatch(
handleEditModelFlow(
reactFlowInstance.getElements(),
flow.title,
flowId
)
).then(() => history.push("/"));
}}
>
Editar flow
</button>
</div>
</ReactFlowProvider>
</div>
) : (
<div>wait</div>
);
};
export default FlowEditionPage;
Hey @CamboimGabriel
at which point do you want to call fitView
exactly? In the FlowEditionPage
this wouldn't be possible (because that's the provider component) but in all children it should work.
Oh, I want to call fitView right after click in vertical/horizontal layout buttons, so what can I do to call fitView in same page?
You would need to put the buttons in their own component. In that component you can use the useZoomPanHelper
hook.
I put fit view in a component but doesnt seem to work
Before:
After click:
Here is the code:
import React from "react";
import { useZoomPanHelper } from "react-flow-renderer";
const LayoutButton = ({ title, onClick }) => {
const { fitView } = useZoomPanHelper();
return (
<>
<button
onClick={() => {
onClick();
fitView();
}}
>
{title}
</button>
</>
);
};
export default LayoutButton;
Is this a problem with vertical/horizontal layout example?
I think the fitView gets called during/before the node positions are updated.
I'm trying to call after but didnt get sucess about it, do u have an idea on how I can do this?
The layouting task is asynchronous. When you call fitView
the new layout is not applied yet. I think it would be nice to have a getTransformByElements
/ getViewportByElements
helper function for this case.
Hey @CamboimGabriel Could you solve this?
Hey @moklick, I removed the horizontal / vertical shift feature from my app, so I didn't have to worry about it anymore, at least atm
I tried to resolve but didnt get success
In case someone is asking the same question, you have to put the proivder in the parent file.
Example: <ReactFlowProvider><FlowEditionPage /></ReactFlowProvider>
Hi, anybody knows how to use some function after fitView updates the layout?
You can just use an useEffect and put fitView in the dependancies
Hi, I'm trying just to call fitView and this error appears, trying search but found nothing, what can I do?
I put just this line of code:
const { fitView } = useZoomPanHelper();