Regression fix: restarting onboarding in desktop app required two attempts (#3240)

* Fixed onboarding modal issue, revealed race

* Remove logs

* Make common reset onboarding code path
This commit is contained in:
Frank Noirot
2024-08-02 15:38:39 -04:00
committed by GitHub
parent 9b594efe53
commit 9dcc955760
7 changed files with 98 additions and 49 deletions

7
src-tauri/Cargo.lock generated
View File

@ -2626,6 +2626,7 @@ dependencies = [
"tower-lsp", "tower-lsp",
"ts-rs", "ts-rs",
"url", "url",
"urlencoding",
"uuid", "uuid",
"validator", "validator",
"wasm-bindgen", "wasm-bindgen",
@ -6258,6 +6259,12 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "urlencoding"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
[[package]] [[package]]
name = "urlpattern" name = "urlpattern"
version = "0.2.0" version = "0.2.0"

View File

@ -6,6 +6,7 @@ import { useLocation, useNavigate } from 'react-router-dom'
import { createAndOpenNewProject } from 'lib/tauriFS' import { createAndOpenNewProject } from 'lib/tauriFS'
import { paths } from 'lib/paths' import { paths } from 'lib/paths'
import { useAbsoluteFilePath } from 'hooks/useAbsoluteFilePath' import { useAbsoluteFilePath } from 'hooks/useAbsoluteFilePath'
import { useLspContext } from './LspProvider'
const HelpMenuDivider = () => ( const HelpMenuDivider = () => (
<div className="h-[1px] bg-chalkboard-110 dark:bg-chalkboard-80" /> <div className="h-[1px] bg-chalkboard-110 dark:bg-chalkboard-80" />
@ -13,6 +14,7 @@ const HelpMenuDivider = () => (
export function HelpMenu(props: React.PropsWithChildren) { export function HelpMenu(props: React.PropsWithChildren) {
const location = useLocation() const location = useLocation()
const { onProjectOpen } = useLspContext()
const filePath = useAbsoluteFilePath() const filePath = useAbsoluteFilePath()
const isInProject = location.pathname.includes(paths.FILE) const isInProject = location.pathname.includes(paths.FILE)
const navigate = useNavigate() const navigate = useNavigate()
@ -106,9 +108,9 @@ export function HelpMenu(props: React.PropsWithChildren) {
}, },
}) })
if (isInProject) { if (isInProject) {
navigate('onboarding') navigate(filePath + paths.ONBOARDING.INDEX)
} else { } else {
createAndOpenNewProject(navigate) createAndOpenNewProject({ onProjectOpen, navigate })
} }
}} }}
> >

View File

@ -3,7 +3,6 @@ import {
faBugSlash, faBugSlash,
faCode, faCode,
faCodeCommit, faCodeCommit,
faExclamationCircle,
faSquareRootVariable, faSquareRootVariable,
} from '@fortawesome/free-solid-svg-icons' } from '@fortawesome/free-solid-svg-icons'
import { KclEditorMenu } from 'components/ModelingSidebar/ModelingPanes/KclEditorMenu' import { KclEditorMenu } from 'components/ModelingSidebar/ModelingPanes/KclEditorMenu'

View File

