2
0

feat(inputs): Add buttons input

This commit is contained in:
Baptiste Arnaud
2022-01-12 09:10:59 +01:00
parent b20bcb1408
commit c02c61cd8b
47 changed files with 1109 additions and 243 deletions

View File

@ -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}

View File

@ -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 (

View File

@ -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}

View 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)
}

View File

@ -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)
)

View File

@ -1 +1 @@
export { TypebotContext } from './TypebotContext'
export { TypebotContext, useTypebot } from './TypebotContext'