Last active
July 11, 2018 06:18
-
-
Save raphaelrk/c2d774ec679f377f4131f624ae6ebc7e to your computer and use it in GitHub Desktop.
React Native Hacker Mode. Execute JS using your fingertips.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* React Native Hacker Mode | |
* Raphael Rouvinov-Kats | |
* | |
* Screen with a REPL | |
* Helpful for debugging your app when it's in production | |
* - See the redux state by typing `return this.props.state` | |
* - Import your redux actions/dispatch to dispatch redux actions | |
* - Fetch your push token and figure out why it's not | |
* registering with your backend ( reason I wrote this! ) | |
* - Etc. | |
* | |
* Works by dynamically adding a function to the class and running it | |
* Only has access to 'this' and not global state, so make sure to | |
* Add any external functions/classes/modules to `this` in the constructor | |
* | |
* Examples: | |
// alert 'done' | |
const m = this.modules; | |
const rn = m.react_native; | |
rn.Alert.alert('test'); | |
return 'done'; | |
// get and log expo push token | |
// only works on device | |
(async () => { | |
const m = this.modules; | |
const e = m.expo; | |
const rn = m.react_native; | |
const n = e.Notifications; | |
const g = n.getExpoPushTokenAsync; | |
this.addRes('token','fetching...'); | |
const token = await g(); | |
this.addRes('token',token); | |
})(); | |
return '__no_log__'; | |
* */ | |
import React, { Component } from 'react'; | |
import {connect} from "react-redux"; | |
import JSONTree from 'react-native-json-tree' | |
import * as expo from 'expo'; | |
import * as react from 'react'; | |
import { | |
FlatList, | |
Image, | |
ScrollView, | |
Text, | |
TextInput, | |
View, | |
Alert, | |
Animated, | |
AppState, | |
AsyncStorage, | |
CameraRoll, | |
Clipboard, | |
DeviceInfo, | |
Dimensions, | |
ImagePickerIOS, | |
InteractionManager, | |
Keyboard, | |
Linking, | |
NetInfo, | |
PanResponder, | |
PixelRatio, | |
Settings, | |
Share, | |
StyleSheet, | |
Systrace, | |
Vibration, | |
YellowBox, | |
Platform, | |
} from 'react-native'; | |
class HackerMode extends Component { | |
constructor(props) { | |
super(props); | |
this.modules = { | |
expo, | |
react, | |
react_native: { | |
Alert, | |
Animated, | |
AppState, | |
AsyncStorage, | |
CameraRoll, | |
Clipboard, | |
DeviceInfo, | |
Dimensions, | |
ImagePickerIOS, | |
InteractionManager, | |
Keyboard, | |
Linking, | |
NetInfo, | |
PanResponder, | |
PixelRatio, | |
Settings, | |
Share, | |
StyleSheet, | |
Systrace, | |
Vibration, | |
YellowBox, | |
Platform, | |
}, | |
}; | |
this.state = { | |
query: '', | |
results: [ | |
{ query: 'return this.props.state', res: props.state }, | |
], | |
}; | |
} | |
shouldComponentUpdate(nextProps, nextState) { | |
return nextState !== this.state; | |
} | |
onChangeText = (text) => { | |
// remove fancy apostrophes and quotes | |
let query = text.replace(/[“”]/g,'"').replace(/[‘’]/g,"'"); | |
this.setState({ query }); | |
}; | |
addRes = (query, res) => { | |
this.setState({ | |
results: [ | |
{ query, res }, | |
...this.state.results, | |
], | |
}) | |
}; | |
blah = async () => {}; | |
run = async () => { | |
const { query } = this.state; | |
let res; | |
// Wrap object literals, otherwise they are expressed as a block | |
// via http://www.mattzabriskie.com/blog/adventures-with-react-native | |
let wrap = query.indexOf('{') === 0 && query.indexOf('}') === code.length - 1; | |
try { | |
this.blah = Function(query); | |
res = await this.blah(); | |
} catch(e) { | |
res = e; | |
} | |
if (res === '__no_log__') return; | |
this.addRes(query, res); | |
}; | |
renderItem = ({ item }) => ( | |
<View style={{ width: '100%' }}> | |
<Text style={styles.queryText}>{item.query}</Text> | |
<JSONTree data={item.res} theme={theme} invertTheme={false}/> | |
</View> | |
); | |
extractKey = (item, idx) => idx; | |
renderSeparator = () => <View style={styles.separator}/>; | |
render() { | |
return ( | |
<View style={styles.container}> | |
<View style={styles.topBar}> | |
<TouchableOpacity onPress={this.props.close}> | |
<Text style={styles.closeIcon}>✕</Text> | |
</TouchableOpacity> | |
<TextInput | |
style={styles.textInput} | |
underlineColorAndroid="transparent" | |
placeholder="let a = 5; return a;" | |
placeholderTextColor="#aaa" | |
value={this.state.query} | |
onChangeText={this.onChangeText} | |
autoCorrect={false} | |
autoCapitalize={'none'} | |
multiline | |
lines={5} | |
/> | |
<TouchableOpacity style={styles.runContainer} onPress={this.run}> | |
<Text style={styles.runText}>Run</Text> | |
</TouchableOpacity> | |
</View> | |
<ScrollView | |
maximumZoomScale={2} | |
minimumZoomScale={0} | |
horizontal | |
> | |
<FlatList | |
onEndReachedThreshold={0.5} | |
renderItem={this.renderItem} | |
data={this.state.results} | |
keyboardShouldPersistTaps="always" | |
keyExtractor={this.extractKey} | |
showsVerticalScrollIndicator={false} | |
ListHeaderComponent={this.renderSeparator} | |
ListFooterComponent={<View style={{ height: getBottomBarHeight() + 40 }} />} | |
style={styles.listStyle} | |
removeClippedSubviews | |
ItemSeparatorComponent={this.renderSeparator} | |
/> | |
</ScrollView> | |
</View> | |
); | |
} | |
} | |
const mapStateToProps = (state) => ({ state }); | |
const mapDispatchToProps = dispatch => ({}); | |
export default connect(mapStateToProps, mapDispatchToProps)(HackerMode); | |
const styles = StyleSheet.create({ | |
container: { | |
flex: 1, | |
backgroundColor: '#7d7d7d' | |
}, | |
topBar: { | |
paddingVertical: 10, | |
alignItems: 'center', | |
flexDirection: 'row', | |
backgroundColor: '#f7f7f7', | |
borderBottomColor: '#e0e0e0', | |
borderBottomWidth: 1, | |
}, | |
closeIcon: { | |
marginHorizontal: 6, | |
color: '#333', | |
fontWeight: 'bold', | |
fontSize: 20, | |
}, | |
textInput: { | |
backgroundColor: '#fff', | |
borderRadius: 4, | |
color: '#000', | |
flex: 1, | |
paddingHorizontal: 10, | |
paddingVertical: 10, | |
borderColor: '#e0e0e0', | |
borderWidth: 1, | |
fontFamily: Platform.OS === 'ios' ? 'Menlo' : 'monospace', | |
fontSize: 10, | |
}, | |
runContainer: { | |
paddingHorizontal: 20, | |
paddingVertical: 7, | |
marginHorizontal: 6, | |
backgroundColor: '#fff', | |
borderColor: '#e0e0e0', | |
borderWidth: 1, | |
}, | |
run: { | |
color: 'black', | |
}, | |
listStyle: { | |
paddingLeft: 10, | |
paddingTop: 10, | |
}, | |
separator: { | |
height: 1, | |
backgroundColor: 'black', | |
}, | |
queryText: { | |
width: '100%', | |
backgroundColor: '#333', | |
color: '#fff', | |
paddingLeft: 10, | |
paddingVertical: 3, | |
fontSize: 14, | |
fontFamily: Platform.OS === 'ios' ? 'Menlo' : 'monospace', | |
}, | |
}); | |
const getBottomBarHeight = () => ifIphoneX(34, 0); | |
const theme = { | |
scheme: 'monokai', | |
author: 'wimer hazenberg (http://www.monokai.nl)', | |
base00: '#272822', | |
base01: '#383830', | |
base02: '#49483e', | |
base03: '#75715e', | |
base04: '#a59f85', | |
base05: '#f8f8f2', | |
base06: '#f5f4f1', | |
base07: '#f9f8f5', | |
base08: '#f92672', | |
base09: '#fd971f', | |
base0A: '#f4bf75', | |
base0B: '#a6e22e', | |
base0C: '#a1efe4', | |
base0D: '#66d9ef', | |
base0E: '#ae81ff', | |
base0F: '#cc6633', | |
nestedNodeLabel: ({ style }, nodeType, expanded) => ({ | |
style: { | |
...style, | |
fontFamily: Platform.OS === 'ios' ? 'Menlo' : 'monospace', | |
} | |
}) | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment