Skip to content
This repository was archived by the owner on Jun 26, 2020. It is now read-only.

Commit a8e7126

Browse files
committed
WIP magic panel
1 parent f8c0e6a commit a8e7126

File tree

2 files changed

+398
-0
lines changed

2 files changed

+398
-0
lines changed

shells/chrome/src/magic.js

Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
/**
2+
* Copyright (c) 2015-present, Facebook, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree. An additional grant
7+
* of patent rights can be found in the PATENTS file in the same directory.
8+
*
9+
* @flow
10+
*/
11+
'use strict';
12+
13+
var React = require('react');
14+
var Bridge = require('../../../agent/Bridge');
15+
var Container = require('../../../frontend/Container');
16+
var NativeStyler = require('../../../plugins/ReactNativeStyle/ReactNativeStyle.js');
17+
var Store = require('../../../frontend/Store');
18+
var keyboardNav = require('../../../frontend/keyboardNav');
19+
20+
var consts = require('../../../agent/consts');
21+
22+
import type {DOMEvent} from '../../../frontend/types';
23+
24+
type Props = {
25+
alreadyFoundReact: boolean,
26+
inject: () => void,
27+
checkForReact: (cb: (isReact: boolean) => void) => void,
28+
reload: () => void,
29+
30+
// optionals
31+
showComponentSource: ?() => void,
32+
reloadSubscribe: ?() => () => void,
33+
showAttrSource: ?() => void,
34+
executeFn: ?() => void,
35+
selectElement: ?() => void,
36+
getNewSelection: ?() => void,
37+
};
38+
39+
class Panel extends React.Component {
40+
_teardownWall: ?() => void;
41+
_keyListener: ?(e: DOMEvent) => void;
42+
_checkTimeout: ?number;
43+
_unMounted: boolean;
44+
_bridge: ?Bridge;
45+
_store: Store;
46+
_unsub: ?() => void;
47+
48+
props: Props;
49+
50+
constructor(props: Props) {
51+
super(props);
52+
this.state = {loading: true, isReact: this.props.alreadyFoundReact};
53+
this._unMounted = false;
54+
window.panel = this;
55+
}
56+
57+
getChildContext(): Object {
58+
return {
59+
store: this._store,
60+
};
61+
}
62+
63+
componentDidMount() {
64+
if (this.props.alreadyFoundReact) {
65+
this.inject();
66+
} else {
67+
this.lookForReact();
68+
}
69+
70+
if (this.props.reloadSubscribe) {
71+
this._unsub = this.props.reloadSubscribe(() => this.reload());
72+
}
73+
}
74+
75+
componentWillUnmount() {
76+
this._unMounted = true;
77+
if (this._unsub) {
78+
this._unsub();
79+
}
80+
}
81+
82+
reload() {
83+
if (this._unsub) {
84+
this._unsub();
85+
}
86+
this.teardown();
87+
if (!this._unMounted) {
88+
this.setState({loading: true}, this.props.reload);
89+
}
90+
}
91+
92+
getNewSelection() {
93+
if (!this._bridge || !this.props.getNewSelection) {
94+
return;
95+
}
96+
this.props.getNewSelection(this._bridge);
97+
}
98+
99+
sendSelection(id: string) {
100+
if (!this._bridge || (!id && !this._store.selected)) {
101+
return;
102+
}
103+
this.props.selectElement(id || this._store.selected, this._bridge);
104+
}
105+
106+
inspectComponent(vbl: string) {
107+
this.props.showComponentSource(vbl || '$r');
108+
}
109+
110+
viewSource(id: string) {
111+
if (!this._bridge) {
112+
return;
113+
}
114+
this._bridge.send('putSelectedInstance', id);
115+
setTimeout(() => {
116+
this.props.showComponentSource('__REACT_DEVTOOLS_GLOBAL_HOOK__.$inst');
117+
}, 100);
118+
}
119+
120+
teardown() {
121+
if (this._keyListener) {
122+
window.removeEventListener('keydown', this._keyListener);
123+
this._keyListener = null;
124+
}
125+
if (this._bridge) {
126+
this._bridge.send('shutdown');
127+
}
128+
if (this._teardownWall) {
129+
this._teardownWall();
130+
this._teardownWall = null;
131+
}
132+
this._bridge = null;
133+
}
134+
135+
inject() {
136+
this.props.inject((wall, teardown) => {
137+
this._teardownWall = teardown;
138+
139+
this._bridge = new Bridge();
140+
// $FlowFixMe flow thinks `this._bridge` might be null
141+
this._bridge.attach(wall);
142+
143+
// xx FlowFixMe this._bridge is not null
144+
if (this._bridge) {
145+
this._store = new Store(this._bridge);
146+
}
147+
this._keyListener = keyboardNav(this._store, window);
148+
149+
window.addEventListener('keydown', this._keyListener);
150+
151+
this._store.on('connected', () => {
152+
this.setState({loading: false});
153+
this.getNewSelection();
154+
});
155+
});
156+
}
157+
158+
componentDidUpdate() {
159+
if (!this.state.isReact) {
160+
if (!this._checkTimeout) {
161+
this._checkTimeout = setTimeout(() => {
162+
this._checkTimeout = null;
163+
this.lookForReact();
164+
}, 200);
165+
}
166+
}
167+
}
168+
169+
lookForReact() {
170+
this.props.checkForReact(isReact => {
171+
if (isReact) {
172+
this.setState({isReact: true, loading: true});
173+
this.inject();
174+
} else {
175+
console.log('still looking...');
176+
this.setState({isReact: false, loading: false});
177+
}
178+
});
179+
}
180+
181+
render() {
182+
if (this.state.loading) {
183+
return (
184+
<div style={styles.loading}>
185+
<h1>Connecting to react...</h1>
186+
<br/>
187+
If this is React Native, you need to interact with the app (just tap the screen) in order to establish the bridge.
188+
</div>
189+
);
190+
}
191+
if (!this.state.isReact) {
192+
return <span>Looking for react...</span>;
193+
}
194+
return (
195+
<Container
196+
reload={this.reload.bind(this)}
197+
menuItems={{
198+
attr: (id, node, val, path, name) => {
199+
if (!val || node.get('nodeType') !== 'Composite' || val[consts.type] !== 'function') {
200+
return;
201+
}
202+
return [this.props.showAttrSource && {
203+
title: 'Show Source',
204+
action: () => this.props.showAttrSource(path),
205+
}, this.props.executeFn && {
206+
title: 'Execute function',
207+
action: () => this.props.executeFn(path),
208+
}];
209+
},
210+
tree: (id, node) => {
211+
return [this.props.showComponentSource && node.get('nodeType') === 'Composite' && {
212+
title: 'Show Source',
213+
action: () => this.viewSource(id),
214+
}, this.props.selectElement && this._store.capabilities.dom && {
215+
title: 'Show in Elements Pane',
216+
action: () => this.sendSelection(id),
217+
}];
218+
},
219+
}}
220+
extraPanes={this._store.capabilities.rnStyle && [panelRNStyle(this._bridge)]}
221+
/>
222+
);
223+
}
224+
}
225+
226+
Panel.childContextTypes = {
227+
store: React.PropTypes.object,
228+
};
229+
230+
var panelRNStyle = bridge => (node, id) => {
231+
var props = node.get('props');
232+
if (!props || !props.style) {
233+
return <strong>No style</strong>;
234+
}
235+
return (
236+
<div>
237+
<h3>React Native Style Editor</h3>
238+
<NativeStyler id={id} bridge={bridge} />
239+
</div>
240+
);
241+
};
242+
243+
var styles = {
244+
chromePane: {
245+
display: 'flex',
246+
},
247+
stretch: {
248+
flex: 1,
249+
},
250+
loading: {
251+
textAlign: 'center',
252+
color: '#888',
253+
padding: 30,
254+
flex: 1,
255+
},
256+
};
257+
258+
module.exports = Panel;
259+

