Display + edge icon on hover (#12635)
This commit is contained in:
@ -39,6 +39,7 @@ import React, {
|
|||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
import { Tag, TagColor } from 'twenty-ui/components';
|
import { Tag, TagColor } from 'twenty-ui/components';
|
||||||
import { THEME_COMMON } from 'twenty-ui/theme';
|
import { THEME_COMMON } from 'twenty-ui/theme';
|
||||||
|
import { workflowInsertStepIdsComponentState } from '@/workflow/workflow-steps/states/workflowInsertStepIdsComponentState';
|
||||||
|
|
||||||
const StyledResetReactflowStyles = styled.div`
|
const StyledResetReactflowStyles = styled.div`
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@ -133,6 +134,11 @@ export const WorkflowDiagramCanvasBase = ({
|
|||||||
const workflowDiagram = useRecoilComponentValueV2(
|
const workflowDiagram = useRecoilComponentValueV2(
|
||||||
workflowDiagramComponentState,
|
workflowDiagramComponentState,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const setWorkflowInsertStepIds = useSetRecoilComponentStateV2(
|
||||||
|
workflowInsertStepIdsComponentState,
|
||||||
|
);
|
||||||
|
|
||||||
const [
|
const [
|
||||||
workflowDiagramFlowInitializationStatus,
|
workflowDiagramFlowInitializationStatus,
|
||||||
setWorkflowDiagramFlowInitializationStatus,
|
setWorkflowDiagramFlowInitializationStatus,
|
||||||
@ -174,6 +180,10 @@ export const WorkflowDiagramCanvasBase = ({
|
|||||||
reactflow.setNodes((nodes) =>
|
reactflow.setNodes((nodes) =>
|
||||||
nodes.map((node) => ({ ...node, selected: false })),
|
nodes.map((node) => ({ ...node, selected: false })),
|
||||||
);
|
);
|
||||||
|
setWorkflowInsertStepIds({
|
||||||
|
parentStepId: undefined,
|
||||||
|
nextStepId: undefined,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
|
|||||||
@ -1,25 +1,36 @@
|
|||||||
import { STEP_ICON_WIDTH } from '@/workflow/workflow-diagram/constants/CreateStepNodeWidth';
|
|
||||||
import { WORKFLOW_DIAGRAM_EDGE_OPTIONS_CLICK_OUTSIDE_ID } from '@/workflow/workflow-diagram/constants/WorkflowDiagramEdgeOptionsClickOutsideId';
|
import { WORKFLOW_DIAGRAM_EDGE_OPTIONS_CLICK_OUTSIDE_ID } from '@/workflow/workflow-diagram/constants/WorkflowDiagramEdgeOptionsClickOutsideId';
|
||||||
import { useStartNodeCreation } from '@/workflow/workflow-diagram/hooks/useStartNodeCreation';
|
import { useStartNodeCreation } from '@/workflow/workflow-diagram/hooks/useStartNodeCreation';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { EdgeLabelRenderer } from '@xyflow/react';
|
import { EdgeLabelRenderer } from '@xyflow/react';
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
|
||||||
import { IconPlus } from 'twenty-ui/display';
|
import { IconPlus } from 'twenty-ui/display';
|
||||||
import { IconButtonGroup } from 'twenty-ui/input';
|
import { IconButtonGroup } from 'twenty-ui/input';
|
||||||
|
import { useState } from 'react';
|
||||||
const EDGE_OPTION_BUTTON_LEFT_MARGIN = 8;
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
|
import { workflowInsertStepIdsComponentState } from '@/workflow/workflow-steps/states/workflowInsertStepIdsComponentState';
|
||||||
|
|
||||||
const StyledIconButtonGroup = styled(IconButtonGroup)`
|
const StyledIconButtonGroup = styled(IconButtonGroup)`
|
||||||
|
border: 1px solid ${({ theme }) => theme.border.color.strong};
|
||||||
pointer-events: all;
|
pointer-events: all;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledContainer = styled.div<{
|
const StyledContainer = styled.div<{
|
||||||
labelX?: number;
|
|
||||||
labelY?: number;
|
labelY?: number;
|
||||||
}>`
|
}>`
|
||||||
position: absolute;
|
position: absolute;
|
||||||
transform: ${({ labelX, labelY }) =>
|
transform: ${({ labelY }) => `translate(${21}px, ${(labelY || 0) - 14}px)`};
|
||||||
`translate(${labelX || 0}px, ${isDefined(labelY) ? labelY - STEP_ICON_WIDTH / 2 : 0}px) translateX(${EDGE_OPTION_BUTTON_LEFT_MARGIN}px)`};
|
`;
|
||||||
|
|
||||||
|
const StyledHoverZone = styled.div`
|
||||||
|
position: absolute;
|
||||||
|
width: 48px;
|
||||||
|
height: 52px;
|
||||||
|
transform: translate(-13px, -16px);
|
||||||
|
background: transparent;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledWrapper = styled.div`
|
||||||
|
pointer-events: all;
|
||||||
|
position: relative;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
type WorkflowDiagramEdgeOptionsProps = {
|
type WorkflowDiagramEdgeOptionsProps = {
|
||||||
@ -30,31 +41,47 @@ type WorkflowDiagramEdgeOptionsProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const WorkflowDiagramEdgeOptions = ({
|
export const WorkflowDiagramEdgeOptions = ({
|
||||||
labelX,
|
|
||||||
labelY,
|
labelY,
|
||||||
parentStepId,
|
parentStepId,
|
||||||
nextStepId,
|
nextStepId,
|
||||||
}: WorkflowDiagramEdgeOptionsProps) => {
|
}: WorkflowDiagramEdgeOptionsProps) => {
|
||||||
|
const [hovered, setHovered] = useState(false);
|
||||||
|
|
||||||
const { startNodeCreation } = useStartNodeCreation();
|
const { startNodeCreation } = useStartNodeCreation();
|
||||||
|
|
||||||
|
const workflowInsertStepIds = useRecoilComponentValueV2(
|
||||||
|
workflowInsertStepIdsComponentState,
|
||||||
|
);
|
||||||
|
|
||||||
|
const isSelected =
|
||||||
|
workflowInsertStepIds.parentStepId === parentStepId &&
|
||||||
|
workflowInsertStepIds.nextStepId === nextStepId;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EdgeLabelRenderer>
|
<EdgeLabelRenderer>
|
||||||
<StyledContainer
|
<StyledContainer
|
||||||
labelX={labelX}
|
|
||||||
labelY={labelY}
|
labelY={labelY}
|
||||||
data-click-outside-id={WORKFLOW_DIAGRAM_EDGE_OPTIONS_CLICK_OUTSIDE_ID}
|
data-click-outside-id={WORKFLOW_DIAGRAM_EDGE_OPTIONS_CLICK_OUTSIDE_ID}
|
||||||
>
|
>
|
||||||
<StyledIconButtonGroup
|
<StyledWrapper
|
||||||
className="nodrag nopan"
|
onMouseEnter={() => setHovered(true)}
|
||||||
iconButtons={[
|
onMouseLeave={() => setHovered(false)}
|
||||||
{
|
>
|
||||||
Icon: IconPlus,
|
<StyledHoverZone />
|
||||||
onClick: () => {
|
{(hovered || isSelected) && (
|
||||||
startNodeCreation({ parentStepId, nextStepId });
|
<StyledIconButtonGroup
|
||||||
},
|
className="nodrag nopan"
|
||||||
},
|
iconButtons={[
|
||||||
]}
|
{
|
||||||
/>
|
Icon: IconPlus,
|
||||||
|
onClick: () => {
|
||||||
|
startNodeCreation({ parentStepId, nextStepId });
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</StyledWrapper>
|
||||||
</StyledContainer>
|
</StyledContainer>
|
||||||
</EdgeLabelRenderer>
|
</EdgeLabelRenderer>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -11,6 +11,7 @@ export const useStartNodeCreation = () => {
|
|||||||
const setWorkflowInsertStepIds = useSetRecoilComponentStateV2(
|
const setWorkflowInsertStepIds = useSetRecoilComponentStateV2(
|
||||||
workflowInsertStepIdsComponentState,
|
workflowInsertStepIdsComponentState,
|
||||||
);
|
);
|
||||||
|
|
||||||
const { openStepSelectInCommandMenu } = useWorkflowCommandMenu();
|
const { openStepSelectInCommandMenu } = useWorkflowCommandMenu();
|
||||||
|
|
||||||
const workflowVisualizerWorkflowId = useRecoilComponentValueV2(
|
const workflowVisualizerWorkflowId = useRecoilComponentValueV2(
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
|
||||||
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||||
import { useGetUpdatableWorkflowVersion } from '@/workflow/hooks/useGetUpdatableWorkflowVersion';
|
import { useGetUpdatableWorkflowVersion } from '@/workflow/hooks/useGetUpdatableWorkflowVersion';
|
||||||
import { workflowLastCreatedStepIdComponentState } from '@/workflow/states/workflowLastCreatedStepIdComponentState';
|
import { workflowLastCreatedStepIdComponentState } from '@/workflow/states/workflowLastCreatedStepIdComponentState';
|
||||||
@ -11,6 +10,7 @@ import { useCreateWorkflowVersionStep } from '@/workflow/workflow-steps/hooks/us
|
|||||||
import { workflowInsertStepIdsComponentState } from '@/workflow/workflow-steps/states/workflowInsertStepIdsComponentState';
|
import { workflowInsertStepIdsComponentState } from '@/workflow/workflow-steps/states/workflowInsertStepIdsComponentState';
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
|
||||||
|
|
||||||
export const useCreateStep = ({
|
export const useCreateStep = ({
|
||||||
workflow,
|
workflow,
|
||||||
@ -26,9 +26,8 @@ export const useCreateStep = ({
|
|||||||
workflowLastCreatedStepIdComponentState,
|
workflowLastCreatedStepIdComponentState,
|
||||||
);
|
);
|
||||||
|
|
||||||
const workflowInsertStepIds = useRecoilComponentValueV2(
|
const [workflowInsertStepIds, setWorkflowInsertStepIds] =
|
||||||
workflowInsertStepIdsComponentState,
|
useRecoilComponentStateV2(workflowInsertStepIdsComponentState);
|
||||||
);
|
|
||||||
|
|
||||||
const { getUpdatableWorkflowVersion } = useGetUpdatableWorkflowVersion();
|
const { getUpdatableWorkflowVersion } = useGetUpdatableWorkflowVersion();
|
||||||
|
|
||||||
@ -63,9 +62,10 @@ export const useCreateStep = ({
|
|||||||
|
|
||||||
setWorkflowSelectedNode(createdStep.id);
|
setWorkflowSelectedNode(createdStep.id);
|
||||||
setWorkflowLastCreatedStepId(createdStep.id);
|
setWorkflowLastCreatedStepId(createdStep.id);
|
||||||
} catch (error) {
|
setWorkflowInsertStepIds({
|
||||||
setIsLoading(false);
|
parentStepId: undefined,
|
||||||
throw error;
|
nextStepId: undefined,
|
||||||
|
});
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -31,6 +31,15 @@ const StyledButton = styled.button`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const StyledAnimatedIconWrapper = styled.span`
|
||||||
|
display: flex;
|
||||||
|
transition: transform 0.1s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: translateY(-3%);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
export const InsideButton = ({
|
export const InsideButton = ({
|
||||||
className,
|
className,
|
||||||
Icon,
|
Icon,
|
||||||
@ -41,7 +50,11 @@ export const InsideButton = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledButton className={className} onClick={onClick} disabled={disabled}>
|
<StyledButton className={className} onClick={onClick} disabled={disabled}>
|
||||||
{Icon && <Icon size={theme.icon.size.sm} />}
|
{Icon && (
|
||||||
|
<StyledAnimatedIconWrapper>
|
||||||
|
<Icon size={theme.icon.size.sm} />
|
||||||
|
</StyledAnimatedIconWrapper>
|
||||||
|
)}
|
||||||
</StyledButton>
|
</StyledButton>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user