agent-ecosystem/src/renderer/components/team/editor/EditorBreadcrumb.tsx
iliya 5b0c7d13fc feat: add project editor with drag & drop file management
- Backend: ProjectFileService with file CRUD, search, git status, file watcher
- IPC: 12 editor channels with security validation and path containment
- Store: editorSlice with multi-tab management, draft persistence, conflict detection
- UI: CodeMirror 6 editor, file tree with DnD, search-in-files, context menus
- Move: fs.rename with EXDEV fallback, full path remapping across all caches
- Tests: comprehensive coverage for services, IPC handlers, store, and utilities
2026-02-28 23:40:41 +02:00

72 lines
2.4 KiB
TypeScript

/**
* Breadcrumb navigation for the active file in the editor.
*
* Each segment is clickable — expands and scrolls the folder in the file tree.
*/
import { useMemo } from 'react';
import { useStore } from '@renderer/store';
import { ChevronRight } from 'lucide-react';
import { getFileIcon } from './fileIcons';
// =============================================================================
// Component
// =============================================================================
export const EditorBreadcrumb = (): React.ReactElement | null => {
const activeTabId = useStore((s) => s.editorActiveTabId);
const projectPath = useStore((s) => s.editorProjectPath);
const expandDirectory = useStore((s) => s.expandDirectory);
const segments = useMemo(() => {
if (!activeTabId || !projectPath) return [];
const relativePath = activeTabId.startsWith(projectPath)
? activeTabId.slice(projectPath.length + 1)
: activeTabId;
return relativePath.split('/');
}, [activeTabId, projectPath]);
if (segments.length === 0) return null;
const fileName = segments[segments.length - 1];
const iconInfo = getFileIcon(fileName);
const Icon = iconInfo.icon;
const handleSegmentClick = (segmentIndex: number): void => {
if (!projectPath) return;
// Build absolute path up to this segment (it's a directory)
const dirSegments = segments.slice(0, segmentIndex + 1);
const dirPath = `${projectPath}/${dirSegments.join('/')}`;
void expandDirectory(dirPath);
};
return (
<div className="flex items-center gap-0.5 overflow-x-auto px-3 py-1 text-xs text-text-muted">
{segments.map((segment, idx) => {
const isLast = idx === segments.length - 1;
return (
<span key={idx} className="flex shrink-0 items-center gap-0.5">
{idx > 0 && <ChevronRight className="text-text-muted/50 size-3" />}
{isLast ? (
<span className="flex items-center gap-1 text-text-secondary">
<Icon className="size-3" style={{ color: iconInfo.color }} />
{segment}
</span>
) : (
<button
onClick={() => handleSegmentClick(idx)}
className="rounded px-0.5 transition-colors hover:bg-surface-raised hover:text-text-secondary"
>
{segment}
</button>
)}
</span>
);
})}
</div>
);
};