shells/chrome/src/panel.js

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
/**
2+
* Copyright (c) 2015-present, Facebook, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree. An additional grant
7+
* of patent rights can be found in the PATENTS file in the same directory.
8+
*
9+
* @flow
10+
*/
11+
'use strict';
12+
13+
var checkForReact = require('./panel/checkForReact');
14+
var inject = require('./panel/inject');
15+
16+
type Listenable = {
17+
addListener: (fn: (message: Object) => void) => void,
18+
}
19+
20+
type Port = {
21+
disconnect: () => void,
22+
onMessage: Listenable,
23+
onDisconnect: Listenable,
24+
postMessage: (data: Object) => void,
25+
};
26+
27+
declare var chrome: {
28+
devtools: {
29+
network: {
30+
onNavigated: {
31+
addListener: (fn: () => void) => void,
32+
removeListener: (fn: () => void) => void,
33+
},
34+
},
35+
inspectedWindow: {
36+
eval: (code: string, cb?: (res: any, err: ?Object) => any) => void,
37+
tabId: number,
38+
},
39+
},
40+
runtime: {
41+
getURL: (path: string) => string,
42+
connect: (config: Object) => Port,
43+
},
44+
};
45+
46+
var config = {
47+
reload,
48+
checkForReact,
49+
reloadSubscribe(reload) {
50+
chrome.devtools.network.onNavigated.addListener(reload);
51+
return () => {
52+
chrome.devtools.network.onNavigated.removeListener(reload);
53+
};
54+
},
55+
getNewSelectiong(bridge) {
56+
chrome.devtools.inspectedWindow.eval('window.__REACT_DEVTOOLS_GLOBAL_HOOK__.$0 = $0');
57+
bridge.send('checkSelection');
58+
},
59+
selectElement(bridge, id) {
60+
bridge.send('putSelectedNode', id);
61+
setTimeout(() => {
62+
chrome.devtools.inspectedWindow.eval('inspect(window.__REACT_DEVTOOLS_GLOBAL_HOOK__.$node)');
63+
}, 100);
64+
},
65+
showComponentSource(vbl) {
66+
var code = `Object.getOwnPropertyDescriptor(window.${vbl}.__proto__.__proto__, 'isMounted') &&
67+
Object.getOwnPropertyDescriptor(window.${vbl}.__proto__.__proto__, 'isMounted').value ?
68+
inspect(window.${vbl}.render) : inspect(window.${vbl}.constructor)`;
69+
chrome.devtools.inspectedWindow.eval(code, (res, err) => {
70+
if (err) {
71+
console.error('Failed to inspect component', err);
72+
}
73+
});
74+
},
75+
showAttrSource(path) {
76+
var attrs = '[' + path.map(m => JSON.stringify(m)).join('][') + ']';
77+
var code = 'inspect(window.$r' + attrs + ')';
78+
chrome.devtools.inspectedWindow.eval(code, (res, err) => {
79+
if (err) {
80+
console.error('Failed to inspect source', err);
81+
}
82+
});
83+
},
84+
executeFn(path) {
85+
var attrs = '[' + path.map(m => JSON.stringify(m)).join('][') + ']';
86+
var code = 'window.$r' + attrs + '()';
87+
chrome.devtools.inspectedWindow.eval(code, (res, err) => {
88+
if (err) {
89+
console.error('Failed to call function', err);
90+
}
91+
});
92+
},
93+
inject(done) {
94+
inject(chrome.runtime.getURL('build/backend.js'), () => {
95+
var port = this._port = chrome.runtime.connect({
96+
name: '' + chrome.devtools.inspectedWindow.tabId,
97+
});
98+
var disconnected = false;
99+
100+
var wall = {
101+
listen(fn) {
102+
port.onMessage.addListener(message => fn(message));
103+
},
104+
send(data) {
105+
if (disconnected) {
106+
return;
107+
}
108+
port.postMessage(data);
109+
},
110+
};
111+
112+
port.onDisconnect.addListener(() => {
113+
disconnected = true;
114+
});
115+
done(wall, () => port.disconnect());
116+
});
117+
},
118+
};
119+
120+
121+
var globalHook = require('../../../backend/GlobalHook');
122+
globalHook(window);
123+
var Panel = require('./magic');
124+
var React = require('react');
125+
126+
var node = document.getElementById('container');
127+
128+
function reload() {
129+
setTimeout(() => {
130+
React.unmountComponentAtNode(node);
131+
node.innerHTML = '';
132+
React.render(<Panel {...config} />, node);
133+
}, 100);
134+
}
135+
136+
React.render(<Panel alreadyFoundReact={true} {...config} />, node);
137+
138+
139+

0 commit comments

Comments
 (0)