mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-01-31 20:03:37 +00:00
This PR introduces a new dedicated Project Settings screen accessible from the sidebar, clearly separating project-specific settings from global application settings. - Added new route `/project-settings` with dedicated view - Sidebar navigation item "Settings" in Tools section (Shift+S shortcut) - Sidebar-based navigation matching global Settings pattern - Sections: Identity, Worktrees, Theme, Danger Zone **Moved to Project Settings:** - Project name and icon customization - Project-specific theme override - Worktree isolation enable/disable (per-project override) - Init script indicator visibility and auto-dismiss - Delete branch by default preference - Initialization script editor - Delete project (Danger Zone) **Remains in Global Settings:** - Global theme (default for all projects) - Global worktree isolation (default for new projects) - Feature Defaults, Model Defaults - API Keys, AI Providers, MCP Servers - Terminal, Keyboard Shortcuts, Audio - Account, Security, Developer settings Both Theme and Worktree Isolation now follow a consistent override pattern: 1. Global Settings defines the default value 2. New projects inherit the global value 3. Project Settings can override for that specific project 4. Changing global setting doesn't affect projects with overrides - Fixed: Changing global theme was incorrectly overwriting project themes - Fixed: Project worktree setting not persisting across sessions - Project settings now properly load from server on component mount - Shell syntax editor: improved background contrast (bg-background) - Shell syntax editor: removed distracting active line highlight - Project Settings header matches Context/Memory views pattern - `apps/ui/src/routes/project-settings.tsx` - `apps/ui/src/components/views/project-settings-view/` (9 files) - Global settings simplified (removed project-specific options) - Sidebar navigation updated with project settings link - App store: added project-specific useWorktrees state/actions - Types: added projectSettings keyboard shortcut - HTTP client: added missing project settings response fields
175 lines
5.9 KiB
TypeScript
175 lines
5.9 KiB
TypeScript
import { useState, useEffect } from 'react';
|
|
import { useAppStore } from '@/store/app-store';
|
|
import { Settings, FolderOpen, Menu } from 'lucide-react';
|
|
import { Button } from '@/components/ui/button';
|
|
import { ProjectIdentitySection } from './project-identity-section';
|
|
import { ProjectThemeSection } from './project-theme-section';
|
|
import { WorktreePreferencesSection } from './worktree-preferences-section';
|
|
import { DangerZoneSection } from '../settings-view/danger-zone/danger-zone-section';
|
|
import { DeleteProjectDialog } from '../settings-view/components/delete-project-dialog';
|
|
import { ProjectSettingsNavigation } from './components/project-settings-navigation';
|
|
import { useProjectSettingsView } from './hooks/use-project-settings-view';
|
|
import type { Project as ElectronProject } from '@/lib/electron';
|
|
|
|
// Breakpoint constant for mobile (matches Tailwind lg breakpoint)
|
|
const LG_BREAKPOINT = 1024;
|
|
|
|
// Convert to the shared types used by components
|
|
interface SettingsProject {
|
|
id: string;
|
|
name: string;
|
|
path: string;
|
|
theme?: string;
|
|
icon?: string | null;
|
|
customIconPath?: string | null;
|
|
}
|
|
|
|
export function ProjectSettingsView() {
|
|
const { currentProject, moveProjectToTrash } = useAppStore();
|
|
const [showDeleteDialog, setShowDeleteDialog] = useState(false);
|
|
|
|
// Use project settings view navigation hook
|
|
const { activeView, navigateTo } = useProjectSettingsView();
|
|
|
|
// Mobile navigation state - default to showing on desktop, hidden on mobile
|
|
const [showNavigation, setShowNavigation] = useState(() => {
|
|
if (typeof window !== 'undefined') {
|
|
return window.innerWidth >= LG_BREAKPOINT;
|
|
}
|
|
return true;
|
|
});
|
|
|
|
// Auto-close navigation on mobile when a section is selected
|
|
useEffect(() => {
|
|
if (typeof window !== 'undefined' && window.innerWidth < LG_BREAKPOINT) {
|
|
setShowNavigation(false);
|
|
}
|
|
}, [activeView]);
|
|
|
|
// Handle window resize to show/hide navigation appropriately
|
|
useEffect(() => {
|
|
const handleResize = () => {
|
|
if (window.innerWidth >= LG_BREAKPOINT) {
|
|
setShowNavigation(true);
|
|
}
|
|
};
|
|
|
|
window.addEventListener('resize', handleResize);
|
|
return () => window.removeEventListener('resize', handleResize);
|
|
}, []);
|
|
|
|
// Convert electron Project to settings-view Project type
|
|
const convertProject = (project: ElectronProject | null): SettingsProject | null => {
|
|
if (!project) return null;
|
|
return {
|
|
id: project.id,
|
|
name: project.name,
|
|
path: project.path,
|
|
theme: project.theme,
|
|
icon: project.icon,
|
|
customIconPath: project.customIconPath,
|
|
};
|
|
};
|
|
|
|
const settingsProject = convertProject(currentProject);
|
|
|
|
// Render the active section based on current view
|
|
const renderActiveSection = () => {
|
|
if (!currentProject) return null;
|
|
|
|
switch (activeView) {
|
|
case 'identity':
|
|
return <ProjectIdentitySection project={currentProject} />;
|
|
case 'theme':
|
|
return <ProjectThemeSection project={currentProject} />;
|
|
case 'worktrees':
|
|
return <WorktreePreferencesSection project={currentProject} />;
|
|
case 'danger':
|
|
return (
|
|
<DangerZoneSection
|
|
project={settingsProject}
|
|
onDeleteClick={() => setShowDeleteDialog(true)}
|
|
/>
|
|
);
|
|
default:
|
|
return <ProjectIdentitySection project={currentProject} />;
|
|
}
|
|
};
|
|
|
|
// Show message if no project is selected
|
|
if (!currentProject) {
|
|
return (
|
|
<div
|
|
className="flex-1 flex flex-col overflow-hidden content-bg"
|
|
data-testid="project-settings-view"
|
|
>
|
|
<div className="flex-1 flex items-center justify-center p-8">
|
|
<div className="text-center max-w-md">
|
|
<div className="w-16 h-16 mx-auto mb-4 rounded-2xl bg-muted/50 flex items-center justify-center">
|
|
<FolderOpen className="w-8 h-8 text-muted-foreground/50" />
|
|
</div>
|
|
<h2 className="text-lg font-semibold text-foreground mb-2">No Project Selected</h2>
|
|
<p className="text-sm text-muted-foreground">
|
|
Select a project from the sidebar to configure project-specific settings.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div
|
|
className="flex-1 flex flex-col overflow-hidden content-bg"
|
|
data-testid="project-settings-view"
|
|
>
|
|
{/* Header */}
|
|
<div className="flex items-center justify-between p-4 border-b border-border bg-glass backdrop-blur-md">
|
|
<div className="flex items-center gap-3">
|
|
{/* Mobile menu button */}
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
onClick={() => setShowNavigation(!showNavigation)}
|
|
className="lg:hidden h-8 w-8 p-0"
|
|
aria-label="Toggle navigation menu"
|
|
>
|
|
<Menu className="w-4 h-4" />
|
|
</Button>
|
|
<Settings className="w-5 h-5 text-muted-foreground" />
|
|
<div>
|
|
<h1 className="text-xl font-bold">Project Settings</h1>
|
|
<p className="text-sm text-muted-foreground">
|
|
Configure settings for {currentProject.name}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Content Area with Sidebar */}
|
|
<div className="flex-1 flex overflow-hidden">
|
|
{/* Side Navigation */}
|
|
<ProjectSettingsNavigation
|
|
activeSection={activeView}
|
|
onNavigate={navigateTo}
|
|
isOpen={showNavigation}
|
|
onClose={() => setShowNavigation(false)}
|
|
/>
|
|
|
|
{/* Content Panel - Shows only the active section */}
|
|
<div className="flex-1 overflow-y-auto p-4 lg:p-8">
|
|
<div className="max-w-4xl mx-auto">{renderActiveSection()}</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Delete Project Confirmation Dialog */}
|
|
<DeleteProjectDialog
|
|
open={showDeleteDialog}
|
|
onOpenChange={setShowDeleteDialog}
|
|
project={currentProject}
|
|
onConfirm={moveProjectToTrash}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|