Compare commits

...

1 Commits

Author SHA1 Message Date
ac64b9fa45 Start of a unified appMachine
+ messy broken migrations of the sub-machines
2024-03-17 10:36:06 -04:00
10 changed files with 427 additions and 146 deletions

View File

@ -26,7 +26,7 @@
"@types/react-dom": "^18.0.0", "@types/react-dom": "^18.0.0",
"@uiw/react-codemirror": "^4.21.20", "@uiw/react-codemirror": "^4.21.20",
"@xstate/inspect": "^0.8.0", "@xstate/inspect": "^0.8.0",
"@xstate/react": "^3.2.2", "@xstate/react": "^4.1.0",
"crypto-js": "^4.2.0", "crypto-js": "^4.2.0",
"debounce-promise": "^3.1.2", "debounce-promise": "^3.1.2",
"formik": "^2.4.3", "formik": "^2.4.3",
@ -50,7 +50,7 @@
"three": "^0.160.0", "three": "^0.160.0",
"toml": "^3.0.0", "toml": "^3.0.0",
"ts-node": "^10.9.1", "ts-node": "^10.9.1",
"typescript": "^5.2.2", "typescript": "^5.4.2",
"uuid": "^9.0.1", "uuid": "^9.0.1",
"vitest": "^1.3.1", "vitest": "^1.3.1",
"vscode-jsonrpc": "^8.1.0", "vscode-jsonrpc": "^8.1.0",
@ -58,7 +58,7 @@
"wasm-pack": "^0.12.1", "wasm-pack": "^0.12.1",
"web-vitals": "^3.5.0", "web-vitals": "^3.5.0",
"ws": "^8.13.0", "ws": "^8.13.0",
"xstate": "^4.38.2", "xstate": "^5.9.1",
"zustand": "^4.4.5" "zustand": "^4.4.5"
}, },
"scripts": { "scripts": {

View File

@ -1,12 +1,11 @@
import { AppMachineContext } from 'machines/appMachine'
import Loading from './components/Loading' import Loading from './components/Loading'
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
// Wrapper around protected routes, used in src/Router.tsx // Wrapper around protected routes, used in src/Router.tsx
export const Auth = ({ children }: React.PropsWithChildren) => { export const Auth = ({ children }: React.PropsWithChildren) => {
const { auth } = useSettingsAuthContext() const isAuthenticating = AppMachineContext.useSelector(s => s.matches('Loading'))
const isLoggingIn = auth?.state.matches('checkIfLoggedIn')
return isLoggingIn ? ( return isAuthenticating ? (
<Loading> <Loading>
<span data-testid="initial-load">Loading Modeling App...</span> <span data-testid="initial-load">Loading Modeling App...</span>
</Loading> </Loading>

View File

@ -16,8 +16,6 @@ import makeUrlPathRelative from './lib/makeUrlPathRelative'
import DownloadAppBanner from 'components/DownloadAppBanner' import DownloadAppBanner from 'components/DownloadAppBanner'
import { WasmErrBanner } from 'components/WasmErrBanner' import { WasmErrBanner } from 'components/WasmErrBanner'
import { CommandBar } from 'components/CommandBar/CommandBar' import { CommandBar } from 'components/CommandBar/CommandBar'
import ModelingMachineProvider from 'components/ModelingMachineProvider'
import FileMachineProvider from 'components/FileMachineProvider'
import { paths } from 'lib/paths' import { paths } from 'lib/paths'
import { import {
fileLoader, fileLoader,
@ -25,10 +23,7 @@ import {
indexLoader, indexLoader,
onboardingRedirectLoader, onboardingRedirectLoader,
} from 'lib/routeLoaders' } from 'lib/routeLoaders'
import { CommandBarProvider } from 'components/CommandBar/CommandBarProvider' import { AppMachineProvider } from 'components/AppMachineProvider'
import SettingsAuthProvider from 'components/SettingsAuthProvider'
import LspProvider from 'components/LspProvider'
import { KclContextProvider } from 'lang/KclSingleton'
export const BROWSER_FILE_NAME = 'new' export const BROWSER_FILE_NAME = 'new'
@ -37,15 +32,9 @@ const router = createBrowserRouter([
loader: indexLoader, loader: indexLoader,
id: paths.INDEX, id: paths.INDEX,
element: ( element: (
<CommandBarProvider> <AppMachineProvider>
<KclContextProvider> <Outlet />
<SettingsAuthProvider> </AppMachineProvider>
<LspProvider>
<Outlet />
</LspProvider>
</SettingsAuthProvider>
</KclContextProvider>
</CommandBarProvider>
), ),
children: [ children: [
{ {
@ -61,14 +50,10 @@ const router = createBrowserRouter([
id: paths.FILE, id: paths.FILE,
element: ( element: (
<Auth> <Auth>
<FileMachineProvider> <Outlet />
<ModelingMachineProvider> <App />
<Outlet /> <CommandBar />
<App /> <WasmErrBanner />
<CommandBar />
</ModelingMachineProvider>
<WasmErrBanner />
</FileMachineProvider>
{!isTauri() && import.meta.env.PROD && <DownloadAppBanner />} {!isTauri() && import.meta.env.PROD && <DownloadAppBanner />}
</Auth> </Auth>
), ),

View File

@ -0,0 +1,21 @@
import { AppMachineContext } from 'machines/appMachine'
import { PropsWithChildren } from 'react'
import { useLocation, useNavigate } from 'react-router-dom'
export const AppMachineProvider = ({ children }: PropsWithChildren) => {
const navigate = useNavigate()
const location = useLocation()
return (
<AppMachineContext.Provider
options={{
input: {
navigate,
location,
},
}}
>
{children}
</AppMachineContext.Provider>
)
}

View File

@ -13,6 +13,7 @@ import {
} from 'components/NetworkHealthIndicator' } from 'components/NetworkHealthIndicator'
import { useKclContext } from 'lang/KclSingleton' import { useKclContext } from 'lang/KclSingleton'
import { useStore } from 'useStore' import { useStore } from 'useStore'
import { AppMachineContext } from 'machines/appMachine'
// This might not be necessary, AnyStateMachine from xstate is working // This might not be necessary, AnyStateMachine from xstate is working
export type AllMachines = export type AllMachines =
@ -46,7 +47,7 @@ export default function useStateMachineCommands<
allCommandsRequireNetwork = false, allCommandsRequireNetwork = false,
onCancel, onCancel,
}: UseStateMachineCommandsArgs<T, S>) { }: UseStateMachineCommandsArgs<T, S>) {
const { commandBarSend } = useCommandsContext() const commandsActorRef = AppMachineContext.useSelector(s => s.children.commands)
const { overallState } = useNetworkStatus() const { overallState } = useNetworkStatus()
const { isExecuting } = useKclContext() const { isExecuting } = useKclContext()
const { isStreamReady } = useStore((s) => ({ const { isStreamReady } = useStore((s) => ({
@ -72,10 +73,10 @@ export default function useStateMachineCommands<
) )
.filter((c) => c !== null) as Command[] // TS isn't smart enough to know this filter removes nulls .filter((c) => c !== null) as Command[] // TS isn't smart enough to know this filter removes nulls
commandBarSend({ type: 'Add commands', data: { commands: newCommands } }) commandsActorRef?.send({ type: 'Add commands', data: { commands: newCommands } })
return () => { return () => {
commandBarSend({ commandsActorRef?.send({
type: 'Remove commands', type: 'Remove commands',
data: { commands: newCommands }, data: { commands: newCommands },
}) })

264
src/machines/appMachine.ts Normal file
View File

@ -0,0 +1,264 @@
import { ProjectWithEntryPointMetadata } from 'lib/types'
import { assign, fromCallback, fromPromise, setup } from 'xstate'
import { settingsMachine } from './settingsMachine'
import { commandBarMachine } from './commandBarMachine'
import { homeMachine } from './homeMachine'
import { modelingMachine } from './modelingMachine'
import { fileMachine } from './fileMachine'
import { useLocation, useNavigate } from 'react-router-dom'
import { TOKEN_PERSIST_KEY, getUser, persistedToken } from './authMachine'
import { Models } from '@kittycad/lib/dist/types/src'
import { createActorContext } from '@xstate/react'
import { paths } from 'lib/paths'
import { isTauri } from 'lib/isTauri'
import { sep } from '@tauri-apps/api/path'
export const appMachine = setup({
types: {
context: {} as {
token?: string
user?: Models['User_type']
currentProject?: ProjectWithEntryPointMetadata
currentFile?: string
navigate: ReturnType<typeof useNavigate>
location: ReturnType<typeof useLocation>
},
events: {} as
| { type: 'Sign out' }
| {
type: 'Open project'
data: {
project: ProjectWithEntryPointMetadata
file: string
}
}
| { type: 'Close project' }
| { type: 'AuthActor.done'; output: Models['User_type'] },
input: {} as {
navigate: ReturnType<typeof useNavigate>
location: ReturnType<typeof useLocation>
},
},
actions: {
'Clear currentProject and currentFile': assign({
currentProject: undefined,
currentFile: undefined,
}),
'Set currentProject': assign({
currentProject: ({ event, context }) =>
event.type === 'Open project'
? event.data.project
: context.currentProject,
}),
'Set currentFile': assign({
currentFile: ({ event, context }) =>
event.type === 'Open project' ? event.data.file : context.currentFile,
}),
'Delete auth state': () => {
localStorage.removeItem(TOKEN_PERSIST_KEY)
assign({ token: undefined })
},
'Go to sign in page': ({ context }) => {
context.navigate(paths.SIGN_IN)
},
'Navigate home': ({ context }) => {
context.navigate(paths.INDEX)
},
'Navigate to file': ({ context, event }) => {
if (event.type !== 'Open project') return
context.navigate(
paths.FILE +
event.data.project.path +
(isTauri() ? sep : '/') +
event.data.file
)
},
'Assign user': assign({
user: ({ event, context }) =>
event.type === 'AuthActor.done' ? event.output : context.user,
}),
'Persist auth state': ({ context }) => {
localStorage.setItem(TOKEN_PERSIST_KEY, context.token || '')
},
},
actors: {
SettingsActor: settingsMachine,
EngineConnectionActor: fromCallback(({ sendBack, receive, input }) => {
// TODO implement actor
}),
CommandBarMachine: commandBarMachine,
LspActor: fromCallback(({ sendBack, receive }) => {
// TODO implement actor
}),
HomeMachine: homeMachine,
ModelingMachine: modelingMachine,
ClientSideSceneActor: fromCallback(({ sendBack, receive }) => {
// TODO implement actor
}),
ProjectMachine: fileMachine,
KclActor: fromCallback(({ sendBack, receive }) => {
// TODO implement actor
}),
AuthActor: fromPromise(
({ input }: { input: { token: string | undefined } }) =>
getUser(input.token)
),
},
schemas: {
events: {
'Sign out': {
type: 'object',
properties: {},
},
'Open project': {
type: 'object',
properties: {},
},
'Close project': {
type: 'object',
properties: {},
},
'': {
type: 'object',
properties: {},
},
},
},
}).createMachine({
/** @xstate-layout N4IgpgJg5mDOIC5QEEAOqAEBZAhgYwAsBLAOzADoBlIqMiDUgYmtowHsBXAFwG0AGALqJQqNrCJcibEsJAAPRADYA7AEZyAFgCcG1QGZVygKyKAHMq3KANCACeiAExH1WvUY2Lnejct0a9AL4BNmiYuISkFCx0DCTkABJsALZgjADyqGAkGKgATmwAVmB4vIKyouKS0rIKCNp85A7KTnxqGq3m5jb2CMqKeuSWWqYjfKoeRmNBIejY+MRkVDQxpOQACvlFJeyZJIwAwgA2YmA5m8WlQkggFRJSMte19ZqqRsp6fHqKfIqq-UbdJRmcjKPjOZymVRaMHvaYgUJzCKLAAybBwEFIUEYEGkFFIADc2ABrCg4bgEfhXERiO7VR6IPTecimLRGYbDIymTyqUyAhAORSKcitVw8zx6Bx6ZpwhHhBYUVHozGMMC5fK5cioQ44LgAMzYuSS5DJXApZWutyqD1AtSMDgc5E8fW0ouUfQcfIcP00GhZpl90PeziCwRAJDYEDgsll80i5RpVpqiAAtEZGhpJkY3F8+KZc74+ao+Bphe1-VL-Vm+PaZbM5ZElrRILF45V7km6g7jNoPB49FooaoBXyNA5TMKVHxi99jO5VLWwrHFtFm6tEilW7TrfJENCQe4dIo+wOB3o+YphsLWu0s8NfK4F4j5Y2VnENoULjsspvE-Tev2r3GAV70FFRz3aRpWiPHwtCcBxVHnUMYyRKJlmbTguB-ds-yHBDyCze1OhUe1jE9PN8OcdpvFeBCL0fesUTRDESCgLC6RtRBJkGJoswQ0EuXaaw7EQQdyC+KVQW+b4Pg0EMAiAA */
context: ({ input }) => ({
token: persistedToken,
navigate: input.navigate,
location: input.location,
}),
id: 'App Machine',
initial: 'Loading',
states: {
'Signed in': {
initial: 'Home',
on: {
'Sign out': {
target: 'Signed out',
actions: [
{
type: 'Delete auth state',
},
{
type: 'Go to sign in page',
},
],
},
},
invoke: [
{
id: 'settings',
systemId: 'settings',
input: {},
src: 'SettingsActor',
},
{
id: 'engine',
systemId: 'engine',
input: {},
src: 'EngineConnectionActor',
},
{
id: 'commands',
systemId: 'commands',
input: {},
src: 'CommandBarMachine',
},
{
id: 'lsp',
systemId: 'lsp',
input: {},
src: 'LspActor',
},
],
states: {
Home: {
on: {
'Open project': {
target: 'Project open',
actions: {
type: 'Navigate to file',
},
},
},
entry: {
type: 'Clear currentProject and currentFile',
},
invoke: {
id: 'home',
input: {},
src: 'HomeMachine',
},
},
'Project open': {
on: {
'Close project': {
target: 'Home',
actions: {
type: 'Navigate home',
},
},
},
entry: [
{
type: 'Set currentProject',
},
{
type: 'Set currentFile',
},
],
invoke: [
{
id: 'modeling',
systemId: 'modeling',
input: {},
src: 'ModelingMachine',
},
{
input: {},
src: 'ClientSideSceneActor',
},
{
id: 'project',
systemId: 'project',
input: {},
src: 'ProjectMachine',
},
{
input: {},
src: 'KclActor',
},
],
},
},
},
'Signed out': {
type: 'final',
},
Loading: {
invoke: {
src: 'AuthActor',
id: 'auth',
input: ({ context: { token } }) => ({ token }),
onDone: {
target: 'Signed in',
actions: [
assign({
user: ({ event }) => event.output,
}),
'Persist auth state',
],
},
onError: 'Signed out',
},
},
},
})
export const AppMachineContext = createActorContext(appMachine)

View File

@ -1,4 +1,4 @@
import { createMachine, assign } from 'xstate' import { createMachine, assign, setup, fromPromise } from 'xstate'
import { Models } from '@kittycad/lib' import { Models } from '@kittycad/lib'
import withBaseURL from '../lib/withBaseURL' import withBaseURL from '../lib/withBaseURL'
import { isTauri } from 'lib/isTauri' import { isTauri } from 'lib/isTauri'
@ -39,86 +39,82 @@ export type Events =
} }
export const TOKEN_PERSIST_KEY = 'TOKEN_PERSIST_KEY' export const TOKEN_PERSIST_KEY = 'TOKEN_PERSIST_KEY'
const persistedToken = export const persistedToken =
localStorage?.getItem(TOKEN_PERSIST_KEY) || localStorage?.getItem(TOKEN_PERSIST_KEY) ||
getCookie('__Secure-next-auth.session-token') || getCookie('__Secure-next-auth.session-token') ||
'' undefined
export const authMachine = createMachine<UserContext, Events>( export const authMachine = setup({
{ types: {
id: 'Auth', events: {} as { type: 'Log out' } | { type: 'Log in' },
initial: 'checkIfLoggedIn', context: {} as UserContext,
states: { },
checkIfLoggedIn: { actors: {
id: 'check-if-logged-in', fetchUser: fromPromise(({ input }: { input: { token: string | undefined }}) => getUser(input.token)),
invoke: { },
src: 'getUser', actions: {
id: 'check-logged-in', goToIndexPage: () => ({}),
onDone: [ goToSignInPage: () => ({}),
{ }
target: 'loggedIn', }).createMachine({
actions: assign({ id: 'Auth',
user: (context, event) => event.data, preserveActionOrder: true,
}), initial: 'checkIfLoggedIn',
}, context: {
], token: persistedToken,
onError: [ },
{ states: {
target: 'loggedOut', checkIfLoggedIn: {
actions: assign({ id: 'check-if-logged-in',
user: () => undefined, invoke: {
}), id: 'getUser',
}, src: 'fetchUser',
], input: ({ context: { token } }) => ({ token }),
}, onDone: [
}, {
loggedIn: { target: 'loggedIn',
entry: ['goToIndexPage'],
on: {
'Log out': {
target: 'loggedOut',
},
},
},
loggedOut: {
entry: ['goToSignInPage'],
on: {
'Log in': {
target: 'checkIfLoggedIn',
actions: assign({ actions: assign({
token: (_, event) => { user: ({ event }) => event.output,
const token = event.token || ''
localStorage.setItem(TOKEN_PERSIST_KEY, token)
return token
},
}), }),
}, },
],
onError: [
{
target: 'loggedOut',
actions: assign({
user: () => undefined,
}),
},
],
},
},
loggedIn: {
entry: ['goToIndexPage'],
on: {
'Log out': {
target: 'loggedOut',
}, },
}, },
}, },
schema: { events: {} as { type: 'Log out' } | { type: 'Log in' } }, loggedOut: {
predictableActionArguments: true, entry: { type: 'goToIndexPage' },
preserveActionOrder: true, on: {
context: { 'Log in': {
token: persistedToken, target: 'checkIfLoggedIn',
},
},
}, },
}, },
{ })
actions: {},
services: { getUser },
guards: {},
delays: {},
}
)
async function getUser(context: UserContext) { export async function getUser(token?: string) {
const url = withBaseURL('/user') const url = withBaseURL('/user')
const headers: { [key: string]: string } = { const headers: { [key: string]: string } = {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
} }
if (!context.token && isTauri()) throw new Error('No token found') if (!token && isTauri()) throw new Error('No token found')
if (context.token) headers['Authorization'] = `Bearer ${context.token}` if (token) headers['Authorization'] = `Bearer ${token}`
if (SKIP_AUTH) return LOCAL_USER if (SKIP_AUTH) return LOCAL_USER
const userPromise = !isTauri() const userPromise = !isTauri()
@ -130,7 +126,7 @@ async function getUser(context: UserContext) {
.then((res) => res.json()) .then((res) => res.json())
.catch((err) => console.error('error from Browser getUser', err)) .catch((err) => console.error('error from Browser getUser', err))
: invoke<Models['User_type'] | Record<'error_code', unknown>>('get_user', { : invoke<Models['User_type'] | Record<'error_code', unknown>>('get_user', {
token: context.token, token: token,
hostname: VITE_KC_API_BASE_URL, hostname: VITE_KC_API_BASE_URL,
}).catch((err) => console.error('error from Tauri getUser', err)) }).catch((err) => console.error('error from Tauri getUser', err))
@ -138,10 +134,10 @@ async function getUser(context: UserContext) {
if ('error_code' in user) throw new Error(user.message) if ('error_code' in user) throw new Error(user.message)
return user return user as Models['User_type']
} }
function getCookie(cname: string): string { export function getCookie(cname: string): string {
if (isTauri()) { if (isTauri()) {
return '' return ''
} }

View File

@ -1,28 +1,54 @@
import { assign, createMachine } from 'xstate' import { assign, createMachine, fromPromise, setup } from 'xstate'
import { type ProjectWithEntryPointMetadata } from 'lib/types' import { type ProjectWithEntryPointMetadata } from 'lib/types'
import { HomeCommandSchema } from 'lib/commandBarConfigs/homeCommandConfig' import { HomeCommandSchema } from 'lib/commandBarConfigs/homeCommandConfig'
import { useNavigate } from 'react-router-dom'
export const homeMachine = createMachine( export const homeMachine = setup({
types: {
context: {} as {
projects: ProjectWithEntryPointMetadata[]
defaultProjectName: string
defaultDirectory: string
navigate: ReturnType<typeof useNavigate>
},
events: {} as
| { type: 'Open project'; data: HomeCommandSchema['Open project'] }
| { type: 'Rename project'; data: HomeCommandSchema['Rename project'] }
| { type: 'Create project'; data: HomeCommandSchema['Create project'] }
| { type: 'Delete project'; data: HomeCommandSchema['Delete project'] },
input: {} as {
navigate: ReturnType<typeof useNavigate>
defaultProjectName: string
defaultDirectory: string
},
},
actions: {} as { toastSuccess: () => void; toastError: () => void },
actors: {
readProjects: fromPromise(async () => {
return []
}),
createProject: fromPromise(async () => {
return
}),
renameProject: fromPromise(async () => {
return
}),
deleteProject: fromPromise(async () => {
return
}),
},
}).createMachine(
{ {
/** @xstate-layout N4IgpgJg5mDOIC5QAkD2BbMACdBDAxgBYCWAdmAHTK6xampYAOATqgFZj4AusAxAMLMwuLthbtOXANoAGALqJQjVLGJdiqUopAAPRAHYAbPooAWABwBGUwE5zAJgeGArM-MAaEAE9EN0wGYKGX97GX1nGVNDS0MbfwBfeM80TBwCEnIqGiZWDm4+ACUwUlxU8TzpeW1lVXVNbT0EcJNg02d-fzt7fU77Tx8EQ0iKCPtnfUsjGRtLGXtE5IxsPCIySmpacsk+QWFRHIluWQUkEBq1DS1TxqN7ChjzOxtXf0t7a37EcwsRibH-ZzRezA8wLEApZbpNZZTa5ba8AAiYAANmB9lsjlVTuc6ldQDdDOYKP5bm0os5TDJDJ8mlEzPpzIZHA4bO9umCIWlVpkNgcKnwAPKMYp8yTHaoqC71a6IEmBUz6BkWZzWDq2Uw0qzOIJAwz+PXWfSmeZJcFLLkZSi7ERkKCi7i8CCaShkABuqAA1pR8EIRGAALQYyonJSS3ENRDA2wUeyvd6dPVhGw0-RhGOp8IA8xGFkc80rS0Ua3qUh2oO8MDMVjMCiMZEiABmqGY6AoPr2AaD4uxYcuEYQoQpQWNNjsMnMgLGKbT3TC7TcOfsNjzqQL0KKJXQtvtXEdzoobs9lCEm87cMxIbOvel+MQqtMQRmS5ks31sZpAUsZkcIX+cQZJIrpC3KUBupTbuWlbVrW9ZcE2LYUCepRnocwYSrUfYyggbzvBQ+jMq49imLYwTUt4iCft+5i-u0-7UfoQEWtCSKoiWZbnruTqZIeXoUBAKJoihFTdqGGE3rod7UdqsQTI8hiGAqrIauRA7RvYeoqhO1jtAqjFrpkLFohBHEVlWzYwY2zatvxrFCWKWKiVKeISdh4yBJE-jGs4fhhA4zg0kRNgxhplhaW0nn4XpUKZEUuAQMZqF8FxLqkO6vG+hAgYcbAIlXmJzmNERdy0RYNiKgpthxDSEU6q8MSTJYjWGFFIEULF8WljuSX7jxx7CJlQY5ZYl44pht4IP61gyPc8njt0lIuH51UKrVVITEyMy2C1hbtQl-KmdBdaWQhGVZYluWjeJjSTf402shMEyuEyljPAFL0UNmMiuN86lWHMiSmvQ-HwKcnL6WA6FOf2k3mESMRDA4RpUm4U4qf6gSEt0QIvvqfjOCaiyrtF6zZPQXWQ+GWFlUEsbmNMf1TV9NLeXDcqRIySnNaaYPEzC5M9vl-b+IyFCjupryPF9jKWP5Kks-cbMWLERHRNt0LFntkgU2NLk4dqsz43YsTK++Kk2C+MbTOOcxzOMrhqzFxTgZ1Qba1dd6BUE1jGsLMxxK9KlDNqm3tMLUQvqYlgO5QhlsTubsFXesTTUuPTfHExshDS0RftRftGgEnTZtHbX9Zr+QJ-2S4Y3qnmTC+4tMyp1EfeOnmeQqdOhyXQrFOXXCV1hCkmLDOnBJYvRRDSsyRzGjiKj0lKdAkANAA */ /** @xstate-layout N4IgpgJg5mDOIC5QAkD2BbMACdBDAxgBYCWAdmAMS6yzFSkDaADALqKgAOqtALsaqXYgAHogAsAJgA0IAJ6IJATgAcAOgBsAdgCsYzYoCMEsdu3LNAXwsy0mHARLlVyallKosHAE6oAVmHweWAoAYS8wXB5sbz8AnmY2JBAuXn5BJNEEAzF1MVVdXUV1JnVtCXUVGXkECW0DVTEAZgNlRvUDbINS5SsbDGw8IjIwZ1cY-0DggCUwUlw7cbiEoRTiPgEhTI6O-KZtJnMzOs0dKsRNVtU9xqbG5Wzc7V6QWwGHYdHYTx8JoNDwyLRH5LVgrbhrNKbRAPPIFMRFEplCrKM4IXL1MRdCq1C7KRSKRrPV72IZOFxfRaTCgAETAABswFFvrFAsskqt1ulQFscrCTPDiqVypU5OdlNpVBJlMUzLkVCcJET+iTHCNyczfsEAPIcWYakGJTjgzlQrK8-L8hFC5Go1oS7aFfSKJpKuyDVWqMIRPikKD6wIUCACEZkABuqAA1iN8ACogBaSnxUHs42QjLQ81wq1IkXVEzqVTSlQlJgGRoEgyut6kkZeyJkP2JihgLw+LyqDh0yIAM1QXnQqhj3rACeBrOTRtSG3TZtKheUTFLEnL2hXYlt9UaEiUBmdYjx6illesL2V7o+Mzm6Ab-p4geDqjDkZG4SvI8TbMnEOn3MQjR0hb4uoFSNGUkhMIoqKrnkByHioigQUoRRViqF6zPMN5Ni2bYdl2PC9v2qivvM75jkmhrJKmP4iH+AF4kUIFgRIEGomImKFkYyiSNo6h3EYigoeeTi0gyPqNmR95OE+UaqBA9KMqRLLkWCU5cjRs4SkwzH3Ioq56eYUHwqo2RboeoHOgYmjqIJ7zCfJYm3s2rZ9rhPZ9gOcmiYpvyfpRqmmg8BagauoHSroXHrqKaIElcRiHlxjRMCuTwnsSQkjDMuAQJhZHBEGUmkOGMkAhAo5KbAvkcmmv4IIozHGXUpYVGxByNKiFRMKoJz4icOSGGYYg2TWqiZdlvq3nlD7SS+ESlYmFUGBRVXUZkdWdQYjVdHurWos0ErLolJxcR05hWUNHqjTl5VOThnZuYRJVlZqlVUWpq31RtBhNdtrSotuEiqLpRR6IelklMeJ7uHJ8BJGltlgCp35vYgcbqKiqPnR86ruBNiMmjOnGaA0pilJomI6FKf11Pk+KtPiJTOkwg2pWe8OfLjKb+QTShqN1bHKOUOQC21UWaJZXV6V0G1MJoUopX0bps3WDmJnj1XqWDm73CYuiy0Ypiol9BZ4gN4raDomh8ZjTiXhh42q5zSOmpIUE3PkvH7KU7QmPo1sjCJjJXb8asrdCmilqofE63oEj69oUEVF1i6x7LBzStZLOK8Nl327lIfIwgZiKF1Nw3GWShJXsu31ZbiUErXuRnZn1YejqsxB3E+cBeHWvZKYMdxxu9TSvuYhNdKjR3FYVhAA */
id: 'Home machine', id: 'Home machine',
initial: 'Reading projects', initial: 'Reading projects',
context: { context: ({ input }) => ({
projects: [] as ProjectWithEntryPointMetadata[], projects: [] as ProjectWithEntryPointMetadata[],
defaultProjectName: '', ...input,
defaultDirectory: '', }),
},
on: {
assign: {
actions: assign((_, event) => ({
...event.data,
})),
target: '.Reading projects',
},
},
states: { states: {
'Has no projects': { 'Has no projects': {
on: { on: {
@ -135,30 +161,9 @@ export const homeMachine = createMachine(
entry: ['navigateToProject'], entry: ['navigateToProject'],
}, },
}, },
schema: {
events: {} as
| { type: 'Open project'; data: HomeCommandSchema['Open project'] }
| { type: 'Rename project'; data: HomeCommandSchema['Rename project'] }
| { type: 'Create project'; data: HomeCommandSchema['Create project'] }
| { type: 'Delete project'; data: HomeCommandSchema['Delete project'] }
| { type: 'navigate'; data: { name: string } }
| {
type: 'done.invoke.read-projects'
data: ProjectWithEntryPointMetadata[]
}
| { type: 'assign'; data: { [key: string]: any } },
}, },
predictableActionArguments: true, predictableActionArguments: true,
preserveActionOrder: true, preserveActionOrder: true,
tsTypes: {} as import('./homeMachine.typegen').Typegen0,
}, },
{
actions: {
setProjects: assign((_, event) => {
return { projects: event.data as ProjectWithEntryPointMetadata[] }
}),
},
}
) )

View File

@ -39,12 +39,17 @@ import { isTauri } from 'lib/isTauri'
import { kclManager } from 'lang/KclSingleton' import { kclManager } from 'lang/KclSingleton'
import { useLspContext } from 'components/LspProvider' import { useLspContext } from 'components/LspProvider'
import { useValidateSettings } from 'hooks/useValidateSettings' import { useValidateSettings } from 'hooks/useValidateSettings'
import { AppMachineContext } from 'machines/appMachine'
// This route only opens in the Tauri desktop context for now, // This route only opens in the Tauri desktop context for now,
// as defined in Router.tsx, so we can use the Tauri APIs and types. // as defined in Router.tsx, so we can use the Tauri APIs and types.
const Home = () => { const Home = () => {
useValidateSettings() useValidateSettings()
const { commandBarSend } = useCommandsContext() const home = AppMachineContext.useSelector(s => s.children.home)
home?.on('done.invoke.read-projects', (e) => {
// do some nonsense
})
const commands = AppMachineContext.useSelector(s => s.children.commands)
const navigate = useNavigate() const navigate = useNavigate()
const { projects: loadedProjects } = useLoaderData() as HomeLoaderData const { projects: loadedProjects } = useLoaderData() as HomeLoaderData
const { const {

View File

@ -2868,10 +2868,10 @@
"@babel/types" "^7.21.4" "@babel/types" "^7.21.4"
recast "^0.23.1" recast "^0.23.1"
"@xstate/react@^3.2.2": "@xstate/react@^4.1.0":
version "3.2.2" version "4.1.0"
resolved "https://registry.yarnpkg.com/@xstate/react/-/react-3.2.2.tgz#ddf0f9d75e2c19375b1e1b7335e72cb99762aed8" resolved "https://registry.yarnpkg.com/@xstate/react/-/react-4.1.0.tgz#369378951e1f7f9326700f65ed02847598aad704"
integrity sha512-feghXWLedyq8JeL13yda3XnHPZKwYDN5HPBLykpLeuNpr9178tQd2/3d0NrH6gSd0sG5mLuLeuD+ck830fgzLQ== integrity sha512-Fh89luCwuMXIVXIua67d8pNuVgdGpqke2jHfIIL+ZjkfNh6YFtPDSwNSZZDhdNUsOW1zZYSbtUzbC8MIUyTSHQ==
dependencies: dependencies:
use-isomorphic-layout-effect "^1.1.2" use-isomorphic-layout-effect "^1.1.2"
use-sync-external-store "^1.0.0" use-sync-external-store "^1.0.0"
@ -8372,10 +8372,10 @@ typed-array-length@^1.0.4:
for-each "^0.3.3" for-each "^0.3.3"
is-typed-array "^1.1.9" is-typed-array "^1.1.9"
typescript@^5.2.2: typescript@^5.4.2:
version "5.3.2" version "5.4.2"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.2.tgz#00d1c7c1c46928c5845c1ee8d0cc2791031d4c43" resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.4.2.tgz#0ae9cebcfae970718474fe0da2c090cad6577372"
integrity sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ== integrity sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==
ua-parser-js@^1.0.35: ua-parser-js@^1.0.35:
version "1.0.35" version "1.0.35"
@ -8912,11 +8912,16 @@ ws@^8.8.0:
resolved "https://registry.yarnpkg.com/xstate/-/xstate-5.0.0-beta.54.tgz#d80f1a9e43ad883a65fc9b399161bd39633bd9bf" resolved "https://registry.yarnpkg.com/xstate/-/xstate-5.0.0-beta.54.tgz#d80f1a9e43ad883a65fc9b399161bd39633bd9bf"
integrity sha512-BTnCPBQ2iTKe4uCnHEe1hNx6VTbXU+5mQGybSQHOjTLiBi4Ryi+tL9T6N1tmqagvM8rfl4XRfvndogfWCWcdpw== integrity sha512-BTnCPBQ2iTKe4uCnHEe1hNx6VTbXU+5mQGybSQHOjTLiBi4Ryi+tL9T6N1tmqagvM8rfl4XRfvndogfWCWcdpw==
xstate@^4.33.4, xstate@^4.38.2: xstate@^4.33.4:
version "4.38.3" version "4.38.3"
resolved "https://registry.yarnpkg.com/xstate/-/xstate-4.38.3.tgz#4e15e7ad3aa0ca1eea2010548a5379966d8f1075" resolved "https://registry.yarnpkg.com/xstate/-/xstate-4.38.3.tgz#4e15e7ad3aa0ca1eea2010548a5379966d8f1075"
integrity sha512-SH7nAaaPQx57dx6qvfcIgqKRXIh4L0A1iYEqim4s1u7c9VoCgzZc+63FY90AKU4ZzOC2cfJzTnpO4zK7fCUzzw== integrity sha512-SH7nAaaPQx57dx6qvfcIgqKRXIh4L0A1iYEqim4s1u7c9VoCgzZc+63FY90AKU4ZzOC2cfJzTnpO4zK7fCUzzw==
xstate@^5.9.1:
version "5.9.1"
resolved "https://registry.yarnpkg.com/xstate/-/xstate-5.9.1.tgz#1f167fae423cadd362762e2ff697d84fd774979c"
integrity sha512-85edx7iMqRJSRlEPevDwc98EWDYUlT5zEQ54AXuRVR+G76gFbcVTAUdtAeqOVxy8zYnUr9FBB5114iK6enljjw==
y18n@^5.0.5: y18n@^5.0.5:
version "5.0.8" version "5.0.8"
resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55"