2
0

Add Graph draft

This commit is contained in:
Baptiste Arnaud
2021-12-16 10:43:49 +01:00
parent 0f85d2cd94
commit da9459edf3
35 changed files with 1938 additions and 116 deletions

View File

@ -1,59 +0,0 @@
import { BrowserJsPlumbInstance } from '@jsplumb/browser-ui'
import {
createContext,
Dispatch,
ReactNode,
SetStateAction,
useContext,
useState,
} from 'react'
import { Step } from 'bot-engine'
type Position = { x: number; y: number; scale: number }
const graphPositionDefaultValue = { x: 400, y: 100, scale: 1 }
const graphContext = createContext<{
position: Position
setGraphPosition: Dispatch<SetStateAction<Position>>
plumbInstance?: BrowserJsPlumbInstance
setPlumbInstance: Dispatch<SetStateAction<BrowserJsPlumbInstance | undefined>>
draggedStep?: Step
setDraggedStep: Dispatch<SetStateAction<Step | undefined>>
}>({
position: graphPositionDefaultValue,
setGraphPosition: () => {
console.log("I'm not instantiated")
},
setPlumbInstance: () => {
console.log("I'm not instantiated")
},
setDraggedStep: () => {
console.log("I'm not instantiated")
},
})
export const GraphProvider = ({ children }: { children: ReactNode }) => {
const [graphPosition, setGraphPosition] = useState(graphPositionDefaultValue)
const [plumbInstance, setPlumbInstance] = useState<
BrowserJsPlumbInstance | undefined
>()
const [draggedStep, setDraggedStep] = useState<Step | undefined>()
return (
<graphContext.Provider
value={{
position: graphPosition,
setGraphPosition,
plumbInstance,
setPlumbInstance,
draggedStep,
setDraggedStep,
}}
>
{children}
</graphContext.Provider>
)
}
export const useGraph = () => useContext(graphContext)

View File

@ -0,0 +1,39 @@
import { Step, StepType } from 'bot-engine'
import {
createContext,
Dispatch,
ReactNode,
SetStateAction,
useContext,
useState,
} from 'react'
const dndContext = createContext<{
draggedStepType?: StepType
setDraggedStepType: Dispatch<SetStateAction<StepType | undefined>>
draggedStep?: Step
setDraggedStep: Dispatch<SetStateAction<Step | undefined>>
}>({
setDraggedStep: () => console.log("I'm not implemented"),
setDraggedStepType: () => console.log("I'm not implemented"),
})
export const DndContext = ({ children }: { children: ReactNode }) => {
const [draggedStep, setDraggedStep] = useState<Step | undefined>()
const [draggedStepType, setDraggedStepType] = useState<StepType | undefined>()
return (
<dndContext.Provider
value={{
draggedStep,
setDraggedStep,
draggedStepType,
setDraggedStepType,
}}
>
{children}
</dndContext.Provider>
)
}
export const useDnd = () => useContext(dndContext)

View File