@ -19,7 +19,8 @@ import { createAndOpenNewProject, getSettingsFolderPaths } from 'lib/tauriFS'
import { paths } from 'lib/paths' import { paths } from 'lib/paths'
import { useDotDotSlash } from 'hooks/useDotDotSlash' import { useDotDotSlash } from 'hooks/useDotDotSlash'
import { sep } from '@tauri-apps/api/path' import { sep } from '@tauri-apps/api/path'
import { ForwardedRef, forwardRef } from 'react' import { ForwardedRef, forwardRef, useEffect } from 'react'
import { useLspContext } from 'components/LspProvider'
interface AllSettingsFieldsProps { interface AllSettingsFieldsProps {
searchParamTab: SettingsLevel searchParamTab: SettingsLevel
@ -33,9 +34,10 @@ export const AllSettingsFields = forwardRef(
) => { ) => {
const location = useLocation() const location = useLocation()
const navigate = useNavigate() const navigate = useNavigate()
const { onProjectOpen } = useLspContext()
const dotDotSlash = useDotDotSlash() const dotDotSlash = useDotDotSlash()
const { const {
settings: { send, context }, settings: { send, context, state },
} = useSettingsAuthContext() } = useSettingsAuthContext()
const projectPath = const projectPath =
@ -48,19 +50,37 @@ export const AllSettingsFields = forwardRef(
) )
: undefined : undefined
function restartOnboarding() { async function restartOnboarding() {
send({ send({
type: `set.app.onboardingStatus`, type: `set.app.onboardingStatus`,
data: { level: 'user', value: '' }, data: { level: 'user', value: '' },
}) })
if (isFileSettings) {
navigate(dotDotSlash(1) + paths.ONBOARDING.INDEX)
} else {
createAndOpenNewProject(navigate)
}
} }
/**
* A "listener" for the XState to return to "idle" state
* when the user resets the onboarding, using the callback above
*/
useEffect(() => {
async function navigateToOnboardingStart() {
if (
state.context.app.onboardingStatus.user === '' &&
state.matches('idle')
) {
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 createAndOpenNewProject({ onProjectOpen, navigate })
}
}
}
navigateToOnboardingStart()
}, [isFileSettings, navigate, state])
return ( return (
<div className="relative overflow-y-auto"> <div className="relative overflow-y-auto">
<div ref={scrollRef} className="flex flex-col gap-4 px-2"> <div ref={scrollRef} className="flex flex-col gap-4 px-2">

View File

@ -14,6 +14,7 @@ import {
listProjects, listProjects,
readAppSettingsFile, readAppSettingsFile,
} from './tauri' } from './tauri'
import { engineCommandManager } from './singletons'
export const isHidden = (fileOrDir: FileEntry) => export const isHidden = (fileOrDir: FileEntry) =>
!!fileOrDir.name?.startsWith('.') !!fileOrDir.name?.startsWith('.')
@ -116,9 +117,23 @@ export async function getSettingsFolderPaths(projectPath?: string) {
} }
} }
export async function createAndOpenNewProject( export async function createAndOpenNewProject({
onProjectOpen,
navigate,
}: {
onProjectOpen: (
project: {
name: string | null
path: string | null
} | null,
file: FileEntry | null
) => void
navigate: (path: string) => void navigate: (path: string) => void
) { }) {
// Clear the scene and end the session.
engineCommandManager.endSession()
// Create a new project with the onboarding project name
const configuration = await readAppSettingsFile() const configuration = await readAppSettingsFile()
const projects = await listProjects(configuration) const projects = await listProjects(configuration)
const nextIndex = getNextProjectIndex(ONBOARDING_PROJECT_NAME, projects) const nextIndex = getNextProjectIndex(ONBOARDING_PROJECT_NAME, projects)
@ -126,6 +141,24 @@ export async function createAndOpenNewProject(
ONBOARDING_PROJECT_NAME, ONBOARDING_PROJECT_NAME,
nextIndex nextIndex
) )
const newFile = await createNewProjectDirectory(name, bracket, configuration) const newProject = await createNewProjectDirectory(
navigate(`${paths.FILE}/${encodeURIComponent(newFile.path)}`) name,
bracket,
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
}`
)
return newProject
} }

View File

@ -3,22 +3,16 @@ import { onboardingPaths } from 'routes/Onboarding/paths'
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext' import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
import { Themes, getSystemTheme } from 'lib/theme' import { Themes, getSystemTheme } from 'lib/theme'
import { bracket } from 'lib/exampleKcl' import { bracket } from 'lib/exampleKcl'
import { import { createAndOpenNewProject } from 'lib/tauriFS'
getNextProjectIndex,
interpolateProjectNameWithIndex,
} from 'lib/tauriFS'
import { isTauri } from 'lib/isTauri' import { isTauri } from 'lib/isTauri'
import { useNavigate } from 'react-router-dom' import { useNavigate, useRouteLoaderData } from 'react-router-dom'
import { paths } from 'lib/paths'
import { codeManager, kclManager } from 'lib/singletons' import { codeManager, kclManager } from 'lib/singletons'
import { join } from '@tauri-apps/api/path' import { APP_NAME } from 'lib/constants'
import {
APP_NAME,
ONBOARDING_PROJECT_NAME,
PROJECT_ENTRYPOINT,
} from 'lib/constants'
import { createNewProjectDirectory, listProjects } from 'lib/tauri'
import { useState } from 'react' import { useState } from 'react'
import { useLspContext } from 'components/LspProvider'
import { IndexLoaderData } from 'lib/types'
import { paths } from 'lib/paths'
import { useFileContext } from 'hooks/useFileContext'
/** /**
* Show either a welcome screen or a warning screen * Show either a welcome screen or a warning screen
@ -47,30 +41,28 @@ function OnboardingResetWarning(props: OnboardingResetWarningProps) {
{!isTauri() ? ( {!isTauri() ? (
<OnboardingWarningWeb {...props} /> <OnboardingWarningWeb {...props} />
) : ( ) : (
<OnboardingWarningDesktop /> <OnboardingWarningDesktop {...props} />
)} )}
</div> </div>
</div> </div>
) )
} }
function OnboardingWarningDesktop() { function OnboardingWarningDesktop(props: OnboardingResetWarningProps) {
const navigate = useNavigate() const navigate = useNavigate()
const dismiss = useDismiss() const dismiss = useDismiss()
const loaderData = useRouteLoaderData(paths.FILE) as IndexLoaderData
const { context: fileContext } = useFileContext()
const { onProjectClose, onProjectOpen } = useLspContext()
async function createAndOpenNewProject() { async function onAccept() {
const projects = await listProjects() onProjectClose(
const nextIndex = getNextProjectIndex(ONBOARDING_PROJECT_NAME, projects) loaderData.file || null,
const name = interpolateProjectNameWithIndex( fileContext.project.path || null,
ONBOARDING_PROJECT_NAME, false
nextIndex
)
const newFile = await createNewProjectDirectory(name, bracket)
navigate(
`${paths.FILE}/${encodeURIComponent(
await join(newFile.path, PROJECT_ENTRYPOINT)
)}${paths.ONBOARDING.INDEX}`
) )
await createAndOpenNewProject({ onProjectOpen, navigate })
props.setShouldShowWarning(false)
} }
return ( return (
@ -88,11 +80,7 @@ function OnboardingWarningDesktop() {
<OnboardingButtons <OnboardingButtons
className="mt-6" className="mt-6"
dismiss={dismiss} dismiss={dismiss}
next={() => { next={onAccept}
void createAndOpenNewProject()
codeManager.updateCodeEditor(bracket)
dismiss()
}}
nextText="Make a new project" nextText="Make a new project"
/> />
</> </>

View File

@ -79,7 +79,7 @@ export const onboardingRoutes = [
export function useDemoCode() { export function useDemoCode() {
useEffect(() => { useEffect(() => {
if (!editorManager.editorView) return if (!editorManager.editorView || codeManager.code === bracket) return
setTimeout(async () => { setTimeout(async () => {
codeManager.updateCodeStateEditor(bracket) codeManager.updateCodeStateEditor(bracket)
kclManager.isFirstRender = true kclManager.isFirstRender = true