2
0

feat(inputs): Add Condition step

This commit is contained in:
Baptiste Arnaud
2022-01-15 17:30:20 +01:00
parent 4ccb7bca49
commit 2814a352b2
30 changed files with 1178 additions and 243 deletions

View File

@ -1,17 +1,19 @@
import { Coordinates } from '@dnd-kit/core/dist/types'
import { Block, ChoiceInputStep, Step, Table, Target, Typebot } from 'models'
import { AnchorsPositionProps } from 'components/board/graph/Edges/Edge'
import { Block, Step, Table, Target, Typebot } from 'models'
import {
AnchorsPositionProps,
EdgeType,
} from 'components/board/graph/Edges/Edge'
import {
stubLength,
blockWidth,
blockAnchorsOffset,
spaceBetweenSteps,
firstStepOffsetY,
firstChoiceItemOffsetY,
ConnectingIds,
Endpoint,
} from 'contexts/GraphContext'
import { roundCorners } from 'svg-round-corners'
import { isChoiceInput, isDefined } from 'utils'
import { headerHeight } from 'components/shared/TypebotHeader'
import { isConditionStep } from 'utils'
export const computeDropOffPath = (
sourcePosition: Coordinates,
@ -27,38 +29,12 @@ export const computeDropOffPath = (
export const computeSourceCoordinates = (
sourcePosition: Coordinates,
sourceStepIndex: number,
sourceChoiceItemIndex?: number
sourceTop: number
) => ({
x: sourcePosition.x + blockWidth,
y:
(sourcePosition.y ?? 0) +
firstStepOffsetY +
spaceBetweenSteps * sourceStepIndex +
(isDefined(sourceChoiceItemIndex)
? firstChoiceItemOffsetY +
(sourceChoiceItemIndex ?? 0) * spaceBetweenSteps
: 0),
y: sourceTop,
})
export const computeFlowChartConnectorPath = ({
sourcePosition,
targetPosition,
sourceType,
totalSegments,
}: AnchorsPositionProps) => {
const segments = getSegments({
sourcePosition,
targetPosition,
sourceType,
totalSegments,
})
return roundCorners(
`M${sourcePosition.x},${sourcePosition.y} ${segments}`,
10
).path
}
const getSegments = ({
sourcePosition,
targetPosition,
@ -152,27 +128,20 @@ const computeFiveSegments = (
type GetAnchorsPositionParams = {
sourceBlock: Block
targetBlock: Block
sourceStepIndex: number
sourceChoiceItemIndex?: number
targetStepIndex?: number
sourceTop: number
targetTop: number
}
export const getAnchorsPosition = ({
sourceBlock,
targetBlock,
sourceStepIndex,
sourceChoiceItemIndex,
targetStepIndex,
sourceTop,
targetTop,
}: GetAnchorsPositionParams): AnchorsPositionProps => {
const targetOffsetY = isDefined(targetStepIndex)
? (targetBlock.graphCoordinates.y ?? 0) +
firstStepOffsetY +
spaceBetweenSteps * targetStepIndex
: undefined
const targetOffsetY = targetTop > 0 ? targetTop : undefined
const sourcePosition = computeSourceCoordinates(
sourceBlock.graphCoordinates,
sourceStepIndex,
sourceChoiceItemIndex
sourceTop
)
let sourceType: 'right' | 'left' = 'right'
if (sourceBlock.graphCoordinates.x > targetBlock.graphCoordinates.x) {
@ -247,78 +216,58 @@ const parseBlockAnchorPosition = (
}
}
export const computeDrawingConnectedPath = (
export const computeEdgePath = ({
sourcePosition,
targetPosition,
sourceType,
totalSegments,
}: AnchorsPositionProps) => {
const segments = getSegments({
sourcePosition,
targetPosition,
sourceType,
totalSegments,
})
return roundCorners(
`M${sourcePosition.x},${sourcePosition.y} ${segments}`,
10
).path
}
export const computeConnectingEdgePath = (
connectingIds: Omit<ConnectingIds, 'target'> & { target: Target },
sourceBlock: Block,
sourceTop: number,
targetTop: number,
typebot: Typebot
) => {
if (!sourceBlock) return ``
const targetBlock = typebot.blocks.byId[connectingIds.target.blockId]
const targetStepIndex = connectingIds.target.stepId
? targetBlock.stepIds.findIndex(
(stepId) => stepId === connectingIds.target?.stepId
)
: undefined
const sourceStepIndex = sourceBlock?.stepIds.indexOf(
connectingIds?.source.stepId
)
const sourceStep = typebot.steps.byId[connectingIds?.source.stepId]
const sourceChoiceItemIndex = isChoiceInput(sourceStep)
? getSourceChoiceItemIndex(sourceStep, connectingIds.source.choiceItemId)
: undefined
const anchorsPosition = getAnchorsPosition({
sourceBlock,
targetBlock,
sourceStepIndex,
sourceChoiceItemIndex,
targetStepIndex,
sourceTop,
targetTop,
})
return computeFlowChartConnectorPath(anchorsPosition)
return computeEdgePath(anchorsPosition)
}
export const computeDrawingPathToMouse = (
sourceBlock: Block,
connectingIds: ConnectingIds,
mousePosition: Coordinates,
steps: Table<Step>
) => {
const sourceStep = steps.byId[connectingIds?.source.stepId ?? '']
return computeConnectingEdgePath({
blockPosition: sourceBlock?.graphCoordinates,
mousePosition,
stepIndex: sourceBlock.stepIds.findIndex(
(stepId) => stepId === connectingIds?.source.stepId
),
choiceItemIndex: isChoiceInput(sourceStep)
? getSourceChoiceItemIndex(sourceStep, connectingIds?.source.choiceItemId)
: undefined,
})
}
const computeConnectingEdgePath = ({
export const computeEdgePathToMouse = ({
blockPosition,
mousePosition,
stepIndex,
choiceItemIndex,
sourceTop,
}: {
blockPosition: Coordinates
mousePosition: Coordinates
stepIndex: number
choiceItemIndex?: number
sourceTop: number
}): string => {
const sourcePosition = {
x:
mousePosition.x - blockPosition.x > blockWidth / 2
? blockPosition.x + blockWidth - 40
: blockPosition.x + 40,
y:
blockPosition.y +
firstStepOffsetY +
stepIndex * spaceBetweenSteps +
(isDefined(choiceItemIndex)
? firstChoiceItemOffsetY + (choiceItemIndex ?? 0) * spaceBetweenSteps
: 0),
y: sourceTop,
}
const sourceType =
mousePosition.x - blockPosition.x > blockWidth / 2 ? 'right' : 'left'
@ -333,8 +282,33 @@ const computeConnectingEdgePath = ({
).path
}
export const getSourceChoiceItemIndex = (
step: ChoiceInputStep,
itemId?: string
) =>
itemId ? step.options.itemIds.indexOf(itemId) : step.options.itemIds.length
export const getEndpointTopOffset = (
graphPosition: Coordinates,
endpoints: Table<Endpoint>,
id?: string
): number => {
if (!id) return 0
const endpointRef = endpoints.byId[id]?.ref
if (!endpointRef) return 0
return (
7 +
(endpointRef.current?.getBoundingClientRect().top ?? 0) -
graphPosition.y -
headerHeight
)
}
export const getTarget = (step: Step, edgeType: EdgeType) => {
if (!step) return
switch (edgeType) {
case EdgeType.STEP:
case EdgeType.CHOICE_ITEM:
return step.target
case EdgeType.CONDITION_TRUE:
if (!isConditionStep(step)) return
return step.trueTarget
case EdgeType.CONDITION_FALSE:
if (!isConditionStep(step)) return
return step.falseTarget
}
}

View File

@ -13,13 +13,18 @@ import {
ChoiceInputStep,
LogicStepType,
LogicStep,
Step,
ConditionStep,
ComparisonOperators,
LogicalOperator,
} from 'models'
import shortId from 'short-uuid'
import shortId, { generate } from 'short-uuid'
import { Typebot } from 'models'
import useSWR from 'swr'
import { fetcher, sendRequest, toKebabCase } from './utils'
import { deepEqual } from 'fast-equals'
import { stringify } from 'qs'
import { isChoiceInput, isConditionStep } from 'utils'
export const useTypebots = ({
folderId,
@ -136,6 +141,26 @@ export const parseNewStep = (
...choiceInput,
}
}
case LogicStepType.CONDITION: {
const id = generate()
const conditionStep: Pick<ConditionStep, 'type' | 'options'> = {
type,
options: {
comparisons: {
byId: {
[id]: { id, comparisonOperator: ComparisonOperators.EQUAL },
},
allIds: [id],
},
logicalOperator: LogicalOperator.AND,
},
}
return {
id,
blockId,
...conditionStep,
}
}
default: {
return {
id,
@ -214,3 +239,6 @@ export const parseNewTypebot = ({
settings,
}
}
export const hasDefaultConnector = (step: Step) =>
!isChoiceInput(step) && !isConditionStep(step)