@ -0,0 +1,247 @@
import { Block, StartBlock, Step, StepType, Target } from 'bot-engine'
import {
createContext,
Dispatch,
ReactNode,
SetStateAction,
useContext,
useState,
} from 'react'
import { parseNewBlock, parseNewStep } from 'services/graph'
import { insertItemInList } from 'services/utils'
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 const firstStepOffsetY = 88
export const spaceBetweenSteps = 66
export type Coordinates = { x: number; y: number }
type Position = Coordinates & { scale: number }
export type Anchor = {
coordinates: Coordinates
}
export type Node = Omit<Block, 'steps'> & {
steps: (Step & {
sourceAnchorsPosition: { left: Coordinates; right: Coordinates }
})[]
}
export type NewBlockPayload = {
x: number
y: number
type?: StepType
step?: Step
}
const graphPositionDefaultValue = { x: 400, y: 100, scale: 1 }
const graphContext = createContext<{
graphPosition: Position
setGraphPosition: Dispatch<SetStateAction<Position>>
connectingIds: { blockId: string; stepId: string; target?: Target } | null
setConnectingIds: Dispatch<
SetStateAction<{ blockId: string; stepId: string; target?: Target } | null>
>
startBlock?: StartBlock
setStartBlock: Dispatch<SetStateAction<StartBlock | undefined>>
blocks: Block[]
setBlocks: Dispatch<SetStateAction<Block[]>>
addNewBlock: (props: NewBlockPayload) => void
updateBlockPosition: (blockId: string, newPositon: Coordinates) => void
addNewStepToBlock: (
blockId: string,
step: StepType | Step,
index: number
) => void
removeStepFromBlock: (blockId: string, stepId: string) => void
addTarget: (connectingIds: {
blockId: string
stepId: string
target?: Target
}) => void
removeTarget: (connectingIds: { blockId: string; stepId: string }) => void
}>({
graphPosition: graphPositionDefaultValue,
setGraphPosition: () => console.log("I'm not instantiated"),
connectingIds: null,
setConnectingIds: () => console.log("I'm not instantiated"),
blocks: [],
setBlocks: () => console.log("I'm not instantiated"),
updateBlockPosition: () => console.log("I'm not instantiated"),
addNewStepToBlock: () => console.log("I'm not instantiated"),
addNewBlock: () => console.log("I'm not instantiated"),
removeStepFromBlock: () => console.log("I'm not instantiated"),
addTarget: () => console.log("I'm not instantiated"),
removeTarget: () => console.log("I'm not instantiated"),
setStartBlock: () => console.log("I'm not instantiated"),
})
export const GraphProvider = ({ children }: { children: ReactNode }) => {
const [graphPosition, setGraphPosition] = useState(graphPositionDefaultValue)
const [connectingIds, setConnectingIds] = useState<{
blockId: string
stepId: string
target?: Target
} | null>(null)
const [blocks, setBlocks] = useState<Block[]>([])
const [startBlock, setStartBlock] = useState<StartBlock | undefined>()
const addNewBlock = ({ x, y, type, step }: NewBlockPayload) => {
const boardCoordinates = {
x,
y,
}
setBlocks((blocks) => [
...blocks.filter((block) => block.steps.length > 0),
parseNewBlock({
step,
type,
totalBlocks: blocks.length,
initialCoordinates: boardCoordinates,
}),
])
}
const updateBlockPosition = (blockId: string, newPosition: Coordinates) => {
setBlocks((blocks) =>
blocks.map((block) =>
block.id === blockId
? { ...block, graphCoordinates: newPosition }
: block
)
)
}
const addNewStepToBlock = (
blockId: string,
step: StepType | Step,
index: number
) => {
setBlocks((blocks) =>
blocks
.map((block) =>
block.id === blockId
? {
...block,
steps: insertItemInList<Step>(
block.steps,
index,
typeof step === 'string'
? parseNewStep(step as StepType, block.id)
: { ...step, blockId: block.id }
),
}
: block
)
.filter((block) => block.steps.length > 0)
)
}
const removeStepFromBlock = (blockId: string, stepId: string) => {
setBlocks((blocks) =>
blocks.map((block) =>
block.id === blockId
? {
...block,
steps: [...block.steps.filter((step) => step.id !== stepId)],
}
: block
)
)
}
const addTarget = ({
blockId,
stepId,
target,
}: {
blockId: string
stepId: string
target?: Target
}) => {
startBlock && blockId === 'start-block'
? setStartBlock({
...startBlock,
steps: [{ ...startBlock.steps[0], target }],
})
: setBlocks((blocks) =>
blocks.map((block) =>
block.id === blockId
? {
...block,
steps: [
...block.steps.map((step) =>
step.id === stepId ? { ...step, target } : step
),
],
}
: block
)
)
}
const removeTarget = ({
blockId,
stepId,
}: {
blockId: string
stepId: string
}) => {
setBlocks((blocks) =>
blocks.map((block) =>
block.id === blockId
? {
...block,
steps: [
...block.steps.map((step) =>
step.id === stepId ? { ...step, target: undefined } : step
),
],
}
: block
)
)
}
return (
<graphContext.Provider
value={{
graphPosition,
setGraphPosition,
connectingIds,
setConnectingIds,
blocks,
setBlocks,
updateBlockPosition,
addNewStepToBlock,
addNewBlock,
removeStepFromBlock,
addTarget,
removeTarget,
startBlock,
setStartBlock,
}}
>
{children}
</graphContext.Provider>
)
}
export const useGraph = () => useContext(graphContext)

View File

@ -0,0 +1,72 @@
import { useToast } from '@chakra-ui/react'
import { Typebot } from 'bot-engine'
import { useRouter } from 'next/router'
import { createContext, ReactNode, useContext, useEffect } from 'react'
import { fetcher } from 'services/utils'
import useSWR from 'swr'
const typebotContext = createContext<{
typebot?: Typebot
}>({})
export const TypebotContext = ({
children,
typebotId,
}: {
children: ReactNode
typebotId?: string
}) => {
const router = useRouter()
const toast = useToast({
position: 'top-right',
status: 'error',
})
const { typebot, isLoading } = useFetchedTypebot({
typebotId,
onError: (error) =>
toast({
title: 'Error while fetching typebot',
description: error.message,
}),
})
useEffect(() => {
if (isLoading) return
if (!typebot) {
toast({ status: 'info', description: "Couldn't find typebot" })
router.replace('/typebots')
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isLoading])
return (
<typebotContext.Provider
value={{
typebot,
}}
>
{children}
</typebotContext.Provider>
)
}
export const useTypebot = () => useContext(typebotContext)
export const useFetchedTypebot = ({
typebotId,
onError,
}: {
typebotId?: string
onError: (error: Error) => void
}) => {
const { data, error, mutate } = useSWR<{ typebot: Typebot }, Error>(
typebotId ? `/api/typebots/${typebotId}` : null,
fetcher
)
if (error) onError(error)
return {
typebot: data?.typebot,
isLoading: !error && !data,
mutate,
}
}