feat(inputs): ✨ Add buttons input
This commit is contained in:
@ -1,4 +1,10 @@
|
||||
import { BubbleStep, BubbleStepType, InputStep, InputStepType } from 'models'
|
||||
import {
|
||||
BubbleStep,
|
||||
BubbleStepType,
|
||||
ChoiceItem,
|
||||
InputStep,
|
||||
InputStepType,
|
||||
} from 'models'
|
||||
import {
|
||||
createContext,
|
||||
Dispatch,
|
||||
@ -16,16 +22,23 @@ const dndContext = createContext<{
|
||||
setDraggedStepType: Dispatch<SetStateAction<DraggableStepType | undefined>>
|
||||
draggedStep?: DraggableStep
|
||||
setDraggedStep: Dispatch<SetStateAction<DraggableStep | undefined>>
|
||||
}>({
|
||||
setDraggedStep: () => console.log("I'm not implemented"),
|
||||
setDraggedStepType: () => console.log("I'm not implemented"),
|
||||
})
|
||||
draggedChoiceItem?: ChoiceItem
|
||||
setDraggedChoiceItem: Dispatch<SetStateAction<ChoiceItem | undefined>>
|
||||
mouseOverBlockId?: string
|
||||
setMouseOverBlockId: Dispatch<SetStateAction<string | undefined>>
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
//@ts-ignore
|
||||
}>({})
|
||||
|
||||
export const DndContext = ({ children }: { children: ReactNode }) => {
|
||||
const [draggedStep, setDraggedStep] = useState<DraggableStep | undefined>()
|
||||
const [draggedStep, setDraggedStep] = useState<DraggableStep>()
|
||||
const [draggedStepType, setDraggedStepType] = useState<
|
||||
DraggableStepType | undefined
|
||||
>()
|
||||
const [draggedChoiceItem, setDraggedChoiceItem] = useState<
|
||||
ChoiceItem | undefined
|
||||
>()
|
||||
const [mouseOverBlockId, setMouseOverBlockId] = useState<string>()
|
||||
|
||||
return (
|
||||
<dndContext.Provider
|
||||
@ -34,6 +47,10 @@ export const DndContext = ({ children }: { children: ReactNode }) => {
|
||||
setDraggedStep,
|
||||
draggedStepType,
|
||||
setDraggedStepType,
|
||||
draggedChoiceItem,
|
||||
setDraggedChoiceItem,
|
||||
mouseOverBlockId,
|
||||
setMouseOverBlockId,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
@ -27,6 +27,8 @@ export const blockAnchorsOffset = {
|
||||
export const firstStepOffsetY = 88
|
||||
export const spaceBetweenSteps = 62
|
||||
|
||||
export const firstChoiceItemOffsetY = 20
|
||||
|
||||
export type Coordinates = { x: number; y: number }
|
||||
|
||||
type Position = Coordinates & { scale: number }
|
||||
@ -43,18 +45,24 @@ export type Node = Omit<Block, 'steps'> & {
|
||||
|
||||
const graphPositionDefaultValue = { x: 400, y: 100, scale: 1 }
|
||||
|
||||
type ConnectingIdsProps = {
|
||||
source: { blockId: string; stepId: string }
|
||||
export type ConnectingIds = {
|
||||
source: ConnectingSourceIds
|
||||
target?: Target
|
||||
} | null
|
||||
}
|
||||
|
||||
export type ConnectingSourceIds = {
|
||||
blockId: string
|
||||
stepId: string
|
||||
choiceItemId?: string
|
||||
}
|
||||
|
||||
type PreviewingIdsProps = { sourceId?: string; targetId?: string }
|
||||
|
||||
const graphContext = createContext<{
|
||||
graphPosition: Position
|
||||
setGraphPosition: Dispatch<SetStateAction<Position>>
|
||||
connectingIds: ConnectingIdsProps
|
||||
setConnectingIds: Dispatch<SetStateAction<ConnectingIdsProps>>
|
||||
connectingIds: ConnectingIds | null
|
||||
setConnectingIds: Dispatch<SetStateAction<ConnectingIds | null>>
|
||||
previewingIds: PreviewingIdsProps
|
||||
setPreviewingIds: Dispatch<SetStateAction<PreviewingIdsProps>>
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
@ -66,7 +74,7 @@ const graphContext = createContext<{
|
||||
|
||||
export const GraphProvider = ({ children }: { children: ReactNode }) => {
|
||||
const [graphPosition, setGraphPosition] = useState(graphPositionDefaultValue)
|
||||
const [connectingIds, setConnectingIds] = useState<ConnectingIdsProps>(null)
|
||||
const [connectingIds, setConnectingIds] = useState<ConnectingIds | null>(null)
|
||||
const [previewingIds, setPreviewingIds] = useState<PreviewingIdsProps>({})
|
||||
|
||||
return (
|
||||
|
@ -27,6 +27,7 @@ import { isDefined } from 'utils'
|
||||
import { BlocksActions, blocksActions } from './actions/blocks'
|
||||
import { useImmer, Updater } from 'use-immer'
|
||||
import { stepsAction, StepsActions } from './actions/steps'
|
||||
import { choiceItemsAction, ChoiceItemsActions } from './actions/choiceItems'
|
||||
|
||||
type UpdateTypebotPayload = Partial<{
|
||||
theme: Theme
|
||||
@ -46,7 +47,8 @@ const typebotContext = createContext<
|
||||
updateTypebot: (updates: UpdateTypebotPayload) => void
|
||||
publishTypebot: () => void
|
||||
} & BlocksActions &
|
||||
StepsActions
|
||||
StepsActions &
|
||||
ChoiceItemsActions
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
//@ts-ignore
|
||||
>({})
|
||||
@ -202,6 +204,7 @@ export const TypebotContext = ({
|
||||
updateTypebot: updateLocalTypebot,
|
||||
...blocksActions(setLocalTypebot as Updater<Typebot>),
|
||||
...stepsAction(setLocalTypebot as Updater<Typebot>),
|
||||
...choiceItemsAction(setLocalTypebot as Updater<Typebot>),
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
79
apps/builder/contexts/TypebotContext/actions/choiceItems.ts
Normal file
79
apps/builder/contexts/TypebotContext/actions/choiceItems.ts
Normal file
@ -0,0 +1,79 @@
|
||||
import { ChoiceItem, InputStepType, Typebot } from 'models'
|
||||
import { Updater } from 'use-immer'
|
||||
import { WritableDraft } from 'immer/dist/types/types-external'
|
||||
import { generate } from 'short-uuid'
|
||||
import assert from 'assert'
|
||||
|
||||
export type ChoiceItemsActions = {
|
||||
createChoiceItem: (
|
||||
item: ChoiceItem | Pick<ChoiceItem, 'stepId'>,
|
||||
index?: number
|
||||
) => void
|
||||
updateChoiceItem: (
|
||||
itemId: string,
|
||||
updates: Partial<Omit<ChoiceItem, 'id'>>
|
||||
) => void
|
||||
deleteChoiceItem: (itemId: string) => void
|
||||
}
|
||||
|
||||
export const choiceItemsAction = (
|
||||
setTypebot: Updater<Typebot>
|
||||
): ChoiceItemsActions => ({
|
||||
createChoiceItem: (
|
||||
item: ChoiceItem | Pick<ChoiceItem, 'stepId'>,
|
||||
index?: number
|
||||
) => {
|
||||
setTypebot((typebot) => {
|
||||
createChoiceItemDraft(typebot, item, index)
|
||||
})
|
||||
},
|
||||
updateChoiceItem: (
|
||||
itemId: string,
|
||||
updates: Partial<Omit<ChoiceItem, 'id'>>
|
||||
) =>
|
||||
setTypebot((typebot) => {
|
||||
typebot.choiceItems.byId[itemId] = {
|
||||
...typebot.choiceItems.byId[itemId],
|
||||
...updates,
|
||||
}
|
||||
}),
|
||||
deleteChoiceItem: (itemId: string) => {
|
||||
setTypebot((typebot) => {
|
||||
removeChoiceItemFromStep(typebot, itemId)
|
||||
deleteChoiceItemDraft(typebot, itemId)
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
const removeChoiceItemFromStep = (
|
||||
typebot: WritableDraft<Typebot>,
|
||||
itemId: string
|
||||
) => {
|
||||
const containerStepId = typebot.choiceItems.byId[itemId].stepId
|
||||
const step = typebot.steps.byId[containerStepId]
|
||||
assert(step.type === InputStepType.CHOICE)
|
||||
step.options?.itemIds.splice(step.options.itemIds.indexOf(itemId), 1)
|
||||
}
|
||||
|
||||
export const deleteChoiceItemDraft = (
|
||||
typebot: WritableDraft<Typebot>,
|
||||
itemId: string
|
||||
) => {
|
||||
delete typebot.choiceItems.byId[itemId]
|
||||
const index = typebot.choiceItems.allIds.indexOf(itemId)
|
||||
if (index !== -1) typebot.choiceItems.allIds.splice(index, 1)
|
||||
}
|
||||
|
||||
export const createChoiceItemDraft = (
|
||||
typebot: WritableDraft<Typebot>,
|
||||
item: ChoiceItem | Pick<ChoiceItem, 'stepId'>,
|
||||
index?: number
|
||||
) => {
|
||||
const step = typebot.steps.byId[item.stepId]
|
||||
assert(step.type === InputStepType.CHOICE)
|
||||
const newItem: ChoiceItem =
|
||||
'id' in item ? { ...item } : { id: generate(), stepId: item.stepId }
|
||||
typebot.choiceItems.byId[newItem.id] = newItem
|
||||
typebot.choiceItems.allIds.push(newItem.id)
|
||||
step.options.itemIds.splice(index ?? 0, 0, newItem.id)
|
||||
}
|
@ -1,8 +1,16 @@
|
||||
import { BubbleStepType, InputStepType, Step, Typebot } from 'models'
|
||||
import {
|
||||
BubbleStepType,
|
||||
ChoiceInputStep,
|
||||
InputStepType,
|
||||
Step,
|
||||
Typebot,
|
||||
} from 'models'
|
||||
import { parseNewStep } from 'services/typebots'
|
||||
import { Updater } from 'use-immer'
|
||||
import { removeEmptyBlocks } from './blocks'
|
||||
import { WritableDraft } from 'immer/dist/types/types-external'
|
||||
import { createChoiceItemDraft, deleteChoiceItemDraft } from './choiceItems'
|
||||
import { isChoiceInput } from 'utils'
|
||||
|
||||
export type StepsActions = {
|
||||
createStep: (
|
||||
@ -14,6 +22,7 @@ export type StepsActions = {
|
||||
stepId: string,
|
||||
updates: Partial<Omit<Step, 'id' | 'type'>>
|
||||
) => void
|
||||
moveStep: (stepId: string) => void
|
||||
deleteStep: (stepId: string) => void
|
||||
}
|
||||
|
||||
@ -32,11 +41,17 @@ export const stepsAction = (setTypebot: Updater<Typebot>): StepsActions => ({
|
||||
setTypebot((typebot) => {
|
||||
typebot.steps.byId[stepId] = { ...typebot.steps.byId[stepId], ...updates }
|
||||
}),
|
||||
deleteStep: (stepId: string) => {
|
||||
moveStep: (stepId: string) => {
|
||||
setTypebot((typebot) => {
|
||||
removeStepIdFromBlock(typebot, stepId)
|
||||
})
|
||||
},
|
||||
deleteStep: (stepId: string) => {
|
||||
setTypebot((typebot) => {
|
||||
const step = typebot.steps.byId[stepId]
|
||||
if (isChoiceInput(step)) deleteChoiceItemsInsideStep(typebot, step)
|
||||
removeStepIdFromBlock(typebot, stepId)
|
||||
deleteStepDraft(typebot, stepId)
|
||||
removeEmptyBlocks(typebot)
|
||||
})
|
||||
},
|
||||
})
|
||||
@ -45,13 +60,8 @@ const removeStepIdFromBlock = (
|
||||
typebot: WritableDraft<Typebot>,
|
||||
stepId: string
|
||||
) => {
|
||||
const containerBlockId = typebot.blocks.allIds.find((blockId) =>
|
||||
typebot.blocks.byId[blockId].stepIds.includes(stepId)
|
||||
) as string
|
||||
typebot.blocks.byId[containerBlockId].stepIds.splice(
|
||||
typebot.blocks.byId[containerBlockId].stepIds.indexOf(stepId),
|
||||
1
|
||||
)
|
||||
const containerBlock = typebot.blocks.byId[typebot.steps.byId[stepId].blockId]
|
||||
containerBlock.stepIds.splice(containerBlock.stepIds.indexOf(stepId), 1)
|
||||
}
|
||||
|
||||
export const deleteStepDraft = (
|
||||
@ -74,6 +84,16 @@ export const createStepDraft = (
|
||||
? parseNewStep(step, blockId)
|
||||
: { ...step, blockId }
|
||||
typebot.steps.byId[newStep.id] = newStep
|
||||
if (isChoiceInput(newStep) && newStep.options.itemIds.length === 0)
|
||||
createChoiceItemDraft(typebot, { stepId: newStep.id })
|
||||
typebot.steps.allIds.push(newStep.id)
|
||||
typebot.blocks.byId[blockId].stepIds.splice(index ?? 0, 0, newStep.id)
|
||||
}
|
||||
|
||||
const deleteChoiceItemsInsideStep = (
|
||||
typebot: WritableDraft<Typebot>,
|
||||
step: ChoiceInputStep
|
||||
) =>
|
||||
step.options?.itemIds.forEach((itemId) =>
|
||||
deleteChoiceItemDraft(typebot, itemId)
|
||||
)
|
||||
|
@ -1 +1 @@
|
||||
export { TypebotContext } from './TypebotContext'
|
||||
export { TypebotContext, useTypebot } from './TypebotContext'
|
||||
|
Reference in New Issue
Block a user