Skip to content

Commit

Permalink
🚸 (editor) Show toolbar on group click
Browse files Browse the repository at this point in the history
  • Loading branch information
baptisteArno committed Feb 23, 2023
1 parent 2ff6991 commit 0619c60
Show file tree
Hide file tree
Showing 4 changed files with 126 additions and 33 deletions.
12 changes: 10 additions & 2 deletions apps/builder/src/features/editor/editor.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,11 +197,19 @@ test('Preview from group should work', async ({ page }) => {
)

await page.goto(`/typebots/${typebotId}/edit`)
await page.click('[aria-label="Preview bot from this group"] >> nth=1')
await page
.getByTestId('group')
.nth(1)
.click({ position: { x: 100, y: 10 } })
await page.click('[aria-label="Preview bot from this group"]')
await expect(
page.locator('typebot-standard').locator('text="Hello this is group 1"')
).toBeVisible()
await page.click('[aria-label="Preview bot from this group"] >> nth=2')
await page
.getByTestId('group')
.nth(2)
.click({ position: { x: 100, y: 10 } })
await page.click('[aria-label="Preview bot from this group"]')
await expect(
page.locator('typebot-standard').locator('text="Hello this is group 2"')
).toBeVisible()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { CopyIcon, PlayIcon, TrashIcon } from '@/components/icons'
import { HStack, IconButton, useColorModeValue } from '@chakra-ui/react'

type Props = {
onPlayClick: () => void
onDuplicateClick: () => void
onDeleteClick: () => void
}

export const GroupFocusToolbar = ({
onPlayClick,
onDuplicateClick,
onDeleteClick,
}: Props) => {
return (
<HStack
rounded="md"
spacing={0}
borderWidth="1px"
bgColor={useColorModeValue('white', 'gray.800')}
shadow="md"
>
<IconButton
icon={<PlayIcon />}
borderRightWidth="1px"
borderRightRadius="none"
aria-label={'Preview bot from this group'}
variant="ghost"
onClick={onPlayClick}
size="sm"
/>
<IconButton
icon={<CopyIcon />}
borderRightWidth="1px"
borderRightRadius="none"
borderLeftRadius="none"
aria-label={'Duplicate group'}
variant="ghost"
onClick={(e) => {
e.stopPropagation()
onDuplicateClick()
}}
size="sm"
/>
<IconButton
aria-label="Delete"
borderLeftRadius="none"
icon={<TrashIcon />}
onClick={onDeleteClick}
variant="ghost"
size="sm"
/>
</HStack>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {
Editable,
EditableInput,
EditablePreview,
IconButton,
SlideFade,
Stack,
useColorModeValue,
} from '@chakra-ui/react'
Expand All @@ -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
Expand Down Expand Up @@ -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()

Expand All @@ -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({
Expand All @@ -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 (
Expand Down Expand Up @@ -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 = {
Expand All @@ -170,6 +179,14 @@ const NonMemoizedDraggableGroupNode = ({
}
)

const focusGroup = () => {
if (ignoreNextFocusIntent) {
setIgnoreNextFocusIntent(false)
return
}
setIsFocused(true)
}

return (
<ContextMenu<HTMLDivElement>
renderMenu={() => <GroupNodeContextMenu groupIndex={groupIndex} />}
Expand All @@ -178,6 +195,7 @@ const NonMemoizedDraggableGroupNode = ({
{(ref, isContextMenuOpened) => (
<Stack
ref={setMultipleRefs([ref, groupRef])}
onClick={focusGroup}
data-testid="group"
p="4"
rounded="xl"
Expand All @@ -186,7 +204,7 @@ const NonMemoizedDraggableGroupNode = ({
isConnecting || isContextMenuOpened || isPreviewing ? '2px' : '1px'
}
borderColor={
isConnecting || isContextMenuOpened || isPreviewing
isConnecting || isContextMenuOpened || isPreviewing || isFocused
? previewingBorderColor
: borderColor
}
Expand All @@ -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}
Expand Down Expand Up @@ -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>
Expand Down
6 changes: 5 additions & 1 deletion apps/builder/src/hooks/useOutsideClick.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ type Handler = (event: MouseEvent) => void
type Props<T> = {
ref: RefObject<T>
handler: Handler
capture?: boolean
}

export const useOutsideClick = <T extends HTMLElement = HTMLElement>({
ref,
handler,
capture,
}: Props<T>): void => {
const triggerHandlerIfOutside = (event: MouseEvent) => {
const el = ref?.current
Expand All @@ -20,5 +22,7 @@ export const useOutsideClick = <T extends HTMLElement = HTMLElement>({
handler(event)
}

useEventListener('pointerdown', triggerHandlerIfOutside)
useEventListener('pointerdown', triggerHandlerIfOutside, null, {
capture,
})
}

4 comments on commit 0619c60

@vercel
Copy link

@vercel vercel bot commented on 0619c60 Feb 23, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

docs – ./apps/docs

docs-git-main-typebot-io.vercel.app
docs.typebot.io
docs-typebot-io.vercel.app

@vercel
Copy link

@vercel vercel bot commented on 0619c60 Feb 23, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

viewer-v2 – ./apps/viewer

ns8.vn
1stop.au
yobot.me
klujo.com
247987.com
8jours.top
aginap.com
bee.cr8.ai
myrentalhost.com
stan.vselise.com
start.taxtree.io
typebot.aloe.bot
voicehelp.cr8.ai
zap.fundviser.in
app.chatforms.net
bot.hostnation.de
bot.maitempah.com
bot.phuonghub.com
bot.reviewzer.com
bot.rihabilita.it
cares.urlabout.me
chat.gaswadern.de
fmm.wpwakanda.com
gentleman-shop.fr
k1.kandabrand.com
lb.ticketfute.com
ov1.wpwakanda.com
ov2.wpwakanda.com
ov3.wpwakanda.com
support.triplo.ai
viewer.typebot.io
1988.bouclidom.com
andreimayer.com.br
bot.danyservice.it
bot.iconicbrows.it
bot.megafox.com.br
bot.neferlopez.com
bots.robomotion.io
cadu.uninta.edu.br
dicanatural.online
digitalhelp.com.au
goalsettingbot.com
pant.maxbot.com.br
positivobra.com.br
survey.digienge.io
this-is-a-test.com
zap.techadviser.in
bot.boston-voip.com
bot.cabinpromos.com
bot.digitalbled.com
bot.dsignagency.com
bot.eventhub.com.au
bot.jepierre.com.br
bot.ltmidias.com.br
bbutton.wpwakanda.com
bot.coachayongzul.com
bot.digitalpointer.id
bot.eikju.photography
bot.incusservices.com
bot.meuesocial.com.br
bot.mycompany.reviews
bot.outstandbrand.com
bot.ramonmatos.com.br
bot.robertohairlab.it
bot.sharemyreview.net
bot.truongnguyen.live
botz.cloudsiteapp.com
cdd.searchcube.com.sg
chat.missarkansas.org
chatbot.ownacademy.co
criar.somaperuzzo.com
sbutton.wpwakanda.com
815639944.21000000.one
aplicacao.bmind.com.br
apply.ansuraniphone.my
bbutton.wpwwakanda.com
bot.ilmuseoaiborghi.it
bot.louismarcondes.com
bot.pratikmandalia.com
bot.t20worldcup.com.au
bot2.mycompany.reviews
c23111azqw.nigerias.io
dieta.barrettamario.it
felipewelington.com.br
form.bridesquadapp.com
form.searchcube.com.sg
gcase.barrettamario.it
help.giversforgood.com
info.clickasuransi.com
kodawariab736.skeep.it
michaeljackson.riku.ai
premium.kandabrand.com
report.gratirabbit.com
resume.gratirabbit.com
83242573.actualizar.xyz
87656003.actualizar.xyz
88152257.actualizar.xyz
91375310.actualizar.xyz
arrivalx2.wpwakanda.com
bot.blackboxtips.com.br

@vercel
Copy link

@vercel vercel bot commented on 0619c60 Feb 23, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vercel
Copy link

@vercel vercel bot commented on 0619c60 Feb 23, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

builder-v2 – ./apps/builder

builder-v2-typebot-io.vercel.app
app.typebot.io
builder-v2-git-main-typebot-io.vercel.app

Please sign in to comment.