Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2,550 changes: 361 additions & 2,189 deletions src/renderer/App.tsx

Large diffs are not rendered by default.

107 changes: 107 additions & 0 deletions src/renderer/components/HomeView.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import React from 'react';
import { motion } from 'framer-motion';
import { FolderOpen, Github, Plus } from 'lucide-react';
import { useTheme } from '../hooks/useTheme';
import emdashLogo from '../../assets/images/emdash/emdash_logo.svg';
import emdashLogoWhite from '../../assets/images/emdash/emdash_logo_white.svg';

/**
* Home view component showing main action buttons and logo
* Extracted from App.tsx to reduce component size
*/

export interface HomeViewProps {
onOpenProject: () => void;
onNewProject: () => void;
onCloneProject: () => void;
}

export const HomeView: React.FC<HomeViewProps> = ({
onOpenProject,
onNewProject,
onCloneProject,
}) => {
const { effectiveTheme } = useTheme();
const isDark = effectiveTheme === 'dark' || effectiveTheme === 'dark-black';
const logoSrc = isDark ? emdashLogoWhite : emdashLogo;

const handleOpenClick = async () => {
const { captureTelemetry } = await import('../lib/telemetryClient');
captureTelemetry('project_open_clicked');
onOpenProject();
};

return (
<div className="flex h-full flex-col overflow-y-auto bg-background text-foreground">
<div className="container mx-auto flex min-h-full max-w-3xl flex-1 flex-col justify-center px-8 py-8">
<div className="mb-3 text-center">
<div className="mb-3 flex items-center justify-center">
<div className="logo-shimmer-container">
<img
key={effectiveTheme}
src={logoSrc}
alt="Emdash"
className="logo-shimmer-image"
/>
<span
className="logo-shimmer-overlay"
aria-hidden="true"
style={{
WebkitMaskImage: `url(${logoSrc})`,
maskImage: `url(${logoSrc})`,
WebkitMaskRepeat: 'no-repeat',
maskRepeat: 'no-repeat',
WebkitMaskSize: 'contain',
maskSize: 'contain',
WebkitMaskPosition: 'center',
maskPosition: 'center',
}}
/>
</div>
</div>
<p className="whitespace-nowrap text-xs text-muted-foreground">
Coding Agent Dashboard
</p>
</div>

<div className="grid grid-cols-1 gap-4 sm:grid-cols-3 sm:gap-2">
<motion.button
whileTap={{ scale: 0.97 }}
transition={{ duration: 0.1, ease: 'easeInOut' }}
onClick={handleOpenClick}
className="group flex flex-col items-start justify-between rounded-lg border border-border bg-muted/20 p-4 text-card-foreground shadow-sm transition-all hover:bg-muted/40 hover:shadow-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
>
<FolderOpen className="mb-5 h-5 w-5 text-foreground opacity-70" />
<div className="w-full min-w-0 text-left">
<h3 className="truncate text-xs font-semibold">Open project</h3>
</div>
</motion.button>

<motion.button
whileTap={{ scale: 0.97 }}
transition={{ duration: 0.1, ease: 'easeInOut' }}
onClick={onNewProject}
className="group flex flex-col items-start justify-between rounded-lg border border-border bg-muted/20 p-4 text-card-foreground shadow-sm transition-all hover:bg-muted/40 hover:shadow-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
>
<Plus className="mb-5 h-5 w-5 text-foreground opacity-70" />
<div className="w-full min-w-0 text-left">
<h3 className="truncate text-xs font-semibold">Create New Project</h3>
</div>
</motion.button>

<motion.button
whileTap={{ scale: 0.97 }}
transition={{ duration: 0.1, ease: 'easeInOut' }}
onClick={onCloneProject}
className="group flex flex-col items-start justify-between rounded-lg border border-border bg-muted/20 p-4 text-card-foreground shadow-sm transition-all hover:bg-muted/40 hover:shadow-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
>
<Github className="mb-5 h-5 w-5 text-foreground opacity-70" />
<div className="w-full min-w-0 text-left">
<h3 className="truncate text-xs font-semibold">Clone from GitHub</h3>
</div>
</motion.button>
</div>
</div>
</div>
);
};
133 changes: 133 additions & 0 deletions src/renderer/components/MainContentArea.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import React from 'react';
import type { Provider } from '../types';
import type { Project, Task } from '../types/app';
import ChatInterface from './ChatInterface';
import { HomeView } from './HomeView';
import KanbanBoard from './kanban/KanbanBoard';
import MultiAgentTask from './MultiAgentTask';
import ProjectMainView from './ProjectMainView';

/**
* Main content area component that switches between different views
* Extracted from App.tsx to reduce complexity
*/

