c8bc4a95a9
Workspace groups: named, foldable, ordered sidebar sections kept per account (agenthub.workspace.groups); a workspace joins one from its menu, a drop on the header, a drag under a row, or Alt+Up/Down past a group's edge; headers drag to reorder; a folded header shows its most urgent state; New workspace in this group; fold/unfold all. Inner repos: GET /api/lanes/:id/repos + ?repo= on git/fs routes; the Changes panel shows a parent folder's repos only when Settings -> Git -> Changes panel turns it on (agenthub.changes.innerRepos, off by default). Cost: the hook writer adds a session's sub-agent transcripts to its tokens and hours (server/lib/subagent-usage.js) — a sub-agent on the session's own model was never counted live (~8% of a week here); an imported session is re-raised on every pass, so a claude -p run first seen with an empty transcript no longer stays at $0 (~10%). A one-time raise-only catch-up (user_version 5) re-reads ~/.claude/projects. Also: omp install script may redirect to its exact install.redirect URL (#85, repo-links test exempts it), native paste in the terminal.
99 lines
3.5 KiB
TypeScript
99 lines
3.5 KiB
TypeScript
/**
|
|
* @file WorkspaceRowMenu — the sidebar workspace row's context menu, opened at
|
|
* the pointer (right-click) or under the row's `⋯` button. Every item is an
|
|
* existing call: Rename (the row's inline field), New workspace here (the
|
|
* Add modal prefilled), Copy path, and Reset/Remove through `DestructiveLaneModal` — Reset only on a
|
|
* managed lane — plus Move to group (the sidebar's own groups,
|
|
* `lib/workspaceGroups.ts`: one of them, a new one, or out of its group). The
|
|
* server's guards are the real ones; the menu only hides
|
|
* what the lane's kind or tool makes meaningless. The shell (position,
|
|
* Escape / outside-click close) is `ContextMenu`.
|
|
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
|
*/
|
|
|
|
import { useTranslation } from "react-i18next";
|
|
import type { Lane } from "../../lib/types";
|
|
import { Check } from "lucide-react";
|
|
import { ContextMenu, MenuItem, MenuSeparator } from "../ContextMenu";
|
|
import type { WorkspaceGroup } from "../../lib/workspaceGroups";
|
|
import { copyText } from "../../lib/clipboard";
|
|
|
|
export type WorkspaceMenuAction = "rename" | "new-here" | "reset" | "remove";
|
|
|
|
export function WorkspaceRowMenu({
|
|
lane,
|
|
x,
|
|
y,
|
|
onClose,
|
|
onAction,
|
|
groups = [],
|
|
groupId = null,
|
|
onGroup,
|
|
}: {
|
|
lane: Lane;
|
|
x: number;
|
|
y: number;
|
|
onClose: () => void;
|
|
/** Items the tree itself carries out (rename field, modals). */
|
|
onAction: (action: WorkspaceMenuAction) => void;
|
|
/** The sidebar's groups and the one this workspace is in. */
|
|
groups?: WorkspaceGroup[];
|
|
groupId?: string | null;
|
|
/** Move it: to a group's id, out of its group (null), or into a new group ("new"). */
|
|
onGroup?: (target: string | null | "new") => void;
|
|
}) {
|
|
const { t } = useTranslation("lanes");
|
|
return (
|
|
<ContextMenu x={x} y={y} onClose={onClose} testId={`workspace-row-menu-${lane.id}`}>
|
|
<MenuItem onSelect={() => onAction("rename")}>{t("menu.rename")}</MenuItem>
|
|
<MenuItem onSelect={() => onAction("new-here")}>{t("menu.newHere")}</MenuItem>
|
|
<MenuItem
|
|
onSelect={() => {
|
|
onClose();
|
|
void copyText(lane.cwd);
|
|
}}
|
|
>
|
|
{t("menu.copyPath")}
|
|
</MenuItem>
|
|
{onGroup && (
|
|
<>
|
|
<MenuSeparator />
|
|
<div className="px-2 pb-0.5 pt-1 text-[10px] font-semibold uppercase tracking-wide text-fg-muted">
|
|
{t("menu.moveToGroup")}
|
|
</div>
|
|
{groups.map((g) => (
|
|
<MenuItem
|
|
key={g.id}
|
|
testId={`workspace-move-group-${g.id}`}
|
|
disabled={g.id === groupId}
|
|
onSelect={() => onGroup(g.id)}
|
|
>
|
|
<Check className={`h-3 w-3 shrink-0 ${g.id === groupId ? "" : "invisible"}`} />
|
|
<span className="truncate">{g.name}</span>
|
|
</MenuItem>
|
|
))}
|
|
<MenuItem testId="workspace-move-group-new" onSelect={() => onGroup("new")}>
|
|
<span className="w-3 shrink-0" />
|
|
{t("menu.newGroupWith")}
|
|
</MenuItem>
|
|
{groupId && (
|
|
<MenuItem testId="workspace-move-group-none" onSelect={() => onGroup(null)}>
|
|
<span className="w-3 shrink-0" />
|
|
{t("menu.removeFromGroup")}
|
|
</MenuItem>
|
|
)}
|
|
</>
|
|
)}
|
|
<MenuSeparator />
|
|
{lane.kind === "managed" && (
|
|
<MenuItem danger onSelect={() => onAction("reset")}>
|
|
{t("menu.reset")}
|
|
</MenuItem>
|
|
)}
|
|
<MenuItem danger onSelect={() => onAction("remove")}>
|
|
{t("menu.remove")}
|
|
</MenuItem>
|
|
</ContextMenu>
|
|
);
|
|
}
|