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

Commit dff74ba

Browse files
authored
Merge pull request #1433 from atom/aw/multi-pane
Pane splitting and copying
2 parents 38c5693 + e37be49 commit dff74ba

File tree

4 files changed

+90
-24
lines changed

4 files changed

+90
-24
lines changed

lib/atom/pane-item.js

Lines changed: 73 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ import {CompositeDisposable} from 'event-kit';
55

66
import URIPattern, {nonURIMatch} from './uri-pattern';
77
import RefHolder from '../models/ref-holder';
8-
import {createItem} from '../helpers';
8+
import StubItem from '../atom-items/stub-item';
9+
import {createItem, autobind} from '../helpers';
910

1011
/**
1112
* 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 {
3738

3839
constructor(props) {
3940
super(props);
41+
autobind(this, 'opener');
4042

4143
const uriPattern = new URIPattern(this.props.uriPattern);
4244
const currentlyOpen = this.props.workspace.getPaneItems()
43-
.map(item => {
45+
.reduce((arr, item) => {
4446
const element = item.getElement ? item.getElement() : null;
4547
const match = item.getURI ? uriPattern.matches(item.getURI()) : nonURIMatch;
4648
const stub = item.setRealItem ? item : null;
47-
return {element, match, stub};
48-
})
49-
.filter(each => each.element && each.match.ok())
50-
.map(each => new OpenItem(each.match, each.element, each.stub));
49+
50+
if (element && match.ok()) {
51+
const openItem = new OpenItem(match, element, stub);
52+
arr.push(openItem);
53+
}
54+
55+
return arr;
56+
}, []);
5157

5258
this.subs = new CompositeDisposable();
5359
this.state = {uriPattern, currentlyOpen};
@@ -65,9 +71,11 @@ export default class PaneItem extends React.Component {
6571

6672
componentDidMount() {
6773
for (const openItem of this.state.currentlyOpen) {
68-
this.subs.add(this.closeListener(openItem.stubItem, openItem));
74+
this.registerCloseListener(openItem.stubItem, openItem);
6975

70-
openItem.hydrateStub();
76+
openItem.hydrateStub({
77+
copy: () => this.copyOpenItem(openItem),
78+
});
7179
}
7280

7381
this.subs.add(this.props.workspace.addOpener(this.opener));
@@ -76,7 +84,7 @@ export default class PaneItem extends React.Component {
7684
render() {
7785
return this.state.currentlyOpen.map(item => {
7886
return (
79-
<Fragment key={item.getURI()}>
87+
<Fragment key={item.getKey()}>
8088
{item.renderPortal(this.props.children)}
8189
</Fragment>
8290
);
@@ -87,7 +95,7 @@ export default class PaneItem extends React.Component {
8795
this.subs.dispose();
8896
}
8997

90-
opener = uri => {
98+
opener(uri) {
9199
const m = this.state.uriPattern.matches(uri);
92100
if (!m.ok()) {
93101
return undefined;
@@ -99,31 +107,61 @@ export default class PaneItem extends React.Component {
99107
this.setState(prevState => ({
100108
currentlyOpen: [...prevState.currentlyOpen, openItem],
101109
}), () => {
102-
const paneItem = openItem.create();
110+
const paneItem = openItem.create({
111+
copy: () => this.copyOpenItem(openItem),
112+
});
113+
this.registerCloseListener(paneItem, openItem);
114+
resolve(paneItem);
115+
});
116+
});
117+
}
103118

104-
this.subs.add(this.closeListener(paneItem, openItem));
119+
copyOpenItem(openItem) {
120+
const m = this.state.uriPattern.matches(openItem.getURI());
121+
if (!m.ok()) {
122+
return null;
123+
}
105124

106-
resolve(paneItem);
125+
const stub = StubItem.create('generic', openItem.getStubProps(), openItem.getURI());
126+
127+
const copiedItem = new OpenItem(m, stub.getElement(), stub);
128+
this.setState(prevState => ({
129+
currentlyOpen: [...prevState.currentlyOpen, copiedItem],
130+
}), () => {
131+
this.registerCloseListener(stub, copiedItem);
132+
copiedItem.hydrateStub({
133+
copy: () => this.copyOpenItem(copiedItem),
107134
});
108135
});
136+
137+
return stub;
109138
}
110139

111-
closeListener(paneItem, openItem) {
112-
return this.props.workspace.onDidDestroyPaneItem(({item}) => {
140+
registerCloseListener(paneItem, openItem) {
141+
const sub = this.props.workspace.onDidDestroyPaneItem(({item}) => {
113142
if (item === paneItem) {
143+
sub.dispose();
144+
this.subs.remove(sub);
114145
this.setState(prevState => ({
115146
currentlyOpen: prevState.currentlyOpen.filter(each => each !== openItem),
116147
}));
117148
}
118149
});
150+
151+
this.subs.add(sub);
119152
}
120153
}
121154

122155
/**
123156
* A subtree rendered through a portal onto a detached DOM node for use as the root as a PaneItem.
124157
*/
125158
class OpenItem {
159+
static nextID = 0
160+
126161
constructor(match, element = null, stub = null) {
162+
this.id = this.constructor.nextID;
163+
this.constructor.nextID++;
164+
127165
this.domNode = element || document.createElement('div');
128166
this.stubItem = stub;
129167
this.match = match;
@@ -134,18 +172,34 @@ class OpenItem {
134172
return this.match.getURI();
135173
}
136174

137-
create() {
175+
create(extra = {}) {
138176
const h = this.itemHolder.isEmpty() ? null : this.itemHolder;
139-
return createItem(this.domNode, h, this.match.getURI());
177+
return createItem(this.domNode, h, this.match.getURI(), extra);
140178
}
141179

142-
hydrateStub() {
180+
hydrateStub(extra = {}) {
143181
if (this.stubItem) {
144-
this.stubItem.setRealItem(this.create());
182+
this.stubItem.setRealItem(this.create(extra));
145183
this.stubItem = null;
146184
}
147185
}
148186

187+
getKey() {
188+
return this.id;
189+
}
190+
191+
getStubProps() {
192+
if (!this.itemHolder.isEmpty()) {
193+
const item = this.itemHolder.get();
194+
return {
195+
title: item.getTitle ? item.getTitle() : null,
196+
iconName: item.getIconName ? item.getIconName() : null,
197+
};
198+
} else {
199+
return {};
200+
}
201+
}
202+
149203
renderPortal(renderProp) {
150204
return ReactDOM.createPortal(
151205
renderProp({

lib/controllers/file-patch-controller.js

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -114,10 +114,6 @@ export default class FilePatchController extends React.Component {
114114
};
115115
}
116116

117-
copy() {
118-
return this.props.deserializers.deserialize(this.serialize());
119-
}
120-
121117
onDidDestroy(callback) {
122118
return this.emitter.on('did-destroy', callback);
123119
}

lib/helpers.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -347,11 +347,13 @@ export function extractCoAuthorsAndRawCommitMessage(commitMessage) {
347347
return {message: rawMessage, coAuthors};
348348
}
349349

350-
export function createItem(node, componentHolder = null, uri = null) {
350+
export function createItem(node, componentHolder = null, uri = null, extra = {}) {
351351
const override = {
352352
getElement: () => node,
353353

354354
getRealItem: () => componentHolder.get(),
355+
356+
...extra,
355357
};
356358

357359
if (uri) {

test/atom/pane-item.test.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,20 @@ describe('PaneItem', function() {
127127
assert.lengthOf(wrapper.update().find('Component'), 2);
128128
});
129129

130+
it('renders a different child item for each item in a different Pane', async function() {
131+
const wrapper = mount(
132+
<PaneItem workspace={workspace} uriPattern="atom-github://pattern/{id}">
133+
{() => <Component text="a prop" />}
134+
</PaneItem>,
135+
);
136+
137+
const item0 = await workspace.open('atom-github://pattern/1');
138+
const pane0 = workspace.paneForItem(item0);
139+
pane0.splitRight({copyActiveItem: true});
140+
141+
assert.lengthOf(wrapper.update().find('Component'), 2);
142+
});
143+
130144
it('passes matched parameters to its render prop', async function() {
131145
let calledWith = null;
132146
mount(

0 commit comments

Comments
 (0)