Skip to content

Commit

Permalink
Add Graph draft
Browse files Browse the repository at this point in the history
  • Loading branch information
baptisteArno committed Dec 16, 2021
1 parent 0f85d2c commit da9459e
Show file tree
Hide file tree
Showing 35 changed files with 1,938 additions and 116 deletions.
32 changes: 32 additions & 0 deletions apps/builder/assets/icons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,35 @@ export const FolderPlusIcon = (props: IconProps) => (
<line x1="9" y1="14" x2="15" y2="14"></line>
</Icon>
)

export const TextIcon = (props: IconProps) => (
<Icon viewBox="0 0 24 24" {...featherIconsBaseProps} {...props}>
<polyline points="4 7 4 4 20 4 20 7"></polyline>
<line x1="9" y1="20" x2="15" y2="20"></line>
<line x1="12" y1="4" x2="12" y2="20"></line>
</Icon>
)

export const ImageIcon = (props: IconProps) => (
<Icon viewBox="0 0 24 24" {...featherIconsBaseProps} {...props}>
<rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
<circle cx="8.5" cy="8.5" r="1.5"></circle>
<polyline points="21 15 16 10 5 21"></polyline>
</Icon>
)

export const CalendarIcon = (props: IconProps) => (
<Icon viewBox="0 0 24 24" {...featherIconsBaseProps} {...props}>
<rect x="3" y="4" width="18" height="18" rx="2" ry="2"></rect>
<line x1="16" y1="2" x2="16" y2="6"></line>
<line x1="8" y1="2" x2="8" y2="6"></line>
<line x1="3" y1="10" x2="21" y2="10"></line>
</Icon>
)

export const FlagIcon = (props: IconProps) => (
<Icon viewBox="0 0 24 24" {...featherIconsBaseProps} {...props}>
<path d="M4 15s1-1 4-1 5 2 8 2 4-1 4-1V3s-1 1-4 1-5-2-8-2-4 1-4 1z"></path>
<line x1="4" y1="22" x2="4" y2="15"></line>
</Icon>
)
14 changes: 14 additions & 0 deletions apps/builder/components/board/Board.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Flex } from '@chakra-ui/react'
import React from 'react'
import StepsList from './StepTypesList'
import Graph from './graph/Graph'
import { DndContext } from 'contexts/DndContext'

export const Board = () => (
<Flex flex="1" pos="relative" bgColor="gray.50">
<DndContext>
<StepsList />
<Graph />
</DndContext>
</Flex>
)
70 changes: 70 additions & 0 deletions apps/builder/components/board/StepTypesList/StepCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { Button, ButtonProps, Flex, HStack } from '@chakra-ui/react'
import { StepType } from 'bot-engine'
import { useDnd } from 'contexts/DndContext'
import React, { useEffect, useState } from 'react'
import { StepIcon } from './StepIcon'
import { StepLabel } from './StepLabel'

export const StepCard = ({
type,
onMouseDown,
}: {
type: StepType
onMouseDown: (e: React.MouseEvent, type: StepType) => void
}) => {
const { draggedStepType } = useDnd()
const [isMouseDown, setIsMouseDown] = useState(false)

useEffect(() => {
setIsMouseDown(draggedStepType === type)
}, [draggedStepType, type])

const handleMouseDown = (e: React.MouseEvent) => onMouseDown(e, type)

return (
<Flex pos="relative">
<Button
as={HStack}
borderWidth="1px"
rounded="lg"
flex="1"
cursor={'grab'}
colorScheme="gray"
opacity={isMouseDown ? '0.4' : '1'}
onMouseDown={handleMouseDown}
>
{!isMouseDown && (
<>
<StepIcon type={type} />
<StepLabel type={type} />
</>
)}
</Button>
</Flex>
)
}

