Display edge button when whole edge is hovered (#13651)
As title ## Before Need to aim center of edge https://github.com/user-attachments/assets/5f014f15-af7b-4884-902d-3b7001782d2d ## After Whole edge triggers hover behavior https://github.com/user-attachments/assets/bddaa6b2-7d62-428a-bee5-c237db16a403
This commit is contained in:
+16
@@ -53,6 +53,7 @@ import { WorkflowDiagramRightClickCommandMenu } from '@/workflow/workflow-diagra
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { FeatureFlagKey } from '~/generated/graphql';
|
||||
import { getOrganizedDiagram } from '@/workflow/workflow-diagram/utils/getOrganizedDiagram';
|
||||
import { useIsEdgeHovered } from '@/workflow/workflow-diagram/hooks/useIsEdgeHovered';
|
||||
|
||||
const StyledResetReactflowStyles = styled.div`
|
||||
height: 100%;
|
||||
@@ -188,6 +189,8 @@ export const WorkflowDiagramCanvasBase = ({
|
||||
workflowDiagramWaitingNodesDimensionsComponentState,
|
||||
);
|
||||
|
||||
const { setEdgeHovered, setNoEdgeHovered } = useIsEdgeHovered();
|
||||
|
||||
const isWorkflowBranchEnabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IS_WORKFLOW_BRANCH_ENABLED,
|
||||
);
|
||||
@@ -436,6 +439,17 @@ export const WorkflowDiagramCanvasBase = ({
|
||||
[handlePaneContextMenu],
|
||||
);
|
||||
|
||||
const onEdgeMouseEnter = useCallback(
|
||||
(_: React.MouseEvent<Element, MouseEvent>, edge: WorkflowDiagramEdge) => {
|
||||
setEdgeHovered(edge.id);
|
||||
},
|
||||
[setEdgeHovered],
|
||||
);
|
||||
|
||||
const onEdgeMouseLeave = useCallback(() => {
|
||||
setNoEdgeHovered();
|
||||
}, [setNoEdgeHovered]);
|
||||
|
||||
return (
|
||||
<StyledResetReactflowStyles ref={containerRef}>
|
||||
<WorkflowDiagramCustomMarkers />
|
||||
@@ -449,6 +463,8 @@ export const WorkflowDiagramCanvasBase = ({
|
||||
edgeTypes={edgeTypes}
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
onEdgeMouseEnter={onEdgeMouseEnter}
|
||||
onEdgeMouseLeave={onEdgeMouseLeave}
|
||||
onNodesChange={handleNodesChanges}
|
||||
onEdgesChange={handleEdgesChange}
|
||||
onConnect={isWorkflowBranchEnabled ? onConnect : undefined}
|
||||
|
||||
+6
-8
@@ -17,10 +17,10 @@ import {
|
||||
EdgeProps,
|
||||
getBezierPath,
|
||||
} from '@xyflow/react';
|
||||
import { useState } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { IconFilter, IconPlus } from 'twenty-ui/display';
|
||||
import { IconButtonGroup } from 'twenty-ui/input';
|
||||
import { useIsEdgeHovered } from '@/workflow/workflow-diagram/hooks/useIsEdgeHovered';
|
||||
|
||||
type WorkflowDiagramDefaultEdgeEditableProps = EdgeProps<WorkflowDiagramEdge>;
|
||||
|
||||
@@ -29,6 +29,7 @@ const StyledIconButtonGroup = styled(IconButtonGroup)`
|
||||
`;
|
||||
|
||||
export const WorkflowDiagramDefaultEdgeEditable = ({
|
||||
id,
|
||||
source,
|
||||
target,
|
||||
sourceX,
|
||||
@@ -40,6 +41,8 @@ export const WorkflowDiagramDefaultEdgeEditable = ({
|
||||
}: WorkflowDiagramDefaultEdgeEditableProps) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const { isEdgeHovered } = useIsEdgeHovered();
|
||||
|
||||
const [edgePath, labelX, labelY] = getBezierPath({
|
||||
sourceX,
|
||||
sourceY,
|
||||
@@ -53,9 +56,8 @@ export const WorkflowDiagramDefaultEdgeEditable = ({
|
||||
const workflow = useWorkflowWithCurrentVersion(workflowVisualizerWorkflowId);
|
||||
|
||||
const { createStep } = useCreateStep({ workflow });
|
||||
const { startNodeCreation } = useStartNodeCreation();
|
||||
|
||||
const [hovered, setHovered] = useState(false);
|
||||
const { startNodeCreation } = useStartNodeCreation();
|
||||
|
||||
const workflowInsertStepIds = useRecoilComponentValueV2(
|
||||
workflowInsertStepIdsComponentState,
|
||||
@@ -83,8 +85,6 @@ export const WorkflowDiagramDefaultEdgeEditable = ({
|
||||
stepId: createdStep.id,
|
||||
stepName: createdStep.name,
|
||||
});
|
||||
|
||||
setHovered(false);
|
||||
};
|
||||
|
||||
const handleNodeButtonClick = () => {
|
||||
@@ -109,11 +109,9 @@ export const WorkflowDiagramDefaultEdgeEditable = ({
|
||||
data-click-outside-id={WORKFLOW_DIAGRAM_EDGE_OPTIONS_CLICK_OUTSIDE_ID}
|
||||
labelX={labelX}
|
||||
labelY={labelY}
|
||||
onMouseEnter={() => setHovered(true)}
|
||||
onMouseLeave={() => setHovered(false)}
|
||||
>
|
||||
<WorkflowDiagramEdgeV2VisibilityContainer
|
||||
shouldDisplay={isSelected || hovered}
|
||||
shouldDisplay={isSelected || isEdgeHovered(id)}
|
||||
>
|
||||
<StyledIconButtonGroup
|
||||
className="nodrag nopan"
|
||||
|
||||
+4
-16
@@ -31,7 +31,6 @@ import {
|
||||
EdgeProps,
|
||||
getBezierPath,
|
||||
} from '@xyflow/react';
|
||||
import { useState } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import {
|
||||
IconDotsVertical,
|
||||
@@ -41,6 +40,7 @@ import {
|
||||
} from 'twenty-ui/display';
|
||||
import { IconButtonGroup } from 'twenty-ui/input';
|
||||
import { MenuItem } from 'twenty-ui/navigation';
|
||||
import { useIsEdgeHovered } from '@/workflow/workflow-diagram/hooks/useIsEdgeHovered';
|
||||
|
||||
type WorkflowDiagramFilterEdgeEditableProps = EdgeProps<WorkflowDiagramEdge>;
|
||||
|
||||
@@ -73,6 +73,7 @@ const StyledConfiguredFilterContainer = styled.div`
|
||||
`;
|
||||
|
||||
export const WorkflowDiagramFilterEdgeEditable = ({
|
||||
id,
|
||||
source,
|
||||
target,
|
||||
sourceY,
|
||||
@@ -105,7 +106,7 @@ export const WorkflowDiagramFilterEdgeEditable = ({
|
||||
const { openDropdown } = useOpenDropdown();
|
||||
const { closeDropdown } = useCloseDropdown();
|
||||
|
||||
const [hovered, setHovered] = useState(false);
|
||||
const { isEdgeHovered } = useIsEdgeHovered();
|
||||
|
||||
const setWorkflowDiagramPanOnDrag = useSetRecoilComponentStateV2(
|
||||
workflowDiagramPanOnDragComponentState,
|
||||
@@ -133,14 +134,6 @@ export const WorkflowDiagramFilterEdgeEditable = ({
|
||||
const { openWorkflowEditFilterInCommandMenu } =
|
||||
useOpenWorkflowEditFilterInCommandMenu();
|
||||
|
||||
const handleMouseEnter = () => {
|
||||
setHovered(true);
|
||||
};
|
||||
|
||||
const handleMouseLeave = () => {
|
||||
setHovered(false);
|
||||
};
|
||||
|
||||
const handleFilterButtonClick = () => {
|
||||
openWorkflowEditFilterInCommandMenu({
|
||||
stepId: data.stepId,
|
||||
@@ -150,7 +143,6 @@ export const WorkflowDiagramFilterEdgeEditable = ({
|
||||
|
||||
const handleAddNodeButtonClick = () => {
|
||||
closeDropdown(dropdownId);
|
||||
setHovered(false);
|
||||
|
||||
startNodeCreation({
|
||||
parentStepId: data.stepId,
|
||||
@@ -173,12 +165,10 @@ export const WorkflowDiagramFilterEdgeEditable = ({
|
||||
data-click-outside-id={WORKFLOW_DIAGRAM_EDGE_OPTIONS_CLICK_OUTSIDE_ID}
|
||||
labelX={labelX}
|
||||
labelY={labelY}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
>
|
||||
<WorkflowDiagramEdgeV2VisibilityContainer shouldDisplay>
|
||||
<StyledConfiguredFilterContainer>
|
||||
{hovered || isDropdownOpen || isEdgeSelected ? (
|
||||
{isEdgeHovered(id) || isDropdownOpen || isEdgeSelected ? (
|
||||
<StyledIconButtonGroup
|
||||
className="nodrag nopan"
|
||||
iconButtons={[
|
||||
@@ -237,7 +227,6 @@ export const WorkflowDiagramFilterEdgeEditable = ({
|
||||
LeftIcon={IconFilter}
|
||||
onClick={() => {
|
||||
closeDropdown(dropdownId);
|
||||
setHovered(false);
|
||||
|
||||
handleFilterButtonClick();
|
||||
}}
|
||||
@@ -247,7 +236,6 @@ export const WorkflowDiagramFilterEdgeEditable = ({
|
||||
LeftIcon={IconFilterX}
|
||||
onClick={() => {
|
||||
closeDropdown(dropdownId);
|
||||
setHovered(false);
|
||||
|
||||
if (!isDefined(data.stepId)) {
|
||||
throw new Error(
|
||||
|
||||
+5
-14
@@ -9,11 +9,11 @@ import {
|
||||
EdgeProps,
|
||||
getBezierPath,
|
||||
} from '@xyflow/react';
|
||||
import { useState } from 'react';
|
||||
import { IconPlus } from 'twenty-ui/display';
|
||||
import { IconButtonGroup } from 'twenty-ui/input';
|
||||
import { WorkflowDiagramEdgeV2Container } from '@/workflow/workflow-diagram/components/WorkflowDiagramEdgeV2Container';
|
||||
import { WorkflowDiagramEdgeV2VisibilityContainer } from '@/workflow/workflow-diagram/components/WorkflowDiagramEdgeV2VisibilityContainer';
|
||||
import { useIsEdgeHovered } from '@/workflow/workflow-diagram/hooks/useIsEdgeHovered';
|
||||
|
||||
const StyledIconButtonGroup = styled(IconButtonGroup)`
|
||||
pointer-events: all;
|
||||
@@ -23,6 +23,7 @@ type WorkflowDiagramFilteringDisabledEdgeEditableProps =
|
||||
EdgeProps<WorkflowDiagramEdge>;
|
||||
|
||||
export const WorkflowDiagramFilteringDisabledEdgeEditable = ({
|
||||
id,
|
||||
markerStart,
|
||||
markerEnd,
|
||||
source,
|
||||
@@ -34,6 +35,8 @@ export const WorkflowDiagramFilteringDisabledEdgeEditable = ({
|
||||
}: WorkflowDiagramFilteringDisabledEdgeEditableProps) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const { isEdgeHovered } = useIsEdgeHovered();
|
||||
|
||||
const [edgePath, labelX, labelY] = getBezierPath({
|
||||
sourceX,
|
||||
sourceY,
|
||||
@@ -41,16 +44,6 @@ export const WorkflowDiagramFilteringDisabledEdgeEditable = ({
|
||||
targetY,
|
||||
});
|
||||
|
||||
const [hovered, setHovered] = useState(false);
|
||||
|
||||
const handleMouseEnter = () => {
|
||||
setHovered(true);
|
||||
};
|
||||
|
||||
const handleMouseLeave = () => {
|
||||
setHovered(false);
|
||||
};
|
||||
|
||||
const { startNodeCreation, isNodeCreationStarted } = useStartNodeCreation();
|
||||
|
||||
const forceDisplayAddButton = isNodeCreationStarted({
|
||||
@@ -80,11 +73,9 @@ export const WorkflowDiagramFilteringDisabledEdgeEditable = ({
|
||||
data-click-outside-id={WORKFLOW_DIAGRAM_EDGE_OPTIONS_CLICK_OUTSIDE_ID}
|
||||
labelX={labelX}
|
||||
labelY={labelY}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
>
|
||||
<WorkflowDiagramEdgeV2VisibilityContainer
|
||||
shouldDisplay={hovered || forceDisplayAddButton}
|
||||
shouldDisplay={isEdgeHovered(id) || forceDisplayAddButton}
|
||||
>
|
||||
<StyledIconButtonGroup
|
||||
className="nodrag nopan"
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import { workflowHoveredEdgeIdComponentState } from '@/workflow/workflow-diagram/states/workflowHoveredEdgeIdComponentState';
|
||||
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
|
||||
|
||||
export const useIsEdgeHovered = () => {
|
||||
const [workflowHoveredEdgeId, setWorkflowHoveredEdgeId] =
|
||||
useRecoilComponentStateV2(workflowHoveredEdgeIdComponentState);
|
||||
|
||||
const isEdgeHovered = (edgeId: string) => {
|
||||
return workflowHoveredEdgeId === edgeId;
|
||||
};
|
||||
|
||||
const setEdgeHovered = (edgeId: string) => {
|
||||
setWorkflowHoveredEdgeId(edgeId);
|
||||
};
|
||||
|
||||
const setNoEdgeHovered = () => {
|
||||
setWorkflowHoveredEdgeId(undefined);
|
||||
};
|
||||
|
||||
return { isEdgeHovered, setEdgeHovered, setNoEdgeHovered };
|
||||
};
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
|
||||
import { WorkflowVisualizerComponentInstanceContext } from '@/workflow/workflow-diagram/states/contexts/WorkflowVisualizerComponentInstanceContext';
|
||||
|
||||
export const workflowHoveredEdgeIdComponentState = createComponentStateV2<
|
||||
string | undefined
|
||||
>({
|
||||
key: 'workflowHoveredEdgeIdComponentState',
|
||||
defaultValue: undefined,
|
||||
componentInstanceContext: WorkflowVisualizerComponentInstanceContext,
|
||||
});
|
||||
Reference in New Issue
Block a user