import { useCallback, useEffect } from 'react' import { useNavigate } from 'react-router-dom' import { waitFor } from 'xstate' import { ActionButton } from '@src/components/ActionButton' import { CustomIcon } from '@src/components/CustomIcon' import Tooltip from '@src/components/Tooltip' import { useAbsoluteFilePath } from '@src/hooks/useAbsoluteFilePath' import { useNetworkContext } from '@src/hooks/useNetworkContext' import { NetworkHealthState } from '@src/hooks/useNetworkStatus' import { EngineConnectionStateType } from '@src/lang/std/engineConnection' import { bracket } from '@src/lib/exampleKcl' import makeUrlPathRelative from '@src/lib/makeUrlPathRelative' import { PATHS } from '@src/lib/paths' import { codeManager, editorManager, kclManager } from '@src/lib/singletons' import { reportRejection, trap } from '@src/lib/trap' import { settingsActor } from '@src/lib/singletons' import { onboardingRoutes } from '@src/routes/Onboarding' import { onboardingPaths } from '@src/routes/Onboarding/paths' import { parse, resultIsOk } from '@src/lang/wasm' import { updateModelingState } from '@src/lang/modelingWorkflows' import { EXECUTION_TYPE_REAL } from '@src/lib/constants' export const kbdClasses = 'py-0.5 px-1 text-sm rounded bg-chalkboard-10 dark:bg-chalkboard-100 border border-chalkboard-50 border-b-2' // Get the 1-indexed step number of the current onboarding step function useStepNumber( slug?: (typeof onboardingPaths)[keyof typeof onboardingPaths] ) { return slug ? slug === onboardingPaths.INDEX ? 1 : onboardingRoutes.findIndex( (r) => r.path === makeUrlPathRelative(slug) ) + 1 : 1 } export function useDemoCode() { const { overallState, immediateState } = useNetworkContext() useEffect(() => { async function setCodeToDemoIfNeeded() { // Don't run if the editor isn't loaded or the code is already the bracket if (!editorManager.editorView || codeManager.code === bracket) { return } // Don't run if the network isn't healthy or the connection isn't established if ( overallState === NetworkHealthState.Disconnected || overallState === NetworkHealthState.Issue || immediateState.type !== EngineConnectionStateType.ConnectionEstablished ) { return } const pResult = parse(bracket) if (trap(pResult) || !resultIsOk(pResult)) { return Promise.reject(pResult) } const ast = pResult.program await updateModelingState(ast, EXECUTION_TYPE_REAL, { kclManager: kclManager, editorManager: editorManager, codeManager: codeManager, }) } setCodeToDemoIfNeeded().catch(reportRejection) }, [editorManager.editorView, immediateState.type, overallState]) } export function useNextClick(newStatus: string) { const filePath = useAbsoluteFilePath() const navigate = useNavigate() return useCallback(() => { settingsActor.send({ type: 'set.app.onboardingStatus', data: { level: 'user', value: newStatus }, }) navigate(filePath + PATHS.ONBOARDING.INDEX.slice(0, -1) + newStatus) }, [filePath, newStatus, settingsActor.send, navigate]) } export function useDismiss() { const filePath = useAbsoluteFilePath() const send = settingsActor.send const navigate = useNavigate() const settingsCallback = useCallback(() => { send({ type: 'set.app.onboardingStatus', data: { level: 'user', value: 'dismissed' }, }) waitFor(settingsActor, (state) => state.matches('idle')) .then(() => navigate(filePath)) .catch(reportRejection) }, [send]) return settingsCallback } export function OnboardingButtons({ currentSlug, className, dismissClassName, onNextOverride, ...props }: { currentSlug?: (typeof onboardingPaths)[keyof typeof onboardingPaths] className?: string dismissClassName?: string onNextOverride?: () => void } & React.HTMLAttributes) { const dismiss = useDismiss() const stepNumber = useStepNumber(currentSlug) const previousStep = !stepNumber || stepNumber === 0 ? null : onboardingRoutes[stepNumber - 2] const goToPrevious = useNextClick( onboardingPaths.INDEX + (previousStep?.path ?? '') ) const nextStep = !stepNumber || stepNumber === onboardingRoutes.length ? null : onboardingRoutes[stepNumber] const goToNext = useNextClick(onboardingPaths.INDEX + (nextStep?.path ?? '')) return ( <>
previousStep?.path || previousStep?.index ? goToPrevious() : dismiss() } iconStart={{ icon: previousStep ? 'arrowLeft' : 'close', className: 'text-chalkboard-10', bgClassName: 'bg-destroy-80 group-hover:bg-destroy-80', }} className="hover:border-destroy-40 hover:bg-destroy-10/50 dark:hover:bg-destroy-80/50" data-testid="onboarding-prev" > {previousStep ? `Back` : 'Dismiss'} {stepNumber !== undefined && (

{stepNumber} / {onboardingRoutes.length}

)} { if (nextStep?.path) { onNextOverride ? onNextOverride() : goToNext() } else { dismiss() } }} iconStart={{ icon: nextStep ? 'arrowRight' : 'checkmark', bgClassName: 'dark:bg-chalkboard-80', }} className="dark:hover:bg-chalkboard-80/50" data-testid="onboarding-next" > {nextStep ? `Next` : 'Finish'}
) }