Skip to content

Commit f7584be

Browse files
authored
Merge pull request #510 from kgryte/refactor-commit-box
Refactor commit box
2 parents 83a0e39 + 3db10b8 commit f7584be

File tree

7 files changed

+527
-336
lines changed

7 files changed

+527
-336
lines changed

src/components/CommitBox.tsx

Lines changed: 127 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,114 +1,166 @@
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 {
8-
stagedCommitButtonDisabledStyle,
9-
stagedCommitButtonReadyStyle,
10-
stagedCommitButtonStyle,
11-
stagedCommitMessageStyle,
12-
stagedCommitStyle,
13-
textInputStyle
14-
} from '../style/BranchHeaderStyle';
5+
commitFormClass,
6+
commitSummaryClass,
7+
commitDescriptionClass,
8+
commitButtonClass,
9+
commitButtonDisabledClass
10+
} from '../style/CommitBox';
1511

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

30+
/**
31+
* Interface describing component state.
32+
*/
2233
export interface ICommitBoxState {
2334
/**
24-
* Commit message
35+
* Commit message summary.
2536
*/
26-
value: string;
37+
summary: string;
38+
39+
/**
40+
* Commit message description.
41+
*/
42+
description: string;
2743
}
2844

45+
/**
46+
* React component for entering a commit message.
47+
*/
2948
export class CommitBox extends React.Component<
3049
ICommitBoxProps,
3150
ICommitBoxState
3251
> {
52+
/**
53+
* Returns a React component for entering a commit message.
54+
*
55+
* @param props - component properties
56+
* @returns React component
57+
*/
3358
constructor(props: ICommitBoxProps) {
3459
super(props);
3560
this.state = {
36-
value: ''
61+
summary: '',
62+
description: ''
3763
};
3864
}
3965

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-
66+
/**
67+
* Renders the component.
68+
*
69+
* @returns fragment
70+
*/
7571
render() {
72+
const disabled = !(this.props.hasFiles && this.state.summary);
7673
return (
77-
<form
78-
className={stagedCommitStyle}
79-
onKeyPress={event => this.onKeyPress(event)}
80-
>
74+
<form className={commitFormClass}>
75+
<input
76+
className={commitSummaryClass}
77+
type="text"
78+
placeholder="Summary (required)"
79+
title="Enter a commit message summary (a single line, preferably less than 50 characters)"
80+
value={this.state.summary}
81+
onChange={this._onSummaryChange}
82+
onKeyPress={this._onSummaryKeyPress}
83+
/>
8184
<TextareaAutosize
82-
className={classes(textInputStyle, stagedCommitMessageStyle)}
83-
disabled={!this.props.hasFiles}
84-
minRows={2}
85-
onChange={this.handleChange}
86-
placeholder={this._placeholder()}
87-
value={this.state.value}
85+
className={commitDescriptionClass}
86+
minRows={5}
87+
placeholder="Description"
88+
title="Enter a commit message description"
89+
value={this.state.description}
90+
onChange={this._onDescriptionChange}
8891
/>
8992
<input
90-
className={this.commitButtonStyle(this.props.hasFiles)}
93+
className={classes(
94+
commitButtonClass,
95+
disabled ? commitButtonDisabledClass : null
96+
)}
9197
type="button"
9298
title="Commit"
93-
disabled={!(this.props.hasFiles && this.state.value)}
94-
onClick={() => {
95-
this.props.commitFunc(this.state.value);
96-
this.initializeInput();
97-
}}
99+
value="Commit"
100+
disabled={disabled}
101+
onClick={this._onCommitClick}
98102
/>
99103
</form>
100104
);
101105
}
102106

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';
107+
/**
108+
* Callback invoked upon clicking a commit message submit button.
109+
*
110+
* @param event - event object
111+
*/
112+
private _onCommitClick = () => {
113+
const msg = this.state.summary + '\n' + this.state.description + '\n';
114+
this.props.onCommit(msg);
115+
116+
// NOTE: we assume here that committing changes always works and we can safely clear component state
117+
this._reset();
118+
};
119+
120+
/**
121+
* Callback invoked upon updating a commit message description.
122+
*
123+
* @param event - event object
124+
*/
125+
private _onDescriptionChange = (event: any): void => {
126+
this.setState({
127+
description: event.target.value
128+
});
129+
};
130+
131+
/**
132+
* Callback invoked upon updating a commit message summary.
133+
*
134+
* @param event - event object
135+
*/
136+
private _onSummaryChange = (event: any): void => {
137+
this.setState({
138+
summary: event.target.value
139+
});
140+
};
141+
142+
/**
143+
* Callback invoked upon a `'keypress'` event when entering a commit message summary.
144+
*
145+
* ## Notes
146+
*
147+
* - Prevents triggering a `'submit'` action when hitting the `ENTER` key while entering a commit message summary.
148+
*
149+
* @param event - event object
150+
*/
151+
private _onSummaryKeyPress(event: any): void {
152+
if (event.which === 13) {
153+
event.preventDefault();
112154
}
155+
}
156+
157+
/**
158+
* Resets component state (e.g., in order to re-initialize the commit message input box).
159+
*/
160+
private _reset = (): void => {
161+
this.setState({
162+
summary: '',
163+
description: ''
164+
});
113165
};
114166
}

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)