mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-02 20:43:36 +00:00
refactor: Enhance project management features and UI components
- Updated create-pr.ts to improve commit error handling and logging. - Enhanced project-switcher.tsx with new folder opening functionality and state management for project setup. - Expanded icon-picker.tsx to include a comprehensive list of icons organized by category. - Replaced dialog components with popover components for auto mode and plan settings, improving UI responsiveness. - Refactored board-view components to streamline feature management and enhance user experience. - Removed outdated dialog components and replaced them with popover alternatives for better accessibility. These changes aim to improve the overall usability and functionality of the project management interface.
This commit is contained in:
@@ -10,43 +10,434 @@ interface IconPickerProps {
|
||||
onSelectIcon: (icon: string | null) => void;
|
||||
}
|
||||
|
||||
// Popular project-related icons
|
||||
// Comprehensive list of project-related icons from Lucide
|
||||
// Organized by category for easier browsing
|
||||
const POPULAR_ICONS = [
|
||||
// Folders & Files
|
||||
'Folder',
|
||||
'FolderOpen',
|
||||
'FolderCode',
|
||||
'FolderGit',
|
||||
'FolderKanban',
|
||||
'Package',
|
||||
'Box',
|
||||
'Boxes',
|
||||
'FolderTree',
|
||||
'FolderInput',
|
||||
'FolderOutput',
|
||||
'FolderPlus',
|
||||
'File',
|
||||
'FileCode',
|
||||
'FileText',
|
||||
'FileJson',
|
||||
'FileImage',
|
||||
'FileVideo',
|
||||
'FileAudio',
|
||||
'FileSpreadsheet',
|
||||
'Files',
|
||||
'Archive',
|
||||
|
||||
// Code & Development
|
||||
'Code',
|
||||
'Code2',
|
||||
'Braces',
|
||||
'FileCode',
|
||||
'Brackets',
|
||||
'Terminal',
|
||||
'Globe',
|
||||
'Server',
|
||||
'Database',
|
||||
'TerminalSquare',
|
||||
'Command',
|
||||
'GitBranch',
|
||||
'GitCommit',
|
||||
'GitMerge',
|
||||
'GitPullRequest',
|
||||
'GitCompare',
|
||||
'GitFork',
|
||||
'GitHub',
|
||||
'Gitlab',
|
||||
'Bitbucket',
|
||||
'Vscode',
|
||||
|
||||
// Packages & Containers
|
||||
'Package',
|
||||
'PackageSearch',
|
||||
'PackageCheck',
|
||||
'PackageX',
|
||||
'Box',
|
||||
'Boxes',
|
||||
'Container',
|
||||
|
||||
// UI & Design
|
||||
'Layout',
|
||||
'LayoutGrid',
|
||||
'LayoutList',
|
||||
'LayoutDashboard',
|
||||
'LayoutTemplate',
|
||||
'Layers',
|
||||
'Layers2',
|
||||
'Layers3',
|
||||
'Blocks',
|
||||
'Component',
|
||||
'Puzzle',
|
||||
'Palette',
|
||||
'Paintbrush',
|
||||
'Brush',
|
||||
'PenTool',
|
||||
'Ruler',
|
||||
'Grid',
|
||||
'Grid3x3',
|
||||
'Square',
|
||||
'RectangleHorizontal',
|
||||
'RectangleVertical',
|
||||
'Circle',
|
||||
|
||||
// Tools & Settings
|
||||
'Cog',
|
||||
'Settings',
|
||||
'Settings2',
|
||||
'Wrench',
|
||||
'Hammer',
|
||||
'Screwdriver',
|
||||
'WrenchIcon',
|
||||
'Tool',
|
||||
'ScrewdriverWrench',
|
||||
'Sliders',
|
||||
'SlidersHorizontal',
|
||||
'Filter',
|
||||
'FilterX',
|
||||
|
||||
// Technology & Infrastructure
|
||||
'Server',
|
||||
'ServerCrash',
|
||||
'ServerCog',
|
||||
'Database',
|
||||
'DatabaseBackup',
|
||||
'CloudUpload',
|
||||
'CloudDownload',
|
||||
'CloudOff',
|
||||
'Globe',
|
||||
'Globe2',
|
||||
'Network',
|
||||
'Wifi',
|
||||
'WifiOff',
|
||||
'Router',
|
||||
'Cpu',
|
||||
'MemoryStick',
|
||||
'HardDrive',
|
||||
'HardDriveIcon',
|
||||
'CircuitBoard',
|
||||
'Microchip',
|
||||
'Monitor',
|
||||
'MonitorSpeaker',
|
||||
'Laptop',
|
||||
'Smartphone',
|
||||
'Tablet',
|
||||
'Mouse',
|
||||
'Keyboard',
|
||||
'Headphones',
|
||||
'Printer',
|
||||
'Scanner',
|
||||
|
||||
// Workflow & Process
|
||||
'Workflow',
|
||||
'Zap',
|
||||
'Rocket',
|
||||
'Sparkles',
|
||||
'Star',
|
||||
'Heart',
|
||||
'Flame',
|
||||
'Lightning',
|
||||
'Bolt',
|
||||
'Target',
|
||||
'Flag',
|
||||
'FlagTriangleRight',
|
||||
'CheckCircle',
|
||||
'CheckCircle2',
|
||||
'XCircle',
|
||||
'AlertCircle',
|
||||
'Info',
|
||||
'HelpCircle',
|
||||
'Clock',
|
||||
'Timer',
|
||||
'Stopwatch',
|
||||
'Calendar',
|
||||
'CalendarDays',
|
||||
'CalendarCheck',
|
||||
'CalendarClock',
|
||||
|
||||
// Security & Access
|
||||
'Shield',
|
||||
'ShieldCheck',
|
||||
'ShieldAlert',
|
||||
'ShieldOff',
|
||||
'Lock',
|
||||
'Unlock',
|
||||
'Key',
|
||||
'Cpu',
|
||||
'CircuitBoard',
|
||||
'Workflow',
|
||||
'KeyRound',
|
||||
'Eye',
|
||||
'EyeOff',
|
||||
'User',
|
||||
'Users',
|
||||
'UserCheck',
|
||||
'UserX',
|
||||
'UserPlus',
|
||||
'UserCog',
|
||||
|
||||
// Business & Finance
|
||||
'Briefcase',
|
||||
'Building',
|
||||
'Building2',
|
||||
'Store',
|
||||
'ShoppingCart',
|
||||
'ShoppingBag',
|
||||
'CreditCard',
|
||||
'Wallet',
|
||||
'DollarSign',
|
||||
'Euro',
|
||||
'PoundSterling',
|
||||
'Yen',
|
||||
'Coins',
|
||||
'Receipt',
|
||||
'ChartBar',
|
||||
'ChartLine',
|
||||
'ChartPie',
|
||||
'TrendingUp',
|
||||
'TrendingDown',
|
||||
'Activity',
|
||||
'BarChart',
|
||||
'LineChart',
|
||||
'PieChart',
|
||||
|
||||
// Communication & Media
|
||||
'MessageSquare',
|
||||
'MessageCircle',
|
||||
'Mail',
|
||||
'MailOpen',
|
||||
'Send',
|
||||
'Inbox',
|
||||
'Phone',
|
||||
'PhoneCall',
|
||||
'Video',
|
||||
'VideoOff',
|
||||
'Camera',
|
||||
'CameraOff',
|
||||
'Image',
|
||||
'ImageIcon',
|
||||
'Film',
|
||||
'Music',
|
||||
'Mic',
|
||||
'MicOff',
|
||||
'Volume',
|
||||
'Volume2',
|
||||
'VolumeX',
|
||||
'Radio',
|
||||
'Podcast',
|
||||
|
||||
// Social & Community
|
||||
'Heart',
|
||||
'HeartHandshake',
|
||||
'Star',
|
||||
'StarOff',
|
||||
'ThumbsUp',
|
||||
'ThumbsDown',
|
||||
'Share',
|
||||
'Share2',
|
||||
'Link',
|
||||
'Link2',
|
||||
'ExternalLink',
|
||||
'AtSign',
|
||||
'Hash',
|
||||
'Hashtag',
|
||||
'Tag',
|
||||
'Tags',
|
||||
|
||||
// Navigation & Location
|
||||
'Compass',
|
||||
'Map',
|
||||
'MapPin',
|
||||
'Navigation',
|
||||
'Navigation2',
|
||||
'Route',
|
||||
'Plane',
|
||||
'Car',
|
||||
'Bike',
|
||||
'Ship',
|
||||
'Train',
|
||||
'Bus',
|
||||
|
||||
// Science & Education
|
||||
'FlaskConical',
|
||||
'FlaskRound',
|
||||
'Beaker',
|
||||
'TestTube',
|
||||
'TestTube2',
|
||||
'Microscope',
|
||||
'Atom',
|
||||
'Brain',
|
||||
'GraduationCap',
|
||||
'Book',
|
||||
'BookOpen',
|
||||
'BookMarked',
|
||||
'Library',
|
||||
'School',
|
||||
'University',
|
||||
|
||||
// Food & Health
|
||||
'Coffee',
|
||||
'Utensils',
|
||||
'UtensilsCrossed',
|
||||
'Apple',
|
||||
'Cherry',
|
||||
'Cookie',
|
||||
'Cake',
|
||||
'Pizza',
|
||||
'Beer',
|
||||
'Wine',
|
||||
'HeartPulse',
|
||||
'Dumbbell',
|
||||
'Running',
|
||||
|
||||
// Nature & Weather
|
||||
'Tree',
|
||||
'TreePine',
|
||||
'Leaf',
|
||||
'Flower',
|
||||
'Flower2',
|
||||
'Sun',
|
||||
'Moon',
|
||||
'CloudRain',
|
||||
'CloudSnow',
|
||||
'CloudLightning',
|
||||
'Droplet',
|
||||
'Wind',
|
||||
'Snowflake',
|
||||
'Umbrella',
|
||||
|
||||
// Objects & Symbols
|
||||
'Puzzle',
|
||||
'PuzzleIcon',
|
||||
'Gamepad',
|
||||
'Gamepad2',
|
||||
'Dice',
|
||||
'Dice1',
|
||||
'Dice6',
|
||||
'Gem',
|
||||
'Crown',
|
||||
'Trophy',
|
||||
'Medal',
|
||||
'Award',
|
||||
'Gift',
|
||||
'GiftIcon',
|
||||
'Bell',
|
||||
'BellOff',
|
||||
'BellRing',
|
||||
'Home',
|
||||
'House',
|
||||
'DoorOpen',
|
||||
'DoorClosed',
|
||||
'Window',
|
||||
'Lightbulb',
|
||||
'LightbulbOff',
|
||||
'Candle',
|
||||
'Flashlight',
|
||||
'FlashlightOff',
|
||||
'Battery',
|
||||
'BatteryFull',
|
||||
'BatteryLow',
|
||||
'BatteryCharging',
|
||||
'Plug',
|
||||
'PlugZap',
|
||||
'Power',
|
||||
'PowerOff',
|
||||
|
||||
// Arrows & Directions
|
||||
'ArrowRight',
|
||||
'ArrowLeft',
|
||||
'ArrowUp',
|
||||
'ArrowDown',
|
||||
'ArrowUpRight',
|
||||
'ArrowDownRight',
|
||||
'ArrowDownLeft',
|
||||
'ArrowUpLeft',
|
||||
'ChevronRight',
|
||||
'ChevronLeft',
|
||||
'ChevronUp',
|
||||
'ChevronDown',
|
||||
'Move',
|
||||
'MoveUp',
|
||||
'MoveDown',
|
||||
'MoveLeft',
|
||||
'MoveRight',
|
||||
'RotateCw',
|
||||
'RotateCcw',
|
||||
'RefreshCw',
|
||||
'RefreshCcw',
|
||||
|
||||
// Shapes & Symbols
|
||||
'Diamond',
|
||||
'Pentagon',
|
||||
'Cross',
|
||||
'Plus',
|
||||
'Minus',
|
||||
'X',
|
||||
'Check',
|
||||
'Divide',
|
||||
'Equal',
|
||||
'Infinity',
|
||||
'Percent',
|
||||
|
||||
// Miscellaneous
|
||||
'Bot',
|
||||
'Wand',
|
||||
'Wand2',
|
||||
'Magic',
|
||||
'Stars',
|
||||
'Comet',
|
||||
'Satellite',
|
||||
'SatelliteDish',
|
||||
'Radar',
|
||||
'RadarIcon',
|
||||
'Scan',
|
||||
'ScanLine',
|
||||
'QrCode',
|
||||
'Barcode',
|
||||
'ScanSearch',
|
||||
'Search',
|
||||
'SearchX',
|
||||
'ZoomIn',
|
||||
'ZoomOut',
|
||||
'Maximize',
|
||||
'Minimize',
|
||||
'Maximize2',
|
||||
'Minimize2',
|
||||
'Expand',
|
||||
'Shrink',
|
||||
'Copy',
|
||||
'CopyCheck',
|
||||
'Clipboard',
|
||||
'ClipboardCheck',
|
||||
'ClipboardCopy',
|
||||
'ClipboardList',
|
||||
'ClipboardPaste',
|
||||
'Scissors',
|
||||
'Cut',
|
||||
'FileEdit',
|
||||
'Pen',
|
||||
'Pencil',
|
||||
'Eraser',
|
||||
'Trash',
|
||||
'Trash2',
|
||||
'Delete',
|
||||
'ArchiveRestore',
|
||||
'Download',
|
||||
'Upload',
|
||||
'Save',
|
||||
'SaveAll',
|
||||
'FilePlus',
|
||||
'FileMinus',
|
||||
'FileX',
|
||||
'FileCheck',
|
||||
'FileQuestion',
|
||||
'FileWarning',
|
||||
'FileSearch',
|
||||
'FolderSearch',
|
||||
'FolderX',
|
||||
'FolderCheck',
|
||||
'FolderMinus',
|
||||
'FolderSync',
|
||||
'FolderUp',
|
||||
'FolderDown',
|
||||
];
|
||||
|
||||
export function IconPicker({ selectedIcon, onSelectIcon }: IconPickerProps) {
|
||||
@@ -94,7 +485,7 @@ export function IconPicker({ selectedIcon, onSelectIcon }: IconPickerProps) {
|
||||
)}
|
||||
|
||||
{/* Icons Grid */}
|
||||
<ScrollArea className="h-64 rounded-md border">
|
||||
<ScrollArea className="h-96 rounded-md border">
|
||||
<div className="grid grid-cols-6 gap-1 p-2">
|
||||
{filteredIcons.map((iconName) => {
|
||||
const IconComponent = getIconComponent(iconName);
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { Edit2, Trash2 } from 'lucide-react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { useAppStore } from '@/store/app-store';
|
||||
import { ConfirmDialog } from '@/components/ui/confirm-dialog';
|
||||
import type { Project } from '@/lib/electron';
|
||||
|
||||
interface ProjectContextMenuProps {
|
||||
@@ -19,6 +20,7 @@ export function ProjectContextMenu({
|
||||
}: ProjectContextMenuProps) {
|
||||
const menuRef = useRef<HTMLDivElement>(null);
|
||||
const { moveProjectToTrash } = useAppStore();
|
||||
const [showRemoveDialog, setShowRemoveDialog] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
@@ -47,57 +49,73 @@ export function ProjectContextMenu({
|
||||
};
|
||||
|
||||
const handleRemove = () => {
|
||||
if (confirm(`Remove "${project.name}" from the project list?`)) {
|
||||
moveProjectToTrash(project.id);
|
||||
}
|
||||
setShowRemoveDialog(true);
|
||||
};
|
||||
|
||||
const handleConfirmRemove = () => {
|
||||
moveProjectToTrash(project.id);
|
||||
onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={menuRef}
|
||||
className={cn(
|
||||
'fixed z-[100] min-w-48 rounded-lg',
|
||||
'bg-popover text-popover-foreground',
|
||||
'border border-border shadow-lg',
|
||||
'animate-in fade-in zoom-in-95 duration-100'
|
||||
)}
|
||||
style={{
|
||||
top: position.y,
|
||||
left: position.x,
|
||||
}}
|
||||
data-testid="project-context-menu"
|
||||
>
|
||||
<div className="p-1">
|
||||
<button
|
||||
onClick={handleEdit}
|
||||
className={cn(
|
||||
'w-full flex items-center gap-2 px-3 py-2 rounded-md',
|
||||
'text-sm font-medium text-left',
|
||||
'hover:bg-accent transition-colors',
|
||||
'focus:outline-none focus:bg-accent'
|
||||
)}
|
||||
data-testid="edit-project-button"
|
||||
>
|
||||
<Edit2 className="w-4 h-4" />
|
||||
<span>Edit Name & Icon</span>
|
||||
</button>
|
||||
<>
|
||||
<div
|
||||
ref={menuRef}
|
||||
className={cn(
|
||||
'fixed z-[100] min-w-48 rounded-lg',
|
||||
'bg-popover text-popover-foreground',
|
||||
'border border-border shadow-lg',
|
||||
'animate-in fade-in zoom-in-95 duration-100'
|
||||
)}
|
||||
style={{
|
||||
top: position.y,
|
||||
left: position.x,
|
||||
}}
|
||||
data-testid="project-context-menu"
|
||||
>
|
||||
<div className="p-1">
|
||||
<button
|
||||
onClick={handleEdit}
|
||||
className={cn(
|
||||
'w-full flex items-center gap-2 px-3 py-2 rounded-md',
|
||||
'text-sm font-medium text-left',
|
||||
'hover:bg-accent transition-colors',
|
||||
'focus:outline-none focus:bg-accent'
|
||||
)}
|
||||
data-testid="edit-project-button"
|
||||
>
|
||||
<Edit2 className="w-4 h-4" />
|
||||
<span>Edit Name & Icon</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={handleRemove}
|
||||
className={cn(
|
||||
'w-full flex items-center gap-2 px-3 py-2 rounded-md',
|
||||
'text-sm font-medium text-left',
|
||||
'text-destructive hover:bg-destructive/10',
|
||||
'transition-colors',
|
||||
'focus:outline-none focus:bg-destructive/10'
|
||||
)}
|
||||
data-testid="remove-project-button"
|
||||
>
|
||||
<Trash2 className="w-4 h-4" />
|
||||
<span>Remove Project</span>
|
||||
</button>
|
||||
<button
|
||||
onClick={handleRemove}
|
||||
className={cn(
|
||||
'w-full flex items-center gap-2 px-3 py-2 rounded-md',
|
||||
'text-sm font-medium text-left',
|
||||
'text-destructive hover:bg-destructive/10',
|
||||
'transition-colors',
|
||||
'focus:outline-none focus:bg-destructive/10'
|
||||
)}
|
||||
data-testid="remove-project-button"
|
||||
>
|
||||
<Trash2 className="w-4 h-4" />
|
||||
<span>Remove Project</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ConfirmDialog
|
||||
open={showRemoveDialog}
|
||||
onOpenChange={setShowRemoveDialog}
|
||||
onConfirm={handleConfirmRemove}
|
||||
title="Remove Project"
|
||||
description={`Are you sure you want to remove "${project.name}" from the project list? This won't delete any files on disk.`}
|
||||
icon={Trash2}
|
||||
iconClassName="text-destructive"
|
||||
confirmText="Remove"
|
||||
confirmVariant="destructive"
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { useState, useCallback, useEffect } from 'react';
|
||||
import { Plus, Bug } from 'lucide-react';
|
||||
import { Plus, Bug, FolderOpen } from 'lucide-react';
|
||||
import { useNavigate } from '@tanstack/react-router';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { useAppStore } from '@/store/app-store';
|
||||
import { useAppStore, type ThemeMode } from '@/store/app-store';
|
||||
import { useOSDetection } from '@/hooks/use-os-detection';
|
||||
import { ProjectSwitcherItem } from './components/project-switcher-item';
|
||||
import { ProjectContextMenu } from './components/project-context-menu';
|
||||
@@ -12,6 +12,9 @@ import { OnboardingDialog } from '@/components/layout/sidebar/dialogs';
|
||||
import { useProjectCreation, useProjectTheme } from '@/components/layout/sidebar/hooks';
|
||||
import type { Project } from '@/lib/electron';
|
||||
import { getElectronAPI } from '@/lib/electron';
|
||||
import { initializeProject, hasAppSpec, hasAutomakerDir } from '@/lib/project-init';
|
||||
import { toast } from 'sonner';
|
||||
import { CreateSpecDialog } from '@/components/views/spec-view/dialogs';
|
||||
|
||||
function getOSAbbreviation(os: string): string {
|
||||
switch (os) {
|
||||
@@ -34,6 +37,8 @@ export function ProjectSwitcher() {
|
||||
setCurrentProject,
|
||||
trashedProjects,
|
||||
upsertAndSetCurrentProject,
|
||||
specCreatingForProject,
|
||||
setSpecCreatingForProject,
|
||||
} = useAppStore();
|
||||
const [contextMenuProject, setContextMenuProject] = useState<Project | null>(null);
|
||||
const [contextMenuPosition, setContextMenuPosition] = useState<{ x: number; y: number } | null>(
|
||||
@@ -41,6 +46,17 @@ export function ProjectSwitcher() {
|
||||
);
|
||||
const [editDialogProject, setEditDialogProject] = useState<Project | null>(null);
|
||||
|
||||
// Setup dialog state for opening existing projects
|
||||
const [showSetupDialog, setShowSetupDialog] = useState(false);
|
||||
const [setupProjectPath, setSetupProjectPath] = useState<string | null>(null);
|
||||
const [projectOverview, setProjectOverview] = useState('');
|
||||
const [generateFeatures, setGenerateFeatures] = useState(true);
|
||||
const [analyzeProject, setAnalyzeProject] = useState(true);
|
||||
const [featureCount, setFeatureCount] = useState(5);
|
||||
|
||||
// Derive isCreatingSpec from store state
|
||||
const isCreatingSpec = specCreatingForProject !== null;
|
||||
|
||||
// Version info
|
||||
const appVersion = typeof __APP_VERSION__ !== 'undefined' ? __APP_VERSION__ : '0.0.0';
|
||||
const { os } = useOSDetection();
|
||||
@@ -108,6 +124,109 @@ export function ProjectSwitcher() {
|
||||
api.openExternalLink('https://github.com/AutoMaker-Org/automaker/issues');
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* Opens the system folder selection dialog and initializes the selected project.
|
||||
*/
|
||||
const handleOpenFolder = useCallback(async () => {
|
||||
const api = getElectronAPI();
|
||||
const result = await api.openDirectory();
|
||||
|
||||
if (!result.canceled && result.filePaths[0]) {
|
||||
const path = result.filePaths[0];
|
||||
// Extract folder name from path (works on both Windows and Mac/Linux)
|
||||
const name = path.split(/[/\\]/).filter(Boolean).pop() || 'Untitled Project';
|
||||
|
||||
try {
|
||||
// Check if this is a brand new project (no .automaker directory)
|
||||
const hadAutomakerDir = await hasAutomakerDir(path);
|
||||
|
||||
// Initialize the .automaker directory structure
|
||||
const initResult = await initializeProject(path);
|
||||
|
||||
if (!initResult.success) {
|
||||
toast.error('Failed to initialize project', {
|
||||
description: initResult.error || 'Unknown error occurred',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Upsert project and set as current (handles both create and update cases)
|
||||
// Theme preservation is handled by the store action
|
||||
const trashedProject = trashedProjects.find((p) => p.path === path);
|
||||
const effectiveTheme =
|
||||
(trashedProject?.theme as ThemeMode | undefined) ||
|
||||
(currentProject?.theme as ThemeMode | undefined) ||
|
||||
globalTheme;
|
||||
upsertAndSetCurrentProject(path, name, effectiveTheme);
|
||||
|
||||
// Check if app_spec.txt exists
|
||||
const specExists = await hasAppSpec(path);
|
||||
|
||||
if (!hadAutomakerDir && !specExists) {
|
||||
// This is a brand new project - show setup dialog
|
||||
setSetupProjectPath(path);
|
||||
setShowSetupDialog(true);
|
||||
toast.success('Project opened', {
|
||||
description: `Opened ${name}. Let's set up your app specification!`,
|
||||
});
|
||||
} else if (initResult.createdFiles && initResult.createdFiles.length > 0) {
|
||||
toast.success(initResult.isNewProject ? 'Project initialized' : 'Project updated', {
|
||||
description: `Set up ${initResult.createdFiles.length} file(s) in .automaker`,
|
||||
});
|
||||
} else {
|
||||
toast.success('Project opened', {
|
||||
description: `Opened ${name}`,
|
||||
});
|
||||
}
|
||||
|
||||
// Navigate to board view
|
||||
navigate({ to: '/board' });
|
||||
} catch (error) {
|
||||
console.error('Failed to open project:', error);
|
||||
toast.error('Failed to open project', {
|
||||
description: error instanceof Error ? error.message : 'Unknown error',
|
||||
});
|
||||
}
|
||||
}
|
||||
}, [trashedProjects, upsertAndSetCurrentProject, currentProject, globalTheme, navigate]);
|
||||
|
||||
// Handler for creating initial spec from the setup dialog
|
||||
const handleCreateInitialSpec = useCallback(async () => {
|
||||
if (!setupProjectPath) return;
|
||||
|
||||
setSpecCreatingForProject(setupProjectPath);
|
||||
setShowSetupDialog(false);
|
||||
|
||||
try {
|
||||
const api = getElectronAPI();
|
||||
await api.generateAppSpec({
|
||||
projectPath: setupProjectPath,
|
||||
projectOverview,
|
||||
generateFeatures,
|
||||
analyzeProject,
|
||||
featureCount,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to generate spec:', error);
|
||||
toast.error('Failed to generate spec', {
|
||||
description: error instanceof Error ? error.message : 'Unknown error',
|
||||
});
|
||||
setSpecCreatingForProject(null);
|
||||
}
|
||||
}, [
|
||||
setupProjectPath,
|
||||
projectOverview,
|
||||
generateFeatures,
|
||||
analyzeProject,
|
||||
featureCount,
|
||||
setSpecCreatingForProject,
|
||||
]);
|
||||
|
||||
const handleSkipSetup = useCallback(() => {
|
||||
setShowSetupDialog(false);
|
||||
setSetupProjectPath(null);
|
||||
}, []);
|
||||
|
||||
// Keyboard shortcuts for project switching (1-9, 0)
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
@@ -204,7 +323,7 @@ export function ProjectSwitcher() {
|
||||
</div>
|
||||
|
||||
{/* Projects List */}
|
||||
<div className="flex-1 overflow-y-auto py-3 px-2 space-y-2">
|
||||
<div className="flex-1 overflow-y-auto pt-1 pb-3 px-2 space-y-2">
|
||||
{projects.map((project, index) => (
|
||||
<ProjectSwitcherItem
|
||||
key={project.id}
|
||||
@@ -219,7 +338,7 @@ export function ProjectSwitcher() {
|
||||
{/* Horizontal rule and Add Project Button - only show if there are projects */}
|
||||
{projects.length > 0 && (
|
||||
<>
|
||||
<div className="w-full h-px bg-border/40 my-2" />
|
||||
<div className="w-full h-px bg-border my-2" />
|
||||
<button
|
||||
onClick={handleNewProject}
|
||||
className={cn(
|
||||
@@ -234,25 +353,55 @@ export function ProjectSwitcher() {
|
||||
>
|
||||
<Plus className="w-5 h-5" />
|
||||
</button>
|
||||
<button
|
||||
onClick={handleOpenFolder}
|
||||
className={cn(
|
||||
'w-full aspect-square rounded-xl flex items-center justify-center',
|
||||
'transition-all duration-200 ease-out',
|
||||
'text-muted-foreground hover:text-foreground',
|
||||
'hover:bg-accent/50 border border-transparent hover:border-border/40',
|
||||
'hover:shadow-sm hover:scale-105 active:scale-95'
|
||||
)}
|
||||
title="Open Project"
|
||||
data-testid="open-project-button"
|
||||
>
|
||||
<FolderOpen className="w-5 h-5" />
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Add Project Button - when no projects, show without rule */}
|
||||
{projects.length === 0 && (
|
||||
<button
|
||||
onClick={handleNewProject}
|
||||
className={cn(
|
||||
'w-full aspect-square rounded-xl flex items-center justify-center',
|
||||
'transition-all duration-200 ease-out',
|
||||
'text-muted-foreground hover:text-foreground',
|
||||
'hover:bg-accent/50 border border-transparent hover:border-border/40',
|
||||
'hover:shadow-sm hover:scale-105 active:scale-95'
|
||||
)}
|
||||
title="New Project"
|
||||
data-testid="new-project-button"
|
||||
>
|
||||
<Plus className="w-5 h-5" />
|
||||
</button>
|
||||
<>
|
||||
<button
|
||||
onClick={handleNewProject}
|
||||
className={cn(
|
||||
'w-full aspect-square rounded-xl flex items-center justify-center',
|
||||
'transition-all duration-200 ease-out',
|
||||
'text-muted-foreground hover:text-foreground',
|
||||
'hover:bg-accent/50 border border-transparent hover:border-border/40',
|
||||
'hover:shadow-sm hover:scale-105 active:scale-95'
|
||||
)}
|
||||
title="New Project"
|
||||
data-testid="new-project-button"
|
||||
>
|
||||
<Plus className="w-5 h-5" />
|
||||
</button>
|
||||
<button
|
||||
onClick={handleOpenFolder}
|
||||
className={cn(
|
||||
'w-full aspect-square rounded-xl flex items-center justify-center',
|
||||
'transition-all duration-200 ease-out',
|
||||
'text-muted-foreground hover:text-foreground',
|
||||
'hover:bg-accent/50 border border-transparent hover:border-border/40',
|
||||
'hover:shadow-sm hover:scale-105 active:scale-95'
|
||||
)}
|
||||
title="Open Project"
|
||||
data-testid="open-project-button"
|
||||
>
|
||||
<FolderOpen className="w-5 h-5" />
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -312,6 +461,26 @@ export function ProjectSwitcher() {
|
||||
onSkip={handleOnboardingSkip}
|
||||
onGenerateSpec={handleOnboardingSkip}
|
||||
/>
|
||||
|
||||
{/* Setup Dialog for Open Project */}
|
||||
<CreateSpecDialog
|
||||
open={showSetupDialog}
|
||||
onOpenChange={setShowSetupDialog}
|
||||
projectOverview={projectOverview}
|
||||
onProjectOverviewChange={setProjectOverview}
|
||||
generateFeatures={generateFeatures}
|
||||
onGenerateFeaturesChange={setGenerateFeatures}
|
||||
analyzeProject={analyzeProject}
|
||||
onAnalyzeProjectChange={setAnalyzeProject}
|
||||
featureCount={featureCount}
|
||||
onFeatureCountChange={setFeatureCount}
|
||||
onCreateSpec={handleCreateInitialSpec}
|
||||
onSkip={handleSkipSetup}
|
||||
isCreatingSpec={isCreatingSpec}
|
||||
showSkipButton={true}
|
||||
title="Set Up Your Project"
|
||||
description="We didn't find an app_spec.txt file. Let us help you generate your app_spec.txt to help describe your project for our system. We'll analyze your project's tech stack and create a comprehensive specification."
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Folder, LucideIcon } from 'lucide-react';
|
||||
import * as LucideIcons from 'lucide-react';
|
||||
import { cn, isMac } from '@/lib/utils';
|
||||
import { getAuthenticatedImageUrl } from '@/lib/api-fetch';
|
||||
import type { Project } from '@/lib/electron';
|
||||
import { isElectron, type Project } from '@/lib/electron';
|
||||
|
||||
interface SidebarHeaderProps {
|
||||
sidebarOpen: boolean;
|
||||
@@ -25,14 +25,17 @@ export function SidebarHeader({ sidebarOpen, currentProject }: SidebarHeaderProp
|
||||
<div
|
||||
className={cn(
|
||||
'shrink-0 flex flex-col',
|
||||
// Add minimal padding on macOS for traffic light buttons
|
||||
isMac && 'pt-2'
|
||||
// Add padding on macOS Electron for traffic light buttons
|
||||
isMac && isElectron() && 'pt-[10px]'
|
||||
)}
|
||||
>
|
||||
{/* Project name and icon display */}
|
||||
{currentProject && (
|
||||
<div
|
||||
className={cn('flex items-center gap-3 px-4 py-3', !sidebarOpen && 'justify-center px-2')}
|
||||
className={cn(
|
||||
'flex items-center gap-3 px-4 pt-3 pb-1',
|
||||
!sidebarOpen && 'justify-center px-2'
|
||||
)}
|
||||
>
|
||||
{/* Project Icon */}
|
||||
<div className="shrink-0">
|
||||
|
||||
@@ -21,7 +21,7 @@ export function SidebarNavigation({
|
||||
navigate,
|
||||
}: SidebarNavigationProps) {
|
||||
return (
|
||||
<nav className={cn('flex-1 overflow-y-auto px-3 pb-2', sidebarOpen ? 'mt-5' : 'mt-1')}>
|
||||
<nav className={cn('flex-1 overflow-y-auto px-3 pb-2', sidebarOpen ? 'mt-1' : 'mt-1')}>
|
||||
{!currentProject && sidebarOpen ? (
|
||||
// Placeholder when no project is selected (only in expanded state)
|
||||
<div className="flex items-center justify-center h-full px-4">
|
||||
|
||||
Reference in New Issue
Block a user