fix: big clean up

This commit is contained in:
Kevin Nadro
2025-04-22 12:59:35 -05:00
parent 4e9bdb5b41
commit 81d6c4c1b3
5 changed files with 18 additions and 1017 deletions

View File

@ -1,492 +0,0 @@
import { useMachine } from '@xstate/react'
import { createContext, useCallback, useEffect, useState } from 'react'
import toast from 'react-hot-toast'
import { useLocation, useNavigate, useSearchParams } from 'react-router-dom'
import type { Actor, AnyStateMachine, Prop, StateFrom } from 'xstate'
import { fromPromise } from 'xstate'
import { useLspContext } from '@src/components/LspProvider'
import { useFileSystemWatcher } from '@src/hooks/useFileSystemWatcher'
import { useProjectsLoader } from '@src/hooks/useProjectsLoader'
import useStateMachineCommands from '@src/hooks/useStateMachineCommands'
import { newKclFile } from '@src/lang/project'
import { projectsCommandBarConfig } from '@src/lib/commandBarConfigs/projectsCommandConfig'
import {
CREATE_FILE_URL_PARAM,
FILE_EXT,
PROJECT_ENTRYPOINT,
} from '@src/lib/constants'
import {
createNewProjectDirectory,
listProjects,
renameProjectDirectory,
} from '@src/lib/desktop'
import {
doesProjectNameNeedInterpolated,
getNextFileName,
getNextProjectIndex,
getUniqueProjectName,
interpolateProjectNameWithIndex,
} from '@src/lib/desktopFS'
import { isDesktop } from '@src/lib/isDesktop'
import { PATHS } from '@src/lib/paths'
import type { Project } from '@src/lib/project'
import { codeManager, kclManager } from '@src/lib/singletons'
import { err } from '@src/lib/trap'
import { useSettings } from '@src/lib/singletons'
import { commandBarActor } from '@src/machines/commandBarMachine'
import { projectsMachine } from '@src/machines/projectsMachine'
type MachineContext<T extends AnyStateMachine> = {
state?: StateFrom<T>
send: Prop<Actor<T>, 'send'>
}
export const ProjectsMachineContext = createContext(
{} as MachineContext<typeof projectsMachine>
)
/**
* Watches the project directory and provides project management-related commands,
* like "Create project", "Open project", "Delete project", etc.
*
* If in the future we implement full-fledge project management in the web version,
* we can unify these components but for now, we need this to be only for the desktop version.
*/
export const ProjectsContextProvider = ({
children,
}: {
children: React.ReactNode
}) => {
return isDesktop() ? (
<ProjectsContextDesktop>{children}</ProjectsContextDesktop>
) : (
<ProjectsContextWeb>{children}</ProjectsContextWeb>
)
}
/**
* We need some of the functionality of the ProjectsContextProvider in the web version
* but we can't perform file system operations in the browser,
* so most of the behavior of this machine is stubbed out.
*/
const ProjectsContextWeb = ({ children }: { children: React.ReactNode }) => {
const [searchParams, setSearchParams] = useSearchParams()
const clearImportSearchParams = useCallback(() => {
// Clear the search parameters related to the "Import file from URL" command
// or we'll never be able cancel or submit it.
searchParams.delete(CREATE_FILE_URL_PARAM)
searchParams.delete('code')
searchParams.delete('name')
searchParams.delete('units')
setSearchParams(searchParams)
}, [searchParams, setSearchParams])
const settings = useSettings()
const [state, send, actor] = useMachine(
projectsMachine.provide({
actions: {
navigateToProject: () => {},
navigateToProjectIfNeeded: () => {},
navigateToFile: () => {},
toastSuccess: ({ event }) =>
toast.success(
('data' in event && typeof event.data === 'string' && event.data) ||
('output' in event &&
'message' in event.output &&
typeof event.output.message === 'string' &&
event.output.message) ||
''
),
toastError: ({ event }) =>
toast.error(
('data' in event && typeof event.data === 'string' && event.data) ||
('output' in event &&
typeof event.output === 'string' &&
event.output) ||
''
),
},
actors: {
readProjects: fromPromise(async () => [] as Project[]),
createProject: fromPromise(async () => ({
message: 'not implemented on web',
})),
renameProject: fromPromise(async () => ({
message: 'not implemented on web',
oldName: '',
newName: '',
})),
deleteProject: fromPromise(async () => ({
message: 'not implemented on web',
name: '',
})),
createFile: fromPromise(async ({ input }) => {
// Browser version doesn't navigate, just overwrites the current file
clearImportSearchParams()
const codeToWrite = newKclFile(
input.code,
settings.modeling.defaultUnit.current
)
if (err(codeToWrite)) return Promise.reject(codeToWrite)
codeManager.updateCodeStateEditor(codeToWrite)
await codeManager.writeToFile()
await kclManager.executeCode()
return {
message: 'File overwritten successfully',
fileName: input.name,
projectName: '',
}
}),
},
}),
{
input: {
projects: [],
defaultProjectName: settings.projects.defaultProjectName.current,
defaultDirectory: settings.app.projectDirectory.current,
hasListedProjects: false,
},
}
)
// register all project-related command palette commands
useStateMachineCommands({
machineId: 'projects',
send,
state,
commandBarConfig: projectsCommandBarConfig,
actor,
onCancel: clearImportSearchParams,
})
return (
<ProjectsMachineContext.Provider
value={{
state,
send,
}}
>
{children}
</ProjectsMachineContext.Provider>
)
}
const ProjectsContextDesktop = ({
children,
}: {
children: React.ReactNode
}) => {
const navigate = useNavigate()
const location = useLocation()
const [searchParams, setSearchParams] = useSearchParams()
const clearImportSearchParams = useCallback(() => {
// Clear the search parameters related to the "Import file from URL" command
// or we'll never be able cancel or submit it.
searchParams.delete(CREATE_FILE_URL_PARAM)
searchParams.delete('code')
searchParams.delete('name')
searchParams.delete('units')
setSearchParams(searchParams)
}, [searchParams, setSearchParams])
const { onProjectOpen } = useLspContext()
const settings = useSettings()
const [projectsLoaderTrigger, setProjectsLoaderTrigger] = useState(0)
const { projectPaths, projectsDir } = useProjectsLoader([
projectsLoaderTrigger,
])
const [state, send, actor] = useMachine(
projectsMachine.provide({
actions: {
navigateToProject: ({ context, event }) => {
const nameFromEventData =
'data' in event &&
event.data &&
'name' in event.data &&
event.data.name
const nameFromOutputData =
'output' in event &&
event.output &&
'name' in event.output &&
event.output.name
const name = nameFromEventData || nameFromOutputData
if (name) {
let projectPath =
context.defaultDirectory + window.electron.path.sep + name
onProjectOpen(
{
name,
path: projectPath,
},
null
)
commandBarActor.send({ type: 'Close' })
const newPathName = `${PATHS.FILE}/${encodeURIComponent(
projectPath
)}`
navigate(newPathName)
}
},
navigateToProjectIfNeeded: ({ event }) => {
if (
event.type.startsWith('xstate.done.actor.') &&
'output' in event
) {
const isInAProject = location.pathname.startsWith(PATHS.FILE)
const isInDeletedProject =
event.type === 'xstate.done.actor.delete-project' &&
isInAProject &&
decodeURIComponent(location.pathname).includes(event.output.name)
if (isInDeletedProject) {
navigate(PATHS.HOME)
return
}
const isInRenamedProject =
event.type === 'xstate.done.actor.rename-project' &&
isInAProject &&
decodeURIComponent(location.pathname).includes(
event.output.oldName
)
if (isInRenamedProject) {
// TODO: In future, we can navigate to the new project path
// directly, but we need to coordinate with
// @lf94's useFileSystemWatcher in SettingsAuthProvider.tsx:224
// Because it's beating us to the punch and updating the route
// const newPathName = location.pathname.replace(
// encodeURIComponent(event.output.oldName),
// encodeURIComponent(event.output.newName)
// )
// navigate(newPathName)
return
}
}
},
navigateToFile: ({ context, event }) => {
if (event.type !== 'xstate.done.actor.create-file') return
// For now, the browser version of create-file doesn't need to navigate
// since it just overwrites the current file.
if (!isDesktop()) return
let projectPath = window.electron.join(
context.defaultDirectory,
event.output.projectName
)
let filePath = window.electron.join(
projectPath,
event.output.fileName
)
onProjectOpen(
{
name: event.output.projectName,
path: projectPath,
},
null
)
const pathToNavigateTo = `${PATHS.FILE}/${encodeURIComponent(
filePath
)}`
navigate(pathToNavigateTo)
},
toastSuccess: ({ event }) =>
toast.success(
('data' in event && typeof event.data === 'string' && event.data) ||
('output' in event &&
'message' in event.output &&
typeof event.output.message === 'string' &&
event.output.message) ||
''
),
toastError: ({ event }) =>
toast.error(
('data' in event && typeof event.data === 'string' && event.data) ||
('output' in event &&
typeof event.output === 'string' &&
event.output) ||
('error' in event &&
event.error instanceof Error &&
event.error.message) ||
''
),
},
actors: {
readProjects: fromPromise(() => {
return listProjects()
}),
createProject: fromPromise(async ({ input }) => {
let name = (
input && 'name' in input && input.name
? input.name
: settings.projects.defaultProjectName.current
).trim()
const uniqueName = getUniqueProjectName(name, input.projects)
await createNewProjectDirectory(uniqueName)
return {
message: `Successfully created "${uniqueName}"`,
name: uniqueName,
}
}),
renameProject: fromPromise(async ({ input }) => {
const {
oldName,
newName,
defaultProjectName,
defaultDirectory,
projects,
} = input
let name = newName ? newName : defaultProjectName
if (doesProjectNameNeedInterpolated(name)) {
const nextIndex = getNextProjectIndex(name, projects)
name = interpolateProjectNameWithIndex(name, nextIndex)
}
// Toast an error if the project name is taken
if (projects.find((p) => p.name === name)) {
return Promise.reject(
new Error(`Project with name "${name}" already exists`)
)
}
await renameProjectDirectory(
window.electron.path.join(defaultDirectory, oldName),
name
)
return {
message: `Successfully renamed "${oldName}" to "${name}"`,
oldName: oldName,
newName: name,
}
}),
deleteProject: fromPromise(async ({ input }) => {
await window.electron.rm(
window.electron.path.join(input.defaultDirectory, input.name),
{
recursive: true,
}
)
return {
message: `Successfully deleted "${input.name}"`,
name: input.name,
}
}),
createFile: fromPromise(async ({ input }) => {
let projectName =
(input.method === 'newProject' ? input.name : input.projectName) ||
settings.projects.defaultProjectName.current
let fileName =
input.method === 'newProject'
? PROJECT_ENTRYPOINT
: input.name.endsWith(FILE_EXT)
? input.name
: input.name + FILE_EXT
let message = 'File created successfully'
const needsInterpolated = doesProjectNameNeedInterpolated(projectName)
if (needsInterpolated) {
const nextIndex = getNextProjectIndex(projectName, input.projects)
projectName = interpolateProjectNameWithIndex(
projectName,
nextIndex
)
}
// Create the project around the file if newProject
let fileLoaded = false
if (input.method === 'newProject') {
await createNewProjectDirectory(projectName, input.code)
fileLoaded = true
message = `Project "${projectName}" created successfully with link contents`
} else {
message = `File "${fileName}" created successfully`
}
// Create the file
let baseDir = window.electron.join(
settings.app.projectDirectory.current,
projectName
)
const { name, path } = getNextFileName({
entryName: fileName,
baseDir,
})
fileName = name
if (!fileLoaded) {
const codeToWrite = newKclFile(
input.code,
settings.modeling.defaultUnit.current
)
if (err(codeToWrite)) return Promise.reject(codeToWrite)
await window.electron.writeFile(path, codeToWrite)
}
// TODO: Return the project's file name if one was created.
return {
message,
fileName,
projectName,
}
}),
},
}),
{
input: {
projects: projectPaths,
defaultProjectName: settings.projects.defaultProjectName.current,
defaultDirectory: settings.app.projectDirectory.current,
hasListedProjects: false,
},
}
)
useFileSystemWatcher(
async () => {
// Gotcha: Chokidar is buggy. It will emit addDir or add on files that did not get created.
// This means while the application initialize and Chokidar initializes you cannot tell if
// a directory or file is actually created or they are buggy signals. This means you must
// ignore all signals during initialization because it is ambiguous. Once those signals settle
// you can actually start listening to real signals.
// If someone creates folders or files during initialization we ignore those events!
if (!actor.getSnapshot().context.hasListedProjects) {
return
}
return setProjectsLoaderTrigger(projectsLoaderTrigger + 1)
},
projectsDir ? [projectsDir] : []
)
// Gotcha: Triggers listProjects() on chokidar changes
// Gotcha: Load the projects when the projectDirectory changes.
const projectDirectory = settings.app.projectDirectory.current
useEffect(() => {
send({ type: 'Read projects', data: {} })
}, [projectPaths, projectDirectory])
// register all project-related command palette commands
useStateMachineCommands({
machineId: 'projects',
send,
state,
commandBarConfig: projectsCommandBarConfig,
actor,
onCancel: clearImportSearchParams,
})
return (
<ProjectsMachineContext.Provider
value={{
state,
send,
}}
>
{children}
</ProjectsMachineContext.Provider>
)
}

