chore: add dev branch policy to AGENTS.md
- Document that dev is the default working branch - main is reserved for stable/production merges only
This commit is contained in:
@@ -85,6 +85,13 @@ Before completion, report:
|
|||||||
|
|
||||||
Do not claim completion without verification evidence.
|
Do not claim completion without verification evidence.
|
||||||
|
|
||||||
|
## Git branch policy
|
||||||
|
|
||||||
|
- **Default working branch:** `dev` — all commits and pushes target `dev` unless the user explicitly requests otherwise.
|
||||||
|
- `main` is the stable/production branch; merge to `main` only when explicitly instructed.
|
||||||
|
- After committing, push to `origin/dev`.
|
||||||
|
- If `dev` does not exist locally, create it from `main` or fetch it from origin.
|
||||||
|
|
||||||
## Git workflow
|
## Git workflow
|
||||||
|
|
||||||
### Auto-commit on spec completion
|
### Auto-commit on spec completion
|
||||||
|
|||||||
@@ -10,296 +10,299 @@ import { SyntaxHighlighter } from "./SyntaxHighlighter";
|
|||||||
import { detectLanguage } from "../../../utils/language";
|
import { detectLanguage } from "../../../utils/language";
|
||||||
|
|
||||||
interface GitFileStatus {
|
interface GitFileStatus {
|
||||||
modified: string[];
|
modified: string[];
|
||||||
added: string[];
|
added: string[];
|
||||||
deleted: string[];
|
deleted: string[];
|
||||||
untracked: string[];
|
untracked: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface FileEditorProps {
|
interface FileEditorProps {
|
||||||
projectId: string;
|
projectId: string;
|
||||||
repoId: string;
|
repoId: string;
|
||||||
gitStatus?: GitFileStatus | null;
|
gitStatus?: GitFileStatus | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const FileEditor: React.FC<FileEditorProps> = ({
|
export const FileEditor: React.FC<FileEditorProps> = ({
|
||||||
projectId,
|
projectId,
|
||||||
repoId,
|
repoId,
|
||||||
gitStatus,
|
gitStatus,
|
||||||
}) => {
|
}) => {
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
const { user } = useAuth();
|
const { user } = useAuth();
|
||||||
|
|
||||||
const [mode, setMode] = useState<"view" | "edit">("view");
|
const [mode, setMode] = useState<"view" | "edit">("view");
|
||||||
const [content, setContent] = useState(">");
|
const [content, setContent] = useState(">");
|
||||||
const [originalContent, setOriginalContent] = useState(">");
|
const [originalContent, setOriginalContent] = useState(">");
|
||||||
const [language, setLanguage] = useState("plaintext");
|
const [language, setLanguage] = useState("plaintext");
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [showCommitDialog, setShowCommitDialog] = useState(false);
|
const [showCommitDialog, setShowCommitDialog] = useState(false);
|
||||||
const [isBinary, setIsBinary] = useState(false);
|
const [isBinary, setIsBinary] = useState(false);
|
||||||
const [saving, setSaving] = useState(false);
|
const [saving, setSaving] = useState(false);
|
||||||
|
|
||||||
const handleDiscard = async () => {
|
const handleDiscard = async () => {
|
||||||
if (!filePath) return;
|
if (!filePath) return;
|
||||||
try {
|
try {
|
||||||
const response = await apiClient.get(
|
const response = await apiClient.get(
|
||||||
`/projects/${projectId}/repositories/${repoId}/files/content`,
|
`/projects/${projectId}/repositories/${repoId}/files/content`,
|
||||||
{
|
{
|
||||||
params: {
|
params: {
|
||||||
branch,
|
branch,
|
||||||
path: filePath,
|
path: filePath,
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
const data = response.data;
|
const data = response.data;
|
||||||
if (data.is_binary) {
|
if (data.is_binary) {
|
||||||
setIsBinary(true);
|
setIsBinary(true);
|
||||||
setContent("Binary file - cannot display");
|
setContent("Binary file - cannot display");
|
||||||
setOriginalContent("");
|
setOriginalContent("");
|
||||||
} else {
|
} else {
|
||||||
setIsBinary(false);
|
setIsBinary(false);
|
||||||
setContent(data.content);
|
setContent(data.content);
|
||||||
setOriginalContent(data.content);
|
setOriginalContent(data.content);
|
||||||
}
|
}
|
||||||
setMode("view");
|
setMode("view");
|
||||||
} catch {
|
} catch {
|
||||||
setError("Failed to discard changes");
|
setError("Failed to discard changes");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const branch = searchParams.get("branch") || "main";
|
const branch = searchParams.get("branch") || "main";
|
||||||
const filePath = searchParams.get("file");
|
const filePath = searchParams.get("file");
|
||||||
|
|
||||||
const fileStatus = gitStatus
|
const fileStatus = gitStatus
|
||||||
? gitStatus.modified.includes(filePath || "")
|
? gitStatus.modified.includes(filePath || "")
|
||||||
? "modified"
|
? "modified"
|
||||||
: gitStatus.added.includes(filePath || "")
|
: gitStatus.added.includes(filePath || "")
|
||||||
? "added"
|
? "added"
|
||||||
: gitStatus.deleted.includes(filePath || "")
|
: gitStatus.deleted.includes(filePath || "")
|
||||||
? "deleted"
|
? "deleted"
|
||||||
: gitStatus.untracked.includes(filePath || "")
|
: gitStatus.untracked.includes(filePath || "")
|
||||||
? "untracked"
|
? "untracked"
|
||||||
: undefined
|
: undefined
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
const loadFile = useCallback(async () => {
|
const loadFile = useCallback(async () => {
|
||||||
if (!filePath) {
|
if (!filePath) {
|
||||||
setContent("");
|
setContent("");
|
||||||
setOriginalContent("");
|
setOriginalContent("");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
try {
|
try {
|
||||||
const response = await apiClient.get(
|
const response = await apiClient.get(
|
||||||
`/projects/${projectId}/repositories/${repoId}/files/content`,
|
`/projects/${projectId}/repositories/${repoId}/files/content`,
|
||||||
{
|
{
|
||||||
params: {
|
params: {
|
||||||
branch,
|
branch,
|
||||||
path: filePath,
|
path: filePath,
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
const data = response.data;
|
const data = response.data;
|
||||||
if (data.is_binary) {
|
if (data.is_binary) {
|
||||||
setIsBinary(true);
|
setIsBinary(true);
|
||||||
setContent("Binary file - cannot display");
|
setContent("Binary file - cannot display");
|
||||||
setOriginalContent("");
|
setOriginalContent("");
|
||||||
} else {
|
} else {
|
||||||
setIsBinary(false);
|
setIsBinary(false);
|
||||||
setContent(data.content);
|
setContent(data.content);
|
||||||
setOriginalContent(data.content);
|
setOriginalContent(data.content);
|
||||||
setLanguage(detectLanguage(filePath));
|
setLanguage(detectLanguage(filePath));
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
setError("Failed to load file");
|
setError("Failed to load file");
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
}, [projectId, repoId, branch, filePath]);
|
}, [projectId, repoId, branch, filePath]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
void loadFile();
|
void loadFile();
|
||||||
}, [loadFile]);
|
}, [loadFile]);
|
||||||
|
|
||||||
const handleEdit = () => {
|
const handleEdit = () => {
|
||||||
if (isBinary) return;
|
if (isBinary) return;
|
||||||
setMode("edit");
|
setMode("edit");
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCancel = () => {
|
const handleCancel = () => {
|
||||||
setContent(originalContent);
|
setContent(originalContent);
|
||||||
setMode("view");
|
setMode("view");
|
||||||
setShowCommitDialog(false);
|
setShowCommitDialog(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSave = () => {
|
const handleSave = () => {
|
||||||
if (content === originalContent) {
|
if (content === originalContent) {
|
||||||
setMode("view");
|
setMode("view");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setShowCommitDialog(true);
|
setShowCommitDialog(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCommit = async (message: string) => {
|
const handleCommit = async (message: string) => {
|
||||||
if (!filePath || !user) return;
|
if (!filePath || !user) return;
|
||||||
|
|
||||||
setSaving(true);
|
setSaving(true);
|
||||||
try {
|
try {
|
||||||
await apiClient.post(
|
await apiClient.post(
|
||||||
`/projects/${projectId}/repositories/${repoId}/files/content`,
|
`/projects/${projectId}/repositories/${repoId}/files/content`,
|
||||||
{
|
{
|
||||||
path: filePath,
|
path: filePath,
|
||||||
branch,
|
branch,
|
||||||
content,
|
content,
|
||||||
commit_message: message,
|
commit_message: message,
|
||||||
author_name: user.name || "User",
|
author_name: user.name || "User",
|
||||||
author_email: user.email || "user@example.com",
|
author_email: user.email || "user@example.com",
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
setOriginalContent(content);
|
setOriginalContent(content);
|
||||||
setMode("view");
|
setMode("view");
|
||||||
setShowCommitDialog(false);
|
setShowCommitDialog(false);
|
||||||
} catch {
|
} catch {
|
||||||
setError("Failed to save changes");
|
setError("Failed to save changes");
|
||||||
} finally {
|
} finally {
|
||||||
setSaving(false);
|
setSaving(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Keyboard shortcuts
|
// Keyboard shortcuts
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleKeyDown = (e: KeyboardEvent) => {
|
const handleKeyDown = (e: KeyboardEvent) => {
|
||||||
if ((e.ctrlKey || e.metaKey) && e.key === "e") {
|
if ((e.ctrlKey || e.metaKey) && e.key === "e") {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (mode === "view" && !isBinary) {
|
if (mode === "view" && !isBinary) {
|
||||||
handleEdit();
|
handleEdit();
|
||||||
} else if (mode === "edit") {
|
} else if (mode === "edit") {
|
||||||
handleCancel();
|
handleCancel();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ((e.ctrlKey || e.metaKey) && e.key === "s") {
|
if ((e.ctrlKey || e.metaKey) && e.key === "s") {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (mode === "edit") {
|
if (mode === "edit") {
|
||||||
handleSave();
|
handleSave();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
window.addEventListener("keydown", handleKeyDown);
|
window.addEventListener("keydown", handleKeyDown);
|
||||||
return () => window.removeEventListener("keydown", handleKeyDown);
|
return () => window.removeEventListener("keydown", handleKeyDown);
|
||||||
}, [mode, isBinary, content, originalContent]);
|
}, [mode, isBinary, content, originalContent]);
|
||||||
|
|
||||||
if (!filePath) {
|
if (!filePath) {
|
||||||
return (
|
return (
|
||||||
<div className="file-viewer-empty">
|
<div className="file-viewer-empty">
|
||||||
<p className="muted">Select a file to view its contents</p>
|
<p className="muted">Select a file to view its contents</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loading) return <p className="muted">Loading file...</p>;
|
if (loading) return <p className="muted">Loading file...</p>;
|
||||||
if (error) return <p className="error-text">{error}</p>;
|
if (error) return <p className="error-text">{error}</p>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.fileEditor}>
|
<div className={styles.fileEditor}>
|
||||||
<div className={styles.fileEditorToolbar}>
|
<div className={styles.fileEditorToolbar}>
|
||||||
<div className="file-breadcrumbs">
|
<div className="file-breadcrumbs">
|
||||||
{filePath?.split("/").map((part, i, arr) => (
|
{filePath?.split("/").map((part, i, arr) => (
|
||||||
<span key={i}>
|
<span key={i}>
|
||||||
{part}
|
{part}
|
||||||
{i < arr.length - 1 && (
|
{i < arr.length - 1 && <span className="breadcrumb-sep">/</span>}
|
||||||
<span className="breadcrumb-sep">/</span>
|
</span>
|
||||||
)}
|
))}
|
||||||
</span>
|
</div>
|
||||||
))}
|
<div className={styles.fileActions}>
|
||||||
</div>
|
{fileStatus && (
|
||||||
<div className={styles.fileActions}>
|
<span
|
||||||
{fileStatus && (
|
className={`git-status-badge ${fileStatus}`}
|
||||||
<span className={`git-status-badge ${fileStatus}`} title={fileStatus}>
|
title={fileStatus}
|
||||||
{fileStatus === "modified" ? "M" : fileStatus === "added" ? "A" : fileStatus === "deleted" ? "D" : "?"}
|
>
|
||||||
</span>
|
{fileStatus === "modified"
|
||||||
)}
|
? "M"
|
||||||
{mode === "view" && !isBinary && (
|
: fileStatus === "added"
|
||||||
<button
|
? "A"
|
||||||
className="btn-primary"
|
: fileStatus === "deleted"
|
||||||
onClick={handleEdit}
|
? "D"
|
||||||
type="button"
|
: "?"}
|
||||||
>
|
</span>
|
||||||
<Icon name="edit" size="sm" />
|
)}
|
||||||
Edit
|
{mode === "view" && !isBinary && (
|
||||||
</button>
|
<button className="btn-primary" onClick={handleEdit} type="button">
|
||||||
)}
|
<Icon name="edit" size="sm" />
|
||||||
{mode === "edit" && (
|
Edit
|
||||||
<>
|
</button>
|
||||||
<button
|
)}
|
||||||
className="btn-primary"
|
{mode === "edit" && (
|
||||||
onClick={handleSave}
|
<>
|
||||||
disabled={content === originalContent || saving}
|
<button
|
||||||
type="button"
|
className="btn-primary"
|
||||||
>
|
onClick={handleSave}
|
||||||
{saving ? (
|
disabled={content === originalContent || saving}
|
||||||
<>
|
type="button"
|
||||||
<Icon name="loading" size="sm" />
|
>
|
||||||
Saving...
|
{saving ? (
|
||||||
</>
|
<>
|
||||||
) : (
|
<Icon name="loading" size="sm" />
|
||||||
<>
|
Saving...
|
||||||
<Icon name="save" size="sm" />
|
</>
|
||||||
Save
|
) : (
|
||||||
</>
|
<>
|
||||||
)}
|
<Icon name="save" size="sm" />
|
||||||
</button>
|
Save
|
||||||
<button
|
</>
|
||||||
className="btn-secondary"
|
)}
|
||||||
onClick={handleDiscard}
|
</button>
|
||||||
type="button"
|
<button
|
||||||
title="Revert to last committed version"
|
className="btn-secondary"
|
||||||
>
|
onClick={handleDiscard}
|
||||||
<Icon name="undo" size="sm" />
|
type="button"
|
||||||
Discard
|
title="Revert to last committed version"
|
||||||
</button>
|
>
|
||||||
<button
|
<Icon name="undo" size="sm" />
|
||||||
className="btn-secondary"
|
Discard
|
||||||
onClick={handleCancel}
|
</button>
|
||||||
type="button"
|
<button
|
||||||
>
|
className="btn-secondary"
|
||||||
<Icon name="cancel" size="sm" />
|
onClick={handleCancel}
|
||||||
Cancel
|
type="button"
|
||||||
</button>
|
>
|
||||||
</>
|
<Icon name="cancel" size="sm" />
|
||||||
)}
|
Cancel
|
||||||
</div>
|
</button>
|
||||||
</div>
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className={styles.fileEditorContent}>
|
<div className={styles.fileEditorContent}>
|
||||||
{mode === "view" && (
|
{mode === "view" && (
|
||||||
<SyntaxHighlighter
|
<SyntaxHighlighter
|
||||||
code={content}
|
code={content}
|
||||||
language={language}
|
language={language}
|
||||||
showLineNumbers={!isBinary}
|
showLineNumbers={!isBinary}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{mode === "edit" && (
|
{mode === "edit" && (
|
||||||
<CodeEditor
|
<CodeEditor
|
||||||
value={content}
|
value={content}
|
||||||
onChange={setContent}
|
onChange={setContent}
|
||||||
language={language}
|
language={language}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<CommitDialog
|
<CommitDialog
|
||||||
isOpen={showCommitDialog}
|
isOpen={showCommitDialog}
|
||||||
filePath={filePath}
|
filePath={filePath}
|
||||||
originalContent={originalContent}
|
originalContent={originalContent}
|
||||||
newContent={content}
|
newContent={content}
|
||||||
onCommit={handleCommit}
|
onCommit={handleCommit}
|
||||||
onCancel={() => setShowCommitDialog(false)}
|
onCancel={() => setShowCommitDialog(false)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
+154
-148
@@ -1,167 +1,173 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import {
|
import {
|
||||||
House,
|
House,
|
||||||
Folder,
|
Folder,
|
||||||
GitBranch,
|
GitBranch,
|
||||||
Gear,
|
Gear,
|
||||||
User,
|
User,
|
||||||
SignOut,
|
SignOut,
|
||||||
Plus,
|
Plus,
|
||||||
PencilSimple,
|
PencilSimple,
|
||||||
Trash,
|
Trash,
|
||||||
FloppyDisk,
|
FloppyDisk,
|
||||||
X,
|
X,
|
||||||
ArrowsClockwise,
|
ArrowsClockwise,
|
||||||
Copy,
|
Copy,
|
||||||
MagnifyingGlass,
|
MagnifyingGlass,
|
||||||
List,
|
List,
|
||||||
Check,
|
Check,
|
||||||
Warning,
|
Warning,
|
||||||
Info,
|
Info,
|
||||||
Spinner,
|
Spinner,
|
||||||
GitCommit,
|
GitCommit,
|
||||||
GitMerge,
|
GitMerge,
|
||||||
ClockCounterClockwise,
|
ClockCounterClockwise,
|
||||||
ArrowDown,
|
ArrowDown,
|
||||||
ArrowUp,
|
ArrowUp,
|
||||||
File,
|
File,
|
||||||
FileText,
|
FileText,
|
||||||
Image,
|
Image,
|
||||||
Binary,
|
Binary,
|
||||||
Code,
|
Code,
|
||||||
ArrowSquareOut,
|
ArrowSquareOut,
|
||||||
Play,
|
Play,
|
||||||
Stop,
|
Stop,
|
||||||
Terminal,
|
Terminal,
|
||||||
ArrowLeft,
|
ArrowLeft,
|
||||||
} from "@phosphor-icons/react";
|
} from "@phosphor-icons/react";
|
||||||
|
|
||||||
export type IconName =
|
export type IconName =
|
||||||
| "dashboard"
|
| "dashboard"
|
||||||
| "projects"
|
| "projects"
|
||||||
| "repositories"
|
| "repositories"
|
||||||
| "settings"
|
| "settings"
|
||||||
| "profile"
|
| "profile"
|
||||||
| "logout"
|
| "logout"
|
||||||
| "add"
|
| "add"
|
||||||
| "edit"
|
| "edit"
|
||||||
| "delete"
|
| "delete"
|
||||||
| "save"
|
| "save"
|
||||||
| "cancel"
|
| "cancel"
|
||||||
| "refresh"
|
| "refresh"
|
||||||
| "copy"
|
| "copy"
|
||||||
| "search"
|
| "search"
|
||||||
| "menu"
|
| "menu"
|
||||||
| "close"
|
| "close"
|
||||||
| "success"
|
| "success"
|
||||||
| "error"
|
| "error"
|
||||||
| "warning"
|
| "warning"
|
||||||
| "info"
|
| "info"
|
||||||
| "loading"
|
| "loading"
|
||||||
| "branch"
|
| "branch"
|
||||||
| "commit"
|
| "commit"
|
||||||
| "merge"
|
| "merge"
|
||||||
| "history"
|
| "history"
|
||||||
| "pull"
|
| "pull"
|
||||||
| "push"
|
| "push"
|
||||||
| "fetch"
|
| "fetch"
|
||||||
| "file"
|
| "file"
|
||||||
| "folder"
|
| "folder"
|
||||||
| "code"
|
| "code"
|
||||||
| "document"
|
| "document"
|
||||||
| "image"
|
| "image"
|
||||||
| "binary"
|
| "binary"
|
||||||
| "external"
|
| "external"
|
||||||
| "play"
|
| "play"
|
||||||
| "stop"
|
| "stop"
|
||||||
| "terminal"
|
| "terminal"
|
||||||
| "arrow-left"
|
| "arrow-left"
|
||||||
| "undo";
|
| "undo";
|
||||||
|
|
||||||
const iconMap: Record<IconName, React.ComponentType<{ size?: number | string; weight?: "thin" | "light" | "regular" | "bold" | "fill" | "duotone" }>> = {
|
const iconMap: Record<
|
||||||
dashboard: House,
|
IconName,
|
||||||
projects: Folder,
|
React.ComponentType<{
|
||||||
repositories: GitBranch,
|
size?: number | string;
|
||||||
settings: Gear,
|
weight?: "thin" | "light" | "regular" | "bold" | "fill" | "duotone";
|
||||||
profile: User,
|
}>
|
||||||
logout: SignOut,
|
> = {
|
||||||
add: Plus,
|
dashboard: House,
|
||||||
edit: PencilSimple,
|
projects: Folder,
|
||||||
delete: Trash,
|
repositories: GitBranch,
|
||||||
save: FloppyDisk,
|
settings: Gear,
|
||||||
cancel: X,
|
profile: User,
|
||||||
refresh: ArrowsClockwise,
|
logout: SignOut,
|
||||||
copy: Copy,
|
add: Plus,
|
||||||
search: MagnifyingGlass,
|
edit: PencilSimple,
|
||||||
menu: List,
|
delete: Trash,
|
||||||
close: X,
|
save: FloppyDisk,
|
||||||
success: Check,
|
cancel: X,
|
||||||
error: X,
|
refresh: ArrowsClockwise,
|
||||||
warning: Warning,
|
copy: Copy,
|
||||||
info: Info,
|
search: MagnifyingGlass,
|
||||||
loading: Spinner,
|
menu: List,
|
||||||
branch: GitBranch,
|
close: X,
|
||||||
commit: GitCommit,
|
success: Check,
|
||||||
merge: GitMerge,
|
error: X,
|
||||||
history: ClockCounterClockwise,
|
warning: Warning,
|
||||||
pull: ArrowDown,
|
info: Info,
|
||||||
push: ArrowUp,
|
loading: Spinner,
|
||||||
fetch: ArrowsClockwise,
|
branch: GitBranch,
|
||||||
file: File,
|
commit: GitCommit,
|
||||||
folder: Folder,
|
merge: GitMerge,
|
||||||
code: Code,
|
history: ClockCounterClockwise,
|
||||||
document: FileText,
|
pull: ArrowDown,
|
||||||
image: Image,
|
push: ArrowUp,
|
||||||
binary: Binary,
|
fetch: ArrowsClockwise,
|
||||||
external: ArrowSquareOut,
|
file: File,
|
||||||
play: Play,
|
folder: Folder,
|
||||||
stop: Stop,
|
code: Code,
|
||||||
terminal: Terminal,
|
document: FileText,
|
||||||
"arrow-left": ArrowLeft,
|
image: Image,
|
||||||
undo: ClockCounterClockwise,
|
binary: Binary,
|
||||||
|
external: ArrowSquareOut,
|
||||||
|
play: Play,
|
||||||
|
stop: Stop,
|
||||||
|
terminal: Terminal,
|
||||||
|
"arrow-left": ArrowLeft,
|
||||||
|
undo: ClockCounterClockwise,
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface IconProps {
|
export interface IconProps {
|
||||||
name: IconName;
|
name: IconName;
|
||||||
size?: "sm" | "md" | "lg" | "xl";
|
size?: "sm" | "md" | "lg" | "xl";
|
||||||
color?: string;
|
color?: string;
|
||||||
weight?: "thin" | "light" | "regular" | "bold" | "fill" | "duotone";
|
weight?: "thin" | "light" | "regular" | "bold" | "fill" | "duotone";
|
||||||
className?: string;
|
className?: string;
|
||||||
ariaLabel?: string;
|
ariaLabel?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const sizeMap: Record<NonNullable<IconProps["size"]>, number> = {
|
const sizeMap: Record<NonNullable<IconProps["size"]>, number> = {
|
||||||
sm: 16,
|
sm: 16,
|
||||||
md: 20,
|
md: 20,
|
||||||
lg: 24,
|
lg: 24,
|
||||||
xl: 32,
|
xl: 32,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Icon: React.FC<IconProps> = ({
|
export const Icon: React.FC<IconProps> = ({
|
||||||
name,
|
name,
|
||||||
size = "md",
|
size = "md",
|
||||||
color,
|
color,
|
||||||
weight = "regular",
|
weight = "regular",
|
||||||
className,
|
className,
|
||||||
ariaLabel,
|
ariaLabel,
|
||||||
}) => {
|
}) => {
|
||||||
const IconComponent = iconMap[name];
|
const IconComponent = iconMap[name];
|
||||||
const sizeValue = sizeMap[size];
|
const sizeValue = sizeMap[size];
|
||||||
|
|
||||||
if (!IconComponent) {
|
if (!IconComponent) {
|
||||||
console.warn(`Icon "${name}" not found`);
|
console.warn(`Icon "${name}" not found`);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span
|
<span
|
||||||
className={`icon icon-${size}${className ? ` ${className}` : ""}`}
|
className={`icon icon-${size}${className ? ` ${className}` : ""}`}
|
||||||
style={{ color }}
|
style={{ color }}
|
||||||
aria-label={ariaLabel}
|
aria-label={ariaLabel}
|
||||||
aria-hidden={!ariaLabel}
|
aria-hidden={!ariaLabel}
|
||||||
role="img"
|
role="img"
|
||||||
>
|
>
|
||||||
<IconComponent size={sizeValue} weight={weight} />
|
<IconComponent size={sizeValue} weight={weight} />
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -198,7 +198,11 @@ export const RepoWorkspace = () => {
|
|||||||
)}
|
)}
|
||||||
<main className="workspace-main">
|
<main className="workspace-main">
|
||||||
{selectedRepoId && (
|
{selectedRepoId && (
|
||||||
<FileEditor projectId={projectId!} repoId={selectedRepoId} gitStatus={gitStatus} />
|
<FileEditor
|
||||||
|
projectId={projectId!}
|
||||||
|
repoId={selectedRepoId}
|
||||||
|
gitStatus={gitStatus}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user