import { For, Show, Suspense, createMemo, lazy, type Accessor, type Component, type JSX, } from "solid-js" import { ChevronDown, ChevronRight, GitBranch, RefreshCw } from "lucide-solid" import DiffToolbar from "../components/DiffToolbar" import SplitFilePanel from "../components/SplitFilePanel" import type { DiffContextMode, DiffViewMode, DiffWordWrapMode, GitChangeEntry, GitChangeListItem } from "../types" import { buildGitChangeListItems } from "../git-changes-model" const LazyMonacoDiffViewer = lazy(() => import("../../../../file-viewer/monaco-diff-viewer").then((module) => ({ default: module.MonacoDiffViewer })), ) interface GitChangesTabProps { t: (key: string, vars?: Record) => string activeSessionId: Accessor entries: Accessor statusLoading: Accessor statusError: Accessor selectedItemId: Accessor selectedBulkItemIds: Accessor> selectedLoading: Accessor selectedError: Accessor selectedBefore: Accessor selectedAfter: Accessor mostChangedItemId: Accessor scopeKey: Accessor diffViewMode: Accessor diffContextMode: Accessor diffWordWrapMode: Accessor onViewModeChange: (mode: DiffViewMode) => void onContextModeChange: (mode: DiffContextMode) => void onWordWrapModeChange: (mode: DiffWordWrapMode) => void onRowClick: (item: GitChangeListItem, event: MouseEvent) => void onRefresh: () => void onInsertContext: (item: GitChangeListItem, selection: { startLine: number; endLine: number }) => void onStageFile: (item: GitChangeListItem) => void onUnstageFile: (item: GitChangeListItem) => void commitMessage: Accessor commitSubmitting: Accessor onCommitMessageInput: (value: string) => void onSubmitCommit: () => void branchLabel: Accessor stagedOpen: Accessor unstagedOpen: Accessor onToggleStagedOpen: () => void onToggleUnstagedOpen: () => void listOpen: Accessor onToggleList: () => void splitWidth: Accessor onResizeMouseDown: (event: MouseEvent) => void onResizeTouchStart: (event: TouchEvent) => void isPhoneLayout: Accessor } const GitChangesTab: Component = (props) => { const sessionId = createMemo(() => props.activeSessionId()) const hasSession = createMemo(() => Boolean(sessionId() && sessionId() !== "info")) const entries = createMemo(() => (hasSession() ? props.entries() : null)) const sorted = createMemo(() => { const list = entries() if (!Array.isArray(list)) return [] return [...list].sort((a, b) => String(a.path || "").localeCompare(String(b.path || ""))) }) const listItems = createMemo(() => buildGitChangeListItems(sorted())) const totals = createMemo(() => { return listItems().reduce( (acc, item) => { acc.additions += typeof item.additions === "number" ? item.additions : 0 acc.deletions += typeof item.deletions === "number" ? item.deletions : 0 return acc }, { additions: 0, deletions: 0 }, ) }) const stagedItems = createMemo(() => listItems().filter((item) => item.section === "staged")) const unstagedItems = createMemo(() => listItems().filter((item) => item.section === "unstaged")) const canCommit = createMemo(() => stagedItems().length > 0 && props.commitMessage().trim().length > 0 && !props.commitSubmitting()) const selectedEntry = createMemo(() => { const list = listItems() const selectedId = props.selectedItemId() const fallbackId = props.mostChangedItemId() const found = list.find((item) => item.id === selectedId) || (fallbackId ? list.find((item) => item.id === fallbackId) : undefined) return found?.entry ?? null }) const emptyViewerMessage = createMemo(() => { if (!hasSession()) return props.t("instanceShell.gitChanges.noSessionSelected") const currentEntries = entries() if (currentEntries === null) return props.t("instanceShell.gitChanges.loading") if (listItems().length === 0) return props.t("instanceShell.gitChanges.empty") return props.t("instanceShell.filesShell.viewerEmpty") }) const binaryViewerActive = createMemo(() => props.selectedError() === props.t("instanceShell.gitChanges.binaryViewer")) const renderContent = (): JSX.Element => { const totalsValue = totals() const selected = selectedEntry() const allItems = listItems() const stagedList = stagedItems() const unstagedList = unstagedItems() const renderViewer = () => (
{emptyViewerMessage()}
} > {(file) => ( {props.t("instanceInfo.loading")}
} > { const selectedId = props.selectedItemId() if (!selectedId) return const item = listItems().find((entry) => entry.id === selectedId) if (!item) return props.onInsertContext(item, selection) }} /> )} } > {(err) => (
{err()}
)} } >
{props.t("instanceInfo.loading")}
) const renderEmptyList = () =>
{emptyViewerMessage()}
const renderListItem = (item: GitChangeListItem) => { const isBulkSelected = createMemo(() => props.selectedBulkItemIds().has(item.id)) const actionLabel = item.section === "staged" ? props.t("instanceShell.gitChanges.actions.unstage") : props.t("instanceShell.gitChanges.actions.stage") const triggerAction = () => { if (item.section === "staged") props.onUnstageFile(item) else props.onStageFile(item) } return (
{ if (event.shiftKey || event.ctrlKey || event.metaKey) { event.preventDefault() } }} onClick={(event) => props.onRowClick(item, event)} title={item.path} >
{item.path}
+{item.additions} -{item.deletions}
) } const renderSection = ( title: string, items: GitChangeListItem[], isOpen: boolean, onToggle: () => void, ) => (
{(item) => renderListItem(item)}
) const renderGroupedList = () => ( 0} fallback={renderEmptyList()}>