This commit is contained in:
2018-08-22 23:44:26 +02:00
parent 7e635595f3
commit c8f41d45ea
24 changed files with 352 additions and 163 deletions

View File

@@ -14,10 +14,12 @@ import {
log,
network,
show,
context,
} from 'react-native-debug-console';
network.attach();
log.attach();
context.hello = () => 'earth';
console.log('fooo');
let xhr = new XMLHttpRequest();
@@ -105,11 +107,11 @@ export default class App extends React.Component {
/>
<View style={{ height: 10 }} />
</View>
<DevTool
{/* <DevTool
style={{
flex: 1,
}}
/>
/> */}
<DevToolModal />
</KeyboardAvoidingView>
</SafeAreaView>

View File

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

View File

@@ -7,6 +7,7 @@ import {
} from 'react-native';
import styled from 'styled-components/native';
import State from '../../data/State';
import { context } from '../../../console';
import log from '../../../log';
import Icon from '../../base/Icon';
@@ -86,8 +87,8 @@ const Input = ({
<Button
onPress={() => {
const newHistory = [...history, text];
const contextKeys = Object.keys(log.context);
const contextValues = Object.values(log.context);
const contextKeys = Object.keys(context);
const contextValues = Object.values(context);
const fn = new Function(...contextKeys, text);
try {
fn(...contextValues);

View File

@@ -101,6 +101,7 @@ const OutputList = ({
const Console = ({
logs,
includeStackTrace,
filter = [],
}) => (
<ScrollView
ref={ref => this.scrollView = ref}
@@ -109,7 +110,7 @@ const Console = ({
}}
>
<Wrapper>
{logs.map((log, i) => (
{logs.filter(l => filter.includes(l.type)).map((log, i) => (
<Row key={i}>
<Emphasis color={getColor(log.type)}>
{log.type}

View File

@@ -1,13 +1,16 @@
import React from 'react';
import {
StyleSheet,
Clipboard,
Alert,
View,
} from 'react-native';
import log from '../../../log';
import Log from '../../data/Log';
import Toolbar from '../../base/Toolbar';
import State from '../../data/State';
import Toolbar, {
Button,
Selector,
Seperator,
} from '../../base/Toolbar';
import Output from './Output';
import Input from './Input';
@@ -17,31 +20,53 @@ const styles = StyleSheet.create({
},
});
const initFilters = [
'error',
'warn',
'info',
'debug',
].map(i => ({
name: i,
value: i,
selected: true,
}))
const Console = ({
includeStackTrace,
}) => (
<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>
<State
initState={{
filters: initFilters,
}}
>
{({ filters }, setState) => (
<View style={styles.container}>
<Toolbar>
<Selector
name="Filter"
icon="filter"
options={filters}
multiSelect
onSelect={(selected) => {
setState({
filters: selected,
});
}}
/>
<Seperator />
<Button
name="Clear"
icon="trash"
onPress={() => log.clear()}
/>
</Toolbar>
<Output filter={filters.filter(f => f.selected).map(f => f.name)} logs={logs} includeStackTrace={includeStackTrace} />
<Input />
</View>
)}
</State>
)}
</Log>
);

View File

@@ -14,7 +14,6 @@ import CellHeader from '../../base/CellHeader';
import Tab from '../Tab';
const WebView = styled.WebView`
background: red;
`;
const theme = {
@@ -42,11 +41,10 @@ const Indented = styled.View`
margin: 0 25px;
`;
const getResponse = (request) => {
const getResponse = (contentType, request) => {
if (request.responseType == 'blob' || request.responseType == 'ArrayBuffer') {
return <Emphasis>🤖 Binary</Emphasis>
}
const contentType = request.getResponseHeader('content-type') || '';
const contentTypes = contentType.split(';').map(c => c.trim());
if (contentTypes.includes('application/json')) {
@@ -57,12 +55,13 @@ const getResponse = (request) => {
return <Fixed selectable={true}>{request.responseText}</Fixed>;
}
let i = 0;
const Data = ({
url,
method,
status,
headers,
request,
requestHeaders,
args = [],
}) => {
const headerInfo = Object.keys(headers).map(key => `${key}: ${headers[key]}`).join('\n');
@@ -73,7 +72,7 @@ const Data = ({
<Cell left="Method" right={method} />
<Cell left="Url" right={url} />
<CellHeader>Response Headers</CellHeader>
<Indented><Fixed>{request.getAllResponseHeaders()}</Fixed></Indented>
<Indented><Fixed>{requestHeaders}</Fixed></Indented>
{headerInfo && (
<Fragment>
<CellHeader>Request Headers</CellHeader>
@@ -92,21 +91,21 @@ const Data = ({
};
const Response = ({
contentType,
request,
}) => (
<ScrollView>
<View>
{getResponse(request)}
{getResponse(contentType, request)}
</View>
</ScrollView>
);
const getPreview = (request, url) => {
const getPreview = (contentType, 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')) {
@@ -134,7 +133,7 @@ const RequestDetails = (props) => (
}, {
name: 'Response',
view: <Response {...props} />
}, ...getPreview(props.request, props.url)]}
}, ...getPreview(props.contentType, props.request, props.url)]}
/>
);

View File

@@ -2,15 +2,14 @@ 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 Toolbar, {
Button,
Seperator,
} from '../../base/Toolbar';
import Details from './Details';
import List from './List';
@@ -27,7 +26,7 @@ const styles = StyleSheet.create({
flex: 1,
},
});
let i = 0;
const Console = () => (
<State>
{({
@@ -38,22 +37,14 @@ 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(),
}]}
/>
<Toolbar>
<Seperator />
<Button
name="Clear"
icon="trash"
onPress={() => network.clear()}
/>
</Toolbar>
<List
selected={selected ? selected.id : undefined}
requests={requests}

View File

@@ -24,7 +24,7 @@ const Keys = ({
<Wrapper>
{keys.map(key => (
<Button
key={key}
key={key || '[unknown]'}
onPress={() => onSelect(key)}
>
<Row

View File

@@ -1,12 +1,11 @@
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 Toolbar, {
Button,
Seperator,
} from '../../base/Toolbar';
import Keys from './Keys';
import Value from './Value';
@@ -20,31 +19,25 @@ const StorageView = () => (
<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),
}]}
/>
<Toolbar>
<Seperator />
<Button
name="Refresh"
icon="reload"
onPress={update}
/>
<Button
name="Clear"
icon="trash"
onPress={clear}
/>
<Button
name="Delete"
icon="remove"
disabled={!selected}
onPress={() => removeItem(selected)}
/>
</Toolbar>
<Keys
selected={selected}
onSelect={(key) => setState({ selected: key })}

View File

@@ -45,6 +45,7 @@ const Console = ({
tabs,
buttons = [],
onClose,
onDownload,
}) => (
<View style={styles.container}>
<State
@@ -66,7 +67,13 @@ const Console = ({
<Text>{name}</Text>
</TouchableOpacity>
))}
{onDownload && (
<Button
onPress={onDownload}
>
<Icon name="download" />
</Button>
)}
{onClose && (
<Button
onPress={onClose}

View File

@@ -2,11 +2,15 @@ import React from 'react';
import {
StyleSheet,
View,
Alert,
Clipboard,
} from 'react-native';
import Tab from './Tab';
import Console from './Console';
import Requests from './Requests';
import Storage from './Storage';
import log from '../../log';
import network from '../../network';
const styles = StyleSheet.create({
container: {
@@ -18,22 +22,65 @@ const DevTool = ({
style,
includeStackTrace,
onClose,
}) => (
<View style={style || styles.container}>
<Tab
tabs={[{
name: 'Console',
view: <Console includeStackTrace={includeStackTrace} />,
}, {
name: 'Network',
view: <Requests />,
}, {
name: 'Storage',
view: <Storage />,
}]}
onClose={onClose}
/>
</View>
);
showNetwork = true,
showStorage = true,
showConsole = true,
additionTools = [],
}) => {
const views = [];
if (showConsole) {
views.push({
name: 'Console',
view: <Console includeStackTrace={includeStackTrace} />,
getData: log.get,
});
}
if (showNetwork) {
views.push({
name: 'Network',
view: <Requests />,
getData: network.get,
});
}
if (showStorage) {
views.push({
name: 'Storage',
view: <Storage />,
});
}
additionTools.forEach(tool => {
views.push(tool);
});
const getData = async () => {
const result = {};
for (let i = 0; i < views.length; i++) {
const view = views[i];
if (view.getData) {
result[view.name] = await view.getData();
}
};
Clipboard.setString(JSON.stringify(result, null, ' '));
Alert.alert(
'Copied to clipboard',
);
}
return (
<View style={style || styles.container}>
<Tab
tabs={views}
onClose={onClose}
onDownload={getData}
/>
</View>
);
};
export default DevTool;

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

View File

@@ -9,6 +9,9 @@ import left from './left.png';
import play from './play.png';
import download from './download.png';
import close from './close.png';
import filter from './filter.png';
import square from './square.png';
import check from './check.png';
const icons = {
reload,
@@ -19,6 +22,9 @@ const icons = {
play,
download,
close,
filter,
square,
check,
}
const Image = styled.Image`

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

@@ -1,47 +0,0 @@
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,31 @@
import React from 'react';
import styled from 'styled-components/native';
import Icon from '../Icon';
import {
Body,
} from '../text';
const Item = styled.TouchableOpacity`
padding: 10px 10px;
opacity: ${({ disabled }) => disabled ? 0.3 : 1};
`;
const Button = ({
name,
icon,
onPress,
disabled,
}) => (
<Item
onPress={disabled ? undefined : onPress}
disabled={disabled}
>
{icon ? (
<Icon name={icon} />
) : (
<Body color={disabled ? '#ccc' : undefined}>{name}</Body>
)}
</Item>
);
export default Button;

View File

@@ -0,0 +1,78 @@
import React, { Fragment } from 'react';
import {
SafeAreaView,
TouchableOpacity,
} from 'react-native';
import styled from 'styled-components/native';
import State from '../../data/State';
import Button from './Button';
import Row from '../Row';
import Icon from '../Icon';
import {
Body,
} from '../text';
const Modal = styled.Modal`
`;
const Selector = ({
...others,
multiSelect = false,
onSelect,
options = [],
}) => (
<State>
{({ open }, setState) => (
<Fragment>
<Button
{...others}
onPress={() => {
setState({ open: true })
}}
/>
<Modal
animationType="slide"
transparent={false}
visible={!!open}
onRequestClose={() => {
setState({ open: false })
}}
>
<SafeAreaView
forceInset={{ top: 'always', vertical: 'always' }}
style={{flex: 1}}
>
<Button
name="Close"
icon="close"
onPress={() => {
setState({ open: false })
}}
/>
{options.map((option) => (
<TouchableOpacity
onPress={() => {
if (!multiSelect) {
options.forEach(o => o.selected = false);
}
option.selected = !option.selected
onSelect(options);
}}
>
<Row
left={(
<Icon name={option.selected ? 'check' : 'square'} />
)}
>
<Body>{option.name}</Body>
</Row>
</TouchableOpacity>
))}
</SafeAreaView>
</Modal>
</Fragment>
)}
</State>
);
export default Selector;

View File

@@ -0,0 +1,7 @@
import styled from 'styled-components/native';
const Seperator = styled.View`
flex: 1;
`;
export default Seperator;

View File

@@ -0,0 +1,29 @@
import React from 'react';
import styled from 'styled-components/native';
import Button from './Button';
import Seperator from './Seperator';
import Selector from './Selector';
export {
Button,
Seperator,
Selector,
}
const Wrapper = styled.View`
flex-direction: row;
align-items: center;
border-bottom-width: 1px;
border-color: #ccc;
padding: 0 10px;
`;
const Toolbar = ({
children,
}) => (
<Wrapper>
{children}
</Wrapper>
)
export default Toolbar;

8
lib/src/console.js Normal file
View File

@@ -0,0 +1,8 @@
import log from './log';
import { AsyncStorage } from 'react-native';
export const context = {
storage: AsyncStorage,
log: (...args) => console.log(...args),
clear: log.clear,
};

View File

@@ -3,6 +3,7 @@ import DevToolModal from './components/DevTool/Modal';
import log from './log';
import network from './network';
import events from './events';
import { context } from './console';
const show = () => events.publish('SHOW_DEVTOOLS');
const hide = () => events.publish('HIDE_DEVTOOLS');
@@ -12,6 +13,7 @@ export {
DevToolModal,
log,
network,
context,
show,
hide,
};

View File

@@ -1,16 +1,11 @@
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),
};
this.clear = this.clear.bind(this);
this.get = this.get.bind(this);
}
listen(fn) {
@@ -27,6 +22,10 @@ class Log {
this.listeners.forEach(l => l(this.logs));
}
get() {
return this.logs;
}
log(type, data, keep) {
const entry = {
type,
@@ -67,6 +66,7 @@ class Log {
info: (...data) => this.info(data, keep),
log: (...data) => this.info(data, keep),
debug: (...data) => this.debug(data, keep),
verbose: (...data) => this.debug(data, keep),
};
ErrorUtils.setGlobalHandler((err, fatal) => {
this.error([err], keep);

View File

@@ -5,6 +5,8 @@ class Network {
this.requests = [];
this.listeners = [];
this.currentId = 0;
// this.clear = this.clear.bind(this);
this.get = this.get.bind(this);
}
listen(fn) {
@@ -12,6 +14,10 @@ class Network {
fn(this.requests);
}
get() {
return this.requests;
}
unlisten(fn) {
this.listeners = this.listeners.filter(l => l !== fn);
}
@@ -44,6 +50,8 @@ class Network {
method,
args: sendArgs,
headers,
requestHeaders: this.getAllResponseHeaders(),
contentType: this.getResponseHeader('content-type') || '',
request: this,
status: this.status,
});
@@ -55,6 +63,7 @@ class Network {
error,
args: sendArgs,
headers,
requestHeaders: this.getAllResponseHeaders(),
request: this,
status: this.status || 'CONN ERR',
});