Skip to content

Commit 1976e1c

Browse files
committed
Refactor file list and commit box components
1 parent e45e397 commit 1976e1c

File tree

3 files changed

+275
-152
lines changed

3 files changed

+275
-152
lines changed

src/components/CommitBox.tsx

Lines changed: 145 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
1-
import { ISettingRegistry } from '@jupyterlab/coreutils';
2-
31
import * as React from 'react';
42
import TextareaAutosize from 'react-textarea-autosize';
53
import { classes } from 'typestyle';
6-
74
import {
85
stagedCommitButtonDisabledStyle,
96
stagedCommitButtonReadyStyle,
@@ -13,102 +10,184 @@ import {
1310
textInputStyle
1411
} from '../style/BranchHeaderStyle';
1512

13+
/**
14+
* Interface describing component properties.
15+
*/
1616
export interface ICommitBoxProps {
17+
/**
18+
* Boolean indicating whether files currently exist which have changes to commit.
19+
*/
1720
hasFiles: boolean;
18-
commitFunc: (message: string) => Promise<void>;
19-
settings: ISettingRegistry.ISettings;
21+
22+
/**
23+
* Callback to invoke in order to commit changes.
24+
*
25+
* @param msg - commit message
26+
* @returns a promise which commits changes
27+
*/
28+
onCommit: (msg: string) => Promise<void>;
2029
}
2130

31+
/**
32+
* Interface describing component state.
33+
*/
2234
export interface ICommitBoxState {
2335
/**
24-
* Commit message
36+
* Commit message summary.
37+
*/
38+
summary: string;
39+
40+
/**
41+
* Commit message description.
2542
*/
26-
value: string;
43+
description: string;
2744
}
2845

46+
/**
47+
* React component for entering a commit message.
48+
*/
2949
export class CommitBox extends React.Component<
3050
ICommitBoxProps,
3151
ICommitBoxState
3252
> {
53+
/**
54+
* Returns a React component for entering a commit message.
55+
*
56+
* @param props - component properties
57+
* @returns React component
58+
*/
3359
constructor(props: ICommitBoxProps) {
3460
super(props);
3561
this.state = {
36-
value: ''
62+
summary: '',
63+
description: ''
3764
};
3865
}
3966

40-
/** Prevent enter key triggered 'submit' action during commit message input */
41-
onKeyPress(event: any): void {
42-
if (event.which === 13) {
43-
event.preventDefault();
44-
this.setState({ value: this.state.value + '\n' });
45-
}
46-
}
47-
48-
/** Initalize commit message input box */
49-
initializeInput = (): void => {
50-
this.setState({
51-
value: ''
52-
});
53-
};
54-
55-
/** Handle input inside commit message box */
56-
handleChange = (event: any): void => {
57-
this.setState({
58-
value: event.target.value
59-
});
60-
};
61-
62-
/** Update state of commit message input box */
63-
commitButtonStyle = (hasStagedFiles: boolean) => {
64-
if (hasStagedFiles) {
65-
if (this.state.value.length === 0) {
66-
return classes(stagedCommitButtonStyle, stagedCommitButtonReadyStyle);
67-
} else {
68-
return stagedCommitButtonStyle;
69-
}
70-
} else {
71-
return classes(stagedCommitButtonStyle, stagedCommitButtonDisabledStyle);
72-
}
73-
};
74-
67+
/**
68+
* Renders the component.
69+
*
70+
* @returns fragment
71+
*/
7572
render() {
7673
return (
77-
<form
78-
className={stagedCommitStyle}
79-
onKeyPress={event => this.onKeyPress(event)}
80-
>
74+
<form className={stagedCommitStyle}>
75+
<textarea
76+
className={classes(textInputStyle, stagedCommitMessageStyle)}
77+
placeholder={'Summary (required)'}
78+
value={this.state.summary}
79+
onChange={this._onSummaryChange}
80+
onKeyPress={this._onSummaryKeyPress}
81+
/>
8182
<TextareaAutosize
8283
className={classes(textInputStyle, stagedCommitMessageStyle)}
83-
disabled={!this.props.hasFiles}
8484
minRows={2}
85-
onChange={this.handleChange}
86-
placeholder={this._placeholder()}
87-
value={this.state.value}
85+
placeholder={'Description'}
86+
value={this.state.description}
87+
onChange={this._onDescriptionChange}
88+
onKeyPress={this._onDescriptionKeyPress}
8889
/>
8990
<input
90-
className={this.commitButtonStyle(this.props.hasFiles)}
91+
className={this._commitButtonStyle()}
9192
type="button"
9293
title="Commit"
93-
disabled={!(this.props.hasFiles && this.state.value)}
94-
onClick={() => {
95-
this.props.commitFunc(this.state.value);
96-
this.initializeInput();
97-
}}
94+
disabled={!(this.props.hasFiles && this.state.summary)}
95+
onClick={this._onCommitClick}
9896
/>
9997
</form>
10098
);
10199
}
102100

103-
protected _placeholder = (): string => {
104-
if (this.props.settings.composite['simpleStaging']) {
105-
return this.props.hasFiles
106-
? 'Input message to commit selected changes'
107-
: 'Select changes to enable commit';
108-
} else {
109-
return this.props.hasFiles
110-
? 'Input message to commit staged changes'
111-
: 'Stage your changes before commit';
101+
/**
102+
* Returns classes for toggling the commit button.
103+
*
104+
* @returns classes to apply
105+
*/
106+
private _commitButtonStyle = (): string => {
107+
if (this.props.hasFiles) {
108+
if (this.state.summary.length === 0) {
109+
return classes(stagedCommitButtonStyle, stagedCommitButtonReadyStyle);
110+
}
111+
return stagedCommitButtonStyle;
112112
}
113+
return classes(stagedCommitButtonStyle, stagedCommitButtonDisabledStyle);
114+
};
115+
116+
/**
117+
* Callback invoked upon clicking a commit message submit button.
118+
*
119+
* @param event - event object
120+
*/
121+
private _onCommitClick = () => {
122+
const msg = this.state.summary + '\n' + this.state.description + '\n';
123+
this.props.onCommit(msg);
124+
125+
// NOTE: we assume here that committing changes always works and we can safely clear component state
126+
this._reset();
127+
};
128+
129+
/**
130+
* Callback invoked upon updating a commit message description.
131+
*
132+
* @param event - event object
133+
*/
134+
private _onDescriptionChange = (event: any): void => {
135+
this.setState({
136+
description: event.target.value
137+
});
138+
};
139+
140+
/**
141+
* Callback invoked upon a `'keypress'` event when entering a commit message description.
142+
*
143+
* ## Notes
144+
*
145+
* - Prevents triggering a `'submit'` action when hitting the `ENTER` key while entering a commit message description.
146+
*
147+
* @param event - event object
148+
*/
149+
private _onDescriptionKeyPress(event: any): void {
150+
if (event.which === 13) {
151+
event.preventDefault();
152+
this.setState({
153+
description: this.state.description + '\n'
154+
});
155+
}
156+
}
157+
158+
/**
159+
* Callback invoked upon updating a commit message summary.
160+
*
161+
* @param event - event object
162+
*/
163+
private _onSummaryChange = (event: any): void => {
164+
this.setState({
165+
summary: event.target.value
166+
});
167+
};
168+
169+
/**
170+
* Callback invoked upon a `'keypress'` event when entering a commit message summary.
171+
*
172+
* ## Notes
173+
*
174+
* - Prevents triggering a `'submit'` action when hitting the `ENTER` key while entering a commit message summary.
175+
*
176+
* @param event - event object
177+
*/
178+
private _onSummaryKeyPress(event: any): void {
179+
if (event.which === 13) {
180+
event.preventDefault();
181+
}
182+
}
183+
184+
/**
185+
* Resets component state (e.g., in order to re-initialize the commit message input box).
186+
*/
187+
private _reset = (): void => {
188+
this.setState({
189+
summary: '',
190+
description: ''
191+
});
113192
};
114193
}

src/components/FileList.tsx

Lines changed: 1 addition & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
import { Dialog, showDialog, showErrorMessage } from '@jupyterlab/apputils';
1+
import { Dialog, showErrorMessage } from '@jupyterlab/apputils';
22
import { ISettingRegistry } from '@jupyterlab/coreutils';
33
import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
4-
import { JSONObject } from '@phosphor/coreutils';
54
import { Menu } from '@phosphor/widgets';
65
import * as React from 'react';
76
import { GitExtension } from '../model';
@@ -13,8 +12,6 @@ import {
1312
} from '../style/FileListStyle';
1413
import { Git } from '../tokens';
1514
import { openListedFile } from '../utils';
16-
import { GitAuthorForm } from '../widgets/AuthorBox';
17-
import { CommitBox } from './CommitBox';
1815
import { openDiffView } from './diff/DiffWidget';
1916
import { GitStage, IGitStageProps } from './GitStage';
2017
import { GitStageSimple } from './GitStageSimple';
@@ -387,29 +384,6 @@ export class FileList extends React.Component<IFileListProps, IFileListState> {
387384
);
388385
};
389386

390-
/** Commit all staged files */
391-
commitAllStagedFiles = async (message: string): Promise<void> => {
392-
try {
393-
if (
394-
message &&
395-
message !== '' &&
396-
(await this._hasIdentity(this.props.model.pathRepository))
397-
) {
398-
await this.props.model.commit(message);
399-
}
400-
} catch (error) {
401-
console.error(error);
402-
showErrorMessage('Fail to commit', error);
403-
}
404-
};
405-
406-
/** Commit all marked files */
407-
commitAllMarkedFiles = async (message: string): Promise<void> => {
408-
await this.resetAllStagedFiles();
409-
await this.addAllMarkedFiles();
410-
await this.commitAllStagedFiles(message);
411-
};
412-
413387
get markedFiles() {
414388
return this.allFilesExcludingUnmodified.filter(file =>
415389
this.props.model.getMark(file.to)
@@ -509,11 +483,6 @@ export class FileList extends React.Component<IFileListProps, IFileListState> {
509483
if (this.props.settings.composite['simpleStaging']) {
510484
return (
511485
<div>
512-
<CommitBox
513-
hasFiles={this.markedFiles.length > 0}
514-
commitFunc={this.commitAllMarkedFiles}
515-
settings={this.props.settings}
516-
/>
517486
<div>
518487
<GitStageSimple
519488
heading={'Changed'}
@@ -529,11 +498,6 @@ export class FileList extends React.Component<IFileListProps, IFileListState> {
529498
} else {
530499
return (
531500
<div onContextMenu={event => event.preventDefault()}>
532-
<CommitBox
533-
hasFiles={this.props.stagedFiles.length > 0}
534-
commitFunc={this.commitAllStagedFiles}
535-
settings={this.props.settings}
536-
/>
537501
<div>
538502
<Staged />
539503
<Changed />
@@ -543,51 +507,4 @@ export class FileList extends React.Component<IFileListProps, IFileListState> {
543507
);
544508
}
545509
}
546-
547-
/**
548-
* Does the user have an known git identity.
549-
*
550-
* @param path Top repository path
551-
* @returns Success status
552-
*/
553-
private async _hasIdentity(path: string): Promise<boolean> {
554-
// If the repository path changes, check the identity
555-
if (path !== this._previousTopRepoPath) {
556-
try {
557-
const apiResponse = await this.props.model.config();
558-
if (apiResponse.ok) {
559-
const options: JSONObject = (await apiResponse.json()).options;
560-
const keys = Object.keys(options);
561-
// If the user name or email is unknown, ask the user to set it.
562-
if (keys.indexOf('user.name') < 0 || keys.indexOf('user.email') < 0) {
563-
let result = await showDialog({
564-
title: 'Who is committing?',
565-
body: new GitAuthorForm()
566-
});
567-
if (!result.button.accept) {
568-
console.log('User refuses to set his identity.');
569-
return false;
570-
}
571-
const identity = result.value;
572-
const response = await this.props.model.config({
573-
'user.name': identity.name,
574-
'user.email': identity.email
575-
});
576-
577-
if (!response.ok) {
578-
console.log(await response.text());
579-
return false;
580-
}
581-
}
582-
this._previousTopRepoPath = path;
583-
}
584-
} catch (error) {
585-
throw new Error('Fail to set your identity. ' + error.message);
586-
}
587-
}
588-
589-
return Promise.resolve(true);
590-
}
591-
592-
private _previousTopRepoPath: string = null;
593510
}

0 commit comments

Comments
 (0)