@ -35,7 +35,7 @@ type Item =
|
|||||||
type Props<T extends Item> = {
|
type Props<T extends Item> = {
|
||||||
isPopoverMatchingInputWidth?: boolean
|
isPopoverMatchingInputWidth?: boolean
|
||||||
selectedItem?: string
|
selectedItem?: string
|
||||||
items: T[]
|
items: readonly T[]
|
||||||
placeholder?: string
|
placeholder?: string
|
||||||
onSelect?: (value: string | undefined, item?: T) => void
|
onSelect?: (value: string | undefined, item?: T) => void
|
||||||
}
|
}
|
||||||
@ -190,11 +190,11 @@ export const Select = <T extends Item>({
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<InputRightElement
|
<InputRightElement
|
||||||
width={selectedItem ? '5rem' : undefined}
|
width={selectedItem && isOpen ? '5rem' : undefined}
|
||||||
pointerEvents="none"
|
pointerEvents="none"
|
||||||
>
|
>
|
||||||
<HStack>
|
<HStack>
|
||||||
{selectedItem && (
|
{selectedItem && isOpen && (
|
||||||
<IconButton
|
<IconButton
|
||||||
onClick={clearSelection}
|
onClick={clearSelection}
|
||||||
icon={<CloseIcon />}
|
icon={<CloseIcon />}
|
||||||
|
@ -1,18 +1,49 @@
|
|||||||
import { Text } from '@chakra-ui/react'
|
import { Text } from '@chakra-ui/react'
|
||||||
import { useTypebot } from '@/features/editor/providers/TypebotProvider'
|
import { useTypebot } from '@/features/editor/providers/TypebotProvider'
|
||||||
import { SetVariableBlock } from '@typebot.io/schemas'
|
import { SetVariableBlock, Variable } from '@typebot.io/schemas'
|
||||||
import { byId } from '@typebot.io/lib'
|
import { byId, isEmpty } from '@typebot.io/lib'
|
||||||
|
|
||||||
export const SetVariableContent = ({ block }: { block: SetVariableBlock }) => {
|
export const SetVariableContent = ({ block }: { block: SetVariableBlock }) => {
|
||||||
const { typebot } = useTypebot()
|
const { typebot } = useTypebot()
|
||||||
const variableName =
|
const variableName =
|
||||||
typebot?.variables.find(byId(block.options.variableId))?.name ?? ''
|
typebot?.variables.find(byId(block.options.variableId))?.name ?? ''
|
||||||
const expression = block.options.expressionToEvaluate ?? ''
|
|
||||||
return (
|
return (
|
||||||
<Text color={'gray.500'} noOfLines={2}>
|
<Text color={'gray.500'} noOfLines={4}>
|
||||||
{variableName === '' && expression === ''
|
{variableName === '' && isEmpty(block.options.expressionToEvaluate)
|
||||||
? 'Click to edit...'
|
? 'Click to edit...'
|
||||||
: `${variableName} ${expression ? `= ${expression}` : ``}`}
|
: getExpression(typebot?.variables ?? [])(block.options)}
|
||||||
</Text>
|
</Text>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getExpression =
|
||||||
|
(variables: Variable[]) =>
|
||||||
|
(options: SetVariableBlock['options']): string | null => {
|
||||||
|
const variableName = variables.find(byId(options.variableId))?.name ?? ''
|
||||||
|
switch (options.type) {
|
||||||
|
case 'Custom':
|
||||||
|
case undefined:
|
||||||
|
return `${variableName} = ${options.expressionToEvaluate}`
|
||||||
|
case 'Map item with same index': {
|
||||||
|
const baseItemVariable = variables.find(
|
||||||
|
byId(options.mapListItemParams?.baseItemVariableId)
|
||||||
|
)
|
||||||
|
const baseListVariable = variables.find(
|
||||||
|
byId(options.mapListItemParams?.baseListVariableId)
|
||||||
|
)
|
||||||
|
const targetListVariable = variables.find(
|
||||||
|
byId(options.mapListItemParams?.targetListVariableId)
|
||||||
|
)
|
||||||
|
return `${variableName} = item in ${targetListVariable?.name} with same index as ${baseItemVariable?.name} in ${baseListVariable?.name}`
|
||||||
|
}
|
||||||
|
case 'Empty':
|
||||||
|
return `Reset ${variableName}`
|
||||||
|
case 'Random ID':
|
||||||
|
case 'Today':
|
||||||
|
case 'Tomorrow':
|
||||||
|
case 'User ID':
|
||||||
|
case 'Yesterday': {
|
||||||
|
return `${variableName} = ${options.type}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { FormLabel, HStack, Stack, Switch, Text } from '@chakra-ui/react'
|
import { FormLabel, Stack, Text } from '@chakra-ui/react'
|
||||||
import { CodeEditor } from '@/components/inputs/CodeEditor'
|
import { CodeEditor } from '@/components/inputs/CodeEditor'
|
||||||
import { SetVariableOptions, Variable } from '@typebot.io/schemas'
|
import { SetVariableOptions, Variable, valueTypes } from '@typebot.io/schemas'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { VariableSearchInput } from '@/components/inputs/VariableSearchInput'
|
import { VariableSearchInput } from '@/components/inputs/VariableSearchInput'
|
||||||
import { Textarea } from '@/components/inputs'
|
|
||||||
import { SwitchWithLabel } from '@/components/inputs/SwitchWithLabel'
|
import { SwitchWithLabel } from '@/components/inputs/SwitchWithLabel'
|
||||||
|
import { Select } from '@/components/inputs/Select'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
options: SetVariableOptions
|
options: SetVariableOptions
|
||||||
@ -15,19 +15,10 @@ export const SetVariableSettings = ({ options, onOptionsChange }: Props) => {
|
|||||||
const updateVariableId = (variable?: Variable) =>
|
const updateVariableId = (variable?: Variable) =>
|
||||||
onOptionsChange({ ...options, variableId: variable?.id })
|
onOptionsChange({ ...options, variableId: variable?.id })
|
||||||
|
|
||||||
const updateExpression = (expressionToEvaluate: string) =>
|
const updateValueType = (type?: string) =>
|
||||||
onOptionsChange({ ...options, expressionToEvaluate })
|
|
||||||
|
|
||||||
const updateExpressionType = () =>
|
|
||||||
onOptionsChange({
|
onOptionsChange({
|
||||||
...options,
|
...options,
|
||||||
isCode: options.isCode ? !options.isCode : true,
|
type: type as SetVariableOptions['type'],
|
||||||
})
|
|
||||||
|
|
||||||
const updateClientExecution = (isExecutedOnClient: boolean) =>
|
|
||||||
onOptionsChange({
|
|
||||||
...options,
|
|
||||||
isExecutedOnClient,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -42,42 +33,110 @@ export const SetVariableSettings = ({ options, onOptionsChange }: Props) => {
|
|||||||
id="variable-search"
|
id="variable-search"
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Stack>
|
|
||||||
<HStack justify="space-between">
|
|
||||||
<FormLabel mb="0" htmlFor="expression">
|
|
||||||
Value:
|
|
||||||
</FormLabel>
|
|
||||||
<HStack>
|
|
||||||
<Text fontSize="sm">Text</Text>
|
|
||||||
<Switch
|
|
||||||
size="sm"
|
|
||||||
isChecked={options.isCode ?? false}
|
|
||||||
onChange={updateExpressionType}
|
|
||||||
/>
|
|
||||||
<Text fontSize="sm">Code</Text>
|
|
||||||
</HStack>
|
|
||||||
</HStack>
|
|
||||||
|
|
||||||
{options.isCode ?? false ? (
|
<Stack>
|
||||||
|
<Text mb="0" fontWeight="medium">
|
||||||
|
Value:
|
||||||
|
</Text>
|
||||||
|
<Select
|
||||||
|
selectedItem={options.type ?? 'Custom'}
|
||||||
|
items={valueTypes}
|
||||||
|
onSelect={updateValueType}
|
||||||
|
/>
|
||||||
|
<SetVariableValue options={options} onOptionsChange={onOptionsChange} />
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const SetVariableValue = ({
|
||||||
|
options,
|
||||||
|
onOptionsChange,
|
||||||
|
}: {
|
||||||
|
options: SetVariableOptions
|
||||||
|
onOptionsChange: (options: SetVariableOptions) => void
|
||||||
|
}): JSX.Element | null => {
|
||||||
|
const updateExpression = (expressionToEvaluate: string) =>
|
||||||
|
onOptionsChange({ ...options, expressionToEvaluate })
|
||||||
|
|
||||||
|
const updateClientExecution = (isExecutedOnClient: boolean) =>
|
||||||
|
onOptionsChange({
|
||||||
|
...options,
|
||||||
|
isExecutedOnClient,
|
||||||
|
})
|
||||||
|
|
||||||
|
const updateItemVariableId = (variable?: Variable) =>
|
||||||
|
onOptionsChange({
|
||||||
|
...options,
|
||||||
|
mapListItemParams: {
|
||||||
|
...options.mapListItemParams,
|
||||||
|
baseItemVariableId: variable?.id,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const updateBaseListVariableId = (variable?: Variable) =>
|
||||||
|
onOptionsChange({
|
||||||
|
...options,
|
||||||
|
mapListItemParams: {
|
||||||
|
...options.mapListItemParams,
|
||||||
|
baseListVariableId: variable?.id,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const updateTargetListVariableId = (variable?: Variable) =>
|
||||||
|
onOptionsChange({
|
||||||
|
...options,
|
||||||
|
mapListItemParams: {
|
||||||
|
...options.mapListItemParams,
|
||||||
|
targetListVariableId: variable?.id,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
switch (options.type) {
|
||||||
|
case 'Custom':
|
||||||
|
case undefined:
|
||||||
|
return (
|
||||||
|
<>
|
||||||
<CodeEditor
|
<CodeEditor
|
||||||
defaultValue={options.expressionToEvaluate ?? ''}
|
defaultValue={options.expressionToEvaluate ?? ''}
|
||||||
onChange={updateExpression}
|
onChange={updateExpression}
|
||||||
lang="javascript"
|
lang="javascript"
|
||||||
/>
|
/>
|
||||||
) : (
|
<SwitchWithLabel
|
||||||
<Textarea
|
label="Execute on client?"
|
||||||
id="expression"
|
moreInfoContent="Check this if you need access to client-only variables like `window` or `document`."
|
||||||
defaultValue={options.expressionToEvaluate ?? ''}
|
initialValue={options.isExecutedOnClient ?? false}
|
||||||
onChange={updateExpression}
|
onCheckChange={updateClientExecution}
|
||||||
/>
|
/>
|
||||||
)}
|
</>
|
||||||
</Stack>
|
)
|
||||||
<SwitchWithLabel
|
case 'Map item with same index': {
|
||||||
label="Execute on client?"
|
return (
|
||||||
moreInfoContent="Check this if you need access to client-only variables like `window` or `document`."
|
<Stack p="2" rounded="md" borderWidth={1}>
|
||||||
initialValue={options.isExecutedOnClient ?? false}
|
<VariableSearchInput
|
||||||
onCheckChange={updateClientExecution}
|
initialVariableId={options.mapListItemParams?.baseItemVariableId}
|
||||||
/>
|
onSelectVariable={updateItemVariableId}
|
||||||
</Stack>
|
placeholder="Base item"
|
||||||
)
|
/>
|
||||||
|
<VariableSearchInput
|
||||||
|
initialVariableId={options.mapListItemParams?.baseListVariableId}
|
||||||
|
onSelectVariable={updateBaseListVariableId}
|
||||||
|
placeholder="Base list"
|
||||||
|
/>
|
||||||
|
<VariableSearchInput
|
||||||
|
initialVariableId={options.mapListItemParams?.targetListVariableId}
|
||||||
|
onSelectVariable={updateTargetListVariableId}
|
||||||
|
placeholder="Target list"
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
case 'Random ID':
|
||||||
|
case 'Today':
|
||||||
|
case 'Tomorrow':
|
||||||
|
case 'User ID':
|
||||||
|
case 'Yesterday':
|
||||||
|
case 'Empty':
|
||||||
|
return null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,10 @@ test.describe('Set variable block', () => {
|
|||||||
await page.click('text=Click to edit... >> nth = 0')
|
await page.click('text=Click to edit... >> nth = 0')
|
||||||
await page.fill('input[placeholder="Select a variable"] >> nth=-1', 'Total')
|
await page.fill('input[placeholder="Select a variable"] >> nth=-1', 'Total')
|
||||||
await page.getByRole('menuitem', { name: 'Create Total' }).click()
|
await page.getByRole('menuitem', { name: 'Create Total' }).click()
|
||||||
await page.fill('textarea', '1000 * {{Num}}')
|
await page
|
||||||
|
.getByTestId('code-editor')
|
||||||
|
.getByRole('textbox')
|
||||||
|
.fill('1000 * {{Num}}')
|
||||||
|
|
||||||
await page.click('text=Click to edit...', { force: true })
|
await page.click('text=Click to edit...', { force: true })
|
||||||
await page.fill(
|
await page.fill(
|
||||||
@ -30,7 +33,10 @@ test.describe('Set variable block', () => {
|
|||||||
'Custom var'
|
'Custom var'
|
||||||
)
|
)
|
||||||
await page.getByRole('menuitem', { name: 'Create Custom var' }).click()
|
await page.getByRole('menuitem', { name: 'Create Custom var' }).click()
|
||||||
await page.fill('textarea', 'Custom value')
|
await page
|
||||||
|
.getByTestId('code-editor')
|
||||||
|
.getByRole('textbox')
|
||||||
|
.fill('Custom value')
|
||||||
|
|
||||||
await page.click('text=Click to edit...', { force: true })
|
await page.click('text=Click to edit...', { force: true })
|
||||||
await page.fill(
|
await page.fill(
|
||||||
@ -38,7 +44,10 @@ test.describe('Set variable block', () => {
|
|||||||
'Addition'
|
'Addition'
|
||||||
)
|
)
|
||||||
await page.getByRole('menuitem', { name: 'Create Addition' }).click()
|
await page.getByRole('menuitem', { name: 'Create Addition' }).click()
|
||||||
await page.fill('textarea', '1000 + {{Total}}')
|
await page
|
||||||
|
.getByTestId('code-editor')
|
||||||
|
.getByRole('textbox')
|
||||||
|
.fill('1000 + {{Total}}')
|
||||||
|
|
||||||
await page.click('text=Preview')
|
await page.click('text=Preview')
|
||||||
await page
|
await page
|
||||||
|
@ -8,9 +8,11 @@ The "Set variable" block allows you to set a particular value to a variable.
|
|||||||
|
|
||||||
<img src="/img/blocks/logic/set-variable.png" width="600" alt="Set variable"/>
|
<img src="/img/blocks/logic/set-variable.png" width="600" alt="Set variable"/>
|
||||||
|
|
||||||
This value can be any kind of plain text but also **Javascript code**.
|
## Custom
|
||||||
|
|
||||||
## Expressions with existing variables
|
You can set your variable with any value with `Custom`. It can be any kind of plain text but also **Javascript code**.
|
||||||
|
|
||||||
|
### Expressions with existing variables
|
||||||
|
|
||||||
It means you can apply operations on existing variables.
|
It means you can apply operations on existing variables.
|
||||||
|
|
||||||
@ -54,9 +56,7 @@ Transform existing variable to upper case or lower case:
|
|||||||
{{Name}}.toLowerCase()
|
{{Name}}.toLowerCase()
|
||||||
```
|
```
|
||||||
|
|
||||||
## Code
|
This can also be Javascript code. It will read the returned value of the code and set it to your variable.
|
||||||
|
|
||||||
The code value should be written Javascript. It will read the returned value of the code and set it to your variable.
|
|
||||||
|
|
||||||
```js
|
```js
|
||||||
const name = 'John' + 'Smith'
|
const name = 'John' + 'Smith'
|
||||||
@ -88,26 +88,12 @@ For example,
|
|||||||
|
|
||||||
:::
|
:::
|
||||||
|
|
||||||
## Current Date
|
## Map
|
||||||
|
|
||||||
You can create a `Submitted at` (or any other name) variable using this code:
|
This is a convenient value block that allows you to easily get an item from a list that has the same index as an item from another list.
|
||||||
|
|
||||||
```js
|
When you are pulling data from another service, sometimes, you will have 2 lists: `Labels` and `Ids`. Labels are the data displayed to the user and Ids are the data used for other requests to that external service.
|
||||||
new Date().toISOString()
|
|
||||||
```
|
|
||||||
|
|
||||||
It will set the variable to the current date and time.
|
This value block allows you to find the `Id` from `Ids` with the same index as `Label` in `Labels`
|
||||||
|
|
||||||
## Random ID
|
<img src="/img/blocks/logic/set-variable-map-item.png" width="600" alt="Set variable map item with same index"/>
|
||||||
|
|
||||||
Or a random ID:
|
|
||||||
|
|
||||||
```js
|
|
||||||
Math.round(Math.random() * 1000000)
|
|
||||||
```
|
|
||||||
|
|
||||||
:::note
|
|
||||||
Keep in mind that the code is executed on the server. So you don't have access to browser variables such as `window` or `document`.
|
|
||||||
:::
|
|
||||||
|
|
||||||
The code can also be multi-line. The Set variable block will get the value following the `return` statement.
|
|
||||||
|
BIN
apps/docs/static/img/blocks/logic/set-variable-map-item.png
vendored
Normal file
BIN
apps/docs/static/img/blocks/logic/set-variable-map-item.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 174 KiB |
BIN
apps/docs/static/img/blocks/logic/set-variable.png
vendored
BIN
apps/docs/static/img/blocks/logic/set-variable.png
vendored
Binary file not shown.
Before Width: | Height: | Size: 288 KiB After Width: | Height: | Size: 121 KiB |
@ -33,10 +33,11 @@ export const executeSetVariable = async (
|
|||||||
],
|
],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const evaluatedExpression = block.options.expressionToEvaluate
|
const expressionToEvaluate = getExpressionToEvaluate(state.result.id)(
|
||||||
? evaluateSetVariableExpression(variables)(
|
block.options
|
||||||
block.options.expressionToEvaluate
|
)
|
||||||
)
|
const evaluatedExpression = expressionToEvaluate
|
||||||
|
? evaluateSetVariableExpression(variables)(expressionToEvaluate)
|
||||||
: undefined
|
: undefined
|
||||||
const existingVariable = variables.find(byId(block.options.variableId))
|
const existingVariable = variables.find(byId(block.options.variableId))
|
||||||
if (!existingVariable) return { outgoingEdgeId: block.outgoingEdgeId }
|
if (!existingVariable) return { outgoingEdgeId: block.outgoingEdgeId }
|
||||||
@ -67,3 +68,35 @@ const evaluateSetVariableExpression =
|
|||||||
return parseVariables(variables)(str)
|
return parseVariables(variables)(str)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getExpressionToEvaluate =
|
||||||
|
(resultId: string | undefined) =>
|
||||||
|
(options: SetVariableBlock['options']): string | null => {
|
||||||
|
switch (options.type) {
|
||||||
|
case 'Today':
|
||||||
|
return 'new Date().toISOString()'
|
||||||
|
case 'Tomorrow': {
|
||||||
|
return 'new Date(Date.now() + 86400000).toISOString()'
|
||||||
|
}
|
||||||
|
case 'Yesterday': {
|
||||||
|
return 'new Date(Date.now() - 86400000).toISOString()'
|
||||||
|
}
|
||||||
|
case 'Random ID': {
|
||||||
|
return 'Math.random().toString(36).substring(2, 15)'
|
||||||
|
}
|
||||||
|
case 'User ID': {
|
||||||
|
return resultId ?? 'Math.random().toString(36).substring(2, 15)'
|
||||||
|
}
|
||||||
|
case 'Map item with same index': {
|
||||||
|
return `const itemIndex = ${options.mapListItemParams?.baseListVariableId}.indexOf(${options.mapListItemParams?.baseItemVariableId})
|
||||||
|
return ${options.mapListItemParams?.targetListVariableId}.at(itemIndex)`
|
||||||
|
}
|
||||||
|
case 'Empty': {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
case 'Custom':
|
||||||
|
case undefined: {
|
||||||
|
return options.expressionToEvaluate ?? null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -2,10 +2,29 @@ import { z } from 'zod'
|
|||||||
import { blockBaseSchema } from '../baseSchemas'
|
import { blockBaseSchema } from '../baseSchemas'
|
||||||
import { LogicBlockType } from './enums'
|
import { LogicBlockType } from './enums'
|
||||||
|
|
||||||
|
export const valueTypes = [
|
||||||
|
'Custom',
|
||||||
|
'Empty',
|
||||||
|
'User ID',
|
||||||
|
'Today',
|
||||||
|
'Yesterday',
|
||||||
|
'Tomorrow',
|
||||||
|
'Random ID',
|
||||||
|
'Map item with same index',
|
||||||
|
] as const
|
||||||
|
|
||||||
export const setVariableOptionsSchema = z.object({
|
export const setVariableOptionsSchema = z.object({
|
||||||
variableId: z.string().optional(),
|
variableId: z.string().optional(),
|
||||||
expressionToEvaluate: z.string().optional(),
|
expressionToEvaluate: z.string().optional(),
|
||||||
isCode: z.boolean().optional(),
|
isCode: z.boolean().optional(),
|
||||||
|
type: z.enum(valueTypes).optional(),
|
||||||
|
mapListItemParams: z
|
||||||
|
.object({
|
||||||
|
baseItemVariableId: z.string().optional(),
|
||||||
|
baseListVariableId: z.string().optional(),
|
||||||
|
targetListVariableId: z.string().optional(),
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
isExecutedOnClient: z.boolean().optional(),
|
isExecutedOnClient: z.boolean().optional(),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user