🚸 (editor) Show toolbar on group click
This commit is contained in:
@ -2,7 +2,7 @@ import {
|
||||
Editable,
|
||||
EditableInput,
|
||||
EditablePreview,
|
||||
IconButton,
|
||||
SlideFade,
|
||||
Stack,
|
||||
useColorModeValue,
|
||||
} from '@chakra-ui/react'
|
||||
@ -18,11 +18,12 @@ import { BlockNodesList } from '../BlockNode/BlockNodesList'
|
||||
import { isDefined, isNotDefined } from 'utils'
|
||||
import { useTypebot, RightPanel, useEditor } from '@/features/editor'
|
||||
import { GroupNodeContextMenu } from './GroupNodeContextMenu'
|
||||
import { PlayIcon } from '@/components/icons'
|
||||
import { useDebounce } from 'use-debounce'
|
||||
import { ContextMenu } from '@/components/ContextMenu'
|
||||
import { setMultipleRefs } from '@/utils/helpers'
|
||||
import { useDrag } from '@use-gesture/react'
|
||||
import { GroupFocusToolbar } from './GroupFocusToolbar'
|
||||
import { useOutsideClick } from '@/hooks/useOutsideClick'
|
||||
|
||||
type Props = {
|
||||
group: Group
|
||||
@ -63,11 +64,9 @@ const NonMemoizedDraggableGroupNode = ({
|
||||
previewingEdge,
|
||||
previewingBlock,
|
||||
isReadOnly,
|
||||
focusedGroupId,
|
||||
setFocusedGroupId,
|
||||
graphPosition,
|
||||
} = useGraph()
|
||||
const { typebot, updateGroup } = useTypebot()
|
||||
const { typebot, updateGroup, deleteGroup, duplicateGroup } = useTypebot()
|
||||
const { setMouseOverGroup, mouseOverGroup } = useBlockDnd()
|
||||
const { setRightPanel, setStartPreviewAtGroup } = useEditor()
|
||||
|
||||
@ -78,6 +77,27 @@ const NonMemoizedDraggableGroupNode = ({
|
||||
)
|
||||
const [groupTitle, setGroupTitle] = useState(group.title)
|
||||
|
||||
const isPreviewing =
|
||||
previewingBlock?.groupId === group.id ||
|
||||
previewingEdge?.from.groupId === group.id ||
|
||||
(previewingEdge?.to.groupId === group.id &&
|
||||
isNotDefined(previewingEdge.to.blockId))
|
||||
|
||||
const isStartGroup =
|
||||
isDefined(group.blocks[0]) && group.blocks[0].type === 'start'
|
||||
|
||||
const groupRef = useRef<HTMLDivElement | null>(null)
|
||||
const [debouncedGroupPosition] = useDebounce(currentCoordinates, 100)
|
||||
const [isFocused, setIsFocused] = useState(false)
|
||||
|
||||
const [ignoreNextFocusIntent, setIgnoreNextFocusIntent] = useState(false)
|
||||
|
||||
useOutsideClick({
|
||||
handler: () => setIsFocused(false),
|
||||
ref: groupRef,
|
||||
capture: true,
|
||||
})
|
||||
|
||||
// When the group is moved from external action (e.g. undo/redo), update the current coordinates
|
||||
useEffect(() => {
|
||||
setCurrentCoordinates({
|
||||
@ -90,17 +110,6 @@ const NonMemoizedDraggableGroupNode = ({
|
||||
useEffect(() => {
|
||||
setGroupTitle(group.title)
|
||||
}, [group.title])
|
||||
|
||||
const isPreviewing =
|
||||
previewingBlock?.groupId === group.id ||
|
||||
previewingEdge?.from.groupId === group.id ||
|
||||
(previewingEdge?.to.groupId === group.id &&
|
||||
isNotDefined(previewingEdge.to.blockId))
|
||||
const isStartGroup =
|
||||
isDefined(group.blocks[0]) && group.blocks[0].type === 'start'
|
||||
|
||||
const groupRef = useRef<HTMLDivElement | null>(null)
|
||||
const [debouncedGroupPosition] = useDebounce(currentCoordinates, 100)
|
||||
useEffect(() => {
|
||||
if (!currentCoordinates || isReadOnly) return
|
||||
if (
|
||||
@ -142,15 +151,15 @@ const NonMemoizedDraggableGroupNode = ({
|
||||
}
|
||||
|
||||
useDrag(
|
||||
({ first, last, offset: [offsetX, offsetY], event, target }) => {
|
||||
({ first, last, offset: [offsetX, offsetY], distance, event, target }) => {
|
||||
event.stopPropagation()
|
||||
if ((target as HTMLElement).classList.contains('prevent-group-drag'))
|
||||
return
|
||||
if (first) {
|
||||
setFocusedGroupId(group.id)
|
||||
setIsMouseDown(true)
|
||||
}
|
||||
if (last) {
|
||||
if (distance[0] > 1 || distance[1] > 1) setIgnoreNextFocusIntent(true)
|
||||
setIsMouseDown(false)
|
||||
}
|
||||
const newCoord = {
|
||||
@ -170,6 +179,14 @@ const NonMemoizedDraggableGroupNode = ({
|
||||
}
|
||||
)
|
||||
|
||||
const focusGroup = () => {
|
||||
if (ignoreNextFocusIntent) {
|
||||
setIgnoreNextFocusIntent(false)
|
||||
return
|
||||
}
|
||||
setIsFocused(true)
|
||||
}
|
||||
|
||||
return (
|
||||
<ContextMenu<HTMLDivElement>
|
||||
renderMenu={() => <GroupNodeContextMenu groupIndex={groupIndex} />}
|
||||
@ -178,6 +195,7 @@ const NonMemoizedDraggableGroupNode = ({
|
||||
{(ref, isContextMenuOpened) => (
|
||||
<Stack
|
||||
ref={setMultipleRefs([ref, groupRef])}
|
||||
onClick={focusGroup}
|
||||
data-testid="group"
|
||||
p="4"
|
||||
rounded="xl"
|
||||
@ -186,7 +204,7 @@ const NonMemoizedDraggableGroupNode = ({
|
||||
isConnecting || isContextMenuOpened || isPreviewing ? '2px' : '1px'
|
||||
}
|
||||
borderColor={
|
||||
isConnecting || isContextMenuOpened || isPreviewing
|
||||
isConnecting || isContextMenuOpened || isPreviewing || isFocused
|
||||
? previewingBorderColor
|
||||
: borderColor
|
||||
}
|
||||
@ -204,7 +222,7 @@ const NonMemoizedDraggableGroupNode = ({
|
||||
cursor={isMouseDown ? 'grabbing' : 'pointer'}
|
||||
shadow="md"
|
||||
_hover={{ shadow: 'lg' }}
|
||||
zIndex={focusedGroupId === group.id ? 10 : 1}
|
||||
zIndex={isFocused ? 10 : 1}
|
||||
>
|
||||
<Editable
|
||||
value={groupTitle}
|
||||
@ -232,16 +250,24 @@ const NonMemoizedDraggableGroupNode = ({
|
||||
isStartGroup={isStartGroup}
|
||||
/>
|
||||
)}
|
||||
<IconButton
|
||||
icon={<PlayIcon />}
|
||||
aria-label={'Preview bot from this group'}
|
||||
pos="absolute"
|
||||
right={2}
|
||||
top={0}
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={startPreviewAtThisGroup}
|
||||
/>
|
||||
<SlideFade
|
||||
in={isFocused}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: '-50px',
|
||||
right: 0,
|
||||
}}
|
||||
unmountOnExit
|
||||
>
|
||||
<GroupFocusToolbar
|
||||
onPlayClick={startPreviewAtThisGroup}
|
||||
onDuplicateClick={() => {
|
||||
setIsFocused(false)
|
||||
duplicateGroup(groupIndex)
|
||||
}}
|
||||
onDeleteClick={() => deleteGroup(groupIndex)}
|
||||
/>
|
||||
</SlideFade>
|
||||
</Stack>
|
||||
)}
|
||||
</ContextMenu>
|
||||
|
Reference in New Issue
Block a user