Skip to content

Commit

Permalink
馃悰 (editor) Fix single block duplication
Browse files Browse the repository at this point in the history
  • Loading branch information
baptisteArno committed Jan 29, 2024
1 parent 47af9a9 commit b668ac1
Show file tree
Hide file tree
Showing 7 changed files with 153 additions and 113 deletions.
2 changes: 2 additions & 0 deletions apps/builder/src/components/ContextMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
} from '@chakra-ui/react'

export interface ContextMenuProps<T extends HTMLElement> {
onOpen?: () => void
renderMenu: () => JSX.Element | null
children: (
ref: MutableRefObject<T | null>,
Expand Down Expand Up @@ -61,6 +62,7 @@ export function ContextMenu<T extends HTMLElement = HTMLElement>(
if (e.currentTarget === targetRef.current) {
e.preventDefault()
e.stopPropagation()
props.onOpen?.()
setIsOpened(true)
setPosition([e.pageX, e.pageY])
} else {
Expand Down
76 changes: 6 additions & 70 deletions apps/builder/src/features/graph/components/Graph.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
import { Fade, Flex, FlexProps, useEventListener } from '@chakra-ui/react'
import React, { useRef, useMemo, useEffect, useState } from 'react'
import { useTypebot } from '@/features/editor/providers/TypebotProvider'
import {
BlockV6,
GroupV6,
PublicTypebotV6,
TypebotV6,
} from '@typebot.io/schemas'
import { BlockV6, PublicTypebotV6, TypebotV6 } from '@typebot.io/schemas'
import { useDebounce } from 'use-debounce'
import GraphElements from './GraphElements'
import { createId } from '@paralleldrive/cuid2'
Expand Down Expand Up @@ -56,7 +51,7 @@ export const Graph = ({
draggedItem,
setDraggedItem,
} = useBlockDnd()
const { pasteGroups, createGroup } = useTypebot()
const { createGroup } = useTypebot()
const { user } = useUser()
const {
isReadOnly,
Expand Down Expand Up @@ -84,9 +79,6 @@ export const Graph = ({
setFocusedGroups: state.setFocusedGroups,
}))
)
const groupsInClipboard = useGroupsStore(
useShallow((state) => state.groupsInClipboard)
)

const [graphPosition, setGraphPosition] = useState(
graphPositionDefaultValue(
Expand Down Expand Up @@ -319,27 +311,7 @@ export const Graph = ({
setAutoMoveDirection(undefined)
}

useKeyboardShortcuts({
paste: () => {
if (!groupsInClipboard || isReadOnly) return
const { groups, oldToNewIdsMapping } = parseGroupsToPaste(
groupsInClipboard.groups,
lastMouseClickPosition ??
projectMouse(
{
x: window.innerWidth / 2,
y: window.innerHeight / 2,
},
graphPosition
)
)
groups.forEach((group) => {
updateGroupCoordinates(group.id, group.graphCoordinates)
})
pasteGroups(groups, groupsInClipboard.edges, oldToNewIdsMapping)
setFocusedGroups(groups.map((g) => g.id))
},
})
useKeyboardShortcuts({})

useEventListener('keydown', (e) => {
if (e.key === ' ') setIsDraggingGraph(true)
Expand Down Expand Up @@ -383,8 +355,11 @@ export const Graph = ({
{selectBoxCoordinates && <SelectBox {...selectBoxCoordinates} />}
<Fade in={!isReadOnly && focusedGroups.length > 1}>
<GroupSelectionMenu
lastMouseClickPosition={lastMouseClickPosition}
focusedGroups={focusedGroups}
blurGroups={blurGroups}
graphPosition={graphPosition}
isReadOnly={isReadOnly}
/>
</Fade>
</>
Expand Down Expand Up @@ -453,42 +428,3 @@ const useAutoMoveBoard = (
clearInterval(interval)
}
}, [autoMoveDirection, setGraphPosition])

const parseGroupsToPaste = (
groups: GroupV6[],
mousePosition: Coordinates
): { groups: GroupV6[]; oldToNewIdsMapping: Map<string, string> } => {
const farLeftGroup = groups.sort(
(a, b) => a.graphCoordinates.x - b.graphCoordinates.x
)[0]
const farLeftGroupCoord = farLeftGroup.graphCoordinates

const oldToNewIdsMapping = new Map<string, string>()
const newGroups = groups.map((group) => {
const newId = createId()
oldToNewIdsMapping.set(group.id, newId)

return {
...group,
id: newId,
graphCoordinates:
group.id === farLeftGroup.id
? mousePosition
: {
x:
mousePosition.x +
group.graphCoordinates.x -
farLeftGroupCoord.x,
y:
mousePosition.y +
group.graphCoordinates.y -
farLeftGroupCoord.y,
},
}
})

return {
groups: newGroups,
oldToNewIdsMapping,
}
}
117 changes: 107 additions & 10 deletions apps/builder/src/features/graph/components/GroupSelectionMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,43 +12,101 @@ import {
import { useRef } from 'react'
import { useGroupsStore } from '../hooks/useGroupsStore'
import { toast } from 'sonner'
import { createId } from '@paralleldrive/cuid2'
import { Edge, GroupV6 } from '@typebot.io/schemas'
import { projectMouse } from '../helpers/projectMouse'
import { Coordinates } from '../types'
import { useShallow } from 'zustand/react/shallow'

type Props = {
graphPosition: Coordinates & { scale: number }
isReadOnly: boolean
lastMouseClickPosition: Coordinates | undefined
focusedGroups: string[]
blurGroups: () => void
}

export const GroupSelectionMenu = ({ focusedGroups, blurGroups }: Props) => {
const { typebot, deleteGroups } = useTypebot()
export const GroupSelectionMenu = ({
graphPosition,
lastMouseClickPosition,
isReadOnly,
focusedGroups,
blurGroups,
}: Props) => {
const { typebot, deleteGroups, pasteGroups } = useTypebot()
const ref = useRef<HTMLDivElement>(null)
const copyGroups = useGroupsStore((state) => state.copyGroups)

const groupsInClipboard = useGroupsStore(
useShallow((state) => state.groupsInClipboard)
)
const { copyGroups, setFocusedGroups, updateGroupCoordinates } =
useGroupsStore(
useShallow((state) => ({
copyGroups: state.copyGroups,
updateGroupCoordinates: state.updateGroupCoordinates,
setFocusedGroups: state.setFocusedGroups,
}))
)

useEventListener('pointerup', (e) => e.stopPropagation(), ref.current)

const handleCopy = () => {
if (!typebot) return
const groups = typebot.groups.filter((g) => focusedGroups.includes(g.id))
copyGroups(
groups,
typebot.edges.filter((edge) =>
groups.find((g) => g.id === edge.to.groupId)
)
const edges = typebot.edges.filter((edge) =>
groups.find((g) => g.id === edge.to.groupId)
)
toast('Groups copied to clipboard')
copyGroups(groups, edges)
return {
groups,
edges,
}
}

const handleDelete = () => {
deleteGroups(focusedGroups)
blurGroups()
}

const handlePaste = (overrideClipBoard?: {
groups: GroupV6[]
edges: Edge[]
}) => {
if (!groupsInClipboard || isReadOnly) return
const clipboard = overrideClipBoard ?? groupsInClipboard
const { groups, oldToNewIdsMapping } = parseGroupsToPaste(
clipboard.groups,
lastMouseClickPosition ??
projectMouse(
{
x: window.innerWidth / 2,
y: window.innerHeight / 2,
},
graphPosition
)
)
groups.forEach((group) => {
updateGroupCoordinates(group.id, group.graphCoordinates)
})
pasteGroups(groups, clipboard.edges, oldToNewIdsMapping)
setFocusedGroups(groups.map((g) => g.id))
}

useKeyboardShortcuts({
copy: handleCopy,
copy: () => {
handleCopy()
toast('Groups copied to clipboard')
},
cut: () => {
handleCopy()
handleDelete()
},
duplicate: () => {
const clipboard = handleCopy()
handlePaste(clipboard)
},
backspace: handleDelete,
paste: handlePaste,
})

return (
Expand Down Expand Up @@ -95,3 +153,42 @@ export const GroupSelectionMenu = ({ focusedGroups, blurGroups }: Props) => {
</HStack>
)
}

const parseGroupsToPaste = (
groups: GroupV6[],
mousePosition: Coordinates
): { groups: GroupV6[]; oldToNewIdsMapping: Map<string, string> } => {
const farLeftGroup = groups.sort(
(a, b) => a.graphCoordinates.x - b.graphCoordinates.x
)[0]
const farLeftGroupCoord = farLeftGroup.graphCoordinates

const oldToNewIdsMapping = new Map<string, string>()
const newGroups = groups.map((group) => {
const newId = createId()
oldToNewIdsMapping.set(group.id, newId)

return {
...group,
id: newId,
graphCoordinates:
group.id === farLeftGroup.id
? mousePosition
: {
x:
mousePosition.x +
group.graphCoordinates.x -
farLeftGroupCoord.x,
y:
mousePosition.y +
group.graphCoordinates.y -
farLeftGroupCoord.y,
},
}
})

return {
groups: newGroups,
oldToNewIdsMapping,
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,19 @@ import {
type Props = {
groupId: string
onPlayClick: () => void
onDuplicateClick: () => void
onDeleteClick: () => void
}

export const GroupFocusToolbar = ({
groupId,
onPlayClick,
onDuplicateClick,
onDeleteClick,
}: Props) => {
export const GroupFocusToolbar = ({ groupId, onPlayClick }: Props) => {
const { hasCopied, onCopy } = useClipboard(groupId)

const dispatchCopyEvent = () => {
dispatchEvent(new KeyboardEvent('keydown', { key: 'c', metaKey: true }))
}

const dispatchDeleteEvent = () => {
dispatchEvent(new KeyboardEvent('keydown', { key: 'Backspace' }))
}

return (
<HStack
rounded="md"
Expand All @@ -44,11 +45,11 @@ export const GroupFocusToolbar = ({
borderRightWidth="1px"
borderRightRadius="none"
borderLeftRadius="none"
aria-label={'Duplicate group'}
aria-label={'Copy group'}
variant="ghost"
onClick={(e) => {
e.stopPropagation()
onDuplicateClick()
dispatchCopyEvent()
}}
size="sm"
/>
Expand All @@ -72,7 +73,7 @@ export const GroupFocusToolbar = ({
aria-label="Delete"
borderLeftRadius="none"
icon={<TrashIcon />}
onClick={onDeleteClick}
onClick={dispatchDeleteEvent}
variant="ghost"
size="sm"
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,7 @@ export const GroupNode = ({ group, groupIndex }: Props) => {
isReadOnly,
graphPosition,
} = useGraph()
const {
typebot,
updateGroup,
updateGroupsCoordinates,
deleteGroup,
duplicateGroup,
} = useTypebot()
const { typebot, updateGroup, updateGroupsCoordinates } = useTypebot()
const { setMouseOverGroup, mouseOverGroup } = useBlockDnd()
const { setRightPanel, setStartPreviewAtGroup } = useEditor()

Expand Down Expand Up @@ -159,7 +153,8 @@ export const GroupNode = ({ group, groupIndex }: Props) => {

return (
<ContextMenu<HTMLDivElement>
renderMenu={() => <GroupNodeContextMenu groupIndex={groupIndex} />}
onOpen={() => focusGroup(group.id)}
renderMenu={() => <GroupNodeContextMenu />}
isDisabled={isReadOnly}
>
{(ref, isContextMenuOpened) => (
Expand Down Expand Up @@ -241,10 +236,6 @@ export const GroupNode = ({ group, groupIndex }: Props) => {
<GroupFocusToolbar
groupId={group.id}
onPlayClick={startPreviewAtThisGroup}
onDuplicateClick={() => {
duplicateGroup(groupIndex)
}}
onDeleteClick={() => deleteGroup(groupIndex)}
/>
</SlideFade>
)}
Expand Down

0 comments on commit b668ac1

Please sign in to comment.