| ... | ... | @@ -8,11 +8,9 @@ import { queryOptions, useMutation, useQuery, useQueryClient } from "@tanstack/r |
| 8 | 8 | import { useCallback, useEffect, useMemo, useRef, useState } from "react"; |
| 9 | 9 | import { |
| 10 | 10 | CheckIcon, |
| 11 | | CircleIcon, |
| 12 | 11 | CloudUploadIcon, |
| 13 | 12 | GitCommitIcon, |
| 14 | 13 | Loader2Icon, |
| 15 | | MinusIcon, |
| 16 | 14 | XIcon, |
| 17 | 15 | } from "lucide-react"; |
| 18 | 16 | import { GitHubIcon } from "./Icons"; |
| ... | ... | @@ -64,20 +62,11 @@ function GitActionIcon(props: { icon: GitActionIconName; disabled: boolean }) { |
| 64 | 62 | return <GitHubIcon className={`h-5 w-5 shrink-0 ${toneClass}`} />; |
| 65 | 63 | } |
| 66 | 64 | |
| 67 | | function GitProgressStatusIcon(props: { status: GitProgressStepStatus }) { |
| 68 | | if (props.status === "running") { |
| 69 | | return <Loader2Icon className="h-4 w-4 animate-spin text-foreground" />; |
| 70 | | } |
| 71 | | if (props.status === "completed") { |
| 72 | | return <CheckIcon className="h-4 w-4 text-emerald-600 dark:text-emerald-300" />; |
| 73 | | } |
| 74 | | if (props.status === "skipped") { |
| 75 | | return <MinusIcon className="h-4 w-4 text-muted-foreground/70" />; |
| 76 | | } |
| 77 | | if (props.status === "failed") { |
| 78 | | return <XIcon className="h-4 w-4 text-rose-500 dark:text-rose-300" />; |
| 79 | | } |
| 80 | | return <CircleIcon className="h-4 w-4 text-muted-foreground/60" />; |
| 65 | function runningStepMessage(stepId: GitProgressStep["id"]): string { |
| 66 | if (stepId === "generate") return "Generating commit message..."; |
| 67 | if (stepId === "commit") return "Committing changes..."; |
| 68 | if (stepId === "push") return "Pushing branch..."; |
| 69 | return "Preparing pull request..."; |
| 81 | 70 | } |
| 82 | 71 | |
| 83 | 72 | function gitActionModalTitle(): string { |
| ... | ... | @@ -315,6 +304,20 @@ export default function GitActionsControl({ api, gitCwd }: GitActionsControlProp |
| 315 | 304 | gitModalProgress.length > 0 || |
| 316 | 305 | gitModalError !== null || |
| 317 | 306 | gitModalResult !== null); |
| 307 | const activeGitProgressStep = useMemo( |
| 308 | () => gitModalProgress.find((step) => step.status === "running") ?? null, |
| 309 | [gitModalProgress], |
| 310 | ); |
| 311 | const gitProgressToastStatus = isGitModalActionRunning |
| 312 | ? "running" |
| 313 | : gitModalError |
| 314 | ? "failed" |
| 315 | : "done"; |
| 316 | const gitProgressToastMessage = isGitModalActionRunning |
| 317 | ? runningStepMessage(activeGitProgressStep?.id ?? "commit") |
| 318 | : gitModalError |
| 319 | ? "Git action failed." |
| 320 | : "Done."; |
| 318 | 321 | |
| 319 | 322 | const refreshGitStatus = useCallback(async () => { |
| 320 | 323 | if (!api || !gitCwd) return; |
| ... | ... | @@ -803,92 +806,40 @@ export default function GitActionsControl({ api, gitCwd }: GitActionsControlProp |
| 803 | 806 | |
| 804 | 807 | {showGitProgressToast && ( |
| 805 | 808 | <div |
| 806 | | className="fixed bottom-4 right-4 z-[90] w-[min(420px,calc(100vw-2rem))]" |
| 809 | className="fixed bottom-4 right-4 z-[90]" |
| 807 | 810 | role="status" |
| 808 | 811 | aria-live="polite" |
| 809 | 812 | > |
| 810 | | <div className="rounded-2xl border border-border bg-popover/95 p-4 shadow-2xl backdrop-blur"> |
| 811 | | <div className="flex items-start justify-between gap-3"> |
| 812 | | <div className="min-w-0"> |
| 813 | | <p className="text-[11px] uppercase tracking-[0.14em] text-muted-foreground/65"> |
| 814 | | Git actions |
| 815 | | </p> |
| 816 | | <p className="mt-1 text-sm font-medium text-foreground"> |
| 817 | | {isGitModalActionRunning |
| 818 | | ? "Running action..." |
| 819 | | : gitModalError |
| 820 | | ? "Action failed" |
| 821 | | : "Action complete"} |
| 822 | | </p> |
| 823 | | </div> |
| 824 | | <button |
| 825 | | type="button" |
| 826 | | className="rounded-md p-1 text-muted-foreground/60 transition-colors duration-150 hover:bg-accent hover:text-foreground disabled:cursor-not-allowed disabled:opacity-50" |
| 827 | | onClick={dismissGitProgressToast} |
| 828 | | disabled={isGitModalActionRunning} |
| 829 | | aria-label="Dismiss git action progress toast" |
| 830 | | > |
| 831 | | <XIcon className="h-4 w-4" /> |
| 832 | | </button> |
| 833 | | </div> |
| 834 | | {gitModalProgress.length > 0 && ( |
| 835 | | <div className="mt-3 overflow-hidden rounded-xl border border-border/80"> |
| 836 | | {gitModalProgress.map((step, index) => { |
| 837 | | const borderClass = |
| 838 | | index < gitModalProgress.length - 1 ? "border-b border-border/70" : ""; |
| 839 | | |
| 840 | | return ( |
| 841 | | <div |
| 842 | | key={step.id} |
| 843 | | className={`flex items-start gap-3 bg-card/45 px-3.5 py-2.5 ${borderClass}`} |
| 844 | | > |
| 845 | | <span className="mt-0.5"> |
| 846 | | <GitProgressStatusIcon status={step.status} /> |
| 847 | | </span> |
| 848 | | <div className="min-w-0"> |
| 849 | | <p className="text-sm text-foreground">{step.label}</p> |
| 850 | | {step.detail && ( |
| 851 | | <p |
| 852 | | className={`mt-0.5 text-xs ${ |
| 853 | | step.status === "failed" |
| 854 | | ? "text-rose-500 dark:text-rose-300" |
| 855 | | : "text-muted-foreground/70" |
| 856 | | }`} |
| 857 | | > |
| 858 | | {step.detail} |
| 859 | | </p> |
| 860 | | )} |
| 861 | | </div> |
| 862 | | </div> |
| 863 | | ); |
| 864 | | })} |
| 865 | | </div> |
| 866 | | )} |
| 867 | | {gitModalError && ( |
| 868 | | <div className="mt-3 rounded-lg border border-rose-300/50 bg-rose-500/10 px-3 py-2 text-xs text-rose-600 dark:text-rose-200"> |
| 869 | | {gitModalError} |
| 870 | | </div> |
| 871 | | )} |
| 872 | | <div className="mt-3 flex justify-end gap-2"> |
| 873 | | {!isGitModalActionRunning && gitModalResult && gitProgressToastOpenPrUrl && ( |
| 874 | | <button |
| 875 | | type="button" |
| 876 | | className="rounded-xl border border-border px-4 py-2 text-sm text-foreground transition-colors duration-150 hover:bg-accent disabled:cursor-not-allowed disabled:opacity-60" |
| 877 | | onClick={openPrFromToast} |
| 878 | | disabled={isGitModalActionRunning} |
| 879 | | > |
| 880 | | Open PR |
| 881 | | </button> |
| 813 | <div className="flex max-w-[calc(100vw-2rem)] items-center gap-2 rounded-xl border border-border bg-popover/95 px-3 py-2 shadow-xl backdrop-blur"> |
| 814 | <span className="shrink-0"> |
| 815 | {gitProgressToastStatus === "running" ? ( |
| 816 | <Loader2Icon className="h-4 w-4 animate-spin text-foreground" /> |
| 817 | ) : gitProgressToastStatus === "failed" ? ( |
| 818 | <XIcon className="h-4 w-4 text-rose-500 dark:text-rose-300" /> |
| 819 | ) : ( |
| 820 | <CheckIcon className="h-4 w-4 text-emerald-600 dark:text-emerald-300" /> |
| 882 | 821 | )} |
| 822 | </span> |
| 823 | <span className="min-w-0 truncate text-sm text-foreground">{gitProgressToastMessage}</span> |
| 824 | {!isGitModalActionRunning && gitModalResult && gitProgressToastOpenPrUrl && ( |
| 883 | 825 | <button |
| 884 | 826 | type="button" |
| 885 | | className="rounded-xl bg-foreground px-4 py-2 text-sm font-medium text-background transition-colors duration-150 hover:bg-foreground/90 disabled:cursor-not-allowed disabled:opacity-60" |
| 886 | | onClick={dismissGitProgressToast} |
| 827 | className="rounded-md bg-foreground px-2.5 py-1 text-xs font-medium text-background transition-colors duration-150 hover:bg-foreground/90 disabled:cursor-not-allowed disabled:opacity-60" |
| 828 | onClick={openPrFromToast} |
| 887 | 829 | disabled={isGitModalActionRunning} |
| 888 | 830 | > |
| 889 | | Done |
| 831 | Open PR |
| 890 | 832 | </button> |
| 891 | | </div> |
| 833 | )} |
| 834 | <button |
| 835 | type="button" |
| 836 | className="rounded p-1 text-muted-foreground/60 transition-colors duration-150 hover:bg-accent hover:text-foreground disabled:cursor-not-allowed disabled:opacity-50" |
| 837 | onClick={dismissGitProgressToast} |
| 838 | disabled={isGitModalActionRunning} |
| 839 | aria-label="Dismiss git action progress toast" |
| 840 | > |
| 841 | <XIcon className="h-4 w-4" /> |
| 842 | </button> |
| 892 | 843 | </div> |
| 893 | 844 | </div> |
| 894 | 845 | )} |