Skip to content

Commit

Permalink
🚸 (condition) Enable multiple condition items in one block
Browse files Browse the repository at this point in the history
Closes #162
  • Loading branch information
baptisteArno committed Nov 16, 2022
1 parent 96eb77d commit 6725c17
Show file tree
Hide file tree
Showing 24 changed files with 327 additions and 216 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ type Props = {
isMouseOver: boolean
}

export const ButtonNodeContent = ({ item, indices, isMouseOver }: Props) => {
export const ButtonsItemNode = ({ item, indices, isMouseOver }: Props) => {
const { deleteItem, updateItem, createItem } = useTypebot()
const [initialContent] = useState(item.content ?? '')
const [itemValue, setItemValue] = useState(item.content ?? 'Click to edit')
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './ButtonsItemNode'
export * from './ButtonsIcon'
export * from './ButtonsOptionsForm'
4 changes: 1 addition & 3 deletions apps/builder/src/features/blocks/inputs/buttons/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1 @@
export { ButtonsOptionsForm } from './components/ButtonsOptionsForm'
export { ButtonNodeContent } from './components/ButtonNodeContent'
export { ButtonsInputIcon } from './components/ButtonsInputIcon'
export * from './components'
Original file line number Diff line number Diff line change
@@ -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) =>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './ConditionItemForm'
Original file line number Diff line number Diff line change
@@ -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 '!='
}
}

This file was deleted.

This file was deleted.

5 changes: 2 additions & 3 deletions apps/builder/src/features/blocks/logic/condition/index.ts
Original file line number Diff line number Diff line change
@@ -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'
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import {
ItemIndices,
Item,
InputBlockType,
BlockWithItems,
ButtonItem,
defaultConditionContent,
ItemType,
} from 'models'
import { SetTypebot } from '../TypebotProvider'
import produce from 'immer'
Expand All @@ -12,29 +12,31 @@ import { byId, blockHasItems } from 'utils'
import cuid from 'cuid'

export type ItemsActions = {
createItem: (
item: ButtonItem | Omit<ButtonItem, 'id'>,
indices: ItemIndices
) => void
createItem: (item: Item | Omit<Item, 'id'>, indices: ItemIndices) => void
updateItem: (indices: ItemIndices, updates: Partial<Omit<Item, 'id'>>) => void
detachItemFromBlock: (indices: ItemIndices) => void
deleteItem: (indices: ItemIndices) => void
}

const itemsAction = (setTypebot: SetTypebot): ItemsActions => ({
createItem: (
item: ButtonItem | Omit<ButtonItem, 'id'>,
item: Item | Omit<Item, 'id'>,
{ groupIndex, blockIndex, itemIndex }: ItemIndices
) =>
setTypebot((typebot) =>
produce(typebot, (typebot) => {
const block = typebot.groups[groupIndex].blocks[blockIndex]
if (block.type !== InputBlockType.CHOICE) return
const block = typebot.groups[groupIndex].blocks[
blockIndex
] as BlockWithItems

const newItem = {
...item,
blockId: block.id,
id: 'id' in item ? item.id : cuid(),
}
content:
item.type === ItemType.CONDITION
? defaultConditionContent
: undefined,
...item,
} as Item
if (item.outgoingEdgeId) {
const edgeIndex = typebot.edges.findIndex(byId(item.outgoingEdgeId))
edgeIndex !== -1
Expand Down
2 changes: 2 additions & 0 deletions apps/builder/src/features/graph/components/Graph.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export const Graph = ({
const {
setGraphPosition: setGlobalGraphPosition,
setOpenedBlockId,
setOpenedItemId,
setPreviewingEdge,
connectingIds,
} = useGraph()
Expand Down Expand Up @@ -126,6 +127,7 @@ export const Graph = ({

const handleClick = () => {
setOpenedBlockId(undefined)
setOpenedItemId(undefined)
setPreviewingEdge(undefined)
}

Expand Down
Loading

0 comments on commit 6725c17

Please sign in to comment.