♻️ (builder) Change to features-centric folder structure
This commit is contained in:
committed by
Baptiste Arnaud
parent
3686465a85
commit
643571fe7d
127
apps/builder/src/features/graph/providers/GraphDndProvider.tsx
Normal file
127
apps/builder/src/features/graph/providers/GraphDndProvider.tsx
Normal file
@@ -0,0 +1,127 @@
|
||||
import { useEventListener } from '@chakra-ui/react'
|
||||
import { ButtonItem, DraggableBlock, DraggableBlockType } from 'models'
|
||||
import {
|
||||
createContext,
|
||||
Dispatch,
|
||||
ReactNode,
|
||||
SetStateAction,
|
||||
useContext,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { Coordinates } from './GraphProvider'
|
||||
|
||||
type GroupInfo = {
|
||||
id: string
|
||||
ref: React.MutableRefObject<HTMLDivElement | null>
|
||||
}
|
||||
|
||||
const graphDndContext = createContext<{
|
||||
draggedBlockType?: DraggableBlockType
|
||||
setDraggedBlockType: Dispatch<SetStateAction<DraggableBlockType | undefined>>
|
||||
draggedBlock?: DraggableBlock
|
||||
setDraggedBlock: Dispatch<SetStateAction<DraggableBlock | undefined>>
|
||||
draggedItem?: ButtonItem
|
||||
setDraggedItem: Dispatch<SetStateAction<ButtonItem | undefined>>
|
||||
mouseOverGroup?: GroupInfo
|
||||
setMouseOverGroup: Dispatch<SetStateAction<GroupInfo | undefined>>
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
//@ts-ignore
|
||||
}>({})
|
||||
|
||||
export type NodePosition = { absolute: Coordinates; relative: Coordinates }
|
||||
|
||||
export const GraphDndProvider = ({ children }: { children: ReactNode }) => {
|
||||
const [draggedBlock, setDraggedBlock] = useState<DraggableBlock>()
|
||||
const [draggedBlockType, setDraggedBlockType] = useState<
|
||||
DraggableBlockType | undefined
|
||||
>()
|
||||
const [draggedItem, setDraggedItem] = useState<ButtonItem | undefined>()
|
||||
const [mouseOverGroup, setMouseOverGroup] = useState<GroupInfo>()
|
||||
|
||||
return (
|
||||
<graphDndContext.Provider
|
||||
value={{
|
||||
draggedBlock,
|
||||
setDraggedBlock,
|
||||
draggedBlockType,
|
||||
setDraggedBlockType,
|
||||
draggedItem,
|
||||
setDraggedItem,
|
||||
mouseOverGroup,
|
||||
setMouseOverGroup,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</graphDndContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export const useDragDistance = ({
|
||||
ref,
|
||||
onDrag,
|
||||
distanceTolerance = 20,
|
||||
isDisabled = false,
|
||||
}: {
|
||||
ref: React.MutableRefObject<HTMLDivElement | null>
|
||||
onDrag: (position: { absolute: Coordinates; relative: Coordinates }) => void
|
||||
distanceTolerance?: number
|
||||
isDisabled: boolean
|
||||
}) => {
|
||||
const mouseDownPosition = useRef<{
|
||||
absolute: Coordinates
|
||||
relative: Coordinates
|
||||
}>()
|
||||
|
||||
const handleMouseUp = () => {
|
||||
if (mouseDownPosition) mouseDownPosition.current = undefined
|
||||
}
|
||||
useEventListener('mouseup', handleMouseUp)
|
||||
|
||||
const handleMouseDown = (e: MouseEvent) => {
|
||||
if (isDisabled || !ref.current) return
|
||||
e.stopPropagation()
|
||||
const { top, left } = ref.current.getBoundingClientRect()
|
||||
mouseDownPosition.current = {
|
||||
absolute: { x: e.clientX, y: e.clientY },
|
||||
relative: {
|
||||
x: e.clientX - left,
|
||||
y: e.clientY - top,
|
||||
},
|
||||
}
|
||||
}
|
||||
useEventListener('mousedown', handleMouseDown, ref.current)
|
||||
|
||||
const handleMouseMove = (e: MouseEvent) => {
|
||||
if (!mouseDownPosition.current) return
|
||||
const { clientX, clientY } = e
|
||||
if (
|
||||
Math.abs(mouseDownPosition.current.absolute.x - clientX) >
|
||||
distanceTolerance ||
|
||||
Math.abs(mouseDownPosition.current.absolute.y - clientY) >
|
||||
distanceTolerance
|
||||
) {
|
||||
onDrag(mouseDownPosition.current)
|
||||
}
|
||||
}
|
||||
useEventListener('mousemove', handleMouseMove)
|
||||
}
|
||||
|
||||
export const computeNearestPlaceholderIndex = (
|
||||
offsetY: number,
|
||||
placeholderRefs: React.MutableRefObject<HTMLDivElement[]>
|
||||
) => {
|
||||
const { closestIndex } = placeholderRefs.current.reduce(
|
||||
(prev, elem, index) => {
|
||||
const elementTop = elem.getBoundingClientRect().top
|
||||
const mouseDistanceFromPlaceholder = Math.abs(offsetY - elementTop)
|
||||
return mouseDistanceFromPlaceholder < prev.value
|
||||
? { closestIndex: index, value: mouseDistanceFromPlaceholder }
|
||||
: prev
|
||||
},
|
||||
{ closestIndex: 0, value: 999999999999 }
|
||||
)
|
||||
return closestIndex
|
||||
}
|
||||
|
||||
export const useBlockDnd = () => useContext(graphDndContext)
|
||||
136
apps/builder/src/features/graph/providers/GraphProvider.tsx
Normal file
136
apps/builder/src/features/graph/providers/GraphProvider.tsx
Normal file
@@ -0,0 +1,136 @@
|
||||
import { Group, Edge, IdMap, Source, Block, Target } from 'models'
|
||||
import {
|
||||
createContext,
|
||||
Dispatch,
|
||||
MutableRefObject,
|
||||
ReactNode,
|
||||
SetStateAction,
|
||||
useContext,
|
||||
useState,
|
||||
} from 'react'
|
||||
|
||||
export const stubLength = 20
|
||||
export const blockWidth = 300
|
||||
export const blockAnchorsOffset = {
|
||||
left: {
|
||||
x: 0,
|
||||
y: 20,
|
||||
},
|
||||
top: {
|
||||
x: blockWidth / 2,
|
||||
y: 0,
|
||||
},
|
||||
right: {
|
||||
x: blockWidth,
|
||||
y: 20,
|
||||
},
|
||||
}
|
||||
|
||||
export type Coordinates = { x: number; y: number }
|
||||
|
||||
type Position = Coordinates & { scale: number }
|
||||
|
||||
export type Anchor = {
|
||||
coordinates: Coordinates
|
||||
}
|
||||
|
||||
export type Node = Omit<Group, 'blocks'> & {
|
||||
blocks: (Block & {
|
||||
sourceAnchorsPosition: { left: Coordinates; right: Coordinates }
|
||||
})[]
|
||||
}
|
||||
|
||||
export const graphPositionDefaultValue = { x: 400, y: 100, scale: 1 }
|
||||
|
||||
export type ConnectingIds = {
|
||||
source: Source
|
||||
target?: Target
|
||||
}
|
||||
|
||||
type BlockId = string
|
||||
type ButtonId = string
|
||||
export type Endpoint = {
|
||||
id: BlockId | ButtonId
|
||||
ref: MutableRefObject<HTMLDivElement | null>
|
||||
}
|
||||
|
||||
export type GroupsCoordinates = IdMap<Coordinates>
|
||||
|
||||
const graphContext = createContext<{
|
||||
graphPosition: Position
|
||||
setGraphPosition: Dispatch<SetStateAction<Position>>
|
||||
connectingIds: ConnectingIds | null
|
||||
setConnectingIds: Dispatch<SetStateAction<ConnectingIds | null>>
|
||||
previewingEdge?: Edge
|
||||
setPreviewingEdge: Dispatch<SetStateAction<Edge | undefined>>
|
||||
sourceEndpoints: IdMap<Endpoint>
|
||||
addSourceEndpoint: (endpoint: Endpoint) => void
|
||||
targetEndpoints: IdMap<Endpoint>
|
||||
addTargetEndpoint: (endpoint: Endpoint) => void
|
||||
openedBlockId?: string
|
||||
setOpenedBlockId: Dispatch<SetStateAction<string | undefined>>
|
||||
isReadOnly: boolean
|
||||
focusedGroupId?: string
|
||||
setFocusedGroupId: Dispatch<SetStateAction<string | undefined>>
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
//@ts-ignore
|
||||
}>({
|
||||
graphPosition: graphPositionDefaultValue,
|
||||
connectingIds: null,
|
||||
})
|
||||
|
||||
export const GraphProvider = ({
|
||||
children,
|
||||
isReadOnly = false,
|
||||
}: {
|
||||
children: ReactNode
|
||||
isReadOnly?: boolean
|
||||
}) => {
|
||||
const [graphPosition, setGraphPosition] = useState(graphPositionDefaultValue)
|
||||
const [connectingIds, setConnectingIds] = useState<ConnectingIds | null>(null)
|
||||
const [previewingEdge, setPreviewingEdge] = useState<Edge>()
|
||||
const [sourceEndpoints, setSourceEndpoints] = useState<IdMap<Endpoint>>({})
|
||||
const [targetEndpoints, setTargetEndpoints] = useState<IdMap<Endpoint>>({})
|
||||
const [openedBlockId, setOpenedBlockId] = useState<string>()
|
||||
const [focusedGroupId, setFocusedGroupId] = useState<string>()
|
||||
|
||||
const addSourceEndpoint = (endpoint: Endpoint) => {
|
||||
setSourceEndpoints((endpoints) => ({
|
||||
...endpoints,
|
||||
[endpoint.id]: endpoint,
|
||||
}))
|
||||
}
|
||||
|
||||
const addTargetEndpoint = (endpoint: Endpoint) => {
|
||||
setTargetEndpoints((endpoints) => ({
|
||||
...endpoints,
|
||||
[endpoint.id]: endpoint,
|
||||
}))
|
||||
}
|
||||
|
||||
return (
|
||||
<graphContext.Provider
|
||||
value={{
|
||||
graphPosition,
|
||||
setGraphPosition,
|
||||
connectingIds,
|
||||
setConnectingIds,
|
||||
previewingEdge,
|
||||
setPreviewingEdge,
|
||||
sourceEndpoints,
|
||||
targetEndpoints,
|
||||
addSourceEndpoint,
|
||||
addTargetEndpoint,
|
||||
openedBlockId,
|
||||
setOpenedBlockId,
|
||||
isReadOnly,
|
||||
focusedGroupId,
|
||||
setFocusedGroupId,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</graphContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export const useGraph = () => useContext(graphContext)
|
||||
@@ -0,0 +1,58 @@
|
||||
import { Group } from 'models'
|
||||
import {
|
||||
ReactNode,
|
||||
useState,
|
||||
useEffect,
|
||||
useContext,
|
||||
createContext,
|
||||
} from 'react'
|
||||
import { GroupsCoordinates, Coordinates } from './GraphProvider'
|
||||
|
||||
const groupsCoordinatesContext = createContext<{
|
||||
groupsCoordinates: GroupsCoordinates
|
||||
updateGroupCoordinates: (groupId: string, newCoord: Coordinates) => void
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
//@ts-ignore
|
||||
}>({})
|
||||
|
||||
export const GroupsCoordinatesProvider = ({
|
||||
children,
|
||||
groups,
|
||||
}: {
|
||||
children: ReactNode
|
||||
groups: Group[]
|
||||
isReadOnly?: boolean
|
||||
}) => {
|
||||
const [groupsCoordinates, setGroupsCoordinates] = useState<GroupsCoordinates>(
|
||||
{}
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
setGroupsCoordinates(
|
||||
groups.reduce(
|
||||
(coords, block) => ({
|
||||
...coords,
|
||||
[block.id]: block.graphCoordinates,
|
||||
}),
|
||||
{}
|
||||
)
|
||||
)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [groups])
|
||||
|
||||
const updateGroupCoordinates = (groupId: string, newCoord: Coordinates) =>
|
||||
setGroupsCoordinates((groupsCoordinates) => ({
|
||||
...groupsCoordinates,
|
||||
[groupId]: newCoord,
|
||||
}))
|
||||
|
||||
return (
|
||||
<groupsCoordinatesContext.Provider
|
||||
value={{ groupsCoordinates, updateGroupCoordinates }}
|
||||
>
|
||||
{children}
|
||||
</groupsCoordinatesContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export const useGroupsCoordinates = () => useContext(groupsCoordinatesContext)
|
||||
3
apps/builder/src/features/graph/providers/index.ts
Normal file
3
apps/builder/src/features/graph/providers/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from './GraphDndProvider'
|
||||
export * from './GraphProvider'
|
||||
export * from './GroupsCoordinateProvider'
|
||||
Reference in New Issue
Block a user