chore(editor): ♻️ Revert tables to arrays
Yet another refacto. I improved many many mechanisms on this one including dnd. It is now end 2 end tested 🎉
This commit is contained in:
@ -1,13 +1,11 @@
|
||||
import { Edge, Table, Target } from 'models'
|
||||
import { Edge, IdMap } from 'models'
|
||||
import { AnchorsPositionProps } from 'components/shared/Graph/Edges/Edge'
|
||||
import {
|
||||
stubLength,
|
||||
blockWidth,
|
||||
blockAnchorsOffset,
|
||||
ConnectingIds,
|
||||
Endpoint,
|
||||
Coordinates,
|
||||
BlocksCoordinates,
|
||||
} from 'contexts/GraphContext'
|
||||
import { roundCorners } from 'svg-round-corners'
|
||||
import { headerHeight } from 'components/shared/TypebotHeader'
|
||||
@ -230,20 +228,11 @@ export const computeEdgePath = ({
|
||||
}
|
||||
|
||||
export const computeConnectingEdgePath = ({
|
||||
connectingIds,
|
||||
sourceBlockCoordinates,
|
||||
targetBlockCoordinates,
|
||||
sourceTop,
|
||||
targetTop,
|
||||
blocksCoordinates,
|
||||
}: {
|
||||
connectingIds: Omit<ConnectingIds, 'target'> & { target: Target }
|
||||
sourceTop: number
|
||||
targetTop?: number
|
||||
blocksCoordinates: BlocksCoordinates
|
||||
}) => {
|
||||
const sourceBlockCoordinates =
|
||||
blocksCoordinates.byId[connectingIds.source.blockId]
|
||||
const targetBlockCoordinates =
|
||||
blocksCoordinates.byId[connectingIds.target.blockId]
|
||||
}: GetAnchorsPositionParams) => {
|
||||
const anchorsPosition = getAnchorsPosition({
|
||||
sourceBlockCoordinates,
|
||||
targetBlockCoordinates,
|
||||
@ -254,23 +243,25 @@ export const computeConnectingEdgePath = ({
|
||||
}
|
||||
|
||||
export const computeEdgePathToMouse = ({
|
||||
blockPosition,
|
||||
sourceBlockCoordinates,
|
||||
mousePosition,
|
||||
sourceTop,
|
||||
}: {
|
||||
blockPosition: Coordinates
|
||||
sourceBlockCoordinates: Coordinates
|
||||
mousePosition: Coordinates
|
||||
sourceTop: number
|
||||
}): string => {
|
||||
const sourcePosition = {
|
||||
x:
|
||||
mousePosition.x - blockPosition.x > blockWidth / 2
|
||||
? blockPosition.x + blockWidth - 40
|
||||
: blockPosition.x + 40,
|
||||
mousePosition.x - sourceBlockCoordinates.x > blockWidth / 2
|
||||
? sourceBlockCoordinates.x + blockWidth - 40
|
||||
: sourceBlockCoordinates.x + 40,
|
||||
y: sourceTop,
|
||||
}
|
||||
const sourceType =
|
||||
mousePosition.x - blockPosition.x > blockWidth / 2 ? 'right' : 'left'
|
||||
mousePosition.x - sourceBlockCoordinates.x > blockWidth / 2
|
||||
? 'right'
|
||||
: 'left'
|
||||
const segments = computeThreeSegments(
|
||||
sourcePosition,
|
||||
mousePosition,
|
||||
@ -284,11 +275,11 @@ export const computeEdgePathToMouse = ({
|
||||
|
||||
export const getEndpointTopOffset = (
|
||||
graphPosition: Coordinates,
|
||||
endpoints: Table<Endpoint>,
|
||||
endpoints: IdMap<Endpoint>,
|
||||
endpointId?: string
|
||||
): number | undefined => {
|
||||
if (!endpointId) return
|
||||
const endpointRef = endpoints.byId[endpointId]?.ref
|
||||
const endpointRef = endpoints[endpointId]?.ref
|
||||
if (!endpointRef) return
|
||||
return (
|
||||
8 +
|
||||
@ -299,4 +290,4 @@ export const getEndpointTopOffset = (
|
||||
}
|
||||
|
||||
export const getSourceEndpointId = (edge?: Edge) =>
|
||||
edge?.from.buttonId ?? edge?.from.stepId + `${edge?.from.conditionType ?? ''}`
|
||||
edge?.from.itemId ?? edge?.from.stepId
|
||||
|
@ -2,7 +2,7 @@ import { sendRequest } from 'utils'
|
||||
import { stringify } from 'qs'
|
||||
import useSWR from 'swr'
|
||||
import { fetcher } from './utils'
|
||||
import { Table, Variable, VariableForTest, WebhookResponse } from 'models'
|
||||
import { StepIndices, Variable, VariableForTest, WebhookResponse } from 'models'
|
||||
|
||||
export const getGoogleSheetsConsentScreenUrl = (
|
||||
redirectUrl: string,
|
||||
@ -69,10 +69,11 @@ export const useSheets = ({
|
||||
export const executeWebhook = (
|
||||
typebotId: string,
|
||||
webhookId: string,
|
||||
variables: Table<Variable>
|
||||
variables: Variable[],
|
||||
{ blockIndex, stepIndex }: StepIndices
|
||||
) =>
|
||||
sendRequest<WebhookResponse>({
|
||||
url: `/api/typebots/${typebotId}/webhooks/${webhookId}/execute`,
|
||||
url: `/api/typebots/${typebotId}/blocks/${blockIndex}/steps/${stepIndex}/executeWebhook`,
|
||||
method: 'POST',
|
||||
body: {
|
||||
variables,
|
||||
@ -80,28 +81,21 @@ export const executeWebhook = (
|
||||
})
|
||||
|
||||
export const convertVariableForTestToVariables = (
|
||||
variablesForTest: Table<VariableForTest> | undefined,
|
||||
variables: Table<Variable>
|
||||
): Table<Variable> => {
|
||||
if (!variablesForTest) return { byId: {}, allIds: [] }
|
||||
return {
|
||||
byId: {
|
||||
...variables.byId,
|
||||
...variablesForTest.allIds.reduce((obj, id) => {
|
||||
const variableForTest = variablesForTest.byId[id]
|
||||
if (!variableForTest.variableId) return {}
|
||||
const variable = variables.byId[variableForTest.variableId ?? '']
|
||||
return {
|
||||
...obj,
|
||||
[variableForTest.variableId]: {
|
||||
...variable,
|
||||
value: variableForTest.value,
|
||||
},
|
||||
}
|
||||
variablesForTest: VariableForTest[],
|
||||
variables: Variable[]
|
||||
): Variable[] => {
|
||||
if (!variablesForTest) return []
|
||||
return [
|
||||
...variables,
|
||||
...variablesForTest
|
||||
.filter((v) => v.variableId)
|
||||
.map((variableForTest) => {
|
||||
const variable = variables.find(
|
||||
(v) => v.id === variableForTest.variableId
|
||||
) as Variable
|
||||
return { ...variable, value: variableForTest.value }
|
||||
}, {}),
|
||||
},
|
||||
allIds: variables.allIds,
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
|
@ -1,26 +1,39 @@
|
||||
import { PublicTypebot, Typebot } from 'models'
|
||||
import {
|
||||
Block,
|
||||
InputStep,
|
||||
PublicBlock,
|
||||
PublicStep,
|
||||
PublicTypebot,
|
||||
Step,
|
||||
Typebot,
|
||||
} from 'models'
|
||||
import shortId from 'short-uuid'
|
||||
import { HStack, Text } from '@chakra-ui/react'
|
||||
import { CalendarIcon } from 'assets/icons'
|
||||
import { StepIcon } from 'components/editor/StepsSideBar/StepIcon'
|
||||
import { isInputStep, sendRequest } from 'utils'
|
||||
import { isDefined } from '@udecode/plate-common'
|
||||
|
||||
export const parseTypebotToPublicTypebot = (
|
||||
typebot: Typebot
|
||||
): PublicTypebot => ({
|
||||
...typebot,
|
||||
id: shortId.generate(),
|
||||
blocks: typebot.blocks,
|
||||
steps: typebot.steps,
|
||||
name: typebot.name,
|
||||
typebotId: typebot.id,
|
||||
theme: typebot.theme,
|
||||
settings: typebot.settings,
|
||||
publicId: typebot.publicId,
|
||||
choiceItems: typebot.choiceItems,
|
||||
variables: typebot.variables,
|
||||
edges: typebot.edges,
|
||||
blocks: parseBlocksToPublicBlocks(typebot.blocks),
|
||||
})
|
||||
|
||||
const parseBlocksToPublicBlocks = (blocks: Block[]): PublicBlock[] =>
|
||||
blocks.map((b) => ({
|
||||
...b,
|
||||
steps: b.steps.map(
|
||||
(s) =>
|
||||
('webhook' in s && isDefined(s.webhook)
|
||||
? { ...s, webhook: s.webhook.id }
|
||||
: s) as PublicStep
|
||||
),
|
||||
}))
|
||||
|
||||
export const createPublishedTypebot = async (
|
||||
typebot: Omit<PublicTypebot, 'id'>
|
||||
) =>
|
||||
@ -41,12 +54,11 @@ export const updatePublishedTypebot = async (
|
||||
})
|
||||
|
||||
export const parseSubmissionsColumns = (
|
||||
typebot?: PublicTypebot
|
||||
typebot: PublicTypebot
|
||||
): {
|
||||
Header: JSX.Element
|
||||
accessor: string
|
||||
}[] => {
|
||||
if (!typebot) return []
|
||||
return [
|
||||
{
|
||||
Header: (
|
||||
@ -57,14 +69,14 @@ export const parseSubmissionsColumns = (
|
||||
),
|
||||
accessor: 'createdAt',
|
||||
},
|
||||
...typebot.blocks.allIds
|
||||
.filter((blockId) => typebot && blockContainsInput(typebot, blockId))
|
||||
.map((blockId) => {
|
||||
const block = typebot.blocks.byId[blockId]
|
||||
const inputStepId = block.stepIds.find((stepId) =>
|
||||
isInputStep(typebot.steps.byId[stepId])
|
||||
)
|
||||
const inputStep = typebot.steps.byId[inputStepId as string]
|
||||
...typebot.blocks
|
||||
.filter(
|
||||
(block) => typebot && block.steps.some((step) => isInputStep(step))
|
||||
)
|
||||
.map((block) => {
|
||||
const inputStep = block.steps.find((step) =>
|
||||
isInputStep(step)
|
||||
) as InputStep
|
||||
return {
|
||||
Header: (
|
||||
<HStack>
|
||||
@ -72,16 +84,8 @@ export const parseSubmissionsColumns = (
|
||||
<Text>{block.title}</Text>
|
||||
</HStack>
|
||||
),
|
||||
accessor: blockId,
|
||||
accessor: block.id,
|
||||
}
|
||||
}),
|
||||
]
|
||||
}
|
||||
|
||||
const blockContainsInput = (
|
||||
typebot: PublicTypebot | Typebot,
|
||||
blockId: string
|
||||
) =>
|
||||
typebot.blocks.byId[blockId].stepIds.some((stepId) =>
|
||||
isInputStep(typebot.steps.byId[stepId])
|
||||
)
|
||||
|
@ -24,18 +24,27 @@ import {
|
||||
defaultUrlInputOptions,
|
||||
defaultChoiceInputOptions,
|
||||
defaultSetVariablesOptions,
|
||||
defaultConditionOptions,
|
||||
defaultRedirectOptions,
|
||||
defaultGoogleSheetsOptions,
|
||||
defaultGoogleAnalyticsOptions,
|
||||
defaultWebhookOptions,
|
||||
StepWithOptionsType,
|
||||
defaultWebhookAttributes,
|
||||
Webhook,
|
||||
Item,
|
||||
ItemType,
|
||||
defaultConditionContent,
|
||||
} from 'models'
|
||||
import shortId, { generate } from 'short-uuid'
|
||||
import { Typebot } from 'models'
|
||||
import useSWR from 'swr'
|
||||
import { fetcher, toKebabCase } from './utils'
|
||||
import { isBubbleStepType, stepTypeHasOption } from 'utils'
|
||||
import {
|
||||
isBubbleStepType,
|
||||
stepTypeHasItems,
|
||||
stepTypeHasOption,
|
||||
stepTypeHasWebhook,
|
||||
} from 'utils'
|
||||
import { deepEqual } from 'fast-equals'
|
||||
import { stringify } from 'qs'
|
||||
import { isChoiceInput, isConditionStep, sendRequest } from 'utils'
|
||||
@ -125,9 +134,35 @@ export const parseNewStep = (
|
||||
options: stepTypeHasOption(type)
|
||||
? parseDefaultStepOptions(type)
|
||||
: undefined,
|
||||
webhook: stepTypeHasWebhook(type) ? parseDefaultWebhook() : undefined,
|
||||
items: stepTypeHasItems(type) ? parseDefaultItems(type, id) : undefined,
|
||||
} as DraggableStep
|
||||
}
|
||||
|
||||
const parseDefaultWebhook = (): Webhook => ({
|
||||
id: generate(),
|
||||
...defaultWebhookAttributes,
|
||||
})
|
||||
|
||||
const parseDefaultItems = (
|
||||
type: LogicStepType.CONDITION | InputStepType.CHOICE,
|
||||
stepId: string
|
||||
): Item[] => {
|
||||
switch (type) {
|
||||
case InputStepType.CHOICE:
|
||||
return [{ id: generate(), stepId, type: ItemType.BUTTON }]
|
||||
case LogicStepType.CONDITION:
|
||||
return [
|
||||
{
|
||||
id: generate(),
|
||||
stepId,
|
||||
type: ItemType.CONDITION,
|
||||
content: defaultConditionContent,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
const parseDefaultContent = (type: BubbleStepType): BubbleStepContent => {
|
||||
switch (type) {
|
||||
case BubbleStepType.TEXT:
|
||||
@ -154,11 +189,9 @@ const parseDefaultStepOptions = (type: StepWithOptionsType): StepOptions => {
|
||||
case InputStepType.URL:
|
||||
return defaultUrlInputOptions
|
||||
case InputStepType.CHOICE:
|
||||
return { ...defaultChoiceInputOptions, itemIds: [generate()] }
|
||||
return defaultChoiceInputOptions
|
||||
case LogicStepType.SET_VARIABLE:
|
||||
return defaultSetVariablesOptions
|
||||
case LogicStepType.CONDITION:
|
||||
return defaultConditionOptions
|
||||
case LogicStepType.REDIRECT:
|
||||
return defaultRedirectOptions
|
||||
case IntegrationStepType.GOOGLE_SHEETS:
|
||||
@ -166,7 +199,7 @@ const parseDefaultStepOptions = (type: StepWithOptionsType): StepOptions => {
|
||||
case IntegrationStepType.GOOGLE_ANALYTICS:
|
||||
return defaultGoogleAnalyticsOptions
|
||||
case IntegrationStepType.WEBHOOK:
|
||||
return { ...defaultWebhookOptions, webhookId: generate() }
|
||||
return defaultWebhookOptions
|
||||
}
|
||||
}
|
||||
|
||||
@ -181,7 +214,6 @@ export const checkIfPublished = (
|
||||
publicTypebot: PublicTypebot
|
||||
) =>
|
||||
deepEqual(typebot.blocks, publicTypebot.blocks) &&
|
||||
deepEqual(typebot.steps, publicTypebot.steps) &&
|
||||
typebot.name === publicTypebot.name &&
|
||||
typebot.publicId === publicTypebot.publicId &&
|
||||
deepEqual(typebot.settings, publicTypebot.settings) &&
|
||||
@ -214,18 +246,15 @@ export const parseNewTypebot = ({
|
||||
id: startBlockId,
|
||||
title: 'Start',
|
||||
graphCoordinates: { x: 0, y: 0 },
|
||||
stepIds: [startStepId],
|
||||
steps: [startStep],
|
||||
}
|
||||
return {
|
||||
folderId,
|
||||
name,
|
||||
ownerId,
|
||||
blocks: { byId: { [startBlockId]: startBlock }, allIds: [startBlockId] },
|
||||
steps: { byId: { [startStepId]: startStep }, allIds: [startStepId] },
|
||||
choiceItems: { byId: {}, allIds: [] },
|
||||
variables: { byId: {}, allIds: [] },
|
||||
edges: { byId: {}, allIds: [] },
|
||||
webhooks: { byId: {}, allIds: [] },
|
||||
blocks: [startBlock],
|
||||
edges: [],
|
||||
variables: [],
|
||||
theme: defaultTheme,
|
||||
settings: defaultSettings,
|
||||
}
|
||||
|
@ -100,11 +100,8 @@ export const removeUndefinedFields = <T>(obj: T): T =>
|
||||
|
||||
export const stepHasOptions = (step: Step) => 'options' in step
|
||||
|
||||
export const parseVariableHighlight = (content: string, typebot?: Typebot) => {
|
||||
if (!typebot) return content
|
||||
const varNames = typebot.variables.allIds.map(
|
||||
(varId) => typebot.variables.byId[varId].name
|
||||
)
|
||||
export const parseVariableHighlight = (content: string, typebot: Typebot) => {
|
||||
const varNames = typebot.variables.map((v) => v.name)
|
||||
return content.replace(/\{\{(.*?)\}\}/g, (fullMatch, foundVar) => {
|
||||
if (varNames.some((val) => foundVar.includes(val))) {
|
||||
return `<span style="background-color:#ff8b1a; color:#ffffff; padding: 0.125rem 0.25rem; border-radius: 0.35rem">${fullMatch.replace(
|
||||
@ -115,3 +112,8 @@ export const parseVariableHighlight = (content: string, typebot?: Typebot) => {
|
||||
return fullMatch
|
||||
})
|
||||
}
|
||||
|
||||
export const setMultipleRefs =
|
||||
(refs: React.MutableRefObject<HTMLDivElement | null>[]) =>
|
||||
(elem: HTMLDivElement) =>
|
||||
refs.forEach((ref) => (ref.current = elem))
|
||||
|
Reference in New Issue
Block a user