export const StepCardOverlay = ({
type,
...props
}: Omit<ButtonProps, 'type'> & { type: StepType }) => {
return (
<Button
as={HStack}
borderWidth="1px"
rounded="lg"
cursor={'grab'}
colorScheme="gray"
w="147px"
pos="fixed"
top="0"
left="0"
transition="none"
pointerEvents="none"
{...props}
>
<StepIcon type={type} />
<StepLabel type={type} />
</Button>
)
}
25 changes: 25 additions & 0 deletions apps/builder/components/board/StepTypesList/StepIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { CalendarIcon, FlagIcon, ImageIcon, TextIcon } from 'assets/icons'
import { StepType } from 'bot-engine'
import React from 'react'

type StepIconProps = { type: StepType }

export const StepIcon = ({ type }: StepIconProps) => {
switch (type) {
case StepType.TEXT: {
return <TextIcon />
}
case StepType.IMAGE: {
return <ImageIcon />
}
case StepType.DATE_PICKER: {
return <CalendarIcon />
}
case StepType.START: {
return <FlagIcon />
}
default: {
return <TextIcon />
}
}
}
22 changes: 22 additions & 0 deletions apps/builder/components/board/StepTypesList/StepLabel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Text } from '@chakra-ui/react'
import { StepType } from 'bot-engine'
import React from 'react'

type Props = { type: StepType }

export const StepLabel = ({ type }: Props) => {
switch (type) {
case StepType.TEXT: {
return <Text>Text</Text>
}
case StepType.IMAGE: {
return <Text>Image</Text>
}
case StepType.DATE_PICKER: {
return <Text>Date</Text>
}
default: {
return <></>
}
}
}
104 changes: 104 additions & 0 deletions apps/builder/components/board/StepTypesList/StepTypesList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import {
Stack,
Input,
Text,
SimpleGrid,
useEventListener,
} from '@chakra-ui/react'
import { StepType } from 'bot-engine'
import { useDnd } from 'contexts/DndContext'
import React, { useState } from 'react'
import { StepCard, StepCardOverlay } from './StepCard'

export const stepListItems: {
bubbles: { type: StepType }[]
inputs: { type: StepType }[]
} = {
bubbles: [{ type: StepType.TEXT }, { type: StepType.IMAGE }],
inputs: [{ type: StepType.DATE_PICKER }],
}

export const StepTypesList = () => {
const { setDraggedStepType, draggedStepType } = useDnd()
const [position, setPosition] = useState({
x: 0,
y: 0,
})
const [relativeCoordinates, setRelativeCoordinates] = useState({ x: 0, y: 0 })

const handleMouseMove = (event: MouseEvent) => {
if (!draggedStepType) return
const { clientX, clientY } = event
setPosition({
...position,
x: clientX - relativeCoordinates.x,
y: clientY - relativeCoordinates.y,
})
}
useEventListener('mousemove', handleMouseMove)

const handleMouseDown = (e: React.MouseEvent, type: StepType) => {
const element = e.currentTarget as HTMLDivElement
const rect = element.getBoundingClientRect()
const relativeX = e.clientX - rect.left
const relativeY = e.clientY - rect.top
setPosition({ x: e.clientX - relativeX, y: e.clientY - relativeY })
setRelativeCoordinates({ x: relativeX, y: relativeY })
setDraggedStepType(type)
}

const handleMouseUp = () => {
if (!draggedStepType) return
setDraggedStepType(undefined)
setPosition({
x: 0,
y: 0,
})
}
useEventListener('mouseup', handleMouseUp)

return (
<Stack
w="320px"
pos="absolute"
left="10px"
top="20px"
h="calc(100vh - 100px)"
rounded="lg"
shadow="lg"
borderWidth="1px"
zIndex="10"
py="4"
px="2"
bgColor="white"
>
<Input placeholder="Search..." />
<Text fontSize="sm" fontWeight="semibold" color="gray.600">
Bubbles
</Text>
<SimpleGrid columns={2} spacing="2">
{stepListItems.bubbles.map((props) => (
<StepCard key={props.type} onMouseDown={handleMouseDown} {...props} />
))}
</SimpleGrid>

<Text fontSize="sm" fontWeight="semibold" color="gray.600">
Inputs
</Text>
<SimpleGrid columns={2} spacing="2">
{stepListItems.inputs.map((props) => (
<StepCard key={props.type} onMouseDown={handleMouseDown} {...props} />
))}
</SimpleGrid>
{draggedStepType && (
<StepCardOverlay
type={draggedStepType}
onMouseUp={handleMouseUp}
style={{
transform: `translate(${position.x}px, ${position.y}px) rotate(-2deg)`,
}}
/>
)}
</Stack>
)
}
1 change: 1 addition & 0 deletions apps/builder/components/board/StepTypesList/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { StepTypesList as default } from './StepTypesList'
112 changes: 112 additions & 0 deletions apps/builder/components/board/graph/BlockNode/BlockNode.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import {
Editable,
EditableInput,
EditablePreview,
Stack,
useEventListener,
} from '@chakra-ui/react'
import React, { useEffect, useRef, useState } from 'react'
import { Block, StartBlock } from 'bot-engine'
import { useGraph } from 'contexts/GraphContext'
import { useDnd } from 'contexts/DndContext'
import { StepsList } from './StepsList'
import { isNotDefined } from 'services/utils'

