2
0

refactor: ♻️ Rename step to block

This commit is contained in:
Baptiste Arnaud
2022-06-11 07:27:38 +02:00
parent 8751766d0e
commit 2df8338505
297 changed files with 4292 additions and 3989 deletions

View File

@ -13,23 +13,23 @@ export enum RightPanel {
const editorContext = createContext<{
rightPanel?: RightPanel
setRightPanel: Dispatch<SetStateAction<RightPanel | undefined>>
startPreviewAtBlock: string | undefined
setStartPreviewAtBlock: Dispatch<SetStateAction<string | undefined>>
startPreviewAtGroup: string | undefined
setStartPreviewAtGroup: Dispatch<SetStateAction<string | undefined>>
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
}>({})
export const EditorContext = ({ children }: { children: ReactNode }) => {
const [rightPanel, setRightPanel] = useState<RightPanel>()
const [startPreviewAtBlock, setStartPreviewAtBlock] = useState<string>()
const [startPreviewAtGroup, setStartPreviewAtGroup] = useState<string>()
return (
<editorContext.Provider
value={{
rightPanel,
setRightPanel,
startPreviewAtBlock,
setStartPreviewAtBlock,
startPreviewAtGroup,
setStartPreviewAtGroup,
}}
>
{children}

View File

@ -1,4 +1,4 @@
import { Block, Edge, IdMap, Source, Step, Target } from 'models'
import { Group, Edge, IdMap, Source, Block, Target } from 'models'
import {
createContext,
Dispatch,
@ -35,8 +35,8 @@ export type Anchor = {
coordinates: Coordinates
}
export type Node = Omit<Block, 'steps'> & {
steps: (Step & {
export type Node = Omit<Group, 'blocks'> & {
blocks: (Block & {
sourceAnchorsPosition: { left: Coordinates; right: Coordinates }
})[]
}
@ -48,18 +48,18 @@ export type ConnectingIds = {
target?: Target
}
type StepId = string
type BlockId = string
type ButtonId = string
export type Endpoint = {
id: StepId | ButtonId
id: BlockId | ButtonId
ref: MutableRefObject<HTMLDivElement | null>
}
export type BlocksCoordinates = IdMap<Coordinates>
export type GroupsCoordinates = IdMap<Coordinates>
const graphContext = createContext<{
blocksCoordinates: BlocksCoordinates
updateBlockCoordinates: (blockId: string, newCoord: Coordinates) => void
groupsCoordinates: GroupsCoordinates
updateGroupCoordinates: (groupId: string, newCoord: Coordinates) => void
graphPosition: Position
setGraphPosition: Dispatch<SetStateAction<Position>>
connectingIds: ConnectingIds | null
@ -70,11 +70,11 @@ const graphContext = createContext<{
addSourceEndpoint: (endpoint: Endpoint) => void
targetEndpoints: IdMap<Endpoint>
addTargetEndpoint: (endpoint: Endpoint) => void
openedStepId?: string
setOpenedStepId: Dispatch<SetStateAction<string | undefined>>
openedBlockId?: string
setOpenedBlockId: Dispatch<SetStateAction<string | undefined>>
isReadOnly: boolean
focusedBlockId?: string
setFocusedBlockId: Dispatch<SetStateAction<string | undefined>>
focusedGroupId?: string
setFocusedGroupId: Dispatch<SetStateAction<string | undefined>>
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
}>({
@ -84,11 +84,11 @@ const graphContext = createContext<{
export const GraphProvider = ({
children,
blocks,
groups,
isReadOnly = false,
}: {
children: ReactNode
blocks: Block[]
groups: Group[]
isReadOnly?: boolean
}) => {
const [graphPosition, setGraphPosition] = useState(graphPositionDefaultValue)
@ -96,15 +96,15 @@ export const GraphProvider = ({
const [previewingEdge, setPreviewingEdge] = useState<Edge>()
const [sourceEndpoints, setSourceEndpoints] = useState<IdMap<Endpoint>>({})
const [targetEndpoints, setTargetEndpoints] = useState<IdMap<Endpoint>>({})
const [openedStepId, setOpenedStepId] = useState<string>()
const [blocksCoordinates, setBlocksCoordinates] = useState<BlocksCoordinates>(
const [openedBlockId, setOpenedBlockId] = useState<string>()
const [groupsCoordinates, setGroupsCoordinates] = useState<GroupsCoordinates>(
{}
)
const [focusedBlockId, setFocusedBlockId] = useState<string>()
const [focusedGroupId, setFocusedGroupId] = useState<string>()
useEffect(() => {
setBlocksCoordinates(
blocks.reduce(
setGroupsCoordinates(
groups.reduce(
(coords, block) => ({
...coords,
[block.id]: block.graphCoordinates,
@ -113,7 +113,7 @@ export const GraphProvider = ({
)
)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [blocks])
}, [groups])
const addSourceEndpoint = (endpoint: Endpoint) => {
setSourceEndpoints((endpoints) => ({
@ -129,10 +129,10 @@ export const GraphProvider = ({
}))
}
const updateBlockCoordinates = (blockId: string, newCoord: Coordinates) =>
setBlocksCoordinates((blocksCoordinates) => ({
...blocksCoordinates,
[blockId]: newCoord,
const updateGroupCoordinates = (groupId: string, newCoord: Coordinates) =>
setGroupsCoordinates((groupsCoordinates) => ({
...groupsCoordinates,
[groupId]: newCoord,
}))
return (
@ -148,13 +148,13 @@ export const GraphProvider = ({
targetEndpoints,
addSourceEndpoint,
addTargetEndpoint,
openedStepId,
setOpenedStepId,
blocksCoordinates,
updateBlockCoordinates,
openedBlockId,
setOpenedBlockId,
groupsCoordinates,
updateGroupCoordinates,
isReadOnly,
focusedBlockId,
setFocusedBlockId,
focusedGroupId,
setFocusedGroupId,
}}
>
{children}

View File

@ -1,5 +1,5 @@
import { useEventListener } from '@chakra-ui/react'
import { ButtonItem, DraggableStep, DraggableStepType } from 'models'
import { ButtonItem, DraggableBlock, DraggableBlockType } from 'models'
import {
createContext,
Dispatch,
@ -11,20 +11,20 @@ import {
} from 'react'
import { Coordinates } from './GraphContext'
type BlockInfo = {
type GroupInfo = {
id: string
ref: React.MutableRefObject<HTMLDivElement | null>
}
const graphDndContext = createContext<{
draggedStepType?: DraggableStepType
setDraggedStepType: Dispatch<SetStateAction<DraggableStepType | undefined>>
draggedStep?: DraggableStep
setDraggedStep: Dispatch<SetStateAction<DraggableStep | undefined>>
draggedBlockType?: DraggableBlockType
setDraggedBlockType: Dispatch<SetStateAction<DraggableBlockType | undefined>>
draggedBlock?: DraggableBlock
setDraggedBlock: Dispatch<SetStateAction<DraggableBlock | undefined>>
draggedItem?: ButtonItem
setDraggedItem: Dispatch<SetStateAction<ButtonItem | undefined>>
mouseOverBlock?: BlockInfo
setMouseOverBlock: Dispatch<SetStateAction<BlockInfo | undefined>>
mouseOverGroup?: GroupInfo
setMouseOverGroup: Dispatch<SetStateAction<GroupInfo | undefined>>
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
}>({})
@ -32,24 +32,24 @@ const graphDndContext = createContext<{
export type NodePosition = { absolute: Coordinates; relative: Coordinates }
export const GraphDndContext = ({ children }: { children: ReactNode }) => {
const [draggedStep, setDraggedStep] = useState<DraggableStep>()
const [draggedStepType, setDraggedStepType] = useState<
DraggableStepType | undefined
const [draggedBlock, setDraggedBlock] = useState<DraggableBlock>()
const [draggedBlockType, setDraggedBlockType] = useState<
DraggableBlockType | undefined
>()
const [draggedItem, setDraggedItem] = useState<ButtonItem | undefined>()
const [mouseOverBlock, setMouseOverBlock] = useState<BlockInfo>()
const [mouseOverGroup, setMouseOverGroup] = useState<GroupInfo>()
return (
<graphDndContext.Provider
value={{
draggedStep,
setDraggedStep,
draggedStepType,
setDraggedStepType,
draggedBlock,
setDraggedBlock,
draggedBlockType,
setDraggedBlockType,
draggedItem,
setDraggedItem,
mouseOverBlock,
setMouseOverBlock,
mouseOverGroup,
setMouseOverGroup,
}}
>
{children}
@ -124,4 +124,4 @@ export const computeNearestPlaceholderIndex = (
return closestIndex
}
export const useStepDnd = () => useContext(graphDndContext)
export const useBlockDnd = () => useContext(graphDndContext)

View File

@ -1,5 +1,5 @@
import {
LogicStepType,
LogicBlockType,
PublicTypebot,
Settings,
Theme,
@ -30,8 +30,8 @@ import {
import { fetcher, preventUserFromRefreshing } from 'services/utils'
import useSWR from 'swr'
import { isDefined, isEmpty, isNotDefined, omit } from 'utils'
import { BlocksActions, blocksActions } from './actions/blocks'
import { stepsAction, StepsActions } from './actions/steps'
import { GroupsActions, groupsActions } from './actions/groups'
import { blocksAction, BlocksActions } from './actions/blocks'
import { variablesAction, VariablesActions } from './actions/variables'
import { edgesAction, EdgesActions } from './actions/edges'
import { useRegisterActions } from 'kbar'
@ -80,8 +80,8 @@ const typebotContext = createContext<
updateTypebot: (updates: UpdateTypebotPayload) => void
publishTypebot: () => void
restorePublishedTypebot: () => void
} & BlocksActions &
StepsActions &
} & GroupsActions &
BlocksActions &
ItemsActions &
VariablesActions &
EdgesActions
@ -122,13 +122,13 @@ export const TypebotContext = ({
},
] = useUndo<Typebot | undefined>(undefined)
const linkedTypebotIds = localTypebot?.blocks
.flatMap((b) => b.steps)
const linkedTypebotIds = localTypebot?.groups
.flatMap((b) => b.blocks)
.reduce<string[]>(
(typebotIds, step) =>
step.type === LogicStepType.TYPEBOT_LINK &&
isDefined(step.options.typebotId)
? [...typebotIds, step.options.typebotId]
(typebotIds, block) =>
block.type === LogicBlockType.TYPEBOT_LINK &&
isDefined(block.options.typebotId)
? [...typebotIds, block.options.typebotId]
: typebotIds,
[]
)
@ -360,8 +360,8 @@ export const TypebotContext = ({
updateTypebot: updateLocalTypebot,
restorePublishedTypebot,
updateWebhook,
...blocksActions(setLocalTypebot as SetTypebot),
...stepsAction(setLocalTypebot as SetTypebot),
...groupsActions(setLocalTypebot as SetTypebot),
...blocksAction(setLocalTypebot as SetTypebot),
...variablesAction(setLocalTypebot as SetTypebot),
...edgesAction(setLocalTypebot as SetTypebot),
...itemsAction(setLocalTypebot as SetTypebot),

View File

@ -1,102 +1,155 @@
import { Coordinates } from 'contexts/GraphContext'
import cuid from 'cuid'
import { produce } from 'immer'
import { WritableDraft } from 'immer/dist/internal'
import {
Block,
DraggableStep,
DraggableStepType,
StepIndices,
Typebot,
DraggableBlock,
DraggableBlockType,
BlockIndices,
} from 'models'
import { parseNewBlock } from 'services/typebots/typebots'
import { removeEmptyGroups } from './groups'
import { WritableDraft } from 'immer/dist/types/types-external'
import { SetTypebot } from '../TypebotContext'
import { cleanUpEdgeDraft } from './edges'
import { createStepDraft, duplicateStepDraft } from './steps'
import produce from 'immer'
import { cleanUpEdgeDraft, deleteEdgeDraft } from './edges'
import cuid from 'cuid'
import { byId, isWebhookBlock, blockHasItems } from 'utils'
import { duplicateItemDraft } from './items'
export type BlocksActions = {
createBlock: (
props: Coordinates & {
id: string
step: DraggableStep | DraggableStepType
indices: StepIndices
}
groupId: string,
block: DraggableBlock | DraggableBlockType,
indices: BlockIndices
) => void
updateBlock: (blockIndex: number, updates: Partial<Omit<Block, 'id'>>) => void
duplicateBlock: (blockIndex: number) => void
deleteBlock: (blockIndex: number) => void
updateBlock: (
indices: BlockIndices,
updates: Partial<Omit<Block, 'id' | 'type'>>
) => void
duplicateBlock: (indices: BlockIndices) => void
detachBlockFromGroup: (indices: BlockIndices) => void
deleteBlock: (indices: BlockIndices) => void
}
const blocksActions = (setTypebot: SetTypebot): BlocksActions => ({
createBlock: ({
id,
step,
indices,
...graphCoordinates
}: Coordinates & {
id: string
step: DraggableStep | DraggableStepType
indices: StepIndices
}) =>
const blocksAction = (setTypebot: SetTypebot): BlocksActions => ({
createBlock: (
groupId: string,
block: DraggableBlock | DraggableBlockType,
indices: BlockIndices
) =>
setTypebot((typebot) =>
produce(typebot, (typebot) => {
const newBlock: Block = {
id,
graphCoordinates,
title: `Group #${typebot.blocks.length}`,
steps: [],
}
typebot.blocks.push(newBlock)
createStepDraft(typebot, step, newBlock.id, indices)
createBlockDraft(typebot, block, groupId, indices)
})
),
updateBlock: (blockIndex: number, updates: Partial<Omit<Block, 'id'>>) =>
updateBlock: (
{ groupIndex, blockIndex }: BlockIndices,
updates: Partial<Omit<Block, 'id' | 'type'>>
) =>
setTypebot((typebot) =>
produce(typebot, (typebot) => {
const block = typebot.blocks[blockIndex]
typebot.blocks[blockIndex] = { ...block, ...updates }
const block = typebot.groups[groupIndex].blocks[blockIndex]
typebot.groups[groupIndex].blocks[blockIndex] = { ...block, ...updates }
})
),
duplicateBlock: (blockIndex: number) =>
duplicateBlock: ({ groupIndex, blockIndex }: BlockIndices) =>
setTypebot((typebot) =>
produce(typebot, (typebot) => {
const block = typebot.blocks[blockIndex]
const id = cuid()
const newBlock: Block = {
...block,
title: `${block.title} copy`,
id,
steps: block.steps.map(duplicateStepDraft(id)),
graphCoordinates: {
x: block.graphCoordinates.x + 200,
y: block.graphCoordinates.y + 100,
},
}
typebot.blocks.splice(blockIndex + 1, 0, newBlock)
const block = { ...typebot.groups[groupIndex].blocks[blockIndex] }
const newBlock = duplicateBlockDraft(block.groupId)(block)
typebot.groups[groupIndex].blocks.splice(blockIndex + 1, 0, newBlock)
})
),
deleteBlock: (blockIndex: number) =>
detachBlockFromGroup: (indices: BlockIndices) =>
setTypebot((typebot) => produce(typebot, removeBlockFromGroup(indices))),
deleteBlock: ({ groupIndex, blockIndex }: BlockIndices) =>
setTypebot((typebot) =>
produce(typebot, (typebot) => {
deleteBlockDraft(typebot)(blockIndex)
const removingBlock = typebot.groups[groupIndex].blocks[blockIndex]
removeBlockFromGroup({ groupIndex, blockIndex })(typebot)
cleanUpEdgeDraft(typebot, removingBlock.id)
removeEmptyGroups(typebot)
})
),
})
const deleteBlockDraft =
(typebot: WritableDraft<Typebot>) => (blockIndex: number) => {
cleanUpEdgeDraft(typebot, typebot.blocks[blockIndex].id)
typebot.blocks.splice(blockIndex, 1)
const removeBlockFromGroup =
({ groupIndex, blockIndex }: BlockIndices) =>
(typebot: WritableDraft<Typebot>) => {
typebot.groups[groupIndex].blocks.splice(blockIndex, 1)
}
const removeEmptyBlocks = (typebot: WritableDraft<Typebot>) => {
const emptyBlocksIndices = typebot.blocks.reduce<number[]>(
(arr, block, idx) => {
block.steps.length === 0 && arr.push(idx)
return arr
},
[]
const createBlockDraft = (
typebot: WritableDraft<Typebot>,
block: DraggableBlock | DraggableBlockType,
groupId: string,
{ groupIndex, blockIndex }: BlockIndices
) => {
const blocks = typebot.groups[groupIndex].blocks
if (
blockIndex === blocks.length &&
blockIndex > 0 &&
blocks[blockIndex - 1].outgoingEdgeId
)
emptyBlocksIndices.forEach(deleteBlockDraft(typebot))
deleteEdgeDraft(typebot, blocks[blockIndex - 1].outgoingEdgeId as string)
typeof block === 'string'
? createNewBlock(typebot, block, groupId, { groupIndex, blockIndex })
: moveBlockToGroup(typebot, block, groupId, { groupIndex, blockIndex })
removeEmptyGroups(typebot)
}
export { blocksActions, removeEmptyBlocks }
const createNewBlock = async (
typebot: WritableDraft<Typebot>,
type: DraggableBlockType,
groupId: string,
{ groupIndex, blockIndex }: BlockIndices
) => {
const newBlock = parseNewBlock(type, groupId)
typebot.groups[groupIndex].blocks.splice(blockIndex ?? 0, 0, newBlock)
}
const moveBlockToGroup = (
typebot: WritableDraft<Typebot>,
block: DraggableBlock,
groupId: string,
{ groupIndex, blockIndex }: BlockIndices
) => {
const newBlock = { ...block, groupId }
const items = blockHasItems(block) ? block.items : []
items.forEach((item) => {
const edgeIndex = typebot.edges.findIndex(byId(item.outgoingEdgeId))
if (edgeIndex === -1) return
typebot.edges[edgeIndex].from.groupId = groupId
})
if (block.outgoingEdgeId) {
if (typebot.groups[groupIndex].blocks.length > blockIndex ?? 0) {
deleteEdgeDraft(typebot, block.outgoingEdgeId)
newBlock.outgoingEdgeId = undefined
} else {
const edgeIndex = typebot.edges.findIndex(byId(block.outgoingEdgeId))
edgeIndex !== -1
? (typebot.edges[edgeIndex].from.groupId = groupId)
: (newBlock.outgoingEdgeId = undefined)
}
}
typebot.groups[groupIndex].blocks.splice(blockIndex ?? 0, 0, newBlock)
}
const duplicateBlockDraft =
(groupId: string) =>
(block: Block): Block => {
const blockId = cuid()
return {
...block,
groupId,
id: blockId,
items: blockHasItems(block)
? block.items.map(duplicateItemDraft(blockId))
: undefined,
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
webhookId: isWebhookBlock(block) ? cuid() : undefined,
outgoingEdgeId: undefined,
}
}
export { blocksAction, createBlockDraft, duplicateBlockDraft }

View File

@ -1,15 +1,15 @@
import {
Typebot,
Edge,
StepWithItems,
StepIndices,
BlockWithItems,
BlockIndices,
ItemIndices,
Step,
Block,
} from 'models'
import { WritableDraft } from 'immer/dist/types/types-external'
import { SetTypebot } from '../TypebotContext'
import { produce } from 'immer'
import { byId, isDefined, stepHasItems } from 'utils'
import { byId, isDefined, blockHasItems } from 'utils'
import cuid from 'cuid'
export type EdgesActions = {
@ -28,25 +28,25 @@ export const edgesAction = (setTypebot: SetTypebot): EdgesActions => ({
}
removeExistingEdge(typebot, edge)
typebot.edges.push(newEdge)
const blockIndex = typebot.blocks.findIndex(byId(edge.from.blockId))
const stepIndex = typebot.blocks[blockIndex].steps.findIndex(
byId(edge.from.stepId)
const groupIndex = typebot.groups.findIndex(byId(edge.from.groupId))
const blockIndex = typebot.groups[groupIndex].blocks.findIndex(
byId(edge.from.blockId)
)
const itemIndex = edge.from.itemId
? (
typebot.blocks[blockIndex].steps[stepIndex] as StepWithItems
typebot.groups[groupIndex].blocks[blockIndex] as BlockWithItems
).items.findIndex(byId(edge.from.itemId))
: null
isDefined(itemIndex) && itemIndex !== -1
? addEdgeIdToItem(typebot, newEdge.id, {
groupIndex,
blockIndex,
stepIndex,
itemIndex,
})
: addEdgeIdToStep(typebot, newEdge.id, {
: addEdgeIdToBlock(typebot, newEdge.id, {
groupIndex,
blockIndex,
stepIndex,
})
})
),
@ -68,20 +68,20 @@ export const edgesAction = (setTypebot: SetTypebot): EdgesActions => ({
),
})
const addEdgeIdToStep = (
const addEdgeIdToBlock = (
typebot: WritableDraft<Typebot>,
edgeId: string,
{ blockIndex, stepIndex }: StepIndices
{ groupIndex, blockIndex }: BlockIndices
) => {
typebot.blocks[blockIndex].steps[stepIndex].outgoingEdgeId = edgeId
typebot.groups[groupIndex].blocks[blockIndex].outgoingEdgeId = edgeId
}
const addEdgeIdToItem = (
typebot: WritableDraft<Typebot>,
edgeId: string,
{ blockIndex, stepIndex, itemIndex }: ItemIndices
{ groupIndex, blockIndex, itemIndex }: ItemIndices
) =>
((typebot.blocks[blockIndex].steps[stepIndex] as StepWithItems).items[
((typebot.groups[groupIndex].blocks[blockIndex] as BlockWithItems).items[
itemIndex
].outgoingEdgeId = edgeId)
@ -101,23 +101,23 @@ const deleteOutgoingEdgeIdProps = (
) => {
const edge = typebot.edges.find(byId(edgeId))
if (!edge) return
const fromBlockIndex = typebot.blocks.findIndex(byId(edge.from.blockId))
const fromStepIndex = typebot.blocks[fromBlockIndex].steps.findIndex(
byId(edge.from.stepId)
const fromGroupIndex = typebot.groups.findIndex(byId(edge.from.groupId))
const fromBlockIndex = typebot.groups[fromGroupIndex].blocks.findIndex(
byId(edge.from.blockId)
)
const step = typebot.blocks[fromBlockIndex].steps[fromStepIndex] as
| Step
const block = typebot.groups[fromGroupIndex].blocks[fromBlockIndex] as
| Block
| undefined
const fromItemIndex =
edge.from.itemId && step && stepHasItems(step)
? step.items.findIndex(byId(edge.from.itemId))
edge.from.itemId && block && blockHasItems(block)
? block.items.findIndex(byId(edge.from.itemId))
: -1
if (fromStepIndex !== -1)
typebot.blocks[fromBlockIndex].steps[fromStepIndex].outgoingEdgeId =
if (fromBlockIndex !== -1)
typebot.groups[fromGroupIndex].blocks[fromBlockIndex].outgoingEdgeId =
undefined
if (fromItemIndex !== -1)
(
typebot.blocks[fromBlockIndex].steps[fromStepIndex] as StepWithItems
typebot.groups[fromGroupIndex].blocks[fromBlockIndex] as BlockWithItems
).items[fromItemIndex].outgoingEdgeId = undefined
}
@ -127,11 +127,11 @@ export const cleanUpEdgeDraft = (
) => {
const edgesToDelete = typebot.edges.filter((edge) =>
[
edge.from.groupId,
edge.from.blockId,
edge.from.stepId,
edge.from.itemId,
edge.to.groupId,
edge.to.blockId,
edge.to.stepId,
].includes(deletedNodeId)
)
edgesToDelete.forEach((edge) => deleteEdgeDraft(typebot, edge.id))
@ -144,6 +144,6 @@ const removeExistingEdge = (
typebot.edges = typebot.edges.filter((e) =>
edge.from.itemId
? e.from.itemId !== edge.from.itemId
: isDefined(e.from.itemId) || e.from.stepId !== edge.from.stepId
: isDefined(e.from.itemId) || e.from.blockId !== edge.from.blockId
)
}

View File

@ -0,0 +1,102 @@
import { Coordinates } from 'contexts/GraphContext'
import cuid from 'cuid'
import { produce } from 'immer'
import { WritableDraft } from 'immer/dist/internal'
import {
Group,
DraggableBlock,
DraggableBlockType,
BlockIndices,
Typebot,
} from 'models'
import { SetTypebot } from '../TypebotContext'
import { cleanUpEdgeDraft } from './edges'
import { createBlockDraft, duplicateBlockDraft } from './blocks'
export type GroupsActions = {
createGroup: (
props: Coordinates & {
id: string
block: DraggableBlock | DraggableBlockType
indices: BlockIndices
}
) => void
updateGroup: (groupIndex: number, updates: Partial<Omit<Group, 'id'>>) => void
duplicateGroup: (groupIndex: number) => void
deleteGroup: (groupIndex: number) => void
}
const groupsActions = (setTypebot: SetTypebot): GroupsActions => ({
createGroup: ({
id,
block,
indices,
...graphCoordinates
}: Coordinates & {
id: string
block: DraggableBlock | DraggableBlockType
indices: BlockIndices
}) =>
setTypebot((typebot) =>
produce(typebot, (typebot) => {
const newGroup: Group = {
id,
graphCoordinates,
title: `Group #${typebot.groups.length}`,
blocks: [],
}
typebot.groups.push(newGroup)
createBlockDraft(typebot, block, newGroup.id, indices)
})
),
updateGroup: (groupIndex: number, updates: Partial<Omit<Group, 'id'>>) =>
setTypebot((typebot) =>
produce(typebot, (typebot) => {
const block = typebot.groups[groupIndex]
typebot.groups[groupIndex] = { ...block, ...updates }
})
),
duplicateGroup: (groupIndex: number) =>
setTypebot((typebot) =>
produce(typebot, (typebot) => {
const group = typebot.groups[groupIndex]
const id = cuid()
const newGroup: Group = {
...group,
title: `${group.title} copy`,
id,
blocks: group.blocks.map(duplicateBlockDraft(id)),
graphCoordinates: {
x: group.graphCoordinates.x + 200,
y: group.graphCoordinates.y + 100,
},
}
typebot.groups.splice(groupIndex + 1, 0, newGroup)
})
),
deleteGroup: (groupIndex: number) =>
setTypebot((typebot) =>
produce(typebot, (typebot) => {
deleteGroupDraft(typebot)(groupIndex)
})
),
})
const deleteGroupDraft =
(typebot: WritableDraft<Typebot>) => (groupIndex: number) => {
cleanUpEdgeDraft(typebot, typebot.groups[groupIndex].id)
typebot.groups.splice(groupIndex, 1)
}
const removeEmptyGroups = (typebot: WritableDraft<Typebot>) => {
const emptyGroupsIndices = typebot.groups.reduce<number[]>(
(arr, group, idx) => {
group.blocks.length === 0 && arr.push(idx)
return arr
},
[]
)
emptyGroupsIndices.forEach(deleteGroupDraft(typebot))
}
export { groupsActions, removeEmptyGroups }

View File

@ -1,14 +1,14 @@
import {
ItemIndices,
Item,
InputStepType,
StepWithItems,
InputBlockType,
BlockWithItems,
ButtonItem,
} from 'models'
import { SetTypebot } from '../TypebotContext'
import produce from 'immer'
import { cleanUpEdgeDraft } from './edges'
import { byId, stepHasItems } from 'utils'
import { byId, blockHasItems } from 'utils'
import cuid from 'cuid'
export type ItemsActions = {
@ -17,79 +17,79 @@ export type ItemsActions = {
indices: ItemIndices
) => void
updateItem: (indices: ItemIndices, updates: Partial<Omit<Item, 'id'>>) => void
detachItemFromStep: (indices: ItemIndices) => void
detachItemFromBlock: (indices: ItemIndices) => void
deleteItem: (indices: ItemIndices) => void
}
const itemsAction = (setTypebot: SetTypebot): ItemsActions => ({
createItem: (
item: ButtonItem | Omit<ButtonItem, 'id'>,
{ blockIndex, stepIndex, itemIndex }: ItemIndices
{ groupIndex, blockIndex, itemIndex }: ItemIndices
) =>
setTypebot((typebot) =>
produce(typebot, (typebot) => {
const step = typebot.blocks[blockIndex].steps[stepIndex]
if (step.type !== InputStepType.CHOICE) return
const block = typebot.groups[groupIndex].blocks[blockIndex]
if (block.type !== InputBlockType.CHOICE) return
const newItem = {
...item,
stepId: step.id,
blockId: block.id,
id: 'id' in item ? item.id : cuid(),
}
if (item.outgoingEdgeId) {
const edgeIndex = typebot.edges.findIndex(byId(item.outgoingEdgeId))
edgeIndex !== -1
? (typebot.edges[edgeIndex].from = {
blockId: step.blockId,
stepId: step.id,
groupId: block.groupId,
blockId: block.id,
itemId: newItem.id,
})
: (newItem.outgoingEdgeId = undefined)
}
step.items.splice(itemIndex, 0, newItem)
block.items.splice(itemIndex, 0, newItem)
})
),
updateItem: (
{ blockIndex, stepIndex, itemIndex }: ItemIndices,
{ groupIndex, blockIndex, itemIndex }: ItemIndices,
updates: Partial<Omit<Item, 'id'>>
) =>
setTypebot((typebot) =>
produce(typebot, (typebot) => {
const step = typebot.blocks[blockIndex].steps[stepIndex]
if (!stepHasItems(step)) return
;(typebot.blocks[blockIndex].steps[stepIndex] as StepWithItems).items[
itemIndex
] = {
...step.items[itemIndex],
const block = typebot.groups[groupIndex].blocks[blockIndex]
if (!blockHasItems(block)) return
;(
typebot.groups[groupIndex].blocks[blockIndex] as BlockWithItems
).items[itemIndex] = {
...block.items[itemIndex],
...updates,
} as Item
})
),
detachItemFromStep: ({ blockIndex, stepIndex, itemIndex }: ItemIndices) =>
detachItemFromBlock: ({ groupIndex, blockIndex, itemIndex }: ItemIndices) =>
setTypebot((typebot) =>
produce(typebot, (typebot) => {
const step = typebot.blocks[blockIndex].steps[
stepIndex
] as StepWithItems
step.items.splice(itemIndex, 1)
const block = typebot.groups[groupIndex].blocks[
blockIndex
] as BlockWithItems
block.items.splice(itemIndex, 1)
})
),
deleteItem: ({ blockIndex, stepIndex, itemIndex }: ItemIndices) =>
deleteItem: ({ groupIndex, blockIndex, itemIndex }: ItemIndices) =>
setTypebot((typebot) =>
produce(typebot, (typebot) => {
const step = typebot.blocks[blockIndex].steps[
stepIndex
] as StepWithItems
const removingItem = step.items[itemIndex]
step.items.splice(itemIndex, 1)
const block = typebot.groups[groupIndex].blocks[
blockIndex
] as BlockWithItems
const removingItem = block.items[itemIndex]
block.items.splice(itemIndex, 1)
cleanUpEdgeDraft(typebot, removingItem.id)
})
),
})
const duplicateItemDraft = (stepId: string) => (item: Item) => ({
const duplicateItemDraft = (blockId: string) => (item: Item) => ({
...item,
id: cuid(),
stepId,
blockId,
outgoingEdgeId: undefined,
})

View File

@ -1,155 +0,0 @@
import {
Step,
Typebot,
DraggableStep,
DraggableStepType,
StepIndices,
} from 'models'
import { parseNewStep } from 'services/typebots/typebots'
import { removeEmptyBlocks } from './blocks'
import { WritableDraft } from 'immer/dist/types/types-external'
import { SetTypebot } from '../TypebotContext'
import produce from 'immer'
import { cleanUpEdgeDraft, deleteEdgeDraft } from './edges'
import cuid from 'cuid'
import { byId, isWebhookStep, stepHasItems } from 'utils'
import { duplicateItemDraft } from './items'
export type StepsActions = {
createStep: (
blockId: string,
step: DraggableStep | DraggableStepType,
indices: StepIndices
) => void
updateStep: (
indices: StepIndices,
updates: Partial<Omit<Step, 'id' | 'type'>>
) => void
duplicateStep: (indices: StepIndices) => void
detachStepFromBlock: (indices: StepIndices) => void
deleteStep: (indices: StepIndices) => void
}
const stepsAction = (setTypebot: SetTypebot): StepsActions => ({
createStep: (
blockId: string,
step: DraggableStep | DraggableStepType,
indices: StepIndices
) =>
setTypebot((typebot) =>
produce(typebot, (typebot) => {
createStepDraft(typebot, step, blockId, indices)
})
),
updateStep: (
{ blockIndex, stepIndex }: StepIndices,
updates: Partial<Omit<Step, 'id' | 'type'>>
) =>
setTypebot((typebot) =>
produce(typebot, (typebot) => {
const step = typebot.blocks[blockIndex].steps[stepIndex]
typebot.blocks[blockIndex].steps[stepIndex] = { ...step, ...updates }
})
),
duplicateStep: ({ blockIndex, stepIndex }: StepIndices) =>
setTypebot((typebot) =>
produce(typebot, (typebot) => {
const step = { ...typebot.blocks[blockIndex].steps[stepIndex] }
const newStep = duplicateStepDraft(step.blockId)(step)
typebot.blocks[blockIndex].steps.splice(stepIndex + 1, 0, newStep)
})
),
detachStepFromBlock: (indices: StepIndices) =>
setTypebot((typebot) => produce(typebot, removeStepFromBlock(indices))),
deleteStep: ({ blockIndex, stepIndex }: StepIndices) =>
setTypebot((typebot) =>
produce(typebot, (typebot) => {
const removingStep = typebot.blocks[blockIndex].steps[stepIndex]
removeStepFromBlock({ blockIndex, stepIndex })(typebot)
cleanUpEdgeDraft(typebot, removingStep.id)
removeEmptyBlocks(typebot)
})
),
})
const removeStepFromBlock =
({ blockIndex, stepIndex }: StepIndices) =>
(typebot: WritableDraft<Typebot>) => {
typebot.blocks[blockIndex].steps.splice(stepIndex, 1)
}
const createStepDraft = (
typebot: WritableDraft<Typebot>,
step: DraggableStep | DraggableStepType,
blockId: string,
{ blockIndex, stepIndex }: StepIndices
) => {
const steps = typebot.blocks[blockIndex].steps
if (
stepIndex === steps.length &&
stepIndex > 0 &&
steps[stepIndex - 1].outgoingEdgeId
)
deleteEdgeDraft(typebot, steps[stepIndex - 1].outgoingEdgeId as string)
typeof step === 'string'
? createNewStep(typebot, step, blockId, { blockIndex, stepIndex })
: moveStepToBlock(typebot, step, blockId, { blockIndex, stepIndex })
removeEmptyBlocks(typebot)
}
const createNewStep = async (
typebot: WritableDraft<Typebot>,
type: DraggableStepType,
blockId: string,
{ blockIndex, stepIndex }: StepIndices
) => {
const newStep = parseNewStep(type, blockId)
typebot.blocks[blockIndex].steps.splice(stepIndex ?? 0, 0, newStep)
}
const moveStepToBlock = (
typebot: WritableDraft<Typebot>,
step: DraggableStep,
blockId: string,
{ blockIndex, stepIndex }: StepIndices
) => {
const newStep = { ...step, blockId }
const items = stepHasItems(step) ? step.items : []
items.forEach((item) => {
const edgeIndex = typebot.edges.findIndex(byId(item.outgoingEdgeId))
if (edgeIndex === -1) return
typebot.edges[edgeIndex].from.blockId = blockId
})
if (step.outgoingEdgeId) {
if (typebot.blocks[blockIndex].steps.length > stepIndex ?? 0) {
deleteEdgeDraft(typebot, step.outgoingEdgeId)
newStep.outgoingEdgeId = undefined
} else {
const edgeIndex = typebot.edges.findIndex(byId(step.outgoingEdgeId))
edgeIndex !== -1
? (typebot.edges[edgeIndex].from.blockId = blockId)
: (newStep.outgoingEdgeId = undefined)
}
}
typebot.blocks[blockIndex].steps.splice(stepIndex ?? 0, 0, newStep)
}
const duplicateStepDraft =
(blockId: string) =>
(step: Step): Step => {
const stepId = cuid()
return {
...step,
blockId,
id: stepId,
items: stepHasItems(step)
? step.items.map(duplicateItemDraft(stepId))
: undefined,
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
webhookId: isWebhookStep(step) ? cuid() : undefined,
outgoingEdgeId: undefined,
}
}
export { stepsAction, createStepDraft, duplicateStepDraft }