From 9fa99ede079e3be6a82ea28b4531729e73f52613 Mon Sep 17 00:00:00 2001 From: Frank Noirot Date: Thu, 24 Apr 2025 16:11:03 -0400 Subject: [PATCH 01/29] Remove unused `telemetryLoader` --- src/Router.tsx | 4 +--- src/lib/routeLoaders.ts | 6 ------ 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/src/Router.tsx b/src/Router.tsx index f592783deff..e457870f2a8 100644 --- a/src/Router.tsx +++ b/src/Router.tsx @@ -28,7 +28,7 @@ import useHotkeyWrapper from '@src/lib/hotkeyWrapper' import { isDesktop } from '@src/lib/isDesktop' import makeUrlPathRelative from '@src/lib/makeUrlPathRelative' import { PATHS } from '@src/lib/paths' -import { fileLoader, homeLoader, telemetryLoader } from '@src/lib/routeLoaders' +import { fileLoader, homeLoader } from '@src/lib/routeLoaders' import { codeManager, engineCommandManager, @@ -110,7 +110,6 @@ const router = createRouter([ }, { id: PATHS.FILE + 'TELEMETRY', - loader: telemetryLoader, children: [ { path: makeUrlPathRelative(PATHS.TELEMETRY), @@ -144,7 +143,6 @@ const router = createRouter([ }, { path: makeUrlPathRelative(PATHS.TELEMETRY), - loader: telemetryLoader, element: , }, ], diff --git a/src/lib/routeLoaders.ts b/src/lib/routeLoaders.ts index de46b4c4a5c..020efedfbfd 100644 --- a/src/lib/routeLoaders.ts +++ b/src/lib/routeLoaders.ts @@ -22,12 +22,6 @@ import type { } from '@src/lib/types' import { settingsActor } from '@src/lib/singletons' -export const telemetryLoader: LoaderFunction = async ({ - params, -}): Promise => { - return null -} - export const fileLoader: LoaderFunction = async ( routerData ): Promise => { From 58757adf6b34e626d47a2f0507addfed050d4a29 Mon Sep 17 00:00:00 2001 From: Frank Noirot Date: Mon, 28 Apr 2025 19:35:26 -0400 Subject: [PATCH 02/29] Remove onboarding redirect behavior --- src/components/RouteProvider.tsx | 29 +---------------------------- 1 file changed, 1 insertion(+), 28 deletions(-) diff --git a/src/components/RouteProvider.tsx b/src/components/RouteProvider.tsx index b18f55743a5..8abfee73c1d 100644 --- a/src/components/RouteProvider.tsx +++ b/src/components/RouteProvider.tsx @@ -32,7 +32,6 @@ export function RouteProvider({ children }: { children: ReactNode }) { const navigation = useNavigation() const navigate = useNavigate() const location = useLocation() - const settings = useSettings() useEffect(() => { // On initialization, the react-router-dom does not send a 'loading' state event. @@ -46,35 +45,9 @@ export function RouteProvider({ children }: { children: ReactNode }) { markOnce('code/willLoadHome') } else if (isFile) { markOnce('code/willLoadFile') - - /** - * TODO: Move to XState. This block has been moved from routerLoaders - * and is borrowing the `isFile` logic from the rest of this - * telemetry-focused `useEffect`. Once `appMachine` knows about - * the current route and navigation, this can be moved into settingsMachine - * to fire as soon as the user settings have been read. - */ - const onboardingStatus: OnboardingStatus = - settings.app.onboardingStatus.current || '' - // '' is the initial state, 'completed' and 'dismissed' are the final states - const needsToOnboard = - onboardingStatus.length === 0 || - !(onboardingStatus === 'completed' || onboardingStatus === 'dismissed') - const shouldRedirectToOnboarding = isFile && needsToOnboard - - if ( - shouldRedirectToOnboarding && - settingsActor.getSnapshot().matches('idle') - ) { - navigate( - (first ? location.pathname : navigation.location?.pathname) + - PATHS.ONBOARDING.INDEX + - onboardingStatus.slice(1) - ) - } } setFirstState(false) - }, [navigation]) + }, [first, navigation, location.pathname]) useEffect(() => { if (!isDesktop()) return From a613700f4d28e708d4463a8acf0a027db3edc838 Mon Sep 17 00:00:00 2001 From: Frank Noirot Date: Mon, 28 Apr 2025 19:36:33 -0400 Subject: [PATCH 03/29] Allow subRoute to be passed to navigateToProject --- src/components/Providers/SystemIOProviderDesktop.tsx | 4 ++-- src/machines/systemIO/systemIOMachine.ts | 7 +++++-- src/machines/systemIO/utils.ts | 2 +- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/components/Providers/SystemIOProviderDesktop.tsx b/src/components/Providers/SystemIOProviderDesktop.tsx index a20b6a25ef9..5b3ca53c30a 100644 --- a/src/components/Providers/SystemIOProviderDesktop.tsx +++ b/src/components/Providers/SystemIOProviderDesktop.tsx @@ -35,14 +35,14 @@ export function SystemIOMachineLogicListenerDesktop() { if (!requestedProjectName.name) { return } - let projectPathWithoutSpecificKCLFile = + const projectPathWithoutSpecificKCLFile = projectDirectoryPath + window.electron.path.sep + requestedProjectName.name const requestedPath = `${PATHS.FILE}/${encodeURIComponent( projectPathWithoutSpecificKCLFile - )}` + )}${requestedProjectName.subRoute || ''}` navigate(requestedPath) }, [requestedProjectName]) } diff --git a/src/machines/systemIO/systemIOMachine.ts b/src/machines/systemIO/systemIOMachine.ts index 57e1750827b..cf7d81201c3 100644 --- a/src/machines/systemIO/systemIOMachine.ts +++ b/src/machines/systemIO/systemIOMachine.ts @@ -39,7 +39,7 @@ export const systemIOMachine = setup({ } | { type: SystemIOMachineEvents.navigateToProject - data: { requestedProjectName: string } + data: { requestedProjectName: string; subRoute?: string } } | { type: SystemIOMachineEvents.navigateToFile @@ -117,7 +117,10 @@ export const systemIOMachine = setup({ [SystemIOMachineActions.setRequestedProjectName]: assign({ requestedProjectName: ({ event }) => { assertEvent(event, SystemIOMachineEvents.navigateToProject) - return { name: event.data.requestedProjectName } + return { + name: event.data.requestedProjectName, + subRoute: event.data.subRoute, + } }, }), [SystemIOMachineActions.setRequestedFileName]: assign({ diff --git a/src/machines/systemIO/utils.ts b/src/machines/systemIO/utils.ts index 5691fe2e005..f19131b6247 100644 --- a/src/machines/systemIO/utils.ts +++ b/src/machines/systemIO/utils.ts @@ -73,7 +73,7 @@ export type SystemIOContext = { /** has the application gone through the initialization of systemIOMachine at least once. * this is required to prevent chokidar from spamming invalid events during initialization. */ hasListedProjects: boolean - requestedProjectName: { name: string } + requestedProjectName: { name: string; subRoute?: string } requestedFileName: { project: string; file: string } canReadWriteProjectDirectory: { value: boolean; error: unknown } clearURLParams: { value: boolean } From f061da2447f9ff0b04708066079258b6ae6b7f54 Mon Sep 17 00:00:00 2001 From: Frank Noirot Date: Mon, 28 Apr 2025 20:44:36 -0400 Subject: [PATCH 04/29] Replace warning dialog routes with toasts --- src/lib/desktopFS.ts | 45 ++--- src/routes/Onboarding/Introduction.tsx | 116 +----------- src/routes/Onboarding/utils.tsx | 252 +++++++++++++++++++++++-- 3 files changed, 255 insertions(+), 158 deletions(-) diff --git a/src/lib/desktopFS.ts b/src/lib/desktopFS.ts index 29bb9d8ef3b..66fdacaa9e6 100644 --- a/src/lib/desktopFS.ts +++ b/src/lib/desktopFS.ts @@ -14,6 +14,10 @@ import { bracket } from '@src/lib/exampleKcl' import { isDesktop } from '@src/lib/isDesktop' import { PATHS } from '@src/lib/paths' import type { FileEntry } from '@src/lib/project' +import makeUrlPathRelative from './makeUrlPathRelative' +import { onboardingPaths } from '@src/routes/Onboarding/paths' +import { SystemIOMachineEvents } from '@src/machines/systemIO/utils' +import { systemIOActor } from './singletons' export const isHidden = (fileOrDir: FileEntry) => !!fileOrDir.name?.startsWith('.') @@ -132,19 +136,9 @@ export async function getSettingsFolderPaths(projectPath?: string) { } } -export async function createAndOpenNewTutorialProject({ - onProjectOpen, - navigate, -}: { - onProjectOpen: ( - project: { - name: string | null - path: string | null - } | null, - file: FileEntry | null - ) => void - navigate: (path: string) => void -}) { +export async function createAndOpenNewTutorialProject( + onboardingStatus = onboardingPaths.INDEX +) { // Create a new project with the onboarding project name const configuration = await readAppSettingsFile() const projects = await listProjects(configuration) @@ -175,19 +169,20 @@ export async function createAndOpenNewTutorialProject({ configuration ) - // Prep the LSP and navigate to the onboarding start - onProjectOpen( - { - name: newProject.name, - path: newProject.path, - }, - null - ) - navigate( - `${PATHS.FILE}/${encodeURIComponent(newProject.default_file)}${ - PATHS.ONBOARDING.INDEX - }` + const filePathAsUri = encodeURIComponent( + newProject.default_file.replace(newProject.path, '') ) + const subRoute = `${filePathAsUri}${PATHS.ONBOARDING.INDEX}${makeUrlPathRelative( + onboardingStatus + )}` + systemIOActor.send({ + type: SystemIOMachineEvents.navigateToProject, + data: { + requestedProjectName: newProject.name, + subRoute, + }, + }) + return newProject } diff --git a/src/routes/Onboarding/Introduction.tsx b/src/routes/Onboarding/Introduction.tsx index ceb0eeb1730..14f1e2ee2e7 100644 --- a/src/routes/Onboarding/Introduction.tsx +++ b/src/routes/Onboarding/Introduction.tsx @@ -1,124 +1,12 @@ -import { useEffect, useState } from 'react' -import { useNavigate, useRouteLoaderData } from 'react-router-dom' - -import { useLspContext } from '@src/components/LspProvider' -import { useFileContext } from '@src/hooks/useFileContext' -import { isKclEmptyOrOnlySettings } from '@src/lang/wasm' import { APP_NAME } from '@src/lib/constants' -import { createAndOpenNewTutorialProject } from '@src/lib/desktopFS' -import { bracket } from '@src/lib/exampleKcl' import { isDesktop } from '@src/lib/isDesktop' -import { PATHS } from '@src/lib/paths' -import { codeManager, kclManager } from '@src/lib/singletons' import { Themes, getSystemTheme } from '@src/lib/theme' -import { reportRejection } from '@src/lib/trap' -import type { IndexLoaderData } from '@src/lib/types' import { useSettings } from '@src/lib/singletons' import { onboardingPaths } from '@src/routes/Onboarding/paths' import { OnboardingButtons, useDemoCode } from '@src/routes/Onboarding/utils' -/** - * Show either a welcome screen or a warning screen - * depending on if the user has code in the editor. - */ -export default function OnboardingIntroduction() { - const [shouldShowWarning, setShouldShowWarning] = useState( - !isKclEmptyOrOnlySettings(codeManager.code) && codeManager.code !== bracket - ) - - return shouldShowWarning ? ( - - ) : ( - - ) -} - -interface OnboardingResetWarningProps { - setShouldShowWarning: (arg: boolean) => void -} - -function OnboardingResetWarning(props: OnboardingResetWarningProps) { - return ( -
-
- {!isDesktop() ? ( - - ) : ( - - )} -
-
- ) -} - -function OnboardingWarningDesktop(props: OnboardingResetWarningProps) { - const navigate = useNavigate() - const loaderData = useRouteLoaderData(PATHS.FILE) as IndexLoaderData - const { context: fileContext } = useFileContext() - const { onProjectClose, onProjectOpen } = useLspContext() - - async function onAccept() { - onProjectClose( - loaderData.file || null, - fileContext.project.path || null, - false - ) - await createAndOpenNewTutorialProject({ onProjectOpen, navigate }) - props.setShouldShowWarning(false) - } - - return ( - <> -

- Would you like to create a new project? -

-
-

- You have some content in this project that we don't want to overwrite. - If you would like to create a new project, please click the button - below. -

-
- { - onAccept().catch(reportRejection) - }} - /> - - ) -} - -function OnboardingWarningWeb(props: OnboardingResetWarningProps) { - useEffect(() => { - async function beforeNavigate() { - // We do want to update both the state and editor here. - codeManager.updateCodeStateEditor(bracket) - await codeManager.writeToFile() - - await kclManager.executeCode() - props.setShouldShowWarning(false) - } - return () => { - beforeNavigate().catch(reportRejection) - } - }, []) - return ( - <> -

- Replaying onboarding resets your code -

-

- We see you have some of your own code written in this project. Please - save it somewhere else before continuing the onboarding. -

- - - ) -} - -function OnboardingIntroductionInner() { +export default function Introduction() { // Reset the code to the bracket code useDemoCode() @@ -182,7 +70,7 @@ function OnboardingIntroductionInner() {

diff --git a/src/routes/Onboarding/utils.tsx b/src/routes/Onboarding/utils.tsx index 30309ad87a8..31a95a6b5c7 100644 --- a/src/routes/Onboarding/utils.tsx +++ b/src/routes/Onboarding/utils.tsx @@ -1,5 +1,9 @@ import { useCallback, useEffect } from 'react' -import { useNavigate } from 'react-router-dom' +import { + type NavigateFunction, + type useLocation, + useNavigate, +} from 'react-router-dom' import { waitFor } from 'xstate' import { ActionButton } from '@src/components/ActionButton' @@ -17,9 +21,16 @@ 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 { isKclEmptyOrOnlySettings, parse, resultIsOk } from '@src/lang/wasm' import { updateModelingState } from '@src/lang/modelingWorkflows' import { EXECUTION_TYPE_REAL } from '@src/lib/constants' +import toast from 'react-hot-toast' +import type CodeManager from '@src/lang/codeManager' +import type { OnboardingStatus } from '@rust/kcl-lib/bindings/OnboardingStatus' +import { createAndOpenNewTutorialProject } from '@src/lib/desktopFS' +import { isDesktop } from '@src/lib/isDesktop' +import type { KclManager } from '@src/lang/KclSingleton' +import { Logo } from '@src/components/Logo' 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' @@ -80,7 +91,7 @@ export function useNextClick(newStatus: string) { data: { level: 'user', value: newStatus }, }) navigate(filePath + PATHS.ONBOARDING.INDEX.slice(0, -1) + newStatus) - }, [filePath, newStatus, settingsActor.send, navigate]) + }, [filePath, newStatus, navigate]) } export function useDismiss() { @@ -94,9 +105,17 @@ export function useDismiss() { data: { level: 'user', value: 'dismissed' }, }) waitFor(settingsActor, (state) => state.matches('idle')) - .then(() => navigate(filePath)) + .then(() => { + navigate(filePath) + toast.success( + 'Click the question mark in the lower-right corner if you ever want to redo the tutorial!', + { + duration: 5_000, + } + ) + }) .catch(reportRejection) - }, [send]) + }, [send, filePath, navigate]) return settingsCallback } @@ -115,7 +134,7 @@ export function OnboardingButtons({ const dismiss = useDismiss() const stepNumber = useStepNumber(currentSlug) const previousStep = - !stepNumber || stepNumber === 0 ? null : onboardingRoutes[stepNumber - 2] + !stepNumber || stepNumber <= 1 ? null : onboardingRoutes[stepNumber - 2] const goToPrevious = useNextClick( onboardingPaths.INDEX + (previousStep?.path ?? '') ) @@ -128,11 +147,11 @@ export function OnboardingButtons({ return ( <>
- previousStep?.path || previousStep?.index - ? goToPrevious() - : dismiss() - } + onClick={() => (previousStep?.path ? goToPrevious() : dismiss())} iconStart={{ - icon: previousStep ? 'arrowLeft' : 'close', + icon: previousStep?.path ? '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'} + {previousStep?.path ? 'Back' : 'Dismiss'} {stepNumber !== undefined && (

- {stepNumber} / {onboardingRoutes.length} + {stepNumber - 1} / {onboardingRoutes.length}

)} - {nextStep ? `Next` : 'Finish'} + {nextStep ? 'Next' : 'Finish'}
) } + +export interface OnboardingUtilDeps { + onboardingStatus: OnboardingStatus + codeManager: CodeManager + kclManager: KclManager + navigate: NavigateFunction +} + +export const ERROR_MUST_WARN = 'Must warn user before overwrite' + +/** + * Accept to begin the onboarding tutorial, + * depending on the platform and the state of the user's code. + */ +export async function acceptOnboarding(deps: OnboardingUtilDeps) { + if (isDesktop()) { + createAndOpenNewTutorialProject(deps.onboardingStatus) + } else { + const isCodeResettable = hasResetReadyCode(deps.codeManager) + if (isCodeResettable) { + resetCodeAndAdvanceOnboarding(deps) + } else { + return Promise.reject(new Error(ERROR_MUST_WARN)) + } + } +} + +/** + * Given that the user has accepted overwriting their web editor, + * advance to the next step and clear their editor. + */ +export async function resetCodeAndAdvanceOnboarding({ + onboardingStatus, + codeManager, + kclManager, + navigate, +}: OnboardingUtilDeps) { + // We do want to update both the state and editor here. + codeManager.updateCodeStateEditor(bracket) + codeManager.writeToFile().catch(reportRejection) + kclManager.executeCode().catch(reportRejection) + // TODO: this is not navigating to the correct `/onboarding/blah` path yet + navigate( + makeUrlPathRelative( + `${PATHS.ONBOARDING.INDEX}${makeUrlPathRelative(onboardingStatus)}` + ) + ) +} + +function hasResetReadyCode(codeManager: CodeManager) { + return ( + isKclEmptyOrOnlySettings(codeManager.code) || codeManager.code === bracket + ) +} + +export function needsToOnboard( + location: ReturnType, + onboardingStatus: OnboardingStatus +) { + return ( + !location.pathname.includes(PATHS.ONBOARDING.INDEX) && + (onboardingStatus.length === 0 || + !(onboardingStatus === 'completed' || onboardingStatus === 'dismissed')) + ) +} + +export const ONBOARDING_TOAST_ID = 'onboarding-toast' + +export function onDismissOnboardingInvite() { + settingsActor.send({ + type: 'set.app.onboardingStatus', + data: { level: 'user', value: 'dismissed' }, + }) + toast.dismiss(ONBOARDING_TOAST_ID) + toast.success( + 'Click the question mark in the lower-right corner if you ever want to do the tutorial!', + { + duration: 5_000, + } + ) +} + +export function TutorialRequestToast(props: OnboardingUtilDeps) { + async function onAccept() { + return acceptOnboarding(props) + .then(() => { + toast.dismiss(ONBOARDING_TOAST_ID) + }) + .catch((reason) => catchOnboardingWarnError(reason, props)) + } + + return ( +
+ +
+
+

Welcome to Zoo Design Studio

+

+ Would you like a tutorial to show you around the app? +

+
+
+ + Not right now + + + Get started + +
+
+
+ ) +} + +/** + * Helper function to catch the `ERROR_MUST_WARN` error from + * `acceptOnboarding` and show a warning toast. + */ +export async function catchOnboardingWarnError( + err: Error, + props: OnboardingUtilDeps +) { + if (err instanceof Error && err.message === ERROR_MUST_WARN) { + toast.success(TutorialWebConfirmationToast(props), { + id: ONBOARDING_TOAST_ID, + duration: Number.POSITIVE_INFINITY, + icon: null, + }) + } else { + toast.dismiss(ONBOARDING_TOAST_ID) + return reportRejection(err) + } +} + +export function TutorialWebConfirmationToast(props: OnboardingUtilDeps) { + function onAccept() { + toast.dismiss(ONBOARDING_TOAST_ID) + resetCodeAndAdvanceOnboarding(props) + } + + return ( +
+ +
+
+

The welcome tutorial resets your code in the browser

+

+ We see you have some of your own code written in this project. + Please save it somewhere else before continuing the onboarding. +

+
+
+ + I'll save it + + + Overwrite my code and begin + +
+
+
+ ) +} From 808ea6e23875041d3b5cf40b38d336f18d7fcf9f Mon Sep 17 00:00:00 2001 From: Frank Noirot Date: Mon, 28 Apr 2025 20:47:19 -0400 Subject: [PATCH 05/29] Wire up new utilities and toasts to UI components --- src/App.tsx | 46 ++++- src/components/HelpMenu.tsx | 183 +++++++++--------- src/components/LowerRightControls.tsx | 4 +- src/components/Settings/AllSettingsFields.tsx | 48 ++--- src/routes/Home.tsx | 2 +- 5 files changed, 160 insertions(+), 123 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 4e501a20e1b..8dfa7582bcc 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -4,6 +4,7 @@ import { useHotkeys } from 'react-hotkeys-hook' import ModalContainer from 'react-modal-promise' import { useLoaderData, + useLocation, useNavigate, useRouteLoaderData, useSearchParams, @@ -26,15 +27,20 @@ import useHotkeyWrapper from '@src/lib/hotkeyWrapper' import { isDesktop } from '@src/lib/isDesktop' import { PATHS } from '@src/lib/paths' import { takeScreenshotOfVideoStreamCanvas } from '@src/lib/screenshot' -import { sceneInfra } from '@src/lib/singletons' +import { sceneInfra, codeManager, kclManager } from '@src/lib/singletons' import { maybeWriteToDisk } from '@src/lib/telemetry' -import { type IndexLoaderData } from '@src/lib/types' +import type { IndexLoaderData } from '@src/lib/types' import { engineStreamActor, useSettings, useToken } from '@src/lib/singletons' import { commandBarActor } from '@src/lib/singletons' import { EngineStreamTransition } from '@src/machines/engineStreamMachine' import { onboardingPaths } from '@src/routes/Onboarding/paths' import { CommandBarOpenButton } from '@src/components/CommandBarOpenButton' import { ShareButton } from '@src/components/ShareButton' +import { + needsToOnboard, + ONBOARDING_TOAST_ID, + TutorialRequestToast, +} from '@src/routes/Onboarding/utils' // CYCLIC REF sceneInfra.camControls.engineStreamActor = engineStreamActor @@ -58,6 +64,7 @@ export function App() { }) }) + const location = useLocation() const navigate = useNavigate() const filePath = useAbsoluteFilePath() const { onProjectOpen } = useLspContext() @@ -66,7 +73,7 @@ export function App() { const ref = useRef(null) // Stream related refs and data - let [searchParams] = useSearchParams() + const [searchParams] = useSearchParams() const pool = searchParams.get('pool') const projectName = project?.name || null @@ -76,9 +83,10 @@ export function App() { const loaderData = useRouteLoaderData(PATHS.FILE) as IndexLoaderData const lastCommandType = commands[commands.length - 1]?.type + // Run LSP file open hook when navigating between projects or files useEffect(() => { onProjectOpen({ name: projectName, path: projectPath }, file || null) - }, [projectName, projectPath]) + }, [onProjectOpen, projectName, projectPath, file]) useHotKeyListener() @@ -132,7 +140,7 @@ export function App() { }) }, 500) } - }, [lastCommandType]) + }, [lastCommandType, loaderData?.project?.path]) useEffect(() => { // When leaving the modeling scene, cut the engine stream. @@ -141,6 +149,32 @@ export function App() { } }, []) + // Show a custom toast to users if they haven't done the onboarding + // and they're on the web + useEffect(() => { + const onboardingStatus = + settings.app.onboardingStatus.current || + settings.app.onboardingStatus.default + const needsOnboarded = needsToOnboard(location, onboardingStatus) + + if (!isDesktop() && needsOnboarded) { + toast.success( + () => + TutorialRequestToast({ + onboardingStatus: settings.app.onboardingStatus.current, + navigate, + codeManager, + kclManager, + }), + { + id: ONBOARDING_TOAST_ID, + duration: Number.POSITIVE_INFINITY, + icon: null, + } + ) + } + }, [location, settings.app.onboardingStatus, navigate]) + return (
{/* */} - + diff --git a/src/components/HelpMenu.tsx b/src/components/HelpMenu.tsx index 7274658a49e..06fc8dc692d 100644 --- a/src/components/HelpMenu.tsx +++ b/src/components/HelpMenu.tsx @@ -1,45 +1,42 @@ import { Popover } from '@headlessui/react' -import { useLocation, useNavigate } from 'react-router-dom' +import { type NavigateFunction, useLocation } from 'react-router-dom' import { CustomIcon } from '@src/components/CustomIcon' -import { useLspContext } from '@src/components/LspProvider' import Tooltip from '@src/components/Tooltip' import { useAbsoluteFilePath } from '@src/hooks/useAbsoluteFilePath' import { useMenuListener } from '@src/hooks/useMenu' -import { createAndOpenNewTutorialProject } from '@src/lib/desktopFS' import { openExternalBrowserIfDesktop } from '@src/lib/openWindow' import { PATHS } from '@src/lib/paths' -import { reportRejection } from '@src/lib/trap' -import { settingsActor } from '@src/lib/singletons' +import { codeManager, kclManager } from '@src/lib/singletons' import type { WebContentSendPayload } from '@src/menu/channels' +import { + acceptOnboarding, + catchOnboardingWarnError, +} from '@src/routes/Onboarding/utils' +import { onboardingPaths } from '@src/routes/Onboarding/paths' const HelpMenuDivider = () => (
) -export function HelpMenu(props: React.PropsWithChildren) { +export function HelpMenu({ + navigate = () => {}, +}: { + navigate?: NavigateFunction +}) { const location = useLocation() - const { onProjectOpen } = useLspContext() const filePath = useAbsoluteFilePath() - const isInProject = location.pathname.includes(PATHS.FILE) - const navigate = useNavigate() const resetOnboardingWorkflow = () => { - settingsActor.send({ - type: 'set.app.onboardingStatus', - data: { - value: '', - level: 'user', - }, - }) - if (isInProject) { - navigate(filePath + PATHS.ONBOARDING.INDEX) - } else { - createAndOpenNewTutorialProject({ - onProjectOpen, - navigate, - }).catch(reportRejection) + const props = { + onboardingStatus: onboardingPaths.INDEX, + navigate, + codeManager, + kclManager, } + acceptOnboarding(props).catch((reason) => + catchOnboardingWarnError(reason, props) + ) } const cb = (data: WebContentSendPayload) => { @@ -68,71 +65,81 @@ export function HelpMenu(props: React.PropsWithChildren) { as="ul" className="absolute right-0 left-auto flex flex-col w-64 gap-1 p-0 py-2 m-0 mb-1 text-sm border border-solid rounded shadow-lg bottom-full align-stretch text-chalkboard-10 dark:text-inherit bg-chalkboard-110 dark:bg-chalkboard-100 border-chalkboard-110 dark:border-chalkboard-80" > - - Report a bug - - - Request a feature - - - Ask the community - - - - KCL code samples - - - KCL docs - - - - Release notes - - { - const targetPath = location.pathname.includes(PATHS.FILE) - ? filePath + PATHS.SETTINGS_KEYBINDINGS - : PATHS.HOME + PATHS.SETTINGS_KEYBINDINGS - navigate(targetPath) - }} - data-testid="keybindings-button" - > - Keyboard shortcuts - - - Reset onboarding - + {({ close }) => ( + <> + + Report a bug + + + Request a feature + + + Ask the community + + + + KCL code samples + + + KCL docs + + + + Release notes + + { + const targetPath = location.pathname.includes(PATHS.FILE) + ? filePath + PATHS.SETTINGS_KEYBINDINGS + : PATHS.HOME + PATHS.SETTINGS_KEYBINDINGS + navigate(targetPath) + }} + data-testid="keybindings-button" + > + Keyboard shortcuts + + { + close() + resetOnboardingWorkflow() + }} + > + Replay onboarding tutorial + + + )} ) diff --git a/src/components/LowerRightControls.tsx b/src/components/LowerRightControls.tsx index 42d539e8ddc..2789cffd8c6 100644 --- a/src/components/LowerRightControls.tsx +++ b/src/components/LowerRightControls.tsx @@ -12,8 +12,10 @@ import { APP_VERSION, getReleaseUrl } from '@src/routes/utils' export function LowerRightControls({ children, + navigate = () => {}, }: { children?: React.ReactNode + navigate?: (path: string) => void }) { const location = useLocation() const filePath = useAbsoluteFilePath() @@ -72,7 +74,7 @@ export function LowerRightControls({ {!location.pathname.startsWith(PATHS.HOME) && ( )} - + ) diff --git a/src/components/Settings/AllSettingsFields.tsx b/src/components/Settings/AllSettingsFields.tsx index 9e50f2e8e2d..b4ef60cadf7 100644 --- a/src/components/Settings/AllSettingsFields.tsx +++ b/src/components/Settings/AllSettingsFields.tsx @@ -6,14 +6,9 @@ import { useLocation, useNavigate } from 'react-router-dom' import { Fragment } from 'react/jsx-runtime' import { ActionButton } from '@src/components/ActionButton' -import { useLspContext } from '@src/components/LspProvider' import { SettingsFieldInput } from '@src/components/Settings/SettingsFieldInput' import { SettingsSection } from '@src/components/Settings/SettingsSection' -import { useDotDotSlash } from '@src/hooks/useDotDotSlash' -import { - createAndOpenNewTutorialProject, - getSettingsFolderPaths, -} from '@src/lib/desktopFS' +import { getSettingsFolderPaths } from '@src/lib/desktopFS' import { isDesktop } from '@src/lib/isDesktop' import { openExternalBrowserIfDesktop } from '@src/lib/openWindow' import { PATHS } from '@src/lib/paths' @@ -28,9 +23,18 @@ import { } from '@src/lib/settings/settingsUtils' import { reportRejection } from '@src/lib/trap' import { toSync } from '@src/lib/utils' -import { settingsActor, useSettings } from '@src/lib/singletons' +import { + codeManager, + kclManager, + settingsActor, + useSettings, +} from '@src/lib/singletons' import { APP_VERSION, IS_NIGHTLY, getReleaseUrl } from '@src/routes/utils' -import { waitFor } from 'xstate' +import { + acceptOnboarding, + catchOnboardingWarnError, +} from '@src/routes/Onboarding/utils' +import { onboardingPaths } from '@src/routes/Onboarding/paths' interface AllSettingsFieldsProps { searchParamTab: SettingsLevel @@ -44,8 +48,6 @@ export const AllSettingsFields = forwardRef( ) => { const location = useLocation() const navigate = useNavigate() - const { onProjectOpen } = useLspContext() - const dotDotSlash = useDotDotSlash() const context = useSettings() const projectPath = useMemo(() => { @@ -63,26 +65,18 @@ export const AllSettingsFields = forwardRef( : undefined return projectPath - }, [location.pathname]) + }, [location.pathname, isFileSettings]) async function restartOnboarding() { - settingsActor.send({ - type: `set.app.onboardingStatus`, - data: { level: 'user', value: '' }, - }) - await waitFor(settingsActor, (s) => s.matches('idle'), { - timeout: 10_000, - }).catch(reportRejection) - - if (isFileSettings) { - // If we're in a project, first navigate to the onboarding start here - // so we can trigger the warning screen if necessary - navigate(dotDotSlash(1) + PATHS.ONBOARDING.INDEX) - } else { - // If we're in the global settings, create a new project and navigate - // to the onboarding start in that project - await createAndOpenNewTutorialProject({ onProjectOpen, navigate }) + const props = { + onboardingStatus: onboardingPaths.INDEX, + navigate, + codeManager, + kclManager, } + acceptOnboarding(props).catch((reason) => + catchOnboardingWarnError(reason, props) + ) } return ( diff --git a/src/routes/Home.tsx b/src/routes/Home.tsx index 5e85e597fc4..f1500dca385 100644 --- a/src/routes/Home.tsx +++ b/src/routes/Home.tsx @@ -324,7 +324,7 @@ const Home = () => { sort={sort} className="flex-1 col-start-2 -col-end-1 overflow-y-auto pr-2 pb-24" /> - +
) From 855fe8d0ef23f46f86d2024944ea95d8bfe582b8 Mon Sep 17 00:00:00 2001 From: Frank Noirot Date: Mon, 28 Apr 2025 20:49:08 -0400 Subject: [PATCH 06/29] Add home sidebar buttons for tutorial flow --- src/components/CustomIcon.tsx | 8 ++++++ src/routes/Home.tsx | 53 +++++++++++++++++++++++++++++++++-- 2 files changed, 59 insertions(+), 2 deletions(-) diff --git a/src/components/CustomIcon.tsx b/src/components/CustomIcon.tsx index c69f7fe4c4a..0d918aef306 100644 --- a/src/components/CustomIcon.tsx +++ b/src/components/CustomIcon.tsx @@ -854,6 +854,14 @@ const CustomIconMap = { /> ), + play: ( + + + + ), rotate: ( { }) }) + const location = useLocation() const navigate = useNavigate() const settings = useSettings() + const onboardingStatus = settings.app.onboardingStatus.current // Menu listeners const cb = (data: WebContentSendPayload) => { @@ -204,6 +217,42 @@ const Home = () => { />
    + {needsToOnboard(location, onboardingStatus) && ( +
  • + { + acceptOnboarding({ + onboardingStatus, + navigate, + codeManager, + kclManager, + }).catch(reportRejection) + }} + className={`${sidebarButtonClasses} !text-primary flex-1`} + iconStart={{ + icon: 'play', + bgClassName: '!bg-primary rounded-sm', + iconClassName: '!text-white', + }} + data-testid="home-tutorial" + > + {onboardingStatus === '' ? 'Start' : 'Continue'} tutorial + + +
  • + )}
  • Date: Mon, 28 Apr 2025 20:49:42 -0400 Subject: [PATCH 07/29] Rename menu item --- e2e/playwright/native-file-menu.spec.ts | 8 +-- e2e/playwright/onboarding-tests.spec.ts | 86 +++++++------------------ src/components/HelpMenu.tsx | 2 +- src/menu/channels.ts | 2 +- src/menu/helpRole.ts | 6 +- src/menu/roles.ts | 2 +- src/routes/SignIn.tsx | 4 +- 7 files changed, 35 insertions(+), 75 deletions(-) diff --git a/e2e/playwright/native-file-menu.spec.ts b/e2e/playwright/native-file-menu.spec.ts index d9967f06efa..5c6dc4d725d 100644 --- a/e2e/playwright/native-file-menu.spec.ts +++ b/e2e/playwright/native-file-menu.spec.ts @@ -450,7 +450,7 @@ test.describe( ) await expect(actual).toBeVisible() }) - test('Home.Help.Reset onboarding', async ({ + test('Home.Help.Replay onboarding tutorial', async ({ tronApp, cmdBar, page, @@ -464,7 +464,7 @@ test.describe( await tronApp.electron.evaluate(async ({ app }) => { if (!app || !app.applicationMenu) return false const menu = app.applicationMenu.getMenuItemById( - 'Help.Reset onboarding' + 'Help.Replay onboarding tutorial' ) if (!menu) { return false @@ -2339,7 +2339,7 @@ test.describe( await scene.connectionEstablished() await expect(toolbar.startSketchBtn).toBeVisible() }) - test('Modeling.Help.Reset onboarding', async ({ + test('Modeling.Help.Replay onboarding tutorial', async ({ tronApp, cmdBar, page, @@ -2358,7 +2358,7 @@ test.describe( await tronApp.electron.evaluate(async ({ app }) => { if (!app || !app.applicationMenu) fail() const menu = app.applicationMenu.getMenuItemById( - 'Help.Reset onboarding' + 'Help.Replay onboarding tutorial' ) if (!menu) fail() menu.click() diff --git a/e2e/playwright/onboarding-tests.spec.ts b/e2e/playwright/onboarding-tests.spec.ts index a2e776c5617..001327f7a88 100644 --- a/e2e/playwright/onboarding-tests.spec.ts +++ b/e2e/playwright/onboarding-tests.spec.ts @@ -40,6 +40,11 @@ test.describe('Onboarding tests', () => { await page.setBodyDimensions({ width: 1200, height: 500 }) await homePage.goToModelingScene() + await test.step('Ensure the onboarding request toast appears', async () => { + await expect(page.getByTestId('onboarding-toast')).toBeVisible() + await page.getByTestId('onboarding-next').click() + }) + // Test that the onboarding pane loaded await expect(page.getByText('Welcome to Design Studio! This')).toBeVisible() @@ -82,6 +87,11 @@ test.describe('Onboarding tests', () => { }) await test.step(`Ensure we see the onboarding stuff`, async () => { + await test.step('Ensure the onboarding request toast appears', async () => { + await expect(page.getByTestId('onboarding-toast')).toBeVisible() + await page.getByTestId('onboarding-next').click() + }) + // Test that the onboarding pane loaded await expect( page.getByText('Welcome to Design Studio! This') @@ -135,7 +145,9 @@ test.describe('Onboarding tests', () => { await replayButton.click() // Ensure we see the warning, and that the code has not yet updated - await expect(page.getByText('Would you like to create')).toBeVisible() + await expect( + page.getByText('Start tutorial in a new project?') + ).toBeVisible() await expect(page.locator('.cm-content')).toHaveText(initialCode) const nextButton = page.getByTestId('onboarding-next') @@ -183,12 +195,17 @@ test.describe('Onboarding tests', () => { await page.setBodyDimensions({ width: 1200, height: 1080 }) await homePage.goToModelingScene() - // Test that the onboarding pane loaded - await expect(page.getByText('Welcome to Design Studio! This')).toBeVisible() - const nextButton = page.getByTestId('onboarding-next') const prevButton = page.getByTestId('onboarding-prev') + await test.step('Ensure the onboarding request toast appears', async () => { + await expect(page.getByTestId('onboarding-toast')).toBeVisible() + await nextButton.click() + }) + + // Test that the onboarding pane loaded + await expect(page.getByText('Welcome to Design Studio! This')).toBeVisible() + while ((await nextButton.innerText()) !== 'Finish') { await nextButton.hover() await nextButton.click() @@ -208,65 +225,6 @@ test.describe('Onboarding tests', () => { await expect.poll(() => page.url()).not.toContain('/onboarding') }) - test('Onboarding redirects and code updating', async ({ - context, - page, - homePage, - tronApp, - }) => { - if (!tronApp) { - fail() - } - await tronApp.cleanProjectDir({ - app: { - onboarding_status: '/export', - }, - }) - - const originalCode = 'sigmaAllow = 15000' - - // Override beforeEach test setup - await context.addInitScript( - async ({ settingsKey, settings, code }) => { - // Give some initial code, so we can test that it's cleared - localStorage.setItem('persistCode', code) - localStorage.setItem(settingsKey, settings) - }, - { - settingsKey: TEST_SETTINGS_KEY, - settings: settingsToToml({ - settings: TEST_SETTINGS_ONBOARDING_EXPORT, - }), - code: originalCode, - } - ) - - await page.setBodyDimensions({ width: 1200, height: 500 }) - await homePage.goToModelingScene() - - // Test that the redirect happened - await expect.poll(() => page.url()).toContain('/onboarding/export') - - // Test that you come back to this page when you refresh - await page.reload() - await expect.poll(() => page.url()).toContain('/onboarding/export') - - // Test that the code changes when you advance to the next step - await page.getByTestId('onboarding-next').hover() - await page.getByTestId('onboarding-next').click() - - // Test that the onboarding pane loaded - const title = page.locator('[data-testid="onboarding-content"]') - await expect(title).toBeAttached() - - await expect(page.locator('.cm-content')).not.toHaveText(originalCode) - - // Test that the code is not empty when you click on the next step - await page.locator('[data-testid="onboarding-next"]').hover() - await page.locator('[data-testid="onboarding-next"]').click() - await expect(page.locator('.cm-content')).toHaveText(/.+/) - }) - test('Onboarding code gets reset to demo on Interactive Numbers step', async ({ page, homePage, @@ -488,7 +446,7 @@ test('Restarting onboarding on desktop takes one attempt', async ({ name: 'Help and resources', }) const restartOnboardingButton = page.getByRole('button', { - name: 'Reset onboarding', + name: 'Replay onboarding tutorial', }) const nextButton = page.getByTestId('onboarding-next') diff --git a/src/components/HelpMenu.tsx b/src/components/HelpMenu.tsx index 06fc8dc692d..09ff53c9b63 100644 --- a/src/components/HelpMenu.tsx +++ b/src/components/HelpMenu.tsx @@ -40,7 +40,7 @@ export function HelpMenu({ } const cb = (data: WebContentSendPayload) => { - if (data.menuLabel === 'Help.Reset onboarding') { + if (data.menuLabel === 'Help.Replay onboarding tutorial') { resetOnboardingWorkflow() } } diff --git a/src/menu/channels.ts b/src/menu/channels.ts index f9ba9f5d8a4..6312fa8db8b 100644 --- a/src/menu/channels.ts +++ b/src/menu/channels.ts @@ -6,7 +6,7 @@ import type { Channel } from '@src/channels' export type MenuLabels = | 'Help.Command Palette...' | 'Help.Report a bug' - | 'Help.Reset onboarding' + | 'Help.Replay onboarding tutorial' | 'Edit.Rename project' | 'Edit.Delete project' | 'Edit.Change project directory' diff --git a/src/menu/helpRole.ts b/src/menu/helpRole.ts index de7a1c1af87..b6941994a77 100644 --- a/src/menu/helpRole.ts +++ b/src/menu/helpRole.ts @@ -84,11 +84,11 @@ export const helpRole = ( }, { type: 'separator' }, { - id: 'Help.Reset onboarding', - label: 'Reset onboarding', + id: 'Help.Replay onboarding tutorial', + label: 'Replay onboarding tutorial', click: () => { typeSafeWebContentsSend(mainWindow, 'menu-action-clicked', { - menuLabel: 'Help.Reset onboarding', + menuLabel: 'Help.Replay onboarding tutorial', }) }, }, diff --git a/src/menu/roles.ts b/src/menu/roles.ts index 8b51ad1dafa..5ead00a0f14 100644 --- a/src/menu/roles.ts +++ b/src/menu/roles.ts @@ -45,7 +45,7 @@ type HelpRoleLabel = | 'Ask the community discourse' | 'KCL code samples' | 'KCL docs' - | 'Reset onboarding' + | 'Replay onboarding tutorial' | 'Show release notes' | 'Manage account' | 'Get started with Text-to-CAD' diff --git a/src/routes/SignIn.tsx b/src/routes/SignIn.tsx index 67e48f30f98..fd14429ef31 100644 --- a/src/routes/SignIn.tsx +++ b/src/routes/SignIn.tsx @@ -26,7 +26,9 @@ const SignIn = () => { if (isDesktop()) { window.electron.createFallbackMenu().catch(reportRejection) // Disable these since they cannot be accessed within the sign in page. - window.electron.disableMenu('Help.Reset onboarding').catch(reportRejection) + window.electron + .disableMenu('Help.Replay onboarding tutorial') + .catch(reportRejection) window.electron.disableMenu('Help.Show all commands').catch(reportRejection) } From 8180036606dd005d83119d2d61cbdfefbc5abd3c Mon Sep 17 00:00:00 2001 From: Frank Noirot Date: Tue, 29 Apr 2025 08:07:35 -0400 Subject: [PATCH 08/29] Add flex-1 so home-layout fills available space --- src/routes/Home.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/Home.tsx b/src/routes/Home.tsx index c7ffac9bf08..d4fde7ce9f1 100644 --- a/src/routes/Home.tsx +++ b/src/routes/Home.tsx @@ -206,7 +206,7 @@ const Home = () => { return (
    -
    +
    Date: Tue, 29 Apr 2025 08:14:11 -0400 Subject: [PATCH 09/29] Remove onboarding avatar tests, they are becoming irrelevant --- e2e/playwright/onboarding-tests.spec.ts | 135 ------------------------ 1 file changed, 135 deletions(-) diff --git a/e2e/playwright/onboarding-tests.spec.ts b/e2e/playwright/onboarding-tests.spec.ts index 001327f7a88..621cf96088c 100644 --- a/e2e/playwright/onboarding-tests.spec.ts +++ b/e2e/playwright/onboarding-tests.spec.ts @@ -277,141 +277,6 @@ test.describe('Onboarding tests', () => { timeout: 10_000, }) }) - - // (lee) The two avatar tests are weird because even on main, we don't have - // anything to do with the avatar inside the onboarding test. Due to the - // low impact of an avatar not showing I'm changing this to fixme. - test('Avatar text updates depending on image load success', async ({ - context, - page, - toolbar, - homePage, - tronApp, - }) => { - if (!tronApp) { - fail() - } - - await tronApp.cleanProjectDir({ - app: { - onboarding_status: '', - }, - }) - - // Override beforeEach test setup - await context.addInitScript( - async ({ settingsKey, settings }) => { - localStorage.setItem(settingsKey, settings) - }, - { - settingsKey: TEST_SETTINGS_KEY, - settings: settingsToToml({ - settings: TEST_SETTINGS_ONBOARDING_USER_MENU, - }), - } - ) - - await page.setBodyDimensions({ width: 1200, height: 500 }) - await homePage.goToModelingScene() - - // Test that the text in this step is correct - const avatarLocator = toolbar.userSidebarButton.locator('img') - const onboardingOverlayLocator = page - .getByTestId('onboarding-content') - .locator('div') - .nth(1) - - // Expect the avatar to be visible and for the text to reference it - await expect(avatarLocator).toBeVisible() - await expect(onboardingOverlayLocator).toBeVisible() - await expect(onboardingOverlayLocator).toContainText('your avatar') - - // This is to force the avatar to 404. - // For our test image (only triggers locally. on CI, it's Kurt's / - // gravatar image ) - await page.route('/cat.jpg', async (route) => { - await route.fulfill({ - status: 404, - contentType: 'text/plain', - body: 'Not Found!', - }) - }) - - // 404 the CI avatar image - await page.route('https://lh3.googleusercontent.com/**', async (route) => { - await route.fulfill({ - status: 404, - contentType: 'text/plain', - body: 'Not Found!', - }) - }) - - await page.reload({ waitUntil: 'domcontentloaded' }) - - // Now expect the text to be different - await expect(avatarLocator).not.toBeVisible() - await expect(onboardingOverlayLocator).toBeVisible() - await expect(onboardingOverlayLocator).toContainText('the menu button') - }) - - test("Avatar text doesn't mention avatar when no avatar", async ({ - context, - page, - toolbar, - homePage, - tronApp, - }) => { - if (!tronApp) { - fail() - } - - await tronApp.cleanProjectDir({ - app: { - onboarding_status: '', - }, - }) - // Override beforeEach test setup - await context.addInitScript( - async ({ settingsKey, settings }) => { - localStorage.setItem(settingsKey, settings) - localStorage.setItem('FORCE_NO_IMAGE', 'FORCE_NO_IMAGE') - }, - { - settingsKey: TEST_SETTINGS_KEY, - settings: settingsToToml({ - settings: TEST_SETTINGS_ONBOARDING_USER_MENU, - }), - } - ) - - await page.setBodyDimensions({ width: 1200, height: 500 }) - await homePage.goToModelingScene() - - // Test that the text in this step is correct - const sidebar = toolbar.userSidebarButton - const avatar = sidebar.locator('img') - const onboardingOverlayLocator = page - .getByTestId('onboarding-content') - .locator('div') - .nth(1) - - // Expect the avatar to be visible and for the text to reference it - await expect(avatar).not.toBeVisible() - await expect(onboardingOverlayLocator).toBeVisible() - await expect(onboardingOverlayLocator).toContainText('the menu button') - - // Test we mention what else is in this menu for https://github.com/KittyCAD/modeling-app/issues/2939 - // which doesn't deserver its own full test spun up - const userMenuFeatures = [ - 'manage your account', - 'report a bug', - 'request a feature', - 'sign out', - ] - for (const feature of userMenuFeatures) { - await expect(onboardingOverlayLocator).toContainText(feature) - } - }) }) test('Restarting onboarding on desktop takes one attempt', async ({ From 4bbffd0f3e6eab766131db8bf788a65fcf5ff3f9 Mon Sep 17 00:00:00 2001 From: Frank Noirot Date: Tue, 29 Apr 2025 09:32:41 -0400 Subject: [PATCH 10/29] Consolidate onboarding tests to one longer one and update it to not use pixel color checks, and use fixtures. --- e2e/playwright/fixtures/homePageFixture.ts | 2 + e2e/playwright/fixtures/toolbarFixture.ts | 4 + e2e/playwright/onboarding-tests.spec.ts | 479 ++++++--------------- src/routes/Home.tsx | 2 +- 4 files changed, 143 insertions(+), 344 deletions(-) diff --git a/e2e/playwright/fixtures/homePageFixture.ts b/e2e/playwright/fixtures/homePageFixture.ts index 4878e5fbb93..43765161682 100644 --- a/e2e/playwright/fixtures/homePageFixture.ts +++ b/e2e/playwright/fixtures/homePageFixture.ts @@ -24,6 +24,7 @@ export class HomePageFixture { projectTextName!: Locator sortByDateBtn!: Locator sortByNameBtn!: Locator + tutorialBtn!: Locator constructor(page: Page) { this.page = page @@ -43,6 +44,7 @@ export class HomePageFixture { this.sortByDateBtn = this.page.getByTestId('home-sort-by-modified') this.sortByNameBtn = this.page.getByTestId('home-sort-by-name') + this.tutorialBtn = this.page.getByTestId('home-tutorial-button') } private _serialiseSortBy = async (): Promise< diff --git a/e2e/playwright/fixtures/toolbarFixture.ts b/e2e/playwright/fixtures/toolbarFixture.ts index ed7bffe79c9..4962f1ee2da 100644 --- a/e2e/playwright/fixtures/toolbarFixture.ts +++ b/e2e/playwright/fixtures/toolbarFixture.ts @@ -17,6 +17,8 @@ type LengthUnitLabel = (typeof baseUnitLabels)[keyof typeof baseUnitLabels] export class ToolbarFixture { public page: Page + projectName: Locator + fileName: Locator extrudeButton!: Locator loftButton!: Locator sweepButton!: Locator @@ -53,6 +55,8 @@ export class ToolbarFixture { constructor(page: Page) { this.page = page + this.projectName = page.getByTestId('app-header-project-name') + this.fileName = page.getByTestId('app-header-file-name') this.extrudeButton = page.getByTestId('extrude') this.loftButton = page.getByTestId('loft') this.sweepButton = page.getByTestId('sweep') diff --git a/e2e/playwright/onboarding-tests.spec.ts b/e2e/playwright/onboarding-tests.spec.ts index 621cf96088c..49fbd892ba3 100644 --- a/e2e/playwright/onboarding-tests.spec.ts +++ b/e2e/playwright/onboarding-tests.spec.ts @@ -1,383 +1,176 @@ -import { join } from 'path' -import { bracket } from '@e2e/playwright/fixtures/bracket' -import { onboardingPaths } from '@src/routes/Onboarding/paths' -import fsp from 'fs/promises' - -import { expectPixelColor } from '@e2e/playwright/fixtures/sceneFixture' -import { - TEST_SETTINGS_KEY, - TEST_SETTINGS_ONBOARDING_EXPORT, - TEST_SETTINGS_ONBOARDING_START, - TEST_SETTINGS_ONBOARDING_USER_MENU, -} from '@e2e/playwright/storageStates' -import { - createProject, - executorInputPath, - getUtils, - settingsToToml, -} from '@e2e/playwright/test-utils' import { expect, test } from '@e2e/playwright/zoo-test' -// Because our default test settings have the onboardingStatus set to 'dismissed', -// we must set it to empty for the tests where we want to see the onboarding immediately. - test.describe('Onboarding tests', () => { - test('Onboarding code is shown in the editor', async ({ + test('Desktop onboarding flow works', async ({ page, homePage, - tronApp, - }) => { - if (!tronApp) { - fail() - } - await tronApp.cleanProjectDir({ - app: { - onboarding_status: '', - }, - }) - - const u = await getUtils(page) - await page.setBodyDimensions({ width: 1200, height: 500 }) - await homePage.goToModelingScene() - - await test.step('Ensure the onboarding request toast appears', async () => { - await expect(page.getByTestId('onboarding-toast')).toBeVisible() - await page.getByTestId('onboarding-next').click() - }) - - // Test that the onboarding pane loaded - await expect(page.getByText('Welcome to Design Studio! This')).toBeVisible() - - // Test that the onboarding pane loaded - await expect(page.getByText('Welcome to Design Studio! This')).toBeVisible() - - // *and* that the code is shown in the editor - await expect(page.locator('.cm-content')).toContainText('// Shelf Bracket') - - // Make sure the model loaded - const XYPlanePoint = { x: 774, y: 116 } as const - const modelColor: [number, number, number] = [45, 45, 45] - await page.mouse.move(XYPlanePoint.x, XYPlanePoint.y) - expect(await u.getGreatestPixDiff(XYPlanePoint, modelColor)).toBeLessThan(8) - }) - - test( - 'Desktop: fresh onboarding executes and loads', - { - tag: '@electron', - }, - async ({ page, tronApp, scene }) => { - if (!tronApp) { - fail() - } - await tronApp.cleanProjectDir({ - app: { - onboarding_status: '', - }, - }) - - const viewportSize = { width: 1200, height: 500 } - await page.setBodyDimensions(viewportSize) - - await test.step(`Create a project and open to the onboarding`, async () => { - await createProject({ name: 'project-link', page }) - await test.step(`Ensure the engine connection works by testing the sketch button`, async () => { - await scene.connectionEstablished() - }) - }) - - await test.step(`Ensure we see the onboarding stuff`, async () => { - await test.step('Ensure the onboarding request toast appears', async () => { - await expect(page.getByTestId('onboarding-toast')).toBeVisible() - await page.getByTestId('onboarding-next').click() - }) - - // Test that the onboarding pane loaded - await expect( - page.getByText('Welcome to Design Studio! This') - ).toBeVisible() - - // *and* that the code is shown in the editor - await expect(page.locator('.cm-content')).toContainText( - '// Shelf Bracket' - ) - - // TODO: jess make less shit - // Make sure the model loaded - //const XYPlanePoint = { x: 986, y: 522 } as const - //const modelColor: [number, number, number] = [76, 76, 76] - //await page.mouse.move(XYPlanePoint.x, XYPlanePoint.y) - - //await expectPixelColor(page, modelColor, XYPlanePoint, 8) - }) - } - ) - - test('Code resets after confirmation', async ({ - page, - homePage, - tronApp, + toolbar, + editor, scene, - }) => { - if (!tronApp) { - fail() - } - await tronApp.cleanProjectDir() - - const initialCode = `sketch001 = startSketchOn(XZ)` - - // Load the page up with some code so we see the confirmation warning - // when we go to replay onboarding - await page.addInitScript((code) => { - localStorage.setItem('persistCode', code) - }, initialCode) - - await page.setBodyDimensions({ width: 1200, height: 500 }) - await homePage.goToModelingScene() - await scene.connectionEstablished() - - // Replay the onboarding - await page.getByRole('link', { name: 'Settings' }).last().click() - const replayButton = page.getByRole('button', { - name: 'Replay onboarding', - }) - await expect(replayButton).toBeVisible() - await replayButton.click() - - // Ensure we see the warning, and that the code has not yet updated - await expect( - page.getByText('Start tutorial in a new project?') - ).toBeVisible() - await expect(page.locator('.cm-content')).toHaveText(initialCode) - - const nextButton = page.getByTestId('onboarding-next') - await nextButton.hover() - await nextButton.click() - - // Ensure we see the introduction and that the code has been reset - await expect(page.getByText('Welcome to Design Studio!')).toBeVisible() - await expect(page.locator('.cm-content')).toContainText('// Shelf Bracket') - - // There used to be old code here that checked if we stored the reset - // code into localStorage but that isn't the case on desktop. It gets - // saved to the file system, which we have other tests for. - }) - - test('Click through each onboarding step and back', async ({ - context, - page, - homePage, tronApp, }) => { if (!tronApp) { fail() } + + // Because our default test settings have the onboardingStatus set to 'dismissed', + // we must set it to empty for the tests where we want to see the onboarding UI. await tronApp.cleanProjectDir({ app: { onboarding_status: '', }, }) - // Override beforeEach test setup - await context.addInitScript( - async ({ settingsKey, settings }) => { - // Give no initial code, so that the onboarding start is shown immediately - localStorage.setItem('persistCode', '') - localStorage.setItem(settingsKey, settings) - }, - { - settingsKey: TEST_SETTINGS_KEY, - settings: settingsToToml({ - settings: TEST_SETTINGS_ONBOARDING_START, - }), - } - ) - - await page.setBodyDimensions({ width: 1200, height: 1080 }) - await homePage.goToModelingScene() + const bracketComment = '// Shelf Bracket' + const tutorialWelcomHeading = page.getByText( + 'Welcome to Design Studio! This' + ) const nextButton = page.getByTestId('onboarding-next') const prevButton = page.getByTestId('onboarding-prev') - - await test.step('Ensure the onboarding request toast appears', async () => { - await expect(page.getByTestId('onboarding-toast')).toBeVisible() - await nextButton.click() + const userMenuButton = toolbar.userSidebarButton + const userMenuSettingsButton = page.getByRole('button', { + name: 'User settings', }) - - // Test that the onboarding pane loaded - await expect(page.getByText('Welcome to Design Studio! This')).toBeVisible() - - while ((await nextButton.innerText()) !== 'Finish') { - 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() - await expect.poll(() => page.url()).not.toContain('/onboarding') - }) - - test('Onboarding code gets reset to demo on Interactive Numbers step', async ({ - page, - homePage, - tronApp, - editor, - toolbar, - }) => { - if (!tronApp) { - fail() - } - await tronApp.cleanProjectDir({ - app: { - onboarding_status: '/parametric-modeling', - }, + const settingsHeading = page.getByRole('heading', { + name: 'Settings', + exact: true, }) - - const badCode = `// This is bad code we shouldn't see` - - await page.setBodyDimensions({ width: 1200, height: 1080 }) - await homePage.goToModelingScene() - - await expect - .poll(() => page.url()) - .toContain(onboardingPaths.PARAMETRIC_MODELING) - - // Check the code got reset on load - await toolbar.openPane('code') - await editor.expectEditor.toContain(bracket, { - shouldNormalise: true, - timeout: 10_000, + const restartOnboardingSettingsButton = page.getByRole('button', { + name: 'Replay onboarding', }) - - // Mess with the code again - await editor.replaceCode('', badCode) - await editor.expectEditor.toContain(badCode, { - shouldNormalise: true, - timeout: 10_000, + const helpMenuButton = page.getByRole('button', { + name: 'Help and resources', }) - - // Click to the next step - await page.locator('[data-testid="onboarding-next"]').hover() - await page.locator('[data-testid="onboarding-next"]').click() - await page.waitForURL('**' + onboardingPaths.INTERACTIVE_NUMBERS, { - waitUntil: 'domcontentloaded', + const helpMenuRestartOnboardingButton = page.getByRole('button', { + name: 'Replay onboarding tutorial', }) + const postDismissToast = page.getByText( + 'Click the question mark in the lower-right corner if you ever want to redo the tutorial!' + ) - // Check that the code has been reset - await editor.expectEditor.toContain(bracket, { - shouldNormalise: true, - timeout: 10_000, + await test.step('Test initial home page view, showing a tutorial button', async () => { + await expect(homePage.tutorialBtn).toBeVisible() + await homePage.expectState({ + projectCards: [], + sortBy: 'last-modified-desc', + }) }) - }) -}) -test('Restarting onboarding on desktop takes one attempt', async ({ - context, - page, - toolbar, - tronApp, -}) => { - if (!tronApp) { - fail() - } - - await tronApp.cleanProjectDir({ - app: { - onboarding_status: 'dismissed', - }, - }) + await test.step('Create a blank project and verify no onboarding chrome is shown', async () => { + await homePage.goToModelingScene() + await expect(toolbar.projectName).toContainText('testDefault') + await expect(tutorialWelcomHeading).not.toBeVisible() + await editor.expectEditor.toContain('@settings(defaultLengthUnit = in)', { + shouldNormalise: true, + }) + await scene.connectionEstablished() + await expect(toolbar.startSketchBtn).toBeEnabled({ timeout: 15_000 }) + }) - await context.folderSetupFn(async (dir) => { - const routerTemplateDir = join(dir, 'router-template-slate') - await fsp.mkdir(routerTemplateDir, { recursive: true }) - await fsp.copyFile( - executorInputPath('router-template-slate.kcl'), - join(routerTemplateDir, 'main.kcl') - ) - }) + await test.step('Go home and verify we still see the tutorial button, then begin it.', async () => { + await toolbar.logoLink.click() + await expect(homePage.tutorialBtn).toBeVisible() + await homePage.expectState({ + projectCards: [ + { + title: 'testDefault', + fileCount: 1, + }, + ], + sortBy: 'last-modified-desc', + }) + await homePage.tutorialBtn.click() + }) - // Our constants - const u = await getUtils(page) - const projectCard = page.getByText('router-template-slate') - const helpMenuButton = page.getByRole('button', { - name: 'Help and resources', - }) - const restartOnboardingButton = page.getByRole('button', { - name: 'Replay onboarding tutorial', - }) - const nextButton = page.getByTestId('onboarding-next') + // This is web-only. + // TODO: write a new test just for the onboarding in browser + // await test.step('Ensure the onboarding request toast appears', async () => { + // await expect(page.getByTestId('onboarding-toast')).toBeVisible() + // await page.getByTestId('onboarding-next').click() + // }) + + await test.step('Ensure we see the welcome screen in a new project', async () => { + await expect(toolbar.projectName).toContainText('Tutorial Project 00') + await expect(tutorialWelcomHeading).toBeVisible() + await editor.expectEditor.toContain(bracketComment) + await scene.connectionEstablished() + await expect(toolbar.startSketchBtn).toBeEnabled({ timeout: 15_000 }) + }) - const tutorialProjectIndicator = page - .getByTestId('project-sidebar-toggle') - .filter({ hasText: 'Tutorial Project 00' }) - const tutorialModalText = page.getByText('Welcome to Design Studio!') - const tutorialDismissButton = page.getByRole('button', { name: 'Dismiss' }) - const userMenuButton = toolbar.userSidebarButton - const userMenuSettingsButton = page.getByRole('button', { - name: 'User settings', - }) - const settingsHeading = page.getByRole('heading', { - name: 'Settings', - exact: true, - }) - const restartOnboardingSettingsButton = page.getByRole('button', { - name: 'Replay onboarding', - }) + await test.step('Test the clicking through the onboarding flow', async () => { + await test.step('Going forward', async () => { + while ((await nextButton.innerText()) !== 'Finish') { + await nextButton.hover() + await nextButton.click() + } + }) - await test.step('Navigate into project', async () => { - await expect(page.getByRole('heading', { name: 'Projects' })).toBeVisible() - await expect(projectCard).toBeVisible() - await projectCard.click() - await u.waitForPageLoad() - }) + await test.step('Going backward', async () => { + while ((await prevButton.innerText()) !== 'Dismiss') { + await prevButton.hover() + await prevButton.click() + } + }) - await test.step('Restart the onboarding from help menu', async () => { - await helpMenuButton.click() - await restartOnboardingButton.click() + // Dismiss the onboarding + await test.step('Dismiss the onboarding', async () => { + await prevButton.hover() + await prevButton.click() + await expect(page.getByTestId('onboarding-content')).not.toBeVisible() + await expect(postDismissToast).toBeVisible() + await expect.poll(() => page.url()).not.toContain('/onboarding') + }) + }) - await nextButton.hover() - await nextButton.click() - }) + await test.step('Resetting onboarding from inside project should always make a new one', async () => { + await test.step('Reset onboarding from settings', async () => { + await userMenuButton.click() + await userMenuSettingsButton.click() + await expect(settingsHeading).toBeVisible() + await expect(restartOnboardingSettingsButton).toBeVisible() + await restartOnboardingSettingsButton.click() + }) - await test.step('Confirm that the onboarding has restarted', async () => { - await expect(tutorialProjectIndicator).toBeVisible() - await expect(tutorialModalText).toBeVisible() - // Make sure the model loaded - const XYPlanePoint = { x: 988, y: 523 } as const - const modelColor: [number, number, number] = [76, 76, 76] + await test.step('Makes a new project', async () => { + await expect(toolbar.projectName).toContainText('Tutorial Project 01') + await expect(tutorialWelcomHeading).toBeVisible() + await editor.expectEditor.toContain(bracketComment) + await scene.connectionEstablished() + await expect(toolbar.startSketchBtn).toBeEnabled({ timeout: 15_000 }) + }) - await page.mouse.move(XYPlanePoint.x, XYPlanePoint.y) - await expectPixelColor(page, modelColor, XYPlanePoint, 8) - await tutorialDismissButton.click() - // Make sure model still there. - await expectPixelColor(page, modelColor, XYPlanePoint, 8) - }) + await test.step('Dismiss the onboarding', async () => { + await postDismissToast.waitFor({ state: 'detached' }) + await page.keyboard.press('Escape') + await expect(postDismissToast).toBeVisible() + await expect(page.getByTestId('onboarding-content')).not.toBeVisible() + await expect.poll(() => page.url()).not.toContain('/onboarding') + }) + }) - await test.step('Clear code and restart onboarding from settings', async () => { - await u.openKclCodePanel() - await expect(u.codeLocator).toContainText('// Shelf Bracket') - await u.codeLocator.selectText() - await u.codeLocator.fill('') + await test.step('Resetting onboarding from home help menu makes a new project', async () => { + await test.step('Go home and reset onboarding from lower-right help menu', async () => { + await toolbar.logoLink.click() + await expect(homePage.tutorialBtn).not.toBeVisible() + await homePage.expectState({ + projectCards: [ + { title: 'Tutorial Project 01', fileCount: 1 }, + { title: 'Tutorial Project 00', fileCount: 1 }, + { title: 'testDefault', fileCount: 1 }, + ], + sortBy: 'last-modified-desc', + }) + await helpMenuButton.click() + await helpMenuRestartOnboardingButton.click() + }) - await test.step('Navigate to settings', async () => { - await userMenuButton.click() - await userMenuSettingsButton.click() - await expect(settingsHeading).toBeVisible() - await expect(restartOnboardingSettingsButton).toBeVisible() + await test.step('Makes a new project', async () => { + await expect(toolbar.projectName).toContainText('Tutorial Project 02') + await expect(tutorialWelcomHeading).toBeVisible() + await editor.expectEditor.toContain(bracketComment) + await scene.connectionEstablished() + await expect(toolbar.startSketchBtn).toBeEnabled({ timeout: 15_000 }) + }) }) - - await restartOnboardingSettingsButton.click() - // Since the code is empty, we should not see the confirmation dialog - await expect(nextButton).not.toBeVisible() - await expect(tutorialProjectIndicator).toBeVisible() - await expect(tutorialModalText).toBeVisible() }) }) diff --git a/src/routes/Home.tsx b/src/routes/Home.tsx index d4fde7ce9f1..38ef49357e9 100644 --- a/src/routes/Home.tsx +++ b/src/routes/Home.tsx @@ -235,7 +235,7 @@ const Home = () => { bgClassName: '!bg-primary rounded-sm', iconClassName: '!text-white', }} - data-testid="home-tutorial" + data-testid="home-tutorial-button" > {onboardingStatus === '' ? 'Start' : 'Continue'} tutorial From 1237d38eebef5d05cd30317b6563eeb82d94a3a6 Mon Sep 17 00:00:00 2001 From: Frank Noirot Date: Tue, 29 Apr 2025 09:52:06 -0400 Subject: [PATCH 11/29] Shorten warning toast button text --- src/routes/Onboarding/utils.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/Onboarding/utils.tsx b/src/routes/Onboarding/utils.tsx index 31a95a6b5c7..c749f9c672e 100644 --- a/src/routes/Onboarding/utils.tsx +++ b/src/routes/Onboarding/utils.tsx @@ -399,7 +399,7 @@ export function TutorialWebConfirmationToast(props: OnboardingUtilDeps) { name="accept" onClick={onAccept} > - Overwrite my code and begin + Overwrite and begin
    From dd05756b2a188231bc8e34d8eb0ef85c1bf54ae4 Mon Sep 17 00:00:00 2001 From: Frank Noirot Date: Tue, 29 Apr 2025 10:52:27 -0400 Subject: [PATCH 12/29] tsc, lint, and circular deps --- src/components/LowerRightControls.tsx | 5 +- src/components/RouteProvider.tsx | 4 +- src/lib/desktopFS.ts | 68 +-------- src/routes/Home.tsx | 2 +- src/routes/Onboarding/Camera.tsx | 7 +- src/routes/Onboarding/CmdK.tsx | 8 +- src/routes/Onboarding/CodeEditor.tsx | 3 +- src/routes/Onboarding/Export.tsx | 7 +- src/routes/Onboarding/FutureWork.tsx | 9 +- src/routes/Onboarding/InteractiveNumbers.tsx | 3 +- src/routes/Onboarding/Introduction.tsx | 8 +- src/routes/Onboarding/ParametricModeling.tsx | 8 +- src/routes/Onboarding/ProjectMenu.tsx | 7 +- src/routes/Onboarding/Sketching.tsx | 8 +- src/routes/Onboarding/Streaming.tsx | 7 +- src/routes/Onboarding/UserMenu.tsx | 8 +- src/routes/Onboarding/index.tsx | 3 +- src/routes/Onboarding/utils.tsx | 138 +++++++++++++++---- 18 files changed, 159 insertions(+), 144 deletions(-) diff --git a/src/components/LowerRightControls.tsx b/src/components/LowerRightControls.tsx index 2789cffd8c6..219ae2334f8 100644 --- a/src/components/LowerRightControls.tsx +++ b/src/components/LowerRightControls.tsx @@ -1,5 +1,4 @@ -import { Link, useLocation } from 'react-router-dom' - +import { Link, type NavigateFunction, useLocation } from 'react-router-dom' import { CustomIcon } from '@src/components/CustomIcon' import { HelpMenu } from '@src/components/HelpMenu' import { NetworkHealthIndicator } from '@src/components/NetworkHealthIndicator' @@ -15,7 +14,7 @@ export function LowerRightControls({ navigate = () => {}, }: { children?: React.ReactNode - navigate?: (path: string) => void + navigate?: NavigateFunction }) { const location = useLocation() const filePath = useAbsoluteFilePath() diff --git a/src/components/RouteProvider.tsx b/src/components/RouteProvider.tsx index 8abfee73c1d..3cb61cfc420 100644 --- a/src/components/RouteProvider.tsx +++ b/src/components/RouteProvider.tsx @@ -7,8 +7,6 @@ import { useRouteLoaderData, } from 'react-router-dom' -import type { OnboardingStatus } from '@rust/kcl-lib/bindings/OnboardingStatus' - import { useAuthNavigation } from '@src/hooks/useAuthNavigation' import { useFileSystemWatcher } from '@src/hooks/useFileSystemWatcher' import { getAppSettingsFilePath } from '@src/lib/desktop' @@ -18,7 +16,7 @@ import { markOnce } from '@src/lib/performance' import { loadAndValidateSettings } from '@src/lib/settings/settingsUtils' import { trap } from '@src/lib/trap' import type { IndexLoaderData } from '@src/lib/types' -import { settingsActor, useSettings } from '@src/lib/singletons' +import { settingsActor } from '@src/lib/singletons' export const RouteProviderContext = createContext({}) diff --git a/src/lib/desktopFS.ts b/src/lib/desktopFS.ts index 66fdacaa9e6..e12225129ca 100644 --- a/src/lib/desktopFS.ts +++ b/src/lib/desktopFS.ts @@ -1,23 +1,7 @@ import { relevantFileExtensions } from '@src/lang/wasmUtils' -import { - FILE_EXT, - INDEX_IDENTIFIER, - MAX_PADDING, - ONBOARDING_PROJECT_NAME, -} from '@src/lib/constants' -import { - createNewProjectDirectory, - listProjects, - readAppSettingsFile, -} from '@src/lib/desktop' -import { bracket } from '@src/lib/exampleKcl' +import { FILE_EXT, INDEX_IDENTIFIER, MAX_PADDING } from '@src/lib/constants' import { isDesktop } from '@src/lib/isDesktop' -import { PATHS } from '@src/lib/paths' import type { FileEntry } from '@src/lib/project' -import makeUrlPathRelative from './makeUrlPathRelative' -import { onboardingPaths } from '@src/routes/Onboarding/paths' -import { SystemIOMachineEvents } from '@src/machines/systemIO/utils' -import { systemIOActor } from './singletons' export const isHidden = (fileOrDir: FileEntry) => !!fileOrDir.name?.startsWith('.') @@ -136,56 +120,6 @@ export async function getSettingsFolderPaths(projectPath?: string) { } } -export async function createAndOpenNewTutorialProject( - onboardingStatus = onboardingPaths.INDEX -) { - // Create a new project with the onboarding project name - const configuration = await readAppSettingsFile() - const projects = await listProjects(configuration) - const nextIndex = getNextProjectIndex(ONBOARDING_PROJECT_NAME, projects) - const name = interpolateProjectNameWithIndex( - ONBOARDING_PROJECT_NAME, - nextIndex - ) - - // Delete the tutorial project if it already exists. - if (isDesktop()) { - if (configuration.settings?.project?.directory === undefined) { - return Promise.reject(new Error('configuration settings are undefined')) - } - - const fullPath = window.electron.join( - configuration.settings.project.directory, - name - ) - if (window.electron.exists(fullPath)) { - await window.electron.rm(fullPath) - } - } - - const newProject = await createNewProjectDirectory( - name, - bracket, - configuration - ) - - const filePathAsUri = encodeURIComponent( - newProject.default_file.replace(newProject.path, '') - ) - const subRoute = `${filePathAsUri}${PATHS.ONBOARDING.INDEX}${makeUrlPathRelative( - onboardingStatus - )}` - systemIOActor.send({ - type: SystemIOMachineEvents.navigateToProject, - data: { - requestedProjectName: newProject.name, - subRoute, - }, - }) - - return newProject -} - /** * Get the next available file name by appending a hyphen and number to the end of the name */ diff --git a/src/routes/Home.tsx b/src/routes/Home.tsx index 38ef49357e9..585ab9af7f5 100644 --- a/src/routes/Home.tsx +++ b/src/routes/Home.tsx @@ -48,7 +48,7 @@ import { acceptOnboarding, needsToOnboard, onDismissOnboardingInvite, -} from './Onboarding/utils' +} from '@src/routes/Onboarding/utils' import Tooltip from '@src/components/Tooltip' type ReadWriteProjectState = { diff --git a/src/routes/Onboarding/Camera.tsx b/src/routes/Onboarding/Camera.tsx index 211cd8e7bed..55c7368d875 100644 --- a/src/routes/Onboarding/Camera.tsx +++ b/src/routes/Onboarding/Camera.tsx @@ -2,17 +2,12 @@ import { SettingsSection } from '@src/components/Settings/SettingsSection' import type { CameraSystem } from '@src/lib/cameraControls' import { cameraMouseDragGuards, cameraSystems } from '@src/lib/cameraControls' import { settingsActor, useSettings } from '@src/lib/singletons' -import { onboardingPaths } from '@src/routes/Onboarding/paths' - import { + onboardingPaths, OnboardingButtons, - useDismiss, - useNextClick, } from '@src/routes/Onboarding/utils' export default function Units() { - useDismiss() - useNextClick(onboardingPaths.STREAMING) const { modeling: { mouseControls }, } = useSettings() diff --git a/src/routes/Onboarding/CmdK.tsx b/src/routes/Onboarding/CmdK.tsx index 7dc2e4e2ca6..7a9af9f78fb 100644 --- a/src/routes/Onboarding/CmdK.tsx +++ b/src/routes/Onboarding/CmdK.tsx @@ -1,9 +1,11 @@ import { COMMAND_PALETTE_HOTKEY } from '@src/components/CommandBar/CommandBar' import usePlatform from '@src/hooks/usePlatform' import { hotkeyDisplay } from '@src/lib/hotkeyWrapper' -import { onboardingPaths } from '@src/routes/Onboarding/paths' - -import { OnboardingButtons, kbdClasses } from '@src/routes/Onboarding/utils' +import { + onboardingPaths, + OnboardingButtons, + kbdClasses, +} from '@src/routes/Onboarding/utils' export default function CmdK() { const platformName = usePlatform() diff --git a/src/routes/Onboarding/CodeEditor.tsx b/src/routes/Onboarding/CodeEditor.tsx index e0e4e47fba5..75589d741ca 100644 --- a/src/routes/Onboarding/CodeEditor.tsx +++ b/src/routes/Onboarding/CodeEditor.tsx @@ -1,6 +1,5 @@ -import { onboardingPaths } from '@src/routes/Onboarding/paths' - import { + onboardingPaths, OnboardingButtons, kbdClasses, useDemoCode, diff --git a/src/routes/Onboarding/Export.tsx b/src/routes/Onboarding/Export.tsx index 5d14b4d876e..1cbd89f1b5b 100644 --- a/src/routes/Onboarding/Export.tsx +++ b/src/routes/Onboarding/Export.tsx @@ -1,7 +1,8 @@ import { APP_NAME } from '@src/lib/constants' -import { onboardingPaths } from '@src/routes/Onboarding/paths' - -import { OnboardingButtons } from '@src/routes/Onboarding/utils' +import { + onboardingPaths, + OnboardingButtons, +} from '@src/routes/Onboarding/utils' export default function Export() { return ( diff --git a/src/routes/Onboarding/FutureWork.tsx b/src/routes/Onboarding/FutureWork.tsx index fa2756adfee..9f3171b9a7f 100644 --- a/src/routes/Onboarding/FutureWork.tsx +++ b/src/routes/Onboarding/FutureWork.tsx @@ -1,11 +1,12 @@ import { useEffect } from 'react' - import { useModelingContext } from '@src/hooks/useModelingContext' import { APP_NAME } from '@src/lib/constants' import { sceneInfra } from '@src/lib/singletons' -import { onboardingPaths } from '@src/routes/Onboarding/paths' - -import { OnboardingButtons, useDemoCode } from '@src/routes/Onboarding/utils' +import { + onboardingPaths, + OnboardingButtons, + useDemoCode, +} from '@src/routes/Onboarding/utils' export default function FutureWork() { const { send } = useModelingContext() diff --git a/src/routes/Onboarding/InteractiveNumbers.tsx b/src/routes/Onboarding/InteractiveNumbers.tsx index d5217f08576..9cda2546994 100644 --- a/src/routes/Onboarding/InteractiveNumbers.tsx +++ b/src/routes/Onboarding/InteractiveNumbers.tsx @@ -1,7 +1,6 @@ import { bracketWidthConstantLine } from '@src/lib/exampleKcl' -import { onboardingPaths } from '@src/routes/Onboarding/paths' - import { + onboardingPaths, OnboardingButtons, kbdClasses, useDemoCode, diff --git a/src/routes/Onboarding/Introduction.tsx b/src/routes/Onboarding/Introduction.tsx index 14f1e2ee2e7..6288ce846a0 100644 --- a/src/routes/Onboarding/Introduction.tsx +++ b/src/routes/Onboarding/Introduction.tsx @@ -2,9 +2,11 @@ import { APP_NAME } from '@src/lib/constants' import { isDesktop } from '@src/lib/isDesktop' import { Themes, getSystemTheme } from '@src/lib/theme' import { useSettings } from '@src/lib/singletons' -import { onboardingPaths } from '@src/routes/Onboarding/paths' - -import { OnboardingButtons, useDemoCode } from '@src/routes/Onboarding/utils' +import { + onboardingPaths, + OnboardingButtons, + useDemoCode, +} from '@src/routes/Onboarding/utils' export default function Introduction() { // Reset the code to the bracket code diff --git a/src/routes/Onboarding/ParametricModeling.tsx b/src/routes/Onboarding/ParametricModeling.tsx index a3fb6fa2468..e404b73390d 100644 --- a/src/routes/Onboarding/ParametricModeling.tsx +++ b/src/routes/Onboarding/ParametricModeling.tsx @@ -2,9 +2,11 @@ import { bracketThicknessCalculationLine } from '@src/lib/exampleKcl' import { isDesktop } from '@src/lib/isDesktop' import { Themes, getSystemTheme } from '@src/lib/theme' import { useSettings } from '@src/lib/singletons' -import { onboardingPaths } from '@src/routes/Onboarding/paths' - -import { OnboardingButtons, useDemoCode } from '@src/routes/Onboarding/utils' +import { + onboardingPaths, + OnboardingButtons, + useDemoCode, +} from '@src/routes/Onboarding/utils' export default function OnboardingParametricModeling() { useDemoCode() diff --git a/src/routes/Onboarding/ProjectMenu.tsx b/src/routes/Onboarding/ProjectMenu.tsx index b8f3ee3d488..cb6a8f70554 100644 --- a/src/routes/Onboarding/ProjectMenu.tsx +++ b/src/routes/Onboarding/ProjectMenu.tsx @@ -1,7 +1,8 @@ import { isDesktop } from '@src/lib/isDesktop' -import { onboardingPaths } from '@src/routes/Onboarding/paths' - -import { OnboardingButtons } from '@src/routes/Onboarding/utils' +import { + onboardingPaths, + OnboardingButtons, +} from '@src/routes/Onboarding/utils' export default function ProjectMenu() { const onDesktop = isDesktop() diff --git a/src/routes/Onboarding/Sketching.tsx b/src/routes/Onboarding/Sketching.tsx index e24c76c1a10..7d947c5053e 100644 --- a/src/routes/Onboarding/Sketching.tsx +++ b/src/routes/Onboarding/Sketching.tsx @@ -1,9 +1,9 @@ import { useEffect } from 'react' - import { codeManager, kclManager } from '@src/lib/singletons' -import { onboardingPaths } from '@src/routes/Onboarding/paths' - -import { OnboardingButtons } from '@src/routes/Onboarding/utils' +import { + onboardingPaths, + OnboardingButtons, +} from '@src/routes/Onboarding/utils' export default function Sketching() { useEffect(() => { diff --git a/src/routes/Onboarding/Streaming.tsx b/src/routes/Onboarding/Streaming.tsx index ef052be5968..1d63aeb7e29 100644 --- a/src/routes/Onboarding/Streaming.tsx +++ b/src/routes/Onboarding/Streaming.tsx @@ -1,6 +1,7 @@ -import { onboardingPaths } from '@src/routes/Onboarding/paths' - -import { OnboardingButtons } from '@src/routes/Onboarding/utils' +import { + onboardingPaths, + OnboardingButtons, +} from '@src/routes/Onboarding/utils' export default function Streaming() { return ( diff --git a/src/routes/Onboarding/UserMenu.tsx b/src/routes/Onboarding/UserMenu.tsx index e3e03fee924..f2b4a5faa02 100644 --- a/src/routes/Onboarding/UserMenu.tsx +++ b/src/routes/Onboarding/UserMenu.tsx @@ -1,9 +1,9 @@ import { useEffect, useState } from 'react' - import { useUser } from '@src/lib/singletons' -import { onboardingPaths } from '@src/routes/Onboarding/paths' - -import { OnboardingButtons } from '@src/routes/Onboarding/utils' +import { + onboardingPaths, + OnboardingButtons, +} from '@src/routes/Onboarding/utils' export default function UserMenu() { const user = useUser() diff --git a/src/routes/Onboarding/index.tsx b/src/routes/Onboarding/index.tsx index 1652d70ac96..e09e8ee932e 100644 --- a/src/routes/Onboarding/index.tsx +++ b/src/routes/Onboarding/index.tsx @@ -14,8 +14,7 @@ import ProjectMenu from '@src/routes/Onboarding/ProjectMenu' import Sketching from '@src/routes/Onboarding/Sketching' import Streaming from '@src/routes/Onboarding/Streaming' import UserMenu from '@src/routes/Onboarding/UserMenu' -import { onboardingPaths } from '@src/routes/Onboarding/paths' -import { useDismiss } from '@src/routes/Onboarding/utils' +import { useDismiss, onboardingPaths } from '@src/routes/Onboarding/utils' export const onboardingRoutes = [ { diff --git a/src/routes/Onboarding/utils.tsx b/src/routes/Onboarding/utils.tsx index c749f9c672e..bd54cdaf704 100644 --- a/src/routes/Onboarding/utils.tsx +++ b/src/routes/Onboarding/utils.tsx @@ -16,25 +16,56 @@ 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 { + codeManager, + editorManager, + kclManager, + systemIOActor, +} 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 { isKclEmptyOrOnlySettings, parse, resultIsOk } from '@src/lang/wasm' import { updateModelingState } from '@src/lang/modelingWorkflows' -import { EXECUTION_TYPE_REAL } from '@src/lib/constants' +import { + EXECUTION_TYPE_REAL, + ONBOARDING_PROJECT_NAME, +} from '@src/lib/constants' import toast from 'react-hot-toast' import type CodeManager from '@src/lang/codeManager' import type { OnboardingStatus } from '@rust/kcl-lib/bindings/OnboardingStatus' -import { createAndOpenNewTutorialProject } from '@src/lib/desktopFS' import { isDesktop } from '@src/lib/isDesktop' import type { KclManager } from '@src/lang/KclSingleton' import { Logo } from '@src/components/Logo' +import { + readAppSettingsFile, + listProjects, + createNewProjectDirectory, +} from '@src/lib/desktop' +import { + getNextProjectIndex, + interpolateProjectNameWithIndex, +} from '@src/lib/desktopFS' +import { SystemIOMachineEvents } from '@src/machines/systemIO/utils' 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' +export const onboardingPaths: Record = { + INDEX: '/', + CAMERA: '/camera', + STREAMING: '/streaming', + EDITOR: '/editor', + PARAMETRIC_MODELING: '/parametric-modeling', + INTERACTIVE_NUMBERS: '/interactive-numbers', + COMMAND_K: '/command-k', + USER_MENU: '/user-menu', + PROJECT_MENU: '/project-menu', + EXPORT: '/export', + MOVE: '/move', + SKETCHING: '/sketching', + FUTURE_WORK: '/future-work', +} as const + // Get the 1-indexed step number of the current onboarding step function useStepNumber( slug?: (typeof onboardingPaths)[keyof typeof onboardingPaths] @@ -42,8 +73,8 @@ function useStepNumber( return slug ? slug === onboardingPaths.INDEX ? 1 - : onboardingRoutes.findIndex( - (r) => r.path === makeUrlPathRelative(slug) + : Object.values(onboardingPaths).findIndex( + (r) => r === makeUrlPathRelative(slug) ) + 1 : 1 } @@ -131,18 +162,19 @@ export function OnboardingButtons({ dismissClassName?: string onNextOverride?: () => void } & React.HTMLAttributes) { + const onboardingPathsArray = Object.values(onboardingPaths) const dismiss = useDismiss() const stepNumber = useStepNumber(currentSlug) const previousStep = - !stepNumber || stepNumber <= 1 ? null : onboardingRoutes[stepNumber - 2] + !stepNumber || stepNumber <= 1 ? null : onboardingPathsArray[stepNumber - 2] const goToPrevious = useNextClick( - onboardingPaths.INDEX + (previousStep?.path ?? '') + onboardingPaths.INDEX + (previousStep ?? '') ) const nextStep = - !stepNumber || stepNumber === onboardingRoutes.length + !stepNumber || stepNumber === onboardingPathsArray.length ? null - : onboardingRoutes[stepNumber] - const goToNext = useNextClick(onboardingPaths.INDEX + (nextStep?.path ?? '')) + : onboardingPathsArray[stepNumber] + const goToNext = useNextClick(onboardingPaths.INDEX + (nextStep ?? '')) return ( <> @@ -168,27 +200,27 @@ export function OnboardingButtons({ > (previousStep?.path ? goToPrevious() : dismiss())} + onClick={() => (previousStep ? goToPrevious() : dismiss())} iconStart={{ - icon: previousStep?.path ? 'arrowLeft' : 'close', + 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?.path ? 'Back' : 'Dismiss'} + {previousStep ? 'Back' : 'Dismiss'} {stepNumber !== undefined && (

    - {stepNumber - 1} / {onboardingRoutes.length} + {stepNumber - 1} / {onboardingPathsArray.length}

    )} { - if (nextStep?.path) { + if (nextStep) { onNextOverride ? onNextOverride() : goToNext() } else { dismiss() @@ -223,15 +255,15 @@ export const ERROR_MUST_WARN = 'Must warn user before overwrite' */ export async function acceptOnboarding(deps: OnboardingUtilDeps) { if (isDesktop()) { - createAndOpenNewTutorialProject(deps.onboardingStatus) - } else { - const isCodeResettable = hasResetReadyCode(deps.codeManager) - if (isCodeResettable) { - resetCodeAndAdvanceOnboarding(deps) - } else { - return Promise.reject(new Error(ERROR_MUST_WARN)) - } + return createAndOpenNewTutorialProject(deps.onboardingStatus) + } + + const isCodeResettable = hasResetReadyCode(deps.codeManager) + if (isCodeResettable) { + return resetCodeAndAdvanceOnboarding(deps) } + + return Promise.reject(new Error(ERROR_MUST_WARN)) } /** @@ -290,8 +322,8 @@ export function onDismissOnboardingInvite() { } export function TutorialRequestToast(props: OnboardingUtilDeps) { - async function onAccept() { - return acceptOnboarding(props) + function onAccept() { + acceptOnboarding(props) .then(() => { toast.dismiss(ONBOARDING_TOAST_ID) }) @@ -362,7 +394,7 @@ export async function catchOnboardingWarnError( export function TutorialWebConfirmationToast(props: OnboardingUtilDeps) { function onAccept() { toast.dismiss(ONBOARDING_TOAST_ID) - resetCodeAndAdvanceOnboarding(props) + resetCodeAndAdvanceOnboarding(props).catch(reportRejection) } return ( @@ -406,3 +438,53 @@ export function TutorialWebConfirmationToast(props: OnboardingUtilDeps) {
    ) } + +export async function createAndOpenNewTutorialProject( + onboardingStatus = onboardingPaths.INDEX +) { + // Create a new project with the onboarding project name + const configuration = await readAppSettingsFile() + const projects = await listProjects(configuration) + const nextIndex = getNextProjectIndex(ONBOARDING_PROJECT_NAME, projects) + const name = interpolateProjectNameWithIndex( + ONBOARDING_PROJECT_NAME, + nextIndex + ) + + // Delete the tutorial project if it already exists. + if (isDesktop()) { + if (configuration.settings?.project?.directory === undefined) { + return Promise.reject(new Error('configuration settings are undefined')) + } + + const fullPath = window.electron.join( + configuration.settings.project.directory, + name + ) + if (window.electron.exists(fullPath)) { + await window.electron.rm(fullPath) + } + } + + const newProject = await createNewProjectDirectory( + name, + bracket, + configuration + ) + + const filePathAsUri = encodeURIComponent( + newProject.default_file.replace(newProject.path, '') + ) + const subRoute = `${filePathAsUri}${PATHS.ONBOARDING.INDEX}${makeUrlPathRelative( + onboardingStatus + )}` + systemIOActor.send({ + type: SystemIOMachineEvents.navigateToProject, + data: { + requestedProjectName: newProject.name, + subRoute, + }, + }) + + return newProject +} From d78223396ac9d43fc36acb5ee2899a96a381b9b3 Mon Sep 17 00:00:00 2001 From: Frank Noirot Date: Tue, 29 Apr 2025 11:03:34 -0400 Subject: [PATCH 13/29] Update circular dep file --- known-circular.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/known-circular.txt b/known-circular.txt index a36dee1ef7f..fbdd980df08 100644 --- a/known-circular.txt +++ b/known-circular.txt @@ -11,4 +11,3 @@ 6) src/lib/singletons.ts -> src/clientSideScene/sceneEntities.ts -> src/clientSideScene/segments.ts -> src/components/Toolbar/angleLengthInfo.ts 7) src/lib/singletons.ts -> src/clientSideScene/sceneEntities.ts -> src/clientSideScene/segments.ts 8) src/hooks/useModelingContext.ts -> src/components/ModelingMachineProvider.tsx -> src/components/Toolbar/Intersect.tsx -> src/components/SetHorVertDistanceModal.tsx -> src/lib/useCalculateKclExpression.ts - 9) src/routes/Onboarding/index.tsx -> src/routes/Onboarding/Camera.tsx -> src/routes/Onboarding/utils.tsx From c92fc25c29fa0817e16ab3464cf43d4836235f73 Mon Sep 17 00:00:00 2001 From: Frank Noirot Date: Tue, 29 Apr 2025 13:32:46 -0400 Subject: [PATCH 14/29] Fix mistakes made in circular update tweaking --- e2e/playwright/storageStates.ts | 11 +++-- rust/kcl-lib/src/settings/types/mod.rs | 3 -- src/App.tsx | 10 ++-- src/components/HelpMenu.tsx | 4 +- .../ModelingSidebar/ModelingPane.tsx | 4 +- .../ModelingSidebar/ModelingSidebar.tsx | 4 +- src/components/Settings/AllSettingsFields.tsx | 5 +- src/lib/paths.ts | 23 +++++++-- src/routes/Onboarding/Camera.tsx | 8 ++-- src/routes/Onboarding/CmdK.tsx | 9 ++-- src/routes/Onboarding/CodeEditor.tsx | 4 +- src/routes/Onboarding/Export.tsx | 8 ++-- src/routes/Onboarding/FutureWork.tsx | 9 ++-- src/routes/Onboarding/InteractiveNumbers.tsx | 6 ++- src/routes/Onboarding/Introduction.tsx | 9 ++-- src/routes/Onboarding/ParametricModeling.tsx | 11 ++--- src/routes/Onboarding/ProjectMenu.tsx | 8 ++-- src/routes/Onboarding/Sketching.tsx | 8 ++-- src/routes/Onboarding/Streaming.tsx | 8 ++-- src/routes/Onboarding/Units.tsx | 5 +- src/routes/Onboarding/UserMenu.tsx | 8 ++-- src/routes/Onboarding/index.tsx | 25 +++++----- src/routes/Onboarding/paths.ts | 17 ------- src/routes/Onboarding/utils.tsx | 48 +++++-------------- 24 files changed, 105 insertions(+), 150 deletions(-) delete mode 100644 src/routes/Onboarding/paths.ts diff --git a/e2e/playwright/storageStates.ts b/e2e/playwright/storageStates.ts index 27f363ea8d0..16d4f92f577 100644 --- a/e2e/playwright/storageStates.ts +++ b/e2e/playwright/storageStates.ts @@ -1,7 +1,7 @@ import type { SaveSettingsPayload } from '@src/lib/settings/settingsTypes' import { Themes } from '@src/lib/theme' import type { DeepPartial } from '@src/lib/types' -import { onboardingPaths } from '@src/routes/Onboarding/paths' +import { ONBOARDING_SUBPATHS } from '@src/lib/paths' import type { Settings } from '@rust/kcl-lib/bindings/Settings' @@ -33,12 +33,15 @@ export const TEST_SETTINGS: DeepPartial = { export const TEST_SETTINGS_ONBOARDING_USER_MENU: DeepPartial = { ...TEST_SETTINGS, - app: { ...TEST_SETTINGS.app, onboarding_status: onboardingPaths.USER_MENU }, + app: { + ...TEST_SETTINGS.app, + onboarding_status: ONBOARDING_SUBPATHS.USER_MENU, + }, } export const TEST_SETTINGS_ONBOARDING_EXPORT: DeepPartial = { ...TEST_SETTINGS, - app: { ...TEST_SETTINGS.app, onboarding_status: onboardingPaths.EXPORT }, + app: { ...TEST_SETTINGS.app, onboarding_status: ONBOARDING_SUBPATHS.EXPORT }, } export const TEST_SETTINGS_ONBOARDING_PARAMETRIC_MODELING: DeepPartial = @@ -46,7 +49,7 @@ export const TEST_SETTINGS_ONBOARDING_PARAMETRIC_MODELING: DeepPartial ...TEST_SETTINGS, app: { ...TEST_SETTINGS.app, - onboarding_status: onboardingPaths.PARAMETRIC_MODELING, + onboarding_status: ONBOARDING_SUBPATHS.PARAMETRIC_MODELING, }, } diff --git a/rust/kcl-lib/src/settings/types/mod.rs b/rust/kcl-lib/src/settings/types/mod.rs index 28398ddd5d8..1f9ba9b09b0 100644 --- a/rust/kcl-lib/src/settings/types/mod.rs +++ b/rust/kcl-lib/src/settings/types/mod.rs @@ -589,9 +589,6 @@ pub enum OnboardingStatus { #[serde(rename = "/export")] #[display("/export")] Export, - #[serde(rename = "/move")] - #[display("/move")] - Move, #[serde(rename = "/sketching")] #[display("/sketching")] Sketching, diff --git a/src/App.tsx b/src/App.tsx index 8dfa7582bcc..8337651d130 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -25,7 +25,7 @@ import { useHotKeyListener } from '@src/hooks/useHotKeyListener' import { writeProjectThumbnailFile } from '@src/lib/desktop' import useHotkeyWrapper from '@src/lib/hotkeyWrapper' import { isDesktop } from '@src/lib/isDesktop' -import { PATHS } from '@src/lib/paths' +import { ONBOARDING_SUBPATHS, PATHS } from '@src/lib/paths' import { takeScreenshotOfVideoStreamCanvas } from '@src/lib/screenshot' import { sceneInfra, codeManager, kclManager } from '@src/lib/singletons' import { maybeWriteToDisk } from '@src/lib/telemetry' @@ -33,7 +33,6 @@ import type { IndexLoaderData } from '@src/lib/types' import { engineStreamActor, useSettings, useToken } from '@src/lib/singletons' import { commandBarActor } from '@src/lib/singletons' import { EngineStreamTransition } from '@src/machines/engineStreamMachine' -import { onboardingPaths } from '@src/routes/Onboarding/paths' import { CommandBarOpenButton } from '@src/components/CommandBarOpenButton' import { ShareButton } from '@src/components/ShareButton' import { @@ -112,9 +111,10 @@ export function App() { toast.success('Your work is auto-saved in real-time') }) - const paneOpacity = [onboardingPaths.CAMERA, onboardingPaths.STREAMING].some( - (p) => p === onboardingStatus.current - ) + const paneOpacity = [ + ONBOARDING_SUBPATHS.CAMERA, + ONBOARDING_SUBPATHS.STREAMING, + ].some((p) => p === onboardingStatus.current) ? 'opacity-20' : '' diff --git a/src/components/HelpMenu.tsx b/src/components/HelpMenu.tsx index 09ff53c9b63..5f823348a06 100644 --- a/src/components/HelpMenu.tsx +++ b/src/components/HelpMenu.tsx @@ -13,7 +13,7 @@ import { acceptOnboarding, catchOnboardingWarnError, } from '@src/routes/Onboarding/utils' -import { onboardingPaths } from '@src/routes/Onboarding/paths' +import { ONBOARDING_SUBPATHS } from '@src/lib/paths' const HelpMenuDivider = () => (
    @@ -29,7 +29,7 @@ export function HelpMenu({ const resetOnboardingWorkflow = () => { const props = { - onboardingStatus: onboardingPaths.INDEX, + onboardingStatus: ONBOARDING_SUBPATHS.INDEX, navigate, codeManager, kclManager, diff --git a/src/components/ModelingSidebar/ModelingPane.tsx b/src/components/ModelingSidebar/ModelingPane.tsx index b919b8ff079..9309292c4ee 100644 --- a/src/components/ModelingSidebar/ModelingPane.tsx +++ b/src/components/ModelingSidebar/ModelingPane.tsx @@ -6,7 +6,7 @@ import { ActionIcon } from '@src/components/ActionIcon' import type { CustomIconName } from '@src/components/CustomIcon' import Tooltip from '@src/components/Tooltip' import { useSettings } from '@src/lib/singletons' -import { onboardingPaths } from '@src/routes/Onboarding/paths' +import { ONBOARDING_SUBPATHS } from '@src/lib/paths' import styles from './ModelingPane.module.css' @@ -71,7 +71,7 @@ export const ModelingPane = ({ const settings = useSettings() const onboardingStatus = settings.app.onboardingStatus const pointerEventsCssClass = - onboardingStatus.current === onboardingPaths.CAMERA + onboardingStatus.current === ONBOARDING_SUBPATHS.CAMERA ? 'pointer-events-none ' : 'pointer-events-auto ' return ( diff --git a/src/components/ModelingSidebar/ModelingSidebar.tsx b/src/components/ModelingSidebar/ModelingSidebar.tsx index 6e5eb2f6861..497a41e3979 100644 --- a/src/components/ModelingSidebar/ModelingSidebar.tsx +++ b/src/components/ModelingSidebar/ModelingSidebar.tsx @@ -24,7 +24,7 @@ import { SIDEBAR_BUTTON_SUFFIX } from '@src/lib/constants' import { isDesktop } from '@src/lib/isDesktop' import { useSettings } from '@src/lib/singletons' import { commandBarActor } from '@src/lib/singletons' -import { onboardingPaths } from '@src/routes/Onboarding/paths' +import { ONBOARDING_SUBPATHS } from '@src/lib/paths' import { reportRejection } from '@src/lib/trap' import { refreshPage } from '@src/lib/utils' import { hotkeyDisplay } from '@src/lib/hotkeyWrapper' @@ -53,7 +53,7 @@ export function ModelingSidebar({ paneOpacity }: ModelingSidebarProps) { const onboardingStatus = settings.app.onboardingStatus const { send, context } = useModelingContext() const pointerEventsCssClass = - onboardingStatus.current === onboardingPaths.CAMERA || + onboardingStatus.current === ONBOARDING_SUBPATHS.CAMERA || context.store?.openPanes.length === 0 ? 'pointer-events-none ' : 'pointer-events-auto ' diff --git a/src/components/Settings/AllSettingsFields.tsx b/src/components/Settings/AllSettingsFields.tsx index b4ef60cadf7..9e3a0dfb14e 100644 --- a/src/components/Settings/AllSettingsFields.tsx +++ b/src/components/Settings/AllSettingsFields.tsx @@ -11,7 +11,7 @@ import { SettingsSection } from '@src/components/Settings/SettingsSection' import { getSettingsFolderPaths } from '@src/lib/desktopFS' import { isDesktop } from '@src/lib/isDesktop' import { openExternalBrowserIfDesktop } from '@src/lib/openWindow' -import { PATHS } from '@src/lib/paths' +import { PATHS, ONBOARDING_SUBPATHS } from '@src/lib/paths' import type { Setting } from '@src/lib/settings/initialSettings' import type { SetEventTypes, @@ -34,7 +34,6 @@ import { acceptOnboarding, catchOnboardingWarnError, } from '@src/routes/Onboarding/utils' -import { onboardingPaths } from '@src/routes/Onboarding/paths' interface AllSettingsFieldsProps { searchParamTab: SettingsLevel @@ -69,7 +68,7 @@ export const AllSettingsFields = forwardRef( async function restartOnboarding() { const props = { - onboardingStatus: onboardingPaths.INDEX, + onboardingStatus: ONBOARDING_SUBPATHS.INDEX, navigate, codeManager, kclManager, diff --git a/src/lib/paths.ts b/src/lib/paths.ts index 4ad514f197e..09d80a902ac 100644 --- a/src/lib/paths.ts +++ b/src/lib/paths.ts @@ -14,7 +14,22 @@ import { isDesktop } from '@src/lib/isDesktop' import { readLocalStorageAppSettingsFile } from '@src/lib/settings/settingsUtils' import { err } from '@src/lib/trap' import type { DeepPartial } from '@src/lib/types' -import { onboardingPaths } from '@src/routes/Onboarding/paths' +import type { OnboardingStatus } from '@rust/kcl-lib/bindings/OnboardingStatus' + +export const ONBOARDING_SUBPATHS: Record = { + INDEX: '/', + CAMERA: '/camera', + STREAMING: '/streaming', + EDITOR: '/editor', + PARAMETRIC_MODELING: '/parametric-modeling', + INTERACTIVE_NUMBERS: '/interactive-numbers', + COMMAND_K: '/command-k', + USER_MENU: '/user-menu', + PROJECT_MENU: '/project-menu', + EXPORT: '/export', + SKETCHING: '/sketching', + FUTURE_WORK: '/future-work', +} as const const prependRoutes = (routesObject: Record) => (prepend: string) => { @@ -27,7 +42,7 @@ const prependRoutes = } type OnboardingPaths = { - [K in keyof typeof onboardingPaths]: `/onboarding${(typeof onboardingPaths)[K]}` + [K in keyof typeof ONBOARDING_SUBPATHS]: `/onboarding${(typeof ONBOARDING_SUBPATHS)[K]}` } const SETTINGS = '/settings' @@ -48,7 +63,9 @@ export const PATHS = { SETTINGS_PROJECT: `${SETTINGS}?tab=project` as const, SETTINGS_KEYBINDINGS: `${SETTINGS}?tab=keybindings` as const, SIGN_IN: '/signin', - ONBOARDING: prependRoutes(onboardingPaths)('/onboarding') as OnboardingPaths, + ONBOARDING: prependRoutes(ONBOARDING_SUBPATHS)( + '/onboarding' + ) as OnboardingPaths, TELEMETRY: '/telemetry', } as const export const BROWSER_PATH = `%2F${BROWSER_PROJECT_NAME}%2F${BROWSER_FILE_NAME}${FILE_EXT}` diff --git a/src/routes/Onboarding/Camera.tsx b/src/routes/Onboarding/Camera.tsx index 55c7368d875..964052ad8e7 100644 --- a/src/routes/Onboarding/Camera.tsx +++ b/src/routes/Onboarding/Camera.tsx @@ -1,11 +1,9 @@ import { SettingsSection } from '@src/components/Settings/SettingsSection' import type { CameraSystem } from '@src/lib/cameraControls' import { cameraMouseDragGuards, cameraSystems } from '@src/lib/cameraControls' +import { ONBOARDING_SUBPATHS } from '@src/lib/paths' import { settingsActor, useSettings } from '@src/lib/singletons' -import { - onboardingPaths, - OnboardingButtons, -} from '@src/routes/Onboarding/utils' +import { OnboardingButtons } from '@src/routes/Onboarding/utils' export default function Units() { const { @@ -61,7 +59,7 @@ export default function Units() {
diff --git a/src/routes/Onboarding/CmdK.tsx b/src/routes/Onboarding/CmdK.tsx index 7a9af9f78fb..25bc97103aa 100644 --- a/src/routes/Onboarding/CmdK.tsx +++ b/src/routes/Onboarding/CmdK.tsx @@ -1,11 +1,8 @@ import { COMMAND_PALETTE_HOTKEY } from '@src/components/CommandBar/CommandBar' import usePlatform from '@src/hooks/usePlatform' import { hotkeyDisplay } from '@src/lib/hotkeyWrapper' -import { - onboardingPaths, - OnboardingButtons, - kbdClasses, -} from '@src/routes/Onboarding/utils' +import { ONBOARDING_SUBPATHS } from '@src/lib/paths' +import { OnboardingButtons, kbdClasses } from '@src/routes/Onboarding/utils' export default function CmdK() { const platformName = usePlatform() @@ -39,7 +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 75589d741ca..b5446db1f3d 100644 --- a/src/routes/Onboarding/CodeEditor.tsx +++ b/src/routes/Onboarding/CodeEditor.tsx @@ -1,5 +1,5 @@ +import { ONBOARDING_SUBPATHS } from '@src/lib/paths' import { - onboardingPaths, OnboardingButtons, kbdClasses, useDemoCode, @@ -69,7 +69,7 @@ export default function OnboardingCodeEditor() { pressing Shift + C.

- + ) diff --git a/src/routes/Onboarding/Export.tsx b/src/routes/Onboarding/Export.tsx index 1cbd89f1b5b..400abd4413f 100644 --- a/src/routes/Onboarding/Export.tsx +++ b/src/routes/Onboarding/Export.tsx @@ -1,8 +1,6 @@ import { APP_NAME } from '@src/lib/constants' -import { - onboardingPaths, - OnboardingButtons, -} from '@src/routes/Onboarding/utils' +import { ONBOARDING_SUBPATHS } from '@src/lib/paths' +import { OnboardingButtons } from '@src/routes/Onboarding/utils' export default function Export() { return ( @@ -51,7 +49,7 @@ export default function Export() { !

- + ) diff --git a/src/routes/Onboarding/FutureWork.tsx b/src/routes/Onboarding/FutureWork.tsx index 9f3171b9a7f..e0e7a966d93 100644 --- a/src/routes/Onboarding/FutureWork.tsx +++ b/src/routes/Onboarding/FutureWork.tsx @@ -2,11 +2,8 @@ import { useEffect } from 'react' import { useModelingContext } from '@src/hooks/useModelingContext' import { APP_NAME } from '@src/lib/constants' import { sceneInfra } from '@src/lib/singletons' -import { - onboardingPaths, - OnboardingButtons, - useDemoCode, -} from '@src/routes/Onboarding/utils' +import { OnboardingButtons, useDemoCode } from '@src/routes/Onboarding/utils' +import { ONBOARDING_SUBPATHS } from '@src/lib/paths' export default function FutureWork() { const { send } = useModelingContext() @@ -59,7 +56,7 @@ export default function FutureWork() {

💚 The Zoo Team

diff --git a/src/routes/Onboarding/InteractiveNumbers.tsx b/src/routes/Onboarding/InteractiveNumbers.tsx index 9cda2546994..62736fe1b87 100644 --- a/src/routes/Onboarding/InteractiveNumbers.tsx +++ b/src/routes/Onboarding/InteractiveNumbers.tsx @@ -1,6 +1,6 @@ import { bracketWidthConstantLine } from '@src/lib/exampleKcl' +import { ONBOARDING_SUBPATHS } from '@src/lib/paths' import { - onboardingPaths, OnboardingButtons, kbdClasses, useDemoCode, @@ -84,7 +84,9 @@ 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 6288ce846a0..1b2e256c583 100644 --- a/src/routes/Onboarding/Introduction.tsx +++ b/src/routes/Onboarding/Introduction.tsx @@ -2,11 +2,8 @@ import { APP_NAME } from '@src/lib/constants' import { isDesktop } from '@src/lib/isDesktop' import { Themes, getSystemTheme } from '@src/lib/theme' import { useSettings } from '@src/lib/singletons' -import { - onboardingPaths, - OnboardingButtons, - useDemoCode, -} from '@src/routes/Onboarding/utils' +import { OnboardingButtons, useDemoCode } from '@src/routes/Onboarding/utils' +import { ONBOARDING_SUBPATHS } from '@src/lib/paths' export default function Introduction() { // Reset the code to the bracket code @@ -72,7 +69,7 @@ export default function Introduction() {

diff --git a/src/routes/Onboarding/ParametricModeling.tsx b/src/routes/Onboarding/ParametricModeling.tsx index e404b73390d..01de9dc648b 100644 --- a/src/routes/Onboarding/ParametricModeling.tsx +++ b/src/routes/Onboarding/ParametricModeling.tsx @@ -2,11 +2,8 @@ import { bracketThicknessCalculationLine } from '@src/lib/exampleKcl' import { isDesktop } from '@src/lib/isDesktop' import { Themes, getSystemTheme } from '@src/lib/theme' import { useSettings } from '@src/lib/singletons' -import { - onboardingPaths, - OnboardingButtons, - useDemoCode, -} from '@src/routes/Onboarding/utils' +import { OnboardingButtons, useDemoCode } from '@src/routes/Onboarding/utils' +import { ONBOARDING_SUBPATHS } from '@src/lib/paths' export default function OnboardingParametricModeling() { useDemoCode() @@ -74,7 +71,9 @@ export default function OnboardingParametricModeling() { - + ) diff --git a/src/routes/Onboarding/ProjectMenu.tsx b/src/routes/Onboarding/ProjectMenu.tsx index cb6a8f70554..1b82e9c60df 100644 --- a/src/routes/Onboarding/ProjectMenu.tsx +++ b/src/routes/Onboarding/ProjectMenu.tsx @@ -1,8 +1,6 @@ import { isDesktop } from '@src/lib/isDesktop' -import { - onboardingPaths, - OnboardingButtons, -} from '@src/routes/Onboarding/utils' +import { ONBOARDING_SUBPATHS } from '@src/lib/paths' +import { OnboardingButtons } from '@src/routes/Onboarding/utils' export default function ProjectMenu() { const onDesktop = isDesktop() @@ -57,7 +55,7 @@ export default function ProjectMenu() { )} - + ) diff --git a/src/routes/Onboarding/Sketching.tsx b/src/routes/Onboarding/Sketching.tsx index 7d947c5053e..e04407c7b71 100644 --- a/src/routes/Onboarding/Sketching.tsx +++ b/src/routes/Onboarding/Sketching.tsx @@ -1,9 +1,7 @@ import { useEffect } from 'react' import { codeManager, kclManager } from '@src/lib/singletons' -import { - onboardingPaths, - OnboardingButtons, -} from '@src/routes/Onboarding/utils' +import { OnboardingButtons } from '@src/routes/Onboarding/utils' +import { ONBOARDING_SUBPATHS } from '@src/lib/paths' export default function Sketching() { useEffect(() => { @@ -42,7 +40,7 @@ export default function Sketching() { always just modifying and generating code in Zoo Design Studio.

diff --git a/src/routes/Onboarding/Streaming.tsx b/src/routes/Onboarding/Streaming.tsx index 1d63aeb7e29..d3a7e3dcc83 100644 --- a/src/routes/Onboarding/Streaming.tsx +++ b/src/routes/Onboarding/Streaming.tsx @@ -1,7 +1,5 @@ -import { - onboardingPaths, - OnboardingButtons, -} from '@src/routes/Onboarding/utils' +import { ONBOARDING_SUBPATHS } from '@src/lib/paths' +import { OnboardingButtons } from '@src/routes/Onboarding/utils' export default function Streaming() { return ( @@ -42,7 +40,7 @@ export default function Streaming() {

diff --git a/src/routes/Onboarding/Units.tsx b/src/routes/Onboarding/Units.tsx index cbccf8e17f8..049d3dbc319 100644 --- a/src/routes/Onboarding/Units.tsx +++ b/src/routes/Onboarding/Units.tsx @@ -4,13 +4,12 @@ import { ActionButton } from '@src/components/ActionButton' import { SettingsSection } from '@src/components/Settings/SettingsSection' import { type BaseUnit, baseUnitsUnion } from '@src/lib/settings/settingsTypes' import { settingsActor, useSettings } from '@src/lib/singletons' -import { onboardingPaths } from '@src/routes/Onboarding/paths' - +import { ONBOARDING_SUBPATHS } from '@src/lib/paths' import { useDismiss, useNextClick } from '@src/routes/Onboarding/utils' export default function Units() { const dismiss = useDismiss() - const next = useNextClick(onboardingPaths.CAMERA) + const next = useNextClick(ONBOARDING_SUBPATHS.CAMERA) const { modeling: { defaultUnit }, } = useSettings() diff --git a/src/routes/Onboarding/UserMenu.tsx b/src/routes/Onboarding/UserMenu.tsx index f2b4a5faa02..07228fc0cf9 100644 --- a/src/routes/Onboarding/UserMenu.tsx +++ b/src/routes/Onboarding/UserMenu.tsx @@ -1,9 +1,7 @@ import { useEffect, useState } from 'react' import { useUser } from '@src/lib/singletons' -import { - onboardingPaths, - OnboardingButtons, -} from '@src/routes/Onboarding/utils' +import { OnboardingButtons } from '@src/routes/Onboarding/utils' +import { ONBOARDING_SUBPATHS } from '@src/lib/paths' export default function UserMenu() { const user = useUser() @@ -48,7 +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 e09e8ee932e..5377c9ece45 100644 --- a/src/routes/Onboarding/index.tsx +++ b/src/routes/Onboarding/index.tsx @@ -14,7 +14,8 @@ import ProjectMenu from '@src/routes/Onboarding/ProjectMenu' import Sketching from '@src/routes/Onboarding/Sketching' import Streaming from '@src/routes/Onboarding/Streaming' import UserMenu from '@src/routes/Onboarding/UserMenu' -import { useDismiss, onboardingPaths } from '@src/routes/Onboarding/utils' +import { useDismiss } from '@src/routes/Onboarding/utils' +import { ONBOARDING_SUBPATHS } from '@src/lib/paths' export const onboardingRoutes = [ { @@ -22,48 +23,48 @@ export const onboardingRoutes = [ element: , }, { - path: makeUrlPathRelative(onboardingPaths.CAMERA), + path: makeUrlPathRelative(ONBOARDING_SUBPATHS.CAMERA), element: , }, { - path: makeUrlPathRelative(onboardingPaths.STREAMING), + path: makeUrlPathRelative(ONBOARDING_SUBPATHS.STREAMING), element: , }, { - path: makeUrlPathRelative(onboardingPaths.EDITOR), + path: makeUrlPathRelative(ONBOARDING_SUBPATHS.EDITOR), element: , }, { - path: makeUrlPathRelative(onboardingPaths.PARAMETRIC_MODELING), + path: makeUrlPathRelative(ONBOARDING_SUBPATHS.PARAMETRIC_MODELING), element: , }, { - path: makeUrlPathRelative(onboardingPaths.INTERACTIVE_NUMBERS), + path: makeUrlPathRelative(ONBOARDING_SUBPATHS.INTERACTIVE_NUMBERS), element: , }, { - path: makeUrlPathRelative(onboardingPaths.COMMAND_K), + path: makeUrlPathRelative(ONBOARDING_SUBPATHS.COMMAND_K), element: , }, { - path: makeUrlPathRelative(onboardingPaths.USER_MENU), + path: makeUrlPathRelative(ONBOARDING_SUBPATHS.USER_MENU), element: , }, { - path: makeUrlPathRelative(onboardingPaths.PROJECT_MENU), + path: makeUrlPathRelative(ONBOARDING_SUBPATHS.PROJECT_MENU), element: , }, { - path: makeUrlPathRelative(onboardingPaths.EXPORT), + path: makeUrlPathRelative(ONBOARDING_SUBPATHS.EXPORT), element: , }, // Export / conversion API { - path: makeUrlPathRelative(onboardingPaths.SKETCHING), + path: makeUrlPathRelative(ONBOARDING_SUBPATHS.SKETCHING), element: , }, { - path: makeUrlPathRelative(onboardingPaths.FUTURE_WORK), + path: makeUrlPathRelative(ONBOARDING_SUBPATHS.FUTURE_WORK), element: , }, ] diff --git a/src/routes/Onboarding/paths.ts b/src/routes/Onboarding/paths.ts deleted file mode 100644 index f8a069331ac..00000000000 --- a/src/routes/Onboarding/paths.ts +++ /dev/null @@ -1,17 +0,0 @@ -import type { OnboardingStatus } from '@rust/kcl-lib/bindings/OnboardingStatus' - -export const onboardingPaths: Record = { - INDEX: '/', - CAMERA: '/camera', - STREAMING: '/streaming', - EDITOR: '/editor', - PARAMETRIC_MODELING: '/parametric-modeling', - INTERACTIVE_NUMBERS: '/interactive-numbers', - COMMAND_K: '/command-k', - USER_MENU: '/user-menu', - PROJECT_MENU: '/project-menu', - EXPORT: '/export', - MOVE: '/move', - SKETCHING: '/sketching', - FUTURE_WORK: '/future-work', -} as const diff --git a/src/routes/Onboarding/utils.tsx b/src/routes/Onboarding/utils.tsx index bd54cdaf704..b9b8edc80eb 100644 --- a/src/routes/Onboarding/utils.tsx +++ b/src/routes/Onboarding/utils.tsx @@ -15,7 +15,7 @@ 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 { ONBOARDING_SUBPATHS, PATHS } from '@src/lib/paths' import { codeManager, editorManager, @@ -50,33 +50,11 @@ import { SystemIOMachineEvents } from '@src/machines/systemIO/utils' 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' -export const onboardingPaths: Record = { - INDEX: '/', - CAMERA: '/camera', - STREAMING: '/streaming', - EDITOR: '/editor', - PARAMETRIC_MODELING: '/parametric-modeling', - INTERACTIVE_NUMBERS: '/interactive-numbers', - COMMAND_K: '/command-k', - USER_MENU: '/user-menu', - PROJECT_MENU: '/project-menu', - EXPORT: '/export', - MOVE: '/move', - SKETCHING: '/sketching', - FUTURE_WORK: '/future-work', -} as const - // Get the 1-indexed step number of the current onboarding step function useStepNumber( - slug?: (typeof onboardingPaths)[keyof typeof onboardingPaths] + slug?: (typeof ONBOARDING_SUBPATHS)[keyof typeof ONBOARDING_SUBPATHS] ) { - return slug - ? slug === onboardingPaths.INDEX - ? 1 - : Object.values(onboardingPaths).findIndex( - (r) => r === makeUrlPathRelative(slug) - ) + 1 - : 1 + return slug ? Object.values(ONBOARDING_SUBPATHS).indexOf(slug) + 1 : -1 } export function useDemoCode() { @@ -157,24 +135,22 @@ export function OnboardingButtons({ onNextOverride, ...props }: { - currentSlug?: (typeof onboardingPaths)[keyof typeof onboardingPaths] + currentSlug?: (typeof ONBOARDING_SUBPATHS)[keyof typeof ONBOARDING_SUBPATHS] className?: string dismissClassName?: string onNextOverride?: () => void } & React.HTMLAttributes) { - const onboardingPathsArray = Object.values(onboardingPaths) + const ONBOARDING_SUBPATHSArray = Object.values(ONBOARDING_SUBPATHS) const dismiss = useDismiss() const stepNumber = useStepNumber(currentSlug) const previousStep = - !stepNumber || stepNumber <= 1 ? null : onboardingPathsArray[stepNumber - 2] - const goToPrevious = useNextClick( - onboardingPaths.INDEX + (previousStep ?? '') - ) + !stepNumber || stepNumber <= 1 ? null : ONBOARDING_SUBPATHSArray[stepNumber] + const goToPrevious = useNextClick(previousStep ?? ONBOARDING_SUBPATHS.INDEX) const nextStep = - !stepNumber || stepNumber === onboardingPathsArray.length + !stepNumber || stepNumber === ONBOARDING_SUBPATHSArray.length ? null - : onboardingPathsArray[stepNumber] - const goToNext = useNextClick(onboardingPaths.INDEX + (nextStep ?? '')) + : ONBOARDING_SUBPATHSArray[stepNumber] + const goToNext = useNextClick(nextStep + ONBOARDING_SUBPATHS.INDEX) return ( <> @@ -213,7 +189,7 @@ export function OnboardingButtons({ {stepNumber !== undefined && (

- {stepNumber - 1} / {onboardingPathsArray.length} + {stepNumber} / {ONBOARDING_SUBPATHSArray.length}

)} Date: Tue, 29 Apr 2025 13:37:18 -0400 Subject: [PATCH 15/29] One more dumb created circular dep --- e2e/playwright/storageStates.ts | 4 +--- e2e/playwright/test-utils.ts | 8 ++------ src/App.tsx | 3 ++- src/components/HelpMenu.tsx | 2 +- .../ModelingSidebar/ModelingPane.tsx | 2 +- .../ModelingSidebar/ModelingSidebar.tsx | 2 +- src/components/Settings/AllSettingsFields.tsx | 3 ++- src/lib/constants.ts | 1 + src/lib/onboardingPaths.ts | 16 ++++++++++++++++ src/lib/paths.ts | 19 ++----------------- src/routes/Onboarding/Camera.tsx | 2 +- src/routes/Onboarding/CmdK.tsx | 2 +- src/routes/Onboarding/CodeEditor.tsx | 2 +- src/routes/Onboarding/Export.tsx | 2 +- src/routes/Onboarding/FutureWork.tsx | 2 +- src/routes/Onboarding/InteractiveNumbers.tsx | 2 +- src/routes/Onboarding/Introduction.tsx | 2 +- src/routes/Onboarding/ParametricModeling.tsx | 2 +- src/routes/Onboarding/ProjectMenu.tsx | 2 +- src/routes/Onboarding/Sketching.tsx | 2 +- src/routes/Onboarding/Streaming.tsx | 2 +- src/routes/Onboarding/Units.tsx | 2 +- src/routes/Onboarding/UserMenu.tsx | 2 +- src/routes/Onboarding/index.tsx | 2 +- src/routes/Onboarding/utils.tsx | 3 ++- src/routes/utils.ts | 2 +- 26 files changed, 46 insertions(+), 47 deletions(-) create mode 100644 src/lib/onboardingPaths.ts diff --git a/e2e/playwright/storageStates.ts b/e2e/playwright/storageStates.ts index 16d4f92f577..b4fb96cf46c 100644 --- a/e2e/playwright/storageStates.ts +++ b/e2e/playwright/storageStates.ts @@ -1,12 +1,10 @@ import type { SaveSettingsPayload } from '@src/lib/settings/settingsTypes' import { Themes } from '@src/lib/theme' import type { DeepPartial } from '@src/lib/types' -import { ONBOARDING_SUBPATHS } from '@src/lib/paths' +import { ONBOARDING_SUBPATHS } from '@src/lib/onboardingPaths' import type { Settings } from '@rust/kcl-lib/bindings/Settings' -export const IS_PLAYWRIGHT_KEY = 'playwright' - export const TEST_SETTINGS_KEY = '/settings.toml' export const TEST_SETTINGS: DeepPartial = { app: { diff --git a/e2e/playwright/test-utils.ts b/e2e/playwright/test-utils.ts index 55dcf9eff43..9f4201e09b0 100644 --- a/e2e/playwright/test-utils.ts +++ b/e2e/playwright/test-utils.ts @@ -5,7 +5,7 @@ import type { BrowserContext, Locator, Page, TestInfo } from '@playwright/test' import { expect } from '@playwright/test' import type { EngineCommand } from '@src/lang/std/artifactGraph' import type { Configuration } from '@src/lang/wasm' -import { COOKIE_NAME } from '@src/lib/constants' +import { COOKIE_NAME, IS_PLAYWRIGHT_KEY } from '@src/lib/constants' import { reportRejection } from '@src/lib/trap' import type { DeepPartial } from '@src/lib/types' import { isArray } from '@src/lib/utils' @@ -18,11 +18,7 @@ import type { ProjectConfiguration } from '@rust/kcl-lib/bindings/ProjectConfigu import { isErrorWhitelisted } from '@e2e/playwright/lib/console-error-whitelist' import { secrets } from '@e2e/playwright/secrets' -import { - IS_PLAYWRIGHT_KEY, - TEST_SETTINGS, - TEST_SETTINGS_KEY, -} from '@e2e/playwright/storageStates' +import { TEST_SETTINGS, TEST_SETTINGS_KEY } from '@e2e/playwright/storageStates' import { test } from '@e2e/playwright/zoo-test' const toNormalizedCode = (text: string) => { diff --git a/src/App.tsx b/src/App.tsx index 8337651d130..e087d074e60 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -25,7 +25,7 @@ import { useHotKeyListener } from '@src/hooks/useHotKeyListener' import { writeProjectThumbnailFile } from '@src/lib/desktop' import useHotkeyWrapper from '@src/lib/hotkeyWrapper' import { isDesktop } from '@src/lib/isDesktop' -import { ONBOARDING_SUBPATHS, PATHS } from '@src/lib/paths' +import { PATHS } from '@src/lib/paths' import { takeScreenshotOfVideoStreamCanvas } from '@src/lib/screenshot' import { sceneInfra, codeManager, kclManager } from '@src/lib/singletons' import { maybeWriteToDisk } from '@src/lib/telemetry' @@ -40,6 +40,7 @@ import { ONBOARDING_TOAST_ID, TutorialRequestToast, } from '@src/routes/Onboarding/utils' +import { ONBOARDING_SUBPATHS } from '@src/lib/onboardingPaths' // CYCLIC REF sceneInfra.camControls.engineStreamActor = engineStreamActor diff --git a/src/components/HelpMenu.tsx b/src/components/HelpMenu.tsx index 5f823348a06..c76fd4f16a8 100644 --- a/src/components/HelpMenu.tsx +++ b/src/components/HelpMenu.tsx @@ -13,7 +13,7 @@ import { acceptOnboarding, catchOnboardingWarnError, } from '@src/routes/Onboarding/utils' -import { ONBOARDING_SUBPATHS } from '@src/lib/paths' +import { ONBOARDING_SUBPATHS } from '@src/lib/onboardingPaths' const HelpMenuDivider = () => (
diff --git a/src/components/ModelingSidebar/ModelingPane.tsx b/src/components/ModelingSidebar/ModelingPane.tsx index 9309292c4ee..264da072a0f 100644 --- a/src/components/ModelingSidebar/ModelingPane.tsx +++ b/src/components/ModelingSidebar/ModelingPane.tsx @@ -6,7 +6,7 @@ import { ActionIcon } from '@src/components/ActionIcon' import type { CustomIconName } from '@src/components/CustomIcon' import Tooltip from '@src/components/Tooltip' import { useSettings } from '@src/lib/singletons' -import { ONBOARDING_SUBPATHS } from '@src/lib/paths' +import { ONBOARDING_SUBPATHS } from '@src/lib/onboardingPaths' import styles from './ModelingPane.module.css' diff --git a/src/components/ModelingSidebar/ModelingSidebar.tsx b/src/components/ModelingSidebar/ModelingSidebar.tsx index 497a41e3979..7e4d18450cf 100644 --- a/src/components/ModelingSidebar/ModelingSidebar.tsx +++ b/src/components/ModelingSidebar/ModelingSidebar.tsx @@ -24,7 +24,7 @@ import { SIDEBAR_BUTTON_SUFFIX } from '@src/lib/constants' import { isDesktop } from '@src/lib/isDesktop' import { useSettings } from '@src/lib/singletons' import { commandBarActor } from '@src/lib/singletons' -import { ONBOARDING_SUBPATHS } from '@src/lib/paths' +import { ONBOARDING_SUBPATHS } from '@src/lib/onboardingPaths' import { reportRejection } from '@src/lib/trap' import { refreshPage } from '@src/lib/utils' import { hotkeyDisplay } from '@src/lib/hotkeyWrapper' diff --git a/src/components/Settings/AllSettingsFields.tsx b/src/components/Settings/AllSettingsFields.tsx index 9e3a0dfb14e..ec39dc2204a 100644 --- a/src/components/Settings/AllSettingsFields.tsx +++ b/src/components/Settings/AllSettingsFields.tsx @@ -11,7 +11,8 @@ import { SettingsSection } from '@src/components/Settings/SettingsSection' import { getSettingsFolderPaths } from '@src/lib/desktopFS' import { isDesktop } from '@src/lib/isDesktop' import { openExternalBrowserIfDesktop } from '@src/lib/openWindow' -import { PATHS, ONBOARDING_SUBPATHS } from '@src/lib/paths' +import { ONBOARDING_SUBPATHS } from '@src/lib/onboardingPaths' +import { PATHS } from '@src/lib/paths' import type { Setting } from '@src/lib/settings/initialSettings' import type { SetEventTypes, diff --git a/src/lib/constants.ts b/src/lib/constants.ts index 4a527381d4c..adbdb695090 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -2,6 +2,7 @@ import type { Models } from '@kittycad/lib/dist/types/src' import type { UnitAngle, UnitLength } from '@rust/kcl-lib/bindings/ModelingCmd' +export const IS_PLAYWRIGHT_KEY = 'playwright' export const APP_NAME = 'Design Studio' /** Search string in new project names to increment as an index */ export const INDEX_IDENTIFIER = '$n' diff --git a/src/lib/onboardingPaths.ts b/src/lib/onboardingPaths.ts new file mode 100644 index 00000000000..95abdb1cae9 --- /dev/null +++ b/src/lib/onboardingPaths.ts @@ -0,0 +1,16 @@ +import type { OnboardingStatus } from '@rust/kcl-lib/bindings/OnboardingStatus' + +export const ONBOARDING_SUBPATHS: Record = { + INDEX: '/', + CAMERA: '/camera', + STREAMING: '/streaming', + EDITOR: '/editor', + PARAMETRIC_MODELING: '/parametric-modeling', + INTERACTIVE_NUMBERS: '/interactive-numbers', + COMMAND_K: '/command-k', + USER_MENU: '/user-menu', + PROJECT_MENU: '/project-menu', + EXPORT: '/export', + SKETCHING: '/sketching', + FUTURE_WORK: '/future-work', +} as const diff --git a/src/lib/paths.ts b/src/lib/paths.ts index 09d80a902ac..a5e89d59e70 100644 --- a/src/lib/paths.ts +++ b/src/lib/paths.ts @@ -2,7 +2,7 @@ import type { PlatformPath } from 'path' import type { Configuration } from '@rust/kcl-lib/bindings/Configuration' -import { IS_PLAYWRIGHT_KEY } from '@e2e/playwright/storageStates' +import { IS_PLAYWRIGHT_KEY } from '@src/lib/constants' import { BROWSER_FILE_NAME, @@ -14,22 +14,7 @@ import { isDesktop } from '@src/lib/isDesktop' import { readLocalStorageAppSettingsFile } from '@src/lib/settings/settingsUtils' import { err } from '@src/lib/trap' import type { DeepPartial } from '@src/lib/types' -import type { OnboardingStatus } from '@rust/kcl-lib/bindings/OnboardingStatus' - -export const ONBOARDING_SUBPATHS: Record = { - INDEX: '/', - CAMERA: '/camera', - STREAMING: '/streaming', - EDITOR: '/editor', - PARAMETRIC_MODELING: '/parametric-modeling', - INTERACTIVE_NUMBERS: '/interactive-numbers', - COMMAND_K: '/command-k', - USER_MENU: '/user-menu', - PROJECT_MENU: '/project-menu', - EXPORT: '/export', - SKETCHING: '/sketching', - FUTURE_WORK: '/future-work', -} as const +import { ONBOARDING_SUBPATHS } from '@src/lib/onboardingPaths' const prependRoutes = (routesObject: Record) => (prepend: string) => { diff --git a/src/routes/Onboarding/Camera.tsx b/src/routes/Onboarding/Camera.tsx index 964052ad8e7..199e26bed5d 100644 --- a/src/routes/Onboarding/Camera.tsx +++ b/src/routes/Onboarding/Camera.tsx @@ -1,7 +1,7 @@ import { SettingsSection } from '@src/components/Settings/SettingsSection' import type { CameraSystem } from '@src/lib/cameraControls' import { cameraMouseDragGuards, cameraSystems } from '@src/lib/cameraControls' -import { ONBOARDING_SUBPATHS } from '@src/lib/paths' +import { ONBOARDING_SUBPATHS } from '@src/lib/onboardingPaths' import { settingsActor, useSettings } from '@src/lib/singletons' import { OnboardingButtons } from '@src/routes/Onboarding/utils' diff --git a/src/routes/Onboarding/CmdK.tsx b/src/routes/Onboarding/CmdK.tsx index 25bc97103aa..161f2974283 100644 --- a/src/routes/Onboarding/CmdK.tsx +++ b/src/routes/Onboarding/CmdK.tsx @@ -1,7 +1,7 @@ import { COMMAND_PALETTE_HOTKEY } from '@src/components/CommandBar/CommandBar' import usePlatform from '@src/hooks/usePlatform' import { hotkeyDisplay } from '@src/lib/hotkeyWrapper' -import { ONBOARDING_SUBPATHS } from '@src/lib/paths' +import { ONBOARDING_SUBPATHS } from '@src/lib/onboardingPaths' import { OnboardingButtons, kbdClasses } from '@src/routes/Onboarding/utils' export default function CmdK() { diff --git a/src/routes/Onboarding/CodeEditor.tsx b/src/routes/Onboarding/CodeEditor.tsx index b5446db1f3d..597885016d7 100644 --- a/src/routes/Onboarding/CodeEditor.tsx +++ b/src/routes/Onboarding/CodeEditor.tsx @@ -1,4 +1,4 @@ -import { ONBOARDING_SUBPATHS } from '@src/lib/paths' +import { ONBOARDING_SUBPATHS } from '@src/lib/onboardingPaths' import { OnboardingButtons, kbdClasses, diff --git a/src/routes/Onboarding/Export.tsx b/src/routes/Onboarding/Export.tsx index 400abd4413f..a7274c06f74 100644 --- a/src/routes/Onboarding/Export.tsx +++ b/src/routes/Onboarding/Export.tsx @@ -1,5 +1,5 @@ import { APP_NAME } from '@src/lib/constants' -import { ONBOARDING_SUBPATHS } from '@src/lib/paths' +import { ONBOARDING_SUBPATHS } from '@src/lib/onboardingPaths' import { OnboardingButtons } from '@src/routes/Onboarding/utils' export default function Export() { diff --git a/src/routes/Onboarding/FutureWork.tsx b/src/routes/Onboarding/FutureWork.tsx index e0e7a966d93..0fbd7585584 100644 --- a/src/routes/Onboarding/FutureWork.tsx +++ b/src/routes/Onboarding/FutureWork.tsx @@ -3,7 +3,7 @@ import { useModelingContext } from '@src/hooks/useModelingContext' import { APP_NAME } from '@src/lib/constants' import { sceneInfra } from '@src/lib/singletons' import { OnboardingButtons, useDemoCode } from '@src/routes/Onboarding/utils' -import { ONBOARDING_SUBPATHS } from '@src/lib/paths' +import { ONBOARDING_SUBPATHS } from '@src/lib/onboardingPaths' export default function FutureWork() { const { send } = useModelingContext() diff --git a/src/routes/Onboarding/InteractiveNumbers.tsx b/src/routes/Onboarding/InteractiveNumbers.tsx index 62736fe1b87..a6b336ac2c8 100644 --- a/src/routes/Onboarding/InteractiveNumbers.tsx +++ b/src/routes/Onboarding/InteractiveNumbers.tsx @@ -1,5 +1,5 @@ import { bracketWidthConstantLine } from '@src/lib/exampleKcl' -import { ONBOARDING_SUBPATHS } from '@src/lib/paths' +import { ONBOARDING_SUBPATHS } from '@src/lib/onboardingPaths' import { OnboardingButtons, kbdClasses, diff --git a/src/routes/Onboarding/Introduction.tsx b/src/routes/Onboarding/Introduction.tsx index 1b2e256c583..e89d7bf4360 100644 --- a/src/routes/Onboarding/Introduction.tsx +++ b/src/routes/Onboarding/Introduction.tsx @@ -3,7 +3,7 @@ import { isDesktop } from '@src/lib/isDesktop' import { Themes, getSystemTheme } from '@src/lib/theme' import { useSettings } from '@src/lib/singletons' import { OnboardingButtons, useDemoCode } from '@src/routes/Onboarding/utils' -import { ONBOARDING_SUBPATHS } from '@src/lib/paths' +import { ONBOARDING_SUBPATHS } from '@src/lib/onboardingPaths' export default function Introduction() { // Reset the code to the bracket code diff --git a/src/routes/Onboarding/ParametricModeling.tsx b/src/routes/Onboarding/ParametricModeling.tsx index 01de9dc648b..189d0044930 100644 --- a/src/routes/Onboarding/ParametricModeling.tsx +++ b/src/routes/Onboarding/ParametricModeling.tsx @@ -3,7 +3,7 @@ import { isDesktop } from '@src/lib/isDesktop' import { Themes, getSystemTheme } from '@src/lib/theme' import { useSettings } from '@src/lib/singletons' import { OnboardingButtons, useDemoCode } from '@src/routes/Onboarding/utils' -import { ONBOARDING_SUBPATHS } from '@src/lib/paths' +import { ONBOARDING_SUBPATHS } from '@src/lib/onboardingPaths' export default function OnboardingParametricModeling() { useDemoCode() diff --git a/src/routes/Onboarding/ProjectMenu.tsx b/src/routes/Onboarding/ProjectMenu.tsx index 1b82e9c60df..6e73d9a791d 100644 --- a/src/routes/Onboarding/ProjectMenu.tsx +++ b/src/routes/Onboarding/ProjectMenu.tsx @@ -1,5 +1,5 @@ import { isDesktop } from '@src/lib/isDesktop' -import { ONBOARDING_SUBPATHS } from '@src/lib/paths' +import { ONBOARDING_SUBPATHS } from '@src/lib/onboardingPaths' import { OnboardingButtons } from '@src/routes/Onboarding/utils' export default function ProjectMenu() { diff --git a/src/routes/Onboarding/Sketching.tsx b/src/routes/Onboarding/Sketching.tsx index e04407c7b71..652557048f5 100644 --- a/src/routes/Onboarding/Sketching.tsx +++ b/src/routes/Onboarding/Sketching.tsx @@ -1,7 +1,7 @@ import { useEffect } from 'react' import { codeManager, kclManager } from '@src/lib/singletons' import { OnboardingButtons } from '@src/routes/Onboarding/utils' -import { ONBOARDING_SUBPATHS } from '@src/lib/paths' +import { ONBOARDING_SUBPATHS } from '@src/lib/onboardingPaths' export default function Sketching() { useEffect(() => { diff --git a/src/routes/Onboarding/Streaming.tsx b/src/routes/Onboarding/Streaming.tsx index d3a7e3dcc83..1583ad92247 100644 --- a/src/routes/Onboarding/Streaming.tsx +++ b/src/routes/Onboarding/Streaming.tsx @@ -1,4 +1,4 @@ -import { ONBOARDING_SUBPATHS } from '@src/lib/paths' +import { ONBOARDING_SUBPATHS } from '@src/lib/onboardingPaths' import { OnboardingButtons } from '@src/routes/Onboarding/utils' export default function Streaming() { diff --git a/src/routes/Onboarding/Units.tsx b/src/routes/Onboarding/Units.tsx index 049d3dbc319..b56a1cde69c 100644 --- a/src/routes/Onboarding/Units.tsx +++ b/src/routes/Onboarding/Units.tsx @@ -4,7 +4,7 @@ import { ActionButton } from '@src/components/ActionButton' import { SettingsSection } from '@src/components/Settings/SettingsSection' import { type BaseUnit, baseUnitsUnion } from '@src/lib/settings/settingsTypes' import { settingsActor, useSettings } from '@src/lib/singletons' -import { ONBOARDING_SUBPATHS } from '@src/lib/paths' +import { ONBOARDING_SUBPATHS } from '@src/lib/onboardingPaths' import { useDismiss, useNextClick } from '@src/routes/Onboarding/utils' export default function Units() { diff --git a/src/routes/Onboarding/UserMenu.tsx b/src/routes/Onboarding/UserMenu.tsx index 07228fc0cf9..53347934937 100644 --- a/src/routes/Onboarding/UserMenu.tsx +++ b/src/routes/Onboarding/UserMenu.tsx @@ -1,7 +1,7 @@ import { useEffect, useState } from 'react' import { useUser } from '@src/lib/singletons' import { OnboardingButtons } from '@src/routes/Onboarding/utils' -import { ONBOARDING_SUBPATHS } from '@src/lib/paths' +import { ONBOARDING_SUBPATHS } from '@src/lib/onboardingPaths' export default function UserMenu() { const user = useUser() diff --git a/src/routes/Onboarding/index.tsx b/src/routes/Onboarding/index.tsx index 5377c9ece45..dbf7be27824 100644 --- a/src/routes/Onboarding/index.tsx +++ b/src/routes/Onboarding/index.tsx @@ -15,7 +15,7 @@ import Sketching from '@src/routes/Onboarding/Sketching' import Streaming from '@src/routes/Onboarding/Streaming' import UserMenu from '@src/routes/Onboarding/UserMenu' import { useDismiss } from '@src/routes/Onboarding/utils' -import { ONBOARDING_SUBPATHS } from '@src/lib/paths' +import { ONBOARDING_SUBPATHS } from '@src/lib/onboardingPaths' export const onboardingRoutes = [ { diff --git a/src/routes/Onboarding/utils.tsx b/src/routes/Onboarding/utils.tsx index b9b8edc80eb..719a6d62935 100644 --- a/src/routes/Onboarding/utils.tsx +++ b/src/routes/Onboarding/utils.tsx @@ -15,7 +15,8 @@ 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 { ONBOARDING_SUBPATHS, PATHS } from '@src/lib/paths' +import { ONBOARDING_SUBPATHS } from '@src/lib/onboardingPaths' +import { PATHS } from '@src/lib/paths' import { codeManager, editorManager, diff --git a/src/routes/utils.ts b/src/routes/utils.ts index 5cadccea19e..bd8bcbd9aa4 100644 --- a/src/routes/utils.ts +++ b/src/routes/utils.ts @@ -1,7 +1,7 @@ import { NODE_ENV } from '@src/env' import { isDesktop } from '@src/lib/isDesktop' -import { IS_PLAYWRIGHT_KEY } from '@e2e/playwright/storageStates' +import { IS_PLAYWRIGHT_KEY } from '@src/lib/constants' const isTestEnv = window?.localStorage.getItem(IS_PLAYWRIGHT_KEY) === 'true' From a75155c57905df4d88c4d07ab61e349c74b3de28 Mon Sep 17 00:00:00 2001 From: Frank Noirot Date: Tue, 29 Apr 2025 14:11:26 -0400 Subject: [PATCH 16/29] Update src/routes/Onboarding/utils.tsx Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com> --- src/routes/Onboarding/utils.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/routes/Onboarding/utils.tsx b/src/routes/Onboarding/utils.tsx index 719a6d62935..93b5b2983b8 100644 --- a/src/routes/Onboarding/utils.tsx +++ b/src/routes/Onboarding/utils.tsx @@ -449,9 +449,11 @@ export async function createAndOpenNewTutorialProject( configuration ) - const filePathAsUri = encodeURIComponent( - newProject.default_file.replace(newProject.path, '') + const relativePath = window.electron.path.relative( + newProject.path, + newProject.default_file ) + const filePathAsUri = encodeURIComponent(relativePath) const subRoute = `${filePathAsUri}${PATHS.ONBOARDING.INDEX}${makeUrlPathRelative( onboardingStatus )}` From 3e6a81f16a1c20b3451fff3c87879f3db2753608 Mon Sep 17 00:00:00 2001 From: Frank Noirot Date: Tue, 29 Apr 2025 16:48:18 -0400 Subject: [PATCH 17/29] Fix narrow screen home layout breaking --- src/routes/Home.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/Home.tsx b/src/routes/Home.tsx index 585ab9af7f5..aed35b0b846 100644 --- a/src/routes/Home.tsx +++ b/src/routes/Home.tsx @@ -215,7 +215,7 @@ const Home = () => { readWriteProjectDir={readWriteProjectDir} className="col-start-2 -col-end-1" /> -
- ) + ); } /** @@ -359,24 +360,24 @@ export function TutorialRequestToast(props: OnboardingUtilDeps) { */ export async function catchOnboardingWarnError( err: Error, - props: OnboardingUtilDeps + props: OnboardingUtilDeps, ) { if (err instanceof Error && err.message === ERROR_MUST_WARN) { toast.success(TutorialWebConfirmationToast(props), { id: ONBOARDING_TOAST_ID, duration: Number.POSITIVE_INFINITY, icon: null, - }) + }); } else { - toast.dismiss(ONBOARDING_TOAST_ID) - return reportRejection(err) + toast.dismiss(ONBOARDING_TOAST_ID); + return reportRejection(err); } } export function TutorialWebConfirmationToast(props: OnboardingUtilDeps) { function onAccept() { - toast.dismiss(ONBOARDING_TOAST_ID) - resetCodeAndAdvanceOnboarding(props).catch(reportRejection) + toast.dismiss(ONBOARDING_TOAST_ID); + resetCodeAndAdvanceOnboarding(props).catch(reportRejection); } return ( @@ -397,7 +398,7 @@ export function TutorialWebConfirmationToast(props: OnboardingUtilDeps) { - ) + ); } From fec8a5a25af6d9218b3464ceb28a1c80765a297b Mon Sep 17 00:00:00 2001 From: Frank Noirot Date: Thu, 1 May 2025 11:13:28 -0400 Subject: [PATCH 24/29] Little biome formatting suggestion fix --- src/lib/paths.ts | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/lib/paths.ts b/src/lib/paths.ts index 7d921d6a4db..0a20ae398d6 100644 --- a/src/lib/paths.ts +++ b/src/lib/paths.ts @@ -147,13 +147,10 @@ export function parseProjectRoute( * /dog/cat */ export function joinRouterPaths(...parts: string[]): string { - return ( - '/' + - parts - .map((part) => part.replace(/^\/+|\/+$/g, '')) // Remove leading/trailing slashes - .filter((part) => part.length > 0) // Remove empty segments - .join('/') - ) + return `/${parts + .map((part) => part.replace(/^\/+|\/+$/g, '')) // Remove leading/trailing slashes + .filter((part) => part.length > 0) // Remove empty segments + .join('/')}` } /** From 144d4d3d8bfd5ca45a6ad07b28c82bf501d9cb0f Mon Sep 17 00:00:00 2001 From: Frank Noirot Date: Thu, 1 May 2025 11:13:48 -0400 Subject: [PATCH 25/29] Units onboarding step was not using OnboardingButtons --- src/routes/Onboarding/Units.tsx | 33 +++++---------------------------- 1 file changed, 5 insertions(+), 28 deletions(-) diff --git a/src/routes/Onboarding/Units.tsx b/src/routes/Onboarding/Units.tsx index b56a1cde69c..e29541d932e 100644 --- a/src/routes/Onboarding/Units.tsx +++ b/src/routes/Onboarding/Units.tsx @@ -1,15 +1,10 @@ -import { faArrowRight, faXmark } from '@fortawesome/free-solid-svg-icons' - -import { ActionButton } from '@src/components/ActionButton' import { SettingsSection } from '@src/components/Settings/SettingsSection' import { type BaseUnit, baseUnitsUnion } from '@src/lib/settings/settingsTypes' import { settingsActor, useSettings } from '@src/lib/singletons' +import { OnboardingButtons } from '@src/routes/Onboarding/utils' import { ONBOARDING_SUBPATHS } from '@src/lib/onboardingPaths' -import { useDismiss, useNextClick } from '@src/routes/Onboarding/utils' export default function Units() { - const dismiss = useDismiss() - const next = useNextClick(ONBOARDING_SUBPATHS.CAMERA) const { modeling: { defaultUnit }, } = useSettings() @@ -43,28 +38,10 @@ export default function Units() { ))} -
- - Dismiss - - - Next: Camera - -
+ ) From a6b0fc82be96dfa40fe930f7054af7363ea7a82b Mon Sep 17 00:00:00 2001 From: Frank Noirot Date: Thu, 1 May 2025 11:14:13 -0400 Subject: [PATCH 26/29] Add type checking of next and previous status, fix useNextClick --- src/routes/Onboarding/index.tsx | 2 +- src/routes/Onboarding/utils.tsx | 291 +++++++++++++++++--------------- 2 files changed, 158 insertions(+), 135 deletions(-) diff --git a/src/routes/Onboarding/index.tsx b/src/routes/Onboarding/index.tsx index dbf7be27824..eab895c6f84 100644 --- a/src/routes/Onboarding/index.tsx +++ b/src/routes/Onboarding/index.tsx @@ -71,7 +71,7 @@ export const onboardingRoutes = [ const Onboarding = () => { const dismiss = useDismiss() - useHotkeys('esc', dismiss) + useHotkeys('esc', () => dismiss()) return (
diff --git a/src/routes/Onboarding/utils.tsx b/src/routes/Onboarding/utils.tsx index c14ad081ccb..d98216cb0fe 100644 --- a/src/routes/Onboarding/utils.tsx +++ b/src/routes/Onboarding/utils.tsx @@ -1,63 +1,66 @@ -import { useCallback, useEffect } from "react"; +import { useCallback, useEffect } from 'react' import { type NavigateFunction, type useLocation, useNavigate, -} from "react-router-dom"; -import { waitFor } from "xstate"; +} 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 { joinRouterPaths, PATHS } from "@src/lib/paths"; +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 { joinRouterPaths, PATHS } from '@src/lib/paths' import { codeManager, editorManager, kclManager, systemIOActor, -} from "@src/lib/singletons"; -import { reportRejection, trap } from "@src/lib/trap"; -import { settingsActor } from "@src/lib/singletons"; -import { isKclEmptyOrOnlySettings, parse, resultIsOk } from "@src/lang/wasm"; -import { updateModelingState } from "@src/lang/modelingWorkflows"; +} from '@src/lib/singletons' +import { err, reportRejection, trap } from '@src/lib/trap' +import { settingsActor } from '@src/lib/singletons' +import { isKclEmptyOrOnlySettings, parse, resultIsOk } from '@src/lang/wasm' +import { updateModelingState } from '@src/lang/modelingWorkflows' import { DEFAULT_PROJECT_KCL_FILE, EXECUTION_TYPE_REAL, ONBOARDING_PROJECT_NAME, -} from "@src/lib/constants"; -import toast from "react-hot-toast"; -import type CodeManager from "@src/lang/codeManager"; -import type { OnboardingStatus } from "@rust/kcl-lib/bindings/OnboardingStatus"; -import { isDesktop } from "@src/lib/isDesktop"; -import type { KclManager } from "@src/lang/KclSingleton"; -import { Logo } from "@src/components/Logo"; -import { SystemIOMachineEvents } from "@src/machines/systemIO/utils"; -import { ONBOARDING_SUBPATHS } from "@src/lib/onboardingPaths"; +} from '@src/lib/constants' +import toast from 'react-hot-toast' +import type CodeManager from '@src/lang/codeManager' +import type { OnboardingStatus } from '@rust/kcl-lib/bindings/OnboardingStatus' +import { isDesktop } from '@src/lib/isDesktop' +import type { KclManager } from '@src/lang/KclSingleton' +import { Logo } from '@src/components/Logo' +import { SystemIOMachineEvents } from '@src/machines/systemIO/utils' +import { + isOnboardingSubPath, + ONBOARDING_SUBPATHS, +} from '@src/lib/onboardingPaths' 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"; + '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 ONBOARDING_SUBPATHS)[keyof typeof ONBOARDING_SUBPATHS], + slug?: (typeof ONBOARDING_SUBPATHS)[keyof typeof ONBOARDING_SUBPATHS] ) { - return slug ? Object.values(ONBOARDING_SUBPATHS).indexOf(slug) + 1 : -1; + return slug ? Object.values(ONBOARDING_SUBPATHS).indexOf(slug) + 1 : -1 } export function useDemoCode() { - const { overallState, immediateState } = useNetworkContext(); + 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; + return } // Don't run if the network isn't healthy or the connection isn't established if ( @@ -65,62 +68,75 @@ export function useDemoCode() { overallState === NetworkHealthState.Issue || immediateState.type !== EngineConnectionStateType.ConnectionEstablished ) { - return; + return } - const pResult = parse(bracket); + const pResult = parse(bracket) if (trap(pResult) || !resultIsOk(pResult)) { - return Promise.reject(pResult); + return Promise.reject(pResult) } - const ast = pResult.program; + const ast = pResult.program await updateModelingState(ast, EXECUTION_TYPE_REAL, { kclManager: kclManager, editorManager: editorManager, codeManager: codeManager, - }); + }) } - setCodeToDemoIfNeeded().catch(reportRejection); - }, [editorManager.editorView, immediateState.type, overallState]); + setCodeToDemoIfNeeded().catch(reportRejection) + }, [editorManager.editorView, immediateState.type, overallState]) } -export function useNextClick(newStatus: string) { - const filePath = useAbsoluteFilePath(); - const navigate = useNavigate(); +export function useNextClick(newStatus: OnboardingStatus) { + const filePath = useAbsoluteFilePath() + const navigate = useNavigate() return useCallback(() => { + if (!isOnboardingSubPath(newStatus)) { + return new Error( + `Failed to navigate to invalid onboarding status ${newStatus}` + ) + } settingsActor.send({ - type: "set.app.onboardingStatus", - data: { level: "user", value: newStatus }, - }); - navigate(filePath + PATHS.ONBOARDING.INDEX.slice(0, -1) + newStatus); - }, [filePath, newStatus, navigate]); + type: 'set.app.onboardingStatus', + data: { level: 'user', value: newStatus }, + }) + navigate(joinRouterPaths(filePath, PATHS.ONBOARDING.INDEX, newStatus)) + }, [filePath, newStatus, navigate]) } export function useDismiss() { - const filePath = useAbsoluteFilePath(); - const send = settingsActor.send; - const navigate = useNavigate(); + 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); - toast.success( - "Click the question mark in the lower-right corner if you ever want to redo the tutorial!", - { - duration: 5_000, - }, - ); + const settingsCallback = useCallback( + ( + dismissalType: + | Extract + | undefined = 'dismissed' + ) => { + send({ + type: 'set.app.onboardingStatus', + data: { level: 'user', value: dismissalType }, }) - .catch(reportRejection); - }, [send, filePath, navigate]); + waitFor(settingsActor, (state) => state.matches('idle')) + .then(() => { + navigate(filePath) + toast.success( + 'Click the question mark in the lower-right corner if you ever want to redo the tutorial!', + { + duration: 5_000, + } + ) + }) + .catch(reportRejection) + }, + [send, filePath, navigate] + ) - return settingsCallback; + return settingsCallback } + export function OnboardingButtons({ currentSlug, className, @@ -128,29 +144,33 @@ export function OnboardingButtons({ onNextOverride, ...props }: { - currentSlug?: (typeof ONBOARDING_SUBPATHS)[keyof typeof ONBOARDING_SUBPATHS]; - className?: string; - dismissClassName?: string; - onNextOverride?: () => void; + currentSlug?: (typeof ONBOARDING_SUBPATHS)[keyof typeof ONBOARDING_SUBPATHS] + className?: string + dismissClassName?: string + onNextOverride?: () => void } & React.HTMLAttributes) { - const onboardingPathsArray = Object.values(ONBOARDING_SUBPATHS); - const dismiss = useDismiss(); - const stepNumber = useStepNumber(currentSlug); + const onboardingPathsArray = Object.values(ONBOARDING_SUBPATHS) + const dismiss = useDismiss() + const stepNumber = useStepNumber(currentSlug) const previousStep = - !stepNumber || stepNumber <= 1 ? null : onboardingPathsArray[stepNumber]; - const goToPrevious = useNextClick(previousStep ?? ONBOARDING_SUBPATHS.INDEX); + !stepNumber || stepNumber <= 1 ? null : onboardingPathsArray[stepNumber] const nextStep = !stepNumber || stepNumber === onboardingPathsArray.length ? null - : onboardingPathsArray[stepNumber]; - // TODO: replace with `??` once I prove this is where things went wrong - const goToNext = useNextClick(nextStep + ONBOARDING_SUBPATHS.INDEX); + : onboardingPathsArray[stepNumber] + + const previousOnboardingStatus: OnboardingStatus = + previousStep ?? ONBOARDING_SUBPATHS.INDEX + const nextOnboardingStatus: OnboardingStatus = nextStep ?? 'completed' + + const goToPrevious = useNextClick(previousOnboardingStatus) + const goToNext = useNextClick(nextOnboardingStatus) return ( <>
(previousStep ? goToPrevious() : dismiss())} iconStart={{ - icon: previousStep ? "arrowLeft" : "close", - className: "text-chalkboard-10", - bgClassName: "bg-destroy-80 group-hover:bg-destroy-80", + 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"} + {previousStep ? 'Back' : 'Dismiss'} {stepNumber !== undefined && (

@@ -191,33 +211,36 @@ export function OnboardingButtons({ Element="button" onClick={() => { if (nextStep) { - onNextOverride ? onNextOverride() : goToNext(); + const result = onNextOverride ? onNextOverride() : goToNext() + if (err(result)) { + reportRejection(result) + } } else { - dismiss(); + dismiss('completed') } }} iconStart={{ - icon: nextStep ? "arrowRight" : "checkmark", - bgClassName: "dark:bg-chalkboard-80", + icon: nextStep ? 'arrowRight' : 'checkmark', + bgClassName: 'dark:bg-chalkboard-80', }} className="dark:hover:bg-chalkboard-80/50" data-testid="onboarding-next" > - {nextStep ? "Next" : "Finish"} + {nextStep ? 'Next' : 'Finish'}

- ); + ) } export interface OnboardingUtilDeps { - onboardingStatus: OnboardingStatus; - codeManager: CodeManager; - kclManager: KclManager; - navigate: NavigateFunction; + onboardingStatus: OnboardingStatus + codeManager: CodeManager + kclManager: KclManager + navigate: NavigateFunction } -export const ERROR_MUST_WARN = "Must warn user before overwrite"; +export const ERROR_MUST_WARN = 'Must warn user before overwrite' /** * Accept to begin the onboarding tutorial, @@ -234,19 +257,19 @@ export async function acceptOnboarding(deps: OnboardingUtilDeps) { requestedCode: bracket, requestedSubRoute: joinRouterPaths( PATHS.ONBOARDING.INDEX, - deps.onboardingStatus, + deps.onboardingStatus ), }, - }); - return Promise.resolve(); + }) + return Promise.resolve() } - const isCodeResettable = hasResetReadyCode(deps.codeManager); + const isCodeResettable = hasResetReadyCode(deps.codeManager) if (isCodeResettable) { - return resetCodeAndAdvanceOnboarding(deps); + return resetCodeAndAdvanceOnboarding(deps) } - return Promise.reject(new Error(ERROR_MUST_WARN)); + return Promise.reject(new Error(ERROR_MUST_WARN)) } /** @@ -260,57 +283,57 @@ export async function resetCodeAndAdvanceOnboarding({ navigate, }: OnboardingUtilDeps) { // We do want to update both the state and editor here. - codeManager.updateCodeStateEditor(bracket); - codeManager.writeToFile().catch(reportRejection); - kclManager.executeCode().catch(reportRejection); + codeManager.updateCodeStateEditor(bracket) + codeManager.writeToFile().catch(reportRejection) + kclManager.executeCode().catch(reportRejection) // TODO: this is not navigating to the correct `/onboarding/blah` path yet navigate( makeUrlPathRelative( - `${PATHS.ONBOARDING.INDEX}${makeUrlPathRelative(onboardingStatus)}`, - ), - ); + `${PATHS.ONBOARDING.INDEX}${makeUrlPathRelative(onboardingStatus)}` + ) + ) } function hasResetReadyCode(codeManager: CodeManager) { return ( isKclEmptyOrOnlySettings(codeManager.code) || codeManager.code === bracket - ); + ) } export function needsToOnboard( location: ReturnType, - onboardingStatus: OnboardingStatus, + onboardingStatus: OnboardingStatus ) { return ( !location.pathname.includes(PATHS.ONBOARDING.INDEX) && (onboardingStatus.length === 0 || - !(onboardingStatus === "completed" || onboardingStatus === "dismissed")) - ); + !(onboardingStatus === 'completed' || onboardingStatus === 'dismissed')) + ) } -export const ONBOARDING_TOAST_ID = "onboarding-toast"; +export const ONBOARDING_TOAST_ID = 'onboarding-toast' export function onDismissOnboardingInvite() { settingsActor.send({ - type: "set.app.onboardingStatus", - data: { level: "user", value: "dismissed" }, - }); - toast.dismiss(ONBOARDING_TOAST_ID); + type: 'set.app.onboardingStatus', + data: { level: 'user', value: 'dismissed' }, + }) + toast.dismiss(ONBOARDING_TOAST_ID) toast.success( - "Click the question mark in the lower-right corner if you ever want to do the tutorial!", + 'Click the question mark in the lower-right corner if you ever want to do the tutorial!', { duration: 5_000, - }, - ); + } + ) } export function TutorialRequestToast(props: OnboardingUtilDeps) { function onAccept() { acceptOnboarding(props) .then(() => { - toast.dismiss(ONBOARDING_TOAST_ID); + toast.dismiss(ONBOARDING_TOAST_ID) }) - .catch((reason) => catchOnboardingWarnError(reason, props)); + .catch((reason) => catchOnboardingWarnError(reason, props)) } return ( @@ -330,7 +353,7 @@ export function TutorialRequestToast(props: OnboardingUtilDeps) {
- ); + ) } /** @@ -360,24 +383,24 @@ export function TutorialRequestToast(props: OnboardingUtilDeps) { */ export async function catchOnboardingWarnError( err: Error, - props: OnboardingUtilDeps, + props: OnboardingUtilDeps ) { if (err instanceof Error && err.message === ERROR_MUST_WARN) { toast.success(TutorialWebConfirmationToast(props), { id: ONBOARDING_TOAST_ID, duration: Number.POSITIVE_INFINITY, icon: null, - }); + }) } else { - toast.dismiss(ONBOARDING_TOAST_ID); - return reportRejection(err); + toast.dismiss(ONBOARDING_TOAST_ID) + return reportRejection(err) } } export function TutorialWebConfirmationToast(props: OnboardingUtilDeps) { function onAccept() { - toast.dismiss(ONBOARDING_TOAST_ID); - resetCodeAndAdvanceOnboarding(props).catch(reportRejection); + toast.dismiss(ONBOARDING_TOAST_ID) + resetCodeAndAdvanceOnboarding(props).catch(reportRejection) } return ( @@ -398,7 +421,7 @@ export function TutorialWebConfirmationToast(props: OnboardingUtilDeps) { - ); + ) } From dd403397a3ec6d36c830824d5c4ab7b2e3ce0324 Mon Sep 17 00:00:00 2001 From: Frank Noirot Date: Thu, 1 May 2025 11:28:31 -0400 Subject: [PATCH 27/29] Thanks Graphite Diamond, I should use that new util --- src/routes/Onboarding/utils.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/Onboarding/utils.tsx b/src/routes/Onboarding/utils.tsx index d98216cb0fe..23ae49e1c15 100644 --- a/src/routes/Onboarding/utils.tsx +++ b/src/routes/Onboarding/utils.tsx @@ -289,7 +289,7 @@ export async function resetCodeAndAdvanceOnboarding({ // TODO: this is not navigating to the correct `/onboarding/blah` path yet navigate( makeUrlPathRelative( - `${PATHS.ONBOARDING.INDEX}${makeUrlPathRelative(onboardingStatus)}` + joinRouterPaths(PATHS.ONBOARDING.INDEX, onboardingStatus) ) ) } From 4e25ae404e1f118707b37bda8008ad8806daec66 Mon Sep 17 00:00:00 2001 From: Frank Noirot Date: Thu, 1 May 2025 11:29:47 -0400 Subject: [PATCH 28/29] Remove TODO comment --- src/routes/Onboarding/utils.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/routes/Onboarding/utils.tsx b/src/routes/Onboarding/utils.tsx index 23ae49e1c15..87361049c00 100644 --- a/src/routes/Onboarding/utils.tsx +++ b/src/routes/Onboarding/utils.tsx @@ -286,7 +286,6 @@ export async function resetCodeAndAdvanceOnboarding({ codeManager.updateCodeStateEditor(bracket) codeManager.writeToFile().catch(reportRejection) kclManager.executeCode().catch(reportRejection) - // TODO: this is not navigating to the correct `/onboarding/blah` path yet navigate( makeUrlPathRelative( joinRouterPaths(PATHS.ONBOARDING.INDEX, onboardingStatus) From f070033c01f3b7872c9d9fb1ec0f63423842b18d Mon Sep 17 00:00:00 2001 From: Frank Noirot Date: Thu, 1 May 2025 18:35:17 -0400 Subject: [PATCH 29/29] Fix botched merge because IS_PLAYWRIGHT moved or something --- e2e/playwright/test-utils.ts | 1 - src/lib/constants.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/e2e/playwright/test-utils.ts b/e2e/playwright/test-utils.ts index 56371ff3055..9f4201e09b0 100644 --- a/e2e/playwright/test-utils.ts +++ b/e2e/playwright/test-utils.ts @@ -19,7 +19,6 @@ import type { ProjectConfiguration } from '@rust/kcl-lib/bindings/ProjectConfigu import { isErrorWhitelisted } from '@e2e/playwright/lib/console-error-whitelist' import { secrets } from '@e2e/playwright/secrets' import { TEST_SETTINGS, TEST_SETTINGS_KEY } from '@e2e/playwright/storageStates' -import { IS_PLAYWRIGHT_KEY } from '@src/lib/constants' import { test } from '@e2e/playwright/zoo-test' const toNormalizedCode = (text: string) => { diff --git a/src/lib/constants.ts b/src/lib/constants.ts index 9789fbc2183..b438fd9dd26 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -2,7 +2,6 @@ import type { Models } from '@kittycad/lib/dist/types/src' import type { UnitAngle, UnitLength } from '@rust/kcl-lib/bindings/ModelingCmd' -export const IS_PLAYWRIGHT_KEY = 'playwright' export const APP_NAME = 'Design Studio' /** Search string in new project names to increment as an index */ export const INDEX_IDENTIFIER = '$n'