Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit e3aa968

Browse files
authored
Merge pull request atom#1510 from atom/aw/markers-and-layers
Markers and Layers and Contexts
2 parents 01e5668 + b204412 commit e3aa968

File tree

10 files changed

+612
-72
lines changed

10 files changed

+612
-72
lines changed

lib/atom/atom-text-editor.js

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import React from 'react';
1+
import React, {Fragment} from 'react';
22
import PropTypes from 'prop-types';
33
import {CompositeDisposable} from 'event-kit';
44

55
import RefHolder from '../models/ref-holder';
6-
import {autobind} from '../helpers';
6+
import {autobind, extractProps} from '../helpers';
77

88
const editorProps = {
99
autoIndent: PropTypes.bool,
@@ -35,19 +35,21 @@ const editorProps = {
3535
showCursorOnSelection: PropTypes.bool,
3636
};
3737

38+
export const TextEditorContext = React.createContext();
39+
3840
export default class AtomTextEditor extends React.PureComponent {
3941
static propTypes = {
4042
...editorProps,
4143
text: PropTypes.string,
4244
didChange: PropTypes.func,
4345
didChangeCursorPosition: PropTypes.func,
46+
children: PropTypes.element,
4447
}
4548

4649
static defaultProps = {
4750
text: '',
4851
didChange: () => {},
4952
didChangeCursorPosition: () => {},
50-
refElement: () => {},
5153
}
5254

5355
constructor(props, context) {
@@ -58,11 +60,21 @@ export default class AtomTextEditor extends React.PureComponent {
5860
this.suppressChange = false;
5961

6062
this.refElement = new RefHolder();
63+
this.refModel = new RefHolder();
64+
65+
this.subs.add(
66+
this.refElement.observe(element => this.refModel.setter(element.getModel())),
67+
);
6168
}
6269

6370
render() {
6471
return (
65-
<atom-text-editor ref={this.refElement.setter} />
72+
<Fragment>
73+
<atom-text-editor ref={this.refElement.setter} />
74+
<TextEditorContext.Provider value={this.refModel}>
75+
{this.props.children}
76+
</TextEditorContext.Provider>
77+
</Fragment>
6678
);
6779
}
6880

@@ -94,12 +106,7 @@ export default class AtomTextEditor extends React.PureComponent {
94106
}
95107

96108
setAttributesOnElement(theProps) {
97-
const modelProps = Object.keys(editorProps).reduce((ps, key) => {
98-
if (theProps[key] !== undefined) {
99-
ps[key] = theProps[key];
100-
}
101-
return ps;
102-
}, {});
109+
const modelProps = extractProps(this.props, editorProps);
103110

104111
const editor = this.getModel();
105112
editor.update(modelProps);

lib/atom/decoration.js

Lines changed: 102 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
import React from 'react';
22
import ReactDOM from 'react-dom';
33
import PropTypes from 'prop-types';
4-
import {CompositeDisposable} from 'event-kit';
4+
import {Disposable} from 'event-kit';
55

6-
import {createItem} from '../helpers';
6+
import {createItem, autobind} from '../helpers';
77
import {RefHolderPropType} from '../prop-types';
8+
import {TextEditorContext} from './atom-text-editor';
9+
import {MarkerContext} from './marker';
10+
import RefHolder from '../models/ref-holder';
811

9-
export default class Decoration extends React.Component {
12+
class WrappedDecoration extends React.Component {
1013
static propTypes = {
11-
editor: PropTypes.object.isRequired,
12-
marker: PropTypes.object.isRequired,
14+
editorHolder: RefHolderPropType.isRequired,
15+
markerHolder: RefHolderPropType.isRequired,
1316
type: PropTypes.oneOf(['line', 'line-number', 'highlight', 'overlay', 'gutter', 'block']).isRequired,
1417
position: PropTypes.oneOf(['head', 'tail', 'before', 'after']),
1518
className: PropTypes.string,
@@ -26,10 +29,14 @@ export default class Decoration extends React.Component {
2629
constructor(props, context) {
2730
super(props, context);
2831

29-
this.decoration = null;
30-
this.subscriptions = new CompositeDisposable();
32+
autobind(this, 'observeParents');
33+
34+
this.decorationHolder = new RefHolder();
35+
this.editorSub = new Disposable();
36+
this.markerSub = new Disposable();
3137

3238
this.domNode = null;
39+
this.item = null;
3340
if (['gutter', 'overlay', 'block'].includes(this.props.type)) {
3441
this.domNode = document.createElement('div');
3542
this.domNode.className = 'react-atom-decoration';
@@ -41,40 +48,20 @@ export default class Decoration extends React.Component {
4148
}
4249

4350
componentDidMount() {
44-
this.setupDecoration();
51+
this.editorSub = this.props.editorHolder.observe(this.observeParents);
52+
this.markerSub = this.props.markerHolder.observe(this.observeParents);
4553
}
4654

47-
shouldComponentUpdate(nextProps) {
48-
if (
49-
this.props.editor !== nextProps.editor ||
50-
this.props.marker !== nextProps.marker ||
51-
this.props.type !== nextProps.type ||
52-
this.props.position !== nextProps.position ||
53-
this.props.className !== nextProps.className ||
54-
this.props.children !== nextProps.children
55-
) { return true; }
56-
57-
// Compare additional options.
58-
const optionKeys = Object.keys(this.props.options);
59-
const nextOptionKeys = Object.keys(nextProps.options);
60-
61-
if (optionKeys.length !== nextOptionKeys.length) {
62-
return true;
63-
} else {
64-
for (let i = 0; i < optionKeys.length; i++) {
65-
const key = optionKeys[i];
66-
if (this.props.options[key] !== nextProps.options[key]) {
67-
return true;
68-
}
69-
}
55+
componentDidUpdate(prevProps) {
56+
if (this.props.editorHolder !== prevProps.editorHolder) {
57+
this.editorSub.dispose();
58+
this.editorSub = this.state.editorHolder.observe(this.observeParents);
7059
}
7160

72-
return false;
73-
}
74-
75-
componentDidUpdate() {
76-
this.decoration && this.decoration.destroy();
77-
this.setupDecoration();
61+
if (this.props.markerHolder !== prevProps.markerHolder) {
62+
this.markerSub.dispose();
63+
this.markerSub = this.state.markerHolder.observe(this.observeParents);
64+
}
7865
}
7966

8067
render() {
@@ -88,33 +75,98 @@ export default class Decoration extends React.Component {
8875
}
8976
}
9077

91-
setupDecoration() {
92-
if (this.decoration) {
78+
observeParents() {
79+
if (this.props.editorHolder.isEmpty() || this.props.markerHolder.isEmpty()) {
9380
return;
9481
}
9582

96-
let item = null;
97-
if (this.usesItem()) {
98-
item = createItem(this.domNode, this.props.itemHolder);
83+
this.createDecoration();
84+
}
85+
86+
createDecoration() {
87+
this.decorationHolder.map(decoration => decoration.destroy());
88+
89+
if (!this.item) {
90+
this.item = createItem(this.domNode, this.props.itemHolder);
9991
}
10092

10193
const options = {
10294
...this.props.options,
10395
type: this.props.type,
10496
position: this.props.position,
10597
class: this.props.className,
106-
item,
98+
item: this.item,
10799
};
108100

109-
this.decoration = this.props.editor.decorateMarker(this.props.marker, options);
110-
this.subscriptions.add(this.decoration.onDidDestroy(() => {
111-
this.decoration = null;
112-
this.subscriptions.dispose();
113-
}));
101+
const editor = this.props.editorHolder.get();
102+
const marker = this.props.markerHolder.get();
103+
104+
this.decorationHolder.setter(
105+
editor.decorateMarker(marker, options),
106+
);
114107
}
115108

116109
componentWillUnmount() {
117-
this.decoration && this.decoration.destroy();
118-
this.subscriptions.dispose();
110+
this.decorationHolder.map(decoration => decoration.destroy());
111+
this.editorSub.dispose();
112+
this.markerSub.dispose();
113+
}
114+
}
115+
116+
export default class Decoration extends React.Component {
117+
static propTypes = {
118+
editor: PropTypes.object,
119+
marker: PropTypes.object,
120+
}
121+
122+
constructor(props) {
123+
super(props);
124+
125+
this.state = {
126+
editorHolder: RefHolder.on(this.props.editor),
127+
markerHolder: RefHolder.on(this.props.marker),
128+
};
129+
}
130+
131+
static getDerivedStateFromProps(props, state) {
132+
const editorChanged = state.editorHolder.map(editor => editor !== props.editor).getOr(props.editor !== undefined);
133+
const markerChanged = state.markerHolder.map(marker => marker !== props.marker).getOr(props.marker !== undefined);
134+
135+
if (!editorChanged && !markerChanged) {
136+
return null;
137+
}
138+
139+
const nextState = {};
140+
if (editorChanged) {
141+
nextState.editorHolder = RefHolder.on(props.editor);
142+
}
143+
if (markerChanged) {
144+
nextState.markerHolder = RefHolder.on(props.marker);
145+
}
146+
return nextState;
147+
}
148+
149+
render() {
150+
if (!this.state.editorHolder.isEmpty() && !this.state.markerHolder.isEmpty()) {
151+
return (
152+
<WrappedDecoration
153+
{...this.props}
154+
editorHolder={this.state.editorHolder}
155+
markerHolder={this.state.markerHolder}
156+
/>
157+
);
158+
}
159+
160+
return (
161+
<TextEditorContext.Consumer>
162+
{editorHolder => (
163+
<MarkerContext.Consumer>
164+
{markerHolder => (
165+
<WrappedDecoration {...this.props} editorHolder={editorHolder} markerHolder={markerHolder} />
166+
)}
167+
</MarkerContext.Consumer>
168+
)}
169+
</TextEditorContext.Consumer>
170+
);
119171
}
120172
}

lib/atom/marker-layer.js

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import React from 'react';
2+
import PropTypes from 'prop-types';
3+
import {Disposable} from 'event-kit';
4+
5+
import {autobind, extractProps} from '../helpers';
6+
import RefHolder from '../models/ref-holder';
7+
import {TextEditorContext} from './atom-text-editor';
8+
9+
const markerLayerProps = {
10+
maintainHistory: PropTypes.bool,
11+
persistent: PropTypes.bool,
12+
};
13+
14+
export const MarkerLayerContext = React.createContext();
15+
16+
class WrappedMarkerLayer extends React.Component {
17+
static propTypes = {
18+
...markerLayerProps,
19+
editor: PropTypes.object,
20+
children: PropTypes.element,
21+
handleID: PropTypes.func,
22+
};
23+
24+
static defaultProps = {
25+
handleID: () => {},
26+
}
27+
28+
constructor(props) {
29+
super(props);
30+
31+
autobind(this, 'createLayer');
32+
33+
this.sub = new Disposable();
34+
this.layerHolder = new RefHolder();
35+
this.state = {
36+
editorHolder: RefHolder.on(this.props.editor),
37+
};
38+
}
39+
40+
static getDerivedStateFromProps(props, state) {
41+
if (state.editorHolder.map(e => e === props.editor).getOr(props.editor === undefined)) {
42+
return null;
43+
}
44+
45+
return {
46+
editorHolder: RefHolder.on(props.editor),
47+
};
48+
}
49+
50+
componentDidMount() {
51+
this.observeEditor();
52+
}
53+
54+
render() {
55+
return (
56+
<MarkerLayerContext.Provider value={this.layerHolder}>
57+
{this.props.children}
58+
</MarkerLayerContext.Provider>
59+
);
60+
}
61+
62+
componentDidUpdate(prevProps, prevState) {
63+
if (this.state.editorHolder !== prevState.editorHolder) {
64+
this.observeEditor();
65+
}
66+
}
67+
68+
componentWillUnmount() {
69+
this.layerHolder.map(layer => layer.destroy());
70+
this.sub.dispose();
71+
}
72+
73+
observeEditor() {
74+
this.sub.dispose();
75+
this.sub = this.state.editorHolder.observe(this.createLayer);
76+
}
77+
78+
createLayer() {
79+
this.layerHolder.map(layer => layer.destroy());
80+
81+
const options = extractProps(this.props, markerLayerProps);
82+
83+
84+
this.layerHolder.setter(
85+
this.state.editorHolder.map(editor => editor.addMarkerLayer(options)).getOr(null),
86+
);
87+
this.props.handleID(this.getID());
88+
}
89+
90+
getID() {
91+
return this.layerHolder.map(layer => layer.id).getOr(undefined);
92+
}
93+
}
94+
95+
export default class MarkerLayer extends React.Component {
96+
render() {
97+
return (
98+
<TextEditorContext.Consumer>
99+
{editor => <WrappedMarkerLayer editor={editor} {...this.props} />}
100+
</TextEditorContext.Consumer>
101+
);
102+
}
103+
}

0 commit comments

Comments
 (0)