Building It Step By Step
Automatically Center Node
Learn how to automatically center the viewport on a node when relevant changes occur.
Getting Started
Navigate to the corresponding folder:
cd step-9-automatically-center-node
Install the dependencies:
npm install
Then run the project:
npm run dev
Automatically Center Node
When you open the application, you'll notice that after certain changes, such as adding a new element, the viewport automatically centers on the relevant node.
To center the viewport on a specific node, we use the center property, which defines the id of the node to center on, as shown below.
// src/shared/kits/workflow/hooks/use-workflow.ts
// ...
function init(schema: WorkflowSchema): State {
const workflow = toWorkflow(schema);
return {
history: {
workflows: [workflow],
pointer: 0,
current: workflow,
},
center: null,
};
}
export function useWorkflow(schema: WorkflowSchema): [State, Dispatch<Action>] {
return useReducer(reducer, schema, init);
}
Each call to applyWorkflowChange returns a center property that, if present, specifies the id of the node to center in the viewport. It is used to update the center property in the state, which in turn triggers the viewport adjustment.
// src/shared/kits/workflow/hooks/use-workflow.ts
// ...
function reducer(state: State, action: Action): State {
switch (action.type) {
/**
* Applies all incoming workflow changes. Each change may request the resulting
* workflow to be saved in the history (`memory`), in which case a new entry
* is added and any forward history (redo states) is discarded.
*/
case "applyWorkflowChanges": {
const workflows: Workflow[] = [...state.history.workflows];
let current: Workflow = state.history.current;
let pointer: number = state.history.pointer;
let center: string | null = null;
for (const change of action.changes) {
const output = applyWorkflowChange(change, current);
current = output.workflow;
if (output.memory) {
pointer++;
workflows.splice(pointer, workflows.length - pointer, {
...output.workflow,
widget: null,
active: null,
});
}
if (output.center) {
center = output.center;
}
}
return {
history: { workflows: workflows, pointer, current },
center: center,
};
}
// ...
}
}
// ...
The functionality to center a node in the viewport whenever the center property changes is handled by the useCenter hook, as shown below.
// src/shared/kits/workflow/components/workflow-view.tsx
// ...
export function useCenter(flows: TypedFlows, center: string | null) {
const { setCenter } = useFlows();
useEffect(() => {
if (center) {
const node = flows.nodes.get(center)!;
const x = node.positionAbsolute.x + node.size.w / 2;
const y = node.positionAbsolute.y + node.size.h / 2;
setCenter(x, y, {
zoom: constants.flows.center.zoom,
duration: constants.flows.center.duration,
});
}
}, [flows, center, setCenter]);
}
Within useCenter, we use the useFlows hook, which requires the FlowsProvider to be used higher in the component tree. In our case, it is set in the root layout.
// src/app/layout.tsx
// ...
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en" className="bg-neutral-950">
<body
className={`${nunitoSans.variable} ${jetBrainsMono.variable} bg-neutral-950 font-sans`}
>
<TooltipProvider>
<FlowsProvider>{children}</FlowsProvider>
</TooltipProvider>
</body>
</html>
);
}