import { useQueryClient } from '@tanstack/react-query'; import { GitMergeIcon, GitPullRequestClosedIcon, GitPullRequestDraftIcon, GitPullRequestIcon, PencilIcon, } from 'lucide-react'; import { useState } from 'react'; import { Badge } from '#/components/atoms/badge.jsx'; import { Button } from '#/components/atoms/button.jsx'; import { Input } from '#/components/atoms/input.jsx'; import { Handle } from '#/components/molecules/handle.jsx'; import { PageHead } from '#/components/molecules/page-head.jsx'; import { publishPull } from '#/lib/publish.js'; import { useSession } from '#/lib/session.jsx'; import { cn } from '#/lib/utils.js'; /** * How each state reads. A forge puts this first because it is the one thing a * reader wants before anything else. * * A forge tells them apart by hue; this theme reads by weight, so the icon and * the word carry the difference and the fill only says how settled the state * is. Withdrawn is the exception, because it is the one state a reader should * not mistake for any other, and destructive is the one hue this palette * keeps. */ const STATES = { open: { label: 'Open', Icon: GitPullRequestIcon, className: 'bg-success text-background', }, merged: { label: 'Merged', Icon: GitMergeIcon, className: 'bg-key text-background', }, draft: { label: 'Draft', Icon: GitPullRequestDraftIcon, className: 'border border-input text-muted-foreground', }, withdrawn: { label: 'Withdrawn', Icon: GitPullRequestClosedIcon, className: 'bg-destructive text-background', }, }; /** The words the author may put on their own branch. */ const STATUSES = [ { status: '', label: 'Open' }, { status: 'draft', label: 'Draft' }, { status: 'withdrawn', label: 'Withdrawn' }, ]; /** * What a pull request is, in the order a reader asks: what it is called, what * state it is in, and who is proposing what to where. * * The sentence carries the facts a forge scatters across a sidebar. It is * derived rather than stored, so each part of it is a fact about now: the * count is the commits main lacks, and the two branch names are the ones the * refs carry. * * The title and the state are the two things the author writes rather than the * graph derives, so the author edits them here. The description is the * conversation's opening word and edits with the conversation. The record * holds all three, so a title edit carries the note the author last wrote. */ export function PullHeader({ repo, pull, defaultBranch, wide = false }) { const { session } = useSession(); const queryClient = useQueryClient(); const mine = session?.did === pull.author.did; const [editing, setEditing] = useState(false); const [title, setTitle] = useState(pull.title ?? ''); const [status, setStatus] = useState(pull.status ?? ''); const [busy, setBusy] = useState(false); const [error, setError] = useState(''); const state = STATES[ pull.merged ? 'merged' : pull.status === 'draft' || pull.status === 'withdrawn' ? pull.status : 'open' ]; const verb = pull.merged ? 'merged' : 'wants to merge'; const edit = () => { setTitle(pull.title ?? ''); setStatus(pull.status ?? ''); setError(''); setEditing(true); }; const save = async (event) => { event.preventDefault(); setBusy(true); setError(''); try { await publishPull(session, { repo, ref: `refs/heads/${pull.branch}`, sha: pull.sha, title: title.trim(), status, note: pull.note ?? '', }); setEditing(false); // The list holds every author's words; the next look at it re-reads. queryClient.invalidateQueries({ queryKey: ['pulls', repo] }); } catch (err) { setError(err instanceof Error ? err.message : String(err)); } finally { setBusy(false); } }; if (editing) { return (
{defaultBranch}
from
{pull.branch}
{/* What the sentence above cannot say, because each is a fact about
this one rather than about every pull request. */}
{pull.behind > 0 && (