Added storage view, and updated the UI in general

This commit is contained in:
2018-08-21 07:10:22 +02:00
parent 41570fb15e
commit 5a10498da7
29 changed files with 472 additions and 24 deletions

View File

@@ -6,6 +6,7 @@ import {
Button,
KeyboardAvoidingView,
SafeAreaView,
AsyncStorage,
} from 'react-native';
import {
DevTool,
@@ -26,12 +27,18 @@ xhr = new XMLHttpRequest();
xhr.open('GET', 'https://google.com/sdfsfsdfsfdf');
xhr.send();
AsyncStorage.setItem('a', 'b');
AsyncStorage.setItem('b', 'c');
AsyncStorage.setItem('c', 'd');
const t = new Promise((resolve, reject) => {
setTimeout(() => {
throw new Error('everything is broken');
}, 1000);
});
export default class App extends React.Component {
render() {
return (

View File

@@ -1,6 +1,6 @@
{
"name": "react-native-debug-console",
"version": "1.0.0-beta.8",
"version": "1.0.0-beta.9",
"main": "src/index.js",
"repository": {
"url": "https://github.com/morten-olsen/react-native-debugger"

View File

@@ -3,21 +3,33 @@ import {
StyleSheet,
View,
Text,
Button,
TextInput,
} from 'react-native';
import styled from 'styled-components/native';
import State from '../../data/State';
import log from '../../../log';
import Icon from '../../base/Icon';
const Button = styled.TouchableOpacity`
padding: 12px 8px;
`;
const styles = StyleSheet.create({
container: {
borderColor: '#ccc',
borderTopWidth: 1,
flexDirection: 'row',
alignItems: 'center',
paddingLeft: 10,
paddingRight: 10,
},
input: {
flex: 1,
fontFamily: 'Menlo-Regular',
borderColor: '#ccc',
borderRadius: 5,
margin: 10,
padding: 5,
},
});
@@ -27,29 +39,70 @@ const Input = ({
<State>
{({
text = '',
history = [],
historyOffset,
}, setState) => (
<View style={styles.container}>
<Button
onPress={() => {
let currentOffset = typeof historyOffset === 'undefined' ? -1 : historyOffset;
currentOffset += 1;
const index = history.length - 1 - currentOffset;
if (history[index]) {
setState({
text: history[index],
historyOffset: currentOffset,
});
}
}}
>
<Icon name="left" />
</Button>
<Button
title=">"
onPress={() => {
let currentOffset = typeof historyOffset === 'undefined' ? -1 : historyOffset;
currentOffset -= 1;
const index = history.length - 1 - currentOffset;
if (history[index]) {
setState({
text: history[index],
historyOffset: currentOffset,
});
}
}}
>
<Icon name="right" />
</Button>
<TextInput
multiline
placeholder="{your code here}"
autoCapitalize="none"
autoCorrect={false}
style={styles.input}
value={text}
onChangeText={text => setState({ text })}
/>
<Button
title="eval"
onPress={() => {
const fn = new Function(text);
const newHistory = [...history, text];
const contextKeys = Object.keys(log.context);
const contextValues = Object.values(log.context);
const fn = new Function(...contextKeys, text);
try {
const response = fn();
log.info([response]);
fn(...contextValues);
setState({
text: '',
history: newHistory,
historyOffset: undefined,
});
} catch (err) {
log.error([err]);
}
}}
/>
>
<Icon name="play" />
</Button>
</View>
)}
</State>

View File

@@ -73,13 +73,13 @@ const formatData = (data, options) => {
/>
);
} else {
return <Fixed>{data.toString()}</Fixed>;
return <Fixed selectable={true}>{data.toString()}</Fixed>;
}
}
if (typeof data === 'object') {
return <JSONTree data={data} />
}
return <Fixed>{data.toString()}</Fixed>;
return <Fixed selectable={true}>{data.toString()}</Fixed>;
}
const OutputList = ({

View File

@@ -1,10 +1,13 @@
import React from 'react';
import {
StyleSheet,
Clipboard,
Alert,
View,
Text,
} from 'react-native';
import log from '../../../log';
import Log from '../../data/Log';
import Toolbar from '../../base/Toolbar';
import Output from './Output';
import Input from './Input';
@@ -20,6 +23,22 @@ const Console = ({
<Log>
{({ logs }) => (
<View style={styles.container}>
<Toolbar
items={[{
name: 'Download',
icon: 'download',
onPress: () => {
Clipboard.setString(JSON.stringify(logs, null, ' '));
Alert.alert(
'Copied to clipboard',
);
},
}, {
name: 'Clear',
icon: 'trash',
onPress: () => log.clear(),
}]}
/>
<Output logs={logs} includeStackTrace={includeStackTrace} />
<Input />
</View>

View File

@@ -62,10 +62,9 @@ class Events extends Component {
behavior="padding"
enabled
>
<DevTool includeStackTrace={includeStackTrace} />
<Button
title="close"
onPress={() => {
<DevTool
includeStackTrace={includeStackTrace}
onClose={() => {
events.publish('HIDE_DEVTOOLS');
}}
/>

View File

@@ -13,6 +13,10 @@ import Cell from '../../base/Cell';
import CellHeader from '../../base/CellHeader';
import Tab from '../Tab';
const WebView = styled.WebView`
background: red;
`;
const theme = {
scheme: 'bright',
author: 'chris kempson (http://chriskempson.com)',
@@ -50,7 +54,7 @@ const getResponse = (request) => {
return <JSONTree theme={theme} data={data} />
}
return <Fixed>{request.responseText}</Fixed>;
return <Fixed selectable={true}>{request.responseText}</Fixed>;
}
const Data = ({
@@ -73,13 +77,13 @@ const Data = ({
{headerInfo && (
<Fragment>
<CellHeader>Request Headers</CellHeader>
<Indented><Fixed>{headerInfo}</Fixed></Indented>
<Indented><Fixed selectable={true}>{headerInfo}</Fixed></Indented>
</Fragment>
)}
{args[0] && (
<Fragment>
<CellHeader>Request Body</CellHeader>
<Fixed>{args[0].toString()}</Fixed>
<Fixed selectable={true}>{args[0].toString()}</Fixed>
</Fragment>
)}
</View>
@@ -98,6 +102,30 @@ const Response = ({
);
const getPreview = (request, url) => {
if (request.responseType == 'blob' || request.responseType == 'ArrayBuffer') {
return [];
}
const contentType = request.getResponseHeader('content-type');
const contentTypes = contentType.split(';').map(c => c.trim());
if (contentTypes.includes('text/html')) {
return [{
name: 'Preview',
view: (
<WebView
source={{
html: request.responseText,
baseUrl: url,
}}
style={{flex: 1}}
/>
),
}]
}
return [];
}
const RequestDetails = (props) => (
<Tab
tabs={[{
@@ -106,7 +134,7 @@ const RequestDetails = (props) => (
}, {
name: 'Response',
view: <Response {...props} />
}]}
}, ...getPreview(props.request, props.url)]}
/>
);

View File

@@ -8,6 +8,8 @@ import {
const ScrollView = styled.ScrollView`
flex: 1;
border-bottom-width: 1px;
border-color: #ccc;
`;
const Wrapper = styled.View`

View File

@@ -2,11 +2,15 @@ import React from 'react';
import {
StyleSheet,
View,
Clipboard,
Alert,
Text,
TouchableOpacity,
} from 'react-native';
import network from '../../../network';
import State from '../../data/State';
import Network from '../../data/Network';
import Toolbar from '../../base/Toolbar';
import Details from './Details';
import List from './List';
@@ -34,6 +38,22 @@ const Console = () => (
const selected = active >= 0 ? requests[active] : undefined;
return (
<View style={styles.container}>
<Toolbar
items={[{
name: 'Download',
icon: 'download',
onPress: () => {
Clipboard.setString(JSON.stringify(requests, null, ' '));
Alert.alert(
'Copied to clipboard',
);
},
}, {
name: 'Clear',
icon: 'trash',
onPress: () => network.clear(),
}]}
/>
<List
selected={selected ? selected.id : undefined}
requests={requests}

View File

@@ -0,0 +1,41 @@
import React from 'react';
import styled from 'styled-components/native';
import Row from '../../base/Row';
import {
Body,
} from '../../base/text';
const Scroll = styled.ScrollView`
flex: 1;
`;
const Wrapper = styled.View`
`;
const Button = styled.TouchableOpacity`
`;
const Keys = ({
keys,
selected,
onSelect,
}) => (
<Scroll>
<Wrapper>
{keys.map(key => (
<Button
key={key}
onPress={() => onSelect(key)}
>
<Row
selected={selected === key}
>
<Body>{key}</Body>
</Row>
</Button>
))}
</Wrapper>
</Scroll>
)
export default Keys;

View File

@@ -0,0 +1,27 @@
import React from 'react';
import styled from 'styled-components/native';
import {
Fixed,
} from '../../base/text';
const Scroll = styled.ScrollView`
flex: 1;
border-top-width: 1px;
border-color: #ccc;
`;
const Wrapper = styled.View`
padding: 8px 16px;
`;
const Value = ({
value,
}) => (
<Scroll>
<Wrapper>
<Fixed selectable={true}>{value}</Fixed>
</Wrapper>
</Scroll>
)
export default Value;

View File

@@ -0,0 +1,64 @@
import React from 'react';
import styled from 'styled-components/native';
import {
Clipboard,
Alert,
} from 'react-native';
import Storage from '../../data/Storage';
import State from '../../data/State';
import Toolbar from '../../base/Toolbar';
import Keys from './Keys';
import Value from './Value';
const Wrapper = styled.View`
flex: 1;
`;
const StorageView = ({
}) => (
<State>
{({ selected }, setState) => (
<Storage>
{(data, update, removeItem, clear) => (
<Wrapper>
<Toolbar
items={[{
name: 'Download',
icon: 'download',
onPress: () => {
Clipboard.setString(JSON.stringify(data, null, ' '));
Alert.alert(
'Copied to clipboard',
);
},
}, {
name: 'Refresh',
icon: 'reload',
onPress: update,
}, {
name: 'Clear',
icon: 'trash',
onPress: clear,
}, {
name: 'Delete',
icon: 'remove',
disabled: !selected,
onPress: () => removeItem(selected),
}]}
/>
<Keys
selected={selected}
onSelect={(key) => setState({ selected: key })}
keys={Object.keys(data)}
/>
{selected && data[selected] && (
<Value value={data[selected]} />
)}
</Wrapper>
)}
</Storage>
)}
</State>
);
export default StorageView;

View File

@@ -1,4 +1,5 @@
import React, { Fragment } from 'react';
import styled from 'styled-components/native';
import PropTypes from 'prop-types';
import {
Text,
@@ -7,6 +8,7 @@ import {
View,
} from 'react-native';
import State from '../data/State';
import Icon from '../base/Icon';
const styles = StyleSheet.create({
container: {
@@ -15,14 +17,15 @@ const styles = StyleSheet.create({
},
tabs: {
flexDirection: 'row',
alignItems: 'center',
borderBottomWidth: 1,
borderColor: '#ccc',
},
tabInactive: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
padding: 10,
borderBottomWidth: 1,
borderColor: '#ccc',
},
tabActive: {
flex: 1,
@@ -34,8 +37,14 @@ const styles = StyleSheet.create({
},
});
const Button = styled.TouchableOpacity`
padding: 10px 20px 10px 0;
`;
const Console = ({
tabs,
buttons = [],
onClose,
}) => (
<View style={styles.container}>
<State
@@ -57,6 +66,14 @@ const Console = ({
<Text>{name}</Text>
</TouchableOpacity>
))}
{onClose && (
<Button
onPress={onClose}
>
<Icon name="close" />
</Button>
)}
</View>
{tabs[active] && tabs[active].view}
</Fragment>

View File

@@ -6,6 +6,7 @@ import {
import Tab from './Tab';
import Console from './Console';
import Requests from './Requests';
import Storage from './Storage';
const styles = StyleSheet.create({
container: {
@@ -15,7 +16,8 @@ const styles = StyleSheet.create({
const DevTool = ({
style,
includeStackTrace
includeStackTrace,
onClose,
}) => (
<View style={style || styles.container}>
<Tab
@@ -25,7 +27,11 @@ const DevTool = ({
}, {
name: 'Network',
view: <Requests />,
}, {
name: 'Storage',
view: <Storage />,
}]}
onClose={onClose}
/>
</View>
);

View File

@@ -22,7 +22,7 @@ const Row = ({
<Left>
<Emphasis>{left}:</Emphasis>
</Left>
<Body>{right}</Body>
<Body selectable={true}>{right}</Body>
</Wrapper>
)

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

View File

@@ -0,0 +1,41 @@
import React from 'react';
import styled from 'styled-components/native';
import reload from './reload.png';
import trash from './trash.png';
import remove from './return.png';
import right from './right.png';
import left from './left.png';
import play from './play.png';
import download from './download.png';
import close from './close.png';
const icons = {
reload,
trash,
remove,
right,
left,
play,
download,
close,
}
const Image = styled.Image`
height: ${({ height }) => height || '16'}px;
width: ${({ width }) => width || '16'}px;
`;
const Icon = ({
name,
width,
height
}) => (
<Image
width={width}
height={height}
source={icons[name]}
/>
);
export default Icon;

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

View File

@@ -5,7 +5,9 @@ const Wrapper = styled.View`
flex-direction: row;
padding: 10px;
border-left-width: ${props => props.selected ? '10px' : '0'};
border-color: #2980b9;
border-left-color: #2980b9;
border-bottom-width: 1px;
border-bottom-color: #efefef;
`;
const Left = styled.View`

View File

@@ -0,0 +1,47 @@
import React from 'react';
import styled from 'styled-components/native';
import Icon from './Icon';
import {
Body,
} from './text';
const Wrapper = styled.View`
flex-direction: row;
justify-content: flex-end;
align-items: center;
border-bottom-width: 1px;
border-color: #ccc;
padding: 0 10px;
`;
const Item = styled.TouchableOpacity`
padding: 10px 10px;
opacity: ${({ disabled }) => disabled ? 0.3 : 1};
`
const Toolbar = ({
items = [],
}) => (
<Wrapper>
{items.map(({
name,
icon,
onPress,
disabled,
}) => (
<Item
key={name}
onPress={disabled ? undefined : onPress}
disabled={disabled}
>
{icon ? (
<Icon name={icon} />
) : (
<Body color={disabled ? '#ccc' : undefined}>{name}</Body>
)}
</Item>
))}
</Wrapper>
)
export default Toolbar;

View File

@@ -0,0 +1,58 @@
import { Component } from 'react';
import { AsyncStorage } from 'react-native';
class Storage extends Component {
constructor() {
super();
this.state = {
data: {},
};
this.update = this.update.bind(this);
this.removeItem = this.removeItem.bind(this);
this.clear = this.clear.bind(this);
}
componentDidMount() {
this.update();
}
async removeItem(name) {
await AsyncStorage.removeItem(name);
await this.update();
}
async clear() {
await AsyncStorage.clear();
await this.update();
}
async update() {
try {
const keys = await AsyncStorage.getAllKeys();
const values = await Promise.all(keys.map(key => AsyncStorage.getItem(key)));
const data = {};
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
const value = values[i];
data[key] = value;
}
this.setState({
data,
});
} catch (err) {
console.error(err);
}
}
render() {
const {
children,
} = this.props;
const {
data,
} = this.state;
return children(data, this.update, this.removeItem, this.clear);
}
}
export default Storage;

View File

@@ -1,9 +1,16 @@
const proxyConsole = window.console;
import { AsyncStorage } from 'react-native';
export const proxyConsole = window.console;
class Log {
constructor() {
this.logs = [];
this.listeners = [];
this.context = {
storage: AsyncStorage,
log: (...args) => console.log(...args),
clear: this.clear.bind(this),
};
}
listen(fn) {
@@ -15,6 +22,11 @@ class Log {
this.listeners = this.listeners.filter(l => l !== fn);
}
clear() {
this.logs = [];
this.listeners.forEach(l => l(this.logs));
}
log(type, data, keep) {
const entry = {
type,

View File

@@ -16,6 +16,11 @@ class Network {
this.listeners = this.listeners.filter(l => l !== fn);
}
clear() {
this.requests = [];
this.listeners.forEach(l => l(this.requests));
}
addRequest(request) {
this.requests.push({
id: this.currentId++,