2
0

(theme) Add progress bar option (#1276)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Introduced progress bar functionality across various components for a
more interactive user experience.
	- Added progress tracking and display in chat sessions.
- **Enhancements**
- Adjusted layout height calculations in the settings and theme pages
for consistency.
- Improved theme customization options with progress bar styling and
placement settings.
- **Bug Fixes**
- Fixed dynamic height calculation issues in settings and theme side
menus by standardizing heights.
- **Style**
- Added custom styling properties for the progress bar in chat
interfaces.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Baptiste Arnaud
2024-02-23 08:31:14 +01:00
committed by GitHub
parent f2b21746bc
commit 2d7ccf17c0
30 changed files with 535 additions and 90 deletions

View File

@ -0,0 +1,130 @@
import { blockHasItems, isDefined, isInputBlock, byId } from '@typebot.io/lib'
import { getBlockById } from '@typebot.io/lib/getBlockById'
import { Block, SessionState } from '@typebot.io/schemas'
type Props = {
typebotsQueue: SessionState['typebotsQueue']
progressMetadata: NonNullable<SessionState['progressMetadata']>
currentInputBlockId: string
}
export const computeCurrentProgress = ({
typebotsQueue,
progressMetadata,
currentInputBlockId,
}: Props) => {
if (progressMetadata.totalAnswers === 0) return 0
const paths = computePossibleNextInputBlocks({
typebotsQueue: typebotsQueue,
blockId: currentInputBlockId,
visitedBlocks: {
[typebotsQueue[0].typebot.id]: [],
},
currentPath: [],
})
return (
(progressMetadata.totalAnswers /
(Math.max(...paths.map((b) => b.length)) +
progressMetadata.totalAnswers)) *
100
)
}
const computePossibleNextInputBlocks = ({
currentPath,
typebotsQueue,
blockId,
visitedBlocks,
}: {
currentPath: string[]
typebotsQueue: SessionState['typebotsQueue']
blockId: string
visitedBlocks: {
[key: string]: string[]
}
}): string[][] => {
if (visitedBlocks[typebotsQueue[0].typebot.id].includes(blockId)) return []
visitedBlocks[typebotsQueue[0].typebot.id].push(blockId)
const possibleNextInputBlocks: string[][] = []
const { block, group, blockIndex } = getBlockById(
blockId,
typebotsQueue[0].typebot.groups
)
if (isInputBlock(block)) currentPath.push(block.id)
const outgoingEdgeIds = getBlockOutgoingEdgeIds(block)
for (const outgoingEdgeId of outgoingEdgeIds) {
const to = typebotsQueue[0].typebot.edges.find(
(e) => e.id === outgoingEdgeId
)?.to
if (!to) continue
const blockId =
to.blockId ??
typebotsQueue[0].typebot.groups.find((g) => g.id === to.groupId)
?.blocks[0].id
if (!blockId) continue
possibleNextInputBlocks.push(
...computePossibleNextInputBlocks({
typebotsQueue,
blockId,
visitedBlocks,
currentPath,
})
)
}
for (const block of group.blocks.slice(blockIndex + 1)) {
possibleNextInputBlocks.push(
...computePossibleNextInputBlocks({
typebotsQueue,
blockId: block.id,
visitedBlocks,
currentPath,
})
)
}
if (outgoingEdgeIds.length > 0 || group.blocks.length !== blockIndex + 1)
return possibleNextInputBlocks
if (typebotsQueue.length > 1) {
const nextEdgeId = typebotsQueue[0].edgeIdToTriggerWhenDone
const to = typebotsQueue[1].typebot.edges.find(byId(nextEdgeId))?.to
if (!to) return possibleNextInputBlocks
const blockId =
to.blockId ??
typebotsQueue[0].typebot.groups.find((g) => g.id === to.groupId)
?.blocks[0].id
if (blockId) {
possibleNextInputBlocks.push(
...computePossibleNextInputBlocks({
typebotsQueue: typebotsQueue.slice(1),
blockId,
visitedBlocks: {
...visitedBlocks,
[typebotsQueue[1].typebot.id]: [],
},
currentPath,
})
)
}
}
possibleNextInputBlocks.push(currentPath)
return possibleNextInputBlocks
}
const getBlockOutgoingEdgeIds = (block: Block) => {
const edgeIds: string[] = []
if (blockHasItems(block)) {
edgeIds.push(...block.items.map((i) => i.outgoingEdgeId).filter(isDefined))
}
if (block.outgoingEdgeId) edgeIds.push(block.outgoingEdgeId)
return edgeIds
}

View File

@ -357,6 +357,9 @@ const setNewAnswerInState =
return {
...state,
progressMetadata: state.progressMetadata
? { totalAnswers: state.progressMetadata.totalAnswers + 1 }
: undefined,
typebotsQueue: state.typebotsQueue.map((typebot, index) =>
index === 0
? {

View File

@ -70,6 +70,13 @@ export const getNextGroup =
...state.typebotsQueue.slice(2),
],
} satisfies SessionState
if (state.progressMetadata)
newSessionState.progressMetadata = {
...state.progressMetadata,
totalAnswers:
state.progressMetadata.totalAnswers +
state.typebotsQueue[0].answers.length,
}
const nextGroup = await getNextGroup(newSessionState)(nextEdgeId)
newSessionState = nextGroup.newSessionState
if (!nextGroup)

View File

@ -137,6 +137,11 @@ export const startSession = async ({
startParams.type === 'preview'
? undefined
: typebot.settings.security?.allowedOrigins,
progressMetadata: initialSessionState?.whatsApp
? undefined
: typebot.theme.general?.progressBar?.isEnabled
? { totalAnswers: 0 }
: undefined,
...initialSessionState,
}