129 lines
3.9 KiB
TypeScript
129 lines
3.9 KiB
TypeScript
/**
|
|
* @file "Create branch here…" and "Create tag here…" from a History commit's
|
|
* menu: a name (and, for a tag, an optional message making it annotated),
|
|
* then git-refs.js's `branch-create {branch, from}` / `tag-create {tag,
|
|
* message?, from}` with that commit's sha as `from` — the server confirms the
|
|
* sha itself. Creating a branch never switches to it here. Announces
|
|
* `agenthub:git-changed` so the other views of the workspace read again.
|
|
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
|
*/
|
|
|
|
import { useState, type KeyboardEvent } from "react";
|
|
import { useTranslation } from "react-i18next";
|
|
import { api, type GitTarget } from "../../../lib/api";
|
|
import { toast } from "../../../lib/toast";
|
|
import { isSubmitEnter } from "../../../lib/keys";
|
|
import { emitGitChanged } from "../../../lib/gitChanged";
|
|
import { GitModal, ModalButton } from "./GitModal";
|
|
|
|
export function CreateBranchDialog({
|
|
target,
|
|
sha,
|
|
short,
|
|
kind = "branch",
|
|
onClose,
|
|
onDone,
|
|
}: {
|
|
target: GitTarget;
|
|
sha: string;
|
|
short: string;
|
|
kind?: "branch" | "tag";
|
|
onClose: () => void;
|
|
onDone: () => void;
|
|
}) {
|
|
const { t } = useTranslation(["gitHistory"]);
|
|
const [name, setName] = useState("");
|
|
const [message, setMessage] = useState("");
|
|
const [error, setError] = useState<string | null>(null);
|
|
const [running, setRunning] = useState(false);
|
|
const tag = kind === "tag";
|
|
|
|
const run = async () => {
|
|
const ref = name.trim();
|
|
if (!ref) return;
|
|
setRunning(true);
|
|
setError(null);
|
|
const action = tag ? "tag-create" : "branch-create";
|
|
try {
|
|
await api.lanes.gitRefAction(
|
|
target,
|
|
action,
|
|
tag
|
|
? { tag: ref, from: sha, ...(message.trim() ? { message: message.trim() } : {}) }
|
|
: { branch: ref, from: sha }
|
|
);
|
|
toast.success(t(tag ? "tagCreated" : "branchCreated", { name: ref, branch: ref, short }));
|
|
onDone();
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : String(err));
|
|
setRunning(false);
|
|
} finally {
|
|
emitGitChanged(typeof target === "number" ? target : target.lane, action);
|
|
}
|
|
};
|
|
|
|
const submit = (e: KeyboardEvent) => {
|
|
if (isSubmitEnter(e)) {
|
|
e.preventDefault();
|
|
void run();
|
|
}
|
|
};
|
|
const input =
|
|
"rounded border border-border-light bg-surface-0 px-2 py-1 font-mono text-[12px] text-fg-primary";
|
|
|
|
return (
|
|
<GitModal
|
|
testId="create-ref"
|
|
onClose={onClose}
|
|
title={t(tag ? "createTagTitle" : "createBranchTitle", { short })}
|
|
footer={
|
|
<>
|
|
<ModalButton onClick={onClose}>{t("cancel")}</ModalButton>
|
|
<ModalButton
|
|
primary
|
|
testId="create-ref-confirm"
|
|
disabled={!name.trim() || running}
|
|
onClick={() => void run()}
|
|
>
|
|
{t(tag ? "createTag" : "createBranch")}
|
|
</ModalButton>
|
|
</>
|
|
}
|
|
>
|
|
<label className="flex flex-col gap-1 text-[12px] text-fg-secondary">
|
|
{t(tag ? "tagName" : "branchName")}
|
|
<input
|
|
autoFocus
|
|
data-testid="create-ref-name"
|
|
value={name}
|
|
maxLength={200}
|
|
onChange={(e) => setName(e.target.value)}
|
|
onKeyDown={submit}
|
|
className={input}
|
|
/>
|
|
</label>
|
|
{tag && (
|
|
<label className="mt-2 flex flex-col gap-1 text-[12px] text-fg-secondary">
|
|
{t("tagMessage")}
|
|
<input
|
|
data-testid="create-ref-message"
|
|
value={message}
|
|
maxLength={1000}
|
|
onChange={(e) => setMessage(e.target.value)}
|
|
onKeyDown={submit}
|
|
className={input}
|
|
/>
|
|
</label>
|
|
)}
|
|
<p className="mt-1.5 text-[11px] text-fg-muted">
|
|
{t(tag ? "createTagHint" : "createBranchHint")}
|
|
</p>
|
|
{error && (
|
|
<p role="alert" className="mt-1.5 text-[12px] text-status-danger [overflow-wrap:anywhere]">
|
|
{error}
|
|
</p>
|
|
)}
|
|
</GitModal>
|
|
);
|
|
}
|