diff --git a/lib/atom/pane-item.js b/lib/atom/pane-item.js index 4f697fa3f2..ecbe19df39 100644 --- a/lib/atom/pane-item.js +++ b/lib/atom/pane-item.js @@ -5,7 +5,8 @@ import {CompositeDisposable} from 'event-kit'; import URIPattern, {nonURIMatch} from './uri-pattern'; import RefHolder from '../models/ref-holder'; -import {createItem} from '../helpers'; +import StubItem from '../atom-items/stub-item'; +import {createItem, autobind} from '../helpers'; /** * PaneItem registers an opener with the current Atom workspace as long as this component is mounted. The opener will @@ -37,17 +38,22 @@ export default class PaneItem extends React.Component { constructor(props) { super(props); + autobind(this, 'opener'); const uriPattern = new URIPattern(this.props.uriPattern); const currentlyOpen = this.props.workspace.getPaneItems() - .map(item => { + .reduce((arr, item) => { const element = item.getElement ? item.getElement() : null; const match = item.getURI ? uriPattern.matches(item.getURI()) : nonURIMatch; const stub = item.setRealItem ? item : null; - return {element, match, stub}; - }) - .filter(each => each.element && each.match.ok()) - .map(each => new OpenItem(each.match, each.element, each.stub)); + + if (element && match.ok()) { + const openItem = new OpenItem(match, element, stub); + arr.push(openItem); + } + + return arr; + }, []); this.subs = new CompositeDisposable(); this.state = {uriPattern, currentlyOpen}; @@ -65,9 +71,11 @@ export default class PaneItem extends React.Component { componentDidMount() { for (const openItem of this.state.currentlyOpen) { - this.subs.add(this.closeListener(openItem.stubItem, openItem)); + this.registerCloseListener(openItem.stubItem, openItem); - openItem.hydrateStub(); + openItem.hydrateStub({ + copy: () => this.copyOpenItem(openItem), + }); } this.subs.add(this.props.workspace.addOpener(this.opener)); @@ -76,7 +84,7 @@ export default class PaneItem extends React.Component { render() { return this.state.currentlyOpen.map(item => { return ( - + {item.renderPortal(this.props.children)} ); @@ -87,7 +95,7 @@ export default class PaneItem extends React.Component { this.subs.dispose(); } - opener = uri => { + opener(uri) { const m = this.state.uriPattern.matches(uri); if (!m.ok()) { return undefined; @@ -99,23 +107,48 @@ export default class PaneItem extends React.Component { this.setState(prevState => ({ currentlyOpen: [...prevState.currentlyOpen, openItem], }), () => { - const paneItem = openItem.create(); + const paneItem = openItem.create({ + copy: () => this.copyOpenItem(openItem), + }); + this.registerCloseListener(paneItem, openItem); + resolve(paneItem); + }); + }); + } - this.subs.add(this.closeListener(paneItem, openItem)); + copyOpenItem(openItem) { + const m = this.state.uriPattern.matches(openItem.getURI()); + if (!m.ok()) { + return null; + } - resolve(paneItem); + const stub = StubItem.create('generic', openItem.getStubProps(), openItem.getURI()); + + const copiedItem = new OpenItem(m, stub.getElement(), stub); + this.setState(prevState => ({ + currentlyOpen: [...prevState.currentlyOpen, copiedItem], + }), () => { + this.registerCloseListener(stub, copiedItem); + copiedItem.hydrateStub({ + copy: () => this.copyOpenItem(copiedItem), }); }); + + return stub; } - closeListener(paneItem, openItem) { - return this.props.workspace.onDidDestroyPaneItem(({item}) => { + registerCloseListener(paneItem, openItem) { + const sub = this.props.workspace.onDidDestroyPaneItem(({item}) => { if (item === paneItem) { + sub.dispose(); + this.subs.remove(sub); this.setState(prevState => ({ currentlyOpen: prevState.currentlyOpen.filter(each => each !== openItem), })); } }); + + this.subs.add(sub); } } @@ -123,7 +156,12 @@ export default class PaneItem extends React.Component { * A subtree rendered through a portal onto a detached DOM node for use as the root as a PaneItem. */ class OpenItem { + static nextID = 0 + constructor(match, element = null, stub = null) { + this.id = this.constructor.nextID; + this.constructor.nextID++; + this.domNode = element || document.createElement('div'); this.stubItem = stub; this.match = match; @@ -134,18 +172,34 @@ class OpenItem { return this.match.getURI(); } - create() { + create(extra = {}) { const h = this.itemHolder.isEmpty() ? null : this.itemHolder; - return createItem(this.domNode, h, this.match.getURI()); + return createItem(this.domNode, h, this.match.getURI(), extra); } - hydrateStub() { + hydrateStub(extra = {}) { if (this.stubItem) { - this.stubItem.setRealItem(this.create()); + this.stubItem.setRealItem(this.create(extra)); this.stubItem = null; } } + getKey() { + return this.id; + } + + getStubProps() { + if (!this.itemHolder.isEmpty()) { + const item = this.itemHolder.get(); + return { + title: item.getTitle ? item.getTitle() : null, + iconName: item.getIconName ? item.getIconName() : null, + }; + } else { + return {}; + } + } + renderPortal(renderProp) { return ReactDOM.createPortal( renderProp({ diff --git a/lib/controllers/file-patch-controller.js b/lib/controllers/file-patch-controller.js index d4982c5f8f..ffe6bc062e 100644 --- a/lib/controllers/file-patch-controller.js +++ b/lib/controllers/file-patch-controller.js @@ -114,10 +114,6 @@ export default class FilePatchController extends React.Component { }; } - copy() { - return this.props.deserializers.deserialize(this.serialize()); - } - onDidDestroy(callback) { return this.emitter.on('did-destroy', callback); } diff --git a/lib/helpers.js b/lib/helpers.js index 8cdebf1f21..18bcadbe45 100644 --- a/lib/helpers.js +++ b/lib/helpers.js @@ -347,11 +347,13 @@ export function extractCoAuthorsAndRawCommitMessage(commitMessage) { return {message: rawMessage, coAuthors}; } -export function createItem(node, componentHolder = null, uri = null) { +export function createItem(node, componentHolder = null, uri = null, extra = {}) { const override = { getElement: () => node, getRealItem: () => componentHolder.get(), + + ...extra, }; if (uri) { diff --git a/test/atom/pane-item.test.js b/test/atom/pane-item.test.js index 29c79ffe51..6fe95f71e1 100644 --- a/test/atom/pane-item.test.js +++ b/test/atom/pane-item.test.js @@ -127,6 +127,20 @@ describe('PaneItem', function() { assert.lengthOf(wrapper.update().find('Component'), 2); }); + it('renders a different child item for each item in a different Pane', async function() { + const wrapper = mount( + + {() => } + , + ); + + const item0 = await workspace.open('atom-github://pattern/1'); + const pane0 = workspace.paneForItem(item0); + pane0.splitRight({copyActiveItem: true}); + + assert.lengthOf(wrapper.update().find('Component'), 2); + }); + it('passes matched parameters to its render prop', async function() { let calledWith = null; mount(