* Remove unused `telemetryLoader` * Remove onboarding redirect behavior * Allow subRoute to be passed to navigateToProject * Replace warning dialog routes with toasts * Wire up new utilities and toasts to UI components * Add home sidebar buttons for tutorial flow * Rename menu item * Add flex-1 so home-layout fills available space * Remove onboarding avatar tests, they are becoming irrelevant * Consolidate onboarding tests to one longer one and update it to not use pixel color checks, and use fixtures. * Shorten warning toast button text * tsc, lint, and circular deps * Update circular dep file * Fix mistakes made in circular update tweaking * One more dumb created circular dep * Update src/routes/Onboarding/utils.tsx Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com> * Fix narrow screen home layout breaking * fix: kevin, navigation routes fixed * fix: filename parsing is correct now for onboarding with the last file sep * Fix e2e test state checks that are diff on Linux * Create onboarding project entirely through systemIOMachine * Fix Windows path construction * Make utility to verify a string is an onboarding value * Little biome formatting suggestion fix * Units onboarding step was not using OnboardingButtons * Add type checking of next and previous status, fix useNextClick * Thanks Graphite Diamond, I should use that new util * Remove TODO comment * Fix botched merge because IS_PLAYWRIGHT moved or something --------- Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com> Co-authored-by: Kevin Nadro <kevin@zoo.dev>
258 lines
7.8 KiB
TypeScript
258 lines
7.8 KiB
TypeScript
import {
|
|
createNewProjectDirectory,
|
|
getProjectInfo,
|
|
mkdirOrNOOP,
|
|
readAppSettingsFile,
|
|
renameProjectDirectory,
|
|
} from '@src/lib/desktop'
|
|
import {
|
|
doesProjectNameNeedInterpolated,
|
|
getNextFileName,
|
|
getNextProjectIndex,
|
|
getUniqueProjectName,
|
|
interpolateProjectNameWithIndex,
|
|
} from '@src/lib/desktopFS'
|
|
import type { Project } from '@src/lib/project'
|
|
import { systemIOMachine } from '@src/machines/systemIO/systemIOMachine'
|
|
import type { SystemIOContext } from '@src/machines/systemIO/utils'
|
|
import {
|
|
NO_PROJECT_DIRECTORY,
|
|
SystemIOMachineActors,
|
|
} from '@src/machines/systemIO/utils'
|
|
import { fromPromise } from 'xstate'
|
|
import type { AppMachineContext } from '@src/lib/types'
|
|
|
|
export const systemIOMachineDesktop = systemIOMachine.provide({
|
|
actors: {
|
|
[SystemIOMachineActors.readFoldersFromProjectDirectory]: fromPromise(
|
|
async ({ input: context }: { input: SystemIOContext }) => {
|
|
const projects = []
|
|
const projectDirectoryPath = context.projectDirectoryPath
|
|
if (projectDirectoryPath === NO_PROJECT_DIRECTORY) {
|
|
return []
|
|
}
|
|
await mkdirOrNOOP(projectDirectoryPath)
|
|
// Gotcha: readdir will list all folders at this project directory even if you do not have readwrite access on the directory path
|
|
const entries = await window.electron.readdir(projectDirectoryPath)
|
|
const { value: canReadWriteProjectDirectory } =
|
|
await window.electron.canReadWriteDirectory(projectDirectoryPath)
|
|
|
|
for (let entry of entries) {
|
|
// Skip directories that start with a dot
|
|
if (entry.startsWith('.')) {
|
|
continue
|
|
}
|
|
const projectPath = window.electron.path.join(
|
|
projectDirectoryPath,
|
|
entry
|
|
)
|
|
|
|
// if it's not a directory ignore.
|
|
// Gotcha: statIsDirectory will work even if you do not have read write permissions on the project path
|
|
const isDirectory = await window.electron.statIsDirectory(projectPath)
|
|
if (!isDirectory) {
|
|
continue
|
|
}
|
|
const project: Project = await getProjectInfo(projectPath)
|
|
if (
|
|
project.kcl_file_count === 0 &&
|
|
project.readWriteAccess &&
|
|
canReadWriteProjectDirectory
|
|
) {
|
|
continue
|
|
}
|
|
projects.push(project)
|
|
}
|
|
return projects
|
|
}
|
|
),
|
|
[SystemIOMachineActors.createProject]: fromPromise(
|
|
async ({
|
|
input,
|
|
}: {
|
|
input: { context: SystemIOContext; requestedProjectName: string }
|
|
}) => {
|
|
const folders = input.context.folders
|
|
const requestedProjectName = input.requestedProjectName
|
|
const uniqueName = getUniqueProjectName(requestedProjectName, folders)
|
|
await createNewProjectDirectory(uniqueName)
|
|
return {
|
|
message: `Successfully created "${uniqueName}"`,
|
|
name: uniqueName,
|
|
}
|
|
}
|
|
),
|
|
[SystemIOMachineActors.renameProject]: fromPromise(
|
|
async ({
|
|
input,
|
|
}: {
|
|
input: {
|
|
context: SystemIOContext
|
|
requestedProjectName: string
|
|
projectName: string
|
|
}
|
|
}) => {
|
|
const folders = input.context.folders
|
|
const requestedProjectName = input.requestedProjectName
|
|
const projectName = input.projectName
|
|
let newProjectName: string = requestedProjectName
|
|
if (doesProjectNameNeedInterpolated(requestedProjectName)) {
|
|
const nextIndex = getNextProjectIndex(requestedProjectName, folders)
|
|
newProjectName = interpolateProjectNameWithIndex(
|
|
requestedProjectName,
|
|
nextIndex
|
|
)
|
|
}
|
|
|
|
// Toast an error if the project name is taken
|
|
if (folders.find((p) => p.name === newProjectName)) {
|
|
return Promise.reject(
|
|
new Error(`Project with name "${newProjectName}" already exists`)
|
|
)
|
|
}
|
|
|
|
await renameProjectDirectory(
|
|
window.electron.path.join(
|
|
input.context.projectDirectoryPath,
|
|
projectName
|
|
),
|
|
newProjectName
|
|
)
|
|
|
|
return {
|
|
message: `Successfully renamed "${projectName}" to "${newProjectName}"`,
|
|
oldName: projectName,
|
|
newName: newProjectName,
|
|
}
|
|
}
|
|
),
|
|
[SystemIOMachineActors.deleteProject]: fromPromise(
|
|
async ({
|
|
input,
|
|
}: {
|
|
input: { context: SystemIOContext; requestedProjectName: string }
|
|
}) => {
|
|
await window.electron.rm(
|
|
window.electron.path.join(
|
|
input.context.projectDirectoryPath,
|
|
input.requestedProjectName
|
|
),
|
|
{
|
|
recursive: true,
|
|
}
|
|
)
|
|
|
|
return {
|
|
message: `Successfully deleted "${input.requestedProjectName}"`,
|
|
name: input.requestedProjectName,
|
|
}
|
|
}
|
|
),
|
|
[SystemIOMachineActors.createKCLFile]: fromPromise(
|
|
async ({
|
|
input,
|
|
}: {
|
|
input: {
|
|
context: SystemIOContext
|
|
requestedProjectName: string
|
|
requestedFileName: string
|
|
requestedCode: string
|
|
rootContext: AppMachineContext
|
|
requestedSubRoute?: string
|
|
}
|
|
}) => {
|
|
const requestedProjectName = input.requestedProjectName
|
|
const requestedFileName = input.requestedFileName
|
|
const requestedCode = input.requestedCode
|
|
const folders = input.context.folders
|
|
|
|
let newProjectName = requestedProjectName
|
|
|
|
if (!newProjectName) {
|
|
newProjectName = getUniqueProjectName(
|
|
input.context.defaultProjectFolderName,
|
|
input.context.folders
|
|
)
|
|
}
|
|
|
|
const needsInterpolated =
|
|
doesProjectNameNeedInterpolated(newProjectName)
|
|
if (needsInterpolated) {
|
|
const nextIndex = getNextProjectIndex(newProjectName, folders)
|
|
newProjectName = interpolateProjectNameWithIndex(
|
|
newProjectName,
|
|
nextIndex
|
|
)
|
|
}
|
|
|
|
const baseDir = window.electron.join(
|
|
input.context.projectDirectoryPath,
|
|
newProjectName
|
|
)
|
|
const { name: newFileName } = getNextFileName({
|
|
entryName: requestedFileName,
|
|
baseDir,
|
|
})
|
|
const configuration = await readAppSettingsFile()
|
|
|
|
// Create the project around the file if newProject
|
|
await createNewProjectDirectory(
|
|
newProjectName,
|
|
requestedCode,
|
|
configuration,
|
|
newFileName
|
|
)
|
|
|
|
return {
|
|
message: 'File created successfully',
|
|
fileName: newFileName,
|
|
projectName: newProjectName,
|
|
subRoute: input.requestedSubRoute || '',
|
|
}
|
|
}
|
|
),
|
|
[SystemIOMachineActors.checkReadWrite]: fromPromise(
|
|
async ({
|
|
input,
|
|
}: {
|
|
input: {
|
|
context: SystemIOContext
|
|
requestedProjectDirectoryPath: string
|
|
}
|
|
}) => {
|
|
const requestProjectDirectoryPath = input.requestedProjectDirectoryPath
|
|
if (!requestProjectDirectoryPath) {
|
|
return { value: true, error: undefined }
|
|
}
|
|
const result = await window.electron.canReadWriteDirectory(
|
|
requestProjectDirectoryPath
|
|
)
|
|
return result
|
|
}
|
|
),
|
|
[SystemIOMachineActors.deleteKCLFile]: fromPromise(
|
|
async ({
|
|
input,
|
|
}: {
|
|
input: {
|
|
context: SystemIOContext
|
|
requestedProjectName: string
|
|
requestedFileName: string
|
|
}
|
|
}) => {
|
|
const path = window.electron.path.join(
|
|
input.context.projectDirectoryPath,
|
|
input.requestedProjectName,
|
|
input.requestedFileName
|
|
)
|
|
await window.electron.rm(path)
|
|
return {
|
|
message: 'File deleted successfully',
|
|
projectName: input.requestedProjectName,
|
|
fileName: input.requestedFileName,
|
|
}
|
|
}
|
|
),
|
|
},
|
|
})
|