import { useMachine } from '@xstate/react' import { useNavigate, useRouteLoaderData } from 'react-router-dom' import { IndexLoaderData, paths } from '../Router' import React, { createContext } from 'react' import { toast } from 'react-hot-toast' import { AnyStateMachine, ContextFrom, EventFrom, InterpreterFrom, Prop, StateFrom, } from 'xstate' import { useCommandsContext } from 'hooks/useCommandsContext' import { DEFAULT_FILE_NAME, fileMachine } from 'machines/fileMachine' import { createDir, removeDir, removeFile, renameFile, writeFile, } from '@tauri-apps/api/fs' import { FILE_EXT, readProject } from 'lib/tauriFS' import { isTauri } from 'lib/isTauri' import { sep } from '@tauri-apps/api/path' type MachineContext = { state: StateFrom context: ContextFrom send: Prop, 'send'> } export const FileContext = createContext( {} as MachineContext ) export const FileMachineProvider = ({ children, }: { children: React.ReactNode }) => { const navigate = useNavigate() const { setCommandBarOpen } = useCommandsContext() const { project } = useRouteLoaderData(paths.FILE) as IndexLoaderData const [state, send] = useMachine(fileMachine, { context: { project, selectedDirectory: project, }, actions: { navigateToFile: ( context: ContextFrom, event: EventFrom ) => { if (event.data && 'name' in event.data) { setCommandBarOpen(false) navigate( `${paths.FILE}/${encodeURIComponent( context.selectedDirectory + sep + event.data.name )}` ) } }, toastSuccess: (_, event) => event.data && toast.success((event.data || '') + ''), toastError: (_, event) => toast.error((event.data || '') + ''), }, services: { readFiles: async (context: ContextFrom) => { const newFiles = isTauri() ? await readProject(context.project.path) : [] return { ...context.project, children: newFiles, } }, createFile: async ( context: ContextFrom, event: EventFrom ) => { let name = event.data.name.trim() || DEFAULT_FILE_NAME if (event.data.makeDir) { await createDir(context.selectedDirectory.path + sep + name) } else { await writeFile( context.selectedDirectory.path + sep + name + (name.endsWith(FILE_EXT) ? '' : FILE_EXT), '' ) } return `Successfully created "${name}"` }, renameFile: async ( context: ContextFrom, event: EventFrom ) => { const { oldName, newName, isDir } = event.data let name = newName ? newName : DEFAULT_FILE_NAME await renameFile( context.selectedDirectory.path + sep + oldName, context.selectedDirectory.path + sep + name + (name.endsWith(FILE_EXT) || isDir ? '' : FILE_EXT) ) return ( oldName !== name && `Successfully renamed "${oldName}" to "${name}"` ) }, deleteFile: async ( context: ContextFrom, event: EventFrom ) => { const isDir = !!event.data.children if (isDir) { await removeDir(event.data.path, { recursive: true, }).catch((e) => console.error('Error deleting directory', e)) } else { await removeFile(event.data.path).catch((e) => console.error('Error deleting file', e) ) } return `Successfully deleted ${isDir ? 'folder' : 'file'} "${ event.data.name }"` }, }, guards: { 'Has at least 1 file': (_, event: EventFrom) => { if (event.type !== 'done.invoke.read-files') return false return !!event?.data?.children && event.data.children.length > 0 }, }, }) return ( {children} ) } export default FileMachineProvider