11import { PanelMessage } from "@components/ui/PanelMessage" ;
2- import { usePanelLayoutStore } from "@features/panels" ;
2+ import { isDiffTabActiveInTree , usePanelLayoutStore } from "@features/panels" ;
33import { useTaskData } from "@features/task-detail/hooks/useTaskData" ;
4- import { FileIcon } from "@phosphor-icons/react" ;
5- import { Badge , Box , Flex , Text } from "@radix-ui/themes" ;
4+ import { ArrowCounterClockwiseIcon , FileIcon } from "@phosphor-icons/react" ;
5+ import { Badge , Box , Flex , IconButton , Text , Tooltip } from "@radix-ui/themes" ;
66import type { ChangedFile , GitFileStatus , Task } from "@shared/types" ;
7- import { useQuery } from "@tanstack/react-query" ;
7+ import { useQuery , useQueryClient } from "@tanstack/react-query" ;
8+ import { showMessageBox } from "@utils/dialog" ;
89import { handleExternalAppAction } from "@utils/handleExternalAppAction" ;
910import {
1011 selectWorktreePath ,
@@ -20,6 +21,7 @@ interface ChangedFileItemProps {
2021 file : ChangedFile ;
2122 taskId : string ;
2223 repoPath : string ;
24+ isActive : boolean ;
2325}
2426
2527function getStatusIndicator ( status : GitFileStatus ) : {
@@ -41,8 +43,55 @@ function getStatusIndicator(status: GitFileStatus): {
4143 }
4244}
4345
44- function ChangedFileItem ( { file, taskId, repoPath } : ChangedFileItemProps ) {
46+ function getDiscardInfo (
47+ file : ChangedFile ,
48+ fileName : string ,
49+ ) : { message : string ; action : string } {
50+ switch ( file . status ) {
51+ case "modified" :
52+ return {
53+ message : `Are you sure you want to discard changes in '${ fileName } '?` ,
54+ action : "Discard File" ,
55+ } ;
56+ case "deleted" :
57+ return {
58+ message : `Are you sure you want to restore '${ fileName } '?` ,
59+ action : "Restore File" ,
60+ } ;
61+ case "added" :
62+ return {
63+ message : `Are you sure you want to remove '${ fileName } '?` ,
64+ action : "Remove File" ,
65+ } ;
66+ case "untracked" :
67+ return {
68+ message : `Are you sure you want to delete '${ fileName } '?` ,
69+ action : "Delete File" ,
70+ } ;
71+ case "renamed" :
72+ return {
73+ message : `Are you sure you want to undo the rename of '${ fileName } '?` ,
74+ action : "Undo Rename File" ,
75+ } ;
76+ default :
77+ return {
78+ message : `Are you sure you want to discard changes in '${ fileName } '?` ,
79+ action : "Discard File" ,
80+ } ;
81+ }
82+ }
83+
84+ function ChangedFileItem ( {
85+ file,
86+ taskId,
87+ repoPath,
88+ isActive,
89+ } : ChangedFileItemProps ) {
4590 const openDiff = usePanelLayoutStore ( ( state ) => state . openDiff ) ;
91+ const closeDiffTabsForFile = usePanelLayoutStore (
92+ ( state ) => state . closeDiffTabsForFile ,
93+ ) ;
94+ const queryClient = useQueryClient ( ) ;
4695 const fileName = file . path . split ( "/" ) . pop ( ) || file . path ;
4796 const indicator = getStatusIndicator ( file . status ) ;
4897
@@ -60,14 +109,45 @@ function ChangedFileItem({ file, taskId, repoPath }: ChangedFileItemProps) {
60109 await handleExternalAppAction ( result . action , fullPath , fileName ) ;
61110 } ;
62111
112+ const handleDiscard = async ( e : React . MouseEvent ) => {
113+ e . preventDefault ( ) ;
114+
115+ const { message, action } = getDiscardInfo ( file , fileName ) ;
116+
117+ const result = await showMessageBox ( {
118+ type : "warning" ,
119+ title : "Discard changes" ,
120+ message,
121+ buttons : [ "Cancel" , action ] ,
122+ defaultId : 0 ,
123+ cancelId : 0 ,
124+ } ) ;
125+
126+ if ( result . response !== 1 ) return ;
127+
128+ await window . electronAPI . discardFileChanges (
129+ repoPath ,
130+ file . path ,
131+ file . status ,
132+ ) ;
133+
134+ closeDiffTabsForFile ( taskId , file . path ) ;
135+
136+ queryClient . invalidateQueries ( {
137+ queryKey : [ "changed-files-head" , repoPath ] ,
138+ } ) ;
139+ } ;
140+
63141 return (
64142 < Flex
65143 align = "center"
66144 gap = "2"
67145 py = "1"
146+ pl = "1"
147+ pr = "2"
68148 onClick = { handleClick }
69149 onContextMenu = { handleContextMenu }
70- className = " hover:bg-gray-2"
150+ className = { `group ${ isActive ? "bg-gray-3" : " hover:bg-gray-2"} ` }
71151 style = { { cursor : "pointer" , whiteSpace : "nowrap" , overflow : "hidden" } }
72152 >
73153 < Badge size = "1" color = { indicator . color } style = { { flexShrink : 0 } } >
@@ -84,10 +164,23 @@ function ChangedFileItem({ file, taskId, repoPath }: ChangedFileItemProps) {
84164 userSelect : "none" ,
85165 overflow : "hidden" ,
86166 textOverflow : "ellipsis" ,
167+ flex : 1 ,
87168 } }
88169 >
89170 { file . originalPath ? `${ file . originalPath } → ${ file . path } ` : file . path }
90171 </ Text >
172+ < Tooltip content = "Discard changes" >
173+ < IconButton
174+ size = "1"
175+ variant = "ghost"
176+ color = "gray"
177+ onClick = { handleDiscard }
178+ className = { isActive ? "" : "opacity-0 group-hover:opacity-100" }
179+ style = { { flexShrink : 0 } }
180+ >
181+ < ArrowCounterClockwiseIcon size = { 12 } />
182+ </ IconButton >
183+ </ Tooltip >
91184 </ Flex >
92185 ) ;
93186}
@@ -96,6 +189,7 @@ export function ChangesPanel({ taskId, task }: ChangesPanelProps) {
96189 const taskData = useTaskData ( { taskId, initialTask : task } ) ;
97190 const worktreePath = useWorkspaceStore ( selectWorktreePath ( taskId ) ) ;
98191 const repoPath = worktreePath ?? taskData . repoPath ;
192+ const layout = usePanelLayoutStore ( ( state ) => state . getLayout ( taskId ) ) ;
99193
100194 const { data : changedFiles = [ ] , isLoading } = useQuery ( {
101195 queryKey : [ "changed-files-head" , repoPath ] ,
@@ -104,6 +198,11 @@ export function ChangesPanel({ taskId, task }: ChangesPanelProps) {
104198 refetchOnMount : "always" ,
105199 } ) ;
106200
201+ const isFileActive = ( file : ChangedFile ) : boolean => {
202+ if ( ! layout ) return false ;
203+ return isDiffTabActiveInTree ( layout . panelTree , file . path , file . status ) ;
204+ } ;
205+
107206 if ( ! repoPath ) {
108207 return < PanelMessage > No repository path available</ PanelMessage > ;
109208 }
@@ -125,6 +224,7 @@ export function ChangesPanel({ taskId, task }: ChangesPanelProps) {
125224 file = { file }
126225 taskId = { taskId }
127226 repoPath = { repoPath }
227+ isActive = { isFileActive ( file ) }
128228 />
129229 ) ) }
130230 </ Flex >
0 commit comments