fix: big add file and folder logic

This commit is contained in:
Kevin
2025-06-18 13:58:53 -05:00
parent 950aa13cdf
commit dddc7e3873
7 changed files with 265 additions and 51 deletions

View File

@ -170,7 +170,7 @@ function RenameForm({
autoCapitalize="off"
autoCorrect="off"
placeholder={row.name}
className="w-full py-1 bg-transparent text-chalkboard-100 placeholder:text-chalkboard-70 dark:text-chalkboard-10 dark:placeholder:text-chalkboard-50 focus:outline-none focus:ring-0"
className="overflow-hidden whitespace-nowrap text-ellipsis py-1 bg-transparent text-chalkboard-100 placeholder:text-chalkboard-70 dark:text-chalkboard-10 dark:placeholder:text-chalkboard-50 focus:outline-none focus:ring-0"
onKeyDown={handleKeyDown}
onBlur={onSubmit}
/>
@ -247,7 +247,7 @@ export const FileExplorerRowElement = ({
name={row.icon}
className="inline-block w-4 text-current mr-1"
/>
{!isMyRowRenaming ? (
{!isMyRowRenaming && !row.isFake ? (
<span className="overflow-hidden whitespace-nowrap text-ellipsis">
{row.name}
</span>

View File

@ -9,10 +9,12 @@ import {
CONTAINER_IS_SELECTED,
STARTING_INDEX_TO_SELECT,
FILE_PLACEHOLDER_NAME,
FOLDER_PLACEHOLDER_NAME
FOLDER_PLACEHOLDER_NAME,
} from '@src/components/Explorer/utils'
import type {
FileExplorerEntry,
FileExplorerRow,
} from '@src/components/Explorer/utils'
import type { FileExplorerEntry,
FileExplorerRow } from '@src/components/Explorer/utils'
import { FileExplorerHeaderActions } from '@src/components/Explorer/FileExplorerHeaderActions'
import { useState, useRef, useEffect } from 'react'
import { systemIOActor } from '@src/lib/singletons'
@ -20,7 +22,9 @@ import { SystemIOMachineEvents } from '@src/machines/systemIO/utils'
import { sortFilesAndDirectories } from '@src/lib/desktopFS'
import {
alwaysEndFileWithEXT,
desktopSafePathSplit,
getEXTWithPeriod,
getParentAbsolutePath,
joinOSPaths,
} from '@src/lib/paths'
import { useProjectDirectoryPath } from '@src/machines/systemIO/hooks'
@ -191,6 +195,7 @@ export const ProjectExplorer = ({
// TODO: Implement renameFolder and renameFile to navigate
setIsRenaming(false)
isRenamingRef.current = false
setFakeRow(null)
if (!event) {
return
@ -205,28 +210,37 @@ export const ProjectExplorer = ({
// Rename a folder
if (row.isFolder) {
if (requestedName !== name) {
systemIOActor.send({
type: SystemIOMachineEvents.renameFolder,
data: {
requestedFolderName: requestedName,
folderName: name,
absolutePathToParentDirectory: joinOSPaths(
projectDirectoryPath,
child.parentPath
),
},
})
// TODO: Gotcha... Set new string open even if it fails?
if (openedRowsRef.current[child.key]) {
// If the file tree had the folder opened make the new one open.
const newOpenedRows = { ...openedRowsRef.current }
const key = constructPath({
parentPath: child.parentPath,
name: requestedName,
if (row.isFake) {
// create
systemIOActor.send({
type: SystemIOMachineEvents.createBlankFolder,
data: {
requestedAbsolutePath: joinOSPaths(getParentAbsolutePath(row.path), requestedName)
},
})
newOpenedRows[key] = true
setOpenedRows(newOpenedRows)
} else {
// rename
systemIOActor.send({
type: SystemIOMachineEvents.renameFolder,
data: {
requestedFolderName: requestedName,
folderName: name,
absolutePathToParentDirectory: getParentAbsolutePath(row.path)
},
})
// TODO: Gotcha... Set new string open even if it fails?
if (openedRowsRef.current[child.key]) {
// If the file tree had the folder opened make the new one open.
const newOpenedRows = { ...openedRowsRef.current }
const key = constructPath({
parentPath: child.parentPath,
name: requestedName,
})
newOpenedRows[key] = true
setOpenedRows(newOpenedRows)
}
}
}
} else {
// rename a file
@ -239,21 +253,29 @@ export const ProjectExplorer = ({
// TODO: OH NO!
return
}
systemIOActor.send({
// create a file if it is fake
if (row.isFake) {
systemIOActor.send({
type: SystemIOMachineEvents.createBlankFile,
data: {
requestedAbsolutePath: joinOSPaths(getParentAbsolutePath(row.path),fileNameForcedWithOriginalExt)
},
})
} else {
// rename the file otherwise
systemIOActor.send({
type: SystemIOMachineEvents.renameFile,
data: {
requestedFileNameWithExtension: fileNameForcedWithOriginalExt,
fileNameWithExtension: name,
absolutePathToParentDirectory: joinOSPaths(
projectDirectoryPath,
child.parentPath
),
absolutePathToParentDirectory: getParentAbsolutePath(row.path)
},
})
}
}
},
}
return row
}) || []
@ -261,16 +283,39 @@ export const ProjectExplorer = ({
let showPlaceHolder = false
if (fakeRow?.isFile) {
// fake row is a file
const showFileAtSameLevel = fakeRow?.entry?.parentPath === row.parentPath && !row.isFolder === (fakeRow?.entry?.children === null) && row.name === FILE_PLACEHOLDER_NAME
const showFileWithinFolder = !row.isFolder && !!fakeRow?.entry?.children && fakeRow?.entry?.key === row.parentPath
showPlaceHolder = showFileAtSameLevel || showFileWithinFolder
} else {
const showFileAtSameLevel =
fakeRow?.entry?.parentPath === row.parentPath &&
!row.isFolder === (fakeRow?.entry?.children === null) &&
row.name === FILE_PLACEHOLDER_NAME
const showFileWithinFolder =
!row.isFolder &&
!!fakeRow?.entry?.children &&
fakeRow?.entry?.key === row.parentPath &&
row.name === FILE_PLACEHOLDER_NAME
const fakeRowIsNullShowRootFile = fakeRow.entry === null && row.parentPath === project.name &&
row.name === FILE_PLACEHOLDER_NAME
showPlaceHolder = showFileAtSameLevel || showFileWithinFolder || fakeRowIsNullShowRootFile
} else if (fakeRow?.isFile === false){
// fake row is a folder
const showFolderAtSameLevel = fakeRow?.entry?.parentPath === row.parentPath && !row.isFolder === (!!fakeRow?.entry?.children) && row.name === FOLDER_PLACEHOLDER_NAME
const showFolderWithinFolder = row.isFolder && !!fakeRow?.entry?.children && fakeRow?.entry?.key === row.parentPath
showPlaceHolder = showFolderAtSameLevel || showFolderWithinFolder
const showFolderAtSameLevel =
fakeRow?.entry?.parentPath === row.parentPath &&
!row.isFolder === !!fakeRow?.entry?.children &&
row.name === FOLDER_PLACEHOLDER_NAME
const showFolderWithinFolder =
row.isFolder &&
!!fakeRow?.entry?.children &&
fakeRow?.entry?.key === row.parentPath &&
row.name === FOLDER_PLACEHOLDER_NAME
const fakeRowIsNullShowRootFolder = fakeRow.entry === null && row.parentPath === project.name &&
row.name === FOLDER_PLACEHOLDER_NAME
showPlaceHolder = showFolderAtSameLevel || showFolderWithinFolder || fakeRowIsNullShowRootFolder
}
const skipPlaceHolder = !(row.name === FILE_PLACEHOLDER_NAME || row.name === FOLDER_PLACEHOLDER_NAME) || showPlaceHolder
const skipPlaceHolder =
!(
row.name === FILE_PLACEHOLDER_NAME ||
row.name === FOLDER_PLACEHOLDER_NAME
) || showPlaceHolder
row.isFake = showPlaceHolder
return row.render && skipPlaceHolder
})

View File

@ -130,26 +130,32 @@ export const flattenProject = (
return flattenTreeInOrder
}
export const addPlaceHoldersForNewFileAndFolder = (children: FileEntry[] | null, parentPath: string) => {
export const addPlaceHoldersForNewFileAndFolder = (
children: FileEntry[] | null,
parentPath: string
) => {
if (children === null) {
return
}
for( let i = 0; i < children.length; i++) {
addPlaceHoldersForNewFileAndFolder(children[i].children, joinOSPaths(parentPath, children[i].name))
for (let i = 0; i < children.length; i++) {
addPlaceHoldersForNewFileAndFolder(
children[i].children,
joinOSPaths(parentPath, children[i].name)
)
}
const placeHolderFolderEntry : FileEntry = {
const placeHolderFolderEntry: FileEntry = {
path: joinOSPaths(parentPath, FOLDER_PLACEHOLDER_NAME),
name: FOLDER_PLACEHOLDER_NAME,
children: []
children: [],
}
children.unshift(placeHolderFolderEntry)
const placeHolderFileEntry : FileEntry = {
const placeHolderFileEntry: FileEntry = {
path: joinOSPaths(parentPath, FILE_PLACEHOLDER_NAME),
name: FILE_PLACEHOLDER_NAME,
children: null
children: null,
}
children.push(placeHolderFileEntry)
}
@ -159,4 +165,4 @@ export const NOTHING_IS_SELECTED: number = -2
export const CONTAINER_IS_SELECTED: number = -1
export const STARTING_INDEX_TO_SELECT: number = 0
export const FOLDER_PLACEHOLDER_NAME = '.zoo-placeholder-folder'
export const FILE_PLACEHOLDER_NAME = '.zoo-placeholder-file'
export const FILE_PLACEHOLDER_NAME = '.zoo-placeholder-file.kcl'

View File

@ -245,3 +245,10 @@ export const getEXTWithPeriod = (filePath: string) => {
}
return extension
}
export const getParentAbsolutePath = (absolutePath: string) => {
const split = desktopSafePathSplit(absolutePath)
split.pop()
const joined = desktopSafePathJoin(split)
return joined
}

View File

@ -162,7 +162,19 @@ export const systemIOMachine = setup({
data: {
requestedPath: string
}
},
}
| {
type: SystemIOMachineEvents.createBlankFile
data: {
requestedAbsolutePath: string
}
}
| {
type: SystemIOMachineEvents.createBlankFolder
data: {
requestedAbsolutePath: string
}
}
},
actions: {
[SystemIOMachineActions.setFolders]: assign({
@ -442,6 +454,38 @@ export const systemIOMachine = setup({
}
}
),
[SystemIOMachineActors.createBlankFile]: fromPromise(
async ({
input,
}: {
input: {
context: SystemIOContext
rootContext: AppMachineContext
requestedAbsolutePath: string
}
}) => {
return {
message: '',
requestedAbsolutePath: '',
}
}
),
[SystemIOMachineActors.createBlankFolder]: fromPromise(
async ({
input,
}: {
input: {
context: SystemIOContext
rootContext: AppMachineContext
requestedAbsolutePath: string
}
}) => {
return {
message: '',
requestedAbsolutePath: '',
}
}
),
},
}).createMachine({
initial: SystemIOMachineStates.idle,
@ -529,6 +573,12 @@ export const systemIOMachine = setup({
[SystemIOMachineEvents.deleteFileOrFolder]: {
target: SystemIOMachineStates.deletingFileOrFolder,
},
[SystemIOMachineEvents.createBlankFile]: {
target: SystemIOMachineStates.creatingBlankFile,
},
[SystemIOMachineEvents.createBlankFolder]: {
target: SystemIOMachineStates.creatingBlankFolder,
},
},
},
[SystemIOMachineStates.readingFolders]: {
@ -919,5 +969,49 @@ export const systemIOMachine = setup({
},
},
},
[SystemIOMachineStates.creatingBlankFile]: {
invoke: {
id: SystemIOMachineActors.createBlankFile,
src: SystemIOMachineActors.createBlankFile,
input: ({ context, event, self }) => {
assertEvent(event, SystemIOMachineEvents.createBlankFile)
return {
context,
requestedAbsolutePath: event.data.requestedAbsolutePath,
rootContext: self.system.get('root').getSnapshot().context,
}
},
onDone: {
target: SystemIOMachineStates.readingFolders,
actions: [SystemIOMachineActions.toastSuccess],
},
onError: {
target: SystemIOMachineStates.idle,
actions: [SystemIOMachineActions.toastError],
},
},
},
[SystemIOMachineStates.creatingBlankFolder]: {
invoke: {
id: SystemIOMachineActors.createBlankFolder,
src: SystemIOMachineActors.createBlankFolder,
input: ({ context, event, self }) => {
assertEvent(event, SystemIOMachineEvents.createBlankFolder)
return {
context,
requestedAbsolutePath: event.data.requestedAbsolutePath,
rootContext: self.system.get('root').getSnapshot().context,
}
},
onDone: {
target: SystemIOMachineStates.readingFolders,
actions: [SystemIOMachineActions.toastSuccess],
},
onError: {
target: SystemIOMachineStates.idle,
actions: [SystemIOMachineActions.toastError],
},
},
},
},
})

View File

@ -270,12 +270,18 @@ export const systemIOMachineDesktop = systemIOMachine.provide({
const configuration = await readAppSettingsFile()
// Create the project around the file if newProject
await createNewProjectDirectory(
try {
const result = await createNewProjectDirectory(
newProjectName,
requestedCode,
configuration,
newFileName
)
)
console.log(result)
} catch (e) {
console.error(e)
}
return {
message: 'File created successfully',
@ -526,5 +532,55 @@ export const systemIOMachineDesktop = systemIOMachine.provide({
}
}
),
[SystemIOMachineActors.createBlankFile]: fromPromise(
async ({
input,
}: {
input: {
context: SystemIOContext
rootContext: AppMachineContext
requestedAbsolutePath: string
}
}) => {
try {
const result = await window.electron.stat(input.requestedAbsolutePath)
if (result) {
return Promise.reject(new Error(`File ${input.requestedAbsolutePath} already exists`))
}
} catch (e) {
console.error(e)
}
await window.electron.writeFile(input.requestedAbsolutePath, '')
return {
message: `File ${input.requestedAbsolutePath} written successfully`,
requestedAbsolutePath: input.requestedAbsolutePath,
}
}
),
[SystemIOMachineActors.createBlankFolder]: fromPromise(
async ({
input,
}: {
input: {
context: SystemIOContext
rootContext: AppMachineContext
requestedAbsolutePath: string
}
}) => {
try {
const result = await window.electron.stat(input.requestedAbsolutePath)
if (result) {
return Promise.reject(new Error(`Folder ${input.requestedAbsolutePath} already exists`))
}
} catch (e) {
console.error(e)
}
await window.electron.mkdir(input.requestedAbsolutePath, {recursive: true})
return {
message: `File ${input.requestedAbsolutePath} written successfully`,
requestedAbsolutePath: input.requestedAbsolutePath,
}
}
),
},
})

View File

@ -19,6 +19,8 @@ export enum SystemIOMachineActors {
renameFolder = 'renameFolder',
renameFile = 'renameFile',
deleteFileOrFolder = 'deleteFileOrFolder',
createBlankFile = 'create blank file',
createBlankFolder = 'create blank folder'
}
export enum SystemIOMachineStates {
@ -39,6 +41,8 @@ export enum SystemIOMachineStates {
renamingFolder = 'renamingFolder',
renamingFile = 'renamingFile',
deletingFileOrFolder = 'deletingFileOrFolder',
creatingBlankFile = 'creatingBlankFile',
creatingBlankFolder = 'creatingBlankFolder'
}
const donePrefix = 'xstate.done.actor.'
@ -70,6 +74,8 @@ export enum SystemIOMachineEvents {
renameFolder = 'rename folder',
renameFile = 'rename file',
deleteFileOrFolder = 'delete file or folder',
createBlankFile = 'create blank file',
createBlankFolder = 'create blank folder'
}
export enum SystemIOMachineActions {