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

Commit

Permalink
Merge pull request #1913 from atom/vy/diff-gate
Browse files Browse the repository at this point in the history
collapse large diffs && collapse/expand any diff within multi file patch view
  • Loading branch information
smashwilson committed Feb 28, 2019
2 parents 3411748 + 3b65cfe commit 96d77c5
Show file tree
Hide file tree
Showing 47 changed files with 4,143 additions and 860 deletions.
4 changes: 2 additions & 2 deletions docs/how-we-work.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,8 @@ At the end of each development sprint:
4. Run `apm uninstall github` and `apm uninstall --dev github` to ensure that you don't have any [locally installed atom/github versions](/CONTRIBUTING.md#living-on-the-edge) that would override the bundled one.
6. Create a [QA issue](https://github.com/atom/github/issues?utf8=%E2%9C%93&q=is%3Aissue+label%3Aquality) in the atom/github repository. Its title should be "_prerelease version_ QA Review" and it should have the "quality" label applied. Populate the issue body with a checklist containing the pull requests that were included in this release; these should be the ones in the "Merged" column of the project board. Omit pull requests that don't have verification steps (like renames, refactoring, adding tests or metrics, or dependency upgrades, for example).
7. Use your `atom-dev` build to verify each and check it off the list.
* :boom: _If verification fails,_
1. Note the failure in an issue comment. Close the issue.
* :boom: _If verification fails,_
1. Note the failure in an issue comment. Close the issue.
1. Correct the failure with more work in the current sprint board. Make changes on master branch.
1. Cherry changes from master to release branch.
1. Begin again by cutting a new pre-release and proceeding through the above steps once again.
Expand Down
37 changes: 35 additions & 2 deletions docs/react-component-atlas.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,8 @@ This is a high-level overview of the structure of the React component tree that
> > > [`<MultiFilePatchController>`](/lib/controllers/multi-file-patch-controller.js)
> > > [`<MultiFilePatchView>`](/lib/views/multi-file-patch-view.js)
> > >
> > > Render a sequence of git-generated file patches within a TextEditor, using decorations to include contextually
> > > relevant controls.
> > > Render a sequence of git-generated file patches within a TextEditor, using decorations to include contextually relevant controls.
> > > See [`MultiFilePatchView` atlas](#multifilepatchview-atlas) below for a more detailed breakdown.
>
> > [`<CommitPreviewItem>`](/lig/items/commit-preview-item.js)
> > [`<CommitPreviewContainer>`](/lib/containers/commit-preview-container.js)
Expand Down Expand Up @@ -178,3 +178,36 @@ This is a high-level overview of the structure of the React component tree that
> > > [`<GithubTileView>`](/lib/views/changed-files-count-view.js)
> > >
> > > Displays the GitHub logo. Clicking it opens the GitHub tab.


## `MultiFilePatchView` Atlas

> [`<MultiFilePatchView>`](/lib/views/multi-file-patch-view.js)
> > [`<AtomTextEditor>`](lib/atom/atom-text-editor.js)
> >
> > React wrapper of an [Atom TextEditor](https://atom.io/docs/api/latest/TextEditor). Each `MultiFilePatchView` contains one `AtomTextEditor`, regardless of the number of file patch.
> >
> > > [`<Gutter>`](lib/atom/gutter.js)
> > >
> > > React wrapper of Atom's [Gutter](https://atom.io/docs/api/latest/Gutter) class.
> >
> > > [`<MarkerLayer>`](lib/atom/marker-layer.js)
> > > >
> > > > React wrapper of Atom's [MarkerLayer](https://atom.io/docs/api/latest/MarkerLayer) class.
> > > >
> > > > [`<Marker>`](lib/atom/marker.js)
> > > >
> > > > React wrapper of Atom's [DisplayMarker](https://atom.io/docs/api/latest/DisplayMarker) class.
> > > >
> > > > > [`<Decoration>`](lib/atom/decoration.js)
> > > > >
> > > > > React wrapper of Atom's [Decoration](https://atom.io/docs/api/latest/Decoration) class.
> > > > >
> > > > > > [`<FilePatchHeaderView>`](lib/views/file-patch-header-view.js)
> > > > > >
> > > > > > Header above each file patch. Handles file patch level operations (e.g. discard change, stage/unstage, jump to file, expand/collapse file patch, etc.)
> > > > >
> > > > > > [`<HunkHeaderView>`](lib/views/hunk-header-view.js)
> > > > > >
> > > > > > Header above each hunk. Handles more granular stage/unstage operation (per hunk or per line).
28 changes: 26 additions & 2 deletions lib/atom/atom-text-editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ const editorCreationProps = {
...editorUpdateProps,
};

const EMPTY_CLASS = 'github-AtomTextEditor-empty';

export const TextEditorContext = React.createContext();

export default class AtomTextEditor extends React.Component {
Expand All @@ -34,7 +36,8 @@ export default class AtomTextEditor extends React.Component {
didAddSelection: PropTypes.func,
didChangeSelectionRange: PropTypes.func,
didDestroySelection: PropTypes.func,
observeSelections: PropTypes.func,

hideEmptiness: PropTypes.bool,

refModel: RefHolderPropType,

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

hideEmptiness: false,
}

constructor(props) {
Expand Down Expand Up @@ -81,15 +86,23 @@ export default class AtomTextEditor extends React.Component {
this.subs.add(
editor.onDidChangeCursorPosition(this.props.didChangeCursorPosition),
editor.observeSelections(this.observeSelections),
editor.onDidChange(this.observeEmptiness),
);

if (editor.isEmpty() && this.props.hideEmptiness) {
editor.getElement().classList.add(EMPTY_CLASS);
}

return null;
});
}

componentDidUpdate(prevProps) {
componentDidUpdate() {
const modelProps = extractProps(this.props, editorUpdateProps);
this.getRefModel().map(editor => editor.update(modelProps));

// When you look into the abyss, the abyss also looks into you
this.observeEmptiness();
}

componentWillUnmount() {
Expand All @@ -110,6 +123,17 @@ export default class AtomTextEditor extends React.Component {
this.props.didAddSelection(selection);
}

observeEmptiness = () => {
this.getRefModel().map(editor => {
if (editor.isEmpty() && this.props.hideEmptiness) {
this.refElement.map(element => element.classList.add(EMPTY_CLASS));
} else {
this.refElement.map(element => element.classList.remove(EMPTY_CLASS));
}
return null;
});
}

contains(element) {
return this.refElement.map(e => e.contains(element)).getOr(false);
}
Expand Down
7 changes: 4 additions & 3 deletions lib/atom/decoration.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const decorationPropTypes = {
onlyNonEmpty: PropTypes.bool,
omitEmptyLastRow: PropTypes.bool,
position: PropTypes.oneOf(['head', 'tail', 'before', 'after']),
order: PropTypes.number,
avoidOverflow: PropTypes.bool,
gutterName: PropTypes.string,
};
Expand Down Expand Up @@ -75,8 +76,8 @@ class BareDecoration extends React.Component {
if (
Object.keys(decorationPropTypes).some(key => this.props[key] !== prevProps[key])
) {
const opts = this.getDecorationOpts(this.props);
this.decorationHolder.map(decoration => decoration.setProperties(opts));
this.decorationHolder.map(decoration => decoration.destroy());
this.createDecoration();
}
}

Expand Down Expand Up @@ -118,7 +119,7 @@ class BareDecoration extends React.Component {
}

createDecoration() {
if (!this.item) {
if (this.usesItem() && !this.item) {
this.item = createItem(this.domNode, this.props.itemHolder);
}

Expand Down
55 changes: 54 additions & 1 deletion lib/containers/changed-file-container.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
import React from 'react';
import PropTypes from 'prop-types';
import yubikiri from 'yubikiri';
import {CompositeDisposable, Emitter} from 'event-kit';

import {autobind} from '../helpers';
import ObserveModel from '../views/observe-model';
import LoadingView from '../views/loading-view';
import ChangedFileController from '../controllers/changed-file-controller';
import PatchBuffer from '../models/patch/patch-buffer';

export default class ChangedFileContainer extends React.Component {
static propTypes = {
repository: PropTypes.object.isRequired,
stagingStatus: PropTypes.oneOf(['staged', 'unstaged']),
relPath: PropTypes.string.isRequired,
largeDiffThreshold: PropTypes.number,

workspace: PropTypes.object.isRequired,
commands: PropTypes.object.isRequired,
Expand All @@ -27,13 +30,38 @@ export default class ChangedFileContainer extends React.Component {
constructor(props) {
super(props);
autobind(this, 'fetchData', 'renderWithData');

this.emitter = new Emitter();

this.patchBuffer = new PatchBuffer();
this.lastMultiFilePatch = null;
this.sub = new CompositeDisposable();

this.state = {renderStatusOverride: null};
}

fetchData(repository) {
const staged = this.props.stagingStatus === 'staged';

const builderOpts = {};
if (this.state.renderStatusOverride !== null) {
builderOpts.renderStatusOverrides = {[this.props.relPath]: this.state.renderStatusOverride};
}
if (this.props.largeDiffThreshold !== undefined) {
builderOpts.largeDiffThreshold = this.props.largeDiffThreshold;
}

const before = () => this.emitter.emit('will-update-patch');
const after = patch => this.emitter.emit('did-update-patch', patch);

return yubikiri({
multiFilePatch: repository.getFilePatchForPath(this.props.relPath, {staged}),
multiFilePatch: repository.getFilePatchForPath(this.props.relPath, {
staged,
patchBuffer: this.patchBuffer,
builder: builderOpts,
before,
after,
}),
isPartiallyStaged: repository.isPartiallyStaged(this.props.relPath),
hasUndoHistory: repository.hasDiscardHistory(this.props.relPath),
});
Expand All @@ -48,15 +76,40 @@ export default class ChangedFileContainer extends React.Component {
}

renderWithData(data) {
const currentMultiFilePatch = data && data.multiFilePatch;
if (currentMultiFilePatch !== this.lastMultiFilePatch) {
this.sub.dispose();
/* istanbul ignore else */
if (currentMultiFilePatch) {
// Keep this component's renderStatusOverride synchronized with the FilePatch we're rendering
this.sub = new CompositeDisposable(
...currentMultiFilePatch.getFilePatches().map(fp => fp.onDidChangeRenderStatus(() => {
this.setState({renderStatusOverride: fp.getRenderStatus()});
})),
);
}
this.lastMultiFilePatch = currentMultiFilePatch;
}

if (this.props.repository.isLoading() || data === null) {
return <LoadingView />;
}

return (
<ChangedFileController
onWillUpdatePatch={this.onWillUpdatePatch}
onDidUpdatePatch={this.onDidUpdatePatch}
{...data}
{...this.props}
/>
);
}

componentWillUnmount() {
this.sub.dispose();
}

onWillUpdatePatch = cb => this.emitter.on('will-update-patch', cb);

onDidUpdatePatch = cb => this.emitter.on('did-update-patch', cb);
}
25 changes: 25 additions & 0 deletions lib/containers/commit-detail-container.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import yubikiri from 'yubikiri';
import {CompositeDisposable} from 'event-kit';

import ObserveModel from '../views/observe-model';
import LoadingView from '../views/loading-view';
Expand All @@ -13,6 +14,13 @@ export default class CommitDetailContainer extends React.Component {
itemType: PropTypes.func.isRequired,
}

constructor(props) {
super(props);

this.lastCommit = null;
this.sub = new CompositeDisposable();
}

fetchData = repository => {
return yubikiri({
commit: repository.getCommit(this.props.sha),
Expand All @@ -31,6 +39,19 @@ export default class CommitDetailContainer extends React.Component {
}

renderResult = data => {
const currentCommit = data && data.commit;
if (currentCommit !== this.lastCommit) {
this.sub.dispose();
if (currentCommit && currentCommit.isPresent()) {
this.sub = new CompositeDisposable(
...currentCommit.getMultiFileDiff().getFilePatches().map(fp => fp.onDidChangeRenderStatus(() => {
this.forceUpdate();
})),
);
}
this.lastCommit = currentCommit;
}

if (this.props.repository.isLoading() || data === null || !data.commit.isPresent()) {
return <LoadingView />;
}
Expand All @@ -42,4 +63,8 @@ export default class CommitDetailContainer extends React.Component {
/>
);
}

componentWillUnmount() {
this.sub.dispose();
}
}
Loading

0 comments on commit 96d77c5

Please sign in to comment.