Skip to content
This repository was archived by the owner on Dec 15, 2022. It is now read-only.

Commit fd6172a

Browse files
authored
Merge pull request #2209 from atom/aw/refactor-dialogs
Refactor dialog handling
2 parents 912d217 + 3a668f8 commit fd6172a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+2120
-1471
lines changed

docs/focus-management.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ We move focus around by registering Atom commands.
2929
For example, in `GitTabView`:
3030

3131
```
32-
this.props.commandRegistry.add(this.refRoot, {
32+
this.props.commands.add(this.refRoot, {
3333
'tool-panel:unfocus': this.blur,
3434
'core:focus-next': this.advanceFocus,
3535
'core:focus-previous': this.retreatFocus,

lib/atom/atom-text-editor.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ export default class AtomTextEditor extends React.Component {
3636
didDestroySelection: PropTypes.func,
3737

3838
hideEmptiness: PropTypes.bool,
39+
preselect: PropTypes.bool,
40+
className: PropTypes.string,
41+
tabIndex: PropTypes.number,
3942

4043
refModel: RefHolderPropType,
4144

@@ -49,6 +52,8 @@ export default class AtomTextEditor extends React.Component {
4952
didDestroySelection: () => {},
5053

5154
hideEmptiness: false,
55+
preselect: false,
56+
tabIndex: 0,
5257
}
5358

5459
constructor(props) {
@@ -77,6 +82,13 @@ export default class AtomTextEditor extends React.Component {
7782

7883
this.refParent.map(element => {
7984
const editor = new TextEditor(modelProps);
85+
editor.getElement().tabIndex = this.props.tabIndex;
86+
if (this.props.className) {
87+
editor.getElement().classList.add(this.props.className);
88+
}
89+
if (this.props.preselect) {
90+
editor.selectAll();
91+
}
8092
element.appendChild(editor.getElement());
8193
this.getRefModel().setter(editor);
8294
this.refElement.setter(editor.getElement());

lib/atom/panel.js

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,12 @@ export default class Panel extends React.Component {
2222
children: PropTypes.element.isRequired,
2323
options: PropTypes.object,
2424
onDidClosePanel: PropTypes.func,
25-
visible: PropTypes.bool,
2625
itemHolder: RefHolderPropType,
2726
}
2827

2928
static defaultProps = {
3029
options: {},
3130
onDidClosePanel: panel => {},
32-
visible: true,
3331
}
3432

3533
constructor(props) {
@@ -46,21 +44,6 @@ export default class Panel extends React.Component {
4644
this.setupPanel();
4745
}
4846

49-
shouldComponentUpdate(newProps) {
50-
return this.props.visible !== newProps.visible;
51-
}
52-
53-
componentDidUpdate() {
54-
if (this.didCloseItem) {
55-
// eslint-disable-next-line no-console
56-
console.error('Unexpected update in `Panel`: the contained panel has been destroyed');
57-
}
58-
59-
if (this.panel) {
60-
this.panel[this.props.visible ? 'show' : 'hide']();
61-
}
62-
}
63-
6447
render() {
6548
return ReactDOM.createPortal(
6649
this.props.children,
@@ -76,7 +59,7 @@ export default class Panel extends React.Component {
7659
const methodName = `add${location}Panel`;
7760

7861
const item = createItem(this.domNode, this.props.itemHolder);
79-
const options = {...this.props.options, visible: this.props.visible, item};
62+
const options = {...this.props.options, item};
8063
this.panel = this.props.workspace[methodName](options);
8164
this.subscriptions.add(
8265
this.panel.onDidDestroy(() => {

lib/autofocus.js

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/**
2+
* When triggered, automatically focus the first element ref passed to this object.
3+
*
4+
* To unconditionally focus a single element:
5+
*
6+
* ```
7+
* class SomeComponent extends React.Component {
8+
* constructor(props) {
9+
* super(props);
10+
* this.autofocus = new Autofocus();
11+
* }
12+
*
13+
* render() {
14+
* return (
15+
* <div className="github-Form">
16+
* <input ref={this.autofocus.target} type="text" />
17+
* <input type="text" />
18+
* </div>
19+
* );
20+
* }
21+
*
22+
* componentDidMount() {
23+
* this.autofocus.trigger();
24+
* }
25+
* }
26+
* ```
27+
*
28+
* If multiple form elements are present, use `firstTarget` to create the ref instead. The rendered ref you assign the
29+
* lowest numeric index will be focused on trigger:
30+
*
31+
* ```
32+
* class SomeComponent extends React.Component {
33+
* constructor(props) {
34+
* super(props);
35+
* this.autofocus = new Autofocus();
36+
* }
37+
*
38+
* render() {
39+
* return (
40+
* <div className="github-Form">
41+
* {this.props.someProp && <input ref={this.autofocus.firstTarget(0)} />}
42+
* <input ref={this.autofocus.firstTarget(1)} type="text" />
43+
* <input type="text" />
44+
* </div>
45+
* );
46+
* }
47+
*
48+
* componentDidMount() {
49+
* this.autofocus.trigger();
50+
* }
51+
* }
52+
* ```
53+
*
54+
*/
55+
export default class AutoFocus {
56+
constructor() {
57+
this.index = Infinity;
58+
this.captured = null;
59+
}
60+
61+
target = element => this.firstTarget(0)(element);
62+
63+
firstTarget = index => element => {
64+
if (index < this.index) {
65+
this.index = index;
66+
this.captured = element;
67+
}
68+
};
69+
70+
trigger() {
71+
if (this.captured !== null) {
72+
setTimeout(() => this.captured.focus(), 0);
73+
}
74+
}
75+
}

lib/controllers/commit-controller.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export default class CommitController extends React.Component {
2424
static propTypes = {
2525
workspace: PropTypes.object.isRequired,
2626
grammars: PropTypes.object.isRequired,
27-
commandRegistry: PropTypes.object.isRequired,
27+
commands: PropTypes.object.isRequired,
2828
config: PropTypes.object.isRequired,
2929
tooltips: PropTypes.object.isRequired,
3030

@@ -104,7 +104,7 @@ export default class CommitController extends React.Component {
104104
prepareToCommit={this.props.prepareToCommit}
105105
commit={this.commit}
106106
abortMerge={this.props.abortMerge}
107-
commandRegistry={this.props.commandRegistry}
107+
commands={this.props.commands}
108108
maximumCharacterLimit={72}
109109
messageBuffer={this.commitMessageBuffer}
110110
isMerging={this.props.isMerging}

lib/controllers/dialogs-controller.js

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
import React from 'react';
2+
import PropTypes from 'prop-types';
3+
4+
import InitDialog from '../views/init-dialog';
5+
import CloneDialog from '../views/clone-dialog';
6+
import CredentialDialog from '../views/credential-dialog';
7+
import OpenIssueishDialog from '../views/open-issueish-dialog';
8+
import OpenCommitDialog from '../views/open-commit-dialog';
9+
10+
const DIALOG_COMPONENTS = {
11+
null: NullDialog,
12+
init: InitDialog,
13+
clone: CloneDialog,
14+
credential: CredentialDialog,
15+
issueish: OpenIssueishDialog,
16+
commit: OpenCommitDialog,
17+
};
18+
19+
export default class DialogsController extends React.Component {
20+
static propTypes = {
21+
// Model
22+
request: PropTypes.shape({
23+
identifier: PropTypes.string.isRequired,
24+
isProgressing: PropTypes.bool.isRequired,
25+
}).isRequired,
26+
27+
// Atom environment
28+
workspace: PropTypes.object.isRequired,
29+
commands: PropTypes.object.isRequired,
30+
config: PropTypes.object.isRequired,
31+
};
32+
33+
state = {
34+
requestInProgress: null,
35+
requestError: [null, null],
36+
}
37+
38+
render() {
39+
const DialogComponent = DIALOG_COMPONENTS[this.props.request.identifier];
40+
return <DialogComponent {...this.getCommonProps()} />;
41+
}
42+
43+
getCommonProps() {
44+
const {request} = this.props;
45+
const accept = request.isProgressing
46+
? async (...args) => {
47+
this.setState({requestError: [null, null], requestInProgress: request});
48+
try {
49+
const result = await request.accept(...args);
50+
this.setState({requestInProgress: null});
51+
return result;
52+
} catch (error) {
53+
this.setState({requestError: [request, error], requestInProgress: null});
54+
return undefined;
55+
}
56+
} : (...args) => {
57+
this.setState({requestError: [null, null]});
58+
try {
59+
return request.accept(...args);
60+
} catch (error) {
61+
this.setState({requestError: [request, error]});
62+
return undefined;
63+
}
64+
};
65+
const wrapped = wrapDialogRequest(request, {accept});
66+
67+
return {
68+
config: this.props.config,
69+
commands: this.props.commands,
70+
workspace: this.props.workspace,
71+
inProgress: this.state.requestInProgress === request,
72+
error: this.state.requestError[0] === request ? this.state.requestError[1] : null,
73+
request: wrapped,
74+
};
75+
}
76+
}
77+
78+
function NullDialog() {
79+
return null;
80+
}
81+
82+
class DialogRequest {
83+
constructor(identifier, params = {}) {
84+
this.identifier = identifier;
85+
this.params = params;
86+
this.isProgressing = false;
87+
this.accept = () => {};
88+
this.cancel = () => {};
89+
}
90+
91+
onAccept(cb) {
92+
this.accept = cb;
93+
}
94+
95+
onProgressingAccept(cb) {
96+
this.isProgressing = true;
97+
this.onAccept(cb);
98+
}
99+
100+
onCancel(cb) {
101+
this.cancel = cb;
102+
}
103+
104+
getParams() {
105+
return this.params;
106+
}
107+
}
108+
109+
function wrapDialogRequest(original, {accept}) {
110+
const dup = new DialogRequest(original.identifier, original.params);
111+
dup.isProgressing = original.isProgressing;
112+
dup.onAccept(accept);
113+
dup.onCancel(original.cancel);
114+
return dup;
115+
}
116+
117+
export const dialogRequests = {
118+
null: {
119+
identifier: 'null',
120+
isProgressing: false,
121+
params: {},
122+
accept: () => {},
123+
cancel: () => {},
124+
},
125+
126+
init({dirPath}) {
127+
return new DialogRequest('init', {dirPath});
128+
},
129+
130+
clone(opts) {
131+
return new DialogRequest('clone', {
132+
sourceURL: '',
133+
destPath: '',
134+
...opts,
135+
});
136+
},
137+
138+
credential(opts) {
139+
return new DialogRequest('credential', {
140+
includeUsername: false,
141+
includeRemember: false,
142+
prompt: 'Please authenticate',
143+
...opts,
144+
});
145+
},
146+
147+
issueish() {
148+
return new DialogRequest('issueish');
149+
},
150+
151+
commit() {
152+
return new DialogRequest('commit');
153+
},
154+
};

lib/controllers/editor-conflict-controller.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import {autobind} from '../helpers';
1515
export default class EditorConflictController extends React.Component {
1616
static propTypes = {
1717
editor: PropTypes.object.isRequired,
18-
commandRegistry: PropTypes.object.isRequired,
18+
commands: PropTypes.object.isRequired,
1919
resolutionProgress: PropTypes.object.isRequired,
2020
isRebase: PropTypes.bool.isRequired,
2121
refreshResolutionProgress: PropTypes.func.isRequired,
@@ -56,7 +56,7 @@ export default class EditorConflictController extends React.Component {
5656
return (
5757
<div>
5858
{this.state.conflicts.size > 0 && (
59-
<Commands registry={this.props.commandRegistry} target="atom-text-editor">
59+
<Commands registry={this.props.commands} target="atom-text-editor">
6060
<Command command="github:resolve-as-ours" callback={this.getResolverUsing([OURS])} />
6161
<Command command="github:resolve-as-theirs" callback={this.getResolverUsing([THEIRS])} />
6262
<Command command="github:resolve-as-base" callback={this.getResolverUsing([BASE])} />

lib/controllers/git-tab-controller.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export default class GitTabController extends React.Component {
3535
fetchInProgress: PropTypes.bool.isRequired,
3636

3737
workspace: PropTypes.object.isRequired,
38-
commandRegistry: PropTypes.object.isRequired,
38+
commands: PropTypes.object.isRequired,
3939
grammars: PropTypes.object.isRequired,
4040
resolutionProgress: PropTypes.object.isRequired,
4141
notificationManager: PropTypes.object.isRequired,
@@ -49,7 +49,7 @@ export default class GitTabController extends React.Component {
4949
undoLastDiscard: PropTypes.func.isRequired,
5050
discardWorkDirChangesForPaths: PropTypes.func.isRequired,
5151
openFiles: PropTypes.func.isRequired,
52-
initializeRepo: PropTypes.func.isRequired,
52+
openInitializeDialog: PropTypes.func.isRequired,
5353
controllerRef: RefHolderPropType,
5454
};
5555

@@ -107,15 +107,15 @@ export default class GitTabController extends React.Component {
107107

108108
resolutionProgress={this.props.resolutionProgress}
109109
workspace={this.props.workspace}
110-
commandRegistry={this.props.commandRegistry}
110+
commands={this.props.commands}
111111
grammars={this.props.grammars}
112112
tooltips={this.props.tooltips}
113113
notificationManager={this.props.notificationManager}
114114
project={this.props.project}
115115
confirm={this.props.confirm}
116116
config={this.props.config}
117117

118-
initializeRepo={this.props.initializeRepo}
118+
openInitializeDialog={this.props.openInitializeDialog}
119119
openFiles={this.props.openFiles}
120120
discardWorkDirChangesForPaths={this.props.discardWorkDirChangesForPaths}
121121
undoLastDiscard={this.props.undoLastDiscard}

0 commit comments

Comments
 (0)