feat(editor): ✨ Duplicate blocks & steps
This commit is contained in:
@ -387,3 +387,10 @@ export const HelpCircleIcon = (props: IconProps) => (
|
||||
<line x1="12" y1="17" x2="12.01" y2="17"></line>
|
||||
</Icon>
|
||||
)
|
||||
|
||||
export const CopyIcon = (props: IconProps) => (
|
||||
<Icon viewBox="0 0 24 24" {...featherIconsBaseProps} {...props}>
|
||||
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
|
||||
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
|
||||
</Icon>
|
||||
)
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { MenuList, MenuItem } from '@chakra-ui/react'
|
||||
import { TrashIcon } from 'assets/icons'
|
||||
import { CopyIcon, TrashIcon } from 'assets/icons'
|
||||
import { useTypebot } from 'contexts/TypebotContext/TypebotContext'
|
||||
|
||||
export const BlockNodeContextMenu = ({
|
||||
@ -7,12 +7,17 @@ export const BlockNodeContextMenu = ({
|
||||
}: {
|
||||
blockIndex: number
|
||||
}) => {
|
||||
const { deleteBlock } = useTypebot()
|
||||
const { deleteBlock, duplicateBlock } = useTypebot()
|
||||
|
||||
const handleDeleteClick = () => deleteBlock(blockIndex)
|
||||
|
||||
const handleDuplicateClick = () => duplicateBlock(blockIndex)
|
||||
|
||||
return (
|
||||
<MenuList>
|
||||
<MenuItem icon={<CopyIcon />} onClick={handleDuplicateClick}>
|
||||
Duplicate
|
||||
</MenuItem>
|
||||
<MenuItem icon={<TrashIcon />} onClick={handleDeleteClick}>
|
||||
Delete
|
||||
</MenuItem>
|
||||
|
@ -1,16 +1,21 @@
|
||||
import { MenuList, MenuItem } from '@chakra-ui/react'
|
||||
import { TrashIcon } from 'assets/icons'
|
||||
import { CopyIcon, TrashIcon } from 'assets/icons'
|
||||
import { useTypebot } from 'contexts/TypebotContext/TypebotContext'
|
||||
import { StepIndices } from 'models'
|
||||
|
||||
type Props = { indices: StepIndices }
|
||||
export const StepNodeContextMenu = ({ indices }: Props) => {
|
||||
const { deleteStep } = useTypebot()
|
||||
const { deleteStep, duplicateStep } = useTypebot()
|
||||
|
||||
const handleDuplicateClick = () => duplicateStep(indices)
|
||||
|
||||
const handleDeleteClick = () => deleteStep(indices)
|
||||
|
||||
return (
|
||||
<MenuList>
|
||||
<MenuItem icon={<CopyIcon />} onClick={handleDuplicateClick}>
|
||||
Duplicate
|
||||
</MenuItem>
|
||||
<MenuItem icon={<TrashIcon />} onClick={handleDeleteClick}>
|
||||
Delete
|
||||
</MenuItem>
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { Coordinates } from 'contexts/GraphContext'
|
||||
import cuid from 'cuid'
|
||||
import { produce } from 'immer'
|
||||
import { WritableDraft } from 'immer/dist/internal'
|
||||
import {
|
||||
@ -21,6 +22,7 @@ export type BlocksActions = {
|
||||
}
|
||||
) => void
|
||||
updateBlock: (blockIndex: number, updates: Partial<Omit<Block, 'id'>>) => void
|
||||
duplicateBlock: (blockIndex: number) => void
|
||||
deleteBlock: (blockIndex: number) => void
|
||||
}
|
||||
|
||||
@ -54,7 +56,24 @@ const blocksActions = (setTypebot: SetTypebot): BlocksActions => ({
|
||||
typebot.blocks[blockIndex] = { ...block, ...updates }
|
||||
})
|
||||
),
|
||||
|
||||
duplicateBlock: (blockIndex: number) =>
|
||||
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((s) => ({ ...s, blockId: id })),
|
||||
graphCoordinates: {
|
||||
x: block.graphCoordinates.x + 200,
|
||||
y: block.graphCoordinates.y + 100,
|
||||
},
|
||||
}
|
||||
typebot.blocks.splice(blockIndex + 1, 0, newBlock)
|
||||
})
|
||||
),
|
||||
deleteBlock: (blockIndex: number) =>
|
||||
setTypebot((typebot) =>
|
||||
produce(typebot, (typebot) => {
|
||||
|
@ -11,6 +11,7 @@ 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'
|
||||
|
||||
export type StepsActions = {
|
||||
createStep: (
|
||||
@ -22,6 +23,7 @@ export type StepsActions = {
|
||||
indices: StepIndices,
|
||||
updates: Partial<Omit<Step, 'id' | 'type'>>
|
||||
) => void
|
||||
duplicateStep: (indices: StepIndices) => void
|
||||
detachStepFromBlock: (indices: StepIndices) => void
|
||||
deleteStep: (indices: StepIndices) => void
|
||||
}
|
||||
@ -47,6 +49,18 @@ const stepsAction = (setTypebot: SetTypebot): StepsActions => ({
|
||||
typebot.blocks[blockIndex].steps[stepIndex] = { ...step, ...updates }
|
||||
})
|
||||
),
|
||||
duplicateStep: ({ blockIndex, stepIndex }: StepIndices) =>
|
||||
setTypebot((typebot) =>
|
||||
produce(typebot, (typebot) => {
|
||||
const step = typebot.blocks[blockIndex].steps[stepIndex]
|
||||
const id = cuid()
|
||||
const newStep: Step = {
|
||||
...step,
|
||||
id,
|
||||
}
|
||||
typebot.blocks[blockIndex].steps.splice(stepIndex + 1, 0, newStep)
|
||||
})
|
||||
),
|
||||
detachStepFromBlock: (indices: StepIndices) =>
|
||||
setTypebot((typebot) => produce(typebot, removeStepFromBlock(indices))),
|
||||
deleteStep: ({ blockIndex, stepIndex }: StepIndices) =>
|
||||
|
@ -111,11 +111,15 @@ test.describe.parallel('Editor', () => {
|
||||
|
||||
await page.goto(`/typebots/${typebotId}/edit`)
|
||||
await page.click('text=Block #1', { button: 'right' })
|
||||
await page.click('text=Duplicate')
|
||||
await expect(page.locator('text="Block #1"')).toBeVisible()
|
||||
await expect(page.locator('text="Block #1 copy"')).toBeVisible()
|
||||
await page.click('text="Block #1"', { button: 'right' })
|
||||
await page.click('text=Delete')
|
||||
await expect(page.locator('text=Block #1')).toBeHidden()
|
||||
await expect(page.locator('text="Block #1"')).toBeHidden()
|
||||
await page.click('button[aria-label="Undo"]')
|
||||
await expect(page.locator('text=Block #1')).toBeVisible()
|
||||
await expect(page.locator('text="Block #1"')).toBeVisible()
|
||||
await page.click('button[aria-label="Redo"]')
|
||||
await expect(page.locator('text=Block #1')).toBeHidden()
|
||||
await expect(page.locator('text="Block #1"')).toBeHidden()
|
||||
})
|
||||
})
|
||||
|
Reference in New Issue
Block a user