closes SKY-155 - adds autopan to workflow editor (#2331)
This commit is contained in:
@@ -30,7 +30,7 @@ import {
|
|||||||
import "@xyflow/react/dist/style.css";
|
import "@xyflow/react/dist/style.css";
|
||||||
import { AxiosError } from "axios";
|
import { AxiosError } from "axios";
|
||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { useBlocker, useParams } from "react-router-dom";
|
import { useBlocker, useParams } from "react-router-dom";
|
||||||
import { stringify as convertToYAML } from "yaml";
|
import { stringify as convertToYAML } from "yaml";
|
||||||
import {
|
import {
|
||||||
@@ -85,6 +85,7 @@ import {
|
|||||||
startNode,
|
startNode,
|
||||||
} from "./workflowEditorUtils";
|
} from "./workflowEditorUtils";
|
||||||
import { parameterIsBitwardenCredential, ParametersState } from "./types";
|
import { parameterIsBitwardenCredential, ParametersState } from "./types";
|
||||||
|
import { useAutoPan } from "./useAutoPan";
|
||||||
|
|
||||||
function convertToParametersYAML(
|
function convertToParametersYAML(
|
||||||
parameters: ParametersState,
|
parameters: ParametersState,
|
||||||
@@ -494,6 +495,10 @@ function FlowRenderer({
|
|||||||
doLayout(newNodesWithUpdatedParameters, newEdges);
|
doLayout(newNodesWithUpdatedParameters, newEdges);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const editorElementRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
useAutoPan(editorElementRef, nodes);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Dialog
|
<Dialog
|
||||||
@@ -542,6 +547,7 @@ function FlowRenderer({
|
|||||||
>
|
>
|
||||||
<DeleteNodeCallbackContext.Provider value={deleteNode}>
|
<DeleteNodeCallbackContext.Provider value={deleteNode}>
|
||||||
<ReactFlow
|
<ReactFlow
|
||||||
|
ref={editorElementRef}
|
||||||
nodes={nodes}
|
nodes={nodes}
|
||||||
edges={edges}
|
edges={edges}
|
||||||
onNodesChange={(changes) => {
|
onNodesChange={(changes) => {
|
||||||
|
|||||||
58
skyvern-frontend/src/routes/workflows/editor/useAutoPan.ts
Normal file
58
skyvern-frontend/src/routes/workflows/editor/useAutoPan.ts
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import { useReactFlow } from "@xyflow/react";
|
||||||
|
import { useLayoutEffect } from "react";
|
||||||
|
|
||||||
|
import { AppNode } from "./nodes";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Some facts:
|
||||||
|
* - the workflow editor is rendered as an infinite canvas
|
||||||
|
* - nodes in the editor can have text fields
|
||||||
|
* - users type in those text fields
|
||||||
|
* - the browser will automatically attempt to scroll to a caret position when
|
||||||
|
* the caret leaves the viewport, because it is Being A Good Browser(tm)
|
||||||
|
* - this causes layout artifacts on the infinite canvas
|
||||||
|
*
|
||||||
|
* `useAutoPan` detects when the viewport is scrolled. (But it should never have
|
||||||
|
* a scroll, as it is an infinite canvas!) If a scroll value is detected, then
|
||||||
|
* we pan the viewport to counteract the scroll, and set the scroll to 0.
|
||||||
|
*
|
||||||
|
* The end result is that:
|
||||||
|
* - if a user is typing in any textual HTML element, and they scroll beyond
|
||||||
|
* the viewport, the viewport will animate-pan to counteract the scroll
|
||||||
|
* - if the user pastes large amounts of text into any textual HTML element,
|
||||||
|
* the viewport will animate-pan to counteract the scroll
|
||||||
|
*
|
||||||
|
* `editorElementRef`: a ref to the top-level editor element (the top-level div
|
||||||
|
* for react-flow, at time of writing)
|
||||||
|
*
|
||||||
|
* `nodes`: `AppNode`s; but could be anything that carries state indicative of a
|
||||||
|
* change in the editor
|
||||||
|
*/
|
||||||
|
const useAutoPan = (
|
||||||
|
editorElementRef: React.RefObject<HTMLDivElement>,
|
||||||
|
nodes: AppNode[],
|
||||||
|
) => {
|
||||||
|
const { setViewport, getViewport } = useReactFlow();
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
const editorElement = editorElementRef.current;
|
||||||
|
if (editorElement) {
|
||||||
|
const scrollTop = editorElement.scrollTop;
|
||||||
|
|
||||||
|
if (scrollTop === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
editorElement.scrollTop = 0;
|
||||||
|
const { x, y, zoom } = getViewport();
|
||||||
|
const panAmount = editorElement.clientHeight * 0.3;
|
||||||
|
|
||||||
|
setViewport(
|
||||||
|
{ x, y: y - (scrollTop + panAmount), zoom },
|
||||||
|
{ duration: 300 },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, [nodes, editorElementRef, setViewport, getViewport]);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { useAutoPan };
|
||||||
Reference in New Issue
Block a user