2
0

🚸 Automatically create variables when pasting groups to a new typebot

Closes #1587
This commit is contained in:
Baptiste Arnaud
2024-06-19 14:09:18 +02:00
parent 67f37c02a4
commit 4ab1803d39
7 changed files with 148 additions and 20 deletions

View File

@ -131,6 +131,7 @@
"next-runtime-env": "1.6.2",
"superjson": "1.12.4",
"typescript": "5.4.5",
"zod": "3.22.4"
"zod": "3.22.4",
"@typebot.io/variables": "workspace:*"
}
}

View File

@ -7,6 +7,7 @@ import {
Edge,
GroupV6,
TypebotV6,
Variable,
} from '@typebot.io/schemas'
import { SetTypebot } from '../TypebotProvider'
import {
@ -15,9 +16,10 @@ import {
duplicateBlockDraft,
} from './blocks'
import { byId, isEmpty } from '@typebot.io/lib'
import { blockHasItems } from '@typebot.io/schemas/helpers'
import { blockHasItems, blockHasOptions } from '@typebot.io/schemas/helpers'
import { Coordinates, CoordinatesMap } from '@/features/graph/types'
import { parseUniqueKey } from '@typebot.io/lib/parseUniqueKey'
import { extractVariableIdsFromObject } from '@typebot.io/variables/extractVariablesFromObject'
export type GroupsActions = {
createGroup: (
@ -34,6 +36,7 @@ export type GroupsActions = {
pasteGroups: (
groups: GroupV6[],
edges: Edge[],
variables: Pick<Variable, 'id' | 'name'>[],
oldToNewIdsMapping: Map<string, string>
) => void
updateGroupsCoordinates: (newCoord: CoordinatesMap) => void
@ -131,12 +134,29 @@ const groupsActions = (setTypebot: SetTypebot): GroupsActions => ({
pasteGroups: (
groups: GroupV6[],
edges: Edge[],
variables: Omit<Variable, 'value'>[],
oldToNewIdsMapping: Map<string, string>
) => {
const createdGroups: GroupV6[] = []
setTypebot((typebot) =>
produce(typebot, (typebot) => {
const edgesToCreate: Edge[] = []
const variablesToCreate: Omit<Variable, 'value'>[] = []
variables.forEach((variable) => {
const existingVariable = typebot.variables.find(
(v) => v.name === variable.name
)
if (existingVariable) {
oldToNewIdsMapping.set(variable.id, existingVariable.id)
return
}
const id = createId()
oldToNewIdsMapping.set(variable.id, id)
variablesToCreate.push({
...variable,
id,
})
})
groups.forEach((group) => {
const groupTitle = isEmpty(group.title)
? ''
@ -151,6 +171,25 @@ const groupsActions = (setTypebot: SetTypebot): GroupsActions => ({
const newBlock = { ...block }
const blockId = createId()
oldToNewIdsMapping.set(newBlock.id, blockId)
console.log(JSON.stringify(newBlock), blockHasOptions(newBlock))
if (blockHasOptions(newBlock) && newBlock.options) {
const variableIdsToReplace = extractVariableIdsFromObject(
newBlock.options
).filter((v) => oldToNewIdsMapping.has(v))
console.log(
JSON.stringify(newBlock.options),
variableIdsToReplace
)
if (variableIdsToReplace.length > 0) {
let optionsStr = JSON.stringify(newBlock.options)
variableIdsToReplace.forEach((variableId) => {
const newId = oldToNewIdsMapping.get(variableId)
if (!newId) return
optionsStr = optionsStr.replace(variableId, newId)
})
newBlock.options = JSON.parse(optionsStr)
}
}
if (blockHasItems(newBlock)) {
newBlock.items = newBlock.items?.map((item) => {
const id = createId()
@ -224,6 +263,10 @@ const groupsActions = (setTypebot: SetTypebot): GroupsActions => ({
}
typebot.edges.push(newEdge)
})
variablesToCreate.forEach((variableToCreate) => {
typebot.variables.unshift(variableToCreate)
})
})
)
},

View File

@ -13,10 +13,14 @@ import { useRef, useState } from 'react'
import { useGroupsStore } from '../hooks/useGroupsStore'
import { toast } from 'sonner'
import { createId } from '@paralleldrive/cuid2'
import { Edge, GroupV6 } from '@typebot.io/schemas'
import { Edge, GroupV6, Variable } from '@typebot.io/schemas'
import { Coordinates } from '../types'
import { useShallow } from 'zustand/react/shallow'
import { projectMouse } from '../helpers/projectMouse'
import {
extractVariableIdReferencesInObject,
extractVariableIdsFromObject,
} from '@typebot.io/variables/extractVariablesFromObject'
type Props = {
graphPosition: Coordinates & { scale: number }
@ -62,10 +66,19 @@ export const GroupSelectionMenu = ({
const edges = typebot.edges.filter((edge) =>
groups.find((g) => g.id === edge.to.groupId)
)
copyGroups(groups, edges)
const variables = extractVariablesFromCopiedGroups(
groups,
typebot.variables
)
copyGroups({
groups,
edges,
variables,
})
return {
groups,
edges,
variables,
}
}
@ -77,6 +90,7 @@ export const GroupSelectionMenu = ({
const handlePaste = (overrideClipBoard?: {
groups: GroupV6[]
edges: Edge[]
variables: Omit<Variable, 'value'>[]
}) => {
if (!groupsInClipboard || isReadOnly || !mousePosition) return
const clipboard = overrideClipBoard ?? groupsInClipboard
@ -87,7 +101,12 @@ export const GroupSelectionMenu = ({
groups.forEach((group) => {
updateGroupCoordinates(group.id, group.graphCoordinates)
})
pasteGroups(groups, clipboard.edges, oldToNewIdsMapping)
pasteGroups(
groups,
clipboard.edges,
clipboard.variables,
oldToNewIdsMapping
)
setFocusedGroups(groups.map((g) => g.id))
}
@ -194,3 +213,33 @@ const parseGroupsToPaste = (
oldToNewIdsMapping,
}
}
export const extractVariablesFromCopiedGroups = (
groups: GroupV6[],
existingVariables: Variable[]
): Omit<Variable, 'value'>[] => {
const groupsStr = JSON.stringify(groups)
if (!groupsStr) return []
const calledVariablesId = extractVariableIdReferencesInObject(
groups,
existingVariables
)
const variableIdsInOptions = extractVariableIdsFromObject(groups)
return [...variableIdsInOptions, ...calledVariablesId].reduce<
Omit<Variable, 'value'>[]
>((acc, id) => {
if (!id) return acc
if (acc.find((v) => v.id === id)) return acc
const variable = existingVariables.find((v) => v.id === id)
if (!variable) return acc
return [
...acc,
{
id: variable.id,
name: variable.name,
isSessionVariable: variable.isSessionVariable,
},
]
}, [])
}

View File

@ -1,13 +1,19 @@
import { createWithEqualityFn } from 'zustand/traditional'
import { Coordinates, CoordinatesMap } from '../types'
import { Edge, Group, GroupV6 } from '@typebot.io/schemas'
import { Edge, Group, GroupV6, Variable } from '@typebot.io/schemas'
import { subscribeWithSelector } from 'zustand/middleware'
import { share } from 'shared-zustand'
type Store = {
focusedGroups: string[]
groupsCoordinates: CoordinatesMap | undefined
groupsInClipboard: { groups: GroupV6[]; edges: Edge[] } | undefined
groupsInClipboard:
| {
groups: GroupV6[]
edges: Edge[]
variables: Omit<Variable, 'value'>[]
}
| undefined
isDraggingGraph: boolean
// TO-DO: remove once Typebot provider is migrated to a Zustand store. We will be able to get it internally in the store (if mutualized).
getGroupsCoordinates: () => CoordinatesMap | undefined
@ -17,7 +23,11 @@ type Store = {
setFocusedGroups: (groupIds: string[]) => void
setGroupsCoordinates: (groups: Group[] | undefined) => void
updateGroupCoordinates: (groupId: string, newCoord: Coordinates) => void
copyGroups: (groups: GroupV6[], edges: Edge[]) => void
copyGroups: (args: {
groups: GroupV6[]
edges: Edge[]
variables: Omit<Variable, 'value'>[]
}) => void
setIsDraggingGraph: (isDragging: boolean) => void
}
@ -83,12 +93,9 @@ export const useGroupsStore = createWithEqualityFn<Store>()(
},
}))
},
copyGroups: (groups, edges) =>
copyGroups: (groupsInClipboard) =>
set({
groupsInClipboard: {
groups,
edges,
},
groupsInClipboard,
}),
setIsDraggingGraph: (isDragging) => set({ isDraggingGraph: isDragging }),
}))