import { FormEvent, useEffect, useRef, useState } from 'react' import { ActionButton } from 'components/ActionButton' import { AppHeader } from 'components/AppHeader' import ProjectCard from 'components/ProjectCard/ProjectCard' import { useNavigate, useSearchParams } from 'react-router-dom' import { Link } from 'react-router-dom' import { toast } from 'react-hot-toast' import Loading from 'components/Loading' import { PATHS } from 'lib/paths' import { getNextSearchParams, getSortFunction, getSortIcon, } from '../lib/sorting' import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext' import { useHotkeys } from 'react-hotkeys-hook' import { isDesktop } from 'lib/isDesktop' import { kclManager } from 'lib/singletons' import { useRefreshSettings } from 'hooks/useRefreshSettings' import { LowerRightControls } from 'components/LowerRightControls' import { ProjectSearchBar, useProjectSearch } from 'components/ProjectSearchBar' import { Project } from 'lib/project' import { markOnce } from 'lib/performance' import { useFileSystemWatcher } from 'hooks/useFileSystemWatcher' import { useProjectsLoader } from 'hooks/useProjectsLoader' import { useProjectsContext } from 'hooks/useProjectsContext' import { commandBarActor } from 'machines/commandBarMachine' import { useCreateFileLinkQuery } from 'hooks/useCreateFileLinkQueryWatcher' // This route only opens in the desktop context for now, // as defined in Router.tsx, so we can use the desktop APIs and types. const Home = () => { const { state, send } = useProjectsContext() const [projectsLoaderTrigger, setProjectsLoaderTrigger] = useState(0) const { projectsDir } = useProjectsLoader([projectsLoaderTrigger]) // Keep a lookout for a URL query string that invokes the 'import file from URL' command useCreateFileLinkQuery((argDefaultValues) => { commandBarActor.send({ type: 'Find and select command', data: { groupId: 'projects', name: 'Import file from URL', argDefaultValues, }, }) }) useRefreshSettings(PATHS.HOME + 'SETTINGS') const navigate = useNavigate() const { settings: { context: settings }, } = useSettingsAuthContext() // Cancel all KCL executions while on the home page useEffect(() => { markOnce('code/didLoadHome') kclManager.cancelAllExecutions() }, []) useHotkeys('backspace', (e) => { e.preventDefault() }) useHotkeys( isDesktop() ? 'mod+,' : 'shift+mod+,', () => navigate(PATHS.HOME + PATHS.SETTINGS), { splitKey: '|', } ) const ref = useRef(null) // Re-read projects listing if the projectDir has any updates. useFileSystemWatcher( async () => { setProjectsLoaderTrigger(projectsLoaderTrigger + 1) }, projectsDir ? [projectsDir] : [] ) const projects = state?.context.projects ?? [] const [searchParams, setSearchParams] = useSearchParams() const { searchResults, query, setQuery } = useProjectSearch(projects) const sort = searchParams.get('sort_by') ?? 'modified:desc' const isSortByModified = sort?.includes('modified') || !sort || sort === null // Update the default project name and directory in the home machine // when the settings change useEffect(() => { send({ type: 'assign', data: { defaultProjectName: settings.projects.defaultProjectName.current, defaultDirectory: settings.app.projectDirectory.current, }, }) }, [ settings.app.projectDirectory.current, settings.projects.defaultProjectName.current, send, ]) async function handleRenameProject( e: FormEvent, project: Project ) { const { newProjectName } = Object.fromEntries( new FormData(e.target as HTMLFormElement) ) if (typeof newProjectName === 'string' && newProjectName.startsWith('.')) { toast.error('Project names cannot start with a dot (.)') return } if (newProjectName !== project.name) { send({ type: 'Rename project', data: { oldName: project.name, newName: newProjectName as string }, }) } } async function handleDeleteProject(project: Project) { send({ type: 'Delete project', data: { name: project.name || '' }, }) } return (

Your Projects

commandBarActor.send({ type: 'Find and select command', data: { groupId: 'projects', name: 'Create project', argDefaultValues: { name: settings.projects.defaultProjectName.current, }, }, }) } className="group !bg-primary !text-chalkboard-10 !border-primary hover:shadow-inner hover:hue-rotate-15" iconStart={{ icon: 'plus', bgClassName: '!bg-transparent rounded-sm', iconClassName: '!text-chalkboard-10 transition-transform group-active:rotate-90', }} data-testid="home-new-file" > Create project
Sort by setSearchParams(getNextSearchParams(sort, 'name')) } iconStart={{ icon: getSortIcon(sort, 'name'), bgClassName: 'bg-transparent', iconClassName: !sort.includes('name') ? '!text-chalkboard-90 dark:!text-chalkboard-30' : '', }} > Name setSearchParams(getNextSearchParams(sort, 'modified')) } iconStart={{ icon: sort ? getSortIcon(sort, 'modified') : 'arrowDown', bgClassName: 'bg-transparent', iconClassName: !isSortByModified ? '!text-chalkboard-90 dark:!text-chalkboard-30' : '', }} > Last Modified

Loaded from{' '} {settings.app.projectDirectory.current} .

{state?.matches('Reading projects') ? ( Loading your Projects... ) : ( <> {searchResults.length > 0 ? (
    {searchResults.sort(getSortFunction(sort)).map((project) => ( ))}
) : (

No Projects found {projects.length === 0 ? ', ready to make your first one?' : ` with the search term "${query}"`}

)} )}
) } export default Home