2
0

🚸 (condition) Enable multiple condition items in one block

Closes #162
This commit is contained in:
Baptiste Arnaud
2022-11-16 14:56:09 +01:00
parent 96eb77d94b
commit 6725c17a02
24 changed files with 327 additions and 216 deletions

View File

@ -1,26 +1,16 @@
import { Flex } from '@chakra-ui/react'
import { DropdownList } from '@/components/DropdownList'
import {
Comparison,
ConditionItem,
ConditionBlock,
LogicalOperator,
} from 'models'
import { Comparison, ConditionItem, LogicalOperator } from 'models'
import React from 'react'
import { ComparisonItem } from './ComparisonsItem'
import { ComparisonItem } from './ComparisonItem'
import { TableList } from '@/components/TableList'
type ConditionSettingsBodyProps = {
block: ConditionBlock
type Props = {
itemContent: ConditionItem['content']
onItemChange: (updates: Partial<ConditionItem>) => void
}
export const ConditionSettingsBody = ({
block,
onItemChange,
}: ConditionSettingsBodyProps) => {
const itemContent = block.items[0].content
export const ConditionItemForm = ({ itemContent, onItemChange }: Props) => {
const handleComparisonsChange = (comparisons: Comparison[]) =>
onItemChange({ content: { ...itemContent, comparisons } })
const handleLogicalOperatorChange = (logicalOperator: LogicalOperator) =>

View File

@ -0,0 +1 @@
export * from './ConditionItemForm'

View File

@ -0,0 +1,180 @@
import {
Stack,
Tag,
Text,
Flex,
Wrap,
Fade,
IconButton,
PopoverTrigger,
Popover,
Portal,
PopoverContent,
PopoverArrow,
PopoverBody,
useEventListener,
} from '@chakra-ui/react'
import { useTypebot } from '@/features/editor'
import {
Comparison,
ConditionItem,
ComparisonOperators,
ItemType,
ItemIndices,
} from 'models'
import React, { useRef } from 'react'
import { byId, isNotDefined } from 'utils'
import { PlusIcon } from '@/components/icons'
import { ConditionItemForm } from './ConditionItemForm'
import { useGraph } from '@/features/graph'
import cuid from 'cuid'
type Props = {
item: ConditionItem
isMouseOver: boolean
indices: ItemIndices
}
export const ConditionItemNode = ({ item, isMouseOver, indices }: Props) => {
const { typebot, createItem, updateItem } = useTypebot()
const { openedItemId, setOpenedItemId } = useGraph()
const ref = useRef<HTMLDivElement | null>(null)
const handleMouseDown = (e: React.MouseEvent) => e.stopPropagation()
const openPopover = () => {
setOpenedItemId(item.id)
}
const handleItemChange = (updates: Partial<ConditionItem>) => {
updateItem(indices, { ...item, ...updates })
}
const handlePlusClick = (event: React.MouseEvent) => {
event.stopPropagation()
const itemIndex = indices.itemIndex + 1
const newItemId = cuid()
createItem(
{
blockId: item.blockId,
type: ItemType.CONDITION,
id: newItemId,
},
{ ...indices, itemIndex }
)
setOpenedItemId(newItemId)
}
const handleMouseWheel = (e: WheelEvent) => {
e.stopPropagation()
}
useEventListener('wheel', handleMouseWheel, ref.current)
return (
<Popover
placement="left"
isLazy
isOpen={openedItemId === item.id}
closeOnBlur={false}
>
<PopoverTrigger>
<Flex p={3} pos="relative" w="full" onClick={openPopover}>
{item.content.comparisons.length === 0 ||
comparisonIsEmpty(item.content.comparisons[0]) ? (
<Text color={'gray.500'}>Configure...</Text>
) : (
<Stack maxW="170px">
{item.content.comparisons.map((comparison, idx) => {
const variable = typebot?.variables.find(
byId(comparison.variableId)
)
return (
<Wrap key={comparison.id} spacing={1} noOfLines={1}>
{idx > 0 && (
<Text>{item.content.logicalOperator ?? ''}</Text>
)}
{variable?.name && (
<Tag bgColor="orange.400" color="white">
{variable.name}
</Tag>
)}
{comparison.comparisonOperator && (
<Text>
{parseComparisonOperatorSymbol(
comparison.comparisonOperator
)}
</Text>
)}
{comparison?.value && (
<Tag bgColor={'gray.200'}>
<Text noOfLines={1}>{comparison.value}</Text>
</Tag>
)}
</Wrap>
)
})}
</Stack>
)}
<Fade
in={isMouseOver}
style={{
position: 'absolute',
bottom: '-15px',
zIndex: 3,
left: '90px',
}}
unmountOnExit
>
<IconButton
aria-label="Add item"
icon={<PlusIcon />}
size="xs"
shadow="md"
colorScheme="gray"
onClick={handlePlusClick}
/>
</Fade>
</Flex>
</PopoverTrigger>
<Portal>
<PopoverContent pos="relative" onMouseDown={handleMouseDown}>
<PopoverArrow />
<PopoverBody
py="6"
overflowY="scroll"
maxH="400px"
shadow="lg"
ref={ref}
>
<ConditionItemForm
itemContent={item.content}
onItemChange={handleItemChange}
/>
</PopoverBody>
</PopoverContent>
</Portal>
</Popover>
)
}
const comparisonIsEmpty = (comparison: Comparison) =>
isNotDefined(comparison.comparisonOperator) &&
isNotDefined(comparison.value) &&
isNotDefined(comparison.variableId)
const parseComparisonOperatorSymbol = (operator: ComparisonOperators) => {
switch (operator) {
case ComparisonOperators.CONTAINS:
return 'contains'
case ComparisonOperators.EQUAL:
return '='
case ComparisonOperators.GREATER:
return '>'
case ComparisonOperators.IS_SET:
return 'is set'
case ComparisonOperators.LESS:
return '<'
case ComparisonOperators.NOT_EQUAL:
return '!='
}
}

View File

@ -1,73 +0,0 @@
import { Stack, Tag, Text, Flex, Wrap } from '@chakra-ui/react'
import { useTypebot } from '@/features/editor'
import { Comparison, ConditionItem, ComparisonOperators } from 'models'
import React from 'react'
import { byId, isNotDefined } from 'utils'
type Props = {
item: ConditionItem
}
export const ConditionNodeContent = ({ item }: Props) => {
const { typebot } = useTypebot()
return (
<Flex px={2} py={2}>
{item.content.comparisons.length === 0 ||
comparisonIsEmpty(item.content.comparisons[0]) ? (
<Text color={'gray.500'}>Configure...</Text>
) : (
<Stack maxW="170px">
{item.content.comparisons.map((comparison, idx) => {
const variable = typebot?.variables.find(
byId(comparison.variableId)
)
return (
<Wrap key={comparison.id} spacing={1} noOfLines={1}>
{idx > 0 && <Text>{item.content.logicalOperator ?? ''}</Text>}
{variable?.name && (
<Tag bgColor="orange.400" color="white">
{variable.name}
</Tag>
)}
{comparison.comparisonOperator && (
<Text>
{parseComparisonOperatorSymbol(
comparison.comparisonOperator
)}
</Text>
)}
{comparison?.value && (
<Tag bgColor={'gray.200'}>
<Text noOfLines={1}>{comparison.value}</Text>
</Tag>
)}
</Wrap>
)
})}
</Stack>
)}
</Flex>
)
}
const comparisonIsEmpty = (comparison: Comparison) =>
isNotDefined(comparison.comparisonOperator) &&
isNotDefined(comparison.value) &&
isNotDefined(comparison.variableId)
const parseComparisonOperatorSymbol = (operator: ComparisonOperators) => {
switch (operator) {
case ComparisonOperators.CONTAINS:
return 'contains'
case ComparisonOperators.EQUAL:
return '='
case ComparisonOperators.GREATER:
return '>'
case ComparisonOperators.IS_SET:
return 'is set'
case ComparisonOperators.LESS:
return '<'
case ComparisonOperators.NOT_EQUAL:
return '!='
}
}

View File

@ -1 +0,0 @@
export { ConditionSettingsBody } from './ConditonSettingsBody'

View File

@ -1,3 +1,2 @@
export { ConditionSettingsBody } from './components/ConditionSettingsBody'
export { ConditionNodeContent } from './components/ConditionNodeContent'
export { ConditionIcon } from './components/ConditionIcon'
export * from './components/ConditionItemNode'
export * from './components/ConditionIcon'