♻️ (builder) Change to features-centric folder structure

This commit is contained in:
Baptiste Arnaud
2022-11-15 09:35:48 +01:00
committed by Baptiste Arnaud
parent 3686465a85
commit 643571fe7d
683 changed files with 3907 additions and 3643 deletions

View 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)

View 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)

View File

@@ -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)

View File

@@ -0,0 +1,3 @@
export * from './GraphDndProvider'
export * from './GraphProvider'
export * from './GroupsCoordinateProvider'