View File

@ -1,177 +1,6 @@
import { CommandBarOverwriteWarning } from '@src/components/CommandBarOverwriteWarning'
import type { StateMachineCommandSetConfig } from '@src/lib/commandTypes'
import { isDesktop } from '@src/lib/isDesktop'
import type { projectsMachine } from '@src/machines/projectsMachine'
import type { Command, CommandArgumentOption } from '@src/lib/commandTypes'
export type ProjectsCommandSchema = {
'Read projects': Record<string, unknown>
'Create project': {
name: string
}
'Open project': {
name: string
}
'Delete project': {
name: string
}
'Rename project': {
oldName: string
newName: string
}
'Import file from URL': {
name: string
code?: string
method: 'newProject' | 'existingProject'
projectName?: string
}
}
export const projectsCommandBarConfig: StateMachineCommandSetConfig<
typeof projectsMachine,
ProjectsCommandSchema
> = {
'Open project': {
icon: 'arrowRight',
description: 'Open a project',
status: isDesktop() ? 'active' : 'inactive',
args: {
name: {
inputType: 'options',
required: true,
options: (_, context) =>
context?.projects.map((p) => ({
name: p.name,
value: p.name,
})) || [],
},
},
},
'Create project': {
icon: 'folderPlus',
description: 'Create a project',
status: isDesktop() ? 'active' : 'inactive',
args: {
name: {
inputType: 'string',
required: true,
defaultValueFromContext: (context) => context.defaultProjectName,
},
},
},
'Delete project': {
icon: 'close',
description: 'Delete a project',
status: isDesktop() ? 'active' : 'inactive',
needsReview: true,
reviewMessage: ({ argumentsToSubmit }) =>
CommandBarOverwriteWarning({
heading: 'Are you sure you want to delete?',
message: `This will permanently delete the project "${argumentsToSubmit.name}" and all its contents.`,
}),
args: {
name: {
inputType: 'options',
required: true,
options: (_, context) =>
context?.projects.map((p) => ({
name: p.name,
value: p.name,
})) || [],
},
},
},
'Rename project': {
icon: 'folder',
description: 'Rename a project',
needsReview: true,
status: isDesktop() ? 'active' : 'inactive',
args: {
oldName: {
inputType: 'options',
required: true,
options: (_, context) =>
context?.projects.map((p) => ({
name: p.name,
value: p.name,
})) || [],
},
newName: {
inputType: 'string',
required: true,
defaultValueFromContext: (context) => context.defaultProjectName,
},
},
},
'Import file from URL': {
icon: 'file',
description: 'Create a file',
needsReview: true,
status: 'active',
args: {
method: {
inputType: 'options',
required: true,
skip: true,
options: isDesktop()
? [
{ name: 'New project', value: 'newProject' },
{ name: 'Existing project', value: 'existingProject' },
]
: [{ name: 'Overwrite', value: 'existingProject' }],
valueSummary(value) {
return isDesktop()
? value === 'newProject'
? 'New project'
: 'Existing project'
: 'Overwrite'
},
},
// TODO: We can't get the currently-opened project to auto-populate here because
// it's not available on projectMachine, but lower in fileMachine. Unify these.
projectName: {
inputType: 'options',
required: (commandsContext) =>
isDesktop() &&
commandsContext.argumentsToSubmit.method === 'existingProject',
skip: true,
options: (_, context) =>
context?.projects.map((p) => ({
name: p.name,
value: p.name,
})) || [],
},
name: {
inputType: 'string',
required: isDesktop(),
skip: true,
},
code: {
inputType: 'text',
required: true,
skip: true,
valueSummary(value) {
const lineCount = value?.trim().split('\n').length
return `${lineCount} line${lineCount === 1 ? '' : 's'}`
},
},
},
reviewMessage(commandBarContext) {
return isDesktop()
? `Will add the contents from URL to a new ${
commandBarContext.argumentsToSubmit.method === 'newProject'
? 'project with file main.kcl'
: `file within the project "${commandBarContext.argumentsToSubmit.projectName}"`
} named "${
commandBarContext.argumentsToSubmit.name
}", and set default units to "${
commandBarContext.argumentsToSubmit.units
}".`
: `Will overwrite the contents of the current file with the contents from the URL.`
},
},
}
import {
folderSnapshot,
defaultProjectFolderNameSnapshot,

View File

@ -9,6 +9,23 @@ import { SceneEntities } from '@src/clientSideScene/sceneEntities'
import { SceneInfra } from '@src/clientSideScene/sceneInfra'
import type { BaseUnit } from '@src/lib/settings/settingsTypes'
import { useSelector } from '@xstate/react'
import { createActor, setup, spawnChild } from 'xstate'
import { isDesktop } from '@src/lib/isDesktop'
import { createSettings } from '@src/lib/settings/initialSettings'
import { authMachine } from '@src/machines/authMachine'
import type { EngineStreamActor } from '@src/machines/engineStreamMachine'
import {
engineStreamContextCreate,
engineStreamMachine,
} from '@src/machines/engineStreamMachine'
import { ACTOR_IDS } from '@src/machines/machineConstants'
import { settingsMachine } from '@src/machines/settingsMachine'
import { systemIOMachineDesktop } from '@src/machines/systemIO/systemIOMachineDesktop'
import { systemIOMachineWeb } from '@src/machines/systemIO/systemIOMachineWeb'
export const codeManager = new CodeManager()
export const engineCommandManager = new EngineCommandManager()
export const rustContext = new RustContext(engineCommandManager)
@ -90,22 +107,6 @@ if (typeof window !== 'undefined') {
},
})
}
import { useSelector } from '@xstate/react'
import { createActor, setup, spawnChild } from 'xstate'
import { isDesktop } from '@src/lib/isDesktop'
import { createSettings } from '@src/lib/settings/initialSettings'
import { authMachine } from '@src/machines/authMachine'
import type { EngineStreamActor } from '@src/machines/engineStreamMachine'
import {
engineStreamContextCreate,
engineStreamMachine,
} from '@src/machines/engineStreamMachine'
import { ACTOR_IDS } from '@src/machines/machineConstants'
import { settingsMachine } from '@src/machines/settingsMachine'
import { systemIOMachineDesktop } from '@src/machines/systemIO/systemIOMachineDesktop'
import { systemIOMachineWeb } from '@src/machines/systemIO/systemIOMachineWeb'
const { AUTH, SETTINGS, SYSTEM_IO, ENGINE_STREAM } = ACTOR_IDS
const appMachineActors = {
[AUTH]: authMachine,

View File

@ -1,337 +0,0 @@
import { assign, fromPromise, setup } from 'xstate'
import type { ProjectsCommandSchema } from '@src/lib/commandBarConfigs/projectsCommandConfig'
import type { Project } from '@src/lib/project'
import { isArray } from '@src/lib/utils'
export const projectsMachine = setup({
types: {
context: {} as {
projects: Project[]
defaultProjectName: string
defaultDirectory: string
hasListedProjects: boolean
},
events: {} as
| { type: 'Read projects'; data: ProjectsCommandSchema['Read projects'] }
| { type: 'Open project'; data: ProjectsCommandSchema['Open project'] }
| {
type: 'Rename project'
data: ProjectsCommandSchema['Rename project']
}
| {
type: 'Create project'
data: ProjectsCommandSchema['Create project']
}
| {
type: 'Delete project'
data: ProjectsCommandSchema['Delete project']
}
| {
type: 'Import file from URL'
data: ProjectsCommandSchema['Import file from URL']
}
| { type: 'navigate'; data: { name: string } }
| {
type: 'xstate.done.actor.read-projects'
output: Project[]
}
| {
type: 'xstate.done.actor.delete-project'
output: { message: string; name: string }
}
| {
type: 'xstate.done.actor.create-project'
output: { message: string; name: string }
}
| {
type: 'xstate.done.actor.rename-project'
output: { message: string; oldName: string; newName: string }
}
| {
type: 'xstate.done.actor.create-file'
output: { message: string; projectName: string; fileName: string }
}
| { type: 'assign'; data: { [key: string]: any } },
input: {} as {
projects: Project[]
defaultProjectName: string
defaultDirectory: string
hasListedProjects: boolean
},
},
actions: {
setProjects: assign({
projects: ({ context, event }) =>
'output' in event && isArray(event.output)
? event.output
: context.projects,
}),
setHasListedProjects: assign({
hasListedProjects: () => true,
}),
toastSuccess: () => {},
toastError: () => {},
navigateToProject: () => {},
navigateToProjectIfNeeded: () => {},
navigateToFile: () => {},
},
actors: {
readProjects: fromPromise(() => Promise.resolve([] as Project[])),
createProject: fromPromise(
(_: { input: { name: string; projects: Project[] } }) =>
Promise.resolve({ message: '' })
),
renameProject: fromPromise(
(_: {
input: {
oldName: string
newName: string
defaultProjectName: string
defaultDirectory: string
projects: Project[]
}
}) =>
Promise.resolve({
message: '',
oldName: '',
newName: '',
})
),
deleteProject: fromPromise(
(_: { input: { defaultDirectory: string; name: string } }) =>
Promise.resolve({
message: '',
name: '',
})
),
createFile: fromPromise(
(_: {
input: ProjectsCommandSchema['Import file from URL'] & {
projects: Project[]
}
}) => Promise.resolve({ message: '', projectName: '', fileName: '' })
),
},
guards: {
'Has at least 1 project': ({ event }) => {
if (event.type !== 'xstate.done.actor.read-projects') return false
return event.output.length ? event.output.length >= 1 : false
},
},
}).createMachine({
/** @xstate-layout N4IgpgJg5mDOIC5QAkD2BbMACdBDAxgBYCWAdmAMS6yzFSkDaADALqKgAOqtALsaqXYgAHogAsAJgA0IAJ6IAjAHYAbADoArBJVMFTCQA4mTAMwmxAXwsy0mHARLkKASXRcATjywAzYgBtsb3cMLABVACUAGWY2JBAuXn5BONEESRl5BAUFFQM1HQUxJSYATmyFAyUTKxsMbDwiMjA1ZGosUlQsDmCAKzB8HlgKcLBcCC7e-sGYoQTiPgEhVIUy9SKSiQ0NEwkclRyMxGKJNRKlMRVDU23zpRqQW3qHJpa2jonUPoGhgGF3UZ42G6nymMzicwWyVAyzKJzECg0OiYGjERhK+kOWUMSlOGiUlTEJlMxRKBnuj3sjXIr1gHy+g2Go3GwPpsDBnG48ySS0QGj0+Q0JTOGkqBg2JhKmNRJy2OyUEn0Kml5LqlMczVatJZUyGI1IuDs2oG7PinMhPIQfKYAqFShF+PFkrkRwkYjU8OUShKKhMKg0pUs1geqoa6ppdJ1FD+AKBk2NrFmZu5KV5-L9tvtYokEsxelRagUCoMiMumyUdpVdlDL01Ee+FAAImAAoC6zwTRDk9DU9b08LRY7MWd1AZcoS-aoLiYFJWnlSNW0jQyAPIcMCkNsdpOLFOWtOC-sO7NOzLbGWFbY6acmFESWdql7R3B8UhQNsUCACZpkABuqAA1s0+D-M+YAALRLluiQ7t2WRMGIbryjeBgGBIEglNsCi5sY6hMOchQqEoCLFCh97VtST4vm+S4UGA7jBO4agcH4z7eKg7joGowExhBcbtgm4LblCIiKPBiHZiKqHoZhuaFhoBbGMYBhiPBCgStUQYUuRzR6gaZDUXxH5fmov4Ac0-z6pgvEgvGsQctBwnLGJahIZJaEYdOmKEfJuQSoRRKSRcZHPNSunoPp750QxTEsTwbEcWoFkGuBkECfZXIwSJcEIS5Ekoe5MnOggXqIaUqFiiUhIInemkhiFzRNi2EU0Z+1KmYBagQM2YCAtZ9JQRljmiTlrn5dJnlFShOLwcpZjKBs+KBrUVb1WojU9c1hlRexMWsexnFdS2KV8QN5q7laNqHlmOaTcpmjoWc8L6HoYrBfOagjGMm02QyrXfqQf4dSBEB9Tqp1dllebichUkeVhRXTiU+QecUKhCps4pvWGn0QN9rJGW1ANmYlTKg98DAKHZpoORanoyqh8oImU3pKJifJ5Ohdr7CYqjIvKWMvDjeORttjHMXtCXA2T0xpdTg20+W9MSIzgorIRXlpr6ORbPs+jwQLFEgVRPj+JQf0mUTHXcaBYG+AE4OZcsxbuts5hbCK+zFmImJgSc5zPV6BQVD6RQG80lERXblCi7tcX7VxRvgVHDtDQgaE4vK2gXKiTA6ItubmJo8IoqOylERUVhBh0XXwHEWn1YmNO7mBKg+2KpzK2IpIBUwBhqUtwYre9tbvEutfpWdsFFnkPpVJnQqz15Ozur36FoypREbGH4Zj438u7tO1qIu5qK5PBGyYjebpEkS44e9oVTbxHr5tnvk9ZeXajTuW+zaHNuzYRUmoeCEp-RKlUHJbeYVhYDDfhDVIxR5IGGnISQk6E+6EkxMUBQX9c66EMMg3Q5Zt7rWNkuOBjsjilDUAqQsZQzzgOkJNUk7o7SmF-tiXOUCmQwMGBQ1OF4TgVGzFUG8yIxQGDZiYPI4oqh+mPsrDSy05xhmfm+KO-CLT+iRucEUuwFQqWzMWXM2YTAuTtL6ZBylkRcMrkAA */
id: 'Home machine',
initial: 'Reading projects',
context: ({ input }) => ({
...input,
}),
on: {
assign: {
actions: assign(({ event }) => ({
...event.data,
})),
},
'Import file from URL': '.Creating file',
},
states: {
'Has no projects': {
on: {
'Read projects': {
target: 'Reading projects',
},
'Create project': {
target: 'Creating project',
},
},
},
'Has projects': {
on: {
'Read projects': {
target: 'Reading projects',
},
'Rename project': {
target: 'Renaming project',
},
'Create project': {
target: 'Creating project',
},
'Delete project': {
target: 'Deleting project',
},
'Open project': {
target: 'Reading projects',
actions: 'navigateToProject',
reenter: true,
},
},
},
'Creating project': {
invoke: {
id: 'create-project',
src: 'createProject',
input: ({ event, context }) => {
if (
event.type !== 'Create project' &&
event.type !== 'Import file from URL'
) {
return {
name: '',
projects: context.projects,
}
}
return {
name: event.data.name,
projects: context.projects,
}
},
onDone: [
{
target: 'Reading projects',
actions: ['toastSuccess', 'navigateToProject'],
},
],
onError: [
{
target: 'Reading projects',
actions: ['toastError'],
},
],
},
},
'Renaming project': {
invoke: {
id: 'rename-project',
src: 'renameProject',
input: ({ event, context }) => {
if (event.type !== 'Rename project') {
// This is to make TS happy
return {
defaultProjectName: context.defaultProjectName,
defaultDirectory: context.defaultDirectory,
oldName: '',
newName: '',
projects: context.projects,
}
}
return {
defaultProjectName: context.defaultProjectName,
defaultDirectory: context.defaultDirectory,
oldName: event.data.oldName,
newName: event.data.newName,
projects: context.projects,
}
},
onDone: [
{
target: '#Home machine.Reading projects',
actions: ['toastSuccess', 'navigateToProjectIfNeeded'],
},
],
onError: [
{
target: '#Home machine.Reading projects',
actions: ['toastError'],
},
],
},
},
'Deleting project': {
invoke: {
id: 'delete-project',
src: 'deleteProject',
input: ({ event, context }) => {
if (event.type !== 'Delete project') {
// This is to make TS happy
return {
defaultDirectory: context.defaultDirectory,
name: '',
}
}
return {
defaultDirectory: context.defaultDirectory,
name: event.data.name,
}
},
onDone: [
{
actions: ['toastSuccess', 'navigateToProjectIfNeeded'],
target: '#Home machine.Reading projects',
},
],
onError: {
actions: ['toastError'],
target: '#Home machine.Has projects',
},
},
},
'Reading projects': {
invoke: {
id: 'read-projects',
src: 'readProjects',
onDone: [
{
guard: 'Has at least 1 project',
target: 'Has projects',
actions: ['setProjects', 'setHasListedProjects'],
},
{
target: 'Has no projects',
actions: ['setProjects', 'setHasListedProjects'],
},
],
onError: [
{
target: 'Has no projects',
actions: ['toastError'],
},
],
},
},
'Creating file': {
invoke: {
id: 'create-file',
src: 'createFile',
input: ({ event, context }) => {
if (event.type !== 'Import file from URL') {
return {
code: '',
name: '',
method: 'existingProject',
projects: context.projects,
}
}
return {
code: event.data.code || '',
name: event.data.name,
method: event.data.method,
projectName: event.data.projectName,
projects: context.projects,
}
},
onDone: {
target: 'Reading projects',
actions: ['navigateToFile', 'toastSuccess'],
},
onError: {
target: 'Reading projects',
actions: 'toastError',
},
},
},
},
})

View File

@ -238,7 +238,7 @@ app.on('ready', (event, data) => {
// Create the mainWindow
mainWindow = createWindow()
// Set menu application to null to avoid default electron menu
// Menu.setApplicationMenu(null)
Menu.setApplicationMenu(null)
})
// For now there is no good reason to separate these out to another file(s)