diff --git a/e2e/playwright/onboarding-tests.spec.ts b/e2e/playwright/onboarding-tests.spec.ts
index 84288213b..6bee39f40 100644
--- a/e2e/playwright/onboarding-tests.spec.ts
+++ b/e2e/playwright/onboarding-tests.spec.ts
@@ -425,7 +425,9 @@ test(
const restartConfirmationButton = page.getByRole('button', {
name: 'Make a new project',
})
- const tutorialProjectIndicator = page.getByText('Tutorial Project 00')
+ const tutorialProjectIndicator = page
+ .getByTestId('project-sidebar-toggle')
+ .filter({ hasText: 'Tutorial Project 00' })
const tutorialModalText = page.getByText('Welcome to Modeling App!')
const tutorialDismissButton = page.getByRole('button', { name: 'Dismiss' })
const userMenuButton = page.getByTestId('user-sidebar-toggle')
diff --git a/e2e/playwright/projects.spec.ts b/e2e/playwright/projects.spec.ts
index a27d26faf..16b174c09 100644
--- a/e2e/playwright/projects.spec.ts
+++ b/e2e/playwright/projects.spec.ts
@@ -507,17 +507,18 @@ test(
'File in the file pane should open with a single click',
{ tag: '@electron' },
async ({ browserName }, testInfo) => {
+ const projectName = 'router-template-slate'
const { electronApp, page } = await setupElectron({
testInfo,
folderSetupFn: async (dir) => {
- await fsp.mkdir(`${dir}/router-template-slate`, { recursive: true })
+ await fsp.mkdir(`${dir}/${projectName}`, { recursive: true })
await fsp.copyFile(
'src/wasm-lib/tests/executor/inputs/router-template-slate.kcl',
- `${dir}/router-template-slate/main.kcl`
+ `${dir}/${projectName}/main.kcl`
)
await fsp.copyFile(
'src/wasm-lib/tests/executor/inputs/focusrite_scarlett_mounting_braket.kcl',
- `${dir}/router-template-slate/otherThingToClickOn.kcl`
+ `${dir}/${projectName}/otherThingToClickOn.kcl`
)
},
})
@@ -526,7 +527,7 @@ test(
page.on('console', console.log)
- await page.getByText('router-template-slate').click()
+ await page.getByText(projectName).click()
await expect(page.getByTestId('loading')).toBeAttached()
await expect(page.getByTestId('loading')).not.toBeAttached({
timeout: 20_000,
diff --git a/e2e/playwright/text-to-cad-tests.spec.ts b/e2e/playwright/text-to-cad-tests.spec.ts
index af14f7cd9..906ffc405 100644
--- a/e2e/playwright/text-to-cad-tests.spec.ts
+++ b/e2e/playwright/text-to-cad-tests.spec.ts
@@ -710,7 +710,9 @@ test(
await page.setViewportSize({ width: 1200, height: 500 })
// Locators
- const projectMenuButton = page.getByRole('button', { name: projectName })
+ const projectMenuButton = page
+ .getByTestId('project-sidebar-toggle')
+ .filter({ hasText: projectName })
const textToCadFileButton = page.getByRole('listitem').filter({
has: page.getByRole('button', { name: textToCadFileName }),
})
diff --git a/src/components/FileTree.tsx b/src/components/FileTree.tsx
index b8b08019b..1e534bdb6 100644
--- a/src/components/FileTree.tsx
+++ b/src/components/FileTree.tsx
@@ -538,3 +538,19 @@ export const FileTreeInner = ({
)
}
+
+export const FileTreeRoot = () => {
+ const loaderData = useRouteLoaderData(PATHS.FILE) as IndexLoaderData
+ const { project } = loaderData
+
+ // project.path should never be empty here but I guess during initial loading
+ // it can be.
+ return (
+
+ {project?.name ?? ''}
+
+ )
+}
diff --git a/src/components/HelpMenu.tsx b/src/components/HelpMenu.tsx
index 8893b02e7..6aecec713 100644
--- a/src/components/HelpMenu.tsx
+++ b/src/components/HelpMenu.tsx
@@ -4,7 +4,7 @@ import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
import { CustomIcon } from './CustomIcon'
import { useLocation, useNavigate } from 'react-router-dom'
import { PATHS } from 'lib/paths'
-import { createAndOpenNewProject } from 'lib/desktopFS'
+import { createAndOpenNewTutorialProject } from 'lib/desktopFS'
import { useAbsoluteFilePath } from 'hooks/useAbsoluteFilePath'
import { useLspContext } from './LspProvider'
import { openExternalBrowserIfDesktop } from 'lib/openWindow'
@@ -116,9 +116,10 @@ export function HelpMenu(props: React.PropsWithChildren) {
if (isInProject) {
navigate(filePath + PATHS.ONBOARDING.INDEX)
} else {
- createAndOpenNewProject({ onProjectOpen, navigate }).catch(
- reportRejection
- )
+ createAndOpenNewTutorialProject({
+ onProjectOpen,
+ navigate,
+ }).catch(reportRejection)
}
}}
>
diff --git a/src/components/ModelingSidebar/ModelingPane.tsx b/src/components/ModelingSidebar/ModelingPane.tsx
index 068c63995..a5ffc079f 100644
--- a/src/components/ModelingSidebar/ModelingPane.tsx
+++ b/src/components/ModelingSidebar/ModelingPane.tsx
@@ -1,3 +1,4 @@
+import { ReactNode } from 'react'
import styles from './ModelingPane.module.css'
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
import { ActionButton } from 'components/ActionButton'
@@ -6,22 +7,24 @@ import { CustomIconName } from 'components/CustomIcon'
import { IconDefinition } from '@fortawesome/free-solid-svg-icons'
import { ActionIcon } from 'components/ActionIcon'
-export interface ModelingPaneProps
- extends React.PropsWithChildren,
- React.HTMLAttributes {
+export interface ModelingPaneProps {
+ id: string
+ children: ReactNode | ReactNode[]
+ className?: string
icon?: CustomIconName | IconDefinition
- title: string
+ title: ReactNode
Menu?: React.ReactNode | React.FC
detailsTestId?: string
onClose: () => void
}
export const ModelingPaneHeader = ({
+ id,
icon,
title,
Menu,
onClose,
-}: Pick) => {
+}: Pick) => {
return (
@@ -34,7 +37,7 @@ export const ModelingPaneHeader = ({
bgClassName="!bg-transparent"
/>
)}
- {title}
+ {title}
{Menu instanceof Function ?
: Menu}
,
+ sidebarName: 'Project Files',
icon: 'folder',
Content: FileTreeInner,
keybinding: 'Shift + F',
diff --git a/src/components/ModelingSidebar/ModelingSidebar.tsx b/src/components/ModelingSidebar/ModelingSidebar.tsx
index 8ca551808..81a3ee202 100644
--- a/src/components/ModelingSidebar/ModelingSidebar.tsx
+++ b/src/components/ModelingSidebar/ModelingSidebar.tsx
@@ -5,6 +5,7 @@ import {
useCallback,
useEffect,
useMemo,
+ ReactNode,
useContext,
} from 'react'
import { useHotkeys } from 'react-hotkeys-hook'
@@ -270,7 +271,8 @@ interface ModelingPaneButtonProps
extends React.HTMLAttributes {
paneConfig: {
id: string
- title: string
+ title: ReactNode
+ sidebarName?: string
icon: CustomIconName | IconDefinition
keybinding: string
iconClassName?: string
@@ -299,7 +301,10 @@ function ModelingPaneButton({
- {paneConfig.title}
+ {paneConfig.sidebarName ?? paneConfig.title}
{paneIsOpen !== undefined ? ` pane` : ''}
- {paneConfig.title}
+ {paneConfig.sidebarName ?? paneConfig.title}
{disabledText !== undefined ? ` (${disabledText})` : ''}
{paneIsOpen !== undefined ? ` pane` : ''}
diff --git a/src/components/Settings/AllSettingsFields.tsx b/src/components/Settings/AllSettingsFields.tsx
index 480223367..97e6c9d9f 100644
--- a/src/components/Settings/AllSettingsFields.tsx
+++ b/src/components/Settings/AllSettingsFields.tsx
@@ -15,7 +15,10 @@ import { SettingsFieldInput } from './SettingsFieldInput'
import toast from 'react-hot-toast'
import { APP_VERSION } from 'routes/Settings'
import { PATHS } from 'lib/paths'
-import { createAndOpenNewProject, getSettingsFolderPaths } from 'lib/desktopFS'
+import {
+ createAndOpenNewTutorialProject,
+ getSettingsFolderPaths,
+} from 'lib/desktopFS'
import { useDotDotSlash } from 'hooks/useDotDotSlash'
import { ForwardedRef, forwardRef, useEffect } from 'react'
import { useLspContext } from 'components/LspProvider'
@@ -79,7 +82,7 @@ export const AllSettingsFields = forwardRef(
} else {
// If we're in the global settings, create a new project and navigate
// to the onboarding start in that project
- await createAndOpenNewProject({ onProjectOpen, navigate })
+ await createAndOpenNewTutorialProject({ onProjectOpen, navigate })
}
}
}
diff --git a/src/lib/desktopFS.ts b/src/lib/desktopFS.ts
index 56093fe66..90018fb4e 100644
--- a/src/lib/desktopFS.ts
+++ b/src/lib/desktopFS.ts
@@ -120,7 +120,7 @@ export async function getSettingsFolderPaths(projectPath?: string) {
}
}
-export async function createAndOpenNewProject({
+export async function createAndOpenNewTutorialProject({
onProjectOpen,
navigate,
}: {
@@ -144,6 +144,22 @@ export async function createAndOpenNewProject({
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,
diff --git a/src/routes/Onboarding/Introduction.tsx b/src/routes/Onboarding/Introduction.tsx
index 8270fc78d..9dac8f05c 100644
--- a/src/routes/Onboarding/Introduction.tsx
+++ b/src/routes/Onboarding/Introduction.tsx
@@ -3,7 +3,7 @@ import { onboardingPaths } from 'routes/Onboarding/paths'
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
import { Themes, getSystemTheme } from 'lib/theme'
import { bracket } from 'lib/exampleKcl'
-import { createAndOpenNewProject } from 'lib/desktopFS'
+import { createAndOpenNewTutorialProject } from 'lib/desktopFS'
import { isDesktop } from 'lib/isDesktop'
import { useNavigate, useRouteLoaderData } from 'react-router-dom'
import { codeManager, kclManager } from 'lib/singletons'
@@ -63,7 +63,7 @@ function OnboardingWarningDesktop(props: OnboardingResetWarningProps) {
fileContext.project.path || null,
false
)
- await createAndOpenNewProject({ onProjectOpen, navigate })
+ await createAndOpenNewTutorialProject({ onProjectOpen, navigate })
props.setShouldShowWarning(false)
}