Files
modeling-app/src/lib/routeLoaders.ts
Frank Noirot 3b7b4f85a1 Update onboarding to V1 browser and desktop flows (#6714)
* Remove unused `telemetryLoader`

* Remove onboarding redirect behavior

* Allow subRoute to be passed to navigateToProject

* Replace warning dialog routes with toasts

* Wire up new utilities and toasts to UI components

* Add home sidebar buttons for tutorial flow

* Rename menu item

* Add flex-1 so home-layout fills available space

* Remove onboarding avatar tests, they are becoming irrelevant

* Consolidate onboarding tests to one longer one

and update it to not use pixel color checks, and use fixtures.

* Shorten warning toast button text

* tsc, lint, and circular deps

* Update circular dep file

* Fix mistakes made in circular update tweaking

* One more dumb created circular dep

* Update src/routes/Onboarding/utils.tsx

Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>

* Fix narrow screen home layout breaking

* fix: kevin, navigation routes fixed

* fix: filename parsing is correct now for onboarding with the last file sep

* Fix e2e test state checks that are diff on Linux

* Create onboarding project entirely through systemIOMachine

* Fix Windows path construction

* Make utility to verify a string is an onboarding value

* Little biome formatting suggestion fix

* Units onboarding step was not using OnboardingButtons

* Add type checking of next and previous status, fix useNextClick

* Update `OnboardingStatus` type on WASM side

* Make onboarding different on browser and web, placeholder component

* Show proof of concept with custom content per route

* Make text type args not insta dismiss when you click anywhere

* Make some utility hooks for the onboarding

* Update requestedProjectName along with requestedProjectName

* Build out a rough draft of desktop onboarding

* Remove unused onboarding route files

* Build out rough draft of browser onboarding content

* @jgomez720 browser flow feedback

* @jgomez420 desktop feedback

* tsc and lints

* Tweaks

* Import is dead, long live Add files

* What's up with my inability to type "highlight"?

* Codespell and String casting

* Update browser sample to be axial fan

* lint and tsc

* codespell again

* Remove unused nightmare function `useDemoCode`

* Add a few unit tests

* Update desktop to use bulk file creation from #6747

* Oops overwrote main.kcl on the modify with text-to-cad step

* Undo the dumb use of `sep` that I introduced

* Fix up project test

which was fragile to the number of steps in the onboarding smh

* Fix up onboarding flow test

* typo

---------

Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
Co-authored-by: Kevin Nadro <kevin@zoo.dev>
2025-05-09 00:37:21 +00:00

206 lines
5.8 KiB
TypeScript

import type { LoaderFunction } from 'react-router-dom'
import { redirect } from 'react-router-dom'
import { waitFor } from 'xstate'
import { fileSystemManager } from '@src/lang/std/fileSystemManager'
import { normalizeLineEndings } from '@src/lib/codeEditor'
import {
BROWSER_FILE_NAME,
BROWSER_PROJECT_NAME,
FILE_EXT,
PROJECT_ENTRYPOINT,
} from '@src/lib/constants'
import { getProjectInfo } from '@src/lib/desktop'
import { isDesktop } from '@src/lib/isDesktop'
import {
BROWSER_PATH,
PATHS,
getProjectMetaByRouteId,
safeEncodeForRouterPaths,
} from '@src/lib/paths'
import {
loadAndValidateSettings,
readLocalStorageAppSettingsFile,
} from '@src/lib/settings/settingsUtils'
import { codeManager } from '@src/lib/singletons'
import type {
FileLoaderData,
HomeLoaderData,
IndexLoaderData,
} from '@src/lib/types'
import { settingsActor } from '@src/lib/singletons'
import { readAppSettingsFile } from '@src/lib/desktop'
export const fileLoader: LoaderFunction = async (
routerData
): Promise<FileLoaderData | Response> => {
const { params } = routerData
let { configuration } = await loadAndValidateSettings()
const projectPathData = await getProjectMetaByRouteId(
readAppSettingsFile,
readLocalStorageAppSettingsFile,
params.id,
configuration
)
const isBrowserProject = params.id === decodeURIComponent(BROWSER_PATH)
let code = ''
if (!isBrowserProject && projectPathData) {
const { projectName, projectPath, currentFileName, currentFilePath } =
projectPathData
const urlObj = new URL(routerData.request.url)
if (!urlObj.pathname.endsWith('/settings')) {
const fallbackFile = isDesktop()
? (await getProjectInfo(projectPath)).default_file
: ''
let fileExists = isDesktop()
if (currentFilePath && fileExists) {
try {
await window.electron.stat(currentFilePath)
} catch (e) {
if (e === 'ENOENT') {
fileExists = false
}
}
}
// If we are navigating to the project and want to navigate to its
// default file, redirect to it keeping everything else in the URL the same.
if (projectPath && !currentFileName && fileExists && params.id) {
const encodedId = safeEncodeForRouterPaths(params.id)
const requestUrlWithDefaultFile = routerData.request.url.replace(
encodedId,
safeEncodeForRouterPaths(fallbackFile)
)
return redirect(requestUrlWithDefaultFile)
}
if (!fileExists || !currentFileName || !currentFilePath || !projectName) {
return redirect(
`${PATHS.FILE}/${encodeURIComponent(
isDesktop() ? fallbackFile : params.id + '/' + PROJECT_ENTRYPOINT
)}${new URL(routerData.request.url).search || ''}`
)
}
code = await window.electron.readFile(currentFilePath, {
encoding: 'utf-8',
})
code = normalizeLineEndings(code)
// If persistCode in localStorage is present, it'll persist that code
// through *anything*. INTENDED FOR TESTS.
if (window.electron.process.env.IS_PLAYWRIGHT) {
code = codeManager.localStoragePersistCode() || code
}
// Update both the state and the editor's code.
// We explicitly do not write to the file here since we are loading from
// the file system and not the editor.
codeManager.updateCurrentFilePath(currentFilePath)
// We pass true on the end here to clear the code editor history.
// This way undo and redo are not super weird when opening new files.
codeManager.updateCodeStateEditor(code, true)
}
// Set the file system manager to the project path
// So that WASM gets an updated path for operations
fileSystemManager.dir = projectPath
const defaultProjectData = {
name: projectName || 'unnamed',
path: projectPath,
children: [],
kcl_file_count: 0,
directory_count: 0,
metadata: null,
default_file: projectPath,
readWriteAccess: true,
}
const maybeProjectInfo = isDesktop()
? await getProjectInfo(projectPath)
: null
const project = maybeProjectInfo ?? defaultProjectData
// Fire off the event to load the project settings
// once we know it's idle.
await waitFor(settingsActor, (state) => state.matches('idle'))
settingsActor.send({
type: 'load.project',
project,
})
const projectData: IndexLoaderData = {
code,
project,
file: {
name: currentFileName || '',
path: currentFilePath || '',
children: [],
},
}
return {
...projectData,
}
}
const project = {
name: BROWSER_PROJECT_NAME,
path: `/${BROWSER_PROJECT_NAME}`,
children: [
{
name: `${BROWSER_FILE_NAME}.${FILE_EXT}`,
path: BROWSER_PATH,
children: [],
},
],
default_file: BROWSER_FILE_NAME,
directory_count: 0,
kcl_file_count: 1,
metadata: null,
readWriteAccess: true,
}
// Fire off the event to load the project settings
// once we know it's idle.
await waitFor(settingsActor, (state) => state.matches('idle'))
settingsActor.send({
type: 'load.project',
project,
})
return {
code,
project,
file: {
name: BROWSER_FILE_NAME,
path: decodeURIComponent(BROWSER_PATH),
children: [],
},
}
}
// Loads the settings and by extension the projects in the default directory
// and returns them to the Home route, along with any errors that occurred
export const homeLoader: LoaderFunction = async ({
request,
}): Promise<HomeLoaderData | Response> => {
const url = new URL(request.url)
if (!isDesktop()) {
return redirect(
PATHS.FILE + '/%2F' + BROWSER_PROJECT_NAME + (url.search || '')
)
}
settingsActor.send({
type: 'clear.project',
})
return {}
}