feat(inputs): ✨ Add number input
This commit is contained in:
@ -208,3 +208,12 @@ export const DownloadIcon = (props: IconProps) => (
|
||||
<line x1="12" y1="15" x2="12" y2="3"></line>
|
||||
</Icon>
|
||||
)
|
||||
|
||||
export const NumberIcon = (props: IconProps) => (
|
||||
<Icon viewBox="0 0 24 24" {...featherIconsBaseProps} {...props}>
|
||||
<line x1="4" y1="9" x2="20" y2="9"></line>
|
||||
<line x1="4" y1="15" x2="20" y2="15"></line>
|
||||
<line x1="10" y1="3" x2="8" y2="21"></line>
|
||||
<line x1="16" y1="3" x2="14" y2="21"></line>
|
||||
</Icon>
|
||||
)
|
||||
|
@ -1,16 +1,19 @@
|
||||
import { Button, ButtonProps, Flex, HStack } from '@chakra-ui/react'
|
||||
import { StepType } from 'models'
|
||||
import { BubbleStepType, InputStepType, StepType } from 'models'
|
||||
import { useDnd } from 'contexts/DndContext'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { StepIcon } from './StepIcon'
|
||||
import { StepLabel } from './StepLabel'
|
||||
import { StepTypeLabel } from './StepTypeLabel'
|
||||
|
||||
export const StepCard = ({
|
||||
type,
|
||||
onMouseDown,
|
||||
}: {
|
||||
type: StepType
|
||||
onMouseDown: (e: React.MouseEvent, type: StepType) => void
|
||||
type: BubbleStepType | InputStepType
|
||||
onMouseDown: (
|
||||
e: React.MouseEvent,
|
||||
type: BubbleStepType | InputStepType
|
||||
) => void
|
||||
}) => {
|
||||
const { draggedStepType } = useDnd()
|
||||
const [isMouseDown, setIsMouseDown] = useState(false)
|
||||
@ -35,7 +38,7 @@ export const StepCard = ({
|
||||
{!isMouseDown && (
|
||||
<>
|
||||
<StepIcon type={type} />
|
||||
<StepLabel type={type} />
|
||||
<StepTypeLabel type={type} />
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
@ -62,7 +65,7 @@ export const StepCardOverlay = ({
|
||||
{...props}
|
||||
>
|
||||
<StepIcon type={type} />
|
||||
<StepLabel type={type} />
|
||||
<StepTypeLabel type={type} />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
@ -1,22 +1,25 @@
|
||||
import { ChatIcon, FlagIcon, TextIcon } from 'assets/icons'
|
||||
import { StepType } from 'models'
|
||||
import { ChatIcon, FlagIcon, NumberIcon, TextIcon } from 'assets/icons'
|
||||
import { BubbleStepType, InputStepType, StepType } from 'models'
|
||||
import React from 'react'
|
||||
|
||||
type StepIconProps = { type: StepType }
|
||||
|
||||
export const StepIcon = ({ type }: StepIconProps) => {
|
||||
switch (type) {
|
||||
case StepType.TEXT: {
|
||||
case BubbleStepType.TEXT: {
|
||||
return <ChatIcon />
|
||||
}
|
||||
case StepType.TEXT: {
|
||||
case InputStepType.TEXT: {
|
||||
return <TextIcon />
|
||||
}
|
||||
case StepType.START: {
|
||||
case InputStepType.NUMBER: {
|
||||
return <NumberIcon />
|
||||
}
|
||||
case 'start': {
|
||||
return <FlagIcon />
|
||||
}
|
||||
default: {
|
||||
return <TextIcon />
|
||||
return <></>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,19 +0,0 @@
|
||||
import { Text } from '@chakra-ui/react'
|
||||
import { StepType } from 'models'
|
||||
import React from 'react'
|
||||
|
||||
type Props = { type: StepType }
|
||||
|
||||
export const StepLabel = ({ type }: Props) => {
|
||||
switch (type) {
|
||||
case StepType.TEXT: {
|
||||
return <Text>Text</Text>
|
||||
}
|
||||
case StepType.TEXT_INPUT: {
|
||||
return <Text>Text</Text>
|
||||
}
|
||||
default: {
|
||||
return <></>
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
import { Text } from '@chakra-ui/react'
|
||||
import { BubbleStepType, InputStepType, StepType } from 'models'
|
||||
import React from 'react'
|
||||
|
||||
type Props = { type: StepType }
|
||||
|
||||
export const StepTypeLabel = ({ type }: Props) => {
|
||||
switch (type) {
|
||||
case BubbleStepType.TEXT:
|
||||
case InputStepType.TEXT: {
|
||||
return <Text>Text</Text>
|
||||
}
|
||||
case InputStepType.NUMBER: {
|
||||
return <Text>Number</Text>
|
||||
}
|
||||
default: {
|
||||
return <></>
|
||||
}
|
||||
}
|
||||
}
|
@ -5,19 +5,11 @@ import {
|
||||
SimpleGrid,
|
||||
useEventListener,
|
||||
} from '@chakra-ui/react'
|
||||
import { StepType } from 'models'
|
||||
import { BubbleStepType, InputStepType } from 'models'
|
||||
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 }],
|
||||
inputs: [{ type: StepType.TEXT_INPUT }],
|
||||
}
|
||||
|
||||
export const StepTypesList = () => {
|
||||
const { setDraggedStepType, draggedStepType } = useDnd()
|
||||
const [position, setPosition] = useState({
|
||||
@ -37,7 +29,10 @@ export const StepTypesList = () => {
|
||||
}
|
||||
useEventListener('mousemove', handleMouseMove)
|
||||
|
||||
const handleMouseDown = (e: React.MouseEvent, type: StepType) => {
|
||||
const handleMouseDown = (
|
||||
e: React.MouseEvent,
|
||||
type: BubbleStepType | InputStepType
|
||||
) => {
|
||||
const element = e.currentTarget as HTMLDivElement
|
||||
const rect = element.getBoundingClientRect()
|
||||
const relativeX = e.clientX - rect.left
|
||||
@ -77,8 +72,8 @@ export const StepTypesList = () => {
|
||||
Bubbles
|
||||
</Text>
|
||||
<SimpleGrid columns={2} spacing="2">
|
||||
{stepListItems.bubbles.map((props) => (
|
||||
<StepCard key={props.type} onMouseDown={handleMouseDown} {...props} />
|
||||
{Object.values(BubbleStepType).map((type) => (
|
||||
<StepCard key={type} type={type} onMouseDown={handleMouseDown} />
|
||||
))}
|
||||
</SimpleGrid>
|
||||
|
||||
@ -86,8 +81,8 @@ export const StepTypesList = () => {
|
||||
Inputs
|
||||
</Text>
|
||||
<SimpleGrid columns={2} spacing="2">
|
||||
{stepListItems.inputs.map((props) => (
|
||||
<StepCard key={props.type} onMouseDown={handleMouseDown} {...props} />
|
||||
{Object.values(InputStepType).map((type) => (
|
||||
<StepCard key={type} type={type} onMouseDown={handleMouseDown} />
|
||||
))}
|
||||
</SimpleGrid>
|
||||
{draggedStepType && (
|
||||
|
@ -0,0 +1,84 @@
|
||||
import { FormLabel, HStack, Stack } from '@chakra-ui/react'
|
||||
import { SmartNumberInput } from 'components/settings/SmartNumberInput'
|
||||
import { DebouncedInput } from 'components/shared/DebouncedInput'
|
||||
import { NumberInputOptions } from 'models'
|
||||
import React from 'react'
|
||||
import { removeUndefinedFields } from 'services/utils'
|
||||
|
||||
type NumberInputSettingsBodyProps = {
|
||||
options?: NumberInputOptions
|
||||
onOptionsChange: (options: NumberInputOptions) => void
|
||||
}
|
||||
|
||||
export const NumberInputSettingsBody = ({
|
||||
options,
|
||||
onOptionsChange,
|
||||
}: NumberInputSettingsBodyProps) => {
|
||||
const handlePlaceholderChange = (placeholder: string) =>
|
||||
onOptionsChange({ ...options, labels: { ...options?.labels, placeholder } })
|
||||
const handleButtonLabelChange = (button: string) =>
|
||||
onOptionsChange({ ...options, labels: { ...options?.labels, button } })
|
||||
const handleMinChange = (min?: number) =>
|
||||
onOptionsChange(removeUndefinedFields({ ...options, min }))
|
||||
const handleMaxChange = (max?: number) =>
|
||||
onOptionsChange(removeUndefinedFields({ ...options, max }))
|
||||
const handleStepChange = (step?: number) =>
|
||||
onOptionsChange(removeUndefinedFields({ ...options, step }))
|
||||
|
||||
return (
|
||||
<Stack spacing={4}>
|
||||
<Stack>
|
||||
<FormLabel mb="0" htmlFor="placeholder">
|
||||
Placeholder:
|
||||
</FormLabel>
|
||||
<DebouncedInput
|
||||
id="placeholder"
|
||||
initialValue={options?.labels?.placeholder ?? 'Type your answer...'}
|
||||
delay={100}
|
||||
onChange={handlePlaceholderChange}
|
||||
/>
|
||||
</Stack>
|
||||
<Stack>
|
||||
<FormLabel mb="0" htmlFor="button">
|
||||
Button label:
|
||||
</FormLabel>
|
||||
<DebouncedInput
|
||||
id="button"
|
||||
initialValue={options?.labels?.button ?? 'Send'}
|
||||
delay={100}
|
||||
onChange={handleButtonLabelChange}
|
||||
/>
|
||||
</Stack>
|
||||
<HStack justifyContent="space-between">
|
||||
<FormLabel mb="0" htmlFor="min">
|
||||
Min:
|
||||
</FormLabel>
|
||||
<SmartNumberInput
|
||||
id="min"
|
||||
initialValue={options?.min}
|
||||
onValueChange={handleMinChange}
|
||||
/>
|
||||
</HStack>
|
||||
<HStack justifyContent="space-between">
|
||||
<FormLabel mb="0" htmlFor="max">
|
||||
Max:
|
||||
</FormLabel>
|
||||
<SmartNumberInput
|
||||
id="max"
|
||||
initialValue={options?.max}
|
||||
onValueChange={handleMaxChange}
|
||||
/>
|
||||
</HStack>
|
||||
<HStack justifyContent="space-between">
|
||||
<FormLabel mb="0" htmlFor="step">
|
||||
Step:
|
||||
</FormLabel>
|
||||
<SmartNumberInput
|
||||
id="step"
|
||||
initialValue={options?.step}
|
||||
onValueChange={handleStepChange}
|
||||
/>
|
||||
</HStack>
|
||||
</Stack>
|
||||
)
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
import { PopoverContent, PopoverArrow, PopoverBody } from '@chakra-ui/react'
|
||||
import { useTypebot } from 'contexts/TypebotContext/TypebotContext'
|
||||
import { Step, StepType, TextInputOptions } from 'models'
|
||||
import { InputStepType, Step, TextInputOptions } from 'models'
|
||||
import { NumberInputSettingsBody } from './NumberInputSettingsBody'
|
||||
import { TextInputSettingsBody } from './TextInputSettingsBody'
|
||||
|
||||
type Props = {
|
||||
@ -25,7 +26,7 @@ const SettingsPopoverBodyContent = ({ step }: Props) => {
|
||||
updateStep(step.id, { options } as Partial<Step>)
|
||||
|
||||
switch (step.type) {
|
||||
case StepType.TEXT_INPUT: {
|
||||
case InputStepType.TEXT: {
|
||||
return (
|
||||
<TextInputSettingsBody
|
||||
options={step.options}
|
||||
@ -33,6 +34,14 @@ const SettingsPopoverBodyContent = ({ step }: Props) => {
|
||||
/>
|
||||
)
|
||||
}
|
||||
case InputStepType.NUMBER: {
|
||||
return (
|
||||
<NumberInputSettingsBody
|
||||
options={step.options}
|
||||
onOptionsChange={handleOptionsChange}
|
||||
/>
|
||||
)
|
||||
}
|
||||
default: {
|
||||
return <></>
|
||||
}
|
||||
|
@ -1,31 +0,0 @@
|
||||
import { Flex, Text } from '@chakra-ui/react'
|
||||
import { Step, StartStep, StepType } from 'models'
|
||||
|
||||
export const StepContent = (props: Step | StartStep) => {
|
||||
switch (props.type) {
|
||||
case StepType.TEXT: {
|
||||
return (
|
||||
<Flex
|
||||
flexDir={'column'}
|
||||
opacity={props.content.html === '' ? '0.5' : '1'}
|
||||
className="slate-html-container"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html:
|
||||
props.content.html === ''
|
||||
? `<p>Click to edit...</p>`
|
||||
: props.content.html,
|
||||
}}
|
||||
></Flex>
|
||||
)
|
||||
}
|
||||
case StepType.TEXT_INPUT: {
|
||||
return <Text color={'gray.500'}>Type your answer...</Text>
|
||||
}
|
||||
case StepType.START: {
|
||||
return <Text>{props.label}</Text>
|
||||
}
|
||||
default: {
|
||||
return <Text>No input</Text>
|
||||
}
|
||||
}
|
||||
}
|
@ -11,15 +11,15 @@ import { Block, Step } from 'models'
|
||||
import { SourceEndpoint } from './SourceEndpoint'
|
||||
import { useGraph } from 'contexts/GraphContext'
|
||||
import { StepIcon } from 'components/board/StepTypesList/StepIcon'
|
||||
import { isDefined } from 'utils'
|
||||
import { isDefined, isTextBubbleStep } from 'utils'
|
||||
import { Coordinates } from '@dnd-kit/core/dist/types'
|
||||
import { TextEditor } from './TextEditor/TextEditor'
|
||||
import { StepContent } from './StepContent'
|
||||
import { StepNodeLabel } from './StepNodeLabel'
|
||||
import { useTypebot } from 'contexts/TypebotContext/TypebotContext'
|
||||
import { ContextMenu } from 'components/shared/ContextMenu'
|
||||
import { StepNodeContextMenu } from './RightClickMenu'
|
||||
import { SettingsPopoverContent } from './SettingsPopoverContent'
|
||||
import { isStepText } from 'services/typebots'
|
||||
import { DraggableStep } from 'contexts/DndContext'
|
||||
|
||||
export const StepNode = ({
|
||||
step,
|
||||
@ -34,7 +34,7 @@ export const StepNode = ({
|
||||
onMouseMoveTopOfElement?: () => void
|
||||
onMouseDown?: (
|
||||
stepNodePosition: { absolute: Coordinates; relative: Coordinates },
|
||||
step: Step
|
||||
step: DraggableStep
|
||||
) => void
|
||||
}) => {
|
||||
const { setConnectingIds, connectingIds } = useGraph()
|
||||
@ -43,7 +43,7 @@ export const StepNode = ({
|
||||
const [mouseDownEvent, setMouseDownEvent] =
|
||||
useState<{ absolute: Coordinates; relative: Coordinates }>()
|
||||
const [isEditing, setIsEditing] = useState<boolean>(
|
||||
isStepText(step) && step.content.plainText === ''
|
||||
isTextBubbleStep(step) && step.content.plainText === ''
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
@ -102,8 +102,8 @@ export const StepNode = ({
|
||||
mouseDownEvent &&
|
||||
onMouseDown &&
|
||||
(event.movementX > 0 || event.movementY > 0)
|
||||
if (isMovingAndIsMouseDown) {
|
||||
onMouseDown(mouseDownEvent, step as Step)
|
||||
if (isMovingAndIsMouseDown && step.type !== 'start') {
|
||||
onMouseDown(mouseDownEvent, step)
|
||||
deleteStep(step.id)
|
||||
setMouseDownEvent(undefined)
|
||||
}
|
||||
@ -142,7 +142,7 @@ export const StepNode = ({
|
||||
connectingIds?.target?.blockId,
|
||||
])
|
||||
|
||||
return isEditing && isStepText(step) ? (
|
||||
return isEditing && isTextBubbleStep(step) ? (
|
||||
<TextEditor
|
||||
stepId={step.id}
|
||||
initialValue={step.content.richText}
|
||||
@ -186,7 +186,7 @@ export const StepNode = ({
|
||||
bgColor="white"
|
||||
>
|
||||
<StepIcon type={step.type} />
|
||||
<StepContent {...step} />
|
||||
<StepNodeLabel {...step} />
|
||||
{isConnectable && (
|
||||
<SourceEndpoint
|
||||
onConnectionDragStart={handleConnectionDragStart}
|
||||
|
@ -0,0 +1,42 @@
|
||||
import { Flex, Text } from '@chakra-ui/react'
|
||||
import { Step, StartStep, BubbleStepType, InputStepType } from 'models'
|
||||
|
||||
export const StepNodeLabel = (props: Step | StartStep) => {
|
||||
switch (props.type) {
|
||||
case BubbleStepType.TEXT: {
|
||||
return (
|
||||
<Flex
|
||||
flexDir={'column'}
|
||||
opacity={props.content.html === '' ? '0.5' : '1'}
|
||||
className="slate-html-container"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html:
|
||||
props.content.html === ''
|
||||
? `<p>Click to edit...</p>`
|
||||
: props.content.html,
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
case InputStepType.TEXT: {
|
||||
return (
|
||||
<Text color={'gray.500'}>
|
||||
{props.options?.labels?.placeholder ?? 'Type your answer...'}
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
case InputStepType.NUMBER: {
|
||||
return (
|
||||
<Text color={'gray.500'}>
|
||||
{props.options?.labels?.placeholder ?? 'Type your answer...'}
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
case 'start': {
|
||||
return <Text>{props.label}</Text>
|
||||
}
|
||||
default: {
|
||||
return <Text>No input</Text>
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
import { StackProps, HStack } from '@chakra-ui/react'
|
||||
import { StartStep, Step } from 'models'
|
||||
import { StepIcon } from 'components/board/StepTypesList/StepIcon'
|
||||
import { StepContent } from './StepContent'
|
||||
import { StepNodeLabel } from './StepNodeLabel'
|
||||
|
||||
export const StepNodeOverlay = ({
|
||||
step,
|
||||
@ -19,7 +19,7 @@ export const StepNodeOverlay = ({
|
||||
{...props}
|
||||
>
|
||||
<StepIcon type={step.type} />
|
||||
<StepContent {...step} />
|
||||
<StepNodeLabel {...step} />
|
||||
</HStack>
|
||||
)
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { useEventListener, Stack, Flex, Portal } from '@chakra-ui/react'
|
||||
import { Step, Table } from 'models'
|
||||
import { useDnd } from 'contexts/DndContext'
|
||||
import { DraggableStep, useDnd } from 'contexts/DndContext'
|
||||
import { Coordinates } from 'contexts/GraphContext'
|
||||
import { useState } from 'react'
|
||||
import { StepNode, StepNodeOverlay } from './StepNode'
|
||||
@ -54,7 +54,7 @@ export const StepsList = ({
|
||||
|
||||
const handleStepMouseDown = (
|
||||
{ absolute, relative }: { absolute: Coordinates; relative: Coordinates },
|
||||
step: Step
|
||||
step: DraggableStep
|
||||
) => {
|
||||
setPosition(absolute)
|
||||
setRelativeCoordinates(relative)
|
||||
|
@ -2,11 +2,10 @@ import { Flex, FlexProps, useEventListener } from '@chakra-ui/react'
|
||||
import React, { useRef, useMemo } from 'react'
|
||||
import { blockWidth, useGraph } from 'contexts/GraphContext'
|
||||
import { BlockNode } from './BlockNode/BlockNode'
|
||||
import { useDnd } from 'contexts/DndContext'
|
||||
import { DraggableStepType, useDnd } from 'contexts/DndContext'
|
||||
import { Edges } from './Edges'
|
||||
import { useTypebot } from 'contexts/TypebotContext/TypebotContext'
|
||||
import { headerHeight } from 'components/shared/TypebotHeader/TypebotHeader'
|
||||
import { StepType } from 'models'
|
||||
|
||||
const Graph = ({ ...props }: FlexProps) => {
|
||||
const { draggedStepType, setDraggedStepType, draggedStep, setDraggedStep } =
|
||||
@ -44,7 +43,7 @@ const Graph = ({ ...props }: FlexProps) => {
|
||||
createBlock({
|
||||
x: e.clientX - graphPosition.x - blockWidth / 3,
|
||||
y: e.clientY - graphPosition.y - 20 - headerHeight,
|
||||
step: draggedStep ?? (draggedStepType as StepType),
|
||||
step: draggedStep ?? (draggedStepType as DraggableStepType),
|
||||
})
|
||||
setDraggedStep(undefined)
|
||||
setDraggedStepType(undefined)
|
||||
|
@ -13,14 +13,14 @@ export const SmartNumberInput = ({
|
||||
onValueChange,
|
||||
...props
|
||||
}: {
|
||||
initialValue: number
|
||||
onValueChange: (value: number) => void
|
||||
initialValue?: number
|
||||
onValueChange: (value?: number) => void
|
||||
} & NumberInputProps) => {
|
||||
const [value, setValue] = useState(initialValue.toString())
|
||||
const [value, setValue] = useState(initialValue?.toString() ?? '')
|
||||
|
||||
useEffect(() => {
|
||||
if (value.endsWith('.') || value.endsWith(',')) return
|
||||
if (value === '') onValueChange(0)
|
||||
if (value === '') onValueChange(undefined)
|
||||
const newValue = parseFloat(value)
|
||||
if (isNaN(newValue)) return
|
||||
onValueChange(newValue)
|
||||
|
@ -17,14 +17,14 @@ export const TypingEmulation = ({
|
||||
onUpdate({ ...typingEmulation, enabled: !typingEmulation.enabled })
|
||||
}
|
||||
|
||||
const handleSpeedChange = (speed: number) => {
|
||||
const handleSpeedChange = (speed?: number) => {
|
||||
if (!typingEmulation) return
|
||||
onUpdate({ ...typingEmulation, speed })
|
||||
onUpdate({ ...typingEmulation, speed: speed ?? 0 })
|
||||
}
|
||||
|
||||
const handleMaxDelayChange = (maxDelay: number) => {
|
||||
const handleMaxDelayChange = (maxDelay?: number) => {
|
||||
if (!typingEmulation) return
|
||||
onUpdate({ ...typingEmulation, maxDelay: maxDelay })
|
||||
onUpdate({ ...typingEmulation, maxDelay: maxDelay ?? 0 })
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Step, StepType } from 'models'
|
||||
import { BubbleStep, BubbleStepType, InputStep, InputStepType } from 'models'
|
||||
import {
|
||||
createContext,
|
||||
Dispatch,
|
||||
@ -8,19 +8,24 @@ import {
|
||||
useState,
|
||||
} from 'react'
|
||||
|
||||
export type DraggableStep = BubbleStep | InputStep
|
||||
export type DraggableStepType = BubbleStepType | InputStepType
|
||||
|
||||
const dndContext = createContext<{
|
||||
draggedStepType?: StepType
|
||||
setDraggedStepType: Dispatch<SetStateAction<StepType | undefined>>
|
||||
draggedStep?: Step
|
||||
setDraggedStep: Dispatch<SetStateAction<Step | undefined>>
|
||||
draggedStepType?: DraggableStepType
|
||||
setDraggedStepType: Dispatch<SetStateAction<DraggableStepType | undefined>>
|
||||
draggedStep?: DraggableStep
|
||||
setDraggedStep: Dispatch<SetStateAction<DraggableStep | 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>()
|
||||
const [draggedStep, setDraggedStep] = useState<DraggableStep | undefined>()
|
||||
const [draggedStepType, setDraggedStepType] = useState<
|
||||
DraggableStepType | undefined
|
||||
>()
|
||||
|
||||
return (
|
||||
<dndContext.Provider
|
||||
|
@ -1,20 +1,25 @@
|
||||
import { Coordinates } from 'contexts/GraphContext'
|
||||
import { WritableDraft } from 'immer/dist/internal'
|
||||
import { Block, Step, StepType, Typebot } from 'models'
|
||||
import { Block, BubbleStepType, InputStepType, Step, Typebot } from 'models'
|
||||
import { parseNewBlock } from 'services/typebots'
|
||||
import { Updater } from 'use-immer'
|
||||
import { createStepDraft, deleteStepDraft } from './steps'
|
||||
|
||||
export type BlocksActions = {
|
||||
createBlock: (props: Coordinates & { step: StepType | Step }) => void
|
||||
createBlock: (
|
||||
props: Coordinates & { step: BubbleStepType | InputStepType | Step }
|
||||
) => void
|
||||
updateBlock: (blockId: string, updates: Partial<Omit<Block, 'id'>>) => void
|
||||
deleteBlock: (blockId: string) => void
|
||||
}
|
||||
|
||||
export const blocksActions = (setTypebot: Updater<Typebot>): BlocksActions => ({
|
||||
createBlock: ({ x, y, step }: Coordinates & { step: StepType | Step }) => {
|
||||
createBlock: ({
|
||||
x,
|
||||
y,
|
||||
step,
|
||||
}: Coordinates & { step: BubbleStepType | InputStepType | Step }) => {
|
||||
setTypebot((typebot) => {
|
||||
removeEmptyBlocks(typebot)
|
||||
const newBlock = parseNewBlock({
|
||||
totalBlocks: typebot.blocks.allIds.length,
|
||||
initialCoordinates: { x, y },
|
||||
@ -22,6 +27,7 @@ export const blocksActions = (setTypebot: Updater<Typebot>): BlocksActions => ({
|
||||
typebot.blocks.byId[newBlock.id] = newBlock
|
||||
typebot.blocks.allIds.push(newBlock.id)
|
||||
createStepDraft(typebot, step, newBlock.id)
|
||||
removeEmptyBlocks(typebot)
|
||||
})
|
||||
},
|
||||
updateBlock: (blockId: string, updates: Partial<Omit<Block, 'id'>>) =>
|
||||
|
@ -1,11 +1,15 @@
|
||||
import { Step, StepType, Typebot } from 'models'
|
||||
import { BubbleStepType, InputStepType, Step, Typebot } from 'models'
|
||||
import { parseNewStep } from 'services/typebots'
|
||||
import { Updater } from 'use-immer'
|
||||
import { removeEmptyBlocks } from './blocks'
|
||||
import { WritableDraft } from 'immer/dist/types/types-external'
|
||||
|
||||
export type StepsActions = {
|
||||
createStep: (blockId: string, step: StepType | Step, index?: number) => void
|
||||
createStep: (
|
||||
blockId: string,
|
||||
step: BubbleStepType | InputStepType | Step,
|
||||
index?: number
|
||||
) => void
|
||||
updateStep: (
|
||||
stepId: string,
|
||||
updates: Partial<Omit<Step, 'id' | 'type'>>
|
||||
@ -14,10 +18,14 @@ export type StepsActions = {
|
||||
}
|
||||
|
||||
export const stepsAction = (setTypebot: Updater<Typebot>): StepsActions => ({
|
||||
createStep: (blockId: string, step: StepType | Step, index?: number) => {
|
||||
createStep: (
|
||||
blockId: string,
|
||||
step: BubbleStepType | InputStepType | Step,
|
||||
index?: number
|
||||
) => {
|
||||
setTypebot((typebot) => {
|
||||
removeEmptyBlocks(typebot)
|
||||
createStepDraft(typebot, step, blockId, index)
|
||||
removeEmptyBlocks(typebot)
|
||||
})
|
||||
},
|
||||
updateStep: (stepId: string, updates: Partial<Omit<Step, 'id' | 'type'>>) =>
|
||||
@ -28,6 +36,7 @@ export const stepsAction = (setTypebot: Updater<Typebot>): StepsActions => ({
|
||||
setTypebot((typebot) => {
|
||||
removeStepIdFromBlock(typebot, stepId)
|
||||
deleteStepDraft(typebot, stepId)
|
||||
removeEmptyBlocks(typebot)
|
||||
})
|
||||
},
|
||||
})
|
||||
@ -56,7 +65,7 @@ export const deleteStepDraft = (
|
||||
|
||||
export const createStepDraft = (
|
||||
typebot: WritableDraft<Typebot>,
|
||||
step: StepType | Step,
|
||||
step: BubbleStepType | InputStepType | Step,
|
||||
blockId: string,
|
||||
index?: number
|
||||
) => {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { PublicTypebot, StepType, Typebot } from 'models'
|
||||
import { InputStepType, PublicTypebot, Typebot } from 'models'
|
||||
import { Plan, PrismaClient } from 'db'
|
||||
import { parseTestTypebot } from './utils'
|
||||
|
||||
@ -58,7 +58,7 @@ const createTypebots = async () => {
|
||||
byId: {
|
||||
step1: {
|
||||
id: 'step1',
|
||||
type: StepType.TEXT_INPUT,
|
||||
type: InputStepType.TEXT,
|
||||
blockId: 'block1',
|
||||
},
|
||||
},
|
||||
|
@ -1,6 +1,5 @@
|
||||
import {
|
||||
Block,
|
||||
StepType,
|
||||
Theme,
|
||||
BackgroundType,
|
||||
Settings,
|
||||
@ -59,7 +58,7 @@ export const parseTestTypebot = ({
|
||||
byId: {
|
||||
step0: {
|
||||
id: 'step0',
|
||||
type: StepType.START,
|
||||
type: 'start',
|
||||
blockId: 'block0',
|
||||
label: 'Start',
|
||||
target: { blockId: 'block1' },
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { parseTestTypebot } from 'cypress/plugins/utils'
|
||||
import { StepType } from 'models'
|
||||
import { BubbleStepType } from 'models'
|
||||
|
||||
describe('Text bubbles', () => {
|
||||
beforeEach(() => {
|
||||
@ -15,7 +15,7 @@ describe('Text bubbles', () => {
|
||||
step1: {
|
||||
id: 'step1',
|
||||
blockId: 'block1',
|
||||
type: StepType.TEXT,
|
||||
type: BubbleStepType.TEXT,
|
||||
content: { html: '', plainText: '', richText: [] },
|
||||
},
|
||||
},
|
||||
|
@ -1,42 +1,14 @@
|
||||
import { parseTestTypebot } from 'cypress/plugins/utils'
|
||||
import { StepType } from 'models'
|
||||
import { InputStep, InputStepType } from 'models'
|
||||
|
||||
describe('Text input', () => {
|
||||
beforeEach(() => {
|
||||
cy.task('seed')
|
||||
cy.task(
|
||||
'createTypebot',
|
||||
parseTestTypebot({
|
||||
id: 'typebot3',
|
||||
name: 'Typebot #3',
|
||||
ownerId: 'test2',
|
||||
steps: {
|
||||
byId: {
|
||||
step1: {
|
||||
id: 'step1',
|
||||
blockId: 'block1',
|
||||
type: StepType.TEXT_INPUT,
|
||||
},
|
||||
},
|
||||
allIds: ['step1'],
|
||||
},
|
||||
blocks: {
|
||||
byId: {
|
||||
block1: {
|
||||
id: 'block1',
|
||||
graphCoordinates: { x: 400, y: 200 },
|
||||
title: 'Block #1',
|
||||
stepIds: ['step1'],
|
||||
},
|
||||
},
|
||||
allIds: ['block1'],
|
||||
},
|
||||
})
|
||||
)
|
||||
createTypebotWithStep({ type: InputStepType.TEXT })
|
||||
cy.signOut()
|
||||
})
|
||||
|
||||
it('text input options should work', () => {
|
||||
it('options should work', () => {
|
||||
cy.signIn('test2@gmail.com')
|
||||
cy.visit('/typebots/typebot3/edit')
|
||||
cy.findByRole('button', { name: 'Preview' }).click()
|
||||
@ -48,6 +20,7 @@ describe('Text input', () => {
|
||||
.type('Your name...')
|
||||
cy.findByRole('textbox', { name: 'Button label:' }).clear().type('Go')
|
||||
cy.findByRole('button', { name: 'Restart' }).click()
|
||||
cy.findByTestId('step-step1').should('contain.text', 'Your name...')
|
||||
getIframeBody().findByPlaceholderText('Your name...').should('exist')
|
||||
getIframeBody().findByRole('button', { name: 'Go' })
|
||||
cy.findByTestId('step-step1').click({ force: true })
|
||||
@ -57,6 +30,68 @@ describe('Text input', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('Number input', () => {
|
||||
beforeEach(() => {
|
||||
cy.task('seed')
|
||||
createTypebotWithStep({ type: InputStepType.NUMBER })
|
||||
cy.signOut()
|
||||
})
|
||||
|
||||
it.only('options should work', () => {
|
||||
cy.signIn('test2@gmail.com')
|
||||
cy.visit('/typebots/typebot3/edit')
|
||||
cy.findByRole('button', { name: 'Preview' }).click()
|
||||
getIframeBody().findByPlaceholderText('Type your answer...').should('exist')
|
||||
getIframeBody().findByRole('button', { name: 'Send' })
|
||||
cy.findByTestId('step-step1').click({ force: true })
|
||||
cy.findByRole('textbox', { name: 'Placeholder:' })
|
||||
.clear()
|
||||
.type('Your name...')
|
||||
cy.findByRole('textbox', { name: 'Button label:' }).clear().type('Go')
|
||||
cy.findByRole('spinbutton', { name: 'Min:' }).type('0')
|
||||
cy.findByRole('spinbutton', { name: 'Max:' }).type('100')
|
||||
cy.findByRole('spinbutton', { name: 'Step:' }).type('10')
|
||||
cy.findByRole('button', { name: 'Restart' }).click()
|
||||
cy.findByTestId('step-step1').should('contain.text', 'Your name...')
|
||||
getIframeBody()
|
||||
.findByPlaceholderText('Your name...')
|
||||
.should('exist')
|
||||
.type('-1{enter}')
|
||||
.clear()
|
||||
.type('150{enter}')
|
||||
getIframeBody().findByRole('button', { name: 'Go' })
|
||||
cy.findByTestId('step-step1').click({ force: true })
|
||||
})
|
||||
})
|
||||
|
||||
const createTypebotWithStep = (step: Omit<InputStep, 'id' | 'blockId'>) => {
|
||||
cy.task(
|
||||
'createTypebot',
|
||||
parseTestTypebot({
|
||||
id: 'typebot3',
|
||||
name: 'Typebot #3',
|
||||
ownerId: 'test2',
|
||||
steps: {
|
||||
byId: {
|
||||
step1: { ...step, id: 'step1', blockId: 'block1' },
|
||||
},
|
||||
allIds: ['step1'],
|
||||
},
|
||||
blocks: {
|
||||
byId: {
|
||||
block1: {
|
||||
id: 'block1',
|
||||
graphCoordinates: { x: 400, y: 200 },
|
||||
title: 'Block #1',
|
||||
stepIds: ['step1'],
|
||||
},
|
||||
},
|
||||
allIds: ['block1'],
|
||||
},
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
const getIframeBody = () => {
|
||||
return cy
|
||||
.get('#typebot-iframe')
|
||||
|
@ -1,9 +1,10 @@
|
||||
import { InputStep, PublicTypebot, Step, StepType, Typebot } from 'models'
|
||||
import { PublicTypebot, Typebot } from 'models'
|
||||
import { sendRequest } from './utils'
|
||||
import shortId from 'short-uuid'
|
||||
import { HStack, Text } from '@chakra-ui/react'
|
||||
import { CalendarIcon } from 'assets/icons'
|
||||
import { StepIcon } from 'components/board/StepTypesList/StepIcon'
|
||||
import { isInputStep } from 'utils'
|
||||
|
||||
export const parseTypebotToPublicTypebot = (
|
||||
typebot: Typebot
|
||||
@ -59,7 +60,7 @@ export const parseSubmissionsColumns = (
|
||||
.map((blockId) => {
|
||||
const block = typebot.blocks.byId[blockId]
|
||||
const inputStepId = block.stepIds.find((stepId) =>
|
||||
stepIsInput(typebot.steps.byId[stepId])
|
||||
isInputStep(typebot.steps.byId[stepId])
|
||||
)
|
||||
const inputStep = typebot.steps.byId[inputStepId as string]
|
||||
return {
|
||||
@ -80,8 +81,5 @@ const blockContainsInput = (
|
||||
blockId: string
|
||||
) =>
|
||||
typebot.blocks.byId[blockId].stepIds.some((stepId) =>
|
||||
stepIsInput(typebot.steps.byId[stepId])
|
||||
isInputStep(typebot.steps.byId[stepId])
|
||||
)
|
||||
|
||||
export const stepIsInput = (step: Step): step is InputStep =>
|
||||
step.type === StepType.TEXT_INPUT
|
||||
|
@ -1,14 +1,15 @@
|
||||
import {
|
||||
Step,
|
||||
StepType,
|
||||
Block,
|
||||
TextStep,
|
||||
TextInputStep,
|
||||
PublicTypebot,
|
||||
BackgroundType,
|
||||
Settings,
|
||||
StartStep,
|
||||
Theme,
|
||||
BubbleStep,
|
||||
InputStep,
|
||||
BubbleStepType,
|
||||
InputStepType,
|
||||
} from 'models'
|
||||
import shortId from 'short-uuid'
|
||||
import { Typebot } from 'models'
|
||||
@ -104,10 +105,13 @@ export const parseNewBlock = ({
|
||||
}
|
||||
}
|
||||
|
||||
export const parseNewStep = (type: StepType, blockId: string): Step => {
|
||||
export const parseNewStep = (
|
||||
type: BubbleStepType | InputStepType,
|
||||
blockId: string
|
||||
): BubbleStep | InputStep => {
|
||||
const id = `s${shortId.generate()}`
|
||||
switch (type) {
|
||||
case StepType.TEXT: {
|
||||
case BubbleStepType.TEXT: {
|
||||
const textStep: Pick<TextStep, 'type' | 'content'> = {
|
||||
type,
|
||||
content: { html: '', richText: [], plainText: '' },
|
||||
@ -118,22 +122,12 @@ export const parseNewStep = (type: StepType, blockId: string): Step => {
|
||||
...textStep,
|
||||
}
|
||||
}
|
||||
case StepType.TEXT_INPUT: {
|
||||
const textStep: Pick<TextInputStep, 'type'> = {
|
||||
type,
|
||||
}
|
||||
return {
|
||||
id,
|
||||
blockId,
|
||||
...textStep,
|
||||
}
|
||||
}
|
||||
default: {
|
||||
const textStep: Pick<TextStep, 'type' | 'content'> = {
|
||||
type: StepType.TEXT,
|
||||
content: { html: '', richText: [], plainText: '' },
|
||||
return {
|
||||
id,
|
||||
blockId,
|
||||
type,
|
||||
}
|
||||
return { blockId, id, ...textStep }
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -180,7 +174,7 @@ export const parseNewTypebot = ({
|
||||
blockId: startBlockId,
|
||||
id: startStepId,
|
||||
label: 'Start',
|
||||
type: StepType.START,
|
||||
type: 'start',
|
||||
}
|
||||
const startBlock: Block = {
|
||||
id: startBlockId,
|
||||
@ -211,6 +205,3 @@ export const parseNewTypebot = ({
|
||||
settings,
|
||||
}
|
||||
}
|
||||
|
||||
export const isStepText = (step: Step): step is TextStep =>
|
||||
step.type === StepType.TEXT
|
||||
|
@ -106,3 +106,12 @@ export const uploadFile = async (file: File, key: string) => {
|
||||
url: upload.ok ? `${url}/${key}` : null,
|
||||
}
|
||||
}
|
||||
|
||||
export const removeUndefinedFields = <T>(obj: T): T =>
|
||||
Object.keys(obj).reduce(
|
||||
(acc, key) =>
|
||||
obj[key as keyof T] === undefined
|
||||
? { ...acc }
|
||||
: { ...acc, [key]: obj[key as keyof T] },
|
||||
{} as T
|
||||
)
|
||||
|
@ -1,11 +1,12 @@
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { useAnswers } from '../../../contexts/AnswersContext'
|
||||
import { useHostAvatars } from '../../../contexts/HostAvatarsContext'
|
||||
import { InputStep, Step } from 'models'
|
||||
import { isTextInputStep, isTextStep } from '../../../services/utils'
|
||||
import { InputStep, InputStepType, Step } from 'models'
|
||||
import { GuestBubble } from './bubbles/GuestBubble'
|
||||
import { HostMessageBubble } from './bubbles/HostMessageBubble'
|
||||
import { TextInput } from './inputs/TextInput'
|
||||
import { isInputStep, isTextBubbleStep, isTextInputStep } from 'utils'
|
||||
import { NumberInput } from './inputs/NumberInput'
|
||||
|
||||
export const ChatStep = ({
|
||||
step,
|
||||
@ -21,9 +22,9 @@ export const ChatStep = ({
|
||||
onTransitionEnd()
|
||||
}
|
||||
|
||||
if (isTextStep(step))
|
||||
if (isTextBubbleStep(step))
|
||||
return <HostMessageBubble step={step} onTransitionEnd={onTransitionEnd} />
|
||||
if (isTextInputStep(step))
|
||||
if (isInputStep(step))
|
||||
return <InputChatStep step={step} onSubmit={handleInputSubmit} />
|
||||
return <span>No step</span>
|
||||
}
|
||||
@ -50,5 +51,10 @@ const InputChatStep = ({
|
||||
if (answer) {
|
||||
return <GuestBubble message={answer} />
|
||||
}
|
||||
return <TextInput step={step} onSubmit={handleSubmit} />
|
||||
switch (step.type) {
|
||||
case InputStepType.TEXT:
|
||||
return <TextInput step={step} onSubmit={handleSubmit} />
|
||||
case InputStepType.NUMBER:
|
||||
return <NumberInput step={step} onSubmit={handleSubmit} />
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React, { useEffect, useRef, useState } from 'react'
|
||||
import { useHostAvatars } from '../../../../contexts/HostAvatarsContext'
|
||||
import { useTypebot } from '../../../../contexts/TypebotContext'
|
||||
import { StepType, TextStep } from 'models'
|
||||
import { BubbleStepType, StepType, TextStep } from 'models'
|
||||
import { computeTypingTimeout } from '../../../../services/chat'
|
||||
import { TypingContent } from './TypingContent'
|
||||
|
||||
@ -62,7 +62,7 @@ export const HostMessageBubble = ({
|
||||
>
|
||||
{isTyping ? <TypingContent /> : <></>}
|
||||
</div>
|
||||
{step.type === StepType.TEXT && (
|
||||
{step.type === BubbleStepType.TEXT && (
|
||||
<p
|
||||
style={{
|
||||
textOverflow: 'ellipsis',
|
||||
|
@ -0,0 +1,57 @@
|
||||
import { NumberInputStep, TextInputStep } from 'models'
|
||||
import React, { FormEvent, useRef, useState } from 'react'
|
||||
import { SendIcon } from '../../../../assets/icons'
|
||||
|
||||
type NumberInputProps = {
|
||||
step: NumberInputStep
|
||||
onSubmit: (value: string) => void
|
||||
}
|
||||
|
||||
export const NumberInput = ({ step, onSubmit }: NumberInputProps) => {
|
||||
const inputRef = useRef(null)
|
||||
const [inputValue, setInputValue] = useState('')
|
||||
|
||||
const handleSubmit = (e: FormEvent) => {
|
||||
e.preventDefault()
|
||||
if (inputValue === '') return
|
||||
onSubmit(inputValue)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col w-full lg:w-4/6">
|
||||
<div className="flex items-center">
|
||||
<form
|
||||
className="flex items-end justify-between rounded-lg pr-2 typebot-input"
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
<input
|
||||
ref={inputRef}
|
||||
className="focus:outline-none bg-transparent px-4 py-4 flex-1 w-full comp-input"
|
||||
type="number"
|
||||
placeholder={
|
||||
step.options?.labels?.placeholder ?? 'Type your answer...'
|
||||
}
|
||||
onChange={(e) => setInputValue(e.target.value)}
|
||||
style={{ appearance: 'auto' }}
|
||||
min={step.options?.min}
|
||||
max={step.options?.max}
|
||||
step={step.options?.step}
|
||||
required
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
className={
|
||||
'my-2 ml-2 py-2 px-4 font-semibold rounded-md text-white focus:outline-none flex items-center disabled:opacity-50 disabled:cursor-not-allowed disabled:brightness-100 transition-all filter hover:brightness-90 active:brightness-75 typebot-button active'
|
||||
}
|
||||
disabled={inputValue === ''}
|
||||
>
|
||||
<span className="hidden xs:flex">
|
||||
{step.options?.labels?.button ?? 'Send'}
|
||||
</span>
|
||||
<SendIcon className="send-icon flex xs:hidden" />
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
@ -1 +1,3 @@
|
||||
export * from './components/TypebotViewer'
|
||||
|
||||
export * from 'util'
|
||||
|
@ -1,7 +0,0 @@
|
||||
import { Step, TextStep, StepType, TextInputStep } from 'models'
|
||||
|
||||
export const isTextStep = (step: Step): step is TextStep =>
|
||||
step.type === StepType.TEXT
|
||||
|
||||
export const isTextInputStep = (step: Step): step is TextInputStep =>
|
||||
step.type === StepType.TEXT_INPUT
|
@ -2,34 +2,53 @@ export type Step = StartStep | BubbleStep | InputStep
|
||||
|
||||
export type BubbleStep = TextStep
|
||||
|
||||
export type InputStep = TextInputStep
|
||||
export type InputStep = TextInputStep | NumberInputStep
|
||||
|
||||
export enum StepType {
|
||||
START = 'start',
|
||||
export type StepType = 'start' | BubbleStepType | InputStepType
|
||||
|
||||
export enum BubbleStepType {
|
||||
TEXT = 'text',
|
||||
TEXT_INPUT = 'text input',
|
||||
}
|
||||
|
||||
export enum InputStepType {
|
||||
TEXT = 'text input',
|
||||
NUMBER = 'number input',
|
||||
}
|
||||
|
||||
export type StepBase = { id: string; blockId: string; target?: Target }
|
||||
|
||||
export type StartStep = StepBase & {
|
||||
type: StepType.START
|
||||
type: 'start'
|
||||
label: string
|
||||
}
|
||||
|
||||
export type TextStep = StepBase & {
|
||||
type: StepType.TEXT
|
||||
type: BubbleStepType.TEXT
|
||||
content: { html: string; richText: unknown[]; plainText: string }
|
||||
}
|
||||
|
||||
export type TextInputStep = StepBase & {
|
||||
type: StepType.TEXT_INPUT
|
||||
type: InputStepType.TEXT
|
||||
options?: TextInputOptions
|
||||
}
|
||||
|
||||
export type TextInputOptions = {
|
||||
export type NumberInputStep = StepBase & {
|
||||
type: InputStepType.NUMBER
|
||||
options?: NumberInputOptions
|
||||
}
|
||||
|
||||
type InputOptionsBase = {
|
||||
labels?: { placeholder?: string; button?: string }
|
||||
}
|
||||
|
||||
export type TextInputOptions = InputOptionsBase & {
|
||||
isLong?: boolean
|
||||
}
|
||||
|
||||
export type NumberInputOptions = InputOptionsBase & {
|
||||
min?: number
|
||||
max?: number
|
||||
step?: number
|
||||
}
|
||||
|
||||
export type Target = { blockId: string; stepId?: string }
|
||||
|
@ -1,4 +1,12 @@
|
||||
import { Table } from 'models'
|
||||
import {
|
||||
BubbleStepType,
|
||||
InputStep,
|
||||
InputStepType,
|
||||
Step,
|
||||
Table,
|
||||
TextInputStep,
|
||||
TextStep,
|
||||
} from 'models'
|
||||
|
||||
export const sendRequest = async <ResponseData>({
|
||||
url,
|
||||
@ -32,3 +40,12 @@ export const filterTable = <T>(ids: string[], table: Table<T>): Table<T> => ({
|
||||
byId: ids.reduce((acc, id) => ({ ...acc, [id]: table.byId[id] }), {}),
|
||||
allIds: ids,
|
||||
})
|
||||
|
||||
export const isInputStep = (step: Step): step is InputStep =>
|
||||
(Object.values(InputStepType) as string[]).includes(step.type)
|
||||
|
||||
export const isTextBubbleStep = (step: Step): step is TextStep =>
|
||||
step.type === BubbleStepType.TEXT
|
||||
|
||||
export const isTextInputStep = (step: Step): step is TextInputStep =>
|
||||
step.type === InputStepType.TEXT
|
||||
|
Reference in New Issue
Block a user