Julius MarmingeT3 Code Mobile [WIP] (#2013)b3e8c035 months agoHistory
249 lines · 7.8 KB · TypeScript
RawDownload
import { scopeProjectRef, scopedThreadKey, scopeThreadRef } from "@t3tools/client-runtime";import type { VcsStatusResult } from "@t3tools/contracts";import { CloudIcon, GitPullRequestIcon, TerminalIcon } from "lucide-react";import { useMemo } from "react";import { usePrimaryEnvironmentId } from "../environments/primary";import {  useSavedEnvironmentRegistryStore,  useSavedEnvironmentRuntimeStore,} from "../environments/runtime";import { useVcsStatus } from "../lib/vcsStatusState";import { type AppState, selectProjectByRef, useStore } from "../store";import { useThreadRunningTerminalIds } from "../terminalSessionState";import { useUiStateStore } from "../uiStateStore";import { resolveChangeRequestPresentation } from "../sourceControlPresentation";import { resolveThreadStatusPill, type ThreadStatusPill } from "./Sidebar.logic";import type { SidebarThreadSummary } from "../types";import { Tooltip, TooltipPopup, TooltipTrigger } from "./ui/tooltip"; export interface PrStatusIndicator {  label: string;  colorClass: string;  tooltip: string;  url: string;} export interface TerminalStatusIndicator {  label: "Terminal process running";  colorClass: string;  pulse: boolean;} export type ThreadPr = VcsStatusResult["pr"]; export function prStatusIndicator(  pr: ThreadPr,  provider: VcsStatusResult["sourceControlProvider"] | null | undefined,): PrStatusIndicator | null {  if (!pr) return null;  const presentation = resolveChangeRequestPresentation(provider);   if (pr.state === "open") {    return {      label: `${presentation.shortName} open`,      colorClass: "text-emerald-600 dark:text-emerald-300/90",      tooltip: `#${pr.number} ${presentation.shortName} open: ${pr.title}`,      url: pr.url,    };  }  if (pr.state === "closed") {    return {      label: `${presentation.shortName} closed`,      colorClass: "text-zinc-500 dark:text-zinc-400/80",      tooltip: `#${pr.number} ${presentation.shortName} closed: ${pr.title}`,      url: pr.url,    };  }  if (pr.state === "merged") {    return {      label: `${presentation.shortName} merged`,      colorClass: "text-violet-600 dark:text-violet-300/90",      tooltip: `#${pr.number} ${presentation.shortName} merged: ${pr.title}`,      url: pr.url,    };  }  return null;} export function ChangeRequestStatusIcon({ className }: { className?: string }) {  return <GitPullRequestIcon className={className} />;} export function resolveThreadPr(  threadBranch: string | null,  gitStatus: VcsStatusResult | null,): ThreadPr | null {  if (threadBranch === null || gitStatus === null || gitStatus.refName !== threadBranch) {    return null;  }   return gitStatus.pr ?? null;} export function terminalStatusFromRunningIds(  runningTerminalIds: ReadonlyArray<string>,): TerminalStatusIndicator | null {  if (runningTerminalIds.length === 0) {    return null;  }  return {    label: "Terminal process running",    colorClass: "text-teal-600 dark:text-teal-300/90",    pulse: true,  };} export function ThreadStatusLabel({  status,  compact = false,}: {  status: ThreadStatusPill;  compact?: boolean;}) {  if (compact) {    return (      <span        title={status.label}        className={`inline-flex size-3.5 shrink-0 items-center justify-center ${status.colorClass}`}      >        <span          className={`size-[9px] rounded-full ${status.dotClass} ${            status.pulse ? "animate-pulse" : ""          }`}        />        <span className="sr-only">{status.label}</span>      </span>    );  }   return (    <span      title={status.label}      className={`inline-flex items-center gap-1 text-[10px] ${status.colorClass}`}    >      <span        className={`h-1.5 w-1.5 rounded-full ${status.dotClass} ${          status.pulse ? "animate-pulse" : ""        }`}      />      <span className="hidden md:inline">{status.label}</span>    </span>  );} /** * Non-interactive leading status icons for a thread row in compact contexts * like the command palette. Shows the change request state icon (if present) and the * thread status dot, matching the sidebar's leading indicators. */export function ThreadRowLeadingStatus({ thread }: { thread: SidebarThreadSummary }) {  const threadRef = scopeThreadRef(thread.environmentId, thread.id);  const lastVisitedAt = useUiStateStore(    (state) => state.threadLastVisitedAtById[scopedThreadKey(threadRef)],  );  const threadProjectCwd = useStore(    useMemo(      () => (state: AppState) =>        selectProjectByRef(state, scopeProjectRef(thread.environmentId, thread.projectId))?.cwd ??        null,      [thread.environmentId, thread.projectId],    ),  );  const gitCwd = thread.worktreePath ?? threadProjectCwd;  const gitStatus = useVcsStatus({    environmentId: thread.environmentId,    cwd: thread.branch != null ? gitCwd : null,  });  const pr = resolveThreadPr(thread.branch, gitStatus.data);  const prStatus = prStatusIndicator(pr, gitStatus.data?.sourceControlProvider);  const threadStatus = resolveThreadStatusPill({    thread: {      ...thread,      lastVisitedAt,    },  });   if (!prStatus && !threadStatus) {    return null;  }   return (    <span className="inline-flex shrink-0 items-center gap-1.5">      {prStatus ? (        <Tooltip>          <TooltipTrigger            render={              <span                aria-label={prStatus.tooltip}                className={`inline-flex items-center justify-center ${prStatus.colorClass}`}              />            }          >            <ChangeRequestStatusIcon className="size-3" />          </TooltipTrigger>          <TooltipPopup side="top">{prStatus.tooltip}</TooltipPopup>        </Tooltip>      ) : null}      {threadStatus ? <ThreadStatusLabel status={threadStatus} /> : null}    </span>  );} /** * Non-interactive trailing status icons for a thread row in compact contexts * like the command palette. Shows a terminal-running indicator and a remote * environment indicator, matching the sidebar's trailing indicators. */export function ThreadRowTrailingStatus({ thread }: { thread: SidebarThreadSummary }) {  const runningTerminalIds = useThreadRunningTerminalIds({    environmentId: thread.environmentId,    threadId: thread.id,  });  const primaryEnvironmentId = usePrimaryEnvironmentId();  const isRemoteThread =    primaryEnvironmentId !== null && thread.environmentId !== primaryEnvironmentId;  const remoteEnvLabel = useSavedEnvironmentRuntimeStore(    (state) => state.byId[thread.environmentId]?.descriptor?.label ?? null,  );  const remoteEnvSavedLabel = useSavedEnvironmentRegistryStore(    (state) => state.byId[thread.environmentId]?.label ?? null,  );  const threadEnvironmentLabel = isRemoteThread    ? (remoteEnvLabel ?? remoteEnvSavedLabel ?? "Remote")    : null;  const terminalStatus = terminalStatusFromRunningIds(runningTerminalIds);   if (!terminalStatus && !isRemoteThread) {    return null;  }   return (    <span className="inline-flex shrink-0 items-center gap-1.5">      {terminalStatus ? (        <span          role="img"          aria-label={terminalStatus.label}          title={terminalStatus.label}          className={`inline-flex items-center justify-center ${terminalStatus.colorClass}`}        >          <TerminalIcon className={`size-3 ${terminalStatus.pulse ? "animate-pulse" : ""}`} />        </span>      ) : null}      {isRemoteThread ? (        <Tooltip>          <TooltipTrigger            render={              <span                aria-label={threadEnvironmentLabel ?? "Remote"}                className="inline-flex items-center justify-center"              />            }          >            <CloudIcon className="size-3 text-muted-foreground/60" />          </TooltipTrigger>          <TooltipPopup side="top">{threadEnvironmentLabel}</TooltipPopup>        </Tooltip>      ) : null}    </span>  );}