Skip to content

Commit d4e8600

Browse files
committed
feat: add commi & branch diff comparison tab and quick diff support (close #25)
1 parent 677981b commit d4e8600

9 files changed

Lines changed: 649 additions & 35 deletions

File tree

wimygit-tauri/src/App.tsx

Lines changed: 57 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { exists } from "@tauri-apps/plugin-fs";
44
import { getCurrentWindow } from "@tauri-apps/api/window";
55
import { APP_NAME } from "./constants";
66
import { Header, TabBar, RepoTabBar, GitLogPanel, LeftSidebar, RepoStateBanner } from "./components/layout";
7-
import type { PendingFilePreview } from "./components/layout/SidebarQuickDiff";
7+
import type { PendingFilePreview, BranchFileDiffInfo } from "./components/layout/SidebarQuickDiff";
88
import { PendingTab } from "./components/tabs";
99
import { MergeEditor } from "./components/shared/MergeEditor";
1010
import type { FileStatus } from "./lib";
@@ -18,6 +18,7 @@ const TagTab = lazy(() => import("./components/tabs/TagTab").then(m => ({ defaul
1818
const WorktreeTab = lazy(() => import("./components/tabs/WorktreeTab").then(m => ({ default: m.WorktreeTab })));
1919
const PluginTab = lazy(() => import("./components/tabs/PluginTab").then(m => ({ default: m.PluginTab })));
2020
const TimeLapsePanel = lazy(() => import("./components/layout/TimeLapsePanel").then(m => ({ default: m.TimeLapsePanel })));
21+
const BranchDiffTab = lazy(() => import("./components/tabs/BranchDiffTab").then(m => ({ default: m.BranchDiffTab })));
2122
import {
2223
isGitRepository,
2324
getRepositoryRoot,
@@ -108,6 +109,10 @@ function App() {
108109
} | null>(null);
109110
const [activeMergeFile, setActiveMergeFile] = useState<FileStatus | null>(null);
110111
const [conflictCount, setConflictCount] = useState(0);
112+
const [branchDiffOpen, setBranchDiffOpen] = useState(false);
113+
const [branchFileDiff, setBranchFileDiff] = useState<BranchFileDiffInfo | null>(null);
114+
const [branchDiffInitialBase, setBranchDiffInitialBase] = useState<string | undefined>();
115+
const [branchDiffInitialCompare, setBranchDiffInitialCompare] = useState<string | undefined>();
111116

112117
// Set window title (visible in taskbar)
113118
useEffect(() => {
@@ -343,6 +348,10 @@ function App() {
343348
setLfsLockCount(0);
344349
setActiveMergeFile(null);
345350
setConflictCount(0);
351+
setBranchDiffOpen(false);
352+
setBranchFileDiff(null);
353+
setBranchDiffInitialBase(undefined);
354+
setBranchDiffInitialCompare(undefined);
346355
}, [activeRepoId]);
347356

348357
// LFS 설치 여부 확인 (레포 변경 시)
@@ -471,6 +480,7 @@ function App() {
471480
refreshKey={activeRepo.refreshKey}
472481
selectedDiff={selectedDiff}
473482
pendingFilePreview={pendingFilePreview}
483+
branchFileDiff={branchFileDiff}
474484
onFileSelect={setSelectedFilePath}
475485
onRefresh={handleRefresh}
476486
highlightPath={workspaceHighlight}
@@ -494,15 +504,29 @@ function App() {
494504
) : (
495505
<>
496506
<TabBar
497-
tabs={BASE_INNER_TABS.map((tab) => {
498-
if (tab.id === "pending" && lfsLockCount > 0)
499-
return { ...tab, label: `Pending Changes [${lfsLockCount} Locked]` };
500-
if (tab.id === "worktrees" && worktreeCount >= 2)
501-
return { ...tab, label: `Worktrees [+${worktreeCount}]` };
502-
return tab;
503-
})}
507+
tabs={[
508+
...BASE_INNER_TABS.map((tab) => {
509+
if (tab.id === "pending" && lfsLockCount > 0)
510+
return { ...tab, label: `Pending Changes [${lfsLockCount} Locked]` };
511+
if (tab.id === "worktrees" && worktreeCount >= 2)
512+
return { ...tab, label: `Worktrees [+${worktreeCount}]` };
513+
return tab;
514+
}),
515+
...(branchDiffOpen
516+
? [{ id: "branch-diff", label: "⇄ Compare", closeable: true }]
517+
: []),
518+
]}
504519
activeTab={activeRepo.activeTab}
505520
onTabChange={handleTabChange}
521+
onTabClose={(tabId) => {
522+
if (tabId === "branch-diff") {
523+
setBranchDiffOpen(false);
524+
setBranchFileDiff(null);
525+
if (activeRepo.activeTab === "branch-diff") {
526+
handleTabChange("branches");
527+
}
528+
}
529+
}}
506530
/>
507531
<div className={activeRepo.activeTab === "pending" ? "contents" : "hidden"}>
508532
<PendingTab
@@ -551,6 +575,12 @@ function App() {
551575
})();
552576
}}
553577
onShowInHistoryFile={(absolutePath) => setSelectedFilePath(absolutePath)}
578+
onOpenCompare={(base, compare) => {
579+
setBranchDiffInitialBase(base || undefined);
580+
setBranchDiffInitialCompare(compare || undefined);
581+
setBranchDiffOpen(true);
582+
handleTabChange("branch-diff");
583+
}}
554584
/>
555585
)}
556586
{activeRepo.activeTab === "branches" && (
@@ -559,6 +589,25 @@ function App() {
559589
refreshKey={activeRepo.refreshKey}
560590
silentRefreshKey={activeRepo.silentRefreshKey}
561591
onRefresh={handleRefresh}
592+
onOpenCompare={(base, compare) => {
593+
setBranchDiffInitialBase(base || undefined);
594+
setBranchDiffInitialCompare(compare || undefined);
595+
setBranchDiffOpen(true);
596+
handleTabChange("branch-diff");
597+
}}
598+
/>
599+
)}
600+
{activeRepo.activeTab === "branch-diff" && branchDiffOpen && (
601+
<BranchDiffTab
602+
repoPath={activeRepo.repoPath}
603+
refreshKey={activeRepo.refreshKey}
604+
initialBase={branchDiffInitialBase}
605+
initialCompare={branchDiffInitialCompare}
606+
onFileSelect={(diff, filename) => {
607+
setSelectedDiff(null);
608+
setPendingFilePreview(null);
609+
setBranchFileDiff({ diff, filename });
610+
}}
562611
/>
563612
)}
564613
{activeRepo.activeTab === "remotes" && (

wimygit-tauri/src/components/layout/LeftSidebar.tsx

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { useState, useEffect, useCallback, useRef } from "react";
22
import { type SelectedDiffInfo } from "../../lib";
33
import { WorkspaceTree } from "./WorkspaceTree";
4-
import { SidebarQuickDiff, type PendingFilePreview } from "./SidebarQuickDiff";
4+
import { SidebarQuickDiff, type PendingFilePreview, type BranchFileDiffInfo } from "./SidebarQuickDiff";
55

66
// ─── constants ────────────────────────────────────────────────────────────────
77

@@ -17,12 +17,13 @@ interface LeftSidebarProps {
1717
refreshKey: number;
1818
selectedDiff?: SelectedDiffInfo | null;
1919
pendingFilePreview?: PendingFilePreview | null;
20+
branchFileDiff?: BranchFileDiffInfo | null;
2021
onFileSelect?: (path: string | null) => void;
2122
onRefresh?: () => void;
2223
highlightPath?: { path: string; triggerCount: number } | null;
2324
}
2425

25-
export function LeftSidebar({ repoPath, refreshKey, selectedDiff, pendingFilePreview, onFileSelect, onRefresh, highlightPath }: LeftSidebarProps) {
26+
export function LeftSidebar({ repoPath, refreshKey, selectedDiff, pendingFilePreview, branchFileDiff, onFileSelect, onRefresh, highlightPath }: LeftSidebarProps) {
2627
const [width, setWidth] = useState(() => {
2728
const quarter = Math.round(window.innerWidth / 4);
2829
return Math.max(MIN_SIDEBAR_WIDTH, quarter);
@@ -50,6 +51,15 @@ export function LeftSidebar({ repoPath, refreshKey, selectedDiff, pendingFilePre
5051
prevPendingFilePreview.current = pendingFilePreview;
5152
}, [pendingFilePreview]);
5253

54+
const prevBranchFileDiff = useRef<BranchFileDiffInfo | null | undefined>(undefined);
55+
useEffect(() => {
56+
if (branchFileDiff && branchFileDiff !== prevBranchFileDiff.current) {
57+
setActiveTab("quickdiff");
58+
localStorage.setItem("sidebar_tab", "quickdiff");
59+
}
60+
prevBranchFileDiff.current = branchFileDiff;
61+
}, [branchFileDiff]);
62+
5363
// Auto-switch to Workspace tab when highlightPath is set
5464
useEffect(() => {
5565
if (highlightPath) {
@@ -155,6 +165,7 @@ export function LeftSidebar({ repoPath, refreshKey, selectedDiff, pendingFilePre
155165
repoPath={repoPath}
156166
selectedDiff={selectedDiff}
157167
pendingFilePreview={pendingFilePreview}
168+
branchFileDiff={branchFileDiff}
158169
onRefresh={onRefresh}
159170
/>
160171
)}

wimygit-tauri/src/components/layout/SidebarQuickDiff.tsx

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -130,14 +130,21 @@ function SingleImagePreview({ src, label, filename }: { src: string; label: stri
130130
);
131131
}
132132

133+
export interface BranchFileDiffInfo {
134+
diff: string;
135+
filename: string;
136+
loading?: boolean;
137+
}
138+
133139
export interface SidebarQuickDiffProps {
134140
repoPath: string;
135141
selectedDiff?: SelectedDiffInfo | null;
136142
pendingFilePreview?: PendingFilePreview | null;
143+
branchFileDiff?: BranchFileDiffInfo | null;
137144
onRefresh?: () => void;
138145
}
139146

140-
export function SidebarQuickDiff({ repoPath, selectedDiff, pendingFilePreview, onRefresh }: SidebarQuickDiffProps) {
147+
export function SidebarQuickDiff({ repoPath, selectedDiff, pendingFilePreview, branchFileDiff, onRefresh }: SidebarQuickDiffProps) {
141148
const [modes, setModes] = useState<DiffMode[]>([{ kind: "combined", label: "Diff" }]);
142149
const [activeMode, setActiveMode] = useState<DiffModeKind>("combined");
143150
const [conflictViewMode, setConflictViewMode] = useState<ConflictViewMode>("unified");
@@ -156,6 +163,7 @@ export function SidebarQuickDiff({ repoPath, selectedDiff, pendingFilePreview, o
156163

157164
const isCommitMode = !!selectedDiff;
158165
const showingPendingPreview = !isCommitMode && !!pendingFilePreview;
166+
const showingBranchFileDiff = !isCommitMode && !pendingFilePreview && !!branchFileDiff;
159167
const currentMode = modes.find((m) => m.kind === activeMode) ?? modes[0];
160168

161169
// ── Rebuild commit modes when selection changes ──
@@ -362,8 +370,8 @@ export function SidebarQuickDiff({ repoPath, selectedDiff, pendingFilePreview, o
362370

363371
const changeContext = (delta: number) => setContextLines((prev) => Math.max(0, prev + delta));
364372

365-
const displayDiff = showingPendingPreview ? pendingDiff : diff;
366-
const displayLoading = showingPendingPreview ? pendingLoading : loadingDiff;
373+
const displayDiff = showingBranchFileDiff ? (branchFileDiff?.diff ?? "") : showingPendingPreview ? pendingDiff : diff;
374+
const displayLoading = showingBranchFileDiff ? (branchFileDiff?.loading ?? false) : showingPendingPreview ? pendingLoading : loadingDiff;
367375
const isImageDiff = !displayLoading && (!!imageDiffSrcs || !!imagePreviewSrc);
368376

369377
useEffect(() => {
@@ -504,6 +512,12 @@ export function SidebarQuickDiff({ repoPath, selectedDiff, pendingFilePreview, o
504512
<span title={pendingFilePreview.filename}>{pendingFilePreview.filename}</span>
505513
</div>
506514
)}
515+
{showingBranchFileDiff && branchFileDiff && (
516+
<div className="shrink-0 px-2 py-1 text-xs border-b border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-850 text-gray-700 dark:text-gray-300 truncate flex items-center gap-1">
517+
<span className="shrink-0 text-blue-500">branch diff</span>
518+
<span title={branchFileDiff.filename}>{branchFileDiff.filename}</span>
519+
</div>
520+
)}
507521

508522
{/* ── Diff viewer / Image preview ── */}
509523
<div className="flex-1 overflow-hidden">
@@ -537,15 +551,15 @@ export function SidebarQuickDiff({ repoPath, selectedDiff, pendingFilePreview, o
537551
) : (
538552
<DiffViewer
539553
diff={displayDiff}
540-
placeholder={isCommitMode ? "No diff available" : showingPendingPreview ? "No changes" : "Select a file from History or Pending Changes"}
554+
placeholder={isCommitMode ? "No diff available" : showingBranchFileDiff ? "No diff" : showingPendingPreview ? "No changes" : "Select a file from History, Pending Changes, or Branch Diff"}
541555
/>
542556
)}
543557
</div>
544558

545559
{/* ── Footer — hidden in image diff mode ── */}
546560
{!isImageDiff && (
547561
<div className="shrink-0 px-2 py-0.5 text-xs text-gray-400 dark:text-gray-500 bg-gray-50 dark:bg-gray-900 border-t border-gray-200 dark:border-gray-700 flex justify-between">
548-
<span>{isCommitMode ? "Commit diff" : showingPendingPreview ? "Pending preview" : "—"}</span>
562+
<span>{isCommitMode ? "Commit diff" : showingBranchFileDiff ? "Branch diff" : showingPendingPreview ? "Pending preview" : "—"}</span>
549563
<span>Context: {contextLines} lines</span>
550564
</div>
551565
)}

wimygit-tauri/src/components/layout/TabBar.tsx

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,25 @@ interface Tab {
22
id: string;
33
label: string;
44
icon?: string;
5+
closeable?: boolean;
56
}
67

78
interface TabBarProps {
89
tabs: Tab[];
910
activeTab: string;
1011
onTabChange: (tabId: string) => void;
12+
onTabClose?: (tabId: string) => void;
1113
}
1214

13-
export function TabBar({ tabs, activeTab, onTabChange }: TabBarProps) {
15+
export function TabBar({ tabs, activeTab, onTabChange, onTabClose }: TabBarProps) {
1416
return (
1517
<div className="flex items-end border-b border-gray-200 dark:border-gray-700 bg-gray-100 dark:bg-gray-900 px-1 pt-1">
1618
{tabs.map((tab) => (
1719
<button
1820
key={tab.id}
1921
onClick={() => onTabChange(tab.id)}
2022
className={`
21-
px-4 text-sm transition-colors rounded-t-lg -mb-px
23+
flex items-center gap-1.5 px-4 text-sm transition-colors rounded-t-lg -mb-px
2224
${
2325
activeTab === tab.id
2426
? "py-2.5 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 border-b-white dark:border-b-gray-800 text-gray-900 dark:text-white"
@@ -28,6 +30,15 @@ export function TabBar({ tabs, activeTab, onTabChange }: TabBarProps) {
2830
>
2931
{tab.icon && <span className="mr-1">{tab.icon}</span>}
3032
{tab.label}
33+
{tab.closeable && (
34+
<span
35+
role="button"
36+
onClick={(e) => { e.stopPropagation(); onTabClose?.(tab.id); }}
37+
className="ml-1 w-4 h-4 flex items-center justify-center rounded text-gray-400 hover:text-gray-700 dark:hover:text-gray-200 hover:bg-gray-300 dark:hover:bg-gray-600 text-xs leading-none"
38+
>
39+
40+
</span>
41+
)}
3142
</button>
3243
))}
3344
</div>

0 commit comments

Comments
 (0)