export interface MainContentAreaProps {
// View state
showHomeView: boolean;
showKanban: boolean;
selectedProject: Project | null;
activeTask: Task | null;
activeTaskProvider: Provider | null;

// Project branch state
projectBranchOptions: Array<{ value: string; label: string }>;
isLoadingBranches: boolean;
projectDefaultBranch: string;

// Loading states
isCreatingTask: boolean;

// Event handlers
onOpenProject: () => void;
onNewProject: () => void;
onCloneProject: () => void;
onCreateTask: () => void;
onSelectTask: (task: Task) => void;
onDeleteTask: (project: Project, task: Task, options?: { silent?: boolean }) => Promise<boolean>;
onDeleteProject: (project: Project) => Promise<void>;
onBaseBranchChange: (branch: string) => void;
onCloseKanban: () => void;
}

export const MainContentArea: React.FC<MainContentAreaProps> = ({
showHomeView,
showKanban,
selectedProject,
activeTask,
activeTaskProvider,
projectBranchOptions,
isLoadingBranches,
projectDefaultBranch,
isCreatingTask,
onOpenProject,
onNewProject,
onCloneProject,
onCreateTask,
onSelectTask,
onDeleteTask,
onDeleteProject,
onBaseBranchChange,
onCloseKanban,
}) => {
// Kanban view
if (selectedProject && showKanban) {
return (
<div className="flex min-h-0 flex-1 flex-col overflow-hidden">
<KanbanBoard
project={selectedProject}
onOpenTask={(task: any) => {
onSelectTask(task);
onCloseKanban();
}}
onCreateTask={onCreateTask}
/>
</div>
);
}

// Home view
if (showHomeView) {
return (
<HomeView
onOpenProject={onOpenProject}
onNewProject={onNewProject}
onCloneProject={onCloneProject}
/>
);
}

// Project view
if (selectedProject) {
return (
<div className="flex min-h-0 flex-1 flex-col overflow-hidden">
{activeTask ? (
// Active task view
(activeTask.metadata as any)?.multiAgent?.enabled ? (
// Multi-agent task
<MultiAgentTask
task={activeTask}
projectName={selectedProject.name}
projectId={selectedProject.id}
/>
) : (
// Single-agent task with chat interface
<ChatInterface
task={activeTask}
projectName={selectedProject.name}
className="min-h-0 flex-1"
initialProvider={activeTaskProvider || undefined}
/>
)
) : (
// Project main view (no active task)
<ProjectMainView
project={selectedProject}
onCreateTask={onCreateTask}
activeTask={activeTask}
onSelectTask={onSelectTask}
onDeleteTask={onDeleteTask}
isCreatingTask={isCreatingTask}
onDeleteProject={onDeleteProject}
branchOptions={projectBranchOptions}
isLoadingBranches={isLoadingBranches}
onBaseBranchChange={onBaseBranchChange}
/>
)}
</div>
);
}

// Fallback (shouldn't happen)
return null;
};
42 changes: 42 additions & 0 deletions src/renderer/constants/layout.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/**
* Layout constants for the application
* Centralizes all layout-related configuration values
*/

// Panel layout configuration
export const TITLEBAR_HEIGHT = '36px';
export const PANEL_LAYOUT_STORAGE_KEY = 'emdash.layout.left-main-right.v2';
export const DEFAULT_PANEL_LAYOUT: [number, number, number] = [20, 60, 20];

// Left sidebar constraints
export const LEFT_SIDEBAR_MIN_SIZE = 16;
export const LEFT_SIDEBAR_MAX_SIZE = 30;

// Right sidebar constraints
export const RIGHT_SIDEBAR_MIN_SIZE = 16;
export const RIGHT_SIDEBAR_MAX_SIZE = 50;

// Main panel constraints
export const MAIN_PANEL_MIN_SIZE = 30;

// Storage keys
export const FIRST_LAUNCH_KEY = 'emdash:first-launch:v1';
export const PROJECT_ORDER_KEY = 'sidebarProjectOrder';

/**
* Clamps the left sidebar size to valid range
*/
export const clampLeftSidebarSize = (value: number): number =>
Math.min(
Math.max(Number.isFinite(value) ? value : DEFAULT_PANEL_LAYOUT[0], LEFT_SIDEBAR_MIN_SIZE),
LEFT_SIDEBAR_MAX_SIZE
);

/**
* Clamps the right sidebar size to valid range
*/
export const clampRightSidebarSize = (value: number): number =>
Math.min(
Math.max(Number.isFinite(value) ? value : DEFAULT_PANEL_LAYOUT[2], RIGHT_SIDEBAR_MIN_SIZE),
RIGHT_SIDEBAR_MAX_SIZE
);
23 changes: 23 additions & 0 deletions src/renderer/constants/providers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/**
* Provider constants for terminal/agent integrations
* Lists all supported coding agent providers
*/

export const TERMINAL_PROVIDER_IDS = [
'qwen',
'codex',
'claude',
'droid',
'gemini',
'cursor',
'copilot',
'amp',
'opencode',
'charm',
'auggie',
'kimi',
'kiro',
'rovo',
] as const;

export type TerminalProviderId = typeof TERMINAL_PROVIDER_IDS[number];
Loading