2025-04-01 23:54:26 -07:00
|
|
|
import type { ReactNode } from 'react'
|
|
|
|
import { createContext, useEffect, useState } from 'react'
|
2025-04-01 14:20:42 -07:00
|
|
|
import {
|
|
|
|
useLocation,
|
|
|
|
useNavigate,
|
2025-04-01 23:54:26 -07:00
|
|
|
useNavigation,
|
2025-04-01 14:20:42 -07:00
|
|
|
useRouteLoaderData,
|
|
|
|
} from 'react-router-dom'
|
2025-04-01 23:54:26 -07:00
|
|
|
|
|
|
|
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'
|
|
|
|
import { isDesktop } from '@src/lib/isDesktop'
|
|
|
|
import { PATHS } from '@src/lib/paths'
|
|
|
|
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/machines/appMachine'
|
2024-11-07 17:23:03 -05:00
|
|
|
|
|
|
|
export const RouteProviderContext = createContext({})
|
|
|
|
|
|
|
|
export function RouteProvider({ children }: { children: ReactNode }) {
|
2025-01-31 14:47:08 -05:00
|
|
|
useAuthNavigation()
|
2025-02-21 13:47:36 -05:00
|
|
|
const loadedProject = useRouteLoaderData(PATHS.FILE) as IndexLoaderData
|
2024-11-07 17:23:03 -05:00
|
|
|
const [first, setFirstState] = useState(true)
|
2025-02-21 13:47:36 -05:00
|
|
|
const [settingsPath, setSettingsPath] = useState<string | undefined>(
|
|
|
|
undefined
|
|
|
|
)
|
2024-11-07 17:23:03 -05:00
|
|
|
const navigation = useNavigation()
|
2025-02-21 13:47:36 -05:00
|
|
|
const navigate = useNavigate()
|
2024-11-07 17:23:03 -05:00
|
|
|
const location = useLocation()
|
2025-02-25 14:35:19 -05:00
|
|
|
const settings = useSettings()
|
2025-01-31 08:01:51 -05:00
|
|
|
|
2024-11-07 17:23:03 -05:00
|
|
|
useEffect(() => {
|
|
|
|
// On initialization, the react-router-dom does not send a 'loading' state event.
|
|
|
|
// it sends an idle event first.
|
|
|
|
const pathname = first ? location.pathname : navigation.location?.pathname
|
|
|
|
const isHome = pathname === PATHS.HOME
|
|
|
|
const isFile =
|
|
|
|
pathname?.includes(PATHS.FILE) &&
|
|
|
|
pathname?.substring(pathname?.length - 4) === '.kcl'
|
|
|
|
if (isHome) {
|
|
|
|
markOnce('code/willLoadHome')
|
|
|
|
} else if (isFile) {
|
|
|
|
markOnce('code/willLoadFile')
|
2025-02-25 14:35:19 -05:00
|
|
|
|
|
|
|
/**
|
|
|
|
* 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)
|
|
|
|
)
|
|
|
|
}
|
2024-11-07 17:23:03 -05:00
|
|
|
}
|
|
|
|
setFirstState(false)
|
|
|
|
}, [navigation])
|
|
|
|
|
2025-02-21 13:47:36 -05:00
|
|
|
useEffect(() => {
|
|
|
|
if (!isDesktop()) return
|
|
|
|
getAppSettingsFilePath().then(setSettingsPath).catch(trap)
|
|
|
|
}, [])
|
|
|
|
|
|
|
|
useFileSystemWatcher(
|
|
|
|
async (eventType: string) => {
|
|
|
|
// If there is a projectPath but it no longer exists it means
|
|
|
|
// it was exterally removed. If we let the code past this condition
|
|
|
|
// execute it will recreate the directory due to code in
|
|
|
|
// loadAndValidateSettings trying to recreate files. I do not
|
|
|
|
// wish to change the behavior in case anything else uses it.
|
|
|
|
// Go home.
|
|
|
|
if (loadedProject?.project?.path) {
|
|
|
|
if (!window.electron.exists(loadedProject?.project?.path)) {
|
|
|
|
navigate(PATHS.HOME)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Only reload if there are changes. Ignore everything else.
|
|
|
|
if (eventType !== 'change') return
|
|
|
|
|
|
|
|
const data = await loadAndValidateSettings(loadedProject?.project?.path)
|
|
|
|
settingsActor.send({
|
|
|
|
type: 'Set all settings',
|
|
|
|
settings: data.settings,
|
|
|
|
doNotPersist: true,
|
|
|
|
})
|
|
|
|
},
|
|
|
|
[settingsPath, loadedProject?.project?.path].filter(
|
|
|
|
(x: string | undefined) => x !== undefined
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
2024-11-07 17:23:03 -05:00
|
|
|
return (
|
|
|
|
<RouteProviderContext.Provider value={{}}>
|
|
|
|
{children}
|
|
|
|
</RouteProviderContext.Provider>
|
|
|
|
)
|
|
|
|
}
|