mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-01 20:23:36 +00:00
When creating new projects, the theme was always explicitly set even when matching the global theme. This caused "Use Global Theme" to be unchecked, preventing global theme changes from affecting the project. Now theme is only set on new projects when explicitly provided or when recovering a trashed project's theme preference.
270 lines
8.5 KiB
TypeScript
270 lines
8.5 KiB
TypeScript
import { useState, useCallback } from 'react';
|
|
import { createLogger } from '@automaker/utils/logger';
|
|
import { getElectronAPI } from '@/lib/electron';
|
|
|
|
const logger = createLogger('ProjectCreation');
|
|
import { initializeProject } from '@/lib/project-init';
|
|
import { toast } from 'sonner';
|
|
import type { StarterTemplate } from '@/lib/templates';
|
|
import type { Project } from '@/lib/electron';
|
|
|
|
interface UseProjectCreationProps {
|
|
upsertAndSetCurrentProject: (path: string, name: string) => Project;
|
|
}
|
|
|
|
export function useProjectCreation({
|
|
upsertAndSetCurrentProject,
|
|
}: UseProjectCreationProps) {
|
|
// Modal state
|
|
const [showNewProjectModal, setShowNewProjectModal] = useState(false);
|
|
const [isCreatingProject, setIsCreatingProject] = useState(false);
|
|
|
|
// Onboarding state
|
|
const [showOnboardingDialog, setShowOnboardingDialog] = useState(false);
|
|
const [newProjectName, setNewProjectName] = useState('');
|
|
const [newProjectPath, setNewProjectPath] = useState('');
|
|
|
|
/**
|
|
* Common logic for all project creation flows
|
|
*/
|
|
const finalizeProjectCreation = useCallback(
|
|
async (projectPath: string, projectName: string) => {
|
|
try {
|
|
// Initialize .automaker directory structure
|
|
await initializeProject(projectPath);
|
|
|
|
// Write initial app_spec.txt with proper XML structure
|
|
// Note: Must follow XML format as defined in apps/server/src/lib/app-spec-format.ts
|
|
const api = getElectronAPI();
|
|
await api.writeFile(
|
|
`${projectPath}/.automaker/app_spec.txt`,
|
|
`<project_specification>
|
|
<project_name>${projectName}</project_name>
|
|
|
|
<overview>
|
|
Describe your project here. This file will be analyzed by an AI agent
|
|
to understand your project structure and tech stack.
|
|
</overview>
|
|
|
|
<technology_stack>
|
|
<!-- The AI agent will fill this in after analyzing your project -->
|
|
</technology_stack>
|
|
|
|
<core_capabilities>
|
|
<!-- List core features and capabilities -->
|
|
</core_capabilities>
|
|
|
|
<implemented_features>
|
|
<!-- The AI agent will populate this based on code analysis -->
|
|
</implemented_features>
|
|
</project_specification>`
|
|
);
|
|
|
|
// Let the store handle theme (trashed project recovery or undefined for global)
|
|
upsertAndSetCurrentProject(projectPath, projectName);
|
|
|
|
setShowNewProjectModal(false);
|
|
|
|
// Show onboarding dialog for new project
|
|
setNewProjectName(projectName);
|
|
setNewProjectPath(projectPath);
|
|
setShowOnboardingDialog(true);
|
|
|
|
toast.success('Project created successfully');
|
|
} catch (error) {
|
|
logger.error('Failed to finalize project:', error);
|
|
toast.error('Failed to initialize project', {
|
|
description: error instanceof Error ? error.message : 'Unknown error',
|
|
});
|
|
throw error;
|
|
}
|
|
},
|
|
[upsertAndSetCurrentProject]
|
|
);
|
|
|
|
/**
|
|
* Create a blank project with .automaker structure
|
|
*/
|
|
const handleCreateBlankProject = useCallback(
|
|
async (projectName: string, parentDir: string) => {
|
|
setIsCreatingProject(true);
|
|
try {
|
|
const api = getElectronAPI();
|
|
const projectPath = `${parentDir}/${projectName}`;
|
|
|
|
// Create project directory
|
|
await api.mkdir(projectPath);
|
|
|
|
// Finalize project setup
|
|
await finalizeProjectCreation(projectPath, projectName);
|
|
} catch (error) {
|
|
logger.error('Failed to create blank project:', error);
|
|
toast.error('Failed to create project', {
|
|
description: error instanceof Error ? error.message : 'Unknown error',
|
|
});
|
|
} finally {
|
|
setIsCreatingProject(false);
|
|
}
|
|
},
|
|
[finalizeProjectCreation]
|
|
);
|
|
|
|
/**
|
|
* Create project from a starter template
|
|
*/
|
|
const handleCreateFromTemplate = useCallback(
|
|
async (template: StarterTemplate, projectName: string, parentDir: string) => {
|
|
setIsCreatingProject(true);
|
|
try {
|
|
const api = getElectronAPI();
|
|
|
|
// Clone template repository
|
|
if (!api.templates) {
|
|
throw new Error('Templates API is not available');
|
|
}
|
|
const cloneResult = await api.templates.clone(template.repoUrl, projectName, parentDir);
|
|
if (!cloneResult.success) {
|
|
throw new Error(cloneResult.error || 'Failed to clone template');
|
|
}
|
|
const projectPath = cloneResult.projectPath!;
|
|
|
|
// Initialize .automaker directory structure
|
|
await initializeProject(projectPath);
|
|
|
|
// Write app_spec.txt with template-specific info
|
|
await api.writeFile(
|
|
`${projectPath}/.automaker/app_spec.txt`,
|
|
`<project_specification>
|
|
<project_name>${projectName}</project_name>
|
|
|
|
<overview>
|
|
This project was created from the "${template.name}" starter template.
|
|
${template.description}
|
|
</overview>
|
|
|
|
<technology_stack>
|
|
${template.techStack.map((tech) => `<technology>${tech}</technology>`).join('\n ')}
|
|
</technology_stack>
|
|
|
|
<core_capabilities>
|
|
${template.features.map((feature) => `<capability>${feature}</capability>`).join('\n ')}
|
|
</core_capabilities>
|
|
|
|
<implemented_features>
|
|
<!-- The AI agent will populate this based on code analysis -->
|
|
</implemented_features>
|
|
</project_specification>`
|
|
);
|
|
|
|
// Let the store handle theme (trashed project recovery or undefined for global)
|
|
upsertAndSetCurrentProject(projectPath, projectName);
|
|
setShowNewProjectModal(false);
|
|
setNewProjectName(projectName);
|
|
setNewProjectPath(projectPath);
|
|
setShowOnboardingDialog(true);
|
|
|
|
toast.success('Project created from template', {
|
|
description: `Created ${projectName} from ${template.name}`,
|
|
});
|
|
} catch (error) {
|
|
logger.error('Failed to create from template:', error);
|
|
toast.error('Failed to create project from template', {
|
|
description: error instanceof Error ? error.message : 'Unknown error',
|
|
});
|
|
} finally {
|
|
setIsCreatingProject(false);
|
|
}
|
|
},
|
|
[upsertAndSetCurrentProject]
|
|
);
|
|
|
|
/**
|
|
* Create project from a custom GitHub URL
|
|
*/
|
|
const handleCreateFromCustomUrl = useCallback(
|
|
async (repoUrl: string, projectName: string, parentDir: string) => {
|
|
setIsCreatingProject(true);
|
|
try {
|
|
const api = getElectronAPI();
|
|
|
|
// Clone custom repository
|
|
if (!api.templates) {
|
|
throw new Error('Templates API is not available');
|
|
}
|
|
const cloneResult = await api.templates.clone(repoUrl, projectName, parentDir);
|
|
if (!cloneResult.success) {
|
|
throw new Error(cloneResult.error || 'Failed to clone repository');
|
|
}
|
|
const projectPath = cloneResult.projectPath!;
|
|
|
|
// Initialize .automaker directory structure
|
|
await initializeProject(projectPath);
|
|
|
|
// Write app_spec.txt with custom URL info
|
|
await api.writeFile(
|
|
`${projectPath}/.automaker/app_spec.txt`,
|
|
`<project_specification>
|
|
<project_name>${projectName}</project_name>
|
|
|
|
<overview>
|
|
This project was cloned from ${repoUrl}.
|
|
The AI agent will analyze the project structure.
|
|
</overview>
|
|
|
|
<technology_stack>
|
|
<!-- The AI agent will fill this in after analyzing your project -->
|
|
</technology_stack>
|
|
|
|
<core_capabilities>
|
|
<!-- List core features and capabilities -->
|
|
</core_capabilities>
|
|
|
|
<implemented_features>
|
|
<!-- The AI agent will populate this based on code analysis -->
|
|
</implemented_features>
|
|
</project_specification>`
|
|
);
|
|
|
|
// Let the store handle theme (trashed project recovery or undefined for global)
|
|
upsertAndSetCurrentProject(projectPath, projectName);
|
|
setShowNewProjectModal(false);
|
|
setNewProjectName(projectName);
|
|
setNewProjectPath(projectPath);
|
|
setShowOnboardingDialog(true);
|
|
|
|
toast.success('Project created from repository', {
|
|
description: `Created ${projectName} from ${repoUrl}`,
|
|
});
|
|
} catch (error) {
|
|
logger.error('Failed to create from custom URL:', error);
|
|
toast.error('Failed to create project from URL', {
|
|
description: error instanceof Error ? error.message : 'Unknown error',
|
|
});
|
|
} finally {
|
|
setIsCreatingProject(false);
|
|
}
|
|
},
|
|
[upsertAndSetCurrentProject]
|
|
);
|
|
|
|
return {
|
|
// Modal state
|
|
showNewProjectModal,
|
|
setShowNewProjectModal,
|
|
isCreatingProject,
|
|
|
|
// Onboarding state
|
|
showOnboardingDialog,
|
|
setShowOnboardingDialog,
|
|
newProjectName,
|
|
setNewProjectName,
|
|
newProjectPath,
|
|
setNewProjectPath,
|
|
|
|
// Handlers
|
|
handleCreateBlankProject,
|
|
handleCreateFromTemplate,
|
|
handleCreateFromCustomUrl,
|
|
};
|
|
}
|