Skip to content
This repository was archived by the owner on Dec 15, 2022. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
104d3c4
Rename commandRegistry to commands because shut up that's why
smashwilson Jul 17, 2019
14c7308
Use InitDialog as a motivating sample for DialogsController
smashwilson Jul 17, 2019
d40987a
Rename "createRepositoryForProjectPath" to "initialize"
smashwilson Jul 17, 2019
e768682
Replace InitDialog management with DialogsController logic
smashwilson Jul 17, 2019
e3ac43a
Missed a prop
smashwilson Jul 17, 2019
569a9bc
Move command registration into InitDialog
smashwilson Jul 17, 2019
0a83364
Accept a className in AtomTextEditor
smashwilson Jul 17, 2019
f93070d
Rewrite CloneDialog
smashwilson Jul 17, 2019
7ff2157
Shorten cloneRepositoryForProjectPath to clone
smashwilson Jul 17, 2019
a4f48b8
Roll CloneDialog into DialogsController
smashwilson Jul 17, 2019
5b993de
Move CredentialDialog into DialogsController
smashwilson Jul 17, 2019
43929f6
Remove CredentialDialog stuff from RootController
smashwilson Jul 17, 2019
d353db8
Use path.join to platform-normalize test path
smashwilson Jul 18, 2019
90ceabd
Rework OpenIssueishDialog
smashwilson Jul 18, 2019
c3c18da
Include OpenIssueishDialog in DialogsController
smashwilson Jul 18, 2019
8b5572d
Use DialogsController to open Issueish dialog
smashwilson Jul 18, 2019
38a5433
Port OpenCommitDialog
smashwilson Jul 18, 2019
2fd134c
Add OpenCommitDialog to DialogsController
smashwilson Jul 18, 2019
0083537
Test openCommitDetailItem
smashwilson Jul 18, 2019
81142cf
Manage the OpenCommitDialog via DialogsController
smashwilson Jul 18, 2019
1b9e363
AutoFocus class to reduce focus management boilerplate in dialogs
smashwilson Jul 18, 2019
4dc6d37
Dialogs should close after they're accepted
smashwilson Jul 18, 2019
020f3e4
Preselect AtomTextEditor text on request
smashwilson Jul 18, 2019
b4b5fcb
Infer and pass init path correctly
smashwilson Jul 18, 2019
8733937
Preselect destination path in InitDialog
smashwilson Jul 18, 2019
b1b8f7c
Improve the initialize dialog default a little
smashwilson Jul 18, 2019
98e6b5d
Remove visible prop from Panel
smashwilson Jul 19, 2019
466d4cb
Always render github-DialogInfo, even if it's empty
smashwilson Jul 19, 2019
f86aa3b
Manage dialog progress and error state more atomically
smashwilson Jul 19, 2019
8050d36
CSS for new dialog elements
smashwilson Jul 19, 2019
b07cfdc
Unconditionally render github-DialogInfo in other dialogs
smashwilson Jul 19, 2019
2b98cb9
Focus management and styling in Clone and Credential dialogs
smashwilson Jul 19, 2019
b183168
CSS work
smashwilson Jul 19, 2019
45be3ed
Close credentials dialog on cancel
smashwilson Jul 19, 2019
baa3c5e
Issueish dialog glitches
smashwilson Jul 19, 2019
8296d3f
Document that weird CSS transition thing
smashwilson Jul 19, 2019
eddc484
Commit dialog tweaks
smashwilson Jul 19, 2019
61acc05
Extract a DialogView for common dialog chrome
smashwilson Jul 19, 2019
4ceb9b2
Set tabIndex on AtomTextEditor element
smashwilson Jul 19, 2019
b4056d3
Change existing dialogs to use DialogView
smashwilson Jul 19, 2019
9446510
Now DialogsController is simpler too
smashwilson Jul 19, 2019
3a668f8
Merge remote-tracking branch 'origin/master' into pr-2209/atom/aw/ref…
smashwilson Jul 21, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/focus-management.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ We move focus around by registering Atom commands.
For example, in `GitTabView`:

