BranchActions

tcmts.studio.BranchActions · #2hew7dmp · term · runs in the browser

uses useState

What to do with another branch: merge it into the studio's own (tcm merge; on main, that lands it, with its tests run first), as one entry if asked, with what it's for; work on it (tcm switch, where the studio can); delete it (tcm branch --delete), asking first, unless it's to keep (main, a session an agent is working on).

import { navigate } from "#codebase/tcmts/react";
import { at, toasts } from "#codebase/tcmts/share";
import { removeBranch, startMerge, switchBranch, toldOf } from "#codebase/tcmts/studio";
import { useState } from "react";
import {
  Button, Checkbox, Dialog, DialogTrigger, Heading, Label, Popover, TextArea, TextField,
} from "react-aria-components";

export function BranchActions({
  name,
  own,
  canSwitch,
  merging,
  keep = false,
}: {
  name: string;
  own: string;
  canSwitch: boolean;
  merging: boolean;
  keep?: boolean;
}) {
  const landing = own === "main";
  const [message, setMessage] = useState("");
  const [squash, setSquash] = useState(/^(session|agent)\//.test(name));
  const [busy, setBusy] = useState(false);
  return (
    <span className="actions rowacts">
      {!merging && (
        <DialogTrigger>
          <Button className="btn small">{landing ? "Land it" : "Merge it in"}</Button>
          <Popover className="menu-pop getit" placement="bottom end" offset={6}>
            <Dialog className="getit-body" aria-label={`Merge ${name}`}>
              {({ close }) => (
                <form
                  className="prodform"
                  onSubmit={async (e) => {
                    e.preventDefault();
                    setBusy(true);
                    try {
                      await startMerge(name, squash, message);
                      close();
                      navigate(at("/branches"));
                    } catch (err) {
                      toasts.add(
                        {
                          title: `Couldn't merge ${name}`,
                          description: (err as Error).message,
                          tone: "bad",
                        },
                        { timeout: 9000 },
                      );
                    }
                    setBusy(false);
                  }}
                >
                  <Heading slot="title" className="getit-title">
                    {landing ? `Land ${name} on main` : `Merge ${name} into ${own}`}
                  </Heading>
                  <p>
                    {landing
                      ? "main is merged into it first, there, then its tests run; it lands only if they pass. One that conflicts or stops typechecking doesn't land: settle it on its branch."
                      : `What conflicts, or stops typechecking, waits apart: ${own} stays as it is until Needs fixing has nothing left.`}
                  </p>
                  <TextField className="fieldrow" value={message} onChange={setMessage}>
                    <Label>What it's for</Label>
                    <TextArea className="field" rows={3} placeholder="A line saying what and why" />
                  </TextField>
                  <Checkbox className="check" isSelected={squash} onChange={setSquash}>
                    <span className="box-mark" aria-hidden="true" />
                    As one entry (its own entries stay on its branch)
                  </Checkbox>
                  <div className="actions">
                    <Button className="btn" onPress={close}>
                      Cancel
                    </Button>
                    <Button className="btn primary" type="submit" isPending={busy}>
                      {landing ? "Land it" : "Merge"}
                    </Button>
                  </div>
                </form>
              )}
            </Dialog>
          </Popover>
        </DialogTrigger>
      )}
      {canSwitch && (
        <Button
          className="btn small"
          onPress={() =>
            void toldOf(switchBranch(name), `Working on ${name}`, `Couldn't switch to ${name}`)
          }
        >
          Work on it
        </Button>
      )}
      {!keep && (
        <DialogTrigger>
          <Button className="btn small danger">Delete</Button>
          <Popover className="menu-pop getit" placement="bottom end" offset={6}>
            <Dialog className="getit-body" aria-label={`Delete ${name}`}>
              {({ close }) => (
                <>
                  <Heading slot="title" className="getit-title">
                    Delete {name}?
                  </Heading>
                  <p>
                    Its names go; the history it made stays (what was merged from it is kept where
                    it was merged).
                  </p>
                  <div className="actions">
                    <Button className="btn" onPress={close}>
                      Keep it
                    </Button>
                    <Button
                      className="btn danger"
                      onPress={() => {
                        close();
                        void toldOf(
                          removeBranch(name),
                          `Deleted ${name}`,
                          `Couldn't delete ${name}`,
                        );
                      }}
                    >
                      Delete
                    </Button>
                  </div>
                </>
              )}
            </Dialog>
          </Popover>
        </DialogTrigger>
      )}
    </span>
  );
}

uses

used by