2
0

🐛 (editor) Fix dragging text bubble after editting

This commit is contained in:
Baptiste Arnaud
2024-01-29 12:03:51 +01:00
parent 0817fbaebb
commit 1ebfc15178
6 changed files with 48 additions and 32 deletions

View File

@ -101,9 +101,6 @@ export const Graph = ({
const [groupRects, setGroupRects] = useState< const [groupRects, setGroupRects] = useState<
{ groupId: string; rect: DOMRect }[] | undefined { groupId: string; rect: DOMRect }[] | undefined
>() >()
const [lastMouseClickPosition, setLastMouseClickPosition] = useState<
Coordinates | undefined
>()
const [isDragging, setIsDragging] = useState(false) const [isDragging, setIsDragging] = useState(false)
const graphContainerRef = useRef<HTMLDivElement | null>(null) const graphContainerRef = useRef<HTMLDivElement | null>(null)
@ -167,7 +164,7 @@ export const Graph = ({
if (isRightClick) e.stopPropagation() if (isRightClick) e.stopPropagation()
} }
const handlePointerUp = (e: PointerEvent) => { const handlePointerUp = () => {
if (isDraggingGraph) return if (isDraggingGraph) return
if ( if (
!selectBoxCoordinates || !selectBoxCoordinates ||
@ -176,9 +173,6 @@ export const Graph = ({
5 5
) { ) {
blurGroups() blurGroups()
setLastMouseClickPosition(
projectMouse({ x: e.clientX, y: e.clientY }, graphPosition)
)
} }
setSelectBoxCoordinates(undefined) setSelectBoxCoordinates(undefined)
setOpenedBlockId(undefined) setOpenedBlockId(undefined)
@ -355,10 +349,9 @@ export const Graph = ({
{selectBoxCoordinates && <SelectBox {...selectBoxCoordinates} />} {selectBoxCoordinates && <SelectBox {...selectBoxCoordinates} />}
<Fade in={!isReadOnly && focusedGroups.length > 1}> <Fade in={!isReadOnly && focusedGroups.length > 1}>
<GroupSelectionMenu <GroupSelectionMenu
lastMouseClickPosition={lastMouseClickPosition} graphPosition={graphPosition}
focusedGroups={focusedGroups} focusedGroups={focusedGroups}
blurGroups={blurGroups} blurGroups={blurGroups}
graphPosition={graphPosition}
isReadOnly={isReadOnly} isReadOnly={isReadOnly}
/> />
</Fade> </Fade>

View File

@ -9,30 +9,29 @@ import {
useColorModeValue, useColorModeValue,
useEventListener, useEventListener,
} from '@chakra-ui/react' } from '@chakra-ui/react'
import { useRef } from 'react' import { useRef, useState } from 'react'
import { useGroupsStore } from '../hooks/useGroupsStore' import { useGroupsStore } from '../hooks/useGroupsStore'
import { toast } from 'sonner' import { toast } from 'sonner'
import { createId } from '@paralleldrive/cuid2' import { createId } from '@paralleldrive/cuid2'
import { Edge, GroupV6 } from '@typebot.io/schemas' import { Edge, GroupV6 } from '@typebot.io/schemas'
import { projectMouse } from '../helpers/projectMouse'
import { Coordinates } from '../types' import { Coordinates } from '../types'
import { useShallow } from 'zustand/react/shallow' import { useShallow } from 'zustand/react/shallow'
import { projectMouse } from '../helpers/projectMouse'
type Props = { type Props = {
graphPosition: Coordinates & { scale: number } graphPosition: Coordinates & { scale: number }
isReadOnly: boolean isReadOnly: boolean
lastMouseClickPosition: Coordinates | undefined
focusedGroups: string[] focusedGroups: string[]
blurGroups: () => void blurGroups: () => void
} }
export const GroupSelectionMenu = ({ export const GroupSelectionMenu = ({
graphPosition, graphPosition,
lastMouseClickPosition,
isReadOnly, isReadOnly,
focusedGroups, focusedGroups,
blurGroups, blurGroups,
}: Props) => { }: Props) => {
const [mousePosition, setMousePosition] = useState<Coordinates>()
const { typebot, deleteGroups, pasteGroups } = useTypebot() const { typebot, deleteGroups, pasteGroups } = useTypebot()
const ref = useRef<HTMLDivElement>(null) const ref = useRef<HTMLDivElement>(null)
@ -50,6 +49,13 @@ export const GroupSelectionMenu = ({
useEventListener('pointerup', (e) => e.stopPropagation(), ref.current) useEventListener('pointerup', (e) => e.stopPropagation(), ref.current)
useEventListener('mousemove', (e) => {
setMousePosition({
x: e.clientX,
y: e.clientY,
})
})
const handleCopy = () => { const handleCopy = () => {
if (!typebot) return if (!typebot) return
const groups = typebot.groups.filter((g) => focusedGroups.includes(g.id)) const groups = typebot.groups.filter((g) => focusedGroups.includes(g.id))
@ -72,18 +78,11 @@ export const GroupSelectionMenu = ({
groups: GroupV6[] groups: GroupV6[]
edges: Edge[] edges: Edge[]
}) => { }) => {
if (!groupsInClipboard || isReadOnly) return if (!groupsInClipboard || isReadOnly || !mousePosition) return
const clipboard = overrideClipBoard ?? groupsInClipboard const clipboard = overrideClipBoard ?? groupsInClipboard
const { groups, oldToNewIdsMapping } = parseGroupsToPaste( const { groups, oldToNewIdsMapping } = parseGroupsToPaste(
clipboard.groups, clipboard.groups,
lastMouseClickPosition ?? projectMouse(mousePosition, graphPosition)
projectMouse(
{
x: window.innerWidth / 2,
y: window.innerHeight / 2,
},
graphPosition
)
) )
groups.forEach((group) => { groups.forEach((group) => {
updateGroupCoordinates(group.id, group.graphCoordinates) updateGroupCoordinates(group.id, group.graphCoordinates)

View File

@ -101,6 +101,7 @@ export const BlockNode = ({
ref: blockRef, ref: blockRef,
onDrag, onDrag,
isDisabled: !onMouseDown, isDisabled: !onMouseDown,
deps: [isEditing],
}) })
const { const {

View File

@ -105,11 +105,13 @@ export const useDragDistance = ({
onDrag, onDrag,
distanceTolerance = 20, distanceTolerance = 20,
isDisabled = false, isDisabled = false,
deps = [],
}: { }: {
ref: React.MutableRefObject<HTMLDivElement | null> ref: React.MutableRefObject<HTMLDivElement | null>
onDrag: (position: { absolute: Coordinates; relative: Coordinates }) => void onDrag: (position: { absolute: Coordinates; relative: Coordinates }) => void
distanceTolerance?: number distanceTolerance?: number
isDisabled: boolean isDisabled: boolean
deps?: unknown[]
}) => { }) => {
const mouseDownPosition = useRef<{ const mouseDownPosition = useRef<{
absolute: Coordinates absolute: Coordinates
@ -119,7 +121,7 @@ export const useDragDistance = ({
const handleMouseUp = () => { const handleMouseUp = () => {
if (mouseDownPosition) mouseDownPosition.current = undefined if (mouseDownPosition) mouseDownPosition.current = undefined
} }
useEventListener('mouseup', handleMouseUp) useEventListener('mouseup', handleMouseUp, undefined, undefined, deps)
const handleMouseDown = (e: MouseEvent) => { const handleMouseDown = (e: MouseEvent) => {
if (isDisabled || !ref.current) return if (isDisabled || !ref.current) return
@ -133,7 +135,7 @@ export const useDragDistance = ({
}, },
} }
} }
useEventListener('mousedown', handleMouseDown, ref) useEventListener('mousedown', handleMouseDown, ref, undefined, deps)
useEffect(() => { useEffect(() => {
let triggered = false let triggered = false

View File

@ -9,7 +9,8 @@ function useEventListener<K extends keyof MediaQueryListEventMap>(
eventName: K, eventName: K,
handler: (event: MediaQueryListEventMap[K]) => void, handler: (event: MediaQueryListEventMap[K]) => void,
element: RefObject<MediaQueryList>, element: RefObject<MediaQueryList>,
options?: Options options?: Options,
deps?: unknown[]
): void ): void
// Window Event based useEventListener interface // Window Event based useEventListener interface
@ -17,7 +18,8 @@ function useEventListener<K extends keyof WindowEventMap>(
eventName: K, eventName: K,
handler: (event: WindowEventMap[K]) => void, handler: (event: WindowEventMap[K]) => void,
element?: undefined, element?: undefined,
options?: Options options?: Options,
deps?: unknown[]
): void ): void
// Element Event based useEventListener interface // Element Event based useEventListener interface
@ -28,7 +30,8 @@ function useEventListener<
eventName: K, eventName: K,
handler: (event: HTMLElementEventMap[K]) => void, handler: (event: HTMLElementEventMap[K]) => void,
element: RefObject<T>, element: RefObject<T>,
options?: Options options?: Options,
deps?: unknown[]
): void ): void
// Document Event based useEventListener interface // Document Event based useEventListener interface
@ -36,7 +39,8 @@ function useEventListener<K extends keyof DocumentEventMap>(
eventName: K, eventName: K,
handler: (event: DocumentEventMap[K]) => void, handler: (event: DocumentEventMap[K]) => void,
element: RefObject<Document>, element: RefObject<Document>,
options?: Options options?: Options,
deps?: unknown[]
): void ): void
function useEventListener< function useEventListener<
@ -54,7 +58,8 @@ function useEventListener<
| Event | Event
) => void, ) => void,
element?: RefObject<T>, element?: RefObject<T>,
options?: Options options?: Options,
deps: unknown[] = []
) { ) {
// Create a ref that stores handler // Create a ref that stores handler
const savedHandler = useRef(handler) const savedHandler = useRef(handler)
@ -72,7 +77,12 @@ function useEventListener<
if (!(targetElement && targetElement.addEventListener)) return if (!(targetElement && targetElement.addEventListener)) return
if (options && typeof options !== 'boolean') if (options && typeof options !== 'boolean')
console.log('Add event listener', { eventName, element, options }) console.log('Add event listener', {
elementText: (targetElement as HTMLElement).textContent,
eventName,
element: targetElement,
options,
})
// Create event listener that calls handler function stored in ref // Create event listener that calls handler function stored in ref
const listener: typeof handler = (event) => savedHandler.current(event) const listener: typeof handler = (event) => savedHandler.current(event)
@ -81,10 +91,16 @@ function useEventListener<
// Remove event listener on cleanup // Remove event listener on cleanup
return () => { return () => {
if (options && typeof options !== 'boolean') if (options && typeof options !== 'boolean')
console.log('Remove event listener') console.log('Remove event listener', {
elementText: (targetElement as HTMLElement).textContent,
eventName,
element: targetElement,
options,
})
targetElement.removeEventListener(eventName, listener, options) targetElement.removeEventListener(eventName, listener, options)
} }
}, [eventName, element, options]) // eslint-disable-next-line react-hooks/exhaustive-deps
}, [eventName, ...deps])
} }
export { useEventListener } export { useEventListener }

View File

@ -1,3 +1,4 @@
import { isNotEmpty } from '@typebot.io/lib'
import { useEventListener } from './useEventListener' import { useEventListener } from './useEventListener'
type Props = { type Props = {
@ -40,6 +41,10 @@ export const useKeyboardShortcuts = ({
event.key === 'Backspace' event.key === 'Backspace'
useEventListener('keydown', (event: KeyboardEvent) => { useEventListener('keydown', (event: KeyboardEvent) => {
if (!event.metaKey && !event.ctrlKey && event.key !== 'Backspace') return
// get text selection
const textSelection = window.getSelection()?.toString()
if (isNotEmpty(textSelection)) return
const target = event.target as HTMLElement | null const target = event.target as HTMLElement | null
const isTyping = const isTyping =
target?.role === 'textbox' || target?.role === 'textbox' ||