diff --git a/e2e/playwright/onboarding-tests.spec.ts b/e2e/playwright/onboarding-tests.spec.ts index 8a802e2a1..804b90c44 100644 --- a/e2e/playwright/onboarding-tests.spec.ts +++ b/e2e/playwright/onboarding-tests.spec.ts @@ -27,7 +27,7 @@ test.describe('Onboarding tests', () => { }, cleanProjectDir: true, }, - async ({ context, page, homePage }) => { + async ({ page, homePage }) => { const u = await getUtils(page) await page.setBodyDimensions({ width: 1200, height: 500 }) await homePage.goToModelingScene() @@ -68,7 +68,7 @@ test.describe('Onboarding tests', () => { }, cleanProjectDir: true, }, - async ({ page, homePage }, testInfo) => { + async ({ page }) => { const u = await getUtils(page) const viewportSize = { width: 1200, height: 500 } @@ -154,7 +154,7 @@ test.describe('Onboarding tests', () => { ) test( - 'Click through each onboarding step', + 'Click through each onboarding step and back', { appSettings: { app: { @@ -187,15 +187,21 @@ test.describe('Onboarding tests', () => { ).toBeVisible() const nextButton = page.getByTestId('onboarding-next') + const prevButton = page.getByTestId('onboarding-prev') while ((await nextButton.innerText()) !== 'Finish') { await nextButton.hover() await nextButton.click() } - // Finish the onboarding - await nextButton.hover() - await nextButton.click() + while ((await prevButton.innerText()) !== 'Dismiss') { + await prevButton.hover() + await prevButton.click() + } + + // Dismiss the onboarding + await prevButton.hover() + await prevButton.click() // Test that the onboarding pane is gone await expect(page.getByTestId('onboarding-content')).not.toBeVisible() @@ -269,7 +275,7 @@ test.describe('Onboarding tests', () => { cleanProjectDir: true, }, - async ({ context, page, homePage }) => { + async ({ page, homePage }) => { const u = await getUtils(page) const badCode = `// This is bad code we shouldn't see` @@ -336,10 +342,10 @@ test.describe('Onboarding tests', () => { await homePage.goToModelingScene() // Test that the text in this step is correct - const avatarLocator = await page + const avatarLocator = page .getByTestId('user-sidebar-toggle') .locator('img') - const onboardingOverlayLocator = await page + const onboardingOverlayLocator = page .getByTestId('onboarding-content') .locator('div') .nth(1) @@ -447,7 +453,7 @@ test.fixme( }, cleanProjectDir: true, }, - async ({ context, page, homePage }, testInfo) => { + async ({ context, page }) => { await context.folderSetupFn(async (dir) => { const routerTemplateDir = join(dir, 'router-template-slate') await fsp.mkdir(routerTemplateDir, { recursive: true }) @@ -486,10 +492,6 @@ test.fixme( }) await test.step('Navigate into project', async () => { - await page.setBodyDimensions({ width: 1200, height: 500 }) - - page.on('console', console.log) - await expect( page.getByRole('heading', { name: 'Your Projects' }) ).toBeVisible() diff --git a/src/routes/Onboarding/Camera.tsx b/src/routes/Onboarding/Camera.tsx index b52b74d72..476adb8f4 100644 --- a/src/routes/Onboarding/Camera.tsx +++ b/src/routes/Onboarding/Camera.tsx @@ -26,7 +26,7 @@ export default function Units() {
diff --git a/src/routes/Onboarding/CmdK.tsx b/src/routes/Onboarding/CmdK.tsx index 432ad1cc5..1ed836eb0 100644 --- a/src/routes/Onboarding/CmdK.tsx +++ b/src/routes/Onboarding/CmdK.tsx @@ -1,19 +1,17 @@ import usePlatform from 'hooks/usePlatform' -import { OnboardingButtons, kbdClasses, useDismiss, useNextClick } from '.' +import { OnboardingButtons, kbdClasses } from '.' import { onboardingPaths } from 'routes/Onboarding/paths' import { hotkeyDisplay } from 'lib/hotkeyWrapper' import { COMMAND_PALETTE_HOTKEY } from 'components/CommandBar/CommandBar' export default function CmdK() { - const dismiss = useDismiss() - const next = useNextClick(onboardingPaths.USER_MENU) const platformName = usePlatform() return (

Command Bar

@@ -38,12 +36,7 @@ export default function CmdK() { . You can control settings, authentication, and file management from the command bar, as well as a growing number of modeling commands.

- +
) diff --git a/src/routes/Onboarding/CodeEditor.tsx b/src/routes/Onboarding/CodeEditor.tsx index 2ba82b3ed..9af30860c 100644 --- a/src/routes/Onboarding/CodeEditor.tsx +++ b/src/routes/Onboarding/CodeEditor.tsx @@ -1,22 +1,14 @@ -import { - kbdClasses, - OnboardingButtons, - useDemoCode, - useDismiss, - useNextClick, -} from '.' +import { kbdClasses, OnboardingButtons, useDemoCode } from '.' import { onboardingPaths } from 'routes/Onboarding/paths' export default function OnboardingCodeEditor() { useDemoCode() - const dismiss = useDismiss() - const next = useNextClick(onboardingPaths.PARAMETRIC_MODELING) return (
@@ -73,12 +65,7 @@ export default function OnboardingCodeEditor() { pressing Shift + C.

- +
) diff --git a/src/routes/Onboarding/Export.tsx b/src/routes/Onboarding/Export.tsx index 10970f022..c71455080 100644 --- a/src/routes/Onboarding/Export.tsx +++ b/src/routes/Onboarding/Export.tsx @@ -1,16 +1,13 @@ import { APP_NAME } from 'lib/constants' -import { OnboardingButtons, useDismiss, useNextClick } from '.' +import { OnboardingButtons } from '.' import { onboardingPaths } from 'routes/Onboarding/paths' export default function Export() { - const dismiss = useDismiss() - const next = useNextClick(onboardingPaths.SKETCHING) - return (
@@ -52,12 +49,7 @@ export default function Export() { !

- +
) diff --git a/src/routes/Onboarding/FutureWork.tsx b/src/routes/Onboarding/FutureWork.tsx index cf7a13466..4be5e0fce 100644 --- a/src/routes/Onboarding/FutureWork.tsx +++ b/src/routes/Onboarding/FutureWork.tsx @@ -1,4 +1,4 @@ -import { OnboardingButtons, useDemoCode, useDismiss } from '.' +import { OnboardingButtons, useDemoCode } from '.' import { useEffect } from 'react' import { useModelingContext } from 'hooks/useModelingContext' import { APP_NAME } from 'lib/constants' @@ -7,7 +7,6 @@ import { sceneInfra } from 'lib/singletons' export default function FutureWork() { const { send } = useModelingContext() - const dismiss = useDismiss() // Reset the code, the camera, and the modeling state useDemoCode() @@ -19,7 +18,7 @@ export default function FutureWork() { return (
-
+

Future Work

We have curves, cuts, multi-profile sketch mode, and many more CAD @@ -59,9 +58,6 @@ export default function FutureWork() {

diff --git a/src/routes/Onboarding/InteractiveNumbers.tsx b/src/routes/Onboarding/InteractiveNumbers.tsx index 3f5987dab..d8a6cb0c5 100644 --- a/src/routes/Onboarding/InteractiveNumbers.tsx +++ b/src/routes/Onboarding/InteractiveNumbers.tsx @@ -1,23 +1,15 @@ -import { - OnboardingButtons, - kbdClasses, - useDemoCode, - useDismiss, - useNextClick, -} from '.' +import { OnboardingButtons, kbdClasses, useDemoCode } from '.' import { onboardingPaths } from 'routes/Onboarding/paths' import { bracketWidthConstantLine } from 'lib/exampleKcl' export default function OnboardingInteractiveNumbers() { useDemoCode() - const dismiss = useDismiss() - const next = useNextClick(onboardingPaths.COMMAND_K) return (
@@ -88,12 +80,7 @@ export default function OnboardingInteractiveNumbers() { your ideas for how to make it better.

- +
) diff --git a/src/routes/Onboarding/Introduction.tsx b/src/routes/Onboarding/Introduction.tsx index 9dac8f05c..ada5d8ab3 100644 --- a/src/routes/Onboarding/Introduction.tsx +++ b/src/routes/Onboarding/Introduction.tsx @@ -1,4 +1,4 @@ -import { OnboardingButtons, useDemoCode, useDismiss, useNextClick } from '.' +import { OnboardingButtons, useDemoCode } from '.' import { onboardingPaths } from 'routes/Onboarding/paths' import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext' import { Themes, getSystemTheme } from 'lib/theme' @@ -8,13 +8,12 @@ import { isDesktop } from 'lib/isDesktop' import { useNavigate, useRouteLoaderData } from 'react-router-dom' import { codeManager, kclManager } from 'lib/singletons' import { APP_NAME } from 'lib/constants' -import { useState } from 'react' +import { useEffect, useState } from 'react' import { IndexLoaderData } from 'lib/types' import { PATHS } from 'lib/paths' import { useFileContext } from 'hooks/useFileContext' import { useLspContext } from 'components/LspProvider' import { reportRejection } from 'lib/trap' -import { toSync } from 'lib/utils' /** * Show either a welcome screen or a warning screen @@ -39,7 +38,7 @@ interface OnboardingResetWarningProps { function OnboardingResetWarning(props: OnboardingResetWarningProps) { return (
-
+
{!isDesktop() ? ( ) : ( @@ -52,7 +51,6 @@ function OnboardingResetWarning(props: OnboardingResetWarningProps) { function OnboardingWarningDesktop(props: OnboardingResetWarningProps) { const navigate = useNavigate() - const dismiss = useDismiss() const loaderData = useRouteLoaderData(PATHS.FILE) as IndexLoaderData const { context: fileContext } = useFileContext() const { onProjectClose, onProjectOpen } = useLspContext() @@ -81,17 +79,28 @@ function OnboardingWarningDesktop(props: OnboardingResetWarningProps) { { + onAccept().catch(reportRejection) + }} /> ) } function OnboardingWarningWeb(props: OnboardingResetWarningProps) { - const dismiss = useDismiss() + useEffect(() => { + async function beforeNavigate() { + // We do want to update both the state and editor here. + codeManager.updateCodeStateEditor(bracket) + await codeManager.writeToFile() + await kclManager.executeCode(true) + props.setShouldShowWarning(false) + } + return () => { + beforeNavigate().catch(reportRejection) + } + }, []) return ( <>

@@ -101,19 +110,7 @@ function OnboardingWarningWeb(props: OnboardingResetWarningProps) { We see you have some of your own code written in this project. Please save it somewhere else before continuing the onboarding.

- { - // We do want to update both the state and editor here. - codeManager.updateCodeStateEditor(bracket) - await codeManager.writeToFile() - - await kclManager.executeCode(true) - props.setShouldShowWarning(false) - }, reportRejection)} - nextText="Overwrite code and continue" - /> + ) } @@ -136,12 +133,10 @@ function OnboardingIntroductionInner() { (theme.current === Themes.System && getSystemTheme() === Themes.Light) ? '-dark' : '' - const dismiss = useDismiss() - const next = useNextClick(onboardingPaths.CAMERA) return (
-
+

diff --git a/src/routes/Onboarding/ParametricModeling.tsx b/src/routes/Onboarding/ParametricModeling.tsx index 41466446a..0fa8428ae 100644 --- a/src/routes/Onboarding/ParametricModeling.tsx +++ b/src/routes/Onboarding/ParametricModeling.tsx @@ -1,4 +1,4 @@ -import { OnboardingButtons, useDemoCode, useDismiss, useNextClick } from '.' +import { OnboardingButtons, useDemoCode } from '.' import { onboardingPaths } from 'routes/Onboarding/paths' import { Themes, getSystemTheme } from 'lib/theme' import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext' @@ -21,14 +21,12 @@ export default function OnboardingParametricModeling() { (theme === Themes.System && getSystemTheme() === Themes.Light) ? '-dark' : '' - const dismiss = useDismiss() - const next = useNextClick(onboardingPaths.INTERACTIVE_NUMBERS) return (
@@ -77,12 +75,7 @@ export default function OnboardingParametricModeling() {
- +
) diff --git a/src/routes/Onboarding/ProjectMenu.tsx b/src/routes/Onboarding/ProjectMenu.tsx index fc41b9958..db02afc26 100644 --- a/src/routes/Onboarding/ProjectMenu.tsx +++ b/src/routes/Onboarding/ProjectMenu.tsx @@ -1,17 +1,15 @@ -import { OnboardingButtons, useDismiss, useNextClick } from '.' +import { OnboardingButtons } from '.' import { onboardingPaths } from 'routes/Onboarding/paths' import { isDesktop } from 'lib/isDesktop' export default function ProjectMenu() { - const dismiss = useDismiss() - const next = useNextClick(onboardingPaths.EXPORT) const onDesktop = isDesktop() return (
@@ -57,12 +55,7 @@ export default function ProjectMenu() { )}
- +
) diff --git a/src/routes/Onboarding/Sketching.tsx b/src/routes/Onboarding/Sketching.tsx index 54f79fdc5..7129ee196 100644 --- a/src/routes/Onboarding/Sketching.tsx +++ b/src/routes/Onboarding/Sketching.tsx @@ -1,12 +1,9 @@ -import { OnboardingButtons, useDismiss, useNextClick } from '.' +import { OnboardingButtons } from '.' import { onboardingPaths } from 'routes/Onboarding/paths' import { useEffect } from 'react' import { codeManager, kclManager } from 'lib/singletons' export default function Sketching() { - const dismiss = useDismiss() - const next = useNextClick(onboardingPaths.FUTURE_WORK) - useEffect(() => { async function clearEditor() { // We do want to update both the state and editor here. @@ -22,7 +19,7 @@ export default function Sketching() {

Sketching

@@ -45,9 +42,6 @@ export default function Sketching() {
diff --git a/src/routes/Onboarding/Streaming.tsx b/src/routes/Onboarding/Streaming.tsx index 8e764840e..bd0028152 100644 --- a/src/routes/Onboarding/Streaming.tsx +++ b/src/routes/Onboarding/Streaming.tsx @@ -1,15 +1,12 @@ -import { OnboardingButtons, useDismiss, useNextClick } from '.' +import { OnboardingButtons } from '.' import { onboardingPaths } from 'routes/Onboarding/paths' export default function Streaming() { - const dismiss = useDismiss() - const next = useNextClick(onboardingPaths.EDITOR) - return (
@@ -44,9 +41,7 @@ export default function Streaming() {
diff --git a/src/routes/Onboarding/UserMenu.tsx b/src/routes/Onboarding/UserMenu.tsx index 3487f3051..ca2448c1b 100644 --- a/src/routes/Onboarding/UserMenu.tsx +++ b/src/routes/Onboarding/UserMenu.tsx @@ -1,12 +1,10 @@ -import { OnboardingButtons, useDismiss, useNextClick } from '.' +import { OnboardingButtons } from '.' import { onboardingPaths } from 'routes/Onboarding/paths' import { useEffect, useState } from 'react' import { useUser } from 'machines/appMachine' export default function UserMenu() { const user = useUser() - const dismiss = useDismiss() - const next = useNextClick(onboardingPaths.PROJECT_MENU) const [avatarErrored, setAvatarErrored] = useState(false) const errorOrNoImage = !user?.image || avatarErrored @@ -32,7 +30,7 @@ export default function UserMenu() {
@@ -48,12 +46,7 @@ export default function UserMenu() { only apply to the current project.

- +
) diff --git a/src/routes/Onboarding/index.tsx b/src/routes/Onboarding/index.tsx index 2c9fba4b0..16d4f32e8 100644 --- a/src/routes/Onboarding/index.tsx +++ b/src/routes/Onboarding/index.tsx @@ -26,6 +26,9 @@ import { reportRejection } from 'lib/trap' import { useNetworkContext } from 'hooks/useNetworkContext' import { NetworkHealthState } from 'hooks/useNetworkStatus' import { EngineConnectionStateType } from 'lang/std/engineConnection' +import { CustomIcon } from 'components/CustomIcon' +import Tooltip from 'components/Tooltip' +import { commandBarActor } from 'machines/commandBarMachine' 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' @@ -163,58 +166,99 @@ export function useStepNumber( : onboardingRoutes.findIndex( (r) => r.path === makeUrlPathRelative(slug) ) + 1 - : undefined + : 1 } export function OnboardingButtons({ - next, - nextText, - dismiss, currentSlug, className, + dismissClassName, + onNextOverride, ...props }: { - next: () => void - nextText?: string - dismiss: () => void 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 ( -
- + +
- {nextText ?? 'Next'} - -
+ + 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'} + +
+ ) }