From c01ffa3f0b2b093d6f27d80f95a45492fd6bb554 Mon Sep 17 00:00:00 2001 From: Baptiste Arnaud Date: Wed, 23 Mar 2022 11:00:43 +0100 Subject: [PATCH] =?UTF-8?q?feat(editor):=20=E2=9C=A8=20Duplicate=20blocks?= =?UTF-8?q?=20&=20steps?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/builder/assets/icons.tsx | 7 +++++++ .../Nodes/BlockNode/BlockNodeContextMenu.tsx | 9 ++++++-- .../Nodes/StepNode/StepNodeContextMenu.tsx | 9 ++++++-- .../contexts/TypebotContext/actions/blocks.ts | 21 ++++++++++++++++++- .../contexts/TypebotContext/actions/steps.ts | 14 +++++++++++++ apps/builder/playwright/tests/editor.spec.ts | 10 ++++++--- 6 files changed, 62 insertions(+), 8 deletions(-) diff --git a/apps/builder/assets/icons.tsx b/apps/builder/assets/icons.tsx index 5ba14117a..af083da8a 100644 --- a/apps/builder/assets/icons.tsx +++ b/apps/builder/assets/icons.tsx @@ -387,3 +387,10 @@ export const HelpCircleIcon = (props: IconProps) => ( ) + +export const CopyIcon = (props: IconProps) => ( + + + + +) diff --git a/apps/builder/components/shared/Graph/Nodes/BlockNode/BlockNodeContextMenu.tsx b/apps/builder/components/shared/Graph/Nodes/BlockNode/BlockNodeContextMenu.tsx index 997fdec71..363c754f0 100644 --- a/apps/builder/components/shared/Graph/Nodes/BlockNode/BlockNodeContextMenu.tsx +++ b/apps/builder/components/shared/Graph/Nodes/BlockNode/BlockNodeContextMenu.tsx @@ -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 ( + } onClick={handleDuplicateClick}> + Duplicate + } onClick={handleDeleteClick}> Delete diff --git a/apps/builder/components/shared/Graph/Nodes/StepNode/StepNodeContextMenu.tsx b/apps/builder/components/shared/Graph/Nodes/StepNode/StepNodeContextMenu.tsx index 1acfbe1ff..aa8f32cc4 100644 --- a/apps/builder/components/shared/Graph/Nodes/StepNode/StepNodeContextMenu.tsx +++ b/apps/builder/components/shared/Graph/Nodes/StepNode/StepNodeContextMenu.tsx @@ -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 ( + } onClick={handleDuplicateClick}> + Duplicate + } onClick={handleDeleteClick}> Delete diff --git a/apps/builder/contexts/TypebotContext/actions/blocks.ts b/apps/builder/contexts/TypebotContext/actions/blocks.ts index 3bf85d4bb..e81737efd 100644 --- a/apps/builder/contexts/TypebotContext/actions/blocks.ts +++ b/apps/builder/contexts/TypebotContext/actions/blocks.ts @@ -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>) => 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) => { diff --git a/apps/builder/contexts/TypebotContext/actions/steps.ts b/apps/builder/contexts/TypebotContext/actions/steps.ts index 8240366c7..92b2d9cf6 100644 --- a/apps/builder/contexts/TypebotContext/actions/steps.ts +++ b/apps/builder/contexts/TypebotContext/actions/steps.ts @@ -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> ) => 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) => diff --git a/apps/builder/playwright/tests/editor.spec.ts b/apps/builder/playwright/tests/editor.spec.ts index b2430178b..913ff5274 100644 --- a/apps/builder/playwright/tests/editor.spec.ts +++ b/apps/builder/playwright/tests/editor.spec.ts @@ -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() }) })