chore(editor): ♻️ Revert tables to arrays
Yet another refacto. I improved many many mechanisms on this one including dnd. It is now end 2 end tested 🎉
This commit is contained in:
@ -4,7 +4,7 @@ import { TransitionGroup, CSSTransition } from 'react-transition-group'
|
||||
import { ChatStep } from './ChatStep'
|
||||
import { AvatarSideContainer } from './AvatarSideContainer'
|
||||
import { HostAvatarsContext } from '../../contexts/HostAvatarsContext'
|
||||
import { Step } from 'models'
|
||||
import { PublicStep } from 'models'
|
||||
import { useTypebot } from '../../contexts/TypebotContext'
|
||||
import {
|
||||
isBubbleStep,
|
||||
@ -14,26 +14,28 @@ import {
|
||||
isLogicStep,
|
||||
} from 'utils'
|
||||
import { executeLogic } from 'services/logic'
|
||||
import { getSingleChoiceTargetId } from 'services/inputs'
|
||||
import { executeIntegration } from 'services/integration'
|
||||
|
||||
type ChatBlockProps = {
|
||||
stepIds: string[]
|
||||
startStepId?: string
|
||||
steps: PublicStep[]
|
||||
startStepIndex: number
|
||||
blockIndex: number
|
||||
onBlockEnd: (edgeId?: string) => void
|
||||
}
|
||||
|
||||
export const ChatBlock = ({
|
||||
stepIds,
|
||||
startStepId,
|
||||
steps,
|
||||
startStepIndex,
|
||||
blockIndex,
|
||||
onBlockEnd,
|
||||
}: ChatBlockProps) => {
|
||||
const { typebot, updateVariableValue } = useTypebot()
|
||||
const [displayedSteps, setDisplayedSteps] = useState<Step[]>([])
|
||||
const [displayedSteps, setDisplayedSteps] = useState<PublicStep[]>([])
|
||||
|
||||
const currentStepIndex = displayedSteps.length - 1
|
||||
|
||||
useEffect(() => {
|
||||
const nextStep =
|
||||
typebot.steps.byId[startStepId ?? stepIds[displayedSteps.length]]
|
||||
const nextStep = steps[startStepIndex]
|
||||
if (nextStep) setDisplayedSteps([...displayedSteps, nextStep])
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
@ -60,6 +62,7 @@ export const ChatBlock = ({
|
||||
typebot.typebotId,
|
||||
currentStep,
|
||||
typebot.variables,
|
||||
{ blockIndex, stepIndex: currentStepIndex },
|
||||
updateVariableValue
|
||||
)
|
||||
nextEdgeId ? onBlockEnd(nextEdgeId) : displayNextStep()
|
||||
@ -85,18 +88,17 @@ export const ChatBlock = ({
|
||||
}
|
||||
const isSingleChoiceStep =
|
||||
isChoiceInput(currentStep) && !currentStep.options.isMultipleChoice
|
||||
if (isSingleChoiceStep)
|
||||
return onBlockEnd(
|
||||
getSingleChoiceTargetId(
|
||||
currentStep,
|
||||
typebot.choiceItems,
|
||||
answerContent
|
||||
)
|
||||
if (isSingleChoiceStep) {
|
||||
onBlockEnd(
|
||||
currentStep.items.find((i) => i.content === answerContent)
|
||||
?.outgoingEdgeId
|
||||
)
|
||||
if (currentStep?.edgeId || displayedSteps.length === stepIds.length)
|
||||
return onBlockEnd(currentStep.edgeId)
|
||||
}
|
||||
|
||||
if (currentStep?.outgoingEdgeId || displayedSteps.length === steps.length)
|
||||
return onBlockEnd(currentStep.outgoingEdgeId)
|
||||
}
|
||||
const nextStep = typebot.steps.byId[stepIds[displayedSteps.length]]
|
||||
const nextStep = steps[displayedSteps.length]
|
||||
if (nextStep) setDisplayedSteps([...displayedSteps, nextStep])
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { useAnswers } from '../../../contexts/AnswersContext'
|
||||
import { useHostAvatars } from '../../../contexts/HostAvatarsContext'
|
||||
import { InputStep, InputStepType, Step } from 'models'
|
||||
import { InputStep, InputStepType, PublicStep, Step } from 'models'
|
||||
import { GuestBubble } from './bubbles/GuestBubble'
|
||||
import { TextForm } from './inputs/TextForm'
|
||||
import { isBubbleStep, isInputStep } from 'utils'
|
||||
@ -13,7 +13,7 @@ export const ChatStep = ({
|
||||
step,
|
||||
onTransitionEnd,
|
||||
}: {
|
||||
step: Step
|
||||
step: PublicStep
|
||||
onTransitionEnd: (answerContent?: string) => void
|
||||
}) => {
|
||||
const { addAnswer } = useAnswers()
|
||||
@ -63,6 +63,6 @@ const InputChatStep = ({
|
||||
case InputStepType.DATE:
|
||||
return <DateForm options={step.options} onSubmit={handleSubmit} />
|
||||
case InputStepType.CHOICE:
|
||||
return <ChoiceForm options={step.options} onSubmit={handleSubmit} />
|
||||
return <ChoiceForm step={step} onSubmit={handleSubmit} />
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,6 @@ import React, { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useHostAvatars } from 'contexts/HostAvatarsContext'
|
||||
import { useTypebot } from 'contexts/TypebotContext'
|
||||
import {
|
||||
Table,
|
||||
Variable,
|
||||
VideoBubbleContent,
|
||||
VideoBubbleContentType,
|
||||
@ -83,7 +82,7 @@ const VideoContent = ({
|
||||
}: {
|
||||
content?: VideoBubbleContent
|
||||
isTyping: boolean
|
||||
variables: Table<Variable>
|
||||
variables: Variable[]
|
||||
}) => {
|
||||
const url = useMemo(
|
||||
() => parseVariables({ text: content?.url, variables: variables }),
|
||||
|
@ -1,65 +1,61 @@
|
||||
import { ChoiceInputOptions } from 'models'
|
||||
import React, { useMemo, useState } from 'react'
|
||||
import { filterTable } from 'utils'
|
||||
import { useTypebot } from '../../../../contexts/TypebotContext'
|
||||
import { ChoiceInputStep } from 'models'
|
||||
import React, { useState } from 'react'
|
||||
import { SendButton } from './SendButton'
|
||||
|
||||
type ChoiceFormProps = {
|
||||
options?: ChoiceInputOptions
|
||||
step: ChoiceInputStep
|
||||
onSubmit: (value: string) => void
|
||||
}
|
||||
|
||||
export const ChoiceForm = ({ options, onSubmit }: ChoiceFormProps) => {
|
||||
const { typebot } = useTypebot()
|
||||
const items = useMemo(
|
||||
() => filterTable(options?.itemIds ?? [], typebot.choiceItems),
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[]
|
||||
)
|
||||
const [selectedIds, setSelectedIds] = useState<string[]>([])
|
||||
export const ChoiceForm = ({ step, onSubmit }: ChoiceFormProps) => {
|
||||
const [selectedIndices, setSelectedIndices] = useState<number[]>([])
|
||||
|
||||
const handleClick = (itemId: string) => (e: React.MouseEvent) => {
|
||||
const handleClick = (itemIndex: number) => (e: React.MouseEvent) => {
|
||||
e.preventDefault()
|
||||
if (options?.isMultipleChoice) toggleSelectedItemId(itemId)
|
||||
else onSubmit(items.byId[itemId].content ?? '')
|
||||
if (step.options?.isMultipleChoice) toggleSelectedItemIndex(itemIndex)
|
||||
else onSubmit(step.items[itemIndex].content ?? '')
|
||||
}
|
||||
|
||||
const toggleSelectedItemId = (itemId: string) => {
|
||||
const existingIndex = selectedIds.indexOf(itemId)
|
||||
const toggleSelectedItemIndex = (itemIndex: number) => {
|
||||
const existingIndex = selectedIndices.indexOf(itemIndex)
|
||||
if (existingIndex !== -1) {
|
||||
selectedIds.splice(existingIndex, 1)
|
||||
setSelectedIds([...selectedIds])
|
||||
selectedIndices.splice(existingIndex, 1)
|
||||
setSelectedIndices([...selectedIndices])
|
||||
} else {
|
||||
setSelectedIds([...selectedIds, itemId])
|
||||
setSelectedIndices([...selectedIndices, itemIndex])
|
||||
}
|
||||
}
|
||||
|
||||
const handleSubmit = () =>
|
||||
onSubmit(selectedIds.map((itemId) => items.byId[itemId].content).join(', '))
|
||||
onSubmit(
|
||||
selectedIndices
|
||||
.map((itemIndex) => step.items[itemIndex].content)
|
||||
.join(', ')
|
||||
)
|
||||
|
||||
return (
|
||||
<form className="flex flex-col" onSubmit={handleSubmit}>
|
||||
<div className="flex flex-wrap">
|
||||
{options?.itemIds.map((itemId) => (
|
||||
{step.items.map((item, idx) => (
|
||||
<button
|
||||
key={itemId}
|
||||
role={options?.isMultipleChoice ? 'checkbox' : 'button'}
|
||||
onClick={handleClick(itemId)}
|
||||
key={item.id}
|
||||
role={step.options?.isMultipleChoice ? 'checkbox' : 'button'}
|
||||
onClick={handleClick(idx)}
|
||||
className={
|
||||
'py-2 px-4 font-semibold rounded-md transition-all filter hover:brightness-90 active:brightness-75 duration-100 focus:outline-none mr-2 mb-2 typebot-button ' +
|
||||
(selectedIds.includes(itemId) || !options?.isMultipleChoice
|
||||
(selectedIndices.includes(idx) || !step.options?.isMultipleChoice
|
||||
? 'active'
|
||||
: '')
|
||||
}
|
||||
data-testid="button"
|
||||
>
|
||||
{items.byId[itemId].content}
|
||||
{item.content}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex">
|
||||
{selectedIds.length > 0 && (
|
||||
<SendButton label={options?.buttonLabel ?? 'Send'} />
|
||||
{selectedIndices.length > 0 && (
|
||||
<SendButton label={step.options?.buttonLabel ?? 'Send'} />
|
||||
)}
|
||||
</div>
|
||||
</form>
|
||||
|
@ -5,11 +5,12 @@ import { useFrame } from 'react-frame-component'
|
||||
import { setCssVariablesValue } from '../services/theme'
|
||||
import { useAnswers } from '../contexts/AnswersContext'
|
||||
import { deepEqual } from 'fast-equals'
|
||||
import { Answer, Block, PublicTypebot } from 'models'
|
||||
import { Answer, Edge, PublicBlock, PublicTypebot } from 'models'
|
||||
import { byId } from 'utils'
|
||||
|
||||
type Props = {
|
||||
typebot: PublicTypebot
|
||||
onNewBlockVisible: (edgeId: string) => void
|
||||
onNewBlockVisible: (edge: Edge) => void
|
||||
onNewAnswer: (answer: Answer) => void
|
||||
onCompleted: () => void
|
||||
}
|
||||
@ -21,30 +22,29 @@ export const ConversationContainer = ({
|
||||
}: Props) => {
|
||||
const { document: frameDocument } = useFrame()
|
||||
const [displayedBlocks, setDisplayedBlocks] = useState<
|
||||
{ block: Block; startStepId?: string }[]
|
||||
{ block: PublicBlock; startStepIndex: number }[]
|
||||
>([])
|
||||
const [localAnswer, setLocalAnswer] = useState<Answer | undefined>()
|
||||
const { answers } = useAnswers()
|
||||
const bottomAnchor = useRef<HTMLDivElement | null>(null)
|
||||
|
||||
const displayNextBlock = (edgeId?: string) => {
|
||||
const edge = typebot.edges.byId[edgeId ?? '']
|
||||
if (!edge) return onCompleted()
|
||||
const nextBlock = {
|
||||
block: typebot.blocks.byId[edge.to.blockId],
|
||||
startStepId: edge.to.stepId,
|
||||
}
|
||||
const nextEdge = typebot.edges.find(byId(edgeId))
|
||||
if (!nextEdge) return onCompleted()
|
||||
const nextBlock = typebot.blocks.find(byId(nextEdge.to.blockId))
|
||||
if (!nextBlock) return onCompleted()
|
||||
onNewBlockVisible(edge.id)
|
||||
setDisplayedBlocks([...displayedBlocks, nextBlock])
|
||||
const startStepIndex = nextEdge.to.stepId
|
||||
? nextBlock.steps.findIndex(byId(nextEdge.to.stepId))
|
||||
: 0
|
||||
onNewBlockVisible(nextEdge)
|
||||
setDisplayedBlocks([
|
||||
...displayedBlocks,
|
||||
{ block: nextBlock, startStepIndex },
|
||||
])
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const blocks = typebot.blocks
|
||||
const firstEdgeId =
|
||||
typebot.steps.byId[blocks.byId[blocks.allIds[0]].stepIds[0]].edgeId
|
||||
if (!firstEdgeId) return
|
||||
displayNextBlock(firstEdgeId)
|
||||
displayNextBlock(typebot.blocks[0].steps[0].outgoingEdgeId)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
@ -68,8 +68,9 @@ export const ConversationContainer = ({
|
||||
{displayedBlocks.map((displayedBlock, idx) => (
|
||||
<ChatBlock
|
||||
key={displayedBlock.block.id + idx}
|
||||
stepIds={displayedBlock.block.stepIds}
|
||||
startStepId={displayedBlock.startStepId}
|
||||
steps={displayedBlock.block.steps}
|
||||
startStepIndex={displayedBlock.startStepIndex}
|
||||
blockIndex={idx}
|
||||
onBlockEnd={displayNextBlock}
|
||||
/>
|
||||
))}
|
||||
|
@ -10,11 +10,11 @@ import phoneNumberInputStyle from 'react-phone-number-input/style.css'
|
||||
import phoneSyle from '../assets/phone.css'
|
||||
import { ConversationContainer } from './ConversationContainer'
|
||||
import { AnswersContext } from '../contexts/AnswersContext'
|
||||
import { Answer, BackgroundType, PublicTypebot } from 'models'
|
||||
import { Answer, BackgroundType, Edge, PublicTypebot } from 'models'
|
||||
|
||||
export type TypebotViewerProps = {
|
||||
typebot: PublicTypebot
|
||||
onNewBlockVisible?: (edgeId: string) => void
|
||||
onNewBlockVisible?: (edge: Edge) => void
|
||||
onNewAnswer?: (answer: Answer) => void
|
||||
onCompleted?: () => void
|
||||
}
|
||||
@ -31,8 +31,8 @@ export const TypebotViewer = ({
|
||||
: 'transparent',
|
||||
[typebot?.theme?.general?.background]
|
||||
)
|
||||
const handleNewBlockVisible = (blockId: string) => {
|
||||
if (onNewBlockVisible) onNewBlockVisible(blockId)
|
||||
const handleNewBlockVisible = (edge: Edge) => {
|
||||
if (onNewBlockVisible) onNewBlockVisible(edge)
|
||||
}
|
||||
const handleNewAnswer = (answer: Answer) => {
|
||||
if (onNewAnswer) onNewAnswer(answer)
|
||||
|
@ -20,13 +20,9 @@ export const TypebotContext = ({
|
||||
const updateVariableValue = (variableId: string, value: string) => {
|
||||
setLocalTypebot((typebot) => ({
|
||||
...typebot,
|
||||
variables: {
|
||||
...typebot.variables,
|
||||
byId: {
|
||||
...typebot.variables.byId,
|
||||
[variableId]: { ...typebot.variables.byId[variableId], value },
|
||||
},
|
||||
},
|
||||
variables: typebot.variables.map((v) =>
|
||||
v.id === variableId ? { ...v, value } : v
|
||||
),
|
||||
}))
|
||||
}
|
||||
return (
|
||||
|
@ -1,13 +0,0 @@
|
||||
import { ChoiceInputStep, ChoiceItem, Table } from 'models'
|
||||
|
||||
export const getSingleChoiceTargetId = (
|
||||
currentStep: ChoiceInputStep,
|
||||
choiceItems: Table<ChoiceItem>,
|
||||
answerContent?: string
|
||||
): string | undefined => {
|
||||
const itemId = currentStep.options.itemIds.find(
|
||||
(itemId) => choiceItems.byId[itemId].content === answerContent
|
||||
)
|
||||
if (!itemId) throw new Error('itemId should exist')
|
||||
return choiceItems.byId[itemId].edgeId ?? currentStep.edgeId
|
||||
}
|
@ -5,7 +5,6 @@ import {
|
||||
GoogleSheetsAction,
|
||||
GoogleSheetsInsertRowOptions,
|
||||
Variable,
|
||||
Table,
|
||||
GoogleSheetsUpdateRowOptions,
|
||||
Cell,
|
||||
GoogleSheetsGetOptions,
|
||||
@ -19,10 +18,12 @@ import { parseVariables, parseVariablesInObject } from './variable'
|
||||
|
||||
const safeEval = eval
|
||||
|
||||
type Indices = { blockIndex: number; stepIndex: number }
|
||||
export const executeIntegration = (
|
||||
typebotId: string,
|
||||
step: IntegrationStep,
|
||||
variables: Table<Variable>,
|
||||
variables: Variable[],
|
||||
indices: Indices,
|
||||
updateVariableValue: (variableId: string, value: string) => void
|
||||
) => {
|
||||
switch (step.type) {
|
||||
@ -31,13 +32,19 @@ export const executeIntegration = (
|
||||
case IntegrationStepType.GOOGLE_ANALYTICS:
|
||||
return executeGoogleAnalyticsIntegration(step, variables)
|
||||
case IntegrationStepType.WEBHOOK:
|
||||
return executeWebhook(typebotId, step, variables, updateVariableValue)
|
||||
return executeWebhook(
|
||||
typebotId,
|
||||
step,
|
||||
variables,
|
||||
indices,
|
||||
updateVariableValue
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export const executeGoogleAnalyticsIntegration = async (
|
||||
step: GoogleAnalyticsStep,
|
||||
variables: Table<Variable>
|
||||
variables: Variable[]
|
||||
) => {
|
||||
if (!step.options?.trackingId) return
|
||||
const { default: initGoogleAnalytics } = await import('../../lib/gtag')
|
||||
@ -47,10 +54,10 @@ export const executeGoogleAnalyticsIntegration = async (
|
||||
|
||||
const executeGoogleSheetIntegration = async (
|
||||
step: GoogleSheetsStep,
|
||||
variables: Table<Variable>,
|
||||
variables: Variable[],
|
||||
updateVariableValue: (variableId: string, value: string) => void
|
||||
) => {
|
||||
if (!('action' in step.options)) return step.edgeId
|
||||
if (!('action' in step.options)) return step.outgoingEdgeId
|
||||
switch (step.options.action) {
|
||||
case GoogleSheetsAction.INSERT_ROW:
|
||||
await insertRowInGoogleSheets(step.options, variables)
|
||||
@ -62,12 +69,12 @@ const executeGoogleSheetIntegration = async (
|
||||
await getRowFromGoogleSheets(step.options, variables, updateVariableValue)
|
||||
break
|
||||
}
|
||||
return step.edgeId
|
||||
return step.outgoingEdgeId
|
||||
}
|
||||
|
||||
const insertRowInGoogleSheets = async (
|
||||
options: GoogleSheetsInsertRowOptions,
|
||||
variables: Table<Variable>
|
||||
variables: Variable[]
|
||||
) => {
|
||||
if (!options.cellsToInsert) return
|
||||
return sendRequest({
|
||||
@ -82,7 +89,7 @@ const insertRowInGoogleSheets = async (
|
||||
|
||||
const updateRowInGoogleSheets = async (
|
||||
options: GoogleSheetsUpdateRowOptions,
|
||||
variables: Table<Variable>
|
||||
variables: Variable[]
|
||||
) => {
|
||||
if (!options.cellsToUpsert || !options.referenceCell) return
|
||||
return sendRequest({
|
||||
@ -104,7 +111,7 @@ const updateRowInGoogleSheets = async (
|
||||
|
||||
const getRowFromGoogleSheets = async (
|
||||
options: GoogleSheetsGetOptions,
|
||||
variables: Table<Variable>,
|
||||
variables: Variable[],
|
||||
updateVariableValue: (variableId: string, value: string) => void
|
||||
) => {
|
||||
if (!options.referenceCell || !options.cellsToExtract) return
|
||||
@ -118,9 +125,7 @@ const getRowFromGoogleSheets = async (
|
||||
variables,
|
||||
}),
|
||||
},
|
||||
columns: options.cellsToExtract.allIds.map(
|
||||
(id) => options.cellsToExtract?.byId[id].column
|
||||
),
|
||||
columns: options.cellsToExtract.map((cell) => cell.column),
|
||||
},
|
||||
{ indices: false }
|
||||
)
|
||||
@ -129,18 +134,15 @@ const getRowFromGoogleSheets = async (
|
||||
method: 'GET',
|
||||
})
|
||||
if (!data) return
|
||||
options.cellsToExtract.allIds.forEach((cellId) => {
|
||||
const cell = options.cellsToExtract?.byId[cellId]
|
||||
if (!cell) return
|
||||
options.cellsToExtract.forEach((cell) =>
|
||||
updateVariableValue(cell.variableId ?? '', data[cell.column ?? ''])
|
||||
})
|
||||
)
|
||||
}
|
||||
const parseCellValues = (
|
||||
cells: Table<Cell>,
|
||||
variables: Table<Variable>
|
||||
cells: Cell[],
|
||||
variables: Variable[]
|
||||
): { [key: string]: string } =>
|
||||
cells.allIds.reduce((row, id) => {
|
||||
const cell = cells.byId[id]
|
||||
cells.reduce((row, cell) => {
|
||||
return !cell.column || !cell.value
|
||||
? row
|
||||
: {
|
||||
@ -152,20 +154,21 @@ const parseCellValues = (
|
||||
const executeWebhook = async (
|
||||
typebotId: string,
|
||||
step: WebhookStep,
|
||||
variables: Table<Variable>,
|
||||
variables: Variable[],
|
||||
indices: Indices,
|
||||
updateVariableValue: (variableId: string, value: string) => void
|
||||
) => {
|
||||
if (!step.options?.webhookId) return step.edgeId
|
||||
if (!step.webhook) return step.outgoingEdgeId
|
||||
const { blockIndex, stepIndex } = indices
|
||||
const { data, error } = await sendRequest({
|
||||
url: `http://localhost:3000/api/typebots/${typebotId}/webhooks/${step.options?.webhookId}/execute`,
|
||||
url: `http://localhost:3000/api/typebots/${typebotId}/blocks/${blockIndex}/steps/${stepIndex}/executeWebhook`,
|
||||
method: 'POST',
|
||||
body: {
|
||||
variables,
|
||||
},
|
||||
})
|
||||
console.error(error)
|
||||
step.options.responseVariableMapping?.allIds.forEach((varMappingId) => {
|
||||
const varMapping = step.options?.responseVariableMapping?.byId[varMappingId]
|
||||
step.options.responseVariableMapping.forEach((varMapping) => {
|
||||
if (!varMapping?.bodyPath || !varMapping.variableId) return
|
||||
const value = safeEval(`(${JSON.stringify(data)}).${varMapping?.bodyPath}`)
|
||||
updateVariableValue(varMapping.variableId, value)
|
||||
|
@ -3,20 +3,21 @@ import {
|
||||
LogicStepType,
|
||||
LogicalOperator,
|
||||
ConditionStep,
|
||||
Table,
|
||||
Variable,
|
||||
ComparisonOperators,
|
||||
SetVariableStep,
|
||||
RedirectStep,
|
||||
Comparison,
|
||||
} from 'models'
|
||||
import { isDefined, isNotDefined } from 'utils'
|
||||
import { sanitizeUrl } from './utils'
|
||||
import { isMathFormula, evaluateExpression, parseVariables } from './variable'
|
||||
|
||||
type EdgeId = string
|
||||
|
||||
export const executeLogic = (
|
||||
step: LogicStep,
|
||||
variables: Table<Variable>,
|
||||
variables: Variable[],
|
||||
updateVariableValue: (variableId: string, expression: string) => void
|
||||
): EdgeId | undefined => {
|
||||
switch (step.type) {
|
||||
@ -31,40 +32,36 @@ export const executeLogic = (
|
||||
|
||||
const executeSetVariable = (
|
||||
step: SetVariableStep,
|
||||
variables: Table<Variable>,
|
||||
variables: Variable[],
|
||||
updateVariableValue: (variableId: string, expression: string) => void
|
||||
): EdgeId | undefined => {
|
||||
if (!step.options?.variableId || !step.options.expressionToEvaluate)
|
||||
return step.edgeId
|
||||
return step.outgoingEdgeId
|
||||
const expression = step.options.expressionToEvaluate
|
||||
const evaluatedExpression = isMathFormula(expression)
|
||||
? evaluateExpression(parseVariables({ text: expression, variables }))
|
||||
: expression
|
||||
updateVariableValue(step.options.variableId, evaluatedExpression)
|
||||
return step.edgeId
|
||||
return step.outgoingEdgeId
|
||||
}
|
||||
|
||||
const executeCondition = (
|
||||
step: ConditionStep,
|
||||
variables: Table<Variable>
|
||||
variables: Variable[]
|
||||
): EdgeId | undefined => {
|
||||
const { content } = step.items[0]
|
||||
const isConditionPassed =
|
||||
step.options?.logicalOperator === LogicalOperator.AND
|
||||
? step.options?.comparisons.allIds.every(
|
||||
executeComparison(step, variables)
|
||||
)
|
||||
: step.options?.comparisons.allIds.some(
|
||||
executeComparison(step, variables)
|
||||
)
|
||||
return isConditionPassed ? step.trueEdgeId : step.falseEdgeId
|
||||
content.logicalOperator === LogicalOperator.AND
|
||||
? content.comparisons.every(executeComparison(variables))
|
||||
: content.comparisons.some(executeComparison(variables))
|
||||
return isConditionPassed ? step.items[0].outgoingEdgeId : step.outgoingEdgeId
|
||||
}
|
||||
|
||||
const executeComparison =
|
||||
(step: ConditionStep, variables: Table<Variable>) =>
|
||||
(comparisonId: string) => {
|
||||
const comparison = step.options?.comparisons.byId[comparisonId]
|
||||
(variables: Variable[]) => (comparison: Comparison) => {
|
||||
if (!comparison?.variableId) return false
|
||||
const inputValue = variables.byId[comparison.variableId].value ?? ''
|
||||
const inputValue =
|
||||
variables.find((v) => v.id === comparison.variableId)?.value ?? ''
|
||||
const { value } = comparison
|
||||
if (isNotDefined(value)) return false
|
||||
switch (comparison.comparisonOperator) {
|
||||
@ -91,12 +88,12 @@ const executeComparison =
|
||||
|
||||
const executeRedirect = (
|
||||
step: RedirectStep,
|
||||
variables: Table<Variable>
|
||||
variables: Variable[]
|
||||
): EdgeId | undefined => {
|
||||
if (!step.options?.url) return step.edgeId
|
||||
if (!step.options?.url) return step.outgoingEdgeId
|
||||
window.open(
|
||||
sanitizeUrl(parseVariables({ text: step.options?.url, variables })),
|
||||
step.options.isNewTab ? '_blank' : '_self'
|
||||
)
|
||||
return step.edgeId
|
||||
return step.outgoingEdgeId
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Table, Variable } from 'models'
|
||||
import { Variable } from 'models'
|
||||
import { isDefined } from 'utils'
|
||||
|
||||
const safeEval = eval
|
||||
@ -11,16 +11,16 @@ export const parseVariables = ({
|
||||
variables,
|
||||
}: {
|
||||
text?: string
|
||||
variables: Table<Variable>
|
||||
variables: Variable[]
|
||||
}): string => {
|
||||
if (!text || text === '') return ''
|
||||
return text.replace(/\{\{(.*?)\}\}/g, (_, fullVariableString) => {
|
||||
const matchedVarName = fullVariableString.replace(/{{|}}/g, '')
|
||||
const matchedVariableId = variables.allIds.find((variableId) => {
|
||||
const variable = variables.byId[variableId]
|
||||
return matchedVarName === variable.name && isDefined(variable.value)
|
||||
})
|
||||
return variables.byId[matchedVariableId ?? '']?.value ?? ''
|
||||
return (
|
||||
variables.find((v) => {
|
||||
return matchedVarName === v.name && isDefined(v.value)
|
||||
})?.value ?? ''
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@ -50,7 +50,7 @@ const countDecimals = (value: number) => {
|
||||
|
||||
export const parseVariablesInObject = (
|
||||
object: { [key: string]: string | number },
|
||||
variables: Table<Variable>
|
||||
variables: Variable[]
|
||||
) =>
|
||||
Object.keys(object).reduce((newObj, key) => {
|
||||
const currentValue = object[key]
|
||||
|
Reference in New Issue
Block a user