Skip to content

Commit 9d6f520

Browse files
committed
update staged changes view
1 parent 75878e9 commit 9d6f520

File tree

7 files changed

+97
-4802
lines changed

7 files changed

+97
-4802
lines changed

.gitignore

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
node_modules/
22
dist
3-
release/
3+
release/
4+
package-lock.json

common_library/src/models/IStatus.ts

+9-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import { IFile } from "./IFile";
44
export interface IStatus{
55
ahead:number;
66
behind:number;
7+
stagedChanges:IChanges;
8+
unstagedChanges:IChanges;
79
created:IFile[];
810
conflicted:IFile[];
911
deleted:IFile[];
@@ -13,10 +15,16 @@ export interface IStatus{
1315
to:string;
1416
}[];
1517
staged:IFile[];
16-
not_added: IFile[];
18+
not_staged: IFile[];
1719
isClean:boolean;
1820
current:string | null;
1921
isDetached:boolean;
2022
headCommit:ICommitInfo;
2123
mergingCommitHash?:string;
2224
}
25+
26+
export interface IChanges{
27+
created:IFile[];
28+
modified:IFile[];
29+
deleted:IFile[];
30+
}

frontend/src/components/selectedRepository/selectedRepoRight/changes/Changes.tsx

+3-3
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ function ChangesComponent(props:IChangesProps) {
7474
useEffect(()=>{
7575
if(!state.selectedFilePath) return;
7676
if(state.selectedFileModel === EnumChangesType.CONFLICTED && state.status?.conflicted?.some(x=> x.path === state.selectedFilePath)) return;
77-
if(state.selectedFileModel === EnumChangesType.CREATED && state.status?.not_added?.some(x=> x.path === state.selectedFilePath)) return;
77+
if(state.selectedFileModel === EnumChangesType.CREATED && state.status?.not_staged?.some(x=> x.path === state.selectedFilePath)) return;
7878
if(state.selectedFileModel === EnumChangesType.DELETED && state.status?.deleted?.some(x=> x.path === state.selectedFilePath)) return;
7979
if(state.selectedFileModel === EnumChangesType.MODIFIED && state.status?.modified?.some(x=> x.path === state.selectedFilePath)) return;
8080
if(state.selectedFileModel === EnumChangesType.STAGED && state.status?.staged?.some(x=> x.path === state.selectedFilePath)) return;
@@ -153,7 +153,7 @@ function ChangesComponent(props:IChangesProps) {
153153
count++;
154154
}
155155
if(state.expandedTabs.includes(EnumChangesType.MODIFIED)){
156-
if(state.status?.not_added.length)
156+
if(state.status?.not_staged?.length)
157157
count++;
158158
}
159159
if(state.expandedTabs.includes(EnumChangesType.STAGED)){
@@ -179,7 +179,7 @@ function ChangesComponent(props:IChangesProps) {
179179
<CommitBox onHeightChange={height=> {setState({commitBoxHeight:height})}} />
180180
{!!state.commitBoxHeight && <Fragment>
181181

182-
{!!state.status?.staged?.length && <StagedChanges stagedChanges={state.status?.staged} onStatusChange={onStatusChange} repoInfoInfo={repoInfo}
182+
{!!state.status?.staged?.length && <StagedChanges changes={state.status?.stagedChanges} onStatusChange={onStatusChange} repoInfoInfo={repoInfo}
183183
handleSelect={path=> handleSelect(path,EnumChangesType.STAGED)} selectedFilePath={state.selectedFilePath} selectedMode={state.selectedFileModel}
184184
isExpanded={state.expandedTabs.includes(EnumChangesType.STAGED)}
185185
hanldeExpand={()=> handleExpand(EnumChangesType.STAGED)}

frontend/src/components/selectedRepository/selectedRepoRight/changes/ModifiedChanges.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@ function ModifiedChangesComponent(props:IModifiedChangesProps){
3636
const items:IModifiedItem[]=[];
3737
if(!props.status)
3838
return items;
39-
if(props.status.not_added?.length){
40-
for(let item of props.status.not_added) {
39+
if(props.status.not_staged?.length){
40+
for(let item of props.status.not_staged) {
4141
items.push({
4242
fileName:item.fileName,
4343
path:item.path,

frontend/src/components/selectedRepository/selectedRepoRight/changes/StagedChanges.tsx

+65-23
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,42 @@
1-
import { IFile, IStatus, RendererEvents, RepositoryInfo } from "common_library";
1+
import { IChanges, IFile, IStatus, RendererEvents, RepositoryInfo } from "common_library";
22
import React, { Fragment, useEffect, useMemo, useRef } from "react";
33
import { FaAngleDown, FaAngleRight, FaMinus } from "react-icons/fa";
44
import { EnumChangesType, UiUtils, useMultiState } from "../../../../lib";
55

6+
interface ISingleFileProps{
7+
fileName:string;
8+
filePath:string;
9+
handleSelect:(path:string)=>void;
10+
isSelected:boolean;
11+
handleUnstage:()=>void;
12+
changeType:"M"|"A"|"D";
13+
}
14+
15+
function SingleFile(props:ISingleFileProps){
16+
const [state,setState]=useMultiState({isHovered:false})
17+
return (
18+
<div key={props.filePath} className={`row g-0 align-items-center flex-nowrap hover w-100 ${props.isSelected ? "selected":""}`}
19+
title={props.fileName} onMouseEnter={()=> setState({isHovered:true})} onMouseLeave={_=> setState({isHovered:false})}
20+
onClick={_=> props.handleSelect(props.filePath)}>
21+
<div className="col-auto overflow-hidden flex-shrink-1" style={{textOverflow:'ellipsis'}}>
22+
<span className={`pe-1 flex-shrink-0 ${props.changeType === "D"?"text-decoration-line-through":""}`}>{props.fileName}</span>
23+
<span className="small text-secondary">{props.filePath}</span>
24+
</div>
25+
26+
<div className="col-auto align-items-center flex-nowrap overflow-hidden flex-grow-1 text-end pe-1">
27+
{state.isHovered && <Fragment>
28+
<span className="hover" title="Unstage" onClick={_=> props.handleUnstage()}><FaMinus /></span>
29+
</Fragment>}
30+
<span>
31+
<span className="ps-1 text-success fw-bold">{props.changeType}</span>
32+
</span>
33+
</div>
34+
</div>
35+
)
36+
}
637

738
interface IStagedChangesProps{
8-
stagedChanges?:IFile[];
39+
changes:IChanges;
940
repoInfoInfo?:RepositoryInfo;
1041
onStatusChange:(status:IStatus)=>void;
1142
handleSelect:(path:string)=>void;
@@ -42,13 +73,21 @@ function StagedChangesComponent(props:IStagedChangesProps){
4273
return props.height -headerRef.current.clientHeight;
4374
},[headerRef.current?.clientHeight,props.height]);
4475

76+
const totalItemCount = useMemo(()=>{
77+
const length = props.changes.created.length + props.changes.deleted.length
78+
+ props.changes.modified.length;
79+
return length;
80+
},[props.changes])
81+
4582
const handleUnstageItem = (item:IFile)=>{
4683
window.ipcRenderer.send(RendererEvents.unStageItem().channel,[item.path],props.repoInfoInfo)
4784
}
4885

4986
const unStageAll=()=>{
50-
if(!props.stagedChanges?.length) return;
51-
window.ipcRenderer.send(RendererEvents.unStageItem().channel,props.stagedChanges.map(x=>x.path),props.repoInfoInfo)
87+
if(!totalItemCount) return;
88+
const paths = [...props.changes.created.map(_=>_.path),...props.changes.deleted.map(_=>_.path),
89+
...props.changes.modified.map(_=>_.path)];
90+
window.ipcRenderer.send(RendererEvents.unStageItem().channel,paths,props.repoInfoInfo)
5291
}
5392

5493
return <div style={{maxHeight:props.height}}>
@@ -57,7 +96,7 @@ function StagedChangesComponent(props:IStagedChangesProps){
5796
<div className="d-flex flex-grow-1" onClick={props.hanldeExpand}>
5897
<span>{props.isExpanded ? <FaAngleDown /> : <FaAngleRight />} </span>
5998
<span>Staged Changes</span>
60-
{!!props.stagedChanges?.length && <span className="text-info">({props.stagedChanges.length})</span>}
99+
{!!totalItemCount && <span className="text-info">({totalItemCount})</span>}
61100
</div>
62101
{state.isHeadHover && <div className="d-flex">
63102
<span className="hover" title="UnStage all" onClick={_=> unStageAll()}><FaMinus /></span>
@@ -67,24 +106,27 @@ function StagedChangesComponent(props:IStagedChangesProps){
67106
{props.isExpanded &&
68107
<div className="container ps-2 border" onMouseLeave={_=> setState({hoveredFile:undefined})}
69108
style={{maxHeight:`${fileListPanelHeight}px`, overflowX:'hidden',overflowY:'auto'}}>
70-
{props.stagedChanges?.map(f=>(
71-
<div key={f.path} className={`row g-0 align-items-center flex-nowrap hover w-100 ${props.selectedMode === EnumChangesType.STAGED && f.path === props.selectedFilePath?"selected":""}`}
72-
title={f.path} onMouseEnter={()=> setState({hoveredFile:f})} onClick={_=> props.handleSelect(f.path)}>
73-
<div className="col-auto overflow-hidden flex-shrink-1" style={{textOverflow:'ellipsis'}}>
74-
<span className="pe-1 flex-shrink-0">{f.fileName}</span>
75-
<span className="small text-secondary">{f.path}</span>
76-
</div>
77-
78-
<div className="col-auto align-items-center flex-nowrap overflow-hidden flex-grow-1 text-end pe-1">
79-
{state.hoveredFile?.path === f.path && <Fragment>
80-
<span className="hover" title="Unstage" onClick={_=> handleUnstageItem(f)}><FaMinus /></span>
81-
</Fragment>}
82-
<span>
83-
<span className="ps-1 text-success fw-bold">{"M"}</span>
84-
</span>
85-
</div>
86-
</div>
87-
))}
109+
{props.changes?.modified?.map(f=>(
110+
<SingleFile key={f.path} fileName={f.fileName} filePath={f.
111+
path} handleSelect={_=> props.handleSelect(f.path)}
112+
handleUnstage={() => handleUnstageItem(f)}
113+
isSelected ={props.selectedMode === EnumChangesType.STAGED && f.path === props.selectedFilePath}
114+
changeType="M" />
115+
))}
116+
{props.changes?.created?.map(f=>(
117+
<SingleFile key={f.path} fileName={f.fileName} filePath={f.
118+
path} handleSelect={_=> props.handleSelect(f.path)}
119+
handleUnstage={() => handleUnstageItem(f)}
120+
isSelected ={props.selectedMode === EnumChangesType.STAGED && f.path === props.selectedFilePath}
121+
changeType="A" />
122+
))}
123+
{props.changes?.deleted?.map(f=>(
124+
<SingleFile key={f.path} fileName={f.fileName} filePath={f.
125+
path} handleSelect={_=> props.handleSelect(f.path)}
126+
handleUnstage={() => handleUnstageItem(f)}
127+
isSelected ={props.selectedMode === EnumChangesType.STAGED && f.path === props.selectedFilePath}
128+
changeType="D"/>
129+
))}
88130
</div>
89131
}
90132
</div>

0 commit comments

Comments
 (0)