export const BlockNode = ({ block }: { block: Block | StartBlock }) => {
const {
updateBlockPosition,
addNewStepToBlock,
connectingIds,
setConnectingIds,
} = useGraph()
const { draggedStep, draggedStepType, setDraggedStepType, setDraggedStep } =
useDnd()
const blockRef = useRef<HTMLDivElement | null>(null)
const [isMouseDown, setIsMouseDown] = useState(false)
const [titleValue, setTitleValue] = useState(block.title)
const [showSortPlaceholders, setShowSortPlaceholders] = useState(false)
const [isConnecting, setIsConnecting] = useState(false)

useEffect(() => {
setIsConnecting(
connectingIds?.target?.blockId === block.id &&
isNotDefined(connectingIds.target?.stepId)
)
}, [block.id, connectingIds])

const handleTitleChange = (title: string) => setTitleValue(title)

const handleMouseDown = () => {
setIsMouseDown(true)
}
const handleMouseUp = () => {
setIsMouseDown(false)
}

const handleMouseMove = (event: MouseEvent) => {
if (!isMouseDown) return
const { movementX, movementY } = event

updateBlockPosition(block.id, {
x: block.graphCoordinates.x + movementX,
y: block.graphCoordinates.y + movementY,
})
}

useEventListener('mousemove', handleMouseMove)

const handleMouseEnter = () => {
if (draggedStepType || draggedStep) setShowSortPlaceholders(true)
if (connectingIds)
setConnectingIds({ ...connectingIds, target: { blockId: block.id } })
}

const handleMouseLeave = () => {
setShowSortPlaceholders(false)
if (connectingIds) setConnectingIds({ ...connectingIds, target: undefined })
}

const handleStepDrop = (index: number) => {
setShowSortPlaceholders(false)
if (draggedStepType) {
addNewStepToBlock(block.id, draggedStepType, index)
setDraggedStepType(undefined)
}
if (draggedStep) {
addNewStepToBlock(block.id, draggedStep, index)
setDraggedStep(undefined)
}
}

return (
<Stack
p="4"
rounded="lg"
bgColor="blue.50"
borderWidth="2px"
borderColor={isConnecting ? 'blue.400' : 'gray.400'}
minW="300px"
transition="border 300ms"
pos="absolute"
style={{
transform: `translate(${block.graphCoordinates.x}px, ${block.graphCoordinates.y}px)`,
}}
onMouseDown={handleMouseDown}
onMouseUp={handleMouseUp}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
ref={blockRef}
>
<Editable value={titleValue} onChange={handleTitleChange}>
<EditablePreview _hover={{ bgColor: 'blue.100' }} px="1" />
<EditableInput minW="0" px="1" />
</Editable>
<StepsList
blockId={block.id}
steps={block.steps}
showSortPlaceholders={showSortPlaceholders}
onMouseUp={handleStepDrop}
/>
</Stack>
)
}
Loading

0 comments on commit da9459e

Please sign in to comment.