perf(editor): 🚸 More predictable edge management
This commit is contained in:
@ -27,8 +27,14 @@ export const Graph = ({
|
|||||||
answersCounts?: AnswersCount[]
|
answersCounts?: AnswersCount[]
|
||||||
onUnlockProPlanClick?: () => void
|
onUnlockProPlanClick?: () => void
|
||||||
} & FlexProps) => {
|
} & FlexProps) => {
|
||||||
const { draggedStepType, setDraggedStepType, draggedStep, setDraggedStep } =
|
const {
|
||||||
useStepDnd()
|
draggedStepType,
|
||||||
|
setDraggedStepType,
|
||||||
|
draggedStep,
|
||||||
|
setDraggedStep,
|
||||||
|
draggedItem,
|
||||||
|
setDraggedItem,
|
||||||
|
} = useStepDnd()
|
||||||
const graphContainerRef = useRef<HTMLDivElement | null>(null)
|
const graphContainerRef = useRef<HTMLDivElement | null>(null)
|
||||||
const editorContainerRef = useRef<HTMLDivElement | null>(null)
|
const editorContainerRef = useRef<HTMLDivElement | null>(null)
|
||||||
const { createBlock } = useTypebot()
|
const { createBlock } = useTypebot()
|
||||||
@ -77,6 +83,7 @@ export const Graph = ({
|
|||||||
|
|
||||||
const handleMouseUp = (e: MouseEvent) => {
|
const handleMouseUp = (e: MouseEvent) => {
|
||||||
if (!typebot) return
|
if (!typebot) return
|
||||||
|
if (draggedItem) setDraggedItem(undefined)
|
||||||
if (!draggedStep && !draggedStepType) return
|
if (!draggedStep && !draggedStepType) return
|
||||||
const coordinates = {
|
const coordinates = {
|
||||||
x: e.clientX - graphPosition.x - blockWidth / 3,
|
x: e.clientX - graphPosition.x - blockWidth / 3,
|
||||||
|
@ -22,7 +22,7 @@ export const ItemNodesList = ({
|
|||||||
indices: { blockIndex, stepIndex },
|
indices: { blockIndex, stepIndex },
|
||||||
isReadOnly = false,
|
isReadOnly = false,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const { typebot, createItem, deleteItem } = useTypebot()
|
const { typebot, createItem, detachItemFromStep } = useTypebot()
|
||||||
const { draggedItem, setDraggedItem, mouseOverBlock } = useStepDnd()
|
const { draggedItem, setDraggedItem, mouseOverBlock } = useStepDnd()
|
||||||
const placeholderRefs = useRef<HTMLDivElement[]>([])
|
const placeholderRefs = useRef<HTMLDivElement[]>([])
|
||||||
const blockId = typebot?.blocks[blockIndex].id
|
const blockId = typebot?.blocks[blockIndex].id
|
||||||
@ -96,7 +96,7 @@ export const ItemNodesList = ({
|
|||||||
) => {
|
) => {
|
||||||
if (!typebot || isReadOnly) return
|
if (!typebot || isReadOnly) return
|
||||||
placeholderRefs.current.splice(itemIndex + 1, 1)
|
placeholderRefs.current.splice(itemIndex + 1, 1)
|
||||||
deleteItem({ blockIndex, stepIndex, itemIndex })
|
detachItemFromStep({ blockIndex, stepIndex, itemIndex })
|
||||||
setPosition(absolute)
|
setPosition(absolute)
|
||||||
setRelativeCoordinates(relative)
|
setRelativeCoordinates(relative)
|
||||||
setDraggedItem(item)
|
setDraggedItem(item)
|
||||||
|
@ -1,4 +1,11 @@
|
|||||||
import { Typebot, Edge, StepWithItems, StepIndices, ItemIndices } from 'models'
|
import {
|
||||||
|
Typebot,
|
||||||
|
Edge,
|
||||||
|
StepWithItems,
|
||||||
|
StepIndices,
|
||||||
|
ItemIndices,
|
||||||
|
Step,
|
||||||
|
} from 'models'
|
||||||
import { WritableDraft } from 'immer/dist/types/types-external'
|
import { WritableDraft } from 'immer/dist/types/types-external'
|
||||||
import { SetTypebot } from '../TypebotContext'
|
import { SetTypebot } from '../TypebotContext'
|
||||||
import { produce } from 'immer'
|
import { produce } from 'immer'
|
||||||
@ -98,9 +105,11 @@ const deleteOutgoingEdgeIdProps = (
|
|||||||
const fromStepIndex = typebot.blocks[fromBlockIndex].steps.findIndex(
|
const fromStepIndex = typebot.blocks[fromBlockIndex].steps.findIndex(
|
||||||
byId(edge.from.stepId)
|
byId(edge.from.stepId)
|
||||||
)
|
)
|
||||||
const step = typebot.blocks[fromBlockIndex].steps[fromStepIndex]
|
const step = typebot.blocks[fromBlockIndex].steps[fromStepIndex] as
|
||||||
|
| Step
|
||||||
|
| undefined
|
||||||
const fromItemIndex =
|
const fromItemIndex =
|
||||||
edge.from.itemId && stepHasItems(step)
|
edge.from.itemId && step && stepHasItems(step)
|
||||||
? step.items.findIndex(byId(edge.from.itemId))
|
? step.items.findIndex(byId(edge.from.itemId))
|
||||||
: -1
|
: -1
|
||||||
if (fromStepIndex !== -1)
|
if (fromStepIndex !== -1)
|
||||||
|
@ -8,29 +8,44 @@ import {
|
|||||||
import { SetTypebot } from '../TypebotContext'
|
import { SetTypebot } from '../TypebotContext'
|
||||||
import produce from 'immer'
|
import produce from 'immer'
|
||||||
import { cleanUpEdgeDraft } from './edges'
|
import { cleanUpEdgeDraft } from './edges'
|
||||||
import { stepHasItems } from 'utils'
|
import { byId, stepHasItems } from 'utils'
|
||||||
import cuid from 'cuid'
|
import cuid from 'cuid'
|
||||||
|
|
||||||
export type ItemsActions = {
|
export type ItemsActions = {
|
||||||
createItem: (item: Omit<ButtonItem, 'id'>, indices: ItemIndices) => void
|
createItem: (
|
||||||
|
item: ButtonItem | Omit<ButtonItem, 'id'>,
|
||||||
|
indices: ItemIndices
|
||||||
|
) => void
|
||||||
updateItem: (indices: ItemIndices, updates: Partial<Omit<Item, 'id'>>) => void
|
updateItem: (indices: ItemIndices, updates: Partial<Omit<Item, 'id'>>) => void
|
||||||
|
detachItemFromStep: (indices: ItemIndices) => void
|
||||||
deleteItem: (indices: ItemIndices) => void
|
deleteItem: (indices: ItemIndices) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const itemsAction = (setTypebot: SetTypebot): ItemsActions => ({
|
const itemsAction = (setTypebot: SetTypebot): ItemsActions => ({
|
||||||
createItem: (
|
createItem: (
|
||||||
item: Omit<ButtonItem, 'id'>,
|
item: ButtonItem | Omit<ButtonItem, 'id'>,
|
||||||
{ blockIndex, stepIndex, itemIndex }: ItemIndices
|
{ blockIndex, stepIndex, itemIndex }: ItemIndices
|
||||||
) =>
|
) =>
|
||||||
setTypebot((typebot) =>
|
setTypebot((typebot) =>
|
||||||
produce(typebot, (typebot) => {
|
produce(typebot, (typebot) => {
|
||||||
const step = typebot.blocks[blockIndex].steps[stepIndex]
|
const step = typebot.blocks[blockIndex].steps[stepIndex]
|
||||||
if (step.type !== InputStepType.CHOICE) return
|
if (step.type !== InputStepType.CHOICE) return
|
||||||
step.items.splice(itemIndex, 0, {
|
const newItem = {
|
||||||
...item,
|
...item,
|
||||||
stepId: step.id,
|
stepId: step.id,
|
||||||
id: cuid(),
|
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,
|
||||||
|
itemId: newItem.id,
|
||||||
|
})
|
||||||
|
: (newItem.outgoingEdgeId = undefined)
|
||||||
|
}
|
||||||
|
step.items.splice(itemIndex, 0, newItem)
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
updateItem: (
|
updateItem: (
|
||||||
@ -49,7 +64,15 @@ const itemsAction = (setTypebot: SetTypebot): ItemsActions => ({
|
|||||||
} as Item
|
} as Item
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
|
detachItemFromStep: ({ blockIndex, stepIndex, itemIndex }: ItemIndices) =>
|
||||||
|
setTypebot((typebot) =>
|
||||||
|
produce(typebot, (typebot) => {
|
||||||
|
const step = typebot.blocks[blockIndex].steps[
|
||||||
|
stepIndex
|
||||||
|
] as StepWithItems
|
||||||
|
step.items.splice(itemIndex, 1)
|
||||||
|
})
|
||||||
|
),
|
||||||
deleteItem: ({ blockIndex, stepIndex, itemIndex }: ItemIndices) =>
|
deleteItem: ({ blockIndex, stepIndex, itemIndex }: ItemIndices) =>
|
||||||
setTypebot((typebot) =>
|
setTypebot((typebot) =>
|
||||||
produce(typebot, (typebot) => {
|
produce(typebot, (typebot) => {
|
||||||
|
@ -13,6 +13,7 @@ import { SetTypebot } from '../TypebotContext'
|
|||||||
import produce from 'immer'
|
import produce from 'immer'
|
||||||
import { cleanUpEdgeDraft, deleteEdgeDraft } from './edges'
|
import { cleanUpEdgeDraft, deleteEdgeDraft } from './edges'
|
||||||
import cuid from 'cuid'
|
import cuid from 'cuid'
|
||||||
|
import { byId } from 'utils'
|
||||||
|
|
||||||
export type StepsActions = {
|
export type StepsActions = {
|
||||||
createStep: (
|
createStep: (
|
||||||
@ -74,7 +75,9 @@ const stepsAction = (setTypebot: SetTypebot): StepsActions => ({
|
|||||||
deleteStep: ({ blockIndex, stepIndex }: StepIndices) =>
|
deleteStep: ({ blockIndex, stepIndex }: StepIndices) =>
|
||||||
setTypebot((typebot) =>
|
setTypebot((typebot) =>
|
||||||
produce(typebot, (typebot) => {
|
produce(typebot, (typebot) => {
|
||||||
|
const removingStep = typebot.blocks[blockIndex].steps[stepIndex]
|
||||||
removeStepFromBlock({ blockIndex, stepIndex })(typebot)
|
removeStepFromBlock({ blockIndex, stepIndex })(typebot)
|
||||||
|
cleanUpEdgeDraft(typebot, removingStep.id)
|
||||||
removeEmptyBlocks(typebot)
|
removeEmptyBlocks(typebot)
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
@ -83,8 +86,6 @@ const stepsAction = (setTypebot: SetTypebot): StepsActions => ({
|
|||||||
const removeStepFromBlock =
|
const removeStepFromBlock =
|
||||||
({ blockIndex, stepIndex }: StepIndices) =>
|
({ blockIndex, stepIndex }: StepIndices) =>
|
||||||
(typebot: WritableDraft<Typebot>) => {
|
(typebot: WritableDraft<Typebot>) => {
|
||||||
const removingStep = typebot.blocks[blockIndex].steps[stepIndex]
|
|
||||||
cleanUpEdgeDraft(typebot, removingStep.id)
|
|
||||||
typebot.blocks[blockIndex].steps.splice(stepIndex, 1)
|
typebot.blocks[blockIndex].steps.splice(stepIndex, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,11 +123,20 @@ const moveStepToBlock = (
|
|||||||
step: DraggableStep,
|
step: DraggableStep,
|
||||||
blockId: string,
|
blockId: string,
|
||||||
{ blockIndex, stepIndex }: StepIndices
|
{ blockIndex, stepIndex }: StepIndices
|
||||||
) =>
|
) => {
|
||||||
typebot.blocks[blockIndex].steps.splice(stepIndex ?? 0, 0, {
|
const newStep = { ...step, blockId }
|
||||||
...step,
|
if (step.outgoingEdgeId) {
|
||||||
blockId,
|
if (typebot.blocks[blockIndex].steps.length > stepIndex ?? 0) {
|
||||||
outgoingEdgeId: undefined,
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
export { stepsAction, createStepDraft }
|
export { stepsAction, createStepDraft }
|
||||||
|
@ -30,7 +30,7 @@ test.describe.parallel('Embed bubble step', () => {
|
|||||||
await page.fill('input[placeholder="Paste the link or code..."]', pdfSrc)
|
await page.fill('input[placeholder="Paste the link or code..."]', pdfSrc)
|
||||||
await expect(page.locator('iframe#embed-bubble-content')).toHaveAttribute(
|
await expect(page.locator('iframe#embed-bubble-content')).toHaveAttribute(
|
||||||
'src',
|
'src',
|
||||||
`https://docs.google.com/viewer?embedded=true&url=${pdfSrc}`
|
pdfSrc
|
||||||
)
|
)
|
||||||
await page.fill(
|
await page.fill(
|
||||||
'input[placeholder="Paste the link or code..."]',
|
'input[placeholder="Paste the link or code..."]',
|
||||||
|
@ -51,7 +51,10 @@ test.describe.parallel('Editor', () => {
|
|||||||
await expect(page.locator('[data-testid="edge"] >> nth=0')).toBeVisible()
|
await expect(page.locator('[data-testid="edge"] >> nth=0')).toBeVisible()
|
||||||
await expect(page.locator('[data-testid="edge"] >> nth=1')).toBeVisible()
|
await expect(page.locator('[data-testid="edge"] >> nth=1')).toBeVisible()
|
||||||
|
|
||||||
await page.click('[data-testid="clickable-edge"] >> nth=0', { force: true })
|
await page.click('[data-testid="clickable-edge"] >> nth=0', {
|
||||||
|
force: true,
|
||||||
|
button: 'right',
|
||||||
|
})
|
||||||
await page.click('text=Delete')
|
await page.click('text=Delete')
|
||||||
const total = await page.locator('[data-testid="edge"]').count()
|
const total = await page.locator('[data-testid="edge"]').count()
|
||||||
expect(total).toBe(1)
|
expect(total).toBe(1)
|
||||||
|
Reference in New Issue
Block a user