126 lines · 3.6 KB · TypeScript
RawDownload
import { useAtomValue } from "@effect/atom-react";import { type VcsActionState, type VcsActionTarget } from "@t3tools/client-runtime/state/vcs";import { Atom } from "effect/unstable/reactivity";import { useCallback, useEffect, useRef, useState } from "react"; import { appAtomRegistry } from "./atom-registry";import { vcsActionManager } from "./vcs"; export function useVcsActionState(target: VcsActionTarget): VcsActionState {  return useAtomValue(vcsActionManager.stateAtom(target));} export interface GitActionResultNotification {  readonly type: "success" | "error";  readonly title: string;  readonly description?: string;  readonly prUrl?: string;} const RESULT_DISMISS_MS = 5_000; const gitActionResultAtom = Atom.make<GitActionResultNotification | null>(null).pipe(  Atom.keepAlive,  Atom.withLabel("mobile:git-action-result"),);let dismissTimer: ReturnType<typeof setTimeout> | null = null; function broadcast(result: GitActionResultNotification | null): void {  appAtomRegistry.set(gitActionResultAtom, result);} export function showGitActionResult(result: GitActionResultNotification): void {  if (dismissTimer) clearTimeout(dismissTimer);  broadcast(result);  dismissTimer = setTimeout(() => {    dismissTimer = null;    broadcast(null);  }, RESULT_DISMISS_MS);} export function dismissGitActionResult(): void {  if (dismissTimer) clearTimeout(dismissTimer);  dismissTimer = null;  broadcast(null);} export function useGitActionResultNotification(): {  readonly result: GitActionResultNotification | null;  readonly dismiss: () => void;} {  const result = useAtomValue(gitActionResultAtom);  return { result, dismiss: dismissGitActionResult };} export type GitActionProgressPhase = "idle" | "running" | "success" | "error"; export interface GitActionProgress {  readonly phase: GitActionProgressPhase;  readonly label: string | null;  readonly description: string | null;  readonly prUrl?: string;} const EMPTY_PROGRESS: GitActionProgress = {  phase: "idle",  label: null,  description: null,}; function formatElapsedSeconds(ms: number | null): string | null {  if (ms === null) return null;  const elapsed = Math.max(0, Math.floor((Date.now() - ms) / 1000));  if (elapsed < 2) return null;  return `Running for ${elapsed}s`;} export function useGitActionProgress(target: VcsActionTarget): GitActionProgress {  const actionState = useVcsActionState(target);  const { result } = useGitActionResultNotification();   const [, forceUpdate] = useState(0);  const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);   const startElapsedTimer = useCallback(() => {    if (intervalRef.current) return;    intervalRef.current = setInterval(() => forceUpdate((n) => n + 1), 1000);  }, []);   const stopElapsedTimer = useCallback(() => {    if (intervalRef.current) {      clearInterval(intervalRef.current);      intervalRef.current = null;    }  }, []);   useEffect(() => {    if (actionState.isRunning) {      startElapsedTimer();    } else {      stopElapsedTimer();    }    return stopElapsedTimer;  }, [actionState.isRunning, startElapsedTimer, stopElapsedTimer]);   if (actionState.isRunning) {    const description =      actionState.lastOutputLine ??      formatElapsedSeconds(actionState.hookStartedAtMs ?? actionState.phaseStartedAtMs);    return {      phase: "running",      label: actionState.currentLabel,      description,    };  }   if (result) {    return {      phase: result.type,      label: result.title,      description: result.description ?? null,      prUrl: result.prUrl,    };  }   return EMPTY_PROGRESS;}