```
this.props.commandRegistry.add(this.refRoot, {
this.props.commands.add(this.refRoot, {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a renaming I slipped in just because it was annoying me 👀

'tool-panel:unfocus': this.blur,
'core:focus-next': this.advanceFocus,
'core:focus-previous': this.retreatFocus,
Expand Down
12 changes: 12 additions & 0 deletions lib/atom/atom-text-editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ export default class AtomTextEditor extends React.Component {
didDestroySelection: PropTypes.func,

hideEmptiness: PropTypes.bool,
preselect: PropTypes.bool,
className: PropTypes.string,
tabIndex: PropTypes.number,

refModel: RefHolderPropType,

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

hideEmptiness: false,
preselect: false,
tabIndex: 0,
}

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

this.refParent.map(element => {
const editor = new TextEditor(modelProps);
editor.getElement().tabIndex = this.props.tabIndex;
if (this.props.className) {
editor.getElement().classList.add(this.props.className);
}
if (this.props.preselect) {
editor.selectAll();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I went back and forth between supporting "all text preselected" and "cursor positioned at the end of the provided buffer." I think I landed on "all text preselected" because it's slightly more flexible (one keypress to decline a suggested starting value!) and because it's slightly easier to code (I don't have to pull anything from the buffer here.)

}
element.appendChild(editor.getElement());
this.getRefModel().setter(editor);
this.refElement.setter(editor.getElement());
Expand Down
19 changes: 1 addition & 18 deletions lib/atom/panel.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,12 @@ export default class Panel extends React.Component {
children: PropTypes.element.isRequired,
options: PropTypes.object,
onDidClosePanel: PropTypes.func,
visible: PropTypes.bool,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔥 Unused props from waaaay back in the day.

itemHolder: RefHolderPropType,
}

static defaultProps = {
options: {},
onDidClosePanel: panel => {},
visible: true,
}

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

shouldComponentUpdate(newProps) {
return this.props.visible !== newProps.visible;
}

componentDidUpdate() {
if (this.didCloseItem) {
// eslint-disable-next-line no-console
console.error('Unexpected update in `Panel`: the contained panel has been destroyed');
}

if (this.panel) {
this.panel[this.props.visible ? 'show' : 'hide']();
}
}

render() {
return ReactDOM.createPortal(
this.props.children,
Expand All @@ -76,7 +59,7 @@ export default class Panel extends React.Component {
const methodName = `add${location}Panel`;

const item = createItem(this.domNode, this.props.itemHolder);
const options = {...this.props.options, visible: this.props.visible, item};
const options = {...this.props.options, item};
this.panel = this.props.workspace[methodName](options);
this.subscriptions.add(
this.panel.onDidDestroy(() => {
Expand Down
75 changes: 75 additions & 0 deletions lib/autofocus.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/**
* When triggered, automatically focus the first element ref passed to this object.
*
* To unconditionally focus a single element:
*
* ```
* class SomeComponent extends React.Component {
* constructor(props) {
* super(props);
* this.autofocus = new Autofocus();
* }
*
* render() {
* return (
* <div className="github-Form">
* <input ref={this.autofocus.target} type="text" />
* <input type="text" />
* </div>
* );
* }
*
* componentDidMount() {
* this.autofocus.trigger();
* }
* }
* ```
*
* If multiple form elements are present, use `firstTarget` to create the ref instead. The rendered ref you assign the
* lowest numeric index will be focused on trigger:
*
* ```
* class SomeComponent extends React.Component {
* constructor(props) {
* super(props);
* this.autofocus = new Autofocus();
* }
*
* render() {
* return (
* <div className="github-Form">
* {this.props.someProp && <input ref={this.autofocus.firstTarget(0)} />}
* <input ref={this.autofocus.firstTarget(1)} type="text" />
* <input type="text" />
* </div>
* );
* }
*
* componentDidMount() {
* this.autofocus.trigger();
* }
* }
* ```
*
*/
export default class AutoFocus {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering if I could generalize this somehow to deal with #1403. It'd likely need to wait on a hooks refactor, though, and be passed as a context... 🤔

constructor() {
this.index = Infinity;
this.captured = null;
}

target = element => this.firstTarget(0)(element);

firstTarget = index => element => {
if (index < this.index) {
this.index = index;
this.captured = element;
}
};

trigger() {
if (this.captured !== null) {
setTimeout(() => this.captured.focus(), 0);
}
}
}
4 changes: 2 additions & 2 deletions lib/controllers/commit-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export default class CommitController extends React.Component {
static propTypes = {
workspace: PropTypes.object.isRequired,
grammars: PropTypes.object.isRequired,
commandRegistry: PropTypes.object.isRequired,
commands: PropTypes.object.isRequired,
config: PropTypes.object.isRequired,
tooltips: PropTypes.object.isRequired,

Expand Down Expand Up @@ -104,7 +104,7 @@ export default class CommitController extends React.Component {
prepareToCommit={this.props.prepareToCommit}
commit={this.commit}
abortMerge={this.props.abortMerge}
commandRegistry={this.props.commandRegistry}
commands={this.props.commands}
maximumCharacterLimit={72}
messageBuffer={this.commitMessageBuffer}
isMerging={this.props.isMerging}
Expand Down
154 changes: 154 additions & 0 deletions lib/controllers/dialogs-controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import React from 'react';
import PropTypes from 'prop-types';

import InitDialog from '../views/init-dialog';
import CloneDialog from '../views/clone-dialog';
import CredentialDialog from '../views/credential-dialog';
import OpenIssueishDialog from '../views/open-issueish-dialog';
import OpenCommitDialog from '../views/open-commit-dialog';

const DIALOG_COMPONENTS = {
null: NullDialog,
init: InitDialog,
clone: CloneDialog,
credential: CredentialDialog,
issueish: OpenIssueishDialog,
commit: OpenCommitDialog,
};

export default class DialogsController extends React.Component {
static propTypes = {
// Model
request: PropTypes.shape({
identifier: PropTypes.string.isRequired,
isProgressing: PropTypes.bool.isRequired,
}).isRequired,

// Atom environment
workspace: PropTypes.object.isRequired,
commands: PropTypes.object.isRequired,
config: PropTypes.object.isRequired,
};

state = {
requestInProgress: null,
requestError: [null, null],
}

render() {
const DialogComponent = DIALOG_COMPONENTS[this.props.request.identifier];
return <DialogComponent {...this.getCommonProps()} />;
}

getCommonProps() {
const {request} = this.props;
const accept = request.isProgressing
? async (...args) => {
this.setState({requestError: [null, null], requestInProgress: request});
try {
const result = await request.accept(...args);
this.setState({requestInProgress: null});
return result;
} catch (error) {
this.setState({requestError: [request, error], requestInProgress: null});
return undefined;
}
} : (...args) => {
this.setState({requestError: [null, null]});
try {
return request.accept(...args);
} catch (error) {
this.setState({requestError: [request, error]});
return undefined;
}
};
const wrapped = wrapDialogRequest(request, {accept});

return {
config: this.props.config,
commands: this.props.commands,
workspace: this.props.workspace,
inProgress: this.state.requestInProgress === request,
error: this.state.requestError[0] === request ? this.state.requestError[1] : null,
request: wrapped,
};
}
}

function NullDialog() {
return null;
}

class DialogRequest {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This class is in here so that I can restrict the scope of "things that need to know the List Of All Possible Dialogs" to only this file. Hopefully that'll make it less error-prone to add new ones in the future.

constructor(identifier, params = {}) {
this.identifier = identifier;
this.params = params;
this.isProgressing = false;
this.accept = () => {};
this.cancel = () => {};
}

onAccept(cb) {
this.accept = cb;
}

onProgressingAccept(cb) {
this.isProgressing = true;
this.onAccept(cb);
}

onCancel(cb) {
this.cancel = cb;
}

getParams() {
return this.params;
}
}

function wrapDialogRequest(original, {accept}) {
const dup = new DialogRequest(original.identifier, original.params);
dup.isProgressing = original.isProgressing;
dup.onAccept(accept);
dup.onCancel(original.cancel);
return dup;
}

export const dialogRequests = {
null: {
identifier: 'null',
isProgressing: false,
params: {},
accept: () => {},
cancel: () => {},
},

init({dirPath}) {
return new DialogRequest('init', {dirPath});
},

clone(opts) {
return new DialogRequest('clone', {
sourceURL: '',
destPath: '',
...opts,
});
},

credential(opts) {
return new DialogRequest('credential', {
includeUsername: false,
includeRemember: false,
prompt: 'Please authenticate',
...opts,
});
},

issueish() {
return new DialogRequest('issueish');
},

commit() {
return new DialogRequest('commit');
},
};
4 changes: 2 additions & 2 deletions lib/controllers/editor-conflict-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {autobind} from '../helpers';
export default class EditorConflictController extends React.Component {
static propTypes = {
editor: PropTypes.object.isRequired,
commandRegistry: PropTypes.object.isRequired,
commands: PropTypes.object.isRequired,
resolutionProgress: PropTypes.object.isRequired,
isRebase: PropTypes.bool.isRequired,
refreshResolutionProgress: PropTypes.func.isRequired,
Expand Down Expand Up @@ -56,7 +56,7 @@ export default class EditorConflictController extends React.Component {
return (
<div>
{this.state.conflicts.size > 0 && (
<Commands registry={this.props.commandRegistry} target="atom-text-editor">
<Commands registry={this.props.commands} target="atom-text-editor">
<Command command="github:resolve-as-ours" callback={this.getResolverUsing([OURS])} />
<Command command="github:resolve-as-theirs" callback={this.getResolverUsing([THEIRS])} />
<Command command="github:resolve-as-base" callback={this.getResolverUsing([BASE])} />
Expand Down
8 changes: 4 additions & 4 deletions lib/controllers/git-tab-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export default class GitTabController extends React.Component {
fetchInProgress: PropTypes.bool.isRequired,

workspace: PropTypes.object.isRequired,
commandRegistry: PropTypes.object.isRequired,
commands: PropTypes.object.isRequired,
grammars: PropTypes.object.isRequired,
resolutionProgress: PropTypes.object.isRequired,
notificationManager: PropTypes.object.isRequired,
Expand All @@ -49,7 +49,7 @@ export default class GitTabController extends React.Component {
undoLastDiscard: PropTypes.func.isRequired,
discardWorkDirChangesForPaths: PropTypes.func.isRequired,
openFiles: PropTypes.func.isRequired,
initializeRepo: PropTypes.func.isRequired,
openInitializeDialog: PropTypes.func.isRequired,
controllerRef: RefHolderPropType,
};

Expand Down Expand Up @@ -107,15 +107,15 @@ export default class GitTabController extends React.Component {

resolutionProgress={this.props.resolutionProgress}
workspace={this.props.workspace}
commandRegistry={this.props.commandRegistry}
commands={this.props.commands}
grammars={this.props.grammars}
tooltips={this.props.tooltips}
notificationManager={this.props.notificationManager}
project={this.props.project}
confirm={this.props.confirm}
config={this.props.config}

initializeRepo={this.props.initializeRepo}
openInitializeDialog={this.props.openInitializeDialog}
openFiles={this.props.openFiles}
discardWorkDirChangesForPaths={this.props.discardWorkDirChangesForPaths}
undoLastDiscard={this.props.undoLastDiscard}
Expand Down
Loading