/* Shared icons, FileIcon, GitPanel, FileTree */
const { useState, useEffect, useRef, useMemo, useCallback } = React;
/* ---- minimal geometric icons ---- */
const Icon = {
search: (p) => (),
branch: (p) => (),
close: (p) => (),
copy: (p) => (),
terminal: (p) => (),
spark: (p) => (),
file: (p) => (),
reveal: (p) => (),
diff: (p) => (),
plus: (p) => (),
minus: (p) => (),
check: (p) => (),
discard: (p) => (),
};
const Chevron = ({ open }) => (
);
const FolderIcon = ({ open }) => (
);
function FileIcon({ path }) {
const ic = HL.iconFor(path);
return {ic.t};
}
/* ============ Git / Source Control panel ============ */
function GitRow({ c, staged, activePath, onOpen, onContext, onToggleStage }) {
const name = c.path.split("/").pop();
const dir = c.path.split("/").slice(0, -1).join("/");
return (
onOpen(c.path, { diff: true })}
onContextMenu={(e) => onContext(e, { path: c.path, kind: "git", staged })}
title={c.path}>
{c.status}
{name}
{dir && {dir}/}
{c.add > 0 && +{c.add}}
{c.del > 0 && -{c.del}}
);
}
function GitPanel({ changes, staged, committed, commitMsg, setCommitMsg, onStage, onUnstage, onStageAll, onUnstageAll, onCommit, onOpen, onContext, activePath }) {
const visible = changes.filter((c) => !committed.has(c.path));
const stagedList = visible.filter((c) => staged.has(c.path));
const changesList = visible.filter((c) => !staged.has(c.path));
const totals = visible.reduce((a, c) => ({ add: a.add + c.add, del: a.del + c.del }), { add: 0, del: 0 });
const canCommit = stagedList.length > 0 && commitMsg.trim().length > 0;
return (
{Icon.branch({})}Source Control
{visible.length}
{visible.length === 0 ? (
{Icon.check({ width: 20, height: 20 })}No changes — working tree clean
) : (
Staged Changes {stagedList.length}
{stagedList.length > 0 && }
{stagedList.length > 0 ? stagedList.map((c) => (
)) : (
Nothing staged — use + to stage a file
)}
Changes {changesList.length}
{changesList.length > 0 && }
{changesList.length > 0 ? changesList.map((c) => (
)) : (
All changes staged
)}
)}
{Icon.branch({})}{PROJECT.branch}
+{totals.add}{" "}
-{totals.del}
);
}
/* ============ File Tree ============ */
function TreeNode({ node, depth, openDirs, toggleDir, onOpen, onContext, activePath, changeMap, committed }) {
const pad = 10 + depth * 13;
if (node.type === "dir") {
const isOpen = openDirs.has(node.path) || node.path === "";
return (
{node.path !== "" && (
toggleDir(node.path)}
onContextMenu={(e) => onContext(e, { path: node.path, kind: "dir" })}>
{node.name}
)}
{isOpen && node.children.map((c) => (
))}
);
}
const status = committed && committed.has(node.path) ? null : changeMap[node.path];
return (
onOpen(node.path)}
onContextMenu={(e) => onContext(e, { path: node.path, kind: "file" })}
title={node.path}>
{node.name}
{status && {status}}
);
}
function FileTree({ tree, openDirs, toggleDir, onOpen, onContext, activePath, changeMap, committed }) {
return (
Explorer
{tree.name}
);
}
Object.assign(window, { Icon, Chevron, FolderIcon, FileIcon, GitPanel, FileTree });