import React, { useCallback, useEffect, useState } from "react"; import { useSearchParams } from "react-router-dom"; import { apiClient } from "../../../api/client"; import { useAuth } from "../../../state/auth"; import { CodeEditor } from "../../code-editor"; import { CommitDialog } from "./commit-dialog"; import { Icon } from "../../icon"; import { SyntaxHighlighter } from "../../syntax-highlighter"; import { detectLanguage } from "../../../utils/language"; interface FileEditorProps { projectId: string; repoId: string; } export const FileEditor: React.FC = ({ projectId, repoId, }) => { const [searchParams] = useSearchParams(); const { user } = useAuth(); const [mode, setMode] = useState<"view" | "edit">("view"); const [content, setContent] = useState(">"); const [originalContent, setOriginalContent] = useState(">"); const [language, setLanguage] = useState("plaintext"); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [showCommitDialog, setShowCommitDialog] = useState(false); const [isBinary, setIsBinary] = useState(false); const [saving, setSaving] = useState(false); const branch = searchParams.get("branch") || "main"; const filePath = searchParams.get("file"); const loadFile = useCallback(async () => { if (!filePath) { setContent(""); setOriginalContent(""); return; } setLoading(true); setError(null); try { const response = await apiClient.get( `/projects/${projectId}/repositories/${repoId}/files/content`, { params: { branch, path: filePath, }, } ); const data = response.data; if (data.is_binary) { setIsBinary(true); setContent("Binary file - cannot display"); setOriginalContent(""); } else { setIsBinary(false); setContent(data.content); setOriginalContent(data.content); setLanguage(detectLanguage(filePath)); } } catch { setError("Failed to load file"); } finally { setLoading(false); } }, [projectId, repoId, branch, filePath]); useEffect(() => { void loadFile(); }, [loadFile]); const handleEdit = () => { if (isBinary) return; setMode("edit"); }; const handleCancel = () => { setContent(originalContent); setMode("view"); setShowCommitDialog(false); }; const handleSave = () => { if (content === originalContent) { setMode("view"); return; } setShowCommitDialog(true); }; const handleCommit = async (message: string) => { if (!filePath || !user) return; setSaving(true); try { await apiClient.post( `/projects/${projectId}/repositories/${repoId}/files/content`, { path: filePath, branch, content, commit_message: message, author_name: user.name || "User", author_email: user.email || "user@example.com", } ); setOriginalContent(content); setMode("view"); setShowCommitDialog(false); } catch { setError("Failed to save changes"); } finally { setSaving(false); } }; // Keyboard shortcuts useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if ((e.ctrlKey || e.metaKey) && e.key === "e") { e.preventDefault(); if (mode === "view" && !isBinary) { handleEdit(); } else if (mode === "edit") { handleCancel(); } } if ((e.ctrlKey || e.metaKey) && e.key === "s") { e.preventDefault(); if (mode === "edit") { handleSave(); } } }; window.addEventListener("keydown", handleKeyDown); return () => window.removeEventListener("keydown", handleKeyDown); }, [mode, isBinary, content, originalContent]); if (!filePath) { return (

Select a file to view its contents

); } if (loading) return

Loading file...

; if (error) return

{error}

; return (
{filePath.split("/").map((part, i, arr) => ( {part} {i < arr.length - 1 && ( / )} ))}
{mode === "view" && !isBinary && ( )} {mode === "edit" && ( <> )}
{mode === "view" && ( )} {mode === "edit" && ( )}
setShowCommitDialog(false)} />
); };