-
Notifications
You must be signed in to change notification settings - Fork 404
Convert the FilePatchController hierarchy to React #617
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mostly just scanned through the tests portion of this, but they looked good. Some comments below. Great job on this! 🎉
@kuychaco reviewed with me so this can count for both of us ;)
hunks={hunks} | ||
filePath={filePath} | ||
stagingStatus={this.props.stagingStatus} | ||
isPartiallyStaged={this.props.isPartiallyStaged} | ||
registerHunkView={this.props.registerHunkView} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very happy to see we can drop this 🙇
lib/helpers.js
Outdated
|
||
export function forceUpdatePromise(component) { | ||
return new Promise(resolve => component.forceUpdate(resolve)); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we still need this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Apparently not! 🔥
lib/views/file-patch-view.js
Outdated
export default class FilePatchView { | ||
constructor(props) { | ||
this.props = props; | ||
this.selection = new FilePatchSelection(this.props.hunks); | ||
this.mouseSelectionInProgress = false; | ||
|
||
window.addEventListener('mouseup', this.mouseup); | ||
this.disposables = new CompositeDisposable(); | ||
this.disposables.add(new Disposable(() => window.removeEventListener('mouseup', this.mouseup))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should probably move the event handler stuff to componentDidMount
- not sure why this was listed above etch.initialize(this)
in the old version.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 Yeah, that's better; done.
lib/views/file-patch-view.js
Outdated
}); | ||
} else if (mode === 'line' && !this.state.selection.getSelectedLines().has(line)) { | ||
await setStatePromise(prevState => { | ||
return {selection: prevState.selection.selectLine(line, event.shiftKey)}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you have an idea of the performance characteristics of the immutable data models compared to the mutable version for large diffs?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
According to the React docs this should move us toward being able to use PureComponent
which will prevent unnecessary rendering when the selection hasn't changed. I haven't, like, measured it, though.
I was cautious to keep ListSelection.copy()
from doing an O(n) copy of the items Array. Right now it is linear with respect to the number of non-coalesced selections, but once ListSelection is immutable, too, it should be able to do a shallow-copy of that as well and be nice and snappy in that regard.
this.selection.selectLine(hunkLines[hunkLines.length - 1], true); | ||
|
||
this.mouseSelectionInProgress = true; | ||
event.persist && event.persist(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this event need to be persist
ed? I don't see any async operations in which it's used.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It does! this.setState
can be asynchronous, although it never is in enzyme. I only knew about this because it was throwing warnings in the console telling me to do this 😉
stageOrUnstageAll() { | ||
this.selectAll(); | ||
async stageOrUnstageAll() { | ||
await this.selectAll(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this need to be async
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It does because the setState
call in selectAll
can be asynchronous, which means that the this.didConfirm()
call would fire before the selection is updated.
Buuut it turns out that selectAll()
wasn't returning a Promise any more, so I should probably fix that 😉
lib/views/pane-item.js
Outdated
@@ -65,7 +65,14 @@ export default class PaneItem extends React.Component { | |||
componentWillUnmount() { | |||
this.subscriptions && this.subscriptions.dispose(); | |||
if (this.paneItem && !this.didCloseItem) { | |||
this.paneItem.destroy(); | |||
if (this.paneItem.destroy) { | |||
this.paneItem.destroy && this.paneItem.destroy(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's a duplicate check for destroy
here
lib/views/pane-item.js
Outdated
} else { | ||
const activePane = this.props.workspace.getActivePane(); | ||
if (activePane) { | ||
activePane.destroyItem(this.paneItem); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmmm, am I wrong in my understanding that we can call destroyItem
here and the pane item will have its destroy
method called?
Also, can we assume that getting the active pane will be correct? Or should we look for the item in all panes?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hah. This is actually me not understanding how Pane.addItem() works and forgetting to implement onDidDestroy
on FilePatchController. Oops.
@@ -76,4 +76,31 @@ export default class Portal extends React.Component { | |||
getElement() { | |||
return this.node; | |||
} | |||
|
|||
getView() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was wondering what you'd think about this one 😁
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, I figured I'd make the React-compatible getItem
version the default, since we're moving everything to React.
new HunkLine('line-7', 'added', -1, 8), | ||
new HunkLine('line-8', 'added', -1, 9), | ||
new HunkLine('line-7', 'added', -1, 8), // | ||
new HunkLine('line-8', 'added', -1, 9), // |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You forgot your //
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops 😅
Changes made. Thanks 💯 for the review :-D
It looks like this caused an issue with the file patch view's height; I can't scroll vertically in the file patch view anymore. |
Continuing the path in #381. I'm also starting to convert the selection models to be immutable along the way to avoid a bunch of
this.selection.selectItem(); this.forceUpdate();
awkwardness.Stuff I don't want to forget to do, in this PR or follow-on ones:
registerHunkView
props; Enzyme selectors work much more cleanly.Portal.getView()
method that returns a Proxy that (a) returns the root DOM node of the portal'd content forViewRegistry
purposes, and (b) acts like the root component instance for all other methods to supportgetTitle()
,onDidDestroy
, and so on.