Compare commits
	
		
			1 Commits
		
	
	
		
			kcl-80
			...
			frank-xsta
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| ac64b9fa45 | 
| @ -26,7 +26,7 @@ | ||||
|     "@types/react-dom": "^18.0.0", | ||||
|     "@uiw/react-codemirror": "^4.21.20", | ||||
|     "@xstate/inspect": "^0.8.0", | ||||
|     "@xstate/react": "^3.2.2", | ||||
|     "@xstate/react": "^4.1.0", | ||||
|     "crypto-js": "^4.2.0", | ||||
|     "debounce-promise": "^3.1.2", | ||||
|     "formik": "^2.4.3", | ||||
| @ -50,7 +50,7 @@ | ||||
|     "three": "^0.160.0", | ||||
|     "toml": "^3.0.0", | ||||
|     "ts-node": "^10.9.1", | ||||
|     "typescript": "^5.2.2", | ||||
|     "typescript": "^5.4.2", | ||||
|     "uuid": "^9.0.1", | ||||
|     "vitest": "^1.3.1", | ||||
|     "vscode-jsonrpc": "^8.1.0", | ||||
| @ -58,7 +58,7 @@ | ||||
|     "wasm-pack": "^0.12.1", | ||||
|     "web-vitals": "^3.5.0", | ||||
|     "ws": "^8.13.0", | ||||
|     "xstate": "^4.38.2", | ||||
|     "xstate": "^5.9.1", | ||||
|     "zustand": "^4.4.5" | ||||
|   }, | ||||
|   "scripts": { | ||||
|  | ||||
| @ -1,12 +1,11 @@ | ||||
| import { AppMachineContext } from 'machines/appMachine' | ||||
| import Loading from './components/Loading' | ||||
| import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext' | ||||
|  | ||||
| // Wrapper around protected routes, used in src/Router.tsx | ||||
| export const Auth = ({ children }: React.PropsWithChildren) => { | ||||
|   const { auth } = useSettingsAuthContext() | ||||
|   const isLoggingIn = auth?.state.matches('checkIfLoggedIn') | ||||
|   const isAuthenticating = AppMachineContext.useSelector(s => s.matches('Loading')) | ||||
|  | ||||
|   return isLoggingIn ? ( | ||||
|   return isAuthenticating ? ( | ||||
|     <Loading> | ||||
|       <span data-testid="initial-load">Loading Modeling App...</span> | ||||
|     </Loading> | ||||
|  | ||||
| @ -16,8 +16,6 @@ import makeUrlPathRelative from './lib/makeUrlPathRelative' | ||||
| import DownloadAppBanner from 'components/DownloadAppBanner' | ||||
| import { WasmErrBanner } from 'components/WasmErrBanner' | ||||
| import { CommandBar } from 'components/CommandBar/CommandBar' | ||||
| import ModelingMachineProvider from 'components/ModelingMachineProvider' | ||||
| import FileMachineProvider from 'components/FileMachineProvider' | ||||
| import { paths } from 'lib/paths' | ||||
| import { | ||||
|   fileLoader, | ||||
| @ -25,10 +23,7 @@ import { | ||||
|   indexLoader, | ||||
|   onboardingRedirectLoader, | ||||
| } from 'lib/routeLoaders' | ||||
| import { CommandBarProvider } from 'components/CommandBar/CommandBarProvider' | ||||
| import SettingsAuthProvider from 'components/SettingsAuthProvider' | ||||
| import LspProvider from 'components/LspProvider' | ||||
| import { KclContextProvider } from 'lang/KclSingleton' | ||||
| import { AppMachineProvider } from 'components/AppMachineProvider' | ||||
|  | ||||
| export const BROWSER_FILE_NAME = 'new' | ||||
|  | ||||
| @ -37,15 +32,9 @@ const router = createBrowserRouter([ | ||||
|     loader: indexLoader, | ||||
|     id: paths.INDEX, | ||||
|     element: ( | ||||
|       <CommandBarProvider> | ||||
|         <KclContextProvider> | ||||
|           <SettingsAuthProvider> | ||||
|             <LspProvider> | ||||
|       <AppMachineProvider> | ||||
|         <Outlet /> | ||||
|             </LspProvider> | ||||
|           </SettingsAuthProvider> | ||||
|         </KclContextProvider> | ||||
|       </CommandBarProvider> | ||||
|       </AppMachineProvider> | ||||
|     ), | ||||
|     children: [ | ||||
|       { | ||||
| @ -61,14 +50,10 @@ const router = createBrowserRouter([ | ||||
|         id: paths.FILE, | ||||
|         element: ( | ||||
|           <Auth> | ||||
|             <FileMachineProvider> | ||||
|               <ModelingMachineProvider> | ||||
|             <Outlet /> | ||||
|             <App /> | ||||
|             <CommandBar /> | ||||
|               </ModelingMachineProvider> | ||||
|             <WasmErrBanner /> | ||||
|             </FileMachineProvider> | ||||
|             {!isTauri() && import.meta.env.PROD && <DownloadAppBanner />} | ||||
|           </Auth> | ||||
|         ), | ||||
|  | ||||
							
								
								
									
										21
									
								
								src/components/AppMachineProvider.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								src/components/AppMachineProvider.tsx
									
									
									
									
									
										Normal 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> | ||||
|   ) | ||||
| } | ||||
| @ -13,6 +13,7 @@ import { | ||||
| } from 'components/NetworkHealthIndicator' | ||||
| import { useKclContext } from 'lang/KclSingleton' | ||||
| import { useStore } from 'useStore' | ||||
| import { AppMachineContext } from 'machines/appMachine' | ||||
|  | ||||
| // This might not be necessary, AnyStateMachine from xstate is working | ||||
| export type AllMachines = | ||||
| @ -46,7 +47,7 @@ export default function useStateMachineCommands< | ||||
|   allCommandsRequireNetwork = false, | ||||
|   onCancel, | ||||
| }: UseStateMachineCommandsArgs<T, S>) { | ||||
|   const { commandBarSend } = useCommandsContext() | ||||
|   const commandsActorRef = AppMachineContext.useSelector(s => s.children.commands) | ||||
|   const { overallState } = useNetworkStatus() | ||||
|   const { isExecuting } = useKclContext() | ||||
|   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 | ||||
|  | ||||
|     commandBarSend({ type: 'Add commands', data: { commands: newCommands } }) | ||||
|     commandsActorRef?.send({ type: 'Add commands', data: { commands: newCommands } }) | ||||
|  | ||||
|     return () => { | ||||
|       commandBarSend({ | ||||
|       commandsActorRef?.send({ | ||||
|         type: 'Remove commands', | ||||
|         data: { commands: newCommands }, | ||||
|       }) | ||||
|  | ||||
							
								
								
									
										264
									
								
								src/machines/appMachine.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										264
									
								
								src/machines/appMachine.ts
									
									
									
									
									
										Normal 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) | ||||
| @ -1,4 +1,4 @@ | ||||
| import { createMachine, assign } from 'xstate' | ||||
| import { createMachine, assign, setup, fromPromise } from 'xstate' | ||||
| import { Models } from '@kittycad/lib' | ||||
| import withBaseURL from '../lib/withBaseURL' | ||||
| import { isTauri } from 'lib/isTauri' | ||||
| @ -39,26 +39,42 @@ export type Events = | ||||
|     } | ||||
|  | ||||
| export const TOKEN_PERSIST_KEY = 'TOKEN_PERSIST_KEY' | ||||
| const persistedToken = | ||||
| export const persistedToken = | ||||
|   localStorage?.getItem(TOKEN_PERSIST_KEY) || | ||||
|   getCookie('__Secure-next-auth.session-token') || | ||||
|   '' | ||||
|   undefined | ||||
|  | ||||
| export const authMachine = createMachine<UserContext, Events>( | ||||
|   { | ||||
| export const authMachine = setup({ | ||||
|   types: { | ||||
|     events: {} as { type: 'Log out' } | { type: 'Log in' }, | ||||
|     context: {} as UserContext, | ||||
|   }, | ||||
|   actors: { | ||||
|     fetchUser: fromPromise(({ input }: { input: { token: string | undefined }}) => getUser(input.token)), | ||||
|   }, | ||||
|   actions: { | ||||
|     goToIndexPage: () => ({}), | ||||
|     goToSignInPage: () => ({}), | ||||
|   } | ||||
| }).createMachine({ | ||||
|   id: 'Auth', | ||||
|   preserveActionOrder: true, | ||||
|   initial: 'checkIfLoggedIn', | ||||
|   context: { | ||||
|     token: persistedToken, | ||||
|   }, | ||||
|   states: { | ||||
|     checkIfLoggedIn: { | ||||
|       id: 'check-if-logged-in', | ||||
|       invoke: { | ||||
|           src: 'getUser', | ||||
|           id: 'check-logged-in', | ||||
|         id: 'getUser', | ||||
|         src: 'fetchUser', | ||||
|         input: ({ context: { token } }) => ({ token }), | ||||
|         onDone: [ | ||||
|           { | ||||
|             target: 'loggedIn', | ||||
|             actions: assign({ | ||||
|                 user: (context, event) => event.data, | ||||
|               user: ({ event }) => event.output, | ||||
|             }), | ||||
|           }, | ||||
|         ], | ||||
| @ -81,44 +97,24 @@ export const authMachine = createMachine<UserContext, Events>( | ||||
|       }, | ||||
|     }, | ||||
|     loggedOut: { | ||||
|         entry: ['goToSignInPage'], | ||||
|       entry: { type: 'goToIndexPage' }, | ||||
|       on: { | ||||
|         'Log in': { | ||||
|           target: 'checkIfLoggedIn', | ||||
|             actions: assign({ | ||||
|               token: (_, event) => { | ||||
|                 const token = event.token || '' | ||||
|                 localStorage.setItem(TOKEN_PERSIST_KEY, token) | ||||
|                 return token | ||||
|               }, | ||||
|             }), | ||||
|         }, | ||||
|       }, | ||||
|     }, | ||||
|   }, | ||||
|     schema: { events: {} as { type: 'Log out' } | { type: 'Log in' } }, | ||||
|     predictableActionArguments: true, | ||||
|     preserveActionOrder: true, | ||||
|     context: { | ||||
|       token: persistedToken, | ||||
|     }, | ||||
|   }, | ||||
|   { | ||||
|     actions: {}, | ||||
|     services: { getUser }, | ||||
|     guards: {}, | ||||
|     delays: {}, | ||||
|   } | ||||
| ) | ||||
| }) | ||||
|  | ||||
| async function getUser(context: UserContext) { | ||||
| export async function getUser(token?: string) { | ||||
|   const url = withBaseURL('/user') | ||||
|   const headers: { [key: string]: string } = { | ||||
|     'Content-Type': 'application/json', | ||||
|   } | ||||
|  | ||||
|   if (!context.token && isTauri()) throw new Error('No token found') | ||||
|   if (context.token) headers['Authorization'] = `Bearer ${context.token}` | ||||
|   if (!token && isTauri()) throw new Error('No token found') | ||||
|   if (token) headers['Authorization'] = `Bearer ${token}` | ||||
|   if (SKIP_AUTH) return LOCAL_USER | ||||
|  | ||||
|   const userPromise = !isTauri() | ||||
| @ -130,7 +126,7 @@ async function getUser(context: UserContext) { | ||||
|         .then((res) => res.json()) | ||||
|         .catch((err) => console.error('error from Browser getUser', err)) | ||||
|     : invoke<Models['User_type'] | Record<'error_code', unknown>>('get_user', { | ||||
|         token: context.token, | ||||
|         token: token, | ||||
|         hostname: VITE_KC_API_BASE_URL, | ||||
|       }).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) | ||||
|  | ||||
|   return user | ||||
|   return user as Models['User_type'] | ||||
| } | ||||
|  | ||||
| function getCookie(cname: string): string { | ||||
| export function getCookie(cname: string): string { | ||||
|   if (isTauri()) { | ||||
|     return '' | ||||
|   } | ||||
|  | ||||
| @ -1,28 +1,54 @@ | ||||
| import { assign, createMachine } from 'xstate' | ||||
| import { assign, createMachine, fromPromise, setup } from 'xstate' | ||||
| import { type ProjectWithEntryPointMetadata } from 'lib/types' | ||||
| 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', | ||||
|  | ||||
|     initial: 'Reading projects', | ||||
|  | ||||
|     context: { | ||||
|     context: ({ input }) => ({ | ||||
|       projects: [] as ProjectWithEntryPointMetadata[], | ||||
|       defaultProjectName: '', | ||||
|       defaultDirectory: '', | ||||
|     }, | ||||
|       ...input, | ||||
|     }), | ||||
|  | ||||
|     on: { | ||||
|       assign: { | ||||
|         actions: assign((_, event) => ({ | ||||
|           ...event.data, | ||||
|         })), | ||||
|         target: '.Reading projects', | ||||
|       }, | ||||
|     }, | ||||
|     states: { | ||||
|       'Has no projects': { | ||||
|         on: { | ||||
| @ -135,30 +161,9 @@ export const homeMachine = createMachine( | ||||
|         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, | ||||
|     preserveActionOrder: true, | ||||
|     tsTypes: {} as import('./homeMachine.typegen').Typegen0, | ||||
|   }, | ||||
|   { | ||||
|     actions: { | ||||
|       setProjects: assign((_, event) => { | ||||
|         return { projects: event.data as ProjectWithEntryPointMetadata[] } | ||||
|       }), | ||||
|     }, | ||||
|   } | ||||
| ) | ||||
|  | ||||
| @ -39,12 +39,17 @@ import { isTauri } from 'lib/isTauri' | ||||
| import { kclManager } from 'lang/KclSingleton' | ||||
| import { useLspContext } from 'components/LspProvider' | ||||
| import { useValidateSettings } from 'hooks/useValidateSettings' | ||||
| import { AppMachineContext } from 'machines/appMachine' | ||||
|  | ||||
| // 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. | ||||
| const Home = () => { | ||||
|   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 { projects: loadedProjects } = useLoaderData() as HomeLoaderData | ||||
|   const { | ||||
|  | ||||
							
								
								
									
										23
									
								
								yarn.lock
									
									
									
									
									
								
							
							
						
						
									
										23
									
								
								yarn.lock
									
									
									
									
									
								
							| @ -2868,10 +2868,10 @@ | ||||
|     "@babel/types" "^7.21.4" | ||||
|     recast "^0.23.1" | ||||
|  | ||||
| "@xstate/react@^3.2.2": | ||||
|   version "3.2.2" | ||||
|   resolved "https://registry.yarnpkg.com/@xstate/react/-/react-3.2.2.tgz#ddf0f9d75e2c19375b1e1b7335e72cb99762aed8" | ||||
|   integrity sha512-feghXWLedyq8JeL13yda3XnHPZKwYDN5HPBLykpLeuNpr9178tQd2/3d0NrH6gSd0sG5mLuLeuD+ck830fgzLQ== | ||||
| "@xstate/react@^4.1.0": | ||||
|   version "4.1.0" | ||||
|   resolved "https://registry.yarnpkg.com/@xstate/react/-/react-4.1.0.tgz#369378951e1f7f9326700f65ed02847598aad704" | ||||
|   integrity sha512-Fh89luCwuMXIVXIua67d8pNuVgdGpqke2jHfIIL+ZjkfNh6YFtPDSwNSZZDhdNUsOW1zZYSbtUzbC8MIUyTSHQ== | ||||
|   dependencies: | ||||
|     use-isomorphic-layout-effect "^1.1.2" | ||||
|     use-sync-external-store "^1.0.0" | ||||
| @ -8372,10 +8372,10 @@ typed-array-length@^1.0.4: | ||||
|     for-each "^0.3.3" | ||||
|     is-typed-array "^1.1.9" | ||||
|  | ||||
| typescript@^5.2.2: | ||||
|   version "5.3.2" | ||||
|   resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.2.tgz#00d1c7c1c46928c5845c1ee8d0cc2791031d4c43" | ||||
|   integrity sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ== | ||||
| typescript@^5.4.2: | ||||
|   version "5.4.2" | ||||
|   resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.4.2.tgz#0ae9cebcfae970718474fe0da2c090cad6577372" | ||||
|   integrity sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ== | ||||
|  | ||||
| ua-parser-js@^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" | ||||
|   integrity sha512-BTnCPBQ2iTKe4uCnHEe1hNx6VTbXU+5mQGybSQHOjTLiBi4Ryi+tL9T6N1tmqagvM8rfl4XRfvndogfWCWcdpw== | ||||
|  | ||||
| xstate@^4.33.4, xstate@^4.38.2: | ||||
| xstate@^4.33.4: | ||||
|   version "4.38.3" | ||||
|   resolved "https://registry.yarnpkg.com/xstate/-/xstate-4.38.3.tgz#4e15e7ad3aa0ca1eea2010548a5379966d8f1075" | ||||
|   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: | ||||
|   version "5.0.8" | ||||
|   resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	